From a1d6a062a951419d41e1837c2a312c9c490bea69 Mon Sep 17 00:00:00 2001 From: YassineYousfi Date: Fri, 12 Sep 2025 12:44:11 -0700 Subject: [PATCH 001/910] add PR ref to new driving model in RELEASES.md --- RELEASES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASES.md b/RELEASES.md index 189aa7ad5..7c0b967ab 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,6 +1,6 @@ Version 0.10.1 (2025-09-08) ======================== -* New driving model +* New driving model #36087 * World Model: removed global localization inputs * World Model: 2x the number of parameters * World Model: trained on 4x the number of segments From 3ca9f351a047f5a7fa93bc8a5d50d42301beca31 Mon Sep 17 00:00:00 2001 From: Armand du Parc Locmaria Date: Fri, 12 Sep 2025 12:45:52 -0700 Subject: [PATCH 002/910] =?UTF-8?q?nevada=20model=20=F0=9F=8C=B5=20(#36114?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cd29ffcf-01dd-4f1c-8808-dc197c174f1d --- selfdrive/modeld/models/driving_policy.onnx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/modeld/models/driving_policy.onnx b/selfdrive/modeld/models/driving_policy.onnx index 7b8784674..cb5a6b3a4 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:ebb38a934d6472c061cc6010f46d9720ca132d631a47e585a893bdd41ade2419 -size 12343535 +oid sha256:5b7c686e13637733ec432d774ff5b665c4e4663982ff03deeefd6f6ffd511741 +size 12343531 From 42d9bd05162505b3975ab3eaec54d1b658db27e2 Mon Sep 17 00:00:00 2001 From: Jimmy <9859727+Quantizr@users.noreply.github.com> Date: Fri, 12 Sep 2025 11:36:50 -1000 Subject: [PATCH 003/910] jotpluggler: sync x axes and autofit y axis (#36143) * sync x axes of all timeseries plots * always autofit y-axis * fix typing --- tools/jotpluggler/pluggle.py | 31 ++++++++++++++++++ tools/jotpluggler/views.py | 63 +++++++++++++++++++++++++++++++++--- 2 files changed, 90 insertions(+), 4 deletions(-) diff --git a/tools/jotpluggler/pluggle.py b/tools/jotpluggler/pluggle.py index 582a44454..57ba2a245 100755 --- a/tools/jotpluggler/pluggle.py +++ b/tools/jotpluggler/pluggle.py @@ -65,6 +65,10 @@ class PlaybackManager: self.current_time_s = 0.0 self.duration_s = 0.0 + self.x_axis_bounds = (0.0, 0.0) # (min_time, max_time) + self.x_axis_observers = [] # callbacks for x-axis changes + self._updating_x_axis = False + def set_route_duration(self, duration: float): self.duration_s = duration self.seek(min(self.current_time_s, duration)) @@ -87,6 +91,33 @@ class PlaybackManager: dpg.configure_item("play_pause_button", texture_tag="play_texture") return self.current_time_s + def set_x_axis_bounds(self, min_time: float, max_time: float, source_panel=None): + if self._updating_x_axis: + return + + new_bounds = (min_time, max_time) + if new_bounds == self.x_axis_bounds: + return + + self.x_axis_bounds = new_bounds + self._updating_x_axis = True # prevent recursive updates + + try: + for callback in self.x_axis_observers: + try: + callback(min_time, max_time, source_panel) + except Exception as e: + print(f"Error in x-axis sync callback: {e}") + finally: + self._updating_x_axis = False + + def add_x_axis_observer(self, callback): + if callback not in self.x_axis_observers: + self.x_axis_observers.append(callback) + + def remove_x_axis_observer(self, callback): + if callback in self.x_axis_observers: + self.x_axis_observers.remove(callback) class MainController: def __init__(self, scale: float = 1.0): diff --git a/tools/jotpluggler/views.py b/tools/jotpluggler/views.py index 4af9a102a..f3da6e3e6 100644 --- a/tools/jotpluggler/views.py +++ b/tools/jotpluggler/views.py @@ -51,9 +51,11 @@ class TimeSeriesPanel(ViewPanel): self._update_lock = threading.RLock() self._results_deque: deque[tuple[str, list, list]] = deque() self._new_data = False + self._last_x_limits = (0.0, 0.0) def create_ui(self, parent_tag: str): self.data_manager.add_observer(self.on_data_loaded) + self.playback_manager.add_x_axis_observer(self._on_x_axis_sync) with dpg.plot(height=-1, width=-1, tag=self.plot_tag, parent=parent_tag, drop_callback=self._on_series_drop, payload_type="TIMESERIES_PAYLOAD"): dpg.add_plot_legend() dpg.add_plot_axis(dpg.mvXAxis, no_label=True, tag=self.x_axis_tag) @@ -70,8 +72,20 @@ class TimeSeriesPanel(ViewPanel): if not self._ui_created: return + current_limits = dpg.get_axis_limits(self.x_axis_tag) + # downsample if plot zoom changed significantly + plot_duration = current_limits[1] - current_limits[0] + if plot_duration > self._last_plot_duration * 2 or plot_duration < self._last_plot_duration * 0.5: + self._downsample_all_series(plot_duration) + # sync x-axis if changed by user + if self._last_x_limits != current_limits: + self.playback_manager.set_x_axis_bounds(current_limits[0], current_limits[1], source_panel=self) + self._last_x_limits = current_limits + self._fit_y_axis(current_limits[0], current_limits[1]) + if self._new_data: # handle new data in main thread self._new_data = False + dpg.set_axis_limits_constraints(self.x_axis_tag, -10, (self.playback_manager.duration_s + 10)) for series_path in list(self._series_data.keys()): self.add_series(series_path, update=True) @@ -96,10 +110,50 @@ class TimeSeriesPanel(ViewPanel): if dpg.does_item_exist(series_tag): dpg.configure_item(series_tag, label=f"{series_path}: {formatted_value}") - # downsample if plot zoom changed significantly - plot_duration = dpg.get_axis_limits(self.x_axis_tag)[1] - dpg.get_axis_limits(self.x_axis_tag)[0] - if plot_duration > self._last_plot_duration * 2 or plot_duration < self._last_plot_duration * 0.5: - self._downsample_all_series(plot_duration) + def _on_x_axis_sync(self, min_time: float, max_time: float, source_panel): + with self._update_lock: + if source_panel == self or not self._ui_created: + return + dpg.set_axis_limits(self.x_axis_tag, min_time, max_time) + dpg.render_dearpygui_frame() + dpg.set_axis_limits_auto(self.x_axis_tag) + self._last_x_limits = (min_time, max_time) + self._fit_y_axis(min_time, max_time) + + def _fit_y_axis(self, x_min: float, x_max: float): + if not self._series_data: + dpg.set_axis_limits(self.y_axis_tag, -1, 1) + return + + global_min = float('inf') + global_max = float('-inf') + found_data = False + + for time_array, value_array in self._series_data.values(): + if len(time_array) == 0: + continue + start_idx, end_idx = np.searchsorted(time_array, [x_min, x_max]) + end_idx = min(end_idx, len(time_array) - 1) + if start_idx <= end_idx: + y_slice = value_array[start_idx:end_idx + 1] + series_min, series_max = np.min(y_slice), np.max(y_slice) + global_min = min(global_min, series_min) + global_max = max(global_max, series_max) + found_data = True + + if not found_data: + dpg.set_axis_limits(self.y_axis_tag, -1, 1) + return + + if global_min == global_max: + padding = max(abs(global_min) * 0.1, 1.0) + y_min, y_max = global_min - padding, global_max + padding + else: + range_size = global_max - global_min + padding = range_size * 0.1 + y_min, y_max = global_min - padding, global_max + padding + + dpg.set_axis_limits(self.y_axis_tag, y_min, y_max) def _downsample_all_series(self, plot_duration): plot_width = dpg.get_item_rect_size(self.plot_tag)[0] @@ -145,6 +199,7 @@ class TimeSeriesPanel(ViewPanel): def destroy_ui(self): with self._update_lock: self.data_manager.remove_observer(self.on_data_loaded) + self.playback_manager.remove_x_axis_observer(self._on_x_axis_sync) if dpg.does_item_exist(self.plot_tag): dpg.delete_item(self.plot_tag) self._ui_created = False From be379e188b004643edb5d1758eaf4b28e35549b6 Mon Sep 17 00:00:00 2001 From: Jimmy <9859727+Quantizr@users.noreply.github.com> Date: Fri, 12 Sep 2025 11:37:00 -1000 Subject: [PATCH 004/910] jotpluggler: fix off by one error (#36144) fix off by one error sometimes causing missed items in datatree --- tools/jotpluggler/datatree.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/jotpluggler/datatree.py b/tools/jotpluggler/datatree.py index 3390fed2e..eb4e4ef58 100644 --- a/tools/jotpluggler/datatree.py +++ b/tools/jotpluggler/datatree.py @@ -154,7 +154,7 @@ class DataTree: for i, part in enumerate(parts): current_path_prefix = f"{current_path_prefix}/{part}" if current_path_prefix else part - if i < len(parts) - 1: + if i < len(parts): parent_nodes_to_recheck.add(current_node) # for incremental changes from new data if part not in current_node.children: current_node.children[part] = DataTreeNode(name=part, full_path=current_path_prefix, parent=current_node) From cbea5f198fbe338850ba6155fdfe7bf905ae2029 Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Fri, 12 Sep 2025 15:34:28 -0700 Subject: [PATCH 005/910] op.sh: more robust switch for submodules --- tools/op.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/op.sh b/tools/op.sh index 54ff8e97e..ae12809eb 100755 --- a/tools/op.sh +++ b/tools/op.sh @@ -366,9 +366,11 @@ function op_switch() { BRANCH="$1" git config --replace-all remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*" + git submodule deinit --all --force git fetch "$REMOTE" "$BRANCH" git checkout -f FETCH_HEAD git checkout -B "$BRANCH" --track "$REMOTE"/"$BRANCH" + git submodule deinit --all --force git reset --hard "${REMOTE}/${BRANCH}" git clean -df git submodule update --init --recursive From 347b23055d03e3af878d7abbd04ca3296f5c1cc2 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 12 Sep 2025 18:59:15 -0700 Subject: [PATCH 006/910] minimal ffmpeg build (#36138) * min ffmpeg * remove avfilter * x264 * merge x264 * simpler * pin x264 * mac * rm that * lil more * move includes to lfs * try this * cleanup * larch --------- Co-authored-by: Comma Device --- .gitattributes | 1 + SConstruct | 4 + system/loggerd/SConscript | 4 +- system/loggerd/tests/test_loggerd.py | 14 +++- third_party/.gitignore | 2 + third_party/ffmpeg/Darwin/lib/libavcodec.a | 3 + third_party/ffmpeg/Darwin/lib/libavformat.a | 3 + third_party/ffmpeg/Darwin/lib/libavutil.a | 3 + third_party/ffmpeg/Darwin/lib/libx264.a | 3 + third_party/ffmpeg/build.sh | 83 +++++++++++++++++++ .../ffmpeg/include/libavcodec/ac3_parser.h | 3 + .../ffmpeg/include/libavcodec/adts_parser.h | 3 + .../ffmpeg/include/libavcodec/avcodec.h | 3 + third_party/ffmpeg/include/libavcodec/avdct.h | 3 + third_party/ffmpeg/include/libavcodec/avfft.h | 3 + third_party/ffmpeg/include/libavcodec/bsf.h | 3 + third_party/ffmpeg/include/libavcodec/codec.h | 3 + .../ffmpeg/include/libavcodec/codec_desc.h | 3 + .../ffmpeg/include/libavcodec/codec_id.h | 3 + .../ffmpeg/include/libavcodec/codec_par.h | 3 + .../ffmpeg/include/libavcodec/d3d11va.h | 3 + third_party/ffmpeg/include/libavcodec/defs.h | 3 + third_party/ffmpeg/include/libavcodec/dirac.h | 3 + .../ffmpeg/include/libavcodec/dv_profile.h | 3 + third_party/ffmpeg/include/libavcodec/dxva2.h | 3 + third_party/ffmpeg/include/libavcodec/jni.h | 3 + .../ffmpeg/include/libavcodec/mediacodec.h | 3 + .../ffmpeg/include/libavcodec/packet.h | 3 + third_party/ffmpeg/include/libavcodec/qsv.h | 3 + third_party/ffmpeg/include/libavcodec/vdpau.h | 3 + .../ffmpeg/include/libavcodec/version.h | 3 + .../ffmpeg/include/libavcodec/version_major.h | 3 + .../ffmpeg/include/libavcodec/videotoolbox.h | 3 + .../ffmpeg/include/libavcodec/vorbis_parser.h | 3 + third_party/ffmpeg/include/libavcodec/xvmc.h | 3 + .../ffmpeg/include/libavformat/avformat.h | 3 + third_party/ffmpeg/include/libavformat/avio.h | 3 + .../ffmpeg/include/libavformat/version.h | 3 + .../include/libavformat/version_major.h | 3 + .../ffmpeg/include/libavutil/adler32.h | 3 + third_party/ffmpeg/include/libavutil/aes.h | 3 + .../ffmpeg/include/libavutil/aes_ctr.h | 3 + .../libavutil/ambient_viewing_environment.h | 3 + .../ffmpeg/include/libavutil/attributes.h | 3 + .../ffmpeg/include/libavutil/audio_fifo.h | 3 + .../ffmpeg/include/libavutil/avassert.h | 3 + .../ffmpeg/include/libavutil/avconfig.h | 3 + .../ffmpeg/include/libavutil/avstring.h | 3 + third_party/ffmpeg/include/libavutil/avutil.h | 3 + third_party/ffmpeg/include/libavutil/base64.h | 3 + .../ffmpeg/include/libavutil/blowfish.h | 3 + third_party/ffmpeg/include/libavutil/bprint.h | 3 + third_party/ffmpeg/include/libavutil/bswap.h | 3 + third_party/ffmpeg/include/libavutil/buffer.h | 3 + .../ffmpeg/include/libavutil/camellia.h | 3 + third_party/ffmpeg/include/libavutil/cast5.h | 3 + .../ffmpeg/include/libavutil/channel_layout.h | 3 + third_party/ffmpeg/include/libavutil/common.h | 3 + third_party/ffmpeg/include/libavutil/cpu.h | 3 + third_party/ffmpeg/include/libavutil/crc.h | 3 + third_party/ffmpeg/include/libavutil/csp.h | 3 + third_party/ffmpeg/include/libavutil/des.h | 3 + .../ffmpeg/include/libavutil/detection_bbox.h | 3 + third_party/ffmpeg/include/libavutil/dict.h | 3 + .../ffmpeg/include/libavutil/display.h | 3 + .../ffmpeg/include/libavutil/dovi_meta.h | 3 + .../ffmpeg/include/libavutil/downmix_info.h | 3 + .../include/libavutil/encryption_info.h | 3 + third_party/ffmpeg/include/libavutil/error.h | 3 + third_party/ffmpeg/include/libavutil/eval.h | 3 + .../ffmpeg/include/libavutil/executor.h | 3 + .../ffmpeg/include/libavutil/ffversion.h | 3 + third_party/ffmpeg/include/libavutil/fifo.h | 3 + third_party/ffmpeg/include/libavutil/file.h | 3 + .../include/libavutil/film_grain_params.h | 3 + third_party/ffmpeg/include/libavutil/frame.h | 3 + third_party/ffmpeg/include/libavutil/hash.h | 3 + .../include/libavutil/hdr_dynamic_metadata.h | 3 + .../libavutil/hdr_dynamic_vivid_metadata.h | 3 + third_party/ffmpeg/include/libavutil/hmac.h | 3 + .../ffmpeg/include/libavutil/hwcontext.h | 3 + .../ffmpeg/include/libavutil/hwcontext_cuda.h | 3 + .../include/libavutil/hwcontext_d3d11va.h | 3 + .../ffmpeg/include/libavutil/hwcontext_drm.h | 3 + .../include/libavutil/hwcontext_dxva2.h | 3 + .../include/libavutil/hwcontext_mediacodec.h | 3 + .../include/libavutil/hwcontext_opencl.h | 3 + .../ffmpeg/include/libavutil/hwcontext_qsv.h | 3 + .../include/libavutil/hwcontext_vaapi.h | 3 + .../include/libavutil/hwcontext_vdpau.h | 3 + .../libavutil/hwcontext_videotoolbox.h | 3 + .../include/libavutil/hwcontext_vulkan.h | 3 + .../ffmpeg/include/libavutil/imgutils.h | 3 + .../ffmpeg/include/libavutil/intfloat.h | 3 + .../ffmpeg/include/libavutil/intreadwrite.h | 3 + third_party/ffmpeg/include/libavutil/lfg.h | 3 + third_party/ffmpeg/include/libavutil/log.h | 3 + third_party/ffmpeg/include/libavutil/lzo.h | 3 + third_party/ffmpeg/include/libavutil/macros.h | 3 + .../libavutil/mastering_display_metadata.h | 3 + .../ffmpeg/include/libavutil/mathematics.h | 3 + third_party/ffmpeg/include/libavutil/md5.h | 3 + third_party/ffmpeg/include/libavutil/mem.h | 3 + .../ffmpeg/include/libavutil/motion_vector.h | 3 + .../ffmpeg/include/libavutil/murmur3.h | 3 + third_party/ffmpeg/include/libavutil/opt.h | 3 + .../ffmpeg/include/libavutil/parseutils.h | 3 + .../ffmpeg/include/libavutil/pixdesc.h | 3 + .../ffmpeg/include/libavutil/pixelutils.h | 3 + third_party/ffmpeg/include/libavutil/pixfmt.h | 3 + .../ffmpeg/include/libavutil/random_seed.h | 3 + .../ffmpeg/include/libavutil/rational.h | 3 + third_party/ffmpeg/include/libavutil/rc4.h | 3 + .../ffmpeg/include/libavutil/replaygain.h | 3 + third_party/ffmpeg/include/libavutil/ripemd.h | 3 + .../ffmpeg/include/libavutil/samplefmt.h | 3 + third_party/ffmpeg/include/libavutil/sha.h | 3 + third_party/ffmpeg/include/libavutil/sha512.h | 3 + .../ffmpeg/include/libavutil/spherical.h | 3 + .../ffmpeg/include/libavutil/stereo3d.h | 3 + third_party/ffmpeg/include/libavutil/tea.h | 3 + .../ffmpeg/include/libavutil/threadmessage.h | 3 + third_party/ffmpeg/include/libavutil/time.h | 3 + .../ffmpeg/include/libavutil/timecode.h | 3 + .../ffmpeg/include/libavutil/timestamp.h | 3 + third_party/ffmpeg/include/libavutil/tree.h | 3 + .../ffmpeg/include/libavutil/twofish.h | 3 + third_party/ffmpeg/include/libavutil/tx.h | 3 + third_party/ffmpeg/include/libavutil/uuid.h | 3 + .../ffmpeg/include/libavutil/version.h | 3 + .../include/libavutil/video_enc_params.h | 3 + .../ffmpeg/include/libavutil/video_hint.h | 3 + third_party/ffmpeg/include/libavutil/xtea.h | 3 + third_party/ffmpeg/include/x264.h | 3 + third_party/ffmpeg/include/x264_config.h | 3 + third_party/ffmpeg/larch64/lib/libavcodec.a | 3 + third_party/ffmpeg/larch64/lib/libavformat.a | 3 + third_party/ffmpeg/larch64/lib/libavutil.a | 3 + third_party/ffmpeg/larch64/lib/libx264.a | 3 + third_party/ffmpeg/x86_64/lib/libavcodec.a | 3 + third_party/ffmpeg/x86_64/lib/libavformat.a | 3 + third_party/ffmpeg/x86_64/lib/libavutil.a | 3 + third_party/ffmpeg/x86_64/lib/libx264.a | 3 + tools/cabana/SConscript | 2 +- tools/install_ubuntu_dependencies.sh | 6 -- tools/mac_setup.sh | 1 - tools/replay/SConscript | 2 +- 147 files changed, 517 insertions(+), 13 deletions(-) create mode 100644 third_party/ffmpeg/Darwin/lib/libavcodec.a create mode 100644 third_party/ffmpeg/Darwin/lib/libavformat.a create mode 100644 third_party/ffmpeg/Darwin/lib/libavutil.a create mode 100644 third_party/ffmpeg/Darwin/lib/libx264.a create mode 100755 third_party/ffmpeg/build.sh create mode 100644 third_party/ffmpeg/include/libavcodec/ac3_parser.h create mode 100644 third_party/ffmpeg/include/libavcodec/adts_parser.h create mode 100644 third_party/ffmpeg/include/libavcodec/avcodec.h create mode 100644 third_party/ffmpeg/include/libavcodec/avdct.h create mode 100644 third_party/ffmpeg/include/libavcodec/avfft.h create mode 100644 third_party/ffmpeg/include/libavcodec/bsf.h create mode 100644 third_party/ffmpeg/include/libavcodec/codec.h create mode 100644 third_party/ffmpeg/include/libavcodec/codec_desc.h create mode 100644 third_party/ffmpeg/include/libavcodec/codec_id.h create mode 100644 third_party/ffmpeg/include/libavcodec/codec_par.h create mode 100644 third_party/ffmpeg/include/libavcodec/d3d11va.h create mode 100644 third_party/ffmpeg/include/libavcodec/defs.h create mode 100644 third_party/ffmpeg/include/libavcodec/dirac.h create mode 100644 third_party/ffmpeg/include/libavcodec/dv_profile.h create mode 100644 third_party/ffmpeg/include/libavcodec/dxva2.h create mode 100644 third_party/ffmpeg/include/libavcodec/jni.h create mode 100644 third_party/ffmpeg/include/libavcodec/mediacodec.h create mode 100644 third_party/ffmpeg/include/libavcodec/packet.h create mode 100644 third_party/ffmpeg/include/libavcodec/qsv.h create mode 100644 third_party/ffmpeg/include/libavcodec/vdpau.h create mode 100644 third_party/ffmpeg/include/libavcodec/version.h create mode 100644 third_party/ffmpeg/include/libavcodec/version_major.h create mode 100644 third_party/ffmpeg/include/libavcodec/videotoolbox.h create mode 100644 third_party/ffmpeg/include/libavcodec/vorbis_parser.h create mode 100644 third_party/ffmpeg/include/libavcodec/xvmc.h create mode 100644 third_party/ffmpeg/include/libavformat/avformat.h create mode 100644 third_party/ffmpeg/include/libavformat/avio.h create mode 100644 third_party/ffmpeg/include/libavformat/version.h create mode 100644 third_party/ffmpeg/include/libavformat/version_major.h create mode 100644 third_party/ffmpeg/include/libavutil/adler32.h create mode 100644 third_party/ffmpeg/include/libavutil/aes.h create mode 100644 third_party/ffmpeg/include/libavutil/aes_ctr.h create mode 100644 third_party/ffmpeg/include/libavutil/ambient_viewing_environment.h create mode 100644 third_party/ffmpeg/include/libavutil/attributes.h create mode 100644 third_party/ffmpeg/include/libavutil/audio_fifo.h create mode 100644 third_party/ffmpeg/include/libavutil/avassert.h create mode 100644 third_party/ffmpeg/include/libavutil/avconfig.h create mode 100644 third_party/ffmpeg/include/libavutil/avstring.h create mode 100644 third_party/ffmpeg/include/libavutil/avutil.h create mode 100644 third_party/ffmpeg/include/libavutil/base64.h create mode 100644 third_party/ffmpeg/include/libavutil/blowfish.h create mode 100644 third_party/ffmpeg/include/libavutil/bprint.h create mode 100644 third_party/ffmpeg/include/libavutil/bswap.h create mode 100644 third_party/ffmpeg/include/libavutil/buffer.h create mode 100644 third_party/ffmpeg/include/libavutil/camellia.h create mode 100644 third_party/ffmpeg/include/libavutil/cast5.h create mode 100644 third_party/ffmpeg/include/libavutil/channel_layout.h create mode 100644 third_party/ffmpeg/include/libavutil/common.h create mode 100644 third_party/ffmpeg/include/libavutil/cpu.h create mode 100644 third_party/ffmpeg/include/libavutil/crc.h create mode 100644 third_party/ffmpeg/include/libavutil/csp.h create mode 100644 third_party/ffmpeg/include/libavutil/des.h create mode 100644 third_party/ffmpeg/include/libavutil/detection_bbox.h create mode 100644 third_party/ffmpeg/include/libavutil/dict.h create mode 100644 third_party/ffmpeg/include/libavutil/display.h create mode 100644 third_party/ffmpeg/include/libavutil/dovi_meta.h create mode 100644 third_party/ffmpeg/include/libavutil/downmix_info.h create mode 100644 third_party/ffmpeg/include/libavutil/encryption_info.h create mode 100644 third_party/ffmpeg/include/libavutil/error.h create mode 100644 third_party/ffmpeg/include/libavutil/eval.h create mode 100644 third_party/ffmpeg/include/libavutil/executor.h create mode 100644 third_party/ffmpeg/include/libavutil/ffversion.h create mode 100644 third_party/ffmpeg/include/libavutil/fifo.h create mode 100644 third_party/ffmpeg/include/libavutil/file.h create mode 100644 third_party/ffmpeg/include/libavutil/film_grain_params.h create mode 100644 third_party/ffmpeg/include/libavutil/frame.h create mode 100644 third_party/ffmpeg/include/libavutil/hash.h create mode 100644 third_party/ffmpeg/include/libavutil/hdr_dynamic_metadata.h create mode 100644 third_party/ffmpeg/include/libavutil/hdr_dynamic_vivid_metadata.h create mode 100644 third_party/ffmpeg/include/libavutil/hmac.h create mode 100644 third_party/ffmpeg/include/libavutil/hwcontext.h create mode 100644 third_party/ffmpeg/include/libavutil/hwcontext_cuda.h create mode 100644 third_party/ffmpeg/include/libavutil/hwcontext_d3d11va.h create mode 100644 third_party/ffmpeg/include/libavutil/hwcontext_drm.h create mode 100644 third_party/ffmpeg/include/libavutil/hwcontext_dxva2.h create mode 100644 third_party/ffmpeg/include/libavutil/hwcontext_mediacodec.h create mode 100644 third_party/ffmpeg/include/libavutil/hwcontext_opencl.h create mode 100644 third_party/ffmpeg/include/libavutil/hwcontext_qsv.h create mode 100644 third_party/ffmpeg/include/libavutil/hwcontext_vaapi.h create mode 100644 third_party/ffmpeg/include/libavutil/hwcontext_vdpau.h create mode 100644 third_party/ffmpeg/include/libavutil/hwcontext_videotoolbox.h create mode 100644 third_party/ffmpeg/include/libavutil/hwcontext_vulkan.h create mode 100644 third_party/ffmpeg/include/libavutil/imgutils.h create mode 100644 third_party/ffmpeg/include/libavutil/intfloat.h create mode 100644 third_party/ffmpeg/include/libavutil/intreadwrite.h create mode 100644 third_party/ffmpeg/include/libavutil/lfg.h create mode 100644 third_party/ffmpeg/include/libavutil/log.h create mode 100644 third_party/ffmpeg/include/libavutil/lzo.h create mode 100644 third_party/ffmpeg/include/libavutil/macros.h create mode 100644 third_party/ffmpeg/include/libavutil/mastering_display_metadata.h create mode 100644 third_party/ffmpeg/include/libavutil/mathematics.h create mode 100644 third_party/ffmpeg/include/libavutil/md5.h create mode 100644 third_party/ffmpeg/include/libavutil/mem.h create mode 100644 third_party/ffmpeg/include/libavutil/motion_vector.h create mode 100644 third_party/ffmpeg/include/libavutil/murmur3.h create mode 100644 third_party/ffmpeg/include/libavutil/opt.h create mode 100644 third_party/ffmpeg/include/libavutil/parseutils.h create mode 100644 third_party/ffmpeg/include/libavutil/pixdesc.h create mode 100644 third_party/ffmpeg/include/libavutil/pixelutils.h create mode 100644 third_party/ffmpeg/include/libavutil/pixfmt.h create mode 100644 third_party/ffmpeg/include/libavutil/random_seed.h create mode 100644 third_party/ffmpeg/include/libavutil/rational.h create mode 100644 third_party/ffmpeg/include/libavutil/rc4.h create mode 100644 third_party/ffmpeg/include/libavutil/replaygain.h create mode 100644 third_party/ffmpeg/include/libavutil/ripemd.h create mode 100644 third_party/ffmpeg/include/libavutil/samplefmt.h create mode 100644 third_party/ffmpeg/include/libavutil/sha.h create mode 100644 third_party/ffmpeg/include/libavutil/sha512.h create mode 100644 third_party/ffmpeg/include/libavutil/spherical.h create mode 100644 third_party/ffmpeg/include/libavutil/stereo3d.h create mode 100644 third_party/ffmpeg/include/libavutil/tea.h create mode 100644 third_party/ffmpeg/include/libavutil/threadmessage.h create mode 100644 third_party/ffmpeg/include/libavutil/time.h create mode 100644 third_party/ffmpeg/include/libavutil/timecode.h create mode 100644 third_party/ffmpeg/include/libavutil/timestamp.h create mode 100644 third_party/ffmpeg/include/libavutil/tree.h create mode 100644 third_party/ffmpeg/include/libavutil/twofish.h create mode 100644 third_party/ffmpeg/include/libavutil/tx.h create mode 100644 third_party/ffmpeg/include/libavutil/uuid.h create mode 100644 third_party/ffmpeg/include/libavutil/version.h create mode 100644 third_party/ffmpeg/include/libavutil/video_enc_params.h create mode 100644 third_party/ffmpeg/include/libavutil/video_hint.h create mode 100644 third_party/ffmpeg/include/libavutil/xtea.h create mode 100644 third_party/ffmpeg/include/x264.h create mode 100644 third_party/ffmpeg/include/x264_config.h create mode 100644 third_party/ffmpeg/larch64/lib/libavcodec.a create mode 100644 third_party/ffmpeg/larch64/lib/libavformat.a create mode 100644 third_party/ffmpeg/larch64/lib/libavutil.a create mode 100644 third_party/ffmpeg/larch64/lib/libx264.a create mode 100644 third_party/ffmpeg/x86_64/lib/libavcodec.a create mode 100644 third_party/ffmpeg/x86_64/lib/libavformat.a create mode 100644 third_party/ffmpeg/x86_64/lib/libavutil.a create mode 100644 third_party/ffmpeg/x86_64/lib/libx264.a diff --git a/.gitattributes b/.gitattributes index cc1605a13..152dbfe45 100644 --- a/.gitattributes +++ b/.gitattributes @@ -15,6 +15,7 @@ third_party/**/*.a filter=lfs diff=lfs merge=lfs -text third_party/**/*.so filter=lfs diff=lfs merge=lfs -text third_party/**/*.so.* filter=lfs diff=lfs merge=lfs -text third_party/**/*.dylib filter=lfs diff=lfs merge=lfs -text +third_party/ffmpeg/include/**/*.h filter=lfs diff=lfs merge=lfs -text third_party/acados/*/t_renderer filter=lfs diff=lfs merge=lfs -text third_party/qt5/larch64/bin/lrelease filter=lfs diff=lfs merge=lfs -text third_party/qt5/larch64/bin/lupdate filter=lfs diff=lfs merge=lfs -text diff --git a/SConstruct b/SConstruct index 5b13bd635..a788b9a29 100644 --- a/SConstruct +++ b/SConstruct @@ -91,6 +91,7 @@ if arch == "larch64": ] libpath = [ + f"#third_party/ffmpeg/{arch}/lib", "/usr/local/lib", "/system/vendor/lib64", f"#third_party/acados/{arch}/lib", @@ -114,6 +115,7 @@ else: libpath = [ f"#third_party/libyuv/{arch}/lib", f"#third_party/acados/{arch}/lib", + f"#third_party/ffmpeg/{arch}/lib", f"{brew_prefix}/lib", f"{brew_prefix}/opt/openssl@3.0/lib", "/System/Library/Frameworks/OpenGL.framework/Libraries", @@ -130,6 +132,7 @@ else: libpath = [ f"#third_party/acados/{arch}/lib", f"#third_party/libyuv/{arch}/lib", + f"#third_party/ffmpeg/{arch}/lib", "/usr/lib", "/usr/local/lib", ] @@ -177,6 +180,7 @@ env = Environment( "#third_party/libyuv/include", "#third_party/json11", "#third_party/linux/include", + "#third_party/ffmpeg/include", "#third_party", "#msgq", ], diff --git a/system/loggerd/SConscript b/system/loggerd/SConscript index cf169f4dc..c87db4232 100644 --- a/system/loggerd/SConscript +++ b/system/loggerd/SConscript @@ -1,8 +1,8 @@ Import('env', 'arch', 'messaging', 'common', 'visionipc') libs = [common, messaging, visionipc, - 'avformat', 'avcodec', 'avutil', - 'yuv', 'OpenCL', 'pthread', 'zstd'] + 'yuv', 'OpenCL', 'pthread', 'zstd', + 'avformat', 'avcodec', 'avutil', 'x264'] src = ['logger.cc', 'zstd_writer.cc', 'video_writer.cc', 'encoder/encoder.cc', 'encoder/v4l_encoder.cc', 'encoder/jpeg_encoder.cc'] if arch != "larch64": diff --git a/system/loggerd/tests/test_loggerd.py b/system/loggerd/tests/test_loggerd.py index c6a4b12e6..ae7196684 100644 --- a/system/loggerd/tests/test_loggerd.py +++ b/system/loggerd/tests/test_loggerd.py @@ -316,8 +316,18 @@ class TestLoggerd: self._publish_camera_and_audio_messages() qcamera_ts_path = os.path.join(self._get_latest_log_dir(), 'qcamera.ts') - ffprobe_cmd = f"ffprobe -i {qcamera_ts_path} -show_streams -select_streams a -loglevel error" - has_audio_stream = subprocess.run(ffprobe_cmd, shell=True, capture_output=True).stdout.strip() != b'' + + # simplest heuristic: look for AAC ADTS syncwords in the TS file + def ts_has_audio_stream(ts_path: str) -> bool: + try: + with open(ts_path, 'rb') as f: + data = f.read() + # ADTS headers typically start with 0xFFF1 or 0xFFF9 + return (b"\xFF\xF1" in data) or (b"\xFF\xF9" in data) + except Exception: + return False + + has_audio_stream = ts_has_audio_stream(qcamera_ts_path) assert has_audio_stream == record_audio raw_audio_in_rlog = any(m.which() == 'rawAudioData' for m in LogReader(os.path.join(self._get_latest_log_dir(), 'rlog.zst'))) diff --git a/third_party/.gitignore b/third_party/.gitignore index 0d20b6487..0d5b3d221 100644 --- a/third_party/.gitignore +++ b/third_party/.gitignore @@ -1 +1,3 @@ *.pyc +src/ +build/ diff --git a/third_party/ffmpeg/Darwin/lib/libavcodec.a b/third_party/ffmpeg/Darwin/lib/libavcodec.a new file mode 100644 index 000000000..a87d3323c --- /dev/null +++ b/third_party/ffmpeg/Darwin/lib/libavcodec.a @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2dcb729a6833558fc0e01abe5e728eba2b3c215abc7dece74d5ce1bc1d279e7d +size 2328832 diff --git a/third_party/ffmpeg/Darwin/lib/libavformat.a b/third_party/ffmpeg/Darwin/lib/libavformat.a new file mode 100644 index 000000000..b7c48f796 --- /dev/null +++ b/third_party/ffmpeg/Darwin/lib/libavformat.a @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:825f145f7168226deae199538113f063e6b71a02999b297c75254dfa466b73cb +size 863544 diff --git a/third_party/ffmpeg/Darwin/lib/libavutil.a b/third_party/ffmpeg/Darwin/lib/libavutil.a new file mode 100644 index 000000000..17985d73b --- /dev/null +++ b/third_party/ffmpeg/Darwin/lib/libavutil.a @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9c13c341440029d06a1044479876843bc93615849554dc636e386f31f6776850 +size 789024 diff --git a/third_party/ffmpeg/Darwin/lib/libx264.a b/third_party/ffmpeg/Darwin/lib/libx264.a new file mode 100644 index 000000000..27375793a --- /dev/null +++ b/third_party/ffmpeg/Darwin/lib/libx264.a @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8bedf59051187092bd9f282acb7f22f7885ed41b2f89692e0478252329bd7e4b +size 1941808 diff --git a/third_party/ffmpeg/build.sh b/third_party/ffmpeg/build.sh new file mode 100755 index 000000000..f8812d6f1 --- /dev/null +++ b/third_party/ffmpeg/build.sh @@ -0,0 +1,83 @@ +#!/usr/bin/env bash +set -e + +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" +cd $DIR + +# Detect arch +ARCHNAME="x86_64" +if [ -f /TICI ]; then + ARCHNAME="larch64" +fi +if [[ "$OSTYPE" == "darwin"* ]]; then + ARCHNAME="Darwin" +fi + +VERSION="6.1.1" # LTS +PREFIX="$DIR/$ARCHNAME" +BUILD_DIR="$DIR/build/" + +mkdir -p "$BUILD_DIR" +rm -rf include/ && mkdir -p include/ +rm -rf "$PREFIX" && mkdir -p "$PREFIX" + +# *** build x264 *** +if [[ ! -d "$DIR/src/x264/" ]]; then + # TODO: pin to a commit + git clone --depth=1 --branch "stable" https://code.videolan.org/videolan/x264.git "$DIR/src/x264/" +fi +cd $DIR/src/x264 +git fetch origin b35605ace3ddf7c1a5d67a2eb553f034aef41d55 +git checkout -f FETCH_HEAD +./configure --prefix="$PREFIX" --enable-static --disable-opencl --enable-pic --disable-cli +make -j8 +make install +cp -a "$PREFIX/include/." "$DIR/include/" + +# *** build ffmpeg *** +mkdir -p "$DIR/src" +if [[ ! -d "$DIR/src/ffmpeg-$VERSION" ]]; then + echo "Downloading FFmpeg $VERSION ..." + curl -L "https://ffmpeg.org/releases/ffmpeg-${VERSION}.tar.xz" -o "$DIR/src/ffmpeg-${VERSION}.tar.xz" + tar -C "$DIR/src" -xf "$DIR/src/ffmpeg-${VERSION}.tar.xz" +fi + +cd $BUILD_DIR + +export PKG_CONFIG_PATH="$PREFIX/lib/pkgconfig:${PKG_CONFIG_PATH:-}" +export EXTRA_CFLAGS="-I$PREFIX/include ${EXTRA_CFLAGS:-}" +export EXTRA_LDFLAGS="-L$PREFIX/lib ${EXTRA_LDFLAGS:-}" +# Configure minimal static FFmpeg for desktop Linux tools +"$DIR/src/ffmpeg-$VERSION/configure" \ + --prefix="$PREFIX" \ + --datadir="$PREFIX" \ + --docdir="$PREFIX" \ + --mandir="$PREFIX" \ + --enable-static --disable-shared \ + --disable-programs --disable-doc --disable-debug \ + --disable-network \ + --disable-avdevice --disable-swscale --disable-swresample --disable-postproc --disable-avfilter \ + --disable-autodetect --disable-iconv \ + --enable-avcodec --enable-avformat --enable-avutil \ + --enable-protocol=file \ + --pkg-config-flags=--static \ + --enable-gpl --enable-libx264 \ + --disable-decoders --enable-decoder=h264,hevc,aac \ + --disable-encoders --enable-encoder=libx264,ffvhuff,aac \ + --disable-demuxers --enable-demuxer=mpegts,hevc,h264,matroska,mov \ + --disable-muxers --enable-muxer=matroska,mpegts \ + --disable-parsers --enable-parser=h264,hevc,aac,vorbis \ + --disable-bsfs \ + --enable-small \ + --extra-cflags="${EXTRA_CFLAGS:-}" \ + --extra-ldflags="${EXTRA_LDFLAGS:-}" + + +make -j$(nproc) +make install +cp -a "$PREFIX/include/." "$DIR/include/" + +# *** cleanup *** +cd $PREFIX +rm -rf share/ doc/ man/ examples/ examples/ include/ lib/pkgconfig/ +rm -f lib/libavfilter* "$DIR/include/libavfilter*" diff --git a/third_party/ffmpeg/include/libavcodec/ac3_parser.h b/third_party/ffmpeg/include/libavcodec/ac3_parser.h new file mode 100644 index 000000000..c8acedae8 --- /dev/null +++ b/third_party/ffmpeg/include/libavcodec/ac3_parser.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:200c6d2e96975196e8ba5f5716223dc9dda999d51578dabce2fca93175a05252 +size 1207 diff --git a/third_party/ffmpeg/include/libavcodec/adts_parser.h b/third_party/ffmpeg/include/libavcodec/adts_parser.h new file mode 100644 index 000000000..031f10320 --- /dev/null +++ b/third_party/ffmpeg/include/libavcodec/adts_parser.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d466583a8dc1260b015e588adbe3abd45f3f8ca0e43722f3088b472e80492a15 +size 1354 diff --git a/third_party/ffmpeg/include/libavcodec/avcodec.h b/third_party/ffmpeg/include/libavcodec/avcodec.h new file mode 100644 index 000000000..82fc7d1bd --- /dev/null +++ b/third_party/ffmpeg/include/libavcodec/avcodec.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fc38e85633a2c6c44fc60b319d7e046a6e4206456565aa61240dd93ee2e81188 +size 114251 diff --git a/third_party/ffmpeg/include/libavcodec/avdct.h b/third_party/ffmpeg/include/libavcodec/avdct.h new file mode 100644 index 000000000..1a57c4300 --- /dev/null +++ b/third_party/ffmpeg/include/libavcodec/avdct.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7c125edc1985ec078f99abb1c9044c4fc76b06a03eb02c026cb968fb9d41fcca +size 2726 diff --git a/third_party/ffmpeg/include/libavcodec/avfft.h b/third_party/ffmpeg/include/libavcodec/avfft.h new file mode 100644 index 000000000..f54c365c1 --- /dev/null +++ b/third_party/ffmpeg/include/libavcodec/avfft.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:81bb6c5359f57de53b0e39766591404b632d15e700d313c97ded234d63d4e393 +size 4081 diff --git a/third_party/ffmpeg/include/libavcodec/bsf.h b/third_party/ffmpeg/include/libavcodec/bsf.h new file mode 100644 index 000000000..54fc91b11 --- /dev/null +++ b/third_party/ffmpeg/include/libavcodec/bsf.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cc26676aa44638fa5cbef953d967e19e2084b46aaa100f0a35ba2d34b302301c +size 11540 diff --git a/third_party/ffmpeg/include/libavcodec/codec.h b/third_party/ffmpeg/include/libavcodec/codec.h new file mode 100644 index 000000000..b8c384172 --- /dev/null +++ b/third_party/ffmpeg/include/libavcodec/codec.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d8b75f2e4128ce59db3172e2bdcbf64540424e6242952dc0912002bee5314254 +size 13460 diff --git a/third_party/ffmpeg/include/libavcodec/codec_desc.h b/third_party/ffmpeg/include/libavcodec/codec_desc.h new file mode 100644 index 000000000..108aa120d --- /dev/null +++ b/third_party/ffmpeg/include/libavcodec/codec_desc.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4c1a8d02398cc82c1913487405c483f4d74ec71ed3e666ed7e7570f581c8f479 +size 3974 diff --git a/third_party/ffmpeg/include/libavcodec/codec_id.h b/third_party/ffmpeg/include/libavcodec/codec_id.h new file mode 100644 index 000000000..72002e689 --- /dev/null +++ b/third_party/ffmpeg/include/libavcodec/codec_id.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:754a846797be20c7c1ca30358d2555873df3bb19b6b2c8011c85697440f600df +size 18020 diff --git a/third_party/ffmpeg/include/libavcodec/codec_par.h b/third_party/ffmpeg/include/libavcodec/codec_par.h new file mode 100644 index 000000000..2c2f42e88 --- /dev/null +++ b/third_party/ffmpeg/include/libavcodec/codec_par.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5f2bef7f23782ac9c0b5769eeb54704103efa5074ca5b657d7f01890795baf08 +size 8065 diff --git a/third_party/ffmpeg/include/libavcodec/d3d11va.h b/third_party/ffmpeg/include/libavcodec/d3d11va.h new file mode 100644 index 000000000..aa372312b --- /dev/null +++ b/third_party/ffmpeg/include/libavcodec/d3d11va.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:74a55a2e3f19ce797e99624a224302f25efa89a115b9bf2e932c8fa179b0cc66 +size 2853 diff --git a/third_party/ffmpeg/include/libavcodec/defs.h b/third_party/ffmpeg/include/libavcodec/defs.h new file mode 100644 index 000000000..6467ef663 --- /dev/null +++ b/third_party/ffmpeg/include/libavcodec/defs.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0bb9d7049499d252c75f1fe15fced5022ebb30d1966d47217848236eb021604a +size 12358 diff --git a/third_party/ffmpeg/include/libavcodec/dirac.h b/third_party/ffmpeg/include/libavcodec/dirac.h new file mode 100644 index 000000000..8bf7c4a0d --- /dev/null +++ b/third_party/ffmpeg/include/libavcodec/dirac.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:09fd1f670422ee61713568abd90fdf371b30e6fe35bc6fb8213f1ad32b45cf56 +size 4126 diff --git a/third_party/ffmpeg/include/libavcodec/dv_profile.h b/third_party/ffmpeg/include/libavcodec/dv_profile.h new file mode 100644 index 000000000..738746f3f --- /dev/null +++ b/third_party/ffmpeg/include/libavcodec/dv_profile.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:19f59e0b20ac583de4bfd76d18889d334bf0b6cdf7b5356723a33f3874738466 +size 3694 diff --git a/third_party/ffmpeg/include/libavcodec/dxva2.h b/third_party/ffmpeg/include/libavcodec/dxva2.h new file mode 100644 index 000000000..aa41fbba8 --- /dev/null +++ b/third_party/ffmpeg/include/libavcodec/dxva2.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e69dc45a7d5a9206b3bfead10caf3117117005cd532c4fc599d9976637c1b9e3 +size 2361 diff --git a/third_party/ffmpeg/include/libavcodec/jni.h b/third_party/ffmpeg/include/libavcodec/jni.h new file mode 100644 index 000000000..942690da8 --- /dev/null +++ b/third_party/ffmpeg/include/libavcodec/jni.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:18ca8eae5bce081b4eef1b1f61a62aefed1d4e6e3cde41487c81fb96ee709e51 +size 1650 diff --git a/third_party/ffmpeg/include/libavcodec/mediacodec.h b/third_party/ffmpeg/include/libavcodec/mediacodec.h new file mode 100644 index 000000000..2afacbd1d --- /dev/null +++ b/third_party/ffmpeg/include/libavcodec/mediacodec.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1f64544000dd2f2ec94604d5fcf7c2a6d26d32d085264356084a4b53a7f0c3f0 +size 3570 diff --git a/third_party/ffmpeg/include/libavcodec/packet.h b/third_party/ffmpeg/include/libavcodec/packet.h new file mode 100644 index 000000000..7c11f362e --- /dev/null +++ b/third_party/ffmpeg/include/libavcodec/packet.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:18eb41b9aa9ec55b3ee3932279cc54ead813710741f004c5ea5a1c2957896f59 +size 28678 diff --git a/third_party/ffmpeg/include/libavcodec/qsv.h b/third_party/ffmpeg/include/libavcodec/qsv.h new file mode 100644 index 000000000..02c4c961f --- /dev/null +++ b/third_party/ffmpeg/include/libavcodec/qsv.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e45780237ab0e9ea9ec11aab9f62dfb8b059138f846632ecacdc024db16dfb1b +size 3844 diff --git a/third_party/ffmpeg/include/libavcodec/vdpau.h b/third_party/ffmpeg/include/libavcodec/vdpau.h new file mode 100644 index 000000000..6ccba34db --- /dev/null +++ b/third_party/ffmpeg/include/libavcodec/vdpau.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3e0c963348495936556724cfb4d2d35eb4c14007ba72d51be94dd26c83d93fb8 +size 5104 diff --git a/third_party/ffmpeg/include/libavcodec/version.h b/third_party/ffmpeg/include/libavcodec/version.h new file mode 100644 index 000000000..9f2069af2 --- /dev/null +++ b/third_party/ffmpeg/include/libavcodec/version.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b6938a71f9eb6b1ef2a201499f47ef1131869b90755932a2ee70a272b2ee0784 +size 1619 diff --git a/third_party/ffmpeg/include/libavcodec/version_major.h b/third_party/ffmpeg/include/libavcodec/version_major.h new file mode 100644 index 000000000..7781faab4 --- /dev/null +++ b/third_party/ffmpeg/include/libavcodec/version_major.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e9562c933e8eebb020eb143d21c4b2b529e2dc38d2d7365bc13adb8d0ef8227b +size 2494 diff --git a/third_party/ffmpeg/include/libavcodec/videotoolbox.h b/third_party/ffmpeg/include/libavcodec/videotoolbox.h new file mode 100644 index 000000000..59f788e02 --- /dev/null +++ b/third_party/ffmpeg/include/libavcodec/videotoolbox.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:19b6ba05f4e9f600044f45f1a5dc04d0da9276dc9a1cb5b07826df5cab5012c9 +size 4677 diff --git a/third_party/ffmpeg/include/libavcodec/vorbis_parser.h b/third_party/ffmpeg/include/libavcodec/vorbis_parser.h new file mode 100644 index 000000000..d578829d4 --- /dev/null +++ b/third_party/ffmpeg/include/libavcodec/vorbis_parser.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:57077b2e1d28d42636cab0f69e4b92b1ad64ac2eaa2843c270a6afaf308a76ae +size 2285 diff --git a/third_party/ffmpeg/include/libavcodec/xvmc.h b/third_party/ffmpeg/include/libavcodec/xvmc.h new file mode 100644 index 000000000..abe162a93 --- /dev/null +++ b/third_party/ffmpeg/include/libavcodec/xvmc.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ca59c5caaaa32368f7c84c714bbf2ad1cba61ec9ca5b5cb1a14cf6975affe8a2 +size 6136 diff --git a/third_party/ffmpeg/include/libavformat/avformat.h b/third_party/ffmpeg/include/libavformat/avformat.h new file mode 100644 index 000000000..a4244e913 --- /dev/null +++ b/third_party/ffmpeg/include/libavformat/avformat.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c1ca6edf1d5eab00126fa7320f01c21ed8cea09dc2d931f84a4015b9e50f8d2a +size 110803 diff --git a/third_party/ffmpeg/include/libavformat/avio.h b/third_party/ffmpeg/include/libavformat/avio.h new file mode 100644 index 000000000..af9666e18 --- /dev/null +++ b/third_party/ffmpeg/include/libavformat/avio.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de6fe4be374481b1ee5ba8722882af7e7698038fd744375de153d1c78f528836 +size 31681 diff --git a/third_party/ffmpeg/include/libavformat/version.h b/third_party/ffmpeg/include/libavformat/version.h new file mode 100644 index 000000000..07fe55f95 --- /dev/null +++ b/third_party/ffmpeg/include/libavformat/version.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:08b64bb52109a07ae3ee1a1dafd12ad336d0a646c23aea075c5024b9437f3b29 +size 1652 diff --git a/third_party/ffmpeg/include/libavformat/version_major.h b/third_party/ffmpeg/include/libavformat/version_major.h new file mode 100644 index 000000000..0efde349b --- /dev/null +++ b/third_party/ffmpeg/include/libavformat/version_major.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2d440097abe2039c3c831d3b91d315857829d04e462415ee632c9f36321d520d +size 2240 diff --git a/third_party/ffmpeg/include/libavutil/adler32.h b/third_party/ffmpeg/include/libavutil/adler32.h new file mode 100644 index 000000000..19f01b344 --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/adler32.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f21a861957bf4b1812ed67fdc528890f9cff1bb483facf0f5109e4a51932fe3b +size 1696 diff --git a/third_party/ffmpeg/include/libavutil/aes.h b/third_party/ffmpeg/include/libavutil/aes.h new file mode 100644 index 000000000..6d81ffa43 --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/aes.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0a86ebeaf9ed33548bf0f92359f7047e521d4d80fe6b0ef1c8ef9505e38b6d28 +size 1912 diff --git a/third_party/ffmpeg/include/libavutil/aes_ctr.h b/third_party/ffmpeg/include/libavutil/aes_ctr.h new file mode 100644 index 000000000..1a156aed3 --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/aes_ctr.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fbbb94888bfab2ea7141e7e1afa5872de099e7ee57baea17632c3d4fdd2cba3f +size 2443 diff --git a/third_party/ffmpeg/include/libavutil/ambient_viewing_environment.h b/third_party/ffmpeg/include/libavutil/ambient_viewing_environment.h new file mode 100644 index 000000000..42a94d194 --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/ambient_viewing_environment.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:00135de08089f8c711bb113588f80575a1d26e86d50b6dac5928a27a0ff9a8c7 +size 2585 diff --git a/third_party/ffmpeg/include/libavutil/attributes.h b/third_party/ffmpeg/include/libavutil/attributes.h new file mode 100644 index 000000000..2a4784261 --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/attributes.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1f51258ba39861e08cbc8d407ec37a8d0fd28669aa7306c1365cb9c675fa1d76 +size 4850 diff --git a/third_party/ffmpeg/include/libavutil/audio_fifo.h b/third_party/ffmpeg/include/libavutil/audio_fifo.h new file mode 100644 index 000000000..28a3364ee --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/audio_fifo.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3c513ca46927346c0673cd9db302afe62dbbf976844f36da0307fd10f7f47bfd +size 5966 diff --git a/third_party/ffmpeg/include/libavutil/avassert.h b/third_party/ffmpeg/include/libavutil/avassert.h new file mode 100644 index 000000000..40ecca50e --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/avassert.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:39dfe16f0790daa5d6b9b1bad3167c3c9e638c72452e0acdaf2d163c9c75ea40 +size 2408 diff --git a/third_party/ffmpeg/include/libavutil/avconfig.h b/third_party/ffmpeg/include/libavutil/avconfig.h new file mode 100644 index 000000000..4e1eedcf9 --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/avconfig.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:975611ad5eba15212d9e1d5fca9d4fdf0daec6d2269b2fcab8e29af8667164bc +size 180 diff --git a/third_party/ffmpeg/include/libavutil/avstring.h b/third_party/ffmpeg/include/libavutil/avstring.h new file mode 100644 index 000000000..ce048414e --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/avstring.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ac51462bf55ff62da39db4b15c7a6b9b783387709f06879196a065691b34edbf +size 14940 diff --git a/third_party/ffmpeg/include/libavutil/avutil.h b/third_party/ffmpeg/include/libavutil/avutil.h new file mode 100644 index 000000000..7f3e2654e --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/avutil.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7640d72de6a72eb9589ed2c623b4ec0924a241097846086c62dc3e05ab70ee9e +size 9968 diff --git a/third_party/ffmpeg/include/libavutil/base64.h b/third_party/ffmpeg/include/libavutil/base64.h new file mode 100644 index 000000000..db8221772 --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/base64.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:81ac13d23f3744fe85ea2651ce903e201cd55fc63fcdd899d2cfe5560d50ef3d +size 2285 diff --git a/third_party/ffmpeg/include/libavutil/blowfish.h b/third_party/ffmpeg/include/libavutil/blowfish.h new file mode 100644 index 000000000..c16f03bfd --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/blowfish.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b955a63c60c8b3be0203ec6c3973f9084d848cf884fe56cd56088301aeef7992 +size 2394 diff --git a/third_party/ffmpeg/include/libavutil/bprint.h b/third_party/ffmpeg/include/libavutil/bprint.h new file mode 100644 index 000000000..9a5528567 --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/bprint.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c19f6dc2c06e55d47bc1fbe9fc7658acc7a0d506b6baf16839a6119a03e873c1 +size 8812 diff --git a/third_party/ffmpeg/include/libavutil/bswap.h b/third_party/ffmpeg/include/libavutil/bswap.h new file mode 100644 index 000000000..b8e017531 --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/bswap.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:855e138f6d8c7947553b1c63a7bab06bcb71362f03c72b761fd9ed61820d71aa +size 2903 diff --git a/third_party/ffmpeg/include/libavutil/buffer.h b/third_party/ffmpeg/include/libavutil/buffer.h new file mode 100644 index 000000000..fd462ec6b --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/buffer.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f16742d574216434580573a2b09f56fc5b66b7dda1960d4f02ba59e3269ba548 +size 11998 diff --git a/third_party/ffmpeg/include/libavutil/camellia.h b/third_party/ffmpeg/include/libavutil/camellia.h new file mode 100644 index 000000000..8d6fdaca6 --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/camellia.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1db30753e71c73f1937e807850069e8215cdf37a1bc3ff89d3a6370a719c1fde +size 2139 diff --git a/third_party/ffmpeg/include/libavutil/cast5.h b/third_party/ffmpeg/include/libavutil/cast5.h new file mode 100644 index 000000000..2dfac6f5b --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/cast5.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:05b2e13aecaa0adbb470081a689f45baffb8e03a71997c31f37a22ea4e383a60 +size 2561 diff --git a/third_party/ffmpeg/include/libavutil/channel_layout.h b/third_party/ffmpeg/include/libavutil/channel_layout.h new file mode 100644 index 000000000..02549bd9a --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/channel_layout.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5df118692f2b7043820f0e641f1968cadb66c39f0ba85fab7435391322a223f5 +size 33727 diff --git a/third_party/ffmpeg/include/libavutil/common.h b/third_party/ffmpeg/include/libavutil/common.h new file mode 100644 index 000000000..8f2dfcaeb --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/common.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d861dac3eaecc326fa8fb717d90f195d961cc8ac7898daccfefd10bd5ea42398 +size 17166 diff --git a/third_party/ffmpeg/include/libavutil/cpu.h b/third_party/ffmpeg/include/libavutil/cpu.h new file mode 100644 index 000000000..3181cd089 --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/cpu.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:37c4ccea939e19b463a2a3e4c101ff8dd03c98847fc045395d28ba0c9db8b662 +size 6320 diff --git a/third_party/ffmpeg/include/libavutil/crc.h b/third_party/ffmpeg/include/libavutil/crc.h new file mode 100644 index 000000000..9cc23bb94 --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/crc.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5728cf65705a46723ea28b4f6c8361aad82b76a90e859943efe8af0edb79ec86 +size 3259 diff --git a/third_party/ffmpeg/include/libavutil/csp.h b/third_party/ffmpeg/include/libavutil/csp.h new file mode 100644 index 000000000..f4c8c5652 --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/csp.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7969fd662f31cf3180403510a6784a14af60d7f9bf3a569dde84585a696dff09 +size 4927 diff --git a/third_party/ffmpeg/include/libavutil/des.h b/third_party/ffmpeg/include/libavutil/des.h new file mode 100644 index 000000000..5df3354f9 --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/des.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:15ebdda1af65d91c4607a3444c5f749d5e9757ff5d7f4b04213b3194603f74d9 +size 2514 diff --git a/third_party/ffmpeg/include/libavutil/detection_bbox.h b/third_party/ffmpeg/include/libavutil/detection_bbox.h new file mode 100644 index 000000000..f81ef0a64 --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/detection_bbox.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8f5817d77af243a52e905947aa5ae73c218d68dba909040b2f63bd2ca6f93922 +size 3524 diff --git a/third_party/ffmpeg/include/libavutil/dict.h b/third_party/ffmpeg/include/libavutil/dict.h new file mode 100644 index 000000000..e01fbf8bb --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/dict.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c95cba1829e7886e31469f4e058bb9af3c8f215619c7ffddbc2045c9039f0554 +size 9374 diff --git a/third_party/ffmpeg/include/libavutil/display.h b/third_party/ffmpeg/include/libavutil/display.h new file mode 100644 index 000000000..6bca44f9f --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/display.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b9c78c80aa9331b945802b6bcd1db4ecc9ec4f9fad41993cc82b880c0dec2576 +size 3472 diff --git a/third_party/ffmpeg/include/libavutil/dovi_meta.h b/third_party/ffmpeg/include/libavutil/dovi_meta.h new file mode 100644 index 000000000..0d545564b --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/dovi_meta.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3a67a422676c5b6e2dfd59b3e6a8e3f816b0602f5d73332c0b1d8e6b43644077 +size 7641 diff --git a/third_party/ffmpeg/include/libavutil/downmix_info.h b/third_party/ffmpeg/include/libavutil/downmix_info.h new file mode 100644 index 000000000..cfebdff5b --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/downmix_info.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2fc23ad8f0750d82fcd6aa3b653998e2ea9721f9d1664df7b6cb80e93d7fa3aa +size 3235 diff --git a/third_party/ffmpeg/include/libavutil/encryption_info.h b/third_party/ffmpeg/include/libavutil/encryption_info.h new file mode 100644 index 000000000..490da787f --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/encryption_info.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ccc3a4a889b8a3c5aaf37b9fb2407bcdf23a065487c7cba718518a517c463b18 +size 7056 diff --git a/third_party/ffmpeg/include/libavutil/error.h b/third_party/ffmpeg/include/libavutil/error.h new file mode 100644 index 000000000..30e492756 --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/error.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f90feec75a317e491618e06ce14d19218e5b0257c885155613b704002a9d5bda +size 5489 diff --git a/third_party/ffmpeg/include/libavutil/eval.h b/third_party/ffmpeg/include/libavutil/eval.h new file mode 100644 index 000000000..49448a05c --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/eval.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5e92af6be91c610c3bcad3a344a2df5e3516153fc89045464447bb0ee44b1f56 +size 6599 diff --git a/third_party/ffmpeg/include/libavutil/executor.h b/third_party/ffmpeg/include/libavutil/executor.h new file mode 100644 index 000000000..91edf0541 --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/executor.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0c949cf5269ad933ed1a200ece06a3998db10147e1403629a398b7be52459cdc +size 1885 diff --git a/third_party/ffmpeg/include/libavutil/ffversion.h b/third_party/ffmpeg/include/libavutil/ffversion.h new file mode 100644 index 000000000..7fe4e4453 --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/ffversion.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1defa7afe4fab2090455bf74435ce185bce721d1b795f94c56994a031ae20080 +size 184 diff --git a/third_party/ffmpeg/include/libavutil/fifo.h b/third_party/ffmpeg/include/libavutil/fifo.h new file mode 100644 index 000000000..8e4cff860 --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/fifo.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0c77e489715a83e1dc1ce3187b5e23b1b0cd52762638919e955d43afae316f9b +size 15452 diff --git a/third_party/ffmpeg/include/libavutil/file.h b/third_party/ffmpeg/include/libavutil/file.h new file mode 100644 index 000000000..44cae7819 --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/file.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9f91c41bc9a14a206bc86c87b9edd7215b5be63b8aade2bda40297dc6752de36 +size 3039 diff --git a/third_party/ffmpeg/include/libavutil/film_grain_params.h b/third_party/ffmpeg/include/libavutil/film_grain_params.h new file mode 100644 index 000000000..8d052ff6d --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/film_grain_params.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e1bda2934dce25cb0bb9fa6eaae230cd21aca79428905b66417f74fc8f0fa72a +size 8499 diff --git a/third_party/ffmpeg/include/libavutil/frame.h b/third_party/ffmpeg/include/libavutil/frame.h new file mode 100644 index 000000000..013f70247 --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/frame.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0512cdd2a4479faac17b556f6311a3307ace2a5278217c4d738bd752c07dc37e +size 35894 diff --git a/third_party/ffmpeg/include/libavutil/hash.h b/third_party/ffmpeg/include/libavutil/hash.h new file mode 100644 index 000000000..db71ca59f --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/hash.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b0896571267220736679eea28c454783795a02a0f1aef008ebe7c40489a75fdd +size 8457 diff --git a/third_party/ffmpeg/include/libavutil/hdr_dynamic_metadata.h b/third_party/ffmpeg/include/libavutil/hdr_dynamic_metadata.h new file mode 100644 index 000000000..f920e30d6 --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/hdr_dynamic_metadata.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:457274c2eda1fe91e83ae76b9d1ab8682e6144adfacbd2c86f5fb4a03ede421f +size 14385 diff --git a/third_party/ffmpeg/include/libavutil/hdr_dynamic_vivid_metadata.h b/third_party/ffmpeg/include/libavutil/hdr_dynamic_vivid_metadata.h new file mode 100644 index 000000000..64e00109f --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/hdr_dynamic_vivid_metadata.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9f50e471376bc9e8ca504a338cfe74ef634b8f2c242ccefed2e4da67197112de +size 10004 diff --git a/third_party/ffmpeg/include/libavutil/hmac.h b/third_party/ffmpeg/include/libavutil/hmac.h new file mode 100644 index 000000000..919dc1ec2 --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/hmac.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d14d625a897d6bba0668acdf33dc597bb0050237c5c1a5f7e568fe36822782e7 +size 2865 diff --git a/third_party/ffmpeg/include/libavutil/hwcontext.h b/third_party/ffmpeg/include/libavutil/hwcontext.h new file mode 100644 index 000000000..5ccfbbe91 --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/hwcontext.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e05c09b0a2f51c800aa57dc21277b5b4534e6783ab3df52dbf81c63e37fe8323 +size 24341 diff --git a/third_party/ffmpeg/include/libavutil/hwcontext_cuda.h b/third_party/ffmpeg/include/libavutil/hwcontext_cuda.h new file mode 100644 index 000000000..c596a2781 --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/hwcontext_cuda.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4878f46347271bc7a9ff26bb1573449a99cc81447684e1034a3edd4b0ff91d9a +size 1843 diff --git a/third_party/ffmpeg/include/libavutil/hwcontext_d3d11va.h b/third_party/ffmpeg/include/libavutil/hwcontext_d3d11va.h new file mode 100644 index 000000000..efdaa8800 --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/hwcontext_d3d11va.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d99c1b5c5dab94c23709d4ce6cf489c2a0b4f4bd57bb04e2196371277dca5af7 +size 6669 diff --git a/third_party/ffmpeg/include/libavutil/hwcontext_drm.h b/third_party/ffmpeg/include/libavutil/hwcontext_drm.h new file mode 100644 index 000000000..fa6732af7 --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/hwcontext_drm.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b598f37f40cf1342f923c0b97784a6f2830b543868eccee046375e096fbd5f24 +size 4673 diff --git a/third_party/ffmpeg/include/libavutil/hwcontext_dxva2.h b/third_party/ffmpeg/include/libavutil/hwcontext_dxva2.h new file mode 100644 index 000000000..d871979e6 --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/hwcontext_dxva2.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:73a0333b65e99675834dcb1b63a5e9339638ccc619f1a2fcba85cdd0e179ade0 +size 2411 diff --git a/third_party/ffmpeg/include/libavutil/hwcontext_mediacodec.h b/third_party/ffmpeg/include/libavutil/hwcontext_mediacodec.h new file mode 100644 index 000000000..efee54fea --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/hwcontext_mediacodec.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8c602859ebca906ba6e43ea548ff28821cf2886b4500b2be1deaaf2d552496d4 +size 1988 diff --git a/third_party/ffmpeg/include/libavutil/hwcontext_opencl.h b/third_party/ffmpeg/include/libavutil/hwcontext_opencl.h new file mode 100644 index 000000000..812b29caa --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/hwcontext_opencl.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ad521fa2fd015cb1aba962468ca4ac176f5fc6b2c4b7be28f05e1c03d89e1b31 +size 3097 diff --git a/third_party/ffmpeg/include/libavutil/hwcontext_qsv.h b/third_party/ffmpeg/include/libavutil/hwcontext_qsv.h new file mode 100644 index 000000000..a5053d51d --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/hwcontext_qsv.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4986f86340b8162ef2724f0e77a380b5133aaa0612749cfb836c38b4c166d20d +size 1960 diff --git a/third_party/ffmpeg/include/libavutil/hwcontext_vaapi.h b/third_party/ffmpeg/include/libavutil/hwcontext_vaapi.h new file mode 100644 index 000000000..b205bc5fc --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/hwcontext_vaapi.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6f6c6a5250dd0f901cdc7de8b9b3db26102719b7e056cd17500009096bfd9b39 +size 3787 diff --git a/third_party/ffmpeg/include/libavutil/hwcontext_vdpau.h b/third_party/ffmpeg/include/libavutil/hwcontext_vdpau.h new file mode 100644 index 000000000..8cd234bf7 --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/hwcontext_vdpau.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6c96373d9e5deb2c500004f3f55ee1d2cea0f76cdfaeabaf5a3ad3e4938e8252 +size 1360 diff --git a/third_party/ffmpeg/include/libavutil/hwcontext_videotoolbox.h b/third_party/ffmpeg/include/libavutil/hwcontext_videotoolbox.h new file mode 100644 index 000000000..db959d5fc --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/hwcontext_videotoolbox.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2f74c83778a0df9738abcae823412436cbbeaff8e9083281fb6f38116cb401c2 +size 3431 diff --git a/third_party/ffmpeg/include/libavutil/hwcontext_vulkan.h b/third_party/ffmpeg/include/libavutil/hwcontext_vulkan.h new file mode 100644 index 000000000..fce119213 --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/hwcontext_vulkan.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:74067b6224e8a9d60969f192fdc17f97a12ff073274f5047c380543647026760 +size 11412 diff --git a/third_party/ffmpeg/include/libavutil/imgutils.h b/third_party/ffmpeg/include/libavutil/imgutils.h new file mode 100644 index 000000000..9636ebc81 --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/imgutils.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b4cceea6c7b3017dea61a9fd514d7460ab13592907addb69a2e92870872207f1 +size 14819 diff --git a/third_party/ffmpeg/include/libavutil/intfloat.h b/third_party/ffmpeg/include/libavutil/intfloat.h new file mode 100644 index 000000000..278a2a631 --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/intfloat.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3a29e4eebc8c269cfd867b96de91d8231773d392c12a8820e46eaba96d2b4ca1 +size 1726 diff --git a/third_party/ffmpeg/include/libavutil/intreadwrite.h b/third_party/ffmpeg/include/libavutil/intreadwrite.h new file mode 100644 index 000000000..e6ae02efb --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/intreadwrite.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f815f4edb0fb58c0b2c7b4f4763a27ee223672fa43ef4c5c0b99fb4575b66bf7 +size 18735 diff --git a/third_party/ffmpeg/include/libavutil/lfg.h b/third_party/ffmpeg/include/libavutil/lfg.h new file mode 100644 index 000000000..4e9488eaa --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/lfg.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c3bc1533172fe74870df8e37f168f28b0ff2e21bdb6a519b6555ec72710c910f +size 2541 diff --git a/third_party/ffmpeg/include/libavutil/log.h b/third_party/ffmpeg/include/libavutil/log.h new file mode 100644 index 000000000..a3dd2c72a --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/log.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8555ad5d41f5f2fa1c7dd0dc65f2145b0517c0a414654575aade8b3968e4c408 +size 12766 diff --git a/third_party/ffmpeg/include/libavutil/lzo.h b/third_party/ffmpeg/include/libavutil/lzo.h new file mode 100644 index 000000000..2e7db3c19 --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/lzo.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:61e89928dee9d83030adececac06aa6c1ae2aada06c5682fde52c52015c53556 +size 2048 diff --git a/third_party/ffmpeg/include/libavutil/macros.h b/third_party/ffmpeg/include/libavutil/macros.h new file mode 100644 index 000000000..57932eaeb --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/macros.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b63b3a268b096f0eed1e91b821714cff334e5dc5bb34365148704393ae15321e +size 2304 diff --git a/third_party/ffmpeg/include/libavutil/mastering_display_metadata.h b/third_party/ffmpeg/include/libavutil/mastering_display_metadata.h new file mode 100644 index 000000000..90983df17 --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/mastering_display_metadata.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9d5743a42306ac0158e26248a1281ae8be9ebfeb02e67f14e0e6ae770a543a65 +size 3944 diff --git a/third_party/ffmpeg/include/libavutil/mathematics.h b/third_party/ffmpeg/include/libavutil/mathematics.h new file mode 100644 index 000000000..e707131c8 --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/mathematics.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:64fac2eb3a42fd3788f5585ac8e65c7d5cd82711730d1f030042ba0a62fe1a62 +size 9563 diff --git a/third_party/ffmpeg/include/libavutil/md5.h b/third_party/ffmpeg/include/libavutil/md5.h new file mode 100644 index 000000000..33a63e05c --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/md5.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5b42de1758d289f78b4d20c47686f443e4ea8a5a6411c0deb357f709d2ef34d7 +size 2092 diff --git a/third_party/ffmpeg/include/libavutil/mem.h b/third_party/ffmpeg/include/libavutil/mem.h new file mode 100644 index 000000000..5a81cd674 --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/mem.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bf9a1d8c218c38b333efb16be076c5778d31d66a0691bdd6175f171c08283d7c +size 20457 diff --git a/third_party/ffmpeg/include/libavutil/motion_vector.h b/third_party/ffmpeg/include/libavutil/motion_vector.h new file mode 100644 index 000000000..6c61741e5 --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/motion_vector.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dc0b0a15a638c8b91df95a418c5951ee5e787d518f22b6e3d70094922536e8bb +size 1770 diff --git a/third_party/ffmpeg/include/libavutil/murmur3.h b/third_party/ffmpeg/include/libavutil/murmur3.h new file mode 100644 index 000000000..353b5a763 --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/murmur3.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:649258a51c4737fa19a025a489e2ac9e9b06a96eafa802f2765178c684382887 +size 3507 diff --git a/third_party/ffmpeg/include/libavutil/opt.h b/third_party/ffmpeg/include/libavutil/opt.h new file mode 100644 index 000000000..ef2b874b2 --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/opt.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4a1679c21453e4337cc7d377efaec6610340a59204c0b58fc102ab3a9da51a39 +size 37201 diff --git a/third_party/ffmpeg/include/libavutil/parseutils.h b/third_party/ffmpeg/include/libavutil/parseutils.h new file mode 100644 index 000000000..de2f938c6 --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/parseutils.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e8efed69396851f429a8258d50e9c4f0431f921687a7c31bf6db13d14f7482c3 +size 7888 diff --git a/third_party/ffmpeg/include/libavutil/pixdesc.h b/third_party/ffmpeg/include/libavutil/pixdesc.h new file mode 100644 index 000000000..ade8e6906 --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/pixdesc.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:253359c22bc051754a9ded9497a850e5296cbfd058a9b73957c23b53d7314d96 +size 16031 diff --git a/third_party/ffmpeg/include/libavutil/pixelutils.h b/third_party/ffmpeg/include/libavutil/pixelutils.h new file mode 100644 index 000000000..2734d292c --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/pixelutils.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:339cd6ffb6460d06401801c5dfb91ca66b9bdc028e1acc9ff4a0f447cfd3785c +size 2051 diff --git a/third_party/ffmpeg/include/libavutil/pixfmt.h b/third_party/ffmpeg/include/libavutil/pixfmt.h new file mode 100644 index 000000000..459bb057c --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/pixfmt.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:69104b943b1c44a54e46635be1e5da0ff6be7cd3f1af6ebc9924d4d965d0e420 +size 41556 diff --git a/third_party/ffmpeg/include/libavutil/random_seed.h b/third_party/ffmpeg/include/libavutil/random_seed.h new file mode 100644 index 000000000..a4e8933e5 --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/random_seed.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4490fd79919aadb18f765caac0c210d22cafa4d63cddcf9275e6f5bf66e2fdea +size 1889 diff --git a/third_party/ffmpeg/include/libavutil/rational.h b/third_party/ffmpeg/include/libavutil/rational.h new file mode 100644 index 000000000..32a39febc --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/rational.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:700422cc782acde6898522ffe1b74658b12ca22e2cfbb8f44986266216de7f21 +size 6100 diff --git a/third_party/ffmpeg/include/libavutil/rc4.h b/third_party/ffmpeg/include/libavutil/rc4.h new file mode 100644 index 000000000..a79ce5a1b --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/rc4.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4ea0fbee43677721ad0d846f703a785aaf9881794d1cca0bcb210241b260fc26 +size 2003 diff --git a/third_party/ffmpeg/include/libavutil/replaygain.h b/third_party/ffmpeg/include/libavutil/replaygain.h new file mode 100644 index 000000000..760b289ee --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/replaygain.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4ec82edbdc4e5493fba3cae6a27566f0f15d1399ccf16e25073ffd50ba8187ea +size 1607 diff --git a/third_party/ffmpeg/include/libavutil/ripemd.h b/third_party/ffmpeg/include/libavutil/ripemd.h new file mode 100644 index 000000000..34cd2714a --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/ripemd.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:df9ef8c29ee31e5bd8ea299b03d51bd25fe937583793a994db53d1df2b316620 +size 2158 diff --git a/third_party/ffmpeg/include/libavutil/samplefmt.h b/third_party/ffmpeg/include/libavutil/samplefmt.h new file mode 100644 index 000000000..c37f4ace9 --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/samplefmt.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e844d8b4a691256238d10de762a6a536ffe52995705166aee0d7f01ce79778a9 +size 10301 diff --git a/third_party/ffmpeg/include/libavutil/sha.h b/third_party/ffmpeg/include/libavutil/sha.h new file mode 100644 index 000000000..58b5ea658 --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/sha.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:91280db6995b1b99b9e5aad0aa211a3177dc4d2841da2fea097f54964b7891fd +size 2368 diff --git a/third_party/ffmpeg/include/libavutil/sha512.h b/third_party/ffmpeg/include/libavutil/sha512.h new file mode 100644 index 000000000..4e62f7fb6 --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/sha512.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:da265152798b221706d7fe95293a0e8cd18fa2b5087bf32504a8120f10e7658f +size 2413 diff --git a/third_party/ffmpeg/include/libavutil/spherical.h b/third_party/ffmpeg/include/libavutil/spherical.h new file mode 100644 index 000000000..2e14e4f29 --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/spherical.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:18b8e559f69a9c93b251c9d3956b2aba7f47ba46ef5a7fa01f8ae858967b73da +size 7997 diff --git a/third_party/ffmpeg/include/libavutil/stereo3d.h b/third_party/ffmpeg/include/libavutil/stereo3d.h new file mode 100644 index 000000000..952eb6cf7 --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/stereo3d.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5ece3ffb6baafd288f9f2d8747cae92b1b3985e6693fa2de6bc200173a1981b6 +size 5224 diff --git a/third_party/ffmpeg/include/libavutil/tea.h b/third_party/ffmpeg/include/libavutil/tea.h new file mode 100644 index 000000000..5b3094167 --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/tea.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3c1e93c566630bb4eeedad3ef3c8719bd6050081ac1c764b1fde81aba4969076 +size 2035 diff --git a/third_party/ffmpeg/include/libavutil/threadmessage.h b/third_party/ffmpeg/include/libavutil/threadmessage.h new file mode 100644 index 000000000..70c8112ba --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/threadmessage.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9bb242d7adc48662b947726843108aff7c34547d7a4a0d0e6f58f54a00fc4c9f +size 3910 diff --git a/third_party/ffmpeg/include/libavutil/time.h b/third_party/ffmpeg/include/libavutil/time.h new file mode 100644 index 000000000..ef77ac592 --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/time.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:40e11fa242e0585996753affb054443e78be25919b7c3063042d0aaff1656760 +size 1800 diff --git a/third_party/ffmpeg/include/libavutil/timecode.h b/third_party/ffmpeg/include/libavutil/timecode.h new file mode 100644 index 000000000..5b54b01bc --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/timecode.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:afd0a634b1abcb282c694ae6c43f0412fe2ccb37fb3f08433af33779ea9e572e +size 7843 diff --git a/third_party/ffmpeg/include/libavutil/timestamp.h b/third_party/ffmpeg/include/libavutil/timestamp.h new file mode 100644 index 000000000..828043fe8 --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/timestamp.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7cfbb6640449104c473cc1bc6f78244ce97636fca294da2867cedaab868c1b8c +size 2617 diff --git a/third_party/ffmpeg/include/libavutil/tree.h b/third_party/ffmpeg/include/libavutil/tree.h new file mode 100644 index 000000000..ca3f72fff --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/tree.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2f8e906917612a05c138036dea7ed9f8faee5899413a523fdad4eb51711bc1e5 +size 5408 diff --git a/third_party/ffmpeg/include/libavutil/twofish.h b/third_party/ffmpeg/include/libavutil/twofish.h new file mode 100644 index 000000000..96cf965ab --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/twofish.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b71714336821e1c606b65620ba4b1ea47e431666be41f3174facbc51047fd814 +size 2245 diff --git a/third_party/ffmpeg/include/libavutil/tx.h b/third_party/ffmpeg/include/libavutil/tx.h new file mode 100644 index 000000000..223ffc495 --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/tx.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:34f83ae31d074847e46579f25e3367210fbbfe1832b5315883cf4a28980ef101 +size 7140 diff --git a/third_party/ffmpeg/include/libavutil/uuid.h b/third_party/ffmpeg/include/libavutil/uuid.h new file mode 100644 index 000000000..3bc380c31 --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/uuid.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e669ce76a6b987e189b4d7ff62d0fd9ad6e334fa4967076cc6d912976574b646 +size 4895 diff --git a/third_party/ffmpeg/include/libavutil/version.h b/third_party/ffmpeg/include/libavutil/version.h new file mode 100644 index 000000000..1f0fa95e0 --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/version.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fed7c6f02a873de2c7e2f7e6d24e48e747b3f5204e36d50e19b5e82358b76470 +size 4832 diff --git a/third_party/ffmpeg/include/libavutil/video_enc_params.h b/third_party/ffmpeg/include/libavutil/video_enc_params.h new file mode 100644 index 000000000..7ce1b5702 --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/video_enc_params.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f287486c4f828f82e579f93ea98fccb98749129544f660decfa56da6f818fd57 +size 5991 diff --git a/third_party/ffmpeg/include/libavutil/video_hint.h b/third_party/ffmpeg/include/libavutil/video_hint.h new file mode 100644 index 000000000..46ad47b11 --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/video_hint.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7c8b7768ffd03af5034a13274d1ca0cc6206b9f2c0c478400aba8a6c79ad5359 +size 3586 diff --git a/third_party/ffmpeg/include/libavutil/xtea.h b/third_party/ffmpeg/include/libavutil/xtea.h new file mode 100644 index 000000000..99bdc08b3 --- /dev/null +++ b/third_party/ffmpeg/include/libavutil/xtea.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2eb91f780cc4ad86095e4ebbce453475d40f4e9b8737d52bdf20a068dfafcdf0 +size 2834 diff --git a/third_party/ffmpeg/include/x264.h b/third_party/ffmpeg/include/x264.h new file mode 100644 index 000000000..17c574177 --- /dev/null +++ b/third_party/ffmpeg/include/x264.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cec084bdb3cd2330800d0726bdbd99d91112df3fdb6d79f94e15ba67808b715c +size 49056 diff --git a/third_party/ffmpeg/include/x264_config.h b/third_party/ffmpeg/include/x264_config.h new file mode 100644 index 000000000..0b4ef0a07 --- /dev/null +++ b/third_party/ffmpeg/include/x264_config.h @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:81acf190604a6c03d8e44dd3a9751ea12dc4823cdff1c9f0b376faaeafe79ed2 +size 172 diff --git a/third_party/ffmpeg/larch64/lib/libavcodec.a b/third_party/ffmpeg/larch64/lib/libavcodec.a new file mode 100644 index 000000000..7e094e181 --- /dev/null +++ b/third_party/ffmpeg/larch64/lib/libavcodec.a @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9cbbf4f95193267b3799652b8c86975642a22ad6c91b1df40e24ec602d315368 +size 2368942 diff --git a/third_party/ffmpeg/larch64/lib/libavformat.a b/third_party/ffmpeg/larch64/lib/libavformat.a new file mode 100644 index 000000000..0cfd4d2df --- /dev/null +++ b/third_party/ffmpeg/larch64/lib/libavformat.a @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f12e0f2a24ff87f328b4045b91d6453133588c2972a96980a05fc6339ff27eec +size 946966 diff --git a/third_party/ffmpeg/larch64/lib/libavutil.a b/third_party/ffmpeg/larch64/lib/libavutil.a new file mode 100644 index 000000000..027081154 --- /dev/null +++ b/third_party/ffmpeg/larch64/lib/libavutil.a @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c0944c03c21379aa6c264e0e1b59538789e0a15c6c77e5ac0ef8d571cb6f5b01 +size 891284 diff --git a/third_party/ffmpeg/larch64/lib/libx264.a b/third_party/ffmpeg/larch64/lib/libx264.a new file mode 100644 index 000000000..5a95720c7 --- /dev/null +++ b/third_party/ffmpeg/larch64/lib/libx264.a @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ceeac8654bb25feea79f64e91197e20a26fee4c2c36af6eae9f13943d8acf2c1 +size 2222366 diff --git a/third_party/ffmpeg/x86_64/lib/libavcodec.a b/third_party/ffmpeg/x86_64/lib/libavcodec.a new file mode 100644 index 000000000..00a4f4b48 --- /dev/null +++ b/third_party/ffmpeg/x86_64/lib/libavcodec.a @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:32ac488b287137c0537da1a81b3fa81e074bb79d9bf5af58185779f3dfe70070 +size 3267672 diff --git a/third_party/ffmpeg/x86_64/lib/libavformat.a b/third_party/ffmpeg/x86_64/lib/libavformat.a new file mode 100644 index 000000000..497fc2a34 --- /dev/null +++ b/third_party/ffmpeg/x86_64/lib/libavformat.a @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:53ba309c4adffc993671f4700e316fd5886f28aead91f051261ae1ffe8dbb03e +size 919414 diff --git a/third_party/ffmpeg/x86_64/lib/libavutil.a b/third_party/ffmpeg/x86_64/lib/libavutil.a new file mode 100644 index 000000000..0fc452317 --- /dev/null +++ b/third_party/ffmpeg/x86_64/lib/libavutil.a @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e4fed041aafbd9672bfff2b8988256ebd3fd77892c4ecdef705c7b81173a3c56 +size 1022522 diff --git a/third_party/ffmpeg/x86_64/lib/libx264.a b/third_party/ffmpeg/x86_64/lib/libx264.a new file mode 100644 index 000000000..84b9d4742 --- /dev/null +++ b/third_party/ffmpeg/x86_64/lib/libx264.a @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e17625b8355736b3d5a8bf8ef49d10b8a531783b2c74d41246be7a35fdce3dff +size 3065330 diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index 89be3cceb..eb6f15817 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -16,7 +16,7 @@ qt_libs = ['qt_util'] + base_libs cabana_env = qt_env.Clone() -cabana_libs = [widgets, cereal, messaging, visionipc, replay_lib, 'avutil', 'avcodec', 'avformat', 'bz2', 'zstd', 'curl', 'yuv', 'usb-1.0'] + qt_libs +cabana_libs = [widgets, cereal, messaging, visionipc, replay_lib, 'avformat', 'avcodec', 'avutil', 'x264', 'bz2', 'zstd', 'curl', 'yuv', 'usb-1.0'] + qt_libs opendbc_path = '-DOPENDBC_FILE_PATH=\'"%s"\'' % (cabana_env.Dir("../../opendbc/dbc").abspath) cabana_env['CXXFLAGS'] += [opendbc_path] diff --git a/tools/install_ubuntu_dependencies.sh b/tools/install_ubuntu_dependencies.sh index f33569704..6abc10c8e 100755 --- a/tools/install_ubuntu_dependencies.sh +++ b/tools/install_ubuntu_dependencies.sh @@ -32,12 +32,6 @@ function install_ubuntu_common_requirements() { libcurl4-openssl-dev \ git \ git-lfs \ - ffmpeg \ - libavformat-dev \ - libavcodec-dev \ - libavdevice-dev \ - libavutil-dev \ - libavfilter-dev \ libbz2-dev \ libeigen3-dev \ libffi-dev \ diff --git a/tools/mac_setup.sh b/tools/mac_setup.sh index 19ef77e01..b5ee988cd 100755 --- a/tools/mac_setup.sh +++ b/tools/mac_setup.sh @@ -38,7 +38,6 @@ brew "zlib" brew "capnp" brew "coreutils" brew "eigen" -brew "ffmpeg" brew "glfw" brew "libarchive" brew "libusb" diff --git a/tools/replay/SConscript b/tools/replay/SConscript index 136c4119f..013cf7658 100644 --- a/tools/replay/SConscript +++ b/tools/replay/SConscript @@ -17,7 +17,7 @@ if arch != "Darwin": replay_lib_src.append("qcom_decoder.cc") replay_lib = replay_env.Library("replay", replay_lib_src, LIBS=base_libs, FRAMEWORKS=base_frameworks) Export('replay_lib') -replay_libs = [replay_lib, 'avutil', 'avcodec', 'avformat', 'bz2', 'zstd', 'curl', 'yuv', 'ncurses'] + base_libs +replay_libs = [replay_lib, 'avformat', 'avcodec', 'avutil', 'bz2', 'zstd', 'curl', 'yuv', 'ncurses', 'x264'] + base_libs replay_env.Program("replay", ["main.cc"], LIBS=replay_libs, FRAMEWORKS=base_frameworks) if GetOption('extras'): From 1870d4905b99f65f3eadaf3c4c5923cc43bc12f7 Mon Sep 17 00:00:00 2001 From: Jimmy <9859727+Quantizr@users.noreply.github.com> Date: Fri, 12 Sep 2025 21:52:01 -0700 Subject: [PATCH 007/910] jotpluggler: add tabs to layout (#36146) * queue syncs in main thread to avoid Glfw Error/segfault * tabs --- tools/jotpluggler/assets/plus.png | 3 + tools/jotpluggler/layout.py | 163 ++++++++++++++++++++++++++---- tools/jotpluggler/pluggle.py | 12 +-- tools/jotpluggler/views.py | 27 +++-- 4 files changed, 171 insertions(+), 34 deletions(-) create mode 100644 tools/jotpluggler/assets/plus.png diff --git a/tools/jotpluggler/assets/plus.png b/tools/jotpluggler/assets/plus.png new file mode 100644 index 000000000..6f8388b24 --- /dev/null +++ b/tools/jotpluggler/assets/plus.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:248b71eafd1b42b0861da92114da3d625221cd88121fff01e0514bf3d79ff3b1 +size 1364 diff --git a/tools/jotpluggler/layout.py b/tools/jotpluggler/layout.py index 917c156f9..b73b7467a 100644 --- a/tools/jotpluggler/layout.py +++ b/tools/jotpluggler/layout.py @@ -5,15 +5,135 @@ from openpilot.tools.jotpluggler.views import TimeSeriesPanel GRIP_SIZE = 4 MIN_PANE_SIZE = 60 - -class PlotLayoutManager: - def __init__(self, data_manager: DataManager, playback_manager, worker_manager, scale: float = 1.0): +class LayoutManager: + def __init__(self, data_manager, playback_manager, worker_manager, scale: float = 1.0): self.data_manager = data_manager self.playback_manager = playback_manager self.worker_manager = worker_manager self.scale = scale self.container_tag = "plot_layout_container" + self.tab_bar_tag = "tab_bar_container" + self.tab_content_tag = "tab_content_area" + + self.active_tab = 0 + initial_panel_layout = PanelLayoutManager(data_manager, playback_manager, worker_manager, scale) + self.tabs: dict = {0: {"name": "Tab 1", "panel_layout": initial_panel_layout}} + self._next_tab_id = self.active_tab + 1 + + self._create_tab_themes() + + def _create_tab_themes(self): + for tag, color in (("active_tab_theme", (37, 37, 38, 255)), ("inactive_tab_theme", (70, 70, 75, 255))): + with dpg.theme(tag=tag): + for cmp, target in ((dpg.mvChildWindow, dpg.mvThemeCol_ChildBg), (dpg.mvInputText, dpg.mvThemeCol_FrameBg), (dpg.mvImageButton, dpg.mvThemeCol_Button)): + with dpg.theme_component(cmp): + dpg.add_theme_color(target, color) + with dpg.theme(tag="tab_bar_theme"): + with dpg.theme_component(dpg.mvChildWindow): + dpg.add_theme_color(dpg.mvThemeCol_ChildBg, (51, 51, 55, 255)) + + def create_ui(self, parent_tag: str): + if dpg.does_item_exist(self.container_tag): + dpg.delete_item(self.container_tag) + + with dpg.child_window(tag=self.container_tag, parent=parent_tag, border=False, width=-1, height=-1, no_scrollbar=True, no_scroll_with_mouse=True): + self._create_tab_bar() + self._create_tab_content() + dpg.bind_item_theme(self.tab_bar_tag, "tab_bar_theme") + + def _create_tab_bar(self): + text_size = int(13 * self.scale) + with dpg.child_window(tag=self.tab_bar_tag, parent=self.container_tag, height=(text_size + 8), border=False, horizontal_scrollbar=True): + with dpg.group(horizontal=True, tag="tab_bar_group"): + for tab_id, tab_data in self.tabs.items(): + self._create_tab_ui(tab_id, tab_data["name"]) + dpg.add_image_button(texture_tag="plus_texture", callback=self.add_tab, width=text_size, height=text_size, tag="add_tab_button") + dpg.bind_item_theme("add_tab_button", "inactive_tab_theme") + + def _create_tab_ui(self, tab_id: int, tab_name: str): + text_size = int(13 * self.scale) + tab_width = int(120 * self.scale) + with dpg.child_window(width=tab_width, height=-1, border=False, no_scrollbar=True, tag=f"tab_window_{tab_id}", parent="tab_bar_group"): + with dpg.group(horizontal=True, tag=f"tab_group_{tab_id}"): + dpg.add_input_text( + default_value=tab_name, width=tab_width - text_size - 16, callback=lambda s, v, u: self.rename_tab(u, v), user_data=tab_id, tag=f"tab_input_{tab_id}" + ) + dpg.add_image_button( + texture_tag="x_texture", callback=lambda s, a, u: self.close_tab(u), user_data=tab_id, width=text_size, height=text_size, tag=f"tab_close_{tab_id}" + ) + with dpg.item_handler_registry(tag=f"tab_handler_{tab_id}"): + dpg.add_item_clicked_handler(callback=lambda s, a, u: self.switch_tab(u), user_data=tab_id) + dpg.bind_item_handler_registry(f"tab_group_{tab_id}", f"tab_handler_{tab_id}") + + theme_tag = "active_tab_theme" if tab_id == self.active_tab else "inactive_tab_theme" + dpg.bind_item_theme(f"tab_window_{tab_id}", theme_tag) + + def _create_tab_content(self): + with dpg.child_window(tag=self.tab_content_tag, parent=self.container_tag, border=False, width=-1, height=-1, no_scrollbar=True, no_scroll_with_mouse=True): + active_panel_layout = self.tabs[self.active_tab]["panel_layout"] + active_panel_layout.create_ui() + + def add_tab(self): + new_panel_layout = PanelLayoutManager(self.data_manager, self.playback_manager, self.worker_manager, self.scale) + new_tab = {"name": f"Tab {self._next_tab_id + 1}", "panel_layout": new_panel_layout} + self.tabs[ self._next_tab_id] = new_tab + self._create_tab_ui( self._next_tab_id, new_tab["name"]) + dpg.move_item("add_tab_button", parent="tab_bar_group") # move plus button to end + self.switch_tab( self._next_tab_id) + self._next_tab_id += 1 + + def close_tab(self, tab_id: int): + if len(self.tabs) <= 1: + return # don't allow closing the last tab + + tab_to_close = self.tabs[tab_id] + tab_to_close["panel_layout"].destroy_ui() + for suffix in ["window", "group", "input", "close", "handler"]: + tag = f"tab_{suffix}_{tab_id}" + if dpg.does_item_exist(tag): + dpg.delete_item(tag) + del self.tabs[tab_id] + + if self.active_tab == tab_id: # switch to another tab if we closed the active one + self.active_tab = next(iter(self.tabs.keys())) + self._switch_tab_content() + dpg.bind_item_theme(f"tab_window_{self.active_tab}", "active_tab_theme") + + def switch_tab(self, tab_id: int): + if tab_id == self.active_tab or tab_id not in self.tabs: + return + + current_panel_layout = self.tabs[self.active_tab]["panel_layout"] + current_panel_layout.destroy_ui() + dpg.bind_item_theme(f"tab_window_{self.active_tab}", "inactive_tab_theme") # deactivate old tab + self.active_tab = tab_id + dpg.bind_item_theme(f"tab_window_{tab_id}", "active_tab_theme") # activate new tab + self._switch_tab_content() + + def _switch_tab_content(self): + dpg.delete_item(self.tab_content_tag, children_only=True) + active_panel_layout = self.tabs[self.active_tab]["panel_layout"] + active_panel_layout.create_ui() + active_panel_layout.update_all_panels() + + def rename_tab(self, tab_id: int, new_name: str): + if tab_id in self.tabs: + self.tabs[tab_id]["name"] = new_name + + def update_all_panels(self): + self.tabs[self.active_tab]["panel_layout"].update_all_panels() + + def on_viewport_resize(self): + self.tabs[self.active_tab]["panel_layout"].on_viewport_resize() + +class PanelLayoutManager: + def __init__(self, data_manager: DataManager, playback_manager, worker_manager, scale: float = 1.0): + self.data_manager = data_manager + self.playback_manager = playback_manager + self.worker_manager = worker_manager + self.scale = scale self.active_panels: list = [] + self.parent_tag = "tab_content_area" self.grip_size = int(GRIP_SIZE * self.scale) self.min_pane_size = int(MIN_PANE_SIZE * self.scale) @@ -21,13 +141,18 @@ class PlotLayoutManager: initial_panel = TimeSeriesPanel(data_manager, playback_manager, worker_manager) self.layout: dict = {"type": "panel", "panel": initial_panel} - def create_ui(self, parent_tag: str): - if dpg.does_item_exist(self.container_tag): - dpg.delete_item(self.container_tag) + def create_ui(self): + self.active_panels.clear() - with dpg.child_window(tag=self.container_tag, parent=parent_tag, border=False, width=-1, height=-1, no_scrollbar=True, no_scroll_with_mouse=True): - container_width, container_height = dpg.get_item_rect_size(self.container_tag) - self._create_ui_recursive(self.layout, self.container_tag, [], container_width, container_height) + if dpg.does_item_exist(self.parent_tag): + dpg.delete_item(self.parent_tag, children_only=True) + + container_width, container_height = dpg.get_item_rect_size(self.parent_tag) + self._create_ui_recursive(self.layout, self.parent_tag, [], container_width, container_height) + + def destroy_ui(self): + self._cleanup_ui_recursive(self.layout, []) + self.active_panels.clear() def _create_ui_recursive(self, layout: dict, parent_tag: str, path: list[int], width: int, height: int): if layout["type"] == "panel": @@ -35,14 +160,14 @@ class PlotLayoutManager: else: self._create_split_ui(layout, parent_tag, path, width, height) - def _create_panel_ui(self, layout: dict, parent_tag: str, path: list[int], width: int, height:int): + def _create_panel_ui(self, layout: dict, parent_tag: str, path: list[int], width: int, height: int): panel_tag = self._path_to_tag(path, "panel") panel = layout["panel"] self.active_panels.append(panel) text_size = int(13 * self.scale) - bar_height = (text_size+24) if width < int(279 * self.scale + 80) else (text_size+8) # adjust height to allow for scrollbar + bar_height = (text_size + 24) if width < int(279 * self.scale + 64) else (text_size + 8) # adjust height to allow for scrollbar - with dpg.child_window(parent=parent_tag, border=True, width=-1, height=-1, no_scrollbar=True): + with dpg.child_window(parent=parent_tag, border=False, width=-1, height=-1, no_scrollbar=True): with dpg.group(horizontal=True): with dpg.child_window(tag=panel_tag, width=-(text_size + 16), height=bar_height, horizontal_scrollbar=True, no_scroll_with_mouse=True, border=False): with dpg.group(horizontal=True): @@ -67,7 +192,7 @@ class PlotLayoutManager: for i, child_layout in enumerate(layout["children"]): child_path = path + [i] container_tag = self._path_to_tag(child_path, "container") - pane_width, pane_height = [(pane_sizes[i], -1), (-1, pane_sizes[i])][orientation] # fill 2nd dim up to the border + pane_width, pane_height = [(pane_sizes[i], -1), (-1, pane_sizes[i])][orientation] # fill 2nd dim up to the border with dpg.child_window(tag=container_tag, width=pane_width, height=pane_height, border=False, no_scrollbar=True): child_width, child_height = [(pane_sizes[i], height), (width, pane_sizes[i])][orientation] self._create_ui_recursive(child_layout, container_tag, child_path, child_width, child_height) @@ -137,7 +262,7 @@ class PlotLayoutManager: if path: container_tag = self._path_to_tag(path, "container") else: # Root update - container_tag = self.container_tag + container_tag = self.parent_tag self._cleanup_ui_recursive(layout, path) dpg.delete_item(container_tag, children_only=True) @@ -181,17 +306,17 @@ class PlotLayoutManager: dpg.configure_item(container_tag, **{size_properties[orientation]: pane_sizes[i]}) child_width, child_height = [(pane_sizes[i], available_sizes[1]), (available_sizes[0], pane_sizes[i])][orientation] self._resize_splits_recursive(child_layout, child_path, child_width, child_height) - else: # leaf node/panel - adjust bar height to allow for scrollbar + else: # leaf node/panel - adjust bar height to allow for scrollbar panel_tag = self._path_to_tag(path, "panel") - if width is not None and width < int(279 * self.scale + 80): # scaled widths of the elements in top bar + fixed 8 padding on left and right of each item - dpg.configure_item(panel_tag, height=(int(13*self.scale) + 24)) + if width is not None and width < int(279 * self.scale + 64): # scaled widths of the elements in top bar + fixed 8 padding on left and right of each item + dpg.configure_item(panel_tag, height=(int(13 * self.scale) + 24)) else: - dpg.configure_item(panel_tag, height=(int(13*self.scale) + 8)) + dpg.configure_item(panel_tag, height=(int(13 * self.scale) + 8)) def _get_split_geometry(self, layout: dict, available_size: tuple[int, int]) -> tuple[int, int, list[int]]: orientation = layout["orientation"] num_grips = len(layout["children"]) - 1 - usable_size = max(self.min_pane_size, available_size[orientation] - (num_grips * (self.grip_size + 8 * (2-orientation)))) # approximate, scaling is weird + usable_size = max(self.min_pane_size, available_size[orientation] - (num_grips * (self.grip_size + 8 * (2 - orientation)))) # approximate, scaling is weird pane_sizes = [max(self.min_pane_size, int(usable_size * prop)) for prop in layout["proportions"]] return orientation, usable_size, pane_sizes diff --git a/tools/jotpluggler/pluggle.py b/tools/jotpluggler/pluggle.py index 57ba2a245..41bf520cd 100755 --- a/tools/jotpluggler/pluggle.py +++ b/tools/jotpluggler/pluggle.py @@ -10,7 +10,7 @@ import signal from openpilot.common.basedir import BASEDIR from openpilot.tools.jotpluggler.data import DataManager from openpilot.tools.jotpluggler.datatree import DataTree -from openpilot.tools.jotpluggler.layout import PlotLayoutManager +from openpilot.tools.jotpluggler.layout import LayoutManager DEMO_ROUTE = "a2a0ccea32023010|2023-07-27--13-01-19" @@ -127,7 +127,7 @@ class MainController: self.worker_manager = WorkerManager() self._create_global_themes() self.data_tree = DataTree(self.data_manager, self.playback_manager) - self.plot_layout_manager = PlotLayoutManager(self.data_manager, self.playback_manager, self.worker_manager, scale=self.scale) + self.layout_manager = LayoutManager(self.data_manager, self.playback_manager, self.worker_manager, scale=self.scale) self.data_manager.add_observer(self.on_data_loaded) def _create_global_themes(self): @@ -168,7 +168,7 @@ class MainController: def setup_ui(self): with dpg.texture_registry(): script_dir = os.path.dirname(os.path.realpath(__file__)) - for image in ["play", "pause", "x", "split_h", "split_v"]: + for image in ["play", "pause", "x", "split_h", "split_v", "plus"]: texture = dpg.load_image(os.path.join(script_dir, "assets", f"{image}.png")) dpg.add_static_texture(width=texture[0], height=texture[1], default_value=texture[3], tag=f"{image}_texture") @@ -186,7 +186,7 @@ class MainController: # Right panel - Plots and timeline with dpg.group(tag="right_panel"): with dpg.child_window(label="Plot Window", border=True, height=-(32 + 13 * self.scale), tag="main_plot_area"): - self.plot_layout_manager.create_ui("main_plot_area") + self.layout_manager.create_ui("main_plot_area") with dpg.child_window(label="Timeline", border=True): with dpg.table(header_row=False, borders_innerH=False, borders_innerV=False, borders_outerH=False, borders_outerV=False): @@ -205,7 +205,7 @@ class MainController: dpg.set_primary_window("Primary Window", True) def on_plot_resize(self, sender, app_data, user_data): - self.plot_layout_manager.on_viewport_resize() + self.layout_manager.on_viewport_resize() def load_route(self): route_name = dpg.get_value("route_input").strip() @@ -227,7 +227,7 @@ class MainController: if not dpg.is_item_active("timeline_slider"): dpg.set_value("timeline_slider", new_time) - self.plot_layout_manager.update_all_panels() + self.layout_manager.update_all_panels() dpg.set_value("fps_counter", f"{dpg.get_frame_rate():.1f} FPS") diff --git a/tools/jotpluggler/views.py b/tools/jotpluggler/views.py index f3da6e3e6..cb993acf0 100644 --- a/tools/jotpluggler/views.py +++ b/tools/jotpluggler/views.py @@ -52,6 +52,8 @@ class TimeSeriesPanel(ViewPanel): self._results_deque: deque[tuple[str, list, list]] = deque() self._new_data = False self._last_x_limits = (0.0, 0.0) + self._queued_x_sync: tuple | None = None + self._queued_reallow_x_zoom = False def create_ui(self, parent_tag: str): self.data_manager.add_observer(self.on_data_loaded) @@ -63,8 +65,7 @@ class TimeSeriesPanel(ViewPanel): timeline_series_tag = dpg.add_inf_line_series(x=[0], label="Timeline", parent=self.y_axis_tag, tag=self.timeline_indicator_tag) dpg.bind_item_theme(timeline_series_tag, "global_timeline_theme") - for series_path in list(self._series_data.keys()): - self.add_series(series_path) + self._new_data = True self._ui_created = True def update(self): @@ -72,6 +73,19 @@ class TimeSeriesPanel(ViewPanel): if not self._ui_created: return + if self._queued_x_sync: + min_time, max_time = self._queued_x_sync + self._queued_x_sync = None + dpg.set_axis_limits(self.x_axis_tag, min_time, max_time) + self._last_x_limits = (min_time, max_time) + self._fit_y_axis(min_time, max_time) + self._queued_reallow_x_zoom = True # must wait a frame before allowing user changes so that axis limits take effect + return + + if self._queued_reallow_x_zoom: + self._queued_reallow_x_zoom = False + dpg.set_axis_limits_auto(self.x_axis_tag) + current_limits = dpg.get_axis_limits(self.x_axis_tag) # downsample if plot zoom changed significantly plot_duration = current_limits[1] - current_limits[0] @@ -112,13 +126,8 @@ class TimeSeriesPanel(ViewPanel): def _on_x_axis_sync(self, min_time: float, max_time: float, source_panel): with self._update_lock: - if source_panel == self or not self._ui_created: - return - dpg.set_axis_limits(self.x_axis_tag, min_time, max_time) - dpg.render_dearpygui_frame() - dpg.set_axis_limits_auto(self.x_axis_tag) - self._last_x_limits = (min_time, max_time) - self._fit_y_axis(min_time, max_time) + if source_panel != self: + self._queued_x_sync = (min_time, max_time) def _fit_y_axis(self, x_min: float, x_max: float): if not self._series_data: From 826c5e96a1a71b5650c82793e2723f2000437135 Mon Sep 17 00:00:00 2001 From: Jimmy <9859727+Quantizr@users.noreply.github.com> Date: Fri, 12 Sep 2025 22:15:56 -0700 Subject: [PATCH 008/910] jotpluggler: migrate logs (#36147) migrate logs --- tools/jotpluggler/data.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/jotpluggler/data.py b/tools/jotpluggler/data.py index 100dfe544..14d54e6be 100644 --- a/tools/jotpluggler/data.py +++ b/tools/jotpluggler/data.py @@ -5,6 +5,7 @@ import bisect from collections import defaultdict from tqdm import tqdm from openpilot.common.swaglog import cloudlog +from openpilot.selfdrive.test.process_replay.migration import migrate_all from openpilot.tools.lib.logreader import _LogFileReader, LogReader @@ -199,7 +200,8 @@ def msgs_to_time_series(msgs): def _process_segment(segment_identifier: str): try: lr = _LogFileReader(segment_identifier, sort_by_time=True) - return msgs_to_time_series(lr) + migrated_msgs = migrate_all(lr) + return msgs_to_time_series(migrated_msgs) except Exception as e: cloudlog.warning(f"Warning: Failed to process segment {segment_identifier}: {e}") return {}, 0.0, 0.0 From 63df46bf22a05ac2118a7f5d35b5d1bbab5bcae7 Mon Sep 17 00:00:00 2001 From: Jimmy <9859727+Quantizr@users.noreply.github.com> Date: Fri, 12 Sep 2025 23:20:12 -0700 Subject: [PATCH 009/910] jotpluggler: store and load layouts (#36148) * store and load layouts * torque controller layout * ignore missing yaml stubs for mypy --- tools/jotpluggler/layout.py | 130 ++++++++++++++---- .../layouts/torque-controller.yaml | 128 +++++++++++++++++ tools/jotpluggler/pluggle.py | 83 ++++++++++- tools/jotpluggler/views.py | 27 +++- 4 files changed, 335 insertions(+), 33 deletions(-) create mode 100644 tools/jotpluggler/layouts/torque-controller.yaml diff --git a/tools/jotpluggler/layout.py b/tools/jotpluggler/layout.py index b73b7467a..13fbee54e 100644 --- a/tools/jotpluggler/layout.py +++ b/tools/jotpluggler/layout.py @@ -20,17 +20,35 @@ class LayoutManager: self.tabs: dict = {0: {"name": "Tab 1", "panel_layout": initial_panel_layout}} self._next_tab_id = self.active_tab + 1 - self._create_tab_themes() + def to_dict(self) -> dict: + return { + "tabs": { + str(tab_id): { + "name": tab_data["name"], + "panel_layout": tab_data["panel_layout"].to_dict() + } + for tab_id, tab_data in self.tabs.items() + } + } - def _create_tab_themes(self): - for tag, color in (("active_tab_theme", (37, 37, 38, 255)), ("inactive_tab_theme", (70, 70, 75, 255))): - with dpg.theme(tag=tag): - for cmp, target in ((dpg.mvChildWindow, dpg.mvThemeCol_ChildBg), (dpg.mvInputText, dpg.mvThemeCol_FrameBg), (dpg.mvImageButton, dpg.mvThemeCol_Button)): - with dpg.theme_component(cmp): - dpg.add_theme_color(target, color) - with dpg.theme(tag="tab_bar_theme"): - with dpg.theme_component(dpg.mvChildWindow): - dpg.add_theme_color(dpg.mvThemeCol_ChildBg, (51, 51, 55, 255)) + def clear_and_load_from_dict(self, data: dict): + tab_ids_to_close = list(self.tabs.keys()) + for tab_id in tab_ids_to_close: + self.close_tab(tab_id, force=True) + + for tab_id_str, tab_data in data["tabs"].items(): + tab_id = int(tab_id_str) + panel_layout = PanelLayoutManager.load_from_dict( + tab_data["panel_layout"], self.data_manager, self.playback_manager, + self.worker_manager, self.scale + ) + self.tabs[tab_id] = { + "name": tab_data["name"], + "panel_layout": panel_layout + } + + self.active_tab = min(self.tabs.keys()) if self.tabs else 0 + self._next_tab_id = max(self.tabs.keys()) + 1 if self.tabs else 1 def create_ui(self, parent_tag: str): if dpg.does_item_exist(self.container_tag): @@ -52,7 +70,7 @@ class LayoutManager: def _create_tab_ui(self, tab_id: int, tab_name: str): text_size = int(13 * self.scale) - tab_width = int(120 * self.scale) + tab_width = int(140 * self.scale) with dpg.child_window(width=tab_width, height=-1, border=False, no_scrollbar=True, tag=f"tab_window_{tab_id}", parent="tab_bar_group"): with dpg.group(horizontal=True, tag=f"tab_group_{tab_id}"): dpg.add_input_text( @@ -70,20 +88,21 @@ class LayoutManager: def _create_tab_content(self): with dpg.child_window(tag=self.tab_content_tag, parent=self.container_tag, border=False, width=-1, height=-1, no_scrollbar=True, no_scroll_with_mouse=True): - active_panel_layout = self.tabs[self.active_tab]["panel_layout"] - active_panel_layout.create_ui() + if self.active_tab in self.tabs: + active_panel_layout = self.tabs[self.active_tab]["panel_layout"] + active_panel_layout.create_ui() def add_tab(self): new_panel_layout = PanelLayoutManager(self.data_manager, self.playback_manager, self.worker_manager, self.scale) new_tab = {"name": f"Tab {self._next_tab_id + 1}", "panel_layout": new_panel_layout} - self.tabs[ self._next_tab_id] = new_tab - self._create_tab_ui( self._next_tab_id, new_tab["name"]) + self.tabs[self._next_tab_id] = new_tab + self._create_tab_ui(self._next_tab_id, new_tab["name"]) dpg.move_item("add_tab_button", parent="tab_bar_group") # move plus button to end - self.switch_tab( self._next_tab_id) + self.switch_tab(self._next_tab_id) self._next_tab_id += 1 - def close_tab(self, tab_id: int): - if len(self.tabs) <= 1: + def close_tab(self, tab_id: int, force = False): + if len(self.tabs) <= 1 and not force: return # don't allow closing the last tab tab_to_close = self.tabs[tab_id] @@ -94,7 +113,7 @@ class LayoutManager: dpg.delete_item(tag) del self.tabs[tab_id] - if self.active_tab == tab_id: # switch to another tab if we closed the active one + if self.active_tab == tab_id and self.tabs: # switch to another tab if we closed the active one self.active_tab = next(iter(self.tabs.keys())) self._switch_tab_content() dpg.bind_item_theme(f"tab_window_{self.active_tab}", "active_tab_theme") @@ -134,6 +153,8 @@ class PanelLayoutManager: self.scale = scale self.active_panels: list = [] self.parent_tag = "tab_content_area" + self._queue_resize = False + self._created_handler_tags: set[str] = set() self.grip_size = int(GRIP_SIZE * self.scale) self.min_pane_size = int(MIN_PANE_SIZE * self.scale) @@ -141,19 +162,70 @@ class PanelLayoutManager: initial_panel = TimeSeriesPanel(data_manager, playback_manager, worker_manager) self.layout: dict = {"type": "panel", "panel": initial_panel} + def to_dict(self) -> dict: + return self._layout_to_dict(self.layout) + + def _layout_to_dict(self, layout: dict) -> dict: + if layout["type"] == "panel": + return { + "type": "panel", + "panel": layout["panel"].to_dict() + } + else: # split + return { + "type": "split", + "orientation": layout["orientation"], + "proportions": layout["proportions"], + "children": [self._layout_to_dict(child) for child in layout["children"]] + } + + @classmethod + def load_from_dict(cls, data: dict, data_manager, playback_manager, worker_manager, scale: float = 1.0): + manager = cls(data_manager, playback_manager, worker_manager, scale) + manager.layout = manager._dict_to_layout(data) + return manager + + def _dict_to_layout(self, data: dict) -> dict: + if data["type"] == "panel": + panel_data = data["panel"] + if panel_data["type"] == "timeseries": + panel = TimeSeriesPanel.load_from_dict( + panel_data, self.data_manager, self.playback_manager, self.worker_manager + ) + return {"type": "panel", "panel": panel} + else: + # Handle future panel types here or make a general mapping + raise ValueError(f"Unknown panel type: {panel_data['type']}") + else: # split + return { + "type": "split", + "orientation": data["orientation"], + "proportions": data["proportions"], + "children": [self._dict_to_layout(child) for child in data["children"]] + } + def create_ui(self): self.active_panels.clear() - if dpg.does_item_exist(self.parent_tag): dpg.delete_item(self.parent_tag, children_only=True) + self._cleanup_all_handlers() container_width, container_height = dpg.get_item_rect_size(self.parent_tag) + if container_width == 0 and container_height == 0: + self._queue_resize = True self._create_ui_recursive(self.layout, self.parent_tag, [], container_width, container_height) def destroy_ui(self): self._cleanup_ui_recursive(self.layout, []) + self._cleanup_all_handlers() self.active_panels.clear() + def _cleanup_all_handlers(self): + for handler_tag in list(self._created_handler_tags): + if dpg.does_item_exist(handler_tag): + dpg.delete_item(handler_tag) + self._created_handler_tags.clear() + def _create_ui_recursive(self, layout: dict, parent_tag: str, path: list[int], width: int, height: int): if layout["type"] == "panel": self._create_panel_ui(layout, parent_tag, path, width, height) @@ -165,13 +237,14 @@ class PanelLayoutManager: panel = layout["panel"] self.active_panels.append(panel) text_size = int(13 * self.scale) - bar_height = (text_size + 24) if width < int(279 * self.scale + 64) else (text_size + 8) # adjust height to allow for scrollbar + bar_height = (text_size + 24) if width < int(329 * self.scale + 64) else (text_size + 8) # adjust height to allow for scrollbar with dpg.child_window(parent=parent_tag, border=False, width=-1, height=-1, no_scrollbar=True): with dpg.group(horizontal=True): with dpg.child_window(tag=panel_tag, width=-(text_size + 16), height=bar_height, horizontal_scrollbar=True, no_scroll_with_mouse=True, border=False): with dpg.group(horizontal=True): - dpg.add_input_text(default_value=panel.title, width=int(100 * self.scale), callback=lambda s, v: setattr(panel, "title", v)) + # if you change the widths make sure to change the sum of widths (currently 329 * scale) + dpg.add_input_text(default_value=panel.title, width=int(150 * self.scale), callback=lambda s, v: setattr(panel, "title", v)) dpg.add_combo(items=["Time Series"], default_value="Time Series", width=int(100 * self.scale)) dpg.add_button(label="Clear", callback=lambda: self.clear_panel(panel), width=int(40 * self.scale)) dpg.add_image_button(texture_tag="split_h_texture", callback=lambda: self.split_panel(path, 0), width=text_size, height=text_size) @@ -280,11 +353,16 @@ class PanelLayoutManager: handler_tag = f"{self._path_to_tag(path, f'grip_{i}')}_handler" if dpg.does_item_exist(handler_tag): dpg.delete_item(handler_tag) + self._created_handler_tags.discard(handler_tag) for i, child in enumerate(layout["children"]): self._cleanup_ui_recursive(child, path + [i]) def update_all_panels(self): + if self._queue_resize: + if (size := dpg.get_item_rect_size(self.parent_tag)) != [0, 0]: + self._queue_resize = False + self._resize_splits_recursive(self.layout, [], *size) for panel in self.active_panels: panel.update() @@ -308,7 +386,7 @@ class PanelLayoutManager: self._resize_splits_recursive(child_layout, child_path, child_width, child_height) else: # leaf node/panel - adjust bar height to allow for scrollbar panel_tag = self._path_to_tag(path, "panel") - if width is not None and width < int(279 * self.scale + 64): # scaled widths of the elements in top bar + fixed 8 padding on left and right of each item + if width is not None and width < int(329 * self.scale + 64): # scaled widths of the elements in top bar + fixed 8 padding on left and right of each item dpg.configure_item(panel_tag, height=(int(13 * self.scale) + 24)) else: dpg.configure_item(panel_tag, height=(int(13 * self.scale) + 8)) @@ -342,16 +420,18 @@ class PanelLayoutManager: def _create_grip(self, parent_tag: str, path: list[int], grip_index: int, orientation: int): grip_tag = self._path_to_tag(path, f"grip_{grip_index}") + handler_tag = f"{grip_tag}_handler" width, height = [(self.grip_size, -1), (-1, self.grip_size)][orientation] with dpg.child_window(tag=grip_tag, parent=parent_tag, width=width, height=height, no_scrollbar=True, border=False): button_tag = dpg.add_button(label="", width=-1, height=-1) - with dpg.item_handler_registry(tag=f"{grip_tag}_handler"): + with dpg.item_handler_registry(tag=handler_tag): user_data = (path, grip_index, orientation) dpg.add_item_active_handler(callback=self._on_grip_drag, user_data=user_data) dpg.add_item_deactivated_handler(callback=self._on_grip_end, user_data=user_data) - dpg.bind_item_handler_registry(button_tag, f"{grip_tag}_handler") + dpg.bind_item_handler_registry(button_tag, handler_tag) + self._created_handler_tags.add(handler_tag) def _on_grip_drag(self, sender, app_data, user_data): path, grip_index, orientation = user_data diff --git a/tools/jotpluggler/layouts/torque-controller.yaml b/tools/jotpluggler/layouts/torque-controller.yaml new file mode 100644 index 000000000..5503be9e6 --- /dev/null +++ b/tools/jotpluggler/layouts/torque-controller.yaml @@ -0,0 +1,128 @@ +tabs: + '0': + name: Lateral Plan Conformance + panel_layout: + type: split + orientation: 1 + proportions: + - 0.3333333333333333 + - 0.3333333333333333 + - 0.3333333333333333 + children: + - type: panel + panel: + type: timeseries + title: desired vs actual + series_paths: + - controlsState/lateralControlState/torqueState/desiredLateralAccel + - controlsState/lateralControlState/torqueState/actualLateralAccel + - type: panel + panel: + type: timeseries + title: ff vs output + series_paths: + - controlsState/lateralControlState/torqueState/f + - carState/steeringPressed + - carControl/actuators/torque + - type: panel + panel: + type: timeseries + title: vehicle speed + series_paths: + - carState/vEgo + '1': + name: Actuator Performance + panel_layout: + type: split + orientation: 1 + proportions: + - 0.3333333333333333 + - 0.3333333333333333 + - 0.3333333333333333 + children: + - type: panel + panel: + type: timeseries + title: calc vs learned latAccelFactor + series_paths: + - liveTorqueParameters/latAccelFactorFiltered + - liveTorqueParameters/latAccelFactorRaw + - carParams/lateralTuning/torque/latAccelFactor + - type: panel + panel: + type: timeseries + title: learned latAccelOffset + series_paths: + - liveTorqueParameters/latAccelOffsetRaw + - liveTorqueParameters/latAccelOffsetFiltered + - type: panel + panel: + type: timeseries + title: calc vs learned friction + series_paths: + - liveTorqueParameters/frictionCoefficientFiltered + - liveTorqueParameters/frictionCoefficientRaw + - carParams/lateralTuning/torque/friction + '2': + name: Vehicle Dynamics + panel_layout: + type: split + orientation: 1 + proportions: + - 0.3333333333333333 + - 0.3333333333333333 + - 0.3333333333333333 + children: + - type: panel + panel: + type: timeseries + title: initial vs learned steerRatio + series_paths: + - carParams/steerRatio + - liveParameters/steerRatio + - type: panel + panel: + type: timeseries + title: initial vs learned tireStiffnessFactor + series_paths: + - carParams/tireStiffnessFactor + - liveParameters/stiffnessFactor + - type: panel + panel: + type: timeseries + title: live steering angle offsets + series_paths: + - liveParameters/angleOffsetDeg + - liveParameters/angleOffsetAverageDeg + '3': + name: Controller PIF Terms + panel_layout: + type: split + orientation: 1 + proportions: + - 0.3333333333333333 + - 0.3333333333333333 + - 0.3333333333333333 + children: + - type: panel + panel: + type: timeseries + title: ff vs output + series_paths: + - carControl/actuators/torque + - controlsState/lateralControlState/torqueState/f + - carState/steeringPressed + - type: panel + panel: + type: timeseries + title: PIF terms + series_paths: + - controlsState/lateralControlState/torqueState/f + - controlsState/lateralControlState/torqueState/p + - controlsState/lateralControlState/torqueState/i + - type: panel + panel: + type: timeseries + title: road roll angle + series_paths: + - liveParameters/roll diff --git a/tools/jotpluggler/pluggle.py b/tools/jotpluggler/pluggle.py index 41bf520cd..61b0a0971 100755 --- a/tools/jotpluggler/pluggle.py +++ b/tools/jotpluggler/pluggle.py @@ -7,6 +7,8 @@ import dearpygui.dearpygui as dpg import multiprocessing import uuid import signal +import yaml # type: ignore +from openpilot.common.swaglog import cloudlog from openpilot.common.basedir import BASEDIR from openpilot.tools.jotpluggler.data import DataManager from openpilot.tools.jotpluggler.datatree import DataTree @@ -131,17 +133,27 @@ class MainController: self.data_manager.add_observer(self.on_data_loaded) def _create_global_themes(self): - with dpg.theme(tag="global_line_theme"): + with dpg.theme(tag="line_theme"): with dpg.theme_component(dpg.mvLineSeries): scaled_thickness = max(1.0, self.scale) dpg.add_theme_style(dpg.mvPlotStyleVar_LineWeight, scaled_thickness, category=dpg.mvThemeCat_Plots) - with dpg.theme(tag="global_timeline_theme"): + with dpg.theme(tag="timeline_theme"): with dpg.theme_component(dpg.mvInfLineSeries): scaled_thickness = max(1.0, self.scale) dpg.add_theme_style(dpg.mvPlotStyleVar_LineWeight, scaled_thickness, category=dpg.mvThemeCat_Plots) dpg.add_theme_color(dpg.mvPlotCol_Line, (255, 0, 0, 128), category=dpg.mvThemeCat_Plots) + for tag, color in (("active_tab_theme", (37, 37, 38, 255)), ("inactive_tab_theme", (70, 70, 75, 255))): + with dpg.theme(tag=tag): + for cmp, target in ((dpg.mvChildWindow, dpg.mvThemeCol_ChildBg), (dpg.mvInputText, dpg.mvThemeCol_FrameBg), (dpg.mvImageButton, dpg.mvThemeCol_Button)): + with dpg.theme_component(cmp): + dpg.add_theme_color(target, color) + + with dpg.theme(tag="tab_bar_theme"): + with dpg.theme_component(dpg.mvChildWindow): + dpg.add_theme_color(dpg.mvThemeCol_ChildBg, (51, 51, 55, 255)) + def on_data_loaded(self, data: dict): duration = data.get('duration', 0.0) self.playback_manager.set_route_duration(duration) @@ -165,6 +177,56 @@ class MainController: dpg.configure_item("timeline_slider", max_value=duration) + def save_layout_to_yaml(self, filepath: str): + layout_dict = self.layout_manager.to_dict() + with open(filepath, 'w') as f: + yaml.dump(layout_dict, f, default_flow_style=False, sort_keys=False) + + def load_layout_from_yaml(self, filepath: str): + with open(filepath) as f: + layout_dict = yaml.safe_load(f) + self.layout_manager.clear_and_load_from_dict(layout_dict) + self.layout_manager.create_ui("main_plot_area") + + def save_layout_dialog(self): + if dpg.does_item_exist("save_layout_dialog"): + dpg.delete_item("save_layout_dialog") + with dpg.file_dialog( + directory_selector=False, show=True, callback=self._save_layout_callback, + tag="save_layout_dialog", width=int(700 * self.scale), height=int(400 * self.scale), + default_filename="layout", default_path="layouts" + ): + dpg.add_file_extension(".yaml") + + def load_layout_dialog(self): + if dpg.does_item_exist("load_layout_dialog"): + dpg.delete_item("load_layout_dialog") + with dpg.file_dialog( + directory_selector=False, show=True, callback=self._load_layout_callback, + tag="load_layout_dialog", width=int(700 * self.scale), height=int(400 * self.scale), default_path="layouts" + ): + dpg.add_file_extension(".yaml") + + def _save_layout_callback(self, sender, app_data): + filepath = app_data['file_path_name'] + try: + self.save_layout_to_yaml(filepath) + dpg.set_value("load_status", f"Layout saved to {os.path.basename(filepath)}") + except Exception: + dpg.set_value("load_status", "Error saving layout") + cloudlog.exception(f"Error saving layout to {filepath}") + dpg.delete_item("save_layout_dialog") + + def _load_layout_callback(self, sender, app_data): + filepath = app_data['file_path_name'] + try: + self.load_layout_from_yaml(filepath) + dpg.set_value("load_status", f"Layout loaded from {os.path.basename(filepath)}") + except Exception: + dpg.set_value("load_status", "Error loading layout") + cloudlog.exception(f"Error loading layout from {filepath}:") + dpg.delete_item("load_layout_dialog") + def setup_ui(self): with dpg.texture_registry(): script_dir = os.path.dirname(os.path.realpath(__file__)) @@ -175,21 +237,30 @@ class MainController: with dpg.window(tag="Primary Window"): with dpg.group(horizontal=True): # Left panel - Data tree - with dpg.child_window(label="Sidebar", width=300 * self.scale, tag="sidebar_window", border=True, resizable_x=True): + with dpg.child_window(label="Sidebar", width=int(300 * self.scale), tag="sidebar_window", border=True, resizable_x=True): with dpg.group(horizontal=True): - dpg.add_input_text(tag="route_input", width=-75 * self.scale, hint="Enter route name...") + dpg.add_input_text(tag="route_input", width=int(-75 * self.scale), hint="Enter route name...") dpg.add_button(label="Load", callback=self.load_route, tag="load_button", width=-1) dpg.add_text("Ready to load route", tag="load_status") dpg.add_separator() + + with dpg.table(header_row=False, policy=dpg.mvTable_SizingStretchProp): + dpg.add_table_column(init_width_or_weight=0.5) + dpg.add_table_column(init_width_or_weight=0.5) + with dpg.table_row(): + dpg.add_button(label="Save Layout", callback=self.save_layout_dialog, width=-1) + dpg.add_button(label="Load Layout", callback=self.load_layout_dialog, width=-1) + dpg.add_separator() + self.data_tree.create_ui("sidebar_window") # Right panel - Plots and timeline with dpg.group(tag="right_panel"): - with dpg.child_window(label="Plot Window", border=True, height=-(32 + 13 * self.scale), tag="main_plot_area"): + with dpg.child_window(label="Plot Window", border=True, height=int(-(32 + 13 * self.scale)), tag="main_plot_area"): self.layout_manager.create_ui("main_plot_area") with dpg.child_window(label="Timeline", border=True): - with dpg.table(header_row=False, borders_innerH=False, borders_innerV=False, borders_outerH=False, borders_outerV=False): + with dpg.table(header_row=False): btn_size = int(13 * self.scale) dpg.add_table_column(width_fixed=True, init_width_or_weight=(btn_size + 8)) # Play button dpg.add_table_column(width_stretch=True) # Timeline slider diff --git a/tools/jotpluggler/views.py b/tools/jotpluggler/views.py index cb993acf0..09bba7493 100644 --- a/tools/jotpluggler/views.py +++ b/tools/jotpluggler/views.py @@ -33,6 +33,15 @@ class ViewPanel(ABC): def update(self): pass + @abstractmethod + def to_dict(self) -> dict: + pass + + @classmethod + @abstractmethod + def load_from_dict(cls, data: dict, data_manager, playback_manager, worker_manager): + pass + class TimeSeriesPanel(ViewPanel): def __init__(self, data_manager, playback_manager, worker_manager, panel_id: str | None = None): @@ -55,6 +64,20 @@ class TimeSeriesPanel(ViewPanel): self._queued_x_sync: tuple | None = None self._queued_reallow_x_zoom = False + def to_dict(self) -> dict: + return { + "type": "timeseries", + "title": self.title, + "series_paths": list(self._series_data.keys()) + } + + @classmethod + def load_from_dict(cls, data: dict, data_manager, playback_manager, worker_manager): + panel = cls(data_manager, playback_manager, worker_manager) + panel.title = data.get("title", "Time Series Plot") + panel._series_data = {path: (np.array([]), np.array([])) for path in data.get("series_paths", [])} + return panel + def create_ui(self, parent_tag: str): self.data_manager.add_observer(self.on_data_loaded) self.playback_manager.add_x_axis_observer(self._on_x_axis_sync) @@ -63,7 +86,7 @@ class TimeSeriesPanel(ViewPanel): dpg.add_plot_axis(dpg.mvXAxis, no_label=True, tag=self.x_axis_tag) dpg.add_plot_axis(dpg.mvYAxis, no_label=True, tag=self.y_axis_tag) timeline_series_tag = dpg.add_inf_line_series(x=[0], label="Timeline", parent=self.y_axis_tag, tag=self.timeline_indicator_tag) - dpg.bind_item_theme(timeline_series_tag, "global_timeline_theme") + dpg.bind_item_theme(timeline_series_tag, "timeline_theme") self._new_data = True self._ui_created = True @@ -199,7 +222,7 @@ class TimeSeriesPanel(ViewPanel): dpg.set_value(series_tag, (time_array, value_array.astype(float))) else: line_series_tag = dpg.add_line_series(x=time_array, y=value_array.astype(float), label=series_path, parent=self.y_axis_tag, tag=series_tag) - dpg.bind_item_theme(line_series_tag, "global_line_theme") + dpg.bind_item_theme(line_series_tag, "line_theme") dpg.fit_axis_data(self.x_axis_tag) dpg.fit_axis_data(self.y_axis_tag) plot_duration = dpg.get_axis_limits(self.x_axis_tag)[1] - dpg.get_axis_limits(self.x_axis_tag)[0] From 8d3b919ef68206bc4a123d986407f798e1f4e990 Mon Sep 17 00:00:00 2001 From: Jimmy <9859727+Quantizr@users.noreply.github.com> Date: Fri, 12 Sep 2025 23:28:23 -0700 Subject: [PATCH 010/910] jotpluggler: better defaults for zooming/fitting (#36149) better defaults for zooming/fitting --- tools/jotpluggler/pluggle.py | 11 +++++++++-- tools/jotpluggler/views.py | 32 ++++++++++++++++++++++---------- 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/tools/jotpluggler/pluggle.py b/tools/jotpluggler/pluggle.py index 61b0a0971..b831a2f15 100755 --- a/tools/jotpluggler/pluggle.py +++ b/tools/jotpluggler/pluggle.py @@ -66,6 +66,7 @@ class PlaybackManager: self.is_playing = False self.current_time_s = 0.0 self.duration_s = 0.0 + self.num_segments = 0 self.x_axis_bounds = (0.0, 0.0) # (min_time, max_time) self.x_axis_observers = [] # callbacks for x-axis changes @@ -131,6 +132,7 @@ class MainController: self.data_tree = DataTree(self.data_manager, self.playback_manager) self.layout_manager = LayoutManager(self.data_manager, self.playback_manager, self.worker_manager, scale=self.scale) self.data_manager.add_observer(self.on_data_loaded) + self._total_segments = 0 def _create_global_themes(self): with dpg.theme(tag="line_theme"): @@ -158,10 +160,15 @@ class MainController: duration = data.get('duration', 0.0) self.playback_manager.set_route_duration(duration) - if data.get('reset'): + if data.get('metadata_loaded'): + self.playback_manager.num_segments = data.get('total_segments', 0) + self._total_segments = data.get('total_segments', 0) + dpg.set_value("load_status", f"Loading... 0/{self._total_segments} segments processed") + elif data.get('reset'): self.playback_manager.current_time_s = 0.0 self.playback_manager.duration_s = 0.0 self.playback_manager.is_playing = False + self._total_segments = 0 dpg.set_value("load_status", "Loading...") dpg.set_value("timeline_slider", 0.0) dpg.configure_item("timeline_slider", max_value=0.0) @@ -173,7 +180,7 @@ class MainController: dpg.configure_item("load_button", enabled=True) elif data.get('segment_added'): segment_count = data.get('segment_count', 0) - dpg.set_value("load_status", f"Loading... {segment_count} segments processed") + dpg.set_value("load_status", f"Loading... {segment_count}/{self._total_segments} segments processed") dpg.configure_item("timeline_slider", max_value=duration) diff --git a/tools/jotpluggler/views.py b/tools/jotpluggler/views.py index 09bba7493..1c4d9a8f3 100644 --- a/tools/jotpluggler/views.py +++ b/tools/jotpluggler/views.py @@ -63,6 +63,7 @@ class TimeSeriesPanel(ViewPanel): self._last_x_limits = (0.0, 0.0) self._queued_x_sync: tuple | None = None self._queued_reallow_x_zoom = False + self._total_segments = self.playback_manager.num_segments def to_dict(self) -> dict: return { @@ -89,6 +90,7 @@ class TimeSeriesPanel(ViewPanel): dpg.bind_item_theme(timeline_series_tag, "timeline_theme") self._new_data = True + self._queued_x_sync = self.playback_manager.x_axis_bounds self._ui_created = True def update(self): @@ -107,7 +109,19 @@ class TimeSeriesPanel(ViewPanel): if self._queued_reallow_x_zoom: self._queued_reallow_x_zoom = False - dpg.set_axis_limits_auto(self.x_axis_tag) + if tuple(dpg.get_axis_limits(self.x_axis_tag)) == self._last_x_limits: + dpg.set_axis_limits_auto(self.x_axis_tag) + else: + self._queued_x_sync = self._last_x_limits # retry, likely too early + return + + if self._new_data: # handle new data in main thread + self._new_data = False + if self._total_segments > 0: + dpg.set_axis_limits_constraints(self.x_axis_tag, -10, self._total_segments * 60 + 10) + self._fit_y_axis(*dpg.get_axis_limits(self.x_axis_tag)) + for series_path in list(self._series_data.keys()): + self.add_series(series_path, update=True) current_limits = dpg.get_axis_limits(self.x_axis_tag) # downsample if plot zoom changed significantly @@ -120,12 +134,6 @@ class TimeSeriesPanel(ViewPanel): self._last_x_limits = current_limits self._fit_y_axis(current_limits[0], current_limits[1]) - if self._new_data: # handle new data in main thread - self._new_data = False - dpg.set_axis_limits_constraints(self.x_axis_tag, -10, (self.playback_manager.duration_s + 10)) - for series_path in list(self._series_data.keys()): - self.add_series(series_path, update=True) - while self._results_deque: # handle downsampled results in main thread results = self._results_deque.popleft() for series_path, downsampled_time, downsampled_values in results: @@ -223,8 +231,7 @@ class TimeSeriesPanel(ViewPanel): else: line_series_tag = dpg.add_line_series(x=time_array, y=value_array.astype(float), label=series_path, parent=self.y_axis_tag, tag=series_tag) dpg.bind_item_theme(line_series_tag, "line_theme") - dpg.fit_axis_data(self.x_axis_tag) - dpg.fit_axis_data(self.y_axis_tag) + self._fit_y_axis(*dpg.get_axis_limits(self.x_axis_tag)) plot_duration = dpg.get_axis_limits(self.x_axis_tag)[1] - dpg.get_axis_limits(self.x_axis_tag)[0] self._downsample_all_series(plot_duration) @@ -252,7 +259,12 @@ class TimeSeriesPanel(ViewPanel): del self._series_data[series_path] def on_data_loaded(self, data: dict): - self._new_data = True + with self._update_lock: + self._new_data = True + if data.get('metadata_loaded'): + self._total_segments = data.get('total_segments', 0) + limits = (-10, self._total_segments * 60 + 10) + self._queued_x_sync = limits def _on_series_drop(self, sender, app_data, user_data): self.add_series(app_data) From c812c3192d6d24578e4037bd7c9f0d7c446c8c54 Mon Sep 17 00:00:00 2001 From: Jimmy <9859727+Quantizr@users.noreply.github.com> Date: Fri, 12 Sep 2025 23:53:41 -0700 Subject: [PATCH 011/910] jotpluggler: fix hidpi/mac font scaling (#36150) fix hidpi/mac font scaling --- tools/jotpluggler/pluggle.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/jotpluggler/pluggle.py b/tools/jotpluggler/pluggle.py index b831a2f15..b9cafa60d 100755 --- a/tools/jotpluggler/pluggle.py +++ b/tools/jotpluggler/pluggle.py @@ -324,8 +324,9 @@ def main(route_to_load=None): scale = 1 with dpg.font_registry(): - default_font = dpg.add_font(os.path.join(BASEDIR, "selfdrive/assets/fonts/JetBrainsMono-Medium.ttf"), int(13 * scale)) + default_font = dpg.add_font(os.path.join(BASEDIR, "selfdrive/assets/fonts/JetBrainsMono-Medium.ttf"), int(13 * scale * 2)) # 2x then scale for hidpi dpg.bind_font(default_font) + dpg.set_global_font_scale(0.5) viewport_width, viewport_height = int(1200 * scale), int(800 * scale) mouse_x, mouse_y = pyautogui.position() # TODO: find better way of creating the window where the user is (default dpg behavior annoying on multiple displays) From f18828228ae9ba7fc1aaa807af519f1c920d38d2 Mon Sep 17 00:00:00 2001 From: Jimmy <9859727+Quantizr@users.noreply.github.com> Date: Sat, 13 Sep 2025 00:13:27 -0700 Subject: [PATCH 012/910] jotpluggler: fix layout folder path loading and total segment (#36151) * forgot to commit this earlier with total segments * look in correct directory --- tools/jotpluggler/data.py | 6 ++++++ tools/jotpluggler/pluggle.py | 9 ++++----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/tools/jotpluggler/data.py b/tools/jotpluggler/data.py index 14d54e6be..cf27857d1 100644 --- a/tools/jotpluggler/data.py +++ b/tools/jotpluggler/data.py @@ -314,6 +314,12 @@ class DataManager: cloudlog.warning(f"Warning: No log segments found for route: {route}") return + total_segments = len(lr.logreader_identifiers) + with self._lock: + observers = self._observers.copy() + for callback in observers: + callback({'metadata_loaded': True, 'total_segments': total_segments}) + num_processes = max(1, multiprocessing.cpu_count() // 2) with multiprocessing.Pool(processes=num_processes) as pool, tqdm(total=len(lr.logreader_identifiers), desc="Processing Segments") as pbar: for segment_result, start_time, end_time in pool.imap(_process_segment, lr.logreader_identifiers): diff --git a/tools/jotpluggler/pluggle.py b/tools/jotpluggler/pluggle.py index b9cafa60d..986df3d7b 100755 --- a/tools/jotpluggler/pluggle.py +++ b/tools/jotpluggler/pluggle.py @@ -199,9 +199,8 @@ class MainController: if dpg.does_item_exist("save_layout_dialog"): dpg.delete_item("save_layout_dialog") with dpg.file_dialog( - directory_selector=False, show=True, callback=self._save_layout_callback, - tag="save_layout_dialog", width=int(700 * self.scale), height=int(400 * self.scale), - default_filename="layout", default_path="layouts" + callback=self._save_layout_callback, tag="save_layout_dialog", width=int(700 * self.scale), height=int(400 * self.scale), + default_filename="layout", default_path=os.path.join(os.path.dirname(os.path.realpath(__file__)), "layouts") ): dpg.add_file_extension(".yaml") @@ -209,8 +208,8 @@ class MainController: if dpg.does_item_exist("load_layout_dialog"): dpg.delete_item("load_layout_dialog") with dpg.file_dialog( - directory_selector=False, show=True, callback=self._load_layout_callback, - tag="load_layout_dialog", width=int(700 * self.scale), height=int(400 * self.scale), default_path="layouts" + callback=self._load_layout_callback, tag="load_layout_dialog", width=int(700 * self.scale), height=int(400 * self.scale), + default_path=os.path.join(os.path.dirname(os.path.realpath(__file__)), "layouts") ): dpg.add_file_extension(".yaml") From 3e0dd06374b86c237d7a5e12f6eec600b980d748 Mon Sep 17 00:00:00 2001 From: Jimmy <9859727+Quantizr@users.noreply.github.com> Date: Sat, 13 Sep 2025 00:20:53 -0700 Subject: [PATCH 013/910] jotpluggler: accept --layout argument to pluggle (#36152) accept layouts as arg to pluggle --- tools/jotpluggler/pluggle.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/tools/jotpluggler/pluggle.py b/tools/jotpluggler/pluggle.py index 986df3d7b..879b67751 100755 --- a/tools/jotpluggler/pluggle.py +++ b/tools/jotpluggler/pluggle.py @@ -312,7 +312,7 @@ class MainController: self.worker_manager.shutdown() -def main(route_to_load=None): +def main(route_to_load=None, layout_to_load=None): dpg.create_context() # TODO: find better way of calculating display scaling @@ -337,6 +337,14 @@ def main(route_to_load=None): controller = MainController(scale=scale) controller.setup_ui() + if layout_to_load: + try: + controller.load_layout_from_yaml(layout_to_load) + print(f"Loaded layout from {layout_to_load}") + except Exception as e: + print(f"Failed to load layout from {layout_to_load}: {e}") + cloudlog.exception(f"Error loading layout from {layout_to_load}") + if route_to_load: dpg.set_value("route_input", route_to_load) controller.load_route() @@ -355,7 +363,8 @@ def main(route_to_load=None): if __name__ == "__main__": parser = argparse.ArgumentParser(description="A tool for visualizing openpilot logs.") parser.add_argument("--demo", action="store_true", help="Use the demo route instead of providing one") + parser.add_argument("--layout", type=str, help="Path to YAML layout file to load on startup") parser.add_argument("route", nargs='?', default=None, help="Optional route name to load on startup.") args = parser.parse_args() route = DEMO_ROUTE if args.demo else args.route - main(route_to_load=route) + main(route_to_load=route, layout_to_load=args.layout) From 04a26ada69c3546bbd850e62b207a81aeb0530fe Mon Sep 17 00:00:00 2001 From: Jimmy <9859727+Quantizr@users.noreply.github.com> Date: Sat, 13 Sep 2025 00:35:12 -0700 Subject: [PATCH 014/910] jotpluggler: fix bug with char width after scaling text (#36154) fix bug with char width after scaling text --- tools/jotpluggler/datatree.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/jotpluggler/datatree.py b/tools/jotpluggler/datatree.py index eb4e4ef58..4f3219dc1 100644 --- a/tools/jotpluggler/datatree.py +++ b/tools/jotpluggler/datatree.py @@ -67,7 +67,7 @@ class DataTree: with self._ui_lock: if self._char_width is None: if size := dpg.get_text_size(" ", font=font): - self._char_width = size[0] + self._char_width = size[0] / 2 # we scale font 2x and downscale to fix hidpi bug if self._new_data: self._process_path_change() From 98d61982f933b3ef83b81c985e2dcf7d34d89028 Mon Sep 17 00:00:00 2001 From: Jimmy <9859727+Quantizr@users.noreply.github.com> Date: Sat, 13 Sep 2025 00:35:35 -0700 Subject: [PATCH 015/910] jotpluggler: add README (#36153) * add README * fix typo --- tools/jotpluggler/README.md | 67 +++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 tools/jotpluggler/README.md diff --git a/tools/jotpluggler/README.md b/tools/jotpluggler/README.md new file mode 100644 index 000000000..c5b43dbd3 --- /dev/null +++ b/tools/jotpluggler/README.md @@ -0,0 +1,67 @@ +# JotPluggler + +JotPluggler is a tool to quickly visualize openpilot logs. + +## Usage + +``` +$ ./jotpluggler/pluggle.py -h +usage: pluggle.py [-h] [--demo] [--layout LAYOUT] [route] + +A tool for visualizing openpilot logs. + +positional arguments: + route Optional route name to load on startup. + +options: + -h, --help show this help message and exit + --demo Use the demo route instead of providing one + --layout LAYOUT Path to YAML layout file to load on startup +``` + +Example using route name: + +`./pluggle.py "a2a0ccea32023010/2023-07-27--13-01-19"` + +Examples using segment: + +`./pluggle.py "a2a0ccea32023010/2023-07-27--13-01-19/1"` + +`./pluggle.py "a2a0ccea32023010/2023-07-27--13-01-19/1/q" # use qlogs` + +Example using segment range: + +`./pluggle.py "a2a0ccea32023010/2023-07-27--13-01-19/0:1"` + +## Demo + +For a quick demo, run this command: + +`./pluggle.py --demo --layout=layouts/torque-controller.yaml` + + +## Basic Usage/Features: +- The text box to load a route is a the top left of the page, accepts standard openpilot format routes (e.g. `a2a0ccea32023010/2023-07-27--13-01-19/0:1`, `https://connect.comma.ai/a2a0ccea32023010/2023-07-27--13-01-19/`) +- The Play/Pause button is at the bottom of the screen, you can drag the bottom slider to seek. The timeline in timeseries plots are synced with the slider. +- The Timeseries List sidebar has several dropdowns, the fields each show the field name and value, synced with the timeline (will show N/A until the time of the first message in that field is reached). +- There is a search bar for the timeseries list, you can search for structs or fields, or both by separating with a "/" +- You can drag and drop any numeric/boolean field from the timeseries list into a timeseries panel. +- You can create more panels with the split buttons (buttons with two rectangles, either horizontal or vertical). You can resize the panels by dragging the grip in between any panel. +- You can load and save layouts with the corresponding buttons. Layouts will save all tabs, panels, titles, timeseries, etc. + +## Layouts + +If you create a layout that's useful for others, consider upstreaming it. + +## Plot Interaction Controls + +- **Left click and drag within the plot area** to pan X + - Left click and drag on an axis to pan an individual axis (disabled for Y-axis) +- **Scroll in the plot area** to zoom in X axes, Y-axis is autofit + - Scroll on an axis to zoom an individual axis +- **Right click and drag** to select data and zoom into the selected data + - Left click while box selecting to cancel the selection +- **Double left click** to fit all visible data + - Double left click on an axis to fit the individual axis (disabled for Y-axis, always autofit) +- **Double right click** to open the plot context menu +- **Click legend label icons** to show/hide plot items From eb821ceb5c6835a560b51ab7850a87f40c5fb22a Mon Sep 17 00:00:00 2001 From: commaci-public <60409688+commaci-public@users.noreply.github.com> Date: Sat, 13 Sep 2025 11:48:35 -0700 Subject: [PATCH 016/910] [bot] Update Python packages (#36118) * Update Python packages * revert tinygrad * can cnt * bump panda * bump panda * update panda test * revert that --------- Co-authored-by: Vehicle Researcher Co-authored-by: Adeeb Shihadeh --- docs/CARS.md | 8 ++++---- msgq_repo | 2 +- opendbc_repo | 2 +- panda | 2 +- selfdrive/pandad/pandad.cc | 2 +- selfdrive/pandad/tests/test_pandad.py | 27 ++++++++------------------- 6 files changed, 16 insertions(+), 27 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index bd6a9c920..7d3f7a944 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -246,10 +246,10 @@ A supported vehicle is one that just works when you install a comma device. All |Škoda|Octavia Scout 2017-19[15](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| |Škoda|Scala 2020-23[15](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[17](#footnotes)||| |Škoda|Superb 2015-22[15](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Tesla[11](#footnotes)|Model 3 (with HW3) 2019-23[10](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Tesla A connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Tesla[11](#footnotes)|Model 3 (with HW4) 2024-25[10](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Tesla B connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Tesla[11](#footnotes)|Model Y (with HW3) 2020-23[10](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Tesla A connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Tesla[11](#footnotes)|Model Y (with HW4) 2024-25[10](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Tesla B connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Tesla[11](#footnotes)|Model 3 (with HW3) 2019-23[10](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Tesla A connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Tesla[11](#footnotes)|Model 3 (with HW4) 2024-25[10](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Tesla B connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Tesla[11](#footnotes)|Model Y (with HW3) 2020-23[10](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Tesla A connector
- 1 USB-C coupler
- 1 comma 3X
- 1 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-25[10](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Tesla B connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| |Toyota|Alphard 2019-20|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| |Toyota|Alphard Hybrid 2021|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| |Toyota|Avalon 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| diff --git a/msgq_repo b/msgq_repo index 5483a02de..89096d90d 160000 --- a/msgq_repo +++ b/msgq_repo @@ -1 +1 @@ -Subproject commit 5483a02de303d40cb2632d59f3f3a54dabfb5965 +Subproject commit 89096d90d2f0f71be63a4af0152fe3b2aa55cf9d diff --git a/opendbc_repo b/opendbc_repo index 4170d7d87..c2ba07083 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 4170d7d876a87904dab4b351aa8139ec3d400430 +Subproject commit c2ba07083c1cfbb0c5b86469bd7b87090291a0d3 diff --git a/panda b/panda index 819fa5854..a2064b86f 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 819fa5854e2e75da7f982f7d06be69c61793d6e1 +Subproject commit a2064b86f3c9908883033a953503f150cedacbc7 diff --git a/selfdrive/pandad/pandad.cc b/selfdrive/pandad/pandad.cc index 8289df549..2931eb4ac 100644 --- a/selfdrive/pandad/pandad.cc +++ b/selfdrive/pandad/pandad.cc @@ -66,7 +66,7 @@ Panda *connect(std::string serial="", uint32_t index=0) { } //panda->enable_deepsleep(); - for (int i = 0; i < PANDA_BUS_CNT; i++) { + for (int i = 0; i < PANDA_CAN_CNT; i++) { panda->set_can_fd_auto(i, true); } diff --git a/selfdrive/pandad/tests/test_pandad.py b/selfdrive/pandad/tests/test_pandad.py index 6a7359fd8..88d3939a6 100644 --- a/selfdrive/pandad/tests/test_pandad.py +++ b/selfdrive/pandad/tests/test_pandad.py @@ -5,8 +5,7 @@ import time import cereal.messaging as messaging from cereal import log from openpilot.common.gpio import gpio_set, gpio_init -from panda import Panda, PandaDFU, PandaProtocolMismatch -from openpilot.common.retry import retry +from panda import Panda, PandaDFU from openpilot.system.manager.process_config import managed_processes from openpilot.system.hardware import HARDWARE from openpilot.system.hardware.tici.pins import GPIO @@ -50,8 +49,7 @@ class TestPandad: assert not Panda.wait_for_dfu(None, 3) assert not Panda.wait_for_panda(None, 3) - @retry(attempts=3) - def _flash_bootstub_and_test(self, fn, expect_mismatch=False): + def _flash_bootstub(self, fn): self._go_to_dfu() pd = PandaDFU(None) if fn is None: @@ -61,16 +59,6 @@ class TestPandad: pd.reset() HARDWARE.reset_internal_panda() - assert Panda.wait_for_panda(None, 10) - if expect_mismatch: - with pytest.raises(PandaProtocolMismatch): - Panda() - else: - with Panda() as p: - assert p.bootstub - - self._run_test(45) - def test_in_dfu(self): HARDWARE.recover_internal_panda() self._run_test(60) @@ -106,13 +94,14 @@ class TestPandad: print("startup times", ts, sum(ts) / len(ts)) assert 0.1 < (sum(ts)/len(ts)) < 0.7 - def test_protocol_version_check(self): - # flash old fw - fn = os.path.join(HERE, "bootstub.panda_h7_spiv0.bin") - self._flash_bootstub_and_test(fn, expect_mismatch=True) + def test_old_spi_protocol(self): + # flash firmware with old SPI protocol + self._flash_bootstub(os.path.join(HERE, "bootstub.panda_h7_spiv0.bin")) + self._run_test(45) def test_release_to_devel_bootstub(self): - self._flash_bootstub_and_test(None) + self._flash_bootstub(None) + self._run_test(45) def test_recover_from_bad_bootstub(self): self._go_to_dfu() From a6adedf6e0ad6c33374ca4d64e25935566d763a3 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 13 Sep 2025 11:58:49 -0700 Subject: [PATCH 017/910] prep for python pandad (#36155) --- system/hardware/base.py | 9 +++++++++ system/hardware/tici/hardware.py | 20 ++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/system/hardware/base.py b/system/hardware/base.py index ce97bf294..17d0ec161 100644 --- a/system/hardware/base.py +++ b/system/hardware/base.py @@ -232,3 +232,12 @@ class HardwareBase(ABC): def get_modem_data_usage(self): return -1, -1 + + def get_voltage(self) -> float: + return 0. + + def get_current(self) -> float: + return 0. + + def set_ir_power(self, percent: int): + pass diff --git a/system/hardware/tici/hardware.py b/system/hardware/tici/hardware.py index 0f50acdc3..36e65ad91 100644 --- a/system/hardware/tici/hardware.py +++ b/system/hardware/tici/hardware.py @@ -116,6 +116,26 @@ class Tici(HardwareBase): def get_serial(self): return self.get_cmdline()['androidboot.serialno'] + def get_voltage(self): + with open("/sys/class/hwmon/hwmon1/in1_input") as f: + return int(f.read()) + + def get_current(self): + with open("/sys/class/hwmon/hwmon1/curr1_input") as f: + return int(f.read()) + + def set_ir_power(self, percent: int): + if self.get_device_type() in ("tici", "tizi"): + return + + value = int((percent / 100) * 300) + with open("/sys/class/leds/led:switch_2/brightness", "w") as f: + f.write("0\n") + with open("/sys/class/leds/led:torch_2/brightness", "w") as f: + f.write(f"{value}\n") + with open("/sys/class/leds/led:switch_2/brightness", "w") as f: + f.write(f"{value}\n") + def get_network_type(self): try: primary_connection = self.nm.Get(NM, 'PrimaryConnection', dbus_interface=DBUS_PROPS, timeout=TIMEOUT) From 96c00271e335a5c451f44185a38976c2f5695fed Mon Sep 17 00:00:00 2001 From: Jason Young <46612682+jyoung8607@users.noreply.github.com> Date: Mon, 15 Sep 2025 14:37:52 -0400 Subject: [PATCH 018/910] pin pycapnp (#36160) --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 1655b565e..489876f78 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,7 @@ dependencies = [ # core "cffi", "scons", - "pycapnp", + "pycapnp==2.1.0", "Cython", "setuptools", "numpy >=2.0", From 889ce4c4fb6ac189817786f5ec4b2584397f25df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20R=C4=85czy?= Date: Mon, 15 Sep 2025 23:04:33 +0200 Subject: [PATCH 019/910] torqued: add DEBUG flag (#36161) Add a debug flag to torqued --- selfdrive/locationd/torqued.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/selfdrive/locationd/torqued.py b/selfdrive/locationd/torqued.py index 71b5291df..3f9b846e8 100755 --- a/selfdrive/locationd/torqued.py +++ b/selfdrive/locationd/torqued.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +import os import numpy as np from collections import deque, defaultdict @@ -242,6 +243,8 @@ class TorqueEstimator(ParameterEstimator): def main(demo=False): config_realtime_process([0, 1, 2, 3], 5) + DEBUG = bool(int(os.getenv("DEBUG", "0"))) + pm = messaging.PubMaster(['liveTorqueParameters']) sm = messaging.SubMaster(['carControl', 'carOutput', 'carState', 'liveCalibration', 'livePose', 'liveDelay'], poll='livePose') @@ -258,7 +261,7 @@ def main(demo=False): # 4Hz driven by livePose if sm.frame % 5 == 0: - pm.send('liveTorqueParameters', estimator.get_msg(valid=sm.all_checks())) + pm.send('liveTorqueParameters', estimator.get_msg(valid=sm.all_checks(), with_points=DEBUG)) # Cache points every 60 seconds while onroad if sm.frame % 240 == 0: From 086e33dd6e7047da3af092fd3c322e8c63b86571 Mon Sep 17 00:00:00 2001 From: Mitchell Goff Date: Tue, 16 Sep 2025 14:25:18 -0700 Subject: [PATCH 020/910] Revert "minimal ffmpeg build (#36138)" This reverts commit 347b23055d03e3af878d7abbd04ca3296f5c1cc2. --- .gitattributes | 1 - SConstruct | 4 - system/loggerd/SConscript | 4 +- system/loggerd/tests/test_loggerd.py | 14 +--- third_party/.gitignore | 2 - third_party/ffmpeg/Darwin/lib/libavcodec.a | 3 - third_party/ffmpeg/Darwin/lib/libavformat.a | 3 - third_party/ffmpeg/Darwin/lib/libavutil.a | 3 - third_party/ffmpeg/Darwin/lib/libx264.a | 3 - third_party/ffmpeg/build.sh | 83 ------------------- .../ffmpeg/include/libavcodec/ac3_parser.h | 3 - .../ffmpeg/include/libavcodec/adts_parser.h | 3 - .../ffmpeg/include/libavcodec/avcodec.h | 3 - third_party/ffmpeg/include/libavcodec/avdct.h | 3 - third_party/ffmpeg/include/libavcodec/avfft.h | 3 - third_party/ffmpeg/include/libavcodec/bsf.h | 3 - third_party/ffmpeg/include/libavcodec/codec.h | 3 - .../ffmpeg/include/libavcodec/codec_desc.h | 3 - .../ffmpeg/include/libavcodec/codec_id.h | 3 - .../ffmpeg/include/libavcodec/codec_par.h | 3 - .../ffmpeg/include/libavcodec/d3d11va.h | 3 - third_party/ffmpeg/include/libavcodec/defs.h | 3 - third_party/ffmpeg/include/libavcodec/dirac.h | 3 - .../ffmpeg/include/libavcodec/dv_profile.h | 3 - third_party/ffmpeg/include/libavcodec/dxva2.h | 3 - third_party/ffmpeg/include/libavcodec/jni.h | 3 - .../ffmpeg/include/libavcodec/mediacodec.h | 3 - .../ffmpeg/include/libavcodec/packet.h | 3 - third_party/ffmpeg/include/libavcodec/qsv.h | 3 - third_party/ffmpeg/include/libavcodec/vdpau.h | 3 - .../ffmpeg/include/libavcodec/version.h | 3 - .../ffmpeg/include/libavcodec/version_major.h | 3 - .../ffmpeg/include/libavcodec/videotoolbox.h | 3 - .../ffmpeg/include/libavcodec/vorbis_parser.h | 3 - third_party/ffmpeg/include/libavcodec/xvmc.h | 3 - .../ffmpeg/include/libavformat/avformat.h | 3 - third_party/ffmpeg/include/libavformat/avio.h | 3 - .../ffmpeg/include/libavformat/version.h | 3 - .../include/libavformat/version_major.h | 3 - .../ffmpeg/include/libavutil/adler32.h | 3 - third_party/ffmpeg/include/libavutil/aes.h | 3 - .../ffmpeg/include/libavutil/aes_ctr.h | 3 - .../libavutil/ambient_viewing_environment.h | 3 - .../ffmpeg/include/libavutil/attributes.h | 3 - .../ffmpeg/include/libavutil/audio_fifo.h | 3 - .../ffmpeg/include/libavutil/avassert.h | 3 - .../ffmpeg/include/libavutil/avconfig.h | 3 - .../ffmpeg/include/libavutil/avstring.h | 3 - third_party/ffmpeg/include/libavutil/avutil.h | 3 - third_party/ffmpeg/include/libavutil/base64.h | 3 - .../ffmpeg/include/libavutil/blowfish.h | 3 - third_party/ffmpeg/include/libavutil/bprint.h | 3 - third_party/ffmpeg/include/libavutil/bswap.h | 3 - third_party/ffmpeg/include/libavutil/buffer.h | 3 - .../ffmpeg/include/libavutil/camellia.h | 3 - third_party/ffmpeg/include/libavutil/cast5.h | 3 - .../ffmpeg/include/libavutil/channel_layout.h | 3 - third_party/ffmpeg/include/libavutil/common.h | 3 - third_party/ffmpeg/include/libavutil/cpu.h | 3 - third_party/ffmpeg/include/libavutil/crc.h | 3 - third_party/ffmpeg/include/libavutil/csp.h | 3 - third_party/ffmpeg/include/libavutil/des.h | 3 - .../ffmpeg/include/libavutil/detection_bbox.h | 3 - third_party/ffmpeg/include/libavutil/dict.h | 3 - .../ffmpeg/include/libavutil/display.h | 3 - .../ffmpeg/include/libavutil/dovi_meta.h | 3 - .../ffmpeg/include/libavutil/downmix_info.h | 3 - .../include/libavutil/encryption_info.h | 3 - third_party/ffmpeg/include/libavutil/error.h | 3 - third_party/ffmpeg/include/libavutil/eval.h | 3 - .../ffmpeg/include/libavutil/executor.h | 3 - .../ffmpeg/include/libavutil/ffversion.h | 3 - third_party/ffmpeg/include/libavutil/fifo.h | 3 - third_party/ffmpeg/include/libavutil/file.h | 3 - .../include/libavutil/film_grain_params.h | 3 - third_party/ffmpeg/include/libavutil/frame.h | 3 - third_party/ffmpeg/include/libavutil/hash.h | 3 - .../include/libavutil/hdr_dynamic_metadata.h | 3 - .../libavutil/hdr_dynamic_vivid_metadata.h | 3 - third_party/ffmpeg/include/libavutil/hmac.h | 3 - .../ffmpeg/include/libavutil/hwcontext.h | 3 - .../ffmpeg/include/libavutil/hwcontext_cuda.h | 3 - .../include/libavutil/hwcontext_d3d11va.h | 3 - .../ffmpeg/include/libavutil/hwcontext_drm.h | 3 - .../include/libavutil/hwcontext_dxva2.h | 3 - .../include/libavutil/hwcontext_mediacodec.h | 3 - .../include/libavutil/hwcontext_opencl.h | 3 - .../ffmpeg/include/libavutil/hwcontext_qsv.h | 3 - .../include/libavutil/hwcontext_vaapi.h | 3 - .../include/libavutil/hwcontext_vdpau.h | 3 - .../libavutil/hwcontext_videotoolbox.h | 3 - .../include/libavutil/hwcontext_vulkan.h | 3 - .../ffmpeg/include/libavutil/imgutils.h | 3 - .../ffmpeg/include/libavutil/intfloat.h | 3 - .../ffmpeg/include/libavutil/intreadwrite.h | 3 - third_party/ffmpeg/include/libavutil/lfg.h | 3 - third_party/ffmpeg/include/libavutil/log.h | 3 - third_party/ffmpeg/include/libavutil/lzo.h | 3 - third_party/ffmpeg/include/libavutil/macros.h | 3 - .../libavutil/mastering_display_metadata.h | 3 - .../ffmpeg/include/libavutil/mathematics.h | 3 - third_party/ffmpeg/include/libavutil/md5.h | 3 - third_party/ffmpeg/include/libavutil/mem.h | 3 - .../ffmpeg/include/libavutil/motion_vector.h | 3 - .../ffmpeg/include/libavutil/murmur3.h | 3 - third_party/ffmpeg/include/libavutil/opt.h | 3 - .../ffmpeg/include/libavutil/parseutils.h | 3 - .../ffmpeg/include/libavutil/pixdesc.h | 3 - .../ffmpeg/include/libavutil/pixelutils.h | 3 - third_party/ffmpeg/include/libavutil/pixfmt.h | 3 - .../ffmpeg/include/libavutil/random_seed.h | 3 - .../ffmpeg/include/libavutil/rational.h | 3 - third_party/ffmpeg/include/libavutil/rc4.h | 3 - .../ffmpeg/include/libavutil/replaygain.h | 3 - third_party/ffmpeg/include/libavutil/ripemd.h | 3 - .../ffmpeg/include/libavutil/samplefmt.h | 3 - third_party/ffmpeg/include/libavutil/sha.h | 3 - third_party/ffmpeg/include/libavutil/sha512.h | 3 - .../ffmpeg/include/libavutil/spherical.h | 3 - .../ffmpeg/include/libavutil/stereo3d.h | 3 - third_party/ffmpeg/include/libavutil/tea.h | 3 - .../ffmpeg/include/libavutil/threadmessage.h | 3 - third_party/ffmpeg/include/libavutil/time.h | 3 - .../ffmpeg/include/libavutil/timecode.h | 3 - .../ffmpeg/include/libavutil/timestamp.h | 3 - third_party/ffmpeg/include/libavutil/tree.h | 3 - .../ffmpeg/include/libavutil/twofish.h | 3 - third_party/ffmpeg/include/libavutil/tx.h | 3 - third_party/ffmpeg/include/libavutil/uuid.h | 3 - .../ffmpeg/include/libavutil/version.h | 3 - .../include/libavutil/video_enc_params.h | 3 - .../ffmpeg/include/libavutil/video_hint.h | 3 - third_party/ffmpeg/include/libavutil/xtea.h | 3 - third_party/ffmpeg/include/x264.h | 3 - third_party/ffmpeg/include/x264_config.h | 3 - third_party/ffmpeg/larch64/lib/libavcodec.a | 3 - third_party/ffmpeg/larch64/lib/libavformat.a | 3 - third_party/ffmpeg/larch64/lib/libavutil.a | 3 - third_party/ffmpeg/larch64/lib/libx264.a | 3 - third_party/ffmpeg/x86_64/lib/libavcodec.a | 3 - third_party/ffmpeg/x86_64/lib/libavformat.a | 3 - third_party/ffmpeg/x86_64/lib/libavutil.a | 3 - third_party/ffmpeg/x86_64/lib/libx264.a | 3 - tools/cabana/SConscript | 2 +- tools/install_ubuntu_dependencies.sh | 6 ++ tools/mac_setup.sh | 1 + tools/replay/SConscript | 2 +- 147 files changed, 13 insertions(+), 517 deletions(-) delete mode 100644 third_party/ffmpeg/Darwin/lib/libavcodec.a delete mode 100644 third_party/ffmpeg/Darwin/lib/libavformat.a delete mode 100644 third_party/ffmpeg/Darwin/lib/libavutil.a delete mode 100644 third_party/ffmpeg/Darwin/lib/libx264.a delete mode 100755 third_party/ffmpeg/build.sh delete mode 100644 third_party/ffmpeg/include/libavcodec/ac3_parser.h delete mode 100644 third_party/ffmpeg/include/libavcodec/adts_parser.h delete mode 100644 third_party/ffmpeg/include/libavcodec/avcodec.h delete mode 100644 third_party/ffmpeg/include/libavcodec/avdct.h delete mode 100644 third_party/ffmpeg/include/libavcodec/avfft.h delete mode 100644 third_party/ffmpeg/include/libavcodec/bsf.h delete mode 100644 third_party/ffmpeg/include/libavcodec/codec.h delete mode 100644 third_party/ffmpeg/include/libavcodec/codec_desc.h delete mode 100644 third_party/ffmpeg/include/libavcodec/codec_id.h delete mode 100644 third_party/ffmpeg/include/libavcodec/codec_par.h delete mode 100644 third_party/ffmpeg/include/libavcodec/d3d11va.h delete mode 100644 third_party/ffmpeg/include/libavcodec/defs.h delete mode 100644 third_party/ffmpeg/include/libavcodec/dirac.h delete mode 100644 third_party/ffmpeg/include/libavcodec/dv_profile.h delete mode 100644 third_party/ffmpeg/include/libavcodec/dxva2.h delete mode 100644 third_party/ffmpeg/include/libavcodec/jni.h delete mode 100644 third_party/ffmpeg/include/libavcodec/mediacodec.h delete mode 100644 third_party/ffmpeg/include/libavcodec/packet.h delete mode 100644 third_party/ffmpeg/include/libavcodec/qsv.h delete mode 100644 third_party/ffmpeg/include/libavcodec/vdpau.h delete mode 100644 third_party/ffmpeg/include/libavcodec/version.h delete mode 100644 third_party/ffmpeg/include/libavcodec/version_major.h delete mode 100644 third_party/ffmpeg/include/libavcodec/videotoolbox.h delete mode 100644 third_party/ffmpeg/include/libavcodec/vorbis_parser.h delete mode 100644 third_party/ffmpeg/include/libavcodec/xvmc.h delete mode 100644 third_party/ffmpeg/include/libavformat/avformat.h delete mode 100644 third_party/ffmpeg/include/libavformat/avio.h delete mode 100644 third_party/ffmpeg/include/libavformat/version.h delete mode 100644 third_party/ffmpeg/include/libavformat/version_major.h delete mode 100644 third_party/ffmpeg/include/libavutil/adler32.h delete mode 100644 third_party/ffmpeg/include/libavutil/aes.h delete mode 100644 third_party/ffmpeg/include/libavutil/aes_ctr.h delete mode 100644 third_party/ffmpeg/include/libavutil/ambient_viewing_environment.h delete mode 100644 third_party/ffmpeg/include/libavutil/attributes.h delete mode 100644 third_party/ffmpeg/include/libavutil/audio_fifo.h delete mode 100644 third_party/ffmpeg/include/libavutil/avassert.h delete mode 100644 third_party/ffmpeg/include/libavutil/avconfig.h delete mode 100644 third_party/ffmpeg/include/libavutil/avstring.h delete mode 100644 third_party/ffmpeg/include/libavutil/avutil.h delete mode 100644 third_party/ffmpeg/include/libavutil/base64.h delete mode 100644 third_party/ffmpeg/include/libavutil/blowfish.h delete mode 100644 third_party/ffmpeg/include/libavutil/bprint.h delete mode 100644 third_party/ffmpeg/include/libavutil/bswap.h delete mode 100644 third_party/ffmpeg/include/libavutil/buffer.h delete mode 100644 third_party/ffmpeg/include/libavutil/camellia.h delete mode 100644 third_party/ffmpeg/include/libavutil/cast5.h delete mode 100644 third_party/ffmpeg/include/libavutil/channel_layout.h delete mode 100644 third_party/ffmpeg/include/libavutil/common.h delete mode 100644 third_party/ffmpeg/include/libavutil/cpu.h delete mode 100644 third_party/ffmpeg/include/libavutil/crc.h delete mode 100644 third_party/ffmpeg/include/libavutil/csp.h delete mode 100644 third_party/ffmpeg/include/libavutil/des.h delete mode 100644 third_party/ffmpeg/include/libavutil/detection_bbox.h delete mode 100644 third_party/ffmpeg/include/libavutil/dict.h delete mode 100644 third_party/ffmpeg/include/libavutil/display.h delete mode 100644 third_party/ffmpeg/include/libavutil/dovi_meta.h delete mode 100644 third_party/ffmpeg/include/libavutil/downmix_info.h delete mode 100644 third_party/ffmpeg/include/libavutil/encryption_info.h delete mode 100644 third_party/ffmpeg/include/libavutil/error.h delete mode 100644 third_party/ffmpeg/include/libavutil/eval.h delete mode 100644 third_party/ffmpeg/include/libavutil/executor.h delete mode 100644 third_party/ffmpeg/include/libavutil/ffversion.h delete mode 100644 third_party/ffmpeg/include/libavutil/fifo.h delete mode 100644 third_party/ffmpeg/include/libavutil/file.h delete mode 100644 third_party/ffmpeg/include/libavutil/film_grain_params.h delete mode 100644 third_party/ffmpeg/include/libavutil/frame.h delete mode 100644 third_party/ffmpeg/include/libavutil/hash.h delete mode 100644 third_party/ffmpeg/include/libavutil/hdr_dynamic_metadata.h delete mode 100644 third_party/ffmpeg/include/libavutil/hdr_dynamic_vivid_metadata.h delete mode 100644 third_party/ffmpeg/include/libavutil/hmac.h delete mode 100644 third_party/ffmpeg/include/libavutil/hwcontext.h delete mode 100644 third_party/ffmpeg/include/libavutil/hwcontext_cuda.h delete mode 100644 third_party/ffmpeg/include/libavutil/hwcontext_d3d11va.h delete mode 100644 third_party/ffmpeg/include/libavutil/hwcontext_drm.h delete mode 100644 third_party/ffmpeg/include/libavutil/hwcontext_dxva2.h delete mode 100644 third_party/ffmpeg/include/libavutil/hwcontext_mediacodec.h delete mode 100644 third_party/ffmpeg/include/libavutil/hwcontext_opencl.h delete mode 100644 third_party/ffmpeg/include/libavutil/hwcontext_qsv.h delete mode 100644 third_party/ffmpeg/include/libavutil/hwcontext_vaapi.h delete mode 100644 third_party/ffmpeg/include/libavutil/hwcontext_vdpau.h delete mode 100644 third_party/ffmpeg/include/libavutil/hwcontext_videotoolbox.h delete mode 100644 third_party/ffmpeg/include/libavutil/hwcontext_vulkan.h delete mode 100644 third_party/ffmpeg/include/libavutil/imgutils.h delete mode 100644 third_party/ffmpeg/include/libavutil/intfloat.h delete mode 100644 third_party/ffmpeg/include/libavutil/intreadwrite.h delete mode 100644 third_party/ffmpeg/include/libavutil/lfg.h delete mode 100644 third_party/ffmpeg/include/libavutil/log.h delete mode 100644 third_party/ffmpeg/include/libavutil/lzo.h delete mode 100644 third_party/ffmpeg/include/libavutil/macros.h delete mode 100644 third_party/ffmpeg/include/libavutil/mastering_display_metadata.h delete mode 100644 third_party/ffmpeg/include/libavutil/mathematics.h delete mode 100644 third_party/ffmpeg/include/libavutil/md5.h delete mode 100644 third_party/ffmpeg/include/libavutil/mem.h delete mode 100644 third_party/ffmpeg/include/libavutil/motion_vector.h delete mode 100644 third_party/ffmpeg/include/libavutil/murmur3.h delete mode 100644 third_party/ffmpeg/include/libavutil/opt.h delete mode 100644 third_party/ffmpeg/include/libavutil/parseutils.h delete mode 100644 third_party/ffmpeg/include/libavutil/pixdesc.h delete mode 100644 third_party/ffmpeg/include/libavutil/pixelutils.h delete mode 100644 third_party/ffmpeg/include/libavutil/pixfmt.h delete mode 100644 third_party/ffmpeg/include/libavutil/random_seed.h delete mode 100644 third_party/ffmpeg/include/libavutil/rational.h delete mode 100644 third_party/ffmpeg/include/libavutil/rc4.h delete mode 100644 third_party/ffmpeg/include/libavutil/replaygain.h delete mode 100644 third_party/ffmpeg/include/libavutil/ripemd.h delete mode 100644 third_party/ffmpeg/include/libavutil/samplefmt.h delete mode 100644 third_party/ffmpeg/include/libavutil/sha.h delete mode 100644 third_party/ffmpeg/include/libavutil/sha512.h delete mode 100644 third_party/ffmpeg/include/libavutil/spherical.h delete mode 100644 third_party/ffmpeg/include/libavutil/stereo3d.h delete mode 100644 third_party/ffmpeg/include/libavutil/tea.h delete mode 100644 third_party/ffmpeg/include/libavutil/threadmessage.h delete mode 100644 third_party/ffmpeg/include/libavutil/time.h delete mode 100644 third_party/ffmpeg/include/libavutil/timecode.h delete mode 100644 third_party/ffmpeg/include/libavutil/timestamp.h delete mode 100644 third_party/ffmpeg/include/libavutil/tree.h delete mode 100644 third_party/ffmpeg/include/libavutil/twofish.h delete mode 100644 third_party/ffmpeg/include/libavutil/tx.h delete mode 100644 third_party/ffmpeg/include/libavutil/uuid.h delete mode 100644 third_party/ffmpeg/include/libavutil/version.h delete mode 100644 third_party/ffmpeg/include/libavutil/video_enc_params.h delete mode 100644 third_party/ffmpeg/include/libavutil/video_hint.h delete mode 100644 third_party/ffmpeg/include/libavutil/xtea.h delete mode 100644 third_party/ffmpeg/include/x264.h delete mode 100644 third_party/ffmpeg/include/x264_config.h delete mode 100644 third_party/ffmpeg/larch64/lib/libavcodec.a delete mode 100644 third_party/ffmpeg/larch64/lib/libavformat.a delete mode 100644 third_party/ffmpeg/larch64/lib/libavutil.a delete mode 100644 third_party/ffmpeg/larch64/lib/libx264.a delete mode 100644 third_party/ffmpeg/x86_64/lib/libavcodec.a delete mode 100644 third_party/ffmpeg/x86_64/lib/libavformat.a delete mode 100644 third_party/ffmpeg/x86_64/lib/libavutil.a delete mode 100644 third_party/ffmpeg/x86_64/lib/libx264.a diff --git a/.gitattributes b/.gitattributes index 152dbfe45..cc1605a13 100644 --- a/.gitattributes +++ b/.gitattributes @@ -15,7 +15,6 @@ third_party/**/*.a filter=lfs diff=lfs merge=lfs -text third_party/**/*.so filter=lfs diff=lfs merge=lfs -text third_party/**/*.so.* filter=lfs diff=lfs merge=lfs -text third_party/**/*.dylib filter=lfs diff=lfs merge=lfs -text -third_party/ffmpeg/include/**/*.h filter=lfs diff=lfs merge=lfs -text third_party/acados/*/t_renderer filter=lfs diff=lfs merge=lfs -text third_party/qt5/larch64/bin/lrelease filter=lfs diff=lfs merge=lfs -text third_party/qt5/larch64/bin/lupdate filter=lfs diff=lfs merge=lfs -text diff --git a/SConstruct b/SConstruct index a788b9a29..5b13bd635 100644 --- a/SConstruct +++ b/SConstruct @@ -91,7 +91,6 @@ if arch == "larch64": ] libpath = [ - f"#third_party/ffmpeg/{arch}/lib", "/usr/local/lib", "/system/vendor/lib64", f"#third_party/acados/{arch}/lib", @@ -115,7 +114,6 @@ else: libpath = [ f"#third_party/libyuv/{arch}/lib", f"#third_party/acados/{arch}/lib", - f"#third_party/ffmpeg/{arch}/lib", f"{brew_prefix}/lib", f"{brew_prefix}/opt/openssl@3.0/lib", "/System/Library/Frameworks/OpenGL.framework/Libraries", @@ -132,7 +130,6 @@ else: libpath = [ f"#third_party/acados/{arch}/lib", f"#third_party/libyuv/{arch}/lib", - f"#third_party/ffmpeg/{arch}/lib", "/usr/lib", "/usr/local/lib", ] @@ -180,7 +177,6 @@ env = Environment( "#third_party/libyuv/include", "#third_party/json11", "#third_party/linux/include", - "#third_party/ffmpeg/include", "#third_party", "#msgq", ], diff --git a/system/loggerd/SConscript b/system/loggerd/SConscript index c87db4232..cf169f4dc 100644 --- a/system/loggerd/SConscript +++ b/system/loggerd/SConscript @@ -1,8 +1,8 @@ Import('env', 'arch', 'messaging', 'common', 'visionipc') libs = [common, messaging, visionipc, - 'yuv', 'OpenCL', 'pthread', 'zstd', - 'avformat', 'avcodec', 'avutil', 'x264'] + 'avformat', 'avcodec', 'avutil', + 'yuv', 'OpenCL', 'pthread', 'zstd'] src = ['logger.cc', 'zstd_writer.cc', 'video_writer.cc', 'encoder/encoder.cc', 'encoder/v4l_encoder.cc', 'encoder/jpeg_encoder.cc'] if arch != "larch64": diff --git a/system/loggerd/tests/test_loggerd.py b/system/loggerd/tests/test_loggerd.py index ae7196684..c6a4b12e6 100644 --- a/system/loggerd/tests/test_loggerd.py +++ b/system/loggerd/tests/test_loggerd.py @@ -316,18 +316,8 @@ class TestLoggerd: self._publish_camera_and_audio_messages() qcamera_ts_path = os.path.join(self._get_latest_log_dir(), 'qcamera.ts') - - # simplest heuristic: look for AAC ADTS syncwords in the TS file - def ts_has_audio_stream(ts_path: str) -> bool: - try: - with open(ts_path, 'rb') as f: - data = f.read() - # ADTS headers typically start with 0xFFF1 or 0xFFF9 - return (b"\xFF\xF1" in data) or (b"\xFF\xF9" in data) - except Exception: - return False - - has_audio_stream = ts_has_audio_stream(qcamera_ts_path) + ffprobe_cmd = f"ffprobe -i {qcamera_ts_path} -show_streams -select_streams a -loglevel error" + has_audio_stream = subprocess.run(ffprobe_cmd, shell=True, capture_output=True).stdout.strip() != b'' assert has_audio_stream == record_audio raw_audio_in_rlog = any(m.which() == 'rawAudioData' for m in LogReader(os.path.join(self._get_latest_log_dir(), 'rlog.zst'))) diff --git a/third_party/.gitignore b/third_party/.gitignore index 0d5b3d221..0d20b6487 100644 --- a/third_party/.gitignore +++ b/third_party/.gitignore @@ -1,3 +1 @@ *.pyc -src/ -build/ diff --git a/third_party/ffmpeg/Darwin/lib/libavcodec.a b/third_party/ffmpeg/Darwin/lib/libavcodec.a deleted file mode 100644 index a87d3323c..000000000 --- a/third_party/ffmpeg/Darwin/lib/libavcodec.a +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2dcb729a6833558fc0e01abe5e728eba2b3c215abc7dece74d5ce1bc1d279e7d -size 2328832 diff --git a/third_party/ffmpeg/Darwin/lib/libavformat.a b/third_party/ffmpeg/Darwin/lib/libavformat.a deleted file mode 100644 index b7c48f796..000000000 --- a/third_party/ffmpeg/Darwin/lib/libavformat.a +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:825f145f7168226deae199538113f063e6b71a02999b297c75254dfa466b73cb -size 863544 diff --git a/third_party/ffmpeg/Darwin/lib/libavutil.a b/third_party/ffmpeg/Darwin/lib/libavutil.a deleted file mode 100644 index 17985d73b..000000000 --- a/third_party/ffmpeg/Darwin/lib/libavutil.a +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9c13c341440029d06a1044479876843bc93615849554dc636e386f31f6776850 -size 789024 diff --git a/third_party/ffmpeg/Darwin/lib/libx264.a b/third_party/ffmpeg/Darwin/lib/libx264.a deleted file mode 100644 index 27375793a..000000000 --- a/third_party/ffmpeg/Darwin/lib/libx264.a +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8bedf59051187092bd9f282acb7f22f7885ed41b2f89692e0478252329bd7e4b -size 1941808 diff --git a/third_party/ffmpeg/build.sh b/third_party/ffmpeg/build.sh deleted file mode 100755 index f8812d6f1..000000000 --- a/third_party/ffmpeg/build.sh +++ /dev/null @@ -1,83 +0,0 @@ -#!/usr/bin/env bash -set -e - -DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" -cd $DIR - -# Detect arch -ARCHNAME="x86_64" -if [ -f /TICI ]; then - ARCHNAME="larch64" -fi -if [[ "$OSTYPE" == "darwin"* ]]; then - ARCHNAME="Darwin" -fi - -VERSION="6.1.1" # LTS -PREFIX="$DIR/$ARCHNAME" -BUILD_DIR="$DIR/build/" - -mkdir -p "$BUILD_DIR" -rm -rf include/ && mkdir -p include/ -rm -rf "$PREFIX" && mkdir -p "$PREFIX" - -# *** build x264 *** -if [[ ! -d "$DIR/src/x264/" ]]; then - # TODO: pin to a commit - git clone --depth=1 --branch "stable" https://code.videolan.org/videolan/x264.git "$DIR/src/x264/" -fi -cd $DIR/src/x264 -git fetch origin b35605ace3ddf7c1a5d67a2eb553f034aef41d55 -git checkout -f FETCH_HEAD -./configure --prefix="$PREFIX" --enable-static --disable-opencl --enable-pic --disable-cli -make -j8 -make install -cp -a "$PREFIX/include/." "$DIR/include/" - -# *** build ffmpeg *** -mkdir -p "$DIR/src" -if [[ ! -d "$DIR/src/ffmpeg-$VERSION" ]]; then - echo "Downloading FFmpeg $VERSION ..." - curl -L "https://ffmpeg.org/releases/ffmpeg-${VERSION}.tar.xz" -o "$DIR/src/ffmpeg-${VERSION}.tar.xz" - tar -C "$DIR/src" -xf "$DIR/src/ffmpeg-${VERSION}.tar.xz" -fi - -cd $BUILD_DIR - -export PKG_CONFIG_PATH="$PREFIX/lib/pkgconfig:${PKG_CONFIG_PATH:-}" -export EXTRA_CFLAGS="-I$PREFIX/include ${EXTRA_CFLAGS:-}" -export EXTRA_LDFLAGS="-L$PREFIX/lib ${EXTRA_LDFLAGS:-}" -# Configure minimal static FFmpeg for desktop Linux tools -"$DIR/src/ffmpeg-$VERSION/configure" \ - --prefix="$PREFIX" \ - --datadir="$PREFIX" \ - --docdir="$PREFIX" \ - --mandir="$PREFIX" \ - --enable-static --disable-shared \ - --disable-programs --disable-doc --disable-debug \ - --disable-network \ - --disable-avdevice --disable-swscale --disable-swresample --disable-postproc --disable-avfilter \ - --disable-autodetect --disable-iconv \ - --enable-avcodec --enable-avformat --enable-avutil \ - --enable-protocol=file \ - --pkg-config-flags=--static \ - --enable-gpl --enable-libx264 \ - --disable-decoders --enable-decoder=h264,hevc,aac \ - --disable-encoders --enable-encoder=libx264,ffvhuff,aac \ - --disable-demuxers --enable-demuxer=mpegts,hevc,h264,matroska,mov \ - --disable-muxers --enable-muxer=matroska,mpegts \ - --disable-parsers --enable-parser=h264,hevc,aac,vorbis \ - --disable-bsfs \ - --enable-small \ - --extra-cflags="${EXTRA_CFLAGS:-}" \ - --extra-ldflags="${EXTRA_LDFLAGS:-}" - - -make -j$(nproc) -make install -cp -a "$PREFIX/include/." "$DIR/include/" - -# *** cleanup *** -cd $PREFIX -rm -rf share/ doc/ man/ examples/ examples/ include/ lib/pkgconfig/ -rm -f lib/libavfilter* "$DIR/include/libavfilter*" diff --git a/third_party/ffmpeg/include/libavcodec/ac3_parser.h b/third_party/ffmpeg/include/libavcodec/ac3_parser.h deleted file mode 100644 index c8acedae8..000000000 --- a/third_party/ffmpeg/include/libavcodec/ac3_parser.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:200c6d2e96975196e8ba5f5716223dc9dda999d51578dabce2fca93175a05252 -size 1207 diff --git a/third_party/ffmpeg/include/libavcodec/adts_parser.h b/third_party/ffmpeg/include/libavcodec/adts_parser.h deleted file mode 100644 index 031f10320..000000000 --- a/third_party/ffmpeg/include/libavcodec/adts_parser.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d466583a8dc1260b015e588adbe3abd45f3f8ca0e43722f3088b472e80492a15 -size 1354 diff --git a/third_party/ffmpeg/include/libavcodec/avcodec.h b/third_party/ffmpeg/include/libavcodec/avcodec.h deleted file mode 100644 index 82fc7d1bd..000000000 --- a/third_party/ffmpeg/include/libavcodec/avcodec.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fc38e85633a2c6c44fc60b319d7e046a6e4206456565aa61240dd93ee2e81188 -size 114251 diff --git a/third_party/ffmpeg/include/libavcodec/avdct.h b/third_party/ffmpeg/include/libavcodec/avdct.h deleted file mode 100644 index 1a57c4300..000000000 --- a/third_party/ffmpeg/include/libavcodec/avdct.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7c125edc1985ec078f99abb1c9044c4fc76b06a03eb02c026cb968fb9d41fcca -size 2726 diff --git a/third_party/ffmpeg/include/libavcodec/avfft.h b/third_party/ffmpeg/include/libavcodec/avfft.h deleted file mode 100644 index f54c365c1..000000000 --- a/third_party/ffmpeg/include/libavcodec/avfft.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:81bb6c5359f57de53b0e39766591404b632d15e700d313c97ded234d63d4e393 -size 4081 diff --git a/third_party/ffmpeg/include/libavcodec/bsf.h b/third_party/ffmpeg/include/libavcodec/bsf.h deleted file mode 100644 index 54fc91b11..000000000 --- a/third_party/ffmpeg/include/libavcodec/bsf.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cc26676aa44638fa5cbef953d967e19e2084b46aaa100f0a35ba2d34b302301c -size 11540 diff --git a/third_party/ffmpeg/include/libavcodec/codec.h b/third_party/ffmpeg/include/libavcodec/codec.h deleted file mode 100644 index b8c384172..000000000 --- a/third_party/ffmpeg/include/libavcodec/codec.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d8b75f2e4128ce59db3172e2bdcbf64540424e6242952dc0912002bee5314254 -size 13460 diff --git a/third_party/ffmpeg/include/libavcodec/codec_desc.h b/third_party/ffmpeg/include/libavcodec/codec_desc.h deleted file mode 100644 index 108aa120d..000000000 --- a/third_party/ffmpeg/include/libavcodec/codec_desc.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4c1a8d02398cc82c1913487405c483f4d74ec71ed3e666ed7e7570f581c8f479 -size 3974 diff --git a/third_party/ffmpeg/include/libavcodec/codec_id.h b/third_party/ffmpeg/include/libavcodec/codec_id.h deleted file mode 100644 index 72002e689..000000000 --- a/third_party/ffmpeg/include/libavcodec/codec_id.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:754a846797be20c7c1ca30358d2555873df3bb19b6b2c8011c85697440f600df -size 18020 diff --git a/third_party/ffmpeg/include/libavcodec/codec_par.h b/third_party/ffmpeg/include/libavcodec/codec_par.h deleted file mode 100644 index 2c2f42e88..000000000 --- a/third_party/ffmpeg/include/libavcodec/codec_par.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5f2bef7f23782ac9c0b5769eeb54704103efa5074ca5b657d7f01890795baf08 -size 8065 diff --git a/third_party/ffmpeg/include/libavcodec/d3d11va.h b/third_party/ffmpeg/include/libavcodec/d3d11va.h deleted file mode 100644 index aa372312b..000000000 --- a/third_party/ffmpeg/include/libavcodec/d3d11va.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:74a55a2e3f19ce797e99624a224302f25efa89a115b9bf2e932c8fa179b0cc66 -size 2853 diff --git a/third_party/ffmpeg/include/libavcodec/defs.h b/third_party/ffmpeg/include/libavcodec/defs.h deleted file mode 100644 index 6467ef663..000000000 --- a/third_party/ffmpeg/include/libavcodec/defs.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0bb9d7049499d252c75f1fe15fced5022ebb30d1966d47217848236eb021604a -size 12358 diff --git a/third_party/ffmpeg/include/libavcodec/dirac.h b/third_party/ffmpeg/include/libavcodec/dirac.h deleted file mode 100644 index 8bf7c4a0d..000000000 --- a/third_party/ffmpeg/include/libavcodec/dirac.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:09fd1f670422ee61713568abd90fdf371b30e6fe35bc6fb8213f1ad32b45cf56 -size 4126 diff --git a/third_party/ffmpeg/include/libavcodec/dv_profile.h b/third_party/ffmpeg/include/libavcodec/dv_profile.h deleted file mode 100644 index 738746f3f..000000000 --- a/third_party/ffmpeg/include/libavcodec/dv_profile.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:19f59e0b20ac583de4bfd76d18889d334bf0b6cdf7b5356723a33f3874738466 -size 3694 diff --git a/third_party/ffmpeg/include/libavcodec/dxva2.h b/third_party/ffmpeg/include/libavcodec/dxva2.h deleted file mode 100644 index aa41fbba8..000000000 --- a/third_party/ffmpeg/include/libavcodec/dxva2.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e69dc45a7d5a9206b3bfead10caf3117117005cd532c4fc599d9976637c1b9e3 -size 2361 diff --git a/third_party/ffmpeg/include/libavcodec/jni.h b/third_party/ffmpeg/include/libavcodec/jni.h deleted file mode 100644 index 942690da8..000000000 --- a/third_party/ffmpeg/include/libavcodec/jni.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:18ca8eae5bce081b4eef1b1f61a62aefed1d4e6e3cde41487c81fb96ee709e51 -size 1650 diff --git a/third_party/ffmpeg/include/libavcodec/mediacodec.h b/third_party/ffmpeg/include/libavcodec/mediacodec.h deleted file mode 100644 index 2afacbd1d..000000000 --- a/third_party/ffmpeg/include/libavcodec/mediacodec.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1f64544000dd2f2ec94604d5fcf7c2a6d26d32d085264356084a4b53a7f0c3f0 -size 3570 diff --git a/third_party/ffmpeg/include/libavcodec/packet.h b/third_party/ffmpeg/include/libavcodec/packet.h deleted file mode 100644 index 7c11f362e..000000000 --- a/third_party/ffmpeg/include/libavcodec/packet.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:18eb41b9aa9ec55b3ee3932279cc54ead813710741f004c5ea5a1c2957896f59 -size 28678 diff --git a/third_party/ffmpeg/include/libavcodec/qsv.h b/third_party/ffmpeg/include/libavcodec/qsv.h deleted file mode 100644 index 02c4c961f..000000000 --- a/third_party/ffmpeg/include/libavcodec/qsv.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e45780237ab0e9ea9ec11aab9f62dfb8b059138f846632ecacdc024db16dfb1b -size 3844 diff --git a/third_party/ffmpeg/include/libavcodec/vdpau.h b/third_party/ffmpeg/include/libavcodec/vdpau.h deleted file mode 100644 index 6ccba34db..000000000 --- a/third_party/ffmpeg/include/libavcodec/vdpau.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3e0c963348495936556724cfb4d2d35eb4c14007ba72d51be94dd26c83d93fb8 -size 5104 diff --git a/third_party/ffmpeg/include/libavcodec/version.h b/third_party/ffmpeg/include/libavcodec/version.h deleted file mode 100644 index 9f2069af2..000000000 --- a/third_party/ffmpeg/include/libavcodec/version.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b6938a71f9eb6b1ef2a201499f47ef1131869b90755932a2ee70a272b2ee0784 -size 1619 diff --git a/third_party/ffmpeg/include/libavcodec/version_major.h b/third_party/ffmpeg/include/libavcodec/version_major.h deleted file mode 100644 index 7781faab4..000000000 --- a/third_party/ffmpeg/include/libavcodec/version_major.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e9562c933e8eebb020eb143d21c4b2b529e2dc38d2d7365bc13adb8d0ef8227b -size 2494 diff --git a/third_party/ffmpeg/include/libavcodec/videotoolbox.h b/third_party/ffmpeg/include/libavcodec/videotoolbox.h deleted file mode 100644 index 59f788e02..000000000 --- a/third_party/ffmpeg/include/libavcodec/videotoolbox.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:19b6ba05f4e9f600044f45f1a5dc04d0da9276dc9a1cb5b07826df5cab5012c9 -size 4677 diff --git a/third_party/ffmpeg/include/libavcodec/vorbis_parser.h b/third_party/ffmpeg/include/libavcodec/vorbis_parser.h deleted file mode 100644 index d578829d4..000000000 --- a/third_party/ffmpeg/include/libavcodec/vorbis_parser.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:57077b2e1d28d42636cab0f69e4b92b1ad64ac2eaa2843c270a6afaf308a76ae -size 2285 diff --git a/third_party/ffmpeg/include/libavcodec/xvmc.h b/third_party/ffmpeg/include/libavcodec/xvmc.h deleted file mode 100644 index abe162a93..000000000 --- a/third_party/ffmpeg/include/libavcodec/xvmc.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ca59c5caaaa32368f7c84c714bbf2ad1cba61ec9ca5b5cb1a14cf6975affe8a2 -size 6136 diff --git a/third_party/ffmpeg/include/libavformat/avformat.h b/third_party/ffmpeg/include/libavformat/avformat.h deleted file mode 100644 index a4244e913..000000000 --- a/third_party/ffmpeg/include/libavformat/avformat.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c1ca6edf1d5eab00126fa7320f01c21ed8cea09dc2d931f84a4015b9e50f8d2a -size 110803 diff --git a/third_party/ffmpeg/include/libavformat/avio.h b/third_party/ffmpeg/include/libavformat/avio.h deleted file mode 100644 index af9666e18..000000000 --- a/third_party/ffmpeg/include/libavformat/avio.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:de6fe4be374481b1ee5ba8722882af7e7698038fd744375de153d1c78f528836 -size 31681 diff --git a/third_party/ffmpeg/include/libavformat/version.h b/third_party/ffmpeg/include/libavformat/version.h deleted file mode 100644 index 07fe55f95..000000000 --- a/third_party/ffmpeg/include/libavformat/version.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:08b64bb52109a07ae3ee1a1dafd12ad336d0a646c23aea075c5024b9437f3b29 -size 1652 diff --git a/third_party/ffmpeg/include/libavformat/version_major.h b/third_party/ffmpeg/include/libavformat/version_major.h deleted file mode 100644 index 0efde349b..000000000 --- a/third_party/ffmpeg/include/libavformat/version_major.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2d440097abe2039c3c831d3b91d315857829d04e462415ee632c9f36321d520d -size 2240 diff --git a/third_party/ffmpeg/include/libavutil/adler32.h b/third_party/ffmpeg/include/libavutil/adler32.h deleted file mode 100644 index 19f01b344..000000000 --- a/third_party/ffmpeg/include/libavutil/adler32.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f21a861957bf4b1812ed67fdc528890f9cff1bb483facf0f5109e4a51932fe3b -size 1696 diff --git a/third_party/ffmpeg/include/libavutil/aes.h b/third_party/ffmpeg/include/libavutil/aes.h deleted file mode 100644 index 6d81ffa43..000000000 --- a/third_party/ffmpeg/include/libavutil/aes.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0a86ebeaf9ed33548bf0f92359f7047e521d4d80fe6b0ef1c8ef9505e38b6d28 -size 1912 diff --git a/third_party/ffmpeg/include/libavutil/aes_ctr.h b/third_party/ffmpeg/include/libavutil/aes_ctr.h deleted file mode 100644 index 1a156aed3..000000000 --- a/third_party/ffmpeg/include/libavutil/aes_ctr.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fbbb94888bfab2ea7141e7e1afa5872de099e7ee57baea17632c3d4fdd2cba3f -size 2443 diff --git a/third_party/ffmpeg/include/libavutil/ambient_viewing_environment.h b/third_party/ffmpeg/include/libavutil/ambient_viewing_environment.h deleted file mode 100644 index 42a94d194..000000000 --- a/third_party/ffmpeg/include/libavutil/ambient_viewing_environment.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:00135de08089f8c711bb113588f80575a1d26e86d50b6dac5928a27a0ff9a8c7 -size 2585 diff --git a/third_party/ffmpeg/include/libavutil/attributes.h b/third_party/ffmpeg/include/libavutil/attributes.h deleted file mode 100644 index 2a4784261..000000000 --- a/third_party/ffmpeg/include/libavutil/attributes.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1f51258ba39861e08cbc8d407ec37a8d0fd28669aa7306c1365cb9c675fa1d76 -size 4850 diff --git a/third_party/ffmpeg/include/libavutil/audio_fifo.h b/third_party/ffmpeg/include/libavutil/audio_fifo.h deleted file mode 100644 index 28a3364ee..000000000 --- a/third_party/ffmpeg/include/libavutil/audio_fifo.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3c513ca46927346c0673cd9db302afe62dbbf976844f36da0307fd10f7f47bfd -size 5966 diff --git a/third_party/ffmpeg/include/libavutil/avassert.h b/third_party/ffmpeg/include/libavutil/avassert.h deleted file mode 100644 index 40ecca50e..000000000 --- a/third_party/ffmpeg/include/libavutil/avassert.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:39dfe16f0790daa5d6b9b1bad3167c3c9e638c72452e0acdaf2d163c9c75ea40 -size 2408 diff --git a/third_party/ffmpeg/include/libavutil/avconfig.h b/third_party/ffmpeg/include/libavutil/avconfig.h deleted file mode 100644 index 4e1eedcf9..000000000 --- a/third_party/ffmpeg/include/libavutil/avconfig.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:975611ad5eba15212d9e1d5fca9d4fdf0daec6d2269b2fcab8e29af8667164bc -size 180 diff --git a/third_party/ffmpeg/include/libavutil/avstring.h b/third_party/ffmpeg/include/libavutil/avstring.h deleted file mode 100644 index ce048414e..000000000 --- a/third_party/ffmpeg/include/libavutil/avstring.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ac51462bf55ff62da39db4b15c7a6b9b783387709f06879196a065691b34edbf -size 14940 diff --git a/third_party/ffmpeg/include/libavutil/avutil.h b/third_party/ffmpeg/include/libavutil/avutil.h deleted file mode 100644 index 7f3e2654e..000000000 --- a/third_party/ffmpeg/include/libavutil/avutil.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7640d72de6a72eb9589ed2c623b4ec0924a241097846086c62dc3e05ab70ee9e -size 9968 diff --git a/third_party/ffmpeg/include/libavutil/base64.h b/third_party/ffmpeg/include/libavutil/base64.h deleted file mode 100644 index db8221772..000000000 --- a/third_party/ffmpeg/include/libavutil/base64.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:81ac13d23f3744fe85ea2651ce903e201cd55fc63fcdd899d2cfe5560d50ef3d -size 2285 diff --git a/third_party/ffmpeg/include/libavutil/blowfish.h b/third_party/ffmpeg/include/libavutil/blowfish.h deleted file mode 100644 index c16f03bfd..000000000 --- a/third_party/ffmpeg/include/libavutil/blowfish.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b955a63c60c8b3be0203ec6c3973f9084d848cf884fe56cd56088301aeef7992 -size 2394 diff --git a/third_party/ffmpeg/include/libavutil/bprint.h b/third_party/ffmpeg/include/libavutil/bprint.h deleted file mode 100644 index 9a5528567..000000000 --- a/third_party/ffmpeg/include/libavutil/bprint.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c19f6dc2c06e55d47bc1fbe9fc7658acc7a0d506b6baf16839a6119a03e873c1 -size 8812 diff --git a/third_party/ffmpeg/include/libavutil/bswap.h b/third_party/ffmpeg/include/libavutil/bswap.h deleted file mode 100644 index b8e017531..000000000 --- a/third_party/ffmpeg/include/libavutil/bswap.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:855e138f6d8c7947553b1c63a7bab06bcb71362f03c72b761fd9ed61820d71aa -size 2903 diff --git a/third_party/ffmpeg/include/libavutil/buffer.h b/third_party/ffmpeg/include/libavutil/buffer.h deleted file mode 100644 index fd462ec6b..000000000 --- a/third_party/ffmpeg/include/libavutil/buffer.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f16742d574216434580573a2b09f56fc5b66b7dda1960d4f02ba59e3269ba548 -size 11998 diff --git a/third_party/ffmpeg/include/libavutil/camellia.h b/third_party/ffmpeg/include/libavutil/camellia.h deleted file mode 100644 index 8d6fdaca6..000000000 --- a/third_party/ffmpeg/include/libavutil/camellia.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1db30753e71c73f1937e807850069e8215cdf37a1bc3ff89d3a6370a719c1fde -size 2139 diff --git a/third_party/ffmpeg/include/libavutil/cast5.h b/third_party/ffmpeg/include/libavutil/cast5.h deleted file mode 100644 index 2dfac6f5b..000000000 --- a/third_party/ffmpeg/include/libavutil/cast5.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:05b2e13aecaa0adbb470081a689f45baffb8e03a71997c31f37a22ea4e383a60 -size 2561 diff --git a/third_party/ffmpeg/include/libavutil/channel_layout.h b/third_party/ffmpeg/include/libavutil/channel_layout.h deleted file mode 100644 index 02549bd9a..000000000 --- a/third_party/ffmpeg/include/libavutil/channel_layout.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5df118692f2b7043820f0e641f1968cadb66c39f0ba85fab7435391322a223f5 -size 33727 diff --git a/third_party/ffmpeg/include/libavutil/common.h b/third_party/ffmpeg/include/libavutil/common.h deleted file mode 100644 index 8f2dfcaeb..000000000 --- a/third_party/ffmpeg/include/libavutil/common.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d861dac3eaecc326fa8fb717d90f195d961cc8ac7898daccfefd10bd5ea42398 -size 17166 diff --git a/third_party/ffmpeg/include/libavutil/cpu.h b/third_party/ffmpeg/include/libavutil/cpu.h deleted file mode 100644 index 3181cd089..000000000 --- a/third_party/ffmpeg/include/libavutil/cpu.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:37c4ccea939e19b463a2a3e4c101ff8dd03c98847fc045395d28ba0c9db8b662 -size 6320 diff --git a/third_party/ffmpeg/include/libavutil/crc.h b/third_party/ffmpeg/include/libavutil/crc.h deleted file mode 100644 index 9cc23bb94..000000000 --- a/third_party/ffmpeg/include/libavutil/crc.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5728cf65705a46723ea28b4f6c8361aad82b76a90e859943efe8af0edb79ec86 -size 3259 diff --git a/third_party/ffmpeg/include/libavutil/csp.h b/third_party/ffmpeg/include/libavutil/csp.h deleted file mode 100644 index f4c8c5652..000000000 --- a/third_party/ffmpeg/include/libavutil/csp.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7969fd662f31cf3180403510a6784a14af60d7f9bf3a569dde84585a696dff09 -size 4927 diff --git a/third_party/ffmpeg/include/libavutil/des.h b/third_party/ffmpeg/include/libavutil/des.h deleted file mode 100644 index 5df3354f9..000000000 --- a/third_party/ffmpeg/include/libavutil/des.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:15ebdda1af65d91c4607a3444c5f749d5e9757ff5d7f4b04213b3194603f74d9 -size 2514 diff --git a/third_party/ffmpeg/include/libavutil/detection_bbox.h b/third_party/ffmpeg/include/libavutil/detection_bbox.h deleted file mode 100644 index f81ef0a64..000000000 --- a/third_party/ffmpeg/include/libavutil/detection_bbox.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8f5817d77af243a52e905947aa5ae73c218d68dba909040b2f63bd2ca6f93922 -size 3524 diff --git a/third_party/ffmpeg/include/libavutil/dict.h b/third_party/ffmpeg/include/libavutil/dict.h deleted file mode 100644 index e01fbf8bb..000000000 --- a/third_party/ffmpeg/include/libavutil/dict.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c95cba1829e7886e31469f4e058bb9af3c8f215619c7ffddbc2045c9039f0554 -size 9374 diff --git a/third_party/ffmpeg/include/libavutil/display.h b/third_party/ffmpeg/include/libavutil/display.h deleted file mode 100644 index 6bca44f9f..000000000 --- a/third_party/ffmpeg/include/libavutil/display.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b9c78c80aa9331b945802b6bcd1db4ecc9ec4f9fad41993cc82b880c0dec2576 -size 3472 diff --git a/third_party/ffmpeg/include/libavutil/dovi_meta.h b/third_party/ffmpeg/include/libavutil/dovi_meta.h deleted file mode 100644 index 0d545564b..000000000 --- a/third_party/ffmpeg/include/libavutil/dovi_meta.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3a67a422676c5b6e2dfd59b3e6a8e3f816b0602f5d73332c0b1d8e6b43644077 -size 7641 diff --git a/third_party/ffmpeg/include/libavutil/downmix_info.h b/third_party/ffmpeg/include/libavutil/downmix_info.h deleted file mode 100644 index cfebdff5b..000000000 --- a/third_party/ffmpeg/include/libavutil/downmix_info.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2fc23ad8f0750d82fcd6aa3b653998e2ea9721f9d1664df7b6cb80e93d7fa3aa -size 3235 diff --git a/third_party/ffmpeg/include/libavutil/encryption_info.h b/third_party/ffmpeg/include/libavutil/encryption_info.h deleted file mode 100644 index 490da787f..000000000 --- a/third_party/ffmpeg/include/libavutil/encryption_info.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ccc3a4a889b8a3c5aaf37b9fb2407bcdf23a065487c7cba718518a517c463b18 -size 7056 diff --git a/third_party/ffmpeg/include/libavutil/error.h b/third_party/ffmpeg/include/libavutil/error.h deleted file mode 100644 index 30e492756..000000000 --- a/third_party/ffmpeg/include/libavutil/error.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f90feec75a317e491618e06ce14d19218e5b0257c885155613b704002a9d5bda -size 5489 diff --git a/third_party/ffmpeg/include/libavutil/eval.h b/third_party/ffmpeg/include/libavutil/eval.h deleted file mode 100644 index 49448a05c..000000000 --- a/third_party/ffmpeg/include/libavutil/eval.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5e92af6be91c610c3bcad3a344a2df5e3516153fc89045464447bb0ee44b1f56 -size 6599 diff --git a/third_party/ffmpeg/include/libavutil/executor.h b/third_party/ffmpeg/include/libavutil/executor.h deleted file mode 100644 index 91edf0541..000000000 --- a/third_party/ffmpeg/include/libavutil/executor.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0c949cf5269ad933ed1a200ece06a3998db10147e1403629a398b7be52459cdc -size 1885 diff --git a/third_party/ffmpeg/include/libavutil/ffversion.h b/third_party/ffmpeg/include/libavutil/ffversion.h deleted file mode 100644 index 7fe4e4453..000000000 --- a/third_party/ffmpeg/include/libavutil/ffversion.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1defa7afe4fab2090455bf74435ce185bce721d1b795f94c56994a031ae20080 -size 184 diff --git a/third_party/ffmpeg/include/libavutil/fifo.h b/third_party/ffmpeg/include/libavutil/fifo.h deleted file mode 100644 index 8e4cff860..000000000 --- a/third_party/ffmpeg/include/libavutil/fifo.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0c77e489715a83e1dc1ce3187b5e23b1b0cd52762638919e955d43afae316f9b -size 15452 diff --git a/third_party/ffmpeg/include/libavutil/file.h b/third_party/ffmpeg/include/libavutil/file.h deleted file mode 100644 index 44cae7819..000000000 --- a/third_party/ffmpeg/include/libavutil/file.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9f91c41bc9a14a206bc86c87b9edd7215b5be63b8aade2bda40297dc6752de36 -size 3039 diff --git a/third_party/ffmpeg/include/libavutil/film_grain_params.h b/third_party/ffmpeg/include/libavutil/film_grain_params.h deleted file mode 100644 index 8d052ff6d..000000000 --- a/third_party/ffmpeg/include/libavutil/film_grain_params.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e1bda2934dce25cb0bb9fa6eaae230cd21aca79428905b66417f74fc8f0fa72a -size 8499 diff --git a/third_party/ffmpeg/include/libavutil/frame.h b/third_party/ffmpeg/include/libavutil/frame.h deleted file mode 100644 index 013f70247..000000000 --- a/third_party/ffmpeg/include/libavutil/frame.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0512cdd2a4479faac17b556f6311a3307ace2a5278217c4d738bd752c07dc37e -size 35894 diff --git a/third_party/ffmpeg/include/libavutil/hash.h b/third_party/ffmpeg/include/libavutil/hash.h deleted file mode 100644 index db71ca59f..000000000 --- a/third_party/ffmpeg/include/libavutil/hash.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b0896571267220736679eea28c454783795a02a0f1aef008ebe7c40489a75fdd -size 8457 diff --git a/third_party/ffmpeg/include/libavutil/hdr_dynamic_metadata.h b/third_party/ffmpeg/include/libavutil/hdr_dynamic_metadata.h deleted file mode 100644 index f920e30d6..000000000 --- a/third_party/ffmpeg/include/libavutil/hdr_dynamic_metadata.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:457274c2eda1fe91e83ae76b9d1ab8682e6144adfacbd2c86f5fb4a03ede421f -size 14385 diff --git a/third_party/ffmpeg/include/libavutil/hdr_dynamic_vivid_metadata.h b/third_party/ffmpeg/include/libavutil/hdr_dynamic_vivid_metadata.h deleted file mode 100644 index 64e00109f..000000000 --- a/third_party/ffmpeg/include/libavutil/hdr_dynamic_vivid_metadata.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9f50e471376bc9e8ca504a338cfe74ef634b8f2c242ccefed2e4da67197112de -size 10004 diff --git a/third_party/ffmpeg/include/libavutil/hmac.h b/third_party/ffmpeg/include/libavutil/hmac.h deleted file mode 100644 index 919dc1ec2..000000000 --- a/third_party/ffmpeg/include/libavutil/hmac.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d14d625a897d6bba0668acdf33dc597bb0050237c5c1a5f7e568fe36822782e7 -size 2865 diff --git a/third_party/ffmpeg/include/libavutil/hwcontext.h b/third_party/ffmpeg/include/libavutil/hwcontext.h deleted file mode 100644 index 5ccfbbe91..000000000 --- a/third_party/ffmpeg/include/libavutil/hwcontext.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e05c09b0a2f51c800aa57dc21277b5b4534e6783ab3df52dbf81c63e37fe8323 -size 24341 diff --git a/third_party/ffmpeg/include/libavutil/hwcontext_cuda.h b/third_party/ffmpeg/include/libavutil/hwcontext_cuda.h deleted file mode 100644 index c596a2781..000000000 --- a/third_party/ffmpeg/include/libavutil/hwcontext_cuda.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4878f46347271bc7a9ff26bb1573449a99cc81447684e1034a3edd4b0ff91d9a -size 1843 diff --git a/third_party/ffmpeg/include/libavutil/hwcontext_d3d11va.h b/third_party/ffmpeg/include/libavutil/hwcontext_d3d11va.h deleted file mode 100644 index efdaa8800..000000000 --- a/third_party/ffmpeg/include/libavutil/hwcontext_d3d11va.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d99c1b5c5dab94c23709d4ce6cf489c2a0b4f4bd57bb04e2196371277dca5af7 -size 6669 diff --git a/third_party/ffmpeg/include/libavutil/hwcontext_drm.h b/third_party/ffmpeg/include/libavutil/hwcontext_drm.h deleted file mode 100644 index fa6732af7..000000000 --- a/third_party/ffmpeg/include/libavutil/hwcontext_drm.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b598f37f40cf1342f923c0b97784a6f2830b543868eccee046375e096fbd5f24 -size 4673 diff --git a/third_party/ffmpeg/include/libavutil/hwcontext_dxva2.h b/third_party/ffmpeg/include/libavutil/hwcontext_dxva2.h deleted file mode 100644 index d871979e6..000000000 --- a/third_party/ffmpeg/include/libavutil/hwcontext_dxva2.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:73a0333b65e99675834dcb1b63a5e9339638ccc619f1a2fcba85cdd0e179ade0 -size 2411 diff --git a/third_party/ffmpeg/include/libavutil/hwcontext_mediacodec.h b/third_party/ffmpeg/include/libavutil/hwcontext_mediacodec.h deleted file mode 100644 index efee54fea..000000000 --- a/third_party/ffmpeg/include/libavutil/hwcontext_mediacodec.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8c602859ebca906ba6e43ea548ff28821cf2886b4500b2be1deaaf2d552496d4 -size 1988 diff --git a/third_party/ffmpeg/include/libavutil/hwcontext_opencl.h b/third_party/ffmpeg/include/libavutil/hwcontext_opencl.h deleted file mode 100644 index 812b29caa..000000000 --- a/third_party/ffmpeg/include/libavutil/hwcontext_opencl.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ad521fa2fd015cb1aba962468ca4ac176f5fc6b2c4b7be28f05e1c03d89e1b31 -size 3097 diff --git a/third_party/ffmpeg/include/libavutil/hwcontext_qsv.h b/third_party/ffmpeg/include/libavutil/hwcontext_qsv.h deleted file mode 100644 index a5053d51d..000000000 --- a/third_party/ffmpeg/include/libavutil/hwcontext_qsv.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4986f86340b8162ef2724f0e77a380b5133aaa0612749cfb836c38b4c166d20d -size 1960 diff --git a/third_party/ffmpeg/include/libavutil/hwcontext_vaapi.h b/third_party/ffmpeg/include/libavutil/hwcontext_vaapi.h deleted file mode 100644 index b205bc5fc..000000000 --- a/third_party/ffmpeg/include/libavutil/hwcontext_vaapi.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6f6c6a5250dd0f901cdc7de8b9b3db26102719b7e056cd17500009096bfd9b39 -size 3787 diff --git a/third_party/ffmpeg/include/libavutil/hwcontext_vdpau.h b/third_party/ffmpeg/include/libavutil/hwcontext_vdpau.h deleted file mode 100644 index 8cd234bf7..000000000 --- a/third_party/ffmpeg/include/libavutil/hwcontext_vdpau.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6c96373d9e5deb2c500004f3f55ee1d2cea0f76cdfaeabaf5a3ad3e4938e8252 -size 1360 diff --git a/third_party/ffmpeg/include/libavutil/hwcontext_videotoolbox.h b/third_party/ffmpeg/include/libavutil/hwcontext_videotoolbox.h deleted file mode 100644 index db959d5fc..000000000 --- a/third_party/ffmpeg/include/libavutil/hwcontext_videotoolbox.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2f74c83778a0df9738abcae823412436cbbeaff8e9083281fb6f38116cb401c2 -size 3431 diff --git a/third_party/ffmpeg/include/libavutil/hwcontext_vulkan.h b/third_party/ffmpeg/include/libavutil/hwcontext_vulkan.h deleted file mode 100644 index fce119213..000000000 --- a/third_party/ffmpeg/include/libavutil/hwcontext_vulkan.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:74067b6224e8a9d60969f192fdc17f97a12ff073274f5047c380543647026760 -size 11412 diff --git a/third_party/ffmpeg/include/libavutil/imgutils.h b/third_party/ffmpeg/include/libavutil/imgutils.h deleted file mode 100644 index 9636ebc81..000000000 --- a/third_party/ffmpeg/include/libavutil/imgutils.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b4cceea6c7b3017dea61a9fd514d7460ab13592907addb69a2e92870872207f1 -size 14819 diff --git a/third_party/ffmpeg/include/libavutil/intfloat.h b/third_party/ffmpeg/include/libavutil/intfloat.h deleted file mode 100644 index 278a2a631..000000000 --- a/third_party/ffmpeg/include/libavutil/intfloat.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3a29e4eebc8c269cfd867b96de91d8231773d392c12a8820e46eaba96d2b4ca1 -size 1726 diff --git a/third_party/ffmpeg/include/libavutil/intreadwrite.h b/third_party/ffmpeg/include/libavutil/intreadwrite.h deleted file mode 100644 index e6ae02efb..000000000 --- a/third_party/ffmpeg/include/libavutil/intreadwrite.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f815f4edb0fb58c0b2c7b4f4763a27ee223672fa43ef4c5c0b99fb4575b66bf7 -size 18735 diff --git a/third_party/ffmpeg/include/libavutil/lfg.h b/third_party/ffmpeg/include/libavutil/lfg.h deleted file mode 100644 index 4e9488eaa..000000000 --- a/third_party/ffmpeg/include/libavutil/lfg.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c3bc1533172fe74870df8e37f168f28b0ff2e21bdb6a519b6555ec72710c910f -size 2541 diff --git a/third_party/ffmpeg/include/libavutil/log.h b/third_party/ffmpeg/include/libavutil/log.h deleted file mode 100644 index a3dd2c72a..000000000 --- a/third_party/ffmpeg/include/libavutil/log.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8555ad5d41f5f2fa1c7dd0dc65f2145b0517c0a414654575aade8b3968e4c408 -size 12766 diff --git a/third_party/ffmpeg/include/libavutil/lzo.h b/third_party/ffmpeg/include/libavutil/lzo.h deleted file mode 100644 index 2e7db3c19..000000000 --- a/third_party/ffmpeg/include/libavutil/lzo.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:61e89928dee9d83030adececac06aa6c1ae2aada06c5682fde52c52015c53556 -size 2048 diff --git a/third_party/ffmpeg/include/libavutil/macros.h b/third_party/ffmpeg/include/libavutil/macros.h deleted file mode 100644 index 57932eaeb..000000000 --- a/third_party/ffmpeg/include/libavutil/macros.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b63b3a268b096f0eed1e91b821714cff334e5dc5bb34365148704393ae15321e -size 2304 diff --git a/third_party/ffmpeg/include/libavutil/mastering_display_metadata.h b/third_party/ffmpeg/include/libavutil/mastering_display_metadata.h deleted file mode 100644 index 90983df17..000000000 --- a/third_party/ffmpeg/include/libavutil/mastering_display_metadata.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9d5743a42306ac0158e26248a1281ae8be9ebfeb02e67f14e0e6ae770a543a65 -size 3944 diff --git a/third_party/ffmpeg/include/libavutil/mathematics.h b/third_party/ffmpeg/include/libavutil/mathematics.h deleted file mode 100644 index e707131c8..000000000 --- a/third_party/ffmpeg/include/libavutil/mathematics.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:64fac2eb3a42fd3788f5585ac8e65c7d5cd82711730d1f030042ba0a62fe1a62 -size 9563 diff --git a/third_party/ffmpeg/include/libavutil/md5.h b/third_party/ffmpeg/include/libavutil/md5.h deleted file mode 100644 index 33a63e05c..000000000 --- a/third_party/ffmpeg/include/libavutil/md5.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5b42de1758d289f78b4d20c47686f443e4ea8a5a6411c0deb357f709d2ef34d7 -size 2092 diff --git a/third_party/ffmpeg/include/libavutil/mem.h b/third_party/ffmpeg/include/libavutil/mem.h deleted file mode 100644 index 5a81cd674..000000000 --- a/third_party/ffmpeg/include/libavutil/mem.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bf9a1d8c218c38b333efb16be076c5778d31d66a0691bdd6175f171c08283d7c -size 20457 diff --git a/third_party/ffmpeg/include/libavutil/motion_vector.h b/third_party/ffmpeg/include/libavutil/motion_vector.h deleted file mode 100644 index 6c61741e5..000000000 --- a/third_party/ffmpeg/include/libavutil/motion_vector.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:dc0b0a15a638c8b91df95a418c5951ee5e787d518f22b6e3d70094922536e8bb -size 1770 diff --git a/third_party/ffmpeg/include/libavutil/murmur3.h b/third_party/ffmpeg/include/libavutil/murmur3.h deleted file mode 100644 index 353b5a763..000000000 --- a/third_party/ffmpeg/include/libavutil/murmur3.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:649258a51c4737fa19a025a489e2ac9e9b06a96eafa802f2765178c684382887 -size 3507 diff --git a/third_party/ffmpeg/include/libavutil/opt.h b/third_party/ffmpeg/include/libavutil/opt.h deleted file mode 100644 index ef2b874b2..000000000 --- a/third_party/ffmpeg/include/libavutil/opt.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4a1679c21453e4337cc7d377efaec6610340a59204c0b58fc102ab3a9da51a39 -size 37201 diff --git a/third_party/ffmpeg/include/libavutil/parseutils.h b/third_party/ffmpeg/include/libavutil/parseutils.h deleted file mode 100644 index de2f938c6..000000000 --- a/third_party/ffmpeg/include/libavutil/parseutils.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e8efed69396851f429a8258d50e9c4f0431f921687a7c31bf6db13d14f7482c3 -size 7888 diff --git a/third_party/ffmpeg/include/libavutil/pixdesc.h b/third_party/ffmpeg/include/libavutil/pixdesc.h deleted file mode 100644 index ade8e6906..000000000 --- a/third_party/ffmpeg/include/libavutil/pixdesc.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:253359c22bc051754a9ded9497a850e5296cbfd058a9b73957c23b53d7314d96 -size 16031 diff --git a/third_party/ffmpeg/include/libavutil/pixelutils.h b/third_party/ffmpeg/include/libavutil/pixelutils.h deleted file mode 100644 index 2734d292c..000000000 --- a/third_party/ffmpeg/include/libavutil/pixelutils.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:339cd6ffb6460d06401801c5dfb91ca66b9bdc028e1acc9ff4a0f447cfd3785c -size 2051 diff --git a/third_party/ffmpeg/include/libavutil/pixfmt.h b/third_party/ffmpeg/include/libavutil/pixfmt.h deleted file mode 100644 index 459bb057c..000000000 --- a/third_party/ffmpeg/include/libavutil/pixfmt.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:69104b943b1c44a54e46635be1e5da0ff6be7cd3f1af6ebc9924d4d965d0e420 -size 41556 diff --git a/third_party/ffmpeg/include/libavutil/random_seed.h b/third_party/ffmpeg/include/libavutil/random_seed.h deleted file mode 100644 index a4e8933e5..000000000 --- a/third_party/ffmpeg/include/libavutil/random_seed.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4490fd79919aadb18f765caac0c210d22cafa4d63cddcf9275e6f5bf66e2fdea -size 1889 diff --git a/third_party/ffmpeg/include/libavutil/rational.h b/third_party/ffmpeg/include/libavutil/rational.h deleted file mode 100644 index 32a39febc..000000000 --- a/third_party/ffmpeg/include/libavutil/rational.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:700422cc782acde6898522ffe1b74658b12ca22e2cfbb8f44986266216de7f21 -size 6100 diff --git a/third_party/ffmpeg/include/libavutil/rc4.h b/third_party/ffmpeg/include/libavutil/rc4.h deleted file mode 100644 index a79ce5a1b..000000000 --- a/third_party/ffmpeg/include/libavutil/rc4.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4ea0fbee43677721ad0d846f703a785aaf9881794d1cca0bcb210241b260fc26 -size 2003 diff --git a/third_party/ffmpeg/include/libavutil/replaygain.h b/third_party/ffmpeg/include/libavutil/replaygain.h deleted file mode 100644 index 760b289ee..000000000 --- a/third_party/ffmpeg/include/libavutil/replaygain.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4ec82edbdc4e5493fba3cae6a27566f0f15d1399ccf16e25073ffd50ba8187ea -size 1607 diff --git a/third_party/ffmpeg/include/libavutil/ripemd.h b/third_party/ffmpeg/include/libavutil/ripemd.h deleted file mode 100644 index 34cd2714a..000000000 --- a/third_party/ffmpeg/include/libavutil/ripemd.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:df9ef8c29ee31e5bd8ea299b03d51bd25fe937583793a994db53d1df2b316620 -size 2158 diff --git a/third_party/ffmpeg/include/libavutil/samplefmt.h b/third_party/ffmpeg/include/libavutil/samplefmt.h deleted file mode 100644 index c37f4ace9..000000000 --- a/third_party/ffmpeg/include/libavutil/samplefmt.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e844d8b4a691256238d10de762a6a536ffe52995705166aee0d7f01ce79778a9 -size 10301 diff --git a/third_party/ffmpeg/include/libavutil/sha.h b/third_party/ffmpeg/include/libavutil/sha.h deleted file mode 100644 index 58b5ea658..000000000 --- a/third_party/ffmpeg/include/libavutil/sha.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:91280db6995b1b99b9e5aad0aa211a3177dc4d2841da2fea097f54964b7891fd -size 2368 diff --git a/third_party/ffmpeg/include/libavutil/sha512.h b/third_party/ffmpeg/include/libavutil/sha512.h deleted file mode 100644 index 4e62f7fb6..000000000 --- a/third_party/ffmpeg/include/libavutil/sha512.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:da265152798b221706d7fe95293a0e8cd18fa2b5087bf32504a8120f10e7658f -size 2413 diff --git a/third_party/ffmpeg/include/libavutil/spherical.h b/third_party/ffmpeg/include/libavutil/spherical.h deleted file mode 100644 index 2e14e4f29..000000000 --- a/third_party/ffmpeg/include/libavutil/spherical.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:18b8e559f69a9c93b251c9d3956b2aba7f47ba46ef5a7fa01f8ae858967b73da -size 7997 diff --git a/third_party/ffmpeg/include/libavutil/stereo3d.h b/third_party/ffmpeg/include/libavutil/stereo3d.h deleted file mode 100644 index 952eb6cf7..000000000 --- a/third_party/ffmpeg/include/libavutil/stereo3d.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5ece3ffb6baafd288f9f2d8747cae92b1b3985e6693fa2de6bc200173a1981b6 -size 5224 diff --git a/third_party/ffmpeg/include/libavutil/tea.h b/third_party/ffmpeg/include/libavutil/tea.h deleted file mode 100644 index 5b3094167..000000000 --- a/third_party/ffmpeg/include/libavutil/tea.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3c1e93c566630bb4eeedad3ef3c8719bd6050081ac1c764b1fde81aba4969076 -size 2035 diff --git a/third_party/ffmpeg/include/libavutil/threadmessage.h b/third_party/ffmpeg/include/libavutil/threadmessage.h deleted file mode 100644 index 70c8112ba..000000000 --- a/third_party/ffmpeg/include/libavutil/threadmessage.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9bb242d7adc48662b947726843108aff7c34547d7a4a0d0e6f58f54a00fc4c9f -size 3910 diff --git a/third_party/ffmpeg/include/libavutil/time.h b/third_party/ffmpeg/include/libavutil/time.h deleted file mode 100644 index ef77ac592..000000000 --- a/third_party/ffmpeg/include/libavutil/time.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:40e11fa242e0585996753affb054443e78be25919b7c3063042d0aaff1656760 -size 1800 diff --git a/third_party/ffmpeg/include/libavutil/timecode.h b/third_party/ffmpeg/include/libavutil/timecode.h deleted file mode 100644 index 5b54b01bc..000000000 --- a/third_party/ffmpeg/include/libavutil/timecode.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:afd0a634b1abcb282c694ae6c43f0412fe2ccb37fb3f08433af33779ea9e572e -size 7843 diff --git a/third_party/ffmpeg/include/libavutil/timestamp.h b/third_party/ffmpeg/include/libavutil/timestamp.h deleted file mode 100644 index 828043fe8..000000000 --- a/third_party/ffmpeg/include/libavutil/timestamp.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7cfbb6640449104c473cc1bc6f78244ce97636fca294da2867cedaab868c1b8c -size 2617 diff --git a/third_party/ffmpeg/include/libavutil/tree.h b/third_party/ffmpeg/include/libavutil/tree.h deleted file mode 100644 index ca3f72fff..000000000 --- a/third_party/ffmpeg/include/libavutil/tree.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2f8e906917612a05c138036dea7ed9f8faee5899413a523fdad4eb51711bc1e5 -size 5408 diff --git a/third_party/ffmpeg/include/libavutil/twofish.h b/third_party/ffmpeg/include/libavutil/twofish.h deleted file mode 100644 index 96cf965ab..000000000 --- a/third_party/ffmpeg/include/libavutil/twofish.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b71714336821e1c606b65620ba4b1ea47e431666be41f3174facbc51047fd814 -size 2245 diff --git a/third_party/ffmpeg/include/libavutil/tx.h b/third_party/ffmpeg/include/libavutil/tx.h deleted file mode 100644 index 223ffc495..000000000 --- a/third_party/ffmpeg/include/libavutil/tx.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:34f83ae31d074847e46579f25e3367210fbbfe1832b5315883cf4a28980ef101 -size 7140 diff --git a/third_party/ffmpeg/include/libavutil/uuid.h b/third_party/ffmpeg/include/libavutil/uuid.h deleted file mode 100644 index 3bc380c31..000000000 --- a/third_party/ffmpeg/include/libavutil/uuid.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e669ce76a6b987e189b4d7ff62d0fd9ad6e334fa4967076cc6d912976574b646 -size 4895 diff --git a/third_party/ffmpeg/include/libavutil/version.h b/third_party/ffmpeg/include/libavutil/version.h deleted file mode 100644 index 1f0fa95e0..000000000 --- a/third_party/ffmpeg/include/libavutil/version.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fed7c6f02a873de2c7e2f7e6d24e48e747b3f5204e36d50e19b5e82358b76470 -size 4832 diff --git a/third_party/ffmpeg/include/libavutil/video_enc_params.h b/third_party/ffmpeg/include/libavutil/video_enc_params.h deleted file mode 100644 index 7ce1b5702..000000000 --- a/third_party/ffmpeg/include/libavutil/video_enc_params.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f287486c4f828f82e579f93ea98fccb98749129544f660decfa56da6f818fd57 -size 5991 diff --git a/third_party/ffmpeg/include/libavutil/video_hint.h b/third_party/ffmpeg/include/libavutil/video_hint.h deleted file mode 100644 index 46ad47b11..000000000 --- a/third_party/ffmpeg/include/libavutil/video_hint.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7c8b7768ffd03af5034a13274d1ca0cc6206b9f2c0c478400aba8a6c79ad5359 -size 3586 diff --git a/third_party/ffmpeg/include/libavutil/xtea.h b/third_party/ffmpeg/include/libavutil/xtea.h deleted file mode 100644 index 99bdc08b3..000000000 --- a/third_party/ffmpeg/include/libavutil/xtea.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2eb91f780cc4ad86095e4ebbce453475d40f4e9b8737d52bdf20a068dfafcdf0 -size 2834 diff --git a/third_party/ffmpeg/include/x264.h b/third_party/ffmpeg/include/x264.h deleted file mode 100644 index 17c574177..000000000 --- a/third_party/ffmpeg/include/x264.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cec084bdb3cd2330800d0726bdbd99d91112df3fdb6d79f94e15ba67808b715c -size 49056 diff --git a/third_party/ffmpeg/include/x264_config.h b/third_party/ffmpeg/include/x264_config.h deleted file mode 100644 index 0b4ef0a07..000000000 --- a/third_party/ffmpeg/include/x264_config.h +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:81acf190604a6c03d8e44dd3a9751ea12dc4823cdff1c9f0b376faaeafe79ed2 -size 172 diff --git a/third_party/ffmpeg/larch64/lib/libavcodec.a b/third_party/ffmpeg/larch64/lib/libavcodec.a deleted file mode 100644 index 7e094e181..000000000 --- a/third_party/ffmpeg/larch64/lib/libavcodec.a +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9cbbf4f95193267b3799652b8c86975642a22ad6c91b1df40e24ec602d315368 -size 2368942 diff --git a/third_party/ffmpeg/larch64/lib/libavformat.a b/third_party/ffmpeg/larch64/lib/libavformat.a deleted file mode 100644 index 0cfd4d2df..000000000 --- a/third_party/ffmpeg/larch64/lib/libavformat.a +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f12e0f2a24ff87f328b4045b91d6453133588c2972a96980a05fc6339ff27eec -size 946966 diff --git a/third_party/ffmpeg/larch64/lib/libavutil.a b/third_party/ffmpeg/larch64/lib/libavutil.a deleted file mode 100644 index 027081154..000000000 --- a/third_party/ffmpeg/larch64/lib/libavutil.a +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c0944c03c21379aa6c264e0e1b59538789e0a15c6c77e5ac0ef8d571cb6f5b01 -size 891284 diff --git a/third_party/ffmpeg/larch64/lib/libx264.a b/third_party/ffmpeg/larch64/lib/libx264.a deleted file mode 100644 index 5a95720c7..000000000 --- a/third_party/ffmpeg/larch64/lib/libx264.a +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ceeac8654bb25feea79f64e91197e20a26fee4c2c36af6eae9f13943d8acf2c1 -size 2222366 diff --git a/third_party/ffmpeg/x86_64/lib/libavcodec.a b/third_party/ffmpeg/x86_64/lib/libavcodec.a deleted file mode 100644 index 00a4f4b48..000000000 --- a/third_party/ffmpeg/x86_64/lib/libavcodec.a +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:32ac488b287137c0537da1a81b3fa81e074bb79d9bf5af58185779f3dfe70070 -size 3267672 diff --git a/third_party/ffmpeg/x86_64/lib/libavformat.a b/third_party/ffmpeg/x86_64/lib/libavformat.a deleted file mode 100644 index 497fc2a34..000000000 --- a/third_party/ffmpeg/x86_64/lib/libavformat.a +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:53ba309c4adffc993671f4700e316fd5886f28aead91f051261ae1ffe8dbb03e -size 919414 diff --git a/third_party/ffmpeg/x86_64/lib/libavutil.a b/third_party/ffmpeg/x86_64/lib/libavutil.a deleted file mode 100644 index 0fc452317..000000000 --- a/third_party/ffmpeg/x86_64/lib/libavutil.a +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e4fed041aafbd9672bfff2b8988256ebd3fd77892c4ecdef705c7b81173a3c56 -size 1022522 diff --git a/third_party/ffmpeg/x86_64/lib/libx264.a b/third_party/ffmpeg/x86_64/lib/libx264.a deleted file mode 100644 index 84b9d4742..000000000 --- a/third_party/ffmpeg/x86_64/lib/libx264.a +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e17625b8355736b3d5a8bf8ef49d10b8a531783b2c74d41246be7a35fdce3dff -size 3065330 diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index eb6f15817..89be3cceb 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -16,7 +16,7 @@ qt_libs = ['qt_util'] + base_libs cabana_env = qt_env.Clone() -cabana_libs = [widgets, cereal, messaging, visionipc, replay_lib, 'avformat', 'avcodec', 'avutil', 'x264', 'bz2', 'zstd', 'curl', 'yuv', 'usb-1.0'] + qt_libs +cabana_libs = [widgets, cereal, messaging, visionipc, replay_lib, 'avutil', 'avcodec', 'avformat', 'bz2', 'zstd', 'curl', 'yuv', 'usb-1.0'] + qt_libs opendbc_path = '-DOPENDBC_FILE_PATH=\'"%s"\'' % (cabana_env.Dir("../../opendbc/dbc").abspath) cabana_env['CXXFLAGS'] += [opendbc_path] diff --git a/tools/install_ubuntu_dependencies.sh b/tools/install_ubuntu_dependencies.sh index 6abc10c8e..f33569704 100755 --- a/tools/install_ubuntu_dependencies.sh +++ b/tools/install_ubuntu_dependencies.sh @@ -32,6 +32,12 @@ function install_ubuntu_common_requirements() { libcurl4-openssl-dev \ git \ git-lfs \ + ffmpeg \ + libavformat-dev \ + libavcodec-dev \ + libavdevice-dev \ + libavutil-dev \ + libavfilter-dev \ libbz2-dev \ libeigen3-dev \ libffi-dev \ diff --git a/tools/mac_setup.sh b/tools/mac_setup.sh index b5ee988cd..19ef77e01 100755 --- a/tools/mac_setup.sh +++ b/tools/mac_setup.sh @@ -38,6 +38,7 @@ brew "zlib" brew "capnp" brew "coreutils" brew "eigen" +brew "ffmpeg" brew "glfw" brew "libarchive" brew "libusb" diff --git a/tools/replay/SConscript b/tools/replay/SConscript index 013cf7658..136c4119f 100644 --- a/tools/replay/SConscript +++ b/tools/replay/SConscript @@ -17,7 +17,7 @@ if arch != "Darwin": replay_lib_src.append("qcom_decoder.cc") replay_lib = replay_env.Library("replay", replay_lib_src, LIBS=base_libs, FRAMEWORKS=base_frameworks) Export('replay_lib') -replay_libs = [replay_lib, 'avformat', 'avcodec', 'avutil', 'bz2', 'zstd', 'curl', 'yuv', 'ncurses', 'x264'] + base_libs +replay_libs = [replay_lib, 'avutil', 'avcodec', 'avformat', 'bz2', 'zstd', 'curl', 'yuv', 'ncurses'] + base_libs replay_env.Program("replay", ["main.cc"], LIBS=replay_libs, FRAMEWORKS=base_frameworks) if GetOption('extras'): From b622e3e0a705fb7f13fff30195f416dc0b7ff758 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 17 Sep 2025 16:33:48 -0700 Subject: [PATCH 021/910] raylib: generic click callback (#36166) * not to be used outside * same * rm * fix that * another fix * ehh probably better to still have * optional --- selfdrive/ui/onroad/augmented_road_view.py | 5 +++- system/ui/widgets/__init__.py | 33 +++++++++++++--------- system/ui/widgets/button.py | 11 ++------ 3 files changed, 27 insertions(+), 22 deletions(-) diff --git a/selfdrive/ui/onroad/augmented_road_view.py b/selfdrive/ui/onroad/augmented_road_view.py index dbc191922..f012e8b06 100644 --- a/selfdrive/ui/onroad/augmented_road_view.py +++ b/selfdrive/ui/onroad/augmented_road_view.py @@ -102,10 +102,13 @@ class AugmentedRoadView(CameraView): # Handle click events if no HUD interaction occurred if not self._hud_renderer.handle_mouse_event(): - if self._click_callback and rl.is_mouse_button_pressed(rl.MouseButton.MOUSE_BUTTON_LEFT): + if self._click_callback is not None and rl.is_mouse_button_pressed(rl.MouseButton.MOUSE_BUTTON_LEFT): if rl.check_collision_point_rec(rl.get_mouse_position(), self._content_rect): self._click_callback() + def _handle_mouse_release(self, _): + pass + def _draw_border(self, rect: rl.Rectangle): border_color = BORDER_COLORS.get(ui_state.status, BORDER_COLORS[UIStatus.DISENGAGED]) rl.draw_rectangle_lines_ex(rect, UI_BORDER_SIZE, border_color) diff --git a/system/ui/widgets/__init__.py b/system/ui/widgets/__init__.py index cca2cec7a..6372b1813 100644 --- a/system/ui/widgets/__init__.py +++ b/system/ui/widgets/__init__.py @@ -15,12 +15,13 @@ class Widget(abc.ABC): def __init__(self): self._rect: rl.Rectangle = rl.Rectangle(0, 0, 0, 0) self._parent_rect: rl.Rectangle = rl.Rectangle(0, 0, 0, 0) - self._is_pressed = [False] * MAX_TOUCH_SLOTS + self.__is_pressed = [False] * MAX_TOUCH_SLOTS # if current mouse/touch down started within the widget's rectangle - self._tracking_is_pressed = [False] * MAX_TOUCH_SLOTS + self.__tracking_is_pressed = [False] * MAX_TOUCH_SLOTS self._enabled: bool | Callable[[], bool] = True self._is_visible: bool | Callable[[], bool] = True self._touch_valid_callback: Callable[[], bool] | None = None + self._click_callback: Callable[[], None] | None = None self._multi_touch = False @property @@ -40,7 +41,7 @@ class Widget(abc.ABC): @property def is_pressed(self) -> bool: - return any(self._is_pressed) + return any(self.__is_pressed) @property def enabled(self) -> bool: @@ -56,6 +57,10 @@ class Widget(abc.ABC): def set_visible(self, visible: bool | Callable[[], bool]) -> None: self._is_visible = visible + def set_click_callback(self, click_callback: Callable[[], None] | None) -> None: + """Set a callback to be called when the widget is clicked.""" + self._click_callback = click_callback + def set_touch_valid_callback(self, touch_callback: Callable[[], bool]) -> None: """Set a callback to determine if the widget can be clicked.""" self._touch_valid_callback = touch_callback @@ -91,28 +96,28 @@ class Widget(abc.ABC): # Allows touch to leave the rect and come back in focus if mouse did not release if mouse_event.left_pressed and self._touch_valid(): if rl.check_collision_point_rec(mouse_event.pos, self._rect): - self._is_pressed[mouse_event.slot] = True - self._tracking_is_pressed[mouse_event.slot] = True + self.__is_pressed[mouse_event.slot] = True + self.__tracking_is_pressed[mouse_event.slot] = True # Callback such as scroll panel signifies user is scrolling elif not self._touch_valid(): - self._is_pressed[mouse_event.slot] = False - self._tracking_is_pressed[mouse_event.slot] = False + self.__is_pressed[mouse_event.slot] = False + self.__tracking_is_pressed[mouse_event.slot] = False elif mouse_event.left_released: - if self._is_pressed[mouse_event.slot] and rl.check_collision_point_rec(mouse_event.pos, self._rect): + if self.__is_pressed[mouse_event.slot] and rl.check_collision_point_rec(mouse_event.pos, self._rect): self._handle_mouse_release(mouse_event.pos) - self._is_pressed[mouse_event.slot] = False - self._tracking_is_pressed[mouse_event.slot] = False + self.__is_pressed[mouse_event.slot] = False + self.__tracking_is_pressed[mouse_event.slot] = False # Mouse/touch is still within our rect elif rl.check_collision_point_rec(mouse_event.pos, self._rect): - if self._tracking_is_pressed[mouse_event.slot]: - self._is_pressed[mouse_event.slot] = True + if self.__tracking_is_pressed[mouse_event.slot]: + self.__is_pressed[mouse_event.slot] = True # Mouse/touch left our rect but may come back into focus later elif not rl.check_collision_point_rec(mouse_event.pos, self._rect): - self._is_pressed[mouse_event.slot] = False + self.__is_pressed[mouse_event.slot] = False return ret @@ -128,6 +133,8 @@ class Widget(abc.ABC): def _handle_mouse_release(self, mouse_pos: MousePos) -> bool: """Optionally handle mouse release events.""" + if self._click_callback: + self._click_callback() return False def show_event(self): diff --git a/system/ui/widgets/button.py b/system/ui/widgets/button.py index a5dab1978..eb38f2059 100644 --- a/system/ui/widgets/button.py +++ b/system/ui/widgets/button.py @@ -165,7 +165,7 @@ def gui_button( class Button(Widget): def __init__(self, text: str, - click_callback: Callable[[], None] = None, + click_callback: Callable[[], None] | None = None, font_size: int = DEFAULT_BUTTON_FONT_SIZE, font_weight: FontWeight = FontWeight.MEDIUM, button_style: ButtonStyle = ButtonStyle.NORMAL, @@ -190,10 +190,6 @@ class Button(Widget): def set_text(self, text): self._label.set_text(text) - def _handle_mouse_release(self, mouse_pos: MousePos): - if self._click_callback and self.enabled: - self._click_callback() - def _update_state(self): if self.enabled: self._label.set_text_color(BUTTON_TEXT_COLOR[self._button_style]) @@ -215,7 +211,7 @@ class ButtonRadio(Button): def __init__(self, text: str, icon, - click_callback: Callable[[], None] = None, + click_callback: Callable[[], None] | None = None, font_size: int = DEFAULT_BUTTON_FONT_SIZE, text_alignment: TextAlignment = TextAlignment.LEFT, border_radius: int = 10, @@ -230,9 +226,8 @@ class ButtonRadio(Button): self.selected = False def _handle_mouse_release(self, mouse_pos: MousePos): + super()._handle_mouse_release(mouse_pos) self.selected = not self.selected - if self._click_callback: - self._click_callback() def _update_state(self): if self.selected: From 30c388aea8961113555211201c6298099d53e4df Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Thu, 18 Sep 2025 00:07:06 -0700 Subject: [PATCH 022/910] fix `is_dirty` when switching branch with `updated` (#36162) * clean * fix --- system/updated/updated.py | 2 ++ system/version.py | 7 ++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/system/updated/updated.py b/system/updated/updated.py index a80a663ec..f9fad5f6f 100755 --- a/system/updated/updated.py +++ b/system/updated/updated.py @@ -113,6 +113,7 @@ def setup_git_options(cwd: str) -> None: ("protocol.version", "2"), ("gc.auto", "0"), ("gc.autoDetach", "false"), + ("remote.origin.fetch", "+refs/heads/*:refs/remotes/origin/*"), ] for option, value in git_cfg: run(["git", "config", option, value], cwd) @@ -389,6 +390,7 @@ class Updater: cloudlog.info("git reset in progress") cmds = [ ["git", "checkout", "--force", "--no-recurse-submodules", "-B", branch, "FETCH_HEAD"], + ["git", "branch", "--set-upstream-to", f"origin/{branch}"], ["git", "reset", "--hard"], ["git", "clean", "-xdff"], ["git", "submodule", "sync"], diff --git a/system/version.py b/system/version.py index 5e4fcf5c3..e32c3d603 100755 --- a/system/version.py +++ b/system/version.py @@ -37,9 +37,7 @@ def is_prebuilt(path: str = BASEDIR) -> bool: @cache def is_dirty(cwd: str = BASEDIR) -> bool: - origin = get_origin() - branch = get_branch() - if not origin or not branch: + if not get_origin() or not get_short_branch(): return True dirty = False @@ -52,6 +50,9 @@ def is_dirty(cwd: str = BASEDIR) -> bool: except subprocess.CalledProcessError: pass + branch = get_branch() + if not branch: + return True dirty = (subprocess.call(["git", "diff-index", "--quiet", branch, "--"], cwd=cwd)) != 0 except subprocess.CalledProcessError: cloudlog.exception("git subprocess failed while checking dirty") From 3751d9cf515d1e00656b0c575d49ff9020e22f3c Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 18 Sep 2025 08:22:15 -0700 Subject: [PATCH 023/910] Remove libsystemd-dev from Ubuntu dependencies (#36167) Removed 'libsystemd-dev' from the list of dependencies. --- tools/install_ubuntu_dependencies.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/install_ubuntu_dependencies.sh b/tools/install_ubuntu_dependencies.sh index f33569704..6ab9b30ab 100755 --- a/tools/install_ubuntu_dependencies.sh +++ b/tools/install_ubuntu_dependencies.sh @@ -53,7 +53,6 @@ function install_ubuntu_common_requirements() { libzmq3-dev \ libzstd-dev \ libsqlite3-dev \ - libsystemd-dev \ locales \ opencl-headers \ ocl-icd-libopencl1 \ From 852598fa0a8feef138ad96acadaf6715cf9f15c2 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 18 Sep 2025 08:34:28 -0700 Subject: [PATCH 024/910] fix mac build (#36168) --- SConstruct | 1 + 1 file changed, 1 insertion(+) diff --git a/SConstruct b/SConstruct index 5b13bd635..d8a390f5d 100644 --- a/SConstruct +++ b/SConstruct @@ -116,6 +116,7 @@ else: f"#third_party/acados/{arch}/lib", f"{brew_prefix}/lib", f"{brew_prefix}/opt/openssl@3.0/lib", + f"{brew_prefix}/opt/llvm/lib/c++", "/System/Library/Frameworks/OpenGL.framework/Libraries", ] From c6a2c9912307144500d2bf6b5c58a6002573a20b Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 18 Sep 2025 09:17:26 -0700 Subject: [PATCH 025/910] prep for vendoring (#36169) * prep for vendoring * less stuff * comment --- tools/install_ubuntu_dependencies.sh | 24 ++++++++++++------------ tools/mac_setup.sh | 2 -- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/tools/install_ubuntu_dependencies.sh b/tools/install_ubuntu_dependencies.sh index 6ab9b30ab..facc9eb9f 100755 --- a/tools/install_ubuntu_dependencies.sh +++ b/tools/install_ubuntu_dependencies.sh @@ -20,40 +20,43 @@ fi # Install common packages function install_ubuntu_common_requirements() { $SUDO apt-get update + + # normal stuff, mostly for the bare docker image $SUDO apt-get install -y --no-install-recommends \ ca-certificates \ clang \ build-essential \ - gcc-arm-none-eabi \ - liblzma-dev \ - capnproto \ - libcapnp-dev \ curl \ + libssl-dev \ libcurl4-openssl-dev \ + locales \ git \ git-lfs \ + xvfb + + # TODO: vendor the rest of these in third_party/ + $SUDO apt-get install -y --no-install-recommends \ + gcc-arm-none-eabi \ + capnproto \ + libcapnp-dev \ ffmpeg \ libavformat-dev \ libavcodec-dev \ libavdevice-dev \ libavutil-dev \ libavfilter-dev \ - libbz2-dev \ libeigen3-dev \ libffi-dev \ - libglew-dev \ libgles2-mesa-dev \ libglfw3-dev \ libglib2.0-0 \ libjpeg-dev \ libqt5charts5-dev \ libncurses5-dev \ - libssl-dev \ libusb-1.0-0-dev \ libzmq3-dev \ libzstd-dev \ libsqlite3-dev \ - locales \ opencl-headers \ ocl-icd-libopencl1 \ ocl-icd-opencl-dev \ @@ -62,8 +65,7 @@ function install_ubuntu_common_requirements() { libqt5svg5-dev \ libqt5serialbus5-dev \ libqt5x11extras5-dev \ - libqt5opengl5-dev \ - xvfb + libqt5opengl5-dev } # Install Ubuntu 24.04 LTS packages @@ -73,8 +75,6 @@ function install_ubuntu_lts_latest_requirements() { $SUDO apt-get install -y --no-install-recommends \ g++-12 \ qtbase5-dev \ - qtchooser \ - qt5-qmake \ qtbase5-dev-tools \ python3-dev \ python3-venv diff --git a/tools/mac_setup.sh b/tools/mac_setup.sh index 19ef77e01..430fc9522 100755 --- a/tools/mac_setup.sh +++ b/tools/mac_setup.sh @@ -34,13 +34,11 @@ fi brew bundle --file=- <<-EOS brew "git-lfs" -brew "zlib" brew "capnp" brew "coreutils" brew "eigen" brew "ffmpeg" brew "glfw" -brew "libarchive" brew "libusb" brew "libtool" brew "llvm" From b637ad49d992d367aaf34c7138e5ad11a5d41968 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 18 Sep 2025 09:25:41 -0700 Subject: [PATCH 026/910] vendor all fonts (#36170) add noto color --- selfdrive/assets/fonts/NotoColorEmoji-Regular.ttf | 3 +++ tools/mac_setup.sh | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 selfdrive/assets/fonts/NotoColorEmoji-Regular.ttf diff --git a/selfdrive/assets/fonts/NotoColorEmoji-Regular.ttf b/selfdrive/assets/fonts/NotoColorEmoji-Regular.ttf new file mode 100644 index 000000000..2579d30f6 --- /dev/null +++ b/selfdrive/assets/fonts/NotoColorEmoji-Regular.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:69f216a4ec672bb910d652678301ffe3094c44e5d03276e794ef793d936a1f1d +size 25096376 diff --git a/tools/mac_setup.sh b/tools/mac_setup.sh index 430fc9522..0ae0b3535 100755 --- a/tools/mac_setup.sh +++ b/tools/mac_setup.sh @@ -48,7 +48,6 @@ brew "zeromq" cask "gcc-arm-embedded" brew "portaudio" brew "gcc@13" -cask "font-noto-color-emoji" EOS echo "[ ] finished brew install t=$SECONDS" From c7a9ea2bf4d43e6c565d22cef22ffdf0ce8cedc5 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 18 Sep 2025 10:59:03 -0700 Subject: [PATCH 027/910] add back libbz2-dev (#36172) * add back libbz2-dev * try this * revert --- tools/install_ubuntu_dependencies.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/install_ubuntu_dependencies.sh b/tools/install_ubuntu_dependencies.sh index facc9eb9f..97e06bcc6 100755 --- a/tools/install_ubuntu_dependencies.sh +++ b/tools/install_ubuntu_dependencies.sh @@ -45,6 +45,7 @@ function install_ubuntu_common_requirements() { libavdevice-dev \ libavutil-dev \ libavfilter-dev \ + libbz2-dev \ libeigen3-dev \ libffi-dev \ libgles2-mesa-dev \ From d05cb31e2e916fba41ba8167030945f427fd811b Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 18 Sep 2025 17:03:16 -0700 Subject: [PATCH 028/910] raylib: fix pairing url --- common/api.py | 4 +++- selfdrive/ui/widgets/pairing_dialog.py | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/common/api.py b/common/api.py index ac231400a..005655b21 100644 --- a/common/api.py +++ b/common/api.py @@ -22,7 +22,7 @@ class Api: def request(self, method, endpoint, timeout=None, access_token=None, **params): return api_get(endpoint, method=method, timeout=timeout, access_token=access_token, **params) - def get_token(self, expiry_hours=1): + def get_token(self, payload_extra=None, expiry_hours=1): now = datetime.now(UTC).replace(tzinfo=None) payload = { 'identity': self.dongle_id, @@ -30,6 +30,8 @@ class Api: 'iat': now, 'exp': now + timedelta(hours=expiry_hours) } + if payload_extra is not None: + payload.update(payload_extra) token = jwt.encode(payload, self.private_key, algorithm='RS256') if isinstance(token, bytes): token = token.decode('utf8') diff --git a/selfdrive/ui/widgets/pairing_dialog.py b/selfdrive/ui/widgets/pairing_dialog.py index 63298914e..79d05eaf4 100644 --- a/selfdrive/ui/widgets/pairing_dialog.py +++ b/selfdrive/ui/widgets/pairing_dialog.py @@ -24,11 +24,11 @@ class PairingDialog: def _get_pairing_url(self) -> str: try: dongle_id = self.params.get("DongleId") or "" - token = Api(dongle_id).get_token() + token = Api(dongle_id).get_token({'pair': True}) except Exception as e: cloudlog.warning(f"Failed to get pairing token: {e}") token = "" - return f"https://connect.comma.ai/setup?token={token}" + return f"https://connect.comma.ai/?pair={token}" def _generate_qr_code(self) -> None: try: From 2a5de8e0f81cace1372b3fdf65305db15fdfc7c5 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 18 Sep 2025 17:11:53 -0700 Subject: [PATCH 029/910] raylib: fix shader antialiasing (#36176) * fix * np --- system/ui/lib/shader_polygon.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/system/ui/lib/shader_polygon.py b/system/ui/lib/shader_polygon.py index 39bc0d5aa..d03ad5d8c 100644 --- a/system/ui/lib/shader_polygon.py +++ b/system/ui/lib/shader_polygon.py @@ -99,19 +99,13 @@ float distanceToEdge(vec2 p) { void main() { vec2 pixel = fragTexCoord * resolution; - // Compute pixel size for anti-aliasing - vec2 pixelGrad = vec2(dFdx(pixel.x), dFdy(pixel.y)); - float pixelSize = length(pixelGrad); - float aaWidth = max(0.5, pixelSize * 1.5); - bool inside = isPointInsidePolygon(pixel); - if (inside) { - finalColor = useGradient == 1 ? getGradientColor(pixel) : fillColor; - return; - } + float sd = (inside ? 1.0 : -1.0) * distanceToEdge(pixel); - float sd = -distanceToEdge(pixel); - float alpha = smoothstep(-aaWidth, aaWidth, sd); + // ~1 pixel wide anti-aliasing + float w = max(0.75, fwidth(sd)); + + float alpha = smoothstep(-w, w, sd); if (alpha > 0.0){ vec4 color = useGradient == 1 ? getGradientColor(pixel) : fillColor; finalColor = vec4(color.rgb, color.a * alpha); From c5999702ae95ca538fa71bdaa8f0a98137bf18b4 Mon Sep 17 00:00:00 2001 From: Jason Young <46612682+jyoung8607@users.noreply.github.com> Date: Fri, 19 Sep 2025 19:40:20 -0400 Subject: [PATCH 030/910] Honda: Add 2026 Honda Passport to release (#36179) * bump opendbc * regen CARS.md * update RELEASES.md --- RELEASES.md | 1 + docs/CARS.md | 3 ++- opendbc_repo | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 7c0b967ab..f5cf63060 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -8,6 +8,7 @@ Version 0.10.1 (2025-09-08) * Honda City 2023 support thanks to vanillagorillaa and drFritz! * Honda N-Box 2018 support thanks to miettal! * Honda Odyssey 2021-25 support thanks to csouers and MVL! +* Honda Passport 2026 support thanks to vanillagorillaa and MVL! Version 0.10.0 (2025-08-05) ======================== diff --git a/docs/CARS.md b/docs/CARS.md index 7d3f7a944..22cb0d46c 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -4,7 +4,7 @@ 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. -# 323 Supported Cars +# 324 Supported Cars |Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Hardware Needed
 |Video|Setup Video| |---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| @@ -102,6 +102,7 @@ A supported vehicle is one that just works when you install a comma device. All |Honda|Odyssey 2018-20|Honda Sensing|openpilot|26 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| |Honda|Odyssey 2021-25|All|openpilot available[1](#footnotes)|0 mph|43 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| |Honda|Passport 2019-25|All|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Honda|Passport 2026|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch 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
||| |Honda|Pilot 2016-22|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| |Honda|Pilot 2023-25|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch C 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
||| |Honda|Ridgeline 2017-25|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| diff --git a/opendbc_repo b/opendbc_repo index c2ba07083..c9e7868cb 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit c2ba07083c1cfbb0c5b86469bd7b87090291a0d3 +Subproject commit c9e7868cb830978c2254ef9f012b843bb4f0ddd7 From 5164555c4f4d6ba2cc6e7c12be785c58f7671087 Mon Sep 17 00:00:00 2001 From: mvl-boston Date: Fri, 19 Sep 2025 19:46:05 -0400 Subject: [PATCH 031/910] Steering Assist warning clarification (#36135) * clarifying warning message * more clarity with steering assist warnings --- selfdrive/selfdrived/events.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/selfdrive/selfdrived/events.py b/selfdrive/selfdrived/events.py index d4a00115d..c865cc94a 100755 --- a/selfdrive/selfdrived/events.py +++ b/selfdrive/selfdrived/events.py @@ -240,7 +240,7 @@ def below_engage_speed_alert(CP: car.CarParams, CS: car.CarState, sm: messaging. def below_steer_speed_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert: return Alert( - f"Steer Unavailable Below {get_display_speed(CP.minSteerSpeed, metric)}", + f"Steer Assist Unavailable Below {get_display_speed(CP.minSteerSpeed, metric)}", "", AlertStatus.userPrompt, AlertSize.small, Priority.LOW, VisualAlert.none, AudibleAlert.prompt, 0.4) @@ -489,7 +489,7 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = { EventName.steerTempUnavailableSilent: { ET.WARNING: Alert( - "Steering Temporarily Unavailable", + "Steering Assist Temporarily Unavailable", "", AlertStatus.userPrompt, AlertSize.small, Priority.LOW, VisualAlert.steerRequired, AudibleAlert.prompt, 1.8), @@ -735,7 +735,7 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = { }, EventName.steerTempUnavailable: { - ET.SOFT_DISABLE: soft_disable_alert("Steering Temporarily Unavailable"), + ET.SOFT_DISABLE: soft_disable_alert("Steering Assist Temporarily Unavailable"), ET.NO_ENTRY: NoEntryAlert("Steering Temporarily Unavailable"), }, From 6ed8f07cb629a6cdcb71739b2022b1281525c211 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 19 Sep 2025 16:46:23 -0700 Subject: [PATCH 032/910] Capnp memoryview (#36163) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * lock * bump opendbc * fix one * and * Add a memoryview fallback in webrtcd * fix * revert * rerevert * bump to master --------- Co-authored-by: Kacper Rączy --- opendbc_repo | 2 +- pyproject.toml | 4 +- system/loggerd/tests/test_loggerd.py | 2 +- system/webrtc/device/video.py | 2 +- system/webrtc/webrtcd.py | 11 +- uv.lock | 773 ++++++++++++++------------- 6 files changed, 406 insertions(+), 388 deletions(-) diff --git a/opendbc_repo b/opendbc_repo index c9e7868cb..0c8676f74 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit c9e7868cb830978c2254ef9f012b843bb4f0ddd7 +Subproject commit 0c8676f74cf386ac914faa80a6b14303693ccde9 diff --git a/pyproject.toml b/pyproject.toml index 489876f78..3d2c2a6f7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,7 @@ dependencies = [ # core "cffi", "scons", - "pycapnp==2.1.0", + "pycapnp", "Cython", "setuptools", "numpy >=2.0", @@ -119,7 +119,7 @@ dev = [ "tabulate", "types-requests", "types-tabulate", - "raylib", + "raylib==5.5.0.2", # 5.5.0.3 has regression with gui_set_style ] tools = [ diff --git a/system/loggerd/tests/test_loggerd.py b/system/loggerd/tests/test_loggerd.py index c6a4b12e6..d63d7d84e 100644 --- a/system/loggerd/tests/test_loggerd.py +++ b/system/loggerd/tests/test_loggerd.py @@ -178,7 +178,7 @@ class TestLoggerd: assert logged_params['AccessToken'] == b'', f"DONT_LOG param value was logged: {repr(logged_params['AccessToken'])}" for param_key, initData_key, v in fake_params: assert getattr(initData, initData_key) == v - assert logged_params[param_key].decode() == v + assert logged_params[param_key].tobytes().decode() == v @pytest.mark.xdist_group("camera_encoder_tests") # setting xdist group ensures tests are run in same worker, prevents encoderd from crashing def test_rotation(self): diff --git a/system/webrtc/device/video.py b/system/webrtc/device/video.py index 1bca90929..ffa1565cb 100644 --- a/system/webrtc/device/video.py +++ b/system/webrtc/device/video.py @@ -30,7 +30,7 @@ class LiveStreamVideoStreamTrack(TiciVideoStreamTrack): evta = getattr(msg, msg.which()) - packet = av.Packet(evta.header + evta.data) + packet = av.Packet(evta.header.tobytes() + evta.data.tobytes()) packet.time_base = self._time_base packet.pts = self._pts diff --git a/system/webrtc/webrtcd.py b/system/webrtc/webrtcd.py index fb93e565f..34abe8ab4 100755 --- a/system/webrtc/webrtcd.py +++ b/system/webrtc/webrtcd.py @@ -22,6 +22,13 @@ from openpilot.system.webrtc.schema import generate_field from cereal import messaging, log +# pycapnp 2.2.0+ is using memoryview instead of bytes for data fields +def memoryview_fallback(obj): + if isinstance(obj, memoryview): + return obj.tobytes().decode() + return obj + + class CerealOutgoingMessageProxy: def __init__(self, sm: messaging.SubMaster): self.sm = sm @@ -37,6 +44,8 @@ class CerealOutgoingMessageProxy: msg_dict = [self.to_json(msg) for msg in msg_content] elif isinstance(msg_content, bytes): msg_dict = msg_content.decode() + elif isinstance(msg_content, memoryview): + msg_dict = msg_content.tobytes().decode() else: msg_dict = msg_content @@ -51,7 +60,7 @@ class CerealOutgoingMessageProxy: msg_dict = self.to_json(self.sm[service]) mono_time, valid = self.sm.logMonoTime[service], self.sm.valid[service] outgoing_msg = {"type": service, "logMonoTime": mono_time, "valid": valid, "data": msg_dict} - encoded_msg = json.dumps(outgoing_msg).encode() + encoded_msg = json.dumps(outgoing_msg, default=memoryview_fallback).encode() for channel in self.channels: channel.send(encoded_msg) diff --git a/uv.lock b/uv.lock index 25d2b626c..0b2bca29d 100644 --- a/uv.lock +++ b/uv.lock @@ -152,21 +152,21 @@ wheels = [ [[package]] name = "azure-core" -version = "1.35.0" +version = "1.35.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "requests" }, { name = "six" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ce/89/f53968635b1b2e53e4aad2dd641488929fef4ca9dfb0b97927fa7697ddf3/azure_core-1.35.0.tar.gz", hash = "sha256:c0be528489485e9ede59b6971eb63c1eaacf83ef53001bfe3904e475e972be5c", size = 339689, upload-time = "2025-07-03T00:55:23.496Z" } +sdist = { url = "https://files.pythonhosted.org/packages/15/6b/2653adc0f33adba8f11b1903701e6b1c10d34ce5d8e25dfa13a422f832b0/azure_core-1.35.1.tar.gz", hash = "sha256:435d05d6df0fff2f73fb3c15493bb4721ede14203f1ff1382aa6b6b2bdd7e562", size = 345290, upload-time = "2025-09-11T22:58:04.481Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/78/bf94897361fdd650850f0f2e405b2293e2f12808239046232bdedf554301/azure_core-1.35.0-py3-none-any.whl", hash = "sha256:8db78c72868a58f3de8991eb4d22c4d368fae226dac1002998d6c50437e7dad1", size = 210708, upload-time = "2025-07-03T00:55:25.238Z" }, + { url = "https://files.pythonhosted.org/packages/27/52/805980aa1ba18282077c484dba634ef0ede1e84eec8be9c92b2e162d0ed6/azure_core-1.35.1-py3-none-any.whl", hash = "sha256:12da0c9e08e48e198f9158b56ddbe33b421477e1dc98c2e1c8f9e254d92c468b", size = 211800, upload-time = "2025-09-11T22:58:06.281Z" }, ] [[package]] name = "azure-identity" -version = "1.24.0" +version = "1.25.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "azure-core" }, @@ -175,9 +175,9 @@ dependencies = [ { name = "msal-extensions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b5/44/f3ee20bacb220b6b4a2b0a6cf7e742eecb383a5ccf604dd79ec27c286b7e/azure_identity-1.24.0.tar.gz", hash = "sha256:6c3a40b2a70af831e920b89e6421e8dcd4af78a0cb38b9642d86c67643d4930c", size = 271630, upload-time = "2025-08-07T22:27:36.258Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/9e/4c9682a286c3c89e437579bd9f64f311020e5125c1321fd3a653166b5716/azure_identity-1.25.0.tar.gz", hash = "sha256:4177df34d684cddc026e6cf684e1abb57767aa9d84e7f2129b080ec45eee7733", size = 278507, upload-time = "2025-09-12T01:30:04.418Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a9/74/17428cb429e8d52f6d0d69ed685f4760a545cb0156594963a9337b53b6c9/azure_identity-1.24.0-py3-none-any.whl", hash = "sha256:9e04997cde0ab02ed66422c74748548e620b7b29361c72ce622acab0267ff7c4", size = 187890, upload-time = "2025-08-07T22:27:38.033Z" }, + { url = "https://files.pythonhosted.org/packages/75/54/81683b6756676a22e037b209695b08008258e603f7e47c56834029c5922a/azure_identity-1.25.0-py3-none-any.whl", hash = "sha256:becaec086bbdf8d1a6aa4fb080c2772a0f824a97d50c29637ec8cc4933f1e82d", size = 190861, upload-time = "2025-09-12T01:30:06.474Z" }, ] [[package]] @@ -197,25 +197,25 @@ wheels = [ [[package]] name = "casadi" -version = "3.7.1" +version = "3.7.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b1/21/1b4ea75dbddfcdbc7cd70d83147dde72899667097c0c05b288fd7015ef10/casadi-3.7.1.tar.gz", hash = "sha256:12577155cd3cd79ba162381bfed6add1541bc770ba3f1f1334e4eb159d9b39ba", size = 6065586, upload-time = "2025-07-23T21:47:56.483Z" } +sdist = { url = "https://files.pythonhosted.org/packages/92/62/1e98662024915ecb09c6894c26a3f497f4afa66570af3f53db4651fc45f1/casadi-3.7.2.tar.gz", hash = "sha256:b4d7bd8acdc4180306903ae1c9eddaf41be2a3ae2fa7154c57174ae64acdc60d", size = 6053600, upload-time = "2025-09-10T10:05:49.521Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/4d/cfe092b2deb65dbf913d38148e30dfe41bbe9f289f892ea7a5d3526532d2/casadi-3.7.1-cp311-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:c13fafdff0cdf73392fe2002245befe7496b5ac99b70adb64babff0099068c8a", size = 47100775, upload-time = "2025-07-23T19:56:59.317Z" }, - { url = "https://files.pythonhosted.org/packages/13/41/c4ad627e447a083fbfd2826f1f4684223f0072fdc49f58157ca6a6ce0a77/casadi-3.7.1-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:74d9c6bad3c7cb347494d52cc3e6faf3fbf3300046fab0e1ddc9439d3fd68486", size = 42282155, upload-time = "2025-07-23T19:59:11.191Z" }, - { url = "https://files.pythonhosted.org/packages/ca/e2/53733933d454657e319cc8074e004ec46660c4797cbffcaf7695a874b0da/casadi-3.7.1-cp311-none-manylinux2014_aarch64.whl", hash = "sha256:6fbc0803e6c7ffb9ba9a41ba38e7c1dadee73835edbacacce3530343f078f3f9", size = 47272638, upload-time = "2025-07-23T20:03:33.017Z" }, - { url = "https://files.pythonhosted.org/packages/e1/72/8002d4fe64325842eb6a7f7ac1e4574c02584e85e5b9d93a08a789095505/casadi-3.7.1-cp311-none-manylinux2014_i686.whl", hash = "sha256:38a16b0c7caff5297df91c7e3a65091824979367e6fc8e6eb0e5ab9f1f832af8", size = 72418203, upload-time = "2025-07-23T20:07:18.681Z" }, - { url = "https://files.pythonhosted.org/packages/96/e5/065287b78c4f6a00a010d7c3f5316df1b95f7c5bda644f2151b54a79a964/casadi-3.7.1-cp311-none-manylinux2014_x86_64.whl", hash = "sha256:2dd75cc1b5ca8009586186e4a63e0cdd83a672bf308edc75ef84f9c896cd971d", size = 75558461, upload-time = "2025-07-23T21:33:20.771Z" }, - { url = "https://files.pythonhosted.org/packages/b0/4c/084d2f3bca33d0b3d509be17c3b678f6bcc03f3409d42e88750cb1e44966/casadi-3.7.1-cp311-none-win_amd64.whl", hash = "sha256:3ab68c6dd621b5f150635bf06aa8e1697cae2fbe0137e9d47270c54d305c334c", size = 50987255, upload-time = "2025-07-23T21:34:20.349Z" }, - { url = "https://files.pythonhosted.org/packages/e1/a3/65942d3e0f7589a72e69dd9dee575963ecf51504e985f132bce8b0d24988/casadi-3.7.1-cp312-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:4a29ae3212b0ae6931d439ca3bfa2ac279826e99c2c9a443704e84021933ab76", size = 47102059, upload-time = "2025-07-23T20:36:47.174Z" }, - { url = "https://files.pythonhosted.org/packages/2e/f4/dc371199583f6b154ba0966c4132418737a2d2e019c9f6ad227ebd279684/casadi-3.7.1-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:70edf1df3fc83401eb5a2f7053ae8394f4e612e61c2ad6de088e3d76d0ec845e", size = 42282702, upload-time = "2025-07-23T20:39:41.499Z" }, - { url = "https://files.pythonhosted.org/packages/8e/ff/5f8a9378cd7b322d1dcc8b8aa9fbc454ad57152b754c94b50ffdc67254a0/casadi-3.7.1-cp312-none-manylinux2014_aarch64.whl", hash = "sha256:a1717bbd07a6eb98acddf5ea5da7e78a9b85b75dbabb55190cc6b0f2812ef39d", size = 47271636, upload-time = "2025-07-23T20:42:59.961Z" }, - { url = "https://files.pythonhosted.org/packages/e1/11/e1b6a76bf288fc86dc8919cbcd2d3a654e7ead4240d9928deb8ed4ee91da/casadi-3.7.1-cp312-none-manylinux2014_i686.whl", hash = "sha256:57ae29d16af0716ebc15d161bcf0bcd97d35631b10c0e80838290d249cac80d7", size = 72410197, upload-time = "2025-07-23T21:35:35.509Z" }, - { url = "https://files.pythonhosted.org/packages/c1/76/bb43ff625066b178132172d41db0c5c962b4af9db15c88b8a23e65376d18/casadi-3.7.1-cp312-none-manylinux2014_x86_64.whl", hash = "sha256:756f3d07d3414dc54f05ebec883ac3db6e307bfe661719d188340504b6bed420", size = 75552890, upload-time = "2025-07-23T21:37:14.638Z" }, - { url = "https://files.pythonhosted.org/packages/11/0c/8026b0b238e00a2e63c7142505cbcd0931b4db4140d55c83962f9d8e0b02/casadi-3.7.1-cp312-none-win_amd64.whl", hash = "sha256:b228fbcd1f41eb8d5405dbc1e0ecb780daf89808392706bb77fd2b240f8a437e", size = 50984453, upload-time = "2025-07-23T21:38:18.21Z" }, + { url = "https://files.pythonhosted.org/packages/29/01/d5e3058775ec8e24a01eb74d36099493b872536ef9e39f1e49624b977778/casadi-3.7.2-cp311-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:f43b0562d05a5e6e81f1885fc4ae426c382e36eebfd8d27f1baff6052178a9b0", size = 47115880, upload-time = "2025-09-10T07:52:24.399Z" }, + { url = "https://files.pythonhosted.org/packages/0e/cf/4af27e010d599a5419129d34fdde41637029a1cca2a40bef0965d6d52228/casadi-3.7.2-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:70add3334b437b60a9bc0f864d094350f1a4fcbf9e8bafec870b61aed64674df", size = 42293337, upload-time = "2025-09-10T08:03:32.556Z" }, + { url = "https://files.pythonhosted.org/packages/ac/4c/d1a50cc840103e00effcbaf8e911b6b3fb6ba2c8f4025466f524854968ed/casadi-3.7.2-cp311-none-manylinux2014_aarch64.whl", hash = "sha256:392d3367a4b33cf223013dad8122a0e549da40b1702a5375f82f85b563e5c0cf", size = 47277175, upload-time = "2025-09-10T08:04:08.811Z" }, + { url = "https://files.pythonhosted.org/packages/be/29/6e5714d124e6ddafbccc3ed774ca603081caa1175c7f0e1c52484184dfb3/casadi-3.7.2-cp311-none-manylinux2014_i686.whl", hash = "sha256:2ce09e0ced6df33048dccd582b5cfa2c9ff5193b12858b2584078afc17761905", size = 72438460, upload-time = "2025-09-10T08:05:02.769Z" }, + { url = "https://files.pythonhosted.org/packages/23/32/ac1f3999273aa4aae48516f6f4b7b267e0cc70d8527866989798cb81312f/casadi-3.7.2-cp311-none-manylinux2014_x86_64.whl", hash = "sha256:5086799a46d10ba884b72fd02c21be09dae52cbc189272354a5d424791b55f37", size = 75574474, upload-time = "2025-09-10T08:06:00.709Z" }, + { url = "https://files.pythonhosted.org/packages/68/78/7fd10709504c1757f70db3893870a891fcb9f1ec9f05e8ef2e3f3b9d7e2f/casadi-3.7.2-cp311-none-win_amd64.whl", hash = "sha256:72aa5727417d781ed216f16b5e93c6ddca5db27d83b0015a729e8ad570cdc465", size = 50994144, upload-time = "2025-09-10T08:06:42.384Z" }, + { url = "https://files.pythonhosted.org/packages/65/c8/689d085447b1966f42bdb8aa4fbebef49a09697dbee32ab02a865c17ac1b/casadi-3.7.2-cp312-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:309ea41a69c9230390d349b0dd899c6a19504d1904c0756bef463e47fb5c8f9a", size = 47116756, upload-time = "2025-09-10T07:53:00.931Z" }, + { url = "https://files.pythonhosted.org/packages/1e/c0/3c4704394a6fd4dfb2123a4fd71ba64a001f340670a3eba45be7a19ac736/casadi-3.7.2-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:6033381234db810b2247d16c6352e679a009ec4365d04008fc768866e011ed58", size = 42293718, upload-time = "2025-09-10T08:07:16.415Z" }, + { url = "https://files.pythonhosted.org/packages/f3/24/4cf05469ddf8544da5e92f359f96d716a97e7482999f085a632bc4ef344a/casadi-3.7.2-cp312-none-manylinux2014_aarch64.whl", hash = "sha256:732f2804d0766454bb75596339e4f2da6662ffb669621da0f630ed4af9e83d6a", size = 47276175, upload-time = "2025-09-10T08:08:09.29Z" }, + { url = "https://files.pythonhosted.org/packages/82/08/b5f57fea03128efd5c860673b6ac44776352e6c1af862b8177f4c503fffe/casadi-3.7.2-cp312-none-manylinux2014_i686.whl", hash = "sha256:cf17298ff0c162735bdf9bf72b765c636ae732130604017a3b52e26e35402857", size = 72430454, upload-time = "2025-09-10T08:09:10.781Z" }, + { url = "https://files.pythonhosted.org/packages/24/ab/d7233c915b12c005655437c6c4cf0ae46cbbb2b20d743cb5e4881ad3104a/casadi-3.7.2-cp312-none-manylinux2014_x86_64.whl", hash = "sha256:cde616930fa1440ad66f1850670399423edd37354eed9b12e74b3817b98d1187", size = 75568903, upload-time = "2025-09-10T08:10:07.108Z" }, + { url = "https://files.pythonhosted.org/packages/3e/b9/5b984124f539656efdf079f3d8f09d73667808ec8d0546e6bce6dc60ade6/casadi-3.7.2-cp312-none-win_amd64.whl", hash = "sha256:81d677d2b020c1307c1eb25eae15686e5de199bb066828c3eaabdfaaaf457ffd", size = 50991347, upload-time = "2025-09-10T08:10:46.629Z" }, ] [[package]] @@ -229,36 +229,38 @@ wheels = [ [[package]] name = "cffi" -version = "1.17.1" +version = "2.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pycparser" }, + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621, upload-time = "2024-09-04T20:45:21.852Z" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/f4/927e3a8899e52a27fa57a48607ff7dc91a9ebe97399b357b85a0c7892e00/cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", size = 182264, upload-time = "2024-09-04T20:43:51.124Z" }, - { url = "https://files.pythonhosted.org/packages/6c/f5/6c3a8efe5f503175aaddcbea6ad0d2c96dad6f5abb205750d1b3df44ef29/cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", size = 178651, upload-time = "2024-09-04T20:43:52.872Z" }, - { url = "https://files.pythonhosted.org/packages/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", size = 445259, upload-time = "2024-09-04T20:43:56.123Z" }, - { url = "https://files.pythonhosted.org/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", size = 469200, upload-time = "2024-09-04T20:43:57.891Z" }, - { url = "https://files.pythonhosted.org/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", size = 477235, upload-time = "2024-09-04T20:44:00.18Z" }, - { url = "https://files.pythonhosted.org/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", size = 459721, upload-time = "2024-09-04T20:44:01.585Z" }, - { url = "https://files.pythonhosted.org/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", size = 467242, upload-time = "2024-09-04T20:44:03.467Z" }, - { url = "https://files.pythonhosted.org/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", size = 477999, upload-time = "2024-09-04T20:44:05.023Z" }, - { url = "https://files.pythonhosted.org/packages/44/74/f2a2460684a1a2d00ca799ad880d54652841a780c4c97b87754f660c7603/cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", size = 454242, upload-time = "2024-09-04T20:44:06.444Z" }, - { url = "https://files.pythonhosted.org/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", size = 478604, upload-time = "2024-09-04T20:44:08.206Z" }, - { url = "https://files.pythonhosted.org/packages/34/33/e1b8a1ba29025adbdcda5fb3a36f94c03d771c1b7b12f726ff7fef2ebe36/cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", size = 171727, upload-time = "2024-09-04T20:44:09.481Z" }, - { url = "https://files.pythonhosted.org/packages/3d/97/50228be003bb2802627d28ec0627837ac0bf35c90cf769812056f235b2d1/cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", size = 181400, upload-time = "2024-09-04T20:44:10.873Z" }, - { url = "https://files.pythonhosted.org/packages/5a/84/e94227139ee5fb4d600a7a4927f322e1d4aea6fdc50bd3fca8493caba23f/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", size = 183178, upload-time = "2024-09-04T20:44:12.232Z" }, - { url = "https://files.pythonhosted.org/packages/da/ee/fb72c2b48656111c4ef27f0f91da355e130a923473bf5ee75c5643d00cca/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", size = 178840, upload-time = "2024-09-04T20:44:13.739Z" }, - { url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803, upload-time = "2024-09-04T20:44:15.231Z" }, - { url = "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850, upload-time = "2024-09-04T20:44:17.188Z" }, - { url = "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729, upload-time = "2024-09-04T20:44:18.688Z" }, - { url = "https://files.pythonhosted.org/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", size = 471256, upload-time = "2024-09-04T20:44:20.248Z" }, - { url = "https://files.pythonhosted.org/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424, upload-time = "2024-09-04T20:44:21.673Z" }, - { url = "https://files.pythonhosted.org/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568, upload-time = "2024-09-04T20:44:23.245Z" }, - { url = "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736, upload-time = "2024-09-04T20:44:24.757Z" }, - { url = "https://files.pythonhosted.org/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448, upload-time = "2024-09-04T20:44:26.208Z" }, - { url = "https://files.pythonhosted.org/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976, upload-time = "2024-09-04T20:44:27.578Z" }, + { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" }, + { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" }, + { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" }, + { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" }, + { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" }, + { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" }, + { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" }, + { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" }, + { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" }, + { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" }, + { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" }, + { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, + { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, + { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, + { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, + { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, + { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, + { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, + { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, + { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, + { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, ] [[package]] @@ -294,14 +296,14 @@ wheels = [ [[package]] name = "click" -version = "8.2.1" +version = "8.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/61/de6cd827efad202d7057d93e0fed9294b96952e188f7384832791c7b2254/click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4", size = 276943, upload-time = "2025-09-18T17:32:23.696Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" }, + { url = "https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc", size = 107295, upload-time = "2025-09-18T17:32:22.42Z" }, ] [[package]] @@ -415,31 +417,31 @@ wheels = [ [[package]] name = "cython" -version = "3.1.3" +version = "3.1.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/18/ab/915337fb39ab4f4539a313df38fc69938df3bf14141b90d61dfd5c2919de/cython-3.1.3.tar.gz", hash = "sha256:10ee785e42328924b78f75a74f66a813cb956b4a9bc91c44816d089d5934c089", size = 3186689, upload-time = "2025-08-13T06:19:13.619Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/f6/d762df1f436a0618455d37f4e4c4872a7cd0dcfc8dec3022ee99e4389c69/cython-3.1.4.tar.gz", hash = "sha256:9aefefe831331e2d66ab31799814eae4d0f8a2d246cbaaaa14d1be29ef777683", size = 3190778, upload-time = "2025-09-16T07:20:33.531Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b5/51/54f5d1bed7b7d003d0584996a030542497aeb05b9241782c217ea71061f5/cython-3.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4b3b2f6587b42efdece2d174a2aa4234da4524cc6673f3955c2e62b60c6d11fd", size = 3002973, upload-time = "2025-08-13T06:19:50.777Z" }, - { url = "https://files.pythonhosted.org/packages/05/07/b4043fed60070ee21b0d9ff3a877d2ecdc79231e55119ce852b79b690306/cython-3.1.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:963cf640d049fcca1cefd62d1653f859892d6dc8e4d958eb49a5babc491de6a1", size = 2865389, upload-time = "2025-08-13T06:19:52.675Z" }, - { url = "https://files.pythonhosted.org/packages/b4/e3/67d349b5310a40f281e153e826ca24d9f7ba2997c06800eda781253dccfd/cython-3.1.3-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2ab05d1bf2d5522ecff35d94ca233b77db2300413597c3ca0b6448377fa4bd7c", size = 3410316, upload-time = "2025-08-13T06:19:55.217Z" }, - { url = "https://files.pythonhosted.org/packages/49/c4/84aae921135174111091547fa726ea5f8bba718cd12b2589a70c2aab8d5c/cython-3.1.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cd517e3be052fb49443585b01f02f46080b3408e32c1108a0fdc4cc25b3c9d30", size = 3189568, upload-time = "2025-08-13T06:19:57.503Z" }, - { url = "https://files.pythonhosted.org/packages/e2/85/1bf18883f1a1f656ad83a671e54553caedb1ee2f39a3e792a14aa82557a2/cython-3.1.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a48e2180d74e3c528561d85b48f9a939a429537f9ea8aac7fb16180e7bff47e2", size = 3312649, upload-time = "2025-08-13T06:19:59.894Z" }, - { url = "https://files.pythonhosted.org/packages/68/da/cc1373decc0d14a25f1b434f47de5e27696f3092319aa5e19fcf84157415/cython-3.1.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e7c9daa90b15f59aa2a0d638ac1b36777a7e80122099952a0295c71190ce14bc", size = 3203821, upload-time = "2025-08-13T06:20:02.124Z" }, - { url = "https://files.pythonhosted.org/packages/0a/32/e10582d6f7b02ee63607f388dbbd34e329c54559c85961ddc5c655266519/cython-3.1.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:08ac646ff42781827f23b7a9b61669cdb92055f52724cd8cbe0e1defc56fce2e", size = 3426853, upload-time = "2025-08-13T06:20:04.474Z" }, - { url = "https://files.pythonhosted.org/packages/e0/da/5b0d1b5a4c7622ec812ad9374c9c6b426d69818f13f51e36f4f25ca01c86/cython-3.1.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bb0e0e7fceaffa22e4dc9600f7f75998eef5cc6ac5a8c0733b482851ba765ef2", size = 3328872, upload-time = "2025-08-13T06:20:06.834Z" }, - { url = "https://files.pythonhosted.org/packages/b0/5f/f102f6c8d27338f0baf094754c67f920828a19612053abc903e66f84506f/cython-3.1.3-cp311-cp311-win32.whl", hash = "sha256:42b1c3ebe36a52e2a8e939c0651e9ca5d30b81d03f800bbf0499deb0503ab565", size = 2480850, upload-time = "2025-08-13T06:20:08.988Z" }, - { url = "https://files.pythonhosted.org/packages/b7/60/415d0f0f7c2e227707baec11c968387d33507d2eb7f26a2950a323c705e5/cython-3.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:34a973844998281951bf54cdd0b6a9946ba03ba94580820738583a00da167d8f", size = 2712560, upload-time = "2025-08-13T06:20:11.877Z" }, - { url = "https://files.pythonhosted.org/packages/79/26/f433fdfd5182b9231819a99acc49a1f856669532306e7a38dce63ba7297e/cython-3.1.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:849ef3d15d4354e5f74cdb6d3c80d80b03209b3bf1f4ff93315890b19da18944", size = 3014237, upload-time = "2025-08-13T06:20:13.77Z" }, - { url = "https://files.pythonhosted.org/packages/e5/6c/1bebf44f5f177f8c750e608f82c08cd699b8f28cc233e799379bfcf2a2c2/cython-3.1.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:93dd0f62a3f8e93166d8584f8b243180d681ba8fed1f530b55d5f70c348c5797", size = 2844261, upload-time = "2025-08-13T06:20:15.619Z" }, - { url = "https://files.pythonhosted.org/packages/c7/74/983005ce5954f6dc8360b105a561e51a60ea619c53296afac98253d1c9d7/cython-3.1.3-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ff4a2cb84798faffb3988bd94636c3ad31a95ff44ef017f09121abffc56f84cf", size = 3388846, upload-time = "2025-08-13T06:20:17.679Z" }, - { url = "https://files.pythonhosted.org/packages/68/50/dbe7edefe9b652bc72d49da07785173e89197b9a2d64a2ac45da9795eccf/cython-3.1.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4b05319e36f34d5388deea5cc2bcfd65f9ebf76f4ea050829421a69625dbba4a", size = 3167022, upload-time = "2025-08-13T06:20:19.863Z" }, - { url = "https://files.pythonhosted.org/packages/4a/55/b50548b77203e22262002feae23a172f95282b4b8deb5ad104f08e3dc20d/cython-3.1.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7ac902a17934a6da46f80755f49413bc4c03a569ae3c834f5d66da7288ba7e6c", size = 3317782, upload-time = "2025-08-13T06:20:21.962Z" }, - { url = "https://files.pythonhosted.org/packages/f6/1b/20a97507d528dc330d67be4fbad6bc767c681d56192bce8f7117a187b2ad/cython-3.1.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7d7a555a864b1b08576f9e8a67f3789796a065837544f9f683ebf3188012fdbd", size = 3179818, upload-time = "2025-08-13T06:20:24.419Z" }, - { url = "https://files.pythonhosted.org/packages/43/a5/7b32a19c4c6bb0e2cc7ae52269b6b23a42f67f730e4abd4f8dca63660f7a/cython-3.1.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b827ce7d97ef8624adcf2bdda594b3dcb6c9b4f124d8f72001d8aea27d69dc1c", size = 3403206, upload-time = "2025-08-13T06:20:26.846Z" }, - { url = "https://files.pythonhosted.org/packages/ba/e1/08cfd4c5e99f79e62d4a7b0009bbd906748633270935c8a55eee51fead76/cython-3.1.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7851107204085f4f02d0eb6b660ddcad2ce4846e8b7a1eaba724a0bd3cd68a6b", size = 3327837, upload-time = "2025-08-13T06:20:28.946Z" }, - { url = "https://files.pythonhosted.org/packages/23/1b/f3d384be86fa2a6e110b42f42bf97295a513197aaa5532f451c73bf5b48e/cython-3.1.3-cp312-cp312-win32.whl", hash = "sha256:ed20f1b45b2da5a4f8e71a80025bca1cdc96ba35820b0b17658a4a025be920b0", size = 2485990, upload-time = "2025-08-13T06:20:31.517Z" }, - { url = "https://files.pythonhosted.org/packages/de/f0/17cff75e3c141bda73d770f7412632f53e55778d3bfdadfeec4dd3a7d1d6/cython-3.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:dc4ca0f4dec55124cd79ddcfc555be1cbe0092cc99bcf1403621d17b9c6218bb", size = 2704002, upload-time = "2025-08-13T06:20:33.773Z" }, - { url = "https://files.pythonhosted.org/packages/56/c8/46ac27096684f33e27dab749ef43c6b0119c6a0d852971eaefb73256dc4c/cython-3.1.3-py3-none-any.whl", hash = "sha256:d13025b34f72f77bf7f65c1cd628914763e6c285f4deb934314c922b91e6be5a", size = 1225725, upload-time = "2025-08-13T06:19:09.593Z" }, + { url = "https://files.pythonhosted.org/packages/b5/ab/0a568bac7c4c052db4ae27edf01e16f3093cdfef04a2dfd313ef1b3c478a/cython-3.1.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d1d7013dba5fb0506794d4ef8947ff5ed021370614950a8d8d04e57c8c84499e", size = 3026389, upload-time = "2025-09-16T07:22:02.212Z" }, + { url = "https://files.pythonhosted.org/packages/cb/b7/51f5566e1309215a7fef744975b2fabb56d3fdc5fa1922fd7e306c14f523/cython-3.1.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:eed989f5c139d6550ef2665b783d86fab99372590c97f10a3c26c4523c5fce9e", size = 2955954, upload-time = "2025-09-16T07:22:03.782Z" }, + { url = "https://files.pythonhosted.org/packages/28/fd/ad8314520000fe96292fb8208c640fa862baa3053d2f3453a2acb50cafb8/cython-3.1.4-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3df3beb8b024dfd73cfddb7f2f7456751cebf6e31655eed3189c209b634bc2f2", size = 3412005, upload-time = "2025-09-16T07:22:05.483Z" }, + { url = "https://files.pythonhosted.org/packages/0c/3b/e570f8bcb392e7943fc9a25d1b2d1646ef0148ff017d3681511acf6bbfdc/cython-3.1.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f8354703f1168e1aaa01348940f719734c1f11298be333bdb5b94101d49677c0", size = 3191100, upload-time = "2025-09-16T07:22:07.144Z" }, + { url = "https://files.pythonhosted.org/packages/78/81/f1ea09f563ebab732542cb11bf363710e53f3842458159ea2c160788bc8e/cython-3.1.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a928bd7d446247855f54f359057ab4a32c465219c8c1e299906a483393a59a9e", size = 3313786, upload-time = "2025-09-16T07:22:09.15Z" }, + { url = "https://files.pythonhosted.org/packages/ca/17/06575eb6175a926523bada7dac1cd05cc74add96cebbf2e8b492a2494291/cython-3.1.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c233bfff4cc7b9d629eecb7345f9b733437f76dc4441951ec393b0a6e29919fc", size = 3205775, upload-time = "2025-09-16T07:22:10.745Z" }, + { url = "https://files.pythonhosted.org/packages/10/ba/61a8cf56a76ab21ddf6476b70884feff2a2e56b6d9010e1e1b1e06c46f70/cython-3.1.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e9691a2cbc2faf0cd819108bceccf9bfc56c15a06d172eafe74157388c44a601", size = 3428423, upload-time = "2025-09-16T07:22:12.404Z" }, + { url = "https://files.pythonhosted.org/packages/c4/c2/42cf9239088d6b4b62c1c017c36e0e839f64c8d68674ce4172d0e0168d3b/cython-3.1.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ada319207432ea7c6691c70b5c112d261637d79d21ba086ae3726fedde79bfbf", size = 3330489, upload-time = "2025-09-16T07:22:14.576Z" }, + { url = "https://files.pythonhosted.org/packages/b5/08/36a619d6b1fc671a11744998e5cdd31790589e3cb4542927c97f3f351043/cython-3.1.4-cp311-cp311-win32.whl", hash = "sha256:dae81313c28222bf7be695f85ae1d16625aac35a0973a3af1e001f63379440c5", size = 2482410, upload-time = "2025-09-16T07:22:17.373Z" }, + { url = "https://files.pythonhosted.org/packages/6d/58/7d9ae7944bcd32e6f02d1a8d5d0c3875125227d050e235584127f2c64ffd/cython-3.1.4-cp311-cp311-win_amd64.whl", hash = "sha256:60d2f192059ac34c5c26527f2beac823d34aaa766ef06792a3b7f290c18ac5e2", size = 2713755, upload-time = "2025-09-16T07:22:18.949Z" }, + { url = "https://files.pythonhosted.org/packages/f0/51/2939c739cfdc67ab94935a2c4fcc75638afd15e1954552655503a4112e92/cython-3.1.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0d26af46505d0e54fe0f05e7ad089fd0eed8fa04f385f3ab88796f554467bcb9", size = 3062976, upload-time = "2025-09-16T07:22:20.517Z" }, + { url = "https://files.pythonhosted.org/packages/eb/bd/a84de57fd01017bf5dba84a49aeee826db21112282bf8d76ab97567ee15d/cython-3.1.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:66ac8bb5068156c92359e3f0eefa138c177d59d1a2e8a89467881fa7d06aba3b", size = 2970701, upload-time = "2025-09-16T07:22:22.644Z" }, + { url = "https://files.pythonhosted.org/packages/71/79/a09004c8e42f5be188c7636b1be479cdb244a6d8837e1878d062e4e20139/cython-3.1.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cb2e42714faec723d2305607a04bafb49a48a8d8f25dd39368d884c058dbcfbc", size = 3387730, upload-time = "2025-09-16T07:22:24.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/bd/979f8c59e247f562642f3eb98a1b453530e1f7954ef071835c08ed2bf6ba/cython-3.1.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0fd655b27997a209a574873304ded9629de588f021154009e8f923475e2c677", size = 3167289, upload-time = "2025-09-16T07:22:26.35Z" }, + { url = "https://files.pythonhosted.org/packages/34/f8/0b98537f0b4e8c01f76d2a6cf75389987538e4d4ac9faf25836fd18c9689/cython-3.1.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9def7c41f4dc339003b1e6875f84edf059989b9c7f5e9a245d3ce12c190742d9", size = 3321099, upload-time = "2025-09-16T07:22:27.957Z" }, + { url = "https://files.pythonhosted.org/packages/f3/39/437968a2e7c7f57eb6e1144f6aca968aa15fbbf169b2d4da5d1ff6c21442/cython-3.1.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:196555584a8716bf7e017e23ca53e9f632ed493f9faa327d0718e7551588f55d", size = 3179897, upload-time = "2025-09-16T07:22:30.014Z" }, + { url = "https://files.pythonhosted.org/packages/2c/04/b3f42915f034d133f1a34e74a2270bc2def02786f9b40dc9028fbb968814/cython-3.1.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:7fff0e739e07a20726484b8898b8628a7b87acb960d0fc5486013c6b77b7bb97", size = 3400936, upload-time = "2025-09-16T07:22:31.705Z" }, + { url = "https://files.pythonhosted.org/packages/21/eb/2ad9fa0896ab6cf29875a09a9f4aaea37c28b79b869a013bf9b58e4e652e/cython-3.1.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c2754034fa10f95052949cd6b07eb2f61d654c1b9cfa0b17ea53a269389422e8", size = 3332131, upload-time = "2025-09-16T07:22:33.32Z" }, + { url = "https://files.pythonhosted.org/packages/3c/bf/f19283f8405e7e564c3353302a8665ea2c589be63a8e1be1b503043366a9/cython-3.1.4-cp312-cp312-win32.whl", hash = "sha256:2e0808ff3614a1dbfd1adfcbff9b2b8119292f1824b3535b4a173205109509f8", size = 2487672, upload-time = "2025-09-16T07:22:35.227Z" }, + { url = "https://files.pythonhosted.org/packages/30/bf/32150a2e6c7b50b81c5dc9e942d41969400223a9c49d04e2ed955709894c/cython-3.1.4-cp312-cp312-win_amd64.whl", hash = "sha256:f262b32327b6bce340cce5d45bbfe3972cb62543a4930460d8564a489f3aea12", size = 2705348, upload-time = "2025-09-16T07:22:37.922Z" }, + { url = "https://files.pythonhosted.org/packages/7c/24/f7351052cf9db771fe4f32fca47fd66e6d9b53d8613b17faf7d130a9d553/cython-3.1.4-py3-none-any.whl", hash = "sha256:d194d95e4fa029a3f6c7d46bdd16d973808c7ea4797586911fdb67cb98b1a2c6", size = 1227541, upload-time = "2025-09-16T07:20:29.595Z" }, ] [[package]] @@ -477,11 +479,11 @@ wheels = [ [[package]] name = "dnspython" -version = "2.7.0" +version = "2.8.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b5/4a/263763cb2ba3816dd94b08ad3a33d5fdae34ecb856678773cc40a3605829/dnspython-2.7.0.tar.gz", hash = "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1", size = 345197, upload-time = "2024-10-05T20:14:59.362Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/8b/57666417c0f90f08bcafa776861060426765fdb422eb10212086fb811d26/dnspython-2.8.0.tar.gz", hash = "sha256:181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f", size = 368251, upload-time = "2025-09-07T18:58:00.022Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/68/1b/e0a87d256e40e8c888847551b20a017a6b98139178505dc7ffb96f04e954/dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86", size = 313632, upload-time = "2024-10-05T20:14:57.687Z" }, + { url = "https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af", size = 331094, upload-time = "2025-09-07T18:57:58.071Z" }, ] [[package]] @@ -525,27 +527,27 @@ wheels = [ [[package]] name = "fonttools" -version = "4.59.2" +version = "4.60.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0d/a5/fba25f9fbdab96e26dedcaeeba125e5f05a09043bf888e0305326e55685b/fonttools-4.59.2.tar.gz", hash = "sha256:e72c0749b06113f50bcb80332364c6be83a9582d6e3db3fe0b280f996dc2ef22", size = 3540889, upload-time = "2025-08-27T16:40:30.97Z" } +sdist = { url = "https://files.pythonhosted.org/packages/27/d9/4eabd956fe123651a1f0efe29d9758b3837b5ae9a98934bdb571117033bb/fonttools-4.60.0.tar.gz", hash = "sha256:8f5927f049091a0ca74d35cce7f78e8f7775c83a6901a8fbe899babcc297146a", size = 3553671, upload-time = "2025-09-17T11:34:01.504Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/53/742fcd750ae0bdc74de4c0ff923111199cc2f90a4ee87aaddad505b6f477/fonttools-4.59.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:511946e8d7ea5c0d6c7a53c4cb3ee48eda9ab9797cd9bf5d95829a398400354f", size = 2774961, upload-time = "2025-08-27T16:38:47.536Z" }, - { url = "https://files.pythonhosted.org/packages/57/2a/976f5f9fa3b4dd911dc58d07358467bec20e813d933bc5d3db1a955dd456/fonttools-4.59.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8e5e2682cf7be766d84f462ba8828d01e00c8751a8e8e7ce12d7784ccb69a30d", size = 2344690, upload-time = "2025-08-27T16:38:49.723Z" }, - { url = "https://files.pythonhosted.org/packages/c1/8f/b7eefc274fcf370911e292e95565c8253b0b87c82a53919ab3c795a4f50e/fonttools-4.59.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5729e12a982dba3eeae650de48b06f3b9ddb51e9aee2fcaf195b7d09a96250e2", size = 5026910, upload-time = "2025-08-27T16:38:51.904Z" }, - { url = "https://files.pythonhosted.org/packages/69/95/864726eaa8f9d4e053d0c462e64d5830ec7c599cbdf1db9e40f25ca3972e/fonttools-4.59.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c52694eae5d652361d59ecdb5a2246bff7cff13b6367a12da8499e9df56d148d", size = 4971031, upload-time = "2025-08-27T16:38:53.676Z" }, - { url = "https://files.pythonhosted.org/packages/24/4c/b8c4735ebdea20696277c70c79e0de615dbe477834e5a7c2569aa1db4033/fonttools-4.59.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f1f1bbc23ba1312bd8959896f46f667753b90216852d2a8cfa2d07e0cb234144", size = 5006112, upload-time = "2025-08-27T16:38:55.69Z" }, - { url = "https://files.pythonhosted.org/packages/3b/23/f9ea29c292aa2fc1ea381b2e5621ac436d5e3e0a5dee24ffe5404e58eae8/fonttools-4.59.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1a1bfe5378962825dabe741720885e8b9ae9745ec7ecc4a5ec1f1ce59a6062bf", size = 5117671, upload-time = "2025-08-27T16:38:58.984Z" }, - { url = "https://files.pythonhosted.org/packages/ba/07/cfea304c555bf06e86071ff2a3916bc90f7c07ec85b23bab758d4908c33d/fonttools-4.59.2-cp311-cp311-win32.whl", hash = "sha256:e937790f3c2c18a1cbc7da101550a84319eb48023a715914477d2e7faeaba570", size = 2218157, upload-time = "2025-08-27T16:39:00.75Z" }, - { url = "https://files.pythonhosted.org/packages/d7/de/35d839aa69db737a3f9f3a45000ca24721834d40118652a5775d5eca8ebb/fonttools-4.59.2-cp311-cp311-win_amd64.whl", hash = "sha256:9836394e2f4ce5f9c0a7690ee93bd90aa1adc6b054f1a57b562c5d242c903104", size = 2265846, upload-time = "2025-08-27T16:39:02.453Z" }, - { url = "https://files.pythonhosted.org/packages/ba/3d/1f45db2df51e7bfa55492e8f23f383d372200be3a0ded4bf56a92753dd1f/fonttools-4.59.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:82906d002c349cad647a7634b004825a7335f8159d0d035ae89253b4abf6f3ea", size = 2769711, upload-time = "2025-08-27T16:39:04.423Z" }, - { url = "https://files.pythonhosted.org/packages/29/df/cd236ab32a8abfd11558f296e064424258db5edefd1279ffdbcfd4fd8b76/fonttools-4.59.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a10c1bd7644dc58f8862d8ba0cf9fb7fef0af01ea184ba6ce3f50ab7dfe74d5a", size = 2340225, upload-time = "2025-08-27T16:39:06.143Z" }, - { url = "https://files.pythonhosted.org/packages/98/12/b6f9f964fe6d4b4dd4406bcbd3328821c3de1f909ffc3ffa558fe72af48c/fonttools-4.59.2-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:738f31f23e0339785fd67652a94bc69ea49e413dfdb14dcb8c8ff383d249464e", size = 4912766, upload-time = "2025-08-27T16:39:08.138Z" }, - { url = "https://files.pythonhosted.org/packages/73/78/82bde2f2d2c306ef3909b927363170b83df96171f74e0ccb47ad344563cd/fonttools-4.59.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ec99f9bdfee9cdb4a9172f9e8fd578cce5feb231f598909e0aecf5418da4f25", size = 4955178, upload-time = "2025-08-27T16:39:10.094Z" }, - { url = "https://files.pythonhosted.org/packages/92/77/7de766afe2d31dda8ee46d7e479f35c7d48747e558961489a2d6e3a02bd4/fonttools-4.59.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0476ea74161322e08c7a982f83558a2b81b491509984523a1a540baf8611cc31", size = 4897898, upload-time = "2025-08-27T16:39:12.087Z" }, - { url = "https://files.pythonhosted.org/packages/c5/77/ce0e0b905d62a06415fda9f2b2e109a24a5db54a59502b769e9e297d2242/fonttools-4.59.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:95922a922daa1f77cc72611747c156cfb38030ead72436a2c551d30ecef519b9", size = 5049144, upload-time = "2025-08-27T16:39:13.84Z" }, - { url = "https://files.pythonhosted.org/packages/d9/ea/870d93aefd23fff2e07cbeebdc332527868422a433c64062c09d4d5e7fe6/fonttools-4.59.2-cp312-cp312-win32.whl", hash = "sha256:39ad9612c6a622726a6a130e8ab15794558591f999673f1ee7d2f3d30f6a3e1c", size = 2206473, upload-time = "2025-08-27T16:39:15.854Z" }, - { url = "https://files.pythonhosted.org/packages/61/c4/e44bad000c4a4bb2e9ca11491d266e857df98ab6d7428441b173f0fe2517/fonttools-4.59.2-cp312-cp312-win_amd64.whl", hash = "sha256:980fd7388e461b19a881d35013fec32c713ffea1fc37aef2f77d11f332dfd7da", size = 2254706, upload-time = "2025-08-27T16:39:17.893Z" }, - { url = "https://files.pythonhosted.org/packages/65/a4/d2f7be3c86708912c02571db0b550121caab8cd88a3c0aacb9cfa15ea66e/fonttools-4.59.2-py3-none-any.whl", hash = "sha256:8bd0f759020e87bb5d323e6283914d9bf4ae35a7307dafb2cbd1e379e720ad37", size = 1132315, upload-time = "2025-08-27T16:40:28.984Z" }, + { url = "https://files.pythonhosted.org/packages/da/3d/c57731fbbf204ef1045caca28d5176430161ead73cd9feac3e9d9ef77ee6/fonttools-4.60.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a9106c202d68ff5f9b4a0094c4d7ad2eaa7e9280f06427b09643215e706eb016", size = 2830883, upload-time = "2025-09-17T11:32:10.552Z" }, + { url = "https://files.pythonhosted.org/packages/cc/2d/b7a6ebaed464ce441c755252cc222af11edc651d17c8f26482f429cc2c0e/fonttools-4.60.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9da3a4a3f2485b156bb429b4f8faa972480fc01f553f7c8c80d05d48f17eec89", size = 2356005, upload-time = "2025-09-17T11:32:13.248Z" }, + { url = "https://files.pythonhosted.org/packages/ee/c2/ea834e921324e2051403e125c1fe0bfbdde4951a7c1784e4ae6bdbd286cc/fonttools-4.60.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f84de764c6057b2ffd4feb50ddef481d92e348f0c70f2c849b723118d352bf3", size = 5041201, upload-time = "2025-09-17T11:32:15.373Z" }, + { url = "https://files.pythonhosted.org/packages/93/3c/1c64a338e9aa410d2d0728827d5bb1301463078cb225b94589f27558b427/fonttools-4.60.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:800b3fa0d5c12ddff02179d45b035a23989a6c597a71c8035c010fff3b2ef1bb", size = 4977696, upload-time = "2025-09-17T11:32:17.674Z" }, + { url = "https://files.pythonhosted.org/packages/07/cc/c8c411a0d9732bb886b870e052f20658fec9cf91118314f253950d2c1d65/fonttools-4.60.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd68f60b030277f292a582d31c374edfadc60bb33d51ec7b6cd4304531819ba", size = 5020386, upload-time = "2025-09-17T11:32:20.089Z" }, + { url = "https://files.pythonhosted.org/packages/13/01/1d3bc07cf92e7f4fc27f06d4494bf6078dc595b2e01b959157a4fd23df12/fonttools-4.60.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:53328e3ca9e5c8660ef6de07c35f8f312c189b757535e12141be7a8ec942de6e", size = 5131575, upload-time = "2025-09-17T11:32:22.582Z" }, + { url = "https://files.pythonhosted.org/packages/5a/16/08db3917ee19e89d2eb0ee637d37cd4136c849dc421ff63f406b9165c1a1/fonttools-4.60.0-cp311-cp311-win32.whl", hash = "sha256:d493c175ddd0b88a5376e61163e3e6fde3be8b8987db9b092e0a84650709c9e7", size = 2229297, upload-time = "2025-09-17T11:32:24.834Z" }, + { url = "https://files.pythonhosted.org/packages/d2/0b/76764da82c0dfcea144861f568d9e83f4b921e84f2be617b451257bb25a7/fonttools-4.60.0-cp311-cp311-win_amd64.whl", hash = "sha256:cc2770c9dc49c2d0366e9683f4d03beb46c98042d7ccc8ddbadf3459ecb051a7", size = 2277193, upload-time = "2025-09-17T11:32:27.094Z" }, + { url = "https://files.pythonhosted.org/packages/2a/9b/706ebf84b55ab03439c1f3a94d6915123c0d96099f4238b254fdacffe03a/fonttools-4.60.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8c68928a438d60dfde90e2f09aa7f848ed201176ca6652341744ceec4215859f", size = 2831953, upload-time = "2025-09-17T11:32:29.39Z" }, + { url = "https://files.pythonhosted.org/packages/76/40/782f485be450846e4f3aecff1f10e42af414fc6e19d235c70020f64278e1/fonttools-4.60.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b7133821249097cffabf0624eafd37f5a3358d5ce814febe9db688e3673e724e", size = 2351716, upload-time = "2025-09-17T11:32:31.46Z" }, + { url = "https://files.pythonhosted.org/packages/39/77/ad8d2a6ecc19716eb488c8cf118de10f7802e14bdf61d136d7b52358d6b1/fonttools-4.60.0-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d3638905d3d77ac8791127ce181f7cb434f37e4204d8b2e31b8f1e154320b41f", size = 4922729, upload-time = "2025-09-17T11:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/6b/48/aa543037c6e7788e1bc36b3f858ac70a59d32d0f45915263d0b330a35140/fonttools-4.60.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7968a26ef010ae89aabbb2f8e9dec1e2709a2541bb8620790451ee8aeb4f6fbf", size = 4967188, upload-time = "2025-09-17T11:32:35.74Z" }, + { url = "https://files.pythonhosted.org/packages/ac/58/e407d2028adc6387947eff8f2940b31f4ed40b9a83c2c7bbc8b9255126e2/fonttools-4.60.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1ef01ca7847c356b0fe026b7b92304bc31dc60a4218689ee0acc66652c1a36b2", size = 4910043, upload-time = "2025-09-17T11:32:38.054Z" }, + { url = "https://files.pythonhosted.org/packages/16/ef/e78519b3c296ef757a21b792fc6a785aa2ef9a2efb098083d8ed5f6ee2ba/fonttools-4.60.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f3482d7ed7867edfcf785f77c1dffc876c4b2ddac19539c075712ff2a0703cf5", size = 5061980, upload-time = "2025-09-17T11:32:40.457Z" }, + { url = "https://files.pythonhosted.org/packages/00/4c/ad72444d1e3ef704ee90af8d5abf198016a39908d322bf41235562fb01a0/fonttools-4.60.0-cp312-cp312-win32.whl", hash = "sha256:8c937c4fe8addff575a984c9519433391180bf52cf35895524a07b520f376067", size = 2217750, upload-time = "2025-09-17T11:32:42.586Z" }, + { url = "https://files.pythonhosted.org/packages/46/55/3e8ac21963e130242f5a9ea2ebc57f5726d704bf4dcca89088b5b637b2d3/fonttools-4.60.0-cp312-cp312-win_amd64.whl", hash = "sha256:99b06d5d6f29f32e312adaed0367112f5ff2d300ea24363d377ec917daf9e8c5", size = 2266025, upload-time = "2025-09-17T11:32:44.8Z" }, + { url = "https://files.pythonhosted.org/packages/f9/a4/247d3e54eb5ed59e94e09866cfc4f9567e274fbf310ba390711851f63b3b/fonttools-4.60.0-py3-none-any.whl", hash = "sha256:496d26e4d14dcccdd6ada2e937e4d174d3138e3d73f5c9b6ec6eb2fd1dab4f66", size = 1142186, upload-time = "2025-09-17T11:33:59.287Z" }, ] [[package]] @@ -637,10 +639,10 @@ name = "gymnasium" version = "1.2.0" 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/fd/17/c2a0e15c2cd5a8e788389b280996db927b923410de676ec5c7b2695e9261/gymnasium-1.2.0.tar.gz", hash = "sha256:344e87561012558f603880baf264ebc97f8a5c997a957b0c9f910281145534b0", size = 821142, upload-time = "2025-06-27T08:21:20.262Z" } wheels = [ @@ -737,11 +739,11 @@ wheels = [ [[package]] name = "kaitaistruct" -version = "0.10" +version = "0.11" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/54/04/dd60b9cb65d580ef6cb6eaee975ad1bdd22d46a3f51b07a1e0606710ea88/kaitaistruct-0.10.tar.gz", hash = "sha256:a044dee29173d6afbacf27bcac39daf89b654dd418cfa009ab82d9178a9ae52a", size = 7061, upload-time = "2022-07-09T00:34:06.729Z" } +sdist = { url = "https://files.pythonhosted.org/packages/27/b8/ca7319556912f68832daa4b81425314857ec08dfccd8dbc8c0f65c992108/kaitaistruct-0.11.tar.gz", hash = "sha256:053ee764288e78b8e53acf748e9733268acbd579b8d82a427b1805453625d74b", size = 11519, upload-time = "2025-09-08T15:46:25.037Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4e/bf/88ad23efc08708bda9a2647169828e3553bb2093a473801db61f75356395/kaitaistruct-0.10-py2.py3-none-any.whl", hash = "sha256:a97350919adbf37fda881f75e9365e2fb88d04832b7a4e57106ec70119efb235", size = 7013, upload-time = "2022-07-09T00:34:03.905Z" }, + { url = "https://files.pythonhosted.org/packages/4a/4a/cf14bf3b1f5ffb13c69cf5f0ea78031247790558ee88984a8bdd22fae60d/kaitaistruct-0.11-py2.py3-none-any.whl", hash = "sha256:5c6ce79177b4e193a577ecd359e26516d1d6d000a0bffd6e1010f2a46a62a561", size = 11372, upload-time = "2025-09-08T15:46:23.635Z" }, ] [[package]] @@ -842,11 +844,11 @@ wheels = [ [[package]] name = "markdown" -version = "3.8.2" +version = "3.9" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/c2/4ab49206c17f75cb08d6311171f2d65798988db4360c4d1485bd0eedd67c/markdown-3.8.2.tar.gz", hash = "sha256:247b9a70dd12e27f67431ce62523e675b866d254f900c4fe75ce3dda62237c45", size = 362071, upload-time = "2025-06-19T17:12:44.483Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8d/37/02347f6d6d8279247a5837082ebc26fc0d5aaeaf75aa013fcbb433c777ab/markdown-3.9.tar.gz", hash = "sha256:d2900fe1782bd33bdbbd56859defef70c2e78fc46668f8eb9df3128138f2cb6a", size = 364585, upload-time = "2025-09-04T20:25:22.885Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/96/2b/34cc11786bc00d0f04d0f5fdc3a2b1ae0b6239eef72d3d345805f9ad92a1/markdown-3.8.2-py3-none-any.whl", hash = "sha256:5c83764dbd4e00bdd94d85a19b8d55ccca20fe35b2e678a1422b380324dd5f24", size = 106827, upload-time = "2025-06-19T17:12:42.994Z" }, + { url = "https://files.pythonhosted.org/packages/70/ae/44c4a6a4cbb496d93c6257954260fe3a6e91b7bed2240e5dad2a717f5111/markdown-3.9-py3-none-any.whl", hash = "sha256:9f4d91ed810864ea88a6f32c07ba8bee1346c0cc1f6b1f9f6c822f2a9667d280", size = 107441, upload-time = "2025-09-04T20:25:21.784Z" }, ] [[package]] @@ -927,22 +929,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" }, @@ -1128,28 +1130,28 @@ wheels = [ [[package]] name = "mypy" -version = "1.17.1" +version = "1.18.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mypy-extensions" }, { name = "pathspec" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8e/22/ea637422dedf0bf36f3ef238eab4e455e2a0dcc3082b5cc067615347ab8e/mypy-1.17.1.tar.gz", hash = "sha256:25e01ec741ab5bb3eec8ba9cdb0f769230368a22c959c4937360efb89b7e9f01", size = 3352570, upload-time = "2025-07-31T07:54:19.204Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c0/77/8f0d0001ffad290cef2f7f216f96c814866248a0b92a722365ed54648e7e/mypy-1.18.2.tar.gz", hash = "sha256:06a398102a5f203d7477b2923dda3634c36727fa5c237d8f859ef90c42a9924b", size = 3448846, upload-time = "2025-09-19T00:11:10.519Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/46/cf/eadc80c4e0a70db1c08921dcc220357ba8ab2faecb4392e3cebeb10edbfa/mypy-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad37544be07c5d7fba814eb370e006df58fed8ad1ef33ed1649cb1889ba6ff58", size = 10921009, upload-time = "2025-07-31T07:53:23.037Z" }, - { url = "https://files.pythonhosted.org/packages/5d/c1/c869d8c067829ad30d9bdae051046561552516cfb3a14f7f0347b7d973ee/mypy-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:064e2ff508e5464b4bd807a7c1625bc5047c5022b85c70f030680e18f37273a5", size = 10047482, upload-time = "2025-07-31T07:53:26.151Z" }, - { url = "https://files.pythonhosted.org/packages/98/b9/803672bab3fe03cee2e14786ca056efda4bb511ea02dadcedde6176d06d0/mypy-1.17.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70401bbabd2fa1aa7c43bb358f54037baf0586f41e83b0ae67dd0534fc64edfd", size = 11832883, upload-time = "2025-07-31T07:53:47.948Z" }, - { url = "https://files.pythonhosted.org/packages/88/fb/fcdac695beca66800918c18697b48833a9a6701de288452b6715a98cfee1/mypy-1.17.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e92bdc656b7757c438660f775f872a669b8ff374edc4d18277d86b63edba6b8b", size = 12566215, upload-time = "2025-07-31T07:54:04.031Z" }, - { url = "https://files.pythonhosted.org/packages/7f/37/a932da3d3dace99ee8eb2043b6ab03b6768c36eb29a02f98f46c18c0da0e/mypy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c1fdf4abb29ed1cb091cf432979e162c208a5ac676ce35010373ff29247bcad5", size = 12751956, upload-time = "2025-07-31T07:53:36.263Z" }, - { url = "https://files.pythonhosted.org/packages/8c/cf/6438a429e0f2f5cab8bc83e53dbebfa666476f40ee322e13cac5e64b79e7/mypy-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:ff2933428516ab63f961644bc49bc4cbe42bbffb2cd3b71cc7277c07d16b1a8b", size = 9507307, upload-time = "2025-07-31T07:53:59.734Z" }, - { url = "https://files.pythonhosted.org/packages/17/a2/7034d0d61af8098ec47902108553122baa0f438df8a713be860f7407c9e6/mypy-1.17.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:69e83ea6553a3ba79c08c6e15dbd9bfa912ec1e493bf75489ef93beb65209aeb", size = 11086295, upload-time = "2025-07-31T07:53:28.124Z" }, - { url = "https://files.pythonhosted.org/packages/14/1f/19e7e44b594d4b12f6ba8064dbe136505cec813549ca3e5191e40b1d3cc2/mypy-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1b16708a66d38abb1e6b5702f5c2c87e133289da36f6a1d15f6a5221085c6403", size = 10112355, upload-time = "2025-07-31T07:53:21.121Z" }, - { url = "https://files.pythonhosted.org/packages/5b/69/baa33927e29e6b4c55d798a9d44db5d394072eef2bdc18c3e2048c9ed1e9/mypy-1.17.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:89e972c0035e9e05823907ad5398c5a73b9f47a002b22359b177d40bdaee7056", size = 11875285, upload-time = "2025-07-31T07:53:55.293Z" }, - { url = "https://files.pythonhosted.org/packages/90/13/f3a89c76b0a41e19490b01e7069713a30949d9a6c147289ee1521bcea245/mypy-1.17.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03b6d0ed2b188e35ee6d5c36b5580cffd6da23319991c49ab5556c023ccf1341", size = 12737895, upload-time = "2025-07-31T07:53:43.623Z" }, - { url = "https://files.pythonhosted.org/packages/23/a1/c4ee79ac484241301564072e6476c5a5be2590bc2e7bfd28220033d2ef8f/mypy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c837b896b37cd103570d776bda106eabb8737aa6dd4f248451aecf53030cdbeb", size = 12931025, upload-time = "2025-07-31T07:54:17.125Z" }, - { url = "https://files.pythonhosted.org/packages/89/b8/7409477be7919a0608900e6320b155c72caab4fef46427c5cc75f85edadd/mypy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:665afab0963a4b39dff7c1fa563cc8b11ecff7910206db4b2e64dd1ba25aed19", size = 9584664, upload-time = "2025-07-31T07:54:12.842Z" }, - { url = "https://files.pythonhosted.org/packages/1d/f3/8fcd2af0f5b806f6cf463efaffd3c9548a28f84220493ecd38d127b6b66d/mypy-1.17.1-py3-none-any.whl", hash = "sha256:a9f52c0351c21fe24c21d8c0eb1f62967b262d6729393397b6f443c3b773c3b9", size = 2283411, upload-time = "2025-07-31T07:53:24.664Z" }, + { url = "https://files.pythonhosted.org/packages/88/87/cafd3ae563f88f94eec33f35ff722d043e09832ea8530ef149ec1efbaf08/mypy-1.18.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:807d9315ab9d464125aa9fcf6d84fde6e1dc67da0b6f80e7405506b8ac72bc7f", size = 12731198, upload-time = "2025-09-19T00:09:44.857Z" }, + { url = "https://files.pythonhosted.org/packages/0f/e0/1e96c3d4266a06d4b0197ace5356d67d937d8358e2ee3ffac71faa843724/mypy-1.18.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:776bb00de1778caf4db739c6e83919c1d85a448f71979b6a0edd774ea8399341", size = 11817879, upload-time = "2025-09-19T00:09:47.131Z" }, + { url = "https://files.pythonhosted.org/packages/72/ef/0c9ba89eb03453e76bdac5a78b08260a848c7bfc5d6603634774d9cd9525/mypy-1.18.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1379451880512ffce14505493bd9fe469e0697543717298242574882cf8cdb8d", size = 12427292, upload-time = "2025-09-19T00:10:22.472Z" }, + { url = "https://files.pythonhosted.org/packages/1a/52/ec4a061dd599eb8179d5411d99775bec2a20542505988f40fc2fee781068/mypy-1.18.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1331eb7fd110d60c24999893320967594ff84c38ac6d19e0a76c5fd809a84c86", size = 13163750, upload-time = "2025-09-19T00:09:51.472Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5f/2cf2ceb3b36372d51568f2208c021870fe7834cf3186b653ac6446511839/mypy-1.18.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ca30b50a51e7ba93b00422e486cbb124f1c56a535e20eff7b2d6ab72b3b2e37", size = 13351827, upload-time = "2025-09-19T00:09:58.311Z" }, + { url = "https://files.pythonhosted.org/packages/c8/7d/2697b930179e7277529eaaec1513f8de622818696857f689e4a5432e5e27/mypy-1.18.2-cp311-cp311-win_amd64.whl", hash = "sha256:664dc726e67fa54e14536f6e1224bcfce1d9e5ac02426d2326e2bb4e081d1ce8", size = 9757983, upload-time = "2025-09-19T00:10:09.071Z" }, + { url = "https://files.pythonhosted.org/packages/07/06/dfdd2bc60c66611dd8335f463818514733bc763e4760dee289dcc33df709/mypy-1.18.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:33eca32dd124b29400c31d7cf784e795b050ace0e1f91b8dc035672725617e34", size = 12908273, upload-time = "2025-09-19T00:10:58.321Z" }, + { url = "https://files.pythonhosted.org/packages/81/14/6a9de6d13a122d5608e1a04130724caf9170333ac5a924e10f670687d3eb/mypy-1.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3c47adf30d65e89b2dcd2fa32f3aeb5e94ca970d2c15fcb25e297871c8e4764", size = 11920910, upload-time = "2025-09-19T00:10:20.043Z" }, + { url = "https://files.pythonhosted.org/packages/5f/a9/b29de53e42f18e8cc547e38daa9dfa132ffdc64f7250e353f5c8cdd44bee/mypy-1.18.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d6c838e831a062f5f29d11c9057c6009f60cb294fea33a98422688181fe2893", size = 12465585, upload-time = "2025-09-19T00:10:33.005Z" }, + { url = "https://files.pythonhosted.org/packages/77/ae/6c3d2c7c61ff21f2bee938c917616c92ebf852f015fb55917fd6e2811db2/mypy-1.18.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01199871b6110a2ce984bde85acd481232d17413868c9807e95c1b0739a58914", size = 13348562, upload-time = "2025-09-19T00:10:11.51Z" }, + { url = "https://files.pythonhosted.org/packages/4d/31/aec68ab3b4aebdf8f36d191b0685d99faa899ab990753ca0fee60fb99511/mypy-1.18.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a2afc0fa0b0e91b4599ddfe0f91e2c26c2b5a5ab263737e998d6817874c5f7c8", size = 13533296, upload-time = "2025-09-19T00:10:06.568Z" }, + { url = "https://files.pythonhosted.org/packages/9f/83/abcb3ad9478fca3ebeb6a5358bb0b22c95ea42b43b7789c7fb1297ca44f4/mypy-1.18.2-cp312-cp312-win_amd64.whl", hash = "sha256:d8068d0afe682c7c4897c0f7ce84ea77f6de953262b12d07038f4d296d547074", size = 9828828, upload-time = "2025-09-19T00:10:28.203Z" }, + { url = "https://files.pythonhosted.org/packages/87/e3/be76d87158ebafa0309946c4a73831974d4d6ab4f4ef40c3b53a385a66fd/mypy-1.18.2-py3-none-any.whl", hash = "sha256:22a1748707dd62b58d2ae53562ffc4d7f8bcc727e8ac7cbc69c053ddc874d47e", size = 2352367, upload-time = "2025-09-19T00:10:15.489Z" }, ] [[package]] @@ -1172,39 +1174,39 @@ wheels = [ [[package]] name = "numpy" -version = "2.3.2" +version = "2.3.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/37/7d/3fec4199c5ffb892bed55cff901e4f39a58c81df9c44c280499e92cad264/numpy-2.3.2.tar.gz", hash = "sha256:e0486a11ec30cdecb53f184d496d1c6a20786c81e55e41640270130056f8ee48", size = 20489306, upload-time = "2025-07-24T21:32:07.553Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/19/95b3d357407220ed24c139018d2518fab0a61a948e68286a25f1a4d049ff/numpy-2.3.3.tar.gz", hash = "sha256:ddc7c39727ba62b80dfdbedf400d1c10ddfa8eefbd7ec8dcb118be8b56d31029", size = 20576648, upload-time = "2025-09-09T16:54:12.543Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/96/26/1320083986108998bd487e2931eed2aeedf914b6e8905431487543ec911d/numpy-2.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:852ae5bed3478b92f093e30f785c98e0cb62fa0a939ed057c31716e18a7a22b9", size = 21259016, upload-time = "2025-07-24T20:24:35.214Z" }, - { url = "https://files.pythonhosted.org/packages/c4/2b/792b341463fa93fc7e55abbdbe87dac316c5b8cb5e94fb7a59fb6fa0cda5/numpy-2.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a0e27186e781a69959d0230dd9909b5e26024f8da10683bd6344baea1885168", size = 14451158, upload-time = "2025-07-24T20:24:58.397Z" }, - { url = "https://files.pythonhosted.org/packages/b7/13/e792d7209261afb0c9f4759ffef6135b35c77c6349a151f488f531d13595/numpy-2.3.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:f0a1a8476ad77a228e41619af2fa9505cf69df928e9aaa165746584ea17fed2b", size = 5379817, upload-time = "2025-07-24T20:25:07.746Z" }, - { url = "https://files.pythonhosted.org/packages/49/ce/055274fcba4107c022b2113a213c7287346563f48d62e8d2a5176ad93217/numpy-2.3.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:cbc95b3813920145032412f7e33d12080f11dc776262df1712e1638207dde9e8", size = 6913606, upload-time = "2025-07-24T20:25:18.84Z" }, - { url = "https://files.pythonhosted.org/packages/17/f2/e4d72e6bc5ff01e2ab613dc198d560714971900c03674b41947e38606502/numpy-2.3.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f75018be4980a7324edc5930fe39aa391d5734531b1926968605416ff58c332d", size = 14589652, upload-time = "2025-07-24T20:25:40.356Z" }, - { url = "https://files.pythonhosted.org/packages/c8/b0/fbeee3000a51ebf7222016e2939b5c5ecf8000a19555d04a18f1e02521b8/numpy-2.3.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20b8200721840f5621b7bd03f8dcd78de33ec522fc40dc2641aa09537df010c3", size = 16938816, upload-time = "2025-07-24T20:26:05.721Z" }, - { url = "https://files.pythonhosted.org/packages/a9/ec/2f6c45c3484cc159621ea8fc000ac5a86f1575f090cac78ac27193ce82cd/numpy-2.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f91e5c028504660d606340a084db4b216567ded1056ea2b4be4f9d10b67197f", size = 16370512, upload-time = "2025-07-24T20:26:30.545Z" }, - { url = "https://files.pythonhosted.org/packages/b5/01/dd67cf511850bd7aefd6347aaae0956ed415abea741ae107834aae7d6d4e/numpy-2.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:fb1752a3bb9a3ad2d6b090b88a9a0ae1cd6f004ef95f75825e2f382c183b2097", size = 18884947, upload-time = "2025-07-24T20:26:58.24Z" }, - { url = "https://files.pythonhosted.org/packages/a7/17/2cf60fd3e6a61d006778735edf67a222787a8c1a7842aed43ef96d777446/numpy-2.3.2-cp311-cp311-win32.whl", hash = "sha256:4ae6863868aaee2f57503c7a5052b3a2807cf7a3914475e637a0ecd366ced220", size = 6599494, upload-time = "2025-07-24T20:27:09.786Z" }, - { url = "https://files.pythonhosted.org/packages/d5/03/0eade211c504bda872a594f045f98ddcc6caef2b7c63610946845e304d3f/numpy-2.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:240259d6564f1c65424bcd10f435145a7644a65a6811cfc3201c4a429ba79170", size = 13087889, upload-time = "2025-07-24T20:27:29.558Z" }, - { url = "https://files.pythonhosted.org/packages/13/32/2c7979d39dafb2a25087e12310fc7f3b9d3c7d960df4f4bc97955ae0ce1d/numpy-2.3.2-cp311-cp311-win_arm64.whl", hash = "sha256:4209f874d45f921bde2cff1ffcd8a3695f545ad2ffbef6d3d3c6768162efab89", size = 10459560, upload-time = "2025-07-24T20:27:46.803Z" }, - { url = "https://files.pythonhosted.org/packages/00/6d/745dd1c1c5c284d17725e5c802ca4d45cfc6803519d777f087b71c9f4069/numpy-2.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bc3186bea41fae9d8e90c2b4fb5f0a1f5a690682da79b92574d63f56b529080b", size = 20956420, upload-time = "2025-07-24T20:28:18.002Z" }, - { url = "https://files.pythonhosted.org/packages/bc/96/e7b533ea5740641dd62b07a790af5d9d8fec36000b8e2d0472bd7574105f/numpy-2.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f4f0215edb189048a3c03bd5b19345bdfa7b45a7a6f72ae5945d2a28272727f", size = 14184660, upload-time = "2025-07-24T20:28:39.522Z" }, - { url = "https://files.pythonhosted.org/packages/2b/53/102c6122db45a62aa20d1b18c9986f67e6b97e0d6fbc1ae13e3e4c84430c/numpy-2.3.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:8b1224a734cd509f70816455c3cffe13a4f599b1bf7130f913ba0e2c0b2006c0", size = 5113382, upload-time = "2025-07-24T20:28:48.544Z" }, - { url = "https://files.pythonhosted.org/packages/2b/21/376257efcbf63e624250717e82b4fae93d60178f09eb03ed766dbb48ec9c/numpy-2.3.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3dcf02866b977a38ba3ec10215220609ab9667378a9e2150615673f3ffd6c73b", size = 6647258, upload-time = "2025-07-24T20:28:59.104Z" }, - { url = "https://files.pythonhosted.org/packages/91/ba/f4ebf257f08affa464fe6036e13f2bf9d4642a40228781dc1235da81be9f/numpy-2.3.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:572d5512df5470f50ada8d1972c5f1082d9a0b7aa5944db8084077570cf98370", size = 14281409, upload-time = "2025-07-24T20:40:30.298Z" }, - { url = "https://files.pythonhosted.org/packages/59/ef/f96536f1df42c668cbacb727a8c6da7afc9c05ece6d558927fb1722693e1/numpy-2.3.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8145dd6d10df13c559d1e4314df29695613575183fa2e2d11fac4c208c8a1f73", size = 16641317, upload-time = "2025-07-24T20:40:56.625Z" }, - { url = "https://files.pythonhosted.org/packages/f6/a7/af813a7b4f9a42f498dde8a4c6fcbff8100eed00182cc91dbaf095645f38/numpy-2.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:103ea7063fa624af04a791c39f97070bf93b96d7af7eb23530cd087dc8dbe9dc", size = 16056262, upload-time = "2025-07-24T20:41:20.797Z" }, - { url = "https://files.pythonhosted.org/packages/8b/5d/41c4ef8404caaa7f05ed1cfb06afe16a25895260eacbd29b4d84dff2920b/numpy-2.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc927d7f289d14f5e037be917539620603294454130b6de200091e23d27dc9be", size = 18579342, upload-time = "2025-07-24T20:41:50.753Z" }, - { url = "https://files.pythonhosted.org/packages/a1/4f/9950e44c5a11636f4a3af6e825ec23003475cc9a466edb7a759ed3ea63bd/numpy-2.3.2-cp312-cp312-win32.whl", hash = "sha256:d95f59afe7f808c103be692175008bab926b59309ade3e6d25009e9a171f7036", size = 6320610, upload-time = "2025-07-24T20:42:01.551Z" }, - { url = "https://files.pythonhosted.org/packages/7c/2f/244643a5ce54a94f0a9a2ab578189c061e4a87c002e037b0829dd77293b6/numpy-2.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:9e196ade2400c0c737d93465327d1ae7c06c7cb8a1756121ebf54b06ca183c7f", size = 12786292, upload-time = "2025-07-24T20:42:20.738Z" }, - { url = "https://files.pythonhosted.org/packages/54/cd/7b5f49d5d78db7badab22d8323c1b6ae458fbf86c4fdfa194ab3cd4eb39b/numpy-2.3.2-cp312-cp312-win_arm64.whl", hash = "sha256:ee807923782faaf60d0d7331f5e86da7d5e3079e28b291973c545476c2b00d07", size = 10194071, upload-time = "2025-07-24T20:42:36.657Z" }, - { url = "https://files.pythonhosted.org/packages/cf/ea/50ebc91d28b275b23b7128ef25c3d08152bc4068f42742867e07a870a42a/numpy-2.3.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:14a91ebac98813a49bc6aa1a0dfc09513dcec1d97eaf31ca21a87221a1cdcb15", size = 21130338, upload-time = "2025-07-24T20:57:54.37Z" }, - { url = "https://files.pythonhosted.org/packages/9f/57/cdd5eac00dd5f137277355c318a955c0d8fb8aa486020c22afd305f8b88f/numpy-2.3.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:71669b5daae692189540cffc4c439468d35a3f84f0c88b078ecd94337f6cb0ec", size = 14375776, upload-time = "2025-07-24T20:58:16.303Z" }, - { url = "https://files.pythonhosted.org/packages/83/85/27280c7f34fcd305c2209c0cdca4d70775e4859a9eaa92f850087f8dea50/numpy-2.3.2-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:69779198d9caee6e547adb933941ed7520f896fd9656834c300bdf4dd8642712", size = 5304882, upload-time = "2025-07-24T20:58:26.199Z" }, - { url = "https://files.pythonhosted.org/packages/48/b4/6500b24d278e15dd796f43824e69939d00981d37d9779e32499e823aa0aa/numpy-2.3.2-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:2c3271cc4097beb5a60f010bcc1cc204b300bb3eafb4399376418a83a1c6373c", size = 6818405, upload-time = "2025-07-24T20:58:37.341Z" }, - { url = "https://files.pythonhosted.org/packages/9b/c9/142c1e03f199d202da8e980c2496213509291b6024fd2735ad28ae7065c7/numpy-2.3.2-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8446acd11fe3dc1830568c941d44449fd5cb83068e5c70bd5a470d323d448296", size = 14419651, upload-time = "2025-07-24T20:58:59.048Z" }, - { url = "https://files.pythonhosted.org/packages/8b/95/8023e87cbea31a750a6c00ff9427d65ebc5fef104a136bfa69f76266d614/numpy-2.3.2-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aa098a5ab53fa407fded5870865c6275a5cd4101cfdef8d6fafc48286a96e981", size = 16760166, upload-time = "2025-07-24T21:28:56.38Z" }, - { url = "https://files.pythonhosted.org/packages/78/e3/6690b3f85a05506733c7e90b577e4762517404ea78bab2ca3a5cb1aeb78d/numpy-2.3.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6936aff90dda378c09bea075af0d9c675fe3a977a9d2402f95a87f440f59f619", size = 12977811, upload-time = "2025-07-24T21:29:18.234Z" }, + { url = "https://files.pythonhosted.org/packages/7a/45/e80d203ef6b267aa29b22714fb558930b27960a0c5ce3c19c999232bb3eb/numpy-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0ffc4f5caba7dfcbe944ed674b7eef683c7e94874046454bb79ed7ee0236f59d", size = 21259253, upload-time = "2025-09-09T15:56:02.094Z" }, + { url = "https://files.pythonhosted.org/packages/52/18/cf2c648fccf339e59302e00e5f2bc87725a3ce1992f30f3f78c9044d7c43/numpy-2.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e7e946c7170858a0295f79a60214424caac2ffdb0063d4d79cb681f9aa0aa569", size = 14450980, upload-time = "2025-09-09T15:56:05.926Z" }, + { url = "https://files.pythonhosted.org/packages/93/fb/9af1082bec870188c42a1c239839915b74a5099c392389ff04215dcee812/numpy-2.3.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:cd4260f64bc794c3390a63bf0728220dd1a68170c169088a1e0dfa2fde1be12f", size = 5379709, upload-time = "2025-09-09T15:56:07.95Z" }, + { url = "https://files.pythonhosted.org/packages/75/0f/bfd7abca52bcbf9a4a65abc83fe18ef01ccdeb37bfb28bbd6ad613447c79/numpy-2.3.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:f0ddb4b96a87b6728df9362135e764eac3cfa674499943ebc44ce96c478ab125", size = 6913923, upload-time = "2025-09-09T15:56:09.443Z" }, + { url = "https://files.pythonhosted.org/packages/79/55/d69adad255e87ab7afda1caf93ca997859092afeb697703e2f010f7c2e55/numpy-2.3.3-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:afd07d377f478344ec6ca2b8d4ca08ae8bd44706763d1efb56397de606393f48", size = 14589591, upload-time = "2025-09-09T15:56:11.234Z" }, + { url = "https://files.pythonhosted.org/packages/10/a2/010b0e27ddeacab7839957d7a8f00e91206e0c2c47abbb5f35a2630e5387/numpy-2.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bc92a5dedcc53857249ca51ef29f5e5f2f8c513e22cfb90faeb20343b8c6f7a6", size = 16938714, upload-time = "2025-09-09T15:56:14.637Z" }, + { url = "https://files.pythonhosted.org/packages/1c/6b/12ce8ede632c7126eb2762b9e15e18e204b81725b81f35176eac14dc5b82/numpy-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7af05ed4dc19f308e1d9fc759f36f21921eb7bbfc82843eeec6b2a2863a0aefa", size = 16370592, upload-time = "2025-09-09T15:56:17.285Z" }, + { url = "https://files.pythonhosted.org/packages/b4/35/aba8568b2593067bb6a8fe4c52babb23b4c3b9c80e1b49dff03a09925e4a/numpy-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:433bf137e338677cebdd5beac0199ac84712ad9d630b74eceeb759eaa45ddf30", size = 18884474, upload-time = "2025-09-09T15:56:20.943Z" }, + { url = "https://files.pythonhosted.org/packages/45/fa/7f43ba10c77575e8be7b0138d107e4f44ca4a1ef322cd16980ea3e8b8222/numpy-2.3.3-cp311-cp311-win32.whl", hash = "sha256:eb63d443d7b4ffd1e873f8155260d7f58e7e4b095961b01c91062935c2491e57", size = 6599794, upload-time = "2025-09-09T15:56:23.258Z" }, + { url = "https://files.pythonhosted.org/packages/0a/a2/a4f78cb2241fe5664a22a10332f2be886dcdea8784c9f6a01c272da9b426/numpy-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:ec9d249840f6a565f58d8f913bccac2444235025bbb13e9a4681783572ee3caa", size = 13088104, upload-time = "2025-09-09T15:56:25.476Z" }, + { url = "https://files.pythonhosted.org/packages/79/64/e424e975adbd38282ebcd4891661965b78783de893b381cbc4832fb9beb2/numpy-2.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:74c2a948d02f88c11a3c075d9733f1ae67d97c6bdb97f2bb542f980458b257e7", size = 10460772, upload-time = "2025-09-09T15:56:27.679Z" }, + { url = "https://files.pythonhosted.org/packages/51/5d/bb7fc075b762c96329147799e1bcc9176ab07ca6375ea976c475482ad5b3/numpy-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cfdd09f9c84a1a934cde1eec2267f0a43a7cd44b2cca4ff95b7c0d14d144b0bf", size = 20957014, upload-time = "2025-09-09T15:56:29.966Z" }, + { url = "https://files.pythonhosted.org/packages/6b/0e/c6211bb92af26517acd52125a237a92afe9c3124c6a68d3b9f81b62a0568/numpy-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb32e3cf0f762aee47ad1ddc6672988f7f27045b0783c887190545baba73aa25", size = 14185220, upload-time = "2025-09-09T15:56:32.175Z" }, + { url = "https://files.pythonhosted.org/packages/22/f2/07bb754eb2ede9073f4054f7c0286b0d9d2e23982e090a80d478b26d35ca/numpy-2.3.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:396b254daeb0a57b1fe0ecb5e3cff6fa79a380fa97c8f7781a6d08cd429418fe", size = 5113918, upload-time = "2025-09-09T15:56:34.175Z" }, + { url = "https://files.pythonhosted.org/packages/81/0a/afa51697e9fb74642f231ea36aca80fa17c8fb89f7a82abd5174023c3960/numpy-2.3.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:067e3d7159a5d8f8a0b46ee11148fc35ca9b21f61e3c49fbd0a027450e65a33b", size = 6647922, upload-time = "2025-09-09T15:56:36.149Z" }, + { url = "https://files.pythonhosted.org/packages/5d/f5/122d9cdb3f51c520d150fef6e87df9279e33d19a9611a87c0d2cf78a89f4/numpy-2.3.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c02d0629d25d426585fb2e45a66154081b9fa677bc92a881ff1d216bc9919a8", size = 14281991, upload-time = "2025-09-09T15:56:40.548Z" }, + { url = "https://files.pythonhosted.org/packages/51/64/7de3c91e821a2debf77c92962ea3fe6ac2bc45d0778c1cbe15d4fce2fd94/numpy-2.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9192da52b9745f7f0766531dcfa978b7763916f158bb63bdb8a1eca0068ab20", size = 16641643, upload-time = "2025-09-09T15:56:43.343Z" }, + { url = "https://files.pythonhosted.org/packages/30/e4/961a5fa681502cd0d68907818b69f67542695b74e3ceaa513918103b7e80/numpy-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cd7de500a5b66319db419dc3c345244404a164beae0d0937283b907d8152e6ea", size = 16056787, upload-time = "2025-09-09T15:56:46.141Z" }, + { url = "https://files.pythonhosted.org/packages/99/26/92c912b966e47fbbdf2ad556cb17e3a3088e2e1292b9833be1dfa5361a1a/numpy-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:93d4962d8f82af58f0b2eb85daaf1b3ca23fe0a85d0be8f1f2b7bb46034e56d7", size = 18579598, upload-time = "2025-09-09T15:56:49.844Z" }, + { url = "https://files.pythonhosted.org/packages/17/b6/fc8f82cb3520768718834f310c37d96380d9dc61bfdaf05fe5c0b7653e01/numpy-2.3.3-cp312-cp312-win32.whl", hash = "sha256:5534ed6b92f9b7dca6c0a19d6df12d41c68b991cef051d108f6dbff3babc4ebf", size = 6320800, upload-time = "2025-09-09T15:56:52.499Z" }, + { url = "https://files.pythonhosted.org/packages/32/ee/de999f2625b80d043d6d2d628c07d0d5555a677a3cf78fdf868d409b8766/numpy-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:497d7cad08e7092dba36e3d296fe4c97708c93daf26643a1ae4b03f6294d30eb", size = 12786615, upload-time = "2025-09-09T15:56:54.422Z" }, + { url = "https://files.pythonhosted.org/packages/49/6e/b479032f8a43559c383acb20816644f5f91c88f633d9271ee84f3b3a996c/numpy-2.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:ca0309a18d4dfea6fc6262a66d06c26cfe4640c3926ceec90e57791a82b6eee5", size = 10195936, upload-time = "2025-09-09T15:56:56.541Z" }, + { url = "https://files.pythonhosted.org/packages/b8/f2/7e0a37cfced2644c9563c529f29fa28acbd0960dde32ece683aafa6f4949/numpy-2.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1e02c7159791cd481e1e6d5ddd766b62a4d5acf8df4d4d1afe35ee9c5c33a41e", size = 21131019, upload-time = "2025-09-09T15:58:42.838Z" }, + { url = "https://files.pythonhosted.org/packages/1a/7e/3291f505297ed63831135a6cc0f474da0c868a1f31b0dd9a9f03a7a0d2ed/numpy-2.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:dca2d0fc80b3893ae72197b39f69d55a3cd8b17ea1b50aa4c62de82419936150", size = 14376288, upload-time = "2025-09-09T15:58:45.425Z" }, + { url = "https://files.pythonhosted.org/packages/bf/4b/ae02e985bdeee73d7b5abdefeb98aef1207e96d4c0621ee0cf228ddfac3c/numpy-2.3.3-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:99683cbe0658f8271b333a1b1b4bb3173750ad59c0c61f5bbdc5b318918fffe3", size = 5305425, upload-time = "2025-09-09T15:58:48.6Z" }, + { url = "https://files.pythonhosted.org/packages/8b/eb/9df215d6d7250db32007941500dc51c48190be25f2401d5b2b564e467247/numpy-2.3.3-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:d9d537a39cc9de668e5cd0e25affb17aec17b577c6b3ae8a3d866b479fbe88d0", size = 6819053, upload-time = "2025-09-09T15:58:50.401Z" }, + { url = "https://files.pythonhosted.org/packages/57/62/208293d7d6b2a8998a4a1f23ac758648c3c32182d4ce4346062018362e29/numpy-2.3.3-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8596ba2f8af5f93b01d97563832686d20206d303024777f6dfc2e7c7c3f1850e", size = 14420354, upload-time = "2025-09-09T15:58:52.704Z" }, + { url = "https://files.pythonhosted.org/packages/ed/0c/8e86e0ff7072e14a71b4c6af63175e40d1e7e933ce9b9e9f765a95b4e0c3/numpy-2.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1ec5615b05369925bd1125f27df33f3b6c8bc10d788d5999ecd8769a1fa04db", size = 16760413, upload-time = "2025-09-09T15:58:55.027Z" }, + { url = "https://files.pythonhosted.org/packages/af/11/0cc63f9f321ccf63886ac203336777140011fb669e739da36d8db3c53b98/numpy-2.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:2e267c7da5bf7309670523896df97f93f6e469fb931161f483cd6882b3b1a5dc", size = 12971844, upload-time = "2025-09-09T15:58:57.359Z" }, ] [[package]] @@ -1261,7 +1263,6 @@ dependencies = [ { name = "cffi" }, { name = "crcmod" }, { name = "cython" }, - { name = "dearpygui" }, { name = "future-fstrings" }, { name = "inputs" }, { name = "json-rpc" }, @@ -1337,6 +1338,7 @@ testing = [ { name = "ruff" }, ] tools = [ + { name = "dearpygui" }, { name = "metadrive-simulator", marker = "platform_machine != 'aarch64'" }, ] @@ -1353,7 +1355,7 @@ requires-dist = [ { name = "crcmod" }, { name = "cython" }, { name = "dbus-next", marker = "extra == 'dev'" }, - { name = "dearpygui", specifier = ">=2.1.0" }, + { name = "dearpygui", marker = "extra == 'tools'", specifier = ">=2.1.0" }, { name = "dictdiffer", marker = "extra == 'dev'" }, { name = "future-fstrings" }, { name = "hypothesis", marker = "extra == 'testing'", specifier = "==6.47.*" }, @@ -1397,7 +1399,7 @@ requires-dist = [ { name = "pywinctl", marker = "extra == 'dev'" }, { name = "pyzmq" }, { name = "qrcode" }, - { name = "raylib", marker = "extra == 'dev'" }, + { name = "raylib", marker = "extra == 'dev'", specifier = "==5.5.0.2" }, { name = "requests" }, { name = "ruff", marker = "extra == 'testing'" }, { name = "scons" }, @@ -1450,8 +1452,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 +1465,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 = [ @@ -1605,31 +1607,32 @@ wheels = [ [[package]] name = "protobuf" -version = "6.32.0" +version = "6.32.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c0/df/fb4a8eeea482eca989b51cffd274aac2ee24e825f0bf3cbce5281fa1567b/protobuf-6.32.0.tar.gz", hash = "sha256:a81439049127067fc49ec1d36e25c6ee1d1a2b7be930675f919258d03c04e7d2", size = 440614, upload-time = "2025-08-14T21:21:25.015Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/a4/cc17347aa2897568beece2e674674359f911d6fe21b0b8d6268cd42727ac/protobuf-6.32.1.tar.gz", hash = "sha256:ee2469e4a021474ab9baafea6cd070e5bf27c7d29433504ddea1a4ee5850f68d", size = 440635, upload-time = "2025-09-11T21:38:42.935Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/33/18/df8c87da2e47f4f1dcc5153a81cd6bca4e429803f4069a299e236e4dd510/protobuf-6.32.0-cp310-abi3-win32.whl", hash = "sha256:84f9e3c1ff6fb0308dbacb0950d8aa90694b0d0ee68e75719cb044b7078fe741", size = 424409, upload-time = "2025-08-14T21:21:12.366Z" }, - { url = "https://files.pythonhosted.org/packages/e1/59/0a820b7310f8139bd8d5a9388e6a38e1786d179d6f33998448609296c229/protobuf-6.32.0-cp310-abi3-win_amd64.whl", hash = "sha256:a8bdbb2f009cfc22a36d031f22a625a38b615b5e19e558a7b756b3279723e68e", size = 435735, upload-time = "2025-08-14T21:21:15.046Z" }, - { url = "https://files.pythonhosted.org/packages/cc/5b/0d421533c59c789e9c9894683efac582c06246bf24bb26b753b149bd88e4/protobuf-6.32.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d52691e5bee6c860fff9a1c86ad26a13afbeb4b168cd4445c922b7e2cf85aaf0", size = 426449, upload-time = "2025-08-14T21:21:16.687Z" }, - { url = "https://files.pythonhosted.org/packages/ec/7b/607764ebe6c7a23dcee06e054fd1de3d5841b7648a90fd6def9a3bb58c5e/protobuf-6.32.0-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:501fe6372fd1c8ea2a30b4d9be8f87955a64d6be9c88a973996cef5ef6f0abf1", size = 322869, upload-time = "2025-08-14T21:21:18.282Z" }, - { url = "https://files.pythonhosted.org/packages/40/01/2e730bd1c25392fc32e3268e02446f0d77cb51a2c3a8486b1798e34d5805/protobuf-6.32.0-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:75a2aab2bd1aeb1f5dc7c5f33bcb11d82ea8c055c9becbb41c26a8c43fd7092c", size = 322009, upload-time = "2025-08-14T21:21:19.893Z" }, - { url = "https://files.pythonhosted.org/packages/9c/f2/80ffc4677aac1bc3519b26bc7f7f5de7fce0ee2f7e36e59e27d8beb32dd1/protobuf-6.32.0-py3-none-any.whl", hash = "sha256:ba377e5b67b908c8f3072a57b63e2c6a4cbd18aea4ed98d2584350dbf46f2783", size = 169287, upload-time = "2025-08-14T21:21:23.515Z" }, + { url = "https://files.pythonhosted.org/packages/c0/98/645183ea03ab3995d29086b8bf4f7562ebd3d10c9a4b14ee3f20d47cfe50/protobuf-6.32.1-cp310-abi3-win32.whl", hash = "sha256:a8a32a84bc9f2aad712041b8b366190f71dde248926da517bde9e832e4412085", size = 424411, upload-time = "2025-09-11T21:38:27.427Z" }, + { url = "https://files.pythonhosted.org/packages/8c/f3/6f58f841f6ebafe076cebeae33fc336e900619d34b1c93e4b5c97a81fdfa/protobuf-6.32.1-cp310-abi3-win_amd64.whl", hash = "sha256:b00a7d8c25fa471f16bc8153d0e53d6c9e827f0953f3c09aaa4331c718cae5e1", size = 435738, upload-time = "2025-09-11T21:38:30.959Z" }, + { url = "https://files.pythonhosted.org/packages/10/56/a8a3f4e7190837139e68c7002ec749190a163af3e330f65d90309145a210/protobuf-6.32.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d8c7e6eb619ffdf105ee4ab76af5a68b60a9d0f66da3ea12d1640e6d8dab7281", size = 426454, upload-time = "2025-09-11T21:38:34.076Z" }, + { url = "https://files.pythonhosted.org/packages/3f/be/8dd0a927c559b37d7a6c8ab79034fd167dcc1f851595f2e641ad62be8643/protobuf-6.32.1-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:2f5b80a49e1eb7b86d85fcd23fe92df154b9730a725c3b38c4e43b9d77018bf4", size = 322874, upload-time = "2025-09-11T21:38:35.509Z" }, + { url = "https://files.pythonhosted.org/packages/5c/f6/88d77011b605ef979aace37b7703e4eefad066f7e84d935e5a696515c2dd/protobuf-6.32.1-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:b1864818300c297265c83a4982fd3169f97122c299f56a56e2445c3698d34710", size = 322013, upload-time = "2025-09-11T21:38:37.017Z" }, + { url = "https://files.pythonhosted.org/packages/97/b7/15cc7d93443d6c6a84626ae3258a91f4c6ac8c0edd5df35ea7658f71b79c/protobuf-6.32.1-py3-none-any.whl", hash = "sha256:2601b779fc7d32a866c6b4404f9d42a3f67c5b9f3f15b4db3cccabe06b95c346", size = 169289, upload-time = "2025-09-11T21:38:41.234Z" }, ] [[package]] name = "psutil" -version = "7.0.0" +version = "7.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2a/80/336820c1ad9286a4ded7e845b2eccfcb27851ab8ac6abece774a6ff4d3de/psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456", size = 497003, upload-time = "2025-02-13T21:54:07.946Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/31/4723d756b59344b643542936e37a31d1d3204bcdc42a7daa8ee9eb06fb50/psutil-7.1.0.tar.gz", hash = "sha256:655708b3c069387c8b77b072fc429a57d0e214221d01c0a772df7dfedcb3bcd2", size = 497660, upload-time = "2025-09-17T20:14:52.902Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ed/e6/2d26234410f8b8abdbf891c9da62bee396583f713fb9f3325a4760875d22/psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25", size = 238051, upload-time = "2025-02-13T21:54:12.36Z" }, - { url = "https://files.pythonhosted.org/packages/04/8b/30f930733afe425e3cbfc0e1468a30a18942350c1a8816acfade80c005c4/psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da", size = 239535, upload-time = "2025-02-13T21:54:16.07Z" }, - { url = "https://files.pythonhosted.org/packages/2a/ed/d362e84620dd22876b55389248e522338ed1bf134a5edd3b8231d7207f6d/psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91", size = 275004, upload-time = "2025-02-13T21:54:18.662Z" }, - { url = "https://files.pythonhosted.org/packages/bf/b9/b0eb3f3cbcb734d930fdf839431606844a825b23eaf9a6ab371edac8162c/psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34", size = 277986, upload-time = "2025-02-13T21:54:21.811Z" }, - { url = "https://files.pythonhosted.org/packages/eb/a2/709e0fe2f093556c17fbafda93ac032257242cabcc7ff3369e2cb76a97aa/psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993", size = 279544, upload-time = "2025-02-13T21:54:24.68Z" }, - { url = "https://files.pythonhosted.org/packages/50/e6/eecf58810b9d12e6427369784efe814a1eec0f492084ce8eb8f4d89d6d61/psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99", size = 241053, upload-time = "2025-02-13T21:54:34.31Z" }, - { url = "https://files.pythonhosted.org/packages/50/1b/6921afe68c74868b4c9fa424dad3be35b095e16687989ebbb50ce4fceb7c/psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553", size = 244885, upload-time = "2025-02-13T21:54:37.486Z" }, + { url = "https://files.pythonhosted.org/packages/46/62/ce4051019ee20ce0ed74432dd73a5bb087a6704284a470bb8adff69a0932/psutil-7.1.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:76168cef4397494250e9f4e73eb3752b146de1dd950040b29186d0cce1d5ca13", size = 245242, upload-time = "2025-09-17T20:14:56.126Z" }, + { url = "https://files.pythonhosted.org/packages/38/61/f76959fba841bf5b61123fbf4b650886dc4094c6858008b5bf73d9057216/psutil-7.1.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:5d007560c8c372efdff9e4579c2846d71de737e4605f611437255e81efcca2c5", size = 246682, upload-time = "2025-09-17T20:14:58.25Z" }, + { url = "https://files.pythonhosted.org/packages/88/7a/37c99d2e77ec30d63398ffa6a660450b8a62517cabe44b3e9bae97696e8d/psutil-7.1.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22e4454970b32472ce7deaa45d045b34d3648ce478e26a04c7e858a0a6e75ff3", size = 287994, upload-time = "2025-09-17T20:14:59.901Z" }, + { url = "https://files.pythonhosted.org/packages/9d/de/04c8c61232f7244aa0a4b9a9fbd63a89d5aeaf94b2fc9d1d16e2faa5cbb0/psutil-7.1.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c70e113920d51e89f212dd7be06219a9b88014e63a4cec69b684c327bc474e3", size = 291163, upload-time = "2025-09-17T20:15:01.481Z" }, + { url = "https://files.pythonhosted.org/packages/f4/58/c4f976234bf6d4737bc8c02a81192f045c307b72cf39c9e5c5a2d78927f6/psutil-7.1.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7d4a113425c037300de3ac8b331637293da9be9713855c4fc9d2d97436d7259d", size = 293625, upload-time = "2025-09-17T20:15:04.492Z" }, + { url = "https://files.pythonhosted.org/packages/79/87/157c8e7959ec39ced1b11cc93c730c4fb7f9d408569a6c59dbd92ceb35db/psutil-7.1.0-cp37-abi3-win32.whl", hash = "sha256:09ad740870c8d219ed8daae0ad3b726d3bf9a028a198e7f3080f6a1888b99bca", size = 244812, upload-time = "2025-09-17T20:15:07.462Z" }, + { url = "https://files.pythonhosted.org/packages/bf/e9/b44c4f697276a7a95b8e94d0e320a7bf7f3318521b23de69035540b39838/psutil-7.1.0-cp37-abi3-win_amd64.whl", hash = "sha256:57f5e987c36d3146c0dd2528cd42151cf96cd359b9d67cfff836995cc5df9a3d", size = 247965, upload-time = "2025-09-17T20:15:09.673Z" }, + { url = "https://files.pythonhosted.org/packages/26/65/1070a6e3c036f39142c2820c4b52e9243246fcfc3f96239ac84472ba361e/psutil-7.1.0-cp37-abi3-win_arm64.whl", hash = "sha256:6937cb68133e7c97b6cc9649a570c9a18ba0efebed46d8c5dae4c07fa1b67a07", size = 244971, upload-time = "2025-09-17T20:15:12.262Z" }, ] [[package]] @@ -1662,47 +1665,47 @@ sdist = { url = "https://files.pythonhosted.org/packages/65/ff/cdae0a8c2118a0de7 [[package]] name = "pycapnp" -version = "2.0.0" +version = "2.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9b/fb/54b46b52c1fa2acd9afd81bd05810c61bb1b05c6084c9625b64bc6d41843/pycapnp-2.0.0.tar.gz", hash = "sha256:503ab9b7b16773590ee226f2460408972c6b1c2cb2d819037115b919bef682be", size = 574848, upload-time = "2024-04-12T15:35:44.019Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9b/96/0b1696ed89950b34506594a2c1f0c19c31d91385dfee81295113e9f22442/pycapnp-2.2.0.tar.gz", hash = "sha256:06466a74e44858b38f920784a8fe74172463a8e73e8cb8298dfe02a95b8997d6", size = 707734, upload-time = "2025-09-13T16:42:04.868Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/82/cf311b1a9800b605759a38a0c337a55a639b685427364294e98a0f9b7306/pycapnp-2.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:829c7eb4e5f23dbcac25466110faf72a691035cf87c5d46e5053da15790e428d", size = 1673673, upload-time = "2024-04-12T15:33:32.211Z" }, - { url = "https://files.pythonhosted.org/packages/ae/55/4c03ca95c568776a1f637db9ffdcf302fb63f46e4d2a4f13edd8cb1a5f90/pycapnp-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dab60fbe3e4eaf99ec97918a0a776216c6c149b6d49261383d91c2201adb475d", size = 1513351, upload-time = "2024-04-12T15:33:35.156Z" }, - { url = "https://files.pythonhosted.org/packages/55/98/e4b2dea076f8a2575abc45cd879a91bc9aa975c69ae2ac1cab61d83c5087/pycapnp-2.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c48a0582078bb74d7326d28571db0b8e6919563365537a5a13e8f5360c12bfc", size = 4910666, upload-time = "2024-04-12T15:33:37.798Z" }, - { url = "https://files.pythonhosted.org/packages/1d/ee/3b5a182588f89074f4002fa6247e3f963bb85bc808acd1ac8deed91f1fa7/pycapnp-2.0.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bcb5ab54aff857e3711d2c0cc934194aaffacdeb3481daa56863daef07d27941", size = 5007434, upload-time = "2024-04-12T15:33:40.362Z" }, - { url = "https://files.pythonhosted.org/packages/c5/e9/515a2ca7fdc84d57c654280d0b71dfd782fd1773d384c0ec0d56dc6fc35b/pycapnp-2.0.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9600778036e6fe9dbea68f0c37678c5f4d561d2f2306b3cb741de5e1670ef2ae", size = 5188923, upload-time = "2024-04-12T15:33:42.33Z" }, - { url = "https://files.pythonhosted.org/packages/70/60/5db346e238985a526ba7589ed24f92195dad39e7dec9d85b17a567600b6f/pycapnp-2.0.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7278ba0262fab8c398e77d634ae7ba026866d44b52cbfc27262be8d396ecacd1", size = 5048105, upload-time = "2024-04-12T15:33:44.294Z" }, - { url = "https://files.pythonhosted.org/packages/e3/ff/02b4a87c9ff9793f26d8f3d95312d902d260c094f216d84e19528a506606/pycapnp-2.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23b2458d43c82302980a96518c96df257429204d2cc02bfff0c8cb6ebb371e01", size = 5063172, upload-time = "2024-04-12T15:33:46.954Z" }, - { url = "https://files.pythonhosted.org/packages/10/a1/35a7e14d765f99cfdcdfdcebc69bdf382f27016944470daa7a03c557f681/pycapnp-2.0.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dd7755cc3fedc2ad8cc7864a0729471ddeff10c184963fe0f3689e295130f1b2", size = 5408718, upload-time = "2024-04-12T15:33:49.587Z" }, - { url = "https://files.pythonhosted.org/packages/5c/59/8bc8a993c38808c6fd90b10becba8de4a54543e8441bd87ce44ef3b7eee4/pycapnp-2.0.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d47baf6b3db9981625ffc5ff188e089f2ebca8e7e1afb97aa5eb7bebb7bf3650", size = 5596714, upload-time = "2024-04-12T15:33:51.518Z" }, - { url = "https://files.pythonhosted.org/packages/ea/7d/79c481ef77f29e81355e92bb250f0d2a37a76f5fe0ba9433bf6c6c88b6e4/pycapnp-2.0.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:b375be92d93fdb6f7ac127ea9390bcec0fed4e485db137b084f9e7114dde7c83", size = 5709896, upload-time = "2024-04-12T15:33:53.716Z" }, - { url = "https://files.pythonhosted.org/packages/59/8d/f2eceeea1e8cae8b8a70a4752af5b772916f455e2ed388d0887e2b57d080/pycapnp-2.0.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:959bfdf1cddb3e5528e2293c4a375382be9a1bf044b073bc2e7eca1eb6b3a9a2", size = 5594823, upload-time = "2024-04-12T15:33:55.573Z" }, - { url = "https://files.pythonhosted.org/packages/2c/86/f8284637b61f83232e5618dd561a66080dd98ce2272d7e3ae89335d4fd97/pycapnp-2.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d873af167cf5cc7578ce5432eefcb442f866c8f7a6c57d188baf8c5e709fa39d", size = 5572564, upload-time = "2024-04-12T15:33:59.112Z" }, - { url = "https://files.pythonhosted.org/packages/b3/b2/7f99d28a9935d1e37ec6955922c57b2be24fe0b74fe25929643686cc11e5/pycapnp-2.0.0-cp311-cp311-win32.whl", hash = "sha256:40ca8018e0b7686d549b920f087049b92a3e6f06976d9f5a8112603fc560cac4", size = 1040268, upload-time = "2024-04-12T15:34:00.933Z" }, - { url = "https://files.pythonhosted.org/packages/1d/37/89ab98961f18cffeae20d98cfc24afcfa85024bc014ecc48b0c4ac264fe0/pycapnp-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:d15cd8e46d541a899c84809095d7d7b3951f43642d1859e7a39bd91910778479", size = 1141758, upload-time = "2024-04-12T15:34:02.607Z" }, - { url = "https://files.pythonhosted.org/packages/ce/d5/0ee84de3ce34a86c373b6cfbea17d5486c2ca942d51efa99a0069723c1e3/pycapnp-2.0.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:0c111ef96676df25b8afef98f369d45f838ad4434e2898e48199eb43ef704efe", size = 1645816, upload-time = "2024-04-12T15:34:04.428Z" }, - { url = "https://files.pythonhosted.org/packages/35/1e/580572083165ba791fac5ae2d8917facb94db6e3f0500421673f55165dac/pycapnp-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0d18906eb1fd1b9f206d93a9591ceedce1d52e7766b66e68f271453f104e9dca", size = 1507892, upload-time = "2024-04-12T15:34:06.933Z" }, - { url = "https://files.pythonhosted.org/packages/d7/ed/46b3cc5d32c525b6a3acb67eb43de2cec692a62775ec1ab66dafe2b7d6ad/pycapnp-2.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5d1ed365ab1beabb8838068907a7190cc0b6f16de3499d783627e670fcc0eb2", size = 4707960, upload-time = "2024-04-12T15:34:08.771Z" }, - { url = "https://files.pythonhosted.org/packages/8e/51/0a0a4d4e44138adb84959478ea4966196c5ad32022f768b9b64d1590cb3e/pycapnp-2.0.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:495b39a7aa2629931bbca27ad743ce591c6c41e8f81792276be424742d9cd1c1", size = 4791780, upload-time = "2024-04-12T15:34:10.863Z" }, - { url = "https://files.pythonhosted.org/packages/28/71/2b59c6ddb253b25b3d01ee6f7b32b0297ac205c7272beeb6d13399054430/pycapnp-2.0.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50e814fbde072dcc3d868b5b5cbb9b7a66a70bff9ad03942f3be9baf3ca1cfc6", size = 4961068, upload-time = "2024-04-12T15:34:13.543Z" }, - { url = "https://files.pythonhosted.org/packages/c3/b8/b64fdefa59d6d2802b5ee0a9439396c23a3e5954da6909be81f2722a234c/pycapnp-2.0.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:920fdda62d5fdef7a48339104dff0ceb9dcc21b138491f854457ba3a3d4d63ec", size = 4872917, upload-time = "2024-04-12T15:34:15.636Z" }, - { url = "https://files.pythonhosted.org/packages/c8/55/867595f575eb6cb3662e9a0b50a24b4be42df86f2938003e586f6c81606f/pycapnp-2.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f9142eb4714c152b09dda0b055ea9dd43fd8fd894132e7eb4fa235fb4915edd", size = 4912169, upload-time = "2024-04-12T15:34:17.758Z" }, - { url = "https://files.pythonhosted.org/packages/e4/11/0d36b45e5005ecdf8510081d16c6fb7b22b49651f64af36d138df97980cc/pycapnp-2.0.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c98f1d0c4d32109d03e42828ce3c65236afc895033633cbed3ca092993702e7b", size = 5201744, upload-time = "2024-04-12T15:34:20.468Z" }, - { url = "https://files.pythonhosted.org/packages/05/29/ad1357998656b7141939e55bb3aea727c7a5478026feed7f8ee8cf52c935/pycapnp-2.0.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4d3250c1875a309d67551843cd8bf3c5e7fccf159b7f5c118a92aee36c0e871c", size = 5351113, upload-time = "2024-04-12T15:34:23.173Z" }, - { url = "https://files.pythonhosted.org/packages/5a/b1/f4c442907948a29b6427dd7436f31d3732bb0d77f5c1dbcad749ba56dac0/pycapnp-2.0.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:174e6babe01f5507111c0ed226cd0b5e9325a9d2850751cfe4a57c1670f13881", size = 5472055, upload-time = "2024-04-12T15:34:25.799Z" }, - { url = "https://files.pythonhosted.org/packages/c1/06/a6eceb8b8015f518c0ccae1de5d1a6e18ed73b62b4b111aff54ce5f4f566/pycapnp-2.0.0-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:ed38ece414341285695526792e020f391f29f5064b2126d0367c8bdeef28e3e9", size = 5395743, upload-time = "2024-04-12T15:34:28.134Z" }, - { url = "https://files.pythonhosted.org/packages/e7/b0/63f2b0327853ae08158de61b4dfc7fa43ae5a5c00f1d28f769e7c30cdf55/pycapnp-2.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8a20b7dc55ef83a1fa446bf12680bce25caeb8f81788b623b072c3ec820db50d", size = 5405076, upload-time = "2024-04-12T15:34:30.305Z" }, - { url = "https://files.pythonhosted.org/packages/7d/24/e025dd95f1abf34e373fbab8841ac8e5fa62afe3af4a4b0c61bd01354400/pycapnp-2.0.0-cp312-cp312-win32.whl", hash = "sha256:145eea66233fb5ac9152cd1c06b999ddb691815126f87f5cc37b9cda5d569f8a", size = 1030361, upload-time = "2024-04-12T15:34:32.25Z" }, - { url = "https://files.pythonhosted.org/packages/3f/70/a71108ee9d4db9a027b665a2c383202407207174f1956195d5be45aca705/pycapnp-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:b8b03000769b29b36a8810f458b931f0f706f42027ee6676821eff28092d7734", size = 1135121, upload-time = "2024-04-12T15:34:34.208Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8d/11dd189691d78e2ca2200bf2a7677bd6bf7594f74ccc7b10ccafafd6761b/pycapnp-2.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1e000956e08cb75070aa35123cbeae73d045e0e6c6aa347a09d53769dc675fa9", size = 1645501, upload-time = "2025-09-13T16:39:36.828Z" }, + { url = "https://files.pythonhosted.org/packages/05/9f/321878b9aeea9eeac1977504a6daf46625a636ec99b687242528263fbbe3/pycapnp-2.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a45081bde2adaca6a5bf98ff949962d466f94114f6e98a0eab8f55d424786f24", size = 1509785, upload-time = "2025-09-13T16:39:38.623Z" }, + { url = "https://files.pythonhosted.org/packages/79/ec/7d6085792c75855785f8e7e97a35ea8746058124238479d4509f0a25852a/pycapnp-2.2.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:a1b10ab77d2a61bc033e74e0c90669222ede6f6b462eb8f1f97f592e6b547500", size = 5338620, upload-time = "2025-09-13T16:39:40.05Z" }, + { url = "https://files.pythonhosted.org/packages/3a/13/4244ffd7d513fca5e5c086251649586c05734923ee16b754cd22a85d7bda/pycapnp-2.2.0-cp311-cp311-manylinux_2_28_i686.whl", hash = "sha256:f325fb69a8a4181ca3591ec311f1ae854dd71a199f6f55761008fdab96544fbb", size = 5420575, upload-time = "2025-09-13T16:39:42.255Z" }, + { url = "https://files.pythonhosted.org/packages/c5/7f/b2f592836d870e1e35a2cc0d4a8ece4c877dbeb2e55b040e0c00b95a01c8/pycapnp-2.2.0-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:e61420b11178a856b962e5e4a89c1755e3b1677762e976a98aa628ad96b70776", size = 5835575, upload-time = "2025-09-13T16:39:43.85Z" }, + { url = "https://files.pythonhosted.org/packages/e4/36/76ce7c4da4141f7e75db0fd4e95d51328f1f583e3d30eb52ed7dcde91bcf/pycapnp-2.2.0-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:6b7b6a5f96ee95a8b33b85f7f87d18129bdba4e3c22cad3b6485b675607b1aeb", size = 5868584, upload-time = "2025-09-13T16:39:45.893Z" }, + { url = "https://files.pythonhosted.org/packages/4b/20/c83deb8734b2b0cc083344ede75b81f5af6466a7d95868adbaf14180c21d/pycapnp-2.2.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:d109f8e9d3b46fb28c64b1a87cae7f26447bf07253de1e7dfd057ee38b0654a4", size = 5530789, upload-time = "2025-09-13T16:39:47.63Z" }, + { url = "https://files.pythonhosted.org/packages/4c/5d/a2600534c492196cb01f102c515bdea3271352bb7d37c6910603b248ddb0/pycapnp-2.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:93db5b840a400c6c22ceb93b0494182d087a5a40f81cf179881f0f5bbcd6e439", size = 6228635, upload-time = "2025-09-13T16:39:49.366Z" }, + { url = "https://files.pythonhosted.org/packages/27/5b/2e21278f7746a36b29e2cde28b21ffc479dd09c2cb1ed963cf0f89e8caf8/pycapnp-2.2.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ab4fd171102c2d7222fd1e80fa190652a95a58e56dd731f6f7e81806ed5bc012", size = 6560947, upload-time = "2025-09-13T16:39:52.215Z" }, + { url = "https://files.pythonhosted.org/packages/74/4e/87bf0adb4644d45658e10eb97e08d16992a7864ff9816061a002b121da23/pycapnp-2.2.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:da4ea4932569279c1901e97cfa761754a7666389dfef5ae5df90ffa20de7566d", size = 6748953, upload-time = "2025-09-13T16:39:53.859Z" }, + { url = "https://files.pythonhosted.org/packages/14/09/8e2c007a9514793fb7a2f675212e4ebc31ae7e59d131c3d561f123793d0a/pycapnp-2.2.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:289da6a5329ec7ff323aa8d10f06709ea086111c3c4558774370907b8f43c7ba", size = 6779013, upload-time = "2025-09-13T16:39:55.931Z" }, + { url = "https://files.pythonhosted.org/packages/33/6f/747705eacdf75797feef5429ad42756ba6afcc51a96bc3f1627c3f2b11a2/pycapnp-2.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:16b7670c6a5720277823c7299401fc1009b62dffb5777e3b121ae66997a6674b", size = 6496769, upload-time = "2025-09-13T16:39:57.554Z" }, + { url = "https://files.pythonhosted.org/packages/5d/4a/d4c65b305a2156f4fe073e84121938780eacd9528d7c977cbc6f1659787e/pycapnp-2.2.0-cp311-cp311-win32.whl", hash = "sha256:ec226235a0677e9cfb020a847fd5ff553ef2de0d67d55e19c1ea4b313263a594", size = 1064932, upload-time = "2025-09-13T16:39:58.967Z" }, + { url = "https://files.pythonhosted.org/packages/e9/3f/66cc7cae9d02462ab633c443ca408003328156218480ab4a4c61272fbab2/pycapnp-2.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:402ec2aacf6256a0969eff6b8b28ab9f5ec5ba92b9cc6b7dffc41756043e15d0", size = 1220053, upload-time = "2025-09-13T16:40:00.89Z" }, + { url = "https://files.pythonhosted.org/packages/3e/79/3d82be14faf0ec38817e5b2e838905356ef801b8d45c40dcaf685d742142/pycapnp-2.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a4a96ebe3f854f5e0f57b4d3571a0fe802686f7fd8cfa603973ec692fb47abce", size = 1637264, upload-time = "2025-09-13T16:40:02.557Z" }, + { url = "https://files.pythonhosted.org/packages/20/b5/a31b28de6093a3907e33a3604e0a24d27c9a24ad420261d23dea519e4559/pycapnp-2.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:70426b32474f29101293dd4271ca9b309ae549422ffb3ba98eb0483897af5cbe", size = 1501144, upload-time = "2025-09-13T16:40:04.376Z" }, + { url = "https://files.pythonhosted.org/packages/cc/76/e2b2702fadeb2d9492ed6ef7df9f5d57d5bac1709108a73c072b279fb7db/pycapnp-2.2.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:fc9eff6b76a2414c377a77c927f187282bc7b83005adfc3df63b43e7cd676d39", size = 5295044, upload-time = "2025-09-13T16:40:06.283Z" }, + { url = "https://files.pythonhosted.org/packages/99/4d/16e8d8ccb4b8b63d4d2c0fd745ed88287de0323e596b091fa515e775fe4f/pycapnp-2.2.0-cp312-cp312-manylinux_2_28_i686.whl", hash = "sha256:a7af220f446e84373764c701197c25c1eabcc13832a7532911c191d81a61387f", size = 5343558, upload-time = "2025-09-13T16:40:07.85Z" }, + { url = "https://files.pythonhosted.org/packages/a2/ae/a570f5cc825b71510877ebe5b8041c437d8e0e9993d5b134efbc31daf844/pycapnp-2.2.0-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:c5caf1e6fcd9a068c7fafb286a915e75fe7585c9525eeac9e8a641cbd92f1982", size = 5667031, upload-time = "2025-09-13T16:40:09.489Z" }, + { url = "https://files.pythonhosted.org/packages/6d/ee/d03a871153e90846fe8ea43274f7e161294f0a443fcf6c784c78edf2ceb0/pycapnp-2.2.0-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:4456362dcc6acec2c801d817ad6e62a798a08aacb5e725a25f013d8318b6ce42", size = 5804310, upload-time = "2025-09-13T16:40:11.097Z" }, + { url = "https://files.pythonhosted.org/packages/3e/8e/fc827b28128dc3921462d563ae8981a8a9ae25bd8f49bc4fe83c0793f804/pycapnp-2.2.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:c66ba31fac33a1707d38eb0fe53aa29fd27ce6a2a4f71f415a1229c30706a75e", size = 5522994, upload-time = "2025-09-13T16:40:12.788Z" }, + { url = "https://files.pythonhosted.org/packages/4f/4d/4bc662dd058b9f79d5a8a691a56bf9941fbe4e340098cf7f9ae49c6c8761/pycapnp-2.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:33d0d1a286e1135e1913d4a3cb0a2c972a206b1984c3d23e87cdf11e1d523b70", size = 6145416, upload-time = "2025-09-13T16:40:14.444Z" }, + { url = "https://files.pythonhosted.org/packages/97/63/44cc5dc368ed833ecc0f20be8523842b965177f4ac16ff81a3663d2f46c3/pycapnp-2.2.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:63aeb83c042c84e5bd9bce9f699be30279d925ea00c3a92b3a4cb8e3f2cbe957", size = 6474037, upload-time = "2025-09-13T16:40:16.104Z" }, + { url = "https://files.pythonhosted.org/packages/95/39/52753dd2dbfc4874b0ac2321a629ccd1355d9470323632701d3b7c1ab0cc/pycapnp-2.2.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e9d025d04115d7341148ca654e144ddecbc9af7d215426e69fa6ec8036b1467f", size = 6604316, upload-time = "2025-09-13T16:40:17.868Z" }, + { url = "https://files.pythonhosted.org/packages/ca/a6/3f178b23a5b9f2afe2d12614f2eea740defa3dd0797397870c6f62952e69/pycapnp-2.2.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:51c658e633fa41fae616fcddebf048a505e3080378853b7e3e267858c2fd20f7", size = 6710383, upload-time = "2025-09-13T16:40:19.622Z" }, + { url = "https://files.pythonhosted.org/packages/ad/ed/934f2f2c8d678c76b5616b165a0277d49d0d40dbe8e3d62a7eb1bf9bac95/pycapnp-2.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5802b63303f66066e32b704d094e2629eff1d40add9ae7c4317753ab601456b4", size = 6445575, upload-time = "2025-09-13T16:40:21.826Z" }, + { url = "https://files.pythonhosted.org/packages/c9/42/cef707de9fbc2e70aa47a26e3c17444846bb7e19cd9ea51a6ed9dce4eced/pycapnp-2.2.0-cp312-cp312-win32.whl", hash = "sha256:e21f69ce0deac8dbd68424067560bfe31102ab6728d410bd66a34c9b7aaceba5", size = 1049436, upload-time = "2025-09-13T16:40:23.425Z" }, + { url = "https://files.pythonhosted.org/packages/17/c9/82dee36033c30c39e4860df82fc475ccc6323b075dfaa518092f1cc722f6/pycapnp-2.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:ddf0364c26028bff6e0f90da41ac28fae96703a74279a7ba6c179eaff794ecbf", size = 1188941, upload-time = "2025-09-13T16:40:24.766Z" }, ] [[package]] name = "pycparser" -version = "2.22" +version = "2.23" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736, upload-time = "2024-03-30T13:22:22.564Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552, upload-time = "2024-03-30T13:22:20.476Z" }, + { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, ] [[package]] @@ -1828,9 +1831,12 @@ wheels = [ [[package]] name = "pymsgbox" -version = "1.0.9" +version = "2.0.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7d/ff/4c6f31a4f08979f12a663f2aeb6c8b765d3bd592e66eaaac445f547bb875/PyMsgBox-1.0.9.tar.gz", hash = "sha256:2194227de8bff7a3d6da541848705a155dcbb2a06ee120d9f280a1d7f51263ff", size = 18829, upload-time = "2020-10-11T01:51:43.227Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/6a/e80da7594ee598a776972d09e2813df2b06b3bc29218f440631dfa7c78a8/pymsgbox-2.0.1.tar.gz", hash = "sha256:98d055c49a511dcc10fa08c3043e7102d468f5e4b3a83c6d3c61df722c7d798d", size = 20768, upload-time = "2025-09-09T00:38:56.863Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6f/3e/08c8cac81b2b2f7502746e6b9c8e5b0ec6432cd882c605560fc409aaf087/pymsgbox-2.0.1-py3-none-any.whl", hash = "sha256:5de8ec19bca2ca7e6c09d39c817c83f17c75cee80275235f43a9931db699f73b", size = 9994, upload-time = "2025-09-09T00:38:55.672Z" }, +] [[package]] name = "pyobjc" @@ -4201,9 +4207,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 = [ @@ -4233,18 +4239,21 @@ wheels = [ [[package]] name = "pyparsing" -version = "3.2.3" +version = "3.2.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bb/22/f1129e69d94ffff626bdb5c835506b3a5b4f3d070f17ea295e12c2c6f60f/pyparsing-3.2.3.tar.gz", hash = "sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be", size = 1088608, upload-time = "2025-03-25T05:01:28.114Z" } +sdist = { url = "https://files.pythonhosted.org/packages/98/c9/b4594e6a81371dfa9eb7a2c110ad682acf985d96115ae8b25a1d63b4bf3b/pyparsing-3.2.4.tar.gz", hash = "sha256:fff89494f45559d0f2ce46613b419f632bbb6afbdaed49696d322bcf98a58e99", size = 1098809, upload-time = "2025-09-13T05:47:19.732Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/05/e7/df2285f3d08fee213f2d041540fa4fc9ca6c2d44cf36d3a035bf2a8d2bcc/pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf", size = 111120, upload-time = "2025-03-25T05:01:24.908Z" }, + { url = "https://files.pythonhosted.org/packages/53/b8/fbab973592e23ae313042d450fc26fa24282ebffba21ba373786e1ce63b4/pyparsing-3.2.4-py3-none-any.whl", hash = "sha256:91d0fcde680d42cd031daf3a6ba20da3107e08a75de50da58360e7d94ab24d36", size = 113869, upload-time = "2025-09-13T05:47:17.863Z" }, ] [[package]] name = "pyperclip" -version = "1.9.0" +version = "1.10.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/30/23/2f0a3efc4d6a32f3b63cdff36cd398d9701d26cda58e3ab97ac79fb5e60d/pyperclip-1.9.0.tar.gz", hash = "sha256:b7de0142ddc81bfc5c7507eea19da920b92252b548b96186caf94a5e2527d310", size = 20961, upload-time = "2024-06-18T20:38:48.401Z" } +sdist = { url = "https://files.pythonhosted.org/packages/15/99/25f4898cf420efb6f45f519de018f4faea5391114a8618b16736ef3029f1/pyperclip-1.10.0.tar.gz", hash = "sha256:180c8346b1186921c75dfd14d9048a6b5d46bfc499778811952c6dd6eb1ca6be", size = 12193, upload-time = "2025-09-18T00:54:00.384Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/bc/22540e73c5f5ae18f02924cd3954a6c9a4aa6b713c841a94c98335d333a1/pyperclip-1.10.0-py3-none-any.whl", hash = "sha256:596fbe55dc59263bff26e61d2afbe10223e2fccb5210c9c96a28d6887cfcc7ec", size = 11062, upload-time = "2025-09-18T00:53:59.252Z" }, +] [[package]] name = "pyprof2calltree" @@ -4278,7 +4287,7 @@ wheels = [ [[package]] name = "pytest" -version = "8.4.1" +version = "8.4.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -4287,21 +4296,22 @@ dependencies = [ { name = "pluggy" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714, upload-time = "2025-06-18T05:48:06.109Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" }, + { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, ] [[package]] name = "pytest-asyncio" -version = "1.1.0" +version = "1.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pytest" }, + { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4e/51/f8794af39eeb870e87a8c8068642fc07bce0c854d6865d7dd0f2a9d338c2/pytest_asyncio-1.1.0.tar.gz", hash = "sha256:796aa822981e01b68c12e4827b8697108f7205020f24b5793b3c41555dab68ea", size = 46652, upload-time = "2025-07-16T04:29:26.393Z" } +sdist = { url = "https://files.pythonhosted.org/packages/42/86/9e3c5f48f7b7b638b216e4b9e645f54d199d7abbbab7a64a13b4e12ba10f/pytest_asyncio-1.2.0.tar.gz", hash = "sha256:c609a64a2a8768462d0c99811ddb8bd2583c33fd33cf7f21af1c142e824ffb57", size = 50119, upload-time = "2025-09-12T07:33:53.816Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/9d/bf86eddabf8c6c9cb1ea9a869d6873b46f105a5d292d3a6f7071f5b07935/pytest_asyncio-1.1.0-py3-none-any.whl", hash = "sha256:5fe2d69607b0bd75c656d1211f969cadba035030156745ee09e7d71740e58ecf", size = 15157, upload-time = "2025-07-16T04:29:24.929Z" }, + { url = "https://files.pythonhosted.org/packages/04/93/2fa34714b7a4ae72f2f8dad66ba17dd9a2c793220719e736dda28b7aec27/pytest_asyncio-1.2.0-py3-none-any.whl", hash = "sha256:8e17ae5e46d8e7efe51ab6494dd2010f4ca8dae51652aa3c8d55acf50bfb2e99", size = 15095, upload-time = "2025-09-12T07:33:52.639Z" }, ] [[package]] @@ -4318,26 +4328,26 @@ wheels = [ [[package]] name = "pytest-mock" -version = "3.14.1" +version = "3.15.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/71/28/67172c96ba684058a4d24ffe144d64783d2a270d0af0d9e792737bddc75c/pytest_mock-3.14.1.tar.gz", hash = "sha256:159e9edac4c451ce77a5cdb9fc5d1100708d2dd4ba3c3df572f14097351af80e", size = 33241, upload-time = "2025-05-26T13:58:45.167Z" } +sdist = { url = "https://files.pythonhosted.org/packages/68/14/eb014d26be205d38ad5ad20d9a80f7d201472e08167f0bb4361e251084a9/pytest_mock-3.15.1.tar.gz", hash = "sha256:1849a238f6f396da19762269de72cb1814ab44416fa73a8686deac10b0d87a0f", size = 34036, upload-time = "2025-09-16T16:37:27.081Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/05/77b60e520511c53d1c1ca75f1930c7dd8e971d0c4379b7f4b3f9644685ba/pytest_mock-3.14.1-py3-none-any.whl", hash = "sha256:178aefcd11307d874b4cd3100344e7e2d888d9791a6a1d9bfe90fbc1b74fd1d0", size = 9923, upload-time = "2025-05-26T13:58:43.487Z" }, + { url = "https://files.pythonhosted.org/packages/5a/cc/06253936f4a7fa2e0f48dfe6d851d9c56df896a9ab09ac019d70b760619c/pytest_mock-3.15.1-py3-none-any.whl", hash = "sha256:0a25e2eb88fe5168d535041d09a4529a188176ae608a6d249ee65abc0949630d", size = 10095, upload-time = "2025-09-16T16:37:25.734Z" }, ] [[package]] name = "pytest-randomly" -version = "3.16.0" +version = "4.0.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c0/68/d221ed7f4a2a49a664da721b8e87b52af6dd317af2a6cb51549cf17ac4b8/pytest_randomly-3.16.0.tar.gz", hash = "sha256:11bf4d23a26484de7860d82f726c0629837cf4064b79157bd18ec9d41d7feb26", size = 13367, upload-time = "2024-10-25T15:45:34.274Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c4/1d/258a4bf1109258c00c35043f40433be5c16647387b6e7cd5582d638c116b/pytest_randomly-4.0.1.tar.gz", hash = "sha256:174e57bb12ac2c26f3578188490bd333f0e80620c3f47340158a86eca0593cd8", size = 14130, upload-time = "2025-09-12T15:23:00.085Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/22/70/b31577d7c46d8e2f9baccfed5067dd8475262a2331ffb0bfdf19361c9bde/pytest_randomly-3.16.0-py3-none-any.whl", hash = "sha256:8633d332635a1a0983d3bba19342196807f6afb17c3eef78e02c2f85dade45d6", size = 8396, upload-time = "2024-10-25T15:45:32.78Z" }, + { url = "https://files.pythonhosted.org/packages/33/3e/a4a9227807b56869790aad3e24472a554b585974fe7e551ea350f50897ae/pytest_randomly-4.0.1-py3-none-any.whl", hash = "sha256:e0dfad2fd4f35e07beff1e47c17fbafcf98f9bf4531fd369d9260e2f858bfcb7", size = 8304, upload-time = "2025-09-12T15:22:58.946Z" }, ] [[package]] @@ -4379,7 +4389,7 @@ wheels = [ [[package]] name = "pytest-xdist" -version = "3.7.1.dev24+g2b4372b" +version = "3.7.1.dev24+g2b4372bd6" source = { git = "https://github.com/sshane/pytest-xdist?rev=2b4372bd62699fb412c4fe2f95bf9f01bd2018da#2b4372bd62699fb412c4fe2f95bf9f01bd2018da" } dependencies = [ { name = "execnet" }, @@ -4421,9 +4431,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 = [ @@ -4521,38 +4531,38 @@ wheels = [ [[package]] name = "pyzmq" -version = "27.0.2" +version = "27.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "implementation_name == 'pypy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f8/66/159f38d184f08b5f971b467f87b1ab142ab1320d5200825c824b32b84b66/pyzmq-27.0.2.tar.gz", hash = "sha256:b398dd713b18de89730447347e96a0240225e154db56e35b6bb8447ffdb07798", size = 281440, upload-time = "2025-08-21T04:23:26.334Z" } +sdist = { url = "https://files.pythonhosted.org/packages/04/0b/3c9baedbdf613ecaa7aa07027780b8867f57b6293b6ee50de316c9f3222b/pyzmq-27.1.0.tar.gz", hash = "sha256:ac0765e3d44455adb6ddbf4417dcce460fc40a05978c08efdf2948072f6db540", size = 281750, upload-time = "2025-09-08T23:10:18.157Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/42/73/034429ab0f4316bf433eb6c20c3f49d1dc13b2ed4e4d951b283d300a0f35/pyzmq-27.0.2-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:063845960df76599ad4fad69fa4d884b3ba38304272104fdcd7e3af33faeeb1d", size = 1333169, upload-time = "2025-08-21T04:21:12.483Z" }, - { url = "https://files.pythonhosted.org/packages/35/02/c42b3b526eb03a570c889eea85a5602797f800a50ba8b09ddbf7db568b78/pyzmq-27.0.2-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:845a35fb21b88786aeb38af8b271d41ab0967985410f35411a27eebdc578a076", size = 909176, upload-time = "2025-08-21T04:21:13.835Z" }, - { url = "https://files.pythonhosted.org/packages/1b/35/a1c0b988fabbdf2dc5fe94b7c2bcfd61e3533e5109297b8e0daf1d7a8d2d/pyzmq-27.0.2-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:515d20b5c3c86db95503faa989853a8ab692aab1e5336db011cd6d35626c4cb1", size = 668972, upload-time = "2025-08-21T04:21:15.315Z" }, - { url = "https://files.pythonhosted.org/packages/a0/63/908ac865da32ceaeecea72adceadad28ca25b23a2ca5ff018e5bff30116f/pyzmq-27.0.2-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:862aedec0b0684a5050cdb5ec13c2da96d2f8dffda48657ed35e312a4e31553b", size = 856962, upload-time = "2025-08-21T04:21:16.652Z" }, - { url = "https://files.pythonhosted.org/packages/2f/5a/90b3cc20b65cdf9391896fcfc15d8db21182eab810b7ea05a2986912fbe2/pyzmq-27.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2cb5bcfc51c7a4fce335d3bc974fd1d6a916abbcdd2b25f6e89d37b8def25f57", size = 1657712, upload-time = "2025-08-21T04:21:18.666Z" }, - { url = "https://files.pythonhosted.org/packages/c4/3c/32a5a80f9be4759325b8d7b22ce674bb87e586b4c80c6a9d77598b60d6f0/pyzmq-27.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:38ff75b2a36e3a032e9fef29a5871e3e1301a37464e09ba364e3c3193f62982a", size = 2035054, upload-time = "2025-08-21T04:21:20.073Z" }, - { url = "https://files.pythonhosted.org/packages/13/61/71084fe2ff2d7dc5713f8740d735336e87544845dae1207a8e2e16d9af90/pyzmq-27.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7a5709abe8d23ca158a9d0a18c037f4193f5b6afeb53be37173a41e9fb885792", size = 1894010, upload-time = "2025-08-21T04:21:21.96Z" }, - { url = "https://files.pythonhosted.org/packages/cb/6b/77169cfb13b696e50112ca496b2ed23c4b7d8860a1ec0ff3e4b9f9926221/pyzmq-27.0.2-cp311-cp311-win32.whl", hash = "sha256:47c5dda2018c35d87be9b83de0890cb92ac0791fd59498847fc4eca6ff56671d", size = 566819, upload-time = "2025-08-21T04:21:23.31Z" }, - { url = "https://files.pythonhosted.org/packages/37/cd/86c4083e0f811f48f11bc0ddf1e7d13ef37adfd2fd4f78f2445f1cc5dec0/pyzmq-27.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:f54ca3e98f8f4d23e989c7d0edcf9da7a514ff261edaf64d1d8653dd5feb0a8b", size = 633264, upload-time = "2025-08-21T04:21:24.761Z" }, - { url = "https://files.pythonhosted.org/packages/a0/69/5b8bb6a19a36a569fac02153a9e083738785892636270f5f68a915956aea/pyzmq-27.0.2-cp311-cp311-win_arm64.whl", hash = "sha256:2ef3067cb5b51b090fb853f423ad7ed63836ec154374282780a62eb866bf5768", size = 559316, upload-time = "2025-08-21T04:21:26.1Z" }, - { url = "https://files.pythonhosted.org/packages/68/69/b3a729e7b03e412bee2b1823ab8d22e20a92593634f664afd04c6c9d9ac0/pyzmq-27.0.2-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:5da05e3c22c95e23bfc4afeee6ff7d4be9ff2233ad6cb171a0e8257cd46b169a", size = 1305910, upload-time = "2025-08-21T04:21:27.609Z" }, - { url = "https://files.pythonhosted.org/packages/15/b7/f6a6a285193d489b223c340b38ee03a673467cb54914da21c3d7849f1b10/pyzmq-27.0.2-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:4e4520577971d01d47e2559bb3175fce1be9103b18621bf0b241abe0a933d040", size = 895507, upload-time = "2025-08-21T04:21:29.005Z" }, - { url = "https://files.pythonhosted.org/packages/17/e6/c4ed2da5ef9182cde1b1f5d0051a986e76339d71720ec1a00be0b49275ad/pyzmq-27.0.2-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:56d7de7bf73165b90bd25a8668659ccb134dd28449116bf3c7e9bab5cf8a8ec9", size = 652670, upload-time = "2025-08-21T04:21:30.71Z" }, - { url = "https://files.pythonhosted.org/packages/0e/66/d781ab0636570d32c745c4e389b1c6b713115905cca69ab6233508622edd/pyzmq-27.0.2-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:340e7cddc32f147c6c00d116a3f284ab07ee63dbd26c52be13b590520434533c", size = 840581, upload-time = "2025-08-21T04:21:32.008Z" }, - { url = "https://files.pythonhosted.org/packages/a6/df/f24790caf565d72544f5c8d8500960b9562c1dc848d6f22f3c7e122e73d4/pyzmq-27.0.2-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ba95693f9df8bb4a9826464fb0fe89033936f35fd4a8ff1edff09a473570afa0", size = 1641931, upload-time = "2025-08-21T04:21:33.371Z" }, - { url = "https://files.pythonhosted.org/packages/65/65/77d27b19fc5e845367f9100db90b9fce924f611b14770db480615944c9c9/pyzmq-27.0.2-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:ca42a6ce2d697537da34f77a1960d21476c6a4af3e539eddb2b114c3cf65a78c", size = 2021226, upload-time = "2025-08-21T04:21:35.301Z" }, - { url = "https://files.pythonhosted.org/packages/5b/65/1ed14421ba27a4207fa694772003a311d1142b7f543179e4d1099b7eb746/pyzmq-27.0.2-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3e44e665d78a07214b2772ccbd4b9bcc6d848d7895f1b2d7653f047b6318a4f6", size = 1878047, upload-time = "2025-08-21T04:21:36.749Z" }, - { url = "https://files.pythonhosted.org/packages/dd/dc/e578549b89b40dc78a387ec471c2a360766690c0a045cd8d1877d401012d/pyzmq-27.0.2-cp312-abi3-win32.whl", hash = "sha256:272d772d116615397d2be2b1417b3b8c8bc8671f93728c2f2c25002a4530e8f6", size = 558757, upload-time = "2025-08-21T04:21:38.2Z" }, - { url = "https://files.pythonhosted.org/packages/b5/89/06600980aefcc535c758414da969f37a5194ea4cdb73b745223f6af3acfb/pyzmq-27.0.2-cp312-abi3-win_amd64.whl", hash = "sha256:734be4f44efba0aa69bf5f015ed13eb69ff29bf0d17ea1e21588b095a3147b8e", size = 619281, upload-time = "2025-08-21T04:21:39.909Z" }, - { url = "https://files.pythonhosted.org/packages/30/84/df8a5c089552d17c9941d1aea4314b606edf1b1622361dae89aacedc6467/pyzmq-27.0.2-cp312-abi3-win_arm64.whl", hash = "sha256:41f0bd56d9279392810950feb2785a419c2920bbf007fdaaa7f4a07332ae492d", size = 552680, upload-time = "2025-08-21T04:21:41.571Z" }, - { url = "https://files.pythonhosted.org/packages/c7/60/027d0032a1e3b1aabcef0e309b9ff8a4099bdd5a60ab38b36a676ff2bd7b/pyzmq-27.0.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e297784aea724294fe95e442e39a4376c2f08aa4fae4161c669f047051e31b02", size = 836007, upload-time = "2025-08-21T04:23:00.447Z" }, - { url = "https://files.pythonhosted.org/packages/25/20/2ed1e6168aaea323df9bb2c451309291f53ba3af372ffc16edd4ce15b9e5/pyzmq-27.0.2-pp311-pypy311_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:e3659a79ded9745bc9c2aef5b444ac8805606e7bc50d2d2eb16dc3ab5483d91f", size = 799932, upload-time = "2025-08-21T04:23:02.052Z" }, - { url = "https://files.pythonhosted.org/packages/fd/25/5c147307de546b502c9373688ce5b25dc22288d23a1ebebe5d587bf77610/pyzmq-27.0.2-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3dba49ff037d02373a9306b58d6c1e0be031438f822044e8767afccfdac4c6b", size = 567459, upload-time = "2025-08-21T04:23:03.593Z" }, - { url = "https://files.pythonhosted.org/packages/71/06/0dc56ffc615c8095cd089c9b98ce5c733e990f09ce4e8eea4aaf1041a532/pyzmq-27.0.2-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de84e1694f9507b29e7b263453a2255a73e3d099d258db0f14539bad258abe41", size = 747088, upload-time = "2025-08-21T04:23:05.334Z" }, - { url = "https://files.pythonhosted.org/packages/06/f6/4a50187e023b8848edd3f0a8e197b1a7fb08d261d8c60aae7cb6c3d71612/pyzmq-27.0.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f0944d65ba2b872b9fcece08411d6347f15a874c775b4c3baae7f278550da0fb", size = 544639, upload-time = "2025-08-21T04:23:07.279Z" }, + { url = "https://files.pythonhosted.org/packages/06/5d/305323ba86b284e6fcb0d842d6adaa2999035f70f8c38a9b6d21ad28c3d4/pyzmq-27.1.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:226b091818d461a3bef763805e75685e478ac17e9008f49fce2d3e52b3d58b86", size = 1333328, upload-time = "2025-09-08T23:07:45.946Z" }, + { url = "https://files.pythonhosted.org/packages/bd/a0/fc7e78a23748ad5443ac3275943457e8452da67fda347e05260261108cbc/pyzmq-27.1.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0790a0161c281ca9723f804871b4027f2e8b5a528d357c8952d08cd1a9c15581", size = 908803, upload-time = "2025-09-08T23:07:47.551Z" }, + { url = "https://files.pythonhosted.org/packages/7e/22/37d15eb05f3bdfa4abea6f6d96eb3bb58585fbd3e4e0ded4e743bc650c97/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c895a6f35476b0c3a54e3eb6ccf41bf3018de937016e6e18748317f25d4e925f", size = 668836, upload-time = "2025-09-08T23:07:49.436Z" }, + { url = "https://files.pythonhosted.org/packages/b1/c4/2a6fe5111a01005fc7af3878259ce17684fabb8852815eda6225620f3c59/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bbf8d3630bf96550b3be8e1fc0fea5cbdc8d5466c1192887bd94869da17a63e", size = 857038, upload-time = "2025-09-08T23:07:51.234Z" }, + { url = "https://files.pythonhosted.org/packages/cb/eb/bfdcb41d0db9cd233d6fb22dc131583774135505ada800ebf14dfb0a7c40/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:15c8bd0fe0dabf808e2d7a681398c4e5ded70a551ab47482067a572c054c8e2e", size = 1657531, upload-time = "2025-09-08T23:07:52.795Z" }, + { url = "https://files.pythonhosted.org/packages/ab/21/e3180ca269ed4a0de5c34417dfe71a8ae80421198be83ee619a8a485b0c7/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:bafcb3dd171b4ae9f19ee6380dfc71ce0390fefaf26b504c0e5f628d7c8c54f2", size = 2034786, upload-time = "2025-09-08T23:07:55.047Z" }, + { url = "https://files.pythonhosted.org/packages/3b/b1/5e21d0b517434b7f33588ff76c177c5a167858cc38ef740608898cd329f2/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e829529fcaa09937189178115c49c504e69289abd39967cd8a4c215761373394", size = 1894220, upload-time = "2025-09-08T23:07:57.172Z" }, + { url = "https://files.pythonhosted.org/packages/03/f2/44913a6ff6941905efc24a1acf3d3cb6146b636c546c7406c38c49c403d4/pyzmq-27.1.0-cp311-cp311-win32.whl", hash = "sha256:6df079c47d5902af6db298ec92151db82ecb557af663098b92f2508c398bb54f", size = 567155, upload-time = "2025-09-08T23:07:59.05Z" }, + { url = "https://files.pythonhosted.org/packages/23/6d/d8d92a0eb270a925c9b4dd039c0b4dc10abc2fcbc48331788824ef113935/pyzmq-27.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:190cbf120fbc0fc4957b56866830def56628934a9d112aec0e2507aa6a032b97", size = 633428, upload-time = "2025-09-08T23:08:00.663Z" }, + { url = "https://files.pythonhosted.org/packages/ae/14/01afebc96c5abbbd713ecfc7469cfb1bc801c819a74ed5c9fad9a48801cb/pyzmq-27.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:eca6b47df11a132d1745eb3b5b5e557a7dae2c303277aa0e69c6ba91b8736e07", size = 559497, upload-time = "2025-09-08T23:08:02.15Z" }, + { url = "https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:452631b640340c928fa343801b0d07eb0c3789a5ffa843f6e1a9cee0ba4eb4fc", size = 1306279, upload-time = "2025-09-08T23:08:03.807Z" }, + { url = "https://files.pythonhosted.org/packages/e8/5e/c3c49fdd0f535ef45eefcc16934648e9e59dace4a37ee88fc53f6cd8e641/pyzmq-27.1.0-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1c179799b118e554b66da67d88ed66cd37a169f1f23b5d9f0a231b4e8d44a113", size = 895645, upload-time = "2025-09-08T23:08:05.301Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e5/b0b2504cb4e903a74dcf1ebae157f9e20ebb6ea76095f6cfffea28c42ecd/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3837439b7f99e60312f0c926a6ad437b067356dc2bc2ec96eb395fd0fe804233", size = 652574, upload-time = "2025-09-08T23:08:06.828Z" }, + { url = "https://files.pythonhosted.org/packages/f8/9b/c108cdb55560eaf253f0cbdb61b29971e9fb34d9c3499b0e96e4e60ed8a5/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43ad9a73e3da1fab5b0e7e13402f0b2fb934ae1c876c51d0afff0e7c052eca31", size = 840995, upload-time = "2025-09-08T23:08:08.396Z" }, + { url = "https://files.pythonhosted.org/packages/c2/bb/b79798ca177b9eb0825b4c9998c6af8cd2a7f15a6a1a4272c1d1a21d382f/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0de3028d69d4cdc475bfe47a6128eb38d8bc0e8f4d69646adfbcd840facbac28", size = 1642070, upload-time = "2025-09-08T23:08:09.989Z" }, + { url = "https://files.pythonhosted.org/packages/9c/80/2df2e7977c4ede24c79ae39dcef3899bfc5f34d1ca7a5b24f182c9b7a9ca/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:cf44a7763aea9298c0aa7dbf859f87ed7012de8bda0f3977b6fb1d96745df856", size = 2021121, upload-time = "2025-09-08T23:08:11.907Z" }, + { url = "https://files.pythonhosted.org/packages/46/bd/2d45ad24f5f5ae7e8d01525eb76786fa7557136555cac7d929880519e33a/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f30f395a9e6fbca195400ce833c731e7b64c3919aa481af4d88c3759e0cb7496", size = 1878550, upload-time = "2025-09-08T23:08:13.513Z" }, + { url = "https://files.pythonhosted.org/packages/e6/2f/104c0a3c778d7c2ab8190e9db4f62f0b6957b53c9d87db77c284b69f33ea/pyzmq-27.1.0-cp312-abi3-win32.whl", hash = "sha256:250e5436a4ba13885494412b3da5d518cd0d3a278a1ae640e113c073a5f88edd", size = 559184, upload-time = "2025-09-08T23:08:15.163Z" }, + { url = "https://files.pythonhosted.org/packages/fc/7f/a21b20d577e4100c6a41795842028235998a643b1ad406a6d4163ea8f53e/pyzmq-27.1.0-cp312-abi3-win_amd64.whl", hash = "sha256:9ce490cf1d2ca2ad84733aa1d69ce6855372cb5ce9223802450c9b2a7cba0ccf", size = 619480, upload-time = "2025-09-08T23:08:17.192Z" }, + { url = "https://files.pythonhosted.org/packages/78/c2/c012beae5f76b72f007a9e91ee9401cb88c51d0f83c6257a03e785c81cc2/pyzmq-27.1.0-cp312-abi3-win_arm64.whl", hash = "sha256:75a2f36223f0d535a0c919e23615fc85a1e23b71f40c7eb43d7b1dedb4d8f15f", size = 552993, upload-time = "2025-09-08T23:08:18.926Z" }, + { url = "https://files.pythonhosted.org/packages/4c/c6/c4dcdecdbaa70969ee1fdced6d7b8f60cfabe64d25361f27ac4665a70620/pyzmq-27.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:18770c8d3563715387139060d37859c02ce40718d1faf299abddcdcc6a649066", size = 836265, upload-time = "2025-09-08T23:09:49.376Z" }, + { url = "https://files.pythonhosted.org/packages/3e/79/f38c92eeaeb03a2ccc2ba9866f0439593bb08c5e3b714ac1d553e5c96e25/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:ac25465d42f92e990f8d8b0546b01c391ad431c3bf447683fdc40565941d0604", size = 800208, upload-time = "2025-09-08T23:09:51.073Z" }, + { url = "https://files.pythonhosted.org/packages/49/0e/3f0d0d335c6b3abb9b7b723776d0b21fa7f3a6c819a0db6097059aada160/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53b40f8ae006f2734ee7608d59ed661419f087521edbfc2149c3932e9c14808c", size = 567747, upload-time = "2025-09-08T23:09:52.698Z" }, + { url = "https://files.pythonhosted.org/packages/a1/cf/f2b3784d536250ffd4be70e049f3b60981235d70c6e8ce7e3ef21e1adb25/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f605d884e7c8be8fe1aa94e0a783bf3f591b84c24e4bc4f3e7564c82ac25e271", size = 747371, upload-time = "2025-09-08T23:09:54.563Z" }, + { url = "https://files.pythonhosted.org/packages/01/1b/5dbe84eefc86f48473947e2f41711aded97eecef1231f4558f1f02713c12/pyzmq-27.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c9f7f6e13dff2e44a6afeaf2cf54cee5929ad64afaf4d40b50f93c58fc687355", size = 544862, upload-time = "2025-09-08T23:09:56.509Z" }, ] [[package]] @@ -4655,28 +4665,28 @@ wheels = [ [[package]] name = "ruff" -version = "0.12.11" +version = "0.13.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/de/55/16ab6a7d88d93001e1ae4c34cbdcfb376652d761799459ff27c1dc20f6fa/ruff-0.12.11.tar.gz", hash = "sha256:c6b09ae8426a65bbee5425b9d0b82796dbb07cb1af045743c79bfb163001165d", size = 5347103, upload-time = "2025-08-28T13:59:08.87Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ab/33/c8e89216845615d14d2d42ba2bee404e7206a8db782f33400754f3799f05/ruff-0.13.1.tar.gz", hash = "sha256:88074c3849087f153d4bb22e92243ad4c1b366d7055f98726bc19aa08dc12d51", size = 5397987, upload-time = "2025-09-18T19:52:44.33Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d6/a2/3b3573e474de39a7a475f3fbaf36a25600bfeb238e1a90392799163b64a0/ruff-0.12.11-py3-none-linux_armv6l.whl", hash = "sha256:93fce71e1cac3a8bf9200e63a38ac5c078f3b6baebffb74ba5274fb2ab276065", size = 11979885, upload-time = "2025-08-28T13:58:26.654Z" }, - { url = "https://files.pythonhosted.org/packages/76/e4/235ad6d1785a2012d3ded2350fd9bc5c5af8c6f56820e696b0118dfe7d24/ruff-0.12.11-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b8e33ac7b28c772440afa80cebb972ffd823621ded90404f29e5ab6d1e2d4b93", size = 12742364, upload-time = "2025-08-28T13:58:30.256Z" }, - { url = "https://files.pythonhosted.org/packages/2c/0d/15b72c5fe6b1e402a543aa9d8960e0a7e19dfb079f5b0b424db48b7febab/ruff-0.12.11-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d69fb9d4937aa19adb2e9f058bc4fbfe986c2040acb1a4a9747734834eaa0bfd", size = 11920111, upload-time = "2025-08-28T13:58:33.677Z" }, - { url = "https://files.pythonhosted.org/packages/3e/c0/f66339d7893798ad3e17fa5a1e587d6fd9806f7c1c062b63f8b09dda6702/ruff-0.12.11-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:411954eca8464595077a93e580e2918d0a01a19317af0a72132283e28ae21bee", size = 12160060, upload-time = "2025-08-28T13:58:35.74Z" }, - { url = "https://files.pythonhosted.org/packages/03/69/9870368326db26f20c946205fb2d0008988aea552dbaec35fbacbb46efaa/ruff-0.12.11-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6a2c0a2e1a450f387bf2c6237c727dd22191ae8c00e448e0672d624b2bbd7fb0", size = 11799848, upload-time = "2025-08-28T13:58:38.051Z" }, - { url = "https://files.pythonhosted.org/packages/25/8c/dd2c7f990e9b3a8a55eee09d4e675027d31727ce33cdb29eab32d025bdc9/ruff-0.12.11-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ca4c3a7f937725fd2413c0e884b5248a19369ab9bdd850b5781348ba283f644", size = 13536288, upload-time = "2025-08-28T13:58:40.046Z" }, - { url = "https://files.pythonhosted.org/packages/7a/30/d5496fa09aba59b5e01ea76775a4c8897b13055884f56f1c35a4194c2297/ruff-0.12.11-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:4d1df0098124006f6a66ecf3581a7f7e754c4df7644b2e6704cd7ca80ff95211", size = 14490633, upload-time = "2025-08-28T13:58:42.285Z" }, - { url = "https://files.pythonhosted.org/packages/9b/2f/81f998180ad53445d403c386549d6946d0748e536d58fce5b5e173511183/ruff-0.12.11-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5a8dd5f230efc99a24ace3b77e3555d3fbc0343aeed3fc84c8d89e75ab2ff793", size = 13888430, upload-time = "2025-08-28T13:58:44.641Z" }, - { url = "https://files.pythonhosted.org/packages/87/71/23a0d1d5892a377478c61dbbcffe82a3476b050f38b5162171942a029ef3/ruff-0.12.11-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4dc75533039d0ed04cd33fb8ca9ac9620b99672fe7ff1533b6402206901c34ee", size = 12913133, upload-time = "2025-08-28T13:58:47.039Z" }, - { url = "https://files.pythonhosted.org/packages/80/22/3c6cef96627f89b344c933781ed38329bfb87737aa438f15da95907cbfd5/ruff-0.12.11-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4fc58f9266d62c6eccc75261a665f26b4ef64840887fc6cbc552ce5b29f96cc8", size = 13169082, upload-time = "2025-08-28T13:58:49.157Z" }, - { url = "https://files.pythonhosted.org/packages/05/b5/68b3ff96160d8b49e8dd10785ff3186be18fd650d356036a3770386e6c7f/ruff-0.12.11-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:5a0113bd6eafd545146440225fe60b4e9489f59eb5f5f107acd715ba5f0b3d2f", size = 13139490, upload-time = "2025-08-28T13:58:51.593Z" }, - { url = "https://files.pythonhosted.org/packages/59/b9/050a3278ecd558f74f7ee016fbdf10591d50119df8d5f5da45a22c6afafc/ruff-0.12.11-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0d737b4059d66295c3ea5720e6efc152623bb83fde5444209b69cd33a53e2000", size = 11958928, upload-time = "2025-08-28T13:58:53.943Z" }, - { url = "https://files.pythonhosted.org/packages/f9/bc/93be37347db854806904a43b0493af8d6873472dfb4b4b8cbb27786eb651/ruff-0.12.11-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:916fc5defee32dbc1fc1650b576a8fed68f5e8256e2180d4d9855aea43d6aab2", size = 11764513, upload-time = "2025-08-28T13:58:55.976Z" }, - { url = "https://files.pythonhosted.org/packages/7a/a1/1471751e2015a81fd8e166cd311456c11df74c7e8769d4aabfbc7584c7ac/ruff-0.12.11-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c984f07d7adb42d3ded5be894fb4007f30f82c87559438b4879fe7aa08c62b39", size = 12745154, upload-time = "2025-08-28T13:58:58.16Z" }, - { url = "https://files.pythonhosted.org/packages/68/ab/2542b14890d0f4872dd81b7b2a6aed3ac1786fae1ce9b17e11e6df9e31e3/ruff-0.12.11-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e07fbb89f2e9249f219d88331c833860489b49cdf4b032b8e4432e9b13e8a4b9", size = 13227653, upload-time = "2025-08-28T13:59:00.276Z" }, - { url = "https://files.pythonhosted.org/packages/22/16/2fbfc61047dbfd009c58a28369a693a1484ad15441723be1cd7fe69bb679/ruff-0.12.11-py3-none-win32.whl", hash = "sha256:c792e8f597c9c756e9bcd4d87cf407a00b60af77078c96f7b6366ea2ce9ba9d3", size = 11944270, upload-time = "2025-08-28T13:59:02.347Z" }, - { url = "https://files.pythonhosted.org/packages/08/a5/34276984705bfe069cd383101c45077ee029c3fe3b28225bf67aa35f0647/ruff-0.12.11-py3-none-win_amd64.whl", hash = "sha256:a3283325960307915b6deb3576b96919ee89432ebd9c48771ca12ee8afe4a0fd", size = 13046600, upload-time = "2025-08-28T13:59:04.751Z" }, - { url = "https://files.pythonhosted.org/packages/84/a8/001d4a7c2b37623a3fd7463208267fb906df40ff31db496157549cfd6e72/ruff-0.12.11-py3-none-win_arm64.whl", hash = "sha256:bae4d6e6a2676f8fb0f98b74594a048bae1b944aab17e9f5d504062303c6dbea", size = 12135290, upload-time = "2025-08-28T13:59:06.933Z" }, + { url = "https://files.pythonhosted.org/packages/f3/41/ca37e340938f45cfb8557a97a5c347e718ef34702546b174e5300dbb1f28/ruff-0.13.1-py3-none-linux_armv6l.whl", hash = "sha256:b2abff595cc3cbfa55e509d89439b5a09a6ee3c252d92020bd2de240836cf45b", size = 12304308, upload-time = "2025-09-18T19:51:56.253Z" }, + { url = "https://files.pythonhosted.org/packages/ff/84/ba378ef4129415066c3e1c80d84e539a0d52feb250685091f874804f28af/ruff-0.13.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:4ee9f4249bf7f8bb3984c41bfaf6a658162cdb1b22e3103eabc7dd1dc5579334", size = 12937258, upload-time = "2025-09-18T19:52:00.184Z" }, + { url = "https://files.pythonhosted.org/packages/8d/b6/ec5e4559ae0ad955515c176910d6d7c93edcbc0ed1a3195a41179c58431d/ruff-0.13.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5c5da4af5f6418c07d75e6f3224e08147441f5d1eac2e6ce10dcce5e616a3bae", size = 12214554, upload-time = "2025-09-18T19:52:02.753Z" }, + { url = "https://files.pythonhosted.org/packages/70/d6/cb3e3b4f03b9b0c4d4d8f06126d34b3394f6b4d764912fe80a1300696ef6/ruff-0.13.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80524f84a01355a59a93cef98d804e2137639823bcee2931f5028e71134a954e", size = 12448181, upload-time = "2025-09-18T19:52:05.279Z" }, + { url = "https://files.pythonhosted.org/packages/d2/ea/bf60cb46d7ade706a246cd3fb99e4cfe854efa3dfbe530d049c684da24ff/ruff-0.13.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff7f5ce8d7988767dd46a148192a14d0f48d1baea733f055d9064875c7d50389", size = 12104599, upload-time = "2025-09-18T19:52:07.497Z" }, + { url = "https://files.pythonhosted.org/packages/2d/3e/05f72f4c3d3a69e65d55a13e1dd1ade76c106d8546e7e54501d31f1dc54a/ruff-0.13.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c55d84715061f8b05469cdc9a446aa6c7294cd4bd55e86a89e572dba14374f8c", size = 13791178, upload-time = "2025-09-18T19:52:10.189Z" }, + { url = "https://files.pythonhosted.org/packages/81/e7/01b1fc403dd45d6cfe600725270ecc6a8f8a48a55bc6521ad820ed3ceaf8/ruff-0.13.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:ac57fed932d90fa1624c946dc67a0a3388d65a7edc7d2d8e4ca7bddaa789b3b0", size = 14814474, upload-time = "2025-09-18T19:52:12.866Z" }, + { url = "https://files.pythonhosted.org/packages/fa/92/d9e183d4ed6185a8df2ce9faa3f22e80e95b5f88d9cc3d86a6d94331da3f/ruff-0.13.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c366a71d5b4f41f86a008694f7a0d75fe409ec298685ff72dc882f882d532e36", size = 14217531, upload-time = "2025-09-18T19:52:15.245Z" }, + { url = "https://files.pythonhosted.org/packages/3b/4a/6ddb1b11d60888be224d721e01bdd2d81faaf1720592858ab8bac3600466/ruff-0.13.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4ea9d1b5ad3e7a83ee8ebb1229c33e5fe771e833d6d3dcfca7b77d95b060d38", size = 13265267, upload-time = "2025-09-18T19:52:17.649Z" }, + { url = "https://files.pythonhosted.org/packages/81/98/3f1d18a8d9ea33ef2ad508f0417fcb182c99b23258ec5e53d15db8289809/ruff-0.13.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0f70202996055b555d3d74b626406476cc692f37b13bac8828acff058c9966a", size = 13243120, upload-time = "2025-09-18T19:52:20.332Z" }, + { url = "https://files.pythonhosted.org/packages/8d/86/b6ce62ce9c12765fa6c65078d1938d2490b2b1d9273d0de384952b43c490/ruff-0.13.1-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:f8cff7a105dad631085d9505b491db33848007d6b487c3c1979dd8d9b2963783", size = 13443084, upload-time = "2025-09-18T19:52:23.032Z" }, + { url = "https://files.pythonhosted.org/packages/a1/6e/af7943466a41338d04503fb5a81b2fd07251bd272f546622e5b1599a7976/ruff-0.13.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:9761e84255443316a258dd7dfbd9bfb59c756e52237ed42494917b2577697c6a", size = 12295105, upload-time = "2025-09-18T19:52:25.263Z" }, + { url = "https://files.pythonhosted.org/packages/3f/97/0249b9a24f0f3ebd12f007e81c87cec6d311de566885e9309fcbac5b24cc/ruff-0.13.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:3d376a88c3102ef228b102211ef4a6d13df330cb0f5ca56fdac04ccec2a99700", size = 12072284, upload-time = "2025-09-18T19:52:27.478Z" }, + { url = "https://files.pythonhosted.org/packages/f6/85/0b64693b2c99d62ae65236ef74508ba39c3febd01466ef7f354885e5050c/ruff-0.13.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:cbefd60082b517a82c6ec8836989775ac05f8991715d228b3c1d86ccc7df7dae", size = 12970314, upload-time = "2025-09-18T19:52:30.212Z" }, + { url = "https://files.pythonhosted.org/packages/96/fc/342e9f28179915d28b3747b7654f932ca472afbf7090fc0c4011e802f494/ruff-0.13.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:dd16b9a5a499fe73f3c2ef09a7885cb1d97058614d601809d37c422ed1525317", size = 13422360, upload-time = "2025-09-18T19:52:32.676Z" }, + { url = "https://files.pythonhosted.org/packages/37/54/6177a0dc10bce6f43e392a2192e6018755473283d0cf43cc7e6afc182aea/ruff-0.13.1-py3-none-win32.whl", hash = "sha256:55e9efa692d7cb18580279f1fbb525146adc401f40735edf0aaeabd93099f9a0", size = 12178448, upload-time = "2025-09-18T19:52:35.545Z" }, + { url = "https://files.pythonhosted.org/packages/64/51/c6a3a33d9938007b8bdc8ca852ecc8d810a407fb513ab08e34af12dc7c24/ruff-0.13.1-py3-none-win_amd64.whl", hash = "sha256:3a3fb595287ee556de947183489f636b9f76a72f0fa9c028bdcabf5bab2cc5e5", size = 13286458, upload-time = "2025-09-18T19:52:38.198Z" }, + { url = "https://files.pythonhosted.org/packages/fd/04/afc078a12cf68592345b1e2d6ecdff837d286bac023d7a22c54c7a698c5b/ruff-0.13.1-py3-none-win_arm64.whl", hash = "sha256:c0bae9ffd92d54e03c2bf266f466da0a65e145f298ee5b5846ed435f6a00518a", size = 12437893, upload-time = "2025-09-18T19:52:41.283Z" }, ] [[package]] @@ -4690,47 +4700,46 @@ wheels = [ [[package]] name = "sentry-sdk" -version = "2.35.2" +version = "2.38.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/bd/79/0ecb942f3f1ad26c40c27f81ff82392d85c01d26a45e3c72c2b37807e680/sentry_sdk-2.35.2.tar.gz", hash = "sha256:e9e8f3c795044beb59f2c8f4c6b9b0f9779e5e604099882df05eec525e782cc6", size = 343377, upload-time = "2025-09-01T11:00:58.633Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/22/60fd703b34d94d216b2387e048ac82de3e86b63bc28869fb076f8bb0204a/sentry_sdk-2.38.0.tar.gz", hash = "sha256:792d2af45e167e2f8a3347143f525b9b6bac6f058fb2014720b40b84ccbeb985", size = 348116, upload-time = "2025-09-15T15:00:37.846Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c0/91/a43308dc82a0e32d80cd0dfdcfca401ecbd0f431ab45f24e48bb97b7800d/sentry_sdk-2.35.2-py2.py3-none-any.whl", hash = "sha256:38c98e3cbb620dd3dd80a8d6e39c753d453dd41f8a9df581b0584c19a52bc926", size = 363975, upload-time = "2025-09-01T11:00:56.574Z" }, + { url = "https://files.pythonhosted.org/packages/7a/84/bde4c4bbb269b71bc09316af8eb00da91f67814d40337cc12ef9c8742541/sentry_sdk-2.38.0-py2.py3-none-any.whl", hash = "sha256:2324aea8573a3fa1576df7fb4d65c4eb8d9929c8fa5939647397a07179eef8d0", size = 370346, upload-time = "2025-09-15T15:00:35.821Z" }, ] [[package]] name = "setproctitle" -version = "1.3.6" +version = "1.3.7" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9e/af/56efe21c53ac81ac87e000b15e60b3d8104224b4313b6eacac3597bd183d/setproctitle-1.3.6.tar.gz", hash = "sha256:c9f32b96c700bb384f33f7cf07954bb609d35dd82752cef57fb2ee0968409169", size = 26889, upload-time = "2025-04-29T13:35:00.184Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8d/48/49393a96a2eef1ab418b17475fb92b8fcfad83d099e678751b05472e69de/setproctitle-1.3.7.tar.gz", hash = "sha256:bc2bc917691c1537d5b9bca1468437176809c7e11e5694ca79a9ca12345dcb9e", size = 27002, upload-time = "2025-09-05T12:51:25.278Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/27/3b/8288d0cd969a63500dd62fc2c99ce6980f9909ccef0770ab1f86c361e0bf/setproctitle-1.3.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a1d856b0f4e4a33e31cdab5f50d0a14998f3a2d726a3fd5cb7c4d45a57b28d1b", size = 17412, upload-time = "2025-04-29T13:32:58.135Z" }, - { url = "https://files.pythonhosted.org/packages/39/37/43a5a3e25ca1048dbbf4db0d88d346226f5f1acd131bb8e660f4bfe2799f/setproctitle-1.3.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:50706b9c0eda55f7de18695bfeead5f28b58aa42fd5219b3b1692d554ecbc9ec", size = 11963, upload-time = "2025-04-29T13:32:59.17Z" }, - { url = "https://files.pythonhosted.org/packages/5b/47/f103c40e133154783c91a10ab08ac9fc410ed835aa85bcf7107cb882f505/setproctitle-1.3.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:af188f3305f0a65c3217c30c6d4c06891e79144076a91e8b454f14256acc7279", size = 31718, upload-time = "2025-04-29T13:33:00.36Z" }, - { url = "https://files.pythonhosted.org/packages/1f/13/7325dd1c008dd6c0ebd370ddb7505977054a87e406f142318e395031a792/setproctitle-1.3.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cce0ed8b3f64c71c140f0ec244e5fdf8ecf78ddf8d2e591d4a8b6aa1c1214235", size = 33027, upload-time = "2025-04-29T13:33:01.499Z" }, - { url = "https://files.pythonhosted.org/packages/0c/0a/6075bfea05a71379d77af98a9ac61163e8b6e5ef1ae58cd2b05871b2079c/setproctitle-1.3.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70100e2087fe05359f249a0b5f393127b3a1819bf34dec3a3e0d4941138650c9", size = 30223, upload-time = "2025-04-29T13:33:03.259Z" }, - { url = "https://files.pythonhosted.org/packages/cc/41/fbf57ec52f4f0776193bd94334a841f0bc9d17e745f89c7790f336420c65/setproctitle-1.3.6-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1065ed36bd03a3fd4186d6c6de5f19846650b015789f72e2dea2d77be99bdca1", size = 31204, upload-time = "2025-04-29T13:33:04.455Z" }, - { url = "https://files.pythonhosted.org/packages/97/b5/f799fb7a00de29fb0ac1dfd015528dea425b9e31a8f1068a0b3df52d317f/setproctitle-1.3.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4adf6a0013fe4e0844e3ba7583ec203ca518b9394c6cc0d3354df2bf31d1c034", size = 31181, upload-time = "2025-04-29T13:33:05.697Z" }, - { url = "https://files.pythonhosted.org/packages/b5/b7/81f101b612014ec61723436022c31146178813d6ca6b947f7b9c84e9daf4/setproctitle-1.3.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:eb7452849f6615871eabed6560ffedfe56bc8af31a823b6be4ce1e6ff0ab72c5", size = 30101, upload-time = "2025-04-29T13:33:07.223Z" }, - { url = "https://files.pythonhosted.org/packages/67/23/681232eed7640eab96719daa8647cc99b639e3daff5c287bd270ef179a73/setproctitle-1.3.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a094b7ce455ca341b59a0f6ce6be2e11411ba6e2860b9aa3dbb37468f23338f4", size = 32438, upload-time = "2025-04-29T13:33:08.538Z" }, - { url = "https://files.pythonhosted.org/packages/19/f8/4d075a7bdc3609ac71535b849775812455e4c40aedfbf0778a6f123b1774/setproctitle-1.3.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ad1c2c2baaba62823a7f348f469a967ece0062140ca39e7a48e4bbb1f20d54c4", size = 30625, upload-time = "2025-04-29T13:33:09.707Z" }, - { url = "https://files.pythonhosted.org/packages/5f/73/a2a8259ebee166aee1ca53eead75de0e190b3ddca4f716e5c7470ebb7ef6/setproctitle-1.3.6-cp311-cp311-win32.whl", hash = "sha256:8050c01331135f77ec99d99307bfbc6519ea24d2f92964b06f3222a804a3ff1f", size = 11488, upload-time = "2025-04-29T13:33:10.953Z" }, - { url = "https://files.pythonhosted.org/packages/c9/15/52cf5e1ff0727d53704cfdde2858eaf237ce523b0b04db65faa84ff83e13/setproctitle-1.3.6-cp311-cp311-win_amd64.whl", hash = "sha256:9b73cf0fe28009a04a35bb2522e4c5b5176cc148919431dcb73fdbdfaab15781", size = 12201, upload-time = "2025-04-29T13:33:12.389Z" }, - { url = "https://files.pythonhosted.org/packages/8f/fb/99456fd94d4207c5f6c40746a048a33a52b4239cd7d9c8d4889e2210ec82/setproctitle-1.3.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:af44bb7a1af163806bbb679eb8432fa7b4fb6d83a5d403b541b675dcd3798638", size = 17399, upload-time = "2025-04-29T13:33:13.406Z" }, - { url = "https://files.pythonhosted.org/packages/d5/48/9699191fe6062827683c43bfa9caac33a2c89f8781dd8c7253fa3dba85fd/setproctitle-1.3.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3cca16fd055316a48f0debfcbfb6af7cea715429fc31515ab3fcac05abd527d8", size = 11966, upload-time = "2025-04-29T13:33:14.976Z" }, - { url = "https://files.pythonhosted.org/packages/33/03/b085d192b9ecb9c7ce6ad6ef30ecf4110b7f39430b58a56245569827fcf4/setproctitle-1.3.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea002088d5554fd75e619742cefc78b84a212ba21632e59931b3501f0cfc8f67", size = 32017, upload-time = "2025-04-29T13:33:16.163Z" }, - { url = "https://files.pythonhosted.org/packages/ae/68/c53162e645816f97212002111420d1b2f75bf6d02632e37e961dc2cd6d8b/setproctitle-1.3.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb465dd5825356c1191a038a86ee1b8166e3562d6e8add95eec04ab484cfb8a2", size = 33419, upload-time = "2025-04-29T13:33:18.239Z" }, - { url = "https://files.pythonhosted.org/packages/ac/0d/119a45d15a816a6cf5ccc61b19729f82620095b27a47e0a6838216a95fae/setproctitle-1.3.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d2c8e20487b3b73c1fa72c56f5c89430617296cd380373e7af3a538a82d4cd6d", size = 30711, upload-time = "2025-04-29T13:33:19.571Z" }, - { url = "https://files.pythonhosted.org/packages/e3/fb/5e9b5068df9e9f31a722a775a5e8322a29a638eaaa3eac5ea7f0b35e6314/setproctitle-1.3.6-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0d6252098e98129a1decb59b46920d4eca17b0395f3d71b0d327d086fefe77d", size = 31742, upload-time = "2025-04-29T13:33:21.172Z" }, - { url = "https://files.pythonhosted.org/packages/35/88/54de1e73e8fce87d587889c7eedb48fc4ee2bbe4e4ca6331690d03024f86/setproctitle-1.3.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cf355fbf0d4275d86f9f57be705d8e5eaa7f8ddb12b24ced2ea6cbd68fdb14dc", size = 31925, upload-time = "2025-04-29T13:33:22.427Z" }, - { url = "https://files.pythonhosted.org/packages/f3/01/65948d7badd66e63e3db247b923143da142790fa293830fdecf832712c2d/setproctitle-1.3.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e288f8a162d663916060beb5e8165a8551312b08efee9cf68302687471a6545d", size = 30981, upload-time = "2025-04-29T13:33:23.739Z" }, - { url = "https://files.pythonhosted.org/packages/22/20/c495e61786f1d38d5dc340b9d9077fee9be3dfc7e89f515afe12e1526dbc/setproctitle-1.3.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b2e54f4a2dc6edf0f5ea5b1d0a608d2af3dcb5aa8c8eeab9c8841b23e1b054fe", size = 33209, upload-time = "2025-04-29T13:33:24.915Z" }, - { url = "https://files.pythonhosted.org/packages/98/3f/a457b8550fbd34d5b482fe20b8376b529e76bf1fbf9a474a6d9a641ab4ad/setproctitle-1.3.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b6f4abde9a2946f57e8daaf1160b2351bcf64274ef539e6675c1d945dbd75e2a", size = 31587, upload-time = "2025-04-29T13:33:26.123Z" }, - { url = "https://files.pythonhosted.org/packages/44/fe/743517340e5a635e3f1c4310baea20c16c66202f96a6f4cead222ffd6d84/setproctitle-1.3.6-cp312-cp312-win32.whl", hash = "sha256:db608db98ccc21248370d30044a60843b3f0f3d34781ceeea67067c508cd5a28", size = 11487, upload-time = "2025-04-29T13:33:27.403Z" }, - { url = "https://files.pythonhosted.org/packages/60/9a/d88f1c1f0f4efff1bd29d9233583ee341114dda7d9613941453984849674/setproctitle-1.3.6-cp312-cp312-win_amd64.whl", hash = "sha256:082413db8a96b1f021088e8ec23f0a61fec352e649aba20881895815388b66d3", size = 12208, upload-time = "2025-04-29T13:33:28.852Z" }, + { url = "https://files.pythonhosted.org/packages/04/cd/1b7ba5cad635510720ce19d7122154df96a2387d2a74217be552887c93e5/setproctitle-1.3.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a600eeb4145fb0ee6c287cb82a2884bd4ec5bbb076921e287039dcc7b7cc6dd0", size = 18085, upload-time = "2025-09-05T12:49:22.183Z" }, + { url = "https://files.pythonhosted.org/packages/8f/1a/b2da0a620490aae355f9d72072ac13e901a9fec809a6a24fc6493a8f3c35/setproctitle-1.3.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:97a090fed480471bb175689859532709e28c085087e344bca45cf318034f70c4", size = 13097, upload-time = "2025-09-05T12:49:23.322Z" }, + { url = "https://files.pythonhosted.org/packages/18/2e/bd03ff02432a181c1787f6fc2a678f53b7dacdd5ded69c318fe1619556e8/setproctitle-1.3.7-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1607b963e7b53e24ec8a2cb4e0ab3ae591d7c6bf0a160feef0551da63452b37f", size = 32191, upload-time = "2025-09-05T12:49:24.567Z" }, + { url = "https://files.pythonhosted.org/packages/28/78/1e62fc0937a8549f2220445ed2175daacee9b6764c7963b16148119b016d/setproctitle-1.3.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a20fb1a3974e2dab857870cf874b325b8705605cb7e7e8bcbb915bca896f52a9", size = 33203, upload-time = "2025-09-05T12:49:25.871Z" }, + { url = "https://files.pythonhosted.org/packages/a0/3c/65edc65db3fa3df400cf13b05e9d41a3c77517b4839ce873aa6b4043184f/setproctitle-1.3.7-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f8d961bba676e07d77665204f36cffaa260f526e7b32d07ab3df6a2c1dfb44ba", size = 34963, upload-time = "2025-09-05T12:49:27.044Z" }, + { url = "https://files.pythonhosted.org/packages/a1/32/89157e3de997973e306e44152522385f428e16f92f3cf113461489e1e2ee/setproctitle-1.3.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:db0fd964fbd3a9f8999b502f65bd2e20883fdb5b1fae3a424e66db9a793ed307", size = 32398, upload-time = "2025-09-05T12:49:28.909Z" }, + { url = "https://files.pythonhosted.org/packages/4a/18/77a765a339ddf046844cb4513353d8e9dcd8183da9cdba6e078713e6b0b2/setproctitle-1.3.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:db116850fcf7cca19492030f8d3b4b6e231278e8fe097a043957d22ce1bdf3ee", size = 33657, upload-time = "2025-09-05T12:49:30.323Z" }, + { url = "https://files.pythonhosted.org/packages/6b/63/f0b6205c64d74d2a24a58644a38ec77bdbaa6afc13747e75973bf8904932/setproctitle-1.3.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:316664d8b24a5c91ee244460bdaf7a74a707adaa9e14fbe0dc0a53168bb9aba1", size = 31836, upload-time = "2025-09-05T12:49:32.309Z" }, + { url = "https://files.pythonhosted.org/packages/ba/51/e1277f9ba302f1a250bbd3eedbbee747a244b3cc682eb58fb9733968f6d8/setproctitle-1.3.7-cp311-cp311-win32.whl", hash = "sha256:b74774ca471c86c09b9d5037c8451fff06bb82cd320d26ae5a01c758088c0d5d", size = 12556, upload-time = "2025-09-05T12:49:33.529Z" }, + { url = "https://files.pythonhosted.org/packages/b6/7b/822a23f17e9003dfdee92cd72758441ca2a3680388da813a371b716fb07f/setproctitle-1.3.7-cp311-cp311-win_amd64.whl", hash = "sha256:acb9097213a8dd3410ed9f0dc147840e45ca9797785272928d4be3f0e69e3be4", size = 13243, upload-time = "2025-09-05T12:49:34.553Z" }, + { url = "https://files.pythonhosted.org/packages/fb/f0/2dc88e842077719d7384d86cc47403e5102810492b33680e7dadcee64cd8/setproctitle-1.3.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2dc99aec591ab6126e636b11035a70991bc1ab7a261da428491a40b84376654e", size = 18049, upload-time = "2025-09-05T12:49:36.241Z" }, + { url = "https://files.pythonhosted.org/packages/f0/b4/50940504466689cda65680c9e9a1e518e5750c10490639fa687489ac7013/setproctitle-1.3.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cdd8aa571b7aa39840fdbea620e308a19691ff595c3a10231e9ee830339dd798", size = 13079, upload-time = "2025-09-05T12:49:38.088Z" }, + { url = "https://files.pythonhosted.org/packages/d0/99/71630546b9395b095f4082be41165d1078204d1696c2d9baade3de3202d0/setproctitle-1.3.7-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2906b6c7959cdb75f46159bf0acd8cc9906cf1361c9e1ded0d065fe8f9039629", size = 32932, upload-time = "2025-09-05T12:49:39.271Z" }, + { url = "https://files.pythonhosted.org/packages/50/22/cee06af4ffcfb0e8aba047bd44f5262e644199ae7527ae2c1f672b86495c/setproctitle-1.3.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6915964a6dda07920a1159321dcd6d94fc7fc526f815ca08a8063aeca3c204f1", size = 33736, upload-time = "2025-09-05T12:49:40.565Z" }, + { url = "https://files.pythonhosted.org/packages/5c/00/a5949a8bb06ef5e7df214fc393bb2fb6aedf0479b17214e57750dfdd0f24/setproctitle-1.3.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cff72899861c765bd4021d1ff1c68d60edc129711a2fdba77f9cb69ef726a8b6", size = 35605, upload-time = "2025-09-05T12:49:42.362Z" }, + { url = "https://files.pythonhosted.org/packages/b0/3a/50caca532a9343828e3bf5778c7a84d6c737a249b1796d50dd680290594d/setproctitle-1.3.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b7cb05bd446687ff816a3aaaf831047fc4c364feff7ada94a66024f1367b448c", size = 33143, upload-time = "2025-09-05T12:49:43.515Z" }, + { url = "https://files.pythonhosted.org/packages/ca/14/b843a251296ce55e2e17c017d6b9f11ce0d3d070e9265de4ecad948b913d/setproctitle-1.3.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3a57b9a00de8cae7e2a1f7b9f0c2ac7b69372159e16a7708aa2f38f9e5cc987a", size = 34434, upload-time = "2025-09-05T12:49:45.31Z" }, + { url = "https://files.pythonhosted.org/packages/c8/b7/06145c238c0a6d2c4bc881f8be230bb9f36d2bf51aff7bddcb796d5eed67/setproctitle-1.3.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d8828b356114f6b308b04afe398ed93803d7fca4a955dd3abe84430e28d33739", size = 32795, upload-time = "2025-09-05T12:49:46.419Z" }, + { url = "https://files.pythonhosted.org/packages/ef/dc/ef76a81fac9bf27b84ed23df19c1f67391a753eed6e3c2254ebcb5133f56/setproctitle-1.3.7-cp312-cp312-win32.whl", hash = "sha256:b0304f905efc845829ac2bc791ddebb976db2885f6171f4a3de678d7ee3f7c9f", size = 12552, upload-time = "2025-09-05T12:49:47.635Z" }, + { url = "https://files.pythonhosted.org/packages/e2/5b/a9fe517912cd6e28cf43a212b80cb679ff179a91b623138a99796d7d18a0/setproctitle-1.3.7-cp312-cp312-win_amd64.whl", hash = "sha256:9888ceb4faea3116cf02a920ff00bfbc8cc899743e4b4ac914b03625bdc3c300", size = 13247, upload-time = "2025-09-05T12:49:49.16Z" }, + { url = "https://files.pythonhosted.org/packages/c3/5b/5e1c117ac84e3cefcf8d7a7f6b2461795a87e20869da065a5c087149060b/setproctitle-1.3.7-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:b1cac6a4b0252b8811d60b6d8d0f157c0fdfed379ac89c25a914e6346cf355a1", size = 12587, upload-time = "2025-09-05T12:51:21.195Z" }, + { url = "https://files.pythonhosted.org/packages/73/02/b9eadc226195dcfa90eed37afe56b5dd6fa2f0e5220ab8b7867b8862b926/setproctitle-1.3.7-pp311-pypy311_pp73-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f1704c9e041f2b1dc38f5be4552e141e1432fba3dd52c72eeffd5bc2db04dc65", size = 14286, upload-time = "2025-09-05T12:51:22.61Z" }, + { url = "https://files.pythonhosted.org/packages/28/26/1be1d2a53c2a91ec48fa2ff4a409b395f836798adf194d99de9c059419ea/setproctitle-1.3.7-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:b08b61976ffa548bd5349ce54404bf6b2d51bd74d4f1b241ed1b0f25bce09c3a", size = 13282, upload-time = "2025-09-05T12:51:24.094Z" }, ] [[package]] @@ -4747,7 +4756,7 @@ name = "shapely" 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/ca/3c/2da625233f4e605155926566c0e7ea8dda361877f48e8b1655e53456f252/shapely-2.1.1.tar.gz", hash = "sha256:500621967f2ffe9642454808009044c21e5b35db89ce69f8a2042c2ffd0e2772", size = 315422, upload-time = "2025-05-19T11:04:41.265Z" } wheels = [ @@ -4771,22 +4780,22 @@ wheels = [ [[package]] name = "siphash24" -version = "1.7" +version = "1.8" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0e/be/f0a0ffbb00c51c5633b41459b5ce9b017c025a9256b4403e648c18e70850/siphash24-1.7.tar.gz", hash = "sha256:6e90fee5f199ea25b4e7303646b31872a437174fe885a93dbd4cf7784eb48164", size = 19801, upload-time = "2024-10-15T13:41:51.924Z" } +sdist = { url = "https://files.pythonhosted.org/packages/67/a2/e049b6fccf7a94bd1b2f68b3059a7d6a7aea86a808cac80cb9ae71ab6254/siphash24-1.8.tar.gz", hash = "sha256:aa932f0af4a7335caef772fdaf73a433a32580405c41eb17ff24077944b0aa97", size = 19946, upload-time = "2025-09-02T20:42:04.856Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4e/67/4ffd23a848739966e1b314ef99f6410035bccee00be14261313787b8f506/siphash24-1.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:de75488e93f1cd12c8d5004efd1ebd958c0265205a9d73e8dd8b071900838841", size = 80493, upload-time = "2024-10-15T13:41:14.727Z" }, - { url = "https://files.pythonhosted.org/packages/56/bd/ec198a8c7aef65e967ae84f633bd9950d784c9e527d738c9a3e4bccc34a5/siphash24-1.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ffca9908450f9f346e97a223185fcd16217d67d84c6f246f3080c4224f41a514", size = 75350, upload-time = "2024-10-15T13:41:16.262Z" }, - { url = "https://files.pythonhosted.org/packages/50/5a/77838c916bd15addfc2e51286db4c442cb12e25eb4f8d296c394c2280240/siphash24-1.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8ff44ce166452993fea267ea1b2fd089d8e7f103b13d360da441f12b0df121d", size = 100567, upload-time = "2024-10-15T13:41:17.435Z" }, - { url = "https://files.pythonhosted.org/packages/f0/aa/736a0a2efae9a6f69ac1ee4d28c2274fcad2150349fac752d6c525c4e06e/siphash24-1.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4062548dcb1eef13bbe0356d6f8675bfe4571ef38d7103445daa82ba167240d1", size = 105630, upload-time = "2024-10-15T13:41:18.578Z" }, - { url = "https://files.pythonhosted.org/packages/79/52/1afbd70142d3db093d49197e3abe15ca2f1a14678299327ba776944b4771/siphash24-1.7-cp311-cp311-win32.whl", hash = "sha256:7b4ea29376b688fbcc3d25707c15a9dfe7b4ebbc4322878d75bb77e199210a39", size = 67648, upload-time = "2024-10-15T13:41:19.606Z" }, - { url = "https://files.pythonhosted.org/packages/b5/1d/bedcd04c2d1d199c9f6b3e61a6caae0e17257696c9f49594e49856b17a99/siphash24-1.7-cp311-cp311-win_amd64.whl", hash = "sha256:ec06104e6ef1e512ee30f1b8aeae2b83c0f55f12a94042f0df5a87d43a1f4c52", size = 80046, upload-time = "2024-10-15T13:41:20.654Z" }, - { url = "https://files.pythonhosted.org/packages/3e/62/93e552af9535a416f684327f870143ee42fc9e816091672467cdfd62cce6/siphash24-1.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:76a64ff0cdd192e4d0a391956d9b121c56ed56e773c5ab7eb7c3e035fd16e8cb", size = 82084, upload-time = "2024-10-15T13:41:21.776Z" }, - { url = "https://files.pythonhosted.org/packages/59/3e/b0791ab53aa9ac191b71a021eab2e75baa7c27d7feb7ec148d7961d148ba/siphash24-1.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:49ca649bc7437d614f758891deade3b187832792a853269219e77f10509f82fe", size = 76233, upload-time = "2024-10-15T13:41:22.787Z" }, - { url = "https://files.pythonhosted.org/packages/29/4c/4c1b809bf302e9b60f3ec09ba115b2a4ac1ff6755735ee8884924fcdb45e/siphash24-1.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc37dd0aed23f76bd257fbd2953fd5d954b329d7463c6ff57263a2699c52dde6", size = 98188, upload-time = "2024-10-15T13:41:24.327Z" }, - { url = "https://files.pythonhosted.org/packages/96/bf/e6b49f8ff88130bd224f291ea77d30fdde4df5f6572c519aca5d8fc8a27c/siphash24-1.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eea490a200891905856b6ad0f9c56d4ec787876220bcb34c49441b2566b97887", size = 102946, upload-time = "2024-10-15T13:41:25.633Z" }, - { url = "https://files.pythonhosted.org/packages/3d/75/45c831626013950fb2ea715c218c3397e5cf2328a67208bf5d8ff69aa9e6/siphash24-1.7-cp312-cp312-win32.whl", hash = "sha256:69eb8c2c112a738875bb283cd53ef5e86874bc5aed17f3020b38e9174208fb79", size = 68323, upload-time = "2024-10-15T13:41:27.349Z" }, - { url = "https://files.pythonhosted.org/packages/e0/d3/39190c40a68defd19b99c1082dd7455543a52283803bfa111b0e45fae968/siphash24-1.7-cp312-cp312-win_amd64.whl", hash = "sha256:7459569ea4669b6feeaf7d299fc5157cc5c69ca1231dc0decb7a7da2397c782e", size = 81000, upload-time = "2024-10-15T13:41:28.364Z" }, + { url = "https://files.pythonhosted.org/packages/82/23/f53f5bd8866c6ea3abe434c9f208e76ea027210d8b75cd0e0dc849661c7a/siphash24-1.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4662ac616bce4d3c9d6003a0d398e56f8be408fc53a166b79fad08d4f34268e", size = 76930, upload-time = "2025-09-02T20:41:00.869Z" }, + { url = "https://files.pythonhosted.org/packages/0b/25/aebf246904424a06e7ffb7a40cfa9ea9e590ea0fac82e182e0f5d1f1d7ef/siphash24-1.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:53d6bed0951a99c6d2891fa6f8acfd5ca80c3e96c60bcee99f6fa01a04773b1c", size = 74315, upload-time = "2025-09-02T20:41:02.38Z" }, + { url = "https://files.pythonhosted.org/packages/59/3f/7010407c3416ef052d46550d54afb2581fb247018fc6500af8c66669eff2/siphash24-1.8-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d114c03648630e9e07dac2fe95442404e4607adca91640d274ece1a4fa71123e", size = 99756, upload-time = "2025-09-02T20:41:03.902Z" }, + { url = "https://files.pythonhosted.org/packages/d4/9f/09c734833e69badd7e3faed806b4372bd6564ae0946bd250d5239885914f/siphash24-1.8-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:88c1a55ff82b127c5d3b96927a430d8859e6a98846a5b979833ac790682dd91b", size = 104044, upload-time = "2025-09-02T20:41:05.505Z" }, + { url = "https://files.pythonhosted.org/packages/24/30/56a26d9141a34433da221f732599e2b23d2d70a966c249a9f00feb9a2915/siphash24-1.8-cp311-cp311-win32.whl", hash = "sha256:9430255e6a1313470f52c07c4a4643c451a5b2853f6d4008e4dda05cafb6ce7c", size = 62196, upload-time = "2025-09-02T20:41:07.299Z" }, + { url = "https://files.pythonhosted.org/packages/47/b2/11b0ae63fd374652544e1b12f72ba2cc3fe6c93c1483bd8ff6935b0a8a4b/siphash24-1.8-cp311-cp311-win_amd64.whl", hash = "sha256:1e4b37e4ef0b4496169adce2a58b6c3f230b5852dfa5f7ad0b2d664596409e47", size = 77162, upload-time = "2025-09-02T20:41:08.878Z" }, + { url = "https://files.pythonhosted.org/packages/7f/82/ce3545ce8052ac7ca104b183415a27ec3335e5ed51978fdd7b433f3cfe5b/siphash24-1.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:df5ed437c6e6cc96196b38728e57cd30b0427df45223475a90e173f5015ef5ba", size = 78136, upload-time = "2025-09-02T20:41:10.083Z" }, + { url = "https://files.pythonhosted.org/packages/15/88/896c3b91bc9deb78c415448b1db67343917f35971a9e23a5967a9d323b8a/siphash24-1.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f4ef78abdf811325c7089a35504df339c48c0007d4af428a044431d329721e56", size = 74588, upload-time = "2025-09-02T20:41:11.251Z" }, + { url = "https://files.pythonhosted.org/packages/12/fd/8dad3f5601db485ba862e1c1f91a5d77fb563650856a6708e9acb40ee53c/siphash24-1.8-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:065eff55c4fefb3a29fd26afb2c072abf7f668ffd53b91d41f92a1c485fcbe5c", size = 98655, upload-time = "2025-09-02T20:41:12.45Z" }, + { url = "https://files.pythonhosted.org/packages/e3/cc/e0c352624c1f2faad270aeb5cce6e173977ef66b9b5e918aa6f32af896bf/siphash24-1.8-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ac6fa84ebfd47677262aa0bcb0f5a70f796f5fc5704b287ee1b65a3bd4fb7a5d", size = 103217, upload-time = "2025-09-02T20:41:13.746Z" }, + { url = "https://files.pythonhosted.org/packages/5b/f6/0b1675bea4d40affcae642d9c7337702a4138b93c544230280712403e968/siphash24-1.8-cp312-cp312-win32.whl", hash = "sha256:6582f73615552ca055e51e03cb02a28e570a641a7f500222c86c2d811b5037eb", size = 63114, upload-time = "2025-09-02T20:41:14.972Z" }, + { url = "https://files.pythonhosted.org/packages/3d/39/afefef85d72ed8b5cf1aa9283f712e3cd43c9682fabbc809dec54baa8452/siphash24-1.8-cp312-cp312-win_amd64.whl", hash = "sha256:44ea6d794a7cbe184e1e1da2df81c5ebb672ab3867935c3e87c08bb0c2fa4879", size = 76232, upload-time = "2025-09-02T20:41:16.112Z" }, ] [[package]] @@ -4833,9 +4842,9 @@ wheels = [ [[package]] name = "spidev" -version = "3.7" +version = "3.8" source = { registry = "https://pypi.org/simple" } -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" } +sdist = { url = "https://files.pythonhosted.org/packages/67/87/039b6eeea781598015b538691bc174cc0bf77df9d4d2d3b8bf9245c0de8c/spidev-3.8.tar.gz", hash = "sha256:2bc02fb8c6312d519ebf1f4331067427c0921d3f77b8bcaf05189a2e8b8382c0", size = 13893, upload-time = "2025-09-15T18:56:20.672Z" } [[package]] name = "sympy" @@ -4872,14 +4881,14 @@ wheels = [ [[package]] name = "types-requests" -version = "2.32.4.20250809" +version = "2.32.4.20250913" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ed/b0/9355adb86ec84d057fea765e4c49cce592aaf3d5117ce5609a95a7fc3dac/types_requests-2.32.4.20250809.tar.gz", hash = "sha256:d8060de1c8ee599311f56ff58010fb4902f462a1470802cf9f6ed27bc46c4df3", size = 23027, upload-time = "2025-08-09T03:17:10.664Z" } +sdist = { url = "https://files.pythonhosted.org/packages/36/27/489922f4505975b11de2b5ad07b4fe1dca0bca9be81a703f26c5f3acfce5/types_requests-2.32.4.20250913.tar.gz", hash = "sha256:abd6d4f9ce3a9383f269775a9835a4c24e5cd6b9f647d64f88aa4613c33def5d", size = 23113, upload-time = "2025-09-13T02:40:02.309Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/6f/ec0012be842b1d888d46884ac5558fd62aeae1f0ec4f7a581433d890d4b5/types_requests-2.32.4.20250809-py3-none-any.whl", hash = "sha256:f73d1832fb519ece02c85b1f09d5f0dd3108938e7d47e7f94bbfa18a6782b163", size = 20644, upload-time = "2025-08-09T03:17:09.716Z" }, + { url = "https://files.pythonhosted.org/packages/2a/20/9a227ea57c1285986c4cf78400d0a91615d25b24e257fd9e2969606bdfae/types_requests-2.32.4.20250913-py3-none-any.whl", hash = "sha256:78c9c1fffebbe0fa487a418e0fa5252017e9c60d1a2da394077f1780f655d7e1", size = 20658, upload-time = "2025-09-13T02:40:01.115Z" }, ] [[package]] @@ -4976,7 +4985,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 = [ @@ -5033,42 +5042,42 @@ wheels = [ [[package]] name = "zstandard" -version = "0.24.0" +version = "0.25.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/09/1b/c20b2ef1d987627765dcd5bf1dadb8ef6564f00a87972635099bb76b7a05/zstandard-0.24.0.tar.gz", hash = "sha256:fe3198b81c00032326342d973e526803f183f97aa9e9a98e3f897ebafe21178f", size = 905681, upload-time = "2025-08-17T18:36:36.352Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/aa/3e0508d5a5dd96529cdc5a97011299056e14c6505b678fd58938792794b1/zstandard-0.25.0.tar.gz", hash = "sha256:7713e1179d162cf5c7906da876ec2ccb9c3a9dcbdffef0cc7f70c3667a205f0b", size = 711513, upload-time = "2025-09-14T22:15:54.002Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/01/1f/5c72806f76043c0ef9191a2b65281dacdf3b65b0828eb13bb2c987c4fb90/zstandard-0.24.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:addfc23e3bd5f4b6787b9ca95b2d09a1a67ad5a3c318daaa783ff90b2d3a366e", size = 795228, upload-time = "2025-08-17T18:21:46.978Z" }, - { url = "https://files.pythonhosted.org/packages/0b/ba/3059bd5cd834666a789251d14417621b5c61233bd46e7d9023ea8bc1043a/zstandard-0.24.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6b005bcee4be9c3984b355336283afe77b2defa76ed6b89332eced7b6fa68b68", size = 640520, upload-time = "2025-08-17T18:21:48.162Z" }, - { url = "https://files.pythonhosted.org/packages/57/07/f0e632bf783f915c1fdd0bf68614c4764cae9dd46ba32cbae4dd659592c3/zstandard-0.24.0-cp311-cp311-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:3f96a9130171e01dbb6c3d4d9925d604e2131a97f540e223b88ba45daf56d6fb", size = 5347682, upload-time = "2025-08-17T18:21:50.266Z" }, - { url = "https://files.pythonhosted.org/packages/a6/4c/63523169fe84773a7462cd090b0989cb7c7a7f2a8b0a5fbf00009ba7d74d/zstandard-0.24.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd0d3d16e63873253bad22b413ec679cf6586e51b5772eb10733899832efec42", size = 5057650, upload-time = "2025-08-17T18:21:52.634Z" }, - { url = "https://files.pythonhosted.org/packages/c6/16/49013f7ef80293f5cebf4c4229535a9f4c9416bbfd238560edc579815dbe/zstandard-0.24.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:b7a8c30d9bf4bd5e4dcfe26900bef0fcd9749acde45cdf0b3c89e2052fda9a13", size = 5404893, upload-time = "2025-08-17T18:21:54.54Z" }, - { url = "https://files.pythonhosted.org/packages/4d/38/78e8bcb5fc32a63b055f2b99e0be49b506f2351d0180173674f516cf8a7a/zstandard-0.24.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:52cd7d9fa0a115c9446abb79b06a47171b7d916c35c10e0c3aa6f01d57561382", size = 5452389, upload-time = "2025-08-17T18:21:56.822Z" }, - { url = "https://files.pythonhosted.org/packages/55/8a/81671f05619edbacd49bd84ce6899a09fc8299be20c09ae92f6618ccb92d/zstandard-0.24.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a0f6fc2ea6e07e20df48752e7700e02e1892c61f9a6bfbacaf2c5b24d5ad504b", size = 5558888, upload-time = "2025-08-17T18:21:58.68Z" }, - { url = "https://files.pythonhosted.org/packages/49/cc/e83feb2d7d22d1f88434defbaeb6e5e91f42a4f607b5d4d2d58912b69d67/zstandard-0.24.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e46eb6702691b24ddb3e31e88b4a499e31506991db3d3724a85bd1c5fc3cfe4e", size = 5048038, upload-time = "2025-08-17T18:22:00.642Z" }, - { url = "https://files.pythonhosted.org/packages/08/c3/7a5c57ff49ef8943877f85c23368c104c2aea510abb339a2dc31ad0a27c3/zstandard-0.24.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5e3b9310fd7f0d12edc75532cd9a56da6293840c84da90070d692e0bb15f186", size = 5573833, upload-time = "2025-08-17T18:22:02.402Z" }, - { url = "https://files.pythonhosted.org/packages/f9/00/64519983cd92535ba4bdd4ac26ac52db00040a52d6c4efb8d1764abcc343/zstandard-0.24.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:76cdfe7f920738ea871f035568f82bad3328cbc8d98f1f6988264096b5264efd", size = 4961072, upload-time = "2025-08-17T18:22:04.384Z" }, - { url = "https://files.pythonhosted.org/packages/72/ab/3a08a43067387d22994fc87c3113636aa34ccd2914a4d2d188ce365c5d85/zstandard-0.24.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:3f2fe35ec84908dddf0fbf66b35d7c2878dbe349552dd52e005c755d3493d61c", size = 5268462, upload-time = "2025-08-17T18:22:06.095Z" }, - { url = "https://files.pythonhosted.org/packages/49/cf/2abb3a1ad85aebe18c53e7eca73223f1546ddfa3bf4d2fb83fc5a064c5ca/zstandard-0.24.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:aa705beb74ab116563f4ce784fa94771f230c05d09ab5de9c397793e725bb1db", size = 5443319, upload-time = "2025-08-17T18:22:08.572Z" }, - { url = "https://files.pythonhosted.org/packages/40/42/0dd59fc2f68f1664cda11c3b26abdf987f4e57cb6b6b0f329520cd074552/zstandard-0.24.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:aadf32c389bb7f02b8ec5c243c38302b92c006da565e120dfcb7bf0378f4f848", size = 5822355, upload-time = "2025-08-17T18:22:10.537Z" }, - { url = "https://files.pythonhosted.org/packages/99/c0/ea4e640fd4f7d58d6f87a1e7aca11fb886ac24db277fbbb879336c912f63/zstandard-0.24.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e40cd0fc734aa1d4bd0e7ad102fd2a1aefa50ce9ef570005ffc2273c5442ddc3", size = 5365257, upload-time = "2025-08-17T18:22:13.159Z" }, - { url = "https://files.pythonhosted.org/packages/27/a9/92da42a5c4e7e4003271f2e1f0efd1f37cfd565d763ad3604e9597980a1c/zstandard-0.24.0-cp311-cp311-win32.whl", hash = "sha256:cda61c46343809ecda43dc620d1333dd7433a25d0a252f2dcc7667f6331c7b61", size = 435559, upload-time = "2025-08-17T18:22:17.29Z" }, - { url = "https://files.pythonhosted.org/packages/e2/8e/2c8e5c681ae4937c007938f954a060fa7c74f36273b289cabdb5ef0e9a7e/zstandard-0.24.0-cp311-cp311-win_amd64.whl", hash = "sha256:3b95fc06489aa9388400d1aab01a83652bc040c9c087bd732eb214909d7fb0dd", size = 505070, upload-time = "2025-08-17T18:22:14.808Z" }, - { url = "https://files.pythonhosted.org/packages/52/10/a2f27a66bec75e236b575c9f7b0d7d37004a03aa2dcde8e2decbe9ed7b4d/zstandard-0.24.0-cp311-cp311-win_arm64.whl", hash = "sha256:ad9fd176ff6800a0cf52bcf59c71e5de4fa25bf3ba62b58800e0f84885344d34", size = 461507, upload-time = "2025-08-17T18:22:15.964Z" }, - { url = "https://files.pythonhosted.org/packages/26/e9/0bd281d9154bba7fc421a291e263911e1d69d6951aa80955b992a48289f6/zstandard-0.24.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a2bda8f2790add22773ee7a4e43c90ea05598bffc94c21c40ae0a9000b0133c3", size = 795710, upload-time = "2025-08-17T18:22:19.189Z" }, - { url = "https://files.pythonhosted.org/packages/36/26/b250a2eef515caf492e2d86732e75240cdac9d92b04383722b9753590c36/zstandard-0.24.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cc76de75300f65b8eb574d855c12518dc25a075dadb41dd18f6322bda3fe15d5", size = 640336, upload-time = "2025-08-17T18:22:20.466Z" }, - { url = "https://files.pythonhosted.org/packages/79/bf/3ba6b522306d9bf097aac8547556b98a4f753dc807a170becaf30dcd6f01/zstandard-0.24.0-cp312-cp312-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:d2b3b4bda1a025b10fe0269369475f420177f2cb06e0f9d32c95b4873c9f80b8", size = 5342533, upload-time = "2025-08-17T18:22:22.326Z" }, - { url = "https://files.pythonhosted.org/packages/ea/ec/22bc75bf054e25accdf8e928bc68ab36b4466809729c554ff3a1c1c8bce6/zstandard-0.24.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9b84c6c210684286e504022d11ec294d2b7922d66c823e87575d8b23eba7c81f", size = 5062837, upload-time = "2025-08-17T18:22:24.416Z" }, - { url = "https://files.pythonhosted.org/packages/48/cc/33edfc9d286e517fb5b51d9c3210e5bcfce578d02a675f994308ca587ae1/zstandard-0.24.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c59740682a686bf835a1a4d8d0ed1eefe31ac07f1c5a7ed5f2e72cf577692b00", size = 5393855, upload-time = "2025-08-17T18:22:26.786Z" }, - { url = "https://files.pythonhosted.org/packages/73/36/59254e9b29da6215fb3a717812bf87192d89f190f23817d88cb8868c47ac/zstandard-0.24.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:6324fde5cf5120fbf6541d5ff3c86011ec056e8d0f915d8e7822926a5377193a", size = 5451058, upload-time = "2025-08-17T18:22:28.885Z" }, - { url = "https://files.pythonhosted.org/packages/9a/c7/31674cb2168b741bbbe71ce37dd397c9c671e73349d88ad3bca9e9fae25b/zstandard-0.24.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:51a86bd963de3f36688553926a84e550d45d7f9745bd1947d79472eca27fcc75", size = 5546619, upload-time = "2025-08-17T18:22:31.115Z" }, - { url = "https://files.pythonhosted.org/packages/e6/01/1a9f22239f08c00c156f2266db857545ece66a6fc0303d45c298564bc20b/zstandard-0.24.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d82ac87017b734f2fb70ff93818c66f0ad2c3810f61040f077ed38d924e19980", size = 5046676, upload-time = "2025-08-17T18:22:33.077Z" }, - { url = "https://files.pythonhosted.org/packages/a7/91/6c0cf8fa143a4988a0361380ac2ef0d7cb98a374704b389fbc38b5891712/zstandard-0.24.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:92ea7855d5bcfb386c34557516c73753435fb2d4a014e2c9343b5f5ba148b5d8", size = 5576381, upload-time = "2025-08-17T18:22:35.391Z" }, - { url = "https://files.pythonhosted.org/packages/e2/77/1526080e22e78871e786ccf3c84bf5cec9ed25110a9585507d3c551da3d6/zstandard-0.24.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3adb4b5414febf074800d264ddf69ecade8c658837a83a19e8ab820e924c9933", size = 4953403, upload-time = "2025-08-17T18:22:37.266Z" }, - { url = "https://files.pythonhosted.org/packages/6e/d0/a3a833930bff01eab697eb8abeafb0ab068438771fa066558d96d7dafbf9/zstandard-0.24.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:6374feaf347e6b83ec13cc5dcfa70076f06d8f7ecd46cc71d58fac798ff08b76", size = 5267396, upload-time = "2025-08-17T18:22:39.757Z" }, - { url = "https://files.pythonhosted.org/packages/f3/5e/90a0db9a61cd4769c06374297ecfcbbf66654f74cec89392519deba64d76/zstandard-0.24.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:13fc548e214df08d896ee5f29e1f91ee35db14f733fef8eabea8dca6e451d1e2", size = 5433269, upload-time = "2025-08-17T18:22:42.131Z" }, - { url = "https://files.pythonhosted.org/packages/ce/58/fc6a71060dd67c26a9c5566e0d7c99248cbe5abfda6b3b65b8f1a28d59f7/zstandard-0.24.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:0a416814608610abf5488889c74e43ffa0343ca6cf43957c6b6ec526212422da", size = 5814203, upload-time = "2025-08-17T18:22:44.017Z" }, - { url = "https://files.pythonhosted.org/packages/5c/6a/89573d4393e3ecbfa425d9a4e391027f58d7810dec5cdb13a26e4cdeef5c/zstandard-0.24.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0d66da2649bb0af4471699aeb7a83d6f59ae30236fb9f6b5d20fb618ef6c6777", size = 5359622, upload-time = "2025-08-17T18:22:45.802Z" }, - { url = "https://files.pythonhosted.org/packages/60/ff/2cbab815d6f02a53a9d8d8703bc727d8408a2e508143ca9af6c3cca2054b/zstandard-0.24.0-cp312-cp312-win32.whl", hash = "sha256:ff19efaa33e7f136fe95f9bbcc90ab7fb60648453b03f95d1de3ab6997de0f32", size = 435968, upload-time = "2025-08-17T18:22:49.493Z" }, - { url = "https://files.pythonhosted.org/packages/ce/a3/8f96b8ddb7ad12344218fbd0fd2805702dafd126ae9f8a1fb91eef7b33da/zstandard-0.24.0-cp312-cp312-win_amd64.whl", hash = "sha256:bc05f8a875eb651d1cc62e12a4a0e6afa5cd0cc231381adb830d2e9c196ea895", size = 505195, upload-time = "2025-08-17T18:22:47.193Z" }, - { url = "https://files.pythonhosted.org/packages/a3/4a/bfca20679da63bfc236634ef2e4b1b4254203098b0170e3511fee781351f/zstandard-0.24.0-cp312-cp312-win_arm64.whl", hash = "sha256:b04c94718f7a8ed7cdd01b162b6caa1954b3c9d486f00ecbbd300f149d2b2606", size = 461605, upload-time = "2025-08-17T18:22:48.317Z" }, + { url = "https://files.pythonhosted.org/packages/2a/83/c3ca27c363d104980f1c9cee1101cc8ba724ac8c28a033ede6aab89585b1/zstandard-0.25.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:933b65d7680ea337180733cf9e87293cc5500cc0eb3fc8769f4d3c88d724ec5c", size = 795254, upload-time = "2025-09-14T22:16:26.137Z" }, + { url = "https://files.pythonhosted.org/packages/ac/4d/e66465c5411a7cf4866aeadc7d108081d8ceba9bc7abe6b14aa21c671ec3/zstandard-0.25.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3f79487c687b1fc69f19e487cd949bf3aae653d181dfb5fde3bf6d18894706f", size = 640559, upload-time = "2025-09-14T22:16:27.973Z" }, + { url = "https://files.pythonhosted.org/packages/12/56/354fe655905f290d3b147b33fe946b0f27e791e4b50a5f004c802cb3eb7b/zstandard-0.25.0-cp311-cp311-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:0bbc9a0c65ce0eea3c34a691e3c4b6889f5f3909ba4822ab385fab9057099431", size = 5348020, upload-time = "2025-09-14T22:16:29.523Z" }, + { url = "https://files.pythonhosted.org/packages/3b/13/2b7ed68bd85e69a2069bcc72141d378f22cae5a0f3b353a2c8f50ef30c1b/zstandard-0.25.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:01582723b3ccd6939ab7b3a78622c573799d5d8737b534b86d0e06ac18dbde4a", size = 5058126, upload-time = "2025-09-14T22:16:31.811Z" }, + { url = "https://files.pythonhosted.org/packages/c9/dd/fdaf0674f4b10d92cb120ccff58bbb6626bf8368f00ebfd2a41ba4a0dc99/zstandard-0.25.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5f1ad7bf88535edcf30038f6919abe087f606f62c00a87d7e33e7fc57cb69fcc", size = 5405390, upload-time = "2025-09-14T22:16:33.486Z" }, + { url = "https://files.pythonhosted.org/packages/0f/67/354d1555575bc2490435f90d67ca4dd65238ff2f119f30f72d5cde09c2ad/zstandard-0.25.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:06acb75eebeedb77b69048031282737717a63e71e4ae3f77cc0c3b9508320df6", size = 5452914, upload-time = "2025-09-14T22:16:35.277Z" }, + { url = "https://files.pythonhosted.org/packages/bb/1f/e9cfd801a3f9190bf3e759c422bbfd2247db9d7f3d54a56ecde70137791a/zstandard-0.25.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9300d02ea7c6506f00e627e287e0492a5eb0371ec1670ae852fefffa6164b072", size = 5559635, upload-time = "2025-09-14T22:16:37.141Z" }, + { url = "https://files.pythonhosted.org/packages/21/88/5ba550f797ca953a52d708c8e4f380959e7e3280af029e38fbf47b55916e/zstandard-0.25.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bfd06b1c5584b657a2892a6014c2f4c20e0db0208c159148fa78c65f7e0b0277", size = 5048277, upload-time = "2025-09-14T22:16:38.807Z" }, + { url = "https://files.pythonhosted.org/packages/46/c0/ca3e533b4fa03112facbe7fbe7779cb1ebec215688e5df576fe5429172e0/zstandard-0.25.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f373da2c1757bb7f1acaf09369cdc1d51d84131e50d5fa9863982fd626466313", size = 5574377, upload-time = "2025-09-14T22:16:40.523Z" }, + { url = "https://files.pythonhosted.org/packages/12/9b/3fb626390113f272abd0799fd677ea33d5fc3ec185e62e6be534493c4b60/zstandard-0.25.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6c0e5a65158a7946e7a7affa6418878ef97ab66636f13353b8502d7ea03c8097", size = 4961493, upload-time = "2025-09-14T22:16:43.3Z" }, + { url = "https://files.pythonhosted.org/packages/cb/d3/23094a6b6a4b1343b27ae68249daa17ae0651fcfec9ed4de09d14b940285/zstandard-0.25.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c8e167d5adf59476fa3e37bee730890e389410c354771a62e3c076c86f9f7778", size = 5269018, upload-time = "2025-09-14T22:16:45.292Z" }, + { url = "https://files.pythonhosted.org/packages/8c/a7/bb5a0c1c0f3f4b5e9d5b55198e39de91e04ba7c205cc46fcb0f95f0383c1/zstandard-0.25.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:98750a309eb2f020da61e727de7d7ba3c57c97cf6213f6f6277bb7fb42a8e065", size = 5443672, upload-time = "2025-09-14T22:16:47.076Z" }, + { url = "https://files.pythonhosted.org/packages/27/22/503347aa08d073993f25109c36c8d9f029c7d5949198050962cb568dfa5e/zstandard-0.25.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:22a086cff1b6ceca18a8dd6096ec631e430e93a8e70a9ca5efa7561a00f826fa", size = 5822753, upload-time = "2025-09-14T22:16:49.316Z" }, + { url = "https://files.pythonhosted.org/packages/e2/be/94267dc6ee64f0f8ba2b2ae7c7a2df934a816baaa7291db9e1aa77394c3c/zstandard-0.25.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:72d35d7aa0bba323965da807a462b0966c91608ef3a48ba761678cb20ce5d8b7", size = 5366047, upload-time = "2025-09-14T22:16:51.328Z" }, + { url = "https://files.pythonhosted.org/packages/7b/a3/732893eab0a3a7aecff8b99052fecf9f605cf0fb5fb6d0290e36beee47a4/zstandard-0.25.0-cp311-cp311-win32.whl", hash = "sha256:f5aeea11ded7320a84dcdd62a3d95b5186834224a9e55b92ccae35d21a8b63d4", size = 436484, upload-time = "2025-09-14T22:16:55.005Z" }, + { url = "https://files.pythonhosted.org/packages/43/a3/c6155f5c1cce691cb80dfd38627046e50af3ee9ddc5d0b45b9b063bfb8c9/zstandard-0.25.0-cp311-cp311-win_amd64.whl", hash = "sha256:daab68faadb847063d0c56f361a289c4f268706b598afbf9ad113cbe5c38b6b2", size = 506183, upload-time = "2025-09-14T22:16:52.753Z" }, + { url = "https://files.pythonhosted.org/packages/8c/3e/8945ab86a0820cc0e0cdbf38086a92868a9172020fdab8a03ac19662b0e5/zstandard-0.25.0-cp311-cp311-win_arm64.whl", hash = "sha256:22a06c5df3751bb7dc67406f5374734ccee8ed37fc5981bf1ad7041831fa1137", size = 462533, upload-time = "2025-09-14T22:16:53.878Z" }, + { url = "https://files.pythonhosted.org/packages/82/fc/f26eb6ef91ae723a03e16eddb198abcfce2bc5a42e224d44cc8b6765e57e/zstandard-0.25.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7b3c3a3ab9daa3eed242d6ecceead93aebbb8f5f84318d82cee643e019c4b73b", size = 795738, upload-time = "2025-09-14T22:16:56.237Z" }, + { url = "https://files.pythonhosted.org/packages/aa/1c/d920d64b22f8dd028a8b90e2d756e431a5d86194caa78e3819c7bf53b4b3/zstandard-0.25.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:913cbd31a400febff93b564a23e17c3ed2d56c064006f54efec210d586171c00", size = 640436, upload-time = "2025-09-14T22:16:57.774Z" }, + { url = "https://files.pythonhosted.org/packages/53/6c/288c3f0bd9fcfe9ca41e2c2fbfd17b2097f6af57b62a81161941f09afa76/zstandard-0.25.0-cp312-cp312-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:011d388c76b11a0c165374ce660ce2c8efa8e5d87f34996aa80f9c0816698b64", size = 5343019, upload-time = "2025-09-14T22:16:59.302Z" }, + { url = "https://files.pythonhosted.org/packages/1e/15/efef5a2f204a64bdb5571e6161d49f7ef0fffdbca953a615efbec045f60f/zstandard-0.25.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6dffecc361d079bb48d7caef5d673c88c8988d3d33fb74ab95b7ee6da42652ea", size = 5063012, upload-time = "2025-09-14T22:17:01.156Z" }, + { url = "https://files.pythonhosted.org/packages/b7/37/a6ce629ffdb43959e92e87ebdaeebb5ac81c944b6a75c9c47e300f85abdf/zstandard-0.25.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7149623bba7fdf7e7f24312953bcf73cae103db8cae49f8154dd1eadc8a29ecb", size = 5394148, upload-time = "2025-09-14T22:17:03.091Z" }, + { url = "https://files.pythonhosted.org/packages/e3/79/2bf870b3abeb5c070fe2d670a5a8d1057a8270f125ef7676d29ea900f496/zstandard-0.25.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:6a573a35693e03cf1d67799fd01b50ff578515a8aeadd4595d2a7fa9f3ec002a", size = 5451652, upload-time = "2025-09-14T22:17:04.979Z" }, + { url = "https://files.pythonhosted.org/packages/53/60/7be26e610767316c028a2cbedb9a3beabdbe33e2182c373f71a1c0b88f36/zstandard-0.25.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5a56ba0db2d244117ed744dfa8f6f5b366e14148e00de44723413b2f3938a902", size = 5546993, upload-time = "2025-09-14T22:17:06.781Z" }, + { url = "https://files.pythonhosted.org/packages/85/c7/3483ad9ff0662623f3648479b0380d2de5510abf00990468c286c6b04017/zstandard-0.25.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:10ef2a79ab8e2974e2075fb984e5b9806c64134810fac21576f0668e7ea19f8f", size = 5046806, upload-time = "2025-09-14T22:17:08.415Z" }, + { url = "https://files.pythonhosted.org/packages/08/b3/206883dd25b8d1591a1caa44b54c2aad84badccf2f1de9e2d60a446f9a25/zstandard-0.25.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aaf21ba8fb76d102b696781bddaa0954b782536446083ae3fdaa6f16b25a1c4b", size = 5576659, upload-time = "2025-09-14T22:17:10.164Z" }, + { url = "https://files.pythonhosted.org/packages/9d/31/76c0779101453e6c117b0ff22565865c54f48f8bd807df2b00c2c404b8e0/zstandard-0.25.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1869da9571d5e94a85a5e8d57e4e8807b175c9e4a6294e3b66fa4efb074d90f6", size = 4953933, upload-time = "2025-09-14T22:17:11.857Z" }, + { url = "https://files.pythonhosted.org/packages/18/e1/97680c664a1bf9a247a280a053d98e251424af51f1b196c6d52f117c9720/zstandard-0.25.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:809c5bcb2c67cd0ed81e9229d227d4ca28f82d0f778fc5fea624a9def3963f91", size = 5268008, upload-time = "2025-09-14T22:17:13.627Z" }, + { url = "https://files.pythonhosted.org/packages/1e/73/316e4010de585ac798e154e88fd81bb16afc5c5cb1a72eeb16dd37e8024a/zstandard-0.25.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f27662e4f7dbf9f9c12391cb37b4c4c3cb90ffbd3b1fb9284dadbbb8935fa708", size = 5433517, upload-time = "2025-09-14T22:17:16.103Z" }, + { url = "https://files.pythonhosted.org/packages/5b/60/dd0f8cfa8129c5a0ce3ea6b7f70be5b33d2618013a161e1ff26c2b39787c/zstandard-0.25.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:99c0c846e6e61718715a3c9437ccc625de26593fea60189567f0118dc9db7512", size = 5814292, upload-time = "2025-09-14T22:17:17.827Z" }, + { url = "https://files.pythonhosted.org/packages/fc/5f/75aafd4b9d11b5407b641b8e41a57864097663699f23e9ad4dbb91dc6bfe/zstandard-0.25.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:474d2596a2dbc241a556e965fb76002c1ce655445e4e3bf38e5477d413165ffa", size = 5360237, upload-time = "2025-09-14T22:17:19.954Z" }, + { url = "https://files.pythonhosted.org/packages/ff/8d/0309daffea4fcac7981021dbf21cdb2e3427a9e76bafbcdbdf5392ff99a4/zstandard-0.25.0-cp312-cp312-win32.whl", hash = "sha256:23ebc8f17a03133b4426bcc04aabd68f8236eb78c3760f12783385171b0fd8bd", size = 436922, upload-time = "2025-09-14T22:17:24.398Z" }, + { url = "https://files.pythonhosted.org/packages/79/3b/fa54d9015f945330510cb5d0b0501e8253c127cca7ebe8ba46a965df18c5/zstandard-0.25.0-cp312-cp312-win_amd64.whl", hash = "sha256:ffef5a74088f1e09947aecf91011136665152e0b4b359c42be3373897fb39b01", size = 506276, upload-time = "2025-09-14T22:17:21.429Z" }, + { url = "https://files.pythonhosted.org/packages/ea/6b/8b51697e5319b1f9ac71087b0af9a40d8a6288ff8025c36486e0c12abcc4/zstandard-0.25.0-cp312-cp312-win_arm64.whl", hash = "sha256:181eb40e0b6a29b3cd2849f825e0fa34397f649170673d385f3598ae17cca2e9", size = 462679, upload-time = "2025-09-14T22:17:23.147Z" }, ] From efbd0b9ea0d3892701651d35a2fd953ec26f3b56 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 19 Sep 2025 16:47:07 -0700 Subject: [PATCH 033/910] cabana typo --- tools/cabana/mainwin.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/cabana/mainwin.cc b/tools/cabana/mainwin.cc index 8361befb5..d65fc5b76 100644 --- a/tools/cabana/mainwin.cc +++ b/tools/cabana/mainwin.cc @@ -122,7 +122,7 @@ void MainWindow::createActions() { auto undo_act = UndoStack::instance()->createUndoAction(this, tr("&Undo")); undo_act->setShortcuts(QKeySequence::Undo); edit_menu->addAction(undo_act); - auto redo_act = UndoStack::instance()->createRedoAction(this, tr("&Rndo")); + auto redo_act = UndoStack::instance()->createRedoAction(this, tr("&Redo")); redo_act->setShortcuts(QKeySequence::Redo); edit_menu->addAction(redo_act); edit_menu->addSeparator(); From c7a37c06d879bf62f4eac957de281a6ceb7d39af Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 19 Sep 2025 17:18:08 -0700 Subject: [PATCH 034/910] Revert "Capnp memoryview (#36163)" This reverts commit 6ed8f07cb629a6cdcb71739b2022b1281525c211. bump --- opendbc_repo | 2 +- pyproject.toml | 4 +- system/loggerd/tests/test_loggerd.py | 2 +- system/webrtc/device/video.py | 2 +- system/webrtc/webrtcd.py | 11 +- uv.lock | 773 +++++++++++++-------------- 6 files changed, 388 insertions(+), 406 deletions(-) diff --git a/opendbc_repo b/opendbc_repo index 0c8676f74..c70bd060c 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 0c8676f74cf386ac914faa80a6b14303693ccde9 +Subproject commit c70bd060c6a410c1083186a1e4165e43a4eda0df diff --git a/pyproject.toml b/pyproject.toml index 3d2c2a6f7..489876f78 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,7 @@ dependencies = [ # core "cffi", "scons", - "pycapnp", + "pycapnp==2.1.0", "Cython", "setuptools", "numpy >=2.0", @@ -119,7 +119,7 @@ dev = [ "tabulate", "types-requests", "types-tabulate", - "raylib==5.5.0.2", # 5.5.0.3 has regression with gui_set_style + "raylib", ] tools = [ diff --git a/system/loggerd/tests/test_loggerd.py b/system/loggerd/tests/test_loggerd.py index d63d7d84e..c6a4b12e6 100644 --- a/system/loggerd/tests/test_loggerd.py +++ b/system/loggerd/tests/test_loggerd.py @@ -178,7 +178,7 @@ class TestLoggerd: assert logged_params['AccessToken'] == b'', f"DONT_LOG param value was logged: {repr(logged_params['AccessToken'])}" for param_key, initData_key, v in fake_params: assert getattr(initData, initData_key) == v - assert logged_params[param_key].tobytes().decode() == v + assert logged_params[param_key].decode() == v @pytest.mark.xdist_group("camera_encoder_tests") # setting xdist group ensures tests are run in same worker, prevents encoderd from crashing def test_rotation(self): diff --git a/system/webrtc/device/video.py b/system/webrtc/device/video.py index ffa1565cb..1bca90929 100644 --- a/system/webrtc/device/video.py +++ b/system/webrtc/device/video.py @@ -30,7 +30,7 @@ class LiveStreamVideoStreamTrack(TiciVideoStreamTrack): evta = getattr(msg, msg.which()) - packet = av.Packet(evta.header.tobytes() + evta.data.tobytes()) + packet = av.Packet(evta.header + evta.data) packet.time_base = self._time_base packet.pts = self._pts diff --git a/system/webrtc/webrtcd.py b/system/webrtc/webrtcd.py index 34abe8ab4..fb93e565f 100755 --- a/system/webrtc/webrtcd.py +++ b/system/webrtc/webrtcd.py @@ -22,13 +22,6 @@ from openpilot.system.webrtc.schema import generate_field from cereal import messaging, log -# pycapnp 2.2.0+ is using memoryview instead of bytes for data fields -def memoryview_fallback(obj): - if isinstance(obj, memoryview): - return obj.tobytes().decode() - return obj - - class CerealOutgoingMessageProxy: def __init__(self, sm: messaging.SubMaster): self.sm = sm @@ -44,8 +37,6 @@ class CerealOutgoingMessageProxy: msg_dict = [self.to_json(msg) for msg in msg_content] elif isinstance(msg_content, bytes): msg_dict = msg_content.decode() - elif isinstance(msg_content, memoryview): - msg_dict = msg_content.tobytes().decode() else: msg_dict = msg_content @@ -60,7 +51,7 @@ class CerealOutgoingMessageProxy: msg_dict = self.to_json(self.sm[service]) mono_time, valid = self.sm.logMonoTime[service], self.sm.valid[service] outgoing_msg = {"type": service, "logMonoTime": mono_time, "valid": valid, "data": msg_dict} - encoded_msg = json.dumps(outgoing_msg, default=memoryview_fallback).encode() + encoded_msg = json.dumps(outgoing_msg).encode() for channel in self.channels: channel.send(encoded_msg) diff --git a/uv.lock b/uv.lock index 0b2bca29d..25d2b626c 100644 --- a/uv.lock +++ b/uv.lock @@ -152,21 +152,21 @@ wheels = [ [[package]] name = "azure-core" -version = "1.35.1" +version = "1.35.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "requests" }, { name = "six" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/15/6b/2653adc0f33adba8f11b1903701e6b1c10d34ce5d8e25dfa13a422f832b0/azure_core-1.35.1.tar.gz", hash = "sha256:435d05d6df0fff2f73fb3c15493bb4721ede14203f1ff1382aa6b6b2bdd7e562", size = 345290, upload-time = "2025-09-11T22:58:04.481Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ce/89/f53968635b1b2e53e4aad2dd641488929fef4ca9dfb0b97927fa7697ddf3/azure_core-1.35.0.tar.gz", hash = "sha256:c0be528489485e9ede59b6971eb63c1eaacf83ef53001bfe3904e475e972be5c", size = 339689, upload-time = "2025-07-03T00:55:23.496Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/27/52/805980aa1ba18282077c484dba634ef0ede1e84eec8be9c92b2e162d0ed6/azure_core-1.35.1-py3-none-any.whl", hash = "sha256:12da0c9e08e48e198f9158b56ddbe33b421477e1dc98c2e1c8f9e254d92c468b", size = 211800, upload-time = "2025-09-11T22:58:06.281Z" }, + { url = "https://files.pythonhosted.org/packages/d4/78/bf94897361fdd650850f0f2e405b2293e2f12808239046232bdedf554301/azure_core-1.35.0-py3-none-any.whl", hash = "sha256:8db78c72868a58f3de8991eb4d22c4d368fae226dac1002998d6c50437e7dad1", size = 210708, upload-time = "2025-07-03T00:55:25.238Z" }, ] [[package]] name = "azure-identity" -version = "1.25.0" +version = "1.24.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "azure-core" }, @@ -175,9 +175,9 @@ dependencies = [ { name = "msal-extensions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4e/9e/4c9682a286c3c89e437579bd9f64f311020e5125c1321fd3a653166b5716/azure_identity-1.25.0.tar.gz", hash = "sha256:4177df34d684cddc026e6cf684e1abb57767aa9d84e7f2129b080ec45eee7733", size = 278507, upload-time = "2025-09-12T01:30:04.418Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/44/f3ee20bacb220b6b4a2b0a6cf7e742eecb383a5ccf604dd79ec27c286b7e/azure_identity-1.24.0.tar.gz", hash = "sha256:6c3a40b2a70af831e920b89e6421e8dcd4af78a0cb38b9642d86c67643d4930c", size = 271630, upload-time = "2025-08-07T22:27:36.258Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/75/54/81683b6756676a22e037b209695b08008258e603f7e47c56834029c5922a/azure_identity-1.25.0-py3-none-any.whl", hash = "sha256:becaec086bbdf8d1a6aa4fb080c2772a0f824a97d50c29637ec8cc4933f1e82d", size = 190861, upload-time = "2025-09-12T01:30:06.474Z" }, + { url = "https://files.pythonhosted.org/packages/a9/74/17428cb429e8d52f6d0d69ed685f4760a545cb0156594963a9337b53b6c9/azure_identity-1.24.0-py3-none-any.whl", hash = "sha256:9e04997cde0ab02ed66422c74748548e620b7b29361c72ce622acab0267ff7c4", size = 187890, upload-time = "2025-08-07T22:27:38.033Z" }, ] [[package]] @@ -197,25 +197,25 @@ wheels = [ [[package]] name = "casadi" -version = "3.7.2" +version = "3.7.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/92/62/1e98662024915ecb09c6894c26a3f497f4afa66570af3f53db4651fc45f1/casadi-3.7.2.tar.gz", hash = "sha256:b4d7bd8acdc4180306903ae1c9eddaf41be2a3ae2fa7154c57174ae64acdc60d", size = 6053600, upload-time = "2025-09-10T10:05:49.521Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b1/21/1b4ea75dbddfcdbc7cd70d83147dde72899667097c0c05b288fd7015ef10/casadi-3.7.1.tar.gz", hash = "sha256:12577155cd3cd79ba162381bfed6add1541bc770ba3f1f1334e4eb159d9b39ba", size = 6065586, upload-time = "2025-07-23T21:47:56.483Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/29/01/d5e3058775ec8e24a01eb74d36099493b872536ef9e39f1e49624b977778/casadi-3.7.2-cp311-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:f43b0562d05a5e6e81f1885fc4ae426c382e36eebfd8d27f1baff6052178a9b0", size = 47115880, upload-time = "2025-09-10T07:52:24.399Z" }, - { url = "https://files.pythonhosted.org/packages/0e/cf/4af27e010d599a5419129d34fdde41637029a1cca2a40bef0965d6d52228/casadi-3.7.2-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:70add3334b437b60a9bc0f864d094350f1a4fcbf9e8bafec870b61aed64674df", size = 42293337, upload-time = "2025-09-10T08:03:32.556Z" }, - { url = "https://files.pythonhosted.org/packages/ac/4c/d1a50cc840103e00effcbaf8e911b6b3fb6ba2c8f4025466f524854968ed/casadi-3.7.2-cp311-none-manylinux2014_aarch64.whl", hash = "sha256:392d3367a4b33cf223013dad8122a0e549da40b1702a5375f82f85b563e5c0cf", size = 47277175, upload-time = "2025-09-10T08:04:08.811Z" }, - { url = "https://files.pythonhosted.org/packages/be/29/6e5714d124e6ddafbccc3ed774ca603081caa1175c7f0e1c52484184dfb3/casadi-3.7.2-cp311-none-manylinux2014_i686.whl", hash = "sha256:2ce09e0ced6df33048dccd582b5cfa2c9ff5193b12858b2584078afc17761905", size = 72438460, upload-time = "2025-09-10T08:05:02.769Z" }, - { url = "https://files.pythonhosted.org/packages/23/32/ac1f3999273aa4aae48516f6f4b7b267e0cc70d8527866989798cb81312f/casadi-3.7.2-cp311-none-manylinux2014_x86_64.whl", hash = "sha256:5086799a46d10ba884b72fd02c21be09dae52cbc189272354a5d424791b55f37", size = 75574474, upload-time = "2025-09-10T08:06:00.709Z" }, - { url = "https://files.pythonhosted.org/packages/68/78/7fd10709504c1757f70db3893870a891fcb9f1ec9f05e8ef2e3f3b9d7e2f/casadi-3.7.2-cp311-none-win_amd64.whl", hash = "sha256:72aa5727417d781ed216f16b5e93c6ddca5db27d83b0015a729e8ad570cdc465", size = 50994144, upload-time = "2025-09-10T08:06:42.384Z" }, - { url = "https://files.pythonhosted.org/packages/65/c8/689d085447b1966f42bdb8aa4fbebef49a09697dbee32ab02a865c17ac1b/casadi-3.7.2-cp312-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:309ea41a69c9230390d349b0dd899c6a19504d1904c0756bef463e47fb5c8f9a", size = 47116756, upload-time = "2025-09-10T07:53:00.931Z" }, - { url = "https://files.pythonhosted.org/packages/1e/c0/3c4704394a6fd4dfb2123a4fd71ba64a001f340670a3eba45be7a19ac736/casadi-3.7.2-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:6033381234db810b2247d16c6352e679a009ec4365d04008fc768866e011ed58", size = 42293718, upload-time = "2025-09-10T08:07:16.415Z" }, - { url = "https://files.pythonhosted.org/packages/f3/24/4cf05469ddf8544da5e92f359f96d716a97e7482999f085a632bc4ef344a/casadi-3.7.2-cp312-none-manylinux2014_aarch64.whl", hash = "sha256:732f2804d0766454bb75596339e4f2da6662ffb669621da0f630ed4af9e83d6a", size = 47276175, upload-time = "2025-09-10T08:08:09.29Z" }, - { url = "https://files.pythonhosted.org/packages/82/08/b5f57fea03128efd5c860673b6ac44776352e6c1af862b8177f4c503fffe/casadi-3.7.2-cp312-none-manylinux2014_i686.whl", hash = "sha256:cf17298ff0c162735bdf9bf72b765c636ae732130604017a3b52e26e35402857", size = 72430454, upload-time = "2025-09-10T08:09:10.781Z" }, - { url = "https://files.pythonhosted.org/packages/24/ab/d7233c915b12c005655437c6c4cf0ae46cbbb2b20d743cb5e4881ad3104a/casadi-3.7.2-cp312-none-manylinux2014_x86_64.whl", hash = "sha256:cde616930fa1440ad66f1850670399423edd37354eed9b12e74b3817b98d1187", size = 75568903, upload-time = "2025-09-10T08:10:07.108Z" }, - { url = "https://files.pythonhosted.org/packages/3e/b9/5b984124f539656efdf079f3d8f09d73667808ec8d0546e6bce6dc60ade6/casadi-3.7.2-cp312-none-win_amd64.whl", hash = "sha256:81d677d2b020c1307c1eb25eae15686e5de199bb066828c3eaabdfaaaf457ffd", size = 50991347, upload-time = "2025-09-10T08:10:46.629Z" }, + { url = "https://files.pythonhosted.org/packages/e6/4d/cfe092b2deb65dbf913d38148e30dfe41bbe9f289f892ea7a5d3526532d2/casadi-3.7.1-cp311-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:c13fafdff0cdf73392fe2002245befe7496b5ac99b70adb64babff0099068c8a", size = 47100775, upload-time = "2025-07-23T19:56:59.317Z" }, + { url = "https://files.pythonhosted.org/packages/13/41/c4ad627e447a083fbfd2826f1f4684223f0072fdc49f58157ca6a6ce0a77/casadi-3.7.1-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:74d9c6bad3c7cb347494d52cc3e6faf3fbf3300046fab0e1ddc9439d3fd68486", size = 42282155, upload-time = "2025-07-23T19:59:11.191Z" }, + { url = "https://files.pythonhosted.org/packages/ca/e2/53733933d454657e319cc8074e004ec46660c4797cbffcaf7695a874b0da/casadi-3.7.1-cp311-none-manylinux2014_aarch64.whl", hash = "sha256:6fbc0803e6c7ffb9ba9a41ba38e7c1dadee73835edbacacce3530343f078f3f9", size = 47272638, upload-time = "2025-07-23T20:03:33.017Z" }, + { url = "https://files.pythonhosted.org/packages/e1/72/8002d4fe64325842eb6a7f7ac1e4574c02584e85e5b9d93a08a789095505/casadi-3.7.1-cp311-none-manylinux2014_i686.whl", hash = "sha256:38a16b0c7caff5297df91c7e3a65091824979367e6fc8e6eb0e5ab9f1f832af8", size = 72418203, upload-time = "2025-07-23T20:07:18.681Z" }, + { url = "https://files.pythonhosted.org/packages/96/e5/065287b78c4f6a00a010d7c3f5316df1b95f7c5bda644f2151b54a79a964/casadi-3.7.1-cp311-none-manylinux2014_x86_64.whl", hash = "sha256:2dd75cc1b5ca8009586186e4a63e0cdd83a672bf308edc75ef84f9c896cd971d", size = 75558461, upload-time = "2025-07-23T21:33:20.771Z" }, + { url = "https://files.pythonhosted.org/packages/b0/4c/084d2f3bca33d0b3d509be17c3b678f6bcc03f3409d42e88750cb1e44966/casadi-3.7.1-cp311-none-win_amd64.whl", hash = "sha256:3ab68c6dd621b5f150635bf06aa8e1697cae2fbe0137e9d47270c54d305c334c", size = 50987255, upload-time = "2025-07-23T21:34:20.349Z" }, + { url = "https://files.pythonhosted.org/packages/e1/a3/65942d3e0f7589a72e69dd9dee575963ecf51504e985f132bce8b0d24988/casadi-3.7.1-cp312-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:4a29ae3212b0ae6931d439ca3bfa2ac279826e99c2c9a443704e84021933ab76", size = 47102059, upload-time = "2025-07-23T20:36:47.174Z" }, + { url = "https://files.pythonhosted.org/packages/2e/f4/dc371199583f6b154ba0966c4132418737a2d2e019c9f6ad227ebd279684/casadi-3.7.1-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:70edf1df3fc83401eb5a2f7053ae8394f4e612e61c2ad6de088e3d76d0ec845e", size = 42282702, upload-time = "2025-07-23T20:39:41.499Z" }, + { url = "https://files.pythonhosted.org/packages/8e/ff/5f8a9378cd7b322d1dcc8b8aa9fbc454ad57152b754c94b50ffdc67254a0/casadi-3.7.1-cp312-none-manylinux2014_aarch64.whl", hash = "sha256:a1717bbd07a6eb98acddf5ea5da7e78a9b85b75dbabb55190cc6b0f2812ef39d", size = 47271636, upload-time = "2025-07-23T20:42:59.961Z" }, + { url = "https://files.pythonhosted.org/packages/e1/11/e1b6a76bf288fc86dc8919cbcd2d3a654e7ead4240d9928deb8ed4ee91da/casadi-3.7.1-cp312-none-manylinux2014_i686.whl", hash = "sha256:57ae29d16af0716ebc15d161bcf0bcd97d35631b10c0e80838290d249cac80d7", size = 72410197, upload-time = "2025-07-23T21:35:35.509Z" }, + { url = "https://files.pythonhosted.org/packages/c1/76/bb43ff625066b178132172d41db0c5c962b4af9db15c88b8a23e65376d18/casadi-3.7.1-cp312-none-manylinux2014_x86_64.whl", hash = "sha256:756f3d07d3414dc54f05ebec883ac3db6e307bfe661719d188340504b6bed420", size = 75552890, upload-time = "2025-07-23T21:37:14.638Z" }, + { url = "https://files.pythonhosted.org/packages/11/0c/8026b0b238e00a2e63c7142505cbcd0931b4db4140d55c83962f9d8e0b02/casadi-3.7.1-cp312-none-win_amd64.whl", hash = "sha256:b228fbcd1f41eb8d5405dbc1e0ecb780daf89808392706bb77fd2b240f8a437e", size = 50984453, upload-time = "2025-07-23T21:38:18.21Z" }, ] [[package]] @@ -229,38 +229,36 @@ wheels = [ [[package]] name = "cffi" -version = "2.0.0" +version = "1.17.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pycparser", marker = "implementation_name != 'PyPy'" }, + { name = "pycparser" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621, upload-time = "2024-09-04T20:45:21.852Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" }, - { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" }, - { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" }, - { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" }, - { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" }, - { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" }, - { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" }, - { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" }, - { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" }, - { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" }, - { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" }, - { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" }, - { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" }, - { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, - { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, - { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, - { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, - { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, - { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, - { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, - { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, - { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, - { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, - { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, - { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, + { url = "https://files.pythonhosted.org/packages/6b/f4/927e3a8899e52a27fa57a48607ff7dc91a9ebe97399b357b85a0c7892e00/cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", size = 182264, upload-time = "2024-09-04T20:43:51.124Z" }, + { url = "https://files.pythonhosted.org/packages/6c/f5/6c3a8efe5f503175aaddcbea6ad0d2c96dad6f5abb205750d1b3df44ef29/cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", size = 178651, upload-time = "2024-09-04T20:43:52.872Z" }, + { url = "https://files.pythonhosted.org/packages/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", size = 445259, upload-time = "2024-09-04T20:43:56.123Z" }, + { url = "https://files.pythonhosted.org/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", size = 469200, upload-time = "2024-09-04T20:43:57.891Z" }, + { url = "https://files.pythonhosted.org/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", size = 477235, upload-time = "2024-09-04T20:44:00.18Z" }, + { url = "https://files.pythonhosted.org/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", size = 459721, upload-time = "2024-09-04T20:44:01.585Z" }, + { url = "https://files.pythonhosted.org/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", size = 467242, upload-time = "2024-09-04T20:44:03.467Z" }, + { url = "https://files.pythonhosted.org/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", size = 477999, upload-time = "2024-09-04T20:44:05.023Z" }, + { url = "https://files.pythonhosted.org/packages/44/74/f2a2460684a1a2d00ca799ad880d54652841a780c4c97b87754f660c7603/cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", size = 454242, upload-time = "2024-09-04T20:44:06.444Z" }, + { url = "https://files.pythonhosted.org/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", size = 478604, upload-time = "2024-09-04T20:44:08.206Z" }, + { url = "https://files.pythonhosted.org/packages/34/33/e1b8a1ba29025adbdcda5fb3a36f94c03d771c1b7b12f726ff7fef2ebe36/cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", size = 171727, upload-time = "2024-09-04T20:44:09.481Z" }, + { url = "https://files.pythonhosted.org/packages/3d/97/50228be003bb2802627d28ec0627837ac0bf35c90cf769812056f235b2d1/cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", size = 181400, upload-time = "2024-09-04T20:44:10.873Z" }, + { url = "https://files.pythonhosted.org/packages/5a/84/e94227139ee5fb4d600a7a4927f322e1d4aea6fdc50bd3fca8493caba23f/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", size = 183178, upload-time = "2024-09-04T20:44:12.232Z" }, + { url = "https://files.pythonhosted.org/packages/da/ee/fb72c2b48656111c4ef27f0f91da355e130a923473bf5ee75c5643d00cca/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", size = 178840, upload-time = "2024-09-04T20:44:13.739Z" }, + { url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803, upload-time = "2024-09-04T20:44:15.231Z" }, + { url = "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850, upload-time = "2024-09-04T20:44:17.188Z" }, + { url = "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729, upload-time = "2024-09-04T20:44:18.688Z" }, + { url = "https://files.pythonhosted.org/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", size = 471256, upload-time = "2024-09-04T20:44:20.248Z" }, + { url = "https://files.pythonhosted.org/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424, upload-time = "2024-09-04T20:44:21.673Z" }, + { url = "https://files.pythonhosted.org/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568, upload-time = "2024-09-04T20:44:23.245Z" }, + { url = "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736, upload-time = "2024-09-04T20:44:24.757Z" }, + { url = "https://files.pythonhosted.org/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448, upload-time = "2024-09-04T20:44:26.208Z" }, + { url = "https://files.pythonhosted.org/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976, upload-time = "2024-09-04T20:44:27.578Z" }, ] [[package]] @@ -296,14 +294,14 @@ wheels = [ [[package]] name = "click" -version = "8.3.0" +version = "8.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/46/61/de6cd827efad202d7057d93e0fed9294b96952e188f7384832791c7b2254/click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4", size = 276943, upload-time = "2025-09-18T17:32:23.696Z" } +sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc", size = 107295, upload-time = "2025-09-18T17:32:22.42Z" }, + { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" }, ] [[package]] @@ -417,31 +415,31 @@ wheels = [ [[package]] name = "cython" -version = "3.1.4" +version = "3.1.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a7/f6/d762df1f436a0618455d37f4e4c4872a7cd0dcfc8dec3022ee99e4389c69/cython-3.1.4.tar.gz", hash = "sha256:9aefefe831331e2d66ab31799814eae4d0f8a2d246cbaaaa14d1be29ef777683", size = 3190778, upload-time = "2025-09-16T07:20:33.531Z" } +sdist = { url = "https://files.pythonhosted.org/packages/18/ab/915337fb39ab4f4539a313df38fc69938df3bf14141b90d61dfd5c2919de/cython-3.1.3.tar.gz", hash = "sha256:10ee785e42328924b78f75a74f66a813cb956b4a9bc91c44816d089d5934c089", size = 3186689, upload-time = "2025-08-13T06:19:13.619Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b5/ab/0a568bac7c4c052db4ae27edf01e16f3093cdfef04a2dfd313ef1b3c478a/cython-3.1.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d1d7013dba5fb0506794d4ef8947ff5ed021370614950a8d8d04e57c8c84499e", size = 3026389, upload-time = "2025-09-16T07:22:02.212Z" }, - { url = "https://files.pythonhosted.org/packages/cb/b7/51f5566e1309215a7fef744975b2fabb56d3fdc5fa1922fd7e306c14f523/cython-3.1.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:eed989f5c139d6550ef2665b783d86fab99372590c97f10a3c26c4523c5fce9e", size = 2955954, upload-time = "2025-09-16T07:22:03.782Z" }, - { url = "https://files.pythonhosted.org/packages/28/fd/ad8314520000fe96292fb8208c640fa862baa3053d2f3453a2acb50cafb8/cython-3.1.4-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3df3beb8b024dfd73cfddb7f2f7456751cebf6e31655eed3189c209b634bc2f2", size = 3412005, upload-time = "2025-09-16T07:22:05.483Z" }, - { url = "https://files.pythonhosted.org/packages/0c/3b/e570f8bcb392e7943fc9a25d1b2d1646ef0148ff017d3681511acf6bbfdc/cython-3.1.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f8354703f1168e1aaa01348940f719734c1f11298be333bdb5b94101d49677c0", size = 3191100, upload-time = "2025-09-16T07:22:07.144Z" }, - { url = "https://files.pythonhosted.org/packages/78/81/f1ea09f563ebab732542cb11bf363710e53f3842458159ea2c160788bc8e/cython-3.1.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a928bd7d446247855f54f359057ab4a32c465219c8c1e299906a483393a59a9e", size = 3313786, upload-time = "2025-09-16T07:22:09.15Z" }, - { url = "https://files.pythonhosted.org/packages/ca/17/06575eb6175a926523bada7dac1cd05cc74add96cebbf2e8b492a2494291/cython-3.1.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c233bfff4cc7b9d629eecb7345f9b733437f76dc4441951ec393b0a6e29919fc", size = 3205775, upload-time = "2025-09-16T07:22:10.745Z" }, - { url = "https://files.pythonhosted.org/packages/10/ba/61a8cf56a76ab21ddf6476b70884feff2a2e56b6d9010e1e1b1e06c46f70/cython-3.1.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e9691a2cbc2faf0cd819108bceccf9bfc56c15a06d172eafe74157388c44a601", size = 3428423, upload-time = "2025-09-16T07:22:12.404Z" }, - { url = "https://files.pythonhosted.org/packages/c4/c2/42cf9239088d6b4b62c1c017c36e0e839f64c8d68674ce4172d0e0168d3b/cython-3.1.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ada319207432ea7c6691c70b5c112d261637d79d21ba086ae3726fedde79bfbf", size = 3330489, upload-time = "2025-09-16T07:22:14.576Z" }, - { url = "https://files.pythonhosted.org/packages/b5/08/36a619d6b1fc671a11744998e5cdd31790589e3cb4542927c97f3f351043/cython-3.1.4-cp311-cp311-win32.whl", hash = "sha256:dae81313c28222bf7be695f85ae1d16625aac35a0973a3af1e001f63379440c5", size = 2482410, upload-time = "2025-09-16T07:22:17.373Z" }, - { url = "https://files.pythonhosted.org/packages/6d/58/7d9ae7944bcd32e6f02d1a8d5d0c3875125227d050e235584127f2c64ffd/cython-3.1.4-cp311-cp311-win_amd64.whl", hash = "sha256:60d2f192059ac34c5c26527f2beac823d34aaa766ef06792a3b7f290c18ac5e2", size = 2713755, upload-time = "2025-09-16T07:22:18.949Z" }, - { url = "https://files.pythonhosted.org/packages/f0/51/2939c739cfdc67ab94935a2c4fcc75638afd15e1954552655503a4112e92/cython-3.1.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0d26af46505d0e54fe0f05e7ad089fd0eed8fa04f385f3ab88796f554467bcb9", size = 3062976, upload-time = "2025-09-16T07:22:20.517Z" }, - { url = "https://files.pythonhosted.org/packages/eb/bd/a84de57fd01017bf5dba84a49aeee826db21112282bf8d76ab97567ee15d/cython-3.1.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:66ac8bb5068156c92359e3f0eefa138c177d59d1a2e8a89467881fa7d06aba3b", size = 2970701, upload-time = "2025-09-16T07:22:22.644Z" }, - { url = "https://files.pythonhosted.org/packages/71/79/a09004c8e42f5be188c7636b1be479cdb244a6d8837e1878d062e4e20139/cython-3.1.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cb2e42714faec723d2305607a04bafb49a48a8d8f25dd39368d884c058dbcfbc", size = 3387730, upload-time = "2025-09-16T07:22:24.271Z" }, - { url = "https://files.pythonhosted.org/packages/fb/bd/979f8c59e247f562642f3eb98a1b453530e1f7954ef071835c08ed2bf6ba/cython-3.1.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0fd655b27997a209a574873304ded9629de588f021154009e8f923475e2c677", size = 3167289, upload-time = "2025-09-16T07:22:26.35Z" }, - { url = "https://files.pythonhosted.org/packages/34/f8/0b98537f0b4e8c01f76d2a6cf75389987538e4d4ac9faf25836fd18c9689/cython-3.1.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9def7c41f4dc339003b1e6875f84edf059989b9c7f5e9a245d3ce12c190742d9", size = 3321099, upload-time = "2025-09-16T07:22:27.957Z" }, - { url = "https://files.pythonhosted.org/packages/f3/39/437968a2e7c7f57eb6e1144f6aca968aa15fbbf169b2d4da5d1ff6c21442/cython-3.1.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:196555584a8716bf7e017e23ca53e9f632ed493f9faa327d0718e7551588f55d", size = 3179897, upload-time = "2025-09-16T07:22:30.014Z" }, - { url = "https://files.pythonhosted.org/packages/2c/04/b3f42915f034d133f1a34e74a2270bc2def02786f9b40dc9028fbb968814/cython-3.1.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:7fff0e739e07a20726484b8898b8628a7b87acb960d0fc5486013c6b77b7bb97", size = 3400936, upload-time = "2025-09-16T07:22:31.705Z" }, - { url = "https://files.pythonhosted.org/packages/21/eb/2ad9fa0896ab6cf29875a09a9f4aaea37c28b79b869a013bf9b58e4e652e/cython-3.1.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c2754034fa10f95052949cd6b07eb2f61d654c1b9cfa0b17ea53a269389422e8", size = 3332131, upload-time = "2025-09-16T07:22:33.32Z" }, - { url = "https://files.pythonhosted.org/packages/3c/bf/f19283f8405e7e564c3353302a8665ea2c589be63a8e1be1b503043366a9/cython-3.1.4-cp312-cp312-win32.whl", hash = "sha256:2e0808ff3614a1dbfd1adfcbff9b2b8119292f1824b3535b4a173205109509f8", size = 2487672, upload-time = "2025-09-16T07:22:35.227Z" }, - { url = "https://files.pythonhosted.org/packages/30/bf/32150a2e6c7b50b81c5dc9e942d41969400223a9c49d04e2ed955709894c/cython-3.1.4-cp312-cp312-win_amd64.whl", hash = "sha256:f262b32327b6bce340cce5d45bbfe3972cb62543a4930460d8564a489f3aea12", size = 2705348, upload-time = "2025-09-16T07:22:37.922Z" }, - { url = "https://files.pythonhosted.org/packages/7c/24/f7351052cf9db771fe4f32fca47fd66e6d9b53d8613b17faf7d130a9d553/cython-3.1.4-py3-none-any.whl", hash = "sha256:d194d95e4fa029a3f6c7d46bdd16d973808c7ea4797586911fdb67cb98b1a2c6", size = 1227541, upload-time = "2025-09-16T07:20:29.595Z" }, + { url = "https://files.pythonhosted.org/packages/b5/51/54f5d1bed7b7d003d0584996a030542497aeb05b9241782c217ea71061f5/cython-3.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4b3b2f6587b42efdece2d174a2aa4234da4524cc6673f3955c2e62b60c6d11fd", size = 3002973, upload-time = "2025-08-13T06:19:50.777Z" }, + { url = "https://files.pythonhosted.org/packages/05/07/b4043fed60070ee21b0d9ff3a877d2ecdc79231e55119ce852b79b690306/cython-3.1.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:963cf640d049fcca1cefd62d1653f859892d6dc8e4d958eb49a5babc491de6a1", size = 2865389, upload-time = "2025-08-13T06:19:52.675Z" }, + { url = "https://files.pythonhosted.org/packages/b4/e3/67d349b5310a40f281e153e826ca24d9f7ba2997c06800eda781253dccfd/cython-3.1.3-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2ab05d1bf2d5522ecff35d94ca233b77db2300413597c3ca0b6448377fa4bd7c", size = 3410316, upload-time = "2025-08-13T06:19:55.217Z" }, + { url = "https://files.pythonhosted.org/packages/49/c4/84aae921135174111091547fa726ea5f8bba718cd12b2589a70c2aab8d5c/cython-3.1.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cd517e3be052fb49443585b01f02f46080b3408e32c1108a0fdc4cc25b3c9d30", size = 3189568, upload-time = "2025-08-13T06:19:57.503Z" }, + { url = "https://files.pythonhosted.org/packages/e2/85/1bf18883f1a1f656ad83a671e54553caedb1ee2f39a3e792a14aa82557a2/cython-3.1.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a48e2180d74e3c528561d85b48f9a939a429537f9ea8aac7fb16180e7bff47e2", size = 3312649, upload-time = "2025-08-13T06:19:59.894Z" }, + { url = "https://files.pythonhosted.org/packages/68/da/cc1373decc0d14a25f1b434f47de5e27696f3092319aa5e19fcf84157415/cython-3.1.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e7c9daa90b15f59aa2a0d638ac1b36777a7e80122099952a0295c71190ce14bc", size = 3203821, upload-time = "2025-08-13T06:20:02.124Z" }, + { url = "https://files.pythonhosted.org/packages/0a/32/e10582d6f7b02ee63607f388dbbd34e329c54559c85961ddc5c655266519/cython-3.1.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:08ac646ff42781827f23b7a9b61669cdb92055f52724cd8cbe0e1defc56fce2e", size = 3426853, upload-time = "2025-08-13T06:20:04.474Z" }, + { url = "https://files.pythonhosted.org/packages/e0/da/5b0d1b5a4c7622ec812ad9374c9c6b426d69818f13f51e36f4f25ca01c86/cython-3.1.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bb0e0e7fceaffa22e4dc9600f7f75998eef5cc6ac5a8c0733b482851ba765ef2", size = 3328872, upload-time = "2025-08-13T06:20:06.834Z" }, + { url = "https://files.pythonhosted.org/packages/b0/5f/f102f6c8d27338f0baf094754c67f920828a19612053abc903e66f84506f/cython-3.1.3-cp311-cp311-win32.whl", hash = "sha256:42b1c3ebe36a52e2a8e939c0651e9ca5d30b81d03f800bbf0499deb0503ab565", size = 2480850, upload-time = "2025-08-13T06:20:08.988Z" }, + { url = "https://files.pythonhosted.org/packages/b7/60/415d0f0f7c2e227707baec11c968387d33507d2eb7f26a2950a323c705e5/cython-3.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:34a973844998281951bf54cdd0b6a9946ba03ba94580820738583a00da167d8f", size = 2712560, upload-time = "2025-08-13T06:20:11.877Z" }, + { url = "https://files.pythonhosted.org/packages/79/26/f433fdfd5182b9231819a99acc49a1f856669532306e7a38dce63ba7297e/cython-3.1.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:849ef3d15d4354e5f74cdb6d3c80d80b03209b3bf1f4ff93315890b19da18944", size = 3014237, upload-time = "2025-08-13T06:20:13.77Z" }, + { url = "https://files.pythonhosted.org/packages/e5/6c/1bebf44f5f177f8c750e608f82c08cd699b8f28cc233e799379bfcf2a2c2/cython-3.1.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:93dd0f62a3f8e93166d8584f8b243180d681ba8fed1f530b55d5f70c348c5797", size = 2844261, upload-time = "2025-08-13T06:20:15.619Z" }, + { url = "https://files.pythonhosted.org/packages/c7/74/983005ce5954f6dc8360b105a561e51a60ea619c53296afac98253d1c9d7/cython-3.1.3-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ff4a2cb84798faffb3988bd94636c3ad31a95ff44ef017f09121abffc56f84cf", size = 3388846, upload-time = "2025-08-13T06:20:17.679Z" }, + { url = "https://files.pythonhosted.org/packages/68/50/dbe7edefe9b652bc72d49da07785173e89197b9a2d64a2ac45da9795eccf/cython-3.1.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4b05319e36f34d5388deea5cc2bcfd65f9ebf76f4ea050829421a69625dbba4a", size = 3167022, upload-time = "2025-08-13T06:20:19.863Z" }, + { url = "https://files.pythonhosted.org/packages/4a/55/b50548b77203e22262002feae23a172f95282b4b8deb5ad104f08e3dc20d/cython-3.1.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7ac902a17934a6da46f80755f49413bc4c03a569ae3c834f5d66da7288ba7e6c", size = 3317782, upload-time = "2025-08-13T06:20:21.962Z" }, + { url = "https://files.pythonhosted.org/packages/f6/1b/20a97507d528dc330d67be4fbad6bc767c681d56192bce8f7117a187b2ad/cython-3.1.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7d7a555a864b1b08576f9e8a67f3789796a065837544f9f683ebf3188012fdbd", size = 3179818, upload-time = "2025-08-13T06:20:24.419Z" }, + { url = "https://files.pythonhosted.org/packages/43/a5/7b32a19c4c6bb0e2cc7ae52269b6b23a42f67f730e4abd4f8dca63660f7a/cython-3.1.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b827ce7d97ef8624adcf2bdda594b3dcb6c9b4f124d8f72001d8aea27d69dc1c", size = 3403206, upload-time = "2025-08-13T06:20:26.846Z" }, + { url = "https://files.pythonhosted.org/packages/ba/e1/08cfd4c5e99f79e62d4a7b0009bbd906748633270935c8a55eee51fead76/cython-3.1.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7851107204085f4f02d0eb6b660ddcad2ce4846e8b7a1eaba724a0bd3cd68a6b", size = 3327837, upload-time = "2025-08-13T06:20:28.946Z" }, + { url = "https://files.pythonhosted.org/packages/23/1b/f3d384be86fa2a6e110b42f42bf97295a513197aaa5532f451c73bf5b48e/cython-3.1.3-cp312-cp312-win32.whl", hash = "sha256:ed20f1b45b2da5a4f8e71a80025bca1cdc96ba35820b0b17658a4a025be920b0", size = 2485990, upload-time = "2025-08-13T06:20:31.517Z" }, + { url = "https://files.pythonhosted.org/packages/de/f0/17cff75e3c141bda73d770f7412632f53e55778d3bfdadfeec4dd3a7d1d6/cython-3.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:dc4ca0f4dec55124cd79ddcfc555be1cbe0092cc99bcf1403621d17b9c6218bb", size = 2704002, upload-time = "2025-08-13T06:20:33.773Z" }, + { url = "https://files.pythonhosted.org/packages/56/c8/46ac27096684f33e27dab749ef43c6b0119c6a0d852971eaefb73256dc4c/cython-3.1.3-py3-none-any.whl", hash = "sha256:d13025b34f72f77bf7f65c1cd628914763e6c285f4deb934314c922b91e6be5a", size = 1225725, upload-time = "2025-08-13T06:19:09.593Z" }, ] [[package]] @@ -479,11 +477,11 @@ wheels = [ [[package]] name = "dnspython" -version = "2.8.0" +version = "2.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8c/8b/57666417c0f90f08bcafa776861060426765fdb422eb10212086fb811d26/dnspython-2.8.0.tar.gz", hash = "sha256:181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f", size = 368251, upload-time = "2025-09-07T18:58:00.022Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/4a/263763cb2ba3816dd94b08ad3a33d5fdae34ecb856678773cc40a3605829/dnspython-2.7.0.tar.gz", hash = "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1", size = 345197, upload-time = "2024-10-05T20:14:59.362Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af", size = 331094, upload-time = "2025-09-07T18:57:58.071Z" }, + { url = "https://files.pythonhosted.org/packages/68/1b/e0a87d256e40e8c888847551b20a017a6b98139178505dc7ffb96f04e954/dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86", size = 313632, upload-time = "2024-10-05T20:14:57.687Z" }, ] [[package]] @@ -527,27 +525,27 @@ wheels = [ [[package]] name = "fonttools" -version = "4.60.0" +version = "4.59.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/27/d9/4eabd956fe123651a1f0efe29d9758b3837b5ae9a98934bdb571117033bb/fonttools-4.60.0.tar.gz", hash = "sha256:8f5927f049091a0ca74d35cce7f78e8f7775c83a6901a8fbe899babcc297146a", size = 3553671, upload-time = "2025-09-17T11:34:01.504Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/a5/fba25f9fbdab96e26dedcaeeba125e5f05a09043bf888e0305326e55685b/fonttools-4.59.2.tar.gz", hash = "sha256:e72c0749b06113f50bcb80332364c6be83a9582d6e3db3fe0b280f996dc2ef22", size = 3540889, upload-time = "2025-08-27T16:40:30.97Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/da/3d/c57731fbbf204ef1045caca28d5176430161ead73cd9feac3e9d9ef77ee6/fonttools-4.60.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a9106c202d68ff5f9b4a0094c4d7ad2eaa7e9280f06427b09643215e706eb016", size = 2830883, upload-time = "2025-09-17T11:32:10.552Z" }, - { url = "https://files.pythonhosted.org/packages/cc/2d/b7a6ebaed464ce441c755252cc222af11edc651d17c8f26482f429cc2c0e/fonttools-4.60.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9da3a4a3f2485b156bb429b4f8faa972480fc01f553f7c8c80d05d48f17eec89", size = 2356005, upload-time = "2025-09-17T11:32:13.248Z" }, - { url = "https://files.pythonhosted.org/packages/ee/c2/ea834e921324e2051403e125c1fe0bfbdde4951a7c1784e4ae6bdbd286cc/fonttools-4.60.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f84de764c6057b2ffd4feb50ddef481d92e348f0c70f2c849b723118d352bf3", size = 5041201, upload-time = "2025-09-17T11:32:15.373Z" }, - { url = "https://files.pythonhosted.org/packages/93/3c/1c64a338e9aa410d2d0728827d5bb1301463078cb225b94589f27558b427/fonttools-4.60.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:800b3fa0d5c12ddff02179d45b035a23989a6c597a71c8035c010fff3b2ef1bb", size = 4977696, upload-time = "2025-09-17T11:32:17.674Z" }, - { url = "https://files.pythonhosted.org/packages/07/cc/c8c411a0d9732bb886b870e052f20658fec9cf91118314f253950d2c1d65/fonttools-4.60.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd68f60b030277f292a582d31c374edfadc60bb33d51ec7b6cd4304531819ba", size = 5020386, upload-time = "2025-09-17T11:32:20.089Z" }, - { url = "https://files.pythonhosted.org/packages/13/01/1d3bc07cf92e7f4fc27f06d4494bf6078dc595b2e01b959157a4fd23df12/fonttools-4.60.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:53328e3ca9e5c8660ef6de07c35f8f312c189b757535e12141be7a8ec942de6e", size = 5131575, upload-time = "2025-09-17T11:32:22.582Z" }, - { url = "https://files.pythonhosted.org/packages/5a/16/08db3917ee19e89d2eb0ee637d37cd4136c849dc421ff63f406b9165c1a1/fonttools-4.60.0-cp311-cp311-win32.whl", hash = "sha256:d493c175ddd0b88a5376e61163e3e6fde3be8b8987db9b092e0a84650709c9e7", size = 2229297, upload-time = "2025-09-17T11:32:24.834Z" }, - { url = "https://files.pythonhosted.org/packages/d2/0b/76764da82c0dfcea144861f568d9e83f4b921e84f2be617b451257bb25a7/fonttools-4.60.0-cp311-cp311-win_amd64.whl", hash = "sha256:cc2770c9dc49c2d0366e9683f4d03beb46c98042d7ccc8ddbadf3459ecb051a7", size = 2277193, upload-time = "2025-09-17T11:32:27.094Z" }, - { url = "https://files.pythonhosted.org/packages/2a/9b/706ebf84b55ab03439c1f3a94d6915123c0d96099f4238b254fdacffe03a/fonttools-4.60.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8c68928a438d60dfde90e2f09aa7f848ed201176ca6652341744ceec4215859f", size = 2831953, upload-time = "2025-09-17T11:32:29.39Z" }, - { url = "https://files.pythonhosted.org/packages/76/40/782f485be450846e4f3aecff1f10e42af414fc6e19d235c70020f64278e1/fonttools-4.60.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b7133821249097cffabf0624eafd37f5a3358d5ce814febe9db688e3673e724e", size = 2351716, upload-time = "2025-09-17T11:32:31.46Z" }, - { url = "https://files.pythonhosted.org/packages/39/77/ad8d2a6ecc19716eb488c8cf118de10f7802e14bdf61d136d7b52358d6b1/fonttools-4.60.0-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d3638905d3d77ac8791127ce181f7cb434f37e4204d8b2e31b8f1e154320b41f", size = 4922729, upload-time = "2025-09-17T11:32:33.659Z" }, - { url = "https://files.pythonhosted.org/packages/6b/48/aa543037c6e7788e1bc36b3f858ac70a59d32d0f45915263d0b330a35140/fonttools-4.60.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7968a26ef010ae89aabbb2f8e9dec1e2709a2541bb8620790451ee8aeb4f6fbf", size = 4967188, upload-time = "2025-09-17T11:32:35.74Z" }, - { url = "https://files.pythonhosted.org/packages/ac/58/e407d2028adc6387947eff8f2940b31f4ed40b9a83c2c7bbc8b9255126e2/fonttools-4.60.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1ef01ca7847c356b0fe026b7b92304bc31dc60a4218689ee0acc66652c1a36b2", size = 4910043, upload-time = "2025-09-17T11:32:38.054Z" }, - { url = "https://files.pythonhosted.org/packages/16/ef/e78519b3c296ef757a21b792fc6a785aa2ef9a2efb098083d8ed5f6ee2ba/fonttools-4.60.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f3482d7ed7867edfcf785f77c1dffc876c4b2ddac19539c075712ff2a0703cf5", size = 5061980, upload-time = "2025-09-17T11:32:40.457Z" }, - { url = "https://files.pythonhosted.org/packages/00/4c/ad72444d1e3ef704ee90af8d5abf198016a39908d322bf41235562fb01a0/fonttools-4.60.0-cp312-cp312-win32.whl", hash = "sha256:8c937c4fe8addff575a984c9519433391180bf52cf35895524a07b520f376067", size = 2217750, upload-time = "2025-09-17T11:32:42.586Z" }, - { url = "https://files.pythonhosted.org/packages/46/55/3e8ac21963e130242f5a9ea2ebc57f5726d704bf4dcca89088b5b637b2d3/fonttools-4.60.0-cp312-cp312-win_amd64.whl", hash = "sha256:99b06d5d6f29f32e312adaed0367112f5ff2d300ea24363d377ec917daf9e8c5", size = 2266025, upload-time = "2025-09-17T11:32:44.8Z" }, - { url = "https://files.pythonhosted.org/packages/f9/a4/247d3e54eb5ed59e94e09866cfc4f9567e274fbf310ba390711851f63b3b/fonttools-4.60.0-py3-none-any.whl", hash = "sha256:496d26e4d14dcccdd6ada2e937e4d174d3138e3d73f5c9b6ec6eb2fd1dab4f66", size = 1142186, upload-time = "2025-09-17T11:33:59.287Z" }, + { url = "https://files.pythonhosted.org/packages/f8/53/742fcd750ae0bdc74de4c0ff923111199cc2f90a4ee87aaddad505b6f477/fonttools-4.59.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:511946e8d7ea5c0d6c7a53c4cb3ee48eda9ab9797cd9bf5d95829a398400354f", size = 2774961, upload-time = "2025-08-27T16:38:47.536Z" }, + { url = "https://files.pythonhosted.org/packages/57/2a/976f5f9fa3b4dd911dc58d07358467bec20e813d933bc5d3db1a955dd456/fonttools-4.59.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8e5e2682cf7be766d84f462ba8828d01e00c8751a8e8e7ce12d7784ccb69a30d", size = 2344690, upload-time = "2025-08-27T16:38:49.723Z" }, + { url = "https://files.pythonhosted.org/packages/c1/8f/b7eefc274fcf370911e292e95565c8253b0b87c82a53919ab3c795a4f50e/fonttools-4.59.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5729e12a982dba3eeae650de48b06f3b9ddb51e9aee2fcaf195b7d09a96250e2", size = 5026910, upload-time = "2025-08-27T16:38:51.904Z" }, + { url = "https://files.pythonhosted.org/packages/69/95/864726eaa8f9d4e053d0c462e64d5830ec7c599cbdf1db9e40f25ca3972e/fonttools-4.59.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c52694eae5d652361d59ecdb5a2246bff7cff13b6367a12da8499e9df56d148d", size = 4971031, upload-time = "2025-08-27T16:38:53.676Z" }, + { url = "https://files.pythonhosted.org/packages/24/4c/b8c4735ebdea20696277c70c79e0de615dbe477834e5a7c2569aa1db4033/fonttools-4.59.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f1f1bbc23ba1312bd8959896f46f667753b90216852d2a8cfa2d07e0cb234144", size = 5006112, upload-time = "2025-08-27T16:38:55.69Z" }, + { url = "https://files.pythonhosted.org/packages/3b/23/f9ea29c292aa2fc1ea381b2e5621ac436d5e3e0a5dee24ffe5404e58eae8/fonttools-4.59.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1a1bfe5378962825dabe741720885e8b9ae9745ec7ecc4a5ec1f1ce59a6062bf", size = 5117671, upload-time = "2025-08-27T16:38:58.984Z" }, + { url = "https://files.pythonhosted.org/packages/ba/07/cfea304c555bf06e86071ff2a3916bc90f7c07ec85b23bab758d4908c33d/fonttools-4.59.2-cp311-cp311-win32.whl", hash = "sha256:e937790f3c2c18a1cbc7da101550a84319eb48023a715914477d2e7faeaba570", size = 2218157, upload-time = "2025-08-27T16:39:00.75Z" }, + { url = "https://files.pythonhosted.org/packages/d7/de/35d839aa69db737a3f9f3a45000ca24721834d40118652a5775d5eca8ebb/fonttools-4.59.2-cp311-cp311-win_amd64.whl", hash = "sha256:9836394e2f4ce5f9c0a7690ee93bd90aa1adc6b054f1a57b562c5d242c903104", size = 2265846, upload-time = "2025-08-27T16:39:02.453Z" }, + { url = "https://files.pythonhosted.org/packages/ba/3d/1f45db2df51e7bfa55492e8f23f383d372200be3a0ded4bf56a92753dd1f/fonttools-4.59.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:82906d002c349cad647a7634b004825a7335f8159d0d035ae89253b4abf6f3ea", size = 2769711, upload-time = "2025-08-27T16:39:04.423Z" }, + { url = "https://files.pythonhosted.org/packages/29/df/cd236ab32a8abfd11558f296e064424258db5edefd1279ffdbcfd4fd8b76/fonttools-4.59.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a10c1bd7644dc58f8862d8ba0cf9fb7fef0af01ea184ba6ce3f50ab7dfe74d5a", size = 2340225, upload-time = "2025-08-27T16:39:06.143Z" }, + { url = "https://files.pythonhosted.org/packages/98/12/b6f9f964fe6d4b4dd4406bcbd3328821c3de1f909ffc3ffa558fe72af48c/fonttools-4.59.2-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:738f31f23e0339785fd67652a94bc69ea49e413dfdb14dcb8c8ff383d249464e", size = 4912766, upload-time = "2025-08-27T16:39:08.138Z" }, + { url = "https://files.pythonhosted.org/packages/73/78/82bde2f2d2c306ef3909b927363170b83df96171f74e0ccb47ad344563cd/fonttools-4.59.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ec99f9bdfee9cdb4a9172f9e8fd578cce5feb231f598909e0aecf5418da4f25", size = 4955178, upload-time = "2025-08-27T16:39:10.094Z" }, + { url = "https://files.pythonhosted.org/packages/92/77/7de766afe2d31dda8ee46d7e479f35c7d48747e558961489a2d6e3a02bd4/fonttools-4.59.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0476ea74161322e08c7a982f83558a2b81b491509984523a1a540baf8611cc31", size = 4897898, upload-time = "2025-08-27T16:39:12.087Z" }, + { url = "https://files.pythonhosted.org/packages/c5/77/ce0e0b905d62a06415fda9f2b2e109a24a5db54a59502b769e9e297d2242/fonttools-4.59.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:95922a922daa1f77cc72611747c156cfb38030ead72436a2c551d30ecef519b9", size = 5049144, upload-time = "2025-08-27T16:39:13.84Z" }, + { url = "https://files.pythonhosted.org/packages/d9/ea/870d93aefd23fff2e07cbeebdc332527868422a433c64062c09d4d5e7fe6/fonttools-4.59.2-cp312-cp312-win32.whl", hash = "sha256:39ad9612c6a622726a6a130e8ab15794558591f999673f1ee7d2f3d30f6a3e1c", size = 2206473, upload-time = "2025-08-27T16:39:15.854Z" }, + { url = "https://files.pythonhosted.org/packages/61/c4/e44bad000c4a4bb2e9ca11491d266e857df98ab6d7428441b173f0fe2517/fonttools-4.59.2-cp312-cp312-win_amd64.whl", hash = "sha256:980fd7388e461b19a881d35013fec32c713ffea1fc37aef2f77d11f332dfd7da", size = 2254706, upload-time = "2025-08-27T16:39:17.893Z" }, + { url = "https://files.pythonhosted.org/packages/65/a4/d2f7be3c86708912c02571db0b550121caab8cd88a3c0aacb9cfa15ea66e/fonttools-4.59.2-py3-none-any.whl", hash = "sha256:8bd0f759020e87bb5d323e6283914d9bf4ae35a7307dafb2cbd1e379e720ad37", size = 1132315, upload-time = "2025-08-27T16:40:28.984Z" }, ] [[package]] @@ -639,10 +637,10 @@ name = "gymnasium" version = "1.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cloudpickle" }, - { name = "farama-notifications" }, - { name = "numpy" }, - { name = "typing-extensions" }, + { 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'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/fd/17/c2a0e15c2cd5a8e788389b280996db927b923410de676ec5c7b2695e9261/gymnasium-1.2.0.tar.gz", hash = "sha256:344e87561012558f603880baf264ebc97f8a5c997a957b0c9f910281145534b0", size = 821142, upload-time = "2025-06-27T08:21:20.262Z" } wheels = [ @@ -739,11 +737,11 @@ wheels = [ [[package]] name = "kaitaistruct" -version = "0.11" +version = "0.10" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/27/b8/ca7319556912f68832daa4b81425314857ec08dfccd8dbc8c0f65c992108/kaitaistruct-0.11.tar.gz", hash = "sha256:053ee764288e78b8e53acf748e9733268acbd579b8d82a427b1805453625d74b", size = 11519, upload-time = "2025-09-08T15:46:25.037Z" } +sdist = { url = "https://files.pythonhosted.org/packages/54/04/dd60b9cb65d580ef6cb6eaee975ad1bdd22d46a3f51b07a1e0606710ea88/kaitaistruct-0.10.tar.gz", hash = "sha256:a044dee29173d6afbacf27bcac39daf89b654dd418cfa009ab82d9178a9ae52a", size = 7061, upload-time = "2022-07-09T00:34:06.729Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4a/4a/cf14bf3b1f5ffb13c69cf5f0ea78031247790558ee88984a8bdd22fae60d/kaitaistruct-0.11-py2.py3-none-any.whl", hash = "sha256:5c6ce79177b4e193a577ecd359e26516d1d6d000a0bffd6e1010f2a46a62a561", size = 11372, upload-time = "2025-09-08T15:46:23.635Z" }, + { url = "https://files.pythonhosted.org/packages/4e/bf/88ad23efc08708bda9a2647169828e3553bb2093a473801db61f75356395/kaitaistruct-0.10-py2.py3-none-any.whl", hash = "sha256:a97350919adbf37fda881f75e9365e2fb88d04832b7a4e57106ec70119efb235", size = 7013, upload-time = "2022-07-09T00:34:03.905Z" }, ] [[package]] @@ -844,11 +842,11 @@ wheels = [ [[package]] name = "markdown" -version = "3.9" +version = "3.8.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8d/37/02347f6d6d8279247a5837082ebc26fc0d5aaeaf75aa013fcbb433c777ab/markdown-3.9.tar.gz", hash = "sha256:d2900fe1782bd33bdbbd56859defef70c2e78fc46668f8eb9df3128138f2cb6a", size = 364585, upload-time = "2025-09-04T20:25:22.885Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/c2/4ab49206c17f75cb08d6311171f2d65798988db4360c4d1485bd0eedd67c/markdown-3.8.2.tar.gz", hash = "sha256:247b9a70dd12e27f67431ce62523e675b866d254f900c4fe75ce3dda62237c45", size = 362071, upload-time = "2025-06-19T17:12:44.483Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/70/ae/44c4a6a4cbb496d93c6257954260fe3a6e91b7bed2240e5dad2a717f5111/markdown-3.9-py3-none-any.whl", hash = "sha256:9f4d91ed810864ea88a6f32c07ba8bee1346c0cc1f6b1f9f6c822f2a9667d280", size = 107441, upload-time = "2025-09-04T20:25:21.784Z" }, + { url = "https://files.pythonhosted.org/packages/96/2b/34cc11786bc00d0f04d0f5fdc3a2b1ae0b6239eef72d3d345805f9ad92a1/markdown-3.8.2-py3-none-any.whl", hash = "sha256:5c83764dbd4e00bdd94d85a19b8d55ccca20fe35b2e678a1422b380324dd5f24", size = 106827, upload-time = "2025-06-19T17:12:42.994Z" }, ] [[package]] @@ -929,22 +927,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" }, - { 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" }, + { 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'" }, ] 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" }, @@ -1130,28 +1128,28 @@ wheels = [ [[package]] name = "mypy" -version = "1.18.2" +version = "1.17.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mypy-extensions" }, { name = "pathspec" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c0/77/8f0d0001ffad290cef2f7f216f96c814866248a0b92a722365ed54648e7e/mypy-1.18.2.tar.gz", hash = "sha256:06a398102a5f203d7477b2923dda3634c36727fa5c237d8f859ef90c42a9924b", size = 3448846, upload-time = "2025-09-19T00:11:10.519Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/22/ea637422dedf0bf36f3ef238eab4e455e2a0dcc3082b5cc067615347ab8e/mypy-1.17.1.tar.gz", hash = "sha256:25e01ec741ab5bb3eec8ba9cdb0f769230368a22c959c4937360efb89b7e9f01", size = 3352570, upload-time = "2025-07-31T07:54:19.204Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/87/cafd3ae563f88f94eec33f35ff722d043e09832ea8530ef149ec1efbaf08/mypy-1.18.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:807d9315ab9d464125aa9fcf6d84fde6e1dc67da0b6f80e7405506b8ac72bc7f", size = 12731198, upload-time = "2025-09-19T00:09:44.857Z" }, - { url = "https://files.pythonhosted.org/packages/0f/e0/1e96c3d4266a06d4b0197ace5356d67d937d8358e2ee3ffac71faa843724/mypy-1.18.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:776bb00de1778caf4db739c6e83919c1d85a448f71979b6a0edd774ea8399341", size = 11817879, upload-time = "2025-09-19T00:09:47.131Z" }, - { url = "https://files.pythonhosted.org/packages/72/ef/0c9ba89eb03453e76bdac5a78b08260a848c7bfc5d6603634774d9cd9525/mypy-1.18.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1379451880512ffce14505493bd9fe469e0697543717298242574882cf8cdb8d", size = 12427292, upload-time = "2025-09-19T00:10:22.472Z" }, - { url = "https://files.pythonhosted.org/packages/1a/52/ec4a061dd599eb8179d5411d99775bec2a20542505988f40fc2fee781068/mypy-1.18.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1331eb7fd110d60c24999893320967594ff84c38ac6d19e0a76c5fd809a84c86", size = 13163750, upload-time = "2025-09-19T00:09:51.472Z" }, - { url = "https://files.pythonhosted.org/packages/c4/5f/2cf2ceb3b36372d51568f2208c021870fe7834cf3186b653ac6446511839/mypy-1.18.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ca30b50a51e7ba93b00422e486cbb124f1c56a535e20eff7b2d6ab72b3b2e37", size = 13351827, upload-time = "2025-09-19T00:09:58.311Z" }, - { url = "https://files.pythonhosted.org/packages/c8/7d/2697b930179e7277529eaaec1513f8de622818696857f689e4a5432e5e27/mypy-1.18.2-cp311-cp311-win_amd64.whl", hash = "sha256:664dc726e67fa54e14536f6e1224bcfce1d9e5ac02426d2326e2bb4e081d1ce8", size = 9757983, upload-time = "2025-09-19T00:10:09.071Z" }, - { url = "https://files.pythonhosted.org/packages/07/06/dfdd2bc60c66611dd8335f463818514733bc763e4760dee289dcc33df709/mypy-1.18.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:33eca32dd124b29400c31d7cf784e795b050ace0e1f91b8dc035672725617e34", size = 12908273, upload-time = "2025-09-19T00:10:58.321Z" }, - { url = "https://files.pythonhosted.org/packages/81/14/6a9de6d13a122d5608e1a04130724caf9170333ac5a924e10f670687d3eb/mypy-1.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3c47adf30d65e89b2dcd2fa32f3aeb5e94ca970d2c15fcb25e297871c8e4764", size = 11920910, upload-time = "2025-09-19T00:10:20.043Z" }, - { url = "https://files.pythonhosted.org/packages/5f/a9/b29de53e42f18e8cc547e38daa9dfa132ffdc64f7250e353f5c8cdd44bee/mypy-1.18.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d6c838e831a062f5f29d11c9057c6009f60cb294fea33a98422688181fe2893", size = 12465585, upload-time = "2025-09-19T00:10:33.005Z" }, - { url = "https://files.pythonhosted.org/packages/77/ae/6c3d2c7c61ff21f2bee938c917616c92ebf852f015fb55917fd6e2811db2/mypy-1.18.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01199871b6110a2ce984bde85acd481232d17413868c9807e95c1b0739a58914", size = 13348562, upload-time = "2025-09-19T00:10:11.51Z" }, - { url = "https://files.pythonhosted.org/packages/4d/31/aec68ab3b4aebdf8f36d191b0685d99faa899ab990753ca0fee60fb99511/mypy-1.18.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a2afc0fa0b0e91b4599ddfe0f91e2c26c2b5a5ab263737e998d6817874c5f7c8", size = 13533296, upload-time = "2025-09-19T00:10:06.568Z" }, - { url = "https://files.pythonhosted.org/packages/9f/83/abcb3ad9478fca3ebeb6a5358bb0b22c95ea42b43b7789c7fb1297ca44f4/mypy-1.18.2-cp312-cp312-win_amd64.whl", hash = "sha256:d8068d0afe682c7c4897c0f7ce84ea77f6de953262b12d07038f4d296d547074", size = 9828828, upload-time = "2025-09-19T00:10:28.203Z" }, - { url = "https://files.pythonhosted.org/packages/87/e3/be76d87158ebafa0309946c4a73831974d4d6ab4f4ef40c3b53a385a66fd/mypy-1.18.2-py3-none-any.whl", hash = "sha256:22a1748707dd62b58d2ae53562ffc4d7f8bcc727e8ac7cbc69c053ddc874d47e", size = 2352367, upload-time = "2025-09-19T00:10:15.489Z" }, + { url = "https://files.pythonhosted.org/packages/46/cf/eadc80c4e0a70db1c08921dcc220357ba8ab2faecb4392e3cebeb10edbfa/mypy-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad37544be07c5d7fba814eb370e006df58fed8ad1ef33ed1649cb1889ba6ff58", size = 10921009, upload-time = "2025-07-31T07:53:23.037Z" }, + { url = "https://files.pythonhosted.org/packages/5d/c1/c869d8c067829ad30d9bdae051046561552516cfb3a14f7f0347b7d973ee/mypy-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:064e2ff508e5464b4bd807a7c1625bc5047c5022b85c70f030680e18f37273a5", size = 10047482, upload-time = "2025-07-31T07:53:26.151Z" }, + { url = "https://files.pythonhosted.org/packages/98/b9/803672bab3fe03cee2e14786ca056efda4bb511ea02dadcedde6176d06d0/mypy-1.17.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70401bbabd2fa1aa7c43bb358f54037baf0586f41e83b0ae67dd0534fc64edfd", size = 11832883, upload-time = "2025-07-31T07:53:47.948Z" }, + { url = "https://files.pythonhosted.org/packages/88/fb/fcdac695beca66800918c18697b48833a9a6701de288452b6715a98cfee1/mypy-1.17.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e92bdc656b7757c438660f775f872a669b8ff374edc4d18277d86b63edba6b8b", size = 12566215, upload-time = "2025-07-31T07:54:04.031Z" }, + { url = "https://files.pythonhosted.org/packages/7f/37/a932da3d3dace99ee8eb2043b6ab03b6768c36eb29a02f98f46c18c0da0e/mypy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c1fdf4abb29ed1cb091cf432979e162c208a5ac676ce35010373ff29247bcad5", size = 12751956, upload-time = "2025-07-31T07:53:36.263Z" }, + { url = "https://files.pythonhosted.org/packages/8c/cf/6438a429e0f2f5cab8bc83e53dbebfa666476f40ee322e13cac5e64b79e7/mypy-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:ff2933428516ab63f961644bc49bc4cbe42bbffb2cd3b71cc7277c07d16b1a8b", size = 9507307, upload-time = "2025-07-31T07:53:59.734Z" }, + { url = "https://files.pythonhosted.org/packages/17/a2/7034d0d61af8098ec47902108553122baa0f438df8a713be860f7407c9e6/mypy-1.17.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:69e83ea6553a3ba79c08c6e15dbd9bfa912ec1e493bf75489ef93beb65209aeb", size = 11086295, upload-time = "2025-07-31T07:53:28.124Z" }, + { url = "https://files.pythonhosted.org/packages/14/1f/19e7e44b594d4b12f6ba8064dbe136505cec813549ca3e5191e40b1d3cc2/mypy-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1b16708a66d38abb1e6b5702f5c2c87e133289da36f6a1d15f6a5221085c6403", size = 10112355, upload-time = "2025-07-31T07:53:21.121Z" }, + { url = "https://files.pythonhosted.org/packages/5b/69/baa33927e29e6b4c55d798a9d44db5d394072eef2bdc18c3e2048c9ed1e9/mypy-1.17.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:89e972c0035e9e05823907ad5398c5a73b9f47a002b22359b177d40bdaee7056", size = 11875285, upload-time = "2025-07-31T07:53:55.293Z" }, + { url = "https://files.pythonhosted.org/packages/90/13/f3a89c76b0a41e19490b01e7069713a30949d9a6c147289ee1521bcea245/mypy-1.17.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03b6d0ed2b188e35ee6d5c36b5580cffd6da23319991c49ab5556c023ccf1341", size = 12737895, upload-time = "2025-07-31T07:53:43.623Z" }, + { url = "https://files.pythonhosted.org/packages/23/a1/c4ee79ac484241301564072e6476c5a5be2590bc2e7bfd28220033d2ef8f/mypy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c837b896b37cd103570d776bda106eabb8737aa6dd4f248451aecf53030cdbeb", size = 12931025, upload-time = "2025-07-31T07:54:17.125Z" }, + { url = "https://files.pythonhosted.org/packages/89/b8/7409477be7919a0608900e6320b155c72caab4fef46427c5cc75f85edadd/mypy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:665afab0963a4b39dff7c1fa563cc8b11ecff7910206db4b2e64dd1ba25aed19", size = 9584664, upload-time = "2025-07-31T07:54:12.842Z" }, + { url = "https://files.pythonhosted.org/packages/1d/f3/8fcd2af0f5b806f6cf463efaffd3c9548a28f84220493ecd38d127b6b66d/mypy-1.17.1-py3-none-any.whl", hash = "sha256:a9f52c0351c21fe24c21d8c0eb1f62967b262d6729393397b6f443c3b773c3b9", size = 2283411, upload-time = "2025-07-31T07:53:24.664Z" }, ] [[package]] @@ -1174,39 +1172,39 @@ wheels = [ [[package]] name = "numpy" -version = "2.3.3" +version = "2.3.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d0/19/95b3d357407220ed24c139018d2518fab0a61a948e68286a25f1a4d049ff/numpy-2.3.3.tar.gz", hash = "sha256:ddc7c39727ba62b80dfdbedf400d1c10ddfa8eefbd7ec8dcb118be8b56d31029", size = 20576648, upload-time = "2025-09-09T16:54:12.543Z" } +sdist = { url = "https://files.pythonhosted.org/packages/37/7d/3fec4199c5ffb892bed55cff901e4f39a58c81df9c44c280499e92cad264/numpy-2.3.2.tar.gz", hash = "sha256:e0486a11ec30cdecb53f184d496d1c6a20786c81e55e41640270130056f8ee48", size = 20489306, upload-time = "2025-07-24T21:32:07.553Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7a/45/e80d203ef6b267aa29b22714fb558930b27960a0c5ce3c19c999232bb3eb/numpy-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0ffc4f5caba7dfcbe944ed674b7eef683c7e94874046454bb79ed7ee0236f59d", size = 21259253, upload-time = "2025-09-09T15:56:02.094Z" }, - { url = "https://files.pythonhosted.org/packages/52/18/cf2c648fccf339e59302e00e5f2bc87725a3ce1992f30f3f78c9044d7c43/numpy-2.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e7e946c7170858a0295f79a60214424caac2ffdb0063d4d79cb681f9aa0aa569", size = 14450980, upload-time = "2025-09-09T15:56:05.926Z" }, - { url = "https://files.pythonhosted.org/packages/93/fb/9af1082bec870188c42a1c239839915b74a5099c392389ff04215dcee812/numpy-2.3.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:cd4260f64bc794c3390a63bf0728220dd1a68170c169088a1e0dfa2fde1be12f", size = 5379709, upload-time = "2025-09-09T15:56:07.95Z" }, - { url = "https://files.pythonhosted.org/packages/75/0f/bfd7abca52bcbf9a4a65abc83fe18ef01ccdeb37bfb28bbd6ad613447c79/numpy-2.3.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:f0ddb4b96a87b6728df9362135e764eac3cfa674499943ebc44ce96c478ab125", size = 6913923, upload-time = "2025-09-09T15:56:09.443Z" }, - { url = "https://files.pythonhosted.org/packages/79/55/d69adad255e87ab7afda1caf93ca997859092afeb697703e2f010f7c2e55/numpy-2.3.3-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:afd07d377f478344ec6ca2b8d4ca08ae8bd44706763d1efb56397de606393f48", size = 14589591, upload-time = "2025-09-09T15:56:11.234Z" }, - { url = "https://files.pythonhosted.org/packages/10/a2/010b0e27ddeacab7839957d7a8f00e91206e0c2c47abbb5f35a2630e5387/numpy-2.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bc92a5dedcc53857249ca51ef29f5e5f2f8c513e22cfb90faeb20343b8c6f7a6", size = 16938714, upload-time = "2025-09-09T15:56:14.637Z" }, - { url = "https://files.pythonhosted.org/packages/1c/6b/12ce8ede632c7126eb2762b9e15e18e204b81725b81f35176eac14dc5b82/numpy-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7af05ed4dc19f308e1d9fc759f36f21921eb7bbfc82843eeec6b2a2863a0aefa", size = 16370592, upload-time = "2025-09-09T15:56:17.285Z" }, - { url = "https://files.pythonhosted.org/packages/b4/35/aba8568b2593067bb6a8fe4c52babb23b4c3b9c80e1b49dff03a09925e4a/numpy-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:433bf137e338677cebdd5beac0199ac84712ad9d630b74eceeb759eaa45ddf30", size = 18884474, upload-time = "2025-09-09T15:56:20.943Z" }, - { url = "https://files.pythonhosted.org/packages/45/fa/7f43ba10c77575e8be7b0138d107e4f44ca4a1ef322cd16980ea3e8b8222/numpy-2.3.3-cp311-cp311-win32.whl", hash = "sha256:eb63d443d7b4ffd1e873f8155260d7f58e7e4b095961b01c91062935c2491e57", size = 6599794, upload-time = "2025-09-09T15:56:23.258Z" }, - { url = "https://files.pythonhosted.org/packages/0a/a2/a4f78cb2241fe5664a22a10332f2be886dcdea8784c9f6a01c272da9b426/numpy-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:ec9d249840f6a565f58d8f913bccac2444235025bbb13e9a4681783572ee3caa", size = 13088104, upload-time = "2025-09-09T15:56:25.476Z" }, - { url = "https://files.pythonhosted.org/packages/79/64/e424e975adbd38282ebcd4891661965b78783de893b381cbc4832fb9beb2/numpy-2.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:74c2a948d02f88c11a3c075d9733f1ae67d97c6bdb97f2bb542f980458b257e7", size = 10460772, upload-time = "2025-09-09T15:56:27.679Z" }, - { url = "https://files.pythonhosted.org/packages/51/5d/bb7fc075b762c96329147799e1bcc9176ab07ca6375ea976c475482ad5b3/numpy-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cfdd09f9c84a1a934cde1eec2267f0a43a7cd44b2cca4ff95b7c0d14d144b0bf", size = 20957014, upload-time = "2025-09-09T15:56:29.966Z" }, - { url = "https://files.pythonhosted.org/packages/6b/0e/c6211bb92af26517acd52125a237a92afe9c3124c6a68d3b9f81b62a0568/numpy-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb32e3cf0f762aee47ad1ddc6672988f7f27045b0783c887190545baba73aa25", size = 14185220, upload-time = "2025-09-09T15:56:32.175Z" }, - { url = "https://files.pythonhosted.org/packages/22/f2/07bb754eb2ede9073f4054f7c0286b0d9d2e23982e090a80d478b26d35ca/numpy-2.3.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:396b254daeb0a57b1fe0ecb5e3cff6fa79a380fa97c8f7781a6d08cd429418fe", size = 5113918, upload-time = "2025-09-09T15:56:34.175Z" }, - { url = "https://files.pythonhosted.org/packages/81/0a/afa51697e9fb74642f231ea36aca80fa17c8fb89f7a82abd5174023c3960/numpy-2.3.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:067e3d7159a5d8f8a0b46ee11148fc35ca9b21f61e3c49fbd0a027450e65a33b", size = 6647922, upload-time = "2025-09-09T15:56:36.149Z" }, - { url = "https://files.pythonhosted.org/packages/5d/f5/122d9cdb3f51c520d150fef6e87df9279e33d19a9611a87c0d2cf78a89f4/numpy-2.3.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c02d0629d25d426585fb2e45a66154081b9fa677bc92a881ff1d216bc9919a8", size = 14281991, upload-time = "2025-09-09T15:56:40.548Z" }, - { url = "https://files.pythonhosted.org/packages/51/64/7de3c91e821a2debf77c92962ea3fe6ac2bc45d0778c1cbe15d4fce2fd94/numpy-2.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9192da52b9745f7f0766531dcfa978b7763916f158bb63bdb8a1eca0068ab20", size = 16641643, upload-time = "2025-09-09T15:56:43.343Z" }, - { url = "https://files.pythonhosted.org/packages/30/e4/961a5fa681502cd0d68907818b69f67542695b74e3ceaa513918103b7e80/numpy-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cd7de500a5b66319db419dc3c345244404a164beae0d0937283b907d8152e6ea", size = 16056787, upload-time = "2025-09-09T15:56:46.141Z" }, - { url = "https://files.pythonhosted.org/packages/99/26/92c912b966e47fbbdf2ad556cb17e3a3088e2e1292b9833be1dfa5361a1a/numpy-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:93d4962d8f82af58f0b2eb85daaf1b3ca23fe0a85d0be8f1f2b7bb46034e56d7", size = 18579598, upload-time = "2025-09-09T15:56:49.844Z" }, - { url = "https://files.pythonhosted.org/packages/17/b6/fc8f82cb3520768718834f310c37d96380d9dc61bfdaf05fe5c0b7653e01/numpy-2.3.3-cp312-cp312-win32.whl", hash = "sha256:5534ed6b92f9b7dca6c0a19d6df12d41c68b991cef051d108f6dbff3babc4ebf", size = 6320800, upload-time = "2025-09-09T15:56:52.499Z" }, - { url = "https://files.pythonhosted.org/packages/32/ee/de999f2625b80d043d6d2d628c07d0d5555a677a3cf78fdf868d409b8766/numpy-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:497d7cad08e7092dba36e3d296fe4c97708c93daf26643a1ae4b03f6294d30eb", size = 12786615, upload-time = "2025-09-09T15:56:54.422Z" }, - { url = "https://files.pythonhosted.org/packages/49/6e/b479032f8a43559c383acb20816644f5f91c88f633d9271ee84f3b3a996c/numpy-2.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:ca0309a18d4dfea6fc6262a66d06c26cfe4640c3926ceec90e57791a82b6eee5", size = 10195936, upload-time = "2025-09-09T15:56:56.541Z" }, - { url = "https://files.pythonhosted.org/packages/b8/f2/7e0a37cfced2644c9563c529f29fa28acbd0960dde32ece683aafa6f4949/numpy-2.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1e02c7159791cd481e1e6d5ddd766b62a4d5acf8df4d4d1afe35ee9c5c33a41e", size = 21131019, upload-time = "2025-09-09T15:58:42.838Z" }, - { url = "https://files.pythonhosted.org/packages/1a/7e/3291f505297ed63831135a6cc0f474da0c868a1f31b0dd9a9f03a7a0d2ed/numpy-2.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:dca2d0fc80b3893ae72197b39f69d55a3cd8b17ea1b50aa4c62de82419936150", size = 14376288, upload-time = "2025-09-09T15:58:45.425Z" }, - { url = "https://files.pythonhosted.org/packages/bf/4b/ae02e985bdeee73d7b5abdefeb98aef1207e96d4c0621ee0cf228ddfac3c/numpy-2.3.3-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:99683cbe0658f8271b333a1b1b4bb3173750ad59c0c61f5bbdc5b318918fffe3", size = 5305425, upload-time = "2025-09-09T15:58:48.6Z" }, - { url = "https://files.pythonhosted.org/packages/8b/eb/9df215d6d7250db32007941500dc51c48190be25f2401d5b2b564e467247/numpy-2.3.3-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:d9d537a39cc9de668e5cd0e25affb17aec17b577c6b3ae8a3d866b479fbe88d0", size = 6819053, upload-time = "2025-09-09T15:58:50.401Z" }, - { url = "https://files.pythonhosted.org/packages/57/62/208293d7d6b2a8998a4a1f23ac758648c3c32182d4ce4346062018362e29/numpy-2.3.3-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8596ba2f8af5f93b01d97563832686d20206d303024777f6dfc2e7c7c3f1850e", size = 14420354, upload-time = "2025-09-09T15:58:52.704Z" }, - { url = "https://files.pythonhosted.org/packages/ed/0c/8e86e0ff7072e14a71b4c6af63175e40d1e7e933ce9b9e9f765a95b4e0c3/numpy-2.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1ec5615b05369925bd1125f27df33f3b6c8bc10d788d5999ecd8769a1fa04db", size = 16760413, upload-time = "2025-09-09T15:58:55.027Z" }, - { url = "https://files.pythonhosted.org/packages/af/11/0cc63f9f321ccf63886ac203336777140011fb669e739da36d8db3c53b98/numpy-2.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:2e267c7da5bf7309670523896df97f93f6e469fb931161f483cd6882b3b1a5dc", size = 12971844, upload-time = "2025-09-09T15:58:57.359Z" }, + { url = "https://files.pythonhosted.org/packages/96/26/1320083986108998bd487e2931eed2aeedf914b6e8905431487543ec911d/numpy-2.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:852ae5bed3478b92f093e30f785c98e0cb62fa0a939ed057c31716e18a7a22b9", size = 21259016, upload-time = "2025-07-24T20:24:35.214Z" }, + { url = "https://files.pythonhosted.org/packages/c4/2b/792b341463fa93fc7e55abbdbe87dac316c5b8cb5e94fb7a59fb6fa0cda5/numpy-2.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a0e27186e781a69959d0230dd9909b5e26024f8da10683bd6344baea1885168", size = 14451158, upload-time = "2025-07-24T20:24:58.397Z" }, + { url = "https://files.pythonhosted.org/packages/b7/13/e792d7209261afb0c9f4759ffef6135b35c77c6349a151f488f531d13595/numpy-2.3.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:f0a1a8476ad77a228e41619af2fa9505cf69df928e9aaa165746584ea17fed2b", size = 5379817, upload-time = "2025-07-24T20:25:07.746Z" }, + { url = "https://files.pythonhosted.org/packages/49/ce/055274fcba4107c022b2113a213c7287346563f48d62e8d2a5176ad93217/numpy-2.3.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:cbc95b3813920145032412f7e33d12080f11dc776262df1712e1638207dde9e8", size = 6913606, upload-time = "2025-07-24T20:25:18.84Z" }, + { url = "https://files.pythonhosted.org/packages/17/f2/e4d72e6bc5ff01e2ab613dc198d560714971900c03674b41947e38606502/numpy-2.3.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f75018be4980a7324edc5930fe39aa391d5734531b1926968605416ff58c332d", size = 14589652, upload-time = "2025-07-24T20:25:40.356Z" }, + { url = "https://files.pythonhosted.org/packages/c8/b0/fbeee3000a51ebf7222016e2939b5c5ecf8000a19555d04a18f1e02521b8/numpy-2.3.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20b8200721840f5621b7bd03f8dcd78de33ec522fc40dc2641aa09537df010c3", size = 16938816, upload-time = "2025-07-24T20:26:05.721Z" }, + { url = "https://files.pythonhosted.org/packages/a9/ec/2f6c45c3484cc159621ea8fc000ac5a86f1575f090cac78ac27193ce82cd/numpy-2.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f91e5c028504660d606340a084db4b216567ded1056ea2b4be4f9d10b67197f", size = 16370512, upload-time = "2025-07-24T20:26:30.545Z" }, + { url = "https://files.pythonhosted.org/packages/b5/01/dd67cf511850bd7aefd6347aaae0956ed415abea741ae107834aae7d6d4e/numpy-2.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:fb1752a3bb9a3ad2d6b090b88a9a0ae1cd6f004ef95f75825e2f382c183b2097", size = 18884947, upload-time = "2025-07-24T20:26:58.24Z" }, + { url = "https://files.pythonhosted.org/packages/a7/17/2cf60fd3e6a61d006778735edf67a222787a8c1a7842aed43ef96d777446/numpy-2.3.2-cp311-cp311-win32.whl", hash = "sha256:4ae6863868aaee2f57503c7a5052b3a2807cf7a3914475e637a0ecd366ced220", size = 6599494, upload-time = "2025-07-24T20:27:09.786Z" }, + { url = "https://files.pythonhosted.org/packages/d5/03/0eade211c504bda872a594f045f98ddcc6caef2b7c63610946845e304d3f/numpy-2.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:240259d6564f1c65424bcd10f435145a7644a65a6811cfc3201c4a429ba79170", size = 13087889, upload-time = "2025-07-24T20:27:29.558Z" }, + { url = "https://files.pythonhosted.org/packages/13/32/2c7979d39dafb2a25087e12310fc7f3b9d3c7d960df4f4bc97955ae0ce1d/numpy-2.3.2-cp311-cp311-win_arm64.whl", hash = "sha256:4209f874d45f921bde2cff1ffcd8a3695f545ad2ffbef6d3d3c6768162efab89", size = 10459560, upload-time = "2025-07-24T20:27:46.803Z" }, + { url = "https://files.pythonhosted.org/packages/00/6d/745dd1c1c5c284d17725e5c802ca4d45cfc6803519d777f087b71c9f4069/numpy-2.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bc3186bea41fae9d8e90c2b4fb5f0a1f5a690682da79b92574d63f56b529080b", size = 20956420, upload-time = "2025-07-24T20:28:18.002Z" }, + { url = "https://files.pythonhosted.org/packages/bc/96/e7b533ea5740641dd62b07a790af5d9d8fec36000b8e2d0472bd7574105f/numpy-2.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f4f0215edb189048a3c03bd5b19345bdfa7b45a7a6f72ae5945d2a28272727f", size = 14184660, upload-time = "2025-07-24T20:28:39.522Z" }, + { url = "https://files.pythonhosted.org/packages/2b/53/102c6122db45a62aa20d1b18c9986f67e6b97e0d6fbc1ae13e3e4c84430c/numpy-2.3.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:8b1224a734cd509f70816455c3cffe13a4f599b1bf7130f913ba0e2c0b2006c0", size = 5113382, upload-time = "2025-07-24T20:28:48.544Z" }, + { url = "https://files.pythonhosted.org/packages/2b/21/376257efcbf63e624250717e82b4fae93d60178f09eb03ed766dbb48ec9c/numpy-2.3.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3dcf02866b977a38ba3ec10215220609ab9667378a9e2150615673f3ffd6c73b", size = 6647258, upload-time = "2025-07-24T20:28:59.104Z" }, + { url = "https://files.pythonhosted.org/packages/91/ba/f4ebf257f08affa464fe6036e13f2bf9d4642a40228781dc1235da81be9f/numpy-2.3.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:572d5512df5470f50ada8d1972c5f1082d9a0b7aa5944db8084077570cf98370", size = 14281409, upload-time = "2025-07-24T20:40:30.298Z" }, + { url = "https://files.pythonhosted.org/packages/59/ef/f96536f1df42c668cbacb727a8c6da7afc9c05ece6d558927fb1722693e1/numpy-2.3.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8145dd6d10df13c559d1e4314df29695613575183fa2e2d11fac4c208c8a1f73", size = 16641317, upload-time = "2025-07-24T20:40:56.625Z" }, + { url = "https://files.pythonhosted.org/packages/f6/a7/af813a7b4f9a42f498dde8a4c6fcbff8100eed00182cc91dbaf095645f38/numpy-2.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:103ea7063fa624af04a791c39f97070bf93b96d7af7eb23530cd087dc8dbe9dc", size = 16056262, upload-time = "2025-07-24T20:41:20.797Z" }, + { url = "https://files.pythonhosted.org/packages/8b/5d/41c4ef8404caaa7f05ed1cfb06afe16a25895260eacbd29b4d84dff2920b/numpy-2.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc927d7f289d14f5e037be917539620603294454130b6de200091e23d27dc9be", size = 18579342, upload-time = "2025-07-24T20:41:50.753Z" }, + { url = "https://files.pythonhosted.org/packages/a1/4f/9950e44c5a11636f4a3af6e825ec23003475cc9a466edb7a759ed3ea63bd/numpy-2.3.2-cp312-cp312-win32.whl", hash = "sha256:d95f59afe7f808c103be692175008bab926b59309ade3e6d25009e9a171f7036", size = 6320610, upload-time = "2025-07-24T20:42:01.551Z" }, + { url = "https://files.pythonhosted.org/packages/7c/2f/244643a5ce54a94f0a9a2ab578189c061e4a87c002e037b0829dd77293b6/numpy-2.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:9e196ade2400c0c737d93465327d1ae7c06c7cb8a1756121ebf54b06ca183c7f", size = 12786292, upload-time = "2025-07-24T20:42:20.738Z" }, + { url = "https://files.pythonhosted.org/packages/54/cd/7b5f49d5d78db7badab22d8323c1b6ae458fbf86c4fdfa194ab3cd4eb39b/numpy-2.3.2-cp312-cp312-win_arm64.whl", hash = "sha256:ee807923782faaf60d0d7331f5e86da7d5e3079e28b291973c545476c2b00d07", size = 10194071, upload-time = "2025-07-24T20:42:36.657Z" }, + { url = "https://files.pythonhosted.org/packages/cf/ea/50ebc91d28b275b23b7128ef25c3d08152bc4068f42742867e07a870a42a/numpy-2.3.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:14a91ebac98813a49bc6aa1a0dfc09513dcec1d97eaf31ca21a87221a1cdcb15", size = 21130338, upload-time = "2025-07-24T20:57:54.37Z" }, + { url = "https://files.pythonhosted.org/packages/9f/57/cdd5eac00dd5f137277355c318a955c0d8fb8aa486020c22afd305f8b88f/numpy-2.3.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:71669b5daae692189540cffc4c439468d35a3f84f0c88b078ecd94337f6cb0ec", size = 14375776, upload-time = "2025-07-24T20:58:16.303Z" }, + { url = "https://files.pythonhosted.org/packages/83/85/27280c7f34fcd305c2209c0cdca4d70775e4859a9eaa92f850087f8dea50/numpy-2.3.2-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:69779198d9caee6e547adb933941ed7520f896fd9656834c300bdf4dd8642712", size = 5304882, upload-time = "2025-07-24T20:58:26.199Z" }, + { url = "https://files.pythonhosted.org/packages/48/b4/6500b24d278e15dd796f43824e69939d00981d37d9779e32499e823aa0aa/numpy-2.3.2-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:2c3271cc4097beb5a60f010bcc1cc204b300bb3eafb4399376418a83a1c6373c", size = 6818405, upload-time = "2025-07-24T20:58:37.341Z" }, + { url = "https://files.pythonhosted.org/packages/9b/c9/142c1e03f199d202da8e980c2496213509291b6024fd2735ad28ae7065c7/numpy-2.3.2-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8446acd11fe3dc1830568c941d44449fd5cb83068e5c70bd5a470d323d448296", size = 14419651, upload-time = "2025-07-24T20:58:59.048Z" }, + { url = "https://files.pythonhosted.org/packages/8b/95/8023e87cbea31a750a6c00ff9427d65ebc5fef104a136bfa69f76266d614/numpy-2.3.2-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aa098a5ab53fa407fded5870865c6275a5cd4101cfdef8d6fafc48286a96e981", size = 16760166, upload-time = "2025-07-24T21:28:56.38Z" }, + { url = "https://files.pythonhosted.org/packages/78/e3/6690b3f85a05506733c7e90b577e4762517404ea78bab2ca3a5cb1aeb78d/numpy-2.3.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6936aff90dda378c09bea075af0d9c675fe3a977a9d2402f95a87f440f59f619", size = 12977811, upload-time = "2025-07-24T21:29:18.234Z" }, ] [[package]] @@ -1263,6 +1261,7 @@ dependencies = [ { name = "cffi" }, { name = "crcmod" }, { name = "cython" }, + { name = "dearpygui" }, { name = "future-fstrings" }, { name = "inputs" }, { name = "json-rpc" }, @@ -1338,7 +1337,6 @@ testing = [ { name = "ruff" }, ] tools = [ - { name = "dearpygui" }, { name = "metadrive-simulator", marker = "platform_machine != 'aarch64'" }, ] @@ -1355,7 +1353,7 @@ requires-dist = [ { name = "crcmod" }, { name = "cython" }, { name = "dbus-next", marker = "extra == 'dev'" }, - { name = "dearpygui", marker = "extra == 'tools'", specifier = ">=2.1.0" }, + { name = "dearpygui", specifier = ">=2.1.0" }, { name = "dictdiffer", marker = "extra == 'dev'" }, { name = "future-fstrings" }, { name = "hypothesis", marker = "extra == 'testing'", specifier = "==6.47.*" }, @@ -1399,7 +1397,7 @@ requires-dist = [ { name = "pywinctl", marker = "extra == 'dev'" }, { name = "pyzmq" }, { name = "qrcode" }, - { name = "raylib", marker = "extra == 'dev'", specifier = "==5.5.0.2" }, + { name = "raylib", marker = "extra == 'dev'" }, { name = "requests" }, { name = "ruff", marker = "extra == 'testing'" }, { name = "scons" }, @@ -1452,8 +1450,8 @@ name = "panda3d-gltf" version = "0.13" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "panda3d" }, - { name = "panda3d-simplepbr" }, + { name = "panda3d", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "panda3d-simplepbr", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, ] 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 = [ @@ -1465,8 +1463,8 @@ name = "panda3d-simplepbr" version = "0.13.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "panda3d" }, - { name = "typing-extensions" }, + { name = "panda3d", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "typing-extensions", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, ] 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 = [ @@ -1607,32 +1605,31 @@ wheels = [ [[package]] name = "protobuf" -version = "6.32.1" +version = "6.32.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fa/a4/cc17347aa2897568beece2e674674359f911d6fe21b0b8d6268cd42727ac/protobuf-6.32.1.tar.gz", hash = "sha256:ee2469e4a021474ab9baafea6cd070e5bf27c7d29433504ddea1a4ee5850f68d", size = 440635, upload-time = "2025-09-11T21:38:42.935Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c0/df/fb4a8eeea482eca989b51cffd274aac2ee24e825f0bf3cbce5281fa1567b/protobuf-6.32.0.tar.gz", hash = "sha256:a81439049127067fc49ec1d36e25c6ee1d1a2b7be930675f919258d03c04e7d2", size = 440614, upload-time = "2025-08-14T21:21:25.015Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c0/98/645183ea03ab3995d29086b8bf4f7562ebd3d10c9a4b14ee3f20d47cfe50/protobuf-6.32.1-cp310-abi3-win32.whl", hash = "sha256:a8a32a84bc9f2aad712041b8b366190f71dde248926da517bde9e832e4412085", size = 424411, upload-time = "2025-09-11T21:38:27.427Z" }, - { url = "https://files.pythonhosted.org/packages/8c/f3/6f58f841f6ebafe076cebeae33fc336e900619d34b1c93e4b5c97a81fdfa/protobuf-6.32.1-cp310-abi3-win_amd64.whl", hash = "sha256:b00a7d8c25fa471f16bc8153d0e53d6c9e827f0953f3c09aaa4331c718cae5e1", size = 435738, upload-time = "2025-09-11T21:38:30.959Z" }, - { url = "https://files.pythonhosted.org/packages/10/56/a8a3f4e7190837139e68c7002ec749190a163af3e330f65d90309145a210/protobuf-6.32.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d8c7e6eb619ffdf105ee4ab76af5a68b60a9d0f66da3ea12d1640e6d8dab7281", size = 426454, upload-time = "2025-09-11T21:38:34.076Z" }, - { url = "https://files.pythonhosted.org/packages/3f/be/8dd0a927c559b37d7a6c8ab79034fd167dcc1f851595f2e641ad62be8643/protobuf-6.32.1-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:2f5b80a49e1eb7b86d85fcd23fe92df154b9730a725c3b38c4e43b9d77018bf4", size = 322874, upload-time = "2025-09-11T21:38:35.509Z" }, - { url = "https://files.pythonhosted.org/packages/5c/f6/88d77011b605ef979aace37b7703e4eefad066f7e84d935e5a696515c2dd/protobuf-6.32.1-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:b1864818300c297265c83a4982fd3169f97122c299f56a56e2445c3698d34710", size = 322013, upload-time = "2025-09-11T21:38:37.017Z" }, - { url = "https://files.pythonhosted.org/packages/97/b7/15cc7d93443d6c6a84626ae3258a91f4c6ac8c0edd5df35ea7658f71b79c/protobuf-6.32.1-py3-none-any.whl", hash = "sha256:2601b779fc7d32a866c6b4404f9d42a3f67c5b9f3f15b4db3cccabe06b95c346", size = 169289, upload-time = "2025-09-11T21:38:41.234Z" }, + { url = "https://files.pythonhosted.org/packages/33/18/df8c87da2e47f4f1dcc5153a81cd6bca4e429803f4069a299e236e4dd510/protobuf-6.32.0-cp310-abi3-win32.whl", hash = "sha256:84f9e3c1ff6fb0308dbacb0950d8aa90694b0d0ee68e75719cb044b7078fe741", size = 424409, upload-time = "2025-08-14T21:21:12.366Z" }, + { url = "https://files.pythonhosted.org/packages/e1/59/0a820b7310f8139bd8d5a9388e6a38e1786d179d6f33998448609296c229/protobuf-6.32.0-cp310-abi3-win_amd64.whl", hash = "sha256:a8bdbb2f009cfc22a36d031f22a625a38b615b5e19e558a7b756b3279723e68e", size = 435735, upload-time = "2025-08-14T21:21:15.046Z" }, + { url = "https://files.pythonhosted.org/packages/cc/5b/0d421533c59c789e9c9894683efac582c06246bf24bb26b753b149bd88e4/protobuf-6.32.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d52691e5bee6c860fff9a1c86ad26a13afbeb4b168cd4445c922b7e2cf85aaf0", size = 426449, upload-time = "2025-08-14T21:21:16.687Z" }, + { url = "https://files.pythonhosted.org/packages/ec/7b/607764ebe6c7a23dcee06e054fd1de3d5841b7648a90fd6def9a3bb58c5e/protobuf-6.32.0-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:501fe6372fd1c8ea2a30b4d9be8f87955a64d6be9c88a973996cef5ef6f0abf1", size = 322869, upload-time = "2025-08-14T21:21:18.282Z" }, + { url = "https://files.pythonhosted.org/packages/40/01/2e730bd1c25392fc32e3268e02446f0d77cb51a2c3a8486b1798e34d5805/protobuf-6.32.0-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:75a2aab2bd1aeb1f5dc7c5f33bcb11d82ea8c055c9becbb41c26a8c43fd7092c", size = 322009, upload-time = "2025-08-14T21:21:19.893Z" }, + { url = "https://files.pythonhosted.org/packages/9c/f2/80ffc4677aac1bc3519b26bc7f7f5de7fce0ee2f7e36e59e27d8beb32dd1/protobuf-6.32.0-py3-none-any.whl", hash = "sha256:ba377e5b67b908c8f3072a57b63e2c6a4cbd18aea4ed98d2584350dbf46f2783", size = 169287, upload-time = "2025-08-14T21:21:23.515Z" }, ] [[package]] name = "psutil" -version = "7.1.0" +version = "7.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b3/31/4723d756b59344b643542936e37a31d1d3204bcdc42a7daa8ee9eb06fb50/psutil-7.1.0.tar.gz", hash = "sha256:655708b3c069387c8b77b072fc429a57d0e214221d01c0a772df7dfedcb3bcd2", size = 497660, upload-time = "2025-09-17T20:14:52.902Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2a/80/336820c1ad9286a4ded7e845b2eccfcb27851ab8ac6abece774a6ff4d3de/psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456", size = 497003, upload-time = "2025-02-13T21:54:07.946Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/46/62/ce4051019ee20ce0ed74432dd73a5bb087a6704284a470bb8adff69a0932/psutil-7.1.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:76168cef4397494250e9f4e73eb3752b146de1dd950040b29186d0cce1d5ca13", size = 245242, upload-time = "2025-09-17T20:14:56.126Z" }, - { url = "https://files.pythonhosted.org/packages/38/61/f76959fba841bf5b61123fbf4b650886dc4094c6858008b5bf73d9057216/psutil-7.1.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:5d007560c8c372efdff9e4579c2846d71de737e4605f611437255e81efcca2c5", size = 246682, upload-time = "2025-09-17T20:14:58.25Z" }, - { url = "https://files.pythonhosted.org/packages/88/7a/37c99d2e77ec30d63398ffa6a660450b8a62517cabe44b3e9bae97696e8d/psutil-7.1.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22e4454970b32472ce7deaa45d045b34d3648ce478e26a04c7e858a0a6e75ff3", size = 287994, upload-time = "2025-09-17T20:14:59.901Z" }, - { url = "https://files.pythonhosted.org/packages/9d/de/04c8c61232f7244aa0a4b9a9fbd63a89d5aeaf94b2fc9d1d16e2faa5cbb0/psutil-7.1.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c70e113920d51e89f212dd7be06219a9b88014e63a4cec69b684c327bc474e3", size = 291163, upload-time = "2025-09-17T20:15:01.481Z" }, - { url = "https://files.pythonhosted.org/packages/f4/58/c4f976234bf6d4737bc8c02a81192f045c307b72cf39c9e5c5a2d78927f6/psutil-7.1.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7d4a113425c037300de3ac8b331637293da9be9713855c4fc9d2d97436d7259d", size = 293625, upload-time = "2025-09-17T20:15:04.492Z" }, - { url = "https://files.pythonhosted.org/packages/79/87/157c8e7959ec39ced1b11cc93c730c4fb7f9d408569a6c59dbd92ceb35db/psutil-7.1.0-cp37-abi3-win32.whl", hash = "sha256:09ad740870c8d219ed8daae0ad3b726d3bf9a028a198e7f3080f6a1888b99bca", size = 244812, upload-time = "2025-09-17T20:15:07.462Z" }, - { url = "https://files.pythonhosted.org/packages/bf/e9/b44c4f697276a7a95b8e94d0e320a7bf7f3318521b23de69035540b39838/psutil-7.1.0-cp37-abi3-win_amd64.whl", hash = "sha256:57f5e987c36d3146c0dd2528cd42151cf96cd359b9d67cfff836995cc5df9a3d", size = 247965, upload-time = "2025-09-17T20:15:09.673Z" }, - { url = "https://files.pythonhosted.org/packages/26/65/1070a6e3c036f39142c2820c4b52e9243246fcfc3f96239ac84472ba361e/psutil-7.1.0-cp37-abi3-win_arm64.whl", hash = "sha256:6937cb68133e7c97b6cc9649a570c9a18ba0efebed46d8c5dae4c07fa1b67a07", size = 244971, upload-time = "2025-09-17T20:15:12.262Z" }, + { url = "https://files.pythonhosted.org/packages/ed/e6/2d26234410f8b8abdbf891c9da62bee396583f713fb9f3325a4760875d22/psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25", size = 238051, upload-time = "2025-02-13T21:54:12.36Z" }, + { url = "https://files.pythonhosted.org/packages/04/8b/30f930733afe425e3cbfc0e1468a30a18942350c1a8816acfade80c005c4/psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da", size = 239535, upload-time = "2025-02-13T21:54:16.07Z" }, + { url = "https://files.pythonhosted.org/packages/2a/ed/d362e84620dd22876b55389248e522338ed1bf134a5edd3b8231d7207f6d/psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91", size = 275004, upload-time = "2025-02-13T21:54:18.662Z" }, + { url = "https://files.pythonhosted.org/packages/bf/b9/b0eb3f3cbcb734d930fdf839431606844a825b23eaf9a6ab371edac8162c/psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34", size = 277986, upload-time = "2025-02-13T21:54:21.811Z" }, + { url = "https://files.pythonhosted.org/packages/eb/a2/709e0fe2f093556c17fbafda93ac032257242cabcc7ff3369e2cb76a97aa/psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993", size = 279544, upload-time = "2025-02-13T21:54:24.68Z" }, + { url = "https://files.pythonhosted.org/packages/50/e6/eecf58810b9d12e6427369784efe814a1eec0f492084ce8eb8f4d89d6d61/psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99", size = 241053, upload-time = "2025-02-13T21:54:34.31Z" }, + { url = "https://files.pythonhosted.org/packages/50/1b/6921afe68c74868b4c9fa424dad3be35b095e16687989ebbb50ce4fceb7c/psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553", size = 244885, upload-time = "2025-02-13T21:54:37.486Z" }, ] [[package]] @@ -1665,47 +1662,47 @@ sdist = { url = "https://files.pythonhosted.org/packages/65/ff/cdae0a8c2118a0de7 [[package]] name = "pycapnp" -version = "2.2.0" +version = "2.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9b/96/0b1696ed89950b34506594a2c1f0c19c31d91385dfee81295113e9f22442/pycapnp-2.2.0.tar.gz", hash = "sha256:06466a74e44858b38f920784a8fe74172463a8e73e8cb8298dfe02a95b8997d6", size = 707734, upload-time = "2025-09-13T16:42:04.868Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9b/fb/54b46b52c1fa2acd9afd81bd05810c61bb1b05c6084c9625b64bc6d41843/pycapnp-2.0.0.tar.gz", hash = "sha256:503ab9b7b16773590ee226f2460408972c6b1c2cb2d819037115b919bef682be", size = 574848, upload-time = "2024-04-12T15:35:44.019Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/8d/11dd189691d78e2ca2200bf2a7677bd6bf7594f74ccc7b10ccafafd6761b/pycapnp-2.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1e000956e08cb75070aa35123cbeae73d045e0e6c6aa347a09d53769dc675fa9", size = 1645501, upload-time = "2025-09-13T16:39:36.828Z" }, - { url = "https://files.pythonhosted.org/packages/05/9f/321878b9aeea9eeac1977504a6daf46625a636ec99b687242528263fbbe3/pycapnp-2.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a45081bde2adaca6a5bf98ff949962d466f94114f6e98a0eab8f55d424786f24", size = 1509785, upload-time = "2025-09-13T16:39:38.623Z" }, - { url = "https://files.pythonhosted.org/packages/79/ec/7d6085792c75855785f8e7e97a35ea8746058124238479d4509f0a25852a/pycapnp-2.2.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:a1b10ab77d2a61bc033e74e0c90669222ede6f6b462eb8f1f97f592e6b547500", size = 5338620, upload-time = "2025-09-13T16:39:40.05Z" }, - { url = "https://files.pythonhosted.org/packages/3a/13/4244ffd7d513fca5e5c086251649586c05734923ee16b754cd22a85d7bda/pycapnp-2.2.0-cp311-cp311-manylinux_2_28_i686.whl", hash = "sha256:f325fb69a8a4181ca3591ec311f1ae854dd71a199f6f55761008fdab96544fbb", size = 5420575, upload-time = "2025-09-13T16:39:42.255Z" }, - { url = "https://files.pythonhosted.org/packages/c5/7f/b2f592836d870e1e35a2cc0d4a8ece4c877dbeb2e55b040e0c00b95a01c8/pycapnp-2.2.0-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:e61420b11178a856b962e5e4a89c1755e3b1677762e976a98aa628ad96b70776", size = 5835575, upload-time = "2025-09-13T16:39:43.85Z" }, - { url = "https://files.pythonhosted.org/packages/e4/36/76ce7c4da4141f7e75db0fd4e95d51328f1f583e3d30eb52ed7dcde91bcf/pycapnp-2.2.0-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:6b7b6a5f96ee95a8b33b85f7f87d18129bdba4e3c22cad3b6485b675607b1aeb", size = 5868584, upload-time = "2025-09-13T16:39:45.893Z" }, - { url = "https://files.pythonhosted.org/packages/4b/20/c83deb8734b2b0cc083344ede75b81f5af6466a7d95868adbaf14180c21d/pycapnp-2.2.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:d109f8e9d3b46fb28c64b1a87cae7f26447bf07253de1e7dfd057ee38b0654a4", size = 5530789, upload-time = "2025-09-13T16:39:47.63Z" }, - { url = "https://files.pythonhosted.org/packages/4c/5d/a2600534c492196cb01f102c515bdea3271352bb7d37c6910603b248ddb0/pycapnp-2.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:93db5b840a400c6c22ceb93b0494182d087a5a40f81cf179881f0f5bbcd6e439", size = 6228635, upload-time = "2025-09-13T16:39:49.366Z" }, - { url = "https://files.pythonhosted.org/packages/27/5b/2e21278f7746a36b29e2cde28b21ffc479dd09c2cb1ed963cf0f89e8caf8/pycapnp-2.2.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ab4fd171102c2d7222fd1e80fa190652a95a58e56dd731f6f7e81806ed5bc012", size = 6560947, upload-time = "2025-09-13T16:39:52.215Z" }, - { url = "https://files.pythonhosted.org/packages/74/4e/87bf0adb4644d45658e10eb97e08d16992a7864ff9816061a002b121da23/pycapnp-2.2.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:da4ea4932569279c1901e97cfa761754a7666389dfef5ae5df90ffa20de7566d", size = 6748953, upload-time = "2025-09-13T16:39:53.859Z" }, - { url = "https://files.pythonhosted.org/packages/14/09/8e2c007a9514793fb7a2f675212e4ebc31ae7e59d131c3d561f123793d0a/pycapnp-2.2.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:289da6a5329ec7ff323aa8d10f06709ea086111c3c4558774370907b8f43c7ba", size = 6779013, upload-time = "2025-09-13T16:39:55.931Z" }, - { url = "https://files.pythonhosted.org/packages/33/6f/747705eacdf75797feef5429ad42756ba6afcc51a96bc3f1627c3f2b11a2/pycapnp-2.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:16b7670c6a5720277823c7299401fc1009b62dffb5777e3b121ae66997a6674b", size = 6496769, upload-time = "2025-09-13T16:39:57.554Z" }, - { url = "https://files.pythonhosted.org/packages/5d/4a/d4c65b305a2156f4fe073e84121938780eacd9528d7c977cbc6f1659787e/pycapnp-2.2.0-cp311-cp311-win32.whl", hash = "sha256:ec226235a0677e9cfb020a847fd5ff553ef2de0d67d55e19c1ea4b313263a594", size = 1064932, upload-time = "2025-09-13T16:39:58.967Z" }, - { url = "https://files.pythonhosted.org/packages/e9/3f/66cc7cae9d02462ab633c443ca408003328156218480ab4a4c61272fbab2/pycapnp-2.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:402ec2aacf6256a0969eff6b8b28ab9f5ec5ba92b9cc6b7dffc41756043e15d0", size = 1220053, upload-time = "2025-09-13T16:40:00.89Z" }, - { url = "https://files.pythonhosted.org/packages/3e/79/3d82be14faf0ec38817e5b2e838905356ef801b8d45c40dcaf685d742142/pycapnp-2.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a4a96ebe3f854f5e0f57b4d3571a0fe802686f7fd8cfa603973ec692fb47abce", size = 1637264, upload-time = "2025-09-13T16:40:02.557Z" }, - { url = "https://files.pythonhosted.org/packages/20/b5/a31b28de6093a3907e33a3604e0a24d27c9a24ad420261d23dea519e4559/pycapnp-2.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:70426b32474f29101293dd4271ca9b309ae549422ffb3ba98eb0483897af5cbe", size = 1501144, upload-time = "2025-09-13T16:40:04.376Z" }, - { url = "https://files.pythonhosted.org/packages/cc/76/e2b2702fadeb2d9492ed6ef7df9f5d57d5bac1709108a73c072b279fb7db/pycapnp-2.2.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:fc9eff6b76a2414c377a77c927f187282bc7b83005adfc3df63b43e7cd676d39", size = 5295044, upload-time = "2025-09-13T16:40:06.283Z" }, - { url = "https://files.pythonhosted.org/packages/99/4d/16e8d8ccb4b8b63d4d2c0fd745ed88287de0323e596b091fa515e775fe4f/pycapnp-2.2.0-cp312-cp312-manylinux_2_28_i686.whl", hash = "sha256:a7af220f446e84373764c701197c25c1eabcc13832a7532911c191d81a61387f", size = 5343558, upload-time = "2025-09-13T16:40:07.85Z" }, - { url = "https://files.pythonhosted.org/packages/a2/ae/a570f5cc825b71510877ebe5b8041c437d8e0e9993d5b134efbc31daf844/pycapnp-2.2.0-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:c5caf1e6fcd9a068c7fafb286a915e75fe7585c9525eeac9e8a641cbd92f1982", size = 5667031, upload-time = "2025-09-13T16:40:09.489Z" }, - { url = "https://files.pythonhosted.org/packages/6d/ee/d03a871153e90846fe8ea43274f7e161294f0a443fcf6c784c78edf2ceb0/pycapnp-2.2.0-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:4456362dcc6acec2c801d817ad6e62a798a08aacb5e725a25f013d8318b6ce42", size = 5804310, upload-time = "2025-09-13T16:40:11.097Z" }, - { url = "https://files.pythonhosted.org/packages/3e/8e/fc827b28128dc3921462d563ae8981a8a9ae25bd8f49bc4fe83c0793f804/pycapnp-2.2.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:c66ba31fac33a1707d38eb0fe53aa29fd27ce6a2a4f71f415a1229c30706a75e", size = 5522994, upload-time = "2025-09-13T16:40:12.788Z" }, - { url = "https://files.pythonhosted.org/packages/4f/4d/4bc662dd058b9f79d5a8a691a56bf9941fbe4e340098cf7f9ae49c6c8761/pycapnp-2.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:33d0d1a286e1135e1913d4a3cb0a2c972a206b1984c3d23e87cdf11e1d523b70", size = 6145416, upload-time = "2025-09-13T16:40:14.444Z" }, - { url = "https://files.pythonhosted.org/packages/97/63/44cc5dc368ed833ecc0f20be8523842b965177f4ac16ff81a3663d2f46c3/pycapnp-2.2.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:63aeb83c042c84e5bd9bce9f699be30279d925ea00c3a92b3a4cb8e3f2cbe957", size = 6474037, upload-time = "2025-09-13T16:40:16.104Z" }, - { url = "https://files.pythonhosted.org/packages/95/39/52753dd2dbfc4874b0ac2321a629ccd1355d9470323632701d3b7c1ab0cc/pycapnp-2.2.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e9d025d04115d7341148ca654e144ddecbc9af7d215426e69fa6ec8036b1467f", size = 6604316, upload-time = "2025-09-13T16:40:17.868Z" }, - { url = "https://files.pythonhosted.org/packages/ca/a6/3f178b23a5b9f2afe2d12614f2eea740defa3dd0797397870c6f62952e69/pycapnp-2.2.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:51c658e633fa41fae616fcddebf048a505e3080378853b7e3e267858c2fd20f7", size = 6710383, upload-time = "2025-09-13T16:40:19.622Z" }, - { url = "https://files.pythonhosted.org/packages/ad/ed/934f2f2c8d678c76b5616b165a0277d49d0d40dbe8e3d62a7eb1bf9bac95/pycapnp-2.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5802b63303f66066e32b704d094e2629eff1d40add9ae7c4317753ab601456b4", size = 6445575, upload-time = "2025-09-13T16:40:21.826Z" }, - { url = "https://files.pythonhosted.org/packages/c9/42/cef707de9fbc2e70aa47a26e3c17444846bb7e19cd9ea51a6ed9dce4eced/pycapnp-2.2.0-cp312-cp312-win32.whl", hash = "sha256:e21f69ce0deac8dbd68424067560bfe31102ab6728d410bd66a34c9b7aaceba5", size = 1049436, upload-time = "2025-09-13T16:40:23.425Z" }, - { url = "https://files.pythonhosted.org/packages/17/c9/82dee36033c30c39e4860df82fc475ccc6323b075dfaa518092f1cc722f6/pycapnp-2.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:ddf0364c26028bff6e0f90da41ac28fae96703a74279a7ba6c179eaff794ecbf", size = 1188941, upload-time = "2025-09-13T16:40:24.766Z" }, + { url = "https://files.pythonhosted.org/packages/cb/82/cf311b1a9800b605759a38a0c337a55a639b685427364294e98a0f9b7306/pycapnp-2.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:829c7eb4e5f23dbcac25466110faf72a691035cf87c5d46e5053da15790e428d", size = 1673673, upload-time = "2024-04-12T15:33:32.211Z" }, + { url = "https://files.pythonhosted.org/packages/ae/55/4c03ca95c568776a1f637db9ffdcf302fb63f46e4d2a4f13edd8cb1a5f90/pycapnp-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dab60fbe3e4eaf99ec97918a0a776216c6c149b6d49261383d91c2201adb475d", size = 1513351, upload-time = "2024-04-12T15:33:35.156Z" }, + { url = "https://files.pythonhosted.org/packages/55/98/e4b2dea076f8a2575abc45cd879a91bc9aa975c69ae2ac1cab61d83c5087/pycapnp-2.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c48a0582078bb74d7326d28571db0b8e6919563365537a5a13e8f5360c12bfc", size = 4910666, upload-time = "2024-04-12T15:33:37.798Z" }, + { url = "https://files.pythonhosted.org/packages/1d/ee/3b5a182588f89074f4002fa6247e3f963bb85bc808acd1ac8deed91f1fa7/pycapnp-2.0.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bcb5ab54aff857e3711d2c0cc934194aaffacdeb3481daa56863daef07d27941", size = 5007434, upload-time = "2024-04-12T15:33:40.362Z" }, + { url = "https://files.pythonhosted.org/packages/c5/e9/515a2ca7fdc84d57c654280d0b71dfd782fd1773d384c0ec0d56dc6fc35b/pycapnp-2.0.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9600778036e6fe9dbea68f0c37678c5f4d561d2f2306b3cb741de5e1670ef2ae", size = 5188923, upload-time = "2024-04-12T15:33:42.33Z" }, + { url = "https://files.pythonhosted.org/packages/70/60/5db346e238985a526ba7589ed24f92195dad39e7dec9d85b17a567600b6f/pycapnp-2.0.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7278ba0262fab8c398e77d634ae7ba026866d44b52cbfc27262be8d396ecacd1", size = 5048105, upload-time = "2024-04-12T15:33:44.294Z" }, + { url = "https://files.pythonhosted.org/packages/e3/ff/02b4a87c9ff9793f26d8f3d95312d902d260c094f216d84e19528a506606/pycapnp-2.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23b2458d43c82302980a96518c96df257429204d2cc02bfff0c8cb6ebb371e01", size = 5063172, upload-time = "2024-04-12T15:33:46.954Z" }, + { url = "https://files.pythonhosted.org/packages/10/a1/35a7e14d765f99cfdcdfdcebc69bdf382f27016944470daa7a03c557f681/pycapnp-2.0.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dd7755cc3fedc2ad8cc7864a0729471ddeff10c184963fe0f3689e295130f1b2", size = 5408718, upload-time = "2024-04-12T15:33:49.587Z" }, + { url = "https://files.pythonhosted.org/packages/5c/59/8bc8a993c38808c6fd90b10becba8de4a54543e8441bd87ce44ef3b7eee4/pycapnp-2.0.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d47baf6b3db9981625ffc5ff188e089f2ebca8e7e1afb97aa5eb7bebb7bf3650", size = 5596714, upload-time = "2024-04-12T15:33:51.518Z" }, + { url = "https://files.pythonhosted.org/packages/ea/7d/79c481ef77f29e81355e92bb250f0d2a37a76f5fe0ba9433bf6c6c88b6e4/pycapnp-2.0.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:b375be92d93fdb6f7ac127ea9390bcec0fed4e485db137b084f9e7114dde7c83", size = 5709896, upload-time = "2024-04-12T15:33:53.716Z" }, + { url = "https://files.pythonhosted.org/packages/59/8d/f2eceeea1e8cae8b8a70a4752af5b772916f455e2ed388d0887e2b57d080/pycapnp-2.0.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:959bfdf1cddb3e5528e2293c4a375382be9a1bf044b073bc2e7eca1eb6b3a9a2", size = 5594823, upload-time = "2024-04-12T15:33:55.573Z" }, + { url = "https://files.pythonhosted.org/packages/2c/86/f8284637b61f83232e5618dd561a66080dd98ce2272d7e3ae89335d4fd97/pycapnp-2.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d873af167cf5cc7578ce5432eefcb442f866c8f7a6c57d188baf8c5e709fa39d", size = 5572564, upload-time = "2024-04-12T15:33:59.112Z" }, + { url = "https://files.pythonhosted.org/packages/b3/b2/7f99d28a9935d1e37ec6955922c57b2be24fe0b74fe25929643686cc11e5/pycapnp-2.0.0-cp311-cp311-win32.whl", hash = "sha256:40ca8018e0b7686d549b920f087049b92a3e6f06976d9f5a8112603fc560cac4", size = 1040268, upload-time = "2024-04-12T15:34:00.933Z" }, + { url = "https://files.pythonhosted.org/packages/1d/37/89ab98961f18cffeae20d98cfc24afcfa85024bc014ecc48b0c4ac264fe0/pycapnp-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:d15cd8e46d541a899c84809095d7d7b3951f43642d1859e7a39bd91910778479", size = 1141758, upload-time = "2024-04-12T15:34:02.607Z" }, + { url = "https://files.pythonhosted.org/packages/ce/d5/0ee84de3ce34a86c373b6cfbea17d5486c2ca942d51efa99a0069723c1e3/pycapnp-2.0.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:0c111ef96676df25b8afef98f369d45f838ad4434e2898e48199eb43ef704efe", size = 1645816, upload-time = "2024-04-12T15:34:04.428Z" }, + { url = "https://files.pythonhosted.org/packages/35/1e/580572083165ba791fac5ae2d8917facb94db6e3f0500421673f55165dac/pycapnp-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0d18906eb1fd1b9f206d93a9591ceedce1d52e7766b66e68f271453f104e9dca", size = 1507892, upload-time = "2024-04-12T15:34:06.933Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ed/46b3cc5d32c525b6a3acb67eb43de2cec692a62775ec1ab66dafe2b7d6ad/pycapnp-2.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5d1ed365ab1beabb8838068907a7190cc0b6f16de3499d783627e670fcc0eb2", size = 4707960, upload-time = "2024-04-12T15:34:08.771Z" }, + { url = "https://files.pythonhosted.org/packages/8e/51/0a0a4d4e44138adb84959478ea4966196c5ad32022f768b9b64d1590cb3e/pycapnp-2.0.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:495b39a7aa2629931bbca27ad743ce591c6c41e8f81792276be424742d9cd1c1", size = 4791780, upload-time = "2024-04-12T15:34:10.863Z" }, + { url = "https://files.pythonhosted.org/packages/28/71/2b59c6ddb253b25b3d01ee6f7b32b0297ac205c7272beeb6d13399054430/pycapnp-2.0.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50e814fbde072dcc3d868b5b5cbb9b7a66a70bff9ad03942f3be9baf3ca1cfc6", size = 4961068, upload-time = "2024-04-12T15:34:13.543Z" }, + { url = "https://files.pythonhosted.org/packages/c3/b8/b64fdefa59d6d2802b5ee0a9439396c23a3e5954da6909be81f2722a234c/pycapnp-2.0.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:920fdda62d5fdef7a48339104dff0ceb9dcc21b138491f854457ba3a3d4d63ec", size = 4872917, upload-time = "2024-04-12T15:34:15.636Z" }, + { url = "https://files.pythonhosted.org/packages/c8/55/867595f575eb6cb3662e9a0b50a24b4be42df86f2938003e586f6c81606f/pycapnp-2.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f9142eb4714c152b09dda0b055ea9dd43fd8fd894132e7eb4fa235fb4915edd", size = 4912169, upload-time = "2024-04-12T15:34:17.758Z" }, + { url = "https://files.pythonhosted.org/packages/e4/11/0d36b45e5005ecdf8510081d16c6fb7b22b49651f64af36d138df97980cc/pycapnp-2.0.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c98f1d0c4d32109d03e42828ce3c65236afc895033633cbed3ca092993702e7b", size = 5201744, upload-time = "2024-04-12T15:34:20.468Z" }, + { url = "https://files.pythonhosted.org/packages/05/29/ad1357998656b7141939e55bb3aea727c7a5478026feed7f8ee8cf52c935/pycapnp-2.0.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4d3250c1875a309d67551843cd8bf3c5e7fccf159b7f5c118a92aee36c0e871c", size = 5351113, upload-time = "2024-04-12T15:34:23.173Z" }, + { url = "https://files.pythonhosted.org/packages/5a/b1/f4c442907948a29b6427dd7436f31d3732bb0d77f5c1dbcad749ba56dac0/pycapnp-2.0.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:174e6babe01f5507111c0ed226cd0b5e9325a9d2850751cfe4a57c1670f13881", size = 5472055, upload-time = "2024-04-12T15:34:25.799Z" }, + { url = "https://files.pythonhosted.org/packages/c1/06/a6eceb8b8015f518c0ccae1de5d1a6e18ed73b62b4b111aff54ce5f4f566/pycapnp-2.0.0-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:ed38ece414341285695526792e020f391f29f5064b2126d0367c8bdeef28e3e9", size = 5395743, upload-time = "2024-04-12T15:34:28.134Z" }, + { url = "https://files.pythonhosted.org/packages/e7/b0/63f2b0327853ae08158de61b4dfc7fa43ae5a5c00f1d28f769e7c30cdf55/pycapnp-2.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8a20b7dc55ef83a1fa446bf12680bce25caeb8f81788b623b072c3ec820db50d", size = 5405076, upload-time = "2024-04-12T15:34:30.305Z" }, + { url = "https://files.pythonhosted.org/packages/7d/24/e025dd95f1abf34e373fbab8841ac8e5fa62afe3af4a4b0c61bd01354400/pycapnp-2.0.0-cp312-cp312-win32.whl", hash = "sha256:145eea66233fb5ac9152cd1c06b999ddb691815126f87f5cc37b9cda5d569f8a", size = 1030361, upload-time = "2024-04-12T15:34:32.25Z" }, + { url = "https://files.pythonhosted.org/packages/3f/70/a71108ee9d4db9a027b665a2c383202407207174f1956195d5be45aca705/pycapnp-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:b8b03000769b29b36a8810f458b931f0f706f42027ee6676821eff28092d7734", size = 1135121, upload-time = "2024-04-12T15:34:34.208Z" }, ] [[package]] name = "pycparser" -version = "2.23" +version = "2.22" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736, upload-time = "2024-03-30T13:22:22.564Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, + { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552, upload-time = "2024-03-30T13:22:20.476Z" }, ] [[package]] @@ -1831,12 +1828,9 @@ wheels = [ [[package]] name = "pymsgbox" -version = "2.0.1" +version = "1.0.9" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ae/6a/e80da7594ee598a776972d09e2813df2b06b3bc29218f440631dfa7c78a8/pymsgbox-2.0.1.tar.gz", hash = "sha256:98d055c49a511dcc10fa08c3043e7102d468f5e4b3a83c6d3c61df722c7d798d", size = 20768, upload-time = "2025-09-09T00:38:56.863Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6f/3e/08c8cac81b2b2f7502746e6b9c8e5b0ec6432cd882c605560fc409aaf087/pymsgbox-2.0.1-py3-none-any.whl", hash = "sha256:5de8ec19bca2ca7e6c09d39c817c83f17c75cee80275235f43a9931db699f73b", size = 9994, upload-time = "2025-09-09T00:38:55.672Z" }, -] +sdist = { url = "https://files.pythonhosted.org/packages/7d/ff/4c6f31a4f08979f12a663f2aeb6c8b765d3bd592e66eaaac445f547bb875/PyMsgBox-1.0.9.tar.gz", hash = "sha256:2194227de8bff7a3d6da541848705a155dcbb2a06ee120d9f280a1d7f51263ff", size = 18829, upload-time = "2020-10-11T01:51:43.227Z" } [[package]] name = "pyobjc" @@ -4207,9 +4201,9 @@ name = "pyopencl" version = "2025.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy" }, - { name = "platformdirs" }, - { name = "pytools" }, + { 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'" }, ] 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 = [ @@ -4239,21 +4233,18 @@ wheels = [ [[package]] name = "pyparsing" -version = "3.2.4" +version = "3.2.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/98/c9/b4594e6a81371dfa9eb7a2c110ad682acf985d96115ae8b25a1d63b4bf3b/pyparsing-3.2.4.tar.gz", hash = "sha256:fff89494f45559d0f2ce46613b419f632bbb6afbdaed49696d322bcf98a58e99", size = 1098809, upload-time = "2025-09-13T05:47:19.732Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bb/22/f1129e69d94ffff626bdb5c835506b3a5b4f3d070f17ea295e12c2c6f60f/pyparsing-3.2.3.tar.gz", hash = "sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be", size = 1088608, upload-time = "2025-03-25T05:01:28.114Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/53/b8/fbab973592e23ae313042d450fc26fa24282ebffba21ba373786e1ce63b4/pyparsing-3.2.4-py3-none-any.whl", hash = "sha256:91d0fcde680d42cd031daf3a6ba20da3107e08a75de50da58360e7d94ab24d36", size = 113869, upload-time = "2025-09-13T05:47:17.863Z" }, + { url = "https://files.pythonhosted.org/packages/05/e7/df2285f3d08fee213f2d041540fa4fc9ca6c2d44cf36d3a035bf2a8d2bcc/pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf", size = 111120, upload-time = "2025-03-25T05:01:24.908Z" }, ] [[package]] name = "pyperclip" -version = "1.10.0" +version = "1.9.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/15/99/25f4898cf420efb6f45f519de018f4faea5391114a8618b16736ef3029f1/pyperclip-1.10.0.tar.gz", hash = "sha256:180c8346b1186921c75dfd14d9048a6b5d46bfc499778811952c6dd6eb1ca6be", size = 12193, upload-time = "2025-09-18T00:54:00.384Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/bc/22540e73c5f5ae18f02924cd3954a6c9a4aa6b713c841a94c98335d333a1/pyperclip-1.10.0-py3-none-any.whl", hash = "sha256:596fbe55dc59263bff26e61d2afbe10223e2fccb5210c9c96a28d6887cfcc7ec", size = 11062, upload-time = "2025-09-18T00:53:59.252Z" }, -] +sdist = { url = "https://files.pythonhosted.org/packages/30/23/2f0a3efc4d6a32f3b63cdff36cd398d9701d26cda58e3ab97ac79fb5e60d/pyperclip-1.9.0.tar.gz", hash = "sha256:b7de0142ddc81bfc5c7507eea19da920b92252b548b96186caf94a5e2527d310", size = 20961, upload-time = "2024-06-18T20:38:48.401Z" } [[package]] name = "pyprof2calltree" @@ -4287,7 +4278,7 @@ wheels = [ [[package]] name = "pytest" -version = "8.4.2" +version = "8.4.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -4296,22 +4287,21 @@ dependencies = [ { name = "pluggy" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } +sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714, upload-time = "2025-06-18T05:48:06.109Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, + { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" }, ] [[package]] name = "pytest-asyncio" -version = "1.2.0" +version = "1.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pytest" }, - { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/42/86/9e3c5f48f7b7b638b216e4b9e645f54d199d7abbbab7a64a13b4e12ba10f/pytest_asyncio-1.2.0.tar.gz", hash = "sha256:c609a64a2a8768462d0c99811ddb8bd2583c33fd33cf7f21af1c142e824ffb57", size = 50119, upload-time = "2025-09-12T07:33:53.816Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/51/f8794af39eeb870e87a8c8068642fc07bce0c854d6865d7dd0f2a9d338c2/pytest_asyncio-1.1.0.tar.gz", hash = "sha256:796aa822981e01b68c12e4827b8697108f7205020f24b5793b3c41555dab68ea", size = 46652, upload-time = "2025-07-16T04:29:26.393Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/04/93/2fa34714b7a4ae72f2f8dad66ba17dd9a2c793220719e736dda28b7aec27/pytest_asyncio-1.2.0-py3-none-any.whl", hash = "sha256:8e17ae5e46d8e7efe51ab6494dd2010f4ca8dae51652aa3c8d55acf50bfb2e99", size = 15095, upload-time = "2025-09-12T07:33:52.639Z" }, + { url = "https://files.pythonhosted.org/packages/c7/9d/bf86eddabf8c6c9cb1ea9a869d6873b46f105a5d292d3a6f7071f5b07935/pytest_asyncio-1.1.0-py3-none-any.whl", hash = "sha256:5fe2d69607b0bd75c656d1211f969cadba035030156745ee09e7d71740e58ecf", size = 15157, upload-time = "2025-07-16T04:29:24.929Z" }, ] [[package]] @@ -4328,26 +4318,26 @@ wheels = [ [[package]] name = "pytest-mock" -version = "3.15.1" +version = "3.14.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/68/14/eb014d26be205d38ad5ad20d9a80f7d201472e08167f0bb4361e251084a9/pytest_mock-3.15.1.tar.gz", hash = "sha256:1849a238f6f396da19762269de72cb1814ab44416fa73a8686deac10b0d87a0f", size = 34036, upload-time = "2025-09-16T16:37:27.081Z" } +sdist = { url = "https://files.pythonhosted.org/packages/71/28/67172c96ba684058a4d24ffe144d64783d2a270d0af0d9e792737bddc75c/pytest_mock-3.14.1.tar.gz", hash = "sha256:159e9edac4c451ce77a5cdb9fc5d1100708d2dd4ba3c3df572f14097351af80e", size = 33241, upload-time = "2025-05-26T13:58:45.167Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/cc/06253936f4a7fa2e0f48dfe6d851d9c56df896a9ab09ac019d70b760619c/pytest_mock-3.15.1-py3-none-any.whl", hash = "sha256:0a25e2eb88fe5168d535041d09a4529a188176ae608a6d249ee65abc0949630d", size = 10095, upload-time = "2025-09-16T16:37:25.734Z" }, + { url = "https://files.pythonhosted.org/packages/b2/05/77b60e520511c53d1c1ca75f1930c7dd8e971d0c4379b7f4b3f9644685ba/pytest_mock-3.14.1-py3-none-any.whl", hash = "sha256:178aefcd11307d874b4cd3100344e7e2d888d9791a6a1d9bfe90fbc1b74fd1d0", size = 9923, upload-time = "2025-05-26T13:58:43.487Z" }, ] [[package]] name = "pytest-randomly" -version = "4.0.1" +version = "3.16.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c4/1d/258a4bf1109258c00c35043f40433be5c16647387b6e7cd5582d638c116b/pytest_randomly-4.0.1.tar.gz", hash = "sha256:174e57bb12ac2c26f3578188490bd333f0e80620c3f47340158a86eca0593cd8", size = 14130, upload-time = "2025-09-12T15:23:00.085Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c0/68/d221ed7f4a2a49a664da721b8e87b52af6dd317af2a6cb51549cf17ac4b8/pytest_randomly-3.16.0.tar.gz", hash = "sha256:11bf4d23a26484de7860d82f726c0629837cf4064b79157bd18ec9d41d7feb26", size = 13367, upload-time = "2024-10-25T15:45:34.274Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/33/3e/a4a9227807b56869790aad3e24472a554b585974fe7e551ea350f50897ae/pytest_randomly-4.0.1-py3-none-any.whl", hash = "sha256:e0dfad2fd4f35e07beff1e47c17fbafcf98f9bf4531fd369d9260e2f858bfcb7", size = 8304, upload-time = "2025-09-12T15:22:58.946Z" }, + { url = "https://files.pythonhosted.org/packages/22/70/b31577d7c46d8e2f9baccfed5067dd8475262a2331ffb0bfdf19361c9bde/pytest_randomly-3.16.0-py3-none-any.whl", hash = "sha256:8633d332635a1a0983d3bba19342196807f6afb17c3eef78e02c2f85dade45d6", size = 8396, upload-time = "2024-10-25T15:45:32.78Z" }, ] [[package]] @@ -4389,7 +4379,7 @@ wheels = [ [[package]] name = "pytest-xdist" -version = "3.7.1.dev24+g2b4372bd6" +version = "3.7.1.dev24+g2b4372b" source = { git = "https://github.com/sshane/pytest-xdist?rev=2b4372bd62699fb412c4fe2f95bf9f01bd2018da#2b4372bd62699fb412c4fe2f95bf9f01bd2018da" } dependencies = [ { name = "execnet" }, @@ -4431,9 +4421,9 @@ name = "pytools" version = "2024.1.10" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "platformdirs" }, - { name = "siphash24" }, - { name = "typing-extensions" }, + { 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'" }, ] 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 = [ @@ -4531,38 +4521,38 @@ wheels = [ [[package]] name = "pyzmq" -version = "27.1.0" +version = "27.0.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "implementation_name == 'pypy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/04/0b/3c9baedbdf613ecaa7aa07027780b8867f57b6293b6ee50de316c9f3222b/pyzmq-27.1.0.tar.gz", hash = "sha256:ac0765e3d44455adb6ddbf4417dcce460fc40a05978c08efdf2948072f6db540", size = 281750, upload-time = "2025-09-08T23:10:18.157Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/66/159f38d184f08b5f971b467f87b1ab142ab1320d5200825c824b32b84b66/pyzmq-27.0.2.tar.gz", hash = "sha256:b398dd713b18de89730447347e96a0240225e154db56e35b6bb8447ffdb07798", size = 281440, upload-time = "2025-08-21T04:23:26.334Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/06/5d/305323ba86b284e6fcb0d842d6adaa2999035f70f8c38a9b6d21ad28c3d4/pyzmq-27.1.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:226b091818d461a3bef763805e75685e478ac17e9008f49fce2d3e52b3d58b86", size = 1333328, upload-time = "2025-09-08T23:07:45.946Z" }, - { url = "https://files.pythonhosted.org/packages/bd/a0/fc7e78a23748ad5443ac3275943457e8452da67fda347e05260261108cbc/pyzmq-27.1.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0790a0161c281ca9723f804871b4027f2e8b5a528d357c8952d08cd1a9c15581", size = 908803, upload-time = "2025-09-08T23:07:47.551Z" }, - { url = "https://files.pythonhosted.org/packages/7e/22/37d15eb05f3bdfa4abea6f6d96eb3bb58585fbd3e4e0ded4e743bc650c97/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c895a6f35476b0c3a54e3eb6ccf41bf3018de937016e6e18748317f25d4e925f", size = 668836, upload-time = "2025-09-08T23:07:49.436Z" }, - { url = "https://files.pythonhosted.org/packages/b1/c4/2a6fe5111a01005fc7af3878259ce17684fabb8852815eda6225620f3c59/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bbf8d3630bf96550b3be8e1fc0fea5cbdc8d5466c1192887bd94869da17a63e", size = 857038, upload-time = "2025-09-08T23:07:51.234Z" }, - { url = "https://files.pythonhosted.org/packages/cb/eb/bfdcb41d0db9cd233d6fb22dc131583774135505ada800ebf14dfb0a7c40/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:15c8bd0fe0dabf808e2d7a681398c4e5ded70a551ab47482067a572c054c8e2e", size = 1657531, upload-time = "2025-09-08T23:07:52.795Z" }, - { url = "https://files.pythonhosted.org/packages/ab/21/e3180ca269ed4a0de5c34417dfe71a8ae80421198be83ee619a8a485b0c7/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:bafcb3dd171b4ae9f19ee6380dfc71ce0390fefaf26b504c0e5f628d7c8c54f2", size = 2034786, upload-time = "2025-09-08T23:07:55.047Z" }, - { url = "https://files.pythonhosted.org/packages/3b/b1/5e21d0b517434b7f33588ff76c177c5a167858cc38ef740608898cd329f2/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e829529fcaa09937189178115c49c504e69289abd39967cd8a4c215761373394", size = 1894220, upload-time = "2025-09-08T23:07:57.172Z" }, - { url = "https://files.pythonhosted.org/packages/03/f2/44913a6ff6941905efc24a1acf3d3cb6146b636c546c7406c38c49c403d4/pyzmq-27.1.0-cp311-cp311-win32.whl", hash = "sha256:6df079c47d5902af6db298ec92151db82ecb557af663098b92f2508c398bb54f", size = 567155, upload-time = "2025-09-08T23:07:59.05Z" }, - { url = "https://files.pythonhosted.org/packages/23/6d/d8d92a0eb270a925c9b4dd039c0b4dc10abc2fcbc48331788824ef113935/pyzmq-27.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:190cbf120fbc0fc4957b56866830def56628934a9d112aec0e2507aa6a032b97", size = 633428, upload-time = "2025-09-08T23:08:00.663Z" }, - { url = "https://files.pythonhosted.org/packages/ae/14/01afebc96c5abbbd713ecfc7469cfb1bc801c819a74ed5c9fad9a48801cb/pyzmq-27.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:eca6b47df11a132d1745eb3b5b5e557a7dae2c303277aa0e69c6ba91b8736e07", size = 559497, upload-time = "2025-09-08T23:08:02.15Z" }, - { url = "https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:452631b640340c928fa343801b0d07eb0c3789a5ffa843f6e1a9cee0ba4eb4fc", size = 1306279, upload-time = "2025-09-08T23:08:03.807Z" }, - { url = "https://files.pythonhosted.org/packages/e8/5e/c3c49fdd0f535ef45eefcc16934648e9e59dace4a37ee88fc53f6cd8e641/pyzmq-27.1.0-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1c179799b118e554b66da67d88ed66cd37a169f1f23b5d9f0a231b4e8d44a113", size = 895645, upload-time = "2025-09-08T23:08:05.301Z" }, - { url = "https://files.pythonhosted.org/packages/f8/e5/b0b2504cb4e903a74dcf1ebae157f9e20ebb6ea76095f6cfffea28c42ecd/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3837439b7f99e60312f0c926a6ad437b067356dc2bc2ec96eb395fd0fe804233", size = 652574, upload-time = "2025-09-08T23:08:06.828Z" }, - { url = "https://files.pythonhosted.org/packages/f8/9b/c108cdb55560eaf253f0cbdb61b29971e9fb34d9c3499b0e96e4e60ed8a5/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43ad9a73e3da1fab5b0e7e13402f0b2fb934ae1c876c51d0afff0e7c052eca31", size = 840995, upload-time = "2025-09-08T23:08:08.396Z" }, - { url = "https://files.pythonhosted.org/packages/c2/bb/b79798ca177b9eb0825b4c9998c6af8cd2a7f15a6a1a4272c1d1a21d382f/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0de3028d69d4cdc475bfe47a6128eb38d8bc0e8f4d69646adfbcd840facbac28", size = 1642070, upload-time = "2025-09-08T23:08:09.989Z" }, - { url = "https://files.pythonhosted.org/packages/9c/80/2df2e7977c4ede24c79ae39dcef3899bfc5f34d1ca7a5b24f182c9b7a9ca/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:cf44a7763aea9298c0aa7dbf859f87ed7012de8bda0f3977b6fb1d96745df856", size = 2021121, upload-time = "2025-09-08T23:08:11.907Z" }, - { url = "https://files.pythonhosted.org/packages/46/bd/2d45ad24f5f5ae7e8d01525eb76786fa7557136555cac7d929880519e33a/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f30f395a9e6fbca195400ce833c731e7b64c3919aa481af4d88c3759e0cb7496", size = 1878550, upload-time = "2025-09-08T23:08:13.513Z" }, - { url = "https://files.pythonhosted.org/packages/e6/2f/104c0a3c778d7c2ab8190e9db4f62f0b6957b53c9d87db77c284b69f33ea/pyzmq-27.1.0-cp312-abi3-win32.whl", hash = "sha256:250e5436a4ba13885494412b3da5d518cd0d3a278a1ae640e113c073a5f88edd", size = 559184, upload-time = "2025-09-08T23:08:15.163Z" }, - { url = "https://files.pythonhosted.org/packages/fc/7f/a21b20d577e4100c6a41795842028235998a643b1ad406a6d4163ea8f53e/pyzmq-27.1.0-cp312-abi3-win_amd64.whl", hash = "sha256:9ce490cf1d2ca2ad84733aa1d69ce6855372cb5ce9223802450c9b2a7cba0ccf", size = 619480, upload-time = "2025-09-08T23:08:17.192Z" }, - { url = "https://files.pythonhosted.org/packages/78/c2/c012beae5f76b72f007a9e91ee9401cb88c51d0f83c6257a03e785c81cc2/pyzmq-27.1.0-cp312-abi3-win_arm64.whl", hash = "sha256:75a2f36223f0d535a0c919e23615fc85a1e23b71f40c7eb43d7b1dedb4d8f15f", size = 552993, upload-time = "2025-09-08T23:08:18.926Z" }, - { url = "https://files.pythonhosted.org/packages/4c/c6/c4dcdecdbaa70969ee1fdced6d7b8f60cfabe64d25361f27ac4665a70620/pyzmq-27.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:18770c8d3563715387139060d37859c02ce40718d1faf299abddcdcc6a649066", size = 836265, upload-time = "2025-09-08T23:09:49.376Z" }, - { url = "https://files.pythonhosted.org/packages/3e/79/f38c92eeaeb03a2ccc2ba9866f0439593bb08c5e3b714ac1d553e5c96e25/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:ac25465d42f92e990f8d8b0546b01c391ad431c3bf447683fdc40565941d0604", size = 800208, upload-time = "2025-09-08T23:09:51.073Z" }, - { url = "https://files.pythonhosted.org/packages/49/0e/3f0d0d335c6b3abb9b7b723776d0b21fa7f3a6c819a0db6097059aada160/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53b40f8ae006f2734ee7608d59ed661419f087521edbfc2149c3932e9c14808c", size = 567747, upload-time = "2025-09-08T23:09:52.698Z" }, - { url = "https://files.pythonhosted.org/packages/a1/cf/f2b3784d536250ffd4be70e049f3b60981235d70c6e8ce7e3ef21e1adb25/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f605d884e7c8be8fe1aa94e0a783bf3f591b84c24e4bc4f3e7564c82ac25e271", size = 747371, upload-time = "2025-09-08T23:09:54.563Z" }, - { url = "https://files.pythonhosted.org/packages/01/1b/5dbe84eefc86f48473947e2f41711aded97eecef1231f4558f1f02713c12/pyzmq-27.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c9f7f6e13dff2e44a6afeaf2cf54cee5929ad64afaf4d40b50f93c58fc687355", size = 544862, upload-time = "2025-09-08T23:09:56.509Z" }, + { url = "https://files.pythonhosted.org/packages/42/73/034429ab0f4316bf433eb6c20c3f49d1dc13b2ed4e4d951b283d300a0f35/pyzmq-27.0.2-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:063845960df76599ad4fad69fa4d884b3ba38304272104fdcd7e3af33faeeb1d", size = 1333169, upload-time = "2025-08-21T04:21:12.483Z" }, + { url = "https://files.pythonhosted.org/packages/35/02/c42b3b526eb03a570c889eea85a5602797f800a50ba8b09ddbf7db568b78/pyzmq-27.0.2-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:845a35fb21b88786aeb38af8b271d41ab0967985410f35411a27eebdc578a076", size = 909176, upload-time = "2025-08-21T04:21:13.835Z" }, + { url = "https://files.pythonhosted.org/packages/1b/35/a1c0b988fabbdf2dc5fe94b7c2bcfd61e3533e5109297b8e0daf1d7a8d2d/pyzmq-27.0.2-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:515d20b5c3c86db95503faa989853a8ab692aab1e5336db011cd6d35626c4cb1", size = 668972, upload-time = "2025-08-21T04:21:15.315Z" }, + { url = "https://files.pythonhosted.org/packages/a0/63/908ac865da32ceaeecea72adceadad28ca25b23a2ca5ff018e5bff30116f/pyzmq-27.0.2-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:862aedec0b0684a5050cdb5ec13c2da96d2f8dffda48657ed35e312a4e31553b", size = 856962, upload-time = "2025-08-21T04:21:16.652Z" }, + { url = "https://files.pythonhosted.org/packages/2f/5a/90b3cc20b65cdf9391896fcfc15d8db21182eab810b7ea05a2986912fbe2/pyzmq-27.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2cb5bcfc51c7a4fce335d3bc974fd1d6a916abbcdd2b25f6e89d37b8def25f57", size = 1657712, upload-time = "2025-08-21T04:21:18.666Z" }, + { url = "https://files.pythonhosted.org/packages/c4/3c/32a5a80f9be4759325b8d7b22ce674bb87e586b4c80c6a9d77598b60d6f0/pyzmq-27.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:38ff75b2a36e3a032e9fef29a5871e3e1301a37464e09ba364e3c3193f62982a", size = 2035054, upload-time = "2025-08-21T04:21:20.073Z" }, + { url = "https://files.pythonhosted.org/packages/13/61/71084fe2ff2d7dc5713f8740d735336e87544845dae1207a8e2e16d9af90/pyzmq-27.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7a5709abe8d23ca158a9d0a18c037f4193f5b6afeb53be37173a41e9fb885792", size = 1894010, upload-time = "2025-08-21T04:21:21.96Z" }, + { url = "https://files.pythonhosted.org/packages/cb/6b/77169cfb13b696e50112ca496b2ed23c4b7d8860a1ec0ff3e4b9f9926221/pyzmq-27.0.2-cp311-cp311-win32.whl", hash = "sha256:47c5dda2018c35d87be9b83de0890cb92ac0791fd59498847fc4eca6ff56671d", size = 566819, upload-time = "2025-08-21T04:21:23.31Z" }, + { url = "https://files.pythonhosted.org/packages/37/cd/86c4083e0f811f48f11bc0ddf1e7d13ef37adfd2fd4f78f2445f1cc5dec0/pyzmq-27.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:f54ca3e98f8f4d23e989c7d0edcf9da7a514ff261edaf64d1d8653dd5feb0a8b", size = 633264, upload-time = "2025-08-21T04:21:24.761Z" }, + { url = "https://files.pythonhosted.org/packages/a0/69/5b8bb6a19a36a569fac02153a9e083738785892636270f5f68a915956aea/pyzmq-27.0.2-cp311-cp311-win_arm64.whl", hash = "sha256:2ef3067cb5b51b090fb853f423ad7ed63836ec154374282780a62eb866bf5768", size = 559316, upload-time = "2025-08-21T04:21:26.1Z" }, + { url = "https://files.pythonhosted.org/packages/68/69/b3a729e7b03e412bee2b1823ab8d22e20a92593634f664afd04c6c9d9ac0/pyzmq-27.0.2-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:5da05e3c22c95e23bfc4afeee6ff7d4be9ff2233ad6cb171a0e8257cd46b169a", size = 1305910, upload-time = "2025-08-21T04:21:27.609Z" }, + { url = "https://files.pythonhosted.org/packages/15/b7/f6a6a285193d489b223c340b38ee03a673467cb54914da21c3d7849f1b10/pyzmq-27.0.2-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:4e4520577971d01d47e2559bb3175fce1be9103b18621bf0b241abe0a933d040", size = 895507, upload-time = "2025-08-21T04:21:29.005Z" }, + { url = "https://files.pythonhosted.org/packages/17/e6/c4ed2da5ef9182cde1b1f5d0051a986e76339d71720ec1a00be0b49275ad/pyzmq-27.0.2-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:56d7de7bf73165b90bd25a8668659ccb134dd28449116bf3c7e9bab5cf8a8ec9", size = 652670, upload-time = "2025-08-21T04:21:30.71Z" }, + { url = "https://files.pythonhosted.org/packages/0e/66/d781ab0636570d32c745c4e389b1c6b713115905cca69ab6233508622edd/pyzmq-27.0.2-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:340e7cddc32f147c6c00d116a3f284ab07ee63dbd26c52be13b590520434533c", size = 840581, upload-time = "2025-08-21T04:21:32.008Z" }, + { url = "https://files.pythonhosted.org/packages/a6/df/f24790caf565d72544f5c8d8500960b9562c1dc848d6f22f3c7e122e73d4/pyzmq-27.0.2-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ba95693f9df8bb4a9826464fb0fe89033936f35fd4a8ff1edff09a473570afa0", size = 1641931, upload-time = "2025-08-21T04:21:33.371Z" }, + { url = "https://files.pythonhosted.org/packages/65/65/77d27b19fc5e845367f9100db90b9fce924f611b14770db480615944c9c9/pyzmq-27.0.2-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:ca42a6ce2d697537da34f77a1960d21476c6a4af3e539eddb2b114c3cf65a78c", size = 2021226, upload-time = "2025-08-21T04:21:35.301Z" }, + { url = "https://files.pythonhosted.org/packages/5b/65/1ed14421ba27a4207fa694772003a311d1142b7f543179e4d1099b7eb746/pyzmq-27.0.2-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3e44e665d78a07214b2772ccbd4b9bcc6d848d7895f1b2d7653f047b6318a4f6", size = 1878047, upload-time = "2025-08-21T04:21:36.749Z" }, + { url = "https://files.pythonhosted.org/packages/dd/dc/e578549b89b40dc78a387ec471c2a360766690c0a045cd8d1877d401012d/pyzmq-27.0.2-cp312-abi3-win32.whl", hash = "sha256:272d772d116615397d2be2b1417b3b8c8bc8671f93728c2f2c25002a4530e8f6", size = 558757, upload-time = "2025-08-21T04:21:38.2Z" }, + { url = "https://files.pythonhosted.org/packages/b5/89/06600980aefcc535c758414da969f37a5194ea4cdb73b745223f6af3acfb/pyzmq-27.0.2-cp312-abi3-win_amd64.whl", hash = "sha256:734be4f44efba0aa69bf5f015ed13eb69ff29bf0d17ea1e21588b095a3147b8e", size = 619281, upload-time = "2025-08-21T04:21:39.909Z" }, + { url = "https://files.pythonhosted.org/packages/30/84/df8a5c089552d17c9941d1aea4314b606edf1b1622361dae89aacedc6467/pyzmq-27.0.2-cp312-abi3-win_arm64.whl", hash = "sha256:41f0bd56d9279392810950feb2785a419c2920bbf007fdaaa7f4a07332ae492d", size = 552680, upload-time = "2025-08-21T04:21:41.571Z" }, + { url = "https://files.pythonhosted.org/packages/c7/60/027d0032a1e3b1aabcef0e309b9ff8a4099bdd5a60ab38b36a676ff2bd7b/pyzmq-27.0.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e297784aea724294fe95e442e39a4376c2f08aa4fae4161c669f047051e31b02", size = 836007, upload-time = "2025-08-21T04:23:00.447Z" }, + { url = "https://files.pythonhosted.org/packages/25/20/2ed1e6168aaea323df9bb2c451309291f53ba3af372ffc16edd4ce15b9e5/pyzmq-27.0.2-pp311-pypy311_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:e3659a79ded9745bc9c2aef5b444ac8805606e7bc50d2d2eb16dc3ab5483d91f", size = 799932, upload-time = "2025-08-21T04:23:02.052Z" }, + { url = "https://files.pythonhosted.org/packages/fd/25/5c147307de546b502c9373688ce5b25dc22288d23a1ebebe5d587bf77610/pyzmq-27.0.2-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3dba49ff037d02373a9306b58d6c1e0be031438f822044e8767afccfdac4c6b", size = 567459, upload-time = "2025-08-21T04:23:03.593Z" }, + { url = "https://files.pythonhosted.org/packages/71/06/0dc56ffc615c8095cd089c9b98ce5c733e990f09ce4e8eea4aaf1041a532/pyzmq-27.0.2-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de84e1694f9507b29e7b263453a2255a73e3d099d258db0f14539bad258abe41", size = 747088, upload-time = "2025-08-21T04:23:05.334Z" }, + { url = "https://files.pythonhosted.org/packages/06/f6/4a50187e023b8848edd3f0a8e197b1a7fb08d261d8c60aae7cb6c3d71612/pyzmq-27.0.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f0944d65ba2b872b9fcece08411d6347f15a874c775b4c3baae7f278550da0fb", size = 544639, upload-time = "2025-08-21T04:23:07.279Z" }, ] [[package]] @@ -4665,28 +4655,28 @@ wheels = [ [[package]] name = "ruff" -version = "0.13.1" +version = "0.12.11" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ab/33/c8e89216845615d14d2d42ba2bee404e7206a8db782f33400754f3799f05/ruff-0.13.1.tar.gz", hash = "sha256:88074c3849087f153d4bb22e92243ad4c1b366d7055f98726bc19aa08dc12d51", size = 5397987, upload-time = "2025-09-18T19:52:44.33Z" } +sdist = { url = "https://files.pythonhosted.org/packages/de/55/16ab6a7d88d93001e1ae4c34cbdcfb376652d761799459ff27c1dc20f6fa/ruff-0.12.11.tar.gz", hash = "sha256:c6b09ae8426a65bbee5425b9d0b82796dbb07cb1af045743c79bfb163001165d", size = 5347103, upload-time = "2025-08-28T13:59:08.87Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/41/ca37e340938f45cfb8557a97a5c347e718ef34702546b174e5300dbb1f28/ruff-0.13.1-py3-none-linux_armv6l.whl", hash = "sha256:b2abff595cc3cbfa55e509d89439b5a09a6ee3c252d92020bd2de240836cf45b", size = 12304308, upload-time = "2025-09-18T19:51:56.253Z" }, - { url = "https://files.pythonhosted.org/packages/ff/84/ba378ef4129415066c3e1c80d84e539a0d52feb250685091f874804f28af/ruff-0.13.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:4ee9f4249bf7f8bb3984c41bfaf6a658162cdb1b22e3103eabc7dd1dc5579334", size = 12937258, upload-time = "2025-09-18T19:52:00.184Z" }, - { url = "https://files.pythonhosted.org/packages/8d/b6/ec5e4559ae0ad955515c176910d6d7c93edcbc0ed1a3195a41179c58431d/ruff-0.13.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5c5da4af5f6418c07d75e6f3224e08147441f5d1eac2e6ce10dcce5e616a3bae", size = 12214554, upload-time = "2025-09-18T19:52:02.753Z" }, - { url = "https://files.pythonhosted.org/packages/70/d6/cb3e3b4f03b9b0c4d4d8f06126d34b3394f6b4d764912fe80a1300696ef6/ruff-0.13.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80524f84a01355a59a93cef98d804e2137639823bcee2931f5028e71134a954e", size = 12448181, upload-time = "2025-09-18T19:52:05.279Z" }, - { url = "https://files.pythonhosted.org/packages/d2/ea/bf60cb46d7ade706a246cd3fb99e4cfe854efa3dfbe530d049c684da24ff/ruff-0.13.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff7f5ce8d7988767dd46a148192a14d0f48d1baea733f055d9064875c7d50389", size = 12104599, upload-time = "2025-09-18T19:52:07.497Z" }, - { url = "https://files.pythonhosted.org/packages/2d/3e/05f72f4c3d3a69e65d55a13e1dd1ade76c106d8546e7e54501d31f1dc54a/ruff-0.13.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c55d84715061f8b05469cdc9a446aa6c7294cd4bd55e86a89e572dba14374f8c", size = 13791178, upload-time = "2025-09-18T19:52:10.189Z" }, - { url = "https://files.pythonhosted.org/packages/81/e7/01b1fc403dd45d6cfe600725270ecc6a8f8a48a55bc6521ad820ed3ceaf8/ruff-0.13.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:ac57fed932d90fa1624c946dc67a0a3388d65a7edc7d2d8e4ca7bddaa789b3b0", size = 14814474, upload-time = "2025-09-18T19:52:12.866Z" }, - { url = "https://files.pythonhosted.org/packages/fa/92/d9e183d4ed6185a8df2ce9faa3f22e80e95b5f88d9cc3d86a6d94331da3f/ruff-0.13.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c366a71d5b4f41f86a008694f7a0d75fe409ec298685ff72dc882f882d532e36", size = 14217531, upload-time = "2025-09-18T19:52:15.245Z" }, - { url = "https://files.pythonhosted.org/packages/3b/4a/6ddb1b11d60888be224d721e01bdd2d81faaf1720592858ab8bac3600466/ruff-0.13.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4ea9d1b5ad3e7a83ee8ebb1229c33e5fe771e833d6d3dcfca7b77d95b060d38", size = 13265267, upload-time = "2025-09-18T19:52:17.649Z" }, - { url = "https://files.pythonhosted.org/packages/81/98/3f1d18a8d9ea33ef2ad508f0417fcb182c99b23258ec5e53d15db8289809/ruff-0.13.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0f70202996055b555d3d74b626406476cc692f37b13bac8828acff058c9966a", size = 13243120, upload-time = "2025-09-18T19:52:20.332Z" }, - { url = "https://files.pythonhosted.org/packages/8d/86/b6ce62ce9c12765fa6c65078d1938d2490b2b1d9273d0de384952b43c490/ruff-0.13.1-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:f8cff7a105dad631085d9505b491db33848007d6b487c3c1979dd8d9b2963783", size = 13443084, upload-time = "2025-09-18T19:52:23.032Z" }, - { url = "https://files.pythonhosted.org/packages/a1/6e/af7943466a41338d04503fb5a81b2fd07251bd272f546622e5b1599a7976/ruff-0.13.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:9761e84255443316a258dd7dfbd9bfb59c756e52237ed42494917b2577697c6a", size = 12295105, upload-time = "2025-09-18T19:52:25.263Z" }, - { url = "https://files.pythonhosted.org/packages/3f/97/0249b9a24f0f3ebd12f007e81c87cec6d311de566885e9309fcbac5b24cc/ruff-0.13.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:3d376a88c3102ef228b102211ef4a6d13df330cb0f5ca56fdac04ccec2a99700", size = 12072284, upload-time = "2025-09-18T19:52:27.478Z" }, - { url = "https://files.pythonhosted.org/packages/f6/85/0b64693b2c99d62ae65236ef74508ba39c3febd01466ef7f354885e5050c/ruff-0.13.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:cbefd60082b517a82c6ec8836989775ac05f8991715d228b3c1d86ccc7df7dae", size = 12970314, upload-time = "2025-09-18T19:52:30.212Z" }, - { url = "https://files.pythonhosted.org/packages/96/fc/342e9f28179915d28b3747b7654f932ca472afbf7090fc0c4011e802f494/ruff-0.13.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:dd16b9a5a499fe73f3c2ef09a7885cb1d97058614d601809d37c422ed1525317", size = 13422360, upload-time = "2025-09-18T19:52:32.676Z" }, - { url = "https://files.pythonhosted.org/packages/37/54/6177a0dc10bce6f43e392a2192e6018755473283d0cf43cc7e6afc182aea/ruff-0.13.1-py3-none-win32.whl", hash = "sha256:55e9efa692d7cb18580279f1fbb525146adc401f40735edf0aaeabd93099f9a0", size = 12178448, upload-time = "2025-09-18T19:52:35.545Z" }, - { url = "https://files.pythonhosted.org/packages/64/51/c6a3a33d9938007b8bdc8ca852ecc8d810a407fb513ab08e34af12dc7c24/ruff-0.13.1-py3-none-win_amd64.whl", hash = "sha256:3a3fb595287ee556de947183489f636b9f76a72f0fa9c028bdcabf5bab2cc5e5", size = 13286458, upload-time = "2025-09-18T19:52:38.198Z" }, - { url = "https://files.pythonhosted.org/packages/fd/04/afc078a12cf68592345b1e2d6ecdff837d286bac023d7a22c54c7a698c5b/ruff-0.13.1-py3-none-win_arm64.whl", hash = "sha256:c0bae9ffd92d54e03c2bf266f466da0a65e145f298ee5b5846ed435f6a00518a", size = 12437893, upload-time = "2025-09-18T19:52:41.283Z" }, + { url = "https://files.pythonhosted.org/packages/d6/a2/3b3573e474de39a7a475f3fbaf36a25600bfeb238e1a90392799163b64a0/ruff-0.12.11-py3-none-linux_armv6l.whl", hash = "sha256:93fce71e1cac3a8bf9200e63a38ac5c078f3b6baebffb74ba5274fb2ab276065", size = 11979885, upload-time = "2025-08-28T13:58:26.654Z" }, + { url = "https://files.pythonhosted.org/packages/76/e4/235ad6d1785a2012d3ded2350fd9bc5c5af8c6f56820e696b0118dfe7d24/ruff-0.12.11-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b8e33ac7b28c772440afa80cebb972ffd823621ded90404f29e5ab6d1e2d4b93", size = 12742364, upload-time = "2025-08-28T13:58:30.256Z" }, + { url = "https://files.pythonhosted.org/packages/2c/0d/15b72c5fe6b1e402a543aa9d8960e0a7e19dfb079f5b0b424db48b7febab/ruff-0.12.11-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d69fb9d4937aa19adb2e9f058bc4fbfe986c2040acb1a4a9747734834eaa0bfd", size = 11920111, upload-time = "2025-08-28T13:58:33.677Z" }, + { url = "https://files.pythonhosted.org/packages/3e/c0/f66339d7893798ad3e17fa5a1e587d6fd9806f7c1c062b63f8b09dda6702/ruff-0.12.11-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:411954eca8464595077a93e580e2918d0a01a19317af0a72132283e28ae21bee", size = 12160060, upload-time = "2025-08-28T13:58:35.74Z" }, + { url = "https://files.pythonhosted.org/packages/03/69/9870368326db26f20c946205fb2d0008988aea552dbaec35fbacbb46efaa/ruff-0.12.11-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6a2c0a2e1a450f387bf2c6237c727dd22191ae8c00e448e0672d624b2bbd7fb0", size = 11799848, upload-time = "2025-08-28T13:58:38.051Z" }, + { url = "https://files.pythonhosted.org/packages/25/8c/dd2c7f990e9b3a8a55eee09d4e675027d31727ce33cdb29eab32d025bdc9/ruff-0.12.11-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ca4c3a7f937725fd2413c0e884b5248a19369ab9bdd850b5781348ba283f644", size = 13536288, upload-time = "2025-08-28T13:58:40.046Z" }, + { url = "https://files.pythonhosted.org/packages/7a/30/d5496fa09aba59b5e01ea76775a4c8897b13055884f56f1c35a4194c2297/ruff-0.12.11-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:4d1df0098124006f6a66ecf3581a7f7e754c4df7644b2e6704cd7ca80ff95211", size = 14490633, upload-time = "2025-08-28T13:58:42.285Z" }, + { url = "https://files.pythonhosted.org/packages/9b/2f/81f998180ad53445d403c386549d6946d0748e536d58fce5b5e173511183/ruff-0.12.11-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5a8dd5f230efc99a24ace3b77e3555d3fbc0343aeed3fc84c8d89e75ab2ff793", size = 13888430, upload-time = "2025-08-28T13:58:44.641Z" }, + { url = "https://files.pythonhosted.org/packages/87/71/23a0d1d5892a377478c61dbbcffe82a3476b050f38b5162171942a029ef3/ruff-0.12.11-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4dc75533039d0ed04cd33fb8ca9ac9620b99672fe7ff1533b6402206901c34ee", size = 12913133, upload-time = "2025-08-28T13:58:47.039Z" }, + { url = "https://files.pythonhosted.org/packages/80/22/3c6cef96627f89b344c933781ed38329bfb87737aa438f15da95907cbfd5/ruff-0.12.11-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4fc58f9266d62c6eccc75261a665f26b4ef64840887fc6cbc552ce5b29f96cc8", size = 13169082, upload-time = "2025-08-28T13:58:49.157Z" }, + { url = "https://files.pythonhosted.org/packages/05/b5/68b3ff96160d8b49e8dd10785ff3186be18fd650d356036a3770386e6c7f/ruff-0.12.11-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:5a0113bd6eafd545146440225fe60b4e9489f59eb5f5f107acd715ba5f0b3d2f", size = 13139490, upload-time = "2025-08-28T13:58:51.593Z" }, + { url = "https://files.pythonhosted.org/packages/59/b9/050a3278ecd558f74f7ee016fbdf10591d50119df8d5f5da45a22c6afafc/ruff-0.12.11-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0d737b4059d66295c3ea5720e6efc152623bb83fde5444209b69cd33a53e2000", size = 11958928, upload-time = "2025-08-28T13:58:53.943Z" }, + { url = "https://files.pythonhosted.org/packages/f9/bc/93be37347db854806904a43b0493af8d6873472dfb4b4b8cbb27786eb651/ruff-0.12.11-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:916fc5defee32dbc1fc1650b576a8fed68f5e8256e2180d4d9855aea43d6aab2", size = 11764513, upload-time = "2025-08-28T13:58:55.976Z" }, + { url = "https://files.pythonhosted.org/packages/7a/a1/1471751e2015a81fd8e166cd311456c11df74c7e8769d4aabfbc7584c7ac/ruff-0.12.11-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c984f07d7adb42d3ded5be894fb4007f30f82c87559438b4879fe7aa08c62b39", size = 12745154, upload-time = "2025-08-28T13:58:58.16Z" }, + { url = "https://files.pythonhosted.org/packages/68/ab/2542b14890d0f4872dd81b7b2a6aed3ac1786fae1ce9b17e11e6df9e31e3/ruff-0.12.11-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e07fbb89f2e9249f219d88331c833860489b49cdf4b032b8e4432e9b13e8a4b9", size = 13227653, upload-time = "2025-08-28T13:59:00.276Z" }, + { url = "https://files.pythonhosted.org/packages/22/16/2fbfc61047dbfd009c58a28369a693a1484ad15441723be1cd7fe69bb679/ruff-0.12.11-py3-none-win32.whl", hash = "sha256:c792e8f597c9c756e9bcd4d87cf407a00b60af77078c96f7b6366ea2ce9ba9d3", size = 11944270, upload-time = "2025-08-28T13:59:02.347Z" }, + { url = "https://files.pythonhosted.org/packages/08/a5/34276984705bfe069cd383101c45077ee029c3fe3b28225bf67aa35f0647/ruff-0.12.11-py3-none-win_amd64.whl", hash = "sha256:a3283325960307915b6deb3576b96919ee89432ebd9c48771ca12ee8afe4a0fd", size = 13046600, upload-time = "2025-08-28T13:59:04.751Z" }, + { url = "https://files.pythonhosted.org/packages/84/a8/001d4a7c2b37623a3fd7463208267fb906df40ff31db496157549cfd6e72/ruff-0.12.11-py3-none-win_arm64.whl", hash = "sha256:bae4d6e6a2676f8fb0f98b74594a048bae1b944aab17e9f5d504062303c6dbea", size = 12135290, upload-time = "2025-08-28T13:59:06.933Z" }, ] [[package]] @@ -4700,46 +4690,47 @@ wheels = [ [[package]] name = "sentry-sdk" -version = "2.38.0" +version = "2.35.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b2/22/60fd703b34d94d216b2387e048ac82de3e86b63bc28869fb076f8bb0204a/sentry_sdk-2.38.0.tar.gz", hash = "sha256:792d2af45e167e2f8a3347143f525b9b6bac6f058fb2014720b40b84ccbeb985", size = 348116, upload-time = "2025-09-15T15:00:37.846Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/79/0ecb942f3f1ad26c40c27f81ff82392d85c01d26a45e3c72c2b37807e680/sentry_sdk-2.35.2.tar.gz", hash = "sha256:e9e8f3c795044beb59f2c8f4c6b9b0f9779e5e604099882df05eec525e782cc6", size = 343377, upload-time = "2025-09-01T11:00:58.633Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7a/84/bde4c4bbb269b71bc09316af8eb00da91f67814d40337cc12ef9c8742541/sentry_sdk-2.38.0-py2.py3-none-any.whl", hash = "sha256:2324aea8573a3fa1576df7fb4d65c4eb8d9929c8fa5939647397a07179eef8d0", size = 370346, upload-time = "2025-09-15T15:00:35.821Z" }, + { url = "https://files.pythonhosted.org/packages/c0/91/a43308dc82a0e32d80cd0dfdcfca401ecbd0f431ab45f24e48bb97b7800d/sentry_sdk-2.35.2-py2.py3-none-any.whl", hash = "sha256:38c98e3cbb620dd3dd80a8d6e39c753d453dd41f8a9df581b0584c19a52bc926", size = 363975, upload-time = "2025-09-01T11:00:56.574Z" }, ] [[package]] name = "setproctitle" -version = "1.3.7" +version = "1.3.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8d/48/49393a96a2eef1ab418b17475fb92b8fcfad83d099e678751b05472e69de/setproctitle-1.3.7.tar.gz", hash = "sha256:bc2bc917691c1537d5b9bca1468437176809c7e11e5694ca79a9ca12345dcb9e", size = 27002, upload-time = "2025-09-05T12:51:25.278Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9e/af/56efe21c53ac81ac87e000b15e60b3d8104224b4313b6eacac3597bd183d/setproctitle-1.3.6.tar.gz", hash = "sha256:c9f32b96c700bb384f33f7cf07954bb609d35dd82752cef57fb2ee0968409169", size = 26889, upload-time = "2025-04-29T13:35:00.184Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/04/cd/1b7ba5cad635510720ce19d7122154df96a2387d2a74217be552887c93e5/setproctitle-1.3.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a600eeb4145fb0ee6c287cb82a2884bd4ec5bbb076921e287039dcc7b7cc6dd0", size = 18085, upload-time = "2025-09-05T12:49:22.183Z" }, - { url = "https://files.pythonhosted.org/packages/8f/1a/b2da0a620490aae355f9d72072ac13e901a9fec809a6a24fc6493a8f3c35/setproctitle-1.3.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:97a090fed480471bb175689859532709e28c085087e344bca45cf318034f70c4", size = 13097, upload-time = "2025-09-05T12:49:23.322Z" }, - { url = "https://files.pythonhosted.org/packages/18/2e/bd03ff02432a181c1787f6fc2a678f53b7dacdd5ded69c318fe1619556e8/setproctitle-1.3.7-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1607b963e7b53e24ec8a2cb4e0ab3ae591d7c6bf0a160feef0551da63452b37f", size = 32191, upload-time = "2025-09-05T12:49:24.567Z" }, - { url = "https://files.pythonhosted.org/packages/28/78/1e62fc0937a8549f2220445ed2175daacee9b6764c7963b16148119b016d/setproctitle-1.3.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a20fb1a3974e2dab857870cf874b325b8705605cb7e7e8bcbb915bca896f52a9", size = 33203, upload-time = "2025-09-05T12:49:25.871Z" }, - { url = "https://files.pythonhosted.org/packages/a0/3c/65edc65db3fa3df400cf13b05e9d41a3c77517b4839ce873aa6b4043184f/setproctitle-1.3.7-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f8d961bba676e07d77665204f36cffaa260f526e7b32d07ab3df6a2c1dfb44ba", size = 34963, upload-time = "2025-09-05T12:49:27.044Z" }, - { url = "https://files.pythonhosted.org/packages/a1/32/89157e3de997973e306e44152522385f428e16f92f3cf113461489e1e2ee/setproctitle-1.3.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:db0fd964fbd3a9f8999b502f65bd2e20883fdb5b1fae3a424e66db9a793ed307", size = 32398, upload-time = "2025-09-05T12:49:28.909Z" }, - { url = "https://files.pythonhosted.org/packages/4a/18/77a765a339ddf046844cb4513353d8e9dcd8183da9cdba6e078713e6b0b2/setproctitle-1.3.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:db116850fcf7cca19492030f8d3b4b6e231278e8fe097a043957d22ce1bdf3ee", size = 33657, upload-time = "2025-09-05T12:49:30.323Z" }, - { url = "https://files.pythonhosted.org/packages/6b/63/f0b6205c64d74d2a24a58644a38ec77bdbaa6afc13747e75973bf8904932/setproctitle-1.3.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:316664d8b24a5c91ee244460bdaf7a74a707adaa9e14fbe0dc0a53168bb9aba1", size = 31836, upload-time = "2025-09-05T12:49:32.309Z" }, - { url = "https://files.pythonhosted.org/packages/ba/51/e1277f9ba302f1a250bbd3eedbbee747a244b3cc682eb58fb9733968f6d8/setproctitle-1.3.7-cp311-cp311-win32.whl", hash = "sha256:b74774ca471c86c09b9d5037c8451fff06bb82cd320d26ae5a01c758088c0d5d", size = 12556, upload-time = "2025-09-05T12:49:33.529Z" }, - { url = "https://files.pythonhosted.org/packages/b6/7b/822a23f17e9003dfdee92cd72758441ca2a3680388da813a371b716fb07f/setproctitle-1.3.7-cp311-cp311-win_amd64.whl", hash = "sha256:acb9097213a8dd3410ed9f0dc147840e45ca9797785272928d4be3f0e69e3be4", size = 13243, upload-time = "2025-09-05T12:49:34.553Z" }, - { url = "https://files.pythonhosted.org/packages/fb/f0/2dc88e842077719d7384d86cc47403e5102810492b33680e7dadcee64cd8/setproctitle-1.3.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2dc99aec591ab6126e636b11035a70991bc1ab7a261da428491a40b84376654e", size = 18049, upload-time = "2025-09-05T12:49:36.241Z" }, - { url = "https://files.pythonhosted.org/packages/f0/b4/50940504466689cda65680c9e9a1e518e5750c10490639fa687489ac7013/setproctitle-1.3.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cdd8aa571b7aa39840fdbea620e308a19691ff595c3a10231e9ee830339dd798", size = 13079, upload-time = "2025-09-05T12:49:38.088Z" }, - { url = "https://files.pythonhosted.org/packages/d0/99/71630546b9395b095f4082be41165d1078204d1696c2d9baade3de3202d0/setproctitle-1.3.7-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2906b6c7959cdb75f46159bf0acd8cc9906cf1361c9e1ded0d065fe8f9039629", size = 32932, upload-time = "2025-09-05T12:49:39.271Z" }, - { url = "https://files.pythonhosted.org/packages/50/22/cee06af4ffcfb0e8aba047bd44f5262e644199ae7527ae2c1f672b86495c/setproctitle-1.3.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6915964a6dda07920a1159321dcd6d94fc7fc526f815ca08a8063aeca3c204f1", size = 33736, upload-time = "2025-09-05T12:49:40.565Z" }, - { url = "https://files.pythonhosted.org/packages/5c/00/a5949a8bb06ef5e7df214fc393bb2fb6aedf0479b17214e57750dfdd0f24/setproctitle-1.3.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cff72899861c765bd4021d1ff1c68d60edc129711a2fdba77f9cb69ef726a8b6", size = 35605, upload-time = "2025-09-05T12:49:42.362Z" }, - { url = "https://files.pythonhosted.org/packages/b0/3a/50caca532a9343828e3bf5778c7a84d6c737a249b1796d50dd680290594d/setproctitle-1.3.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b7cb05bd446687ff816a3aaaf831047fc4c364feff7ada94a66024f1367b448c", size = 33143, upload-time = "2025-09-05T12:49:43.515Z" }, - { url = "https://files.pythonhosted.org/packages/ca/14/b843a251296ce55e2e17c017d6b9f11ce0d3d070e9265de4ecad948b913d/setproctitle-1.3.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3a57b9a00de8cae7e2a1f7b9f0c2ac7b69372159e16a7708aa2f38f9e5cc987a", size = 34434, upload-time = "2025-09-05T12:49:45.31Z" }, - { url = "https://files.pythonhosted.org/packages/c8/b7/06145c238c0a6d2c4bc881f8be230bb9f36d2bf51aff7bddcb796d5eed67/setproctitle-1.3.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d8828b356114f6b308b04afe398ed93803d7fca4a955dd3abe84430e28d33739", size = 32795, upload-time = "2025-09-05T12:49:46.419Z" }, - { url = "https://files.pythonhosted.org/packages/ef/dc/ef76a81fac9bf27b84ed23df19c1f67391a753eed6e3c2254ebcb5133f56/setproctitle-1.3.7-cp312-cp312-win32.whl", hash = "sha256:b0304f905efc845829ac2bc791ddebb976db2885f6171f4a3de678d7ee3f7c9f", size = 12552, upload-time = "2025-09-05T12:49:47.635Z" }, - { url = "https://files.pythonhosted.org/packages/e2/5b/a9fe517912cd6e28cf43a212b80cb679ff179a91b623138a99796d7d18a0/setproctitle-1.3.7-cp312-cp312-win_amd64.whl", hash = "sha256:9888ceb4faea3116cf02a920ff00bfbc8cc899743e4b4ac914b03625bdc3c300", size = 13247, upload-time = "2025-09-05T12:49:49.16Z" }, - { url = "https://files.pythonhosted.org/packages/c3/5b/5e1c117ac84e3cefcf8d7a7f6b2461795a87e20869da065a5c087149060b/setproctitle-1.3.7-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:b1cac6a4b0252b8811d60b6d8d0f157c0fdfed379ac89c25a914e6346cf355a1", size = 12587, upload-time = "2025-09-05T12:51:21.195Z" }, - { url = "https://files.pythonhosted.org/packages/73/02/b9eadc226195dcfa90eed37afe56b5dd6fa2f0e5220ab8b7867b8862b926/setproctitle-1.3.7-pp311-pypy311_pp73-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f1704c9e041f2b1dc38f5be4552e141e1432fba3dd52c72eeffd5bc2db04dc65", size = 14286, upload-time = "2025-09-05T12:51:22.61Z" }, - { url = "https://files.pythonhosted.org/packages/28/26/1be1d2a53c2a91ec48fa2ff4a409b395f836798adf194d99de9c059419ea/setproctitle-1.3.7-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:b08b61976ffa548bd5349ce54404bf6b2d51bd74d4f1b241ed1b0f25bce09c3a", size = 13282, upload-time = "2025-09-05T12:51:24.094Z" }, + { url = "https://files.pythonhosted.org/packages/27/3b/8288d0cd969a63500dd62fc2c99ce6980f9909ccef0770ab1f86c361e0bf/setproctitle-1.3.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a1d856b0f4e4a33e31cdab5f50d0a14998f3a2d726a3fd5cb7c4d45a57b28d1b", size = 17412, upload-time = "2025-04-29T13:32:58.135Z" }, + { url = "https://files.pythonhosted.org/packages/39/37/43a5a3e25ca1048dbbf4db0d88d346226f5f1acd131bb8e660f4bfe2799f/setproctitle-1.3.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:50706b9c0eda55f7de18695bfeead5f28b58aa42fd5219b3b1692d554ecbc9ec", size = 11963, upload-time = "2025-04-29T13:32:59.17Z" }, + { url = "https://files.pythonhosted.org/packages/5b/47/f103c40e133154783c91a10ab08ac9fc410ed835aa85bcf7107cb882f505/setproctitle-1.3.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:af188f3305f0a65c3217c30c6d4c06891e79144076a91e8b454f14256acc7279", size = 31718, upload-time = "2025-04-29T13:33:00.36Z" }, + { url = "https://files.pythonhosted.org/packages/1f/13/7325dd1c008dd6c0ebd370ddb7505977054a87e406f142318e395031a792/setproctitle-1.3.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cce0ed8b3f64c71c140f0ec244e5fdf8ecf78ddf8d2e591d4a8b6aa1c1214235", size = 33027, upload-time = "2025-04-29T13:33:01.499Z" }, + { url = "https://files.pythonhosted.org/packages/0c/0a/6075bfea05a71379d77af98a9ac61163e8b6e5ef1ae58cd2b05871b2079c/setproctitle-1.3.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70100e2087fe05359f249a0b5f393127b3a1819bf34dec3a3e0d4941138650c9", size = 30223, upload-time = "2025-04-29T13:33:03.259Z" }, + { url = "https://files.pythonhosted.org/packages/cc/41/fbf57ec52f4f0776193bd94334a841f0bc9d17e745f89c7790f336420c65/setproctitle-1.3.6-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1065ed36bd03a3fd4186d6c6de5f19846650b015789f72e2dea2d77be99bdca1", size = 31204, upload-time = "2025-04-29T13:33:04.455Z" }, + { url = "https://files.pythonhosted.org/packages/97/b5/f799fb7a00de29fb0ac1dfd015528dea425b9e31a8f1068a0b3df52d317f/setproctitle-1.3.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4adf6a0013fe4e0844e3ba7583ec203ca518b9394c6cc0d3354df2bf31d1c034", size = 31181, upload-time = "2025-04-29T13:33:05.697Z" }, + { url = "https://files.pythonhosted.org/packages/b5/b7/81f101b612014ec61723436022c31146178813d6ca6b947f7b9c84e9daf4/setproctitle-1.3.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:eb7452849f6615871eabed6560ffedfe56bc8af31a823b6be4ce1e6ff0ab72c5", size = 30101, upload-time = "2025-04-29T13:33:07.223Z" }, + { url = "https://files.pythonhosted.org/packages/67/23/681232eed7640eab96719daa8647cc99b639e3daff5c287bd270ef179a73/setproctitle-1.3.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a094b7ce455ca341b59a0f6ce6be2e11411ba6e2860b9aa3dbb37468f23338f4", size = 32438, upload-time = "2025-04-29T13:33:08.538Z" }, + { url = "https://files.pythonhosted.org/packages/19/f8/4d075a7bdc3609ac71535b849775812455e4c40aedfbf0778a6f123b1774/setproctitle-1.3.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ad1c2c2baaba62823a7f348f469a967ece0062140ca39e7a48e4bbb1f20d54c4", size = 30625, upload-time = "2025-04-29T13:33:09.707Z" }, + { url = "https://files.pythonhosted.org/packages/5f/73/a2a8259ebee166aee1ca53eead75de0e190b3ddca4f716e5c7470ebb7ef6/setproctitle-1.3.6-cp311-cp311-win32.whl", hash = "sha256:8050c01331135f77ec99d99307bfbc6519ea24d2f92964b06f3222a804a3ff1f", size = 11488, upload-time = "2025-04-29T13:33:10.953Z" }, + { url = "https://files.pythonhosted.org/packages/c9/15/52cf5e1ff0727d53704cfdde2858eaf237ce523b0b04db65faa84ff83e13/setproctitle-1.3.6-cp311-cp311-win_amd64.whl", hash = "sha256:9b73cf0fe28009a04a35bb2522e4c5b5176cc148919431dcb73fdbdfaab15781", size = 12201, upload-time = "2025-04-29T13:33:12.389Z" }, + { url = "https://files.pythonhosted.org/packages/8f/fb/99456fd94d4207c5f6c40746a048a33a52b4239cd7d9c8d4889e2210ec82/setproctitle-1.3.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:af44bb7a1af163806bbb679eb8432fa7b4fb6d83a5d403b541b675dcd3798638", size = 17399, upload-time = "2025-04-29T13:33:13.406Z" }, + { url = "https://files.pythonhosted.org/packages/d5/48/9699191fe6062827683c43bfa9caac33a2c89f8781dd8c7253fa3dba85fd/setproctitle-1.3.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3cca16fd055316a48f0debfcbfb6af7cea715429fc31515ab3fcac05abd527d8", size = 11966, upload-time = "2025-04-29T13:33:14.976Z" }, + { url = "https://files.pythonhosted.org/packages/33/03/b085d192b9ecb9c7ce6ad6ef30ecf4110b7f39430b58a56245569827fcf4/setproctitle-1.3.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea002088d5554fd75e619742cefc78b84a212ba21632e59931b3501f0cfc8f67", size = 32017, upload-time = "2025-04-29T13:33:16.163Z" }, + { url = "https://files.pythonhosted.org/packages/ae/68/c53162e645816f97212002111420d1b2f75bf6d02632e37e961dc2cd6d8b/setproctitle-1.3.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb465dd5825356c1191a038a86ee1b8166e3562d6e8add95eec04ab484cfb8a2", size = 33419, upload-time = "2025-04-29T13:33:18.239Z" }, + { url = "https://files.pythonhosted.org/packages/ac/0d/119a45d15a816a6cf5ccc61b19729f82620095b27a47e0a6838216a95fae/setproctitle-1.3.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d2c8e20487b3b73c1fa72c56f5c89430617296cd380373e7af3a538a82d4cd6d", size = 30711, upload-time = "2025-04-29T13:33:19.571Z" }, + { url = "https://files.pythonhosted.org/packages/e3/fb/5e9b5068df9e9f31a722a775a5e8322a29a638eaaa3eac5ea7f0b35e6314/setproctitle-1.3.6-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0d6252098e98129a1decb59b46920d4eca17b0395f3d71b0d327d086fefe77d", size = 31742, upload-time = "2025-04-29T13:33:21.172Z" }, + { url = "https://files.pythonhosted.org/packages/35/88/54de1e73e8fce87d587889c7eedb48fc4ee2bbe4e4ca6331690d03024f86/setproctitle-1.3.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cf355fbf0d4275d86f9f57be705d8e5eaa7f8ddb12b24ced2ea6cbd68fdb14dc", size = 31925, upload-time = "2025-04-29T13:33:22.427Z" }, + { url = "https://files.pythonhosted.org/packages/f3/01/65948d7badd66e63e3db247b923143da142790fa293830fdecf832712c2d/setproctitle-1.3.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e288f8a162d663916060beb5e8165a8551312b08efee9cf68302687471a6545d", size = 30981, upload-time = "2025-04-29T13:33:23.739Z" }, + { url = "https://files.pythonhosted.org/packages/22/20/c495e61786f1d38d5dc340b9d9077fee9be3dfc7e89f515afe12e1526dbc/setproctitle-1.3.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b2e54f4a2dc6edf0f5ea5b1d0a608d2af3dcb5aa8c8eeab9c8841b23e1b054fe", size = 33209, upload-time = "2025-04-29T13:33:24.915Z" }, + { url = "https://files.pythonhosted.org/packages/98/3f/a457b8550fbd34d5b482fe20b8376b529e76bf1fbf9a474a6d9a641ab4ad/setproctitle-1.3.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b6f4abde9a2946f57e8daaf1160b2351bcf64274ef539e6675c1d945dbd75e2a", size = 31587, upload-time = "2025-04-29T13:33:26.123Z" }, + { url = "https://files.pythonhosted.org/packages/44/fe/743517340e5a635e3f1c4310baea20c16c66202f96a6f4cead222ffd6d84/setproctitle-1.3.6-cp312-cp312-win32.whl", hash = "sha256:db608db98ccc21248370d30044a60843b3f0f3d34781ceeea67067c508cd5a28", size = 11487, upload-time = "2025-04-29T13:33:27.403Z" }, + { url = "https://files.pythonhosted.org/packages/60/9a/d88f1c1f0f4efff1bd29d9233583ee341114dda7d9613941453984849674/setproctitle-1.3.6-cp312-cp312-win_amd64.whl", hash = "sha256:082413db8a96b1f021088e8ec23f0a61fec352e649aba20881895815388b66d3", size = 12208, upload-time = "2025-04-29T13:33:28.852Z" }, ] [[package]] @@ -4756,7 +4747,7 @@ name = "shapely" version = "2.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy" }, + { name = "numpy", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, ] 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 = [ @@ -4780,22 +4771,22 @@ wheels = [ [[package]] name = "siphash24" -version = "1.8" +version = "1.7" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/67/a2/e049b6fccf7a94bd1b2f68b3059a7d6a7aea86a808cac80cb9ae71ab6254/siphash24-1.8.tar.gz", hash = "sha256:aa932f0af4a7335caef772fdaf73a433a32580405c41eb17ff24077944b0aa97", size = 19946, upload-time = "2025-09-02T20:42:04.856Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0e/be/f0a0ffbb00c51c5633b41459b5ce9b017c025a9256b4403e648c18e70850/siphash24-1.7.tar.gz", hash = "sha256:6e90fee5f199ea25b4e7303646b31872a437174fe885a93dbd4cf7784eb48164", size = 19801, upload-time = "2024-10-15T13:41:51.924Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/82/23/f53f5bd8866c6ea3abe434c9f208e76ea027210d8b75cd0e0dc849661c7a/siphash24-1.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4662ac616bce4d3c9d6003a0d398e56f8be408fc53a166b79fad08d4f34268e", size = 76930, upload-time = "2025-09-02T20:41:00.869Z" }, - { url = "https://files.pythonhosted.org/packages/0b/25/aebf246904424a06e7ffb7a40cfa9ea9e590ea0fac82e182e0f5d1f1d7ef/siphash24-1.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:53d6bed0951a99c6d2891fa6f8acfd5ca80c3e96c60bcee99f6fa01a04773b1c", size = 74315, upload-time = "2025-09-02T20:41:02.38Z" }, - { url = "https://files.pythonhosted.org/packages/59/3f/7010407c3416ef052d46550d54afb2581fb247018fc6500af8c66669eff2/siphash24-1.8-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d114c03648630e9e07dac2fe95442404e4607adca91640d274ece1a4fa71123e", size = 99756, upload-time = "2025-09-02T20:41:03.902Z" }, - { url = "https://files.pythonhosted.org/packages/d4/9f/09c734833e69badd7e3faed806b4372bd6564ae0946bd250d5239885914f/siphash24-1.8-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:88c1a55ff82b127c5d3b96927a430d8859e6a98846a5b979833ac790682dd91b", size = 104044, upload-time = "2025-09-02T20:41:05.505Z" }, - { url = "https://files.pythonhosted.org/packages/24/30/56a26d9141a34433da221f732599e2b23d2d70a966c249a9f00feb9a2915/siphash24-1.8-cp311-cp311-win32.whl", hash = "sha256:9430255e6a1313470f52c07c4a4643c451a5b2853f6d4008e4dda05cafb6ce7c", size = 62196, upload-time = "2025-09-02T20:41:07.299Z" }, - { url = "https://files.pythonhosted.org/packages/47/b2/11b0ae63fd374652544e1b12f72ba2cc3fe6c93c1483bd8ff6935b0a8a4b/siphash24-1.8-cp311-cp311-win_amd64.whl", hash = "sha256:1e4b37e4ef0b4496169adce2a58b6c3f230b5852dfa5f7ad0b2d664596409e47", size = 77162, upload-time = "2025-09-02T20:41:08.878Z" }, - { url = "https://files.pythonhosted.org/packages/7f/82/ce3545ce8052ac7ca104b183415a27ec3335e5ed51978fdd7b433f3cfe5b/siphash24-1.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:df5ed437c6e6cc96196b38728e57cd30b0427df45223475a90e173f5015ef5ba", size = 78136, upload-time = "2025-09-02T20:41:10.083Z" }, - { url = "https://files.pythonhosted.org/packages/15/88/896c3b91bc9deb78c415448b1db67343917f35971a9e23a5967a9d323b8a/siphash24-1.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f4ef78abdf811325c7089a35504df339c48c0007d4af428a044431d329721e56", size = 74588, upload-time = "2025-09-02T20:41:11.251Z" }, - { url = "https://files.pythonhosted.org/packages/12/fd/8dad3f5601db485ba862e1c1f91a5d77fb563650856a6708e9acb40ee53c/siphash24-1.8-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:065eff55c4fefb3a29fd26afb2c072abf7f668ffd53b91d41f92a1c485fcbe5c", size = 98655, upload-time = "2025-09-02T20:41:12.45Z" }, - { url = "https://files.pythonhosted.org/packages/e3/cc/e0c352624c1f2faad270aeb5cce6e173977ef66b9b5e918aa6f32af896bf/siphash24-1.8-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ac6fa84ebfd47677262aa0bcb0f5a70f796f5fc5704b287ee1b65a3bd4fb7a5d", size = 103217, upload-time = "2025-09-02T20:41:13.746Z" }, - { url = "https://files.pythonhosted.org/packages/5b/f6/0b1675bea4d40affcae642d9c7337702a4138b93c544230280712403e968/siphash24-1.8-cp312-cp312-win32.whl", hash = "sha256:6582f73615552ca055e51e03cb02a28e570a641a7f500222c86c2d811b5037eb", size = 63114, upload-time = "2025-09-02T20:41:14.972Z" }, - { url = "https://files.pythonhosted.org/packages/3d/39/afefef85d72ed8b5cf1aa9283f712e3cd43c9682fabbc809dec54baa8452/siphash24-1.8-cp312-cp312-win_amd64.whl", hash = "sha256:44ea6d794a7cbe184e1e1da2df81c5ebb672ab3867935c3e87c08bb0c2fa4879", size = 76232, upload-time = "2025-09-02T20:41:16.112Z" }, + { url = "https://files.pythonhosted.org/packages/4e/67/4ffd23a848739966e1b314ef99f6410035bccee00be14261313787b8f506/siphash24-1.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:de75488e93f1cd12c8d5004efd1ebd958c0265205a9d73e8dd8b071900838841", size = 80493, upload-time = "2024-10-15T13:41:14.727Z" }, + { url = "https://files.pythonhosted.org/packages/56/bd/ec198a8c7aef65e967ae84f633bd9950d784c9e527d738c9a3e4bccc34a5/siphash24-1.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ffca9908450f9f346e97a223185fcd16217d67d84c6f246f3080c4224f41a514", size = 75350, upload-time = "2024-10-15T13:41:16.262Z" }, + { url = "https://files.pythonhosted.org/packages/50/5a/77838c916bd15addfc2e51286db4c442cb12e25eb4f8d296c394c2280240/siphash24-1.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8ff44ce166452993fea267ea1b2fd089d8e7f103b13d360da441f12b0df121d", size = 100567, upload-time = "2024-10-15T13:41:17.435Z" }, + { url = "https://files.pythonhosted.org/packages/f0/aa/736a0a2efae9a6f69ac1ee4d28c2274fcad2150349fac752d6c525c4e06e/siphash24-1.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4062548dcb1eef13bbe0356d6f8675bfe4571ef38d7103445daa82ba167240d1", size = 105630, upload-time = "2024-10-15T13:41:18.578Z" }, + { url = "https://files.pythonhosted.org/packages/79/52/1afbd70142d3db093d49197e3abe15ca2f1a14678299327ba776944b4771/siphash24-1.7-cp311-cp311-win32.whl", hash = "sha256:7b4ea29376b688fbcc3d25707c15a9dfe7b4ebbc4322878d75bb77e199210a39", size = 67648, upload-time = "2024-10-15T13:41:19.606Z" }, + { url = "https://files.pythonhosted.org/packages/b5/1d/bedcd04c2d1d199c9f6b3e61a6caae0e17257696c9f49594e49856b17a99/siphash24-1.7-cp311-cp311-win_amd64.whl", hash = "sha256:ec06104e6ef1e512ee30f1b8aeae2b83c0f55f12a94042f0df5a87d43a1f4c52", size = 80046, upload-time = "2024-10-15T13:41:20.654Z" }, + { url = "https://files.pythonhosted.org/packages/3e/62/93e552af9535a416f684327f870143ee42fc9e816091672467cdfd62cce6/siphash24-1.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:76a64ff0cdd192e4d0a391956d9b121c56ed56e773c5ab7eb7c3e035fd16e8cb", size = 82084, upload-time = "2024-10-15T13:41:21.776Z" }, + { url = "https://files.pythonhosted.org/packages/59/3e/b0791ab53aa9ac191b71a021eab2e75baa7c27d7feb7ec148d7961d148ba/siphash24-1.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:49ca649bc7437d614f758891deade3b187832792a853269219e77f10509f82fe", size = 76233, upload-time = "2024-10-15T13:41:22.787Z" }, + { url = "https://files.pythonhosted.org/packages/29/4c/4c1b809bf302e9b60f3ec09ba115b2a4ac1ff6755735ee8884924fcdb45e/siphash24-1.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc37dd0aed23f76bd257fbd2953fd5d954b329d7463c6ff57263a2699c52dde6", size = 98188, upload-time = "2024-10-15T13:41:24.327Z" }, + { url = "https://files.pythonhosted.org/packages/96/bf/e6b49f8ff88130bd224f291ea77d30fdde4df5f6572c519aca5d8fc8a27c/siphash24-1.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eea490a200891905856b6ad0f9c56d4ec787876220bcb34c49441b2566b97887", size = 102946, upload-time = "2024-10-15T13:41:25.633Z" }, + { url = "https://files.pythonhosted.org/packages/3d/75/45c831626013950fb2ea715c218c3397e5cf2328a67208bf5d8ff69aa9e6/siphash24-1.7-cp312-cp312-win32.whl", hash = "sha256:69eb8c2c112a738875bb283cd53ef5e86874bc5aed17f3020b38e9174208fb79", size = 68323, upload-time = "2024-10-15T13:41:27.349Z" }, + { url = "https://files.pythonhosted.org/packages/e0/d3/39190c40a68defd19b99c1082dd7455543a52283803bfa111b0e45fae968/siphash24-1.7-cp312-cp312-win_amd64.whl", hash = "sha256:7459569ea4669b6feeaf7d299fc5157cc5c69ca1231dc0decb7a7da2397c782e", size = 81000, upload-time = "2024-10-15T13:41:28.364Z" }, ] [[package]] @@ -4842,9 +4833,9 @@ wheels = [ [[package]] name = "spidev" -version = "3.8" +version = "3.7" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/67/87/039b6eeea781598015b538691bc174cc0bf77df9d4d2d3b8bf9245c0de8c/spidev-3.8.tar.gz", hash = "sha256:2bc02fb8c6312d519ebf1f4331067427c0921d3f77b8bcaf05189a2e8b8382c0", size = 13893, upload-time = "2025-09-15T18:56:20.672Z" } +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" @@ -4881,14 +4872,14 @@ wheels = [ [[package]] name = "types-requests" -version = "2.32.4.20250913" +version = "2.32.4.20250809" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/36/27/489922f4505975b11de2b5ad07b4fe1dca0bca9be81a703f26c5f3acfce5/types_requests-2.32.4.20250913.tar.gz", hash = "sha256:abd6d4f9ce3a9383f269775a9835a4c24e5cd6b9f647d64f88aa4613c33def5d", size = 23113, upload-time = "2025-09-13T02:40:02.309Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ed/b0/9355adb86ec84d057fea765e4c49cce592aaf3d5117ce5609a95a7fc3dac/types_requests-2.32.4.20250809.tar.gz", hash = "sha256:d8060de1c8ee599311f56ff58010fb4902f462a1470802cf9f6ed27bc46c4df3", size = 23027, upload-time = "2025-08-09T03:17:10.664Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/20/9a227ea57c1285986c4cf78400d0a91615d25b24e257fd9e2969606bdfae/types_requests-2.32.4.20250913-py3-none-any.whl", hash = "sha256:78c9c1fffebbe0fa487a418e0fa5252017e9c60d1a2da394077f1780f655d7e1", size = 20658, upload-time = "2025-09-13T02:40:01.115Z" }, + { url = "https://files.pythonhosted.org/packages/2b/6f/ec0012be842b1d888d46884ac5558fd62aeae1f0ec4f7a581433d890d4b5/types_requests-2.32.4.20250809-py3-none-any.whl", hash = "sha256:f73d1832fb519ece02c85b1f09d5f0dd3108938e7d47e7f94bbfa18a6782b163", size = 20644, upload-time = "2025-08-09T03:17:09.716Z" }, ] [[package]] @@ -4985,7 +4976,7 @@ name = "yapf" version = "0.43.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "platformdirs" }, + { name = "platformdirs", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, ] 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 = [ @@ -5042,42 +5033,42 @@ wheels = [ [[package]] name = "zstandard" -version = "0.25.0" +version = "0.24.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fd/aa/3e0508d5a5dd96529cdc5a97011299056e14c6505b678fd58938792794b1/zstandard-0.25.0.tar.gz", hash = "sha256:7713e1179d162cf5c7906da876ec2ccb9c3a9dcbdffef0cc7f70c3667a205f0b", size = 711513, upload-time = "2025-09-14T22:15:54.002Z" } +sdist = { url = "https://files.pythonhosted.org/packages/09/1b/c20b2ef1d987627765dcd5bf1dadb8ef6564f00a87972635099bb76b7a05/zstandard-0.24.0.tar.gz", hash = "sha256:fe3198b81c00032326342d973e526803f183f97aa9e9a98e3f897ebafe21178f", size = 905681, upload-time = "2025-08-17T18:36:36.352Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/83/c3ca27c363d104980f1c9cee1101cc8ba724ac8c28a033ede6aab89585b1/zstandard-0.25.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:933b65d7680ea337180733cf9e87293cc5500cc0eb3fc8769f4d3c88d724ec5c", size = 795254, upload-time = "2025-09-14T22:16:26.137Z" }, - { url = "https://files.pythonhosted.org/packages/ac/4d/e66465c5411a7cf4866aeadc7d108081d8ceba9bc7abe6b14aa21c671ec3/zstandard-0.25.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3f79487c687b1fc69f19e487cd949bf3aae653d181dfb5fde3bf6d18894706f", size = 640559, upload-time = "2025-09-14T22:16:27.973Z" }, - { url = "https://files.pythonhosted.org/packages/12/56/354fe655905f290d3b147b33fe946b0f27e791e4b50a5f004c802cb3eb7b/zstandard-0.25.0-cp311-cp311-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:0bbc9a0c65ce0eea3c34a691e3c4b6889f5f3909ba4822ab385fab9057099431", size = 5348020, upload-time = "2025-09-14T22:16:29.523Z" }, - { url = "https://files.pythonhosted.org/packages/3b/13/2b7ed68bd85e69a2069bcc72141d378f22cae5a0f3b353a2c8f50ef30c1b/zstandard-0.25.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:01582723b3ccd6939ab7b3a78622c573799d5d8737b534b86d0e06ac18dbde4a", size = 5058126, upload-time = "2025-09-14T22:16:31.811Z" }, - { url = "https://files.pythonhosted.org/packages/c9/dd/fdaf0674f4b10d92cb120ccff58bbb6626bf8368f00ebfd2a41ba4a0dc99/zstandard-0.25.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5f1ad7bf88535edcf30038f6919abe087f606f62c00a87d7e33e7fc57cb69fcc", size = 5405390, upload-time = "2025-09-14T22:16:33.486Z" }, - { url = "https://files.pythonhosted.org/packages/0f/67/354d1555575bc2490435f90d67ca4dd65238ff2f119f30f72d5cde09c2ad/zstandard-0.25.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:06acb75eebeedb77b69048031282737717a63e71e4ae3f77cc0c3b9508320df6", size = 5452914, upload-time = "2025-09-14T22:16:35.277Z" }, - { url = "https://files.pythonhosted.org/packages/bb/1f/e9cfd801a3f9190bf3e759c422bbfd2247db9d7f3d54a56ecde70137791a/zstandard-0.25.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9300d02ea7c6506f00e627e287e0492a5eb0371ec1670ae852fefffa6164b072", size = 5559635, upload-time = "2025-09-14T22:16:37.141Z" }, - { url = "https://files.pythonhosted.org/packages/21/88/5ba550f797ca953a52d708c8e4f380959e7e3280af029e38fbf47b55916e/zstandard-0.25.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bfd06b1c5584b657a2892a6014c2f4c20e0db0208c159148fa78c65f7e0b0277", size = 5048277, upload-time = "2025-09-14T22:16:38.807Z" }, - { url = "https://files.pythonhosted.org/packages/46/c0/ca3e533b4fa03112facbe7fbe7779cb1ebec215688e5df576fe5429172e0/zstandard-0.25.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f373da2c1757bb7f1acaf09369cdc1d51d84131e50d5fa9863982fd626466313", size = 5574377, upload-time = "2025-09-14T22:16:40.523Z" }, - { url = "https://files.pythonhosted.org/packages/12/9b/3fb626390113f272abd0799fd677ea33d5fc3ec185e62e6be534493c4b60/zstandard-0.25.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6c0e5a65158a7946e7a7affa6418878ef97ab66636f13353b8502d7ea03c8097", size = 4961493, upload-time = "2025-09-14T22:16:43.3Z" }, - { url = "https://files.pythonhosted.org/packages/cb/d3/23094a6b6a4b1343b27ae68249daa17ae0651fcfec9ed4de09d14b940285/zstandard-0.25.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c8e167d5adf59476fa3e37bee730890e389410c354771a62e3c076c86f9f7778", size = 5269018, upload-time = "2025-09-14T22:16:45.292Z" }, - { url = "https://files.pythonhosted.org/packages/8c/a7/bb5a0c1c0f3f4b5e9d5b55198e39de91e04ba7c205cc46fcb0f95f0383c1/zstandard-0.25.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:98750a309eb2f020da61e727de7d7ba3c57c97cf6213f6f6277bb7fb42a8e065", size = 5443672, upload-time = "2025-09-14T22:16:47.076Z" }, - { url = "https://files.pythonhosted.org/packages/27/22/503347aa08d073993f25109c36c8d9f029c7d5949198050962cb568dfa5e/zstandard-0.25.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:22a086cff1b6ceca18a8dd6096ec631e430e93a8e70a9ca5efa7561a00f826fa", size = 5822753, upload-time = "2025-09-14T22:16:49.316Z" }, - { url = "https://files.pythonhosted.org/packages/e2/be/94267dc6ee64f0f8ba2b2ae7c7a2df934a816baaa7291db9e1aa77394c3c/zstandard-0.25.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:72d35d7aa0bba323965da807a462b0966c91608ef3a48ba761678cb20ce5d8b7", size = 5366047, upload-time = "2025-09-14T22:16:51.328Z" }, - { url = "https://files.pythonhosted.org/packages/7b/a3/732893eab0a3a7aecff8b99052fecf9f605cf0fb5fb6d0290e36beee47a4/zstandard-0.25.0-cp311-cp311-win32.whl", hash = "sha256:f5aeea11ded7320a84dcdd62a3d95b5186834224a9e55b92ccae35d21a8b63d4", size = 436484, upload-time = "2025-09-14T22:16:55.005Z" }, - { url = "https://files.pythonhosted.org/packages/43/a3/c6155f5c1cce691cb80dfd38627046e50af3ee9ddc5d0b45b9b063bfb8c9/zstandard-0.25.0-cp311-cp311-win_amd64.whl", hash = "sha256:daab68faadb847063d0c56f361a289c4f268706b598afbf9ad113cbe5c38b6b2", size = 506183, upload-time = "2025-09-14T22:16:52.753Z" }, - { url = "https://files.pythonhosted.org/packages/8c/3e/8945ab86a0820cc0e0cdbf38086a92868a9172020fdab8a03ac19662b0e5/zstandard-0.25.0-cp311-cp311-win_arm64.whl", hash = "sha256:22a06c5df3751bb7dc67406f5374734ccee8ed37fc5981bf1ad7041831fa1137", size = 462533, upload-time = "2025-09-14T22:16:53.878Z" }, - { url = "https://files.pythonhosted.org/packages/82/fc/f26eb6ef91ae723a03e16eddb198abcfce2bc5a42e224d44cc8b6765e57e/zstandard-0.25.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7b3c3a3ab9daa3eed242d6ecceead93aebbb8f5f84318d82cee643e019c4b73b", size = 795738, upload-time = "2025-09-14T22:16:56.237Z" }, - { url = "https://files.pythonhosted.org/packages/aa/1c/d920d64b22f8dd028a8b90e2d756e431a5d86194caa78e3819c7bf53b4b3/zstandard-0.25.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:913cbd31a400febff93b564a23e17c3ed2d56c064006f54efec210d586171c00", size = 640436, upload-time = "2025-09-14T22:16:57.774Z" }, - { url = "https://files.pythonhosted.org/packages/53/6c/288c3f0bd9fcfe9ca41e2c2fbfd17b2097f6af57b62a81161941f09afa76/zstandard-0.25.0-cp312-cp312-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:011d388c76b11a0c165374ce660ce2c8efa8e5d87f34996aa80f9c0816698b64", size = 5343019, upload-time = "2025-09-14T22:16:59.302Z" }, - { url = "https://files.pythonhosted.org/packages/1e/15/efef5a2f204a64bdb5571e6161d49f7ef0fffdbca953a615efbec045f60f/zstandard-0.25.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6dffecc361d079bb48d7caef5d673c88c8988d3d33fb74ab95b7ee6da42652ea", size = 5063012, upload-time = "2025-09-14T22:17:01.156Z" }, - { url = "https://files.pythonhosted.org/packages/b7/37/a6ce629ffdb43959e92e87ebdaeebb5ac81c944b6a75c9c47e300f85abdf/zstandard-0.25.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7149623bba7fdf7e7f24312953bcf73cae103db8cae49f8154dd1eadc8a29ecb", size = 5394148, upload-time = "2025-09-14T22:17:03.091Z" }, - { url = "https://files.pythonhosted.org/packages/e3/79/2bf870b3abeb5c070fe2d670a5a8d1057a8270f125ef7676d29ea900f496/zstandard-0.25.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:6a573a35693e03cf1d67799fd01b50ff578515a8aeadd4595d2a7fa9f3ec002a", size = 5451652, upload-time = "2025-09-14T22:17:04.979Z" }, - { url = "https://files.pythonhosted.org/packages/53/60/7be26e610767316c028a2cbedb9a3beabdbe33e2182c373f71a1c0b88f36/zstandard-0.25.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5a56ba0db2d244117ed744dfa8f6f5b366e14148e00de44723413b2f3938a902", size = 5546993, upload-time = "2025-09-14T22:17:06.781Z" }, - { url = "https://files.pythonhosted.org/packages/85/c7/3483ad9ff0662623f3648479b0380d2de5510abf00990468c286c6b04017/zstandard-0.25.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:10ef2a79ab8e2974e2075fb984e5b9806c64134810fac21576f0668e7ea19f8f", size = 5046806, upload-time = "2025-09-14T22:17:08.415Z" }, - { url = "https://files.pythonhosted.org/packages/08/b3/206883dd25b8d1591a1caa44b54c2aad84badccf2f1de9e2d60a446f9a25/zstandard-0.25.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aaf21ba8fb76d102b696781bddaa0954b782536446083ae3fdaa6f16b25a1c4b", size = 5576659, upload-time = "2025-09-14T22:17:10.164Z" }, - { url = "https://files.pythonhosted.org/packages/9d/31/76c0779101453e6c117b0ff22565865c54f48f8bd807df2b00c2c404b8e0/zstandard-0.25.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1869da9571d5e94a85a5e8d57e4e8807b175c9e4a6294e3b66fa4efb074d90f6", size = 4953933, upload-time = "2025-09-14T22:17:11.857Z" }, - { url = "https://files.pythonhosted.org/packages/18/e1/97680c664a1bf9a247a280a053d98e251424af51f1b196c6d52f117c9720/zstandard-0.25.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:809c5bcb2c67cd0ed81e9229d227d4ca28f82d0f778fc5fea624a9def3963f91", size = 5268008, upload-time = "2025-09-14T22:17:13.627Z" }, - { url = "https://files.pythonhosted.org/packages/1e/73/316e4010de585ac798e154e88fd81bb16afc5c5cb1a72eeb16dd37e8024a/zstandard-0.25.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f27662e4f7dbf9f9c12391cb37b4c4c3cb90ffbd3b1fb9284dadbbb8935fa708", size = 5433517, upload-time = "2025-09-14T22:17:16.103Z" }, - { url = "https://files.pythonhosted.org/packages/5b/60/dd0f8cfa8129c5a0ce3ea6b7f70be5b33d2618013a161e1ff26c2b39787c/zstandard-0.25.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:99c0c846e6e61718715a3c9437ccc625de26593fea60189567f0118dc9db7512", size = 5814292, upload-time = "2025-09-14T22:17:17.827Z" }, - { url = "https://files.pythonhosted.org/packages/fc/5f/75aafd4b9d11b5407b641b8e41a57864097663699f23e9ad4dbb91dc6bfe/zstandard-0.25.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:474d2596a2dbc241a556e965fb76002c1ce655445e4e3bf38e5477d413165ffa", size = 5360237, upload-time = "2025-09-14T22:17:19.954Z" }, - { url = "https://files.pythonhosted.org/packages/ff/8d/0309daffea4fcac7981021dbf21cdb2e3427a9e76bafbcdbdf5392ff99a4/zstandard-0.25.0-cp312-cp312-win32.whl", hash = "sha256:23ebc8f17a03133b4426bcc04aabd68f8236eb78c3760f12783385171b0fd8bd", size = 436922, upload-time = "2025-09-14T22:17:24.398Z" }, - { url = "https://files.pythonhosted.org/packages/79/3b/fa54d9015f945330510cb5d0b0501e8253c127cca7ebe8ba46a965df18c5/zstandard-0.25.0-cp312-cp312-win_amd64.whl", hash = "sha256:ffef5a74088f1e09947aecf91011136665152e0b4b359c42be3373897fb39b01", size = 506276, upload-time = "2025-09-14T22:17:21.429Z" }, - { url = "https://files.pythonhosted.org/packages/ea/6b/8b51697e5319b1f9ac71087b0af9a40d8a6288ff8025c36486e0c12abcc4/zstandard-0.25.0-cp312-cp312-win_arm64.whl", hash = "sha256:181eb40e0b6a29b3cd2849f825e0fa34397f649170673d385f3598ae17cca2e9", size = 462679, upload-time = "2025-09-14T22:17:23.147Z" }, + { url = "https://files.pythonhosted.org/packages/01/1f/5c72806f76043c0ef9191a2b65281dacdf3b65b0828eb13bb2c987c4fb90/zstandard-0.24.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:addfc23e3bd5f4b6787b9ca95b2d09a1a67ad5a3c318daaa783ff90b2d3a366e", size = 795228, upload-time = "2025-08-17T18:21:46.978Z" }, + { url = "https://files.pythonhosted.org/packages/0b/ba/3059bd5cd834666a789251d14417621b5c61233bd46e7d9023ea8bc1043a/zstandard-0.24.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6b005bcee4be9c3984b355336283afe77b2defa76ed6b89332eced7b6fa68b68", size = 640520, upload-time = "2025-08-17T18:21:48.162Z" }, + { url = "https://files.pythonhosted.org/packages/57/07/f0e632bf783f915c1fdd0bf68614c4764cae9dd46ba32cbae4dd659592c3/zstandard-0.24.0-cp311-cp311-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:3f96a9130171e01dbb6c3d4d9925d604e2131a97f540e223b88ba45daf56d6fb", size = 5347682, upload-time = "2025-08-17T18:21:50.266Z" }, + { url = "https://files.pythonhosted.org/packages/a6/4c/63523169fe84773a7462cd090b0989cb7c7a7f2a8b0a5fbf00009ba7d74d/zstandard-0.24.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd0d3d16e63873253bad22b413ec679cf6586e51b5772eb10733899832efec42", size = 5057650, upload-time = "2025-08-17T18:21:52.634Z" }, + { url = "https://files.pythonhosted.org/packages/c6/16/49013f7ef80293f5cebf4c4229535a9f4c9416bbfd238560edc579815dbe/zstandard-0.24.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:b7a8c30d9bf4bd5e4dcfe26900bef0fcd9749acde45cdf0b3c89e2052fda9a13", size = 5404893, upload-time = "2025-08-17T18:21:54.54Z" }, + { url = "https://files.pythonhosted.org/packages/4d/38/78e8bcb5fc32a63b055f2b99e0be49b506f2351d0180173674f516cf8a7a/zstandard-0.24.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:52cd7d9fa0a115c9446abb79b06a47171b7d916c35c10e0c3aa6f01d57561382", size = 5452389, upload-time = "2025-08-17T18:21:56.822Z" }, + { url = "https://files.pythonhosted.org/packages/55/8a/81671f05619edbacd49bd84ce6899a09fc8299be20c09ae92f6618ccb92d/zstandard-0.24.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a0f6fc2ea6e07e20df48752e7700e02e1892c61f9a6bfbacaf2c5b24d5ad504b", size = 5558888, upload-time = "2025-08-17T18:21:58.68Z" }, + { url = "https://files.pythonhosted.org/packages/49/cc/e83feb2d7d22d1f88434defbaeb6e5e91f42a4f607b5d4d2d58912b69d67/zstandard-0.24.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e46eb6702691b24ddb3e31e88b4a499e31506991db3d3724a85bd1c5fc3cfe4e", size = 5048038, upload-time = "2025-08-17T18:22:00.642Z" }, + { url = "https://files.pythonhosted.org/packages/08/c3/7a5c57ff49ef8943877f85c23368c104c2aea510abb339a2dc31ad0a27c3/zstandard-0.24.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5e3b9310fd7f0d12edc75532cd9a56da6293840c84da90070d692e0bb15f186", size = 5573833, upload-time = "2025-08-17T18:22:02.402Z" }, + { url = "https://files.pythonhosted.org/packages/f9/00/64519983cd92535ba4bdd4ac26ac52db00040a52d6c4efb8d1764abcc343/zstandard-0.24.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:76cdfe7f920738ea871f035568f82bad3328cbc8d98f1f6988264096b5264efd", size = 4961072, upload-time = "2025-08-17T18:22:04.384Z" }, + { url = "https://files.pythonhosted.org/packages/72/ab/3a08a43067387d22994fc87c3113636aa34ccd2914a4d2d188ce365c5d85/zstandard-0.24.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:3f2fe35ec84908dddf0fbf66b35d7c2878dbe349552dd52e005c755d3493d61c", size = 5268462, upload-time = "2025-08-17T18:22:06.095Z" }, + { url = "https://files.pythonhosted.org/packages/49/cf/2abb3a1ad85aebe18c53e7eca73223f1546ddfa3bf4d2fb83fc5a064c5ca/zstandard-0.24.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:aa705beb74ab116563f4ce784fa94771f230c05d09ab5de9c397793e725bb1db", size = 5443319, upload-time = "2025-08-17T18:22:08.572Z" }, + { url = "https://files.pythonhosted.org/packages/40/42/0dd59fc2f68f1664cda11c3b26abdf987f4e57cb6b6b0f329520cd074552/zstandard-0.24.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:aadf32c389bb7f02b8ec5c243c38302b92c006da565e120dfcb7bf0378f4f848", size = 5822355, upload-time = "2025-08-17T18:22:10.537Z" }, + { url = "https://files.pythonhosted.org/packages/99/c0/ea4e640fd4f7d58d6f87a1e7aca11fb886ac24db277fbbb879336c912f63/zstandard-0.24.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e40cd0fc734aa1d4bd0e7ad102fd2a1aefa50ce9ef570005ffc2273c5442ddc3", size = 5365257, upload-time = "2025-08-17T18:22:13.159Z" }, + { url = "https://files.pythonhosted.org/packages/27/a9/92da42a5c4e7e4003271f2e1f0efd1f37cfd565d763ad3604e9597980a1c/zstandard-0.24.0-cp311-cp311-win32.whl", hash = "sha256:cda61c46343809ecda43dc620d1333dd7433a25d0a252f2dcc7667f6331c7b61", size = 435559, upload-time = "2025-08-17T18:22:17.29Z" }, + { url = "https://files.pythonhosted.org/packages/e2/8e/2c8e5c681ae4937c007938f954a060fa7c74f36273b289cabdb5ef0e9a7e/zstandard-0.24.0-cp311-cp311-win_amd64.whl", hash = "sha256:3b95fc06489aa9388400d1aab01a83652bc040c9c087bd732eb214909d7fb0dd", size = 505070, upload-time = "2025-08-17T18:22:14.808Z" }, + { url = "https://files.pythonhosted.org/packages/52/10/a2f27a66bec75e236b575c9f7b0d7d37004a03aa2dcde8e2decbe9ed7b4d/zstandard-0.24.0-cp311-cp311-win_arm64.whl", hash = "sha256:ad9fd176ff6800a0cf52bcf59c71e5de4fa25bf3ba62b58800e0f84885344d34", size = 461507, upload-time = "2025-08-17T18:22:15.964Z" }, + { url = "https://files.pythonhosted.org/packages/26/e9/0bd281d9154bba7fc421a291e263911e1d69d6951aa80955b992a48289f6/zstandard-0.24.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a2bda8f2790add22773ee7a4e43c90ea05598bffc94c21c40ae0a9000b0133c3", size = 795710, upload-time = "2025-08-17T18:22:19.189Z" }, + { url = "https://files.pythonhosted.org/packages/36/26/b250a2eef515caf492e2d86732e75240cdac9d92b04383722b9753590c36/zstandard-0.24.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cc76de75300f65b8eb574d855c12518dc25a075dadb41dd18f6322bda3fe15d5", size = 640336, upload-time = "2025-08-17T18:22:20.466Z" }, + { url = "https://files.pythonhosted.org/packages/79/bf/3ba6b522306d9bf097aac8547556b98a4f753dc807a170becaf30dcd6f01/zstandard-0.24.0-cp312-cp312-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:d2b3b4bda1a025b10fe0269369475f420177f2cb06e0f9d32c95b4873c9f80b8", size = 5342533, upload-time = "2025-08-17T18:22:22.326Z" }, + { url = "https://files.pythonhosted.org/packages/ea/ec/22bc75bf054e25accdf8e928bc68ab36b4466809729c554ff3a1c1c8bce6/zstandard-0.24.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9b84c6c210684286e504022d11ec294d2b7922d66c823e87575d8b23eba7c81f", size = 5062837, upload-time = "2025-08-17T18:22:24.416Z" }, + { url = "https://files.pythonhosted.org/packages/48/cc/33edfc9d286e517fb5b51d9c3210e5bcfce578d02a675f994308ca587ae1/zstandard-0.24.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c59740682a686bf835a1a4d8d0ed1eefe31ac07f1c5a7ed5f2e72cf577692b00", size = 5393855, upload-time = "2025-08-17T18:22:26.786Z" }, + { url = "https://files.pythonhosted.org/packages/73/36/59254e9b29da6215fb3a717812bf87192d89f190f23817d88cb8868c47ac/zstandard-0.24.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:6324fde5cf5120fbf6541d5ff3c86011ec056e8d0f915d8e7822926a5377193a", size = 5451058, upload-time = "2025-08-17T18:22:28.885Z" }, + { url = "https://files.pythonhosted.org/packages/9a/c7/31674cb2168b741bbbe71ce37dd397c9c671e73349d88ad3bca9e9fae25b/zstandard-0.24.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:51a86bd963de3f36688553926a84e550d45d7f9745bd1947d79472eca27fcc75", size = 5546619, upload-time = "2025-08-17T18:22:31.115Z" }, + { url = "https://files.pythonhosted.org/packages/e6/01/1a9f22239f08c00c156f2266db857545ece66a6fc0303d45c298564bc20b/zstandard-0.24.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d82ac87017b734f2fb70ff93818c66f0ad2c3810f61040f077ed38d924e19980", size = 5046676, upload-time = "2025-08-17T18:22:33.077Z" }, + { url = "https://files.pythonhosted.org/packages/a7/91/6c0cf8fa143a4988a0361380ac2ef0d7cb98a374704b389fbc38b5891712/zstandard-0.24.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:92ea7855d5bcfb386c34557516c73753435fb2d4a014e2c9343b5f5ba148b5d8", size = 5576381, upload-time = "2025-08-17T18:22:35.391Z" }, + { url = "https://files.pythonhosted.org/packages/e2/77/1526080e22e78871e786ccf3c84bf5cec9ed25110a9585507d3c551da3d6/zstandard-0.24.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3adb4b5414febf074800d264ddf69ecade8c658837a83a19e8ab820e924c9933", size = 4953403, upload-time = "2025-08-17T18:22:37.266Z" }, + { url = "https://files.pythonhosted.org/packages/6e/d0/a3a833930bff01eab697eb8abeafb0ab068438771fa066558d96d7dafbf9/zstandard-0.24.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:6374feaf347e6b83ec13cc5dcfa70076f06d8f7ecd46cc71d58fac798ff08b76", size = 5267396, upload-time = "2025-08-17T18:22:39.757Z" }, + { url = "https://files.pythonhosted.org/packages/f3/5e/90a0db9a61cd4769c06374297ecfcbbf66654f74cec89392519deba64d76/zstandard-0.24.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:13fc548e214df08d896ee5f29e1f91ee35db14f733fef8eabea8dca6e451d1e2", size = 5433269, upload-time = "2025-08-17T18:22:42.131Z" }, + { url = "https://files.pythonhosted.org/packages/ce/58/fc6a71060dd67c26a9c5566e0d7c99248cbe5abfda6b3b65b8f1a28d59f7/zstandard-0.24.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:0a416814608610abf5488889c74e43ffa0343ca6cf43957c6b6ec526212422da", size = 5814203, upload-time = "2025-08-17T18:22:44.017Z" }, + { url = "https://files.pythonhosted.org/packages/5c/6a/89573d4393e3ecbfa425d9a4e391027f58d7810dec5cdb13a26e4cdeef5c/zstandard-0.24.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0d66da2649bb0af4471699aeb7a83d6f59ae30236fb9f6b5d20fb618ef6c6777", size = 5359622, upload-time = "2025-08-17T18:22:45.802Z" }, + { url = "https://files.pythonhosted.org/packages/60/ff/2cbab815d6f02a53a9d8d8703bc727d8408a2e508143ca9af6c3cca2054b/zstandard-0.24.0-cp312-cp312-win32.whl", hash = "sha256:ff19efaa33e7f136fe95f9bbcc90ab7fb60648453b03f95d1de3ab6997de0f32", size = 435968, upload-time = "2025-08-17T18:22:49.493Z" }, + { url = "https://files.pythonhosted.org/packages/ce/a3/8f96b8ddb7ad12344218fbd0fd2805702dafd126ae9f8a1fb91eef7b33da/zstandard-0.24.0-cp312-cp312-win_amd64.whl", hash = "sha256:bc05f8a875eb651d1cc62e12a4a0e6afa5cd0cc231381adb830d2e9c196ea895", size = 505195, upload-time = "2025-08-17T18:22:47.193Z" }, + { url = "https://files.pythonhosted.org/packages/a3/4a/bfca20679da63bfc236634ef2e4b1b4254203098b0170e3511fee781351f/zstandard-0.24.0-cp312-cp312-win_arm64.whl", hash = "sha256:b04c94718f7a8ed7cdd01b162b6caa1954b3c9d486f00ecbbd300f149d2b2606", size = 461605, upload-time = "2025-08-17T18:22:48.317Z" }, ] From b6e0d4807a3dad8715d0bd48fe1676e6abfbfcb8 Mon Sep 17 00:00:00 2001 From: commaci-public <60409688+commaci-public@users.noreply.github.com> Date: Sat, 20 Sep 2025 20:10:51 -0700 Subject: [PATCH 035/910] [bot] Update Python packages (#36184) * Update Python packages * not available anymore * also this * also this * maybe? * version * try * Revert "version" This reverts commit 9ac4401b9ca59677b82736faff8baf66861df5f2. * revert * cffi * issue * comment --------- Co-authored-by: Vehicle Researcher Co-authored-by: Maxime Desroches --- panda | 2 +- pyproject.toml | 2 +- selfdrive/modeld/SConscript | 4 +- selfdrive/modeld/dmonitoringmodeld.py | 2 +- selfdrive/modeld/modeld.py | 2 +- tinygrad_repo | 2 +- uv.lock | 771 +++++++++++++------------- 7 files changed, 395 insertions(+), 390 deletions(-) diff --git a/panda b/panda index a2064b86f..1289337ce 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit a2064b86f3c9908883033a953503f150cedacbc7 +Subproject commit 1289337ceb6205ad985a5469baa950b319329327 diff --git a/pyproject.toml b/pyproject.toml index 489876f78..9c8a6148a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -119,7 +119,7 @@ dev = [ "tabulate", "types-requests", "types-tabulate", - "raylib", + "raylib < 5.5.0.3", # TODO: unpin when they fix https://github.com/electronstudio/raylib-python-cffi/issues/186 ] tools = [ diff --git a/selfdrive/modeld/SConscript b/selfdrive/modeld/SConscript index 6802b45e6..1a6df4bb9 100644 --- a/selfdrive/modeld/SConscript +++ b/selfdrive/modeld/SConscript @@ -51,8 +51,8 @@ def tg_compile(flags, model_name): for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']: flags = { 'larch64': 'DEV=QCOM', - 'Darwin': 'DEV=CPU IMAGE=0', - }.get(arch, 'DEV=LLVM IMAGE=0') + 'Darwin': f'DEV=CPU HOME={os.path.expanduser("~")} IMAGE=0', # tinygrad calls brew which needs a $HOME in the env + }.get(arch, 'DEV=CPU CPU_LLVM=1 IMAGE=0') tg_compile(flags, model_name) # Compile BIG model if USB GPU is available diff --git a/selfdrive/modeld/dmonitoringmodeld.py b/selfdrive/modeld/dmonitoringmodeld.py index 215056e6e..5aeb035bd 100755 --- a/selfdrive/modeld/dmonitoringmodeld.py +++ b/selfdrive/modeld/dmonitoringmodeld.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 import os from openpilot.system.hardware import TICI -os.environ['DEV'] = 'QCOM' if TICI else 'LLVM' +os.environ['DEV'] = 'QCOM' if TICI else 'CPU' from tinygrad.tensor import Tensor from tinygrad.dtype import dtypes import math diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index e08fc30c2..006eeef6f 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 import os from openpilot.system.hardware import TICI -os.environ['DEV'] = 'QCOM' if TICI else 'LLVM' +os.environ['DEV'] = 'QCOM' if TICI else 'CPU' USBGPU = "USBGPU" in os.environ if USBGPU: os.environ['DEV'] = 'AMD' diff --git a/tinygrad_repo b/tinygrad_repo index 965ea59b1..73c8dae60 160000 --- a/tinygrad_repo +++ b/tinygrad_repo @@ -1 +1 @@ -Subproject commit 965ea59b16679793b8f48368ac24c4a0ef587e71 +Subproject commit 73c8dae60d0eaa3a27a252f7967d2dab8378109e diff --git a/uv.lock b/uv.lock index 25d2b626c..c24d9dbbb 100644 --- a/uv.lock +++ b/uv.lock @@ -152,21 +152,21 @@ wheels = [ [[package]] name = "azure-core" -version = "1.35.0" +version = "1.35.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "requests" }, { name = "six" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ce/89/f53968635b1b2e53e4aad2dd641488929fef4ca9dfb0b97927fa7697ddf3/azure_core-1.35.0.tar.gz", hash = "sha256:c0be528489485e9ede59b6971eb63c1eaacf83ef53001bfe3904e475e972be5c", size = 339689, upload-time = "2025-07-03T00:55:23.496Z" } +sdist = { url = "https://files.pythonhosted.org/packages/15/6b/2653adc0f33adba8f11b1903701e6b1c10d34ce5d8e25dfa13a422f832b0/azure_core-1.35.1.tar.gz", hash = "sha256:435d05d6df0fff2f73fb3c15493bb4721ede14203f1ff1382aa6b6b2bdd7e562", size = 345290, upload-time = "2025-09-11T22:58:04.481Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/78/bf94897361fdd650850f0f2e405b2293e2f12808239046232bdedf554301/azure_core-1.35.0-py3-none-any.whl", hash = "sha256:8db78c72868a58f3de8991eb4d22c4d368fae226dac1002998d6c50437e7dad1", size = 210708, upload-time = "2025-07-03T00:55:25.238Z" }, + { url = "https://files.pythonhosted.org/packages/27/52/805980aa1ba18282077c484dba634ef0ede1e84eec8be9c92b2e162d0ed6/azure_core-1.35.1-py3-none-any.whl", hash = "sha256:12da0c9e08e48e198f9158b56ddbe33b421477e1dc98c2e1c8f9e254d92c468b", size = 211800, upload-time = "2025-09-11T22:58:06.281Z" }, ] [[package]] name = "azure-identity" -version = "1.24.0" +version = "1.25.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "azure-core" }, @@ -175,9 +175,9 @@ dependencies = [ { name = "msal-extensions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b5/44/f3ee20bacb220b6b4a2b0a6cf7e742eecb383a5ccf604dd79ec27c286b7e/azure_identity-1.24.0.tar.gz", hash = "sha256:6c3a40b2a70af831e920b89e6421e8dcd4af78a0cb38b9642d86c67643d4930c", size = 271630, upload-time = "2025-08-07T22:27:36.258Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/9e/4c9682a286c3c89e437579bd9f64f311020e5125c1321fd3a653166b5716/azure_identity-1.25.0.tar.gz", hash = "sha256:4177df34d684cddc026e6cf684e1abb57767aa9d84e7f2129b080ec45eee7733", size = 278507, upload-time = "2025-09-12T01:30:04.418Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a9/74/17428cb429e8d52f6d0d69ed685f4760a545cb0156594963a9337b53b6c9/azure_identity-1.24.0-py3-none-any.whl", hash = "sha256:9e04997cde0ab02ed66422c74748548e620b7b29361c72ce622acab0267ff7c4", size = 187890, upload-time = "2025-08-07T22:27:38.033Z" }, + { url = "https://files.pythonhosted.org/packages/75/54/81683b6756676a22e037b209695b08008258e603f7e47c56834029c5922a/azure_identity-1.25.0-py3-none-any.whl", hash = "sha256:becaec086bbdf8d1a6aa4fb080c2772a0f824a97d50c29637ec8cc4933f1e82d", size = 190861, upload-time = "2025-09-12T01:30:06.474Z" }, ] [[package]] @@ -197,25 +197,25 @@ wheels = [ [[package]] name = "casadi" -version = "3.7.1" +version = "3.7.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b1/21/1b4ea75dbddfcdbc7cd70d83147dde72899667097c0c05b288fd7015ef10/casadi-3.7.1.tar.gz", hash = "sha256:12577155cd3cd79ba162381bfed6add1541bc770ba3f1f1334e4eb159d9b39ba", size = 6065586, upload-time = "2025-07-23T21:47:56.483Z" } +sdist = { url = "https://files.pythonhosted.org/packages/92/62/1e98662024915ecb09c6894c26a3f497f4afa66570af3f53db4651fc45f1/casadi-3.7.2.tar.gz", hash = "sha256:b4d7bd8acdc4180306903ae1c9eddaf41be2a3ae2fa7154c57174ae64acdc60d", size = 6053600, upload-time = "2025-09-10T10:05:49.521Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/4d/cfe092b2deb65dbf913d38148e30dfe41bbe9f289f892ea7a5d3526532d2/casadi-3.7.1-cp311-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:c13fafdff0cdf73392fe2002245befe7496b5ac99b70adb64babff0099068c8a", size = 47100775, upload-time = "2025-07-23T19:56:59.317Z" }, - { url = "https://files.pythonhosted.org/packages/13/41/c4ad627e447a083fbfd2826f1f4684223f0072fdc49f58157ca6a6ce0a77/casadi-3.7.1-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:74d9c6bad3c7cb347494d52cc3e6faf3fbf3300046fab0e1ddc9439d3fd68486", size = 42282155, upload-time = "2025-07-23T19:59:11.191Z" }, - { url = "https://files.pythonhosted.org/packages/ca/e2/53733933d454657e319cc8074e004ec46660c4797cbffcaf7695a874b0da/casadi-3.7.1-cp311-none-manylinux2014_aarch64.whl", hash = "sha256:6fbc0803e6c7ffb9ba9a41ba38e7c1dadee73835edbacacce3530343f078f3f9", size = 47272638, upload-time = "2025-07-23T20:03:33.017Z" }, - { url = "https://files.pythonhosted.org/packages/e1/72/8002d4fe64325842eb6a7f7ac1e4574c02584e85e5b9d93a08a789095505/casadi-3.7.1-cp311-none-manylinux2014_i686.whl", hash = "sha256:38a16b0c7caff5297df91c7e3a65091824979367e6fc8e6eb0e5ab9f1f832af8", size = 72418203, upload-time = "2025-07-23T20:07:18.681Z" }, - { url = "https://files.pythonhosted.org/packages/96/e5/065287b78c4f6a00a010d7c3f5316df1b95f7c5bda644f2151b54a79a964/casadi-3.7.1-cp311-none-manylinux2014_x86_64.whl", hash = "sha256:2dd75cc1b5ca8009586186e4a63e0cdd83a672bf308edc75ef84f9c896cd971d", size = 75558461, upload-time = "2025-07-23T21:33:20.771Z" }, - { url = "https://files.pythonhosted.org/packages/b0/4c/084d2f3bca33d0b3d509be17c3b678f6bcc03f3409d42e88750cb1e44966/casadi-3.7.1-cp311-none-win_amd64.whl", hash = "sha256:3ab68c6dd621b5f150635bf06aa8e1697cae2fbe0137e9d47270c54d305c334c", size = 50987255, upload-time = "2025-07-23T21:34:20.349Z" }, - { url = "https://files.pythonhosted.org/packages/e1/a3/65942d3e0f7589a72e69dd9dee575963ecf51504e985f132bce8b0d24988/casadi-3.7.1-cp312-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:4a29ae3212b0ae6931d439ca3bfa2ac279826e99c2c9a443704e84021933ab76", size = 47102059, upload-time = "2025-07-23T20:36:47.174Z" }, - { url = "https://files.pythonhosted.org/packages/2e/f4/dc371199583f6b154ba0966c4132418737a2d2e019c9f6ad227ebd279684/casadi-3.7.1-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:70edf1df3fc83401eb5a2f7053ae8394f4e612e61c2ad6de088e3d76d0ec845e", size = 42282702, upload-time = "2025-07-23T20:39:41.499Z" }, - { url = "https://files.pythonhosted.org/packages/8e/ff/5f8a9378cd7b322d1dcc8b8aa9fbc454ad57152b754c94b50ffdc67254a0/casadi-3.7.1-cp312-none-manylinux2014_aarch64.whl", hash = "sha256:a1717bbd07a6eb98acddf5ea5da7e78a9b85b75dbabb55190cc6b0f2812ef39d", size = 47271636, upload-time = "2025-07-23T20:42:59.961Z" }, - { url = "https://files.pythonhosted.org/packages/e1/11/e1b6a76bf288fc86dc8919cbcd2d3a654e7ead4240d9928deb8ed4ee91da/casadi-3.7.1-cp312-none-manylinux2014_i686.whl", hash = "sha256:57ae29d16af0716ebc15d161bcf0bcd97d35631b10c0e80838290d249cac80d7", size = 72410197, upload-time = "2025-07-23T21:35:35.509Z" }, - { url = "https://files.pythonhosted.org/packages/c1/76/bb43ff625066b178132172d41db0c5c962b4af9db15c88b8a23e65376d18/casadi-3.7.1-cp312-none-manylinux2014_x86_64.whl", hash = "sha256:756f3d07d3414dc54f05ebec883ac3db6e307bfe661719d188340504b6bed420", size = 75552890, upload-time = "2025-07-23T21:37:14.638Z" }, - { url = "https://files.pythonhosted.org/packages/11/0c/8026b0b238e00a2e63c7142505cbcd0931b4db4140d55c83962f9d8e0b02/casadi-3.7.1-cp312-none-win_amd64.whl", hash = "sha256:b228fbcd1f41eb8d5405dbc1e0ecb780daf89808392706bb77fd2b240f8a437e", size = 50984453, upload-time = "2025-07-23T21:38:18.21Z" }, + { url = "https://files.pythonhosted.org/packages/29/01/d5e3058775ec8e24a01eb74d36099493b872536ef9e39f1e49624b977778/casadi-3.7.2-cp311-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:f43b0562d05a5e6e81f1885fc4ae426c382e36eebfd8d27f1baff6052178a9b0", size = 47115880, upload-time = "2025-09-10T07:52:24.399Z" }, + { url = "https://files.pythonhosted.org/packages/0e/cf/4af27e010d599a5419129d34fdde41637029a1cca2a40bef0965d6d52228/casadi-3.7.2-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:70add3334b437b60a9bc0f864d094350f1a4fcbf9e8bafec870b61aed64674df", size = 42293337, upload-time = "2025-09-10T08:03:32.556Z" }, + { url = "https://files.pythonhosted.org/packages/ac/4c/d1a50cc840103e00effcbaf8e911b6b3fb6ba2c8f4025466f524854968ed/casadi-3.7.2-cp311-none-manylinux2014_aarch64.whl", hash = "sha256:392d3367a4b33cf223013dad8122a0e549da40b1702a5375f82f85b563e5c0cf", size = 47277175, upload-time = "2025-09-10T08:04:08.811Z" }, + { url = "https://files.pythonhosted.org/packages/be/29/6e5714d124e6ddafbccc3ed774ca603081caa1175c7f0e1c52484184dfb3/casadi-3.7.2-cp311-none-manylinux2014_i686.whl", hash = "sha256:2ce09e0ced6df33048dccd582b5cfa2c9ff5193b12858b2584078afc17761905", size = 72438460, upload-time = "2025-09-10T08:05:02.769Z" }, + { url = "https://files.pythonhosted.org/packages/23/32/ac1f3999273aa4aae48516f6f4b7b267e0cc70d8527866989798cb81312f/casadi-3.7.2-cp311-none-manylinux2014_x86_64.whl", hash = "sha256:5086799a46d10ba884b72fd02c21be09dae52cbc189272354a5d424791b55f37", size = 75574474, upload-time = "2025-09-10T08:06:00.709Z" }, + { url = "https://files.pythonhosted.org/packages/68/78/7fd10709504c1757f70db3893870a891fcb9f1ec9f05e8ef2e3f3b9d7e2f/casadi-3.7.2-cp311-none-win_amd64.whl", hash = "sha256:72aa5727417d781ed216f16b5e93c6ddca5db27d83b0015a729e8ad570cdc465", size = 50994144, upload-time = "2025-09-10T08:06:42.384Z" }, + { url = "https://files.pythonhosted.org/packages/65/c8/689d085447b1966f42bdb8aa4fbebef49a09697dbee32ab02a865c17ac1b/casadi-3.7.2-cp312-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:309ea41a69c9230390d349b0dd899c6a19504d1904c0756bef463e47fb5c8f9a", size = 47116756, upload-time = "2025-09-10T07:53:00.931Z" }, + { url = "https://files.pythonhosted.org/packages/1e/c0/3c4704394a6fd4dfb2123a4fd71ba64a001f340670a3eba45be7a19ac736/casadi-3.7.2-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:6033381234db810b2247d16c6352e679a009ec4365d04008fc768866e011ed58", size = 42293718, upload-time = "2025-09-10T08:07:16.415Z" }, + { url = "https://files.pythonhosted.org/packages/f3/24/4cf05469ddf8544da5e92f359f96d716a97e7482999f085a632bc4ef344a/casadi-3.7.2-cp312-none-manylinux2014_aarch64.whl", hash = "sha256:732f2804d0766454bb75596339e4f2da6662ffb669621da0f630ed4af9e83d6a", size = 47276175, upload-time = "2025-09-10T08:08:09.29Z" }, + { url = "https://files.pythonhosted.org/packages/82/08/b5f57fea03128efd5c860673b6ac44776352e6c1af862b8177f4c503fffe/casadi-3.7.2-cp312-none-manylinux2014_i686.whl", hash = "sha256:cf17298ff0c162735bdf9bf72b765c636ae732130604017a3b52e26e35402857", size = 72430454, upload-time = "2025-09-10T08:09:10.781Z" }, + { url = "https://files.pythonhosted.org/packages/24/ab/d7233c915b12c005655437c6c4cf0ae46cbbb2b20d743cb5e4881ad3104a/casadi-3.7.2-cp312-none-manylinux2014_x86_64.whl", hash = "sha256:cde616930fa1440ad66f1850670399423edd37354eed9b12e74b3817b98d1187", size = 75568903, upload-time = "2025-09-10T08:10:07.108Z" }, + { url = "https://files.pythonhosted.org/packages/3e/b9/5b984124f539656efdf079f3d8f09d73667808ec8d0546e6bce6dc60ade6/casadi-3.7.2-cp312-none-win_amd64.whl", hash = "sha256:81d677d2b020c1307c1eb25eae15686e5de199bb066828c3eaabdfaaaf457ffd", size = 50991347, upload-time = "2025-09-10T08:10:46.629Z" }, ] [[package]] @@ -229,36 +229,38 @@ wheels = [ [[package]] name = "cffi" -version = "1.17.1" +version = "2.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pycparser" }, + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621, upload-time = "2024-09-04T20:45:21.852Z" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/f4/927e3a8899e52a27fa57a48607ff7dc91a9ebe97399b357b85a0c7892e00/cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", size = 182264, upload-time = "2024-09-04T20:43:51.124Z" }, - { url = "https://files.pythonhosted.org/packages/6c/f5/6c3a8efe5f503175aaddcbea6ad0d2c96dad6f5abb205750d1b3df44ef29/cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", size = 178651, upload-time = "2024-09-04T20:43:52.872Z" }, - { url = "https://files.pythonhosted.org/packages/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", size = 445259, upload-time = "2024-09-04T20:43:56.123Z" }, - { url = "https://files.pythonhosted.org/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", size = 469200, upload-time = "2024-09-04T20:43:57.891Z" }, - { url = "https://files.pythonhosted.org/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", size = 477235, upload-time = "2024-09-04T20:44:00.18Z" }, - { url = "https://files.pythonhosted.org/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", size = 459721, upload-time = "2024-09-04T20:44:01.585Z" }, - { url = "https://files.pythonhosted.org/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", size = 467242, upload-time = "2024-09-04T20:44:03.467Z" }, - { url = "https://files.pythonhosted.org/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", size = 477999, upload-time = "2024-09-04T20:44:05.023Z" }, - { url = "https://files.pythonhosted.org/packages/44/74/f2a2460684a1a2d00ca799ad880d54652841a780c4c97b87754f660c7603/cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", size = 454242, upload-time = "2024-09-04T20:44:06.444Z" }, - { url = "https://files.pythonhosted.org/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", size = 478604, upload-time = "2024-09-04T20:44:08.206Z" }, - { url = "https://files.pythonhosted.org/packages/34/33/e1b8a1ba29025adbdcda5fb3a36f94c03d771c1b7b12f726ff7fef2ebe36/cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", size = 171727, upload-time = "2024-09-04T20:44:09.481Z" }, - { url = "https://files.pythonhosted.org/packages/3d/97/50228be003bb2802627d28ec0627837ac0bf35c90cf769812056f235b2d1/cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", size = 181400, upload-time = "2024-09-04T20:44:10.873Z" }, - { url = "https://files.pythonhosted.org/packages/5a/84/e94227139ee5fb4d600a7a4927f322e1d4aea6fdc50bd3fca8493caba23f/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", size = 183178, upload-time = "2024-09-04T20:44:12.232Z" }, - { url = "https://files.pythonhosted.org/packages/da/ee/fb72c2b48656111c4ef27f0f91da355e130a923473bf5ee75c5643d00cca/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", size = 178840, upload-time = "2024-09-04T20:44:13.739Z" }, - { url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803, upload-time = "2024-09-04T20:44:15.231Z" }, - { url = "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850, upload-time = "2024-09-04T20:44:17.188Z" }, - { url = "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729, upload-time = "2024-09-04T20:44:18.688Z" }, - { url = "https://files.pythonhosted.org/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", size = 471256, upload-time = "2024-09-04T20:44:20.248Z" }, - { url = "https://files.pythonhosted.org/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424, upload-time = "2024-09-04T20:44:21.673Z" }, - { url = "https://files.pythonhosted.org/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568, upload-time = "2024-09-04T20:44:23.245Z" }, - { url = "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736, upload-time = "2024-09-04T20:44:24.757Z" }, - { url = "https://files.pythonhosted.org/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448, upload-time = "2024-09-04T20:44:26.208Z" }, - { url = "https://files.pythonhosted.org/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976, upload-time = "2024-09-04T20:44:27.578Z" }, + { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" }, + { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" }, + { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" }, + { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" }, + { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" }, + { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" }, + { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" }, + { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" }, + { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" }, + { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" }, + { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" }, + { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, + { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, + { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, + { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, + { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, + { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, + { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, + { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, + { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, + { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, ] [[package]] @@ -294,14 +296,14 @@ wheels = [ [[package]] name = "click" -version = "8.2.1" +version = "8.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/61/de6cd827efad202d7057d93e0fed9294b96952e188f7384832791c7b2254/click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4", size = 276943, upload-time = "2025-09-18T17:32:23.696Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" }, + { url = "https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc", size = 107295, upload-time = "2025-09-18T17:32:22.42Z" }, ] [[package]] @@ -415,31 +417,31 @@ wheels = [ [[package]] name = "cython" -version = "3.1.3" +version = "3.1.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/18/ab/915337fb39ab4f4539a313df38fc69938df3bf14141b90d61dfd5c2919de/cython-3.1.3.tar.gz", hash = "sha256:10ee785e42328924b78f75a74f66a813cb956b4a9bc91c44816d089d5934c089", size = 3186689, upload-time = "2025-08-13T06:19:13.619Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/f6/d762df1f436a0618455d37f4e4c4872a7cd0dcfc8dec3022ee99e4389c69/cython-3.1.4.tar.gz", hash = "sha256:9aefefe831331e2d66ab31799814eae4d0f8a2d246cbaaaa14d1be29ef777683", size = 3190778, upload-time = "2025-09-16T07:20:33.531Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b5/51/54f5d1bed7b7d003d0584996a030542497aeb05b9241782c217ea71061f5/cython-3.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4b3b2f6587b42efdece2d174a2aa4234da4524cc6673f3955c2e62b60c6d11fd", size = 3002973, upload-time = "2025-08-13T06:19:50.777Z" }, - { url = "https://files.pythonhosted.org/packages/05/07/b4043fed60070ee21b0d9ff3a877d2ecdc79231e55119ce852b79b690306/cython-3.1.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:963cf640d049fcca1cefd62d1653f859892d6dc8e4d958eb49a5babc491de6a1", size = 2865389, upload-time = "2025-08-13T06:19:52.675Z" }, - { url = "https://files.pythonhosted.org/packages/b4/e3/67d349b5310a40f281e153e826ca24d9f7ba2997c06800eda781253dccfd/cython-3.1.3-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2ab05d1bf2d5522ecff35d94ca233b77db2300413597c3ca0b6448377fa4bd7c", size = 3410316, upload-time = "2025-08-13T06:19:55.217Z" }, - { url = "https://files.pythonhosted.org/packages/49/c4/84aae921135174111091547fa726ea5f8bba718cd12b2589a70c2aab8d5c/cython-3.1.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cd517e3be052fb49443585b01f02f46080b3408e32c1108a0fdc4cc25b3c9d30", size = 3189568, upload-time = "2025-08-13T06:19:57.503Z" }, - { url = "https://files.pythonhosted.org/packages/e2/85/1bf18883f1a1f656ad83a671e54553caedb1ee2f39a3e792a14aa82557a2/cython-3.1.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a48e2180d74e3c528561d85b48f9a939a429537f9ea8aac7fb16180e7bff47e2", size = 3312649, upload-time = "2025-08-13T06:19:59.894Z" }, - { url = "https://files.pythonhosted.org/packages/68/da/cc1373decc0d14a25f1b434f47de5e27696f3092319aa5e19fcf84157415/cython-3.1.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e7c9daa90b15f59aa2a0d638ac1b36777a7e80122099952a0295c71190ce14bc", size = 3203821, upload-time = "2025-08-13T06:20:02.124Z" }, - { url = "https://files.pythonhosted.org/packages/0a/32/e10582d6f7b02ee63607f388dbbd34e329c54559c85961ddc5c655266519/cython-3.1.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:08ac646ff42781827f23b7a9b61669cdb92055f52724cd8cbe0e1defc56fce2e", size = 3426853, upload-time = "2025-08-13T06:20:04.474Z" }, - { url = "https://files.pythonhosted.org/packages/e0/da/5b0d1b5a4c7622ec812ad9374c9c6b426d69818f13f51e36f4f25ca01c86/cython-3.1.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bb0e0e7fceaffa22e4dc9600f7f75998eef5cc6ac5a8c0733b482851ba765ef2", size = 3328872, upload-time = "2025-08-13T06:20:06.834Z" }, - { url = "https://files.pythonhosted.org/packages/b0/5f/f102f6c8d27338f0baf094754c67f920828a19612053abc903e66f84506f/cython-3.1.3-cp311-cp311-win32.whl", hash = "sha256:42b1c3ebe36a52e2a8e939c0651e9ca5d30b81d03f800bbf0499deb0503ab565", size = 2480850, upload-time = "2025-08-13T06:20:08.988Z" }, - { url = "https://files.pythonhosted.org/packages/b7/60/415d0f0f7c2e227707baec11c968387d33507d2eb7f26a2950a323c705e5/cython-3.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:34a973844998281951bf54cdd0b6a9946ba03ba94580820738583a00da167d8f", size = 2712560, upload-time = "2025-08-13T06:20:11.877Z" }, - { url = "https://files.pythonhosted.org/packages/79/26/f433fdfd5182b9231819a99acc49a1f856669532306e7a38dce63ba7297e/cython-3.1.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:849ef3d15d4354e5f74cdb6d3c80d80b03209b3bf1f4ff93315890b19da18944", size = 3014237, upload-time = "2025-08-13T06:20:13.77Z" }, - { url = "https://files.pythonhosted.org/packages/e5/6c/1bebf44f5f177f8c750e608f82c08cd699b8f28cc233e799379bfcf2a2c2/cython-3.1.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:93dd0f62a3f8e93166d8584f8b243180d681ba8fed1f530b55d5f70c348c5797", size = 2844261, upload-time = "2025-08-13T06:20:15.619Z" }, - { url = "https://files.pythonhosted.org/packages/c7/74/983005ce5954f6dc8360b105a561e51a60ea619c53296afac98253d1c9d7/cython-3.1.3-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ff4a2cb84798faffb3988bd94636c3ad31a95ff44ef017f09121abffc56f84cf", size = 3388846, upload-time = "2025-08-13T06:20:17.679Z" }, - { url = "https://files.pythonhosted.org/packages/68/50/dbe7edefe9b652bc72d49da07785173e89197b9a2d64a2ac45da9795eccf/cython-3.1.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4b05319e36f34d5388deea5cc2bcfd65f9ebf76f4ea050829421a69625dbba4a", size = 3167022, upload-time = "2025-08-13T06:20:19.863Z" }, - { url = "https://files.pythonhosted.org/packages/4a/55/b50548b77203e22262002feae23a172f95282b4b8deb5ad104f08e3dc20d/cython-3.1.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7ac902a17934a6da46f80755f49413bc4c03a569ae3c834f5d66da7288ba7e6c", size = 3317782, upload-time = "2025-08-13T06:20:21.962Z" }, - { url = "https://files.pythonhosted.org/packages/f6/1b/20a97507d528dc330d67be4fbad6bc767c681d56192bce8f7117a187b2ad/cython-3.1.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7d7a555a864b1b08576f9e8a67f3789796a065837544f9f683ebf3188012fdbd", size = 3179818, upload-time = "2025-08-13T06:20:24.419Z" }, - { url = "https://files.pythonhosted.org/packages/43/a5/7b32a19c4c6bb0e2cc7ae52269b6b23a42f67f730e4abd4f8dca63660f7a/cython-3.1.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b827ce7d97ef8624adcf2bdda594b3dcb6c9b4f124d8f72001d8aea27d69dc1c", size = 3403206, upload-time = "2025-08-13T06:20:26.846Z" }, - { url = "https://files.pythonhosted.org/packages/ba/e1/08cfd4c5e99f79e62d4a7b0009bbd906748633270935c8a55eee51fead76/cython-3.1.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7851107204085f4f02d0eb6b660ddcad2ce4846e8b7a1eaba724a0bd3cd68a6b", size = 3327837, upload-time = "2025-08-13T06:20:28.946Z" }, - { url = "https://files.pythonhosted.org/packages/23/1b/f3d384be86fa2a6e110b42f42bf97295a513197aaa5532f451c73bf5b48e/cython-3.1.3-cp312-cp312-win32.whl", hash = "sha256:ed20f1b45b2da5a4f8e71a80025bca1cdc96ba35820b0b17658a4a025be920b0", size = 2485990, upload-time = "2025-08-13T06:20:31.517Z" }, - { url = "https://files.pythonhosted.org/packages/de/f0/17cff75e3c141bda73d770f7412632f53e55778d3bfdadfeec4dd3a7d1d6/cython-3.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:dc4ca0f4dec55124cd79ddcfc555be1cbe0092cc99bcf1403621d17b9c6218bb", size = 2704002, upload-time = "2025-08-13T06:20:33.773Z" }, - { url = "https://files.pythonhosted.org/packages/56/c8/46ac27096684f33e27dab749ef43c6b0119c6a0d852971eaefb73256dc4c/cython-3.1.3-py3-none-any.whl", hash = "sha256:d13025b34f72f77bf7f65c1cd628914763e6c285f4deb934314c922b91e6be5a", size = 1225725, upload-time = "2025-08-13T06:19:09.593Z" }, + { url = "https://files.pythonhosted.org/packages/b5/ab/0a568bac7c4c052db4ae27edf01e16f3093cdfef04a2dfd313ef1b3c478a/cython-3.1.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d1d7013dba5fb0506794d4ef8947ff5ed021370614950a8d8d04e57c8c84499e", size = 3026389, upload-time = "2025-09-16T07:22:02.212Z" }, + { url = "https://files.pythonhosted.org/packages/cb/b7/51f5566e1309215a7fef744975b2fabb56d3fdc5fa1922fd7e306c14f523/cython-3.1.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:eed989f5c139d6550ef2665b783d86fab99372590c97f10a3c26c4523c5fce9e", size = 2955954, upload-time = "2025-09-16T07:22:03.782Z" }, + { url = "https://files.pythonhosted.org/packages/28/fd/ad8314520000fe96292fb8208c640fa862baa3053d2f3453a2acb50cafb8/cython-3.1.4-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3df3beb8b024dfd73cfddb7f2f7456751cebf6e31655eed3189c209b634bc2f2", size = 3412005, upload-time = "2025-09-16T07:22:05.483Z" }, + { url = "https://files.pythonhosted.org/packages/0c/3b/e570f8bcb392e7943fc9a25d1b2d1646ef0148ff017d3681511acf6bbfdc/cython-3.1.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f8354703f1168e1aaa01348940f719734c1f11298be333bdb5b94101d49677c0", size = 3191100, upload-time = "2025-09-16T07:22:07.144Z" }, + { url = "https://files.pythonhosted.org/packages/78/81/f1ea09f563ebab732542cb11bf363710e53f3842458159ea2c160788bc8e/cython-3.1.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a928bd7d446247855f54f359057ab4a32c465219c8c1e299906a483393a59a9e", size = 3313786, upload-time = "2025-09-16T07:22:09.15Z" }, + { url = "https://files.pythonhosted.org/packages/ca/17/06575eb6175a926523bada7dac1cd05cc74add96cebbf2e8b492a2494291/cython-3.1.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c233bfff4cc7b9d629eecb7345f9b733437f76dc4441951ec393b0a6e29919fc", size = 3205775, upload-time = "2025-09-16T07:22:10.745Z" }, + { url = "https://files.pythonhosted.org/packages/10/ba/61a8cf56a76ab21ddf6476b70884feff2a2e56b6d9010e1e1b1e06c46f70/cython-3.1.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e9691a2cbc2faf0cd819108bceccf9bfc56c15a06d172eafe74157388c44a601", size = 3428423, upload-time = "2025-09-16T07:22:12.404Z" }, + { url = "https://files.pythonhosted.org/packages/c4/c2/42cf9239088d6b4b62c1c017c36e0e839f64c8d68674ce4172d0e0168d3b/cython-3.1.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ada319207432ea7c6691c70b5c112d261637d79d21ba086ae3726fedde79bfbf", size = 3330489, upload-time = "2025-09-16T07:22:14.576Z" }, + { url = "https://files.pythonhosted.org/packages/b5/08/36a619d6b1fc671a11744998e5cdd31790589e3cb4542927c97f3f351043/cython-3.1.4-cp311-cp311-win32.whl", hash = "sha256:dae81313c28222bf7be695f85ae1d16625aac35a0973a3af1e001f63379440c5", size = 2482410, upload-time = "2025-09-16T07:22:17.373Z" }, + { url = "https://files.pythonhosted.org/packages/6d/58/7d9ae7944bcd32e6f02d1a8d5d0c3875125227d050e235584127f2c64ffd/cython-3.1.4-cp311-cp311-win_amd64.whl", hash = "sha256:60d2f192059ac34c5c26527f2beac823d34aaa766ef06792a3b7f290c18ac5e2", size = 2713755, upload-time = "2025-09-16T07:22:18.949Z" }, + { url = "https://files.pythonhosted.org/packages/f0/51/2939c739cfdc67ab94935a2c4fcc75638afd15e1954552655503a4112e92/cython-3.1.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0d26af46505d0e54fe0f05e7ad089fd0eed8fa04f385f3ab88796f554467bcb9", size = 3062976, upload-time = "2025-09-16T07:22:20.517Z" }, + { url = "https://files.pythonhosted.org/packages/eb/bd/a84de57fd01017bf5dba84a49aeee826db21112282bf8d76ab97567ee15d/cython-3.1.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:66ac8bb5068156c92359e3f0eefa138c177d59d1a2e8a89467881fa7d06aba3b", size = 2970701, upload-time = "2025-09-16T07:22:22.644Z" }, + { url = "https://files.pythonhosted.org/packages/71/79/a09004c8e42f5be188c7636b1be479cdb244a6d8837e1878d062e4e20139/cython-3.1.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cb2e42714faec723d2305607a04bafb49a48a8d8f25dd39368d884c058dbcfbc", size = 3387730, upload-time = "2025-09-16T07:22:24.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/bd/979f8c59e247f562642f3eb98a1b453530e1f7954ef071835c08ed2bf6ba/cython-3.1.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0fd655b27997a209a574873304ded9629de588f021154009e8f923475e2c677", size = 3167289, upload-time = "2025-09-16T07:22:26.35Z" }, + { url = "https://files.pythonhosted.org/packages/34/f8/0b98537f0b4e8c01f76d2a6cf75389987538e4d4ac9faf25836fd18c9689/cython-3.1.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9def7c41f4dc339003b1e6875f84edf059989b9c7f5e9a245d3ce12c190742d9", size = 3321099, upload-time = "2025-09-16T07:22:27.957Z" }, + { url = "https://files.pythonhosted.org/packages/f3/39/437968a2e7c7f57eb6e1144f6aca968aa15fbbf169b2d4da5d1ff6c21442/cython-3.1.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:196555584a8716bf7e017e23ca53e9f632ed493f9faa327d0718e7551588f55d", size = 3179897, upload-time = "2025-09-16T07:22:30.014Z" }, + { url = "https://files.pythonhosted.org/packages/2c/04/b3f42915f034d133f1a34e74a2270bc2def02786f9b40dc9028fbb968814/cython-3.1.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:7fff0e739e07a20726484b8898b8628a7b87acb960d0fc5486013c6b77b7bb97", size = 3400936, upload-time = "2025-09-16T07:22:31.705Z" }, + { url = "https://files.pythonhosted.org/packages/21/eb/2ad9fa0896ab6cf29875a09a9f4aaea37c28b79b869a013bf9b58e4e652e/cython-3.1.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c2754034fa10f95052949cd6b07eb2f61d654c1b9cfa0b17ea53a269389422e8", size = 3332131, upload-time = "2025-09-16T07:22:33.32Z" }, + { url = "https://files.pythonhosted.org/packages/3c/bf/f19283f8405e7e564c3353302a8665ea2c589be63a8e1be1b503043366a9/cython-3.1.4-cp312-cp312-win32.whl", hash = "sha256:2e0808ff3614a1dbfd1adfcbff9b2b8119292f1824b3535b4a173205109509f8", size = 2487672, upload-time = "2025-09-16T07:22:35.227Z" }, + { url = "https://files.pythonhosted.org/packages/30/bf/32150a2e6c7b50b81c5dc9e942d41969400223a9c49d04e2ed955709894c/cython-3.1.4-cp312-cp312-win_amd64.whl", hash = "sha256:f262b32327b6bce340cce5d45bbfe3972cb62543a4930460d8564a489f3aea12", size = 2705348, upload-time = "2025-09-16T07:22:37.922Z" }, + { url = "https://files.pythonhosted.org/packages/7c/24/f7351052cf9db771fe4f32fca47fd66e6d9b53d8613b17faf7d130a9d553/cython-3.1.4-py3-none-any.whl", hash = "sha256:d194d95e4fa029a3f6c7d46bdd16d973808c7ea4797586911fdb67cb98b1a2c6", size = 1227541, upload-time = "2025-09-16T07:20:29.595Z" }, ] [[package]] @@ -477,11 +479,11 @@ wheels = [ [[package]] name = "dnspython" -version = "2.7.0" +version = "2.8.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b5/4a/263763cb2ba3816dd94b08ad3a33d5fdae34ecb856678773cc40a3605829/dnspython-2.7.0.tar.gz", hash = "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1", size = 345197, upload-time = "2024-10-05T20:14:59.362Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/8b/57666417c0f90f08bcafa776861060426765fdb422eb10212086fb811d26/dnspython-2.8.0.tar.gz", hash = "sha256:181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f", size = 368251, upload-time = "2025-09-07T18:58:00.022Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/68/1b/e0a87d256e40e8c888847551b20a017a6b98139178505dc7ffb96f04e954/dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86", size = 313632, upload-time = "2024-10-05T20:14:57.687Z" }, + { url = "https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af", size = 331094, upload-time = "2025-09-07T18:57:58.071Z" }, ] [[package]] @@ -525,27 +527,27 @@ wheels = [ [[package]] name = "fonttools" -version = "4.59.2" +version = "4.60.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0d/a5/fba25f9fbdab96e26dedcaeeba125e5f05a09043bf888e0305326e55685b/fonttools-4.59.2.tar.gz", hash = "sha256:e72c0749b06113f50bcb80332364c6be83a9582d6e3db3fe0b280f996dc2ef22", size = 3540889, upload-time = "2025-08-27T16:40:30.97Z" } +sdist = { url = "https://files.pythonhosted.org/packages/27/d9/4eabd956fe123651a1f0efe29d9758b3837b5ae9a98934bdb571117033bb/fonttools-4.60.0.tar.gz", hash = "sha256:8f5927f049091a0ca74d35cce7f78e8f7775c83a6901a8fbe899babcc297146a", size = 3553671, upload-time = "2025-09-17T11:34:01.504Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/53/742fcd750ae0bdc74de4c0ff923111199cc2f90a4ee87aaddad505b6f477/fonttools-4.59.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:511946e8d7ea5c0d6c7a53c4cb3ee48eda9ab9797cd9bf5d95829a398400354f", size = 2774961, upload-time = "2025-08-27T16:38:47.536Z" }, - { url = "https://files.pythonhosted.org/packages/57/2a/976f5f9fa3b4dd911dc58d07358467bec20e813d933bc5d3db1a955dd456/fonttools-4.59.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8e5e2682cf7be766d84f462ba8828d01e00c8751a8e8e7ce12d7784ccb69a30d", size = 2344690, upload-time = "2025-08-27T16:38:49.723Z" }, - { url = "https://files.pythonhosted.org/packages/c1/8f/b7eefc274fcf370911e292e95565c8253b0b87c82a53919ab3c795a4f50e/fonttools-4.59.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5729e12a982dba3eeae650de48b06f3b9ddb51e9aee2fcaf195b7d09a96250e2", size = 5026910, upload-time = "2025-08-27T16:38:51.904Z" }, - { url = "https://files.pythonhosted.org/packages/69/95/864726eaa8f9d4e053d0c462e64d5830ec7c599cbdf1db9e40f25ca3972e/fonttools-4.59.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c52694eae5d652361d59ecdb5a2246bff7cff13b6367a12da8499e9df56d148d", size = 4971031, upload-time = "2025-08-27T16:38:53.676Z" }, - { url = "https://files.pythonhosted.org/packages/24/4c/b8c4735ebdea20696277c70c79e0de615dbe477834e5a7c2569aa1db4033/fonttools-4.59.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f1f1bbc23ba1312bd8959896f46f667753b90216852d2a8cfa2d07e0cb234144", size = 5006112, upload-time = "2025-08-27T16:38:55.69Z" }, - { url = "https://files.pythonhosted.org/packages/3b/23/f9ea29c292aa2fc1ea381b2e5621ac436d5e3e0a5dee24ffe5404e58eae8/fonttools-4.59.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1a1bfe5378962825dabe741720885e8b9ae9745ec7ecc4a5ec1f1ce59a6062bf", size = 5117671, upload-time = "2025-08-27T16:38:58.984Z" }, - { url = "https://files.pythonhosted.org/packages/ba/07/cfea304c555bf06e86071ff2a3916bc90f7c07ec85b23bab758d4908c33d/fonttools-4.59.2-cp311-cp311-win32.whl", hash = "sha256:e937790f3c2c18a1cbc7da101550a84319eb48023a715914477d2e7faeaba570", size = 2218157, upload-time = "2025-08-27T16:39:00.75Z" }, - { url = "https://files.pythonhosted.org/packages/d7/de/35d839aa69db737a3f9f3a45000ca24721834d40118652a5775d5eca8ebb/fonttools-4.59.2-cp311-cp311-win_amd64.whl", hash = "sha256:9836394e2f4ce5f9c0a7690ee93bd90aa1adc6b054f1a57b562c5d242c903104", size = 2265846, upload-time = "2025-08-27T16:39:02.453Z" }, - { url = "https://files.pythonhosted.org/packages/ba/3d/1f45db2df51e7bfa55492e8f23f383d372200be3a0ded4bf56a92753dd1f/fonttools-4.59.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:82906d002c349cad647a7634b004825a7335f8159d0d035ae89253b4abf6f3ea", size = 2769711, upload-time = "2025-08-27T16:39:04.423Z" }, - { url = "https://files.pythonhosted.org/packages/29/df/cd236ab32a8abfd11558f296e064424258db5edefd1279ffdbcfd4fd8b76/fonttools-4.59.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a10c1bd7644dc58f8862d8ba0cf9fb7fef0af01ea184ba6ce3f50ab7dfe74d5a", size = 2340225, upload-time = "2025-08-27T16:39:06.143Z" }, - { url = "https://files.pythonhosted.org/packages/98/12/b6f9f964fe6d4b4dd4406bcbd3328821c3de1f909ffc3ffa558fe72af48c/fonttools-4.59.2-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:738f31f23e0339785fd67652a94bc69ea49e413dfdb14dcb8c8ff383d249464e", size = 4912766, upload-time = "2025-08-27T16:39:08.138Z" }, - { url = "https://files.pythonhosted.org/packages/73/78/82bde2f2d2c306ef3909b927363170b83df96171f74e0ccb47ad344563cd/fonttools-4.59.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ec99f9bdfee9cdb4a9172f9e8fd578cce5feb231f598909e0aecf5418da4f25", size = 4955178, upload-time = "2025-08-27T16:39:10.094Z" }, - { url = "https://files.pythonhosted.org/packages/92/77/7de766afe2d31dda8ee46d7e479f35c7d48747e558961489a2d6e3a02bd4/fonttools-4.59.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0476ea74161322e08c7a982f83558a2b81b491509984523a1a540baf8611cc31", size = 4897898, upload-time = "2025-08-27T16:39:12.087Z" }, - { url = "https://files.pythonhosted.org/packages/c5/77/ce0e0b905d62a06415fda9f2b2e109a24a5db54a59502b769e9e297d2242/fonttools-4.59.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:95922a922daa1f77cc72611747c156cfb38030ead72436a2c551d30ecef519b9", size = 5049144, upload-time = "2025-08-27T16:39:13.84Z" }, - { url = "https://files.pythonhosted.org/packages/d9/ea/870d93aefd23fff2e07cbeebdc332527868422a433c64062c09d4d5e7fe6/fonttools-4.59.2-cp312-cp312-win32.whl", hash = "sha256:39ad9612c6a622726a6a130e8ab15794558591f999673f1ee7d2f3d30f6a3e1c", size = 2206473, upload-time = "2025-08-27T16:39:15.854Z" }, - { url = "https://files.pythonhosted.org/packages/61/c4/e44bad000c4a4bb2e9ca11491d266e857df98ab6d7428441b173f0fe2517/fonttools-4.59.2-cp312-cp312-win_amd64.whl", hash = "sha256:980fd7388e461b19a881d35013fec32c713ffea1fc37aef2f77d11f332dfd7da", size = 2254706, upload-time = "2025-08-27T16:39:17.893Z" }, - { url = "https://files.pythonhosted.org/packages/65/a4/d2f7be3c86708912c02571db0b550121caab8cd88a3c0aacb9cfa15ea66e/fonttools-4.59.2-py3-none-any.whl", hash = "sha256:8bd0f759020e87bb5d323e6283914d9bf4ae35a7307dafb2cbd1e379e720ad37", size = 1132315, upload-time = "2025-08-27T16:40:28.984Z" }, + { url = "https://files.pythonhosted.org/packages/da/3d/c57731fbbf204ef1045caca28d5176430161ead73cd9feac3e9d9ef77ee6/fonttools-4.60.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a9106c202d68ff5f9b4a0094c4d7ad2eaa7e9280f06427b09643215e706eb016", size = 2830883, upload-time = "2025-09-17T11:32:10.552Z" }, + { url = "https://files.pythonhosted.org/packages/cc/2d/b7a6ebaed464ce441c755252cc222af11edc651d17c8f26482f429cc2c0e/fonttools-4.60.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9da3a4a3f2485b156bb429b4f8faa972480fc01f553f7c8c80d05d48f17eec89", size = 2356005, upload-time = "2025-09-17T11:32:13.248Z" }, + { url = "https://files.pythonhosted.org/packages/ee/c2/ea834e921324e2051403e125c1fe0bfbdde4951a7c1784e4ae6bdbd286cc/fonttools-4.60.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f84de764c6057b2ffd4feb50ddef481d92e348f0c70f2c849b723118d352bf3", size = 5041201, upload-time = "2025-09-17T11:32:15.373Z" }, + { url = "https://files.pythonhosted.org/packages/93/3c/1c64a338e9aa410d2d0728827d5bb1301463078cb225b94589f27558b427/fonttools-4.60.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:800b3fa0d5c12ddff02179d45b035a23989a6c597a71c8035c010fff3b2ef1bb", size = 4977696, upload-time = "2025-09-17T11:32:17.674Z" }, + { url = "https://files.pythonhosted.org/packages/07/cc/c8c411a0d9732bb886b870e052f20658fec9cf91118314f253950d2c1d65/fonttools-4.60.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd68f60b030277f292a582d31c374edfadc60bb33d51ec7b6cd4304531819ba", size = 5020386, upload-time = "2025-09-17T11:32:20.089Z" }, + { url = "https://files.pythonhosted.org/packages/13/01/1d3bc07cf92e7f4fc27f06d4494bf6078dc595b2e01b959157a4fd23df12/fonttools-4.60.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:53328e3ca9e5c8660ef6de07c35f8f312c189b757535e12141be7a8ec942de6e", size = 5131575, upload-time = "2025-09-17T11:32:22.582Z" }, + { url = "https://files.pythonhosted.org/packages/5a/16/08db3917ee19e89d2eb0ee637d37cd4136c849dc421ff63f406b9165c1a1/fonttools-4.60.0-cp311-cp311-win32.whl", hash = "sha256:d493c175ddd0b88a5376e61163e3e6fde3be8b8987db9b092e0a84650709c9e7", size = 2229297, upload-time = "2025-09-17T11:32:24.834Z" }, + { url = "https://files.pythonhosted.org/packages/d2/0b/76764da82c0dfcea144861f568d9e83f4b921e84f2be617b451257bb25a7/fonttools-4.60.0-cp311-cp311-win_amd64.whl", hash = "sha256:cc2770c9dc49c2d0366e9683f4d03beb46c98042d7ccc8ddbadf3459ecb051a7", size = 2277193, upload-time = "2025-09-17T11:32:27.094Z" }, + { url = "https://files.pythonhosted.org/packages/2a/9b/706ebf84b55ab03439c1f3a94d6915123c0d96099f4238b254fdacffe03a/fonttools-4.60.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8c68928a438d60dfde90e2f09aa7f848ed201176ca6652341744ceec4215859f", size = 2831953, upload-time = "2025-09-17T11:32:29.39Z" }, + { url = "https://files.pythonhosted.org/packages/76/40/782f485be450846e4f3aecff1f10e42af414fc6e19d235c70020f64278e1/fonttools-4.60.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b7133821249097cffabf0624eafd37f5a3358d5ce814febe9db688e3673e724e", size = 2351716, upload-time = "2025-09-17T11:32:31.46Z" }, + { url = "https://files.pythonhosted.org/packages/39/77/ad8d2a6ecc19716eb488c8cf118de10f7802e14bdf61d136d7b52358d6b1/fonttools-4.60.0-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d3638905d3d77ac8791127ce181f7cb434f37e4204d8b2e31b8f1e154320b41f", size = 4922729, upload-time = "2025-09-17T11:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/6b/48/aa543037c6e7788e1bc36b3f858ac70a59d32d0f45915263d0b330a35140/fonttools-4.60.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7968a26ef010ae89aabbb2f8e9dec1e2709a2541bb8620790451ee8aeb4f6fbf", size = 4967188, upload-time = "2025-09-17T11:32:35.74Z" }, + { url = "https://files.pythonhosted.org/packages/ac/58/e407d2028adc6387947eff8f2940b31f4ed40b9a83c2c7bbc8b9255126e2/fonttools-4.60.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1ef01ca7847c356b0fe026b7b92304bc31dc60a4218689ee0acc66652c1a36b2", size = 4910043, upload-time = "2025-09-17T11:32:38.054Z" }, + { url = "https://files.pythonhosted.org/packages/16/ef/e78519b3c296ef757a21b792fc6a785aa2ef9a2efb098083d8ed5f6ee2ba/fonttools-4.60.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f3482d7ed7867edfcf785f77c1dffc876c4b2ddac19539c075712ff2a0703cf5", size = 5061980, upload-time = "2025-09-17T11:32:40.457Z" }, + { url = "https://files.pythonhosted.org/packages/00/4c/ad72444d1e3ef704ee90af8d5abf198016a39908d322bf41235562fb01a0/fonttools-4.60.0-cp312-cp312-win32.whl", hash = "sha256:8c937c4fe8addff575a984c9519433391180bf52cf35895524a07b520f376067", size = 2217750, upload-time = "2025-09-17T11:32:42.586Z" }, + { url = "https://files.pythonhosted.org/packages/46/55/3e8ac21963e130242f5a9ea2ebc57f5726d704bf4dcca89088b5b637b2d3/fonttools-4.60.0-cp312-cp312-win_amd64.whl", hash = "sha256:99b06d5d6f29f32e312adaed0367112f5ff2d300ea24363d377ec917daf9e8c5", size = 2266025, upload-time = "2025-09-17T11:32:44.8Z" }, + { url = "https://files.pythonhosted.org/packages/f9/a4/247d3e54eb5ed59e94e09866cfc4f9567e274fbf310ba390711851f63b3b/fonttools-4.60.0-py3-none-any.whl", hash = "sha256:496d26e4d14dcccdd6ada2e937e4d174d3138e3d73f5c9b6ec6eb2fd1dab4f66", size = 1142186, upload-time = "2025-09-17T11:33:59.287Z" }, ] [[package]] @@ -637,10 +639,10 @@ name = "gymnasium" version = "1.2.0" 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/fd/17/c2a0e15c2cd5a8e788389b280996db927b923410de676ec5c7b2695e9261/gymnasium-1.2.0.tar.gz", hash = "sha256:344e87561012558f603880baf264ebc97f8a5c997a957b0c9f910281145534b0", size = 821142, upload-time = "2025-06-27T08:21:20.262Z" } wheels = [ @@ -737,11 +739,11 @@ wheels = [ [[package]] name = "kaitaistruct" -version = "0.10" +version = "0.11" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/54/04/dd60b9cb65d580ef6cb6eaee975ad1bdd22d46a3f51b07a1e0606710ea88/kaitaistruct-0.10.tar.gz", hash = "sha256:a044dee29173d6afbacf27bcac39daf89b654dd418cfa009ab82d9178a9ae52a", size = 7061, upload-time = "2022-07-09T00:34:06.729Z" } +sdist = { url = "https://files.pythonhosted.org/packages/27/b8/ca7319556912f68832daa4b81425314857ec08dfccd8dbc8c0f65c992108/kaitaistruct-0.11.tar.gz", hash = "sha256:053ee764288e78b8e53acf748e9733268acbd579b8d82a427b1805453625d74b", size = 11519, upload-time = "2025-09-08T15:46:25.037Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4e/bf/88ad23efc08708bda9a2647169828e3553bb2093a473801db61f75356395/kaitaistruct-0.10-py2.py3-none-any.whl", hash = "sha256:a97350919adbf37fda881f75e9365e2fb88d04832b7a4e57106ec70119efb235", size = 7013, upload-time = "2022-07-09T00:34:03.905Z" }, + { url = "https://files.pythonhosted.org/packages/4a/4a/cf14bf3b1f5ffb13c69cf5f0ea78031247790558ee88984a8bdd22fae60d/kaitaistruct-0.11-py2.py3-none-any.whl", hash = "sha256:5c6ce79177b4e193a577ecd359e26516d1d6d000a0bffd6e1010f2a46a62a561", size = 11372, upload-time = "2025-09-08T15:46:23.635Z" }, ] [[package]] @@ -842,11 +844,11 @@ wheels = [ [[package]] name = "markdown" -version = "3.8.2" +version = "3.9" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/c2/4ab49206c17f75cb08d6311171f2d65798988db4360c4d1485bd0eedd67c/markdown-3.8.2.tar.gz", hash = "sha256:247b9a70dd12e27f67431ce62523e675b866d254f900c4fe75ce3dda62237c45", size = 362071, upload-time = "2025-06-19T17:12:44.483Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8d/37/02347f6d6d8279247a5837082ebc26fc0d5aaeaf75aa013fcbb433c777ab/markdown-3.9.tar.gz", hash = "sha256:d2900fe1782bd33bdbbd56859defef70c2e78fc46668f8eb9df3128138f2cb6a", size = 364585, upload-time = "2025-09-04T20:25:22.885Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/96/2b/34cc11786bc00d0f04d0f5fdc3a2b1ae0b6239eef72d3d345805f9ad92a1/markdown-3.8.2-py3-none-any.whl", hash = "sha256:5c83764dbd4e00bdd94d85a19b8d55ccca20fe35b2e678a1422b380324dd5f24", size = 106827, upload-time = "2025-06-19T17:12:42.994Z" }, + { url = "https://files.pythonhosted.org/packages/70/ae/44c4a6a4cbb496d93c6257954260fe3a6e91b7bed2240e5dad2a717f5111/markdown-3.9-py3-none-any.whl", hash = "sha256:9f4d91ed810864ea88a6f32c07ba8bee1346c0cc1f6b1f9f6c822f2a9667d280", size = 107441, upload-time = "2025-09-04T20:25:21.784Z" }, ] [[package]] @@ -927,22 +929,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" }, @@ -1128,28 +1130,28 @@ wheels = [ [[package]] name = "mypy" -version = "1.17.1" +version = "1.18.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mypy-extensions" }, { name = "pathspec" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8e/22/ea637422dedf0bf36f3ef238eab4e455e2a0dcc3082b5cc067615347ab8e/mypy-1.17.1.tar.gz", hash = "sha256:25e01ec741ab5bb3eec8ba9cdb0f769230368a22c959c4937360efb89b7e9f01", size = 3352570, upload-time = "2025-07-31T07:54:19.204Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c0/77/8f0d0001ffad290cef2f7f216f96c814866248a0b92a722365ed54648e7e/mypy-1.18.2.tar.gz", hash = "sha256:06a398102a5f203d7477b2923dda3634c36727fa5c237d8f859ef90c42a9924b", size = 3448846, upload-time = "2025-09-19T00:11:10.519Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/46/cf/eadc80c4e0a70db1c08921dcc220357ba8ab2faecb4392e3cebeb10edbfa/mypy-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad37544be07c5d7fba814eb370e006df58fed8ad1ef33ed1649cb1889ba6ff58", size = 10921009, upload-time = "2025-07-31T07:53:23.037Z" }, - { url = "https://files.pythonhosted.org/packages/5d/c1/c869d8c067829ad30d9bdae051046561552516cfb3a14f7f0347b7d973ee/mypy-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:064e2ff508e5464b4bd807a7c1625bc5047c5022b85c70f030680e18f37273a5", size = 10047482, upload-time = "2025-07-31T07:53:26.151Z" }, - { url = "https://files.pythonhosted.org/packages/98/b9/803672bab3fe03cee2e14786ca056efda4bb511ea02dadcedde6176d06d0/mypy-1.17.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70401bbabd2fa1aa7c43bb358f54037baf0586f41e83b0ae67dd0534fc64edfd", size = 11832883, upload-time = "2025-07-31T07:53:47.948Z" }, - { url = "https://files.pythonhosted.org/packages/88/fb/fcdac695beca66800918c18697b48833a9a6701de288452b6715a98cfee1/mypy-1.17.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e92bdc656b7757c438660f775f872a669b8ff374edc4d18277d86b63edba6b8b", size = 12566215, upload-time = "2025-07-31T07:54:04.031Z" }, - { url = "https://files.pythonhosted.org/packages/7f/37/a932da3d3dace99ee8eb2043b6ab03b6768c36eb29a02f98f46c18c0da0e/mypy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c1fdf4abb29ed1cb091cf432979e162c208a5ac676ce35010373ff29247bcad5", size = 12751956, upload-time = "2025-07-31T07:53:36.263Z" }, - { url = "https://files.pythonhosted.org/packages/8c/cf/6438a429e0f2f5cab8bc83e53dbebfa666476f40ee322e13cac5e64b79e7/mypy-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:ff2933428516ab63f961644bc49bc4cbe42bbffb2cd3b71cc7277c07d16b1a8b", size = 9507307, upload-time = "2025-07-31T07:53:59.734Z" }, - { url = "https://files.pythonhosted.org/packages/17/a2/7034d0d61af8098ec47902108553122baa0f438df8a713be860f7407c9e6/mypy-1.17.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:69e83ea6553a3ba79c08c6e15dbd9bfa912ec1e493bf75489ef93beb65209aeb", size = 11086295, upload-time = "2025-07-31T07:53:28.124Z" }, - { url = "https://files.pythonhosted.org/packages/14/1f/19e7e44b594d4b12f6ba8064dbe136505cec813549ca3e5191e40b1d3cc2/mypy-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1b16708a66d38abb1e6b5702f5c2c87e133289da36f6a1d15f6a5221085c6403", size = 10112355, upload-time = "2025-07-31T07:53:21.121Z" }, - { url = "https://files.pythonhosted.org/packages/5b/69/baa33927e29e6b4c55d798a9d44db5d394072eef2bdc18c3e2048c9ed1e9/mypy-1.17.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:89e972c0035e9e05823907ad5398c5a73b9f47a002b22359b177d40bdaee7056", size = 11875285, upload-time = "2025-07-31T07:53:55.293Z" }, - { url = "https://files.pythonhosted.org/packages/90/13/f3a89c76b0a41e19490b01e7069713a30949d9a6c147289ee1521bcea245/mypy-1.17.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03b6d0ed2b188e35ee6d5c36b5580cffd6da23319991c49ab5556c023ccf1341", size = 12737895, upload-time = "2025-07-31T07:53:43.623Z" }, - { url = "https://files.pythonhosted.org/packages/23/a1/c4ee79ac484241301564072e6476c5a5be2590bc2e7bfd28220033d2ef8f/mypy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c837b896b37cd103570d776bda106eabb8737aa6dd4f248451aecf53030cdbeb", size = 12931025, upload-time = "2025-07-31T07:54:17.125Z" }, - { url = "https://files.pythonhosted.org/packages/89/b8/7409477be7919a0608900e6320b155c72caab4fef46427c5cc75f85edadd/mypy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:665afab0963a4b39dff7c1fa563cc8b11ecff7910206db4b2e64dd1ba25aed19", size = 9584664, upload-time = "2025-07-31T07:54:12.842Z" }, - { url = "https://files.pythonhosted.org/packages/1d/f3/8fcd2af0f5b806f6cf463efaffd3c9548a28f84220493ecd38d127b6b66d/mypy-1.17.1-py3-none-any.whl", hash = "sha256:a9f52c0351c21fe24c21d8c0eb1f62967b262d6729393397b6f443c3b773c3b9", size = 2283411, upload-time = "2025-07-31T07:53:24.664Z" }, + { url = "https://files.pythonhosted.org/packages/88/87/cafd3ae563f88f94eec33f35ff722d043e09832ea8530ef149ec1efbaf08/mypy-1.18.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:807d9315ab9d464125aa9fcf6d84fde6e1dc67da0b6f80e7405506b8ac72bc7f", size = 12731198, upload-time = "2025-09-19T00:09:44.857Z" }, + { url = "https://files.pythonhosted.org/packages/0f/e0/1e96c3d4266a06d4b0197ace5356d67d937d8358e2ee3ffac71faa843724/mypy-1.18.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:776bb00de1778caf4db739c6e83919c1d85a448f71979b6a0edd774ea8399341", size = 11817879, upload-time = "2025-09-19T00:09:47.131Z" }, + { url = "https://files.pythonhosted.org/packages/72/ef/0c9ba89eb03453e76bdac5a78b08260a848c7bfc5d6603634774d9cd9525/mypy-1.18.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1379451880512ffce14505493bd9fe469e0697543717298242574882cf8cdb8d", size = 12427292, upload-time = "2025-09-19T00:10:22.472Z" }, + { url = "https://files.pythonhosted.org/packages/1a/52/ec4a061dd599eb8179d5411d99775bec2a20542505988f40fc2fee781068/mypy-1.18.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1331eb7fd110d60c24999893320967594ff84c38ac6d19e0a76c5fd809a84c86", size = 13163750, upload-time = "2025-09-19T00:09:51.472Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5f/2cf2ceb3b36372d51568f2208c021870fe7834cf3186b653ac6446511839/mypy-1.18.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ca30b50a51e7ba93b00422e486cbb124f1c56a535e20eff7b2d6ab72b3b2e37", size = 13351827, upload-time = "2025-09-19T00:09:58.311Z" }, + { url = "https://files.pythonhosted.org/packages/c8/7d/2697b930179e7277529eaaec1513f8de622818696857f689e4a5432e5e27/mypy-1.18.2-cp311-cp311-win_amd64.whl", hash = "sha256:664dc726e67fa54e14536f6e1224bcfce1d9e5ac02426d2326e2bb4e081d1ce8", size = 9757983, upload-time = "2025-09-19T00:10:09.071Z" }, + { url = "https://files.pythonhosted.org/packages/07/06/dfdd2bc60c66611dd8335f463818514733bc763e4760dee289dcc33df709/mypy-1.18.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:33eca32dd124b29400c31d7cf784e795b050ace0e1f91b8dc035672725617e34", size = 12908273, upload-time = "2025-09-19T00:10:58.321Z" }, + { url = "https://files.pythonhosted.org/packages/81/14/6a9de6d13a122d5608e1a04130724caf9170333ac5a924e10f670687d3eb/mypy-1.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3c47adf30d65e89b2dcd2fa32f3aeb5e94ca970d2c15fcb25e297871c8e4764", size = 11920910, upload-time = "2025-09-19T00:10:20.043Z" }, + { url = "https://files.pythonhosted.org/packages/5f/a9/b29de53e42f18e8cc547e38daa9dfa132ffdc64f7250e353f5c8cdd44bee/mypy-1.18.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d6c838e831a062f5f29d11c9057c6009f60cb294fea33a98422688181fe2893", size = 12465585, upload-time = "2025-09-19T00:10:33.005Z" }, + { url = "https://files.pythonhosted.org/packages/77/ae/6c3d2c7c61ff21f2bee938c917616c92ebf852f015fb55917fd6e2811db2/mypy-1.18.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01199871b6110a2ce984bde85acd481232d17413868c9807e95c1b0739a58914", size = 13348562, upload-time = "2025-09-19T00:10:11.51Z" }, + { url = "https://files.pythonhosted.org/packages/4d/31/aec68ab3b4aebdf8f36d191b0685d99faa899ab990753ca0fee60fb99511/mypy-1.18.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a2afc0fa0b0e91b4599ddfe0f91e2c26c2b5a5ab263737e998d6817874c5f7c8", size = 13533296, upload-time = "2025-09-19T00:10:06.568Z" }, + { url = "https://files.pythonhosted.org/packages/9f/83/abcb3ad9478fca3ebeb6a5358bb0b22c95ea42b43b7789c7fb1297ca44f4/mypy-1.18.2-cp312-cp312-win_amd64.whl", hash = "sha256:d8068d0afe682c7c4897c0f7ce84ea77f6de953262b12d07038f4d296d547074", size = 9828828, upload-time = "2025-09-19T00:10:28.203Z" }, + { url = "https://files.pythonhosted.org/packages/87/e3/be76d87158ebafa0309946c4a73831974d4d6ab4f4ef40c3b53a385a66fd/mypy-1.18.2-py3-none-any.whl", hash = "sha256:22a1748707dd62b58d2ae53562ffc4d7f8bcc727e8ac7cbc69c053ddc874d47e", size = 2352367, upload-time = "2025-09-19T00:10:15.489Z" }, ] [[package]] @@ -1172,39 +1174,39 @@ wheels = [ [[package]] name = "numpy" -version = "2.3.2" +version = "2.3.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/37/7d/3fec4199c5ffb892bed55cff901e4f39a58c81df9c44c280499e92cad264/numpy-2.3.2.tar.gz", hash = "sha256:e0486a11ec30cdecb53f184d496d1c6a20786c81e55e41640270130056f8ee48", size = 20489306, upload-time = "2025-07-24T21:32:07.553Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/19/95b3d357407220ed24c139018d2518fab0a61a948e68286a25f1a4d049ff/numpy-2.3.3.tar.gz", hash = "sha256:ddc7c39727ba62b80dfdbedf400d1c10ddfa8eefbd7ec8dcb118be8b56d31029", size = 20576648, upload-time = "2025-09-09T16:54:12.543Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/96/26/1320083986108998bd487e2931eed2aeedf914b6e8905431487543ec911d/numpy-2.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:852ae5bed3478b92f093e30f785c98e0cb62fa0a939ed057c31716e18a7a22b9", size = 21259016, upload-time = "2025-07-24T20:24:35.214Z" }, - { url = "https://files.pythonhosted.org/packages/c4/2b/792b341463fa93fc7e55abbdbe87dac316c5b8cb5e94fb7a59fb6fa0cda5/numpy-2.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a0e27186e781a69959d0230dd9909b5e26024f8da10683bd6344baea1885168", size = 14451158, upload-time = "2025-07-24T20:24:58.397Z" }, - { url = "https://files.pythonhosted.org/packages/b7/13/e792d7209261afb0c9f4759ffef6135b35c77c6349a151f488f531d13595/numpy-2.3.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:f0a1a8476ad77a228e41619af2fa9505cf69df928e9aaa165746584ea17fed2b", size = 5379817, upload-time = "2025-07-24T20:25:07.746Z" }, - { url = "https://files.pythonhosted.org/packages/49/ce/055274fcba4107c022b2113a213c7287346563f48d62e8d2a5176ad93217/numpy-2.3.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:cbc95b3813920145032412f7e33d12080f11dc776262df1712e1638207dde9e8", size = 6913606, upload-time = "2025-07-24T20:25:18.84Z" }, - { url = "https://files.pythonhosted.org/packages/17/f2/e4d72e6bc5ff01e2ab613dc198d560714971900c03674b41947e38606502/numpy-2.3.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f75018be4980a7324edc5930fe39aa391d5734531b1926968605416ff58c332d", size = 14589652, upload-time = "2025-07-24T20:25:40.356Z" }, - { url = "https://files.pythonhosted.org/packages/c8/b0/fbeee3000a51ebf7222016e2939b5c5ecf8000a19555d04a18f1e02521b8/numpy-2.3.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20b8200721840f5621b7bd03f8dcd78de33ec522fc40dc2641aa09537df010c3", size = 16938816, upload-time = "2025-07-24T20:26:05.721Z" }, - { url = "https://files.pythonhosted.org/packages/a9/ec/2f6c45c3484cc159621ea8fc000ac5a86f1575f090cac78ac27193ce82cd/numpy-2.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f91e5c028504660d606340a084db4b216567ded1056ea2b4be4f9d10b67197f", size = 16370512, upload-time = "2025-07-24T20:26:30.545Z" }, - { url = "https://files.pythonhosted.org/packages/b5/01/dd67cf511850bd7aefd6347aaae0956ed415abea741ae107834aae7d6d4e/numpy-2.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:fb1752a3bb9a3ad2d6b090b88a9a0ae1cd6f004ef95f75825e2f382c183b2097", size = 18884947, upload-time = "2025-07-24T20:26:58.24Z" }, - { url = "https://files.pythonhosted.org/packages/a7/17/2cf60fd3e6a61d006778735edf67a222787a8c1a7842aed43ef96d777446/numpy-2.3.2-cp311-cp311-win32.whl", hash = "sha256:4ae6863868aaee2f57503c7a5052b3a2807cf7a3914475e637a0ecd366ced220", size = 6599494, upload-time = "2025-07-24T20:27:09.786Z" }, - { url = "https://files.pythonhosted.org/packages/d5/03/0eade211c504bda872a594f045f98ddcc6caef2b7c63610946845e304d3f/numpy-2.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:240259d6564f1c65424bcd10f435145a7644a65a6811cfc3201c4a429ba79170", size = 13087889, upload-time = "2025-07-24T20:27:29.558Z" }, - { url = "https://files.pythonhosted.org/packages/13/32/2c7979d39dafb2a25087e12310fc7f3b9d3c7d960df4f4bc97955ae0ce1d/numpy-2.3.2-cp311-cp311-win_arm64.whl", hash = "sha256:4209f874d45f921bde2cff1ffcd8a3695f545ad2ffbef6d3d3c6768162efab89", size = 10459560, upload-time = "2025-07-24T20:27:46.803Z" }, - { url = "https://files.pythonhosted.org/packages/00/6d/745dd1c1c5c284d17725e5c802ca4d45cfc6803519d777f087b71c9f4069/numpy-2.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bc3186bea41fae9d8e90c2b4fb5f0a1f5a690682da79b92574d63f56b529080b", size = 20956420, upload-time = "2025-07-24T20:28:18.002Z" }, - { url = "https://files.pythonhosted.org/packages/bc/96/e7b533ea5740641dd62b07a790af5d9d8fec36000b8e2d0472bd7574105f/numpy-2.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f4f0215edb189048a3c03bd5b19345bdfa7b45a7a6f72ae5945d2a28272727f", size = 14184660, upload-time = "2025-07-24T20:28:39.522Z" }, - { url = "https://files.pythonhosted.org/packages/2b/53/102c6122db45a62aa20d1b18c9986f67e6b97e0d6fbc1ae13e3e4c84430c/numpy-2.3.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:8b1224a734cd509f70816455c3cffe13a4f599b1bf7130f913ba0e2c0b2006c0", size = 5113382, upload-time = "2025-07-24T20:28:48.544Z" }, - { url = "https://files.pythonhosted.org/packages/2b/21/376257efcbf63e624250717e82b4fae93d60178f09eb03ed766dbb48ec9c/numpy-2.3.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3dcf02866b977a38ba3ec10215220609ab9667378a9e2150615673f3ffd6c73b", size = 6647258, upload-time = "2025-07-24T20:28:59.104Z" }, - { url = "https://files.pythonhosted.org/packages/91/ba/f4ebf257f08affa464fe6036e13f2bf9d4642a40228781dc1235da81be9f/numpy-2.3.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:572d5512df5470f50ada8d1972c5f1082d9a0b7aa5944db8084077570cf98370", size = 14281409, upload-time = "2025-07-24T20:40:30.298Z" }, - { url = "https://files.pythonhosted.org/packages/59/ef/f96536f1df42c668cbacb727a8c6da7afc9c05ece6d558927fb1722693e1/numpy-2.3.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8145dd6d10df13c559d1e4314df29695613575183fa2e2d11fac4c208c8a1f73", size = 16641317, upload-time = "2025-07-24T20:40:56.625Z" }, - { url = "https://files.pythonhosted.org/packages/f6/a7/af813a7b4f9a42f498dde8a4c6fcbff8100eed00182cc91dbaf095645f38/numpy-2.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:103ea7063fa624af04a791c39f97070bf93b96d7af7eb23530cd087dc8dbe9dc", size = 16056262, upload-time = "2025-07-24T20:41:20.797Z" }, - { url = "https://files.pythonhosted.org/packages/8b/5d/41c4ef8404caaa7f05ed1cfb06afe16a25895260eacbd29b4d84dff2920b/numpy-2.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc927d7f289d14f5e037be917539620603294454130b6de200091e23d27dc9be", size = 18579342, upload-time = "2025-07-24T20:41:50.753Z" }, - { url = "https://files.pythonhosted.org/packages/a1/4f/9950e44c5a11636f4a3af6e825ec23003475cc9a466edb7a759ed3ea63bd/numpy-2.3.2-cp312-cp312-win32.whl", hash = "sha256:d95f59afe7f808c103be692175008bab926b59309ade3e6d25009e9a171f7036", size = 6320610, upload-time = "2025-07-24T20:42:01.551Z" }, - { url = "https://files.pythonhosted.org/packages/7c/2f/244643a5ce54a94f0a9a2ab578189c061e4a87c002e037b0829dd77293b6/numpy-2.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:9e196ade2400c0c737d93465327d1ae7c06c7cb8a1756121ebf54b06ca183c7f", size = 12786292, upload-time = "2025-07-24T20:42:20.738Z" }, - { url = "https://files.pythonhosted.org/packages/54/cd/7b5f49d5d78db7badab22d8323c1b6ae458fbf86c4fdfa194ab3cd4eb39b/numpy-2.3.2-cp312-cp312-win_arm64.whl", hash = "sha256:ee807923782faaf60d0d7331f5e86da7d5e3079e28b291973c545476c2b00d07", size = 10194071, upload-time = "2025-07-24T20:42:36.657Z" }, - { url = "https://files.pythonhosted.org/packages/cf/ea/50ebc91d28b275b23b7128ef25c3d08152bc4068f42742867e07a870a42a/numpy-2.3.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:14a91ebac98813a49bc6aa1a0dfc09513dcec1d97eaf31ca21a87221a1cdcb15", size = 21130338, upload-time = "2025-07-24T20:57:54.37Z" }, - { url = "https://files.pythonhosted.org/packages/9f/57/cdd5eac00dd5f137277355c318a955c0d8fb8aa486020c22afd305f8b88f/numpy-2.3.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:71669b5daae692189540cffc4c439468d35a3f84f0c88b078ecd94337f6cb0ec", size = 14375776, upload-time = "2025-07-24T20:58:16.303Z" }, - { url = "https://files.pythonhosted.org/packages/83/85/27280c7f34fcd305c2209c0cdca4d70775e4859a9eaa92f850087f8dea50/numpy-2.3.2-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:69779198d9caee6e547adb933941ed7520f896fd9656834c300bdf4dd8642712", size = 5304882, upload-time = "2025-07-24T20:58:26.199Z" }, - { url = "https://files.pythonhosted.org/packages/48/b4/6500b24d278e15dd796f43824e69939d00981d37d9779e32499e823aa0aa/numpy-2.3.2-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:2c3271cc4097beb5a60f010bcc1cc204b300bb3eafb4399376418a83a1c6373c", size = 6818405, upload-time = "2025-07-24T20:58:37.341Z" }, - { url = "https://files.pythonhosted.org/packages/9b/c9/142c1e03f199d202da8e980c2496213509291b6024fd2735ad28ae7065c7/numpy-2.3.2-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8446acd11fe3dc1830568c941d44449fd5cb83068e5c70bd5a470d323d448296", size = 14419651, upload-time = "2025-07-24T20:58:59.048Z" }, - { url = "https://files.pythonhosted.org/packages/8b/95/8023e87cbea31a750a6c00ff9427d65ebc5fef104a136bfa69f76266d614/numpy-2.3.2-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aa098a5ab53fa407fded5870865c6275a5cd4101cfdef8d6fafc48286a96e981", size = 16760166, upload-time = "2025-07-24T21:28:56.38Z" }, - { url = "https://files.pythonhosted.org/packages/78/e3/6690b3f85a05506733c7e90b577e4762517404ea78bab2ca3a5cb1aeb78d/numpy-2.3.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6936aff90dda378c09bea075af0d9c675fe3a977a9d2402f95a87f440f59f619", size = 12977811, upload-time = "2025-07-24T21:29:18.234Z" }, + { url = "https://files.pythonhosted.org/packages/7a/45/e80d203ef6b267aa29b22714fb558930b27960a0c5ce3c19c999232bb3eb/numpy-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0ffc4f5caba7dfcbe944ed674b7eef683c7e94874046454bb79ed7ee0236f59d", size = 21259253, upload-time = "2025-09-09T15:56:02.094Z" }, + { url = "https://files.pythonhosted.org/packages/52/18/cf2c648fccf339e59302e00e5f2bc87725a3ce1992f30f3f78c9044d7c43/numpy-2.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e7e946c7170858a0295f79a60214424caac2ffdb0063d4d79cb681f9aa0aa569", size = 14450980, upload-time = "2025-09-09T15:56:05.926Z" }, + { url = "https://files.pythonhosted.org/packages/93/fb/9af1082bec870188c42a1c239839915b74a5099c392389ff04215dcee812/numpy-2.3.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:cd4260f64bc794c3390a63bf0728220dd1a68170c169088a1e0dfa2fde1be12f", size = 5379709, upload-time = "2025-09-09T15:56:07.95Z" }, + { url = "https://files.pythonhosted.org/packages/75/0f/bfd7abca52bcbf9a4a65abc83fe18ef01ccdeb37bfb28bbd6ad613447c79/numpy-2.3.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:f0ddb4b96a87b6728df9362135e764eac3cfa674499943ebc44ce96c478ab125", size = 6913923, upload-time = "2025-09-09T15:56:09.443Z" }, + { url = "https://files.pythonhosted.org/packages/79/55/d69adad255e87ab7afda1caf93ca997859092afeb697703e2f010f7c2e55/numpy-2.3.3-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:afd07d377f478344ec6ca2b8d4ca08ae8bd44706763d1efb56397de606393f48", size = 14589591, upload-time = "2025-09-09T15:56:11.234Z" }, + { url = "https://files.pythonhosted.org/packages/10/a2/010b0e27ddeacab7839957d7a8f00e91206e0c2c47abbb5f35a2630e5387/numpy-2.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bc92a5dedcc53857249ca51ef29f5e5f2f8c513e22cfb90faeb20343b8c6f7a6", size = 16938714, upload-time = "2025-09-09T15:56:14.637Z" }, + { url = "https://files.pythonhosted.org/packages/1c/6b/12ce8ede632c7126eb2762b9e15e18e204b81725b81f35176eac14dc5b82/numpy-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7af05ed4dc19f308e1d9fc759f36f21921eb7bbfc82843eeec6b2a2863a0aefa", size = 16370592, upload-time = "2025-09-09T15:56:17.285Z" }, + { url = "https://files.pythonhosted.org/packages/b4/35/aba8568b2593067bb6a8fe4c52babb23b4c3b9c80e1b49dff03a09925e4a/numpy-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:433bf137e338677cebdd5beac0199ac84712ad9d630b74eceeb759eaa45ddf30", size = 18884474, upload-time = "2025-09-09T15:56:20.943Z" }, + { url = "https://files.pythonhosted.org/packages/45/fa/7f43ba10c77575e8be7b0138d107e4f44ca4a1ef322cd16980ea3e8b8222/numpy-2.3.3-cp311-cp311-win32.whl", hash = "sha256:eb63d443d7b4ffd1e873f8155260d7f58e7e4b095961b01c91062935c2491e57", size = 6599794, upload-time = "2025-09-09T15:56:23.258Z" }, + { url = "https://files.pythonhosted.org/packages/0a/a2/a4f78cb2241fe5664a22a10332f2be886dcdea8784c9f6a01c272da9b426/numpy-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:ec9d249840f6a565f58d8f913bccac2444235025bbb13e9a4681783572ee3caa", size = 13088104, upload-time = "2025-09-09T15:56:25.476Z" }, + { url = "https://files.pythonhosted.org/packages/79/64/e424e975adbd38282ebcd4891661965b78783de893b381cbc4832fb9beb2/numpy-2.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:74c2a948d02f88c11a3c075d9733f1ae67d97c6bdb97f2bb542f980458b257e7", size = 10460772, upload-time = "2025-09-09T15:56:27.679Z" }, + { url = "https://files.pythonhosted.org/packages/51/5d/bb7fc075b762c96329147799e1bcc9176ab07ca6375ea976c475482ad5b3/numpy-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cfdd09f9c84a1a934cde1eec2267f0a43a7cd44b2cca4ff95b7c0d14d144b0bf", size = 20957014, upload-time = "2025-09-09T15:56:29.966Z" }, + { url = "https://files.pythonhosted.org/packages/6b/0e/c6211bb92af26517acd52125a237a92afe9c3124c6a68d3b9f81b62a0568/numpy-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb32e3cf0f762aee47ad1ddc6672988f7f27045b0783c887190545baba73aa25", size = 14185220, upload-time = "2025-09-09T15:56:32.175Z" }, + { url = "https://files.pythonhosted.org/packages/22/f2/07bb754eb2ede9073f4054f7c0286b0d9d2e23982e090a80d478b26d35ca/numpy-2.3.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:396b254daeb0a57b1fe0ecb5e3cff6fa79a380fa97c8f7781a6d08cd429418fe", size = 5113918, upload-time = "2025-09-09T15:56:34.175Z" }, + { url = "https://files.pythonhosted.org/packages/81/0a/afa51697e9fb74642f231ea36aca80fa17c8fb89f7a82abd5174023c3960/numpy-2.3.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:067e3d7159a5d8f8a0b46ee11148fc35ca9b21f61e3c49fbd0a027450e65a33b", size = 6647922, upload-time = "2025-09-09T15:56:36.149Z" }, + { url = "https://files.pythonhosted.org/packages/5d/f5/122d9cdb3f51c520d150fef6e87df9279e33d19a9611a87c0d2cf78a89f4/numpy-2.3.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c02d0629d25d426585fb2e45a66154081b9fa677bc92a881ff1d216bc9919a8", size = 14281991, upload-time = "2025-09-09T15:56:40.548Z" }, + { url = "https://files.pythonhosted.org/packages/51/64/7de3c91e821a2debf77c92962ea3fe6ac2bc45d0778c1cbe15d4fce2fd94/numpy-2.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9192da52b9745f7f0766531dcfa978b7763916f158bb63bdb8a1eca0068ab20", size = 16641643, upload-time = "2025-09-09T15:56:43.343Z" }, + { url = "https://files.pythonhosted.org/packages/30/e4/961a5fa681502cd0d68907818b69f67542695b74e3ceaa513918103b7e80/numpy-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cd7de500a5b66319db419dc3c345244404a164beae0d0937283b907d8152e6ea", size = 16056787, upload-time = "2025-09-09T15:56:46.141Z" }, + { url = "https://files.pythonhosted.org/packages/99/26/92c912b966e47fbbdf2ad556cb17e3a3088e2e1292b9833be1dfa5361a1a/numpy-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:93d4962d8f82af58f0b2eb85daaf1b3ca23fe0a85d0be8f1f2b7bb46034e56d7", size = 18579598, upload-time = "2025-09-09T15:56:49.844Z" }, + { url = "https://files.pythonhosted.org/packages/17/b6/fc8f82cb3520768718834f310c37d96380d9dc61bfdaf05fe5c0b7653e01/numpy-2.3.3-cp312-cp312-win32.whl", hash = "sha256:5534ed6b92f9b7dca6c0a19d6df12d41c68b991cef051d108f6dbff3babc4ebf", size = 6320800, upload-time = "2025-09-09T15:56:52.499Z" }, + { url = "https://files.pythonhosted.org/packages/32/ee/de999f2625b80d043d6d2d628c07d0d5555a677a3cf78fdf868d409b8766/numpy-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:497d7cad08e7092dba36e3d296fe4c97708c93daf26643a1ae4b03f6294d30eb", size = 12786615, upload-time = "2025-09-09T15:56:54.422Z" }, + { url = "https://files.pythonhosted.org/packages/49/6e/b479032f8a43559c383acb20816644f5f91c88f633d9271ee84f3b3a996c/numpy-2.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:ca0309a18d4dfea6fc6262a66d06c26cfe4640c3926ceec90e57791a82b6eee5", size = 10195936, upload-time = "2025-09-09T15:56:56.541Z" }, + { url = "https://files.pythonhosted.org/packages/b8/f2/7e0a37cfced2644c9563c529f29fa28acbd0960dde32ece683aafa6f4949/numpy-2.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1e02c7159791cd481e1e6d5ddd766b62a4d5acf8df4d4d1afe35ee9c5c33a41e", size = 21131019, upload-time = "2025-09-09T15:58:42.838Z" }, + { url = "https://files.pythonhosted.org/packages/1a/7e/3291f505297ed63831135a6cc0f474da0c868a1f31b0dd9a9f03a7a0d2ed/numpy-2.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:dca2d0fc80b3893ae72197b39f69d55a3cd8b17ea1b50aa4c62de82419936150", size = 14376288, upload-time = "2025-09-09T15:58:45.425Z" }, + { url = "https://files.pythonhosted.org/packages/bf/4b/ae02e985bdeee73d7b5abdefeb98aef1207e96d4c0621ee0cf228ddfac3c/numpy-2.3.3-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:99683cbe0658f8271b333a1b1b4bb3173750ad59c0c61f5bbdc5b318918fffe3", size = 5305425, upload-time = "2025-09-09T15:58:48.6Z" }, + { url = "https://files.pythonhosted.org/packages/8b/eb/9df215d6d7250db32007941500dc51c48190be25f2401d5b2b564e467247/numpy-2.3.3-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:d9d537a39cc9de668e5cd0e25affb17aec17b577c6b3ae8a3d866b479fbe88d0", size = 6819053, upload-time = "2025-09-09T15:58:50.401Z" }, + { url = "https://files.pythonhosted.org/packages/57/62/208293d7d6b2a8998a4a1f23ac758648c3c32182d4ce4346062018362e29/numpy-2.3.3-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8596ba2f8af5f93b01d97563832686d20206d303024777f6dfc2e7c7c3f1850e", size = 14420354, upload-time = "2025-09-09T15:58:52.704Z" }, + { url = "https://files.pythonhosted.org/packages/ed/0c/8e86e0ff7072e14a71b4c6af63175e40d1e7e933ce9b9e9f765a95b4e0c3/numpy-2.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1ec5615b05369925bd1125f27df33f3b6c8bc10d788d5999ecd8769a1fa04db", size = 16760413, upload-time = "2025-09-09T15:58:55.027Z" }, + { url = "https://files.pythonhosted.org/packages/af/11/0cc63f9f321ccf63886ac203336777140011fb669e739da36d8db3c53b98/numpy-2.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:2e267c7da5bf7309670523896df97f93f6e469fb931161f483cd6882b3b1a5dc", size = 12971844, upload-time = "2025-09-09T15:58:57.359Z" }, ] [[package]] @@ -1261,7 +1263,6 @@ dependencies = [ { name = "cffi" }, { name = "crcmod" }, { name = "cython" }, - { name = "dearpygui" }, { name = "future-fstrings" }, { name = "inputs" }, { name = "json-rpc" }, @@ -1337,6 +1338,7 @@ testing = [ { name = "ruff" }, ] tools = [ + { name = "dearpygui" }, { name = "metadrive-simulator", marker = "platform_machine != 'aarch64'" }, ] @@ -1353,7 +1355,7 @@ requires-dist = [ { name = "crcmod" }, { name = "cython" }, { name = "dbus-next", marker = "extra == 'dev'" }, - { name = "dearpygui", specifier = ">=2.1.0" }, + { name = "dearpygui", marker = "extra == 'tools'", specifier = ">=2.1.0" }, { name = "dictdiffer", marker = "extra == 'dev'" }, { name = "future-fstrings" }, { name = "hypothesis", marker = "extra == 'testing'", specifier = "==6.47.*" }, @@ -1376,7 +1378,7 @@ requires-dist = [ { name = "psutil" }, { name = "pyaudio" }, { name = "pyautogui", marker = "extra == 'dev'" }, - { name = "pycapnp" }, + { name = "pycapnp", specifier = "==2.1.0" }, { name = "pycryptodome" }, { name = "pygame", marker = "extra == 'dev'" }, { name = "pyjwt" }, @@ -1397,7 +1399,7 @@ requires-dist = [ { name = "pywinctl", marker = "extra == 'dev'" }, { name = "pyzmq" }, { name = "qrcode" }, - { name = "raylib", marker = "extra == 'dev'" }, + { name = "raylib", marker = "extra == 'dev'", specifier = "<5.5.0.3" }, { name = "requests" }, { name = "ruff", marker = "extra == 'testing'" }, { name = "scons" }, @@ -1450,8 +1452,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 +1465,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 = [ @@ -1605,31 +1607,32 @@ wheels = [ [[package]] name = "protobuf" -version = "6.32.0" +version = "6.32.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c0/df/fb4a8eeea482eca989b51cffd274aac2ee24e825f0bf3cbce5281fa1567b/protobuf-6.32.0.tar.gz", hash = "sha256:a81439049127067fc49ec1d36e25c6ee1d1a2b7be930675f919258d03c04e7d2", size = 440614, upload-time = "2025-08-14T21:21:25.015Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/a4/cc17347aa2897568beece2e674674359f911d6fe21b0b8d6268cd42727ac/protobuf-6.32.1.tar.gz", hash = "sha256:ee2469e4a021474ab9baafea6cd070e5bf27c7d29433504ddea1a4ee5850f68d", size = 440635, upload-time = "2025-09-11T21:38:42.935Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/33/18/df8c87da2e47f4f1dcc5153a81cd6bca4e429803f4069a299e236e4dd510/protobuf-6.32.0-cp310-abi3-win32.whl", hash = "sha256:84f9e3c1ff6fb0308dbacb0950d8aa90694b0d0ee68e75719cb044b7078fe741", size = 424409, upload-time = "2025-08-14T21:21:12.366Z" }, - { url = "https://files.pythonhosted.org/packages/e1/59/0a820b7310f8139bd8d5a9388e6a38e1786d179d6f33998448609296c229/protobuf-6.32.0-cp310-abi3-win_amd64.whl", hash = "sha256:a8bdbb2f009cfc22a36d031f22a625a38b615b5e19e558a7b756b3279723e68e", size = 435735, upload-time = "2025-08-14T21:21:15.046Z" }, - { url = "https://files.pythonhosted.org/packages/cc/5b/0d421533c59c789e9c9894683efac582c06246bf24bb26b753b149bd88e4/protobuf-6.32.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d52691e5bee6c860fff9a1c86ad26a13afbeb4b168cd4445c922b7e2cf85aaf0", size = 426449, upload-time = "2025-08-14T21:21:16.687Z" }, - { url = "https://files.pythonhosted.org/packages/ec/7b/607764ebe6c7a23dcee06e054fd1de3d5841b7648a90fd6def9a3bb58c5e/protobuf-6.32.0-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:501fe6372fd1c8ea2a30b4d9be8f87955a64d6be9c88a973996cef5ef6f0abf1", size = 322869, upload-time = "2025-08-14T21:21:18.282Z" }, - { url = "https://files.pythonhosted.org/packages/40/01/2e730bd1c25392fc32e3268e02446f0d77cb51a2c3a8486b1798e34d5805/protobuf-6.32.0-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:75a2aab2bd1aeb1f5dc7c5f33bcb11d82ea8c055c9becbb41c26a8c43fd7092c", size = 322009, upload-time = "2025-08-14T21:21:19.893Z" }, - { url = "https://files.pythonhosted.org/packages/9c/f2/80ffc4677aac1bc3519b26bc7f7f5de7fce0ee2f7e36e59e27d8beb32dd1/protobuf-6.32.0-py3-none-any.whl", hash = "sha256:ba377e5b67b908c8f3072a57b63e2c6a4cbd18aea4ed98d2584350dbf46f2783", size = 169287, upload-time = "2025-08-14T21:21:23.515Z" }, + { url = "https://files.pythonhosted.org/packages/c0/98/645183ea03ab3995d29086b8bf4f7562ebd3d10c9a4b14ee3f20d47cfe50/protobuf-6.32.1-cp310-abi3-win32.whl", hash = "sha256:a8a32a84bc9f2aad712041b8b366190f71dde248926da517bde9e832e4412085", size = 424411, upload-time = "2025-09-11T21:38:27.427Z" }, + { url = "https://files.pythonhosted.org/packages/8c/f3/6f58f841f6ebafe076cebeae33fc336e900619d34b1c93e4b5c97a81fdfa/protobuf-6.32.1-cp310-abi3-win_amd64.whl", hash = "sha256:b00a7d8c25fa471f16bc8153d0e53d6c9e827f0953f3c09aaa4331c718cae5e1", size = 435738, upload-time = "2025-09-11T21:38:30.959Z" }, + { url = "https://files.pythonhosted.org/packages/10/56/a8a3f4e7190837139e68c7002ec749190a163af3e330f65d90309145a210/protobuf-6.32.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d8c7e6eb619ffdf105ee4ab76af5a68b60a9d0f66da3ea12d1640e6d8dab7281", size = 426454, upload-time = "2025-09-11T21:38:34.076Z" }, + { url = "https://files.pythonhosted.org/packages/3f/be/8dd0a927c559b37d7a6c8ab79034fd167dcc1f851595f2e641ad62be8643/protobuf-6.32.1-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:2f5b80a49e1eb7b86d85fcd23fe92df154b9730a725c3b38c4e43b9d77018bf4", size = 322874, upload-time = "2025-09-11T21:38:35.509Z" }, + { url = "https://files.pythonhosted.org/packages/5c/f6/88d77011b605ef979aace37b7703e4eefad066f7e84d935e5a696515c2dd/protobuf-6.32.1-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:b1864818300c297265c83a4982fd3169f97122c299f56a56e2445c3698d34710", size = 322013, upload-time = "2025-09-11T21:38:37.017Z" }, + { url = "https://files.pythonhosted.org/packages/97/b7/15cc7d93443d6c6a84626ae3258a91f4c6ac8c0edd5df35ea7658f71b79c/protobuf-6.32.1-py3-none-any.whl", hash = "sha256:2601b779fc7d32a866c6b4404f9d42a3f67c5b9f3f15b4db3cccabe06b95c346", size = 169289, upload-time = "2025-09-11T21:38:41.234Z" }, ] [[package]] name = "psutil" -version = "7.0.0" +version = "7.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2a/80/336820c1ad9286a4ded7e845b2eccfcb27851ab8ac6abece774a6ff4d3de/psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456", size = 497003, upload-time = "2025-02-13T21:54:07.946Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/31/4723d756b59344b643542936e37a31d1d3204bcdc42a7daa8ee9eb06fb50/psutil-7.1.0.tar.gz", hash = "sha256:655708b3c069387c8b77b072fc429a57d0e214221d01c0a772df7dfedcb3bcd2", size = 497660, upload-time = "2025-09-17T20:14:52.902Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ed/e6/2d26234410f8b8abdbf891c9da62bee396583f713fb9f3325a4760875d22/psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25", size = 238051, upload-time = "2025-02-13T21:54:12.36Z" }, - { url = "https://files.pythonhosted.org/packages/04/8b/30f930733afe425e3cbfc0e1468a30a18942350c1a8816acfade80c005c4/psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da", size = 239535, upload-time = "2025-02-13T21:54:16.07Z" }, - { url = "https://files.pythonhosted.org/packages/2a/ed/d362e84620dd22876b55389248e522338ed1bf134a5edd3b8231d7207f6d/psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91", size = 275004, upload-time = "2025-02-13T21:54:18.662Z" }, - { url = "https://files.pythonhosted.org/packages/bf/b9/b0eb3f3cbcb734d930fdf839431606844a825b23eaf9a6ab371edac8162c/psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34", size = 277986, upload-time = "2025-02-13T21:54:21.811Z" }, - { url = "https://files.pythonhosted.org/packages/eb/a2/709e0fe2f093556c17fbafda93ac032257242cabcc7ff3369e2cb76a97aa/psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993", size = 279544, upload-time = "2025-02-13T21:54:24.68Z" }, - { url = "https://files.pythonhosted.org/packages/50/e6/eecf58810b9d12e6427369784efe814a1eec0f492084ce8eb8f4d89d6d61/psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99", size = 241053, upload-time = "2025-02-13T21:54:34.31Z" }, - { url = "https://files.pythonhosted.org/packages/50/1b/6921afe68c74868b4c9fa424dad3be35b095e16687989ebbb50ce4fceb7c/psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553", size = 244885, upload-time = "2025-02-13T21:54:37.486Z" }, + { url = "https://files.pythonhosted.org/packages/46/62/ce4051019ee20ce0ed74432dd73a5bb087a6704284a470bb8adff69a0932/psutil-7.1.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:76168cef4397494250e9f4e73eb3752b146de1dd950040b29186d0cce1d5ca13", size = 245242, upload-time = "2025-09-17T20:14:56.126Z" }, + { url = "https://files.pythonhosted.org/packages/38/61/f76959fba841bf5b61123fbf4b650886dc4094c6858008b5bf73d9057216/psutil-7.1.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:5d007560c8c372efdff9e4579c2846d71de737e4605f611437255e81efcca2c5", size = 246682, upload-time = "2025-09-17T20:14:58.25Z" }, + { url = "https://files.pythonhosted.org/packages/88/7a/37c99d2e77ec30d63398ffa6a660450b8a62517cabe44b3e9bae97696e8d/psutil-7.1.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22e4454970b32472ce7deaa45d045b34d3648ce478e26a04c7e858a0a6e75ff3", size = 287994, upload-time = "2025-09-17T20:14:59.901Z" }, + { url = "https://files.pythonhosted.org/packages/9d/de/04c8c61232f7244aa0a4b9a9fbd63a89d5aeaf94b2fc9d1d16e2faa5cbb0/psutil-7.1.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c70e113920d51e89f212dd7be06219a9b88014e63a4cec69b684c327bc474e3", size = 291163, upload-time = "2025-09-17T20:15:01.481Z" }, + { url = "https://files.pythonhosted.org/packages/f4/58/c4f976234bf6d4737bc8c02a81192f045c307b72cf39c9e5c5a2d78927f6/psutil-7.1.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7d4a113425c037300de3ac8b331637293da9be9713855c4fc9d2d97436d7259d", size = 293625, upload-time = "2025-09-17T20:15:04.492Z" }, + { url = "https://files.pythonhosted.org/packages/79/87/157c8e7959ec39ced1b11cc93c730c4fb7f9d408569a6c59dbd92ceb35db/psutil-7.1.0-cp37-abi3-win32.whl", hash = "sha256:09ad740870c8d219ed8daae0ad3b726d3bf9a028a198e7f3080f6a1888b99bca", size = 244812, upload-time = "2025-09-17T20:15:07.462Z" }, + { url = "https://files.pythonhosted.org/packages/bf/e9/b44c4f697276a7a95b8e94d0e320a7bf7f3318521b23de69035540b39838/psutil-7.1.0-cp37-abi3-win_amd64.whl", hash = "sha256:57f5e987c36d3146c0dd2528cd42151cf96cd359b9d67cfff836995cc5df9a3d", size = 247965, upload-time = "2025-09-17T20:15:09.673Z" }, + { url = "https://files.pythonhosted.org/packages/26/65/1070a6e3c036f39142c2820c4b52e9243246fcfc3f96239ac84472ba361e/psutil-7.1.0-cp37-abi3-win_arm64.whl", hash = "sha256:6937cb68133e7c97b6cc9649a570c9a18ba0efebed46d8c5dae4c07fa1b67a07", size = 244971, upload-time = "2025-09-17T20:15:12.262Z" }, ] [[package]] @@ -1662,47 +1665,43 @@ sdist = { url = "https://files.pythonhosted.org/packages/65/ff/cdae0a8c2118a0de7 [[package]] name = "pycapnp" -version = "2.0.0" +version = "2.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9b/fb/54b46b52c1fa2acd9afd81bd05810c61bb1b05c6084c9625b64bc6d41843/pycapnp-2.0.0.tar.gz", hash = "sha256:503ab9b7b16773590ee226f2460408972c6b1c2cb2d819037115b919bef682be", size = 574848, upload-time = "2024-04-12T15:35:44.019Z" } +sdist = { url = "https://files.pythonhosted.org/packages/15/86/a57e3c92acd3e1d2fc3dcad683ada191f722e4ac927e1a384b228ec2780a/pycapnp-2.1.0.tar.gz", hash = "sha256:69cc3d861fee1c9b26c73ad2e8a5d51e76ad87e4ff9be33a4fd2fc72f5846aec", size = 689734, upload-time = "2025-09-05T03:50:40.851Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/82/cf311b1a9800b605759a38a0c337a55a639b685427364294e98a0f9b7306/pycapnp-2.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:829c7eb4e5f23dbcac25466110faf72a691035cf87c5d46e5053da15790e428d", size = 1673673, upload-time = "2024-04-12T15:33:32.211Z" }, - { url = "https://files.pythonhosted.org/packages/ae/55/4c03ca95c568776a1f637db9ffdcf302fb63f46e4d2a4f13edd8cb1a5f90/pycapnp-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dab60fbe3e4eaf99ec97918a0a776216c6c149b6d49261383d91c2201adb475d", size = 1513351, upload-time = "2024-04-12T15:33:35.156Z" }, - { url = "https://files.pythonhosted.org/packages/55/98/e4b2dea076f8a2575abc45cd879a91bc9aa975c69ae2ac1cab61d83c5087/pycapnp-2.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c48a0582078bb74d7326d28571db0b8e6919563365537a5a13e8f5360c12bfc", size = 4910666, upload-time = "2024-04-12T15:33:37.798Z" }, - { url = "https://files.pythonhosted.org/packages/1d/ee/3b5a182588f89074f4002fa6247e3f963bb85bc808acd1ac8deed91f1fa7/pycapnp-2.0.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bcb5ab54aff857e3711d2c0cc934194aaffacdeb3481daa56863daef07d27941", size = 5007434, upload-time = "2024-04-12T15:33:40.362Z" }, - { url = "https://files.pythonhosted.org/packages/c5/e9/515a2ca7fdc84d57c654280d0b71dfd782fd1773d384c0ec0d56dc6fc35b/pycapnp-2.0.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9600778036e6fe9dbea68f0c37678c5f4d561d2f2306b3cb741de5e1670ef2ae", size = 5188923, upload-time = "2024-04-12T15:33:42.33Z" }, - { url = "https://files.pythonhosted.org/packages/70/60/5db346e238985a526ba7589ed24f92195dad39e7dec9d85b17a567600b6f/pycapnp-2.0.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7278ba0262fab8c398e77d634ae7ba026866d44b52cbfc27262be8d396ecacd1", size = 5048105, upload-time = "2024-04-12T15:33:44.294Z" }, - { url = "https://files.pythonhosted.org/packages/e3/ff/02b4a87c9ff9793f26d8f3d95312d902d260c094f216d84e19528a506606/pycapnp-2.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23b2458d43c82302980a96518c96df257429204d2cc02bfff0c8cb6ebb371e01", size = 5063172, upload-time = "2024-04-12T15:33:46.954Z" }, - { url = "https://files.pythonhosted.org/packages/10/a1/35a7e14d765f99cfdcdfdcebc69bdf382f27016944470daa7a03c557f681/pycapnp-2.0.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dd7755cc3fedc2ad8cc7864a0729471ddeff10c184963fe0f3689e295130f1b2", size = 5408718, upload-time = "2024-04-12T15:33:49.587Z" }, - { url = "https://files.pythonhosted.org/packages/5c/59/8bc8a993c38808c6fd90b10becba8de4a54543e8441bd87ce44ef3b7eee4/pycapnp-2.0.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d47baf6b3db9981625ffc5ff188e089f2ebca8e7e1afb97aa5eb7bebb7bf3650", size = 5596714, upload-time = "2024-04-12T15:33:51.518Z" }, - { url = "https://files.pythonhosted.org/packages/ea/7d/79c481ef77f29e81355e92bb250f0d2a37a76f5fe0ba9433bf6c6c88b6e4/pycapnp-2.0.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:b375be92d93fdb6f7ac127ea9390bcec0fed4e485db137b084f9e7114dde7c83", size = 5709896, upload-time = "2024-04-12T15:33:53.716Z" }, - { url = "https://files.pythonhosted.org/packages/59/8d/f2eceeea1e8cae8b8a70a4752af5b772916f455e2ed388d0887e2b57d080/pycapnp-2.0.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:959bfdf1cddb3e5528e2293c4a375382be9a1bf044b073bc2e7eca1eb6b3a9a2", size = 5594823, upload-time = "2024-04-12T15:33:55.573Z" }, - { url = "https://files.pythonhosted.org/packages/2c/86/f8284637b61f83232e5618dd561a66080dd98ce2272d7e3ae89335d4fd97/pycapnp-2.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d873af167cf5cc7578ce5432eefcb442f866c8f7a6c57d188baf8c5e709fa39d", size = 5572564, upload-time = "2024-04-12T15:33:59.112Z" }, - { url = "https://files.pythonhosted.org/packages/b3/b2/7f99d28a9935d1e37ec6955922c57b2be24fe0b74fe25929643686cc11e5/pycapnp-2.0.0-cp311-cp311-win32.whl", hash = "sha256:40ca8018e0b7686d549b920f087049b92a3e6f06976d9f5a8112603fc560cac4", size = 1040268, upload-time = "2024-04-12T15:34:00.933Z" }, - { url = "https://files.pythonhosted.org/packages/1d/37/89ab98961f18cffeae20d98cfc24afcfa85024bc014ecc48b0c4ac264fe0/pycapnp-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:d15cd8e46d541a899c84809095d7d7b3951f43642d1859e7a39bd91910778479", size = 1141758, upload-time = "2024-04-12T15:34:02.607Z" }, - { url = "https://files.pythonhosted.org/packages/ce/d5/0ee84de3ce34a86c373b6cfbea17d5486c2ca942d51efa99a0069723c1e3/pycapnp-2.0.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:0c111ef96676df25b8afef98f369d45f838ad4434e2898e48199eb43ef704efe", size = 1645816, upload-time = "2024-04-12T15:34:04.428Z" }, - { url = "https://files.pythonhosted.org/packages/35/1e/580572083165ba791fac5ae2d8917facb94db6e3f0500421673f55165dac/pycapnp-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0d18906eb1fd1b9f206d93a9591ceedce1d52e7766b66e68f271453f104e9dca", size = 1507892, upload-time = "2024-04-12T15:34:06.933Z" }, - { url = "https://files.pythonhosted.org/packages/d7/ed/46b3cc5d32c525b6a3acb67eb43de2cec692a62775ec1ab66dafe2b7d6ad/pycapnp-2.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5d1ed365ab1beabb8838068907a7190cc0b6f16de3499d783627e670fcc0eb2", size = 4707960, upload-time = "2024-04-12T15:34:08.771Z" }, - { url = "https://files.pythonhosted.org/packages/8e/51/0a0a4d4e44138adb84959478ea4966196c5ad32022f768b9b64d1590cb3e/pycapnp-2.0.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:495b39a7aa2629931bbca27ad743ce591c6c41e8f81792276be424742d9cd1c1", size = 4791780, upload-time = "2024-04-12T15:34:10.863Z" }, - { url = "https://files.pythonhosted.org/packages/28/71/2b59c6ddb253b25b3d01ee6f7b32b0297ac205c7272beeb6d13399054430/pycapnp-2.0.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50e814fbde072dcc3d868b5b5cbb9b7a66a70bff9ad03942f3be9baf3ca1cfc6", size = 4961068, upload-time = "2024-04-12T15:34:13.543Z" }, - { url = "https://files.pythonhosted.org/packages/c3/b8/b64fdefa59d6d2802b5ee0a9439396c23a3e5954da6909be81f2722a234c/pycapnp-2.0.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:920fdda62d5fdef7a48339104dff0ceb9dcc21b138491f854457ba3a3d4d63ec", size = 4872917, upload-time = "2024-04-12T15:34:15.636Z" }, - { url = "https://files.pythonhosted.org/packages/c8/55/867595f575eb6cb3662e9a0b50a24b4be42df86f2938003e586f6c81606f/pycapnp-2.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f9142eb4714c152b09dda0b055ea9dd43fd8fd894132e7eb4fa235fb4915edd", size = 4912169, upload-time = "2024-04-12T15:34:17.758Z" }, - { url = "https://files.pythonhosted.org/packages/e4/11/0d36b45e5005ecdf8510081d16c6fb7b22b49651f64af36d138df97980cc/pycapnp-2.0.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c98f1d0c4d32109d03e42828ce3c65236afc895033633cbed3ca092993702e7b", size = 5201744, upload-time = "2024-04-12T15:34:20.468Z" }, - { url = "https://files.pythonhosted.org/packages/05/29/ad1357998656b7141939e55bb3aea727c7a5478026feed7f8ee8cf52c935/pycapnp-2.0.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4d3250c1875a309d67551843cd8bf3c5e7fccf159b7f5c118a92aee36c0e871c", size = 5351113, upload-time = "2024-04-12T15:34:23.173Z" }, - { url = "https://files.pythonhosted.org/packages/5a/b1/f4c442907948a29b6427dd7436f31d3732bb0d77f5c1dbcad749ba56dac0/pycapnp-2.0.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:174e6babe01f5507111c0ed226cd0b5e9325a9d2850751cfe4a57c1670f13881", size = 5472055, upload-time = "2024-04-12T15:34:25.799Z" }, - { url = "https://files.pythonhosted.org/packages/c1/06/a6eceb8b8015f518c0ccae1de5d1a6e18ed73b62b4b111aff54ce5f4f566/pycapnp-2.0.0-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:ed38ece414341285695526792e020f391f29f5064b2126d0367c8bdeef28e3e9", size = 5395743, upload-time = "2024-04-12T15:34:28.134Z" }, - { url = "https://files.pythonhosted.org/packages/e7/b0/63f2b0327853ae08158de61b4dfc7fa43ae5a5c00f1d28f769e7c30cdf55/pycapnp-2.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8a20b7dc55ef83a1fa446bf12680bce25caeb8f81788b623b072c3ec820db50d", size = 5405076, upload-time = "2024-04-12T15:34:30.305Z" }, - { url = "https://files.pythonhosted.org/packages/7d/24/e025dd95f1abf34e373fbab8841ac8e5fa62afe3af4a4b0c61bd01354400/pycapnp-2.0.0-cp312-cp312-win32.whl", hash = "sha256:145eea66233fb5ac9152cd1c06b999ddb691815126f87f5cc37b9cda5d569f8a", size = 1030361, upload-time = "2024-04-12T15:34:32.25Z" }, - { url = "https://files.pythonhosted.org/packages/3f/70/a71108ee9d4db9a027b665a2c383202407207174f1956195d5be45aca705/pycapnp-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:b8b03000769b29b36a8810f458b931f0f706f42027ee6676821eff28092d7734", size = 1135121, upload-time = "2024-04-12T15:34:34.208Z" }, + { url = "https://files.pythonhosted.org/packages/68/7c/934750a0ca77431a22e68e11521dcc6b801bea3ff37331d6a519e5ad142e/pycapnp-2.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:efacc439ec287d9e8a0ebf01a515404eff795659401e65ba6f1819c7b24f4380", size = 1628855, upload-time = "2025-09-05T03:48:32.317Z" }, + { url = "https://files.pythonhosted.org/packages/2e/a2/fd2c10b3f2e5010c747aa946b27fe09f665d65d5dc2afdd31838a3ef2f5d/pycapnp-2.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f3d8af535a8b44dfd71731a191386c6b821b8a4915806948893d18c79f547a8e", size = 1496942, upload-time = "2025-09-05T03:48:34.905Z" }, + { url = "https://files.pythonhosted.org/packages/0b/8a/42bd0e4c094ef534ac6890d34adae580cbbf5b0497fc0a6340bea833a617/pycapnp-2.1.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:117d1d5ebfc08cc189aca4f771b34fedc1291a3f9417167bd2d9b2a4e607e640", size = 5200170, upload-time = "2025-09-05T03:48:36.502Z" }, + { url = "https://files.pythonhosted.org/packages/af/30/2e92268383135082191c3dea4a9ad184d20b7fb2dda1477fd6ee520fd88e/pycapnp-2.1.0-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:d881ccc69e381863a88c7b6c7092a6baecb6dfc8c5558d66bc967c7f778fe7bc", size = 5684026, upload-time = "2025-09-05T03:48:38.063Z" }, + { url = "https://files.pythonhosted.org/packages/46/9c/bca1cbd7711c9c0f0f62ca95a49835369a61c4f6527a6900c8982045bf2f/pycapnp-2.1.0-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:8a4ea330e38ba83f6f03fbdc1f58642eb53e6f6f66734a426fa592dc988d70e9", size = 5709307, upload-time = "2025-09-05T03:48:40.127Z" }, + { url = "https://files.pythonhosted.org/packages/2d/29/cd14676d992c7b166baa7e022b369c15240d408b202410d105b23b25f737/pycapnp-2.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:fb2563de4619d359820de9d47b4704e4f7eda193ffc4a56e39cdcd2c8301c336", size = 5386505, upload-time = "2025-09-05T03:48:41.785Z" }, + { url = "https://files.pythonhosted.org/packages/ae/dd/2fc57cebe9be7e4cd3d6aec0b9c8a0db9772c1b17c37cfe4f04c050422cf/pycapnp-2.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5265d1ae34f9c089fa6983f6c1be404ce480c82b927017290bd703328fa3f5df", size = 6095180, upload-time = "2025-09-05T03:48:43.795Z" }, + { url = "https://files.pythonhosted.org/packages/5a/16/da8c1ada7770a532c859df475533eec5a1b2f5e81a269466a2fe670c5747/pycapnp-2.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b0a56370a868f752375a785bfb7e06b55cbe71605972615d1220c380bc452380", size = 6603414, upload-time = "2025-09-05T03:48:45.457Z" }, + { url = "https://files.pythonhosted.org/packages/f0/e6/a36eacaf2da6a5ac9c6565600e559edf95115ff990aa3379aee8dd7ba4fe/pycapnp-2.1.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5d7403c25275cf4badf6f9d0c07b1cb94fcdd599df81aba9b621c32b3dcefae9", size = 6621440, upload-time = "2025-09-05T03:48:47.706Z" }, + { url = "https://files.pythonhosted.org/packages/81/54/9150c03638cf4ecdf1664867382d0049146c658d6de30f189817c502df1a/pycapnp-2.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dea5d0d250fe4851b42cd380a207d773ebae76a990e542a888a5f1442f4c247e", size = 6354219, upload-time = "2025-09-05T03:48:49.336Z" }, + { url = "https://files.pythonhosted.org/packages/66/3e/e49ba2d74456d53b570c8d30a660c3b29ecfea075d5dd663132ff9049f19/pycapnp-2.1.0-cp311-cp311-win32.whl", hash = "sha256:593844c3cd92937eb5e7cd47ea3a62cde2d49a1fc05dba644f513c68f60f1318", size = 1053647, upload-time = "2025-09-05T03:48:51.108Z" }, + { url = "https://files.pythonhosted.org/packages/53/de/2b61908dc6abf25b17fed6b5a3b42a2226ec09467a3944f1d845ac29ef9b/pycapnp-2.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:ac13dd30062bb9985ae9ec4feca106af2b4fdac6468a09c7b74ad754f3921a06", size = 1208911, upload-time = "2025-09-05T03:48:53.219Z" }, + { url = "https://files.pythonhosted.org/packages/74/0e/66b41ba600e5f2523e900b7cc0d2e8496b397a1f2d6a5b7b323ab83418b7/pycapnp-2.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2d2ec561bc948d11f64f43bf9601bede5d6a603d105ae311bd5583c7130624a4", size = 1619223, upload-time = "2025-09-05T03:48:54.64Z" }, + { url = "https://files.pythonhosted.org/packages/40/6e/9bcb30180bd40cb0534124ff7f8ba8746a735018d593f608bf40c97821c0/pycapnp-2.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:132cd97f57f6b6636323ca9b68d389dd90b96e87af38cde31e2b5c5a064f277e", size = 1484321, upload-time = "2025-09-05T03:48:55.85Z" }, + { url = "https://files.pythonhosted.org/packages/14/0a/9ee1c9ecaff499e4fd1df2f0335bc20f666ec6ce5cd80f8ab055007f3c9b/pycapnp-2.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:568e79268ba7c02a71fe558a8aec1ae3c0f0e6aff809ff618a46afe4964957d2", size = 5143502, upload-time = "2025-09-05T03:48:57.733Z" }, + { url = "https://files.pythonhosted.org/packages/4d/50/65837e1416f7a8861ca1e8fe4582a5aef37192d7ef5e2ecfe46880bfdf9c/pycapnp-2.1.0-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:bcbf6f882d78d368c8e4bb792295392f5c4d71ddffa13a48da27e7bd47b99e37", size = 5508134, upload-time = "2025-09-05T03:48:59.383Z" }, + { url = "https://files.pythonhosted.org/packages/a1/59/46df6db800e77dbc3cc940723fb3fd7bc837327c858edf464a0f904bf547/pycapnp-2.1.0-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:dc25b96e393410dde25c61c1df3ce644700ef94826c829426d58c2c6b3e2d2f5", size = 5631794, upload-time = "2025-09-05T03:49:03.511Z" }, + { url = "https://files.pythonhosted.org/packages/63/9d/18e978500d5f6bd8d152f4d6919e3cfb83ead8a71c14613bbb54322df8b9/pycapnp-2.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:48938e0436ab1be615fc0a41434119a2065490a6212b9a5e56949e89b0588b76", size = 5369378, upload-time = "2025-09-05T03:49:05.539Z" }, + { url = "https://files.pythonhosted.org/packages/96/dc/726f1917e9996dc29f9fd1cf30674a14546cdbdfa0777e1982b6bd1ad628/pycapnp-2.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0c20de0f6e0b3fa9fa1df3864cf46051db3511b63bc29514d1092af65f2b82a0", size = 5999140, upload-time = "2025-09-05T03:49:07.341Z" }, + { url = "https://files.pythonhosted.org/packages/fd/3a/3bbc4c5776fc32fbf8a59df5c7c5810efd292b933cd6545eb4b16d896268/pycapnp-2.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:18caca6527862475167c10ea0809531130585aa8a86cc76cd1629eb87ee30637", size = 6454308, upload-time = "2025-09-05T03:49:08.998Z" }, + { url = "https://files.pythonhosted.org/packages/bf/dd/17e2d7808424f10ffddc47329b980488ed83ec716c504791787e593a7a93/pycapnp-2.1.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9dcc11237697007b66e3bfc500d2ad892bd79672c9b50d61fbf728c6aaf936de", size = 6544212, upload-time = "2025-09-05T03:49:10.675Z" }, + { url = "https://files.pythonhosted.org/packages/6a/5b/68090013128d7853f34c43828dd4dc80a7c8516fd1b56057b134e1e4c2c0/pycapnp-2.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c151edf78155b6416e7cb31e2e333d302d742ba52bb37d4dbdf71e75cc999d46", size = 6295279, upload-time = "2025-09-05T03:49:12.712Z" }, + { url = "https://files.pythonhosted.org/packages/5b/52/7d85212b4fcf127588888f71d3dbf5558ee7dc302eba760b12b1b325f9a3/pycapnp-2.1.0-cp312-cp312-win32.whl", hash = "sha256:c09b28419321dafafc644d60c57ff8ccaf3c3e686801b6060c612a7a3c580944", size = 1038995, upload-time = "2025-09-05T03:49:14.165Z" }, + { url = "https://files.pythonhosted.org/packages/f2/12/25d283ebf5c28717364647672e7494dc46196ca7a662f5420e4866f45687/pycapnp-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:560cb69cc02b0347e85b0629e4c2f0a316240900aa905392f9df6bab0a359989", size = 1176620, upload-time = "2025-09-05T03:49:15.545Z" }, ] [[package]] name = "pycparser" -version = "2.22" +version = "2.23" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736, upload-time = "2024-03-30T13:22:22.564Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552, upload-time = "2024-03-30T13:22:20.476Z" }, + { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, ] [[package]] @@ -1828,9 +1827,12 @@ wheels = [ [[package]] name = "pymsgbox" -version = "1.0.9" +version = "2.0.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7d/ff/4c6f31a4f08979f12a663f2aeb6c8b765d3bd592e66eaaac445f547bb875/PyMsgBox-1.0.9.tar.gz", hash = "sha256:2194227de8bff7a3d6da541848705a155dcbb2a06ee120d9f280a1d7f51263ff", size = 18829, upload-time = "2020-10-11T01:51:43.227Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/6a/e80da7594ee598a776972d09e2813df2b06b3bc29218f440631dfa7c78a8/pymsgbox-2.0.1.tar.gz", hash = "sha256:98d055c49a511dcc10fa08c3043e7102d468f5e4b3a83c6d3c61df722c7d798d", size = 20768, upload-time = "2025-09-09T00:38:56.863Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6f/3e/08c8cac81b2b2f7502746e6b9c8e5b0ec6432cd882c605560fc409aaf087/pymsgbox-2.0.1-py3-none-any.whl", hash = "sha256:5de8ec19bca2ca7e6c09d39c817c83f17c75cee80275235f43a9931db699f73b", size = 9994, upload-time = "2025-09-09T00:38:55.672Z" }, +] [[package]] name = "pyobjc" @@ -4201,9 +4203,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 = [ @@ -4233,18 +4235,21 @@ wheels = [ [[package]] name = "pyparsing" -version = "3.2.3" +version = "3.2.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bb/22/f1129e69d94ffff626bdb5c835506b3a5b4f3d070f17ea295e12c2c6f60f/pyparsing-3.2.3.tar.gz", hash = "sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be", size = 1088608, upload-time = "2025-03-25T05:01:28.114Z" } +sdist = { url = "https://files.pythonhosted.org/packages/98/c9/b4594e6a81371dfa9eb7a2c110ad682acf985d96115ae8b25a1d63b4bf3b/pyparsing-3.2.4.tar.gz", hash = "sha256:fff89494f45559d0f2ce46613b419f632bbb6afbdaed49696d322bcf98a58e99", size = 1098809, upload-time = "2025-09-13T05:47:19.732Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/05/e7/df2285f3d08fee213f2d041540fa4fc9ca6c2d44cf36d3a035bf2a8d2bcc/pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf", size = 111120, upload-time = "2025-03-25T05:01:24.908Z" }, + { url = "https://files.pythonhosted.org/packages/53/b8/fbab973592e23ae313042d450fc26fa24282ebffba21ba373786e1ce63b4/pyparsing-3.2.4-py3-none-any.whl", hash = "sha256:91d0fcde680d42cd031daf3a6ba20da3107e08a75de50da58360e7d94ab24d36", size = 113869, upload-time = "2025-09-13T05:47:17.863Z" }, ] [[package]] name = "pyperclip" -version = "1.9.0" +version = "1.10.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/30/23/2f0a3efc4d6a32f3b63cdff36cd398d9701d26cda58e3ab97ac79fb5e60d/pyperclip-1.9.0.tar.gz", hash = "sha256:b7de0142ddc81bfc5c7507eea19da920b92252b548b96186caf94a5e2527d310", size = 20961, upload-time = "2024-06-18T20:38:48.401Z" } +sdist = { url = "https://files.pythonhosted.org/packages/15/99/25f4898cf420efb6f45f519de018f4faea5391114a8618b16736ef3029f1/pyperclip-1.10.0.tar.gz", hash = "sha256:180c8346b1186921c75dfd14d9048a6b5d46bfc499778811952c6dd6eb1ca6be", size = 12193, upload-time = "2025-09-18T00:54:00.384Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/bc/22540e73c5f5ae18f02924cd3954a6c9a4aa6b713c841a94c98335d333a1/pyperclip-1.10.0-py3-none-any.whl", hash = "sha256:596fbe55dc59263bff26e61d2afbe10223e2fccb5210c9c96a28d6887cfcc7ec", size = 11062, upload-time = "2025-09-18T00:53:59.252Z" }, +] [[package]] name = "pyprof2calltree" @@ -4278,7 +4283,7 @@ wheels = [ [[package]] name = "pytest" -version = "8.4.1" +version = "8.4.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -4287,21 +4292,22 @@ dependencies = [ { name = "pluggy" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714, upload-time = "2025-06-18T05:48:06.109Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" }, + { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, ] [[package]] name = "pytest-asyncio" -version = "1.1.0" +version = "1.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pytest" }, + { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4e/51/f8794af39eeb870e87a8c8068642fc07bce0c854d6865d7dd0f2a9d338c2/pytest_asyncio-1.1.0.tar.gz", hash = "sha256:796aa822981e01b68c12e4827b8697108f7205020f24b5793b3c41555dab68ea", size = 46652, upload-time = "2025-07-16T04:29:26.393Z" } +sdist = { url = "https://files.pythonhosted.org/packages/42/86/9e3c5f48f7b7b638b216e4b9e645f54d199d7abbbab7a64a13b4e12ba10f/pytest_asyncio-1.2.0.tar.gz", hash = "sha256:c609a64a2a8768462d0c99811ddb8bd2583c33fd33cf7f21af1c142e824ffb57", size = 50119, upload-time = "2025-09-12T07:33:53.816Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/9d/bf86eddabf8c6c9cb1ea9a869d6873b46f105a5d292d3a6f7071f5b07935/pytest_asyncio-1.1.0-py3-none-any.whl", hash = "sha256:5fe2d69607b0bd75c656d1211f969cadba035030156745ee09e7d71740e58ecf", size = 15157, upload-time = "2025-07-16T04:29:24.929Z" }, + { url = "https://files.pythonhosted.org/packages/04/93/2fa34714b7a4ae72f2f8dad66ba17dd9a2c793220719e736dda28b7aec27/pytest_asyncio-1.2.0-py3-none-any.whl", hash = "sha256:8e17ae5e46d8e7efe51ab6494dd2010f4ca8dae51652aa3c8d55acf50bfb2e99", size = 15095, upload-time = "2025-09-12T07:33:52.639Z" }, ] [[package]] @@ -4318,26 +4324,26 @@ wheels = [ [[package]] name = "pytest-mock" -version = "3.14.1" +version = "3.15.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/71/28/67172c96ba684058a4d24ffe144d64783d2a270d0af0d9e792737bddc75c/pytest_mock-3.14.1.tar.gz", hash = "sha256:159e9edac4c451ce77a5cdb9fc5d1100708d2dd4ba3c3df572f14097351af80e", size = 33241, upload-time = "2025-05-26T13:58:45.167Z" } +sdist = { url = "https://files.pythonhosted.org/packages/68/14/eb014d26be205d38ad5ad20d9a80f7d201472e08167f0bb4361e251084a9/pytest_mock-3.15.1.tar.gz", hash = "sha256:1849a238f6f396da19762269de72cb1814ab44416fa73a8686deac10b0d87a0f", size = 34036, upload-time = "2025-09-16T16:37:27.081Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/05/77b60e520511c53d1c1ca75f1930c7dd8e971d0c4379b7f4b3f9644685ba/pytest_mock-3.14.1-py3-none-any.whl", hash = "sha256:178aefcd11307d874b4cd3100344e7e2d888d9791a6a1d9bfe90fbc1b74fd1d0", size = 9923, upload-time = "2025-05-26T13:58:43.487Z" }, + { url = "https://files.pythonhosted.org/packages/5a/cc/06253936f4a7fa2e0f48dfe6d851d9c56df896a9ab09ac019d70b760619c/pytest_mock-3.15.1-py3-none-any.whl", hash = "sha256:0a25e2eb88fe5168d535041d09a4529a188176ae608a6d249ee65abc0949630d", size = 10095, upload-time = "2025-09-16T16:37:25.734Z" }, ] [[package]] name = "pytest-randomly" -version = "3.16.0" +version = "4.0.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c0/68/d221ed7f4a2a49a664da721b8e87b52af6dd317af2a6cb51549cf17ac4b8/pytest_randomly-3.16.0.tar.gz", hash = "sha256:11bf4d23a26484de7860d82f726c0629837cf4064b79157bd18ec9d41d7feb26", size = 13367, upload-time = "2024-10-25T15:45:34.274Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c4/1d/258a4bf1109258c00c35043f40433be5c16647387b6e7cd5582d638c116b/pytest_randomly-4.0.1.tar.gz", hash = "sha256:174e57bb12ac2c26f3578188490bd333f0e80620c3f47340158a86eca0593cd8", size = 14130, upload-time = "2025-09-12T15:23:00.085Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/22/70/b31577d7c46d8e2f9baccfed5067dd8475262a2331ffb0bfdf19361c9bde/pytest_randomly-3.16.0-py3-none-any.whl", hash = "sha256:8633d332635a1a0983d3bba19342196807f6afb17c3eef78e02c2f85dade45d6", size = 8396, upload-time = "2024-10-25T15:45:32.78Z" }, + { url = "https://files.pythonhosted.org/packages/33/3e/a4a9227807b56869790aad3e24472a554b585974fe7e551ea350f50897ae/pytest_randomly-4.0.1-py3-none-any.whl", hash = "sha256:e0dfad2fd4f35e07beff1e47c17fbafcf98f9bf4531fd369d9260e2f858bfcb7", size = 8304, upload-time = "2025-09-12T15:22:58.946Z" }, ] [[package]] @@ -4379,7 +4385,7 @@ wheels = [ [[package]] name = "pytest-xdist" -version = "3.7.1.dev24+g2b4372b" +version = "3.7.1.dev24+g2b4372bd6" source = { git = "https://github.com/sshane/pytest-xdist?rev=2b4372bd62699fb412c4fe2f95bf9f01bd2018da#2b4372bd62699fb412c4fe2f95bf9f01bd2018da" } dependencies = [ { name = "execnet" }, @@ -4421,9 +4427,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 = [ @@ -4521,38 +4527,38 @@ wheels = [ [[package]] name = "pyzmq" -version = "27.0.2" +version = "27.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "implementation_name == 'pypy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f8/66/159f38d184f08b5f971b467f87b1ab142ab1320d5200825c824b32b84b66/pyzmq-27.0.2.tar.gz", hash = "sha256:b398dd713b18de89730447347e96a0240225e154db56e35b6bb8447ffdb07798", size = 281440, upload-time = "2025-08-21T04:23:26.334Z" } +sdist = { url = "https://files.pythonhosted.org/packages/04/0b/3c9baedbdf613ecaa7aa07027780b8867f57b6293b6ee50de316c9f3222b/pyzmq-27.1.0.tar.gz", hash = "sha256:ac0765e3d44455adb6ddbf4417dcce460fc40a05978c08efdf2948072f6db540", size = 281750, upload-time = "2025-09-08T23:10:18.157Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/42/73/034429ab0f4316bf433eb6c20c3f49d1dc13b2ed4e4d951b283d300a0f35/pyzmq-27.0.2-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:063845960df76599ad4fad69fa4d884b3ba38304272104fdcd7e3af33faeeb1d", size = 1333169, upload-time = "2025-08-21T04:21:12.483Z" }, - { url = "https://files.pythonhosted.org/packages/35/02/c42b3b526eb03a570c889eea85a5602797f800a50ba8b09ddbf7db568b78/pyzmq-27.0.2-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:845a35fb21b88786aeb38af8b271d41ab0967985410f35411a27eebdc578a076", size = 909176, upload-time = "2025-08-21T04:21:13.835Z" }, - { url = "https://files.pythonhosted.org/packages/1b/35/a1c0b988fabbdf2dc5fe94b7c2bcfd61e3533e5109297b8e0daf1d7a8d2d/pyzmq-27.0.2-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:515d20b5c3c86db95503faa989853a8ab692aab1e5336db011cd6d35626c4cb1", size = 668972, upload-time = "2025-08-21T04:21:15.315Z" }, - { url = "https://files.pythonhosted.org/packages/a0/63/908ac865da32ceaeecea72adceadad28ca25b23a2ca5ff018e5bff30116f/pyzmq-27.0.2-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:862aedec0b0684a5050cdb5ec13c2da96d2f8dffda48657ed35e312a4e31553b", size = 856962, upload-time = "2025-08-21T04:21:16.652Z" }, - { url = "https://files.pythonhosted.org/packages/2f/5a/90b3cc20b65cdf9391896fcfc15d8db21182eab810b7ea05a2986912fbe2/pyzmq-27.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2cb5bcfc51c7a4fce335d3bc974fd1d6a916abbcdd2b25f6e89d37b8def25f57", size = 1657712, upload-time = "2025-08-21T04:21:18.666Z" }, - { url = "https://files.pythonhosted.org/packages/c4/3c/32a5a80f9be4759325b8d7b22ce674bb87e586b4c80c6a9d77598b60d6f0/pyzmq-27.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:38ff75b2a36e3a032e9fef29a5871e3e1301a37464e09ba364e3c3193f62982a", size = 2035054, upload-time = "2025-08-21T04:21:20.073Z" }, - { url = "https://files.pythonhosted.org/packages/13/61/71084fe2ff2d7dc5713f8740d735336e87544845dae1207a8e2e16d9af90/pyzmq-27.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7a5709abe8d23ca158a9d0a18c037f4193f5b6afeb53be37173a41e9fb885792", size = 1894010, upload-time = "2025-08-21T04:21:21.96Z" }, - { url = "https://files.pythonhosted.org/packages/cb/6b/77169cfb13b696e50112ca496b2ed23c4b7d8860a1ec0ff3e4b9f9926221/pyzmq-27.0.2-cp311-cp311-win32.whl", hash = "sha256:47c5dda2018c35d87be9b83de0890cb92ac0791fd59498847fc4eca6ff56671d", size = 566819, upload-time = "2025-08-21T04:21:23.31Z" }, - { url = "https://files.pythonhosted.org/packages/37/cd/86c4083e0f811f48f11bc0ddf1e7d13ef37adfd2fd4f78f2445f1cc5dec0/pyzmq-27.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:f54ca3e98f8f4d23e989c7d0edcf9da7a514ff261edaf64d1d8653dd5feb0a8b", size = 633264, upload-time = "2025-08-21T04:21:24.761Z" }, - { url = "https://files.pythonhosted.org/packages/a0/69/5b8bb6a19a36a569fac02153a9e083738785892636270f5f68a915956aea/pyzmq-27.0.2-cp311-cp311-win_arm64.whl", hash = "sha256:2ef3067cb5b51b090fb853f423ad7ed63836ec154374282780a62eb866bf5768", size = 559316, upload-time = "2025-08-21T04:21:26.1Z" }, - { url = "https://files.pythonhosted.org/packages/68/69/b3a729e7b03e412bee2b1823ab8d22e20a92593634f664afd04c6c9d9ac0/pyzmq-27.0.2-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:5da05e3c22c95e23bfc4afeee6ff7d4be9ff2233ad6cb171a0e8257cd46b169a", size = 1305910, upload-time = "2025-08-21T04:21:27.609Z" }, - { url = "https://files.pythonhosted.org/packages/15/b7/f6a6a285193d489b223c340b38ee03a673467cb54914da21c3d7849f1b10/pyzmq-27.0.2-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:4e4520577971d01d47e2559bb3175fce1be9103b18621bf0b241abe0a933d040", size = 895507, upload-time = "2025-08-21T04:21:29.005Z" }, - { url = "https://files.pythonhosted.org/packages/17/e6/c4ed2da5ef9182cde1b1f5d0051a986e76339d71720ec1a00be0b49275ad/pyzmq-27.0.2-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:56d7de7bf73165b90bd25a8668659ccb134dd28449116bf3c7e9bab5cf8a8ec9", size = 652670, upload-time = "2025-08-21T04:21:30.71Z" }, - { url = "https://files.pythonhosted.org/packages/0e/66/d781ab0636570d32c745c4e389b1c6b713115905cca69ab6233508622edd/pyzmq-27.0.2-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:340e7cddc32f147c6c00d116a3f284ab07ee63dbd26c52be13b590520434533c", size = 840581, upload-time = "2025-08-21T04:21:32.008Z" }, - { url = "https://files.pythonhosted.org/packages/a6/df/f24790caf565d72544f5c8d8500960b9562c1dc848d6f22f3c7e122e73d4/pyzmq-27.0.2-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ba95693f9df8bb4a9826464fb0fe89033936f35fd4a8ff1edff09a473570afa0", size = 1641931, upload-time = "2025-08-21T04:21:33.371Z" }, - { url = "https://files.pythonhosted.org/packages/65/65/77d27b19fc5e845367f9100db90b9fce924f611b14770db480615944c9c9/pyzmq-27.0.2-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:ca42a6ce2d697537da34f77a1960d21476c6a4af3e539eddb2b114c3cf65a78c", size = 2021226, upload-time = "2025-08-21T04:21:35.301Z" }, - { url = "https://files.pythonhosted.org/packages/5b/65/1ed14421ba27a4207fa694772003a311d1142b7f543179e4d1099b7eb746/pyzmq-27.0.2-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3e44e665d78a07214b2772ccbd4b9bcc6d848d7895f1b2d7653f047b6318a4f6", size = 1878047, upload-time = "2025-08-21T04:21:36.749Z" }, - { url = "https://files.pythonhosted.org/packages/dd/dc/e578549b89b40dc78a387ec471c2a360766690c0a045cd8d1877d401012d/pyzmq-27.0.2-cp312-abi3-win32.whl", hash = "sha256:272d772d116615397d2be2b1417b3b8c8bc8671f93728c2f2c25002a4530e8f6", size = 558757, upload-time = "2025-08-21T04:21:38.2Z" }, - { url = "https://files.pythonhosted.org/packages/b5/89/06600980aefcc535c758414da969f37a5194ea4cdb73b745223f6af3acfb/pyzmq-27.0.2-cp312-abi3-win_amd64.whl", hash = "sha256:734be4f44efba0aa69bf5f015ed13eb69ff29bf0d17ea1e21588b095a3147b8e", size = 619281, upload-time = "2025-08-21T04:21:39.909Z" }, - { url = "https://files.pythonhosted.org/packages/30/84/df8a5c089552d17c9941d1aea4314b606edf1b1622361dae89aacedc6467/pyzmq-27.0.2-cp312-abi3-win_arm64.whl", hash = "sha256:41f0bd56d9279392810950feb2785a419c2920bbf007fdaaa7f4a07332ae492d", size = 552680, upload-time = "2025-08-21T04:21:41.571Z" }, - { url = "https://files.pythonhosted.org/packages/c7/60/027d0032a1e3b1aabcef0e309b9ff8a4099bdd5a60ab38b36a676ff2bd7b/pyzmq-27.0.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e297784aea724294fe95e442e39a4376c2f08aa4fae4161c669f047051e31b02", size = 836007, upload-time = "2025-08-21T04:23:00.447Z" }, - { url = "https://files.pythonhosted.org/packages/25/20/2ed1e6168aaea323df9bb2c451309291f53ba3af372ffc16edd4ce15b9e5/pyzmq-27.0.2-pp311-pypy311_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:e3659a79ded9745bc9c2aef5b444ac8805606e7bc50d2d2eb16dc3ab5483d91f", size = 799932, upload-time = "2025-08-21T04:23:02.052Z" }, - { url = "https://files.pythonhosted.org/packages/fd/25/5c147307de546b502c9373688ce5b25dc22288d23a1ebebe5d587bf77610/pyzmq-27.0.2-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3dba49ff037d02373a9306b58d6c1e0be031438f822044e8767afccfdac4c6b", size = 567459, upload-time = "2025-08-21T04:23:03.593Z" }, - { url = "https://files.pythonhosted.org/packages/71/06/0dc56ffc615c8095cd089c9b98ce5c733e990f09ce4e8eea4aaf1041a532/pyzmq-27.0.2-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de84e1694f9507b29e7b263453a2255a73e3d099d258db0f14539bad258abe41", size = 747088, upload-time = "2025-08-21T04:23:05.334Z" }, - { url = "https://files.pythonhosted.org/packages/06/f6/4a50187e023b8848edd3f0a8e197b1a7fb08d261d8c60aae7cb6c3d71612/pyzmq-27.0.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f0944d65ba2b872b9fcece08411d6347f15a874c775b4c3baae7f278550da0fb", size = 544639, upload-time = "2025-08-21T04:23:07.279Z" }, + { url = "https://files.pythonhosted.org/packages/06/5d/305323ba86b284e6fcb0d842d6adaa2999035f70f8c38a9b6d21ad28c3d4/pyzmq-27.1.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:226b091818d461a3bef763805e75685e478ac17e9008f49fce2d3e52b3d58b86", size = 1333328, upload-time = "2025-09-08T23:07:45.946Z" }, + { url = "https://files.pythonhosted.org/packages/bd/a0/fc7e78a23748ad5443ac3275943457e8452da67fda347e05260261108cbc/pyzmq-27.1.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0790a0161c281ca9723f804871b4027f2e8b5a528d357c8952d08cd1a9c15581", size = 908803, upload-time = "2025-09-08T23:07:47.551Z" }, + { url = "https://files.pythonhosted.org/packages/7e/22/37d15eb05f3bdfa4abea6f6d96eb3bb58585fbd3e4e0ded4e743bc650c97/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c895a6f35476b0c3a54e3eb6ccf41bf3018de937016e6e18748317f25d4e925f", size = 668836, upload-time = "2025-09-08T23:07:49.436Z" }, + { url = "https://files.pythonhosted.org/packages/b1/c4/2a6fe5111a01005fc7af3878259ce17684fabb8852815eda6225620f3c59/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bbf8d3630bf96550b3be8e1fc0fea5cbdc8d5466c1192887bd94869da17a63e", size = 857038, upload-time = "2025-09-08T23:07:51.234Z" }, + { url = "https://files.pythonhosted.org/packages/cb/eb/bfdcb41d0db9cd233d6fb22dc131583774135505ada800ebf14dfb0a7c40/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:15c8bd0fe0dabf808e2d7a681398c4e5ded70a551ab47482067a572c054c8e2e", size = 1657531, upload-time = "2025-09-08T23:07:52.795Z" }, + { url = "https://files.pythonhosted.org/packages/ab/21/e3180ca269ed4a0de5c34417dfe71a8ae80421198be83ee619a8a485b0c7/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:bafcb3dd171b4ae9f19ee6380dfc71ce0390fefaf26b504c0e5f628d7c8c54f2", size = 2034786, upload-time = "2025-09-08T23:07:55.047Z" }, + { url = "https://files.pythonhosted.org/packages/3b/b1/5e21d0b517434b7f33588ff76c177c5a167858cc38ef740608898cd329f2/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e829529fcaa09937189178115c49c504e69289abd39967cd8a4c215761373394", size = 1894220, upload-time = "2025-09-08T23:07:57.172Z" }, + { url = "https://files.pythonhosted.org/packages/03/f2/44913a6ff6941905efc24a1acf3d3cb6146b636c546c7406c38c49c403d4/pyzmq-27.1.0-cp311-cp311-win32.whl", hash = "sha256:6df079c47d5902af6db298ec92151db82ecb557af663098b92f2508c398bb54f", size = 567155, upload-time = "2025-09-08T23:07:59.05Z" }, + { url = "https://files.pythonhosted.org/packages/23/6d/d8d92a0eb270a925c9b4dd039c0b4dc10abc2fcbc48331788824ef113935/pyzmq-27.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:190cbf120fbc0fc4957b56866830def56628934a9d112aec0e2507aa6a032b97", size = 633428, upload-time = "2025-09-08T23:08:00.663Z" }, + { url = "https://files.pythonhosted.org/packages/ae/14/01afebc96c5abbbd713ecfc7469cfb1bc801c819a74ed5c9fad9a48801cb/pyzmq-27.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:eca6b47df11a132d1745eb3b5b5e557a7dae2c303277aa0e69c6ba91b8736e07", size = 559497, upload-time = "2025-09-08T23:08:02.15Z" }, + { url = "https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:452631b640340c928fa343801b0d07eb0c3789a5ffa843f6e1a9cee0ba4eb4fc", size = 1306279, upload-time = "2025-09-08T23:08:03.807Z" }, + { url = "https://files.pythonhosted.org/packages/e8/5e/c3c49fdd0f535ef45eefcc16934648e9e59dace4a37ee88fc53f6cd8e641/pyzmq-27.1.0-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1c179799b118e554b66da67d88ed66cd37a169f1f23b5d9f0a231b4e8d44a113", size = 895645, upload-time = "2025-09-08T23:08:05.301Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e5/b0b2504cb4e903a74dcf1ebae157f9e20ebb6ea76095f6cfffea28c42ecd/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3837439b7f99e60312f0c926a6ad437b067356dc2bc2ec96eb395fd0fe804233", size = 652574, upload-time = "2025-09-08T23:08:06.828Z" }, + { url = "https://files.pythonhosted.org/packages/f8/9b/c108cdb55560eaf253f0cbdb61b29971e9fb34d9c3499b0e96e4e60ed8a5/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43ad9a73e3da1fab5b0e7e13402f0b2fb934ae1c876c51d0afff0e7c052eca31", size = 840995, upload-time = "2025-09-08T23:08:08.396Z" }, + { url = "https://files.pythonhosted.org/packages/c2/bb/b79798ca177b9eb0825b4c9998c6af8cd2a7f15a6a1a4272c1d1a21d382f/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0de3028d69d4cdc475bfe47a6128eb38d8bc0e8f4d69646adfbcd840facbac28", size = 1642070, upload-time = "2025-09-08T23:08:09.989Z" }, + { url = "https://files.pythonhosted.org/packages/9c/80/2df2e7977c4ede24c79ae39dcef3899bfc5f34d1ca7a5b24f182c9b7a9ca/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:cf44a7763aea9298c0aa7dbf859f87ed7012de8bda0f3977b6fb1d96745df856", size = 2021121, upload-time = "2025-09-08T23:08:11.907Z" }, + { url = "https://files.pythonhosted.org/packages/46/bd/2d45ad24f5f5ae7e8d01525eb76786fa7557136555cac7d929880519e33a/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f30f395a9e6fbca195400ce833c731e7b64c3919aa481af4d88c3759e0cb7496", size = 1878550, upload-time = "2025-09-08T23:08:13.513Z" }, + { url = "https://files.pythonhosted.org/packages/e6/2f/104c0a3c778d7c2ab8190e9db4f62f0b6957b53c9d87db77c284b69f33ea/pyzmq-27.1.0-cp312-abi3-win32.whl", hash = "sha256:250e5436a4ba13885494412b3da5d518cd0d3a278a1ae640e113c073a5f88edd", size = 559184, upload-time = "2025-09-08T23:08:15.163Z" }, + { url = "https://files.pythonhosted.org/packages/fc/7f/a21b20d577e4100c6a41795842028235998a643b1ad406a6d4163ea8f53e/pyzmq-27.1.0-cp312-abi3-win_amd64.whl", hash = "sha256:9ce490cf1d2ca2ad84733aa1d69ce6855372cb5ce9223802450c9b2a7cba0ccf", size = 619480, upload-time = "2025-09-08T23:08:17.192Z" }, + { url = "https://files.pythonhosted.org/packages/78/c2/c012beae5f76b72f007a9e91ee9401cb88c51d0f83c6257a03e785c81cc2/pyzmq-27.1.0-cp312-abi3-win_arm64.whl", hash = "sha256:75a2f36223f0d535a0c919e23615fc85a1e23b71f40c7eb43d7b1dedb4d8f15f", size = 552993, upload-time = "2025-09-08T23:08:18.926Z" }, + { url = "https://files.pythonhosted.org/packages/4c/c6/c4dcdecdbaa70969ee1fdced6d7b8f60cfabe64d25361f27ac4665a70620/pyzmq-27.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:18770c8d3563715387139060d37859c02ce40718d1faf299abddcdcc6a649066", size = 836265, upload-time = "2025-09-08T23:09:49.376Z" }, + { url = "https://files.pythonhosted.org/packages/3e/79/f38c92eeaeb03a2ccc2ba9866f0439593bb08c5e3b714ac1d553e5c96e25/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:ac25465d42f92e990f8d8b0546b01c391ad431c3bf447683fdc40565941d0604", size = 800208, upload-time = "2025-09-08T23:09:51.073Z" }, + { url = "https://files.pythonhosted.org/packages/49/0e/3f0d0d335c6b3abb9b7b723776d0b21fa7f3a6c819a0db6097059aada160/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53b40f8ae006f2734ee7608d59ed661419f087521edbfc2149c3932e9c14808c", size = 567747, upload-time = "2025-09-08T23:09:52.698Z" }, + { url = "https://files.pythonhosted.org/packages/a1/cf/f2b3784d536250ffd4be70e049f3b60981235d70c6e8ce7e3ef21e1adb25/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f605d884e7c8be8fe1aa94e0a783bf3f591b84c24e4bc4f3e7564c82ac25e271", size = 747371, upload-time = "2025-09-08T23:09:54.563Z" }, + { url = "https://files.pythonhosted.org/packages/01/1b/5dbe84eefc86f48473947e2f41711aded97eecef1231f4558f1f02713c12/pyzmq-27.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c9f7f6e13dff2e44a6afeaf2cf54cee5929ad64afaf4d40b50f93c58fc687355", size = 544862, upload-time = "2025-09-08T23:09:56.509Z" }, ] [[package]] @@ -4655,28 +4661,28 @@ wheels = [ [[package]] name = "ruff" -version = "0.12.11" +version = "0.13.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/de/55/16ab6a7d88d93001e1ae4c34cbdcfb376652d761799459ff27c1dc20f6fa/ruff-0.12.11.tar.gz", hash = "sha256:c6b09ae8426a65bbee5425b9d0b82796dbb07cb1af045743c79bfb163001165d", size = 5347103, upload-time = "2025-08-28T13:59:08.87Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ab/33/c8e89216845615d14d2d42ba2bee404e7206a8db782f33400754f3799f05/ruff-0.13.1.tar.gz", hash = "sha256:88074c3849087f153d4bb22e92243ad4c1b366d7055f98726bc19aa08dc12d51", size = 5397987, upload-time = "2025-09-18T19:52:44.33Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d6/a2/3b3573e474de39a7a475f3fbaf36a25600bfeb238e1a90392799163b64a0/ruff-0.12.11-py3-none-linux_armv6l.whl", hash = "sha256:93fce71e1cac3a8bf9200e63a38ac5c078f3b6baebffb74ba5274fb2ab276065", size = 11979885, upload-time = "2025-08-28T13:58:26.654Z" }, - { url = "https://files.pythonhosted.org/packages/76/e4/235ad6d1785a2012d3ded2350fd9bc5c5af8c6f56820e696b0118dfe7d24/ruff-0.12.11-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b8e33ac7b28c772440afa80cebb972ffd823621ded90404f29e5ab6d1e2d4b93", size = 12742364, upload-time = "2025-08-28T13:58:30.256Z" }, - { url = "https://files.pythonhosted.org/packages/2c/0d/15b72c5fe6b1e402a543aa9d8960e0a7e19dfb079f5b0b424db48b7febab/ruff-0.12.11-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d69fb9d4937aa19adb2e9f058bc4fbfe986c2040acb1a4a9747734834eaa0bfd", size = 11920111, upload-time = "2025-08-28T13:58:33.677Z" }, - { url = "https://files.pythonhosted.org/packages/3e/c0/f66339d7893798ad3e17fa5a1e587d6fd9806f7c1c062b63f8b09dda6702/ruff-0.12.11-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:411954eca8464595077a93e580e2918d0a01a19317af0a72132283e28ae21bee", size = 12160060, upload-time = "2025-08-28T13:58:35.74Z" }, - { url = "https://files.pythonhosted.org/packages/03/69/9870368326db26f20c946205fb2d0008988aea552dbaec35fbacbb46efaa/ruff-0.12.11-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6a2c0a2e1a450f387bf2c6237c727dd22191ae8c00e448e0672d624b2bbd7fb0", size = 11799848, upload-time = "2025-08-28T13:58:38.051Z" }, - { url = "https://files.pythonhosted.org/packages/25/8c/dd2c7f990e9b3a8a55eee09d4e675027d31727ce33cdb29eab32d025bdc9/ruff-0.12.11-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ca4c3a7f937725fd2413c0e884b5248a19369ab9bdd850b5781348ba283f644", size = 13536288, upload-time = "2025-08-28T13:58:40.046Z" }, - { url = "https://files.pythonhosted.org/packages/7a/30/d5496fa09aba59b5e01ea76775a4c8897b13055884f56f1c35a4194c2297/ruff-0.12.11-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:4d1df0098124006f6a66ecf3581a7f7e754c4df7644b2e6704cd7ca80ff95211", size = 14490633, upload-time = "2025-08-28T13:58:42.285Z" }, - { url = "https://files.pythonhosted.org/packages/9b/2f/81f998180ad53445d403c386549d6946d0748e536d58fce5b5e173511183/ruff-0.12.11-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5a8dd5f230efc99a24ace3b77e3555d3fbc0343aeed3fc84c8d89e75ab2ff793", size = 13888430, upload-time = "2025-08-28T13:58:44.641Z" }, - { url = "https://files.pythonhosted.org/packages/87/71/23a0d1d5892a377478c61dbbcffe82a3476b050f38b5162171942a029ef3/ruff-0.12.11-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4dc75533039d0ed04cd33fb8ca9ac9620b99672fe7ff1533b6402206901c34ee", size = 12913133, upload-time = "2025-08-28T13:58:47.039Z" }, - { url = "https://files.pythonhosted.org/packages/80/22/3c6cef96627f89b344c933781ed38329bfb87737aa438f15da95907cbfd5/ruff-0.12.11-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4fc58f9266d62c6eccc75261a665f26b4ef64840887fc6cbc552ce5b29f96cc8", size = 13169082, upload-time = "2025-08-28T13:58:49.157Z" }, - { url = "https://files.pythonhosted.org/packages/05/b5/68b3ff96160d8b49e8dd10785ff3186be18fd650d356036a3770386e6c7f/ruff-0.12.11-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:5a0113bd6eafd545146440225fe60b4e9489f59eb5f5f107acd715ba5f0b3d2f", size = 13139490, upload-time = "2025-08-28T13:58:51.593Z" }, - { url = "https://files.pythonhosted.org/packages/59/b9/050a3278ecd558f74f7ee016fbdf10591d50119df8d5f5da45a22c6afafc/ruff-0.12.11-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0d737b4059d66295c3ea5720e6efc152623bb83fde5444209b69cd33a53e2000", size = 11958928, upload-time = "2025-08-28T13:58:53.943Z" }, - { url = "https://files.pythonhosted.org/packages/f9/bc/93be37347db854806904a43b0493af8d6873472dfb4b4b8cbb27786eb651/ruff-0.12.11-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:916fc5defee32dbc1fc1650b576a8fed68f5e8256e2180d4d9855aea43d6aab2", size = 11764513, upload-time = "2025-08-28T13:58:55.976Z" }, - { url = "https://files.pythonhosted.org/packages/7a/a1/1471751e2015a81fd8e166cd311456c11df74c7e8769d4aabfbc7584c7ac/ruff-0.12.11-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c984f07d7adb42d3ded5be894fb4007f30f82c87559438b4879fe7aa08c62b39", size = 12745154, upload-time = "2025-08-28T13:58:58.16Z" }, - { url = "https://files.pythonhosted.org/packages/68/ab/2542b14890d0f4872dd81b7b2a6aed3ac1786fae1ce9b17e11e6df9e31e3/ruff-0.12.11-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e07fbb89f2e9249f219d88331c833860489b49cdf4b032b8e4432e9b13e8a4b9", size = 13227653, upload-time = "2025-08-28T13:59:00.276Z" }, - { url = "https://files.pythonhosted.org/packages/22/16/2fbfc61047dbfd009c58a28369a693a1484ad15441723be1cd7fe69bb679/ruff-0.12.11-py3-none-win32.whl", hash = "sha256:c792e8f597c9c756e9bcd4d87cf407a00b60af77078c96f7b6366ea2ce9ba9d3", size = 11944270, upload-time = "2025-08-28T13:59:02.347Z" }, - { url = "https://files.pythonhosted.org/packages/08/a5/34276984705bfe069cd383101c45077ee029c3fe3b28225bf67aa35f0647/ruff-0.12.11-py3-none-win_amd64.whl", hash = "sha256:a3283325960307915b6deb3576b96919ee89432ebd9c48771ca12ee8afe4a0fd", size = 13046600, upload-time = "2025-08-28T13:59:04.751Z" }, - { url = "https://files.pythonhosted.org/packages/84/a8/001d4a7c2b37623a3fd7463208267fb906df40ff31db496157549cfd6e72/ruff-0.12.11-py3-none-win_arm64.whl", hash = "sha256:bae4d6e6a2676f8fb0f98b74594a048bae1b944aab17e9f5d504062303c6dbea", size = 12135290, upload-time = "2025-08-28T13:59:06.933Z" }, + { url = "https://files.pythonhosted.org/packages/f3/41/ca37e340938f45cfb8557a97a5c347e718ef34702546b174e5300dbb1f28/ruff-0.13.1-py3-none-linux_armv6l.whl", hash = "sha256:b2abff595cc3cbfa55e509d89439b5a09a6ee3c252d92020bd2de240836cf45b", size = 12304308, upload-time = "2025-09-18T19:51:56.253Z" }, + { url = "https://files.pythonhosted.org/packages/ff/84/ba378ef4129415066c3e1c80d84e539a0d52feb250685091f874804f28af/ruff-0.13.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:4ee9f4249bf7f8bb3984c41bfaf6a658162cdb1b22e3103eabc7dd1dc5579334", size = 12937258, upload-time = "2025-09-18T19:52:00.184Z" }, + { url = "https://files.pythonhosted.org/packages/8d/b6/ec5e4559ae0ad955515c176910d6d7c93edcbc0ed1a3195a41179c58431d/ruff-0.13.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5c5da4af5f6418c07d75e6f3224e08147441f5d1eac2e6ce10dcce5e616a3bae", size = 12214554, upload-time = "2025-09-18T19:52:02.753Z" }, + { url = "https://files.pythonhosted.org/packages/70/d6/cb3e3b4f03b9b0c4d4d8f06126d34b3394f6b4d764912fe80a1300696ef6/ruff-0.13.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80524f84a01355a59a93cef98d804e2137639823bcee2931f5028e71134a954e", size = 12448181, upload-time = "2025-09-18T19:52:05.279Z" }, + { url = "https://files.pythonhosted.org/packages/d2/ea/bf60cb46d7ade706a246cd3fb99e4cfe854efa3dfbe530d049c684da24ff/ruff-0.13.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff7f5ce8d7988767dd46a148192a14d0f48d1baea733f055d9064875c7d50389", size = 12104599, upload-time = "2025-09-18T19:52:07.497Z" }, + { url = "https://files.pythonhosted.org/packages/2d/3e/05f72f4c3d3a69e65d55a13e1dd1ade76c106d8546e7e54501d31f1dc54a/ruff-0.13.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c55d84715061f8b05469cdc9a446aa6c7294cd4bd55e86a89e572dba14374f8c", size = 13791178, upload-time = "2025-09-18T19:52:10.189Z" }, + { url = "https://files.pythonhosted.org/packages/81/e7/01b1fc403dd45d6cfe600725270ecc6a8f8a48a55bc6521ad820ed3ceaf8/ruff-0.13.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:ac57fed932d90fa1624c946dc67a0a3388d65a7edc7d2d8e4ca7bddaa789b3b0", size = 14814474, upload-time = "2025-09-18T19:52:12.866Z" }, + { url = "https://files.pythonhosted.org/packages/fa/92/d9e183d4ed6185a8df2ce9faa3f22e80e95b5f88d9cc3d86a6d94331da3f/ruff-0.13.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c366a71d5b4f41f86a008694f7a0d75fe409ec298685ff72dc882f882d532e36", size = 14217531, upload-time = "2025-09-18T19:52:15.245Z" }, + { url = "https://files.pythonhosted.org/packages/3b/4a/6ddb1b11d60888be224d721e01bdd2d81faaf1720592858ab8bac3600466/ruff-0.13.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4ea9d1b5ad3e7a83ee8ebb1229c33e5fe771e833d6d3dcfca7b77d95b060d38", size = 13265267, upload-time = "2025-09-18T19:52:17.649Z" }, + { url = "https://files.pythonhosted.org/packages/81/98/3f1d18a8d9ea33ef2ad508f0417fcb182c99b23258ec5e53d15db8289809/ruff-0.13.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0f70202996055b555d3d74b626406476cc692f37b13bac8828acff058c9966a", size = 13243120, upload-time = "2025-09-18T19:52:20.332Z" }, + { url = "https://files.pythonhosted.org/packages/8d/86/b6ce62ce9c12765fa6c65078d1938d2490b2b1d9273d0de384952b43c490/ruff-0.13.1-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:f8cff7a105dad631085d9505b491db33848007d6b487c3c1979dd8d9b2963783", size = 13443084, upload-time = "2025-09-18T19:52:23.032Z" }, + { url = "https://files.pythonhosted.org/packages/a1/6e/af7943466a41338d04503fb5a81b2fd07251bd272f546622e5b1599a7976/ruff-0.13.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:9761e84255443316a258dd7dfbd9bfb59c756e52237ed42494917b2577697c6a", size = 12295105, upload-time = "2025-09-18T19:52:25.263Z" }, + { url = "https://files.pythonhosted.org/packages/3f/97/0249b9a24f0f3ebd12f007e81c87cec6d311de566885e9309fcbac5b24cc/ruff-0.13.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:3d376a88c3102ef228b102211ef4a6d13df330cb0f5ca56fdac04ccec2a99700", size = 12072284, upload-time = "2025-09-18T19:52:27.478Z" }, + { url = "https://files.pythonhosted.org/packages/f6/85/0b64693b2c99d62ae65236ef74508ba39c3febd01466ef7f354885e5050c/ruff-0.13.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:cbefd60082b517a82c6ec8836989775ac05f8991715d228b3c1d86ccc7df7dae", size = 12970314, upload-time = "2025-09-18T19:52:30.212Z" }, + { url = "https://files.pythonhosted.org/packages/96/fc/342e9f28179915d28b3747b7654f932ca472afbf7090fc0c4011e802f494/ruff-0.13.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:dd16b9a5a499fe73f3c2ef09a7885cb1d97058614d601809d37c422ed1525317", size = 13422360, upload-time = "2025-09-18T19:52:32.676Z" }, + { url = "https://files.pythonhosted.org/packages/37/54/6177a0dc10bce6f43e392a2192e6018755473283d0cf43cc7e6afc182aea/ruff-0.13.1-py3-none-win32.whl", hash = "sha256:55e9efa692d7cb18580279f1fbb525146adc401f40735edf0aaeabd93099f9a0", size = 12178448, upload-time = "2025-09-18T19:52:35.545Z" }, + { url = "https://files.pythonhosted.org/packages/64/51/c6a3a33d9938007b8bdc8ca852ecc8d810a407fb513ab08e34af12dc7c24/ruff-0.13.1-py3-none-win_amd64.whl", hash = "sha256:3a3fb595287ee556de947183489f636b9f76a72f0fa9c028bdcabf5bab2cc5e5", size = 13286458, upload-time = "2025-09-18T19:52:38.198Z" }, + { url = "https://files.pythonhosted.org/packages/fd/04/afc078a12cf68592345b1e2d6ecdff837d286bac023d7a22c54c7a698c5b/ruff-0.13.1-py3-none-win_arm64.whl", hash = "sha256:c0bae9ffd92d54e03c2bf266f466da0a65e145f298ee5b5846ed435f6a00518a", size = 12437893, upload-time = "2025-09-18T19:52:41.283Z" }, ] [[package]] @@ -4690,47 +4696,46 @@ wheels = [ [[package]] name = "sentry-sdk" -version = "2.35.2" +version = "2.38.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/bd/79/0ecb942f3f1ad26c40c27f81ff82392d85c01d26a45e3c72c2b37807e680/sentry_sdk-2.35.2.tar.gz", hash = "sha256:e9e8f3c795044beb59f2c8f4c6b9b0f9779e5e604099882df05eec525e782cc6", size = 343377, upload-time = "2025-09-01T11:00:58.633Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/22/60fd703b34d94d216b2387e048ac82de3e86b63bc28869fb076f8bb0204a/sentry_sdk-2.38.0.tar.gz", hash = "sha256:792d2af45e167e2f8a3347143f525b9b6bac6f058fb2014720b40b84ccbeb985", size = 348116, upload-time = "2025-09-15T15:00:37.846Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c0/91/a43308dc82a0e32d80cd0dfdcfca401ecbd0f431ab45f24e48bb97b7800d/sentry_sdk-2.35.2-py2.py3-none-any.whl", hash = "sha256:38c98e3cbb620dd3dd80a8d6e39c753d453dd41f8a9df581b0584c19a52bc926", size = 363975, upload-time = "2025-09-01T11:00:56.574Z" }, + { url = "https://files.pythonhosted.org/packages/7a/84/bde4c4bbb269b71bc09316af8eb00da91f67814d40337cc12ef9c8742541/sentry_sdk-2.38.0-py2.py3-none-any.whl", hash = "sha256:2324aea8573a3fa1576df7fb4d65c4eb8d9929c8fa5939647397a07179eef8d0", size = 370346, upload-time = "2025-09-15T15:00:35.821Z" }, ] [[package]] name = "setproctitle" -version = "1.3.6" +version = "1.3.7" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9e/af/56efe21c53ac81ac87e000b15e60b3d8104224b4313b6eacac3597bd183d/setproctitle-1.3.6.tar.gz", hash = "sha256:c9f32b96c700bb384f33f7cf07954bb609d35dd82752cef57fb2ee0968409169", size = 26889, upload-time = "2025-04-29T13:35:00.184Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8d/48/49393a96a2eef1ab418b17475fb92b8fcfad83d099e678751b05472e69de/setproctitle-1.3.7.tar.gz", hash = "sha256:bc2bc917691c1537d5b9bca1468437176809c7e11e5694ca79a9ca12345dcb9e", size = 27002, upload-time = "2025-09-05T12:51:25.278Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/27/3b/8288d0cd969a63500dd62fc2c99ce6980f9909ccef0770ab1f86c361e0bf/setproctitle-1.3.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a1d856b0f4e4a33e31cdab5f50d0a14998f3a2d726a3fd5cb7c4d45a57b28d1b", size = 17412, upload-time = "2025-04-29T13:32:58.135Z" }, - { url = "https://files.pythonhosted.org/packages/39/37/43a5a3e25ca1048dbbf4db0d88d346226f5f1acd131bb8e660f4bfe2799f/setproctitle-1.3.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:50706b9c0eda55f7de18695bfeead5f28b58aa42fd5219b3b1692d554ecbc9ec", size = 11963, upload-time = "2025-04-29T13:32:59.17Z" }, - { url = "https://files.pythonhosted.org/packages/5b/47/f103c40e133154783c91a10ab08ac9fc410ed835aa85bcf7107cb882f505/setproctitle-1.3.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:af188f3305f0a65c3217c30c6d4c06891e79144076a91e8b454f14256acc7279", size = 31718, upload-time = "2025-04-29T13:33:00.36Z" }, - { url = "https://files.pythonhosted.org/packages/1f/13/7325dd1c008dd6c0ebd370ddb7505977054a87e406f142318e395031a792/setproctitle-1.3.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cce0ed8b3f64c71c140f0ec244e5fdf8ecf78ddf8d2e591d4a8b6aa1c1214235", size = 33027, upload-time = "2025-04-29T13:33:01.499Z" }, - { url = "https://files.pythonhosted.org/packages/0c/0a/6075bfea05a71379d77af98a9ac61163e8b6e5ef1ae58cd2b05871b2079c/setproctitle-1.3.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70100e2087fe05359f249a0b5f393127b3a1819bf34dec3a3e0d4941138650c9", size = 30223, upload-time = "2025-04-29T13:33:03.259Z" }, - { url = "https://files.pythonhosted.org/packages/cc/41/fbf57ec52f4f0776193bd94334a841f0bc9d17e745f89c7790f336420c65/setproctitle-1.3.6-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1065ed36bd03a3fd4186d6c6de5f19846650b015789f72e2dea2d77be99bdca1", size = 31204, upload-time = "2025-04-29T13:33:04.455Z" }, - { url = "https://files.pythonhosted.org/packages/97/b5/f799fb7a00de29fb0ac1dfd015528dea425b9e31a8f1068a0b3df52d317f/setproctitle-1.3.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4adf6a0013fe4e0844e3ba7583ec203ca518b9394c6cc0d3354df2bf31d1c034", size = 31181, upload-time = "2025-04-29T13:33:05.697Z" }, - { url = "https://files.pythonhosted.org/packages/b5/b7/81f101b612014ec61723436022c31146178813d6ca6b947f7b9c84e9daf4/setproctitle-1.3.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:eb7452849f6615871eabed6560ffedfe56bc8af31a823b6be4ce1e6ff0ab72c5", size = 30101, upload-time = "2025-04-29T13:33:07.223Z" }, - { url = "https://files.pythonhosted.org/packages/67/23/681232eed7640eab96719daa8647cc99b639e3daff5c287bd270ef179a73/setproctitle-1.3.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a094b7ce455ca341b59a0f6ce6be2e11411ba6e2860b9aa3dbb37468f23338f4", size = 32438, upload-time = "2025-04-29T13:33:08.538Z" }, - { url = "https://files.pythonhosted.org/packages/19/f8/4d075a7bdc3609ac71535b849775812455e4c40aedfbf0778a6f123b1774/setproctitle-1.3.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ad1c2c2baaba62823a7f348f469a967ece0062140ca39e7a48e4bbb1f20d54c4", size = 30625, upload-time = "2025-04-29T13:33:09.707Z" }, - { url = "https://files.pythonhosted.org/packages/5f/73/a2a8259ebee166aee1ca53eead75de0e190b3ddca4f716e5c7470ebb7ef6/setproctitle-1.3.6-cp311-cp311-win32.whl", hash = "sha256:8050c01331135f77ec99d99307bfbc6519ea24d2f92964b06f3222a804a3ff1f", size = 11488, upload-time = "2025-04-29T13:33:10.953Z" }, - { url = "https://files.pythonhosted.org/packages/c9/15/52cf5e1ff0727d53704cfdde2858eaf237ce523b0b04db65faa84ff83e13/setproctitle-1.3.6-cp311-cp311-win_amd64.whl", hash = "sha256:9b73cf0fe28009a04a35bb2522e4c5b5176cc148919431dcb73fdbdfaab15781", size = 12201, upload-time = "2025-04-29T13:33:12.389Z" }, - { url = "https://files.pythonhosted.org/packages/8f/fb/99456fd94d4207c5f6c40746a048a33a52b4239cd7d9c8d4889e2210ec82/setproctitle-1.3.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:af44bb7a1af163806bbb679eb8432fa7b4fb6d83a5d403b541b675dcd3798638", size = 17399, upload-time = "2025-04-29T13:33:13.406Z" }, - { url = "https://files.pythonhosted.org/packages/d5/48/9699191fe6062827683c43bfa9caac33a2c89f8781dd8c7253fa3dba85fd/setproctitle-1.3.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3cca16fd055316a48f0debfcbfb6af7cea715429fc31515ab3fcac05abd527d8", size = 11966, upload-time = "2025-04-29T13:33:14.976Z" }, - { url = "https://files.pythonhosted.org/packages/33/03/b085d192b9ecb9c7ce6ad6ef30ecf4110b7f39430b58a56245569827fcf4/setproctitle-1.3.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea002088d5554fd75e619742cefc78b84a212ba21632e59931b3501f0cfc8f67", size = 32017, upload-time = "2025-04-29T13:33:16.163Z" }, - { url = "https://files.pythonhosted.org/packages/ae/68/c53162e645816f97212002111420d1b2f75bf6d02632e37e961dc2cd6d8b/setproctitle-1.3.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb465dd5825356c1191a038a86ee1b8166e3562d6e8add95eec04ab484cfb8a2", size = 33419, upload-time = "2025-04-29T13:33:18.239Z" }, - { url = "https://files.pythonhosted.org/packages/ac/0d/119a45d15a816a6cf5ccc61b19729f82620095b27a47e0a6838216a95fae/setproctitle-1.3.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d2c8e20487b3b73c1fa72c56f5c89430617296cd380373e7af3a538a82d4cd6d", size = 30711, upload-time = "2025-04-29T13:33:19.571Z" }, - { url = "https://files.pythonhosted.org/packages/e3/fb/5e9b5068df9e9f31a722a775a5e8322a29a638eaaa3eac5ea7f0b35e6314/setproctitle-1.3.6-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0d6252098e98129a1decb59b46920d4eca17b0395f3d71b0d327d086fefe77d", size = 31742, upload-time = "2025-04-29T13:33:21.172Z" }, - { url = "https://files.pythonhosted.org/packages/35/88/54de1e73e8fce87d587889c7eedb48fc4ee2bbe4e4ca6331690d03024f86/setproctitle-1.3.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cf355fbf0d4275d86f9f57be705d8e5eaa7f8ddb12b24ced2ea6cbd68fdb14dc", size = 31925, upload-time = "2025-04-29T13:33:22.427Z" }, - { url = "https://files.pythonhosted.org/packages/f3/01/65948d7badd66e63e3db247b923143da142790fa293830fdecf832712c2d/setproctitle-1.3.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e288f8a162d663916060beb5e8165a8551312b08efee9cf68302687471a6545d", size = 30981, upload-time = "2025-04-29T13:33:23.739Z" }, - { url = "https://files.pythonhosted.org/packages/22/20/c495e61786f1d38d5dc340b9d9077fee9be3dfc7e89f515afe12e1526dbc/setproctitle-1.3.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b2e54f4a2dc6edf0f5ea5b1d0a608d2af3dcb5aa8c8eeab9c8841b23e1b054fe", size = 33209, upload-time = "2025-04-29T13:33:24.915Z" }, - { url = "https://files.pythonhosted.org/packages/98/3f/a457b8550fbd34d5b482fe20b8376b529e76bf1fbf9a474a6d9a641ab4ad/setproctitle-1.3.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b6f4abde9a2946f57e8daaf1160b2351bcf64274ef539e6675c1d945dbd75e2a", size = 31587, upload-time = "2025-04-29T13:33:26.123Z" }, - { url = "https://files.pythonhosted.org/packages/44/fe/743517340e5a635e3f1c4310baea20c16c66202f96a6f4cead222ffd6d84/setproctitle-1.3.6-cp312-cp312-win32.whl", hash = "sha256:db608db98ccc21248370d30044a60843b3f0f3d34781ceeea67067c508cd5a28", size = 11487, upload-time = "2025-04-29T13:33:27.403Z" }, - { url = "https://files.pythonhosted.org/packages/60/9a/d88f1c1f0f4efff1bd29d9233583ee341114dda7d9613941453984849674/setproctitle-1.3.6-cp312-cp312-win_amd64.whl", hash = "sha256:082413db8a96b1f021088e8ec23f0a61fec352e649aba20881895815388b66d3", size = 12208, upload-time = "2025-04-29T13:33:28.852Z" }, + { url = "https://files.pythonhosted.org/packages/04/cd/1b7ba5cad635510720ce19d7122154df96a2387d2a74217be552887c93e5/setproctitle-1.3.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a600eeb4145fb0ee6c287cb82a2884bd4ec5bbb076921e287039dcc7b7cc6dd0", size = 18085, upload-time = "2025-09-05T12:49:22.183Z" }, + { url = "https://files.pythonhosted.org/packages/8f/1a/b2da0a620490aae355f9d72072ac13e901a9fec809a6a24fc6493a8f3c35/setproctitle-1.3.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:97a090fed480471bb175689859532709e28c085087e344bca45cf318034f70c4", size = 13097, upload-time = "2025-09-05T12:49:23.322Z" }, + { url = "https://files.pythonhosted.org/packages/18/2e/bd03ff02432a181c1787f6fc2a678f53b7dacdd5ded69c318fe1619556e8/setproctitle-1.3.7-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1607b963e7b53e24ec8a2cb4e0ab3ae591d7c6bf0a160feef0551da63452b37f", size = 32191, upload-time = "2025-09-05T12:49:24.567Z" }, + { url = "https://files.pythonhosted.org/packages/28/78/1e62fc0937a8549f2220445ed2175daacee9b6764c7963b16148119b016d/setproctitle-1.3.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a20fb1a3974e2dab857870cf874b325b8705605cb7e7e8bcbb915bca896f52a9", size = 33203, upload-time = "2025-09-05T12:49:25.871Z" }, + { url = "https://files.pythonhosted.org/packages/a0/3c/65edc65db3fa3df400cf13b05e9d41a3c77517b4839ce873aa6b4043184f/setproctitle-1.3.7-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f8d961bba676e07d77665204f36cffaa260f526e7b32d07ab3df6a2c1dfb44ba", size = 34963, upload-time = "2025-09-05T12:49:27.044Z" }, + { url = "https://files.pythonhosted.org/packages/a1/32/89157e3de997973e306e44152522385f428e16f92f3cf113461489e1e2ee/setproctitle-1.3.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:db0fd964fbd3a9f8999b502f65bd2e20883fdb5b1fae3a424e66db9a793ed307", size = 32398, upload-time = "2025-09-05T12:49:28.909Z" }, + { url = "https://files.pythonhosted.org/packages/4a/18/77a765a339ddf046844cb4513353d8e9dcd8183da9cdba6e078713e6b0b2/setproctitle-1.3.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:db116850fcf7cca19492030f8d3b4b6e231278e8fe097a043957d22ce1bdf3ee", size = 33657, upload-time = "2025-09-05T12:49:30.323Z" }, + { url = "https://files.pythonhosted.org/packages/6b/63/f0b6205c64d74d2a24a58644a38ec77bdbaa6afc13747e75973bf8904932/setproctitle-1.3.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:316664d8b24a5c91ee244460bdaf7a74a707adaa9e14fbe0dc0a53168bb9aba1", size = 31836, upload-time = "2025-09-05T12:49:32.309Z" }, + { url = "https://files.pythonhosted.org/packages/ba/51/e1277f9ba302f1a250bbd3eedbbee747a244b3cc682eb58fb9733968f6d8/setproctitle-1.3.7-cp311-cp311-win32.whl", hash = "sha256:b74774ca471c86c09b9d5037c8451fff06bb82cd320d26ae5a01c758088c0d5d", size = 12556, upload-time = "2025-09-05T12:49:33.529Z" }, + { url = "https://files.pythonhosted.org/packages/b6/7b/822a23f17e9003dfdee92cd72758441ca2a3680388da813a371b716fb07f/setproctitle-1.3.7-cp311-cp311-win_amd64.whl", hash = "sha256:acb9097213a8dd3410ed9f0dc147840e45ca9797785272928d4be3f0e69e3be4", size = 13243, upload-time = "2025-09-05T12:49:34.553Z" }, + { url = "https://files.pythonhosted.org/packages/fb/f0/2dc88e842077719d7384d86cc47403e5102810492b33680e7dadcee64cd8/setproctitle-1.3.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2dc99aec591ab6126e636b11035a70991bc1ab7a261da428491a40b84376654e", size = 18049, upload-time = "2025-09-05T12:49:36.241Z" }, + { url = "https://files.pythonhosted.org/packages/f0/b4/50940504466689cda65680c9e9a1e518e5750c10490639fa687489ac7013/setproctitle-1.3.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cdd8aa571b7aa39840fdbea620e308a19691ff595c3a10231e9ee830339dd798", size = 13079, upload-time = "2025-09-05T12:49:38.088Z" }, + { url = "https://files.pythonhosted.org/packages/d0/99/71630546b9395b095f4082be41165d1078204d1696c2d9baade3de3202d0/setproctitle-1.3.7-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2906b6c7959cdb75f46159bf0acd8cc9906cf1361c9e1ded0d065fe8f9039629", size = 32932, upload-time = "2025-09-05T12:49:39.271Z" }, + { url = "https://files.pythonhosted.org/packages/50/22/cee06af4ffcfb0e8aba047bd44f5262e644199ae7527ae2c1f672b86495c/setproctitle-1.3.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6915964a6dda07920a1159321dcd6d94fc7fc526f815ca08a8063aeca3c204f1", size = 33736, upload-time = "2025-09-05T12:49:40.565Z" }, + { url = "https://files.pythonhosted.org/packages/5c/00/a5949a8bb06ef5e7df214fc393bb2fb6aedf0479b17214e57750dfdd0f24/setproctitle-1.3.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cff72899861c765bd4021d1ff1c68d60edc129711a2fdba77f9cb69ef726a8b6", size = 35605, upload-time = "2025-09-05T12:49:42.362Z" }, + { url = "https://files.pythonhosted.org/packages/b0/3a/50caca532a9343828e3bf5778c7a84d6c737a249b1796d50dd680290594d/setproctitle-1.3.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b7cb05bd446687ff816a3aaaf831047fc4c364feff7ada94a66024f1367b448c", size = 33143, upload-time = "2025-09-05T12:49:43.515Z" }, + { url = "https://files.pythonhosted.org/packages/ca/14/b843a251296ce55e2e17c017d6b9f11ce0d3d070e9265de4ecad948b913d/setproctitle-1.3.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3a57b9a00de8cae7e2a1f7b9f0c2ac7b69372159e16a7708aa2f38f9e5cc987a", size = 34434, upload-time = "2025-09-05T12:49:45.31Z" }, + { url = "https://files.pythonhosted.org/packages/c8/b7/06145c238c0a6d2c4bc881f8be230bb9f36d2bf51aff7bddcb796d5eed67/setproctitle-1.3.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d8828b356114f6b308b04afe398ed93803d7fca4a955dd3abe84430e28d33739", size = 32795, upload-time = "2025-09-05T12:49:46.419Z" }, + { url = "https://files.pythonhosted.org/packages/ef/dc/ef76a81fac9bf27b84ed23df19c1f67391a753eed6e3c2254ebcb5133f56/setproctitle-1.3.7-cp312-cp312-win32.whl", hash = "sha256:b0304f905efc845829ac2bc791ddebb976db2885f6171f4a3de678d7ee3f7c9f", size = 12552, upload-time = "2025-09-05T12:49:47.635Z" }, + { url = "https://files.pythonhosted.org/packages/e2/5b/a9fe517912cd6e28cf43a212b80cb679ff179a91b623138a99796d7d18a0/setproctitle-1.3.7-cp312-cp312-win_amd64.whl", hash = "sha256:9888ceb4faea3116cf02a920ff00bfbc8cc899743e4b4ac914b03625bdc3c300", size = 13247, upload-time = "2025-09-05T12:49:49.16Z" }, + { url = "https://files.pythonhosted.org/packages/c3/5b/5e1c117ac84e3cefcf8d7a7f6b2461795a87e20869da065a5c087149060b/setproctitle-1.3.7-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:b1cac6a4b0252b8811d60b6d8d0f157c0fdfed379ac89c25a914e6346cf355a1", size = 12587, upload-time = "2025-09-05T12:51:21.195Z" }, + { url = "https://files.pythonhosted.org/packages/73/02/b9eadc226195dcfa90eed37afe56b5dd6fa2f0e5220ab8b7867b8862b926/setproctitle-1.3.7-pp311-pypy311_pp73-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f1704c9e041f2b1dc38f5be4552e141e1432fba3dd52c72eeffd5bc2db04dc65", size = 14286, upload-time = "2025-09-05T12:51:22.61Z" }, + { url = "https://files.pythonhosted.org/packages/28/26/1be1d2a53c2a91ec48fa2ff4a409b395f836798adf194d99de9c059419ea/setproctitle-1.3.7-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:b08b61976ffa548bd5349ce54404bf6b2d51bd74d4f1b241ed1b0f25bce09c3a", size = 13282, upload-time = "2025-09-05T12:51:24.094Z" }, ] [[package]] @@ -4747,7 +4752,7 @@ name = "shapely" 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/ca/3c/2da625233f4e605155926566c0e7ea8dda361877f48e8b1655e53456f252/shapely-2.1.1.tar.gz", hash = "sha256:500621967f2ffe9642454808009044c21e5b35db89ce69f8a2042c2ffd0e2772", size = 315422, upload-time = "2025-05-19T11:04:41.265Z" } wheels = [ @@ -4771,22 +4776,22 @@ wheels = [ [[package]] name = "siphash24" -version = "1.7" +version = "1.8" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0e/be/f0a0ffbb00c51c5633b41459b5ce9b017c025a9256b4403e648c18e70850/siphash24-1.7.tar.gz", hash = "sha256:6e90fee5f199ea25b4e7303646b31872a437174fe885a93dbd4cf7784eb48164", size = 19801, upload-time = "2024-10-15T13:41:51.924Z" } +sdist = { url = "https://files.pythonhosted.org/packages/67/a2/e049b6fccf7a94bd1b2f68b3059a7d6a7aea86a808cac80cb9ae71ab6254/siphash24-1.8.tar.gz", hash = "sha256:aa932f0af4a7335caef772fdaf73a433a32580405c41eb17ff24077944b0aa97", size = 19946, upload-time = "2025-09-02T20:42:04.856Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4e/67/4ffd23a848739966e1b314ef99f6410035bccee00be14261313787b8f506/siphash24-1.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:de75488e93f1cd12c8d5004efd1ebd958c0265205a9d73e8dd8b071900838841", size = 80493, upload-time = "2024-10-15T13:41:14.727Z" }, - { url = "https://files.pythonhosted.org/packages/56/bd/ec198a8c7aef65e967ae84f633bd9950d784c9e527d738c9a3e4bccc34a5/siphash24-1.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ffca9908450f9f346e97a223185fcd16217d67d84c6f246f3080c4224f41a514", size = 75350, upload-time = "2024-10-15T13:41:16.262Z" }, - { url = "https://files.pythonhosted.org/packages/50/5a/77838c916bd15addfc2e51286db4c442cb12e25eb4f8d296c394c2280240/siphash24-1.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8ff44ce166452993fea267ea1b2fd089d8e7f103b13d360da441f12b0df121d", size = 100567, upload-time = "2024-10-15T13:41:17.435Z" }, - { url = "https://files.pythonhosted.org/packages/f0/aa/736a0a2efae9a6f69ac1ee4d28c2274fcad2150349fac752d6c525c4e06e/siphash24-1.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4062548dcb1eef13bbe0356d6f8675bfe4571ef38d7103445daa82ba167240d1", size = 105630, upload-time = "2024-10-15T13:41:18.578Z" }, - { url = "https://files.pythonhosted.org/packages/79/52/1afbd70142d3db093d49197e3abe15ca2f1a14678299327ba776944b4771/siphash24-1.7-cp311-cp311-win32.whl", hash = "sha256:7b4ea29376b688fbcc3d25707c15a9dfe7b4ebbc4322878d75bb77e199210a39", size = 67648, upload-time = "2024-10-15T13:41:19.606Z" }, - { url = "https://files.pythonhosted.org/packages/b5/1d/bedcd04c2d1d199c9f6b3e61a6caae0e17257696c9f49594e49856b17a99/siphash24-1.7-cp311-cp311-win_amd64.whl", hash = "sha256:ec06104e6ef1e512ee30f1b8aeae2b83c0f55f12a94042f0df5a87d43a1f4c52", size = 80046, upload-time = "2024-10-15T13:41:20.654Z" }, - { url = "https://files.pythonhosted.org/packages/3e/62/93e552af9535a416f684327f870143ee42fc9e816091672467cdfd62cce6/siphash24-1.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:76a64ff0cdd192e4d0a391956d9b121c56ed56e773c5ab7eb7c3e035fd16e8cb", size = 82084, upload-time = "2024-10-15T13:41:21.776Z" }, - { url = "https://files.pythonhosted.org/packages/59/3e/b0791ab53aa9ac191b71a021eab2e75baa7c27d7feb7ec148d7961d148ba/siphash24-1.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:49ca649bc7437d614f758891deade3b187832792a853269219e77f10509f82fe", size = 76233, upload-time = "2024-10-15T13:41:22.787Z" }, - { url = "https://files.pythonhosted.org/packages/29/4c/4c1b809bf302e9b60f3ec09ba115b2a4ac1ff6755735ee8884924fcdb45e/siphash24-1.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc37dd0aed23f76bd257fbd2953fd5d954b329d7463c6ff57263a2699c52dde6", size = 98188, upload-time = "2024-10-15T13:41:24.327Z" }, - { url = "https://files.pythonhosted.org/packages/96/bf/e6b49f8ff88130bd224f291ea77d30fdde4df5f6572c519aca5d8fc8a27c/siphash24-1.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eea490a200891905856b6ad0f9c56d4ec787876220bcb34c49441b2566b97887", size = 102946, upload-time = "2024-10-15T13:41:25.633Z" }, - { url = "https://files.pythonhosted.org/packages/3d/75/45c831626013950fb2ea715c218c3397e5cf2328a67208bf5d8ff69aa9e6/siphash24-1.7-cp312-cp312-win32.whl", hash = "sha256:69eb8c2c112a738875bb283cd53ef5e86874bc5aed17f3020b38e9174208fb79", size = 68323, upload-time = "2024-10-15T13:41:27.349Z" }, - { url = "https://files.pythonhosted.org/packages/e0/d3/39190c40a68defd19b99c1082dd7455543a52283803bfa111b0e45fae968/siphash24-1.7-cp312-cp312-win_amd64.whl", hash = "sha256:7459569ea4669b6feeaf7d299fc5157cc5c69ca1231dc0decb7a7da2397c782e", size = 81000, upload-time = "2024-10-15T13:41:28.364Z" }, + { url = "https://files.pythonhosted.org/packages/82/23/f53f5bd8866c6ea3abe434c9f208e76ea027210d8b75cd0e0dc849661c7a/siphash24-1.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4662ac616bce4d3c9d6003a0d398e56f8be408fc53a166b79fad08d4f34268e", size = 76930, upload-time = "2025-09-02T20:41:00.869Z" }, + { url = "https://files.pythonhosted.org/packages/0b/25/aebf246904424a06e7ffb7a40cfa9ea9e590ea0fac82e182e0f5d1f1d7ef/siphash24-1.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:53d6bed0951a99c6d2891fa6f8acfd5ca80c3e96c60bcee99f6fa01a04773b1c", size = 74315, upload-time = "2025-09-02T20:41:02.38Z" }, + { url = "https://files.pythonhosted.org/packages/59/3f/7010407c3416ef052d46550d54afb2581fb247018fc6500af8c66669eff2/siphash24-1.8-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d114c03648630e9e07dac2fe95442404e4607adca91640d274ece1a4fa71123e", size = 99756, upload-time = "2025-09-02T20:41:03.902Z" }, + { url = "https://files.pythonhosted.org/packages/d4/9f/09c734833e69badd7e3faed806b4372bd6564ae0946bd250d5239885914f/siphash24-1.8-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:88c1a55ff82b127c5d3b96927a430d8859e6a98846a5b979833ac790682dd91b", size = 104044, upload-time = "2025-09-02T20:41:05.505Z" }, + { url = "https://files.pythonhosted.org/packages/24/30/56a26d9141a34433da221f732599e2b23d2d70a966c249a9f00feb9a2915/siphash24-1.8-cp311-cp311-win32.whl", hash = "sha256:9430255e6a1313470f52c07c4a4643c451a5b2853f6d4008e4dda05cafb6ce7c", size = 62196, upload-time = "2025-09-02T20:41:07.299Z" }, + { url = "https://files.pythonhosted.org/packages/47/b2/11b0ae63fd374652544e1b12f72ba2cc3fe6c93c1483bd8ff6935b0a8a4b/siphash24-1.8-cp311-cp311-win_amd64.whl", hash = "sha256:1e4b37e4ef0b4496169adce2a58b6c3f230b5852dfa5f7ad0b2d664596409e47", size = 77162, upload-time = "2025-09-02T20:41:08.878Z" }, + { url = "https://files.pythonhosted.org/packages/7f/82/ce3545ce8052ac7ca104b183415a27ec3335e5ed51978fdd7b433f3cfe5b/siphash24-1.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:df5ed437c6e6cc96196b38728e57cd30b0427df45223475a90e173f5015ef5ba", size = 78136, upload-time = "2025-09-02T20:41:10.083Z" }, + { url = "https://files.pythonhosted.org/packages/15/88/896c3b91bc9deb78c415448b1db67343917f35971a9e23a5967a9d323b8a/siphash24-1.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f4ef78abdf811325c7089a35504df339c48c0007d4af428a044431d329721e56", size = 74588, upload-time = "2025-09-02T20:41:11.251Z" }, + { url = "https://files.pythonhosted.org/packages/12/fd/8dad3f5601db485ba862e1c1f91a5d77fb563650856a6708e9acb40ee53c/siphash24-1.8-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:065eff55c4fefb3a29fd26afb2c072abf7f668ffd53b91d41f92a1c485fcbe5c", size = 98655, upload-time = "2025-09-02T20:41:12.45Z" }, + { url = "https://files.pythonhosted.org/packages/e3/cc/e0c352624c1f2faad270aeb5cce6e173977ef66b9b5e918aa6f32af896bf/siphash24-1.8-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ac6fa84ebfd47677262aa0bcb0f5a70f796f5fc5704b287ee1b65a3bd4fb7a5d", size = 103217, upload-time = "2025-09-02T20:41:13.746Z" }, + { url = "https://files.pythonhosted.org/packages/5b/f6/0b1675bea4d40affcae642d9c7337702a4138b93c544230280712403e968/siphash24-1.8-cp312-cp312-win32.whl", hash = "sha256:6582f73615552ca055e51e03cb02a28e570a641a7f500222c86c2d811b5037eb", size = 63114, upload-time = "2025-09-02T20:41:14.972Z" }, + { url = "https://files.pythonhosted.org/packages/3d/39/afefef85d72ed8b5cf1aa9283f712e3cd43c9682fabbc809dec54baa8452/siphash24-1.8-cp312-cp312-win_amd64.whl", hash = "sha256:44ea6d794a7cbe184e1e1da2df81c5ebb672ab3867935c3e87c08bb0c2fa4879", size = 76232, upload-time = "2025-09-02T20:41:16.112Z" }, ] [[package]] @@ -4833,9 +4838,9 @@ wheels = [ [[package]] name = "spidev" -version = "3.7" +version = "3.8" source = { registry = "https://pypi.org/simple" } -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" } +sdist = { url = "https://files.pythonhosted.org/packages/67/87/039b6eeea781598015b538691bc174cc0bf77df9d4d2d3b8bf9245c0de8c/spidev-3.8.tar.gz", hash = "sha256:2bc02fb8c6312d519ebf1f4331067427c0921d3f77b8bcaf05189a2e8b8382c0", size = 13893, upload-time = "2025-09-15T18:56:20.672Z" } [[package]] name = "sympy" @@ -4872,14 +4877,14 @@ wheels = [ [[package]] name = "types-requests" -version = "2.32.4.20250809" +version = "2.32.4.20250913" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ed/b0/9355adb86ec84d057fea765e4c49cce592aaf3d5117ce5609a95a7fc3dac/types_requests-2.32.4.20250809.tar.gz", hash = "sha256:d8060de1c8ee599311f56ff58010fb4902f462a1470802cf9f6ed27bc46c4df3", size = 23027, upload-time = "2025-08-09T03:17:10.664Z" } +sdist = { url = "https://files.pythonhosted.org/packages/36/27/489922f4505975b11de2b5ad07b4fe1dca0bca9be81a703f26c5f3acfce5/types_requests-2.32.4.20250913.tar.gz", hash = "sha256:abd6d4f9ce3a9383f269775a9835a4c24e5cd6b9f647d64f88aa4613c33def5d", size = 23113, upload-time = "2025-09-13T02:40:02.309Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/6f/ec0012be842b1d888d46884ac5558fd62aeae1f0ec4f7a581433d890d4b5/types_requests-2.32.4.20250809-py3-none-any.whl", hash = "sha256:f73d1832fb519ece02c85b1f09d5f0dd3108938e7d47e7f94bbfa18a6782b163", size = 20644, upload-time = "2025-08-09T03:17:09.716Z" }, + { url = "https://files.pythonhosted.org/packages/2a/20/9a227ea57c1285986c4cf78400d0a91615d25b24e257fd9e2969606bdfae/types_requests-2.32.4.20250913-py3-none-any.whl", hash = "sha256:78c9c1fffebbe0fa487a418e0fa5252017e9c60d1a2da394077f1780f655d7e1", size = 20658, upload-time = "2025-09-13T02:40:01.115Z" }, ] [[package]] @@ -4976,7 +4981,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 = [ @@ -5033,42 +5038,42 @@ wheels = [ [[package]] name = "zstandard" -version = "0.24.0" +version = "0.25.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/09/1b/c20b2ef1d987627765dcd5bf1dadb8ef6564f00a87972635099bb76b7a05/zstandard-0.24.0.tar.gz", hash = "sha256:fe3198b81c00032326342d973e526803f183f97aa9e9a98e3f897ebafe21178f", size = 905681, upload-time = "2025-08-17T18:36:36.352Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/aa/3e0508d5a5dd96529cdc5a97011299056e14c6505b678fd58938792794b1/zstandard-0.25.0.tar.gz", hash = "sha256:7713e1179d162cf5c7906da876ec2ccb9c3a9dcbdffef0cc7f70c3667a205f0b", size = 711513, upload-time = "2025-09-14T22:15:54.002Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/01/1f/5c72806f76043c0ef9191a2b65281dacdf3b65b0828eb13bb2c987c4fb90/zstandard-0.24.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:addfc23e3bd5f4b6787b9ca95b2d09a1a67ad5a3c318daaa783ff90b2d3a366e", size = 795228, upload-time = "2025-08-17T18:21:46.978Z" }, - { url = "https://files.pythonhosted.org/packages/0b/ba/3059bd5cd834666a789251d14417621b5c61233bd46e7d9023ea8bc1043a/zstandard-0.24.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6b005bcee4be9c3984b355336283afe77b2defa76ed6b89332eced7b6fa68b68", size = 640520, upload-time = "2025-08-17T18:21:48.162Z" }, - { url = "https://files.pythonhosted.org/packages/57/07/f0e632bf783f915c1fdd0bf68614c4764cae9dd46ba32cbae4dd659592c3/zstandard-0.24.0-cp311-cp311-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:3f96a9130171e01dbb6c3d4d9925d604e2131a97f540e223b88ba45daf56d6fb", size = 5347682, upload-time = "2025-08-17T18:21:50.266Z" }, - { url = "https://files.pythonhosted.org/packages/a6/4c/63523169fe84773a7462cd090b0989cb7c7a7f2a8b0a5fbf00009ba7d74d/zstandard-0.24.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd0d3d16e63873253bad22b413ec679cf6586e51b5772eb10733899832efec42", size = 5057650, upload-time = "2025-08-17T18:21:52.634Z" }, - { url = "https://files.pythonhosted.org/packages/c6/16/49013f7ef80293f5cebf4c4229535a9f4c9416bbfd238560edc579815dbe/zstandard-0.24.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:b7a8c30d9bf4bd5e4dcfe26900bef0fcd9749acde45cdf0b3c89e2052fda9a13", size = 5404893, upload-time = "2025-08-17T18:21:54.54Z" }, - { url = "https://files.pythonhosted.org/packages/4d/38/78e8bcb5fc32a63b055f2b99e0be49b506f2351d0180173674f516cf8a7a/zstandard-0.24.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:52cd7d9fa0a115c9446abb79b06a47171b7d916c35c10e0c3aa6f01d57561382", size = 5452389, upload-time = "2025-08-17T18:21:56.822Z" }, - { url = "https://files.pythonhosted.org/packages/55/8a/81671f05619edbacd49bd84ce6899a09fc8299be20c09ae92f6618ccb92d/zstandard-0.24.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a0f6fc2ea6e07e20df48752e7700e02e1892c61f9a6bfbacaf2c5b24d5ad504b", size = 5558888, upload-time = "2025-08-17T18:21:58.68Z" }, - { url = "https://files.pythonhosted.org/packages/49/cc/e83feb2d7d22d1f88434defbaeb6e5e91f42a4f607b5d4d2d58912b69d67/zstandard-0.24.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e46eb6702691b24ddb3e31e88b4a499e31506991db3d3724a85bd1c5fc3cfe4e", size = 5048038, upload-time = "2025-08-17T18:22:00.642Z" }, - { url = "https://files.pythonhosted.org/packages/08/c3/7a5c57ff49ef8943877f85c23368c104c2aea510abb339a2dc31ad0a27c3/zstandard-0.24.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5e3b9310fd7f0d12edc75532cd9a56da6293840c84da90070d692e0bb15f186", size = 5573833, upload-time = "2025-08-17T18:22:02.402Z" }, - { url = "https://files.pythonhosted.org/packages/f9/00/64519983cd92535ba4bdd4ac26ac52db00040a52d6c4efb8d1764abcc343/zstandard-0.24.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:76cdfe7f920738ea871f035568f82bad3328cbc8d98f1f6988264096b5264efd", size = 4961072, upload-time = "2025-08-17T18:22:04.384Z" }, - { url = "https://files.pythonhosted.org/packages/72/ab/3a08a43067387d22994fc87c3113636aa34ccd2914a4d2d188ce365c5d85/zstandard-0.24.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:3f2fe35ec84908dddf0fbf66b35d7c2878dbe349552dd52e005c755d3493d61c", size = 5268462, upload-time = "2025-08-17T18:22:06.095Z" }, - { url = "https://files.pythonhosted.org/packages/49/cf/2abb3a1ad85aebe18c53e7eca73223f1546ddfa3bf4d2fb83fc5a064c5ca/zstandard-0.24.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:aa705beb74ab116563f4ce784fa94771f230c05d09ab5de9c397793e725bb1db", size = 5443319, upload-time = "2025-08-17T18:22:08.572Z" }, - { url = "https://files.pythonhosted.org/packages/40/42/0dd59fc2f68f1664cda11c3b26abdf987f4e57cb6b6b0f329520cd074552/zstandard-0.24.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:aadf32c389bb7f02b8ec5c243c38302b92c006da565e120dfcb7bf0378f4f848", size = 5822355, upload-time = "2025-08-17T18:22:10.537Z" }, - { url = "https://files.pythonhosted.org/packages/99/c0/ea4e640fd4f7d58d6f87a1e7aca11fb886ac24db277fbbb879336c912f63/zstandard-0.24.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e40cd0fc734aa1d4bd0e7ad102fd2a1aefa50ce9ef570005ffc2273c5442ddc3", size = 5365257, upload-time = "2025-08-17T18:22:13.159Z" }, - { url = "https://files.pythonhosted.org/packages/27/a9/92da42a5c4e7e4003271f2e1f0efd1f37cfd565d763ad3604e9597980a1c/zstandard-0.24.0-cp311-cp311-win32.whl", hash = "sha256:cda61c46343809ecda43dc620d1333dd7433a25d0a252f2dcc7667f6331c7b61", size = 435559, upload-time = "2025-08-17T18:22:17.29Z" }, - { url = "https://files.pythonhosted.org/packages/e2/8e/2c8e5c681ae4937c007938f954a060fa7c74f36273b289cabdb5ef0e9a7e/zstandard-0.24.0-cp311-cp311-win_amd64.whl", hash = "sha256:3b95fc06489aa9388400d1aab01a83652bc040c9c087bd732eb214909d7fb0dd", size = 505070, upload-time = "2025-08-17T18:22:14.808Z" }, - { url = "https://files.pythonhosted.org/packages/52/10/a2f27a66bec75e236b575c9f7b0d7d37004a03aa2dcde8e2decbe9ed7b4d/zstandard-0.24.0-cp311-cp311-win_arm64.whl", hash = "sha256:ad9fd176ff6800a0cf52bcf59c71e5de4fa25bf3ba62b58800e0f84885344d34", size = 461507, upload-time = "2025-08-17T18:22:15.964Z" }, - { url = "https://files.pythonhosted.org/packages/26/e9/0bd281d9154bba7fc421a291e263911e1d69d6951aa80955b992a48289f6/zstandard-0.24.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a2bda8f2790add22773ee7a4e43c90ea05598bffc94c21c40ae0a9000b0133c3", size = 795710, upload-time = "2025-08-17T18:22:19.189Z" }, - { url = "https://files.pythonhosted.org/packages/36/26/b250a2eef515caf492e2d86732e75240cdac9d92b04383722b9753590c36/zstandard-0.24.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cc76de75300f65b8eb574d855c12518dc25a075dadb41dd18f6322bda3fe15d5", size = 640336, upload-time = "2025-08-17T18:22:20.466Z" }, - { url = "https://files.pythonhosted.org/packages/79/bf/3ba6b522306d9bf097aac8547556b98a4f753dc807a170becaf30dcd6f01/zstandard-0.24.0-cp312-cp312-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:d2b3b4bda1a025b10fe0269369475f420177f2cb06e0f9d32c95b4873c9f80b8", size = 5342533, upload-time = "2025-08-17T18:22:22.326Z" }, - { url = "https://files.pythonhosted.org/packages/ea/ec/22bc75bf054e25accdf8e928bc68ab36b4466809729c554ff3a1c1c8bce6/zstandard-0.24.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9b84c6c210684286e504022d11ec294d2b7922d66c823e87575d8b23eba7c81f", size = 5062837, upload-time = "2025-08-17T18:22:24.416Z" }, - { url = "https://files.pythonhosted.org/packages/48/cc/33edfc9d286e517fb5b51d9c3210e5bcfce578d02a675f994308ca587ae1/zstandard-0.24.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c59740682a686bf835a1a4d8d0ed1eefe31ac07f1c5a7ed5f2e72cf577692b00", size = 5393855, upload-time = "2025-08-17T18:22:26.786Z" }, - { url = "https://files.pythonhosted.org/packages/73/36/59254e9b29da6215fb3a717812bf87192d89f190f23817d88cb8868c47ac/zstandard-0.24.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:6324fde5cf5120fbf6541d5ff3c86011ec056e8d0f915d8e7822926a5377193a", size = 5451058, upload-time = "2025-08-17T18:22:28.885Z" }, - { url = "https://files.pythonhosted.org/packages/9a/c7/31674cb2168b741bbbe71ce37dd397c9c671e73349d88ad3bca9e9fae25b/zstandard-0.24.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:51a86bd963de3f36688553926a84e550d45d7f9745bd1947d79472eca27fcc75", size = 5546619, upload-time = "2025-08-17T18:22:31.115Z" }, - { url = "https://files.pythonhosted.org/packages/e6/01/1a9f22239f08c00c156f2266db857545ece66a6fc0303d45c298564bc20b/zstandard-0.24.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d82ac87017b734f2fb70ff93818c66f0ad2c3810f61040f077ed38d924e19980", size = 5046676, upload-time = "2025-08-17T18:22:33.077Z" }, - { url = "https://files.pythonhosted.org/packages/a7/91/6c0cf8fa143a4988a0361380ac2ef0d7cb98a374704b389fbc38b5891712/zstandard-0.24.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:92ea7855d5bcfb386c34557516c73753435fb2d4a014e2c9343b5f5ba148b5d8", size = 5576381, upload-time = "2025-08-17T18:22:35.391Z" }, - { url = "https://files.pythonhosted.org/packages/e2/77/1526080e22e78871e786ccf3c84bf5cec9ed25110a9585507d3c551da3d6/zstandard-0.24.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3adb4b5414febf074800d264ddf69ecade8c658837a83a19e8ab820e924c9933", size = 4953403, upload-time = "2025-08-17T18:22:37.266Z" }, - { url = "https://files.pythonhosted.org/packages/6e/d0/a3a833930bff01eab697eb8abeafb0ab068438771fa066558d96d7dafbf9/zstandard-0.24.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:6374feaf347e6b83ec13cc5dcfa70076f06d8f7ecd46cc71d58fac798ff08b76", size = 5267396, upload-time = "2025-08-17T18:22:39.757Z" }, - { url = "https://files.pythonhosted.org/packages/f3/5e/90a0db9a61cd4769c06374297ecfcbbf66654f74cec89392519deba64d76/zstandard-0.24.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:13fc548e214df08d896ee5f29e1f91ee35db14f733fef8eabea8dca6e451d1e2", size = 5433269, upload-time = "2025-08-17T18:22:42.131Z" }, - { url = "https://files.pythonhosted.org/packages/ce/58/fc6a71060dd67c26a9c5566e0d7c99248cbe5abfda6b3b65b8f1a28d59f7/zstandard-0.24.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:0a416814608610abf5488889c74e43ffa0343ca6cf43957c6b6ec526212422da", size = 5814203, upload-time = "2025-08-17T18:22:44.017Z" }, - { url = "https://files.pythonhosted.org/packages/5c/6a/89573d4393e3ecbfa425d9a4e391027f58d7810dec5cdb13a26e4cdeef5c/zstandard-0.24.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0d66da2649bb0af4471699aeb7a83d6f59ae30236fb9f6b5d20fb618ef6c6777", size = 5359622, upload-time = "2025-08-17T18:22:45.802Z" }, - { url = "https://files.pythonhosted.org/packages/60/ff/2cbab815d6f02a53a9d8d8703bc727d8408a2e508143ca9af6c3cca2054b/zstandard-0.24.0-cp312-cp312-win32.whl", hash = "sha256:ff19efaa33e7f136fe95f9bbcc90ab7fb60648453b03f95d1de3ab6997de0f32", size = 435968, upload-time = "2025-08-17T18:22:49.493Z" }, - { url = "https://files.pythonhosted.org/packages/ce/a3/8f96b8ddb7ad12344218fbd0fd2805702dafd126ae9f8a1fb91eef7b33da/zstandard-0.24.0-cp312-cp312-win_amd64.whl", hash = "sha256:bc05f8a875eb651d1cc62e12a4a0e6afa5cd0cc231381adb830d2e9c196ea895", size = 505195, upload-time = "2025-08-17T18:22:47.193Z" }, - { url = "https://files.pythonhosted.org/packages/a3/4a/bfca20679da63bfc236634ef2e4b1b4254203098b0170e3511fee781351f/zstandard-0.24.0-cp312-cp312-win_arm64.whl", hash = "sha256:b04c94718f7a8ed7cdd01b162b6caa1954b3c9d486f00ecbbd300f149d2b2606", size = 461605, upload-time = "2025-08-17T18:22:48.317Z" }, + { url = "https://files.pythonhosted.org/packages/2a/83/c3ca27c363d104980f1c9cee1101cc8ba724ac8c28a033ede6aab89585b1/zstandard-0.25.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:933b65d7680ea337180733cf9e87293cc5500cc0eb3fc8769f4d3c88d724ec5c", size = 795254, upload-time = "2025-09-14T22:16:26.137Z" }, + { url = "https://files.pythonhosted.org/packages/ac/4d/e66465c5411a7cf4866aeadc7d108081d8ceba9bc7abe6b14aa21c671ec3/zstandard-0.25.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3f79487c687b1fc69f19e487cd949bf3aae653d181dfb5fde3bf6d18894706f", size = 640559, upload-time = "2025-09-14T22:16:27.973Z" }, + { url = "https://files.pythonhosted.org/packages/12/56/354fe655905f290d3b147b33fe946b0f27e791e4b50a5f004c802cb3eb7b/zstandard-0.25.0-cp311-cp311-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:0bbc9a0c65ce0eea3c34a691e3c4b6889f5f3909ba4822ab385fab9057099431", size = 5348020, upload-time = "2025-09-14T22:16:29.523Z" }, + { url = "https://files.pythonhosted.org/packages/3b/13/2b7ed68bd85e69a2069bcc72141d378f22cae5a0f3b353a2c8f50ef30c1b/zstandard-0.25.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:01582723b3ccd6939ab7b3a78622c573799d5d8737b534b86d0e06ac18dbde4a", size = 5058126, upload-time = "2025-09-14T22:16:31.811Z" }, + { url = "https://files.pythonhosted.org/packages/c9/dd/fdaf0674f4b10d92cb120ccff58bbb6626bf8368f00ebfd2a41ba4a0dc99/zstandard-0.25.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5f1ad7bf88535edcf30038f6919abe087f606f62c00a87d7e33e7fc57cb69fcc", size = 5405390, upload-time = "2025-09-14T22:16:33.486Z" }, + { url = "https://files.pythonhosted.org/packages/0f/67/354d1555575bc2490435f90d67ca4dd65238ff2f119f30f72d5cde09c2ad/zstandard-0.25.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:06acb75eebeedb77b69048031282737717a63e71e4ae3f77cc0c3b9508320df6", size = 5452914, upload-time = "2025-09-14T22:16:35.277Z" }, + { url = "https://files.pythonhosted.org/packages/bb/1f/e9cfd801a3f9190bf3e759c422bbfd2247db9d7f3d54a56ecde70137791a/zstandard-0.25.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9300d02ea7c6506f00e627e287e0492a5eb0371ec1670ae852fefffa6164b072", size = 5559635, upload-time = "2025-09-14T22:16:37.141Z" }, + { url = "https://files.pythonhosted.org/packages/21/88/5ba550f797ca953a52d708c8e4f380959e7e3280af029e38fbf47b55916e/zstandard-0.25.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bfd06b1c5584b657a2892a6014c2f4c20e0db0208c159148fa78c65f7e0b0277", size = 5048277, upload-time = "2025-09-14T22:16:38.807Z" }, + { url = "https://files.pythonhosted.org/packages/46/c0/ca3e533b4fa03112facbe7fbe7779cb1ebec215688e5df576fe5429172e0/zstandard-0.25.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f373da2c1757bb7f1acaf09369cdc1d51d84131e50d5fa9863982fd626466313", size = 5574377, upload-time = "2025-09-14T22:16:40.523Z" }, + { url = "https://files.pythonhosted.org/packages/12/9b/3fb626390113f272abd0799fd677ea33d5fc3ec185e62e6be534493c4b60/zstandard-0.25.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6c0e5a65158a7946e7a7affa6418878ef97ab66636f13353b8502d7ea03c8097", size = 4961493, upload-time = "2025-09-14T22:16:43.3Z" }, + { url = "https://files.pythonhosted.org/packages/cb/d3/23094a6b6a4b1343b27ae68249daa17ae0651fcfec9ed4de09d14b940285/zstandard-0.25.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c8e167d5adf59476fa3e37bee730890e389410c354771a62e3c076c86f9f7778", size = 5269018, upload-time = "2025-09-14T22:16:45.292Z" }, + { url = "https://files.pythonhosted.org/packages/8c/a7/bb5a0c1c0f3f4b5e9d5b55198e39de91e04ba7c205cc46fcb0f95f0383c1/zstandard-0.25.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:98750a309eb2f020da61e727de7d7ba3c57c97cf6213f6f6277bb7fb42a8e065", size = 5443672, upload-time = "2025-09-14T22:16:47.076Z" }, + { url = "https://files.pythonhosted.org/packages/27/22/503347aa08d073993f25109c36c8d9f029c7d5949198050962cb568dfa5e/zstandard-0.25.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:22a086cff1b6ceca18a8dd6096ec631e430e93a8e70a9ca5efa7561a00f826fa", size = 5822753, upload-time = "2025-09-14T22:16:49.316Z" }, + { url = "https://files.pythonhosted.org/packages/e2/be/94267dc6ee64f0f8ba2b2ae7c7a2df934a816baaa7291db9e1aa77394c3c/zstandard-0.25.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:72d35d7aa0bba323965da807a462b0966c91608ef3a48ba761678cb20ce5d8b7", size = 5366047, upload-time = "2025-09-14T22:16:51.328Z" }, + { url = "https://files.pythonhosted.org/packages/7b/a3/732893eab0a3a7aecff8b99052fecf9f605cf0fb5fb6d0290e36beee47a4/zstandard-0.25.0-cp311-cp311-win32.whl", hash = "sha256:f5aeea11ded7320a84dcdd62a3d95b5186834224a9e55b92ccae35d21a8b63d4", size = 436484, upload-time = "2025-09-14T22:16:55.005Z" }, + { url = "https://files.pythonhosted.org/packages/43/a3/c6155f5c1cce691cb80dfd38627046e50af3ee9ddc5d0b45b9b063bfb8c9/zstandard-0.25.0-cp311-cp311-win_amd64.whl", hash = "sha256:daab68faadb847063d0c56f361a289c4f268706b598afbf9ad113cbe5c38b6b2", size = 506183, upload-time = "2025-09-14T22:16:52.753Z" }, + { url = "https://files.pythonhosted.org/packages/8c/3e/8945ab86a0820cc0e0cdbf38086a92868a9172020fdab8a03ac19662b0e5/zstandard-0.25.0-cp311-cp311-win_arm64.whl", hash = "sha256:22a06c5df3751bb7dc67406f5374734ccee8ed37fc5981bf1ad7041831fa1137", size = 462533, upload-time = "2025-09-14T22:16:53.878Z" }, + { url = "https://files.pythonhosted.org/packages/82/fc/f26eb6ef91ae723a03e16eddb198abcfce2bc5a42e224d44cc8b6765e57e/zstandard-0.25.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7b3c3a3ab9daa3eed242d6ecceead93aebbb8f5f84318d82cee643e019c4b73b", size = 795738, upload-time = "2025-09-14T22:16:56.237Z" }, + { url = "https://files.pythonhosted.org/packages/aa/1c/d920d64b22f8dd028a8b90e2d756e431a5d86194caa78e3819c7bf53b4b3/zstandard-0.25.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:913cbd31a400febff93b564a23e17c3ed2d56c064006f54efec210d586171c00", size = 640436, upload-time = "2025-09-14T22:16:57.774Z" }, + { url = "https://files.pythonhosted.org/packages/53/6c/288c3f0bd9fcfe9ca41e2c2fbfd17b2097f6af57b62a81161941f09afa76/zstandard-0.25.0-cp312-cp312-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:011d388c76b11a0c165374ce660ce2c8efa8e5d87f34996aa80f9c0816698b64", size = 5343019, upload-time = "2025-09-14T22:16:59.302Z" }, + { url = "https://files.pythonhosted.org/packages/1e/15/efef5a2f204a64bdb5571e6161d49f7ef0fffdbca953a615efbec045f60f/zstandard-0.25.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6dffecc361d079bb48d7caef5d673c88c8988d3d33fb74ab95b7ee6da42652ea", size = 5063012, upload-time = "2025-09-14T22:17:01.156Z" }, + { url = "https://files.pythonhosted.org/packages/b7/37/a6ce629ffdb43959e92e87ebdaeebb5ac81c944b6a75c9c47e300f85abdf/zstandard-0.25.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7149623bba7fdf7e7f24312953bcf73cae103db8cae49f8154dd1eadc8a29ecb", size = 5394148, upload-time = "2025-09-14T22:17:03.091Z" }, + { url = "https://files.pythonhosted.org/packages/e3/79/2bf870b3abeb5c070fe2d670a5a8d1057a8270f125ef7676d29ea900f496/zstandard-0.25.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:6a573a35693e03cf1d67799fd01b50ff578515a8aeadd4595d2a7fa9f3ec002a", size = 5451652, upload-time = "2025-09-14T22:17:04.979Z" }, + { url = "https://files.pythonhosted.org/packages/53/60/7be26e610767316c028a2cbedb9a3beabdbe33e2182c373f71a1c0b88f36/zstandard-0.25.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5a56ba0db2d244117ed744dfa8f6f5b366e14148e00de44723413b2f3938a902", size = 5546993, upload-time = "2025-09-14T22:17:06.781Z" }, + { url = "https://files.pythonhosted.org/packages/85/c7/3483ad9ff0662623f3648479b0380d2de5510abf00990468c286c6b04017/zstandard-0.25.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:10ef2a79ab8e2974e2075fb984e5b9806c64134810fac21576f0668e7ea19f8f", size = 5046806, upload-time = "2025-09-14T22:17:08.415Z" }, + { url = "https://files.pythonhosted.org/packages/08/b3/206883dd25b8d1591a1caa44b54c2aad84badccf2f1de9e2d60a446f9a25/zstandard-0.25.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aaf21ba8fb76d102b696781bddaa0954b782536446083ae3fdaa6f16b25a1c4b", size = 5576659, upload-time = "2025-09-14T22:17:10.164Z" }, + { url = "https://files.pythonhosted.org/packages/9d/31/76c0779101453e6c117b0ff22565865c54f48f8bd807df2b00c2c404b8e0/zstandard-0.25.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1869da9571d5e94a85a5e8d57e4e8807b175c9e4a6294e3b66fa4efb074d90f6", size = 4953933, upload-time = "2025-09-14T22:17:11.857Z" }, + { url = "https://files.pythonhosted.org/packages/18/e1/97680c664a1bf9a247a280a053d98e251424af51f1b196c6d52f117c9720/zstandard-0.25.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:809c5bcb2c67cd0ed81e9229d227d4ca28f82d0f778fc5fea624a9def3963f91", size = 5268008, upload-time = "2025-09-14T22:17:13.627Z" }, + { url = "https://files.pythonhosted.org/packages/1e/73/316e4010de585ac798e154e88fd81bb16afc5c5cb1a72eeb16dd37e8024a/zstandard-0.25.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f27662e4f7dbf9f9c12391cb37b4c4c3cb90ffbd3b1fb9284dadbbb8935fa708", size = 5433517, upload-time = "2025-09-14T22:17:16.103Z" }, + { url = "https://files.pythonhosted.org/packages/5b/60/dd0f8cfa8129c5a0ce3ea6b7f70be5b33d2618013a161e1ff26c2b39787c/zstandard-0.25.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:99c0c846e6e61718715a3c9437ccc625de26593fea60189567f0118dc9db7512", size = 5814292, upload-time = "2025-09-14T22:17:17.827Z" }, + { url = "https://files.pythonhosted.org/packages/fc/5f/75aafd4b9d11b5407b641b8e41a57864097663699f23e9ad4dbb91dc6bfe/zstandard-0.25.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:474d2596a2dbc241a556e965fb76002c1ce655445e4e3bf38e5477d413165ffa", size = 5360237, upload-time = "2025-09-14T22:17:19.954Z" }, + { url = "https://files.pythonhosted.org/packages/ff/8d/0309daffea4fcac7981021dbf21cdb2e3427a9e76bafbcdbdf5392ff99a4/zstandard-0.25.0-cp312-cp312-win32.whl", hash = "sha256:23ebc8f17a03133b4426bcc04aabd68f8236eb78c3760f12783385171b0fd8bd", size = 436922, upload-time = "2025-09-14T22:17:24.398Z" }, + { url = "https://files.pythonhosted.org/packages/79/3b/fa54d9015f945330510cb5d0b0501e8253c127cca7ebe8ba46a965df18c5/zstandard-0.25.0-cp312-cp312-win_amd64.whl", hash = "sha256:ffef5a74088f1e09947aecf91011136665152e0b4b359c42be3373897fb39b01", size = 506276, upload-time = "2025-09-14T22:17:21.429Z" }, + { url = "https://files.pythonhosted.org/packages/ea/6b/8b51697e5319b1f9ac71087b0af9a40d8a6288ff8025c36486e0c12abcc4/zstandard-0.25.0-cp312-cp312-win_arm64.whl", hash = "sha256:181eb40e0b6a29b3cd2849f825e0fa34397f649170673d385f3598ae17cca2e9", size = 462679, upload-time = "2025-09-14T22:17:23.147Z" }, ] From 61d5a5053407c15e801303062bfa5ac1d9eec027 Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Sun, 21 Sep 2025 22:44:14 -0700 Subject: [PATCH 036/910] Revert "fix `is_dirty` when switching branch with `updated` (#36162)" This reverts commit 30c388aea8961113555211201c6298099d53e4df. --- system/updated/updated.py | 2 -- system/version.py | 7 +++---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/system/updated/updated.py b/system/updated/updated.py index f9fad5f6f..a80a663ec 100755 --- a/system/updated/updated.py +++ b/system/updated/updated.py @@ -113,7 +113,6 @@ def setup_git_options(cwd: str) -> None: ("protocol.version", "2"), ("gc.auto", "0"), ("gc.autoDetach", "false"), - ("remote.origin.fetch", "+refs/heads/*:refs/remotes/origin/*"), ] for option, value in git_cfg: run(["git", "config", option, value], cwd) @@ -390,7 +389,6 @@ class Updater: cloudlog.info("git reset in progress") cmds = [ ["git", "checkout", "--force", "--no-recurse-submodules", "-B", branch, "FETCH_HEAD"], - ["git", "branch", "--set-upstream-to", f"origin/{branch}"], ["git", "reset", "--hard"], ["git", "clean", "-xdff"], ["git", "submodule", "sync"], diff --git a/system/version.py b/system/version.py index e32c3d603..5e4fcf5c3 100755 --- a/system/version.py +++ b/system/version.py @@ -37,7 +37,9 @@ def is_prebuilt(path: str = BASEDIR) -> bool: @cache def is_dirty(cwd: str = BASEDIR) -> bool: - if not get_origin() or not get_short_branch(): + origin = get_origin() + branch = get_branch() + if not origin or not branch: return True dirty = False @@ -50,9 +52,6 @@ def is_dirty(cwd: str = BASEDIR) -> bool: except subprocess.CalledProcessError: pass - branch = get_branch() - if not branch: - return True dirty = (subprocess.call(["git", "diff-index", "--quiet", branch, "--"], cwd=cwd)) != 0 except subprocess.CalledProcessError: cloudlog.exception("git subprocess failed while checking dirty") From 073503a6f203b8dde08ff13ac1cd3891835b09c2 Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Sun, 21 Sep 2025 23:53:07 -0700 Subject: [PATCH 037/910] fix `is_dirty` when fetching branch with `updated` (#36187) fix is_dirty --- system/updated/updated.py | 3 +++ system/version.py | 7 ++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/system/updated/updated.py b/system/updated/updated.py index a80a663ec..a4a1f8f34 100755 --- a/system/updated/updated.py +++ b/system/updated/updated.py @@ -382,6 +382,8 @@ class Updater: setup_git_options(OVERLAY_MERGED) + run(["git", "config", "--replace-all", "remote.origin.fetch", "+refs/heads/*:refs/remotes/origin/*"], OVERLAY_MERGED) + branch = self.target_branch git_fetch_output = run(["git", "fetch", "origin", branch], OVERLAY_MERGED) cloudlog.info("git fetch success: %s", git_fetch_output) @@ -389,6 +391,7 @@ class Updater: cloudlog.info("git reset in progress") cmds = [ ["git", "checkout", "--force", "--no-recurse-submodules", "-B", branch, "FETCH_HEAD"], + ["git", "branch", "--set-upstream-to", f"origin/{branch}"], ["git", "reset", "--hard"], ["git", "clean", "-xdff"], ["git", "submodule", "sync"], diff --git a/system/version.py b/system/version.py index 5e4fcf5c3..e32c3d603 100755 --- a/system/version.py +++ b/system/version.py @@ -37,9 +37,7 @@ def is_prebuilt(path: str = BASEDIR) -> bool: @cache def is_dirty(cwd: str = BASEDIR) -> bool: - origin = get_origin() - branch = get_branch() - if not origin or not branch: + if not get_origin() or not get_short_branch(): return True dirty = False @@ -52,6 +50,9 @@ def is_dirty(cwd: str = BASEDIR) -> bool: except subprocess.CalledProcessError: pass + branch = get_branch() + if not branch: + return True dirty = (subprocess.call(["git", "diff-index", "--quiet", branch, "--"], cwd=cwd)) != 0 except subprocess.CalledProcessError: cloudlog.exception("git subprocess failed while checking dirty") From cd335623791d78e73994d8c5237a65629c2201b6 Mon Sep 17 00:00:00 2001 From: commaci-public <60409688+commaci-public@users.noreply.github.com> Date: Mon, 22 Sep 2025 13:43:26 -0700 Subject: [PATCH 038/910] [bot] Update Python packages (#36188) Update Python packages Co-authored-by: Vehicle Researcher --- opendbc_repo | 2 +- tinygrad_repo | 2 +- uv.lock | 128 +++++++++++++++++++++++++------------------------- 3 files changed, 67 insertions(+), 65 deletions(-) diff --git a/opendbc_repo b/opendbc_repo index c70bd060c..6894a012c 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit c70bd060c6a410c1083186a1e4165e43a4eda0df +Subproject commit 6894a012cd7ec36678b8abd3d95dc79975d5dbe2 diff --git a/tinygrad_repo b/tinygrad_repo index 73c8dae60..a6fd96f62 160000 --- a/tinygrad_repo +++ b/tinygrad_repo @@ -1 +1 @@ -Subproject commit 73c8dae60d0eaa3a27a252f7967d2dab8378109e +Subproject commit a6fd96f62050efd4a2fe7c885d1a11f87e3c5b0a diff --git a/uv.lock b/uv.lock index c24d9dbbb..9c791b811 100644 --- a/uv.lock +++ b/uv.lock @@ -798,48 +798,50 @@ wheels = [ [[package]] name = "lxml" -version = "6.0.1" +version = "6.0.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8f/bd/f9d01fd4132d81c6f43ab01983caea69ec9614b913c290a26738431a015d/lxml-6.0.1.tar.gz", hash = "sha256:2b3a882ebf27dd026df3801a87cf49ff791336e0f94b0fad195db77e01240690", size = 4070214, upload-time = "2025-08-22T10:37:53.525Z" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/88/262177de60548e5a2bfc46ad28232c9e9cbde697bd94132aeb80364675cb/lxml-6.0.2.tar.gz", hash = "sha256:cd79f3367bd74b317dda655dc8fcfa304d9eb6e4fb06b7168c5cf27f96e0cd62", size = 4073426, upload-time = "2025-09-22T04:04:59.287Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/29/c8/262c1d19339ef644cdc9eb5aad2e85bd2d1fa2d7c71cdef3ede1a3eed84d/lxml-6.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c6acde83f7a3d6399e6d83c1892a06ac9b14ea48332a5fbd55d60b9897b9570a", size = 8422719, upload-time = "2025-08-22T10:32:24.848Z" }, - { url = "https://files.pythonhosted.org/packages/e5/d4/1b0afbeb801468a310642c3a6f6704e53c38a4a6eb1ca6faea013333e02f/lxml-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0d21c9cacb6a889cbb8eeb46c77ef2c1dd529cde10443fdeb1de847b3193c541", size = 4575763, upload-time = "2025-08-22T10:32:27.057Z" }, - { url = "https://files.pythonhosted.org/packages/5b/c1/8db9b5402bf52ceb758618313f7423cd54aea85679fcf607013707d854a8/lxml-6.0.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:847458b7cd0d04004895f1fb2cca8e7c0f8ec923c49c06b7a72ec2d48ea6aca2", size = 4943244, upload-time = "2025-08-22T10:32:28.847Z" }, - { url = "https://files.pythonhosted.org/packages/e7/78/838e115358dd2369c1c5186080dd874a50a691fb5cd80db6afe5e816e2c6/lxml-6.0.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1dc13405bf315d008fe02b1472d2a9d65ee1c73c0a06de5f5a45e6e404d9a1c0", size = 5081725, upload-time = "2025-08-22T10:32:30.666Z" }, - { url = "https://files.pythonhosted.org/packages/c7/b6/bdcb3a3ddd2438c5b1a1915161f34e8c85c96dc574b0ef3be3924f36315c/lxml-6.0.1-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70f540c229a8c0a770dcaf6d5af56a5295e0fc314fc7ef4399d543328054bcea", size = 5021238, upload-time = "2025-08-22T10:32:32.49Z" }, - { url = "https://files.pythonhosted.org/packages/73/e5/1bfb96185dc1a64c7c6fbb7369192bda4461952daa2025207715f9968205/lxml-6.0.1-cp311-cp311-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:d2f73aef768c70e8deb8c4742fca4fd729b132fda68458518851c7735b55297e", size = 5343744, upload-time = "2025-08-22T10:32:34.385Z" }, - { url = "https://files.pythonhosted.org/packages/a2/ae/df3ea9ebc3c493b9c6bdc6bd8c554ac4e147f8d7839993388aab57ec606d/lxml-6.0.1-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e7f4066b85a4fa25ad31b75444bd578c3ebe6b8ed47237896341308e2ce923c3", size = 5223477, upload-time = "2025-08-22T10:32:36.256Z" }, - { url = "https://files.pythonhosted.org/packages/37/b3/65e1e33600542c08bc03a4c5c9c306c34696b0966a424a3be6ffec8038ed/lxml-6.0.1-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:0cce65db0cd8c750a378639900d56f89f7d6af11cd5eda72fde054d27c54b8ce", size = 4676626, upload-time = "2025-08-22T10:32:38.793Z" }, - { url = "https://files.pythonhosted.org/packages/7a/46/ee3ed8f3a60e9457d7aea46542d419917d81dbfd5700fe64b2a36fb5ef61/lxml-6.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c372d42f3eee5844b69dcab7b8d18b2f449efd54b46ac76970d6e06b8e8d9a66", size = 5066042, upload-time = "2025-08-22T10:32:41.134Z" }, - { url = "https://files.pythonhosted.org/packages/9c/b9/8394538e7cdbeb3bfa36bc74924be1a4383e0bb5af75f32713c2c4aa0479/lxml-6.0.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:2e2b0e042e1408bbb1c5f3cfcb0f571ff4ac98d8e73f4bf37c5dd179276beedd", size = 4724714, upload-time = "2025-08-22T10:32:43.94Z" }, - { url = "https://files.pythonhosted.org/packages/b3/21/3ef7da1ea2a73976c1a5a311d7cde5d379234eec0968ee609517714940b4/lxml-6.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cc73bb8640eadd66d25c5a03175de6801f63c535f0f3cf50cac2f06a8211f420", size = 5247376, upload-time = "2025-08-22T10:32:46.263Z" }, - { url = "https://files.pythonhosted.org/packages/26/7d/0980016f124f00c572cba6f4243e13a8e80650843c66271ee692cddf25f3/lxml-6.0.1-cp311-cp311-win32.whl", hash = "sha256:7c23fd8c839708d368e406282d7953cee5134f4592ef4900026d84566d2b4c88", size = 3609499, upload-time = "2025-08-22T10:32:48.156Z" }, - { url = "https://files.pythonhosted.org/packages/b1/08/28440437521f265eff4413eb2a65efac269c4c7db5fd8449b586e75d8de2/lxml-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:2516acc6947ecd3c41a4a4564242a87c6786376989307284ddb115f6a99d927f", size = 4036003, upload-time = "2025-08-22T10:32:50.662Z" }, - { url = "https://files.pythonhosted.org/packages/7b/dc/617e67296d98099213a505d781f04804e7b12923ecd15a781a4ab9181992/lxml-6.0.1-cp311-cp311-win_arm64.whl", hash = "sha256:cb46f8cfa1b0334b074f40c0ff94ce4d9a6755d492e6c116adb5f4a57fb6ad96", size = 3679662, upload-time = "2025-08-22T10:32:52.739Z" }, - { url = "https://files.pythonhosted.org/packages/b0/a9/82b244c8198fcdf709532e39a1751943a36b3e800b420adc739d751e0299/lxml-6.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c03ac546adaabbe0b8e4a15d9ad815a281afc8d36249c246aecf1aaad7d6f200", size = 8422788, upload-time = "2025-08-22T10:32:56.612Z" }, - { url = "https://files.pythonhosted.org/packages/c9/8d/1ed2bc20281b0e7ed3e6c12b0a16e64ae2065d99be075be119ba88486e6d/lxml-6.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:33b862c7e3bbeb4ba2c96f3a039f925c640eeba9087a4dc7a572ec0f19d89392", size = 4593547, upload-time = "2025-08-22T10:32:59.016Z" }, - { url = "https://files.pythonhosted.org/packages/76/53/d7fd3af95b72a3493bf7fbe842a01e339d8f41567805cecfecd5c71aa5ee/lxml-6.0.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7a3ec1373f7d3f519de595032d4dcafae396c29407cfd5073f42d267ba32440d", size = 4948101, upload-time = "2025-08-22T10:33:00.765Z" }, - { url = "https://files.pythonhosted.org/packages/9d/51/4e57cba4d55273c400fb63aefa2f0d08d15eac021432571a7eeefee67bed/lxml-6.0.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:03b12214fb1608f4cffa181ec3d046c72f7e77c345d06222144744c122ded870", size = 5108090, upload-time = "2025-08-22T10:33:03.108Z" }, - { url = "https://files.pythonhosted.org/packages/f6/6e/5f290bc26fcc642bc32942e903e833472271614e24d64ad28aaec09d5dae/lxml-6.0.1-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:207ae0d5f0f03b30f95e649a6fa22aa73f5825667fee9c7ec6854d30e19f2ed8", size = 5021791, upload-time = "2025-08-22T10:33:06.972Z" }, - { url = "https://files.pythonhosted.org/packages/13/d4/2e7551a86992ece4f9a0f6eebd4fb7e312d30f1e372760e2109e721d4ce6/lxml-6.0.1-cp312-cp312-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:32297b09ed4b17f7b3f448de87a92fb31bb8747496623483788e9f27c98c0f00", size = 5358861, upload-time = "2025-08-22T10:33:08.967Z" }, - { url = "https://files.pythonhosted.org/packages/8a/5f/cb49d727fc388bf5fd37247209bab0da11697ddc5e976ccac4826599939e/lxml-6.0.1-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7e18224ea241b657a157c85e9cac82c2b113ec90876e01e1f127312006233756", size = 5652569, upload-time = "2025-08-22T10:33:10.815Z" }, - { url = "https://files.pythonhosted.org/packages/ca/b8/66c1ef8c87ad0f958b0a23998851e610607c74849e75e83955d5641272e6/lxml-6.0.1-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a07a994d3c46cd4020c1ea566345cf6815af205b1e948213a4f0f1d392182072", size = 5252262, upload-time = "2025-08-22T10:33:12.673Z" }, - { url = "https://files.pythonhosted.org/packages/1a/ef/131d3d6b9590e64fdbb932fbc576b81fcc686289da19c7cb796257310e82/lxml-6.0.1-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:2287fadaa12418a813b05095485c286c47ea58155930cfbd98c590d25770e225", size = 4710309, upload-time = "2025-08-22T10:33:14.952Z" }, - { url = "https://files.pythonhosted.org/packages/bc/3f/07f48ae422dce44902309aa7ed386c35310929dc592439c403ec16ef9137/lxml-6.0.1-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b4e597efca032ed99f418bd21314745522ab9fa95af33370dcee5533f7f70136", size = 5265786, upload-time = "2025-08-22T10:33:16.721Z" }, - { url = "https://files.pythonhosted.org/packages/11/c7/125315d7b14ab20d9155e8316f7d287a4956098f787c22d47560b74886c4/lxml-6.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9696d491f156226decdd95d9651c6786d43701e49f32bf23715c975539aa2b3b", size = 5062272, upload-time = "2025-08-22T10:33:18.478Z" }, - { url = "https://files.pythonhosted.org/packages/8b/c3/51143c3a5fc5168a7c3ee626418468ff20d30f5a59597e7b156c1e61fba8/lxml-6.0.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e4e3cd3585f3c6f87cdea44cda68e692cc42a012f0131d25957ba4ce755241a7", size = 4786955, upload-time = "2025-08-22T10:33:20.34Z" }, - { url = "https://files.pythonhosted.org/packages/11/86/73102370a420ec4529647b31c4a8ce8c740c77af3a5fae7a7643212d6f6e/lxml-6.0.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:45cbc92f9d22c28cd3b97f8d07fcefa42e569fbd587dfdac76852b16a4924277", size = 5673557, upload-time = "2025-08-22T10:33:22.282Z" }, - { url = "https://files.pythonhosted.org/packages/d7/2d/aad90afaec51029aef26ef773b8fd74a9e8706e5e2f46a57acd11a421c02/lxml-6.0.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:f8c9bcfd2e12299a442fba94459adf0b0d001dbc68f1594439bfa10ad1ecb74b", size = 5254211, upload-time = "2025-08-22T10:33:24.15Z" }, - { url = "https://files.pythonhosted.org/packages/63/01/c9e42c8c2d8b41f4bdefa42ab05448852e439045f112903dd901b8fbea4d/lxml-6.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1e9dc2b9f1586e7cd77753eae81f8d76220eed9b768f337dc83a3f675f2f0cf9", size = 5275817, upload-time = "2025-08-22T10:33:26.007Z" }, - { url = "https://files.pythonhosted.org/packages/bc/1f/962ea2696759abe331c3b0e838bb17e92224f39c638c2068bf0d8345e913/lxml-6.0.1-cp312-cp312-win32.whl", hash = "sha256:987ad5c3941c64031f59c226167f55a04d1272e76b241bfafc968bdb778e07fb", size = 3610889, upload-time = "2025-08-22T10:33:28.169Z" }, - { url = "https://files.pythonhosted.org/packages/41/e2/22c86a990b51b44442b75c43ecb2f77b8daba8c4ba63696921966eac7022/lxml-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:abb05a45394fd76bf4a60c1b7bec0e6d4e8dfc569fc0e0b1f634cd983a006ddc", size = 4010925, upload-time = "2025-08-22T10:33:29.874Z" }, - { url = "https://files.pythonhosted.org/packages/b2/21/dc0c73325e5eb94ef9c9d60dbb5dcdcb2e7114901ea9509735614a74e75a/lxml-6.0.1-cp312-cp312-win_arm64.whl", hash = "sha256:c4be29bce35020d8579d60aa0a4e95effd66fcfce31c46ffddf7e5422f73a299", size = 3671922, upload-time = "2025-08-22T10:33:31.535Z" }, - { url = "https://files.pythonhosted.org/packages/41/37/41961f53f83ded57b37e65e4f47d1c6c6ef5fd02cb1d6ffe028ba0efa7d4/lxml-6.0.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b556aaa6ef393e989dac694b9c95761e32e058d5c4c11ddeef33f790518f7a5e", size = 3903412, upload-time = "2025-08-22T10:37:40.758Z" }, - { url = "https://files.pythonhosted.org/packages/3d/47/8631ea73f3dc776fb6517ccde4d5bd5072f35f9eacbba8c657caa4037a69/lxml-6.0.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:64fac7a05ebb3737b79fd89fe5a5b6c5546aac35cfcfd9208eb6e5d13215771c", size = 4224810, upload-time = "2025-08-22T10:37:42.839Z" }, - { url = "https://files.pythonhosted.org/packages/3d/b8/39ae30ca3b1516729faeef941ed84bf8f12321625f2644492ed8320cb254/lxml-6.0.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:038d3c08babcfce9dc89aaf498e6da205efad5b7106c3b11830a488d4eadf56b", size = 4329221, upload-time = "2025-08-22T10:37:45.223Z" }, - { url = "https://files.pythonhosted.org/packages/9c/ea/048dea6cdfc7a72d40ae8ed7e7d23cf4a6b6a6547b51b492a3be50af0e80/lxml-6.0.1-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:445f2cee71c404ab4259bc21e20339a859f75383ba2d7fb97dfe7c163994287b", size = 4270228, upload-time = "2025-08-22T10:37:47.276Z" }, - { url = "https://files.pythonhosted.org/packages/6b/d4/c2b46e432377c45d611ae2f669aa47971df1586c1a5240675801d0f02bac/lxml-6.0.1-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e352d8578e83822d70bea88f3d08b9912528e4c338f04ab707207ab12f4b7aac", size = 4416077, upload-time = "2025-08-22T10:37:49.822Z" }, - { url = "https://files.pythonhosted.org/packages/b6/db/8f620f1ac62cf32554821b00b768dd5957ac8e3fd051593532be5b40b438/lxml-6.0.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:51bd5d1a9796ca253db6045ab45ca882c09c071deafffc22e06975b7ace36300", size = 3518127, upload-time = "2025-08-22T10:37:51.66Z" }, + { url = "https://files.pythonhosted.org/packages/77/d5/becbe1e2569b474a23f0c672ead8a29ac50b2dc1d5b9de184831bda8d14c/lxml-6.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:13e35cbc684aadf05d8711a5d1b5857c92e5e580efa9a0d2be197199c8def607", size = 8634365, upload-time = "2025-09-22T04:00:45.672Z" }, + { url = "https://files.pythonhosted.org/packages/28/66/1ced58f12e804644426b85d0bb8a4478ca77bc1761455da310505f1a3526/lxml-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b1675e096e17c6fe9c0e8c81434f5736c0739ff9ac6123c87c2d452f48fc938", size = 4650793, upload-time = "2025-09-22T04:00:47.783Z" }, + { url = "https://files.pythonhosted.org/packages/11/84/549098ffea39dfd167e3f174b4ce983d0eed61f9d8d25b7bf2a57c3247fc/lxml-6.0.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8ac6e5811ae2870953390452e3476694196f98d447573234592d30488147404d", size = 4944362, upload-time = "2025-09-22T04:00:49.845Z" }, + { url = "https://files.pythonhosted.org/packages/ac/bd/f207f16abf9749d2037453d56b643a7471d8fde855a231a12d1e095c4f01/lxml-6.0.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5aa0fc67ae19d7a64c3fe725dc9a1bb11f80e01f78289d05c6f62545affec438", size = 5083152, upload-time = "2025-09-22T04:00:51.709Z" }, + { url = "https://files.pythonhosted.org/packages/15/ae/bd813e87d8941d52ad5b65071b1affb48da01c4ed3c9c99e40abb266fbff/lxml-6.0.2-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de496365750cc472b4e7902a485d3f152ecf57bd3ba03ddd5578ed8ceb4c5964", size = 5023539, upload-time = "2025-09-22T04:00:53.593Z" }, + { url = "https://files.pythonhosted.org/packages/02/cd/9bfef16bd1d874fbe0cb51afb00329540f30a3283beb9f0780adbb7eec03/lxml-6.0.2-cp311-cp311-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:200069a593c5e40b8f6fc0d84d86d970ba43138c3e68619ffa234bc9bb806a4d", size = 5344853, upload-time = "2025-09-22T04:00:55.524Z" }, + { url = "https://files.pythonhosted.org/packages/b8/89/ea8f91594bc5dbb879734d35a6f2b0ad50605d7fb419de2b63d4211765cc/lxml-6.0.2-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d2de809c2ee3b888b59f995625385f74629707c9355e0ff856445cdcae682b7", size = 5225133, upload-time = "2025-09-22T04:00:57.269Z" }, + { url = "https://files.pythonhosted.org/packages/b9/37/9c735274f5dbec726b2db99b98a43950395ba3d4a1043083dba2ad814170/lxml-6.0.2-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:b2c3da8d93cf5db60e8858c17684c47d01fee6405e554fb55018dd85fc23b178", size = 4677944, upload-time = "2025-09-22T04:00:59.052Z" }, + { url = "https://files.pythonhosted.org/packages/20/28/7dfe1ba3475d8bfca3878365075abe002e05d40dfaaeb7ec01b4c587d533/lxml-6.0.2-cp311-cp311-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:442de7530296ef5e188373a1ea5789a46ce90c4847e597856570439621d9c553", size = 5284535, upload-time = "2025-09-22T04:01:01.335Z" }, + { url = "https://files.pythonhosted.org/packages/e7/cf/5f14bc0de763498fc29510e3532bf2b4b3a1c1d5d0dff2e900c16ba021ef/lxml-6.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2593c77efde7bfea7f6389f1ab249b15ed4aa5bc5cb5131faa3b843c429fbedb", size = 5067343, upload-time = "2025-09-22T04:01:03.13Z" }, + { url = "https://files.pythonhosted.org/packages/1c/b0/bb8275ab5472f32b28cfbbcc6db7c9d092482d3439ca279d8d6fa02f7025/lxml-6.0.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:3e3cb08855967a20f553ff32d147e14329b3ae70ced6edc2f282b94afbc74b2a", size = 4725419, upload-time = "2025-09-22T04:01:05.013Z" }, + { url = "https://files.pythonhosted.org/packages/25/4c/7c222753bc72edca3b99dbadba1b064209bc8ed4ad448af990e60dcce462/lxml-6.0.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:2ed6c667fcbb8c19c6791bbf40b7268ef8ddf5a96940ba9404b9f9a304832f6c", size = 5275008, upload-time = "2025-09-22T04:01:07.327Z" }, + { url = "https://files.pythonhosted.org/packages/6c/8c/478a0dc6b6ed661451379447cdbec77c05741a75736d97e5b2b729687828/lxml-6.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b8f18914faec94132e5b91e69d76a5c1d7b0c73e2489ea8929c4aaa10b76bbf7", size = 5248906, upload-time = "2025-09-22T04:01:09.452Z" }, + { url = "https://files.pythonhosted.org/packages/2d/d9/5be3a6ab2784cdf9accb0703b65e1b64fcdd9311c9f007630c7db0cfcce1/lxml-6.0.2-cp311-cp311-win32.whl", hash = "sha256:6605c604e6daa9e0d7f0a2137bdc47a2e93b59c60a65466353e37f8272f47c46", size = 3610357, upload-time = "2025-09-22T04:01:11.102Z" }, + { url = "https://files.pythonhosted.org/packages/e2/7d/ca6fb13349b473d5732fb0ee3eec8f6c80fc0688e76b7d79c1008481bf1f/lxml-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e5867f2651016a3afd8dd2c8238baa66f1e2802f44bc17e236f547ace6647078", size = 4036583, upload-time = "2025-09-22T04:01:12.766Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a2/51363b5ecd3eab46563645f3a2c3836a2fc67d01a1b87c5017040f39f567/lxml-6.0.2-cp311-cp311-win_arm64.whl", hash = "sha256:4197fb2534ee05fd3e7afaab5d8bfd6c2e186f65ea7f9cd6a82809c887bd1285", size = 3680591, upload-time = "2025-09-22T04:01:14.874Z" }, + { url = "https://files.pythonhosted.org/packages/f3/c8/8ff2bc6b920c84355146cd1ab7d181bc543b89241cfb1ebee824a7c81457/lxml-6.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a59f5448ba2ceccd06995c95ea59a7674a10de0810f2ce90c9006f3cbc044456", size = 8661887, upload-time = "2025-09-22T04:01:17.265Z" }, + { url = "https://files.pythonhosted.org/packages/37/6f/9aae1008083bb501ef63284220ce81638332f9ccbfa53765b2b7502203cf/lxml-6.0.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e8113639f3296706fbac34a30813929e29247718e88173ad849f57ca59754924", size = 4667818, upload-time = "2025-09-22T04:01:19.688Z" }, + { url = "https://files.pythonhosted.org/packages/f1/ca/31fb37f99f37f1536c133476674c10b577e409c0a624384147653e38baf2/lxml-6.0.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a8bef9b9825fa8bc816a6e641bb67219489229ebc648be422af695f6e7a4fa7f", size = 4950807, upload-time = "2025-09-22T04:01:21.487Z" }, + { url = "https://files.pythonhosted.org/packages/da/87/f6cb9442e4bada8aab5ae7e1046264f62fdbeaa6e3f6211b93f4c0dd97f1/lxml-6.0.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:65ea18d710fd14e0186c2f973dc60bb52039a275f82d3c44a0e42b43440ea534", size = 5109179, upload-time = "2025-09-22T04:01:23.32Z" }, + { url = "https://files.pythonhosted.org/packages/c8/20/a7760713e65888db79bbae4f6146a6ae5c04e4a204a3c48896c408cd6ed2/lxml-6.0.2-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c371aa98126a0d4c739ca93ceffa0fd7a5d732e3ac66a46e74339acd4d334564", size = 5023044, upload-time = "2025-09-22T04:01:25.118Z" }, + { url = "https://files.pythonhosted.org/packages/a2/b0/7e64e0460fcb36471899f75831509098f3fd7cd02a3833ac517433cb4f8f/lxml-6.0.2-cp312-cp312-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:700efd30c0fa1a3581d80a748157397559396090a51d306ea59a70020223d16f", size = 5359685, upload-time = "2025-09-22T04:01:27.398Z" }, + { url = "https://files.pythonhosted.org/packages/b9/e1/e5df362e9ca4e2f48ed6411bd4b3a0ae737cc842e96877f5bf9428055ab4/lxml-6.0.2-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c33e66d44fe60e72397b487ee92e01da0d09ba2d66df8eae42d77b6d06e5eba0", size = 5654127, upload-time = "2025-09-22T04:01:29.629Z" }, + { url = "https://files.pythonhosted.org/packages/c6/d1/232b3309a02d60f11e71857778bfcd4acbdb86c07db8260caf7d008b08f8/lxml-6.0.2-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90a345bbeaf9d0587a3aaffb7006aa39ccb6ff0e96a57286c0cb2fd1520ea192", size = 5253958, upload-time = "2025-09-22T04:01:31.535Z" }, + { url = "https://files.pythonhosted.org/packages/35/35/d955a070994725c4f7d80583a96cab9c107c57a125b20bb5f708fe941011/lxml-6.0.2-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:064fdadaf7a21af3ed1dcaa106b854077fbeada827c18f72aec9346847cd65d0", size = 4711541, upload-time = "2025-09-22T04:01:33.801Z" }, + { url = "https://files.pythonhosted.org/packages/1e/be/667d17363b38a78c4bd63cfd4b4632029fd68d2c2dc81f25ce9eb5224dd5/lxml-6.0.2-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fbc74f42c3525ac4ffa4b89cbdd00057b6196bcefe8bce794abd42d33a018092", size = 5267426, upload-time = "2025-09-22T04:01:35.639Z" }, + { url = "https://files.pythonhosted.org/packages/ea/47/62c70aa4a1c26569bc958c9ca86af2bb4e1f614e8c04fb2989833874f7ae/lxml-6.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6ddff43f702905a4e32bc24f3f2e2edfe0f8fde3277d481bffb709a4cced7a1f", size = 5064917, upload-time = "2025-09-22T04:01:37.448Z" }, + { url = "https://files.pythonhosted.org/packages/bd/55/6ceddaca353ebd0f1908ef712c597f8570cc9c58130dbb89903198e441fd/lxml-6.0.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6da5185951d72e6f5352166e3da7b0dc27aa70bd1090b0eb3f7f7212b53f1bb8", size = 4788795, upload-time = "2025-09-22T04:01:39.165Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e8/fd63e15da5e3fd4c2146f8bbb3c14e94ab850589beab88e547b2dbce22e1/lxml-6.0.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:57a86e1ebb4020a38d295c04fc79603c7899e0df71588043eb218722dabc087f", size = 5676759, upload-time = "2025-09-22T04:01:41.506Z" }, + { url = "https://files.pythonhosted.org/packages/76/47/b3ec58dc5c374697f5ba37412cd2728f427d056315d124dd4b61da381877/lxml-6.0.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:2047d8234fe735ab77802ce5f2297e410ff40f5238aec569ad7c8e163d7b19a6", size = 5255666, upload-time = "2025-09-22T04:01:43.363Z" }, + { url = "https://files.pythonhosted.org/packages/19/93/03ba725df4c3d72afd9596eef4a37a837ce8e4806010569bedfcd2cb68fd/lxml-6.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6f91fd2b2ea15a6800c8e24418c0775a1694eefc011392da73bc6cef2623b322", size = 5277989, upload-time = "2025-09-22T04:01:45.215Z" }, + { url = "https://files.pythonhosted.org/packages/c6/80/c06de80bfce881d0ad738576f243911fccf992687ae09fd80b734712b39c/lxml-6.0.2-cp312-cp312-win32.whl", hash = "sha256:3ae2ce7d6fedfb3414a2b6c5e20b249c4c607f72cb8d2bb7cc9c6ec7c6f4e849", size = 3611456, upload-time = "2025-09-22T04:01:48.243Z" }, + { url = "https://files.pythonhosted.org/packages/f7/d7/0cdfb6c3e30893463fb3d1e52bc5f5f99684a03c29a0b6b605cfae879cd5/lxml-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:72c87e5ee4e58a8354fb9c7c84cbf95a1c8236c127a5d1b7683f04bed8361e1f", size = 4011793, upload-time = "2025-09-22T04:01:50.042Z" }, + { url = "https://files.pythonhosted.org/packages/ea/7b/93c73c67db235931527301ed3785f849c78991e2e34f3fd9a6663ffda4c5/lxml-6.0.2-cp312-cp312-win_arm64.whl", hash = "sha256:61cb10eeb95570153e0c0e554f58df92ecf5109f75eacad4a95baa709e26c3d6", size = 3672836, upload-time = "2025-09-22T04:01:52.145Z" }, + { url = "https://files.pythonhosted.org/packages/0b/11/29d08bc103a62c0eba8016e7ed5aeebbf1e4312e83b0b1648dd203b0e87d/lxml-6.0.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1c06035eafa8404b5cf475bb37a9f6088b0aca288d4ccc9d69389750d5543700", size = 3949829, upload-time = "2025-09-22T04:04:45.608Z" }, + { url = "https://files.pythonhosted.org/packages/12/b3/52ab9a3b31e5ab8238da241baa19eec44d2ab426532441ee607165aebb52/lxml-6.0.2-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c7d13103045de1bdd6fe5d61802565f1a3537d70cd3abf596aa0af62761921ee", size = 4226277, upload-time = "2025-09-22T04:04:47.754Z" }, + { url = "https://files.pythonhosted.org/packages/a0/33/1eaf780c1baad88224611df13b1c2a9dfa460b526cacfe769103ff50d845/lxml-6.0.2-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0a3c150a95fbe5ac91de323aa756219ef9cf7fde5a3f00e2281e30f33fa5fa4f", size = 4330433, upload-time = "2025-09-22T04:04:49.907Z" }, + { url = "https://files.pythonhosted.org/packages/7a/c1/27428a2ff348e994ab4f8777d3a0ad510b6b92d37718e5887d2da99952a2/lxml-6.0.2-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:60fa43be34f78bebb27812ed90f1925ec99560b0fa1decdb7d12b84d857d31e9", size = 4272119, upload-time = "2025-09-22T04:04:51.801Z" }, + { url = "https://files.pythonhosted.org/packages/f0/d0/3020fa12bcec4ab62f97aab026d57c2f0cfd480a558758d9ca233bb6a79d/lxml-6.0.2-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:21c73b476d3cfe836be731225ec3421fa2f048d84f6df6a8e70433dff1376d5a", size = 4417314, upload-time = "2025-09-22T04:04:55.024Z" }, + { url = "https://files.pythonhosted.org/packages/6c/77/d7f491cbc05303ac6801651aabeb262d43f319288c1ea96c66b1d2692ff3/lxml-6.0.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:27220da5be049e936c3aca06f174e8827ca6445a4353a1995584311487fc4e3e", size = 3518768, upload-time = "2025-09-22T04:04:57.097Z" }, ] [[package]] @@ -4235,11 +4237,11 @@ wheels = [ [[package]] name = "pyparsing" -version = "3.2.4" +version = "3.2.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/98/c9/b4594e6a81371dfa9eb7a2c110ad682acf985d96115ae8b25a1d63b4bf3b/pyparsing-3.2.4.tar.gz", hash = "sha256:fff89494f45559d0f2ce46613b419f632bbb6afbdaed49696d322bcf98a58e99", size = 1098809, upload-time = "2025-09-13T05:47:19.732Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/a5/181488fc2b9d093e3972d2a472855aae8a03f000592dbfce716a512b3359/pyparsing-3.2.5.tar.gz", hash = "sha256:2df8d5b7b2802ef88e8d016a2eb9c7aeaa923529cd251ed0fe4608275d4105b6", size = 1099274, upload-time = "2025-09-21T04:11:06.277Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/53/b8/fbab973592e23ae313042d450fc26fa24282ebffba21ba373786e1ce63b4/pyparsing-3.2.4-py3-none-any.whl", hash = "sha256:91d0fcde680d42cd031daf3a6ba20da3107e08a75de50da58360e7d94ab24d36", size = 113869, upload-time = "2025-09-13T05:47:17.863Z" }, + { url = "https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl", hash = "sha256:e38a4f02064cf41fe6593d328d0512495ad1f3d8a91c4f73fc401b3079a59a5e", size = 113890, upload-time = "2025-09-21T04:11:04.117Z" }, ] [[package]] @@ -4626,28 +4628,28 @@ wheels = [ [[package]] name = "ruamel-yaml-clib" -version = "0.2.12" +version = "0.2.13" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/20/84/80203abff8ea4993a87d823a5f632e4d92831ef75d404c9fc78d0176d2b5/ruamel.yaml.clib-0.2.12.tar.gz", hash = "sha256:6c8fbb13ec503f99a91901ab46e0b07ae7941cd527393187039aec586fdfd36f", size = 225315, upload-time = "2024-10-20T10:10:56.22Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3c/fd/32c91c4d301ebe7931a3c44f7593650613afe14854e3cc9d2764321b7a46/ruamel.yaml.clib-0.2.13.tar.gz", hash = "sha256:8d4f8d7853053a5a19171a0f515f1e14f503bd3e772c4da6bafe7d0893d4f299", size = 201264, upload-time = "2025-09-22T13:33:47.268Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/8f/683c6ad562f558cbc4f7c029abcd9599148c51c54b5ef0f24f2638da9fbb/ruamel.yaml.clib-0.2.12-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:4a6679521a58256a90b0d89e03992c15144c5f3858f40d7c18886023d7943db6", size = 132224, upload-time = "2024-10-20T10:12:45.162Z" }, - { url = "https://files.pythonhosted.org/packages/3c/d2/b79b7d695e2f21da020bd44c782490578f300dd44f0a4c57a92575758a76/ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:d84318609196d6bd6da0edfa25cedfbabd8dbde5140a0a23af29ad4b8f91fb1e", size = 641480, upload-time = "2024-10-20T10:12:46.758Z" }, - { url = "https://files.pythonhosted.org/packages/68/6e/264c50ce2a31473a9fdbf4fa66ca9b2b17c7455b31ef585462343818bd6c/ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb43a269eb827806502c7c8efb7ae7e9e9d0573257a46e8e952f4d4caba4f31e", size = 739068, upload-time = "2024-10-20T10:12:48.605Z" }, - { url = "https://files.pythonhosted.org/packages/86/29/88c2567bc893c84d88b4c48027367c3562ae69121d568e8a3f3a8d363f4d/ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:811ea1594b8a0fb466172c384267a4e5e367298af6b228931f273b111f17ef52", size = 703012, upload-time = "2024-10-20T10:12:51.124Z" }, - { url = "https://files.pythonhosted.org/packages/11/46/879763c619b5470820f0cd6ca97d134771e502776bc2b844d2adb6e37753/ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cf12567a7b565cbf65d438dec6cfbe2917d3c1bdddfce84a9930b7d35ea59642", size = 704352, upload-time = "2024-10-21T11:26:41.438Z" }, - { url = "https://files.pythonhosted.org/packages/02/80/ece7e6034256a4186bbe50dee28cd032d816974941a6abf6a9d65e4228a7/ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7dd5adc8b930b12c8fc5b99e2d535a09889941aa0d0bd06f4749e9a9397c71d2", size = 737344, upload-time = "2024-10-21T11:26:43.62Z" }, - { url = "https://files.pythonhosted.org/packages/f0/ca/e4106ac7e80efbabdf4bf91d3d32fc424e41418458251712f5672eada9ce/ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1492a6051dab8d912fc2adeef0e8c72216b24d57bd896ea607cb90bb0c4981d3", size = 714498, upload-time = "2024-12-11T19:58:15.592Z" }, - { url = "https://files.pythonhosted.org/packages/67/58/b1f60a1d591b771298ffa0428237afb092c7f29ae23bad93420b1eb10703/ruamel.yaml.clib-0.2.12-cp311-cp311-win32.whl", hash = "sha256:bd0a08f0bab19093c54e18a14a10b4322e1eacc5217056f3c063bd2f59853ce4", size = 100205, upload-time = "2024-10-20T10:12:52.865Z" }, - { url = "https://files.pythonhosted.org/packages/b4/4f/b52f634c9548a9291a70dfce26ca7ebce388235c93588a1068028ea23fcc/ruamel.yaml.clib-0.2.12-cp311-cp311-win_amd64.whl", hash = "sha256:a274fb2cb086c7a3dea4322ec27f4cb5cc4b6298adb583ab0e211a4682f241eb", size = 118185, upload-time = "2024-10-20T10:12:54.652Z" }, - { url = "https://files.pythonhosted.org/packages/48/41/e7a405afbdc26af961678474a55373e1b323605a4f5e2ddd4a80ea80f628/ruamel.yaml.clib-0.2.12-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:20b0f8dc160ba83b6dcc0e256846e1a02d044e13f7ea74a3d1d56ede4e48c632", size = 133433, upload-time = "2024-10-20T10:12:55.657Z" }, - { url = "https://files.pythonhosted.org/packages/ec/b0/b850385604334c2ce90e3ee1013bd911aedf058a934905863a6ea95e9eb4/ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:943f32bc9dedb3abff9879edc134901df92cfce2c3d5c9348f172f62eb2d771d", size = 647362, upload-time = "2024-10-20T10:12:57.155Z" }, - { url = "https://files.pythonhosted.org/packages/44/d0/3f68a86e006448fb6c005aee66565b9eb89014a70c491d70c08de597f8e4/ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95c3829bb364fdb8e0332c9931ecf57d9be3519241323c5274bd82f709cebc0c", size = 754118, upload-time = "2024-10-20T10:12:58.501Z" }, - { url = "https://files.pythonhosted.org/packages/52/a9/d39f3c5ada0a3bb2870d7db41901125dbe2434fa4f12ca8c5b83a42d7c53/ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:749c16fcc4a2b09f28843cda5a193e0283e47454b63ec4b81eaa2242f50e4ccd", size = 706497, upload-time = "2024-10-20T10:13:00.211Z" }, - { url = "https://files.pythonhosted.org/packages/b0/fa/097e38135dadd9ac25aecf2a54be17ddf6e4c23e43d538492a90ab3d71c6/ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bf165fef1f223beae7333275156ab2022cffe255dcc51c27f066b4370da81e31", size = 698042, upload-time = "2024-10-21T11:26:46.038Z" }, - { url = "https://files.pythonhosted.org/packages/ec/d5/a659ca6f503b9379b930f13bc6b130c9f176469b73b9834296822a83a132/ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:32621c177bbf782ca5a18ba4d7af0f1082a3f6e517ac2a18b3974d4edf349680", size = 745831, upload-time = "2024-10-21T11:26:47.487Z" }, - { url = "https://files.pythonhosted.org/packages/db/5d/36619b61ffa2429eeaefaab4f3374666adf36ad8ac6330d855848d7d36fd/ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b82a7c94a498853aa0b272fd5bc67f29008da798d4f93a2f9f289feb8426a58d", size = 715692, upload-time = "2024-12-11T19:58:17.252Z" }, - { url = "https://files.pythonhosted.org/packages/b1/82/85cb92f15a4231c89b95dfe08b09eb6adca929ef7df7e17ab59902b6f589/ruamel.yaml.clib-0.2.12-cp312-cp312-win32.whl", hash = "sha256:e8c4ebfcfd57177b572e2040777b8abc537cdef58a2120e830124946aa9b42c5", size = 98777, upload-time = "2024-10-20T10:13:01.395Z" }, - { url = "https://files.pythonhosted.org/packages/d7/8f/c3654f6f1ddb75daf3922c3d8fc6005b1ab56671ad56ffb874d908bfa668/ruamel.yaml.clib-0.2.12-cp312-cp312-win_amd64.whl", hash = "sha256:0467c5965282c62203273b838ae77c0d29d7638c8a4e3a1c8bdd3602c10904e4", size = 115523, upload-time = "2024-10-20T10:13:02.768Z" }, + { url = "https://files.pythonhosted.org/packages/bb/86/fa6be53f11d1c663a261d137d616bbb326cb937b361a55a00cacf9ba76aa/ruamel.yaml.clib-0.2.13-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:825a7483da63b9448fdad9778269a5d02e5f64040aa8326fec071e830c2fc49c", size = 136894, upload-time = "2025-09-22T13:32:56.684Z" }, + { url = "https://files.pythonhosted.org/packages/70/f1/d26dbf3c53befd01d8b341cb29c555c1e910ef29478b4ebd50d1d67fbc64/ruamel.yaml.clib-0.2.13-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:54707e2200b9e1c34a62af09edd3ea3469a722951365311398458abe36ff45d6", size = 640033, upload-time = "2025-09-22T13:33:00.01Z" }, + { url = "https://files.pythonhosted.org/packages/a8/80/119ce9e40690b7e0dfc6bec41369333593f8738fa4f58dbbffdb9b80339b/ruamel.yaml.clib-0.2.13-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:958b1fc6334cd745db7099a69da4f60503cfbbc05ce1412b1d06c5922cd8314b", size = 738065, upload-time = "2025-09-22T13:32:57.712Z" }, + { url = "https://files.pythonhosted.org/packages/0b/85/09625df6b3ccf0ae0ddc93c0ab4d89f785debc0ae1e7efa541696b788b6a/ruamel.yaml.clib-0.2.13-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d50c415df5ba918c483a2486a90dd5a3e6df634fca688f71d266e15a618d643f", size = 700721, upload-time = "2025-09-22T13:32:58.881Z" }, + { url = "https://files.pythonhosted.org/packages/e0/2e/c05050760b20fe10cb17b3fe7efd5d5653baefa862121f5b0825418f3d8b/ruamel.yaml.clib-0.2.13-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bbdb57e57a5bb3510bfb43496bab44857546c56c7f04bb7bfd0b248388d41c2e", size = 641589, upload-time = "2025-09-22T13:33:01.482Z" }, + { url = "https://files.pythonhosted.org/packages/d7/e0/6cfd5c61070f0e5556a724c753919b8758b86fe89f5b53eb9f53d0506b27/ruamel.yaml.clib-0.2.13-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:df8ef03eb60fc2e1c0570612363091898b97a7b3181422e1b34c3ee22d7e0a9e", size = 743806, upload-time = "2025-09-22T13:33:02.682Z" }, + { url = "https://files.pythonhosted.org/packages/85/01/14964bd94bbe809497d1affc0625e428803cc9fd21e145b1b2edcbbc3c92/ruamel.yaml.clib-0.2.13-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:89168de27694f7903a1317381525efdf7ea2c377b5b1eeeb927940870a7e8945", size = 769530, upload-time = "2025-09-22T13:33:04.044Z" }, + { url = "https://files.pythonhosted.org/packages/1d/c0/a39cc7de38b5acc81241dda9888ecabc1ee90fb3ebf9189ddfc65c08555d/ruamel.yaml.clib-0.2.13-cp311-cp311-win32.whl", hash = "sha256:8cdb4c720137bb7834cf2c09753d5f3468023eebb7f6ad2604ddf920328753dd", size = 100254, upload-time = "2025-09-22T13:33:06.243Z" }, + { url = "https://files.pythonhosted.org/packages/1f/7f/d5c1e0279df8dd9ca92138bec8387a07112d97598f668ccb3190d0bc06aa/ruamel.yaml.clib-0.2.13-cp311-cp311-win_amd64.whl", hash = "sha256:670d8a9c5b22af152863236b7a7fec471aafefc5e89b637b8f98d280cb92a0ce", size = 118235, upload-time = "2025-09-22T13:33:05.202Z" }, + { url = "https://files.pythonhosted.org/packages/50/ae/b6b3ed185206af04c779eec77a4a57587278b713b72034957d127307bbef/ruamel.yaml.clib-0.2.13-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:810500056a0e294131042eca6687a786e82aad817e8e0bc5ce059d2f95b67fef", size = 137955, upload-time = "2025-09-22T13:33:07.585Z" }, + { url = "https://files.pythonhosted.org/packages/6f/2b/b0307fc587cebabd8faaaeb1476c825fe4dbb5ad2b5c152dfbcc31b258c4/ruamel.yaml.clib-0.2.13-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:f8e44b1b583f93a8f4d00e5285d9ea8ccd9524cdd4c54788db2880a11f21287d", size = 645916, upload-time = "2025-09-22T13:33:11.619Z" }, + { url = "https://files.pythonhosted.org/packages/74/a9/2ddad6eb03195206aca765b4ff7bd7098c46fbc65957d5ac53f96ef8d6f4/ruamel.yaml.clib-0.2.13-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d1ab027be86f7d5ccc9b777a22194fd3850012df413f216bf1608768b4daa2f", size = 753115, upload-time = "2025-09-22T13:33:08.793Z" }, + { url = "https://files.pythonhosted.org/packages/42/a0/ded38d60fc34f69afef6f3e379f896a80d34532bb6b4828e95f7003be610/ruamel.yaml.clib-0.2.13-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:55f01d82d7d96213f0963609876ae841c81e217985c668100c5fb95cc9a564b3", size = 703451, upload-time = "2025-09-22T13:33:10.111Z" }, + { url = "https://files.pythonhosted.org/packages/3e/49/121067d5621a77913dc0fd473bdf2ec4a515c564806b107d3b1257cd5b14/ruamel.yaml.clib-0.2.13-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:031c7cdd5751cda19ddf90e42756ec2ee72a30791c2f65f5f800f9ee929862f0", size = 647403, upload-time = "2025-09-22T13:33:12.778Z" }, + { url = "https://files.pythonhosted.org/packages/15/fc/0845929db1840eec6aa6133b8c89941e251c3bc67180aee7d71a30bc1c80/ruamel.yaml.clib-0.2.13-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ba745411caabc994963729663a7c2d09398795c36dc4b5672ca4fdc445ab6544", size = 745860, upload-time = "2025-09-22T13:33:13.99Z" }, + { url = "https://files.pythonhosted.org/packages/d6/0a/dd0007d41a321edf64c4bda0aab70936281ee213406e6913105499f7d62a/ruamel.yaml.clib-0.2.13-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fb90267cc3748858236b9cedb580191be1d0d218f6cde64e954fa759d90fb08d", size = 770202, upload-time = "2025-09-22T13:33:15.181Z" }, + { url = "https://files.pythonhosted.org/packages/93/0d/eeed0a9824b14fa089a82247fca96de6b202af84e4093ba2a15afc0b4cb9/ruamel.yaml.clib-0.2.13-cp312-cp312-win32.whl", hash = "sha256:5188c3212c10fcd14a6de8ce787c0713a49fc5972054703f790cfd9f1d27a90b", size = 98831, upload-time = "2025-09-22T13:33:17.362Z" }, + { url = "https://files.pythonhosted.org/packages/25/45/c882a32e4b5c0ed6a5b4e0933af427fef48f3aad7259b87f38e33ee20536/ruamel.yaml.clib-0.2.13-cp312-cp312-win_amd64.whl", hash = "sha256:518b13f9fe601a559a759f3d21cdb2590cb85611934a7c0f09c09dc1a869ec83", size = 115567, upload-time = "2025-09-22T13:33:16.358Z" }, ] [[package]] From 6901e3417b5f4bdbb4dc705c23cea5277bae22b6 Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Mon, 22 Sep 2025 15:18:43 -0700 Subject: [PATCH 039/910] add 3X release branch to `RELEASE_BRANCHES` (#36190) add --- Jenkinsfile | 2 +- system/version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index f3a63d3de..ad8e85136 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -167,7 +167,7 @@ node { env.GIT_COMMIT = checkout(scm).GIT_COMMIT def excludeBranches = ['__nightly', 'devel', 'devel-staging', 'release3', 'release3-staging', - 'release-tici', 'testing-closet*', 'hotfix-*'] + 'release-tici', 'release-tizi', 'testing-closet*', 'hotfix-*'] def excludeRegex = excludeBranches.join('|').replaceAll('\\*', '.*') if (env.BRANCH_NAME != 'master' && !env.BRANCH_NAME.contains('__jenkins_loop_')) { diff --git a/system/version.py b/system/version.py index e32c3d603..9c5a8348f 100755 --- a/system/version.py +++ b/system/version.py @@ -10,7 +10,7 @@ from openpilot.common.basedir import BASEDIR from openpilot.common.swaglog import cloudlog from openpilot.common.git import get_commit, get_origin, get_branch, get_short_branch, get_commit_date -RELEASE_BRANCHES = ['release3-staging', 'release3', 'release-tici', 'nightly'] +RELEASE_BRANCHES = ['release3-staging', 'release3', 'release-tici', 'release-tizi', 'nightly'] TESTED_BRANCHES = RELEASE_BRANCHES + ['devel', 'devel-staging', 'nightly-dev'] BUILD_METADATA_FILENAME = "build.json" From 222e880561f8af4a3ae90d3d944353348f30f702 Mon Sep 17 00:00:00 2001 From: Jason Young <46612682+jyoung8607@users.noreply.github.com> Date: Wed, 24 Sep 2025 15:24:15 -0400 Subject: [PATCH 040/910] Honda: Add 2021 Acura TLX to release (#36193) * bump opendbc * regen CARS.md * add to RELEASES.md --- RELEASES.md | 1 + docs/CARS.md | 3 ++- opendbc_repo | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index f5cf63060..966d5d380 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -5,6 +5,7 @@ Version 0.10.1 (2025-09-08) * World Model: 2x the number of parameters * World Model: trained on 4x the number of segments * Driving Vision Model: trained on 4x the number of segments +* Acura TLX 2021 support thanks to MVL! * Honda City 2023 support thanks to vanillagorillaa and drFritz! * Honda N-Box 2018 support thanks to miettal! * Honda Odyssey 2021-25 support thanks to csouers and MVL! diff --git a/docs/CARS.md b/docs/CARS.md index 22cb0d46c..3ea12f651 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -4,7 +4,7 @@ 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. -# 324 Supported Cars +# 325 Supported Cars |Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Hardware Needed
 |Video|Setup Video| |---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| @@ -13,6 +13,7 @@ A supported vehicle is one that just works when you install a comma device. All |Acura|MDX 2025|All except Type S|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch 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
||| |Acura|RDX 2016-18|AcuraWatch Plus or Advance Package|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| |Acura|RDX 2019-21|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Acura|TLX 2021|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| |Audi|A3 2014-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| |Audi|A3 Sportback e-tron 2017-18|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| |Audi|Q2 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| diff --git a/opendbc_repo b/opendbc_repo index 6894a012c..2eec1af10 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 6894a012cd7ec36678b8abd3d95dc79975d5dbe2 +Subproject commit 2eec1af104972b7784644bf38c4c5afb52fc070a From afc7ff1b7ad4f30c4fe7c2c832ad2d0ebfa89005 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 24 Sep 2025 17:14:06 -0700 Subject: [PATCH 041/910] raylib: fix multilang dialog height (#36196) * fix multilang dialog height * clean up --- system/ui/widgets/option_dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/ui/widgets/option_dialog.py b/system/ui/widgets/option_dialog.py index 3140f419f..8f33124b5 100644 --- a/system/ui/widgets/option_dialog.py +++ b/system/ui/widgets/option_dialog.py @@ -37,7 +37,7 @@ class MultiOptionDialog(Widget): options_y = content_rect.y + TITLE_FONT_SIZE + ITEM_SPACING options_h = content_rect.height - TITLE_FONT_SIZE - BUTTON_HEIGHT - 2 * ITEM_SPACING view_rect = rl.Rectangle(content_rect.x, options_y, content_rect.width, options_h) - content_h = len(self.options) * (ITEM_HEIGHT + 10) + content_h = len(self.options) * (ITEM_HEIGHT + LIST_ITEM_SPACING) list_content_rect = rl.Rectangle(content_rect.x, options_y, content_rect.width, content_h) # Scroll and render options From 6aecf59536e00168f68114054bce09647a5d64e3 Mon Sep 17 00:00:00 2001 From: Greg Hogan Date: Thu, 25 Sep 2025 17:42:11 -0700 Subject: [PATCH 042/910] add ssh hostname comma- prefix for convenience (#36199) --- tools/scripts/ssh.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/scripts/ssh.py b/tools/scripts/ssh.py index 0429799a2..e454afb69 100755 --- a/tools/scripts/ssh.py +++ b/tools/scripts/ssh.py @@ -50,7 +50,7 @@ if __name__ == "__main__": if args.debug: command += ["-v"] command += [ - f"comma@{dongle_id}", + f"comma@comma-{dongle_id}", ] if args.debug: print(" ".join([f"'{c}'" if " " in c else c for c in command])) From 54297487672fbf24ad4e7eb3e046e8aa00db17b5 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 25 Sep 2025 19:27:13 -0700 Subject: [PATCH 043/910] raylib: fix button clicking on device (#36201) * fix button clicking on device * clean up --- system/ui/widgets/list_view.py | 37 +++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/system/ui/widgets/list_view.py b/system/ui/widgets/list_view.py index a8d81a8ba..e4ff4133a 100644 --- a/system/ui/widgets/list_view.py +++ b/system/ui/widgets/list_view.py @@ -6,7 +6,7 @@ from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.lib.wrap_text import wrap_text from openpilot.system.ui.widgets import Widget -from openpilot.system.ui.widgets.button import gui_button, ButtonStyle +from openpilot.system.ui.widgets.button import Button, gui_button, ButtonStyle from openpilot.system.ui.widgets.toggle import Toggle, WIDTH as TOGGLE_WIDTH, HEIGHT as TOGGLE_HEIGHT ITEM_BASE_WIDTH = 600 @@ -73,21 +73,38 @@ class ButtonAction(ItemAction): def __init__(self, text: str | Callable[[], str], width: int = BUTTON_WIDTH, enabled: bool | Callable[[], bool] = True): super().__init__(width, enabled) self._text_source = text + self._pressed = False + + def pressed(): + self._pressed = True + + self._button = Button( + self.text, + font_size=BUTTON_FONT_SIZE, + font_weight=BUTTON_FONT_WEIGHT, + button_style=ButtonStyle.LIST_ACTION, + border_radius=BUTTON_BORDER_RADIUS, + click_callback=pressed, + ) + self._button.set_enabled(_resolve_value(enabled)) + + def set_touch_valid_callback(self, touch_callback: Callable[[], bool]) -> None: + super().set_touch_valid_callback(touch_callback) + self._button.set_touch_valid_callback(touch_callback) @property def text(self): return _resolve_value(self._text_source, "Error") def _render(self, rect: rl.Rectangle) -> bool: - return gui_button( - rl.Rectangle(rect.x, rect.y + (rect.height - BUTTON_HEIGHT) / 2, BUTTON_WIDTH, BUTTON_HEIGHT), - self.text, - border_radius=BUTTON_BORDER_RADIUS, - font_weight=BUTTON_FONT_WEIGHT, - font_size=BUTTON_FONT_SIZE, - button_style=ButtonStyle.LIST_ACTION, - is_enabled=self.enabled, - ) == 1 + self._button.set_text(self.text) + button_rect = rl.Rectangle(rect.x, rect.y + (rect.height - BUTTON_HEIGHT) / 2, BUTTON_WIDTH, BUTTON_HEIGHT) + self._button.render(button_rect) + + # TODO: just use the generic Widget click callbacks everywhere, no returning from render + pressed = self._pressed + self._pressed = False + return pressed class TextAction(ItemAction): From 56c49b3b42d27afdd8ce7fe4e6e6021e9fa5f29d Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 25 Sep 2025 19:27:27 -0700 Subject: [PATCH 044/910] cleanup dead build flags --- SConstruct | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/SConstruct b/SConstruct index d8a390f5d..6f035e304 100644 --- a/SConstruct +++ b/SConstruct @@ -31,26 +31,12 @@ AddOption('--ubsan', action='store_true', help='turn on UBSan') -AddOption('--coverage', - action='store_true', - help='build with test coverage options') - -AddOption('--clazy', - action='store_true', - help='build with clazy') - AddOption('--ccflags', action='store', type='string', default='', help='pass arbitrary flags over the command line') -AddOption('--external-sconscript', - action='store', - metavar='FILE', - dest='external_sconscript', - help='add an external SConscript to the build') - AddOption('--mutation', action='store_true', help='generate mutation-ready code') @@ -292,17 +278,6 @@ qt_env['CXXFLAGS'] += qt_flags qt_env['LIBPATH'] += ['#selfdrive/ui', ] qt_env['LIBS'] = qt_libs -if GetOption("clazy"): - checks = [ - "level0", - "level1", - "no-range-loop", - "no-non-pod-global-static", - ] - qt_env['CXX'] = 'clazy' - qt_env['ENV']['CLAZY_IGNORE_DIRS'] = qt_dirs[0] - qt_env['ENV']['CLAZY_CHECKS'] = ','.join(checks) - Export('env', 'qt_env', 'arch', 'real_arch') # Build common module @@ -352,7 +327,3 @@ if Dir('#tools/cabana/').exists() and GetOption('extras'): SConscript(['tools/replay/SConscript']) if arch != "larch64": SConscript(['tools/cabana/SConscript']) - -external_sconscript = GetOption('external_sconscript') -if external_sconscript: - SConscript([external_sconscript]) From 1ca9fe35c220f88ea0a6f3cc8c1b42b272c3442c Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 25 Sep 2025 20:16:14 -0700 Subject: [PATCH 045/910] raylib: networking parity with QT (#36197) * match style * all this was not naught * cool can do this * fix toggle callback - also not for naught * always process callbacks * toggle stuff * cleaner * tethering password * clean up * todos for later * this is fineee * add metered options * wifi metered button * add hidden network buutton and fix instant modal to modal * damped filter * Revert "damped filter" This reverts commit f9f98d5d708fb15cf1ebef4bdace577f0e347658. * fix metered toggle when disconnected * fix tethering enabled * ohh * fix keyboard title * disable edit button temp * move here * proper disable * clean up * more * move for loop into enqueue function * flippy * got more :( * todo * clean up * mypy * rename * todo * rename * again * again * format --- selfdrive/ui/layouts/settings/settings.py | 4 +- selfdrive/ui/layouts/sidebar.py | 4 +- system/ui/lib/application.py | 8 +- system/ui/lib/networkmanager.py | 4 +- system/ui/lib/wifi_manager.py | 267 +++++++++++++++++++--- system/ui/widgets/button.py | 20 +- system/ui/widgets/keyboard.py | 7 +- system/ui/widgets/list_view.py | 23 +- system/ui/widgets/network.py | 209 +++++++++++++++-- system/ui/widgets/toggle.py | 7 + 10 files changed, 482 insertions(+), 71 deletions(-) diff --git a/selfdrive/ui/layouts/settings/settings.py b/selfdrive/ui/layouts/settings/settings.py index a731a9158..d43382f19 100644 --- a/selfdrive/ui/layouts/settings/settings.py +++ b/selfdrive/ui/layouts/settings/settings.py @@ -11,7 +11,7 @@ from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.lib.wifi_manager import WifiManager from openpilot.system.ui.widgets import Widget -from openpilot.system.ui.widgets.network import WifiManagerUI +from openpilot.system.ui.widgets.network import NetworkUI # Settings close button SETTINGS_CLOSE_TEXT = "×" @@ -59,7 +59,7 @@ class SettingsLayout(Widget): self._panels = { PanelType.DEVICE: PanelInfo("Device", DeviceLayout()), - PanelType.NETWORK: PanelInfo("Network", WifiManagerUI(wifi_manager)), + PanelType.NETWORK: PanelInfo("Network", NetworkUI(wifi_manager)), PanelType.TOGGLES: PanelInfo("Toggles", TogglesLayout()), PanelType.SOFTWARE: PanelInfo("Software", SoftwareLayout()), PanelType.FIREHOSE: PanelInfo("Firehose", FirehoseLayout()), diff --git a/selfdrive/ui/layouts/sidebar.py b/selfdrive/ui/layouts/sidebar.py index cdbfa4c04..4d47a9878 100644 --- a/selfdrive/ui/layouts/sidebar.py +++ b/selfdrive/ui/layouts/sidebar.py @@ -189,11 +189,11 @@ class Sidebar(Widget): # Draw colored left edge (clipped rounded rectangle) edge_rect = rl.Rectangle(metric_rect.x + 4, metric_rect.y + 4, 100, 118) rl.begin_scissor_mode(int(metric_rect.x + 4), int(metric_rect.y), 18, int(metric_rect.height)) - rl.draw_rectangle_rounded(edge_rect, 0.18, 10, metric.color) + rl.draw_rectangle_rounded(edge_rect, 0.3, 10, metric.color) rl.end_scissor_mode() # Draw border - rl.draw_rectangle_rounded_lines_ex(metric_rect, 0.15, 10, 2, Colors.METRIC_BORDER) + rl.draw_rectangle_rounded_lines_ex(metric_rect, 0.3, 10, 2, Colors.METRIC_BORDER) # Draw label and value labels = [metric.label, metric.value] diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 3f433e1fc..5b35f7ac9 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -269,11 +269,11 @@ class GuiApplication: raise Exception if result >= 0: - # Execute callback with the result and clear the overlay - if self._modal_overlay.callback is not None: - self._modal_overlay.callback(result) - + # Clear the overlay and execute the callback + original_modal = self._modal_overlay self._modal_overlay = ModalOverlay() + if original_modal.callback is not None: + original_modal.callback(result) else: yield diff --git a/system/ui/lib/networkmanager.py b/system/ui/lib/networkmanager.py index 07b5d42f4..ffa2ff4db 100644 --- a/system/ui/lib/networkmanager.py +++ b/system/ui/lib/networkmanager.py @@ -21,9 +21,11 @@ NM_ACCESS_POINT_IFACE = 'org.freedesktop.NetworkManager.AccessPoint' NM_SETTINGS_PATH = '/org/freedesktop/NetworkManager/Settings' NM_SETTINGS_IFACE = 'org.freedesktop.NetworkManager.Settings' NM_CONNECTION_IFACE = 'org.freedesktop.NetworkManager.Settings.Connection' +NM_ACTIVE_CONNECTION_IFACE = 'org.freedesktop.NetworkManager.Connection.Active' NM_WIRELESS_IFACE = 'org.freedesktop.NetworkManager.Device.Wireless' NM_PROPERTIES_IFACE = 'org.freedesktop.DBus.Properties' -NM_DEVICE_IFACE = "org.freedesktop.NetworkManager.Device" +NM_DEVICE_IFACE = 'org.freedesktop.NetworkManager.Device' +NM_IP4_CONFIG_IFACE = 'org.freedesktop.NetworkManager.IP4Config' NM_DEVICE_TYPE_WIFI = 2 NM_DEVICE_TYPE_MODEM = 8 diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index af9ae943e..15aeb94ed 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -15,6 +15,7 @@ from jeepney.low_level import MessageType from jeepney.wrappers import Properties from openpilot.common.swaglog import cloudlog +from openpilot.common.params import Params from openpilot.system.ui.lib.networkmanager import (NM, NM_WIRELESS_IFACE, NM_802_11_AP_SEC_PAIR_WEP40, NM_802_11_AP_SEC_PAIR_WEP104, NM_802_11_AP_SEC_GROUP_WEP40, NM_802_11_AP_SEC_GROUP_WEP104, NM_802_11_AP_SEC_KEY_MGMT_PSK, @@ -23,8 +24,8 @@ from openpilot.system.ui.lib.networkmanager import (NM, NM_WIRELESS_IFACE, NM_80 NM_PATH, NM_IFACE, NM_ACCESS_POINT_IFACE, NM_SETTINGS_PATH, NM_SETTINGS_IFACE, NM_CONNECTION_IFACE, NM_DEVICE_IFACE, NM_DEVICE_TYPE_WIFI, NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT, - NM_DEVICE_STATE_REASON_NEW_ACTIVATION, - NMDeviceState) + NM_DEVICE_STATE_REASON_NEW_ACTIVATION, NM_ACTIVE_CONNECTION_IFACE, + NM_IP4_CONFIG_IFACE, NMDeviceState) TETHERING_IP_ADDRESS = "192.168.43.1" DEFAULT_TETHERING_PASSWORD = "swagswagcomma" @@ -40,6 +41,12 @@ class SecurityType(IntEnum): UNSUPPORTED = 4 +class MeteredType(IntEnum): + UNKNOWN = 0 + YES = 1 + NO = 2 + + def get_security_type(flags: int, wpa_flags: int, rsn_flags: int) -> SecurityType: wpa_props = wpa_flags | rsn_flags @@ -114,7 +121,7 @@ class AccessPoint: class WifiManager: def __init__(self): - self._networks = [] # a network can be comprised of multiple APs + self._networks: list[Network] = [] # a network can be comprised of multiple APs self._active = True # used to not run when not in settings self._exit = False @@ -132,15 +139,23 @@ class WifiManager: # State self._connecting_to_ssid: str = "" + self._ipv4_address: str = "" + self._current_network_metered: MeteredType = MeteredType.UNKNOWN + self._tethering_password: str = "" self._last_network_update: float = 0.0 self._callback_queue: list[Callable] = [] + self._tethering_ssid = "weedle" + dongle_id = Params().get("DongleId") + if dongle_id: + self._tethering_ssid += "-" + dongle_id[:4] + # Callbacks - self._need_auth: Callable[[str], None] | None = None - self._activated: Callable[[], None] | None = None - self._forgotten: Callable[[], None] | None = None - self._networks_updated: Callable[[list[Network]], None] | None = None - self._disconnected: Callable[[], None] | None = None + self._need_auth: list[Callable[[str], None]] = [] + self._activated: list[Callable[[], None]] = [] + self._forgotten: list[Callable[[], None]] = [] + self._networks_updated: list[Callable[[list[Network]], None]] = [] + self._disconnected: list[Callable[[], None]] = [] self._lock = threading.Lock() @@ -152,19 +167,37 @@ class WifiManager: atexit.register(self.stop) - def set_callbacks(self, need_auth: Callable[[str], None], - activated: Callable[[], None] | None, - forgotten: Callable[[], None], - networks_updated: Callable[[list[Network]], None], - disconnected: Callable[[], None]): - self._need_auth = need_auth - self._activated = activated - self._forgotten = forgotten - self._networks_updated = networks_updated - self._disconnected = disconnected + def set_callbacks(self, need_auth: Callable[[str], None] | None = None, + activated: Callable[[], None] | None = None, + forgotten: Callable[[], None] | None = None, + networks_updated: Callable[[list[Network]], None] | None = None, + disconnected: Callable[[], None] | None = None): + if need_auth is not None: + self._need_auth.append(need_auth) + if activated is not None: + self._activated.append(activated) + if forgotten is not None: + self._forgotten.append(forgotten) + if networks_updated is not None: + self._networks_updated.append(networks_updated) + if disconnected is not None: + self._disconnected.append(disconnected) - def _enqueue_callback(self, cb: Callable, *args): - self._callback_queue.append(lambda: cb(*args)) + @property + def ipv4_address(self) -> str: + return self._ipv4_address + + @property + def current_network_metered(self) -> MeteredType: + return self._current_network_metered + + @property + def tethering_password(self) -> str: + return self._tethering_password + + def _enqueue_callbacks(self, cbs: list[Callable], *args): + for cb in cbs: + self._callback_queue.append(lambda _cb=cb: _cb(*args)) def process_callbacks(self): # Call from UI thread to run any pending callbacks @@ -180,10 +213,13 @@ class WifiManager: self._last_network_update = 0.0 def _monitor_state(self): + # TODO: make an initialize function to only wait in one place device_path = self._wait_for_wifi_device() if device_path is None: return + self._tethering_password = self._get_tethering_password() + rule = MatchRule( type="signal", interface=NM_DEVICE_IFACE, @@ -211,20 +247,18 @@ class WifiManager: # BAD PASSWORD if new_state == NMDeviceState.NEED_AUTH and change_reason == NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT and len(self._connecting_to_ssid): self.forget_connection(self._connecting_to_ssid, block=True) - if self._need_auth is not None: - self._enqueue_callback(self._need_auth, self._connecting_to_ssid) + self._enqueue_callbacks(self._need_auth, self._connecting_to_ssid) self._connecting_to_ssid = "" elif new_state == NMDeviceState.ACTIVATED: - if self._activated is not None: + if len(self._activated): self._update_networks() - self._enqueue_callback(self._activated) + self._enqueue_callbacks(self._activated) self._connecting_to_ssid = "" elif new_state == NMDeviceState.DISCONNECTED and change_reason != NM_DEVICE_STATE_REASON_NEW_ACTIVATION: self._connecting_to_ssid = "" - if self._disconnected is not None: - self._enqueue_callback(self._disconnected) + self._enqueue_callbacks(self._forgotten) def _network_scanner(self): self._wait_for_wifi_device() @@ -285,14 +319,24 @@ class WifiManager: conns[ssid] = conn_path return conns - def connect_to_network(self, ssid: str, password: str): + def _get_active_connections(self): + return self._router_main.send_and_get_reply(Properties(self._nm).get('ActiveConnections')).body[0][1] + + # TODO: use this + def _get_connection_settings(self, conn_path: str) -> dict: + conn_addr = DBusAddress(conn_path, bus_name=NM, interface=NM_CONNECTION_IFACE) + reply = self._router_main.send_and_get_reply(new_method_call(conn_addr, 'GetSettings')) + if reply.header.message_type == MessageType.error: + cloudlog.warning(f'Failed to get connection settings: {reply}') + return {} + return dict(reply.body[0]) + + def connect_to_network(self, ssid: str, password: str, hidden: bool = False): def worker(): # Clear all connections that may already exist to the network we are connecting to self._connecting_to_ssid = ssid self.forget_connection(ssid, block=True) - is_hidden = False - connection = { 'connection': { 'type': ('s', '802-11-wireless'), @@ -302,7 +346,7 @@ class WifiManager: }, '802-11-wireless': { 'ssid': ('ay', ssid.encode("utf-8")), - 'hidden': ('b', is_hidden), + 'hidden': ('b', hidden), 'mode': ('s', 'infrastructure'), }, 'ipv4': { @@ -332,9 +376,9 @@ class WifiManager: conn_addr = DBusAddress(conn_path, bus_name=NM, interface=NM_CONNECTION_IFACE) self._router_main.send_and_get_reply(new_method_call(conn_addr, 'Delete')) - if self._forgotten is not None: + if len(self._forgotten): self._update_networks() - self._enqueue_callback(self._forgotten) + self._enqueue_callbacks(self._forgotten) if block: worker() @@ -358,6 +402,137 @@ class WifiManager: else: threading.Thread(target=worker, daemon=True).start() + def _deactivate_connection(self, ssid: str): + for conn_path in self._get_active_connections(): + conn_addr = DBusAddress(conn_path, bus_name=NM, interface=NM_ACTIVE_CONNECTION_IFACE) + specific_obj_path = self._router_main.send_and_get_reply(Properties(conn_addr).get('SpecificObject')).body[0][1] + + if specific_obj_path != "/": + ap_addr = DBusAddress(specific_obj_path, bus_name=NM, interface=NM_ACCESS_POINT_IFACE) + ap_ssid = bytes(self._router_main.send_and_get_reply(Properties(ap_addr).get('Ssid')).body[0][1]).decode("utf-8", "replace") + + if ap_ssid == ssid: + self._router_main.send_and_get_reply(new_method_call(self._nm, 'DeactivateConnection', 'o', (conn_path,))) + return + + def is_tethering_active(self) -> bool: + for network in self._networks: + if network.is_connected: + return bool(network.ssid == self._tethering_ssid) + return False + + def set_tethering_password(self, password: str): + def worker(): + conn_path = self._get_connections().get(self._tethering_ssid, None) + if conn_path is None: + cloudlog.warning('No tethering connection found') + return + + settings = self._get_connection_settings(conn_path) + if len(settings) == 0: + cloudlog.warning(f'Failed to get tethering settings for {conn_path}') + return + + settings['802-11-wireless-security']['psk'] = ('s', password) + + conn_addr = DBusAddress(conn_path, bus_name=NM, interface=NM_CONNECTION_IFACE) + reply = self._router_main.send_and_get_reply(new_method_call(conn_addr, 'Update', 'a{sa{sv}}', (settings,))) + if reply.header.message_type == MessageType.error: + cloudlog.warning(f'Failed to update tethering settings: {reply}') + return + + self._tethering_password = password + if self.is_tethering_active(): + self.activate_connection(self._tethering_ssid, block=True) + + threading.Thread(target=worker, daemon=True).start() + + def _get_tethering_password(self) -> str: + conn_path = self._get_connections().get(self._tethering_ssid, None) + if conn_path is None: + cloudlog.warning('No tethering connection found') + return '' + + reply = self._router_main.send_and_get_reply(new_method_call( + DBusAddress(conn_path, bus_name=NM, interface=NM_CONNECTION_IFACE), + 'GetSecrets', 's', ('802-11-wireless-security',) + )) + + if reply.header.message_type == MessageType.error: + cloudlog.warning(f'Failed to get tethering password: {reply}') + return '' + + secrets = reply.body[0] + if '802-11-wireless-security' not in secrets: + return '' + + return str(secrets['802-11-wireless-security'].get('psk', ('s', ''))[1]) + + def set_tethering_active(self, active: bool): + def worker(): + if active: + self.activate_connection(self._tethering_ssid, block=True) + else: + self._deactivate_connection(self._tethering_ssid) + + threading.Thread(target=worker, daemon=True).start() + + def _update_current_network_metered(self) -> None: + if self._wifi_device is None: + cloudlog.warning("No WiFi device found") + return + + self._current_network_metered = MeteredType.UNKNOWN + for active_conn in self._get_active_connections(): + conn_addr = DBusAddress(active_conn, bus_name=NM, interface=NM_ACTIVE_CONNECTION_IFACE) + conn_type = self._router_main.send_and_get_reply(Properties(conn_addr).get('Type')).body[0][1] + + if conn_type == '802-11-wireless': + conn_path = self._router_main.send_and_get_reply(Properties(conn_addr).get('Connection')).body[0][1] + if conn_path == "/": + continue + + settings = self._get_connection_settings(conn_path) + + if len(settings) == 0: + cloudlog.warning(f'Failed to get connection settings for {conn_path}') + continue + + metered_prop = settings['connection'].get('metered', ('i', 0))[1] + if metered_prop == MeteredType.YES: + self._current_network_metered = MeteredType.YES + elif metered_prop == MeteredType.NO: + self._current_network_metered = MeteredType.NO + print('current_network_metered', self._current_network_metered) + return + + def set_current_network_metered(self, metered: MeteredType): + def worker(): + for active_conn in self._get_active_connections(): + conn_addr = DBusAddress(active_conn, bus_name=NM, interface=NM_ACTIVE_CONNECTION_IFACE) + conn_type = self._router_main.send_and_get_reply(Properties(conn_addr).get('Type')).body[0][1] + + if conn_type == '802-11-wireless' and not self.is_tethering_active(): + conn_path = self._router_main.send_and_get_reply(Properties(conn_addr).get('Connection')).body[0][1] + if conn_path == "/": + continue + + settings = self._get_connection_settings(conn_path) + + if len(settings) == 0: + cloudlog.warning(f'Failed to get connection settings for {conn_path}') + return + + settings['connection']['metered'] = ('i', int(metered)) + + conn_addr = DBusAddress(conn_path, bus_name=NM, interface=NM_CONNECTION_IFACE) + reply = self._router_main.send_and_get_reply(new_method_call(conn_addr, 'Update', 'a{sa{sv}}', (settings,))) + if reply.header.message_type == MessageType.error: + cloudlog.warning(f'Failed to update tethering settings: {reply}') + return + + threading.Thread(target=worker, daemon=True).start() + def _request_scan(self): if self._wifi_device is None: cloudlog.warning("No WiFi device found") @@ -409,8 +584,32 @@ class WifiManager: networks.sort(key=lambda n: (-n.is_connected, -n.strength, n.ssid.lower())) self._networks = networks - if self._networks_updated is not None: - self._enqueue_callback(self._networks_updated, self._networks) + self._update_ipv4_address() + self._update_current_network_metered() + + self._enqueue_callbacks(self._networks_updated, self._networks) + + def _update_ipv4_address(self): + if self._wifi_device is None: + cloudlog.warning("No WiFi device found") + return + + self._ipv4_address = "" + + for conn_path in self._get_active_connections(): + conn_addr = DBusAddress(conn_path, bus_name=NM, interface=NM_ACTIVE_CONNECTION_IFACE) + conn_type = self._router_main.send_and_get_reply(Properties(conn_addr).get('Type')).body[0][1] + if conn_type == '802-11-wireless': + ip4config_path = self._router_main.send_and_get_reply(Properties(conn_addr).get('Ip4Config')).body[0][1] + + if ip4config_path != "/": + ip4config_addr = DBusAddress(ip4config_path, bus_name=NM, interface=NM_IP4_CONFIG_IFACE) + address_data = self._router_main.send_and_get_reply(Properties(ip4config_addr).get('AddressData')).body[0][1] + + for entry in address_data: + if 'address' in entry: + self._ipv4_address = entry['address'][1] + return def __del__(self): self.stop() diff --git a/system/ui/widgets/button.py b/system/ui/widgets/button.py index eb38f2059..1e3f28eb8 100644 --- a/system/ui/widgets/button.py +++ b/system/ui/widgets/button.py @@ -14,6 +14,7 @@ class ButtonStyle(IntEnum): PRIMARY = 1 # For main actions DANGER = 2 # For critical actions, like reboot or delete TRANSPARENT = 3 # For buttons with transparent background and border + TRANSPARENT_WHITE = 3 # For buttons with transparent background and border ACTION = 4 LIST_ACTION = 5 # For list items with action buttons NO_EFFECT = 6 @@ -23,8 +24,6 @@ class ButtonStyle(IntEnum): ICON_PADDING = 15 DEFAULT_BUTTON_FONT_SIZE = 60 -BUTTON_DISABLED_TEXT_COLOR = rl.Color(228, 228, 228, 51) -BUTTON_DISABLED_BACKGROUND_COLOR = rl.Color(51, 51, 51, 255) ACTION_BUTTON_FONT_SIZE = 48 BUTTON_TEXT_COLOR = { @@ -32,6 +31,7 @@ BUTTON_TEXT_COLOR = { ButtonStyle.PRIMARY: rl.Color(228, 228, 228, 255), ButtonStyle.DANGER: rl.Color(228, 228, 228, 255), ButtonStyle.TRANSPARENT: rl.BLACK, + ButtonStyle.TRANSPARENT_WHITE: rl.WHITE, ButtonStyle.ACTION: rl.BLACK, ButtonStyle.LIST_ACTION: rl.Color(228, 228, 228, 255), ButtonStyle.NO_EFFECT: rl.Color(228, 228, 228, 255), @@ -39,11 +39,16 @@ BUTTON_TEXT_COLOR = { ButtonStyle.FORGET_WIFI: rl.Color(51, 51, 51, 255), } +BUTTON_DISABLED_TEXT_COLORS = { + ButtonStyle.TRANSPARENT_WHITE: rl.WHITE, +} + BUTTON_BACKGROUND_COLORS = { ButtonStyle.NORMAL: rl.Color(51, 51, 51, 255), ButtonStyle.PRIMARY: rl.Color(70, 91, 234, 255), ButtonStyle.DANGER: rl.Color(255, 36, 36, 255), ButtonStyle.TRANSPARENT: rl.BLACK, + ButtonStyle.TRANSPARENT_WHITE: rl.BLANK, ButtonStyle.ACTION: rl.Color(189, 189, 189, 255), ButtonStyle.LIST_ACTION: rl.Color(57, 57, 57, 255), ButtonStyle.NO_EFFECT: rl.Color(51, 51, 51, 255), @@ -56,6 +61,7 @@ BUTTON_PRESSED_BACKGROUND_COLORS = { ButtonStyle.PRIMARY: rl.Color(48, 73, 244, 255), ButtonStyle.DANGER: rl.Color(255, 36, 36, 255), ButtonStyle.TRANSPARENT: rl.BLACK, + ButtonStyle.TRANSPARENT_WHITE: rl.BLANK, ButtonStyle.ACTION: rl.Color(130, 130, 130, 255), ButtonStyle.LIST_ACTION: rl.Color(74, 74, 74, 74), ButtonStyle.NO_EFFECT: rl.Color(51, 51, 51, 255), @@ -63,6 +69,10 @@ BUTTON_PRESSED_BACKGROUND_COLORS = { ButtonStyle.FORGET_WIFI: rl.Color(130, 130, 130, 255), } +BUTTON_DISABLED_BACKGROUND_COLORS = { + ButtonStyle.TRANSPARENT_WHITE: rl.BLANK, +} + _pressed_buttons: set[str] = set() # Track mouse press state globally @@ -156,7 +166,7 @@ def gui_button( # Draw the button text if any if text: - color = BUTTON_TEXT_COLOR[button_style] if is_enabled else BUTTON_DISABLED_TEXT_COLOR + color = BUTTON_TEXT_COLOR[button_style] if is_enabled else BUTTON_DISABLED_TEXT_COLORS.get(button_style, rl.Color(228, 228, 228, 51)) rl.draw_text_ex(font, text, text_pos, font_size, 0, color) return result @@ -198,8 +208,8 @@ class Button(Widget): else: self._background_color = BUTTON_BACKGROUND_COLORS[self._button_style] elif self._button_style != ButtonStyle.NO_EFFECT: - self._background_color = BUTTON_DISABLED_BACKGROUND_COLOR - self._label.set_text_color(BUTTON_DISABLED_TEXT_COLOR) + self._background_color = BUTTON_DISABLED_BACKGROUND_COLORS.get(self._button_style, rl.Color(51, 51, 51, 255)) + self._label.set_text_color(BUTTON_DISABLED_TEXT_COLORS.get(self._button_style, rl.Color(228, 228, 228, 51))) def _render(self, _): roundness = self._border_radius / (min(self._rect.width, self._rect.height) / 2) diff --git a/system/ui/widgets/keyboard.py b/system/ui/widgets/keyboard.py index 25843d1ce..70f06f6b9 100644 --- a/system/ui/widgets/keyboard.py +++ b/system/ui/widgets/keyboard.py @@ -104,6 +104,9 @@ class Keyboard(Widget): self._all_keys[CAPS_LOCK_KEY] = Button("", partial(self._key_callback, CAPS_LOCK_KEY), icon=self._key_icons[CAPS_LOCK_KEY], button_style=ButtonStyle.KEYBOARD, multi_touch=True) + def set_text(self, text: str): + self._input_box.text = text + @property def text(self): return self._input_box.text @@ -243,7 +246,9 @@ class Keyboard(Widget): if not self._caps_lock and self._layout_name == "uppercase": self._layout_name = "lowercase" - def reset(self): + def reset(self, min_text_size: int | None = None): + if min_text_size is not None: + self._min_text_size = min_text_size self._render_return_status = -1 self.clear() diff --git a/system/ui/widgets/list_view.py b/system/ui/widgets/list_view.py index e4ff4133a..648ce47a6 100644 --- a/system/ui/widgets/list_view.py +++ b/system/ui/widgets/list_view.py @@ -41,6 +41,9 @@ class ItemAction(Widget, ABC): self.set_rect(rl.Rectangle(0, 0, width, 0)) self._enabled_source = enabled + def set_enabled(self, enabled: bool | Callable[[], bool]): + self._enabled_source = enabled + @property def enabled(self): return _resolve_value(self._enabled_source, False) @@ -58,8 +61,9 @@ class ToggleAction(ItemAction): def _render(self, rect: rl.Rectangle) -> bool: self.toggle.set_enabled(self.enabled) - self.toggle.render(rl.Rectangle(rect.x, rect.y + (rect.height - TOGGLE_HEIGHT) / 2, self._rect.width, TOGGLE_HEIGHT)) - return False + clicked = self.toggle.render(rl.Rectangle(rect.x, rect.y + (rect.height - TOGGLE_HEIGHT) / 2, self._rect.width, TOGGLE_HEIGHT)) + self.state = self.toggle.get_state() + return bool(clicked) def set_state(self, state: bool): self.state = state @@ -86,7 +90,7 @@ class ButtonAction(ItemAction): border_radius=BUTTON_BORDER_RADIUS, click_callback=pressed, ) - self._button.set_enabled(_resolve_value(enabled)) + self.set_enabled(enabled) def set_touch_valid_callback(self, touch_callback: Callable[[], bool]) -> None: super().set_touch_valid_callback(touch_callback) @@ -98,6 +102,7 @@ class ButtonAction(ItemAction): def _render(self, rect: rl.Rectangle) -> bool: self._button.set_text(self.text) + self._button.set_enabled(_resolve_value(self.enabled)) button_rect = rl.Rectangle(rect.x, rect.y + (rect.height - BUTTON_HEIGHT) / 2, BUTTON_WIDTH, BUTTON_HEIGHT) self._button.render(button_rect) @@ -121,6 +126,10 @@ class TextAction(ItemAction): def text(self): return _resolve_value(self._text_source, "Error") + def _update_state(self): + text_width = measure_text_cached(self._font, self.text, ITEM_TEXT_FONT_SIZE).x + self._rect.width = int(text_width + TEXT_PADDING) + def _render(self, rect: rl.Rectangle) -> bool: current_text = self.text text_size = measure_text_cached(self._font, current_text, ITEM_TEXT_FONT_SIZE) @@ -183,7 +192,7 @@ class MultipleButtonAction(ItemAction): # Check button state mouse_pos = rl.get_mouse_position() - is_hovered = rl.check_collision_point_rec(mouse_pos, button_rect) + is_hovered = rl.check_collision_point_rec(mouse_pos, button_rect) and self.enabled is_pressed = is_hovered and rl.is_mouse_button_down(rl.MouseButton.MOUSE_BUTTON_LEFT) and self.is_pressed is_selected = i == self.selected_button @@ -195,6 +204,9 @@ class MultipleButtonAction(ItemAction): else: bg_color = rl.Color(57, 57, 57, 255) # Gray + if not self.enabled: + bg_color = rl.Color(bg_color.r, bg_color.g, bg_color.b, 150) # Dim + # Draw button rl.draw_rectangle_rounded(button_rect, 1.0, 20, bg_color) @@ -202,7 +214,8 @@ class MultipleButtonAction(ItemAction): text_size = measure_text_cached(self._font, text, 40) text_x = button_x + (self.button_width - text_size.x) / 2 text_y = button_y + (BUTTON_HEIGHT - text_size.y) / 2 - rl.draw_text_ex(self._font, text, rl.Vector2(text_x, text_y), 40, 0, rl.Color(228, 228, 228, 255)) + text_color = rl.Color(228, 228, 228, 255) if self.enabled else rl.Color(150, 150, 150, 255) + rl.draw_text_ex(self._font, text, rl.Vector2(text_x, text_y), 40, 0, text_color) # Handle click if is_hovered and rl.is_mouse_button_released(rl.MouseButton.MOUSE_BUTTON_LEFT) and self.is_pressed: diff --git a/system/ui/widgets/network.py b/system/ui/widgets/network.py index 3e6317a49..7a7215b48 100644 --- a/system/ui/widgets/network.py +++ b/system/ui/widgets/network.py @@ -3,14 +3,17 @@ from functools import partial from typing import cast import pyray as rl -from openpilot.system.ui.lib.application import gui_app +from openpilot.common.filter_simple import FirstOrderFilter +from openpilot.system.ui.lib.application import gui_app, DEFAULT_FPS from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel -from openpilot.system.ui.lib.wifi_manager import WifiManager, SecurityType, Network +from openpilot.system.ui.lib.wifi_manager import WifiManager, SecurityType, Network, MeteredType from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.button import ButtonStyle, Button from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog from openpilot.system.ui.widgets.keyboard import Keyboard from openpilot.system.ui.widgets.label import TextAlignment, gui_label +from openpilot.system.ui.widgets.scroller import Scroller +from openpilot.system.ui.widgets.list_view import text_item, button_item, ListItem, ToggleAction, MultipleButtonAction, ButtonAction NM_DEVICE_STATE_NEED_AUTH = 60 MIN_PASSWORD_LENGTH = 8 @@ -26,6 +29,11 @@ STRENGTH_ICONS = [ ] +class PanelType(IntEnum): + WIFI = 0 + ADVANCED = 1 + + class UIState(IntEnum): IDLE = 0 CONNECTING = 1 @@ -34,10 +42,179 @@ class UIState(IntEnum): FORGETTING = 4 +class NavButton(Widget): + def __init__(self, text: str): + super().__init__() + self.text = text + self.set_rect(rl.Rectangle(0, 0, 400, 100)) + self._x_pos_filter = FirstOrderFilter(0.0, 0.05, 1 / DEFAULT_FPS, initialized=False) + self._y_pos_filter = FirstOrderFilter(0.0, 0.05, 1 / DEFAULT_FPS, initialized=False) + + def set_position(self, x: float, y: float) -> None: + x = self._x_pos_filter.update(x) + y = self._y_pos_filter.update(y) + changed = (self._rect.x != x or self._rect.y != y) + self._rect.x, self._rect.y = x, y + if changed: + self._update_layout_rects() + + def _render(self, _): + color = rl.Color(74, 74, 74, 255) if self.is_pressed else rl.Color(57, 57, 57, 255) + rl.draw_rectangle_rounded(self._rect, 0.6, 10, color) + gui_label(self.rect, self.text, font_size=60, alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER) + + +class NetworkUI(Widget): + def __init__(self, wifi_manager: WifiManager): + super().__init__() + self._wifi_manager = wifi_manager + self._current_panel: PanelType = PanelType.WIFI + self._wifi_panel = WifiManagerUI(wifi_manager) + self._advanced_panel = AdvancedNetworkSettings(wifi_manager) + self._nav_button = NavButton("Advanced") + self._nav_button.set_click_callback(self._cycle_panel) + + def _update_state(self): + self._wifi_manager.process_callbacks() + + def show_event(self): + self._set_current_panel(PanelType.WIFI) + self._wifi_panel.show_event() + + def hide_event(self): + self._wifi_panel.hide_event() + + def _cycle_panel(self): + if self._current_panel == PanelType.WIFI: + self._set_current_panel(PanelType.ADVANCED) + else: + self._set_current_panel(PanelType.WIFI) + + def _render(self, _): + # subtract button + content_rect = rl.Rectangle(self._rect.x, self._rect.y + self._nav_button.rect.height + 20, + self._rect.width, self._rect.height - self._nav_button.rect.height - 20) + if self._current_panel == PanelType.WIFI: + self._nav_button.text = "Advanced" + self._nav_button.set_position(self._rect.x + self._rect.width - self._nav_button.rect.width, self._rect.y + 10) + self._wifi_panel.render(content_rect) + else: + self._nav_button.text = "Back" + self._nav_button.set_position(self._rect.x, self._rect.y + 10) + self._advanced_panel.render(content_rect) + + self._nav_button.render() + + def _set_current_panel(self, panel: PanelType): + self._current_panel = panel + + +class AdvancedNetworkSettings(Widget): + def __init__(self, wifi_manager: WifiManager): + super().__init__() + self._wifi_manager = wifi_manager + self._wifi_manager.set_callbacks(networks_updated=self._on_network_updated) + + self._keyboard = Keyboard(max_text_size=MAX_PASSWORD_LENGTH, min_text_size=MIN_PASSWORD_LENGTH, show_password_toggle=True) + + # Tethering + self._tethering_action = ToggleAction(initial_state=False) + self._tethering_btn = ListItem(title="Enable Tethering", action_item=self._tethering_action, callback=self._toggle_tethering) + + # Tethering Password + self._tethering_password_action = ButtonAction(text="EDIT") + self._tethering_password_btn = ListItem(title="Tethering Password", action_item=self._tethering_password_action, callback=self._edit_tethering_password) + + # TODO: Roaming toggle, edit APN settings, and cellular metered toggle + + # Metered + self._wifi_metered_action = MultipleButtonAction(["default", "metered", "unmetered"], 255, 0, callback=self._toggle_wifi_metered) + self._wifi_metered_btn = ListItem(title="Wi-Fi Network Metered", description="Prevent large data uploads when on a metered Wi-Fi connection", + action_item=self._wifi_metered_action) + + items: list[Widget] = [ + self._tethering_btn, + self._tethering_password_btn, + text_item("IP Address", lambda: self._wifi_manager.ipv4_address), + self._wifi_metered_btn, + button_item("Hidden Network", "CONNECT", callback=self._connect_to_hidden_network), + ] + + self._scroller = Scroller(items, line_separator=True, spacing=0) + + def _on_network_updated(self, networks: list[Network]): + self._tethering_action.set_enabled(True) + self._tethering_action.set_state(self._wifi_manager.is_tethering_active()) + self._tethering_password_action.set_enabled(True) + + if self._wifi_manager.is_tethering_active() or self._wifi_manager.ipv4_address == "": + self._wifi_metered_action.set_enabled(False) + self._wifi_metered_action.selected_button = 0 + elif self._wifi_manager.ipv4_address != "": + metered = self._wifi_manager.current_network_metered + self._wifi_metered_action.set_enabled(True) + self._wifi_metered_action.selected_button = int(metered) if metered in (MeteredType.UNKNOWN, MeteredType.YES, MeteredType.NO) else 0 + + def _toggle_tethering(self): + checked = self._tethering_action.state + self._tethering_action.set_enabled(False) + if checked: + self._wifi_metered_action.set_enabled(False) + self._wifi_manager.set_tethering_active(checked) + + def _toggle_wifi_metered(self, metered): + metered_type = {0: MeteredType.UNKNOWN, 1: MeteredType.YES, 2: MeteredType.NO}.get(metered, MeteredType.UNKNOWN) + self._wifi_metered_action.set_enabled(False) + self._wifi_manager.set_current_network_metered(metered_type) + + def _connect_to_hidden_network(self): + def connect_hidden(result): + if result != 1: + return + + ssid = self._keyboard.text + if not ssid: + return + + def enter_password(result): + password = self._keyboard.text + if password == "": + # connect without password + self._wifi_manager.connect_to_network(ssid, "", hidden=True) + return + + self._wifi_manager.connect_to_network(ssid, password, hidden=True) + + self._keyboard.reset(min_text_size=0) + self._keyboard.set_title("Enter password", f"for \"{ssid}\"") + gui_app.set_modal_overlay(self._keyboard, enter_password) + + self._keyboard.reset(min_text_size=1) + self._keyboard.set_title("Enter SSID", "") + gui_app.set_modal_overlay(self._keyboard, connect_hidden) + + def _edit_tethering_password(self): + def update_password(result): + if result != 1: + return + + password = self._keyboard.text + self._wifi_manager.set_tethering_password(password) + self._tethering_password_action.set_enabled(False) + + self._keyboard.reset(min_text_size=MIN_PASSWORD_LENGTH) + self._keyboard.set_title("Enter new tethering password", "") + self._keyboard.set_text(self._wifi_manager.tethering_password) + gui_app.set_modal_overlay(self._keyboard, update_password) + + def _render(self, _): + self._scroller.render(self._rect) + + class WifiManagerUI(Widget): def __init__(self, wifi_manager: WifiManager): super().__init__() - self.wifi_manager = wifi_manager + self._wifi_manager = wifi_manager self.state: UIState = UIState.IDLE self._state_network: Network | None = None # for CONNECTING / NEEDS_AUTH / SHOW_FORGET_CONFIRM / FORGETTING self._password_retry: bool = False # for NEEDS_AUTH @@ -51,33 +228,31 @@ class WifiManagerUI(Widget): self._forget_networks_buttons: dict[str, Button] = {} self._confirm_dialog = ConfirmDialog("", "Forget", "Cancel") - self.wifi_manager.set_callbacks(need_auth=self._on_need_auth, - activated=self._on_activated, - forgotten=self._on_forgotten, - networks_updated=self._on_network_updated, - disconnected=self._on_disconnected) + self._wifi_manager.set_callbacks(need_auth=self._on_need_auth, + activated=self._on_activated, + forgotten=self._on_forgotten, + networks_updated=self._on_network_updated, + disconnected=self._on_disconnected) def show_event(self): # start/stop scanning when widget is visible - self.wifi_manager.set_active(True) + self._wifi_manager.set_active(True) def hide_event(self): - self.wifi_manager.set_active(False) + self._wifi_manager.set_active(False) def _load_icons(self): for icon in STRENGTH_ICONS + ["icons/checkmark.png", "icons/circled_slash.png", "icons/lock_closed.png"]: gui_app.texture(icon, ICON_SIZE, ICON_SIZE) def _render(self, rect: rl.Rectangle): - self.wifi_manager.process_callbacks() - if not self._networks: gui_label(rect, "Scanning Wi-Fi networks...", 72, alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER) return if self.state == UIState.NEEDS_AUTH and self._state_network: self.keyboard.set_title("Wrong password" if self._password_retry else "Enter password", f"for {self._state_network.ssid}") - self.keyboard.reset() + self.keyboard.reset(min_text_size=MIN_PASSWORD_LENGTH) gui_app.set_modal_overlay(self.keyboard, lambda result: self._on_password_entered(cast(Network, self._state_network), result)) elif self.state == UIState.SHOW_FORGET_CONFIRM and self._state_network: self._confirm_dialog.set_text(f'Forget Wi-Fi Network "{self._state_network.ssid}"?') @@ -200,20 +375,20 @@ class WifiManagerUI(Widget): self.state = UIState.CONNECTING self._state_network = network if network.is_saved and not password: - self.wifi_manager.activate_connection(network.ssid) + self._wifi_manager.activate_connection(network.ssid) else: - self.wifi_manager.connect_to_network(network.ssid, password) + self._wifi_manager.connect_to_network(network.ssid, password) def forget_network(self, network: Network): self.state = UIState.FORGETTING self._state_network = network - self.wifi_manager.forget_connection(network.ssid) + self._wifi_manager.forget_connection(network.ssid) def _on_network_updated(self, networks: list[Network]): self._networks = networks for n in self._networks: self._networks_buttons[n.ssid] = Button(n.ssid, partial(self._networks_buttons_callback, n), font_size=55, text_alignment=TextAlignment.LEFT, - button_style=ButtonStyle.NO_EFFECT) + button_style=ButtonStyle.TRANSPARENT_WHITE) self._forget_networks_buttons[n.ssid] = Button("Forget", partial(self._forget_networks_buttons_callback, n), button_style=ButtonStyle.FORGET_WIFI, font_size=45) diff --git a/system/ui/widgets/toggle.py b/system/ui/widgets/toggle.py index eba5ef9e5..968afda9c 100644 --- a/system/ui/widgets/toggle.py +++ b/system/ui/widgets/toggle.py @@ -20,6 +20,7 @@ class Toggle(Widget): self._enabled = True self._progress = 1.0 if initial_state else 0.0 self._target = self._progress + self._clicked = False def set_rect(self, rect: rl.Rectangle): self._rect = rl.Rectangle(rect.x, rect.y, WIDTH, HEIGHT) @@ -28,6 +29,7 @@ class Toggle(Widget): if not self._enabled: return + self._clicked = True self._state = not self._state self._target = 1.0 if self._state else 0.0 @@ -66,5 +68,10 @@ class Toggle(Widget): knob_y = self._rect.y + HEIGHT / 2 rl.draw_circle(int(knob_x), int(knob_y), HEIGHT / 2, knob_color) + # TODO: use click callback + clicked = self._clicked + self._clicked = False + return clicked + def _blend_color(self, c1, c2, t): return rl.Color(int(c1.r + (c2.r - c1.r) * t), int(c1.g + (c2.g - c1.g) * t), int(c1.b + (c2.b - c1.b) * t), 255) From 2c377e534fa3f0857440415c3c3056d8f6ee4018 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 25 Sep 2025 20:48:12 -0700 Subject: [PATCH 046/910] raylib: wifi manager initialize function (#36203) * init func * rm print * rm * use get_conn settings in another place --- system/ui/lib/wifi_manager.py | 95 +++++++++++++++++++++++------------ 1 file changed, 63 insertions(+), 32 deletions(-) diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index 15aeb94ed..3ce5c839e 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -158,15 +158,26 @@ class WifiManager: self._disconnected: list[Callable[[], None]] = [] self._lock = threading.Lock() - self._scan_thread = threading.Thread(target=self._network_scanner, daemon=True) - self._scan_thread.start() - self._state_thread = threading.Thread(target=self._monitor_state, daemon=True) - self._state_thread.start() - + self._initialize() atexit.register(self.stop) + def _initialize(self): + def worker(): + self._wait_for_wifi_device() + + self._scan_thread.start() + self._state_thread.start() + + if self._tethering_ssid not in self._get_connections(): + self._add_tethering_connection() + + self._tethering_password = self._get_tethering_password() + cloudlog.debug("WifiManager initialized") + + threading.Thread(target=worker, daemon=True).start() + def set_callbacks(self, need_auth: Callable[[str], None] | None = None, activated: Callable[[], None] | None = None, forgotten: Callable[[], None] | None = None, @@ -213,18 +224,11 @@ class WifiManager: self._last_network_update = 0.0 def _monitor_state(self): - # TODO: make an initialize function to only wait in one place - device_path = self._wait_for_wifi_device() - if device_path is None: - return - - self._tethering_password = self._get_tethering_password() - rule = MatchRule( type="signal", interface=NM_DEVICE_IFACE, member="StateChanged", - path=device_path, + path=self._wifi_device, ) # Filter for StateChanged signal @@ -261,8 +265,6 @@ class WifiManager: self._enqueue_callbacks(self._forgotten) def _network_scanner(self): - self._wait_for_wifi_device() - while not self._exit: if self._active: if time.monotonic() - self._last_network_update > SCAN_PERIOD_SECONDS: @@ -273,15 +275,12 @@ class WifiManager: self._last_network_update = time.monotonic() time.sleep(1 / 2.) - def _wait_for_wifi_device(self) -> str | None: - with self._lock: - device_path: str | None = None - while not self._exit: - device_path = self._get_wifi_device() - if device_path is not None: - break - time.sleep(1) - return device_path + def _wait_for_wifi_device(self): + while not self._exit: + device_path = self._get_wifi_device() + if device_path is not None: + break + time.sleep(1) def _get_wifi_device(self) -> str | None: if self._wifi_device is not None: @@ -304,15 +303,12 @@ class WifiManager: conns: dict[str, str] = {} for conn_path in known_connections: - conn_addr = DBusAddress(conn_path, bus_name=NM, interface=NM_CONNECTION_IFACE) - reply = self._router_main.send_and_get_reply(new_method_call(conn_addr, "GetSettings")) + settings = self._get_connection_settings(conn_path) - # ignore connections removed during iteration (need auth, etc.) - if reply.header.message_type == MessageType.error: - cloudlog.warning(f"Failed to get connection properties for {conn_path}") + if len(settings) == 0: + cloudlog.warning(f'Failed to get connection settings for {conn_path}') continue - settings = reply.body[0] if "802-11-wireless" in settings: ssid = settings['802-11-wireless']['ssid'][1].decode("utf-8", "replace") if ssid != "": @@ -322,7 +318,6 @@ class WifiManager: def _get_active_connections(self): return self._router_main.send_and_get_reply(Properties(self._nm).get('ActiveConnections')).body[0][1] - # TODO: use this def _get_connection_settings(self, conn_path: str) -> dict: conn_addr = DBusAddress(conn_path, bus_name=NM, interface=NM_CONNECTION_IFACE) reply = self._router_main.send_and_get_reply(new_method_call(conn_addr, 'GetSettings')) @@ -331,6 +326,43 @@ class WifiManager: return {} return dict(reply.body[0]) + def _add_tethering_connection(self): + connection = { + 'connection': { + 'type': ('s', '802-11-wireless'), + 'uuid': ('s', str(uuid.uuid4())), + 'id': ('s', 'Hotspot'), + 'autoconnect-retries': ('i', 0), + 'interface-name': ('s', 'wlan0'), + 'autoconnect': ('b', False), + }, + '802-11-wireless': { + 'band': ('s', 'bg'), + 'mode': ('s', 'ap'), + 'ssid': ('ay', self._tethering_ssid.encode("utf-8")), + }, + '802-11-wireless-security': { + 'group': ('as', ['ccmp']), + 'key-mgmt': ('s', 'wpa-psk'), + 'pairwise': ('as', ['ccmp']), + 'proto': ('as', ['rsn']), + 'psk': ('s', DEFAULT_TETHERING_PASSWORD), + }, + 'ipv4': { + 'method': ('s', 'shared'), + 'address-data': ('aa{sv}', [[ + ('address', ('s', TETHERING_IP_ADDRESS)), + ('prefix', ('u', 24)), + ]]), + 'gateway': ('s', TETHERING_IP_ADDRESS), + 'never-default': ('b', True), + }, + 'ipv6': {'method': ('s', 'ignore')}, + } + + settings_addr = DBusAddress(NM_SETTINGS_PATH, bus_name=NM, interface=NM_SETTINGS_IFACE) + self._router_main.send_and_get_reply(new_method_call(settings_addr, 'AddConnection', 'a{sa{sv}}', (connection,))) + def connect_to_network(self, ssid: str, password: str, hidden: bool = False): def worker(): # Clear all connections that may already exist to the network we are connecting to @@ -503,7 +535,6 @@ class WifiManager: self._current_network_metered = MeteredType.YES elif metered_prop == MeteredType.NO: self._current_network_metered = MeteredType.NO - print('current_network_metered', self._current_network_metered) return def set_current_network_metered(self, metered: MeteredType): From cf5b743de64f4a95827c6da9bf6c42d7ae9ebf3a Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 25 Sep 2025 20:55:14 -0700 Subject: [PATCH 047/910] build system cleanups (#36202) * it's all common * never getting fixed * it's just tici * reorders * qcom2 -> tici * Revert "qcom2 -> tici" This reverts commit f4d849b2952cb0e662975805db6a1d32511ed392. * Reapply "qcom2 -> tici" This reverts commit 58b193cb8de872830f8a7821a339edca14e4a337. * is tici * lil more * Revert "is tici" This reverts commit a169be18d3fdcb3ef8317a63a89d8becadabfad8. * Revert "Reapply "qcom2 -> tici"" This reverts commit 26f9c0e7d068fc8a1a5f07383b3616e619cd4e8c. * qcom2 -> __tici__ * lil more * mv lenv * clean that up * lil more] * fix * lil more --- SConstruct | 250 ++++++++++--------------- common/SConscript | 11 +- selfdrive/modeld/SConscript | 4 +- selfdrive/ui/qt/qt_window.cc | 2 +- selfdrive/ui/qt/qt_window.h | 2 +- selfdrive/ui/qt/widgets/cameraview.cc | 12 +- selfdrive/ui/qt/widgets/cameraview.h | 4 +- system/camerad/SConscript | 4 +- system/camerad/cameras/camera_qcom2.cc | 2 +- system/hardware/hw.h | 2 +- system/loggerd/encoderd.cc | 2 +- 11 files changed, 117 insertions(+), 178 deletions(-) diff --git a/SConstruct b/SConstruct index 6f035e304..80273db10 100644 --- a/SConstruct +++ b/SConstruct @@ -3,144 +3,52 @@ import subprocess import sys import sysconfig import platform +import shlex import numpy as np import SCons.Errors SCons.Warnings.warningAsException(True) -# pending upstream fix - https://github.com/SCons/scons/issues/4461 -#SetOption('warn', 'all') - -TICI = os.path.isfile('/TICI') -AGNOS = TICI - Decider('MD5-timestamp') SetOption('num_jobs', max(1, int(os.cpu_count()/2))) -AddOption('--kaitai', - action='store_true', - help='Regenerate kaitai struct parsers') - -AddOption('--asan', - action='store_true', - help='turn on ASAN') - -AddOption('--ubsan', - action='store_true', - help='turn on UBSan') - -AddOption('--ccflags', - action='store', - type='string', - default='', - help='pass arbitrary flags over the command line') - -AddOption('--mutation', - action='store_true', - help='generate mutation-ready code') - +AddOption('--kaitai', action='store_true', help='Regenerate kaitai struct parsers') +AddOption('--asan', action='store_true', help='turn on ASAN') +AddOption('--ubsan', action='store_true', help='turn on UBSan') +AddOption('--mutation', action='store_true', help='generate mutation-ready code') +AddOption('--ccflags', action='store', type='string', default='', help='pass arbitrary flags over the command line') AddOption('--minimal', action='store_false', dest='extras', default=os.path.exists(File('#.lfsconfig').abspath), # minimal by default on release branch (where there's no LFS) help='the minimum build to run openpilot. no tests, tools, etc.') -## Architecture name breakdown (arch) -## - larch64: linux tici aarch64 -## - aarch64: linux pc aarch64 -## - x86_64: linux pc x64 -## - Darwin: mac x64 or arm64 -real_arch = arch = subprocess.check_output(["uname", "-m"], encoding='utf8').rstrip() +# Detect platform +arch = subprocess.check_output(["uname", "-m"], encoding='utf8').rstrip() if platform.system() == "Darwin": arch = "Darwin" brew_prefix = subprocess.check_output(['brew', '--prefix'], encoding='utf8').strip() -elif arch == "aarch64" and AGNOS: +elif arch == "aarch64" and os.path.isfile('/TICI'): arch = "larch64" -assert arch in ["larch64", "aarch64", "x86_64", "Darwin"] - -lenv = { - "PATH": os.environ['PATH'], - "PYTHONPATH": Dir("#").abspath + ':' + Dir(f"#third_party/acados").abspath, - - "ACADOS_SOURCE_DIR": Dir("#third_party/acados").abspath, - "ACADOS_PYTHON_INTERFACE_PATH": Dir("#third_party/acados/acados_template").abspath, - "TERA_PATH": Dir("#").abspath + f"/third_party/acados/{arch}/t_renderer" -} - -rpath = [] - -if arch == "larch64": - cpppath = [ - "#third_party/opencl/include", - ] - - libpath = [ - "/usr/local/lib", - "/system/vendor/lib64", - f"#third_party/acados/{arch}/lib", - ] - - libpath += [ - "#third_party/libyuv/larch64/lib", - "/usr/lib/aarch64-linux-gnu" - ] - cflags = ["-DQCOM2", "-mcpu=cortex-a57"] - cxxflags = ["-DQCOM2", "-mcpu=cortex-a57"] - rpath += ["/usr/local/lib"] -else: - cflags = [] - cxxflags = [] - cpppath = [] - rpath += [] - - # MacOS - if arch == "Darwin": - libpath = [ - f"#third_party/libyuv/{arch}/lib", - f"#third_party/acados/{arch}/lib", - f"{brew_prefix}/lib", - f"{brew_prefix}/opt/openssl@3.0/lib", - f"{brew_prefix}/opt/llvm/lib/c++", - "/System/Library/Frameworks/OpenGL.framework/Libraries", - ] - - cflags += ["-DGL_SILENCE_DEPRECATION"] - cxxflags += ["-DGL_SILENCE_DEPRECATION"] - cpppath += [ - f"{brew_prefix}/include", - f"{brew_prefix}/opt/openssl@3.0/include", - ] - # Linux - else: - libpath = [ - f"#third_party/acados/{arch}/lib", - f"#third_party/libyuv/{arch}/lib", - "/usr/lib", - "/usr/local/lib", - ] - -if GetOption('asan'): - ccflags = ["-fsanitize=address", "-fno-omit-frame-pointer"] - ldflags = ["-fsanitize=address"] -elif GetOption('ubsan'): - ccflags = ["-fsanitize=undefined"] - ldflags = ["-fsanitize=undefined"] -else: - ccflags = [] - ldflags = [] - -# no --as-needed on mac linker -if arch != "Darwin": - ldflags += ["-Wl,--as-needed", "-Wl,--no-undefined"] - -ccflags_option = GetOption('ccflags') -if ccflags_option: - ccflags += ccflags_option.split(' ') +assert arch in [ + "larch64", # linux tici arm64 + "aarch64", # linux pc arm64 + "x86_64", # linux pc x64 + "Darwin", # macOS arm64 (x86 not supported) +] env = Environment( - ENV=lenv, + ENV={ + "PATH": os.environ['PATH'], + "PYTHONPATH": Dir("#").abspath + ':' + Dir(f"#third_party/acados").abspath, + "ACADOS_SOURCE_DIR": Dir("#third_party/acados").abspath, + "ACADOS_PYTHON_INTERFACE_PATH": Dir("#third_party/acados/acados_template").abspath, + "TERA_PATH": Dir("#").abspath + f"/third_party/acados/{arch}/t_renderer" + }, + CC='clang', + CXX='clang++', CCFLAGS=[ "-g", "-fPIC", @@ -153,36 +61,31 @@ env = Environment( "-Wno-c99-designator", "-Wno-reorder-init-list", "-Wno-vla-cxx-extension", - ] + cflags + ccflags, - - CPPPATH=cpppath + [ + ], + CFLAGS=["-std=gnu11"], + CXXFLAGS=["-std=c++1z"], + CPPPATH=[ "#", + "#msgq", + "#third_party", + "#third_party/json11", + "#third_party/linux/include", "#third_party/acados/include", "#third_party/acados/include/blasfeo/include", "#third_party/acados/include/hpipm/include", "#third_party/catch2/include", "#third_party/libyuv/include", - "#third_party/json11", - "#third_party/linux/include", - "#third_party", - "#msgq", ], - - CC='clang', - CXX='clang++', - LINKFLAGS=ldflags, - - RPATH=rpath, - - CFLAGS=["-std=gnu11"] + cflags, - CXXFLAGS=["-std=c++1z"] + cxxflags, - LIBPATH=libpath + [ + LIBPATH=[ + "#common", "#msgq_repo", "#third_party", "#selfdrive/pandad", - "#common", "#rednose/helpers", + f"#third_party/libyuv/{arch}/lib", + f"#third_party/acados/{arch}/lib", ], + RPATH=[], CYTHONCFILESUFFIX=".cpp", COMPILATIONDB_USE_ABSPATH=True, REDNOSE_ROOT="#", @@ -190,29 +93,63 @@ env = Environment( toolpath=["#site_scons/site_tools", "#rednose_repo/site_scons/site_tools"], ) -if arch == "Darwin": - # RPATH is not supported on macOS, instead use the linker flags - darwin_rpath_link_flags = [f"-Wl,-rpath,{path}" for path in env["RPATH"]] - env["LINKFLAGS"] += darwin_rpath_link_flags +# Arch-specific flags and paths +if arch == "larch64": + env.Append(CPPPATH=["#third_party/opencl/include"]) + env.Append(LIBPATH=[ + "/usr/local/lib", + "/system/vendor/lib64", + "/usr/lib/aarch64-linux-gnu", + ]) + arch_flags = ["-D__TICI__", "-mcpu=cortex-a57"] + env.Append(CCFLAGS=arch_flags) + env.Append(CXXFLAGS=arch_flags) +elif arch == "Darwin": + env.Append(LIBPATH=[ + f"{brew_prefix}/lib", + f"{brew_prefix}/opt/openssl@3.0/lib", + f"{brew_prefix}/opt/llvm/lib/c++", + "/System/Library/Frameworks/OpenGL.framework/Libraries", + ]) + env.Append(CCFLAGS=["-DGL_SILENCE_DEPRECATION"]) + env.Append(CXXFLAGS=["-DGL_SILENCE_DEPRECATION"]) + env.Append(CPPPATH=[ + f"{brew_prefix}/include", + f"{brew_prefix}/opt/openssl@3.0/include", + ]) +else: + env.Append(LIBPATH=[ + "/usr/lib", + "/usr/local/lib", + ]) -env.CompilationDatabase('compile_commands.json') +# Sanitizers and extra CCFLAGS from CLI +if GetOption('asan'): + env.Append(CCFLAGS=["-fsanitize=address", "-fno-omit-frame-pointer"]) + env.Append(LINKFLAGS=["-fsanitize=address"]) +elif GetOption('ubsan'): + env.Append(CCFLAGS=["-fsanitize=undefined"]) + env.Append(LINKFLAGS=["-fsanitize=undefined"]) -# Setup cache dir -cache_dir = '/data/scons_cache' if AGNOS else '/tmp/scons_cache' -CacheDir(cache_dir) -Clean(["."], cache_dir) +_extra_cc = shlex.split(GetOption('ccflags') or '') +if _extra_cc: + env.Append(CCFLAGS=_extra_cc) +# no --as-needed on mac linker +if arch != "Darwin": + env.Append(LINKFLAGS=["-Wl,--as-needed", "-Wl,--no-undefined"]) + +# progress output node_interval = 5 node_count = 0 def progress_function(node): global node_count node_count += node_interval sys.stderr.write("progress: %d\n" % node_count) - if os.environ.get('SCONS_PROGRESS'): Progress(progress_function, interval=node_interval) -# Cython build environment +# ********** Cython build environment ********** py_include = sysconfig.get_paths()['include'] envCython = env.Clone() envCython["CPPPATH"] += [py_include, np.get_include()] @@ -221,14 +158,14 @@ envCython["CCFLAGS"].remove("-Werror") envCython["LIBS"] = [] if arch == "Darwin": - envCython["LINKFLAGS"] = ["-bundle", "-undefined", "dynamic_lookup"] + darwin_rpath_link_flags + envCython["LINKFLAGS"] = env["LINKFLAGS"] + ["-bundle", "-undefined", "dynamic_lookup"] else: envCython["LINKFLAGS"] = ["-pthread", "-shared"] np_version = SCons.Script.Value(np.__version__) Export('envCython', 'np_version') -# Qt build environment +# ********** Qt build environment ********** qt_env = env.Clone() qt_modules = ["Widgets", "Gui", "Core", "Network", "Concurrent", "DBus", "Xml"] @@ -278,16 +215,20 @@ qt_env['CXXFLAGS'] += qt_flags qt_env['LIBPATH'] += ['#selfdrive/ui', ] qt_env['LIBS'] = qt_libs -Export('env', 'qt_env', 'arch', 'real_arch') +Export('env', 'qt_env', 'arch') + +# Setup cache dir +cache_dir = '/data/scons_cache' if arch == "larch64" else '/tmp/scons_cache' +CacheDir(cache_dir) +Clean(["."], cache_dir) + +# ********** start building stuff ********** # Build common module SConscript(['common/SConscript']) -Import('_common', '_gpucommon') - +Import('_common') common = [_common, 'json11', 'zmq'] -gpucommon = [_gpucommon] - -Export('common', 'gpucommon') +Export('common') # Build messaging (cereal + msgq + socketmaster + their dependencies) # Enable swaglog include in submodules @@ -327,3 +268,6 @@ if Dir('#tools/cabana/').exists() and GetOption('extras'): SConscript(['tools/replay/SConscript']) if arch != "larch64": SConscript(['tools/cabana/SConscript']) + + +env.CompilationDatabase('compile_commands.json') diff --git a/common/SConscript b/common/SConscript index 3cdb6fc5a..0891b7903 100644 --- a/common/SConscript +++ b/common/SConscript @@ -5,17 +5,12 @@ common_libs = [ 'swaglog.cc', 'util.cc', 'watchdog.cc', - 'ratekeeper.cc' -] - -_common = env.Library('common', common_libs, LIBS="json11") - -files = [ + 'ratekeeper.cc', 'clutil.cc', ] -_gpucommon = env.Library('gpucommon', files) -Export('_common', '_gpucommon') +_common = env.Library('common', common_libs, LIBS="json11") +Export('_common') if GetOption('extras'): env.Program('tests/test_common', diff --git a/selfdrive/modeld/SConscript b/selfdrive/modeld/SConscript index 1a6df4bb9..f20855c2c 100644 --- a/selfdrive/modeld/SConscript +++ b/selfdrive/modeld/SConscript @@ -1,11 +1,11 @@ import os import glob -Import('env', 'envCython', 'arch', 'cereal', 'messaging', 'common', 'gpucommon', 'visionipc', 'transformations') +Import('env', 'envCython', 'arch', 'cereal', 'messaging', 'common', 'visionipc', 'transformations') lenv = env.Clone() lenvCython = envCython.Clone() -libs = [cereal, messaging, visionipc, gpucommon, common, 'capnp', 'kj', 'pthread'] +libs = [cereal, messaging, visionipc, common, 'capnp', 'kj', 'pthread'] frameworks = [] common_src = [ diff --git a/selfdrive/ui/qt/qt_window.cc b/selfdrive/ui/qt/qt_window.cc index 8d3d7cf72..cdd817ae2 100644 --- a/selfdrive/ui/qt/qt_window.cc +++ b/selfdrive/ui/qt/qt_window.cc @@ -13,7 +13,7 @@ void setMainWindow(QWidget *w) { } w->show(); -#ifdef QCOM2 +#ifdef __TICI__ QPlatformNativeInterface *native = QGuiApplication::platformNativeInterface(); wl_surface *s = reinterpret_cast(native->nativeResourceForWindow("surface", w->windowHandle())); wl_surface_set_buffer_transform(s, WL_OUTPUT_TRANSFORM_270); diff --git a/selfdrive/ui/qt/qt_window.h b/selfdrive/ui/qt/qt_window.h index 6f16e0095..b9783477e 100644 --- a/selfdrive/ui/qt/qt_window.h +++ b/selfdrive/ui/qt/qt_window.h @@ -6,7 +6,7 @@ #include #include -#ifdef QCOM2 +#ifdef __TICI__ #include #include #include diff --git a/selfdrive/ui/qt/widgets/cameraview.cc b/selfdrive/ui/qt/widgets/cameraview.cc index 81ef61339..5f533cd09 100644 --- a/selfdrive/ui/qt/widgets/cameraview.cc +++ b/selfdrive/ui/qt/widgets/cameraview.cc @@ -27,7 +27,7 @@ const char frame_vertex_shader[] = "}\n"; const char frame_fragment_shader[] = -#ifdef QCOM2 +#ifdef __TICI__ "#version 300 es\n" "#extension GL_OES_EGL_image_external_essl3 : enable\n" "precision mediump float;\n" @@ -79,7 +79,7 @@ CameraWidget::~CameraWidget() { glDeleteVertexArrays(1, &frame_vao); glDeleteBuffers(1, &frame_vbo); glDeleteBuffers(1, &frame_ibo); -#ifndef QCOM2 +#ifndef __TICI__ glDeleteTextures(2, textures); #endif } @@ -137,7 +137,7 @@ void CameraWidget::initializeGL() { glUseProgram(program->programId()); -#ifdef QCOM2 +#ifdef __TICI__ glUniform1i(program->uniformLocation("uTexture"), 0); #else glGenTextures(2, textures); @@ -165,7 +165,7 @@ void CameraWidget::stopVipcThread() { vipc_thread = nullptr; } -#ifdef QCOM2 +#ifdef __TICI__ EGLDisplay egl_display = eglGetCurrentDisplay(); assert(egl_display != EGL_NO_DISPLAY); for (auto &pair : egl_images) { @@ -226,7 +226,7 @@ void CameraWidget::paintGL() { glUseProgram(program->programId()); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); -#ifdef QCOM2 +#ifdef __TICI__ // no frame copy glActiveTexture(GL_TEXTURE0); glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, egl_images[frame->idx]); @@ -263,7 +263,7 @@ void CameraWidget::vipcConnected(VisionIpcClient *vipc_client) { stream_height = vipc_client->buffers[0].height; stream_stride = vipc_client->buffers[0].stride; -#ifdef QCOM2 +#ifdef __TICI__ EGLDisplay egl_display = eglGetCurrentDisplay(); assert(egl_display != EGL_NO_DISPLAY); for (auto &pair : egl_images) { diff --git a/selfdrive/ui/qt/widgets/cameraview.h b/selfdrive/ui/qt/widgets/cameraview.h index 29aa8493c..598603a08 100644 --- a/selfdrive/ui/qt/widgets/cameraview.h +++ b/selfdrive/ui/qt/widgets/cameraview.h @@ -13,7 +13,7 @@ #include #include -#ifdef QCOM2 +#ifdef __TICI__ #define EGL_EGLEXT_PROTOTYPES #define EGL_NO_X11 #define GL_TEXTURE_EXTERNAL_OES 0x8D65 @@ -63,7 +63,7 @@ protected: std::unique_ptr program; QColor bg = QColor("#000000"); -#ifdef QCOM2 +#ifdef __TICI__ std::map egl_images; #endif diff --git a/system/camerad/SConscript b/system/camerad/SConscript index 734f748a2..e288c6d8b 100644 --- a/system/camerad/SConscript +++ b/system/camerad/SConscript @@ -1,6 +1,6 @@ -Import('env', 'arch', 'messaging', 'common', 'gpucommon', 'visionipc') +Import('env', 'arch', 'messaging', 'common', 'visionipc') -libs = [common, 'OpenCL', messaging, visionipc, gpucommon] +libs = [common, 'OpenCL', messaging, visionipc] if arch != "Darwin": camera_obj = env.Object(['cameras/camera_qcom2.cc', 'cameras/camera_common.cc', 'cameras/spectra.cc', diff --git a/system/camerad/cameras/camera_qcom2.cc b/system/camerad/cameras/camera_qcom2.cc index 8c4602bb3..85f358977 100644 --- a/system/camerad/cameras/camera_qcom2.cc +++ b/system/camerad/cameras/camera_qcom2.cc @@ -12,7 +12,7 @@ #include #include -#ifdef QCOM2 +#ifdef __TICI__ #include "CL/cl_ext_qcom.h" #else #define CL_PRIORITY_HINT_HIGH_QCOM NULL diff --git a/system/hardware/hw.h b/system/hardware/hw.h index d2083a598..a9058401c 100644 --- a/system/hardware/hw.h +++ b/system/hardware/hw.h @@ -5,7 +5,7 @@ #include "system/hardware/base.h" #include "common/util.h" -#if QCOM2 +#if __TICI__ #include "system/hardware/tici/hardware.h" #define Hardware HardwareTici #else diff --git a/system/loggerd/encoderd.cc b/system/loggerd/encoderd.cc index 3237d1307..9d4b81a3f 100644 --- a/system/loggerd/encoderd.cc +++ b/system/loggerd/encoderd.cc @@ -3,7 +3,7 @@ #include "system/loggerd/loggerd.h" #include "system/loggerd/encoder/jpeg_encoder.h" -#ifdef QCOM2 +#ifdef __TICI__ #include "system/loggerd/encoder/v4l_encoder.h" #define Encoder V4LEncoder #else From 1fbec6f601515599a39e65372ae4e256c1d8e642 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 25 Sep 2025 21:02:26 -0700 Subject: [PATCH 048/910] remove .clang-tidy --- .clang-tidy | 19 ------------------- .dockerignore | 21 --------------------- .gitignore | 9 +-------- 3 files changed, 1 insertion(+), 48 deletions(-) delete mode 100644 .clang-tidy diff --git a/.clang-tidy b/.clang-tidy deleted file mode 100644 index cc55fefd2..000000000 --- a/.clang-tidy +++ /dev/null @@ -1,19 +0,0 @@ ---- -Checks: ' -bugprone-*, --bugprone-integer-division, --bugprone-narrowing-conversions, -performance-*, -clang-analyzer-*, -misc-*, --misc-unused-parameters, -modernize-*, --modernize-avoid-c-arrays, --modernize-deprecated-headers, --modernize-use-auto, --modernize-use-using, --modernize-use-nullptr, --modernize-use-trailing-return-type, -' -CheckOptions: -... diff --git a/.dockerignore b/.dockerignore index cfc34e584..631ea0484 100644 --- a/.dockerignore +++ b/.dockerignore @@ -13,27 +13,6 @@ *.o-* *.os *.os-* -*.so -*.a venv/ .venv/ - -notebooks -phone -massivemap -neos -installer -chffr/app2 -chffr/backend/env -selfdrive/nav -selfdrive/baseui -selfdrive/test/simulator2 -**/cache_data -xx/plus -xx/community -xx/projects -!xx/projects/eon_testing_master -!xx/projects/map3d -xx/ops -xx/junk diff --git a/.gitignore b/.gitignore index c594fb53d..0832b3964 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,6 @@ venv/ .overlay_init .overlay_consistent .sconsign.dblite -model2.png a.out .hypothesis .cache/ @@ -44,22 +43,15 @@ clcache compile_commands.json compare_runtime*.html -persist selfdrive/pandad/pandad cereal/services.h cereal/gen cereal/messaging/bridge -selfdrive/mapd/default_speeds_by_region.json selfdrive/ui/translations/tmp -selfdrive/test/longitudinal_maneuvers/out selfdrive/car/tests/cars_dump system/camerad/camerad system/camerad/test/ae_gray_test -notebooks -hyperthneed -provisioning - .coverage* coverage.xml htmlcov @@ -73,6 +65,7 @@ comma*.sh selfdrive/modeld/models/*.pkl +# openpilot log files *.bz2 *.zst From 33f01084d16301025a58a7eba42083c74998eb6a Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 25 Sep 2025 23:44:12 -0700 Subject: [PATCH 049/910] raylib: implement cell settings (#36204) * get vibing * simplify * vibing is bad * simplify * fix that * now update * clean up * last two * cell is UpdateUnsaved so we don't need to disable * we only need actions * we only need actions * sort * stuff * dont deactivate * clean up * clean up * more * ipv4 fwd * warns * fixz * rm * clean up * one return point * format * top --- system/ui/lib/wifi_manager.py | 125 +++++++++++++++++++++++++++++----- system/ui/widgets/network.py | 82 +++++++++++++++++++--- 2 files changed, 180 insertions(+), 27 deletions(-) diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index 3ce5c839e..e24686566 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -2,6 +2,7 @@ import atexit import threading import time import uuid +import subprocess from collections.abc import Callable from dataclasses import dataclass from enum import IntEnum @@ -23,7 +24,7 @@ from openpilot.system.ui.lib.networkmanager import (NM, NM_WIRELESS_IFACE, NM_80 NM_802_11_AP_FLAGS_PRIVACY, NM_802_11_AP_FLAGS_WPS, NM_PATH, NM_IFACE, NM_ACCESS_POINT_IFACE, NM_SETTINGS_PATH, NM_SETTINGS_IFACE, NM_CONNECTION_IFACE, NM_DEVICE_IFACE, - NM_DEVICE_TYPE_WIFI, NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT, + NM_DEVICE_TYPE_WIFI, NM_DEVICE_TYPE_MODEM, NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT, NM_DEVICE_STATE_REASON_NEW_ACTIVATION, NM_ACTIVE_CONNECTION_IFACE, NM_IP4_CONFIG_IFACE, NMDeviceState) @@ -142,6 +143,8 @@ class WifiManager: self._ipv4_address: str = "" self._current_network_metered: MeteredType = MeteredType.UNKNOWN self._tethering_password: str = "" + self._ipv4_forward = False + self._last_network_update: float = 0.0 self._callback_queue: list[Callable] = [] @@ -277,25 +280,24 @@ class WifiManager: def _wait_for_wifi_device(self): while not self._exit: - device_path = self._get_wifi_device() + device_path = self._get_adapter(NM_DEVICE_TYPE_WIFI) if device_path is not None: + self._wifi_device = device_path break time.sleep(1) - def _get_wifi_device(self) -> str | None: - if self._wifi_device is not None: - return self._wifi_device - - device_paths = self._router_main.send_and_get_reply(new_method_call(self._nm, 'GetDevices')).body[0] - for device_path in device_paths: - dev_addr = DBusAddress(device_path, bus_name=NM, interface=NM_DEVICE_IFACE) - dev_type = self._router_main.send_and_get_reply(Properties(dev_addr).get('DeviceType')).body[0][1] - - if dev_type == NM_DEVICE_TYPE_WIFI: - self._wifi_device = device_path - break - - return self._wifi_device + def _get_adapter(self, adapter_type: int) -> str | None: + # Return the first NetworkManager device path matching adapter_type + try: + device_paths = self._router_main.send_and_get_reply(new_method_call(self._nm, 'GetDevices')).body[0] + for device_path in device_paths: + dev_addr = DBusAddress(device_path, bus_name=NM, interface=NM_DEVICE_IFACE) + dev_type = self._router_main.send_and_get_reply(Properties(dev_addr).get('DeviceType')).body[0][1] + if dev_type == adapter_type: + return str(device_path) + except Exception as e: + cloudlog.exception(f"Error getting adapter type {adapter_type}: {e}") + return None def _get_connections(self) -> dict[str, str]: settings_addr = DBusAddress(NM_SETTINGS_PATH, bus_name=NM, interface=NM_SETTINGS_IFACE) @@ -500,10 +502,18 @@ class WifiManager: return str(secrets['802-11-wireless-security'].get('psk', ('s', ''))[1]) + def set_ipv4_forward(self, enabled: bool): + self._ipv4_forward = enabled + def set_tethering_active(self, active: bool): def worker(): if active: self.activate_connection(self._tethering_ssid, block=True) + + if not self._ipv4_forward: + time.sleep(5) + cloudlog.warning("net.ipv4.ip_forward = 0") + subprocess.run(["sudo", "sysctl", "net.ipv4.ip_forward=0"], check=False) else: self._deactivate_connection(self._tethering_ssid) @@ -645,6 +655,89 @@ class WifiManager: def __del__(self): self.stop() + def update_gsm_settings(self, roaming: bool, apn: str, metered: bool): + """Update GSM settings for cellular connection""" + + def worker(): + try: + lte_connection_path = self._get_lte_connection_path() + if not lte_connection_path: + cloudlog.warning("No LTE connection found") + return + + settings = self._get_connection_settings(lte_connection_path) + + if len(settings) == 0: + cloudlog.warning(f"Failed to get connection settings for {lte_connection_path}") + return + + # Ensure dicts exist + if 'gsm' not in settings: + settings['gsm'] = {} + if 'connection' not in settings: + settings['connection'] = {} + + changes = False + auto_config = apn == "" + + if settings['gsm'].get('auto-config', ('b', False))[1] != auto_config: + cloudlog.warning(f'Changing gsm.auto-config to {auto_config}') + settings['gsm']['auto-config'] = ('b', auto_config) + changes = True + + if settings['gsm'].get('apn', ('s', ''))[1] != apn: + cloudlog.warning(f'Changing gsm.apn to {apn}') + settings['gsm']['apn'] = ('s', apn) + changes = True + + if settings['gsm'].get('home-only', ('b', False))[1] == roaming: + cloudlog.warning(f'Changing gsm.home-only to {not roaming}') + settings['gsm']['home-only'] = ('b', not roaming) + changes = True + + # Unknown means NetworkManager decides + metered_int = int(MeteredType.UNKNOWN if metered else MeteredType.NO) + if settings['connection'].get('metered', ('i', 0))[1] != metered_int: + cloudlog.warning(f'Changing connection.metered to {metered_int}') + settings['connection']['metered'] = ('i', metered_int) + changes = True + + if changes: + # Update the connection settings (temporary update) + conn_addr = DBusAddress(lte_connection_path, bus_name=NM, interface=NM_CONNECTION_IFACE) + reply = self._router_main.send_and_get_reply(new_method_call(conn_addr, 'UpdateUnsaved', 'a{sa{sv}}', (settings,))) + + if reply.header.message_type == MessageType.error: + cloudlog.warning(f"Failed to update GSM settings: {reply}") + return + + self._activate_modem_connection(lte_connection_path) + except Exception as e: + cloudlog.exception(f"Error updating GSM settings: {e}") + + threading.Thread(target=worker, daemon=True).start() + + def _get_lte_connection_path(self) -> str | None: + try: + settings_addr = DBusAddress(NM_SETTINGS_PATH, bus_name=NM, interface=NM_SETTINGS_IFACE) + known_connections = self._router_main.send_and_get_reply(new_method_call(settings_addr, 'ListConnections')).body[0] + + for conn_path in known_connections: + settings = self._get_connection_settings(conn_path) + if settings and settings.get('connection', {}).get('id', ('s', ''))[1] == 'lte': + return str(conn_path) + except Exception as e: + cloudlog.exception(f"Error finding LTE connection: {e}") + return None + + def _activate_modem_connection(self, connection_path: str): + try: + modem_device = self._get_adapter(NM_DEVICE_TYPE_MODEM) + if modem_device and connection_path: + self._router_main.send_and_get_reply(new_method_call(self._nm, 'ActivateConnection', 'ooo', (connection_path, modem_device, "/"))) + except Exception as e: + cloudlog.exception(f"Error activating modem connection: {e}") + def stop(self): if not self._exit: self._exit = True diff --git a/system/ui/widgets/network.py b/system/ui/widgets/network.py index 7a7215b48..119901da4 100644 --- a/system/ui/widgets/network.py +++ b/system/ui/widgets/network.py @@ -4,6 +4,7 @@ from typing import cast import pyray as rl from openpilot.common.filter_simple import FirstOrderFilter +from openpilot.common.params import Params from openpilot.system.ui.lib.application import gui_app, DEFAULT_FPS from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel from openpilot.system.ui.lib.wifi_manager import WifiManager, SecurityType, Network, MeteredType @@ -13,7 +14,9 @@ from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog from openpilot.system.ui.widgets.keyboard import Keyboard from openpilot.system.ui.widgets.label import TextAlignment, gui_label from openpilot.system.ui.widgets.scroller import Scroller -from openpilot.system.ui.widgets.list_view import text_item, button_item, ListItem, ToggleAction, MultipleButtonAction, ButtonAction +from openpilot.system.ui.widgets.list_view import ButtonAction, ListItem, MultipleButtonAction, ToggleAction, button_item, text_item +from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.selfdrive.ui.lib.prime_state import PrimeType NM_DEVICE_STATE_NEED_AUTH = 60 MIN_PASSWORD_LENGTH = 8 @@ -114,34 +117,54 @@ class AdvancedNetworkSettings(Widget): super().__init__() self._wifi_manager = wifi_manager self._wifi_manager.set_callbacks(networks_updated=self._on_network_updated) + self._params = Params() self._keyboard = Keyboard(max_text_size=MAX_PASSWORD_LENGTH, min_text_size=MIN_PASSWORD_LENGTH, show_password_toggle=True) # Tethering self._tethering_action = ToggleAction(initial_state=False) - self._tethering_btn = ListItem(title="Enable Tethering", action_item=self._tethering_action, callback=self._toggle_tethering) + tethering_btn = ListItem(title="Enable Tethering", action_item=self._tethering_action, callback=self._toggle_tethering) - # Tethering Password + # Edit tethering password self._tethering_password_action = ButtonAction(text="EDIT") - self._tethering_password_btn = ListItem(title="Tethering Password", action_item=self._tethering_password_action, callback=self._edit_tethering_password) + tethering_password_btn = ListItem(title="Tethering Password", action_item=self._tethering_password_action, callback=self._edit_tethering_password) - # TODO: Roaming toggle, edit APN settings, and cellular metered toggle + # Roaming toggle + roaming_enabled = self._params.get_bool("GsmRoaming") + self._roaming_action = ToggleAction(initial_state=roaming_enabled) + self._roaming_btn = ListItem(title="Enable Roaming", action_item=self._roaming_action, callback=self._toggle_roaming) - # Metered + # Cellular metered toggle + cellular_metered = self._params.get_bool("GsmMetered") + self._cellular_metered_action = ToggleAction(initial_state=cellular_metered) + self._cellular_metered_btn = ListItem(title="Cellular Metered", description="Prevent large data uploads when on a metered cellular connection", + action_item=self._cellular_metered_action, callback=self._toggle_cellular_metered) + + # APN setting + self._apn_btn = button_item("APN Setting", "EDIT", callback=self._edit_apn) + + # Wi-Fi metered toggle self._wifi_metered_action = MultipleButtonAction(["default", "metered", "unmetered"], 255, 0, callback=self._toggle_wifi_metered) - self._wifi_metered_btn = ListItem(title="Wi-Fi Network Metered", description="Prevent large data uploads when on a metered Wi-Fi connection", - action_item=self._wifi_metered_action) + wifi_metered_btn = ListItem(title="Wi-Fi Network Metered", description="Prevent large data uploads when on a metered Wi-Fi connection", + action_item=self._wifi_metered_action) items: list[Widget] = [ - self._tethering_btn, - self._tethering_password_btn, + tethering_btn, + tethering_password_btn, text_item("IP Address", lambda: self._wifi_manager.ipv4_address), - self._wifi_metered_btn, + self._roaming_btn, + self._apn_btn, + self._cellular_metered_btn, + wifi_metered_btn, button_item("Hidden Network", "CONNECT", callback=self._connect_to_hidden_network), ] self._scroller = Scroller(items, line_separator=True, spacing=0) + # Set initial config + metered = self._params.get_bool("GsmMetered") + self._wifi_manager.update_gsm_settings(roaming_enabled, self._params.get("GsmApn") or "", metered) + def _on_network_updated(self, networks: list[Network]): self._tethering_action.set_enabled(True) self._tethering_action.set_state(self._wifi_manager.is_tethering_active()) @@ -162,6 +185,35 @@ class AdvancedNetworkSettings(Widget): self._wifi_metered_action.set_enabled(False) self._wifi_manager.set_tethering_active(checked) + def _toggle_roaming(self): + roaming_state = self._roaming_action.state + self._params.put_bool("GsmRoaming", roaming_state) + self._wifi_manager.update_gsm_settings(roaming_state, self._params.get("GsmApn") or "", self._params.get_bool("GsmMetered")) + + def _edit_apn(self): + def update_apn(result): + if result != 1: + return + + apn = self._keyboard.text.strip() + if apn == "": + self._params.remove("GsmApn") + else: + self._params.put("GsmApn", apn) + + self._wifi_manager.update_gsm_settings(self._params.get_bool("GsmRoaming"), apn, self._params.get_bool("GsmMetered")) + + current_apn = self._params.get("GsmApn") or "" + self._keyboard.reset(min_text_size=0) + self._keyboard.set_title("Enter APN", "leave blank for automatic configuration") + self._keyboard.set_text(current_apn) + gui_app.set_modal_overlay(self._keyboard, update_apn) + + def _toggle_cellular_metered(self): + metered = self._cellular_metered_action.state + self._params.put_bool("GsmMetered", metered) + self._wifi_manager.update_gsm_settings(self._params.get_bool("GsmRoaming"), self._params.get("GsmApn") or "", metered) + def _toggle_wifi_metered(self, metered): metered_type = {0: MeteredType.UNKNOWN, 1: MeteredType.YES, 2: MeteredType.NO}.get(metered, MeteredType.UNKNOWN) self._wifi_metered_action.set_enabled(False) @@ -207,6 +259,14 @@ class AdvancedNetworkSettings(Widget): self._keyboard.set_text(self._wifi_manager.tethering_password) gui_app.set_modal_overlay(self._keyboard, update_password) + def _update_state(self): + # If not using prime SIM, show GSM settings and enable IPv4 forwarding + show_cell_settings = ui_state.prime_state.get_type() in (PrimeType.NONE, PrimeType.LITE) + self._wifi_manager.set_ipv4_forward(show_cell_settings) + self._roaming_btn.set_visible(show_cell_settings) + self._apn_btn.set_visible(show_cell_settings) + self._cellular_metered_btn.set_visible(show_cell_settings) + def _render(self, _): self._scroller.render(self._rect) From 0711160b1c1c88f939401b7ae6f5664b834d9c52 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 26 Sep 2025 18:59:15 -0700 Subject: [PATCH 050/910] raylib: dismiss dialog on pair (#36205) * show for unknown * use Button to make clicking work * close on pair * close on pair * make widget! * dynamic pairing btn * whyyy * clean up * can do this * this button is also hard to tap --- selfdrive/ui/layouts/settings/device.py | 5 ++++- selfdrive/ui/lib/prime_state.py | 20 ++++++++++++-------- selfdrive/ui/ui.py | 9 ++++----- selfdrive/ui/widgets/pairing_dialog.py | 11 +++++++++-- selfdrive/ui/widgets/setup.py | 15 +++++++-------- system/ui/lib/application.py | 3 ++- system/ui/widgets/scroller.py | 11 ++++++++--- 7 files changed, 46 insertions(+), 28 deletions(-) diff --git a/selfdrive/ui/layouts/settings/device.py b/selfdrive/ui/layouts/settings/device.py index df8bba030..14847df10 100644 --- a/selfdrive/ui/layouts/settings/device.py +++ b/selfdrive/ui/layouts/settings/device.py @@ -44,10 +44,13 @@ class DeviceLayout(Widget): dongle_id = self._params.get("DongleId") or "N/A" serial = self._params.get("HardwareSerial") or "N/A" + self._pair_device_btn = button_item("Pair Device", "PAIR", DESCRIPTIONS['pair_device'], callback=self._pair_device) + self._pair_device_btn.set_visible(lambda: not ui_state.prime_state.is_paired()) + items = [ text_item("Dongle ID", dongle_id), text_item("Serial", serial), - button_item("Pair Device", "PAIR", DESCRIPTIONS['pair_device'], callback=self._pair_device), + self._pair_device_btn, button_item("Driver Camera", "PREVIEW", DESCRIPTIONS['driver_camera'], callback=self._show_driver_camera, enabled=ui_state.is_offroad), button_item("Reset Calibration", "RESET", DESCRIPTIONS['reset_calibration'], callback=self._reset_calibration_prompt), regulatory_btn := button_item("Regulatory", "VIEW", callback=self._on_regulatory), diff --git a/selfdrive/ui/lib/prime_state.py b/selfdrive/ui/lib/prime_state.py index be2132c1b..30ad0f763 100644 --- a/selfdrive/ui/lib/prime_state.py +++ b/selfdrive/ui/lib/prime_state.py @@ -11,14 +11,14 @@ from openpilot.selfdrive.ui.lib.api_helpers import get_token class PrimeType(IntEnum): - UNKNOWN = -2, - UNPAIRED = -1, - NONE = 0, - MAGENTA = 1, - LITE = 2, - BLUE = 3, - MAGENTA_NEW = 4, - PURPLE = 5, + UNKNOWN = -2 + UNPAIRED = -1 + NONE = 0 + MAGENTA = 1 + LITE = 2 + BLUE = 3 + MAGENTA_NEW = 4 + PURPLE = 5 class PrimeState: @@ -96,5 +96,9 @@ class PrimeState: with self._lock: return bool(self.prime_type > PrimeType.NONE) + def is_paired(self) -> bool: + with self._lock: + return self.prime_type > PrimeType.UNPAIRED + def __del__(self): self.stop() diff --git a/selfdrive/ui/ui.py b/selfdrive/ui/ui.py index bb2b431e0..0230e16a4 100755 --- a/selfdrive/ui/ui.py +++ b/selfdrive/ui/ui.py @@ -10,15 +10,14 @@ def main(): gui_app.init_window("UI") main_layout = MainLayout() main_layout.set_rect(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) - for _ in gui_app.render(): + for showing_dialog in gui_app.render(): ui_state.update() - # TODO handle brigntness and awake state here - - main_layout.render() - kick_watchdog() + if not showing_dialog: + main_layout.render() + if __name__ == "__main__": main() diff --git a/selfdrive/ui/widgets/pairing_dialog.py b/selfdrive/ui/widgets/pairing_dialog.py index 79d05eaf4..55d53125d 100644 --- a/selfdrive/ui/widgets/pairing_dialog.py +++ b/selfdrive/ui/widgets/pairing_dialog.py @@ -6,17 +6,20 @@ import time from openpilot.common.api import Api from openpilot.common.swaglog import cloudlog from openpilot.common.params import Params +from openpilot.system.ui.widgets import Widget from openpilot.system.ui.lib.application import FontWeight, gui_app from openpilot.system.ui.lib.wrap_text import wrap_text from openpilot.system.ui.lib.text_measure import measure_text_cached +from openpilot.selfdrive.ui.ui_state import ui_state -class PairingDialog: +class PairingDialog(Widget): """Dialog for device pairing with QR code.""" QR_REFRESH_INTERVAL = 300 # 5 minutes in seconds def __init__(self): + super().__init__() self.params = Params() self.qr_texture: rl.Texture | None = None self.last_qr_generation = 0 @@ -60,7 +63,11 @@ class PairingDialog: self._generate_qr_code() self.last_qr_generation = current_time - def render(self, rect: rl.Rectangle) -> int: + def _update_state(self): + if ui_state.prime_state.is_paired(): + gui_app.set_modal_overlay(None) + + def _render(self, rect: rl.Rectangle) -> int: rl.clear_background(rl.Color(224, 224, 224, 255)) self._check_qr_refresh() diff --git a/selfdrive/ui/widgets/setup.py b/selfdrive/ui/widgets/setup.py index 35a7c4101..bf6d113f6 100644 --- a/selfdrive/ui/widgets/setup.py +++ b/selfdrive/ui/widgets/setup.py @@ -1,11 +1,10 @@ import pyray as rl -from openpilot.selfdrive.ui.lib.prime_state import PrimeType from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.selfdrive.ui.widgets.pairing_dialog import PairingDialog from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.lib.wrap_text import wrap_text from openpilot.system.ui.widgets import Widget -from openpilot.system.ui.widgets.button import gui_button, ButtonStyle +from openpilot.system.ui.widgets.button import Button, ButtonStyle class SetupWidget(Widget): @@ -13,12 +12,15 @@ class SetupWidget(Widget): super().__init__() self._open_settings_callback = None self._pairing_dialog: PairingDialog | None = None + self._pair_device_btn = Button("Pair device", self._show_pairing, button_style=ButtonStyle.PRIMARY) + self._open_settings_btn = Button("Open", lambda: self._open_settings_callback() if self._open_settings_callback else None, + button_style=ButtonStyle.PRIMARY) def set_open_settings_callback(self, callback): self._open_settings_callback = callback def _render(self, rect: rl.Rectangle): - if ui_state.prime_state.get_type() == PrimeType.UNPAIRED: + if not ui_state.prime_state.is_paired(): self._render_registration(rect) else: self._render_firehose_prompt(rect) @@ -46,8 +48,7 @@ class SetupWidget(Widget): y += 50 button_rect = rl.Rectangle(x, y + 50, w, 128) - if gui_button(button_rect, "Pair device", button_style=ButtonStyle.PRIMARY): - self._show_pairing() + self._pair_device_btn.render(button_rect) def _render_firehose_prompt(self, rect: rl.Rectangle): """Render firehose prompt widget.""" @@ -80,9 +81,7 @@ class SetupWidget(Widget): # Open button button_height = 48 + 64 # font size + padding button_rect = rl.Rectangle(x, y, w, button_height) - if gui_button(button_rect, "Open", button_style=ButtonStyle.PRIMARY): - if self._open_settings_callback: - self._open_settings_callback() + self._open_settings_btn.render(button_rect) def _show_pairing(self): if not self._pairing_dialog: diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 5b35f7ac9..911891f10 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -274,8 +274,9 @@ class GuiApplication: self._modal_overlay = ModalOverlay() if original_modal.callback is not None: original_modal.callback(result) + yield True else: - yield + yield False if self._render_texture: rl.end_texture_mode() diff --git a/system/ui/widgets/scroller.py b/system/ui/widgets/scroller.py index f7304bf6a..757500a71 100644 --- a/system/ui/widgets/scroller.py +++ b/system/ui/widgets/scroller.py @@ -27,7 +27,7 @@ class Scroller(Widget): super().__init__() self._items: list[Widget] = [] self._spacing = spacing - self._line_separator = line_separator + self._line_separator = LineSeparator() if line_separator else None self._pad_end = pad_end self.scroll_panel = GuiScrollPanel() @@ -36,14 +36,19 @@ class Scroller(Widget): self.add_widget(item) def add_widget(self, item: Widget) -> None: - if self._line_separator and len(self._items) > 0: - self._items.append(LineSeparator()) self._items.append(item) item.set_touch_valid_callback(self.scroll_panel.is_touch_valid) def _render(self, _): # TODO: don't draw items that are not in the viewport visible_items = [item for item in self._items if item.is_visible] + + # Add line separator between items + if self._line_separator is not None: + l = len(visible_items) + for i in range(1, len(visible_items)): + visible_items.insert(l - i, self._line_separator) + content_height = sum(item.rect.height for item in visible_items) + self._spacing * (len(visible_items)) if not self._pad_end: content_height -= self._spacing From 9297cd2f3e2b7586ebc56a463289735edcfde794 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 26 Sep 2025 21:08:39 -0700 Subject: [PATCH 051/910] raylib: use filter for allow throttle (#36209) * use time here * use epic filter * rm * intermediary * tune --- selfdrive/ui/onroad/alert_renderer.py | 4 ++-- selfdrive/ui/onroad/model_renderer.py | 26 ++++++++------------------ selfdrive/ui/ui_state.py | 2 ++ 3 files changed, 12 insertions(+), 20 deletions(-) diff --git a/selfdrive/ui/onroad/alert_renderer.py b/selfdrive/ui/onroad/alert_renderer.py index e8817f24b..c529694df 100644 --- a/selfdrive/ui/onroad/alert_renderer.py +++ b/selfdrive/ui/onroad/alert_renderer.py @@ -4,7 +4,7 @@ from dataclasses import dataclass from cereal import messaging, log from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.system.hardware import TICI -from openpilot.system.ui.lib.application import gui_app, FontWeight, DEFAULT_FPS +from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.label import gui_text_box @@ -76,7 +76,7 @@ class AlertRenderer(Widget): # Check if selfdriveState messages have stopped arriving if not sm.updated['selfdriveState']: recv_frame = sm.recv_frame['selfdriveState'] - time_since_onroad = (sm.frame - ui_state.started_frame) / DEFAULT_FPS + time_since_onroad = time.monotonic() - ui_state.started_time # 1. Never received selfdriveState since going onroad waiting_for_startup = recv_frame < ui_state.started_frame diff --git a/selfdrive/ui/onroad/model_renderer.py b/selfdrive/ui/onroad/model_renderer.py index 932773755..89bdb48ac 100644 --- a/selfdrive/ui/onroad/model_renderer.py +++ b/selfdrive/ui/onroad/model_renderer.py @@ -3,6 +3,7 @@ import numpy as np import pyray as rl from cereal import messaging, car from dataclasses import dataclass, field +from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.params import Params from openpilot.selfdrive.locationd.calibrationd import HEIGHT_INIT from openpilot.selfdrive.ui.ui_state import ui_state @@ -49,7 +50,7 @@ class ModelRenderer(Widget): super().__init__() self._longitudinal_control = False self._experimental_mode = False - self._blend_factor = 1.0 + self._blend_filter = FirstOrderFilter(1.0, 0.25, 1 / DEFAULT_FPS) self._prev_allow_throttle = True self._lane_line_probs = np.zeros(4, dtype=np.float32) self._road_edge_stds = np.zeros(2, dtype=np.float32) @@ -277,6 +278,9 @@ class ModelRenderer(Widget): if not self._path.projected_points.size: return + allow_throttle = sm['longitudinalPlan'].allowThrottle or not self._longitudinal_control + self._blend_filter.update(int(allow_throttle)) + if self._experimental_mode: # Draw with acceleration coloring if len(self._exp_gradient['colors']) > 1: @@ -284,23 +288,9 @@ class ModelRenderer(Widget): else: draw_polygon(self._rect, self._path.projected_points, rl.Color(255, 255, 255, 30)) else: - # Draw with throttle/no throttle gradient - allow_throttle = sm['longitudinalPlan'].allowThrottle or not self._longitudinal_control - - # Start transition if throttle state changes - if allow_throttle != self._prev_allow_throttle: - self._prev_allow_throttle = allow_throttle - self._blend_factor = max(1.0 - self._blend_factor, 0.0) - - # Update blend factor - if self._blend_factor < 1.0: - self._blend_factor = min(self._blend_factor + PATH_BLEND_INCREMENT, 1.0) - - begin_colors = NO_THROTTLE_COLORS if allow_throttle else THROTTLE_COLORS - end_colors = THROTTLE_COLORS if allow_throttle else NO_THROTTLE_COLORS - - # Blend colors based on transition - blended_colors = self._blend_colors(begin_colors, end_colors, self._blend_factor) + # Blend throttle/no throttle colors based on transition + blend_factor = round(self._blend_filter.x * 100) / 100 + blended_colors = self._blend_colors(NO_THROTTLE_COLORS, THROTTLE_COLORS, blend_factor) gradient = { 'start': (0.0, 1.0), # Bottom of path 'end': (0.0, 0.0), # Top of path diff --git a/selfdrive/ui/ui_state.py b/selfdrive/ui/ui_state.py index c4a2c0ca1..13532620c 100644 --- a/selfdrive/ui/ui_state.py +++ b/selfdrive/ui/ui_state.py @@ -59,6 +59,7 @@ class UIState: # UI Status tracking self.status: UIStatus = UIStatus.DISENGAGED self.started_frame: int = 0 + self.started_time: float = 0.0 self._engaged_prev: bool = False self._started_prev: bool = False @@ -131,6 +132,7 @@ class UIState: if self.started: self.status = UIStatus.DISENGAGED self.started_frame = self.sm.frame + self.started_time = time.monotonic() self._started_prev = self.started From 04365f12ffefc0978c83bb8d44917ebee08e601d Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 26 Sep 2025 21:10:09 -0700 Subject: [PATCH 052/910] raylib: remove unused globals --- selfdrive/ui/onroad/model_renderer.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/selfdrive/ui/onroad/model_renderer.py b/selfdrive/ui/onroad/model_renderer.py index 89bdb48ac..d84e16cb5 100644 --- a/selfdrive/ui/onroad/model_renderer.py +++ b/selfdrive/ui/onroad/model_renderer.py @@ -14,8 +14,6 @@ from openpilot.system.ui.widgets import Widget CLIP_MARGIN = 500 MIN_DRAW_DISTANCE = 10.0 MAX_DRAW_DISTANCE = 100.0 -PATH_COLOR_TRANSITION_DURATION = 0.5 # Seconds for color transition animation -PATH_BLEND_INCREMENT = 1.0 / (PATH_COLOR_TRANSITION_DURATION * DEFAULT_FPS) MAX_POINTS = 200 From 8de8c3eb00383cb5b8fa18c41ddc4899dccc1e61 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 26 Sep 2025 21:38:59 -0700 Subject: [PATCH 053/910] raylib: 20 FPS onroad (#36208) * 20 * dynamic fps * flip * init to be safe * fix possible fps weirdness * gate on change * not now * rev --- common/filter_simple.py | 11 ++++++++--- selfdrive/ui/layouts/main.py | 10 ++++++++++ selfdrive/ui/onroad/model_renderer.py | 5 +++-- selfdrive/ui/ui_state.py | 4 ++-- system/ui/lib/application.py | 17 ++++++++++++----- system/ui/widgets/network.py | 8 +++++--- 6 files changed, 40 insertions(+), 15 deletions(-) diff --git a/common/filter_simple.py b/common/filter_simple.py index 9ea6fe307..8a7105063 100644 --- a/common/filter_simple.py +++ b/common/filter_simple.py @@ -1,16 +1,21 @@ class FirstOrderFilter: def __init__(self, x0, rc, dt, initialized=True): self.x = x0 - self.dt = dt + self._dt = dt self.update_alpha(rc) self.initialized = initialized + def update_dt(self, dt): + self._dt = dt + self.update_alpha(self._rc) + def update_alpha(self, rc): - self.alpha = self.dt / (rc + self.dt) + self._rc = rc + self._alpha = self._dt / (self._rc + self._dt) def update(self, x): if self.initialized: - self.x = (1. - self.alpha) * self.x + self.alpha * x + self.x = (1. - self._alpha) * self.x + self._alpha * x else: self.initialized = True self.x = x diff --git a/selfdrive/ui/layouts/main.py b/selfdrive/ui/layouts/main.py index 777d2f4c3..0a8fadea9 100644 --- a/selfdrive/ui/layouts/main.py +++ b/selfdrive/ui/layouts/main.py @@ -1,6 +1,7 @@ import pyray as rl from enum import IntEnum import cereal.messaging as messaging +from openpilot.system.ui.lib.application import gui_app from openpilot.selfdrive.ui.layouts.sidebar import Sidebar, SIDEBAR_WIDTH from openpilot.selfdrive.ui.layouts.home import HomeLayout from openpilot.selfdrive.ui.layouts.settings.settings import SettingsLayout, PanelType @@ -9,6 +10,10 @@ from openpilot.selfdrive.ui.ui_state import device, ui_state from openpilot.system.ui.widgets import Widget +ONROAD_FPS = 20 +OFFROAD_FPS = 60 + + class MainState(IntEnum): HOME = 0 SETTINGS = 1 @@ -25,6 +30,8 @@ class MainLayout(Widget): self._current_mode = MainState.HOME self._prev_onroad = False + gui_app.set_target_fps(OFFROAD_FPS) + # Initialize layouts self._layouts = {MainState.HOME: HomeLayout(), MainState.SETTINGS: SettingsLayout(), MainState.ONROAD: AugmentedRoadView()} @@ -74,6 +81,9 @@ class MainLayout(Widget): self._current_mode = layout self._layouts[self._current_mode].show_event() + # No need to draw onroad faster than source (model at 20Hz) and prevents screen tearing + gui_app.set_target_fps(ONROAD_FPS if self._current_mode == MainState.ONROAD else OFFROAD_FPS) + def open_settings(self, panel_type: PanelType): self._layouts[MainState.SETTINGS].set_current_panel(panel_type) self._set_current_layout(MainState.SETTINGS) diff --git a/selfdrive/ui/onroad/model_renderer.py b/selfdrive/ui/onroad/model_renderer.py index d84e16cb5..4c036873d 100644 --- a/selfdrive/ui/onroad/model_renderer.py +++ b/selfdrive/ui/onroad/model_renderer.py @@ -7,7 +7,7 @@ from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.params import Params from openpilot.selfdrive.locationd.calibrationd import HEIGHT_INIT from openpilot.selfdrive.ui.ui_state import ui_state -from openpilot.system.ui.lib.application import DEFAULT_FPS +from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.lib.shader_polygon import draw_polygon from openpilot.system.ui.widgets import Widget @@ -48,7 +48,7 @@ class ModelRenderer(Widget): super().__init__() self._longitudinal_control = False self._experimental_mode = False - self._blend_filter = FirstOrderFilter(1.0, 0.25, 1 / DEFAULT_FPS) + self._blend_filter = FirstOrderFilter(1.0, 0.25, 1 / gui_app.target_fps) self._prev_allow_throttle = True self._lane_line_probs = np.zeros(4, dtype=np.float32) self._road_edge_stds = np.zeros(2, dtype=np.float32) @@ -277,6 +277,7 @@ class ModelRenderer(Widget): return allow_throttle = sm['longitudinalPlan'].allowThrottle or not self._longitudinal_control + self._blend_filter.update_dt(1 / gui_app.target_fps) self._blend_filter.update(int(allow_throttle)) if self._experimental_mode: diff --git a/selfdrive/ui/ui_state.py b/selfdrive/ui/ui_state.py index 13532620c..39a65a919 100644 --- a/selfdrive/ui/ui_state.py +++ b/selfdrive/ui/ui_state.py @@ -9,7 +9,6 @@ from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.params import Params, UnknownKeyName from openpilot.common.swaglog import cloudlog from openpilot.selfdrive.ui.lib.prime_state import PrimeState -from openpilot.system.ui.lib.application import DEFAULT_FPS from openpilot.system.hardware import HARDWARE from openpilot.system.ui.lib.application import gui_app @@ -153,7 +152,7 @@ class Device: self._offroad_brightness: int = BACKLIGHT_OFFROAD self._last_brightness: int = 0 - self._brightness_filter = FirstOrderFilter(BACKLIGHT_OFFROAD, 10.00, 1 / DEFAULT_FPS) + self._brightness_filter = FirstOrderFilter(BACKLIGHT_OFFROAD, 10.00, 1 / gui_app.target_fps) self._brightness_thread: threading.Thread | None = None def reset_interactive_timeout(self, timeout: int = -1) -> None: @@ -190,6 +189,7 @@ class Device: clipped_brightness = float(np.clip(100 * clipped_brightness, 10, 100)) + self._brightness_filter.update_dt(1 / gui_app.target_fps) brightness = round(self._brightness_filter.update(clipped_brightness)) if not self._awake: brightness = 0 diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 911891f10..481fd9804 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -14,7 +14,7 @@ from openpilot.common.swaglog import cloudlog from openpilot.system.hardware import HARDWARE, PC from openpilot.common.realtime import Ratekeeper -DEFAULT_FPS = int(os.getenv("FPS", "60")) +_DEFAULT_FPS = int(os.getenv("FPS", "60")) 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 @@ -130,7 +130,7 @@ class GuiApplication: 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._target_fps: int = _DEFAULT_FPS self._last_fps_log_time: float = time.monotonic() self._window_close_requested = False self._trace_log_callback = None @@ -145,7 +145,7 @@ class GuiApplication: def request_close(self): self._window_close_requested = True - def init_window(self, title: str, fps: int = DEFAULT_FPS): + def init_window(self, title: str, fps: int = _DEFAULT_FPS): atexit.register(self.close) # Automatically call close() on exit HARDWARE.set_display_power(True) @@ -164,15 +164,22 @@ class GuiApplication: 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_target_fps(fps) self._set_styles() self._load_fonts() if not PC: self._mouse.start() + @property + def target_fps(self): + return self._target_fps + + def set_target_fps(self, fps: int): + self._target_fps = fps + rl.set_target_fps(fps) + def set_modal_overlay(self, overlay, callback: Callable | None = None): self._modal_overlay = ModalOverlay(overlay=overlay, callback=callback) diff --git a/system/ui/widgets/network.py b/system/ui/widgets/network.py index 119901da4..986d7158d 100644 --- a/system/ui/widgets/network.py +++ b/system/ui/widgets/network.py @@ -5,7 +5,7 @@ from typing import cast import pyray as rl from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.params import Params -from openpilot.system.ui.lib.application import gui_app, DEFAULT_FPS +from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel from openpilot.system.ui.lib.wifi_manager import WifiManager, SecurityType, Network, MeteredType from openpilot.system.ui.widgets import Widget @@ -50,10 +50,12 @@ class NavButton(Widget): super().__init__() self.text = text self.set_rect(rl.Rectangle(0, 0, 400, 100)) - self._x_pos_filter = FirstOrderFilter(0.0, 0.05, 1 / DEFAULT_FPS, initialized=False) - self._y_pos_filter = FirstOrderFilter(0.0, 0.05, 1 / DEFAULT_FPS, initialized=False) + self._x_pos_filter = FirstOrderFilter(0.0, 0.05, 1 / gui_app.target_fps, initialized=False) + self._y_pos_filter = FirstOrderFilter(0.0, 0.05, 1 / gui_app.target_fps, initialized=False) def set_position(self, x: float, y: float) -> None: + self._x_pos_filter.update_dt(1 / gui_app.target_fps) + self._y_pos_filter.update_dt(1 / gui_app.target_fps) x = self._x_pos_filter.update(x) y = self._y_pos_filter.update(y) changed = (self._rect.x != x or self._rect.y != y) From 5cbfc7705bbd46562f4425cd06ed4d337f0d9d7f Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 26 Sep 2025 21:41:12 -0700 Subject: [PATCH 054/910] Revert "raylib: 20 FPS onroad (#36208)" This reverts commit 8de8c3eb00383cb5b8fa18c41ddc4899dccc1e61. --- common/filter_simple.py | 11 +++-------- selfdrive/ui/layouts/main.py | 10 ---------- selfdrive/ui/onroad/model_renderer.py | 5 ++--- selfdrive/ui/ui_state.py | 4 ++-- system/ui/lib/application.py | 17 +++++------------ system/ui/widgets/network.py | 8 +++----- 6 files changed, 15 insertions(+), 40 deletions(-) diff --git a/common/filter_simple.py b/common/filter_simple.py index 8a7105063..9ea6fe307 100644 --- a/common/filter_simple.py +++ b/common/filter_simple.py @@ -1,21 +1,16 @@ class FirstOrderFilter: def __init__(self, x0, rc, dt, initialized=True): self.x = x0 - self._dt = dt + self.dt = dt self.update_alpha(rc) self.initialized = initialized - def update_dt(self, dt): - self._dt = dt - self.update_alpha(self._rc) - def update_alpha(self, rc): - self._rc = rc - self._alpha = self._dt / (self._rc + self._dt) + self.alpha = self.dt / (rc + self.dt) def update(self, x): if self.initialized: - self.x = (1. - self._alpha) * self.x + self._alpha * x + self.x = (1. - self.alpha) * self.x + self.alpha * x else: self.initialized = True self.x = x diff --git a/selfdrive/ui/layouts/main.py b/selfdrive/ui/layouts/main.py index 0a8fadea9..777d2f4c3 100644 --- a/selfdrive/ui/layouts/main.py +++ b/selfdrive/ui/layouts/main.py @@ -1,7 +1,6 @@ import pyray as rl from enum import IntEnum import cereal.messaging as messaging -from openpilot.system.ui.lib.application import gui_app from openpilot.selfdrive.ui.layouts.sidebar import Sidebar, SIDEBAR_WIDTH from openpilot.selfdrive.ui.layouts.home import HomeLayout from openpilot.selfdrive.ui.layouts.settings.settings import SettingsLayout, PanelType @@ -10,10 +9,6 @@ from openpilot.selfdrive.ui.ui_state import device, ui_state from openpilot.system.ui.widgets import Widget -ONROAD_FPS = 20 -OFFROAD_FPS = 60 - - class MainState(IntEnum): HOME = 0 SETTINGS = 1 @@ -30,8 +25,6 @@ class MainLayout(Widget): self._current_mode = MainState.HOME self._prev_onroad = False - gui_app.set_target_fps(OFFROAD_FPS) - # Initialize layouts self._layouts = {MainState.HOME: HomeLayout(), MainState.SETTINGS: SettingsLayout(), MainState.ONROAD: AugmentedRoadView()} @@ -81,9 +74,6 @@ class MainLayout(Widget): self._current_mode = layout self._layouts[self._current_mode].show_event() - # No need to draw onroad faster than source (model at 20Hz) and prevents screen tearing - gui_app.set_target_fps(ONROAD_FPS if self._current_mode == MainState.ONROAD else OFFROAD_FPS) - def open_settings(self, panel_type: PanelType): self._layouts[MainState.SETTINGS].set_current_panel(panel_type) self._set_current_layout(MainState.SETTINGS) diff --git a/selfdrive/ui/onroad/model_renderer.py b/selfdrive/ui/onroad/model_renderer.py index 4c036873d..d84e16cb5 100644 --- a/selfdrive/ui/onroad/model_renderer.py +++ b/selfdrive/ui/onroad/model_renderer.py @@ -7,7 +7,7 @@ from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.params import Params from openpilot.selfdrive.locationd.calibrationd import HEIGHT_INIT from openpilot.selfdrive.ui.ui_state import ui_state -from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.lib.application import DEFAULT_FPS from openpilot.system.ui.lib.shader_polygon import draw_polygon from openpilot.system.ui.widgets import Widget @@ -48,7 +48,7 @@ class ModelRenderer(Widget): super().__init__() self._longitudinal_control = False self._experimental_mode = False - self._blend_filter = FirstOrderFilter(1.0, 0.25, 1 / gui_app.target_fps) + self._blend_filter = FirstOrderFilter(1.0, 0.25, 1 / DEFAULT_FPS) self._prev_allow_throttle = True self._lane_line_probs = np.zeros(4, dtype=np.float32) self._road_edge_stds = np.zeros(2, dtype=np.float32) @@ -277,7 +277,6 @@ class ModelRenderer(Widget): return allow_throttle = sm['longitudinalPlan'].allowThrottle or not self._longitudinal_control - self._blend_filter.update_dt(1 / gui_app.target_fps) self._blend_filter.update(int(allow_throttle)) if self._experimental_mode: diff --git a/selfdrive/ui/ui_state.py b/selfdrive/ui/ui_state.py index 39a65a919..13532620c 100644 --- a/selfdrive/ui/ui_state.py +++ b/selfdrive/ui/ui_state.py @@ -9,6 +9,7 @@ from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.params import Params, UnknownKeyName from openpilot.common.swaglog import cloudlog from openpilot.selfdrive.ui.lib.prime_state import PrimeState +from openpilot.system.ui.lib.application import DEFAULT_FPS from openpilot.system.hardware import HARDWARE from openpilot.system.ui.lib.application import gui_app @@ -152,7 +153,7 @@ class Device: self._offroad_brightness: int = BACKLIGHT_OFFROAD self._last_brightness: int = 0 - self._brightness_filter = FirstOrderFilter(BACKLIGHT_OFFROAD, 10.00, 1 / gui_app.target_fps) + self._brightness_filter = FirstOrderFilter(BACKLIGHT_OFFROAD, 10.00, 1 / DEFAULT_FPS) self._brightness_thread: threading.Thread | None = None def reset_interactive_timeout(self, timeout: int = -1) -> None: @@ -189,7 +190,6 @@ class Device: clipped_brightness = float(np.clip(100 * clipped_brightness, 10, 100)) - self._brightness_filter.update_dt(1 / gui_app.target_fps) brightness = round(self._brightness_filter.update(clipped_brightness)) if not self._awake: brightness = 0 diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 481fd9804..911891f10 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -14,7 +14,7 @@ from openpilot.common.swaglog import cloudlog from openpilot.system.hardware import HARDWARE, PC from openpilot.common.realtime import Ratekeeper -_DEFAULT_FPS = int(os.getenv("FPS", "60")) +DEFAULT_FPS = int(os.getenv("FPS", "60")) 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 @@ -130,7 +130,7 @@ class GuiApplication: 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._target_fps: int = DEFAULT_FPS self._last_fps_log_time: float = time.monotonic() self._window_close_requested = False self._trace_log_callback = None @@ -145,7 +145,7 @@ class GuiApplication: def request_close(self): self._window_close_requested = True - def init_window(self, title: str, fps: int = _DEFAULT_FPS): + def init_window(self, title: str, fps: int = DEFAULT_FPS): atexit.register(self.close) # Automatically call close() on exit HARDWARE.set_display_power(True) @@ -164,22 +164,15 @@ class GuiApplication: 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.set_target_fps(fps) + self._target_fps = fps self._set_styles() self._load_fonts() if not PC: self._mouse.start() - @property - def target_fps(self): - return self._target_fps - - def set_target_fps(self, fps: int): - self._target_fps = fps - rl.set_target_fps(fps) - def set_modal_overlay(self, overlay, callback: Callable | None = None): self._modal_overlay = ModalOverlay(overlay=overlay, callback=callback) diff --git a/system/ui/widgets/network.py b/system/ui/widgets/network.py index 986d7158d..119901da4 100644 --- a/system/ui/widgets/network.py +++ b/system/ui/widgets/network.py @@ -5,7 +5,7 @@ from typing import cast import pyray as rl from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.params import Params -from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.lib.application import gui_app, DEFAULT_FPS from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel from openpilot.system.ui.lib.wifi_manager import WifiManager, SecurityType, Network, MeteredType from openpilot.system.ui.widgets import Widget @@ -50,12 +50,10 @@ class NavButton(Widget): super().__init__() self.text = text self.set_rect(rl.Rectangle(0, 0, 400, 100)) - self._x_pos_filter = FirstOrderFilter(0.0, 0.05, 1 / gui_app.target_fps, initialized=False) - self._y_pos_filter = FirstOrderFilter(0.0, 0.05, 1 / gui_app.target_fps, initialized=False) + self._x_pos_filter = FirstOrderFilter(0.0, 0.05, 1 / DEFAULT_FPS, initialized=False) + self._y_pos_filter = FirstOrderFilter(0.0, 0.05, 1 / DEFAULT_FPS, initialized=False) def set_position(self, x: float, y: float) -> None: - self._x_pos_filter.update_dt(1 / gui_app.target_fps) - self._y_pos_filter.update_dt(1 / gui_app.target_fps) x = self._x_pos_filter.update(x) y = self._y_pos_filter.update(y) changed = (self._rect.x != x or self._rect.y != y) From 19fc66f88a690c8c78e4b79441bb9fc46fbec04e Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 26 Sep 2025 21:41:51 -0700 Subject: [PATCH 055/910] Fix tearing offroad --- system/ui/lib/application.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 911891f10..132d78e5a 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -11,10 +11,10 @@ from enum import StrEnum from typing import NamedTuple from importlib.resources import as_file, files from openpilot.common.swaglog import cloudlog -from openpilot.system.hardware import HARDWARE, PC +from openpilot.system.hardware import HARDWARE, PC, TICI from openpilot.common.realtime import Ratekeeper -DEFAULT_FPS = int(os.getenv("FPS", "60")) +DEFAULT_FPS = int(os.getenv("FPS", 20 if TICI else 60)) 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 From ed185e90f680adc127111bbec29579619b5d753c Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 26 Sep 2025 21:43:15 -0700 Subject: [PATCH 056/910] Reapply "raylib: 20 FPS onroad (#36208)" This reverts commit 5cbfc7705bbd46562f4425cd06ed4d337f0d9d7f. --- common/filter_simple.py | 11 ++++++++--- selfdrive/ui/layouts/main.py | 10 ++++++++++ selfdrive/ui/onroad/model_renderer.py | 5 +++-- selfdrive/ui/ui_state.py | 4 ++-- system/ui/lib/application.py | 19 +++++++++++++------ system/ui/widgets/network.py | 8 +++++--- 6 files changed, 41 insertions(+), 16 deletions(-) diff --git a/common/filter_simple.py b/common/filter_simple.py index 9ea6fe307..8a7105063 100644 --- a/common/filter_simple.py +++ b/common/filter_simple.py @@ -1,16 +1,21 @@ class FirstOrderFilter: def __init__(self, x0, rc, dt, initialized=True): self.x = x0 - self.dt = dt + self._dt = dt self.update_alpha(rc) self.initialized = initialized + def update_dt(self, dt): + self._dt = dt + self.update_alpha(self._rc) + def update_alpha(self, rc): - self.alpha = self.dt / (rc + self.dt) + self._rc = rc + self._alpha = self._dt / (self._rc + self._dt) def update(self, x): if self.initialized: - self.x = (1. - self.alpha) * self.x + self.alpha * x + self.x = (1. - self._alpha) * self.x + self._alpha * x else: self.initialized = True self.x = x diff --git a/selfdrive/ui/layouts/main.py b/selfdrive/ui/layouts/main.py index 777d2f4c3..0a8fadea9 100644 --- a/selfdrive/ui/layouts/main.py +++ b/selfdrive/ui/layouts/main.py @@ -1,6 +1,7 @@ import pyray as rl from enum import IntEnum import cereal.messaging as messaging +from openpilot.system.ui.lib.application import gui_app from openpilot.selfdrive.ui.layouts.sidebar import Sidebar, SIDEBAR_WIDTH from openpilot.selfdrive.ui.layouts.home import HomeLayout from openpilot.selfdrive.ui.layouts.settings.settings import SettingsLayout, PanelType @@ -9,6 +10,10 @@ from openpilot.selfdrive.ui.ui_state import device, ui_state from openpilot.system.ui.widgets import Widget +ONROAD_FPS = 20 +OFFROAD_FPS = 60 + + class MainState(IntEnum): HOME = 0 SETTINGS = 1 @@ -25,6 +30,8 @@ class MainLayout(Widget): self._current_mode = MainState.HOME self._prev_onroad = False + gui_app.set_target_fps(OFFROAD_FPS) + # Initialize layouts self._layouts = {MainState.HOME: HomeLayout(), MainState.SETTINGS: SettingsLayout(), MainState.ONROAD: AugmentedRoadView()} @@ -74,6 +81,9 @@ class MainLayout(Widget): self._current_mode = layout self._layouts[self._current_mode].show_event() + # No need to draw onroad faster than source (model at 20Hz) and prevents screen tearing + gui_app.set_target_fps(ONROAD_FPS if self._current_mode == MainState.ONROAD else OFFROAD_FPS) + def open_settings(self, panel_type: PanelType): self._layouts[MainState.SETTINGS].set_current_panel(panel_type) self._set_current_layout(MainState.SETTINGS) diff --git a/selfdrive/ui/onroad/model_renderer.py b/selfdrive/ui/onroad/model_renderer.py index d84e16cb5..4c036873d 100644 --- a/selfdrive/ui/onroad/model_renderer.py +++ b/selfdrive/ui/onroad/model_renderer.py @@ -7,7 +7,7 @@ from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.params import Params from openpilot.selfdrive.locationd.calibrationd import HEIGHT_INIT from openpilot.selfdrive.ui.ui_state import ui_state -from openpilot.system.ui.lib.application import DEFAULT_FPS +from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.lib.shader_polygon import draw_polygon from openpilot.system.ui.widgets import Widget @@ -48,7 +48,7 @@ class ModelRenderer(Widget): super().__init__() self._longitudinal_control = False self._experimental_mode = False - self._blend_filter = FirstOrderFilter(1.0, 0.25, 1 / DEFAULT_FPS) + self._blend_filter = FirstOrderFilter(1.0, 0.25, 1 / gui_app.target_fps) self._prev_allow_throttle = True self._lane_line_probs = np.zeros(4, dtype=np.float32) self._road_edge_stds = np.zeros(2, dtype=np.float32) @@ -277,6 +277,7 @@ class ModelRenderer(Widget): return allow_throttle = sm['longitudinalPlan'].allowThrottle or not self._longitudinal_control + self._blend_filter.update_dt(1 / gui_app.target_fps) self._blend_filter.update(int(allow_throttle)) if self._experimental_mode: diff --git a/selfdrive/ui/ui_state.py b/selfdrive/ui/ui_state.py index 13532620c..39a65a919 100644 --- a/selfdrive/ui/ui_state.py +++ b/selfdrive/ui/ui_state.py @@ -9,7 +9,6 @@ from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.params import Params, UnknownKeyName from openpilot.common.swaglog import cloudlog from openpilot.selfdrive.ui.lib.prime_state import PrimeState -from openpilot.system.ui.lib.application import DEFAULT_FPS from openpilot.system.hardware import HARDWARE from openpilot.system.ui.lib.application import gui_app @@ -153,7 +152,7 @@ class Device: self._offroad_brightness: int = BACKLIGHT_OFFROAD self._last_brightness: int = 0 - self._brightness_filter = FirstOrderFilter(BACKLIGHT_OFFROAD, 10.00, 1 / DEFAULT_FPS) + self._brightness_filter = FirstOrderFilter(BACKLIGHT_OFFROAD, 10.00, 1 / gui_app.target_fps) self._brightness_thread: threading.Thread | None = None def reset_interactive_timeout(self, timeout: int = -1) -> None: @@ -190,6 +189,7 @@ class Device: clipped_brightness = float(np.clip(100 * clipped_brightness, 10, 100)) + self._brightness_filter.update_dt(1 / gui_app.target_fps) brightness = round(self._brightness_filter.update(clipped_brightness)) if not self._awake: brightness = 0 diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 132d78e5a..481fd9804 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -11,10 +11,10 @@ from enum import StrEnum from typing import NamedTuple from importlib.resources import as_file, files from openpilot.common.swaglog import cloudlog -from openpilot.system.hardware import HARDWARE, PC, TICI +from openpilot.system.hardware import HARDWARE, PC from openpilot.common.realtime import Ratekeeper -DEFAULT_FPS = int(os.getenv("FPS", 20 if TICI else 60)) +_DEFAULT_FPS = int(os.getenv("FPS", "60")) 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 @@ -130,7 +130,7 @@ class GuiApplication: 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._target_fps: int = _DEFAULT_FPS self._last_fps_log_time: float = time.monotonic() self._window_close_requested = False self._trace_log_callback = None @@ -145,7 +145,7 @@ class GuiApplication: def request_close(self): self._window_close_requested = True - def init_window(self, title: str, fps: int = DEFAULT_FPS): + def init_window(self, title: str, fps: int = _DEFAULT_FPS): atexit.register(self.close) # Automatically call close() on exit HARDWARE.set_display_power(True) @@ -164,15 +164,22 @@ class GuiApplication: 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_target_fps(fps) self._set_styles() self._load_fonts() if not PC: self._mouse.start() + @property + def target_fps(self): + return self._target_fps + + def set_target_fps(self, fps: int): + self._target_fps = fps + rl.set_target_fps(fps) + def set_modal_overlay(self, overlay, callback: Callable | None = None): self._modal_overlay = ModalOverlay(overlay=overlay, callback=callback) diff --git a/system/ui/widgets/network.py b/system/ui/widgets/network.py index 119901da4..986d7158d 100644 --- a/system/ui/widgets/network.py +++ b/system/ui/widgets/network.py @@ -5,7 +5,7 @@ from typing import cast import pyray as rl from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.params import Params -from openpilot.system.ui.lib.application import gui_app, DEFAULT_FPS +from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel from openpilot.system.ui.lib.wifi_manager import WifiManager, SecurityType, Network, MeteredType from openpilot.system.ui.widgets import Widget @@ -50,10 +50,12 @@ class NavButton(Widget): super().__init__() self.text = text self.set_rect(rl.Rectangle(0, 0, 400, 100)) - self._x_pos_filter = FirstOrderFilter(0.0, 0.05, 1 / DEFAULT_FPS, initialized=False) - self._y_pos_filter = FirstOrderFilter(0.0, 0.05, 1 / DEFAULT_FPS, initialized=False) + self._x_pos_filter = FirstOrderFilter(0.0, 0.05, 1 / gui_app.target_fps, initialized=False) + self._y_pos_filter = FirstOrderFilter(0.0, 0.05, 1 / gui_app.target_fps, initialized=False) def set_position(self, x: float, y: float) -> None: + self._x_pos_filter.update_dt(1 / gui_app.target_fps) + self._y_pos_filter.update_dt(1 / gui_app.target_fps) x = self._x_pos_filter.update(x) y = self._y_pos_filter.update(y) changed = (self._rect.x != x or self._rect.y != y) From 2feddf32b2c5510168d8b453f49999e8615a17bc Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 26 Sep 2025 22:40:38 -0700 Subject: [PATCH 057/910] raylib: fix lost onroad tap events (#36211) * debug * see it's good to have abstraction * clean up * fine * wtf do you mean mypy? how can you not coerce this? --- selfdrive/ui/layouts/main.py | 2 +- selfdrive/ui/onroad/augmented_road_view.py | 16 ++++----------- selfdrive/ui/onroad/exp_button.py | 23 +++++++++------------- selfdrive/ui/onroad/hud_renderer.py | 6 +++--- system/ui/widgets/__init__.py | 5 +++++ 5 files changed, 22 insertions(+), 30 deletions(-) diff --git a/selfdrive/ui/layouts/main.py b/selfdrive/ui/layouts/main.py index 0a8fadea9..ffb45f821 100644 --- a/selfdrive/ui/layouts/main.py +++ b/selfdrive/ui/layouts/main.py @@ -50,7 +50,7 @@ class MainLayout(Widget): on_flag=self._on_bookmark_clicked) self._layouts[MainState.HOME]._setup_widget.set_open_settings_callback(lambda: self.open_settings(PanelType.FIREHOSE)) self._layouts[MainState.SETTINGS].set_callbacks(on_close=self._set_mode_for_state) - self._layouts[MainState.ONROAD].set_callbacks(on_click=self._on_onroad_clicked) + self._layouts[MainState.ONROAD].set_click_callback(self._on_onroad_clicked) device.add_interactive_timeout_callback(self._set_mode_for_state) def _update_layout_rects(self): diff --git a/selfdrive/ui/onroad/augmented_road_view.py b/selfdrive/ui/onroad/augmented_road_view.py index f012e8b06..0a4c45163 100644 --- a/selfdrive/ui/onroad/augmented_road_view.py +++ b/selfdrive/ui/onroad/augmented_road_view.py @@ -1,6 +1,5 @@ import numpy as np import pyray as rl -from collections.abc import Callable from cereal import log from msgq.visionipc import VisionStreamType from openpilot.selfdrive.ui.ui_state import ui_state, UIStatus, UI_BORDER_SIZE @@ -49,12 +48,6 @@ class AugmentedRoadView(CameraView): self.alert_renderer = AlertRenderer() self.driver_state_renderer = DriverStateRenderer() - # Callbacks - self._click_callback: Callable | None = None - - def set_callbacks(self, on_click: Callable | None = None): - self._click_callback = on_click - def _render(self, rect): # Only render when system is started to avoid invalid data access if not ui_state.started: @@ -100,13 +93,12 @@ class AugmentedRoadView(CameraView): # End clipping region rl.end_scissor_mode() - # Handle click events if no HUD interaction occurred - if not self._hud_renderer.handle_mouse_event(): - if self._click_callback is not None and rl.is_mouse_button_pressed(rl.MouseButton.MOUSE_BUTTON_LEFT): - if rl.check_collision_point_rec(rl.get_mouse_position(), self._content_rect): - self._click_callback() + def _handle_mouse_press(self, _): + if not self._hud_renderer.user_interacting() and self._click_callback is not None: + self._click_callback() def _handle_mouse_release(self, _): + # We only call click callback on press if not interacting with HUD pass def _draw_border(self, rect: rl.Rectangle): diff --git a/selfdrive/ui/onroad/exp_button.py b/selfdrive/ui/onroad/exp_button.py index 27f576307..175233c5b 100644 --- a/selfdrive/ui/onroad/exp_button.py +++ b/selfdrive/ui/onroad/exp_button.py @@ -32,26 +32,21 @@ class ExpButton(Widget): self._experimental_mode = selfdrive_state.experimentalMode self._engageable = selfdrive_state.engageable or selfdrive_state.enabled - def handle_mouse_event(self) -> bool: - if rl.check_collision_point_rec(rl.get_mouse_position(), self._rect): - if (rl.is_mouse_button_released(rl.MouseButton.MOUSE_BUTTON_LEFT) and - self._is_toggle_allowed()): - new_mode = not self._experimental_mode - self._params.put_bool("ExperimentalMode", new_mode) + def _handle_mouse_release(self, _): + super()._handle_mouse_release(_) + if self._is_toggle_allowed(): + new_mode = not self._experimental_mode + self._params.put_bool("ExperimentalMode", new_mode) - # Hold new state temporarily - self._held_mode = new_mode - self._hold_end_time = time.monotonic() + self._hold_duration - return True - return False + # Hold new state temporarily + self._held_mode = new_mode + self._hold_end_time = time.monotonic() + self._hold_duration def _render(self, rect: rl.Rectangle) -> None: center_x = int(self._rect.x + self._rect.width // 2) center_y = int(self._rect.y + self._rect.height // 2) - mouse_over = rl.check_collision_point_rec(rl.get_mouse_position(), self._rect) - mouse_down = rl.is_mouse_button_down(rl.MouseButton.MOUSE_BUTTON_LEFT) and self.is_pressed - self._white_color.a = 180 if (mouse_down and mouse_over) or not self._engageable else 255 + self._white_color.a = 180 if self.is_pressed or not self._engageable else 255 texture = self._txt_exp if self._held_or_actual_mode() else self._txt_wheel rl.draw_circle(center_x, center_y, self._rect.width / 2, self._black_bg) diff --git a/selfdrive/ui/onroad/hud_renderer.py b/selfdrive/ui/onroad/hud_renderer.py index 536d99338..c813d852b 100644 --- a/selfdrive/ui/onroad/hud_renderer.py +++ b/selfdrive/ui/onroad/hud_renderer.py @@ -69,7 +69,7 @@ class HudRenderer(Widget): self._font_bold: rl.Font = gui_app.font(FontWeight.BOLD) self._font_medium: rl.Font = gui_app.font(FontWeight.MEDIUM) - self._exp_button = ExpButton(UI_CONFIG.button_size, UI_CONFIG.wheel_icon_size) + self._exp_button: ExpButton = ExpButton(UI_CONFIG.button_size, UI_CONFIG.wheel_icon_size) def _update_state(self) -> None: """Update HUD state based on car state and controls state.""" @@ -120,8 +120,8 @@ class HudRenderer(Widget): button_y = rect.y + UI_CONFIG.border_size self._exp_button.render(rl.Rectangle(button_x, button_y, UI_CONFIG.button_size, UI_CONFIG.button_size)) - def handle_mouse_event(self) -> bool: - return bool(self._exp_button.handle_mouse_event()) + def user_interacting(self) -> bool: + return self._exp_button.is_pressed def _draw_set_speed(self, rect: rl.Rectangle) -> None: """Draw the MAX speed indicator box.""" diff --git a/system/ui/widgets/__init__.py b/system/ui/widgets/__init__.py index 6372b1813..0157dd3ef 100644 --- a/system/ui/widgets/__init__.py +++ b/system/ui/widgets/__init__.py @@ -96,6 +96,7 @@ class Widget(abc.ABC): # Allows touch to leave the rect and come back in focus if mouse did not release if mouse_event.left_pressed and self._touch_valid(): if rl.check_collision_point_rec(mouse_event.pos, self._rect): + self._handle_mouse_press(mouse_event.pos) self.__is_pressed[mouse_event.slot] = True self.__tracking_is_pressed[mouse_event.slot] = True @@ -131,6 +132,10 @@ class Widget(abc.ABC): def _update_layout_rects(self) -> None: """Optionally update any layout rects on Widget rect change.""" + def _handle_mouse_press(self, mouse_pos: MousePos) -> bool: + """Optionally handle mouse press events.""" + return False + def _handle_mouse_release(self, mouse_pos: MousePos) -> bool: """Optionally handle mouse release events.""" if self._click_callback: From 35e2fc7dd91d7db2328185f045810236b0e37b1d Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 26 Sep 2025 23:49:17 -0700 Subject: [PATCH 058/910] raylib: use touch thread in all places (#36212) * fix not opening alerts * whops * rm mouse pressed from offroad alerts * ah its a base class * one last place * fix * rm lines --- selfdrive/ui/layouts/home.py | 21 ++++------------- selfdrive/ui/onroad/driver_camera_dialog.py | 7 +++--- selfdrive/ui/widgets/offroad_alerts.py | 26 ++++++++++----------- 3 files changed, 21 insertions(+), 33 deletions(-) diff --git a/selfdrive/ui/layouts/home.py b/selfdrive/ui/layouts/home.py index f102481c3..9ee533d2a 100644 --- a/selfdrive/ui/layouts/home.py +++ b/selfdrive/ui/layouts/home.py @@ -8,7 +8,7 @@ from openpilot.selfdrive.ui.widgets.exp_mode_button import ExperimentalModeButto from openpilot.selfdrive.ui.widgets.prime import PrimeWidget from openpilot.selfdrive.ui.widgets.setup import SetupWidget from openpilot.system.ui.lib.text_measure import measure_text_cached -from openpilot.system.ui.lib.application import gui_app, FontWeight, DEFAULT_TEXT_COLOR +from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos, DEFAULT_TEXT_COLOR from openpilot.system.ui.widgets import Widget HEADER_HEIGHT = 80 @@ -72,7 +72,6 @@ class HomeLayout(Widget): self._refresh() self.last_refresh = current_time - self._handle_input() self._render_header() # Render content based on current state @@ -110,25 +109,13 @@ class HomeLayout(Widget): self.alert_notif_rect.x = notif_x self.alert_notif_rect.y = self.header_rect.y + (self.header_rect.height - 60) // 2 - def _handle_input(self): - if not rl.is_mouse_button_pressed(rl.MouseButton.MOUSE_BUTTON_LEFT): - return - - mouse_pos = rl.get_mouse_position() + def _handle_mouse_release(self, mouse_pos: MousePos): + super()._handle_mouse_release(mouse_pos) if self.update_available and rl.check_collision_point_rec(mouse_pos, self.update_notif_rect): self._set_state(HomeLayoutState.UPDATE) - return - - if self.alert_count > 0 and rl.check_collision_point_rec(mouse_pos, self.alert_notif_rect): + elif self.alert_count > 0 and rl.check_collision_point_rec(mouse_pos, self.alert_notif_rect): self._set_state(HomeLayoutState.ALERTS) - return - - # Content area input handling - if self.current_state == HomeLayoutState.UPDATE: - self.update_alert.handle_input(mouse_pos, True) - elif self.current_state == HomeLayoutState.ALERTS: - self.offroad_alert.handle_input(mouse_pos, True) def _render_header(self): font = gui_app.font(FontWeight.MEDIUM) diff --git a/selfdrive/ui/onroad/driver_camera_dialog.py b/selfdrive/ui/onroad/driver_camera_dialog.py index c8b4e6203..6c5508ce7 100644 --- a/selfdrive/ui/onroad/driver_camera_dialog.py +++ b/selfdrive/ui/onroad/driver_camera_dialog.py @@ -13,12 +13,13 @@ class DriverCameraDialog(CameraView): super().__init__("camerad", VisionStreamType.VISION_STREAM_DRIVER) self.driver_state_renderer = DriverStateRenderer() + def _handle_mouse_release(self, _): + super()._handle_mouse_release(_) + gui_app.set_modal_overlay(None) + def _render(self, rect): super()._render(rect) - if rl.is_mouse_button_pressed(rl.MouseButton.MOUSE_BUTTON_LEFT): - return 1 - if not self.frame: gui_label( rect, diff --git a/selfdrive/ui/widgets/offroad_alerts.py b/selfdrive/ui/widgets/offroad_alerts.py index f54bd9116..39fef3182 100644 --- a/selfdrive/ui/widgets/offroad_alerts.py +++ b/selfdrive/ui/widgets/offroad_alerts.py @@ -1,11 +1,14 @@ +import os import json import pyray as rl from abc import ABC, abstractmethod from collections.abc import Callable from dataclasses import dataclass +from openpilot.common.swaglog import cloudlog +from openpilot.common.basedir import BASEDIR from openpilot.common.params import Params from openpilot.system.hardware import HARDWARE -from openpilot.system.ui.lib.application import gui_app, FontWeight +from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.lib.wrap_text import wrap_text @@ -69,26 +72,23 @@ class AbstractAlert(Widget, ABC): def get_content_height(self) -> float: pass - def handle_input(self, mouse_pos: rl.Vector2, mouse_clicked: bool) -> bool: - if not mouse_clicked or not self.scroll_panel.is_touch_valid(): - return False + def _handle_mouse_release(self, mouse_pos: MousePos): + super()._handle_mouse_release(mouse_pos) + + if not self.scroll_panel.is_touch_valid(): + return if rl.check_collision_point_rec(mouse_pos, self.dismiss_btn_rect): if self.dismiss_callback: self.dismiss_callback() - return True - if self.snooze_visible and rl.check_collision_point_rec(mouse_pos, self.snooze_btn_rect): + elif self.snooze_visible and rl.check_collision_point_rec(mouse_pos, self.snooze_btn_rect): self.params.put_bool("SnoozeUpdate", True) if self.dismiss_callback: self.dismiss_callback() - return True - if self.has_reboot_btn and rl.check_collision_point_rec(mouse_pos, self.reboot_btn_rect): + elif self.has_reboot_btn and rl.check_collision_point_rec(mouse_pos, self.reboot_btn_rect): HARDWARE.reboot() - return True - - return False def _render(self, rect: rl.Rectangle): rl.draw_rectangle_rounded(rect, AlertConstants.BORDER_RADIUS / rect.width, 10, AlertColors.BACKGROUND) @@ -233,14 +233,14 @@ class OffroadAlert(AbstractAlert): def _build_alerts(self): self.sorted_alerts = [] try: - with open("../selfdrived/alerts_offroad.json", "rb") as f: + with open(os.path.join(BASEDIR, "selfdrive/selfdrived/alerts_offroad.json"), "rb") as f: alerts_config = json.load(f) for key, config in sorted(alerts_config.items(), key=lambda x: x[1].get("severity", 0), reverse=True): severity = config.get("severity", 0) alert_data = AlertData(key=key, text="", severity=severity) self.sorted_alerts.append(alert_data) except (FileNotFoundError, json.JSONDecodeError): - pass + cloudlog.exception("Failed to load offroad alerts") def _render_content(self, content_rect: rl.Rectangle): y_offset = 20 From ef93981bfa3116442729ffe1b98a7c6f817c68ad Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 27 Sep 2025 02:37:35 -0700 Subject: [PATCH 059/910] raylib: ui diff test (#36213) * add raylib ui test * match qt * exe * vibing is epic * this is epic * format * add more settings * fix to actually use raylib * add kb * global * pair * rm cmts * show event * this is so stupid clean up * clean up * rename dir * clean up * no more vibe * rm * ugh it's always slightly different for no reason * nvm region is actually broken * 1l --- .github/workflows/raylib_ui_preview.yaml | 174 ++++++++++++++++++ .github/workflows/selfdrive_tests.yaml | 26 +++ selfdrive/ui/layouts/home.py | 4 + .../ui/tests/test_ui/raylib_screenshots.py | 146 +++++++++++++++ 4 files changed, 350 insertions(+) create mode 100644 .github/workflows/raylib_ui_preview.yaml create mode 100755 selfdrive/ui/tests/test_ui/raylib_screenshots.py diff --git a/.github/workflows/raylib_ui_preview.yaml b/.github/workflows/raylib_ui_preview.yaml new file mode 100644 index 000000000..11e745a96 --- /dev/null +++ b/.github/workflows/raylib_ui_preview.yaml @@ -0,0 +1,174 @@ +name: "raylib ui preview" +on: + push: + branches: + - master + pull_request_target: + types: [assigned, opened, synchronize, reopened, edited] + branches: + - 'master' + paths: + - 'selfdrive/ui/**' + - 'system/ui/**' + workflow_dispatch: + +env: + UI_JOB_NAME: "Create raylib UI Report" + REPORT_NAME: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && 'master' || github.event.number }} + SHA: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && github.sha || github.event.pull_request.head.sha }} + BRANCH_NAME: "openpilot/pr-${{ github.event.number }}-raylib-ui" + +jobs: + preview: + if: github.repository == 'commaai/openpilot' + name: preview + runs-on: ubuntu-latest + timeout-minutes: 20 + permissions: + contents: read + pull-requests: write + actions: read + steps: + - name: Waiting for ui generation to start + run: sleep 30 + + - name: Waiting for ui generation to end + uses: lewagon/wait-on-check-action@v1.3.4 + with: + ref: ${{ env.SHA }} + check-name: ${{ env.UI_JOB_NAME }} + repo-token: ${{ secrets.GITHUB_TOKEN }} + allowed-conclusions: success + wait-interval: 20 + + - name: Getting workflow run ID + id: get_run_id + run: | + echo "run_id=$(curl https://api.github.com/repos/${{ github.repository }}/commits/${{ env.SHA }}/check-runs | jq -r '.check_runs[] | select(.name == "${{ env.UI_JOB_NAME }}") | .html_url | capture("(?[0-9]+)") | .number')" >> $GITHUB_OUTPUT + + - name: Getting proposed ui + id: download-artifact + uses: dawidd6/action-download-artifact@v6 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + run_id: ${{ steps.get_run_id.outputs.run_id }} + search_artifacts: true + name: raylib-report-${{ env.REPORT_NAME }} + path: ${{ github.workspace }}/pr_ui + + - name: Getting master ui + uses: actions/checkout@v4 + with: + repository: commaai/ci-artifacts + ssh-key: ${{ secrets.CI_ARTIFACTS_DEPLOY_KEY }} + path: ${{ github.workspace }}/master_ui_raylib + ref: openpilot_master_ui_raylib + + - name: Saving new master ui + if: github.ref == 'refs/heads/master' && github.event_name == 'push' + working-directory: ${{ github.workspace }}/master_ui_raylib + run: | + git checkout --orphan=new_master_ui_raylib + git rm -rf * + git branch -D openpilot_master_ui_raylib + git branch -m openpilot_master_ui_raylib + git config user.name "GitHub Actions Bot" + git config user.email "<>" + mv ${{ github.workspace }}/pr_ui/*.png . + git add . + git commit -m "raylib screenshots for commit ${{ env.SHA }}" + git push origin openpilot_master_ui_raylib --force + + - name: Finding diff + if: github.event_name == 'pull_request_target' + id: find_diff + run: >- + sudo apt-get update && sudo apt-get install -y imagemagick + + scenes=$(find ${{ github.workspace }}/pr_ui/*.png -type f -printf "%f\n" | cut -d '.' -f 1 | grep -v 'pair_device') + A=($scenes) + + DIFF="" + TABLE="
All Screenshots" + TABLE="${TABLE}" + + for ((i=0; i<${#A[*]}; i=i+1)); + do + # Check if the master file exists + if [ ! -f "${{ github.workspace }}/master_ui_raylib/${A[$i]}.png" ]; then + # This is a new file in PR UI that doesn't exist in master + DIFF="${DIFF}
" + DIFF="${DIFF}${A[$i]} : \$\${\\color{cyan}\\text{NEW}}\$\$" + DIFF="${DIFF}
" + + DIFF="${DIFF}" + DIFF="${DIFF} " + DIFF="${DIFF}" + + DIFF="${DIFF}
" + DIFF="${DIFF}
" + elif ! compare -fuzz 2% -highlight-color DeepSkyBlue1 -lowlight-color Black -compose Src ${{ github.workspace }}/master_ui_raylib/${A[$i]}.png ${{ github.workspace }}/pr_ui/${A[$i]}.png ${{ github.workspace }}/pr_ui/${A[$i]}_diff.png; then + convert ${{ github.workspace }}/pr_ui/${A[$i]}_diff.png -transparent black mask.png + composite mask.png ${{ github.workspace }}/master_ui_raylib/${A[$i]}.png composite_diff.png + convert -delay 100 ${{ github.workspace }}/master_ui_raylib/${A[$i]}.png composite_diff.png -loop 0 ${{ github.workspace }}/pr_ui/${A[$i]}_diff.gif + + mv ${{ github.workspace }}/master_ui_raylib/${A[$i]}.png ${{ github.workspace }}/pr_ui/${A[$i]}_master_ref.png + + DIFF="${DIFF}
" + DIFF="${DIFF}${A[$i]} : \$\${\\color{red}\\text{DIFFERENT}}\$\$" + DIFF="${DIFF}" + + DIFF="${DIFF}" + DIFF="${DIFF} " + DIFF="${DIFF} " + DIFF="${DIFF}" + + DIFF="${DIFF}" + DIFF="${DIFF} " + DIFF="${DIFF} " + DIFF="${DIFF}" + + DIFF="${DIFF}
master proposed
diff composite diff
" + DIFF="${DIFF}
" + else + rm -f ${{ github.workspace }}/pr_ui/${A[$i]}_diff.png + fi + + INDEX=$(($i % 2)) + if [[ $INDEX -eq 0 ]]; then + TABLE="${TABLE}" + fi + TABLE="${TABLE} " + if [[ $INDEX -eq 1 || $(($i + 1)) -eq ${#A[*]} ]]; then + TABLE="${TABLE}" + fi + done + + TABLE="${TABLE}" + + echo "DIFF=$DIFF$TABLE" >> "$GITHUB_OUTPUT" + + - name: Saving proposed ui + if: github.event_name == 'pull_request_target' + working-directory: ${{ github.workspace }}/master_ui_raylib + run: | + git config user.name "GitHub Actions Bot" + git config user.email "<>" + git checkout --orphan=${{ env.BRANCH_NAME }} + git rm -rf * + mv ${{ github.workspace }}/pr_ui/* . + git add . + git commit -m "raylib screenshots for PR #${{ github.event.number }}" + git push origin ${{ env.BRANCH_NAME }} --force + + - name: Comment Screenshots on PR + if: github.event_name == 'pull_request_target' + uses: thollander/actions-comment-pull-request@v2 + with: + message: | + + ## raylib UI Preview + ${{ steps.find_diff.outputs.DIFF }} + comment_tag: run_id_screenshots_raylib + pr_number: ${{ github.event.number }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml index beb426c66..57b1158be 100644 --- a/.github/workflows/selfdrive_tests.yaml +++ b/.github/workflows/selfdrive_tests.yaml @@ -277,3 +277,29 @@ jobs: with: name: report-${{ inputs.run_number || '1' }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && 'master' || github.event.number }} path: selfdrive/ui/tests/test_ui/report_1/screenshots + + create_raylib_ui_report: + name: Create raylib UI Report + runs-on: ${{ + (github.repository == 'commaai/openpilot') && + ((github.event_name != 'pull_request') || + (github.event.pull_request.head.repo.full_name == 'commaai/openpilot')) + && fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]') + || fromJSON('["ubuntu-24.04"]') }} + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - uses: ./.github/workflows/setup-with-retry + - name: Build openpilot + run: ${{ env.RUN }} "scons -j$(nproc)" + - name: Create raylib UI Report + run: > + ${{ env.RUN }} "PYTHONWARNINGS=ignore && + source selfdrive/test/setup_xvfb.sh && + python3 selfdrive/ui/tests/test_ui/raylib_screenshots.py" + - name: Upload Raylib UI Report + uses: actions/upload-artifact@v4 + with: + name: raylib-report-${{ inputs.run_number || '1' }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && 'master' || github.event.number }} + path: selfdrive/ui/tests/test_ui/raylib_report/screenshots diff --git a/selfdrive/ui/layouts/home.py b/selfdrive/ui/layouts/home.py index 9ee533d2a..320e7477f 100644 --- a/selfdrive/ui/layouts/home.py +++ b/selfdrive/ui/layouts/home.py @@ -56,6 +56,10 @@ class HomeLayout(Widget): self._exp_mode_button = ExperimentalModeButton() self._setup_callbacks() + def show_event(self): + self.last_refresh = time.monotonic() + self._refresh() + def _setup_callbacks(self): self.update_alert.set_dismiss_callback(lambda: self._set_state(HomeLayoutState.HOME)) self.offroad_alert.set_dismiss_callback(lambda: self._set_state(HomeLayoutState.HOME)) diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py new file mode 100755 index 000000000..7fb22e448 --- /dev/null +++ b/selfdrive/ui/tests/test_ui/raylib_screenshots.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python3 +import os +import sys +import shutil +import time +import pathlib +from collections import namedtuple + +import pyautogui +import pywinctl + +from cereal import log +from cereal import messaging +from cereal.messaging import PubMaster +from openpilot.common.params import Params +from openpilot.common.prefix import OpenpilotPrefix +from openpilot.selfdrive.test.helpers import with_processes +from openpilot.selfdrive.selfdrived.alertmanager import set_offroad_alert + +TEST_DIR = pathlib.Path(__file__).parent +TEST_OUTPUT_DIR = TEST_DIR / "raylib_report" +SCREENSHOTS_DIR = TEST_OUTPUT_DIR / "screenshots" +UI_DELAY = 0.1 + +# Offroad alerts to test +OFFROAD_ALERTS = ['Offroad_IsTakingSnapshot'] + + +def setup_homescreen(click, pm: PubMaster): + pass + + +def setup_settings_device(click, pm: PubMaster): + click(100, 100) + + +def setup_settings_network(click, pm: PubMaster): + setup_settings_device(click, pm) + click(278, 450) + + +def setup_settings_toggles(click, pm: PubMaster): + setup_settings_device(click, pm) + click(278, 600) + + +def setup_settings_software(click, pm: PubMaster): + setup_settings_device(click, pm) + click(278, 720) + + +def setup_settings_firehose(click, pm: PubMaster): + setup_settings_device(click, pm) + click(278, 845) + + +def setup_settings_developer(click, pm: PubMaster): + setup_settings_device(click, pm) + click(278, 950) + + +def setup_keyboard(click, pm: PubMaster): + setup_settings_developer(click, pm) + click(1930, 270) + + +def setup_pair_device(click, pm: PubMaster): + click(1950, 800) + + +def setup_offroad_alert(click, pm: PubMaster): + set_offroad_alert("Offroad_TemperatureTooHigh", True, extra_text='99') + for alert in OFFROAD_ALERTS: + set_offroad_alert(alert, True) + + setup_settings_device(click, pm) + click(240, 216) + + +CASES = { + "homescreen": setup_homescreen, + "settings_device": setup_settings_device, + "settings_network": setup_settings_network, + "settings_toggles": setup_settings_toggles, + "settings_software": setup_settings_software, + "settings_firehose": setup_settings_firehose, + "settings_developer": setup_settings_developer, + "keyboard": setup_keyboard, + "pair_device": setup_pair_device, + "offroad_alert": setup_offroad_alert, +} + + +class TestUI: + def __init__(self): + os.environ["SCALE"] = os.getenv("SCALE", "1") + sys.modules["mouseinfo"] = False + + def setup(self): + # Seed minimal offroad state + self.pm = PubMaster(["deviceState"]) + ds = messaging.new_message('deviceState') + ds.deviceState.networkType = log.DeviceState.NetworkType.wifi + for _ in range(5): + self.pm.send('deviceState', ds) + ds.clear_write_flag() + time.sleep(0.05) + time.sleep(0.5) + try: + self.ui = pywinctl.getWindowsWithTitle("UI")[0] + except Exception as e: + print(f"failed to find ui window, assuming that it's in the top left (for Xvfb) {e}") + self.ui = namedtuple("bb", ["left", "top", "width", "height"])(0, 0, 2160, 1080) + + def screenshot(self, name: str): + full_screenshot = pyautogui.screenshot() + cropped = full_screenshot.crop((self.ui.left, self.ui.top, self.ui.left + self.ui.width, self.ui.top + self.ui.height)) + cropped.save(SCREENSHOTS_DIR / f"{name}.png") + + def click(self, x: int, y: int, *args, **kwargs): + pyautogui.mouseDown(self.ui.left + x, self.ui.top + y, *args, **kwargs) + time.sleep(0.01) + pyautogui.mouseUp(self.ui.left + x, self.ui.top + y, *args, **kwargs) + + @with_processes(["raylib_ui"]) + def test_ui(self, name, setup_case): + self.setup() + setup_case(self.click, self.pm) + self.screenshot(name) + + +def create_screenshots(): + if TEST_OUTPUT_DIR.exists(): + shutil.rmtree(TEST_OUTPUT_DIR) + SCREENSHOTS_DIR.mkdir(parents=True) + + t = TestUI() + with OpenpilotPrefix(): + params = Params() + params.put("DongleId", "123456789012345") + for name, setup in CASES.items(): + t.test_ui(name, setup) + + +if __name__ == "__main__": + create_screenshots() From bc30b01eb7d0787f100a52313bcd59e3c3c4a855 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 27 Sep 2025 02:53:21 -0700 Subject: [PATCH 060/910] Fix raylib ui report (#36215) hmm --- .github/workflows/ui_preview.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ui_preview.yaml b/.github/workflows/ui_preview.yaml index 9ec7a5922..b5c3b21dc 100644 --- a/.github/workflows/ui_preview.yaml +++ b/.github/workflows/ui_preview.yaml @@ -53,7 +53,7 @@ jobs: github_token: ${{ secrets.GITHUB_TOKEN }} run_id: ${{ steps.get_run_id.outputs.run_id }} search_artifacts: true - name: report-1-${{ env.REPORT_NAME }} + name: raylib-report-1-${{ env.REPORT_NAME }} path: ${{ github.workspace }}/pr_ui - name: Getting master ui From e6bd88371e30257f359c2a6126f5d39e3bbf8ba1 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 27 Sep 2025 02:55:40 -0700 Subject: [PATCH 061/910] fix! --- .github/workflows/raylib_ui_preview.yaml | 2 +- .github/workflows/ui_preview.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/raylib_ui_preview.yaml b/.github/workflows/raylib_ui_preview.yaml index 11e745a96..ff9655d15 100644 --- a/.github/workflows/raylib_ui_preview.yaml +++ b/.github/workflows/raylib_ui_preview.yaml @@ -53,7 +53,7 @@ jobs: github_token: ${{ secrets.GITHUB_TOKEN }} run_id: ${{ steps.get_run_id.outputs.run_id }} search_artifacts: true - name: raylib-report-${{ env.REPORT_NAME }} + name: raylib-report-1-${{ env.REPORT_NAME }} path: ${{ github.workspace }}/pr_ui - name: Getting master ui diff --git a/.github/workflows/ui_preview.yaml b/.github/workflows/ui_preview.yaml index b5c3b21dc..9ec7a5922 100644 --- a/.github/workflows/ui_preview.yaml +++ b/.github/workflows/ui_preview.yaml @@ -53,7 +53,7 @@ jobs: github_token: ${{ secrets.GITHUB_TOKEN }} run_id: ${{ steps.get_run_id.outputs.run_id }} search_artifacts: true - name: raylib-report-1-${{ env.REPORT_NAME }} + name: report-1-${{ env.REPORT_NAME }} path: ${{ github.workspace }}/pr_ui - name: Getting master ui From 56c77fd5fac2c9705630bb23d820ac086d67e6ac Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 27 Sep 2025 03:32:19 -0700 Subject: [PATCH 062/910] re-run From e9434befaa9a671b13fa191b173509e101e9b0fd Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 27 Sep 2025 03:37:23 -0700 Subject: [PATCH 063/910] Refactor offroad alerts loading to use OFFROAD_ALERTS (#36214) * Refactor offroad alerts loading to use OFFROAD_ALERTS * clean up --- selfdrive/ui/widgets/offroad_alerts.py | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/selfdrive/ui/widgets/offroad_alerts.py b/selfdrive/ui/widgets/offroad_alerts.py index 39fef3182..da0ea287c 100644 --- a/selfdrive/ui/widgets/offroad_alerts.py +++ b/selfdrive/ui/widgets/offroad_alerts.py @@ -1,11 +1,7 @@ -import os -import json import pyray as rl from abc import ABC, abstractmethod from collections.abc import Callable from dataclasses import dataclass -from openpilot.common.swaglog import cloudlog -from openpilot.common.basedir import BASEDIR from openpilot.common.params import Params from openpilot.system.hardware import HARDWARE from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos @@ -13,6 +9,7 @@ from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.lib.wrap_text import wrap_text from openpilot.system.ui.widgets import Widget +from openpilot.selfdrive.selfdrived.alertmanager import OFFROAD_ALERTS class AlertColors: @@ -232,15 +229,10 @@ class OffroadAlert(AbstractAlert): def _build_alerts(self): self.sorted_alerts = [] - try: - with open(os.path.join(BASEDIR, "selfdrive/selfdrived/alerts_offroad.json"), "rb") as f: - alerts_config = json.load(f) - for key, config in sorted(alerts_config.items(), key=lambda x: x[1].get("severity", 0), reverse=True): - severity = config.get("severity", 0) - alert_data = AlertData(key=key, text="", severity=severity) - self.sorted_alerts.append(alert_data) - except (FileNotFoundError, json.JSONDecodeError): - cloudlog.exception("Failed to load offroad alerts") + for key, config in sorted(OFFROAD_ALERTS.items(), key=lambda x: x[1].get("severity", 0), reverse=True): + severity = config.get("severity", 0) + alert_data = AlertData(key=key, text="", severity=severity) + self.sorted_alerts.append(alert_data) def _render_content(self, content_rect: rl.Rectangle): y_offset = 20 From 7ccab2bdb96414e1332d5f0b2403366d18a14f44 Mon Sep 17 00:00:00 2001 From: commaci-public <60409688+commaci-public@users.noreply.github.com> Date: Sun, 28 Sep 2025 13:50:13 -0700 Subject: [PATCH 064/910] [bot] Update Python packages (#36220) * Update Python packages * revert tg, model diff looks a bit fishy --------- Co-authored-by: Vehicle Researcher Co-authored-by: Adeeb Shihadeh --- opendbc_repo | 2 +- panda | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/opendbc_repo b/opendbc_repo index 2eec1af10..aa3d32a63 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 2eec1af104972b7784644bf38c4c5afb52fc070a +Subproject commit aa3d32a63b7d05e69777fa90147192220abde8a3 diff --git a/panda b/panda index 1289337ce..48c776a3d 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 1289337ceb6205ad985a5469baa950b319329327 +Subproject commit 48c776a3df87f68da00e099510f2904c2217b303 From 070a13096b4d6134cc3f029cbdc08f08fb616417 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 29 Sep 2025 13:03:03 -0700 Subject: [PATCH 065/910] raylib: add todo for niceness (#36210) * not nice * hmm * debug * todo * revert * yep --- selfdrive/ui/ui.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/selfdrive/ui/ui.py b/selfdrive/ui/ui.py index 0230e16a4..5fb6fd573 100755 --- a/selfdrive/ui/ui.py +++ b/selfdrive/ui/ui.py @@ -7,6 +7,9 @@ from openpilot.selfdrive.ui.ui_state import ui_state def main(): + # TODO: https://github.com/commaai/agnos-builder/pull/490 + # os.nice(-20) + gui_app.init_window("UI") main_layout = MainLayout() main_layout.set_rect(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) From b5ec0e9744baaf5bae7989dd71b60006e49205ee Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 30 Sep 2025 02:39:19 -0700 Subject: [PATCH 066/910] raylib: fix regulatory --- system/ui/lib/application.py | 2 +- system/ui/widgets/html_render.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 481fd9804..ed2a20dd1 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -334,7 +334,7 @@ class GuiApplication: for layout in KEYBOARD_LAYOUTS.values(): all_chars.update(key for row in layout for key in row) all_chars = "".join(all_chars) - all_chars += "–✓×°" + all_chars += "–✓×°§" codepoint_count = rl.ffi.new("int *", 1) codepoints = rl.load_codepoints(all_chars, codepoint_count) diff --git a/system/ui/widgets/html_render.py b/system/ui/widgets/html_render.py index 247b7a549..d68ea5899 100644 --- a/system/ui/widgets/html_render.py +++ b/system/ui/widgets/html_render.py @@ -35,6 +35,7 @@ class HtmlElement: class HtmlRenderer(Widget): def __init__(self, file_path: str): + super().__init__() self.elements: list[HtmlElement] = [] self._normal_font = gui_app.font(FontWeight.NORMAL) self._bold_font = gui_app.font(FontWeight.BOLD) @@ -122,7 +123,7 @@ class HtmlRenderer(Widget): rl.end_scissor_mode() button_width = (rect.width - 3 * 50) // 3 - button_x = content_rect.x + (content_rect.width - button_width) / 2 + button_x = content_rect.x + content_rect.width - button_width button_y = content_rect.y + content_rect.height - button_height button_rect = rl.Rectangle(button_x, button_y, button_width, button_height) if gui_button(button_rect, "OK", button_style=ButtonStyle.PRIMARY) == 1: From aaf2aac050e1c0e7f33e018f415b07d9e6ce0c54 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 30 Sep 2025 03:11:42 -0700 Subject: [PATCH 067/910] raylib: training guide (#36224) * fix regulatory * debug slow loading * easy gather step coords * gotcha * and fix * dm option * fix final * fixes * progress bar! * "vibe coding is great" * wtf gpt5 * jfc * hand crafted >> vibe * it's slow so only load images if we're doing any kind of training * tf * format * clean up * clean up * no float * cmt * more clean up * clean up * eww * rm * no debug * match y * clean that up * here too * windows --- selfdrive/ui/layouts/main.py | 6 + selfdrive/ui/layouts/onboarding.py | 197 ++++++++++++++++++++++++ selfdrive/ui/layouts/settings/device.py | 14 +- 3 files changed, 212 insertions(+), 5 deletions(-) create mode 100644 selfdrive/ui/layouts/onboarding.py diff --git a/selfdrive/ui/layouts/main.py b/selfdrive/ui/layouts/main.py index ffb45f821..97f9d6b22 100644 --- a/selfdrive/ui/layouts/main.py +++ b/selfdrive/ui/layouts/main.py @@ -8,6 +8,7 @@ from openpilot.selfdrive.ui.layouts.settings.settings import SettingsLayout, Pan from openpilot.selfdrive.ui.onroad.augmented_road_view import AugmentedRoadView from openpilot.selfdrive.ui.ui_state import device, ui_state from openpilot.system.ui.widgets import Widget +from openpilot.selfdrive.ui.layouts.onboarding import OnboardingWindow ONROAD_FPS = 20 @@ -41,6 +42,11 @@ class MainLayout(Widget): # Set callbacks self._setup_callbacks() + # Start onboarding if terms or training not completed + self._onboarding_window = OnboardingWindow() + if not self._onboarding_window.completed: + gui_app.set_modal_overlay(self._onboarding_window) + def _render(self, _): self._handle_onroad_transition() self._render_main_content() diff --git a/selfdrive/ui/layouts/onboarding.py b/selfdrive/ui/layouts/onboarding.py new file mode 100644 index 000000000..ea6fedebf --- /dev/null +++ b/selfdrive/ui/layouts/onboarding.py @@ -0,0 +1,197 @@ +import os +import re +from enum import IntEnum + +import pyray as rl +from openpilot.common.basedir import BASEDIR +from openpilot.system.ui.lib.application import FontWeight, gui_app +from openpilot.system.ui.widgets import Widget +from openpilot.system.ui.widgets.button import Button, ButtonStyle +from openpilot.system.ui.widgets.label import Label, TextAlignment +from openpilot.selfdrive.ui.ui_state import ui_state + +DEBUG = False + +STEP_RECTS = [rl.Rectangle(104, 800, 633, 175), rl.Rectangle(1835, 0, 2159, 1080), rl.Rectangle(1835, 0, 2156, 1080), + rl.Rectangle(1526, 473, 427, 472), rl.Rectangle(1643, 441, 217, 223), rl.Rectangle(1835, 0, 2155, 1080), + rl.Rectangle(1786, 591, 267, 236), rl.Rectangle(1353, 0, 804, 1080), rl.Rectangle(1458, 485, 633, 211), + rl.Rectangle(95, 794, 1158, 187), rl.Rectangle(1560, 170, 392, 397), rl.Rectangle(1835, 0, 2159, 1080), + rl.Rectangle(1351, 0, 807, 1080), rl.Rectangle(1835, 0, 2158, 1080), rl.Rectangle(1531, 82, 441, 920), + rl.Rectangle(1336, 438, 490, 393), rl.Rectangle(1835, 0, 2159, 1080), rl.Rectangle(1835, 0, 2159, 1080), + rl.Rectangle(87, 795, 1187, 186)] + +DM_RECORD_STEP = 9 +DM_RECORD_YES_RECT = rl.Rectangle(695, 794, 558, 187) + +RESTART_TRAINING_RECT = rl.Rectangle(87, 795, 472, 186) + + +class OnboardingState(IntEnum): + TERMS = 0 + ONBOARDING = 1 + DECLINE = 2 + + +class TrainingGuide(Widget): + def __init__(self, completed_callback=None): + super().__init__() + self._completed_callback = completed_callback + + self._step = 0 + self._load_images() + + def _load_images(self): + self._images = [] + paths = [fn for fn in os.listdir(os.path.join(BASEDIR, "selfdrive/assets/training")) if re.match(r'^step\d*\.png$', fn)] + paths = sorted(paths, key=lambda x: int(re.search(r'\d+', x).group())) + for fn in paths: + path = os.path.join(BASEDIR, "selfdrive/assets/training", fn) + self._images.append(gui_app.texture(path, gui_app.width, gui_app.height)) + + def _handle_mouse_release(self, mouse_pos): + if rl.check_collision_point_rec(mouse_pos, STEP_RECTS[self._step]): + # Record DM camera? + if self._step == DM_RECORD_STEP: + yes = rl.check_collision_point_rec(mouse_pos, DM_RECORD_YES_RECT) + print(f"putting RecordFront to {yes}") + ui_state.params.put_bool("RecordFront", yes) + + # Restart training? + elif self._step == len(self._images) - 1: + if rl.check_collision_point_rec(mouse_pos, RESTART_TRAINING_RECT): + self._step = -1 + + self._step += 1 + + # Finished? + if self._step >= len(self._images): + self._step = 0 + if self._completed_callback: + self._completed_callback() + + def _render(self, _): + rl.draw_texture(self._images[self._step], 0, 0, rl.WHITE) + + # progress bar + if 0 < self._step < len(STEP_RECTS) - 1: + h = 20 + w = int((self._step / (len(STEP_RECTS) - 1)) * self._rect.width) + rl.draw_rectangle(int(self._rect.x), int(self._rect.y + self._rect.height - h), + w, h, rl.Color(70, 91, 234, 255)) + + if DEBUG: + rl.draw_rectangle_lines_ex(STEP_RECTS[self._step], 3, rl.RED) + + return -1 + + +class TermsPage(Widget): + def __init__(self, on_accept=None, on_decline=None): + super().__init__() + self._on_accept = on_accept + self._on_decline = on_decline + + self._title = Label("Welcome to openpilot", font_size=90, font_weight=FontWeight.BOLD, text_alignment=TextAlignment.LEFT) + self._desc = Label("You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing.", + font_size=90, font_weight=FontWeight.MEDIUM, text_alignment=TextAlignment.LEFT) + + self._decline_btn = Button("Decline", click_callback=on_decline) + self._accept_btn = Button("Agree", button_style=ButtonStyle.PRIMARY, click_callback=on_accept) + + def _render(self, _): + welcome_x = self._rect.x + 165 + welcome_y = self._rect.y + 165 + welcome_rect = rl.Rectangle(welcome_x, welcome_y, self._rect.width - welcome_x, 90) + self._title.render(welcome_rect) + + desc_x = welcome_x + # TODO: Label doesn't top align when wrapping + desc_y = welcome_y - 100 + desc_rect = rl.Rectangle(desc_x, desc_y, self._rect.width - desc_x, self._rect.height - desc_y - 250) + self._desc.render(desc_rect) + + btn_y = self._rect.y + self._rect.height - 160 - 45 + btn_width = (self._rect.width - 45 * 3) / 2 + self._decline_btn.render(rl.Rectangle(self._rect.x + 45, btn_y, btn_width, 160)) + self._accept_btn.render(rl.Rectangle(self._rect.x + 45 * 2 + btn_width, btn_y, btn_width, 160)) + + if DEBUG: + rl.draw_rectangle_lines_ex(welcome_rect, 3, rl.RED) + rl.draw_rectangle_lines_ex(desc_rect, 3, rl.RED) + + return -1 + + +class DeclinePage(Widget): + def __init__(self, back_callback=None): + super().__init__() + self._text = Label("You must accept the Terms and Conditions in order to use openpilot.", + font_size=90, font_weight=FontWeight.MEDIUM, text_alignment=TextAlignment.LEFT) + self._back_btn = Button("Back", click_callback=back_callback) + self._uninstall_btn = Button("Decline, uninstall openpilot", button_style=ButtonStyle.DANGER, + click_callback=self._on_uninstall_clicked) + + def _on_uninstall_clicked(self): + ui_state.params.put_bool("DoUninstall", True) + gui_app.request_close() + + def _render(self, _): + btn_y = self._rect.y + self._rect.height - 160 - 45 + btn_width = (self._rect.width - 45 * 3) / 2 + self._back_btn.render(rl.Rectangle(self._rect.x + 45, btn_y, btn_width, 160)) + self._uninstall_btn.render(rl.Rectangle(self._rect.x + 45 * 2 + btn_width, btn_y, btn_width, 160)) + + # text rect in middle of top and button + text_height = btn_y - (200 + 45) + text_rect = rl.Rectangle(self._rect.x + 165, self._rect.y + (btn_y - text_height) / 2 + 10, self._rect.width - (165 * 2), text_height) + if DEBUG: + rl.draw_rectangle_lines_ex(text_rect, 3, rl.RED) + self._text.render(text_rect) + + +class OnboardingWindow(Widget): + def __init__(self): + super().__init__() + self._current_terms_version = ui_state.params.get("TermsVersion") + self._current_training_version = ui_state.params.get("TrainingVersion") + self._accepted_terms: bool = ui_state.params.get("HasAcceptedTerms") == self._current_terms_version + self._training_done: bool = ui_state.params.get("CompletedTrainingVersion") == self._current_training_version + + self._state = OnboardingState.TERMS if not self._accepted_terms else OnboardingState.ONBOARDING + + # Windows + self._terms = TermsPage(on_accept=self._on_terms_accepted, on_decline=self._on_terms_declined) + self._training_guide: TrainingGuide | None = None + self._decline_page = DeclinePage(back_callback=self._on_decline_back) + + @property + def completed(self) -> bool: + return self._accepted_terms and self._training_done + + def _on_terms_declined(self): + self._state = OnboardingState.DECLINE + + def _on_decline_back(self): + self._state = OnboardingState.TERMS + + def _on_terms_accepted(self): + ui_state.params.put("HasAcceptedTerms", self._current_terms_version) + self._state = OnboardingState.ONBOARDING + if self._training_done: + gui_app.set_modal_overlay(None) + + def _on_completed_training(self): + ui_state.params.put("CompletedTrainingVersion", self._current_training_version) + gui_app.set_modal_overlay(None) + + def _render(self, _): + if self._training_guide is None: + self._training_guide = TrainingGuide(completed_callback=self._on_completed_training) + + if self._state == OnboardingState.TERMS: + self._terms.render(self._rect) + if self._state == OnboardingState.ONBOARDING: + self._training_guide.render(self._rect) + elif self._state == OnboardingState.DECLINE: + self._decline_page.render(self._rect) + return -1 diff --git a/selfdrive/ui/layouts/settings/device.py b/selfdrive/ui/layouts/settings/device.py index 14847df10..b34042d43 100644 --- a/selfdrive/ui/layouts/settings/device.py +++ b/selfdrive/ui/layouts/settings/device.py @@ -5,6 +5,7 @@ from openpilot.common.basedir import BASEDIR from openpilot.common.params import Params from openpilot.selfdrive.ui.onroad.driver_camera_dialog import DriverCameraDialog from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.selfdrive.ui.layouts.onboarding import TrainingGuide from openpilot.selfdrive.ui.widgets.pairing_dialog import PairingDialog from openpilot.system.hardware import TICI from openpilot.system.ui.lib.application import gui_app @@ -36,6 +37,7 @@ class DeviceLayout(Widget): self._driver_camera: DriverCameraDialog | None = None self._pair_device_dialog: PairingDialog | None = None self._fcc_dialog: HtmlRenderer | None = None + self._training_guide: TrainingGuide | None = None items = self._initialize_items() self._scroller = Scroller(items, line_separator=True, spacing=0) @@ -145,9 +147,11 @@ class DeviceLayout(Widget): def _on_regulatory(self): if not self._fcc_dialog: self._fcc_dialog = HtmlRenderer(os.path.join(BASEDIR, "selfdrive/assets/offroad/fcc.html")) + gui_app.set_modal_overlay(self._fcc_dialog, callback=lambda result: setattr(self, '_fcc_dialog', None)) - gui_app.set_modal_overlay(self._fcc_dialog, - callback=lambda result: setattr(self, '_fcc_dialog', None), - ) - - def _on_review_training_guide(self): pass + def _on_review_training_guide(self): + if not self._training_guide: + def completed_callback(): + gui_app.set_modal_overlay(None) + self._training_guide = TrainingGuide(completed_callback=completed_callback) + gui_app.set_modal_overlay(self._training_guide) From e4784d44f6d56efc6ba2663cceddb4aee5796297 Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Tue, 30 Sep 2025 14:33:10 -0700 Subject: [PATCH 068/910] bump panda (#36226) bump --- panda | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panda b/panda index 48c776a3d..615009cf0 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 48c776a3df87f68da00e099510f2904c2217b303 +Subproject commit 615009cf0f8fb8f3feadac160fbb0a07e4de171b From 16a42067208006eb39f190bb23de502eb0adfd74 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 30 Sep 2025 16:34:45 -0700 Subject: [PATCH 069/910] Revert "Reapply "raylib: 20 FPS onroad (#36208)"" This reverts commit ed185e90f680adc127111bbec29579619b5d753c. --- common/filter_simple.py | 11 +++-------- selfdrive/ui/layouts/main.py | 10 ---------- selfdrive/ui/onroad/model_renderer.py | 5 ++--- selfdrive/ui/ui_state.py | 4 ++-- system/ui/lib/application.py | 19 ++++++------------- system/ui/widgets/network.py | 8 +++----- 6 files changed, 16 insertions(+), 41 deletions(-) diff --git a/common/filter_simple.py b/common/filter_simple.py index 8a7105063..9ea6fe307 100644 --- a/common/filter_simple.py +++ b/common/filter_simple.py @@ -1,21 +1,16 @@ class FirstOrderFilter: def __init__(self, x0, rc, dt, initialized=True): self.x = x0 - self._dt = dt + self.dt = dt self.update_alpha(rc) self.initialized = initialized - def update_dt(self, dt): - self._dt = dt - self.update_alpha(self._rc) - def update_alpha(self, rc): - self._rc = rc - self._alpha = self._dt / (self._rc + self._dt) + self.alpha = self.dt / (rc + self.dt) def update(self, x): if self.initialized: - self.x = (1. - self._alpha) * self.x + self._alpha * x + self.x = (1. - self.alpha) * self.x + self.alpha * x else: self.initialized = True self.x = x diff --git a/selfdrive/ui/layouts/main.py b/selfdrive/ui/layouts/main.py index 97f9d6b22..6628ab4d6 100644 --- a/selfdrive/ui/layouts/main.py +++ b/selfdrive/ui/layouts/main.py @@ -1,7 +1,6 @@ import pyray as rl from enum import IntEnum import cereal.messaging as messaging -from openpilot.system.ui.lib.application import gui_app from openpilot.selfdrive.ui.layouts.sidebar import Sidebar, SIDEBAR_WIDTH from openpilot.selfdrive.ui.layouts.home import HomeLayout from openpilot.selfdrive.ui.layouts.settings.settings import SettingsLayout, PanelType @@ -11,10 +10,6 @@ from openpilot.system.ui.widgets import Widget from openpilot.selfdrive.ui.layouts.onboarding import OnboardingWindow -ONROAD_FPS = 20 -OFFROAD_FPS = 60 - - class MainState(IntEnum): HOME = 0 SETTINGS = 1 @@ -31,8 +26,6 @@ class MainLayout(Widget): self._current_mode = MainState.HOME self._prev_onroad = False - gui_app.set_target_fps(OFFROAD_FPS) - # Initialize layouts self._layouts = {MainState.HOME: HomeLayout(), MainState.SETTINGS: SettingsLayout(), MainState.ONROAD: AugmentedRoadView()} @@ -87,9 +80,6 @@ class MainLayout(Widget): self._current_mode = layout self._layouts[self._current_mode].show_event() - # No need to draw onroad faster than source (model at 20Hz) and prevents screen tearing - gui_app.set_target_fps(ONROAD_FPS if self._current_mode == MainState.ONROAD else OFFROAD_FPS) - def open_settings(self, panel_type: PanelType): self._layouts[MainState.SETTINGS].set_current_panel(panel_type) self._set_current_layout(MainState.SETTINGS) diff --git a/selfdrive/ui/onroad/model_renderer.py b/selfdrive/ui/onroad/model_renderer.py index 4c036873d..d84e16cb5 100644 --- a/selfdrive/ui/onroad/model_renderer.py +++ b/selfdrive/ui/onroad/model_renderer.py @@ -7,7 +7,7 @@ from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.params import Params from openpilot.selfdrive.locationd.calibrationd import HEIGHT_INIT from openpilot.selfdrive.ui.ui_state import ui_state -from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.lib.application import DEFAULT_FPS from openpilot.system.ui.lib.shader_polygon import draw_polygon from openpilot.system.ui.widgets import Widget @@ -48,7 +48,7 @@ class ModelRenderer(Widget): super().__init__() self._longitudinal_control = False self._experimental_mode = False - self._blend_filter = FirstOrderFilter(1.0, 0.25, 1 / gui_app.target_fps) + self._blend_filter = FirstOrderFilter(1.0, 0.25, 1 / DEFAULT_FPS) self._prev_allow_throttle = True self._lane_line_probs = np.zeros(4, dtype=np.float32) self._road_edge_stds = np.zeros(2, dtype=np.float32) @@ -277,7 +277,6 @@ class ModelRenderer(Widget): return allow_throttle = sm['longitudinalPlan'].allowThrottle or not self._longitudinal_control - self._blend_filter.update_dt(1 / gui_app.target_fps) self._blend_filter.update(int(allow_throttle)) if self._experimental_mode: diff --git a/selfdrive/ui/ui_state.py b/selfdrive/ui/ui_state.py index 39a65a919..13532620c 100644 --- a/selfdrive/ui/ui_state.py +++ b/selfdrive/ui/ui_state.py @@ -9,6 +9,7 @@ from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.params import Params, UnknownKeyName from openpilot.common.swaglog import cloudlog from openpilot.selfdrive.ui.lib.prime_state import PrimeState +from openpilot.system.ui.lib.application import DEFAULT_FPS from openpilot.system.hardware import HARDWARE from openpilot.system.ui.lib.application import gui_app @@ -152,7 +153,7 @@ class Device: self._offroad_brightness: int = BACKLIGHT_OFFROAD self._last_brightness: int = 0 - self._brightness_filter = FirstOrderFilter(BACKLIGHT_OFFROAD, 10.00, 1 / gui_app.target_fps) + self._brightness_filter = FirstOrderFilter(BACKLIGHT_OFFROAD, 10.00, 1 / DEFAULT_FPS) self._brightness_thread: threading.Thread | None = None def reset_interactive_timeout(self, timeout: int = -1) -> None: @@ -189,7 +190,6 @@ class Device: clipped_brightness = float(np.clip(100 * clipped_brightness, 10, 100)) - self._brightness_filter.update_dt(1 / gui_app.target_fps) brightness = round(self._brightness_filter.update(clipped_brightness)) if not self._awake: brightness = 0 diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index ed2a20dd1..4973efa06 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -11,10 +11,10 @@ from enum import StrEnum from typing import NamedTuple from importlib.resources import as_file, files from openpilot.common.swaglog import cloudlog -from openpilot.system.hardware import HARDWARE, PC +from openpilot.system.hardware import HARDWARE, PC, TICI from openpilot.common.realtime import Ratekeeper -_DEFAULT_FPS = int(os.getenv("FPS", "60")) +DEFAULT_FPS = int(os.getenv("FPS", 20 if TICI else 60)) 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 @@ -130,7 +130,7 @@ class GuiApplication: 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._target_fps: int = DEFAULT_FPS self._last_fps_log_time: float = time.monotonic() self._window_close_requested = False self._trace_log_callback = None @@ -145,7 +145,7 @@ class GuiApplication: def request_close(self): self._window_close_requested = True - def init_window(self, title: str, fps: int = _DEFAULT_FPS): + def init_window(self, title: str, fps: int = DEFAULT_FPS): atexit.register(self.close) # Automatically call close() on exit HARDWARE.set_display_power(True) @@ -164,22 +164,15 @@ class GuiApplication: 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.set_target_fps(fps) + self._target_fps = fps self._set_styles() self._load_fonts() if not PC: self._mouse.start() - @property - def target_fps(self): - return self._target_fps - - def set_target_fps(self, fps: int): - self._target_fps = fps - rl.set_target_fps(fps) - def set_modal_overlay(self, overlay, callback: Callable | None = None): self._modal_overlay = ModalOverlay(overlay=overlay, callback=callback) diff --git a/system/ui/widgets/network.py b/system/ui/widgets/network.py index 986d7158d..119901da4 100644 --- a/system/ui/widgets/network.py +++ b/system/ui/widgets/network.py @@ -5,7 +5,7 @@ from typing import cast import pyray as rl from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.params import Params -from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.lib.application import gui_app, DEFAULT_FPS from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel from openpilot.system.ui.lib.wifi_manager import WifiManager, SecurityType, Network, MeteredType from openpilot.system.ui.widgets import Widget @@ -50,12 +50,10 @@ class NavButton(Widget): super().__init__() self.text = text self.set_rect(rl.Rectangle(0, 0, 400, 100)) - self._x_pos_filter = FirstOrderFilter(0.0, 0.05, 1 / gui_app.target_fps, initialized=False) - self._y_pos_filter = FirstOrderFilter(0.0, 0.05, 1 / gui_app.target_fps, initialized=False) + self._x_pos_filter = FirstOrderFilter(0.0, 0.05, 1 / DEFAULT_FPS, initialized=False) + self._y_pos_filter = FirstOrderFilter(0.0, 0.05, 1 / DEFAULT_FPS, initialized=False) def set_position(self, x: float, y: float) -> None: - self._x_pos_filter.update_dt(1 / gui_app.target_fps) - self._y_pos_filter.update_dt(1 / gui_app.target_fps) x = self._x_pos_filter.update(x) y = self._y_pos_filter.update(y) changed = (self._rect.x != x or self._rect.y != y) From 3efa52f53ba03368250044307bed19462655f603 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 30 Sep 2025 20:05:40 -0700 Subject: [PATCH 070/910] fix missing import --- selfdrive/ui/layouts/main.py | 1 + 1 file changed, 1 insertion(+) diff --git a/selfdrive/ui/layouts/main.py b/selfdrive/ui/layouts/main.py index 6628ab4d6..1f5612224 100644 --- a/selfdrive/ui/layouts/main.py +++ b/selfdrive/ui/layouts/main.py @@ -1,6 +1,7 @@ import pyray as rl from enum import IntEnum import cereal.messaging as messaging +from openpilot.system.ui.lib.application import gui_app from openpilot.selfdrive.ui.layouts.sidebar import Sidebar, SIDEBAR_WIDTH from openpilot.selfdrive.ui.layouts.home import HomeLayout from openpilot.selfdrive.ui.layouts.settings.settings import SettingsLayout, PanelType From d24a14cb39d0d8b443fcf9f10c51e1a22e660af0 Mon Sep 17 00:00:00 2001 From: ZwX1616 Date: Tue, 30 Sep 2025 20:32:19 -0700 Subject: [PATCH 071/910] =?UTF-8?q?DM:=20Large=20Donut=20model=20?= =?UTF-8?q?=F0=9F=8D=A9=20(#36198)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 59cfd731-6f80-4857-9271-10d952165079/225 * deprecate at the end --- cereal/log.capnp | 10 +++++----- selfdrive/modeld/dmonitoringmodeld.py | 12 ++++-------- selfdrive/modeld/models/README.md | 3 +-- selfdrive/modeld/models/dmonitoring_model.current | 2 -- selfdrive/modeld/models/dmonitoring_model.onnx | 4 ++-- selfdrive/monitoring/helpers.py | 10 +--------- selfdrive/monitoring/test_monitoring.py | 1 - 7 files changed, 13 insertions(+), 29 deletions(-) delete mode 100644 selfdrive/modeld/models/dmonitoring_model.current diff --git a/cereal/log.capnp b/cereal/log.capnp index b98c1f524..019fbbe10 100644 --- a/cereal/log.capnp +++ b/cereal/log.capnp @@ -2146,13 +2146,10 @@ struct Joystick { struct DriverStateV2 { frameId @0 :UInt32; modelExecutionTime @1 :Float32; - dspExecutionTimeDEPRECATED @2 :Float32; gpuExecutionTime @8 :Float32; rawPredictions @3 :Data; - poorVisionProb @4 :Float32; wheelOnRightProb @5 :Float32; - leftDriverData @6 :DriverData; rightDriverData @7 :DriverData; @@ -2167,10 +2164,13 @@ struct DriverStateV2 { leftBlinkProb @7 :Float32; rightBlinkProb @8 :Float32; sunglassesProb @9 :Float32; - occludedProb @10 :Float32; - readyProb @11 :List(Float32); notReadyProb @12 :List(Float32); + occludedProbDEPRECATED @10 :Float32; + readyProbDEPRECATED @11 :List(Float32); } + + dspExecutionTimeDEPRECATED @2 :Float32; + poorVisionProbDEPRECATED @4 :Float32; } struct DriverStateDEPRECATED @0xb83c6cc593ed0a00 { diff --git a/selfdrive/modeld/dmonitoringmodeld.py b/selfdrive/modeld/dmonitoringmodeld.py index 5aeb035bd..2851a3e7d 100755 --- a/selfdrive/modeld/dmonitoringmodeld.py +++ b/selfdrive/modeld/dmonitoringmodeld.py @@ -25,13 +25,13 @@ from openpilot.selfdrive.modeld.runners.tinygrad_helpers import qcom_tensor_from MODEL_WIDTH, MODEL_HEIGHT = DM_INPUT_SIZE CALIB_LEN = 3 FEATURE_LEN = 512 -OUTPUT_SIZE = 84 + FEATURE_LEN +OUTPUT_SIZE = 83 + FEATURE_LEN PROCESS_NAME = "selfdrive.modeld.dmonitoringmodeld" SEND_RAW_PRED = os.getenv('SEND_RAW_PRED') MODEL_PKL_PATH = Path(__file__).parent / 'models/dmonitoring_model_tinygrad.pkl' - +# TODO: slice from meta class DriverStateResult(ctypes.Structure): _fields_ = [ ("face_orientation", ctypes.c_float*3), @@ -46,8 +46,8 @@ class DriverStateResult(ctypes.Structure): ("left_blink_prob", ctypes.c_float), ("right_blink_prob", ctypes.c_float), ("sunglasses_prob", ctypes.c_float), - ("occluded_prob", ctypes.c_float), - ("ready_prob", ctypes.c_float*4), + ("_unused_c", ctypes.c_float), + ("_unused_d", ctypes.c_float*4), ("not_ready_prob", ctypes.c_float*2)] @@ -55,7 +55,6 @@ class DMonitoringModelResult(ctypes.Structure): _fields_ = [ ("driver_state_lhd", DriverStateResult), ("driver_state_rhd", DriverStateResult), - ("poor_vision_prob", ctypes.c_float), ("wheel_on_right_prob", ctypes.c_float), ("features", ctypes.c_float*FEATURE_LEN)] @@ -107,8 +106,6 @@ def fill_driver_state(msg, ds_result: DriverStateResult): msg.leftBlinkProb = float(sigmoid(ds_result.left_blink_prob)) msg.rightBlinkProb = float(sigmoid(ds_result.right_blink_prob)) msg.sunglassesProb = float(sigmoid(ds_result.sunglasses_prob)) - msg.occludedProb = float(sigmoid(ds_result.occluded_prob)) - msg.readyProb = [float(sigmoid(x)) for x in ds_result.ready_prob] msg.notReadyProb = [float(sigmoid(x)) for x in ds_result.not_ready_prob] @@ -119,7 +116,6 @@ def get_driverstate_packet(model_output: np.ndarray, frame_id: int, location_ts: ds.frameId = frame_id ds.modelExecutionTime = execution_time ds.gpuExecutionTime = gpu_execution_time - ds.poorVisionProb = float(sigmoid(model_result.poor_vision_prob)) ds.wheelOnRightProb = float(sigmoid(model_result.wheel_on_right_prob)) ds.rawPredictions = model_output.tobytes() if SEND_RAW_PRED else b'' fill_driver_state(ds.leftDriverData, model_result.driver_state_lhd) diff --git a/selfdrive/modeld/models/README.md b/selfdrive/modeld/models/README.md index 255f28d80..04b69c61c 100644 --- a/selfdrive/modeld/models/README.md +++ b/selfdrive/modeld/models/README.md @@ -62,6 +62,5 @@ Refer to **slice_outputs** and **parse_vision_outputs/parse_policy_outputs** in * (deprecated) distracted probabilities: 2 * using phone probability: 1 * distracted probability: 1 - * common outputs 2 - * poor camera vision probability: 1 + * common outputs 1 * left hand drive probability: 1 diff --git a/selfdrive/modeld/models/dmonitoring_model.current b/selfdrive/modeld/models/dmonitoring_model.current deleted file mode 100644 index 121871ef2..000000000 --- a/selfdrive/modeld/models/dmonitoring_model.current +++ /dev/null @@ -1,2 +0,0 @@ -fa69be01-b430-4504-9d72-7dcb058eb6dd -d9fb22d1c4fa3ca3d201dbc8edf1d0f0918e53e6 diff --git a/selfdrive/modeld/models/dmonitoring_model.onnx b/selfdrive/modeld/models/dmonitoring_model.onnx index dcc727510..5ae91f67a 100644 --- a/selfdrive/modeld/models/dmonitoring_model.onnx +++ b/selfdrive/modeld/models/dmonitoring_model.onnx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:50efe6451a3fb3fa04b6bb0e846544533329bd46ecefe9e657e91214dee2aaeb -size 7196502 +oid sha256:9b2117ee4907add59e3fbe6829cda74e0ad71c0835b0ebb9373ba9425de0d336 +size 7191776 diff --git a/selfdrive/monitoring/helpers.py b/selfdrive/monitoring/helpers.py index a9cb21a3f..c2e5bc3fe 100644 --- a/selfdrive/monitoring/helpers.py +++ b/selfdrive/monitoring/helpers.py @@ -38,8 +38,6 @@ class DRIVER_MONITOR_SETTINGS: self._EE_THRESH12 = 15.0 self._EE_MAX_OFFSET1 = 0.06 self._EE_MIN_OFFSET1 = 0.025 - self._EE_THRESH21 = 0.01 - self._EE_THRESH22 = 0.35 self._POSE_PITCH_THRESHOLD = 0.3133 self._POSE_PITCH_THRESHOLD_SLACK = 0.3237 @@ -137,11 +135,8 @@ class DriverMonitoring: self.pose = DriverPose(self.settings._POSE_OFFSET_MAX_COUNT) self.blink = DriverBlink() self.eev1 = 0. - self.eev2 = 1. self.ee1_offseter = RunningStatFilter(max_trackable=self.settings._POSE_OFFSET_MAX_COUNT) - self.ee2_offseter = RunningStatFilter(max_trackable=self.settings._POSE_OFFSET_MAX_COUNT) self.ee1_calibrated = False - self.ee2_calibrated = False self.always_on = always_on self.distracted_types = [] @@ -262,7 +257,7 @@ class DriverMonitoring: driver_data = driver_state.rightDriverData if self.wheel_on_right else driver_state.leftDriverData if not all(len(x) > 0 for x in (driver_data.faceOrientation, driver_data.facePosition, driver_data.faceOrientationStd, driver_data.facePositionStd, - driver_data.readyProb, driver_data.notReadyProb)): + driver_data.notReadyProb)): return self.face_detected = driver_data.faceProb > self.settings._FACE_THRESHOLD @@ -279,7 +274,6 @@ class DriverMonitoring: self.blink.right = driver_data.rightBlinkProb * (driver_data.rightEyeProb > self.settings._EYE_THRESHOLD) \ * (driver_data.sunglassesProb < self.settings._SG_THRESHOLD) self.eev1 = driver_data.notReadyProb[0] - self.eev2 = driver_data.readyProb[0] self.distracted_types = self._get_distracted_types() self.driver_distracted = (DistractedType.DISTRACTED_E2E in self.distracted_types or DistractedType.DISTRACTED_POSE in self.distracted_types @@ -293,12 +287,10 @@ class DriverMonitoring: self.pose.pitch_offseter.push_and_update(self.pose.pitch) self.pose.yaw_offseter.push_and_update(self.pose.yaw) self.ee1_offseter.push_and_update(self.eev1) - self.ee2_offseter.push_and_update(self.eev2) self.pose.calibrated = self.pose.pitch_offseter.filtered_stat.n > self.settings._POSE_OFFSET_MIN_COUNT and \ self.pose.yaw_offseter.filtered_stat.n > self.settings._POSE_OFFSET_MIN_COUNT self.ee1_calibrated = self.ee1_offseter.filtered_stat.n > self.settings._POSE_OFFSET_MIN_COUNT - self.ee2_calibrated = self.ee2_offseter.filtered_stat.n > self.settings._POSE_OFFSET_MIN_COUNT self.is_model_uncertain = self.hi_stds > self.settings._HI_STD_FALLBACK_TIME self._set_timers(self.face_detected and not self.is_model_uncertain) diff --git a/selfdrive/monitoring/test_monitoring.py b/selfdrive/monitoring/test_monitoring.py index 2a20b20dc..1cc710188 100644 --- a/selfdrive/monitoring/test_monitoring.py +++ b/selfdrive/monitoring/test_monitoring.py @@ -25,7 +25,6 @@ def make_msg(face_detected, distracted=False, model_uncertain=False): ds.leftDriverData.faceOrientationStd = [1.*model_uncertain, 1.*model_uncertain, 1.*model_uncertain] ds.leftDriverData.facePositionStd = [1.*model_uncertain, 1.*model_uncertain] # TODO: test both separately when e2e is used - ds.leftDriverData.readyProb = [0., 0., 0., 0.] ds.leftDriverData.notReadyProb = [0., 0.] return ds From 63e0e038fadfaed5f307a1813251929f0238acf3 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 30 Sep 2025 22:11:21 -0700 Subject: [PATCH 072/910] raylib: don't use DEFAULT_FPS (#36228) * dont use DEFAULT_FPS * replace --- selfdrive/ui/onroad/model_renderer.py | 4 ++-- selfdrive/ui/ui_state.py | 5 ++--- system/ui/lib/application.py | 10 +++++++--- system/ui/widgets/network.py | 6 +++--- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/selfdrive/ui/onroad/model_renderer.py b/selfdrive/ui/onroad/model_renderer.py index d84e16cb5..a770c0f92 100644 --- a/selfdrive/ui/onroad/model_renderer.py +++ b/selfdrive/ui/onroad/model_renderer.py @@ -7,7 +7,7 @@ from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.params import Params from openpilot.selfdrive.locationd.calibrationd import HEIGHT_INIT from openpilot.selfdrive.ui.ui_state import ui_state -from openpilot.system.ui.lib.application import DEFAULT_FPS +from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.lib.shader_polygon import draw_polygon from openpilot.system.ui.widgets import Widget @@ -48,7 +48,7 @@ class ModelRenderer(Widget): super().__init__() self._longitudinal_control = False self._experimental_mode = False - self._blend_filter = FirstOrderFilter(1.0, 0.25, 1 / DEFAULT_FPS) + self._blend_filter = FirstOrderFilter(1.0, 0.25, 1 / gui_app.target_fps) self._prev_allow_throttle = True self._lane_line_probs = np.zeros(4, dtype=np.float32) self._road_edge_stds = np.zeros(2, dtype=np.float32) diff --git a/selfdrive/ui/ui_state.py b/selfdrive/ui/ui_state.py index 13532620c..bb3abcddb 100644 --- a/selfdrive/ui/ui_state.py +++ b/selfdrive/ui/ui_state.py @@ -9,9 +9,8 @@ from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.params import Params, UnknownKeyName from openpilot.common.swaglog import cloudlog from openpilot.selfdrive.ui.lib.prime_state import PrimeState -from openpilot.system.ui.lib.application import DEFAULT_FPS -from openpilot.system.hardware import HARDWARE from openpilot.system.ui.lib.application import gui_app +from openpilot.system.hardware import HARDWARE UI_BORDER_SIZE = 30 BACKLIGHT_OFFROAD = 50 @@ -153,7 +152,7 @@ class Device: self._offroad_brightness: int = BACKLIGHT_OFFROAD self._last_brightness: int = 0 - self._brightness_filter = FirstOrderFilter(BACKLIGHT_OFFROAD, 10.00, 1 / DEFAULT_FPS) + self._brightness_filter = FirstOrderFilter(BACKLIGHT_OFFROAD, 10.00, 1 / gui_app.target_fps) self._brightness_thread: threading.Thread | None = None def reset_interactive_timeout(self, timeout: int = -1) -> None: diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 4973efa06..a16f0b0bd 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -14,7 +14,7 @@ from openpilot.common.swaglog import cloudlog from openpilot.system.hardware import HARDWARE, PC, TICI from openpilot.common.realtime import Ratekeeper -DEFAULT_FPS = int(os.getenv("FPS", 20 if TICI else 60)) +_DEFAULT_FPS = int(os.getenv("FPS", 20 if TICI else 60)) 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 @@ -130,7 +130,7 @@ class GuiApplication: 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._target_fps: int = _DEFAULT_FPS self._last_fps_log_time: float = time.monotonic() self._window_close_requested = False self._trace_log_callback = None @@ -142,10 +142,14 @@ class GuiApplication: # Debug variables self._mouse_history: deque[MousePos] = deque(maxlen=MOUSE_THREAD_RATE) + @property + def target_fps(self): + return self._target_fps + def request_close(self): self._window_close_requested = True - def init_window(self, title: str, fps: int = DEFAULT_FPS): + def init_window(self, title: str, fps: int = _DEFAULT_FPS): atexit.register(self.close) # Automatically call close() on exit HARDWARE.set_display_power(True) diff --git a/system/ui/widgets/network.py b/system/ui/widgets/network.py index 119901da4..03385609d 100644 --- a/system/ui/widgets/network.py +++ b/system/ui/widgets/network.py @@ -5,7 +5,7 @@ from typing import cast import pyray as rl from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.params import Params -from openpilot.system.ui.lib.application import gui_app, DEFAULT_FPS +from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel from openpilot.system.ui.lib.wifi_manager import WifiManager, SecurityType, Network, MeteredType from openpilot.system.ui.widgets import Widget @@ -50,8 +50,8 @@ class NavButton(Widget): super().__init__() self.text = text self.set_rect(rl.Rectangle(0, 0, 400, 100)) - self._x_pos_filter = FirstOrderFilter(0.0, 0.05, 1 / DEFAULT_FPS, initialized=False) - self._y_pos_filter = FirstOrderFilter(0.0, 0.05, 1 / DEFAULT_FPS, initialized=False) + self._x_pos_filter = FirstOrderFilter(0.0, 0.05, 1 / gui_app.target_fps, initialized=False) + self._y_pos_filter = FirstOrderFilter(0.0, 0.05, 1 / gui_app.target_fps, initialized=False) def set_position(self, x: float, y: float) -> None: x = self._x_pos_filter.update(x) From 5f33b2fb2dafb9957f539d9d94fc51cd4a82244a Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 30 Sep 2025 22:25:43 -0700 Subject: [PATCH 073/910] raylib: frame independent scroller (#36227) * rm that * almost * yess * some work * more * todo * okay viber is good once in a while * temp * chadder can't do this * revert * this was broken anyway * fixes * mouse wheel scroll * some clean up * kinda works * way better * can tap to stop * more clean up * more clean up * revert last mouse * fix * debug only * no print * ahh setup.py fps doesn't affect DEFAULT_FPS ofc * rest * fix text * fix touch valid for network --- selfdrive/ui/layouts/settings/firehose.py | 6 +- selfdrive/ui/widgets/offroad_alerts.py | 4 +- system/ui/lib/scroll_panel.py | 247 ++++++++-------------- system/ui/setup.py | 6 +- system/ui/text.py | 8 +- system/ui/widgets/html_render.py | 4 +- system/ui/widgets/network.py | 29 ++- system/ui/widgets/option_dialog.py | 4 +- system/ui/widgets/scroller.py | 5 +- 9 files changed, 123 insertions(+), 190 deletions(-) diff --git a/selfdrive/ui/layouts/settings/firehose.py b/selfdrive/ui/layouts/settings/firehose.py index b3db1fa5f..7e1d3b61b 100644 --- a/selfdrive/ui/layouts/settings/firehose.py +++ b/selfdrive/ui/layouts/settings/firehose.py @@ -71,7 +71,7 @@ class FirehoseLayout(Widget): content_rect = rl.Rectangle(rect.x, rect.y, rect.width, content_height) # Handle scrolling and render with clipping - scroll_offset = self.scroll_panel.handle_scroll(rect, content_rect) + scroll_offset = self.scroll_panel.update(rect, content_rect) rl.begin_scissor_mode(int(rect.x), int(rect.y), int(rect.width), int(rect.height)) self._render_content(rect, scroll_offset) rl.end_scissor_mode() @@ -106,9 +106,9 @@ class FirehoseLayout(Widget): return height - def _render_content(self, rect: rl.Rectangle, scroll_offset: rl.Vector2): + def _render_content(self, rect: rl.Rectangle, scroll_offset: float): x = int(rect.x + 40) - y = int(rect.y + 40 + scroll_offset.y) + y = int(rect.y + 40 + scroll_offset) w = int(rect.width - 80) # Title diff --git a/selfdrive/ui/widgets/offroad_alerts.py b/selfdrive/ui/widgets/offroad_alerts.py index da0ea287c..6ca0ca2c4 100644 --- a/selfdrive/ui/widgets/offroad_alerts.py +++ b/selfdrive/ui/widgets/offroad_alerts.py @@ -109,7 +109,7 @@ class AbstractAlert(Widget, ABC): def _render_scrollable_content(self): content_total_height = self.get_content_height() content_bounds = rl.Rectangle(0, 0, self.scroll_panel_rect.width, content_total_height) - scroll_offset = self.scroll_panel.handle_scroll(self.scroll_panel_rect, content_bounds) + scroll_offset = self.scroll_panel.update(self.scroll_panel_rect, content_bounds) rl.begin_scissor_mode( int(self.scroll_panel_rect.x), @@ -120,7 +120,7 @@ class AbstractAlert(Widget, ABC): content_rect_with_scroll = rl.Rectangle( self.scroll_panel_rect.x, - self.scroll_panel_rect.y + scroll_offset.y, + self.scroll_panel_rect.y + scroll_offset, self.scroll_panel_rect.width, content_total_height, ) diff --git a/system/ui/lib/scroll_panel.py b/system/ui/lib/scroll_panel.py index e2296fd5e..5dacae821 100644 --- a/system/ui/lib/scroll_panel.py +++ b/system/ui/lib/scroll_panel.py @@ -1,189 +1,124 @@ -import time +import math import pyray as rl -from collections import deque from enum import IntEnum -from openpilot.system.ui.lib.application import gui_app, MouseEvent, MousePos +from openpilot.system.ui.lib.application import gui_app, MouseEvent +from openpilot.common.filter_simple import FirstOrderFilter # Scroll constants for smooth scrolling behavior -MOUSE_WHEEL_SCROLL_SPEED = 30 -INERTIA_FRICTION = 0.92 # The rate at which the inertia slows down -MIN_VELOCITY = 0.5 # Minimum velocity before stopping the inertia -DRAG_THRESHOLD = 12 # 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 +MOUSE_WHEEL_SCROLL_SPEED = 50 +BOUNCE_RETURN_RATE = 5 # ~0.92 at 60fps +MIN_VELOCITY = 2 # px/s, changes from auto scroll to steady state +MIN_VELOCITY_FOR_CLICKING = 2 * 60 # px/s, accepts clicks while auto scrolling below this velocity +DRAG_THRESHOLD = 12 # pixels of movement to consider it a drag, not a click + +DEBUG = False class ScrollState(IntEnum): - IDLE = 0 - DRAGGING_CONTENT = 1 - DRAGGING_SCROLLBAR = 2 - BOUNCING = 3 + IDLE = 0 # Not dragging, content may be bouncing or scrolling with inertia + DRAGGING_CONTENT = 1 # User is actively dragging the content class GuiScrollPanel: - def __init__(self, show_vertical_scroll_bar: bool = False): + def __init__(self): self._scroll_state: ScrollState = ScrollState.IDLE self._last_mouse_y: float = 0.0 self._start_mouse_y: float = 0.0 # Track the initial mouse position for drag detection - self._offset = rl.Vector2(0, 0) - 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: bool = False - self._bounce_offset: float = 0.0 - self._velocity_history: deque[float] = deque(maxlen=VELOCITY_HISTORY_SIZE) + self._offset_filter_y = FirstOrderFilter(0.0, 0.1, 1 / gui_app.target_fps) + self._velocity_filter_y = FirstOrderFilter(0.0, 0.05, 1 / gui_app.target_fps) 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: - # TODO: HACK: this class is driven by mouse events, so we need to ensure we have at least one event to process - for mouse_event in gui_app.mouse_events or [MouseEvent(MousePos(0, 0), 0, False, False, False, time.monotonic())]: + def update(self, bounds: rl.Rectangle, content: rl.Rectangle) -> float: + for mouse_event in gui_app.mouse_events: if mouse_event.slot == 0: self._handle_mouse_event(mouse_event, bounds, content) - return self._offset - def _handle_mouse_event(self, mouse_event: MouseEvent, bounds: rl.Rectangle, content: rl.Rectangle): - # Store rectangles for reference - self._content_rect = content - self._bounds_rect = bounds + self._update_state(bounds, content) - max_scroll_y = max(content.height - bounds.height, 0) + return float(self._offset_filter_y.x) - # Start dragging on mouse press - if rl.check_collision_point_rec(mouse_event.pos, bounds) and mouse_event.left_pressed: - 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) - scrollbar_x = bounds.x + bounds.width - scrollbar_width - if mouse_event.pos.x >= scrollbar_x: - self._scroll_state = ScrollState.DRAGGING_SCROLLBAR - - # TODO: hacky - # when clicking while moving, go straight into dragging - self._is_dragging = abs(self._velocity_y) > MIN_VELOCITY - self._last_mouse_y = mouse_event.pos.y - self._start_mouse_y = mouse_event.pos.y - self._last_drag_time = mouse_event.t - self._velocity_history.clear() - self._velocity_y = 0.0 - self._bounce_offset = 0.0 - - # Handle active dragging - if self._scroll_state == ScrollState.DRAGGING_CONTENT or self._scroll_state == ScrollState.DRAGGING_SCROLLBAR: - if mouse_event.left_down: - delta_y = mouse_event.pos.y - self._last_mouse_y - - # Track velocity for inertia - time_since_last_drag = mouse_event.t - self._last_drag_time - if time_since_last_drag > 0: - # TODO: HACK: /2 since we usually get two touch events per frame - drag_velocity = delta_y / time_since_last_drag / 60.0 / 2 # TODO: shouldn't be hardcoded - self._velocity_history.append(drag_velocity) - - self._last_drag_time = mouse_event.t - - # Detect actual dragging - total_drag = abs(mouse_event.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: - scroll_ratio = content.height / bounds.height - self._offset.y -= delta_y * scroll_ratio - - self._last_mouse_y = mouse_event.pos.y - - elif mouse_event.left_released: - # 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 + def _update_state(self, bounds: rl.Rectangle, content: rl.Rectangle): + if DEBUG: + rl.draw_rectangle_lines(0, 0, abs(int(self._velocity_filter_y.x)), 10, rl.RED) # Handle mouse wheel - wheel_move = rl.get_mouse_wheel_move() - if wheel_move != 0: - self._velocity_y = 0.0 + self._offset_filter_y.x += rl.get_mouse_wheel_move() * MOUSE_WHEEL_SCROLL_SPEED - 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) + max_scroll_distance = max(0, content.height - bounds.height) if self._scroll_state == ScrollState.IDLE: - if abs(self._velocity_y) > MIN_VELOCITY: - self._offset.y += self._velocity_y - self._velocity_y *= INERTIA_FRICTION + above_bounds, below_bounds = self._check_bounds(bounds, content) - if self._offset.y > 0 or self._offset.y < -max_scroll_y: - self._scroll_state = ScrollState.BOUNCING + # Decay velocity when idle + if abs(self._velocity_filter_y.x) > MIN_VELOCITY: + # Faster decay if bouncing back from out of bounds + friction = math.exp(-BOUNCE_RETURN_RATE * 1 / gui_app.target_fps) + self._velocity_filter_y.x *= friction ** 2 if (above_bounds or below_bounds) else friction else: - self._velocity_y = 0.0 + self._velocity_filter_y.x = 0.0 - # 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 + if above_bounds or below_bounds: + if above_bounds: + self._offset_filter_y.update(0) + else: + self._offset_filter_y.update(-max_scroll_distance) - distance = target_y - self._offset.y - bounce_step = distance * BOUNCE_RETURN_SPEED - self._offset.y += bounce_step - self._velocity_y *= INERTIA_FRICTION * 0.8 + self._offset_filter_y.x += self._velocity_filter_y.x / gui_app.target_fps - if abs(distance) < 0.5 and abs(self._velocity_y) < MIN_VELOCITY: - self._offset.y = target_y - self._velocity_y = 0.0 + elif self._scroll_state == ScrollState.DRAGGING_CONTENT: + # Mouse not moving, decay velocity + if not len(gui_app.mouse_events): + self._velocity_filter_y.update(0.0) + + # Settle to exact bounds + if abs(self._offset_filter_y.x) < 1e-2: + self._offset_filter_y.x = 0.0 + elif abs(self._offset_filter_y.x + max_scroll_distance) < 1e-2: + self._offset_filter_y.x = -max_scroll_distance + + def _handle_mouse_event(self, mouse_event: MouseEvent, bounds: rl.Rectangle, content: rl.Rectangle): + if self._scroll_state == ScrollState.IDLE: + if mouse_event.left_pressed: + self._start_mouse_y = mouse_event.pos.y + # Interrupt scrolling with new drag + # TODO: stop scrolling with any tap, need to fix is_touch_valid + if abs(self._velocity_filter_y.x) > MIN_VELOCITY_FOR_CLICKING: + self._scroll_state = ScrollState.DRAGGING_CONTENT + # Start velocity at initial measurement for more immediate response + self._velocity_filter_y.initialized = False + + if mouse_event.left_down: + if abs(mouse_event.pos.y - self._start_mouse_y) > DRAG_THRESHOLD: + self._scroll_state = ScrollState.DRAGGING_CONTENT + # Start velocity at initial measurement for more immediate response + self._velocity_filter_y.initialized = False + + elif self._scroll_state == ScrollState.DRAGGING_CONTENT: + if mouse_event.left_released: self._scroll_state = ScrollState.IDLE + else: + delta_y = mouse_event.pos.y - self._last_mouse_y + above_bounds, below_bounds = self._check_bounds(bounds, content) + # Rubber banding effect when out of bands + if above_bounds or below_bounds: + delta_y /= 3 - # 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) + self._offset_filter_y.x += delta_y + + # Track velocity for inertia + dt = mouse_event.t - self._last_drag_time + if dt > 0: + drag_velocity = delta_y / dt + self._velocity_filter_y.update(drag_velocity) + + # TODO: just store last mouse event! + self._last_drag_time = mouse_event.t + self._last_mouse_y = mouse_event.pos.y + + def _check_bounds(self, bounds: rl.Rectangle, content: rl.Rectangle) -> tuple[bool, bool]: + max_scroll_distance = max(0, content.height - bounds.height) + above_bounds = self._offset_filter_y.x > 0 + below_bounds = self._offset_filter_y.x < -max_scroll_distance + return above_bounds, below_bounds def is_touch_valid(self): - return not self._is_dragging - - 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)) + return self._scroll_state == ScrollState.IDLE and abs(self._velocity_filter_y.x) < MIN_VELOCITY_FOR_CLICKING diff --git a/system/ui/setup.py b/system/ui/setup.py index a985e783b..e0d737cb1 100755 --- a/system/ui/setup.py +++ b/system/ui/setup.py @@ -299,20 +299,20 @@ class Setup(Widget): def render_custom_software_warning(self, rect: rl.Rectangle): warn_rect = rl.Rectangle(rect.x, rect.y, rect.width, 1500) - offset = self._custom_software_warning_body_scroll_panel.handle_scroll(rect, warn_rect) + offset = self._custom_software_warning_body_scroll_panel.update(rect, warn_rect) button_width = (rect.width - MARGIN * 3) / 2 button_y = rect.height - MARGIN - BUTTON_HEIGHT rl.begin_scissor_mode(int(rect.x), int(rect.y), int(rect.width), int(button_y - BODY_FONT_SIZE)) - y_offset = rect.y + offset.y + y_offset = rect.y + offset self._custom_software_warning_title_label.render(rl.Rectangle(rect.x + 50, y_offset + 150, rect.width - 265, TITLE_FONT_SIZE)) self._custom_software_warning_body_label.render(rl.Rectangle(rect.x + 50, y_offset + 200 , rect.width - 50, BODY_FONT_SIZE * 3)) rl.end_scissor_mode() self._custom_software_warning_back_button.render(rl.Rectangle(rect.x + MARGIN, button_y, button_width, BUTTON_HEIGHT)) self._custom_software_warning_continue_button.render(rl.Rectangle(rect.x + MARGIN * 2 + button_width, button_y, button_width, BUTTON_HEIGHT)) - if offset.y < (rect.height - warn_rect.height): + if offset < (rect.height - warn_rect.height): self._custom_software_warning_continue_button.set_enabled(True) self._custom_software_warning_continue_button.set_text("Continue") diff --git a/system/ui/text.py b/system/ui/text.py index 61ac043b7..3db930eb2 100755 --- a/system/ui/text.py +++ b/system/ui/text.py @@ -53,14 +53,14 @@ class TextWindow(Widget): self._textarea_rect = rl.Rectangle(MARGIN, MARGIN, gui_app.width - MARGIN * 2, gui_app.height - MARGIN * 2) self._wrapped_lines = wrap_text(text, FONT_SIZE, self._textarea_rect.width - 20) self._content_rect = rl.Rectangle(0, 0, self._textarea_rect.width - 20, len(self._wrapped_lines) * LINE_HEIGHT) - self._scroll_panel = GuiScrollPanel(show_vertical_scroll_bar=True) - self._scroll_panel._offset.y = -max(self._content_rect.height - self._textarea_rect.height, 0) + self._scroll_panel = GuiScrollPanel() + self._scroll_panel._offset_filter_y.x = -max(self._content_rect.height - self._textarea_rect.height, 0) def _render(self, rect: rl.Rectangle): - scroll = self._scroll_panel.handle_scroll(self._textarea_rect, self._content_rect) + scroll = self._scroll_panel.update(self._textarea_rect, self._content_rect) rl.begin_scissor_mode(int(self._textarea_rect.x), int(self._textarea_rect.y), int(self._textarea_rect.width), int(self._textarea_rect.height)) for i, line in enumerate(self._wrapped_lines): - position = rl.Vector2(self._textarea_rect.x + scroll.x, self._textarea_rect.y + scroll.y + i * LINE_HEIGHT) + position = rl.Vector2(self._textarea_rect.x, self._textarea_rect.y + scroll + i * LINE_HEIGHT) if position.y + LINE_HEIGHT < self._textarea_rect.y or position.y > self._textarea_rect.y + self._textarea_rect.height: continue rl.draw_text_ex(gui_app.font(), line, position, FONT_SIZE, 0, rl.WHITE) diff --git a/system/ui/widgets/html_render.py b/system/ui/widgets/html_render.py index d68ea5899..bb7eeb7c5 100644 --- a/system/ui/widgets/html_render.py +++ b/system/ui/widgets/html_render.py @@ -116,10 +116,10 @@ class HtmlRenderer(Widget): total_height = self.get_total_height(int(scrollable_rect.width)) scroll_content_rect = rl.Rectangle(scrollable_rect.x, scrollable_rect.y, scrollable_rect.width, total_height) - scroll_offset = self._scroll_panel.handle_scroll(scrollable_rect, scroll_content_rect) + scroll_offset = self._scroll_panel.update(scrollable_rect, scroll_content_rect) rl.begin_scissor_mode(int(scrollable_rect.x), int(scrollable_rect.y), int(scrollable_rect.width), int(scrollable_rect.height)) - self._render_content(scrollable_rect, scroll_offset.y) + self._render_content(scrollable_rect, scroll_offset) rl.end_scissor_mode() button_width = (rect.width - 3 * 50) // 3 diff --git a/system/ui/widgets/network.py b/system/ui/widgets/network.py index 03385609d..3dd2a4d65 100644 --- a/system/ui/widgets/network.py +++ b/system/ui/widgets/network.py @@ -339,24 +339,23 @@ class WifiManagerUI(Widget): def _draw_network_list(self, rect: rl.Rectangle): content_rect = rl.Rectangle(rect.x, rect.y, rect.width, len(self._networks) * ITEM_HEIGHT) - offset = self.scroll_panel.handle_scroll(rect, content_rect) - clicked = self.scroll_panel.is_touch_valid() and rl.is_mouse_button_released(rl.MouseButton.MOUSE_BUTTON_LEFT) + offset = self.scroll_panel.update(rect, content_rect) rl.begin_scissor_mode(int(rect.x), int(rect.y), int(rect.width), int(rect.height)) for i, network in enumerate(self._networks): - y_offset = rect.y + i * ITEM_HEIGHT + offset.y + y_offset = rect.y + i * ITEM_HEIGHT + offset 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) + self._draw_network_item(item_rect, network) 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: Network, clicked: bool): + def _draw_network_item(self, rect, network: Network): 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) @@ -396,18 +395,16 @@ class WifiManagerUI(Widget): self._draw_signal_strength_icon(signal_icon_rect, network) def _networks_buttons_callback(self, network): - if self.scroll_panel.is_touch_valid(): - if not network.is_saved and network.security_type != SecurityType.OPEN: - self.state = UIState.NEEDS_AUTH - self._state_network = network - self._password_retry = False - elif not network.is_connected: - self.connect_to_network(network) + if not network.is_saved and network.security_type != SecurityType.OPEN: + self.state = UIState.NEEDS_AUTH + self._state_network = network + self._password_retry = False + elif not network.is_connected: + self.connect_to_network(network) def _forget_networks_buttons_callback(self, network): - if self.scroll_panel.is_touch_valid(): - self.state = UIState.SHOW_FORGET_CONFIRM - self._state_network = network + self.state = UIState.SHOW_FORGET_CONFIRM + self._state_network = network def _draw_status_icon(self, rect, network: Network): """Draw the status icon based on network's connection state""" @@ -449,8 +446,10 @@ class WifiManagerUI(Widget): for n in self._networks: self._networks_buttons[n.ssid] = Button(n.ssid, partial(self._networks_buttons_callback, n), font_size=55, text_alignment=TextAlignment.LEFT, button_style=ButtonStyle.TRANSPARENT_WHITE) + self._networks_buttons[n.ssid].set_touch_valid_callback(lambda: self.scroll_panel.is_touch_valid()) self._forget_networks_buttons[n.ssid] = Button("Forget", partial(self._forget_networks_buttons_callback, n), button_style=ButtonStyle.FORGET_WIFI, font_size=45) + self._forget_networks_buttons[n.ssid].set_touch_valid_callback(lambda: self.scroll_panel.is_touch_valid()) def _on_need_auth(self, ssid): network = next((n for n in self._networks if n.ssid == ssid), None) diff --git a/system/ui/widgets/option_dialog.py b/system/ui/widgets/option_dialog.py index 8f33124b5..cbab024f0 100644 --- a/system/ui/widgets/option_dialog.py +++ b/system/ui/widgets/option_dialog.py @@ -41,12 +41,12 @@ class MultiOptionDialog(Widget): list_content_rect = rl.Rectangle(content_rect.x, options_y, content_rect.width, content_h) # Scroll and render options - offset = self.scroll.handle_scroll(view_rect, list_content_rect) + offset = self.scroll.update(view_rect, list_content_rect) valid_click = self.scroll.is_touch_valid() and rl.is_mouse_button_released(rl.MouseButton.MOUSE_BUTTON_LEFT) rl.begin_scissor_mode(int(view_rect.x), int(options_y), int(view_rect.width), int(options_h)) for i, option in enumerate(self.options): - item_y = options_y + i * (ITEM_HEIGHT + LIST_ITEM_SPACING) + offset.y + item_y = options_y + i * (ITEM_HEIGHT + LIST_ITEM_SPACING) + offset item_rect = rl.Rectangle(view_rect.x, item_y, view_rect.width, ITEM_HEIGHT) if rl.check_collision_recs(item_rect, view_rect): diff --git a/system/ui/widgets/scroller.py b/system/ui/widgets/scroller.py index 757500a71..c76f30d19 100644 --- a/system/ui/widgets/scroller.py +++ b/system/ui/widgets/scroller.py @@ -52,7 +52,7 @@ class Scroller(Widget): content_height = sum(item.rect.height for item in visible_items) + self._spacing * (len(visible_items)) if not self._pad_end: content_height -= self._spacing - scroll = self.scroll_panel.handle_scroll(self._rect, rl.Rectangle(0, 0, self._rect.width, content_height)) + scroll = self.scroll_panel.update(self._rect, rl.Rectangle(0, 0, self._rect.width, content_height)) rl.begin_scissor_mode(int(self._rect.x), int(self._rect.y), int(self._rect.width), int(self._rect.height)) @@ -68,8 +68,7 @@ class Scroller(Widget): cur_height += item.rect.height + self._spacing * (idx != 0) # Consider scroll - x += scroll.x - y += scroll.y + y += scroll # Update item state item.set_position(x, y) From 5c0c2a17b0539aecfd7401ae96ca946a1142418e Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 30 Sep 2025 22:30:45 -0700 Subject: [PATCH 074/910] raylib: add mic indicator (#36207) * update lang * mic indicator * clean up * clean up * switch * fix * revert --- selfdrive/ui/layouts/main.py | 3 ++- selfdrive/ui/layouts/settings/toggles.py | 2 +- selfdrive/ui/layouts/sidebar.py | 23 ++++++++++++++++++++++- selfdrive/ui/ui_state.py | 1 + 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/selfdrive/ui/layouts/main.py b/selfdrive/ui/layouts/main.py index 1f5612224..a2401ef8b 100644 --- a/selfdrive/ui/layouts/main.py +++ b/selfdrive/ui/layouts/main.py @@ -47,7 +47,8 @@ class MainLayout(Widget): def _setup_callbacks(self): self._sidebar.set_callbacks(on_settings=self._on_settings_clicked, - on_flag=self._on_bookmark_clicked) + on_flag=self._on_bookmark_clicked, + open_settings=lambda: self.open_settings(PanelType.TOGGLES)) self._layouts[MainState.HOME]._setup_widget.set_open_settings_callback(lambda: self.open_settings(PanelType.FIREHOSE)) self._layouts[MainState.SETTINGS].set_callbacks(on_close=self._set_mode_for_state) self._layouts[MainState.ONROAD].set_click_callback(self._on_onroad_clicked) diff --git a/selfdrive/ui/layouts/settings/toggles.py b/selfdrive/ui/layouts/settings/toggles.py index 58afcec5e..1e0e7bfd5 100644 --- a/selfdrive/ui/layouts/settings/toggles.py +++ b/selfdrive/ui/layouts/settings/toggles.py @@ -76,7 +76,7 @@ class TogglesLayout(Widget): icon="monitoring.png", ), toggle_item( - "Record Microphone Audio", + "Record and Upload Microphone Audio", DESCRIPTIONS["RecordAudio"], self._params.get_bool("RecordAudio"), icon="microphone.png", diff --git a/selfdrive/ui/layouts/sidebar.py b/selfdrive/ui/layouts/sidebar.py index 4d47a9878..5987d062f 100644 --- a/selfdrive/ui/layouts/sidebar.py +++ b/selfdrive/ui/layouts/sidebar.py @@ -71,20 +71,26 @@ class Sidebar(Widget): self._temp_status = MetricData("TEMP", "GOOD", Colors.GOOD) self._panda_status = MetricData("VEHICLE", "ONLINE", Colors.GOOD) self._connect_status = MetricData("CONNECT", "OFFLINE", Colors.WARNING) + self._recording_audio = False self._home_img = gui_app.texture("images/button_home.png", HOME_BTN.width, HOME_BTN.height) self._flag_img = gui_app.texture("images/button_flag.png", HOME_BTN.width, HOME_BTN.height) self._settings_img = gui_app.texture("images/button_settings.png", SETTINGS_BTN.width, SETTINGS_BTN.height) + self._mic_img = gui_app.texture("icons/microphone.png", 30, 30) + self._mic_indicator_rect = rl.Rectangle(0, 0, 0, 0) self._font_regular = gui_app.font(FontWeight.NORMAL) self._font_bold = gui_app.font(FontWeight.SEMI_BOLD) # Callbacks self._on_settings_click: Callable | None = None self._on_flag_click: Callable | None = None + self._open_settings_callback: Callable | None = None - def set_callbacks(self, on_settings: Callable | None = None, on_flag: Callable | None = None): + def set_callbacks(self, on_settings: Callable | None = None, on_flag: Callable | None = None, + open_settings: Callable | None = None): self._on_settings_click = on_settings self._on_flag_click = on_flag + self._open_settings_callback = open_settings def _render(self, rect: rl.Rectangle): # Background @@ -101,6 +107,7 @@ class Sidebar(Widget): device_state = sm['deviceState'] + self._recording_audio = sm.alive['rawAudioData'] self._update_network_status(device_state) self._update_temperature_status(device_state) self._update_connection_status(device_state) @@ -143,6 +150,9 @@ class Sidebar(Widget): elif rl.check_collision_point_rec(mouse_pos, HOME_BTN) and ui_state.started: if self._on_flag_click: self._on_flag_click() + elif self._recording_audio and rl.check_collision_point_rec(mouse_pos, self._mic_indicator_rect): + if self._open_settings_callback: + self._open_settings_callback() def _draw_buttons(self, rect: rl.Rectangle): mouse_pos = rl.get_mouse_position() @@ -160,6 +170,17 @@ class Sidebar(Widget): tint = Colors.BUTTON_PRESSED if (ui_state.started and flag_pressed) else Colors.BUTTON_NORMAL rl.draw_texture(button_img, int(HOME_BTN.x), int(HOME_BTN.y), tint) + # Microphone button + if self._recording_audio: + self._mic_indicator_rect = rl.Rectangle(rect.x + rect.width - 138, rect.y + 245, 75, 40) + + mic_pressed = mouse_down and rl.check_collision_point_rec(mouse_pos, self._mic_indicator_rect) + bg_color = rl.Color(Colors.DANGER.r, Colors.DANGER.g, Colors.DANGER.b, int(255 * 0.65)) if mic_pressed else Colors.DANGER + + rl.draw_rectangle_rounded(self._mic_indicator_rect, 1, 10, bg_color) + rl.draw_texture(self._mic_img, int(self._mic_indicator_rect.x + (self._mic_indicator_rect.width - self._mic_img.width) / 2), + int(self._mic_indicator_rect.y + (self._mic_indicator_rect.height - self._mic_img.height) / 2), Colors.WHITE) + def _draw_network_indicator(self, rect: rl.Rectangle): # Signal strength dots x_start = rect.x + 58 diff --git a/selfdrive/ui/ui_state.py b/selfdrive/ui/ui_state.py index bb3abcddb..9c45519b4 100644 --- a/selfdrive/ui/ui_state.py +++ b/selfdrive/ui/ui_state.py @@ -50,6 +50,7 @@ class UIState: "managerState", "selfdriveState", "longitudinalPlan", + "rawAudioData", ] ) From b593b7cc43926fa6a5f20776319b4f91f86c4b05 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 30 Sep 2025 23:43:23 -0700 Subject: [PATCH 075/910] raylib: SSH key text entry works more than once (#36230) * impossible * jarn * actually space * forgot --- selfdrive/ui/widgets/ssh_key.py | 4 ++-- system/ui/widgets/button.py | 12 ++++++------ system/ui/widgets/network.py | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/selfdrive/ui/widgets/ssh_key.py b/selfdrive/ui/widgets/ssh_key.py index 4f4a8dcdf..2fc13d4c9 100644 --- a/selfdrive/ui/widgets/ssh_key.py +++ b/selfdrive/ui/widgets/ssh_key.py @@ -34,7 +34,7 @@ class SshKeyAction(ItemAction): def __init__(self): super().__init__(self.MAX_WIDTH, True) - self._keyboard = Keyboard() + self._keyboard = Keyboard(min_text_size=1) self._params = Params() self._error_message: str = "" self._text_font = gui_app.font(FontWeight.MEDIUM) @@ -82,7 +82,7 @@ class SshKeyAction(ItemAction): def _handle_button_click(self): if self._state == SshKeyActionState.ADD: - self._keyboard.clear() + self._keyboard.reset() self._keyboard.set_title("Enter your GitHub username") gui_app.set_modal_overlay(self._keyboard, callback=self._on_username_submit) elif self._state == SshKeyActionState.REMOVE: diff --git a/system/ui/widgets/button.py b/system/ui/widgets/button.py index 1e3f28eb8..32d63952a 100644 --- a/system/ui/widgets/button.py +++ b/system/ui/widgets/button.py @@ -14,7 +14,7 @@ class ButtonStyle(IntEnum): PRIMARY = 1 # For main actions DANGER = 2 # For critical actions, like reboot or delete TRANSPARENT = 3 # For buttons with transparent background and border - TRANSPARENT_WHITE = 3 # For buttons with transparent background and border + TRANSPARENT_WHITE_TEXT = 3 # For buttons with transparent background and border and white text ACTION = 4 LIST_ACTION = 5 # For list items with action buttons NO_EFFECT = 6 @@ -31,7 +31,7 @@ BUTTON_TEXT_COLOR = { ButtonStyle.PRIMARY: rl.Color(228, 228, 228, 255), ButtonStyle.DANGER: rl.Color(228, 228, 228, 255), ButtonStyle.TRANSPARENT: rl.BLACK, - ButtonStyle.TRANSPARENT_WHITE: rl.WHITE, + ButtonStyle.TRANSPARENT_WHITE_TEXT: rl.WHITE, ButtonStyle.ACTION: rl.BLACK, ButtonStyle.LIST_ACTION: rl.Color(228, 228, 228, 255), ButtonStyle.NO_EFFECT: rl.Color(228, 228, 228, 255), @@ -40,7 +40,7 @@ BUTTON_TEXT_COLOR = { } BUTTON_DISABLED_TEXT_COLORS = { - ButtonStyle.TRANSPARENT_WHITE: rl.WHITE, + ButtonStyle.TRANSPARENT_WHITE_TEXT: rl.WHITE, } BUTTON_BACKGROUND_COLORS = { @@ -48,7 +48,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.TRANSPARENT_WHITE: rl.BLANK, + ButtonStyle.TRANSPARENT_WHITE_TEXT: rl.BLANK, ButtonStyle.ACTION: rl.Color(189, 189, 189, 255), ButtonStyle.LIST_ACTION: rl.Color(57, 57, 57, 255), ButtonStyle.NO_EFFECT: rl.Color(51, 51, 51, 255), @@ -61,7 +61,7 @@ BUTTON_PRESSED_BACKGROUND_COLORS = { ButtonStyle.PRIMARY: rl.Color(48, 73, 244, 255), ButtonStyle.DANGER: rl.Color(255, 36, 36, 255), ButtonStyle.TRANSPARENT: rl.BLACK, - ButtonStyle.TRANSPARENT_WHITE: rl.BLANK, + ButtonStyle.TRANSPARENT_WHITE_TEXT: rl.BLANK, ButtonStyle.ACTION: rl.Color(130, 130, 130, 255), ButtonStyle.LIST_ACTION: rl.Color(74, 74, 74, 74), ButtonStyle.NO_EFFECT: rl.Color(51, 51, 51, 255), @@ -70,7 +70,7 @@ BUTTON_PRESSED_BACKGROUND_COLORS = { } BUTTON_DISABLED_BACKGROUND_COLORS = { - ButtonStyle.TRANSPARENT_WHITE: rl.BLANK, + ButtonStyle.TRANSPARENT_WHITE_TEXT: rl.BLANK, } _pressed_buttons: set[str] = set() # Track mouse press state globally diff --git a/system/ui/widgets/network.py b/system/ui/widgets/network.py index 3dd2a4d65..9eaac6030 100644 --- a/system/ui/widgets/network.py +++ b/system/ui/widgets/network.py @@ -445,7 +445,7 @@ class WifiManagerUI(Widget): self._networks = networks for n in self._networks: self._networks_buttons[n.ssid] = Button(n.ssid, partial(self._networks_buttons_callback, n), font_size=55, text_alignment=TextAlignment.LEFT, - button_style=ButtonStyle.TRANSPARENT_WHITE) + button_style=ButtonStyle.TRANSPARENT_WHITE_TEXT) self._networks_buttons[n.ssid].set_touch_valid_callback(lambda: self.scroll_panel.is_touch_valid()) self._forget_networks_buttons[n.ssid] = Button("Forget", partial(self._forget_networks_buttons_callback, n), button_style=ButtonStyle.FORGET_WIFI, font_size=45) From 9493f2a0eb889428d5e17967ca7ce919ab466848 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 30 Sep 2025 23:48:04 -0700 Subject: [PATCH 076/910] raylib: remove functional confirmation dialog (#36231) * rm * yess * clean up --- selfdrive/ui/layouts/settings/device.py | 26 ++++------ selfdrive/ui/layouts/settings/software.py | 8 ++- selfdrive/ui/widgets/ssh_key.py | 2 +- system/ui/widgets/confirm_dialog.py | 60 ++--------------------- 4 files changed, 19 insertions(+), 77 deletions(-) diff --git a/selfdrive/ui/layouts/settings/device.py b/selfdrive/ui/layouts/settings/device.py index b34042d43..892853ca3 100644 --- a/selfdrive/ui/layouts/settings/device.py +++ b/selfdrive/ui/layouts/settings/device.py @@ -10,7 +10,7 @@ from openpilot.selfdrive.ui.widgets.pairing_dialog import PairingDialog from openpilot.system.hardware import TICI from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.widgets import Widget, DialogResult -from openpilot.system.ui.widgets.confirm_dialog import confirm_dialog, alert_dialog +from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog, alert_dialog from openpilot.system.ui.widgets.html_render import HtmlRenderer from openpilot.system.ui.widgets.list_view import text_item, button_item, dual_button_item from openpilot.system.ui.widgets.option_dialog import MultiOptionDialog @@ -92,13 +92,11 @@ class DeviceLayout(Widget): def _reset_calibration_prompt(self): if ui_state.engaged: - gui_app.set_modal_overlay(lambda: alert_dialog("Disengage to Reset Calibration")) + gui_app.set_modal_overlay(alert_dialog("Disengage to Reset Calibration")) return - gui_app.set_modal_overlay( - lambda: confirm_dialog("Are you sure you want to reset calibration?", "Reset"), - callback=self._reset_calibration, - ) + dialog = ConfirmDialog("Are you sure you want to reset calibration?", "Reset") + gui_app.set_modal_overlay(dialog, callback=self._reset_calibration) def _reset_calibration(self, result: int): if ui_state.engaged or result != DialogResult.CONFIRM: @@ -113,13 +111,11 @@ class DeviceLayout(Widget): def _reboot_prompt(self): if ui_state.engaged: - gui_app.set_modal_overlay(lambda: alert_dialog("Disengage to Reboot")) + gui_app.set_modal_overlay(alert_dialog("Disengage to Reboot")) return - gui_app.set_modal_overlay( - lambda: confirm_dialog("Are you sure you want to reboot?", "Reboot"), - callback=self._perform_reboot, - ) + dialog = ConfirmDialog("Are you sure you want to reboot?", "Reboot") + gui_app.set_modal_overlay(dialog, callback=self._perform_reboot) def _perform_reboot(self, result: int): if not ui_state.engaged and result == DialogResult.CONFIRM: @@ -127,13 +123,11 @@ class DeviceLayout(Widget): def _power_off_prompt(self): if ui_state.engaged: - gui_app.set_modal_overlay(lambda: alert_dialog("Disengage to Power Off")) + gui_app.set_modal_overlay(alert_dialog("Disengage to Power Off")) return - gui_app.set_modal_overlay( - lambda: confirm_dialog("Are you sure you want to power off?", "Power Off"), - callback=self._perform_power_off, - ) + dialog = ConfirmDialog("Are you sure you want to power off?", "Power Off") + gui_app.set_modal_overlay(dialog, callback=self._perform_power_off) def _perform_power_off(self, result: int): if not ui_state.engaged and result == DialogResult.CONFIRM: diff --git a/selfdrive/ui/layouts/settings/software.py b/selfdrive/ui/layouts/settings/software.py index 4361725a1..034907001 100644 --- a/selfdrive/ui/layouts/settings/software.py +++ b/selfdrive/ui/layouts/settings/software.py @@ -1,7 +1,7 @@ from openpilot.common.params import Params from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.widgets import Widget, DialogResult -from openpilot.system.ui.widgets.confirm_dialog import confirm_dialog +from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog from openpilot.system.ui.widgets.list_view import button_item, text_item from openpilot.system.ui.widgets.scroller import Scroller @@ -36,7 +36,5 @@ class SoftwareLayout(Widget): if result == DialogResult.CONFIRM: self._params.put_bool("DoUninstall", True) - gui_app.set_modal_overlay( - lambda: confirm_dialog("Are you sure you want to uninstall?", "Uninstall"), - callback=handle_uninstall_confirmation, - ) + dialog = ConfirmDialog("Are you sure you want to uninstall?", "Uninstall") + gui_app.set_modal_overlay(dialog, callback=handle_uninstall_confirmation) diff --git a/selfdrive/ui/widgets/ssh_key.py b/selfdrive/ui/widgets/ssh_key.py index 2fc13d4c9..5611be8f7 100644 --- a/selfdrive/ui/widgets/ssh_key.py +++ b/selfdrive/ui/widgets/ssh_key.py @@ -49,7 +49,7 @@ class SshKeyAction(ItemAction): # Show error dialog if there's an error if self._error_message: message = copy.copy(self._error_message) - gui_app.set_modal_overlay(lambda: alert_dialog(message)) + gui_app.set_modal_overlay(alert_dialog(message)) self._username = "" self._error_message = "" diff --git a/system/ui/widgets/confirm_dialog.py b/system/ui/widgets/confirm_dialog.py index 1021b5452..b1dc54bf7 100644 --- a/system/ui/widgets/confirm_dialog.py +++ b/system/ui/widgets/confirm_dialog.py @@ -1,8 +1,8 @@ import pyray as rl from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.widgets import DialogResult -from openpilot.system.ui.widgets.button import gui_button, ButtonStyle, Button -from openpilot.system.ui.widgets.label import gui_text_box, Label +from openpilot.system.ui.widgets.button import ButtonStyle, Button +from openpilot.system.ui.widgets.label import Label from openpilot.system.ui.widgets import Widget DIALOG_WIDTH = 1520 @@ -12,6 +12,7 @@ MARGIN = 50 TEXT_AREA_HEIGHT_REDUCTION = 200 BACKGROUND_COLOR = rl.Color(27, 27, 27, 255) + class ConfirmDialog(Widget): def __init__(self, text: str, confirm_text: str, cancel_text: str = "Cancel"): super().__init__() @@ -66,57 +67,6 @@ class ConfirmDialog(Widget): return self._dialog_result -def confirm_dialog(message: str, confirm_text: str, cancel_text: str = "Cancel") -> DialogResult: - 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 - bottom = dialog_rect.y + dialog_rect.height - button_width = (dialog_rect.width - 3 * MARGIN) // 2 - no_button_x = dialog_rect.x + MARGIN - yes_button_x = dialog_rect.x + dialog_rect.width - button_width - MARGIN - button_y = bottom - BUTTON_HEIGHT - MARGIN - no_button = rl.Rectangle(no_button_x, button_y, button_width, BUTTON_HEIGHT) - yes_button = rl.Rectangle(yes_button_x, button_y, button_width, BUTTON_HEIGHT) - - # Draw the dialog background - rl.draw_rectangle_rec(dialog_rect, BACKGROUND_COLOR) - - # Draw the message in the dialog, centered - text_rect = rl.Rectangle(dialog_rect.x + MARGIN, dialog_rect.y, dialog_rect.width - 2 * MARGIN, dialog_rect.height - TEXT_AREA_HEIGHT_REDUCTION) - gui_text_box( - text_rect, - message, - font_size=70, - alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER, - alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE, - font_weight=FontWeight.BOLD, - ) - - # Initialize result; -1 means no action taken yet - result = DialogResult.NO_ACTION - - # Check for keyboard input for accessibility - if rl.is_key_pressed(rl.KeyboardKey.KEY_ENTER): - result = DialogResult.CONFIRM - elif rl.is_key_pressed(rl.KeyboardKey.KEY_ESCAPE): - result = DialogResult.CANCEL - - # Check for button clicks - if cancel_text: - if gui_button(yes_button, confirm_text, button_style=ButtonStyle.PRIMARY): - result = DialogResult.CONFIRM - if gui_button(no_button, cancel_text): - result = DialogResult.CANCEL - else: - centered_button_x = dialog_rect.x + (dialog_rect.width - button_width) / 2 - centered_yes_button = rl.Rectangle(centered_button_x, button_y, button_width, BUTTON_HEIGHT) - if gui_button(centered_yes_button, confirm_text, button_style=ButtonStyle.PRIMARY): - result = DialogResult.CONFIRM - - return result - - -def alert_dialog(message: str, button_text: str = "OK") -> DialogResult: - return confirm_dialog(message, button_text, cancel_text="") +def alert_dialog(message: str, button_text: str = "OK"): + return ConfirmDialog(message, button_text, cancel_text="") From eadab06f591889499d8ceb85da5f376e51b559cc Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 1 Oct 2025 00:32:09 -0700 Subject: [PATCH 077/910] raylib: remove gui_button (#36229) * vibing can be good * and listview * rm that * html render * text.py * ssh keys * updater w/ Auto * wow gpt5 actually is better * well this is better * huh wifi still doesn't work * lfg * lint * manager waits for exit * wait a minute this changes nothing * this will work * whoops * clean up html * actually useless * clean up option * typing * bump --- selfdrive/ui/layouts/settings/device.py | 2 +- selfdrive/ui/widgets/ssh_key.py | 26 ++--- system/ui/text.py | 19 ++-- system/ui/updater.py | 30 +++--- system/ui/widgets/button.py | 120 ++++-------------------- system/ui/widgets/html_render.py | 10 +- system/ui/widgets/list_view.py | 26 ++--- system/ui/widgets/option_dialog.py | 52 +++++----- 8 files changed, 101 insertions(+), 184 deletions(-) diff --git a/selfdrive/ui/layouts/settings/device.py b/selfdrive/ui/layouts/settings/device.py index 892853ca3..cb705d46f 100644 --- a/selfdrive/ui/layouts/settings/device.py +++ b/selfdrive/ui/layouts/settings/device.py @@ -141,7 +141,7 @@ class DeviceLayout(Widget): def _on_regulatory(self): if not self._fcc_dialog: self._fcc_dialog = HtmlRenderer(os.path.join(BASEDIR, "selfdrive/assets/offroad/fcc.html")) - gui_app.set_modal_overlay(self._fcc_dialog, callback=lambda result: setattr(self, '_fcc_dialog', None)) + gui_app.set_modal_overlay(self._fcc_dialog) def _on_review_training_guide(self): if not self._training_guide: diff --git a/selfdrive/ui/widgets/ssh_key.py b/selfdrive/ui/widgets/ssh_key.py index 5611be8f7..370141bd6 100644 --- a/selfdrive/ui/widgets/ssh_key.py +++ b/selfdrive/ui/widgets/ssh_key.py @@ -2,13 +2,14 @@ import pyray as rl import requests import threading import copy +from collections.abc import Callable from enum import Enum from openpilot.common.params import Params from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.widgets import DialogResult -from openpilot.system.ui.widgets.button import gui_button, ButtonStyle +from openpilot.system.ui.widgets.button import Button, ButtonStyle from openpilot.system.ui.widgets.confirm_dialog import alert_dialog from openpilot.system.ui.widgets.keyboard import Keyboard from openpilot.system.ui.widgets.list_view import ( @@ -38,9 +39,15 @@ class SshKeyAction(ItemAction): self._params = Params() self._error_message: str = "" self._text_font = gui_app.font(FontWeight.MEDIUM) + self._button = Button("", click_callback=self._handle_button_click, button_style=ButtonStyle.LIST_ACTION, + border_radius=BUTTON_BORDER_RADIUS, font_size=BUTTON_FONT_SIZE) self._refresh_state() + def set_touch_valid_callback(self, touch_callback: Callable[[], bool]) -> None: + super().set_touch_valid_callback(touch_callback) + self._button.set_touch_valid_callback(touch_callback) + def _refresh_state(self): self._username = self._params.get("GithubUsername") self._state = SshKeyActionState.REMOVE if self._params.get("GithubSshKeys") else SshKeyActionState.ADD @@ -66,18 +73,11 @@ class SshKeyAction(ItemAction): ) # Draw button - if gui_button( - rl.Rectangle( - rect.x + rect.width - BUTTON_WIDTH, rect.y + (rect.height - BUTTON_HEIGHT) / 2, BUTTON_WIDTH, BUTTON_HEIGHT - ), - self._state.value, - is_enabled=self._state != SshKeyActionState.LOADING, - border_radius=BUTTON_BORDER_RADIUS, - font_size=BUTTON_FONT_SIZE, - button_style=ButtonStyle.LIST_ACTION, - ): - self._handle_button_click() - return True + button_rect = rl.Rectangle(rect.x + rect.width - BUTTON_WIDTH, rect.y + (rect.height - BUTTON_HEIGHT) / 2, BUTTON_WIDTH, BUTTON_HEIGHT) + self._button.set_rect(button_rect) + self._button.set_text(self._state.value) + self._button.set_enabled(self._state != SshKeyActionState.LOADING) + self._button.render(button_rect) return False def _handle_button_click(self): diff --git a/system/ui/text.py b/system/ui/text.py index 3db930eb2..707b30983 100755 --- a/system/ui/text.py +++ b/system/ui/text.py @@ -7,7 +7,7 @@ from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.widgets import Widget -from openpilot.system.ui.widgets.button import gui_button, ButtonStyle +from openpilot.system.ui.widgets.button import Button, ButtonStyle MARGIN = 50 SPACING = 40 @@ -56,6 +56,15 @@ class TextWindow(Widget): self._scroll_panel = GuiScrollPanel() self._scroll_panel._offset_filter_y.x = -max(self._content_rect.height - self._textarea_rect.height, 0) + button_text = "Exit" if PC else "Reboot" + self._button = Button(button_text, click_callback=self._on_button_clicked, button_style=ButtonStyle.TRANSPARENT_WHITE_BORDER) + + @staticmethod + def _on_button_clicked(): + gui_app.request_close() + if not PC: + HARDWARE.reboot() + def _render(self, rect: rl.Rectangle): scroll = self._scroll_panel.update(self._textarea_rect, self._content_rect) rl.begin_scissor_mode(int(self._textarea_rect.x), int(self._textarea_rect.y), int(self._textarea_rect.width), int(self._textarea_rect.height)) @@ -67,13 +76,7 @@ class TextWindow(Widget): rl.end_scissor_mode() button_bounds = rl.Rectangle(rect.width - MARGIN - BUTTON_SIZE.x - SPACING, rect.height - MARGIN - BUTTON_SIZE.y, BUTTON_SIZE.x, BUTTON_SIZE.y) - ret = gui_button(button_bounds, "Exit" if PC else "Reboot", button_style=ButtonStyle.TRANSPARENT) - if ret: - if PC: - gui_app.request_close() - else: - HARDWARE.reboot() - return ret + self._button.render(button_bounds) if __name__ == "__main__": diff --git a/system/ui/updater.py b/system/ui/updater.py index b3cdc82cf..48903fa5b 100755 --- a/system/ui/updater.py +++ b/system/ui/updater.py @@ -9,7 +9,7 @@ from openpilot.system.hardware import HARDWARE from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.lib.wifi_manager import WifiManager from openpilot.system.ui.widgets import Widget -from openpilot.system.ui.widgets.button import gui_button, ButtonStyle +from openpilot.system.ui.widgets.button import Button, ButtonStyle from openpilot.system.ui.widgets.label import gui_text_box, gui_label from openpilot.system.ui.widgets.network import WifiManagerUI @@ -45,8 +45,17 @@ class Updater(Widget): self.update_thread = None self.wifi_manager_ui = WifiManagerUI(WifiManager()) + # Buttons + self._wifi_button = Button("Connect to Wi-Fi", click_callback=lambda: self.set_current_screen(Screen.WIFI)) + self._install_button = Button("Install", click_callback=self.install_update, button_style=ButtonStyle.PRIMARY) + self._back_button = Button("Back", click_callback=lambda: self.set_current_screen(Screen.PROMPT)) + self._reboot_button = Button("Reboot", click_callback=lambda: HARDWARE.reboot()) + + def set_current_screen(self, screen: Screen): + self.current_screen = screen + def install_update(self): - self.current_screen = Screen.PROGRESS + self.set_current_screen(Screen.PROGRESS) self.progress_value = 0 self.progress_text = "Downloading..." self.show_reboot_button = False @@ -96,15 +105,11 @@ class Updater(Widget): # WiFi button wifi_button_rect = rl.Rectangle(MARGIN, button_y, button_width, BUTTON_HEIGHT) - if gui_button(wifi_button_rect, "Connect to Wi-Fi"): - self.current_screen = Screen.WIFI - return # Return to avoid processing other buttons after screen change + self._wifi_button.render(wifi_button_rect) # Install button install_button_rect = rl.Rectangle(MARGIN * 2 + button_width, button_y, button_width, BUTTON_HEIGHT) - if gui_button(install_button_rect, "Install", button_style=ButtonStyle.PRIMARY): - self.install_update() - return # Return to avoid further processing after action + self._install_button.render(install_button_rect) def render_wifi_screen(self, rect: rl.Rectangle): # Draw the Wi-Fi manager UI @@ -112,9 +117,7 @@ class Updater(Widget): self.wifi_manager_ui.render(wifi_rect) back_button_rect = rl.Rectangle(MARGIN, rect.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 + self._back_button.render(back_button_rect) def render_progress_screen(self, rect: rl.Rectangle): title_rect = rl.Rectangle(MARGIN + 100, 330, rect.width - MARGIN * 2 - 200, 100) @@ -133,10 +136,7 @@ class Updater(Widget): # Show reboot button if needed if self.show_reboot_button: reboot_rect = rl.Rectangle(MARGIN + 100, rect.height - MARGIN - BUTTON_HEIGHT, BUTTON_WIDTH, BUTTON_HEIGHT) - if gui_button(reboot_rect, "Reboot"): - # Return True to signal main loop to exit before rebooting - HARDWARE.reboot() - return + self._reboot_button.render(reboot_rect) def _render(self, rect: rl.Rectangle): if self.current_screen == Screen.PROMPT: diff --git a/system/ui/widgets/button.py b/system/ui/widgets/button.py index 32d63952a..141f682db 100644 --- a/system/ui/widgets/button.py +++ b/system/ui/widgets/button.py @@ -3,8 +3,7 @@ from enum import IntEnum import pyray as rl -from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos -from openpilot.system.ui.lib.text_measure import measure_text_cached +from openpilot.system.ui.lib.application import FontWeight, MousePos from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.label import TextAlignment, Label @@ -14,7 +13,8 @@ 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 - TRANSPARENT_WHITE_TEXT = 3 # For buttons with transparent background and border and white text + TRANSPARENT_WHITE_TEXT = 9 # For buttons with transparent background and border and white text + TRANSPARENT_WHITE_BORDER = 10 # For buttons with transparent background and white border and text ACTION = 4 LIST_ACTION = 5 # For list items with action buttons NO_EFFECT = 6 @@ -32,6 +32,7 @@ BUTTON_TEXT_COLOR = { ButtonStyle.DANGER: rl.Color(228, 228, 228, 255), ButtonStyle.TRANSPARENT: rl.BLACK, ButtonStyle.TRANSPARENT_WHITE_TEXT: rl.WHITE, + ButtonStyle.TRANSPARENT_WHITE_BORDER: rl.Color(228, 228, 228, 255), ButtonStyle.ACTION: rl.BLACK, ButtonStyle.LIST_ACTION: rl.Color(228, 228, 228, 255), ButtonStyle.NO_EFFECT: rl.Color(228, 228, 228, 255), @@ -49,6 +50,7 @@ BUTTON_BACKGROUND_COLORS = { ButtonStyle.DANGER: rl.Color(255, 36, 36, 255), ButtonStyle.TRANSPARENT: rl.BLACK, ButtonStyle.TRANSPARENT_WHITE_TEXT: rl.BLANK, + ButtonStyle.TRANSPARENT_WHITE_BORDER: rl.BLACK, ButtonStyle.ACTION: rl.Color(189, 189, 189, 255), ButtonStyle.LIST_ACTION: rl.Color(57, 57, 57, 255), ButtonStyle.NO_EFFECT: rl.Color(51, 51, 51, 255), @@ -62,6 +64,7 @@ BUTTON_PRESSED_BACKGROUND_COLORS = { ButtonStyle.DANGER: rl.Color(255, 36, 36, 255), ButtonStyle.TRANSPARENT: rl.BLACK, ButtonStyle.TRANSPARENT_WHITE_TEXT: rl.BLANK, + ButtonStyle.TRANSPARENT_WHITE_BORDER: rl.BLANK, ButtonStyle.ACTION: rl.Color(130, 130, 130, 255), ButtonStyle.LIST_ACTION: rl.Color(74, 74, 74, 74), ButtonStyle.NO_EFFECT: rl.Color(51, 51, 51, 255), @@ -73,104 +76,6 @@ BUTTON_DISABLED_BACKGROUND_COLORS = { ButtonStyle.TRANSPARENT_WHITE_TEXT: rl.BLANK, } -_pressed_buttons: set[str] = set() # Track mouse press state globally - - -# TODO: This should be a Widget class - -def gui_button( - rect: rl.Rectangle, - text: str, - font_size: int = DEFAULT_BUTTON_FONT_SIZE, - font_weight: FontWeight = FontWeight.MEDIUM, - 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] - 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] - - # 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) - if button_style != ButtonStyle.TRANSPARENT: - rl.draw_rectangle_rounded(rect, roundness, 20, bg_color) - else: - rl.draw_rectangle_rounded(rect, roundness, 20, rl.BLACK) - rl.draw_rectangle_rounded_lines_ex(rect, roundness, 20, 2, rl.WHITE) - - # Handle icon and text positioning - font = gui_app.font(font_weight) - text_size = measure_text_cached(font, text, font_size) - text_pos = rl.Vector2(0, rect.y + (rect.height - text_size.y) // 2) # Vertical centering - - # 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: - color = BUTTON_TEXT_COLOR[button_style] if is_enabled else BUTTON_DISABLED_TEXT_COLORS.get(button_style, rl.Color(228, 228, 228, 51)) - rl.draw_text_ex(font, text, text_pos, font_size, 0, color) - - return result - class Button(Widget): def __init__(self, @@ -182,7 +87,7 @@ class Button(Widget): border_radius: int = 10, text_alignment: TextAlignment = TextAlignment.CENTER, text_padding: int = 20, - icon = None, + icon=None, multi_touch: bool = False, ): @@ -200,6 +105,11 @@ class Button(Widget): def set_text(self, text): self._label.set_text(text) + def set_button_style(self, button_style: ButtonStyle): + self._button_style = button_style + self._background_color = BUTTON_BACKGROUND_COLORS[self._button_style] + self._label.set_text_color(BUTTON_TEXT_COLOR[self._button_style]) + def _update_state(self): if self.enabled: self._label.set_text_color(BUTTON_TEXT_COLOR[self._button_style]) @@ -213,7 +123,11 @@ class Button(Widget): def _render(self, _): roundness = self._border_radius / (min(self._rect.width, self._rect.height) / 2) - rl.draw_rectangle_rounded(self._rect, roundness, 10, self._background_color) + if self._button_style == ButtonStyle.TRANSPARENT_WHITE_BORDER: + rl.draw_rectangle_rounded(self._rect, roundness, 10, rl.BLACK) + rl.draw_rectangle_rounded_lines_ex(self._rect, roundness, 10, 2, rl.WHITE) + else: + rl.draw_rectangle_rounded(self._rect, roundness, 10, self._background_color) self._label.render(self._rect) diff --git a/system/ui/widgets/html_render.py b/system/ui/widgets/html_render.py index bb7eeb7c5..b87022785 100644 --- a/system/ui/widgets/html_render.py +++ b/system/ui/widgets/html_render.py @@ -6,8 +6,8 @@ from typing import Any from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel from openpilot.system.ui.lib.wrap_text import wrap_text -from openpilot.system.ui.widgets import Widget, DialogResult -from openpilot.system.ui.widgets.button import gui_button, ButtonStyle +from openpilot.system.ui.widgets import Widget +from openpilot.system.ui.widgets.button import Button, ButtonStyle class ElementType(Enum): @@ -40,6 +40,7 @@ class HtmlRenderer(Widget): self._normal_font = gui_app.font(FontWeight.NORMAL) self._bold_font = gui_app.font(FontWeight.BOLD) self._scroll_panel = GuiScrollPanel() + self._ok_button = Button("OK", click_callback=lambda: gui_app.set_modal_overlay(None), button_style=ButtonStyle.PRIMARY) self.styles: dict[ElementType, dict[str, Any]] = { ElementType.H1: {"size": 68, "weight": FontWeight.BOLD, "color": rl.BLACK, "margin_top": 20, "margin_bottom": 16}, @@ -126,10 +127,9 @@ class HtmlRenderer(Widget): button_x = content_rect.x + content_rect.width - button_width button_y = content_rect.y + content_rect.height - button_height button_rect = rl.Rectangle(button_x, button_y, button_width, button_height) - if gui_button(button_rect, "OK", button_style=ButtonStyle.PRIMARY) == 1: - return DialogResult.CONFIRM + self._ok_button.render(button_rect) - return DialogResult.NO_ACTION + return -1 def _render_content(self, rect: rl.Rectangle, scroll_offset: float = 0) -> float: current_y = rect.y + scroll_offset diff --git a/system/ui/widgets/list_view.py b/system/ui/widgets/list_view.py index 648ce47a6..f9cdf7f2b 100644 --- a/system/ui/widgets/list_view.py +++ b/system/ui/widgets/list_view.py @@ -6,7 +6,7 @@ from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.lib.wrap_text import wrap_text from openpilot.system.ui.widgets import Widget -from openpilot.system.ui.widgets.button import Button, gui_button, ButtonStyle +from openpilot.system.ui.widgets.button import Button, ButtonStyle from openpilot.system.ui.widgets.toggle import Toggle, WIDTH as TOGGLE_WIDTH, HEIGHT as TOGGLE_HEIGHT ITEM_BASE_WIDTH = 600 @@ -149,9 +149,16 @@ class DualButtonAction(ItemAction): right_callback: Callable = None, enabled: bool | Callable[[], bool] = True): super().__init__(width=0, enabled=enabled) # Width 0 means use full width self.left_text, self.right_text = left_text, right_text - self.left_callback, self.right_callback = left_callback, right_callback - def _render(self, rect: rl.Rectangle) -> bool: + self.left_button = Button(left_text, click_callback=left_callback, button_style=ButtonStyle.LIST_ACTION) + self.right_button = Button(right_text, click_callback=right_callback, button_style=ButtonStyle.DANGER) + + def set_touch_valid_callback(self, touch_callback: Callable[[], bool]) -> None: + super().set_touch_valid_callback(touch_callback) + self.left_button.set_touch_valid_callback(touch_callback) + self.right_button.set_touch_valid_callback(touch_callback) + + def _render(self, rect: rl.Rectangle): button_spacing = 30 button_height = 120 button_width = (rect.width - button_spacing) / 2 @@ -160,16 +167,9 @@ class DualButtonAction(ItemAction): left_rect = rl.Rectangle(rect.x, button_y, button_width, button_height) right_rect = rl.Rectangle(rect.x + button_width + button_spacing, button_y, button_width, button_height) - left_clicked = gui_button(left_rect, self.left_text, button_style=ButtonStyle.LIST_ACTION) == 1 - right_clicked = gui_button(right_rect, self.right_text, button_style=ButtonStyle.DANGER) == 1 - - if left_clicked and self.left_callback: - self.left_callback() - return True - if right_clicked and self.right_callback: - self.right_callback() - return True - return False + # Render buttons + self.left_button.render(left_rect) + self.right_button.render(right_rect) class MultipleButtonAction(ItemAction): diff --git a/system/ui/widgets/option_dialog.py b/system/ui/widgets/option_dialog.py index cbab024f0..604cd59fd 100644 --- a/system/ui/widgets/option_dialog.py +++ b/system/ui/widgets/option_dialog.py @@ -1,9 +1,9 @@ import pyray as rl -from openpilot.system.ui.lib.application import FontWeight -from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel +from openpilot.system.ui.lib.application import FontWeight, gui_app from openpilot.system.ui.widgets import Widget -from openpilot.system.ui.widgets.button import gui_button, ButtonStyle, TextAlignment +from openpilot.system.ui.widgets.button import Button, ButtonStyle, TextAlignment from openpilot.system.ui.widgets.label import gui_label +from openpilot.system.ui.widgets.scroller import Scroller # Constants MARGIN = 50 @@ -22,7 +22,17 @@ class MultiOptionDialog(Widget): self.options = options self.current = current self.selection = current - self.scroll = GuiScrollPanel() + + # Create scroller with option buttons + self.option_buttons = [Button(option, click_callback=lambda opt=option: self._on_option_clicked(opt), + text_alignment=TextAlignment.LEFT, button_style=ButtonStyle.NORMAL) for option in options] + self.scroller = Scroller(self.option_buttons, spacing=LIST_ITEM_SPACING) + + self.cancel_button = Button("Cancel", click_callback=lambda: gui_app.set_modal_overlay(None)) + self.select_button = Button("Select", click_callback=lambda: gui_app.set_modal_overlay(None), button_style=ButtonStyle.PRIMARY) + + def _on_option_clicked(self, option): + self.selection = option def _render(self, rect): dialog_rect = rl.Rectangle(rect.x + MARGIN, rect.y + MARGIN, rect.width - 2 * MARGIN, rect.height - 2 * MARGIN) @@ -36,36 +46,26 @@ class MultiOptionDialog(Widget): # Options area options_y = content_rect.y + TITLE_FONT_SIZE + ITEM_SPACING options_h = content_rect.height - TITLE_FONT_SIZE - BUTTON_HEIGHT - 2 * ITEM_SPACING - view_rect = rl.Rectangle(content_rect.x, options_y, content_rect.width, options_h) - content_h = len(self.options) * (ITEM_HEIGHT + LIST_ITEM_SPACING) - list_content_rect = rl.Rectangle(content_rect.x, options_y, content_rect.width, content_h) + options_rect = rl.Rectangle(content_rect.x, options_y, content_rect.width, options_h) - # Scroll and render options - offset = self.scroll.update(view_rect, list_content_rect) - valid_click = self.scroll.is_touch_valid() and rl.is_mouse_button_released(rl.MouseButton.MOUSE_BUTTON_LEFT) - - rl.begin_scissor_mode(int(view_rect.x), int(options_y), int(view_rect.width), int(options_h)) + # Update button styles and set width based on selection for i, option in enumerate(self.options): - item_y = options_y + i * (ITEM_HEIGHT + LIST_ITEM_SPACING) + offset - item_rect = rl.Rectangle(view_rect.x, item_y, view_rect.width, ITEM_HEIGHT) + selected = option == self.selection + button = self.option_buttons[i] + button.set_button_style(ButtonStyle.PRIMARY if selected else ButtonStyle.NORMAL) + button.set_rect(rl.Rectangle(0, 0, options_rect.width, ITEM_HEIGHT)) - if rl.check_collision_recs(item_rect, view_rect): - selected = option == self.selection - style = ButtonStyle.PRIMARY if selected else ButtonStyle.NORMAL - - if gui_button(item_rect, option, button_style=style, text_alignment=TextAlignment.LEFT) and valid_click: - self.selection = option - rl.end_scissor_mode() + self.scroller.render(options_rect) # Buttons button_y = content_rect.y + content_rect.height - BUTTON_HEIGHT button_w = (content_rect.width - BUTTON_SPACING) / 2 - if gui_button(rl.Rectangle(content_rect.x, button_y, button_w, BUTTON_HEIGHT), "Cancel"): - return 0 + cancel_rect = rl.Rectangle(content_rect.x, button_y, button_w, BUTTON_HEIGHT) + self.cancel_button.render(cancel_rect) - if gui_button(rl.Rectangle(content_rect.x + button_w + BUTTON_SPACING, button_y, button_w, BUTTON_HEIGHT), - "Select", is_enabled=self.selection != self.current, button_style=ButtonStyle.PRIMARY): - return 1 + select_rect = rl.Rectangle(content_rect.x + button_w + BUTTON_SPACING, button_y, button_w, BUTTON_HEIGHT) + self.select_button.set_enabled(self.selection != self.current) + self.select_button.render(select_rect) return -1 From 29a6f0504a7afce2f039a51f3da49ed4cbfffb7f Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 1 Oct 2025 00:46:51 -0700 Subject: [PATCH 078/910] raylib: fix WiFi in setup and updater (#36232) move back to more base class --- system/ui/widgets/network.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/system/ui/widgets/network.py b/system/ui/widgets/network.py index 9eaac6030..5dfdff9b9 100644 --- a/system/ui/widgets/network.py +++ b/system/ui/widgets/network.py @@ -77,9 +77,6 @@ class NetworkUI(Widget): self._nav_button = NavButton("Advanced") self._nav_button.set_click_callback(self._cycle_panel) - def _update_state(self): - self._wifi_manager.process_callbacks() - def show_event(self): self._set_current_panel(PanelType.WIFI) self._wifi_panel.show_event() @@ -305,6 +302,9 @@ class WifiManagerUI(Widget): for icon in STRENGTH_ICONS + ["icons/checkmark.png", "icons/circled_slash.png", "icons/lock_closed.png"]: gui_app.texture(icon, ICON_SIZE, ICON_SIZE) + def _update_state(self): + self._wifi_manager.process_callbacks() + def _render(self, rect: rl.Rectangle): if not self._networks: gui_label(rect, "Scanning Wi-Fi networks...", 72, alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER) From b8ae62a0b1b93c134058421fe1ed347db2eff55d Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 1 Oct 2025 01:02:35 -0700 Subject: [PATCH 079/910] raylib scroll panel: check bounds (#36233) check in bounds rect for scroll panel!! --- system/ui/lib/scroll_panel.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/system/ui/lib/scroll_panel.py b/system/ui/lib/scroll_panel.py index 5dacae821..d0d59df1f 100644 --- a/system/ui/lib/scroll_panel.py +++ b/system/ui/lib/scroll_panel.py @@ -77,20 +77,21 @@ class GuiScrollPanel: def _handle_mouse_event(self, mouse_event: MouseEvent, bounds: rl.Rectangle, content: rl.Rectangle): if self._scroll_state == ScrollState.IDLE: - if mouse_event.left_pressed: - self._start_mouse_y = mouse_event.pos.y - # Interrupt scrolling with new drag - # TODO: stop scrolling with any tap, need to fix is_touch_valid - if abs(self._velocity_filter_y.x) > MIN_VELOCITY_FOR_CLICKING: - self._scroll_state = ScrollState.DRAGGING_CONTENT - # Start velocity at initial measurement for more immediate response - self._velocity_filter_y.initialized = False + if rl.check_collision_point_rec(mouse_event.pos, bounds): + if mouse_event.left_pressed: + self._start_mouse_y = mouse_event.pos.y + # Interrupt scrolling with new drag + # TODO: stop scrolling with any tap, need to fix is_touch_valid + if abs(self._velocity_filter_y.x) > MIN_VELOCITY_FOR_CLICKING: + self._scroll_state = ScrollState.DRAGGING_CONTENT + # Start velocity at initial measurement for more immediate response + self._velocity_filter_y.initialized = False - if mouse_event.left_down: - if abs(mouse_event.pos.y - self._start_mouse_y) > DRAG_THRESHOLD: - self._scroll_state = ScrollState.DRAGGING_CONTENT - # Start velocity at initial measurement for more immediate response - self._velocity_filter_y.initialized = False + if mouse_event.left_down: + if abs(mouse_event.pos.y - self._start_mouse_y) > DRAG_THRESHOLD: + self._scroll_state = ScrollState.DRAGGING_CONTENT + # Start velocity at initial measurement for more immediate response + self._velocity_filter_y.initialized = False elif self._scroll_state == ScrollState.DRAGGING_CONTENT: if mouse_event.left_released: From 49570c11c6fc565b0ca89552a07ed16543233a0d Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 2 Oct 2025 02:04:09 -0700 Subject: [PATCH 080/910] Remove animation from networking --- system/ui/widgets/network.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/system/ui/widgets/network.py b/system/ui/widgets/network.py index 5dfdff9b9..35ceef1ff 100644 --- a/system/ui/widgets/network.py +++ b/system/ui/widgets/network.py @@ -3,7 +3,6 @@ from functools import partial from typing import cast import pyray as rl -from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.params import Params from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel @@ -50,16 +49,6 @@ class NavButton(Widget): super().__init__() self.text = text self.set_rect(rl.Rectangle(0, 0, 400, 100)) - self._x_pos_filter = FirstOrderFilter(0.0, 0.05, 1 / gui_app.target_fps, initialized=False) - self._y_pos_filter = FirstOrderFilter(0.0, 0.05, 1 / gui_app.target_fps, initialized=False) - - def set_position(self, x: float, y: float) -> None: - x = self._x_pos_filter.update(x) - y = self._y_pos_filter.update(y) - changed = (self._rect.x != x or self._rect.y != y) - self._rect.x, self._rect.y = x, y - if changed: - self._update_layout_rects() def _render(self, _): color = rl.Color(74, 74, 74, 255) if self.is_pressed else rl.Color(57, 57, 57, 255) From 3fd352a7eff8b5b95e423da717f56987f0252e0f Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 2 Oct 2025 03:57:10 -0700 Subject: [PATCH 081/910] raylib: updater UI (#36235) * auto attempt * gpt5 * Revert "gpt5" This reverts commit 556d6d9ee4d53aca0f4612023db6cfb2bed7ce29. * clean up * fixes * use raylib * fixes * debug * test update * more * rm * add value to button like qt * bump * bump * fixes * bump * fix * bump * clean up * time ago like qt rm * bump * clean up * updated can fail to respond on boot leading to stuck state * fix color fix * bump * bump * add back * test update * no unknown just '' * ffix --- selfdrive/ui/layouts/settings/software.py | 141 ++++++++++++++++++++-- system/manager/process_config.py | 4 +- system/ui/widgets/list_view.py | 26 +++- 3 files changed, 158 insertions(+), 13 deletions(-) diff --git a/selfdrive/ui/layouts/settings/software.py b/selfdrive/ui/layouts/settings/software.py index 034907001..7440512a6 100644 --- a/selfdrive/ui/layouts/settings/software.py +++ b/selfdrive/ui/layouts/settings/software.py @@ -1,24 +1,69 @@ -from openpilot.common.params import Params +import os +import time +import datetime +from openpilot.common.time_helpers import system_time_valid +from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.widgets import Widget, DialogResult from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog -from openpilot.system.ui.widgets.list_view import button_item, text_item +from openpilot.system.ui.widgets.list_view import button_item, text_item, ListItem from openpilot.system.ui.widgets.scroller import Scroller +# TODO: remove this. updater fails to respond on startup if time is not correct +UPDATED_TIMEOUT = 10 # seconds to wait for updated to respond + + +def time_ago(date: datetime.datetime | None) -> str: + if not date: + return "never" + + if not system_time_valid(): + return date.strftime("%a %b %d %Y") + + now = datetime.datetime.now(datetime.UTC) + if date.tzinfo is None: + date = date.replace(tzinfo=datetime.UTC) + + diff_seconds = int((now - date).total_seconds()) + if diff_seconds < 60: + return "now" + if diff_seconds < 3600: + m = diff_seconds // 60 + return f"{m} minute{'s' if m != 1 else ''} ago" + if diff_seconds < 86400: + h = diff_seconds // 3600 + return f"{h} hour{'s' if h != 1 else ''} ago" + if diff_seconds < 604800: + d = diff_seconds // 86400 + return f"{d} day{'s' if d != 1 else ''} ago" + return date.strftime("%a %b %d %Y") + class SoftwareLayout(Widget): def __init__(self): super().__init__() - self._params = Params() + self._onroad_label = ListItem(title="Updates are only downloaded while the car is off.") + self._version_item = text_item("Current Version", ui_state.params.get("UpdaterCurrentDescription") or "") + self._download_btn = button_item("Download", "CHECK", callback=self._on_download_update) + + # Install button is initially hidden + self._install_btn = button_item("Install Update", "INSTALL", callback=self._on_install_update) + self._install_btn.set_visible(False) + + # Track waiting-for-updater transition to avoid brief re-enable while still idle + self._waiting_for_updater = False + self._waiting_start_ts: float = 0.0 + items = self._init_items() self._scroller = Scroller(items, line_separator=True, spacing=0) def _init_items(self): items = [ - text_item("Current Version", ""), - button_item("Download", "CHECK", callback=self._on_download_update), - button_item("Install Update", "INSTALL", callback=self._on_install_update), + self._onroad_label, + self._version_item, + self._download_btn, + self._install_btn, button_item("Target Branch", "SELECT", callback=self._on_select_branch), button_item("Uninstall", "UNINSTALL", callback=self._on_uninstall), ] @@ -27,14 +72,90 @@ class SoftwareLayout(Widget): def _render(self, rect): self._scroller.render(rect) - def _on_download_update(self): pass - def _on_install_update(self): pass - def _on_select_branch(self): pass + def _update_state(self): + # Show/hide onroad warning + self._onroad_label.set_visible(ui_state.is_onroad()) + + # Update current version and release notes + current_desc = ui_state.params.get("UpdaterCurrentDescription") or "" + current_release_notes = (ui_state.params.get("UpdaterCurrentReleaseNotes") or b"").decode("utf-8", "replace") + self._version_item.action_item.set_text(current_desc) + self._version_item.description = current_release_notes + + # Update download button visibility and state + self._download_btn.set_visible(ui_state.is_offroad()) + + updater_state = ui_state.params.get("UpdaterState") or "idle" + failed_count = ui_state.params.get("UpdateFailedCount") or 0 + fetch_available = ui_state.params.get_bool("UpdaterFetchAvailable") + update_available = ui_state.params.get_bool("UpdateAvailable") + + if updater_state != "idle": + # Updater responded + self._waiting_for_updater = False + self._download_btn.action_item.set_enabled(False) + self._download_btn.action_item.set_value(updater_state) + else: + if failed_count > 0: + self._download_btn.action_item.set_value("failed to check for update") + self._download_btn.action_item.set_text("CHECK") + elif fetch_available: + self._download_btn.action_item.set_value("update available") + self._download_btn.action_item.set_text("DOWNLOAD") + else: + last_update = ui_state.params.get("LastUpdateTime") + if last_update: + formatted = time_ago(last_update) + self._download_btn.action_item.set_value(f"up to date, last checked {formatted}") + else: + self._download_btn.action_item.set_value("up to date, last checked never") + self._download_btn.action_item.set_text("CHECK") + + # If we've been waiting too long without a state change, reset state + if self._waiting_for_updater and (time.monotonic() - self._waiting_start_ts > UPDATED_TIMEOUT): + self._waiting_for_updater = False + + # Only enable if we're not waiting for updater to flip out of idle + self._download_btn.action_item.set_enabled(not self._waiting_for_updater) + + # Update install button + self._install_btn.set_visible(ui_state.is_offroad() and update_available) + if update_available: + new_desc = ui_state.params.get("UpdaterNewDescription") or "" + new_release_notes = (ui_state.params.get("UpdaterNewReleaseNotes") or b"").decode("utf-8", "replace") + self._install_btn.action_item.set_text("INSTALL") + self._install_btn.action_item.set_value(new_desc) + self._install_btn.description = new_release_notes + # Enable install button for testing (like Qt showEvent) + self._install_btn.action_item.set_enabled(True) + else: + self._install_btn.set_visible(False) + + def _on_download_update(self): + # Check if we should start checking or start downloading + self._download_btn.action_item.set_enabled(False) + if self._download_btn.action_item.text == "CHECK": + # Start checking for updates + self._waiting_for_updater = True + self._waiting_start_ts = time.monotonic() + os.system("pkill -SIGUSR1 -f system.updated.updated") + else: + # Start downloading + self._waiting_for_updater = True + self._waiting_start_ts = time.monotonic() + os.system("pkill -SIGHUP -f system.updated.updated") def _on_uninstall(self): def handle_uninstall_confirmation(result): if result == DialogResult.CONFIRM: - self._params.put_bool("DoUninstall", True) + ui_state.params.put_bool("DoUninstall", True) dialog = ConfirmDialog("Are you sure you want to uninstall?", "Uninstall") gui_app.set_modal_overlay(dialog, callback=handle_uninstall_confirmation) + + def _on_install_update(self): + # Trigger reboot to install update + self._install_btn.action_item.set_enabled(False) + ui_state.params.put_bool("DoReboot", True) + + def _on_select_branch(self): pass diff --git a/system/manager/process_config.py b/system/manager/process_config.py index 22f159e89..55e2812ef 100644 --- a/system/manager/process_config.py +++ b/system/manager/process_config.py @@ -80,8 +80,8 @@ procs = [ PythonProcess("dmonitoringmodeld", "selfdrive.modeld.dmonitoringmodeld", driverview, enabled=(WEBCAM or not PC)), PythonProcess("sensord", "system.sensord.sensord", only_onroad, enabled=not PC), - NativeProcess("ui", "selfdrive/ui", ["./ui"], always_run, watchdog_max_dt=(5 if not PC else None)), - PythonProcess("raylib_ui", "selfdrive.ui.ui", always_run, enabled=False, watchdog_max_dt=(5 if not PC else None)), + NativeProcess("ui", "selfdrive/ui", ["./ui"], always_run, enabled=False, watchdog_max_dt=(5 if not PC else None)), + PythonProcess("raylib_ui", "selfdrive.ui.ui", always_run, watchdog_max_dt=(5 if not PC else None)), PythonProcess("soundd", "selfdrive.ui.soundd", only_onroad), PythonProcess("locationd", "selfdrive.locationd.locationd", only_onroad), NativeProcess("_pandad", "selfdrive/pandad", ["./pandad"], always_run, enabled=False), diff --git a/system/ui/widgets/list_view.py b/system/ui/widgets/list_view.py index f9cdf7f2b..c9ccff821 100644 --- a/system/ui/widgets/list_view.py +++ b/system/ui/widgets/list_view.py @@ -14,6 +14,7 @@ ITEM_BASE_HEIGHT = 170 ITEM_PADDING = 20 ITEM_TEXT_FONT_SIZE = 50 ITEM_TEXT_COLOR = rl.WHITE +ITEM_TEXT_VALUE_COLOR = rl.Color(170, 170, 170, 255) ITEM_DESC_TEXT_COLOR = rl.Color(128, 128, 128, 255) ITEM_DESC_FONT_SIZE = 40 ITEM_DESC_V_OFFSET = 140 @@ -77,7 +78,9 @@ class ButtonAction(ItemAction): def __init__(self, text: str | Callable[[], str], width: int = BUTTON_WIDTH, enabled: bool | Callable[[], bool] = True): super().__init__(width, enabled) self._text_source = text + self._value_source: str | Callable[[], str] | None = None self._pressed = False + self._font = gui_app.font(FontWeight.NORMAL) def pressed(): self._pressed = True @@ -96,16 +99,34 @@ class ButtonAction(ItemAction): super().set_touch_valid_callback(touch_callback) self._button.set_touch_valid_callback(touch_callback) + def set_text(self, text: str | Callable[[], str]): + self._text_source = text + + def set_value(self, value: str | Callable[[], str]): + self._value_source = value + @property def text(self): return _resolve_value(self._text_source, "Error") + @property + def value(self): + return _resolve_value(self._value_source, "") + def _render(self, rect: rl.Rectangle) -> bool: self._button.set_text(self.text) self._button.set_enabled(_resolve_value(self.enabled)) button_rect = rl.Rectangle(rect.x, rect.y + (rect.height - BUTTON_HEIGHT) / 2, BUTTON_WIDTH, BUTTON_HEIGHT) self._button.render(button_rect) + value_text = self.value + if value_text: + spacing = 20 + text_size = measure_text_cached(self._font, value_text, ITEM_TEXT_FONT_SIZE) + text_x = button_rect.x - spacing - text_size.x + text_y = rect.y + (rect.height - text_size.y) / 2 + rl.draw_text_ex(self._font, value_text, rl.Vector2(text_x, text_y), ITEM_TEXT_FONT_SIZE, 0, ITEM_TEXT_VALUE_COLOR) + # TODO: just use the generic Widget click callbacks everywhere, no returning from render pressed = self._pressed self._pressed = False @@ -139,6 +160,9 @@ class TextAction(ItemAction): rl.draw_text_ex(self._font, current_text, rl.Vector2(text_x, text_y), ITEM_TEXT_FONT_SIZE, 0, self.color) return False + def set_text(self, text: str | Callable[[], str]): + self._text_source = text + def get_width(self) -> int: text_width = measure_text_cached(self._font, self.text, ITEM_TEXT_FONT_SIZE).x return int(text_width + TEXT_PADDING) @@ -382,7 +406,7 @@ def button_item(title: str, button_text: str | Callable[[], str], description: s def text_item(title: str, value: str | Callable[[], str], description: str | Callable[[], str] | None = None, callback: Callable | None = None, enabled: bool | Callable[[], bool] = True) -> ListItem: - action = TextAction(text=value, color=rl.Color(170, 170, 170, 255), enabled=enabled) + action = TextAction(text=value, color=ITEM_TEXT_VALUE_COLOR, enabled=enabled) return ListItem(title=title, description=description, action_item=action, callback=callback) From ec7e3192bbc3e8766ed1775ed224acb3684e109c Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 2 Oct 2025 04:00:09 -0700 Subject: [PATCH 082/910] revert that --- system/manager/process_config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system/manager/process_config.py b/system/manager/process_config.py index 55e2812ef..22f159e89 100644 --- a/system/manager/process_config.py +++ b/system/manager/process_config.py @@ -80,8 +80,8 @@ procs = [ PythonProcess("dmonitoringmodeld", "selfdrive.modeld.dmonitoringmodeld", driverview, enabled=(WEBCAM or not PC)), PythonProcess("sensord", "system.sensord.sensord", only_onroad, enabled=not PC), - NativeProcess("ui", "selfdrive/ui", ["./ui"], always_run, enabled=False, watchdog_max_dt=(5 if not PC else None)), - PythonProcess("raylib_ui", "selfdrive.ui.ui", always_run, watchdog_max_dt=(5 if not PC else None)), + NativeProcess("ui", "selfdrive/ui", ["./ui"], always_run, watchdog_max_dt=(5 if not PC else None)), + PythonProcess("raylib_ui", "selfdrive.ui.ui", always_run, enabled=False, watchdog_max_dt=(5 if not PC else None)), PythonProcess("soundd", "selfdrive.ui.soundd", only_onroad), PythonProcess("locationd", "selfdrive.locationd.locationd", only_onroad), NativeProcess("_pandad", "selfdrive/pandad", ["./pandad"], always_run, enabled=False), From cc52f980b35131b2eec95bad3937ca5711853433 Mon Sep 17 00:00:00 2001 From: Armand du Parc Locmaria Date: Thu, 2 Oct 2025 15:31:39 -0700 Subject: [PATCH 083/910] Dockerfile.openpilot uv run scons (#36236) * Dockerfile.openpilot_base use UV_PROJECT_ENVIRONMENT * Revert "Dockerfile.openpilot_base use UV_PROJECT_ENVIRONMENT" This reverts commit 3725e54ce0727077ca4347d24ca38e25d5864d47. * Reapply "Dockerfile.openpilot_base use UV_PROJECT_ENVIRONMENT" This reverts commit 11b04f57acb9c81fcc5a22a6a6d78d666c59ca6c. * use uv run to pick up correct ppath --- Dockerfile.openpilot | 4 +++- Dockerfile.openpilot_base | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Dockerfile.openpilot b/Dockerfile.openpilot index d85be7712..14c1e6477 100644 --- a/Dockerfile.openpilot +++ b/Dockerfile.openpilot @@ -9,4 +9,6 @@ WORKDIR ${OPENPILOT_PATH} COPY . ${OPENPILOT_PATH}/ -RUN scons --cache-readonly -j$(nproc) +ENV UV_BIN="/home/batman/.local/bin/" +ENV PATH="$UV_BIN:$PATH" +RUN uv run scons --cache-readonly -j$(nproc) diff --git a/Dockerfile.openpilot_base b/Dockerfile.openpilot_base index 44d8d95e9..e3bb03de0 100644 --- a/Dockerfile.openpilot_base +++ b/Dockerfile.openpilot_base @@ -71,8 +71,8 @@ USER $USER COPY --chown=$USER pyproject.toml uv.lock /home/$USER COPY --chown=$USER tools/install_python_dependencies.sh /home/$USER/tools/ -ENV VIRTUAL_ENV=/home/$USER/.venv -ENV PATH="$VIRTUAL_ENV/bin:$PATH" +ENV UV_PROJECT_ENVIRONMENT=/home/$USER/.venv +ENV PATH="$UV_PROJECT_ENVIRONMENT/bin:$PATH" RUN cd /home/$USER && \ tools/install_python_dependencies.sh && \ rm -rf tools/ pyproject.toml uv.lock .cache From 1ee798439ae650e832f1224a17cdfb657cecca23 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 2 Oct 2025 21:09:17 -0700 Subject: [PATCH 084/910] raylib: WiFi fixes (#36239) * proces in AN and WM * clean * ban api * fix * fiix * fix pairing dialog * cleanup * fix multi action button hard to click * fix * fix right margin of multi action * clean up --- pyproject.toml | 6 +++++- selfdrive/ui/widgets/pairing_dialog.py | 30 +++++++++++++++----------- system/ui/lib/application.py | 4 ++-- system/ui/widgets/list_view.py | 30 ++++++++++++-------------- system/ui/widgets/network.py | 2 ++ 5 files changed, 40 insertions(+), 32 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9c8a6148a..a0d135db6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -262,8 +262,12 @@ lint.flake8-implicit-str-concat.allow-multiline = false "tools".msg = "Use openpilot.tools" "pytest.main".msg = "pytest.main requires special handling that is easy to mess up!" "unittest".msg = "Use pytest" -"pyray.measure_text_ex".msg = "Use openpilot.system.ui.lib.text_measure" "time.time".msg = "Use time.monotonic" +# raylib banned APIs +"pyray.measure_text_ex".msg = "Use openpilot.system.ui.lib.text_measure" +"pyray.is_mouse_button_pressed".msg = "This can miss events. Use Widget._handle_mouse_press" +"pyray.is_mouse_button_released".msg = "This can miss events. Use Widget._handle_mouse_release" + [tool.ruff.format] quote-style = "preserve" diff --git a/selfdrive/ui/widgets/pairing_dialog.py b/selfdrive/ui/widgets/pairing_dialog.py index 55d53125d..7676635e1 100644 --- a/selfdrive/ui/widgets/pairing_dialog.py +++ b/selfdrive/ui/widgets/pairing_dialog.py @@ -13,6 +13,18 @@ from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.selfdrive.ui.ui_state import ui_state +class IconButton(Widget): + def __init__(self, texture: rl.Texture): + super().__init__() + self._texture = texture + + def _render(self, rect: rl.Rectangle): + color = rl.Color(180, 180, 180, 150) if self.is_pressed else rl.WHITE + draw_x = rect.x + (rect.width - self._texture.width) / 2 + draw_y = rect.y + (rect.height - self._texture.height) / 2 + rl.draw_texture(self._texture, int(draw_x), int(draw_y), color) + + class PairingDialog(Widget): """Dialog for device pairing with QR code.""" @@ -23,6 +35,8 @@ class PairingDialog(Widget): self.params = Params() self.qr_texture: rl.Texture | None = None self.last_qr_generation = 0 + self._close_btn = IconButton(gui_app.texture("icons/close.png", 80, 80)) + self._close_btn.set_click_callback(lambda: gui_app.set_modal_overlay(None)) def _get_pairing_url(self) -> str: try: @@ -78,19 +92,9 @@ class PairingDialog(Widget): # Close button close_size = 80 - close_icon = gui_app.texture("icons/close.png", close_size, close_size) - close_rect = rl.Rectangle(content_rect.x, y, close_size, close_size) - - mouse_pos = rl.get_mouse_position() - is_hover = rl.check_collision_point_rec(mouse_pos, close_rect) - is_pressed = rl.is_mouse_button_down(rl.MouseButton.MOUSE_BUTTON_LEFT) - is_released = rl.is_mouse_button_released(rl.MouseButton.MOUSE_BUTTON_LEFT) - - color = rl.Color(180, 180, 180, 150) if (is_hover and is_pressed) else rl.WHITE - rl.draw_texture(close_icon, int(content_rect.x), int(y), color) - - if (is_hover and is_released) or rl.is_key_pressed(rl.KeyboardKey.KEY_ESCAPE): - return 1 + pad = 20 + close_rect = rl.Rectangle(content_rect.x - pad, y - pad, close_size + pad * 2, close_size + pad * 2) + self._close_btn.render(close_rect) y += close_size + 40 diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index a16f0b0bd..6c703a516 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -108,8 +108,8 @@ class MouseState: ev = MouseEvent( MousePos(x, y), slot, - rl.is_mouse_button_pressed(slot), - rl.is_mouse_button_released(slot), + rl.is_mouse_button_pressed(slot), # noqa: TID251 + rl.is_mouse_button_released(slot), # noqa: TID251 rl.is_mouse_button_down(slot), time.monotonic(), ) diff --git a/system/ui/widgets/list_view.py b/system/ui/widgets/list_view.py index c9ccff821..787732549 100644 --- a/system/ui/widgets/list_view.py +++ b/system/ui/widgets/list_view.py @@ -198,17 +198,16 @@ class DualButtonAction(ItemAction): class MultipleButtonAction(ItemAction): def __init__(self, buttons: list[str], button_width: int, selected_index: int = 0, callback: Callable = None): - super().__init__(width=len(buttons) * (button_width + 20), enabled=True) + super().__init__(width=len(buttons) * button_width + (len(buttons) - 1) * RIGHT_ITEM_PADDING, enabled=True) self.buttons = buttons self.button_width = button_width self.selected_button = selected_index self.callback = callback self._font = gui_app.font(FontWeight.MEDIUM) - def _render(self, rect: rl.Rectangle) -> bool: - spacing = 20 + def _render(self, rect: rl.Rectangle): + spacing = RIGHT_ITEM_PADDING button_y = rect.y + (rect.height - BUTTON_HEIGHT) / 2 - clicked = -1 for i, text in enumerate(self.buttons): button_x = rect.x + i * (self.button_width + spacing) @@ -216,8 +215,7 @@ class MultipleButtonAction(ItemAction): # Check button state mouse_pos = rl.get_mouse_position() - is_hovered = rl.check_collision_point_rec(mouse_pos, button_rect) and self.enabled - is_pressed = is_hovered and rl.is_mouse_button_down(rl.MouseButton.MOUSE_BUTTON_LEFT) and self.is_pressed + is_pressed = rl.check_collision_point_rec(mouse_pos, button_rect) and self.enabled and self.is_pressed is_selected = i == self.selected_button # Button colors @@ -241,16 +239,16 @@ class MultipleButtonAction(ItemAction): text_color = rl.Color(228, 228, 228, 255) if self.enabled else rl.Color(150, 150, 150, 255) rl.draw_text_ex(self._font, text, rl.Vector2(text_x, text_y), 40, 0, text_color) - # Handle click - if is_hovered and rl.is_mouse_button_released(rl.MouseButton.MOUSE_BUTTON_LEFT) and self.is_pressed: - clicked = i - - if clicked >= 0: - self.selected_button = clicked - if self.callback: - self.callback(clicked) - return True - return False + def _handle_mouse_release(self, mouse_pos: MousePos): + spacing = RIGHT_ITEM_PADDING + button_y = self._rect.y + (self._rect.height - BUTTON_HEIGHT) / 2 + for i, _text in enumerate(self.buttons): + button_x = self._rect.x + i * (self.button_width + spacing) + button_rect = rl.Rectangle(button_x, button_y, self.button_width, BUTTON_HEIGHT) + if rl.check_collision_point_rec(mouse_pos, button_rect): + self.selected_button = i + if self.callback: + self.callback(i) class ListItem(Widget): diff --git a/system/ui/widgets/network.py b/system/ui/widgets/network.py index 35ceef1ff..8a7bd9f51 100644 --- a/system/ui/widgets/network.py +++ b/system/ui/widgets/network.py @@ -246,6 +246,8 @@ class AdvancedNetworkSettings(Widget): gui_app.set_modal_overlay(self._keyboard, update_password) def _update_state(self): + self._wifi_manager.process_callbacks() + # If not using prime SIM, show GSM settings and enable IPv4 forwarding show_cell_settings = ui_state.prime_state.get_type() in (PrimeType.NONE, PrimeType.LITE) self._wifi_manager.set_ipv4_forward(show_cell_settings) From 21fd3d03201fc7d9cb857fe068b3e51fb855c80d Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 2 Oct 2025 21:21:50 -0700 Subject: [PATCH 085/910] raylib: use extra text in offroad alerts (#36241) * replace properly * test --- selfdrive/ui/tests/test_ui/raylib_screenshots.py | 2 +- selfdrive/ui/widgets/offroad_alerts.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py index 7fb22e448..1ac5083fc 100755 --- a/selfdrive/ui/tests/test_ui/raylib_screenshots.py +++ b/selfdrive/ui/tests/test_ui/raylib_screenshots.py @@ -69,7 +69,7 @@ def setup_pair_device(click, pm: PubMaster): def setup_offroad_alert(click, pm: PubMaster): - set_offroad_alert("Offroad_TemperatureTooHigh", True, extra_text='99') + set_offroad_alert("Offroad_TemperatureTooHigh", True, extra_text='99C') for alert in OFFROAD_ALERTS: set_offroad_alert(alert, True) diff --git a/selfdrive/ui/widgets/offroad_alerts.py b/selfdrive/ui/widgets/offroad_alerts.py index 6ca0ca2c4..7ad96bc9a 100644 --- a/selfdrive/ui/widgets/offroad_alerts.py +++ b/selfdrive/ui/widgets/offroad_alerts.py @@ -190,7 +190,7 @@ class OffroadAlert(AbstractAlert): alert_json = self.params.get(alert_data.key) if alert_json: - text = alert_json.get("text", "").replace("{}", alert_json.get("extra", "")) + text = alert_json.get("text", "").replace("%1", alert_json.get("extra", "")) alert_data.text = text alert_data.visible = bool(text) From 75e52427d1da872f17002cdec3b5e7ce27b7acfc Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 2 Oct 2025 22:53:02 -0700 Subject: [PATCH 086/910] raylib: fix when we show offroad alerts and styles (#36240) * fix how we show alerts * test this too * match border radius * simplifty * keep * back * fix alert spacing * fix alert text padding * cmt * cmt --- selfdrive/ui/layouts/home.py | 21 +++++++----- .../ui/tests/test_ui/raylib_screenshots.py | 1 + selfdrive/ui/widgets/offroad_alerts.py | 33 +++++++++++-------- 3 files changed, 33 insertions(+), 22 deletions(-) diff --git a/selfdrive/ui/layouts/home.py b/selfdrive/ui/layouts/home.py index 320e7477f..c33b16fac 100644 --- a/selfdrive/ui/layouts/home.py +++ b/selfdrive/ui/layouts/home.py @@ -41,6 +41,8 @@ class HomeLayout(Widget): self.update_available = False self.alert_count = 0 + self._prev_update_available = False + self._prev_alerts_present = False self.header_rect = rl.Rectangle(0, 0, 0, 0) self.content_rect = rl.Rectangle(0, 0, 0, 0) @@ -185,20 +187,23 @@ class HomeLayout(Widget): def _refresh(self): # TODO: implement _update_state with a timer - self.update_available = self.update_alert.refresh() - self.alert_count = self.offroad_alert.refresh() - self._update_state_priority(self.update_available, self.alert_count > 0) - - def _update_state_priority(self, update_available: bool, alerts_present: bool): - current_state = self.current_state + update_available = self.update_alert.refresh() + alert_count = self.offroad_alert.refresh() + alerts_present = alert_count > 0 + # Show panels on transition from no alert/update to any alerts/update if not update_available and not alerts_present: self.current_state = HomeLayoutState.HOME - elif update_available and (current_state == HomeLayoutState.HOME or (not alerts_present and current_state == HomeLayoutState.ALERTS)): + elif update_available and ((not self._prev_update_available) or (not alerts_present and self.current_state == HomeLayoutState.ALERTS)): self.current_state = HomeLayoutState.UPDATE - elif alerts_present and (current_state == HomeLayoutState.HOME or (not update_available and current_state == HomeLayoutState.UPDATE)): + elif alerts_present and ((not self._prev_alerts_present) or (not update_available and self.current_state == HomeLayoutState.UPDATE)): self.current_state = HomeLayoutState.ALERTS + self.update_available = update_available + self.alert_count = alert_count + self._prev_update_available = update_available + self._prev_alerts_present = alerts_present + def _get_version_text(self) -> str: brand = "openpilot" description = self.params.get("UpdaterCurrentDescription") diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py index 1ac5083fc..81eef60d6 100755 --- a/selfdrive/ui/tests/test_ui/raylib_screenshots.py +++ b/selfdrive/ui/tests/test_ui/raylib_screenshots.py @@ -70,6 +70,7 @@ def setup_pair_device(click, pm: PubMaster): def setup_offroad_alert(click, pm: PubMaster): set_offroad_alert("Offroad_TemperatureTooHigh", True, extra_text='99C') + set_offroad_alert("Offroad_ExcessiveActuation", True, extra_text='longitudinal') for alert in OFFROAD_ALERTS: set_offroad_alert(alert, True) diff --git a/selfdrive/ui/widgets/offroad_alerts.py b/selfdrive/ui/widgets/offroad_alerts.py index 7ad96bc9a..586d90dc4 100644 --- a/selfdrive/ui/widgets/offroad_alerts.py +++ b/selfdrive/ui/widgets/offroad_alerts.py @@ -29,9 +29,10 @@ class AlertConstants: MARGIN = 50 SPACING = 30 FONT_SIZE = 48 - BORDER_RADIUS = 30 + BORDER_RADIUS = 30 * 2 # matches Qt's 30px ALERT_HEIGHT = 120 - ALERT_SPACING = 20 + ALERT_SPACING = 10 + ALERT_INSET = 60 @dataclass @@ -88,7 +89,7 @@ class AbstractAlert(Widget, ABC): HARDWARE.reboot() def _render(self, rect: rl.Rectangle): - rl.draw_rectangle_rounded(rect, AlertConstants.BORDER_RADIUS / rect.width, 10, AlertColors.BACKGROUND) + rl.draw_rectangle_rounded(rect, AlertConstants.BORDER_RADIUS / rect.height, 10, AlertColors.BACKGROUND) footer_height = AlertConstants.BUTTON_SIZE[1] + AlertConstants.SPACING content_height = rect.height - 2 * AlertConstants.MARGIN - footer_height @@ -138,7 +139,8 @@ class AbstractAlert(Widget, ABC): self.dismiss_btn_rect.x = rect.x + AlertConstants.MARGIN self.dismiss_btn_rect.y = footer_y - rl.draw_rectangle_rounded(self.dismiss_btn_rect, 0.3, 10, AlertColors.BUTTON) + roundness = AlertConstants.BORDER_RADIUS / self.dismiss_btn_rect.height + rl.draw_rectangle_rounded(self.dismiss_btn_rect, roundness, 10, AlertColors.BUTTON) text = "Close" text_width = measure_text_cached(font, text, AlertConstants.FONT_SIZE).x @@ -151,7 +153,8 @@ class AbstractAlert(Widget, ABC): if self.snooze_visible: self.snooze_btn_rect.x = rect.x + rect.width - AlertConstants.MARGIN - AlertConstants.SNOOZE_BUTTON_SIZE[0] self.snooze_btn_rect.y = footer_y - rl.draw_rectangle_rounded(self.snooze_btn_rect, 0.3, 10, AlertColors.SNOOZE_BG) + roundness = AlertConstants.BORDER_RADIUS / self.snooze_btn_rect.height + rl.draw_rectangle_rounded(self.snooze_btn_rect, roundness, 10, AlertColors.SNOOZE_BG) text = "Snooze Update" text_width = measure_text_cached(font, text, AlertConstants.FONT_SIZE).x @@ -162,7 +165,8 @@ class AbstractAlert(Widget, ABC): elif self.has_reboot_btn: self.reboot_btn_rect.x = rect.x + rect.width - AlertConstants.MARGIN - AlertConstants.REBOOT_BUTTON_SIZE[0] self.reboot_btn_rect.y = footer_y - rl.draw_rectangle_rounded(self.reboot_btn_rect, 0.3, 10, AlertColors.BUTTON) + roundness = AlertConstants.BORDER_RADIUS / self.reboot_btn_rect.height + rl.draw_rectangle_rounded(self.reboot_btn_rect, roundness, 10, AlertColors.BUTTON) text = "Reboot and Update" text_width = measure_text_cached(font, text, AlertConstants.FONT_SIZE).x @@ -215,11 +219,11 @@ class OffroadAlert(AbstractAlert): if not alert_data.visible: continue - text_width = int(self.content_rect.width - 90) + text_width = int(self.content_rect.width - (AlertConstants.ALERT_INSET * 2)) wrapped_lines = wrap_text(font, alert_data.text, AlertConstants.FONT_SIZE, text_width) line_count = len(wrapped_lines) text_height = line_count * (AlertConstants.FONT_SIZE + 5) - alert_item_height = max(text_height + 40, AlertConstants.ALERT_HEIGHT) + alert_item_height = max(text_height + (AlertConstants.ALERT_INSET * 2), AlertConstants.ALERT_HEIGHT) total_height += alert_item_height + AlertConstants.ALERT_SPACING if total_height > 20: @@ -235,7 +239,7 @@ class OffroadAlert(AbstractAlert): self.sorted_alerts.append(alert_data) def _render_content(self, content_rect: rl.Rectangle): - y_offset = 20 + y_offset = AlertConstants.ALERT_SPACING font = gui_app.font(FontWeight.NORMAL) for alert_data in self.sorted_alerts: @@ -243,11 +247,11 @@ class OffroadAlert(AbstractAlert): continue bg_color = AlertColors.HIGH_SEVERITY if alert_data.severity > 0 else AlertColors.LOW_SEVERITY - text_width = int(content_rect.width - 90) + text_width = int(content_rect.width - (AlertConstants.ALERT_INSET * 2)) wrapped_lines = wrap_text(font, alert_data.text, AlertConstants.FONT_SIZE, text_width) line_count = len(wrapped_lines) text_height = line_count * (AlertConstants.FONT_SIZE + 5) - alert_item_height = max(text_height + 40, AlertConstants.ALERT_HEIGHT) + alert_item_height = max(text_height + (AlertConstants.ALERT_INSET * 2), AlertConstants.ALERT_HEIGHT) alert_rect = rl.Rectangle( content_rect.x + 10, @@ -256,10 +260,11 @@ class OffroadAlert(AbstractAlert): alert_item_height, ) - rl.draw_rectangle_rounded(alert_rect, 0.2, 10, bg_color) + roundness = AlertConstants.BORDER_RADIUS / min(alert_rect.height, alert_rect.width) + rl.draw_rectangle_rounded(alert_rect, roundness, 10, bg_color) - text_x = alert_rect.x + 30 - text_y = alert_rect.y + 20 + text_x = alert_rect.x + AlertConstants.ALERT_INSET + text_y = alert_rect.y + AlertConstants.ALERT_INSET for i, line in enumerate(wrapped_lines): rl.draw_text_ex( From 21273c921e99a40f85b65e51b5b650327e18781b Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 2 Oct 2025 23:54:31 -0700 Subject: [PATCH 087/910] raylib: excessive actuation offroad alert (#36242) * excessive actuation check * from gpt * back * use buttons * use widgets for ultimate clean up - no ai slop * feature parity * revert * clean up --- selfdrive/ui/widgets/offroad_alerts.py | 151 ++++++++++++++----------- 1 file changed, 86 insertions(+), 65 deletions(-) diff --git a/selfdrive/ui/widgets/offroad_alerts.py b/selfdrive/ui/widgets/offroad_alerts.py index 586d90dc4..8ff8f27be 100644 --- a/selfdrive/ui/widgets/offroad_alerts.py +++ b/selfdrive/ui/widgets/offroad_alerts.py @@ -1,10 +1,11 @@ import pyray as rl +from enum import IntEnum from abc import ABC, abstractmethod from collections.abc import Callable from dataclasses import dataclass from openpilot.common.params import Params from openpilot.system.hardware import HARDWARE -from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos +from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.lib.wrap_text import wrap_text @@ -17,15 +18,16 @@ class AlertColors: LOW_SEVERITY = rl.Color(41, 41, 41, 255) BACKGROUND = rl.Color(57, 57, 57, 255) BUTTON = rl.WHITE + BUTTON_PRESSED = rl.Color(200, 200, 200, 255) BUTTON_TEXT = rl.BLACK SNOOZE_BG = rl.Color(79, 79, 79, 255) + SNOOZE_BG_PRESSED = rl.Color(100, 100, 100, 255) TEXT = rl.WHITE class AlertConstants: - BUTTON_SIZE = (400, 125) - SNOOZE_BUTTON_SIZE = (550, 125) - REBOOT_BUTTON_SIZE = (600, 125) + MIN_BUTTON_WIDTH = 400 + BUTTON_HEIGHT = 125 MARGIN = 50 SPACING = 30 FONT_SIZE = 48 @@ -43,6 +45,41 @@ class AlertData: visible: bool = False +class ButtonStyle(IntEnum): + LIGHT = 0 + DARK = 1 + + +class ActionButton(Widget): + def __init__(self, text: str, style: ButtonStyle = ButtonStyle.LIGHT, + min_width: int = AlertConstants.MIN_BUTTON_WIDTH): + super().__init__() + self._style = style + self._min_width = min_width + self._font = gui_app.font(FontWeight.MEDIUM) + self.set_text(text) + + def set_text(self, text: str): + self._text = text + self._text_width = measure_text_cached(gui_app.font(FontWeight.MEDIUM), self._text, AlertConstants.FONT_SIZE).x + self._rect.width = max(self._text_width + 60 * 2, self._min_width) + self._rect.height = AlertConstants.BUTTON_HEIGHT + + def _render(self, _): + roundness = AlertConstants.BORDER_RADIUS / self._rect.height + bg_color = AlertColors.BUTTON if self._style == ButtonStyle.LIGHT else AlertColors.SNOOZE_BG + if self.is_pressed: + bg_color = AlertColors.BUTTON_PRESSED if self._style == ButtonStyle.LIGHT else AlertColors.SNOOZE_BG_PRESSED + + rl.draw_rectangle_rounded(self._rect, roundness, 10, bg_color) + + # center text + color = rl.WHITE if self._style == ButtonStyle.DARK else rl.BLACK + text_x = int(self._rect.x + (self._rect.width - self._text_width) // 2) + text_y = int(self._rect.y + (self._rect.height - AlertConstants.FONT_SIZE) // 2) + rl.draw_text_ex(self._font, self._text, rl.Vector2(text_x, text_y), AlertConstants.FONT_SIZE, 0, color) + + class AbstractAlert(Widget, ABC): def __init__(self, has_reboot_btn: bool = False): super().__init__() @@ -50,17 +87,35 @@ class AbstractAlert(Widget, ABC): self.has_reboot_btn = has_reboot_btn self.dismiss_callback: Callable | None = None - self.dismiss_btn_rect = rl.Rectangle(0, 0, *AlertConstants.BUTTON_SIZE) - self.snooze_btn_rect = rl.Rectangle(0, 0, *AlertConstants.SNOOZE_BUTTON_SIZE) - self.reboot_btn_rect = rl.Rectangle(0, 0, *AlertConstants.REBOOT_BUTTON_SIZE) + def snooze_callback(): + self.params.put_bool("SnoozeUpdate", True) + if self.dismiss_callback: + self.dismiss_callback() - self.snooze_visible = False + def excessive_actuation_callback(): + self.params.remove("Offroad_ExcessiveActuation") + if self.dismiss_callback: + self.dismiss_callback() + + self.dismiss_btn = ActionButton("Close") + + self.snooze_btn = ActionButton("Snooze Update", style=ButtonStyle.DARK) + self.snooze_btn.set_click_callback(snooze_callback) + + self.excessive_actuation_btn = ActionButton("Acknowledge Excessive Actuation", style=ButtonStyle.DARK, min_width=800) + self.excessive_actuation_btn.set_click_callback(excessive_actuation_callback) + + self.reboot_btn = ActionButton("Reboot and Update", min_width=600) + self.reboot_btn.set_click_callback(lambda: HARDWARE.reboot()) + + # TODO: just use a Scroller? self.content_rect = rl.Rectangle(0, 0, 0, 0) self.scroll_panel_rect = rl.Rectangle(0, 0, 0, 0) self.scroll_panel = GuiScrollPanel() def set_dismiss_callback(self, callback: Callable): self.dismiss_callback = callback + self.dismiss_btn.set_click_callback(self.dismiss_callback) @abstractmethod def refresh(self) -> bool: @@ -70,28 +125,10 @@ class AbstractAlert(Widget, ABC): def get_content_height(self) -> float: pass - def _handle_mouse_release(self, mouse_pos: MousePos): - super()._handle_mouse_release(mouse_pos) - - if not self.scroll_panel.is_touch_valid(): - return - - if rl.check_collision_point_rec(mouse_pos, self.dismiss_btn_rect): - if self.dismiss_callback: - self.dismiss_callback() - - elif self.snooze_visible and rl.check_collision_point_rec(mouse_pos, self.snooze_btn_rect): - self.params.put_bool("SnoozeUpdate", True) - if self.dismiss_callback: - self.dismiss_callback() - - elif self.has_reboot_btn and rl.check_collision_point_rec(mouse_pos, self.reboot_btn_rect): - HARDWARE.reboot() - def _render(self, rect: rl.Rectangle): rl.draw_rectangle_rounded(rect, AlertConstants.BORDER_RADIUS / rect.height, 10, AlertColors.BACKGROUND) - footer_height = AlertConstants.BUTTON_SIZE[1] + AlertConstants.SPACING + footer_height = AlertConstants.BUTTON_HEIGHT + AlertConstants.SPACING content_height = rect.height - 2 * AlertConstants.MARGIN - footer_height self.content_rect = rl.Rectangle( @@ -134,47 +171,26 @@ class AbstractAlert(Widget, ABC): pass def _render_footer(self, rect: rl.Rectangle): - footer_y = rect.y + rect.height - AlertConstants.MARGIN - AlertConstants.BUTTON_SIZE[1] - font = gui_app.font(FontWeight.MEDIUM) + footer_y = rect.y + rect.height - AlertConstants.MARGIN - AlertConstants.BUTTON_HEIGHT - self.dismiss_btn_rect.x = rect.x + AlertConstants.MARGIN - self.dismiss_btn_rect.y = footer_y - roundness = AlertConstants.BORDER_RADIUS / self.dismiss_btn_rect.height - rl.draw_rectangle_rounded(self.dismiss_btn_rect, roundness, 10, AlertColors.BUTTON) + dismiss_x = rect.x + AlertConstants.MARGIN + self.dismiss_btn.set_position(dismiss_x, footer_y) + self.dismiss_btn.render() - text = "Close" - text_width = measure_text_cached(font, text, AlertConstants.FONT_SIZE).x - text_x = self.dismiss_btn_rect.x + (AlertConstants.BUTTON_SIZE[0] - text_width) // 2 - text_y = self.dismiss_btn_rect.y + (AlertConstants.BUTTON_SIZE[1] - AlertConstants.FONT_SIZE) // 2 - rl.draw_text_ex( - font, text, rl.Vector2(int(text_x), int(text_y)), AlertConstants.FONT_SIZE, 0, AlertColors.BUTTON_TEXT - ) + if self.has_reboot_btn: + reboot_x = rect.x + rect.width - AlertConstants.MARGIN - self.reboot_btn.rect.width + self.reboot_btn.set_position(reboot_x, footer_y) + self.reboot_btn.render() - if self.snooze_visible: - self.snooze_btn_rect.x = rect.x + rect.width - AlertConstants.MARGIN - AlertConstants.SNOOZE_BUTTON_SIZE[0] - self.snooze_btn_rect.y = footer_y - roundness = AlertConstants.BORDER_RADIUS / self.snooze_btn_rect.height - rl.draw_rectangle_rounded(self.snooze_btn_rect, roundness, 10, AlertColors.SNOOZE_BG) + elif self.excessive_actuation_btn.is_visible: + actuation_x = rect.x + rect.width - AlertConstants.MARGIN - self.excessive_actuation_btn.rect.width + self.excessive_actuation_btn.set_position(actuation_x, footer_y) + self.excessive_actuation_btn.render() - text = "Snooze Update" - text_width = measure_text_cached(font, text, AlertConstants.FONT_SIZE).x - text_x = self.snooze_btn_rect.x + (AlertConstants.SNOOZE_BUTTON_SIZE[0] - text_width) // 2 - text_y = self.snooze_btn_rect.y + (AlertConstants.SNOOZE_BUTTON_SIZE[1] - AlertConstants.FONT_SIZE) // 2 - rl.draw_text_ex(font, text, rl.Vector2(int(text_x), int(text_y)), AlertConstants.FONT_SIZE, 0, AlertColors.TEXT) - - elif self.has_reboot_btn: - self.reboot_btn_rect.x = rect.x + rect.width - AlertConstants.MARGIN - AlertConstants.REBOOT_BUTTON_SIZE[0] - self.reboot_btn_rect.y = footer_y - roundness = AlertConstants.BORDER_RADIUS / self.reboot_btn_rect.height - rl.draw_rectangle_rounded(self.reboot_btn_rect, roundness, 10, AlertColors.BUTTON) - - text = "Reboot and Update" - text_width = measure_text_cached(font, text, AlertConstants.FONT_SIZE).x - text_x = self.reboot_btn_rect.x + (AlertConstants.REBOOT_BUTTON_SIZE[0] - text_width) // 2 - text_y = self.reboot_btn_rect.y + (AlertConstants.REBOOT_BUTTON_SIZE[1] - AlertConstants.FONT_SIZE) // 2 - rl.draw_text_ex( - font, text, rl.Vector2(int(text_x), int(text_y)), AlertConstants.FONT_SIZE, 0, AlertColors.BUTTON_TEXT - ) + elif self.snooze_btn.is_visible: + snooze_x = rect.x + rect.width - AlertConstants.MARGIN - self.snooze_btn.rect.width + self.snooze_btn.set_position(snooze_x, footer_y) + self.snooze_btn.render() class OffroadAlert(AbstractAlert): @@ -188,6 +204,7 @@ class OffroadAlert(AbstractAlert): active_count = 0 connectivity_needed = False + excessive_actuation = False for alert_data in self.sorted_alerts: text = "" @@ -205,7 +222,11 @@ class OffroadAlert(AbstractAlert): if alert_data.key == "Offroad_ConnectivityNeeded" and alert_data.visible: connectivity_needed = True - self.snooze_visible = connectivity_needed + if alert_data.key == "Offroad_ExcessiveActuation" and alert_data.visible: + excessive_actuation = True + + self.excessive_actuation_btn.set_visible(excessive_actuation) + self.snooze_btn.set_visible(connectivity_needed and not excessive_actuation) return active_count def get_content_height(self) -> float: From 540fff52262477f62c4ee846ed537f0a81bafc08 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 3 Oct 2025 00:20:30 -0700 Subject: [PATCH 088/910] raylib: draw update button and fix incorrect font (#36243) * always update layout rects * don't ever use raylib font * use it * such as --- pyproject.toml | 1 + selfdrive/ui/layouts/home.py | 4 ++-- selfdrive/ui/widgets/offroad_alerts.py | 2 +- selfdrive/ui/widgets/pairing_dialog.py | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index a0d135db6..9a8de2b74 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -268,6 +268,7 @@ lint.flake8-implicit-str-concat.allow-multiline = false "pyray.measure_text_ex".msg = "Use openpilot.system.ui.lib.text_measure" "pyray.is_mouse_button_pressed".msg = "This can miss events. Use Widget._handle_mouse_press" "pyray.is_mouse_button_released".msg = "This can miss events. Use Widget._handle_mouse_release" +"pyray.draw_text".msg = "Use a function (such as rl.draw_font_ex) that takes font as an argument" [tool.ruff.format] quote-style = "preserve" diff --git a/selfdrive/ui/layouts/home.py b/selfdrive/ui/layouts/home.py index c33b16fac..39b39d33d 100644 --- a/selfdrive/ui/layouts/home.py +++ b/selfdrive/ui/layouts/home.py @@ -88,7 +88,7 @@ class HomeLayout(Widget): elif self.current_state == HomeLayoutState.ALERTS: self._render_alerts_view() - def _update_layout_rects(self): + def _update_state(self): self.header_rect = rl.Rectangle( self._rect.x + CONTENT_MARGIN, self._rect.y + CONTENT_MARGIN, self._rect.width - 2 * CONTENT_MARGIN, HEADER_HEIGHT ) @@ -129,7 +129,7 @@ class HomeLayout(Widget): # Update notification button if self.update_available: # Highlight if currently viewing updates - highlight_color = rl.Color(255, 140, 40, 255) if self.current_state == HomeLayoutState.UPDATE else rl.Color(255, 102, 0, 255) + highlight_color = rl.Color(75, 95, 255, 255) if self.current_state == HomeLayoutState.UPDATE else rl.Color(54, 77, 239, 255) rl.draw_rectangle_rounded(self.update_notif_rect, 0.3, 10, highlight_color) text = "UPDATE" diff --git a/selfdrive/ui/widgets/offroad_alerts.py b/selfdrive/ui/widgets/offroad_alerts.py index 8ff8f27be..e26b5d313 100644 --- a/selfdrive/ui/widgets/offroad_alerts.py +++ b/selfdrive/ui/widgets/offroad_alerts.py @@ -341,4 +341,4 @@ class UpdateAlert(AbstractAlert): text_width = rl.measure_text(no_notes_text, AlertConstants.FONT_SIZE) text_x = content_rect.x + (content_rect.width - text_width) // 2 text_y = content_rect.y + 50 - rl.draw_text(no_notes_text, int(text_x), int(text_y), AlertConstants.FONT_SIZE, AlertColors.TEXT) + rl.draw_text_ex(gui_app.font(FontWeight.NORMAL), no_notes_text, (int(text_x), int(text_y)), AlertConstants.FONT_SIZE, 0, AlertColors.TEXT) diff --git a/selfdrive/ui/widgets/pairing_dialog.py b/selfdrive/ui/widgets/pairing_dialog.py index 7676635e1..64e1b701f 100644 --- a/selfdrive/ui/widgets/pairing_dialog.py +++ b/selfdrive/ui/widgets/pairing_dialog.py @@ -146,7 +146,7 @@ class PairingDialog(Widget): rl.draw_circle(int(circle_x), int(circle_y), circle_radius, rl.Color(70, 70, 70, 255)) number = str(i + 1) number_width = measure_text_cached(font, number, 30).x - rl.draw_text(number, int(circle_x - number_width // 2), int(circle_y - 15), 30, rl.WHITE) + rl.draw_text_ex(font, number, (int(circle_x - number_width // 2), int(circle_y - 15)), 30, 0, rl.WHITE) # Text rl.draw_text_ex(font, "\n".join(wrapped), rl.Vector2(text_x, y), 47, 0.0, rl.BLACK) From d567442136d06897bf8b0f46fd729226266ae9e0 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 3 Oct 2025 00:37:47 -0700 Subject: [PATCH 089/910] raylib: split out HTML renderer (#36244) * stash * ok chatter is useful for once * why doesn't it understand?! * rm that * clean up --- selfdrive/ui/layouts/settings/device.py | 6 +- selfdrive/ui/widgets/offroad_alerts.py | 2 + system/ui/widgets/html_render.py | 76 ++++++++++++++----------- 3 files changed, 49 insertions(+), 35 deletions(-) diff --git a/selfdrive/ui/layouts/settings/device.py b/selfdrive/ui/layouts/settings/device.py index cb705d46f..c41c8b0a2 100644 --- a/selfdrive/ui/layouts/settings/device.py +++ b/selfdrive/ui/layouts/settings/device.py @@ -11,7 +11,7 @@ from openpilot.system.hardware import TICI from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.widgets import Widget, DialogResult from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog, alert_dialog -from openpilot.system.ui.widgets.html_render import HtmlRenderer +from openpilot.system.ui.widgets.html_render import HtmlModal from openpilot.system.ui.widgets.list_view import text_item, button_item, dual_button_item from openpilot.system.ui.widgets.option_dialog import MultiOptionDialog from openpilot.system.ui.widgets.scroller import Scroller @@ -36,7 +36,7 @@ class DeviceLayout(Widget): self._select_language_dialog: MultiOptionDialog | None = None self._driver_camera: DriverCameraDialog | None = None self._pair_device_dialog: PairingDialog | None = None - self._fcc_dialog: HtmlRenderer | None = None + self._fcc_dialog: HtmlModal | None = None self._training_guide: TrainingGuide | None = None items = self._initialize_items() @@ -140,7 +140,7 @@ class DeviceLayout(Widget): def _on_regulatory(self): if not self._fcc_dialog: - self._fcc_dialog = HtmlRenderer(os.path.join(BASEDIR, "selfdrive/assets/offroad/fcc.html")) + self._fcc_dialog = HtmlModal(os.path.join(BASEDIR, "selfdrive/assets/offroad/fcc.html")) gui_app.set_modal_overlay(self._fcc_dialog) def _on_review_training_guide(self): diff --git a/selfdrive/ui/widgets/offroad_alerts.py b/selfdrive/ui/widgets/offroad_alerts.py index e26b5d313..444688b7a 100644 --- a/selfdrive/ui/widgets/offroad_alerts.py +++ b/selfdrive/ui/widgets/offroad_alerts.py @@ -10,6 +10,7 @@ from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.lib.wrap_text import wrap_text from openpilot.system.ui.widgets import Widget +from openpilot.system.ui.widgets.html_render import HtmlRenderer from openpilot.selfdrive.selfdrived.alertmanager import OFFROAD_ALERTS @@ -306,6 +307,7 @@ class UpdateAlert(AbstractAlert): self.release_notes = "" self._wrapped_release_notes = "" self._cached_content_height: float = 0.0 + self._html_renderer: HtmlRenderer | None = None def refresh(self) -> bool: update_available: bool = self.params.get_bool("UpdateAvailable") diff --git a/system/ui/widgets/html_render.py b/system/ui/widgets/html_render.py index b87022785..db30833f1 100644 --- a/system/ui/widgets/html_render.py +++ b/system/ui/widgets/html_render.py @@ -34,13 +34,11 @@ class HtmlElement: class HtmlRenderer(Widget): - def __init__(self, file_path: str): + def __init__(self, file_path: str | None = None, text: str | None = None): super().__init__() self.elements: list[HtmlElement] = [] self._normal_font = gui_app.font(FontWeight.NORMAL) self._bold_font = gui_app.font(FontWeight.BOLD) - self._scroll_panel = GuiScrollPanel() - self._ok_button = Button("OK", click_callback=lambda: gui_app.set_modal_overlay(None), button_style=ButtonStyle.PRIMARY) self.styles: dict[ElementType, dict[str, Any]] = { ElementType.H1: {"size": 68, "weight": FontWeight.BOLD, "color": rl.BLACK, "margin_top": 20, "margin_bottom": 16}, @@ -53,7 +51,12 @@ class HtmlRenderer(Widget): ElementType.BR: {"size": 0, "weight": FontWeight.NORMAL, "color": rl.BLACK, "margin_top": 0, "margin_bottom": 12}, } - self.parse_html_file(file_path) + if file_path is not None: + self.parse_html_file(file_path) + elif text is not None: + self.parse_html_content(text) + else: + raise ValueError("Either file_path or text must be provided") def parse_html_file(self, file_path: str) -> None: with open(file_path, encoding='utf-8') as file: @@ -106,33 +109,7 @@ class HtmlRenderer(Widget): self.elements.append(element) def _render(self, rect: rl.Rectangle): - margin = 50 - content_rect = rl.Rectangle(rect.x + margin, rect.y + margin, rect.width - (margin * 2), rect.height - (margin * 2)) - - button_height = 160 - button_spacing = 20 - scrollable_height = content_rect.height - button_height - button_spacing - - scrollable_rect = rl.Rectangle(content_rect.x, content_rect.y, content_rect.width, scrollable_height) - - total_height = self.get_total_height(int(scrollable_rect.width)) - scroll_content_rect = rl.Rectangle(scrollable_rect.x, scrollable_rect.y, scrollable_rect.width, total_height) - scroll_offset = self._scroll_panel.update(scrollable_rect, scroll_content_rect) - - rl.begin_scissor_mode(int(scrollable_rect.x), int(scrollable_rect.y), int(scrollable_rect.width), int(scrollable_rect.height)) - self._render_content(scrollable_rect, scroll_offset) - rl.end_scissor_mode() - - button_width = (rect.width - 3 * 50) // 3 - button_x = content_rect.x + content_rect.width - button_width - button_y = content_rect.y + content_rect.height - button_height - button_rect = rl.Rectangle(button_x, button_y, button_width, button_height) - self._ok_button.render(button_rect) - - return -1 - - def _render_content(self, rect: rl.Rectangle, scroll_offset: float = 0) -> float: - current_y = rect.y + scroll_offset + current_y = rect.y padding = 20 content_width = rect.width - (padding * 2) @@ -164,7 +141,7 @@ class HtmlRenderer(Widget): # Apply bottom margin current_y += element.margin_bottom - return current_y - rect.y - scroll_offset # Return total content height + return current_y - rect.y def get_total_height(self, content_width: int) -> float: total_height = 0.0 @@ -193,3 +170,38 @@ class HtmlRenderer(Widget): if weight == FontWeight.BOLD: return self._bold_font return self._normal_font + + +class HtmlModal(Widget): + def __init__(self, file_path: str | None = None, text: str | None = None): + super().__init__() + self._content = HtmlRenderer(file_path=file_path, text=text) + self._scroll_panel = GuiScrollPanel() + self._ok_button = Button("OK", click_callback=lambda: gui_app.set_modal_overlay(None), button_style=ButtonStyle.PRIMARY) + + def _render(self, rect: rl.Rectangle): + margin = 50 + content_rect = rl.Rectangle(rect.x + margin, rect.y + margin, rect.width - (margin * 2), rect.height - (margin * 2)) + + button_height = 160 + button_spacing = 20 + scrollable_height = content_rect.height - button_height - button_spacing + + scrollable_rect = rl.Rectangle(content_rect.x, content_rect.y, content_rect.width, scrollable_height) + + total_height = self._content.get_total_height(int(scrollable_rect.width)) + scroll_content_rect = rl.Rectangle(scrollable_rect.x, scrollable_rect.y, scrollable_rect.width, total_height) + scroll_offset = self._scroll_panel.update(scrollable_rect, scroll_content_rect) + scroll_content_rect.y += scroll_offset + + rl.begin_scissor_mode(int(scrollable_rect.x), int(scrollable_rect.y), int(scrollable_rect.width), int(scrollable_rect.height)) + self._content.render(scroll_content_rect) + rl.end_scissor_mode() + + button_width = (rect.width - 3 * 50) // 3 + button_x = content_rect.x + content_rect.width - button_width + button_y = content_rect.y + content_rect.height - button_height + button_rect = rl.Rectangle(button_x, button_y, button_width, button_height) + self._ok_button.render(button_rect) + + return -1 From 150ff72646e7d7f9056246e2898f639fc0e2345f Mon Sep 17 00:00:00 2001 From: Armand du Parc Locmaria Date: Fri, 3 Oct 2025 14:36:12 -0700 Subject: [PATCH 090/910] Dockerfile.openpilot: don't set UV_PROJECT_ENVIRONMENT (#36246) * Dockerfile.openpilot: don't uv sync with root * Revert "Dockerfile.openpilot: don't uv sync with root" This reverts commit 2c271d0b5b55d6ae2ece6b28dc90a96e6e891ded. * don't set UV_PROJECT_ENVIRONMENT --- Dockerfile.openpilot | 2 +- Dockerfile.openpilot_base | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile.openpilot b/Dockerfile.openpilot index 14c1e6477..106a06e3a 100644 --- a/Dockerfile.openpilot +++ b/Dockerfile.openpilot @@ -11,4 +11,4 @@ COPY . ${OPENPILOT_PATH}/ ENV UV_BIN="/home/batman/.local/bin/" ENV PATH="$UV_BIN:$PATH" -RUN uv run scons --cache-readonly -j$(nproc) +RUN UV_PROJECT_ENVIRONMENT=$VIRTUAL_ENV uv run scons --cache-readonly -j$(nproc) diff --git a/Dockerfile.openpilot_base b/Dockerfile.openpilot_base index e3bb03de0..44d8d95e9 100644 --- a/Dockerfile.openpilot_base +++ b/Dockerfile.openpilot_base @@ -71,8 +71,8 @@ USER $USER COPY --chown=$USER pyproject.toml uv.lock /home/$USER COPY --chown=$USER tools/install_python_dependencies.sh /home/$USER/tools/ -ENV UV_PROJECT_ENVIRONMENT=/home/$USER/.venv -ENV PATH="$UV_PROJECT_ENVIRONMENT/bin:$PATH" +ENV VIRTUAL_ENV=/home/$USER/.venv +ENV PATH="$VIRTUAL_ENV/bin:$PATH" RUN cd /home/$USER && \ tools/install_python_dependencies.sh && \ rm -rf tools/ pyproject.toml uv.lock .cache From 670b6011da567d2db2a30d5621eba314eda447ee Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 3 Oct 2025 16:13:40 -0700 Subject: [PATCH 091/910] raylib: match QT confirmation dialog size (#36248) * closer to qt * this too * eval --- system/ui/widgets/confirm_dialog.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/system/ui/widgets/confirm_dialog.py b/system/ui/widgets/confirm_dialog.py index b1dc54bf7..606b04b6e 100644 --- a/system/ui/widgets/confirm_dialog.py +++ b/system/ui/widgets/confirm_dialog.py @@ -5,8 +5,8 @@ from openpilot.system.ui.widgets.button import ButtonStyle, Button from openpilot.system.ui.widgets.label import Label from openpilot.system.ui.widgets import Widget -DIALOG_WIDTH = 1520 -DIALOG_HEIGHT = 600 +DIALOG_WIDTH = 1748 +DIALOG_HEIGHT = 690 BUTTON_HEIGHT = 160 MARGIN = 50 TEXT_AREA_HEIGHT_REDUCTION = 200 @@ -16,7 +16,7 @@ BACKGROUND_COLOR = rl.Color(27, 27, 27, 255) class ConfirmDialog(Widget): def __init__(self, text: str, confirm_text: str, cancel_text: str = "Cancel"): super().__init__() - self._label = Label(text, 70, FontWeight.BOLD) + self._label = Label(text, 70, FontWeight.BOLD, text_color=rl.Color(201, 201, 201, 255)) self._cancel_button = Button(cancel_text, self._cancel_button_callback) self._confirm_button = Button(confirm_text, self._confirm_button_callback, button_style=ButtonStyle.PRIMARY) self._dialog_result = DialogResult.NO_ACTION From bd357adb8b7deda194a8f25e00ba58134e099fd3 Mon Sep 17 00:00:00 2001 From: YassineYousfi Date: Fri, 3 Oct 2025 17:01:20 -0700 Subject: [PATCH 092/910] update release notes for 0.10.1 --- RELEASES.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/RELEASES.md b/RELEASES.md index 966d5d380..568be6c35 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,10 +1,12 @@ Version 0.10.1 (2025-09-08) ======================== -* New driving model #36087 +* New driving model #36114 * World Model: removed global localization inputs * World Model: 2x the number of parameters * World Model: trained on 4x the number of segments + * VAE Compression Model: new architecture and training objective * Driving Vision Model: trained on 4x the number of segments +* New Driver Monitoring model #36198 * Acura TLX 2021 support thanks to MVL! * Honda City 2023 support thanks to vanillagorillaa and drFritz! * Honda N-Box 2018 support thanks to miettal! From 7b2b10bc9e28fb1080b1415e5173b0b29683b66f Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 3 Oct 2025 17:34:22 -0700 Subject: [PATCH 093/910] raylib screenshots: raise ui delay --- selfdrive/ui/tests/test_ui/raylib_screenshots.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py index 81eef60d6..acb317cba 100755 --- a/selfdrive/ui/tests/test_ui/raylib_screenshots.py +++ b/selfdrive/ui/tests/test_ui/raylib_screenshots.py @@ -20,7 +20,7 @@ from openpilot.selfdrive.selfdrived.alertmanager import set_offroad_alert TEST_DIR = pathlib.Path(__file__).parent TEST_OUTPUT_DIR = TEST_DIR / "raylib_report" SCREENSHOTS_DIR = TEST_OUTPUT_DIR / "screenshots" -UI_DELAY = 0.1 +UI_DELAY = 0.2 # Offroad alerts to test OFFROAD_ALERTS = ['Offroad_IsTakingSnapshot'] From 9670e3a5eb4add3a26a7875d455b6101d4bfcebe Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 3 Oct 2025 17:34:53 -0700 Subject: [PATCH 094/910] raylib: add confirmation dialog (#36252) * conf * update case * fix * fix * rm * back * alread setup * avail --- .../ui/tests/test_ui/raylib_screenshots.py | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py index acb317cba..d7e08112b 100755 --- a/selfdrive/ui/tests/test_ui/raylib_screenshots.py +++ b/selfdrive/ui/tests/test_ui/raylib_screenshots.py @@ -12,10 +12,12 @@ import pywinctl from cereal import log from cereal import messaging from cereal.messaging import PubMaster +from openpilot.common.basedir import BASEDIR from openpilot.common.params import Params from openpilot.common.prefix import OpenpilotPrefix from openpilot.selfdrive.test.helpers import with_processes from openpilot.selfdrive.selfdrived.alertmanager import set_offroad_alert +from openpilot.system.updated.updated import parse_release_notes TEST_DIR = pathlib.Path(__file__).parent TEST_OUTPUT_DIR = TEST_DIR / "raylib_report" @@ -34,6 +36,10 @@ def setup_settings_device(click, pm: PubMaster): click(100, 100) +def close_settings(click, pm: PubMaster): + click(240, 216) + + def setup_settings_network(click, pm: PubMaster): setup_settings_device(click, pm) click(278, 450) @@ -75,7 +81,22 @@ def setup_offroad_alert(click, pm: PubMaster): set_offroad_alert(alert, True) setup_settings_device(click, pm) - click(240, 216) + close_settings(click, pm) + + +def setup_confirmation_dialog(click, pm: PubMaster): + setup_settings_device(click, pm) + click(1985, 791) # reset calibration + + +def setup_update_available(click, pm: PubMaster): + params = Params() + params.put_bool("UpdateAvailable", True) + params.put("UpdaterNewReleaseNotes", parse_release_notes(BASEDIR)) + description = "0.10.1 / html-release-notes-2 / 7864838 / Oct 03" + params.put("UpdaterCurrentDescription", description) + setup_settings_device(click, pm) + close_settings(click, pm) CASES = { @@ -89,6 +110,8 @@ CASES = { "keyboard": setup_keyboard, "pair_device": setup_pair_device, "offroad_alert": setup_offroad_alert, + "update_available": setup_update_available, + "confirmation_dialog": setup_confirmation_dialog, } From edc5a0412c3b1ef3b831ec93d9454905c11037bb Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 3 Oct 2025 17:35:05 -0700 Subject: [PATCH 095/910] rename to setup_settings --- .../ui/tests/test_ui/raylib_screenshots.py | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py index d7e08112b..f570a701b 100755 --- a/selfdrive/ui/tests/test_ui/raylib_screenshots.py +++ b/selfdrive/ui/tests/test_ui/raylib_screenshots.py @@ -32,7 +32,7 @@ def setup_homescreen(click, pm: PubMaster): pass -def setup_settings_device(click, pm: PubMaster): +def setup_settings(click, pm: PubMaster): click(100, 100) @@ -41,27 +41,27 @@ def close_settings(click, pm: PubMaster): def setup_settings_network(click, pm: PubMaster): - setup_settings_device(click, pm) + setup_settings(click, pm) click(278, 450) def setup_settings_toggles(click, pm: PubMaster): - setup_settings_device(click, pm) + setup_settings(click, pm) click(278, 600) def setup_settings_software(click, pm: PubMaster): - setup_settings_device(click, pm) + setup_settings(click, pm) click(278, 720) def setup_settings_firehose(click, pm: PubMaster): - setup_settings_device(click, pm) + setup_settings(click, pm) click(278, 845) def setup_settings_developer(click, pm: PubMaster): - setup_settings_device(click, pm) + setup_settings(click, pm) click(278, 950) @@ -80,12 +80,12 @@ def setup_offroad_alert(click, pm: PubMaster): for alert in OFFROAD_ALERTS: set_offroad_alert(alert, True) - setup_settings_device(click, pm) + setup_settings(click, pm) close_settings(click, pm) def setup_confirmation_dialog(click, pm: PubMaster): - setup_settings_device(click, pm) + setup_settings(click, pm) click(1985, 791) # reset calibration @@ -95,13 +95,13 @@ def setup_update_available(click, pm: PubMaster): params.put("UpdaterNewReleaseNotes", parse_release_notes(BASEDIR)) description = "0.10.1 / html-release-notes-2 / 7864838 / Oct 03" params.put("UpdaterCurrentDescription", description) - setup_settings_device(click, pm) + setup_settings(click, pm) close_settings(click, pm) CASES = { "homescreen": setup_homescreen, - "settings_device": setup_settings_device, + "settings_device": setup_settings, "settings_network": setup_settings_network, "settings_toggles": setup_settings_toggles, "settings_software": setup_settings_software, From 45f497e8f68e06f1256b0b26dcf640d2191c32e7 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 3 Oct 2025 17:46:39 -0700 Subject: [PATCH 096/910] raylib screenshots: add mouse click helper (#36253) * add helper * rm * name * fix --- .../ui/tests/test_ui/print_mouse_coords.py | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100755 selfdrive/ui/tests/test_ui/print_mouse_coords.py diff --git a/selfdrive/ui/tests/test_ui/print_mouse_coords.py b/selfdrive/ui/tests/test_ui/print_mouse_coords.py new file mode 100755 index 000000000..1e88ce57d --- /dev/null +++ b/selfdrive/ui/tests/test_ui/print_mouse_coords.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +""" +Simple script to print mouse coordinates on Ubuntu. +Run with: python print_mouse_coords.py +Press Ctrl+C to exit. +""" + +from pynput import mouse + +print("Mouse coordinate printer - Press Ctrl+C to exit") +print("Click to set the top left origin") + +origin: tuple[int, int] | None = None +clicks: list[tuple[int, int]] = [] + + +def on_click(x, y, button, pressed): + global origin, clicks + if pressed: # Only on mouse down, not up + if origin is None: + origin = (x, y) + print(f"Origin set to: {x},{y}") + else: + rel_x = x - origin[0] + rel_y = y - origin[1] + clicks.append((rel_x, rel_y)) + print(f"Clicks: {clicks}") + + +if __name__ == "__main__": + try: + # Start mouse listener + with mouse.Listener(on_click=on_click) as listener: + listener.join() + except KeyboardInterrupt: + print("\nExiting...") From 39b97d4e18b99712344f898ee03975889d64fb6f Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 3 Oct 2025 17:50:02 -0700 Subject: [PATCH 097/910] raylib screenshots: use long branch name (#36254) * stress test * everything --- selfdrive/ui/tests/test_ui/raylib_screenshots.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py index f570a701b..36d4e1f50 100755 --- a/selfdrive/ui/tests/test_ui/raylib_screenshots.py +++ b/selfdrive/ui/tests/test_ui/raylib_screenshots.py @@ -28,6 +28,14 @@ UI_DELAY = 0.2 OFFROAD_ALERTS = ['Offroad_IsTakingSnapshot'] +def put_update_params(params: Params): + params.put("UpdaterCurrentReleaseNotes", parse_release_notes(BASEDIR)) + params.put("UpdaterNewReleaseNotes", parse_release_notes(BASEDIR)) + description = "0.10.1 / this-is-a-really-super-mega-long-branch-name / 7864838 / Oct 03" + params.put("UpdaterCurrentDescription", description) + params.put("UpdaterNewDescription", description) + + def setup_homescreen(click, pm: PubMaster): pass @@ -51,6 +59,7 @@ def setup_settings_toggles(click, pm: PubMaster): def setup_settings_software(click, pm: PubMaster): + put_update_params(Params()) setup_settings(click, pm) click(278, 720) @@ -92,9 +101,7 @@ def setup_confirmation_dialog(click, pm: PubMaster): def setup_update_available(click, pm: PubMaster): params = Params() params.put_bool("UpdateAvailable", True) - params.put("UpdaterNewReleaseNotes", parse_release_notes(BASEDIR)) - description = "0.10.1 / html-release-notes-2 / 7864838 / Oct 03" - params.put("UpdaterCurrentDescription", description) + put_update_params(params) setup_settings(click, pm) close_settings(click, pm) From 844c3286254ab36490e129f19175f3c0cd06fa2f Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 3 Oct 2025 17:59:19 -0700 Subject: [PATCH 098/910] raylib screenshots: prevent saving black frame raylib screenshots: prevent saving black frame --- selfdrive/ui/tests/test_ui/raylib_screenshots.py | 1 + 1 file changed, 1 insertion(+) diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py index 36d4e1f50..ecd130258 100755 --- a/selfdrive/ui/tests/test_ui/raylib_screenshots.py +++ b/selfdrive/ui/tests/test_ui/raylib_screenshots.py @@ -156,6 +156,7 @@ class TestUI: @with_processes(["raylib_ui"]) def test_ui(self, name, setup_case): self.setup() + time.sleep(UI_DELAY) # wait for UI to start setup_case(self.click, self.pm) self.screenshot(name) From a8328cb5ffd4df13a02a7e925aa673ee07397924 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 3 Oct 2025 17:59:42 -0700 Subject: [PATCH 099/910] raylib screenshots: find diff faster (#36255) * ? * run it * wrong * here too * revert --- .github/workflows/raylib_ui_preview.yaml | 2 +- .github/workflows/ui_preview.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/raylib_ui_preview.yaml b/.github/workflows/raylib_ui_preview.yaml index ff9655d15..3986c8592 100644 --- a/.github/workflows/raylib_ui_preview.yaml +++ b/.github/workflows/raylib_ui_preview.yaml @@ -83,7 +83,7 @@ jobs: if: github.event_name == 'pull_request_target' id: find_diff run: >- - sudo apt-get update && sudo apt-get install -y imagemagick + sudo apt-get install -y imagemagick scenes=$(find ${{ github.workspace }}/pr_ui/*.png -type f -printf "%f\n" | cut -d '.' -f 1 | grep -v 'pair_device') A=($scenes) diff --git a/.github/workflows/ui_preview.yaml b/.github/workflows/ui_preview.yaml index 9ec7a5922..79ab373c2 100644 --- a/.github/workflows/ui_preview.yaml +++ b/.github/workflows/ui_preview.yaml @@ -83,7 +83,7 @@ jobs: if: github.event_name == 'pull_request_target' id: find_diff run: >- - sudo apt-get update && sudo apt-get install -y imagemagick + sudo apt-get install -y imagemagick scenes=$(find ${{ github.workspace }}/pr_ui/*.png -type f -printf "%f\n" | cut -d '.' -f 1 | grep -v 'pair_device') A=($scenes) From 4d53a26a06973a7d6fa4fef1e238a3bb9e64e67c Mon Sep 17 00:00:00 2001 From: Armand du Parc Locmaria Date: Fri, 3 Oct 2025 19:43:03 -0700 Subject: [PATCH 100/910] relock after inplace metadrive update (#36256) * relock after inplace metadrive update * Revert "relock after inplace metadrive update" This reverts commit 18193ffe34b66085e18605e6c9289ddcd658844d. * just the hash --- uv.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uv.lock b/uv.lock index 9c791b811..324622129 100644 --- a/uv.lock +++ b/uv.lock @@ -949,7 +949,7 @@ dependencies = [ { 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" }, + { 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:d0afaf3b005e35e14b929d5491d2d5b64562d0c1cd5093ba969fb63908670dd4" }, ] [package.metadata] From 99a83e5522da426f8eeeff85838d3e0d0b70b0fc Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 3 Oct 2025 20:35:32 -0700 Subject: [PATCH 101/910] Revert "raylib screenshots: find diff faster (#36255)" This reverts commit a8328cb5ffd4df13a02a7e925aa673ee07397924. --- .github/workflows/raylib_ui_preview.yaml | 2 +- .github/workflows/ui_preview.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/raylib_ui_preview.yaml b/.github/workflows/raylib_ui_preview.yaml index 3986c8592..ff9655d15 100644 --- a/.github/workflows/raylib_ui_preview.yaml +++ b/.github/workflows/raylib_ui_preview.yaml @@ -83,7 +83,7 @@ jobs: if: github.event_name == 'pull_request_target' id: find_diff run: >- - sudo apt-get install -y imagemagick + sudo apt-get update && sudo apt-get install -y imagemagick scenes=$(find ${{ github.workspace }}/pr_ui/*.png -type f -printf "%f\n" | cut -d '.' -f 1 | grep -v 'pair_device') A=($scenes) diff --git a/.github/workflows/ui_preview.yaml b/.github/workflows/ui_preview.yaml index 79ab373c2..9ec7a5922 100644 --- a/.github/workflows/ui_preview.yaml +++ b/.github/workflows/ui_preview.yaml @@ -83,7 +83,7 @@ jobs: if: github.event_name == 'pull_request_target' id: find_diff run: >- - sudo apt-get install -y imagemagick + sudo apt-get update && sudo apt-get install -y imagemagick scenes=$(find ${{ github.workspace }}/pr_ui/*.png -type f -printf "%f\n" | cut -d '.' -f 1 | grep -v 'pair_device') A=($scenes) From 89d350a7912919dd570abd51f7cbc41bd8c19b07 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 3 Oct 2025 20:42:42 -0700 Subject: [PATCH 102/910] raylib html renderer: fixups (#36257) * this wasn't used * override text size and color * render untagged text as paragraph * and indent * cache expensive height calc * fmt * fix that * unclear if this is even needed * and that * huh * debug * Revert "debug" This reverts commit 7d446d2a37a96e6bd1001c566d4f8e8f417f8fb7. --- system/ui/widgets/html_render.py | 105 ++++++++++++++++++++++--------- 1 file changed, 76 insertions(+), 29 deletions(-) diff --git a/system/ui/widgets/html_render.py b/system/ui/widgets/html_render.py index db30833f1..b032df4d9 100644 --- a/system/ui/widgets/html_render.py +++ b/system/ui/widgets/html_render.py @@ -9,6 +9,8 @@ from openpilot.system.ui.lib.wrap_text import wrap_text from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.button import Button, ButtonStyle +LIST_INDENT_PX = 40 + class ElementType(Enum): H1 = "h1" @@ -18,39 +20,60 @@ class ElementType(Enum): H5 = "h5" H6 = "h6" P = "p" + UL = "ul" + LI = "li" BR = "br" +TAG_NAMES = '|'.join([t.value for t in ElementType]) +START_TAG_RE = re.compile(f'<({TAG_NAMES})>') +END_TAG_RE = re.compile(f'') + + +def is_tag(token: str) -> tuple[bool, bool, ElementType | None]: + supported_tag = bool(START_TAG_RE.fullmatch(token)) + supported_end_tag = bool(END_TAG_RE.fullmatch(token)) + tag = ElementType(token[1:-1].strip('/')) if supported_tag or supported_end_tag else None + return supported_tag, supported_end_tag, tag + + @dataclass class HtmlElement: type: ElementType content: str font_size: int font_weight: FontWeight - color: rl.Color margin_top: int margin_bottom: int line_height: float = 1.2 + indent_level: int = 0 class HtmlRenderer(Widget): - def __init__(self, file_path: str | None = None, text: str | None = None): + def __init__(self, file_path: str | None = None, text: str | None = None, + text_size: dict | None = None, text_color: rl.Color = rl.WHITE): super().__init__() - self.elements: list[HtmlElement] = [] + self._text_color = text_color self._normal_font = gui_app.font(FontWeight.NORMAL) self._bold_font = gui_app.font(FontWeight.BOLD) + self._indent_level = 0 + if text_size is None: + text_size = {} + + # Untagged text defaults to

self.styles: dict[ElementType, dict[str, Any]] = { - ElementType.H1: {"size": 68, "weight": FontWeight.BOLD, "color": rl.BLACK, "margin_top": 20, "margin_bottom": 16}, - ElementType.H2: {"size": 60, "weight": FontWeight.BOLD, "color": rl.BLACK, "margin_top": 24, "margin_bottom": 12}, - ElementType.H3: {"size": 52, "weight": FontWeight.BOLD, "color": rl.BLACK, "margin_top": 20, "margin_bottom": 10}, - ElementType.H4: {"size": 48, "weight": FontWeight.BOLD, "color": rl.BLACK, "margin_top": 16, "margin_bottom": 8}, - ElementType.H5: {"size": 44, "weight": FontWeight.BOLD, "color": rl.BLACK, "margin_top": 12, "margin_bottom": 6}, - ElementType.H6: {"size": 40, "weight": FontWeight.BOLD, "color": rl.BLACK, "margin_top": 10, "margin_bottom": 4}, - ElementType.P: {"size": 38, "weight": FontWeight.NORMAL, "color": rl.Color(40, 40, 40, 255), "margin_top": 8, "margin_bottom": 12}, - ElementType.BR: {"size": 0, "weight": FontWeight.NORMAL, "color": rl.BLACK, "margin_top": 0, "margin_bottom": 12}, + ElementType.H1: {"size": 68, "weight": FontWeight.BOLD, "margin_top": 20, "margin_bottom": 16}, + ElementType.H2: {"size": 60, "weight": FontWeight.BOLD, "margin_top": 24, "margin_bottom": 12}, + ElementType.H3: {"size": 52, "weight": FontWeight.BOLD, "margin_top": 20, "margin_bottom": 10}, + ElementType.H4: {"size": 48, "weight": FontWeight.BOLD, "margin_top": 16, "margin_bottom": 8}, + ElementType.H5: {"size": 44, "weight": FontWeight.BOLD, "margin_top": 12, "margin_bottom": 6}, + ElementType.H6: {"size": 40, "weight": FontWeight.BOLD, "margin_top": 10, "margin_bottom": 4}, + ElementType.P: {"size": text_size.get(ElementType.P, 38), "weight": FontWeight.NORMAL, "margin_top": 8, "margin_bottom": 12}, + ElementType.BR: {"size": 0, "weight": FontWeight.NORMAL, "margin_top": 0, "margin_bottom": 12}, } + self.elements: list[HtmlElement] = [] if file_path is not None: self.parse_html_file(file_path) elif text is not None: @@ -73,25 +96,48 @@ class HtmlRenderer(Widget): html_content = re.sub(r']*>', '', html_content) html_content = re.sub(r']*>', '', html_content) - # Find all HTML elements - pattern = r'<(h[1-6]|p)(?:[^>]*)>(.*?)|' - matches = re.finditer(pattern, html_content, re.DOTALL | re.IGNORECASE) + # Parse HTML + tokens = re.findall(r']+>|<[^>]+>|[^<\s]+', html_content) + + def close_tag(): + nonlocal current_content + nonlocal current_tag + + # If no tag is set, default to paragraph so we don't lose text + if current_tag is None: + current_tag = ElementType.P + + text = ' '.join(current_content).strip() + current_content = [] + if text: + if current_tag == ElementType.LI: + text = '• ' + text + self._add_element(current_tag, text) + + current_content: list[str] = [] + current_tag: ElementType | None = None + for token in tokens: + is_start_tag, is_end_tag, tag = is_tag(token) + if tag is not None: + if tag == ElementType.BR: + self._add_element(ElementType.BR, "") + + elif tag == ElementType.UL: + self._indent_level = self._indent_level + 1 if is_start_tag else max(0, self._indent_level - 1) + + elif is_start_tag or is_end_tag: + # Always add content regardless of opening or closing tag + close_tag() + + # TODO: reset to None if end tag? + if is_start_tag: + current_tag = tag - for match in matches: - if match.group(0).lower().startswith(' tags - self._add_element(ElementType.BR, "") else: - tag = match.group(1).lower() - content = match.group(2).strip() + current_content.append(token) - # Clean up content - remove extra whitespace - content = re.sub(r'\s+', ' ', content) - content = content.strip() - - if content: # Only add non-empty elements - element_type = ElementType(tag) - self._add_element(element_type, content) + if current_content: + close_tag() def _add_element(self, element_type: ElementType, content: str) -> None: style = self.styles[element_type] @@ -101,9 +147,9 @@ class HtmlRenderer(Widget): content=content, font_size=style["size"], font_weight=style["weight"], - color=style["color"], margin_top=style["margin_top"], margin_bottom=style["margin_bottom"], + indent_level=self._indent_level, ) self.elements.append(element) @@ -134,7 +180,8 @@ class HtmlRenderer(Widget): if current_y > rect.y + rect.height: break - rl.draw_text_ex(font, line, rl.Vector2(rect.x + padding, current_y), element.font_size, 0, rl.WHITE) + text_x = rect.x + (max(element.indent_level - 1, 0) * LIST_INDENT_PX) + rl.draw_text_ex(font, line, rl.Vector2(text_x + padding, current_y), element.font_size, 0, self._text_color) current_y += element.font_size * element.line_height From 12b3d0e08dbb10318186cd37b833c40dbb98749f Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 3 Oct 2025 20:52:50 -0700 Subject: [PATCH 103/910] raylib: cache wrap text (#36258) * cache html height * clean up * todo --- system/ui/lib/wrap_text.py | 8 ++++++++ system/ui/widgets/html_render.py | 1 + 2 files changed, 9 insertions(+) diff --git a/system/ui/lib/wrap_text.py b/system/ui/lib/wrap_text.py index 35dc5ac40..f6caa3c5f 100644 --- a/system/ui/lib/wrap_text.py +++ b/system/ui/lib/wrap_text.py @@ -36,7 +36,14 @@ def _break_long_word(font: rl.Font, word: str, font_size: int, max_width: int) - return parts +_cache: dict[int, list[str]] = {} + + def wrap_text(font: rl.Font, text: str, font_size: int, max_width: int) -> list[str]: + key = hash((font.texture.id, text, font_size, max_width)) + if key in _cache: + return _cache[key] + if not text or max_width <= 0: return [] @@ -100,4 +107,5 @@ def wrap_text(font: rl.Font, text: str, font_size: int, max_width: int) -> list[ # Add all lines from this paragraph all_lines.extend(lines) + _cache[key] = all_lines return all_lines diff --git a/system/ui/widgets/html_render.py b/system/ui/widgets/html_render.py index b032df4d9..f7dc9e934 100644 --- a/system/ui/widgets/html_render.py +++ b/system/ui/widgets/html_render.py @@ -155,6 +155,7 @@ class HtmlRenderer(Widget): self.elements.append(element) def _render(self, rect: rl.Rectangle): + # TODO: speed up by removing duplicate calculations across renders current_y = rect.y padding = 20 content_width = rect.width - (padding * 2) From bd9888a439712f951f682bea38fb070ab1a240c0 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 3 Oct 2025 21:29:20 -0700 Subject: [PATCH 104/910] raylib screenshots: add software release notes (#36259) add software --- selfdrive/ui/tests/test_ui/raylib_screenshots.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py index ecd130258..e77a2d4d7 100755 --- a/selfdrive/ui/tests/test_ui/raylib_screenshots.py +++ b/selfdrive/ui/tests/test_ui/raylib_screenshots.py @@ -98,7 +98,7 @@ def setup_confirmation_dialog(click, pm: PubMaster): click(1985, 791) # reset calibration -def setup_update_available(click, pm: PubMaster): +def setup_homescreen_update_available(click, pm: PubMaster): params = Params() params.put_bool("UpdateAvailable", True) put_update_params(params) @@ -106,6 +106,12 @@ def setup_update_available(click, pm: PubMaster): close_settings(click, pm) +def setup_software_release_notes(click, pm: PubMaster): + setup_settings(click, pm) + setup_settings_software(click, pm) + click(588, 110) # expand description for current version + + CASES = { "homescreen": setup_homescreen, "settings_device": setup_settings, @@ -117,8 +123,9 @@ CASES = { "keyboard": setup_keyboard, "pair_device": setup_pair_device, "offroad_alert": setup_offroad_alert, - "update_available": setup_update_available, + "homescreen_update_available": setup_homescreen_update_available, "confirmation_dialog": setup_confirmation_dialog, + "software_release_notes": setup_software_release_notes, } From 2337704602fe492741afbe856f19118ae562f196 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 3 Oct 2025 21:47:53 -0700 Subject: [PATCH 105/910] raylib: release notes are drawn with HTML renderer (#36245) * stash * ok chatter is useful for once * draw text outside tags * hmm * undo that shit * i don't like this chatgpt * Revert "i don't like this chatgpt" This reverts commit 5b511911d81242457bfb5fc808a9b9f35fe9f7a2. * more robust parsing (works with missing tags, markdown.py actually had bug) + add indent level * the html looks weird but is correct - the old parser didn't handle it * clean up * some * move out * clean up * oh this was wrong * draft * rm that * fix * fix indentation for new driving model * clean up * some clean up * more clean up * more clean up * and this * cmt * ok this is egregious mypy --- selfdrive/ui/layouts/settings/software.py | 4 +- selfdrive/ui/widgets/offroad_alerts.py | 27 +++----- system/ui/lib/application.py | 2 +- system/ui/widgets/html_render.py | 8 ++- system/ui/widgets/list_view.py | 75 +++++++++++------------ 5 files changed, 55 insertions(+), 61 deletions(-) diff --git a/selfdrive/ui/layouts/settings/software.py b/selfdrive/ui/layouts/settings/software.py index 7440512a6..7aebc609f 100644 --- a/selfdrive/ui/layouts/settings/software.py +++ b/selfdrive/ui/layouts/settings/software.py @@ -80,7 +80,7 @@ class SoftwareLayout(Widget): current_desc = ui_state.params.get("UpdaterCurrentDescription") or "" current_release_notes = (ui_state.params.get("UpdaterCurrentReleaseNotes") or b"").decode("utf-8", "replace") self._version_item.action_item.set_text(current_desc) - self._version_item.description = current_release_notes + self._version_item.set_description(current_release_notes) # Update download button visibility and state self._download_btn.set_visible(ui_state.is_offroad()) @@ -125,7 +125,7 @@ class SoftwareLayout(Widget): new_release_notes = (ui_state.params.get("UpdaterNewReleaseNotes") or b"").decode("utf-8", "replace") self._install_btn.action_item.set_text("INSTALL") self._install_btn.action_item.set_value(new_desc) - self._install_btn.description = new_release_notes + self._install_btn.set_description(new_release_notes) # Enable install button for testing (like Qt showEvent) self._install_btn.action_item.set_enabled(True) else: diff --git a/selfdrive/ui/widgets/offroad_alerts.py b/selfdrive/ui/widgets/offroad_alerts.py index 444688b7a..104597163 100644 --- a/selfdrive/ui/widgets/offroad_alerts.py +++ b/selfdrive/ui/widgets/offroad_alerts.py @@ -14,6 +14,9 @@ from openpilot.system.ui.widgets.html_render import HtmlRenderer from openpilot.selfdrive.selfdrived.alertmanager import OFFROAD_ALERTS +NO_RELEASE_NOTES = "

No release notes available.

" + + class AlertColors: HIGH_SEVERITY = rl.Color(226, 44, 44, 255) LOW_SEVERITY = rl.Color(41, 41, 41, 255) @@ -307,13 +310,16 @@ class UpdateAlert(AbstractAlert): self.release_notes = "" self._wrapped_release_notes = "" self._cached_content_height: float = 0.0 - self._html_renderer: HtmlRenderer | None = None + self._html_renderer = HtmlRenderer(text="") def refresh(self) -> bool: update_available: bool = self.params.get_bool("UpdateAvailable") if update_available: - self.release_notes = self.params.get("UpdaterNewReleaseNotes") + self.release_notes = (self.params.get("UpdaterNewReleaseNotes") or b"").decode("utf8").strip() + self._html_renderer.parse_html_content(self.release_notes or NO_RELEASE_NOTES) self._cached_content_height = 0 + else: + self._html_renderer.parse_html_content(NO_RELEASE_NOTES) return update_available @@ -329,18 +335,5 @@ class UpdateAlert(AbstractAlert): return self._cached_content_height def _render_content(self, content_rect: rl.Rectangle): - if self.release_notes: - rl.draw_text_ex( - gui_app.font(FontWeight.NORMAL), - self._wrapped_release_notes, - rl.Vector2(content_rect.x + 30, content_rect.y + 30), - AlertConstants.FONT_SIZE, - 0.0, - AlertColors.TEXT, - ) - else: - no_notes_text = "No release notes available." - text_width = rl.measure_text(no_notes_text, AlertConstants.FONT_SIZE) - text_x = content_rect.x + (content_rect.width - text_width) // 2 - text_y = content_rect.y + 50 - rl.draw_text_ex(gui_app.font(FontWeight.NORMAL), no_notes_text, (int(text_x), int(text_y)), AlertConstants.FONT_SIZE, 0, AlertColors.TEXT) + notes_rect = rl.Rectangle(content_rect.x + 30, content_rect.y + 30, content_rect.width - 60, content_rect.height - 60) + self._html_renderer.render(notes_rect) diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 6c703a516..a42b1d112 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -331,7 +331,7 @@ class GuiApplication: for layout in KEYBOARD_LAYOUTS.values(): all_chars.update(key for row in layout for key in row) all_chars = "".join(all_chars) - all_chars += "–✓×°§" + all_chars += "–✓×°§•" codepoint_count = rl.ffi.new("int *", 1) codepoints = rl.load_codepoints(all_chars, codepoint_count) diff --git a/system/ui/widgets/html_render.py b/system/ui/widgets/html_render.py index f7dc9e934..2c3eeb379 100644 --- a/system/ui/widgets/html_render.py +++ b/system/ui/widgets/html_render.py @@ -70,6 +70,7 @@ class HtmlRenderer(Widget): ElementType.H5: {"size": 44, "weight": FontWeight.BOLD, "margin_top": 12, "margin_bottom": 6}, ElementType.H6: {"size": 40, "weight": FontWeight.BOLD, "margin_top": 10, "margin_bottom": 4}, ElementType.P: {"size": text_size.get(ElementType.P, 38), "weight": FontWeight.NORMAL, "margin_top": 8, "margin_bottom": 12}, + ElementType.LI: {"size": 38, "weight": FontWeight.NORMAL, "color": rl.Color(40, 40, 40, 255), "margin_top": 6, "margin_bottom": 6}, ElementType.BR: {"size": 0, "weight": FontWeight.NORMAL, "margin_top": 0, "margin_bottom": 12}, } @@ -122,9 +123,6 @@ class HtmlRenderer(Widget): if tag == ElementType.BR: self._add_element(ElementType.BR, "") - elif tag == ElementType.UL: - self._indent_level = self._indent_level + 1 if is_start_tag else max(0, self._indent_level - 1) - elif is_start_tag or is_end_tag: # Always add content regardless of opening or closing tag close_tag() @@ -133,6 +131,10 @@ class HtmlRenderer(Widget): if is_start_tag: current_tag = tag + # increment after we add the content for the current tag + if tag == ElementType.UL: + self._indent_level = self._indent_level + 1 if is_start_tag else max(0, self._indent_level - 1) + else: current_content.append(token) diff --git a/system/ui/widgets/list_view.py b/system/ui/widgets/list_view.py index 787732549..056aa2d90 100644 --- a/system/ui/widgets/list_view.py +++ b/system/ui/widgets/list_view.py @@ -4,10 +4,10 @@ from collections.abc import Callable from abc import ABC from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos from openpilot.system.ui.lib.text_measure import measure_text_cached -from openpilot.system.ui.lib.wrap_text import wrap_text from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.button import Button, ButtonStyle from openpilot.system.ui.widgets.toggle import Toggle, WIDTH as TOGGLE_WIDTH, HEIGHT as TOGGLE_HEIGHT +from openpilot.system.ui.widgets.html_render import HtmlRenderer, ElementType ITEM_BASE_WIDTH = 600 ITEM_BASE_HEIGHT = 170 @@ -258,7 +258,7 @@ class ListItem(Widget): super().__init__() self.title = title self.icon = icon - self.description = description + self._description = description self.description_visible = description_visible self.callback = callback self.action_item = action_item @@ -267,11 +267,12 @@ class ListItem(Widget): self._font = gui_app.font(FontWeight.NORMAL) self._icon_texture = gui_app.texture(os.path.join("icons", self.icon), ICON_SIZE, ICON_SIZE) if self.icon else None + self._html_renderer = HtmlRenderer(text="", text_size={ElementType.P: ITEM_DESC_FONT_SIZE}, + text_color=ITEM_DESC_TEXT_COLOR) + self.set_description(self.description) + # Cached properties for performance - self._prev_max_width: int = 0 - self._wrapped_description: str | None = None - self._prev_description: str | None = None - self._description_height: float = 0 + self._prev_description: str | None = self.description def set_touch_valid_callback(self, touch_callback: Callable[[], bool]) -> None: super().set_touch_valid_callback(touch_callback) @@ -295,9 +296,15 @@ class ListItem(Widget): if self.description: self.description_visible = not self.description_visible - content_width = self.get_content_width(int(self._rect.width - ITEM_PADDING * 2)) + content_width = int(self._rect.width - ITEM_PADDING * 2) self._rect.height = self.get_item_height(self._font, content_width) + def _update_state(self): + # Detect changes if description is callback + new_description = self.description + if new_description != self._prev_description: + self.set_description(new_description) + def _render(self, _): if not self.is_visible: return @@ -323,16 +330,16 @@ class ListItem(Widget): rl.draw_text_ex(self._font, self.title, rl.Vector2(text_x, item_y), ITEM_TEXT_FONT_SIZE, 0, ITEM_TEXT_COLOR) # Draw description if visible - current_description = self.get_description() - if self.description_visible and current_description and self._wrapped_description: - rl.draw_text_ex( - self._font, - self._wrapped_description, - rl.Vector2(text_x, self._rect.y + ITEM_DESC_V_OFFSET), - ITEM_DESC_FONT_SIZE, - 0, - ITEM_DESC_TEXT_COLOR, + if self.description_visible: + content_width = int(self._rect.width - ITEM_PADDING * 2) + description_height = self._html_renderer.get_total_height(content_width) + description_rect = rl.Rectangle( + self._rect.x + ITEM_PADDING, + self._rect.y + ITEM_DESC_V_OFFSET, + content_width, + description_height ) + self._html_renderer.render(description_rect) # Draw right item if present if self.action_item: @@ -343,33 +350,25 @@ class ListItem(Widget): if self.callback: self.callback() - def get_description(self): - return _resolve_value(self.description, None) + def set_description(self, description: str | Callable[[], str] | None): + self._description = description + new_desc = self.description + self._html_renderer.parse_html_content(new_desc) + self._prev_description = new_desc + + @property + def description(self): + return _resolve_value(self._description, "") def get_item_height(self, font: rl.Font, max_width: int) -> float: if not self.is_visible: return 0 - current_description = self.get_description() - if self.description_visible and current_description: - if ( - not self._wrapped_description - or current_description != self._prev_description - or max_width != self._prev_max_width - ): - self._prev_max_width = max_width - self._prev_description = current_description - - wrapped_lines = wrap_text(font, current_description, ITEM_DESC_FONT_SIZE, max_width) - self._wrapped_description = "\n".join(wrapped_lines) - self._description_height = len(wrapped_lines) * ITEM_DESC_FONT_SIZE + 10 - return ITEM_BASE_HEIGHT + self._description_height - (ITEM_BASE_HEIGHT - ITEM_DESC_V_OFFSET) + ITEM_PADDING - return ITEM_BASE_HEIGHT - - def get_content_width(self, total_width: int) -> int: - if self.action_item and self.action_item.rect.width > 0: - return total_width - int(self.action_item.rect.width) - RIGHT_ITEM_PADDING - return total_width + height = float(ITEM_BASE_HEIGHT) + if self.description_visible: + description_height = self._html_renderer.get_total_height(max_width) + height += description_height - (ITEM_BASE_HEIGHT - ITEM_DESC_V_OFFSET) + ITEM_PADDING + return height def get_right_item_rect(self, item_rect: rl.Rectangle) -> rl.Rectangle: if not self.action_item: From 703f3d0573edac611881ddeeb0263bf3ff458d37 Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Fri, 3 Oct 2025 22:09:00 -0700 Subject: [PATCH 106/910] disable sim test for now --- .github/workflows/selfdrive_tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml index 57b1158be..35ced1e38 100644 --- a/.github/workflows/selfdrive_tests.yaml +++ b/.github/workflows/selfdrive_tests.yaml @@ -225,7 +225,7 @@ jobs: (github.event.pull_request.head.repo.full_name == 'commaai/openpilot')) && fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]') || fromJSON('["ubuntu-24.04"]') }} - if: (github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot')) + if: false # FIXME: Started to timeout recently steps: - uses: actions/checkout@v4 with: From 0eb90ecb3e4e26f72ccc62eb21e16deb989f27eb Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 3 Oct 2025 23:04:55 -0700 Subject: [PATCH 107/910] raylib: elide list item actions (#36262) fix --- system/ui/widgets/list_view.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/system/ui/widgets/list_view.py b/system/ui/widgets/list_view.py index 056aa2d90..509c49be3 100644 --- a/system/ui/widgets/list_view.py +++ b/system/ui/widgets/list_view.py @@ -7,6 +7,7 @@ from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.button import Button, ButtonStyle from openpilot.system.ui.widgets.toggle import Toggle, WIDTH as TOGGLE_WIDTH, HEIGHT as TOGGLE_HEIGHT +from openpilot.system.ui.widgets.label import gui_label from openpilot.system.ui.widgets.html_render import HtmlRenderer, ElementType ITEM_BASE_WIDTH = 600 @@ -42,6 +43,10 @@ class ItemAction(Widget, ABC): self.set_rect(rl.Rectangle(0, 0, width, 0)) self._enabled_source = enabled + def get_width_hint(self) -> float: + # Return's action ideal width, 0 means use full width + return self._rect.width + def set_enabled(self, enabled: bool | Callable[[], bool]): self._enabled_source = enabled @@ -147,17 +152,14 @@ class TextAction(ItemAction): def text(self): return _resolve_value(self._text_source, "Error") - def _update_state(self): + def get_width_hint(self) -> float: text_width = measure_text_cached(self._font, self.text, ITEM_TEXT_FONT_SIZE).x - self._rect.width = int(text_width + TEXT_PADDING) + return text_width + TEXT_PADDING def _render(self, rect: rl.Rectangle) -> bool: - current_text = self.text - text_size = measure_text_cached(self._font, current_text, ITEM_TEXT_FONT_SIZE) - - text_x = rect.x + (rect.width - text_size.x) / 2 - text_y = rect.y + (rect.height - text_size.y) / 2 - rl.draw_text_ex(self._font, current_text, rl.Vector2(text_x, text_y), ITEM_TEXT_FONT_SIZE, 0, self.color) + gui_label(self._rect, self.text, font_size=ITEM_TEXT_FONT_SIZE, color=self.color, + font_weight=FontWeight.NORMAL, alignment=rl.GuiTextAlignment.TEXT_ALIGN_RIGHT, + alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE) return False def set_text(self, text: str | Callable[[], str]): @@ -374,11 +376,16 @@ class ListItem(Widget): if not self.action_item: return rl.Rectangle(0, 0, 0, 0) - right_width = self.action_item.rect.width + right_width = self.action_item.get_width_hint() if right_width == 0: # Full width action (like DualButtonAction) return rl.Rectangle(item_rect.x + ITEM_PADDING, item_rect.y, item_rect.width - (ITEM_PADDING * 2), ITEM_BASE_HEIGHT) + # Clip width to available space, never overlapping this Item's title + content_width = item_rect.width - (ITEM_PADDING * 2) + title_width = measure_text_cached(self._font, self.title, ITEM_TEXT_FONT_SIZE).x + right_width = min(content_width - title_width, right_width) + right_x = item_rect.x + item_rect.width - right_width right_y = item_rect.y return rl.Rectangle(right_x, right_y, right_width, ITEM_BASE_HEIGHT) From e423f8f605bfb521393de9ce80f13c1d281f3613 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 3 Oct 2025 23:17:51 -0700 Subject: [PATCH 108/910] raylib: elide version on homescreen (#36263) * elide ver on hom * rm line * blank --- selfdrive/ui/layouts/home.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/selfdrive/ui/layouts/home.py b/selfdrive/ui/layouts/home.py index 39b39d33d..f7f57c680 100644 --- a/selfdrive/ui/layouts/home.py +++ b/selfdrive/ui/layouts/home.py @@ -8,7 +8,8 @@ from openpilot.selfdrive.ui.widgets.exp_mode_button import ExperimentalModeButto from openpilot.selfdrive.ui.widgets.prime import PrimeWidget from openpilot.selfdrive.ui.widgets.setup import SetupWidget from openpilot.system.ui.lib.text_measure import measure_text_cached -from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos, DEFAULT_TEXT_COLOR +from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos +from openpilot.system.ui.widgets.label import gui_label from openpilot.system.ui.widgets import Widget HEADER_HEIGHT = 80 @@ -126,8 +127,12 @@ class HomeLayout(Widget): def _render_header(self): font = gui_app.font(FontWeight.MEDIUM) + version_text_width = self.header_rect.width + # Update notification button if self.update_available: + version_text_width -= self.update_notif_rect.width + # Highlight if currently viewing updates highlight_color = rl.Color(75, 95, 255, 255) if self.current_state == HomeLayoutState.UPDATE else rl.Color(54, 77, 239, 255) rl.draw_rectangle_rounded(self.update_notif_rect, 0.3, 10, highlight_color) @@ -140,6 +145,8 @@ class HomeLayout(Widget): # Alert notification button if self.alert_count > 0: + version_text_width -= self.alert_notif_rect.width + # Highlight if currently viewing alerts highlight_color = rl.Color(255, 70, 70, 255) if self.current_state == HomeLayoutState.ALERTS else rl.Color(226, 44, 44, 255) rl.draw_rectangle_rounded(self.alert_notif_rect, 0.3, 10, highlight_color) @@ -151,11 +158,12 @@ class HomeLayout(Widget): rl.draw_text_ex(font, alert_text, rl.Vector2(int(text_x), int(text_y)), HEAD_BUTTON_FONT_SIZE, 0, rl.WHITE) # Version text (right aligned) - version_text = self._get_version_text() - text_width = measure_text_cached(gui_app.font(FontWeight.NORMAL), version_text, 48).x - version_x = self.header_rect.x + self.header_rect.width - text_width - version_y = self.header_rect.y + (self.header_rect.height - 48) // 2 - rl.draw_text_ex(gui_app.font(FontWeight.NORMAL), version_text, rl.Vector2(int(version_x), int(version_y)), 48, 0, DEFAULT_TEXT_COLOR) + if self.update_available or self.alert_count > 0: + version_text_width -= SPACING * 1.5 + + version_rect = rl.Rectangle(self.header_rect.x + self.header_rect.width - version_text_width, self.header_rect.y, + version_text_width, self.header_rect.height) + gui_label(version_rect, self._get_version_text(), 48, rl.WHITE, alignment=rl.GuiTextAlignment.TEXT_ALIGN_RIGHT) def _render_home_content(self): self._render_left_column() From 3fd9e94a3414fae490fb5169186ffe528c012333 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 3 Oct 2025 23:18:20 -0700 Subject: [PATCH 109/910] raylib: all system apps work without anything built (#36261) * all system apps work without scons * better * fix * revert * fix * dont add * huh --- system/ui/lib/wifi_manager.py | 15 ++++++++++----- system/ui/widgets/network.py | 13 ++++++++++--- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index e24686566..5594742cb 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -16,7 +16,6 @@ from jeepney.low_level import MessageType from jeepney.wrappers import Properties from openpilot.common.swaglog import cloudlog -from openpilot.common.params import Params from openpilot.system.ui.lib.networkmanager import (NM, NM_WIRELESS_IFACE, NM_802_11_AP_SEC_PAIR_WEP40, NM_802_11_AP_SEC_PAIR_WEP104, NM_802_11_AP_SEC_GROUP_WEP40, NM_802_11_AP_SEC_GROUP_WEP104, NM_802_11_AP_SEC_KEY_MGMT_PSK, @@ -28,6 +27,11 @@ from openpilot.system.ui.lib.networkmanager import (NM, NM_WIRELESS_IFACE, NM_80 NM_DEVICE_STATE_REASON_NEW_ACTIVATION, NM_ACTIVE_CONNECTION_IFACE, NM_IP4_CONFIG_IFACE, NMDeviceState) +try: + from openpilot.common.params import Params +except Exception: + Params = None + TETHERING_IP_ADDRESS = "192.168.43.1" DEFAULT_TETHERING_PASSWORD = "swagswagcomma" SIGNAL_QUEUE_SIZE = 10 @@ -149,9 +153,10 @@ class WifiManager: self._callback_queue: list[Callable] = [] self._tethering_ssid = "weedle" - dongle_id = Params().get("DongleId") - if dongle_id: - self._tethering_ssid += "-" + dongle_id[:4] + if Params is not None: + dongle_id = Params().get("DongleId") + if dongle_id: + self._tethering_ssid += "-" + dongle_id[:4] # Callbacks self._need_auth: list[Callable[[str], None]] = [] @@ -173,7 +178,7 @@ class WifiManager: self._scan_thread.start() self._state_thread.start() - if self._tethering_ssid not in self._get_connections(): + if Params is not None and self._tethering_ssid not in self._get_connections(): self._add_tethering_connection() self._tethering_password = self._get_tethering_password() diff --git a/system/ui/widgets/network.py b/system/ui/widgets/network.py index 8a7bd9f51..85b98f10a 100644 --- a/system/ui/widgets/network.py +++ b/system/ui/widgets/network.py @@ -3,7 +3,6 @@ from functools import partial from typing import cast import pyray as rl -from openpilot.common.params import Params from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel from openpilot.system.ui.lib.wifi_manager import WifiManager, SecurityType, Network, MeteredType @@ -14,8 +13,16 @@ from openpilot.system.ui.widgets.keyboard import Keyboard from openpilot.system.ui.widgets.label import TextAlignment, gui_label from openpilot.system.ui.widgets.scroller import Scroller from openpilot.system.ui.widgets.list_view import ButtonAction, ListItem, MultipleButtonAction, ToggleAction, button_item, text_item -from openpilot.selfdrive.ui.ui_state import ui_state -from openpilot.selfdrive.ui.lib.prime_state import PrimeType + +# These are only used for AdvancedNetworkSettings, standalone apps just need WifiManagerUI +try: + from openpilot.common.params import Params + from openpilot.selfdrive.ui.ui_state import ui_state + from openpilot.selfdrive.ui.lib.prime_state import PrimeType +except Exception: + Params = None + ui_state = None # type: ignore + PrimeType = None # type: ignore NM_DEVICE_STATE_NEED_AUTH = 60 MIN_PASSWORD_LENGTH = 8 From 943aaef76a885bc337a3253b3b1cd09ecb97fa53 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 3 Oct 2025 23:32:17 -0700 Subject: [PATCH 110/910] raylib: match Qt onroad alert colors (#36264) fix alert colors --- selfdrive/ui/onroad/alert_renderer.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/selfdrive/ui/onroad/alert_renderer.py b/selfdrive/ui/onroad/alert_renderer.py index c529694df..e81129b13 100644 --- a/selfdrive/ui/onroad/alert_renderer.py +++ b/selfdrive/ui/onroad/alert_renderer.py @@ -26,9 +26,9 @@ SELFDRIVE_UNRESPONSIVE_TIMEOUT = 10 # Seconds # Constants ALERT_COLORS = { - AlertStatus.normal: rl.Color(0, 0, 0, 235), # Black - AlertStatus.userPrompt: rl.Color(0xFE, 0x8C, 0x34, 235), # Orange - AlertStatus.critical: rl.Color(0xC9, 0x22, 0x31, 235), # Red + AlertStatus.normal: rl.Color(0x15, 0x15, 0x15, 0xF1), # #151515 with alpha 0xF1 + AlertStatus.userPrompt: rl.Color(0xDA, 0x6F, 0x25, 0xF1), # #DA6F25 with alpha 0xF1 + AlertStatus.critical: rl.Color(0xC9, 0x22, 0x31, 0xF1), # #C92231 with alpha 0xF1 } From c88ab5cd123923f66aed2a2f74beaad5515d8ce9 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 3 Oct 2025 23:38:10 -0700 Subject: [PATCH 111/910] Switch to raylib for UI (#36238) * flip * change this --- system/manager/manager.py | 2 +- system/manager/process_config.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/system/manager/manager.py b/system/manager/manager.py index 36055d863..d97aa8b66 100755 --- a/system/manager/manager.py +++ b/system/manager/manager.py @@ -221,7 +221,7 @@ if __name__ == "__main__": cloudlog.exception("Manager failed to start") try: - managed_processes['ui'].stop() + managed_processes['raylib_ui'].stop() except Exception: pass diff --git a/system/manager/process_config.py b/system/manager/process_config.py index 22f159e89..55e2812ef 100644 --- a/system/manager/process_config.py +++ b/system/manager/process_config.py @@ -80,8 +80,8 @@ procs = [ PythonProcess("dmonitoringmodeld", "selfdrive.modeld.dmonitoringmodeld", driverview, enabled=(WEBCAM or not PC)), PythonProcess("sensord", "system.sensord.sensord", only_onroad, enabled=not PC), - NativeProcess("ui", "selfdrive/ui", ["./ui"], always_run, watchdog_max_dt=(5 if not PC else None)), - PythonProcess("raylib_ui", "selfdrive.ui.ui", always_run, enabled=False, watchdog_max_dt=(5 if not PC else None)), + NativeProcess("ui", "selfdrive/ui", ["./ui"], always_run, enabled=False, watchdog_max_dt=(5 if not PC else None)), + PythonProcess("raylib_ui", "selfdrive.ui.ui", always_run, watchdog_max_dt=(5 if not PC else None)), PythonProcess("soundd", "selfdrive.ui.soundd", only_onroad), PythonProcess("locationd", "selfdrive.locationd.locationd", only_onroad), NativeProcess("_pandad", "selfdrive/pandad", ["./pandad"], always_run, enabled=False), From 2bc97ee23f7d97f34d2469f698f974e723e8a1df Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 4 Oct 2025 00:05:20 -0700 Subject: [PATCH 112/910] raylib: fix DM popup (#36265) * come on * try * better * better * multiple places! * debug * works * temp * whoops * wonder if this wortks * ah need this! * wtf is this when deleted? * another day no modal show event * clean * fix * ugh * need this --- selfdrive/ui/onroad/driver_camera_dialog.py | 11 +++++++++-- system/ui/lib/application.py | 4 ++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/onroad/driver_camera_dialog.py b/selfdrive/ui/onroad/driver_camera_dialog.py index 6c5508ce7..a3bd2e23e 100644 --- a/selfdrive/ui/onroad/driver_camera_dialog.py +++ b/selfdrive/ui/onroad/driver_camera_dialog.py @@ -3,7 +3,7 @@ import pyray as rl from msgq.visionipc import VisionStreamType from openpilot.selfdrive.ui.onroad.cameraview import CameraView from openpilot.selfdrive.ui.onroad.driver_state import DriverStateRenderer -from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.selfdrive.ui.ui_state import ui_state, device from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.widgets.label import gui_label @@ -12,10 +12,17 @@ class DriverCameraDialog(CameraView): def __init__(self): super().__init__("camerad", VisionStreamType.VISION_STREAM_DRIVER) self.driver_state_renderer = DriverStateRenderer() + # TODO: this can grow unbounded, should be given some thought + device.add_interactive_timeout_callback(self.stop_dmonitoringmodeld) + ui_state.params.put_bool("IsDriverViewEnabled", True) + + def stop_dmonitoringmodeld(self): + ui_state.params.put_bool("IsDriverViewEnabled", False) + gui_app.set_modal_overlay(None) def _handle_mouse_release(self, _): super()._handle_mouse_release(_) - gui_app.set_modal_overlay(None) + self.stop_dmonitoringmodeld() def _render(self, rect): super()._render(rect) diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index a42b1d112..473eaa5fa 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -178,6 +178,10 @@ class GuiApplication: self._mouse.start() def set_modal_overlay(self, overlay, callback: Callable | None = None): + if self._modal_overlay.overlay is not None: + if self._modal_overlay.callback is not None: + self._modal_overlay.callback(-1) + self._modal_overlay = ModalOverlay(overlay=overlay, callback=callback) def texture(self, asset_path: str, width: int, height: int, alpha_premultiply=False, keep_aspect_ratio=True): From 7933c10c975ee24afbd2ef5554ba85005c94101a Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 4 Oct 2025 00:32:49 -0700 Subject: [PATCH 113/910] raylib: font sizes from QT should match (#36237) * debug * hacks everywhere but kind of works * by font * fix sidebar * stash * test update * just use a const * just use a const * better * clean up * fix label * simplify * gpt5 is yet again garbage * rm that * clean up * rm * blank * clean up * I really don't like this but shrug * fix * fix experimental text --- selfdrive/ui/layouts/sidebar.py | 29 +++++++++++++++---------- selfdrive/ui/widgets/exp_mode_button.py | 6 ++--- system/ui/lib/application.py | 15 +++++++++++++ system/ui/lib/text_measure.py | 3 ++- system/ui/widgets/html_render.py | 10 ++++----- 5 files changed, 43 insertions(+), 20 deletions(-) diff --git a/selfdrive/ui/layouts/sidebar.py b/selfdrive/ui/layouts/sidebar.py index 5987d062f..34354ecba 100644 --- a/selfdrive/ui/layouts/sidebar.py +++ b/selfdrive/ui/layouts/sidebar.py @@ -216,14 +216,21 @@ class Sidebar(Widget): # Draw border rl.draw_rectangle_rounded_lines_ex(metric_rect, 0.3, 10, 2, Colors.METRIC_BORDER) - # Draw label and value - labels = [metric.label, metric.value] - text_y = metric_rect.y + (metric_rect.height / 2 - len(labels) * FONT_SIZE) - for text in labels: - text_size = measure_text_cached(self._font_bold, text, FONT_SIZE) - text_y += text_size.y - text_pos = rl.Vector2( - metric_rect.x + 22 + (metric_rect.width - 22 - text_size.x) / 2, - text_y - ) - rl.draw_text_ex(self._font_bold, text, text_pos, FONT_SIZE, 0, Colors.WHITE) + label_size = measure_text_cached(self._font_bold, metric.label, FONT_SIZE) + value_size = measure_text_cached(self._font_bold, metric.value, FONT_SIZE) + text_height = label_size.y + value_size.y + + label_y = metric_rect.y + (metric_rect.height - text_height) / 2 + value_y = label_y + label_size.y + + # label + rl.draw_text_ex(self._font_bold, metric.label, rl.Vector2( + metric_rect.x + 22 + (metric_rect.width - 22 - label_size.x) / 2, + label_y + ), FONT_SIZE, 0, Colors.WHITE) + + # value + rl.draw_text_ex(self._font_bold, metric.value, rl.Vector2( + metric_rect.x + 22 + (metric_rect.width - 22 - value_size.x) / 2, + value_y + ), FONT_SIZE, 0, Colors.WHITE) diff --git a/selfdrive/ui/widgets/exp_mode_button.py b/selfdrive/ui/widgets/exp_mode_button.py index 961876895..6fe7b6843 100644 --- a/selfdrive/ui/widgets/exp_mode_button.py +++ b/selfdrive/ui/widgets/exp_mode_button.py @@ -1,6 +1,6 @@ import pyray as rl from openpilot.common.params import Params -from openpilot.system.ui.lib.application import gui_app, FontWeight +from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE from openpilot.system.ui.widgets import Widget @@ -9,7 +9,7 @@ class ExperimentalModeButton(Widget): super().__init__() self.img_width = 80 - self.horizontal_padding = 50 + self.horizontal_padding = 30 self.button_height = 125 self.params = Params() @@ -51,7 +51,7 @@ class ExperimentalModeButton(Widget): # Draw text label (left aligned) text = "EXPERIMENTAL MODE ON" if self.experimental_mode else "CHILL MODE ON" text_x = rect.x + self.horizontal_padding - text_y = rect.y + rect.height / 2 - 45 // 2 # Center vertically + text_y = rect.y + rect.height / 2 - 45 * FONT_SCALE // 2 # Center vertically rl.draw_text_ex(gui_app.font(FontWeight.NORMAL), text, rl.Vector2(int(text_x), int(text_y)), 45, 0, rl.BLACK) diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 473eaa5fa..755a335e4 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -30,6 +30,10 @@ SCALE = float(os.getenv("SCALE", "1.0")) DEFAULT_TEXT_SIZE = 60 DEFAULT_TEXT_COLOR = rl.WHITE +# Qt draws fonts accounting for ascent/descent differently, so compensate to match old styles +# The real scales for the fonts below range from 1.212 to 1.266 +FONT_SCALE = 1.242 + ASSETS_DIR = files("openpilot.selfdrive").joinpath("assets") FONT_DIR = ASSETS_DIR.joinpath("fonts") @@ -173,6 +177,7 @@ class GuiApplication: self._target_fps = fps self._set_styles() self._load_fonts() + self._patch_text_functions() if not PC: self._mouse.start() @@ -356,6 +361,16 @@ 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 _patch_text_functions(self): + # Wrap pyray text APIs to apply a global text size scale so our px sizes match Qt + if not hasattr(rl, "_orig_draw_text_ex"): + rl._orig_draw_text_ex = rl.draw_text_ex + + def _draw_text_ex_scaled(font, text, position, font_size, spacing, tint): + return rl._orig_draw_text_ex(font, text, position, font_size * FONT_SCALE, spacing, tint) + + rl.draw_text_ex = _draw_text_ex_scaled + def _set_log_callback(self): ffi_libc = cffi.FFI() ffi_libc.cdef(""" diff --git a/system/ui/lib/text_measure.py b/system/ui/lib/text_measure.py index c172f9425..fcb7b25cc 100644 --- a/system/ui/lib/text_measure.py +++ b/system/ui/lib/text_measure.py @@ -1,4 +1,5 @@ import pyray as rl +from openpilot.system.ui.lib.application import FONT_SCALE _cache: dict[int, rl.Vector2] = {} @@ -9,6 +10,6 @@ def measure_text_cached(font: rl.Font, text: str, font_size: int, spacing: int = if key in _cache: return _cache[key] - result = rl.measure_text_ex(font, text, font_size, spacing) # noqa: TID251 + result = rl.measure_text_ex(font, text, font_size * FONT_SCALE, spacing) # noqa: TID251 _cache[key] = result return result diff --git a/system/ui/widgets/html_render.py b/system/ui/widgets/html_render.py index 2c3eeb379..91a1ccbbc 100644 --- a/system/ui/widgets/html_render.py +++ b/system/ui/widgets/html_render.py @@ -3,7 +3,7 @@ import pyray as rl from dataclasses import dataclass from enum import Enum from typing import Any -from openpilot.system.ui.lib.application import gui_app, FontWeight +from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel from openpilot.system.ui.lib.wrap_text import wrap_text from openpilot.system.ui.widgets import Widget @@ -176,8 +176,8 @@ class HtmlRenderer(Widget): wrapped_lines = wrap_text(font, element.content, element.font_size, int(content_width)) for line in wrapped_lines: - if current_y < rect.y - element.font_size: - current_y += element.font_size * element.line_height + if current_y < rect.y - element.font_size * FONT_SCALE: + current_y += element.font_size * FONT_SCALE * element.line_height continue if current_y > rect.y + rect.height: @@ -186,7 +186,7 @@ class HtmlRenderer(Widget): text_x = rect.x + (max(element.indent_level - 1, 0) * LIST_INDENT_PX) rl.draw_text_ex(font, line, rl.Vector2(text_x + padding, current_y), element.font_size, 0, self._text_color) - current_y += element.font_size * element.line_height + current_y += element.font_size * FONT_SCALE * element.line_height # Apply bottom margin current_y += element.margin_bottom @@ -210,7 +210,7 @@ class HtmlRenderer(Widget): wrapped_lines = wrap_text(font, element.content, element.font_size, int(usable_width)) for _ in wrapped_lines: - total_height += element.font_size * element.line_height + total_height += element.font_size * FONT_SCALE * element.line_height total_height += element.margin_bottom From ebe47a580ccea938d5cb6d3140e5f6a2760cdc76 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 4 Oct 2025 01:04:05 -0700 Subject: [PATCH 114/910] raylib: fix registration box height --- selfdrive/ui/widgets/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/widgets/setup.py b/selfdrive/ui/widgets/setup.py index bf6d113f6..e4e44b7d0 100644 --- a/selfdrive/ui/widgets/setup.py +++ b/selfdrive/ui/widgets/setup.py @@ -28,7 +28,7 @@ class SetupWidget(Widget): def _render_registration(self, rect: rl.Rectangle): """Render registration prompt.""" - rl.draw_rectangle_rounded(rl.Rectangle(rect.x, rect.y, rect.width, 590), 0.02, 20, rl.Color(51, 51, 51, 255)) + rl.draw_rectangle_rounded(rl.Rectangle(rect.x, rect.y, rect.width, 630), 0.02, 20, rl.Color(51, 51, 51, 255)) x = rect.x + 64 y = rect.y + 48 From 586e49cab35566258468abae75ff4152a5c3a1ac Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 4 Oct 2025 01:04:20 -0700 Subject: [PATCH 115/910] Revert "Switch to raylib for UI (#36238)" This reverts commit c88ab5cd123923f66aed2a2f74beaad5515d8ce9. --- system/manager/manager.py | 2 +- system/manager/process_config.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/system/manager/manager.py b/system/manager/manager.py index d97aa8b66..36055d863 100755 --- a/system/manager/manager.py +++ b/system/manager/manager.py @@ -221,7 +221,7 @@ if __name__ == "__main__": cloudlog.exception("Manager failed to start") try: - managed_processes['raylib_ui'].stop() + managed_processes['ui'].stop() except Exception: pass diff --git a/system/manager/process_config.py b/system/manager/process_config.py index 55e2812ef..22f159e89 100644 --- a/system/manager/process_config.py +++ b/system/manager/process_config.py @@ -80,8 +80,8 @@ procs = [ PythonProcess("dmonitoringmodeld", "selfdrive.modeld.dmonitoringmodeld", driverview, enabled=(WEBCAM or not PC)), PythonProcess("sensord", "system.sensord.sensord", only_onroad, enabled=not PC), - NativeProcess("ui", "selfdrive/ui", ["./ui"], always_run, enabled=False, watchdog_max_dt=(5 if not PC else None)), - PythonProcess("raylib_ui", "selfdrive.ui.ui", always_run, watchdog_max_dt=(5 if not PC else None)), + NativeProcess("ui", "selfdrive/ui", ["./ui"], always_run, watchdog_max_dt=(5 if not PC else None)), + PythonProcess("raylib_ui", "selfdrive.ui.ui", always_run, enabled=False, watchdog_max_dt=(5 if not PC else None)), PythonProcess("soundd", "selfdrive.ui.soundd", only_onroad), PythonProcess("locationd", "selfdrive.locationd.locationd", only_onroad), NativeProcess("_pandad", "selfdrive/pandad", ["./pandad"], always_run, enabled=False), From cc7ecd53c7135d1a9ef53bddc9881486de00b3ce Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Sat, 4 Oct 2025 02:45:40 -0700 Subject: [PATCH 116/910] raylib: bump commit --- third_party/raylib/build.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/third_party/raylib/build.sh b/third_party/raylib/build.sh index 544feb1c5..f786213da 100755 --- a/third_party/raylib/build.sh +++ b/third_party/raylib/build.sh @@ -30,7 +30,7 @@ fi cd raylib_repo -COMMIT=${1:-39e6d8b52db159ba2ab3214b46d89a8069e09394} +COMMIT=${1:-63ea627abb4488adc9c7b5e2f8dcc77a7d6bfa3b} git fetch origin $COMMIT git reset --hard $COMMIT git clean -xdff . @@ -57,7 +57,7 @@ if [ -f /TICI ]; then cd raylib_python_repo - BINDINGS_COMMIT="ef8141c7979d5fa630ef4108605fc221f07d8cb7" + BINDINGS_COMMIT="ab0191f445272ca66758f9dd345b7395518d6a77" git fetch origin $BINDINGS_COMMIT git reset --hard $BINDINGS_COMMIT git clean -xdff . From 31801a73121b729d11a7dd4a7ccd9698f4d8fc92 Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Sat, 4 Oct 2025 02:47:25 -0700 Subject: [PATCH 117/910] no more wayland for installer --- selfdrive/ui/SConscript | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index ba9fa8b7a..2b57be722 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -71,7 +71,7 @@ if GetOption('extras'): raylib_libs = common + ["raylib"] if arch == "larch64": - raylib_libs += ["GLESv2", "wayland-client", "wayland-egl", "EGL"] + raylib_libs += ["GLESv2", "EGL", "gdb", "drm"] else: raylib_libs += ["GL"] From 35296a869286f7b19a9a5da9b2b7b36317d0717e Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 6 Oct 2025 00:56:13 -0700 Subject: [PATCH 118/910] flip setting order (#36266) flip --- selfdrive/ui/layouts/settings/device.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/layouts/settings/device.py b/selfdrive/ui/layouts/settings/device.py index c41c8b0a2..62abd2b99 100644 --- a/selfdrive/ui/layouts/settings/device.py +++ b/selfdrive/ui/layouts/settings/device.py @@ -55,8 +55,8 @@ class DeviceLayout(Widget): self._pair_device_btn, button_item("Driver Camera", "PREVIEW", DESCRIPTIONS['driver_camera'], callback=self._show_driver_camera, enabled=ui_state.is_offroad), button_item("Reset Calibration", "RESET", DESCRIPTIONS['reset_calibration'], callback=self._reset_calibration_prompt), - regulatory_btn := button_item("Regulatory", "VIEW", callback=self._on_regulatory), button_item("Review Training Guide", "REVIEW", DESCRIPTIONS['review_guide'], self._on_review_training_guide), + regulatory_btn := button_item("Regulatory", "VIEW", callback=self._on_regulatory), button_item("Change Language", "CHANGE", callback=self._show_language_selection, enabled=ui_state.is_offroad), dual_button_item("Reboot", "Power Off", left_callback=self._reboot_prompt, right_callback=self._power_off_prompt), ] From a7fe9db773e126147c84c91015a7869e4ebb0fea Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Mon, 6 Oct 2025 16:37:50 -0700 Subject: [PATCH 119/910] fix installer build --- selfdrive/ui/SConscript | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index 2b57be722..7ede8c394 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -71,7 +71,7 @@ if GetOption('extras'): raylib_libs = common + ["raylib"] if arch == "larch64": - raylib_libs += ["GLESv2", "EGL", "gdb", "drm"] + raylib_libs += ["GLESv2", "EGL", "gbm", "drm"] else: raylib_libs += ["GL"] From e1912fa5bef1519451a9f082abaeb4fc2364e1cb Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 8 Oct 2025 03:51:37 -0700 Subject: [PATCH 120/910] raylib: speed up polygon shader (#36275) * actually works * fix shader grad * switch * our own triangulate * this is amazing * ok 100 is too much for 3x. 10? * fix colors * review intern chad * fmt * rm for the line count * bye * rm * see the diff * start to revert nulleffect * fix * fix * always feather * aliasing doesn't seem necessary * aliasing doesn't seem necessary * fix lane lines disappearing halfway up due to buggy deduping -- very simple triangulation function takes ~same CPU time + same GPU utilization on PC (nvidia-smi) * remove old * even simpler triangulate * this is useless * more revert * split color out again * clean up ai bs * back to original names * more clean up * stop it * this limiting logic split out feels more even // less super dense * typing * clean up a little * move to get grad color * RM * flip * document * clean up * clean up * clean * clean up * not a "state" * clean up * that did nothing * cmt --- selfdrive/ui/onroad/model_renderer.py | 32 +-- system/ui/lib/shader_polygon.py | 277 ++++++++++---------------- 2 files changed, 118 insertions(+), 191 deletions(-) diff --git a/selfdrive/ui/onroad/model_renderer.py b/selfdrive/ui/onroad/model_renderer.py index a770c0f92..3877da81c 100644 --- a/selfdrive/ui/onroad/model_renderer.py +++ b/selfdrive/ui/onroad/model_renderer.py @@ -8,7 +8,7 @@ from openpilot.common.params import Params from openpilot.selfdrive.locationd.calibrationd import HEIGHT_INIT from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.system.ui.lib.application import gui_app -from openpilot.system.ui.lib.shader_polygon import draw_polygon +from openpilot.system.ui.lib.shader_polygon import draw_polygon, Gradient from openpilot.system.ui.widgets import Widget CLIP_MARGIN = 500 @@ -66,12 +66,12 @@ class ModelRenderer(Widget): self._transform_dirty = True self._clip_region = None - self._exp_gradient = { - 'start': (0.0, 1.0), # Bottom of path - 'end': (0.0, 0.0), # Top of path - 'colors': [], - 'stops': [], - } + self._exp_gradient = Gradient( + start=(0.0, 1.0), # Bottom of path + end=(0.0, 0.0), # Top of path + colors=[], + stops=[], + ) # Get longitudinal control setting from car parameters if car_params := Params().get("CarParams"): @@ -226,8 +226,8 @@ class ModelRenderer(Widget): i += 1 + (1 if (i + 2) < max_len else 0) # Store the gradient in the path object - self._exp_gradient['colors'] = segment_colors - self._exp_gradient['stops'] = gradient_stops + self._exp_gradient.colors = segment_colors + self._exp_gradient.stops = gradient_stops def _update_lead_vehicle(self, d_rel, v_rel, point, rect): speed_buff, lead_buff = 10.0, 40.0 @@ -281,7 +281,7 @@ class ModelRenderer(Widget): if self._experimental_mode: # Draw with acceleration coloring - if len(self._exp_gradient['colors']) > 1: + if len(self._exp_gradient.colors) > 1: draw_polygon(self._rect, self._path.projected_points, gradient=self._exp_gradient) else: draw_polygon(self._rect, self._path.projected_points, rl.Color(255, 255, 255, 30)) @@ -289,12 +289,12 @@ class ModelRenderer(Widget): # Blend throttle/no throttle colors based on transition blend_factor = round(self._blend_filter.x * 100) / 100 blended_colors = self._blend_colors(NO_THROTTLE_COLORS, THROTTLE_COLORS, blend_factor) - gradient = { - 'start': (0.0, 1.0), # Bottom of path - 'end': (0.0, 0.0), # Top of path - 'colors': blended_colors, - 'stops': [0.0, 0.5, 1.0], - } + gradient = Gradient( + start=(0.0, 1.0), # Bottom of path + end=(0.0, 0.0), # Top of path + colors=blended_colors, + stops=[0.0, 0.5, 1.0], + ) draw_polygon(self._rect, self._path.projected_points, gradient=gradient) def _draw_lead_indicator(self): diff --git a/system/ui/lib/shader_polygon.py b/system/ui/lib/shader_polygon.py index d03ad5d8c..44c26c80e 100644 --- a/system/ui/lib/shader_polygon.py +++ b/system/ui/lib/shader_polygon.py @@ -1,9 +1,33 @@ import platform import pyray as rl import numpy as np -from typing import Any +from dataclasses import dataclass +from typing import Any, Optional, cast +from openpilot.system.ui.lib.application import gui_app + +MAX_GRADIENT_COLORS = 15 # includes stops as well + + +@dataclass +class Gradient: + start: tuple[float, float] + end: tuple[float, float] + colors: list[rl.Color] + stops: list[float] + + def __post_init__(self): + if len(self.colors) > MAX_GRADIENT_COLORS: + self.colors = self.colors[:MAX_GRADIENT_COLORS] + print(f"Warning: Gradient colors truncated to {MAX_GRADIENT_COLORS} entries") + + if len(self.stops) > MAX_GRADIENT_COLORS: + self.stops = self.stops[:MAX_GRADIENT_COLORS] + print(f"Warning: Gradient stops truncated to {MAX_GRADIENT_COLORS} entries") + + if not len(self.stops): + color_count = min(len(self.colors), MAX_GRADIENT_COLORS) + self.stops = [i / max(1, color_count - 1) for i in range(color_count)] -MAX_GRADIENT_COLORS = 15 VERSION = """ #version 300 es @@ -18,100 +42,43 @@ FRAGMENT_SHADER = VERSION + """ in vec2 fragTexCoord; out vec4 finalColor; -uniform vec2 points[100]; -uniform int pointCount; uniform vec4 fillColor; -uniform vec2 resolution; +// Gradient line defined in *screen pixels* uniform int useGradient; -uniform vec2 gradientStart; -uniform vec2 gradientEnd; +uniform vec2 gradientStart; // e.g. vec2(0, 0) +uniform vec2 gradientEnd; // e.g. vec2(0, screenHeight) uniform vec4 gradientColors[15]; uniform float gradientStops[15]; uniform int gradientColorCount; -vec4 getGradientColor(vec2 pos) { - vec2 gradientDir = gradientEnd - gradientStart; - float gradientLength = length(gradientDir); - if (gradientLength < 0.001) return gradientColors[0]; +vec4 getGradientColor(vec2 p) { + // Compute t from screen-space position + vec2 d = gradientStart - gradientEnd; + float len2 = max(dot(d, d), 1e-6); + float t = clamp(dot(p - gradientEnd, d) / len2, 0.0, 1.0); - vec2 normalizedDir = gradientDir / gradientLength; - float t = clamp(dot(pos - gradientStart, normalizedDir) / gradientLength, 0.0, 1.0); + // Clamp to range + float t0 = gradientStops[0]; + float tn = gradientStops[gradientColorCount-1]; + if (t <= t0) return gradientColors[0]; + if (t >= tn) return gradientColors[gradientColorCount-1]; - if (gradientColorCount <= 1) return gradientColors[0]; - - // handle t before first / after last stop - if (t <= gradientStops[0]) return gradientColors[0]; - if (t >= gradientStops[gradientColorCount-1]) return gradientColors[gradientColorCount-1]; for (int i = 0; i < gradientColorCount - 1; i++) { - if (t >= gradientStops[i] && t <= gradientStops[i+1]) { - float segmentT = (t - gradientStops[i]) / (gradientStops[i+1] - gradientStops[i]); - return mix(gradientColors[i], gradientColors[i+1], segmentT); + float a = gradientStops[i]; + float b = gradientStops[i+1]; + if (t >= a && t <= b) { + float k = (t - a) / max(b - a, 1e-6); + return mix(gradientColors[i], gradientColors[i+1], k); } } return gradientColors[gradientColorCount-1]; } -bool isPointInsidePolygon(vec2 p) { - if (pointCount < 3) return false; - int crossings = 0; - for (int i = 0, j = pointCount - 1; i < pointCount; j = i++) { - vec2 pi = points[i]; - vec2 pj = points[j]; - if (distance(pi, pj) < 0.001) continue; - if (((pi.y > p.y) != (pj.y > p.y)) && - (p.x < (pj.x - pi.x) * (p.y - pi.y) / (pj.y - pi.y + 0.001) + pi.x)) { - crossings++; - } - } - return (crossings & 1) == 1; -} - -float distanceToEdge(vec2 p) { - float minDist = 1000.0; - - for (int i = 0, j = pointCount - 1; i < pointCount; j = i++) { - vec2 edge0 = points[j]; - vec2 edge1 = points[i]; - - if (distance(edge0, edge1) < 0.0001) continue; - - vec2 v1 = p - edge0; - vec2 v2 = edge1 - edge0; - float l2 = dot(v2, v2); - - if (l2 < 0.0001) { - float dist = length(v1); - minDist = min(minDist, dist); - continue; - } - - float t = clamp(dot(v1, v2) / l2, 0.0, 1.0); - vec2 projection = edge0 + t * v2; - float dist = length(p - projection); - minDist = min(minDist, dist); - } - - return minDist; -} - void main() { - vec2 pixel = fragTexCoord * resolution; - - bool inside = isPointInsidePolygon(pixel); - float sd = (inside ? 1.0 : -1.0) * distanceToEdge(pixel); - - // ~1 pixel wide anti-aliasing - float w = max(0.75, fwidth(sd)); - - float alpha = smoothstep(-w, w, sd); - if (alpha > 0.0){ - vec4 color = useGradient == 1 ? getGradientColor(pixel) : fillColor; - finalColor = vec4(color.rgb, color.a * alpha); - } else { - discard; - } + // TODO: do proper antialiasing + finalColor = useGradient == 1 ? getGradientColor(gl_FragCoord.xy) : fillColor; } """ @@ -149,14 +116,10 @@ class ShaderState: self.initialized = False self.shader = None - self.white_texture = None # Shader uniform locations self.locations = { - 'pointCount': None, 'fillColor': None, - 'resolution': None, - 'points': None, 'useGradient': None, 'gradientStart': None, 'gradientEnd': None, @@ -167,12 +130,8 @@ class ShaderState: } # Pre-allocated FFI objects - self.point_count_ptr = rl.ffi.new("int[]", [0]) - self.resolution_ptr = rl.ffi.new("float[]", [0.0, 0.0]) self.fill_color_ptr = rl.ffi.new("float[]", [0.0, 0.0, 0.0, 0.0]) self.use_gradient_ptr = rl.ffi.new("int[]", [0]) - self.gradient_start_ptr = rl.ffi.new("float[]", [0.0, 0.0]) - self.gradient_end_ptr = rl.ffi.new("float[]", [0.0, 0.0]) self.color_count_ptr = rl.ffi.new("int[]", [0]) self.gradient_colors_ptr = rl.ffi.new("float[]", MAX_GRADIENT_COLORS * 4) self.gradient_stops_ptr = rl.ffi.new("float[]", MAX_GRADIENT_COLORS) @@ -183,30 +142,19 @@ class ShaderState: self.shader = rl.load_shader_from_memory(VERTEX_SHADER, FRAGMENT_SHADER) - # Create and cache white texture - white_img = rl.gen_image_color(2, 2, rl.WHITE) - self.white_texture = rl.load_texture_from_image(white_img) - rl.set_texture_filter(self.white_texture, rl.TEXTURE_FILTER_BILINEAR) - rl.unload_image(white_img) - # Cache all uniform locations for uniform in self.locations.keys(): self.locations[uniform] = rl.get_shader_location(self.shader, uniform) - # Setup default MVP matrix - mvp_ptr = rl.ffi.new("float[16]", [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0]) - rl.set_shader_value_matrix(self.shader, self.locations['mvp'], rl.Matrix(*mvp_ptr)) + # Orthographic MVP (origin top-left) + proj = rl.matrix_ortho(0, gui_app.width, gui_app.height, 0, -1, 1) + rl.set_shader_value_matrix(self.shader, self.locations['mvp'], proj) self.initialized = True def cleanup(self): if not self.initialized: return - - if self.white_texture: - rl.unload_texture(self.white_texture) - self.white_texture = None - if self.shader: rl.unload_shader(self.shader) self.shader = None @@ -214,103 +162,82 @@ class ShaderState: self.initialized = False -def _configure_shader_color(state, color, gradient, clipped_rect, original_rect): - use_gradient = 1 if gradient else 0 +def _configure_shader_color(state: ShaderState, color: Optional[rl.Color], # noqa: UP045 + gradient: Gradient | None, origin_rect: rl.Rectangle): + assert (color is not None) != (gradient is not None), "Either color or gradient must be provided" + + use_gradient = 1 if (gradient is not None and len(gradient.colors) >= 1) else 0 state.use_gradient_ptr[0] = use_gradient rl.set_shader_value(state.shader, state.locations['useGradient'], state.use_gradient_ptr, UNIFORM_INT) if use_gradient: - start = np.array(gradient['start']) * np.array([original_rect.width, original_rect.height]) + np.array([original_rect.x, original_rect.y]) - end = np.array(gradient['end']) * np.array([original_rect.width, original_rect.height]) + np.array([original_rect.x, original_rect.y]) - start = start - np.array([clipped_rect.x, clipped_rect.y]) - end = end - np.array([clipped_rect.x, clipped_rect.y]) - state.gradient_start_ptr[0:2] = start.astype(np.float32) - state.gradient_end_ptr[0:2] = end.astype(np.float32) - rl.set_shader_value(state.shader, state.locations['gradientStart'], state.gradient_start_ptr, UNIFORM_VEC2) - rl.set_shader_value(state.shader, state.locations['gradientEnd'], state.gradient_end_ptr, UNIFORM_VEC2) + gradient = cast(Gradient, gradient) + state.color_count_ptr[0] = len(gradient.colors) + for i in range(len(gradient.colors)): + c = gradient.colors[i] + base = i * 4 + state.gradient_colors_ptr[base:base + 4] = [c.r / 255.0, c.g / 255.0, c.b / 255.0, c.a / 255.0] + rl.set_shader_value_v(state.shader, state.locations['gradientColors'], state.gradient_colors_ptr, UNIFORM_VEC4, len(gradient.colors)) - colors = gradient['colors'] - color_count = min(len(colors), MAX_GRADIENT_COLORS) - state.color_count_ptr[0] = color_count - for i, c in enumerate(colors[:color_count]): - base_idx = i * 4 - state.gradient_colors_ptr[base_idx:base_idx+4] = [c.r / 255.0, c.g / 255.0, c.b / 255.0, c.a / 255.0] - rl.set_shader_value_v(state.shader, state.locations['gradientColors'], state.gradient_colors_ptr, UNIFORM_VEC4, color_count) - - stops = gradient.get('stops', [i / max(1, color_count - 1) for i in range(color_count)]) - stops = np.clip(stops[:color_count], 0.0, 1.0) - state.gradient_stops_ptr[0:color_count] = stops - rl.set_shader_value_v(state.shader, state.locations['gradientStops'], state.gradient_stops_ptr, UNIFORM_FLOAT, color_count) + for i in range(len(gradient.stops)): + s = float(gradient.stops[i]) + state.gradient_stops_ptr[i] = 0.0 if s < 0.0 else 1.0 if s > 1.0 else s + rl.set_shader_value_v(state.shader, state.locations['gradientStops'], state.gradient_stops_ptr, UNIFORM_FLOAT, len(gradient.stops)) rl.set_shader_value(state.shader, state.locations['gradientColorCount'], state.color_count_ptr, UNIFORM_INT) + + # Map normalized start/end to screen pixels + start_vec = rl.Vector2(origin_rect.x + gradient.start[0] * origin_rect.width, origin_rect.y + gradient.start[1] * origin_rect.height) + end_vec = rl.Vector2(origin_rect.x + gradient.end[0] * origin_rect.width, origin_rect.y + gradient.end[1] * origin_rect.height) + rl.set_shader_value(state.shader, state.locations['gradientStart'], start_vec, UNIFORM_VEC2) + rl.set_shader_value(state.shader, state.locations['gradientEnd'], end_vec, UNIFORM_VEC2) else: color = color or rl.WHITE state.fill_color_ptr[0:4] = [color.r / 255.0, color.g / 255.0, color.b / 255.0, color.a / 255.0] rl.set_shader_value(state.shader, state.locations['fillColor'], state.fill_color_ptr, UNIFORM_VEC4) -def draw_polygon(origin_rect: rl.Rectangle, points: np.ndarray, color=None, gradient=None): - """ - Draw a complex polygon using shader-based even-odd fill rule +def triangulate(pts: np.ndarray) -> list[tuple[float, float]]: + """Only supports simple polygons with two chains (ribbon).""" - Args: - rect: Rectangle defining the drawing area - points: numpy array of (x,y) points defining the polygon - color: Solid fill color (rl.Color) - gradient: Dict with gradient parameters: - { - 'start': (x1, y1), # Start point (normalized 0-1) - 'end': (x2, y2), # End point (normalized 0-1) - 'colors': [rl.Color], # List of colors at stops - 'stops': [float] # List of positions (0-1) - } + # TODO: consider deduping close screenspace points + # interleave points to produce a triangle strip + assert len(pts) % 2 == 0, "Interleaving expects even number of points" + + tri_strip = [] + for i in range(len(pts) // 2): + tri_strip.append(pts[i]) + tri_strip.append(pts[-i - 1]) + + return cast(list, np.array(tri_strip).tolist()) + + +def draw_polygon(origin_rect: rl.Rectangle, points: np.ndarray, + color: Optional[rl.Color] = None, gradient: Gradient | None = None): # noqa: UP045 + + """ + Draw a ribbon polygon (two chains) with a triangle strip and gradient. + - Input must be [L0..Lk-1, Rk-1..R0], even count, no crossings/holes. """ if len(points) < 3: return + # Initialize shader on-demand state = ShaderState.get_instance() - if not state.initialized: - state.initialize() + state.initialize() - # Find bounding box - min_xy = np.min(points, axis=0) - max_xy = np.max(points, axis=0) - clip_x = max(origin_rect.x, min_xy[0]) - clip_y = max(origin_rect.y, min_xy[1]) - clip_right = min(origin_rect.x + origin_rect.width, max_xy[0]) - clip_bottom = min(origin_rect.y + origin_rect.height, max_xy[1]) + # Ensure (N,2) float32 contiguous array + pts = np.ascontiguousarray(points, dtype=np.float32) + assert pts.ndim == 2 and pts.shape[1] == 2, "points must be (N,2)" - # Check if polygon is completely off-screen - if clip_x >= clip_right or clip_y >= clip_bottom: - return + # Configure gradient shader + _configure_shader_color(state, color, gradient, origin_rect) - clipped_rect = rl.Rectangle(clip_x, clip_y, clip_right - clip_x, clip_bottom - clip_y) + # Triangulate via interleaving + tri_strip = triangulate(pts) - # Transform points relative to the CLIPPED area - transformed_points = points - np.array([clip_x, clip_y]) - - # Set shader values - state.point_count_ptr[0] = len(transformed_points) - rl.set_shader_value(state.shader, state.locations['pointCount'], state.point_count_ptr, UNIFORM_INT) - - state.resolution_ptr[0:2] = [clipped_rect.width, clipped_rect.height] - rl.set_shader_value(state.shader, state.locations['resolution'], state.resolution_ptr, UNIFORM_VEC2) - - flat_points = np.ascontiguousarray(transformed_points.flatten().astype(np.float32)) - points_ptr = rl.ffi.cast("float *", flat_points.ctypes.data) - rl.set_shader_value_v(state.shader, state.locations['points'], points_ptr, UNIFORM_VEC2, len(transformed_points)) - - _configure_shader_color(state, color, gradient, clipped_rect, origin_rect) - - # Render + # Draw strip, color here doesn't matter rl.begin_shader_mode(state.shader) - rl.draw_texture_pro( - state.white_texture, - rl.Rectangle(0, 0, 2, 2), - clipped_rect, - rl.Vector2(0, 0), - 0.0, - rl.WHITE, - ) + rl.draw_triangle_strip(tri_strip, len(tri_strip), rl.WHITE) rl.end_shader_mode() From e62781cccbbbd4eeed89c3c18a8873b90dccdc74 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 8 Oct 2025 04:05:49 -0700 Subject: [PATCH 121/910] Revert "raylib: font sizes from QT should match (#36237)" This reverts commit 7933c10c975ee24afbd2ef5554ba85005c94101a. --- selfdrive/ui/layouts/sidebar.py | 29 ++++++++++--------------- selfdrive/ui/widgets/exp_mode_button.py | 6 ++--- system/ui/lib/application.py | 15 ------------- system/ui/lib/text_measure.py | 3 +-- system/ui/widgets/html_render.py | 10 ++++----- 5 files changed, 20 insertions(+), 43 deletions(-) diff --git a/selfdrive/ui/layouts/sidebar.py b/selfdrive/ui/layouts/sidebar.py index 34354ecba..5987d062f 100644 --- a/selfdrive/ui/layouts/sidebar.py +++ b/selfdrive/ui/layouts/sidebar.py @@ -216,21 +216,14 @@ class Sidebar(Widget): # Draw border rl.draw_rectangle_rounded_lines_ex(metric_rect, 0.3, 10, 2, Colors.METRIC_BORDER) - label_size = measure_text_cached(self._font_bold, metric.label, FONT_SIZE) - value_size = measure_text_cached(self._font_bold, metric.value, FONT_SIZE) - text_height = label_size.y + value_size.y - - label_y = metric_rect.y + (metric_rect.height - text_height) / 2 - value_y = label_y + label_size.y - - # label - rl.draw_text_ex(self._font_bold, metric.label, rl.Vector2( - metric_rect.x + 22 + (metric_rect.width - 22 - label_size.x) / 2, - label_y - ), FONT_SIZE, 0, Colors.WHITE) - - # value - rl.draw_text_ex(self._font_bold, metric.value, rl.Vector2( - metric_rect.x + 22 + (metric_rect.width - 22 - value_size.x) / 2, - value_y - ), FONT_SIZE, 0, Colors.WHITE) + # Draw label and value + labels = [metric.label, metric.value] + text_y = metric_rect.y + (metric_rect.height / 2 - len(labels) * FONT_SIZE) + for text in labels: + text_size = measure_text_cached(self._font_bold, text, FONT_SIZE) + text_y += text_size.y + text_pos = rl.Vector2( + metric_rect.x + 22 + (metric_rect.width - 22 - text_size.x) / 2, + text_y + ) + rl.draw_text_ex(self._font_bold, text, text_pos, FONT_SIZE, 0, Colors.WHITE) diff --git a/selfdrive/ui/widgets/exp_mode_button.py b/selfdrive/ui/widgets/exp_mode_button.py index 6fe7b6843..961876895 100644 --- a/selfdrive/ui/widgets/exp_mode_button.py +++ b/selfdrive/ui/widgets/exp_mode_button.py @@ -1,6 +1,6 @@ import pyray as rl from openpilot.common.params import Params -from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE +from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.widgets import Widget @@ -9,7 +9,7 @@ class ExperimentalModeButton(Widget): super().__init__() self.img_width = 80 - self.horizontal_padding = 30 + self.horizontal_padding = 50 self.button_height = 125 self.params = Params() @@ -51,7 +51,7 @@ class ExperimentalModeButton(Widget): # Draw text label (left aligned) text = "EXPERIMENTAL MODE ON" if self.experimental_mode else "CHILL MODE ON" text_x = rect.x + self.horizontal_padding - text_y = rect.y + rect.height / 2 - 45 * FONT_SCALE // 2 # Center vertically + text_y = rect.y + rect.height / 2 - 45 // 2 # Center vertically rl.draw_text_ex(gui_app.font(FontWeight.NORMAL), text, rl.Vector2(int(text_x), int(text_y)), 45, 0, rl.BLACK) diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 755a335e4..473eaa5fa 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -30,10 +30,6 @@ SCALE = float(os.getenv("SCALE", "1.0")) DEFAULT_TEXT_SIZE = 60 DEFAULT_TEXT_COLOR = rl.WHITE -# Qt draws fonts accounting for ascent/descent differently, so compensate to match old styles -# The real scales for the fonts below range from 1.212 to 1.266 -FONT_SCALE = 1.242 - ASSETS_DIR = files("openpilot.selfdrive").joinpath("assets") FONT_DIR = ASSETS_DIR.joinpath("fonts") @@ -177,7 +173,6 @@ class GuiApplication: self._target_fps = fps self._set_styles() self._load_fonts() - self._patch_text_functions() if not PC: self._mouse.start() @@ -361,16 +356,6 @@ 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 _patch_text_functions(self): - # Wrap pyray text APIs to apply a global text size scale so our px sizes match Qt - if not hasattr(rl, "_orig_draw_text_ex"): - rl._orig_draw_text_ex = rl.draw_text_ex - - def _draw_text_ex_scaled(font, text, position, font_size, spacing, tint): - return rl._orig_draw_text_ex(font, text, position, font_size * FONT_SCALE, spacing, tint) - - rl.draw_text_ex = _draw_text_ex_scaled - def _set_log_callback(self): ffi_libc = cffi.FFI() ffi_libc.cdef(""" diff --git a/system/ui/lib/text_measure.py b/system/ui/lib/text_measure.py index fcb7b25cc..c172f9425 100644 --- a/system/ui/lib/text_measure.py +++ b/system/ui/lib/text_measure.py @@ -1,5 +1,4 @@ import pyray as rl -from openpilot.system.ui.lib.application import FONT_SCALE _cache: dict[int, rl.Vector2] = {} @@ -10,6 +9,6 @@ def measure_text_cached(font: rl.Font, text: str, font_size: int, spacing: int = if key in _cache: return _cache[key] - result = rl.measure_text_ex(font, text, font_size * FONT_SCALE, spacing) # noqa: TID251 + result = rl.measure_text_ex(font, text, font_size, spacing) # noqa: TID251 _cache[key] = result return result diff --git a/system/ui/widgets/html_render.py b/system/ui/widgets/html_render.py index 91a1ccbbc..2c3eeb379 100644 --- a/system/ui/widgets/html_render.py +++ b/system/ui/widgets/html_render.py @@ -3,7 +3,7 @@ import pyray as rl from dataclasses import dataclass from enum import Enum from typing import Any -from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE +from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel from openpilot.system.ui.lib.wrap_text import wrap_text from openpilot.system.ui.widgets import Widget @@ -176,8 +176,8 @@ class HtmlRenderer(Widget): wrapped_lines = wrap_text(font, element.content, element.font_size, int(content_width)) for line in wrapped_lines: - if current_y < rect.y - element.font_size * FONT_SCALE: - current_y += element.font_size * FONT_SCALE * element.line_height + if current_y < rect.y - element.font_size: + current_y += element.font_size * element.line_height continue if current_y > rect.y + rect.height: @@ -186,7 +186,7 @@ class HtmlRenderer(Widget): text_x = rect.x + (max(element.indent_level - 1, 0) * LIST_INDENT_PX) rl.draw_text_ex(font, line, rl.Vector2(text_x + padding, current_y), element.font_size, 0, self._text_color) - current_y += element.font_size * FONT_SCALE * element.line_height + current_y += element.font_size * element.line_height # Apply bottom margin current_y += element.margin_bottom @@ -210,7 +210,7 @@ class HtmlRenderer(Widget): wrapped_lines = wrap_text(font, element.content, element.font_size, int(usable_width)) for _ in wrapped_lines: - total_height += element.font_size * FONT_SCALE * element.line_height + total_height += element.font_size * element.line_height total_height += element.margin_bottom From 9f32f217e665e0f7162f88be39381da87ba645d9 Mon Sep 17 00:00:00 2001 From: felsager <76905857+felsager@users.noreply.github.com> Date: Wed, 8 Oct 2025 14:26:53 -0700 Subject: [PATCH 122/910] Latcontrol: type annotate update inputs and clip_curvature output (#36282) --- selfdrive/controls/lib/drive_helpers.py | 2 +- selfdrive/controls/lib/latcontrol.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/controls/lib/drive_helpers.py b/selfdrive/controls/lib/drive_helpers.py index e28fa3021..bf6dd04f6 100644 --- a/selfdrive/controls/lib/drive_helpers.py +++ b/selfdrive/controls/lib/drive_helpers.py @@ -22,7 +22,7 @@ def smooth_value(val, prev_val, tau, dt=DT_MDL): alpha = 1 - np.exp(-dt/tau) if tau > 0 else 1 return alpha * val + (1 - alpha) * prev_val -def clip_curvature(v_ego, prev_curvature, new_curvature, roll): +def clip_curvature(v_ego, prev_curvature, new_curvature, roll) -> tuple[float, bool]: # This function respects ISO lateral jerk and acceleration limits + a max curvature v_ego = max(v_ego, MIN_SPEED) max_curvature_rate = MAX_LATERAL_JERK / (v_ego ** 2) # inexact calculation, check https://github.com/commaai/openpilot/pull/24755 diff --git a/selfdrive/controls/lib/latcontrol.py b/selfdrive/controls/lib/latcontrol.py index 2a8b873e2..7cd04240d 100644 --- a/selfdrive/controls/lib/latcontrol.py +++ b/selfdrive/controls/lib/latcontrol.py @@ -15,7 +15,7 @@ class LatControl(ABC): self.steer_max = 1.0 @abstractmethod - def update(self, active, CS, VM, params, steer_limited_by_safety, desired_curvature, calibrated_pose, curvature_limited): + def update(self, active: bool, CS, VM, params, steer_limited_by_safety: bool, desired_curvature: float, calibrated_pose, curvature_limited: bool): pass def reset(self): From 2deb4e6f6577908f3befca0ce940e9f8de69292e Mon Sep 17 00:00:00 2001 From: felsager <76905857+felsager@users.noreply.github.com> Date: Wed, 8 Oct 2025 14:39:05 -0700 Subject: [PATCH 123/910] Lateral controllers: pass dt (delta time) explictly (#36281) --- selfdrive/car/tests/test_car_interfaces.py | 6 +++--- selfdrive/controls/controlsd.py | 8 ++++---- selfdrive/controls/lib/latcontrol.py | 7 +++---- selfdrive/controls/lib/latcontrol_angle.py | 4 ++-- selfdrive/controls/lib/latcontrol_pid.py | 4 ++-- selfdrive/controls/lib/latcontrol_torque.py | 4 ++-- selfdrive/controls/tests/test_latcontrol.py | 3 ++- 7 files changed, 18 insertions(+), 18 deletions(-) diff --git a/selfdrive/car/tests/test_car_interfaces.py b/selfdrive/car/tests/test_car_interfaces.py index c40443d7e..24d2faa0d 100644 --- a/selfdrive/car/tests/test_car_interfaces.py +++ b/selfdrive/car/tests/test_car_interfaces.py @@ -54,8 +54,8 @@ class TestCarInterfaces: # hypothesis also slows down significantly with just one more message draw LongControl(car_params) if car_params.steerControlType == CarParams.SteerControlType.angle: - LatControlAngle(car_params, car_interface) + LatControlAngle(car_params, car_interface, DT_CTRL) elif car_params.lateralTuning.which() == 'pid': - LatControlPID(car_params, car_interface) + LatControlPID(car_params, car_interface, DT_CTRL) elif car_params.lateralTuning.which() == 'torque': - LatControlTorque(car_params, car_interface) + LatControlTorque(car_params, car_interface, DT_CTRL) diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 029d16e59..4442e6cbe 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -6,7 +6,7 @@ from cereal import car, log import cereal.messaging as messaging from openpilot.common.constants import CV from openpilot.common.params import Params -from openpilot.common.realtime import config_realtime_process, Priority, Ratekeeper +from openpilot.common.realtime import config_realtime_process, DT_CTRL, Priority, Ratekeeper from openpilot.common.swaglog import cloudlog from opendbc.car.car_helpers import interfaces @@ -51,11 +51,11 @@ class Controls: self.VM = VehicleModel(self.CP) self.LaC: LatControl if self.CP.steerControlType == car.CarParams.SteerControlType.angle: - self.LaC = LatControlAngle(self.CP, self.CI) + self.LaC = LatControlAngle(self.CP, self.CI, DT_CTRL) elif self.CP.lateralTuning.which() == 'pid': - self.LaC = LatControlPID(self.CP, self.CI) + self.LaC = LatControlPID(self.CP, self.CI, DT_CTRL) elif self.CP.lateralTuning.which() == 'torque': - self.LaC = LatControlTorque(self.CP, self.CI) + self.LaC = LatControlTorque(self.CP, self.CI, DT_CTRL) def update(self): self.sm.update(15) diff --git a/selfdrive/controls/lib/latcontrol.py b/selfdrive/controls/lib/latcontrol.py index 7cd04240d..a97ed3e54 100644 --- a/selfdrive/controls/lib/latcontrol.py +++ b/selfdrive/controls/lib/latcontrol.py @@ -1,12 +1,11 @@ import numpy as np from abc import abstractmethod, ABC -from openpilot.common.realtime import DT_CTRL - class LatControl(ABC): - def __init__(self, CP, CI): - self.sat_count_rate = 1.0 * DT_CTRL + def __init__(self, CP, CI, dt): + self.dt = dt + self.sat_count_rate = 1.0 * self.dt self.sat_limit = CP.steerLimitTimer self.sat_count = 0. self.sat_check_min_speed = 10. diff --git a/selfdrive/controls/lib/latcontrol_angle.py b/selfdrive/controls/lib/latcontrol_angle.py index ac3515148..cea9f7481 100644 --- a/selfdrive/controls/lib/latcontrol_angle.py +++ b/selfdrive/controls/lib/latcontrol_angle.py @@ -8,8 +8,8 @@ STEER_ANGLE_SATURATION_THRESHOLD = 2.5 # Degrees class LatControlAngle(LatControl): - def __init__(self, CP, CI): - super().__init__(CP, CI) + def __init__(self, CP, CI, dt): + super().__init__(CP, CI, dt) self.sat_check_min_speed = 5. self.use_steer_limited_by_safety = CP.brand == "tesla" diff --git a/selfdrive/controls/lib/latcontrol_pid.py b/selfdrive/controls/lib/latcontrol_pid.py index 00a083509..d8eae451e 100644 --- a/selfdrive/controls/lib/latcontrol_pid.py +++ b/selfdrive/controls/lib/latcontrol_pid.py @@ -6,8 +6,8 @@ from openpilot.common.pid import PIDController class LatControlPID(LatControl): - def __init__(self, CP, CI): - super().__init__(CP, CI) + def __init__(self, CP, CI, dt): + super().__init__(CP, CI, dt) self.pid = PIDController((CP.lateralTuning.pid.kpBP, CP.lateralTuning.pid.kpV), (CP.lateralTuning.pid.kiBP, CP.lateralTuning.pid.kiV), k_f=CP.lateralTuning.pid.kf, pos_limit=self.steer_max, neg_limit=-self.steer_max) diff --git a/selfdrive/controls/lib/latcontrol_torque.py b/selfdrive/controls/lib/latcontrol_torque.py index 5a2814e08..bcaf93458 100644 --- a/selfdrive/controls/lib/latcontrol_torque.py +++ b/selfdrive/controls/lib/latcontrol_torque.py @@ -23,8 +23,8 @@ LOW_SPEED_Y = [15, 13, 10, 5] class LatControlTorque(LatControl): - def __init__(self, CP, CI): - super().__init__(CP, CI) + def __init__(self, CP, CI, dt): + super().__init__(CP, CI, dt) self.torque_params = CP.lateralTuning.torque.as_builder() self.torque_from_lateral_accel = CI.torque_from_lateral_accel() self.lateral_accel_from_torque = CI.lateral_accel_from_torque() diff --git a/selfdrive/controls/tests/test_latcontrol.py b/selfdrive/controls/tests/test_latcontrol.py index 0ce06dc99..c564e9d3e 100644 --- a/selfdrive/controls/tests/test_latcontrol.py +++ b/selfdrive/controls/tests/test_latcontrol.py @@ -7,6 +7,7 @@ from opendbc.car.toyota.values import CAR as TOYOTA from opendbc.car.nissan.values import CAR as NISSAN from opendbc.car.gm.values import CAR as GM from opendbc.car.vehicle_model import VehicleModel +from openpilot.common.realtime import DT_CTRL from openpilot.selfdrive.controls.lib.latcontrol_pid import LatControlPID from openpilot.selfdrive.controls.lib.latcontrol_torque import LatControlTorque from openpilot.selfdrive.controls.lib.latcontrol_angle import LatControlAngle @@ -22,7 +23,7 @@ class TestLatControl: CI = CarInterface(CP) VM = VehicleModel(CP) - controller = controller(CP.as_reader(), CI) + controller = controller(CP.as_reader(), CI, DT_CTRL) CS = car.CarState.new_message() CS.vEgo = 30 From 0b62dbe16b80acb69515b45a80a966f49d806c64 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 8 Oct 2025 17:29:45 -0700 Subject: [PATCH 124/910] raylib: more closely match Qt alert sizes (#36283) * hmm this doesn't work * clean up * more * bad fmtr * match sidebar net texts * better --- selfdrive/ui/layouts/sidebar.py | 8 ++++---- selfdrive/ui/onroad/alert_renderer.py | 19 +++++++++---------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/selfdrive/ui/layouts/sidebar.py b/selfdrive/ui/layouts/sidebar.py index 5987d062f..8fa38145d 100644 --- a/selfdrive/ui/layouts/sidebar.py +++ b/selfdrive/ui/layouts/sidebar.py @@ -40,13 +40,13 @@ class Colors: NETWORK_TYPES = { - NetworkType.none: "Offline", - NetworkType.wifi: "WiFi", + NetworkType.none: "--", + NetworkType.wifi: "Wi-Fi", + NetworkType.ethernet: "ETH", NetworkType.cell2G: "2G", NetworkType.cell3G: "3G", NetworkType.cell4G: "LTE", NetworkType.cell5G: "5G", - NetworkType.ethernet: "Ethernet", } @@ -172,7 +172,7 @@ class Sidebar(Widget): # Microphone button if self._recording_audio: - self._mic_indicator_rect = rl.Rectangle(rect.x + rect.width - 138, rect.y + 245, 75, 40) + self._mic_indicator_rect = rl.Rectangle(rect.x + rect.width - 130, rect.y + 245, 75, 40) mic_pressed = mouse_down and rl.check_collision_point_rec(mouse_pos, self._mic_indicator_rect) bg_color = rl.Color(Colors.DANGER.r, Colors.DANGER.g, Colors.DANGER.b, int(255 * 0.65)) if mic_pressed else Colors.DANGER diff --git a/selfdrive/ui/onroad/alert_renderer.py b/selfdrive/ui/onroad/alert_renderer.py index e81129b13..2802363f8 100644 --- a/selfdrive/ui/onroad/alert_renderer.py +++ b/selfdrive/ui/onroad/alert_renderer.py @@ -21,6 +21,11 @@ ALERT_FONT_SMALL = 66 ALERT_FONT_MEDIUM = 74 ALERT_FONT_BIG = 88 +ALERT_HEIGHTS = { + AlertSize.small: 271, + AlertSize.mid: 420, +} + SELFDRIVE_STATE_TIMEOUT = 5 # Seconds SELFDRIVE_UNRESPONSIVE_TIMEOUT = 10 # Seconds @@ -119,15 +124,9 @@ class AlertRenderer(Widget): if size == AlertSize.full: return rect - height = (ALERT_FONT_MEDIUM + 2 * ALERT_PADDING if size == AlertSize.small else - ALERT_FONT_BIG + ALERT_LINE_SPACING + ALERT_FONT_SMALL + 2 * ALERT_PADDING) - - return rl.Rectangle( - rect.x + ALERT_MARGIN, - rect.y + rect.height - ALERT_MARGIN - height, - rect.width - 2 * ALERT_MARGIN, - height - ) + h = ALERT_HEIGHTS.get(size, rect.height) + return rl.Rectangle(rect.x + ALERT_MARGIN, rect.y + rect.height - h + ALERT_MARGIN, + rect.width - ALERT_MARGIN * 2, h - ALERT_MARGIN * 2) def _draw_background(self, rect: rl.Rectangle, alert: Alert) -> None: color = ALERT_COLORS.get(alert.status, ALERT_COLORS[AlertStatus.normal]) @@ -152,7 +151,7 @@ class AlertRenderer(Widget): font_size1 = 132 if is_long else 177 align_ment = rl.GuiTextAlignment.TEXT_ALIGN_CENTER vertical_align = rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE - text_rect = rl.Rectangle(rect.x, rect.y, rect.width, rect.height // 2) + text_rect = rl.Rectangle(rect.x, rect.y, rect.width, rect.height) gui_text_box(text_rect, alert.text1, font_size1, alignment=align_ment, alignment_vertical=vertical_align, font_weight=FontWeight.BOLD) text_rect.y = rect.y + rect.height // 2 From 226465e8827e76579b8aca1cbb25b9734d0fb124 Mon Sep 17 00:00:00 2001 From: felsager <76905857+felsager@users.noreply.github.com> Date: Wed, 8 Oct 2025 18:29:54 -0700 Subject: [PATCH 125/910] Latcontrol: refactor pid error to factor out lateral jerk component (#36280) --- selfdrive/controls/controlsd.py | 6 ++-- selfdrive/controls/lib/latcontrol.py | 2 +- selfdrive/controls/lib/latcontrol_angle.py | 2 +- selfdrive/controls/lib/latcontrol_pid.py | 2 +- selfdrive/controls/lib/latcontrol_torque.py | 34 ++++++++++++++------- selfdrive/controls/tests/test_latcontrol.py | 6 ++-- selfdrive/test/process_replay/ref_commit | 2 +- 7 files changed, 34 insertions(+), 20 deletions(-) diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 4442e6cbe..9e31ac152 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -17,6 +17,7 @@ from openpilot.selfdrive.controls.lib.latcontrol_pid import LatControlPID from openpilot.selfdrive.controls.lib.latcontrol_angle import LatControlAngle, STEER_ANGLE_SATURATION_THRESHOLD from openpilot.selfdrive.controls.lib.latcontrol_torque import LatControlTorque from openpilot.selfdrive.controls.lib.longcontrol import LongControl +from openpilot.selfdrive.modeld.modeld import LAT_SMOOTH_SECONDS from openpilot.selfdrive.locationd.helpers import PoseCalibrator, Pose State = log.SelfdriveState.OpenpilotState @@ -35,7 +36,7 @@ class Controls: self.CI = interfaces[self.CP.carFingerprint](self.CP) - self.sm = messaging.SubMaster(['liveParameters', 'liveTorqueParameters', 'modelV2', 'selfdriveState', + self.sm = messaging.SubMaster(['liveDelay', 'liveParameters', 'liveTorqueParameters', 'modelV2', 'selfdriveState', 'liveCalibration', 'livePose', 'longitudinalPlan', 'carState', 'carOutput', 'driverMonitoringState', 'onroadEvents', 'driverAssistance'], poll='selfdriveState') self.pm = messaging.PubMaster(['carControl', 'controlsState']) @@ -117,11 +118,12 @@ class Controls: # Reset desired curvature to current to avoid violating the limits on engage new_desired_curvature = model_v2.action.desiredCurvature if CC.latActive else self.curvature self.desired_curvature, curvature_limited = clip_curvature(CS.vEgo, self.desired_curvature, new_desired_curvature, lp.roll) + lat_delay = self.sm["liveDelay"].lateralDelay + LAT_SMOOTH_SECONDS actuators.curvature = self.desired_curvature steer, steeringAngleDeg, lac_log = self.LaC.update(CC.latActive, CS, self.VM, lp, self.steer_limited_by_safety, self.desired_curvature, - curvature_limited) # TODO what if not available + curvature_limited, lat_delay) actuators.torque = float(steer) actuators.steeringAngleDeg = float(steeringAngleDeg) # Ensure no NaNs/Infs diff --git a/selfdrive/controls/lib/latcontrol.py b/selfdrive/controls/lib/latcontrol.py index a97ed3e54..00cf0dab9 100644 --- a/selfdrive/controls/lib/latcontrol.py +++ b/selfdrive/controls/lib/latcontrol.py @@ -14,7 +14,7 @@ class LatControl(ABC): self.steer_max = 1.0 @abstractmethod - def update(self, active: bool, CS, VM, params, steer_limited_by_safety: bool, desired_curvature: float, calibrated_pose, curvature_limited: bool): + def update(self, active: bool, CS, VM, params, steer_limited_by_safety: bool, desired_curvature: float, curvature_limited: bool, lat_delay: float): pass def reset(self): diff --git a/selfdrive/controls/lib/latcontrol_angle.py b/selfdrive/controls/lib/latcontrol_angle.py index cea9f7481..808c9a659 100644 --- a/selfdrive/controls/lib/latcontrol_angle.py +++ b/selfdrive/controls/lib/latcontrol_angle.py @@ -13,7 +13,7 @@ class LatControlAngle(LatControl): self.sat_check_min_speed = 5. self.use_steer_limited_by_safety = CP.brand == "tesla" - def update(self, active, CS, VM, params, steer_limited_by_safety, desired_curvature, curvature_limited): + def update(self, active, CS, VM, params, steer_limited_by_safety, desired_curvature, curvature_limited, lat_delay): angle_log = log.ControlsState.LateralAngleState.new_message() if not active: diff --git a/selfdrive/controls/lib/latcontrol_pid.py b/selfdrive/controls/lib/latcontrol_pid.py index d8eae451e..4cc1c47f6 100644 --- a/selfdrive/controls/lib/latcontrol_pid.py +++ b/selfdrive/controls/lib/latcontrol_pid.py @@ -13,7 +13,7 @@ class LatControlPID(LatControl): k_f=CP.lateralTuning.pid.kf, pos_limit=self.steer_max, neg_limit=-self.steer_max) self.get_steer_feedforward = CI.get_steer_feedforward_function() - def update(self, active, CS, VM, params, steer_limited_by_safety, desired_curvature, curvature_limited): + def update(self, active, CS, VM, params, steer_limited_by_safety, desired_curvature, curvature_limited, lat_delay): pid_log = log.ControlsState.LateralPIDState.new_message() pid_log.steeringAngleDeg = float(CS.steeringAngleDeg) pid_log.steeringRateDeg = float(CS.steeringRateDeg) diff --git a/selfdrive/controls/lib/latcontrol_torque.py b/selfdrive/controls/lib/latcontrol_torque.py index bcaf93458..e50d91351 100644 --- a/selfdrive/controls/lib/latcontrol_torque.py +++ b/selfdrive/controls/lib/latcontrol_torque.py @@ -1,9 +1,11 @@ import math import numpy as np +from collections import deque from cereal import log from opendbc.car.lateral import FRICTION_THRESHOLD, get_friction from openpilot.common.constants import ACCELERATION_DUE_TO_GRAVITY +from openpilot.selfdrive.controls.lib.drive_helpers import MIN_SPEED from openpilot.selfdrive.controls.lib.latcontrol import LatControl from openpilot.common.pid import PIDController @@ -29,9 +31,11 @@ class LatControlTorque(LatControl): self.torque_from_lateral_accel = CI.torque_from_lateral_accel() self.lateral_accel_from_torque = CI.lateral_accel_from_torque() self.pid = PIDController(self.torque_params.kp, self.torque_params.ki, - k_f=self.torque_params.kf) + k_f=self.torque_params.kf, rate=1/self.dt) self.update_limits() self.steering_angle_deadzone_deg = self.torque_params.steeringAngleDeadzoneDeg + self.LATACCEL_REQUEST_BUFFER_NUM_FRAMES = int(1 / self.dt) + self.requested_lateral_accel_buffer = deque([0.] * self.LATACCEL_REQUEST_BUFFER_NUM_FRAMES , maxlen=self.LATACCEL_REQUEST_BUFFER_NUM_FRAMES) def update_live_torque_params(self, latAccelFactor, latAccelOffset, friction): self.torque_params.latAccelFactor = latAccelFactor @@ -43,7 +47,7 @@ class LatControlTorque(LatControl): self.pid.set_limits(self.lateral_accel_from_torque(self.steer_max, self.torque_params), self.lateral_accel_from_torque(-self.steer_max, self.torque_params)) - def update(self, active, CS, VM, params, steer_limited_by_safety, desired_curvature, curvature_limited): + def update(self, active, CS, VM, params, steer_limited_by_safety, desired_curvature, curvature_limited, lat_delay): pid_log = log.ControlsState.LateralTorqueState.new_message() if not active: output_torque = 0.0 @@ -53,21 +57,29 @@ class LatControlTorque(LatControl): roll_compensation = params.roll * ACCELERATION_DUE_TO_GRAVITY curvature_deadzone = abs(VM.calc_curvature(math.radians(self.steering_angle_deadzone_deg), CS.vEgo, 0.0)) - desired_lateral_accel = desired_curvature * CS.vEgo ** 2 + delay_frames = int(np.clip(lat_delay / self.dt, 1, self.LATACCEL_REQUEST_BUFFER_NUM_FRAMES)) + expected_lateral_accel = self.requested_lateral_accel_buffer[-delay_frames] + # TODO factor out lateral jerk from error to later replace it with delay independent alternative + future_desired_lateral_accel = desired_curvature * CS.vEgo ** 2 + self.requested_lateral_accel_buffer.append(future_desired_lateral_accel) + gravity_adjusted_future_lateral_accel = future_desired_lateral_accel - roll_compensation + desired_lateral_jerk = (future_desired_lateral_accel - expected_lateral_accel) / lat_delay actual_lateral_accel = actual_curvature * CS.vEgo ** 2 lateral_accel_deadzone = curvature_deadzone * CS.vEgo ** 2 - low_speed_factor = np.interp(CS.vEgo, LOW_SPEED_X, LOW_SPEED_Y)**2 - setpoint = desired_lateral_accel + low_speed_factor * desired_curvature - measurement = actual_lateral_accel + low_speed_factor * actual_curvature - gravity_adjusted_lateral_accel = desired_lateral_accel - roll_compensation + low_speed_factor = np.interp(CS.vEgo, LOW_SPEED_X, LOW_SPEED_Y)**2 / (np.clip(CS.vEgo, MIN_SPEED, np.inf) ** 2) + setpoint = lat_delay * desired_lateral_jerk + expected_lateral_accel + measurement = actual_lateral_accel + error = setpoint - measurement + error_lsf = error + low_speed_factor * error # do error correction in lateral acceleration space, convert at end to handle non-linear torque responses correctly - pid_log.error = float(setpoint - measurement) - ff = gravity_adjusted_lateral_accel + pid_log.error = float(error_lsf) + ff = gravity_adjusted_future_lateral_accel # latAccelOffset corrects roll compensation bias from device roll misalignment relative to car roll ff -= self.torque_params.latAccelOffset - ff += get_friction(desired_lateral_accel - actual_lateral_accel, lateral_accel_deadzone, FRICTION_THRESHOLD, self.torque_params) + # TODO jerk is weighted by lat_delay for legacy reasons, but should be made independent of it + ff += get_friction(error, lateral_accel_deadzone, FRICTION_THRESHOLD, self.torque_params) freeze_integrator = steer_limited_by_safety or CS.steeringPressed or CS.vEgo < 5 output_lataccel = self.pid.update(pid_log.error, @@ -83,7 +95,7 @@ class LatControlTorque(LatControl): pid_log.f = float(self.pid.f) pid_log.output = float(-output_torque) # TODO: log lat accel? pid_log.actualLateralAccel = float(actual_lateral_accel) - pid_log.desiredLateralAccel = float(desired_lateral_accel) + pid_log.desiredLateralAccel = float(expected_lateral_accel) pid_log.saturated = bool(self._check_saturation(self.steer_max - abs(output_torque) < 1e-3, CS, steer_limited_by_safety, curvature_limited)) # TODO left is positive in this convention diff --git a/selfdrive/controls/tests/test_latcontrol.py b/selfdrive/controls/tests/test_latcontrol.py index c564e9d3e..354c7f00a 100644 --- a/selfdrive/controls/tests/test_latcontrol.py +++ b/selfdrive/controls/tests/test_latcontrol.py @@ -33,13 +33,13 @@ class TestLatControl: # Saturate for curvature limited and controller limited for _ in range(1000): - _, _, lac_log = controller.update(True, CS, VM, params, False, 0, True) + _, _, lac_log = controller.update(True, CS, VM, params, False, 0, True, 0.2) assert lac_log.saturated for _ in range(1000): - _, _, lac_log = controller.update(True, CS, VM, params, False, 0, False) + _, _, lac_log = controller.update(True, CS, VM, params, False, 0, False, 0.2) assert not lac_log.saturated for _ in range(1000): - _, _, lac_log = controller.update(True, CS, VM, params, False, 1, False) + _, _, lac_log = controller.update(True, CS, VM, params, False, 1, False, 0.2) assert lac_log.saturated diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index a833fadb9..f55bcc378 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -afcab1abb62b9d5678342956cced4712f44e909e \ No newline at end of file +4c2f4e9d3d6667c990900ad63f7f5a6b23a8bcdf \ No newline at end of file From dcc5afa8fa10b6a0aff551971aff5bbf71bb9191 Mon Sep 17 00:00:00 2001 From: "kostas.pats" <35031825+kostas1507@users.noreply.github.com> Date: Thu, 9 Oct 2025 05:30:32 +0000 Subject: [PATCH 126/910] improve webrtc stack for use in camera focusing (#36268) * made LiveStreamVideoStreamTrack use system time to calculate pts * fixes as requested * Align panda submodule with master (panda@615009c) * made loggerd accept a run time env variable to pick stream bitrate * added /notify endpoint to send json to all session's data channel * fixed static analysis error * adapted webrtc stream test to new pts calculation method * fixed static erro * fixed wrong indent * fixed import order * delete accidental newline * remove excess spaces Co-authored-by: Maxime Desroches * remove excess spaces Co-authored-by: Maxime Desroches * changed exeption handling based on review * fixed typo on exception handling --------- Co-authored-by: Maxime Desroches --- system/loggerd/loggerd.h | 4 +++- system/webrtc/device/video.py | 8 +++++--- system/webrtc/tests/test_stream_session.py | 7 +++++-- system/webrtc/webrtcd.py | 15 +++++++++++++++ 4 files changed, 28 insertions(+), 6 deletions(-) diff --git a/system/loggerd/loggerd.h b/system/loggerd/loggerd.h index 967caec86..8e3a74d2d 100644 --- a/system/loggerd/loggerd.h +++ b/system/loggerd/loggerd.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include "cereal/messaging/messaging.h" @@ -46,7 +47,8 @@ struct EncoderSettings { } static EncoderSettings StreamEncoderSettings() { - return EncoderSettings{.encode_type = cereal::EncodeIndex::Type::QCAMERA_H264, .bitrate = 1'000'000, .gop_size = 15}; + int _stream_bitrate = getenv("STREAM_BITRATE") ? atoi(getenv("STREAM_BITRATE")) : 1'000'000; + return EncoderSettings{.encode_type = cereal::EncodeIndex::Type::QCAMERA_H264, .bitrate = _stream_bitrate , .gop_size = 15}; } }; diff --git a/system/webrtc/device/video.py b/system/webrtc/device/video.py index 1bca90929..50feab4f4 100644 --- a/system/webrtc/device/video.py +++ b/system/webrtc/device/video.py @@ -1,4 +1,5 @@ import asyncio +import time import av from teleoprtc.tracks import TiciVideoStreamTrack @@ -20,6 +21,7 @@ class LiveStreamVideoStreamTrack(TiciVideoStreamTrack): self._sock = messaging.sub_sock(self.camera_to_sock_mapping[camera_type], conflate=True) self._pts = 0 + self._t0_ns = time.monotonic_ns() async def recv(self): while True: @@ -32,10 +34,10 @@ class LiveStreamVideoStreamTrack(TiciVideoStreamTrack): packet = av.Packet(evta.header + evta.data) packet.time_base = self._time_base - packet.pts = self._pts - self.log_debug("track sending frame %s", self._pts) - self._pts += self._dt * self._clock_rate + self._pts = ((time.monotonic_ns() - self._t0_ns) * self._clock_rate) // 1_000_000_000 + packet.pts = self._pts + self.log_debug("track sending frame %d", self._pts) return packet diff --git a/system/webrtc/tests/test_stream_session.py b/system/webrtc/tests/test_stream_session.py index 113fa5e7e..e31fda372 100644 --- a/system/webrtc/tests/test_stream_session.py +++ b/system/webrtc/tests/test_stream_session.py @@ -1,5 +1,6 @@ import asyncio import json +import time # for aiortc and its dependencies import warnings warnings.filterwarnings("ignore", category=DeprecationWarning) @@ -14,7 +15,6 @@ from cereal import messaging, log from openpilot.system.webrtc.webrtcd import CerealOutgoingMessageProxy, CerealIncomingMessageProxy from openpilot.system.webrtc.device.video import LiveStreamVideoStreamTrack from openpilot.system.webrtc.device.audio import AudioInputStreamTrack -from openpilot.common.realtime import DT_DMON class TestStreamSession: @@ -81,7 +81,10 @@ class TestStreamSession: for i in range(5): packet = self.loop.run_until_complete(track.recv()) assert packet.time_base == VIDEO_TIME_BASE - assert packet.pts == int(i * DT_DMON * VIDEO_CLOCK_RATE) + if i == 0: + start_ns = time.monotonic_ns() + start_pts = packet.pts + assert abs(i + packet.pts - (start_pts + (((time.monotonic_ns() - start_ns) * VIDEO_CLOCK_RATE) // 1_000_000_000))) < 450 #5ms assert packet.size == 0 def test_input_audio_track(self, mocker): diff --git a/system/webrtc/webrtcd.py b/system/webrtc/webrtcd.py index fb93e565f..c19f1bf9d 100755 --- a/system/webrtc/webrtcd.py +++ b/system/webrtc/webrtcd.py @@ -239,6 +239,20 @@ async def get_schema(request: 'web.Request'): schema_dict = {s: generate_field(log.Event.schema.fields[s]) for s in services} return web.json_response(schema_dict) +async def post_notify(request: 'web.Request'): + try: + payload = await request.json() + except Exception as e: + raise web.HTTPBadRequest(text="Invalid JSON") from e + + for session in list(request.app.get('streams', {}).values()): + try: + ch = session.stream.get_messaging_channel() + ch.send(json.dumps(payload)) + except Exception: + continue + + return web.Response(status=200, text="OK") async def on_shutdown(app: 'web.Application'): for session in app['streams'].values(): @@ -258,6 +272,7 @@ def webrtcd_thread(host: str, port: int, debug: bool): app['debug'] = debug app.on_shutdown.append(on_shutdown) app.router.add_post("/stream", get_stream) + app.router.add_post("/notify", post_notify) app.router.add_get("/schema", get_schema) web.run_app(app, host=host, port=port) From 0736f325fcee68bcd8834fcfa4a374089e316b19 Mon Sep 17 00:00:00 2001 From: felsager <76905857+felsager@users.noreply.github.com> Date: Thu, 9 Oct 2025 10:29:35 -0700 Subject: [PATCH 127/910] Latcontrol torque: cleaner low_speed_factor calculation (#36287) --- selfdrive/controls/lib/latcontrol_torque.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/controls/lib/latcontrol_torque.py b/selfdrive/controls/lib/latcontrol_torque.py index e50d91351..4878bde3a 100644 --- a/selfdrive/controls/lib/latcontrol_torque.py +++ b/selfdrive/controls/lib/latcontrol_torque.py @@ -67,7 +67,7 @@ class LatControlTorque(LatControl): actual_lateral_accel = actual_curvature * CS.vEgo ** 2 lateral_accel_deadzone = curvature_deadzone * CS.vEgo ** 2 - low_speed_factor = np.interp(CS.vEgo, LOW_SPEED_X, LOW_SPEED_Y)**2 / (np.clip(CS.vEgo, MIN_SPEED, np.inf) ** 2) + low_speed_factor = (np.interp(CS.vEgo, LOW_SPEED_X, LOW_SPEED_Y) / max(CS.vEgo, MIN_SPEED)) ** 2 setpoint = lat_delay * desired_lateral_jerk + expected_lateral_accel measurement = actual_lateral_accel error = setpoint - measurement From 4c9ca91b98e244849556e59342e7447c400bb2f3 Mon Sep 17 00:00:00 2001 From: felsager <76905857+felsager@users.noreply.github.com> Date: Thu, 9 Oct 2025 10:34:26 -0700 Subject: [PATCH 128/910] Latcontrol: use more accurate naming for saturation time (#36286) --- selfdrive/controls/lib/latcontrol.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/selfdrive/controls/lib/latcontrol.py b/selfdrive/controls/lib/latcontrol.py index 00cf0dab9..d69796738 100644 --- a/selfdrive/controls/lib/latcontrol.py +++ b/selfdrive/controls/lib/latcontrol.py @@ -5,9 +5,8 @@ from abc import abstractmethod, ABC class LatControl(ABC): def __init__(self, CP, CI, dt): self.dt = dt - self.sat_count_rate = 1.0 * self.dt self.sat_limit = CP.steerLimitTimer - self.sat_count = 0. + self.sat_time = 0. self.sat_check_min_speed = 10. # we define the steer torque scale as [-1.0...1.0] @@ -18,13 +17,13 @@ class LatControl(ABC): pass def reset(self): - self.sat_count = 0. + self.sat_time = 0. def _check_saturation(self, saturated, CS, steer_limited_by_safety, curvature_limited): # Saturated only if control output is not being limited by car torque/angle rate limits if (saturated or curvature_limited) and CS.vEgo > self.sat_check_min_speed and not steer_limited_by_safety and not CS.steeringPressed: - self.sat_count += self.sat_count_rate + self.sat_time += self.dt else: - self.sat_count -= self.sat_count_rate - self.sat_count = np.clip(self.sat_count, 0.0, self.sat_limit) - return self.sat_count > (self.sat_limit - 1e-3) + self.sat_time -= self.dt + self.sat_time = np.clip(self.sat_time, 0.0, self.sat_limit) + return self.sat_time > (self.sat_limit - 1e-3) From 22d5cbd0fac51a440749193cf5565ab71cf181f6 Mon Sep 17 00:00:00 2001 From: felsager <76905857+felsager@users.noreply.github.com> Date: Thu, 9 Oct 2025 11:10:44 -0700 Subject: [PATCH 129/910] PID: coefficients should be in front, i_rate should be i_dt (#36288) --- common/pid.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/common/pid.py b/common/pid.py index 99142280c..ff5084781 100644 --- a/common/pid.py +++ b/common/pid.py @@ -16,7 +16,7 @@ class PIDController: self.set_limits(pos_limit, neg_limit) - self.i_rate = 1.0 / rate + self.i_dt = 1.0 / rate self.speed = 0.0 self.reset() @@ -46,12 +46,12 @@ class PIDController: def update(self, error, error_rate=0.0, speed=0.0, feedforward=0., freeze_integrator=False): self.speed = speed - self.p = float(error) * self.k_p - self.f = feedforward * self.k_f - self.d = error_rate * self.k_d + self.p = self.k_p * float(error) + self.d = self.k_d * error_rate + self.f = self.k_f * feedforward if not freeze_integrator: - i = self.i + error * self.k_i * self.i_rate + i = self.i + self.k_i * self.i_dt * error # Don't allow windup if already clipping test_control = self.p + i + self.d + self.f From d07981ea3cebae51e0654b532c399bb69972d9fd Mon Sep 17 00:00:00 2001 From: felsager <76905857+felsager@users.noreply.github.com> Date: Thu, 9 Oct 2025 11:48:13 -0700 Subject: [PATCH 130/910] bump opendbc (#36289) --- opendbc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc_repo b/opendbc_repo index aa3d32a63..dfa6807eb 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit aa3d32a63b7d05e69777fa90147192220abde8a3 +Subproject commit dfa6807ebf9f0edb593189a703b857c834a88a75 From 4d085424f80009ee5ea4ae8f4519ed871905b382 Mon Sep 17 00:00:00 2001 From: YassineYousfi Date: Thu, 9 Oct 2025 12:58:27 -0700 Subject: [PATCH 131/910] =?UTF-8?q?North=20Nevada=20Model=20=F0=9F=8F=9C?= =?UTF-8?q?=EF=B8=8F=20(#36276)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * e2d9c622-25a8-4ccd-8c8e-c62537b7aa0c/400 * 0e620593-e85f-40c2-9adf-1e945651ed13/400 --- selfdrive/modeld/models/driving_policy.onnx | 4 ++-- selfdrive/modeld/models/driving_vision.onnx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/selfdrive/modeld/models/driving_policy.onnx b/selfdrive/modeld/models/driving_policy.onnx index cb5a6b3a4..89444c3ea 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:5b7c686e13637733ec432d774ff5b665c4e4663982ff03deeefd6f6ffd511741 -size 12343531 +oid sha256:792fca7c24faadc1183327eec7e04d06dd6ec0e53ac7bb43cb2b9823c6cfb32f +size 12343535 diff --git a/selfdrive/modeld/models/driving_vision.onnx b/selfdrive/modeld/models/driving_vision.onnx index 4b4fa05df..0411ddb74 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:befac016a247b7ad5dc5b55d339d127774ed7bd2b848f1583f72aa4caee37781 -size 46271991 +oid sha256:cf6376aa9a090f0da26c280ef69eabf9bbdd51d1faac9ed392919c3db69be916 +size 46271942 From de805e4af734722f3c73db358d03fe5d5dc489fd Mon Sep 17 00:00:00 2001 From: felsager <76905857+felsager@users.noreply.github.com> Date: Thu, 9 Oct 2025 14:57:12 -0700 Subject: [PATCH 132/910] Lateral torque controller: use measurement rate as error rate (#36291) --- selfdrive/controls/lib/latcontrol_torque.py | 25 +++++++++++++-------- selfdrive/test/process_replay/ref_commit | 2 +- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/selfdrive/controls/lib/latcontrol_torque.py b/selfdrive/controls/lib/latcontrol_torque.py index 4878bde3a..2a1b666ef 100644 --- a/selfdrive/controls/lib/latcontrol_torque.py +++ b/selfdrive/controls/lib/latcontrol_torque.py @@ -4,7 +4,9 @@ from collections import deque from cereal import log from opendbc.car.lateral import FRICTION_THRESHOLD, get_friction +from opendbc.car.tests.test_lateral_limits import MAX_LAT_JERK_UP from openpilot.common.constants import ACCELERATION_DUE_TO_GRAVITY +from openpilot.common.filter_simple import FirstOrderFilter from openpilot.selfdrive.controls.lib.drive_helpers import MIN_SPEED from openpilot.selfdrive.controls.lib.latcontrol import LatControl from openpilot.common.pid import PIDController @@ -36,6 +38,8 @@ class LatControlTorque(LatControl): self.steering_angle_deadzone_deg = self.torque_params.steeringAngleDeadzoneDeg self.LATACCEL_REQUEST_BUFFER_NUM_FRAMES = int(1 / self.dt) self.requested_lateral_accel_buffer = deque([0.] * self.LATACCEL_REQUEST_BUFFER_NUM_FRAMES , maxlen=self.LATACCEL_REQUEST_BUFFER_NUM_FRAMES) + self.previous_measurement = 0.0 + self.measurement_rate_filter = FirstOrderFilter(0.0, 1 / (2 * np.pi * (MAX_LAT_JERK_UP - 0.5)), self.dt) def update_live_torque_params(self, latAccelFactor, latAccelOffset, friction): self.torque_params.latAccelFactor = latAccelFactor @@ -53,9 +57,10 @@ class LatControlTorque(LatControl): output_torque = 0.0 pid_log.active = False else: - actual_curvature = -VM.calc_curvature(math.radians(CS.steeringAngleDeg - params.angleOffsetDeg), CS.vEgo, params.roll) + measured_curvature = -VM.calc_curvature(math.radians(CS.steeringAngleDeg - params.angleOffsetDeg), CS.vEgo, params.roll) roll_compensation = params.roll * ACCELERATION_DUE_TO_GRAVITY curvature_deadzone = abs(VM.calc_curvature(math.radians(self.steering_angle_deadzone_deg), CS.vEgo, 0.0)) + lateral_accel_deadzone = curvature_deadzone * CS.vEgo ** 2 delay_frames = int(np.clip(lat_delay / self.dt, 1, self.LATACCEL_REQUEST_BUFFER_NUM_FRAMES)) expected_lateral_accel = self.requested_lateral_accel_buffer[-delay_frames] @@ -64,12 +69,13 @@ class LatControlTorque(LatControl): self.requested_lateral_accel_buffer.append(future_desired_lateral_accel) gravity_adjusted_future_lateral_accel = future_desired_lateral_accel - roll_compensation desired_lateral_jerk = (future_desired_lateral_accel - expected_lateral_accel) / lat_delay - actual_lateral_accel = actual_curvature * CS.vEgo ** 2 - lateral_accel_deadzone = curvature_deadzone * CS.vEgo ** 2 + + measurement = measured_curvature * CS.vEgo ** 2 + measurement_rate = self.measurement_rate_filter.update((measurement - self.previous_measurement) / self.dt) + self.previous_measurement = measurement low_speed_factor = (np.interp(CS.vEgo, LOW_SPEED_X, LOW_SPEED_Y) / max(CS.vEgo, MIN_SPEED)) ** 2 setpoint = lat_delay * desired_lateral_jerk + expected_lateral_accel - measurement = actual_lateral_accel error = setpoint - measurement error_lsf = error + low_speed_factor * error @@ -83,9 +89,10 @@ class LatControlTorque(LatControl): freeze_integrator = steer_limited_by_safety or CS.steeringPressed or CS.vEgo < 5 output_lataccel = self.pid.update(pid_log.error, - feedforward=ff, - speed=CS.vEgo, - freeze_integrator=freeze_integrator) + -measurement_rate, + feedforward=ff, + speed=CS.vEgo, + freeze_integrator=freeze_integrator) output_torque = self.torque_from_lateral_accel(output_lataccel, self.torque_params) pid_log.active = True @@ -94,8 +101,8 @@ class LatControlTorque(LatControl): pid_log.d = float(self.pid.d) pid_log.f = float(self.pid.f) pid_log.output = float(-output_torque) # TODO: log lat accel? - pid_log.actualLateralAccel = float(actual_lateral_accel) - pid_log.desiredLateralAccel = float(expected_lateral_accel) + pid_log.actualLateralAccel = float(measurement) + pid_log.desiredLateralAccel = float(setpoint) pid_log.saturated = bool(self._check_saturation(self.steer_max - abs(output_torque) < 1e-3, CS, steer_limited_by_safety, curvature_limited)) # TODO left is positive in this convention diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index f55bcc378..538aa34f2 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -4c2f4e9d3d6667c990900ad63f7f5a6b23a8bcdf \ No newline at end of file +5342f5684c2498a33d6178f3b59efd5e83b73acc \ No newline at end of file From 1b90b42647b78d7d007712b063f049a137fa1779 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 9 Oct 2025 19:10:10 -0700 Subject: [PATCH 133/910] Html renderer: reset tag so untagged text doesn't inherit last tag --- system/ui/widgets/html_render.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/system/ui/widgets/html_render.py b/system/ui/widgets/html_render.py index 2c3eeb379..8d713ee7e 100644 --- a/system/ui/widgets/html_render.py +++ b/system/ui/widgets/html_render.py @@ -127,9 +127,10 @@ class HtmlRenderer(Widget): # Always add content regardless of opening or closing tag close_tag() - # TODO: reset to None if end tag? if is_start_tag: current_tag = tag + else: + current_tag = None # increment after we add the content for the current tag if tag == ElementType.UL: From 29767988525b1e1d3dac3bd573a7328da788c909 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 9 Oct 2025 19:50:27 -0700 Subject: [PATCH 134/910] raylib: implement toggles (#36284) * start on exp mode * more * fmt * rm * 2nd try * almost there * clean up * and this * fmt * more * exp is colored when active * move out, and rm redudnant self.state * revert html changes for now * fix untagged text inheriting previous tag * why would this be unknown * here too * update live with car * clean up + refresh toggles on showEvent + catch from cursor about setting desc if no carparams * not sure why * fix disengaged re-enabling locked toggles --- selfdrive/ui/layouts/settings/toggles.py | 189 ++++++++++++++++++----- selfdrive/ui/ui_state.py | 24 ++- system/ui/widgets/list_view.py | 28 ++-- system/ui/widgets/toggle.py | 8 +- 4 files changed, 195 insertions(+), 54 deletions(-) diff --git a/selfdrive/ui/layouts/settings/toggles.py b/selfdrive/ui/layouts/settings/toggles.py index 1e0e7bfd5..01f663d4b 100644 --- a/selfdrive/ui/layouts/settings/toggles.py +++ b/selfdrive/ui/layouts/settings/toggles.py @@ -1,7 +1,11 @@ -from openpilot.common.params import Params +from cereal import log +from openpilot.common.params import Params, UnknownKeyName from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.list_view import multiple_button_item, toggle_item from openpilot.system.ui.widgets.scroller import Scroller +from openpilot.selfdrive.ui.ui_state import ui_state + +PERSONALITY_TO_INT = log.LongitudinalPersonality.schema.enumerants # Description constants DESCRIPTIONS = { @@ -30,66 +34,179 @@ class TogglesLayout(Widget): def __init__(self): super().__init__() self._params = Params() - items = [ - toggle_item( + + # param, title, desc, icon, needs_restart + self._toggle_defs = { + "OpenpilotEnabledToggle": ( "Enable openpilot", DESCRIPTIONS["OpenpilotEnabledToggle"], - self._params.get_bool("OpenpilotEnabledToggle"), - icon="chffr_wheel.png", + "chffr_wheel.png", + True, ), - toggle_item( + "ExperimentalMode": ( "Experimental Mode", - initial_state=self._params.get_bool("ExperimentalMode"), - icon="experimental_white.png", + "", + "experimental_white.png", + False, ), - toggle_item( + "DisengageOnAccelerator": ( "Disengage on Accelerator Pedal", DESCRIPTIONS["DisengageOnAccelerator"], - self._params.get_bool("DisengageOnAccelerator"), - icon="disengage_on_accelerator.png", + "disengage_on_accelerator.png", + False, ), - multiple_button_item( - "Driving Personality", - DESCRIPTIONS["LongitudinalPersonality"], - buttons=["Aggressive", "Standard", "Relaxed"], - button_width=255, - callback=self._set_longitudinal_personality, - selected_index=self._params.get("LongitudinalPersonality", return_default=True), - icon="speed_limit.png" - ), - toggle_item( + "IsLdwEnabled": ( "Enable Lane Departure Warnings", DESCRIPTIONS["IsLdwEnabled"], - self._params.get_bool("IsLdwEnabled"), - icon="warning.png", + "warning.png", + False, ), - toggle_item( + "AlwaysOnDM": ( "Always-On Driver Monitoring", DESCRIPTIONS["AlwaysOnDM"], - self._params.get_bool("AlwaysOnDM"), - icon="monitoring.png", + "monitoring.png", + False, ), - toggle_item( + "RecordFront": ( "Record and Upload Driver Camera", DESCRIPTIONS["RecordFront"], - self._params.get_bool("RecordFront"), - icon="monitoring.png", + "monitoring.png", + True, ), - toggle_item( + "RecordAudio": ( "Record and Upload Microphone Audio", DESCRIPTIONS["RecordAudio"], - self._params.get_bool("RecordAudio"), - icon="microphone.png", + "microphone.png", + True, ), - toggle_item( - "Use Metric System", DESCRIPTIONS["IsMetric"], self._params.get_bool("IsMetric"), icon="metric.png" + "IsMetric": ( + "Use Metric System", + DESCRIPTIONS["IsMetric"], + "metric.png", + False, ), - ] + } - self._scroller = Scroller(items, line_separator=True, spacing=0) + self._long_personality_setting = multiple_button_item( + "Driving Personality", + DESCRIPTIONS["LongitudinalPersonality"], + buttons=["Aggressive", "Standard", "Relaxed"], + button_width=255, + callback=self._set_longitudinal_personality, + selected_index=self._params.get("LongitudinalPersonality", return_default=True), + icon="speed_limit.png" + ) + + self._toggles = {} + self._locked_toggles = set() + for param, (title, desc, icon, needs_restart) in self._toggle_defs.items(): + toggle = toggle_item( + title, + desc, + self._params.get_bool(param), + callback=lambda state, p=param: self._toggle_callback(state, p), + icon=icon, + ) + + try: + locked = self._params.get_bool(param + "Lock") + except UnknownKeyName: + locked = False + toggle.action_item.set_enabled(not locked) + + if needs_restart and not locked: + toggle.set_description(toggle.description + " Changing this setting will restart openpilot if the car is powered on.") + + # track for engaged state updates + if locked: + self._locked_toggles.add(param) + + self._toggles[param] = toggle + + # insert longitudinal personality after NDOG toggle + if param == "DisengageOnAccelerator": + self._toggles["LongitudinalPersonality"] = self._long_personality_setting + + self._update_experimental_mode_icon() + self._scroller = Scroller(list(self._toggles.values()), line_separator=True, spacing=0) + + def _update_state(self): + if ui_state.sm.updated["selfdriveState"]: + personality = PERSONALITY_TO_INT[ui_state.sm["selfdriveState"].personality] + if personality != ui_state.personality and ui_state.started: + self._long_personality_setting.action_item.set_selected_button(personality) + ui_state.personality = personality + + # these toggles need restart, block while engaged + for toggle_def in self._toggle_defs: + if self._toggle_defs[toggle_def][3] and toggle_def not in self._locked_toggles: + self._toggles[toggle_def].action_item.set_enabled(not ui_state.engaged) + + def show_event(self): + self._update_toggles() + + def _update_toggles(self): + e2e_description = ( + "openpilot defaults to driving in chill mode. Experimental mode enables alpha-level features that aren't ready for chill mode. " + + "Experimental features are listed below:
" + + "

End-to-End Longitudinal Control


" + + "Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. " + + "Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; " + + "mistakes should be expected.
" + + "

New Driving Visualization


" + + "The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. " + + "The Experimental mode logo will also be shown in the top right corner." + ) + + is_release = self._params.get_bool("IsReleaseBranch") + + if ui_state.CP is not None: + if ui_state.has_longitudinal_control: + self._toggles["ExperimentalMode"].action_item.set_enabled(True) + self._toggles["ExperimentalMode"].set_description(e2e_description) + self._long_personality_setting.action_item.set_enabled(True) + else: + # no long for now + self._toggles["ExperimentalMode"].action_item.set_enabled(False) + self._toggles["ExperimentalMode"].action_item.set_state(False) + self._long_personality_setting.action_item.set_enabled(False) + self._params.remove("ExperimentalMode") + + unavailable = "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." + + long_desc = unavailable + " openpilot longitudinal control may come in a future update." + if ui_state.CP.getAlphaLongitudinalAvailable(): + if is_release: + long_desc = unavailable + " " + ("An alpha version of openpilot longitudinal control can be tested, along with " + + "Experimental mode, on non-release branches.") + else: + long_desc = "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." + + self._toggles["ExperimentalMode"].set_description("" + long_desc + "

" + e2e_description) + else: + self._toggles["ExperimentalMode"].set_description(e2e_description) + + self._update_experimental_mode_icon() + + # TODO: make a param control list item so we don't need to manage internal state as much here + # refresh toggles from params to mirror external changes + for param in self._toggle_defs: + self._toggles[param].action_item.set_state(self._params.get_bool(param)) def _render(self, rect): self._scroller.render(rect) + def _update_experimental_mode_icon(self): + icon = "experimental.png" if self._toggles["ExperimentalMode"].action_item.get_state() else "experimental_white.png" + self._toggles["ExperimentalMode"].set_icon(icon) + + def _toggle_callback(self, state: bool, param: str): + if param == "ExperimentalMode": + self._update_experimental_mode_icon() + + self._params.put_bool(param, state) + if self._toggle_defs[param][3]: + self._params.put_bool("OnroadCycleRequested", True) + def _set_longitudinal_personality(self, button_index: int): self._params.put("LongitudinalPersonality", button_index) diff --git a/selfdrive/ui/ui_state.py b/selfdrive/ui/ui_state.py index 9c45519b4..be8351798 100644 --- a/selfdrive/ui/ui_state.py +++ b/selfdrive/ui/ui_state.py @@ -4,9 +4,9 @@ import time import threading from collections.abc import Callable from enum import Enum -from cereal import messaging, log +from cereal import messaging, car, log from openpilot.common.filter_simple import FirstOrderFilter -from openpilot.common.params import Params, UnknownKeyName +from openpilot.common.params import Params from openpilot.common.swaglog import cloudlog from openpilot.selfdrive.ui.lib.prime_state import PrimeState from openpilot.system.ui.lib.application import gui_app @@ -69,7 +69,10 @@ class UIState: self.ignition: bool = False self.panda_type: log.PandaState.PandaType = log.PandaState.PandaType.unknown self.personality: log.LongitudinalPersonality = log.LongitudinalPersonality.standard + self.has_longitudinal_control: bool = False + self.CP: car.CarParams | None = None self.light_sensor: float = -1.0 + self._param_update_time: float = 0.0 self._update_params() @@ -87,6 +90,9 @@ class UIState: self.sm.update(0) self._update_state() self._update_status() + if time.monotonic() - self._param_update_time > 5.0: + self._update_params() + self._param_update_time = time.monotonic() device.update() def _update_state(self) -> None: @@ -137,10 +143,16 @@ class UIState: self._started_prev = self.started def _update_params(self) -> None: - try: - self.is_metric = self.params.get_bool("IsMetric") - except UnknownKeyName: - self.is_metric = False + self.is_metric = self.params.get_bool("IsMetric") + + # Update longitudinal control state + CP_bytes = self.params.get("CarParams") + if CP_bytes is not None: + self.CP = messaging.log_from_bytes(CP_bytes, car.CarParams) + if self.CP.alphaLongitudinalAvailable: + self.has_longitudinal_control = self.params.get_bool("AlphaLongitudinalEnabled") + else: + self.has_longitudinal_control = self.CP.openpilotLongitudinalControl class Device: diff --git a/system/ui/widgets/list_view.py b/system/ui/widgets/list_view.py index 509c49be3..bbf66c655 100644 --- a/system/ui/widgets/list_view.py +++ b/system/ui/widgets/list_view.py @@ -56,10 +56,10 @@ class ItemAction(Widget, ABC): class ToggleAction(ItemAction): - def __init__(self, initial_state: bool = False, width: int = TOGGLE_WIDTH, enabled: bool | Callable[[], bool] = True): + def __init__(self, initial_state: bool = False, width: int = TOGGLE_WIDTH, enabled: bool | Callable[[], bool] = True, + callback: Callable[[bool], None] | None = None): super().__init__(width, enabled) - self.toggle = Toggle(initial_state=initial_state) - self.state = initial_state + self.toggle = Toggle(initial_state=initial_state, callback=callback) def set_touch_valid_callback(self, touch_callback: Callable[[], bool]) -> None: super().set_touch_valid_callback(touch_callback) @@ -68,15 +68,13 @@ class ToggleAction(ItemAction): def _render(self, rect: rl.Rectangle) -> bool: self.toggle.set_enabled(self.enabled) clicked = self.toggle.render(rl.Rectangle(rect.x, rect.y + (rect.height - TOGGLE_HEIGHT) / 2, self._rect.width, TOGGLE_HEIGHT)) - self.state = self.toggle.get_state() return bool(clicked) def set_state(self, state: bool): - self.state = state self.toggle.set_state(state) def get_state(self) -> bool: - return self.state + return self.toggle.get_state() class ButtonAction(ItemAction): @@ -207,6 +205,13 @@ class MultipleButtonAction(ItemAction): self.callback = callback self._font = gui_app.font(FontWeight.MEDIUM) + def set_selected_button(self, index: int): + if 0 <= index < len(self.buttons): + self.selected_button = index + + def get_selected_button(self) -> int: + return self.selected_button + def _render(self, rect: rl.Rectangle): spacing = RIGHT_ITEM_PADDING button_y = rect.y + (rect.height - BUTTON_HEIGHT) / 2 @@ -259,7 +264,7 @@ class ListItem(Widget): action_item: ItemAction | None = None): super().__init__() self.title = title - self.icon = icon + self.set_icon(icon) self._description = description self.description_visible = description_visible self.callback = callback @@ -267,7 +272,6 @@ class ListItem(Widget): self.set_rect(rl.Rectangle(0, 0, ITEM_BASE_WIDTH, ITEM_BASE_HEIGHT)) self._font = gui_app.font(FontWeight.NORMAL) - self._icon_texture = gui_app.texture(os.path.join("icons", self.icon), ICON_SIZE, ICON_SIZE) if self.icon else None self._html_renderer = HtmlRenderer(text="", text_size={ElementType.P: ITEM_DESC_FONT_SIZE}, text_color=ITEM_DESC_TEXT_COLOR) @@ -352,6 +356,10 @@ class ListItem(Widget): if self.callback: self.callback() + def set_icon(self, icon: str | None): + self.icon = icon + self._icon_texture = gui_app.texture(os.path.join("icons", self.icon), ICON_SIZE, ICON_SIZE) if self.icon else None + def set_description(self, description: str | Callable[[], str] | None): self._description = description new_desc = self.description @@ -398,8 +406,8 @@ def simple_item(title: str, callback: Callable | None = None) -> ListItem: def toggle_item(title: str, description: str | Callable[[], str] | None = None, initial_state: bool = False, callback: Callable | None = None, icon: str = "", enabled: bool | Callable[[], bool] = True) -> ListItem: - action = ToggleAction(initial_state=initial_state, enabled=enabled) - return ListItem(title=title, description=description, action_item=action, icon=icon, callback=callback) + action = ToggleAction(initial_state=initial_state, enabled=enabled, callback=callback) + return ListItem(title=title, description=description, action_item=action, icon=icon) def button_item(title: str, button_text: str | Callable[[], str], description: str | Callable[[], str] | None = None, diff --git a/system/ui/widgets/toggle.py b/system/ui/widgets/toggle.py index 968afda9c..0fbf3c844 100644 --- a/system/ui/widgets/toggle.py +++ b/system/ui/widgets/toggle.py @@ -1,4 +1,5 @@ import pyray as rl +from collections.abc import Callable from openpilot.system.ui.lib.application import MousePos from openpilot.system.ui.widgets import Widget @@ -14,9 +15,10 @@ ANIMATION_SPEED = 8.0 class Toggle(Widget): - def __init__(self, initial_state=False): + def __init__(self, initial_state: bool = False, callback: Callable[[bool], None] | None = None): super().__init__() self._state = initial_state + self._callback = callback self._enabled = True self._progress = 1.0 if initial_state else 0.0 self._target = self._progress @@ -32,8 +34,10 @@ class Toggle(Widget): self._clicked = True self._state = not self._state self._target = 1.0 if self._state else 0.0 + if self._callback: + self._callback(self._state) - def get_state(self): + def get_state(self) -> bool: return self._state def set_state(self, state: bool): From d6651ccd82eafd79ee1f70f5979cb2e17442e5e7 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 9 Oct 2025 23:09:55 -0700 Subject: [PATCH 135/910] raylib: implement developer panel (#36292) * first pass by cursor * fix * tell it what's good * stash * desc * clean up junk * alpha long can't use onroad cycle again due to faults * lint * fix kb --- selfdrive/ui/layouts/settings/developer.py | 142 ++++++++++++++---- selfdrive/ui/layouts/settings/toggles.py | 5 +- .../ui/tests/test_ui/raylib_screenshots.py | 2 +- 3 files changed, 115 insertions(+), 34 deletions(-) diff --git a/selfdrive/ui/layouts/settings/developer.py b/selfdrive/ui/layouts/settings/developer.py index cfef0f84d..143bb2c26 100644 --- a/selfdrive/ui/layouts/settings/developer.py +++ b/selfdrive/ui/layouts/settings/developer.py @@ -1,5 +1,6 @@ from openpilot.common.params import Params from openpilot.selfdrive.ui.widgets.ssh_key import ssh_key_item +from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.list_view import toggle_item from openpilot.system.ui.widgets.scroller import Scroller @@ -10,11 +11,15 @@ DESCRIPTIONS = { "ADB (Android Debug Bridge) allows connecting to your device over USB or over the network. " + "See https://docs.comma.ai/how-to/connect-to-comma for more info." ), - 'joystick_debug_mode': "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)", 'ssh_key': ( "Warning: This grants SSH access to all public keys in your GitHub settings. Never enter a GitHub username " + "other than your own. A comma employee will NEVER ask you to add their GitHub username." ), + 'alpha_longitudinal': ( + "WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB).

" + + "On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. " + + "Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha." + ), } @@ -22,32 +27,55 @@ class DeveloperLayout(Widget): def __init__(self): super().__init__() self._params = Params() + self._is_release = self._params.get_bool("IsReleaseBranch") + + # Build items and keep references for callbacks/state updates + self._adb_toggle = toggle_item( + "Enable ADB", + description=DESCRIPTIONS["enable_adb"], + initial_state=self._params.get_bool("AdbEnabled"), + callback=self._on_enable_adb, + ) + + # SSH enable toggle + SSH key management + self._ssh_toggle = toggle_item( + "Enable SSH", + description="", + initial_state=self._params.get_bool("SshEnabled"), + callback=self._on_enable_ssh, + ) + self._ssh_keys = ssh_key_item("SSH Keys", description=DESCRIPTIONS["ssh_key"]) + + self._joystick_toggle = toggle_item( + "Joystick Debug Mode", + description="", + initial_state=self._params.get_bool("JoystickDebugMode"), + callback=self._on_joystick_debug_mode, + ) + + self._long_maneuver_toggle = toggle_item( + "Longitudinal Maneuver Mode", + description="", + initial_state=self._params.get_bool("LongitudinalManeuverMode"), + callback=self._on_long_maneuver_mode, + ) + + self._alpha_long_toggle = toggle_item( + "openpilot Longitudinal Control (Alpha)", + description=DESCRIPTIONS["alpha_longitudinal"], + initial_state=self._params.get_bool("AlphaLongitudinalEnabled"), + callback=self._on_alpha_long_enabled, + ) + + self._alpha_long_toggle.set_description(self._alpha_long_toggle.description + " Changing this setting will restart openpilot if the car is powered on.") + items = [ - toggle_item( - "Enable ADB", - description=DESCRIPTIONS["enable_adb"], - initial_state=self._params.get_bool("AdbEnabled"), - callback=self._on_enable_adb, - ), - ssh_key_item("SSH Key", description=DESCRIPTIONS["ssh_key"]), - toggle_item( - "Joystick Debug Mode", - description=DESCRIPTIONS["joystick_debug_mode"], - initial_state=self._params.get_bool("JoystickDebugMode"), - callback=self._on_joystick_debug_mode, - ), - toggle_item( - "Longitudinal Maneuver Mode", - description="", - initial_state=self._params.get_bool("LongitudinalManeuverMode"), - callback=self._on_long_maneuver_mode, - ), - toggle_item( - "openpilot Longitudinal Control (Alpha)", - description="", - initial_state=self._params.get_bool("AlphaLongitudinalEnabled"), - callback=self._on_alpha_long_enabled, - ), + self._adb_toggle, + self._ssh_toggle, + self._ssh_keys, + self._joystick_toggle, + self._long_maneuver_toggle, + self._alpha_long_toggle, ] self._scroller = Scroller(items, line_separator=True, spacing=0) @@ -55,7 +83,61 @@ class DeveloperLayout(Widget): def _render(self, rect): self._scroller.render(rect) - def _on_enable_adb(self): pass - def _on_joystick_debug_mode(self): pass - def _on_long_maneuver_mode(self): pass - def _on_alpha_long_enabled(self): pass + def show_event(self): + self._update_toggles() + + def _update_toggles(self): + # Hide non-release toggles on release builds + for item in (self._adb_toggle, self._joystick_toggle, self._long_maneuver_toggle, self._alpha_long_toggle): + item.set_visible(not self._is_release) + + # CP gating + if ui_state.CP is not None: + alpha_avail = ui_state.CP.alphaLongitudinalAvailable + if not alpha_avail or self._is_release: + self._alpha_long_toggle.set_visible(False) + self._params.remove("AlphaLongitudinalEnabled") + else: + self._alpha_long_toggle.set_visible(True) + + self._long_maneuver_toggle.action_item.set_enabled(ui_state.has_longitudinal_control and ui_state.is_offroad) + else: + self._long_maneuver_toggle.action_item.set_enabled(False) + self._alpha_long_toggle.set_visible(False) + + # TODO: make a param control list item so we don't need to manage internal state as much here + # refresh toggles from params to mirror external changes + for key, item in ( + ("AdbEnabled", self._adb_toggle), + ("SshEnabled", self._ssh_toggle), + ("JoystickDebugMode", self._joystick_toggle), + ("LongitudinalManeuverMode", self._long_maneuver_toggle), + ("AlphaLongitudinalEnabled", self._alpha_long_toggle), + ): + item.action_item.set_state(self._params.get_bool(key)) + + def _update_state(self): + # Disable toggles that require onroad restart + # TODO: we can do an onroad cycle, but alpha long toggle requires a deinit function to re-enable radar and not fault + for item in (self._adb_toggle, self._joystick_toggle, self._long_maneuver_toggle, self._alpha_long_toggle): + item.action_item.set_enabled(ui_state.is_offroad) + + def _on_enable_adb(self, state: bool): + self._params.put_bool("AdbEnabled", state) + + def _on_enable_ssh(self, state: bool): + self._params.put_bool("SshEnabled", state) + + def _on_joystick_debug_mode(self, state: bool): + self._params.put_bool("JoystickDebugMode", state) + self._params.put_bool("LongitudinalManeuverMode", False) + self._long_maneuver_toggle.action_item.set_state(False) + + def _on_long_maneuver_mode(self, state: bool): + self._params.put_bool("LongitudinalManeuverMode", state) + self._params.put_bool("JoystickDebugMode", False) + self._joystick_toggle.action_item.set_state(False) + + def _on_alpha_long_enabled(self, state: bool): + self._params.put_bool("AlphaLongitudinalEnabled", state) + self._update_toggles() diff --git a/selfdrive/ui/layouts/settings/toggles.py b/selfdrive/ui/layouts/settings/toggles.py index 01f663d4b..fddcf32fb 100644 --- a/selfdrive/ui/layouts/settings/toggles.py +++ b/selfdrive/ui/layouts/settings/toggles.py @@ -34,6 +34,7 @@ class TogglesLayout(Widget): def __init__(self): super().__init__() self._params = Params() + self._is_release = self._params.get_bool("IsReleaseBranch") # param, title, desc, icon, needs_restart self._toggle_defs = { @@ -158,8 +159,6 @@ class TogglesLayout(Widget): "The Experimental mode logo will also be shown in the top right corner." ) - is_release = self._params.get_bool("IsReleaseBranch") - if ui_state.CP is not None: if ui_state.has_longitudinal_control: self._toggles["ExperimentalMode"].action_item.set_enabled(True) @@ -176,7 +175,7 @@ class TogglesLayout(Widget): long_desc = unavailable + " openpilot longitudinal control may come in a future update." if ui_state.CP.getAlphaLongitudinalAvailable(): - if is_release: + if self._is_release: long_desc = unavailable + " " + ("An alpha version of openpilot longitudinal control can be tested, along with " + "Experimental mode, on non-release branches.") else: diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py index e77a2d4d7..fb42b94d6 100755 --- a/selfdrive/ui/tests/test_ui/raylib_screenshots.py +++ b/selfdrive/ui/tests/test_ui/raylib_screenshots.py @@ -76,7 +76,7 @@ def setup_settings_developer(click, pm: PubMaster): def setup_keyboard(click, pm: PubMaster): setup_settings_developer(click, pm) - click(1930, 270) + click(1930, 470) def setup_pair_device(click, pm: PubMaster): From b521a913ab4bb12a33d506efae7f8a939bff4ae2 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 10 Oct 2025 01:18:07 -0700 Subject: [PATCH 136/910] raylib: confirm dialog uses HTML renderer 2 (#36297) * start * keep it simple * rm --- system/ui/widgets/confirm_dialog.py | 35 ++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/system/ui/widgets/confirm_dialog.py b/system/ui/widgets/confirm_dialog.py index 606b04b6e..e9d0cb53b 100644 --- a/system/ui/widgets/confirm_dialog.py +++ b/system/ui/widgets/confirm_dialog.py @@ -3,27 +3,35 @@ from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.widgets import DialogResult from openpilot.system.ui.widgets.button import ButtonStyle, Button from openpilot.system.ui.widgets.label import Label +from openpilot.system.ui.widgets.html_render import HtmlRenderer from openpilot.system.ui.widgets import Widget +from openpilot.system.ui.widgets.scroller import Scroller -DIALOG_WIDTH = 1748 -DIALOG_HEIGHT = 690 +OUTER_MARGIN = 200 +RICH_OUTER_MARGIN = 100 BUTTON_HEIGHT = 160 MARGIN = 50 -TEXT_AREA_HEIGHT_REDUCTION = 200 +TEXT_PADDING = 10 BACKGROUND_COLOR = rl.Color(27, 27, 27, 255) class ConfirmDialog(Widget): - def __init__(self, text: str, confirm_text: str, cancel_text: str = "Cancel"): + def __init__(self, text: str, confirm_text: str, cancel_text: str = "Cancel", rich: bool = False): super().__init__() self._label = Label(text, 70, FontWeight.BOLD, text_color=rl.Color(201, 201, 201, 255)) + self._html_renderer = HtmlRenderer(text=text) self._cancel_button = Button(cancel_text, self._cancel_button_callback) self._confirm_button = Button(confirm_text, self._confirm_button_callback, button_style=ButtonStyle.PRIMARY) + self._rich = rich self._dialog_result = DialogResult.NO_ACTION self._cancel_text = cancel_text + self._scroller = Scroller([self._html_renderer], line_separator=False, spacing=0) def set_text(self, text): - self._label.set_text(text) + if not self._rich: + self._label.set_text(text) + else: + self._html_renderer.parse_html_content(text) def reset(self): self._dialog_result = DialogResult.NO_ACTION @@ -35,9 +43,11 @@ class ConfirmDialog(Widget): self._dialog_result = DialogResult.CONFIRM def _render(self, rect: rl.Rectangle): - 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) + dialog_x = OUTER_MARGIN if not self._rich else RICH_OUTER_MARGIN + dialog_y = OUTER_MARGIN if not self._rich else RICH_OUTER_MARGIN + dialog_width = gui_app.width - 2 * dialog_x + dialog_height = gui_app.height - 2 * dialog_y + dialog_rect = rl.Rectangle(dialog_x, dialog_y, dialog_width, dialog_height) bottom = dialog_rect.y + dialog_rect.height button_width = (dialog_rect.width - 3 * MARGIN) // 2 @@ -49,8 +59,13 @@ class ConfirmDialog(Widget): rl.draw_rectangle_rec(dialog_rect, BACKGROUND_COLOR) - text_rect = rl.Rectangle(dialog_rect.x + MARGIN, dialog_rect.y, dialog_rect.width - 2 * MARGIN, dialog_rect.height - TEXT_AREA_HEIGHT_REDUCTION) - self._label.render(text_rect) + text_rect = rl.Rectangle(dialog_rect.x + MARGIN, dialog_rect.y + TEXT_PADDING, + dialog_rect.width - 2 * MARGIN, dialog_rect.height - BUTTON_HEIGHT - MARGIN - TEXT_PADDING * 2) + if not self._rich: + self._label.render(text_rect) + else: + self._html_renderer.set_rect(text_rect) + self._scroller.render(text_rect) if rl.is_key_pressed(rl.KeyboardKey.KEY_ENTER): self._dialog_result = DialogResult.CONFIRM From cac8d3f405c760cfcb8272b7065916b4da77456d Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 10 Oct 2025 01:40:29 -0700 Subject: [PATCH 137/910] raylib: fix missing showing dialog in setup/updater (#36298) * fix showing dialog * here for safety --- system/ui/reset.py | 4 +++- system/ui/setup.py | 4 +++- system/ui/updater.py | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/system/ui/reset.py b/system/ui/reset.py index a5cf1731d..85ce4a858 100755 --- a/system/ui/reset.py +++ b/system/ui/reset.py @@ -126,7 +126,9 @@ def main(): if mode == ResetMode.FORMAT: reset.start_reset() - for _ in gui_app.render(): + for showing_dialog in gui_app.render(): + if showing_dialog: + continue if not reset.render(rl.Rectangle(45, 200, gui_app.width - 90, gui_app.height - 245)): break diff --git a/system/ui/setup.py b/system/ui/setup.py index e0d737cb1..4068af01e 100755 --- a/system/ui/setup.py +++ b/system/ui/setup.py @@ -423,7 +423,9 @@ def main(): try: gui_app.init_window("Setup", 20) setup = Setup() - for _ in gui_app.render(): + for showing_dialog in gui_app.render(): + if showing_dialog: + continue setup.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) setup.close() except Exception as e: diff --git a/system/ui/updater.py b/system/ui/updater.py index 48903fa5b..4184c5cb1 100755 --- a/system/ui/updater.py +++ b/system/ui/updater.py @@ -158,7 +158,9 @@ def main(): try: gui_app.init_window("System Update") updater = Updater(updater_path, manifest_path) - for _ in gui_app.render(): + for showing_dialog in gui_app.render(): + if showing_dialog: + continue updater.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) finally: # Make sure we clean up even if there's an error From 0f40afa357af84a04e65c8ed40e51c87dafa7920 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 10 Oct 2025 01:41:11 -0700 Subject: [PATCH 138/910] raylib: fix black updater bg for network (#36299) fix black bg --- system/ui/updater.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/system/ui/updater.py b/system/ui/updater.py index 4184c5cb1..38a93687e 100755 --- a/system/ui/updater.py +++ b/system/ui/updater.py @@ -113,8 +113,11 @@ class Updater(Widget): def render_wifi_screen(self, rect: rl.Rectangle): # Draw the Wi-Fi manager UI - wifi_rect = rl.Rectangle(MARGIN + 50, MARGIN, rect.width - MARGIN * 2 - 100, rect.height - MARGIN * 2 - BUTTON_HEIGHT - 20) - self.wifi_manager_ui.render(wifi_rect) + wifi_rect = rl.Rectangle(rect.x + MARGIN, rect.y + MARGIN, rect.width - MARGIN * 2, + rect.height - BUTTON_HEIGHT - MARGIN * 3) + rl.draw_rectangle_rounded(wifi_rect, 0.035, 10, rl.Color(51, 51, 51, 255)) + wifi_content_rect = rl.Rectangle(wifi_rect.x + 50, wifi_rect.y, wifi_rect.width - 100, wifi_rect.height) + self.wifi_manager_ui.render(wifi_content_rect) back_button_rect = rl.Rectangle(MARGIN, rect.height - MARGIN - BUTTON_HEIGHT, BUTTON_WIDTH, BUTTON_HEIGHT) self._back_button.render(back_button_rect) From ddbbcc6f5dab250cfeaec478cf819c9cec737632 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 10 Oct 2025 03:03:35 -0700 Subject: [PATCH 139/910] raylib: add experimental mode + alpha long confirmation dialog + related fixes (#36295) * here's everything * just the dev part * same for exp mode! * use rich * fix br not working in p * html height needs to be different than content/text rect * fix confirmation * fix * fix 2.5s lag * clean up * use correct param * add offroad and engaged callback too * nl * lint --- selfdrive/ui/layouts/settings/developer.py | 37 +++++++++++++++---- selfdrive/ui/layouts/settings/toggles.py | 42 ++++++++++++++++++---- selfdrive/ui/ui_state.py | 25 ++++++++++--- system/ui/widgets/confirm_dialog.py | 8 +++-- system/ui/widgets/html_render.py | 2 ++ 5 files changed, 92 insertions(+), 22 deletions(-) diff --git a/selfdrive/ui/layouts/settings/developer.py b/selfdrive/ui/layouts/settings/developer.py index 143bb2c26..32d278ee9 100644 --- a/selfdrive/ui/layouts/settings/developer.py +++ b/selfdrive/ui/layouts/settings/developer.py @@ -4,6 +4,9 @@ from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.list_view import toggle_item from openpilot.system.ui.widgets.scroller import Scroller +from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog +from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.widgets import DialogResult # Description constants DESCRIPTIONS = { @@ -80,6 +83,9 @@ class DeveloperLayout(Widget): self._scroller = Scroller(items, line_separator=True, spacing=0) + # Toggles should be not available to change in onroad state + ui_state.add_offroad_transition_callback(self._update_toggles) + def _render(self, rect): self._scroller.render(rect) @@ -87,7 +93,10 @@ class DeveloperLayout(Widget): self._update_toggles() def _update_toggles(self): + ui_state.update_params() + # Hide non-release toggles on release builds + # TODO: we can do an onroad cycle, but alpha long toggle requires a deinit function to re-enable radar and not fault for item in (self._adb_toggle, self._joystick_toggle, self._long_maneuver_toggle, self._alpha_long_toggle): item.set_visible(not self._is_release) @@ -100,7 +109,11 @@ class DeveloperLayout(Widget): else: self._alpha_long_toggle.set_visible(True) - self._long_maneuver_toggle.action_item.set_enabled(ui_state.has_longitudinal_control and ui_state.is_offroad) + long_man_enabled = ui_state.has_longitudinal_control and ui_state.is_offroad() + self._long_maneuver_toggle.action_item.set_enabled(long_man_enabled) + if not long_man_enabled: + self._long_maneuver_toggle.action_item.set_state(False) + self._params.put_bool("LongitudinalManeuverMode", False) else: self._long_maneuver_toggle.action_item.set_enabled(False) self._alpha_long_toggle.set_visible(False) @@ -116,12 +129,6 @@ class DeveloperLayout(Widget): ): item.action_item.set_state(self._params.get_bool(key)) - def _update_state(self): - # Disable toggles that require onroad restart - # TODO: we can do an onroad cycle, but alpha long toggle requires a deinit function to re-enable radar and not fault - for item in (self._adb_toggle, self._joystick_toggle, self._long_maneuver_toggle, self._alpha_long_toggle): - item.action_item.set_enabled(ui_state.is_offroad) - def _on_enable_adb(self, state: bool): self._params.put_bool("AdbEnabled", state) @@ -139,5 +146,21 @@ class DeveloperLayout(Widget): self._joystick_toggle.action_item.set_state(False) def _on_alpha_long_enabled(self, state: bool): + if state: + def confirm_callback(result: int): + if result == DialogResult.CONFIRM: + self._params.put_bool("AlphaLongitudinalEnabled", True) + self._update_toggles() + else: + self._alpha_long_toggle.action_item.set_state(False) + + # show confirmation dialog + content = (f"

{self._alpha_long_toggle.title}


" + + f"

{self._alpha_long_toggle.description}

") + + dlg = ConfirmDialog(content, "Enable", rich=True) + gui_app.set_modal_overlay(dlg, callback=confirm_callback) + return + self._params.put_bool("AlphaLongitudinalEnabled", state) self._update_toggles() diff --git a/selfdrive/ui/layouts/settings/toggles.py b/selfdrive/ui/layouts/settings/toggles.py index fddcf32fb..8fdbd6f9a 100644 --- a/selfdrive/ui/layouts/settings/toggles.py +++ b/selfdrive/ui/layouts/settings/toggles.py @@ -3,6 +3,9 @@ from openpilot.common.params import Params, UnknownKeyName from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.list_view import multiple_button_item, toggle_item from openpilot.system.ui.widgets.scroller import Scroller +from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog +from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.widgets import DialogResult from openpilot.selfdrive.ui.ui_state import ui_state PERSONALITY_TO_INT = log.LongitudinalPersonality.schema.enumerants @@ -131,6 +134,8 @@ class TogglesLayout(Widget): self._update_experimental_mode_icon() self._scroller = Scroller(list(self._toggles.values()), line_separator=True, spacing=0) + ui_state.add_engaged_transition_callback(self._update_toggles) + def _update_state(self): if ui_state.sm.updated["selfdriveState"]: personality = PERSONALITY_TO_INT[ui_state.sm["selfdriveState"].personality] @@ -138,15 +143,12 @@ class TogglesLayout(Widget): self._long_personality_setting.action_item.set_selected_button(personality) ui_state.personality = personality - # these toggles need restart, block while engaged - for toggle_def in self._toggle_defs: - if self._toggle_defs[toggle_def][3] and toggle_def not in self._locked_toggles: - self._toggles[toggle_def].action_item.set_enabled(not ui_state.engaged) - def show_event(self): self._update_toggles() def _update_toggles(self): + ui_state.update_params() + e2e_description = ( "openpilot defaults to driving in chill mode. Experimental mode enables alpha-level features that aren't ready for chill mode. " + "Experimental features are listed below:
" + @@ -174,7 +176,7 @@ class TogglesLayout(Widget): unavailable = "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." long_desc = unavailable + " openpilot longitudinal control may come in a future update." - if ui_state.CP.getAlphaLongitudinalAvailable(): + if ui_state.CP.alphaLongitudinalAvailable: if self._is_release: long_desc = unavailable + " " + ("An alpha version of openpilot longitudinal control can be tested, along with " + "Experimental mode, on non-release branches.") @@ -192,6 +194,11 @@ class TogglesLayout(Widget): for param in self._toggle_defs: self._toggles[param].action_item.set_state(self._params.get_bool(param)) + # these toggles need restart, block while engaged + for toggle_def in self._toggle_defs: + if self._toggle_defs[toggle_def][3] and toggle_def not in self._locked_toggles: + self._toggles[toggle_def].action_item.set_enabled(not ui_state.engaged) + def _render(self, rect): self._scroller.render(rect) @@ -199,9 +206,30 @@ class TogglesLayout(Widget): icon = "experimental.png" if self._toggles["ExperimentalMode"].action_item.get_state() else "experimental_white.png" self._toggles["ExperimentalMode"].set_icon(icon) + def _handle_experimental_mode_toggle(self, state: bool): + confirmed = self._params.get_bool("ExperimentalModeConfirmed") + if state and not confirmed: + def confirm_callback(result: int): + if result == DialogResult.CONFIRM: + self._params.put_bool("ExperimentalMode", True) + self._params.put_bool("ExperimentalModeConfirmed", True) + else: + self._toggles["ExperimentalMode"].action_item.set_state(False) + self._update_experimental_mode_icon() + + # show confirmation dialog + content = (f"

{self._toggles['ExperimentalMode'].title}


" + + f"

{self._toggles['ExperimentalMode'].description}

") + dlg = ConfirmDialog(content, "Enable", rich=True) + gui_app.set_modal_overlay(dlg, callback=confirm_callback) + else: + self._update_experimental_mode_icon() + self._params.put_bool("ExperimentalMode", state) + def _toggle_callback(self, state: bool, param: str): if param == "ExperimentalMode": - self._update_experimental_mode_icon() + self._handle_experimental_mode_toggle(state) + return self._params.put_bool(param, state) if self._toggle_defs[param][3]: diff --git a/selfdrive/ui/ui_state.py b/selfdrive/ui/ui_state.py index be8351798..f4b9e1a9b 100644 --- a/selfdrive/ui/ui_state.py +++ b/selfdrive/ui/ui_state.py @@ -74,7 +74,17 @@ class UIState: self.light_sensor: float = -1.0 self._param_update_time: float = 0.0 - self._update_params() + # Callbacks + self._offroad_transition_callbacks: list[Callable[[], None]] = [] + self._engaged_transition_callbacks: list[Callable[[], None]] = [] + + self.update_params() + + def add_offroad_transition_callback(self, callback: Callable[[], None]): + self._offroad_transition_callbacks.append(callback) + + def add_engaged_transition_callback(self, callback: Callable[[], None]): + self._engaged_transition_callbacks.append(callback) @property def engaged(self) -> bool: @@ -91,8 +101,7 @@ class UIState: self._update_state() self._update_status() if time.monotonic() - self._param_update_time > 5.0: - self._update_params() - self._param_update_time = time.monotonic() + self.update_params() device.update() def _update_state(self) -> None: @@ -131,6 +140,8 @@ class UIState: # Check for engagement state changes if self.engaged != self._engaged_prev: + for callback in self._engaged_transition_callbacks: + callback() self._engaged_prev = self.engaged # Handle onroad/offroad transition @@ -140,19 +151,23 @@ class UIState: self.started_frame = self.sm.frame self.started_time = time.monotonic() + for callback in self._offroad_transition_callbacks: + callback() + self._started_prev = self.started - def _update_params(self) -> None: + def update_params(self) -> None: self.is_metric = self.params.get_bool("IsMetric") # Update longitudinal control state - CP_bytes = self.params.get("CarParams") + CP_bytes = self.params.get("CarParamsPersistent") if CP_bytes is not None: self.CP = messaging.log_from_bytes(CP_bytes, car.CarParams) if self.CP.alphaLongitudinalAvailable: self.has_longitudinal_control = self.params.get_bool("AlphaLongitudinalEnabled") else: self.has_longitudinal_control = self.CP.openpilotLongitudinalControl + self._param_update_time = time.monotonic() class Device: diff --git a/system/ui/widgets/confirm_dialog.py b/system/ui/widgets/confirm_dialog.py index e9d0cb53b..789ae53f3 100644 --- a/system/ui/widgets/confirm_dialog.py +++ b/system/ui/widgets/confirm_dialog.py @@ -3,7 +3,7 @@ from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.widgets import DialogResult from openpilot.system.ui.widgets.button import ButtonStyle, Button from openpilot.system.ui.widgets.label import Label -from openpilot.system.ui.widgets.html_render import HtmlRenderer +from openpilot.system.ui.widgets.html_render import HtmlRenderer, ElementType from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.scroller import Scroller @@ -19,7 +19,7 @@ class ConfirmDialog(Widget): def __init__(self, text: str, confirm_text: str, cancel_text: str = "Cancel", rich: bool = False): super().__init__() self._label = Label(text, 70, FontWeight.BOLD, text_color=rl.Color(201, 201, 201, 255)) - self._html_renderer = HtmlRenderer(text=text) + self._html_renderer = HtmlRenderer(text=text, text_size={ElementType.P: 50}) self._cancel_button = Button(cancel_text, self._cancel_button_callback) self._confirm_button = Button(confirm_text, self._confirm_button_callback, button_style=ButtonStyle.PRIMARY) self._rich = rich @@ -64,7 +64,9 @@ class ConfirmDialog(Widget): if not self._rich: self._label.render(text_rect) else: - self._html_renderer.set_rect(text_rect) + html_rect = rl.Rectangle(text_rect.x, text_rect.y, text_rect.width, + self._html_renderer.get_total_height(int(text_rect.width))) + self._html_renderer.set_rect(html_rect) self._scroller.render(text_rect) if rl.is_key_pressed(rl.KeyboardKey.KEY_ENTER): diff --git a/system/ui/widgets/html_render.py b/system/ui/widgets/html_render.py index 8d713ee7e..938867f74 100644 --- a/system/ui/widgets/html_render.py +++ b/system/ui/widgets/html_render.py @@ -121,6 +121,8 @@ class HtmlRenderer(Widget): is_start_tag, is_end_tag, tag = is_tag(token) if tag is not None: if tag == ElementType.BR: + # Close current tag and add a line break + close_tag() self._add_element(ElementType.BR, "") elif is_start_tag or is_end_tag: From f04ee804529b71c5976fe24fcc0902191befa223 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 10 Oct 2025 03:57:12 -0700 Subject: [PATCH 140/910] raylib: implement calibration description (#36300) * this is all cursor * also cursor * inline reset calib * calib desc * way better * huh * clean up * rcvr * stash changes to change params * Revert "stash changes to change params" This reverts commit ee998f04c4235ed20493b83e35c9f28e182d89b0. --- selfdrive/ui/layouts/settings/developer.py | 2 - selfdrive/ui/layouts/settings/device.py | 82 ++++++++++++++++++---- system/ui/widgets/confirm_dialog.py | 8 +-- system/ui/widgets/list_view.py | 8 +++ 4 files changed, 79 insertions(+), 21 deletions(-) diff --git a/selfdrive/ui/layouts/settings/developer.py b/selfdrive/ui/layouts/settings/developer.py index 32d278ee9..d3a829df8 100644 --- a/selfdrive/ui/layouts/settings/developer.py +++ b/selfdrive/ui/layouts/settings/developer.py @@ -70,8 +70,6 @@ class DeveloperLayout(Widget): callback=self._on_alpha_long_enabled, ) - self._alpha_long_toggle.set_description(self._alpha_long_toggle.description + " Changing this setting will restart openpilot if the car is powered on.") - items = [ self._adb_toggle, self._ssh_toggle, diff --git a/selfdrive/ui/layouts/settings/device.py b/selfdrive/ui/layouts/settings/device.py index 62abd2b99..758200c0a 100644 --- a/selfdrive/ui/layouts/settings/device.py +++ b/selfdrive/ui/layouts/settings/device.py @@ -1,8 +1,11 @@ import os import json +import math +from cereal import messaging, log from openpilot.common.basedir import BASEDIR from openpilot.common.params import Params +from openpilot.common.swaglog import cloudlog from openpilot.selfdrive.ui.onroad.driver_camera_dialog import DriverCameraDialog from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.selfdrive.ui.layouts.onboarding import TrainingGuide @@ -20,10 +23,7 @@ from openpilot.system.ui.widgets.scroller import Scroller DESCRIPTIONS = { 'pair_device': "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer.", 'driver_camera': "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)", - 'reset_calibration': ( - "openpilot requires the device to be mounted within 4° left or right and within 5° " + - "up or 9° down. openpilot is continuously calibrating, resetting is rarely required." - ), + 'reset_calibration': "openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down.", 'review_guide': "Review the rules, features, and limitations of openpilot", } @@ -49,12 +49,15 @@ class DeviceLayout(Widget): self._pair_device_btn = button_item("Pair Device", "PAIR", DESCRIPTIONS['pair_device'], callback=self._pair_device) self._pair_device_btn.set_visible(lambda: not ui_state.prime_state.is_paired()) + self._reset_calib_btn = button_item("Reset Calibration", "RESET", DESCRIPTIONS['reset_calibration'], callback=self._reset_calibration_prompt) + self._reset_calib_btn.set_description_opened_callback(self._update_calib_description) + items = [ text_item("Dongle ID", dongle_id), text_item("Serial", serial), self._pair_device_btn, button_item("Driver Camera", "PREVIEW", DESCRIPTIONS['driver_camera'], callback=self._show_driver_camera, enabled=ui_state.is_offroad), - button_item("Reset Calibration", "RESET", DESCRIPTIONS['reset_calibration'], callback=self._reset_calibration_prompt), + self._reset_calib_btn, button_item("Review Training Guide", "REVIEW", DESCRIPTIONS['review_guide'], self._on_review_training_guide), regulatory_btn := button_item("Regulatory", "VIEW", callback=self._on_regulatory), button_item("Change Language", "CHANGE", callback=self._show_language_selection, enabled=ui_state.is_offroad), @@ -95,19 +98,68 @@ class DeviceLayout(Widget): gui_app.set_modal_overlay(alert_dialog("Disengage to Reset Calibration")) return + def reset_calibration(result: int): + # Check engaged again in case it changed while the dialog was open + if ui_state.engaged or result != DialogResult.CONFIRM: + return + + self._params.remove("CalibrationParams") + self._params.remove("LiveTorqueParameters") + self._params.remove("LiveParameters") + self._params.remove("LiveParametersV2") + self._params.remove("LiveDelay") + self._params.put_bool("OnroadCycleRequested", True) + self._update_calib_description() + dialog = ConfirmDialog("Are you sure you want to reset calibration?", "Reset") - gui_app.set_modal_overlay(dialog, callback=self._reset_calibration) + gui_app.set_modal_overlay(dialog, callback=reset_calibration) - def _reset_calibration(self, result: int): - if ui_state.engaged or result != DialogResult.CONFIRM: - return + def _update_calib_description(self): + desc = DESCRIPTIONS['reset_calibration'] - self._params.remove("CalibrationParams") - self._params.remove("LiveTorqueParameters") - self._params.remove("LiveParameters") - self._params.remove("LiveParametersV2") - self._params.remove("LiveDelay") - self._params.put_bool("OnroadCycleRequested", True) + calib_bytes = self._params.get("CalibrationParams") + if calib_bytes: + try: + calib = messaging.log_from_bytes(calib_bytes, log.Event).liveCalibration + + if calib.calStatus != log.LiveCalibrationData.Status.uncalibrated: + pitch = math.degrees(calib.rpyCalib[1]) + yaw = math.degrees(calib.rpyCalib[2]) + desc += f" Your device is pointed {abs(pitch):.1f}° {'down' if pitch > 0 else 'up'} and {abs(yaw):.1f}° {'left' if yaw > 0 else 'right'}." + except Exception: + cloudlog.exception("invalid CalibrationParams") + + lag_perc = 0 + lag_bytes = self._params.get("LiveDelay") + if lag_bytes: + try: + lag_perc = messaging.log_from_bytes(lag_bytes, log.Event).liveDelay.calPerc + except Exception: + cloudlog.exception("invalid LiveDelay") + if lag_perc < 100: + desc += f"

Steering lag calibration is {lag_perc}% complete." + else: + desc += "

Steering lag calibration is complete." + + torque_bytes = self._params.get("LiveTorqueParameters") + if torque_bytes: + try: + torque = messaging.log_from_bytes(torque_bytes, log.Event).liveTorqueParameters + # don't add for non-torque cars + if torque.useParams: + torque_perc = torque.calPerc + if torque_perc < 100: + desc += f" Steering torque response calibration is {torque_perc}% complete." + else: + desc += " Steering torque response calibration is complete." + except Exception: + cloudlog.exception("invalid LiveTorqueParameters") + + desc += "

" + desc += ("openpilot is continuously calibrating, resetting is rarely required. " + + "Resetting calibration will restart openpilot if the car is powered on.") + + self._reset_calib_btn.set_description(desc) def _reboot_prompt(self): if ui_state.engaged: diff --git a/system/ui/widgets/confirm_dialog.py b/system/ui/widgets/confirm_dialog.py index 789ae53f3..6d3e143b5 100644 --- a/system/ui/widgets/confirm_dialog.py +++ b/system/ui/widgets/confirm_dialog.py @@ -78,12 +78,12 @@ class ConfirmDialog(Widget): self._confirm_button.render(confirm_button) self._cancel_button.render(cancel_button) else: - centered_button_x = dialog_rect.x + (dialog_rect.width - button_width) / 2 - centered_confirm_button = rl.Rectangle(centered_button_x, button_y, button_width, BUTTON_HEIGHT) - self._confirm_button.render(centered_confirm_button) + full_button_width = dialog_rect.width - 2 * MARGIN + full_confirm_button = rl.Rectangle(dialog_rect.x + MARGIN, button_y, full_button_width, BUTTON_HEIGHT) + self._confirm_button.render(full_confirm_button) return self._dialog_result -def alert_dialog(message: str, button_text: str = "OK"): +def alert_dialog(message: str, button_text: str = "Ok"): return ConfirmDialog(message, button_text, cancel_text="") diff --git a/system/ui/widgets/list_view.py b/system/ui/widgets/list_view.py index bbf66c655..d203ec813 100644 --- a/system/ui/widgets/list_view.py +++ b/system/ui/widgets/list_view.py @@ -268,6 +268,7 @@ class ListItem(Widget): self._description = description self.description_visible = description_visible self.callback = callback + self.description_opened_callback: Callable | None = None self.action_item = action_item self.set_rect(rl.Rectangle(0, 0, ITEM_BASE_WIDTH, ITEM_BASE_HEIGHT)) @@ -280,6 +281,9 @@ class ListItem(Widget): # Cached properties for performance self._prev_description: str | None = self.description + def set_description_opened_callback(self, callback: Callable) -> None: + self.description_opened_callback = callback + def set_touch_valid_callback(self, touch_callback: Callable[[], bool]) -> None: super().set_touch_valid_callback(touch_callback) if self.action_item: @@ -302,6 +306,10 @@ class ListItem(Widget): if self.description: self.description_visible = not self.description_visible + # do callback first in case receiver changes description + if self.description_visible and self.description_opened_callback is not None: + self.description_opened_callback() + content_width = int(self._rect.width - ITEM_PADDING * 2) self._rect.height = self.get_item_height(self._font, content_width) From 4ff77a47525174f42e6d1af385336ccb9150a871 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 10 Oct 2025 04:10:24 -0700 Subject: [PATCH 141/910] raylib: fix DMoji (#36301) * wow first time cursor made a tiny change to fix a problem! * rm --- selfdrive/ui/onroad/driver_state.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/onroad/driver_state.py b/selfdrive/ui/onroad/driver_state.py index a25d9bd31..69ed5f458 100644 --- a/selfdrive/ui/onroad/driver_state.py +++ b/selfdrive/ui/onroad/driver_state.py @@ -222,7 +222,7 @@ class DriverStateRenderer(Widget): radius_y = arc_data.height / 2 x_coords = center_x + np.cos(angles) * radius_x - y_coords = center_y + np.sin(angles) * radius_y + y_coords = center_y - np.sin(angles) * radius_y arc_lines = self.h_arc_lines if is_horizontal else self.v_arc_lines for i, (x_coord, y_coord) in enumerate(zip(x_coords, y_coords, strict=True)): From fdcf8b592e5596386c5639875ce260b19267cc2f Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 10 Oct 2025 21:07:36 -0700 Subject: [PATCH 142/910] raylib: reset scrollers and description expansions on show event (#36304) * scroll up on hide * switch to show * dismiss descriptions too! * all is show * all is show * clean up * visible items helper * Revert "visible items helper" This reverts commit e64f05b69155483aa0f3d74bd511f5d7c1ecfb79. * reset --- selfdrive/ui/layouts/home.py | 15 ++++++++++++--- selfdrive/ui/layouts/settings/developer.py | 1 + selfdrive/ui/layouts/settings/device.py | 3 +++ selfdrive/ui/layouts/settings/firehose.py | 3 +++ selfdrive/ui/layouts/settings/software.py | 3 +++ selfdrive/ui/layouts/settings/toggles.py | 1 + selfdrive/ui/widgets/offroad_alerts.py | 3 +++ system/ui/lib/scroll_panel.py | 5 +++++ system/ui/widgets/list_view.py | 10 ++++++++-- system/ui/widgets/scroller.py | 12 ++++++++++++ 10 files changed, 51 insertions(+), 5 deletions(-) diff --git a/selfdrive/ui/layouts/home.py b/selfdrive/ui/layouts/home.py index f7f57c680..9243d8ea1 100644 --- a/selfdrive/ui/layouts/home.py +++ b/selfdrive/ui/layouts/home.py @@ -36,6 +36,8 @@ class HomeLayout(Widget): self.update_alert = UpdateAlert() self.offroad_alert = OffroadAlert() + self._layout_widgets = {HomeLayoutState.UPDATE: self.update_alert, HomeLayoutState.ALERTS: self.offroad_alert} + self.current_state = HomeLayoutState.HOME self.last_refresh = 0 self.settings_callback: callable | None = None @@ -71,6 +73,13 @@ class HomeLayout(Widget): self.settings_callback = callback def _set_state(self, state: HomeLayoutState): + # propagate show/hide events + if state != self.current_state: + if state in self._layout_widgets: + self._layout_widgets[state].show_event() + if self.current_state in self._layout_widgets: + self._layout_widgets[self.current_state].hide_event() + self.current_state = state def _render(self, rect: rl.Rectangle): @@ -201,11 +210,11 @@ class HomeLayout(Widget): # Show panels on transition from no alert/update to any alerts/update if not update_available and not alerts_present: - self.current_state = HomeLayoutState.HOME + self._set_state(HomeLayoutState.HOME) elif update_available and ((not self._prev_update_available) or (not alerts_present and self.current_state == HomeLayoutState.ALERTS)): - self.current_state = HomeLayoutState.UPDATE + self._set_state(HomeLayoutState.UPDATE) elif alerts_present and ((not self._prev_alerts_present) or (not update_available and self.current_state == HomeLayoutState.UPDATE)): - self.current_state = HomeLayoutState.ALERTS + self._set_state(HomeLayoutState.ALERTS) self.update_available = update_available self.alert_count = alert_count diff --git a/selfdrive/ui/layouts/settings/developer.py b/selfdrive/ui/layouts/settings/developer.py index d3a829df8..bc8fc953c 100644 --- a/selfdrive/ui/layouts/settings/developer.py +++ b/selfdrive/ui/layouts/settings/developer.py @@ -88,6 +88,7 @@ class DeveloperLayout(Widget): self._scroller.render(rect) def show_event(self): + self._scroller.show_event() self._update_toggles() def _update_toggles(self): diff --git a/selfdrive/ui/layouts/settings/device.py b/selfdrive/ui/layouts/settings/device.py index 758200c0a..286ba36f0 100644 --- a/selfdrive/ui/layouts/settings/device.py +++ b/selfdrive/ui/layouts/settings/device.py @@ -66,6 +66,9 @@ class DeviceLayout(Widget): regulatory_btn.set_visible(TICI) return items + def show_event(self): + self._scroller.show_event() + def _render(self, rect): self._scroller.render(rect) diff --git a/selfdrive/ui/layouts/settings/firehose.py b/selfdrive/ui/layouts/settings/firehose.py index 7e1d3b61b..353a9d976 100644 --- a/selfdrive/ui/layouts/settings/firehose.py +++ b/selfdrive/ui/layouts/settings/firehose.py @@ -49,6 +49,9 @@ class FirehoseLayout(Widget): self.update_thread.start() self.last_update_time = 0 + def show_event(self): + self.scroll_panel.set_offset(0) + def _get_segment_count(self) -> int: stats = self.params.get(self.PARAM_KEY) if not stats: diff --git a/selfdrive/ui/layouts/settings/software.py b/selfdrive/ui/layouts/settings/software.py index 7aebc609f..a337b9034 100644 --- a/selfdrive/ui/layouts/settings/software.py +++ b/selfdrive/ui/layouts/settings/software.py @@ -69,6 +69,9 @@ class SoftwareLayout(Widget): ] return items + def show_event(self): + self._scroller.show_event() + def _render(self, rect): self._scroller.render(rect) diff --git a/selfdrive/ui/layouts/settings/toggles.py b/selfdrive/ui/layouts/settings/toggles.py index 8fdbd6f9a..c75cc7574 100644 --- a/selfdrive/ui/layouts/settings/toggles.py +++ b/selfdrive/ui/layouts/settings/toggles.py @@ -144,6 +144,7 @@ class TogglesLayout(Widget): ui_state.personality = personality def show_event(self): + self._scroller.show_event() self._update_toggles() def _update_toggles(self): diff --git a/selfdrive/ui/widgets/offroad_alerts.py b/selfdrive/ui/widgets/offroad_alerts.py index 104597163..49edafa0f 100644 --- a/selfdrive/ui/widgets/offroad_alerts.py +++ b/selfdrive/ui/widgets/offroad_alerts.py @@ -117,6 +117,9 @@ class AbstractAlert(Widget, ABC): self.scroll_panel_rect = rl.Rectangle(0, 0, 0, 0) self.scroll_panel = GuiScrollPanel() + def show_event(self): + self.scroll_panel.set_offset(0) + def set_dismiss_callback(self, callback: Callable): self.dismiss_callback = callback self.dismiss_btn.set_click_callback(self.dismiss_callback) diff --git a/system/ui/lib/scroll_panel.py b/system/ui/lib/scroll_panel.py index d0d59df1f..f0031138f 100644 --- a/system/ui/lib/scroll_panel.py +++ b/system/ui/lib/scroll_panel.py @@ -123,3 +123,8 @@ class GuiScrollPanel: def is_touch_valid(self): return self._scroll_state == ScrollState.IDLE and abs(self._velocity_filter_y.x) < MIN_VELOCITY_FOR_CLICKING + + def set_offset(self, position: float) -> None: + self._offset_filter_y.x = position + self._velocity_filter_y.x = 0.0 + self._scroll_state = ScrollState.IDLE diff --git a/system/ui/widgets/list_view.py b/system/ui/widgets/list_view.py index d203ec813..b1c4b44df 100644 --- a/system/ui/widgets/list_view.py +++ b/system/ui/widgets/list_view.py @@ -281,6 +281,9 @@ class ListItem(Widget): # Cached properties for performance self._prev_description: str | None = self.description + def show_event(self): + self._set_description_visible(False) + def set_description_opened_callback(self, callback: Callable) -> None: self.description_opened_callback = callback @@ -304,8 +307,11 @@ class ListItem(Widget): # Click was on right item, don't toggle description return - if self.description: - self.description_visible = not self.description_visible + self._set_description_visible(not self.description_visible) + + def _set_description_visible(self, visible: bool): + if self.description and self.description_visible != visible: + self.description_visible = visible # do callback first in case receiver changes description if self.description_visible and self.description_opened_callback is not None: self.description_opened_callback() diff --git a/system/ui/widgets/scroller.py b/system/ui/widgets/scroller.py index c76f30d19..f19a6fbfd 100644 --- a/system/ui/widgets/scroller.py +++ b/system/ui/widgets/scroller.py @@ -76,3 +76,15 @@ class Scroller(Widget): item.render() rl.end_scissor_mode() + + def show_event(self): + super().show_event() + # Reset to top + self.scroll_panel.set_offset(0) + for item in self._items: + item.show_event() + + def hide_event(self): + super().hide_event() + for item in self._items: + item.hide_event() From b6dbb0fd8d79915f6f67d70500d5c971f72f33df Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 11 Oct 2025 03:29:24 -0700 Subject: [PATCH 143/910] raylib: font sizes from QT should match (#36306) * pt 2 * fix line height * fixup html renderer * fix sidebar * fix label line height * firehose fixups * fix ssh value font styling * fixup inputbot * do experimental mode * pairing dialog numbers * fix radius for prime user * add emoji to firehose mode * full screen registration * fix registration btn size * fix update and alerts * debugging * Revert "debugging" This reverts commit 0095372e9479d8c727bcc8a78061f582d852133d. * firehose styling * fix offroad alerts missing bottom spacing expansion * huge oof * huge oof --- selfdrive/ui/layouts/home.py | 13 ++-- selfdrive/ui/layouts/main.py | 1 + selfdrive/ui/layouts/settings/firehose.py | 77 ++++++++--------------- selfdrive/ui/layouts/sidebar.py | 4 +- selfdrive/ui/widgets/exp_mode_button.py | 11 +--- selfdrive/ui/widgets/offroad_alerts.py | 20 +++--- selfdrive/ui/widgets/pairing_dialog.py | 4 +- selfdrive/ui/widgets/prime.py | 2 +- selfdrive/ui/widgets/setup.py | 19 +++--- selfdrive/ui/widgets/ssh_key.py | 10 +-- system/ui/lib/application.py | 15 +++++ system/ui/lib/scroll_panel.py | 4 ++ system/ui/lib/text_measure.py | 3 +- system/ui/widgets/html_render.py | 32 +++++----- system/ui/widgets/inputbox.py | 6 +- system/ui/widgets/label.py | 6 +- 16 files changed, 113 insertions(+), 114 deletions(-) diff --git a/selfdrive/ui/layouts/home.py b/selfdrive/ui/layouts/home.py index 9243d8ea1..70960ee22 100644 --- a/selfdrive/ui/layouts/home.py +++ b/selfdrive/ui/layouts/home.py @@ -68,6 +68,7 @@ class HomeLayout(Widget): def _setup_callbacks(self): self.update_alert.set_dismiss_callback(lambda: self._set_state(HomeLayoutState.HOME)) self.offroad_alert.set_dismiss_callback(lambda: self._set_state(HomeLayoutState.HOME)) + self._exp_mode_button.set_click_callback(lambda: self.settings_callback() if self.settings_callback else None) def set_settings_callback(self, callback: Callable): self.settings_callback = callback @@ -147,9 +148,9 @@ class HomeLayout(Widget): rl.draw_rectangle_rounded(self.update_notif_rect, 0.3, 10, highlight_color) text = "UPDATE" - text_width = measure_text_cached(font, text, HEAD_BUTTON_FONT_SIZE).x - text_x = self.update_notif_rect.x + (self.update_notif_rect.width - text_width) // 2 - text_y = self.update_notif_rect.y + (self.update_notif_rect.height - HEAD_BUTTON_FONT_SIZE) // 2 + text_size = measure_text_cached(font, text, HEAD_BUTTON_FONT_SIZE) + text_x = self.update_notif_rect.x + (self.update_notif_rect.width - text_size.x) // 2 + text_y = self.update_notif_rect.y + (self.update_notif_rect.height - text_size.y) // 2 rl.draw_text_ex(font, text, rl.Vector2(int(text_x), int(text_y)), HEAD_BUTTON_FONT_SIZE, 0, rl.WHITE) # Alert notification button @@ -161,9 +162,9 @@ class HomeLayout(Widget): rl.draw_rectangle_rounded(self.alert_notif_rect, 0.3, 10, highlight_color) alert_text = f"{self.alert_count} ALERT{'S' if self.alert_count > 1 else ''}" - text_width = measure_text_cached(font, alert_text, HEAD_BUTTON_FONT_SIZE).x - text_x = self.alert_notif_rect.x + (self.alert_notif_rect.width - text_width) // 2 - text_y = self.alert_notif_rect.y + (self.alert_notif_rect.height - HEAD_BUTTON_FONT_SIZE) // 2 + text_size = measure_text_cached(font, alert_text, HEAD_BUTTON_FONT_SIZE) + text_x = self.alert_notif_rect.x + (self.alert_notif_rect.width - text_size.x) // 2 + text_y = self.alert_notif_rect.y + (self.alert_notif_rect.height - text_size.y) // 2 rl.draw_text_ex(font, alert_text, rl.Vector2(int(text_x), int(text_y)), HEAD_BUTTON_FONT_SIZE, 0, rl.WHITE) # Version text (right aligned) diff --git a/selfdrive/ui/layouts/main.py b/selfdrive/ui/layouts/main.py index a2401ef8b..702854f98 100644 --- a/selfdrive/ui/layouts/main.py +++ b/selfdrive/ui/layouts/main.py @@ -50,6 +50,7 @@ class MainLayout(Widget): on_flag=self._on_bookmark_clicked, open_settings=lambda: self.open_settings(PanelType.TOGGLES)) self._layouts[MainState.HOME]._setup_widget.set_open_settings_callback(lambda: self.open_settings(PanelType.FIREHOSE)) + self._layouts[MainState.HOME].set_settings_callback(lambda: self.open_settings(PanelType.TOGGLES)) self._layouts[MainState.SETTINGS].set_callbacks(on_close=self._set_mode_for_state) self._layouts[MainState.ONROAD].set_click_callback(self._on_onroad_clicked) device.add_interactive_timeout_callback(self._set_mode_for_state) diff --git a/selfdrive/ui/layouts/settings/firehose.py b/selfdrive/ui/layouts/settings/firehose.py index 353a9d976..e8eaaa44f 100644 --- a/selfdrive/ui/layouts/settings/firehose.py +++ b/selfdrive/ui/layouts/settings/firehose.py @@ -7,7 +7,8 @@ from openpilot.common.params import Params from openpilot.common.swaglog import cloudlog from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.system.athena.registration import UNREGISTERED_DONGLE_ID -from openpilot.system.ui.lib.application import gui_app, FontWeight +from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE +from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel from openpilot.system.ui.lib.wrap_text import wrap_text from openpilot.system.ui.widgets import Widget @@ -21,7 +22,7 @@ DESCRIPTION = ( ) INSTRUCTIONS = ( "For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.\n\n" - + "Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.\n\n" + + "Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.\n\n\n" + "Frequently Asked Questions\n\n" + "Does it matter how or where I drive? Nope, just drive as you normally would.\n\n" + "Do all of my segments get pulled in Firehose Mode? No, we selectively pull a subset of your segments.\n\n" @@ -43,6 +44,7 @@ class FirehoseLayout(Widget): self.params = Params() self.segment_count = self._get_segment_count() self.scroll_panel = GuiScrollPanel() + self._content_height = 0 self.running = True self.update_thread = threading.Thread(target=self._update_loop, daemon=True) @@ -69,88 +71,61 @@ class FirehoseLayout(Widget): def _render(self, rect: rl.Rectangle): # Calculate content dimensions - content_width = rect.width - 80 - content_height = self._calculate_content_height(int(content_width)) - content_rect = rl.Rectangle(rect.x, rect.y, rect.width, content_height) + content_rect = rl.Rectangle(rect.x, rect.y, rect.width, self._content_height) # Handle scrolling and render with clipping scroll_offset = self.scroll_panel.update(rect, content_rect) rl.begin_scissor_mode(int(rect.x), int(rect.y), int(rect.width), int(rect.height)) - self._render_content(rect, scroll_offset) + self._content_height = self._render_content(rect, scroll_offset) rl.end_scissor_mode() - def _calculate_content_height(self, content_width: int) -> int: - height = 80 # Top margin - - # Title - height += 100 + 40 - - # Description - desc_font = gui_app.font(FontWeight.NORMAL) - desc_lines = wrap_text(desc_font, DESCRIPTION, 45, content_width) - height += len(desc_lines) * 45 + 40 - - # Status section - height += 32 # Separator - status_text, _ = self._get_status() - status_lines = wrap_text(gui_app.font(FontWeight.BOLD), status_text, 60, content_width) - height += len(status_lines) * 60 + 20 - - # Contribution count (if available) - if self.segment_count > 0: - contrib_text = f"{self.segment_count} segment(s) of your driving is in the training dataset so far." - contrib_lines = wrap_text(gui_app.font(FontWeight.BOLD), contrib_text, 52, content_width) - height += len(contrib_lines) * 52 + 20 - - # Instructions section - height += 32 # Separator - inst_lines = wrap_text(gui_app.font(FontWeight.NORMAL), INSTRUCTIONS, 40, content_width) - height += len(inst_lines) * 40 + 40 # Bottom margin - - return height - - def _render_content(self, rect: rl.Rectangle, scroll_offset: float): + def _render_content(self, rect: rl.Rectangle, scroll_offset: float) -> int: x = int(rect.x + 40) y = int(rect.y + 40 + scroll_offset) w = int(rect.width - 80) - # Title + # Title (centered) title_font = gui_app.font(FontWeight.MEDIUM) - rl.draw_text_ex(title_font, TITLE, rl.Vector2(x, y), 100, 0, rl.WHITE) - y += 140 + text_width = measure_text_cached(title_font, TITLE, 100).x + title_x = rect.x + (rect.width - text_width) / 2 + rl.draw_text_ex(title_font, TITLE, rl.Vector2(title_x, y), 100, 0, rl.WHITE) + y += 200 # Description y = self._draw_wrapped_text(x, y, w, DESCRIPTION, gui_app.font(FontWeight.NORMAL), 45, rl.WHITE) - y += 40 + y += 40 + 20 # Separator rl.draw_rectangle(x, y, w, 2, self.GRAY) - y += 30 + y += 30 + 20 # Status status_text, status_color = self._get_status() y = self._draw_wrapped_text(x, y, w, status_text, gui_app.font(FontWeight.BOLD), 60, status_color) - y += 20 + y += 20 + 20 # Contribution count (if available) if self.segment_count > 0: contrib_text = f"{self.segment_count} segment(s) of your driving is in the training dataset so far." y = self._draw_wrapped_text(x, y, w, contrib_text, gui_app.font(FontWeight.BOLD), 52, rl.WHITE) - y += 20 + y += 20 + 20 # Separator rl.draw_rectangle(x, y, w, 2, self.GRAY) - y += 30 + y += 30 + 20 # Instructions - self._draw_wrapped_text(x, y, w, INSTRUCTIONS, gui_app.font(FontWeight.NORMAL), 40, self.LIGHT_GRAY) + y = self._draw_wrapped_text(x, y, w, INSTRUCTIONS, gui_app.font(FontWeight.NORMAL), 40, self.LIGHT_GRAY) - def _draw_wrapped_text(self, x, y, width, text, font, size, color): - wrapped = wrap_text(font, text, size, width) + # bottom margin + remove effect of scroll offset + return int(round(y - self.scroll_panel.offset + 40)) + + def _draw_wrapped_text(self, x, y, width, text, font, font_size, color): + wrapped = wrap_text(font, text, font_size, width) for line in wrapped: - rl.draw_text_ex(font, line, rl.Vector2(x, y), size, 0, color) - y += size - return y + rl.draw_text_ex(font, line, rl.Vector2(x, y), font_size, 0, color) + y += font_size * FONT_SCALE + return round(y) def _get_status(self) -> tuple[str, rl.Color]: network_type = ui_state.sm["deviceState"].networkType diff --git a/selfdrive/ui/layouts/sidebar.py b/selfdrive/ui/layouts/sidebar.py index 8fa38145d..c499d35d4 100644 --- a/selfdrive/ui/layouts/sidebar.py +++ b/selfdrive/ui/layouts/sidebar.py @@ -4,7 +4,7 @@ from dataclasses import dataclass from collections.abc import Callable from cereal import log from openpilot.selfdrive.ui.ui_state import ui_state -from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos +from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos, FONT_SCALE from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.widgets import Widget @@ -218,7 +218,7 @@ class Sidebar(Widget): # Draw label and value labels = [metric.label, metric.value] - text_y = metric_rect.y + (metric_rect.height / 2 - len(labels) * FONT_SIZE) + text_y = metric_rect.y + (metric_rect.height / 2 - len(labels) * FONT_SIZE * FONT_SCALE) for text in labels: text_size = measure_text_cached(self._font_bold, text, FONT_SIZE) text_y += text_size.y diff --git a/selfdrive/ui/widgets/exp_mode_button.py b/selfdrive/ui/widgets/exp_mode_button.py index 961876895..40a649899 100644 --- a/selfdrive/ui/widgets/exp_mode_button.py +++ b/selfdrive/ui/widgets/exp_mode_button.py @@ -1,6 +1,6 @@ import pyray as rl from openpilot.common.params import Params -from openpilot.system.ui.lib.application import gui_app, FontWeight +from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE from openpilot.system.ui.widgets import Widget @@ -9,7 +9,7 @@ class ExperimentalModeButton(Widget): super().__init__() self.img_width = 80 - self.horizontal_padding = 50 + self.horizontal_padding = 25 self.button_height = 125 self.params = Params() @@ -31,11 +31,6 @@ class ExperimentalModeButton(Widget): rl.draw_rectangle_gradient_h(int(rect.x), int(rect.y), int(rect.width), int(rect.height), start_color, end_color) - def _handle_mouse_release(self, mouse_pos): - self.experimental_mode = not self.experimental_mode - # TODO: Opening settings for ExperimentalMode - self.params.put_bool("ExperimentalMode", self.experimental_mode) - def _render(self, rect): rl.draw_rectangle_rounded(rect, 0.08, 20, rl.WHITE) @@ -51,7 +46,7 @@ class ExperimentalModeButton(Widget): # Draw text label (left aligned) text = "EXPERIMENTAL MODE ON" if self.experimental_mode else "CHILL MODE ON" text_x = rect.x + self.horizontal_padding - text_y = rect.y + rect.height / 2 - 45 // 2 # Center vertically + text_y = rect.y + rect.height / 2 - 45 * FONT_SCALE // 2 # Center vertically rl.draw_text_ex(gui_app.font(FontWeight.NORMAL), text, rl.Vector2(int(text_x), int(text_y)), 45, 0, rl.BLACK) diff --git a/selfdrive/ui/widgets/offroad_alerts.py b/selfdrive/ui/widgets/offroad_alerts.py index 49edafa0f..0b3ca8176 100644 --- a/selfdrive/ui/widgets/offroad_alerts.py +++ b/selfdrive/ui/widgets/offroad_alerts.py @@ -5,7 +5,7 @@ from collections.abc import Callable from dataclasses import dataclass from openpilot.common.params import Params from openpilot.system.hardware import HARDWARE -from openpilot.system.ui.lib.application import gui_app, FontWeight +from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.lib.wrap_text import wrap_text @@ -65,8 +65,8 @@ class ActionButton(Widget): def set_text(self, text: str): self._text = text - self._text_width = measure_text_cached(gui_app.font(FontWeight.MEDIUM), self._text, AlertConstants.FONT_SIZE).x - self._rect.width = max(self._text_width + 60 * 2, self._min_width) + self._text_size = measure_text_cached(gui_app.font(FontWeight.MEDIUM), self._text, AlertConstants.FONT_SIZE) + self._rect.width = max(self._text_size.x + 60 * 2, self._min_width) self._rect.height = AlertConstants.BUTTON_HEIGHT def _render(self, _): @@ -79,8 +79,8 @@ class ActionButton(Widget): # center text color = rl.WHITE if self._style == ButtonStyle.DARK else rl.BLACK - text_x = int(self._rect.x + (self._rect.width - self._text_width) // 2) - text_y = int(self._rect.y + (self._rect.height - AlertConstants.FONT_SIZE) // 2) + text_x = int(self._rect.x + (self._rect.width - self._text_size.x) // 2) + text_y = int(self._rect.y + (self._rect.height - self._text_size.y) // 2) rl.draw_text_ex(self._font, self._text, rl.Vector2(text_x, text_y), AlertConstants.FONT_SIZE, 0, color) @@ -250,9 +250,9 @@ class OffroadAlert(AbstractAlert): text_width = int(self.content_rect.width - (AlertConstants.ALERT_INSET * 2)) wrapped_lines = wrap_text(font, alert_data.text, AlertConstants.FONT_SIZE, text_width) line_count = len(wrapped_lines) - text_height = line_count * (AlertConstants.FONT_SIZE + 5) + text_height = line_count * (AlertConstants.FONT_SIZE * FONT_SCALE) alert_item_height = max(text_height + (AlertConstants.ALERT_INSET * 2), AlertConstants.ALERT_HEIGHT) - total_height += alert_item_height + AlertConstants.ALERT_SPACING + total_height += round(alert_item_height + AlertConstants.ALERT_SPACING) if total_height > 20: total_height = total_height - AlertConstants.ALERT_SPACING + 20 @@ -278,7 +278,7 @@ class OffroadAlert(AbstractAlert): text_width = int(content_rect.width - (AlertConstants.ALERT_INSET * 2)) wrapped_lines = wrap_text(font, alert_data.text, AlertConstants.FONT_SIZE, text_width) line_count = len(wrapped_lines) - text_height = line_count * (AlertConstants.FONT_SIZE + 5) + text_height = line_count * (AlertConstants.FONT_SIZE * FONT_SCALE) alert_item_height = max(text_height + (AlertConstants.ALERT_INSET * 2), AlertConstants.ALERT_HEIGHT) alert_rect = rl.Rectangle( @@ -298,13 +298,13 @@ class OffroadAlert(AbstractAlert): rl.draw_text_ex( font, line, - rl.Vector2(text_x, text_y + i * (AlertConstants.FONT_SIZE + 5)), + rl.Vector2(text_x, text_y + i * AlertConstants.FONT_SIZE * FONT_SCALE), AlertConstants.FONT_SIZE, 0, AlertColors.TEXT, ) - y_offset += alert_item_height + AlertConstants.ALERT_SPACING + y_offset += round(alert_item_height + AlertConstants.ALERT_SPACING) class UpdateAlert(AbstractAlert): diff --git a/selfdrive/ui/widgets/pairing_dialog.py b/selfdrive/ui/widgets/pairing_dialog.py index 64e1b701f..252a7dd94 100644 --- a/selfdrive/ui/widgets/pairing_dialog.py +++ b/selfdrive/ui/widgets/pairing_dialog.py @@ -145,8 +145,8 @@ class PairingDialog(Widget): # Circle and number rl.draw_circle(int(circle_x), int(circle_y), circle_radius, rl.Color(70, 70, 70, 255)) number = str(i + 1) - number_width = measure_text_cached(font, number, 30).x - rl.draw_text_ex(font, number, (int(circle_x - number_width // 2), int(circle_y - 15)), 30, 0, rl.WHITE) + number_size = measure_text_cached(font, number, 30) + rl.draw_text_ex(font, number, (int(circle_x - number_size.x // 2), int(circle_y - number_size.y // 2)), 30, 0, rl.WHITE) # Text rl.draw_text_ex(font, "\n".join(wrapped), rl.Vector2(text_x, y), 47, 0.0, rl.BLACK) diff --git a/selfdrive/ui/widgets/prime.py b/selfdrive/ui/widgets/prime.py index 6b601f6df..1e31ac8a1 100644 --- a/selfdrive/ui/widgets/prime.py +++ b/selfdrive/ui/widgets/prime.py @@ -52,7 +52,7 @@ class PrimeWidget(Widget): def _render_for_prime_user(self, rect: rl.Rectangle): """Renders the prime user widget with subscription status.""" - rl.draw_rectangle_rounded(rl.Rectangle(rect.x, rect.y, rect.width, 230), 0.02, 10, self.PRIME_BG_COLOR) + rl.draw_rectangle_rounded(rl.Rectangle(rect.x, rect.y, rect.width, 230), 0.05, 10, self.PRIME_BG_COLOR) x = rect.x + 56 y = rect.y + 40 diff --git a/selfdrive/ui/widgets/setup.py b/selfdrive/ui/widgets/setup.py index e4e44b7d0..dfaa3e400 100644 --- a/selfdrive/ui/widgets/setup.py +++ b/selfdrive/ui/widgets/setup.py @@ -1,10 +1,11 @@ import pyray as rl from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.selfdrive.ui.widgets.pairing_dialog import PairingDialog -from openpilot.system.ui.lib.application import gui_app, FontWeight +from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE from openpilot.system.ui.lib.wrap_text import wrap_text from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.button import Button, ButtonStyle +from openpilot.system.ui.widgets.label import Label class SetupWidget(Widget): @@ -15,6 +16,7 @@ class SetupWidget(Widget): self._pair_device_btn = Button("Pair device", self._show_pairing, button_style=ButtonStyle.PRIMARY) self._open_settings_btn = Button("Open", lambda: self._open_settings_callback() if self._open_settings_callback else None, button_style=ButtonStyle.PRIMARY) + self._firehose_label = Label("🔥Firehose Mode 🔥", font_weight=FontWeight.MEDIUM, font_size=64) def set_open_settings_callback(self, callback): self._open_settings_callback = callback @@ -28,7 +30,7 @@ class SetupWidget(Widget): def _render_registration(self, rect: rl.Rectangle): """Render registration prompt.""" - rl.draw_rectangle_rounded(rl.Rectangle(rect.x, rect.y, rect.width, 630), 0.02, 20, rl.Color(51, 51, 51, 255)) + rl.draw_rectangle_rounded(rl.Rectangle(rect.x, rect.y, rect.width, rect.height), 0.02, 20, rl.Color(51, 51, 51, 255)) x = rect.x + 64 y = rect.y + 48 @@ -45,15 +47,15 @@ class SetupWidget(Widget): wrapped = wrap_text(light_font, desc, 50, int(w)) for line in wrapped: rl.draw_text_ex(light_font, line, rl.Vector2(x, y), 50, 0, rl.WHITE) - y += 50 + y += 50 * FONT_SCALE - button_rect = rl.Rectangle(x, y + 50, w, 128) + button_rect = rl.Rectangle(x, y + 30, w, 200) self._pair_device_btn.render(button_rect) def _render_firehose_prompt(self, rect: rl.Rectangle): """Render firehose prompt widget.""" - rl.draw_rectangle_rounded(rl.Rectangle(rect.x, rect.y, rect.width, 450), 0.02, 20, rl.Color(51, 51, 51, 255)) + rl.draw_rectangle_rounded(rl.Rectangle(rect.x, rect.y, rect.width, 500), 0.02, 20, rl.Color(51, 51, 51, 255)) # Content margins (56, 40, 56, 40) x = rect.x + 56 @@ -62,9 +64,8 @@ class SetupWidget(Widget): spacing = 42 # Title with fire emojis - title_font = gui_app.font(FontWeight.MEDIUM) - title_text = "Firehose Mode" - rl.draw_text_ex(title_font, title_text, rl.Vector2(x, y), 64, 0, rl.WHITE) + # TODO: fix Label centering with emojis + self._firehose_label.render(rl.Rectangle(x - 40, y, w, 64)) y += 64 + spacing # Description @@ -74,7 +75,7 @@ class SetupWidget(Widget): for line in wrapped_desc: rl.draw_text_ex(desc_font, line, rl.Vector2(x, y), 40, 0, rl.WHITE) - y += 40 + y += 40 * FONT_SCALE y += spacing diff --git a/selfdrive/ui/widgets/ssh_key.py b/selfdrive/ui/widgets/ssh_key.py index 370141bd6..dc3c5a4a7 100644 --- a/selfdrive/ui/widgets/ssh_key.py +++ b/selfdrive/ui/widgets/ssh_key.py @@ -21,6 +21,8 @@ from openpilot.system.ui.widgets.list_view import ( BUTTON_WIDTH, ) +VALUE_FONT_SIZE = 48 + class SshKeyActionState(Enum): LOADING = "LOADING" @@ -38,7 +40,7 @@ class SshKeyAction(ItemAction): self._keyboard = Keyboard(min_text_size=1) self._params = Params() self._error_message: str = "" - self._text_font = gui_app.font(FontWeight.MEDIUM) + self._text_font = gui_app.font(FontWeight.NORMAL) self._button = Button("", click_callback=self._handle_button_click, button_style=ButtonStyle.LIST_ACTION, border_radius=BUTTON_BORDER_RADIUS, font_size=BUTTON_FONT_SIZE) @@ -62,14 +64,14 @@ class SshKeyAction(ItemAction): # Draw username if exists if self._username: - text_size = measure_text_cached(self._text_font, self._username, BUTTON_FONT_SIZE) + text_size = measure_text_cached(self._text_font, self._username, VALUE_FONT_SIZE) rl.draw_text_ex( self._text_font, self._username, (rect.x + rect.width - BUTTON_WIDTH - text_size.x - 30, rect.y + (rect.height - text_size.y) / 2), - BUTTON_FONT_SIZE, + VALUE_FONT_SIZE, 1.0, - rl.WHITE, + rl.Color(170, 170, 170, 255), ) # Draw button diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 473eaa5fa..755a335e4 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -30,6 +30,10 @@ SCALE = float(os.getenv("SCALE", "1.0")) DEFAULT_TEXT_SIZE = 60 DEFAULT_TEXT_COLOR = rl.WHITE +# Qt draws fonts accounting for ascent/descent differently, so compensate to match old styles +# The real scales for the fonts below range from 1.212 to 1.266 +FONT_SCALE = 1.242 + ASSETS_DIR = files("openpilot.selfdrive").joinpath("assets") FONT_DIR = ASSETS_DIR.joinpath("fonts") @@ -173,6 +177,7 @@ class GuiApplication: self._target_fps = fps self._set_styles() self._load_fonts() + self._patch_text_functions() if not PC: self._mouse.start() @@ -356,6 +361,16 @@ 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 _patch_text_functions(self): + # Wrap pyray text APIs to apply a global text size scale so our px sizes match Qt + if not hasattr(rl, "_orig_draw_text_ex"): + rl._orig_draw_text_ex = rl.draw_text_ex + + def _draw_text_ex_scaled(font, text, position, font_size, spacing, tint): + return rl._orig_draw_text_ex(font, text, position, font_size * FONT_SCALE, spacing, tint) + + rl.draw_text_ex = _draw_text_ex_scaled + def _set_log_callback(self): ffi_libc = cffi.FFI() ffi_libc.cdef(""" diff --git a/system/ui/lib/scroll_panel.py b/system/ui/lib/scroll_panel.py index f0031138f..a5b9fc70d 100644 --- a/system/ui/lib/scroll_panel.py +++ b/system/ui/lib/scroll_panel.py @@ -128,3 +128,7 @@ class GuiScrollPanel: self._offset_filter_y.x = position self._velocity_filter_y.x = 0.0 self._scroll_state = ScrollState.IDLE + + @property + def offset(self) -> float: + return float(self._offset_filter_y.x) diff --git a/system/ui/lib/text_measure.py b/system/ui/lib/text_measure.py index c172f9425..fcb7b25cc 100644 --- a/system/ui/lib/text_measure.py +++ b/system/ui/lib/text_measure.py @@ -1,4 +1,5 @@ import pyray as rl +from openpilot.system.ui.lib.application import FONT_SCALE _cache: dict[int, rl.Vector2] = {} @@ -9,6 +10,6 @@ def measure_text_cached(font: rl.Font, text: str, font_size: int, spacing: int = if key in _cache: return _cache[key] - result = rl.measure_text_ex(font, text, font_size, spacing) # noqa: TID251 + result = rl.measure_text_ex(font, text, font_size * FONT_SCALE, spacing) # noqa: TID251 _cache[key] = result return result diff --git a/system/ui/widgets/html_render.py b/system/ui/widgets/html_render.py index 938867f74..7ca62409d 100644 --- a/system/ui/widgets/html_render.py +++ b/system/ui/widgets/html_render.py @@ -3,7 +3,7 @@ import pyray as rl from dataclasses import dataclass from enum import Enum from typing import Any -from openpilot.system.ui.lib.application import gui_app, FontWeight +from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel from openpilot.system.ui.lib.wrap_text import wrap_text from openpilot.system.ui.widgets import Widget @@ -45,7 +45,7 @@ class HtmlElement: font_weight: FontWeight margin_top: int margin_bottom: int - line_height: float = 1.2 + line_height: float = 0.9 # matches Qt visually, unsure why not default 1.2 indent_level: int = 0 @@ -61,16 +61,19 @@ class HtmlRenderer(Widget): if text_size is None: text_size = {} + # Base paragraph size (Qt stylesheet default is 48px in offroad alerts) + base_p_size = int(text_size.get(ElementType.P, 48)) + # Untagged text defaults to

self.styles: dict[ElementType, dict[str, Any]] = { - ElementType.H1: {"size": 68, "weight": FontWeight.BOLD, "margin_top": 20, "margin_bottom": 16}, - ElementType.H2: {"size": 60, "weight": FontWeight.BOLD, "margin_top": 24, "margin_bottom": 12}, - ElementType.H3: {"size": 52, "weight": FontWeight.BOLD, "margin_top": 20, "margin_bottom": 10}, - ElementType.H4: {"size": 48, "weight": FontWeight.BOLD, "margin_top": 16, "margin_bottom": 8}, - ElementType.H5: {"size": 44, "weight": FontWeight.BOLD, "margin_top": 12, "margin_bottom": 6}, - ElementType.H6: {"size": 40, "weight": FontWeight.BOLD, "margin_top": 10, "margin_bottom": 4}, - ElementType.P: {"size": text_size.get(ElementType.P, 38), "weight": FontWeight.NORMAL, "margin_top": 8, "margin_bottom": 12}, - ElementType.LI: {"size": 38, "weight": FontWeight.NORMAL, "color": rl.Color(40, 40, 40, 255), "margin_top": 6, "margin_bottom": 6}, + ElementType.H1: {"size": round(base_p_size * 2), "weight": FontWeight.BOLD, "margin_top": 20, "margin_bottom": 16}, + ElementType.H2: {"size": round(base_p_size * 1.50), "weight": FontWeight.BOLD, "margin_top": 24, "margin_bottom": 12}, + ElementType.H3: {"size": round(base_p_size * 1.17), "weight": FontWeight.BOLD, "margin_top": 20, "margin_bottom": 10}, + ElementType.H4: {"size": round(base_p_size * 1.00), "weight": FontWeight.BOLD, "margin_top": 16, "margin_bottom": 8}, + ElementType.H5: {"size": round(base_p_size * 0.83), "weight": FontWeight.BOLD, "margin_top": 12, "margin_bottom": 6}, + ElementType.H6: {"size": round(base_p_size * 0.67), "weight": FontWeight.BOLD, "margin_top": 10, "margin_bottom": 4}, + ElementType.P: {"size": base_p_size, "weight": FontWeight.NORMAL, "margin_top": 8, "margin_bottom": 12}, + ElementType.LI: {"size": base_p_size, "weight": FontWeight.NORMAL, "color": rl.Color(40, 40, 40, 255), "margin_top": 6, "margin_bottom": 6}, ElementType.BR: {"size": 0, "weight": FontWeight.NORMAL, "margin_top": 0, "margin_bottom": 12}, } @@ -179,8 +182,9 @@ class HtmlRenderer(Widget): wrapped_lines = wrap_text(font, element.content, element.font_size, int(content_width)) for line in wrapped_lines: - if current_y < rect.y - element.font_size: - current_y += element.font_size * element.line_height + # Use FONT_SCALE from wrapped raylib text functions to match what is drawn + if current_y < rect.y - element.font_size * FONT_SCALE: + current_y += element.font_size * FONT_SCALE * element.line_height continue if current_y > rect.y + rect.height: @@ -189,7 +193,7 @@ class HtmlRenderer(Widget): text_x = rect.x + (max(element.indent_level - 1, 0) * LIST_INDENT_PX) rl.draw_text_ex(font, line, rl.Vector2(text_x + padding, current_y), element.font_size, 0, self._text_color) - current_y += element.font_size * element.line_height + current_y += element.font_size * FONT_SCALE * element.line_height # Apply bottom margin current_y += element.margin_bottom @@ -213,7 +217,7 @@ class HtmlRenderer(Widget): wrapped_lines = wrap_text(font, element.content, element.font_size, int(usable_width)) for _ in wrapped_lines: - total_height += element.font_size * element.line_height + total_height += element.font_size * FONT_SCALE * element.line_height total_height += element.margin_bottom diff --git a/system/ui/widgets/inputbox.py b/system/ui/widgets/inputbox.py index 239d63037..f53e3f0eb 100644 --- a/system/ui/widgets/inputbox.py +++ b/system/ui/widgets/inputbox.py @@ -1,6 +1,6 @@ import pyray as rl import time -from openpilot.system.ui.lib.application import gui_app, MousePos +from openpilot.system.ui.lib.application import gui_app, MousePos, FONT_SCALE from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.widgets import Widget @@ -130,7 +130,7 @@ class InputBox(Widget): 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)), + rl.Vector2(int(rect.x + padding - self._text_offset), int(rect.y + rect.height / 2 - font_size * FONT_SCALE / 2)), font_size, 0, text_color, @@ -145,7 +145,7 @@ class InputBox(Widget): # Apply text offset to cursor position cursor_x -= self._text_offset - cursor_height = font_size + 4 + cursor_height = font_size * FONT_SCALE + 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) diff --git a/system/ui/widgets/label.py b/system/ui/widgets/label.py index 2da9a9f8d..33c4ef29d 100644 --- a/system/ui/widgets/label.py +++ b/system/ui/widgets/label.py @@ -3,7 +3,7 @@ from itertools import zip_longest import pyray as rl -from openpilot.system.ui.lib.application import gui_app, FontWeight, DEFAULT_TEXT_SIZE, DEFAULT_TEXT_COLOR +from openpilot.system.ui.lib.application import gui_app, FontWeight, DEFAULT_TEXT_SIZE, DEFAULT_TEXT_COLOR, FONT_SCALE from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.lib.utils import GuiStyleContext from openpilot.system.ui.lib.emoji import find_emoji, emoji_tex @@ -171,7 +171,7 @@ class Label(Widget): tex = emoji_tex(emoji) rl.draw_texture_ex(tex, line_pos, 0.0, self._font_size / tex.height, self._text_color) - line_pos.x += self._font_size + line_pos.x += self._font_size * FONT_SCALE prev_index = end rl.draw_text_ex(self._font, text[prev_index:], line_pos, self._font_size, 0, self._text_color) - text_pos.y += text_size.y or self._font_size + text_pos.y += text_size.y or self._font_size * FONT_SCALE From cc816043c10754e4a706031b9ef181dade290c26 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 11 Oct 2025 03:36:47 -0700 Subject: [PATCH 144/910] raylib: add non-inline b tag (#36305) * debug * here too * clean up --- selfdrive/ui/layouts/settings/developer.py | 2 +- selfdrive/ui/layouts/settings/toggles.py | 4 ++-- system/ui/widgets/confirm_dialog.py | 2 +- system/ui/widgets/html_render.py | 13 +++++++++++-- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/selfdrive/ui/layouts/settings/developer.py b/selfdrive/ui/layouts/settings/developer.py index bc8fc953c..ec4d1fe98 100644 --- a/selfdrive/ui/layouts/settings/developer.py +++ b/selfdrive/ui/layouts/settings/developer.py @@ -154,7 +154,7 @@ class DeveloperLayout(Widget): self._alpha_long_toggle.action_item.set_state(False) # show confirmation dialog - content = (f"

{self._alpha_long_toggle.title}


" + + content = (f"

{self._alpha_long_toggle.title}


" + f"

{self._alpha_long_toggle.description}

") dlg = ConfirmDialog(content, "Enable", rich=True) diff --git a/selfdrive/ui/layouts/settings/toggles.py b/selfdrive/ui/layouts/settings/toggles.py index c75cc7574..01195142e 100644 --- a/selfdrive/ui/layouts/settings/toggles.py +++ b/selfdrive/ui/layouts/settings/toggles.py @@ -151,7 +151,7 @@ class TogglesLayout(Widget): ui_state.update_params() e2e_description = ( - "openpilot defaults to driving in chill mode. Experimental mode enables alpha-level features that aren't ready for chill mode. " + + "openpilot defaults to driving in chill mode. Experimental mode enables alpha-level features that aren't ready for chill mode. " + "Experimental features are listed below:
" + "

End-to-End Longitudinal Control


" + "Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. " + @@ -219,7 +219,7 @@ class TogglesLayout(Widget): self._update_experimental_mode_icon() # show confirmation dialog - content = (f"

{self._toggles['ExperimentalMode'].title}


" + + content = (f"

{self._toggles['ExperimentalMode'].title}


" + f"

{self._toggles['ExperimentalMode'].description}

") dlg = ConfirmDialog(content, "Enable", rich=True) gui_app.set_modal_overlay(dlg, callback=confirm_callback) diff --git a/system/ui/widgets/confirm_dialog.py b/system/ui/widgets/confirm_dialog.py index 6d3e143b5..274307395 100644 --- a/system/ui/widgets/confirm_dialog.py +++ b/system/ui/widgets/confirm_dialog.py @@ -19,7 +19,7 @@ class ConfirmDialog(Widget): def __init__(self, text: str, confirm_text: str, cancel_text: str = "Cancel", rich: bool = False): super().__init__() self._label = Label(text, 70, FontWeight.BOLD, text_color=rl.Color(201, 201, 201, 255)) - self._html_renderer = HtmlRenderer(text=text, text_size={ElementType.P: 50}) + self._html_renderer = HtmlRenderer(text=text, text_size={ElementType.P: 50}, center_text=True) self._cancel_button = Button(cancel_text, self._cancel_button_callback) self._confirm_button = Button(confirm_text, self._confirm_button_callback, button_style=ButtonStyle.PRIMARY) self._rich = rich diff --git a/system/ui/widgets/html_render.py b/system/ui/widgets/html_render.py index 7ca62409d..f90f78fe1 100644 --- a/system/ui/widgets/html_render.py +++ b/system/ui/widgets/html_render.py @@ -8,6 +8,7 @@ from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel from openpilot.system.ui.lib.wrap_text import wrap_text from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.button import Button, ButtonStyle +from openpilot.system.ui.lib.text_measure import measure_text_cached LIST_INDENT_PX = 40 @@ -20,6 +21,7 @@ class ElementType(Enum): H5 = "h5" H6 = "h6" P = "p" + B = "b" UL = "ul" LI = "li" BR = "br" @@ -51,9 +53,10 @@ class HtmlElement: class HtmlRenderer(Widget): def __init__(self, file_path: str | None = None, text: str | None = None, - text_size: dict | None = None, text_color: rl.Color = rl.WHITE): + text_size: dict | None = None, text_color: rl.Color = rl.WHITE, center_text: bool = False): super().__init__() self._text_color = text_color + self._center_text = center_text self._normal_font = gui_app.font(FontWeight.NORMAL) self._bold_font = gui_app.font(FontWeight.BOLD) self._indent_level = 0 @@ -73,6 +76,7 @@ class HtmlRenderer(Widget): ElementType.H5: {"size": round(base_p_size * 0.83), "weight": FontWeight.BOLD, "margin_top": 12, "margin_bottom": 6}, ElementType.H6: {"size": round(base_p_size * 0.67), "weight": FontWeight.BOLD, "margin_top": 10, "margin_bottom": 4}, ElementType.P: {"size": base_p_size, "weight": FontWeight.NORMAL, "margin_top": 8, "margin_bottom": 12}, + ElementType.B: {"size": base_p_size, "weight": FontWeight.BOLD, "margin_top": 8, "margin_bottom": 12}, ElementType.LI: {"size": base_p_size, "weight": FontWeight.NORMAL, "color": rl.Color(40, 40, 40, 255), "margin_top": 6, "margin_bottom": 6}, ElementType.BR: {"size": 0, "weight": FontWeight.NORMAL, "margin_top": 0, "margin_bottom": 12}, } @@ -190,7 +194,12 @@ class HtmlRenderer(Widget): if current_y > rect.y + rect.height: break - text_x = rect.x + (max(element.indent_level - 1, 0) * LIST_INDENT_PX) + if self._center_text: + text_width = measure_text_cached(font, line, element.font_size).x + text_x = rect.x + (rect.width - text_width) / 2 + else: # left align + text_x = rect.x + (max(element.indent_level - 1, 0) * LIST_INDENT_PX) + rl.draw_text_ex(font, line, rl.Vector2(text_x + padding, current_y), element.font_size, 0, self._text_color) current_y += element.font_size * FONT_SCALE * element.line_height From 2305fb59a2b7e39495927361e09d6fe8a45c14f3 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 11 Oct 2025 03:40:40 -0700 Subject: [PATCH 145/910] raylib: fix mic indicator (#36309) * fix mic * move out --- selfdrive/ui/layouts/sidebar.py | 2 +- selfdrive/ui/ui_state.py | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/selfdrive/ui/layouts/sidebar.py b/selfdrive/ui/layouts/sidebar.py index c499d35d4..4ba7f2aa1 100644 --- a/selfdrive/ui/layouts/sidebar.py +++ b/selfdrive/ui/layouts/sidebar.py @@ -107,7 +107,7 @@ class Sidebar(Widget): device_state = sm['deviceState'] - self._recording_audio = sm.alive['rawAudioData'] + self._recording_audio = ui_state.recording_audio self._update_network_status(device_state) self._update_temperature_status(device_state) self._update_connection_status(device_state) diff --git a/selfdrive/ui/ui_state.py b/selfdrive/ui/ui_state.py index f4b9e1a9b..3ac9dbea1 100644 --- a/selfdrive/ui/ui_state.py +++ b/selfdrive/ui/ui_state.py @@ -67,6 +67,7 @@ class UIState: self.is_metric: bool = self.params.get_bool("IsMetric") self.started: bool = False self.ignition: bool = False + self.recording_audio: bool = False self.panda_type: log.PandaState.PandaType = log.PandaState.PandaType.unknown self.personality: log.LongitudinalPersonality = log.LongitudinalPersonality.standard self.has_longitudinal_control: bool = False @@ -128,6 +129,11 @@ class UIState: # Update started state self.started = self.sm["deviceState"].started and self.ignition + # Update recording audio state + self.recording_audio = self.params.get_bool("RecordAudio") and self.started + + self.is_metric = self.params.get_bool("IsMetric") + def _update_status(self) -> None: if self.started and self.sm.updated["selfdriveState"]: ss = self.sm["selfdriveState"] @@ -157,8 +163,7 @@ class UIState: self._started_prev = self.started def update_params(self) -> None: - self.is_metric = self.params.get_bool("IsMetric") - + # For slower operations # Update longitudinal control state CP_bytes = self.params.get("CarParamsPersistent") if CP_bytes is not None: From 0e6f78a656f5292f7234608011099855c6e0964e Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Sat, 11 Oct 2025 04:23:16 -0700 Subject: [PATCH 146/910] raylib is now a core dependancy --- pyproject.toml | 2 +- uv.lock | 68 +++++++++++++++++++++++++------------------------- 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9a8de2b74..7248caa4f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,6 +72,7 @@ dependencies = [ "zstandard", # ui + "raylib < 5.5.0.3", # TODO: unpin when they fix https://github.com/electronstudio/raylib-python-cffi/issues/186 "qrcode", ] @@ -119,7 +120,6 @@ dev = [ "tabulate", "types-requests", "types-tabulate", - "raylib < 5.5.0.3", # TODO: unpin when they fix https://github.com/electronstudio/raylib-python-cffi/issues/186 ] tools = [ diff --git a/uv.lock b/uv.lock index 324622129..476945120 100644 --- a/uv.lock +++ b/uv.lock @@ -639,10 +639,10 @@ name = "gymnasium" version = "1.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cloudpickle" }, - { name = "farama-notifications" }, - { name = "numpy" }, - { name = "typing-extensions" }, + { 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'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/fd/17/c2a0e15c2cd5a8e788389b280996db927b923410de676ec5c7b2695e9261/gymnasium-1.2.0.tar.gz", hash = "sha256:344e87561012558f603880baf264ebc97f8a5c997a957b0c9f910281145534b0", size = 821142, upload-time = "2025-06-27T08:21:20.262Z" } wheels = [ @@ -931,22 +931,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" }, - { 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" }, + { 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'" }, ] 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:d0afaf3b005e35e14b929d5491d2d5b64562d0c1cd5093ba969fb63908670dd4" }, @@ -1281,6 +1281,7 @@ dependencies = [ { name = "pyserial" }, { name = "pyzmq" }, { name = "qrcode" }, + { name = "raylib" }, { name = "requests" }, { name = "scons" }, { name = "sentry-sdk" }, @@ -1313,7 +1314,6 @@ dev = [ { name = "pyprof2calltree" }, { name = "pytools", marker = "platform_machine != 'aarch64'" }, { name = "pywinctl" }, - { name = "raylib" }, { name = "tabulate" }, { name = "types-requests" }, { name = "types-tabulate" }, @@ -1401,7 +1401,7 @@ requires-dist = [ { name = "pywinctl", marker = "extra == 'dev'" }, { name = "pyzmq" }, { name = "qrcode" }, - { name = "raylib", marker = "extra == 'dev'", specifier = "<5.5.0.3" }, + { name = "raylib", specifier = "<5.5.0.3" }, { name = "requests" }, { name = "ruff", marker = "extra == 'testing'" }, { name = "scons" }, @@ -1454,8 +1454,8 @@ name = "panda3d-gltf" version = "0.13" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "panda3d" }, - { name = "panda3d-simplepbr" }, + { name = "panda3d", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "panda3d-simplepbr", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, ] 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 = [ @@ -1467,8 +1467,8 @@ name = "panda3d-simplepbr" version = "0.13.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "panda3d" }, - { name = "typing-extensions" }, + { name = "panda3d", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "typing-extensions", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, ] 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 = [ @@ -4205,9 +4205,9 @@ name = "pyopencl" version = "2025.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy" }, - { name = "platformdirs" }, - { name = "pytools" }, + { 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'" }, ] 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 = [ @@ -4429,9 +4429,9 @@ name = "pytools" version = "2024.1.10" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "platformdirs" }, - { name = "siphash24" }, - { name = "typing-extensions" }, + { 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'" }, ] 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 = [ @@ -4754,7 +4754,7 @@ name = "shapely" version = "2.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy" }, + { name = "numpy", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, ] 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 = [ @@ -4983,7 +4983,7 @@ name = "yapf" version = "0.43.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "platformdirs" }, + { name = "platformdirs", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/23/97/b6f296d1e9cc1ec25c7604178b48532fa5901f721bcf1b8d8148b13e5588/yapf-0.43.0.tar.gz", hash = "sha256:00d3aa24bfedff9420b2e0d5d9f5ab6d9d4268e72afbf59bb3fa542781d5218e", size = 254907, upload-time = "2024-11-14T00:11:41.584Z" } wheels = [ From a6e28ac2ee4480594544a4a456fdcbf23add719d Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 11 Oct 2025 04:27:01 -0700 Subject: [PATCH 147/910] raylib: disable mouse thread lag print (#36312) every other process disables this --- system/ui/lib/application.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 755a335e4..d3bc3d494 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -76,7 +76,7 @@ class MouseState: self._events: deque[MouseEvent] = deque(maxlen=MOUSE_THREAD_RATE) # bound event list self._prev_mouse_event: list[MouseEvent | None] = [None] * MAX_TOUCH_SLOTS - self._rk = Ratekeeper(MOUSE_THREAD_RATE) + self._rk = Ratekeeper(MOUSE_THREAD_RATE, print_delay_threshold=None) self._lock = threading.Lock() self._exit_event = threading.Event() self._thread = None From 348114e5bd0d4fd551854058a23d25ac40d28086 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 11 Oct 2025 04:27:19 -0700 Subject: [PATCH 148/910] raylib: remove cut stuff (#36310) remove cut stuff --- selfdrive/ui/layouts/settings/device.py | 3 ++- selfdrive/ui/layouts/settings/software.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/layouts/settings/device.py b/selfdrive/ui/layouts/settings/device.py index 286ba36f0..35021492e 100644 --- a/selfdrive/ui/layouts/settings/device.py +++ b/selfdrive/ui/layouts/settings/device.py @@ -60,7 +60,8 @@ class DeviceLayout(Widget): self._reset_calib_btn, button_item("Review Training Guide", "REVIEW", DESCRIPTIONS['review_guide'], self._on_review_training_guide), regulatory_btn := button_item("Regulatory", "VIEW", callback=self._on_regulatory), - button_item("Change Language", "CHANGE", callback=self._show_language_selection, enabled=ui_state.is_offroad), + # TODO: implement multilang + # button_item("Change Language", "CHANGE", callback=self._show_language_selection, enabled=ui_state.is_offroad), dual_button_item("Reboot", "Power Off", left_callback=self._reboot_prompt, right_callback=self._power_off_prompt), ] regulatory_btn.set_visible(TICI) diff --git a/selfdrive/ui/layouts/settings/software.py b/selfdrive/ui/layouts/settings/software.py index a337b9034..0c17a54fb 100644 --- a/selfdrive/ui/layouts/settings/software.py +++ b/selfdrive/ui/layouts/settings/software.py @@ -64,7 +64,8 @@ class SoftwareLayout(Widget): self._version_item, self._download_btn, self._install_btn, - button_item("Target Branch", "SELECT", callback=self._on_select_branch), + # TODO: implement branch switching + # button_item("Target Branch", "SELECT", callback=self._on_select_branch), button_item("Uninstall", "UNINSTALL", callback=self._on_uninstall), ] return items From c2af5a82ff9b7e741c53c2266a1d85716814c13b Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Sat, 11 Oct 2025 04:27:45 -0700 Subject: [PATCH 149/910] add earcut python package --- pyproject.toml | 1 + uv.lock | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 7248caa4f..78bf8c22e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -74,6 +74,7 @@ dependencies = [ # ui "raylib < 5.5.0.3", # TODO: unpin when they fix https://github.com/electronstudio/raylib-python-cffi/issues/186 "qrcode", + "mapbox-earcut", ] [project.optional-dependencies] diff --git a/uv.lock b/uv.lock index 476945120..c34a6d9b7 100644 --- a/uv.lock +++ b/uv.lock @@ -844,6 +844,29 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6c/77/d7f491cbc05303ac6801651aabeb262d43f319288c1ea96c66b1d2692ff3/lxml-6.0.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:27220da5be049e936c3aca06f174e8827ca6445a4353a1995584311487fc4e3e", size = 3518768, upload-time = "2025-09-22T04:04:57.097Z" }, ] +[[package]] +name = "mapbox-earcut" +version = "1.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8d/70/0a322197c1178f47941e5e6e13b0a4adeaaa7c465c18e3b4ead3eba49860/mapbox_earcut-1.0.3.tar.gz", hash = "sha256:b6bac5d519d9947a6321a699c15d58e0b5740da61b9210ed229e05ad207c1c04", size = 24029, upload-time = "2024-12-25T12:49:09.119Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/61/d7/b37a45c248100e7285a40de87a8b1808ca4ca10228e265f2d0c320702d96/mapbox_earcut-1.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bbf24029e7447eb0351000f4fd3185327a00dac5ed756b07330b0bdaed6932db", size = 71057, upload-time = "2024-12-25T12:48:09.131Z" }, + { url = "https://files.pythonhosted.org/packages/1b/df/2b63eb0d3a24e14f67adc816de18c2e09f3eb0997c512ace84dd59c3ed96/mapbox_earcut-1.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:998e2f1e3769538f7656a34296d08a37cb71ce57aa8cf4387572bc00029b52ce", size = 65300, upload-time = "2024-12-25T12:48:11.677Z" }, + { url = "https://files.pythonhosted.org/packages/87/37/9dd9575f5c00e35d480e7150e5bb315a35d9cf5642bfb75ca628a31e1341/mapbox_earcut-1.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df2382d84d6d168f73479673d297753e37440772f233cc03ebb54d150e37b174", size = 96965, upload-time = "2024-12-25T12:48:12.968Z" }, + { url = "https://files.pythonhosted.org/packages/3b/91/5708233941b5bf73149ba35f7aa32c6ee2cf4a33cd33069e7dba69d4129f/mapbox_earcut-1.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ccddb4bb04f11beab62943eb5a1bcd52c5a71d236bfce0ecc03e45e97fdb24b", size = 1070953, upload-time = "2024-12-25T12:48:15.495Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fe/b35b999ba786aa17ddc47bc04231de076665eb511e1cd58cf6fef3581172/mapbox_earcut-1.0.3-cp311-cp311-win32.whl", hash = "sha256:f19b2bcf6475bc591f48437d3214691a6730f39b1f6dfd7505b69c4345485b0c", size = 65245, upload-time = "2024-12-25T12:48:17.826Z" }, + { url = "https://files.pythonhosted.org/packages/11/81/18ac08b0bb0c22dd9028c7ecb31ae4086d31128b13fb3903e717331072ac/mapbox_earcut-1.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:811a64ad5e6ecf09b96af533e5c169299ba173e53eb4ff0209de1adcfae314be", size = 72356, upload-time = "2024-12-25T12:48:20.164Z" }, + { url = "https://files.pythonhosted.org/packages/96/7c/707a4ce96e078f7d382cc32b4a6c2326eca68d77ead5e990f5f940d16140/mapbox_earcut-1.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5be71b7ec2180a27ce1178d53933430a3292b6ac3f94f2144513ee51d9034007", size = 70333, upload-time = "2024-12-25T12:48:22.565Z" }, + { url = "https://files.pythonhosted.org/packages/fb/47/ba2a14732f6e197b0ed879a1992b4d85054294b23627ad681b4fb1251d16/mapbox_earcut-1.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:eb874f7562a49ae0fb7bd47bcc9b4854cc53e3e4f7f26674f02f3cadb006ce16", size = 64697, upload-time = "2024-12-25T12:48:25.025Z" }, + { url = "https://files.pythonhosted.org/packages/e7/68/59a514811da76c3c801207bd6d7094ea5ba75648c2e7f15d4cb98b08216f/mapbox_earcut-1.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73b9f06f2f8a795d835342aa80e021cfceda78fdca7bc07dc1a0b4aca90239f3", size = 96182, upload-time = "2024-12-25T12:48:26.316Z" }, + { url = "https://files.pythonhosted.org/packages/3f/79/97bf509ade0f9aeb5b5f94b1aff86393c2f584379a80e392fdfcbea434ae/mapbox_earcut-1.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fdc55574ef7b613004874a459d2d59c07e1ef45cebb83f86c4958f7d3e2d6069", size = 1070584, upload-time = "2024-12-25T12:48:29.065Z" }, + { url = "https://files.pythonhosted.org/packages/de/7a/5a6e205bab9ff49d1dae392f6179a444f820880d8985f26080816fa6c7ba/mapbox_earcut-1.0.3-cp312-cp312-win32.whl", hash = "sha256:790f52c67a0bd81032eaf61ebc181b1825b8b6daf01cb69e9eaa38521dd07aeb", size = 65375, upload-time = "2024-12-25T12:48:30.618Z" }, + { url = "https://files.pythonhosted.org/packages/7a/59/674a67f92772563d5a943ce2c4ed834ed341e3a0fd77b8eb4b79057f5193/mapbox_earcut-1.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:cc1bbf35be0d9853dd448374330684ddbd0112497dee7d21b7417b0ab6236ac7", size = 72575, upload-time = "2024-12-25T12:48:33.544Z" }, +] + [[package]] name = "markdown" version = "3.9" @@ -1270,6 +1293,7 @@ dependencies = [ { name = "json-rpc" }, { name = "kaitaistruct" }, { name = "libusb1" }, + { name = "mapbox-earcut" }, { name = "numpy" }, { name = "onnx" }, { name = "psutil" }, @@ -1367,6 +1391,7 @@ requires-dist = [ { name = "json-rpc" }, { name = "kaitaistruct" }, { name = "libusb1" }, + { name = "mapbox-earcut" }, { name = "matplotlib", marker = "extra == 'dev'" }, { name = "metadrive-simulator", marker = "platform_machine != 'aarch64' and extra == 'tools'", url = "https://github.com/commaai/metadrive/releases/download/MetaDrive-minimal-0.4.2.4/metadrive_simulator-0.4.2.4-py3-none-any.whl" }, { name = "mkdocs", marker = "extra == 'docs'" }, From aa7f6973c0a7427317e8916f11bfb01390379bbf Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 11 Oct 2025 06:01:17 -0700 Subject: [PATCH 150/910] raylib: match Qt onroad button disabling (#36316) * fixxx * clean up * disable onroad: adb, joystick, alpha long --- selfdrive/ui/layouts/settings/developer.py | 3 +++ selfdrive/ui/layouts/settings/device.py | 13 ++++++++++--- system/ui/widgets/list_view.py | 7 +++++++ 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/selfdrive/ui/layouts/settings/developer.py b/selfdrive/ui/layouts/settings/developer.py index ec4d1fe98..fac00b60a 100644 --- a/selfdrive/ui/layouts/settings/developer.py +++ b/selfdrive/ui/layouts/settings/developer.py @@ -38,6 +38,7 @@ class DeveloperLayout(Widget): description=DESCRIPTIONS["enable_adb"], initial_state=self._params.get_bool("AdbEnabled"), callback=self._on_enable_adb, + enabled=ui_state.is_offroad, ) # SSH enable toggle + SSH key management @@ -54,6 +55,7 @@ class DeveloperLayout(Widget): description="", initial_state=self._params.get_bool("JoystickDebugMode"), callback=self._on_joystick_debug_mode, + enabled=ui_state.is_offroad, ) self._long_maneuver_toggle = toggle_item( @@ -68,6 +70,7 @@ class DeveloperLayout(Widget): description=DESCRIPTIONS["alpha_longitudinal"], initial_state=self._params.get_bool("AlphaLongitudinalEnabled"), callback=self._on_alpha_long_enabled, + enabled=ui_state.is_offroad, ) items = [ diff --git a/selfdrive/ui/layouts/settings/device.py b/selfdrive/ui/layouts/settings/device.py index 35021492e..66341db37 100644 --- a/selfdrive/ui/layouts/settings/device.py +++ b/selfdrive/ui/layouts/settings/device.py @@ -42,6 +42,8 @@ class DeviceLayout(Widget): items = self._initialize_items() self._scroller = Scroller(items, line_separator=True, spacing=0) + ui_state.add_offroad_transition_callback(self._offroad_transition) + def _initialize_items(self): dongle_id = self._params.get("DongleId") or "N/A" serial = self._params.get("HardwareSerial") or "N/A" @@ -52,21 +54,26 @@ class DeviceLayout(Widget): self._reset_calib_btn = button_item("Reset Calibration", "RESET", DESCRIPTIONS['reset_calibration'], callback=self._reset_calibration_prompt) self._reset_calib_btn.set_description_opened_callback(self._update_calib_description) + self._power_off_btn = dual_button_item("Reboot", "Power Off", left_callback=self._reboot_prompt, right_callback=self._power_off_prompt) + items = [ text_item("Dongle ID", dongle_id), text_item("Serial", serial), self._pair_device_btn, button_item("Driver Camera", "PREVIEW", DESCRIPTIONS['driver_camera'], callback=self._show_driver_camera, enabled=ui_state.is_offroad), self._reset_calib_btn, - button_item("Review Training Guide", "REVIEW", DESCRIPTIONS['review_guide'], self._on_review_training_guide), - regulatory_btn := button_item("Regulatory", "VIEW", callback=self._on_regulatory), + button_item("Review Training Guide", "REVIEW", DESCRIPTIONS['review_guide'], self._on_review_training_guide, enabled=ui_state.is_offroad), + regulatory_btn := button_item("Regulatory", "VIEW", callback=self._on_regulatory, enabled=ui_state.is_offroad), # TODO: implement multilang # button_item("Change Language", "CHANGE", callback=self._show_language_selection, enabled=ui_state.is_offroad), - dual_button_item("Reboot", "Power Off", left_callback=self._reboot_prompt, right_callback=self._power_off_prompt), + self._power_off_btn, ] regulatory_btn.set_visible(TICI) return items + def _offroad_transition(self): + self._power_off_btn.action_item.right_button.set_visible(ui_state.is_offroad()) + def show_event(self): self._scroller.show_event() diff --git a/system/ui/widgets/list_view.py b/system/ui/widgets/list_view.py index b1c4b44df..aaa67472d 100644 --- a/system/ui/widgets/list_view.py +++ b/system/ui/widgets/list_view.py @@ -191,6 +191,13 @@ class DualButtonAction(ItemAction): left_rect = rl.Rectangle(rect.x, button_y, button_width, button_height) right_rect = rl.Rectangle(rect.x + button_width + button_spacing, button_y, button_width, button_height) + # expand one to full width if other is not visible + if not self.left_button.is_visible: + right_rect.x = rect.x + right_rect.width = rect.width + elif not self.right_button.is_visible: + left_rect.width = rect.width + # Render buttons self.left_button.render(left_rect) self.right_button.render(right_rect) From fb772122218b7ff7bf716bacdb420d819f598a46 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 11 Oct 2025 06:02:10 -0700 Subject: [PATCH 151/910] raylib: add more spacing to network nav buttons --- system/ui/widgets/network.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/system/ui/widgets/network.py b/system/ui/widgets/network.py index 85b98f10a..a50881c8c 100644 --- a/system/ui/widgets/network.py +++ b/system/ui/widgets/network.py @@ -88,15 +88,15 @@ class NetworkUI(Widget): def _render(self, _): # subtract button - content_rect = rl.Rectangle(self._rect.x, self._rect.y + self._nav_button.rect.height + 20, - self._rect.width, self._rect.height - self._nav_button.rect.height - 20) + content_rect = rl.Rectangle(self._rect.x, self._rect.y + self._nav_button.rect.height + 40, + self._rect.width, self._rect.height - self._nav_button.rect.height - 40) if self._current_panel == PanelType.WIFI: self._nav_button.text = "Advanced" - self._nav_button.set_position(self._rect.x + self._rect.width - self._nav_button.rect.width, self._rect.y + 10) + self._nav_button.set_position(self._rect.x + self._rect.width - self._nav_button.rect.width, self._rect.y + 20) self._wifi_panel.render(content_rect) else: self._nav_button.text = "Back" - self._nav_button.set_position(self._rect.x, self._rect.y + 10) + self._nav_button.set_position(self._rect.x, self._rect.y + 20) self._advanced_panel.render(content_rect) self._nav_button.render() From e8a17b49639aa872ad9097d8ab71405f1cde53d7 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 11 Oct 2025 06:04:27 -0700 Subject: [PATCH 152/910] raylib: fix stale frames going onroad (#36314) * fix * try * flip * now flip * fix network nav button heights * revert --- selfdrive/ui/onroad/cameraview.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/selfdrive/ui/onroad/cameraview.py b/selfdrive/ui/onroad/cameraview.py index ca031e2f1..c8ee9b140 100644 --- a/selfdrive/ui/onroad/cameraview.py +++ b/selfdrive/ui/onroad/cameraview.py @@ -8,6 +8,7 @@ from openpilot.system.hardware import TICI 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 from openpilot.system.ui.widgets import Widget +from openpilot.selfdrive.ui.ui_state import ui_state CONNECTION_RETRY_INTERVAL = 0.2 # seconds between connection attempts @@ -103,6 +104,18 @@ class CameraView(Widget): self.egl_texture = rl.load_texture_from_image(temp_image) rl.unload_image(temp_image) + ui_state.add_offroad_transition_callback(self._offroad_transition) + + def _offroad_transition(self): + if ui_state.is_onroad(): + # Prevent old frames from showing when going onroad. Qt has a separate thread + # which drains the VisionIpcClient SubSocket for us. Re-connecting is not enough + # and only clears internal buffers, not the message queue. + self.frame = None + if self.client: + del self.client + self.client = VisionIpcClient(self._name, self._stream_type, conflate=True) + def _set_placeholder_color(self, color: rl.Color): """Set a placeholder color to be drawn when no frame is available.""" self._placeholder_color = color From 14993f58e3224fb488e65fa7a16617510b49843b Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 11 Oct 2025 06:28:49 -0700 Subject: [PATCH 153/910] raylib: set speed fixes (#36317) * remove msaa artifacting by heavily reducing segments and match radius * always draw set speed with '-' like qt * clean up * match qt behavior for rivian --- selfdrive/ui/onroad/hud_renderer.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/selfdrive/ui/onroad/hud_renderer.py b/selfdrive/ui/onroad/hud_renderer.py index c813d852b..98c3d686f 100644 --- a/selfdrive/ui/onroad/hud_renderer.py +++ b/selfdrive/ui/onroad/hud_renderer.py @@ -60,7 +60,7 @@ class HudRenderer(Widget): super().__init__() """Initialize the HUD renderer.""" self.is_cruise_set: bool = False - self.is_cruise_available: bool = False + self.is_cruise_available: bool = True self.set_speed: float = SET_SPEED_NA self.speed: float = 0.0 self.v_ego_cluster_seen: bool = False @@ -130,8 +130,8 @@ class HudRenderer(Widget): y = rect.y + 45 set_speed_rect = rl.Rectangle(x, y, set_speed_width, UI_CONFIG.set_speed_height) - rl.draw_rectangle_rounded(set_speed_rect, 0.2, 30, COLORS.black_translucent) - rl.draw_rectangle_rounded_lines_ex(set_speed_rect, 0.2, 30, 6, COLORS.border_translucent) + rl.draw_rectangle_rounded(set_speed_rect, 0.35, 10, COLORS.black_translucent) + rl.draw_rectangle_rounded_lines_ex(set_speed_rect, 0.35, 10, 6, COLORS.border_translucent) max_color = COLORS.grey set_speed_color = COLORS.dark_grey From cec7a5dc989cb7a23475de2748354ed7b24dcc64 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 11 Oct 2025 06:55:35 -0700 Subject: [PATCH 154/910] raylib: fix styling for fullscreen alerts (#36318) * fix that * fix styling * and this * revert * fix full * revert --- selfdrive/ui/onroad/alert_renderer.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/selfdrive/ui/onroad/alert_renderer.py b/selfdrive/ui/onroad/alert_renderer.py index 2802363f8..02b015315 100644 --- a/selfdrive/ui/onroad/alert_renderer.py +++ b/selfdrive/ui/onroad/alert_renderer.py @@ -63,8 +63,8 @@ ALERT_CRITICAL_TIMEOUT = Alert( ALERT_CRITICAL_REBOOT = Alert( text1="System Unresponsive", text2="Reboot Device", - size=AlertSize.full, - status=AlertStatus.critical, + size=AlertSize.mid, + status=AlertStatus.normal, ) @@ -149,13 +149,17 @@ class AlertRenderer(Widget): else: is_long = len(alert.text1) > 15 font_size1 = 132 if is_long else 177 - align_ment = rl.GuiTextAlignment.TEXT_ALIGN_CENTER - vertical_align = rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE - text_rect = rl.Rectangle(rect.x, rect.y, rect.width, rect.height) - gui_text_box(text_rect, alert.text1, font_size1, alignment=align_ment, alignment_vertical=vertical_align, font_weight=FontWeight.BOLD) - text_rect.y = rect.y + rect.height // 2 - gui_text_box(text_rect, alert.text2, ALERT_FONT_BIG, alignment=align_ment) + align_center = rl.GuiTextAlignment.TEXT_ALIGN_CENTER + align_top = rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP + + top_offset = 240 if is_long else 270 + title_rect = rl.Rectangle(rect.x, rect.y + top_offset, rect.width, 600) + gui_text_box(title_rect, alert.text1, font_size1, alignment=align_center, alignment_vertical=align_top, font_weight=FontWeight.BOLD) + + bottom_offset = 361 if is_long else 420 + subtitle_rect = rl.Rectangle(rect.x, rect.y + rect.height - bottom_offset, rect.width, 300) + gui_text_box(subtitle_rect, alert.text2, ALERT_FONT_BIG, alignment=align_center, alignment_vertical=align_top) def _draw_centered(self, text, rect, font, font_size, center_y=True, color=rl.WHITE) -> None: text_size = measure_text_cached(font, text, font_size) From b3eba70b7ac406a8054a32d4f26015ff4b149d21 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 11 Oct 2025 07:41:29 -0700 Subject: [PATCH 155/910] raylib: flip! (#36319) * flip! * add ui * ? * qt is extra * low node * add uiDebug * fix * fix dat * bump double increase for tol * it's ~11ms but double for tol * fix report * Update selfdrive/test/test_onroad.py --- selfdrive/SConscript | 3 ++- selfdrive/test/test_onroad.py | 6 +++--- selfdrive/ui/onroad/augmented_road_view.py | 12 +++++++++++- selfdrive/ui/tests/test_raylib_ui.py | 2 +- selfdrive/ui/tests/test_ui/raylib_screenshots.py | 2 +- system/manager/build.py | 2 +- system/manager/process_config.py | 4 ++-- 7 files changed, 21 insertions(+), 10 deletions(-) diff --git a/selfdrive/SConscript b/selfdrive/SConscript index 0b49e6911..43bf7d047 100644 --- a/selfdrive/SConscript +++ b/selfdrive/SConscript @@ -3,4 +3,5 @@ SConscript(['controls/lib/lateral_mpc_lib/SConscript']) SConscript(['controls/lib/longitudinal_mpc_lib/SConscript']) SConscript(['locationd/SConscript']) SConscript(['modeld/SConscript']) -SConscript(['ui/SConscript']) \ No newline at end of file +if GetOption('extras'): + SConscript(['ui/SConscript']) diff --git a/selfdrive/test/test_onroad.py b/selfdrive/test/test_onroad.py index b4b9b9dbb..9fc76489b 100644 --- a/selfdrive/test/test_onroad.py +++ b/selfdrive/test/test_onroad.py @@ -32,7 +32,7 @@ CPU usage budget TEST_DURATION = 25 LOG_OFFSET = 8 -MAX_TOTAL_CPU = 300. # total for all 8 cores +MAX_TOTAL_CPU = 315. # total for all 8 cores PROCS = { # Baseline CPU usage by process "selfdrive.controls.controlsd": 16.0, @@ -42,7 +42,7 @@ PROCS = { "./encoderd": 13.0, "./camerad": 10.0, "selfdrive.controls.plannerd": 8.0, - "./ui": 18.0, + "selfdrive.ui.ui": 24.0, "system.sensord.sensord": 13.0, "selfdrive.controls.radard": 2.0, "selfdrive.modeld.modeld": 22.0, @@ -215,7 +215,7 @@ class TestOnroad: print(result) assert max(ts) < 250. - assert np.mean(ts) < 10. + assert np.mean(ts) < 20. # TODO: ~6-11ms, increase consistency #self.assertLess(np.std(ts), 5.) # some slow frames are expected since camerad/modeld can preempt ui diff --git a/selfdrive/ui/onroad/augmented_road_view.py b/selfdrive/ui/onroad/augmented_road_view.py index 0a4c45163..cac2f9c96 100644 --- a/selfdrive/ui/onroad/augmented_road_view.py +++ b/selfdrive/ui/onroad/augmented_road_view.py @@ -1,6 +1,7 @@ +import time import numpy as np import pyray as rl -from cereal import log +from cereal import log, messaging from msgq.visionipc import VisionStreamType from openpilot.selfdrive.ui.ui_state import ui_state, UIStatus, UI_BORDER_SIZE from openpilot.selfdrive.ui.onroad.alert_renderer import AlertRenderer @@ -48,8 +49,12 @@ class AugmentedRoadView(CameraView): self.alert_renderer = AlertRenderer() self.driver_state_renderer = DriverStateRenderer() + # debug + self._pm = messaging.PubMaster(['uiDebug']) + def _render(self, rect): # Only render when system is started to avoid invalid data access + start_draw = time.monotonic() if not ui_state.started: return @@ -93,6 +98,11 @@ class AugmentedRoadView(CameraView): # End clipping region rl.end_scissor_mode() + # publish uiDebug + msg = messaging.new_message('uiDebug') + msg.uiDebug.drawTimeMillis = (time.monotonic() - start_draw) * 1000 + self._pm.send('uiDebug', msg) + def _handle_mouse_press(self, _): if not self._hud_renderer.user_interacting() and self._click_callback is not None: self._click_callback() diff --git a/selfdrive/ui/tests/test_raylib_ui.py b/selfdrive/ui/tests/test_raylib_ui.py index 3f2330197..69ba946dc 100644 --- a/selfdrive/ui/tests/test_raylib_ui.py +++ b/selfdrive/ui/tests/test_raylib_ui.py @@ -2,7 +2,7 @@ import time from openpilot.selfdrive.test.helpers import with_processes -@with_processes(["raylib_ui"]) +@with_processes(["ui"]) def test_raylib_ui(): """Test initialization of the UI widgets is successful.""" time.sleep(1) diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py index fb42b94d6..bd40b8962 100755 --- a/selfdrive/ui/tests/test_ui/raylib_screenshots.py +++ b/selfdrive/ui/tests/test_ui/raylib_screenshots.py @@ -160,7 +160,7 @@ class TestUI: time.sleep(0.01) pyautogui.mouseUp(self.ui.left + x, self.ui.top + y, *args, **kwargs) - @with_processes(["raylib_ui"]) + @with_processes(["ui"]) def test_ui(self, name, setup_case): self.setup() time.sleep(UI_DELAY) # wait for UI to start diff --git a/system/manager/build.py b/system/manager/build.py index b6153ee8a..c88befd45 100755 --- a/system/manager/build.py +++ b/system/manager/build.py @@ -14,7 +14,7 @@ from openpilot.system.version import get_build_metadata MAX_CACHE_SIZE = 4e9 if "CI" in os.environ else 2e9 CACHE_DIR = Path("/data/scons_cache" if AGNOS else "/tmp/scons_cache") -TOTAL_SCONS_NODES = 3275 +TOTAL_SCONS_NODES = 2280 MAX_BUILD_PROGRESS = 100 def build(spinner: Spinner, dirty: bool = False, minimal: bool = False) -> None: diff --git a/system/manager/process_config.py b/system/manager/process_config.py index 22f159e89..b8a1e0988 100644 --- a/system/manager/process_config.py +++ b/system/manager/process_config.py @@ -80,8 +80,8 @@ procs = [ PythonProcess("dmonitoringmodeld", "selfdrive.modeld.dmonitoringmodeld", driverview, enabled=(WEBCAM or not PC)), PythonProcess("sensord", "system.sensord.sensord", only_onroad, enabled=not PC), - NativeProcess("ui", "selfdrive/ui", ["./ui"], always_run, watchdog_max_dt=(5 if not PC else None)), - PythonProcess("raylib_ui", "selfdrive.ui.ui", always_run, enabled=False, watchdog_max_dt=(5 if not PC else None)), + # NativeProcess("ui", "selfdrive/ui", ["./ui"], always_run, enabled=False, watchdog_max_dt=(5 if not PC else None)), + PythonProcess("ui", "selfdrive.ui.ui", always_run, watchdog_max_dt=(5 if not PC else None)), PythonProcess("soundd", "selfdrive.ui.soundd", only_onroad), PythonProcess("locationd", "selfdrive.locationd.locationd", only_onroad), NativeProcess("_pandad", "selfdrive/pandad", ["./pandad"], always_run, enabled=False), From 49d9b8bb001a4739cfd91d7b1039e7d8cd5bb8f2 Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Sat, 11 Oct 2025 23:06:06 -0700 Subject: [PATCH 156/910] ui: fix cloudlog spam (#36321) * dark office * check * back * fix * remove * remove --- selfdrive/ui/ui_state.py | 1 - system/ui/lib/application.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/selfdrive/ui/ui_state.py b/selfdrive/ui/ui_state.py index 3ac9dbea1..dab01f924 100644 --- a/selfdrive/ui/ui_state.py +++ b/selfdrive/ui/ui_state.py @@ -228,7 +228,6 @@ class Device: if brightness != self._last_brightness: if self._brightness_thread is None or not self._brightness_thread.is_alive(): - cloudlog.debug(f"setting display brightness {brightness}") self._brightness_thread = threading.Thread(target=HARDWARE.set_screen_brightness, args=(brightness,)) self._brightness_thread.start() self._last_brightness = brightness diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index d3bc3d494..f0bcdcdc6 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -160,7 +160,7 @@ class GuiApplication: HARDWARE.set_screen_brightness(65) self._set_log_callback() - rl.set_trace_log_level(rl.TraceLogLevel.LOG_ALL) + rl.set_trace_log_level(rl.TraceLogLevel.LOG_WARNING) flags = rl.ConfigFlags.FLAG_MSAA_4X_HINT if ENABLE_VSYNC: From 32f65bae55bd75e94a33bc85eba3dde54ebf9b89 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 11 Oct 2025 23:29:08 -0700 Subject: [PATCH 157/910] alpha long: allow toggle while onroad + restart op it's alpha, and some cars don't fault (we allow other toggles which would fault, so why not enable) --- selfdrive/ui/layouts/settings/developer.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/selfdrive/ui/layouts/settings/developer.py b/selfdrive/ui/layouts/settings/developer.py index fac00b60a..21eb44efe 100644 --- a/selfdrive/ui/layouts/settings/developer.py +++ b/selfdrive/ui/layouts/settings/developer.py @@ -70,9 +70,11 @@ class DeveloperLayout(Widget): description=DESCRIPTIONS["alpha_longitudinal"], initial_state=self._params.get_bool("AlphaLongitudinalEnabled"), callback=self._on_alpha_long_enabled, - enabled=ui_state.is_offroad, + enabled=lambda: not ui_state.engaged, ) + self._alpha_long_toggle.set_description(self._alpha_long_toggle.description + " Changing this setting will restart openpilot if the car is powered on.") + items = [ self._adb_toggle, self._ssh_toggle, @@ -152,6 +154,7 @@ class DeveloperLayout(Widget): def confirm_callback(result: int): if result == DialogResult.CONFIRM: self._params.put_bool("AlphaLongitudinalEnabled", True) + self._params.put_bool("OnroadCycleRequested", True) self._update_toggles() else: self._alpha_long_toggle.action_item.set_state(False) @@ -162,7 +165,8 @@ class DeveloperLayout(Widget): dlg = ConfirmDialog(content, "Enable", rich=True) gui_app.set_modal_overlay(dlg, callback=confirm_callback) - return - self._params.put_bool("AlphaLongitudinalEnabled", state) - self._update_toggles() + else: + self._params.put_bool("AlphaLongitudinalEnabled", False) + self._params.put_bool("OnroadCycleRequested", True) + self._update_toggles() From 5f7b05e8084fe1d1057735e54f2aa906f4b12345 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 11 Oct 2025 23:36:05 -0700 Subject: [PATCH 158/910] raylib: don't create vipc client twice first time --- selfdrive/ui/onroad/cameraview.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/selfdrive/ui/onroad/cameraview.py b/selfdrive/ui/onroad/cameraview.py index c8ee9b140..744fdbf13 100644 --- a/selfdrive/ui/onroad/cameraview.py +++ b/selfdrive/ui/onroad/cameraview.py @@ -107,7 +107,8 @@ class CameraView(Widget): ui_state.add_offroad_transition_callback(self._offroad_transition) def _offroad_transition(self): - if ui_state.is_onroad(): + # Reconnect if not first time going onroad + if ui_state.is_onroad() and self.frame is not None: # Prevent old frames from showing when going onroad. Qt has a separate thread # which drains the VisionIpcClient SubSocket for us. Re-connecting is not enough # and only clears internal buffers, not the message queue. From 13d0aefd7c14908cad6df33d09dce8ea623c3b02 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 11 Oct 2025 23:40:42 -0700 Subject: [PATCH 159/910] raylib: don't get old onroad alert on startup --- selfdrive/ui/onroad/alert_renderer.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/selfdrive/ui/onroad/alert_renderer.py b/selfdrive/ui/onroad/alert_renderer.py index 02b015315..9754d488e 100644 --- a/selfdrive/ui/onroad/alert_renderer.py +++ b/selfdrive/ui/onroad/alert_renderer.py @@ -79,8 +79,8 @@ class AlertRenderer(Widget): ss = sm['selfdriveState'] # Check if selfdriveState messages have stopped arriving + recv_frame = sm.recv_frame['selfdriveState'] if not sm.updated['selfdriveState']: - recv_frame = sm.recv_frame['selfdriveState'] time_since_onroad = time.monotonic() - ui_state.started_time # 1. Never received selfdriveState since going onroad @@ -100,6 +100,10 @@ class AlertRenderer(Widget): if ss.alertSize == 0: return None + # Don't get old alert + if recv_frame < ui_state.started_frame: + return None + # Return current alert return Alert(text1=ss.alertText1, text2=ss.alertText2, size=ss.alertSize.raw, status=ss.alertStatus.raw) From 129445cd1d2c86a3bd3c4dba8369a3506a656772 Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Sun, 12 Oct 2025 00:37:36 -0700 Subject: [PATCH 160/910] setup: don't wait for so long (#36323) nice --- system/ui/setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system/ui/setup.py b/system/ui/setup.py index 4068af01e..99a3569fb 100755 --- a/system/ui/setup.py +++ b/system/ui/setup.py @@ -343,7 +343,7 @@ class Setup(Widget): shutil.copyfile(INSTALLER_SOURCE_PATH, INSTALLER_DESTINATION_PATH) # give time for installer UI to take over - time.sleep(1) + time.sleep(0.1) gui_app.request_close() else: self.state = SetupState.NETWORK_SETUP @@ -406,7 +406,7 @@ class Setup(Widget): f.write(self.download_url) # give time for installer UI to take over - time.sleep(5) + time.sleep(0.1) gui_app.request_close() except Exception: From de0a1e66d828f5723c4ce00957d5adae842e1a4b Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 13 Oct 2025 13:54:50 -0700 Subject: [PATCH 161/910] software download screenshot (#36326) * software * clean up * Qt ButtonControl has 0 padding * clean up * clean up --- .../ui/tests/test_ui/raylib_screenshots.py | 22 +++++++++++++------ system/ui/widgets/button.py | 2 +- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py index bd40b8962..20d0f2ecb 100755 --- a/selfdrive/ui/tests/test_ui/raylib_screenshots.py +++ b/selfdrive/ui/tests/test_ui/raylib_screenshots.py @@ -64,6 +64,19 @@ def setup_settings_software(click, pm: PubMaster): click(278, 720) +def setup_settings_software_download(click, pm: PubMaster): + params = Params() + # setup_settings_software but with "DOWNLOAD" button to test long text + params.put("UpdaterState", "idle") + params.put_bool("UpdaterFetchAvailable", True) + setup_settings_software(click, pm) + + +def setup_settings_software_release_notes(click, pm: PubMaster): + setup_settings_software(click, pm) + click(588, 110) # expand description for current version + + def setup_settings_firehose(click, pm: PubMaster): setup_settings(click, pm) click(278, 845) @@ -106,18 +119,14 @@ def setup_homescreen_update_available(click, pm: PubMaster): close_settings(click, pm) -def setup_software_release_notes(click, pm: PubMaster): - setup_settings(click, pm) - setup_settings_software(click, pm) - click(588, 110) # expand description for current version - - CASES = { "homescreen": setup_homescreen, "settings_device": setup_settings, "settings_network": setup_settings_network, "settings_toggles": setup_settings_toggles, "settings_software": setup_settings_software, + "settings_software_download": setup_settings_software_download, + "settings_software_release_notes": setup_settings_software_release_notes, "settings_firehose": setup_settings_firehose, "settings_developer": setup_settings_developer, "keyboard": setup_keyboard, @@ -125,7 +134,6 @@ CASES = { "offroad_alert": setup_offroad_alert, "homescreen_update_available": setup_homescreen_update_available, "confirmation_dialog": setup_confirmation_dialog, - "software_release_notes": setup_software_release_notes, } diff --git a/system/ui/widgets/button.py b/system/ui/widgets/button.py index 141f682db..ef28df811 100644 --- a/system/ui/widgets/button.py +++ b/system/ui/widgets/button.py @@ -86,7 +86,7 @@ class Button(Widget): button_style: ButtonStyle = ButtonStyle.NORMAL, border_radius: int = 10, text_alignment: TextAlignment = TextAlignment.CENTER, - text_padding: int = 20, + text_padding: int = 0, icon=None, multi_touch: bool = False, ): From 692f5fdd72b06119a7dd2989d86fdb125f9b72f6 Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Mon, 13 Oct 2025 15:56:47 -0500 Subject: [PATCH 162/910] Add screenshot for experimental mode description (raylib UI test) (#36327) * feat: add test case to expand experimental mode description * Update selfdrive/ui/tests/test_ui/raylib_screenshots.py --------- Co-authored-by: Shane Smiskol --- selfdrive/ui/tests/test_ui/raylib_screenshots.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py index 20d0f2ecb..a65f198ad 100755 --- a/selfdrive/ui/tests/test_ui/raylib_screenshots.py +++ b/selfdrive/ui/tests/test_ui/raylib_screenshots.py @@ -119,6 +119,11 @@ def setup_homescreen_update_available(click, pm: PubMaster): close_settings(click, pm) +def setup_experimental_mode_description(click, pm: PubMaster): + setup_settings_toggles(click, pm) + click(1200, 280) # expand description for experimental mode + + CASES = { "homescreen": setup_homescreen, "settings_device": setup_settings, @@ -134,6 +139,7 @@ CASES = { "offroad_alert": setup_offroad_alert, "homescreen_update_available": setup_homescreen_update_available, "confirmation_dialog": setup_confirmation_dialog, + "experimental_mode_description": setup_experimental_mode_description, } From 41fa0cdf8271b8cd83b15eb349e0b48e5c12866b Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Mon, 13 Oct 2025 16:07:11 -0500 Subject: [PATCH 163/910] fix cycle offroad alerts (#36302) * fix: use parse_release_notes in cycle_offroad_alerts * fix: set update available param true * Update selfdrive/ui/tests/cycle_offroad_alerts.py --------- Co-authored-by: Shane Smiskol --- selfdrive/ui/tests/cycle_offroad_alerts.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/selfdrive/ui/tests/cycle_offroad_alerts.py b/selfdrive/ui/tests/cycle_offroad_alerts.py index e468d88e0..b577b74b0 100755 --- a/selfdrive/ui/tests/cycle_offroad_alerts.py +++ b/selfdrive/ui/tests/cycle_offroad_alerts.py @@ -7,6 +7,7 @@ import json from openpilot.common.basedir import BASEDIR from openpilot.common.params import Params from openpilot.selfdrive.selfdrived.alertmanager import set_offroad_alert +from openpilot.system.updated.updated import parse_release_notes if __name__ == "__main__": params = Params() @@ -18,9 +19,7 @@ if __name__ == "__main__": while True: print("setting alert update") params.put_bool("UpdateAvailable", True) - r = open(os.path.join(BASEDIR, "RELEASES.md")).read() - r = r[:r.find('\n\n')] # Slice latest release notes - params.put("UpdaterNewReleaseNotes", r + "\n") + params.put("UpdaterNewReleaseNotes", parse_release_notes(BASEDIR)) time.sleep(t) params.put_bool("UpdateAvailable", False) From 8e3757ac875a244adb1cef4d029cb8c59b345982 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 13 Oct 2025 16:49:39 -0700 Subject: [PATCH 164/910] raylib: image dimensions are optional (#36332) * meas * now no resizing * clean up --- selfdrive/ui/layouts/onboarding.py | 2 +- system/ui/lib/application.py | 33 ++++++++++++++++-------------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/selfdrive/ui/layouts/onboarding.py b/selfdrive/ui/layouts/onboarding.py index ea6fedebf..bccb36712 100644 --- a/selfdrive/ui/layouts/onboarding.py +++ b/selfdrive/ui/layouts/onboarding.py @@ -46,7 +46,7 @@ class TrainingGuide(Widget): paths = sorted(paths, key=lambda x: int(re.search(r'\d+', x).group())) for fn in paths: path = os.path.join(BASEDIR, "selfdrive/assets/training", fn) - self._images.append(gui_app.texture(path, gui_app.width, gui_app.height)) + self._images.append(gui_app.texture(path)) def _handle_mouse_release(self, mouse_pos): if rl.check_collision_point_rec(mouse_pos, STEP_RECTS[self._step]): diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index f0bcdcdc6..1925122d0 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -189,7 +189,8 @@ class GuiApplication: self._modal_overlay = ModalOverlay(overlay=overlay, callback=callback) - def texture(self, asset_path: str, width: int, height: int, alpha_premultiply=False, keep_aspect_ratio=True): + def texture(self, asset_path: str, width: int | None = None, height: int | None = None, + 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] @@ -199,29 +200,31 @@ class GuiApplication: self._textures[cache_key] = texture_obj return texture_obj - def _load_texture_from_image(self, image_path: str, width: int, height: int, alpha_premultiply=False, keep_aspect_ratio=True): + def _load_texture_from_image(self, image_path: str, width: int | None = None, height: int | None = None, + alpha_premultiply=False, keep_aspect_ratio=True): """Load and resize a texture, storing it for later automatic unloading.""" image = rl.load_image(image_path) if alpha_premultiply: rl.image_alpha_premultiply(image) - # Resize with aspect ratio preservation if requested - if keep_aspect_ratio: - orig_width = image.width - orig_height = image.height + if width is not None and height is not None: + # 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 + 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) + # 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) + 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 From f4041dc1f0ffbdd261fdc342d312e6a0f96c7c1e Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 14 Oct 2025 00:33:38 -0700 Subject: [PATCH 165/910] raylib: black sidebar (#36347) black sidebar --- selfdrive/ui/layouts/sidebar.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/selfdrive/ui/layouts/sidebar.py b/selfdrive/ui/layouts/sidebar.py index 4ba7f2aa1..9337b3c23 100644 --- a/selfdrive/ui/layouts/sidebar.py +++ b/selfdrive/ui/layouts/sidebar.py @@ -23,7 +23,6 @@ NetworkType = log.DeviceState.NetworkType # Color scheme class Colors: - SIDEBAR_BG = rl.Color(57, 57, 57, 255) WHITE = rl.WHITE WHITE_DIM = rl.Color(255, 255, 255, 85) GRAY = rl.Color(84, 84, 84, 255) @@ -94,7 +93,7 @@ class Sidebar(Widget): def _render(self, rect: rl.Rectangle): # Background - rl.draw_rectangle_rec(rect, Colors.SIDEBAR_BG) + rl.draw_rectangle_rec(rect, rl.BLACK) self._draw_buttons(rect) self._draw_network_indicator(rect) From a2cce7f897dd6a1516fb17fd9f595e5af3f0e626 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 14 Oct 2025 00:39:22 -0700 Subject: [PATCH 166/910] raylib: fix border radius (#36346) * colors * revert color * rev --- selfdrive/ui/widgets/exp_mode_button.py | 3 +-- selfdrive/ui/widgets/prime.py | 4 ++-- selfdrive/ui/widgets/setup.py | 4 ++-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/selfdrive/ui/widgets/exp_mode_button.py b/selfdrive/ui/widgets/exp_mode_button.py index 40a649899..97c399cda 100644 --- a/selfdrive/ui/widgets/exp_mode_button.py +++ b/selfdrive/ui/widgets/exp_mode_button.py @@ -32,10 +32,9 @@ class ExperimentalModeButton(Widget): start_color, end_color) def _render(self, rect): - rl.draw_rectangle_rounded(rect, 0.08, 20, rl.WHITE) - rl.begin_scissor_mode(int(rect.x), int(rect.y), int(rect.width), int(rect.height)) self._draw_gradient_background(rect) + rl.draw_rectangle_rounded_lines_ex(self._rect, 0.19, 10, 5, rl.BLACK) rl.end_scissor_mode() # Draw vertical separator line diff --git a/selfdrive/ui/widgets/prime.py b/selfdrive/ui/widgets/prime.py index 1e31ac8a1..e779ead33 100644 --- a/selfdrive/ui/widgets/prime.py +++ b/selfdrive/ui/widgets/prime.py @@ -22,7 +22,7 @@ class PrimeWidget(Widget): def _render_for_non_prime_users(self, rect: rl.Rectangle): """Renders the advertisement for non-Prime users.""" - rl.draw_rectangle_rounded(rect, 0.02, 10, self.PRIME_BG_COLOR) + rl.draw_rectangle_rounded(rect, 0.025, 10, self.PRIME_BG_COLOR) # Layout x, y = rect.x + 80, rect.y + 90 @@ -52,7 +52,7 @@ class PrimeWidget(Widget): def _render_for_prime_user(self, rect: rl.Rectangle): """Renders the prime user widget with subscription status.""" - rl.draw_rectangle_rounded(rl.Rectangle(rect.x, rect.y, rect.width, 230), 0.05, 10, self.PRIME_BG_COLOR) + rl.draw_rectangle_rounded(rl.Rectangle(rect.x, rect.y, rect.width, 230), 0.1, 10, self.PRIME_BG_COLOR) x = rect.x + 56 y = rect.y + 40 diff --git a/selfdrive/ui/widgets/setup.py b/selfdrive/ui/widgets/setup.py index dfaa3e400..f786d1ab2 100644 --- a/selfdrive/ui/widgets/setup.py +++ b/selfdrive/ui/widgets/setup.py @@ -30,7 +30,7 @@ class SetupWidget(Widget): def _render_registration(self, rect: rl.Rectangle): """Render registration prompt.""" - rl.draw_rectangle_rounded(rl.Rectangle(rect.x, rect.y, rect.width, rect.height), 0.02, 20, rl.Color(51, 51, 51, 255)) + rl.draw_rectangle_rounded(rl.Rectangle(rect.x, rect.y, rect.width, rect.height), 0.03, 20, rl.Color(51, 51, 51, 255)) x = rect.x + 64 y = rect.y + 48 @@ -55,7 +55,7 @@ class SetupWidget(Widget): def _render_firehose_prompt(self, rect: rl.Rectangle): """Render firehose prompt widget.""" - rl.draw_rectangle_rounded(rl.Rectangle(rect.x, rect.y, rect.width, 500), 0.02, 20, rl.Color(51, 51, 51, 255)) + rl.draw_rectangle_rounded(rl.Rectangle(rect.x, rect.y, rect.width, 500), 0.04, 20, rl.Color(51, 51, 51, 255)) # Content margins (56, 40, 56, 40) x = rect.x + 56 From 5f0e9fce612f408b5fcc9c952bdd143669a122af Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 14 Oct 2025 00:40:41 -0700 Subject: [PATCH 167/910] raylib: update experimental mode homescreen button (#36344) * update homescreen exp mode button * and here * Apply suggestions from code review --- selfdrive/ui/layouts/home.py | 4 ++++ selfdrive/ui/widgets/exp_mode_button.py | 3 +++ 2 files changed, 7 insertions(+) diff --git a/selfdrive/ui/layouts/home.py b/selfdrive/ui/layouts/home.py index 70960ee22..519e0e020 100644 --- a/selfdrive/ui/layouts/home.py +++ b/selfdrive/ui/layouts/home.py @@ -62,6 +62,7 @@ class HomeLayout(Widget): self._setup_callbacks() def show_event(self): + self._exp_mode_button.show_event() self.last_refresh = time.monotonic() self._refresh() @@ -76,6 +77,9 @@ class HomeLayout(Widget): def _set_state(self, state: HomeLayoutState): # propagate show/hide events if state != self.current_state: + if state == HomeLayoutState.HOME: + self._exp_mode_button.show_event() + if state in self._layout_widgets: self._layout_widgets[state].show_event() if self.current_state in self._layout_widgets: diff --git a/selfdrive/ui/widgets/exp_mode_button.py b/selfdrive/ui/widgets/exp_mode_button.py index 97c399cda..23031be30 100644 --- a/selfdrive/ui/widgets/exp_mode_button.py +++ b/selfdrive/ui/widgets/exp_mode_button.py @@ -18,6 +18,9 @@ class ExperimentalModeButton(Widget): self.chill_pixmap = gui_app.texture("icons/couch.png", self.img_width, self.img_width) self.experimental_pixmap = gui_app.texture("icons/experimental_grey.png", self.img_width, self.img_width) + def show_event(self): + self.experimental_mode = self.params.get_bool("ExperimentalMode") + def _get_gradient_colors(self): alpha = 0xCC if self.is_pressed else 0xFF From 87443cd34d8db902a5e665b9ef679f09899a1c1a Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 14 Oct 2025 00:45:03 -0700 Subject: [PATCH 168/910] raylib: background onboarding texture loading (#36343) * this seems best so far * better * clean up * debug * debug * clean up * final --- selfdrive/ui/layouts/onboarding.py | 40 +++++++++++++++++++++--------- system/ui/lib/application.py | 12 ++++++--- 2 files changed, 36 insertions(+), 16 deletions(-) diff --git a/selfdrive/ui/layouts/onboarding.py b/selfdrive/ui/layouts/onboarding.py index bccb36712..f4ab1a05c 100644 --- a/selfdrive/ui/layouts/onboarding.py +++ b/selfdrive/ui/layouts/onboarding.py @@ -1,5 +1,6 @@ import os import re +import threading from enum import IntEnum import pyray as rl @@ -38,15 +39,24 @@ class TrainingGuide(Widget): self._completed_callback = completed_callback self._step = 0 - self._load_images() + self._load_image_paths() - def _load_images(self): - self._images = [] + # Load first image now so we show something immediately + self._textures = [gui_app.texture(self._image_paths[0])] + self._image_objs = [] + + threading.Thread(target=self._preload_thread, daemon=True).start() + + def _load_image_paths(self): paths = [fn for fn in os.listdir(os.path.join(BASEDIR, "selfdrive/assets/training")) if re.match(r'^step\d*\.png$', fn)] paths = sorted(paths, key=lambda x: int(re.search(r'\d+', x).group())) - for fn in paths: - path = os.path.join(BASEDIR, "selfdrive/assets/training", fn) - self._images.append(gui_app.texture(path)) + self._image_paths = [os.path.join(BASEDIR, "selfdrive/assets/training", fn) for fn in paths] + + def _preload_thread(self): + # PNG loading is slow in raylib, so we preload in a thread and upload to GPU in main thread + # We've already loaded the first image on init + for path in self._image_paths[1:]: + self._image_objs.append(gui_app._load_image_from_path(path)) def _handle_mouse_release(self, mouse_pos): if rl.check_collision_point_rec(mouse_pos, STEP_RECTS[self._step]): @@ -57,30 +67,36 @@ class TrainingGuide(Widget): ui_state.params.put_bool("RecordFront", yes) # Restart training? - elif self._step == len(self._images) - 1: + elif self._step == len(self._image_paths) - 1: if rl.check_collision_point_rec(mouse_pos, RESTART_TRAINING_RECT): self._step = -1 self._step += 1 # Finished? - if self._step >= len(self._images): + if self._step >= len(self._image_paths): self._step = 0 if self._completed_callback: self._completed_callback() + def _update_state(self): + if len(self._image_objs): + self._textures.append(gui_app._load_texture_from_image(self._image_objs.pop(0))) + def _render(self, _): - rl.draw_texture(self._images[self._step], 0, 0, rl.WHITE) + # Safeguard against fast tapping + step = min(self._step, len(self._textures) - 1) + rl.draw_texture(self._textures[step], 0, 0, rl.WHITE) # progress bar - if 0 < self._step < len(STEP_RECTS) - 1: + if 0 < step < len(STEP_RECTS) - 1: h = 20 - w = int((self._step / (len(STEP_RECTS) - 1)) * self._rect.width) + w = int((step / (len(STEP_RECTS) - 1)) * self._rect.width) rl.draw_rectangle(int(self._rect.x), int(self._rect.y + self._rect.height - h), w, h, rl.Color(70, 91, 234, 255)) if DEBUG: - rl.draw_rectangle_lines_ex(STEP_RECTS[self._step], 3, rl.RED) + rl.draw_rectangle_lines_ex(STEP_RECTS[step], 3, rl.RED) return -1 diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 1925122d0..4a62c996c 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -196,13 +196,14 @@ class GuiApplication: 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) + image_obj = self._load_image_from_path(fspath.as_posix(), width, height, alpha_premultiply, keep_aspect_ratio) + texture_obj = self._load_texture_from_image(image_obj) self._textures[cache_key] = texture_obj return texture_obj - def _load_texture_from_image(self, image_path: str, width: int | None = None, height: int | None = None, - alpha_premultiply=False, keep_aspect_ratio=True): - """Load and resize a texture, storing it for later automatic unloading.""" + def _load_image_from_path(self, image_path: str, width: int | None = None, height: int | None = None, + alpha_premultiply: bool = False, keep_aspect_ratio: bool = True) -> rl.Image: + """Load and resize an image, storing it for later automatic unloading.""" image = rl.load_image(image_path) if alpha_premultiply: @@ -225,7 +226,10 @@ class GuiApplication: rl.image_resize(image, new_width, new_height) else: rl.image_resize(image, width, height) + return image + def _load_texture_from_image(self, image: rl.Image) -> rl.Texture: + """Send image to GPU and unload original image.""" texture = rl.load_texture_from_image(image) # Set texture filtering to smooth the result rl.set_texture_filter(texture, rl.TextureFilter.TEXTURE_FILTER_BILINEAR) From 3546b625e7e37e5abfb187a679fd379c75eed96a Mon Sep 17 00:00:00 2001 From: felsager <76905857+felsager@users.noreply.github.com> Date: Tue, 14 Oct 2025 13:22:17 -0700 Subject: [PATCH 169/910] latcontrol_torque: change in kp should not affect effective low speed factor gain (#36335) --- selfdrive/controls/lib/latcontrol_torque.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/controls/lib/latcontrol_torque.py b/selfdrive/controls/lib/latcontrol_torque.py index 2a1b666ef..664e86663 100644 --- a/selfdrive/controls/lib/latcontrol_torque.py +++ b/selfdrive/controls/lib/latcontrol_torque.py @@ -77,7 +77,7 @@ class LatControlTorque(LatControl): low_speed_factor = (np.interp(CS.vEgo, LOW_SPEED_X, LOW_SPEED_Y) / max(CS.vEgo, MIN_SPEED)) ** 2 setpoint = lat_delay * desired_lateral_jerk + expected_lateral_accel error = setpoint - measurement - error_lsf = error + low_speed_factor * error + error_lsf = error + low_speed_factor / self.torque_params.kp * error # do error correction in lateral acceleration space, convert at end to handle non-linear torque responses correctly pid_log.error = float(error_lsf) From 8a1fcd8991cde9c122bfe5d01b835dee85b1b681 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 14 Oct 2025 21:17:36 -0700 Subject: [PATCH 170/910] raylib: fix possible DM crash (#36354) * fix * bruh * clean up * here * rm --- selfdrive/ui/onroad/alert_renderer.py | 5 ++--- selfdrive/ui/onroad/augmented_road_view.py | 4 ++-- selfdrive/ui/onroad/driver_state.py | 14 ++++++-------- 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/selfdrive/ui/onroad/alert_renderer.py b/selfdrive/ui/onroad/alert_renderer.py index 9754d488e..362f49a51 100644 --- a/selfdrive/ui/onroad/alert_renderer.py +++ b/selfdrive/ui/onroad/alert_renderer.py @@ -107,10 +107,10 @@ class AlertRenderer(Widget): # Return current alert return Alert(text1=ss.alertText1, text2=ss.alertText2, size=ss.alertSize.raw, status=ss.alertStatus.raw) - def _render(self, rect: rl.Rectangle) -> bool: + def _render(self, rect: rl.Rectangle): alert = self.get_alert(ui_state.sm) if not alert: - return False + return alert_rect = self._get_alert_rect(rect, alert.size) self._draw_background(alert_rect, alert) @@ -122,7 +122,6 @@ class AlertRenderer(Widget): alert_rect.height - 2 * ALERT_PADDING ) self._draw_text(text_rect, alert) - return True def _get_alert_rect(self, rect: rl.Rectangle, size: int) -> rl.Rectangle: if size == AlertSize.full: diff --git a/selfdrive/ui/onroad/augmented_road_view.py b/selfdrive/ui/onroad/augmented_road_view.py index cac2f9c96..bffa30ad2 100644 --- a/selfdrive/ui/onroad/augmented_road_view.py +++ b/selfdrive/ui/onroad/augmented_road_view.py @@ -89,8 +89,8 @@ class AugmentedRoadView(CameraView): # Draw all UI overlays self.model_renderer.render(self._content_rect) self._hud_renderer.render(self._content_rect) - if not self.alert_renderer.render(self._content_rect): - self.driver_state_renderer.render(self._content_rect) + self.alert_renderer.render(self._content_rect) + self.driver_state_renderer.render(self._content_rect) # Custom UI extension point - add custom overlays here # Use self._content_rect for positioning within camera bounds diff --git a/selfdrive/ui/onroad/driver_state.py b/selfdrive/ui/onroad/driver_state.py index 69ed5f458..8aaef1bcf 100644 --- a/selfdrive/ui/onroad/driver_state.py +++ b/selfdrive/ui/onroad/driver_state.py @@ -1,10 +1,13 @@ import numpy as np import pyray as rl +from cereal import log from dataclasses import dataclass from openpilot.selfdrive.ui.ui_state import ui_state, UI_BORDER_SIZE from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.widgets import Widget +AlertSize = log.SelfdriveState.AlertSize + # Default 3D coordinates for face keypoints as a NumPy array DEFAULT_FACE_KPTS_3D = np.array([ [-5.98, -51.20, 8.00], [-17.64, -49.14, 8.00], [-23.81, -46.40, 8.00], [-29.98, -40.91, 8.00], @@ -50,7 +53,6 @@ class DriverStateRenderer(Widget): self.is_active = False self.is_rhd = False self.dm_fade_state = 0.0 - self.last_rect: rl.Rectangle = rl.Rectangle(0, 0, 0, 0) self.driver_pose_vals = np.zeros(3, dtype=np.float32) self.driver_pose_diff = np.zeros(3, dtype=np.float32) self.driver_pose_sins = np.zeros(3, dtype=np.float32) @@ -75,8 +77,8 @@ class DriverStateRenderer(Widget): self.engaged_color = rl.Color(26, 242, 66, 255) self.disengaged_color = rl.Color(139, 139, 139, 255) - self.set_visible(lambda: (ui_state.sm.recv_frame['driverStateV2'] > ui_state.started_frame and - ui_state.sm.seen['driverMonitoringState'])) + self.set_visible(lambda: (ui_state.sm["selfdriveState"].alertSize == AlertSize.none and + ui_state.sm.recv_frame["driverStateV2"] > ui_state.started_frame)) def _render(self, rect): # Set opacity based on active state @@ -106,11 +108,7 @@ class DriverStateRenderer(Widget): def _update_state(self): """Update the driver monitoring state based on model data""" sm = ui_state.sm - if not sm.updated["driverMonitoringState"]: - if (self._rect.x != self.last_rect.x or self._rect.y != self.last_rect.y or - self._rect.width != self.last_rect.width or self._rect.height != self.last_rect.height): - self._pre_calculate_drawing_elements() - self.last_rect = self._rect + if not self.is_visible: return # Get monitoring state From 59bddfba8dca350d3fdc1d6fa389070d4ccc14a2 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 14 Oct 2025 21:24:36 -0700 Subject: [PATCH 171/910] raylib: rounded onroad corners (#36348) * rounded corners * use scissor * 0.1 * middle * don't trust chatter * round * clean p * cleanup * rev --- selfdrive/ui/onroad/augmented_road_view.py | 31 +++++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/selfdrive/ui/onroad/augmented_road_view.py b/selfdrive/ui/onroad/augmented_road_view.py index bffa30ad2..0e0adceaf 100644 --- a/selfdrive/ui/onroad/augmented_road_view.py +++ b/selfdrive/ui/onroad/augmented_road_view.py @@ -20,9 +20,9 @@ WIDE_CAM = VisionStreamType.VISION_STREAM_WIDE_ROAD DEFAULT_DEVICE_CAMERA = DEVICE_CAMERAS["tici", "ar0231"] BORDER_COLORS = { - UIStatus.DISENGAGED: rl.Color(0x17, 0x33, 0x49, 0xC8), # Blue for disengaged state - UIStatus.OVERRIDE: rl.Color(0x91, 0x9B, 0x95, 0xF1), # Gray for override state - UIStatus.ENGAGED: rl.Color(0x17, 0x86, 0x44, 0xF1), # Green for engaged state + UIStatus.DISENGAGED: rl.Color(0x12, 0x28, 0x39, 0xFF), # Blue for disengaged state + UIStatus.OVERRIDE: rl.Color(0x89, 0x92, 0x8D, 0xFF), # Gray for override state + UIStatus.ENGAGED: rl.Color(0x16, 0x7F, 0x40, 0xFF), # Green for engaged state } WIDE_CAM_MAX_SPEED = 10.0 # m/s (22 mph) @@ -71,9 +71,6 @@ class AugmentedRoadView(CameraView): rect.height - 2 * UI_BORDER_SIZE, ) - # Draw colored border based on driving state - self._draw_border(rect) - # Enable scissor mode to clip all rendering within content rectangle boundaries # This creates a rendering viewport that prevents graphics from drawing outside the border rl.begin_scissor_mode( @@ -98,6 +95,9 @@ class AugmentedRoadView(CameraView): # End clipping region rl.end_scissor_mode() + # Draw colored border based on driving state + self._draw_border(rect) + # publish uiDebug msg = messaging.new_message('uiDebug') msg.uiDebug.drawTimeMillis = (time.monotonic() - start_draw) * 1000 @@ -112,8 +112,25 @@ class AugmentedRoadView(CameraView): pass def _draw_border(self, rect: rl.Rectangle): + rl.begin_scissor_mode(int(rect.x), int(rect.y), int(rect.width), int(rect.height)) + border_roundness = 0.15 border_color = BORDER_COLORS.get(ui_state.status, BORDER_COLORS[UIStatus.DISENGAGED]) - rl.draw_rectangle_lines_ex(rect, UI_BORDER_SIZE, border_color) + border_rect = rl.Rectangle(rect.x + UI_BORDER_SIZE, rect.y + UI_BORDER_SIZE, + rect.width - 2 * UI_BORDER_SIZE, rect.height - 2 * UI_BORDER_SIZE) + rl.draw_rectangle_rounded_lines_ex(border_rect, border_roundness, 10, UI_BORDER_SIZE, border_color) + + # black bg around colored border + black_bg_thickness = UI_BORDER_SIZE + black_bg_rect = rl.Rectangle( + border_rect.x - UI_BORDER_SIZE, + border_rect.y - UI_BORDER_SIZE, + border_rect.width + 2 * UI_BORDER_SIZE, + border_rect.height + 2 * UI_BORDER_SIZE, + ) + edge_offset = (black_bg_rect.height - border_rect.height) / 2 # distance between rect edges + roundness_out = (border_roundness * border_rect.height + 2 * edge_offset) / max(1.0, black_bg_rect.height) + rl.draw_rectangle_rounded_lines_ex(black_bg_rect, roundness_out, 10, black_bg_thickness, rl.BLACK) + rl.end_scissor_mode() def _switch_stream_if_needed(self, sm): if sm['selfdriveState'].experimentalMode and WIDE_CAM in self.available_streams: From c44548ba0f0464aad0bd9b52a067829e95d4d37a Mon Sep 17 00:00:00 2001 From: ZwX1616 Date: Tue, 14 Oct 2025 21:27:52 -0700 Subject: [PATCH 172/910] camerad: make wide brightness more consistent with road (#36355) align --- system/camerad/cameras/camera_qcom2.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/camerad/cameras/camera_qcom2.cc b/system/camerad/cameras/camera_qcom2.cc index 85f358977..f2a064c60 100644 --- a/system/camerad/cameras/camera_qcom2.cc +++ b/system/camerad/cameras/camera_qcom2.cc @@ -89,7 +89,7 @@ void CameraState::set_exposure_rect() { // set areas for each camera, shouldn't be changed std::vector> ae_targets = { // (Rect, F) - std::make_pair((Rect){96, 250, 1734, 524}, 567.0), // wide + std::make_pair((Rect){96, 400, 1734, 524}, 567.0), // wide std::make_pair((Rect){96, 160, 1734, 986}, 2648.0), // road std::make_pair((Rect){96, 242, 1736, 906}, 567.0) // driver }; From 0c64818f525fb297417f87a7ed4a4ce60351163e Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Tue, 14 Oct 2025 23:36:04 -0500 Subject: [PATCH 173/910] Add screenshot for advanced network settings (#36351) add screenshot for advanced network settings --- selfdrive/ui/tests/test_ui/raylib_screenshots.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py index a65f198ad..ea3f9c480 100755 --- a/selfdrive/ui/tests/test_ui/raylib_screenshots.py +++ b/selfdrive/ui/tests/test_ui/raylib_screenshots.py @@ -53,6 +53,11 @@ def setup_settings_network(click, pm: PubMaster): click(278, 450) +def setup_settings_network_advanced(click, pm: PubMaster): + setup_settings_network(click, pm) + click(1880, 100) + + def setup_settings_toggles(click, pm: PubMaster): setup_settings(click, pm) click(278, 600) @@ -128,6 +133,7 @@ CASES = { "homescreen": setup_homescreen, "settings_device": setup_settings, "settings_network": setup_settings_network, + "settings_network_advanced": setup_settings_network_advanced, "settings_toggles": setup_settings_toggles, "settings_software": setup_settings_software, "settings_software_download": setup_settings_software_download, From f290fb1e05f590f8d89f62c26b631b8e7d70a6be Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Wed, 15 Oct 2025 00:15:31 -0500 Subject: [PATCH 174/910] keyboard: fix double space (#36345) * support multiple characters added add cursor position * fix * remove double space * Revert "fix" This reverts commit c938a52995b6f5343b461f408af5838b78f453d2. * Revert "support multiple characters added add cursor position" This reverts commit d8225a768686a88f2bdaabae6d2a57c541ac7f77. --- system/ui/widgets/keyboard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/ui/widgets/keyboard.py b/system/ui/widgets/keyboard.py index 70f06f6b9..85bc477bf 100644 --- a/system/ui/widgets/keyboard.py +++ b/system/ui/widgets/keyboard.py @@ -19,7 +19,7 @@ DELETE_REPEAT_INTERVAL = 0.07 CONTENT_MARGIN = 50 BACKSPACE_KEY = "<-" ENTER_KEY = "->" -SPACE_KEY = " " +SPACE_KEY = " " SHIFT_INACTIVE_KEY = "SHIFT_OFF" SHIFT_ACTIVE_KEY = "SHIFT_ON" CAPS_LOCK_KEY = "CAPS" From 3553a754a46c1b99c9f810545471c1421e44ab65 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 14 Oct 2025 22:45:33 -0700 Subject: [PATCH 175/910] Fix vendored emoji font (#36357) * add font * use it * rm old one --- selfdrive/assets/fonts/NotoColorEmoji-Regular.ttf | 3 --- selfdrive/assets/fonts/NotoColorEmoji.ttf | 3 +++ system/ui/lib/emoji.py | 4 +++- 3 files changed, 6 insertions(+), 4 deletions(-) delete mode 100644 selfdrive/assets/fonts/NotoColorEmoji-Regular.ttf create mode 100644 selfdrive/assets/fonts/NotoColorEmoji.ttf diff --git a/selfdrive/assets/fonts/NotoColorEmoji-Regular.ttf b/selfdrive/assets/fonts/NotoColorEmoji-Regular.ttf deleted file mode 100644 index 2579d30f6..000000000 --- a/selfdrive/assets/fonts/NotoColorEmoji-Regular.ttf +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:69f216a4ec672bb910d652678301ffe3094c44e5d03276e794ef793d936a1f1d -size 25096376 diff --git a/selfdrive/assets/fonts/NotoColorEmoji.ttf b/selfdrive/assets/fonts/NotoColorEmoji.ttf new file mode 100644 index 000000000..778e821ce --- /dev/null +++ b/selfdrive/assets/fonts/NotoColorEmoji.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:93cdc4ee9aa40e2afceecc63da0ca05ec7aab4bec991ece51a6b52389f48a477 +size 10788068 diff --git a/system/ui/lib/emoji.py b/system/ui/lib/emoji.py index 28139158a..519f567d3 100644 --- a/system/ui/lib/emoji.py +++ b/system/ui/lib/emoji.py @@ -4,6 +4,8 @@ import re from PIL import Image, ImageDraw, ImageFont import pyray as rl +from openpilot.system.ui.lib.application import FONT_DIR + _cache: dict[str, rl.Texture] = {} EMOJI_REGEX = re.compile( @@ -37,7 +39,7 @@ def emoji_tex(emoji): if emoji not in _cache: img = Image.new("RGBA", (128, 128), (0, 0, 0, 0)) draw = ImageDraw.Draw(img) - font = ImageFont.truetype("NotoColorEmoji", 109) + font = ImageFont.truetype(FONT_DIR.joinpath("NotoColorEmoji.ttf"), 109) draw.text((0, 0), emoji, font=font, embedded_color=True) buffer = io.BytesIO() img.save(buffer, format="PNG") From 57223958b533f4f507b1851c50001896f00ac085 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 14 Oct 2025 23:56:15 -0700 Subject: [PATCH 176/910] raylib screenshots: add more homescreen states (#36356) * hmm can do yeidl approach * clean up * clean up * flip * and add paired + prime * sort and add update params * try * all should have branch name * test * clean up * add offroad alert to update screen --- .../ui/tests/test_ui/raylib_screenshots.py | 42 ++++++++++++------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py index ea3f9c480..3f3958f9d 100755 --- a/selfdrive/ui/tests/test_ui/raylib_screenshots.py +++ b/selfdrive/ui/tests/test_ui/raylib_screenshots.py @@ -31,15 +31,19 @@ OFFROAD_ALERTS = ['Offroad_IsTakingSnapshot'] def put_update_params(params: Params): params.put("UpdaterCurrentReleaseNotes", parse_release_notes(BASEDIR)) params.put("UpdaterNewReleaseNotes", parse_release_notes(BASEDIR)) - description = "0.10.1 / this-is-a-really-super-mega-long-branch-name / 7864838 / Oct 03" - params.put("UpdaterCurrentDescription", description) - params.put("UpdaterNewDescription", description) def setup_homescreen(click, pm: PubMaster): pass +def setup_homescreen_update_available(click, pm: PubMaster): + params = Params() + params.put_bool("UpdateAvailable", True) + put_update_params(params) + setup_offroad_alert(click, pm) + + def setup_settings(click, pm: PubMaster): click(100, 100) @@ -102,6 +106,7 @@ def setup_pair_device(click, pm: PubMaster): def setup_offroad_alert(click, pm: PubMaster): + put_update_params(Params()) set_offroad_alert("Offroad_TemperatureTooHigh", True, extra_text='99C') set_offroad_alert("Offroad_ExcessiveActuation", True, extra_text='longitudinal') for alert in OFFROAD_ALERTS: @@ -116,14 +121,6 @@ def setup_confirmation_dialog(click, pm: PubMaster): click(1985, 791) # reset calibration -def setup_homescreen_update_available(click, pm: PubMaster): - params = Params() - params.put_bool("UpdateAvailable", True) - put_update_params(params) - setup_settings(click, pm) - close_settings(click, pm) - - def setup_experimental_mode_description(click, pm: PubMaster): setup_settings_toggles(click, pm) click(1200, 280) # expand description for experimental mode @@ -131,6 +128,9 @@ def setup_experimental_mode_description(click, pm: PubMaster): CASES = { "homescreen": setup_homescreen, + "homescreen_paired": setup_homescreen, + "homescreen_prime": setup_homescreen, + "homescreen_update_available": setup_homescreen_update_available, "settings_device": setup_settings, "settings_network": setup_settings_network, "settings_network_advanced": setup_settings_network_advanced, @@ -143,7 +143,6 @@ CASES = { "keyboard": setup_keyboard, "pair_device": setup_pair_device, "offroad_alert": setup_offroad_alert, - "homescreen_update_available": setup_homescreen_update_available, "confirmation_dialog": setup_confirmation_dialog, "experimental_mode_description": setup_experimental_mode_description, } @@ -194,10 +193,21 @@ def create_screenshots(): SCREENSHOTS_DIR.mkdir(parents=True) t = TestUI() - with OpenpilotPrefix(): - params = Params() - params.put("DongleId", "123456789012345") - for name, setup in CASES.items(): + for name, setup in CASES.items(): + with OpenpilotPrefix(): + params = Params() + params.put("DongleId", "123456789012345") + + # Set branch name + description = "0.10.1 / this-is-a-really-super-mega-long-branch-name / 7864838 / Oct 03" + params.put("UpdaterCurrentDescription", description) + params.put("UpdaterNewDescription", description) + + if name == "homescreen_paired": + params.put("PrimeType", 0) # NONE + elif name == "homescreen_prime": + params.put("PrimeType", 2) # LITE + t.test_ui(name, setup) From 75858673c4215d3dfbb3f0190ec8dabb776bb948 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 15 Oct 2025 00:01:52 -0700 Subject: [PATCH 177/910] less rounded border --- selfdrive/ui/onroad/augmented_road_view.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/onroad/augmented_road_view.py b/selfdrive/ui/onroad/augmented_road_view.py index 0e0adceaf..661496a03 100644 --- a/selfdrive/ui/onroad/augmented_road_view.py +++ b/selfdrive/ui/onroad/augmented_road_view.py @@ -113,7 +113,7 @@ class AugmentedRoadView(CameraView): def _draw_border(self, rect: rl.Rectangle): rl.begin_scissor_mode(int(rect.x), int(rect.y), int(rect.width), int(rect.height)) - border_roundness = 0.15 + border_roundness = 0.12 border_color = BORDER_COLORS.get(ui_state.status, BORDER_COLORS[UIStatus.DISENGAGED]) border_rect = rl.Rectangle(rect.x + UI_BORDER_SIZE, rect.y + UI_BORDER_SIZE, rect.width - 2 * UI_BORDER_SIZE, rect.height - 2 * UI_BORDER_SIZE) From 3e566129905216ae533a374e4b3a78c11b8a4515 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 15 Oct 2025 00:06:04 -0700 Subject: [PATCH 178/910] raylib: fix emoji vertical centering (#36358) * space * font scale * fix centering --- selfdrive/ui/widgets/setup.py | 4 ++-- system/ui/widgets/label.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/selfdrive/ui/widgets/setup.py b/selfdrive/ui/widgets/setup.py index f786d1ab2..b0f122227 100644 --- a/selfdrive/ui/widgets/setup.py +++ b/selfdrive/ui/widgets/setup.py @@ -16,7 +16,7 @@ class SetupWidget(Widget): self._pair_device_btn = Button("Pair device", self._show_pairing, button_style=ButtonStyle.PRIMARY) self._open_settings_btn = Button("Open", lambda: self._open_settings_callback() if self._open_settings_callback else None, button_style=ButtonStyle.PRIMARY) - self._firehose_label = Label("🔥Firehose Mode 🔥", font_weight=FontWeight.MEDIUM, font_size=64) + self._firehose_label = Label("🔥 Firehose Mode 🔥", font_weight=FontWeight.MEDIUM, font_size=64) def set_open_settings_callback(self, callback): self._open_settings_callback = callback @@ -65,7 +65,7 @@ class SetupWidget(Widget): # Title with fire emojis # TODO: fix Label centering with emojis - self._firehose_label.render(rl.Rectangle(x - 40, y, w, 64)) + self._firehose_label.render(rl.Rectangle(x - 48, y, w, 64)) y += 64 + spacing # Description diff --git a/system/ui/widgets/label.py b/system/ui/widgets/label.py index 33c4ef29d..65b579209 100644 --- a/system/ui/widgets/label.py +++ b/system/ui/widgets/label.py @@ -170,7 +170,7 @@ class Label(Widget): line_pos.x += width_before.x tex = emoji_tex(emoji) - rl.draw_texture_ex(tex, line_pos, 0.0, self._font_size / tex.height, self._text_color) + rl.draw_texture_ex(tex, line_pos, 0.0, self._font_size / tex.height * FONT_SCALE, self._text_color) line_pos.x += self._font_size * FONT_SCALE prev_index = end rl.draw_text_ex(self._font, text[prev_index:], line_pos, self._font_size, 0, self._text_color) From a2c4fe1c90b17476e4b2caa77214ad2c4b193493 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 15 Oct 2025 00:11:16 -0700 Subject: [PATCH 179/910] raylib: fix setup styles (#36322) * hardcoding is bad for you * do updater * reset * lint * duh! * fixup setup * fixup updater * unround --- system/ui/reset.py | 6 ++-- system/ui/setup.py | 59 +++++++++++++++++++++----------------- system/ui/updater.py | 6 ++-- system/ui/widgets/label.py | 4 +-- 4 files changed, 40 insertions(+), 35 deletions(-) diff --git a/system/ui/reset.py b/system/ui/reset.py index 85ce4a858..8f6466ce4 100755 --- a/system/ui/reset.py +++ b/system/ui/reset.py @@ -8,7 +8,7 @@ from enum import IntEnum import pyray as rl from openpilot.system.hardware import PC -from openpilot.system.ui.lib.application import gui_app, FontWeight +from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.button import Button, ButtonStyle from openpilot.system.ui.widgets.label import gui_label, gui_text_box @@ -70,10 +70,10 @@ class Reset(Widget): exit(0) def _render(self, rect: rl.Rectangle): - label_rect = rl.Rectangle(rect.x + 140, rect.y, rect.width - 280, 100) + label_rect = rl.Rectangle(rect.x + 140, rect.y, rect.width - 280, 100 * FONT_SCALE) gui_label(label_rect, "System Reset", 100, font_weight=FontWeight.BOLD) - text_rect = rl.Rectangle(rect.x + 140, rect.y + 140, rect.width - 280, rect.height - 90 - 100) + text_rect = rl.Rectangle(rect.x + 140, rect.y + 140, rect.width - 280, rect.height - 90 - 100 * FONT_SCALE) gui_text_box(text_rect, self._get_body_text(), 90) button_height = 160 diff --git a/system/ui/setup.py b/system/ui/setup.py index 99a3569fb..a7fa4369a 100755 --- a/system/ui/setup.py +++ b/system/ui/setup.py @@ -14,7 +14,7 @@ from cereal import log from openpilot.common.run import run_cmd from openpilot.system.hardware import HARDWARE from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel -from openpilot.system.ui.lib.application import gui_app, FontWeight +from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.button import Button, ButtonStyle, ButtonRadio from openpilot.system.ui.widgets.keyboard import Keyboard @@ -24,10 +24,10 @@ from openpilot.system.ui.widgets.network import WifiManagerUI, WifiManager NetworkType = log.DeviceState.NetworkType MARGIN = 50 -TITLE_FONT_SIZE = 116 +TITLE_FONT_SIZE = 90 TITLE_FONT_WEIGHT = FontWeight.MEDIUM NEXT_BUTTON_WIDTH = 310 -BODY_FONT_SIZE = 96 +BODY_FONT_SIZE = 80 BUTTON_HEIGHT = 160 BUTTON_SPACING = 50 @@ -48,6 +48,7 @@ cd /data/openpilot exec ./launch_openpilot.sh """ + class SetupState(IntEnum): LOW_VOLTAGE = 0 GETTING_STARTED = 1 @@ -100,7 +101,7 @@ class Setup(Widget): self._download_failed_reboot_button = Button("Reboot device", HARDWARE.reboot) self._download_failed_startover_button = Button("Start over", self._download_failed_startover_button_callback, button_style=ButtonStyle.PRIMARY) self._download_failed_title_label = Label("Download Failed", TITLE_FONT_SIZE, FontWeight.BOLD, TextAlignment.LEFT) - self._download_failed_url_label = Label("", 64, FontWeight.NORMAL, TextAlignment.LEFT) + self._download_failed_url_label = Label("", 52, FontWeight.NORMAL, TextAlignment.LEFT) self._download_failed_body_label = Label("", BODY_FONT_SIZE, text_alignment=TextAlignment.LEFT) self._network_setup_back_button = Button("Back", self._network_setup_back_button_callback) @@ -113,15 +114,16 @@ class Setup(Widget): button_style=ButtonStyle.PRIMARY) self._custom_software_warning_continue_button.set_enabled(False) self._custom_software_warning_back_button = Button("Back", self._custom_software_warning_back_button_callback) - self._custom_software_warning_title_label = Label("WARNING: Custom Software", 100, FontWeight.BOLD, TextAlignment.LEFT, text_color=rl.Color(255,89,79,255), + self._custom_software_warning_title_label = Label("WARNING: Custom Software", 81, FontWeight.BOLD, TextAlignment.LEFT, + text_color=rl.Color(255, 89, 79, 255), text_padding=60) self._custom_software_warning_body_label = Label("Use caution when installing third-party software.\n\n" - + "⚠️ It has not been tested by comma.\n\n" - + "⚠️ It may not comply with relevant safety standards.\n\n" - + "⚠️ It may cause damage to your device and/or vehicle.\n\n" - + "If you'd like to proceed, use https://flash.comma.ai " - + "to restore your device to a factory state later.", - 85, text_alignment=TextAlignment.LEFT, text_padding=60) + + "⚠️ It has not been tested by comma.\n\n" + + "⚠️ It may not comply with relevant safety standards.\n\n" + + "⚠️ It may cause damage to your device and/or vehicle.\n\n" + + "If you'd like to proceed, use https://flash.comma.ai " + + "to restore your device to a factory state later.", + 68, text_alignment=TextAlignment.LEFT, text_padding=60) self._custom_software_warning_body_scroll_panel = GuiScrollPanel() self._downloading_body_label = Label("Downloading...", TITLE_FONT_SIZE, FontWeight.MEDIUM) @@ -191,8 +193,8 @@ class Setup(Widget): def render_low_voltage(self, rect: rl.Rectangle): rl.draw_texture(self.warning, int(rect.x + 150), int(rect.y + 110), rl.WHITE) - self._low_voltage_title_label.render(rl.Rectangle(rect.x + 150, rect.y + 110 + 150 + 100, rect.width - 500 - 150, TITLE_FONT_SIZE)) - self._low_voltage_body_label.render(rl.Rectangle(rect.x + 150, rect.y + 110 + 150 + 150, rect.width - 500, BODY_FONT_SIZE * 3)) + self._low_voltage_title_label.render(rl.Rectangle(rect.x + 150, rect.y + 110 + 150 + 100, rect.width - 500 - 150, TITLE_FONT_SIZE * FONT_SCALE)) + self._low_voltage_body_label.render(rl.Rectangle(rect.x + 150, rect.y + 110 + 150 + 150, rect.width - 500, BODY_FONT_SIZE * FONT_SCALE * 3)) button_width = (rect.width - MARGIN * 3) / 2 button_y = rect.height - MARGIN - BUTTON_HEIGHT @@ -200,8 +202,9 @@ class Setup(Widget): self._low_voltage_continue_button.render(rl.Rectangle(rect.x + MARGIN * 2 + button_width, button_y, button_width, BUTTON_HEIGHT)) def render_getting_started(self, rect: rl.Rectangle): - self._getting_started_title_label.render(rl.Rectangle(rect.x + 165, rect.y + 280, rect.width - 265, TITLE_FONT_SIZE)) - self._getting_started_body_label.render(rl.Rectangle(rect.x + 165, rect.y + 280 + TITLE_FONT_SIZE, rect.width - 500, BODY_FONT_SIZE * 3)) + self._getting_started_title_label.render(rl.Rectangle(rect.x + 165, rect.y + 280, rect.width - 265, TITLE_FONT_SIZE * FONT_SCALE)) + self._getting_started_body_label.render(rl.Rectangle(rect.x + 165, rect.y + 280 + TITLE_FONT_SIZE * FONT_SCALE, rect.width - 500, + BODY_FONT_SIZE * FONT_SCALE * 3)) btn_rect = rl.Rectangle(rect.width - NEXT_BUTTON_WIDTH, 0, NEXT_BUTTON_WIDTH, rect.height) self._getting_started_button.render(btn_rect) @@ -233,10 +236,10 @@ class Setup(Widget): self.network_check_thread.join() def render_network_setup(self, rect: rl.Rectangle): - self._network_setup_title_label.render(rl.Rectangle(rect.x + MARGIN, rect.y + MARGIN, rect.width - MARGIN * 2, TITLE_FONT_SIZE)) + self._network_setup_title_label.render(rl.Rectangle(rect.x + MARGIN, rect.y + MARGIN, rect.width - MARGIN * 2, TITLE_FONT_SIZE * FONT_SCALE)) - 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) + wifi_rect = rl.Rectangle(rect.x + MARGIN, rect.y + TITLE_FONT_SIZE * FONT_SCALE + MARGIN + 25, rect.width - MARGIN * 2, + rect.height - TITLE_FONT_SIZE * FONT_SCALE - 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) @@ -254,21 +257,22 @@ class Setup(Widget): self._network_setup_continue_button.render(rl.Rectangle(rect.x + MARGIN + button_width + BUTTON_SPACING, button_y, button_width, BUTTON_HEIGHT)) def render_software_selection(self, rect: rl.Rectangle): - self._software_selection_title_label.render(rl.Rectangle(rect.x + MARGIN, rect.y + MARGIN, rect.width - MARGIN * 2, TITLE_FONT_SIZE)) + self._software_selection_title_label.render(rl.Rectangle(rect.x + MARGIN, rect.y + MARGIN, rect.width - MARGIN * 2, TITLE_FONT_SIZE * FONT_SCALE)) radio_height = 230 radio_spacing = 30 self._software_selection_continue_button.set_enabled(False) - openpilot_rect = rl.Rectangle(rect.x + MARGIN, rect.y + TITLE_FONT_SIZE + MARGIN * 2, rect.width - MARGIN * 2, radio_height) + openpilot_rect = rl.Rectangle(rect.x + MARGIN, rect.y + TITLE_FONT_SIZE * FONT_SCALE + MARGIN * 2, rect.width - MARGIN * 2, radio_height) self._software_selection_openpilot_button.render(openpilot_rect) if self._software_selection_openpilot_button.selected: self._software_selection_continue_button.set_enabled(True) self._software_selection_custom_software_button.selected = False - 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_rect = rl.Rectangle(rect.x + MARGIN, rect.y + TITLE_FONT_SIZE * FONT_SCALE + MARGIN * 2 + radio_height + radio_spacing, rect.width - MARGIN * 2, + radio_height) self._software_selection_custom_software_button.render(custom_rect) if self._software_selection_custom_software_button.selected: @@ -282,12 +286,13 @@ class Setup(Widget): self._software_selection_continue_button.render(rl.Rectangle(rect.x + MARGIN + button_width + BUTTON_SPACING, button_y, button_width, BUTTON_HEIGHT)) def render_downloading(self, rect: rl.Rectangle): - self._downloading_body_label.render(rl.Rectangle(rect.x, rect.y + rect.height / 2 - TITLE_FONT_SIZE / 2, rect.width, TITLE_FONT_SIZE)) + self._downloading_body_label.render(rl.Rectangle(rect.x, rect.y + rect.height / 2 - TITLE_FONT_SIZE * FONT_SCALE / 2, rect.width, + TITLE_FONT_SIZE * FONT_SCALE)) def render_download_failed(self, rect: rl.Rectangle): - self._download_failed_title_label.render(rl.Rectangle(rect.x + 117, rect.y + 185, rect.width - 117, TITLE_FONT_SIZE)) + self._download_failed_title_label.render(rl.Rectangle(rect.x + 117, rect.y + 185, rect.width - 117, TITLE_FONT_SIZE * FONT_SCALE)) self._download_failed_url_label.set_text(self.failed_url) - self._download_failed_url_label.render(rl.Rectangle(rect.x + 117, rect.y + 185 + TITLE_FONT_SIZE + 67, rect.width - 117 - 100, 64)) + self._download_failed_url_label.render(rl.Rectangle(rect.x + 117, rect.y + 185 + TITLE_FONT_SIZE * FONT_SCALE + 67, rect.width - 117 - 100, 64)) self._download_failed_body_label.set_text(self.failed_reason) self._download_failed_body_label.render(rl.Rectangle(rect.x + 117, rect.y, rect.width - 117 - 100, rect.height)) @@ -304,10 +309,10 @@ class Setup(Widget): button_width = (rect.width - MARGIN * 3) / 2 button_y = rect.height - MARGIN - BUTTON_HEIGHT - rl.begin_scissor_mode(int(rect.x), int(rect.y), int(rect.width), int(button_y - BODY_FONT_SIZE)) + rl.begin_scissor_mode(int(rect.x), int(rect.y), int(rect.width), int(button_y - BODY_FONT_SIZE * FONT_SCALE)) y_offset = rect.y + offset - self._custom_software_warning_title_label.render(rl.Rectangle(rect.x + 50, y_offset + 150, rect.width - 265, TITLE_FONT_SIZE)) - self._custom_software_warning_body_label.render(rl.Rectangle(rect.x + 50, y_offset + 200 , rect.width - 50, BODY_FONT_SIZE * 3)) + self._custom_software_warning_title_label.render(rl.Rectangle(rect.x + 50, y_offset + 150, rect.width - 265, TITLE_FONT_SIZE * FONT_SCALE)) + self._custom_software_warning_body_label.render(rl.Rectangle(rect.x + 50, y_offset + 200, rect.width - 50, BODY_FONT_SIZE * FONT_SCALE * 3)) rl.end_scissor_mode() self._custom_software_warning_back_button.render(rl.Rectangle(rect.x + MARGIN, button_y, button_width, BUTTON_HEIGHT)) diff --git a/system/ui/updater.py b/system/ui/updater.py index 38a93687e..5dd5a69c6 100755 --- a/system/ui/updater.py +++ b/system/ui/updater.py @@ -6,7 +6,7 @@ import pyray as rl from enum import IntEnum from openpilot.system.hardware import HARDWARE -from openpilot.system.ui.lib.application import gui_app, FontWeight +from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE from openpilot.system.ui.lib.wifi_manager import WifiManager from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.button import Button, ButtonStyle @@ -89,14 +89,14 @@ class Updater(Widget): def render_prompt_screen(self, rect: rl.Rectangle): # Title - title_rect = rl.Rectangle(MARGIN + 50, 250, rect.width - MARGIN * 2 - 100, TITLE_FONT_SIZE) + title_rect = rl.Rectangle(MARGIN + 50, 250, rect.width - MARGIN * 2 - 100, TITLE_FONT_SIZE * FONT_SCALE) 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_rect = rl.Rectangle(MARGIN + 50, 250 + TITLE_FONT_SIZE + 75, rect.width - MARGIN * 2 - 100, BODY_FONT_SIZE * 3) + desc_rect = rl.Rectangle(MARGIN + 50, 250 + TITLE_FONT_SIZE * FONT_SCALE + 75, rect.width - MARGIN * 2 - 100, BODY_FONT_SIZE * FONT_SCALE * 4) gui_text_box(desc_rect, desc_text, BODY_FONT_SIZE) # Buttons at the bottom diff --git a/system/ui/widgets/label.py b/system/ui/widgets/label.py index 65b579209..d995ec1b5 100644 --- a/system/ui/widgets/label.py +++ b/system/ui/widgets/label.py @@ -76,8 +76,8 @@ def gui_text_box( ): styles = [ (rl.GuiControl.DEFAULT, rl.GuiControlProperty.TEXT_COLOR_NORMAL, rl.color_to_int(color)), - (rl.GuiControl.DEFAULT, rl.GuiDefaultProperty.TEXT_SIZE, font_size), - (rl.GuiControl.DEFAULT, rl.GuiDefaultProperty.TEXT_LINE_SPACING, font_size), + (rl.GuiControl.DEFAULT, rl.GuiDefaultProperty.TEXT_SIZE, round(font_size * FONT_SCALE)), + (rl.GuiControl.DEFAULT, rl.GuiDefaultProperty.TEXT_LINE_SPACING, round(font_size * FONT_SCALE)), (rl.GuiControl.DEFAULT, rl.GuiControlProperty.TEXT_ALIGNMENT, alignment), (rl.GuiControl.DEFAULT, rl.GuiDefaultProperty.TEXT_ALIGNMENT_VERTICAL, alignment_vertical), (rl.GuiControl.DEFAULT, rl.GuiDefaultProperty.TEXT_WRAP_MODE, rl.GuiTextWrapMode.TEXT_WRAP_WORD) From 2fd4b53aaf058bb730b46f1c22d18dfe7f5c067c Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 15 Oct 2025 00:48:05 -0700 Subject: [PATCH 180/910] raylib: smooth path distance (#36278) * smooth max distance * junk * clean up * final * you can read * Update selfdrive/ui/onroad/model_renderer.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * true * fix --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- selfdrive/ui/onroad/model_renderer.py | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/selfdrive/ui/onroad/model_renderer.py b/selfdrive/ui/onroad/model_renderer.py index 3877da81c..125053f17 100644 --- a/selfdrive/ui/onroad/model_renderer.py +++ b/selfdrive/ui/onroad/model_renderer.py @@ -169,12 +169,12 @@ class ModelRenderer(Widget): # Update lane lines using raw points for i, lane_line in enumerate(self._lane_lines): lane_line.projected_points = self._map_line_to_polygon( - lane_line.raw_points, 0.025 * self._lane_line_probs[i], 0.0, max_idx + lane_line.raw_points, 0.025 * self._lane_line_probs[i], 0.0, max_idx, max_distance ) # Update road edges using raw points for road_edge in self._road_edges: - road_edge.projected_points = self._map_line_to_polygon(road_edge.raw_points, 0.025, 0.0, max_idx) + road_edge.projected_points = self._map_line_to_polygon(road_edge.raw_points, 0.025, 0.0, max_idx, max_distance) # Update path using raw points if lead and lead.status: @@ -183,7 +183,7 @@ class ModelRenderer(Widget): max_idx = self._get_path_length_idx(path_x_array, max_distance) self._path.projected_points = self._map_line_to_polygon( - self._path.raw_points, 0.9, self._path_offset_z, max_idx, allow_invert=False + self._path.raw_points, 0.9, self._path_offset_z, max_idx, max_distance, allow_invert=False ) self._update_experimental_gradient() @@ -307,11 +307,11 @@ class ModelRenderer(Widget): rl.draw_triangle_fan(lead.chevron, len(lead.chevron), rl.Color(201, 34, 49, lead.fill_alpha)) @staticmethod - def _get_path_length_idx(pos_x_array: np.ndarray, path_height: float) -> int: - """Get the index corresponding to the given path height""" + def _get_path_length_idx(pos_x_array: np.ndarray, path_distance: float) -> int: + """Get the index corresponding to the given path distance""" if len(pos_x_array) == 0: return 0 - indices = np.where(pos_x_array <= path_height)[0] + indices = np.where(pos_x_array <= path_distance)[0] return indices[-1] if indices.size > 0 else 0 def _map_to_screen(self, in_x, in_y, in_z): @@ -330,13 +330,24 @@ class ModelRenderer(Widget): return (x, y) - def _map_line_to_polygon(self, line: np.ndarray, y_off: float, z_off: float, max_idx: int, allow_invert: bool = True) -> np.ndarray: + def _map_line_to_polygon(self, line: np.ndarray, y_off: float, z_off: float, max_idx: int, max_distance: float, allow_invert: bool = True) -> np.ndarray: """Convert 3D line to 2D polygon for rendering.""" if line.shape[0] == 0: return np.empty((0, 2), dtype=np.float32) # Slice points and filter non-negative x-coordinates points = line[:max_idx + 1] + + # Interpolate around max_idx so path end is smooth (max_distance is always >= p0.x) + if 0 < max_idx < line.shape[0] - 1: + p0 = line[max_idx] + p1 = line[max_idx + 1] + x0, x1 = p0[0], p1[0] + interp_y = np.interp(max_distance, [x0, x1], [p0[1], p1[1]]) + interp_z = np.interp(max_distance, [x0, x1], [p0[2], p1[2]]) + interp_point = np.array([max_distance, interp_y, interp_z], dtype=points.dtype) + points = np.concatenate((points, interp_point[None, :]), axis=0) + points = points[points[:, 0] >= 0] if points.shape[0] == 0: return np.empty((0, 2), dtype=np.float32) From ec33519dc749290d7b9d75af46708ee66ca255b7 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 15 Oct 2025 01:38:00 -0700 Subject: [PATCH 181/910] raylib: revert 0 button padding (#36360) * back to 20 * here only --- system/ui/widgets/button.py | 2 +- system/ui/widgets/list_view.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/system/ui/widgets/button.py b/system/ui/widgets/button.py index ef28df811..141f682db 100644 --- a/system/ui/widgets/button.py +++ b/system/ui/widgets/button.py @@ -86,7 +86,7 @@ class Button(Widget): button_style: ButtonStyle = ButtonStyle.NORMAL, border_radius: int = 10, text_alignment: TextAlignment = TextAlignment.CENTER, - text_padding: int = 0, + text_padding: int = 20, icon=None, multi_touch: bool = False, ): diff --git a/system/ui/widgets/list_view.py b/system/ui/widgets/list_view.py index aaa67472d..1f4f69a6b 100644 --- a/system/ui/widgets/list_view.py +++ b/system/ui/widgets/list_view.py @@ -95,6 +95,7 @@ class ButtonAction(ItemAction): button_style=ButtonStyle.LIST_ACTION, border_radius=BUTTON_BORDER_RADIUS, click_callback=pressed, + text_padding=0, ) self.set_enabled(enabled) @@ -174,8 +175,8 @@ class DualButtonAction(ItemAction): super().__init__(width=0, enabled=enabled) # Width 0 means use full width self.left_text, self.right_text = left_text, right_text - self.left_button = Button(left_text, click_callback=left_callback, button_style=ButtonStyle.LIST_ACTION) - self.right_button = Button(right_text, click_callback=right_callback, button_style=ButtonStyle.DANGER) + self.left_button = Button(left_text, click_callback=left_callback, button_style=ButtonStyle.LIST_ACTION, text_padding=0) + self.right_button = Button(right_text, click_callback=right_callback, button_style=ButtonStyle.DANGER, text_padding=0) def set_touch_valid_callback(self, touch_callback: Callable[[], bool]) -> None: super().set_touch_valid_callback(touch_callback) From 530ad2925df5f29cb213a2907ae5841ac6400042 Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Wed, 15 Oct 2025 17:03:49 -0700 Subject: [PATCH 182/910] ui: clean raylib even on SIGINT (#36368) * fix * keep * fix --- system/ui/lib/application.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 4a62c996c..ff7163793 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -2,6 +2,8 @@ import atexit import cffi import os import time +import signal +import sys import pyray as rl import threading from collections.abc import Callable @@ -154,7 +156,11 @@ class GuiApplication: self._window_close_requested = True def init_window(self, title: str, fps: int = _DEFAULT_FPS): - atexit.register(self.close) # Automatically call close() on exit + def _close(sig, frame): + self.close() + sys.exit(0) + signal.signal(signal.SIGINT, _close) + atexit.register(self.close) HARDWARE.set_display_power(True) HARDWARE.set_screen_brightness(65) From 36d77debd04587ee3c9db838cd6c33540ba8a5ac Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 15 Oct 2025 19:32:13 -0700 Subject: [PATCH 183/910] raylib: remove redundant text center enum (#36372) * rm * type * fix * fix --- selfdrive/ui/layouts/onboarding.py | 8 ++++---- system/ui/setup.py | 25 +++++++++++++------------ system/ui/widgets/button.py | 6 +++--- system/ui/widgets/keyboard.py | 6 +++--- system/ui/widgets/label.py | 23 +++++++++-------------- system/ui/widgets/network.py | 6 +++--- system/ui/widgets/option_dialog.py | 4 ++-- 7 files changed, 37 insertions(+), 41 deletions(-) diff --git a/selfdrive/ui/layouts/onboarding.py b/selfdrive/ui/layouts/onboarding.py index f4ab1a05c..a817fc53a 100644 --- a/selfdrive/ui/layouts/onboarding.py +++ b/selfdrive/ui/layouts/onboarding.py @@ -8,7 +8,7 @@ from openpilot.common.basedir import BASEDIR from openpilot.system.ui.lib.application import FontWeight, gui_app from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.button import Button, ButtonStyle -from openpilot.system.ui.widgets.label import Label, TextAlignment +from openpilot.system.ui.widgets.label import Label from openpilot.selfdrive.ui.ui_state import ui_state DEBUG = False @@ -107,9 +107,9 @@ class TermsPage(Widget): self._on_accept = on_accept self._on_decline = on_decline - self._title = Label("Welcome to openpilot", font_size=90, font_weight=FontWeight.BOLD, text_alignment=TextAlignment.LEFT) + self._title = Label("Welcome to openpilot", font_size=90, font_weight=FontWeight.BOLD, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT) self._desc = Label("You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing.", - font_size=90, font_weight=FontWeight.MEDIUM, text_alignment=TextAlignment.LEFT) + font_size=90, font_weight=FontWeight.MEDIUM, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT) self._decline_btn = Button("Decline", click_callback=on_decline) self._accept_btn = Button("Agree", button_style=ButtonStyle.PRIMARY, click_callback=on_accept) @@ -142,7 +142,7 @@ class DeclinePage(Widget): def __init__(self, back_callback=None): super().__init__() self._text = Label("You must accept the Terms and Conditions in order to use openpilot.", - font_size=90, font_weight=FontWeight.MEDIUM, text_alignment=TextAlignment.LEFT) + font_size=90, font_weight=FontWeight.MEDIUM, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT) self._back_btn = Button("Back", click_callback=back_callback) self._uninstall_btn = Button("Decline, uninstall openpilot", button_style=ButtonStyle.DANGER, click_callback=self._on_uninstall_clicked) diff --git a/system/ui/setup.py b/system/ui/setup.py index a7fa4369a..b41e4e7cd 100755 --- a/system/ui/setup.py +++ b/system/ui/setup.py @@ -18,7 +18,7 @@ from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.button import Button, ButtonStyle, ButtonRadio from openpilot.system.ui.widgets.keyboard import Keyboard -from openpilot.system.ui.widgets.label import Label, TextAlignment +from openpilot.system.ui.widgets.label import Label from openpilot.system.ui.widgets.network import WifiManagerUI, WifiManager NetworkType = log.DeviceState.NetworkType @@ -79,16 +79,17 @@ class Setup(Widget): self.warning = gui_app.texture("icons/warning.png", 150, 150) self.checkmark = gui_app.texture("icons/circled_check.png", 100, 100) - self._low_voltage_title_label = Label("WARNING: Low Voltage", TITLE_FONT_SIZE, FontWeight.MEDIUM, TextAlignment.LEFT, text_color=rl.Color(255, 89, 79, 255)) + self._low_voltage_title_label = Label("WARNING: Low Voltage", TITLE_FONT_SIZE, FontWeight.MEDIUM, rl.GuiTextAlignment.TEXT_ALIGN_LEFT, + text_color=rl.Color(255, 89, 79, 255)) self._low_voltage_body_label = Label("Power your device in a car with a harness or proceed at your own risk.", BODY_FONT_SIZE, - text_alignment=TextAlignment.LEFT) + text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT) self._low_voltage_continue_button = Button("Continue", self._low_voltage_continue_button_callback) self._low_voltage_poweroff_button = Button("Power Off", HARDWARE.shutdown) self._getting_started_button = Button("", self._getting_started_button_callback, button_style=ButtonStyle.PRIMARY, border_radius=0) - self._getting_started_title_label = Label("Getting Started", TITLE_FONT_SIZE, FontWeight.BOLD, TextAlignment.LEFT) + self._getting_started_title_label = Label("Getting Started", TITLE_FONT_SIZE, FontWeight.BOLD, rl.GuiTextAlignment.TEXT_ALIGN_LEFT) self._getting_started_body_label = Label("Before we get on the road, let's finish installation and cover some details.", - BODY_FONT_SIZE, text_alignment=TextAlignment.LEFT) + BODY_FONT_SIZE, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT) self._software_selection_openpilot_button = ButtonRadio("openpilot", self.checkmark, font_size=BODY_FONT_SIZE, text_padding=80) self._software_selection_custom_software_button = ButtonRadio("Custom Software", self.checkmark, font_size=BODY_FONT_SIZE, text_padding=80) @@ -96,25 +97,25 @@ class Setup(Widget): button_style=ButtonStyle.PRIMARY) self._software_selection_continue_button.set_enabled(False) self._software_selection_back_button = Button("Back", self._software_selection_back_button_callback) - self._software_selection_title_label = Label("Choose Software to Use", TITLE_FONT_SIZE, FontWeight.BOLD, TextAlignment.LEFT) + self._software_selection_title_label = Label("Choose Software to Use", TITLE_FONT_SIZE, FontWeight.BOLD, rl.GuiTextAlignment.TEXT_ALIGN_LEFT) self._download_failed_reboot_button = Button("Reboot device", HARDWARE.reboot) self._download_failed_startover_button = Button("Start over", self._download_failed_startover_button_callback, button_style=ButtonStyle.PRIMARY) - self._download_failed_title_label = Label("Download Failed", TITLE_FONT_SIZE, FontWeight.BOLD, TextAlignment.LEFT) - self._download_failed_url_label = Label("", 52, FontWeight.NORMAL, TextAlignment.LEFT) - self._download_failed_body_label = Label("", BODY_FONT_SIZE, text_alignment=TextAlignment.LEFT) + self._download_failed_title_label = Label("Download Failed", TITLE_FONT_SIZE, FontWeight.BOLD, rl.GuiTextAlignment.TEXT_ALIGN_LEFT) + self._download_failed_url_label = Label("", 52, FontWeight.NORMAL, rl.GuiTextAlignment.TEXT_ALIGN_LEFT) + self._download_failed_body_label = Label("", BODY_FONT_SIZE, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT) self._network_setup_back_button = Button("Back", self._network_setup_back_button_callback) self._network_setup_continue_button = Button("Waiting for internet", self._network_setup_continue_button_callback, button_style=ButtonStyle.PRIMARY) self._network_setup_continue_button.set_enabled(False) - self._network_setup_title_label = Label("Connect to Wi-Fi", TITLE_FONT_SIZE, FontWeight.BOLD, TextAlignment.LEFT) + self._network_setup_title_label = Label("Connect to Wi-Fi", TITLE_FONT_SIZE, FontWeight.BOLD, rl.GuiTextAlignment.TEXT_ALIGN_LEFT) self._custom_software_warning_continue_button = Button("Scroll to continue", self._custom_software_warning_continue_button_callback, button_style=ButtonStyle.PRIMARY) self._custom_software_warning_continue_button.set_enabled(False) self._custom_software_warning_back_button = Button("Back", self._custom_software_warning_back_button_callback) - self._custom_software_warning_title_label = Label("WARNING: Custom Software", 81, FontWeight.BOLD, TextAlignment.LEFT, + self._custom_software_warning_title_label = Label("WARNING: Custom Software", 81, FontWeight.BOLD, rl.GuiTextAlignment.TEXT_ALIGN_LEFT, text_color=rl.Color(255, 89, 79, 255), text_padding=60) self._custom_software_warning_body_label = Label("Use caution when installing third-party software.\n\n" @@ -123,7 +124,7 @@ class Setup(Widget): + "⚠️ It may cause damage to your device and/or vehicle.\n\n" + "If you'd like to proceed, use https://flash.comma.ai " + "to restore your device to a factory state later.", - 68, text_alignment=TextAlignment.LEFT, text_padding=60) + 68, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT, text_padding=60) self._custom_software_warning_body_scroll_panel = GuiScrollPanel() self._downloading_body_label = Label("Downloading...", TITLE_FONT_SIZE, FontWeight.MEDIUM) diff --git a/system/ui/widgets/button.py b/system/ui/widgets/button.py index 141f682db..fa6e5ed7d 100644 --- a/system/ui/widgets/button.py +++ b/system/ui/widgets/button.py @@ -5,7 +5,7 @@ import pyray as rl from openpilot.system.ui.lib.application import FontWeight, MousePos from openpilot.system.ui.widgets import Widget -from openpilot.system.ui.widgets.label import TextAlignment, Label +from openpilot.system.ui.widgets.label import Label class ButtonStyle(IntEnum): @@ -85,7 +85,7 @@ class Button(Widget): font_weight: FontWeight = FontWeight.MEDIUM, button_style: ButtonStyle = ButtonStyle.NORMAL, border_radius: int = 10, - text_alignment: TextAlignment = TextAlignment.CENTER, + text_alignment: int = rl.GuiTextAlignment.TEXT_ALIGN_CENTER, text_padding: int = 20, icon=None, multi_touch: bool = False, @@ -137,7 +137,7 @@ class ButtonRadio(Button): icon, click_callback: Callable[[], None] | None = None, font_size: int = DEFAULT_BUTTON_FONT_SIZE, - text_alignment: TextAlignment = TextAlignment.LEFT, + text_alignment: int = rl.GuiTextAlignment.TEXT_ALIGN_LEFT, border_radius: int = 10, text_padding: int = 20, ): diff --git a/system/ui/widgets/keyboard.py b/system/ui/widgets/keyboard.py index 85bc477bf..69aab81bc 100644 --- a/system/ui/widgets/keyboard.py +++ b/system/ui/widgets/keyboard.py @@ -8,7 +8,7 @@ from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.button import ButtonStyle, Button from openpilot.system.ui.widgets.inputbox import InputBox -from openpilot.system.ui.widgets.label import Label, TextAlignment +from openpilot.system.ui.widgets.label import Label KEY_FONT_SIZE = 96 DOUBLE_CLICK_THRESHOLD = 0.5 # seconds @@ -62,8 +62,8 @@ class Keyboard(Widget): self._layout_name: Literal["lowercase", "uppercase", "numbers", "specials"] = "lowercase" self._caps_lock = False self._last_shift_press_time = 0 - self._title = Label("", 90, FontWeight.BOLD, TextAlignment.LEFT) - self._sub_title = Label("", 55, FontWeight.NORMAL, TextAlignment.LEFT) + self._title = Label("", 90, FontWeight.BOLD, rl.GuiTextAlignment.TEXT_ALIGN_LEFT) + self._sub_title = Label("", 55, FontWeight.NORMAL, rl.GuiTextAlignment.TEXT_ALIGN_LEFT) self._max_text_size = max_text_size self._min_text_size = min_text_size diff --git a/system/ui/widgets/label.py b/system/ui/widgets/label.py index d995ec1b5..a3cabcac3 100644 --- a/system/ui/widgets/label.py +++ b/system/ui/widgets/label.py @@ -1,6 +1,5 @@ -from enum import IntEnum from itertools import zip_longest - +from typing import Union import pyray as rl from openpilot.system.ui.lib.application import gui_app, FontWeight, DEFAULT_TEXT_SIZE, DEFAULT_TEXT_COLOR, FONT_SCALE @@ -12,10 +11,6 @@ from openpilot.system.ui.widgets import Widget ICON_PADDING = 15 -class TextAlignment(IntEnum): - LEFT = 0 - CENTER = 1 - RIGHT = 2 # TODO: This should be a Widget class def gui_label( @@ -98,10 +93,10 @@ class Label(Widget): text: str, font_size: int = DEFAULT_TEXT_SIZE, font_weight: FontWeight = FontWeight.NORMAL, - text_alignment: TextAlignment = TextAlignment.CENTER, + text_alignment: int = rl.GuiTextAlignment.TEXT_ALIGN_CENTER, text_padding: int = 20, text_color: rl.Color = DEFAULT_TEXT_COLOR, - icon = None, + icon: Union[rl.Texture, None] = None, # noqa: UP007 ): super().__init__() @@ -127,7 +122,7 @@ class Label(Widget): def _update_text(self, text): self._emojis = [] self._text_size = [] - self._text = wrap_text(self._font, text, self._font_size, self._rect.width - (self._text_padding*2)) + self._text = wrap_text(self._font, text, self._font_size, self._rect.width - (self._text_padding * 2)) for t in self._text: self._emojis.append(find_emoji(t)) self._text_size.append(measure_text_cached(self._font, t, self._font_size)) @@ -140,10 +135,10 @@ class Label(Widget): if self._icon: icon_y = self._rect.y + (self._rect.height - self._icon.height) / 2 if text: - if self._text_alignment == TextAlignment.LEFT: + if self._text_alignment == rl.GuiTextAlignment.TEXT_ALIGN_LEFT: icon_x = self._rect.x + self._text_padding text_pos.x = self._icon.width + ICON_PADDING - elif self._text_alignment == TextAlignment.CENTER: + elif self._text_alignment == rl.GuiTextAlignment.TEXT_ALIGN_CENTER: total_width = self._icon.width + ICON_PADDING + text_size.x icon_x = self._rect.x + (self._rect.width - total_width) / 2 text_pos.x = self._icon.width + ICON_PADDING @@ -155,11 +150,11 @@ class Label(Widget): for text, text_size, emojis in zip_longest(self._text, self._text_size, self._emojis, fillvalue=[]): line_pos = rl.Vector2(text_pos.x, text_pos.y) - if self._text_alignment == TextAlignment.LEFT: + if self._text_alignment == rl.GuiTextAlignment.TEXT_ALIGN_LEFT: line_pos.x += self._rect.x + self._text_padding - elif self._text_alignment == TextAlignment.CENTER: + elif self._text_alignment == rl.GuiTextAlignment.TEXT_ALIGN_CENTER: line_pos.x += self._rect.x + (self._rect.width - text_size.x) // 2 - elif self._text_alignment == TextAlignment.RIGHT: + elif self._text_alignment == rl.GuiTextAlignment.TEXT_ALIGN_RIGHT: line_pos.x += self._rect.x + self._rect.width - text_size.x - self._text_padding prev_index = 0 diff --git a/system/ui/widgets/network.py b/system/ui/widgets/network.py index a50881c8c..3bbf2e640 100644 --- a/system/ui/widgets/network.py +++ b/system/ui/widgets/network.py @@ -10,7 +10,7 @@ from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.button import ButtonStyle, Button from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog from openpilot.system.ui.widgets.keyboard import Keyboard -from openpilot.system.ui.widgets.label import TextAlignment, gui_label +from openpilot.system.ui.widgets.label import gui_label from openpilot.system.ui.widgets.scroller import Scroller from openpilot.system.ui.widgets.list_view import ButtonAction, ListItem, MultipleButtonAction, ToggleAction, button_item, text_item @@ -442,8 +442,8 @@ class WifiManagerUI(Widget): def _on_network_updated(self, networks: list[Network]): self._networks = networks for n in self._networks: - self._networks_buttons[n.ssid] = Button(n.ssid, partial(self._networks_buttons_callback, n), font_size=55, text_alignment=TextAlignment.LEFT, - button_style=ButtonStyle.TRANSPARENT_WHITE_TEXT) + self._networks_buttons[n.ssid] = Button(n.ssid, partial(self._networks_buttons_callback, n), font_size=55, + text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT, button_style=ButtonStyle.TRANSPARENT_WHITE_TEXT) self._networks_buttons[n.ssid].set_touch_valid_callback(lambda: self.scroll_panel.is_touch_valid()) self._forget_networks_buttons[n.ssid] = Button("Forget", partial(self._forget_networks_buttons_callback, n), button_style=ButtonStyle.FORGET_WIFI, font_size=45) diff --git a/system/ui/widgets/option_dialog.py b/system/ui/widgets/option_dialog.py index 604cd59fd..813e9cca8 100644 --- a/system/ui/widgets/option_dialog.py +++ b/system/ui/widgets/option_dialog.py @@ -1,7 +1,7 @@ import pyray as rl from openpilot.system.ui.lib.application import FontWeight, gui_app from openpilot.system.ui.widgets import Widget -from openpilot.system.ui.widgets.button import Button, ButtonStyle, TextAlignment +from openpilot.system.ui.widgets.button import Button, ButtonStyle from openpilot.system.ui.widgets.label import gui_label from openpilot.system.ui.widgets.scroller import Scroller @@ -25,7 +25,7 @@ class MultiOptionDialog(Widget): # Create scroller with option buttons self.option_buttons = [Button(option, click_callback=lambda opt=option: self._on_option_clicked(opt), - text_alignment=TextAlignment.LEFT, button_style=ButtonStyle.NORMAL) for option in options] + text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT, button_style=ButtonStyle.NORMAL) for option in options] self.scroller = Scroller(self.option_buttons, spacing=LIST_ITEM_SPACING) self.cancel_button = Button("Cancel", click_callback=lambda: gui_app.set_modal_overlay(None)) From cb612a4b9063546ac206421e2266fdbb019d1c55 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 15 Oct 2025 20:13:42 -0700 Subject: [PATCH 184/910] raylib: no Label padding (#36374) * none * try this * fix * stash * remove text padding from label, but keep for button * simpler is to default to 0 * fix --- system/ui/setup.py | 23 ++++++++++++----------- system/ui/widgets/keyboard.py | 4 ++-- system/ui/widgets/label.py | 2 +- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/system/ui/setup.py b/system/ui/setup.py index b41e4e7cd..e362703cd 100755 --- a/system/ui/setup.py +++ b/system/ui/setup.py @@ -80,16 +80,16 @@ class Setup(Widget): self.checkmark = gui_app.texture("icons/circled_check.png", 100, 100) self._low_voltage_title_label = Label("WARNING: Low Voltage", TITLE_FONT_SIZE, FontWeight.MEDIUM, rl.GuiTextAlignment.TEXT_ALIGN_LEFT, - text_color=rl.Color(255, 89, 79, 255)) + text_color=rl.Color(255, 89, 79, 255), text_padding=20) self._low_voltage_body_label = Label("Power your device in a car with a harness or proceed at your own risk.", BODY_FONT_SIZE, - text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT) + text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT, text_padding=20) self._low_voltage_continue_button = Button("Continue", self._low_voltage_continue_button_callback) self._low_voltage_poweroff_button = Button("Power Off", HARDWARE.shutdown) self._getting_started_button = Button("", self._getting_started_button_callback, button_style=ButtonStyle.PRIMARY, border_radius=0) - self._getting_started_title_label = Label("Getting Started", TITLE_FONT_SIZE, FontWeight.BOLD, rl.GuiTextAlignment.TEXT_ALIGN_LEFT) + self._getting_started_title_label = Label("Getting Started", TITLE_FONT_SIZE, FontWeight.BOLD, rl.GuiTextAlignment.TEXT_ALIGN_LEFT, text_padding=20) self._getting_started_body_label = Label("Before we get on the road, let's finish installation and cover some details.", - BODY_FONT_SIZE, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT) + BODY_FONT_SIZE, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT, text_padding=20) self._software_selection_openpilot_button = ButtonRadio("openpilot", self.checkmark, font_size=BODY_FONT_SIZE, text_padding=80) self._software_selection_custom_software_button = ButtonRadio("Custom Software", self.checkmark, font_size=BODY_FONT_SIZE, text_padding=80) @@ -97,19 +97,20 @@ class Setup(Widget): button_style=ButtonStyle.PRIMARY) self._software_selection_continue_button.set_enabled(False) self._software_selection_back_button = Button("Back", self._software_selection_back_button_callback) - self._software_selection_title_label = Label("Choose Software to Use", TITLE_FONT_SIZE, FontWeight.BOLD, rl.GuiTextAlignment.TEXT_ALIGN_LEFT) + self._software_selection_title_label = Label("Choose Software to Use", TITLE_FONT_SIZE, FontWeight.BOLD, rl.GuiTextAlignment.TEXT_ALIGN_LEFT, + text_padding=20) self._download_failed_reboot_button = Button("Reboot device", HARDWARE.reboot) self._download_failed_startover_button = Button("Start over", self._download_failed_startover_button_callback, button_style=ButtonStyle.PRIMARY) - self._download_failed_title_label = Label("Download Failed", TITLE_FONT_SIZE, FontWeight.BOLD, rl.GuiTextAlignment.TEXT_ALIGN_LEFT) - self._download_failed_url_label = Label("", 52, FontWeight.NORMAL, rl.GuiTextAlignment.TEXT_ALIGN_LEFT) - self._download_failed_body_label = Label("", BODY_FONT_SIZE, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT) + self._download_failed_title_label = Label("Download Failed", TITLE_FONT_SIZE, FontWeight.BOLD, rl.GuiTextAlignment.TEXT_ALIGN_LEFT, text_padding=20) + self._download_failed_url_label = Label("", 52, FontWeight.NORMAL, rl.GuiTextAlignment.TEXT_ALIGN_LEFT, text_padding=20) + self._download_failed_body_label = Label("", BODY_FONT_SIZE, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT, text_padding=20) self._network_setup_back_button = Button("Back", self._network_setup_back_button_callback) self._network_setup_continue_button = Button("Waiting for internet", self._network_setup_continue_button_callback, button_style=ButtonStyle.PRIMARY) self._network_setup_continue_button.set_enabled(False) - self._network_setup_title_label = Label("Connect to Wi-Fi", TITLE_FONT_SIZE, FontWeight.BOLD, rl.GuiTextAlignment.TEXT_ALIGN_LEFT) + self._network_setup_title_label = Label("Connect to Wi-Fi", TITLE_FONT_SIZE, FontWeight.BOLD, rl.GuiTextAlignment.TEXT_ALIGN_LEFT, text_padding=20) self._custom_software_warning_continue_button = Button("Scroll to continue", self._custom_software_warning_continue_button_callback, button_style=ButtonStyle.PRIMARY) @@ -127,7 +128,7 @@ class Setup(Widget): 68, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT, text_padding=60) self._custom_software_warning_body_scroll_panel = GuiScrollPanel() - self._downloading_body_label = Label("Downloading...", TITLE_FONT_SIZE, FontWeight.MEDIUM) + self._downloading_body_label = Label("Downloading...", TITLE_FONT_SIZE, FontWeight.MEDIUM, text_padding=20) try: with open("/sys/class/hwmon/hwmon1/in1_input") as f: @@ -335,7 +336,7 @@ class Setup(Widget): elif result == 0: self.state = SetupState.SOFTWARE_SELECTION - self.keyboard.reset() + self.keyboard.reset(min_text_size=1) self.keyboard.set_title("Enter URL", "for Custom Software") gui_app.set_modal_overlay(self.keyboard, callback=handle_keyboard_result) diff --git a/system/ui/widgets/keyboard.py b/system/ui/widgets/keyboard.py index 69aab81bc..6663c4e0e 100644 --- a/system/ui/widgets/keyboard.py +++ b/system/ui/widgets/keyboard.py @@ -62,8 +62,8 @@ class Keyboard(Widget): self._layout_name: Literal["lowercase", "uppercase", "numbers", "specials"] = "lowercase" self._caps_lock = False self._last_shift_press_time = 0 - self._title = Label("", 90, FontWeight.BOLD, rl.GuiTextAlignment.TEXT_ALIGN_LEFT) - self._sub_title = Label("", 55, FontWeight.NORMAL, rl.GuiTextAlignment.TEXT_ALIGN_LEFT) + self._title = Label("", 90, FontWeight.BOLD, rl.GuiTextAlignment.TEXT_ALIGN_LEFT, text_padding=20) + self._sub_title = Label("", 55, FontWeight.NORMAL, rl.GuiTextAlignment.TEXT_ALIGN_LEFT, text_padding=20) self._max_text_size = max_text_size self._min_text_size = min_text_size diff --git a/system/ui/widgets/label.py b/system/ui/widgets/label.py index a3cabcac3..6407d17d5 100644 --- a/system/ui/widgets/label.py +++ b/system/ui/widgets/label.py @@ -94,7 +94,7 @@ class Label(Widget): font_size: int = DEFAULT_TEXT_SIZE, font_weight: FontWeight = FontWeight.NORMAL, text_alignment: int = rl.GuiTextAlignment.TEXT_ALIGN_CENTER, - text_padding: int = 20, + text_padding: int = 0, text_color: rl.Color = DEFAULT_TEXT_COLOR, icon: Union[rl.Texture, None] = None, # noqa: UP007 ): From d9fc6c0086b577490e4548d2c4cd3fc38aa02e50 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 15 Oct 2025 21:11:00 -0700 Subject: [PATCH 185/910] raylib: small Label clean up (#36377) * do * clean up * text raw is the default! --- system/ui/widgets/label.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/system/ui/widgets/label.py b/system/ui/widgets/label.py index 6407d17d5..32cc7063d 100644 --- a/system/ui/widgets/label.py +++ b/system/ui/widgets/label.py @@ -107,34 +107,35 @@ class Label(Widget): self._text_padding = text_padding self._text_color = text_color self._icon = icon + + self._text = text self.set_text(text) def set_text(self, text): - self._text_raw = text - self._update_text(self._text_raw) + self._text = text + self._update_text(self._text) def set_text_color(self, color): self._text_color = color def _update_layout_rects(self): - self._update_text(self._text_raw) + self._update_text(self._text) def _update_text(self, text): self._emojis = [] self._text_size = [] - self._text = wrap_text(self._font, text, self._font_size, self._rect.width - (self._text_padding * 2)) - for t in self._text: + self._text_wrapped = wrap_text(self._font, text, self._font_size, round(self._rect.width - (self._text_padding * 2))) + for t in self._text_wrapped: self._emojis.append(find_emoji(t)) self._text_size.append(measure_text_cached(self._font, t, self._font_size)) def _render(self, _): - text = self._text[0] if self._text else None text_size = self._text_size[0] if self._text_size else rl.Vector2(0.0, 0.0) - text_pos = rl.Vector2(0, (self._rect.y + (self._rect.height - (text_size.y)) // 2)) + text_pos = rl.Vector2(self._rect.x, (self._rect.y + (self._rect.height - text_size.y) // 2)) if self._icon: icon_y = self._rect.y + (self._rect.height - self._icon.height) / 2 - if text: + if len(self._text_wrapped) > 0: if self._text_alignment == rl.GuiTextAlignment.TEXT_ALIGN_LEFT: icon_x = self._rect.x + self._text_padding text_pos.x = self._icon.width + ICON_PADDING @@ -148,14 +149,14 @@ class Label(Widget): icon_x = self._rect.x + (self._rect.width - self._icon.width) / 2 rl.draw_texture_v(self._icon, rl.Vector2(icon_x, icon_y), rl.WHITE) - for text, text_size, emojis in zip_longest(self._text, self._text_size, self._emojis, fillvalue=[]): + for text, text_size, emojis in zip_longest(self._text_wrapped, self._text_size, self._emojis, fillvalue=[]): line_pos = rl.Vector2(text_pos.x, text_pos.y) if self._text_alignment == rl.GuiTextAlignment.TEXT_ALIGN_LEFT: - line_pos.x += self._rect.x + self._text_padding + line_pos.x += self._text_padding elif self._text_alignment == rl.GuiTextAlignment.TEXT_ALIGN_CENTER: - line_pos.x += self._rect.x + (self._rect.width - text_size.x) // 2 + line_pos.x += (self._rect.width - text_size.x) // 2 elif self._text_alignment == rl.GuiTextAlignment.TEXT_ALIGN_RIGHT: - line_pos.x += self._rect.x + self._rect.width - text_size.x - self._text_padding + line_pos.x += self._rect.width - text_size.x - self._text_padding prev_index = 0 for start, end, emoji in emojis: From 80a8df06430eeaf11daa3e8d9a2c8ee3e4784bc1 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 15 Oct 2025 21:53:52 -0700 Subject: [PATCH 186/910] raylib: fix emoji centering with Label (#36376) * kinda works * but spacing was off, so back to big emoji * rm debug * fixed! * fixed! * fix newline in emoji pattern * fix * fix dat --- selfdrive/ui/widgets/prime.py | 2 +- selfdrive/ui/widgets/setup.py | 3 +-- system/ui/lib/emoji.py | 2 +- system/ui/lib/text_measure.py | 20 +++++++++++++++++++- 4 files changed, 22 insertions(+), 5 deletions(-) diff --git a/selfdrive/ui/widgets/prime.py b/selfdrive/ui/widgets/prime.py index e779ead33..bbbd52a8c 100644 --- a/selfdrive/ui/widgets/prime.py +++ b/selfdrive/ui/widgets/prime.py @@ -46,7 +46,7 @@ class PrimeWidget(Widget): features = ["Remote access", "24/7 LTE connectivity", "1 year of drive storage", "Remote snapshots"] for i, feature in enumerate(features): item_y = features_y + 80 + i * 65 - gui_label(rl.Rectangle(x, item_y, 50, 60), "✓", 50, color=rl.Color(70, 91, 234, 255)) + gui_label(rl.Rectangle(x, item_y, 100, 60), "✓", 50, color=rl.Color(70, 91, 234, 255)) gui_label(rl.Rectangle(x + 60, item_y, w - 60, 60), feature, 50) def _render_for_prime_user(self, rect: rl.Rectangle): diff --git a/selfdrive/ui/widgets/setup.py b/selfdrive/ui/widgets/setup.py index b0f122227..46a587735 100644 --- a/selfdrive/ui/widgets/setup.py +++ b/selfdrive/ui/widgets/setup.py @@ -64,8 +64,7 @@ class SetupWidget(Widget): spacing = 42 # Title with fire emojis - # TODO: fix Label centering with emojis - self._firehose_label.render(rl.Rectangle(x - 48, y, w, 64)) + self._firehose_label.render(rl.Rectangle(rect.x, y, rect.width, 64)) y += 64 + spacing # Description diff --git a/system/ui/lib/emoji.py b/system/ui/lib/emoji.py index 519f567d3..dc85c0faf 100644 --- a/system/ui/lib/emoji.py +++ b/system/ui/lib/emoji.py @@ -28,7 +28,7 @@ EMOJI_REGEX = re.compile( \u231a \ufe0f \u3030 -]+""", +]+""".replace("\n", ""), flags=re.UNICODE ) diff --git a/system/ui/lib/text_measure.py b/system/ui/lib/text_measure.py index fcb7b25cc..b91090e05 100644 --- a/system/ui/lib/text_measure.py +++ b/system/ui/lib/text_measure.py @@ -1,5 +1,6 @@ import pyray as rl from openpilot.system.ui.lib.application import FONT_SCALE +from openpilot.system.ui.lib.emoji import find_emoji _cache: dict[int, rl.Vector2] = {} @@ -10,6 +11,23 @@ def measure_text_cached(font: rl.Font, text: str, font_size: int, spacing: int = if key in _cache: return _cache[key] - result = rl.measure_text_ex(font, text, font_size * FONT_SCALE, spacing) # noqa: TID251 + # Measure normal characters without emojis, then add standard width for each found emoji + emoji = find_emoji(text) + if emoji: + non_emoji_text = "" + last_index = 0 + for start, end, _ in emoji: + non_emoji_text += text[last_index:start] + last_index = end + else: + non_emoji_text = text + + result = rl.measure_text_ex(font, non_emoji_text, font_size * FONT_SCALE, spacing) # noqa: TID251 + if emoji: + result.x += len(emoji) * font_size * FONT_SCALE + # If just emoji assume a single line height + if result.y == 0: + result.y = font_size * FONT_SCALE + _cache[key] = result return result From b29b1964ba232f407f164212ddfe84d96395dfb7 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 15 Oct 2025 22:10:55 -0700 Subject: [PATCH 187/910] raylib screenshots: test onroad (#36369) * test onroad * person * onroad alert * mid and full * all * can do this * tf * tf * clean up --- .../ui/tests/test_ui/raylib_screenshots.py | 75 ++++++++++++++++++- 1 file changed, 74 insertions(+), 1 deletion(-) diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py index 3f3958f9d..013287aae 100755 --- a/selfdrive/ui/tests/test_ui/raylib_screenshots.py +++ b/selfdrive/ui/tests/test_ui/raylib_screenshots.py @@ -19,6 +19,9 @@ from openpilot.selfdrive.test.helpers import with_processes from openpilot.selfdrive.selfdrived.alertmanager import set_offroad_alert from openpilot.system.updated.updated import parse_release_notes +AlertSize = log.SelfdriveState.AlertSize +AlertStatus = log.SelfdriveState.AlertStatus + TEST_DIR = pathlib.Path(__file__).parent TEST_OUTPUT_DIR = TEST_DIR / "raylib_report" SCREENSHOTS_DIR = TEST_OUTPUT_DIR / "screenshots" @@ -126,6 +129,71 @@ def setup_experimental_mode_description(click, pm: PubMaster): click(1200, 280) # expand description for experimental mode +def setup_onroad(click, pm: PubMaster): + ds = messaging.new_message('deviceState') + ds.deviceState.started = True + + ps = messaging.new_message('pandaStates', 1) + ps.pandaStates[0].pandaType = log.PandaState.PandaType.dos + ps.pandaStates[0].ignitionLine = True + + driverState = messaging.new_message('driverStateV2') + driverState.driverStateV2.leftDriverData.faceOrientation = [0, 0, 0] + + for _ in range(5): + pm.send('deviceState', ds) + pm.send('pandaStates', ps) + pm.send('driverStateV2', driverState) + ds.clear_write_flag() + ps.clear_write_flag() + driverState.clear_write_flag() + time.sleep(0.05) + + +def setup_onroad_sidebar(click, pm: PubMaster): + setup_onroad(click, pm) + click(100, 100) # open sidebar + + +def setup_onroad_small_alert(click, pm: PubMaster): + setup_onroad(click, pm) + alert = messaging.new_message('selfdriveState') + alert.selfdriveState.alertSize = AlertSize.small + alert.selfdriveState.alertText1 = "Small Alert" + alert.selfdriveState.alertText2 = "This is a small alert" + alert.selfdriveState.alertStatus = AlertStatus.normal + for _ in range(5): + pm.send('selfdriveState', alert) + alert.clear_write_flag() + time.sleep(0.05) + + +def setup_onroad_medium_alert(click, pm: PubMaster): + setup_onroad(click, pm) + alert = messaging.new_message('selfdriveState') + alert.selfdriveState.alertSize = AlertSize.mid + alert.selfdriveState.alertText1 = "Medium Alert" + alert.selfdriveState.alertText2 = "This is a medium alert" + alert.selfdriveState.alertStatus = AlertStatus.userPrompt + for _ in range(5): + pm.send('selfdriveState', alert) + alert.clear_write_flag() + time.sleep(0.05) + + +def setup_onroad_full_alert(click, pm: PubMaster): + setup_onroad(click, pm) + alert = messaging.new_message('selfdriveState') + alert.selfdriveState.alertSize = AlertSize.full + alert.selfdriveState.alertText1 = "TAKE CONTROL IMMEDIATELY" + alert.selfdriveState.alertText2 = "Calibration Invalid: Remount Device & Recalibrate" + alert.selfdriveState.alertStatus = AlertStatus.critical + for _ in range(5): + pm.send('selfdriveState', alert) + alert.clear_write_flag() + time.sleep(0.05) + + CASES = { "homescreen": setup_homescreen, "homescreen_paired": setup_homescreen, @@ -145,6 +213,11 @@ CASES = { "offroad_alert": setup_offroad_alert, "confirmation_dialog": setup_confirmation_dialog, "experimental_mode_description": setup_experimental_mode_description, + "onroad": setup_onroad, + "onroad_sidebar": setup_onroad_sidebar, + "onroad_small_alert": setup_onroad_small_alert, + "onroad_medium_alert": setup_onroad_medium_alert, + "onroad_full_alert": setup_onroad_full_alert, } @@ -155,7 +228,7 @@ class TestUI: def setup(self): # Seed minimal offroad state - self.pm = PubMaster(["deviceState"]) + self.pm = PubMaster(["deviceState", "pandaStates", "driverStateV2", "selfdriveState"]) ds = messaging.new_message('deviceState') ds.deviceState.networkType = log.DeviceState.NetworkType.wifi for _ in range(5): From 65e1fd299e2fc085c5e3d5b32a366736df85de54 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 15 Oct 2025 22:53:37 -0700 Subject: [PATCH 188/910] raylib: fix full size alert text (#36379) * stash so far * try this * better * fast * rename * revert * clean up * yes * hack to make it work for now * actually fix * fix --- selfdrive/ui/onroad/alert_renderer.py | 20 ++++++++----- .../ui/tests/test_ui/raylib_screenshots.py | 29 ++++++++++++++++++- system/ui/widgets/button.py | 4 +-- system/ui/widgets/label.py | 11 ++++++- 4 files changed, 53 insertions(+), 11 deletions(-) diff --git a/selfdrive/ui/onroad/alert_renderer.py b/selfdrive/ui/onroad/alert_renderer.py index 362f49a51..0f944bac5 100644 --- a/selfdrive/ui/onroad/alert_renderer.py +++ b/selfdrive/ui/onroad/alert_renderer.py @@ -7,7 +7,7 @@ from openpilot.system.hardware import TICI from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.widgets import Widget -from openpilot.system.ui.widgets.label import gui_text_box +from openpilot.system.ui.widgets.label import Label AlertSize = log.SelfdriveState.AlertSize AlertStatus = log.SelfdriveState.AlertStatus @@ -74,6 +74,12 @@ class AlertRenderer(Widget): self.font_regular: rl.Font = gui_app.font(FontWeight.NORMAL) self.font_bold: rl.Font = gui_app.font(FontWeight.BOLD) + # font size is set dynamically + self._full_text1_label = Label("", font_size=0, font_weight=FontWeight.BOLD, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER, + text_alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP) + self._full_text2_label = Label("", font_size=ALERT_FONT_BIG, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER, + text_alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP) + def get_alert(self, sm: messaging.SubMaster) -> Alert | None: """Generate the current alert based on selfdrive state.""" ss = sm['selfdriveState'] @@ -153,16 +159,16 @@ class AlertRenderer(Widget): is_long = len(alert.text1) > 15 font_size1 = 132 if is_long else 177 - align_center = rl.GuiTextAlignment.TEXT_ALIGN_CENTER - align_top = rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP - - top_offset = 240 if is_long else 270 + top_offset = 200 if is_long or '\n' in alert.text1 else 270 title_rect = rl.Rectangle(rect.x, rect.y + top_offset, rect.width, 600) - gui_text_box(title_rect, alert.text1, font_size1, alignment=align_center, alignment_vertical=align_top, font_weight=FontWeight.BOLD) + self._full_text1_label.set_font_size(font_size1) + self._full_text1_label.set_text(alert.text1) + self._full_text1_label.render(title_rect) bottom_offset = 361 if is_long else 420 subtitle_rect = rl.Rectangle(rect.x, rect.y + rect.height - bottom_offset, rect.width, 300) - gui_text_box(subtitle_rect, alert.text2, ALERT_FONT_BIG, alignment=align_center, alignment_vertical=align_top) + self._full_text2_label.set_text(alert.text2) + self._full_text2_label.render(subtitle_rect) def _draw_centered(self, text, rect, font, font_size, center_y=True, color=rl.WHITE) -> None: text_size = measure_text_cached(font, text, font_size) diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py index 013287aae..6de774648 100755 --- a/selfdrive/ui/tests/test_ui/raylib_screenshots.py +++ b/selfdrive/ui/tests/test_ui/raylib_screenshots.py @@ -182,12 +182,37 @@ def setup_onroad_medium_alert(click, pm: PubMaster): def setup_onroad_full_alert(click, pm: PubMaster): + setup_onroad(click, pm) + alert = messaging.new_message('selfdriveState') + alert.selfdriveState.alertSize = AlertSize.full + alert.selfdriveState.alertText1 = "DISENGAGE IMMEDIATELY" + alert.selfdriveState.alertText2 = "Driver Distracted" + alert.selfdriveState.alertStatus = AlertStatus.critical + for _ in range(5): + pm.send('selfdriveState', alert) + alert.clear_write_flag() + time.sleep(0.05) + + +def setup_onroad_full_alert_multiline(click, pm: PubMaster): + setup_onroad(click, pm) + alert = messaging.new_message('selfdriveState') + alert.selfdriveState.alertSize = AlertSize.full + alert.selfdriveState.alertText1 = "Reverse\nGear" + alert.selfdriveState.alertStatus = AlertStatus.normal + for _ in range(5): + pm.send('selfdriveState', alert) + alert.clear_write_flag() + time.sleep(0.05) + + +def setup_onroad_full_alert_long_text(click, pm: PubMaster): setup_onroad(click, pm) alert = messaging.new_message('selfdriveState') alert.selfdriveState.alertSize = AlertSize.full alert.selfdriveState.alertText1 = "TAKE CONTROL IMMEDIATELY" alert.selfdriveState.alertText2 = "Calibration Invalid: Remount Device & Recalibrate" - alert.selfdriveState.alertStatus = AlertStatus.critical + alert.selfdriveState.alertStatus = AlertStatus.userPrompt for _ in range(5): pm.send('selfdriveState', alert) alert.clear_write_flag() @@ -218,6 +243,8 @@ CASES = { "onroad_small_alert": setup_onroad_small_alert, "onroad_medium_alert": setup_onroad_medium_alert, "onroad_full_alert": setup_onroad_full_alert, + "onroad_full_alert_multiline": setup_onroad_full_alert_multiline, + "onroad_full_alert_long_text": setup_onroad_full_alert_long_text, } diff --git a/system/ui/widgets/button.py b/system/ui/widgets/button.py index fa6e5ed7d..15d48b4e1 100644 --- a/system/ui/widgets/button.py +++ b/system/ui/widgets/button.py @@ -96,8 +96,8 @@ class Button(Widget): self._border_radius = border_radius self._background_color = BUTTON_BACKGROUND_COLORS[self._button_style] - self._label = Label(text, font_size, font_weight, text_alignment, text_padding, - BUTTON_TEXT_COLOR[self._button_style], icon=icon) + self._label = Label(text, font_size, font_weight, text_alignment, text_padding=text_padding, + text_color=BUTTON_TEXT_COLOR[self._button_style], icon=icon) self._click_callback = click_callback self._multi_touch = multi_touch diff --git a/system/ui/widgets/label.py b/system/ui/widgets/label.py index 32cc7063d..99aed529a 100644 --- a/system/ui/widgets/label.py +++ b/system/ui/widgets/label.py @@ -94,6 +94,7 @@ class Label(Widget): font_size: int = DEFAULT_TEXT_SIZE, font_weight: FontWeight = FontWeight.NORMAL, text_alignment: int = rl.GuiTextAlignment.TEXT_ALIGN_CENTER, + text_alignment_vertical: int = rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE, text_padding: int = 0, text_color: rl.Color = DEFAULT_TEXT_COLOR, icon: Union[rl.Texture, None] = None, # noqa: UP007 @@ -104,6 +105,7 @@ class Label(Widget): self._font = gui_app.font(self._font_weight) self._font_size = font_size self._text_alignment = text_alignment + self._text_alignment_vertical = text_alignment_vertical self._text_padding = text_padding self._text_color = text_color self._icon = icon @@ -118,6 +120,10 @@ class Label(Widget): def set_text_color(self, color): self._text_color = color + def set_font_size(self, size): + self._font_size = size + self._update_text(self._text) + def _update_layout_rects(self): self._update_text(self._text) @@ -131,7 +137,10 @@ class Label(Widget): def _render(self, _): text_size = self._text_size[0] if self._text_size else rl.Vector2(0.0, 0.0) - text_pos = rl.Vector2(self._rect.x, (self._rect.y + (self._rect.height - text_size.y) // 2)) + if self._text_alignment_vertical == rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE: + text_pos = rl.Vector2(self._rect.x, (self._rect.y + (self._rect.height - text_size.y) // 2)) + else: + text_pos = rl.Vector2(self._rect.x, self._rect.y) if self._icon: icon_y = self._rect.y + (self._rect.height - self._icon.height) / 2 From 783b717af8e43b8c9671168c75fecdbae73d504c Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Thu, 16 Oct 2025 00:49:05 -0700 Subject: [PATCH 189/910] AGNOS 14 (#36313) * version * updater * this order * manifest * update * prod * logic * magic * new * bump * bump * new * b * bump * prod --- .gitattributes | 3 +- launch_env.sh | 2 +- selfdrive/test/test_onroad.py | 2 +- system/hardware/tici/agnos.json | 58 ++++++++--------- system/hardware/tici/all-partitions.json | 82 ++++++++++++------------ system/hardware/tici/updater | 20 +++++- system/hardware/tici/updater_magic | 3 + system/hardware/tici/updater_weston | 3 + third_party/raylib/build.sh | 2 +- third_party/raylib/larch64/libraylib.a | 4 +- 10 files changed, 100 insertions(+), 79 deletions(-) create mode 100755 system/hardware/tici/updater_magic create mode 100755 system/hardware/tici/updater_weston diff --git a/.gitattributes b/.gitattributes index cc1605a13..50ac49dd7 100644 --- a/.gitattributes +++ b/.gitattributes @@ -10,7 +10,8 @@ *.wav filter=lfs diff=lfs merge=lfs -text selfdrive/car/tests/test_models_segs.txt filter=lfs diff=lfs merge=lfs -text -system/hardware/tici/updater filter=lfs diff=lfs merge=lfs -text +system/hardware/tici/updater_weston filter=lfs diff=lfs merge=lfs -text +system/hardware/tici/updater_magic filter=lfs diff=lfs merge=lfs -text third_party/**/*.a filter=lfs diff=lfs merge=lfs -text third_party/**/*.so filter=lfs diff=lfs merge=lfs -text third_party/**/*.so.* filter=lfs diff=lfs merge=lfs -text diff --git a/launch_env.sh b/launch_env.sh index 67dd5ee79..74bcf1bae 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="13.1" + export AGNOS_VERSION="14" fi export STAGING_ROOT="/data/safe_staging" diff --git a/selfdrive/test/test_onroad.py b/selfdrive/test/test_onroad.py index 9fc76489b..40f13e11c 100644 --- a/selfdrive/test/test_onroad.py +++ b/selfdrive/test/test_onroad.py @@ -285,7 +285,7 @@ class TestOnroad: # check for big leaks. note that memory usage is # expected to go up while the MSGQ buffers fill up - assert np.average(mems) <= 65, "Average memory usage above 65%" + assert np.average(mems) <= 85, "Average memory usage above 85%" assert np.max(np.diff(mems)) <= 4, "Max memory increase too high" assert np.average(np.diff(mems)) <= 1, "Average memory increase too high" diff --git a/system/hardware/tici/agnos.json b/system/hardware/tici/agnos.json index d93963cf2..bd731f5dd 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-effa23294138e2297b85a5b482a885184c437b5ab25d74f2a62d4fce4e68f63b.img.xz", - "hash": "effa23294138e2297b85a5b482a885184c437b5ab25d74f2a62d4fce4e68f63b", - "hash_raw": "effa23294138e2297b85a5b482a885184c437b5ab25d74f2a62d4fce4e68f63b", + "url": "https://commadist.azureedge.net/agnosupdate/xbl-98e0642e2af77596e64d107b2c525a36e54d41d879268fd2fcab9255a2b29723.img.xz", + "hash": "98e0642e2af77596e64d107b2c525a36e54d41d879268fd2fcab9255a2b29723", + "hash_raw": "98e0642e2af77596e64d107b2c525a36e54d41d879268fd2fcab9255a2b29723", "size": 3282256, "sparse": false, "full_check": true, "has_ab": true, - "ondevice_hash": "ed61a650bea0c56652dd0fc68465d8fc722a4e6489dc8f257630c42c6adcdc89" + "ondevice_hash": "907c705f72ebcbd3030e03da9ef4c65a3d599e056a79aa9e7c369fdff8e54dc4" }, { "name": "xbl_config", - "url": "https://commadist.azureedge.net/agnosupdate/xbl_config-63d019efed684601f145ef37628e62c8da73f5053a8e51d7de09e72b8b11f97c.img.xz", - "hash": "63d019efed684601f145ef37628e62c8da73f5053a8e51d7de09e72b8b11f97c", - "hash_raw": "63d019efed684601f145ef37628e62c8da73f5053a8e51d7de09e72b8b11f97c", + "url": "https://commadist.azureedge.net/agnosupdate/xbl_config-4d8add65e80b3e5ca49a64fac76025ee3a57a1523abd9caa407aa8c5fb721b0f.img.xz", + "hash": "4d8add65e80b3e5ca49a64fac76025ee3a57a1523abd9caa407aa8c5fb721b0f", + "hash_raw": "4d8add65e80b3e5ca49a64fac76025ee3a57a1523abd9caa407aa8c5fb721b0f", "size": 98124, "sparse": false, "full_check": true, "has_ab": true, - "ondevice_hash": "b12801ffaa81e58e3cef914488d3b447e35483ba549b28c6cd9deb4814c3265f" + "ondevice_hash": "46c472f52fb97a4836d08d0e790f5c8512651f520ce004bc3bbc6a143fc7a3c2" }, { "name": "abl", @@ -34,51 +34,51 @@ }, { "name": "aop", - "url": "https://commadist.azureedge.net/agnosupdate/aop-21370172e590bd4ea907a558bcd6df20dc7a6c7d38b8e62fdde18f4a512ba9e9.img.xz", - "hash": "21370172e590bd4ea907a558bcd6df20dc7a6c7d38b8e62fdde18f4a512ba9e9", - "hash_raw": "21370172e590bd4ea907a558bcd6df20dc7a6c7d38b8e62fdde18f4a512ba9e9", + "url": "https://commadist.azureedge.net/agnosupdate/aop-d8add1d4c1b6b443debf7bb80040e88a12140d248a328650d65ceaa0df04c1b7.img.xz", + "hash": "d8add1d4c1b6b443debf7bb80040e88a12140d248a328650d65ceaa0df04c1b7", + "hash_raw": "d8add1d4c1b6b443debf7bb80040e88a12140d248a328650d65ceaa0df04c1b7", "size": 184364, "sparse": false, "full_check": true, "has_ab": true, - "ondevice_hash": "c1be2f4aac5b3af49b904b027faec418d05efd7bd5144eb4fdfcba602bcf2180" + "ondevice_hash": "e320da0d3f73aa09277a8be740c59f9cc605d2098b46a842c93ea2ac0ac97cb0" }, { "name": "devcfg", - "url": "https://commadist.azureedge.net/agnosupdate/devcfg-d7d7e52963bbedbbf8a7e66847579ca106a0a729ce2cf60f4b8d8ea4b535d620.img.xz", - "hash": "d7d7e52963bbedbbf8a7e66847579ca106a0a729ce2cf60f4b8d8ea4b535d620", - "hash_raw": "d7d7e52963bbedbbf8a7e66847579ca106a0a729ce2cf60f4b8d8ea4b535d620", + "url": "https://commadist.azureedge.net/agnosupdate/devcfg-7e8a836cf75a9097b1c78960d36f883699fcc3858d8a1d28338f889f6af25cc8.img.xz", + "hash": "7e8a836cf75a9097b1c78960d36f883699fcc3858d8a1d28338f889f6af25cc8", + "hash_raw": "7e8a836cf75a9097b1c78960d36f883699fcc3858d8a1d28338f889f6af25cc8", "size": 40336, "sparse": false, "full_check": true, "has_ab": true, - "ondevice_hash": "17b229668b20305ff8fa3cd5f94716a3aaa1e5bf9d1c24117eff7f2f81ae719f" + "ondevice_hash": "7e9412d154036216e56c2346d24455dd45f56d6de4c9e8837597f22d59c83d93" }, { "name": "boot", - "url": "https://commadist.azureedge.net/agnosupdate/boot-b96882012ab6cddda04f440009c798a6cff65977f984b12072e89afa592d86cb.img.xz", - "hash": "b96882012ab6cddda04f440009c798a6cff65977f984b12072e89afa592d86cb", - "hash_raw": "b96882012ab6cddda04f440009c798a6cff65977f984b12072e89afa592d86cb", - "size": 17442816, + "url": "https://commadist.azureedge.net/agnosupdate/boot-08291eb52a9d54b77e191ea1a78addf24aae28e15306ac3118d03ac9be29fbe9.img.xz", + "hash": "08291eb52a9d54b77e191ea1a78addf24aae28e15306ac3118d03ac9be29fbe9", + "hash_raw": "08291eb52a9d54b77e191ea1a78addf24aae28e15306ac3118d03ac9be29fbe9", + "size": 17868800, "sparse": false, "full_check": true, "has_ab": true, - "ondevice_hash": "8ed6c2796be5c5b29d64e6413b8e878d5bd1a3981d15216d2b5e84140cc4ea2a" + "ondevice_hash": "18fab2e1eb2e43e5c39e20ee20e0d391586de528df6dbfdab6dabcdab835ee3e" }, { "name": "system", - "url": "https://commadist.azureedge.net/agnosupdate/system-2b1bb223bf2100376ad5d543bfa4a483f33327b3478ec20ab36048388472c4bc.img.xz", - "hash": "325414e5c9f7516b2bf0fedb6abe6682f717897a6d84ab70d5afe91a59f244e9", - "hash_raw": "2b1bb223bf2100376ad5d543bfa4a483f33327b3478ec20ab36048388472c4bc", - "size": 4718592000, + "url": "https://commadist.azureedge.net/agnosupdate/system-6be13dfe085e03955d37fb317a1f2ee58d751eb0ef996c91bbb37c98a12a4ac3.img.xz", + "hash": "eafa98c2dab3bc1bd799416070d724a0fca8138e6e730d391eea6002de9f3f44", + "hash_raw": "6be13dfe085e03955d37fb317a1f2ee58d751eb0ef996c91bbb37c98a12a4ac3", + "size": 5368709120, "sparse": true, "full_check": false, "has_ab": true, - "ondevice_hash": "79f4f6d0b5b4a416f0f31261b430943a78e37c26d0e226e0ef412fe0eae3c727", + "ondevice_hash": "169c9d230fc0806cb0f30844f38c543a568b1a159e380e33e5be8fce344b96bf", "alt": { - "hash": "2b1bb223bf2100376ad5d543bfa4a483f33327b3478ec20ab36048388472c4bc", - "url": "https://commadist.azureedge.net/agnosupdate/system-2b1bb223bf2100376ad5d543bfa4a483f33327b3478ec20ab36048388472c4bc.img", - "size": 4718592000 + "hash": "6be13dfe085e03955d37fb317a1f2ee58d751eb0ef996c91bbb37c98a12a4ac3", + "url": "https://commadist.azureedge.net/agnosupdate/system-6be13dfe085e03955d37fb317a1f2ee58d751eb0ef996c91bbb37c98a12a4ac3.img", + "size": 5368709120 } } ] \ No newline at end of file diff --git a/system/hardware/tici/all-partitions.json b/system/hardware/tici/all-partitions.json index ebffc01df..38b5ebe7c 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-effa23294138e2297b85a5b482a885184c437b5ab25d74f2a62d4fce4e68f63b.img.xz", - "hash": "effa23294138e2297b85a5b482a885184c437b5ab25d74f2a62d4fce4e68f63b", - "hash_raw": "effa23294138e2297b85a5b482a885184c437b5ab25d74f2a62d4fce4e68f63b", + "url": "https://commadist.azureedge.net/agnosupdate/xbl-98e0642e2af77596e64d107b2c525a36e54d41d879268fd2fcab9255a2b29723.img.xz", + "hash": "98e0642e2af77596e64d107b2c525a36e54d41d879268fd2fcab9255a2b29723", + "hash_raw": "98e0642e2af77596e64d107b2c525a36e54d41d879268fd2fcab9255a2b29723", "size": 3282256, "sparse": false, "full_check": true, "has_ab": true, - "ondevice_hash": "ed61a650bea0c56652dd0fc68465d8fc722a4e6489dc8f257630c42c6adcdc89" + "ondevice_hash": "907c705f72ebcbd3030e03da9ef4c65a3d599e056a79aa9e7c369fdff8e54dc4" }, { "name": "xbl_config", - "url": "https://commadist.azureedge.net/agnosupdate/xbl_config-63d019efed684601f145ef37628e62c8da73f5053a8e51d7de09e72b8b11f97c.img.xz", - "hash": "63d019efed684601f145ef37628e62c8da73f5053a8e51d7de09e72b8b11f97c", - "hash_raw": "63d019efed684601f145ef37628e62c8da73f5053a8e51d7de09e72b8b11f97c", + "url": "https://commadist.azureedge.net/agnosupdate/xbl_config-4d8add65e80b3e5ca49a64fac76025ee3a57a1523abd9caa407aa8c5fb721b0f.img.xz", + "hash": "4d8add65e80b3e5ca49a64fac76025ee3a57a1523abd9caa407aa8c5fb721b0f", + "hash_raw": "4d8add65e80b3e5ca49a64fac76025ee3a57a1523abd9caa407aa8c5fb721b0f", "size": 98124, "sparse": false, "full_check": true, "has_ab": true, - "ondevice_hash": "b12801ffaa81e58e3cef914488d3b447e35483ba549b28c6cd9deb4814c3265f" + "ondevice_hash": "46c472f52fb97a4836d08d0e790f5c8512651f520ce004bc3bbc6a143fc7a3c2" }, { "name": "abl", @@ -163,14 +163,14 @@ }, { "name": "aop", - "url": "https://commadist.azureedge.net/agnosupdate/aop-21370172e590bd4ea907a558bcd6df20dc7a6c7d38b8e62fdde18f4a512ba9e9.img.xz", - "hash": "21370172e590bd4ea907a558bcd6df20dc7a6c7d38b8e62fdde18f4a512ba9e9", - "hash_raw": "21370172e590bd4ea907a558bcd6df20dc7a6c7d38b8e62fdde18f4a512ba9e9", + "url": "https://commadist.azureedge.net/agnosupdate/aop-d8add1d4c1b6b443debf7bb80040e88a12140d248a328650d65ceaa0df04c1b7.img.xz", + "hash": "d8add1d4c1b6b443debf7bb80040e88a12140d248a328650d65ceaa0df04c1b7", + "hash_raw": "d8add1d4c1b6b443debf7bb80040e88a12140d248a328650d65ceaa0df04c1b7", "size": 184364, "sparse": false, "full_check": true, "has_ab": true, - "ondevice_hash": "c1be2f4aac5b3af49b904b027faec418d05efd7bd5144eb4fdfcba602bcf2180" + "ondevice_hash": "e320da0d3f73aa09277a8be740c59f9cc605d2098b46a842c93ea2ac0ac97cb0" }, { "name": "bluetooth", @@ -207,14 +207,14 @@ }, { "name": "devcfg", - "url": "https://commadist.azureedge.net/agnosupdate/devcfg-d7d7e52963bbedbbf8a7e66847579ca106a0a729ce2cf60f4b8d8ea4b535d620.img.xz", - "hash": "d7d7e52963bbedbbf8a7e66847579ca106a0a729ce2cf60f4b8d8ea4b535d620", - "hash_raw": "d7d7e52963bbedbbf8a7e66847579ca106a0a729ce2cf60f4b8d8ea4b535d620", + "url": "https://commadist.azureedge.net/agnosupdate/devcfg-7e8a836cf75a9097b1c78960d36f883699fcc3858d8a1d28338f889f6af25cc8.img.xz", + "hash": "7e8a836cf75a9097b1c78960d36f883699fcc3858d8a1d28338f889f6af25cc8", + "hash_raw": "7e8a836cf75a9097b1c78960d36f883699fcc3858d8a1d28338f889f6af25cc8", "size": 40336, "sparse": false, "full_check": true, "has_ab": true, - "ondevice_hash": "17b229668b20305ff8fa3cd5f94716a3aaa1e5bf9d1c24117eff7f2f81ae719f" + "ondevice_hash": "7e9412d154036216e56c2346d24455dd45f56d6de4c9e8837597f22d59c83d93" }, { "name": "devinfo", @@ -339,62 +339,62 @@ }, { "name": "boot", - "url": "https://commadist.azureedge.net/agnosupdate/boot-b96882012ab6cddda04f440009c798a6cff65977f984b12072e89afa592d86cb.img.xz", - "hash": "b96882012ab6cddda04f440009c798a6cff65977f984b12072e89afa592d86cb", - "hash_raw": "b96882012ab6cddda04f440009c798a6cff65977f984b12072e89afa592d86cb", - "size": 17442816, + "url": "https://commadist.azureedge.net/agnosupdate/boot-08291eb52a9d54b77e191ea1a78addf24aae28e15306ac3118d03ac9be29fbe9.img.xz", + "hash": "08291eb52a9d54b77e191ea1a78addf24aae28e15306ac3118d03ac9be29fbe9", + "hash_raw": "08291eb52a9d54b77e191ea1a78addf24aae28e15306ac3118d03ac9be29fbe9", + "size": 17868800, "sparse": false, "full_check": true, "has_ab": true, - "ondevice_hash": "8ed6c2796be5c5b29d64e6413b8e878d5bd1a3981d15216d2b5e84140cc4ea2a" + "ondevice_hash": "18fab2e1eb2e43e5c39e20ee20e0d391586de528df6dbfdab6dabcdab835ee3e" }, { "name": "system", - "url": "https://commadist.azureedge.net/agnosupdate/system-2b1bb223bf2100376ad5d543bfa4a483f33327b3478ec20ab36048388472c4bc.img.xz", - "hash": "325414e5c9f7516b2bf0fedb6abe6682f717897a6d84ab70d5afe91a59f244e9", - "hash_raw": "2b1bb223bf2100376ad5d543bfa4a483f33327b3478ec20ab36048388472c4bc", - "size": 4718592000, + "url": "https://commadist.azureedge.net/agnosupdate/system-6be13dfe085e03955d37fb317a1f2ee58d751eb0ef996c91bbb37c98a12a4ac3.img.xz", + "hash": "eafa98c2dab3bc1bd799416070d724a0fca8138e6e730d391eea6002de9f3f44", + "hash_raw": "6be13dfe085e03955d37fb317a1f2ee58d751eb0ef996c91bbb37c98a12a4ac3", + "size": 5368709120, "sparse": true, "full_check": false, "has_ab": true, - "ondevice_hash": "79f4f6d0b5b4a416f0f31261b430943a78e37c26d0e226e0ef412fe0eae3c727", + "ondevice_hash": "169c9d230fc0806cb0f30844f38c543a568b1a159e380e33e5be8fce344b96bf", "alt": { - "hash": "2b1bb223bf2100376ad5d543bfa4a483f33327b3478ec20ab36048388472c4bc", - "url": "https://commadist.azureedge.net/agnosupdate/system-2b1bb223bf2100376ad5d543bfa4a483f33327b3478ec20ab36048388472c4bc.img", - "size": 4718592000 + "hash": "6be13dfe085e03955d37fb317a1f2ee58d751eb0ef996c91bbb37c98a12a4ac3", + "url": "https://commadist.azureedge.net/agnosupdate/system-6be13dfe085e03955d37fb317a1f2ee58d751eb0ef996c91bbb37c98a12a4ac3.img", + "size": 5368709120 } }, { "name": "userdata_90", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_90-b3112984d2a8534a83d2ce43d35efdd10c7d163d9699f611f0f72ad9e9cb5af9.img.xz", - "hash": "bea163e6fb6ac6224c7f32619affb5afb834cd859971b0cab6d8297dd0098f0a", - "hash_raw": "b3112984d2a8534a83d2ce43d35efdd10c7d163d9699f611f0f72ad9e9cb5af9", + "url": "https://commadist.azureedge.net/agnosupdate/userdata_90-dc81085a13dd7c19d94f10e53c6b5aefdefa83bb26a544674b3e768f09604da5.img.xz", + "hash": "085426cdb3a2ee8139817ffb622a3e25f0b95dd1b29faaa0ba99db8e27d4b7ed", + "hash_raw": "dc81085a13dd7c19d94f10e53c6b5aefdefa83bb26a544674b3e768f09604da5", "size": 96636764160, "sparse": true, "full_check": true, "has_ab": false, - "ondevice_hash": "f4841c6ae3207197886e5efbd50f44cc24822680d7b785fa2d2743c657f23287" + "ondevice_hash": "10e3b8d0f9a9eb1744368d31015b4397456e18a5fd94584a83669afed00bba92" }, { "name": "userdata_89", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_89-3e63f670e4270474cec96f4da9250ee4e87e3106b0b043b7e82371e1c761e167.img.xz", - "hash": "b5458a29dd7d4a4c9b7ad77b8baa5f804142ac78d97c6668839bf2a650e32518", - "hash_raw": "3e63f670e4270474cec96f4da9250ee4e87e3106b0b043b7e82371e1c761e167", + "url": "https://commadist.azureedge.net/agnosupdate/userdata_89-208d12e657cd343134df03ff1af2e2b4526ac03d1f7657bd53bfa76370681251.img.xz", + "hash": "796bd992e35f88cd7765a4e0273069b8cb8b2ba52be323a84716602a4443197b", + "hash_raw": "208d12e657cd343134df03ff1af2e2b4526ac03d1f7657bd53bfa76370681251", "size": 95563022336, "sparse": true, "full_check": true, "has_ab": false, - "ondevice_hash": "1dc10c542d3b019258fc08dc7dfdb49d9abad065e46d030b89bc1a2e0197f526" + "ondevice_hash": "c647752c040e56dc8d7b1cc23c1a40fa6e30ac5edc93fc4eed10704de1d2cbc7" }, { "name": "userdata_30", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_30-1d3885d4370974e55f0c6f567fd0344fc5ee10db067aa5810fbaf402eadb032c.img.xz", - "hash": "687d178cfc91be5d7e8aa1333405b610fdce01775b8333bd0985b81642b94eea", - "hash_raw": "1d3885d4370974e55f0c6f567fd0344fc5ee10db067aa5810fbaf402eadb032c", + "url": "https://commadist.azureedge.net/agnosupdate/userdata_30-3a4b4129a2d8ca6ceec090eeee73aaffd24c4f2160b280931e967bb83b1e811d.img.xz", + "hash": "e12f2bdd4365dbe4a84a71a6bc311ef995b47c2f7bff4b1691595466da2e017a", + "hash_raw": "3a4b4129a2d8ca6ceec090eeee73aaffd24c4f2160b280931e967bb83b1e811d", "size": 32212254720, "sparse": true, "full_check": true, "has_ab": false, - "ondevice_hash": "9ddbd1dae6ee7dc919f018364cf2f29dad138c9203c5a49aea0cbb9bf2e137e5" + "ondevice_hash": "4f2996a1ae3512e8e803f40028a18344877806f73d4ffd4ae29f66d96055671e" } ] \ No newline at end of file diff --git a/system/hardware/tici/updater b/system/hardware/tici/updater index 23cdc140f..69ce323a1 100755 --- a/system/hardware/tici/updater +++ b/system/hardware/tici/updater @@ -1,3 +1,17 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:eba5f44e6a763e1f74d1c718993218adcc72cba4caafe99b595fa701151a4c54 -size 10448792 +#!/usr/bin/env bash + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" + +AGNOS_PY=$1 +MANIFEST=$2 + +if [[ ! -f "$AGNOS_PY" || ! -f "$MANIFEST" ]]; then + echo "invalid args" + exit 1 +fi + +if systemctl is-active --quiet weston-ready; then + $DIR/updater_weston $AGNOS_PY $MANIFEST +else + $DIR/updater_magic $AGNOS_PY $MANIFEST +fi diff --git a/system/hardware/tici/updater_magic b/system/hardware/tici/updater_magic new file mode 100755 index 000000000..248797350 --- /dev/null +++ b/system/hardware/tici/updater_magic @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ffc9893e48b10096062f5fd1a14016addf7adb969f20f31ff26e68579992283c +size 20780744 diff --git a/system/hardware/tici/updater_weston b/system/hardware/tici/updater_weston new file mode 100755 index 000000000..23cdc140f --- /dev/null +++ b/system/hardware/tici/updater_weston @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eba5f44e6a763e1f74d1c718993218adcc72cba4caafe99b595fa701151a4c54 +size 10448792 diff --git a/third_party/raylib/build.sh b/third_party/raylib/build.sh index f786213da..870f4d597 100755 --- a/third_party/raylib/build.sh +++ b/third_party/raylib/build.sh @@ -30,7 +30,7 @@ fi cd raylib_repo -COMMIT=${1:-63ea627abb4488adc9c7b5e2f8dcc77a7d6bfa3b} +COMMIT=${1:-4ee3b8ce4ee5a1604a7c055a417178c65fb795ef} git fetch origin $COMMIT git reset --hard $COMMIT git clean -xdff . diff --git a/third_party/raylib/larch64/libraylib.a b/third_party/raylib/larch64/libraylib.a index e1c5c1930..a1c5e903e 100644 --- a/third_party/raylib/larch64/libraylib.a +++ b/third_party/raylib/larch64/libraylib.a @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9c3125236db11e7bebcc6ad5868444ed0605c6343f98b212d39267c092b3b481 -size 3140628 +oid sha256:a73b6b91500a37d08bc2a3ee26951175ca0f95a7d0fa0ec97c823dcdf5a660dc +size 3155684 From e1ad4daf8d93c57b59b266d38157842fabd17032 Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Thu, 16 Oct 2025 01:59:23 -0700 Subject: [PATCH 190/910] installer: branch migration (#36315) * mig * fix * fix * more * staging --- selfdrive/ui/installer/installer.cc | 34 ++++++++++++++++++++++++----- system/ui/setup.py | 4 +++- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/selfdrive/ui/installer/installer.cc b/selfdrive/ui/installer/installer.cc index a9b84b5c0..5cb0a38e0 100644 --- a/selfdrive/ui/installer/installer.cc +++ b/selfdrive/ui/installer/installer.cc @@ -5,6 +5,7 @@ #include "common/swaglog.h" #include "common/util.h" +#include "system/hardware/hw.h" #include "third_party/raylib/include/raylib.h" int freshClone(); @@ -38,6 +39,27 @@ extern const uint8_t inter_ttf_end[] asm("_binary_selfdrive_ui_installer_inter_a Font font; +std::vector tici_prebuilt_branches = {"release3", "release-tizi", "release3-staging", "nightly", "nightly-dev"}; +std::string migrated_branch; + +void branchMigration() { + migrated_branch = BRANCH_STR; + cereal::InitData::DeviceType device_type = Hardware::get_device_type(); + if (device_type == cereal::InitData::DeviceType::TICI) { + if (std::find(tici_prebuilt_branches.begin(), tici_prebuilt_branches.end(), BRANCH_STR) != tici_prebuilt_branches.end()) { + migrated_branch = "release-tici"; + } else if (BRANCH_STR == "master") { + migrated_branch = "master-tici"; + } + } else if (device_type == cereal::InitData::DeviceType::TIZI) { + if (BRANCH_STR == "release3") { + migrated_branch = "release-tizi"; + } else if (BRANCH_STR == "release3-staging") { + migrated_branch = "release-tizi-staging"; + } + } +} + void run(const char* cmd) { int err = std::system(cmd); assert(err == 0); @@ -87,7 +109,7 @@ int doInstall() { int freshClone() { LOGD("Doing fresh clone"); std::string cmd = util::string_format("git clone --progress %s -b %s --depth=1 --recurse-submodules %s 2>&1", - GIT_URL.c_str(), BRANCH_STR.c_str(), TMP_INSTALL_PATH); + GIT_URL.c_str(), migrated_branch.c_str(), TMP_INSTALL_PATH); return executeGitCommand(cmd); } @@ -95,11 +117,11 @@ int cachedFetch(const std::string &cache) { LOGD("Fetching with cache: %s", cache.c_str()); run(util::string_format("cp -rp %s %s", cache.c_str(), TMP_INSTALL_PATH).c_str()); - run(util::string_format("cd %s && git remote set-branches --add origin %s", TMP_INSTALL_PATH, BRANCH_STR.c_str()).c_str()); + run(util::string_format("cd %s && git remote set-branches --add origin %s", TMP_INSTALL_PATH, migrated_branch.c_str()).c_str()); renderProgress(10); - return executeGitCommand(util::string_format("cd %s && git fetch --progress origin %s 2>&1", TMP_INSTALL_PATH, BRANCH_STR.c_str())); + return executeGitCommand(util::string_format("cd %s && git fetch --progress origin %s 2>&1", TMP_INSTALL_PATH, migrated_branch.c_str())); } int executeGitCommand(const std::string &cmd) { @@ -142,8 +164,8 @@ void cloneFinished(int exitCode) { // ensure correct branch is checked out int err = chdir(TMP_INSTALL_PATH); assert(err == 0); - run(("git checkout " + BRANCH_STR).c_str()); - run(("git reset --hard origin/" + BRANCH_STR).c_str()); + run(("git checkout " + migrated_branch).c_str()); + run(("git reset --hard origin/" + migrated_branch).c_str()); run("git submodule update --init"); // move into place @@ -193,6 +215,8 @@ int main(int argc, char *argv[]) { font = LoadFontFromMemory(".ttf", inter_ttf, inter_ttf_end - inter_ttf, FONT_SIZE, NULL, 0); SetTextureFilter(font.texture, TEXTURE_FILTER_BILINEAR); + branchMigration(); + if (util::file_exists(CONTINUE_PATH)) { finishInstall(); } else { diff --git a/system/ui/setup.py b/system/ui/setup.py index e362703cd..5dbf59748 100755 --- a/system/ui/setup.py +++ b/system/ui/setup.py @@ -376,7 +376,9 @@ class Setup(Widget): fd, tmpfile = tempfile.mkstemp(prefix="installer_") - headers = {"User-Agent": USER_AGENT, "X-openpilot-serial": HARDWARE.get_serial()} + headers = {"User-Agent": USER_AGENT, + "X-openpilot-serial": HARDWARE.get_serial(), + "X-openpilot-device-type": HARDWARE.get_device_type()} req = urllib.request.Request(self.download_url, headers=headers) with open(tmpfile, 'wb') as f, urllib.request.urlopen(req, timeout=30) as response: From 845f6ec8cfe6ae7541ef024fb49bd32ce633d572 Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Thu, 16 Oct 2025 02:06:09 -0700 Subject: [PATCH 191/910] build new staging branch --- Jenkinsfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index ad8e85136..73fa74c1c 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -167,7 +167,7 @@ node { env.GIT_COMMIT = checkout(scm).GIT_COMMIT def excludeBranches = ['__nightly', 'devel', 'devel-staging', 'release3', 'release3-staging', - 'release-tici', 'release-tizi', 'testing-closet*', 'hotfix-*'] + 'release-tici', 'release-tizi', 'release-tizi-staging', 'testing-closet*', 'hotfix-*'] def excludeRegex = excludeBranches.join('|').replaceAll('\\*', '.*') if (env.BRANCH_NAME != 'master' && !env.BRANCH_NAME.contains('__jenkins_loop_')) { @@ -178,8 +178,8 @@ node { try { if (env.BRANCH_NAME == 'devel-staging') { - deviceStage("build release3-staging", "tizi-needs-can", [], [ - step("build release3-staging", "RELEASE_BRANCH=release3-staging $SOURCE_DIR/release/build_release.sh"), + deviceStage("build release-tizi-staging", "tizi-needs-can", [], [ + step("build release-tizi-staging", "RELEASE_BRANCH=release-tizi-staging $SOURCE_DIR/release/build_release.sh"), ]) } From 25da8e9d44ee8422d00a56631ef3f9a6bcc7411d Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 16 Oct 2025 02:22:02 -0700 Subject: [PATCH 192/910] raylib: fix crash from too many colors (#36382) * fix * bump --- selfdrive/ui/onroad/model_renderer.py | 8 ++++++-- system/ui/lib/shader_polygon.py | 6 +++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/selfdrive/ui/onroad/model_renderer.py b/selfdrive/ui/onroad/model_renderer.py index 125053f17..1e4fbbb78 100644 --- a/selfdrive/ui/onroad/model_renderer.py +++ b/selfdrive/ui/onroad/model_renderer.py @@ -226,8 +226,12 @@ class ModelRenderer(Widget): i += 1 + (1 if (i + 2) < max_len else 0) # Store the gradient in the path object - self._exp_gradient.colors = segment_colors - self._exp_gradient.stops = gradient_stops + self._exp_gradient = Gradient( + start=(0.0, 1.0), # Bottom of path + end=(0.0, 0.0), # Top of path + colors=segment_colors, + stops=gradient_stops, + ) def _update_lead_vehicle(self, d_rel, v_rel, point, rect): speed_buff, lead_buff = 10.0, 40.0 diff --git a/system/ui/lib/shader_polygon.py b/system/ui/lib/shader_polygon.py index 44c26c80e..28585b08b 100644 --- a/system/ui/lib/shader_polygon.py +++ b/system/ui/lib/shader_polygon.py @@ -5,7 +5,7 @@ from dataclasses import dataclass from typing import Any, Optional, cast from openpilot.system.ui.lib.application import gui_app -MAX_GRADIENT_COLORS = 15 # includes stops as well +MAX_GRADIENT_COLORS = 20 # includes stops as well @dataclass @@ -48,8 +48,8 @@ uniform vec4 fillColor; uniform int useGradient; uniform vec2 gradientStart; // e.g. vec2(0, 0) uniform vec2 gradientEnd; // e.g. vec2(0, screenHeight) -uniform vec4 gradientColors[15]; -uniform float gradientStops[15]; +uniform vec4 gradientColors[20]; +uniform float gradientStops[20]; uniform int gradientColorCount; vec4 getGradientColor(vec2 p) { From 702bebf176dbfd5803c53cdae306eb4545b74d95 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 16 Oct 2025 02:22:49 -0700 Subject: [PATCH 193/910] raylib: fix temporarily untoggleable onroad experimental mode button (#36383) * gpt got it after 2 tries, but still not immed mergeable * bad bot --- selfdrive/ui/onroad/exp_button.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/selfdrive/ui/onroad/exp_button.py b/selfdrive/ui/onroad/exp_button.py index 175233c5b..e5d817141 100644 --- a/selfdrive/ui/onroad/exp_button.py +++ b/selfdrive/ui/onroad/exp_button.py @@ -66,8 +66,5 @@ class ExpButton(Widget): if not self._params.get_bool("ExperimentalModeConfirmed"): return False - car_params = ui_state.sm["carParams"] - if car_params.alphaLongitudinalAvailable: - return self._params.get_bool("AlphaLongitudinalEnabled") - else: - return car_params.openpilotLongitudinalControl + # Mirror exp mode toggle using persistent car params + return ui_state.has_longitudinal_control From d71d2bd2d0a69977d71e58ea2b2ad589ae3d9f89 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 16 Oct 2025 03:45:50 -0700 Subject: [PATCH 194/910] test_onroad: ignore first few ui timing frames (#36385) clean up --- selfdrive/test/test_onroad.py | 3 ++- selfdrive/ui/onroad/cameraview.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/selfdrive/test/test_onroad.py b/selfdrive/test/test_onroad.py index 40f13e11c..00abc525a 100644 --- a/selfdrive/test/test_onroad.py +++ b/selfdrive/test/test_onroad.py @@ -206,7 +206,8 @@ class TestOnroad: result += "-------------- UI Draw Timing ------------------\n" result += "------------------------------------------------\n" - ts = self.ts['uiDebug']['drawTimeMillis'] + # skip first few frames -- connecting to vipc + ts = self.ts['uiDebug']['drawTimeMillis'][10:] result += f"min {min(ts):.2f}ms\n" result += f"max {max(ts):.2f}ms\n" result += f"std {np.std(ts):.2f}ms\n" diff --git a/selfdrive/ui/onroad/cameraview.py b/selfdrive/ui/onroad/cameraview.py index 744fdbf13..5098b6a06 100644 --- a/selfdrive/ui/onroad/cameraview.py +++ b/selfdrive/ui/onroad/cameraview.py @@ -68,6 +68,7 @@ else: class CameraView(Widget): def __init__(self, name: str, stream_type: VisionStreamType): super().__init__() + # TODO: implement a receiver and connect thread self._name = name # Primary stream self.client = VisionIpcClient(name, stream_type, conflate=True) From 64f3759fd0717d9e6d53878aeb30fa91452e2b42 Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Thu, 16 Oct 2025 15:07:45 -0700 Subject: [PATCH 195/910] cleanup release branches --- system/version.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system/version.py b/system/version.py index 9c5a8348f..f59509715 100755 --- a/system/version.py +++ b/system/version.py @@ -10,8 +10,8 @@ from openpilot.common.basedir import BASEDIR from openpilot.common.swaglog import cloudlog from openpilot.common.git import get_commit, get_origin, get_branch, get_short_branch, get_commit_date -RELEASE_BRANCHES = ['release3-staging', 'release3', 'release-tici', 'release-tizi', 'nightly'] -TESTED_BRANCHES = RELEASE_BRANCHES + ['devel', 'devel-staging', 'nightly-dev'] +RELEASE_BRANCHES = ['release-tizi-staging', 'release-tici', 'release-tizi', 'nightly'] +TESTED_BRANCHES = RELEASE_BRANCHES + ['devel-staging', 'nightly-dev'] BUILD_METADATA_FILENAME = "build.json" From ef988aca280f312d55f866e7e8b2aa8e57b673dd Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Thu, 16 Oct 2025 23:23:39 -0700 Subject: [PATCH 196/910] raylib: bump version --- third_party/raylib/build.sh | 2 +- third_party/raylib/larch64/libraylib.a | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/third_party/raylib/build.sh b/third_party/raylib/build.sh index 870f4d597..0b5024448 100755 --- a/third_party/raylib/build.sh +++ b/third_party/raylib/build.sh @@ -30,7 +30,7 @@ fi cd raylib_repo -COMMIT=${1:-4ee3b8ce4ee5a1604a7c055a417178c65fb795ef} +COMMIT=${1:-aa6ade09ac4bfb2847a356535f2d9f87e49ab089} git fetch origin $COMMIT git reset --hard $COMMIT git clean -xdff . diff --git a/third_party/raylib/larch64/libraylib.a b/third_party/raylib/larch64/libraylib.a index a1c5e903e..954aa0d48 100644 --- a/third_party/raylib/larch64/libraylib.a +++ b/third_party/raylib/larch64/libraylib.a @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a73b6b91500a37d08bc2a3ee26951175ca0f95a7d0fa0ec97c823dcdf5a660dc -size 3155684 +oid sha256:8bb734fb8733e1762081945f4f3ddf8d3f7379a0ea4790ee925803cd00129ccb +size 3156548 From 5dabb678ce5860fb0e7cf1041f09712308ccf1c6 Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Thu, 16 Oct 2025 23:35:31 -0700 Subject: [PATCH 197/910] ci: just stop power_monitor on devices --- selfdrive/test/setup_device_ci.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/test/setup_device_ci.sh b/selfdrive/test/setup_device_ci.sh index 98909bfb5..d56f11bcc 100755 --- a/selfdrive/test/setup_device_ci.sh +++ b/selfdrive/test/setup_device_ci.sh @@ -54,7 +54,7 @@ while true; do # /data/ciui.py & #fi - awk '{print \$1}' /proc/uptime > /var/tmp/power_watchdog + sudo systemctl stop power_monitor sleep 5s done From 727a750b3411773eb0873ecb5554a057af852f5f Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Thu, 16 Oct 2025 23:37:44 -0700 Subject: [PATCH 198/910] ci: stop power_monitor once --- selfdrive/test/setup_device_ci.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/test/setup_device_ci.sh b/selfdrive/test/setup_device_ci.sh index d56f11bcc..2a1442a20 100755 --- a/selfdrive/test/setup_device_ci.sh +++ b/selfdrive/test/setup_device_ci.sh @@ -42,6 +42,7 @@ sudo systemctl restart NetworkManager sudo systemctl disable ssh-param-watcher.path sudo systemctl disable ssh-param-watcher.service sudo mount -o ro,remount / +sudo systemctl stop power_monitor while true; do if ! sudo systemctl is-active -q ssh; then @@ -54,7 +55,6 @@ while true; do # /data/ciui.py & #fi - sudo systemctl stop power_monitor sleep 5s done From 92cd656c68432830e7e49348c613d496af4c9d5d Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Fri, 17 Oct 2025 00:26:29 -0700 Subject: [PATCH 199/910] ui: remove watchdog (#36388) out --- selfdrive/ui/ui.py | 3 --- system/manager/process_config.py | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/selfdrive/ui/ui.py b/selfdrive/ui/ui.py index 5fb6fd573..3eb72ec10 100755 --- a/selfdrive/ui/ui.py +++ b/selfdrive/ui/ui.py @@ -1,6 +1,5 @@ #!/usr/bin/env python3 import pyray as rl -from openpilot.common.watchdog import kick_watchdog from openpilot.system.ui.lib.application import gui_app from openpilot.selfdrive.ui.layouts.main import MainLayout from openpilot.selfdrive.ui.ui_state import ui_state @@ -16,8 +15,6 @@ def main(): for showing_dialog in gui_app.render(): ui_state.update() - kick_watchdog() - if not showing_dialog: main_layout.render() diff --git a/system/manager/process_config.py b/system/manager/process_config.py index b8a1e0988..0c35a3d3c 100644 --- a/system/manager/process_config.py +++ b/system/manager/process_config.py @@ -81,7 +81,7 @@ procs = [ PythonProcess("sensord", "system.sensord.sensord", only_onroad, enabled=not PC), # NativeProcess("ui", "selfdrive/ui", ["./ui"], always_run, enabled=False, watchdog_max_dt=(5 if not PC else None)), - PythonProcess("ui", "selfdrive.ui.ui", always_run, watchdog_max_dt=(5 if not PC else None)), + PythonProcess("ui", "selfdrive.ui.ui", always_run), PythonProcess("soundd", "selfdrive.ui.soundd", only_onroad), PythonProcess("locationd", "selfdrive.locationd.locationd", only_onroad), NativeProcess("_pandad", "selfdrive/pandad", ["./pandad"], always_run, enabled=False), From 13d98fd2d589b050a3be0a6f7d11e411f49c449e Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Fri, 17 Oct 2025 00:36:15 -0700 Subject: [PATCH 200/910] test_onroad: skip more frames for ui timings --- selfdrive/test/test_onroad.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/test/test_onroad.py b/selfdrive/test/test_onroad.py index 00abc525a..0c3bdea4c 100644 --- a/selfdrive/test/test_onroad.py +++ b/selfdrive/test/test_onroad.py @@ -207,7 +207,7 @@ class TestOnroad: result += "------------------------------------------------\n" # skip first few frames -- connecting to vipc - ts = self.ts['uiDebug']['drawTimeMillis'][10:] + ts = self.ts['uiDebug']['drawTimeMillis'][15:] result += f"min {min(ts):.2f}ms\n" result += f"max {max(ts):.2f}ms\n" result += f"std {np.std(ts):.2f}ms\n" From 821e4da2c78ff25406c3b40fcf1b0b8867d241b9 Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Fri, 17 Oct 2025 01:12:55 -0700 Subject: [PATCH 201/910] AGNOS 14.1 (#36389) * stag * prod --- launch_env.sh | 2 +- system/hardware/tici/agnos.json | 12 ++++---- system/hardware/tici/all-partitions.json | 36 ++++++++++++------------ 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/launch_env.sh b/launch_env.sh index 74bcf1bae..5c14af6dc 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="14" + export AGNOS_VERSION="14.1" fi export STAGING_ROOT="/data/safe_staging" diff --git a/system/hardware/tici/agnos.json b/system/hardware/tici/agnos.json index bd731f5dd..25cbe9f32 100644 --- a/system/hardware/tici/agnos.json +++ b/system/hardware/tici/agnos.json @@ -67,17 +67,17 @@ }, { "name": "system", - "url": "https://commadist.azureedge.net/agnosupdate/system-6be13dfe085e03955d37fb317a1f2ee58d751eb0ef996c91bbb37c98a12a4ac3.img.xz", - "hash": "eafa98c2dab3bc1bd799416070d724a0fca8138e6e730d391eea6002de9f3f44", - "hash_raw": "6be13dfe085e03955d37fb317a1f2ee58d751eb0ef996c91bbb37c98a12a4ac3", + "url": "https://commadist.azureedge.net/agnosupdate/system-26ddd23c2beea0382b784fdcf2fa86fd2ce1eff42828922a7e0b9d3f8ff77fa3.img.xz", + "hash": "3f65337c978b659408567df57a4787241ffa6ce7d6dff857b9e29c22d010a931", + "hash_raw": "26ddd23c2beea0382b784fdcf2fa86fd2ce1eff42828922a7e0b9d3f8ff77fa3", "size": 5368709120, "sparse": true, "full_check": false, "has_ab": true, - "ondevice_hash": "169c9d230fc0806cb0f30844f38c543a568b1a159e380e33e5be8fce344b96bf", + "ondevice_hash": "91fe31b15ec64a5271093b9ae39dc9d59225ed4f9eb35d3c9dd099f293fb5f0d", "alt": { - "hash": "6be13dfe085e03955d37fb317a1f2ee58d751eb0ef996c91bbb37c98a12a4ac3", - "url": "https://commadist.azureedge.net/agnosupdate/system-6be13dfe085e03955d37fb317a1f2ee58d751eb0ef996c91bbb37c98a12a4ac3.img", + "hash": "26ddd23c2beea0382b784fdcf2fa86fd2ce1eff42828922a7e0b9d3f8ff77fa3", + "url": "https://commadist.azureedge.net/agnosupdate/system-26ddd23c2beea0382b784fdcf2fa86fd2ce1eff42828922a7e0b9d3f8ff77fa3.img", "size": 5368709120 } } diff --git a/system/hardware/tici/all-partitions.json b/system/hardware/tici/all-partitions.json index 38b5ebe7c..298571f31 100644 --- a/system/hardware/tici/all-partitions.json +++ b/system/hardware/tici/all-partitions.json @@ -350,51 +350,51 @@ }, { "name": "system", - "url": "https://commadist.azureedge.net/agnosupdate/system-6be13dfe085e03955d37fb317a1f2ee58d751eb0ef996c91bbb37c98a12a4ac3.img.xz", - "hash": "eafa98c2dab3bc1bd799416070d724a0fca8138e6e730d391eea6002de9f3f44", - "hash_raw": "6be13dfe085e03955d37fb317a1f2ee58d751eb0ef996c91bbb37c98a12a4ac3", + "url": "https://commadist.azureedge.net/agnosupdate/system-26ddd23c2beea0382b784fdcf2fa86fd2ce1eff42828922a7e0b9d3f8ff77fa3.img.xz", + "hash": "3f65337c978b659408567df57a4787241ffa6ce7d6dff857b9e29c22d010a931", + "hash_raw": "26ddd23c2beea0382b784fdcf2fa86fd2ce1eff42828922a7e0b9d3f8ff77fa3", "size": 5368709120, "sparse": true, "full_check": false, "has_ab": true, - "ondevice_hash": "169c9d230fc0806cb0f30844f38c543a568b1a159e380e33e5be8fce344b96bf", + "ondevice_hash": "91fe31b15ec64a5271093b9ae39dc9d59225ed4f9eb35d3c9dd099f293fb5f0d", "alt": { - "hash": "6be13dfe085e03955d37fb317a1f2ee58d751eb0ef996c91bbb37c98a12a4ac3", - "url": "https://commadist.azureedge.net/agnosupdate/system-6be13dfe085e03955d37fb317a1f2ee58d751eb0ef996c91bbb37c98a12a4ac3.img", + "hash": "26ddd23c2beea0382b784fdcf2fa86fd2ce1eff42828922a7e0b9d3f8ff77fa3", + "url": "https://commadist.azureedge.net/agnosupdate/system-26ddd23c2beea0382b784fdcf2fa86fd2ce1eff42828922a7e0b9d3f8ff77fa3.img", "size": 5368709120 } }, { "name": "userdata_90", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_90-dc81085a13dd7c19d94f10e53c6b5aefdefa83bb26a544674b3e768f09604da5.img.xz", - "hash": "085426cdb3a2ee8139817ffb622a3e25f0b95dd1b29faaa0ba99db8e27d4b7ed", - "hash_raw": "dc81085a13dd7c19d94f10e53c6b5aefdefa83bb26a544674b3e768f09604da5", + "url": "https://commadist.azureedge.net/agnosupdate/userdata_90-a154dec5ebad07f63ebef989a1f7e44c449b9fb94b1048157d426ff0e78feef8.img.xz", + "hash": "32ef650ba25cbf867eb4699096e33027aa0ab79e05de2d1dfee3601b00b4fdf6", + "hash_raw": "a154dec5ebad07f63ebef989a1f7e44c449b9fb94b1048157d426ff0e78feef8", "size": 96636764160, "sparse": true, "full_check": true, "has_ab": false, - "ondevice_hash": "10e3b8d0f9a9eb1744368d31015b4397456e18a5fd94584a83669afed00bba92" + "ondevice_hash": "9f21158f9055983c237d47a8eea8e27e978b5f25383756a7a9363a7bd9f7f72e" }, { "name": "userdata_89", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_89-208d12e657cd343134df03ff1af2e2b4526ac03d1f7657bd53bfa76370681251.img.xz", - "hash": "796bd992e35f88cd7765a4e0273069b8cb8b2ba52be323a84716602a4443197b", - "hash_raw": "208d12e657cd343134df03ff1af2e2b4526ac03d1f7657bd53bfa76370681251", + "url": "https://commadist.azureedge.net/agnosupdate/userdata_89-31ebdff72d44d3f60bdf0920e39171795494c275b8cff023cf23ec592af7a4b3.img.xz", + "hash": "a62837b235be14b257baf05ddc6bddd026c8859bbb4f154d0323c7efa58cb938", + "hash_raw": "31ebdff72d44d3f60bdf0920e39171795494c275b8cff023cf23ec592af7a4b3", "size": 95563022336, "sparse": true, "full_check": true, "has_ab": false, - "ondevice_hash": "c647752c040e56dc8d7b1cc23c1a40fa6e30ac5edc93fc4eed10704de1d2cbc7" + "ondevice_hash": "a5caa169c840de6d1804b4186a1d26486be95e1837c4df16ec45952665356942" }, { "name": "userdata_30", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_30-3a4b4129a2d8ca6ceec090eeee73aaffd24c4f2160b280931e967bb83b1e811d.img.xz", - "hash": "e12f2bdd4365dbe4a84a71a6bc311ef995b47c2f7bff4b1691595466da2e017a", - "hash_raw": "3a4b4129a2d8ca6ceec090eeee73aaffd24c4f2160b280931e967bb83b1e811d", + "url": "https://commadist.azureedge.net/agnosupdate/userdata_30-16518389a1ed7ad6277dbab75d18aa13833fb4ed4010f456438f2c2ac8c61140.img.xz", + "hash": "cb8c2fc2ae83cacb86af4ce96c6d61e4bd3cd2591e612e12878c27fa51030ffa", + "hash_raw": "16518389a1ed7ad6277dbab75d18aa13833fb4ed4010f456438f2c2ac8c61140", "size": 32212254720, "sparse": true, "full_check": true, "has_ab": false, - "ondevice_hash": "4f2996a1ae3512e8e803f40028a18344877806f73d4ffd4ae29f66d96055671e" + "ondevice_hash": "5dd8e1f87a3f985ece80f7a36da1cbdabd77bcc11d26fc7bb85540069eff8ead" } ] \ No newline at end of file From 18e8f648c2310327dc422b77d30ddde2f4e2a944 Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Fri, 17 Oct 2025 01:48:08 -0700 Subject: [PATCH 202/910] Revert "AGNOS 14.1 (#36389)" This reverts commit 821e4da2c78ff25406c3b40fcf1b0b8867d241b9. --- launch_env.sh | 2 +- system/hardware/tici/agnos.json | 12 ++++---- system/hardware/tici/all-partitions.json | 36 ++++++++++++------------ 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/launch_env.sh b/launch_env.sh index 5c14af6dc..74bcf1bae 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="14.1" + export AGNOS_VERSION="14" fi export STAGING_ROOT="/data/safe_staging" diff --git a/system/hardware/tici/agnos.json b/system/hardware/tici/agnos.json index 25cbe9f32..bd731f5dd 100644 --- a/system/hardware/tici/agnos.json +++ b/system/hardware/tici/agnos.json @@ -67,17 +67,17 @@ }, { "name": "system", - "url": "https://commadist.azureedge.net/agnosupdate/system-26ddd23c2beea0382b784fdcf2fa86fd2ce1eff42828922a7e0b9d3f8ff77fa3.img.xz", - "hash": "3f65337c978b659408567df57a4787241ffa6ce7d6dff857b9e29c22d010a931", - "hash_raw": "26ddd23c2beea0382b784fdcf2fa86fd2ce1eff42828922a7e0b9d3f8ff77fa3", + "url": "https://commadist.azureedge.net/agnosupdate/system-6be13dfe085e03955d37fb317a1f2ee58d751eb0ef996c91bbb37c98a12a4ac3.img.xz", + "hash": "eafa98c2dab3bc1bd799416070d724a0fca8138e6e730d391eea6002de9f3f44", + "hash_raw": "6be13dfe085e03955d37fb317a1f2ee58d751eb0ef996c91bbb37c98a12a4ac3", "size": 5368709120, "sparse": true, "full_check": false, "has_ab": true, - "ondevice_hash": "91fe31b15ec64a5271093b9ae39dc9d59225ed4f9eb35d3c9dd099f293fb5f0d", + "ondevice_hash": "169c9d230fc0806cb0f30844f38c543a568b1a159e380e33e5be8fce344b96bf", "alt": { - "hash": "26ddd23c2beea0382b784fdcf2fa86fd2ce1eff42828922a7e0b9d3f8ff77fa3", - "url": "https://commadist.azureedge.net/agnosupdate/system-26ddd23c2beea0382b784fdcf2fa86fd2ce1eff42828922a7e0b9d3f8ff77fa3.img", + "hash": "6be13dfe085e03955d37fb317a1f2ee58d751eb0ef996c91bbb37c98a12a4ac3", + "url": "https://commadist.azureedge.net/agnosupdate/system-6be13dfe085e03955d37fb317a1f2ee58d751eb0ef996c91bbb37c98a12a4ac3.img", "size": 5368709120 } } diff --git a/system/hardware/tici/all-partitions.json b/system/hardware/tici/all-partitions.json index 298571f31..38b5ebe7c 100644 --- a/system/hardware/tici/all-partitions.json +++ b/system/hardware/tici/all-partitions.json @@ -350,51 +350,51 @@ }, { "name": "system", - "url": "https://commadist.azureedge.net/agnosupdate/system-26ddd23c2beea0382b784fdcf2fa86fd2ce1eff42828922a7e0b9d3f8ff77fa3.img.xz", - "hash": "3f65337c978b659408567df57a4787241ffa6ce7d6dff857b9e29c22d010a931", - "hash_raw": "26ddd23c2beea0382b784fdcf2fa86fd2ce1eff42828922a7e0b9d3f8ff77fa3", + "url": "https://commadist.azureedge.net/agnosupdate/system-6be13dfe085e03955d37fb317a1f2ee58d751eb0ef996c91bbb37c98a12a4ac3.img.xz", + "hash": "eafa98c2dab3bc1bd799416070d724a0fca8138e6e730d391eea6002de9f3f44", + "hash_raw": "6be13dfe085e03955d37fb317a1f2ee58d751eb0ef996c91bbb37c98a12a4ac3", "size": 5368709120, "sparse": true, "full_check": false, "has_ab": true, - "ondevice_hash": "91fe31b15ec64a5271093b9ae39dc9d59225ed4f9eb35d3c9dd099f293fb5f0d", + "ondevice_hash": "169c9d230fc0806cb0f30844f38c543a568b1a159e380e33e5be8fce344b96bf", "alt": { - "hash": "26ddd23c2beea0382b784fdcf2fa86fd2ce1eff42828922a7e0b9d3f8ff77fa3", - "url": "https://commadist.azureedge.net/agnosupdate/system-26ddd23c2beea0382b784fdcf2fa86fd2ce1eff42828922a7e0b9d3f8ff77fa3.img", + "hash": "6be13dfe085e03955d37fb317a1f2ee58d751eb0ef996c91bbb37c98a12a4ac3", + "url": "https://commadist.azureedge.net/agnosupdate/system-6be13dfe085e03955d37fb317a1f2ee58d751eb0ef996c91bbb37c98a12a4ac3.img", "size": 5368709120 } }, { "name": "userdata_90", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_90-a154dec5ebad07f63ebef989a1f7e44c449b9fb94b1048157d426ff0e78feef8.img.xz", - "hash": "32ef650ba25cbf867eb4699096e33027aa0ab79e05de2d1dfee3601b00b4fdf6", - "hash_raw": "a154dec5ebad07f63ebef989a1f7e44c449b9fb94b1048157d426ff0e78feef8", + "url": "https://commadist.azureedge.net/agnosupdate/userdata_90-dc81085a13dd7c19d94f10e53c6b5aefdefa83bb26a544674b3e768f09604da5.img.xz", + "hash": "085426cdb3a2ee8139817ffb622a3e25f0b95dd1b29faaa0ba99db8e27d4b7ed", + "hash_raw": "dc81085a13dd7c19d94f10e53c6b5aefdefa83bb26a544674b3e768f09604da5", "size": 96636764160, "sparse": true, "full_check": true, "has_ab": false, - "ondevice_hash": "9f21158f9055983c237d47a8eea8e27e978b5f25383756a7a9363a7bd9f7f72e" + "ondevice_hash": "10e3b8d0f9a9eb1744368d31015b4397456e18a5fd94584a83669afed00bba92" }, { "name": "userdata_89", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_89-31ebdff72d44d3f60bdf0920e39171795494c275b8cff023cf23ec592af7a4b3.img.xz", - "hash": "a62837b235be14b257baf05ddc6bddd026c8859bbb4f154d0323c7efa58cb938", - "hash_raw": "31ebdff72d44d3f60bdf0920e39171795494c275b8cff023cf23ec592af7a4b3", + "url": "https://commadist.azureedge.net/agnosupdate/userdata_89-208d12e657cd343134df03ff1af2e2b4526ac03d1f7657bd53bfa76370681251.img.xz", + "hash": "796bd992e35f88cd7765a4e0273069b8cb8b2ba52be323a84716602a4443197b", + "hash_raw": "208d12e657cd343134df03ff1af2e2b4526ac03d1f7657bd53bfa76370681251", "size": 95563022336, "sparse": true, "full_check": true, "has_ab": false, - "ondevice_hash": "a5caa169c840de6d1804b4186a1d26486be95e1837c4df16ec45952665356942" + "ondevice_hash": "c647752c040e56dc8d7b1cc23c1a40fa6e30ac5edc93fc4eed10704de1d2cbc7" }, { "name": "userdata_30", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_30-16518389a1ed7ad6277dbab75d18aa13833fb4ed4010f456438f2c2ac8c61140.img.xz", - "hash": "cb8c2fc2ae83cacb86af4ce96c6d61e4bd3cd2591e612e12878c27fa51030ffa", - "hash_raw": "16518389a1ed7ad6277dbab75d18aa13833fb4ed4010f456438f2c2ac8c61140", + "url": "https://commadist.azureedge.net/agnosupdate/userdata_30-3a4b4129a2d8ca6ceec090eeee73aaffd24c4f2160b280931e967bb83b1e811d.img.xz", + "hash": "e12f2bdd4365dbe4a84a71a6bc311ef995b47c2f7bff4b1691595466da2e017a", + "hash_raw": "3a4b4129a2d8ca6ceec090eeee73aaffd24c4f2160b280931e967bb83b1e811d", "size": 32212254720, "sparse": true, "full_check": true, "has_ab": false, - "ondevice_hash": "5dd8e1f87a3f985ece80f7a36da1cbdabd77bcc11d26fc7bb85540069eff8ead" + "ondevice_hash": "4f2996a1ae3512e8e803f40028a18344877806f73d4ffd4ae29f66d96055671e" } ] \ No newline at end of file From cc683f20400cabb6c8090a7efbbe96905ab04952 Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Fri, 17 Oct 2025 02:50:17 -0700 Subject: [PATCH 203/910] AGNOS 14.2 (#36390) * version * env --- launch_env.sh | 2 +- system/hardware/tici/agnos.json | 12 ++++---- system/hardware/tici/all-partitions.json | 36 ++++++++++++------------ 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/launch_env.sh b/launch_env.sh index 74bcf1bae..07ec162f0 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="14" + export AGNOS_VERSION="14.2" fi export STAGING_ROOT="/data/safe_staging" diff --git a/system/hardware/tici/agnos.json b/system/hardware/tici/agnos.json index bd731f5dd..035d8aa01 100644 --- a/system/hardware/tici/agnos.json +++ b/system/hardware/tici/agnos.json @@ -67,17 +67,17 @@ }, { "name": "system", - "url": "https://commadist.azureedge.net/agnosupdate/system-6be13dfe085e03955d37fb317a1f2ee58d751eb0ef996c91bbb37c98a12a4ac3.img.xz", - "hash": "eafa98c2dab3bc1bd799416070d724a0fca8138e6e730d391eea6002de9f3f44", - "hash_raw": "6be13dfe085e03955d37fb317a1f2ee58d751eb0ef996c91bbb37c98a12a4ac3", + "url": "https://commadist.azureedge.net/agnosupdate/system-1a653b2a2006eb19017b9f091928a51fbb0b91c1ab218971779936892c9bd71b.img.xz", + "hash": "8d4b4dd80a8a537adf82faa07928066bec4568eae73bdcf4a5f0da94fb77b485", + "hash_raw": "1a653b2a2006eb19017b9f091928a51fbb0b91c1ab218971779936892c9bd71b", "size": 5368709120, "sparse": true, "full_check": false, "has_ab": true, - "ondevice_hash": "169c9d230fc0806cb0f30844f38c543a568b1a159e380e33e5be8fce344b96bf", + "ondevice_hash": "a9569b9286fba882be003f9710383ae6de229a72db936e80be08dbd2c23f320e", "alt": { - "hash": "6be13dfe085e03955d37fb317a1f2ee58d751eb0ef996c91bbb37c98a12a4ac3", - "url": "https://commadist.azureedge.net/agnosupdate/system-6be13dfe085e03955d37fb317a1f2ee58d751eb0ef996c91bbb37c98a12a4ac3.img", + "hash": "1a653b2a2006eb19017b9f091928a51fbb0b91c1ab218971779936892c9bd71b", + "url": "https://commadist.azureedge.net/agnosupdate/system-1a653b2a2006eb19017b9f091928a51fbb0b91c1ab218971779936892c9bd71b.img", "size": 5368709120 } } diff --git a/system/hardware/tici/all-partitions.json b/system/hardware/tici/all-partitions.json index 38b5ebe7c..6b99df1c3 100644 --- a/system/hardware/tici/all-partitions.json +++ b/system/hardware/tici/all-partitions.json @@ -350,51 +350,51 @@ }, { "name": "system", - "url": "https://commadist.azureedge.net/agnosupdate/system-6be13dfe085e03955d37fb317a1f2ee58d751eb0ef996c91bbb37c98a12a4ac3.img.xz", - "hash": "eafa98c2dab3bc1bd799416070d724a0fca8138e6e730d391eea6002de9f3f44", - "hash_raw": "6be13dfe085e03955d37fb317a1f2ee58d751eb0ef996c91bbb37c98a12a4ac3", + "url": "https://commadist.azureedge.net/agnosupdate/system-1a653b2a2006eb19017b9f091928a51fbb0b91c1ab218971779936892c9bd71b.img.xz", + "hash": "8d4b4dd80a8a537adf82faa07928066bec4568eae73bdcf4a5f0da94fb77b485", + "hash_raw": "1a653b2a2006eb19017b9f091928a51fbb0b91c1ab218971779936892c9bd71b", "size": 5368709120, "sparse": true, "full_check": false, "has_ab": true, - "ondevice_hash": "169c9d230fc0806cb0f30844f38c543a568b1a159e380e33e5be8fce344b96bf", + "ondevice_hash": "a9569b9286fba882be003f9710383ae6de229a72db936e80be08dbd2c23f320e", "alt": { - "hash": "6be13dfe085e03955d37fb317a1f2ee58d751eb0ef996c91bbb37c98a12a4ac3", - "url": "https://commadist.azureedge.net/agnosupdate/system-6be13dfe085e03955d37fb317a1f2ee58d751eb0ef996c91bbb37c98a12a4ac3.img", + "hash": "1a653b2a2006eb19017b9f091928a51fbb0b91c1ab218971779936892c9bd71b", + "url": "https://commadist.azureedge.net/agnosupdate/system-1a653b2a2006eb19017b9f091928a51fbb0b91c1ab218971779936892c9bd71b.img", "size": 5368709120 } }, { "name": "userdata_90", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_90-dc81085a13dd7c19d94f10e53c6b5aefdefa83bb26a544674b3e768f09604da5.img.xz", - "hash": "085426cdb3a2ee8139817ffb622a3e25f0b95dd1b29faaa0ba99db8e27d4b7ed", - "hash_raw": "dc81085a13dd7c19d94f10e53c6b5aefdefa83bb26a544674b3e768f09604da5", + "url": "https://commadist.azureedge.net/agnosupdate/userdata_90-a154dec5ebad07f63ebef989a1f7e44c449b9fb94b1048157d426ff0e78feef8.img.xz", + "hash": "32ef650ba25cbf867eb4699096e33027aa0ab79e05de2d1dfee3601b00b4fdf6", + "hash_raw": "a154dec5ebad07f63ebef989a1f7e44c449b9fb94b1048157d426ff0e78feef8", "size": 96636764160, "sparse": true, "full_check": true, "has_ab": false, - "ondevice_hash": "10e3b8d0f9a9eb1744368d31015b4397456e18a5fd94584a83669afed00bba92" + "ondevice_hash": "9f21158f9055983c237d47a8eea8e27e978b5f25383756a7a9363a7bd9f7f72e" }, { "name": "userdata_89", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_89-208d12e657cd343134df03ff1af2e2b4526ac03d1f7657bd53bfa76370681251.img.xz", - "hash": "796bd992e35f88cd7765a4e0273069b8cb8b2ba52be323a84716602a4443197b", - "hash_raw": "208d12e657cd343134df03ff1af2e2b4526ac03d1f7657bd53bfa76370681251", + "url": "https://commadist.azureedge.net/agnosupdate/userdata_89-31ebdff72d44d3f60bdf0920e39171795494c275b8cff023cf23ec592af7a4b3.img.xz", + "hash": "a62837b235be14b257baf05ddc6bddd026c8859bbb4f154d0323c7efa58cb938", + "hash_raw": "31ebdff72d44d3f60bdf0920e39171795494c275b8cff023cf23ec592af7a4b3", "size": 95563022336, "sparse": true, "full_check": true, "has_ab": false, - "ondevice_hash": "c647752c040e56dc8d7b1cc23c1a40fa6e30ac5edc93fc4eed10704de1d2cbc7" + "ondevice_hash": "a5caa169c840de6d1804b4186a1d26486be95e1837c4df16ec45952665356942" }, { "name": "userdata_30", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_30-3a4b4129a2d8ca6ceec090eeee73aaffd24c4f2160b280931e967bb83b1e811d.img.xz", - "hash": "e12f2bdd4365dbe4a84a71a6bc311ef995b47c2f7bff4b1691595466da2e017a", - "hash_raw": "3a4b4129a2d8ca6ceec090eeee73aaffd24c4f2160b280931e967bb83b1e811d", + "url": "https://commadist.azureedge.net/agnosupdate/userdata_30-16518389a1ed7ad6277dbab75d18aa13833fb4ed4010f456438f2c2ac8c61140.img.xz", + "hash": "cb8c2fc2ae83cacb86af4ce96c6d61e4bd3cd2591e612e12878c27fa51030ffa", + "hash_raw": "16518389a1ed7ad6277dbab75d18aa13833fb4ed4010f456438f2c2ac8c61140", "size": 32212254720, "sparse": true, "full_check": true, "has_ab": false, - "ondevice_hash": "4f2996a1ae3512e8e803f40028a18344877806f73d4ffd4ae29f66d96055671e" + "ondevice_hash": "5dd8e1f87a3f985ece80f7a36da1cbdabd77bcc11d26fc7bb85540069eff8ead" } ] \ No newline at end of file From 646f6a1006d69ba6c073f7048776fb86327c4288 Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Fri, 17 Oct 2025 19:27:13 -0500 Subject: [PATCH 204/910] raylib screenshots: alpha long toggle confirmation dialog (#36386) add alpha long toggle confirmation test --- selfdrive/ui/tests/test_ui/raylib_screenshots.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py index 6de774648..2e96b3bd4 100755 --- a/selfdrive/ui/tests/test_ui/raylib_screenshots.py +++ b/selfdrive/ui/tests/test_ui/raylib_screenshots.py @@ -9,7 +9,7 @@ from collections import namedtuple import pyautogui import pywinctl -from cereal import log +from cereal import car, log from cereal import messaging from cereal.messaging import PubMaster from openpilot.common.basedir import BASEDIR @@ -95,6 +95,10 @@ def setup_settings_firehose(click, pm: PubMaster): def setup_settings_developer(click, pm: PubMaster): + CP = car.CarParams() + CP.alphaLongitudinalAvailable = True # show alpha long control toggle + Params().put("CarParamsPersistent", CP.to_bytes()) + setup_settings(click, pm) click(278, 950) @@ -129,6 +133,11 @@ def setup_experimental_mode_description(click, pm: PubMaster): click(1200, 280) # expand description for experimental mode +def setup_openpilot_long_confirmation_dialog(click, pm: PubMaster): + setup_settings_developer(click, pm) + click(2000, 960) # toggle openpilot longitudinal control + + def setup_onroad(click, pm: PubMaster): ds = messaging.new_message('deviceState') ds.deviceState.started = True @@ -238,6 +247,7 @@ CASES = { "offroad_alert": setup_offroad_alert, "confirmation_dialog": setup_confirmation_dialog, "experimental_mode_description": setup_experimental_mode_description, + "openpilot_long_confirmation_dialog": setup_openpilot_long_confirmation_dialog, "onroad": setup_onroad, "onroad_sidebar": setup_onroad_sidebar, "onroad_small_alert": setup_onroad_small_alert, From 1f5e0b6f682470e2308778d35b3995d44cc46c0e Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 17 Oct 2025 19:00:06 -0700 Subject: [PATCH 205/910] raylib: show dialog when attempting to pair without internet (#36396) * match qt * clean up * bb * ofc * use alert_dialog --- selfdrive/ui/widgets/setup.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/selfdrive/ui/widgets/setup.py b/selfdrive/ui/widgets/setup.py index 46a587735..0538570e4 100644 --- a/selfdrive/ui/widgets/setup.py +++ b/selfdrive/ui/widgets/setup.py @@ -1,9 +1,11 @@ import pyray as rl +from openpilot.common.time_helpers import system_time_valid from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.selfdrive.ui.widgets.pairing_dialog import PairingDialog from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE from openpilot.system.ui.lib.wrap_text import wrap_text from openpilot.system.ui.widgets import Widget +from openpilot.system.ui.widgets.confirm_dialog import alert_dialog from openpilot.system.ui.widgets.button import Button, ButtonStyle from openpilot.system.ui.widgets.label import Label @@ -84,6 +86,11 @@ class SetupWidget(Widget): self._open_settings_btn.render(button_rect) def _show_pairing(self): + if not system_time_valid(): + dlg = alert_dialog("Please connect to Wi-Fi to complete initial pairing") + gui_app.set_modal_overlay(dlg) + return + if not self._pairing_dialog: self._pairing_dialog = PairingDialog() gui_app.set_modal_overlay(self._pairing_dialog, lambda result: setattr(self, '_pairing_dialog', None)) From b28425b8c3261ac6e406f0f3a9b726e0a2b28c3c Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 17 Oct 2025 19:11:12 -0700 Subject: [PATCH 206/910] raylib: fix broken pairing dialog first 5m after startup (#36397) * always try on dialog show * except logging * huge oof --- selfdrive/ui/widgets/pairing_dialog.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/selfdrive/ui/widgets/pairing_dialog.py b/selfdrive/ui/widgets/pairing_dialog.py index 252a7dd94..3778586f8 100644 --- a/selfdrive/ui/widgets/pairing_dialog.py +++ b/selfdrive/ui/widgets/pairing_dialog.py @@ -34,7 +34,7 @@ class PairingDialog(Widget): super().__init__() self.params = Params() self.qr_texture: rl.Texture | None = None - self.last_qr_generation = 0 + self.last_qr_generation = float('-inf') self._close_btn = IconButton(gui_app.texture("icons/close.png", 80, 80)) self._close_btn.set_click_callback(lambda: gui_app.set_modal_overlay(None)) @@ -42,8 +42,8 @@ class PairingDialog(Widget): try: dongle_id = self.params.get("DongleId") or "" token = Api(dongle_id).get_token({'pair': True}) - except Exception as e: - cloudlog.warning(f"Failed to get pairing token: {e}") + except Exception: + cloudlog.exception("Failed to get pairing token") token = "" return f"https://connect.comma.ai/?pair={token}" @@ -67,8 +67,8 @@ class PairingDialog(Widget): rl_image.format = rl.PixelFormat.PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 self.qr_texture = rl.load_texture_from_image(rl_image) - except Exception as e: - cloudlog.warning(f"QR code generation failed: {e}") + except Exception: + cloudlog.exception("QR code generation failed") self.qr_texture = None def _check_qr_refresh(self) -> None: From 7534b2a160faa683412c04c1254440e338931c5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Sat, 18 Oct 2025 11:12:47 -0700 Subject: [PATCH 207/910] PID: no more ff gain (#36398) * No more ff gain * typo --- common/pid.py | 5 ++--- selfdrive/controls/lib/latcontrol_pid.py | 5 +++-- selfdrive/controls/lib/latcontrol_torque.py | 3 +-- selfdrive/controls/lib/longcontrol.py | 2 +- system/hardware/fan_controller.py | 2 +- 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/common/pid.py b/common/pid.py index ff5084781..e3fa8afdf 100644 --- a/common/pid.py +++ b/common/pid.py @@ -2,11 +2,10 @@ import numpy as np from numbers import Number class PIDController: - def __init__(self, k_p, k_i, k_f=0., k_d=0., pos_limit=1e308, neg_limit=-1e308, rate=100): + def __init__(self, k_p, k_i, k_d=0., pos_limit=1e308, neg_limit=-1e308, rate=100): self._k_p = k_p self._k_i = k_i self._k_d = k_d - self.k_f = k_f # feedforward gain if isinstance(self._k_p, Number): self._k_p = [[0], [self._k_p]] if isinstance(self._k_i, Number): @@ -48,7 +47,7 @@ class PIDController: self.speed = speed self.p = self.k_p * float(error) self.d = self.k_d * error_rate - self.f = self.k_f * feedforward + self.f = feedforward if not freeze_integrator: i = self.i + self.k_i * self.i_dt * error diff --git a/selfdrive/controls/lib/latcontrol_pid.py b/selfdrive/controls/lib/latcontrol_pid.py index 4cc1c47f6..14ab9f21b 100644 --- a/selfdrive/controls/lib/latcontrol_pid.py +++ b/selfdrive/controls/lib/latcontrol_pid.py @@ -10,7 +10,8 @@ class LatControlPID(LatControl): super().__init__(CP, CI, dt) self.pid = PIDController((CP.lateralTuning.pid.kpBP, CP.lateralTuning.pid.kpV), (CP.lateralTuning.pid.kiBP, CP.lateralTuning.pid.kiV), - k_f=CP.lateralTuning.pid.kf, pos_limit=self.steer_max, neg_limit=-self.steer_max) + pos_limit=self.steer_max, neg_limit=-self.steer_max) + self.ff_factor = CP.lateralTuning.pid.kf self.get_steer_feedforward = CI.get_steer_feedforward_function() def update(self, active, CS, VM, params, steer_limited_by_safety, desired_curvature, curvature_limited, lat_delay): @@ -30,7 +31,7 @@ class LatControlPID(LatControl): else: # offset does not contribute to resistive torque - ff = self.get_steer_feedforward(angle_steers_des_no_offset, CS.vEgo) + ff = self.ff_factor * self.get_steer_feedforward(angle_steers_des_no_offset, CS.vEgo) freeze_integrator = steer_limited_by_safety or CS.steeringPressed or CS.vEgo < 5 output_torque = self.pid.update(error, diff --git a/selfdrive/controls/lib/latcontrol_torque.py b/selfdrive/controls/lib/latcontrol_torque.py index 664e86663..fbef5828e 100644 --- a/selfdrive/controls/lib/latcontrol_torque.py +++ b/selfdrive/controls/lib/latcontrol_torque.py @@ -32,8 +32,7 @@ class LatControlTorque(LatControl): self.torque_params = CP.lateralTuning.torque.as_builder() self.torque_from_lateral_accel = CI.torque_from_lateral_accel() self.lateral_accel_from_torque = CI.lateral_accel_from_torque() - self.pid = PIDController(self.torque_params.kp, self.torque_params.ki, - k_f=self.torque_params.kf, rate=1/self.dt) + self.pid = PIDController(self.torque_params.kp, self.torque_params.ki, rate=1/self.dt) self.update_limits() self.steering_angle_deadzone_deg = self.torque_params.steeringAngleDeadzoneDeg self.LATACCEL_REQUEST_BUFFER_NUM_FRAMES = int(1 / self.dt) diff --git a/selfdrive/controls/lib/longcontrol.py b/selfdrive/controls/lib/longcontrol.py index 6d4f92246..62dbc842c 100644 --- a/selfdrive/controls/lib/longcontrol.py +++ b/selfdrive/controls/lib/longcontrol.py @@ -50,7 +50,7 @@ class LongControl: self.long_control_state = LongCtrlState.off self.pid = PIDController((CP.longitudinalTuning.kpBP, CP.longitudinalTuning.kpV), (CP.longitudinalTuning.kiBP, CP.longitudinalTuning.kiV), - k_f=CP.longitudinalTuning.kf, rate=1 / DT_CTRL) + rate=1 / DT_CTRL) self.last_output_accel = 0.0 def reset(self): diff --git a/system/hardware/fan_controller.py b/system/hardware/fan_controller.py index 4c7adc0a3..365688429 100755 --- a/system/hardware/fan_controller.py +++ b/system/hardware/fan_controller.py @@ -18,7 +18,7 @@ class TiciFanController(BaseFanController): cloudlog.info("Setting up TICI fan handler") self.last_ignition = False - self.controller = PIDController(k_p=0, k_i=4e-3, k_f=1, rate=(1 / DT_HW)) + self.controller = PIDController(k_p=0, k_i=4e-3, rate=(1 / DT_HW)) def update(self, cur_temp: float, ignition: bool) -> int: self.controller.pos_limit = 100 if ignition else 30 From 3ef5037c1654e1d9142dff85db85a7e2a972d212 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 20 Oct 2025 11:40:03 -0700 Subject: [PATCH 208/910] uploader: fix env var parsing --- system/loggerd/uploader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/loggerd/uploader.py b/system/loggerd/uploader.py index 38fc0e920..bc1957250 100755 --- a/system/loggerd/uploader.py +++ b/system/loggerd/uploader.py @@ -29,7 +29,7 @@ MAX_UPLOAD_SIZES = { "qcam": 5*1e6, } -allow_sleep = bool(os.getenv("UPLOADER_SLEEP", "1")) +allow_sleep = bool(int(os.getenv("UPLOADER_SLEEP", "1"))) force_wifi = os.getenv("FORCEWIFI") is not None fake_upload = os.getenv("FAKEUPLOAD") is not None From 3c957c6e9d8f05138b8a80523d50db5b5ca2cb73 Mon Sep 17 00:00:00 2001 From: YassineYousfi Date: Mon, 20 Oct 2025 14:09:42 -0700 Subject: [PATCH 209/910] =?UTF-8?q?The=20Cool=20People's=20model=20?= =?UTF-8?q?=F0=9F=98=8E=20(#36249)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * cb8f0d7e-6627-4d7f-ad97-10d0078f2d2c/400 * ci? * fd9a6816-8758-466b-bbde-3c1413b98f0a/400 --- selfdrive/modeld/models/driving_policy.onnx | 4 ++-- selfdrive/modeld/models/driving_vision.onnx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/selfdrive/modeld/models/driving_policy.onnx b/selfdrive/modeld/models/driving_policy.onnx index 89444c3ea..1e764af9b 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:792fca7c24faadc1183327eec7e04d06dd6ec0e53ac7bb43cb2b9823c6cfb32f -size 12343535 +oid sha256:c5a1f0655ddf266ed42ad1980389d96f47cc5e756da1fa3ca1477a920bb9b157 +size 13926324 diff --git a/selfdrive/modeld/models/driving_vision.onnx b/selfdrive/modeld/models/driving_vision.onnx index 0411ddb74..441c4a16a 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:cf6376aa9a090f0da26c280ef69eabf9bbdd51d1faac9ed392919c3db69be916 +oid sha256:8f16d548ea4eb5d01518a9e90d4527cd97c31a84bcaf6f695dead8f0015fecc4 size 46271942 From 8752093801d5c7212fe5c2cb1c728b3003a7ddce Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 20 Oct 2025 15:45:44 -0700 Subject: [PATCH 210/910] raylib: fix option dialog (#36405) * fix dialog * rm --- system/ui/widgets/option_dialog.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/system/ui/widgets/option_dialog.py b/system/ui/widgets/option_dialog.py index 813e9cca8..b7e30b696 100644 --- a/system/ui/widgets/option_dialog.py +++ b/system/ui/widgets/option_dialog.py @@ -1,6 +1,6 @@ import pyray as rl -from openpilot.system.ui.lib.application import FontWeight, gui_app -from openpilot.system.ui.widgets import Widget +from openpilot.system.ui.lib.application import FontWeight +from openpilot.system.ui.widgets import Widget, DialogResult from openpilot.system.ui.widgets.button import Button, ButtonStyle from openpilot.system.ui.widgets.label import gui_label from openpilot.system.ui.widgets.scroller import Scroller @@ -22,14 +22,19 @@ class MultiOptionDialog(Widget): self.options = options self.current = current self.selection = current + self._result: DialogResult = DialogResult.NO_ACTION # Create scroller with option buttons self.option_buttons = [Button(option, click_callback=lambda opt=option: self._on_option_clicked(opt), - text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT, button_style=ButtonStyle.NORMAL) for option in options] + text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT, button_style=ButtonStyle.NORMAL, + text_padding=50, elide_right=True) for option in options] self.scroller = Scroller(self.option_buttons, spacing=LIST_ITEM_SPACING) - self.cancel_button = Button("Cancel", click_callback=lambda: gui_app.set_modal_overlay(None)) - self.select_button = Button("Select", click_callback=lambda: gui_app.set_modal_overlay(None), button_style=ButtonStyle.PRIMARY) + self.cancel_button = Button("Cancel", click_callback=lambda: self._set_result(DialogResult.CANCEL)) + self.select_button = Button("Select", click_callback=lambda: self._set_result(DialogResult.CONFIRM), button_style=ButtonStyle.PRIMARY) + + def _set_result(self, result: DialogResult): + self._result = result def _on_option_clicked(self, option): self.selection = option @@ -68,4 +73,4 @@ class MultiOptionDialog(Widget): self.select_button.set_enabled(self.selection != self.current) self.select_button.render(select_rect) - return -1 + return self._result From 8720e5d7128d98724ba1d2fb0bcc56577761a253 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 20 Oct 2025 16:14:02 -0700 Subject: [PATCH 211/910] tools: pass args to op adb --- tools/op.sh | 2 +- tools/scripts/adb_ssh.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/op.sh b/tools/op.sh index ae12809eb..8b5062ad9 100755 --- a/tools/op.sh +++ b/tools/op.sh @@ -284,7 +284,7 @@ function op_venv() { function op_adb() { op_before_cmd - op_run_command tools/scripts/adb_ssh.sh + op_run_command tools/scripts/adb_ssh.sh "$@" } function op_ssh() { diff --git a/tools/scripts/adb_ssh.sh b/tools/scripts/adb_ssh.sh index 43c8e07de..2fe2873a3 100755 --- a/tools/scripts/adb_ssh.sh +++ b/tools/scripts/adb_ssh.sh @@ -4,4 +4,4 @@ set -e # this is a little nicer than "adb shell" since # "adb shell" doesn't do full terminal emulation adb forward tcp:2222 tcp:22 -ssh comma@localhost -p 2222 +ssh comma@localhost -p 2222 "$@" From 01715f6f9a857c75d9eb9d049adfa5163e7beebf Mon Sep 17 00:00:00 2001 From: Bruce Wayne Date: Mon, 20 Oct 2025 16:26:22 -0700 Subject: [PATCH 212/910] test car model: use factor for torque --- selfdrive/car/tests/test_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/car/tests/test_models.py b/selfdrive/car/tests/test_models.py index 8996ad646..94f5b3323 100644 --- a/selfdrive/car/tests/test_models.py +++ b/selfdrive/car/tests/test_models.py @@ -183,7 +183,7 @@ class TestCarModelBase(unittest.TestCase): if tuning == 'pid': self.assertTrue(len(self.CP.lateralTuning.pid.kpV)) elif tuning == 'torque': - self.assertTrue(self.CP.lateralTuning.torque.kf > 0) + self.assertTrue(self.CP.lateralTuning.torque.latAccelFactor > 0) else: raise Exception("unknown tuning") From b2e3dd17ea520c693976aa9db887ea24d566a18b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Mon, 20 Oct 2025 17:16:03 -0700 Subject: [PATCH 213/910] torque gains not car specific (#36404) * torque gains not car specific * remove opendbc interfaces longitudinal control kf field assignment that makes hitl test fail * typo * another typo * bump * bump openbc * update ref --------- Co-authored-by: felsager --- opendbc_repo | 2 +- selfdrive/controls/lib/latcontrol_torque.py | 7 +++++-- selfdrive/test/process_replay/ref_commit | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/opendbc_repo b/opendbc_repo index dfa6807eb..b59f8bdcc 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit dfa6807ebf9f0edb593189a703b857c834a88a75 +Subproject commit b59f8bdcca8d375b4a5a652d2f2d2ec9cd3503d3 diff --git a/selfdrive/controls/lib/latcontrol_torque.py b/selfdrive/controls/lib/latcontrol_torque.py index fbef5828e..f2b1e0b66 100644 --- a/selfdrive/controls/lib/latcontrol_torque.py +++ b/selfdrive/controls/lib/latcontrol_torque.py @@ -24,6 +24,9 @@ from openpilot.common.pid import PIDController LOW_SPEED_X = [0, 10, 20, 30] LOW_SPEED_Y = [15, 13, 10, 5] +KP = 1.0 +KI = 0.3 +KD = 0.0 class LatControlTorque(LatControl): @@ -32,7 +35,7 @@ class LatControlTorque(LatControl): self.torque_params = CP.lateralTuning.torque.as_builder() self.torque_from_lateral_accel = CI.torque_from_lateral_accel() self.lateral_accel_from_torque = CI.lateral_accel_from_torque() - self.pid = PIDController(self.torque_params.kp, self.torque_params.ki, rate=1/self.dt) + self.pid = PIDController(KP, KI, k_d=KD, rate=1/self.dt) self.update_limits() self.steering_angle_deadzone_deg = self.torque_params.steeringAngleDeadzoneDeg self.LATACCEL_REQUEST_BUFFER_NUM_FRAMES = int(1 / self.dt) @@ -76,7 +79,7 @@ class LatControlTorque(LatControl): low_speed_factor = (np.interp(CS.vEgo, LOW_SPEED_X, LOW_SPEED_Y) / max(CS.vEgo, MIN_SPEED)) ** 2 setpoint = lat_delay * desired_lateral_jerk + expected_lateral_accel error = setpoint - measurement - error_lsf = error + low_speed_factor / self.torque_params.kp * error + error_lsf = error + low_speed_factor / KP * error # do error correction in lateral acceleration space, convert at end to handle non-linear torque responses correctly pid_log.error = float(error_lsf) diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 538aa34f2..dd73ed12f 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -5342f5684c2498a33d6178f3b59efd5e83b73acc \ No newline at end of file +55e82ab6370865a1427ebc1d559921a5354d9cbf \ No newline at end of file From 338119229745c39a0a9d433d11a3f2057c9ef74f Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 20 Oct 2025 18:35:34 -0700 Subject: [PATCH 214/910] Multilang: remove main prefix (#36406) * rename * fix --- common/params_keys.h | 2 +- selfdrive/ui/.gitignore | 2 +- .../ui/tests/create_test_translations.sh | 4 ++-- selfdrive/ui/tests/test_runner.cc | 2 +- selfdrive/ui/tests/test_translations.py | 2 +- .../ui/translations/{main_ar.ts => ar.ts} | 0 selfdrive/ui/translations/auto_translate.py | 2 +- .../ui/translations/{main_de.ts => de.ts} | 0 .../ui/translations/{main_en.ts => en.ts} | 0 .../ui/translations/{main_es.ts => es.ts} | 0 .../ui/translations/{main_fr.ts => fr.ts} | 0 .../ui/translations/{main_ja.ts => ja.ts} | 0 .../ui/translations/{main_ko.ts => ko.ts} | 0 selfdrive/ui/translations/languages.json | 24 +++++++++---------- .../ui/translations/{main_nl.ts => nl.ts} | 0 .../ui/translations/{main_pl.ts => pl.ts} | 0 .../translations/{main_pt-BR.ts => pt-BR.ts} | 0 .../ui/translations/{main_th.ts => th.ts} | 0 .../ui/translations/{main_tr.ts => tr.ts} | 0 .../{main_zh-CHS.ts => zh-CHS.ts} | 0 .../{main_zh-CHT.ts => zh-CHT.ts} | 0 selfdrive/ui/update_translations.py | 2 +- 22 files changed, 20 insertions(+), 20 deletions(-) rename selfdrive/ui/translations/{main_ar.ts => ar.ts} (100%) rename selfdrive/ui/translations/{main_de.ts => de.ts} (100%) rename selfdrive/ui/translations/{main_en.ts => en.ts} (100%) rename selfdrive/ui/translations/{main_es.ts => es.ts} (100%) rename selfdrive/ui/translations/{main_fr.ts => fr.ts} (100%) rename selfdrive/ui/translations/{main_ja.ts => ja.ts} (100%) rename selfdrive/ui/translations/{main_ko.ts => ko.ts} (100%) rename selfdrive/ui/translations/{main_nl.ts => nl.ts} (100%) rename selfdrive/ui/translations/{main_pl.ts => pl.ts} (100%) rename selfdrive/ui/translations/{main_pt-BR.ts => pt-BR.ts} (100%) rename selfdrive/ui/translations/{main_th.ts => th.ts} (100%) rename selfdrive/ui/translations/{main_tr.ts => tr.ts} (100%) rename selfdrive/ui/translations/{main_zh-CHS.ts => zh-CHS.ts} (100%) rename selfdrive/ui/translations/{main_zh-CHT.ts => zh-CHT.ts} (100%) diff --git a/common/params_keys.h b/common/params_keys.h index 211b4d550..f6e8d781c 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -66,7 +66,7 @@ inline static std::unordered_map keys = { {"IsTakingSnapshot", {CLEAR_ON_MANAGER_START, BOOL}}, {"IsTestedBranch", {CLEAR_ON_MANAGER_START, BOOL}}, {"JoystickDebugMode", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}}, - {"LanguageSetting", {PERSISTENT, STRING, "main_en"}}, + {"LanguageSetting", {PERSISTENT, STRING, "en"}}, {"LastAthenaPingTime", {CLEAR_ON_MANAGER_START, INT}}, {"LastGPSPosition", {PERSISTENT, STRING}}, {"LastManagerExitReason", {CLEAR_ON_MANAGER_START, STRING}}, diff --git a/selfdrive/ui/.gitignore b/selfdrive/ui/.gitignore index 7e9eaf932..5e7b02a1a 100644 --- a/selfdrive/ui/.gitignore +++ b/selfdrive/ui/.gitignore @@ -1,7 +1,7 @@ moc_* *.moc -translations/main_test_en.* +translations/test_en.* ui mui diff --git a/selfdrive/ui/tests/create_test_translations.sh b/selfdrive/ui/tests/create_test_translations.sh index ed0890d94..1587a8820 100755 --- a/selfdrive/ui/tests/create_test_translations.sh +++ b/selfdrive/ui/tests/create_test_translations.sh @@ -4,8 +4,8 @@ set -e UI_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"/.. TEST_TEXT="(WRAPPED_SOURCE_TEXT)" -TEST_TS_FILE=$UI_DIR/translations/main_test_en.ts -TEST_QM_FILE=$UI_DIR/translations/main_test_en.qm +TEST_TS_FILE=$UI_DIR/translations/test_en.ts +TEST_QM_FILE=$UI_DIR/translations/test_en.qm # translation strings UNFINISHED="<\/translation>" diff --git a/selfdrive/ui/tests/test_runner.cc b/selfdrive/ui/tests/test_runner.cc index c8cc0d3e0..4bde92169 100644 --- a/selfdrive/ui/tests/test_runner.cc +++ b/selfdrive/ui/tests/test_runner.cc @@ -10,7 +10,7 @@ int main(int argc, char **argv) { // unit tests for Qt QApplication app(argc, argv); - QString language_file = "main_test_en"; + QString language_file = "test_en"; // FIXME: pytest-cpp considers this print as a test case qDebug() << "Loading language:" << language_file; diff --git a/selfdrive/ui/tests/test_translations.py b/selfdrive/ui/tests/test_translations.py index 2ae3356bb..edd9a3041 100644 --- a/selfdrive/ui/tests/test_translations.py +++ b/selfdrive/ui/tests/test_translations.py @@ -93,7 +93,7 @@ class TestTranslations: def test_bad_language(self): IGNORED_WORDS = {'pédale'} - match = re.search(r'_([a-zA-Z]{2,3})', self.file) + match = re.search(r'([a-zA-Z]{2,3})', self.file) assert match, f"{self.name} - could not parse language" try: diff --git a/selfdrive/ui/translations/main_ar.ts b/selfdrive/ui/translations/ar.ts similarity index 100% rename from selfdrive/ui/translations/main_ar.ts rename to selfdrive/ui/translations/ar.ts diff --git a/selfdrive/ui/translations/auto_translate.py b/selfdrive/ui/translations/auto_translate.py index c2e4bbc55..6251e0339 100755 --- a/selfdrive/ui/translations/auto_translate.py +++ b/selfdrive/ui/translations/auto_translate.py @@ -26,7 +26,7 @@ def get_language_files(languages: list[str] = None) -> dict[str, pathlib.Path]: for filename in language_dict.values(): path = TRANSLATIONS_DIR / f"{filename}.ts" - language = path.stem.split("main_")[1] + language = path.stem if languages is None or language in languages: files[language] = path diff --git a/selfdrive/ui/translations/main_de.ts b/selfdrive/ui/translations/de.ts similarity index 100% rename from selfdrive/ui/translations/main_de.ts rename to selfdrive/ui/translations/de.ts diff --git a/selfdrive/ui/translations/main_en.ts b/selfdrive/ui/translations/en.ts similarity index 100% rename from selfdrive/ui/translations/main_en.ts rename to selfdrive/ui/translations/en.ts diff --git a/selfdrive/ui/translations/main_es.ts b/selfdrive/ui/translations/es.ts similarity index 100% rename from selfdrive/ui/translations/main_es.ts rename to selfdrive/ui/translations/es.ts diff --git a/selfdrive/ui/translations/main_fr.ts b/selfdrive/ui/translations/fr.ts similarity index 100% rename from selfdrive/ui/translations/main_fr.ts rename to selfdrive/ui/translations/fr.ts diff --git a/selfdrive/ui/translations/main_ja.ts b/selfdrive/ui/translations/ja.ts similarity index 100% rename from selfdrive/ui/translations/main_ja.ts rename to selfdrive/ui/translations/ja.ts diff --git a/selfdrive/ui/translations/main_ko.ts b/selfdrive/ui/translations/ko.ts similarity index 100% rename from selfdrive/ui/translations/main_ko.ts rename to selfdrive/ui/translations/ko.ts diff --git a/selfdrive/ui/translations/languages.json b/selfdrive/ui/translations/languages.json index 132b5088d..b0674dee8 100644 --- a/selfdrive/ui/translations/languages.json +++ b/selfdrive/ui/translations/languages.json @@ -1,14 +1,14 @@ { - "English": "main_en", - "Deutsch": "main_de", - "Français": "main_fr", - "Português": "main_pt-BR", - "Español": "main_es", - "Türkçe": "main_tr", - "العربية": "main_ar", - "ไทย": "main_th", - "中文(繁體)": "main_zh-CHT", - "中文(简体)": "main_zh-CHS", - "한국어": "main_ko", - "日本語": "main_ja" + "English": "en", + "Deutsch": "de", + "Français": "fr", + "Português": "pt-BR", + "Español": "es", + "Türkçe": "tr", + "العربية": "ar", + "ไทย": "th", + "中文(繁體)": "zh-CHT", + "中文(简体)": "zh-CHS", + "한국어": "ko", + "日本語": "ja" } diff --git a/selfdrive/ui/translations/main_nl.ts b/selfdrive/ui/translations/nl.ts similarity index 100% rename from selfdrive/ui/translations/main_nl.ts rename to selfdrive/ui/translations/nl.ts diff --git a/selfdrive/ui/translations/main_pl.ts b/selfdrive/ui/translations/pl.ts similarity index 100% rename from selfdrive/ui/translations/main_pl.ts rename to selfdrive/ui/translations/pl.ts diff --git a/selfdrive/ui/translations/main_pt-BR.ts b/selfdrive/ui/translations/pt-BR.ts similarity index 100% rename from selfdrive/ui/translations/main_pt-BR.ts rename to selfdrive/ui/translations/pt-BR.ts diff --git a/selfdrive/ui/translations/main_th.ts b/selfdrive/ui/translations/th.ts similarity index 100% rename from selfdrive/ui/translations/main_th.ts rename to selfdrive/ui/translations/th.ts diff --git a/selfdrive/ui/translations/main_tr.ts b/selfdrive/ui/translations/tr.ts similarity index 100% rename from selfdrive/ui/translations/main_tr.ts rename to selfdrive/ui/translations/tr.ts diff --git a/selfdrive/ui/translations/main_zh-CHS.ts b/selfdrive/ui/translations/zh-CHS.ts similarity index 100% rename from selfdrive/ui/translations/main_zh-CHS.ts rename to selfdrive/ui/translations/zh-CHS.ts diff --git a/selfdrive/ui/translations/main_zh-CHT.ts b/selfdrive/ui/translations/zh-CHT.ts similarity index 100% rename from selfdrive/ui/translations/main_zh-CHT.ts rename to selfdrive/ui/translations/zh-CHT.ts diff --git a/selfdrive/ui/update_translations.py b/selfdrive/ui/update_translations.py index 65880bdad..424b78851 100755 --- a/selfdrive/ui/update_translations.py +++ b/selfdrive/ui/update_translations.py @@ -9,7 +9,7 @@ UI_DIR = os.path.join(BASEDIR, "selfdrive", "ui") TRANSLATIONS_DIR = os.path.join(UI_DIR, "translations") LANGUAGES_FILE = os.path.join(TRANSLATIONS_DIR, "languages.json") TRANSLATIONS_INCLUDE_FILE = os.path.join(TRANSLATIONS_DIR, "alerts_generated.h") -PLURAL_ONLY = ["main_en"] # base language, only create entries for strings with plural forms +PLURAL_ONLY = ["en"] # base language, only create entries for strings with plural forms def generate_translations_include(): From 9801e486d9898d0d89b6d22d9cd4686a4920e2a5 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 20 Oct 2025 18:36:13 -0700 Subject: [PATCH 215/910] fix incorrect Button argument --- system/ui/widgets/option_dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/ui/widgets/option_dialog.py b/system/ui/widgets/option_dialog.py index b7e30b696..593a371fd 100644 --- a/system/ui/widgets/option_dialog.py +++ b/system/ui/widgets/option_dialog.py @@ -27,7 +27,7 @@ class MultiOptionDialog(Widget): # Create scroller with option buttons self.option_buttons = [Button(option, click_callback=lambda opt=option: self._on_option_clicked(opt), text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT, button_style=ButtonStyle.NORMAL, - text_padding=50, elide_right=True) for option in options] + text_padding=50) for option in options] self.scroller = Scroller(self.option_buttons, spacing=LIST_ITEM_SPACING) self.cancel_button = Button("Cancel", click_callback=lambda: self._set_result(DialogResult.CANCEL)) From 650946cd2ace2f55631f13466b1906249afada59 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Tue, 21 Oct 2025 10:17:10 +0800 Subject: [PATCH 216/910] raylib:use context manager for BytesIO (#36407) use context manager for BytesIO --- system/ui/lib/emoji.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/system/ui/lib/emoji.py b/system/ui/lib/emoji.py index dc85c0faf..54f742952 100644 --- a/system/ui/lib/emoji.py +++ b/system/ui/lib/emoji.py @@ -41,9 +41,9 @@ def emoji_tex(emoji): draw = ImageDraw.Draw(img) font = ImageFont.truetype(FONT_DIR.joinpath("NotoColorEmoji.ttf"), 109) draw.text((0, 0), emoji, font=font, embedded_color=True) - buffer = io.BytesIO() - img.save(buffer, format="PNG") - l = buffer.tell() - buffer.seek(0) - _cache[emoji] = rl.load_texture_from_image(rl.load_image_from_memory(".png", buffer.getvalue(), l)) + with io.BytesIO() as buffer: + img.save(buffer, format="PNG") + l = buffer.tell() + buffer.seek(0) + _cache[emoji] = rl.load_texture_from_image(rl.load_image_from_memory(".png", buffer.getvalue(), l)) return _cache[emoji] From 9b2f7341d85fbdeaa743ebc83a79fa04e9074686 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 20 Oct 2025 21:39:04 -0700 Subject: [PATCH 217/910] raylib: wrap text for multilang (#36410) * fix multilang dialog height * split to file * stash * Revert "stash" This reverts commit deb4239fe69f0260420fad03f2350e622e31542f. * add updater * add files * stuff * try rev * stash * works! * works! * this should be the flow? * cursor wrapping -- it missed entire sections, changed formatting, and didn't use trn properly!!!!!!!!!!!!!!!!! * update translations * learned my lesson * this should be the one thing it's good at * update trans * onroad wrap * spanish * rename * clean up * load all * Revert "load all" This reverts commit 6f2a45861c914ffb9d40a5edd15751afd798d614. * jp translations * try jp * Revert "try jp" This reverts commit d0524b10110104baafcdc1ec385c3d57bc5ef901. * remove languages we can't add rn * tr * pt and fr * ai cannot be trusted * ai cannot be trusted * missing trans * add fonts * Revert "remove languages we can't add rn" This reverts commit 73dc75fae2b9e347d867b6636dab6e2b5fe59da7. * painfully slow to startup * only load what we need * Reapply "remove languages we can't add rn" This reverts commit 52cb48f3b838520a421f9b90e5ea4409c27d4bd0. * stash! * rm * Revert "stash!" This reverts commit 31d7c361079a8e57039a0117c81d59bf84f191c7. * revert this * revert that * make this dynamic! * device * revert * firehose * stuff * revert application * back * full revert * clean up * network * more system * fix dat * fixy --- selfdrive/ui/layouts/home.py | 5 +- selfdrive/ui/layouts/onboarding.py | 15 +++--- selfdrive/ui/layouts/settings/developer.py | 19 +++---- selfdrive/ui/layouts/settings/device.py | 59 +++++++++++---------- selfdrive/ui/layouts/settings/firehose.py | 14 ++--- selfdrive/ui/layouts/settings/settings.py | 13 ++--- selfdrive/ui/layouts/settings/software.py | 41 +++++++------- selfdrive/ui/layouts/settings/toggles.py | 53 +++++++++--------- selfdrive/ui/layouts/sidebar.py | 39 +++++++------- selfdrive/ui/onroad/alert_renderer.py | 13 ++--- selfdrive/ui/onroad/driver_camera_dialog.py | 3 +- selfdrive/ui/onroad/hud_renderer.py | 5 +- selfdrive/ui/update_translations.py | 4 +- selfdrive/ui/widgets/exp_mode_button.py | 3 +- selfdrive/ui/widgets/offroad_alerts.py | 11 ++-- selfdrive/ui/widgets/pairing_dialog.py | 11 ++-- selfdrive/ui/widgets/prime.py | 13 ++--- selfdrive/ui/widgets/setup.py | 15 +++--- selfdrive/ui/widgets/ssh_key.py | 15 +++--- system/ui/lib/multilang.py | 10 ++++ system/ui/widgets/confirm_dialog.py | 9 +++- system/ui/widgets/html_render.py | 3 +- system/ui/widgets/keyboard.py | 5 +- system/ui/widgets/list_view.py | 5 +- system/ui/widgets/network.py | 49 ++++++++--------- system/ui/widgets/option_dialog.py | 5 +- 26 files changed, 238 insertions(+), 199 deletions(-) create mode 100644 system/ui/lib/multilang.py diff --git a/selfdrive/ui/layouts/home.py b/selfdrive/ui/layouts/home.py index 519e0e020..34a7558cd 100644 --- a/selfdrive/ui/layouts/home.py +++ b/selfdrive/ui/layouts/home.py @@ -9,6 +9,7 @@ from openpilot.selfdrive.ui.widgets.prime import PrimeWidget from openpilot.selfdrive.ui.widgets.setup import SetupWidget from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos +from openpilot.system.ui.lib.multilang import tr, trn from openpilot.system.ui.widgets.label import gui_label from openpilot.system.ui.widgets import Widget @@ -151,7 +152,7 @@ class HomeLayout(Widget): highlight_color = rl.Color(75, 95, 255, 255) if self.current_state == HomeLayoutState.UPDATE else rl.Color(54, 77, 239, 255) rl.draw_rectangle_rounded(self.update_notif_rect, 0.3, 10, highlight_color) - text = "UPDATE" + text = tr("UPDATE") text_size = measure_text_cached(font, text, HEAD_BUTTON_FONT_SIZE) text_x = self.update_notif_rect.x + (self.update_notif_rect.width - text_size.x) // 2 text_y = self.update_notif_rect.y + (self.update_notif_rect.height - text_size.y) // 2 @@ -165,7 +166,7 @@ class HomeLayout(Widget): highlight_color = rl.Color(255, 70, 70, 255) if self.current_state == HomeLayoutState.ALERTS else rl.Color(226, 44, 44, 255) rl.draw_rectangle_rounded(self.alert_notif_rect, 0.3, 10, highlight_color) - alert_text = f"{self.alert_count} ALERT{'S' if self.alert_count > 1 else ''}" + alert_text = trn("{} ALERT", "{} ALERTS", self.alert_count).format(self.alert_count) text_size = measure_text_cached(font, alert_text, HEAD_BUTTON_FONT_SIZE) text_x = self.alert_notif_rect.x + (self.alert_notif_rect.width - text_size.x) // 2 text_y = self.alert_notif_rect.y + (self.alert_notif_rect.height - text_size.y) // 2 diff --git a/selfdrive/ui/layouts/onboarding.py b/selfdrive/ui/layouts/onboarding.py index a817fc53a..df259a8fb 100644 --- a/selfdrive/ui/layouts/onboarding.py +++ b/selfdrive/ui/layouts/onboarding.py @@ -6,6 +6,7 @@ from enum import IntEnum import pyray as rl from openpilot.common.basedir import BASEDIR from openpilot.system.ui.lib.application import FontWeight, gui_app +from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.button import Button, ButtonStyle from openpilot.system.ui.widgets.label import Label @@ -107,12 +108,12 @@ class TermsPage(Widget): self._on_accept = on_accept self._on_decline = on_decline - self._title = Label("Welcome to openpilot", font_size=90, font_weight=FontWeight.BOLD, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT) - self._desc = Label("You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing.", + self._title = Label(tr("Welcome to openpilot"), font_size=90, font_weight=FontWeight.BOLD, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT) + self._desc = Label(tr("You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing."), font_size=90, font_weight=FontWeight.MEDIUM, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT) - self._decline_btn = Button("Decline", click_callback=on_decline) - self._accept_btn = Button("Agree", button_style=ButtonStyle.PRIMARY, click_callback=on_accept) + self._decline_btn = Button(tr("Decline"), click_callback=on_decline) + self._accept_btn = Button(tr("Agree"), button_style=ButtonStyle.PRIMARY, click_callback=on_accept) def _render(self, _): welcome_x = self._rect.x + 165 @@ -141,10 +142,10 @@ class TermsPage(Widget): class DeclinePage(Widget): def __init__(self, back_callback=None): super().__init__() - self._text = Label("You must accept the Terms and Conditions in order to use openpilot.", + self._text = Label(tr("You must accept the Terms and Conditions in order to use openpilot."), font_size=90, font_weight=FontWeight.MEDIUM, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT) - self._back_btn = Button("Back", click_callback=back_callback) - self._uninstall_btn = Button("Decline, uninstall openpilot", button_style=ButtonStyle.DANGER, + self._back_btn = Button(tr("Back"), click_callback=back_callback) + self._uninstall_btn = Button(tr("Decline, uninstall openpilot"), button_style=ButtonStyle.DANGER, click_callback=self._on_uninstall_clicked) def _on_uninstall_clicked(self): diff --git a/selfdrive/ui/layouts/settings/developer.py b/selfdrive/ui/layouts/settings/developer.py index 21eb44efe..91268960c 100644 --- a/selfdrive/ui/layouts/settings/developer.py +++ b/selfdrive/ui/layouts/settings/developer.py @@ -6,19 +6,20 @@ from openpilot.system.ui.widgets.list_view import toggle_item from openpilot.system.ui.widgets.scroller import Scroller from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.widgets import DialogResult # Description constants DESCRIPTIONS = { - 'enable_adb': ( + 'enable_adb': tr( "ADB (Android Debug Bridge) allows connecting to your device over USB or over the network. " + "See https://docs.comma.ai/how-to/connect-to-comma for more info." ), - 'ssh_key': ( + 'ssh_key': tr( "Warning: This grants SSH access to all public keys in your GitHub settings. Never enter a GitHub username " + "other than your own. A comma employee will NEVER ask you to add their GitHub username." ), - 'alpha_longitudinal': ( + 'alpha_longitudinal': tr( "WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB).

" + "On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. " + "Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha." @@ -34,7 +35,7 @@ class DeveloperLayout(Widget): # Build items and keep references for callbacks/state updates self._adb_toggle = toggle_item( - "Enable ADB", + tr("Enable ADB"), description=DESCRIPTIONS["enable_adb"], initial_state=self._params.get_bool("AdbEnabled"), callback=self._on_enable_adb, @@ -43,7 +44,7 @@ class DeveloperLayout(Widget): # SSH enable toggle + SSH key management self._ssh_toggle = toggle_item( - "Enable SSH", + tr("Enable SSH"), description="", initial_state=self._params.get_bool("SshEnabled"), callback=self._on_enable_ssh, @@ -51,7 +52,7 @@ class DeveloperLayout(Widget): self._ssh_keys = ssh_key_item("SSH Keys", description=DESCRIPTIONS["ssh_key"]) self._joystick_toggle = toggle_item( - "Joystick Debug Mode", + tr("Joystick Debug Mode"), description="", initial_state=self._params.get_bool("JoystickDebugMode"), callback=self._on_joystick_debug_mode, @@ -59,14 +60,14 @@ class DeveloperLayout(Widget): ) self._long_maneuver_toggle = toggle_item( - "Longitudinal Maneuver Mode", + tr("Longitudinal Maneuver Mode"), description="", initial_state=self._params.get_bool("LongitudinalManeuverMode"), callback=self._on_long_maneuver_mode, ) self._alpha_long_toggle = toggle_item( - "openpilot Longitudinal Control (Alpha)", + tr("openpilot Longitudinal Control (Alpha)"), description=DESCRIPTIONS["alpha_longitudinal"], initial_state=self._params.get_bool("AlphaLongitudinalEnabled"), callback=self._on_alpha_long_enabled, @@ -163,7 +164,7 @@ class DeveloperLayout(Widget): content = (f"

{self._alpha_long_toggle.title}


" + f"

{self._alpha_long_toggle.description}

") - dlg = ConfirmDialog(content, "Enable", rich=True) + dlg = ConfirmDialog(content, tr("Enable"), rich=True) gui_app.set_modal_overlay(dlg, callback=confirm_callback) else: diff --git a/selfdrive/ui/layouts/settings/device.py b/selfdrive/ui/layouts/settings/device.py index 66341db37..e9bdf59e2 100644 --- a/selfdrive/ui/layouts/settings/device.py +++ b/selfdrive/ui/layouts/settings/device.py @@ -12,6 +12,7 @@ from openpilot.selfdrive.ui.layouts.onboarding import TrainingGuide from openpilot.selfdrive.ui.widgets.pairing_dialog import PairingDialog from openpilot.system.hardware import TICI from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.widgets import Widget, DialogResult from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog, alert_dialog from openpilot.system.ui.widgets.html_render import HtmlModal @@ -21,10 +22,10 @@ from openpilot.system.ui.widgets.scroller import Scroller # Description constants DESCRIPTIONS = { - 'pair_device': "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer.", - 'driver_camera': "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)", - 'reset_calibration': "openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down.", - 'review_guide': "Review the rules, features, and limitations of openpilot", + 'pair_device': tr("Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer."), + 'driver_camera': tr("Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)"), + 'reset_calibration': tr("openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down."), + 'review_guide': tr("Review the rules, features, and limitations of openpilot"), } @@ -45,27 +46,27 @@ class DeviceLayout(Widget): ui_state.add_offroad_transition_callback(self._offroad_transition) def _initialize_items(self): - dongle_id = self._params.get("DongleId") or "N/A" - serial = self._params.get("HardwareSerial") or "N/A" + dongle_id = self._params.get("DongleId") or tr("N/A") + serial = self._params.get("HardwareSerial") or tr("N/A") - self._pair_device_btn = button_item("Pair Device", "PAIR", DESCRIPTIONS['pair_device'], callback=self._pair_device) + self._pair_device_btn = button_item(tr("Pair Device"), tr("PAIR"), DESCRIPTIONS['pair_device'], callback=self._pair_device) self._pair_device_btn.set_visible(lambda: not ui_state.prime_state.is_paired()) - self._reset_calib_btn = button_item("Reset Calibration", "RESET", DESCRIPTIONS['reset_calibration'], callback=self._reset_calibration_prompt) + self._reset_calib_btn = button_item(tr("Reset Calibration"), tr("RESET"), DESCRIPTIONS['reset_calibration'], callback=self._reset_calibration_prompt) self._reset_calib_btn.set_description_opened_callback(self._update_calib_description) - self._power_off_btn = dual_button_item("Reboot", "Power Off", left_callback=self._reboot_prompt, right_callback=self._power_off_prompt) + self._power_off_btn = dual_button_item(tr("Reboot"), tr("Power Off"), left_callback=self._reboot_prompt, right_callback=self._power_off_prompt) items = [ - text_item("Dongle ID", dongle_id), - text_item("Serial", serial), + text_item(tr("Dongle ID"), dongle_id), + text_item(tr("Serial"), serial), self._pair_device_btn, - button_item("Driver Camera", "PREVIEW", DESCRIPTIONS['driver_camera'], callback=self._show_driver_camera, enabled=ui_state.is_offroad), + button_item(tr("Driver Camera"), tr("PREVIEW"), DESCRIPTIONS['driver_camera'], callback=self._show_driver_camera, enabled=ui_state.is_offroad), self._reset_calib_btn, - button_item("Review Training Guide", "REVIEW", DESCRIPTIONS['review_guide'], self._on_review_training_guide, enabled=ui_state.is_offroad), - regulatory_btn := button_item("Regulatory", "VIEW", callback=self._on_regulatory, enabled=ui_state.is_offroad), + button_item(tr("Review Training Guide"), tr("REVIEW"), DESCRIPTIONS['review_guide'], self._on_review_training_guide, enabled=ui_state.is_offroad), + regulatory_btn := button_item(tr("Regulatory"), tr("VIEW"), callback=self._on_regulatory, enabled=ui_state.is_offroad), # TODO: implement multilang - # button_item("Change Language", "CHANGE", callback=self._show_language_selection, enabled=ui_state.is_offroad), + # button_item(tr("Change Language"), tr("CHANGE"), callback=self._show_language_selection, enabled=ui_state.is_offroad), self._power_off_btn, ] regulatory_btn.set_visible(TICI) @@ -106,7 +107,7 @@ class DeviceLayout(Widget): def _reset_calibration_prompt(self): if ui_state.engaged: - gui_app.set_modal_overlay(alert_dialog("Disengage to Reset Calibration")) + gui_app.set_modal_overlay(alert_dialog(tr("Disengage to Reset Calibration"))) return def reset_calibration(result: int): @@ -122,7 +123,7 @@ class DeviceLayout(Widget): self._params.put_bool("OnroadCycleRequested", True) self._update_calib_description() - dialog = ConfirmDialog("Are you sure you want to reset calibration?", "Reset") + dialog = ConfirmDialog(tr("Are you sure you want to reset calibration?"), tr("Reset")) gui_app.set_modal_overlay(dialog, callback=reset_calibration) def _update_calib_description(self): @@ -136,7 +137,8 @@ class DeviceLayout(Widget): if calib.calStatus != log.LiveCalibrationData.Status.uncalibrated: pitch = math.degrees(calib.rpyCalib[1]) yaw = math.degrees(calib.rpyCalib[2]) - desc += f" Your device is pointed {abs(pitch):.1f}° {'down' if pitch > 0 else 'up'} and {abs(yaw):.1f}° {'left' if yaw > 0 else 'right'}." + desc += tr(" Your device is pointed {:.1f}° {} and {:.1f}° {}.").format(abs(pitch), tr("down") if pitch > 0 else tr("up"), + abs(yaw), tr("left") if yaw > 0 else tr("right")) except Exception: cloudlog.exception("invalid CalibrationParams") @@ -148,9 +150,9 @@ class DeviceLayout(Widget): except Exception: cloudlog.exception("invalid LiveDelay") if lag_perc < 100: - desc += f"

Steering lag calibration is {lag_perc}% complete." + desc += tr("

Steering lag calibration is {}% complete.").format(lag_perc) else: - desc += "

Steering lag calibration is complete." + desc += tr("

Steering lag calibration is complete.") torque_bytes = self._params.get("LiveTorqueParameters") if torque_bytes: @@ -160,24 +162,24 @@ class DeviceLayout(Widget): if torque.useParams: torque_perc = torque.calPerc if torque_perc < 100: - desc += f" Steering torque response calibration is {torque_perc}% complete." + desc += tr(" Steering torque response calibration is {}% complete.").format(torque_perc) else: - desc += " Steering torque response calibration is complete." + desc += tr(" Steering torque response calibration is complete.") except Exception: cloudlog.exception("invalid LiveTorqueParameters") desc += "

" - desc += ("openpilot is continuously calibrating, resetting is rarely required. " + - "Resetting calibration will restart openpilot if the car is powered on.") + desc += tr("openpilot is continuously calibrating, resetting is rarely required. " + + "Resetting calibration will restart openpilot if the car is powered on.") self._reset_calib_btn.set_description(desc) def _reboot_prompt(self): if ui_state.engaged: - gui_app.set_modal_overlay(alert_dialog("Disengage to Reboot")) + gui_app.set_modal_overlay(alert_dialog(tr("Disengage to Reboot"))) return - dialog = ConfirmDialog("Are you sure you want to reboot?", "Reboot") + dialog = ConfirmDialog(tr("Are you sure you want to reboot?"), tr("Reboot")) gui_app.set_modal_overlay(dialog, callback=self._perform_reboot) def _perform_reboot(self, result: int): @@ -186,10 +188,10 @@ class DeviceLayout(Widget): def _power_off_prompt(self): if ui_state.engaged: - gui_app.set_modal_overlay(alert_dialog("Disengage to Power Off")) + gui_app.set_modal_overlay(alert_dialog(tr("Disengage to Power Off"))) return - dialog = ConfirmDialog("Are you sure you want to power off?", "Power Off") + dialog = ConfirmDialog(tr("Are you sure you want to power off?"), tr("Power Off")) gui_app.set_modal_overlay(dialog, callback=self._perform_power_off) def _perform_power_off(self, result: int): @@ -210,5 +212,6 @@ class DeviceLayout(Widget): if not self._training_guide: def completed_callback(): gui_app.set_modal_overlay(None) + self._training_guide = TrainingGuide(completed_callback=completed_callback) gui_app.set_modal_overlay(self._training_guide) diff --git a/selfdrive/ui/layouts/settings/firehose.py b/selfdrive/ui/layouts/settings/firehose.py index e8eaaa44f..f4d70eaa4 100644 --- a/selfdrive/ui/layouts/settings/firehose.py +++ b/selfdrive/ui/layouts/settings/firehose.py @@ -8,19 +8,20 @@ from openpilot.common.swaglog import cloudlog from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.system.athena.registration import UNREGISTERED_DONGLE_ID from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE +from openpilot.system.ui.lib.multilang import tr, trn from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel from openpilot.system.ui.lib.wrap_text import wrap_text from openpilot.system.ui.widgets import Widget from openpilot.selfdrive.ui.lib.api_helpers import get_token -TITLE = "Firehose Mode" -DESCRIPTION = ( +TITLE = tr("Firehose Mode") +DESCRIPTION = tr( "openpilot learns to drive by watching humans, like you, drive.\n\n" + "Firehose Mode allows you to maximize your training data uploads to improve " + "openpilot's driving models. More data means bigger models, which means better Experimental Mode." ) -INSTRUCTIONS = ( +INSTRUCTIONS = tr( "For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.\n\n" + "Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.\n\n\n" + "Frequently Asked Questions\n\n" @@ -106,7 +107,8 @@ class FirehoseLayout(Widget): # Contribution count (if available) if self.segment_count > 0: - contrib_text = f"{self.segment_count} segment(s) of your driving is in the training dataset so far." + contrib_text = trn("{} segment of your driving is in the training dataset so far.", + "{} segments of your driving is in the training dataset so far.", self.segment_count).format(self.segment_count) y = self._draw_wrapped_text(x, y, w, contrib_text, gui_app.font(FontWeight.BOLD), 52, rl.WHITE) y += 20 + 20 @@ -132,9 +134,9 @@ class FirehoseLayout(Widget): network_metered = ui_state.sm["deviceState"].networkMetered if not network_metered and network_type != 0: # Not metered and connected - return "ACTIVE", self.GREEN + return tr("ACTIVE"), self.GREEN else: - return "INACTIVE: connect to an unmetered network", self.RED + return tr("INACTIVE: connect to an unmetered network"), self.RED def _fetch_firehose_stats(self): try: diff --git a/selfdrive/ui/layouts/settings/settings.py b/selfdrive/ui/layouts/settings/settings.py index d43382f19..7f431d3a7 100644 --- a/selfdrive/ui/layouts/settings/settings.py +++ b/selfdrive/ui/layouts/settings/settings.py @@ -8,6 +8,7 @@ from openpilot.selfdrive.ui.layouts.settings.firehose import FirehoseLayout from openpilot.selfdrive.ui.layouts.settings.software import SoftwareLayout from openpilot.selfdrive.ui.layouts.settings.toggles import TogglesLayout from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos +from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.lib.wifi_manager import WifiManager from openpilot.system.ui.widgets import Widget @@ -58,12 +59,12 @@ class SettingsLayout(Widget): wifi_manager.set_active(False) self._panels = { - PanelType.DEVICE: PanelInfo("Device", DeviceLayout()), - PanelType.NETWORK: PanelInfo("Network", NetworkUI(wifi_manager)), - PanelType.TOGGLES: PanelInfo("Toggles", TogglesLayout()), - PanelType.SOFTWARE: PanelInfo("Software", SoftwareLayout()), - PanelType.FIREHOSE: PanelInfo("Firehose", FirehoseLayout()), - PanelType.DEVELOPER: PanelInfo("Developer", DeveloperLayout()), + PanelType.DEVICE: PanelInfo(tr("Device"), DeviceLayout()), + PanelType.NETWORK: PanelInfo(tr("Network"), NetworkUI(wifi_manager)), + PanelType.TOGGLES: PanelInfo(tr("Toggles"), TogglesLayout()), + PanelType.SOFTWARE: PanelInfo(tr("Software"), SoftwareLayout()), + PanelType.FIREHOSE: PanelInfo(tr("Firehose"), FirehoseLayout()), + PanelType.DEVELOPER: PanelInfo(tr("Developer"), DeveloperLayout()), } self._font_medium = gui_app.font(FontWeight.MEDIUM) diff --git a/selfdrive/ui/layouts/settings/software.py b/selfdrive/ui/layouts/settings/software.py index 0c17a54fb..8e0cfdbc3 100644 --- a/selfdrive/ui/layouts/settings/software.py +++ b/selfdrive/ui/layouts/settings/software.py @@ -4,6 +4,7 @@ import datetime from openpilot.common.time_helpers import system_time_valid from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.lib.multilang import tr, trn from openpilot.system.ui.widgets import Widget, DialogResult from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog from openpilot.system.ui.widgets.list_view import button_item, text_item, ListItem @@ -15,7 +16,7 @@ UPDATED_TIMEOUT = 10 # seconds to wait for updated to respond def time_ago(date: datetime.datetime | None) -> str: if not date: - return "never" + return tr("never") if not system_time_valid(): return date.strftime("%a %b %d %Y") @@ -26,16 +27,16 @@ def time_ago(date: datetime.datetime | None) -> str: diff_seconds = int((now - date).total_seconds()) if diff_seconds < 60: - return "now" + return tr("now") if diff_seconds < 3600: m = diff_seconds // 60 - return f"{m} minute{'s' if m != 1 else ''} ago" + return trn("{} minute ago", "{} minutes ago", m).format(m) if diff_seconds < 86400: h = diff_seconds // 3600 - return f"{h} hour{'s' if h != 1 else ''} ago" + return trn("{} hour ago", "{} hours ago", h).format(h) if diff_seconds < 604800: d = diff_seconds // 86400 - return f"{d} day{'s' if d != 1 else ''} ago" + return trn("{} day ago", "{} days ago", d).format(d) return date.strftime("%a %b %d %Y") @@ -43,12 +44,12 @@ class SoftwareLayout(Widget): def __init__(self): super().__init__() - self._onroad_label = ListItem(title="Updates are only downloaded while the car is off.") - self._version_item = text_item("Current Version", ui_state.params.get("UpdaterCurrentDescription") or "") - self._download_btn = button_item("Download", "CHECK", callback=self._on_download_update) + self._onroad_label = ListItem(title=tr("Updates are only downloaded while the car is off.")) + self._version_item = text_item(tr("Current Version"), ui_state.params.get("UpdaterCurrentDescription") or "") + self._download_btn = button_item(tr("Download"), tr("CHECK"), callback=self._on_download_update) # Install button is initially hidden - self._install_btn = button_item("Install Update", "INSTALL", callback=self._on_install_update) + self._install_btn = button_item(tr("Install Update"), tr("INSTALL"), callback=self._on_install_update) self._install_btn.set_visible(False) # Track waiting-for-updater transition to avoid brief re-enable while still idle @@ -66,7 +67,7 @@ class SoftwareLayout(Widget): self._install_btn, # TODO: implement branch switching # button_item("Target Branch", "SELECT", callback=self._on_select_branch), - button_item("Uninstall", "UNINSTALL", callback=self._on_uninstall), + button_item("Uninstall", tr("UNINSTALL"), callback=self._on_uninstall), ] return items @@ -101,19 +102,19 @@ class SoftwareLayout(Widget): self._download_btn.action_item.set_value(updater_state) else: if failed_count > 0: - self._download_btn.action_item.set_value("failed to check for update") - self._download_btn.action_item.set_text("CHECK") + self._download_btn.action_item.set_value(tr("failed to check for update")) + self._download_btn.action_item.set_text(tr("CHECK")) elif fetch_available: - self._download_btn.action_item.set_value("update available") - self._download_btn.action_item.set_text("DOWNLOAD") + self._download_btn.action_item.set_value(tr("update available")) + self._download_btn.action_item.set_text(tr("DOWNLOAD")) else: last_update = ui_state.params.get("LastUpdateTime") if last_update: formatted = time_ago(last_update) - self._download_btn.action_item.set_value(f"up to date, last checked {formatted}") + self._download_btn.action_item.set_value(tr("up to date, last checked {}").format(formatted)) else: - self._download_btn.action_item.set_value("up to date, last checked never") - self._download_btn.action_item.set_text("CHECK") + self._download_btn.action_item.set_value(tr("up to date, last checked never")) + self._download_btn.action_item.set_text(tr("CHECK")) # If we've been waiting too long without a state change, reset state if self._waiting_for_updater and (time.monotonic() - self._waiting_start_ts > UPDATED_TIMEOUT): @@ -127,7 +128,7 @@ class SoftwareLayout(Widget): if update_available: new_desc = ui_state.params.get("UpdaterNewDescription") or "" new_release_notes = (ui_state.params.get("UpdaterNewReleaseNotes") or b"").decode("utf-8", "replace") - self._install_btn.action_item.set_text("INSTALL") + self._install_btn.action_item.set_text(tr("INSTALL")) self._install_btn.action_item.set_value(new_desc) self._install_btn.set_description(new_release_notes) # Enable install button for testing (like Qt showEvent) @@ -138,7 +139,7 @@ class SoftwareLayout(Widget): def _on_download_update(self): # Check if we should start checking or start downloading self._download_btn.action_item.set_enabled(False) - if self._download_btn.action_item.text == "CHECK": + if self._download_btn.action_item.text == tr("CHECK"): # Start checking for updates self._waiting_for_updater = True self._waiting_start_ts = time.monotonic() @@ -154,7 +155,7 @@ class SoftwareLayout(Widget): if result == DialogResult.CONFIRM: ui_state.params.put_bool("DoUninstall", True) - dialog = ConfirmDialog("Are you sure you want to uninstall?", "Uninstall") + dialog = ConfirmDialog(tr("Are you sure you want to uninstall?"), tr("Uninstall")) gui_app.set_modal_overlay(dialog, callback=handle_uninstall_confirmation) def _on_install_update(self): diff --git a/selfdrive/ui/layouts/settings/toggles.py b/selfdrive/ui/layouts/settings/toggles.py index 01195142e..1e4d5c6c8 100644 --- a/selfdrive/ui/layouts/settings/toggles.py +++ b/selfdrive/ui/layouts/settings/toggles.py @@ -5,6 +5,7 @@ from openpilot.system.ui.widgets.list_view import multiple_button_item, toggle_i from openpilot.system.ui.widgets.scroller import Scroller from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.widgets import DialogResult from openpilot.selfdrive.ui.ui_state import ui_state @@ -12,24 +13,24 @@ PERSONALITY_TO_INT = log.LongitudinalPersonality.schema.enumerants # Description constants DESCRIPTIONS = { - "OpenpilotEnabledToggle": ( + "OpenpilotEnabledToggle": tr( "Use the openpilot system for adaptive cruise control and lane keep driver assistance. " + "Your attention is required at all times to use this feature." ), - "DisengageOnAccelerator": "When enabled, pressing the accelerator pedal will disengage openpilot.", - "LongitudinalPersonality": ( + "DisengageOnAccelerator": tr("When enabled, pressing the accelerator pedal will disengage openpilot."), + "LongitudinalPersonality": tr( "Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. " + "In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with " + "your steering wheel distance button." ), - "IsLdwEnabled": ( + "IsLdwEnabled": 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)." ), - "AlwaysOnDM": "Enable driver monitoring even when openpilot is not engaged.", - 'RecordFront': "Upload data from the driver facing camera and help improve the driver monitoring algorithm.", - "IsMetric": "Display speed in km/h instead of mph.", - "RecordAudio": "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect.", + "AlwaysOnDM": tr("Enable driver monitoring even when openpilot is not engaged."), + 'RecordFront': tr("Upload data from the driver facing camera and help improve the driver monitoring algorithm."), + "IsMetric": tr("Display speed in km/h instead of mph."), + "RecordAudio": tr("Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect."), } @@ -42,49 +43,49 @@ class TogglesLayout(Widget): # param, title, desc, icon, needs_restart self._toggle_defs = { "OpenpilotEnabledToggle": ( - "Enable openpilot", + tr("Enable openpilot"), DESCRIPTIONS["OpenpilotEnabledToggle"], "chffr_wheel.png", True, ), "ExperimentalMode": ( - "Experimental Mode", + tr("Experimental Mode"), "", "experimental_white.png", False, ), "DisengageOnAccelerator": ( - "Disengage on Accelerator Pedal", + tr("Disengage on Accelerator Pedal"), DESCRIPTIONS["DisengageOnAccelerator"], "disengage_on_accelerator.png", False, ), "IsLdwEnabled": ( - "Enable Lane Departure Warnings", + tr("Enable Lane Departure Warnings"), DESCRIPTIONS["IsLdwEnabled"], "warning.png", False, ), "AlwaysOnDM": ( - "Always-On Driver Monitoring", + tr("Always-On Driver Monitoring"), DESCRIPTIONS["AlwaysOnDM"], "monitoring.png", False, ), "RecordFront": ( - "Record and Upload Driver Camera", + tr("Record and Upload Driver Camera"), DESCRIPTIONS["RecordFront"], "monitoring.png", True, ), "RecordAudio": ( - "Record and Upload Microphone Audio", + tr("Record and Upload Microphone Audio"), DESCRIPTIONS["RecordAudio"], "microphone.png", True, ), "IsMetric": ( - "Use Metric System", + tr("Use Metric System"), DESCRIPTIONS["IsMetric"], "metric.png", False, @@ -92,9 +93,9 @@ class TogglesLayout(Widget): } self._long_personality_setting = multiple_button_item( - "Driving Personality", + tr("Driving Personality"), DESCRIPTIONS["LongitudinalPersonality"], - buttons=["Aggressive", "Standard", "Relaxed"], + buttons=[tr("Aggressive"), tr("Standard"), tr("Relaxed")], button_width=255, callback=self._set_longitudinal_personality, selected_index=self._params.get("LongitudinalPersonality", return_default=True), @@ -119,7 +120,7 @@ class TogglesLayout(Widget): toggle.action_item.set_enabled(not locked) if needs_restart and not locked: - toggle.set_description(toggle.description + " Changing this setting will restart openpilot if the car is powered on.") + toggle.set_description(toggle.description + tr(" Changing this setting will restart openpilot if the car is powered on.")) # track for engaged state updates if locked: @@ -150,7 +151,7 @@ class TogglesLayout(Widget): def _update_toggles(self): ui_state.update_params() - e2e_description = ( + e2e_description = tr( "openpilot defaults to driving in chill mode. Experimental mode enables alpha-level features that aren't ready for chill mode. " + "Experimental features are listed below:
" + "

End-to-End Longitudinal Control


" + @@ -174,15 +175,15 @@ class TogglesLayout(Widget): self._long_personality_setting.action_item.set_enabled(False) self._params.remove("ExperimentalMode") - unavailable = "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." + unavailable = tr("Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control.") - long_desc = unavailable + " openpilot longitudinal control may come in a future update." + long_desc = unavailable + " " + tr("openpilot longitudinal control may come in a future update.") if ui_state.CP.alphaLongitudinalAvailable: if self._is_release: - long_desc = unavailable + " " + ("An alpha version of openpilot longitudinal control can be tested, along with " + - "Experimental mode, on non-release branches.") + long_desc = unavailable + " " + tr("An alpha version of openpilot longitudinal control can be tested, along with " + + "Experimental mode, on non-release branches.") else: - long_desc = "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." + long_desc = tr("Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode.") self._toggles["ExperimentalMode"].set_description("" + long_desc + "

" + e2e_description) else: @@ -221,7 +222,7 @@ class TogglesLayout(Widget): # show confirmation dialog content = (f"

{self._toggles['ExperimentalMode'].title}


" + f"

{self._toggles['ExperimentalMode'].description}

") - dlg = ConfirmDialog(content, "Enable", rich=True) + dlg = ConfirmDialog(content, tr("Enable"), rich=True) gui_app.set_modal_overlay(dlg, callback=confirm_callback) else: self._update_experimental_mode_icon() diff --git a/selfdrive/ui/layouts/sidebar.py b/selfdrive/ui/layouts/sidebar.py index 9337b3c23..48b577ea5 100644 --- a/selfdrive/ui/layouts/sidebar.py +++ b/selfdrive/ui/layouts/sidebar.py @@ -5,6 +5,7 @@ from collections.abc import Callable from cereal import log from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos, FONT_SCALE +from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.widgets import Widget @@ -39,13 +40,13 @@ class Colors: NETWORK_TYPES = { - NetworkType.none: "--", - NetworkType.wifi: "Wi-Fi", - NetworkType.ethernet: "ETH", - NetworkType.cell2G: "2G", - NetworkType.cell3G: "3G", - NetworkType.cell4G: "LTE", - NetworkType.cell5G: "5G", + NetworkType.none: tr("--"), + NetworkType.wifi: tr("Wi-Fi"), + NetworkType.ethernet: tr("ETH"), + NetworkType.cell2G: tr("2G"), + NetworkType.cell3G: tr("3G"), + NetworkType.cell4G: tr("LTE"), + NetworkType.cell5G: tr("5G"), } @@ -67,9 +68,9 @@ class Sidebar(Widget): self._net_type = NETWORK_TYPES.get(NetworkType.none) self._net_strength = 0 - self._temp_status = MetricData("TEMP", "GOOD", Colors.GOOD) - self._panda_status = MetricData("VEHICLE", "ONLINE", Colors.GOOD) - self._connect_status = MetricData("CONNECT", "OFFLINE", Colors.WARNING) + self._temp_status = MetricData(tr("TEMP"), tr("GOOD"), Colors.GOOD) + self._panda_status = MetricData(tr("VEHICLE"), tr("ONLINE"), Colors.GOOD) + self._connect_status = MetricData(tr("CONNECT"), tr("OFFLINE"), Colors.WARNING) self._recording_audio = False self._home_img = gui_app.texture("images/button_home.png", HOME_BTN.width, HOME_BTN.height) @@ -113,7 +114,7 @@ class Sidebar(Widget): self._update_panda_status() def _update_network_status(self, device_state): - self._net_type = NETWORK_TYPES.get(device_state.networkType.raw, "Unknown") + self._net_type = NETWORK_TYPES.get(device_state.networkType.raw, tr("Unknown")) strength = device_state.networkStrength self._net_strength = max(0, min(5, strength.raw + 1)) if strength > 0 else 0 @@ -121,26 +122,26 @@ class Sidebar(Widget): thermal_status = device_state.thermalStatus if thermal_status == ThermalStatus.green: - self._temp_status.update("TEMP", "GOOD", Colors.GOOD) + self._temp_status.update(tr("TEMP"), tr("GOOD"), Colors.GOOD) elif thermal_status == ThermalStatus.yellow: - self._temp_status.update("TEMP", "OK", Colors.WARNING) + self._temp_status.update(tr("TEMP"), tr("OK"), Colors.WARNING) else: - self._temp_status.update("TEMP", "HIGH", Colors.DANGER) + self._temp_status.update(tr("TEMP"), tr("HIGH"), Colors.DANGER) def _update_connection_status(self, device_state): last_ping = device_state.lastAthenaPingTime if last_ping == 0: - self._connect_status.update("CONNECT", "OFFLINE", Colors.WARNING) + self._connect_status.update(tr("CONNECT"), tr("OFFLINE"), Colors.WARNING) elif time.monotonic_ns() - last_ping < 80_000_000_000: # 80 seconds in nanoseconds - self._connect_status.update("CONNECT", "ONLINE", Colors.GOOD) + self._connect_status.update(tr("CONNECT"), tr("ONLINE"), Colors.GOOD) else: - self._connect_status.update("CONNECT", "ERROR", Colors.DANGER) + self._connect_status.update(tr("CONNECT"), tr("ERROR"), Colors.DANGER) def _update_panda_status(self): if ui_state.panda_type == log.PandaState.PandaType.unknown: - self._panda_status.update("NO", "PANDA", Colors.DANGER) + self._panda_status.update(tr("NO"), tr("PANDA"), Colors.DANGER) else: - self._panda_status.update("VEHICLE", "ONLINE", Colors.GOOD) + self._panda_status.update(tr("VEHICLE"), tr("ONLINE"), Colors.GOOD) def _handle_mouse_release(self, mouse_pos: MousePos): if rl.check_collision_point_rec(mouse_pos, SETTINGS_BTN): diff --git a/selfdrive/ui/onroad/alert_renderer.py b/selfdrive/ui/onroad/alert_renderer.py index 0f944bac5..a81fbfc44 100644 --- a/selfdrive/ui/onroad/alert_renderer.py +++ b/selfdrive/ui/onroad/alert_renderer.py @@ -5,6 +5,7 @@ from cereal import messaging, log from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.system.hardware import TICI from openpilot.system.ui.lib.application import gui_app, FontWeight +from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.label import Label @@ -47,22 +48,22 @@ class Alert: # Pre-defined alert instances ALERT_STARTUP_PENDING = Alert( - text1="openpilot Unavailable", - text2="Waiting to start", + text1=tr("openpilot Unavailable"), + text2=tr("Waiting to start"), size=AlertSize.mid, status=AlertStatus.normal, ) ALERT_CRITICAL_TIMEOUT = Alert( - text1="TAKE CONTROL IMMEDIATELY", - text2="System Unresponsive", + text1=tr("TAKE CONTROL IMMEDIATELY"), + text2=tr("System Unresponsive"), size=AlertSize.full, status=AlertStatus.critical, ) ALERT_CRITICAL_REBOOT = Alert( - text1="System Unresponsive", - text2="Reboot Device", + text1=tr("System Unresponsive"), + text2=tr("Reboot Device"), size=AlertSize.mid, status=AlertStatus.normal, ) diff --git a/selfdrive/ui/onroad/driver_camera_dialog.py b/selfdrive/ui/onroad/driver_camera_dialog.py index a3bd2e23e..543ea35e8 100644 --- a/selfdrive/ui/onroad/driver_camera_dialog.py +++ b/selfdrive/ui/onroad/driver_camera_dialog.py @@ -5,6 +5,7 @@ from openpilot.selfdrive.ui.onroad.cameraview import CameraView from openpilot.selfdrive.ui.onroad.driver_state import DriverStateRenderer from openpilot.selfdrive.ui.ui_state import ui_state, device from openpilot.system.ui.lib.application import gui_app, FontWeight +from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.widgets.label import gui_label @@ -30,7 +31,7 @@ class DriverCameraDialog(CameraView): if not self.frame: gui_label( rect, - "camera starting", + tr("camera starting"), font_size=100, font_weight=FontWeight.BOLD, alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER, diff --git a/selfdrive/ui/onroad/hud_renderer.py b/selfdrive/ui/onroad/hud_renderer.py index 98c3d686f..a2459c27e 100644 --- a/selfdrive/ui/onroad/hud_renderer.py +++ b/selfdrive/ui/onroad/hud_renderer.py @@ -4,6 +4,7 @@ from openpilot.common.constants import CV from openpilot.selfdrive.ui.onroad.exp_button import ExpButton from openpilot.selfdrive.ui.ui_state import ui_state, UIStatus from openpilot.system.ui.lib.application import gui_app, FontWeight +from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.widgets import Widget @@ -144,7 +145,7 @@ class HudRenderer(Widget): elif ui_state.status == UIStatus.OVERRIDE: max_color = COLORS.override - max_text = "MAX" + max_text = tr("MAX") max_text_width = measure_text_cached(self._font_semi_bold, max_text, FONT_SIZES.max_speed).x rl.draw_text_ex( self._font_semi_bold, @@ -173,7 +174,7 @@ class HudRenderer(Widget): speed_pos = rl.Vector2(rect.x + rect.width / 2 - speed_text_size.x / 2, 180 - speed_text_size.y / 2) rl.draw_text_ex(self._font_bold, speed_text, speed_pos, FONT_SIZES.current_speed, 0, COLORS.white) - unit_text = "km/h" if ui_state.is_metric else "mph" + unit_text = tr("km/h") if ui_state.is_metric else tr("mph") unit_text_size = measure_text_cached(self._font_medium, unit_text, FONT_SIZES.speed_unit) unit_pos = rl.Vector2(rect.x + rect.width / 2 - unit_text_size.x / 2, 290 - unit_text_size.y / 2) rl.draw_text_ex(self._font_medium, unit_text, unit_pos, FONT_SIZES.speed_unit, 0, COLORS.white_translucent) diff --git a/selfdrive/ui/update_translations.py b/selfdrive/ui/update_translations.py index 424b78851..643d24601 100755 --- a/selfdrive/ui/update_translations.py +++ b/selfdrive/ui/update_translations.py @@ -4,10 +4,8 @@ import json import os from openpilot.common.basedir import BASEDIR +from openpilot.system.ui.lib.multilang import UI_DIR, TRANSLATIONS_DIR, LANGUAGES_FILE -UI_DIR = os.path.join(BASEDIR, "selfdrive", "ui") -TRANSLATIONS_DIR = os.path.join(UI_DIR, "translations") -LANGUAGES_FILE = os.path.join(TRANSLATIONS_DIR, "languages.json") TRANSLATIONS_INCLUDE_FILE = os.path.join(TRANSLATIONS_DIR, "alerts_generated.h") PLURAL_ONLY = ["en"] # base language, only create entries for strings with plural forms diff --git a/selfdrive/ui/widgets/exp_mode_button.py b/selfdrive/ui/widgets/exp_mode_button.py index 23031be30..faa3bf877 100644 --- a/selfdrive/ui/widgets/exp_mode_button.py +++ b/selfdrive/ui/widgets/exp_mode_button.py @@ -1,6 +1,7 @@ import pyray as rl from openpilot.common.params import Params from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE +from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.widgets import Widget @@ -46,7 +47,7 @@ class ExperimentalModeButton(Widget): rl.draw_line_ex(rl.Vector2(line_x, rect.y), rl.Vector2(line_x, rect.y + rect.height), 3, separator_color) # Draw text label (left aligned) - text = "EXPERIMENTAL MODE ON" if self.experimental_mode else "CHILL MODE ON" + text = tr("EXPERIMENTAL MODE ON") if self.experimental_mode else tr("CHILL MODE ON") text_x = rect.x + self.horizontal_padding text_y = rect.y + rect.height / 2 - 45 * FONT_SCALE // 2 # Center vertically diff --git a/selfdrive/ui/widgets/offroad_alerts.py b/selfdrive/ui/widgets/offroad_alerts.py index 0b3ca8176..0bd5b161b 100644 --- a/selfdrive/ui/widgets/offroad_alerts.py +++ b/selfdrive/ui/widgets/offroad_alerts.py @@ -6,6 +6,7 @@ from dataclasses import dataclass from openpilot.common.params import Params from openpilot.system.hardware import HARDWARE from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE +from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.lib.wrap_text import wrap_text @@ -14,7 +15,7 @@ from openpilot.system.ui.widgets.html_render import HtmlRenderer from openpilot.selfdrive.selfdrived.alertmanager import OFFROAD_ALERTS -NO_RELEASE_NOTES = "

No release notes available.

" +NO_RELEASE_NOTES = tr("

No release notes available.

") class AlertColors: @@ -101,15 +102,15 @@ class AbstractAlert(Widget, ABC): if self.dismiss_callback: self.dismiss_callback() - self.dismiss_btn = ActionButton("Close") + self.dismiss_btn = ActionButton(tr("Close")) - self.snooze_btn = ActionButton("Snooze Update", style=ButtonStyle.DARK) + self.snooze_btn = ActionButton(tr("Snooze Update"), style=ButtonStyle.DARK) self.snooze_btn.set_click_callback(snooze_callback) - self.excessive_actuation_btn = ActionButton("Acknowledge Excessive Actuation", style=ButtonStyle.DARK, min_width=800) + self.excessive_actuation_btn = ActionButton(tr("Acknowledge Excessive Actuation"), style=ButtonStyle.DARK, min_width=800) self.excessive_actuation_btn.set_click_callback(excessive_actuation_callback) - self.reboot_btn = ActionButton("Reboot and Update", min_width=600) + self.reboot_btn = ActionButton(tr("Reboot and Update"), min_width=600) self.reboot_btn.set_click_callback(lambda: HARDWARE.reboot()) # TODO: just use a Scroller? diff --git a/selfdrive/ui/widgets/pairing_dialog.py b/selfdrive/ui/widgets/pairing_dialog.py index 3778586f8..85b42d1a7 100644 --- a/selfdrive/ui/widgets/pairing_dialog.py +++ b/selfdrive/ui/widgets/pairing_dialog.py @@ -8,6 +8,7 @@ from openpilot.common.swaglog import cloudlog from openpilot.common.params import Params from openpilot.system.ui.widgets import Widget from openpilot.system.ui.lib.application import FontWeight, gui_app +from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.lib.wrap_text import wrap_text from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.selfdrive.ui.ui_state import ui_state @@ -99,7 +100,7 @@ class PairingDialog(Widget): y += close_size + 40 # Title - title = "Pair your device to your comma account" + title = tr("Pair your device to your comma account") title_font = gui_app.font(FontWeight.NORMAL) left_width = int(content_rect.width * 0.5 - 15) @@ -124,9 +125,9 @@ class PairingDialog(Widget): def _render_instructions(self, rect: rl.Rectangle) -> None: instructions = [ - "Go to https://connect.comma.ai on your phone", - "Click \"add new device\" and scan the QR code on the right", - "Bookmark connect.comma.ai to your home screen to use it like an app", + tr("Go to https://connect.comma.ai on your phone"), + tr("Click \"add new device\" and scan the QR code on the right"), + tr("Bookmark connect.comma.ai to your home screen to use it like an app"), ] font = gui_app.font(FontWeight.BOLD) @@ -157,7 +158,7 @@ class PairingDialog(Widget): rl.draw_rectangle_rounded(rect, 0.1, 20, rl.Color(240, 240, 240, 255)) error_font = gui_app.font(FontWeight.BOLD) rl.draw_text_ex( - error_font, "QR Code Error", rl.Vector2(rect.x + 20, rect.y + rect.height // 2 - 15), 30, 0.0, rl.RED + error_font, tr("QR Code Error"), rl.Vector2(rect.x + 20, rect.y + rect.height // 2 - 15), 30, 0.0, rl.RED ) return diff --git a/selfdrive/ui/widgets/prime.py b/selfdrive/ui/widgets/prime.py index bbbd52a8c..49a0e56cd 100644 --- a/selfdrive/ui/widgets/prime.py +++ b/selfdrive/ui/widgets/prime.py @@ -2,6 +2,7 @@ import pyray as rl from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.system.ui.lib.application import gui_app, FontWeight +from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.lib.wrap_text import wrap_text from openpilot.system.ui.widgets import Widget @@ -29,21 +30,21 @@ class PrimeWidget(Widget): w = rect.width - 160 # Title - gui_label(rl.Rectangle(x, y, w, 90), "Upgrade Now", 75, font_weight=FontWeight.BOLD) + gui_label(rl.Rectangle(x, y, w, 90), tr("Upgrade Now"), 75, font_weight=FontWeight.BOLD) # Description with wrapping desc_y = y + 140 font = gui_app.font(FontWeight.LIGHT) - wrapped_text = "\n".join(wrap_text(font, "Become a comma prime member at connect.comma.ai", 56, int(w))) + wrapped_text = "\n".join(wrap_text(font, tr("Become a comma prime member at connect.comma.ai"), 56, int(w))) text_size = measure_text_cached(font, wrapped_text, 56) rl.draw_text_ex(font, wrapped_text, rl.Vector2(x, desc_y), 56, 0, rl.WHITE) # Features section features_y = desc_y + text_size.y + 50 - gui_label(rl.Rectangle(x, features_y, w, 50), "PRIME FEATURES:", 41, font_weight=FontWeight.BOLD) + gui_label(rl.Rectangle(x, features_y, w, 50), tr("PRIME FEATURES:"), 41, font_weight=FontWeight.BOLD) # Feature list - features = ["Remote access", "24/7 LTE connectivity", "1 year of drive storage", "Remote snapshots"] + features = [tr("Remote access"), tr("24/7 LTE connectivity"), tr("1 year of drive storage"), tr("Remote snapshots")] for i, feature in enumerate(features): item_y = features_y + 80 + i * 65 gui_label(rl.Rectangle(x, item_y, 100, 60), "✓", 50, color=rl.Color(70, 91, 234, 255)) @@ -58,5 +59,5 @@ class PrimeWidget(Widget): y = rect.y + 40 font = gui_app.font(FontWeight.BOLD) - rl.draw_text_ex(font, "✓ SUBSCRIBED", rl.Vector2(x, y), 41, 0, rl.Color(134, 255, 78, 255)) - rl.draw_text_ex(font, "comma prime", rl.Vector2(x, y + 61), 75, 0, rl.WHITE) + rl.draw_text_ex(font, tr("✓ SUBSCRIBED"), rl.Vector2(x, y), 41, 0, rl.Color(134, 255, 78, 255)) + rl.draw_text_ex(font, tr("comma prime"), rl.Vector2(x, y + 61), 75, 0, rl.WHITE) diff --git a/selfdrive/ui/widgets/setup.py b/selfdrive/ui/widgets/setup.py index 0538570e4..ea88180ef 100644 --- a/selfdrive/ui/widgets/setup.py +++ b/selfdrive/ui/widgets/setup.py @@ -3,6 +3,7 @@ from openpilot.common.time_helpers import system_time_valid from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.selfdrive.ui.widgets.pairing_dialog import PairingDialog from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE +from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.lib.wrap_text import wrap_text from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.confirm_dialog import alert_dialog @@ -15,10 +16,10 @@ class SetupWidget(Widget): super().__init__() self._open_settings_callback = None self._pairing_dialog: PairingDialog | None = None - self._pair_device_btn = Button("Pair device", self._show_pairing, button_style=ButtonStyle.PRIMARY) - self._open_settings_btn = Button("Open", lambda: self._open_settings_callback() if self._open_settings_callback else None, + self._pair_device_btn = Button(tr("Pair device"), self._show_pairing, button_style=ButtonStyle.PRIMARY) + self._open_settings_btn = Button(tr("Open"), lambda: self._open_settings_callback() if self._open_settings_callback else None, button_style=ButtonStyle.PRIMARY) - self._firehose_label = Label("🔥 Firehose Mode 🔥", font_weight=FontWeight.MEDIUM, font_size=64) + self._firehose_label = Label(tr("🔥 Firehose Mode 🔥"), font_weight=FontWeight.MEDIUM, font_size=64) def set_open_settings_callback(self, callback): self._open_settings_callback = callback @@ -40,11 +41,11 @@ class SetupWidget(Widget): # Title font = gui_app.font(FontWeight.BOLD) - rl.draw_text_ex(font, "Finish Setup", rl.Vector2(x, y), 75, 0, rl.WHITE) + rl.draw_text_ex(font, tr("Finish Setup"), rl.Vector2(x, y), 75, 0, rl.WHITE) y += 113 # 75 + 38 spacing # Description - desc = "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer." + desc = tr("Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer.") light_font = gui_app.font(FontWeight.LIGHT) wrapped = wrap_text(light_font, desc, 50, int(w)) for line in wrapped: @@ -71,7 +72,7 @@ class SetupWidget(Widget): # Description desc_font = gui_app.font(FontWeight.NORMAL) - desc_text = "Maximize your training data uploads to improve openpilot's driving models." + desc_text = tr("Maximize your training data uploads to improve openpilot's driving models.") wrapped_desc = wrap_text(desc_font, desc_text, 40, int(w)) for line in wrapped_desc: @@ -87,7 +88,7 @@ class SetupWidget(Widget): def _show_pairing(self): if not system_time_valid(): - dlg = alert_dialog("Please connect to Wi-Fi to complete initial pairing") + dlg = alert_dialog(tr("Please connect to Wi-Fi to complete initial pairing")) gui_app.set_modal_overlay(dlg) return diff --git a/selfdrive/ui/widgets/ssh_key.py b/selfdrive/ui/widgets/ssh_key.py index dc3c5a4a7..e44c4aad5 100644 --- a/selfdrive/ui/widgets/ssh_key.py +++ b/selfdrive/ui/widgets/ssh_key.py @@ -7,6 +7,7 @@ from enum import Enum from openpilot.common.params import Params from openpilot.system.ui.lib.application import gui_app, FontWeight +from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.widgets import DialogResult from openpilot.system.ui.widgets.button import Button, ButtonStyle @@ -25,9 +26,9 @@ VALUE_FONT_SIZE = 48 class SshKeyActionState(Enum): - LOADING = "LOADING" - ADD = "ADD" - REMOVE = "REMOVE" + LOADING = tr("LOADING") + ADD = tr("ADD") + REMOVE = tr("REMOVE") class SshKeyAction(ItemAction): @@ -85,7 +86,7 @@ class SshKeyAction(ItemAction): def _handle_button_click(self): if self._state == SshKeyActionState.ADD: self._keyboard.reset() - self._keyboard.set_title("Enter your GitHub username") + self._keyboard.set_title(tr("Enter your GitHub username")) gui_app.set_modal_overlay(self._keyboard, callback=self._on_username_submit) elif self._state == SshKeyActionState.REMOVE: self._params.remove("GithubUsername") @@ -110,7 +111,7 @@ class SshKeyAction(ItemAction): response.raise_for_status() keys = response.text.strip() if not keys: - raise requests.exceptions.HTTPError("No SSH keys found") + raise requests.exceptions.HTTPError(tr("No SSH keys found")) # Success - save keys self._params.put("GithubUsername", username) @@ -119,10 +120,10 @@ class SshKeyAction(ItemAction): self._username = username except requests.exceptions.Timeout: - self._error_message = "Request timed out" + self._error_message = tr("Request timed out") self._state = SshKeyActionState.ADD except Exception: - self._error_message = f"No SSH keys found for user '{username}'" + self._error_message = tr("No SSH keys found for user '{}'").format(username) self._state = SshKeyActionState.ADD diff --git a/system/ui/lib/multilang.py b/system/ui/lib/multilang.py new file mode 100644 index 000000000..c020b33d5 --- /dev/null +++ b/system/ui/lib/multilang.py @@ -0,0 +1,10 @@ +import os +import gettext +from openpilot.common.basedir import BASEDIR + +UI_DIR = os.path.join(BASEDIR, "selfdrive", "ui") +TRANSLATIONS_DIR = os.path.join(UI_DIR, "translations") +LANGUAGES_FILE = os.path.join(TRANSLATIONS_DIR, "languages.json") + +tr = gettext.gettext +trn = gettext.ngettext diff --git a/system/ui/widgets/confirm_dialog.py b/system/ui/widgets/confirm_dialog.py index 274307395..8c5ae0aa0 100644 --- a/system/ui/widgets/confirm_dialog.py +++ b/system/ui/widgets/confirm_dialog.py @@ -1,5 +1,6 @@ import pyray as rl from openpilot.system.ui.lib.application import gui_app, FontWeight +from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.widgets import DialogResult from openpilot.system.ui.widgets.button import ButtonStyle, Button from openpilot.system.ui.widgets.label import Label @@ -16,8 +17,10 @@ BACKGROUND_COLOR = rl.Color(27, 27, 27, 255) class ConfirmDialog(Widget): - def __init__(self, text: str, confirm_text: str, cancel_text: str = "Cancel", rich: bool = False): + def __init__(self, text: str, confirm_text: str, cancel_text: str | None = None, rich: bool = False): super().__init__() + if cancel_text is None: + cancel_text = tr("Cancel") self._label = Label(text, 70, FontWeight.BOLD, text_color=rl.Color(201, 201, 201, 255)) self._html_renderer = HtmlRenderer(text=text, text_size={ElementType.P: 50}, center_text=True) self._cancel_button = Button(cancel_text, self._cancel_button_callback) @@ -85,5 +88,7 @@ class ConfirmDialog(Widget): return self._dialog_result -def alert_dialog(message: str, button_text: str = "Ok"): +def alert_dialog(message: str, button_text: str | None = None): + if button_text is None: + button_text = tr("OK") return ConfirmDialog(message, button_text, cancel_text="") diff --git a/system/ui/widgets/html_render.py b/system/ui/widgets/html_render.py index f90f78fe1..368d02cdf 100644 --- a/system/ui/widgets/html_render.py +++ b/system/ui/widgets/html_render.py @@ -4,6 +4,7 @@ from dataclasses import dataclass from enum import Enum from typing import Any from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE +from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel from openpilot.system.ui.lib.wrap_text import wrap_text from openpilot.system.ui.widgets import Widget @@ -243,7 +244,7 @@ class HtmlModal(Widget): super().__init__() self._content = HtmlRenderer(file_path=file_path, text=text) self._scroll_panel = GuiScrollPanel() - self._ok_button = Button("OK", click_callback=lambda: gui_app.set_modal_overlay(None), button_style=ButtonStyle.PRIMARY) + self._ok_button = Button(tr("OK"), click_callback=lambda: gui_app.set_modal_overlay(None), button_style=ButtonStyle.PRIMARY) def _render(self, rect: rl.Rectangle): margin = 50 diff --git a/system/ui/widgets/keyboard.py b/system/ui/widgets/keyboard.py index 6663c4e0e..ac006c254 100644 --- a/system/ui/widgets/keyboard.py +++ b/system/ui/widgets/keyboard.py @@ -5,6 +5,7 @@ from typing import Literal import pyray as rl from openpilot.system.ui.lib.application import gui_app, FontWeight +from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.button import ButtonStyle, Button from openpilot.system.ui.widgets.inputbox import InputBox @@ -77,7 +78,7 @@ class Keyboard(Widget): self._backspace_last_repeat: float = 0.0 self._render_return_status = -1 - self._cancel_button = Button("Cancel", self._cancel_button_callback) + self._cancel_button = Button(tr("Cancel"), self._cancel_button_callback) self._eye_button = Button("", self._eye_button_callback, button_style=ButtonStyle.TRANSPARENT) @@ -98,7 +99,7 @@ class Keyboard(Widget): if key in self._key_icons: texture = self._key_icons[key] self._all_keys[key] = Button("", partial(self._key_callback, key), icon=texture, - button_style=ButtonStyle.PRIMARY if key == ENTER_KEY else ButtonStyle.KEYBOARD, multi_touch=True) + button_style=ButtonStyle.PRIMARY if key == ENTER_KEY else ButtonStyle.KEYBOARD, multi_touch=True) else: self._all_keys[key] = Button(key, partial(self._key_callback, key), button_style=ButtonStyle.KEYBOARD, font_size=85, multi_touch=True) self._all_keys[CAPS_LOCK_KEY] = Button("", partial(self._key_callback, CAPS_LOCK_KEY), icon=self._key_icons[CAPS_LOCK_KEY], diff --git a/system/ui/widgets/list_view.py b/system/ui/widgets/list_view.py index 1f4f69a6b..55abe02fe 100644 --- a/system/ui/widgets/list_view.py +++ b/system/ui/widgets/list_view.py @@ -3,6 +3,7 @@ import pyray as rl from collections.abc import Callable from abc import ABC from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos +from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.button import Button, ButtonStyle @@ -111,7 +112,7 @@ class ButtonAction(ItemAction): @property def text(self): - return _resolve_value(self._text_source, "Error") + return _resolve_value(self._text_source, tr("Error")) @property def value(self): @@ -149,7 +150,7 @@ class TextAction(ItemAction): @property def text(self): - return _resolve_value(self._text_source, "Error") + return _resolve_value(self._text_source, tr("Error")) def get_width_hint(self) -> float: text_width = measure_text_cached(self._font, self.text, ITEM_TEXT_FONT_SIZE).x diff --git a/system/ui/widgets/network.py b/system/ui/widgets/network.py index 3bbf2e640..a59030363 100644 --- a/system/ui/widgets/network.py +++ b/system/ui/widgets/network.py @@ -4,6 +4,7 @@ from typing import cast import pyray as rl from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel from openpilot.system.ui.lib.wifi_manager import WifiManager, SecurityType, Network, MeteredType from openpilot.system.ui.widgets import Widget @@ -70,7 +71,7 @@ class NetworkUI(Widget): self._current_panel: PanelType = PanelType.WIFI self._wifi_panel = WifiManagerUI(wifi_manager) self._advanced_panel = AdvancedNetworkSettings(wifi_manager) - self._nav_button = NavButton("Advanced") + self._nav_button = NavButton(tr("Advanced")) self._nav_button.set_click_callback(self._cycle_panel) def show_event(self): @@ -91,11 +92,11 @@ class NetworkUI(Widget): content_rect = rl.Rectangle(self._rect.x, self._rect.y + self._nav_button.rect.height + 40, self._rect.width, self._rect.height - self._nav_button.rect.height - 40) if self._current_panel == PanelType.WIFI: - self._nav_button.text = "Advanced" + self._nav_button.text = tr("Advanced") self._nav_button.set_position(self._rect.x + self._rect.width - self._nav_button.rect.width, self._rect.y + 20) self._wifi_panel.render(content_rect) else: - self._nav_button.text = "Back" + self._nav_button.text = tr("Back") self._nav_button.set_position(self._rect.x, self._rect.y + 20) self._advanced_panel.render(content_rect) @@ -116,40 +117,40 @@ class AdvancedNetworkSettings(Widget): # Tethering self._tethering_action = ToggleAction(initial_state=False) - tethering_btn = ListItem(title="Enable Tethering", action_item=self._tethering_action, callback=self._toggle_tethering) + tethering_btn = ListItem(title=tr("Enable Tethering"), action_item=self._tethering_action, callback=self._toggle_tethering) # Edit tethering password - self._tethering_password_action = ButtonAction(text="EDIT") - tethering_password_btn = ListItem(title="Tethering Password", action_item=self._tethering_password_action, callback=self._edit_tethering_password) + self._tethering_password_action = ButtonAction(text=tr("EDIT")) + tethering_password_btn = ListItem(title=tr("Tethering Password"), action_item=self._tethering_password_action, callback=self._edit_tethering_password) # Roaming toggle roaming_enabled = self._params.get_bool("GsmRoaming") self._roaming_action = ToggleAction(initial_state=roaming_enabled) - self._roaming_btn = ListItem(title="Enable Roaming", action_item=self._roaming_action, callback=self._toggle_roaming) + self._roaming_btn = ListItem(title=tr("Enable Roaming"), action_item=self._roaming_action, callback=self._toggle_roaming) # Cellular metered toggle cellular_metered = self._params.get_bool("GsmMetered") self._cellular_metered_action = ToggleAction(initial_state=cellular_metered) - self._cellular_metered_btn = ListItem(title="Cellular Metered", description="Prevent large data uploads when on a metered cellular connection", + self._cellular_metered_btn = ListItem(title=tr("Cellular Metered"), description=tr("Prevent large data uploads when on a metered cellular connection"), action_item=self._cellular_metered_action, callback=self._toggle_cellular_metered) # APN setting - self._apn_btn = button_item("APN Setting", "EDIT", callback=self._edit_apn) + self._apn_btn = button_item(tr("APN Setting"), tr("EDIT"), callback=self._edit_apn) # Wi-Fi metered toggle - self._wifi_metered_action = MultipleButtonAction(["default", "metered", "unmetered"], 255, 0, callback=self._toggle_wifi_metered) - wifi_metered_btn = ListItem(title="Wi-Fi Network Metered", description="Prevent large data uploads when on a metered Wi-Fi connection", + self._wifi_metered_action = MultipleButtonAction([tr("default"), tr("metered"), tr("unmetered")], 255, 0, callback=self._toggle_wifi_metered) + wifi_metered_btn = ListItem(title=tr("Wi-Fi Network Metered"), description=tr("Prevent large data uploads when on a metered Wi-Fi connection"), action_item=self._wifi_metered_action) items: list[Widget] = [ tethering_btn, tethering_password_btn, - text_item("IP Address", lambda: self._wifi_manager.ipv4_address), + text_item(tr("IP Address"), lambda: self._wifi_manager.ipv4_address), self._roaming_btn, self._apn_btn, self._cellular_metered_btn, wifi_metered_btn, - button_item("Hidden Network", "CONNECT", callback=self._connect_to_hidden_network), + button_item(tr("Hidden Network"), tr("CONNECT"), callback=self._connect_to_hidden_network), ] self._scroller = Scroller(items, line_separator=True, spacing=0) @@ -198,7 +199,7 @@ class AdvancedNetworkSettings(Widget): current_apn = self._params.get("GsmApn") or "" self._keyboard.reset(min_text_size=0) - self._keyboard.set_title("Enter APN", "leave blank for automatic configuration") + self._keyboard.set_title(tr("Enter APN"), tr("leave blank for automatic configuration")) self._keyboard.set_text(current_apn) gui_app.set_modal_overlay(self._keyboard, update_apn) @@ -231,11 +232,11 @@ class AdvancedNetworkSettings(Widget): self._wifi_manager.connect_to_network(ssid, password, hidden=True) self._keyboard.reset(min_text_size=0) - self._keyboard.set_title("Enter password", f"for \"{ssid}\"") + self._keyboard.set_title(tr("Enter password"), tr("for \"{}\"").format(ssid)) gui_app.set_modal_overlay(self._keyboard, enter_password) self._keyboard.reset(min_text_size=1) - self._keyboard.set_title("Enter SSID", "") + self._keyboard.set_title(tr("Enter SSID"), "") gui_app.set_modal_overlay(self._keyboard, connect_hidden) def _edit_tethering_password(self): @@ -248,7 +249,7 @@ class AdvancedNetworkSettings(Widget): self._tethering_password_action.set_enabled(False) self._keyboard.reset(min_text_size=MIN_PASSWORD_LENGTH) - self._keyboard.set_title("Enter new tethering password", "") + self._keyboard.set_title(tr("Enter new tethering password"), "") self._keyboard.set_text(self._wifi_manager.tethering_password) gui_app.set_modal_overlay(self._keyboard, update_password) @@ -281,7 +282,7 @@ class WifiManagerUI(Widget): self._networks: list[Network] = [] self._networks_buttons: dict[str, Button] = {} self._forget_networks_buttons: dict[str, Button] = {} - self._confirm_dialog = ConfirmDialog("", "Forget", "Cancel") + self._confirm_dialog = ConfirmDialog("", tr("Forget"), tr("Cancel")) self._wifi_manager.set_callbacks(need_auth=self._on_need_auth, activated=self._on_activated, @@ -305,15 +306,15 @@ class WifiManagerUI(Widget): def _render(self, rect: rl.Rectangle): if not self._networks: - gui_label(rect, "Scanning Wi-Fi networks...", 72, alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER) + gui_label(rect, tr("Scanning Wi-Fi networks..."), 72, alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER) return if self.state == UIState.NEEDS_AUTH and self._state_network: - self.keyboard.set_title("Wrong password" if self._password_retry else "Enter password", f"for {self._state_network.ssid}") + self.keyboard.set_title(tr("Wrong password") if self._password_retry else tr("Enter password"), tr("for \"{}\"").format(self._state_network.ssid)) self.keyboard.reset(min_text_size=MIN_PASSWORD_LENGTH) gui_app.set_modal_overlay(self.keyboard, lambda result: self._on_password_entered(cast(Network, self._state_network), result)) elif self.state == UIState.SHOW_FORGET_CONFIRM and self._state_network: - self._confirm_dialog.set_text(f'Forget Wi-Fi Network "{self._state_network.ssid}"?') + self._confirm_dialog.set_text(tr("Forget Wi-Fi Network \"{}\"?").format(self._state_network.ssid)) self._confirm_dialog.reset() gui_app.set_modal_overlay(self._confirm_dialog, callback=lambda result: self.on_forgot_confirm_finished(self._state_network, result)) else: @@ -363,11 +364,11 @@ class WifiManagerUI(Widget): if self.state == UIState.CONNECTING and self._state_network: if self._state_network.ssid == network.ssid: self._networks_buttons[network.ssid].set_enabled(False) - status_text = "CONNECTING..." + status_text = tr("CONNECTING...") elif self.state == UIState.FORGETTING and self._state_network: if self._state_network.ssid == network.ssid: self._networks_buttons[network.ssid].set_enabled(False) - status_text = "FORGETTING..." + status_text = tr("FORGETTING...") elif network.security_type == SecurityType.UNSUPPORTED: self._networks_buttons[network.ssid].set_enabled(False) else: @@ -445,7 +446,7 @@ class WifiManagerUI(Widget): self._networks_buttons[n.ssid] = Button(n.ssid, partial(self._networks_buttons_callback, n), font_size=55, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT, button_style=ButtonStyle.TRANSPARENT_WHITE_TEXT) self._networks_buttons[n.ssid].set_touch_valid_callback(lambda: self.scroll_panel.is_touch_valid()) - self._forget_networks_buttons[n.ssid] = Button("Forget", partial(self._forget_networks_buttons_callback, n), button_style=ButtonStyle.FORGET_WIFI, + self._forget_networks_buttons[n.ssid] = Button(tr("Forget"), partial(self._forget_networks_buttons_callback, n), button_style=ButtonStyle.FORGET_WIFI, font_size=45) self._forget_networks_buttons[n.ssid].set_touch_valid_callback(lambda: self.scroll_panel.is_touch_valid()) diff --git a/system/ui/widgets/option_dialog.py b/system/ui/widgets/option_dialog.py index 593a371fd..8c63ca3f9 100644 --- a/system/ui/widgets/option_dialog.py +++ b/system/ui/widgets/option_dialog.py @@ -1,5 +1,6 @@ import pyray as rl from openpilot.system.ui.lib.application import FontWeight +from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.widgets import Widget, DialogResult from openpilot.system.ui.widgets.button import Button, ButtonStyle from openpilot.system.ui.widgets.label import gui_label @@ -30,8 +31,8 @@ class MultiOptionDialog(Widget): text_padding=50) for option in options] self.scroller = Scroller(self.option_buttons, spacing=LIST_ITEM_SPACING) - self.cancel_button = Button("Cancel", click_callback=lambda: self._set_result(DialogResult.CANCEL)) - self.select_button = Button("Select", click_callback=lambda: self._set_result(DialogResult.CONFIRM), button_style=ButtonStyle.PRIMARY) + self.cancel_button = Button(tr("Cancel"), click_callback=lambda: self._set_result(DialogResult.CANCEL)) + self.select_button = Button(tr("Select"), click_callback=lambda: self._set_result(DialogResult.CONFIRM), button_style=ButtonStyle.PRIMARY) def _set_result(self, result: DialogResult): self._result = result From cc8f6eadfe0e80ab23b5c281779dc5df7868a319 Mon Sep 17 00:00:00 2001 From: ZwX1616 Date: Tue, 21 Oct 2025 13:58:48 -0700 Subject: [PATCH 218/910] =?UTF-8?q?DM:=20Medium=20Fanta=20model=20?= =?UTF-8?q?=F0=9F=A5=A4=20(#36409)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit M fanta: e456b6c5-2dd0-400e-bf0f-6bb5a908971a --- selfdrive/modeld/models/dmonitoring_model.onnx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/modeld/models/dmonitoring_model.onnx b/selfdrive/modeld/models/dmonitoring_model.onnx index 5ae91f67a..1b6a8c3e9 100644 --- a/selfdrive/modeld/models/dmonitoring_model.onnx +++ b/selfdrive/modeld/models/dmonitoring_model.onnx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9b2117ee4907add59e3fbe6829cda74e0ad71c0835b0ebb9373ba9425de0d336 +oid sha256:3a53626ab84757813fb16a1441704f2ae7192bef88c331bdc2415be6981d204f size 7191776 From 5289b08bcf0b136ef1829f4951d48aa22bdb1945 Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Tue, 21 Oct 2025 21:54:40 -0700 Subject: [PATCH 219/910] bump retry count for `micd` and `soundd` (#36415) retry --- selfdrive/ui/soundd.py | 2 +- system/micd.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/soundd.py b/selfdrive/ui/soundd.py index 13f3b2209..44c463b18 100644 --- a/selfdrive/ui/soundd.py +++ b/selfdrive/ui/soundd.py @@ -124,7 +124,7 @@ class Soundd: volume = ((weighted_db - AMBIENT_DB) / DB_SCALE) * (MAX_VOLUME - MIN_VOLUME) + MIN_VOLUME return math.pow(10, (np.clip(volume, MIN_VOLUME, MAX_VOLUME) - 1)) - @retry(attempts=7, delay=3) + @retry(attempts=10, delay=3) def get_stream(self, sd): # reload sounddevice to reinitialize portaudio sd._terminate() diff --git a/system/micd.py b/system/micd.py index 02ef82390..b3558a15a 100755 --- a/system/micd.py +++ b/system/micd.py @@ -94,7 +94,7 @@ class Mic: self.measurements = self.measurements[FFT_SAMPLES:] - @retry(attempts=7, delay=3) + @retry(attempts=10, delay=3) def get_stream(self, sd): # reload sounddevice to reinitialize portaudio sd._terminate() From d2bb8fe537ccddbd4ce1067fc6ae25976d3f4be1 Mon Sep 17 00:00:00 2001 From: felsager <76905857+felsager@users.noreply.github.com> Date: Wed, 22 Oct 2025 10:44:14 -0700 Subject: [PATCH 220/910] latcontrol_torque: more descriptive variable names (#36422) --- selfdrive/controls/lib/latcontrol_torque.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/selfdrive/controls/lib/latcontrol_torque.py b/selfdrive/controls/lib/latcontrol_torque.py index f2b1e0b66..31d982859 100644 --- a/selfdrive/controls/lib/latcontrol_torque.py +++ b/selfdrive/controls/lib/latcontrol_torque.py @@ -4,7 +4,6 @@ from collections import deque from cereal import log from opendbc.car.lateral import FRICTION_THRESHOLD, get_friction -from opendbc.car.tests.test_lateral_limits import MAX_LAT_JERK_UP from openpilot.common.constants import ACCELERATION_DUE_TO_GRAVITY from openpilot.common.filter_simple import FirstOrderFilter from openpilot.selfdrive.controls.lib.drive_helpers import MIN_SPEED @@ -28,6 +27,8 @@ KP = 1.0 KI = 0.3 KD = 0.0 +LP_FILTER_CUTOFF_HZ = 1.2 +LAT_ACCEL_REQUEST_BUFFER_SECONDS = 1.0 class LatControlTorque(LatControl): def __init__(self, CP, CI, dt): @@ -35,13 +36,13 @@ class LatControlTorque(LatControl): self.torque_params = CP.lateralTuning.torque.as_builder() self.torque_from_lateral_accel = CI.torque_from_lateral_accel() self.lateral_accel_from_torque = CI.lateral_accel_from_torque() - self.pid = PIDController(KP, KI, k_d=KD, rate=1/self.dt) + self.pid = PIDController(KP, KI, KD, rate=1/self.dt) self.update_limits() self.steering_angle_deadzone_deg = self.torque_params.steeringAngleDeadzoneDeg - self.LATACCEL_REQUEST_BUFFER_NUM_FRAMES = int(1 / self.dt) - self.requested_lateral_accel_buffer = deque([0.] * self.LATACCEL_REQUEST_BUFFER_NUM_FRAMES , maxlen=self.LATACCEL_REQUEST_BUFFER_NUM_FRAMES) + self.lat_accel_request_buffer_len = int(LAT_ACCEL_REQUEST_BUFFER_SECONDS / self.dt) + self.lat_accel_request_buffer = deque([0.] * self.lat_accel_request_buffer_len , maxlen=self.lat_accel_request_buffer_len) self.previous_measurement = 0.0 - self.measurement_rate_filter = FirstOrderFilter(0.0, 1 / (2 * np.pi * (MAX_LAT_JERK_UP - 0.5)), self.dt) + self.measurement_rate_filter = FirstOrderFilter(0.0, 1 / (2 * np.pi * LP_FILTER_CUTOFF_HZ), self.dt) def update_live_torque_params(self, latAccelFactor, latAccelOffset, friction): self.torque_params.latAccelFactor = latAccelFactor @@ -64,11 +65,11 @@ class LatControlTorque(LatControl): curvature_deadzone = abs(VM.calc_curvature(math.radians(self.steering_angle_deadzone_deg), CS.vEgo, 0.0)) lateral_accel_deadzone = curvature_deadzone * CS.vEgo ** 2 - delay_frames = int(np.clip(lat_delay / self.dt, 1, self.LATACCEL_REQUEST_BUFFER_NUM_FRAMES)) - expected_lateral_accel = self.requested_lateral_accel_buffer[-delay_frames] + delay_frames = int(np.clip(lat_delay / self.dt, 1, self.lat_accel_request_buffer_len)) + expected_lateral_accel = self.lat_accel_request_buffer[-delay_frames] # TODO factor out lateral jerk from error to later replace it with delay independent alternative future_desired_lateral_accel = desired_curvature * CS.vEgo ** 2 - self.requested_lateral_accel_buffer.append(future_desired_lateral_accel) + self.lat_accel_request_buffer.append(future_desired_lateral_accel) gravity_adjusted_future_lateral_accel = future_desired_lateral_accel - roll_compensation desired_lateral_jerk = (future_desired_lateral_accel - expected_lateral_accel) / lat_delay From a2e7f3788f609c5691418baa8ffddec442770594 Mon Sep 17 00:00:00 2001 From: felsager <76905857+felsager@users.noreply.github.com> Date: Wed, 22 Oct 2025 10:56:34 -0700 Subject: [PATCH 221/910] LateralTorqueState: log controller version and desired lateral jerk (#36421) --- cereal/log.capnp | 2 ++ selfdrive/controls/lib/latcontrol_torque.py | 3 +++ selfdrive/test/process_replay/ref_commit | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/cereal/log.capnp b/cereal/log.capnp index 019fbbe10..6cd8196ae 100644 --- a/cereal/log.capnp +++ b/cereal/log.capnp @@ -918,6 +918,8 @@ struct ControlsState @0x97ff69c53601abf1 { saturated @7 :Bool; actualLateralAccel @9 :Float32; desiredLateralAccel @10 :Float32; + desiredLateralJerk @11 :Float32; + version @12 :Int32; } struct LateralLQRState { diff --git a/selfdrive/controls/lib/latcontrol_torque.py b/selfdrive/controls/lib/latcontrol_torque.py index 31d982859..c46b0965c 100644 --- a/selfdrive/controls/lib/latcontrol_torque.py +++ b/selfdrive/controls/lib/latcontrol_torque.py @@ -29,6 +29,7 @@ KD = 0.0 LP_FILTER_CUTOFF_HZ = 1.2 LAT_ACCEL_REQUEST_BUFFER_SECONDS = 1.0 +VERSION = 0 class LatControlTorque(LatControl): def __init__(self, CP, CI, dt): @@ -56,6 +57,7 @@ class LatControlTorque(LatControl): def update(self, active, CS, VM, params, steer_limited_by_safety, desired_curvature, curvature_limited, lat_delay): pid_log = log.ControlsState.LateralTorqueState.new_message() + pid_log.version = VERSION if not active: output_torque = 0.0 pid_log.active = False @@ -106,6 +108,7 @@ class LatControlTorque(LatControl): pid_log.output = float(-output_torque) # TODO: log lat accel? pid_log.actualLateralAccel = float(measurement) pid_log.desiredLateralAccel = float(setpoint) + pid_log.desiredLateralJerk= float(desired_lateral_jerk) pid_log.saturated = bool(self._check_saturation(self.steer_max - abs(output_torque) < 1e-3, CS, steer_limited_by_safety, curvature_limited)) # TODO left is positive in this convention diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index dd73ed12f..732b441da 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -55e82ab6370865a1427ebc1d559921a5354d9cbf \ No newline at end of file +1841c3c365689310f7f337a2aa6966d79bc4a60c \ No newline at end of file From b1b7c505a1e1ed3cf3206da8ca925a71122479f6 Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Wed, 22 Oct 2025 13:28:37 -0500 Subject: [PATCH 222/910] raylib: add danger button pressed style (#36413) add danger hover style --- system/ui/widgets/button.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/ui/widgets/button.py b/system/ui/widgets/button.py index 15d48b4e1..baf45ce95 100644 --- a/system/ui/widgets/button.py +++ b/system/ui/widgets/button.py @@ -61,7 +61,7 @@ BUTTON_BACKGROUND_COLORS = { BUTTON_PRESSED_BACKGROUND_COLORS = { ButtonStyle.NORMAL: rl.Color(74, 74, 74, 255), ButtonStyle.PRIMARY: rl.Color(48, 73, 244, 255), - ButtonStyle.DANGER: rl.Color(255, 36, 36, 255), + ButtonStyle.DANGER: rl.Color(204, 0, 0, 255), ButtonStyle.TRANSPARENT: rl.BLACK, ButtonStyle.TRANSPARENT_WHITE_TEXT: rl.BLANK, ButtonStyle.TRANSPARENT_WHITE_BORDER: rl.BLANK, From 4489517eeb7d02d6ce82842b9ddcf740ead8e19b Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Wed, 22 Oct 2025 13:35:58 -0500 Subject: [PATCH 223/910] keyboard: replace duplicate period key (#36361) switch between underscore and hypen instead of period --- system/ui/widgets/keyboard.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system/ui/widgets/keyboard.py b/system/ui/widgets/keyboard.py index ac006c254..80b87483d 100644 --- a/system/ui/widgets/keyboard.py +++ b/system/ui/widgets/keyboard.py @@ -45,13 +45,13 @@ KEYBOARD_LAYOUTS = { "numbers": [ ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"], ["-", "/", ":", ";", "(", ")", "$", "&", "@", "\""], - [SYMBOL_KEY, ".", ",", "?", "!", "`", BACKSPACE_KEY], + [SYMBOL_KEY, "_", ",", "?", "!", "`", BACKSPACE_KEY], [ABC_KEY, SPACE_KEY, ".", ENTER_KEY], ], "specials": [ ["[", "]", "{", "}", "#", "%", "^", "*", "+", "="], ["_", "\\", "|", "~", "<", ">", "€", "£", "¥", "•"], - [NUMERIC_KEY, ".", ",", "?", "!", "'", BACKSPACE_KEY], + [NUMERIC_KEY, "-", ",", "?", "!", "'", BACKSPACE_KEY], [ABC_KEY, SPACE_KEY, ".", ENTER_KEY], ], } From 936740201ced09e617d7249e92746849934de5b6 Mon Sep 17 00:00:00 2001 From: felsager <76905857+felsager@users.noreply.github.com> Date: Wed, 22 Oct 2025 11:50:37 -0700 Subject: [PATCH 224/910] latcontrol_torque: refactor low speed factor into pid controller (#36364) --- selfdrive/controls/lib/latcontrol_torque.py | 23 +++++++++------------ selfdrive/test/process_replay/ref_commit | 2 +- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/selfdrive/controls/lib/latcontrol_torque.py b/selfdrive/controls/lib/latcontrol_torque.py index c46b0965c..443fd1851 100644 --- a/selfdrive/controls/lib/latcontrol_torque.py +++ b/selfdrive/controls/lib/latcontrol_torque.py @@ -6,7 +6,6 @@ from cereal import log from opendbc.car.lateral import FRICTION_THRESHOLD, get_friction from openpilot.common.constants import ACCELERATION_DUE_TO_GRAVITY from openpilot.common.filter_simple import FirstOrderFilter -from openpilot.selfdrive.controls.lib.drive_helpers import MIN_SPEED from openpilot.selfdrive.controls.lib.latcontrol import LatControl from openpilot.common.pid import PIDController @@ -16,16 +15,16 @@ from openpilot.common.pid import PIDController # wheel slip, or to speed. # This controller applies torque to achieve desired lateral -# accelerations. To compensate for the low speed effects we -# use a LOW_SPEED_FACTOR in the error. Additionally, there is -# friction in the steering wheel that needs to be overcome to -# move it at all, this is compensated for too. +# accelerations. To compensate for the low speed effects the +# proportional gain is increased at low speeds by the PID controller. +# Additionally, there is friction in the steering wheel that needs +# to be overcome to move it at all, this is compensated for too. -LOW_SPEED_X = [0, 10, 20, 30] -LOW_SPEED_Y = [15, 13, 10, 5] KP = 1.0 KI = 0.3 KD = 0.0 +INTERP_SPEEDS = [1, 1.5, 2.0, 3.0, 5, 7.5, 10, 15, 30] +KP_INTERP = [250, 120, 65, 30, 11.5, 5.5, 3.5, 2.0, KP] LP_FILTER_CUTOFF_HZ = 1.2 LAT_ACCEL_REQUEST_BUFFER_SECONDS = 1.0 @@ -37,7 +36,7 @@ class LatControlTorque(LatControl): self.torque_params = CP.lateralTuning.torque.as_builder() self.torque_from_lateral_accel = CI.torque_from_lateral_accel() self.lateral_accel_from_torque = CI.lateral_accel_from_torque() - self.pid = PIDController(KP, KI, KD, rate=1/self.dt) + self.pid = PIDController([INTERP_SPEEDS, KP_INTERP], KI, KD, rate=1/self.dt) self.update_limits() self.steering_angle_deadzone_deg = self.torque_params.steeringAngleDeadzoneDeg self.lat_accel_request_buffer_len = int(LAT_ACCEL_REQUEST_BUFFER_SECONDS / self.dt) @@ -79,13 +78,11 @@ class LatControlTorque(LatControl): measurement_rate = self.measurement_rate_filter.update((measurement - self.previous_measurement) / self.dt) self.previous_measurement = measurement - low_speed_factor = (np.interp(CS.vEgo, LOW_SPEED_X, LOW_SPEED_Y) / max(CS.vEgo, MIN_SPEED)) ** 2 setpoint = lat_delay * desired_lateral_jerk + expected_lateral_accel error = setpoint - measurement - error_lsf = error + low_speed_factor / KP * error # do error correction in lateral acceleration space, convert at end to handle non-linear torque responses correctly - pid_log.error = float(error_lsf) + pid_log.error = float(error) ff = gravity_adjusted_future_lateral_accel # latAccelOffset corrects roll compensation bias from device roll misalignment relative to car roll ff -= self.torque_params.latAccelOffset @@ -105,10 +102,10 @@ class LatControlTorque(LatControl): pid_log.i = float(self.pid.i) pid_log.d = float(self.pid.d) pid_log.f = float(self.pid.f) - pid_log.output = float(-output_torque) # TODO: log lat accel? + pid_log.output = float(-output_torque) # TODO: log lat accel? pid_log.actualLateralAccel = float(measurement) pid_log.desiredLateralAccel = float(setpoint) - pid_log.desiredLateralJerk= float(desired_lateral_jerk) + pid_log.desiredLateralJerk = float(desired_lateral_jerk) pid_log.saturated = bool(self._check_saturation(self.steer_max - abs(output_torque) < 1e-3, CS, steer_limited_by_safety, curvature_limited)) # TODO left is positive in this convention diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 732b441da..cdd4301fc 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -1841c3c365689310f7f337a2aa6966d79bc4a60c \ No newline at end of file +b508f43fb0481bce0859c9b6ab4f45ee690b8dab \ No newline at end of file From f983df0c70194d772fa33c5698ceb6484c5b3e14 Mon Sep 17 00:00:00 2001 From: ZwX1616 Date: Wed, 22 Oct 2025 15:35:58 -0700 Subject: [PATCH 225/910] camerad: faster exposure convergence at startup (#36424) * might converge faster * accept darker at start * accept darker at start * it was unreasonably lax --- system/camerad/cameras/camera_qcom2.cc | 2 +- system/camerad/test/test_exposure.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/system/camerad/cameras/camera_qcom2.cc b/system/camerad/cameras/camera_qcom2.cc index f2a064c60..d741e13cf 100644 --- a/system/camerad/cameras/camera_qcom2.cc +++ b/system/camerad/cameras/camera_qcom2.cc @@ -50,7 +50,7 @@ public: Rect ae_xywh = {}; float measured_grey_fraction = 0; - float target_grey_fraction = 0.3; + float target_grey_fraction = 0.125; float fl_pix = 0; std::unique_ptr pm; diff --git a/system/camerad/test/test_exposure.py b/system/camerad/test/test_exposure.py index b853b0f2f..6f89e0480 100644 --- a/system/camerad/test/test_exposure.py +++ b/system/camerad/test/test_exposure.py @@ -20,7 +20,7 @@ class TestCamerad: def _is_exposure_okay(self, i, med_mean=None): if med_mean is None: - med_mean = np.array([[0.2,0.4],[0.2,0.6]]) + med_mean = np.array([[0.18,0.3],[0.18,0.3]]) h, w = i.shape[:2] i = i[h//10:9*h//10,w//10:9*w//10] med_ex, mean_ex = med_mean From 5af1099fbfe503bdc50dbfa734d3cd83bf754a39 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 22 Oct 2025 15:36:09 -0700 Subject: [PATCH 226/910] rm watchdog (#36425) --- common/SConscript | 1 - common/watchdog.cc | 12 --------- common/watchdog.h | 5 ---- common/watchdog.py | 22 ----------------- selfdrive/ui/qt/offroad/settings.cc | 2 -- selfdrive/ui/ui.cc | 4 --- system/manager/process.py | 38 ++--------------------------- system/manager/process_config.py | 2 +- 8 files changed, 3 insertions(+), 83 deletions(-) delete mode 100644 common/watchdog.cc delete mode 100644 common/watchdog.h delete mode 100644 common/watchdog.py diff --git a/common/SConscript b/common/SConscript index 0891b7903..c771ee78b 100644 --- a/common/SConscript +++ b/common/SConscript @@ -4,7 +4,6 @@ common_libs = [ 'params.cc', 'swaglog.cc', 'util.cc', - 'watchdog.cc', 'ratekeeper.cc', 'clutil.cc', ] diff --git a/common/watchdog.cc b/common/watchdog.cc deleted file mode 100644 index 44e8c83e6..000000000 --- a/common/watchdog.cc +++ /dev/null @@ -1,12 +0,0 @@ -#include - -#include "common/watchdog.h" -#include "common/util.h" -#include "system/hardware/hw.h" - -const std::string watchdog_fn_prefix = Path::shm_path() + "/wd_"; // + - -bool watchdog_kick(uint64_t ts) { - static std::string fn = watchdog_fn_prefix + std::to_string(getpid()); - return util::write_file(fn.c_str(), &ts, sizeof(ts), O_WRONLY | O_CREAT) > 0; -} diff --git a/common/watchdog.h b/common/watchdog.h deleted file mode 100644 index 12dd2ca03..000000000 --- a/common/watchdog.h +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - -#include - -bool watchdog_kick(uint64_t ts); diff --git a/common/watchdog.py b/common/watchdog.py deleted file mode 100644 index ddb6f744e..000000000 --- a/common/watchdog.py +++ /dev/null @@ -1,22 +0,0 @@ -import os -import time -import struct -from openpilot.system.hardware.hw import Paths - -WATCHDOG_FN = f"{Paths.shm_path()}/wd_" -_LAST_KICK = 0.0 - -def kick_watchdog(): - global _LAST_KICK - current_time = time.monotonic() - - if current_time - _LAST_KICK < 1.0: - return - - try: - with open(f"{WATCHDOG_FN}{os.getpid()}", 'wb') as f: - f.write(struct.pack(' -#include "common/watchdog.h" #include "common/util.h" #include "selfdrive/ui/qt/network/networking.h" #include "selfdrive/ui/qt/offroad/settings.h" @@ -270,7 +269,6 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) { // put language setting, exit Qt UI, and trigger fast restart params.put("LanguageSetting", langs[selection].toStdString()); qApp->exit(18); - watchdog_kick(0); } }); addItem(translateBtn); diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index 9ec61b9b8..ed851a41c 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -8,7 +8,6 @@ #include "common/transformations/orientation.hpp" #include "common/swaglog.h" #include "common/util.h" -#include "common/watchdog.h" #include "system/hardware/hw.h" #define BACKLIGHT_DT 0.05 @@ -116,9 +115,6 @@ void UIState::update() { update_state(this); updateStatus(); - if (sm->frame % UI_FREQ == 0) { - watchdog_kick(nanos_since_boot()); - } emit uiUpdate(*this); } diff --git a/system/manager/process.py b/system/manager/process.py index 5e86e87c7..e6b6a44c4 100644 --- a/system/manager/process.py +++ b/system/manager/process.py @@ -1,7 +1,6 @@ import importlib import os import signal -import struct import time import subprocess from collections.abc import Callable, ValuesView @@ -16,9 +15,6 @@ import openpilot.system.sentry as sentry from openpilot.common.basedir import BASEDIR from openpilot.common.params import Params from openpilot.common.swaglog import cloudlog -from openpilot.common.watchdog import WATCHDOG_FN - -ENABLE_WATCHDOG = os.getenv("NO_WATCHDOG") is None def launcher(proc: str, name: str) -> None: @@ -70,10 +66,6 @@ class ManagerProcess(ABC): proc: Process | None = None enabled = True name = "" - - last_watchdog_time = 0 - watchdog_max_dt: int | None = None - watchdog_seen = False shutting_down = False @abstractmethod @@ -88,26 +80,6 @@ class ManagerProcess(ABC): self.stop(sig=signal.SIGKILL) self.start() - def check_watchdog(self, started: bool) -> None: - if self.watchdog_max_dt is None or self.proc is None: - return - - try: - fn = WATCHDOG_FN + str(self.proc.pid) - with open(fn, "rb") as f: - self.last_watchdog_time = struct.unpack('Q', f.read())[0] - except Exception: - pass - - dt = time.monotonic() - self.last_watchdog_time / 1e9 - - if dt > self.watchdog_max_dt: - if self.watchdog_seen and ENABLE_WATCHDOG: - cloudlog.error(f"Watchdog timeout for {self.name} (exitcode {self.proc.exitcode}) restarting ({started=})") - self.restart() - else: - self.watchdog_seen = True - def stop(self, retry: bool = True, block: bool = True, sig: signal.Signals = None) -> int | None: if self.proc is None: return None @@ -167,14 +139,13 @@ class ManagerProcess(ABC): class NativeProcess(ManagerProcess): - def __init__(self, name, cwd, cmdline, should_run, enabled=True, sigkill=False, watchdog_max_dt=None): + def __init__(self, name, cwd, cmdline, should_run, enabled=True, sigkill=False): self.name = name self.cwd = cwd self.cmdline = cmdline self.should_run = should_run self.enabled = enabled self.sigkill = sigkill - self.watchdog_max_dt = watchdog_max_dt self.launcher = nativelauncher def prepare(self) -> None: @@ -192,18 +163,16 @@ class NativeProcess(ManagerProcess): cloudlog.info(f"starting process {self.name}") self.proc = Process(name=self.name, target=self.launcher, args=(self.cmdline, cwd, self.name)) self.proc.start() - self.watchdog_seen = False self.shutting_down = False class PythonProcess(ManagerProcess): - def __init__(self, name, module, should_run, enabled=True, sigkill=False, watchdog_max_dt=None): + def __init__(self, name, module, should_run, enabled=True, sigkill=False): self.name = name self.module = module self.should_run = should_run self.enabled = enabled self.sigkill = sigkill - self.watchdog_max_dt = watchdog_max_dt self.launcher = launcher def prepare(self) -> None: @@ -226,7 +195,6 @@ class PythonProcess(ManagerProcess): cloudlog.info(f"starting python {self.module}") self.proc = Process(name=name, target=self.launcher, args=(self.module, self.name)) self.proc.start() - self.watchdog_seen = False self.shutting_down = False @@ -288,8 +256,6 @@ def ensure_running(procs: ValuesView[ManagerProcess], started: bool, params=None else: p.stop(block=False) - p.check_watchdog(started) - for p in running: p.start() diff --git a/system/manager/process_config.py b/system/manager/process_config.py index 0c35a3d3c..5c02227ca 100644 --- a/system/manager/process_config.py +++ b/system/manager/process_config.py @@ -80,7 +80,7 @@ procs = [ PythonProcess("dmonitoringmodeld", "selfdrive.modeld.dmonitoringmodeld", driverview, enabled=(WEBCAM or not PC)), PythonProcess("sensord", "system.sensord.sensord", only_onroad, enabled=not PC), - # NativeProcess("ui", "selfdrive/ui", ["./ui"], always_run, enabled=False, watchdog_max_dt=(5 if not PC else None)), + # NativeProcess("ui", "selfdrive/ui", ["./ui"], always_run, enabled=False), PythonProcess("ui", "selfdrive.ui.ui", always_run), PythonProcess("soundd", "selfdrive.ui.soundd", only_onroad), PythonProcess("locationd", "selfdrive.locationd.locationd", only_onroad), From 99fdd59042251c7271d41c7c0b4fcacdb01ab885 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 22 Oct 2025 16:11:37 -0700 Subject: [PATCH 227/910] agnos 14.3 (#36426) --- launch_env.sh | 2 +- system/hardware/tici/agnos.json | 26 ++++++------ system/hardware/tici/all-partitions.json | 50 ++++++++++++------------ 3 files changed, 39 insertions(+), 39 deletions(-) diff --git a/launch_env.sh b/launch_env.sh index 07ec162f0..9bbe0916f 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="14.2" + export AGNOS_VERSION="14.3" fi export STAGING_ROOT="/data/safe_staging" diff --git a/system/hardware/tici/agnos.json b/system/hardware/tici/agnos.json index 035d8aa01..61ba1d38e 100644 --- a/system/hardware/tici/agnos.json +++ b/system/hardware/tici/agnos.json @@ -56,29 +56,29 @@ }, { "name": "boot", - "url": "https://commadist.azureedge.net/agnosupdate/boot-08291eb52a9d54b77e191ea1a78addf24aae28e15306ac3118d03ac9be29fbe9.img.xz", - "hash": "08291eb52a9d54b77e191ea1a78addf24aae28e15306ac3118d03ac9be29fbe9", - "hash_raw": "08291eb52a9d54b77e191ea1a78addf24aae28e15306ac3118d03ac9be29fbe9", - "size": 17868800, + "url": "https://commadist.azureedge.net/agnosupdate/boot-273ba209936c5697bea478d1bc21dbc4ede1588508d33f5d2e7ad5d5c581d925.img.xz", + "hash": "273ba209936c5697bea478d1bc21dbc4ede1588508d33f5d2e7ad5d5c581d925", + "hash_raw": "273ba209936c5697bea478d1bc21dbc4ede1588508d33f5d2e7ad5d5c581d925", + "size": 17496064, "sparse": false, "full_check": true, "has_ab": true, - "ondevice_hash": "18fab2e1eb2e43e5c39e20ee20e0d391586de528df6dbfdab6dabcdab835ee3e" + "ondevice_hash": "562c9137843994995188f21f5ec3bac408ac7dee030627aef701853814a1d33e" }, { "name": "system", - "url": "https://commadist.azureedge.net/agnosupdate/system-1a653b2a2006eb19017b9f091928a51fbb0b91c1ab218971779936892c9bd71b.img.xz", - "hash": "8d4b4dd80a8a537adf82faa07928066bec4568eae73bdcf4a5f0da94fb77b485", - "hash_raw": "1a653b2a2006eb19017b9f091928a51fbb0b91c1ab218971779936892c9bd71b", - "size": 5368709120, + "url": "https://commadist.azureedge.net/agnosupdate/system-71b51d24f70d6831f597d369d4b37f78262258096bc3d662826275c4f1fbd42c.img.xz", + "hash": "2e41ebf1cf7e3801fa447c1e133d6d1b928546c412c36a843f5bd344557b9908", + "hash_raw": "71b51d24f70d6831f597d369d4b37f78262258096bc3d662826275c4f1fbd42c", + "size": 4718592000, "sparse": true, "full_check": false, "has_ab": true, - "ondevice_hash": "a9569b9286fba882be003f9710383ae6de229a72db936e80be08dbd2c23f320e", + "ondevice_hash": "1d65fd6d8bb4b0c6f9920e0b5c49c7743b5949f8057676511765f1900ab6a771", "alt": { - "hash": "1a653b2a2006eb19017b9f091928a51fbb0b91c1ab218971779936892c9bd71b", - "url": "https://commadist.azureedge.net/agnosupdate/system-1a653b2a2006eb19017b9f091928a51fbb0b91c1ab218971779936892c9bd71b.img", - "size": 5368709120 + "hash": "71b51d24f70d6831f597d369d4b37f78262258096bc3d662826275c4f1fbd42c", + "url": "https://commadist.azureedge.net/agnosupdate/system-71b51d24f70d6831f597d369d4b37f78262258096bc3d662826275c4f1fbd42c.img", + "size": 4718592000 } } ] \ No newline at end of file diff --git a/system/hardware/tici/all-partitions.json b/system/hardware/tici/all-partitions.json index 6b99df1c3..434c0567d 100644 --- a/system/hardware/tici/all-partitions.json +++ b/system/hardware/tici/all-partitions.json @@ -339,62 +339,62 @@ }, { "name": "boot", - "url": "https://commadist.azureedge.net/agnosupdate/boot-08291eb52a9d54b77e191ea1a78addf24aae28e15306ac3118d03ac9be29fbe9.img.xz", - "hash": "08291eb52a9d54b77e191ea1a78addf24aae28e15306ac3118d03ac9be29fbe9", - "hash_raw": "08291eb52a9d54b77e191ea1a78addf24aae28e15306ac3118d03ac9be29fbe9", - "size": 17868800, + "url": "https://commadist.azureedge.net/agnosupdate/boot-273ba209936c5697bea478d1bc21dbc4ede1588508d33f5d2e7ad5d5c581d925.img.xz", + "hash": "273ba209936c5697bea478d1bc21dbc4ede1588508d33f5d2e7ad5d5c581d925", + "hash_raw": "273ba209936c5697bea478d1bc21dbc4ede1588508d33f5d2e7ad5d5c581d925", + "size": 17496064, "sparse": false, "full_check": true, "has_ab": true, - "ondevice_hash": "18fab2e1eb2e43e5c39e20ee20e0d391586de528df6dbfdab6dabcdab835ee3e" + "ondevice_hash": "562c9137843994995188f21f5ec3bac408ac7dee030627aef701853814a1d33e" }, { "name": "system", - "url": "https://commadist.azureedge.net/agnosupdate/system-1a653b2a2006eb19017b9f091928a51fbb0b91c1ab218971779936892c9bd71b.img.xz", - "hash": "8d4b4dd80a8a537adf82faa07928066bec4568eae73bdcf4a5f0da94fb77b485", - "hash_raw": "1a653b2a2006eb19017b9f091928a51fbb0b91c1ab218971779936892c9bd71b", - "size": 5368709120, + "url": "https://commadist.azureedge.net/agnosupdate/system-71b51d24f70d6831f597d369d4b37f78262258096bc3d662826275c4f1fbd42c.img.xz", + "hash": "2e41ebf1cf7e3801fa447c1e133d6d1b928546c412c36a843f5bd344557b9908", + "hash_raw": "71b51d24f70d6831f597d369d4b37f78262258096bc3d662826275c4f1fbd42c", + "size": 4718592000, "sparse": true, "full_check": false, "has_ab": true, - "ondevice_hash": "a9569b9286fba882be003f9710383ae6de229a72db936e80be08dbd2c23f320e", + "ondevice_hash": "1d65fd6d8bb4b0c6f9920e0b5c49c7743b5949f8057676511765f1900ab6a771", "alt": { - "hash": "1a653b2a2006eb19017b9f091928a51fbb0b91c1ab218971779936892c9bd71b", - "url": "https://commadist.azureedge.net/agnosupdate/system-1a653b2a2006eb19017b9f091928a51fbb0b91c1ab218971779936892c9bd71b.img", - "size": 5368709120 + "hash": "71b51d24f70d6831f597d369d4b37f78262258096bc3d662826275c4f1fbd42c", + "url": "https://commadist.azureedge.net/agnosupdate/system-71b51d24f70d6831f597d369d4b37f78262258096bc3d662826275c4f1fbd42c.img", + "size": 4718592000 } }, { "name": "userdata_90", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_90-a154dec5ebad07f63ebef989a1f7e44c449b9fb94b1048157d426ff0e78feef8.img.xz", - "hash": "32ef650ba25cbf867eb4699096e33027aa0ab79e05de2d1dfee3601b00b4fdf6", - "hash_raw": "a154dec5ebad07f63ebef989a1f7e44c449b9fb94b1048157d426ff0e78feef8", + "url": "https://commadist.azureedge.net/agnosupdate/userdata_90-8ed6bf12f84d64770aca750daacbd1578d199cc470ecd81ace255cd9cf5065e8.img.xz", + "hash": "4b1aae460d6a81d27691e2aa4fd96634eca607cac65305b5147350f1578beebd", + "hash_raw": "8ed6bf12f84d64770aca750daacbd1578d199cc470ecd81ace255cd9cf5065e8", "size": 96636764160, "sparse": true, "full_check": true, "has_ab": false, - "ondevice_hash": "9f21158f9055983c237d47a8eea8e27e978b5f25383756a7a9363a7bd9f7f72e" + "ondevice_hash": "be4365d37920853fb05fbffa793f7818890a41e4869c36ab16439a5f18d8d945" }, { "name": "userdata_89", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_89-31ebdff72d44d3f60bdf0920e39171795494c275b8cff023cf23ec592af7a4b3.img.xz", - "hash": "a62837b235be14b257baf05ddc6bddd026c8859bbb4f154d0323c7efa58cb938", - "hash_raw": "31ebdff72d44d3f60bdf0920e39171795494c275b8cff023cf23ec592af7a4b3", + "url": "https://commadist.azureedge.net/agnosupdate/userdata_89-11fe1e0f066bb8639f6bbbf2c71f19472f1ae436b96c92fe2d21c456dce1bf88.img.xz", + "hash": "517273cd69a34ae523e6075baa79dd46fe5a940e376faba50f26ec0b74a27610", + "hash_raw": "11fe1e0f066bb8639f6bbbf2c71f19472f1ae436b96c92fe2d21c456dce1bf88", "size": 95563022336, "sparse": true, "full_check": true, "has_ab": false, - "ondevice_hash": "a5caa169c840de6d1804b4186a1d26486be95e1837c4df16ec45952665356942" + "ondevice_hash": "498a5bc3ed091663543d2882831b45e3f92743df458a901cf6f64e791e518cb2" }, { "name": "userdata_30", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_30-16518389a1ed7ad6277dbab75d18aa13833fb4ed4010f456438f2c2ac8c61140.img.xz", - "hash": "cb8c2fc2ae83cacb86af4ce96c6d61e4bd3cd2591e612e12878c27fa51030ffa", - "hash_raw": "16518389a1ed7ad6277dbab75d18aa13833fb4ed4010f456438f2c2ac8c61140", + "url": "https://commadist.azureedge.net/agnosupdate/userdata_30-a60a00504f28f72179c44dfa539ef447c8e12a614aa23cef29e4b04c394505f1.img.xz", + "hash": "c32098db26bf2aefd1fa6184e3ac5db304adb35bedc6693b8729046c40aad7fb", + "hash_raw": "a60a00504f28f72179c44dfa539ef447c8e12a614aa23cef29e4b04c394505f1", "size": 32212254720, "sparse": true, "full_check": true, "has_ab": false, - "ondevice_hash": "5dd8e1f87a3f985ece80f7a36da1cbdabd77bcc11d26fc7bb85540069eff8ead" + "ondevice_hash": "cf16e217c88d92bc99439cd3e8e24e6a1776bfc4b175044643bfc8b42968bd38" } ] \ No newline at end of file From c33c9ff22a7ef901529907d801787b408841e53e Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Thu, 23 Oct 2025 07:17:57 +0800 Subject: [PATCH 228/910] raylib: optimize html renderer with height caching (#36418) optimize html renderer with height caching --- system/ui/widgets/html_render.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/system/ui/widgets/html_render.py b/system/ui/widgets/html_render.py index 368d02cdf..b7e28a8d5 100644 --- a/system/ui/widgets/html_render.py +++ b/system/ui/widgets/html_render.py @@ -65,6 +65,9 @@ class HtmlRenderer(Widget): if text_size is None: text_size = {} + self._cached_height: float | None = None + self._cached_width: int = -1 + # Base paragraph size (Qt stylesheet default is 48px in offroad alerts) base_p_size = int(text_size.get(ElementType.P, 48)) @@ -97,6 +100,8 @@ class HtmlRenderer(Widget): def parse_html_content(self, html_content: str) -> None: self.elements.clear() + self._cached_height = None + self._cached_width = -1 # Remove HTML comments html_content = re.sub(r'', '', html_content, flags=re.DOTALL) @@ -211,6 +216,9 @@ class HtmlRenderer(Widget): return current_y - rect.y def get_total_height(self, content_width: int) -> float: + if self._cached_height is not None and self._cached_width == content_width: + return self._cached_height + total_height = 0.0 padding = 20 usable_width = content_width - (padding * 2) @@ -231,6 +239,10 @@ class HtmlRenderer(Widget): total_height += element.margin_bottom + # Store result in cache + self._cached_height = total_height + self._cached_width = content_width + return total_height def _get_font(self, weight: FontWeight): From 215acefbb4094740698903a9f60bb2e57eabe5b0 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Thu, 23 Oct 2025 07:18:11 +0800 Subject: [PATCH 229/910] raylib: precompile regex patterns for faster HTML parsing (#36417) precompiled regex --- system/ui/widgets/html_render.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/system/ui/widgets/html_render.py b/system/ui/widgets/html_render.py index b7e28a8d5..7d90d5692 100644 --- a/system/ui/widgets/html_render.py +++ b/system/ui/widgets/html_render.py @@ -31,6 +31,10 @@ class ElementType(Enum): TAG_NAMES = '|'.join([t.value for t in ElementType]) START_TAG_RE = re.compile(f'<({TAG_NAMES})>') END_TAG_RE = re.compile(f'') +COMMENT_RE = re.compile(r'', flags=re.DOTALL) +DOCTYPE_RE = re.compile(r']*>') +HTML_BODY_TAGS_RE = re.compile(r']*>') +TOKEN_RE = re.compile(r']+>|<[^>]+>|[^<\s]+') def is_tag(token: str) -> tuple[bool, bool, ElementType | None]: @@ -104,14 +108,14 @@ class HtmlRenderer(Widget): self._cached_width = -1 # Remove HTML comments - html_content = re.sub(r'', '', html_content, flags=re.DOTALL) + html_content = COMMENT_RE.sub('', html_content) # Remove DOCTYPE, html, head, body tags but keep their content - html_content = re.sub(r']*>', '', html_content) - html_content = re.sub(r']*>', '', html_content) + html_content = DOCTYPE_RE.sub('', html_content) + html_content = HTML_BODY_TAGS_RE.sub('', html_content) # Parse HTML - tokens = re.findall(r']+>|<[^>]+>|[^<\s]+', html_content) + tokens = TOKEN_RE.findall(html_content) def close_tag(): nonlocal current_content From 00e20f15242a2ea4abfca5ba794a537c8f8aa32f Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Wed, 22 Oct 2025 18:19:37 -0500 Subject: [PATCH 230/910] raylib: fix "Reboot" button pressed style (#36412) use normal style for dual button action left button --- system/ui/widgets/list_view.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/ui/widgets/list_view.py b/system/ui/widgets/list_view.py index 55abe02fe..a02c7a1eb 100644 --- a/system/ui/widgets/list_view.py +++ b/system/ui/widgets/list_view.py @@ -176,7 +176,7 @@ class DualButtonAction(ItemAction): super().__init__(width=0, enabled=enabled) # Width 0 means use full width self.left_text, self.right_text = left_text, right_text - self.left_button = Button(left_text, click_callback=left_callback, button_style=ButtonStyle.LIST_ACTION, text_padding=0) + self.left_button = Button(left_text, click_callback=left_callback, button_style=ButtonStyle.NORMAL, text_padding=0) self.right_button = Button(right_text, click_callback=right_callback, button_style=ButtonStyle.DANGER, text_padding=0) def set_touch_valid_callback(self, touch_callback: Callable[[], bool]) -> None: From 856f8d3d47983a80bfdb08d2208bcef9278f5232 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Thu, 23 Oct 2025 07:22:51 +0800 Subject: [PATCH 231/910] raylib: add branch switcher (#36411) * add branch switcher * improve --- selfdrive/ui/layouts/settings/software.py | 32 ++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/selfdrive/ui/layouts/settings/software.py b/selfdrive/ui/layouts/settings/software.py index 8e0cfdbc3..1b78b5c9e 100644 --- a/selfdrive/ui/layouts/settings/software.py +++ b/selfdrive/ui/layouts/settings/software.py @@ -8,6 +8,7 @@ from openpilot.system.ui.lib.multilang import tr, trn from openpilot.system.ui.widgets import Widget, DialogResult from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog from openpilot.system.ui.widgets.list_view import button_item, text_item, ListItem +from openpilot.system.ui.widgets.option_dialog import MultiOptionDialog from openpilot.system.ui.widgets.scroller import Scroller # TODO: remove this. updater fails to respond on startup if time is not correct @@ -52,6 +53,9 @@ class SoftwareLayout(Widget): self._install_btn = button_item(tr("Install Update"), tr("INSTALL"), callback=self._on_install_update) self._install_btn.set_visible(False) + self._select_branch_dialog: MultiOptionDialog | None = None + self._target_branch_btn = button_item(tr("Target Branch"), tr("SELECT"), callback=self._on_select_branch) + # Track waiting-for-updater transition to avoid brief re-enable while still idle self._waiting_for_updater = False self._waiting_start_ts: float = 0.0 @@ -65,8 +69,7 @@ class SoftwareLayout(Widget): self._version_item, self._download_btn, self._install_btn, - # TODO: implement branch switching - # button_item("Target Branch", "SELECT", callback=self._on_select_branch), + self._target_branch_btn, button_item("Uninstall", tr("UNINSTALL"), callback=self._on_uninstall), ] return items @@ -87,6 +90,8 @@ class SoftwareLayout(Widget): self._version_item.action_item.set_text(current_desc) self._version_item.set_description(current_release_notes) + self._target_branch_btn.action_item.set_value(ui_state.params.get("UpdaterTargetBranch") or "") + # Update download button visibility and state self._download_btn.set_visible(ui_state.is_offroad()) @@ -163,4 +168,25 @@ class SoftwareLayout(Widget): self._install_btn.action_item.set_enabled(False) ui_state.params.put_bool("DoReboot", True) - def _on_select_branch(self): pass + def _on_select_branch(self): + current_branch = ui_state.params.get("GitBranch") or "" + branches_str = ui_state.params.get("UpdaterAvailableBranches") or "" + + available_branches = [b.strip() for b in branches_str.split(",") if b.strip()] + priority_branches = [current_branch, "devel-staging", "devel", "nightly", "nightly-dev", "master"] + for branch in priority_branches: + if branch in available_branches: + available_branches.remove(branch) + available_branches.insert(0, branch) + + self._select_branch_dialog = MultiOptionDialog(tr("Select a branch"), available_branches, current=current_branch) + gui_app.set_modal_overlay(self._select_branch_dialog, callback=self._handle_branch_selection) + + def _handle_branch_selection(self, result: int): + if result == 1 and self._select_branch_dialog: + selected = self._select_branch_dialog.selection + self._target_branch_btn.action_item.set_value(selected) + ui_state.params.put("UpdaterTargetBranch", selected) + os.system("pkill -SIGHUP -f system.updated.updated") + + self._select_branch_dialog = None From 4ccafff123c45ea644e20818381b9cde37b9e232 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 22 Oct 2025 16:28:28 -0700 Subject: [PATCH 232/910] raylib: multilang (#36195) * fix multilang dialog height * split to file * stash * Revert "stash" This reverts commit deb4239fe69f0260420fad03f2350e622e31542f. * add updater * add files * stuff * try rev * stash * works! * works! * this should be the flow? * cursor wrapping -- it missed entire sections, changed formatting, and didn't use trn properly!!!!!!!!!!!!!!!!! * update translations * learned my lesson * this should be the one thing it's good at * update trans * onroad wrap * spanish * rename * clean up * load all * Revert "load all" This reverts commit 6f2a45861c914ffb9d40a5edd15751afd798d614. * jp translations * try jp * Revert "try jp" This reverts commit d0524b10110104baafcdc1ec385c3d57bc5ef901. * remove languages we can't add rn * tr * pt and fr * ai cannot be trusted * ai cannot be trusted * missing trans * add fonts * Revert "remove languages we can't add rn" This reverts commit 73dc75fae2b9e347d867b6636dab6e2b5fe59da7. * painfully slow to startup * only load what we need * Reapply "remove languages we can't add rn" This reverts commit 52cb48f3b838520a421f9b90e5ea4409c27d4bd0. * add system * that's sick that this just works (dynamic) * fix description falling back to first str + support callable titles in list items * device is now live! * make firehose live * developer * network live * software live * and that * toggles live * regen * start to clean up gpt * revert op sans * bruh * update translations * rm old script * add noops for descriptions to fix translating away from non-english after startup * missing de * do filtering in multilang.py * clean up clean up * codespell: ignore po * fix update * should not depend * more live * sidebar and offroad alert panel live * fix issues with offroad alerts * fix firehose live * fix weird tr("") behavior * sh key live bugfix * setup.py live * update * update * no fuzzy matching -- breaks dynamic translations * rm this * fix calib desc live trans * change onroad * rm dfonts * clean up device * missing live * update * op lint * not true * add to gitignore * speed up startup by reducing chars by ~half * fix scons * fix crash going from qt * preserve original lang * cancel kb live translate * no preserve * fix lint --- .gitignore | 1 + pyproject.toml | 2 +- selfdrive/SConscript | 3 +- selfdrive/ui/SConscript | 183 +-- selfdrive/ui/layouts/settings/developer.py | 29 +- selfdrive/ui/layouts/settings/device.py | 64 +- selfdrive/ui/layouts/settings/firehose.py | 17 +- selfdrive/ui/layouts/settings/settings.py | 19 +- selfdrive/ui/layouts/settings/software.py | 10 +- selfdrive/ui/layouts/settings/toggles.py | 43 +- selfdrive/ui/layouts/sidebar.py | 44 +- selfdrive/ui/translations/app.pot | 1134 ++++++++++++++++++ selfdrive/ui/translations/app_de.po | 1225 +++++++++++++++++++ selfdrive/ui/translations/app_en.po | 1211 +++++++++++++++++++ selfdrive/ui/translations/app_es.po | 1229 +++++++++++++++++++ selfdrive/ui/translations/app_fr.po | 1234 ++++++++++++++++++++ selfdrive/ui/translations/app_pt-BR.po | 1220 +++++++++++++++++++ selfdrive/ui/translations/app_tr.po | 1214 +++++++++++++++++++ selfdrive/ui/update_translations_raylib.py | 38 + selfdrive/ui/widgets/offroad_alerts.py | 39 +- selfdrive/ui/widgets/setup.py | 6 +- selfdrive/ui/widgets/ssh_key.py | 12 +- system/ui/lib/application.py | 12 +- system/ui/lib/multilang.py | 70 +- system/ui/widgets/button.py | 2 +- system/ui/widgets/keyboard.py | 2 +- system/ui/widgets/label.py | 12 +- system/ui/widgets/list_view.py | 38 +- system/ui/widgets/network.py | 30 +- 29 files changed, 8874 insertions(+), 269 deletions(-) create mode 100644 selfdrive/ui/translations/app.pot create mode 100644 selfdrive/ui/translations/app_de.po create mode 100644 selfdrive/ui/translations/app_en.po create mode 100644 selfdrive/ui/translations/app_es.po create mode 100644 selfdrive/ui/translations/app_fr.po create mode 100644 selfdrive/ui/translations/app_pt-BR.po create mode 100644 selfdrive/ui/translations/app_tr.po create mode 100755 selfdrive/ui/update_translations_raylib.py diff --git a/.gitignore b/.gitignore index 0832b3964..d3306a11b 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,7 @@ a.out *.pyxbldc *.vcd *.qm +*.mo *_pyx.cpp config.json clcache diff --git a/pyproject.toml b/pyproject.toml index 78bf8c22e..6cc6298eb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -177,7 +177,7 @@ quiet-level = 3 # if you've got a short variable name that's getting flagged, add it here ignore-words-list = "bu,ro,te,ue,alo,hda,ois,nam,nams,ned,som,parm,setts,inout,warmup,bumb,nd,sie,preints,whit,indexIn,ws,uint,grey,deque,stdio,amin,BA,LITE,atEnd,UIs,errorString,arange,FocusIn,od,tim,relA,hist,copyable,jupyter,thead,TGE,abl,lite" builtin = "clear,rare,informal,code,names,en-GB_to_en-US" -skip = "./third_party/*, ./tinygrad/*, ./tinygrad_repo/*, ./msgq/*, ./panda/*, ./opendbc/*, ./opendbc_repo/*, ./rednose/*, ./rednose_repo/*, ./teleoprtc/*, ./teleoprtc_repo/*, *.ts, uv.lock, *.onnx, ./cereal/gen/*, */c_generated_code/*, docs/assets/*, tools/plotjuggler/layouts/*" +skip = "./third_party/*, ./tinygrad/*, ./tinygrad_repo/*, ./msgq/*, ./panda/*, ./opendbc/*, ./opendbc_repo/*, ./rednose/*, ./rednose_repo/*, ./teleoprtc/*, ./teleoprtc_repo/*, *.ts, *.po, uv.lock, *.onnx, ./cereal/gen/*, */c_generated_code/*, docs/assets/*, tools/plotjuggler/layouts/*" [tool.mypy] python_version = "3.11" diff --git a/selfdrive/SConscript b/selfdrive/SConscript index 43bf7d047..55f347c44 100644 --- a/selfdrive/SConscript +++ b/selfdrive/SConscript @@ -3,5 +3,4 @@ SConscript(['controls/lib/lateral_mpc_lib/SConscript']) SConscript(['controls/lib/longitudinal_mpc_lib/SConscript']) SConscript(['locationd/SConscript']) SConscript(['modeld/SConscript']) -if GetOption('extras'): - SConscript(['ui/SConscript']) +SConscript(['ui/SConscript']) diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index 7ede8c394..a7670b248 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -1,98 +1,111 @@ +import os import json Import('env', 'qt_env', 'arch', 'common', 'messaging', 'visionipc', 'transformations') -base_libs = [common, messaging, visionipc, transformations, - 'm', 'OpenCL', 'ssl', 'crypto', 'pthread'] + qt_env["LIBS"] - -if arch == 'larch64': - base_libs.append('EGL') - -if arch == "Darwin": - del base_libs[base_libs.index('OpenCL')] - qt_env['FRAMEWORKS'] += ['OpenCL'] - -# FIXME: remove this once we're on 5.15 (24.04) -qt_env['CXXFLAGS'] += ["-Wno-deprecated-declarations"] - -qt_util = qt_env.Library("qt_util", ["#selfdrive/ui/qt/api.cc", "#selfdrive/ui/qt/util.cc"], LIBS=base_libs) -widgets_src = ["qt/widgets/input.cc", "qt/widgets/wifi.cc", "qt/prime_state.cc", - "qt/widgets/ssh_keys.cc", "qt/widgets/toggle.cc", "qt/widgets/controls.cc", - "qt/widgets/offroad_alerts.cc", "qt/widgets/prime.cc", "qt/widgets/keyboard.cc", - "qt/widgets/scrollview.cc", "qt/widgets/cameraview.cc", "#third_party/qrcode/QrCode.cc", - "qt/request_repeater.cc", "qt/qt_window.cc", "qt/network/networking.cc", "qt/network/wifi_manager.cc"] - -widgets = qt_env.Library("qt_widgets", widgets_src, LIBS=base_libs) -Export('widgets') -qt_libs = [widgets, qt_util] + base_libs - -qt_src = ["main.cc", "ui.cc", "qt/sidebar.cc", "qt/body.cc", - "qt/window.cc", "qt/home.cc", "qt/offroad/settings.cc", - "qt/offroad/software_settings.cc", "qt/offroad/developer_panel.cc", "qt/offroad/onboarding.cc", - "qt/offroad/driverview.cc", "qt/offroad/experimental_mode.cc", "qt/offroad/firehose.cc", - "qt/onroad/onroad_home.cc", "qt/onroad/annotated_camera.cc", "qt/onroad/model.cc", - "qt/onroad/buttons.cc", "qt/onroad/alerts.cc", "qt/onroad/driver_monitoring.cc", "qt/onroad/hud.cc"] - -# build translation files +# compile gettext .po -> .mo translations with open(File("translations/languages.json").abspath) as f: languages = json.loads(f.read()) -translation_sources = [f"#selfdrive/ui/translations/{l}.ts" for l in languages.values()] -translation_targets = [src.replace(".ts", ".qm") for src in translation_sources] -lrelease_bin = 'third_party/qt5/larch64/bin/lrelease' if arch == 'larch64' else 'lrelease' -lrelease = qt_env.Command(translation_targets, translation_sources, f"{lrelease_bin} $SOURCES") -qt_env.NoClean(translation_sources) -qt_env.Precious(translation_sources) +po_sources = [f"#selfdrive/ui/translations/app_{l}.po" for l in languages.values()] +po_sources = [src for src in po_sources if os.path.exists(File(src).abspath)] +mo_targets = [src.replace(".po", ".mo") for src in po_sources] +mo_build = [] +for src, tgt in zip(po_sources, mo_targets): + mo_build.append(qt_env.Command(tgt, src, "msgfmt -o $TARGET $SOURCE")) +mo_alias = qt_env.Alias('mo', mo_build) +qt_env.AlwaysBuild(mo_alias) -# create qrc file for compiled translations to include with assets -translations_assets_src = "#selfdrive/assets/translations_assets.qrc" -with open(File(translations_assets_src).abspath, 'w') as f: - f.write('\n\n') - f.write('\n'.join([f'../ui/translations/{l}.qm' for l in languages.values()])) - f.write('\n\n') - -# build assets -assets = "#selfdrive/assets/assets.cc" -assets_src = "#selfdrive/assets/assets.qrc" -qt_env.Command(assets, [assets_src, translations_assets_src], f"rcc $SOURCES -o $TARGET") -qt_env.Depends(assets, Glob('#selfdrive/assets/*', exclude=[assets, assets_src, translations_assets_src, "#selfdrive/assets/assets.o"]) + [lrelease]) -asset_obj = qt_env.Object("assets", assets) - -# build main UI -qt_env.Program("ui", qt_src + [asset_obj], LIBS=qt_libs) if GetOption('extras'): - qt_src.remove("main.cc") # replaced by test_runner - qt_env.Program('tests/test_translations', [asset_obj, 'tests/test_runner.cc', 'tests/test_translations.cc'] + qt_src, LIBS=qt_libs) + base_libs = [common, messaging, visionipc, transformations, + 'm', 'OpenCL', 'ssl', 'crypto', 'pthread'] + qt_env["LIBS"] - # build installers - if arch != "Darwin": - raylib_env = env.Clone() - raylib_env['LIBPATH'] += [f'#third_party/raylib/{arch}/'] - raylib_env['LINKFLAGS'].append('-Wl,-strip-debug') + if arch == 'larch64': + base_libs.append('EGL') - raylib_libs = common + ["raylib"] - if arch == "larch64": - raylib_libs += ["GLESv2", "EGL", "gbm", "drm"] - else: - raylib_libs += ["GL"] + if arch == "Darwin": + del base_libs[base_libs.index('OpenCL')] + qt_env['FRAMEWORKS'] += ['OpenCL'] - release = "release3" - installers = [ - ("openpilot", release), - ("openpilot_test", f"{release}-staging"), - ("openpilot_nightly", "nightly"), - ("openpilot_internal", "nightly-dev"), - ] + # FIXME: remove this once we're on 5.15 (24.04) + qt_env['CXXFLAGS'] += ["-Wno-deprecated-declarations"] - cont = raylib_env.Command("installer/continue_openpilot.o", "installer/continue_openpilot.sh", - "ld -r -b binary -o $TARGET $SOURCE") - inter = raylib_env.Command("installer/inter_ttf.o", "installer/inter-ascii.ttf", - "ld -r -b binary -o $TARGET $SOURCE") - for name, branch in installers: - d = {'BRANCH': f"'\"{branch}\"'"} - if "internal" in name: - d['INTERNAL'] = "1" + qt_util = qt_env.Library("qt_util", ["#selfdrive/ui/qt/api.cc", "#selfdrive/ui/qt/util.cc"], LIBS=base_libs) + widgets_src = ["qt/widgets/input.cc", "qt/widgets/wifi.cc", "qt/prime_state.cc", + "qt/widgets/ssh_keys.cc", "qt/widgets/toggle.cc", "qt/widgets/controls.cc", + "qt/widgets/offroad_alerts.cc", "qt/widgets/prime.cc", "qt/widgets/keyboard.cc", + "qt/widgets/scrollview.cc", "qt/widgets/cameraview.cc", "#third_party/qrcode/QrCode.cc", + "qt/request_repeater.cc", "qt/qt_window.cc", "qt/network/networking.cc", "qt/network/wifi_manager.cc"] - obj = raylib_env.Object(f"installer/installers/installer_{name}.o", ["installer/installer.cc"], CPPDEFINES=d) - f = raylib_env.Program(f"installer/installers/installer_{name}", [obj, cont, inter], LIBS=raylib_libs) - # keep installers small - assert f[0].get_size() < 1900*1e3, f[0].get_size() + widgets = qt_env.Library("qt_widgets", widgets_src, LIBS=base_libs) + Export('widgets') + qt_libs = [widgets, qt_util] + base_libs + + qt_src = ["main.cc", "ui.cc", "qt/sidebar.cc", "qt/body.cc", + "qt/window.cc", "qt/home.cc", "qt/offroad/settings.cc", + "qt/offroad/software_settings.cc", "qt/offroad/developer_panel.cc", "qt/offroad/onboarding.cc", + "qt/offroad/driverview.cc", "qt/offroad/experimental_mode.cc", "qt/offroad/firehose.cc", + "qt/onroad/onroad_home.cc", "qt/onroad/annotated_camera.cc", "qt/onroad/model.cc", + "qt/onroad/buttons.cc", "qt/onroad/alerts.cc", "qt/onroad/driver_monitoring.cc", "qt/onroad/hud.cc"] + + # build translation files + translation_sources = [f"#selfdrive/ui/translations/{l}.ts" for l in languages.values()] + translation_targets = [src.replace(".ts", ".qm") for src in translation_sources] + lrelease_bin = 'third_party/qt5/larch64/bin/lrelease' if arch == 'larch64' else 'lrelease' + + lrelease = qt_env.Command(translation_targets, translation_sources, f"{lrelease_bin} $SOURCES") + qt_env.NoClean(translation_sources) + qt_env.Precious(translation_sources) + + # create qrc file for compiled translations to include with assets + translations_assets_src = "#selfdrive/assets/translations_assets.qrc" + with open(File(translations_assets_src).abspath, 'w') as f: + f.write('\n\n') + f.write('\n'.join([f'../ui/translations/{l}.qm' for l in languages.values()])) + f.write('\n\n') + + # build assets + assets = "#selfdrive/assets/assets.cc" + assets_src = "#selfdrive/assets/assets.qrc" + qt_env.Command(assets, [assets_src, translations_assets_src], f"rcc $SOURCES -o $TARGET") + qt_env.Depends(assets, Glob('#selfdrive/assets/*', exclude=[assets, assets_src, translations_assets_src, "#selfdrive/assets/assets.o"]) + [lrelease]) + asset_obj = qt_env.Object("assets", assets) + + # build main UI + qt_env.Program("ui", qt_src + [asset_obj], LIBS=qt_libs) + if GetOption('extras'): + qt_src.remove("main.cc") # replaced by test_runner + qt_env.Program('tests/test_translations', [asset_obj, 'tests/test_runner.cc', 'tests/test_translations.cc'] + qt_src, LIBS=qt_libs) + + # build installers + if arch != "Darwin": + raylib_env = env.Clone() + raylib_env['LIBPATH'] += [f'#third_party/raylib/{arch}/'] + raylib_env['LINKFLAGS'].append('-Wl,-strip-debug') + + raylib_libs = common + ["raylib"] + if arch == "larch64": + raylib_libs += ["GLESv2", "EGL", "gbm", "drm"] + else: + raylib_libs += ["GL"] + + release = "release3" + installers = [ + ("openpilot", release), + ("openpilot_test", f"{release}-staging"), + ("openpilot_nightly", "nightly"), + ("openpilot_internal", "nightly-dev"), + ] + + cont = raylib_env.Command("installer/continue_openpilot.o", "installer/continue_openpilot.sh", + "ld -r -b binary -o $TARGET $SOURCE") + inter = raylib_env.Command("installer/inter_ttf.o", "installer/inter-ascii.ttf", + "ld -r -b binary -o $TARGET $SOURCE") + for name, branch in installers: + d = {'BRANCH': f"'\"{branch}\"'"} + if "internal" in name: + d['INTERNAL'] = "1" + + obj = raylib_env.Object(f"installer/installers/installer_{name}.o", ["installer/installer.cc"], CPPDEFINES=d) + f = raylib_env.Program(f"installer/installers/installer_{name}", [obj, cont, inter], LIBS=raylib_libs) + # keep installers small + assert f[0].get_size() < 1900*1e3, f[0].get_size() diff --git a/selfdrive/ui/layouts/settings/developer.py b/selfdrive/ui/layouts/settings/developer.py index 91268960c..4b8f08796 100644 --- a/selfdrive/ui/layouts/settings/developer.py +++ b/selfdrive/ui/layouts/settings/developer.py @@ -6,23 +6,24 @@ from openpilot.system.ui.widgets.list_view import toggle_item from openpilot.system.ui.widgets.scroller import Scroller from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog from openpilot.system.ui.lib.application import gui_app -from openpilot.system.ui.lib.multilang import tr +from openpilot.system.ui.lib.multilang import tr, tr_noop from openpilot.system.ui.widgets import DialogResult # Description constants DESCRIPTIONS = { - 'enable_adb': tr( + 'enable_adb': tr_noop( "ADB (Android Debug Bridge) allows connecting to your device over USB or over the network. " + "See https://docs.comma.ai/how-to/connect-to-comma for more info." ), - 'ssh_key': tr( + 'ssh_key': tr_noop( "Warning: This grants SSH access to all public keys in your GitHub settings. Never enter a GitHub username " + "other than your own. A comma employee will NEVER ask you to add their GitHub username." ), - 'alpha_longitudinal': tr( + 'alpha_longitudinal': tr_noop( "WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB).

" + "On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. " + - "Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha." + "Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha. " + + "Changing this setting will restart openpilot if the car is powered on." ), } @@ -35,8 +36,8 @@ class DeveloperLayout(Widget): # Build items and keep references for callbacks/state updates self._adb_toggle = toggle_item( - tr("Enable ADB"), - description=DESCRIPTIONS["enable_adb"], + lambda: tr("Enable ADB"), + description=lambda: DESCRIPTIONS["enable_adb"], initial_state=self._params.get_bool("AdbEnabled"), callback=self._on_enable_adb, enabled=ui_state.is_offroad, @@ -44,15 +45,15 @@ class DeveloperLayout(Widget): # SSH enable toggle + SSH key management self._ssh_toggle = toggle_item( - tr("Enable SSH"), + lambda: tr("Enable SSH"), description="", initial_state=self._params.get_bool("SshEnabled"), callback=self._on_enable_ssh, ) - self._ssh_keys = ssh_key_item("SSH Keys", description=DESCRIPTIONS["ssh_key"]) + self._ssh_keys = ssh_key_item(lambda: tr("SSH Keys"), description=lambda: DESCRIPTIONS["ssh_key"]) self._joystick_toggle = toggle_item( - tr("Joystick Debug Mode"), + lambda: tr("Joystick Debug Mode"), description="", initial_state=self._params.get_bool("JoystickDebugMode"), callback=self._on_joystick_debug_mode, @@ -60,22 +61,20 @@ class DeveloperLayout(Widget): ) self._long_maneuver_toggle = toggle_item( - tr("Longitudinal Maneuver Mode"), + lambda: tr("Longitudinal Maneuver Mode"), description="", initial_state=self._params.get_bool("LongitudinalManeuverMode"), callback=self._on_long_maneuver_mode, ) self._alpha_long_toggle = toggle_item( - tr("openpilot Longitudinal Control (Alpha)"), - description=DESCRIPTIONS["alpha_longitudinal"], + lambda: tr("openpilot Longitudinal Control (Alpha)"), + description=lambda: DESCRIPTIONS["alpha_longitudinal"], initial_state=self._params.get_bool("AlphaLongitudinalEnabled"), callback=self._on_alpha_long_enabled, enabled=lambda: not ui_state.engaged, ) - self._alpha_long_toggle.set_description(self._alpha_long_toggle.description + " Changing this setting will restart openpilot if the car is powered on.") - items = [ self._adb_toggle, self._ssh_toggle, diff --git a/selfdrive/ui/layouts/settings/device.py b/selfdrive/ui/layouts/settings/device.py index e9bdf59e2..3f762a884 100644 --- a/selfdrive/ui/layouts/settings/device.py +++ b/selfdrive/ui/layouts/settings/device.py @@ -1,5 +1,4 @@ import os -import json import math from cereal import messaging, log @@ -12,7 +11,7 @@ from openpilot.selfdrive.ui.layouts.onboarding import TrainingGuide from openpilot.selfdrive.ui.widgets.pairing_dialog import PairingDialog from openpilot.system.hardware import TICI from openpilot.system.ui.lib.application import gui_app -from openpilot.system.ui.lib.multilang import tr +from openpilot.system.ui.lib.multilang import multilang, tr, tr_noop from openpilot.system.ui.widgets import Widget, DialogResult from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog, alert_dialog from openpilot.system.ui.widgets.html_render import HtmlModal @@ -22,10 +21,10 @@ from openpilot.system.ui.widgets.scroller import Scroller # Description constants DESCRIPTIONS = { - 'pair_device': tr("Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer."), - 'driver_camera': tr("Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)"), - 'reset_calibration': tr("openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down."), - 'review_guide': tr("Review the rules, features, and limitations of openpilot"), + 'pair_device': tr_noop("Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer."), + 'driver_camera': tr_noop("Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)"), + 'reset_calibration': tr_noop("openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down."), + 'review_guide': tr_noop("Review the rules, features, and limitations of openpilot"), } @@ -46,27 +45,27 @@ class DeviceLayout(Widget): ui_state.add_offroad_transition_callback(self._offroad_transition) def _initialize_items(self): - dongle_id = self._params.get("DongleId") or tr("N/A") - serial = self._params.get("HardwareSerial") or tr("N/A") - - self._pair_device_btn = button_item(tr("Pair Device"), tr("PAIR"), DESCRIPTIONS['pair_device'], callback=self._pair_device) + self._pair_device_btn = button_item(lambda: tr("Pair Device"), lambda: tr("PAIR"), lambda: tr(DESCRIPTIONS['pair_device']), callback=self._pair_device) self._pair_device_btn.set_visible(lambda: not ui_state.prime_state.is_paired()) - self._reset_calib_btn = button_item(tr("Reset Calibration"), tr("RESET"), DESCRIPTIONS['reset_calibration'], callback=self._reset_calibration_prompt) + self._reset_calib_btn = button_item(lambda: tr("Reset Calibration"), lambda: tr("RESET"), lambda: tr(DESCRIPTIONS['reset_calibration']), + callback=self._reset_calibration_prompt) self._reset_calib_btn.set_description_opened_callback(self._update_calib_description) - self._power_off_btn = dual_button_item(tr("Reboot"), tr("Power Off"), left_callback=self._reboot_prompt, right_callback=self._power_off_prompt) + self._power_off_btn = dual_button_item(lambda: tr("Reboot"), lambda: tr("Power Off"), + left_callback=self._reboot_prompt, right_callback=self._power_off_prompt) items = [ - text_item(tr("Dongle ID"), dongle_id), - text_item(tr("Serial"), serial), + text_item(lambda: tr("Dongle ID"), self._params.get("DongleId") or (lambda: tr("N/A"))), + text_item(lambda: tr("Serial"), self._params.get("HardwareSerial") or (lambda: tr("N/A"))), self._pair_device_btn, - button_item(tr("Driver Camera"), tr("PREVIEW"), DESCRIPTIONS['driver_camera'], callback=self._show_driver_camera, enabled=ui_state.is_offroad), + button_item(lambda: tr("Driver Camera"), lambda: tr("PREVIEW"), lambda: tr(DESCRIPTIONS['driver_camera']), + callback=self._show_driver_camera, enabled=ui_state.is_offroad), self._reset_calib_btn, - button_item(tr("Review Training Guide"), tr("REVIEW"), DESCRIPTIONS['review_guide'], self._on_review_training_guide, enabled=ui_state.is_offroad), - regulatory_btn := button_item(tr("Regulatory"), tr("VIEW"), callback=self._on_regulatory, enabled=ui_state.is_offroad), - # TODO: implement multilang - # button_item(tr("Change Language"), tr("CHANGE"), callback=self._show_language_selection, enabled=ui_state.is_offroad), + button_item(lambda: tr("Review Training Guide"), lambda: tr("REVIEW"), lambda: tr(DESCRIPTIONS['review_guide']), + self._on_review_training_guide, enabled=ui_state.is_offroad), + regulatory_btn := button_item(lambda: tr("Regulatory"), lambda: tr("VIEW"), callback=self._on_regulatory, enabled=ui_state.is_offroad), + button_item(lambda: tr("Change Language"), lambda: tr("CHANGE"), callback=self._show_language_dialog), self._power_off_btn, ] regulatory_btn.set_visible(TICI) @@ -81,23 +80,16 @@ class DeviceLayout(Widget): def _render(self, rect): self._scroller.render(rect) - def _show_language_selection(self): - try: - languages_file = os.path.join(BASEDIR, "selfdrive/ui/translations/languages.json") - with open(languages_file, encoding='utf-8') as f: - languages = json.load(f) + def _show_language_dialog(self): + def handle_language_selection(result: int): + if result == 1 and self._select_language_dialog: + selected_language = multilang.languages[self._select_language_dialog.selection] + multilang.change_language(selected_language) + self._update_calib_description() + self._select_language_dialog = None - self._select_language_dialog = MultiOptionDialog("Select a language", languages) - gui_app.set_modal_overlay(self._select_language_dialog, callback=self._handle_language_selection) - except FileNotFoundError: - pass - - def _handle_language_selection(self, result: int): - if result == 1 and self._select_language_dialog: - selected_language = self._select_language_dialog.selection - self._params.put("LanguageSetting", selected_language) - - self._select_language_dialog = None + self._select_language_dialog = MultiOptionDialog(tr("Select a language"), multilang.languages, multilang.codes[multilang.language]) + gui_app.set_modal_overlay(self._select_language_dialog, callback=handle_language_selection) def _show_driver_camera(self): if not self._driver_camera: @@ -127,7 +119,7 @@ class DeviceLayout(Widget): gui_app.set_modal_overlay(dialog, callback=reset_calibration) def _update_calib_description(self): - desc = DESCRIPTIONS['reset_calibration'] + desc = tr(DESCRIPTIONS['reset_calibration']) calib_bytes = self._params.get("CalibrationParams") if calib_bytes: diff --git a/selfdrive/ui/layouts/settings/firehose.py b/selfdrive/ui/layouts/settings/firehose.py index f4d70eaa4..bbd4aef53 100644 --- a/selfdrive/ui/layouts/settings/firehose.py +++ b/selfdrive/ui/layouts/settings/firehose.py @@ -8,20 +8,20 @@ from openpilot.common.swaglog import cloudlog from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.system.athena.registration import UNREGISTERED_DONGLE_ID from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE -from openpilot.system.ui.lib.multilang import tr, trn +from openpilot.system.ui.lib.multilang import tr, trn, tr_noop from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel from openpilot.system.ui.lib.wrap_text import wrap_text from openpilot.system.ui.widgets import Widget from openpilot.selfdrive.ui.lib.api_helpers import get_token -TITLE = tr("Firehose Mode") -DESCRIPTION = tr( +TITLE = tr_noop("Firehose Mode") +DESCRIPTION = tr_noop( "openpilot learns to drive by watching humans, like you, drive.\n\n" + "Firehose Mode allows you to maximize your training data uploads to improve " + "openpilot's driving models. More data means bigger models, which means better Experimental Mode." ) -INSTRUCTIONS = tr( +INSTRUCTIONS = tr_noop( "For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.\n\n" + "Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.\n\n\n" + "Frequently Asked Questions\n\n" @@ -86,14 +86,15 @@ class FirehoseLayout(Widget): w = int(rect.width - 80) # Title (centered) + title_text = tr(TITLE) # live translate title_font = gui_app.font(FontWeight.MEDIUM) - text_width = measure_text_cached(title_font, TITLE, 100).x + text_width = measure_text_cached(title_font, title_text, 100).x title_x = rect.x + (rect.width - text_width) / 2 - rl.draw_text_ex(title_font, TITLE, rl.Vector2(title_x, y), 100, 0, rl.WHITE) + rl.draw_text_ex(title_font, title_text, rl.Vector2(title_x, y), 100, 0, rl.WHITE) y += 200 # Description - y = self._draw_wrapped_text(x, y, w, DESCRIPTION, gui_app.font(FontWeight.NORMAL), 45, rl.WHITE) + y = self._draw_wrapped_text(x, y, w, tr(DESCRIPTION), gui_app.font(FontWeight.NORMAL), 45, rl.WHITE) y += 40 + 20 # Separator @@ -117,7 +118,7 @@ class FirehoseLayout(Widget): y += 30 + 20 # Instructions - y = self._draw_wrapped_text(x, y, w, INSTRUCTIONS, gui_app.font(FontWeight.NORMAL), 40, self.LIGHT_GRAY) + y = self._draw_wrapped_text(x, y, w, tr(INSTRUCTIONS), gui_app.font(FontWeight.NORMAL), 40, self.LIGHT_GRAY) # bottom margin + remove effect of scroll offset return int(round(y - self.scroll_panel.offset + 40)) diff --git a/selfdrive/ui/layouts/settings/settings.py b/selfdrive/ui/layouts/settings/settings.py index 7f431d3a7..f13383fa5 100644 --- a/selfdrive/ui/layouts/settings/settings.py +++ b/selfdrive/ui/layouts/settings/settings.py @@ -8,7 +8,7 @@ from openpilot.selfdrive.ui.layouts.settings.firehose import FirehoseLayout from openpilot.selfdrive.ui.layouts.settings.software import SoftwareLayout from openpilot.selfdrive.ui.layouts.settings.toggles import TogglesLayout from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos -from openpilot.system.ui.lib.multilang import tr +from openpilot.system.ui.lib.multilang import tr, tr_noop from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.lib.wifi_manager import WifiManager from openpilot.system.ui.widgets import Widget @@ -59,12 +59,12 @@ class SettingsLayout(Widget): wifi_manager.set_active(False) self._panels = { - PanelType.DEVICE: PanelInfo(tr("Device"), DeviceLayout()), - PanelType.NETWORK: PanelInfo(tr("Network"), NetworkUI(wifi_manager)), - PanelType.TOGGLES: PanelInfo(tr("Toggles"), TogglesLayout()), - PanelType.SOFTWARE: PanelInfo(tr("Software"), SoftwareLayout()), - PanelType.FIREHOSE: PanelInfo(tr("Firehose"), FirehoseLayout()), - PanelType.DEVELOPER: PanelInfo(tr("Developer"), DeveloperLayout()), + PanelType.DEVICE: PanelInfo(tr_noop("Device"), DeviceLayout()), + PanelType.NETWORK: PanelInfo(tr_noop("Network"), NetworkUI(wifi_manager)), + PanelType.TOGGLES: PanelInfo(tr_noop("Toggles"), TogglesLayout()), + PanelType.SOFTWARE: PanelInfo(tr_noop("Software"), SoftwareLayout()), + PanelType.FIREHOSE: PanelInfo(tr_noop("Firehose"), FirehoseLayout()), + PanelType.DEVELOPER: PanelInfo(tr_noop("Developer"), DeveloperLayout()), } self._font_medium = gui_app.font(FontWeight.MEDIUM) @@ -116,11 +116,12 @@ class SettingsLayout(Widget): is_selected = panel_type == self._current_panel text_color = TEXT_SELECTED if is_selected else TEXT_NORMAL # Draw button text (right-aligned) - text_size = measure_text_cached(self._font_medium, panel_info.name, 65) + panel_name = tr(panel_info.name) + text_size = measure_text_cached(self._font_medium, panel_name, 65) text_pos = rl.Vector2( button_rect.x + button_rect.width - text_size.x, button_rect.y + (button_rect.height - text_size.y) / 2 ) - rl.draw_text_ex(self._font_medium, panel_info.name, text_pos, 65, 0, text_color) + rl.draw_text_ex(self._font_medium, panel_name, text_pos, 65, 0, text_color) # Store button rect for click detection panel_info.button_rect = button_rect diff --git a/selfdrive/ui/layouts/settings/software.py b/selfdrive/ui/layouts/settings/software.py index 1b78b5c9e..73c8946f5 100644 --- a/selfdrive/ui/layouts/settings/software.py +++ b/selfdrive/ui/layouts/settings/software.py @@ -45,12 +45,12 @@ class SoftwareLayout(Widget): def __init__(self): super().__init__() - self._onroad_label = ListItem(title=tr("Updates are only downloaded while the car is off.")) - self._version_item = text_item(tr("Current Version"), ui_state.params.get("UpdaterCurrentDescription") or "") - self._download_btn = button_item(tr("Download"), tr("CHECK"), callback=self._on_download_update) + self._onroad_label = ListItem(lambda: tr("Updates are only downloaded while the car is off.")) + self._version_item = text_item(lambda: tr("Current Version"), ui_state.params.get("UpdaterCurrentDescription") or "") + self._download_btn = button_item(lambda: tr("Download"), lambda: tr("CHECK"), callback=self._on_download_update) # Install button is initially hidden - self._install_btn = button_item(tr("Install Update"), tr("INSTALL"), callback=self._on_install_update) + self._install_btn = button_item(lambda: tr("Install Update"), lambda: tr("INSTALL"), callback=self._on_install_update) self._install_btn.set_visible(False) self._select_branch_dialog: MultiOptionDialog | None = None @@ -70,7 +70,7 @@ class SoftwareLayout(Widget): self._download_btn, self._install_btn, self._target_branch_btn, - button_item("Uninstall", tr("UNINSTALL"), callback=self._on_uninstall), + button_item(lambda: tr("Uninstall"), lambda: tr("UNINSTALL"), callback=self._on_uninstall), ] return items diff --git a/selfdrive/ui/layouts/settings/toggles.py b/selfdrive/ui/layouts/settings/toggles.py index 1e4d5c6c8..e4441edec 100644 --- a/selfdrive/ui/layouts/settings/toggles.py +++ b/selfdrive/ui/layouts/settings/toggles.py @@ -5,7 +5,7 @@ from openpilot.system.ui.widgets.list_view import multiple_button_item, toggle_i from openpilot.system.ui.widgets.scroller import Scroller from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog from openpilot.system.ui.lib.application import gui_app -from openpilot.system.ui.lib.multilang import tr +from openpilot.system.ui.lib.multilang import tr, tr_noop from openpilot.system.ui.widgets import DialogResult from openpilot.selfdrive.ui.ui_state import ui_state @@ -13,24 +13,24 @@ PERSONALITY_TO_INT = log.LongitudinalPersonality.schema.enumerants # Description constants DESCRIPTIONS = { - "OpenpilotEnabledToggle": tr( + "OpenpilotEnabledToggle": tr_noop( "Use the openpilot system for adaptive cruise control and lane keep driver assistance. " + "Your attention is required at all times to use this feature." ), - "DisengageOnAccelerator": tr("When enabled, pressing the accelerator pedal will disengage openpilot."), - "LongitudinalPersonality": tr( + "DisengageOnAccelerator": tr_noop("When enabled, pressing the accelerator pedal will disengage openpilot."), + "LongitudinalPersonality": tr_noop( "Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. " + "In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with " + "your steering wheel distance button." ), - "IsLdwEnabled": tr( + "IsLdwEnabled": tr_noop( "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)." ), - "AlwaysOnDM": tr("Enable driver monitoring even when openpilot is not engaged."), - 'RecordFront': tr("Upload data from the driver facing camera and help improve the driver monitoring algorithm."), - "IsMetric": tr("Display speed in km/h instead of mph."), - "RecordAudio": tr("Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect."), + "AlwaysOnDM": tr_noop("Enable driver monitoring even when openpilot is not engaged."), + 'RecordFront': tr_noop("Upload data from the driver facing camera and help improve the driver monitoring algorithm."), + "IsMetric": tr_noop("Display speed in km/h instead of mph."), + "RecordAudio": tr_noop("Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect."), } @@ -43,49 +43,49 @@ class TogglesLayout(Widget): # param, title, desc, icon, needs_restart self._toggle_defs = { "OpenpilotEnabledToggle": ( - tr("Enable openpilot"), + lambda: tr("Enable openpilot"), DESCRIPTIONS["OpenpilotEnabledToggle"], "chffr_wheel.png", True, ), "ExperimentalMode": ( - tr("Experimental Mode"), + lambda: tr("Experimental Mode"), "", "experimental_white.png", False, ), "DisengageOnAccelerator": ( - tr("Disengage on Accelerator Pedal"), + lambda: tr("Disengage on Accelerator Pedal"), DESCRIPTIONS["DisengageOnAccelerator"], "disengage_on_accelerator.png", False, ), "IsLdwEnabled": ( - tr("Enable Lane Departure Warnings"), + lambda: tr("Enable Lane Departure Warnings"), DESCRIPTIONS["IsLdwEnabled"], "warning.png", False, ), "AlwaysOnDM": ( - tr("Always-On Driver Monitoring"), + lambda: tr("Always-On Driver Monitoring"), DESCRIPTIONS["AlwaysOnDM"], "monitoring.png", False, ), "RecordFront": ( - tr("Record and Upload Driver Camera"), + lambda: tr("Record and Upload Driver Camera"), DESCRIPTIONS["RecordFront"], "monitoring.png", True, ), "RecordAudio": ( - tr("Record and Upload Microphone Audio"), + lambda: tr("Record and Upload Microphone Audio"), DESCRIPTIONS["RecordAudio"], "microphone.png", True, ), "IsMetric": ( - tr("Use Metric System"), + lambda: tr("Use Metric System"), DESCRIPTIONS["IsMetric"], "metric.png", False, @@ -93,9 +93,9 @@ class TogglesLayout(Widget): } self._long_personality_setting = multiple_button_item( - tr("Driving Personality"), + lambda: tr("Driving Personality"), DESCRIPTIONS["LongitudinalPersonality"], - buttons=[tr("Aggressive"), tr("Standard"), tr("Relaxed")], + buttons=[lambda: tr("Aggressive"), lambda: tr("Standard"), lambda: tr("Relaxed")], button_width=255, callback=self._set_longitudinal_personality, selected_index=self._params.get("LongitudinalPersonality", return_default=True), @@ -119,8 +119,11 @@ class TogglesLayout(Widget): locked = False toggle.action_item.set_enabled(not locked) + # Make description callable for live translation + additional_desc = "" if needs_restart and not locked: - toggle.set_description(toggle.description + tr(" Changing this setting will restart openpilot if the car is powered on.")) + additional_desc = tr("Changing this setting will restart openpilot if the car is powered on.") + toggle.set_description(lambda og_desc=toggle.description, add_desc=additional_desc: tr(og_desc) + (" " + tr(add_desc) if add_desc else "")) # track for engaged state updates if locked: diff --git a/selfdrive/ui/layouts/sidebar.py b/selfdrive/ui/layouts/sidebar.py index 48b577ea5..d468442b1 100644 --- a/selfdrive/ui/layouts/sidebar.py +++ b/selfdrive/ui/layouts/sidebar.py @@ -5,7 +5,7 @@ from collections.abc import Callable from cereal import log from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos, FONT_SCALE -from openpilot.system.ui.lib.multilang import tr +from openpilot.system.ui.lib.multilang import tr, tr_noop from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.widgets import Widget @@ -40,13 +40,13 @@ class Colors: NETWORK_TYPES = { - NetworkType.none: tr("--"), - NetworkType.wifi: tr("Wi-Fi"), - NetworkType.ethernet: tr("ETH"), - NetworkType.cell2G: tr("2G"), - NetworkType.cell3G: tr("3G"), - NetworkType.cell4G: tr("LTE"), - NetworkType.cell5G: tr("5G"), + NetworkType.none: tr_noop("--"), + NetworkType.wifi: tr_noop("Wi-Fi"), + NetworkType.ethernet: tr_noop("ETH"), + NetworkType.cell2G: tr_noop("2G"), + NetworkType.cell3G: tr_noop("3G"), + NetworkType.cell4G: tr_noop("LTE"), + NetworkType.cell5G: tr_noop("5G"), } @@ -68,9 +68,9 @@ class Sidebar(Widget): self._net_type = NETWORK_TYPES.get(NetworkType.none) self._net_strength = 0 - self._temp_status = MetricData(tr("TEMP"), tr("GOOD"), Colors.GOOD) - self._panda_status = MetricData(tr("VEHICLE"), tr("ONLINE"), Colors.GOOD) - self._connect_status = MetricData(tr("CONNECT"), tr("OFFLINE"), Colors.WARNING) + self._temp_status = MetricData(tr_noop("TEMP"), tr_noop("GOOD"), Colors.GOOD) + self._panda_status = MetricData(tr_noop("VEHICLE"), tr_noop("ONLINE"), Colors.GOOD) + self._connect_status = MetricData(tr_noop("CONNECT"), tr_noop("OFFLINE"), Colors.WARNING) self._recording_audio = False self._home_img = gui_app.texture("images/button_home.png", HOME_BTN.width, HOME_BTN.height) @@ -114,7 +114,7 @@ class Sidebar(Widget): self._update_panda_status() def _update_network_status(self, device_state): - self._net_type = NETWORK_TYPES.get(device_state.networkType.raw, tr("Unknown")) + self._net_type = NETWORK_TYPES.get(device_state.networkType.raw, tr_noop("Unknown")) strength = device_state.networkStrength self._net_strength = max(0, min(5, strength.raw + 1)) if strength > 0 else 0 @@ -122,26 +122,26 @@ class Sidebar(Widget): thermal_status = device_state.thermalStatus if thermal_status == ThermalStatus.green: - self._temp_status.update(tr("TEMP"), tr("GOOD"), Colors.GOOD) + self._temp_status.update(tr_noop("TEMP"), tr_noop("GOOD"), Colors.GOOD) elif thermal_status == ThermalStatus.yellow: - self._temp_status.update(tr("TEMP"), tr("OK"), Colors.WARNING) + self._temp_status.update(tr_noop("TEMP"), tr_noop("OK"), Colors.WARNING) else: - self._temp_status.update(tr("TEMP"), tr("HIGH"), Colors.DANGER) + self._temp_status.update(tr_noop("TEMP"), tr_noop("HIGH"), Colors.DANGER) def _update_connection_status(self, device_state): last_ping = device_state.lastAthenaPingTime if last_ping == 0: - self._connect_status.update(tr("CONNECT"), tr("OFFLINE"), Colors.WARNING) + self._connect_status.update(tr_noop("CONNECT"), tr_noop("OFFLINE"), Colors.WARNING) elif time.monotonic_ns() - last_ping < 80_000_000_000: # 80 seconds in nanoseconds - self._connect_status.update(tr("CONNECT"), tr("ONLINE"), Colors.GOOD) + self._connect_status.update(tr_noop("CONNECT"), tr_noop("ONLINE"), Colors.GOOD) else: - self._connect_status.update(tr("CONNECT"), tr("ERROR"), Colors.DANGER) + self._connect_status.update(tr_noop("CONNECT"), tr_noop("ERROR"), Colors.DANGER) def _update_panda_status(self): if ui_state.panda_type == log.PandaState.PandaType.unknown: - self._panda_status.update(tr("NO"), tr("PANDA"), Colors.DANGER) + self._panda_status.update(tr_noop("NO"), tr_noop("PANDA"), Colors.DANGER) else: - self._panda_status.update(tr("VEHICLE"), tr("ONLINE"), Colors.GOOD) + self._panda_status.update(tr_noop("VEHICLE"), tr_noop("ONLINE"), Colors.GOOD) def _handle_mouse_release(self, mouse_pos: MousePos): if rl.check_collision_point_rec(mouse_pos, SETTINGS_BTN): @@ -197,7 +197,7 @@ class Sidebar(Widget): # Network type text text_y = rect.y + 247 text_pos = rl.Vector2(rect.x + 58, text_y) - rl.draw_text_ex(self._font_regular, self._net_type, text_pos, FONT_SIZE, 0, Colors.WHITE) + rl.draw_text_ex(self._font_regular, tr(self._net_type), text_pos, FONT_SIZE, 0, Colors.WHITE) def _draw_metrics(self, rect: rl.Rectangle): metrics = [(self._temp_status, 338), (self._panda_status, 496), (self._connect_status, 654)] @@ -217,7 +217,7 @@ class Sidebar(Widget): rl.draw_rectangle_rounded_lines_ex(metric_rect, 0.3, 10, 2, Colors.METRIC_BORDER) # Draw label and value - labels = [metric.label, metric.value] + labels = [tr(metric.label), tr(metric.value)] text_y = metric_rect.y + (metric_rect.height / 2 - len(labels) * FONT_SIZE * FONT_SCALE) for text in labels: text_size = measure_text_cached(self._font_bold, text, FONT_SIZE) diff --git a/selfdrive/ui/translations/app.pot b/selfdrive/ui/translations/app.pot new file mode 100644 index 000000000..b68a19787 --- /dev/null +++ b/selfdrive/ui/translations/app.pot @@ -0,0 +1,1134 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-10-22 15:30-0700\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" + +#: /home/batman/openpilot/system/ui/widgets/html_render.py:247 +#: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:93 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:127 +#, python-format +msgid "OK" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:23 +#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:34 +#: /home/batman/openpilot/system/ui/widgets/keyboard.py:81 +#: /home/batman/openpilot/system/ui/widgets/network.py:318 +#, python-format +msgid "Cancel" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:35 +#, python-format +msgid "Select" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:74 +#: /home/batman/openpilot/system/ui/widgets/network.py:95 +#, python-format +msgid "Advanced" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:99 +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:147 +#, python-format +msgid "Back" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:120 +#, python-format +msgid "Enable Tethering" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:123 +#: /home/batman/openpilot/system/ui/widgets/network.py:139 +#, python-format +msgid "EDIT" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:124 +#, python-format +msgid "Tethering Password" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:129 +#, python-format +msgid "Enable Roaming" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:134 +#, python-format +msgid "Cellular Metered" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:135 +#, python-format +msgid "Prevent large data uploads when on a metered cellular connection" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:139 +#, python-format +msgid "APN Setting" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#, python-format +msgid "default" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#, python-format +msgid "metered" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#, python-format +msgid "unmetered" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:144 +#, python-format +msgid "Wi-Fi Network Metered" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:144 +#, python-format +msgid "Prevent large data uploads when on a metered Wi-Fi connection" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:150 +#, python-format +msgid "IP Address" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:155 +#, python-format +msgid "Hidden Network" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:155 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:138 +#, python-format +msgid "CONNECT" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:204 +#, python-format +msgid "Enter APN" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:204 +#, python-format +msgid "leave blank for automatic configuration" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:237 +#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#, python-format +msgid "Enter password" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:237 +#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#, python-format +msgid "for \"{}\"" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:241 +#, python-format +msgid "Enter SSID" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:254 +#, python-format +msgid "Enter new tethering password" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:310 +#, python-format +msgid "Scanning Wi-Fi networks..." +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#, python-format +msgid "Wrong password" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:318 +#: /home/batman/openpilot/system/ui/widgets/network.py:451 +#, python-format +msgid "Forget" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:319 +#, python-format +msgid "Forget Wi-Fi Network \"{}\"?" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:369 +#, python-format +msgid "CONNECTING..." +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:373 +#, python-format +msgid "FORGETTING..." +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/list_view.py:115 +#: /home/batman/openpilot/system/ui/widgets/list_view.py:153 +#, python-format +msgid "Error" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:103 +#, python-format +msgid "Pair your device to your comma account" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:128 +#, python-format +msgid "Go to https://connect.comma.ai on your phone" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:129 +#, python-format +msgid "Click \"add new device\" and scan the QR code on the right" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:130 +#, python-format +msgid "Bookmark connect.comma.ai to your home screen to use it like an app" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:161 +#, python-format +msgid "QR Code Error" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:29 +msgid "LOADING" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:30 +msgid "ADD" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:31 +msgid "REMOVE" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:89 +#, python-format +msgid "Enter your GitHub username" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:114 +#, python-format +msgid "No SSH keys found" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:123 +#, python-format +msgid "Request timed out" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:126 +#, python-format +msgid "No SSH keys found for user '{}'" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:33 +#, python-format +msgid "Upgrade Now" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:38 +#, python-format +msgid "Become a comma prime member at connect.comma.ai" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:44 +#, python-format +msgid "PRIME FEATURES:" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "Remote access" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "24/7 LTE connectivity" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "1 year of drive storage" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "Remote snapshots" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:62 +#, python-format +msgid "✓ SUBSCRIBED" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:63 +#, python-format +msgid "comma prime" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/exp_mode_button.py:50 +#, python-format +msgid "EXPERIMENTAL MODE ON" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/exp_mode_button.py:50 +#, python-format +msgid "CHILL MODE ON" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 +#, python-format +msgid "Close" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 +#, python-format +msgid "Snooze Update" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 +#, python-format +msgid "Acknowledge Excessive Actuation" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 +#, python-format +msgid "Reboot and Update" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:320 +#, python-format +msgid "No release notes available." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:19 +#, python-format +msgid "Pair device" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:20 +#, python-format +msgid "Open" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:22 +#, python-format +msgid "🔥 Firehose Mode 🔥" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:44 +#, python-format +msgid "Finish Setup" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:48 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:24 +#, python-format +msgid "" +"Pair your device with comma connect (connect.comma.ai) and claim your comma " +"prime offer." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:75 +#, python-format +msgid "" +"Maximize your training data uploads to improve openpilot's driving models." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:91 +#, python-format +msgid "Please connect to Wi-Fi to complete initial pairing" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/home.py:155 +#, python-format +msgid "UPDATE" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/home.py:169 +#, python-format +msgid "{} ALERT" +msgid_plural "{} ALERTS" +msgstr[0] "" +msgstr[1] "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:43 +msgid "--" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:44 +msgid "Wi-Fi" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:45 +msgid "ETH" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:46 +msgid "2G" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:47 +msgid "3G" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:48 +msgid "LTE" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:49 +msgid "5G" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:129 +msgid "TEMP" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:125 +msgid "GOOD" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:144 +msgid "VEHICLE" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:144 +msgid "ONLINE" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:134 +msgid "OFFLINE" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:117 +msgid "Unknown" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:129 +msgid "HIGH" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:138 +msgid "ERROR" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:142 +msgid "NO" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:142 +msgid "PANDA" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:111 +#, python-format +msgid "Welcome to openpilot" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:112 +#, python-format +msgid "" +"You must accept the Terms and Conditions to use openpilot. Read the latest " +"terms at https://comma.ai/terms before continuing." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:115 +#, python-format +msgid "Decline" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:116 +#, python-format +msgid "Agree" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:145 +#, python-format +msgid "You must accept the Terms and Conditions in order to use openpilot." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:148 +#, python-format +msgid "Decline, uninstall openpilot" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:18 +msgid "Firehose Mode" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:20 +msgid "" +"openpilot learns to drive by watching humans, like you, drive.\n" +"\n" +"Firehose Mode allows you to maximize your training data uploads to improve " +"openpilot's driving models. More data means bigger models, which means " +"better Experimental Mode." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:25 +msgid "" +"For maximum effectiveness, bring your device inside and connect to a good " +"USB-C adapter and Wi-Fi weekly.\n" +"\n" +"Firehose Mode can also work while you're driving if connected to a hotspot " +"or unlimited SIM card.\n" +"\n" +"\n" +"Frequently Asked Questions\n" +"\n" +"Does it matter how or where I drive? Nope, just drive as you normally " +"would.\n" +"\n" +"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a " +"subset of your segments.\n" +"\n" +"What's a good USB-C adapter? Any fast phone or laptop charger should be " +"fine.\n" +"\n" +"Does it matter which software I run? Yes, only upstream openpilot (and " +"particular forks) are able to be used for training." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:111 +#, python-format +msgid "{} segment of your driving is in the training dataset so far." +msgid_plural "{} segments of your driving is in the training dataset so far." +msgstr[0] "" +msgstr[1] "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:138 +#, python-format +msgid "ACTIVE" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:140 +#, python-format +msgid "INACTIVE: connect to an unmetered network" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:15 +msgid "" +"ADB (Android Debug Bridge) allows connecting to your device over USB or over " +"the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:19 +msgid "" +"Warning: This grants SSH access to all public keys in your GitHub settings. " +"Never enter a GitHub username other than your own. A comma employee will " +"NEVER ask you to add their GitHub username." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:23 +msgid "" +"WARNING: openpilot longitudinal control is in alpha for this car and will " +"disable Automatic Emergency Braking (AEB).

On this car, openpilot " +"defaults to the car's built-in ACC instead of openpilot's longitudinal " +"control. Enable this to switch to openpilot longitudinal control. Enabling " +"Experimental mode is recommended when enabling openpilot longitudinal " +"control alpha. Changing this setting will restart openpilot if the car is " +"powered on." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:39 +#, python-format +msgid "Enable ADB" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:48 +#, python-format +msgid "Enable SSH" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:53 +#, python-format +msgid "SSH Keys" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:56 +#, python-format +msgid "Joystick Debug Mode" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:64 +#, python-format +msgid "Longitudinal Maneuver Mode" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:71 +#, python-format +msgid "openpilot Longitudinal Control (Alpha)" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:166 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:228 +#, python-format +msgid "Enable" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:19 +#, python-format +msgid "never" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:30 +#, python-format +msgid "now" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:33 +#, python-format +msgid "{} minute ago" +msgid_plural "{} minutes ago" +msgstr[0] "" +msgstr[1] "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:36 +#, python-format +msgid "{} hour ago" +msgid_plural "{} hours ago" +msgstr[0] "" +msgstr[1] "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:39 +#, python-format +msgid "{} day ago" +msgid_plural "{} days ago" +msgstr[0] "" +msgstr[1] "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:47 +#, python-format +msgid "Updates are only downloaded while the car is off." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:48 +#, python-format +msgid "Current Version" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:49 +#, python-format +msgid "Download" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:49 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:106 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:117 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:142 +#, python-format +msgid "CHECK" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:52 +#, python-format +msgid "Install Update" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:52 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:131 +#, python-format +msgid "INSTALL" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:70 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:158 +#, python-format +msgid "Uninstall" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:70 +#, python-format +msgid "UNINSTALL" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:105 +#, python-format +msgid "failed to check for update" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:108 +#, python-format +msgid "update available" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:109 +#, python-format +msgid "DOWNLOAD" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:114 +#, python-format +msgid "up to date, last checked {}" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:116 +#, python-format +msgid "up to date, last checked never" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:158 +#, python-format +msgid "Are you sure you want to uninstall?" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:25 +msgid "" +"Preview the driver facing camera to ensure that driver monitoring has good " +"visibility. (vehicle must be off)" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:26 +msgid "" +"openpilot requires the device to be mounted within 4° left or right and " +"within 5° up or 9° down." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:27 +msgid "Review the rules, features, and limitations of openpilot" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:48 +#, python-format +msgid "Pair Device" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:48 +#, python-format +msgid "PAIR" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:51 +#, python-format +msgid "Reset Calibration" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:51 +#, python-format +msgid "RESET" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:174 +#, python-format +msgid "Reboot" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:186 +#, python-format +msgid "Power Off" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:59 +#, python-format +msgid "Dongle ID" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:59 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:60 +#, python-format +msgid "N/A" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:60 +#, python-format +msgid "Serial" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:62 +#, python-format +msgid "Driver Camera" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:62 +#, python-format +msgid "PREVIEW" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:65 +#, python-format +msgid "Review Training Guide" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:65 +#, python-format +msgid "REVIEW" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:67 +#, python-format +msgid "Regulatory" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:67 +#, python-format +msgid "VIEW" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:68 +#, python-format +msgid "Change Language" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:68 +#, python-format +msgid "CHANGE" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:91 +#, python-format +msgid "Select a language" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:102 +#, python-format +msgid "Disengage to Reset Calibration" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:118 +#, python-format +msgid "Are you sure you want to reset calibration?" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:118 +#, python-format +msgid "Reset" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:132 +#, python-format +msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:132 +#, python-format +msgid "down" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:132 +#, python-format +msgid "up" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#, python-format +msgid "left" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#, python-format +msgid "right" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:145 +#, python-format +msgid "

Steering lag calibration is {}% complete." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:147 +#, python-format +msgid "

Steering lag calibration is complete." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:157 +#, python-format +msgid " Steering torque response calibration is {}% complete." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:159 +#, python-format +msgid " Steering torque response calibration is complete." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:164 +#, python-format +msgid "" +"openpilot is continuously calibrating, resetting is rarely required. " +"Resetting calibration will restart openpilot if the car is powered on." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:171 +#, python-format +msgid "Disengage to Reboot" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:174 +#, python-format +msgid "Are you sure you want to reboot?" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:183 +#, python-format +msgid "Disengage to Power Off" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:186 +#, python-format +msgid "Are you sure you want to power off?" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:62 +msgid "Device" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:63 +msgid "Network" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:64 +msgid "Toggles" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:65 +msgid "Software" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:66 +msgid "Firehose" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:67 +msgid "Developer" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:17 +msgid "" +"Use the openpilot system for adaptive cruise control and lane keep driver " +"assistance. Your attention is required at all times to use this feature." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:20 +msgid "When enabled, pressing the accelerator pedal will disengage openpilot." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:22 +msgid "" +"Standard is recommended. In aggressive mode, openpilot will follow lead cars " +"closer and be more aggressive with the gas and brake. In relaxed mode " +"openpilot will stay further away from lead cars. On supported cars, you can " +"cycle through these personalities with your steering wheel distance button." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:27 +msgid "" +"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)." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:30 +msgid "Enable driver monitoring even when openpilot is not engaged." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +msgid "" +"Upload data from the driver facing camera and help improve the driver " +"monitoring algorithm." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:32 +msgid "Display speed in km/h instead of mph." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +msgid "" +"Record and store microphone audio while driving. The audio will be included " +"in the dashcam video in comma connect." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:46 +#, python-format +msgid "Enable openpilot" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:52 +#, python-format +msgid "Experimental Mode" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:58 +#, python-format +msgid "Disengage on Accelerator Pedal" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:64 +#, python-format +msgid "Enable Lane Departure Warnings" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:70 +#, python-format +msgid "Always-On Driver Monitoring" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:76 +#, python-format +msgid "Record and Upload Driver Camera" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:82 +#, python-format +msgid "Record and Upload Microphone Audio" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:88 +#, python-format +msgid "Use Metric System" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:96 +#, python-format +msgid "Driving Personality" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#, python-format +msgid "Aggressive" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#, python-format +msgid "Standard" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#, python-format +msgid "Relaxed" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:125 +#, python-format +msgid "Changing this setting will restart openpilot if the car is powered on." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:158 +#, python-format +msgid "" +"openpilot defaults to driving in chill mode. Experimental mode enables alpha-" +"level features that aren't ready for chill mode. Experimental features are " +"listed below:

End-to-End Longitudinal Control


Let the driving " +"model control the gas and brakes. openpilot will drive as it thinks a human " +"would, including stopping for red lights and stop signs. Since the driving " +"model decides the speed to drive, the set speed will only act as an upper " +"bound. This is an alpha quality feature; mistakes should be expected." +"

New Driving Visualization


The driving visualization will " +"transition to the road-facing wide-angle camera at low speeds to better show " +"some turns. The Experimental mode logo will also be shown in the top right " +"corner." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:181 +#, python-format +msgid "" +"Experimental mode is currently unavailable on this car since the car's stock " +"ACC is used for longitudinal control." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:183 +#, python-format +msgid "openpilot longitudinal control may come in a future update." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:186 +#, python-format +msgid "" +"An alpha version of openpilot longitudinal control can be tested, along with " +"Experimental mode, on non-release branches." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:189 +#, python-format +msgid "" +"Enable the openpilot longitudinal control (alpha) toggle to allow " +"Experimental mode." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:148 +#, python-format +msgid "MAX" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:177 +#, python-format +msgid "km/h" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:177 +#, python-format +msgid "mph" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:34 +#, python-format +msgid "camera starting" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:51 +#, python-format +msgid "openpilot Unavailable" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:52 +#, python-format +msgid "Waiting to start" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:58 +#, python-format +msgid "TAKE CONTROL IMMEDIATELY" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:59 +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:65 +#, python-format +msgid "System Unresponsive" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:66 +#, python-format +msgid "Reboot Device" +msgstr "" diff --git a/selfdrive/ui/translations/app_de.po b/selfdrive/ui/translations/app_de.po new file mode 100644 index 000000000..34b3d8321 --- /dev/null +++ b/selfdrive/ui/translations/app_de.po @@ -0,0 +1,1225 @@ +# German translations for PACKAGE package. +# Copyright (C) 2025 THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# Automatically generated, 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-10-22 15:30-0700\n" +"PO-Revision-Date: 2025-10-20 16:35-0700\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: de\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:159 +#, python-format +msgid " Steering torque response calibration is complete." +msgstr " Die Lenkmoment-Reaktionskalibrierung ist abgeschlossen." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:157 +#, python-format +msgid " Steering torque response calibration is {}% complete." +msgstr " Die Lenkmoment-Reaktionskalibrierung ist zu {}% abgeschlossen." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:132 +#, python-format +msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." +msgstr " Ihr Gerät ist um {:.1f}° {} und {:.1f}° {} ausgerichtet." + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:43 +msgid "--" +msgstr "--" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "1 year of drive storage" +msgstr "1 Jahr Fahrtdatenspeicherung" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "24/7 LTE connectivity" +msgstr "24/7 LTE‑Verbindung" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:46 +msgid "2G" +msgstr "2G" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:47 +msgid "3G" +msgstr "3G" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:49 +msgid "5G" +msgstr "5G" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:23 +msgid "" +"WARNING: openpilot longitudinal control is in alpha for this car and will " +"disable Automatic Emergency Braking (AEB).

On this car, openpilot " +"defaults to the car's built-in ACC instead of openpilot's longitudinal " +"control. Enable this to switch to openpilot longitudinal control. Enabling " +"Experimental mode is recommended when enabling openpilot longitudinal " +"control alpha. Changing this setting will restart openpilot if the car is " +"powered on." +msgstr "" +"WARNUNG: Die Längsregelung von openpilot befindet sich für dieses " +"Fahrzeug in der Alpha-Phase und deaktiviert das automatische Notbremssystem " +"(AEB).

Auf diesem Fahrzeug verwendet openpilot standardmäßig den " +"integrierten ACC statt der openpilot-Längsregelung. Aktivieren Sie dies, um " +"auf die openpilot-Längsregelung umzuschalten. Das Aktivieren des " +"Experimentalmodus wird empfohlen, wenn Sie die openpilot-Längsregelung " +"(Alpha) aktivieren." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:147 +#, python-format +msgid "

Steering lag calibration is complete." +msgstr "

Kalibrierung der Lenkverzögerung abgeschlossen." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:145 +#, python-format +msgid "

Steering lag calibration is {}% complete." +msgstr "

Kalibrierung der Lenkverzögerung zu {}% abgeschlossen." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:138 +#, python-format +msgid "ACTIVE" +msgstr "AKTIV" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:15 +msgid "" +"ADB (Android Debug Bridge) allows connecting to your device over USB or over " +"the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." +msgstr "" +"ADB (Android Debug Bridge) ermöglicht die Verbindung mit Ihrem Gerät über " +"USB oder über das Netzwerk. Siehe https://docs.comma.ai/how-to/connect-to-" +"comma für weitere Informationen." + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:30 +msgid "ADD" +msgstr "HINZUFÜGEN" + +#: /home/batman/openpilot/system/ui/widgets/network.py:139 +#, python-format +msgid "APN Setting" +msgstr "APN‑Einstellung" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 +#, python-format +msgid "Acknowledge Excessive Actuation" +msgstr "Übermäßige Betätigung bestätigen" + +#: /home/batman/openpilot/system/ui/widgets/network.py:74 +#: /home/batman/openpilot/system/ui/widgets/network.py:95 +#, python-format +msgid "Advanced" +msgstr "Erweitert" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#, python-format +msgid "Aggressive" +msgstr "Aggressiv" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:116 +#, python-format +msgid "Agree" +msgstr "Zustimmen" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:70 +#, python-format +msgid "Always-On Driver Monitoring" +msgstr "Immer aktive Fahrerüberwachung" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:186 +#, python-format +msgid "" +"An alpha version of openpilot longitudinal control can be tested, along with " +"Experimental mode, on non-release branches." +msgstr "" +"Eine Alpha-Version der openpilot-Längsregelung kann zusammen mit dem " +"Experimentalmodus auf Nicht-Release-Zweigen getestet werden." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:186 +#, python-format +msgid "Are you sure you want to power off?" +msgstr "Sind Sie sicher, dass Sie ausschalten möchten?" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:174 +#, python-format +msgid "Are you sure you want to reboot?" +msgstr "Sind Sie sicher, dass Sie neu starten möchten?" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:118 +#, python-format +msgid "Are you sure you want to reset calibration?" +msgstr "Sind Sie sicher, dass Sie die Kalibrierung zurücksetzen möchten?" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:158 +#, python-format +msgid "Are you sure you want to uninstall?" +msgstr "Sind Sie sicher, dass Sie deinstallieren möchten?" + +#: /home/batman/openpilot/system/ui/widgets/network.py:99 +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:147 +#, python-format +msgid "Back" +msgstr "Zurück" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:38 +#, python-format +msgid "Become a comma prime member at connect.comma.ai" +msgstr "Werden Sie comma prime Mitglied auf connect.comma.ai" + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:130 +#, python-format +msgid "Bookmark connect.comma.ai to your home screen to use it like an app" +msgstr "" +"Fügen Sie connect.comma.ai Ihrem Startbildschirm hinzu, um es wie eine App " +"zu verwenden" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:68 +#, python-format +msgid "CHANGE" +msgstr "ÄNDERN" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:49 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:106 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:117 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:142 +#, python-format +msgid "CHECK" +msgstr "PRÜFEN" + +#: /home/batman/openpilot/selfdrive/ui/widgets/exp_mode_button.py:50 +#, python-format +msgid "CHILL MODE ON" +msgstr "CHILL‑MODUS AKTIV" + +#: /home/batman/openpilot/system/ui/widgets/network.py:155 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:138 +#, python-format +msgid "CONNECT" +msgstr "VERBINDUNG" + +#: /home/batman/openpilot/system/ui/widgets/network.py:369 +#, python-format +msgid "CONNECTING..." +msgstr "VERBINDUNG" + +#: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:23 +#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:34 +#: /home/batman/openpilot/system/ui/widgets/keyboard.py:81 +#: /home/batman/openpilot/system/ui/widgets/network.py:318 +#, python-format +msgid "Cancel" +msgstr "Abbrechen" + +#: /home/batman/openpilot/system/ui/widgets/network.py:134 +#, python-format +msgid "Cellular Metered" +msgstr "Getaktete Mobilfunkverbindung" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:68 +#, python-format +msgid "Change Language" +msgstr "Sprache ändern" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:125 +#, python-format +msgid "Changing this setting will restart openpilot if the car is powered on." +msgstr "" +" Durch Ändern dieser Einstellung wird openpilot neu gestartet, wenn das Auto " +"eingeschaltet ist." + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:129 +#, python-format +msgid "Click \"add new device\" and scan the QR code on the right" +msgstr "Klicken Sie auf \"add new device\" und scannen Sie den QR‑Code rechts" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 +#, python-format +msgid "Close" +msgstr "Schließen" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:48 +#, python-format +msgid "Current Version" +msgstr "Aktuelle Version" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:109 +#, python-format +msgid "DOWNLOAD" +msgstr "HERUNTERLADEN" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:115 +#, python-format +msgid "Decline" +msgstr "Ablehnen" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:148 +#, python-format +msgid "Decline, uninstall openpilot" +msgstr "Ablehnen, openpilot deinstallieren" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:67 +msgid "Developer" +msgstr "Entwickler" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:62 +msgid "Device" +msgstr "Gerät" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:58 +#, python-format +msgid "Disengage on Accelerator Pedal" +msgstr "Beim Gaspedal deaktivieren" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:183 +#, python-format +msgid "Disengage to Power Off" +msgstr "Zum Ausschalten deaktivieren" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:171 +#, python-format +msgid "Disengage to Reboot" +msgstr "Zum Neustart deaktivieren" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:102 +#, python-format +msgid "Disengage to Reset Calibration" +msgstr "Zum Zurücksetzen der Kalibrierung deaktivieren" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:32 +msgid "Display speed in km/h instead of mph." +msgstr "Geschwindigkeit in km/h statt mph anzeigen." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:59 +#, python-format +msgid "Dongle ID" +msgstr "Dongle-ID" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:49 +#, python-format +msgid "Download" +msgstr "Herunterladen" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:62 +#, python-format +msgid "Driver Camera" +msgstr "Fahrerkamera" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:96 +#, python-format +msgid "Driving Personality" +msgstr "Fahrstil" + +#: /home/batman/openpilot/system/ui/widgets/network.py:123 +#: /home/batman/openpilot/system/ui/widgets/network.py:139 +#, python-format +msgid "EDIT" +msgstr "BEARBEITEN" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:138 +msgid "ERROR" +msgstr "FEHLER" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:45 +msgid "ETH" +msgstr "ETH" + +#: /home/batman/openpilot/selfdrive/ui/widgets/exp_mode_button.py:50 +#, python-format +msgid "EXPERIMENTAL MODE ON" +msgstr "EXPERIMENTALMODUS AKTIV" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:166 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:228 +#, python-format +msgid "Enable" +msgstr "Aktivieren" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:39 +#, python-format +msgid "Enable ADB" +msgstr "ADB aktivieren" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:64 +#, python-format +msgid "Enable Lane Departure Warnings" +msgstr "Spurverlassenswarnungen aktivieren" + +#: /home/batman/openpilot/system/ui/widgets/network.py:129 +#, python-format +msgid "Enable Roaming" +msgstr "openpilot aktivieren" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:48 +#, python-format +msgid "Enable SSH" +msgstr "SSH aktivieren" + +#: /home/batman/openpilot/system/ui/widgets/network.py:120 +#, python-format +msgid "Enable Tethering" +msgstr "Spurverlassenswarnungen aktivieren" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:30 +msgid "Enable driver monitoring even when openpilot is not engaged." +msgstr "Fahrerüberwachung auch aktivieren, wenn openpilot nicht aktiv ist." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:46 +#, python-format +msgid "Enable openpilot" +msgstr "openpilot aktivieren" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:189 +#, python-format +msgid "" +"Enable the openpilot longitudinal control (alpha) toggle to allow " +"Experimental mode." +msgstr "" +"Den Schalter für die openpilot-Längsregelung (Alpha) aktivieren, um den " +"Experimentalmodus zu erlauben." + +#: /home/batman/openpilot/system/ui/widgets/network.py:204 +#, python-format +msgid "Enter APN" +msgstr "APN eingeben" + +#: /home/batman/openpilot/system/ui/widgets/network.py:241 +#, python-format +msgid "Enter SSID" +msgstr "SSID eingeben" + +#: /home/batman/openpilot/system/ui/widgets/network.py:254 +#, python-format +msgid "Enter new tethering password" +msgstr "Neues Tethering‑Passwort eingeben" + +#: /home/batman/openpilot/system/ui/widgets/network.py:237 +#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#, python-format +msgid "Enter password" +msgstr "Passwort eingeben" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:89 +#, python-format +msgid "Enter your GitHub username" +msgstr "Geben Sie Ihren GitHub‑Benutzernamen ein" + +#: /home/batman/openpilot/system/ui/widgets/list_view.py:115 +#: /home/batman/openpilot/system/ui/widgets/list_view.py:153 +#, python-format +msgid "Error" +msgstr "Fehler" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:52 +#, python-format +msgid "Experimental Mode" +msgstr "Experimentalmodus" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:181 +#, python-format +msgid "" +"Experimental mode is currently unavailable on this car since the car's stock " +"ACC is used for longitudinal control." +msgstr "" +"Der Experimentalmodus ist derzeit auf diesem Fahrzeug nicht verfügbar, da " +"der serienmäßige ACC für die Längsregelung verwendet wird." + +#: /home/batman/openpilot/system/ui/widgets/network.py:373 +#, python-format +msgid "FORGETTING..." +msgstr "WIRD VERGESSEN..." + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:44 +#, python-format +msgid "Finish Setup" +msgstr "Einrichtung abschließen" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:66 +msgid "Firehose" +msgstr "Firehose" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:18 +msgid "Firehose Mode" +msgstr "Firehose‑Modus" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:25 +msgid "" +"For maximum effectiveness, bring your device inside and connect to a good " +"USB-C adapter and Wi-Fi weekly.\n" +"\n" +"Firehose Mode can also work while you're driving if connected to a hotspot " +"or unlimited SIM card.\n" +"\n" +"\n" +"Frequently Asked Questions\n" +"\n" +"Does it matter how or where I drive? Nope, just drive as you normally " +"would.\n" +"\n" +"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a " +"subset of your segments.\n" +"\n" +"What's a good USB-C adapter? Any fast phone or laptop charger should be " +"fine.\n" +"\n" +"Does it matter which software I run? Yes, only upstream openpilot (and " +"particular forks) are able to be used for training." +msgstr "" +"Für maximale Wirksamkeit bringen Sie Ihr Gerät regelmäßig ins Haus und " +"verbinden es wöchentlich mit einem guten USB‑C‑Adapter und WLAN.\n" +"\n" +"Der Firehose‑Modus kann auch während der Fahrt funktionieren, wenn eine " +"Verbindung zu einem Hotspot oder einer unbegrenzten SIM besteht.\n" +"\n" +"\n" +"Häufig gestellte Fragen\n" +"\n" +"Spielt es eine Rolle, wie oder wo ich fahre? Nein, fahren Sie einfach wie " +"gewöhnlich.\n" +"\n" +"Werden alle meine Segmente im Firehose‑Modus abgeholt? Nein, wir ziehen " +"selektiv eine Teilmenge Ihrer Segmente.\n" +"\n" +"Was ist ein guter USB‑C‑Adapter? Jeder schnelle Telefon‑ oder Laptoplader " +"sollte ausreichen.\n" +"\n" +"Spielt es eine Rolle, welche Software ich verwende? Ja, nur " +"Upstream‑openpilot (und bestimmte Forks) können für das Training verwendet " +"werden." + +#: /home/batman/openpilot/system/ui/widgets/network.py:318 +#: /home/batman/openpilot/system/ui/widgets/network.py:451 +#, python-format +msgid "Forget" +msgstr "Vergessen" + +#: /home/batman/openpilot/system/ui/widgets/network.py:319 +#, python-format +msgid "Forget Wi-Fi Network \"{}\"?" +msgstr "WLAN‑Netz „{}“ vergessen?" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:125 +msgid "GOOD" +msgstr "GUT" + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:128 +#, python-format +msgid "Go to https://connect.comma.ai on your phone" +msgstr "Gehen Sie auf Ihrem Telefon zu https://connect.comma.ai" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:129 +msgid "HIGH" +msgstr "HOCH" + +#: /home/batman/openpilot/system/ui/widgets/network.py:155 +#, python-format +msgid "Hidden Network" +msgstr "Netzwerk" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:140 +#, python-format +msgid "INACTIVE: connect to an unmetered network" +msgstr "INAKTIV: Mit einem unlimitierten Netzwerk verbinden" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:52 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:131 +#, python-format +msgid "INSTALL" +msgstr "INSTALLIEREN" + +#: /home/batman/openpilot/system/ui/widgets/network.py:150 +#, python-format +msgid "IP Address" +msgstr "IP‑Adresse" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:52 +#, python-format +msgid "Install Update" +msgstr "Update installieren" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:56 +#, python-format +msgid "Joystick Debug Mode" +msgstr "Joystick‑Debugmodus" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:29 +msgid "LOADING" +msgstr "LADEN" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:48 +msgid "LTE" +msgstr "LTE" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:64 +#, python-format +msgid "Longitudinal Maneuver Mode" +msgstr "Längsmanövermodus" + +#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:148 +#, python-format +msgid "MAX" +msgstr "MAX" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:75 +#, python-format +msgid "" +"Maximize your training data uploads to improve openpilot's driving models." +msgstr "" +"Maximieren Sie Ihre Trainingsdaten‑Uploads, um die Fahrmodelle von openpilot " +"zu verbessern." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:59 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:60 +#, python-format +msgid "N/A" +msgstr "k. A." + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:142 +msgid "NO" +msgstr "KEIN" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:63 +msgid "Network" +msgstr "Netzwerk" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:114 +#, python-format +msgid "No SSH keys found" +msgstr "Keine SSH‑Schlüssel gefunden" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:126 +#, python-format +msgid "No SSH keys found for user '{}'" +msgstr "Keine SSH‑Schlüssel für Benutzer '{username}' gefunden" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:320 +#, python-format +msgid "No release notes available." +msgstr "Keine Versionshinweise verfügbar." + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:134 +msgid "OFFLINE" +msgstr "OFFLINE" + +#: /home/batman/openpilot/system/ui/widgets/html_render.py:247 +#: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:93 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:127 +#, python-format +msgid "OK" +msgstr "OK" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:144 +msgid "ONLINE" +msgstr "ONLINE" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:20 +#, python-format +msgid "Open" +msgstr "Öffnen" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:48 +#, python-format +msgid "PAIR" +msgstr "KOPPELN" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:142 +msgid "PANDA" +msgstr "PANDA" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:62 +#, python-format +msgid "PREVIEW" +msgstr "VORSCHAU" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:44 +#, python-format +msgid "PRIME FEATURES:" +msgstr "PRIME‑FUNKTIONEN:" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:48 +#, python-format +msgid "Pair Device" +msgstr "Gerät koppeln" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:19 +#, python-format +msgid "Pair device" +msgstr "Gerät koppeln" + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:103 +#, python-format +msgid "Pair your device to your comma account" +msgstr "Koppeln Sie Ihr Gerät mit Ihrem comma‑Konto" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:48 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:24 +#, python-format +msgid "" +"Pair your device with comma connect (connect.comma.ai) and claim your comma " +"prime offer." +msgstr "" +"Koppeln Sie Ihr Gerät mit comma connect (connect.comma.ai) und lösen Sie Ihr " +"comma‑prime‑Angebot ein." + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:91 +#, python-format +msgid "Please connect to Wi-Fi to complete initial pairing" +msgstr "Bitte mit WLAN verbinden, um das erste Koppeln abzuschließen" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:186 +#, python-format +msgid "Power Off" +msgstr "Ausschalten" + +#: /home/batman/openpilot/system/ui/widgets/network.py:144 +#, python-format +msgid "Prevent large data uploads when on a metered Wi-Fi connection" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:135 +#, python-format +msgid "Prevent large data uploads when on a metered cellular connection" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:25 +msgid "" +"Preview the driver facing camera to ensure that driver monitoring has good " +"visibility. (vehicle must be off)" +msgstr "" +"Vorschau der Fahrer‑Kamera, um sicherzustellen, dass die Fahrerüberwachung " +"gute Sicht hat. (Fahrzeug muss ausgeschaltet sein)" + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:161 +#, python-format +msgid "QR Code Error" +msgstr "QR‑Code‑Fehler" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:31 +msgid "REMOVE" +msgstr "ENTFERNEN" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:51 +#, python-format +msgid "RESET" +msgstr "ZURÜCKSETZEN" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:65 +#, python-format +msgid "REVIEW" +msgstr "ANSEHEN" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:174 +#, python-format +msgid "Reboot" +msgstr "Neustart" + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:66 +#, python-format +msgid "Reboot Device" +msgstr "Gerät neu starten" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 +#, python-format +msgid "Reboot and Update" +msgstr "Neustarten und aktualisieren" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:27 +msgid "" +"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)." +msgstr "" +"Erhalten Sie Warnungen, um zurück in die Spur zu lenken, wenn Ihr Fahrzeug " +"ohne Blinker über eine erkannte Spurlinie driftet und über 31 mph (50 km/h) " +"fährt." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:76 +#, python-format +msgid "Record and Upload Driver Camera" +msgstr "Fahrerkamera aufzeichnen und hochladen" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:82 +#, python-format +msgid "Record and Upload Microphone Audio" +msgstr "Mikrofonton aufzeichnen und hochladen" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +msgid "" +"Record and store microphone audio while driving. The audio will be included " +"in the dashcam video in comma connect." +msgstr "" +"Mikrofonton während der Fahrt aufzeichnen und speichern. Die Audiospur wird " +"im Dashcam‑Video in comma connect enthalten sein." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:67 +#, python-format +msgid "Regulatory" +msgstr "Vorschriften" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#, python-format +msgid "Relaxed" +msgstr "Entspannt" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "Remote access" +msgstr "Fernzugriff" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "Remote snapshots" +msgstr "Remote‑Schnappschüsse" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:123 +#, python-format +msgid "Request timed out" +msgstr "Zeitüberschreitung bei der Anfrage" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:118 +#, python-format +msgid "Reset" +msgstr "Zurücksetzen" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:51 +#, python-format +msgid "Reset Calibration" +msgstr "Kalibrierung zurücksetzen" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:65 +#, python-format +msgid "Review Training Guide" +msgstr "Trainingsanleitung ansehen" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:27 +msgid "Review the rules, features, and limitations of openpilot" +msgstr "" +"Überprüfen Sie die Regeln, Funktionen und Einschränkungen von openpilot" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:53 +#, python-format +msgid "SSH Keys" +msgstr "SSH‑Schlüssel" + +#: /home/batman/openpilot/system/ui/widgets/network.py:310 +#, python-format +msgid "Scanning Wi-Fi networks..." +msgstr "WLAN‑Netzwerke werden gesucht..." + +#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:35 +#, python-format +msgid "Select" +msgstr "Auswählen" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:91 +#, python-format +msgid "Select a language" +msgstr "Sprache auswählen" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:60 +#, python-format +msgid "Serial" +msgstr "Seriennummer" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 +#, python-format +msgid "Snooze Update" +msgstr "Update verschieben" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:65 +msgid "Software" +msgstr "Software" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#, python-format +msgid "Standard" +msgstr "Standard" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:22 +msgid "" +"Standard is recommended. In aggressive mode, openpilot will follow lead cars " +"closer and be more aggressive with the gas and brake. In relaxed mode " +"openpilot will stay further away from lead cars. On supported cars, you can " +"cycle through these personalities with your steering wheel distance button." +msgstr "" +"Standard wird empfohlen. Im aggressiven Modus folgt openpilot " +"vorausfahrenden Fahrzeugen näher und ist beim Gasgeben und Bremsen " +"aggressiver. Im entspannten Modus bleibt openpilot weiter entfernt. Bei " +"unterstützten Fahrzeugen können Sie mit der Abstandstaste am Lenkrad " +"zwischen diesen Profilen wechseln." + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:59 +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:65 +#, python-format +msgid "System Unresponsive" +msgstr "System reagiert nicht" + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:58 +#, python-format +msgid "TAKE CONTROL IMMEDIATELY" +msgstr "SOFORT DIE KONTROLLE ÜBERNEHMEN" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:129 +msgid "TEMP" +msgstr "TEMP" + +#: /home/batman/openpilot/system/ui/widgets/network.py:124 +#, python-format +msgid "Tethering Password" +msgstr "Tethering‑Passwort" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:64 +msgid "Toggles" +msgstr "Schalter" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:70 +#, python-format +msgid "UNINSTALL" +msgstr "DEINSTALLIEREN" + +#: /home/batman/openpilot/selfdrive/ui/layouts/home.py:155 +#, python-format +msgid "UPDATE" +msgstr "UPDATE" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:70 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:158 +#, python-format +msgid "Uninstall" +msgstr "Deinstallieren" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:117 +msgid "Unknown" +msgstr "Unbekannt" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:47 +#, python-format +msgid "Updates are only downloaded while the car is off." +msgstr "Updates werden nur heruntergeladen, wenn das Auto aus ist." + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:33 +#, python-format +msgid "Upgrade Now" +msgstr "Jetzt abonnieren" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +msgid "" +"Upload data from the driver facing camera and help improve the driver " +"monitoring algorithm." +msgstr "" +"Daten von der Fahrer‑Kamera hochladen und den Fahrerüberwachungs‑Algorithmus " +"verbessern." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:88 +#, python-format +msgid "Use Metric System" +msgstr "Metersystem verwenden" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:17 +msgid "" +"Use the openpilot system for adaptive cruise control and lane keep driver " +"assistance. Your attention is required at all times to use this feature." +msgstr "" +"Verwenden Sie openpilot für adaptive Geschwindigkeitsregelung und " +"Spurhalteassistenz. Ihre Aufmerksamkeit ist jederzeit erforderlich, um diese " +"Funktion zu nutzen." + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:144 +msgid "VEHICLE" +msgstr "FAHRZEUG" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:67 +#, python-format +msgid "VIEW" +msgstr "ANSEHEN" + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:52 +#, python-format +msgid "Waiting to start" +msgstr "Warten auf Start" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:19 +msgid "" +"Warning: This grants SSH access to all public keys in your GitHub settings. " +"Never enter a GitHub username other than your own. A comma employee will " +"NEVER ask you to add their GitHub username." +msgstr "" +"Warnung: Dies gewährt SSH‑Zugriff auf alle öffentlichen Schlüssel in Ihren " +"GitHub‑Einstellungen. Geben Sie niemals einen anderen GitHub‑Benutzernamen " +"als Ihren eigenen ein. Ein comma‑Mitarbeiter wird Sie NIEMALS bitten, seinen " +"GitHub‑Benutzernamen hinzuzufügen." + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:111 +#, python-format +msgid "Welcome to openpilot" +msgstr "Willkommen bei openpilot" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:20 +msgid "When enabled, pressing the accelerator pedal will disengage openpilot." +msgstr "Wenn aktiviert, deaktiviert das Drücken des Gaspedals openpilot." + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:44 +msgid "Wi-Fi" +msgstr "WLAN" + +#: /home/batman/openpilot/system/ui/widgets/network.py:144 +#, python-format +msgid "Wi-Fi Network Metered" +msgstr "Getaktetes WLAN‑Netzwerk" + +#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#, python-format +msgid "Wrong password" +msgstr "Falsches Passwort" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:145 +#, python-format +msgid "You must accept the Terms and Conditions in order to use openpilot." +msgstr "" +"Sie müssen die Nutzungsbedingungen akzeptieren, um openpilot zu verwenden." + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:112 +#, python-format +msgid "" +"You must accept the Terms and Conditions to use openpilot. Read the latest " +"terms at https://comma.ai/terms before continuing." +msgstr "" +"Sie müssen die Nutzungsbedingungen akzeptieren, um openpilot zu verwenden. " +"Lesen Sie die aktuellen Bedingungen unter https://comma.ai/terms, bevor Sie " +"fortfahren." + +#: /home/batman/openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:34 +#, python-format +msgid "camera starting" +msgstr "Kamera startet" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:63 +#, python-format +msgid "comma prime" +msgstr "comma prime" + +#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#, python-format +msgid "default" +msgstr "Standard" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:132 +#, python-format +msgid "down" +msgstr "unten" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:105 +#, python-format +msgid "failed to check for update" +msgstr "Überprüfung auf Updates fehlgeschlagen" + +#: /home/batman/openpilot/system/ui/widgets/network.py:237 +#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#, python-format +msgid "for \"{}\"" +msgstr "für „{}“" + +#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:177 +#, python-format +msgid "km/h" +msgstr "km/h" + +#: /home/batman/openpilot/system/ui/widgets/network.py:204 +#, python-format +msgid "leave blank for automatic configuration" +msgstr "für automatische Konfiguration leer lassen" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#, python-format +msgid "left" +msgstr "links" + +#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#, python-format +msgid "metered" +msgstr "getaktet" + +#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:177 +#, python-format +msgid "mph" +msgstr "mph" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:19 +#, python-format +msgid "never" +msgstr "nie" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:30 +#, python-format +msgid "now" +msgstr "jetzt" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:71 +#, python-format +msgid "openpilot Longitudinal Control (Alpha)" +msgstr "openpilot Längsregelung (Alpha)" + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:51 +#, python-format +msgid "openpilot Unavailable" +msgstr "openpilot nicht verfügbar" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:158 +#, python-format +msgid "" +"openpilot defaults to driving in chill mode. Experimental mode enables alpha-" +"level features that aren't ready for chill mode. Experimental features are " +"listed below:

End-to-End Longitudinal Control


Let the driving " +"model control the gas and brakes. openpilot will drive as it thinks a human " +"would, including stopping for red lights and stop signs. Since the driving " +"model decides the speed to drive, the set speed will only act as an upper " +"bound. This is an alpha quality feature; mistakes should be expected." +"

New Driving Visualization


The driving visualization will " +"transition to the road-facing wide-angle camera at low speeds to better show " +"some turns. The Experimental mode logo will also be shown in the top right " +"corner." +msgstr "" +"openpilot fährt standardmäßig im Chill‑Modus. Der Experimentalmodus " +"aktiviert Funktionen im Alpha‑Status, die für den Chill‑Modus noch nicht " +"bereit sind. Die experimentellen Funktionen sind unten aufgeführt:" +"

End-to‑End‑Längsregelung


Das Fahrmodell steuert Gas und " +"Bremse. openpilot fährt so, wie es einen Menschen einschätzt, einschließlich " +"Anhalten an roten Ampeln und Stoppschildern. Da das Modell die " +"Geschwindigkeit bestimmt, dient die eingestellte Geschwindigkeit nur als " +"Obergrenze. Dies ist eine Alpha‑Funktion; Fehler sind zu erwarten." +"

Neue Fahrvisualisierung


Die Visualisierung wechselt bei " +"niedriger Geschwindigkeit auf die nach vorn gerichtete Weitwinkelkamera, um " +"manche Kurven besser zu zeigen. Das Experimentalmodus‑Logo wird außerdem " +"oben rechts angezeigt." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:164 +#, python-format +msgid "" +"openpilot is continuously calibrating, resetting is rarely required. " +"Resetting calibration will restart openpilot if the car is powered on." +msgstr "" +" Durch Ändern dieser Einstellung wird openpilot neu gestartet, wenn das Auto " +"eingeschaltet ist." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:20 +msgid "" +"openpilot learns to drive by watching humans, like you, drive.\n" +"\n" +"Firehose Mode allows you to maximize your training data uploads to improve " +"openpilot's driving models. More data means bigger models, which means " +"better Experimental Mode." +msgstr "" +"openpilot lernt das Fahren, indem es Menschen wie Sie beobachtet.\n" +"\n" +"Der Firehose‑Modus ermöglicht es Ihnen, Ihre Trainingsdaten‑Uploads zu " +"maximieren, um die Fahrmodelle von openpilot zu verbessern. Mehr Daten " +"bedeuten größere Modelle – und damit einen besseren Experimentalmodus." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:183 +#, python-format +msgid "openpilot longitudinal control may come in a future update." +msgstr "Die openpilot‑Längsregelung könnte in einem zukünftigen Update kommen." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:26 +msgid "" +"openpilot requires the device to be mounted within 4° left or right and " +"within 5° up or 9° down." +msgstr "" +"openpilot erfordert, dass das Gerät innerhalb von 4° nach links oder rechts " +"und innerhalb von 5° nach oben oder 9° nach unten montiert ist." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#, python-format +msgid "right" +msgstr "rechts" + +#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#, python-format +msgid "unmetered" +msgstr "unbegrenzt" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:132 +#, python-format +msgid "up" +msgstr "oben" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:116 +#, python-format +msgid "up to date, last checked never" +msgstr "Aktuell, zuletzt geprüft: nie" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:114 +#, python-format +msgid "up to date, last checked {}" +msgstr "Aktuell, zuletzt geprüft: {}" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:108 +#, python-format +msgid "update available" +msgstr "Update verfügbar" + +#: /home/batman/openpilot/selfdrive/ui/layouts/home.py:169 +#, python-format +msgid "{} ALERT" +msgid_plural "{} ALERTS" +msgstr[0] "{} WARNUNG" +msgstr[1] "{} WARNUNGEN" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:39 +#, python-format +msgid "{} day ago" +msgid_plural "{} days ago" +msgstr[0] "vor {} Tag" +msgstr[1] "vor {} Tagen" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:36 +#, python-format +msgid "{} hour ago" +msgid_plural "{} hours ago" +msgstr[0] "vor {} Stunde" +msgstr[1] "vor {} Stunden" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:33 +#, python-format +msgid "{} minute ago" +msgid_plural "{} minutes ago" +msgstr[0] "vor {} Minute" +msgstr[1] "vor {} Minuten" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:111 +#, python-format +msgid "{} segment of your driving is in the training dataset so far." +msgid_plural "{} segments of your driving is in the training dataset so far." +msgstr[0] "{} Segment Ihrer Fahrten ist bisher im Trainingsdatensatz." +msgstr[1] "{} Segmente Ihrer Fahrten sind bisher im Trainingsdatensatz." + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:62 +#, python-format +msgid "✓ SUBSCRIBED" +msgstr "✓ ABONNIERT" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:22 +#, python-format +msgid "🔥 Firehose Mode 🔥" +msgstr "🔥 Firehose‑Modus 🔥" diff --git a/selfdrive/ui/translations/app_en.po b/selfdrive/ui/translations/app_en.po new file mode 100644 index 000000000..55554e9d9 --- /dev/null +++ b/selfdrive/ui/translations/app_en.po @@ -0,0 +1,1211 @@ +# English translations for PACKAGE package. +# Copyright (C) 2025 THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# Automatically generated, 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-10-22 15:30-0700\n" +"PO-Revision-Date: 2025-10-21 18:18-0700\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: en\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:159 +#, python-format +msgid " Steering torque response calibration is complete." +msgstr " Steering torque response calibration is complete." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:157 +#, python-format +msgid " Steering torque response calibration is {}% complete." +msgstr " Steering torque response calibration is {}% complete." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:132 +#, python-format +msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." +msgstr " Your device is pointed {:.1f}° {} and {:.1f}° {}." + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:43 +msgid "--" +msgstr "--" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "1 year of drive storage" +msgstr "1 year of drive storage" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "24/7 LTE connectivity" +msgstr "24/7 LTE connectivity" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:46 +msgid "2G" +msgstr "2G" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:47 +msgid "3G" +msgstr "3G" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:49 +msgid "5G" +msgstr "5G" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:23 +msgid "" +"WARNING: openpilot longitudinal control is in alpha for this car and will " +"disable Automatic Emergency Braking (AEB).

On this car, openpilot " +"defaults to the car's built-in ACC instead of openpilot's longitudinal " +"control. Enable this to switch to openpilot longitudinal control. Enabling " +"Experimental mode is recommended when enabling openpilot longitudinal " +"control alpha. Changing this setting will restart openpilot if the car is " +"powered on." +msgstr "" +"WARNING: openpilot longitudinal control is in alpha for this car and will " +"disable Automatic Emergency Braking (AEB).

On this car, openpilot " +"defaults to the car's built-in ACC instead of openpilot's longitudinal " +"control. Enable this to switch to openpilot longitudinal control. Enabling " +"Experimental mode is recommended when enabling openpilot longitudinal " +"control alpha. Changing this setting will restart openpilot if the car is " +"powered on." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:147 +#, python-format +msgid "

Steering lag calibration is complete." +msgstr "

Steering lag calibration is complete." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:145 +#, python-format +msgid "

Steering lag calibration is {}% complete." +msgstr "

Steering lag calibration is {}% complete." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:138 +#, python-format +msgid "ACTIVE" +msgstr "ACTIVE" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:15 +msgid "" +"ADB (Android Debug Bridge) allows connecting to your device over USB or over " +"the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." +msgstr "" +"ADB (Android Debug Bridge) allows connecting to your device over USB or over " +"the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:30 +msgid "ADD" +msgstr "ADD" + +#: /home/batman/openpilot/system/ui/widgets/network.py:139 +#, python-format +msgid "APN Setting" +msgstr "APN Setting" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 +#, python-format +msgid "Acknowledge Excessive Actuation" +msgstr "Acknowledge Excessive Actuation" + +#: /home/batman/openpilot/system/ui/widgets/network.py:74 +#: /home/batman/openpilot/system/ui/widgets/network.py:95 +#, python-format +msgid "Advanced" +msgstr "Advanced" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#, python-format +msgid "Aggressive" +msgstr "Aggressive" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:116 +#, python-format +msgid "Agree" +msgstr "Agree" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:70 +#, python-format +msgid "Always-On Driver Monitoring" +msgstr "Always-On Driver Monitoring" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:186 +#, python-format +msgid "" +"An alpha version of openpilot longitudinal control can be tested, along with " +"Experimental mode, on non-release branches." +msgstr "" +"An alpha version of openpilot longitudinal control can be tested, along with " +"Experimental mode, on non-release branches." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:186 +#, python-format +msgid "Are you sure you want to power off?" +msgstr "Are you sure you want to power off?" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:174 +#, python-format +msgid "Are you sure you want to reboot?" +msgstr "Are you sure you want to reboot?" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:118 +#, python-format +msgid "Are you sure you want to reset calibration?" +msgstr "Are you sure you want to reset calibration?" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:158 +#, python-format +msgid "Are you sure you want to uninstall?" +msgstr "Are you sure you want to uninstall?" + +#: /home/batman/openpilot/system/ui/widgets/network.py:99 +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:147 +#, python-format +msgid "Back" +msgstr "Back" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:38 +#, python-format +msgid "Become a comma prime member at connect.comma.ai" +msgstr "Become a comma prime member at connect.comma.ai" + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:130 +#, python-format +msgid "Bookmark connect.comma.ai to your home screen to use it like an app" +msgstr "Bookmark connect.comma.ai to your home screen to use it like an app" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:68 +#, python-format +msgid "CHANGE" +msgstr "CHANGE" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:49 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:106 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:117 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:142 +#, python-format +msgid "CHECK" +msgstr "CHECK" + +#: /home/batman/openpilot/selfdrive/ui/widgets/exp_mode_button.py:50 +#, python-format +msgid "CHILL MODE ON" +msgstr "CHILL MODE ON" + +#: /home/batman/openpilot/system/ui/widgets/network.py:155 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:138 +#, python-format +msgid "CONNECT" +msgstr "CONNECT" + +#: /home/batman/openpilot/system/ui/widgets/network.py:369 +#, python-format +msgid "CONNECTING..." +msgstr "CONNECTING..." + +#: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:23 +#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:34 +#: /home/batman/openpilot/system/ui/widgets/keyboard.py:81 +#: /home/batman/openpilot/system/ui/widgets/network.py:318 +#, python-format +msgid "Cancel" +msgstr "Cancel" + +#: /home/batman/openpilot/system/ui/widgets/network.py:134 +#, python-format +msgid "Cellular Metered" +msgstr "Cellular Metered" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:68 +#, python-format +msgid "Change Language" +msgstr "Change Language" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:125 +#, python-format +msgid "Changing this setting will restart openpilot if the car is powered on." +msgstr "Changing this setting will restart openpilot if the car is powered on." + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:129 +#, python-format +msgid "Click \"add new device\" and scan the QR code on the right" +msgstr "Click \"add new device\" and scan the QR code on the right" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 +#, python-format +msgid "Close" +msgstr "Close" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:48 +#, python-format +msgid "Current Version" +msgstr "Current Version" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:109 +#, python-format +msgid "DOWNLOAD" +msgstr "DOWNLOAD" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:115 +#, python-format +msgid "Decline" +msgstr "Decline" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:148 +#, python-format +msgid "Decline, uninstall openpilot" +msgstr "Decline, uninstall openpilot" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:67 +msgid "Developer" +msgstr "Developer" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:62 +msgid "Device" +msgstr "Device" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:58 +#, python-format +msgid "Disengage on Accelerator Pedal" +msgstr "Disengage on Accelerator Pedal" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:183 +#, python-format +msgid "Disengage to Power Off" +msgstr "Disengage to Power Off" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:171 +#, python-format +msgid "Disengage to Reboot" +msgstr "Disengage to Reboot" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:102 +#, python-format +msgid "Disengage to Reset Calibration" +msgstr "Disengage to Reset Calibration" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:32 +msgid "Display speed in km/h instead of mph." +msgstr "Display speed in km/h instead of mph." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:59 +#, python-format +msgid "Dongle ID" +msgstr "Dongle ID" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:49 +#, python-format +msgid "Download" +msgstr "Download" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:62 +#, python-format +msgid "Driver Camera" +msgstr "Driver Camera" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:96 +#, python-format +msgid "Driving Personality" +msgstr "Driving Personality" + +#: /home/batman/openpilot/system/ui/widgets/network.py:123 +#: /home/batman/openpilot/system/ui/widgets/network.py:139 +#, python-format +msgid "EDIT" +msgstr "EDIT" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:138 +msgid "ERROR" +msgstr "ERROR" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:45 +msgid "ETH" +msgstr "ETH" + +#: /home/batman/openpilot/selfdrive/ui/widgets/exp_mode_button.py:50 +#, python-format +msgid "EXPERIMENTAL MODE ON" +msgstr "EXPERIMENTAL MODE ON" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:166 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:228 +#, python-format +msgid "Enable" +msgstr "Enable" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:39 +#, python-format +msgid "Enable ADB" +msgstr "Enable ADB" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:64 +#, python-format +msgid "Enable Lane Departure Warnings" +msgstr "Enable Lane Departure Warnings" + +#: /home/batman/openpilot/system/ui/widgets/network.py:129 +#, python-format +msgid "Enable Roaming" +msgstr "Enable Roaming" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:48 +#, python-format +msgid "Enable SSH" +msgstr "Enable SSH" + +#: /home/batman/openpilot/system/ui/widgets/network.py:120 +#, python-format +msgid "Enable Tethering" +msgstr "Enable Tethering" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:30 +msgid "Enable driver monitoring even when openpilot is not engaged." +msgstr "Enable driver monitoring even when openpilot is not engaged." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:46 +#, python-format +msgid "Enable openpilot" +msgstr "Enable openpilot" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:189 +#, python-format +msgid "" +"Enable the openpilot longitudinal control (alpha) toggle to allow " +"Experimental mode." +msgstr "" +"Enable the openpilot longitudinal control (alpha) toggle to allow " +"Experimental mode." + +#: /home/batman/openpilot/system/ui/widgets/network.py:204 +#, python-format +msgid "Enter APN" +msgstr "Enter APN" + +#: /home/batman/openpilot/system/ui/widgets/network.py:241 +#, python-format +msgid "Enter SSID" +msgstr "Enter SSID" + +#: /home/batman/openpilot/system/ui/widgets/network.py:254 +#, python-format +msgid "Enter new tethering password" +msgstr "Enter new tethering password" + +#: /home/batman/openpilot/system/ui/widgets/network.py:237 +#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#, python-format +msgid "Enter password" +msgstr "Enter password" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:89 +#, python-format +msgid "Enter your GitHub username" +msgstr "Enter your GitHub username" + +#: /home/batman/openpilot/system/ui/widgets/list_view.py:115 +#: /home/batman/openpilot/system/ui/widgets/list_view.py:153 +#, python-format +msgid "Error" +msgstr "Error" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:52 +#, python-format +msgid "Experimental Mode" +msgstr "Experimental Mode" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:181 +#, python-format +msgid "" +"Experimental mode is currently unavailable on this car since the car's stock " +"ACC is used for longitudinal control." +msgstr "" +"Experimental mode is currently unavailable on this car since the car's stock " +"ACC is used for longitudinal control." + +#: /home/batman/openpilot/system/ui/widgets/network.py:373 +#, python-format +msgid "FORGETTING..." +msgstr "FORGETTING..." + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:44 +#, python-format +msgid "Finish Setup" +msgstr "Finish Setup" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:66 +msgid "Firehose" +msgstr "Firehose" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:18 +msgid "Firehose Mode" +msgstr "Firehose Mode" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:25 +msgid "" +"For maximum effectiveness, bring your device inside and connect to a good " +"USB-C adapter and Wi-Fi weekly.\n" +"\n" +"Firehose Mode can also work while you're driving if connected to a hotspot " +"or unlimited SIM card.\n" +"\n" +"\n" +"Frequently Asked Questions\n" +"\n" +"Does it matter how or where I drive? Nope, just drive as you normally " +"would.\n" +"\n" +"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a " +"subset of your segments.\n" +"\n" +"What's a good USB-C adapter? Any fast phone or laptop charger should be " +"fine.\n" +"\n" +"Does it matter which software I run? Yes, only upstream openpilot (and " +"particular forks) are able to be used for training." +msgstr "" +"For maximum effectiveness, bring your device inside and connect to a good " +"USB-C adapter and Wi-Fi weekly.\n" +"\n" +"Firehose Mode can also work while you're driving if connected to a hotspot " +"or unlimited SIM card.\n" +"\n" +"\n" +"Frequently Asked Questions\n" +"\n" +"Does it matter how or where I drive? Nope, just drive as you normally " +"would.\n" +"\n" +"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a " +"subset of your segments.\n" +"\n" +"What's a good USB-C adapter? Any fast phone or laptop charger should be " +"fine.\n" +"\n" +"Does it matter which software I run? Yes, only upstream openpilot (and " +"particular forks) are able to be used for training." + +#: /home/batman/openpilot/system/ui/widgets/network.py:318 +#: /home/batman/openpilot/system/ui/widgets/network.py:451 +#, python-format +msgid "Forget" +msgstr "Forget" + +#: /home/batman/openpilot/system/ui/widgets/network.py:319 +#, python-format +msgid "Forget Wi-Fi Network \"{}\"?" +msgstr "Forget Wi-Fi Network \"{}\"?" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:125 +msgid "GOOD" +msgstr "GOOD" + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:128 +#, python-format +msgid "Go to https://connect.comma.ai on your phone" +msgstr "Go to https://connect.comma.ai on your phone" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:129 +msgid "HIGH" +msgstr "HIGH" + +#: /home/batman/openpilot/system/ui/widgets/network.py:155 +#, python-format +msgid "Hidden Network" +msgstr "Hidden Network" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:140 +#, python-format +msgid "INACTIVE: connect to an unmetered network" +msgstr "INACTIVE: connect to an unmetered network" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:52 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:131 +#, python-format +msgid "INSTALL" +msgstr "INSTALL" + +#: /home/batman/openpilot/system/ui/widgets/network.py:150 +#, python-format +msgid "IP Address" +msgstr "IP Address" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:52 +#, python-format +msgid "Install Update" +msgstr "Install Update" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:56 +#, python-format +msgid "Joystick Debug Mode" +msgstr "Joystick Debug Mode" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:29 +msgid "LOADING" +msgstr "LOADING" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:48 +msgid "LTE" +msgstr "LTE" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:64 +#, python-format +msgid "Longitudinal Maneuver Mode" +msgstr "Longitudinal Maneuver Mode" + +#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:148 +#, python-format +msgid "MAX" +msgstr "MAX" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:75 +#, python-format +msgid "" +"Maximize your training data uploads to improve openpilot's driving models." +msgstr "" +"Maximize your training data uploads to improve openpilot's driving models." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:59 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:60 +#, python-format +msgid "N/A" +msgstr "N/A" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:142 +msgid "NO" +msgstr "NO" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:63 +msgid "Network" +msgstr "Network" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:114 +#, python-format +msgid "No SSH keys found" +msgstr "No SSH keys found" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:126 +#, python-format +msgid "No SSH keys found for user '{}'" +msgstr "No SSH keys found for user '{}'" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:320 +#, python-format +msgid "No release notes available." +msgstr "No release notes available." + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:134 +msgid "OFFLINE" +msgstr "OFFLINE" + +#: /home/batman/openpilot/system/ui/widgets/html_render.py:247 +#: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:93 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:127 +#, python-format +msgid "OK" +msgstr "OK" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:144 +msgid "ONLINE" +msgstr "ONLINE" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:20 +#, python-format +msgid "Open" +msgstr "Open" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:48 +#, python-format +msgid "PAIR" +msgstr "PAIR" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:142 +msgid "PANDA" +msgstr "PANDA" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:62 +#, python-format +msgid "PREVIEW" +msgstr "PREVIEW" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:44 +#, python-format +msgid "PRIME FEATURES:" +msgstr "PRIME FEATURES:" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:48 +#, python-format +msgid "Pair Device" +msgstr "Pair Device" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:19 +#, python-format +msgid "Pair device" +msgstr "Pair device" + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:103 +#, python-format +msgid "Pair your device to your comma account" +msgstr "Pair your device to your comma account" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:48 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:24 +#, python-format +msgid "" +"Pair your device with comma connect (connect.comma.ai) and claim your comma " +"prime offer." +msgstr "" +"Pair your device with comma connect (connect.comma.ai) and claim your comma " +"prime offer." + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:91 +#, python-format +msgid "Please connect to Wi-Fi to complete initial pairing" +msgstr "Please connect to Wi-Fi to complete initial pairing" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:186 +#, python-format +msgid "Power Off" +msgstr "Power Off" + +#: /home/batman/openpilot/system/ui/widgets/network.py:144 +#, python-format +msgid "Prevent large data uploads when on a metered Wi-Fi connection" +msgstr "Prevent large data uploads when on a metered Wi-Fi connection" + +#: /home/batman/openpilot/system/ui/widgets/network.py:135 +#, python-format +msgid "Prevent large data uploads when on a metered cellular connection" +msgstr "Prevent large data uploads when on a metered cellular connection" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:25 +msgid "" +"Preview the driver facing camera to ensure that driver monitoring has good " +"visibility. (vehicle must be off)" +msgstr "" +"Preview the driver facing camera to ensure that driver monitoring has good " +"visibility. (vehicle must be off)" + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:161 +#, python-format +msgid "QR Code Error" +msgstr "QR Code Error" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:31 +msgid "REMOVE" +msgstr "REMOVE" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:51 +#, python-format +msgid "RESET" +msgstr "RESET" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:65 +#, python-format +msgid "REVIEW" +msgstr "REVIEW" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:174 +#, python-format +msgid "Reboot" +msgstr "Reboot" + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:66 +#, python-format +msgid "Reboot Device" +msgstr "Reboot Device" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 +#, python-format +msgid "Reboot and Update" +msgstr "Reboot and Update" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:27 +msgid "" +"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)." +msgstr "" +"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)." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:76 +#, python-format +msgid "Record and Upload Driver Camera" +msgstr "Record and Upload Driver Camera" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:82 +#, python-format +msgid "Record and Upload Microphone Audio" +msgstr "Record and Upload Microphone Audio" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +msgid "" +"Record and store microphone audio while driving. The audio will be included " +"in the dashcam video in comma connect." +msgstr "" +"Record and store microphone audio while driving. The audio will be included " +"in the dashcam video in comma connect." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:67 +#, python-format +msgid "Regulatory" +msgstr "Regulatory" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#, python-format +msgid "Relaxed" +msgstr "Relaxed" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "Remote access" +msgstr "Remote access" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "Remote snapshots" +msgstr "Remote snapshots" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:123 +#, python-format +msgid "Request timed out" +msgstr "Request timed out" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:118 +#, python-format +msgid "Reset" +msgstr "Reset" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:51 +#, python-format +msgid "Reset Calibration" +msgstr "Reset Calibration" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:65 +#, python-format +msgid "Review Training Guide" +msgstr "Review Training Guide" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:27 +msgid "Review the rules, features, and limitations of openpilot" +msgstr "Review the rules, features, and limitations of openpilot" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:53 +#, python-format +msgid "SSH Keys" +msgstr "SSH Keys" + +#: /home/batman/openpilot/system/ui/widgets/network.py:310 +#, python-format +msgid "Scanning Wi-Fi networks..." +msgstr "Scanning Wi-Fi networks..." + +#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:35 +#, python-format +msgid "Select" +msgstr "Select" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:91 +#, python-format +msgid "Select a language" +msgstr "Select a language" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:60 +#, python-format +msgid "Serial" +msgstr "Serial" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 +#, python-format +msgid "Snooze Update" +msgstr "Snooze Update" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:65 +msgid "Software" +msgstr "Software" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#, python-format +msgid "Standard" +msgstr "Standard" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:22 +msgid "" +"Standard is recommended. In aggressive mode, openpilot will follow lead cars " +"closer and be more aggressive with the gas and brake. In relaxed mode " +"openpilot will stay further away from lead cars. On supported cars, you can " +"cycle through these personalities with your steering wheel distance button." +msgstr "" +"Standard is recommended. In aggressive mode, openpilot will follow lead cars " +"closer and be more aggressive with the gas and brake. In relaxed mode " +"openpilot will stay further away from lead cars. On supported cars, you can " +"cycle through these personalities with your steering wheel distance button." + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:59 +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:65 +#, python-format +msgid "System Unresponsive" +msgstr "System Unresponsive" + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:58 +#, python-format +msgid "TAKE CONTROL IMMEDIATELY" +msgstr "TAKE CONTROL IMMEDIATELY" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:129 +msgid "TEMP" +msgstr "TEMP" + +#: /home/batman/openpilot/system/ui/widgets/network.py:124 +#, python-format +msgid "Tethering Password" +msgstr "Tethering Password" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:64 +msgid "Toggles" +msgstr "Toggles" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:70 +#, python-format +msgid "UNINSTALL" +msgstr "UNINSTALL" + +#: /home/batman/openpilot/selfdrive/ui/layouts/home.py:155 +#, python-format +msgid "UPDATE" +msgstr "UPDATE" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:70 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:158 +#, python-format +msgid "Uninstall" +msgstr "Uninstall" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:117 +msgid "Unknown" +msgstr "Unknown" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:47 +#, python-format +msgid "Updates are only downloaded while the car is off." +msgstr "Updates are only downloaded while the car is off." + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:33 +#, python-format +msgid "Upgrade Now" +msgstr "Upgrade Now" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +msgid "" +"Upload data from the driver facing camera and help improve the driver " +"monitoring algorithm." +msgstr "" +"Upload data from the driver facing camera and help improve the driver " +"monitoring algorithm." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:88 +#, python-format +msgid "Use Metric System" +msgstr "Use Metric System" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:17 +msgid "" +"Use the openpilot system for adaptive cruise control and lane keep driver " +"assistance. Your attention is required at all times to use this feature." +msgstr "" +"Use the openpilot system for adaptive cruise control and lane keep driver " +"assistance. Your attention is required at all times to use this feature." + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:144 +msgid "VEHICLE" +msgstr "VEHICLE" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:67 +#, python-format +msgid "VIEW" +msgstr "VIEW" + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:52 +#, python-format +msgid "Waiting to start" +msgstr "Waiting to start" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:19 +msgid "" +"Warning: This grants SSH access to all public keys in your GitHub settings. " +"Never enter a GitHub username other than your own. A comma employee will " +"NEVER ask you to add their GitHub username." +msgstr "" +"Warning: This grants SSH access to all public keys in your GitHub settings. " +"Never enter a GitHub username other than your own. A comma employee will " +"NEVER ask you to add their GitHub username." + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:111 +#, python-format +msgid "Welcome to openpilot" +msgstr "Welcome to openpilot" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:20 +msgid "When enabled, pressing the accelerator pedal will disengage openpilot." +msgstr "When enabled, pressing the accelerator pedal will disengage openpilot." + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:44 +msgid "Wi-Fi" +msgstr "Wi-Fi" + +#: /home/batman/openpilot/system/ui/widgets/network.py:144 +#, python-format +msgid "Wi-Fi Network Metered" +msgstr "Wi-Fi Network Metered" + +#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#, python-format +msgid "Wrong password" +msgstr "Wrong password" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:145 +#, python-format +msgid "You must accept the Terms and Conditions in order to use openpilot." +msgstr "You must accept the Terms and Conditions in order to use openpilot." + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:112 +#, python-format +msgid "" +"You must accept the Terms and Conditions to use openpilot. Read the latest " +"terms at https://comma.ai/terms before continuing." +msgstr "" +"You must accept the Terms and Conditions to use openpilot. Read the latest " +"terms at https://comma.ai/terms before continuing." + +#: /home/batman/openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:34 +#, python-format +msgid "camera starting" +msgstr "camera starting" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:63 +#, python-format +msgid "comma prime" +msgstr "comma prime" + +#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#, python-format +msgid "default" +msgstr "default" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:132 +#, python-format +msgid "down" +msgstr "down" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:105 +#, python-format +msgid "failed to check for update" +msgstr "failed to check for update" + +#: /home/batman/openpilot/system/ui/widgets/network.py:237 +#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#, python-format +msgid "for \"{}\"" +msgstr "for \"{}\"" + +#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:177 +#, python-format +msgid "km/h" +msgstr "km/h" + +#: /home/batman/openpilot/system/ui/widgets/network.py:204 +#, python-format +msgid "leave blank for automatic configuration" +msgstr "leave blank for automatic configuration" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#, python-format +msgid "left" +msgstr "left" + +#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#, python-format +msgid "metered" +msgstr "metered" + +#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:177 +#, python-format +msgid "mph" +msgstr "mph" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:19 +#, python-format +msgid "never" +msgstr "never" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:30 +#, python-format +msgid "now" +msgstr "now" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:71 +#, python-format +msgid "openpilot Longitudinal Control (Alpha)" +msgstr "openpilot Longitudinal Control (Alpha)" + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:51 +#, python-format +msgid "openpilot Unavailable" +msgstr "openpilot Unavailable" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:158 +#, python-format +msgid "" +"openpilot defaults to driving in chill mode. Experimental mode enables alpha-" +"level features that aren't ready for chill mode. Experimental features are " +"listed below:

End-to-End Longitudinal Control


Let the driving " +"model control the gas and brakes. openpilot will drive as it thinks a human " +"would, including stopping for red lights and stop signs. Since the driving " +"model decides the speed to drive, the set speed will only act as an upper " +"bound. This is an alpha quality feature; mistakes should be expected." +"

New Driving Visualization


The driving visualization will " +"transition to the road-facing wide-angle camera at low speeds to better show " +"some turns. The Experimental mode logo will also be shown in the top right " +"corner." +msgstr "" +"openpilot defaults to driving in chill mode. Experimental mode enables alpha-" +"level features that aren't ready for chill mode. Experimental features are " +"listed below:

End-to-End Longitudinal Control


Let the driving " +"model control the gas and brakes. openpilot will drive as it thinks a human " +"would, including stopping for red lights and stop signs. Since the driving " +"model decides the speed to drive, the set speed will only act as an upper " +"bound. This is an alpha quality feature; mistakes should be expected." +"

New Driving Visualization


The driving visualization will " +"transition to the road-facing wide-angle camera at low speeds to better show " +"some turns. The Experimental mode logo will also be shown in the top right " +"corner." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:164 +#, python-format +msgid "" +"openpilot is continuously calibrating, resetting is rarely required. " +"Resetting calibration will restart openpilot if the car is powered on." +msgstr "" +"openpilot is continuously calibrating, resetting is rarely required. " +"Resetting calibration will restart openpilot if the car is powered on." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:20 +msgid "" +"openpilot learns to drive by watching humans, like you, drive.\n" +"\n" +"Firehose Mode allows you to maximize your training data uploads to improve " +"openpilot's driving models. More data means bigger models, which means " +"better Experimental Mode." +msgstr "" +"openpilot learns to drive by watching humans, like you, drive.\n" +"\n" +"Firehose Mode allows you to maximize your training data uploads to improve " +"openpilot's driving models. More data means bigger models, which means " +"better Experimental Mode." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:183 +#, python-format +msgid "openpilot longitudinal control may come in a future update." +msgstr "openpilot longitudinal control may come in a future update." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:26 +msgid "" +"openpilot requires the device to be mounted within 4° left or right and " +"within 5° up or 9° down." +msgstr "" +"openpilot requires the device to be mounted within 4° left or right and " +"within 5° up or 9° down." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#, python-format +msgid "right" +msgstr "right" + +#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#, python-format +msgid "unmetered" +msgstr "unmetered" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:132 +#, python-format +msgid "up" +msgstr "up" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:116 +#, python-format +msgid "up to date, last checked never" +msgstr "up to date, last checked never" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:114 +#, python-format +msgid "up to date, last checked {}" +msgstr "up to date, last checked {}" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:108 +#, python-format +msgid "update available" +msgstr "update available" + +#: /home/batman/openpilot/selfdrive/ui/layouts/home.py:169 +#, python-format +msgid "{} ALERT" +msgid_plural "{} ALERTS" +msgstr[0] "{} ALERT" +msgstr[1] "{} ALERTS" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:39 +#, python-format +msgid "{} day ago" +msgid_plural "{} days ago" +msgstr[0] "{} day ago" +msgstr[1] "{} days ago" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:36 +#, python-format +msgid "{} hour ago" +msgid_plural "{} hours ago" +msgstr[0] "{} hour ago" +msgstr[1] "{} hours ago" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:33 +#, python-format +msgid "{} minute ago" +msgid_plural "{} minutes ago" +msgstr[0] "{} minute ago" +msgstr[1] "{} minutes ago" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:111 +#, python-format +msgid "{} segment of your driving is in the training dataset so far." +msgid_plural "{} segments of your driving is in the training dataset so far." +msgstr[0] "{} segment of your driving is in the training dataset so far." +msgstr[1] "{} segments of your driving is in the training dataset so far." + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:62 +#, python-format +msgid "✓ SUBSCRIBED" +msgstr "✓ SUBSCRIBED" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:22 +#, python-format +msgid "🔥 Firehose Mode 🔥" +msgstr "🔥 Firehose Mode 🔥" diff --git a/selfdrive/ui/translations/app_es.po b/selfdrive/ui/translations/app_es.po new file mode 100644 index 000000000..a7424c091 --- /dev/null +++ b/selfdrive/ui/translations/app_es.po @@ -0,0 +1,1229 @@ +# Spanish translations for PACKAGE package. +# Copyright (C) 2025 THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# Automatically generated, 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-10-22 15:30-0700\n" +"PO-Revision-Date: 2025-10-20 16:35-0700\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:159 +#, python-format +msgid " Steering torque response calibration is complete." +msgstr " La calibración de respuesta de par de dirección está completa." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:157 +#, python-format +msgid " Steering torque response calibration is {}% complete." +msgstr " La calibración de respuesta de par de dirección está {}% completa." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:132 +#, python-format +msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." +msgstr " Tu dispositivo está orientado {:.1f}° {} y {:.1f}° {}." + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:43 +msgid "--" +msgstr "--" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "1 year of drive storage" +msgstr "1 año de almacenamiento de conducción" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "24/7 LTE connectivity" +msgstr "Conectividad LTE 24/7" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:46 +msgid "2G" +msgstr "2G" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:47 +msgid "3G" +msgstr "3G" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:49 +msgid "5G" +msgstr "5G" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:23 +msgid "" +"WARNING: openpilot longitudinal control is in alpha for this car and will " +"disable Automatic Emergency Braking (AEB).

On this car, openpilot " +"defaults to the car's built-in ACC instead of openpilot's longitudinal " +"control. Enable this to switch to openpilot longitudinal control. Enabling " +"Experimental mode is recommended when enabling openpilot longitudinal " +"control alpha. Changing this setting will restart openpilot if the car is " +"powered on." +msgstr "" +"ADVERTENCIA: el control longitudinal de openpilot está en alpha para este " +"coche y deshabilitará el Frenado Automático de Emergencia (AEB).

En este coche, openpilot usa por defecto el ACC integrado del " +"coche en lugar del control longitudinal de openpilot. Activa esto para " +"cambiar al control longitudinal de openpilot. Se recomienda activar el modo " +"Experimental al habilitar el control longitudinal de openpilot (alpha)." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:147 +#, python-format +msgid "

Steering lag calibration is complete." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:145 +#, python-format +msgid "

Steering lag calibration is {}% complete." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:138 +#, python-format +msgid "ACTIVE" +msgstr "ACTIVO" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:15 +msgid "" +"ADB (Android Debug Bridge) allows connecting to your device over USB or over " +"the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." +msgstr "" +"ADB (Android Debug Bridge) permite conectar tu dispositivo por USB o por la " +"red. Consulta https://docs.comma.ai/how-to/connect-to-comma para más " +"información." + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:30 +msgid "ADD" +msgstr "AÑADIR" + +#: /home/batman/openpilot/system/ui/widgets/network.py:139 +#, python-format +msgid "APN Setting" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 +#, python-format +msgid "Acknowledge Excessive Actuation" +msgstr "Reconocer actuación excesiva" + +#: /home/batman/openpilot/system/ui/widgets/network.py:74 +#: /home/batman/openpilot/system/ui/widgets/network.py:95 +#, python-format +msgid "Advanced" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#, python-format +msgid "Aggressive" +msgstr "Agresivo" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:116 +#, python-format +msgid "Agree" +msgstr "Aceptar" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:70 +#, python-format +msgid "Always-On Driver Monitoring" +msgstr "Supervisión del conductor siempre activa" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:186 +#, python-format +msgid "" +"An alpha version of openpilot longitudinal control can be tested, along with " +"Experimental mode, on non-release branches." +msgstr "" +"Se puede probar una versión alpha del control longitudinal de openpilot, " +"junto con el modo Experimental, en ramas que no son de lanzamiento." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:186 +#, python-format +msgid "Are you sure you want to power off?" +msgstr "¿Seguro que quieres apagar?" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:174 +#, python-format +msgid "Are you sure you want to reboot?" +msgstr "¿Seguro que quieres reiniciar?" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:118 +#, python-format +msgid "Are you sure you want to reset calibration?" +msgstr "¿Seguro que quieres restablecer la calibración?" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:158 +#, python-format +msgid "Are you sure you want to uninstall?" +msgstr "¿Seguro que quieres desinstalar?" + +#: /home/batman/openpilot/system/ui/widgets/network.py:99 +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:147 +#, python-format +msgid "Back" +msgstr "Atrás" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:38 +#, python-format +msgid "Become a comma prime member at connect.comma.ai" +msgstr "Hazte miembro de comma prime en connect.comma.ai" + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:130 +#, python-format +msgid "Bookmark connect.comma.ai to your home screen to use it like an app" +msgstr "" +"Añade connect.comma.ai a tu pantalla de inicio para usarlo como una app" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:68 +#, python-format +msgid "CHANGE" +msgstr "CAMBIAR" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:49 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:106 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:117 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:142 +#, python-format +msgid "CHECK" +msgstr "COMPROBAR" + +#: /home/batman/openpilot/selfdrive/ui/widgets/exp_mode_button.py:50 +#, python-format +msgid "CHILL MODE ON" +msgstr "MODO CHILL ACTIVADO" + +#: /home/batman/openpilot/system/ui/widgets/network.py:155 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:138 +#, python-format +msgid "CONNECT" +msgstr "CONECTAR" + +#: /home/batman/openpilot/system/ui/widgets/network.py:369 +#, python-format +msgid "CONNECTING..." +msgstr "CONECTAR" + +#: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:23 +#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:34 +#: /home/batman/openpilot/system/ui/widgets/keyboard.py:81 +#: /home/batman/openpilot/system/ui/widgets/network.py:318 +#, python-format +msgid "Cancel" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:134 +#, python-format +msgid "Cellular Metered" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:68 +#, python-format +msgid "Change Language" +msgstr "Cambiar idioma" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:125 +#, python-format +msgid "Changing this setting will restart openpilot if the car is powered on." +msgstr "" +" Cambiar esta configuración reiniciará openpilot si el coche está encendido." + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:129 +#, python-format +msgid "Click \"add new device\" and scan the QR code on the right" +msgstr "" +"Haz clic en \"añadir nuevo dispositivo\" y escanea el código QR de la derecha" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 +#, python-format +msgid "Close" +msgstr "Cerrar" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:48 +#, python-format +msgid "Current Version" +msgstr "Versión actual" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:109 +#, python-format +msgid "DOWNLOAD" +msgstr "DESCARGAR" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:115 +#, python-format +msgid "Decline" +msgstr "Rechazar" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:148 +#, python-format +msgid "Decline, uninstall openpilot" +msgstr "Rechazar, desinstalar openpilot" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:67 +msgid "Developer" +msgstr "Desarrollador" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:62 +msgid "Device" +msgstr "Dispositivo" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:58 +#, python-format +msgid "Disengage on Accelerator Pedal" +msgstr "Desactivar con el pedal del acelerador" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:183 +#, python-format +msgid "Disengage to Power Off" +msgstr "Desactivar para apagar" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:171 +#, python-format +msgid "Disengage to Reboot" +msgstr "Desactivar para reiniciar" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:102 +#, python-format +msgid "Disengage to Reset Calibration" +msgstr "Desactivar para restablecer la calibración" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:32 +msgid "Display speed in km/h instead of mph." +msgstr "Mostrar la velocidad en km/h en lugar de mph." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:59 +#, python-format +msgid "Dongle ID" +msgstr "ID del dongle" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:49 +#, python-format +msgid "Download" +msgstr "Descargar" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:62 +#, python-format +msgid "Driver Camera" +msgstr "Cámara del conductor" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:96 +#, python-format +msgid "Driving Personality" +msgstr "Estilo de conducción" + +#: /home/batman/openpilot/system/ui/widgets/network.py:123 +#: /home/batman/openpilot/system/ui/widgets/network.py:139 +#, python-format +msgid "EDIT" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:138 +msgid "ERROR" +msgstr "ERROR" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:45 +msgid "ETH" +msgstr "ETH" + +#: /home/batman/openpilot/selfdrive/ui/widgets/exp_mode_button.py:50 +#, python-format +msgid "EXPERIMENTAL MODE ON" +msgstr "MODO EXPERIMENTAL ACTIVADO" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:166 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:228 +#, python-format +msgid "Enable" +msgstr "Activar" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:39 +#, python-format +msgid "Enable ADB" +msgstr "Activar ADB" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:64 +#, python-format +msgid "Enable Lane Departure Warnings" +msgstr "Activar advertencias de salida de carril" + +#: /home/batman/openpilot/system/ui/widgets/network.py:129 +#, python-format +msgid "Enable Roaming" +msgstr "Activar openpilot" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:48 +#, python-format +msgid "Enable SSH" +msgstr "Activar SSH" + +#: /home/batman/openpilot/system/ui/widgets/network.py:120 +#, python-format +msgid "Enable Tethering" +msgstr "Activar advertencias de salida de carril" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:30 +msgid "Enable driver monitoring even when openpilot is not engaged." +msgstr "" +"Activar la supervisión del conductor incluso cuando openpilot no esté " +"activado." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:46 +#, python-format +msgid "Enable openpilot" +msgstr "Activar openpilot" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:189 +#, python-format +msgid "" +"Enable the openpilot longitudinal control (alpha) toggle to allow " +"Experimental mode." +msgstr "" +"Activa el interruptor de control longitudinal de openpilot (alpha) para " +"permitir el modo Experimental." + +#: /home/batman/openpilot/system/ui/widgets/network.py:204 +#, python-format +msgid "Enter APN" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:241 +#, python-format +msgid "Enter SSID" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:254 +#, python-format +msgid "Enter new tethering password" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:237 +#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#, python-format +msgid "Enter password" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:89 +#, python-format +msgid "Enter your GitHub username" +msgstr "Introduce tu nombre de usuario de GitHub" + +#: /home/batman/openpilot/system/ui/widgets/list_view.py:115 +#: /home/batman/openpilot/system/ui/widgets/list_view.py:153 +#, python-format +msgid "Error" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:52 +#, python-format +msgid "Experimental Mode" +msgstr "Modo experimental" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:181 +#, python-format +msgid "" +"Experimental mode is currently unavailable on this car since the car's stock " +"ACC is used for longitudinal control." +msgstr "" +"El modo experimental no está disponible actualmente en este coche, ya que se " +"usa el ACC de fábrica para el control longitudinal." + +#: /home/batman/openpilot/system/ui/widgets/network.py:373 +#, python-format +msgid "FORGETTING..." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:44 +#, python-format +msgid "Finish Setup" +msgstr "Finalizar configuración" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:66 +msgid "Firehose" +msgstr "Firehose" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:18 +msgid "Firehose Mode" +msgstr "Modo Firehose" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:25 +msgid "" +"For maximum effectiveness, bring your device inside and connect to a good " +"USB-C adapter and Wi-Fi weekly.\n" +"\n" +"Firehose Mode can also work while you're driving if connected to a hotspot " +"or unlimited SIM card.\n" +"\n" +"\n" +"Frequently Asked Questions\n" +"\n" +"Does it matter how or where I drive? Nope, just drive as you normally " +"would.\n" +"\n" +"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a " +"subset of your segments.\n" +"\n" +"What's a good USB-C adapter? Any fast phone or laptop charger should be " +"fine.\n" +"\n" +"Does it matter which software I run? Yes, only upstream openpilot (and " +"particular forks) are able to be used for training." +msgstr "" +"Para la máxima efectividad, lleva tu dispositivo al interior y conéctalo " +"semanalmente a un buen adaptador USB‑C y Wi‑Fi.\n" +"\n" +"El Modo Firehose también puede funcionar mientras conduces si está conectado " +"a un hotspot o a una SIM ilimitada.\n" +"\n" +"\n" +"Preguntas frecuentes\n" +"\n" +"¿Importa cómo o dónde conduzco? No, conduce como normalmente lo harías.\n" +"\n" +"¿Se suben todos mis segmentos en el Modo Firehose? No, seleccionamos un " +"subconjunto de tus segmentos.\n" +"\n" +"¿Qué es un buen adaptador USB‑C? Cualquier cargador rápido de teléfono o " +"laptop sirve.\n" +"\n" +"¿Importa qué software ejecuto? Sí, solo openpilot upstream (y forks " +"particulares) pueden usarse para entrenamiento." + +#: /home/batman/openpilot/system/ui/widgets/network.py:318 +#: /home/batman/openpilot/system/ui/widgets/network.py:451 +#, python-format +msgid "Forget" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:319 +#, python-format +msgid "Forget Wi-Fi Network \"{}\"?" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:125 +msgid "GOOD" +msgstr "BUENO" + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:128 +#, python-format +msgid "Go to https://connect.comma.ai on your phone" +msgstr "Ve a https://connect.comma.ai en tu teléfono" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:129 +msgid "HIGH" +msgstr "ALTO" + +#: /home/batman/openpilot/system/ui/widgets/network.py:155 +#, python-format +msgid "Hidden Network" +msgstr "Red" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:140 +#, python-format +msgid "INACTIVE: connect to an unmetered network" +msgstr "INACTIVO: conéctate a una red sin límites" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:52 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:131 +#, python-format +msgid "INSTALL" +msgstr "INSTALAR" + +#: /home/batman/openpilot/system/ui/widgets/network.py:150 +#, python-format +msgid "IP Address" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:52 +#, python-format +msgid "Install Update" +msgstr "Instalar actualización" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:56 +#, python-format +msgid "Joystick Debug Mode" +msgstr "Modo de depuración de joystick" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:29 +msgid "LOADING" +msgstr "CARGANDO" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:48 +msgid "LTE" +msgstr "LTE" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:64 +#, python-format +msgid "Longitudinal Maneuver Mode" +msgstr "Modo de maniobra longitudinal" + +#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:148 +#, python-format +msgid "MAX" +msgstr "MÁX" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:75 +#, python-format +msgid "" +"Maximize your training data uploads to improve openpilot's driving models." +msgstr "" +"Maximiza tus cargas de datos de entrenamiento para mejorar los modelos de " +"conducción de openpilot." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:59 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:60 +#, python-format +msgid "N/A" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:142 +msgid "NO" +msgstr "NO" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:63 +msgid "Network" +msgstr "Red" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:114 +#, python-format +msgid "No SSH keys found" +msgstr "No se encontraron claves SSH" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:126 +#, python-format +msgid "No SSH keys found for user '{}'" +msgstr "No se encontraron claves SSH para el usuario '{username}'" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:320 +#, python-format +msgid "No release notes available." +msgstr "No hay notas de versión disponibles." + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:134 +msgid "OFFLINE" +msgstr "SIN CONEXIÓN" + +#: /home/batman/openpilot/system/ui/widgets/html_render.py:247 +#: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:93 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:127 +#, python-format +msgid "OK" +msgstr "OK" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:144 +msgid "ONLINE" +msgstr "EN LÍNEA" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:20 +#, python-format +msgid "Open" +msgstr "Abrir" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:48 +#, python-format +msgid "PAIR" +msgstr "EMPAREJAR" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:142 +msgid "PANDA" +msgstr "PANDA" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:62 +#, python-format +msgid "PREVIEW" +msgstr "VISTA PREVIA" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:44 +#, python-format +msgid "PRIME FEATURES:" +msgstr "FUNCIONES PRIME:" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:48 +#, python-format +msgid "Pair Device" +msgstr "Emparejar dispositivo" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:19 +#, python-format +msgid "Pair device" +msgstr "Emparejar dispositivo" + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:103 +#, python-format +msgid "Pair your device to your comma account" +msgstr "Empareja tu dispositivo con tu cuenta de comma" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:48 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:24 +#, python-format +msgid "" +"Pair your device with comma connect (connect.comma.ai) and claim your comma " +"prime offer." +msgstr "" +"Empareja tu dispositivo con comma connect (connect.comma.ai) y reclama tu " +"oferta de comma prime." + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:91 +#, python-format +msgid "Please connect to Wi-Fi to complete initial pairing" +msgstr "Conéctate a Wi‑Fi para completar el emparejamiento inicial" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:186 +#, python-format +msgid "Power Off" +msgstr "Apagar" + +#: /home/batman/openpilot/system/ui/widgets/network.py:144 +#, python-format +msgid "Prevent large data uploads when on a metered Wi-Fi connection" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:135 +#, python-format +msgid "Prevent large data uploads when on a metered cellular connection" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:25 +msgid "" +"Preview the driver facing camera to ensure that driver monitoring has good " +"visibility. (vehicle must be off)" +msgstr "" +"Previsualiza la cámara hacia el conductor para asegurarte de que la " +"supervisión del conductor tenga buena visibilidad. (el vehículo debe estar " +"apagado)" + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:161 +#, python-format +msgid "QR Code Error" +msgstr "Error de código QR" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:31 +msgid "REMOVE" +msgstr "ELIMINAR" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:51 +#, python-format +msgid "RESET" +msgstr "RESTABLECER" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:65 +#, python-format +msgid "REVIEW" +msgstr "REVISAR" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:174 +#, python-format +msgid "Reboot" +msgstr "Reiniciar" + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:66 +#, python-format +msgid "Reboot Device" +msgstr "Reiniciar dispositivo" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 +#, python-format +msgid "Reboot and Update" +msgstr "Reiniciar y actualizar" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:27 +msgid "" +"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)." +msgstr "" +"Recibe alertas para volver al carril cuando tu vehículo se desvíe sobre una " +"línea de carril detectada sin la direccional activada mientras conduces a " +"más de 31 mph (50 km/h)." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:76 +#, python-format +msgid "Record and Upload Driver Camera" +msgstr "Grabar y subir cámara del conductor" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:82 +#, python-format +msgid "Record and Upload Microphone Audio" +msgstr "Grabar y subir audio del micrófono" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +msgid "" +"Record and store microphone audio while driving. The audio will be included " +"in the dashcam video in comma connect." +msgstr "" +"Grabar y almacenar audio del micrófono mientras conduces. El audio se " +"incluirá en el video de la dashcam en comma connect." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:67 +#, python-format +msgid "Regulatory" +msgstr "Reglamentario" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#, python-format +msgid "Relaxed" +msgstr "Relajado" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "Remote access" +msgstr "Acceso remoto" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "Remote snapshots" +msgstr "Capturas remotas" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:123 +#, python-format +msgid "Request timed out" +msgstr "Se agotó el tiempo de espera de la solicitud" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:118 +#, python-format +msgid "Reset" +msgstr "Restablecer" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:51 +#, python-format +msgid "Reset Calibration" +msgstr "Restablecer calibración" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:65 +#, python-format +msgid "Review Training Guide" +msgstr "Revisar guía de entrenamiento" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:27 +msgid "Review the rules, features, and limitations of openpilot" +msgstr "Revisa las reglas, funciones y limitaciones de openpilot" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:53 +#, python-format +msgid "SSH Keys" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:310 +#, python-format +msgid "Scanning Wi-Fi networks..." +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:35 +#, python-format +msgid "Select" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:91 +#, python-format +msgid "Select a language" +msgstr "Selecciona un idioma" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:60 +#, python-format +msgid "Serial" +msgstr "Número de serie" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 +#, python-format +msgid "Snooze Update" +msgstr "Posponer actualización" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:65 +msgid "Software" +msgstr "Software" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#, python-format +msgid "Standard" +msgstr "Estándar" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:22 +msgid "" +"Standard is recommended. In aggressive mode, openpilot will follow lead cars " +"closer and be more aggressive with the gas and brake. In relaxed mode " +"openpilot will stay further away from lead cars. On supported cars, you can " +"cycle through these personalities with your steering wheel distance button." +msgstr "" +"Se recomienda Estándar. En modo agresivo, openpilot seguirá más de cerca a " +"los coches delanteros y será más agresivo con el acelerador y el freno. En " +"modo relajado, openpilot se mantendrá más lejos de los coches delanteros. En " +"coches compatibles, puedes cambiar entre estas personalidades con el botón " +"de distancia del volante." + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:59 +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:65 +#, python-format +msgid "System Unresponsive" +msgstr "Sistema sin respuesta" + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:58 +#, python-format +msgid "TAKE CONTROL IMMEDIATELY" +msgstr "TOME EL CONTROL INMEDIATAMENTE" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:129 +msgid "TEMP" +msgstr "TEMP" + +#: /home/batman/openpilot/system/ui/widgets/network.py:124 +#, python-format +msgid "Tethering Password" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:64 +msgid "Toggles" +msgstr "Interruptores" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:70 +#, python-format +msgid "UNINSTALL" +msgstr "DESINSTALAR" + +#: /home/batman/openpilot/selfdrive/ui/layouts/home.py:155 +#, python-format +msgid "UPDATE" +msgstr "ACTUALIZAR" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:70 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:158 +#, python-format +msgid "Uninstall" +msgstr "Desinstalar" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:117 +msgid "Unknown" +msgstr "Desconocido" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:47 +#, python-format +msgid "Updates are only downloaded while the car is off." +msgstr "Las actualizaciones solo se descargan cuando el coche está apagado." + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:33 +#, python-format +msgid "Upgrade Now" +msgstr "Mejorar ahora" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +msgid "" +"Upload data from the driver facing camera and help improve the driver " +"monitoring algorithm." +msgstr "" +"Sube datos de la cámara orientada al conductor y ayuda a mejorar el " +"algoritmo de supervisión del conductor." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:88 +#, python-format +msgid "Use Metric System" +msgstr "Usar sistema métrico" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:17 +msgid "" +"Use the openpilot system for adaptive cruise control and lane keep driver " +"assistance. Your attention is required at all times to use this feature." +msgstr "" +"Usa el sistema openpilot para control de crucero adaptativo y asistencia de " +"mantenimiento de carril. Tu atención se requiere en todo momento para usar " +"esta función." + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:144 +msgid "VEHICLE" +msgstr "VEHÍCULO" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:67 +#, python-format +msgid "VIEW" +msgstr "VER" + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:52 +#, python-format +msgid "Waiting to start" +msgstr "Esperando para iniciar" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:19 +msgid "" +"Warning: This grants SSH access to all public keys in your GitHub settings. " +"Never enter a GitHub username other than your own. A comma employee will " +"NEVER ask you to add their GitHub username." +msgstr "" +"Advertencia: Esto otorga acceso SSH a todas las claves públicas en tu " +"configuración de GitHub. Nunca introduzcas un nombre de usuario de GitHub " +"que no sea el tuyo. Un empleado de comma NUNCA te pedirá que agregues su " +"nombre de usuario de GitHub." + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:111 +#, python-format +msgid "Welcome to openpilot" +msgstr "Bienvenido a openpilot" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:20 +msgid "When enabled, pressing the accelerator pedal will disengage openpilot." +msgstr "" +"Cuando está activado, al presionar el pedal del acelerador se desactivará " +"openpilot." + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:44 +msgid "Wi-Fi" +msgstr "Wi‑Fi" + +#: /home/batman/openpilot/system/ui/widgets/network.py:144 +#, python-format +msgid "Wi-Fi Network Metered" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#, python-format +msgid "Wrong password" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:145 +#, python-format +msgid "You must accept the Terms and Conditions in order to use openpilot." +msgstr "Debes aceptar los Términos y Condiciones para poder usar openpilot." + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:112 +#, python-format +msgid "" +"You must accept the Terms and Conditions to use openpilot. Read the latest " +"terms at https://comma.ai/terms before continuing." +msgstr "" +"Debes aceptar los Términos y Condiciones para usar openpilot. Lee los " +"términos más recientes en https://comma.ai/terms antes de continuar." + +#: /home/batman/openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:34 +#, python-format +msgid "camera starting" +msgstr "iniciando cámara" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:63 +#, python-format +msgid "comma prime" +msgstr "comma prime" + +#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#, python-format +msgid "default" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:132 +#, python-format +msgid "down" +msgstr "abajo" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:105 +#, python-format +msgid "failed to check for update" +msgstr "Error al buscar actualizaciones" + +#: /home/batman/openpilot/system/ui/widgets/network.py:237 +#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#, python-format +msgid "for \"{}\"" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:177 +#, python-format +msgid "km/h" +msgstr "km/h" + +#: /home/batman/openpilot/system/ui/widgets/network.py:204 +#, python-format +msgid "leave blank for automatic configuration" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#, python-format +msgid "left" +msgstr "izquierda" + +#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#, python-format +msgid "metered" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:177 +#, python-format +msgid "mph" +msgstr "mph" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:19 +#, python-format +msgid "never" +msgstr "nunca" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:30 +#, python-format +msgid "now" +msgstr "ahora" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:71 +#, python-format +msgid "openpilot Longitudinal Control (Alpha)" +msgstr "Control longitudinal de openpilot (Alpha)" + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:51 +#, python-format +msgid "openpilot Unavailable" +msgstr "openpilot no disponible" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:158 +#, python-format +msgid "" +"openpilot defaults to driving in chill mode. Experimental mode enables alpha-" +"level features that aren't ready for chill mode. Experimental features are " +"listed below:

End-to-End Longitudinal Control


Let the driving " +"model control the gas and brakes. openpilot will drive as it thinks a human " +"would, including stopping for red lights and stop signs. Since the driving " +"model decides the speed to drive, the set speed will only act as an upper " +"bound. This is an alpha quality feature; mistakes should be expected." +"

New Driving Visualization


The driving visualization will " +"transition to the road-facing wide-angle camera at low speeds to better show " +"some turns. The Experimental mode logo will also be shown in the top right " +"corner." +msgstr "" +"openpilot conduce por defecto en modo chill. El modo Experimental habilita " +"funciones de nivel alpha que no están listas para el modo chill. Las " +"funciones experimentales se enumeran a continuación:

Control " +"longitudinal de extremo a extremo


Deja que el modelo de conducción " +"controle el acelerador y los frenos. openpilot conducirá como piensa que lo " +"haría un humano, incluyendo detenerse en luces rojas y señales de alto. Dado " +"que el modelo decide la velocidad a la que conducir, la velocidad " +"establecida solo actuará como límite superior. Esta es una función de " +"calidad alpha; se deben esperar errores.

Nueva visualización de " +"conducción


La visualización de conducción hará la transición a la " +"cámara gran angular orientada a la carretera a bajas velocidades para " +"mostrar mejor algunos giros. El logotipo del modo Experimental también se " +"mostrará en la esquina superior derecha." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:164 +#, python-format +msgid "" +"openpilot is continuously calibrating, resetting is rarely required. " +"Resetting calibration will restart openpilot if the car is powered on." +msgstr "" +" Cambiar esta configuración reiniciará openpilot si el coche está encendido." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:20 +msgid "" +"openpilot learns to drive by watching humans, like you, drive.\n" +"\n" +"Firehose Mode allows you to maximize your training data uploads to improve " +"openpilot's driving models. More data means bigger models, which means " +"better Experimental Mode." +msgstr "" +"openpilot aprende a conducir observando a humanos, como tú, conducir.\n" +"\n" +"El Modo Firehose te permite maximizar tus cargas de datos de entrenamiento " +"para mejorar los modelos de conducción de openpilot. Más datos significan " +"modelos más grandes, lo que significa un mejor Modo Experimental." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:183 +#, python-format +msgid "openpilot longitudinal control may come in a future update." +msgstr "" +"El control longitudinal de openpilot podría llegar en una actualización " +"futura." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:26 +msgid "" +"openpilot requires the device to be mounted within 4° left or right and " +"within 5° up or 9° down." +msgstr "" +"openpilot requiere que el dispositivo esté montado dentro de 4° a izquierda " +"o derecha y dentro de 5° hacia arriba o 9° hacia abajo." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#, python-format +msgid "right" +msgstr "derecha" + +#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#, python-format +msgid "unmetered" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:132 +#, python-format +msgid "up" +msgstr "arriba" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:116 +#, python-format +msgid "up to date, last checked never" +msgstr "actualizado, última comprobación: nunca" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:114 +#, python-format +msgid "up to date, last checked {}" +msgstr "actualizado, última comprobación: {}" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:108 +#, python-format +msgid "update available" +msgstr "actualización disponible" + +#: /home/batman/openpilot/selfdrive/ui/layouts/home.py:169 +#, python-format +msgid "{} ALERT" +msgid_plural "{} ALERTS" +msgstr[0] "{} ALERTA" +msgstr[1] "{} ALERTAS" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:39 +#, python-format +msgid "{} day ago" +msgid_plural "{} days ago" +msgstr[0] "hace {} día" +msgstr[1] "hace {} días" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:36 +#, python-format +msgid "{} hour ago" +msgid_plural "{} hours ago" +msgstr[0] "hace {} hora" +msgstr[1] "hace {} horas" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:33 +#, python-format +msgid "{} minute ago" +msgid_plural "{} minutes ago" +msgstr[0] "hace {} minuto" +msgstr[1] "hace {} minutos" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:111 +#, python-format +msgid "{} segment of your driving is in the training dataset so far." +msgid_plural "{} segments of your driving is in the training dataset so far." +msgstr[0] "" +"{} segmento de tu conducción está en el conjunto de entrenamiento hasta " +"ahora." +msgstr[1] "" +"{} segmentos de tu conducción están en el conjunto de entrenamiento hasta " +"ahora." + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:62 +#, python-format +msgid "✓ SUBSCRIBED" +msgstr "✓ SUSCRITO" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:22 +#, python-format +msgid "🔥 Firehose Mode 🔥" +msgstr "🔥 Modo Firehose 🔥" diff --git a/selfdrive/ui/translations/app_fr.po b/selfdrive/ui/translations/app_fr.po new file mode 100644 index 000000000..12bdcb3a8 --- /dev/null +++ b/selfdrive/ui/translations/app_fr.po @@ -0,0 +1,1234 @@ +# French translations for PACKAGE package. +# Copyright (C) 2025 THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# Automatically generated, 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-10-22 15:30-0700\n" +"PO-Revision-Date: 2025-10-20 18:19-0700\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: fr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:159 +#, python-format +msgid " Steering torque response calibration is complete." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:157 +#, python-format +msgid " Steering torque response calibration is {}% complete." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:132 +#, python-format +msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:43 +msgid "--" +msgstr "--" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "1 year of drive storage" +msgstr "1 an de stockage de trajets" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "24/7 LTE connectivity" +msgstr "Connexion LTE 24/7" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:46 +msgid "2G" +msgstr "2G" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:47 +msgid "3G" +msgstr "3G" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:49 +msgid "5G" +msgstr "5G" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:23 +msgid "" +"WARNING: openpilot longitudinal control is in alpha for this car and will " +"disable Automatic Emergency Braking (AEB).

On this car, openpilot " +"defaults to the car's built-in ACC instead of openpilot's longitudinal " +"control. Enable this to switch to openpilot longitudinal control. Enabling " +"Experimental mode is recommended when enabling openpilot longitudinal " +"control alpha. Changing this setting will restart openpilot if the car is " +"powered on." +msgstr "" +"ATTENTION : le contrôle longitudinal openpilot est en alpha pour cette " +"voiture et désactivera le freinage d'urgence automatique (AEB).

Sur cette voiture, openpilot utilise par défaut le régulateur de " +"vitesse adaptatif intégré au véhicule plutôt que le contrôle longitudinal " +"d'openpilot. Activez ceci pour passer au contrôle longitudinal openpilot. Il " +"est recommandé d'activer le mode expérimental lors de l'activation du " +"contrôle longitudinal openpilot alpha." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:147 +#, python-format +msgid "

Steering lag calibration is complete." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:145 +#, python-format +msgid "

Steering lag calibration is {}% complete." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:138 +#, python-format +msgid "ACTIVE" +msgstr "ACTIF" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:15 +msgid "" +"ADB (Android Debug Bridge) allows connecting to your device over USB or over " +"the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." +msgstr "" +"ADB (Android Debug Bridge) permet de connecter votre appareil via USB ou via " +"le réseau. Voir https://docs.comma.ai/how-to/connect-to-comma pour plus " +"d'informations." + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:30 +msgid "ADD" +msgstr "AJOUTER" + +#: /home/batman/openpilot/system/ui/widgets/network.py:139 +#, python-format +msgid "APN Setting" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 +#, python-format +msgid "Acknowledge Excessive Actuation" +msgstr "Accuser réception d'actionnement excessif" + +#: /home/batman/openpilot/system/ui/widgets/network.py:74 +#: /home/batman/openpilot/system/ui/widgets/network.py:95 +#, python-format +msgid "Advanced" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#, python-format +msgid "Aggressive" +msgstr "Agressif" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:116 +#, python-format +msgid "Agree" +msgstr "Accepter" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:70 +#, python-format +msgid "Always-On Driver Monitoring" +msgstr "Surveillance continue du conducteur" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:186 +#, python-format +msgid "" +"An alpha version of openpilot longitudinal control can be tested, along with " +"Experimental mode, on non-release branches." +msgstr "" +"Une version alpha du contrôle longitudinal openpilot peut être testée, avec " +"le mode expérimental, sur des branches non publiées." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:186 +#, python-format +msgid "Are you sure you want to power off?" +msgstr "Êtes-vous sûr de vouloir éteindre ?" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:174 +#, python-format +msgid "Are you sure you want to reboot?" +msgstr "Êtes-vous sûr de vouloir redémarrer ?" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:118 +#, python-format +msgid "Are you sure you want to reset calibration?" +msgstr "Êtes-vous sûr de vouloir réinitialiser la calibration ?" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:158 +#, python-format +msgid "Are you sure you want to uninstall?" +msgstr "Êtes-vous sûr de vouloir désinstaller ?" + +#: /home/batman/openpilot/system/ui/widgets/network.py:99 +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:147 +#, python-format +msgid "Back" +msgstr "Retour" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:38 +#, python-format +msgid "Become a comma prime member at connect.comma.ai" +msgstr "Devenez membre comma prime sur connect.comma.ai" + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:130 +#, python-format +msgid "Bookmark connect.comma.ai to your home screen to use it like an app" +msgstr "" +"Ajoutez connect.comma.ai à votre écran d'accueil pour l'utiliser comme une " +"application" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:68 +#, python-format +msgid "CHANGE" +msgstr "CHANGER" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:49 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:106 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:117 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:142 +#, python-format +msgid "CHECK" +msgstr "VÉRIFIER" + +#: /home/batman/openpilot/selfdrive/ui/widgets/exp_mode_button.py:50 +#, python-format +msgid "CHILL MODE ON" +msgstr "MODE CHILL ACTIVÉ" + +#: /home/batman/openpilot/system/ui/widgets/network.py:155 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:138 +#, python-format +msgid "CONNECT" +msgstr "CONNECTER" + +#: /home/batman/openpilot/system/ui/widgets/network.py:369 +#, python-format +msgid "CONNECTING..." +msgstr "CONNECTER" + +#: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:23 +#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:34 +#: /home/batman/openpilot/system/ui/widgets/keyboard.py:81 +#: /home/batman/openpilot/system/ui/widgets/network.py:318 +#, python-format +msgid "Cancel" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:134 +#, python-format +msgid "Cellular Metered" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:68 +#, python-format +msgid "Change Language" +msgstr "Changer la langue" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:125 +#, python-format +msgid "Changing this setting will restart openpilot if the car is powered on." +msgstr "" +" La modification de ce réglage redémarrera openpilot si la voiture est sous " +"tension." + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:129 +#, python-format +msgid "Click \"add new device\" and scan the QR code on the right" +msgstr "Cliquez sur \"add new device\" et scannez le code QR à droite" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 +#, python-format +msgid "Close" +msgstr "Fermer" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:48 +#, python-format +msgid "Current Version" +msgstr "Version actuelle" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:109 +#, python-format +msgid "DOWNLOAD" +msgstr "TÉLÉCHARGER" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:115 +#, python-format +msgid "Decline" +msgstr "Refuser" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:148 +#, python-format +msgid "Decline, uninstall openpilot" +msgstr "Refuser, désinstaller openpilot" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:67 +msgid "Developer" +msgstr "Développeur" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:62 +msgid "Device" +msgstr "Appareil" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:58 +#, python-format +msgid "Disengage on Accelerator Pedal" +msgstr "Désengager à l'appui sur l'accélérateur" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:183 +#, python-format +msgid "Disengage to Power Off" +msgstr "Désengager pour éteindre" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:171 +#, python-format +msgid "Disengage to Reboot" +msgstr "Désengager pour redémarrer" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:102 +#, python-format +msgid "Disengage to Reset Calibration" +msgstr "Désengager pour réinitialiser la calibration" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:32 +msgid "Display speed in km/h instead of mph." +msgstr "Afficher la vitesse en km/h au lieu de mph." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:59 +#, python-format +msgid "Dongle ID" +msgstr "ID du dongle" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:49 +#, python-format +msgid "Download" +msgstr "Télécharger" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:62 +#, python-format +msgid "Driver Camera" +msgstr "Caméra conducteur" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:96 +#, python-format +msgid "Driving Personality" +msgstr "Personnalité de conduite" + +#: /home/batman/openpilot/system/ui/widgets/network.py:123 +#: /home/batman/openpilot/system/ui/widgets/network.py:139 +#, python-format +msgid "EDIT" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:138 +msgid "ERROR" +msgstr "ERREUR" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:45 +msgid "ETH" +msgstr "ETH" + +#: /home/batman/openpilot/selfdrive/ui/widgets/exp_mode_button.py:50 +#, python-format +msgid "EXPERIMENTAL MODE ON" +msgstr "MODE EXPÉRIMENTAL ACTIVÉ" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:166 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:228 +#, python-format +msgid "Enable" +msgstr "Activer" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:39 +#, python-format +msgid "Enable ADB" +msgstr "Activer ADB" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:64 +#, python-format +msgid "Enable Lane Departure Warnings" +msgstr "Activer les alertes de sortie de voie" + +#: /home/batman/openpilot/system/ui/widgets/network.py:129 +#, python-format +msgid "Enable Roaming" +msgstr "Activer openpilot" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:48 +#, python-format +msgid "Enable SSH" +msgstr "Activer SSH" + +#: /home/batman/openpilot/system/ui/widgets/network.py:120 +#, python-format +msgid "Enable Tethering" +msgstr "Activer les alertes de sortie de voie" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:30 +msgid "Enable driver monitoring even when openpilot is not engaged." +msgstr "" +"Activer la surveillance du conducteur même lorsque openpilot n'est pas " +"engagé." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:46 +#, python-format +msgid "Enable openpilot" +msgstr "Activer openpilot" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:189 +#, python-format +msgid "" +"Enable the openpilot longitudinal control (alpha) toggle to allow " +"Experimental mode." +msgstr "" +"Activez l'option de contrôle longitudinal openpilot (alpha) pour autoriser " +"le mode expérimental." + +#: /home/batman/openpilot/system/ui/widgets/network.py:204 +#, python-format +msgid "Enter APN" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:241 +#, python-format +msgid "Enter SSID" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:254 +#, python-format +msgid "Enter new tethering password" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:237 +#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#, python-format +msgid "Enter password" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:89 +#, python-format +msgid "Enter your GitHub username" +msgstr "Entrez votre nom d'utilisateur GitHub" + +#: /home/batman/openpilot/system/ui/widgets/list_view.py:115 +#: /home/batman/openpilot/system/ui/widgets/list_view.py:153 +#, python-format +msgid "Error" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:52 +#, python-format +msgid "Experimental Mode" +msgstr "Mode expérimental" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:181 +#, python-format +msgid "" +"Experimental mode is currently unavailable on this car since the car's stock " +"ACC is used for longitudinal control." +msgstr "" +"Le mode expérimental est actuellement indisponible sur cette voiture car " +"l'ACC d'origine est utilisé pour le contrôle longitudinal." + +#: /home/batman/openpilot/system/ui/widgets/network.py:373 +#, python-format +msgid "FORGETTING..." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:44 +#, python-format +msgid "Finish Setup" +msgstr "Terminer la configuration" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:66 +msgid "Firehose" +msgstr "Firehose" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:18 +msgid "Firehose Mode" +msgstr "Mode Firehose" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:25 +msgid "" +"For maximum effectiveness, bring your device inside and connect to a good " +"USB-C adapter and Wi-Fi weekly.\n" +"\n" +"Firehose Mode can also work while you're driving if connected to a hotspot " +"or unlimited SIM card.\n" +"\n" +"\n" +"Frequently Asked Questions\n" +"\n" +"Does it matter how or where I drive? Nope, just drive as you normally " +"would.\n" +"\n" +"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a " +"subset of your segments.\n" +"\n" +"What's a good USB-C adapter? Any fast phone or laptop charger should be " +"fine.\n" +"\n" +"Does it matter which software I run? Yes, only upstream openpilot (and " +"particular forks) are able to be used for training." +msgstr "" +"Pour une efficacité maximale, rentrez votre appareil et connectez-le chaque " +"semaine à un bon adaptateur USB-C et au Wi‑Fi.\n" +"\n" +"Le Mode Firehose peut aussi fonctionner pendant que vous conduisez si vous " +"êtes connecté à un hotspot ou à une carte SIM illimitée.\n" +"\n" +"\n" +"Foire aux questions\n" +"\n" +"Est-ce que la manière ou l'endroit où je conduis compte ? Non, conduisez " +"normalement.\n" +"\n" +"Tous mes segments sont-ils récupérés en Mode Firehose ? Non, nous récupérons " +"de façon sélective un sous-ensemble de vos segments.\n" +"\n" +"Quel est un bon adaptateur USB-C ? Tout chargeur rapide de téléphone ou " +"d'ordinateur portable convient.\n" +"\n" +"Le logiciel utilisé importe-t-il ? Oui, seul openpilot amont (et certains " +"forks) peut être utilisé pour l'entraînement." + +#: /home/batman/openpilot/system/ui/widgets/network.py:318 +#: /home/batman/openpilot/system/ui/widgets/network.py:451 +#, python-format +msgid "Forget" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:319 +#, python-format +msgid "Forget Wi-Fi Network \"{}\"?" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:125 +msgid "GOOD" +msgstr "BON" + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:128 +#, python-format +msgid "Go to https://connect.comma.ai on your phone" +msgstr "Allez sur https://connect.comma.ai sur votre téléphone" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:129 +msgid "HIGH" +msgstr "ÉLEVÉ" + +#: /home/batman/openpilot/system/ui/widgets/network.py:155 +#, python-format +msgid "Hidden Network" +msgstr "Réseau" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:140 +#, python-format +msgid "INACTIVE: connect to an unmetered network" +msgstr "INACTIF : connectez-vous à un réseau non limité" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:52 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:131 +#, python-format +msgid "INSTALL" +msgstr "INSTALLER" + +#: /home/batman/openpilot/system/ui/widgets/network.py:150 +#, python-format +msgid "IP Address" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:52 +#, python-format +msgid "Install Update" +msgstr "Installer la mise à jour" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:56 +#, python-format +msgid "Joystick Debug Mode" +msgstr "Mode débogage joystick" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:29 +msgid "LOADING" +msgstr "CHARGEMENT" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:48 +msgid "LTE" +msgstr "LTE" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:64 +#, python-format +msgid "Longitudinal Maneuver Mode" +msgstr "Mode de manœuvre longitudinale" + +#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:148 +#, python-format +msgid "MAX" +msgstr "MAX" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:75 +#, python-format +msgid "" +"Maximize your training data uploads to improve openpilot's driving models." +msgstr "" +"Maximisez vos envois de données d'entraînement pour améliorer les modèles de " +"conduite d'openpilot." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:59 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:60 +#, python-format +msgid "N/A" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:142 +msgid "NO" +msgstr "NON" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:63 +msgid "Network" +msgstr "Réseau" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:114 +#, python-format +msgid "No SSH keys found" +msgstr "Aucune clé SSH trouvée" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:126 +#, python-format +msgid "No SSH keys found for user '{}'" +msgstr "Aucune clé SSH trouvée pour l'utilisateur '{username}'" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:320 +#, python-format +msgid "No release notes available." +msgstr "Aucune note de version disponible." + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:134 +msgid "OFFLINE" +msgstr "HORS LIGNE" + +#: /home/batman/openpilot/system/ui/widgets/html_render.py:247 +#: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:93 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:127 +#, python-format +msgid "OK" +msgstr "OK" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:144 +msgid "ONLINE" +msgstr "EN LIGNE" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:20 +#, python-format +msgid "Open" +msgstr "Ouvrir" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:48 +#, python-format +msgid "PAIR" +msgstr "ASSOCIER" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:142 +msgid "PANDA" +msgstr "PANDA" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:62 +#, python-format +msgid "PREVIEW" +msgstr "APERÇU" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:44 +#, python-format +msgid "PRIME FEATURES:" +msgstr "FONCTIONNALITÉS PRIME :" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:48 +#, python-format +msgid "Pair Device" +msgstr "Associer l'appareil" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:19 +#, python-format +msgid "Pair device" +msgstr "Associer l'appareil" + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:103 +#, python-format +msgid "Pair your device to your comma account" +msgstr "Associez votre appareil à votre compte comma" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:48 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:24 +#, python-format +msgid "" +"Pair your device with comma connect (connect.comma.ai) and claim your comma " +"prime offer." +msgstr "" +"Associez votre appareil à comma connect (connect.comma.ai) et réclamez votre " +"offre comma prime." + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:91 +#, python-format +msgid "Please connect to Wi-Fi to complete initial pairing" +msgstr "Veuillez vous connecter au Wi‑Fi pour terminer l'association initiale" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:186 +#, python-format +msgid "Power Off" +msgstr "Éteindre" + +#: /home/batman/openpilot/system/ui/widgets/network.py:144 +#, python-format +msgid "Prevent large data uploads when on a metered Wi-Fi connection" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:135 +#, python-format +msgid "Prevent large data uploads when on a metered cellular connection" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:25 +msgid "" +"Preview the driver facing camera to ensure that driver monitoring has good " +"visibility. (vehicle must be off)" +msgstr "" +"Prévisualisez la caméra orientée conducteur pour vous assurer que la " +"surveillance du conducteur a une bonne visibilité. (le véhicule doit être " +"éteint)" + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:161 +#, python-format +msgid "QR Code Error" +msgstr "Erreur de code QR" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:31 +msgid "REMOVE" +msgstr "SUPPRIMER" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:51 +#, python-format +msgid "RESET" +msgstr "RÉINITIALISER" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:65 +#, python-format +msgid "REVIEW" +msgstr "CONSULTER" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:174 +#, python-format +msgid "Reboot" +msgstr "Redémarrer" + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:66 +#, python-format +msgid "Reboot Device" +msgstr "Redémarrer l'appareil" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 +#, python-format +msgid "Reboot and Update" +msgstr "Redémarrer et mettre à jour" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:27 +msgid "" +"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)." +msgstr "" +"Recevez des alertes pour revenir dans la voie lorsque votre véhicule dépasse " +"une ligne de voie détectée sans clignotant activé en roulant au-delà de 31 " +"mph (50 km/h)." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:76 +#, python-format +msgid "Record and Upload Driver Camera" +msgstr "Enregistrer et téléverser la caméra conducteur" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:82 +#, python-format +msgid "Record and Upload Microphone Audio" +msgstr "Enregistrer et téléverser l'audio du microphone" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +msgid "" +"Record and store microphone audio while driving. The audio will be included " +"in the dashcam video in comma connect." +msgstr "" +"Enregistrer et stocker l'audio du microphone pendant la conduite. L'audio " +"sera inclus dans la vidéo dashcam dans comma connect." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:67 +#, python-format +msgid "Regulatory" +msgstr "Réglementaire" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#, python-format +msgid "Relaxed" +msgstr "Détendu" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "Remote access" +msgstr "Accès à distance" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "Remote snapshots" +msgstr "Captures à distance" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:123 +#, python-format +msgid "Request timed out" +msgstr "Délai de la requête dépassé" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:118 +#, python-format +msgid "Reset" +msgstr "Réinitialiser" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:51 +#, python-format +msgid "Reset Calibration" +msgstr "Réinitialiser la calibration" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:65 +#, python-format +msgid "Review Training Guide" +msgstr "Consulter le guide d'entraînement" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:27 +msgid "Review the rules, features, and limitations of openpilot" +msgstr "Consultez les règles, fonctionnalités et limitations d'openpilot" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:53 +#, python-format +msgid "SSH Keys" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:310 +#, python-format +msgid "Scanning Wi-Fi networks..." +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:35 +#, python-format +msgid "Select" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:91 +#, python-format +msgid "Select a language" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:60 +#, python-format +msgid "Serial" +msgstr "Numéro de série" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 +#, python-format +msgid "Snooze Update" +msgstr "Reporter la mise à jour" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:65 +msgid "Software" +msgstr "Logiciel" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#, python-format +msgid "Standard" +msgstr "Standard" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:22 +msgid "" +"Standard is recommended. In aggressive mode, openpilot will follow lead cars " +"closer and be more aggressive with the gas and brake. In relaxed mode " +"openpilot will stay further away from lead cars. On supported cars, you can " +"cycle through these personalities with your steering wheel distance button." +msgstr "" +"Le mode standard est recommandé. En mode agressif, openpilot suivra les " +"véhicules de tête de plus près et sera plus agressif avec l'accélérateur et " +"le frein. En mode détendu, openpilot restera plus éloigné des véhicules de " +"tête. Sur les voitures compatibles, vous pouvez parcourir ces personnalités " +"avec le bouton de distance du volant." + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:59 +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:65 +#, python-format +msgid "System Unresponsive" +msgstr "Système non réactif" + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:58 +#, python-format +msgid "TAKE CONTROL IMMEDIATELY" +msgstr "REPRENEZ IMMÉDIATEMENT LE CONTRÔLE" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:129 +msgid "TEMP" +msgstr "TEMPÉRATURE" + +#: /home/batman/openpilot/system/ui/widgets/network.py:124 +#, python-format +msgid "Tethering Password" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:64 +msgid "Toggles" +msgstr "Options" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:70 +#, python-format +msgid "UNINSTALL" +msgstr "DÉSINSTALLER" + +#: /home/batman/openpilot/selfdrive/ui/layouts/home.py:155 +#, python-format +msgid "UPDATE" +msgstr "METTRE À JOUR" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:70 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:158 +#, python-format +msgid "Uninstall" +msgstr "Désinstaller" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:117 +msgid "Unknown" +msgstr "Inconnu" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:47 +#, python-format +msgid "Updates are only downloaded while the car is off." +msgstr "" +"Les mises à jour ne sont téléchargées que lorsque la voiture est éteinte." + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:33 +#, python-format +msgid "Upgrade Now" +msgstr "Mettre à niveau maintenant" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +msgid "" +"Upload data from the driver facing camera and help improve the driver " +"monitoring algorithm." +msgstr "" +"Téléverser les données de la caméra orientée conducteur et aider à améliorer " +"l'algorithme de surveillance du conducteur." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:88 +#, python-format +msgid "Use Metric System" +msgstr "Utiliser le système métrique" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:17 +msgid "" +"Use the openpilot system for adaptive cruise control and lane keep driver " +"assistance. Your attention is required at all times to use this feature." +msgstr "" +"Utilisez le système openpilot pour l'ACC et l'assistance au maintien de " +"voie. Votre attention est requise en permanence pour utiliser cette " +"fonctionnalité." + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:144 +msgid "VEHICLE" +msgstr "VÉHICULE" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:67 +#, python-format +msgid "VIEW" +msgstr "VOIR" + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:52 +#, python-format +msgid "Waiting to start" +msgstr "En attente de démarrage" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:19 +msgid "" +"Warning: This grants SSH access to all public keys in your GitHub settings. " +"Never enter a GitHub username other than your own. A comma employee will " +"NEVER ask you to add their GitHub username." +msgstr "" +"Avertissement : Ceci accorde un accès SSH à toutes les clés publiques dans " +"vos paramètres GitHub. N'entrez jamais un nom d'utilisateur GitHub autre que " +"le vôtre. Un employé comma ne vous demandera JAMAIS d'ajouter son nom " +"d'utilisateur GitHub." + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:111 +#, python-format +msgid "Welcome to openpilot" +msgstr "Bienvenue sur openpilot" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:20 +msgid "When enabled, pressing the accelerator pedal will disengage openpilot." +msgstr "" +"Lorsque activé, appuyer sur la pédale d'accélérateur désengagera openpilot." + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:44 +msgid "Wi-Fi" +msgstr "Wi‑Fi" + +#: /home/batman/openpilot/system/ui/widgets/network.py:144 +#, python-format +msgid "Wi-Fi Network Metered" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#, python-format +msgid "Wrong password" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:145 +#, python-format +msgid "You must accept the Terms and Conditions in order to use openpilot." +msgstr "Vous devez accepter les conditions générales pour utiliser openpilot." + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:112 +#, python-format +msgid "" +"You must accept the Terms and Conditions to use openpilot. Read the latest " +"terms at https://comma.ai/terms before continuing." +msgstr "" +"Vous devez accepter les conditions générales pour utiliser openpilot. Lisez " +"les dernières conditions sur https://comma.ai/terms avant de continuer." + +#: /home/batman/openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:34 +#, python-format +msgid "camera starting" +msgstr "démarrage de la caméra" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:63 +#, python-format +msgid "comma prime" +msgstr "comma prime" + +#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#, python-format +msgid "default" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:132 +#, python-format +msgid "down" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:105 +#, python-format +msgid "failed to check for update" +msgstr "échec de la vérification de mise à jour" + +#: /home/batman/openpilot/system/ui/widgets/network.py:237 +#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#, python-format +msgid "for \"{}\"" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:177 +#, python-format +msgid "km/h" +msgstr "km/h" + +#: /home/batman/openpilot/system/ui/widgets/network.py:204 +#, python-format +msgid "leave blank for automatic configuration" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#, python-format +msgid "left" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#, python-format +msgid "metered" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:177 +#, python-format +msgid "mph" +msgstr "mph" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:19 +#, python-format +msgid "never" +msgstr "jamais" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:30 +#, python-format +msgid "now" +msgstr "maintenant" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:71 +#, python-format +msgid "openpilot Longitudinal Control (Alpha)" +msgstr "Contrôle longitudinal openpilot (Alpha)" + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:51 +#, python-format +msgid "openpilot Unavailable" +msgstr "openpilot indisponible" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:158 +#, python-format +msgid "" +"openpilot defaults to driving in chill mode. Experimental mode enables alpha-" +"level features that aren't ready for chill mode. Experimental features are " +"listed below:

End-to-End Longitudinal Control


Let the driving " +"model control the gas and brakes. openpilot will drive as it thinks a human " +"would, including stopping for red lights and stop signs. Since the driving " +"model decides the speed to drive, the set speed will only act as an upper " +"bound. This is an alpha quality feature; mistakes should be expected." +"

New Driving Visualization


The driving visualization will " +"transition to the road-facing wide-angle camera at low speeds to better show " +"some turns. The Experimental mode logo will also be shown in the top right " +"corner." +msgstr "" +"openpilot roule par défaut en mode chill. Le mode expérimental active des " +"fonctionnalités de niveau alpha qui ne sont pas prêtes pour le mode chill. " +"Les fonctionnalités expérimentales sont listées ci‑dessous:

Contrôle " +"longitudinal de bout en bout


Laissez le modèle de conduite contrôler " +"l'accélérateur et les freins. openpilot conduira comme il pense qu'un humain " +"le ferait, y compris s'arrêter aux feux rouges et aux panneaux stop. Comme " +"le modèle décide de la vitesse à adopter, la vitesse réglée n'agira que " +"comme une limite supérieure. C'est une fonctionnalité de qualité alpha ; des " +"erreurs sont à prévoir.

Nouvelle visualisation de conduite


La " +"visualisation passera à la caméra grand angle orientée route à basse vitesse " +"pour mieux montrer certains virages. Le logo du mode expérimental sera " +"également affiché en haut à droite." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:164 +#, python-format +msgid "" +"openpilot is continuously calibrating, resetting is rarely required. " +"Resetting calibration will restart openpilot if the car is powered on." +msgstr "" +" La modification de ce réglage redémarrera openpilot si la voiture est sous " +"tension." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:20 +msgid "" +"openpilot learns to drive by watching humans, like you, drive.\n" +"\n" +"Firehose Mode allows you to maximize your training data uploads to improve " +"openpilot's driving models. More data means bigger models, which means " +"better Experimental Mode." +msgstr "" +"openpilot apprend à conduire en regardant des humains, comme vous, " +"conduire.\n" +"\n" +"Le Mode Firehose vous permet de maximiser vos envois de données " +"d'entraînement pour améliorer les modèles de conduite d'openpilot. Plus de " +"données signifie des modèles plus grands, ce qui signifie un meilleur Mode " +"expérimental." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:183 +#, python-format +msgid "openpilot longitudinal control may come in a future update." +msgstr "" +"Le contrôle longitudinal openpilot pourra arriver dans une future mise à " +"jour." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:26 +msgid "" +"openpilot requires the device to be mounted within 4° left or right and " +"within 5° up or 9° down." +msgstr "" +"openpilot exige que l'appareil soit monté à moins de 4° à gauche ou à droite " +"et à moins de 5° vers le haut ou 9° vers le bas." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#, python-format +msgid "right" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#, python-format +msgid "unmetered" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:132 +#, python-format +msgid "up" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:116 +#, python-format +msgid "up to date, last checked never" +msgstr "à jour, dernière vérification jamais" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:114 +#, python-format +msgid "up to date, last checked {}" +msgstr "à jour, dernière vérification {}" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:108 +#, python-format +msgid "update available" +msgstr "mise à jour disponible" + +#: /home/batman/openpilot/selfdrive/ui/layouts/home.py:169 +#, python-format +msgid "{} ALERT" +msgid_plural "{} ALERTS" +msgstr[0] "{} ALERTE" +msgstr[1] "{} ALERTES" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:39 +#, python-format +msgid "{} day ago" +msgid_plural "{} days ago" +msgstr[0] "il y a {} jour" +msgstr[1] "il y a {} jours" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:36 +#, python-format +msgid "{} hour ago" +msgid_plural "{} hours ago" +msgstr[0] "il y a {} heure" +msgstr[1] "il y a {} heures" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:33 +#, python-format +msgid "{} minute ago" +msgid_plural "{} minutes ago" +msgstr[0] "il y a {} minute" +msgstr[1] "il y a {} minutes" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:111 +#, python-format +msgid "{} segment of your driving is in the training dataset so far." +msgid_plural "{} segments of your driving is in the training dataset so far." +msgstr[0] "" +"{} segment de votre conduite est dans l'ensemble d'entraînement jusqu'à " +"présent." +msgstr[1] "" +"{} segments de votre conduite sont dans l'ensemble d'entraînement jusqu'à " +"présent." + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:62 +#, python-format +msgid "✓ SUBSCRIBED" +msgstr "✓ ABONNÉ" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:22 +#, python-format +msgid "🔥 Firehose Mode 🔥" +msgstr "🔥 Mode Firehose 🔥" diff --git a/selfdrive/ui/translations/app_pt-BR.po b/selfdrive/ui/translations/app_pt-BR.po new file mode 100644 index 000000000..1829dccd8 --- /dev/null +++ b/selfdrive/ui/translations/app_pt-BR.po @@ -0,0 +1,1220 @@ +# Language pt-BR translations for PACKAGE package. +# Copyright (C) 2025 THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# Automatically generated, 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-10-22 15:30-0700\n" +"PO-Revision-Date: 2025-10-21 00:00-0700\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: pt-BR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:159 +#, python-format +msgid " Steering torque response calibration is complete." +msgstr " A calibração da resposta de torque da direção foi concluída." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:157 +#, python-format +msgid " Steering torque response calibration is {}% complete." +msgstr " A calibração da resposta de torque da direção está {}% concluída." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:132 +#, python-format +msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." +msgstr " Seu dispositivo está apontado {:.1f}° {} e {:.1f}° {}." + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:43 +msgid "--" +msgstr "--" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "1 year of drive storage" +msgstr "1 ano de armazenamento de condução" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "24/7 LTE connectivity" +msgstr "Conectividade LTE 24/7" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:46 +msgid "2G" +msgstr "2G" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:47 +msgid "3G" +msgstr "3G" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:49 +msgid "5G" +msgstr "5G" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:23 +msgid "" +"WARNING: openpilot longitudinal control is in alpha for this car and will " +"disable Automatic Emergency Braking (AEB).

On this car, openpilot " +"defaults to the car's built-in ACC instead of openpilot's longitudinal " +"control. Enable this to switch to openpilot longitudinal control. Enabling " +"Experimental mode is recommended when enabling openpilot longitudinal " +"control alpha. Changing this setting will restart openpilot if the car is " +"powered on." +msgstr "" +"AVISO: o controle longitudinal do openpilot está em alpha para este carro " +"e desativará a Frenagem Automática de Emergência (AEB).

Neste " +"carro, o openpilot usa por padrão o ACC integrado do carro em vez do " +"controle longitudinal do openpilot. Ative isto para alternar para o controle " +"longitudinal do openpilot. Recomenda-se ativar o Modo Experimental ao ativar " +"o controle longitudinal do openpilot em alpha." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:147 +#, python-format +msgid "

Steering lag calibration is complete." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:145 +#, python-format +msgid "

Steering lag calibration is {}% complete." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:138 +#, python-format +msgid "ACTIVE" +msgstr "ATIVO" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:15 +msgid "" +"ADB (Android Debug Bridge) allows connecting to your device over USB or over " +"the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." +msgstr "" +"ADB (Android Debug Bridge) permite conectar ao seu dispositivo via USB ou " +"pela rede. Veja https://docs.comma.ai/how-to/connect-to-comma para mais " +"informações." + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:30 +msgid "ADD" +msgstr "ADICIONAR" + +#: /home/batman/openpilot/system/ui/widgets/network.py:139 +#, python-format +msgid "APN Setting" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 +#, python-format +msgid "Acknowledge Excessive Actuation" +msgstr "Reconhecer Atuação Excessiva" + +#: /home/batman/openpilot/system/ui/widgets/network.py:74 +#: /home/batman/openpilot/system/ui/widgets/network.py:95 +#, python-format +msgid "Advanced" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#, python-format +msgid "Aggressive" +msgstr "Agressivo" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:116 +#, python-format +msgid "Agree" +msgstr "Concordo" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:70 +#, python-format +msgid "Always-On Driver Monitoring" +msgstr "Monitoramento de Motorista Sempre Ativo" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:186 +#, python-format +msgid "" +"An alpha version of openpilot longitudinal control can be tested, along with " +"Experimental mode, on non-release branches." +msgstr "" +"Uma versão alpha do controle longitudinal do openpilot pode ser testada, " +"junto com o Modo Experimental, em ramificações fora de release." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:186 +#, python-format +msgid "Are you sure you want to power off?" +msgstr "Tem certeza de que deseja desligar?" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:174 +#, python-format +msgid "Are you sure you want to reboot?" +msgstr "Tem certeza de que deseja reiniciar?" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:118 +#, python-format +msgid "Are you sure you want to reset calibration?" +msgstr "Tem certeza de que deseja redefinir a calibração?" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:158 +#, python-format +msgid "Are you sure you want to uninstall?" +msgstr "Tem certeza de que deseja desinstalar?" + +#: /home/batman/openpilot/system/ui/widgets/network.py:99 +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:147 +#, python-format +msgid "Back" +msgstr "Voltar" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:38 +#, python-format +msgid "Become a comma prime member at connect.comma.ai" +msgstr "Torne-se membro comma prime em connect.comma.ai" + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:130 +#, python-format +msgid "Bookmark connect.comma.ai to your home screen to use it like an app" +msgstr "Adicione connect.comma.ai à tela inicial para usá-lo como um app" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:68 +#, python-format +msgid "CHANGE" +msgstr "ALTERAR" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:49 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:106 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:117 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:142 +#, python-format +msgid "CHECK" +msgstr "VERIFICAR" + +#: /home/batman/openpilot/selfdrive/ui/widgets/exp_mode_button.py:50 +#, python-format +msgid "CHILL MODE ON" +msgstr "MODO CHILL ATIVO" + +#: /home/batman/openpilot/system/ui/widgets/network.py:155 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:138 +#, python-format +msgid "CONNECT" +msgstr "CONECTAR" + +#: /home/batman/openpilot/system/ui/widgets/network.py:369 +#, python-format +msgid "CONNECTING..." +msgstr "CONECTAR" + +#: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:23 +#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:34 +#: /home/batman/openpilot/system/ui/widgets/keyboard.py:81 +#: /home/batman/openpilot/system/ui/widgets/network.py:318 +#, python-format +msgid "Cancel" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:134 +#, python-format +msgid "Cellular Metered" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:68 +#, python-format +msgid "Change Language" +msgstr "Alterar Idioma" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:125 +#, python-format +msgid "Changing this setting will restart openpilot if the car is powered on." +msgstr "" +" Alterar esta configuração reiniciará o openpilot se o carro estiver ligado." + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:129 +#, python-format +msgid "Click \"add new device\" and scan the QR code on the right" +msgstr "Toque em \"adicionar novo dispositivo\" e escaneie o QR code à direita" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 +#, python-format +msgid "Close" +msgstr "Fechar" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:48 +#, python-format +msgid "Current Version" +msgstr "Versão Atual" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:109 +#, python-format +msgid "DOWNLOAD" +msgstr "BAIXAR" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:115 +#, python-format +msgid "Decline" +msgstr "Recusar" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:148 +#, python-format +msgid "Decline, uninstall openpilot" +msgstr "Recusar, desinstalar o openpilot" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:67 +msgid "Developer" +msgstr "Desenvolvedor" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:62 +msgid "Device" +msgstr "Dispositivo" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:58 +#, python-format +msgid "Disengage on Accelerator Pedal" +msgstr "Desativar ao pressionar o acelerador" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:183 +#, python-format +msgid "Disengage to Power Off" +msgstr "Desativar para Desligar" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:171 +#, python-format +msgid "Disengage to Reboot" +msgstr "Desativar para Reiniciar" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:102 +#, python-format +msgid "Disengage to Reset Calibration" +msgstr "Desativar para Redefinir Calibração" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:32 +msgid "Display speed in km/h instead of mph." +msgstr "Exibir velocidade em km/h em vez de mph." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:59 +#, python-format +msgid "Dongle ID" +msgstr "ID do Dongle" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:49 +#, python-format +msgid "Download" +msgstr "Baixar" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:62 +#, python-format +msgid "Driver Camera" +msgstr "Câmera do Motorista" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:96 +#, python-format +msgid "Driving Personality" +msgstr "Personalidade de Condução" + +#: /home/batman/openpilot/system/ui/widgets/network.py:123 +#: /home/batman/openpilot/system/ui/widgets/network.py:139 +#, python-format +msgid "EDIT" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:138 +msgid "ERROR" +msgstr "ERRO" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:45 +msgid "ETH" +msgstr "ETH" + +#: /home/batman/openpilot/selfdrive/ui/widgets/exp_mode_button.py:50 +#, python-format +msgid "EXPERIMENTAL MODE ON" +msgstr "MODO EXPERIMENTAL ATIVO" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:166 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:228 +#, python-format +msgid "Enable" +msgstr "Ativar" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:39 +#, python-format +msgid "Enable ADB" +msgstr "Ativar ADB" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:64 +#, python-format +msgid "Enable Lane Departure Warnings" +msgstr "Ativar alertas de saída de faixa" + +#: /home/batman/openpilot/system/ui/widgets/network.py:129 +#, python-format +msgid "Enable Roaming" +msgstr "Ativar openpilot" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:48 +#, python-format +msgid "Enable SSH" +msgstr "Ativar SSH" + +#: /home/batman/openpilot/system/ui/widgets/network.py:120 +#, python-format +msgid "Enable Tethering" +msgstr "Ativar alertas de saída de faixa" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:30 +msgid "Enable driver monitoring even when openpilot is not engaged." +msgstr "" +"Ativar monitoramento do motorista mesmo quando o openpilot não está engajado." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:46 +#, python-format +msgid "Enable openpilot" +msgstr "Ativar openpilot" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:189 +#, python-format +msgid "" +"Enable the openpilot longitudinal control (alpha) toggle to allow " +"Experimental mode." +msgstr "" +"Ative a opção de controle longitudinal do openpilot (alpha) para permitir o " +"Modo Experimental." + +#: /home/batman/openpilot/system/ui/widgets/network.py:204 +#, python-format +msgid "Enter APN" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:241 +#, python-format +msgid "Enter SSID" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:254 +#, python-format +msgid "Enter new tethering password" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:237 +#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#, python-format +msgid "Enter password" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:89 +#, python-format +msgid "Enter your GitHub username" +msgstr "Digite seu nome de usuário do GitHub" + +#: /home/batman/openpilot/system/ui/widgets/list_view.py:115 +#: /home/batman/openpilot/system/ui/widgets/list_view.py:153 +#, python-format +msgid "Error" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:52 +#, python-format +msgid "Experimental Mode" +msgstr "Modo Experimental" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:181 +#, python-format +msgid "" +"Experimental mode is currently unavailable on this car since the car's stock " +"ACC is used for longitudinal control." +msgstr "" +"O Modo Experimental está indisponível neste carro pois o ACC original do " +"carro é usado para controle longitudinal." + +#: /home/batman/openpilot/system/ui/widgets/network.py:373 +#, python-format +msgid "FORGETTING..." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:44 +#, python-format +msgid "Finish Setup" +msgstr "Concluir Configuração" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:66 +msgid "Firehose" +msgstr "Firehose" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:18 +msgid "Firehose Mode" +msgstr "Modo Firehose" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:25 +msgid "" +"For maximum effectiveness, bring your device inside and connect to a good " +"USB-C adapter and Wi-Fi weekly.\n" +"\n" +"Firehose Mode can also work while you're driving if connected to a hotspot " +"or unlimited SIM card.\n" +"\n" +"\n" +"Frequently Asked Questions\n" +"\n" +"Does it matter how or where I drive? Nope, just drive as you normally " +"would.\n" +"\n" +"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a " +"subset of your segments.\n" +"\n" +"What's a good USB-C adapter? Any fast phone or laptop charger should be " +"fine.\n" +"\n" +"Does it matter which software I run? Yes, only upstream openpilot (and " +"particular forks) are able to be used for training." +msgstr "" +"Para máxima efetividade, leve seu dispositivo para dentro e conecte a um bom " +"adaptador USB-C e Wi‑Fi semanalmente.\n" +"\n" +"O Modo Firehose também pode funcionar enquanto você dirige se estiver " +"conectado a um hotspot ou a um SIM ilimitado.\n" +"\n" +"\n" +"Perguntas Frequentes\n" +"\n" +"Importa como ou onde eu dirijo? Não, apenas dirija como normalmente.\n" +"\n" +"Todos os meus segmentos são puxados no Modo Firehose? Não, puxamos " +"seletivamente um subconjunto dos seus segmentos.\n" +"\n" +"Qual é um bom adaptador USB‑C? Qualquer carregador rápido de telefone ou " +"laptop serve.\n" +"\n" +"Importa qual software eu executo? Sim, apenas o openpilot upstream (e forks " +"específicos) podem ser usados para treinamento." + +#: /home/batman/openpilot/system/ui/widgets/network.py:318 +#: /home/batman/openpilot/system/ui/widgets/network.py:451 +#, python-format +msgid "Forget" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:319 +#, python-format +msgid "Forget Wi-Fi Network \"{}\"?" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:125 +msgid "GOOD" +msgstr "BOM" + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:128 +#, python-format +msgid "Go to https://connect.comma.ai on your phone" +msgstr "Acesse https://connect.comma.ai no seu telefone" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:129 +msgid "HIGH" +msgstr "ALTO" + +#: /home/batman/openpilot/system/ui/widgets/network.py:155 +#, python-format +msgid "Hidden Network" +msgstr "Rede" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:140 +#, python-format +msgid "INACTIVE: connect to an unmetered network" +msgstr "INATIVO: conecte a uma rede sem franquia" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:52 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:131 +#, python-format +msgid "INSTALL" +msgstr "INSTALAR" + +#: /home/batman/openpilot/system/ui/widgets/network.py:150 +#, python-format +msgid "IP Address" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:52 +#, python-format +msgid "Install Update" +msgstr "Instalar Atualização" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:56 +#, python-format +msgid "Joystick Debug Mode" +msgstr "Modo de Depuração do Joystick" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:29 +msgid "LOADING" +msgstr "CARREGANDO" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:48 +msgid "LTE" +msgstr "LTE" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:64 +#, python-format +msgid "Longitudinal Maneuver Mode" +msgstr "Modo de Manobra Longitudinal" + +#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:148 +#, python-format +msgid "MAX" +msgstr "MÁX" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:75 +#, python-format +msgid "" +"Maximize your training data uploads to improve openpilot's driving models." +msgstr "" +"Maximize seus envios de dados de treinamento para melhorar os modelos de " +"condução do openpilot." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:59 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:60 +#, python-format +msgid "N/A" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:142 +msgid "NO" +msgstr "NÃO" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:63 +msgid "Network" +msgstr "Rede" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:114 +#, python-format +msgid "No SSH keys found" +msgstr "Nenhuma chave SSH encontrada" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:126 +#, python-format +msgid "No SSH keys found for user '{}'" +msgstr "Nenhuma chave SSH encontrada para o usuário '{username}'" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:320 +#, python-format +msgid "No release notes available." +msgstr "Sem notas de versão disponíveis." + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:134 +msgid "OFFLINE" +msgstr "OFFLINE" + +#: /home/batman/openpilot/system/ui/widgets/html_render.py:247 +#: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:93 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:127 +#, python-format +msgid "OK" +msgstr "OK" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:144 +msgid "ONLINE" +msgstr "ONLINE" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:20 +#, python-format +msgid "Open" +msgstr "Abrir" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:48 +#, python-format +msgid "PAIR" +msgstr "EMPARELHAR" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:142 +msgid "PANDA" +msgstr "PANDA" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:62 +#, python-format +msgid "PREVIEW" +msgstr "PRÉVIA" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:44 +#, python-format +msgid "PRIME FEATURES:" +msgstr "RECURSOS PRIME:" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:48 +#, python-format +msgid "Pair Device" +msgstr "Emparelhar Dispositivo" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:19 +#, python-format +msgid "Pair device" +msgstr "Emparelhar dispositivo" + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:103 +#, python-format +msgid "Pair your device to your comma account" +msgstr "Emparelhe seu dispositivo à sua conta comma" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:48 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:24 +#, python-format +msgid "" +"Pair your device with comma connect (connect.comma.ai) and claim your comma " +"prime offer." +msgstr "" +"Emparelhe seu dispositivo com o comma connect (connect.comma.ai) e resgate " +"sua oferta comma prime." + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:91 +#, python-format +msgid "Please connect to Wi-Fi to complete initial pairing" +msgstr "Conecte-se ao Wi‑Fi para concluir o emparelhamento inicial" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:186 +#, python-format +msgid "Power Off" +msgstr "Desligar" + +#: /home/batman/openpilot/system/ui/widgets/network.py:144 +#, python-format +msgid "Prevent large data uploads when on a metered Wi-Fi connection" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:135 +#, python-format +msgid "Prevent large data uploads when on a metered cellular connection" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:25 +msgid "" +"Preview the driver facing camera to ensure that driver monitoring has good " +"visibility. (vehicle must be off)" +msgstr "" +"Pré-visualize a câmera voltada para o motorista para garantir que o " +"monitoramento do motorista tenha boa visibilidade. (veículo deve estar " +"desligado)" + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:161 +#, python-format +msgid "QR Code Error" +msgstr "Erro no QR Code" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:31 +msgid "REMOVE" +msgstr "REMOVER" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:51 +#, python-format +msgid "RESET" +msgstr "REDEFINIR" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:65 +#, python-format +msgid "REVIEW" +msgstr "REVISAR" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:174 +#, python-format +msgid "Reboot" +msgstr "Reiniciar" + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:66 +#, python-format +msgid "Reboot Device" +msgstr "Reiniciar Dispositivo" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 +#, python-format +msgid "Reboot and Update" +msgstr "Reiniciar e Atualizar" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:27 +msgid "" +"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)." +msgstr "" +"Receba alertas para voltar à faixa quando seu veículo cruzar uma linha de " +"faixa detectada sem seta ativada ao dirigir acima de 31 mph (50 km/h)." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:76 +#, python-format +msgid "Record and Upload Driver Camera" +msgstr "Gravar e Enviar Câmera do Motorista" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:82 +#, python-format +msgid "Record and Upload Microphone Audio" +msgstr "Gravar e Enviar Áudio do Microfone" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +msgid "" +"Record and store microphone audio while driving. The audio will be included " +"in the dashcam video in comma connect." +msgstr "" +"Grave e armazene o áudio do microfone enquanto dirige. O áudio será incluído " +"no vídeo da dashcam no comma connect." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:67 +#, python-format +msgid "Regulatory" +msgstr "Regulatório" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#, python-format +msgid "Relaxed" +msgstr "Relaxado" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "Remote access" +msgstr "Acesso remoto" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "Remote snapshots" +msgstr "Capturas remotas" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:123 +#, python-format +msgid "Request timed out" +msgstr "Tempo da solicitação esgotado" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:118 +#, python-format +msgid "Reset" +msgstr "Redefinir" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:51 +#, python-format +msgid "Reset Calibration" +msgstr "Redefinir Calibração" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:65 +#, python-format +msgid "Review Training Guide" +msgstr "Revisar Guia de Treinamento" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:27 +msgid "Review the rules, features, and limitations of openpilot" +msgstr "Revise as regras, recursos e limitações do openpilot" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:53 +#, python-format +msgid "SSH Keys" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:310 +#, python-format +msgid "Scanning Wi-Fi networks..." +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:35 +#, python-format +msgid "Select" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:91 +#, python-format +msgid "Select a language" +msgstr "Selecione um idioma" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:60 +#, python-format +msgid "Serial" +msgstr "Serial" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 +#, python-format +msgid "Snooze Update" +msgstr "Adiar Atualização" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:65 +msgid "Software" +msgstr "Software" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#, python-format +msgid "Standard" +msgstr "Padrão" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:22 +msgid "" +"Standard is recommended. In aggressive mode, openpilot will follow lead cars " +"closer and be more aggressive with the gas and brake. In relaxed mode " +"openpilot will stay further away from lead cars. On supported cars, you can " +"cycle through these personalities with your steering wheel distance button." +msgstr "" +"Padrão é recomendado. No modo agressivo, o openpilot seguirá veículos à " +"frente mais de perto e será mais agressivo com acelerador e freio. No modo " +"relaxado, o openpilot ficará mais longe dos veículos à frente. Em carros " +"compatíveis, você pode alternar essas personalidades com o botão de " +"distância do volante." + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:59 +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:65 +#, python-format +msgid "System Unresponsive" +msgstr "Sistema sem resposta" + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:58 +#, python-format +msgid "TAKE CONTROL IMMEDIATELY" +msgstr "ASSUMA O CONTROLE IMEDIATAMENTE" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:129 +msgid "TEMP" +msgstr "TEMP" + +#: /home/batman/openpilot/system/ui/widgets/network.py:124 +#, python-format +msgid "Tethering Password" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:64 +msgid "Toggles" +msgstr "Alternâncias" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:70 +#, python-format +msgid "UNINSTALL" +msgstr "DESINSTALAR" + +#: /home/batman/openpilot/selfdrive/ui/layouts/home.py:155 +#, python-format +msgid "UPDATE" +msgstr "ATUALIZAR" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:70 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:158 +#, python-format +msgid "Uninstall" +msgstr "Desinstalar" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:117 +msgid "Unknown" +msgstr "Desconhecido" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:47 +#, python-format +msgid "Updates are only downloaded while the car is off." +msgstr "Atualizações são baixadas apenas com o carro desligado." + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:33 +#, python-format +msgid "Upgrade Now" +msgstr "Atualizar Agora" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +msgid "" +"Upload data from the driver facing camera and help improve the driver " +"monitoring algorithm." +msgstr "" +"Envie dados da câmera voltada para o motorista e ajude a melhorar o " +"algoritmo de monitoramento do motorista." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:88 +#, python-format +msgid "Use Metric System" +msgstr "Usar Sistema Métrico" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:17 +msgid "" +"Use the openpilot system for adaptive cruise control and lane keep driver " +"assistance. Your attention is required at all times to use this feature." +msgstr "" +"Use o sistema openpilot para controle de cruzeiro adaptativo e assistência " +"de permanência em faixa. Sua atenção é necessária o tempo todo para usar " +"este recurso." + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:144 +msgid "VEHICLE" +msgstr "VEÍCULO" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:67 +#, python-format +msgid "VIEW" +msgstr "VER" + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:52 +#, python-format +msgid "Waiting to start" +msgstr "Aguardando para iniciar" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:19 +msgid "" +"Warning: This grants SSH access to all public keys in your GitHub settings. " +"Never enter a GitHub username other than your own. A comma employee will " +"NEVER ask you to add their GitHub username." +msgstr "" +"Aviso: Isso concede acesso SSH a todas as chaves públicas nas suas " +"configurações do GitHub. Nunca informe um nome de usuário do GitHub que não " +"seja o seu. Um funcionário da comma NUNCA pedirá para você adicionar o nome " +"de usuário do GitHub dele." + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:111 +#, python-format +msgid "Welcome to openpilot" +msgstr "Bem-vindo ao openpilot" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:20 +msgid "When enabled, pressing the accelerator pedal will disengage openpilot." +msgstr "" +"Quando ativado, pressionar o pedal do acelerador desengajará o openpilot." + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:44 +msgid "Wi-Fi" +msgstr "Wi‑Fi" + +#: /home/batman/openpilot/system/ui/widgets/network.py:144 +#, python-format +msgid "Wi-Fi Network Metered" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#, python-format +msgid "Wrong password" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:145 +#, python-format +msgid "You must accept the Terms and Conditions in order to use openpilot." +msgstr "Você deve aceitar os Termos e Condições para usar o openpilot." + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:112 +#, python-format +msgid "" +"You must accept the Terms and Conditions to use openpilot. Read the latest " +"terms at https://comma.ai/terms before continuing." +msgstr "" +"Você deve aceitar os Termos e Condições para usar o openpilot. Leia os " +"termos mais recentes em https://comma.ai/terms antes de continuar." + +#: /home/batman/openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:34 +#, python-format +msgid "camera starting" +msgstr "câmera iniciando" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:63 +#, python-format +msgid "comma prime" +msgstr "comma prime" + +#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#, python-format +msgid "default" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:132 +#, python-format +msgid "down" +msgstr "para baixo" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:105 +#, python-format +msgid "failed to check for update" +msgstr "falha ao verificar atualização" + +#: /home/batman/openpilot/system/ui/widgets/network.py:237 +#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#, python-format +msgid "for \"{}\"" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:177 +#, python-format +msgid "km/h" +msgstr "km/h" + +#: /home/batman/openpilot/system/ui/widgets/network.py:204 +#, python-format +msgid "leave blank for automatic configuration" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#, python-format +msgid "left" +msgstr "à esquerda" + +#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#, python-format +msgid "metered" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:177 +#, python-format +msgid "mph" +msgstr "mph" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:19 +#, python-format +msgid "never" +msgstr "nunca" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:30 +#, python-format +msgid "now" +msgstr "agora" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:71 +#, python-format +msgid "openpilot Longitudinal Control (Alpha)" +msgstr "Controle Longitudinal do openpilot (Alpha)" + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:51 +#, python-format +msgid "openpilot Unavailable" +msgstr "openpilot Indisponível" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:158 +#, python-format +msgid "" +"openpilot defaults to driving in chill mode. Experimental mode enables alpha-" +"level features that aren't ready for chill mode. Experimental features are " +"listed below:

End-to-End Longitudinal Control


Let the driving " +"model control the gas and brakes. openpilot will drive as it thinks a human " +"would, including stopping for red lights and stop signs. Since the driving " +"model decides the speed to drive, the set speed will only act as an upper " +"bound. This is an alpha quality feature; mistakes should be expected." +"

New Driving Visualization


The driving visualization will " +"transition to the road-facing wide-angle camera at low speeds to better show " +"some turns. The Experimental mode logo will also be shown in the top right " +"corner." +msgstr "" +"o openpilot dirige por padrão no modo chill. O Modo Experimental habilita " +"recursos em nível alpha que não estão prontos para o modo chill. Os recursos " +"experimentais são listados abaixo:

Controle Longitudinal End-to-End
Permita que o modelo de condução controle o acelerador e os freios. O " +"openpilot dirigirá como acha que um humano faria, incluindo parar em sinais " +"e semáforos vermelhos. Como o modelo decide a velocidade, a velocidade " +"definida atuará apenas como limite superior. Este é um recurso de qualidade " +"alpha; erros devem ser esperados.

Nova Visualização de Condução
A visualização de condução mudará para a câmera grande-angular " +"voltada para a estrada em baixas velocidades para mostrar melhor algumas " +"curvas. O logotipo do Modo Experimental também será exibido no canto " +"superior direito." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:164 +#, python-format +msgid "" +"openpilot is continuously calibrating, resetting is rarely required. " +"Resetting calibration will restart openpilot if the car is powered on." +msgstr "" +" Alterar esta configuração reiniciará o openpilot se o carro estiver ligado." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:20 +msgid "" +"openpilot learns to drive by watching humans, like you, drive.\n" +"\n" +"Firehose Mode allows you to maximize your training data uploads to improve " +"openpilot's driving models. More data means bigger models, which means " +"better Experimental Mode." +msgstr "" +"o openpilot aprende a dirigir observando humanos, como você, dirigirem.\n" +"\n" +"O Modo Firehose permite maximizar seus envios de dados de treinamento para " +"melhorar os modelos de condução do openpilot. Mais dados significam modelos " +"maiores, o que significa um Modo Experimental melhor." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:183 +#, python-format +msgid "openpilot longitudinal control may come in a future update." +msgstr "" +"o controle longitudinal do openpilot pode vir em uma atualização futura." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:26 +msgid "" +"openpilot requires the device to be mounted within 4° left or right and " +"within 5° up or 9° down." +msgstr "" +"o openpilot requer que o dispositivo seja montado dentro de 4° para a " +"esquerda ou direita e dentro de 5° para cima ou 9° para baixo." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#, python-format +msgid "right" +msgstr "à direita" + +#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#, python-format +msgid "unmetered" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:132 +#, python-format +msgid "up" +msgstr "para cima" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:116 +#, python-format +msgid "up to date, last checked never" +msgstr "atualizado, última verificação: nunca" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:114 +#, python-format +msgid "up to date, last checked {}" +msgstr "atualizado, última verificação: {}" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:108 +#, python-format +msgid "update available" +msgstr "atualização disponível" + +#: /home/batman/openpilot/selfdrive/ui/layouts/home.py:169 +#, python-format +msgid "{} ALERT" +msgid_plural "{} ALERTS" +msgstr[0] "{} ALERTA" +msgstr[1] "{} ALERTAS" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:39 +#, python-format +msgid "{} day ago" +msgid_plural "{} days ago" +msgstr[0] "{} dia atrás" +msgstr[1] "{} dias atrás" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:36 +#, python-format +msgid "{} hour ago" +msgid_plural "{} hours ago" +msgstr[0] "{} hora atrás" +msgstr[1] "{} horas atrás" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:33 +#, python-format +msgid "{} minute ago" +msgid_plural "{} minutes ago" +msgstr[0] "{} minuto atrás" +msgstr[1] "{} minutos atrás" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:111 +#, python-format +msgid "{} segment of your driving is in the training dataset so far." +msgid_plural "{} segments of your driving is in the training dataset so far." +msgstr[0] "" +"{} segmento da sua condução está no conjunto de treinamento até agora." +msgstr[1] "" +"{} segmentos da sua condução estão no conjunto de treinamento até agora." + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:62 +#, python-format +msgid "✓ SUBSCRIBED" +msgstr "✓ ASSINADO" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:22 +#, python-format +msgid "🔥 Firehose Mode 🔥" +msgstr "🔥 Modo Firehose 🔥" diff --git a/selfdrive/ui/translations/app_tr.po b/selfdrive/ui/translations/app_tr.po new file mode 100644 index 000000000..29d3b9b9f --- /dev/null +++ b/selfdrive/ui/translations/app_tr.po @@ -0,0 +1,1214 @@ +# Turkish translations for PACKAGE package. +# Copyright (C) 2025 THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# Automatically generated, 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-10-22 15:30-0700\n" +"PO-Revision-Date: 2025-10-20 18:19-0700\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: tr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:159 +#, python-format +msgid " Steering torque response calibration is complete." +msgstr " Direksiyon tork tepkisi kalibrasyonu tamamlandı." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:157 +#, python-format +msgid " Steering torque response calibration is {}% complete." +msgstr " Direksiyon tork tepkisi kalibrasyonu {}% tamamlandı." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:132 +#, python-format +msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." +msgstr " Cihazınız {:.1f}° {} ve {:.1f}° {} yönünde konumlandırılmış." + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:43 +msgid "--" +msgstr "--" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "1 year of drive storage" +msgstr "1 yıl sürüş depolaması" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "24/7 LTE connectivity" +msgstr "7/24 LTE bağlantısı" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:46 +msgid "2G" +msgstr "2G" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:47 +msgid "3G" +msgstr "3G" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:49 +msgid "5G" +msgstr "5G" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:23 +msgid "" +"WARNING: openpilot longitudinal control is in alpha for this car and will " +"disable Automatic Emergency Braking (AEB).

On this car, openpilot " +"defaults to the car's built-in ACC instead of openpilot's longitudinal " +"control. Enable this to switch to openpilot longitudinal control. Enabling " +"Experimental mode is recommended when enabling openpilot longitudinal " +"control alpha. Changing this setting will restart openpilot if the car is " +"powered on." +msgstr "" +"UYARI: Bu araç için openpilot boylamsal kontrolü alfa aşamasındadır ve " +"Otomatik Acil Frenlemeyi (AEB) devre dışı bırakacaktır.

Bu araçta " +"openpilot, openpilot'un boylamsal kontrolü yerine aracın yerleşik ACC'sini " +"varsayılan olarak kullanır. openpilot boylamsal kontrolüne geçmek için bunu " +"etkinleştirin. openpilot boylamsal kontrol alfayı etkinleştirirken Deneysel " +"modu etkinleştirmeniz önerilir." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:147 +#, python-format +msgid "

Steering lag calibration is complete." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:145 +#, python-format +msgid "

Steering lag calibration is {}% complete." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:138 +#, python-format +msgid "ACTIVE" +msgstr "AKTİF" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:15 +msgid "" +"ADB (Android Debug Bridge) allows connecting to your device over USB or over " +"the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." +msgstr "" +"ADB (Android Debug Bridge), cihazınıza USB veya ağ üzerinden bağlanmayı " +"sağlar. Daha fazla bilgi için https://docs.comma.ai/how-to/connect-to-comma " +"adresine bakın." + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:30 +msgid "ADD" +msgstr "EKLE" + +#: /home/batman/openpilot/system/ui/widgets/network.py:139 +#, python-format +msgid "APN Setting" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 +#, python-format +msgid "Acknowledge Excessive Actuation" +msgstr "Aşırı Müdahaleyi Onayla" + +#: /home/batman/openpilot/system/ui/widgets/network.py:74 +#: /home/batman/openpilot/system/ui/widgets/network.py:95 +#, python-format +msgid "Advanced" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#, python-format +msgid "Aggressive" +msgstr "Agresif" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:116 +#, python-format +msgid "Agree" +msgstr "Kabul et" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:70 +#, python-format +msgid "Always-On Driver Monitoring" +msgstr "Sürekli Sürücü İzleme" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:186 +#, python-format +msgid "" +"An alpha version of openpilot longitudinal control can be tested, along with " +"Experimental mode, on non-release branches." +msgstr "" +"openpilot boylamsal kontrolünün alfa sürümü, Deneysel mod ile birlikte, " +"yayın dışı dallarda test edilebilir." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:186 +#, python-format +msgid "Are you sure you want to power off?" +msgstr "Kapatmak istediğinizden emin misiniz?" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:174 +#, python-format +msgid "Are you sure you want to reboot?" +msgstr "Yeniden başlatmak istediğinizden emin misiniz?" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:118 +#, python-format +msgid "Are you sure you want to reset calibration?" +msgstr "Kalibrasyonu sıfırlamak istediğinizden emin misiniz?" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:158 +#, python-format +msgid "Are you sure you want to uninstall?" +msgstr "Kaldırmak istediğinizden emin misiniz?" + +#: /home/batman/openpilot/system/ui/widgets/network.py:99 +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:147 +#, python-format +msgid "Back" +msgstr "Geri" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:38 +#, python-format +msgid "Become a comma prime member at connect.comma.ai" +msgstr "connect.comma.ai adresinde comma prime üyesi olun" + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:130 +#, python-format +msgid "Bookmark connect.comma.ai to your home screen to use it like an app" +msgstr "" +"connect.comma.ai'yi ana ekranınıza ekleyerek bir uygulama gibi kullanın" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:68 +#, python-format +msgid "CHANGE" +msgstr "DEĞİŞTİR" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:49 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:106 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:117 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:142 +#, python-format +msgid "CHECK" +msgstr "KONTROL ET" + +#: /home/batman/openpilot/selfdrive/ui/widgets/exp_mode_button.py:50 +#, python-format +msgid "CHILL MODE ON" +msgstr "CHILL MODU AÇIK" + +#: /home/batman/openpilot/system/ui/widgets/network.py:155 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:138 +#, python-format +msgid "CONNECT" +msgstr "BAĞLAN" + +#: /home/batman/openpilot/system/ui/widgets/network.py:369 +#, python-format +msgid "CONNECTING..." +msgstr "BAĞLAN" + +#: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:23 +#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:34 +#: /home/batman/openpilot/system/ui/widgets/keyboard.py:81 +#: /home/batman/openpilot/system/ui/widgets/network.py:318 +#, python-format +msgid "Cancel" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:134 +#, python-format +msgid "Cellular Metered" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:68 +#, python-format +msgid "Change Language" +msgstr "Dili Değiştir" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:125 +#, python-format +msgid "Changing this setting will restart openpilot if the car is powered on." +msgstr "" +" Bu ayarı değiştirmek, araç çalışıyorsa openpilot'u yeniden başlatacaktır." + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:129 +#, python-format +msgid "Click \"add new device\" and scan the QR code on the right" +msgstr "\"yeni cihaz ekle\"ye tıklayın ve sağdaki QR kodunu tarayın" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 +#, python-format +msgid "Close" +msgstr "Kapat" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:48 +#, python-format +msgid "Current Version" +msgstr "Geçerli Sürüm" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:109 +#, python-format +msgid "DOWNLOAD" +msgstr "İNDİR" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:115 +#, python-format +msgid "Decline" +msgstr "Reddet" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:148 +#, python-format +msgid "Decline, uninstall openpilot" +msgstr "Reddet, openpilot'u kaldır" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:67 +msgid "Developer" +msgstr "Geliştirici" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:62 +msgid "Device" +msgstr "Cihaz" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:58 +#, python-format +msgid "Disengage on Accelerator Pedal" +msgstr "Gaz Pedalında Devreden Çık" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:183 +#, python-format +msgid "Disengage to Power Off" +msgstr "Kapatmak için Devreden Çıkın" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:171 +#, python-format +msgid "Disengage to Reboot" +msgstr "Yeniden Başlatmak için Devreden Çıkın" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:102 +#, python-format +msgid "Disengage to Reset Calibration" +msgstr "Kalibrasyonu Sıfırlamak için Devreden Çıkın" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:32 +msgid "Display speed in km/h instead of mph." +msgstr "Hızı mph yerine km/h olarak göster." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:59 +#, python-format +msgid "Dongle ID" +msgstr "Dongle ID" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:49 +#, python-format +msgid "Download" +msgstr "İndir" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:62 +#, python-format +msgid "Driver Camera" +msgstr "Sürücü Kamerası" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:96 +#, python-format +msgid "Driving Personality" +msgstr "Sürüş Kişiliği" + +#: /home/batman/openpilot/system/ui/widgets/network.py:123 +#: /home/batman/openpilot/system/ui/widgets/network.py:139 +#, python-format +msgid "EDIT" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:138 +msgid "ERROR" +msgstr "HATA" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:45 +msgid "ETH" +msgstr "ETH" + +#: /home/batman/openpilot/selfdrive/ui/widgets/exp_mode_button.py:50 +#, python-format +msgid "EXPERIMENTAL MODE ON" +msgstr "DENEYSEL MOD AÇIK" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:166 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:228 +#, python-format +msgid "Enable" +msgstr "Etkinleştir" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:39 +#, python-format +msgid "Enable ADB" +msgstr "ADB'yi Etkinleştir" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:64 +#, python-format +msgid "Enable Lane Departure Warnings" +msgstr "Şerit Terk Uyarılarını Etkinleştir" + +#: /home/batman/openpilot/system/ui/widgets/network.py:129 +#, python-format +msgid "Enable Roaming" +msgstr "openpilot'u etkinleştir" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:48 +#, python-format +msgid "Enable SSH" +msgstr "SSH'yi Etkinleştir" + +#: /home/batman/openpilot/system/ui/widgets/network.py:120 +#, python-format +msgid "Enable Tethering" +msgstr "Şerit Terk Uyarılarını Etkinleştir" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:30 +msgid "Enable driver monitoring even when openpilot is not engaged." +msgstr "openpilot devrede değilken bile sürücü izlemesini etkinleştir." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:46 +#, python-format +msgid "Enable openpilot" +msgstr "openpilot'u etkinleştir" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:189 +#, python-format +msgid "" +"Enable the openpilot longitudinal control (alpha) toggle to allow " +"Experimental mode." +msgstr "" +"Deneysel modu etkinleştirmek için openpilot boylamsal kontrolünü (alfa) açın." + +#: /home/batman/openpilot/system/ui/widgets/network.py:204 +#, python-format +msgid "Enter APN" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:241 +#, python-format +msgid "Enter SSID" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:254 +#, python-format +msgid "Enter new tethering password" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:237 +#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#, python-format +msgid "Enter password" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:89 +#, python-format +msgid "Enter your GitHub username" +msgstr "GitHub kullanıcı adınızı girin" + +#: /home/batman/openpilot/system/ui/widgets/list_view.py:115 +#: /home/batman/openpilot/system/ui/widgets/list_view.py:153 +#, python-format +msgid "Error" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:52 +#, python-format +msgid "Experimental Mode" +msgstr "Deneysel Mod" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:181 +#, python-format +msgid "" +"Experimental mode is currently unavailable on this car since the car's stock " +"ACC is used for longitudinal control." +msgstr "" +"Bu araçta boylamsal kontrol için stok ACC kullanıldığından şu anda Deneysel " +"mod kullanılamıyor." + +#: /home/batman/openpilot/system/ui/widgets/network.py:373 +#, python-format +msgid "FORGETTING..." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:44 +#, python-format +msgid "Finish Setup" +msgstr "Kurulumu Bitir" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:66 +msgid "Firehose" +msgstr "Firehose" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:18 +msgid "Firehose Mode" +msgstr "Firehose Modu" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:25 +msgid "" +"For maximum effectiveness, bring your device inside and connect to a good " +"USB-C adapter and Wi-Fi weekly.\n" +"\n" +"Firehose Mode can also work while you're driving if connected to a hotspot " +"or unlimited SIM card.\n" +"\n" +"\n" +"Frequently Asked Questions\n" +"\n" +"Does it matter how or where I drive? Nope, just drive as you normally " +"would.\n" +"\n" +"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a " +"subset of your segments.\n" +"\n" +"What's a good USB-C adapter? Any fast phone or laptop charger should be " +"fine.\n" +"\n" +"Does it matter which software I run? Yes, only upstream openpilot (and " +"particular forks) are able to be used for training." +msgstr "" +"Maksimum verim için cihazınızı içeri alın ve haftalık olarak iyi bir USB-C " +"adaptörüne ve Wi‑Fi'a bağlayın.\n" +"\n" +"Firehose Modu, bir hotspot'a veya sınırsız SIM karta bağlıyken sürüş " +"sırasında da çalışabilir.\n" +"\n" +"\n" +"Sıkça Sorulan Sorular\n" +"\n" +"Nasıl veya nerede sürdüğüm önemli mi? Hayır, normalde nasıl sürüyorsanız " +"öyle sürün.\n" +"\n" +"Firehose Modu'nda tüm segmentlerim çekiliyor mu? Hayır, segmentlerinizin bir " +"alt kümesini seçerek çekiyoruz.\n" +"\n" +"İyi bir USB‑C adaptörü nedir? Hızlı bir telefon veya dizüstü şarj cihazı " +"uygundur.\n" +"\n" +"Hangi yazılımı çalıştırdığım önemli mi? Evet, yalnızca upstream openpilot " +"(ve bazı fork'lar) eğitim için kullanılabilir." + +#: /home/batman/openpilot/system/ui/widgets/network.py:318 +#: /home/batman/openpilot/system/ui/widgets/network.py:451 +#, python-format +msgid "Forget" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:319 +#, python-format +msgid "Forget Wi-Fi Network \"{}\"?" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:125 +msgid "GOOD" +msgstr "İYİ" + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:128 +#, python-format +msgid "Go to https://connect.comma.ai on your phone" +msgstr "Telefonunuzda https://connect.comma.ai adresine gidin" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:129 +msgid "HIGH" +msgstr "YÜKSEK" + +#: /home/batman/openpilot/system/ui/widgets/network.py:155 +#, python-format +msgid "Hidden Network" +msgstr "Ağ" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:140 +#, python-format +msgid "INACTIVE: connect to an unmetered network" +msgstr "PASİF: sınırsız bir ağa bağlanın" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:52 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:131 +#, python-format +msgid "INSTALL" +msgstr "YÜKLE" + +#: /home/batman/openpilot/system/ui/widgets/network.py:150 +#, python-format +msgid "IP Address" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:52 +#, python-format +msgid "Install Update" +msgstr "Güncellemeyi Yükle" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:56 +#, python-format +msgid "Joystick Debug Mode" +msgstr "Joystick Hata Ayıklama Modu" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:29 +msgid "LOADING" +msgstr "YÜKLENİYOR" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:48 +msgid "LTE" +msgstr "LTE" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:64 +#, python-format +msgid "Longitudinal Maneuver Mode" +msgstr "Boylamsal Manevra Modu" + +#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:148 +#, python-format +msgid "MAX" +msgstr "MAKS" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:75 +#, python-format +msgid "" +"Maximize your training data uploads to improve openpilot's driving models." +msgstr "" +"openpilot'un sürüş modellerini iyileştirmek için eğitim veri yüklemelerinizi " +"en üst düzeye çıkarın." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:59 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:60 +#, python-format +msgid "N/A" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:142 +msgid "NO" +msgstr "HAYIR" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:63 +msgid "Network" +msgstr "Ağ" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:114 +#, python-format +msgid "No SSH keys found" +msgstr "SSH anahtarı bulunamadı" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:126 +#, python-format +msgid "No SSH keys found for user '{}'" +msgstr "'{username}' için SSH anahtarı bulunamadı" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:320 +#, python-format +msgid "No release notes available." +msgstr "Sürüm notu mevcut değil." + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:134 +msgid "OFFLINE" +msgstr "ÇEVRİMDIŞI" + +#: /home/batman/openpilot/system/ui/widgets/html_render.py:247 +#: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:93 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:127 +#, python-format +msgid "OK" +msgstr "OK" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:144 +msgid "ONLINE" +msgstr "ÇEVRİMİÇİ" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:20 +#, python-format +msgid "Open" +msgstr "Aç" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:48 +#, python-format +msgid "PAIR" +msgstr "EŞLE" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:142 +msgid "PANDA" +msgstr "PANDA" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:62 +#, python-format +msgid "PREVIEW" +msgstr "ÖNİZLEME" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:44 +#, python-format +msgid "PRIME FEATURES:" +msgstr "PRIME ÖZELLİKLERİ:" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:48 +#, python-format +msgid "Pair Device" +msgstr "Cihazı Eşle" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:19 +#, python-format +msgid "Pair device" +msgstr "Cihazı eşle" + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:103 +#, python-format +msgid "Pair your device to your comma account" +msgstr "Cihazınızı comma hesabınızla eşleştirin" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:48 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:24 +#, python-format +msgid "" +"Pair your device with comma connect (connect.comma.ai) and claim your comma " +"prime offer." +msgstr "" +"Cihazınızı comma connect (connect.comma.ai) ile eşleştirin ve comma prime " +"teklifinizi alın." + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:91 +#, python-format +msgid "Please connect to Wi-Fi to complete initial pairing" +msgstr "İlk eşleştirmeyi tamamlamak için lütfen Wi‑Fi'a bağlanın" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:186 +#, python-format +msgid "Power Off" +msgstr "Kapat" + +#: /home/batman/openpilot/system/ui/widgets/network.py:144 +#, python-format +msgid "Prevent large data uploads when on a metered Wi-Fi connection" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:135 +#, python-format +msgid "Prevent large data uploads when on a metered cellular connection" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:25 +msgid "" +"Preview the driver facing camera to ensure that driver monitoring has good " +"visibility. (vehicle must be off)" +msgstr "" +"Sürücü izleme görünürlüğünün iyi olduğundan emin olmak için sürücüye bakan " +"kamerayı önizleyin. (araç kapalı olmalıdır)" + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:161 +#, python-format +msgid "QR Code Error" +msgstr "QR Kod Hatası" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:31 +msgid "REMOVE" +msgstr "KALDIR" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:51 +#, python-format +msgid "RESET" +msgstr "SIFIRLA" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:65 +#, python-format +msgid "REVIEW" +msgstr "GÖZDEN GEÇİR" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:174 +#, python-format +msgid "Reboot" +msgstr "Yeniden Başlat" + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:66 +#, python-format +msgid "Reboot Device" +msgstr "Cihazı Yeniden Başlat" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 +#, python-format +msgid "Reboot and Update" +msgstr "Yeniden Başlat ve Güncelle" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:27 +msgid "" +"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)." +msgstr "" +"Araç 31 mph (50 km/h) üzerindeyken sinyal verilmeden algılanan şerit " +"çizgisini aştığınızda şeride geri dönmeniz için uyarılar alın." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:76 +#, python-format +msgid "Record and Upload Driver Camera" +msgstr "Sürücü Kamerasını Kaydet ve Yükle" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:82 +#, python-format +msgid "Record and Upload Microphone Audio" +msgstr "Mikrofon Sesini Kaydet ve Yükle" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +msgid "" +"Record and store microphone audio while driving. The audio will be included " +"in the dashcam video in comma connect." +msgstr "" +"Sürüş sırasında mikrofon sesini kaydedip saklayın. Ses, comma connect'teki " +"ön kamera videosuna dahil edilecektir." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:67 +#, python-format +msgid "Regulatory" +msgstr "Mevzuat" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#, python-format +msgid "Relaxed" +msgstr "Rahat" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "Remote access" +msgstr "Uzaktan erişim" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "Remote snapshots" +msgstr "Uzaktan anlık görüntüler" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:123 +#, python-format +msgid "Request timed out" +msgstr "İstek zaman aşımına uğradı" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:118 +#, python-format +msgid "Reset" +msgstr "Sıfırla" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:51 +#, python-format +msgid "Reset Calibration" +msgstr "Kalibrasyonu Sıfırla" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:65 +#, python-format +msgid "Review Training Guide" +msgstr "Eğitim Kılavuzunu İncele" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:27 +msgid "Review the rules, features, and limitations of openpilot" +msgstr "" +"openpilot'un kurallarını, özelliklerini ve sınırlamalarını gözden geçirin" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:53 +#, python-format +msgid "SSH Keys" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:310 +#, python-format +msgid "Scanning Wi-Fi networks..." +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:35 +#, python-format +msgid "Select" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:91 +#, python-format +msgid "Select a language" +msgstr "Bir dil seçin" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:60 +#, python-format +msgid "Serial" +msgstr "Seri" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 +#, python-format +msgid "Snooze Update" +msgstr "Güncellemeyi Ertele" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:65 +msgid "Software" +msgstr "Yazılım" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#, python-format +msgid "Standard" +msgstr "Standart" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:22 +msgid "" +"Standard is recommended. In aggressive mode, openpilot will follow lead cars " +"closer and be more aggressive with the gas and brake. In relaxed mode " +"openpilot will stay further away from lead cars. On supported cars, you can " +"cycle through these personalities with your steering wheel distance button." +msgstr "" +"Standart önerilir. Agresif modda openpilot öndeki aracı daha yakından takip " +"eder ve gaz/fren kullanımında daha ataktır. Rahat modda openpilot öndeki " +"araçlardan daha uzak durur. Desteklenen araçlarda bu kişilikler arasında " +"direksiyon mesafe düğmesiyle geçiş yapabilirsiniz." + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:59 +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:65 +#, python-format +msgid "System Unresponsive" +msgstr "Sistem Yanıt Vermiyor" + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:58 +#, python-format +msgid "TAKE CONTROL IMMEDIATELY" +msgstr "HEMEN KONTROLÜ DEVRALIN" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:129 +msgid "TEMP" +msgstr "TEMP" + +#: /home/batman/openpilot/system/ui/widgets/network.py:124 +#, python-format +msgid "Tethering Password" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:64 +msgid "Toggles" +msgstr "Seçenekler" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:70 +#, python-format +msgid "UNINSTALL" +msgstr "KALDIR" + +#: /home/batman/openpilot/selfdrive/ui/layouts/home.py:155 +#, python-format +msgid "UPDATE" +msgstr "GÜNCELLE" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:70 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:158 +#, python-format +msgid "Uninstall" +msgstr "Kaldır" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:117 +msgid "Unknown" +msgstr "Bilinmiyor" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:47 +#, python-format +msgid "Updates are only downloaded while the car is off." +msgstr "Güncellemeler yalnızca araç kapalıyken indirilir." + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:33 +#, python-format +msgid "Upgrade Now" +msgstr "Şimdi Yükselt" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +msgid "" +"Upload data from the driver facing camera and help improve the driver " +"monitoring algorithm." +msgstr "" +"Sürücüye bakan kameradan veri yükleyin ve sürücü izleme algoritmasını " +"geliştirmeye yardımcı olun." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:88 +#, python-format +msgid "Use Metric System" +msgstr "Metrik Sistemi Kullan" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:17 +msgid "" +"Use the openpilot system for adaptive cruise control and lane keep driver " +"assistance. Your attention is required at all times to use this feature." +msgstr "" +"Uyarlanabilir hız sabitleyici ve şerit koruma sürücü yardımında openpilot " +"sistemini kullanın. Bu özelliği kullanırken her zaman dikkatli olmanız " +"gerekir." + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:144 +msgid "VEHICLE" +msgstr "ARAÇ" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:67 +#, python-format +msgid "VIEW" +msgstr "GÖRÜNTÜLE" + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:52 +#, python-format +msgid "Waiting to start" +msgstr "Başlatma bekleniyor" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:19 +msgid "" +"Warning: This grants SSH access to all public keys in your GitHub settings. " +"Never enter a GitHub username other than your own. A comma employee will " +"NEVER ask you to add their GitHub username." +msgstr "" +"Uyarı: Bu, GitHub ayarlarınızdaki tüm açık anahtarlara SSH erişimi verir. " +"Kendi adınız dışında asla bir GitHub kullanıcı adı girmeyin. Bir comma " +"çalışanı sizden asla GitHub kullanıcı adlarını eklemenizi İSTEMEZ." + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:111 +#, python-format +msgid "Welcome to openpilot" +msgstr "openpilot'a hoş geldiniz" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:20 +msgid "When enabled, pressing the accelerator pedal will disengage openpilot." +msgstr "" +"Etkinleştirildiğinde, gaz pedalına basmak openpilot'u devreden çıkarır." + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:44 +msgid "Wi-Fi" +msgstr "Wi‑Fi" + +#: /home/batman/openpilot/system/ui/widgets/network.py:144 +#, python-format +msgid "Wi-Fi Network Metered" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#, python-format +msgid "Wrong password" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:145 +#, python-format +msgid "You must accept the Terms and Conditions in order to use openpilot." +msgstr "openpilot'u kullanmak için Şartlar ve Koşulları kabul etmelisiniz." + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:112 +#, python-format +msgid "" +"You must accept the Terms and Conditions to use openpilot. Read the latest " +"terms at https://comma.ai/terms before continuing." +msgstr "" +"openpilot'u kullanmak için Şartlar ve Koşulları kabul etmelisiniz. Devam " +"etmeden önce en güncel şartları https://comma.ai/terms adresinde okuyun." + +#: /home/batman/openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:34 +#, python-format +msgid "camera starting" +msgstr "kamera başlatılıyor" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:63 +#, python-format +msgid "comma prime" +msgstr "comma prime" + +#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#, python-format +msgid "default" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:132 +#, python-format +msgid "down" +msgstr "aşağı" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:105 +#, python-format +msgid "failed to check for update" +msgstr "güncelleme kontrolü başarısız" + +#: /home/batman/openpilot/system/ui/widgets/network.py:237 +#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#, python-format +msgid "for \"{}\"" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:177 +#, python-format +msgid "km/h" +msgstr "km/h" + +#: /home/batman/openpilot/system/ui/widgets/network.py:204 +#, python-format +msgid "leave blank for automatic configuration" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#, python-format +msgid "left" +msgstr "sol" + +#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#, python-format +msgid "metered" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:177 +#, python-format +msgid "mph" +msgstr "mph" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:19 +#, python-format +msgid "never" +msgstr "asla" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:30 +#, python-format +msgid "now" +msgstr "şimdi" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:71 +#, python-format +msgid "openpilot Longitudinal Control (Alpha)" +msgstr "openpilot Boylamsal Kontrol (Alfa)" + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:51 +#, python-format +msgid "openpilot Unavailable" +msgstr "openpilot Kullanılamıyor" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:158 +#, python-format +msgid "" +"openpilot defaults to driving in chill mode. Experimental mode enables alpha-" +"level features that aren't ready for chill mode. Experimental features are " +"listed below:

End-to-End Longitudinal Control


Let the driving " +"model control the gas and brakes. openpilot will drive as it thinks a human " +"would, including stopping for red lights and stop signs. Since the driving " +"model decides the speed to drive, the set speed will only act as an upper " +"bound. This is an alpha quality feature; mistakes should be expected." +"

New Driving Visualization


The driving visualization will " +"transition to the road-facing wide-angle camera at low speeds to better show " +"some turns. The Experimental mode logo will also be shown in the top right " +"corner." +msgstr "" +"openpilot varsayılan olarak chill modunda sürer. Deneysel mod, chill moduna " +"hazır olmayan alfa seviyesindeki özellikleri etkinleştirir. Deneysel " +"özellikler aşağıda listelenmiştir:

Uçtan Uca Boylamsal Kontrol
Sürüş modelinin gaz ve frenleri kontrol etmesine izin verin. " +"openpilot, kırmızı ışıklarda ve dur işaretlerinde durmak dahil, bir insan " +"nasıl sürer diye düşündüğüne göre sürer. Hızı sürüş modeli belirlediğinden, " +"ayarlanan hız yalnızca üst sınır olarak işlev görür. Bu bir alfa kalitesinde " +"özelliktir; hatalar beklenmelidir.

Yeni Sürüş Görselleştirmesi
Sürüş görselleştirmesi, düşük hızlarda bazı dönüşleri daha iyi " +"göstermek için yola bakan geniş açılı kameraya geçer. Deneysel mod logosu " +"sağ üst köşede de gösterilecektir." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:164 +#, python-format +msgid "" +"openpilot is continuously calibrating, resetting is rarely required. " +"Resetting calibration will restart openpilot if the car is powered on." +msgstr "" +" Bu ayarı değiştirmek, araç çalışıyorsa openpilot'u yeniden başlatacaktır." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:20 +msgid "" +"openpilot learns to drive by watching humans, like you, drive.\n" +"\n" +"Firehose Mode allows you to maximize your training data uploads to improve " +"openpilot's driving models. More data means bigger models, which means " +"better Experimental Mode." +msgstr "" +"openpilot, sizin gibi insanların nasıl sürdüğünü izleyerek sürmeyi öğrenir.\n" +"\n" +"Firehose Modu, openpilot'un sürüş modellerini geliştirmek için eğitim veri " +"yüklemelerinizi en üst düzeye çıkarmanıza olanak tanır. Daha fazla veri, " +"daha büyük modeller demektir; bu da daha iyi Deneysel Mod anlamına gelir." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:183 +#, python-format +msgid "openpilot longitudinal control may come in a future update." +msgstr "openpilot boylamsal kontrolü gelecekteki bir güncellemede gelebilir." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:26 +msgid "" +"openpilot requires the device to be mounted within 4° left or right and " +"within 5° up or 9° down." +msgstr "" +"openpilot, cihazın sağa/sola 4° ve yukarı 5° veya aşağı 9° içinde monte " +"edilmesini gerektirir." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#, python-format +msgid "right" +msgstr "sağ" + +#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#, python-format +msgid "unmetered" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:132 +#, python-format +msgid "up" +msgstr "yukarı" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:116 +#, python-format +msgid "up to date, last checked never" +msgstr "güncel, son kontrol asla" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:114 +#, python-format +msgid "up to date, last checked {}" +msgstr "güncel, son kontrol {}" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:108 +#, python-format +msgid "update available" +msgstr "güncelleme mevcut" + +#: /home/batman/openpilot/selfdrive/ui/layouts/home.py:169 +#, python-format +msgid "{} ALERT" +msgid_plural "{} ALERTS" +msgstr[0] "{} UYARI" +msgstr[1] "{} UYARILAR" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:39 +#, python-format +msgid "{} day ago" +msgid_plural "{} days ago" +msgstr[0] "{} gün önce" +msgstr[1] "{} gün önce" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:36 +#, python-format +msgid "{} hour ago" +msgid_plural "{} hours ago" +msgstr[0] "{} saat önce" +msgstr[1] "{} saat önce" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:33 +#, python-format +msgid "{} minute ago" +msgid_plural "{} minutes ago" +msgstr[0] "{} dakika önce" +msgstr[1] "{} dakika önce" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:111 +#, python-format +msgid "{} segment of your driving is in the training dataset so far." +msgid_plural "{} segments of your driving is in the training dataset so far." +msgstr[0] "{} segment sürüşünüz eğitim veri setinde." +msgstr[1] "{} segment sürüşünüz eğitim veri setinde." + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:62 +#, python-format +msgid "✓ SUBSCRIBED" +msgstr "✓ ABONE" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:22 +#, python-format +msgid "🔥 Firehose Mode 🔥" +msgstr "🔥 Firehose Modu 🔥" diff --git a/selfdrive/ui/update_translations_raylib.py b/selfdrive/ui/update_translations_raylib.py new file mode 100755 index 000000000..e91820f24 --- /dev/null +++ b/selfdrive/ui/update_translations_raylib.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +from itertools import chain +import os +from openpilot.system.ui.lib.multilang import SYSTEM_UI_DIR, UI_DIR, TRANSLATIONS_DIR, multilang + + +def update_translations(): + files = [] + for root, _, filenames in chain(os.walk(SYSTEM_UI_DIR), + os.walk(os.path.join(UI_DIR, "widgets")), + os.walk(os.path.join(UI_DIR, "layouts")), + os.walk(os.path.join(UI_DIR, "onroad"))): + for filename in filenames: + if filename.endswith(".py"): + files.append(os.path.join(root, filename)) + + # Create main translation file + cmd = ("xgettext -L Python --keyword=tr --keyword=trn:1,2 --keyword=tr_noop --from-code=UTF-8 " + + "--flag=tr:1:python-brace-format --flag=trn:1:python-brace-format --flag=trn:2:python-brace-format " + + "-o translations/app.pot {}").format(" ".join(files)) + + ret = os.system(cmd) + assert ret == 0 + + # Generate/update translation files for each language + for name in multilang.languages.values(): + if os.path.exists(os.path.join(TRANSLATIONS_DIR, f"app_{name}.po")): + cmd = f"msgmerge --update --no-fuzzy-matching --backup=none --sort-output translations/app_{name}.po translations/app.pot" + ret = os.system(cmd) + assert ret == 0 + else: + cmd = f"msginit -l {name} --no-translator --input translations/app.pot --output-file translations/app_{name}.po" + ret = os.system(cmd) + assert ret == 0 + + +if __name__ == "__main__": + update_translations() diff --git a/selfdrive/ui/widgets/offroad_alerts.py b/selfdrive/ui/widgets/offroad_alerts.py index 0bd5b161b..802243ff3 100644 --- a/selfdrive/ui/widgets/offroad_alerts.py +++ b/selfdrive/ui/widgets/offroad_alerts.py @@ -15,9 +15,6 @@ from openpilot.system.ui.widgets.html_render import HtmlRenderer from openpilot.selfdrive.selfdrived.alertmanager import OFFROAD_ALERTS -NO_RELEASE_NOTES = tr("

No release notes available.

") - - class AlertColors: HIGH_SEVERITY = rl.Color(226, 44, 44, 255) LOW_SEVERITY = rl.Color(41, 41, 41, 255) @@ -56,21 +53,23 @@ class ButtonStyle(IntEnum): class ActionButton(Widget): - def __init__(self, text: str, style: ButtonStyle = ButtonStyle.LIGHT, + def __init__(self, text: str | Callable[[], str], style: ButtonStyle = ButtonStyle.LIGHT, min_width: int = AlertConstants.MIN_BUTTON_WIDTH): super().__init__() + self._text = text self._style = style self._min_width = min_width self._font = gui_app.font(FontWeight.MEDIUM) - self.set_text(text) - def set_text(self, text: str): - self._text = text - self._text_size = measure_text_cached(gui_app.font(FontWeight.MEDIUM), self._text, AlertConstants.FONT_SIZE) - self._rect.width = max(self._text_size.x + 60 * 2, self._min_width) - self._rect.height = AlertConstants.BUTTON_HEIGHT + @property + def text(self) -> str: + return self._text() if callable(self._text) else self._text def _render(self, _): + text_size = measure_text_cached(gui_app.font(FontWeight.MEDIUM), self.text, AlertConstants.FONT_SIZE) + self._rect.width = max(text_size.x + 60 * 2, self._min_width) + self._rect.height = AlertConstants.BUTTON_HEIGHT + roundness = AlertConstants.BORDER_RADIUS / self._rect.height bg_color = AlertColors.BUTTON if self._style == ButtonStyle.LIGHT else AlertColors.SNOOZE_BG if self.is_pressed: @@ -80,9 +79,9 @@ class ActionButton(Widget): # center text color = rl.WHITE if self._style == ButtonStyle.DARK else rl.BLACK - text_x = int(self._rect.x + (self._rect.width - self._text_size.x) // 2) - text_y = int(self._rect.y + (self._rect.height - self._text_size.y) // 2) - rl.draw_text_ex(self._font, self._text, rl.Vector2(text_x, text_y), AlertConstants.FONT_SIZE, 0, color) + text_x = int(self._rect.x + (self._rect.width - text_size.x) // 2) + text_y = int(self._rect.y + (self._rect.height - text_size.y) // 2) + rl.draw_text_ex(self._font, self.text, rl.Vector2(text_x, text_y), AlertConstants.FONT_SIZE, 0, color) class AbstractAlert(Widget, ABC): @@ -102,15 +101,15 @@ class AbstractAlert(Widget, ABC): if self.dismiss_callback: self.dismiss_callback() - self.dismiss_btn = ActionButton(tr("Close")) + self.dismiss_btn = ActionButton(lambda: tr("Close")) - self.snooze_btn = ActionButton(tr("Snooze Update"), style=ButtonStyle.DARK) + self.snooze_btn = ActionButton(lambda: tr("Snooze Update"), style=ButtonStyle.DARK) self.snooze_btn.set_click_callback(snooze_callback) - self.excessive_actuation_btn = ActionButton(tr("Acknowledge Excessive Actuation"), style=ButtonStyle.DARK, min_width=800) + self.excessive_actuation_btn = ActionButton(lambda: tr("Acknowledge Excessive Actuation"), style=ButtonStyle.DARK, min_width=800) self.excessive_actuation_btn.set_click_callback(excessive_actuation_callback) - self.reboot_btn = ActionButton(tr("Reboot and Update"), min_width=600) + self.reboot_btn = ActionButton(lambda: tr("Reboot and Update"), min_width=600) self.reboot_btn.set_click_callback(lambda: HARDWARE.reboot()) # TODO: just use a Scroller? @@ -318,12 +317,14 @@ class UpdateAlert(AbstractAlert): def refresh(self) -> bool: update_available: bool = self.params.get_bool("UpdateAvailable") + no_release_notes = "

" + tr("No release notes available.") + "

" + if update_available: self.release_notes = (self.params.get("UpdaterNewReleaseNotes") or b"").decode("utf8").strip() - self._html_renderer.parse_html_content(self.release_notes or NO_RELEASE_NOTES) + self._html_renderer.parse_html_content(self.release_notes or no_release_notes) self._cached_content_height = 0 else: - self._html_renderer.parse_html_content(NO_RELEASE_NOTES) + self._html_renderer.parse_html_content(no_release_notes) return update_available diff --git a/selfdrive/ui/widgets/setup.py b/selfdrive/ui/widgets/setup.py index ea88180ef..fcc5a1410 100644 --- a/selfdrive/ui/widgets/setup.py +++ b/selfdrive/ui/widgets/setup.py @@ -16,10 +16,10 @@ class SetupWidget(Widget): super().__init__() self._open_settings_callback = None self._pairing_dialog: PairingDialog | None = None - self._pair_device_btn = Button(tr("Pair device"), self._show_pairing, button_style=ButtonStyle.PRIMARY) - self._open_settings_btn = Button(tr("Open"), lambda: self._open_settings_callback() if self._open_settings_callback else None, + self._pair_device_btn = Button(lambda: tr("Pair device"), self._show_pairing, button_style=ButtonStyle.PRIMARY) + self._open_settings_btn = Button(lambda: tr("Open"), lambda: self._open_settings_callback() if self._open_settings_callback else None, button_style=ButtonStyle.PRIMARY) - self._firehose_label = Label(tr("🔥 Firehose Mode 🔥"), font_weight=FontWeight.MEDIUM, font_size=64) + self._firehose_label = Label(lambda: tr("🔥 Firehose Mode 🔥"), font_weight=FontWeight.MEDIUM, font_size=64) def set_open_settings_callback(self, callback): self._open_settings_callback = callback diff --git a/selfdrive/ui/widgets/ssh_key.py b/selfdrive/ui/widgets/ssh_key.py index e44c4aad5..88389cb05 100644 --- a/selfdrive/ui/widgets/ssh_key.py +++ b/selfdrive/ui/widgets/ssh_key.py @@ -7,7 +7,7 @@ from enum import Enum from openpilot.common.params import Params from openpilot.system.ui.lib.application import gui_app, FontWeight -from openpilot.system.ui.lib.multilang import tr +from openpilot.system.ui.lib.multilang import tr, tr_noop from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.widgets import DialogResult from openpilot.system.ui.widgets.button import Button, ButtonStyle @@ -26,9 +26,9 @@ VALUE_FONT_SIZE = 48 class SshKeyActionState(Enum): - LOADING = tr("LOADING") - ADD = tr("ADD") - REMOVE = tr("REMOVE") + LOADING = tr_noop("LOADING") + ADD = tr_noop("ADD") + REMOVE = tr_noop("REMOVE") class SshKeyAction(ItemAction): @@ -78,7 +78,7 @@ class SshKeyAction(ItemAction): # Draw button button_rect = rl.Rectangle(rect.x + rect.width - BUTTON_WIDTH, rect.y + (rect.height - BUTTON_HEIGHT) / 2, BUTTON_WIDTH, BUTTON_HEIGHT) self._button.set_rect(button_rect) - self._button.set_text(self._state.value) + self._button.set_text(tr(self._state.value)) self._button.set_enabled(self._state != SshKeyActionState.LOADING) self._button.render(button_rect) return False @@ -127,5 +127,5 @@ class SshKeyAction(ItemAction): self._state = SshKeyActionState.ADD -def ssh_key_item(title: str, description: str): +def ssh_key_item(title: str | Callable[[], str], description: str | Callable[[], str]) -> ListItem: return ListItem(title=title, description=description, action_item=SshKeyAction()) diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index ff7163793..476a33a99 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -14,6 +14,7 @@ from typing import NamedTuple from importlib.resources import as_file, files from openpilot.common.swaglog import cloudlog from openpilot.system.hardware import HARDWARE, PC, TICI +from openpilot.system.ui.lib.multilang import TRANSLATIONS_DIR, multilang from openpilot.common.realtime import Ratekeeper _DEFAULT_FPS = int(os.getenv("FPS", 20 if TICI else 60)) @@ -352,8 +353,17 @@ class GuiApplication: all_chars = set() for layout in KEYBOARD_LAYOUTS.values(): all_chars.update(key for row in layout for key in row) + all_chars |= set("–‑✓×°§•") + + # Load only the characters used in translations + for language in multilang.codes: + try: + with open(os.path.join(TRANSLATIONS_DIR, f"app_{language}.po")) as f: + all_chars |= set(f.read()) + except FileNotFoundError: + cloudlog.warning(f"Translation file for language '{language}' not found when loading fonts.") + all_chars = "".join(all_chars) - all_chars += "–✓×°§•" codepoint_count = rl.ffi.new("int *", 1) codepoints = rl.load_codepoints(all_chars, codepoint_count) diff --git a/system/ui/lib/multilang.py b/system/ui/lib/multilang.py index c020b33d5..767080892 100644 --- a/system/ui/lib/multilang.py +++ b/system/ui/lib/multilang.py @@ -1,10 +1,76 @@ import os +import json import gettext +from openpilot.common.params import Params from openpilot.common.basedir import BASEDIR +SYSTEM_UI_DIR = os.path.join(BASEDIR, "system", "ui") UI_DIR = os.path.join(BASEDIR, "selfdrive", "ui") TRANSLATIONS_DIR = os.path.join(UI_DIR, "translations") LANGUAGES_FILE = os.path.join(TRANSLATIONS_DIR, "languages.json") -tr = gettext.gettext -trn = gettext.ngettext +SUPPORTED_LANGUAGES = [ + "en", + "de", + "fr", + "pt-BR", + "es", + "tr", +] + + +class Multilang: + def __init__(self): + self._params = Params() + self.languages = {} + self.codes = {} + self._translation: gettext.NullTranslations | gettext.GNUTranslations = gettext.NullTranslations() + self._load_languages() + + @property + def language(self) -> str: + lang = str(self._params.get("LanguageSetting")).strip("main_") + if lang not in SUPPORTED_LANGUAGES: + lang = "en" + return lang + + def setup(self): + language = self.language + try: + with open(os.path.join(TRANSLATIONS_DIR, f'app_{language}.mo'), 'rb') as fh: + translation = gettext.GNUTranslations(fh) + translation.install() + self._translation = translation + print(f"Loaded translations for language: {language}") + except FileNotFoundError: + print(f"No translation file found for language: {language}, using default.") + gettext.install('app') + self._translation = gettext.NullTranslations() + return None + + def change_language(self, language_code: str) -> None: + # Reinstall gettext with the selected language + self._params.put("LanguageSetting", language_code) + self.setup() + + def tr(self, text: str) -> str: + return self._translation.gettext(text) + + def trn(self, singular: str, plural: str, n: int) -> str: + return self._translation.ngettext(singular, plural, n) + + def _load_languages(self): + with open(LANGUAGES_FILE, encoding='utf-8') as f: + self.languages = {k: v for k, v in json.load(f).items() if v in SUPPORTED_LANGUAGES} + self.codes = {v: k for k, v in self.languages.items() if v in SUPPORTED_LANGUAGES} + + +multilang = Multilang() +multilang.setup() + +tr, trn = multilang.tr, multilang.trn + + +# no-op marker for static strings translated later +def tr_noop(s: str) -> str: + return s diff --git a/system/ui/widgets/button.py b/system/ui/widgets/button.py index baf45ce95..84969d032 100644 --- a/system/ui/widgets/button.py +++ b/system/ui/widgets/button.py @@ -79,7 +79,7 @@ BUTTON_DISABLED_BACKGROUND_COLORS = { class Button(Widget): def __init__(self, - text: str, + text: str | Callable[[], str], click_callback: Callable[[], None] | None = None, font_size: int = DEFAULT_BUTTON_FONT_SIZE, font_weight: FontWeight = FontWeight.MEDIUM, diff --git a/system/ui/widgets/keyboard.py b/system/ui/widgets/keyboard.py index 80b87483d..b0e103568 100644 --- a/system/ui/widgets/keyboard.py +++ b/system/ui/widgets/keyboard.py @@ -78,7 +78,7 @@ class Keyboard(Widget): self._backspace_last_repeat: float = 0.0 self._render_return_status = -1 - self._cancel_button = Button(tr("Cancel"), self._cancel_button_callback) + self._cancel_button = Button(lambda: tr("Cancel"), self._cancel_button_callback) self._eye_button = Button("", self._eye_button_callback, button_style=ButtonStyle.TRANSPARENT) diff --git a/system/ui/widgets/label.py b/system/ui/widgets/label.py index 99aed529a..756495436 100644 --- a/system/ui/widgets/label.py +++ b/system/ui/widgets/label.py @@ -1,3 +1,4 @@ +from collections.abc import Callable from itertools import zip_longest from typing import Union import pyray as rl @@ -12,6 +13,13 @@ from openpilot.system.ui.widgets import Widget ICON_PADDING = 15 +# TODO: make this common +def _resolve_value(value, default=""): + if callable(value): + return value() + return value if value is not None else default + + # TODO: This should be a Widget class def gui_label( rect: rl.Rectangle, @@ -90,7 +98,7 @@ def gui_text_box( # Non-interactive text area. Can render emojis and an optional specified icon. class Label(Widget): def __init__(self, - text: str, + text: str | Callable[[], str], font_size: int = DEFAULT_TEXT_SIZE, font_weight: FontWeight = FontWeight.NORMAL, text_alignment: int = rl.GuiTextAlignment.TEXT_ALIGN_CENTER, @@ -130,7 +138,7 @@ class Label(Widget): def _update_text(self, text): self._emojis = [] self._text_size = [] - self._text_wrapped = wrap_text(self._font, text, self._font_size, round(self._rect.width - (self._text_padding * 2))) + self._text_wrapped = wrap_text(self._font, _resolve_value(text), self._font_size, round(self._rect.width - (self._text_padding * 2))) for t in self._text_wrapped: self._emojis.append(find_emoji(t)) self._text_size.append(measure_text_cached(self._font, t, self._font_size)) diff --git a/system/ui/widgets/list_view.py b/system/ui/widgets/list_view.py index a02c7a1eb..a604c8e16 100644 --- a/system/ui/widgets/list_view.py +++ b/system/ui/widgets/list_view.py @@ -171,11 +171,9 @@ class TextAction(ItemAction): class DualButtonAction(ItemAction): - def __init__(self, left_text: str, right_text: str, left_callback: Callable = None, + def __init__(self, left_text: str | Callable[[], str], right_text: str | Callable[[], str], left_callback: Callable = None, right_callback: Callable = None, enabled: bool | Callable[[], bool] = True): super().__init__(width=0, enabled=enabled) # Width 0 means use full width - self.left_text, self.right_text = left_text, right_text - self.left_button = Button(left_text, click_callback=left_callback, button_style=ButtonStyle.NORMAL, text_padding=0) self.right_button = Button(right_text, click_callback=right_callback, button_style=ButtonStyle.DANGER, text_padding=0) @@ -206,7 +204,7 @@ class DualButtonAction(ItemAction): class MultipleButtonAction(ItemAction): - def __init__(self, buttons: list[str], button_width: int, selected_index: int = 0, callback: Callable = None): + def __init__(self, buttons: list[str | Callable[[], str]], button_width: int, selected_index: int = 0, callback: Callable = None): super().__init__(width=len(buttons) * button_width + (len(buttons) - 1) * RIGHT_ITEM_PADDING, enabled=True) self.buttons = buttons self.button_width = button_width @@ -225,7 +223,7 @@ class MultipleButtonAction(ItemAction): spacing = RIGHT_ITEM_PADDING button_y = rect.y + (rect.height - BUTTON_HEIGHT) / 2 - for i, text in enumerate(self.buttons): + for i, _text in enumerate(self.buttons): button_x = rect.x + i * (self.button_width + spacing) button_rect = rl.Rectangle(button_x, button_y, self.button_width, BUTTON_HEIGHT) @@ -249,6 +247,7 @@ class MultipleButtonAction(ItemAction): rl.draw_rectangle_rounded(button_rect, 1.0, 20, bg_color) # Draw text + text = _resolve_value(_text, "") text_size = measure_text_cached(self._font, text, 40) text_x = button_x + (self.button_width - text_size.x) / 2 text_y = button_y + (BUTTON_HEIGHT - text_size.y) / 2 @@ -258,7 +257,7 @@ class MultipleButtonAction(ItemAction): def _handle_mouse_release(self, mouse_pos: MousePos): spacing = RIGHT_ITEM_PADDING button_y = self._rect.y + (self._rect.height - BUTTON_HEIGHT) / 2 - for i, _text in enumerate(self.buttons): + for i, _ in enumerate(self.buttons): button_x = self._rect.x + i * (self.button_width + spacing) button_rect = rl.Rectangle(button_x, button_y, self.button_width, BUTTON_HEIGHT) if rl.check_collision_point_rec(mouse_pos, button_rect): @@ -268,11 +267,11 @@ class MultipleButtonAction(ItemAction): class ListItem(Widget): - def __init__(self, title: str = "", icon: str | None = None, description: str | Callable[[], str] | None = None, + def __init__(self, title: str | Callable[[], str] = "", icon: str | None = None, description: str | Callable[[], str] | None = None, description_visible: bool = False, callback: Callable | None = None, action_item: ItemAction | None = None): super().__init__() - self.title = title + self._title = title self.set_icon(icon) self._description = description self.description_visible = description_visible @@ -285,7 +284,7 @@ class ListItem(Widget): self._html_renderer = HtmlRenderer(text="", text_size={ElementType.P: ITEM_DESC_FONT_SIZE}, text_color=ITEM_DESC_TEXT_COLOR) - self.set_description(self.description) + self._parse_description(self.description) # Cached properties for performance self._prev_description: str | None = self.description @@ -332,7 +331,7 @@ class ListItem(Widget): # Detect changes if description is callback new_description = self.description if new_description != self._prev_description: - self.set_description(new_description) + self._parse_description(new_description) def _render(self, _): if not self.is_visible: @@ -385,10 +384,15 @@ class ListItem(Widget): def set_description(self, description: str | Callable[[], str] | None): self._description = description - new_desc = self.description + + def _parse_description(self, new_desc): self._html_renderer.parse_html_content(new_desc) self._prev_description = new_desc + @property + def title(self): + return _resolve_value(self._title, "") + @property def description(self): return _resolve_value(self._description, "") @@ -423,35 +427,35 @@ class ListItem(Widget): # Factory functions -def simple_item(title: str, callback: Callable | None = None) -> ListItem: +def simple_item(title: str | Callable[[], str], callback: Callable | None = None) -> ListItem: return ListItem(title=title, callback=callback) -def toggle_item(title: str, description: str | Callable[[], str] | None = None, initial_state: bool = False, +def toggle_item(title: str | Callable[[], str], description: str | Callable[[], str] | None = None, initial_state: bool = False, callback: Callable | None = None, icon: str = "", enabled: bool | Callable[[], bool] = True) -> ListItem: action = ToggleAction(initial_state=initial_state, enabled=enabled, callback=callback) return ListItem(title=title, description=description, action_item=action, icon=icon) -def button_item(title: str, button_text: str | Callable[[], str], description: str | Callable[[], str] | None = None, +def button_item(title: str | Callable[[], str], button_text: str | Callable[[], str], description: str | Callable[[], str] | None = None, callback: Callable | None = None, enabled: bool | Callable[[], bool] = True) -> ListItem: action = ButtonAction(text=button_text, enabled=enabled) return ListItem(title=title, description=description, action_item=action, callback=callback) -def text_item(title: str, value: str | Callable[[], str], description: str | Callable[[], str] | None = None, +def text_item(title: str | Callable[[], str], value: str | Callable[[], str], description: str | Callable[[], str] | None = None, callback: Callable | None = None, enabled: bool | Callable[[], bool] = True) -> ListItem: action = TextAction(text=value, color=ITEM_TEXT_VALUE_COLOR, enabled=enabled) return ListItem(title=title, description=description, action_item=action, callback=callback) -def dual_button_item(left_text: str, right_text: str, left_callback: Callable = None, right_callback: Callable = None, +def dual_button_item(left_text: str | Callable[[], str], right_text: str | Callable[[], str], left_callback: Callable = None, right_callback: Callable = None, description: str | Callable[[], str] | None = None, enabled: bool | Callable[[], bool] = True) -> ListItem: action = DualButtonAction(left_text, right_text, left_callback, right_callback, enabled) return ListItem(title="", description=description, action_item=action) -def multiple_button_item(title: str, description: str, buttons: list[str], selected_index: int, +def multiple_button_item(title: str | Callable[[], str], description: str | Callable[[], str], buttons: list[str | Callable[[], str]], selected_index: int, button_width: int = BUTTON_WIDTH, callback: Callable = None, icon: str = ""): action = MultipleButtonAction(buttons, button_width, selected_index, callback=callback) return ListItem(title=title, description=description, icon=icon, action_item=action) diff --git a/system/ui/widgets/network.py b/system/ui/widgets/network.py index a59030363..e705c55f7 100644 --- a/system/ui/widgets/network.py +++ b/system/ui/widgets/network.py @@ -117,40 +117,42 @@ class AdvancedNetworkSettings(Widget): # Tethering self._tethering_action = ToggleAction(initial_state=False) - tethering_btn = ListItem(title=tr("Enable Tethering"), action_item=self._tethering_action, callback=self._toggle_tethering) + tethering_btn = ListItem(lambda: tr("Enable Tethering"), action_item=self._tethering_action, callback=self._toggle_tethering) # Edit tethering password - self._tethering_password_action = ButtonAction(text=tr("EDIT")) - tethering_password_btn = ListItem(title=tr("Tethering Password"), action_item=self._tethering_password_action, callback=self._edit_tethering_password) + self._tethering_password_action = ButtonAction(lambda: tr("EDIT")) + tethering_password_btn = ListItem(lambda: tr("Tethering Password"), action_item=self._tethering_password_action, callback=self._edit_tethering_password) # Roaming toggle roaming_enabled = self._params.get_bool("GsmRoaming") self._roaming_action = ToggleAction(initial_state=roaming_enabled) - self._roaming_btn = ListItem(title=tr("Enable Roaming"), action_item=self._roaming_action, callback=self._toggle_roaming) + self._roaming_btn = ListItem(lambda: tr("Enable Roaming"), action_item=self._roaming_action, callback=self._toggle_roaming) # Cellular metered toggle cellular_metered = self._params.get_bool("GsmMetered") self._cellular_metered_action = ToggleAction(initial_state=cellular_metered) - self._cellular_metered_btn = ListItem(title=tr("Cellular Metered"), description=tr("Prevent large data uploads when on a metered cellular connection"), + self._cellular_metered_btn = ListItem(lambda: tr("Cellular Metered"), + description=lambda: tr("Prevent large data uploads when on a metered cellular connection"), action_item=self._cellular_metered_action, callback=self._toggle_cellular_metered) # APN setting - self._apn_btn = button_item(tr("APN Setting"), tr("EDIT"), callback=self._edit_apn) + self._apn_btn = button_item(lambda: tr("APN Setting"), lambda: tr("EDIT"), callback=self._edit_apn) # Wi-Fi metered toggle - self._wifi_metered_action = MultipleButtonAction([tr("default"), tr("metered"), tr("unmetered")], 255, 0, callback=self._toggle_wifi_metered) - wifi_metered_btn = ListItem(title=tr("Wi-Fi Network Metered"), description=tr("Prevent large data uploads when on a metered Wi-Fi connection"), + self._wifi_metered_action = MultipleButtonAction([lambda: tr("default"), lambda: tr("metered"), lambda: tr("unmetered")], 255, 0, + callback=self._toggle_wifi_metered) + wifi_metered_btn = ListItem(lambda: tr("Wi-Fi Network Metered"), description=lambda: tr("Prevent large data uploads when on a metered Wi-Fi connection"), action_item=self._wifi_metered_action) items: list[Widget] = [ tethering_btn, tethering_password_btn, - text_item(tr("IP Address"), lambda: self._wifi_manager.ipv4_address), + text_item(lambda: tr("IP Address"), lambda: self._wifi_manager.ipv4_address), self._roaming_btn, self._apn_btn, self._cellular_metered_btn, wifi_metered_btn, - button_item(tr("Hidden Network"), tr("CONNECT"), callback=self._connect_to_hidden_network), + button_item(lambda: tr("Hidden Network"), lambda: tr("CONNECT"), callback=self._connect_to_hidden_network), ] self._scroller = Scroller(items, line_separator=True, spacing=0) @@ -282,7 +284,6 @@ class WifiManagerUI(Widget): self._networks: list[Network] = [] self._networks_buttons: dict[str, Button] = {} self._forget_networks_buttons: dict[str, Button] = {} - self._confirm_dialog = ConfirmDialog("", tr("Forget"), tr("Cancel")) self._wifi_manager.set_callbacks(need_auth=self._on_need_auth, activated=self._on_activated, @@ -314,9 +315,10 @@ class WifiManagerUI(Widget): self.keyboard.reset(min_text_size=MIN_PASSWORD_LENGTH) gui_app.set_modal_overlay(self.keyboard, lambda result: self._on_password_entered(cast(Network, self._state_network), result)) elif self.state == UIState.SHOW_FORGET_CONFIRM and self._state_network: - self._confirm_dialog.set_text(tr("Forget Wi-Fi Network \"{}\"?").format(self._state_network.ssid)) - self._confirm_dialog.reset() - gui_app.set_modal_overlay(self._confirm_dialog, callback=lambda result: self.on_forgot_confirm_finished(self._state_network, result)) + confirm_dialog = ConfirmDialog("", tr("Forget"), tr("Cancel")) + confirm_dialog.set_text(tr("Forget Wi-Fi Network \"{}\"?").format(self._state_network.ssid)) + confirm_dialog.reset() + gui_app.set_modal_overlay(confirm_dialog, callback=lambda result: self.on_forgot_confirm_finished(self._state_network, result)) else: self._draw_network_list(rect) From a8660b5b4fef2827ee218d3641e4c456211527ea Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 22 Oct 2025 17:55:28 -0700 Subject: [PATCH 233/910] Revert "raylib: add branch switcher (#36411)" This reverts commit 856f8d3d47983a80bfdb08d2208bcef9278f5232. --- selfdrive/ui/layouts/settings/software.py | 32 +++-------------------- 1 file changed, 3 insertions(+), 29 deletions(-) diff --git a/selfdrive/ui/layouts/settings/software.py b/selfdrive/ui/layouts/settings/software.py index 73c8946f5..6f4629c75 100644 --- a/selfdrive/ui/layouts/settings/software.py +++ b/selfdrive/ui/layouts/settings/software.py @@ -8,7 +8,6 @@ from openpilot.system.ui.lib.multilang import tr, trn from openpilot.system.ui.widgets import Widget, DialogResult from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog from openpilot.system.ui.widgets.list_view import button_item, text_item, ListItem -from openpilot.system.ui.widgets.option_dialog import MultiOptionDialog from openpilot.system.ui.widgets.scroller import Scroller # TODO: remove this. updater fails to respond on startup if time is not correct @@ -53,9 +52,6 @@ class SoftwareLayout(Widget): self._install_btn = button_item(lambda: tr("Install Update"), lambda: tr("INSTALL"), callback=self._on_install_update) self._install_btn.set_visible(False) - self._select_branch_dialog: MultiOptionDialog | None = None - self._target_branch_btn = button_item(tr("Target Branch"), tr("SELECT"), callback=self._on_select_branch) - # Track waiting-for-updater transition to avoid brief re-enable while still idle self._waiting_for_updater = False self._waiting_start_ts: float = 0.0 @@ -69,7 +65,8 @@ class SoftwareLayout(Widget): self._version_item, self._download_btn, self._install_btn, - self._target_branch_btn, + # TODO: implement branch switching + # button_item("Target Branch", "SELECT", callback=self._on_select_branch), button_item(lambda: tr("Uninstall"), lambda: tr("UNINSTALL"), callback=self._on_uninstall), ] return items @@ -90,8 +87,6 @@ class SoftwareLayout(Widget): self._version_item.action_item.set_text(current_desc) self._version_item.set_description(current_release_notes) - self._target_branch_btn.action_item.set_value(ui_state.params.get("UpdaterTargetBranch") or "") - # Update download button visibility and state self._download_btn.set_visible(ui_state.is_offroad()) @@ -168,25 +163,4 @@ class SoftwareLayout(Widget): self._install_btn.action_item.set_enabled(False) ui_state.params.put_bool("DoReboot", True) - def _on_select_branch(self): - current_branch = ui_state.params.get("GitBranch") or "" - branches_str = ui_state.params.get("UpdaterAvailableBranches") or "" - - available_branches = [b.strip() for b in branches_str.split(",") if b.strip()] - priority_branches = [current_branch, "devel-staging", "devel", "nightly", "nightly-dev", "master"] - for branch in priority_branches: - if branch in available_branches: - available_branches.remove(branch) - available_branches.insert(0, branch) - - self._select_branch_dialog = MultiOptionDialog(tr("Select a branch"), available_branches, current=current_branch) - gui_app.set_modal_overlay(self._select_branch_dialog, callback=self._handle_branch_selection) - - def _handle_branch_selection(self, result: int): - if result == 1 and self._select_branch_dialog: - selected = self._select_branch_dialog.selection - self._target_branch_btn.action_item.set_value(selected) - ui_state.params.put("UpdaterTargetBranch", selected) - os.system("pkill -SIGHUP -f system.updated.updated") - - self._select_branch_dialog = None + def _on_select_branch(self): pass From 2c41dbc472e554381048234c0481b957486a9fb0 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 22 Oct 2025 18:10:32 -0700 Subject: [PATCH 234/910] raylib: hit rect for scroller items (#36432) * hit rect * clean up * comment * oh this is actually epic * rm line * type --- system/ui/widgets/__init__.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/system/ui/widgets/__init__.py b/system/ui/widgets/__init__.py index 0157dd3ef..562cde39e 100644 --- a/system/ui/widgets/__init__.py +++ b/system/ui/widgets/__init__.py @@ -14,7 +14,7 @@ class DialogResult(IntEnum): class Widget(abc.ABC): def __init__(self): self._rect: rl.Rectangle = rl.Rectangle(0, 0, 0, 0) - self._parent_rect: rl.Rectangle = rl.Rectangle(0, 0, 0, 0) + self._parent_rect: rl.Rectangle | None = None self.__is_pressed = [False] * MAX_TOUCH_SLOTS # if current mouse/touch down started within the widget's rectangle self.__tracking_is_pressed = [False] * MAX_TOUCH_SLOTS @@ -75,6 +75,13 @@ class Widget(abc.ABC): if changed: self._update_layout_rects() + @property + def _hit_rect(self) -> rl.Rectangle: + # restrict touches to within parent rect if set, useful inside Scroller + if self._parent_rect is None: + return self._rect + return rl.get_collision_rec(self._rect, self._parent_rect) + def render(self, rect: rl.Rectangle = None) -> bool | int | None: if rect is not None: self.set_rect(rect) @@ -95,7 +102,7 @@ class Widget(abc.ABC): # Ignores touches/presses that start outside our rect # Allows touch to leave the rect and come back in focus if mouse did not release if mouse_event.left_pressed and self._touch_valid(): - if rl.check_collision_point_rec(mouse_event.pos, self._rect): + if rl.check_collision_point_rec(mouse_event.pos, self._hit_rect): self._handle_mouse_press(mouse_event.pos) self.__is_pressed[mouse_event.slot] = True self.__tracking_is_pressed[mouse_event.slot] = True @@ -106,18 +113,18 @@ class Widget(abc.ABC): self.__tracking_is_pressed[mouse_event.slot] = False elif mouse_event.left_released: - if self.__is_pressed[mouse_event.slot] and rl.check_collision_point_rec(mouse_event.pos, self._rect): + if self.__is_pressed[mouse_event.slot] and rl.check_collision_point_rec(mouse_event.pos, self._hit_rect): self._handle_mouse_release(mouse_event.pos) self.__is_pressed[mouse_event.slot] = False self.__tracking_is_pressed[mouse_event.slot] = False # Mouse/touch is still within our rect - elif rl.check_collision_point_rec(mouse_event.pos, self._rect): + elif rl.check_collision_point_rec(mouse_event.pos, self._hit_rect): if self.__tracking_is_pressed[mouse_event.slot]: self.__is_pressed[mouse_event.slot] = True # Mouse/touch left our rect but may come back into focus later - elif not rl.check_collision_point_rec(mouse_event.pos, self._rect): + elif not rl.check_collision_point_rec(mouse_event.pos, self._hit_rect): self.__is_pressed[mouse_event.slot] = False return ret From 8f720a54f6c9c590fedfa9006745d354b7de117f Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 22 Oct 2025 18:54:09 -0700 Subject: [PATCH 235/910] raylib: add branch switcher (#36359) * it's adversarial * try 2 * just do this * kinda works but doesn' tmatch * fine * qt is banned word * test * fix test * add elide support to Label * fixup * Revert "add elide support to Label" This reverts commit 28c3e0e7457345083d93f7b6a909a4103bd50d55. * Reapply "add elide support to Label" This reverts commit 92c2d6694146f164f30060d7621e19006e2fe2df. * todo * elide button value properly + debug/stash * clean up * clean up * yep looks good * clean up * eval visible once * no s * don't need * can do this * but this also works * clip to parent rect * fixes and multilang * clean up * set target branch * whops --- selfdrive/ui/layouts/settings/software.py | 46 +++++++++++++++---- .../ui/tests/test_ui/raylib_screenshots.py | 19 ++++++-- system/ui/widgets/button.py | 3 +- system/ui/widgets/label.py | 34 ++++++++++++-- system/ui/widgets/list_view.py | 19 +++++--- system/ui/widgets/option_dialog.py | 6 +-- 6 files changed, 101 insertions(+), 26 deletions(-) diff --git a/selfdrive/ui/layouts/settings/software.py b/selfdrive/ui/layouts/settings/software.py index 6f4629c75..09b09ddec 100644 --- a/selfdrive/ui/layouts/settings/software.py +++ b/selfdrive/ui/layouts/settings/software.py @@ -8,6 +8,7 @@ from openpilot.system.ui.lib.multilang import tr, trn from openpilot.system.ui.widgets import Widget, DialogResult from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog from openpilot.system.ui.widgets.list_view import button_item, text_item, ListItem +from openpilot.system.ui.widgets.option_dialog import MultiOptionDialog from openpilot.system.ui.widgets.scroller import Scroller # TODO: remove this. updater fails to respond on startup if time is not correct @@ -56,20 +57,20 @@ class SoftwareLayout(Widget): self._waiting_for_updater = False self._waiting_start_ts: float = 0.0 - items = self._init_items() - self._scroller = Scroller(items, line_separator=True, spacing=0) + # Branch switcher + self._branch_btn = button_item(lambda: tr("Target Branch"), lambda: tr("SELECT"), callback=self._on_select_branch) + self._branch_btn.set_visible(not ui_state.params.get_bool("IsTestedBranch")) + self._branch_btn.action_item.set_value(ui_state.params.get("UpdaterTargetBranch") or "") + self._branch_dialog: MultiOptionDialog | None = None - def _init_items(self): - items = [ + self._scroller = Scroller([ self._onroad_label, self._version_item, self._download_btn, self._install_btn, - # TODO: implement branch switching - # button_item("Target Branch", "SELECT", callback=self._on_select_branch), + self._branch_btn, button_item(lambda: tr("Uninstall"), lambda: tr("UNINSTALL"), callback=self._on_uninstall), - ] - return items + ], line_separator=True, spacing=0) def show_event(self): self._scroller.show_event() @@ -123,6 +124,10 @@ class SoftwareLayout(Widget): # Only enable if we're not waiting for updater to flip out of idle self._download_btn.action_item.set_enabled(not self._waiting_for_updater) + # Update target branch button value + current_branch = ui_state.params.get("UpdaterTargetBranch") or "" + self._branch_btn.action_item.set_value(current_branch) + # Update install button self._install_btn.set_visible(ui_state.is_offroad() and update_available) if update_available: @@ -163,4 +168,27 @@ class SoftwareLayout(Widget): self._install_btn.action_item.set_enabled(False) ui_state.params.put_bool("DoReboot", True) - def _on_select_branch(self): pass + def _on_select_branch(self): + # Get available branches and order + current_git_branch = ui_state.params.get("GitBranch") or "" + branches_str = ui_state.params.get("UpdaterAvailableBranches") or "" + branches = [b for b in branches_str.split(",") if b] + + for b in [current_git_branch, "devel-staging", "devel", "nightly", "nightly-dev", "master"]: + if b in branches: + branches.remove(b) + branches.insert(0, b) + + current_target = ui_state.params.get("UpdaterTargetBranch") or "" + self._branch_dialog = MultiOptionDialog("Select a branch", branches, current_target) + + def handle_selection(result): + # Confirmed selection + if result == DialogResult.CONFIRM and self._branch_dialog is not None and self._branch_dialog.selection: + selection = self._branch_dialog.selection + ui_state.params.put("UpdaterTargetBranch", selection) + self._branch_btn.action_item.set_value(selection) + os.system("pkill -SIGUSR1 -f system.updated.updated") + self._branch_dialog = None + + gui_app.set_modal_overlay(self._branch_dialog, callback=handle_selection) diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py index 2e96b3bd4..29fae5b61 100755 --- a/selfdrive/ui/tests/test_ui/raylib_screenshots.py +++ b/selfdrive/ui/tests/test_ui/raylib_screenshots.py @@ -27,6 +27,9 @@ TEST_OUTPUT_DIR = TEST_DIR / "raylib_report" SCREENSHOTS_DIR = TEST_OUTPUT_DIR / "screenshots" UI_DELAY = 0.2 +BRANCH_NAME = "this-is-a-really-super-mega-ultra-max-extreme-ultimate-long-branch-name" +VERSION = f"0.10.1 / {BRANCH_NAME} / 7864838 / Oct 03" + # Offroad alerts to test OFFROAD_ALERTS = ['Offroad_IsTakingSnapshot'] @@ -34,6 +37,7 @@ OFFROAD_ALERTS = ['Offroad_IsTakingSnapshot'] def put_update_params(params: Params): params.put("UpdaterCurrentReleaseNotes", parse_release_notes(BASEDIR)) params.put("UpdaterNewReleaseNotes", parse_release_notes(BASEDIR)) + params.put("UpdaterTargetBranch", BRANCH_NAME) def setup_homescreen(click, pm: PubMaster): @@ -89,6 +93,15 @@ def setup_settings_software_release_notes(click, pm: PubMaster): click(588, 110) # expand description for current version +def setup_settings_software_branch_switcher(click, pm: PubMaster): + setup_settings_software(click, pm) + params = Params() + params.put("UpdaterAvailableBranches", f"master,nightly,release,{BRANCH_NAME}") + params.put("GitBranch", BRANCH_NAME) # should be on top + params.put("UpdaterTargetBranch", "nightly") # should be selected + click(1984, 449) + + def setup_settings_firehose(click, pm: PubMaster): setup_settings(click, pm) click(278, 845) @@ -240,6 +253,7 @@ CASES = { "settings_software": setup_settings_software, "settings_software_download": setup_settings_software_download, "settings_software_release_notes": setup_settings_software_release_notes, + "settings_software_branch_switcher": setup_settings_software_branch_switcher, "settings_firehose": setup_settings_firehose, "settings_developer": setup_settings_developer, "keyboard": setup_keyboard, @@ -309,9 +323,8 @@ def create_screenshots(): params.put("DongleId", "123456789012345") # Set branch name - description = "0.10.1 / this-is-a-really-super-mega-long-branch-name / 7864838 / Oct 03" - params.put("UpdaterCurrentDescription", description) - params.put("UpdaterNewDescription", description) + params.put("UpdaterCurrentDescription", VERSION) + params.put("UpdaterNewDescription", VERSION) if name == "homescreen_paired": params.put("PrimeType", 0) # NONE diff --git a/system/ui/widgets/button.py b/system/ui/widgets/button.py index 84969d032..bd6517ad4 100644 --- a/system/ui/widgets/button.py +++ b/system/ui/widgets/button.py @@ -88,6 +88,7 @@ class Button(Widget): text_alignment: int = rl.GuiTextAlignment.TEXT_ALIGN_CENTER, text_padding: int = 20, icon=None, + elide_right: bool = False, multi_touch: bool = False, ): @@ -97,7 +98,7 @@ class Button(Widget): self._background_color = BUTTON_BACKGROUND_COLORS[self._button_style] self._label = Label(text, font_size, font_weight, text_alignment, text_padding=text_padding, - text_color=BUTTON_TEXT_COLOR[self._button_style], icon=icon) + text_color=BUTTON_TEXT_COLOR[self._button_style], icon=icon, elide_right=elide_right) self._click_callback = click_callback self._multi_touch = multi_touch diff --git a/system/ui/widgets/label.py b/system/ui/widgets/label.py index 756495436..10f6f1400 100644 --- a/system/ui/widgets/label.py +++ b/system/ui/widgets/label.py @@ -37,17 +37,17 @@ def gui_label( # Elide text to fit within the rectangle if elide_right and text_size.x > rect.width: - ellipsis = "..." + _ellipsis = "..." left, right = 0, len(text) while left < right: mid = (left + right) // 2 - candidate = text[:mid] + ellipsis + candidate = text[:mid] + _ellipsis candidate_size = measure_text_cached(font, candidate, font_size) if candidate_size.x <= rect.width: left = mid + 1 else: right = mid - display_text = text[: left - 1] + ellipsis if left > 0 else ellipsis + display_text = text[: left - 1] + _ellipsis if left > 0 else _ellipsis text_size = measure_text_cached(font, display_text, font_size) # Calculate horizontal position based on alignment @@ -106,6 +106,7 @@ class Label(Widget): text_padding: int = 0, text_color: rl.Color = DEFAULT_TEXT_COLOR, icon: Union[rl.Texture, None] = None, # noqa: UP007 + elide_right: bool = False, ): super().__init__() @@ -117,6 +118,7 @@ class Label(Widget): self._text_padding = text_padding self._text_color = text_color self._icon = icon + self._elide_right = elide_right self._text = text self.set_text(text) @@ -138,7 +140,31 @@ class Label(Widget): def _update_text(self, text): self._emojis = [] self._text_size = [] - self._text_wrapped = wrap_text(self._font, _resolve_value(text), self._font_size, round(self._rect.width - (self._text_padding * 2))) + text = _resolve_value(text) + + if self._elide_right: + display_text = text + + # Elide text to fit within the rectangle + text_size = measure_text_cached(self._font, text, self._font_size) + content_width = self._rect.width - self._text_padding * 2 + if text_size.x > content_width: + _ellipsis = "..." + left, right = 0, len(text) + while left < right: + mid = (left + right) // 2 + candidate = text[:mid] + _ellipsis + candidate_size = measure_text_cached(self._font, candidate, self._font_size) + if candidate_size.x <= content_width: + left = mid + 1 + else: + right = mid + display_text = text[: left - 1] + _ellipsis if left > 0 else _ellipsis + + self._text_wrapped = [display_text] + else: + self._text_wrapped = wrap_text(self._font, text, self._font_size, round(self._rect.width - (self._text_padding * 2))) + for t in self._text_wrapped: self._emojis.append(find_emoji(t)) self._text_size.append(measure_text_cached(self._font, t, self._font_size)) diff --git a/system/ui/widgets/list_view.py b/system/ui/widgets/list_view.py index a604c8e16..e5f234ed3 100644 --- a/system/ui/widgets/list_view.py +++ b/system/ui/widgets/list_view.py @@ -100,6 +100,14 @@ class ButtonAction(ItemAction): ) self.set_enabled(enabled) + def get_width_hint(self) -> float: + value_text = self.value + if value_text: + text_width = measure_text_cached(self._font, value_text, ITEM_TEXT_FONT_SIZE).x + return text_width + BUTTON_WIDTH + TEXT_PADDING + else: + return BUTTON_WIDTH + def set_touch_valid_callback(self, touch_callback: Callable[[], bool]) -> None: super().set_touch_valid_callback(touch_callback) self._button.set_touch_valid_callback(touch_callback) @@ -121,16 +129,15 @@ class ButtonAction(ItemAction): def _render(self, rect: rl.Rectangle) -> bool: self._button.set_text(self.text) self._button.set_enabled(_resolve_value(self.enabled)) - button_rect = rl.Rectangle(rect.x, rect.y + (rect.height - BUTTON_HEIGHT) / 2, BUTTON_WIDTH, BUTTON_HEIGHT) + button_rect = rl.Rectangle(rect.x + rect.width - BUTTON_WIDTH, rect.y + (rect.height - BUTTON_HEIGHT) / 2, BUTTON_WIDTH, BUTTON_HEIGHT) self._button.render(button_rect) value_text = self.value if value_text: - spacing = 20 - text_size = measure_text_cached(self._font, value_text, ITEM_TEXT_FONT_SIZE) - text_x = button_rect.x - spacing - text_size.x - text_y = rect.y + (rect.height - text_size.y) / 2 - rl.draw_text_ex(self._font, value_text, rl.Vector2(text_x, text_y), ITEM_TEXT_FONT_SIZE, 0, ITEM_TEXT_VALUE_COLOR) + value_rect = rl.Rectangle(rect.x, rect.y, rect.width - BUTTON_WIDTH - TEXT_PADDING, rect.height) + gui_label(value_rect, value_text, font_size=ITEM_TEXT_FONT_SIZE, color=ITEM_TEXT_VALUE_COLOR, + font_weight=FontWeight.NORMAL, alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT, + alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE) # TODO: just use the generic Widget click callbacks everywhere, no returning from render pressed = self._pressed diff --git a/system/ui/widgets/option_dialog.py b/system/ui/widgets/option_dialog.py index 8c63ca3f9..fc42b4808 100644 --- a/system/ui/widgets/option_dialog.py +++ b/system/ui/widgets/option_dialog.py @@ -28,11 +28,11 @@ class MultiOptionDialog(Widget): # Create scroller with option buttons self.option_buttons = [Button(option, click_callback=lambda opt=option: self._on_option_clicked(opt), text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT, button_style=ButtonStyle.NORMAL, - text_padding=50) for option in options] + text_padding=50, elide_right=True) for option in options] self.scroller = Scroller(self.option_buttons, spacing=LIST_ITEM_SPACING) - self.cancel_button = Button(tr("Cancel"), click_callback=lambda: self._set_result(DialogResult.CANCEL)) - self.select_button = Button(tr("Select"), click_callback=lambda: self._set_result(DialogResult.CONFIRM), button_style=ButtonStyle.PRIMARY) + self.cancel_button = Button(lambda: tr("Cancel"), click_callback=lambda: self._set_result(DialogResult.CANCEL)) + self.select_button = Button(lambda: tr("Select"), click_callback=lambda: self._set_result(DialogResult.CONFIRM), button_style=ButtonStyle.PRIMARY) def _set_result(self, result: DialogResult): self._result = result From b14270bd7122ac7a8f0e5e200dd705b2a3bc020f Mon Sep 17 00:00:00 2001 From: YassineYousfi Date: Wed, 22 Oct 2025 18:56:18 -0700 Subject: [PATCH 236/910] update RELEASES.md --- RELEASES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASES.md b/RELEASES.md index 568be6c35..558d32209 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,6 +1,6 @@ Version 0.10.1 (2025-09-08) ======================== -* New driving model #36114 +* New driving model #36276 * World Model: removed global localization inputs * World Model: 2x the number of parameters * World Model: trained on 4x the number of segments From a0d48b6c63ead47cd0fe031a6cc92a7eb3d29464 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 22 Oct 2025 19:18:54 -0700 Subject: [PATCH 237/910] raylib: unifont for CJK languages (#36430) * add rest of langs * unifont * all langs are supported * add japanese translations * fix strip! * add language name chars * use unifont in lang selection * add korean * test all langs * doesn't work * unifont font fallback for multilang * add ar translations * fix labels not updating until scrolling * t chinese * more chn * we already default * wrap * update * fix thai * fix missing chinese langs and all are supported! * clean up * update * ??? mypy r u ok ??? * fix default option font weight --- .gitattributes | 1 + selfdrive/assets/fonts/unifont.otf | 3 + selfdrive/ui/layouts/settings/device.py | 5 +- selfdrive/ui/layouts/settings/software.py | 2 +- selfdrive/ui/translations/app.pot | 113 +- selfdrive/ui/translations/app_ar.po | 1237 +++++++++++++++++++++ selfdrive/ui/translations/app_de.po | 113 +- selfdrive/ui/translations/app_en.po | 113 +- selfdrive/ui/translations/app_es.po | 113 +- selfdrive/ui/translations/app_fr.po | 113 +- selfdrive/ui/translations/app_ja.po | 1216 ++++++++++++++++++++ selfdrive/ui/translations/app_ko.po | 1209 ++++++++++++++++++++ selfdrive/ui/translations/app_pt-BR.po | 113 +- selfdrive/ui/translations/app_th.po | 1148 +++++++++++++++++++ selfdrive/ui/translations/app_tr.po | 113 +- selfdrive/ui/translations/app_zh-CHS.po | 1193 ++++++++++++++++++++ selfdrive/ui/translations/app_zh-CHT.po | 1192 ++++++++++++++++++++ system/ui/lib/application.py | 19 +- system/ui/lib/multilang.py | 42 +- system/ui/lib/text_measure.py | 3 +- system/ui/lib/wrap_text.py | 2 + system/ui/widgets/label.py | 7 +- system/ui/widgets/option_dialog.py | 3 +- 23 files changed, 7700 insertions(+), 373 deletions(-) create mode 100644 selfdrive/assets/fonts/unifont.otf create mode 100644 selfdrive/ui/translations/app_ar.po create mode 100644 selfdrive/ui/translations/app_ja.po create mode 100644 selfdrive/ui/translations/app_ko.po create mode 100644 selfdrive/ui/translations/app_th.po create mode 100644 selfdrive/ui/translations/app_zh-CHS.po create mode 100644 selfdrive/ui/translations/app_zh-CHT.po diff --git a/.gitattributes b/.gitattributes index 50ac49dd7..41a7367d8 100644 --- a/.gitattributes +++ b/.gitattributes @@ -7,6 +7,7 @@ *.png filter=lfs diff=lfs merge=lfs -text *.gif filter=lfs diff=lfs merge=lfs -text *.ttf filter=lfs diff=lfs merge=lfs -text +*.otf filter=lfs diff=lfs merge=lfs -text *.wav filter=lfs diff=lfs merge=lfs -text selfdrive/car/tests/test_models_segs.txt filter=lfs diff=lfs merge=lfs -text diff --git a/selfdrive/assets/fonts/unifont.otf b/selfdrive/assets/fonts/unifont.otf new file mode 100644 index 000000000..b85597b1f --- /dev/null +++ b/selfdrive/assets/fonts/unifont.otf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9712a9bc089af7ddc06e0826aa84f2ee23ed2f1a1dddaf2a89c2483e753a8475 +size 5321484 diff --git a/selfdrive/ui/layouts/settings/device.py b/selfdrive/ui/layouts/settings/device.py index 3f762a884..f5f37fbd3 100644 --- a/selfdrive/ui/layouts/settings/device.py +++ b/selfdrive/ui/layouts/settings/device.py @@ -10,7 +10,7 @@ from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.selfdrive.ui.layouts.onboarding import TrainingGuide from openpilot.selfdrive.ui.widgets.pairing_dialog import PairingDialog from openpilot.system.hardware import TICI -from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.lib.application import FontWeight, gui_app from openpilot.system.ui.lib.multilang import multilang, tr, tr_noop from openpilot.system.ui.widgets import Widget, DialogResult from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog, alert_dialog @@ -88,7 +88,8 @@ class DeviceLayout(Widget): self._update_calib_description() self._select_language_dialog = None - self._select_language_dialog = MultiOptionDialog(tr("Select a language"), multilang.languages, multilang.codes[multilang.language]) + self._select_language_dialog = MultiOptionDialog(tr("Select a language"), multilang.languages, multilang.codes[multilang.language], + option_font_weight=FontWeight.UNIFONT) gui_app.set_modal_overlay(self._select_language_dialog, callback=handle_language_selection) def _show_driver_camera(self): diff --git a/selfdrive/ui/layouts/settings/software.py b/selfdrive/ui/layouts/settings/software.py index 09b09ddec..8166a8a9e 100644 --- a/selfdrive/ui/layouts/settings/software.py +++ b/selfdrive/ui/layouts/settings/software.py @@ -180,7 +180,7 @@ class SoftwareLayout(Widget): branches.insert(0, b) current_target = ui_state.params.get("UpdaterTargetBranch") or "" - self._branch_dialog = MultiOptionDialog("Select a branch", branches, current_target) + self._branch_dialog = MultiOptionDialog(tr("Select a branch"), branches, current_target) def handle_selection(result): # Confirmed selection diff --git a/selfdrive/ui/translations/app.pot b/selfdrive/ui/translations/app.pot index b68a19787..e6a0e880d 100644 --- a/selfdrive/ui/translations/app.pot +++ b/selfdrive/ui/translations/app.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-22 15:30-0700\n" +"POT-Creation-Date: 2025-10-22 19:09-0700\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -18,7 +18,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" -#: /home/batman/openpilot/system/ui/widgets/html_render.py:247 +#: /home/batman/openpilot/system/ui/widgets/html_render.py:263 #: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:93 #: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:127 #, python-format @@ -26,14 +26,14 @@ msgid "OK" msgstr "" #: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:23 -#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:34 +#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:35 #: /home/batman/openpilot/system/ui/widgets/keyboard.py:81 #: /home/batman/openpilot/system/ui/widgets/network.py:318 #, python-format msgid "Cancel" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:35 +#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:36 #, python-format msgid "Select" msgstr "" @@ -193,8 +193,8 @@ msgstr "" msgid "FORGETTING..." msgstr "" -#: /home/batman/openpilot/system/ui/widgets/list_view.py:115 -#: /home/batman/openpilot/system/ui/widgets/list_view.py:153 +#: /home/batman/openpilot/system/ui/widgets/list_view.py:123 +#: /home/batman/openpilot/system/ui/widgets/list_view.py:160 #, python-format msgid "Error" msgstr "" @@ -609,112 +609,127 @@ msgstr "" msgid "Enable" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:19 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:20 #, python-format msgid "never" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:30 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:31 #, python-format msgid "now" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:33 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:34 #, python-format msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "" msgstr[1] "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:36 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:37 #, python-format msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "" msgstr[1] "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:39 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:40 #, python-format msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "" msgstr[1] "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:47 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:48 #, python-format msgid "Updates are only downloaded while the car is off." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:48 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:49 #, python-format msgid "Current Version" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:49 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:50 #, python-format msgid "Download" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:49 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:106 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:117 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:142 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:50 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:107 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:118 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:147 #, python-format msgid "CHECK" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:52 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:53 #, python-format msgid "Install Update" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:52 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:131 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:53 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:136 #, python-format msgid "INSTALL" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:70 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:158 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:61 +#, python-format +msgid "Target Branch" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:61 +#, python-format +msgid "SELECT" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:72 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:163 #, python-format msgid "Uninstall" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:70 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:72 #, python-format msgid "UNINSTALL" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:105 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:106 #, python-format msgid "failed to check for update" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:108 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:109 #, python-format msgid "update available" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:109 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:110 #, python-format msgid "DOWNLOAD" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:114 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:115 #, python-format msgid "up to date, last checked {}" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:116 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:117 #, python-format msgid "up to date, last checked never" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:158 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:163 #, python-format msgid "Are you sure you want to uninstall?" msgstr "" +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:183 +#, python-format +msgid "Select a branch" +msgstr "" + #: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:25 msgid "" "Preview the driver facing camera to ensure that driver monitoring has good " @@ -752,13 +767,13 @@ msgid "RESET" msgstr "" #: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:174 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:175 #, python-format msgid "Reboot" msgstr "" #: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:186 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:187 #, python-format msgid "Power Off" msgstr "" @@ -824,89 +839,89 @@ msgstr "" msgid "Select a language" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:102 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:103 #, python-format msgid "Disengage to Reset Calibration" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:118 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:119 #, python-format msgid "Are you sure you want to reset calibration?" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:118 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:119 #, python-format msgid "Reset" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:132 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:132 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid "down" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:132 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid "up" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:134 #, python-format msgid "left" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:134 #, python-format msgid "right" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:145 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:146 #, python-format msgid "

Steering lag calibration is {}% complete." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:147 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:148 #, python-format msgid "

Steering lag calibration is complete." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:157 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:158 #, python-format msgid " Steering torque response calibration is {}% complete." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:159 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:160 #, python-format msgid " Steering torque response calibration is complete." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:164 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:165 #, python-format msgid "" "openpilot is continuously calibrating, resetting is rarely required. " "Resetting calibration will restart openpilot if the car is powered on." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:171 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:172 #, python-format msgid "Disengage to Reboot" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:174 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:175 #, python-format msgid "Are you sure you want to reboot?" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:183 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:184 #, python-format msgid "Disengage to Power Off" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:186 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:187 #, python-format msgid "Are you sure you want to power off?" msgstr "" diff --git a/selfdrive/ui/translations/app_ar.po b/selfdrive/ui/translations/app_ar.po new file mode 100644 index 000000000..5860c94dd --- /dev/null +++ b/selfdrive/ui/translations/app_ar.po @@ -0,0 +1,1237 @@ +# Arabic translations for PACKAGE package. +# Copyright (C) 2025 THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# Automatically generated, 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-10-22 18:57-0700\n" +"PO-Revision-Date: 2025-10-22 16:32-0700\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: ar\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=6; plural=n==0?0:n==1?1:n==2?2:(n%100>=3 && " +"n%100<=10)?3:(n%100>=11 && n%100<=99)?4:5;\n" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:160 +#, python-format +msgid " Steering torque response calibration is complete." +msgstr " اكتملت معايرة استجابة عزم التوجيه." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:158 +#, python-format +msgid " Steering torque response calibration is {}% complete." +msgstr " اكتملت معايرة استجابة عزم التوجيه بنسبة {}٪." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#, python-format +msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." +msgstr " جهازك موجه بمقدار {:.1f}° {} و {:.1f}° {}." + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:43 +msgid "--" +msgstr "--" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "1 year of drive storage" +msgstr "سنة واحدة من تخزين القيادة" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "24/7 LTE connectivity" +msgstr "اتصال LTE على مدار الساعة" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:46 +msgid "2G" +msgstr "2G" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:47 +msgid "3G" +msgstr "3G" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:49 +msgid "5G" +msgstr "5G" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:23 +msgid "" +"WARNING: openpilot longitudinal control is in alpha for this car and will " +"disable Automatic Emergency Braking (AEB).

On this car, openpilot " +"defaults to the car's built-in ACC instead of openpilot's longitudinal " +"control. Enable this to switch to openpilot longitudinal control. Enabling " +"Experimental mode is recommended when enabling openpilot longitudinal " +"control alpha. Changing this setting will restart openpilot if the car is " +"powered on." +msgstr "" +"تحذير: التحكم الطولي لـ openpilot في مرحلة ألفا لهذه السيارة وسيُعطّل نظام " +"الكبح التلقائي في حالات الطوارئ (AEB).

في هذه السيارة، يعتمد " +"openpilot افتراضياً على نظام ACC المدمج بدلاً من التحكم الطولي لـ openpilot. " +"فعّل هذا الخيار للتبديل إلى التحكم الطولي لـ openpilot. يُنصح بتمكين وضع " +"التجربة عند تفعيل نسخة ألفا من التحكم الطولي. تغيير هذا الإعداد سيعيد تشغيل " +"openpilot إذا كانت السيارة قيد التشغيل." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:148 +#, python-format +msgid "

Steering lag calibration is complete." +msgstr "

اكتملت معايرة تأخر التوجيه." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:146 +#, python-format +msgid "

Steering lag calibration is {}% complete." +msgstr "

اكتملت معايرة تأخر التوجيه بنسبة {}٪." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:138 +#, python-format +msgid "ACTIVE" +msgstr "نشط" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:15 +msgid "" +"ADB (Android Debug Bridge) allows connecting to your device over USB or over " +"the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." +msgstr "" +"يتيح ADB (Android Debug Bridge) الاتصال بجهازك عبر USB أو عبر الشبكة. راجع " +"https://docs.comma.ai/how-to/connect-to-comma لمزيد من المعلومات." + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:30 +msgid "ADD" +msgstr "إضافة" + +#: /home/batman/openpilot/system/ui/widgets/network.py:139 +#, python-format +msgid "APN Setting" +msgstr "إعداد APN" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 +#, python-format +msgid "Acknowledge Excessive Actuation" +msgstr "تأكيد التشغيل المفرط" + +#: /home/batman/openpilot/system/ui/widgets/network.py:74 +#: /home/batman/openpilot/system/ui/widgets/network.py:95 +#, python-format +msgid "Advanced" +msgstr "متقدم" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#, python-format +msgid "Aggressive" +msgstr "عدواني" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:116 +#, python-format +msgid "Agree" +msgstr "موافقة" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:70 +#, python-format +msgid "Always-On Driver Monitoring" +msgstr "مراقبة السائق دائماً" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:186 +#, python-format +msgid "" +"An alpha version of openpilot longitudinal control can be tested, along with " +"Experimental mode, on non-release branches." +msgstr "" +"يمكن اختبار نسخة ألفا من التحكم الطولي لـ openpilot، مع وضع التجربة، على " +"الفروع غير الإصدارية." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:187 +#, python-format +msgid "Are you sure you want to power off?" +msgstr "هل أنت متأكد أنك تريد إيقاف التشغيل؟" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:175 +#, python-format +msgid "Are you sure you want to reboot?" +msgstr "هل أنت متأكد أنك تريد إعادة التشغيل؟" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:119 +#, python-format +msgid "Are you sure you want to reset calibration?" +msgstr "هل أنت متأكد أنك تريد إعادة ضبط المعايرة؟" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:163 +#, python-format +msgid "Are you sure you want to uninstall?" +msgstr "هل أنت متأكد أنك تريد إلغاء التثبيت؟" + +#: /home/batman/openpilot/system/ui/widgets/network.py:99 +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:147 +#, python-format +msgid "Back" +msgstr "رجوع" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:38 +#, python-format +msgid "Become a comma prime member at connect.comma.ai" +msgstr "انضم إلى comma prime عبر connect.comma.ai" + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:130 +#, python-format +msgid "Bookmark connect.comma.ai to your home screen to use it like an app" +msgstr "ثبّت connect.comma.ai على شاشتك الرئيسية لاستخدامه كتطبيق" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:68 +#, python-format +msgid "CHANGE" +msgstr "تغيير" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:50 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:107 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:118 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:147 +#, python-format +msgid "CHECK" +msgstr "تحقق" + +#: /home/batman/openpilot/selfdrive/ui/widgets/exp_mode_button.py:50 +#, python-format +msgid "CHILL MODE ON" +msgstr "وضع الهدوء مُفعل" + +#: /home/batman/openpilot/system/ui/widgets/network.py:155 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:138 +#, python-format +msgid "CONNECT" +msgstr "CONNECT" + +#: /home/batman/openpilot/system/ui/widgets/network.py:369 +#, python-format +msgid "CONNECTING..." +msgstr "CONNECTING..." + +#: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:23 +#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:35 +#: /home/batman/openpilot/system/ui/widgets/keyboard.py:81 +#: /home/batman/openpilot/system/ui/widgets/network.py:318 +#, python-format +msgid "Cancel" +msgstr "إلغاء" + +#: /home/batman/openpilot/system/ui/widgets/network.py:134 +#, python-format +msgid "Cellular Metered" +msgstr "خلوي بتعرفة محدودة" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:68 +#, python-format +msgid "Change Language" +msgstr "تغيير اللغة" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:125 +#, python-format +msgid "Changing this setting will restart openpilot if the car is powered on." +msgstr "" +"سيؤدي تغيير هذا الإعداد إلى إعادة تشغيل openpilot إذا كانت السيارة قيد " +"التشغيل." + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:129 +#, python-format +msgid "Click \"add new device\" and scan the QR code on the right" +msgstr "اضغط \"إضافة جهاز جديد\" ثم امسح رمز QR على اليمين" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 +#, python-format +msgid "Close" +msgstr "إغلاق" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:49 +#, python-format +msgid "Current Version" +msgstr "الإصدار الحالي" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:110 +#, python-format +msgid "DOWNLOAD" +msgstr "تنزيل" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:115 +#, python-format +msgid "Decline" +msgstr "رفض" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:148 +#, python-format +msgid "Decline, uninstall openpilot" +msgstr "رفض، وإلغاء تثبيت openpilot" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:67 +msgid "Developer" +msgstr "المطور" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:62 +msgid "Device" +msgstr "الجهاز" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:58 +#, python-format +msgid "Disengage on Accelerator Pedal" +msgstr "فصل عند الضغط على دواسة الوقود" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:184 +#, python-format +msgid "Disengage to Power Off" +msgstr "افصل لإيقاف التشغيل" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:172 +#, python-format +msgid "Disengage to Reboot" +msgstr "افصل لإعادة التشغيل" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:103 +#, python-format +msgid "Disengage to Reset Calibration" +msgstr "افصل لإعادة ضبط المعايرة" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:32 +msgid "Display speed in km/h instead of mph." +msgstr "عرض السرعة بالكيلومتر/ساعة بدلاً من الميل/ساعة." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:59 +#, python-format +msgid "Dongle ID" +msgstr "معرّف الدونجل" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:50 +#, python-format +msgid "Download" +msgstr "تنزيل" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:62 +#, python-format +msgid "Driver Camera" +msgstr "كاميرا السائق" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:96 +#, python-format +msgid "Driving Personality" +msgstr "شخصية القيادة" + +#: /home/batman/openpilot/system/ui/widgets/network.py:123 +#: /home/batman/openpilot/system/ui/widgets/network.py:139 +#, python-format +msgid "EDIT" +msgstr "تعديل" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:138 +msgid "ERROR" +msgstr "خطأ" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:45 +msgid "ETH" +msgstr "ETH" + +#: /home/batman/openpilot/selfdrive/ui/widgets/exp_mode_button.py:50 +#, python-format +msgid "EXPERIMENTAL MODE ON" +msgstr "وضع التجربة مُفعل" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:166 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:228 +#, python-format +msgid "Enable" +msgstr "تمكين" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:39 +#, python-format +msgid "Enable ADB" +msgstr "تمكين ADB" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:64 +#, python-format +msgid "Enable Lane Departure Warnings" +msgstr "تمكين تحذيرات مغادرة المسار" + +#: /home/batman/openpilot/system/ui/widgets/network.py:129 +#, python-format +msgid "Enable Roaming" +msgstr "تمكين التجوال" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:48 +#, python-format +msgid "Enable SSH" +msgstr "تمكين SSH" + +#: /home/batman/openpilot/system/ui/widgets/network.py:120 +#, python-format +msgid "Enable Tethering" +msgstr "تمكين الربط" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:30 +msgid "Enable driver monitoring even when openpilot is not engaged." +msgstr "تمكين مراقبة السائق حتى عندما لا يكون openpilot مُشغلاً." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:46 +#, python-format +msgid "Enable openpilot" +msgstr "تمكين openpilot" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:189 +#, python-format +msgid "" +"Enable the openpilot longitudinal control (alpha) toggle to allow " +"Experimental mode." +msgstr "فعّل تبديل التحكم الطولي (ألفا) لـ openpilot للسماح بوضع التجربة." + +#: /home/batman/openpilot/system/ui/widgets/network.py:204 +#, python-format +msgid "Enter APN" +msgstr "أدخل APN" + +#: /home/batman/openpilot/system/ui/widgets/network.py:241 +#, python-format +msgid "Enter SSID" +msgstr "أدخل SSID" + +#: /home/batman/openpilot/system/ui/widgets/network.py:254 +#, python-format +msgid "Enter new tethering password" +msgstr "أدخل كلمة مرور الربط الجديدة" + +#: /home/batman/openpilot/system/ui/widgets/network.py:237 +#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#, python-format +msgid "Enter password" +msgstr "أدخل كلمة المرور" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:89 +#, python-format +msgid "Enter your GitHub username" +msgstr "أدخل اسم مستخدم GitHub الخاص بك" + +#: /home/batman/openpilot/system/ui/widgets/list_view.py:123 +#: /home/batman/openpilot/system/ui/widgets/list_view.py:160 +#, python-format +msgid "Error" +msgstr "خطأ" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:52 +#, python-format +msgid "Experimental Mode" +msgstr "وضع التجربة" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:181 +#, python-format +msgid "" +"Experimental mode is currently unavailable on this car since the car's stock " +"ACC is used for longitudinal control." +msgstr "" +"وضع التجربة غير متاح حالياً في هذه السيارة لأن نظام ACC الأصلي يُستخدم للتحكم " +"الطولي." + +#: /home/batman/openpilot/system/ui/widgets/network.py:373 +#, python-format +msgid "FORGETTING..." +msgstr "جارٍ النسيان..." + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:44 +#, python-format +msgid "Finish Setup" +msgstr "إنهاء الإعداد" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:66 +msgid "Firehose" +msgstr "Firehose" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:18 +msgid "Firehose Mode" +msgstr "وضع Firehose" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:25 +msgid "" +"For maximum effectiveness, bring your device inside and connect to a good " +"USB-C adapter and Wi-Fi weekly.\n" +"\n" +"Firehose Mode can also work while you're driving if connected to a hotspot " +"or unlimited SIM card.\n" +"\n" +"\n" +"Frequently Asked Questions\n" +"\n" +"Does it matter how or where I drive? Nope, just drive as you normally " +"would.\n" +"\n" +"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a " +"subset of your segments.\n" +"\n" +"What's a good USB-C adapter? Any fast phone or laptop charger should be " +"fine.\n" +"\n" +"Does it matter which software I run? Yes, only upstream openpilot (and " +"particular forks) are able to be used for training." +msgstr "" +"لأقصى فعالية، أحضر جهازك إلى الداخل واتصل بمحوّل USB‑C جيد وبشبكة Wi‑Fi " +"أسبوعياً.\n" +"\n" +"يمكن أن يعمل وضع Firehose أيضاً أثناء القيادة إذا كنت متصلاً بنقطة اتصال أو " +"بشريحة غير محدودة.\n" +"\n" +"\n" +"الأسئلة الشائعة\n" +"\n" +"هل يهم كيف أو أين أقود؟ لا، قد بقدر المعتاد.\n" +"\n" +"هل يتم سحب كل مقاطعي في وضع Firehose؟ لا، نقوم بسحب مجموعة فرعية من " +"المقاطع.\n" +"\n" +"ما هو محول USB‑C الجيد؟ أي شاحن هاتف أو حاسب محمول سريع سيكون مناسباً.\n" +"\n" +"هل يهم أي برنامج أشغّل؟ نعم، فقط openpilot الأصلي (وبعض التفرعات المحددة) " +"يمكن استخدامه للتدريب." + +#: /home/batman/openpilot/system/ui/widgets/network.py:318 +#: /home/batman/openpilot/system/ui/widgets/network.py:451 +#, python-format +msgid "Forget" +msgstr "نسيان" + +#: /home/batman/openpilot/system/ui/widgets/network.py:319 +#, python-format +msgid "Forget Wi-Fi Network \"{}\"?" +msgstr "هل تريد نسيان شبكة Wi‑Fi \"{}\"؟" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:125 +msgid "GOOD" +msgstr "جيد" + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:128 +#, python-format +msgid "Go to https://connect.comma.ai on your phone" +msgstr "اذهب إلى https://connect.comma.ai على هاتفك" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:129 +msgid "HIGH" +msgstr "مرتفع" + +#: /home/batman/openpilot/system/ui/widgets/network.py:155 +#, python-format +msgid "Hidden Network" +msgstr "شبكة مخفية" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:140 +#, python-format +msgid "INACTIVE: connect to an unmetered network" +msgstr "غير نشط: اتصل بشبكة غير محدودة التعرفة" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:53 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:136 +#, python-format +msgid "INSTALL" +msgstr "تثبيت" + +#: /home/batman/openpilot/system/ui/widgets/network.py:150 +#, python-format +msgid "IP Address" +msgstr "عنوان IP" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:53 +#, python-format +msgid "Install Update" +msgstr "تثبيت التحديث" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:56 +#, python-format +msgid "Joystick Debug Mode" +msgstr "وضع تصحيح عصا التحكم" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:29 +msgid "LOADING" +msgstr "جارٍ التحميل" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:48 +msgid "LTE" +msgstr "LTE" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:64 +#, python-format +msgid "Longitudinal Maneuver Mode" +msgstr "وضع المناورة الطولية" + +#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:148 +#, python-format +msgid "MAX" +msgstr "أقصى" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:75 +#, python-format +msgid "" +"Maximize your training data uploads to improve openpilot's driving models." +msgstr "زد من تحميل بيانات التدريب لتحسين نماذج قيادة openpilot." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:59 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:60 +#, python-format +msgid "N/A" +msgstr "غير متوفر" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:142 +msgid "NO" +msgstr "لا" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:63 +msgid "Network" +msgstr "الشبكة" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:114 +#, python-format +msgid "No SSH keys found" +msgstr "لم يتم العثور على مفاتيح SSH" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:126 +#, python-format +msgid "No SSH keys found for user '{}'" +msgstr "لم يتم العثور على مفاتيح SSH للمستخدم '{}'" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:320 +#, python-format +msgid "No release notes available." +msgstr "لا توجد ملاحظات إصدار متاحة." + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:134 +msgid "OFFLINE" +msgstr "غير متصل" + +#: /home/batman/openpilot/system/ui/widgets/html_render.py:263 +#: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:93 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:127 +#, python-format +msgid "OK" +msgstr "موافق" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:144 +msgid "ONLINE" +msgstr "متصل" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:20 +#, python-format +msgid "Open" +msgstr "فتح" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:48 +#, python-format +msgid "PAIR" +msgstr "إقران" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:142 +msgid "PANDA" +msgstr "PANDA" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:62 +#, python-format +msgid "PREVIEW" +msgstr "معاينة" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:44 +#, python-format +msgid "PRIME FEATURES:" +msgstr "ميزات PRIME:" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:48 +#, python-format +msgid "Pair Device" +msgstr "إقران الجهاز" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:19 +#, python-format +msgid "Pair device" +msgstr "إقران الجهاز" + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:103 +#, python-format +msgid "Pair your device to your comma account" +msgstr "قم بإقران جهازك بحساب comma" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:48 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:24 +#, python-format +msgid "" +"Pair your device with comma connect (connect.comma.ai) and claim your comma " +"prime offer." +msgstr "" +"أقرِن جهازك مع comma connect (connect.comma.ai) واحصل على عرض comma prime." + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:91 +#, python-format +msgid "Please connect to Wi-Fi to complete initial pairing" +msgstr "يرجى الاتصال بشبكة Wi‑Fi لإكمال الاقتران الأولي" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:187 +#, python-format +msgid "Power Off" +msgstr "إيقاف التشغيل" + +#: /home/batman/openpilot/system/ui/widgets/network.py:144 +#, python-format +msgid "Prevent large data uploads when on a metered Wi-Fi connection" +msgstr "منع رفع البيانات الكبيرة عند الاتصال بشبكة Wi‑Fi محدودة التعرفة" + +#: /home/batman/openpilot/system/ui/widgets/network.py:135 +#, python-format +msgid "Prevent large data uploads when on a metered cellular connection" +msgstr "منع رفع البيانات الكبيرة عند الاتصال الخلوي محدود التعرفة" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:25 +msgid "" +"Preview the driver facing camera to ensure that driver monitoring has good " +"visibility. (vehicle must be off)" +msgstr "" +"عاين كاميرا مواجهة السائق للتأكد من أن مراقبة السائق تتم برؤية جيدة. (يجب أن " +"تكون المركبة متوقفة)" + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:161 +#, python-format +msgid "QR Code Error" +msgstr "خطأ في رمز QR" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:31 +msgid "REMOVE" +msgstr "إزالة" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:51 +#, python-format +msgid "RESET" +msgstr "إعادة ضبط" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:65 +#, python-format +msgid "REVIEW" +msgstr "مراجعة" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:175 +#, python-format +msgid "Reboot" +msgstr "إعادة التشغيل" + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:66 +#, python-format +msgid "Reboot Device" +msgstr "إعادة تشغيل الجهاز" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 +#, python-format +msgid "Reboot and Update" +msgstr "إعادة التشغيل والتحديث" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:27 +msgid "" +"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)." +msgstr "" +"استقبال تنبيهات للتوجيه للعودة إلى المسار عند انحراف المركبة فوق خط المسار " +"المُكتشف بدون إشارة انعطاف مفعّلة أثناء القيادة فوق 31 ميل/س (50 كم/س)." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:76 +#, python-format +msgid "Record and Upload Driver Camera" +msgstr "تسجيل ورفع فيديو كاميرا السائق" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:82 +#, python-format +msgid "Record and Upload Microphone Audio" +msgstr "تسجيل ورفع صوت الميكروفون" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +msgid "" +"Record and store microphone audio while driving. The audio will be included " +"in the dashcam video in comma connect." +msgstr "" +"تسجيل وتخزين صوت الميكروفون أثناء القيادة. سيُدرج الصوت في فيديو الكاميرا " +"الأمامية في comma connect." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:67 +#, python-format +msgid "Regulatory" +msgstr "لوائح" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#, python-format +msgid "Relaxed" +msgstr "مسترخٍ" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "Remote access" +msgstr "وصول عن بُعد" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "Remote snapshots" +msgstr "لقطات عن بُعد" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:123 +#, python-format +msgid "Request timed out" +msgstr "انتهت مهلة الطلب" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:119 +#, python-format +msgid "Reset" +msgstr "إعادة ضبط" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:51 +#, python-format +msgid "Reset Calibration" +msgstr "إعادة ضبط المعايرة" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:65 +#, python-format +msgid "Review Training Guide" +msgstr "مراجعة دليل التدريب" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:27 +msgid "Review the rules, features, and limitations of openpilot" +msgstr "مراجعة قواعد وميزات وحدود openpilot" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:61 +#, python-format +msgid "SELECT" +msgstr "اختيار" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:53 +#, python-format +msgid "SSH Keys" +msgstr "مفاتيح SSH" + +#: /home/batman/openpilot/system/ui/widgets/network.py:310 +#, python-format +msgid "Scanning Wi-Fi networks..." +msgstr "جارٍ مسح شبكات Wi‑Fi..." + +#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:36 +#, python-format +msgid "Select" +msgstr "اختيار" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:183 +#, python-format +msgid "Select a branch" +msgstr "اختر فرعاً" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:91 +#, python-format +msgid "Select a language" +msgstr "اختر لغة" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:60 +#, python-format +msgid "Serial" +msgstr "الرقم التسلسلي" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 +#, python-format +msgid "Snooze Update" +msgstr "تأجيل التحديث" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:65 +msgid "Software" +msgstr "البرمجيات" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#, python-format +msgid "Standard" +msgstr "قياسي" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:22 +msgid "" +"Standard is recommended. In aggressive mode, openpilot will follow lead cars " +"closer and be more aggressive with the gas and brake. In relaxed mode " +"openpilot will stay further away from lead cars. On supported cars, you can " +"cycle through these personalities with your steering wheel distance button." +msgstr "" +"يوصى بالوضع القياسي. في الوضع العدواني، سيتبع openpilot السيارات الأمامية عن " +"قرب وسيكون أكثر شدة في الوقود والفرامل. في الوضع المسترخي سيبقى بعيداً أكثر " +"عن السيارات الأمامية. في السيارات المدعومة، يمكنك التنقل بين هذه الشخصيات " +"بزر مسافة المقود." + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:59 +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:65 +#, python-format +msgid "System Unresponsive" +msgstr "النظام لا يستجيب" + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:58 +#, python-format +msgid "TAKE CONTROL IMMEDIATELY" +msgstr "تولَّ السيطرة فوراً" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:129 +msgid "TEMP" +msgstr "الحرارة" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:61 +#, python-format +msgid "Target Branch" +msgstr "الفرع المستهدف" + +#: /home/batman/openpilot/system/ui/widgets/network.py:124 +#, python-format +msgid "Tethering Password" +msgstr "كلمة مرور الربط" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:64 +msgid "Toggles" +msgstr "مفاتيح التبديل" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:72 +#, python-format +msgid "UNINSTALL" +msgstr "إلغاء التثبيت" + +#: /home/batman/openpilot/selfdrive/ui/layouts/home.py:155 +#, python-format +msgid "UPDATE" +msgstr "تحديث" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:72 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:163 +#, python-format +msgid "Uninstall" +msgstr "إلغاء التثبيت" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:117 +msgid "Unknown" +msgstr "غير معروف" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:48 +#, python-format +msgid "Updates are only downloaded while the car is off." +msgstr "يتم تنزيل التحديثات فقط عندما تكون السيارة متوقفة." + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:33 +#, python-format +msgid "Upgrade Now" +msgstr "الترقية الآن" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +msgid "" +"Upload data from the driver facing camera and help improve the driver " +"monitoring algorithm." +msgstr "" +"ارفع بيانات من كاميرا مواجهة السائق وساعد في تحسين خوارزمية مراقبة السائق." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:88 +#, python-format +msgid "Use Metric System" +msgstr "استخدام النظام المتري" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:17 +msgid "" +"Use the openpilot system for adaptive cruise control and lane keep driver " +"assistance. Your attention is required at all times to use this feature." +msgstr "" +"استخدم نظام openpilot للتحكم الذكي بالسرعة والمساعدة على البقاء داخل المسار. " +"يتطلب استخدام هذه الميزة انتباهك الكامل في جميع الأوقات." + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:144 +msgid "VEHICLE" +msgstr "المركبة" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:67 +#, python-format +msgid "VIEW" +msgstr "عرض" + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:52 +#, python-format +msgid "Waiting to start" +msgstr "بانتظار البدء" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:19 +msgid "" +"Warning: This grants SSH access to all public keys in your GitHub settings. " +"Never enter a GitHub username other than your own. A comma employee will " +"NEVER ask you to add their GitHub username." +msgstr "" +"تحذير: يمنح هذا وصول SSH إلى جميع المفاتيح العامة في إعدادات GitHub الخاصة " +"بك. لا تُدخل مطلقاً اسم مستخدم GitHub غير اسمك. لن يطلب منك موظف في comma أبداً " +"إضافة اسم مستخدمهم." + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:111 +#, python-format +msgid "Welcome to openpilot" +msgstr "مرحباً بك في openpilot" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:20 +msgid "When enabled, pressing the accelerator pedal will disengage openpilot." +msgstr "عند التمكين، سيؤدي الضغط على دواسة الوقود إلى فصل openpilot." + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:44 +msgid "Wi-Fi" +msgstr "Wi‑Fi" + +#: /home/batman/openpilot/system/ui/widgets/network.py:144 +#, python-format +msgid "Wi-Fi Network Metered" +msgstr "شبكة Wi‑Fi محدودة التعرفة" + +#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#, python-format +msgid "Wrong password" +msgstr "كلمة مرور خاطئة" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:145 +#, python-format +msgid "You must accept the Terms and Conditions in order to use openpilot." +msgstr "يجب عليك قبول الشروط والأحكام لاستخدام openpilot." + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:112 +#, python-format +msgid "" +"You must accept the Terms and Conditions to use openpilot. Read the latest " +"terms at https://comma.ai/terms before continuing." +msgstr "" +"يجب عليك قبول الشروط والأحكام لاستخدام openpilot. اقرأ أحدث الشروط على " +"https://comma.ai/terms قبل المتابعة." + +#: /home/batman/openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:34 +#, python-format +msgid "camera starting" +msgstr "بدء تشغيل الكاميرا" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:63 +#, python-format +msgid "comma prime" +msgstr "comma prime" + +#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#, python-format +msgid "default" +msgstr "افتراضي" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#, python-format +msgid "down" +msgstr "أسفل" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:106 +#, python-format +msgid "failed to check for update" +msgstr "فشل التحقق من وجود تحديث" + +#: /home/batman/openpilot/system/ui/widgets/network.py:237 +#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#, python-format +msgid "for \"{}\"" +msgstr "لـ \"{}\"" + +#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:177 +#, python-format +msgid "km/h" +msgstr "كم/س" + +#: /home/batman/openpilot/system/ui/widgets/network.py:204 +#, python-format +msgid "leave blank for automatic configuration" +msgstr "اتركه فارغاً للإعداد التلقائي" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:134 +#, python-format +msgid "left" +msgstr "يسار" + +#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#, python-format +msgid "metered" +msgstr "محدود التعرفة" + +#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:177 +#, python-format +msgid "mph" +msgstr "ميل/س" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:20 +#, python-format +msgid "never" +msgstr "أبداً" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:31 +#, python-format +msgid "now" +msgstr "الآن" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:71 +#, python-format +msgid "openpilot Longitudinal Control (Alpha)" +msgstr "التحكم الطولي لـ openpilot (ألفا)" + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:51 +#, python-format +msgid "openpilot Unavailable" +msgstr "openpilot غير متاح" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:158 +#, python-format +msgid "" +"openpilot defaults to driving in chill mode. Experimental mode enables alpha-" +"level features that aren't ready for chill mode. Experimental features are " +"listed below:

End-to-End Longitudinal Control


Let the driving " +"model control the gas and brakes. openpilot will drive as it thinks a human " +"would, including stopping for red lights and stop signs. Since the driving " +"model decides the speed to drive, the set speed will only act as an upper " +"bound. This is an alpha quality feature; mistakes should be expected." +"

New Driving Visualization


The driving visualization will " +"transition to the road-facing wide-angle camera at low speeds to better show " +"some turns. The Experimental mode logo will also be shown in the top right " +"corner." +msgstr "" +"يعمل openpilot افتراضياً في وضع الهدوء. يفعّل وضع التجربة ميزات بمستوى ألفا " +"غير الجاهزة لوضع الهدوء. الميزات التجريبية مدرجة أدناه:

التحكم الطولي " +"من طرف لطرف


دع نموذج القيادة يتحكم في الوقود والفرامل. سيقود " +"openpilot كما يظن أن الإنسان سيقود، بما في ذلك التوقف عند الإشارات الحمراء " +"وعلامات التوقف. بما أن نموذج القيادة يقرر السرعة، فإن السرعة المضبوطة تعمل " +"كحد أعلى فقط. هذه ميزة بجودة ألفا؛ يُتوقع حدوث أخطاء.

تصوير قيادة " +"جديد


سينتقل عرض القيادة إلى الكاميرا الواسعة المواجهة للطريق عند " +"السرعات المنخفضة لإظهار بعض المنعطفات بشكل أفضل. كما سيظهر شعار وضع التجربة " +"في الزاوية العلوية اليمنى." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:165 +#, python-format +msgid "" +"openpilot is continuously calibrating, resetting is rarely required. " +"Resetting calibration will restart openpilot if the car is powered on." +msgstr "" +"يقوم openpilot بالمعايرة بشكل مستمر، ونادراً ما تتطلب إعادة الضبط. ستؤدي " +"إعادة ضبط المعايرة إلى إعادة تشغيل openpilot إذا كانت السيارة قيد التشغيل." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:20 +msgid "" +"openpilot learns to drive by watching humans, like you, drive.\n" +"\n" +"Firehose Mode allows you to maximize your training data uploads to improve " +"openpilot's driving models. More data means bigger models, which means " +"better Experimental Mode." +msgstr "" +"يتعلم openpilot القيادة بمشاهدة البشر، مثلك، يقودون.\n" +"\n" +"يتيح وضع Firehose زيادة تحميل بيانات التدريب لتحسين نماذج قيادة openpilot. " +"المزيد من البيانات يعني نماذج أكبر، مما يعني وضع تجربة أفضل." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:183 +#, python-format +msgid "openpilot longitudinal control may come in a future update." +msgstr "قد يأتي التحكم الطولي لـ openpilot في تحديث مستقبلي." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:26 +msgid "" +"openpilot requires the device to be mounted within 4° left or right and " +"within 5° up or 9° down." +msgstr "" +"يتطلب openpilot تركيب الجهاز ضمن 4° يساراً أو يميناً وضمن 5° للأعلى أو 9° " +"للأسفل." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:134 +#, python-format +msgid "right" +msgstr "يمين" + +#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#, python-format +msgid "unmetered" +msgstr "غير محدود" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#, python-format +msgid "up" +msgstr "أعلى" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:117 +#, python-format +msgid "up to date, last checked never" +msgstr "محدّث، آخر تحقق: أبداً" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:115 +#, python-format +msgid "up to date, last checked {}" +msgstr "محدّث، آخر تحقق {}" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:109 +#, python-format +msgid "update available" +msgstr "تحديث متاح" + +#: /home/batman/openpilot/selfdrive/ui/layouts/home.py:169 +#, python-format +msgid "{} ALERT" +msgid_plural "{} ALERTS" +msgstr[0] "{} تنبيه" +msgstr[1] "{} تنبيه" +msgstr[2] "{} تنبيهان" +msgstr[3] "{} تنبيهات" +msgstr[4] "{} تنبيهات" +msgstr[5] "{} تنبيه" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:40 +#, python-format +msgid "{} day ago" +msgid_plural "{} days ago" +msgstr[0] "قبل {} يوم" +msgstr[1] "قبل {} يوم" +msgstr[2] "قبل {} يومين" +msgstr[3] "قبل {} أيام" +msgstr[4] "قبل {} أيام" +msgstr[5] "قبل {} يوم" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:37 +#, python-format +msgid "{} hour ago" +msgid_plural "{} hours ago" +msgstr[0] "قبل {} ساعة" +msgstr[1] "قبل {} ساعة" +msgstr[2] "قبل {} ساعتين" +msgstr[3] "قبل {} ساعات" +msgstr[4] "قبل {} ساعات" +msgstr[5] "قبل {} ساعة" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:34 +#, python-format +msgid "{} minute ago" +msgid_plural "{} minutes ago" +msgstr[0] "قبل {} دقيقة" +msgstr[1] "قبل {} دقيقة" +msgstr[2] "قبل {} دقيقتين" +msgstr[3] "قبل {} دقائق" +msgstr[4] "قبل {} دقائق" +msgstr[5] "قبل {} دقيقة" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:111 +#, python-format +msgid "{} segment of your driving is in the training dataset so far." +msgid_plural "{} segments of your driving is in the training dataset so far." +msgstr[0] "{} مقطع من قيادتك ضمن مجموعة بيانات التدريب حتى الآن." +msgstr[1] "{} مقطع من قيادتك ضمن مجموعة بيانات التدريب حتى الآن." +msgstr[2] "{} مقطعان من قيادتك ضمن مجموعة بيانات التدريب حتى الآن." +msgstr[3] "{} مقاطع من قيادتك ضمن مجموعة بيانات التدريب حتى الآن." +msgstr[4] "{} مقاطع من قيادتك ضمن مجموعة بيانات التدريب حتى الآن." +msgstr[5] "{} مقطع من قيادتك ضمن مجموعة بيانات التدريب حتى الآن." + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:62 +#, python-format +msgid "✓ SUBSCRIBED" +msgstr "✓ مشترك" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:22 +#, python-format +msgid "🔥 Firehose Mode 🔥" +msgstr "🔥 وضع Firehose 🔥" diff --git a/selfdrive/ui/translations/app_de.po b/selfdrive/ui/translations/app_de.po index 34b3d8321..cac0faf88 100644 --- a/selfdrive/ui/translations/app_de.po +++ b/selfdrive/ui/translations/app_de.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-22 15:30-0700\n" +"POT-Creation-Date: 2025-10-22 18:57-0700\n" "PO-Revision-Date: 2025-10-20 16:35-0700\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" @@ -17,17 +17,17 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:159 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:160 #, python-format msgid " Steering torque response calibration is complete." msgstr " Die Lenkmoment-Reaktionskalibrierung ist abgeschlossen." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:157 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:158 #, python-format msgid " Steering torque response calibration is {}% complete." msgstr " Die Lenkmoment-Reaktionskalibrierung ist zu {}% abgeschlossen." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:132 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr " Ihr Gerät ist um {:.1f}° {} und {:.1f}° {} ausgerichtet." @@ -76,12 +76,12 @@ msgstr "" "Experimentalmodus wird empfohlen, wenn Sie die openpilot-Längsregelung " "(Alpha) aktivieren." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:147 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:148 #, python-format msgid "

Steering lag calibration is complete." msgstr "

Kalibrierung der Lenkverzögerung abgeschlossen." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:145 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:146 #, python-format msgid "

Steering lag calibration is {}% complete." msgstr "

Kalibrierung der Lenkverzögerung zu {}% abgeschlossen." @@ -144,22 +144,22 @@ msgstr "" "Eine Alpha-Version der openpilot-Längsregelung kann zusammen mit dem " "Experimentalmodus auf Nicht-Release-Zweigen getestet werden." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:186 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:187 #, python-format msgid "Are you sure you want to power off?" msgstr "Sind Sie sicher, dass Sie ausschalten möchten?" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:174 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:175 #, python-format msgid "Are you sure you want to reboot?" msgstr "Sind Sie sicher, dass Sie neu starten möchten?" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:118 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:119 #, python-format msgid "Are you sure you want to reset calibration?" msgstr "Sind Sie sicher, dass Sie die Kalibrierung zurücksetzen möchten?" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:158 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:163 #, python-format msgid "Are you sure you want to uninstall?" msgstr "Sind Sie sicher, dass Sie deinstallieren möchten?" @@ -187,10 +187,10 @@ msgstr "" msgid "CHANGE" msgstr "ÄNDERN" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:49 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:106 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:117 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:142 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:50 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:107 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:118 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:147 #, python-format msgid "CHECK" msgstr "PRÜFEN" @@ -215,7 +215,7 @@ msgid "CONNECTING..." msgstr "VERBINDUNG" #: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:23 -#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:34 +#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:35 #: /home/batman/openpilot/system/ui/widgets/keyboard.py:81 #: /home/batman/openpilot/system/ui/widgets/network.py:318 #, python-format @@ -249,12 +249,12 @@ msgstr "Klicken Sie auf \"add new device\" und scannen Sie den QR‑Code rechts" msgid "Close" msgstr "Schließen" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:48 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:49 #, python-format msgid "Current Version" msgstr "Aktuelle Version" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:109 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:110 #, python-format msgid "DOWNLOAD" msgstr "HERUNTERLADEN" @@ -282,17 +282,17 @@ msgstr "Gerät" msgid "Disengage on Accelerator Pedal" msgstr "Beim Gaspedal deaktivieren" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:183 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:184 #, python-format msgid "Disengage to Power Off" msgstr "Zum Ausschalten deaktivieren" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:171 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:172 #, python-format msgid "Disengage to Reboot" msgstr "Zum Neustart deaktivieren" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:102 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:103 #, python-format msgid "Disengage to Reset Calibration" msgstr "Zum Zurücksetzen der Kalibrierung deaktivieren" @@ -306,7 +306,7 @@ msgstr "Geschwindigkeit in km/h statt mph anzeigen." msgid "Dongle ID" msgstr "Dongle-ID" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:49 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:50 #, python-format msgid "Download" msgstr "Herunterladen" @@ -415,8 +415,8 @@ msgstr "Passwort eingeben" msgid "Enter your GitHub username" msgstr "Geben Sie Ihren GitHub‑Benutzernamen ein" -#: /home/batman/openpilot/system/ui/widgets/list_view.py:115 -#: /home/batman/openpilot/system/ui/widgets/list_view.py:153 +#: /home/batman/openpilot/system/ui/widgets/list_view.py:123 +#: /home/batman/openpilot/system/ui/widgets/list_view.py:160 #, python-format msgid "Error" msgstr "Fehler" @@ -533,8 +533,8 @@ msgstr "Netzwerk" msgid "INACTIVE: connect to an unmetered network" msgstr "INAKTIV: Mit einem unlimitierten Netzwerk verbinden" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:52 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:131 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:53 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:136 #, python-format msgid "INSTALL" msgstr "INSTALLIEREN" @@ -544,7 +544,7 @@ msgstr "INSTALLIEREN" msgid "IP Address" msgstr "IP‑Adresse" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:52 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:53 #, python-format msgid "Install Update" msgstr "Update installieren" @@ -614,7 +614,7 @@ msgstr "Keine Versionshinweise verfügbar." msgid "OFFLINE" msgstr "OFFLINE" -#: /home/batman/openpilot/system/ui/widgets/html_render.py:247 +#: /home/batman/openpilot/system/ui/widgets/html_render.py:263 #: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:93 #: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:127 #, python-format @@ -682,7 +682,7 @@ msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "Bitte mit WLAN verbinden, um das erste Koppeln abzuschließen" #: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:186 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:187 #, python-format msgid "Power Off" msgstr "Ausschalten" @@ -725,7 +725,7 @@ msgid "REVIEW" msgstr "ANSEHEN" #: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:174 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:175 #, python-format msgid "Reboot" msgstr "Neustart" @@ -793,7 +793,7 @@ msgstr "Remote‑Schnappschüsse" msgid "Request timed out" msgstr "Zeitüberschreitung bei der Anfrage" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:118 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:119 #, python-format msgid "Reset" msgstr "Zurücksetzen" @@ -813,6 +813,11 @@ msgid "Review the rules, features, and limitations of openpilot" msgstr "" "Überprüfen Sie die Regeln, Funktionen und Einschränkungen von openpilot" +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:61 +#, python-format +msgid "SELECT" +msgstr "" + #: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:53 #, python-format msgid "SSH Keys" @@ -823,11 +828,16 @@ msgstr "SSH‑Schlüssel" msgid "Scanning Wi-Fi networks..." msgstr "WLAN‑Netzwerke werden gesucht..." -#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:35 +#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:36 #, python-format msgid "Select" msgstr "Auswählen" +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:183 +#, python-format +msgid "Select a branch" +msgstr "" + #: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:91 #, python-format msgid "Select a language" @@ -883,6 +893,11 @@ msgstr "SOFORT DIE KONTROLLE ÜBERNEHMEN" msgid "TEMP" msgstr "TEMP" +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:61 +#, python-format +msgid "Target Branch" +msgstr "" + #: /home/batman/openpilot/system/ui/widgets/network.py:124 #, python-format msgid "Tethering Password" @@ -892,7 +907,7 @@ msgstr "Tethering‑Passwort" msgid "Toggles" msgstr "Schalter" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:70 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:72 #, python-format msgid "UNINSTALL" msgstr "DEINSTALLIEREN" @@ -902,8 +917,8 @@ msgstr "DEINSTALLIEREN" msgid "UPDATE" msgstr "UPDATE" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:70 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:158 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:72 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:163 #, python-format msgid "Uninstall" msgstr "Deinstallieren" @@ -912,7 +927,7 @@ msgstr "Deinstallieren" msgid "Unknown" msgstr "Unbekannt" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:47 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:48 #, python-format msgid "Updates are only downloaded while the car is off." msgstr "Updates werden nur heruntergeladen, wenn das Auto aus ist." @@ -1024,12 +1039,12 @@ msgstr "comma prime" msgid "default" msgstr "Standard" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:132 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid "down" msgstr "unten" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:105 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:106 #, python-format msgid "failed to check for update" msgstr "Überprüfung auf Updates fehlgeschlagen" @@ -1050,7 +1065,7 @@ msgstr "km/h" msgid "leave blank for automatic configuration" msgstr "für automatische Konfiguration leer lassen" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:134 #, python-format msgid "left" msgstr "links" @@ -1065,12 +1080,12 @@ msgstr "getaktet" msgid "mph" msgstr "mph" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:19 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:20 #, python-format msgid "never" msgstr "nie" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:30 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:31 #, python-format msgid "now" msgstr "jetzt" @@ -1113,7 +1128,7 @@ msgstr "" "manche Kurven besser zu zeigen. Das Experimentalmodus‑Logo wird außerdem " "oben rechts angezeigt." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:164 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:165 #, python-format msgid "" "openpilot is continuously calibrating, resetting is rarely required. " @@ -1149,7 +1164,7 @@ msgstr "" "openpilot erfordert, dass das Gerät innerhalb von 4° nach links oder rechts " "und innerhalb von 5° nach oben oder 9° nach unten montiert ist." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:134 #, python-format msgid "right" msgstr "rechts" @@ -1159,22 +1174,22 @@ msgstr "rechts" msgid "unmetered" msgstr "unbegrenzt" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:132 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid "up" msgstr "oben" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:116 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:117 #, python-format msgid "up to date, last checked never" msgstr "Aktuell, zuletzt geprüft: nie" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:114 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:115 #, python-format msgid "up to date, last checked {}" msgstr "Aktuell, zuletzt geprüft: {}" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:108 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:109 #, python-format msgid "update available" msgstr "Update verfügbar" @@ -1186,21 +1201,21 @@ msgid_plural "{} ALERTS" msgstr[0] "{} WARNUNG" msgstr[1] "{} WARNUNGEN" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:39 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:40 #, python-format msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "vor {} Tag" msgstr[1] "vor {} Tagen" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:36 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:37 #, python-format msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "vor {} Stunde" msgstr[1] "vor {} Stunden" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:33 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:34 #, python-format msgid "{} minute ago" msgid_plural "{} minutes ago" diff --git a/selfdrive/ui/translations/app_en.po b/selfdrive/ui/translations/app_en.po index 55554e9d9..66fecf358 100644 --- a/selfdrive/ui/translations/app_en.po +++ b/selfdrive/ui/translations/app_en.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-22 15:30-0700\n" +"POT-Creation-Date: 2025-10-22 18:57-0700\n" "PO-Revision-Date: 2025-10-21 18:18-0700\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" @@ -17,17 +17,17 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:159 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:160 #, python-format msgid " Steering torque response calibration is complete." msgstr " Steering torque response calibration is complete." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:157 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:158 #, python-format msgid " Steering torque response calibration is {}% complete." msgstr " Steering torque response calibration is {}% complete." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:132 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr " Your device is pointed {:.1f}° {} and {:.1f}° {}." @@ -76,12 +76,12 @@ msgstr "" "control alpha. Changing this setting will restart openpilot if the car is " "powered on." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:147 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:148 #, python-format msgid "

Steering lag calibration is complete." msgstr "

Steering lag calibration is complete." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:145 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:146 #, python-format msgid "

Steering lag calibration is {}% complete." msgstr "

Steering lag calibration is {}% complete." @@ -143,22 +143,22 @@ msgstr "" "An alpha version of openpilot longitudinal control can be tested, along with " "Experimental mode, on non-release branches." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:186 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:187 #, python-format msgid "Are you sure you want to power off?" msgstr "Are you sure you want to power off?" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:174 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:175 #, python-format msgid "Are you sure you want to reboot?" msgstr "Are you sure you want to reboot?" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:118 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:119 #, python-format msgid "Are you sure you want to reset calibration?" msgstr "Are you sure you want to reset calibration?" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:158 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:163 #, python-format msgid "Are you sure you want to uninstall?" msgstr "Are you sure you want to uninstall?" @@ -184,10 +184,10 @@ msgstr "Bookmark connect.comma.ai to your home screen to use it like an app" msgid "CHANGE" msgstr "CHANGE" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:49 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:106 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:117 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:142 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:50 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:107 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:118 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:147 #, python-format msgid "CHECK" msgstr "CHECK" @@ -212,7 +212,7 @@ msgid "CONNECTING..." msgstr "CONNECTING..." #: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:23 -#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:34 +#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:35 #: /home/batman/openpilot/system/ui/widgets/keyboard.py:81 #: /home/batman/openpilot/system/ui/widgets/network.py:318 #, python-format @@ -244,12 +244,12 @@ msgstr "Click \"add new device\" and scan the QR code on the right" msgid "Close" msgstr "Close" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:48 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:49 #, python-format msgid "Current Version" msgstr "Current Version" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:109 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:110 #, python-format msgid "DOWNLOAD" msgstr "DOWNLOAD" @@ -277,17 +277,17 @@ msgstr "Device" msgid "Disengage on Accelerator Pedal" msgstr "Disengage on Accelerator Pedal" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:183 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:184 #, python-format msgid "Disengage to Power Off" msgstr "Disengage to Power Off" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:171 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:172 #, python-format msgid "Disengage to Reboot" msgstr "Disengage to Reboot" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:102 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:103 #, python-format msgid "Disengage to Reset Calibration" msgstr "Disengage to Reset Calibration" @@ -301,7 +301,7 @@ msgstr "Display speed in km/h instead of mph." msgid "Dongle ID" msgstr "Dongle ID" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:49 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:50 #, python-format msgid "Download" msgstr "Download" @@ -410,8 +410,8 @@ msgstr "Enter password" msgid "Enter your GitHub username" msgstr "Enter your GitHub username" -#: /home/batman/openpilot/system/ui/widgets/list_view.py:115 -#: /home/batman/openpilot/system/ui/widgets/list_view.py:153 +#: /home/batman/openpilot/system/ui/widgets/list_view.py:123 +#: /home/batman/openpilot/system/ui/widgets/list_view.py:160 #, python-format msgid "Error" msgstr "Error" @@ -527,8 +527,8 @@ msgstr "Hidden Network" msgid "INACTIVE: connect to an unmetered network" msgstr "INACTIVE: connect to an unmetered network" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:52 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:131 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:53 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:136 #, python-format msgid "INSTALL" msgstr "INSTALL" @@ -538,7 +538,7 @@ msgstr "INSTALL" msgid "IP Address" msgstr "IP Address" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:52 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:53 #, python-format msgid "Install Update" msgstr "Install Update" @@ -607,7 +607,7 @@ msgstr "No release notes available." msgid "OFFLINE" msgstr "OFFLINE" -#: /home/batman/openpilot/system/ui/widgets/html_render.py:247 +#: /home/batman/openpilot/system/ui/widgets/html_render.py:263 #: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:93 #: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:127 #, python-format @@ -675,7 +675,7 @@ msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "Please connect to Wi-Fi to complete initial pairing" #: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:186 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:187 #, python-format msgid "Power Off" msgstr "Power Off" @@ -718,7 +718,7 @@ msgid "REVIEW" msgstr "REVIEW" #: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:174 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:175 #, python-format msgid "Reboot" msgstr "Reboot" @@ -786,7 +786,7 @@ msgstr "Remote snapshots" msgid "Request timed out" msgstr "Request timed out" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:118 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:119 #, python-format msgid "Reset" msgstr "Reset" @@ -805,6 +805,11 @@ msgstr "Review Training Guide" msgid "Review the rules, features, and limitations of openpilot" msgstr "Review the rules, features, and limitations of openpilot" +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:61 +#, python-format +msgid "SELECT" +msgstr "" + #: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:53 #, python-format msgid "SSH Keys" @@ -815,11 +820,16 @@ msgstr "SSH Keys" msgid "Scanning Wi-Fi networks..." msgstr "Scanning Wi-Fi networks..." -#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:35 +#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:36 #, python-format msgid "Select" msgstr "Select" +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:183 +#, python-format +msgid "Select a branch" +msgstr "" + #: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:91 #, python-format msgid "Select a language" @@ -874,6 +884,11 @@ msgstr "TAKE CONTROL IMMEDIATELY" msgid "TEMP" msgstr "TEMP" +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:61 +#, python-format +msgid "Target Branch" +msgstr "" + #: /home/batman/openpilot/system/ui/widgets/network.py:124 #, python-format msgid "Tethering Password" @@ -883,7 +898,7 @@ msgstr "Tethering Password" msgid "Toggles" msgstr "Toggles" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:70 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:72 #, python-format msgid "UNINSTALL" msgstr "UNINSTALL" @@ -893,8 +908,8 @@ msgstr "UNINSTALL" msgid "UPDATE" msgstr "UPDATE" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:70 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:158 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:72 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:163 #, python-format msgid "Uninstall" msgstr "Uninstall" @@ -903,7 +918,7 @@ msgstr "Uninstall" msgid "Unknown" msgstr "Unknown" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:47 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:48 #, python-format msgid "Updates are only downloaded while the car is off." msgstr "Updates are only downloaded while the car is off." @@ -1011,12 +1026,12 @@ msgstr "comma prime" msgid "default" msgstr "default" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:132 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid "down" msgstr "down" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:105 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:106 #, python-format msgid "failed to check for update" msgstr "failed to check for update" @@ -1037,7 +1052,7 @@ msgstr "km/h" msgid "leave blank for automatic configuration" msgstr "leave blank for automatic configuration" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:134 #, python-format msgid "left" msgstr "left" @@ -1052,12 +1067,12 @@ msgstr "metered" msgid "mph" msgstr "mph" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:19 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:20 #, python-format msgid "never" msgstr "never" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:30 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:31 #, python-format msgid "now" msgstr "now" @@ -1099,7 +1114,7 @@ msgstr "" "some turns. The Experimental mode logo will also be shown in the top right " "corner." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:164 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:165 #, python-format msgid "" "openpilot is continuously calibrating, resetting is rarely required. " @@ -1135,7 +1150,7 @@ msgstr "" "openpilot requires the device to be mounted within 4° left or right and " "within 5° up or 9° down." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:134 #, python-format msgid "right" msgstr "right" @@ -1145,22 +1160,22 @@ msgstr "right" msgid "unmetered" msgstr "unmetered" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:132 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid "up" msgstr "up" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:116 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:117 #, python-format msgid "up to date, last checked never" msgstr "up to date, last checked never" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:114 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:115 #, python-format msgid "up to date, last checked {}" msgstr "up to date, last checked {}" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:108 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:109 #, python-format msgid "update available" msgstr "update available" @@ -1172,21 +1187,21 @@ msgid_plural "{} ALERTS" msgstr[0] "{} ALERT" msgstr[1] "{} ALERTS" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:39 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:40 #, python-format msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "{} day ago" msgstr[1] "{} days ago" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:36 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:37 #, python-format msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "{} hour ago" msgstr[1] "{} hours ago" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:33 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:34 #, python-format msgid "{} minute ago" msgid_plural "{} minutes ago" diff --git a/selfdrive/ui/translations/app_es.po b/selfdrive/ui/translations/app_es.po index a7424c091..04dffbf3e 100644 --- a/selfdrive/ui/translations/app_es.po +++ b/selfdrive/ui/translations/app_es.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-22 15:30-0700\n" +"POT-Creation-Date: 2025-10-22 18:57-0700\n" "PO-Revision-Date: 2025-10-20 16:35-0700\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" @@ -17,17 +17,17 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:159 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:160 #, python-format msgid " Steering torque response calibration is complete." msgstr " La calibración de respuesta de par de dirección está completa." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:157 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:158 #, python-format msgid " Steering torque response calibration is {}% complete." msgstr " La calibración de respuesta de par de dirección está {}% completa." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:132 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr " Tu dispositivo está orientado {:.1f}° {} y {:.1f}° {}." @@ -75,12 +75,12 @@ msgstr "" "cambiar al control longitudinal de openpilot. Se recomienda activar el modo " "Experimental al habilitar el control longitudinal de openpilot (alpha)." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:147 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:148 #, python-format msgid "

Steering lag calibration is complete." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:145 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:146 #, python-format msgid "

Steering lag calibration is {}% complete." msgstr "" @@ -143,22 +143,22 @@ msgstr "" "Se puede probar una versión alpha del control longitudinal de openpilot, " "junto con el modo Experimental, en ramas que no son de lanzamiento." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:186 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:187 #, python-format msgid "Are you sure you want to power off?" msgstr "¿Seguro que quieres apagar?" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:174 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:175 #, python-format msgid "Are you sure you want to reboot?" msgstr "¿Seguro que quieres reiniciar?" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:118 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:119 #, python-format msgid "Are you sure you want to reset calibration?" msgstr "¿Seguro que quieres restablecer la calibración?" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:158 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:163 #, python-format msgid "Are you sure you want to uninstall?" msgstr "¿Seguro que quieres desinstalar?" @@ -185,10 +185,10 @@ msgstr "" msgid "CHANGE" msgstr "CAMBIAR" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:49 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:106 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:117 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:142 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:50 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:107 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:118 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:147 #, python-format msgid "CHECK" msgstr "COMPROBAR" @@ -213,7 +213,7 @@ msgid "CONNECTING..." msgstr "CONECTAR" #: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:23 -#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:34 +#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:35 #: /home/batman/openpilot/system/ui/widgets/keyboard.py:81 #: /home/batman/openpilot/system/ui/widgets/network.py:318 #, python-format @@ -247,12 +247,12 @@ msgstr "" msgid "Close" msgstr "Cerrar" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:48 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:49 #, python-format msgid "Current Version" msgstr "Versión actual" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:109 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:110 #, python-format msgid "DOWNLOAD" msgstr "DESCARGAR" @@ -280,17 +280,17 @@ msgstr "Dispositivo" msgid "Disengage on Accelerator Pedal" msgstr "Desactivar con el pedal del acelerador" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:183 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:184 #, python-format msgid "Disengage to Power Off" msgstr "Desactivar para apagar" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:171 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:172 #, python-format msgid "Disengage to Reboot" msgstr "Desactivar para reiniciar" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:102 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:103 #, python-format msgid "Disengage to Reset Calibration" msgstr "Desactivar para restablecer la calibración" @@ -304,7 +304,7 @@ msgstr "Mostrar la velocidad en km/h en lugar de mph." msgid "Dongle ID" msgstr "ID del dongle" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:49 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:50 #, python-format msgid "Download" msgstr "Descargar" @@ -415,8 +415,8 @@ msgstr "" msgid "Enter your GitHub username" msgstr "Introduce tu nombre de usuario de GitHub" -#: /home/batman/openpilot/system/ui/widgets/list_view.py:115 -#: /home/batman/openpilot/system/ui/widgets/list_view.py:153 +#: /home/batman/openpilot/system/ui/widgets/list_view.py:123 +#: /home/batman/openpilot/system/ui/widgets/list_view.py:160 #, python-format msgid "Error" msgstr "" @@ -531,8 +531,8 @@ msgstr "Red" msgid "INACTIVE: connect to an unmetered network" msgstr "INACTIVO: conéctate a una red sin límites" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:52 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:131 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:53 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:136 #, python-format msgid "INSTALL" msgstr "INSTALAR" @@ -542,7 +542,7 @@ msgstr "INSTALAR" msgid "IP Address" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:52 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:53 #, python-format msgid "Install Update" msgstr "Instalar actualización" @@ -612,7 +612,7 @@ msgstr "No hay notas de versión disponibles." msgid "OFFLINE" msgstr "SIN CONEXIÓN" -#: /home/batman/openpilot/system/ui/widgets/html_render.py:247 +#: /home/batman/openpilot/system/ui/widgets/html_render.py:263 #: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:93 #: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:127 #, python-format @@ -680,7 +680,7 @@ msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "Conéctate a Wi‑Fi para completar el emparejamiento inicial" #: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:186 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:187 #, python-format msgid "Power Off" msgstr "Apagar" @@ -724,7 +724,7 @@ msgid "REVIEW" msgstr "REVISAR" #: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:174 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:175 #, python-format msgid "Reboot" msgstr "Reiniciar" @@ -792,7 +792,7 @@ msgstr "Capturas remotas" msgid "Request timed out" msgstr "Se agotó el tiempo de espera de la solicitud" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:118 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:119 #, python-format msgid "Reset" msgstr "Restablecer" @@ -811,6 +811,11 @@ msgstr "Revisar guía de entrenamiento" msgid "Review the rules, features, and limitations of openpilot" msgstr "Revisa las reglas, funciones y limitaciones de openpilot" +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:61 +#, python-format +msgid "SELECT" +msgstr "" + #: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:53 #, python-format msgid "SSH Keys" @@ -821,11 +826,16 @@ msgstr "" msgid "Scanning Wi-Fi networks..." msgstr "" -#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:35 +#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:36 #, python-format msgid "Select" msgstr "" +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:183 +#, python-format +msgid "Select a branch" +msgstr "" + #: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:91 #, python-format msgid "Select a language" @@ -881,6 +891,11 @@ msgstr "TOME EL CONTROL INMEDIATAMENTE" msgid "TEMP" msgstr "TEMP" +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:61 +#, python-format +msgid "Target Branch" +msgstr "" + #: /home/batman/openpilot/system/ui/widgets/network.py:124 #, python-format msgid "Tethering Password" @@ -890,7 +905,7 @@ msgstr "" msgid "Toggles" msgstr "Interruptores" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:70 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:72 #, python-format msgid "UNINSTALL" msgstr "DESINSTALAR" @@ -900,8 +915,8 @@ msgstr "DESINSTALAR" msgid "UPDATE" msgstr "ACTUALIZAR" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:70 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:158 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:72 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:163 #, python-format msgid "Uninstall" msgstr "Desinstalar" @@ -910,7 +925,7 @@ msgstr "Desinstalar" msgid "Unknown" msgstr "Desconocido" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:47 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:48 #, python-format msgid "Updates are only downloaded while the car is off." msgstr "Las actualizaciones solo se descargan cuando el coche está apagado." @@ -1022,12 +1037,12 @@ msgstr "comma prime" msgid "default" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:132 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid "down" msgstr "abajo" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:105 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:106 #, python-format msgid "failed to check for update" msgstr "Error al buscar actualizaciones" @@ -1048,7 +1063,7 @@ msgstr "km/h" msgid "leave blank for automatic configuration" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:134 #, python-format msgid "left" msgstr "izquierda" @@ -1063,12 +1078,12 @@ msgstr "" msgid "mph" msgstr "mph" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:19 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:20 #, python-format msgid "never" msgstr "nunca" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:30 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:31 #, python-format msgid "now" msgstr "ahora" @@ -1112,7 +1127,7 @@ msgstr "" "mostrar mejor algunos giros. El logotipo del modo Experimental también se " "mostrará en la esquina superior derecha." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:164 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:165 #, python-format msgid "" "openpilot is continuously calibrating, resetting is rarely required. " @@ -1149,7 +1164,7 @@ msgstr "" "openpilot requiere que el dispositivo esté montado dentro de 4° a izquierda " "o derecha y dentro de 5° hacia arriba o 9° hacia abajo." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:134 #, python-format msgid "right" msgstr "derecha" @@ -1159,22 +1174,22 @@ msgstr "derecha" msgid "unmetered" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:132 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid "up" msgstr "arriba" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:116 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:117 #, python-format msgid "up to date, last checked never" msgstr "actualizado, última comprobación: nunca" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:114 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:115 #, python-format msgid "up to date, last checked {}" msgstr "actualizado, última comprobación: {}" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:108 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:109 #, python-format msgid "update available" msgstr "actualización disponible" @@ -1186,21 +1201,21 @@ msgid_plural "{} ALERTS" msgstr[0] "{} ALERTA" msgstr[1] "{} ALERTAS" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:39 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:40 #, python-format msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "hace {} día" msgstr[1] "hace {} días" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:36 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:37 #, python-format msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "hace {} hora" msgstr[1] "hace {} horas" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:33 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:34 #, python-format msgid "{} minute ago" msgid_plural "{} minutes ago" diff --git a/selfdrive/ui/translations/app_fr.po b/selfdrive/ui/translations/app_fr.po index 12bdcb3a8..4b0370403 100644 --- a/selfdrive/ui/translations/app_fr.po +++ b/selfdrive/ui/translations/app_fr.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-22 15:30-0700\n" +"POT-Creation-Date: 2025-10-22 18:57-0700\n" "PO-Revision-Date: 2025-10-20 18:19-0700\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" @@ -17,17 +17,17 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:159 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:160 #, python-format msgid " Steering torque response calibration is complete." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:157 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:158 #, python-format msgid " Steering torque response calibration is {}% complete." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:132 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr "" @@ -76,12 +76,12 @@ msgstr "" "est recommandé d'activer le mode expérimental lors de l'activation du " "contrôle longitudinal openpilot alpha." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:147 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:148 #, python-format msgid "

Steering lag calibration is complete." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:145 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:146 #, python-format msgid "

Steering lag calibration is {}% complete." msgstr "" @@ -144,22 +144,22 @@ msgstr "" "Une version alpha du contrôle longitudinal openpilot peut être testée, avec " "le mode expérimental, sur des branches non publiées." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:186 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:187 #, python-format msgid "Are you sure you want to power off?" msgstr "Êtes-vous sûr de vouloir éteindre ?" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:174 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:175 #, python-format msgid "Are you sure you want to reboot?" msgstr "Êtes-vous sûr de vouloir redémarrer ?" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:118 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:119 #, python-format msgid "Are you sure you want to reset calibration?" msgstr "Êtes-vous sûr de vouloir réinitialiser la calibration ?" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:158 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:163 #, python-format msgid "Are you sure you want to uninstall?" msgstr "Êtes-vous sûr de vouloir désinstaller ?" @@ -187,10 +187,10 @@ msgstr "" msgid "CHANGE" msgstr "CHANGER" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:49 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:106 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:117 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:142 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:50 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:107 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:118 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:147 #, python-format msgid "CHECK" msgstr "VÉRIFIER" @@ -215,7 +215,7 @@ msgid "CONNECTING..." msgstr "CONNECTER" #: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:23 -#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:34 +#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:35 #: /home/batman/openpilot/system/ui/widgets/keyboard.py:81 #: /home/batman/openpilot/system/ui/widgets/network.py:318 #, python-format @@ -249,12 +249,12 @@ msgstr "Cliquez sur \"add new device\" et scannez le code QR à droite" msgid "Close" msgstr "Fermer" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:48 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:49 #, python-format msgid "Current Version" msgstr "Version actuelle" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:109 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:110 #, python-format msgid "DOWNLOAD" msgstr "TÉLÉCHARGER" @@ -282,17 +282,17 @@ msgstr "Appareil" msgid "Disengage on Accelerator Pedal" msgstr "Désengager à l'appui sur l'accélérateur" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:183 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:184 #, python-format msgid "Disengage to Power Off" msgstr "Désengager pour éteindre" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:171 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:172 #, python-format msgid "Disengage to Reboot" msgstr "Désengager pour redémarrer" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:102 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:103 #, python-format msgid "Disengage to Reset Calibration" msgstr "Désengager pour réinitialiser la calibration" @@ -306,7 +306,7 @@ msgstr "Afficher la vitesse en km/h au lieu de mph." msgid "Dongle ID" msgstr "ID du dongle" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:49 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:50 #, python-format msgid "Download" msgstr "Télécharger" @@ -417,8 +417,8 @@ msgstr "" msgid "Enter your GitHub username" msgstr "Entrez votre nom d'utilisateur GitHub" -#: /home/batman/openpilot/system/ui/widgets/list_view.py:115 -#: /home/batman/openpilot/system/ui/widgets/list_view.py:153 +#: /home/batman/openpilot/system/ui/widgets/list_view.py:123 +#: /home/batman/openpilot/system/ui/widgets/list_view.py:160 #, python-format msgid "Error" msgstr "" @@ -534,8 +534,8 @@ msgstr "Réseau" msgid "INACTIVE: connect to an unmetered network" msgstr "INACTIF : connectez-vous à un réseau non limité" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:52 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:131 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:53 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:136 #, python-format msgid "INSTALL" msgstr "INSTALLER" @@ -545,7 +545,7 @@ msgstr "INSTALLER" msgid "IP Address" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:52 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:53 #, python-format msgid "Install Update" msgstr "Installer la mise à jour" @@ -615,7 +615,7 @@ msgstr "Aucune note de version disponible." msgid "OFFLINE" msgstr "HORS LIGNE" -#: /home/batman/openpilot/system/ui/widgets/html_render.py:247 +#: /home/batman/openpilot/system/ui/widgets/html_render.py:263 #: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:93 #: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:127 #, python-format @@ -683,7 +683,7 @@ msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "Veuillez vous connecter au Wi‑Fi pour terminer l'association initiale" #: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:186 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:187 #, python-format msgid "Power Off" msgstr "Éteindre" @@ -727,7 +727,7 @@ msgid "REVIEW" msgstr "CONSULTER" #: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:174 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:175 #, python-format msgid "Reboot" msgstr "Redémarrer" @@ -795,7 +795,7 @@ msgstr "Captures à distance" msgid "Request timed out" msgstr "Délai de la requête dépassé" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:118 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:119 #, python-format msgid "Reset" msgstr "Réinitialiser" @@ -814,6 +814,11 @@ msgstr "Consulter le guide d'entraînement" msgid "Review the rules, features, and limitations of openpilot" msgstr "Consultez les règles, fonctionnalités et limitations d'openpilot" +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:61 +#, python-format +msgid "SELECT" +msgstr "" + #: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:53 #, python-format msgid "SSH Keys" @@ -824,11 +829,16 @@ msgstr "" msgid "Scanning Wi-Fi networks..." msgstr "" -#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:35 +#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:36 #, python-format msgid "Select" msgstr "" +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:183 +#, python-format +msgid "Select a branch" +msgstr "" + #: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:91 #, python-format msgid "Select a language" @@ -884,6 +894,11 @@ msgstr "REPRENEZ IMMÉDIATEMENT LE CONTRÔLE" msgid "TEMP" msgstr "TEMPÉRATURE" +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:61 +#, python-format +msgid "Target Branch" +msgstr "" + #: /home/batman/openpilot/system/ui/widgets/network.py:124 #, python-format msgid "Tethering Password" @@ -893,7 +908,7 @@ msgstr "" msgid "Toggles" msgstr "Options" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:70 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:72 #, python-format msgid "UNINSTALL" msgstr "DÉSINSTALLER" @@ -903,8 +918,8 @@ msgstr "DÉSINSTALLER" msgid "UPDATE" msgstr "METTRE À JOUR" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:70 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:158 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:72 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:163 #, python-format msgid "Uninstall" msgstr "Désinstaller" @@ -913,7 +928,7 @@ msgstr "Désinstaller" msgid "Unknown" msgstr "Inconnu" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:47 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:48 #, python-format msgid "Updates are only downloaded while the car is off." msgstr "" @@ -1025,12 +1040,12 @@ msgstr "comma prime" msgid "default" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:132 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid "down" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:105 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:106 #, python-format msgid "failed to check for update" msgstr "échec de la vérification de mise à jour" @@ -1051,7 +1066,7 @@ msgstr "km/h" msgid "leave blank for automatic configuration" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:134 #, python-format msgid "left" msgstr "" @@ -1066,12 +1081,12 @@ msgstr "" msgid "mph" msgstr "mph" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:19 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:20 #, python-format msgid "never" msgstr "jamais" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:30 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:31 #, python-format msgid "now" msgstr "maintenant" @@ -1114,7 +1129,7 @@ msgstr "" "pour mieux montrer certains virages. Le logo du mode expérimental sera " "également affiché en haut à droite." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:164 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:165 #, python-format msgid "" "openpilot is continuously calibrating, resetting is rarely required. " @@ -1154,7 +1169,7 @@ msgstr "" "openpilot exige que l'appareil soit monté à moins de 4° à gauche ou à droite " "et à moins de 5° vers le haut ou 9° vers le bas." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:134 #, python-format msgid "right" msgstr "" @@ -1164,22 +1179,22 @@ msgstr "" msgid "unmetered" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:132 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid "up" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:116 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:117 #, python-format msgid "up to date, last checked never" msgstr "à jour, dernière vérification jamais" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:114 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:115 #, python-format msgid "up to date, last checked {}" msgstr "à jour, dernière vérification {}" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:108 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:109 #, python-format msgid "update available" msgstr "mise à jour disponible" @@ -1191,21 +1206,21 @@ msgid_plural "{} ALERTS" msgstr[0] "{} ALERTE" msgstr[1] "{} ALERTES" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:39 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:40 #, python-format msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "il y a {} jour" msgstr[1] "il y a {} jours" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:36 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:37 #, python-format msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "il y a {} heure" msgstr[1] "il y a {} heures" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:33 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:34 #, python-format msgid "{} minute ago" msgid_plural "{} minutes ago" diff --git a/selfdrive/ui/translations/app_ja.po b/selfdrive/ui/translations/app_ja.po new file mode 100644 index 000000000..0ae886963 --- /dev/null +++ b/selfdrive/ui/translations/app_ja.po @@ -0,0 +1,1216 @@ +# Japanese translations for PACKAGE package. +# Copyright (C) 2025 THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# Automatically generated, 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-10-22 18:57-0700\n" +"PO-Revision-Date: 2025-10-22 16:32-0700\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: ja\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:160 +#, python-format +msgid " Steering torque response calibration is complete." +msgstr " ステアリングトルク応答のキャリブレーションが完了しました。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:158 +#, python-format +msgid " Steering torque response calibration is {}% complete." +msgstr " ステアリングトルク応答のキャリブレーションは{}%完了しました。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#, python-format +msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." +msgstr " デバイスは{:.1f}°{}、{:.1f}°{}の向きです。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:43 +msgid "--" +msgstr "--" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "1 year of drive storage" +msgstr "走行データを1年間保存" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "24/7 LTE connectivity" +msgstr "24時間365日のLTE接続" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:46 +msgid "2G" +msgstr "2G" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:47 +msgid "3G" +msgstr "3G" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:49 +msgid "5G" +msgstr "5G" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:23 +msgid "" +"WARNING: openpilot longitudinal control is in alpha for this car and will " +"disable Automatic Emergency Braking (AEB).

On this car, openpilot " +"defaults to the car's built-in ACC instead of openpilot's longitudinal " +"control. Enable this to switch to openpilot longitudinal control. Enabling " +"Experimental mode is recommended when enabling openpilot longitudinal " +"control alpha. Changing this setting will restart openpilot if the car is " +"powered on." +msgstr "" +"警告: この車におけるopenpilotの縦制御はアルファ版であり、自動緊急ブレーキ" +"(AEB)を無効にします。

この車では、openpilotは縦制御として" +"openpilotではなく車両の内蔵ACCを既定で使用します。openpilotの縦制御に切り替え" +"るにはこの設定を有効にしてください。openpilot縦制御アルファを有効にする場合は" +"実験モードの有効化を推奨します。この設定を変更すると、車が起動中の場合は" +"openpilotが再起動します。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:148 +#, python-format +msgid "

Steering lag calibration is complete." +msgstr "

ステアリング遅延のキャリブレーションが完了しました。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:146 +#, python-format +msgid "

Steering lag calibration is {}% complete." +msgstr "

ステアリング遅延のキャリブレーションは{}%完了しました。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:138 +#, python-format +msgid "ACTIVE" +msgstr "アクティブ" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:15 +msgid "" +"ADB (Android Debug Bridge) allows connecting to your device over USB or over " +"the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." +msgstr "" +"ADB(Android Debug Bridge)を使用すると、USBまたはネットワーク経由でデバイス" +"に接続できます。詳しくは https://docs.comma.ai/how-to/connect-to-comma を参照" +"してください。" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:30 +msgid "ADD" +msgstr "追加" + +#: /home/batman/openpilot/system/ui/widgets/network.py:139 +#, python-format +msgid "APN Setting" +msgstr "APN設定" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 +#, python-format +msgid "Acknowledge Excessive Actuation" +msgstr "過度な作動を承認" + +#: /home/batman/openpilot/system/ui/widgets/network.py:74 +#: /home/batman/openpilot/system/ui/widgets/network.py:95 +#, python-format +msgid "Advanced" +msgstr "詳細設定" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#, python-format +msgid "Aggressive" +msgstr "アグレッシブ" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:116 +#, python-format +msgid "Agree" +msgstr "同意する" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:70 +#, python-format +msgid "Always-On Driver Monitoring" +msgstr "常時ドライバーモニタリング" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:186 +#, python-format +msgid "" +"An alpha version of openpilot longitudinal control can be tested, along with " +"Experimental mode, on non-release branches." +msgstr "" +"openpilotの縦制御アルファ版は、実験モードと併せて非リリースブランチでテストで" +"きます。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:187 +#, python-format +msgid "Are you sure you want to power off?" +msgstr "本当に電源をオフにしますか?" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:175 +#, python-format +msgid "Are you sure you want to reboot?" +msgstr "本当に再起動しますか?" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:119 +#, python-format +msgid "Are you sure you want to reset calibration?" +msgstr "本当にキャリブレーションをリセットしますか?" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:163 +#, python-format +msgid "Are you sure you want to uninstall?" +msgstr "本当にアンインストールしますか?" + +#: /home/batman/openpilot/system/ui/widgets/network.py:99 +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:147 +#, python-format +msgid "Back" +msgstr "戻る" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:38 +#, python-format +msgid "Become a comma prime member at connect.comma.ai" +msgstr "connect.comma.aiで comma prime に加入" + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:130 +#, python-format +msgid "Bookmark connect.comma.ai to your home screen to use it like an app" +msgstr "connect.comma.aiをホーム画面に追加してアプリのように使いましょう" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:68 +#, python-format +msgid "CHANGE" +msgstr "変更" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:50 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:107 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:118 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:147 +#, python-format +msgid "CHECK" +msgstr "確認" + +#: /home/batman/openpilot/selfdrive/ui/widgets/exp_mode_button.py:50 +#, python-format +msgid "CHILL MODE ON" +msgstr "チルモードON" + +#: /home/batman/openpilot/system/ui/widgets/network.py:155 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:138 +#, python-format +msgid "CONNECT" +msgstr "接続" + +#: /home/batman/openpilot/system/ui/widgets/network.py:369 +#, python-format +msgid "CONNECTING..." +msgstr "接続中..." + +#: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:23 +#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:35 +#: /home/batman/openpilot/system/ui/widgets/keyboard.py:81 +#: /home/batman/openpilot/system/ui/widgets/network.py:318 +#, python-format +msgid "Cancel" +msgstr "キャンセル" + +#: /home/batman/openpilot/system/ui/widgets/network.py:134 +#, python-format +msgid "Cellular Metered" +msgstr "従量課金の携帯回線" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:68 +#, python-format +msgid "Change Language" +msgstr "言語を変更" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:125 +#, python-format +msgid "Changing this setting will restart openpilot if the car is powered on." +msgstr "車が起動中の場合、この設定を変更するとopenpilotが再起動します。" + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:129 +#, python-format +msgid "Click \"add new device\" and scan the QR code on the right" +msgstr "\"add new device\"を押して右側のQRコードをスキャン" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 +#, python-format +msgid "Close" +msgstr "閉じる" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:49 +#, python-format +msgid "Current Version" +msgstr "現在のバージョン" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:110 +#, python-format +msgid "DOWNLOAD" +msgstr "ダウンロード" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:115 +#, python-format +msgid "Decline" +msgstr "拒否する" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:148 +#, python-format +msgid "Decline, uninstall openpilot" +msgstr "拒否してopenpilotをアンインストール" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:67 +msgid "Developer" +msgstr "開発者" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:62 +msgid "Device" +msgstr "デバイス" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:58 +#, python-format +msgid "Disengage on Accelerator Pedal" +msgstr "アクセルで解除" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:184 +#, python-format +msgid "Disengage to Power Off" +msgstr "解除して電源オフ" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:172 +#, python-format +msgid "Disengage to Reboot" +msgstr "解除して再起動" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:103 +#, python-format +msgid "Disengage to Reset Calibration" +msgstr "解除してキャリブレーションをリセット" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:32 +msgid "Display speed in km/h instead of mph." +msgstr "速度をmphではなくkm/hで表示します。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:59 +#, python-format +msgid "Dongle ID" +msgstr "ドングルID" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:50 +#, python-format +msgid "Download" +msgstr "ダウンロード" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:62 +#, python-format +msgid "Driver Camera" +msgstr "ドライバーカメラ" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:96 +#, python-format +msgid "Driving Personality" +msgstr "走行性格" + +#: /home/batman/openpilot/system/ui/widgets/network.py:123 +#: /home/batman/openpilot/system/ui/widgets/network.py:139 +#, python-format +msgid "EDIT" +msgstr "編集" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:138 +msgid "ERROR" +msgstr "エラー" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:45 +msgid "ETH" +msgstr "ETH" + +#: /home/batman/openpilot/selfdrive/ui/widgets/exp_mode_button.py:50 +#, python-format +msgid "EXPERIMENTAL MODE ON" +msgstr "実験モードON" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:166 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:228 +#, python-format +msgid "Enable" +msgstr "有効化" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:39 +#, python-format +msgid "Enable ADB" +msgstr "ADBを有効化" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:64 +#, python-format +msgid "Enable Lane Departure Warnings" +msgstr "車線逸脱警報を有効化" + +#: /home/batman/openpilot/system/ui/widgets/network.py:129 +#, python-format +msgid "Enable Roaming" +msgstr "ローミングを有効化" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:48 +#, python-format +msgid "Enable SSH" +msgstr "SSHを有効化" + +#: /home/batman/openpilot/system/ui/widgets/network.py:120 +#, python-format +msgid "Enable Tethering" +msgstr "テザリングを有効化" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:30 +msgid "Enable driver monitoring even when openpilot is not engaged." +msgstr "openpilotが未作動でもドライバーモニタリングを有効にします。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:46 +#, python-format +msgid "Enable openpilot" +msgstr "openpilotを有効化" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:189 +#, python-format +msgid "" +"Enable the openpilot longitudinal control (alpha) toggle to allow " +"Experimental mode." +msgstr "" +"openpilot縦制御(アルファ)のトグルを有効にすると実験モードが使用できます。" + +#: /home/batman/openpilot/system/ui/widgets/network.py:204 +#, python-format +msgid "Enter APN" +msgstr "APNを入力" + +#: /home/batman/openpilot/system/ui/widgets/network.py:241 +#, python-format +msgid "Enter SSID" +msgstr "SSIDを入力" + +#: /home/batman/openpilot/system/ui/widgets/network.py:254 +#, python-format +msgid "Enter new tethering password" +msgstr "新しいテザリングのパスワードを入力" + +#: /home/batman/openpilot/system/ui/widgets/network.py:237 +#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#, python-format +msgid "Enter password" +msgstr "パスワードを入力" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:89 +#, python-format +msgid "Enter your GitHub username" +msgstr "GitHubユーザー名を入力" + +#: /home/batman/openpilot/system/ui/widgets/list_view.py:123 +#: /home/batman/openpilot/system/ui/widgets/list_view.py:160 +#, python-format +msgid "Error" +msgstr "エラー" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:52 +#, python-format +msgid "Experimental Mode" +msgstr "実験モード" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:181 +#, python-format +msgid "" +"Experimental mode is currently unavailable on this car since the car's stock " +"ACC is used for longitudinal control." +msgstr "" +"この車では縦制御に純正ACCを使用するため、現在実験モードは利用できません。" + +#: /home/batman/openpilot/system/ui/widgets/network.py:373 +#, python-format +msgid "FORGETTING..." +msgstr "削除中..." + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:44 +#, python-format +msgid "Finish Setup" +msgstr "セットアップを完了" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:66 +msgid "Firehose" +msgstr "Firehose" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:18 +msgid "Firehose Mode" +msgstr "Firehoseモード" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:25 +msgid "" +"For maximum effectiveness, bring your device inside and connect to a good " +"USB-C adapter and Wi-Fi weekly.\n" +"\n" +"Firehose Mode can also work while you're driving if connected to a hotspot " +"or unlimited SIM card.\n" +"\n" +"\n" +"Frequently Asked Questions\n" +"\n" +"Does it matter how or where I drive? Nope, just drive as you normally " +"would.\n" +"\n" +"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a " +"subset of your segments.\n" +"\n" +"What's a good USB-C adapter? Any fast phone or laptop charger should be " +"fine.\n" +"\n" +"Does it matter which software I run? Yes, only upstream openpilot (and " +"particular forks) are able to be used for training." +msgstr "" +"最大限の効果を得るため、デバイスを屋内に持ち込み、週に一度は品質の良いUSB-Cア" +"ダプターとWi‑Fiに接続してください。\n" +"\n" +"Firehoseモードは、ホットスポットや無制限SIMに接続していれば走行中でも動作しま" +"す。\n" +"\n" +"\n" +"よくある質問\n" +"\n" +"運転の仕方や場所は関係ありますか? いいえ。普段どおりに運転してください。\n" +"\n" +"Firehoseモードではすべてのセグメントが取得されますか? いいえ。セグメントの一" +"部を選択的に取得します。\n" +"\n" +"良いUSB‑Cアダプターとは? 高速なスマホまたはノートPC用充電器で問題ありませ" +"ん。\n" +"\n" +"どのソフトウェアを使うかは重要ですか? はい。学習に使えるのは上流のopenpilot" +"(および特定のフォーク)のみです。" + +#: /home/batman/openpilot/system/ui/widgets/network.py:318 +#: /home/batman/openpilot/system/ui/widgets/network.py:451 +#, python-format +msgid "Forget" +msgstr "削除" + +#: /home/batman/openpilot/system/ui/widgets/network.py:319 +#, python-format +msgid "Forget Wi-Fi Network \"{}\"?" +msgstr "Wi‑Fiネットワーク「{}」を削除しますか?" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:125 +msgid "GOOD" +msgstr "良好" + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:128 +#, python-format +msgid "Go to https://connect.comma.ai on your phone" +msgstr "スマートフォンで https://connect.comma.ai にアクセス" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:129 +msgid "HIGH" +msgstr "高温" + +#: /home/batman/openpilot/system/ui/widgets/network.py:155 +#, python-format +msgid "Hidden Network" +msgstr "非公開ネットワーク" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:140 +#, python-format +msgid "INACTIVE: connect to an unmetered network" +msgstr "非アクティブ:非従量のネットワークに接続してください" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:53 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:136 +#, python-format +msgid "INSTALL" +msgstr "インストール" + +#: /home/batman/openpilot/system/ui/widgets/network.py:150 +#, python-format +msgid "IP Address" +msgstr "IPアドレス" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:53 +#, python-format +msgid "Install Update" +msgstr "アップデートをインストール" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:56 +#, python-format +msgid "Joystick Debug Mode" +msgstr "ジョイスティックデバッグモード" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:29 +msgid "LOADING" +msgstr "読み込み中" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:48 +msgid "LTE" +msgstr "LTE" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:64 +#, python-format +msgid "Longitudinal Maneuver Mode" +msgstr "縦制御マヌーバーモード" + +#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:148 +#, python-format +msgid "MAX" +msgstr "最大" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:75 +#, python-format +msgid "" +"Maximize your training data uploads to improve openpilot's driving models." +msgstr "" +"学習データのアップロードを最大化してopenpilotの運転モデルを改善しましょう。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:59 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:60 +#, python-format +msgid "N/A" +msgstr "該当なし" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:142 +msgid "NO" +msgstr "いいえ" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:63 +msgid "Network" +msgstr "ネットワーク" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:114 +#, python-format +msgid "No SSH keys found" +msgstr "SSH鍵が見つかりません" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:126 +#, python-format +msgid "No SSH keys found for user '{}'" +msgstr "ユーザー'{}'のSSH鍵が見つかりません" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:320 +#, python-format +msgid "No release notes available." +msgstr "リリースノートはありません。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:134 +msgid "OFFLINE" +msgstr "オフライン" + +#: /home/batman/openpilot/system/ui/widgets/html_render.py:263 +#: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:93 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:127 +#, python-format +msgid "OK" +msgstr "OK" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:144 +msgid "ONLINE" +msgstr "オンライン" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:20 +#, python-format +msgid "Open" +msgstr "開く" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:48 +#, python-format +msgid "PAIR" +msgstr "ペアリング" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:142 +msgid "PANDA" +msgstr "PANDA" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:62 +#, python-format +msgid "PREVIEW" +msgstr "プレビュー" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:44 +#, python-format +msgid "PRIME FEATURES:" +msgstr "prime の特典:" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:48 +#, python-format +msgid "Pair Device" +msgstr "デバイスをペアリング" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:19 +#, python-format +msgid "Pair device" +msgstr "デバイスをペアリング" + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:103 +#, python-format +msgid "Pair your device to your comma account" +msgstr "デバイスをあなたの comma アカウントにペアリング" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:48 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:24 +#, python-format +msgid "" +"Pair your device with comma connect (connect.comma.ai) and claim your comma " +"prime offer." +msgstr "" +"デバイスを comma connect(connect.comma.ai)とペアリングして、comma prime 特" +"典を受け取りましょう。" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:91 +#, python-format +msgid "Please connect to Wi-Fi to complete initial pairing" +msgstr "初回ペアリングを完了するにはWi‑Fiに接続してください" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:187 +#, python-format +msgid "Power Off" +msgstr "電源オフ" + +#: /home/batman/openpilot/system/ui/widgets/network.py:144 +#, python-format +msgid "Prevent large data uploads when on a metered Wi-Fi connection" +msgstr "従量課金のWi‑Fi接続時は大きなデータのアップロードを抑制" + +#: /home/batman/openpilot/system/ui/widgets/network.py:135 +#, python-format +msgid "Prevent large data uploads when on a metered cellular connection" +msgstr "従量課金の携帯回線接続時は大きなデータのアップロードを抑制" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:25 +msgid "" +"Preview the driver facing camera to ensure that driver monitoring has good " +"visibility. (vehicle must be off)" +msgstr "" +"ドライバー向きカメラのプレビューでモニタリングの視界を確認します。(車両は停" +"止状態である必要があります)" + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:161 +#, python-format +msgid "QR Code Error" +msgstr "QRコードエラー" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:31 +msgid "REMOVE" +msgstr "削除" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:51 +#, python-format +msgid "RESET" +msgstr "リセット" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:65 +#, python-format +msgid "REVIEW" +msgstr "確認" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:175 +#, python-format +msgid "Reboot" +msgstr "再起動" + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:66 +#, python-format +msgid "Reboot Device" +msgstr "デバイスを再起動" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 +#, python-format +msgid "Reboot and Update" +msgstr "再起動して更新" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:27 +msgid "" +"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)." +msgstr "" +"時速31mph(50km/h)を超えて走行中にウインカーを出さず検出された車線を外れた場" +"合、車線内に戻るよう警告を受け取ります。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:76 +#, python-format +msgid "Record and Upload Driver Camera" +msgstr "ドライバーカメラを記録してアップロード" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:82 +#, python-format +msgid "Record and Upload Microphone Audio" +msgstr "マイク音声を記録してアップロード" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +msgid "" +"Record and store microphone audio while driving. The audio will be included " +"in the dashcam video in comma connect." +msgstr "" +"走行中にマイク音声を記録・保存します。音声は comma connect のドライブレコー" +"ダー動画に含まれます。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:67 +#, python-format +msgid "Regulatory" +msgstr "規制情報" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#, python-format +msgid "Relaxed" +msgstr "リラックス" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "Remote access" +msgstr "リモートアクセス" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "Remote snapshots" +msgstr "リモートスナップショット" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:123 +#, python-format +msgid "Request timed out" +msgstr "リクエストがタイムアウトしました" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:119 +#, python-format +msgid "Reset" +msgstr "リセット" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:51 +#, python-format +msgid "Reset Calibration" +msgstr "キャリブレーションをリセット" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:65 +#, python-format +msgid "Review Training Guide" +msgstr "トレーニングガイドを確認" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:27 +msgid "Review the rules, features, and limitations of openpilot" +msgstr "openpilotのルール、機能、制限を確認" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:61 +#, python-format +msgid "SELECT" +msgstr "選択" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:53 +#, python-format +msgid "SSH Keys" +msgstr "SSH鍵" + +#: /home/batman/openpilot/system/ui/widgets/network.py:310 +#, python-format +msgid "Scanning Wi-Fi networks..." +msgstr "Wi‑Fiネットワークを検索中..." + +#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:36 +#, python-format +msgid "Select" +msgstr "選択" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:183 +#, python-format +msgid "Select a branch" +msgstr "ブランチを選択" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:91 +#, python-format +msgid "Select a language" +msgstr "言語を選択" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:60 +#, python-format +msgid "Serial" +msgstr "シリアル" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 +#, python-format +msgid "Snooze Update" +msgstr "更新を後で通知" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:65 +msgid "Software" +msgstr "ソフトウェア" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#, python-format +msgid "Standard" +msgstr "スタンダード" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:22 +msgid "" +"Standard is recommended. In aggressive mode, openpilot will follow lead cars " +"closer and be more aggressive with the gas and brake. In relaxed mode " +"openpilot will stay further away from lead cars. On supported cars, you can " +"cycle through these personalities with your steering wheel distance button." +msgstr "" +"標準を推奨します。アグレッシブでは前走車に近づき、加減速も積極的になります。" +"リラックスでは前走車との距離を保ちます。対応車種ではステアリングの車間ボタン" +"でこれらの性格を切り替えられます。" + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:59 +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:65 +#, python-format +msgid "System Unresponsive" +msgstr "システムが応答しません" + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:58 +#, python-format +msgid "TAKE CONTROL IMMEDIATELY" +msgstr "すぐに手動介入してください" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:129 +msgid "TEMP" +msgstr "温度" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:61 +#, python-format +msgid "Target Branch" +msgstr "対象ブランチ" + +#: /home/batman/openpilot/system/ui/widgets/network.py:124 +#, python-format +msgid "Tethering Password" +msgstr "テザリングのパスワード" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:64 +msgid "Toggles" +msgstr "トグル" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:72 +#, python-format +msgid "UNINSTALL" +msgstr "アンインストール" + +#: /home/batman/openpilot/selfdrive/ui/layouts/home.py:155 +#, python-format +msgid "UPDATE" +msgstr "更新" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:72 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:163 +#, python-format +msgid "Uninstall" +msgstr "アンインストール" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:117 +msgid "Unknown" +msgstr "不明" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:48 +#, python-format +msgid "Updates are only downloaded while the car is off." +msgstr "アップデートは車両の電源が切れている間のみダウンロードされます。" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:33 +#, python-format +msgid "Upgrade Now" +msgstr "今すぐアップグレード" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +msgid "" +"Upload data from the driver facing camera and help improve the driver " +"monitoring algorithm." +msgstr "" +"ドライバー向きカメラのデータをアップロードしてモニタリングアルゴリズムの改善" +"に協力してください。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:88 +#, python-format +msgid "Use Metric System" +msgstr "メートル法を使用" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:17 +msgid "" +"Use the openpilot system for adaptive cruise control and lane keep driver " +"assistance. Your attention is required at all times to use this feature." +msgstr "" +"ACCと車線維持支援にopenpilotを使用します。本機能の使用中は常に注意が必要で" +"す。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:144 +msgid "VEHICLE" +msgstr "車両" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:67 +#, python-format +msgid "VIEW" +msgstr "表示" + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:52 +#, python-format +msgid "Waiting to start" +msgstr "開始待機中" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:19 +msgid "" +"Warning: This grants SSH access to all public keys in your GitHub settings. " +"Never enter a GitHub username other than your own. A comma employee will " +"NEVER ask you to add their GitHub username." +msgstr "" +"警告: これはGitHub設定内のすべての公開鍵にSSHアクセスを与えます。自分以外の" +"GitHubユーザー名を絶対に入力しないでください。comma の従業員が自分のGitHub" +"ユーザー名を追加するよう求めることは決してありません。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:111 +#, python-format +msgid "Welcome to openpilot" +msgstr "openpilotへようこそ" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:20 +msgid "When enabled, pressing the accelerator pedal will disengage openpilot." +msgstr "有効にすると、アクセルを踏むとopenpilotが解除されます。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:44 +msgid "Wi-Fi" +msgstr "Wi‑Fi" + +#: /home/batman/openpilot/system/ui/widgets/network.py:144 +#, python-format +msgid "Wi-Fi Network Metered" +msgstr "Wi‑Fiネットワーク(従量課金)" + +#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#, python-format +msgid "Wrong password" +msgstr "パスワードが違います" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:145 +#, python-format +msgid "You must accept the Terms and Conditions in order to use openpilot." +msgstr "openpilotを使用するには、利用規約に同意する必要があります。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:112 +#, python-format +msgid "" +"You must accept the Terms and Conditions to use openpilot. Read the latest " +"terms at https://comma.ai/terms before continuing." +msgstr "" +"openpilotを使用するには利用規約に同意する必要があります。続行する前に " +"https://comma.ai/terms の最新の規約をお読みください。" + +#: /home/batman/openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:34 +#, python-format +msgid "camera starting" +msgstr "カメラを起動中" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:63 +#, python-format +msgid "comma prime" +msgstr "comma prime" + +#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#, python-format +msgid "default" +msgstr "既定" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#, python-format +msgid "down" +msgstr "下" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:106 +#, python-format +msgid "failed to check for update" +msgstr "アップデートの確認に失敗しました" + +#: /home/batman/openpilot/system/ui/widgets/network.py:237 +#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#, python-format +msgid "for \"{}\"" +msgstr "「{}」向け" + +#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:177 +#, python-format +msgid "km/h" +msgstr "km/h" + +#: /home/batman/openpilot/system/ui/widgets/network.py:204 +#, python-format +msgid "leave blank for automatic configuration" +msgstr "自動設定の場合は空欄のままにしてください" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:134 +#, python-format +msgid "left" +msgstr "左" + +#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#, python-format +msgid "metered" +msgstr "従量" + +#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:177 +#, python-format +msgid "mph" +msgstr "mph" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:20 +#, python-format +msgid "never" +msgstr "なし" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:31 +#, python-format +msgid "now" +msgstr "今" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:71 +#, python-format +msgid "openpilot Longitudinal Control (Alpha)" +msgstr "openpilot 縦制御(アルファ)" + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:51 +#, python-format +msgid "openpilot Unavailable" +msgstr "openpilotは利用できません" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:158 +#, python-format +msgid "" +"openpilot defaults to driving in chill mode. Experimental mode enables alpha-" +"level features that aren't ready for chill mode. Experimental features are " +"listed below:

End-to-End Longitudinal Control


Let the driving " +"model control the gas and brakes. openpilot will drive as it thinks a human " +"would, including stopping for red lights and stop signs. Since the driving " +"model decides the speed to drive, the set speed will only act as an upper " +"bound. This is an alpha quality feature; mistakes should be expected." +"

New Driving Visualization


The driving visualization will " +"transition to the road-facing wide-angle camera at low speeds to better show " +"some turns. The Experimental mode logo will also be shown in the top right " +"corner." +msgstr "" +"openpilotは既定でチルモードで走行します。実験モードでは、チルモードにはまだ準" +"備ができていないアルファレベルの機能が有効になります。実験的な機能は以下のと" +"おりです:

エンドツーエンド縦制御


運転モデルがアクセルとブレー" +"キを制御します。openpilotは人間のように走行し、赤信号や一時停止でも停止しま" +"す。走行速度は運転モデルが決めるため、設定速度は上限としてのみ機能します。こ" +"れはアルファ品質の機能であり、誤動作が発生する可能性があります。

新し" +"い運転ビジュアライゼーション


低速時には道路向きの広角カメラに切り替わ" +"り、一部の曲がりをより良く表示します。画面右上には実験モードのロゴも表示され" +"ます。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:165 +#, python-format +msgid "" +"openpilot is continuously calibrating, resetting is rarely required. " +"Resetting calibration will restart openpilot if the car is powered on." +msgstr "" +"openpilotは継続的にキャリブレーションを行っており、リセットが必要になることは" +"稀です。車が起動中にキャリブレーションをリセットするとopenpilotが再起動しま" +"す。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:20 +msgid "" +"openpilot learns to drive by watching humans, like you, drive.\n" +"\n" +"Firehose Mode allows you to maximize your training data uploads to improve " +"openpilot's driving models. More data means bigger models, which means " +"better Experimental Mode." +msgstr "" +"openpilotは、あなたのような人間の運転を見て運転を学習します。\n" +"\n" +"Firehoseモードを使うと、学習データのアップロードを最大化してopenpilotの運転モ" +"デルを改善できます。データが増えるほどモデルが大きくなり、実験モードがより良" +"くなります。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:183 +#, python-format +msgid "openpilot longitudinal control may come in a future update." +msgstr "openpilotの縦制御は将来のアップデートで提供される可能性があります。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:26 +msgid "" +"openpilot requires the device to be mounted within 4° left or right and " +"within 5° up or 9° down." +msgstr "" +"openpilotでは、デバイスの取り付け角度が左右±4°、上方向5°以内、下方向9°以内で" +"ある必要があります。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:134 +#, python-format +msgid "right" +msgstr "右" + +#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#, python-format +msgid "unmetered" +msgstr "非従量" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#, python-format +msgid "up" +msgstr "上" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:117 +#, python-format +msgid "up to date, last checked never" +msgstr "最新です。最終確認: なし" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:115 +#, python-format +msgid "up to date, last checked {}" +msgstr "最新です。最終確認: {}" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:109 +#, python-format +msgid "update available" +msgstr "更新があります" + +#: /home/batman/openpilot/selfdrive/ui/layouts/home.py:169 +#, python-format +msgid "{} ALERT" +msgid_plural "{} ALERTS" +msgstr[0] "{}件のアラート" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:40 +#, python-format +msgid "{} day ago" +msgid_plural "{} days ago" +msgstr[0] "{}日前" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:37 +#, python-format +msgid "{} hour ago" +msgid_plural "{} hours ago" +msgstr[0] "{}時間前" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:34 +#, python-format +msgid "{} minute ago" +msgid_plural "{} minutes ago" +msgstr[0] "{}分前" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:111 +#, python-format +msgid "{} segment of your driving is in the training dataset so far." +msgid_plural "{} segments of your driving is in the training dataset so far." +msgstr[0] "" +"これまでにあなたの走行の{}セグメントが学習データセットに含まれています。" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:62 +#, python-format +msgid "✓ SUBSCRIBED" +msgstr "✓ 登録済み" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:22 +#, python-format +msgid "🔥 Firehose Mode 🔥" +msgstr "🔥 Firehoseモード 🔥" diff --git a/selfdrive/ui/translations/app_ko.po b/selfdrive/ui/translations/app_ko.po new file mode 100644 index 000000000..c07d44901 --- /dev/null +++ b/selfdrive/ui/translations/app_ko.po @@ -0,0 +1,1209 @@ +# Korean translations for PACKAGE package. +# Copyright (C) 2025 THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# Automatically generated, 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-10-22 18:57-0700\n" +"PO-Revision-Date: 2025-10-22 16:32-0700\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: ko\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:160 +#, python-format +msgid " Steering torque response calibration is complete." +msgstr " 스티어링 토크 응답 보정이 완료되었습니다." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:158 +#, python-format +msgid " Steering torque response calibration is {}% complete." +msgstr " 스티어링 토크 응답 보정이 {}% 완료되었습니다." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#, python-format +msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." +msgstr " 장치는 {:.1f}° {} 및 {:.1f}° {} 방향을 가리키고 있습니다." + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:43 +msgid "--" +msgstr "--" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "1 year of drive storage" +msgstr "주행 데이터 1년 보관" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "24/7 LTE connectivity" +msgstr "연중무휴 LTE 연결" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:46 +msgid "2G" +msgstr "2G" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:47 +msgid "3G" +msgstr "3G" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:49 +msgid "5G" +msgstr "5G" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:23 +msgid "" +"WARNING: openpilot longitudinal control is in alpha for this car and will " +"disable Automatic Emergency Braking (AEB).

On this car, openpilot " +"defaults to the car's built-in ACC instead of openpilot's longitudinal " +"control. Enable this to switch to openpilot longitudinal control. Enabling " +"Experimental mode is recommended when enabling openpilot longitudinal " +"control alpha. Changing this setting will restart openpilot if the car is " +"powered on." +msgstr "" +"경고: 이 차량에서 openpilot의 종방향 제어는 알파 버전이며 자동 긴급 제동" +"(AEB)을 비활성화합니다.

이 차량에서는 openpilot 종방향 제어 대신 " +"차량 내장 ACC가 기본으로 사용됩니다. openpilot 종방향 제어로 전환하려면 이 설" +"정을 켜세요. 종방향 제어 알파를 켤 때는 실험 모드 사용을 권장합니다. 차량 전" +"원이 켜져 있는 경우 이 설정을 변경하면 openpilot이 재시작됩니다." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:148 +#, python-format +msgid "

Steering lag calibration is complete." +msgstr "

스티어링 지연 보정이 완료되었습니다." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:146 +#, python-format +msgid "

Steering lag calibration is {}% complete." +msgstr "

스티어링 지연 보정이 {}% 완료되었습니다." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:138 +#, python-format +msgid "ACTIVE" +msgstr "활성" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:15 +msgid "" +"ADB (Android Debug Bridge) allows connecting to your device over USB or over " +"the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." +msgstr "" +"ADB(Android Debug Bridge)를 사용하면 USB 또는 네트워크로 장치에 연결할 수 있" +"습니다. 자세한 내용은 https://docs.comma.ai/how-to/connect-to-comma 를 참고하" +"세요." + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:30 +msgid "ADD" +msgstr "추가" + +#: /home/batman/openpilot/system/ui/widgets/network.py:139 +#, python-format +msgid "APN Setting" +msgstr "APN 설정" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 +#, python-format +msgid "Acknowledge Excessive Actuation" +msgstr "과도한 작동을 확인" + +#: /home/batman/openpilot/system/ui/widgets/network.py:74 +#: /home/batman/openpilot/system/ui/widgets/network.py:95 +#, python-format +msgid "Advanced" +msgstr "고급" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#, python-format +msgid "Aggressive" +msgstr "공격적" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:116 +#, python-format +msgid "Agree" +msgstr "동의" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:70 +#, python-format +msgid "Always-On Driver Monitoring" +msgstr "항상 켜짐 운전자 모니터링" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:186 +#, python-format +msgid "" +"An alpha version of openpilot longitudinal control can be tested, along with " +"Experimental mode, on non-release branches." +msgstr "" +"openpilot 종방향 제어 알파 버전은 실험 모드와 함께 비릴리스 브랜치에서 테스트" +"할 수 있습니다." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:187 +#, python-format +msgid "Are you sure you want to power off?" +msgstr "정말 전원을 끄시겠습니까?" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:175 +#, python-format +msgid "Are you sure you want to reboot?" +msgstr "정말 재시작하시겠습니까?" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:119 +#, python-format +msgid "Are you sure you want to reset calibration?" +msgstr "정말 보정을 재설정하시겠습니까?" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:163 +#, python-format +msgid "Are you sure you want to uninstall?" +msgstr "정말 제거하시겠습니까?" + +#: /home/batman/openpilot/system/ui/widgets/network.py:99 +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:147 +#, python-format +msgid "Back" +msgstr "뒤로" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:38 +#, python-format +msgid "Become a comma prime member at connect.comma.ai" +msgstr "connect.comma.ai에서 comma prime 회원이 되세요" + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:130 +#, python-format +msgid "Bookmark connect.comma.ai to your home screen to use it like an app" +msgstr "connect.comma.ai를 홈 화면에 추가하여 앱처럼 사용하세요" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:68 +#, python-format +msgid "CHANGE" +msgstr "변경" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:50 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:107 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:118 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:147 +#, python-format +msgid "CHECK" +msgstr "확인" + +#: /home/batman/openpilot/selfdrive/ui/widgets/exp_mode_button.py:50 +#, python-format +msgid "CHILL MODE ON" +msgstr "칠 모드 켜짐" + +#: /home/batman/openpilot/system/ui/widgets/network.py:155 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:138 +#, python-format +msgid "CONNECT" +msgstr "연결" + +#: /home/batman/openpilot/system/ui/widgets/network.py:369 +#, python-format +msgid "CONNECTING..." +msgstr "연결 중..." + +#: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:23 +#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:35 +#: /home/batman/openpilot/system/ui/widgets/keyboard.py:81 +#: /home/batman/openpilot/system/ui/widgets/network.py:318 +#, python-format +msgid "Cancel" +msgstr "취소" + +#: /home/batman/openpilot/system/ui/widgets/network.py:134 +#, python-format +msgid "Cellular Metered" +msgstr "종량제 셀룰러" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:68 +#, python-format +msgid "Change Language" +msgstr "언어 변경" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:125 +#, python-format +msgid "Changing this setting will restart openpilot if the car is powered on." +msgstr "차량 전원이 켜져 있으면 이 설정을 변경할 때 openpilot이 재시작됩니다." + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:129 +#, python-format +msgid "Click \"add new device\" and scan the QR code on the right" +msgstr "\"add new device\"를 눌러 오른쪽의 QR 코드를 스캔하세요" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 +#, python-format +msgid "Close" +msgstr "닫기" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:49 +#, python-format +msgid "Current Version" +msgstr "현재 버전" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:110 +#, python-format +msgid "DOWNLOAD" +msgstr "다운로드" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:115 +#, python-format +msgid "Decline" +msgstr "거부" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:148 +#, python-format +msgid "Decline, uninstall openpilot" +msgstr "거부하고 openpilot 제거" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:67 +msgid "Developer" +msgstr "개발자" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:62 +msgid "Device" +msgstr "장치" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:58 +#, python-format +msgid "Disengage on Accelerator Pedal" +msgstr "가속 페달로 해제" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:184 +#, python-format +msgid "Disengage to Power Off" +msgstr "해제 후 전원 끄기" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:172 +#, python-format +msgid "Disengage to Reboot" +msgstr "해제 후 재시작" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:103 +#, python-format +msgid "Disengage to Reset Calibration" +msgstr "해제 후 보정 재설정" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:32 +msgid "Display speed in km/h instead of mph." +msgstr "속도를 mph 대신 km/h로 표시합니다." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:59 +#, python-format +msgid "Dongle ID" +msgstr "동글 ID" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:50 +#, python-format +msgid "Download" +msgstr "다운로드" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:62 +#, python-format +msgid "Driver Camera" +msgstr "운전자 카메라" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:96 +#, python-format +msgid "Driving Personality" +msgstr "주행 성향" + +#: /home/batman/openpilot/system/ui/widgets/network.py:123 +#: /home/batman/openpilot/system/ui/widgets/network.py:139 +#, python-format +msgid "EDIT" +msgstr "편집" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:138 +msgid "ERROR" +msgstr "오류" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:45 +msgid "ETH" +msgstr "ETH" + +#: /home/batman/openpilot/selfdrive/ui/widgets/exp_mode_button.py:50 +#, python-format +msgid "EXPERIMENTAL MODE ON" +msgstr "실험 모드 켜짐" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:166 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:228 +#, python-format +msgid "Enable" +msgstr "사용" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:39 +#, python-format +msgid "Enable ADB" +msgstr "ADB 사용" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:64 +#, python-format +msgid "Enable Lane Departure Warnings" +msgstr "차선 이탈 경고 사용" + +#: /home/batman/openpilot/system/ui/widgets/network.py:129 +#, python-format +msgid "Enable Roaming" +msgstr "로밍 사용" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:48 +#, python-format +msgid "Enable SSH" +msgstr "SSH 사용" + +#: /home/batman/openpilot/system/ui/widgets/network.py:120 +#, python-format +msgid "Enable Tethering" +msgstr "테더링 사용" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:30 +msgid "Enable driver monitoring even when openpilot is not engaged." +msgstr "openpilot이 작동 중이 아닐 때도 운전자 모니터링을 사용합니다." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:46 +#, python-format +msgid "Enable openpilot" +msgstr "openpilot 사용" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:189 +#, python-format +msgid "" +"Enable the openpilot longitudinal control (alpha) toggle to allow " +"Experimental mode." +msgstr "실험 모드를 사용하려면 openpilot 종방향 제어(알파) 토글을 켜세요." + +#: /home/batman/openpilot/system/ui/widgets/network.py:204 +#, python-format +msgid "Enter APN" +msgstr "APN 입력" + +#: /home/batman/openpilot/system/ui/widgets/network.py:241 +#, python-format +msgid "Enter SSID" +msgstr "SSID 입력" + +#: /home/batman/openpilot/system/ui/widgets/network.py:254 +#, python-format +msgid "Enter new tethering password" +msgstr "새 테더링 비밀번호 입력" + +#: /home/batman/openpilot/system/ui/widgets/network.py:237 +#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#, python-format +msgid "Enter password" +msgstr "비밀번호 입력" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:89 +#, python-format +msgid "Enter your GitHub username" +msgstr "GitHub 사용자 이름 입력" + +#: /home/batman/openpilot/system/ui/widgets/list_view.py:123 +#: /home/batman/openpilot/system/ui/widgets/list_view.py:160 +#, python-format +msgid "Error" +msgstr "오류" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:52 +#, python-format +msgid "Experimental Mode" +msgstr "실험 모드" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:181 +#, python-format +msgid "" +"Experimental mode is currently unavailable on this car since the car's stock " +"ACC is used for longitudinal control." +msgstr "" +"이 차량은 종방향 제어에 순정 ACC를 사용하므로 현재 실험 모드를 사용할 수 없습" +"니다." + +#: /home/batman/openpilot/system/ui/widgets/network.py:373 +#, python-format +msgid "FORGETTING..." +msgstr "삭제 중..." + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:44 +#, python-format +msgid "Finish Setup" +msgstr "설정 완료" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:66 +msgid "Firehose" +msgstr "Firehose" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:18 +msgid "Firehose Mode" +msgstr "Firehose 모드" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:25 +msgid "" +"For maximum effectiveness, bring your device inside and connect to a good " +"USB-C adapter and Wi-Fi weekly.\n" +"\n" +"Firehose Mode can also work while you're driving if connected to a hotspot " +"or unlimited SIM card.\n" +"\n" +"\n" +"Frequently Asked Questions\n" +"\n" +"Does it matter how or where I drive? Nope, just drive as you normally " +"would.\n" +"\n" +"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a " +"subset of your segments.\n" +"\n" +"What's a good USB-C adapter? Any fast phone or laptop charger should be " +"fine.\n" +"\n" +"Does it matter which software I run? Yes, only upstream openpilot (and " +"particular forks) are able to be used for training." +msgstr "" +"최대의 효과를 위해 주 1회는 장치를 실내로 가져와 품질 좋은 USB‑C 어댑터와 " +"Wi‑Fi에 연결하세요.\n" +"\n" +"핫스팟이나 무제한 SIM에 연결되어 있다면 주행 중에도 Firehose 모드가 동작합니" +"다.\n" +"\n" +"\n" +"자주 묻는 질문\n" +"\n" +"어떻게, 어디서 운전하는지가 중요한가요? 아니요. 평소처럼 운전하세요.\n" +"\n" +"Firehose 모드에서 모든 세그먼트가 가져가지나요? 아니요. 일부 세그먼트만 선택" +"적으로 가져갑니다.\n" +"\n" +"좋은 USB‑C 어댑터는 무엇인가요? 빠른 휴대폰 또는 노트북 충전기면 충분합니" +"다.\n" +"\n" +"어떤 소프트웨어를 실행하는지가 중요한가요? 예. 학습에는 업스트림 " +"openpilot(및 일부 포크)만 사용할 수 있습니다." + +#: /home/batman/openpilot/system/ui/widgets/network.py:318 +#: /home/batman/openpilot/system/ui/widgets/network.py:451 +#, python-format +msgid "Forget" +msgstr "삭제" + +#: /home/batman/openpilot/system/ui/widgets/network.py:319 +#, python-format +msgid "Forget Wi-Fi Network \"{}\"?" +msgstr "Wi‑Fi 네트워크 \"{}\"를 삭제하시겠습니까?" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:125 +msgid "GOOD" +msgstr "양호" + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:128 +#, python-format +msgid "Go to https://connect.comma.ai on your phone" +msgstr "휴대폰에서 https://connect.comma.ai 에 접속하세요" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:129 +msgid "HIGH" +msgstr "높음" + +#: /home/batman/openpilot/system/ui/widgets/network.py:155 +#, python-format +msgid "Hidden Network" +msgstr "숨겨진 네트워크" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:140 +#, python-format +msgid "INACTIVE: connect to an unmetered network" +msgstr "비활성: 비종량제 네트워크에 연결하세요" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:53 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:136 +#, python-format +msgid "INSTALL" +msgstr "설치" + +#: /home/batman/openpilot/system/ui/widgets/network.py:150 +#, python-format +msgid "IP Address" +msgstr "IP 주소" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:53 +#, python-format +msgid "Install Update" +msgstr "업데이트 설치" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:56 +#, python-format +msgid "Joystick Debug Mode" +msgstr "조이스틱 디버그 모드" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:29 +msgid "LOADING" +msgstr "로딩 중" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:48 +msgid "LTE" +msgstr "LTE" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:64 +#, python-format +msgid "Longitudinal Maneuver Mode" +msgstr "종방향 매뉴버 모드" + +#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:148 +#, python-format +msgid "MAX" +msgstr "최대" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:75 +#, python-format +msgid "" +"Maximize your training data uploads to improve openpilot's driving models." +msgstr "학습 데이터 업로드를 최대화하여 openpilot의 주행 모델을 개선하세요." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:59 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:60 +#, python-format +msgid "N/A" +msgstr "해당 없음" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:142 +msgid "NO" +msgstr "아니오" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:63 +msgid "Network" +msgstr "네트워크" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:114 +#, python-format +msgid "No SSH keys found" +msgstr "SSH 키를 찾을 수 없습니다" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:126 +#, python-format +msgid "No SSH keys found for user '{}'" +msgstr "사용자 '{}'의 SSH 키를 찾을 수 없습니다" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:320 +#, python-format +msgid "No release notes available." +msgstr "릴리스 노트가 없습니다." + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:134 +msgid "OFFLINE" +msgstr "오프라인" + +#: /home/batman/openpilot/system/ui/widgets/html_render.py:263 +#: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:93 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:127 +#, python-format +msgid "OK" +msgstr "확인" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:144 +msgid "ONLINE" +msgstr "온라인" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:20 +#, python-format +msgid "Open" +msgstr "열기" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:48 +#, python-format +msgid "PAIR" +msgstr "페어링" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:142 +msgid "PANDA" +msgstr "PANDA" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:62 +#, python-format +msgid "PREVIEW" +msgstr "미리보기" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:44 +#, python-format +msgid "PRIME FEATURES:" +msgstr "prime 기능:" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:48 +#, python-format +msgid "Pair Device" +msgstr "장치 페어링" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:19 +#, python-format +msgid "Pair device" +msgstr "장치 페어링" + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:103 +#, python-format +msgid "Pair your device to your comma account" +msgstr "장치를 귀하의 comma 계정에 페어링하세요" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:48 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:24 +#, python-format +msgid "" +"Pair your device with comma connect (connect.comma.ai) and claim your comma " +"prime offer." +msgstr "" +"장치를 comma connect(connect.comma.ai)와 페어링하고 comma prime 혜택을 받으세" +"요." + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:91 +#, python-format +msgid "Please connect to Wi-Fi to complete initial pairing" +msgstr "초기 페어링을 완료하려면 Wi‑Fi에 연결하세요" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:187 +#, python-format +msgid "Power Off" +msgstr "전원 끄기" + +#: /home/batman/openpilot/system/ui/widgets/network.py:144 +#, python-format +msgid "Prevent large data uploads when on a metered Wi-Fi connection" +msgstr "종량제 Wi‑Fi 연결 시 대용량 업로드 방지" + +#: /home/batman/openpilot/system/ui/widgets/network.py:135 +#, python-format +msgid "Prevent large data uploads when on a metered cellular connection" +msgstr "종량제 셀룰러 연결 시 대용량 업로드 방지" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:25 +msgid "" +"Preview the driver facing camera to ensure that driver monitoring has good " +"visibility. (vehicle must be off)" +msgstr "" +"운전자 모니터링의 가시성을 확인하기 위해 운전자 카메라를 미리 봅니다. (차량" +"은 꺼져 있어야 합니다)" + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:161 +#, python-format +msgid "QR Code Error" +msgstr "QR 코드 오류" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:31 +msgid "REMOVE" +msgstr "제거" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:51 +#, python-format +msgid "RESET" +msgstr "재설정" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:65 +#, python-format +msgid "REVIEW" +msgstr "검토" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:175 +#, python-format +msgid "Reboot" +msgstr "재시작" + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:66 +#, python-format +msgid "Reboot Device" +msgstr "장치 재시작" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 +#, python-format +msgid "Reboot and Update" +msgstr "재시작 및 업데이트" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:27 +msgid "" +"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)." +msgstr "" +"시속 31mph(50km/h) 이상에서 방향지시등 없이 감지된 차선 밖으로 벗어나면 차선" +"으로 복귀하라는 경고를 받습니다." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:76 +#, python-format +msgid "Record and Upload Driver Camera" +msgstr "운전자 카메라 기록 및 업로드" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:82 +#, python-format +msgid "Record and Upload Microphone Audio" +msgstr "마이크 오디오 기록 및 업로드" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +msgid "" +"Record and store microphone audio while driving. The audio will be included " +"in the dashcam video in comma connect." +msgstr "" +"주행 중 마이크 오디오를 기록하고 저장합니다. 오디오는 comma connect의 대시캠 " +"영상에 포함됩니다." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:67 +#, python-format +msgid "Regulatory" +msgstr "규제 정보" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#, python-format +msgid "Relaxed" +msgstr "편안함" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "Remote access" +msgstr "원격 액세스" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "Remote snapshots" +msgstr "원격 스냅샷" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:123 +#, python-format +msgid "Request timed out" +msgstr "요청 시간이 초과되었습니다" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:119 +#, python-format +msgid "Reset" +msgstr "재설정" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:51 +#, python-format +msgid "Reset Calibration" +msgstr "보정 재설정" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:65 +#, python-format +msgid "Review Training Guide" +msgstr "학습 가이드 검토" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:27 +msgid "Review the rules, features, and limitations of openpilot" +msgstr "openpilot의 규칙, 기능 및 제한을 검토" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:61 +#, python-format +msgid "SELECT" +msgstr "선택" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:53 +#, python-format +msgid "SSH Keys" +msgstr "SSH 키" + +#: /home/batman/openpilot/system/ui/widgets/network.py:310 +#, python-format +msgid "Scanning Wi-Fi networks..." +msgstr "Wi‑Fi 네트워크 검색 중..." + +#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:36 +#, python-format +msgid "Select" +msgstr "선택" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:183 +#, python-format +msgid "Select a branch" +msgstr "브랜치 선택" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:91 +#, python-format +msgid "Select a language" +msgstr "언어 선택" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:60 +#, python-format +msgid "Serial" +msgstr "시리얼" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 +#, python-format +msgid "Snooze Update" +msgstr "업데이트 나중에 알림" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:65 +msgid "Software" +msgstr "소프트웨어" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#, python-format +msgid "Standard" +msgstr "표준" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:22 +msgid "" +"Standard is recommended. In aggressive mode, openpilot will follow lead cars " +"closer and be more aggressive with the gas and brake. In relaxed mode " +"openpilot will stay further away from lead cars. On supported cars, you can " +"cycle through these personalities with your steering wheel distance button." +msgstr "" +"표준을 권장합니다. 공격적 모드에서는 앞차를 더 가깝게 따라가고 가감속이 더 적" +"극적입니다. 편안함 모드에서는 앞차와 거리를 더 둡니다. 지원 차량에서는 스티어" +"링의 차간 버튼으로 이 성향들을 전환할 수 있습니다." + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:59 +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:65 +#, python-format +msgid "System Unresponsive" +msgstr "시스템 응답 없음" + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:58 +#, python-format +msgid "TAKE CONTROL IMMEDIATELY" +msgstr "즉시 수동 조작하세요" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:129 +msgid "TEMP" +msgstr "온도" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:61 +#, python-format +msgid "Target Branch" +msgstr "대상 브랜치" + +#: /home/batman/openpilot/system/ui/widgets/network.py:124 +#, python-format +msgid "Tethering Password" +msgstr "테더링 비밀번호" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:64 +msgid "Toggles" +msgstr "토글" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:72 +#, python-format +msgid "UNINSTALL" +msgstr "제거" + +#: /home/batman/openpilot/selfdrive/ui/layouts/home.py:155 +#, python-format +msgid "UPDATE" +msgstr "업데이트" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:72 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:163 +#, python-format +msgid "Uninstall" +msgstr "제거" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:117 +msgid "Unknown" +msgstr "알 수 없음" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:48 +#, python-format +msgid "Updates are only downloaded while the car is off." +msgstr "업데이트는 차량 전원이 꺼져 있을 때만 다운로드됩니다." + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:33 +#, python-format +msgid "Upgrade Now" +msgstr "지금 업그레이드" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +msgid "" +"Upload data from the driver facing camera and help improve the driver " +"monitoring algorithm." +msgstr "" +"운전자 방향 카메라 데이터를 업로드하여 운전자 모니터링 알고리즘 개선에 도움" +"을 주세요." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:88 +#, python-format +msgid "Use Metric System" +msgstr "미터법 사용" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:17 +msgid "" +"Use the openpilot system for adaptive cruise control and lane keep driver " +"assistance. Your attention is required at all times to use this feature." +msgstr "" +"ACC 및 차선 유지 보조에 openpilot을 사용합니다. 이 기능을 사용할 때는 항상 주" +"의가 필요합니다." + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:144 +msgid "VEHICLE" +msgstr "차량" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:67 +#, python-format +msgid "VIEW" +msgstr "보기" + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:52 +#, python-format +msgid "Waiting to start" +msgstr "시작 대기 중" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:19 +msgid "" +"Warning: This grants SSH access to all public keys in your GitHub settings. " +"Never enter a GitHub username other than your own. A comma employee will " +"NEVER ask you to add their GitHub username." +msgstr "" +"경고: 이는 GitHub 설정의 모든 공개 키에 SSH 액세스를 부여합니다. 자신의 것이 " +"아닌 GitHub 사용자 이름을 절대 입력하지 마세요. comma 직원이 본인의 GitHub 사" +"용자 이름 추가를 요구하는 일은 결코 없습니다." + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:111 +#, python-format +msgid "Welcome to openpilot" +msgstr "openpilot에 오신 것을 환영합니다" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:20 +msgid "When enabled, pressing the accelerator pedal will disengage openpilot." +msgstr "이 옵션을 켜면 가속 페달을 밟을 때 openpilot이 해제됩니다." + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:44 +msgid "Wi-Fi" +msgstr "Wi‑Fi" + +#: /home/batman/openpilot/system/ui/widgets/network.py:144 +#, python-format +msgid "Wi-Fi Network Metered" +msgstr "Wi‑Fi 네트워크 종량제" + +#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#, python-format +msgid "Wrong password" +msgstr "비밀번호가 올바르지 않습니다" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:145 +#, python-format +msgid "You must accept the Terms and Conditions in order to use openpilot." +msgstr "openpilot을 사용하려면 약관에 동의해야 합니다." + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:112 +#, python-format +msgid "" +"You must accept the Terms and Conditions to use openpilot. Read the latest " +"terms at https://comma.ai/terms before continuing." +msgstr "" +"openpilot을 사용하려면 약관에 동의해야 합니다. 계속하기 전에 https://comma." +"ai/terms 에서 최신 약관을 읽어주세요." + +#: /home/batman/openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:34 +#, python-format +msgid "camera starting" +msgstr "카메라 시작 중" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:63 +#, python-format +msgid "comma prime" +msgstr "comma prime" + +#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#, python-format +msgid "default" +msgstr "기본값" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#, python-format +msgid "down" +msgstr "아래" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:106 +#, python-format +msgid "failed to check for update" +msgstr "업데이트 확인 실패" + +#: /home/batman/openpilot/system/ui/widgets/network.py:237 +#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#, python-format +msgid "for \"{}\"" +msgstr "\"{}\"용" + +#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:177 +#, python-format +msgid "km/h" +msgstr "km/h" + +#: /home/batman/openpilot/system/ui/widgets/network.py:204 +#, python-format +msgid "leave blank for automatic configuration" +msgstr "자동 구성을 사용하려면 비워 두세요" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:134 +#, python-format +msgid "left" +msgstr "왼쪽" + +#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#, python-format +msgid "metered" +msgstr "종량제" + +#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:177 +#, python-format +msgid "mph" +msgstr "mph" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:20 +#, python-format +msgid "never" +msgstr "없음" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:31 +#, python-format +msgid "now" +msgstr "지금" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:71 +#, python-format +msgid "openpilot Longitudinal Control (Alpha)" +msgstr "openpilot 종방향 제어(알파)" + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:51 +#, python-format +msgid "openpilot Unavailable" +msgstr "openpilot 사용 불가" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:158 +#, python-format +msgid "" +"openpilot defaults to driving in chill mode. Experimental mode enables alpha-" +"level features that aren't ready for chill mode. Experimental features are " +"listed below:

End-to-End Longitudinal Control


Let the driving " +"model control the gas and brakes. openpilot will drive as it thinks a human " +"would, including stopping for red lights and stop signs. Since the driving " +"model decides the speed to drive, the set speed will only act as an upper " +"bound. This is an alpha quality feature; mistakes should be expected." +"

New Driving Visualization


The driving visualization will " +"transition to the road-facing wide-angle camera at low speeds to better show " +"some turns. The Experimental mode logo will also be shown in the top right " +"corner." +msgstr "" +"openpilot은 기본적으로 칠 모드로 주행합니다. 실험 모드를 사용하면 칠 모드에 " +"아직 준비되지 않은 알파 수준의 기능이 활성화됩니다. 실험 기능은 아래와 같습니" +"다:

엔드투엔드 종방향 제어


주행 모델이 가속과 제동을 제어합니" +"다. openpilot은 빨간 신호 및 정지 표지에서의 정지를 포함해 사람이 운전한다고 " +"판단하는 방식으로 주행합니다. 주행 속도는 모델이 결정하므로 설정 속도는 상한" +"으로만 동작합니다. 알파 품질 기능이므로 오작동이 발생할 수 있습니다.

" +"새로운 주행 시각화


저속에서는 도로 방향의 광각 카메라로 전환되어 일" +"부 회전을 더 잘 보여줍니다. 화면 오른쪽 위에는 실험 모드 로고도 표시됩니다." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:165 +#, python-format +msgid "" +"openpilot is continuously calibrating, resetting is rarely required. " +"Resetting calibration will restart openpilot if the car is powered on." +msgstr "" +"openpilot은 지속적으로 보정을 진행하므로 재설정이 필요한 경우는 드뭅니다. 차" +"량 전원이 켜져 있을 때 보정을 재설정하면 openpilot이 재시작됩니다." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:20 +msgid "" +"openpilot learns to drive by watching humans, like you, drive.\n" +"\n" +"Firehose Mode allows you to maximize your training data uploads to improve " +"openpilot's driving models. More data means bigger models, which means " +"better Experimental Mode." +msgstr "" +"openpilot은 당신과 같은 사람의 운전을 보며 운전을 학습합니다.\n" +"\n" +"Firehose 모드는 학습 데이터 업로드를 최대화하여 openpilot의 주행 모델을 개선" +"할 수 있게 해줍니다. 데이터가 많을수록 모델은 커지고, 실험 모드는 더 좋아집니" +"다." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:183 +#, python-format +msgid "openpilot longitudinal control may come in a future update." +msgstr "openpilot 종방향 제어는 향후 업데이트에서 제공될 수 있습니다." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:26 +msgid "" +"openpilot requires the device to be mounted within 4° left or right and " +"within 5° up or 9° down." +msgstr "openpilot은 장치를 좌우 4°, 위쪽 5°, 아래쪽 9° 이내로 장착해야 합니다." + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:134 +#, python-format +msgid "right" +msgstr "오른쪽" + +#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#, python-format +msgid "unmetered" +msgstr "비종량제" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#, python-format +msgid "up" +msgstr "위" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:117 +#, python-format +msgid "up to date, last checked never" +msgstr "최신입니다. 마지막 확인: 없음" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:115 +#, python-format +msgid "up to date, last checked {}" +msgstr "최신입니다. 마지막 확인: {}" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:109 +#, python-format +msgid "update available" +msgstr "업데이트 가능" + +#: /home/batman/openpilot/selfdrive/ui/layouts/home.py:169 +#, python-format +msgid "{} ALERT" +msgid_plural "{} ALERTS" +msgstr[0] "{}건의 알림" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:40 +#, python-format +msgid "{} day ago" +msgid_plural "{} days ago" +msgstr[0] "{}일 전" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:37 +#, python-format +msgid "{} hour ago" +msgid_plural "{} hours ago" +msgstr[0] "{}시간 전" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:34 +#, python-format +msgid "{} minute ago" +msgid_plural "{} minutes ago" +msgstr[0] "{}분 전" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:111 +#, python-format +msgid "{} segment of your driving is in the training dataset so far." +msgid_plural "{} segments of your driving is in the training dataset so far." +msgstr[0] "현재까지 귀하의 주행 {}세그먼트가 학습 데이터셋에 포함되었습니다." + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:62 +#, python-format +msgid "✓ SUBSCRIBED" +msgstr "✓ 구독됨" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:22 +#, python-format +msgid "🔥 Firehose Mode 🔥" +msgstr "🔥 Firehose 모드 🔥" diff --git a/selfdrive/ui/translations/app_pt-BR.po b/selfdrive/ui/translations/app_pt-BR.po index 1829dccd8..a14304bb8 100644 --- a/selfdrive/ui/translations/app_pt-BR.po +++ b/selfdrive/ui/translations/app_pt-BR.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-22 15:30-0700\n" +"POT-Creation-Date: 2025-10-22 18:57-0700\n" "PO-Revision-Date: 2025-10-21 00:00-0700\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" @@ -17,17 +17,17 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:159 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:160 #, python-format msgid " Steering torque response calibration is complete." msgstr " A calibração da resposta de torque da direção foi concluída." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:157 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:158 #, python-format msgid " Steering torque response calibration is {}% complete." msgstr " A calibração da resposta de torque da direção está {}% concluída." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:132 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr " Seu dispositivo está apontado {:.1f}° {} e {:.1f}° {}." @@ -75,12 +75,12 @@ msgstr "" "longitudinal do openpilot. Recomenda-se ativar o Modo Experimental ao ativar " "o controle longitudinal do openpilot em alpha." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:147 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:148 #, python-format msgid "

Steering lag calibration is complete." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:145 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:146 #, python-format msgid "

Steering lag calibration is {}% complete." msgstr "" @@ -143,22 +143,22 @@ msgstr "" "Uma versão alpha do controle longitudinal do openpilot pode ser testada, " "junto com o Modo Experimental, em ramificações fora de release." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:186 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:187 #, python-format msgid "Are you sure you want to power off?" msgstr "Tem certeza de que deseja desligar?" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:174 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:175 #, python-format msgid "Are you sure you want to reboot?" msgstr "Tem certeza de que deseja reiniciar?" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:118 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:119 #, python-format msgid "Are you sure you want to reset calibration?" msgstr "Tem certeza de que deseja redefinir a calibração?" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:158 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:163 #, python-format msgid "Are you sure you want to uninstall?" msgstr "Tem certeza de que deseja desinstalar?" @@ -184,10 +184,10 @@ msgstr "Adicione connect.comma.ai à tela inicial para usá-lo como um app" msgid "CHANGE" msgstr "ALTERAR" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:49 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:106 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:117 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:142 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:50 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:107 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:118 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:147 #, python-format msgid "CHECK" msgstr "VERIFICAR" @@ -212,7 +212,7 @@ msgid "CONNECTING..." msgstr "CONECTAR" #: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:23 -#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:34 +#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:35 #: /home/batman/openpilot/system/ui/widgets/keyboard.py:81 #: /home/batman/openpilot/system/ui/widgets/network.py:318 #, python-format @@ -245,12 +245,12 @@ msgstr "Toque em \"adicionar novo dispositivo\" e escaneie o QR code à direita" msgid "Close" msgstr "Fechar" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:48 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:49 #, python-format msgid "Current Version" msgstr "Versão Atual" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:109 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:110 #, python-format msgid "DOWNLOAD" msgstr "BAIXAR" @@ -278,17 +278,17 @@ msgstr "Dispositivo" msgid "Disengage on Accelerator Pedal" msgstr "Desativar ao pressionar o acelerador" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:183 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:184 #, python-format msgid "Disengage to Power Off" msgstr "Desativar para Desligar" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:171 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:172 #, python-format msgid "Disengage to Reboot" msgstr "Desativar para Reiniciar" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:102 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:103 #, python-format msgid "Disengage to Reset Calibration" msgstr "Desativar para Redefinir Calibração" @@ -302,7 +302,7 @@ msgstr "Exibir velocidade em km/h em vez de mph." msgid "Dongle ID" msgstr "ID do Dongle" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:49 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:50 #, python-format msgid "Download" msgstr "Baixar" @@ -412,8 +412,8 @@ msgstr "" msgid "Enter your GitHub username" msgstr "Digite seu nome de usuário do GitHub" -#: /home/batman/openpilot/system/ui/widgets/list_view.py:115 -#: /home/batman/openpilot/system/ui/widgets/list_view.py:153 +#: /home/batman/openpilot/system/ui/widgets/list_view.py:123 +#: /home/batman/openpilot/system/ui/widgets/list_view.py:160 #, python-format msgid "Error" msgstr "" @@ -528,8 +528,8 @@ msgstr "Rede" msgid "INACTIVE: connect to an unmetered network" msgstr "INATIVO: conecte a uma rede sem franquia" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:52 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:131 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:53 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:136 #, python-format msgid "INSTALL" msgstr "INSTALAR" @@ -539,7 +539,7 @@ msgstr "INSTALAR" msgid "IP Address" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:52 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:53 #, python-format msgid "Install Update" msgstr "Instalar Atualização" @@ -609,7 +609,7 @@ msgstr "Sem notas de versão disponíveis." msgid "OFFLINE" msgstr "OFFLINE" -#: /home/batman/openpilot/system/ui/widgets/html_render.py:247 +#: /home/batman/openpilot/system/ui/widgets/html_render.py:263 #: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:93 #: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:127 #, python-format @@ -677,7 +677,7 @@ msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "Conecte-se ao Wi‑Fi para concluir o emparelhamento inicial" #: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:186 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:187 #, python-format msgid "Power Off" msgstr "Desligar" @@ -721,7 +721,7 @@ msgid "REVIEW" msgstr "REVISAR" #: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:174 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:175 #, python-format msgid "Reboot" msgstr "Reiniciar" @@ -788,7 +788,7 @@ msgstr "Capturas remotas" msgid "Request timed out" msgstr "Tempo da solicitação esgotado" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:118 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:119 #, python-format msgid "Reset" msgstr "Redefinir" @@ -807,6 +807,11 @@ msgstr "Revisar Guia de Treinamento" msgid "Review the rules, features, and limitations of openpilot" msgstr "Revise as regras, recursos e limitações do openpilot" +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:61 +#, python-format +msgid "SELECT" +msgstr "" + #: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:53 #, python-format msgid "SSH Keys" @@ -817,11 +822,16 @@ msgstr "" msgid "Scanning Wi-Fi networks..." msgstr "" -#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:35 +#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:36 #, python-format msgid "Select" msgstr "" +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:183 +#, python-format +msgid "Select a branch" +msgstr "" + #: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:91 #, python-format msgid "Select a language" @@ -877,6 +887,11 @@ msgstr "ASSUMA O CONTROLE IMEDIATAMENTE" msgid "TEMP" msgstr "TEMP" +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:61 +#, python-format +msgid "Target Branch" +msgstr "" + #: /home/batman/openpilot/system/ui/widgets/network.py:124 #, python-format msgid "Tethering Password" @@ -886,7 +901,7 @@ msgstr "" msgid "Toggles" msgstr "Alternâncias" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:70 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:72 #, python-format msgid "UNINSTALL" msgstr "DESINSTALAR" @@ -896,8 +911,8 @@ msgstr "DESINSTALAR" msgid "UPDATE" msgstr "ATUALIZAR" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:70 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:158 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:72 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:163 #, python-format msgid "Uninstall" msgstr "Desinstalar" @@ -906,7 +921,7 @@ msgstr "Desinstalar" msgid "Unknown" msgstr "Desconhecido" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:47 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:48 #, python-format msgid "Updates are only downloaded while the car is off." msgstr "Atualizações são baixadas apenas com o carro desligado." @@ -1017,12 +1032,12 @@ msgstr "comma prime" msgid "default" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:132 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid "down" msgstr "para baixo" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:105 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:106 #, python-format msgid "failed to check for update" msgstr "falha ao verificar atualização" @@ -1043,7 +1058,7 @@ msgstr "km/h" msgid "leave blank for automatic configuration" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:134 #, python-format msgid "left" msgstr "à esquerda" @@ -1058,12 +1073,12 @@ msgstr "" msgid "mph" msgstr "mph" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:19 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:20 #, python-format msgid "never" msgstr "nunca" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:30 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:31 #, python-format msgid "now" msgstr "agora" @@ -1106,7 +1121,7 @@ msgstr "" "curvas. O logotipo do Modo Experimental também será exibido no canto " "superior direito." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:164 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:165 #, python-format msgid "" "openpilot is continuously calibrating, resetting is rarely required. " @@ -1142,7 +1157,7 @@ msgstr "" "o openpilot requer que o dispositivo seja montado dentro de 4° para a " "esquerda ou direita e dentro de 5° para cima ou 9° para baixo." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:134 #, python-format msgid "right" msgstr "à direita" @@ -1152,22 +1167,22 @@ msgstr "à direita" msgid "unmetered" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:132 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid "up" msgstr "para cima" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:116 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:117 #, python-format msgid "up to date, last checked never" msgstr "atualizado, última verificação: nunca" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:114 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:115 #, python-format msgid "up to date, last checked {}" msgstr "atualizado, última verificação: {}" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:108 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:109 #, python-format msgid "update available" msgstr "atualização disponível" @@ -1179,21 +1194,21 @@ msgid_plural "{} ALERTS" msgstr[0] "{} ALERTA" msgstr[1] "{} ALERTAS" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:39 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:40 #, python-format msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "{} dia atrás" msgstr[1] "{} dias atrás" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:36 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:37 #, python-format msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "{} hora atrás" msgstr[1] "{} horas atrás" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:33 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:34 #, python-format msgid "{} minute ago" msgid_plural "{} minutes ago" diff --git a/selfdrive/ui/translations/app_th.po b/selfdrive/ui/translations/app_th.po new file mode 100644 index 000000000..87420ed07 --- /dev/null +++ b/selfdrive/ui/translations/app_th.po @@ -0,0 +1,1148 @@ +# Thai translations for PACKAGE package. +# Copyright (C) 2025 THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# Automatically generated, 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-10-22 18:57-0700\n" +"PO-Revision-Date: 2025-10-22 16:32-0700\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: th\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:160 +#, python-format +msgid " Steering torque response calibration is complete." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:158 +#, python-format +msgid " Steering torque response calibration is {}% complete." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#, python-format +msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:43 +msgid "--" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "1 year of drive storage" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "24/7 LTE connectivity" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:46 +msgid "2G" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:47 +msgid "3G" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:49 +msgid "5G" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:23 +msgid "" +"WARNING: openpilot longitudinal control is in alpha for this car and will " +"disable Automatic Emergency Braking (AEB).

On this car, openpilot " +"defaults to the car's built-in ACC instead of openpilot's longitudinal " +"control. Enable this to switch to openpilot longitudinal control. Enabling " +"Experimental mode is recommended when enabling openpilot longitudinal " +"control alpha. Changing this setting will restart openpilot if the car is " +"powered on." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:148 +#, python-format +msgid "

Steering lag calibration is complete." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:146 +#, python-format +msgid "

Steering lag calibration is {}% complete." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:138 +#, python-format +msgid "ACTIVE" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:15 +msgid "" +"ADB (Android Debug Bridge) allows connecting to your device over USB or over " +"the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:30 +msgid "ADD" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:139 +#, python-format +msgid "APN Setting" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 +#, python-format +msgid "Acknowledge Excessive Actuation" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:74 +#: /home/batman/openpilot/system/ui/widgets/network.py:95 +#, python-format +msgid "Advanced" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#, python-format +msgid "Aggressive" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:116 +#, python-format +msgid "Agree" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:70 +#, python-format +msgid "Always-On Driver Monitoring" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:186 +#, python-format +msgid "" +"An alpha version of openpilot longitudinal control can be tested, along with " +"Experimental mode, on non-release branches." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:187 +#, python-format +msgid "Are you sure you want to power off?" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:175 +#, python-format +msgid "Are you sure you want to reboot?" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:119 +#, python-format +msgid "Are you sure you want to reset calibration?" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:163 +#, python-format +msgid "Are you sure you want to uninstall?" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:99 +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:147 +#, python-format +msgid "Back" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:38 +#, python-format +msgid "Become a comma prime member at connect.comma.ai" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:130 +#, python-format +msgid "Bookmark connect.comma.ai to your home screen to use it like an app" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:68 +#, python-format +msgid "CHANGE" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:50 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:107 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:118 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:147 +#, python-format +msgid "CHECK" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/exp_mode_button.py:50 +#, python-format +msgid "CHILL MODE ON" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:155 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:138 +#, python-format +msgid "CONNECT" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:369 +#, python-format +msgid "CONNECTING..." +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:23 +#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:35 +#: /home/batman/openpilot/system/ui/widgets/keyboard.py:81 +#: /home/batman/openpilot/system/ui/widgets/network.py:318 +#, python-format +msgid "Cancel" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:134 +#, python-format +msgid "Cellular Metered" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:68 +#, python-format +msgid "Change Language" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:125 +#, python-format +msgid "Changing this setting will restart openpilot if the car is powered on." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:129 +#, python-format +msgid "Click \"add new device\" and scan the QR code on the right" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 +#, python-format +msgid "Close" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:49 +#, python-format +msgid "Current Version" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:110 +#, python-format +msgid "DOWNLOAD" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:115 +#, python-format +msgid "Decline" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:148 +#, python-format +msgid "Decline, uninstall openpilot" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:67 +msgid "Developer" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:62 +msgid "Device" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:58 +#, python-format +msgid "Disengage on Accelerator Pedal" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:184 +#, python-format +msgid "Disengage to Power Off" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:172 +#, python-format +msgid "Disengage to Reboot" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:103 +#, python-format +msgid "Disengage to Reset Calibration" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:32 +msgid "Display speed in km/h instead of mph." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:59 +#, python-format +msgid "Dongle ID" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:50 +#, python-format +msgid "Download" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:62 +#, python-format +msgid "Driver Camera" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:96 +#, python-format +msgid "Driving Personality" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:123 +#: /home/batman/openpilot/system/ui/widgets/network.py:139 +#, python-format +msgid "EDIT" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:138 +msgid "ERROR" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:45 +msgid "ETH" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/exp_mode_button.py:50 +#, python-format +msgid "EXPERIMENTAL MODE ON" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:166 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:228 +#, python-format +msgid "Enable" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:39 +#, python-format +msgid "Enable ADB" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:64 +#, python-format +msgid "Enable Lane Departure Warnings" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:129 +#, python-format +msgid "Enable Roaming" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:48 +#, python-format +msgid "Enable SSH" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:120 +#, python-format +msgid "Enable Tethering" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:30 +msgid "Enable driver monitoring even when openpilot is not engaged." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:46 +#, python-format +msgid "Enable openpilot" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:189 +#, python-format +msgid "" +"Enable the openpilot longitudinal control (alpha) toggle to allow " +"Experimental mode." +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:204 +#, python-format +msgid "Enter APN" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:241 +#, python-format +msgid "Enter SSID" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:254 +#, python-format +msgid "Enter new tethering password" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:237 +#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#, python-format +msgid "Enter password" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:89 +#, python-format +msgid "Enter your GitHub username" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/list_view.py:123 +#: /home/batman/openpilot/system/ui/widgets/list_view.py:160 +#, python-format +msgid "Error" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:52 +#, python-format +msgid "Experimental Mode" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:181 +#, python-format +msgid "" +"Experimental mode is currently unavailable on this car since the car's stock " +"ACC is used for longitudinal control." +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:373 +#, python-format +msgid "FORGETTING..." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:44 +#, python-format +msgid "Finish Setup" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:66 +msgid "Firehose" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:18 +msgid "Firehose Mode" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:25 +msgid "" +"For maximum effectiveness, bring your device inside and connect to a good " +"USB-C adapter and Wi-Fi weekly.\n" +"\n" +"Firehose Mode can also work while you're driving if connected to a hotspot " +"or unlimited SIM card.\n" +"\n" +"\n" +"Frequently Asked Questions\n" +"\n" +"Does it matter how or where I drive? Nope, just drive as you normally " +"would.\n" +"\n" +"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a " +"subset of your segments.\n" +"\n" +"What's a good USB-C adapter? Any fast phone or laptop charger should be " +"fine.\n" +"\n" +"Does it matter which software I run? Yes, only upstream openpilot (and " +"particular forks) are able to be used for training." +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:318 +#: /home/batman/openpilot/system/ui/widgets/network.py:451 +#, python-format +msgid "Forget" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:319 +#, python-format +msgid "Forget Wi-Fi Network \"{}\"?" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:125 +msgid "GOOD" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:128 +#, python-format +msgid "Go to https://connect.comma.ai on your phone" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:129 +msgid "HIGH" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:155 +#, python-format +msgid "Hidden Network" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:140 +#, python-format +msgid "INACTIVE: connect to an unmetered network" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:53 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:136 +#, python-format +msgid "INSTALL" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:150 +#, python-format +msgid "IP Address" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:53 +#, python-format +msgid "Install Update" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:56 +#, python-format +msgid "Joystick Debug Mode" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:29 +msgid "LOADING" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:48 +msgid "LTE" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:64 +#, python-format +msgid "Longitudinal Maneuver Mode" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:148 +#, python-format +msgid "MAX" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:75 +#, python-format +msgid "" +"Maximize your training data uploads to improve openpilot's driving models." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:59 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:60 +#, python-format +msgid "N/A" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:142 +msgid "NO" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:63 +msgid "Network" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:114 +#, python-format +msgid "No SSH keys found" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:126 +#, python-format +msgid "No SSH keys found for user '{}'" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:320 +#, python-format +msgid "No release notes available." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:134 +msgid "OFFLINE" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/html_render.py:263 +#: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:93 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:127 +#, python-format +msgid "OK" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:144 +msgid "ONLINE" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:20 +#, python-format +msgid "Open" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:48 +#, python-format +msgid "PAIR" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:142 +msgid "PANDA" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:62 +#, python-format +msgid "PREVIEW" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:44 +#, python-format +msgid "PRIME FEATURES:" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:48 +#, python-format +msgid "Pair Device" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:19 +#, python-format +msgid "Pair device" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:103 +#, python-format +msgid "Pair your device to your comma account" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:48 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:24 +#, python-format +msgid "" +"Pair your device with comma connect (connect.comma.ai) and claim your comma " +"prime offer." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:91 +#, python-format +msgid "Please connect to Wi-Fi to complete initial pairing" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:187 +#, python-format +msgid "Power Off" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:144 +#, python-format +msgid "Prevent large data uploads when on a metered Wi-Fi connection" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:135 +#, python-format +msgid "Prevent large data uploads when on a metered cellular connection" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:25 +msgid "" +"Preview the driver facing camera to ensure that driver monitoring has good " +"visibility. (vehicle must be off)" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:161 +#, python-format +msgid "QR Code Error" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:31 +msgid "REMOVE" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:51 +#, python-format +msgid "RESET" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:65 +#, python-format +msgid "REVIEW" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:175 +#, python-format +msgid "Reboot" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:66 +#, python-format +msgid "Reboot Device" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 +#, python-format +msgid "Reboot and Update" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:27 +msgid "" +"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)." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:76 +#, python-format +msgid "Record and Upload Driver Camera" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:82 +#, python-format +msgid "Record and Upload Microphone Audio" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +msgid "" +"Record and store microphone audio while driving. The audio will be included " +"in the dashcam video in comma connect." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:67 +#, python-format +msgid "Regulatory" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#, python-format +msgid "Relaxed" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "Remote access" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "Remote snapshots" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:123 +#, python-format +msgid "Request timed out" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:119 +#, python-format +msgid "Reset" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:51 +#, python-format +msgid "Reset Calibration" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:65 +#, python-format +msgid "Review Training Guide" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:27 +msgid "Review the rules, features, and limitations of openpilot" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:61 +#, python-format +msgid "SELECT" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:53 +#, python-format +msgid "SSH Keys" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:310 +#, python-format +msgid "Scanning Wi-Fi networks..." +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:36 +#, python-format +msgid "Select" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:183 +#, python-format +msgid "Select a branch" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:91 +#, python-format +msgid "Select a language" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:60 +#, python-format +msgid "Serial" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 +#, python-format +msgid "Snooze Update" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:65 +msgid "Software" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#, python-format +msgid "Standard" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:22 +msgid "" +"Standard is recommended. In aggressive mode, openpilot will follow lead cars " +"closer and be more aggressive with the gas and brake. In relaxed mode " +"openpilot will stay further away from lead cars. On supported cars, you can " +"cycle through these personalities with your steering wheel distance button." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:59 +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:65 +#, python-format +msgid "System Unresponsive" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:58 +#, python-format +msgid "TAKE CONTROL IMMEDIATELY" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:129 +msgid "TEMP" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:61 +#, python-format +msgid "Target Branch" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:124 +#, python-format +msgid "Tethering Password" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:64 +msgid "Toggles" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:72 +#, python-format +msgid "UNINSTALL" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/home.py:155 +#, python-format +msgid "UPDATE" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:72 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:163 +#, python-format +msgid "Uninstall" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:117 +msgid "Unknown" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:48 +#, python-format +msgid "Updates are only downloaded while the car is off." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:33 +#, python-format +msgid "Upgrade Now" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +msgid "" +"Upload data from the driver facing camera and help improve the driver " +"monitoring algorithm." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:88 +#, python-format +msgid "Use Metric System" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:17 +msgid "" +"Use the openpilot system for adaptive cruise control and lane keep driver " +"assistance. Your attention is required at all times to use this feature." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:144 +msgid "VEHICLE" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:67 +#, python-format +msgid "VIEW" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:52 +#, python-format +msgid "Waiting to start" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:19 +msgid "" +"Warning: This grants SSH access to all public keys in your GitHub settings. " +"Never enter a GitHub username other than your own. A comma employee will " +"NEVER ask you to add their GitHub username." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:111 +#, python-format +msgid "Welcome to openpilot" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:20 +msgid "When enabled, pressing the accelerator pedal will disengage openpilot." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:44 +msgid "Wi-Fi" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:144 +#, python-format +msgid "Wi-Fi Network Metered" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#, python-format +msgid "Wrong password" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:145 +#, python-format +msgid "You must accept the Terms and Conditions in order to use openpilot." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:112 +#, python-format +msgid "" +"You must accept the Terms and Conditions to use openpilot. Read the latest " +"terms at https://comma.ai/terms before continuing." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:34 +#, python-format +msgid "camera starting" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:63 +#, python-format +msgid "comma prime" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#, python-format +msgid "default" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#, python-format +msgid "down" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:106 +#, python-format +msgid "failed to check for update" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:237 +#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#, python-format +msgid "for \"{}\"" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:177 +#, python-format +msgid "km/h" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:204 +#, python-format +msgid "leave blank for automatic configuration" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:134 +#, python-format +msgid "left" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#, python-format +msgid "metered" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:177 +#, python-format +msgid "mph" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:20 +#, python-format +msgid "never" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:31 +#, python-format +msgid "now" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:71 +#, python-format +msgid "openpilot Longitudinal Control (Alpha)" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:51 +#, python-format +msgid "openpilot Unavailable" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:158 +#, python-format +msgid "" +"openpilot defaults to driving in chill mode. Experimental mode enables alpha-" +"level features that aren't ready for chill mode. Experimental features are " +"listed below:

End-to-End Longitudinal Control


Let the driving " +"model control the gas and brakes. openpilot will drive as it thinks a human " +"would, including stopping for red lights and stop signs. Since the driving " +"model decides the speed to drive, the set speed will only act as an upper " +"bound. This is an alpha quality feature; mistakes should be expected." +"

New Driving Visualization


The driving visualization will " +"transition to the road-facing wide-angle camera at low speeds to better show " +"some turns. The Experimental mode logo will also be shown in the top right " +"corner." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:165 +#, python-format +msgid "" +"openpilot is continuously calibrating, resetting is rarely required. " +"Resetting calibration will restart openpilot if the car is powered on." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:20 +msgid "" +"openpilot learns to drive by watching humans, like you, drive.\n" +"\n" +"Firehose Mode allows you to maximize your training data uploads to improve " +"openpilot's driving models. More data means bigger models, which means " +"better Experimental Mode." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:183 +#, python-format +msgid "openpilot longitudinal control may come in a future update." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:26 +msgid "" +"openpilot requires the device to be mounted within 4° left or right and " +"within 5° up or 9° down." +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:134 +#, python-format +msgid "right" +msgstr "" + +#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#, python-format +msgid "unmetered" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#, python-format +msgid "up" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:117 +#, python-format +msgid "up to date, last checked never" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:115 +#, python-format +msgid "up to date, last checked {}" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:109 +#, python-format +msgid "update available" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/home.py:169 +#, python-format +msgid "{} ALERT" +msgid_plural "{} ALERTS" +msgstr[0] "" +msgstr[1] "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:40 +#, python-format +msgid "{} day ago" +msgid_plural "{} days ago" +msgstr[0] "" +msgstr[1] "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:37 +#, python-format +msgid "{} hour ago" +msgid_plural "{} hours ago" +msgstr[0] "" +msgstr[1] "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:34 +#, python-format +msgid "{} minute ago" +msgid_plural "{} minutes ago" +msgstr[0] "" +msgstr[1] "" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:111 +#, python-format +msgid "{} segment of your driving is in the training dataset so far." +msgid_plural "{} segments of your driving is in the training dataset so far." +msgstr[0] "" +msgstr[1] "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:62 +#, python-format +msgid "✓ SUBSCRIBED" +msgstr "" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:22 +#, python-format +msgid "🔥 Firehose Mode 🔥" +msgstr "" diff --git a/selfdrive/ui/translations/app_tr.po b/selfdrive/ui/translations/app_tr.po index 29d3b9b9f..022621f57 100644 --- a/selfdrive/ui/translations/app_tr.po +++ b/selfdrive/ui/translations/app_tr.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-22 15:30-0700\n" +"POT-Creation-Date: 2025-10-22 18:57-0700\n" "PO-Revision-Date: 2025-10-20 18:19-0700\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" @@ -17,17 +17,17 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:159 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:160 #, python-format msgid " Steering torque response calibration is complete." msgstr " Direksiyon tork tepkisi kalibrasyonu tamamlandı." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:157 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:158 #, python-format msgid " Steering torque response calibration is {}% complete." msgstr " Direksiyon tork tepkisi kalibrasyonu {}% tamamlandı." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:132 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr " Cihazınız {:.1f}° {} ve {:.1f}° {} yönünde konumlandırılmış." @@ -75,12 +75,12 @@ msgstr "" "etkinleştirin. openpilot boylamsal kontrol alfayı etkinleştirirken Deneysel " "modu etkinleştirmeniz önerilir." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:147 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:148 #, python-format msgid "

Steering lag calibration is complete." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:145 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:146 #, python-format msgid "

Steering lag calibration is {}% complete." msgstr "" @@ -143,22 +143,22 @@ msgstr "" "openpilot boylamsal kontrolünün alfa sürümü, Deneysel mod ile birlikte, " "yayın dışı dallarda test edilebilir." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:186 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:187 #, python-format msgid "Are you sure you want to power off?" msgstr "Kapatmak istediğinizden emin misiniz?" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:174 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:175 #, python-format msgid "Are you sure you want to reboot?" msgstr "Yeniden başlatmak istediğinizden emin misiniz?" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:118 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:119 #, python-format msgid "Are you sure you want to reset calibration?" msgstr "Kalibrasyonu sıfırlamak istediğinizden emin misiniz?" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:158 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:163 #, python-format msgid "Are you sure you want to uninstall?" msgstr "Kaldırmak istediğinizden emin misiniz?" @@ -185,10 +185,10 @@ msgstr "" msgid "CHANGE" msgstr "DEĞİŞTİR" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:49 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:106 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:117 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:142 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:50 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:107 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:118 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:147 #, python-format msgid "CHECK" msgstr "KONTROL ET" @@ -213,7 +213,7 @@ msgid "CONNECTING..." msgstr "BAĞLAN" #: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:23 -#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:34 +#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:35 #: /home/batman/openpilot/system/ui/widgets/keyboard.py:81 #: /home/batman/openpilot/system/ui/widgets/network.py:318 #, python-format @@ -246,12 +246,12 @@ msgstr "\"yeni cihaz ekle\"ye tıklayın ve sağdaki QR kodunu tarayın" msgid "Close" msgstr "Kapat" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:48 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:49 #, python-format msgid "Current Version" msgstr "Geçerli Sürüm" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:109 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:110 #, python-format msgid "DOWNLOAD" msgstr "İNDİR" @@ -279,17 +279,17 @@ msgstr "Cihaz" msgid "Disengage on Accelerator Pedal" msgstr "Gaz Pedalında Devreden Çık" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:183 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:184 #, python-format msgid "Disengage to Power Off" msgstr "Kapatmak için Devreden Çıkın" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:171 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:172 #, python-format msgid "Disengage to Reboot" msgstr "Yeniden Başlatmak için Devreden Çıkın" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:102 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:103 #, python-format msgid "Disengage to Reset Calibration" msgstr "Kalibrasyonu Sıfırlamak için Devreden Çıkın" @@ -303,7 +303,7 @@ msgstr "Hızı mph yerine km/h olarak göster." msgid "Dongle ID" msgstr "Dongle ID" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:49 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:50 #, python-format msgid "Download" msgstr "İndir" @@ -411,8 +411,8 @@ msgstr "" msgid "Enter your GitHub username" msgstr "GitHub kullanıcı adınızı girin" -#: /home/batman/openpilot/system/ui/widgets/list_view.py:115 -#: /home/batman/openpilot/system/ui/widgets/list_view.py:153 +#: /home/batman/openpilot/system/ui/widgets/list_view.py:123 +#: /home/batman/openpilot/system/ui/widgets/list_view.py:160 #, python-format msgid "Error" msgstr "" @@ -528,8 +528,8 @@ msgstr "Ağ" msgid "INACTIVE: connect to an unmetered network" msgstr "PASİF: sınırsız bir ağa bağlanın" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:52 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:131 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:53 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:136 #, python-format msgid "INSTALL" msgstr "YÜKLE" @@ -539,7 +539,7 @@ msgstr "YÜKLE" msgid "IP Address" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:52 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:53 #, python-format msgid "Install Update" msgstr "Güncellemeyi Yükle" @@ -609,7 +609,7 @@ msgstr "Sürüm notu mevcut değil." msgid "OFFLINE" msgstr "ÇEVRİMDIŞI" -#: /home/batman/openpilot/system/ui/widgets/html_render.py:247 +#: /home/batman/openpilot/system/ui/widgets/html_render.py:263 #: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:93 #: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:127 #, python-format @@ -677,7 +677,7 @@ msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "İlk eşleştirmeyi tamamlamak için lütfen Wi‑Fi'a bağlanın" #: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:186 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:187 #, python-format msgid "Power Off" msgstr "Kapat" @@ -720,7 +720,7 @@ msgid "REVIEW" msgstr "GÖZDEN GEÇİR" #: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:174 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:175 #, python-format msgid "Reboot" msgstr "Yeniden Başlat" @@ -787,7 +787,7 @@ msgstr "Uzaktan anlık görüntüler" msgid "Request timed out" msgstr "İstek zaman aşımına uğradı" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:118 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:119 #, python-format msgid "Reset" msgstr "Sıfırla" @@ -807,6 +807,11 @@ msgid "Review the rules, features, and limitations of openpilot" msgstr "" "openpilot'un kurallarını, özelliklerini ve sınırlamalarını gözden geçirin" +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:61 +#, python-format +msgid "SELECT" +msgstr "" + #: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:53 #, python-format msgid "SSH Keys" @@ -817,11 +822,16 @@ msgstr "" msgid "Scanning Wi-Fi networks..." msgstr "" -#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:35 +#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:36 #, python-format msgid "Select" msgstr "" +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:183 +#, python-format +msgid "Select a branch" +msgstr "" + #: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:91 #, python-format msgid "Select a language" @@ -876,6 +886,11 @@ msgstr "HEMEN KONTROLÜ DEVRALIN" msgid "TEMP" msgstr "TEMP" +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:61 +#, python-format +msgid "Target Branch" +msgstr "" + #: /home/batman/openpilot/system/ui/widgets/network.py:124 #, python-format msgid "Tethering Password" @@ -885,7 +900,7 @@ msgstr "" msgid "Toggles" msgstr "Seçenekler" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:70 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:72 #, python-format msgid "UNINSTALL" msgstr "KALDIR" @@ -895,8 +910,8 @@ msgstr "KALDIR" msgid "UPDATE" msgstr "GÜNCELLE" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:70 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:158 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:72 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:163 #, python-format msgid "Uninstall" msgstr "Kaldır" @@ -905,7 +920,7 @@ msgstr "Kaldır" msgid "Unknown" msgstr "Bilinmiyor" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:47 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:48 #, python-format msgid "Updates are only downloaded while the car is off." msgstr "Güncellemeler yalnızca araç kapalıyken indirilir." @@ -1015,12 +1030,12 @@ msgstr "comma prime" msgid "default" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:132 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid "down" msgstr "aşağı" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:105 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:106 #, python-format msgid "failed to check for update" msgstr "güncelleme kontrolü başarısız" @@ -1041,7 +1056,7 @@ msgstr "km/h" msgid "leave blank for automatic configuration" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:134 #, python-format msgid "left" msgstr "sol" @@ -1056,12 +1071,12 @@ msgstr "" msgid "mph" msgstr "mph" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:19 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:20 #, python-format msgid "never" msgstr "asla" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:30 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:31 #, python-format msgid "now" msgstr "şimdi" @@ -1103,7 +1118,7 @@ msgstr "" "göstermek için yola bakan geniş açılı kameraya geçer. Deneysel mod logosu " "sağ üst köşede de gösterilecektir." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:164 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:165 #, python-format msgid "" "openpilot is continuously calibrating, resetting is rarely required. " @@ -1138,7 +1153,7 @@ msgstr "" "openpilot, cihazın sağa/sola 4° ve yukarı 5° veya aşağı 9° içinde monte " "edilmesini gerektirir." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:134 #, python-format msgid "right" msgstr "sağ" @@ -1148,22 +1163,22 @@ msgstr "sağ" msgid "unmetered" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:132 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid "up" msgstr "yukarı" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:116 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:117 #, python-format msgid "up to date, last checked never" msgstr "güncel, son kontrol asla" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:114 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:115 #, python-format msgid "up to date, last checked {}" msgstr "güncel, son kontrol {}" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:108 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:109 #, python-format msgid "update available" msgstr "güncelleme mevcut" @@ -1175,21 +1190,21 @@ msgid_plural "{} ALERTS" msgstr[0] "{} UYARI" msgstr[1] "{} UYARILAR" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:39 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:40 #, python-format msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "{} gün önce" msgstr[1] "{} gün önce" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:36 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:37 #, python-format msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "{} saat önce" msgstr[1] "{} saat önce" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:33 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:34 #, python-format msgid "{} minute ago" msgid_plural "{} minutes ago" diff --git a/selfdrive/ui/translations/app_zh-CHS.po b/selfdrive/ui/translations/app_zh-CHS.po new file mode 100644 index 000000000..d09add97d --- /dev/null +++ b/selfdrive/ui/translations/app_zh-CHS.po @@ -0,0 +1,1193 @@ +# Language zh-CHS translations for PACKAGE package. +# Copyright (C) 2025 THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# Automatically generated, 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-10-22 19:09-0700\n" +"PO-Revision-Date: 2025-10-22 16:32-0700\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: zh-CHS\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:160 +#, python-format +msgid " Steering torque response calibration is complete." +msgstr " 转向扭矩响应校准完成。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:158 +#, python-format +msgid " Steering torque response calibration is {}% complete." +msgstr " 转向扭矩响应校准已完成 {}%。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#, python-format +msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." +msgstr " 您的设备朝向 {:.1f}° {} 与 {:.1f}° {}。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:43 +msgid "--" +msgstr "--" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "1 year of drive storage" +msgstr "1 年行驶数据存储" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "24/7 LTE connectivity" +msgstr "全天候 LTE 连接" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:46 +msgid "2G" +msgstr "2G" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:47 +msgid "3G" +msgstr "3G" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:49 +msgid "5G" +msgstr "5G" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:23 +msgid "" +"WARNING: openpilot longitudinal control is in alpha for this car and will " +"disable Automatic Emergency Braking (AEB).

On this car, openpilot " +"defaults to the car's built-in ACC instead of openpilot's longitudinal " +"control. Enable this to switch to openpilot longitudinal control. Enabling " +"Experimental mode is recommended when enabling openpilot longitudinal " +"control alpha. Changing this setting will restart openpilot if the car is " +"powered on." +msgstr "" +"警告:此车型的 openpilot 纵向控制仍为 alpha,将会停用自动紧急制动 (AEB)。" +"

在此车型上,openpilot 默认使用车载 ACC,而非 openpilot 的纵向控" +"制。启用此选项可切换为 openpilot 纵向控制。建议同时启用实验模式。若车辆通电," +"更改此设置将会重启 openpilot。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:148 +#, python-format +msgid "

Steering lag calibration is complete." +msgstr "

转向延迟校准完成。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:146 +#, python-format +msgid "

Steering lag calibration is {}% complete." +msgstr "

转向延迟校准已完成 {}%。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:138 +#, python-format +msgid "ACTIVE" +msgstr "已启用" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:15 +msgid "" +"ADB (Android Debug Bridge) allows connecting to your device over USB or over " +"the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." +msgstr "" +"ADB(Android 调试桥)可通过 USB 或网络连接到您的设备。详见 https://docs." +"comma.ai/how-to/connect-to-comma。" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:30 +msgid "ADD" +msgstr "添加" + +#: /home/batman/openpilot/system/ui/widgets/network.py:139 +#, python-format +msgid "APN Setting" +msgstr "APN 设置" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 +#, python-format +msgid "Acknowledge Excessive Actuation" +msgstr "确认过度作动" + +#: /home/batman/openpilot/system/ui/widgets/network.py:74 +#: /home/batman/openpilot/system/ui/widgets/network.py:95 +#, python-format +msgid "Advanced" +msgstr "高级" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#, python-format +msgid "Aggressive" +msgstr "激进" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:116 +#, python-format +msgid "Agree" +msgstr "同意" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:70 +#, python-format +msgid "Always-On Driver Monitoring" +msgstr "始终启用驾驶员监控" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:186 +#, python-format +msgid "" +"An alpha version of openpilot longitudinal control can be tested, along with " +"Experimental mode, on non-release branches." +msgstr "openpilot 纵向控制的 alpha 版本可在非发布分支搭配实验模式进行测试。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:187 +#, python-format +msgid "Are you sure you want to power off?" +msgstr "确定要关机吗?" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:175 +#, python-format +msgid "Are you sure you want to reboot?" +msgstr "确定要重启吗?" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:119 +#, python-format +msgid "Are you sure you want to reset calibration?" +msgstr "确定要重置校准吗?" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:163 +#, python-format +msgid "Are you sure you want to uninstall?" +msgstr "确定要卸载吗?" + +#: /home/batman/openpilot/system/ui/widgets/network.py:99 +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:147 +#, python-format +msgid "Back" +msgstr "返回" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:38 +#, python-format +msgid "Become a comma prime member at connect.comma.ai" +msgstr "前往 connect.comma.ai 成为 comma prime 会员" + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:130 +#, python-format +msgid "Bookmark connect.comma.ai to your home screen to use it like an app" +msgstr "将 connect.comma.ai 添加到主屏幕,像应用一样使用" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:68 +#, python-format +msgid "CHANGE" +msgstr "更改" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:50 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:107 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:118 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:147 +#, python-format +msgid "CHECK" +msgstr "检查" + +#: /home/batman/openpilot/selfdrive/ui/widgets/exp_mode_button.py:50 +#, python-format +msgid "CHILL MODE ON" +msgstr "安稳模式已开启" + +#: /home/batman/openpilot/system/ui/widgets/network.py:155 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:138 +#, python-format +msgid "CONNECT" +msgstr "CONNECT" + +#: /home/batman/openpilot/system/ui/widgets/network.py:369 +#, python-format +msgid "CONNECTING..." +msgstr "CONNECTING..." + +#: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:23 +#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:35 +#: /home/batman/openpilot/system/ui/widgets/keyboard.py:81 +#: /home/batman/openpilot/system/ui/widgets/network.py:318 +#, python-format +msgid "Cancel" +msgstr "取消" + +#: /home/batman/openpilot/system/ui/widgets/network.py:134 +#, python-format +msgid "Cellular Metered" +msgstr "蜂窝计量" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:68 +#, python-format +msgid "Change Language" +msgstr "更改语言" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:125 +#, python-format +msgid "Changing this setting will restart openpilot if the car is powered on." +msgstr "若车辆通电,更改此设置将重启 openpilot。" + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:129 +#, python-format +msgid "Click \"add new device\" and scan the QR code on the right" +msgstr "点击“添加新设备”,扫描右侧二维码" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 +#, python-format +msgid "Close" +msgstr "关闭" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:49 +#, python-format +msgid "Current Version" +msgstr "当前版本" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:110 +#, python-format +msgid "DOWNLOAD" +msgstr "下载" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:115 +#, python-format +msgid "Decline" +msgstr "拒绝" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:148 +#, python-format +msgid "Decline, uninstall openpilot" +msgstr "拒绝并卸载 openpilot" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:67 +msgid "Developer" +msgstr "开发者" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:62 +msgid "Device" +msgstr "设备" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:58 +#, python-format +msgid "Disengage on Accelerator Pedal" +msgstr "踩下加速踏板时脱离" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:184 +#, python-format +msgid "Disengage to Power Off" +msgstr "脱离以关机" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:172 +#, python-format +msgid "Disengage to Reboot" +msgstr "脱离以重启" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:103 +#, python-format +msgid "Disengage to Reset Calibration" +msgstr "脱离以重置校准" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:32 +msgid "Display speed in km/h instead of mph." +msgstr "以 km/h 显示速度(非 mph)。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:59 +#, python-format +msgid "Dongle ID" +msgstr "Dongle ID" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:50 +#, python-format +msgid "Download" +msgstr "下载" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:62 +#, python-format +msgid "Driver Camera" +msgstr "车内摄像头" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:96 +#, python-format +msgid "Driving Personality" +msgstr "驾驶风格" + +#: /home/batman/openpilot/system/ui/widgets/network.py:123 +#: /home/batman/openpilot/system/ui/widgets/network.py:139 +#, python-format +msgid "EDIT" +msgstr "编辑" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:138 +msgid "ERROR" +msgstr "错误" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:45 +msgid "ETH" +msgstr "ETH" + +#: /home/batman/openpilot/selfdrive/ui/widgets/exp_mode_button.py:50 +#, python-format +msgid "EXPERIMENTAL MODE ON" +msgstr "实验模式已开启" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:166 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:228 +#, python-format +msgid "Enable" +msgstr "启用" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:39 +#, python-format +msgid "Enable ADB" +msgstr "启用 ADB" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:64 +#, python-format +msgid "Enable Lane Departure Warnings" +msgstr "启用车道偏离警示" + +#: /home/batman/openpilot/system/ui/widgets/network.py:129 +#, python-format +msgid "Enable Roaming" +msgstr "启用漫游" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:48 +#, python-format +msgid "Enable SSH" +msgstr "启用 SSH" + +#: /home/batman/openpilot/system/ui/widgets/network.py:120 +#, python-format +msgid "Enable Tethering" +msgstr "启用网络共享" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:30 +msgid "Enable driver monitoring even when openpilot is not engaged." +msgstr "即使未启用 openpilot 也启用驾驶员监控。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:46 +#, python-format +msgid "Enable openpilot" +msgstr "启用 openpilot" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:189 +#, python-format +msgid "" +"Enable the openpilot longitudinal control (alpha) toggle to allow " +"Experimental mode." +msgstr "启用 openpilot 纵向控制(alpha)开关,以使用实验模式。" + +#: /home/batman/openpilot/system/ui/widgets/network.py:204 +#, python-format +msgid "Enter APN" +msgstr "输入 APN" + +#: /home/batman/openpilot/system/ui/widgets/network.py:241 +#, python-format +msgid "Enter SSID" +msgstr "输入 SSID" + +#: /home/batman/openpilot/system/ui/widgets/network.py:254 +#, python-format +msgid "Enter new tethering password" +msgstr "输入新的网络共享密码" + +#: /home/batman/openpilot/system/ui/widgets/network.py:237 +#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#, python-format +msgid "Enter password" +msgstr "输入密码" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:89 +#, python-format +msgid "Enter your GitHub username" +msgstr "输入您的 GitHub 用户名" + +#: /home/batman/openpilot/system/ui/widgets/list_view.py:123 +#: /home/batman/openpilot/system/ui/widgets/list_view.py:160 +#, python-format +msgid "Error" +msgstr "错误" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:52 +#, python-format +msgid "Experimental Mode" +msgstr "实验模式" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:181 +#, python-format +msgid "" +"Experimental mode is currently unavailable on this car since the car's stock " +"ACC is used for longitudinal control." +msgstr "此车型当前无法使用实验模式,因为纵向控制使用的是原厂 ACC。" + +#: /home/batman/openpilot/system/ui/widgets/network.py:373 +#, python-format +msgid "FORGETTING..." +msgstr "正在遗忘..." + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:44 +#, python-format +msgid "Finish Setup" +msgstr "完成设置" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:66 +msgid "Firehose" +msgstr "Firehose" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:18 +msgid "Firehose Mode" +msgstr "Firehose 模式" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:25 +msgid "" +"For maximum effectiveness, bring your device inside and connect to a good " +"USB-C adapter and Wi-Fi weekly.\n" +"\n" +"Firehose Mode can also work while you're driving if connected to a hotspot " +"or unlimited SIM card.\n" +"\n" +"\n" +"Frequently Asked Questions\n" +"\n" +"Does it matter how or where I drive? Nope, just drive as you normally " +"would.\n" +"\n" +"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a " +"subset of your segments.\n" +"\n" +"What's a good USB-C adapter? Any fast phone or laptop charger should be " +"fine.\n" +"\n" +"Does it matter which software I run? Yes, only upstream openpilot (and " +"particular forks) are able to be used for training." +msgstr "" +"为达到最佳效果,请将设备带到室内,并每周连接优质 USB‑C 充电器与 Wi‑Fi。\n" +"\n" +"若连接热点或不限流量卡,行车中也可使用 Firehose 模式。\n" +"\n" +"\n" +"常见问题\n" +"\n" +"我怎么开、在哪开有区别吗?没有,平常怎么开就怎么开。\n" +"\n" +"Firehose 模式会拉取我所有片段吗?不会,我们会选择性拉取部分片段。\n" +"\n" +"什么是好的 USB‑C 充电器?任何快速的手机或笔电充电器都可以。\n" +"\n" +"我跑什么软件有区别吗?有,只有上游 openpilot(及特定分支)可用于训练。" + +#: /home/batman/openpilot/system/ui/widgets/network.py:318 +#: /home/batman/openpilot/system/ui/widgets/network.py:451 +#, python-format +msgid "Forget" +msgstr "忘记" + +#: /home/batman/openpilot/system/ui/widgets/network.py:319 +#, python-format +msgid "Forget Wi-Fi Network \"{}\"?" +msgstr "要忘记 Wi‑Fi 网络“{}”吗?" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:125 +msgid "GOOD" +msgstr "良好" + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:128 +#, python-format +msgid "Go to https://connect.comma.ai on your phone" +msgstr "在手机上前往 https://connect.comma.ai" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:129 +msgid "HIGH" +msgstr "高" + +#: /home/batman/openpilot/system/ui/widgets/network.py:155 +#, python-format +msgid "Hidden Network" +msgstr "隐藏网络" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:140 +#, python-format +msgid "INACTIVE: connect to an unmetered network" +msgstr "未启用:请连接不限流量网络" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:53 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:136 +#, python-format +msgid "INSTALL" +msgstr "安装" + +#: /home/batman/openpilot/system/ui/widgets/network.py:150 +#, python-format +msgid "IP Address" +msgstr "IP 地址" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:53 +#, python-format +msgid "Install Update" +msgstr "安装更新" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:56 +#, python-format +msgid "Joystick Debug Mode" +msgstr "摇杆调试模式" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:29 +msgid "LOADING" +msgstr "加载中" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:48 +msgid "LTE" +msgstr "LTE" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:64 +#, python-format +msgid "Longitudinal Maneuver Mode" +msgstr "纵向操作模式" + +#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:148 +#, python-format +msgid "MAX" +msgstr "最大" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:75 +#, python-format +msgid "" +"Maximize your training data uploads to improve openpilot's driving models." +msgstr "最大化上传训练数据,以改进 openpilot 的驾驶模型。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:59 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:60 +#, python-format +msgid "N/A" +msgstr "无" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:142 +msgid "NO" +msgstr "否" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:63 +msgid "Network" +msgstr "网络" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:114 +#, python-format +msgid "No SSH keys found" +msgstr "未找到 SSH 密钥" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:126 +#, python-format +msgid "No SSH keys found for user '{}'" +msgstr "未找到用户“{}”的 SSH 密钥" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:320 +#, python-format +msgid "No release notes available." +msgstr "暂无发行说明。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:134 +msgid "OFFLINE" +msgstr "离线" + +#: /home/batman/openpilot/system/ui/widgets/html_render.py:263 +#: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:93 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:127 +#, python-format +msgid "OK" +msgstr "确定" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:144 +msgid "ONLINE" +msgstr "在线" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:20 +#, python-format +msgid "Open" +msgstr "打开" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:48 +#, python-format +msgid "PAIR" +msgstr "配对" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:142 +msgid "PANDA" +msgstr "PANDA" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:62 +#, python-format +msgid "PREVIEW" +msgstr "预览" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:44 +#, python-format +msgid "PRIME FEATURES:" +msgstr "PRIME 功能:" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:48 +#, python-format +msgid "Pair Device" +msgstr "配对设备" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:19 +#, python-format +msgid "Pair device" +msgstr "配对设备" + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:103 +#, python-format +msgid "Pair your device to your comma account" +msgstr "将设备配对到您的 comma 账号" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:48 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:24 +#, python-format +msgid "" +"Pair your device with comma connect (connect.comma.ai) and claim your comma " +"prime offer." +msgstr "" +"将设备与 comma connect(connect.comma.ai)配对,领取您的 comma prime 优惠。" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:91 +#, python-format +msgid "Please connect to Wi-Fi to complete initial pairing" +msgstr "请连接 Wi‑Fi 以完成初始配对" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:187 +#, python-format +msgid "Power Off" +msgstr "关机" + +#: /home/batman/openpilot/system/ui/widgets/network.py:144 +#, python-format +msgid "Prevent large data uploads when on a metered Wi-Fi connection" +msgstr "在计量制 Wi‑Fi 连接时避免大量上传" + +#: /home/batman/openpilot/system/ui/widgets/network.py:135 +#, python-format +msgid "Prevent large data uploads when on a metered cellular connection" +msgstr "在计量制蜂窝网络时避免大量上传" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:25 +msgid "" +"Preview the driver facing camera to ensure that driver monitoring has good " +"visibility. (vehicle must be off)" +msgstr "预览车内摄像头以确保驾驶员监控视野良好。(车辆必须熄火)" + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:161 +#, python-format +msgid "QR Code Error" +msgstr "二维码错误" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:31 +msgid "REMOVE" +msgstr "移除" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:51 +#, python-format +msgid "RESET" +msgstr "重置" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:65 +#, python-format +msgid "REVIEW" +msgstr "查看" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:175 +#, python-format +msgid "Reboot" +msgstr "重启" + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:66 +#, python-format +msgid "Reboot Device" +msgstr "重启设备" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 +#, python-format +msgid "Reboot and Update" +msgstr "重启并更新" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:27 +msgid "" +"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)." +msgstr "" +"当车辆以超过 31 mph(50 km/h)行驶且未打转向灯越过检测到的车道线时,接收引导" +"回车道的警报。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:76 +#, python-format +msgid "Record and Upload Driver Camera" +msgstr "录制并上传车内摄像头" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:82 +#, python-format +msgid "Record and Upload Microphone Audio" +msgstr "录制并上传麦克风音频" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +msgid "" +"Record and store microphone audio while driving. The audio will be included " +"in the dashcam video in comma connect." +msgstr "" +"行驶时录制并保存麦克风音频。音频将包含在 comma connect 的行车记录视频中。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:67 +#, python-format +msgid "Regulatory" +msgstr "法规" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#, python-format +msgid "Relaxed" +msgstr "从容" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "Remote access" +msgstr "远程访问" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "Remote snapshots" +msgstr "远程快照" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:123 +#, python-format +msgid "Request timed out" +msgstr "请求超时" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:119 +#, python-format +msgid "Reset" +msgstr "重置" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:51 +#, python-format +msgid "Reset Calibration" +msgstr "重置校准" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:65 +#, python-format +msgid "Review Training Guide" +msgstr "查看训练指南" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:27 +msgid "Review the rules, features, and limitations of openpilot" +msgstr "查看 openpilot 的规则、功能与限制" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:61 +#, python-format +msgid "SELECT" +msgstr "选择" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:53 +#, python-format +msgid "SSH Keys" +msgstr "SSH 密钥" + +#: /home/batman/openpilot/system/ui/widgets/network.py:310 +#, python-format +msgid "Scanning Wi-Fi networks..." +msgstr "正在扫描 Wi‑Fi 网络…" + +#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:36 +#, python-format +msgid "Select" +msgstr "选择" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:183 +#, python-format +msgid "Select a branch" +msgstr "选择分支" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:91 +#, python-format +msgid "Select a language" +msgstr "选择语言" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:60 +#, python-format +msgid "Serial" +msgstr "序列号" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 +#, python-format +msgid "Snooze Update" +msgstr "延后更新" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:65 +msgid "Software" +msgstr "软件" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#, python-format +msgid "Standard" +msgstr "标准" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:22 +msgid "" +"Standard is recommended. In aggressive mode, openpilot will follow lead cars " +"closer and be more aggressive with the gas and brake. In relaxed mode " +"openpilot will stay further away from lead cars. On supported cars, you can " +"cycle through these personalities with your steering wheel distance button." +msgstr "" +"建议使用标准模式。激进模式下,openpilot 会更贴近前车,油门与刹车更为激进;从" +"容模式下,会与前车保持更远距离。在支持的车型上,可用方向盘距离按钮切换这些风" +"格。" + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:59 +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:65 +#, python-format +msgid "System Unresponsive" +msgstr "系统无响应" + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:58 +#, python-format +msgid "TAKE CONTROL IMMEDIATELY" +msgstr "请立即接管控制" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:129 +msgid "TEMP" +msgstr "温度" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:61 +#, python-format +msgid "Target Branch" +msgstr "目标分支" + +#: /home/batman/openpilot/system/ui/widgets/network.py:124 +#, python-format +msgid "Tethering Password" +msgstr "网络共享密码" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:64 +msgid "Toggles" +msgstr "切换" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:72 +#, python-format +msgid "UNINSTALL" +msgstr "卸载" + +#: /home/batman/openpilot/selfdrive/ui/layouts/home.py:155 +#, python-format +msgid "UPDATE" +msgstr "更新" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:72 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:163 +#, python-format +msgid "Uninstall" +msgstr "卸载" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:117 +msgid "Unknown" +msgstr "未知" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:48 +#, python-format +msgid "Updates are only downloaded while the car is off." +msgstr "仅在车辆熄火时下载更新。" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:33 +#, python-format +msgid "Upgrade Now" +msgstr "立即升级" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +msgid "" +"Upload data from the driver facing camera and help improve the driver " +"monitoring algorithm." +msgstr "上传车内摄像头数据,帮助改进驾驶员监控算法。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:88 +#, python-format +msgid "Use Metric System" +msgstr "使用公制" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:17 +msgid "" +"Use the openpilot system for adaptive cruise control and lane keep driver " +"assistance. Your attention is required at all times to use this feature." +msgstr "" +"使用 openpilot 进行自适应巡航与车道保持辅助。使用此功能时,您必须始终保持专" +"注。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:144 +msgid "VEHICLE" +msgstr "车辆" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:67 +#, python-format +msgid "VIEW" +msgstr "查看" + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:52 +#, python-format +msgid "Waiting to start" +msgstr "等待开始" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:19 +msgid "" +"Warning: This grants SSH access to all public keys in your GitHub settings. " +"Never enter a GitHub username other than your own. A comma employee will " +"NEVER ask you to add their GitHub username." +msgstr "" +"警告:这将授予对您 GitHub 设置中所有公钥的 SSH 访问权限。请勿输入非您本人的 " +"GitHub 用户名。comma 员工绝不会要求您添加他们的用户名。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:111 +#, python-format +msgid "Welcome to openpilot" +msgstr "欢迎使用 openpilot" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:20 +msgid "When enabled, pressing the accelerator pedal will disengage openpilot." +msgstr "启用后,踩下加速踏板将会脱离 openpilot。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:44 +msgid "Wi-Fi" +msgstr "Wi‑Fi" + +#: /home/batman/openpilot/system/ui/widgets/network.py:144 +#, python-format +msgid "Wi-Fi Network Metered" +msgstr "Wi‑Fi 计量网络" + +#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#, python-format +msgid "Wrong password" +msgstr "密码错误" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:145 +#, python-format +msgid "You must accept the Terms and Conditions in order to use openpilot." +msgstr "您必须接受条款与条件才能使用 openpilot。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:112 +#, python-format +msgid "" +"You must accept the Terms and Conditions to use openpilot. Read the latest " +"terms at https://comma.ai/terms before continuing." +msgstr "" +"您必须接受条款与条件才能使用 openpilot。继续前请阅读 https://comma.ai/terms " +"上的最新条款。" + +#: /home/batman/openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:34 +#, python-format +msgid "camera starting" +msgstr "相机启动中" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:63 +#, python-format +msgid "comma prime" +msgstr "comma prime" + +#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#, python-format +msgid "default" +msgstr "默认" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#, python-format +msgid "down" +msgstr "下" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:106 +#, python-format +msgid "failed to check for update" +msgstr "检查更新失败" + +#: /home/batman/openpilot/system/ui/widgets/network.py:237 +#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#, python-format +msgid "for \"{}\"" +msgstr "用于“{}”" + +#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:177 +#, python-format +msgid "km/h" +msgstr "公里/时" + +#: /home/batman/openpilot/system/ui/widgets/network.py:204 +#, python-format +msgid "leave blank for automatic configuration" +msgstr "留空以自动配置" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:134 +#, python-format +msgid "left" +msgstr "左" + +#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#, python-format +msgid "metered" +msgstr "计量" + +#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:177 +#, python-format +msgid "mph" +msgstr "英里/时" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:20 +#, python-format +msgid "never" +msgstr "从不" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:31 +#, python-format +msgid "now" +msgstr "现在" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:71 +#, python-format +msgid "openpilot Longitudinal Control (Alpha)" +msgstr "openpilot 纵向控制(Alpha)" + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:51 +#, python-format +msgid "openpilot Unavailable" +msgstr "openpilot 无法使用" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:158 +#, python-format +msgid "" +"openpilot defaults to driving in chill mode. Experimental mode enables alpha-" +"level features that aren't ready for chill mode. Experimental features are " +"listed below:

End-to-End Longitudinal Control


Let the driving " +"model control the gas and brakes. openpilot will drive as it thinks a human " +"would, including stopping for red lights and stop signs. Since the driving " +"model decides the speed to drive, the set speed will only act as an upper " +"bound. This is an alpha quality feature; mistakes should be expected." +"

New Driving Visualization


The driving visualization will " +"transition to the road-facing wide-angle camera at low speeds to better show " +"some turns. The Experimental mode logo will also be shown in the top right " +"corner." +msgstr "" +"openpilot 默认以安稳模式行驶。实验模式会启用尚未准备好用于安稳模式的 Alpha 级" +"功能。实验功能如下:

端到端纵向控制


让驾驶模型控制油门与刹车。" +"openpilot 会像人类一样驾驶,包括在红灯与停牌前停车。由于驾驶模型决定行驶速" +"度,设定速度仅作为上限。这是 Alpha 质量功能;预期会有错误。

全新驾驶可" +"视化


在低速时,驾驶可视化将切换至面向道路的广角摄像头以更好显示部分转" +"弯。右上角也会显示实验模式图标。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:165 +#, python-format +msgid "" +"openpilot is continuously calibrating, resetting is rarely required. " +"Resetting calibration will restart openpilot if the car is powered on." +msgstr "" +"openpilot 持续进行校准,通常无需重置。若车辆通电,重置校准将会重启 " +"openpilot。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:20 +msgid "" +"openpilot learns to drive by watching humans, like you, drive.\n" +"\n" +"Firehose Mode allows you to maximize your training data uploads to improve " +"openpilot's driving models. More data means bigger models, which means " +"better Experimental Mode." +msgstr "" +"openpilot 通过观察人类(例如您)的驾驶来学习。\n" +"\n" +"Firehose 模式可让您最大化上传训练数据,以改进 openpilot 的驾驶模型。更多数据" +"意味着更大的模型,也意味着更好的实验模式。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:183 +#, python-format +msgid "openpilot longitudinal control may come in a future update." +msgstr "openpilot 纵向控制可能会在未来更新中提供。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:26 +msgid "" +"openpilot requires the device to be mounted within 4° left or right and " +"within 5° up or 9° down." +msgstr "openpilot 要求设备安装在左右 4°、上 5° 或下 9° 以内。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:134 +#, python-format +msgid "right" +msgstr "右" + +#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#, python-format +msgid "unmetered" +msgstr "不限流量" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#, python-format +msgid "up" +msgstr "上" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:117 +#, python-format +msgid "up to date, last checked never" +msgstr "已是最新,最后检查:从未" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:115 +#, python-format +msgid "up to date, last checked {}" +msgstr "已是最新,最后检查:{}" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:109 +#, python-format +msgid "update available" +msgstr "有可用更新" + +#: /home/batman/openpilot/selfdrive/ui/layouts/home.py:169 +#, python-format +msgid "{} ALERT" +msgid_plural "{} ALERTS" +msgstr[0] "{} 条警报" +msgstr[1] "{} 条警报" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:40 +#, python-format +msgid "{} day ago" +msgid_plural "{} days ago" +msgstr[0] "{} 天前" +msgstr[1] "{} 天前" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:37 +#, python-format +msgid "{} hour ago" +msgid_plural "{} hours ago" +msgstr[0] "{} 小时前" +msgstr[1] "{} 小时前" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:34 +#, python-format +msgid "{} minute ago" +msgid_plural "{} minutes ago" +msgstr[0] "{} 分钟前" +msgstr[1] "{} 分钟前" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:111 +#, python-format +msgid "{} segment of your driving is in the training dataset so far." +msgid_plural "{} segments of your driving is in the training dataset so far." +msgstr[0] "目前已有 {} 个您的驾驶片段被纳入训练数据集。" +msgstr[1] "目前已有 {} 个您的驾驶片段被纳入训练数据集。" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:62 +#, python-format +msgid "✓ SUBSCRIBED" +msgstr "✓ 已订阅" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:22 +#, python-format +msgid "🔥 Firehose Mode 🔥" +msgstr "🔥 Firehose 模式 🔥" diff --git a/selfdrive/ui/translations/app_zh-CHT.po b/selfdrive/ui/translations/app_zh-CHT.po new file mode 100644 index 000000000..1e9e2c094 --- /dev/null +++ b/selfdrive/ui/translations/app_zh-CHT.po @@ -0,0 +1,1192 @@ +# Language zh-CHT translations for PACKAGE package. +# Copyright (C) 2025 THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# Automatically generated, 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-10-22 19:09-0700\n" +"PO-Revision-Date: 2025-10-22 16:32-0700\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: zh-CHT\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:160 +#, python-format +msgid " Steering torque response calibration is complete." +msgstr " 轉向扭矩回應校正完成。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:158 +#, python-format +msgid " Steering torque response calibration is {}% complete." +msgstr " 轉向扭矩回應校正已完成 {}%。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#, python-format +msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." +msgstr " 您的裝置朝向 {:.1f}° {} 與 {:.1f}° {}。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:43 +msgid "--" +msgstr "--" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "1 year of drive storage" +msgstr "1 年行駛資料儲存" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "24/7 LTE connectivity" +msgstr "全年無休 LTE 連線" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:46 +msgid "2G" +msgstr "2G" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:47 +msgid "3G" +msgstr "3G" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:49 +msgid "5G" +msgstr "5G" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:23 +msgid "" +"WARNING: openpilot longitudinal control is in alpha for this car and will " +"disable Automatic Emergency Braking (AEB).

On this car, openpilot " +"defaults to the car's built-in ACC instead of openpilot's longitudinal " +"control. Enable this to switch to openpilot longitudinal control. Enabling " +"Experimental mode is recommended when enabling openpilot longitudinal " +"control alpha. Changing this setting will restart openpilot if the car is " +"powered on." +msgstr "" +"警告:此車款的 openpilot 縱向控制仍為 alpha,將會停用自動緊急煞車 (AEB)。" +"

在此車款上,openpilot 預設使用車載 ACC,而非 openpilot 的縱向控" +"制。啟用此選項可切換為 openpilot 縱向控制。建議同時啟用實驗模式。若車輛通電," +"變更此設定將會重新啟動 openpilot。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:148 +#, python-format +msgid "

Steering lag calibration is complete." +msgstr "

轉向延遲校正完成。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:146 +#, python-format +msgid "

Steering lag calibration is {}% complete." +msgstr "

轉向延遲校正已完成 {}%。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:138 +#, python-format +msgid "ACTIVE" +msgstr "啟用" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:15 +msgid "" +"ADB (Android Debug Bridge) allows connecting to your device over USB or over " +"the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." +msgstr "" +"ADB (Android Debug Bridge) 可透過 USB 或網路連線至您的裝置。詳見 https://" +"docs.comma.ai/how-to/connect-to-comma。" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:30 +msgid "ADD" +msgstr "新增" + +#: /home/batman/openpilot/system/ui/widgets/network.py:139 +#, python-format +msgid "APN Setting" +msgstr "APN 設定" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 +#, python-format +msgid "Acknowledge Excessive Actuation" +msgstr "確認過度作動" + +#: /home/batman/openpilot/system/ui/widgets/network.py:74 +#: /home/batman/openpilot/system/ui/widgets/network.py:95 +#, python-format +msgid "Advanced" +msgstr "進階" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#, python-format +msgid "Aggressive" +msgstr "積極" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:116 +#, python-format +msgid "Agree" +msgstr "同意" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:70 +#, python-format +msgid "Always-On Driver Monitoring" +msgstr "持續啟用駕駛監控" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:186 +#, python-format +msgid "" +"An alpha version of openpilot longitudinal control can be tested, along with " +"Experimental mode, on non-release branches." +msgstr "openpilot 縱向控制的 alpha 版本可於非發行分支搭配實驗模式進行測試。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:187 +#, python-format +msgid "Are you sure you want to power off?" +msgstr "確定要關機嗎?" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:175 +#, python-format +msgid "Are you sure you want to reboot?" +msgstr "確定要重新啟動嗎?" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:119 +#, python-format +msgid "Are you sure you want to reset calibration?" +msgstr "確定要重設校正嗎?" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:163 +#, python-format +msgid "Are you sure you want to uninstall?" +msgstr "確定要解除安裝嗎?" + +#: /home/batman/openpilot/system/ui/widgets/network.py:99 +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:147 +#, python-format +msgid "Back" +msgstr "返回" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:38 +#, python-format +msgid "Become a comma prime member at connect.comma.ai" +msgstr "前往 connect.comma.ai 成為 comma prime 會員" + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:130 +#, python-format +msgid "Bookmark connect.comma.ai to your home screen to use it like an app" +msgstr "將 connect.comma.ai 加到主畫面,像 App 一樣使用" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:68 +#, python-format +msgid "CHANGE" +msgstr "變更" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:50 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:107 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:118 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:147 +#, python-format +msgid "CHECK" +msgstr "檢查" + +#: /home/batman/openpilot/selfdrive/ui/widgets/exp_mode_button.py:50 +#, python-format +msgid "CHILL MODE ON" +msgstr "安穩模式已開啟" + +#: /home/batman/openpilot/system/ui/widgets/network.py:155 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:138 +#, python-format +msgid "CONNECT" +msgstr "CONNECT" + +#: /home/batman/openpilot/system/ui/widgets/network.py:369 +#, python-format +msgid "CONNECTING..." +msgstr "CONNECTING..." + +#: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:23 +#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:35 +#: /home/batman/openpilot/system/ui/widgets/keyboard.py:81 +#: /home/batman/openpilot/system/ui/widgets/network.py:318 +#, python-format +msgid "Cancel" +msgstr "取消" + +#: /home/batman/openpilot/system/ui/widgets/network.py:134 +#, python-format +msgid "Cellular Metered" +msgstr "行動網路計量" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:68 +#, python-format +msgid "Change Language" +msgstr "變更語言" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:125 +#, python-format +msgid "Changing this setting will restart openpilot if the car is powered on." +msgstr "若車輛通電,變更此設定將重新啟動 openpilot。" + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:129 +#, python-format +msgid "Click \"add new device\" and scan the QR code on the right" +msgstr "點選「新增裝置」,掃描右側 QR 碼" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 +#, python-format +msgid "Close" +msgstr "關閉" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:49 +#, python-format +msgid "Current Version" +msgstr "目前版本" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:110 +#, python-format +msgid "DOWNLOAD" +msgstr "下載" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:115 +#, python-format +msgid "Decline" +msgstr "拒絕" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:148 +#, python-format +msgid "Decline, uninstall openpilot" +msgstr "拒絕並解除安裝 openpilot" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:67 +msgid "Developer" +msgstr "開發人員" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:62 +msgid "Device" +msgstr "裝置" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:58 +#, python-format +msgid "Disengage on Accelerator Pedal" +msgstr "踩下加速踏板時脫離" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:184 +#, python-format +msgid "Disengage to Power Off" +msgstr "脫離以關機" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:172 +#, python-format +msgid "Disengage to Reboot" +msgstr "脫離以重新啟動" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:103 +#, python-format +msgid "Disengage to Reset Calibration" +msgstr "脫離以重設校正" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:32 +msgid "Display speed in km/h instead of mph." +msgstr "以 km/h 顯示速度(非 mph)。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:59 +#, python-format +msgid "Dongle ID" +msgstr "Dongle ID" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:50 +#, python-format +msgid "Download" +msgstr "下載" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:62 +#, python-format +msgid "Driver Camera" +msgstr "車內鏡頭" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:96 +#, python-format +msgid "Driving Personality" +msgstr "駕駛風格" + +#: /home/batman/openpilot/system/ui/widgets/network.py:123 +#: /home/batman/openpilot/system/ui/widgets/network.py:139 +#, python-format +msgid "EDIT" +msgstr "編輯" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:138 +msgid "ERROR" +msgstr "錯誤" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:45 +msgid "ETH" +msgstr "ETH" + +#: /home/batman/openpilot/selfdrive/ui/widgets/exp_mode_button.py:50 +#, python-format +msgid "EXPERIMENTAL MODE ON" +msgstr "實驗模式已開啟" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:166 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:228 +#, python-format +msgid "Enable" +msgstr "啟用" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:39 +#, python-format +msgid "Enable ADB" +msgstr "啟用 ADB" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:64 +#, python-format +msgid "Enable Lane Departure Warnings" +msgstr "啟用偏離車道警示" + +#: /home/batman/openpilot/system/ui/widgets/network.py:129 +#, python-format +msgid "Enable Roaming" +msgstr "啟用漫遊" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:48 +#, python-format +msgid "Enable SSH" +msgstr "啟用 SSH" + +#: /home/batman/openpilot/system/ui/widgets/network.py:120 +#, python-format +msgid "Enable Tethering" +msgstr "啟用網路共享" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:30 +msgid "Enable driver monitoring even when openpilot is not engaged." +msgstr "即使未啟動 openpilot 亦啟用駕駛監控。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:46 +#, python-format +msgid "Enable openpilot" +msgstr "啟用 openpilot" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:189 +#, python-format +msgid "" +"Enable the openpilot longitudinal control (alpha) toggle to allow " +"Experimental mode." +msgstr "啟用 openpilot 縱向控制(alpha)切換,以使用實驗模式。" + +#: /home/batman/openpilot/system/ui/widgets/network.py:204 +#, python-format +msgid "Enter APN" +msgstr "輸入 APN" + +#: /home/batman/openpilot/system/ui/widgets/network.py:241 +#, python-format +msgid "Enter SSID" +msgstr "輸入 SSID" + +#: /home/batman/openpilot/system/ui/widgets/network.py:254 +#, python-format +msgid "Enter new tethering password" +msgstr "輸入新的網路共享密碼" + +#: /home/batman/openpilot/system/ui/widgets/network.py:237 +#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#, python-format +msgid "Enter password" +msgstr "輸入密碼" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:89 +#, python-format +msgid "Enter your GitHub username" +msgstr "輸入您的 GitHub 使用者名稱" + +#: /home/batman/openpilot/system/ui/widgets/list_view.py:123 +#: /home/batman/openpilot/system/ui/widgets/list_view.py:160 +#, python-format +msgid "Error" +msgstr "錯誤" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:52 +#, python-format +msgid "Experimental Mode" +msgstr "實驗模式" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:181 +#, python-format +msgid "" +"Experimental mode is currently unavailable on this car since the car's stock " +"ACC is used for longitudinal control." +msgstr "此車款目前無法使用實驗模式,因為縱向控制使用的是原廠 ACC。" + +#: /home/batman/openpilot/system/ui/widgets/network.py:373 +#, python-format +msgid "FORGETTING..." +msgstr "正在遺忘..." + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:44 +#, python-format +msgid "Finish Setup" +msgstr "完成設定" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:66 +msgid "Firehose" +msgstr "Firehose" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:18 +msgid "Firehose Mode" +msgstr "Firehose 模式" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:25 +msgid "" +"For maximum effectiveness, bring your device inside and connect to a good " +"USB-C adapter and Wi-Fi weekly.\n" +"\n" +"Firehose Mode can also work while you're driving if connected to a hotspot " +"or unlimited SIM card.\n" +"\n" +"\n" +"Frequently Asked Questions\n" +"\n" +"Does it matter how or where I drive? Nope, just drive as you normally " +"would.\n" +"\n" +"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a " +"subset of your segments.\n" +"\n" +"What's a good USB-C adapter? Any fast phone or laptop charger should be " +"fine.\n" +"\n" +"Does it matter which software I run? Yes, only upstream openpilot (and " +"particular forks) are able to be used for training." +msgstr "" +"為達最佳效果,請將裝置帶到室內,並每週連接優質 USB‑C 充電器與 Wi‑Fi。\n" +"\n" +"若連上熱點或吃到飽門號,行車中也可使用 Firehose 模式。\n" +"\n" +"\n" +"常見問題\n" +"\n" +"我怎麼開、在哪裡開有差嗎?沒有,平常怎麼開就怎麼開。\n" +"\n" +"Firehose 模式會拉取我所有片段嗎?不會,我們會選擇性拉取部分片段。\n" +"\n" +"什麼是好的 USB‑C 充電器?任何快速的手機或筆電充電器都可以。\n" +"\n" +"我跑什麼軟體有差嗎?有,只有上游 openpilot(及特定分支)可用於訓練。" + +#: /home/batman/openpilot/system/ui/widgets/network.py:318 +#: /home/batman/openpilot/system/ui/widgets/network.py:451 +#, python-format +msgid "Forget" +msgstr "忘記" + +#: /home/batman/openpilot/system/ui/widgets/network.py:319 +#, python-format +msgid "Forget Wi-Fi Network \"{}\"?" +msgstr "要忘記 Wi‑Fi 網路「{}」嗎?" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:125 +msgid "GOOD" +msgstr "良好" + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:128 +#, python-format +msgid "Go to https://connect.comma.ai on your phone" +msgstr "在手機上前往 https://connect.comma.ai" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:129 +msgid "HIGH" +msgstr "高" + +#: /home/batman/openpilot/system/ui/widgets/network.py:155 +#, python-format +msgid "Hidden Network" +msgstr "隱藏網路" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:140 +#, python-format +msgid "INACTIVE: connect to an unmetered network" +msgstr "未啟用:請連接不限流量網路" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:53 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:136 +#, python-format +msgid "INSTALL" +msgstr "安裝" + +#: /home/batman/openpilot/system/ui/widgets/network.py:150 +#, python-format +msgid "IP Address" +msgstr "IP 位址" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:53 +#, python-format +msgid "Install Update" +msgstr "安裝更新" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:56 +#, python-format +msgid "Joystick Debug Mode" +msgstr "搖桿除錯模式" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:29 +msgid "LOADING" +msgstr "載入中" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:48 +msgid "LTE" +msgstr "LTE" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:64 +#, python-format +msgid "Longitudinal Maneuver Mode" +msgstr "縱向操作模式" + +#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:148 +#, python-format +msgid "MAX" +msgstr "最大" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:75 +#, python-format +msgid "" +"Maximize your training data uploads to improve openpilot's driving models." +msgstr "最大化上傳訓練資料,以改進 openpilot 的駕駛模型。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:59 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:60 +#, python-format +msgid "N/A" +msgstr "無" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:142 +msgid "NO" +msgstr "否" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:63 +msgid "Network" +msgstr "網路" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:114 +#, python-format +msgid "No SSH keys found" +msgstr "找不到 SSH 金鑰" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:126 +#, python-format +msgid "No SSH keys found for user '{}'" +msgstr "找不到使用者 '{}' 的 SSH 金鑰" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:320 +#, python-format +msgid "No release notes available." +msgstr "無可用發行說明。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:134 +msgid "OFFLINE" +msgstr "離線" + +#: /home/batman/openpilot/system/ui/widgets/html_render.py:263 +#: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:93 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:127 +#, python-format +msgid "OK" +msgstr "確定" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:144 +msgid "ONLINE" +msgstr "線上" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:20 +#, python-format +msgid "Open" +msgstr "開啟" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:48 +#, python-format +msgid "PAIR" +msgstr "配對" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:142 +msgid "PANDA" +msgstr "PANDA" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:62 +#, python-format +msgid "PREVIEW" +msgstr "預覽" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:44 +#, python-format +msgid "PRIME FEATURES:" +msgstr "PRIME 功能:" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:48 +#, python-format +msgid "Pair Device" +msgstr "配對裝置" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:19 +#, python-format +msgid "Pair device" +msgstr "配對裝置" + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:103 +#, python-format +msgid "Pair your device to your comma account" +msgstr "將裝置配對至您的 comma 帳號" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:48 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:24 +#, python-format +msgid "" +"Pair your device with comma connect (connect.comma.ai) and claim your comma " +"prime offer." +msgstr "" +"將裝置與 comma connect(connect.comma.ai)配對,領取您的 comma prime 優惠。" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:91 +#, python-format +msgid "Please connect to Wi-Fi to complete initial pairing" +msgstr "請連線至 Wi‑Fi 以完成初始化配對" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:187 +#, python-format +msgid "Power Off" +msgstr "關機" + +#: /home/batman/openpilot/system/ui/widgets/network.py:144 +#, python-format +msgid "Prevent large data uploads when on a metered Wi-Fi connection" +msgstr "在計量制 Wi‑Fi 連線時避免大量上傳" + +#: /home/batman/openpilot/system/ui/widgets/network.py:135 +#, python-format +msgid "Prevent large data uploads when on a metered cellular connection" +msgstr "在計量制行動網路時避免大量上傳" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:25 +msgid "" +"Preview the driver facing camera to ensure that driver monitoring has good " +"visibility. (vehicle must be off)" +msgstr "預覽車內鏡頭以確保駕駛監控視野良好。(車輛須熄火)" + +#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:161 +#, python-format +msgid "QR Code Error" +msgstr "QR 碼錯誤" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:31 +msgid "REMOVE" +msgstr "移除" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:51 +#, python-format +msgid "RESET" +msgstr "重設" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:65 +#, python-format +msgid "REVIEW" +msgstr "檢視" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:175 +#, python-format +msgid "Reboot" +msgstr "重新啟動" + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:66 +#, python-format +msgid "Reboot Device" +msgstr "重新啟動裝置" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 +#, python-format +msgid "Reboot and Update" +msgstr "重新啟動並更新" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:27 +msgid "" +"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)." +msgstr "" +"當車輛以超過 31 mph(50 km/h)行駛且未打方向燈越過偵測到的車道線時,接收轉向" +"回車道的警示。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:76 +#, python-format +msgid "Record and Upload Driver Camera" +msgstr "錄製並上傳車內鏡頭" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:82 +#, python-format +msgid "Record and Upload Microphone Audio" +msgstr "錄製並上傳麥克風音訊" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +msgid "" +"Record and store microphone audio while driving. The audio will be included " +"in the dashcam video in comma connect." +msgstr "" +"行車時錄製並儲存麥克風音訊。音訊將包含在 comma connect 的行車紀錄影片中。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:67 +#, python-format +msgid "Regulatory" +msgstr "法規" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#, python-format +msgid "Relaxed" +msgstr "從容" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "Remote access" +msgstr "遠端存取" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "Remote snapshots" +msgstr "遠端擷圖" + +#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:123 +#, python-format +msgid "Request timed out" +msgstr "要求逾時" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:119 +#, python-format +msgid "Reset" +msgstr "重設" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:51 +#, python-format +msgid "Reset Calibration" +msgstr "重設校正" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:65 +#, python-format +msgid "Review Training Guide" +msgstr "檢視訓練指南" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:27 +msgid "Review the rules, features, and limitations of openpilot" +msgstr "檢視 openpilot 的規則、功能與限制" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:61 +#, python-format +msgid "SELECT" +msgstr "選取" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:53 +#, python-format +msgid "SSH Keys" +msgstr "SSH 金鑰" + +#: /home/batman/openpilot/system/ui/widgets/network.py:310 +#, python-format +msgid "Scanning Wi-Fi networks..." +msgstr "正在掃描 Wi‑Fi 網路…" + +#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:36 +#, python-format +msgid "Select" +msgstr "選取" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:183 +#, python-format +msgid "Select a branch" +msgstr "選取分支" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:91 +#, python-format +msgid "Select a language" +msgstr "選取語言" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:60 +#, python-format +msgid "Serial" +msgstr "序號" + +#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 +#, python-format +msgid "Snooze Update" +msgstr "延後更新" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:65 +msgid "Software" +msgstr "軟體" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#, python-format +msgid "Standard" +msgstr "標準" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:22 +msgid "" +"Standard is recommended. In aggressive mode, openpilot will follow lead cars " +"closer and be more aggressive with the gas and brake. In relaxed mode " +"openpilot will stay further away from lead cars. On supported cars, you can " +"cycle through these personalities with your steering wheel distance button." +msgstr "" +"建議使用標準模式。積極模式下,openpilot 會更貼近前車,油門與煞車反應更積極;" +"從容模式下,會與前車保持更遠距離。於支援車款,可用方向盤距離按鈕切換這些風" +"格。" + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:59 +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:65 +#, python-format +msgid "System Unresponsive" +msgstr "系統無回應" + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:58 +#, python-format +msgid "TAKE CONTROL IMMEDIATELY" +msgstr "請立刻接手控制" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:129 +msgid "TEMP" +msgstr "溫度" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:61 +#, python-format +msgid "Target Branch" +msgstr "目標分支" + +#: /home/batman/openpilot/system/ui/widgets/network.py:124 +#, python-format +msgid "Tethering Password" +msgstr "網路共享密碼" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:64 +msgid "Toggles" +msgstr "切換" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:72 +#, python-format +msgid "UNINSTALL" +msgstr "解除安裝" + +#: /home/batman/openpilot/selfdrive/ui/layouts/home.py:155 +#, python-format +msgid "UPDATE" +msgstr "更新" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:72 +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:163 +#, python-format +msgid "Uninstall" +msgstr "解除安裝" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:117 +msgid "Unknown" +msgstr "未知" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:48 +#, python-format +msgid "Updates are only downloaded while the car is off." +msgstr "僅在車輛熄火時下載更新。" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:33 +#, python-format +msgid "Upgrade Now" +msgstr "立即升級" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +msgid "" +"Upload data from the driver facing camera and help improve the driver " +"monitoring algorithm." +msgstr "上傳車內鏡頭資料,協助改善駕駛監控演算法。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:88 +#, python-format +msgid "Use Metric System" +msgstr "使用公制" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:17 +msgid "" +"Use the openpilot system for adaptive cruise control and lane keep driver " +"assistance. Your attention is required at all times to use this feature." +msgstr "" +"使用 openpilot 進行 ACC 與車道維持輔助。使用此功能時,您必須始終保持專注。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:144 +msgid "VEHICLE" +msgstr "車輛" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:67 +#, python-format +msgid "VIEW" +msgstr "檢視" + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:52 +#, python-format +msgid "Waiting to start" +msgstr "等待開始" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:19 +msgid "" +"Warning: This grants SSH access to all public keys in your GitHub settings. " +"Never enter a GitHub username other than your own. A comma employee will " +"NEVER ask you to add their GitHub username." +msgstr "" +"警告:這將授予對您 GitHub 設定中所有公開金鑰的 SSH 存取權。請勿輸入非您本人" +"的 GitHub 帳號。comma 員工絕不會要求您新增他們的帳號。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:111 +#, python-format +msgid "Welcome to openpilot" +msgstr "歡迎使用 openpilot" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:20 +msgid "When enabled, pressing the accelerator pedal will disengage openpilot." +msgstr "啟用後,踩下加速踏板將會脫離 openpilot。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:44 +msgid "Wi-Fi" +msgstr "Wi‑Fi" + +#: /home/batman/openpilot/system/ui/widgets/network.py:144 +#, python-format +msgid "Wi-Fi Network Metered" +msgstr "Wi‑Fi 計量網路" + +#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#, python-format +msgid "Wrong password" +msgstr "密碼錯誤" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:145 +#, python-format +msgid "You must accept the Terms and Conditions in order to use openpilot." +msgstr "您必須接受條款與細則才能使用 openpilot。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:112 +#, python-format +msgid "" +"You must accept the Terms and Conditions to use openpilot. Read the latest " +"terms at https://comma.ai/terms before continuing." +msgstr "" +"您必須接受條款與細則才能使用 openpilot。繼續前請閱讀 https://comma.ai/terms " +"上的最新條款。" + +#: /home/batman/openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:34 +#, python-format +msgid "camera starting" +msgstr "相機啟動中" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:63 +#, python-format +msgid "comma prime" +msgstr "comma prime" + +#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#, python-format +msgid "default" +msgstr "預設" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#, python-format +msgid "down" +msgstr "下" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:106 +#, python-format +msgid "failed to check for update" +msgstr "檢查更新失敗" + +#: /home/batman/openpilot/system/ui/widgets/network.py:237 +#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#, python-format +msgid "for \"{}\"" +msgstr "適用於「{}」" + +#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:177 +#, python-format +msgid "km/h" +msgstr "公里/時" + +#: /home/batman/openpilot/system/ui/widgets/network.py:204 +#, python-format +msgid "leave blank for automatic configuration" +msgstr "留空以自動設定" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:134 +#, python-format +msgid "left" +msgstr "左" + +#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#, python-format +msgid "metered" +msgstr "計量" + +#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:177 +#, python-format +msgid "mph" +msgstr "英里/時" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:20 +#, python-format +msgid "never" +msgstr "從不" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:31 +#, python-format +msgid "now" +msgstr "現在" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:71 +#, python-format +msgid "openpilot Longitudinal Control (Alpha)" +msgstr "openpilot 縱向控制(Alpha)" + +#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:51 +#, python-format +msgid "openpilot Unavailable" +msgstr "openpilot 無法使用" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:158 +#, python-format +msgid "" +"openpilot defaults to driving in chill mode. Experimental mode enables alpha-" +"level features that aren't ready for chill mode. Experimental features are " +"listed below:

End-to-End Longitudinal Control


Let the driving " +"model control the gas and brakes. openpilot will drive as it thinks a human " +"would, including stopping for red lights and stop signs. Since the driving " +"model decides the speed to drive, the set speed will only act as an upper " +"bound. This is an alpha quality feature; mistakes should be expected." +"

New Driving Visualization


The driving visualization will " +"transition to the road-facing wide-angle camera at low speeds to better show " +"some turns. The Experimental mode logo will also be shown in the top right " +"corner." +msgstr "" +"openpilot 預設以安穩模式行駛。實驗模式啟用尚未準備好進入安穩模式的 Alpha 等級" +"功能。實驗功能如下:

端到端縱向控制


讓駕駛模型控制油門與煞車。" +"openpilot 會如同人類駕駛般行駛,包括在紅燈與停車標誌前停車。由於駕駛模型決定" +"行駛速度,設定速度僅作為上限。此為 Alpha 品質功能;預期會有失誤。

全新" +"駕駛視覺化


在低速時,駕駛視覺化將切換至面向道路的廣角鏡頭以更好呈現部" +"分轉彎。右上角亦會顯示實驗模式圖示。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:165 +#, python-format +msgid "" +"openpilot is continuously calibrating, resetting is rarely required. " +"Resetting calibration will restart openpilot if the car is powered on." +msgstr "" +"openpilot 會持續校正,通常不需重設。若車輛通電,重設校正將重新啟動 " +"openpilot。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:20 +msgid "" +"openpilot learns to drive by watching humans, like you, drive.\n" +"\n" +"Firehose Mode allows you to maximize your training data uploads to improve " +"openpilot's driving models. More data means bigger models, which means " +"better Experimental Mode." +msgstr "" +"openpilot 透過觀察人類(也就是您)的駕駛方式來學習。\n" +"\n" +"Firehose 模式可讓您最大化上傳訓練資料,以改進 openpilot 的駕駛模型。更多資料" +"代表更大的模型,也就代表更好的實驗模式。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:183 +#, python-format +msgid "openpilot longitudinal control may come in a future update." +msgstr "openpilot 縱向控制可能於未來更新提供。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:26 +msgid "" +"openpilot requires the device to be mounted within 4° left or right and " +"within 5° up or 9° down." +msgstr "openpilot 要求裝置安裝在左右 4°、上 5° 或下 9° 以內。" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:134 +#, python-format +msgid "right" +msgstr "右" + +#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#, python-format +msgid "unmetered" +msgstr "不限流量" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#, python-format +msgid "up" +msgstr "上" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:117 +#, python-format +msgid "up to date, last checked never" +msgstr "已為最新,最後檢查:從未" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:115 +#, python-format +msgid "up to date, last checked {}" +msgstr "已為最新,最後檢查:{}" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:109 +#, python-format +msgid "update available" +msgstr "有可用更新" + +#: /home/batman/openpilot/selfdrive/ui/layouts/home.py:169 +#, python-format +msgid "{} ALERT" +msgid_plural "{} ALERTS" +msgstr[0] "{} 則警示" +msgstr[1] "{} 則警示" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:40 +#, python-format +msgid "{} day ago" +msgid_plural "{} days ago" +msgstr[0] "{} 天前" +msgstr[1] "{} 天前" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:37 +#, python-format +msgid "{} hour ago" +msgid_plural "{} hours ago" +msgstr[0] "{} 小時前" +msgstr[1] "{} 小時前" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:34 +#, python-format +msgid "{} minute ago" +msgid_plural "{} minutes ago" +msgstr[0] "{} 分鐘前" +msgstr[1] "{} 分鐘前" + +#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:111 +#, python-format +msgid "{} segment of your driving is in the training dataset so far." +msgid_plural "{} segments of your driving is in the training dataset so far." +msgstr[0] "目前已有 {} 個您的駕駛片段納入訓練資料集。" +msgstr[1] "目前已有 {} 個您的駕駛片段納入訓練資料集。" + +#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:62 +#, python-format +msgid "✓ SUBSCRIBED" +msgstr "✓ 已訂閱" + +#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:22 +#, python-format +msgid "🔥 Firehose Mode 🔥" +msgstr "🔥 Firehose 模式 🔥" diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 476a33a99..68e1fdc44 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -51,6 +51,14 @@ class FontWeight(StrEnum): BOLD = "Inter-Bold.ttf" EXTRA_BOLD = "Inter-ExtraBold.ttf" BLACK = "Inter-Black.ttf" + UNIFONT = "unifont.otf" + + +def font_fallback(font: rl.Font) -> rl.Font: + """Fall back to unifont for languages that require it.""" + if multilang.requires_unifont(): + return gui_app.font(FontWeight.UNIFONT) + return font @dataclass @@ -335,7 +343,7 @@ class GuiApplication: except KeyboardInterrupt: pass - def font(self, font_weight: FontWeight = FontWeight.NORMAL): + def font(self, font_weight: FontWeight = FontWeight.NORMAL) -> rl.Font: return self._fonts[font_weight] @property @@ -356,14 +364,16 @@ class GuiApplication: all_chars |= set("–‑✓×°§•") # Load only the characters used in translations - for language in multilang.codes: + for language, code in multilang.languages.items(): + all_chars |= set(language) try: - with open(os.path.join(TRANSLATIONS_DIR, f"app_{language}.po")) as f: + with open(os.path.join(TRANSLATIONS_DIR, f"app_{code}.po")) as f: all_chars |= set(f.read()) except FileNotFoundError: - cloudlog.warning(f"Translation file for language '{language}' not found when loading fonts.") + cloudlog.warning(f"Translation file for language '{code}' not found when loading fonts.") all_chars = "".join(all_chars) + cloudlog.debug(f"Loading fonts with {len(all_chars)} glyphs.") codepoint_count = rl.ffi.new("int *", 1) codepoints = rl.load_codepoints(all_chars, codepoint_count) @@ -390,6 +400,7 @@ class GuiApplication: rl._orig_draw_text_ex = rl.draw_text_ex def _draw_text_ex_scaled(font, text, position, font_size, spacing, tint): + font = font_fallback(font) return rl._orig_draw_text_ex(font, text, position, font_size * FONT_SCALE, spacing, tint) rl.draw_text_ex = _draw_text_ex_scaled diff --git a/system/ui/lib/multilang.py b/system/ui/lib/multilang.py index 767080892..03d519e60 100644 --- a/system/ui/lib/multilang.py +++ b/system/ui/lib/multilang.py @@ -3,25 +3,27 @@ import json import gettext from openpilot.common.params import Params from openpilot.common.basedir import BASEDIR +from openpilot.common.swaglog import cloudlog SYSTEM_UI_DIR = os.path.join(BASEDIR, "system", "ui") UI_DIR = os.path.join(BASEDIR, "selfdrive", "ui") TRANSLATIONS_DIR = os.path.join(UI_DIR, "translations") LANGUAGES_FILE = os.path.join(TRANSLATIONS_DIR, "languages.json") -SUPPORTED_LANGUAGES = [ - "en", - "de", - "fr", - "pt-BR", - "es", - "tr", +UNIFONT_LANGUAGES = [ + "ar", + "th", + "zh-CHT", + "zh-CHS", + "ko", + "ja", ] class Multilang: def __init__(self): self._params = Params() + self._language: str = "en" self.languages = {} self.codes = {} self._translation: gettext.NullTranslations | gettext.GNUTranslations = gettext.NullTranslations() @@ -29,28 +31,28 @@ class Multilang: @property def language(self) -> str: - lang = str(self._params.get("LanguageSetting")).strip("main_") - if lang not in SUPPORTED_LANGUAGES: - lang = "en" - return lang + return self._language + + def requires_unifont(self) -> bool: + """Certain languages require unifont to render their glyphs.""" + return self._language in UNIFONT_LANGUAGES def setup(self): - language = self.language try: - with open(os.path.join(TRANSLATIONS_DIR, f'app_{language}.mo'), 'rb') as fh: + with open(os.path.join(TRANSLATIONS_DIR, f'app_{self._language}.mo'), 'rb') as fh: translation = gettext.GNUTranslations(fh) translation.install() self._translation = translation - print(f"Loaded translations for language: {language}") + cloudlog.warning(f"Loaded translations for language: {self._language}") except FileNotFoundError: - print(f"No translation file found for language: {language}, using default.") + cloudlog.error(f"No translation file found for language: {self._language}, using default.") gettext.install('app') self._translation = gettext.NullTranslations() - return None def change_language(self, language_code: str) -> None: # Reinstall gettext with the selected language self._params.put("LanguageSetting", language_code) + self._language = language_code self.setup() def tr(self, text: str) -> str: @@ -61,8 +63,12 @@ class Multilang: def _load_languages(self): with open(LANGUAGES_FILE, encoding='utf-8') as f: - self.languages = {k: v for k, v in json.load(f).items() if v in SUPPORTED_LANGUAGES} - self.codes = {v: k for k, v in self.languages.items() if v in SUPPORTED_LANGUAGES} + self.languages = json.load(f) + self.codes = {v: k for k, v in self.languages.items()} + + lang = str(self._params.get("LanguageSetting")).removeprefix("main_") + if lang in self.codes: + self._language = lang multilang = Multilang() diff --git a/system/ui/lib/text_measure.py b/system/ui/lib/text_measure.py index b91090e05..ea6fa6336 100644 --- a/system/ui/lib/text_measure.py +++ b/system/ui/lib/text_measure.py @@ -1,5 +1,5 @@ import pyray as rl -from openpilot.system.ui.lib.application import FONT_SCALE +from openpilot.system.ui.lib.application import FONT_SCALE, font_fallback from openpilot.system.ui.lib.emoji import find_emoji _cache: dict[int, rl.Vector2] = {} @@ -7,6 +7,7 @@ _cache: dict[int, rl.Vector2] = {} def measure_text_cached(font: rl.Font, text: str, font_size: int, spacing: int = 0) -> rl.Vector2: """Caches text measurements to avoid redundant calculations.""" + font = font_fallback(font) key = hash((font.texture.id, text, font_size, spacing)) if key in _cache: return _cache[key] diff --git a/system/ui/lib/wrap_text.py b/system/ui/lib/wrap_text.py index f6caa3c5f..d51975087 100644 --- a/system/ui/lib/wrap_text.py +++ b/system/ui/lib/wrap_text.py @@ -1,5 +1,6 @@ import pyray as rl from openpilot.system.ui.lib.text_measure import measure_text_cached +from openpilot.system.ui.lib.application import font_fallback def _break_long_word(font: rl.Font, word: str, font_size: int, max_width: int) -> list[str]: @@ -40,6 +41,7 @@ _cache: dict[int, list[str]] = {} def wrap_text(font: rl.Font, text: str, font_size: int, max_width: int) -> list[str]: + font = font_fallback(font) key = hash((font.texture.id, text, font_size, max_width)) if key in _cache: return _cache[key] diff --git a/system/ui/widgets/label.py b/system/ui/widgets/label.py index 10f6f1400..425920810 100644 --- a/system/ui/widgets/label.py +++ b/system/ui/widgets/label.py @@ -134,9 +134,6 @@ class Label(Widget): self._font_size = size self._update_text(self._text) - def _update_layout_rects(self): - self._update_text(self._text) - def _update_text(self, text): self._emojis = [] self._text_size = [] @@ -170,6 +167,10 @@ class Label(Widget): self._text_size.append(measure_text_cached(self._font, t, self._font_size)) def _render(self, _): + # Text can be a callable + # TODO: cache until text changed + self._update_text(self._text) + text_size = self._text_size[0] if self._text_size else rl.Vector2(0.0, 0.0) if self._text_alignment_vertical == rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE: text_pos = rl.Vector2(self._rect.x, (self._rect.y + (self._rect.height - text_size.y) // 2)) diff --git a/system/ui/widgets/option_dialog.py b/system/ui/widgets/option_dialog.py index fc42b4808..3b2201164 100644 --- a/system/ui/widgets/option_dialog.py +++ b/system/ui/widgets/option_dialog.py @@ -17,7 +17,7 @@ LIST_ITEM_SPACING = 25 class MultiOptionDialog(Widget): - def __init__(self, title, options, current=""): + def __init__(self, title, options, current="", option_font_weight=FontWeight.MEDIUM): super().__init__() self.title = title self.options = options @@ -27,6 +27,7 @@ class MultiOptionDialog(Widget): # Create scroller with option buttons self.option_buttons = [Button(option, click_callback=lambda opt=option: self._on_option_clicked(opt), + font_weight=option_font_weight, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT, button_style=ButtonStyle.NORMAL, text_padding=50, elide_right=True) for option in options] self.scroller = Scroller(self.option_buttons, spacing=LIST_ITEM_SPACING) From 4f52f3f3c5b0bd31375650b68cd78eb1e2d0a668 Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Wed, 22 Oct 2025 21:48:44 -0500 Subject: [PATCH 238/910] raylib: match QT colors for danger button style (#36431) match colors for DANGER style --- system/ui/widgets/button.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system/ui/widgets/button.py b/system/ui/widgets/button.py index bd6517ad4..5b9c51886 100644 --- a/system/ui/widgets/button.py +++ b/system/ui/widgets/button.py @@ -47,7 +47,7 @@ BUTTON_DISABLED_TEXT_COLORS = { BUTTON_BACKGROUND_COLORS = { ButtonStyle.NORMAL: rl.Color(51, 51, 51, 255), ButtonStyle.PRIMARY: rl.Color(70, 91, 234, 255), - ButtonStyle.DANGER: rl.Color(255, 36, 36, 255), + ButtonStyle.DANGER: rl.Color(226, 44, 44, 255), ButtonStyle.TRANSPARENT: rl.BLACK, ButtonStyle.TRANSPARENT_WHITE_TEXT: rl.BLANK, ButtonStyle.TRANSPARENT_WHITE_BORDER: rl.BLACK, @@ -61,7 +61,7 @@ BUTTON_BACKGROUND_COLORS = { BUTTON_PRESSED_BACKGROUND_COLORS = { ButtonStyle.NORMAL: rl.Color(74, 74, 74, 255), ButtonStyle.PRIMARY: rl.Color(48, 73, 244, 255), - ButtonStyle.DANGER: rl.Color(204, 0, 0, 255), + ButtonStyle.DANGER: rl.Color(255, 36, 36, 255), ButtonStyle.TRANSPARENT: rl.BLACK, ButtonStyle.TRANSPARENT_WHITE_TEXT: rl.BLANK, ButtonStyle.TRANSPARENT_WHITE_BORDER: rl.BLANK, From 378212e5ab9d460f6f47c047f52fcc0b52df22b0 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Thu, 23 Oct 2025 12:24:07 +0800 Subject: [PATCH 239/910] cabana: remove dependency on selfdrive/ui (#36434) remove dependency on selfdrive/ui --- tools/cabana/SConscript | 64 +++++++++++- tools/cabana/cabana.cc | 1 - tools/cabana/detailwidget.cc | 1 + tools/cabana/detailwidget.h | 2 +- tools/cabana/streams/routes.h | 3 +- tools/cabana/utils/api.cc | 161 ++++++++++++++++++++++++++++++ tools/cabana/utils/api.h | 47 +++++++++ tools/cabana/utils/elidedlabel.cc | 29 ++++++ tools/cabana/utils/elidedlabel.h | 25 +++++ tools/cabana/utils/util.cc | 86 +++++++++++++++- tools/cabana/utils/util.h | 2 + 11 files changed, 410 insertions(+), 11 deletions(-) create mode 100644 tools/cabana/utils/api.cc create mode 100644 tools/cabana/utils/api.h create mode 100644 tools/cabana/utils/elidedlabel.cc create mode 100644 tools/cabana/utils/elidedlabel.h diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index 89be3cceb..e3674452d 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -1,7 +1,61 @@ -Import('qt_env', 'arch', 'common', 'messaging', 'visionipc', 'replay_lib', 'cereal', 'widgets') +import subprocess +import os + +Import('env', 'arch', 'common', 'messaging', 'visionipc', 'replay_lib', 'cereal') + +qt_env = env.Clone() +qt_modules = ["Widgets", "Gui", "Core", "Network", "Concurrent", "DBus", "Xml"] + +qt_libs = [] +if arch == "Darwin": + brew_prefix = subprocess.check_output(['brew', '--prefix'], encoding='utf8').strip() + qt_env['QTDIR'] = f"{brew_prefix}/opt/qt@5" + qt_dirs = [ + os.path.join(qt_env['QTDIR'], "include"), + ] + qt_dirs += [f"{qt_env['QTDIR']}/include/Qt{m}" for m in qt_modules] + qt_env["LINKFLAGS"] += ["-F" + os.path.join(qt_env['QTDIR'], "lib")] + qt_env["FRAMEWORKS"] += [f"Qt{m}" for m in qt_modules] + ["OpenGL"] + qt_env.AppendENVPath('PATH', os.path.join(qt_env['QTDIR'], "bin")) +else: + qt_install_prefix = subprocess.check_output(['qmake', '-query', 'QT_INSTALL_PREFIX'], encoding='utf8').strip() + qt_install_headers = subprocess.check_output(['qmake', '-query', 'QT_INSTALL_HEADERS'], encoding='utf8').strip() + + qt_env['QTDIR'] = qt_install_prefix + qt_dirs = [ + f"{qt_install_headers}", + ] + + qt_gui_path = os.path.join(qt_install_headers, "QtGui") + qt_gui_dirs = [d for d in os.listdir(qt_gui_path) if os.path.isdir(os.path.join(qt_gui_path, d))] + qt_dirs += [f"{qt_install_headers}/QtGui/{qt_gui_dirs[0]}/QtGui", ] if qt_gui_dirs else [] + qt_dirs += [f"{qt_install_headers}/Qt{m}" for m in qt_modules] + + qt_libs = [f"Qt5{m}" for m in qt_modules] + if arch == "larch64": + qt_libs += ["GLESv2", "wayland-client"] + qt_env.PrependENVPath('PATH', Dir("#third_party/qt5/larch64/bin/").abspath) + elif arch != "Darwin": + qt_libs += ["GL"] +qt_env['QT3DIR'] = qt_env['QTDIR'] +qt_env.Tool('qt3') + +qt_env['CPPPATH'] += qt_dirs + ["#third_party/qrcode"] +qt_flags = [ + "-D_REENTRANT", + "-DQT_NO_DEBUG", + "-DQT_WIDGETS_LIB", + "-DQT_GUI_LIB", + "-DQT_CORE_LIB", + "-DQT_MESSAGELOGCONTEXT", +] +qt_env['CXXFLAGS'] += qt_flags +qt_env['CXXFLAGS'] += ["-Wno-deprecated-declarations"] +qt_env['LIBPATH'] += ['#selfdrive/ui', ] +qt_env['LIBS'] = qt_libs base_frameworks = qt_env['FRAMEWORKS'] -base_libs = [common, messaging, cereal, visionipc, 'qt_util', 'm', 'ssl', 'crypto', 'pthread'] + qt_env["LIBS"] +base_libs = [common, messaging, cereal, visionipc, 'm', 'ssl', 'crypto', 'pthread'] + qt_env["LIBS"] if arch == "Darwin": base_frameworks.append('OpenCL') @@ -12,11 +66,11 @@ else: base_libs.append('Qt5Charts') base_libs.append('Qt5SerialBus') -qt_libs = ['qt_util'] + base_libs +qt_libs = base_libs cabana_env = qt_env.Clone() -cabana_libs = [widgets, cereal, messaging, visionipc, replay_lib, 'avutil', 'avcodec', 'avformat', 'bz2', 'zstd', 'curl', 'yuv', 'usb-1.0'] + qt_libs +cabana_libs = [cereal, messaging, visionipc, replay_lib, 'avutil', 'avcodec', 'avformat', 'bz2', 'zstd', 'curl', 'yuv', 'usb-1.0'] + qt_libs opendbc_path = '-DOPENDBC_FILE_PATH=\'"%s"\'' % (cabana_env.Dir("../../opendbc/dbc").abspath) cabana_env['CXXFLAGS'] += [opendbc_path] @@ -28,7 +82,7 @@ cabana_env.Depends(assets, Glob('/assets/*', exclude=[assets, assets_src, "asset cabana_lib = cabana_env.Library("cabana_lib", ['mainwin.cc', 'streams/socketcanstream.cc', 'streams/pandastream.cc', 'streams/devicestream.cc', 'streams/livestream.cc', 'streams/abstractstream.cc', 'streams/replaystream.cc', 'binaryview.cc', 'historylog.cc', 'videowidget.cc', 'signalview.cc', 'streams/routes.cc', 'dbc/dbc.cc', 'dbc/dbcfile.cc', 'dbc/dbcmanager.cc', - 'utils/export.cc', 'utils/util.cc', + 'utils/export.cc', 'utils/util.cc', 'utils/elidedlabel.cc', 'utils/api.cc', 'chart/chartswidget.cc', 'chart/chart.cc', 'chart/signalselector.cc', 'chart/tiplabel.cc', 'chart/sparkline.cc', 'commands.cc', 'messageswidget.cc', 'streamselector.cc', 'settings.cc', 'panda.cc', 'cameraview.cc', 'detailwidget.cc', 'tools/findsimilarbits.cc', 'tools/findsignal.cc', 'tools/routeinfo.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) diff --git a/tools/cabana/cabana.cc b/tools/cabana/cabana.cc index 97f178b1f..bc50afc03 100644 --- a/tools/cabana/cabana.cc +++ b/tools/cabana/cabana.cc @@ -1,7 +1,6 @@ #include #include -#include "selfdrive/ui/qt/util.h" #include "tools/cabana/mainwin.h" #include "tools/cabana/streams/devicestream.h" #include "tools/cabana/streams/pandastream.h" diff --git a/tools/cabana/detailwidget.cc b/tools/cabana/detailwidget.cc index 2e213988b..4eda46f37 100644 --- a/tools/cabana/detailwidget.cc +++ b/tools/cabana/detailwidget.cc @@ -3,6 +3,7 @@ #include #include #include +#include #include #include "tools/cabana/commands.h" diff --git a/tools/cabana/detailwidget.h b/tools/cabana/detailwidget.h index 47304fbdc..6df164b44 100644 --- a/tools/cabana/detailwidget.h +++ b/tools/cabana/detailwidget.h @@ -6,11 +6,11 @@ #include #include -#include "selfdrive/ui/qt/widgets/controls.h" #include "tools/cabana/binaryview.h" #include "tools/cabana/chart/chartswidget.h" #include "tools/cabana/historylog.h" #include "tools/cabana/signalview.h" +#include "tools/cabana/utils/elidedlabel.h" class EditMessageDialog : public QDialog { public: diff --git a/tools/cabana/streams/routes.h b/tools/cabana/streams/routes.h index ee5071221..045dc6722 100644 --- a/tools/cabana/streams/routes.h +++ b/tools/cabana/streams/routes.h @@ -2,8 +2,7 @@ #include #include - -#include "selfdrive/ui/qt/api.h" +#include "tools/cabana/utils/api.h" class RouteListWidget; class OneShotHttpRequest; diff --git a/tools/cabana/utils/api.cc b/tools/cabana/utils/api.cc new file mode 100644 index 000000000..99e904c49 --- /dev/null +++ b/tools/cabana/utils/api.cc @@ -0,0 +1,161 @@ +#include "selfdrive/ui/qt/api.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "common/util.h" +#include "system/hardware/hw.h" +#include "selfdrive/ui/qt/util.h" + +QString getVersion() { + static QString version = QString::fromStdString(Params().get("Version")); + return version; +} + +QString getUserAgent() { + return "openpilot-" + getVersion(); +} + +std::optional getDongleId() { + std::string id = Params().get("DongleId"); + + if (!id.empty() && (id != "UnregisteredDevice")) { + return QString::fromStdString(id); + } else { + return {}; + } +} + +namespace CommaApi { + +RSA *get_rsa_private_key() { + static std::unique_ptr rsa_private(nullptr, RSA_free); + if (!rsa_private) { + FILE *fp = fopen(Path::rsa_file().c_str(), "rb"); + if (!fp) { + qDebug() << "No RSA private key found, please run manager.py or registration.py"; + return nullptr; + } + rsa_private.reset(PEM_read_RSAPrivateKey(fp, NULL, NULL, NULL)); + fclose(fp); + } + return rsa_private.get(); +} + +QByteArray rsa_sign(const QByteArray &data) { + RSA *rsa_private = get_rsa_private_key(); + if (!rsa_private) return {}; + + QByteArray sig(RSA_size(rsa_private), Qt::Uninitialized); + unsigned int sig_len; + int ret = RSA_sign(NID_sha256, (unsigned char*)data.data(), data.size(), (unsigned char*)sig.data(), &sig_len, rsa_private); + assert(ret == 1); + assert(sig.size() == sig_len); + return sig; +} + +QString create_jwt(const QJsonObject &payloads, int expiry) { + QJsonObject header = {{"alg", "RS256"}}; + + auto t = QDateTime::currentSecsSinceEpoch(); + QJsonObject payload = {{"identity", getDongleId().value_or("")}, {"nbf", t}, {"iat", t}, {"exp", t + expiry}}; + for (auto it = payloads.begin(); it != payloads.end(); ++it) { + payload.insert(it.key(), it.value()); + } + + auto b64_opts = QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals; + QString jwt = QJsonDocument(header).toJson(QJsonDocument::Compact).toBase64(b64_opts) + '.' + + QJsonDocument(payload).toJson(QJsonDocument::Compact).toBase64(b64_opts); + + auto hash = QCryptographicHash::hash(jwt.toUtf8(), QCryptographicHash::Sha256); + return jwt + "." + rsa_sign(hash).toBase64(b64_opts); +} + +} // namespace CommaApi + +HttpRequest::HttpRequest(QObject *parent, bool create_jwt, int timeout) : create_jwt(create_jwt), QObject(parent) { + networkTimer = new QTimer(this); + networkTimer->setSingleShot(true); + networkTimer->setInterval(timeout); + connect(networkTimer, &QTimer::timeout, this, &HttpRequest::requestTimeout); +} + +bool HttpRequest::active() const { + return reply != nullptr; +} + +bool HttpRequest::timeout() const { + return reply && reply->error() == QNetworkReply::OperationCanceledError; +} + +void HttpRequest::sendRequest(const QString &requestURL, const HttpRequest::Method method) { + if (active()) { + qDebug() << "HttpRequest is active"; + return; + } + QString token; + if (create_jwt) { + token = CommaApi::create_jwt(); + } else { + QString token_json = QString::fromStdString(util::read_file(util::getenv("HOME") + "/.comma/auth.json")); + QJsonDocument json_d = QJsonDocument::fromJson(token_json.toUtf8()); + token = json_d["access_token"].toString(); + } + + QNetworkRequest request; + request.setUrl(QUrl(requestURL)); + request.setRawHeader("User-Agent", getUserAgent().toUtf8()); + + if (!token.isEmpty()) { + request.setRawHeader(QByteArray("Authorization"), ("JWT " + token).toUtf8()); + } + + if (method == HttpRequest::Method::GET) { + reply = nam()->get(request); + } else if (method == HttpRequest::Method::DELETE) { + reply = nam()->deleteResource(request); + } + + networkTimer->start(); + connect(reply, &QNetworkReply::finished, this, &HttpRequest::requestFinished); +} + +void HttpRequest::requestTimeout() { + reply->abort(); +} + +void HttpRequest::requestFinished() { + networkTimer->stop(); + + if (reply->error() == QNetworkReply::NoError) { + emit requestDone(reply->readAll(), true, reply->error()); + } else { + QString error; + if (reply->error() == QNetworkReply::OperationCanceledError) { + nam()->clearAccessCache(); + nam()->clearConnectionCache(); + error = "Request timed out"; + } else { + error = reply->errorString(); + } + emit requestDone(error, false, reply->error()); + } + + reply->deleteLater(); + reply = nullptr; +} + +QNetworkAccessManager *HttpRequest::nam() { + static QNetworkAccessManager *networkAccessManager = new QNetworkAccessManager(qApp); + return networkAccessManager; +} diff --git a/tools/cabana/utils/api.h b/tools/cabana/utils/api.h new file mode 100644 index 000000000..ad64d7e72 --- /dev/null +++ b/tools/cabana/utils/api.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include +#include +#include + +#include "common/util.h" + +namespace CommaApi { + +const QString BASE_URL = util::getenv("API_HOST", "https://api.commadotai.com").c_str(); +QByteArray rsa_sign(const QByteArray &data); +QString create_jwt(const QJsonObject &payloads = {}, int expiry = 3600); + +} // namespace CommaApi + +/** + * Makes a request to the request endpoint. + */ + +class HttpRequest : public QObject { + Q_OBJECT + +public: + enum class Method {GET, DELETE}; + + explicit HttpRequest(QObject* parent, bool create_jwt = true, int timeout = 20000); + void sendRequest(const QString &requestURL, const Method method = Method::GET); + bool active() const; + bool timeout() const; + +signals: + void requestDone(const QString &response, bool success, QNetworkReply::NetworkError error); + +protected: + QNetworkReply *reply = nullptr; + +private: + static QNetworkAccessManager *nam(); + QTimer *networkTimer = nullptr; + bool create_jwt; + +private slots: + void requestTimeout(); + void requestFinished(); +}; diff --git a/tools/cabana/utils/elidedlabel.cc b/tools/cabana/utils/elidedlabel.cc new file mode 100644 index 000000000..629a108b1 --- /dev/null +++ b/tools/cabana/utils/elidedlabel.cc @@ -0,0 +1,29 @@ +#include "tools/cabana/utils/elidedlabel.h" +#include +#include + +ElidedLabel::ElidedLabel(QWidget *parent) : ElidedLabel({}, parent) {} + +ElidedLabel::ElidedLabel(const QString &text, QWidget *parent) : QLabel(text.trimmed(), parent) { + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + setMinimumWidth(1); +} + +void ElidedLabel::resizeEvent(QResizeEvent* event) { + QLabel::resizeEvent(event); + lastText_ = elidedText_ = ""; +} + +void ElidedLabel::paintEvent(QPaintEvent *event) { + const QString curText = text(); + if (curText != lastText_) { + elidedText_ = fontMetrics().elidedText(curText, Qt::ElideRight, contentsRect().width()); + lastText_ = curText; + } + + QPainter painter(this); + drawFrame(&painter); + QStyleOption opt; + opt.initFrom(this); + style()->drawItemText(&painter, contentsRect(), alignment(), opt.palette, isEnabled(), elidedText_, foregroundRole()); +} diff --git a/tools/cabana/utils/elidedlabel.h b/tools/cabana/utils/elidedlabel.h new file mode 100644 index 000000000..577eea12a --- /dev/null +++ b/tools/cabana/utils/elidedlabel.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include + +class ElidedLabel : public QLabel { + Q_OBJECT + +public: + explicit ElidedLabel(QWidget *parent = 0); + explicit ElidedLabel(const QString &text, QWidget *parent = 0); + +signals: + void clicked(); + +protected: + void paintEvent(QPaintEvent *event) override; + void resizeEvent(QResizeEvent* event) override; + void mouseReleaseEvent(QMouseEvent *event) override { + if (rect().contains(event->pos())) { + emit clicked(); + } + } + QString lastText_, elidedText_; +}; diff --git a/tools/cabana/utils/util.cc b/tools/cabana/utils/util.cc index f27df4bf1..ca069b358 100644 --- a/tools/cabana/utils/util.cc +++ b/tools/cabana/utils/util.cc @@ -10,11 +10,16 @@ #include #include +#include #include #include #include - -#include "selfdrive/ui/qt/util.h" +#include +#include +#include +#include +#include +#include "common/util.h" // SegmentTree @@ -276,3 +281,80 @@ QString signalToolTip(const cabana::Signal *sig) { )").arg(sig->name).arg(sig->start_bit).arg(sig->size).arg(sig->msb).arg(sig->lsb) .arg(sig->is_little_endian ? "Y" : "N").arg(sig->is_signed ? "Y" : "N"); } + +void setSurfaceFormat() { + QSurfaceFormat fmt; +#ifdef __APPLE__ + fmt.setVersion(3, 2); + fmt.setProfile(QSurfaceFormat::OpenGLContextProfile::CoreProfile); + fmt.setRenderableType(QSurfaceFormat::OpenGL); +#else + fmt.setRenderableType(QSurfaceFormat::OpenGLES); +#endif + fmt.setSamples(16); + fmt.setStencilBufferSize(1); + QSurfaceFormat::setDefaultFormat(fmt); +} + +void sigTermHandler(int s) { + std::signal(s, SIG_DFL); + qApp->quit(); +} + +void initApp(int argc, char *argv[], bool disable_hidpi) { + // setup signal handlers to exit gracefully + std::signal(SIGINT, sigTermHandler); + std::signal(SIGTERM, sigTermHandler); + + QString app_dir; +#ifdef __APPLE__ + // Get the devicePixelRatio, and scale accordingly to maintain 1:1 rendering + QApplication tmp(argc, argv); + app_dir = QCoreApplication::applicationDirPath(); + if (disable_hidpi) { + qputenv("QT_SCALE_FACTOR", QString::number(1.0 / tmp.devicePixelRatio()).toLocal8Bit()); + } +#else + app_dir = QFileInfo(util::readlink("/proc/self/exe").c_str()).path(); +#endif + + qputenv("QT_DBL_CLICK_DIST", QByteArray::number(150)); + // ensure the current dir matches the exectuable's directory + QDir::setCurrent(app_dir); + + setSurfaceFormat(); +} + +static QHash load_bootstrap_icons() { + QHash icons; + + QFile f(":/bootstrap-icons.svg"); + if (f.open(QIODevice::ReadOnly | QIODevice::Text)) { + QDomDocument xml; + xml.setContent(&f); + QDomNode n = xml.documentElement().firstChild(); + while (!n.isNull()) { + QDomElement e = n.toElement(); + if (!e.isNull() && e.hasAttribute("id")) { + QString svg_str; + QTextStream stream(&svg_str); + n.save(stream, 0); + svg_str.replace("", ""); + icons[e.attribute("id")] = svg_str.toUtf8(); + } + n = n.nextSibling(); + } + } + return icons; +} + +QPixmap bootstrapPixmap(const QString &id) { + static QHash icons = load_bootstrap_icons(); + + QPixmap pixmap; + if (auto it = icons.find(id); it != icons.end()) { + pixmap.loadFromData(it.value(), "svg"); + } + return pixmap; +} diff --git a/tools/cabana/utils/util.h b/tools/cabana/utils/util.h index 5c1cbe2d6..f839ffe7f 100644 --- a/tools/cabana/utils/util.h +++ b/tools/cabana/utils/util.h @@ -166,3 +166,5 @@ private: int num_decimals(double num); QString signalToolTip(const cabana::Signal *sig); inline QString toHexString(int value) { return QString("0x%1").arg(QString::number(value, 16).toUpper(), 2, '0'); } +void initApp(int argc, char *argv[], bool disable_hidpi = true); +QPixmap bootstrapPixmap(const QString &id); From 1e73025f86d7f6cbf745a1b8507dde4bf47ec0d5 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 22 Oct 2025 22:18:07 -0700 Subject: [PATCH 240/910] Remove Qt (#36427) * rm qt from ui scons * rm qt translation litter * rm ccs * more * fix cabana * more * more * more --- .github/workflows/selfdrive_tests.yaml | 38 - .github/workflows/ui_preview.yaml | 174 --- .gitignore | 1 - SConstruct | 52 +- pyproject.toml | 2 +- selfdrive/ui/SConscript | 128 +- selfdrive/ui/main.cc | 30 - selfdrive/ui/qt/api.cc | 142 --- selfdrive/ui/qt/api.h | 47 - selfdrive/ui/qt/body.cc | 161 --- selfdrive/ui/qt/body.h | 38 - selfdrive/ui/qt/home.cc | 243 ---- selfdrive/ui/qt/home.h | 73 -- selfdrive/ui/qt/network/networking.cc | 413 ------ selfdrive/ui/qt/network/networking.h | 105 -- selfdrive/ui/qt/network/networkmanager.h | 48 - selfdrive/ui/qt/network/wifi_manager.cc | 539 -------- selfdrive/ui/qt/network/wifi_manager.h | 111 -- selfdrive/ui/qt/offroad/developer_panel.cc | 95 -- selfdrive/ui/qt/offroad/developer_panel.h | 22 - selfdrive/ui/qt/offroad/driverview.cc | 82 -- selfdrive/ui/qt/offroad/driverview.h | 23 - selfdrive/ui/qt/offroad/experimental_mode.cc | 76 -- selfdrive/ui/qt/offroad/experimental_mode.h | 31 - selfdrive/ui/qt/offroad/firehose.cc | 112 -- selfdrive/ui/qt/offroad/firehose.h | 27 - selfdrive/ui/qt/offroad/onboarding.cc | 211 ---- selfdrive/ui/qt/offroad/onboarding.h | 107 -- selfdrive/ui/qt/offroad/settings.cc | 535 -------- selfdrive/ui/qt/offroad/settings.h | 106 -- selfdrive/ui/qt/offroad/software_settings.cc | 156 --- selfdrive/ui/qt/onroad/alerts.cc | 112 -- selfdrive/ui/qt/onroad/alerts.h | 39 - selfdrive/ui/qt/onroad/annotated_camera.cc | 157 --- selfdrive/ui/qt/onroad/annotated_camera.h | 37 - selfdrive/ui/qt/onroad/buttons.cc | 49 - selfdrive/ui/qt/onroad/buttons.h | 28 - selfdrive/ui/qt/onroad/driver_monitoring.cc | 107 -- selfdrive/ui/qt/onroad/driver_monitoring.h | 24 - selfdrive/ui/qt/onroad/hud.cc | 112 -- selfdrive/ui/qt/onroad/hud.h | 26 - selfdrive/ui/qt/onroad/model.cc | 250 ---- selfdrive/ui/qt/onroad/model.h | 39 - selfdrive/ui/qt/onroad/onroad_home.cc | 65 - selfdrive/ui/qt/onroad/onroad_home.h | 22 - selfdrive/ui/qt/prime_state.cc | 48 - selfdrive/ui/qt/prime_state.h | 33 - selfdrive/ui/qt/qt_window.cc | 36 - selfdrive/ui/qt/qt_window.h | 20 - selfdrive/ui/qt/request_repeater.cc | 27 - selfdrive/ui/qt/request_repeater.h | 15 - selfdrive/ui/qt/sidebar.cc | 165 --- selfdrive/ui/qt/sidebar.h | 66 - selfdrive/ui/qt/util.cc | 228 ---- selfdrive/ui/qt/util.h | 54 - selfdrive/ui/qt/widgets/cameraview.cc | 365 ------ selfdrive/ui/qt/widgets/cameraview.h | 89 -- selfdrive/ui/qt/widgets/controls.cc | 141 --- selfdrive/ui/qt/widgets/controls.h | 319 ----- selfdrive/ui/qt/widgets/input.cc | 336 ----- selfdrive/ui/qt/widgets/input.h | 71 -- selfdrive/ui/qt/widgets/keyboard.cc | 182 --- selfdrive/ui/qt/widgets/keyboard.h | 42 - selfdrive/ui/qt/widgets/offroad_alerts.cc | 138 -- selfdrive/ui/qt/widgets/offroad_alerts.h | 44 - selfdrive/ui/qt/widgets/prime.cc | 265 ---- selfdrive/ui/qt/widgets/prime.h | 70 - selfdrive/ui/qt/widgets/scrollview.cc | 49 - selfdrive/ui/qt/widgets/scrollview.h | 12 - selfdrive/ui/qt/widgets/ssh_keys.cc | 64 - selfdrive/ui/qt/widgets/ssh_keys.h | 32 - selfdrive/ui/qt/widgets/toggle.cc | 83 -- selfdrive/ui/qt/widgets/toggle.h | 44 - selfdrive/ui/qt/widgets/wifi.cc | 45 - selfdrive/ui/qt/widgets/wifi.h | 14 - selfdrive/ui/qt/window.cc | 98 -- selfdrive/ui/qt/window.h | 25 - .../ui/tests/create_test_translations.sh | 18 - selfdrive/ui/tests/test_runner.cc | 26 - selfdrive/ui/tests/test_translations.cc | 48 - selfdrive/ui/tests/test_translations.py | 4 +- selfdrive/ui/tests/test_ui/run.py | 310 ----- selfdrive/ui/translations/README.md | 71 -- selfdrive/ui/translations/ar.ts | 1090 ---------------- selfdrive/ui/translations/create_badges.py | 62 - selfdrive/ui/translations/de.ts | 1072 ---------------- selfdrive/ui/translations/en.ts | 48 - selfdrive/ui/translations/es.ts | 1074 ---------------- selfdrive/ui/translations/fr.ts | 1068 ---------------- selfdrive/ui/translations/ja.ts | 1069 ---------------- selfdrive/ui/translations/ko.ts | 1069 ---------------- selfdrive/ui/translations/nl.ts | 1120 ---------------- selfdrive/ui/translations/pl.ts | 1124 ----------------- selfdrive/ui/translations/pt-BR.ts | 1074 ---------------- selfdrive/ui/translations/th.ts | 1065 ---------------- selfdrive/ui/translations/tr.ts | 1062 ---------------- selfdrive/ui/translations/zh-CHS.ts | 1069 ---------------- selfdrive/ui/translations/zh-CHT.ts | 1069 ---------------- selfdrive/ui/ui.cc | 199 --- selfdrive/ui/ui.h | 132 -- selfdrive/ui/update_translations.py | 66 +- selfdrive/ui/update_translations_raylib.py | 38 - system/manager/process_config.py | 1 - third_party/qt5/larch64/bin/lrelease | 3 - third_party/qt5/larch64/bin/lupdate | 3 - tools/cabana/utils/api.cc | 5 +- 106 files changed, 70 insertions(+), 23204 deletions(-) delete mode 100644 .github/workflows/ui_preview.yaml delete mode 100644 selfdrive/ui/main.cc delete mode 100644 selfdrive/ui/qt/api.cc delete mode 100644 selfdrive/ui/qt/api.h delete mode 100644 selfdrive/ui/qt/body.cc delete mode 100644 selfdrive/ui/qt/body.h delete mode 100644 selfdrive/ui/qt/home.cc delete mode 100644 selfdrive/ui/qt/home.h delete mode 100644 selfdrive/ui/qt/network/networking.cc delete mode 100644 selfdrive/ui/qt/network/networking.h delete mode 100644 selfdrive/ui/qt/network/networkmanager.h delete mode 100644 selfdrive/ui/qt/network/wifi_manager.cc delete mode 100644 selfdrive/ui/qt/network/wifi_manager.h delete mode 100644 selfdrive/ui/qt/offroad/developer_panel.cc delete mode 100644 selfdrive/ui/qt/offroad/developer_panel.h delete mode 100644 selfdrive/ui/qt/offroad/driverview.cc delete mode 100644 selfdrive/ui/qt/offroad/driverview.h delete mode 100644 selfdrive/ui/qt/offroad/experimental_mode.cc delete mode 100644 selfdrive/ui/qt/offroad/experimental_mode.h delete mode 100644 selfdrive/ui/qt/offroad/firehose.cc delete mode 100644 selfdrive/ui/qt/offroad/firehose.h delete mode 100644 selfdrive/ui/qt/offroad/onboarding.cc delete mode 100644 selfdrive/ui/qt/offroad/onboarding.h delete mode 100644 selfdrive/ui/qt/offroad/settings.cc delete mode 100644 selfdrive/ui/qt/offroad/settings.h delete mode 100644 selfdrive/ui/qt/offroad/software_settings.cc delete mode 100644 selfdrive/ui/qt/onroad/alerts.cc delete mode 100644 selfdrive/ui/qt/onroad/alerts.h delete mode 100644 selfdrive/ui/qt/onroad/annotated_camera.cc delete mode 100644 selfdrive/ui/qt/onroad/annotated_camera.h delete mode 100644 selfdrive/ui/qt/onroad/buttons.cc delete mode 100644 selfdrive/ui/qt/onroad/buttons.h delete mode 100644 selfdrive/ui/qt/onroad/driver_monitoring.cc delete mode 100644 selfdrive/ui/qt/onroad/driver_monitoring.h delete mode 100644 selfdrive/ui/qt/onroad/hud.cc delete mode 100644 selfdrive/ui/qt/onroad/hud.h delete mode 100644 selfdrive/ui/qt/onroad/model.cc delete mode 100644 selfdrive/ui/qt/onroad/model.h delete mode 100644 selfdrive/ui/qt/onroad/onroad_home.cc delete mode 100644 selfdrive/ui/qt/onroad/onroad_home.h delete mode 100644 selfdrive/ui/qt/prime_state.cc delete mode 100644 selfdrive/ui/qt/prime_state.h delete mode 100644 selfdrive/ui/qt/qt_window.cc delete mode 100644 selfdrive/ui/qt/qt_window.h delete mode 100644 selfdrive/ui/qt/request_repeater.cc delete mode 100644 selfdrive/ui/qt/request_repeater.h delete mode 100644 selfdrive/ui/qt/sidebar.cc delete mode 100644 selfdrive/ui/qt/sidebar.h delete mode 100644 selfdrive/ui/qt/util.cc delete mode 100644 selfdrive/ui/qt/util.h delete mode 100644 selfdrive/ui/qt/widgets/cameraview.cc delete mode 100644 selfdrive/ui/qt/widgets/cameraview.h delete mode 100644 selfdrive/ui/qt/widgets/controls.cc delete mode 100644 selfdrive/ui/qt/widgets/controls.h delete mode 100644 selfdrive/ui/qt/widgets/input.cc delete mode 100644 selfdrive/ui/qt/widgets/input.h delete mode 100644 selfdrive/ui/qt/widgets/keyboard.cc delete mode 100644 selfdrive/ui/qt/widgets/keyboard.h delete mode 100644 selfdrive/ui/qt/widgets/offroad_alerts.cc delete mode 100644 selfdrive/ui/qt/widgets/offroad_alerts.h delete mode 100644 selfdrive/ui/qt/widgets/prime.cc delete mode 100644 selfdrive/ui/qt/widgets/prime.h delete mode 100644 selfdrive/ui/qt/widgets/scrollview.cc delete mode 100644 selfdrive/ui/qt/widgets/scrollview.h delete mode 100644 selfdrive/ui/qt/widgets/ssh_keys.cc delete mode 100644 selfdrive/ui/qt/widgets/ssh_keys.h delete mode 100644 selfdrive/ui/qt/widgets/toggle.cc delete mode 100644 selfdrive/ui/qt/widgets/toggle.h delete mode 100644 selfdrive/ui/qt/widgets/wifi.cc delete mode 100644 selfdrive/ui/qt/widgets/wifi.h delete mode 100644 selfdrive/ui/qt/window.cc delete mode 100644 selfdrive/ui/qt/window.h delete mode 100755 selfdrive/ui/tests/create_test_translations.sh delete mode 100644 selfdrive/ui/tests/test_runner.cc delete mode 100644 selfdrive/ui/tests/test_translations.cc delete mode 100755 selfdrive/ui/tests/test_ui/run.py delete mode 100644 selfdrive/ui/translations/README.md delete mode 100644 selfdrive/ui/translations/ar.ts delete mode 100755 selfdrive/ui/translations/create_badges.py delete mode 100644 selfdrive/ui/translations/de.ts delete mode 100644 selfdrive/ui/translations/en.ts delete mode 100644 selfdrive/ui/translations/es.ts delete mode 100644 selfdrive/ui/translations/fr.ts delete mode 100644 selfdrive/ui/translations/ja.ts delete mode 100644 selfdrive/ui/translations/ko.ts delete mode 100644 selfdrive/ui/translations/nl.ts delete mode 100644 selfdrive/ui/translations/pl.ts delete mode 100644 selfdrive/ui/translations/pt-BR.ts delete mode 100644 selfdrive/ui/translations/th.ts delete mode 100644 selfdrive/ui/translations/tr.ts delete mode 100644 selfdrive/ui/translations/zh-CHS.ts delete mode 100644 selfdrive/ui/translations/zh-CHT.ts delete mode 100644 selfdrive/ui/ui.cc delete mode 100644 selfdrive/ui/ui.h delete mode 100755 selfdrive/ui/update_translations_raylib.py delete mode 100755 third_party/qt5/larch64/bin/lrelease delete mode 100755 third_party/qt5/larch64/bin/lupdate diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml index 35ced1e38..3f60fab73 100644 --- a/.github/workflows/selfdrive_tests.yaml +++ b/.github/workflows/selfdrive_tests.yaml @@ -164,8 +164,6 @@ jobs: # Pre-compile Python bytecode so each pytest worker doesn't need to $PYTEST --collect-only -m 'not slow' -qq && \ MAX_EXAMPLES=1 $PYTEST -m 'not slow' && \ - ./selfdrive/ui/tests/create_test_translations.sh && \ - QT_QPA_PLATFORM=offscreen ./selfdrive/ui/tests/test_translations && \ chmod -R 777 /tmp/comma_download_cache" process_replay: @@ -242,42 +240,6 @@ jobs: source selfdrive/test/setup_vsound.sh && \ CI=1 pytest -s tools/sim/tests/test_metadrive_bridge.py" - create_ui_report: - # This job name needs to be the same as UI_JOB_NAME in ui_preview.yaml - name: Create UI Report - runs-on: ${{ - (github.repository == 'commaai/openpilot') && - ((github.event_name != 'pull_request') || - (github.event.pull_request.head.repo.full_name == 'commaai/openpilot')) - && fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]') - || fromJSON('["ubuntu-24.04"]') }} - if: false # FIXME: FrameReader is broken on CI runners - steps: - - uses: actions/checkout@v4 - with: - submodules: true - - uses: ./.github/workflows/setup-with-retry - - name: caching frames - id: frames-cache - uses: actions/cache@v4 - with: - path: .ci_cache/comma_download_cache - key: ui_screenshots_test_${{ hashFiles('selfdrive/ui/tests/test_ui/run.py') }} - - name: Build openpilot - run: ${{ env.RUN }} "scons -j$(nproc)" - - name: Create Test Report - timeout-minutes: ${{ ((steps.frames-cache.outputs.cache-hit == 'true') && 1 || 3) }} - run: > - ${{ env.RUN }} "PYTHONWARNINGS=ignore && - source selfdrive/test/setup_xvfb.sh && - CACHE_ROOT=/tmp/comma_download_cache python3 selfdrive/ui/tests/test_ui/run.py && - chmod -R 777 /tmp/comma_download_cache" - - name: Upload Test Report - uses: actions/upload-artifact@v4 - with: - name: report-${{ inputs.run_number || '1' }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && 'master' || github.event.number }} - path: selfdrive/ui/tests/test_ui/report_1/screenshots - create_raylib_ui_report: name: Create raylib UI Report runs-on: ${{ diff --git a/.github/workflows/ui_preview.yaml b/.github/workflows/ui_preview.yaml deleted file mode 100644 index 9ec7a5922..000000000 --- a/.github/workflows/ui_preview.yaml +++ /dev/null @@ -1,174 +0,0 @@ -name: "ui preview" -on: - push: - branches: - - master - pull_request_target: - types: [assigned, opened, synchronize, reopened, edited] - branches: - - 'master' - paths: - - 'selfdrive/ui/**' - workflow_dispatch: - -env: - UI_JOB_NAME: "Create UI Report" - REPORT_NAME: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && 'master' || github.event.number }} - SHA: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && github.sha || github.event.pull_request.head.sha }} - BRANCH_NAME: "openpilot/pr-${{ github.event.number }}" - -jobs: - preview: - #if: github.repository == 'commaai/openpilot' - if: false # FIXME: FrameReader is broken on CI runners - name: preview - runs-on: ubuntu-latest - timeout-minutes: 20 - permissions: - contents: read - pull-requests: write - actions: read - steps: - - name: Waiting for ui generation to start - run: sleep 30 - - - name: Waiting for ui generation to end - uses: lewagon/wait-on-check-action@v1.3.4 - with: - ref: ${{ env.SHA }} - check-name: ${{ env.UI_JOB_NAME }} - repo-token: ${{ secrets.GITHUB_TOKEN }} - allowed-conclusions: success - wait-interval: 20 - - - name: Getting workflow run ID - id: get_run_id - run: | - echo "run_id=$(curl https://api.github.com/repos/${{ github.repository }}/commits/${{ env.SHA }}/check-runs | jq -r '.check_runs[] | select(.name == "${{ env.UI_JOB_NAME }}") | .html_url | capture("(?[0-9]+)") | .number')" >> $GITHUB_OUTPUT - - - name: Getting proposed ui - id: download-artifact - uses: dawidd6/action-download-artifact@v6 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - run_id: ${{ steps.get_run_id.outputs.run_id }} - search_artifacts: true - name: report-1-${{ env.REPORT_NAME }} - path: ${{ github.workspace }}/pr_ui - - - name: Getting master ui - uses: actions/checkout@v4 - with: - repository: commaai/ci-artifacts - ssh-key: ${{ secrets.CI_ARTIFACTS_DEPLOY_KEY }} - path: ${{ github.workspace }}/master_ui - ref: openpilot_master_ui - - - name: Saving new master ui - if: github.ref == 'refs/heads/master' && github.event_name == 'push' - working-directory: ${{ github.workspace }}/master_ui - run: | - git checkout --orphan=new_master_ui - git rm -rf * - git branch -D openpilot_master_ui - git branch -m openpilot_master_ui - git config user.name "GitHub Actions Bot" - git config user.email "<>" - mv ${{ github.workspace }}/pr_ui/*.png . - git add . - git commit -m "screenshots for commit ${{ env.SHA }}" - git push origin openpilot_master_ui --force - - - name: Finding diff - if: github.event_name == 'pull_request_target' - id: find_diff - run: >- - sudo apt-get update && sudo apt-get install -y imagemagick - - scenes=$(find ${{ github.workspace }}/pr_ui/*.png -type f -printf "%f\n" | cut -d '.' -f 1 | grep -v 'pair_device') - A=($scenes) - - DIFF="" - TABLE="
All Screenshots" - TABLE="${TABLE}" - - for ((i=0; i<${#A[*]}; i=i+1)); - do - # Check if the master file exists - if [ ! -f "${{ github.workspace }}/master_ui/${A[$i]}.png" ]; then - # This is a new file in PR UI that doesn't exist in master - DIFF="${DIFF}
" - DIFF="${DIFF}${A[$i]} : \$\${\\color{cyan}\\text{NEW}}\$\$" - DIFF="${DIFF}
" - - DIFF="${DIFF}" - DIFF="${DIFF} " - DIFF="${DIFF}" - - DIFF="${DIFF}
" - DIFF="${DIFF}
" - elif ! compare -fuzz 2% -highlight-color DeepSkyBlue1 -lowlight-color Black -compose Src ${{ github.workspace }}/master_ui/${A[$i]}.png ${{ github.workspace }}/pr_ui/${A[$i]}.png ${{ github.workspace }}/pr_ui/${A[$i]}_diff.png; then - convert ${{ github.workspace }}/pr_ui/${A[$i]}_diff.png -transparent black mask.png - composite mask.png ${{ github.workspace }}/master_ui/${A[$i]}.png composite_diff.png - convert -delay 100 ${{ github.workspace }}/master_ui/${A[$i]}.png composite_diff.png -loop 0 ${{ github.workspace }}/pr_ui/${A[$i]}_diff.gif - - mv ${{ github.workspace }}/master_ui/${A[$i]}.png ${{ github.workspace }}/pr_ui/${A[$i]}_master_ref.png - - DIFF="${DIFF}
" - DIFF="${DIFF}${A[$i]} : \$\${\\color{red}\\text{DIFFERENT}}\$\$" - DIFF="${DIFF}" - - DIFF="${DIFF}" - DIFF="${DIFF} " - DIFF="${DIFF} " - DIFF="${DIFF}" - - DIFF="${DIFF}" - DIFF="${DIFF} " - DIFF="${DIFF} " - DIFF="${DIFF}" - - DIFF="${DIFF}
master proposed
diff composite diff
" - DIFF="${DIFF}
" - else - rm -f ${{ github.workspace }}/pr_ui/${A[$i]}_diff.png - fi - - INDEX=$(($i % 2)) - if [[ $INDEX -eq 0 ]]; then - TABLE="${TABLE}" - fi - TABLE="${TABLE} " - if [[ $INDEX -eq 1 || $(($i + 1)) -eq ${#A[*]} ]]; then - TABLE="${TABLE}" - fi - done - - TABLE="${TABLE}" - - echo "DIFF=$DIFF$TABLE" >> "$GITHUB_OUTPUT" - - - name: Saving proposed ui - if: github.event_name == 'pull_request_target' - working-directory: ${{ github.workspace }}/master_ui - run: | - git config user.name "GitHub Actions Bot" - git config user.email "<>" - git checkout --orphan=${{ env.BRANCH_NAME }} - git rm -rf * - mv ${{ github.workspace }}/pr_ui/* . - git add . - git commit -m "screenshots for PR #${{ github.event.number }}" - git push origin ${{ env.BRANCH_NAME }} --force - - - name: Comment Screenshots on PR - if: github.event_name == 'pull_request_target' - uses: thollander/actions-comment-pull-request@v2 - with: - message: | - - ## UI Preview - ${{ steps.find_diff.outputs.DIFF }} - comment_tag: run_id_screenshots - pr_number: ${{ github.event.number }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index d3306a11b..b700df0c8 100644 --- a/.gitignore +++ b/.gitignore @@ -36,7 +36,6 @@ a.out *.class *.pyxbldc *.vcd -*.qm *.mo *_pyx.cpp config.json diff --git a/SConstruct b/SConstruct index 80273db10..648afdae5 100644 --- a/SConstruct +++ b/SConstruct @@ -165,57 +165,7 @@ else: np_version = SCons.Script.Value(np.__version__) Export('envCython', 'np_version') -# ********** Qt build environment ********** -qt_env = env.Clone() -qt_modules = ["Widgets", "Gui", "Core", "Network", "Concurrent", "DBus", "Xml"] - -qt_libs = [] -if arch == "Darwin": - qt_env['QTDIR'] = f"{brew_prefix}/opt/qt@5" - qt_dirs = [ - os.path.join(qt_env['QTDIR'], "include"), - ] - qt_dirs += [f"{qt_env['QTDIR']}/include/Qt{m}" for m in qt_modules] - qt_env["LINKFLAGS"] += ["-F" + os.path.join(qt_env['QTDIR'], "lib")] - qt_env["FRAMEWORKS"] += [f"Qt{m}" for m in qt_modules] + ["OpenGL"] - qt_env.AppendENVPath('PATH', os.path.join(qt_env['QTDIR'], "bin")) -else: - qt_install_prefix = subprocess.check_output(['qmake', '-query', 'QT_INSTALL_PREFIX'], encoding='utf8').strip() - qt_install_headers = subprocess.check_output(['qmake', '-query', 'QT_INSTALL_HEADERS'], encoding='utf8').strip() - - qt_env['QTDIR'] = qt_install_prefix - qt_dirs = [ - f"{qt_install_headers}", - ] - - qt_gui_path = os.path.join(qt_install_headers, "QtGui") - qt_gui_dirs = [d for d in os.listdir(qt_gui_path) if os.path.isdir(os.path.join(qt_gui_path, d))] - qt_dirs += [f"{qt_install_headers}/QtGui/{qt_gui_dirs[0]}/QtGui", ] if qt_gui_dirs else [] - qt_dirs += [f"{qt_install_headers}/Qt{m}" for m in qt_modules] - - qt_libs = [f"Qt5{m}" for m in qt_modules] - if arch == "larch64": - qt_libs += ["GLESv2", "wayland-client"] - qt_env.PrependENVPath('PATH', Dir("#third_party/qt5/larch64/bin/").abspath) - elif arch != "Darwin": - qt_libs += ["GL"] -qt_env['QT3DIR'] = qt_env['QTDIR'] -qt_env.Tool('qt3') - -qt_env['CPPPATH'] += qt_dirs + ["#third_party/qrcode"] -qt_flags = [ - "-D_REENTRANT", - "-DQT_NO_DEBUG", - "-DQT_WIDGETS_LIB", - "-DQT_GUI_LIB", - "-DQT_CORE_LIB", - "-DQT_MESSAGELOGCONTEXT", -] -qt_env['CXXFLAGS'] += qt_flags -qt_env['LIBPATH'] += ['#selfdrive/ui', ] -qt_env['LIBS'] = qt_libs - -Export('env', 'qt_env', 'arch') +Export('env', 'arch') # Setup cache dir cache_dir = '/data/scons_cache' if arch == "larch64" else '/tmp/scons_cache' diff --git a/pyproject.toml b/pyproject.toml index 6cc6298eb..8cb877c35 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -177,7 +177,7 @@ quiet-level = 3 # if you've got a short variable name that's getting flagged, add it here ignore-words-list = "bu,ro,te,ue,alo,hda,ois,nam,nams,ned,som,parm,setts,inout,warmup,bumb,nd,sie,preints,whit,indexIn,ws,uint,grey,deque,stdio,amin,BA,LITE,atEnd,UIs,errorString,arange,FocusIn,od,tim,relA,hist,copyable,jupyter,thead,TGE,abl,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, *.po, uv.lock, *.onnx, ./cereal/gen/*, */c_generated_code/*, docs/assets/*, tools/plotjuggler/layouts/*" +skip = "./third_party/*, ./tinygrad/*, ./tinygrad_repo/*, ./msgq/*, ./panda/*, ./opendbc/*, ./opendbc_repo/*, ./rednose/*, ./rednose_repo/*, ./teleoprtc/*, ./teleoprtc_repo/*, *.po, uv.lock, *.onnx, ./cereal/gen/*, */c_generated_code/*, docs/assets/*, tools/plotjuggler/layouts/*" [tool.mypy] python_version = "3.11" diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index a7670b248..a9b40f0d2 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -1,6 +1,9 @@ import os import json -Import('env', 'qt_env', 'arch', 'common', 'messaging', 'visionipc', 'transformations') +Import('env', 'arch', 'common', 'messaging', 'visionipc', 'transformations') + +raylib_env = env.Clone() + # compile gettext .po -> .mo translations with open(File("translations/languages.json").abspath) as f: @@ -11,101 +14,40 @@ po_sources = [src for src in po_sources if os.path.exists(File(src).abspath)] mo_targets = [src.replace(".po", ".mo") for src in po_sources] mo_build = [] for src, tgt in zip(po_sources, mo_targets): - mo_build.append(qt_env.Command(tgt, src, "msgfmt -o $TARGET $SOURCE")) -mo_alias = qt_env.Alias('mo', mo_build) -qt_env.AlwaysBuild(mo_alias) + mo_build.append(raylib_env.Command(tgt, src, "msgfmt -o $TARGET $SOURCE")) +mo_alias = raylib_env.Alias('mo', mo_build) +raylib_env.AlwaysBuild(mo_alias) if GetOption('extras'): - base_libs = [common, messaging, visionipc, transformations, - 'm', 'OpenCL', 'ssl', 'crypto', 'pthread'] + qt_env["LIBS"] + # build installers + if arch != "Darwin": + raylib_env['LIBPATH'] += [f'#third_party/raylib/{arch}/'] + raylib_env['LINKFLAGS'].append('-Wl,-strip-debug') - if arch == 'larch64': - base_libs.append('EGL') + raylib_libs = common + ["raylib"] + if arch == "larch64": + raylib_libs += ["GLESv2", "EGL", "gbm", "drm"] + else: + raylib_libs += ["GL"] - if arch == "Darwin": - del base_libs[base_libs.index('OpenCL')] - qt_env['FRAMEWORKS'] += ['OpenCL'] + release = "release3" + installers = [ + ("openpilot", release), + ("openpilot_test", f"{release}-staging"), + ("openpilot_nightly", "nightly"), + ("openpilot_internal", "nightly-dev"), + ] - # FIXME: remove this once we're on 5.15 (24.04) - qt_env['CXXFLAGS'] += ["-Wno-deprecated-declarations"] + cont = raylib_env.Command("installer/continue_openpilot.o", "installer/continue_openpilot.sh", + "ld -r -b binary -o $TARGET $SOURCE") + inter = raylib_env.Command("installer/inter_ttf.o", "installer/inter-ascii.ttf", + "ld -r -b binary -o $TARGET $SOURCE") + for name, branch in installers: + d = {'BRANCH': f"'\"{branch}\"'"} + if "internal" in name: + d['INTERNAL'] = "1" - qt_util = qt_env.Library("qt_util", ["#selfdrive/ui/qt/api.cc", "#selfdrive/ui/qt/util.cc"], LIBS=base_libs) - widgets_src = ["qt/widgets/input.cc", "qt/widgets/wifi.cc", "qt/prime_state.cc", - "qt/widgets/ssh_keys.cc", "qt/widgets/toggle.cc", "qt/widgets/controls.cc", - "qt/widgets/offroad_alerts.cc", "qt/widgets/prime.cc", "qt/widgets/keyboard.cc", - "qt/widgets/scrollview.cc", "qt/widgets/cameraview.cc", "#third_party/qrcode/QrCode.cc", - "qt/request_repeater.cc", "qt/qt_window.cc", "qt/network/networking.cc", "qt/network/wifi_manager.cc"] - - widgets = qt_env.Library("qt_widgets", widgets_src, LIBS=base_libs) - Export('widgets') - qt_libs = [widgets, qt_util] + base_libs - - qt_src = ["main.cc", "ui.cc", "qt/sidebar.cc", "qt/body.cc", - "qt/window.cc", "qt/home.cc", "qt/offroad/settings.cc", - "qt/offroad/software_settings.cc", "qt/offroad/developer_panel.cc", "qt/offroad/onboarding.cc", - "qt/offroad/driverview.cc", "qt/offroad/experimental_mode.cc", "qt/offroad/firehose.cc", - "qt/onroad/onroad_home.cc", "qt/onroad/annotated_camera.cc", "qt/onroad/model.cc", - "qt/onroad/buttons.cc", "qt/onroad/alerts.cc", "qt/onroad/driver_monitoring.cc", "qt/onroad/hud.cc"] - - # build translation files - translation_sources = [f"#selfdrive/ui/translations/{l}.ts" for l in languages.values()] - translation_targets = [src.replace(".ts", ".qm") for src in translation_sources] - lrelease_bin = 'third_party/qt5/larch64/bin/lrelease' if arch == 'larch64' else 'lrelease' - - lrelease = qt_env.Command(translation_targets, translation_sources, f"{lrelease_bin} $SOURCES") - qt_env.NoClean(translation_sources) - qt_env.Precious(translation_sources) - - # create qrc file for compiled translations to include with assets - translations_assets_src = "#selfdrive/assets/translations_assets.qrc" - with open(File(translations_assets_src).abspath, 'w') as f: - f.write('\n\n') - f.write('\n'.join([f'../ui/translations/{l}.qm' for l in languages.values()])) - f.write('\n\n') - - # build assets - assets = "#selfdrive/assets/assets.cc" - assets_src = "#selfdrive/assets/assets.qrc" - qt_env.Command(assets, [assets_src, translations_assets_src], f"rcc $SOURCES -o $TARGET") - qt_env.Depends(assets, Glob('#selfdrive/assets/*', exclude=[assets, assets_src, translations_assets_src, "#selfdrive/assets/assets.o"]) + [lrelease]) - asset_obj = qt_env.Object("assets", assets) - - # build main UI - qt_env.Program("ui", qt_src + [asset_obj], LIBS=qt_libs) - if GetOption('extras'): - qt_src.remove("main.cc") # replaced by test_runner - qt_env.Program('tests/test_translations', [asset_obj, 'tests/test_runner.cc', 'tests/test_translations.cc'] + qt_src, LIBS=qt_libs) - - # build installers - if arch != "Darwin": - raylib_env = env.Clone() - raylib_env['LIBPATH'] += [f'#third_party/raylib/{arch}/'] - raylib_env['LINKFLAGS'].append('-Wl,-strip-debug') - - raylib_libs = common + ["raylib"] - if arch == "larch64": - raylib_libs += ["GLESv2", "EGL", "gbm", "drm"] - else: - raylib_libs += ["GL"] - - release = "release3" - installers = [ - ("openpilot", release), - ("openpilot_test", f"{release}-staging"), - ("openpilot_nightly", "nightly"), - ("openpilot_internal", "nightly-dev"), - ] - - cont = raylib_env.Command("installer/continue_openpilot.o", "installer/continue_openpilot.sh", - "ld -r -b binary -o $TARGET $SOURCE") - inter = raylib_env.Command("installer/inter_ttf.o", "installer/inter-ascii.ttf", - "ld -r -b binary -o $TARGET $SOURCE") - for name, branch in installers: - d = {'BRANCH': f"'\"{branch}\"'"} - if "internal" in name: - d['INTERNAL'] = "1" - - obj = raylib_env.Object(f"installer/installers/installer_{name}.o", ["installer/installer.cc"], CPPDEFINES=d) - f = raylib_env.Program(f"installer/installers/installer_{name}", [obj, cont, inter], LIBS=raylib_libs) - # keep installers small - assert f[0].get_size() < 1900*1e3, f[0].get_size() + obj = raylib_env.Object(f"installer/installers/installer_{name}.o", ["installer/installer.cc"], CPPDEFINES=d) + f = raylib_env.Program(f"installer/installers/installer_{name}", [obj, cont, inter], LIBS=raylib_libs) + # keep installers small + assert f[0].get_size() < 1900*1e3, f[0].get_size() diff --git a/selfdrive/ui/main.cc b/selfdrive/ui/main.cc deleted file mode 100644 index 4903a3db3..000000000 --- a/selfdrive/ui/main.cc +++ /dev/null @@ -1,30 +0,0 @@ -#include - -#include -#include - -#include "system/hardware/hw.h" -#include "selfdrive/ui/qt/qt_window.h" -#include "selfdrive/ui/qt/util.h" -#include "selfdrive/ui/qt/window.h" - -int main(int argc, char *argv[]) { - setpriority(PRIO_PROCESS, 0, -20); - - qInstallMessageHandler(swagLogMessageHandler); - initApp(argc, argv); - - QTranslator translator; - QString translation_file = QString::fromStdString(Params().get("LanguageSetting")); - if (!translator.load(QString(":/%1").arg(translation_file)) && translation_file.length()) { - qCritical() << "Failed to load translation file:" << translation_file; - } - - QApplication a(argc, argv); - a.installTranslator(&translator); - - MainWindow w; - setMainWindow(&w); - a.installEventFilter(&w); - return a.exec(); -} diff --git a/selfdrive/ui/qt/api.cc b/selfdrive/ui/qt/api.cc deleted file mode 100644 index 6889b40e5..000000000 --- a/selfdrive/ui/qt/api.cc +++ /dev/null @@ -1,142 +0,0 @@ -#include "selfdrive/ui/qt/api.h" - -#include -#include - -#include -#include -#include -#include -#include -#include - -#include -#include - -#include "common/util.h" -#include "system/hardware/hw.h" -#include "selfdrive/ui/qt/util.h" - -namespace CommaApi { - -RSA *get_rsa_private_key() { - static std::unique_ptr rsa_private(nullptr, RSA_free); - if (!rsa_private) { - FILE *fp = fopen(Path::rsa_file().c_str(), "rb"); - if (!fp) { - qDebug() << "No RSA private key found, please run manager.py or registration.py"; - return nullptr; - } - rsa_private.reset(PEM_read_RSAPrivateKey(fp, NULL, NULL, NULL)); - fclose(fp); - } - return rsa_private.get(); -} - -QByteArray rsa_sign(const QByteArray &data) { - RSA *rsa_private = get_rsa_private_key(); - if (!rsa_private) return {}; - - QByteArray sig(RSA_size(rsa_private), Qt::Uninitialized); - unsigned int sig_len; - int ret = RSA_sign(NID_sha256, (unsigned char*)data.data(), data.size(), (unsigned char*)sig.data(), &sig_len, rsa_private); - assert(ret == 1); - assert(sig.size() == sig_len); - return sig; -} - -QString create_jwt(const QJsonObject &payloads, int expiry) { - QJsonObject header = {{"alg", "RS256"}}; - - auto t = QDateTime::currentSecsSinceEpoch(); - QJsonObject payload = {{"identity", getDongleId().value_or("")}, {"nbf", t}, {"iat", t}, {"exp", t + expiry}}; - for (auto it = payloads.begin(); it != payloads.end(); ++it) { - payload.insert(it.key(), it.value()); - } - - auto b64_opts = QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals; - QString jwt = QJsonDocument(header).toJson(QJsonDocument::Compact).toBase64(b64_opts) + '.' + - QJsonDocument(payload).toJson(QJsonDocument::Compact).toBase64(b64_opts); - - auto hash = QCryptographicHash::hash(jwt.toUtf8(), QCryptographicHash::Sha256); - return jwt + "." + rsa_sign(hash).toBase64(b64_opts); -} - -} // namespace CommaApi - -HttpRequest::HttpRequest(QObject *parent, bool create_jwt, int timeout) : create_jwt(create_jwt), QObject(parent) { - networkTimer = new QTimer(this); - networkTimer->setSingleShot(true); - networkTimer->setInterval(timeout); - connect(networkTimer, &QTimer::timeout, this, &HttpRequest::requestTimeout); -} - -bool HttpRequest::active() const { - return reply != nullptr; -} - -bool HttpRequest::timeout() const { - return reply && reply->error() == QNetworkReply::OperationCanceledError; -} - -void HttpRequest::sendRequest(const QString &requestURL, const HttpRequest::Method method) { - if (active()) { - qDebug() << "HttpRequest is active"; - return; - } - QString token; - if (create_jwt) { - token = CommaApi::create_jwt(); - } else { - QString token_json = QString::fromStdString(util::read_file(util::getenv("HOME") + "/.comma/auth.json")); - QJsonDocument json_d = QJsonDocument::fromJson(token_json.toUtf8()); - token = json_d["access_token"].toString(); - } - - QNetworkRequest request; - request.setUrl(QUrl(requestURL)); - request.setRawHeader("User-Agent", getUserAgent().toUtf8()); - - if (!token.isEmpty()) { - request.setRawHeader(QByteArray("Authorization"), ("JWT " + token).toUtf8()); - } - - if (method == HttpRequest::Method::GET) { - reply = nam()->get(request); - } else if (method == HttpRequest::Method::DELETE) { - reply = nam()->deleteResource(request); - } - - networkTimer->start(); - connect(reply, &QNetworkReply::finished, this, &HttpRequest::requestFinished); -} - -void HttpRequest::requestTimeout() { - reply->abort(); -} - -void HttpRequest::requestFinished() { - networkTimer->stop(); - - if (reply->error() == QNetworkReply::NoError) { - emit requestDone(reply->readAll(), true, reply->error()); - } else { - QString error; - if (reply->error() == QNetworkReply::OperationCanceledError) { - nam()->clearAccessCache(); - nam()->clearConnectionCache(); - error = "Request timed out"; - } else { - error = reply->errorString(); - } - emit requestDone(error, false, reply->error()); - } - - reply->deleteLater(); - reply = nullptr; -} - -QNetworkAccessManager *HttpRequest::nam() { - static QNetworkAccessManager *networkAccessManager = new QNetworkAccessManager(qApp); - return networkAccessManager; -} diff --git a/selfdrive/ui/qt/api.h b/selfdrive/ui/qt/api.h deleted file mode 100644 index ad64d7e72..000000000 --- a/selfdrive/ui/qt/api.h +++ /dev/null @@ -1,47 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -#include "common/util.h" - -namespace CommaApi { - -const QString BASE_URL = util::getenv("API_HOST", "https://api.commadotai.com").c_str(); -QByteArray rsa_sign(const QByteArray &data); -QString create_jwt(const QJsonObject &payloads = {}, int expiry = 3600); - -} // namespace CommaApi - -/** - * Makes a request to the request endpoint. - */ - -class HttpRequest : public QObject { - Q_OBJECT - -public: - enum class Method {GET, DELETE}; - - explicit HttpRequest(QObject* parent, bool create_jwt = true, int timeout = 20000); - void sendRequest(const QString &requestURL, const Method method = Method::GET); - bool active() const; - bool timeout() const; - -signals: - void requestDone(const QString &response, bool success, QNetworkReply::NetworkError error); - -protected: - QNetworkReply *reply = nullptr; - -private: - static QNetworkAccessManager *nam(); - QTimer *networkTimer = nullptr; - bool create_jwt; - -private slots: - void requestTimeout(); - void requestFinished(); -}; diff --git a/selfdrive/ui/qt/body.cc b/selfdrive/ui/qt/body.cc deleted file mode 100644 index e01adbe06..000000000 --- a/selfdrive/ui/qt/body.cc +++ /dev/null @@ -1,161 +0,0 @@ -#include "selfdrive/ui/qt/body.h" - -#include -#include - -#include -#include - -#include "common/params.h" -#include "common/timing.h" - -RecordButton::RecordButton(QWidget *parent) : QPushButton(parent) { - setCheckable(true); - setChecked(false); - setFixedSize(148, 148); - - QObject::connect(this, &QPushButton::toggled, [=]() { - setEnabled(false); - }); -} - -void RecordButton::paintEvent(QPaintEvent *event) { - QPainter p(this); - p.setRenderHint(QPainter::Antialiasing); - - QPoint center(width() / 2, height() / 2); - - QColor bg(isChecked() ? "#FFFFFF" : "#737373"); - QColor accent(isChecked() ? "#FF0000" : "#FFFFFF"); - if (!isEnabled()) { - bg = QColor("#404040"); - accent = QColor("#FFFFFF"); - } - - if (isDown()) { - accent.setAlphaF(0.7); - } - - p.setPen(Qt::NoPen); - p.setBrush(bg); - p.drawEllipse(center, 74, 74); - - p.setPen(QPen(accent, 6)); - p.setBrush(Qt::NoBrush); - p.drawEllipse(center, 42, 42); - - p.setPen(Qt::NoPen); - p.setBrush(accent); - p.drawEllipse(center, 22, 22); -} - - -BodyWindow::BodyWindow(QWidget *parent) : fuel_filter(1.0, 5., 1. / UI_FREQ), QWidget(parent) { - QStackedLayout *layout = new QStackedLayout(this); - layout->setStackingMode(QStackedLayout::StackAll); - - QWidget *w = new QWidget; - QVBoxLayout *vlayout = new QVBoxLayout(w); - vlayout->setMargin(45); - layout->addWidget(w); - - // face - face = new QLabel(); - face->setAlignment(Qt::AlignCenter); - layout->addWidget(face); - awake = new QMovie("../assets/body/awake.gif", {}, this); - awake->setCacheMode(QMovie::CacheAll); - sleep = new QMovie("../assets/body/sleep.gif", {}, this); - sleep->setCacheMode(QMovie::CacheAll); - - // record button - btn = new RecordButton(this); - vlayout->addWidget(btn, 0, Qt::AlignBottom | Qt::AlignRight); - QObject::connect(btn, &QPushButton::clicked, [=](bool checked) { - btn->setEnabled(false); - Params().putBool("DisableLogging", !checked); - last_button = nanos_since_boot(); - }); - w->raise(); - - QObject::connect(uiState(), &UIState::uiUpdate, this, &BodyWindow::updateState); -} - -void BodyWindow::paintEvent(QPaintEvent *event) { - QPainter p(this); - p.setRenderHint(QPainter::Antialiasing); - - p.fillRect(rect(), QColor(0, 0, 0)); - - // battery outline + detail - p.translate(width() - 136, 16); - const QColor gray = QColor("#737373"); - p.setBrush(Qt::NoBrush); - p.setPen(QPen(gray, 4, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)); - p.drawRoundedRect(2, 2, 78, 36, 8, 8); - - p.setPen(Qt::NoPen); - p.setBrush(gray); - p.drawRoundedRect(84, 12, 6, 16, 4, 4); - p.drawRect(84, 12, 3, 16); - - // battery level - double fuel = std::clamp(fuel_filter.x(), 0.2f, 1.0f); - const int m = 5; // manual margin since we can't do an inner border - p.setPen(Qt::NoPen); - p.setBrush(fuel > 0.25 ? QColor("#32D74B") : QColor("#FF453A")); - p.drawRoundedRect(2 + m, 2 + m, (78 - 2*m)*fuel, 36 - 2*m, 4, 4); - - // charging status - if (charging) { - p.setPen(Qt::NoPen); - p.setBrush(Qt::white); - const QPolygonF charger({ - QPointF(12.31, 0), - QPointF(12.31, 16.92), - QPointF(18.46, 16.92), - QPointF(6.15, 40), - QPointF(6.15, 23.08), - QPointF(0, 23.08), - }); - p.drawPolygon(charger.translated(98, 0)); - } -} - -void BodyWindow::offroadTransition(bool offroad) { - btn->setChecked(true); - btn->setEnabled(true); - fuel_filter.reset(1.0); -} - -void BodyWindow::updateState(const UIState &s) { - if (!isVisible()) { - return; - } - - const SubMaster &sm = *(s.sm); - auto cs = sm["carState"].getCarState(); - - charging = cs.getCharging(); - fuel_filter.update(cs.getFuelGauge()); - - // TODO: use carState.standstill when that's fixed - const bool standstill = std::abs(cs.getVEgo()) < 0.01; - QMovie *m = standstill ? sleep : awake; - if (m != face->movie()) { - face->setMovie(m); - face->movie()->start(); - } - - // update record button state - if (sm.updated("managerState") && (sm.rcv_time("managerState") - last_button)*1e-9 > 0.5) { - for (auto proc : sm["managerState"].getManagerState().getProcesses()) { - if (proc.getName() == "loggerd") { - btn->setEnabled(true); - btn->setChecked(proc.getRunning()); - } - } - } - - update(); -} diff --git a/selfdrive/ui/qt/body.h b/selfdrive/ui/qt/body.h deleted file mode 100644 index 567a54d49..000000000 --- a/selfdrive/ui/qt/body.h +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once - -#include -#include -#include - -#include "common/util.h" -#include "selfdrive/ui/ui.h" - -class RecordButton : public QPushButton { - Q_OBJECT - -public: - RecordButton(QWidget* parent = 0); - -private: - void paintEvent(QPaintEvent*) override; -}; - -class BodyWindow : public QWidget { - Q_OBJECT - -public: - BodyWindow(QWidget* parent = 0); - -private: - bool charging = false; - uint64_t last_button = 0; - FirstOrderFilter fuel_filter; - QLabel *face; - QMovie *awake, *sleep; - RecordButton *btn; - void paintEvent(QPaintEvent*) override; - -private slots: - void updateState(const UIState &s); - void offroadTransition(bool onroad); -}; diff --git a/selfdrive/ui/qt/home.cc b/selfdrive/ui/qt/home.cc deleted file mode 100644 index 8b1fdf474..000000000 --- a/selfdrive/ui/qt/home.cc +++ /dev/null @@ -1,243 +0,0 @@ -#include "selfdrive/ui/qt/home.h" - -#include -#include -#include -#include - -#include "selfdrive/ui/qt/offroad/experimental_mode.h" -#include "selfdrive/ui/qt/util.h" -#include "selfdrive/ui/qt/widgets/prime.h" - -// HomeWindow: the container for the offroad and onroad UIs - -HomeWindow::HomeWindow(QWidget* parent) : QWidget(parent) { - QHBoxLayout *main_layout = new QHBoxLayout(this); - main_layout->setMargin(0); - main_layout->setSpacing(0); - - sidebar = new Sidebar(this); - main_layout->addWidget(sidebar); - QObject::connect(sidebar, &Sidebar::openSettings, this, &HomeWindow::openSettings); - - slayout = new QStackedLayout(); - main_layout->addLayout(slayout); - - home = new OffroadHome(this); - QObject::connect(home, &OffroadHome::openSettings, this, &HomeWindow::openSettings); - slayout->addWidget(home); - - onroad = new OnroadWindow(this); - slayout->addWidget(onroad); - - body = new BodyWindow(this); - slayout->addWidget(body); - - driver_view = new DriverViewWindow(this); - connect(driver_view, &DriverViewWindow::done, [=] { - showDriverView(false); - }); - slayout->addWidget(driver_view); - setAttribute(Qt::WA_NoSystemBackground); - QObject::connect(uiState(), &UIState::uiUpdate, this, &HomeWindow::updateState); - QObject::connect(uiState(), &UIState::offroadTransition, this, &HomeWindow::offroadTransition); - QObject::connect(uiState(), &UIState::offroadTransition, sidebar, &Sidebar::offroadTransition); -} - -void HomeWindow::showSidebar(bool show) { - sidebar->setVisible(show); -} - -void HomeWindow::updateState(const UIState &s) { - const SubMaster &sm = *(s.sm); - - // switch to the generic robot UI - if (onroad->isVisible() && !body->isEnabled() && sm["carParams"].getCarParams().getNotCar()) { - body->setEnabled(true); - slayout->setCurrentWidget(body); - } -} - -void HomeWindow::offroadTransition(bool offroad) { - body->setEnabled(false); - sidebar->setVisible(offroad); - if (offroad) { - slayout->setCurrentWidget(home); - } else { - slayout->setCurrentWidget(onroad); - } -} - -void HomeWindow::showDriverView(bool show) { - if (show) { - emit closeSettings(); - slayout->setCurrentWidget(driver_view); - } else { - slayout->setCurrentWidget(home); - } - sidebar->setVisible(show == false); -} - -void HomeWindow::mousePressEvent(QMouseEvent* e) { - // Handle sidebar collapsing - if ((onroad->isVisible() || body->isVisible()) && (!sidebar->isVisible() || e->x() > sidebar->width())) { - sidebar->setVisible(!sidebar->isVisible()); - } -} - -void HomeWindow::mouseDoubleClickEvent(QMouseEvent* e) { - HomeWindow::mousePressEvent(e); - const SubMaster &sm = *(uiState()->sm); - if (sm["carParams"].getCarParams().getNotCar()) { - if (onroad->isVisible()) { - slayout->setCurrentWidget(body); - } else if (body->isVisible()) { - slayout->setCurrentWidget(onroad); - } - showSidebar(false); - } -} - -// OffroadHome: the offroad home page - -OffroadHome::OffroadHome(QWidget* parent) : QFrame(parent) { - QVBoxLayout* main_layout = new QVBoxLayout(this); - main_layout->setContentsMargins(40, 40, 40, 40); - - // top header - QHBoxLayout* header_layout = new QHBoxLayout(); - header_layout->setContentsMargins(0, 0, 0, 0); - header_layout->setSpacing(16); - - update_notif = new QPushButton(tr("UPDATE")); - update_notif->setVisible(false); - update_notif->setStyleSheet("background-color: #364DEF;"); - QObject::connect(update_notif, &QPushButton::clicked, [=]() { center_layout->setCurrentIndex(1); }); - header_layout->addWidget(update_notif, 0, Qt::AlignHCenter | Qt::AlignLeft); - - alert_notif = new QPushButton(); - alert_notif->setVisible(false); - alert_notif->setStyleSheet("background-color: #E22C2C;"); - QObject::connect(alert_notif, &QPushButton::clicked, [=] { center_layout->setCurrentIndex(2); }); - header_layout->addWidget(alert_notif, 0, Qt::AlignHCenter | Qt::AlignLeft); - - version = new ElidedLabel(); - header_layout->addWidget(version, 0, Qt::AlignHCenter | Qt::AlignRight); - - main_layout->addLayout(header_layout); - - // main content - main_layout->addSpacing(25); - center_layout = new QStackedLayout(); - - QWidget *home_widget = new QWidget(this); - { - QHBoxLayout *home_layout = new QHBoxLayout(home_widget); - home_layout->setContentsMargins(0, 0, 0, 0); - home_layout->setSpacing(30); - - // left: PrimeAdWidget - QStackedWidget *left_widget = new QStackedWidget(this); - QVBoxLayout *left_prime_layout = new QVBoxLayout(); - left_prime_layout->setContentsMargins(0, 0, 0, 0); - QWidget *prime_user = new PrimeUserWidget(); - prime_user->setStyleSheet(R"( - border-radius: 10px; - background-color: #333333; - )"); - left_prime_layout->addWidget(prime_user); - left_prime_layout->addStretch(); - left_widget->addWidget(new LayoutWidget(left_prime_layout)); - left_widget->addWidget(new PrimeAdWidget); - left_widget->setStyleSheet("border-radius: 10px;"); - - connect(uiState()->prime_state, &PrimeState::changed, [left_widget]() { - left_widget->setCurrentIndex(uiState()->prime_state->isSubscribed() ? 0 : 1); - }); - - home_layout->addWidget(left_widget, 1); - - // right: ExperimentalModeButton, SetupWidget - QWidget* right_widget = new QWidget(this); - QVBoxLayout* right_column = new QVBoxLayout(right_widget); - right_column->setContentsMargins(0, 0, 0, 0); - right_widget->setFixedWidth(750); - right_column->setSpacing(30); - - ExperimentalModeButton *experimental_mode = new ExperimentalModeButton(this); - QObject::connect(experimental_mode, &ExperimentalModeButton::openSettings, this, &OffroadHome::openSettings); - right_column->addWidget(experimental_mode, 1); - - SetupWidget *setup_widget = new SetupWidget; - QObject::connect(setup_widget, &SetupWidget::openSettings, this, &OffroadHome::openSettings); - right_column->addWidget(setup_widget, 1); - - home_layout->addWidget(right_widget, 1); - } - center_layout->addWidget(home_widget); - - // add update & alerts widgets - update_widget = new UpdateAlert(); - QObject::connect(update_widget, &UpdateAlert::dismiss, [=]() { center_layout->setCurrentIndex(0); }); - center_layout->addWidget(update_widget); - alerts_widget = new OffroadAlert(); - QObject::connect(alerts_widget, &OffroadAlert::dismiss, [=]() { center_layout->setCurrentIndex(0); }); - center_layout->addWidget(alerts_widget); - - main_layout->addLayout(center_layout, 1); - - // set up refresh timer - timer = new QTimer(this); - timer->callOnTimeout(this, &OffroadHome::refresh); - - setStyleSheet(R"( - * { - color: white; - } - OffroadHome { - background-color: black; - } - OffroadHome > QPushButton { - padding: 15px 30px; - border-radius: 5px; - font-size: 40px; - font-weight: 500; - } - OffroadHome > QLabel { - font-size: 55px; - } - )"); -} - -void OffroadHome::showEvent(QShowEvent *event) { - refresh(); - timer->start(10 * 1000); -} - -void OffroadHome::hideEvent(QHideEvent *event) { - timer->stop(); -} - -void OffroadHome::refresh() { - version->setText(getBrand() + " " + QString::fromStdString(params.get("UpdaterCurrentDescription"))); - - bool updateAvailable = update_widget->refresh(); - int alerts = alerts_widget->refresh(); - - // pop-up new notification - int idx = center_layout->currentIndex(); - if (!updateAvailable && !alerts) { - idx = 0; - } else if (updateAvailable && (!update_notif->isVisible() || (!alerts && idx == 2))) { - idx = 1; - } else if (alerts && (!alert_notif->isVisible() || (!updateAvailable && idx == 1))) { - idx = 2; - } - center_layout->setCurrentIndex(idx); - - update_notif->setVisible(updateAvailable); - alert_notif->setVisible(alerts); - if (alerts) { - alert_notif->setText(QString::number(alerts) + (alerts > 1 ? tr(" ALERTS") : tr(" ALERT"))); - } -} diff --git a/selfdrive/ui/qt/home.h b/selfdrive/ui/qt/home.h deleted file mode 100644 index a19b70ac3..000000000 --- a/selfdrive/ui/qt/home.h +++ /dev/null @@ -1,73 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -#include "common/params.h" -#include "selfdrive/ui/qt/offroad/driverview.h" -#include "selfdrive/ui/qt/body.h" -#include "selfdrive/ui/qt/onroad/onroad_home.h" -#include "selfdrive/ui/qt/sidebar.h" -#include "selfdrive/ui/qt/widgets/controls.h" -#include "selfdrive/ui/qt/widgets/offroad_alerts.h" -#include "selfdrive/ui/ui.h" - -class OffroadHome : public QFrame { - Q_OBJECT - -public: - explicit OffroadHome(QWidget* parent = 0); - -signals: - void openSettings(int index = 0, const QString ¶m = ""); - -private: - void showEvent(QShowEvent *event) override; - void hideEvent(QHideEvent *event) override; - void refresh(); - - Params params; - - QTimer* timer; - ElidedLabel* version; - QStackedLayout* center_layout; - UpdateAlert *update_widget; - OffroadAlert* alerts_widget; - QPushButton* alert_notif; - QPushButton* update_notif; -}; - -class HomeWindow : public QWidget { - Q_OBJECT - -public: - explicit HomeWindow(QWidget* parent = 0); - -signals: - void openSettings(int index = 0, const QString ¶m = ""); - void closeSettings(); - -public slots: - void offroadTransition(bool offroad); - void showDriverView(bool show); - void showSidebar(bool show); - -protected: - void mousePressEvent(QMouseEvent* e) override; - void mouseDoubleClickEvent(QMouseEvent* e) override; - -private: - Sidebar *sidebar; - OffroadHome *home; - OnroadWindow *onroad; - BodyWindow *body; - DriverViewWindow *driver_view; - QStackedLayout *slayout; - -private slots: - void updateState(const UIState &s); -}; diff --git a/selfdrive/ui/qt/network/networking.cc b/selfdrive/ui/qt/network/networking.cc deleted file mode 100644 index f19786a46..000000000 --- a/selfdrive/ui/qt/network/networking.cc +++ /dev/null @@ -1,413 +0,0 @@ -#include "selfdrive/ui/qt/network/networking.h" - -#include - -#include -#include -#include - -#include "selfdrive/ui/qt/qt_window.h" -#include "selfdrive/ui/qt/util.h" -#include "selfdrive/ui/qt/widgets/controls.h" -#include "selfdrive/ui/qt/widgets/scrollview.h" - -static const int ICON_WIDTH = 49; - -// Networking functions - -Networking::Networking(QWidget* parent, bool show_advanced) : QFrame(parent) { - main_layout = new QStackedLayout(this); - - wifi = new WifiManager(this); - connect(wifi, &WifiManager::refreshSignal, this, &Networking::refresh); - connect(wifi, &WifiManager::wrongPassword, this, &Networking::wrongPassword); - - wifiScreen = new QWidget(this); - QVBoxLayout* vlayout = new QVBoxLayout(wifiScreen); - vlayout->setContentsMargins(20, 20, 20, 20); - if (show_advanced) { - QPushButton* advancedSettings = new QPushButton(tr("Advanced")); - advancedSettings->setObjectName("advanced_btn"); - advancedSettings->setStyleSheet("margin-right: 30px;"); - advancedSettings->setFixedSize(400, 100); - connect(advancedSettings, &QPushButton::clicked, [=]() { main_layout->setCurrentWidget(an); }); - vlayout->addSpacing(10); - vlayout->addWidget(advancedSettings, 0, Qt::AlignRight); - vlayout->addSpacing(10); - } - - wifiWidget = new WifiUI(this, wifi); - wifiWidget->setObjectName("wifiWidget"); - connect(wifiWidget, &WifiUI::connectToNetwork, this, &Networking::connectToNetwork); - - ScrollView *wifiScroller = new ScrollView(wifiWidget, this); - wifiScroller->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); - vlayout->addWidget(wifiScroller, 1); - main_layout->addWidget(wifiScreen); - - an = new AdvancedNetworking(this, wifi); - connect(an, &AdvancedNetworking::backPress, [=]() { main_layout->setCurrentWidget(wifiScreen); }); - connect(an, &AdvancedNetworking::requestWifiScreen, [=]() { main_layout->setCurrentWidget(wifiScreen); }); - main_layout->addWidget(an); - - QPalette pal = palette(); - pal.setColor(QPalette::Window, QColor(0x29, 0x29, 0x29)); - setAutoFillBackground(true); - setPalette(pal); - - setStyleSheet(R"( - #wifiWidget > QPushButton, #back_btn, #advanced_btn { - font-size: 50px; - margin: 0px; - padding: 15px; - border-width: 0; - border-radius: 30px; - color: #dddddd; - background-color: #393939; - } - #back_btn:pressed, #advanced_btn:pressed { - background-color: #4a4a4a; - } - )"); - main_layout->setCurrentWidget(wifiScreen); -} - -void Networking::setPrimeType(PrimeState::Type type) { - an->setGsmVisible(type == PrimeState::PRIME_TYPE_NONE || type == PrimeState::PRIME_TYPE_LITE); - wifi->ipv4_forward = (type == PrimeState::PRIME_TYPE_NONE || type == PrimeState::PRIME_TYPE_LITE); -} - -void Networking::refresh() { - wifiWidget->refresh(); - an->refresh(); -} - -void Networking::connectToNetwork(const Network n) { - if (wifi->isKnownConnection(n.ssid)) { - wifi->activateWifiConnection(n.ssid); - } else if (n.security_type == SecurityType::OPEN) { - wifi->connect(n, false); - } else if (n.security_type == SecurityType::WPA) { - QString pass = InputDialog::getText(tr("Enter password"), this, tr("for \"%1\"").arg(QString::fromUtf8(n.ssid)), true, 8); - if (!pass.isEmpty()) { - wifi->connect(n, false, pass); - } - } -} - -void Networking::wrongPassword(const QString &ssid) { - if (wifi->seenNetworks.contains(ssid)) { - const Network &n = wifi->seenNetworks.value(ssid); - QString pass = InputDialog::getText(tr("Wrong password"), this, tr("for \"%1\"").arg(QString::fromUtf8(n.ssid)), true, 8); - if (!pass.isEmpty()) { - wifi->connect(n, false, pass); - } - } -} - -void Networking::showEvent(QShowEvent *event) { - wifi->start(); -} - -void Networking::hideEvent(QHideEvent *event) { - main_layout->setCurrentWidget(wifiScreen); - wifi->stop(); -} - -// AdvancedNetworking functions - -AdvancedNetworking::AdvancedNetworking(QWidget* parent, WifiManager* wifi): QWidget(parent), wifi(wifi) { - - QVBoxLayout* main_layout = new QVBoxLayout(this); - main_layout->setMargin(40); - main_layout->setSpacing(20); - - // Back button - QPushButton* back = new QPushButton(tr("Back")); - back->setObjectName("back_btn"); - back->setFixedSize(400, 100); - connect(back, &QPushButton::clicked, [=]() { emit backPress(); }); - main_layout->addWidget(back, 0, Qt::AlignLeft); - - ListWidget *list = new ListWidget(this); - // Enable tethering layout - tetheringToggle = new ToggleControl(tr("Enable Tethering"), "", "", wifi->isTetheringEnabled()); - list->addItem(tetheringToggle); - QObject::connect(tetheringToggle, &ToggleControl::toggleFlipped, this, &AdvancedNetworking::toggleTethering); - - // Change tethering password - ButtonControl *editPasswordButton = new ButtonControl(tr("Tethering Password"), tr("EDIT")); - connect(editPasswordButton, &ButtonControl::clicked, [=]() { - QString pass = InputDialog::getText(tr("Enter new tethering password"), this, "", true, 8, wifi->getTetheringPassword()); - if (!pass.isEmpty()) { - wifi->changeTetheringPassword(pass); - } - }); - list->addItem(editPasswordButton); - - // IP address - ipLabel = new LabelControl(tr("IP Address"), wifi->ipv4_address); - list->addItem(ipLabel); - - // Roaming toggle - const bool roamingEnabled = params.getBool("GsmRoaming"); - roamingToggle = new ToggleControl(tr("Enable Roaming"), "", "", roamingEnabled); - QObject::connect(roamingToggle, &ToggleControl::toggleFlipped, [=](bool state) { - params.putBool("GsmRoaming", state); - wifi->updateGsmSettings(state, QString::fromStdString(params.get("GsmApn")), params.getBool("GsmMetered")); - }); - list->addItem(roamingToggle); - - // APN settings - editApnButton = new ButtonControl(tr("APN Setting"), tr("EDIT")); - connect(editApnButton, &ButtonControl::clicked, [=]() { - const QString cur_apn = QString::fromStdString(params.get("GsmApn")); - QString apn = InputDialog::getText(tr("Enter APN"), this, tr("leave blank for automatic configuration"), false, -1, cur_apn).trimmed(); - - if (apn.isEmpty()) { - params.remove("GsmApn"); - } else { - params.put("GsmApn", apn.toStdString()); - } - wifi->updateGsmSettings(params.getBool("GsmRoaming"), apn, params.getBool("GsmMetered")); - }); - list->addItem(editApnButton); - - // Cellular metered toggle (prime lite or none) - const bool metered = params.getBool("GsmMetered"); - 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(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")); - connect(hiddenNetworkButton, &ButtonControl::clicked, [=]() { - QString ssid = InputDialog::getText(tr("Enter SSID"), this, "", false, 1); - if (!ssid.isEmpty()) { - QString pass = InputDialog::getText(tr("Enter password"), this, tr("for \"%1\"").arg(ssid), true, -1); - Network hidden_network; - hidden_network.ssid = ssid.toUtf8(); - if (!pass.isEmpty()) { - hidden_network.security_type = SecurityType::WPA; - wifi->connect(hidden_network, true, pass); - } else { - wifi->connect(hidden_network, true); - } - emit requestWifiScreen(); - } - }); - list->addItem(hiddenNetworkButton); - - // Set initial config - wifi->updateGsmSettings(roamingEnabled, QString::fromStdString(params.get("GsmApn")), metered); - - main_layout->addWidget(new ScrollView(list, this)); - main_layout->addStretch(1); -} - -void AdvancedNetworking::setGsmVisible(bool visible) { - roamingToggle->setVisible(visible); - editApnButton->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 - -WifiUI::WifiUI(QWidget *parent, WifiManager* wifi) : QWidget(parent), wifi(wifi) { - QVBoxLayout *main_layout = new QVBoxLayout(this); - main_layout->setContentsMargins(0, 0, 0, 0); - main_layout->setSpacing(0); - - // load imgs - for (const auto &s : {"low", "medium", "high", "full"}) { - QPixmap pix(ASSET_PATH + "/icons/wifi_strength_" + s + ".svg"); - strengths.push_back(pix.scaledToHeight(68, 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;"); - main_layout->addWidget(scanningLabel, 0, Qt::AlignCenter); - - wifi_list_widget = new ListWidget(this); - wifi_list_widget->setVisible(false); - main_layout->addWidget(wifi_list_widget); - - setStyleSheet(R"( - QScrollBar::handle:vertical { - min-height: 0px; - border-radius: 4px; - background-color: #8A8A8A; - } - #forgetBtn { - font-size: 32px; - font-weight: 600; - color: #292929; - background-color: #BDBDBD; - border-width: 1px solid #828282; - border-radius: 5px; - padding: 40px; - padding-bottom: 16px; - padding-top: 16px; - } - #forgetBtn:pressed { - background-color: #828282; - } - #connecting { - font-size: 32px; - font-weight: 600; - color: white; - border-radius: 0; - padding: 27px; - padding-left: 43px; - padding-right: 43px; - background-color: black; - } - #ssidLabel { - text-align: left; - border: none; - padding-top: 50px; - padding-bottom: 50px; - } - #ssidLabel:disabled { - color: #696969; - } - )"); -} - -void WifiUI::refresh() { - bool is_empty = wifi->seenNetworks.isEmpty(); - scanningLabel->setVisible(is_empty); - wifi_list_widget->setVisible(!is_empty); - if (is_empty) return; - - setUpdatesEnabled(false); - - const bool is_tethering_enabled = wifi->isTetheringEnabled(); - QList sortedNetworks = wifi->seenNetworks.values(); - std::sort(sortedNetworks.begin(), sortedNetworks.end(), compare_by_strength); - - int n = 0; - for (Network &network : sortedNetworks) { - QPixmap status_icon; - if (network.connected == ConnectedType::CONNECTED) { - status_icon = checkmark; - } else if (network.security_type == SecurityType::UNSUPPORTED) { - status_icon = circled_slash; - } else if (network.security_type == SecurityType::WPA) { - status_icon = lock; - } - bool show_forget_btn = wifi->isKnownConnection(network.ssid) && !is_tethering_enabled; - QPixmap strength = strengths[strengthLevel(network.strength)]; - - auto item = getItem(n++); - item->setItem(network, status_icon, show_forget_btn, strength); - item->setVisible(true); - } - for (; n < wifi_items.size(); ++n) wifi_items[n]->setVisible(false); - - setUpdatesEnabled(true); -} - -WifiItem *WifiUI::getItem(int n) { - auto item = n < wifi_items.size() ? wifi_items[n] : wifi_items.emplace_back(new WifiItem(tr("CONNECTING..."), tr("FORGET"))); - if (!item->parentWidget()) { - QObject::connect(item, &WifiItem::connectToNetwork, this, &WifiUI::connectToNetwork); - QObject::connect(item, &WifiItem::forgotNetwork, [this](const Network n) { - if (ConfirmationDialog::confirm(tr("Forget Wi-Fi Network \"%1\"?").arg(QString::fromUtf8(n.ssid)), tr("Forget"), this)) - wifi->forgetConnection(n.ssid); - }); - wifi_list_widget->addItem(item); - } - return item; -} - -// WifiItem - -WifiItem::WifiItem(const QString &connecting_text, const QString &forget_text, QWidget *parent) : QWidget(parent) { - QHBoxLayout *hlayout = new QHBoxLayout(this); - hlayout->setContentsMargins(44, 0, 73, 0); - hlayout->setSpacing(50); - - hlayout->addWidget(ssidLabel = new ElidedLabel()); - ssidLabel->setObjectName("ssidLabel"); - ssidLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); - hlayout->addWidget(connecting = new QPushButton(connecting_text), 0, Qt::AlignRight); - connecting->setObjectName("connecting"); - hlayout->addWidget(forgetBtn = new QPushButton(forget_text), 0, Qt::AlignRight); - forgetBtn->setObjectName("forgetBtn"); - hlayout->addWidget(iconLabel = new QLabel(), 0, Qt::AlignRight); - hlayout->addWidget(strengthLabel = new QLabel(), 0, Qt::AlignRight); - - iconLabel->setFixedWidth(ICON_WIDTH); - QObject::connect(forgetBtn, &QPushButton::clicked, [this]() { emit forgotNetwork(network); }); - QObject::connect(ssidLabel, &ElidedLabel::clicked, [this]() { - if (network.connected == ConnectedType::DISCONNECTED) emit connectToNetwork(network); - }); -} - -void WifiItem::setItem(const Network &n, const QPixmap &status_icon, bool show_forget_btn, const QPixmap &strength_icon) { - network = n; - - ssidLabel->setText(n.ssid); - ssidLabel->setEnabled(n.security_type != SecurityType::UNSUPPORTED); - ssidLabel->setFont(InterFont(55, network.connected == ConnectedType::DISCONNECTED ? QFont::Normal : QFont::Bold)); - - connecting->setVisible(n.connected == ConnectedType::CONNECTING); - forgetBtn->setVisible(show_forget_btn); - - iconLabel->setPixmap(status_icon); - strengthLabel->setPixmap(strength_icon); -} diff --git a/selfdrive/ui/qt/network/networking.h b/selfdrive/ui/qt/network/networking.h deleted file mode 100644 index 0bdf64e45..000000000 --- a/selfdrive/ui/qt/network/networking.h +++ /dev/null @@ -1,105 +0,0 @@ -#pragma once - -#include - -#include "selfdrive/ui/qt/network/wifi_manager.h" -#include "selfdrive/ui/qt/prime_state.h" -#include "selfdrive/ui/qt/widgets/input.h" -#include "selfdrive/ui/qt/widgets/ssh_keys.h" -#include "selfdrive/ui/qt/widgets/toggle.h" - -class WifiItem : public QWidget { - Q_OBJECT -public: - explicit WifiItem(const QString &connecting_text, const QString &forget_text, QWidget* parent = nullptr); - void setItem(const Network& n, const QPixmap &icon, bool show_forget_btn, const QPixmap &strength); - -signals: - // Cannot pass Network by reference. it may change after the signal is sent. - void connectToNetwork(const Network n); - void forgotNetwork(const Network n); - -protected: - ElidedLabel* ssidLabel; - QPushButton* connecting; - QPushButton* forgetBtn; - QLabel* iconLabel; - QLabel* strengthLabel; - Network network; -}; - -class WifiUI : public QWidget { - Q_OBJECT - -public: - explicit WifiUI(QWidget *parent = 0, WifiManager* wifi = 0); - -private: - WifiItem *getItem(int n); - - WifiManager *wifi = nullptr; - QLabel *scanningLabel = nullptr; - QPixmap lock; - QPixmap checkmark; - QPixmap circled_slash; - QVector strengths; - ListWidget *wifi_list_widget = nullptr; - std::vector wifi_items; - -signals: - void connectToNetwork(const Network n); - -public slots: - void refresh(); -}; - -class AdvancedNetworking : public QWidget { - Q_OBJECT -public: - explicit AdvancedNetworking(QWidget* parent = 0, WifiManager* wifi = 0); - void setGsmVisible(bool visible); - -private: - LabelControl* ipLabel; - ToggleControl* tetheringToggle; - ToggleControl* roamingToggle; - ButtonControl* editApnButton; - ButtonControl* hiddenNetworkButton; - ToggleControl* cellularMeteredToggle; - MultiButtonControl* wifiMeteredToggle; - WifiManager* wifi = nullptr; - Params params; - -signals: - void backPress(); - void requestWifiScreen(); - -public slots: - void toggleTethering(bool enabled); - void refresh(); -}; - -class Networking : public QFrame { - Q_OBJECT - -public: - explicit Networking(QWidget* parent = 0, bool show_advanced = true); - void setPrimeType(PrimeState::Type type); - WifiManager* wifi = nullptr; - -private: - QStackedLayout* main_layout = nullptr; - QWidget* wifiScreen = nullptr; - AdvancedNetworking* an = nullptr; - WifiUI* wifiWidget; - - void showEvent(QShowEvent* event) override; - void hideEvent(QHideEvent* event) override; - -public slots: - void refresh(); - -private slots: - void connectToNetwork(const Network n); - void wrongPassword(const QString &ssid); -}; diff --git a/selfdrive/ui/qt/network/networkmanager.h b/selfdrive/ui/qt/network/networkmanager.h deleted file mode 100644 index 8bdeaf3bb..000000000 --- a/selfdrive/ui/qt/network/networkmanager.h +++ /dev/null @@ -1,48 +0,0 @@ -#pragma once - -/** - * We are using a NetworkManager DBUS API : https://developer.gnome.org/NetworkManager/1.26/spec.html - * */ - -// https://developer.gnome.org/NetworkManager/1.26/nm-dbus-types.html#NM80211ApFlags -const int NM_802_11_AP_FLAGS_NONE = 0x00000000; -const int NM_802_11_AP_FLAGS_PRIVACY = 0x00000001; -const int NM_802_11_AP_FLAGS_WPS = 0x00000002; - -// https://developer.gnome.org/NetworkManager/1.26/nm-dbus-types.html#NM80211ApSecurityFlags -const int NM_802_11_AP_SEC_PAIR_WEP40 = 0x00000001; -const int NM_802_11_AP_SEC_PAIR_WEP104 = 0x00000002; -const int NM_802_11_AP_SEC_GROUP_WEP40 = 0x00000010; -const int NM_802_11_AP_SEC_GROUP_WEP104 = 0x00000020; -const int NM_802_11_AP_SEC_KEY_MGMT_PSK = 0x00000100; -const int NM_802_11_AP_SEC_KEY_MGMT_802_1X = 0x00000200; - -const QString NM_DBUS_PATH = "/org/freedesktop/NetworkManager"; -const QString NM_DBUS_PATH_SETTINGS = "/org/freedesktop/NetworkManager/Settings"; - -const QString NM_DBUS_INTERFACE = "org.freedesktop.NetworkManager"; -const QString NM_DBUS_INTERFACE_PROPERTIES = "org.freedesktop.DBus.Properties"; -const QString NM_DBUS_INTERFACE_SETTINGS = "org.freedesktop.NetworkManager.Settings"; -const QString NM_DBUS_INTERFACE_SETTINGS_CONNECTION = "org.freedesktop.NetworkManager.Settings.Connection"; -const QString NM_DBUS_INTERFACE_DEVICE = "org.freedesktop.NetworkManager.Device"; -const QString NM_DBUS_INTERFACE_DEVICE_WIRELESS = "org.freedesktop.NetworkManager.Device.Wireless"; -const QString NM_DBUS_INTERFACE_ACCESS_POINT = "org.freedesktop.NetworkManager.AccessPoint"; -const QString NM_DBUS_INTERFACE_ACTIVE_CONNECTION = "org.freedesktop.NetworkManager.Connection.Active"; -const QString NM_DBUS_INTERFACE_IP4_CONFIG = "org.freedesktop.NetworkManager.IP4Config"; - -const QString NM_DBUS_SERVICE = "org.freedesktop.NetworkManager"; - -const int NM_DEVICE_STATE_UNKNOWN = 0; -const int NM_DEVICE_STATE_ACTIVATED = 100; -const int NM_DEVICE_STATE_NEED_AUTH = 60; -const int NM_DEVICE_TYPE_WIFI = 2; -const int NM_DEVICE_TYPE_MODEM = 8; -const int NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT = 8; -const int DBUS_TIMEOUT = 100; - -// https://developer-old.gnome.org/NetworkManager/1.26/nm-dbus-types.html#NMMetered -const int NM_METERED_UNKNOWN = 0; -const int NM_METERED_YES = 1; -const int NM_METERED_NO = 2; -const int NM_METERED_GUESS_YES = 3; -const int NM_METERED_GUESS_NO = 4; diff --git a/selfdrive/ui/qt/network/wifi_manager.cc b/selfdrive/ui/qt/network/wifi_manager.cc deleted file mode 100644 index d4ad8974b..000000000 --- a/selfdrive/ui/qt/network/wifi_manager.cc +++ /dev/null @@ -1,539 +0,0 @@ -#include "selfdrive/ui/qt/network/wifi_manager.h" - -#include - -#include "common/swaglog.h" -#include "selfdrive/ui/qt/util.h" - -bool compare_by_strength(const Network &a, const Network &b) { - return std::tuple(a.connected, strengthLevel(a.strength), b.ssid) > - std::tuple(b.connected, strengthLevel(b.strength), a.ssid); -} - -template -T call(const QString &path, const QString &interface, const QString &method, Args &&...args) { - QDBusInterface nm(NM_DBUS_SERVICE, path, interface, QDBusConnection::systemBus()); - nm.setTimeout(DBUS_TIMEOUT); - - QDBusMessage response = nm.call(method, std::forward(args)...); - if (response.type() == QDBusMessage::ErrorMessage) { - qCritical() << "DBus call error:" << response.errorMessage(); - return T(); - } - - if constexpr (std::is_same_v) { - return response; - } else if (response.arguments().count() >= 1) { - QVariant vFirst = response.arguments().at(0).value().variant(); - if (vFirst.canConvert()) { - return vFirst.value(); - } - QDebug critical = qCritical(); - critical << "Variant unpacking failure :" << method << ','; - (critical << ... << args); - } - return T(); -} - -template -QDBusPendingCall asyncCall(const QString &path, const QString &interface, const QString &method, Args &&...args) { - QDBusInterface nm = QDBusInterface(NM_DBUS_SERVICE, path, interface, QDBusConnection::systemBus()); - return nm.asyncCall(method, args...); -} - -bool emptyPath(const QString &path) { - return path == "" || path == "/"; -} - -WifiManager::WifiManager(QObject *parent) : QObject(parent) { - qDBusRegisterMetaType(); - qDBusRegisterMetaType(); - - // Set tethering ssid as "weedle" + first 4 characters of a dongle id - tethering_ssid = "weedle"; - if (auto dongle_id = getDongleId()) { - tethering_ssid += "-" + dongle_id->left(4); - } - - adapter = getAdapter(); - if (!adapter.isEmpty()) { - setup(); - } else { - QDBusConnection::systemBus().connect(NM_DBUS_SERVICE, NM_DBUS_PATH, NM_DBUS_INTERFACE, "DeviceAdded", this, SLOT(deviceAdded(QDBusObjectPath))); - } - - timer.callOnTimeout(this, &WifiManager::requestScan); - - initConnections(); -} - -void WifiManager::setup() { - auto bus = QDBusConnection::systemBus(); - bus.connect(NM_DBUS_SERVICE, adapter, NM_DBUS_INTERFACE_DEVICE, "StateChanged", this, SLOT(stateChange(unsigned int, unsigned int, unsigned int))); - bus.connect(NM_DBUS_SERVICE, adapter, NM_DBUS_INTERFACE_PROPERTIES, "PropertiesChanged", this, SLOT(propertyChange(QString, QVariantMap, QStringList))); - - bus.connect(NM_DBUS_SERVICE, NM_DBUS_PATH_SETTINGS, NM_DBUS_INTERFACE_SETTINGS, "ConnectionRemoved", this, SLOT(connectionRemoved(QDBusObjectPath))); - bus.connect(NM_DBUS_SERVICE, NM_DBUS_PATH_SETTINGS, NM_DBUS_INTERFACE_SETTINGS, "NewConnection", this, SLOT(newConnection(QDBusObjectPath))); - - raw_adapter_state = call(adapter, NM_DBUS_INTERFACE_PROPERTIES, "Get", NM_DBUS_INTERFACE_DEVICE, "State"); - activeAp = call(adapter, NM_DBUS_INTERFACE_PROPERTIES, "Get", NM_DBUS_INTERFACE_DEVICE_WIRELESS, "ActiveAccessPoint").path(); - - requestScan(); -} - -void WifiManager::start() { - timer.start(5000); - refreshNetworks(); -} - -void WifiManager::stop() { - timer.stop(); -} - -void WifiManager::refreshNetworks() { - if (adapter.isEmpty() || !timer.isActive()) return; - - QDBusPendingCall pending_call = asyncCall(adapter, NM_DBUS_INTERFACE_DEVICE_WIRELESS, "GetAllAccessPoints"); - QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pending_call); - QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, &WifiManager::refreshFinished); -} - -void WifiManager::refreshFinished(QDBusPendingCallWatcher *watcher) { - ipv4_address = getIp4Address(); - seenNetworks.clear(); - - const QDBusReply> watcher_reply = *watcher; - if (!watcher_reply.isValid()) { - qCritical() << "Failed to refresh"; - watcher->deleteLater(); - return; - } - - for (const QDBusObjectPath &path : watcher_reply.value()) { - QDBusReply reply = call(path.path(), NM_DBUS_INTERFACE_PROPERTIES, "GetAll", NM_DBUS_INTERFACE_ACCESS_POINT); - if (!reply.isValid()) { - qCritical() << "Failed to retrieve properties for path:" << path.path(); - continue; - } - - auto properties = reply.value(); - const QByteArray ssid = properties["Ssid"].toByteArray(); - if (ssid.isEmpty()) continue; - - // May be multiple access points for each SSID. - // Use first for ssid and security type, then update connected status and strength using all - if (!seenNetworks.contains(ssid)) { - seenNetworks[ssid] = {ssid, 0U, ConnectedType::DISCONNECTED, getSecurityType(properties)}; - } - - if (path.path() == activeAp) { - seenNetworks[ssid].connected = (ssid == connecting_to_network) ? ConnectedType::CONNECTING : ConnectedType::CONNECTED; - } - - uint32_t strength = properties["Strength"].toUInt(); - if (seenNetworks[ssid].strength < strength) { - seenNetworks[ssid].strength = strength; - } - } - - emit refreshSignal(); - watcher->deleteLater(); -} - -QString WifiManager::getIp4Address() { - if (raw_adapter_state != NM_DEVICE_STATE_ACTIVATED) return ""; - - for (const auto &p : getActiveConnections()) { - QString type = call(p.path(), NM_DBUS_INTERFACE_PROPERTIES, "Get", NM_DBUS_INTERFACE_ACTIVE_CONNECTION, "Type"); - if (type == "802-11-wireless") { - auto ip4config = call(p.path(), NM_DBUS_INTERFACE_PROPERTIES, "Get", NM_DBUS_INTERFACE_ACTIVE_CONNECTION, "Ip4Config"); - const auto &arr = call(ip4config.path(), NM_DBUS_INTERFACE_PROPERTIES, "Get", NM_DBUS_INTERFACE_IP4_CONFIG, "AddressData"); - QVariantMap path; - arr.beginArray(); - while (!arr.atEnd()) { - arr >> path; - arr.endArray(); - return path.value("address").value(); - } - arr.endArray(); - } - } - return ""; -} - -SecurityType WifiManager::getSecurityType(const QVariantMap &properties) { - int sflag = properties["Flags"].toUInt(); - int wpaflag = properties["WpaFlags"].toUInt(); - int rsnflag = properties["RsnFlags"].toUInt(); - int wpa_props = wpaflag | rsnflag; - - // obtained by looking at flags of networks in the office as reported by an Android phone - const int supports_wpa = NM_802_11_AP_SEC_PAIR_WEP40 | NM_802_11_AP_SEC_PAIR_WEP104 | NM_802_11_AP_SEC_GROUP_WEP40 | NM_802_11_AP_SEC_GROUP_WEP104 | NM_802_11_AP_SEC_KEY_MGMT_PSK; - - if ((sflag == NM_802_11_AP_FLAGS_NONE) || ((sflag & NM_802_11_AP_FLAGS_WPS) && !(wpa_props & supports_wpa))) { - return SecurityType::OPEN; - } else if ((sflag & NM_802_11_AP_FLAGS_PRIVACY) && (wpa_props & supports_wpa) && !(wpa_props & NM_802_11_AP_SEC_KEY_MGMT_802_1X)) { - return SecurityType::WPA; - } else { - LOGW("Unsupported network! sflag: %d, wpaflag: %d, rsnflag: %d", sflag, wpaflag, rsnflag); - return SecurityType::UNSUPPORTED; - } -} - -void WifiManager::connect(const Network &n, const bool is_hidden, const QString &password, const QString &username) { - setCurrentConnecting(n.ssid); - forgetConnection(n.ssid); // Clear all connections that may already exist to the network we are connecting - Connection connection; - connection["connection"]["type"] = "802-11-wireless"; - connection["connection"]["uuid"] = QUuid::createUuid().toString().remove('{').remove('}'); - connection["connection"]["id"] = "openpilot connection " + QString::fromStdString(n.ssid.toStdString()); - connection["connection"]["autoconnect-retries"] = 0; - - connection["802-11-wireless"]["ssid"] = n.ssid; - connection["802-11-wireless"]["hidden"] = is_hidden; - connection["802-11-wireless"]["mode"] = "infrastructure"; - - if (n.security_type == SecurityType::WPA) { - connection["802-11-wireless-security"]["key-mgmt"] = "wpa-psk"; - connection["802-11-wireless-security"]["auth-alg"] = "open"; - connection["802-11-wireless-security"]["psk"] = password; - } - - connection["ipv4"]["method"] = "auto"; - connection["ipv4"]["dns-priority"] = 600; - connection["ipv6"]["method"] = "ignore"; - - asyncCall(NM_DBUS_PATH_SETTINGS, NM_DBUS_INTERFACE_SETTINGS, "AddConnection", QVariant::fromValue(connection)); -} - -void WifiManager::deactivateConnectionBySsid(const QString &ssid) { - for (QDBusObjectPath active_connection : getActiveConnections()) { - auto pth = call(active_connection.path(), NM_DBUS_INTERFACE_PROPERTIES, "Get", NM_DBUS_INTERFACE_ACTIVE_CONNECTION, "SpecificObject"); - if (!emptyPath(pth.path())) { - QString Ssid = get_property(pth.path(), "Ssid"); - if (Ssid == ssid) { - deactivateConnection(active_connection); - return; - } - } - } -} - -void WifiManager::deactivateConnection(const QDBusObjectPath &path) { - asyncCall(NM_DBUS_PATH, NM_DBUS_INTERFACE, "DeactivateConnection", QVariant::fromValue(path)); -} - -QVector WifiManager::getActiveConnections() { - auto result = call(NM_DBUS_PATH, NM_DBUS_INTERFACE_PROPERTIES, "Get", NM_DBUS_INTERFACE, "ActiveConnections"); - return qdbus_cast>(result); -} - -bool WifiManager::isKnownConnection(const QString &ssid) { - return !getConnectionPath(ssid).path().isEmpty(); -} - -void WifiManager::forgetConnection(const QString &ssid) { - const QDBusObjectPath &path = getConnectionPath(ssid); - if (!path.path().isEmpty()) { - call(path.path(), NM_DBUS_INTERFACE_SETTINGS_CONNECTION, "Delete"); - } -} - -void WifiManager::setCurrentConnecting(const QString &ssid) { - connecting_to_network = ssid; - for (auto &network : seenNetworks) { - network.connected = (network.ssid == ssid) ? ConnectedType::CONNECTING : ConnectedType::DISCONNECTED; - } - emit refreshSignal(); -} - -uint WifiManager::getAdapterType(const QDBusObjectPath &path) { - return call(path.path(), NM_DBUS_INTERFACE_PROPERTIES, "Get", NM_DBUS_INTERFACE_DEVICE, "DeviceType"); -} - -void WifiManager::requestScan() { - if (!adapter.isEmpty()) { - asyncCall(adapter, NM_DBUS_INTERFACE_DEVICE_WIRELESS, "RequestScan", QVariantMap()); - } -} - -QByteArray WifiManager::get_property(const QString &network_path , const QString &property) { - return call(network_path, NM_DBUS_INTERFACE_PROPERTIES, "Get", NM_DBUS_INTERFACE_ACCESS_POINT, property); -} - -QString WifiManager::getAdapter(const uint adapter_type) { - QDBusReply> response = call(NM_DBUS_PATH, NM_DBUS_INTERFACE, "GetDevices"); - for (const QDBusObjectPath &path : response.value()) { - if (getAdapterType(path) == adapter_type) { - return path.path(); - } - } - return ""; -} - -void WifiManager::stateChange(unsigned int new_state, unsigned int previous_state, unsigned int change_reason) { - raw_adapter_state = new_state; - if (new_state == NM_DEVICE_STATE_NEED_AUTH && change_reason == NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT && !connecting_to_network.isEmpty()) { - forgetConnection(connecting_to_network); - emit wrongPassword(connecting_to_network); - } else if (new_state == NM_DEVICE_STATE_ACTIVATED) { - connecting_to_network = ""; - refreshNetworks(); - } -} - -// https://developer.gnome.org/NetworkManager/stable/gdbus-org.freedesktop.NetworkManager.Device.Wireless.html -void WifiManager::propertyChange(const QString &interface, const QVariantMap &props, const QStringList &invalidated_props) { - if (interface == NM_DBUS_INTERFACE_DEVICE_WIRELESS && props.contains("LastScan")) { - refreshNetworks(); - } else if (interface == NM_DBUS_INTERFACE_DEVICE_WIRELESS && props.contains("ActiveAccessPoint")) { - activeAp = props.value("ActiveAccessPoint").value().path(); - } -} - -void WifiManager::deviceAdded(const QDBusObjectPath &path) { - if (getAdapterType(path) == NM_DEVICE_TYPE_WIFI && emptyPath(adapter)) { - adapter = path.path(); - setup(); - } -} - -void WifiManager::connectionRemoved(const QDBusObjectPath &path) { - knownConnections.remove(path); -} - -void WifiManager::newConnection(const QDBusObjectPath &path) { - Connection settings = getConnectionSettings(path); - if (settings.value("connection").value("type") == "802-11-wireless") { - knownConnections[path] = settings.value("802-11-wireless").value("ssid").toString(); - if (knownConnections[path] != tethering_ssid) { - activateWifiConnection(knownConnections[path]); - } - } -} - -QDBusObjectPath WifiManager::getConnectionPath(const QString &ssid) { - return knownConnections.key(ssid); -} - -Connection WifiManager::getConnectionSettings(const QDBusObjectPath &path) { - return QDBusReply(call(path.path(), NM_DBUS_INTERFACE_SETTINGS_CONNECTION, "GetSettings")).value(); -} - -void WifiManager::initConnections() { - const QDBusReply> response = call(NM_DBUS_PATH_SETTINGS, NM_DBUS_INTERFACE_SETTINGS, "ListConnections"); - for (const QDBusObjectPath &path : response.value()) { - const Connection settings = getConnectionSettings(path); - if (settings.value("connection").value("type") == "802-11-wireless") { - knownConnections[path] = settings.value("802-11-wireless").value("ssid").toString(); - } else if (settings.value("connection").value("id") == "lte") { - lteConnectionPath = path; - } - } - - if (!isKnownConnection(tethering_ssid)) { - addTetheringConnection(); - } -} - -std::optional WifiManager::activateWifiConnection(const QString &ssid) { - const QDBusObjectPath &path = getConnectionPath(ssid); - if (!path.path().isEmpty()) { - setCurrentConnecting(ssid); - return asyncCall(NM_DBUS_PATH, NM_DBUS_INTERFACE, "ActivateConnection", QVariant::fromValue(path), QVariant::fromValue(QDBusObjectPath(adapter)), QVariant::fromValue(QDBusObjectPath("/"))); - } - return std::nullopt; -} - -void WifiManager::activateModemConnection(const QDBusObjectPath &path) { - QString modem = getAdapter(NM_DEVICE_TYPE_MODEM); - if (!path.path().isEmpty() && !modem.isEmpty()) { - asyncCall(NM_DBUS_PATH, NM_DBUS_INTERFACE, "ActivateConnection", QVariant::fromValue(path), QVariant::fromValue(QDBusObjectPath(modem)), QVariant::fromValue(QDBusObjectPath("/"))); - } -} - -// 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"); - - if (primary_type == "802-3-ethernet") { - return NetworkType::ETHERNET; - } else if (primary_type == "802-11-wireless" && !isTetheringEnabled()) { - return NetworkType::WIFI; - } else { - for (const QDBusObjectPath &conn : getActiveConnections()) { - auto type = call(conn.path(), NM_DBUS_INTERFACE_PROPERTIES, "Get", NM_DBUS_INTERFACE_ACTIVE_CONNECTION, "Type"); - if (type == "gsm") { - return NetworkType::CELL; - } - } - } - 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; - bool auto_config = apn.isEmpty(); - Connection settings = getConnectionSettings(lteConnectionPath); - if (settings.value("gsm").value("auto-config").toBool() != auto_config) { - qWarning() << "Changing gsm.auto-config to" << auto_config; - settings["gsm"]["auto-config"] = auto_config; - changes = true; - } - - if (settings.value("gsm").value("apn").toString() != apn) { - qWarning() << "Changing gsm.apn to" << apn; - settings["gsm"]["apn"] = apn; - changes = true; - } - - if (settings.value("gsm").value("home-only").toBool() == roaming) { - qWarning() << "Changing gsm.home-only to" << !roaming; - settings["gsm"]["home-only"] = !roaming; - changes = true; - } - - int meteredInt = metered ? NM_METERED_UNKNOWN : NM_METERED_NO; - if (settings.value("connection").value("metered").toInt() != meteredInt) { - qWarning() << "Changing connection.metered to" << meteredInt; - settings["connection"]["metered"] = meteredInt; - changes = true; - } - - if (changes) { - QDBusPendingCall pending_call = asyncCall(lteConnectionPath.path(), NM_DBUS_INTERFACE_SETTINGS_CONNECTION, "UpdateUnsaved", QVariant::fromValue(settings)); // update is temporary - QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pending_call); - QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, watcher]() { - deactivateConnection(lteConnectionPath); - activateModemConnection(lteConnectionPath); - watcher->deleteLater(); - }); - } - } -} - -// Functions for tethering -void WifiManager::addTetheringConnection() { - Connection connection; - connection["connection"]["id"] = "Hotspot"; - connection["connection"]["uuid"] = QUuid::createUuid().toString().remove('{').remove('}'); - connection["connection"]["type"] = "802-11-wireless"; - connection["connection"]["interface-name"] = "wlan0"; - connection["connection"]["autoconnect"] = false; - - connection["802-11-wireless"]["band"] = "bg"; - connection["802-11-wireless"]["mode"] = "ap"; - connection["802-11-wireless"]["ssid"] = tethering_ssid.toUtf8(); - - connection["802-11-wireless-security"]["group"] = QStringList("ccmp"); - connection["802-11-wireless-security"]["key-mgmt"] = "wpa-psk"; - connection["802-11-wireless-security"]["pairwise"] = QStringList("ccmp"); - connection["802-11-wireless-security"]["proto"] = QStringList("rsn"); - connection["802-11-wireless-security"]["psk"] = defaultTetheringPassword; - - connection["ipv4"]["method"] = "shared"; - QVariantMap address; - address["address"] = "192.168.43.1"; - address["prefix"] = 24u; - connection["ipv4"]["address-data"] = QVariant::fromValue(IpConfig() << address); - connection["ipv4"]["gateway"] = "192.168.43.1"; - connection["ipv4"]["never-default"] = true; - connection["ipv6"]["method"] = "ignore"; - - asyncCall(NM_DBUS_PATH_SETTINGS, NM_DBUS_INTERFACE_SETTINGS, "AddConnection", QVariant::fromValue(connection)); -} - -void WifiManager::tetheringActivated(QDBusPendingCallWatcher *call) { - if (!ipv4_forward) { - QTimer::singleShot(5000, this, [=] { - qWarning() << "net.ipv4.ip_forward = 0"; - std::system("sudo sysctl net.ipv4.ip_forward=0"); - }); - } - call->deleteLater(); - tethering_on = true; -} - -void WifiManager::setTetheringEnabled(bool enabled) { - if (enabled) { - auto pending_call = activateWifiConnection(tethering_ssid); - - if (pending_call) { - QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(*pending_call); - QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, &WifiManager::tetheringActivated); - } - - } else { - deactivateConnectionBySsid(tethering_ssid); - tethering_on = false; - } -} - -bool WifiManager::isTetheringEnabled() { - if (!emptyPath(activeAp)) { - return get_property(activeAp, "Ssid") == tethering_ssid; - } - return false; -} - -QString WifiManager::getTetheringPassword() { - const QDBusObjectPath &path = getConnectionPath(tethering_ssid); - if (!path.path().isEmpty()) { - QDBusReply> response = call(path.path(), NM_DBUS_INTERFACE_SETTINGS_CONNECTION, "GetSecrets", "802-11-wireless-security"); - return response.value().value("802-11-wireless-security").value("psk").toString(); - } - return ""; -} - -void WifiManager::changeTetheringPassword(const QString &newPassword) { - const QDBusObjectPath &path = getConnectionPath(tethering_ssid); - if (!path.path().isEmpty()) { - Connection settings = getConnectionSettings(path); - settings["802-11-wireless-security"]["psk"] = newPassword; - call(path.path(), NM_DBUS_INTERFACE_SETTINGS_CONNECTION, "Update", QVariant::fromValue(settings)); - if (isTetheringEnabled()) { - activateWifiConnection(tethering_ssid); - } - } -} diff --git a/selfdrive/ui/qt/network/wifi_manager.h b/selfdrive/ui/qt/network/wifi_manager.h deleted file mode 100644 index cab932a38..000000000 --- a/selfdrive/ui/qt/network/wifi_manager.h +++ /dev/null @@ -1,111 +0,0 @@ -#pragma once - -#include -#include -#include - -#include "selfdrive/ui/qt/network/networkmanager.h" - -enum class SecurityType { - OPEN, - WPA, - UNSUPPORTED -}; -enum class ConnectedType { - DISCONNECTED, - CONNECTING, - CONNECTED -}; -enum class NetworkType { - NONE, - WIFI, - CELL, - ETHERNET -}; -enum class MeteredType { - UNKNOWN, - YES, - NO -}; - -typedef QMap Connection; -typedef QVector IpConfig; - -struct Network { - QByteArray ssid; - unsigned int strength; - ConnectedType connected; - SecurityType security_type; -}; -bool compare_by_strength(const Network &a, const Network &b); -inline int strengthLevel(unsigned int strength) { return std::clamp((int)round(strength / 33.), 0, 3); } - -class WifiManager : public QObject { - Q_OBJECT - -public: - QMap seenNetworks; - QMap knownConnections; - QString ipv4_address; - bool tethering_on = false; - bool ipv4_forward = false; - - explicit WifiManager(QObject* parent); - void start(); - void stop(); - void requestScan(); - void forgetConnection(const QString &ssid); - 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 = {}); - - // Tethering functions - void setTetheringEnabled(bool enabled); - bool isTetheringEnabled(); - void changeTetheringPassword(const QString &newPassword); - QString getTetheringPassword(); - -private: - QString adapter; // Path to network manager wifi-device - QTimer timer; - unsigned int raw_adapter_state = NM_DEVICE_STATE_UNKNOWN; // Connection status https://developer.gnome.org/NetworkManager/1.26/nm-dbus-types.html#NMDeviceState - QString connecting_to_network; - QString tethering_ssid; - const QString defaultTetheringPassword = "swagswagcomma"; - QString activeAp; - QDBusObjectPath lteConnectionPath; - - QString getAdapter(const uint = NM_DEVICE_TYPE_WIFI); - uint getAdapterType(const QDBusObjectPath &path); - QString getIp4Address(); - void deactivateConnectionBySsid(const QString &ssid); - void deactivateConnection(const QDBusObjectPath &path); - QVector getActiveConnections(); - QByteArray get_property(const QString &network_path, const QString &property); - SecurityType getSecurityType(const QVariantMap &properties); - QDBusObjectPath getConnectionPath(const QString &ssid); - Connection getConnectionSettings(const QDBusObjectPath &path); - void initConnections(); - void setup(); - void refreshNetworks(); - void activateModemConnection(const QDBusObjectPath &path); - void addTetheringConnection(); - void setCurrentConnecting(const QString &ssid); - -signals: - void wrongPassword(const QString &ssid); - void refreshSignal(); - -private slots: - void stateChange(unsigned int new_state, unsigned int previous_state, unsigned int change_reason); - void propertyChange(const QString &interface, const QVariantMap &props, const QStringList &invalidated_props); - void deviceAdded(const QDBusObjectPath &path); - void connectionRemoved(const QDBusObjectPath &path); - void newConnection(const QDBusObjectPath &path); - void refreshFinished(QDBusPendingCallWatcher *call); - void tetheringActivated(QDBusPendingCallWatcher *call); -}; diff --git a/selfdrive/ui/qt/offroad/developer_panel.cc b/selfdrive/ui/qt/offroad/developer_panel.cc deleted file mode 100644 index a095228da..000000000 --- a/selfdrive/ui/qt/offroad/developer_panel.cc +++ /dev/null @@ -1,95 +0,0 @@ -#include "selfdrive/ui/qt/offroad/developer_panel.h" -#include "selfdrive/ui/qt/widgets/ssh_keys.h" -#include "selfdrive/ui/qt/widgets/controls.h" - -DeveloperPanel::DeveloperPanel(SettingsWindow *parent) : ListWidget(parent) { - adbToggle = new ParamControl("AdbEnabled", tr("Enable ADB"), - tr("ADB (Android Debug Bridge) allows connecting to your device over USB or over the network. See https://docs.comma.ai/how-to/connect-to-comma for more info."), ""); - addItem(adbToggle); - - // SSH keys - addItem(new SshToggle()); - addItem(new SshControl()); - - joystickToggle = new ParamControl("JoystickDebugMode", tr("Joystick Debug Mode"), "", ""); - QObject::connect(joystickToggle, &ParamControl::toggleFlipped, [=](bool state) { - params.putBool("LongitudinalManeuverMode", false); - longManeuverToggle->refresh(); - }); - addItem(joystickToggle); - - longManeuverToggle = new ParamControl("LongitudinalManeuverMode", tr("Longitudinal Maneuver Mode"), "", ""); - QObject::connect(longManeuverToggle, &ParamControl::toggleFlipped, [=](bool state) { - params.putBool("JoystickDebugMode", false); - joystickToggle->refresh(); - }); - addItem(longManeuverToggle); - - experimentalLongitudinalToggle = new ParamControl( - "AlphaLongitudinalEnabled", - tr("openpilot Longitudinal Control (Alpha)"), - QString("%1

%2") - .arg(tr("WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB).")) - .arg(tr("On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. " - "Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha.")), - "" - ); - experimentalLongitudinalToggle->setConfirmation(true, false); - QObject::connect(experimentalLongitudinalToggle, &ParamControl::toggleFlipped, [=]() { - updateToggles(offroad); - }); - addItem(experimentalLongitudinalToggle); - - // Joystick and longitudinal maneuvers should be hidden on release branches - is_release = params.getBool("IsReleaseBranch"); - - // Toggles should be not available to change in onroad state - QObject::connect(uiState(), &UIState::offroadTransition, this, &DeveloperPanel::updateToggles); -} - -void DeveloperPanel::updateToggles(bool _offroad) { - for (auto btn : findChildren()) { - btn->setVisible(!is_release); - - /* - * experimentalLongitudinalToggle should be toggelable when: - * - visible, and - * - during onroad & offroad states - */ - if (btn != experimentalLongitudinalToggle) { - btn->setEnabled(_offroad); - } - } - - // longManeuverToggle and experimentalLongitudinalToggle should not be toggleable if the car does not have longitudinal control - auto cp_bytes = params.get("CarParamsPersistent"); - if (!cp_bytes.empty()) { - AlignedBuffer aligned_buf; - capnp::FlatArrayMessageReader cmsg(aligned_buf.align(cp_bytes.data(), cp_bytes.size())); - cereal::CarParams::Reader CP = cmsg.getRoot(); - - if (!CP.getAlphaLongitudinalAvailable() || is_release) { - params.remove("AlphaLongitudinalEnabled"); - experimentalLongitudinalToggle->setEnabled(false); - } - - /* - * experimentalLongitudinalToggle should be visible when: - * - is not a release branch, and - * - the car supports experimental longitudinal control (alpha) - */ - experimentalLongitudinalToggle->setVisible(CP.getAlphaLongitudinalAvailable() && !is_release); - - longManeuverToggle->setEnabled(hasLongitudinalControl(CP) && _offroad); - } else { - longManeuverToggle->setEnabled(false); - experimentalLongitudinalToggle->setVisible(false); - } - experimentalLongitudinalToggle->refresh(); - - offroad = _offroad; -} - -void DeveloperPanel::showEvent(QShowEvent *event) { - updateToggles(offroad); -} diff --git a/selfdrive/ui/qt/offroad/developer_panel.h b/selfdrive/ui/qt/offroad/developer_panel.h deleted file mode 100644 index c73421b18..000000000 --- a/selfdrive/ui/qt/offroad/developer_panel.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include "selfdrive/ui/qt/offroad/settings.h" - -class DeveloperPanel : public ListWidget { - Q_OBJECT -public: - explicit DeveloperPanel(SettingsWindow *parent); - void showEvent(QShowEvent *event) override; - -private: - Params params; - ParamControl* adbToggle; - ParamControl* joystickToggle; - ParamControl* longManeuverToggle; - ParamControl* experimentalLongitudinalToggle; - bool is_release; - bool offroad = false; - -private slots: - void updateToggles(bool _offroad); -}; diff --git a/selfdrive/ui/qt/offroad/driverview.cc b/selfdrive/ui/qt/offroad/driverview.cc deleted file mode 100644 index 9010227f1..000000000 --- a/selfdrive/ui/qt/offroad/driverview.cc +++ /dev/null @@ -1,82 +0,0 @@ -#include "selfdrive/ui/qt/offroad/driverview.h" - -#include -#include - -#include "selfdrive/ui/qt/util.h" - -DriverViewWindow::DriverViewWindow(QWidget* parent) : CameraWidget("camerad", VISION_STREAM_DRIVER, parent) { - QObject::connect(this, &CameraWidget::clicked, this, &DriverViewWindow::done); - QObject::connect(device(), &Device::interactiveTimeout, this, [this]() { - if (isVisible()) { - emit done(); - } - }); -} - -void DriverViewWindow::showEvent(QShowEvent* event) { - params.putBool("IsDriverViewEnabled", true); - device()->resetInteractiveTimeout(60); - CameraWidget::showEvent(event); -} - -void DriverViewWindow::hideEvent(QHideEvent* event) { - params.putBool("IsDriverViewEnabled", false); - stopVipcThread(); - CameraWidget::hideEvent(event); -} - -void DriverViewWindow::paintGL() { - CameraWidget::paintGL(); - - std::lock_guard lk(frame_lock); - QPainter p(this); - // startup msg - if (frames.empty()) { - p.setPen(Qt::white); - p.setRenderHint(QPainter::TextAntialiasing); - p.setFont(InterFont(100, QFont::Bold)); - p.drawText(geometry(), Qt::AlignCenter, tr("camera starting")); - return; - } - - const auto &sm = *(uiState()->sm); - cereal::DriverStateV2::Reader driver_state = sm["driverStateV2"].getDriverStateV2(); - bool is_rhd = driver_state.getWheelOnRightProb() > 0.5; - auto driver_data = is_rhd ? driver_state.getRightDriverData() : driver_state.getLeftDriverData(); - - bool face_detected = driver_data.getFaceProb() > 0.7; - if (face_detected) { - auto fxy_list = driver_data.getFacePosition(); - auto std_list = driver_data.getFaceOrientationStd(); - float face_x = fxy_list[0]; - float face_y = fxy_list[1]; - float face_std = std::max(std_list[0], std_list[1]); - - float alpha = 0.7; - if (face_std > 0.15) { - alpha = std::max(0.7 - (face_std-0.15)*3.5, 0.0); - } - const int box_size = 220; - // use approx instead of distort_points - int fbox_x = 1080.0 - 1714.0 * face_x; - int fbox_y = -135.0 + (504.0 + std::abs(face_x)*112.0) + (1205.0 - std::abs(face_x)*724.0) * face_y; - p.setPen(QPen(QColor(255, 255, 255, alpha * 255), 10)); - p.drawRoundedRect(fbox_x - box_size / 2, fbox_y - box_size / 2, box_size, box_size, 35.0, 35.0); - } - - driver_monitor.updateState(*uiState()); - driver_monitor.draw(p, rect()); -} - -mat4 DriverViewWindow::calcFrameMatrix() { - const float driver_view_ratio = 2.0; - const float yscale = stream_height * driver_view_ratio / stream_width; - const float xscale = yscale * glHeight() / glWidth() * stream_width / stream_height; - return mat4{{ - xscale, 0.0, 0.0, 0.0, - 0.0, yscale, 0.0, 0.0, - 0.0, 0.0, 1.0, 0.0, - 0.0, 0.0, 0.0, 1.0, - }}; -} diff --git a/selfdrive/ui/qt/offroad/driverview.h b/selfdrive/ui/qt/offroad/driverview.h deleted file mode 100644 index f6eb752fe..000000000 --- a/selfdrive/ui/qt/offroad/driverview.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -#include "selfdrive/ui/qt/widgets/cameraview.h" -#include "selfdrive/ui/qt/onroad/driver_monitoring.h" - -class DriverViewWindow : public CameraWidget { - Q_OBJECT - -public: - explicit DriverViewWindow(QWidget *parent); - -signals: - void done(); - -protected: - mat4 calcFrameMatrix() override; - void showEvent(QShowEvent *event) override; - void hideEvent(QHideEvent *event) override; - void paintGL() override; - - Params params; - DriverMonitorRenderer driver_monitor; -}; diff --git a/selfdrive/ui/qt/offroad/experimental_mode.cc b/selfdrive/ui/qt/offroad/experimental_mode.cc deleted file mode 100644 index e255073f3..000000000 --- a/selfdrive/ui/qt/offroad/experimental_mode.cc +++ /dev/null @@ -1,76 +0,0 @@ -#include "selfdrive/ui/qt/offroad/experimental_mode.h" - -#include -#include -#include -#include -#include - -#include "selfdrive/ui/ui.h" - -ExperimentalModeButton::ExperimentalModeButton(QWidget *parent) : QPushButton(parent) { - chill_pixmap = QPixmap("../assets/icons/couch.svg").scaledToWidth(img_width, Qt::SmoothTransformation); - experimental_pixmap = QPixmap("../assets/icons/experimental_grey.svg").scaledToWidth(img_width, Qt::SmoothTransformation); - - // go to toggles and expand experimental mode description - connect(this, &QPushButton::clicked, [=]() { emit openSettings(2, "ExperimentalMode"); }); - - setFixedHeight(125); - QHBoxLayout *main_layout = new QHBoxLayout; - main_layout->setContentsMargins(horizontal_padding, 0, horizontal_padding, 0); - - mode_label = new QLabel; - mode_icon = new QLabel; - mode_icon->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); - - main_layout->addWidget(mode_label, 1, Qt::AlignLeft); - main_layout->addWidget(mode_icon, 0, Qt::AlignRight); - - setLayout(main_layout); - - setStyleSheet(R"( - QPushButton { - border: none; - } - - QLabel { - font-size: 45px; - font-weight: 300; - text-align: left; - font-family: JetBrainsMono; - color: #000000; - } - )"); -} - -void ExperimentalModeButton::paintEvent(QPaintEvent *event) { - QPainter p(this); - p.setPen(Qt::NoPen); - p.setRenderHint(QPainter::Antialiasing); - - QPainterPath path; - path.addRoundedRect(rect(), 10, 10); - - // gradient - bool pressed = isDown(); - QLinearGradient gradient(rect().left(), 0, rect().right(), 0); - if (experimental_mode) { - gradient.setColorAt(0, QColor(255, 155, 63, pressed ? 0xcc : 0xff)); - gradient.setColorAt(1, QColor(219, 56, 34, pressed ? 0xcc : 0xff)); - } else { - gradient.setColorAt(0, QColor(20, 255, 171, pressed ? 0xcc : 0xff)); - gradient.setColorAt(1, QColor(35, 149, 255, pressed ? 0xcc : 0xff)); - } - p.fillPath(path, gradient); - - // vertical line - p.setPen(QPen(QColor(0, 0, 0, 0x4d), 3, Qt::SolidLine)); - int line_x = rect().right() - img_width - (2 * horizontal_padding); - p.drawLine(line_x, rect().bottom(), line_x, rect().top()); -} - -void ExperimentalModeButton::showEvent(QShowEvent *event) { - experimental_mode = params.getBool("ExperimentalMode"); - mode_icon->setPixmap(experimental_mode ? experimental_pixmap : chill_pixmap); - mode_label->setText(experimental_mode ? tr("EXPERIMENTAL MODE ON") : tr("CHILL MODE ON")); -} diff --git a/selfdrive/ui/qt/offroad/experimental_mode.h b/selfdrive/ui/qt/offroad/experimental_mode.h deleted file mode 100644 index bfb7638bb..000000000 --- a/selfdrive/ui/qt/offroad/experimental_mode.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -#include -#include - -#include "common/params.h" - -class ExperimentalModeButton : public QPushButton { - Q_OBJECT - -public: - explicit ExperimentalModeButton(QWidget* parent = 0); - -signals: - void openSettings(int index = 0, const QString &toggle = ""); - -private: - void showEvent(QShowEvent *event) override; - - Params params; - bool experimental_mode; - int img_width = 100; - int horizontal_padding = 30; - QPixmap experimental_pixmap; - QPixmap chill_pixmap; - QLabel *mode_label; - QLabel *mode_icon; - -protected: - void paintEvent(QPaintEvent *event) override; -}; diff --git a/selfdrive/ui/qt/offroad/firehose.cc b/selfdrive/ui/qt/offroad/firehose.cc deleted file mode 100644 index aa158b4c7..000000000 --- a/selfdrive/ui/qt/offroad/firehose.cc +++ /dev/null @@ -1,112 +0,0 @@ -#include "selfdrive/ui/qt/offroad/firehose.h" -#include "selfdrive/ui/ui.h" -#include "selfdrive/ui/qt/offroad/settings.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -FirehosePanel::FirehosePanel(SettingsWindow *parent) : QWidget((QWidget*)parent) { - layout = new QVBoxLayout(this); - layout->setContentsMargins(40, 40, 40, 40); - layout->setSpacing(20); - - // header - QLabel *title = new QLabel(tr("Firehose Mode")); - title->setStyleSheet("font-size: 100px; font-weight: 500; font-family: 'Noto Color Emoji';"); - layout->addWidget(title, 0, Qt::AlignCenter); - - // Create a container for the content - QFrame *content = new QFrame(); - content->setStyleSheet("background-color: #292929; border-radius: 15px; padding: 20px;"); - QVBoxLayout *content_layout = new QVBoxLayout(content); - content_layout->setSpacing(20); - - // Top description - QLabel *description = new QLabel(tr("openpilot learns to drive by watching humans, like you, drive.\n\nFirehose Mode allows you to maximize your training data uploads to improve openpilot's driving models. More data means bigger models, which means better Experimental Mode.")); - description->setStyleSheet("font-size: 45px; padding-bottom: 20px;"); - description->setWordWrap(true); - content_layout->addWidget(description); - - // Add a separator - QFrame *line = new QFrame(); - line->setFrameShape(QFrame::HLine); - line->setFrameShadow(QFrame::Sunken); - line->setStyleSheet("background-color: #444444; margin-top: 5px; margin-bottom: 5px;"); - content_layout->addWidget(line); - - toggle_label = new QLabel(tr("Firehose Mode: ACTIVE")); - toggle_label->setStyleSheet("font-size: 60px; font-weight: bold; color: white;"); - content_layout->addWidget(toggle_label); - - // Add contribution label - contribution_label = new QLabel(); - contribution_label->setStyleSheet("font-size: 52px; margin-top: 10px; margin-bottom: 10px;"); - contribution_label->setWordWrap(true); - contribution_label->hide(); - content_layout->addWidget(contribution_label); - - // Add a separator before detailed instructions - QFrame *line2 = new QFrame(); - line2->setFrameShape(QFrame::HLine); - line2->setFrameShadow(QFrame::Sunken); - line2->setStyleSheet("background-color: #444444; margin-top: 10px; margin-bottom: 10px;"); - content_layout->addWidget(line2); - - // Detailed instructions at the bottom - detailed_instructions = new QLabel(tr( - "For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.
" - "
" - "Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.
" - "

" - "Frequently Asked Questions

" - "Does it matter how or where I drive? Nope, just drive as you normally would.

" - "Do all of my segments get pulled in Firehose Mode? No, we selectively pull a subset of your segments.

" - "What's a good USB-C adapter? Any fast phone or laptop charger should be fine.

" - "Does it matter which software I run? Yes, only upstream openpilot (and particular forks) are able to be used for training." - )); - detailed_instructions->setStyleSheet("font-size: 40px; color: #E4E4E4;"); - detailed_instructions->setWordWrap(true); - content_layout->addWidget(detailed_instructions); - - layout->addWidget(content, 1); - - // Set up the API request for firehose stats - const QString dongle_id = QString::fromStdString(Params().get("DongleId")); - firehose_stats = new RequestRepeater(this, CommaApi::BASE_URL + "/v1/devices/" + dongle_id + "/firehose_stats", - "ApiCache_FirehoseStats", 30, true); - QObject::connect(firehose_stats, &RequestRepeater::requestDone, [=](const QString &response, bool success) { - if (success) { - QJsonDocument doc = QJsonDocument::fromJson(response.toUtf8()); - QJsonObject json = doc.object(); - int count = json["firehose"].toInt(); - contribution_label->setText(tr("%n segment(s) of your driving is in the training dataset so far.", "", count)); - contribution_label->show(); - } - }); - - QObject::connect(uiState(), &UIState::uiUpdate, this, &FirehosePanel::refresh); -} - -void FirehosePanel::refresh() { - auto deviceState = (*uiState()->sm)["deviceState"].getDeviceState(); - auto networkType = deviceState.getNetworkType(); - bool networkMetered = deviceState.getNetworkMetered(); - - bool is_active = !networkMetered && (networkType != cereal::DeviceState::NetworkType::NONE); - if (is_active) { - toggle_label->setText(tr("ACTIVE")); - toggle_label->setStyleSheet("font-size: 60px; font-weight: bold; color: #2ecc71;"); - } else { - toggle_label->setText(tr("INACTIVE: connect to an unmetered network")); - toggle_label->setStyleSheet("font-size: 60px;"); - } -} diff --git a/selfdrive/ui/qt/offroad/firehose.h b/selfdrive/ui/qt/offroad/firehose.h deleted file mode 100644 index 9082cb0f9..000000000 --- a/selfdrive/ui/qt/offroad/firehose.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -#include -#include -#include -#include "selfdrive/ui/qt/request_repeater.h" - -// Forward declarations -class SettingsWindow; - -class FirehosePanel : public QWidget { - Q_OBJECT -public: - explicit FirehosePanel(SettingsWindow *parent); - -private: - QVBoxLayout *layout; - - QLabel *detailed_instructions; - QLabel *contribution_label; - QLabel *toggle_label; - - RequestRepeater *firehose_stats; - -private slots: - void refresh(); -}; diff --git a/selfdrive/ui/qt/offroad/onboarding.cc b/selfdrive/ui/qt/offroad/onboarding.cc deleted file mode 100644 index 139056667..000000000 --- a/selfdrive/ui/qt/offroad/onboarding.cc +++ /dev/null @@ -1,211 +0,0 @@ -#include "selfdrive/ui/qt/offroad/onboarding.h" - -#include - -#include -#include -#include -#include - -#include "common/util.h" -#include "common/params.h" -#include "selfdrive/ui/qt/util.h" -#include "selfdrive/ui/qt/widgets/input.h" - -TrainingGuide::TrainingGuide(QWidget *parent) : QFrame(parent) { - setAttribute(Qt::WA_OpaquePaintEvent); -} - -void TrainingGuide::mouseReleaseEvent(QMouseEvent *e) { - if (click_timer.elapsed() < 250) { - return; - } - click_timer.restart(); - - auto contains = [this](QRect r, const QPoint &pt) { - if (image.size() != image_raw_size) { - QTransform transform; - transform.translate((width()- image.width()) / 2.0, (height()- image.height()) / 2.0); - transform.scale(image.width() / (float)image_raw_size.width(), image.height() / (float)image_raw_size.height()); - r= transform.mapRect(r); - } - return r.contains(pt); - }; - - if (contains(boundingRect[currentIndex], e->pos())) { - if (currentIndex == 9) { - const QRect yes = QRect(707, 804, 531, 164); - Params().putBool("RecordFront", contains(yes, e->pos())); - } - currentIndex += 1; - } else if (currentIndex == (boundingRect.size() - 2) && contains(boundingRect.last(), e->pos())) { - currentIndex = 0; - } - - if (currentIndex >= (boundingRect.size() - 1)) { - emit completedTraining(); - } else { - update(); - } -} - -void TrainingGuide::showEvent(QShowEvent *event) { - currentIndex = 0; - click_timer.start(); -} - -QImage TrainingGuide::loadImage(int id) { - QImage img(img_path + QString("step%1.png").arg(id)); - image_raw_size = img.size(); - if (image_raw_size != rect().size()) { - img = img.scaled(width(), height(), Qt::KeepAspectRatio, Qt::SmoothTransformation); - } - return img; -} - -void TrainingGuide::paintEvent(QPaintEvent *event) { - QPainter painter(this); - - QRect bg(0, 0, painter.device()->width(), painter.device()->height()); - painter.fillRect(bg, QColor("#000000")); - - image = loadImage(currentIndex); - QRect rect(image.rect()); - rect.moveCenter(bg.center()); - painter.drawImage(rect.topLeft(), image); - - // progress bar - if (currentIndex > 0 && currentIndex < (boundingRect.size() - 2)) { - const int h = 20; - const int w = (currentIndex / (float)(boundingRect.size() - 2)) * width(); - painter.fillRect(QRect(0, height() - h, w, h), QColor("#465BEA")); - } -} - -void TermsPage::showEvent(QShowEvent *event) { - QVBoxLayout *main_layout = new QVBoxLayout(this); - main_layout->setContentsMargins(45, 35, 45, 45); - main_layout->setSpacing(0); - - QVBoxLayout *vlayout = new QVBoxLayout(); - vlayout->setContentsMargins(165, 165, 165, 0); - main_layout->addLayout(vlayout); - - QLabel *title = new QLabel(tr("Welcome to openpilot")); - title->setStyleSheet("font-size: 90px; font-weight: 500;"); - vlayout->addWidget(title, 0, Qt::AlignTop | Qt::AlignLeft); - - vlayout->addSpacing(90); - QLabel *desc = new QLabel(tr("You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing.")); - desc->setWordWrap(true); - desc->setStyleSheet("font-size: 80px; font-weight: 300;"); - vlayout->addWidget(desc, 0); - - vlayout->addStretch(); - - QHBoxLayout* buttons = new QHBoxLayout; - buttons->setMargin(0); - buttons->setSpacing(45); - main_layout->addLayout(buttons); - - QPushButton *decline_btn = new QPushButton(tr("Decline")); - buttons->addWidget(decline_btn); - QObject::connect(decline_btn, &QPushButton::clicked, this, &TermsPage::declinedTerms); - - accept_btn = new QPushButton(tr("Agree")); - accept_btn->setStyleSheet(R"( - QPushButton { - background-color: #465BEA; - } - QPushButton:pressed { - background-color: #3049F4; - } - )"); - buttons->addWidget(accept_btn); - QObject::connect(accept_btn, &QPushButton::clicked, this, &TermsPage::acceptedTerms); -} - -void DeclinePage::showEvent(QShowEvent *event) { - if (layout()) { - return; - } - - QVBoxLayout *main_layout = new QVBoxLayout(this); - main_layout->setMargin(45); - main_layout->setSpacing(40); - - QLabel *text = new QLabel(this); - text->setText(tr("You must accept the Terms and Conditions in order to use openpilot.")); - text->setStyleSheet(R"(font-size: 80px; font-weight: 300; margin: 200px;)"); - text->setWordWrap(true); - main_layout->addWidget(text, 0, Qt::AlignCenter); - - QHBoxLayout* buttons = new QHBoxLayout; - buttons->setSpacing(45); - main_layout->addLayout(buttons); - - QPushButton *back_btn = new QPushButton(tr("Back")); - buttons->addWidget(back_btn); - - QObject::connect(back_btn, &QPushButton::clicked, this, &DeclinePage::getBack); - - QPushButton *uninstall_btn = new QPushButton(tr("Decline, uninstall %1").arg(getBrand())); - uninstall_btn->setStyleSheet("background-color: #B73D3D"); - buttons->addWidget(uninstall_btn); - QObject::connect(uninstall_btn, &QPushButton::clicked, [=]() { - Params().putBool("DoUninstall", true); - }); -} - -void OnboardingWindow::updateActiveScreen() { - if (!accepted_terms) { - setCurrentIndex(0); - } else if (!training_done) { - setCurrentIndex(1); - } else { - emit onboardingDone(); - } -} - -OnboardingWindow::OnboardingWindow(QWidget *parent) : QStackedWidget(parent) { - std::string current_terms_version = params.get("TermsVersion"); - std::string current_training_version = params.get("TrainingVersion"); - accepted_terms = params.get("HasAcceptedTerms") == current_terms_version; - training_done = params.get("CompletedTrainingVersion") == current_training_version; - - TermsPage* terms = new TermsPage(this); - addWidget(terms); - connect(terms, &TermsPage::acceptedTerms, [=]() { - params.put("HasAcceptedTerms", current_terms_version); - accepted_terms = true; - updateActiveScreen(); - }); - connect(terms, &TermsPage::declinedTerms, [=]() { setCurrentIndex(2); }); - - TrainingGuide* tr = new TrainingGuide(this); - addWidget(tr); - connect(tr, &TrainingGuide::completedTraining, [=]() { - training_done = true; - params.put("CompletedTrainingVersion", current_training_version); - updateActiveScreen(); - }); - - DeclinePage* declinePage = new DeclinePage(this); - addWidget(declinePage); - connect(declinePage, &DeclinePage::getBack, [=]() { updateActiveScreen(); }); - - setStyleSheet(R"( - * { - color: white; - background-color: black; - } - QPushButton { - height: 160px; - font-size: 55px; - font-weight: 400; - border-radius: 10px; - background-color: #4F4F4F; - } - )"); - updateActiveScreen(); -} diff --git a/selfdrive/ui/qt/offroad/onboarding.h b/selfdrive/ui/qt/offroad/onboarding.h deleted file mode 100644 index db229c5fa..000000000 --- a/selfdrive/ui/qt/offroad/onboarding.h +++ /dev/null @@ -1,107 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -#include "common/params.h" -#include "selfdrive/ui/qt/qt_window.h" - -class TrainingGuide : public QFrame { - Q_OBJECT - -public: - explicit TrainingGuide(QWidget *parent = 0); - -private: - void showEvent(QShowEvent *event) override; - void paintEvent(QPaintEvent *event) override; - void mouseReleaseEvent(QMouseEvent* e) override; - QImage loadImage(int id); - - QImage image; - QSize image_raw_size; - int currentIndex = 0; - - // Bounding boxes for each training guide step - const QRect continueBtn = {1840, 0, 320, 1080}; - QVector boundingRect { - QRect(112, 804, 618, 164), - continueBtn, - continueBtn, - QRect(1641, 558, 210, 313), - QRect(1662, 528, 184, 108), - continueBtn, - QRect(1814, 621, 211, 170), - QRect(1350, 0, 497, 755), - QRect(1540, 386, 468, 238), - QRect(112, 804, 1126, 164), - QRect(1598, 199, 316, 333), - continueBtn, - QRect(1364, 90, 796, 990), - continueBtn, - QRect(1593, 114, 318, 853), - QRect(1379, 511, 391, 243), - continueBtn, - continueBtn, - QRect(630, 804, 626, 164), - QRect(108, 804, 426, 164), - }; - - const QString img_path = "../assets/training/"; - QElapsedTimer click_timer; - -signals: - void completedTraining(); -}; - - -class TermsPage : public QFrame { - Q_OBJECT - -public: - explicit TermsPage(QWidget *parent = 0) : QFrame(parent) {} - -private: - void showEvent(QShowEvent *event) override; - - QPushButton *accept_btn; - -signals: - void acceptedTerms(); - void declinedTerms(); -}; - -class DeclinePage : public QFrame { - Q_OBJECT - -public: - explicit DeclinePage(QWidget *parent = 0) : QFrame(parent) {} - -private: - void showEvent(QShowEvent *event) override; - -signals: - void getBack(); -}; - -class OnboardingWindow : public QStackedWidget { - Q_OBJECT - -public: - explicit OnboardingWindow(QWidget *parent = 0); - inline void showTrainingGuide() { setCurrentIndex(1); } - inline bool completed() const { return accepted_terms && training_done; } - -private: - void updateActiveScreen(); - - Params params; - bool accepted_terms = false, training_done = false; - -signals: - void onboardingDone(); -}; diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc deleted file mode 100644 index 6eb8693a1..000000000 --- a/selfdrive/ui/qt/offroad/settings.cc +++ /dev/null @@ -1,535 +0,0 @@ -#include -#include -#include -#include -#include - -#include - -#include "common/util.h" -#include "selfdrive/ui/qt/network/networking.h" -#include "selfdrive/ui/qt/offroad/settings.h" -#include "selfdrive/ui/qt/qt_window.h" -#include "selfdrive/ui/qt/widgets/prime.h" -#include "selfdrive/ui/qt/widgets/scrollview.h" -#include "selfdrive/ui/qt/offroad/developer_panel.h" -#include "selfdrive/ui/qt/offroad/firehose.h" - -TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { - // param, title, desc, icon, restart needed - std::vector> toggle_defs{ - { - "OpenpilotEnabledToggle", - tr("Enable openpilot"), - tr("Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature."), - "../assets/icons/chffr_wheel.png", - true, - }, - { - "ExperimentalMode", - tr("Experimental Mode"), - "", - "../assets/icons/experimental_white.svg", - false, - }, - { - "DisengageOnAccelerator", - tr("Disengage on Accelerator Pedal"), - tr("When enabled, pressing the accelerator pedal will disengage openpilot."), - "../assets/icons/disengage_on_accelerator.svg", - false, - }, - { - "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/icons/warning.png", - false, - }, - { - "AlwaysOnDM", - tr("Always-On Driver Monitoring"), - tr("Enable driver monitoring even when openpilot is not engaged."), - "../assets/icons/monitoring.png", - false, - }, - { - "RecordFront", - tr("Record and Upload Driver Camera"), - tr("Upload data from the driver facing camera and help improve the driver monitoring algorithm."), - "../assets/icons/monitoring.png", - true, - }, - { - "RecordAudio", - tr("Record and Upload Microphone Audio"), - tr("Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect."), - "../assets/icons/microphone.png", - true, - }, - { - "IsMetric", - tr("Use Metric System"), - tr("Display speed in km/h instead of mph."), - "../assets/icons/metric.png", - false, - }, - }; - - - std::vector longi_button_texts{tr("Aggressive"), tr("Standard"), tr("Relaxed")}; - long_personality_setting = new ButtonParamControl("LongitudinalPersonality", tr("Driving Personality"), - tr("Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. " - "In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with " - "your steering wheel distance button."), - "../assets/icons/speed_limit.png", - longi_button_texts); - - // set up uiState update for personality setting - QObject::connect(uiState(), &UIState::uiUpdate, this, &TogglesPanel::updateState); - - for (auto &[param, title, desc, icon, needs_restart] : toggle_defs) { - auto toggle = new ParamControl(param, title, desc, icon, this); - - bool locked = params.getBool((param + "Lock").toStdString()); - toggle->setEnabled(!locked); - - if (needs_restart && !locked) { - toggle->setDescription(toggle->getDescription() + tr(" Changing this setting will restart openpilot if the car is powered on.")); - - QObject::connect(uiState(), &UIState::engagedChanged, [toggle](bool engaged) { - toggle->setEnabled(!engaged); - }); - - QObject::connect(toggle, &ParamControl::toggleFlipped, [=](bool state) { - params.putBool("OnroadCycleRequested", true); - }); - } - - addItem(toggle); - toggles[param.toStdString()] = toggle; - - // insert longitudinal personality after NDOG toggle - if (param == "DisengageOnAccelerator") { - addItem(long_personality_setting); - } - } - - // Toggles with confirmation dialogs - toggles["ExperimentalMode"]->setActiveIcon("../assets/icons/experimental.svg"); - toggles["ExperimentalMode"]->setConfirmation(true, true); -} - -void TogglesPanel::updateState(const UIState &s) { - const SubMaster &sm = *(s.sm); - - if (sm.updated("selfdriveState")) { - auto personality = sm["selfdriveState"].getSelfdriveState().getPersonality(); - if (personality != s.scene.personality && s.scene.started && isVisible()) { - long_personality_setting->setCheckedButton(static_cast(personality)); - } - uiState()->scene.personality = personality; - } -} - -void TogglesPanel::expandToggleDescription(const QString ¶m) { - toggles[param.toStdString()]->showDescription(); -} - -void TogglesPanel::scrollToToggle(const QString ¶m) { - if (auto it = toggles.find(param.toStdString()); it != toggles.end()) { - auto scroll_area = qobject_cast(parent()->parent()); - if (scroll_area) { - scroll_area->ensureWidgetVisible(it->second); - } - } -} - -void TogglesPanel::showEvent(QShowEvent *event) { - updateToggles(); -} - -void TogglesPanel::updateToggles() { - auto experimental_mode_toggle = toggles["ExperimentalMode"]; - const QString e2e_description = QString("%1
" - "

%2


" - "%3
" - "

%4


" - "%5
") - .arg(tr("openpilot defaults to driving in chill mode. Experimental mode enables alpha-level features that aren't ready for chill mode. Experimental features are listed below:")) - .arg(tr("End-to-End Longitudinal Control")) - .arg(tr("Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. " - "Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; " - "mistakes should be expected.")) - .arg(tr("New Driving Visualization")) - .arg(tr("The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner.")); - - const bool is_release = params.getBool("IsReleaseBranch"); - auto cp_bytes = params.get("CarParamsPersistent"); - if (!cp_bytes.empty()) { - AlignedBuffer aligned_buf; - capnp::FlatArrayMessageReader cmsg(aligned_buf.align(cp_bytes.data(), cp_bytes.size())); - cereal::CarParams::Reader CP = cmsg.getRoot(); - - if (hasLongitudinalControl(CP)) { - // normal description and toggle - experimental_mode_toggle->setEnabled(true); - experimental_mode_toggle->setDescription(e2e_description); - long_personality_setting->setEnabled(true); - } else { - // no long for now - experimental_mode_toggle->setEnabled(false); - long_personality_setting->setEnabled(false); - params.remove("ExperimentalMode"); - - const QString unavailable = tr("Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control."); - - QString long_desc = unavailable + " " + \ - tr("openpilot longitudinal control may come in a future update."); - if (CP.getAlphaLongitudinalAvailable()) { - if (is_release) { - long_desc = unavailable + " " + tr("An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches."); - } else { - long_desc = tr("Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode."); - } - } - experimental_mode_toggle->setDescription("" + long_desc + "

" + e2e_description); - } - - experimental_mode_toggle->refresh(); - } else { - experimental_mode_toggle->setDescription(e2e_description); - } -} - -DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) { - setSpacing(50); - addItem(new LabelControl(tr("Dongle ID"), getDongleId().value_or(tr("N/A")))); - addItem(new LabelControl(tr("Serial"), params.get("HardwareSerial").c_str())); - - pair_device = new ButtonControl(tr("Pair Device"), tr("PAIR"), - tr("Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer.")); - connect(pair_device, &ButtonControl::clicked, [=]() { - PairingPopup popup(this); - popup.exec(); - }); - addItem(pair_device); - - // offroad-only buttons - - auto dcamBtn = new ButtonControl(tr("Driver Camera"), tr("PREVIEW"), - tr("Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)")); - connect(dcamBtn, &ButtonControl::clicked, [=]() { emit showDriverView(); }); - addItem(dcamBtn); - - resetCalibBtn = new ButtonControl(tr("Reset Calibration"), tr("RESET"), ""); - connect(resetCalibBtn, &ButtonControl::showDescriptionEvent, this, &DevicePanel::updateCalibDescription); - connect(resetCalibBtn, &ButtonControl::clicked, [&]() { - if (!uiState()->engaged()) { - if (ConfirmationDialog::confirm(tr("Are you sure you want to reset calibration?"), tr("Reset"), this)) { - // Check engaged again in case it changed while the dialog was open - if (!uiState()->engaged()) { - params.remove("CalibrationParams"); - params.remove("LiveTorqueParameters"); - params.remove("LiveParameters"); - params.remove("LiveParametersV2"); - params.remove("LiveDelay"); - params.putBool("OnroadCycleRequested", true); - updateCalibDescription(); - } - } - } else { - ConfirmationDialog::alert(tr("Disengage to Reset Calibration"), this); - } - }); - addItem(resetCalibBtn); - - auto retrainingBtn = new ButtonControl(tr("Review Training Guide"), tr("REVIEW"), tr("Review the rules, features, and limitations of openpilot")); - connect(retrainingBtn, &ButtonControl::clicked, [=]() { - if (ConfirmationDialog::confirm(tr("Are you sure you want to review the training guide?"), tr("Review"), this)) { - emit reviewTrainingGuide(); - } - }); - addItem(retrainingBtn); - - if (Hardware::TICI()) { - auto regulatoryBtn = new ButtonControl(tr("Regulatory"), tr("VIEW"), ""); - connect(regulatoryBtn, &ButtonControl::clicked, [=]() { - const std::string txt = util::read_file("../assets/offroad/fcc.html"); - ConfirmationDialog::rich(QString::fromStdString(txt), this); - }); - addItem(regulatoryBtn); - } - - auto translateBtn = new ButtonControl(tr("Change Language"), tr("CHANGE"), ""); - connect(translateBtn, &ButtonControl::clicked, [=]() { - QMap langs = getSupportedLanguages(); - QString selection = MultiOptionDialog::getSelection(tr("Select a language"), langs.keys(), langs.key(uiState()->language), this); - if (!selection.isEmpty()) { - // put language setting, exit Qt UI, and trigger fast restart - params.put("LanguageSetting", langs[selection].toStdString()); - qApp->exit(18); - } - }); - addItem(translateBtn); - - QObject::connect(uiState()->prime_state, &PrimeState::changed, [this] (PrimeState::Type type) { - pair_device->setVisible(type == PrimeState::PRIME_TYPE_UNPAIRED); - }); - QObject::connect(uiState(), &UIState::offroadTransition, [=](bool offroad) { - for (auto btn : findChildren()) { - if (btn != pair_device && btn != resetCalibBtn) { - btn->setEnabled(offroad); - } - } - }); - - // power buttons - QHBoxLayout *power_layout = new QHBoxLayout(); - power_layout->setSpacing(30); - - QPushButton *reboot_btn = new QPushButton(tr("Reboot")); - reboot_btn->setObjectName("reboot_btn"); - power_layout->addWidget(reboot_btn); - QObject::connect(reboot_btn, &QPushButton::clicked, this, &DevicePanel::reboot); - - QPushButton *poweroff_btn = new QPushButton(tr("Power Off")); - poweroff_btn->setObjectName("poweroff_btn"); - power_layout->addWidget(poweroff_btn); - QObject::connect(poweroff_btn, &QPushButton::clicked, this, &DevicePanel::poweroff); - - if (!Hardware::PC()) { - connect(uiState(), &UIState::offroadTransition, poweroff_btn, &QPushButton::setVisible); - } - - setStyleSheet(R"( - #reboot_btn { height: 120px; border-radius: 15px; background-color: #393939; } - #reboot_btn:pressed { background-color: #4a4a4a; } - #poweroff_btn { height: 120px; border-radius: 15px; background-color: #E22C2C; } - #poweroff_btn:pressed { background-color: #FF2424; } - )"); - addItem(power_layout); -} - -void DevicePanel::updateCalibDescription() { - QString desc = tr("openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down."); - std::string calib_bytes = params.get("CalibrationParams"); - if (!calib_bytes.empty()) { - try { - AlignedBuffer aligned_buf; - capnp::FlatArrayMessageReader cmsg(aligned_buf.align(calib_bytes.data(), calib_bytes.size())); - auto calib = cmsg.getRoot().getLiveCalibration(); - if (calib.getCalStatus() != cereal::LiveCalibrationData::Status::UNCALIBRATED) { - double pitch = calib.getRpyCalib()[1] * (180 / M_PI); - double yaw = calib.getRpyCalib()[2] * (180 / M_PI); - desc += tr(" Your device is pointed %1° %2 and %3° %4.") - .arg(QString::number(std::abs(pitch), 'g', 1), pitch > 0 ? tr("down") : tr("up"), - QString::number(std::abs(yaw), 'g', 1), yaw > 0 ? tr("left") : tr("right")); - } - } catch (kj::Exception) { - qInfo() << "invalid CalibrationParams"; - } - } - - int lag_perc = 0; - std::string lag_bytes = params.get("LiveDelay"); - if (!lag_bytes.empty()) { - try { - AlignedBuffer aligned_buf; - capnp::FlatArrayMessageReader cmsg(aligned_buf.align(lag_bytes.data(), lag_bytes.size())); - lag_perc = cmsg.getRoot().getLiveDelay().getCalPerc(); - } catch (kj::Exception) { - qInfo() << "invalid LiveDelay"; - } - } - if (lag_perc < 100) { - desc += tr("\n\nSteering lag calibration is %1% complete.").arg(lag_perc); - } else { - desc += tr("\n\nSteering lag calibration is complete."); - } - - std::string torque_bytes = params.get("LiveTorqueParameters"); - if (!torque_bytes.empty()) { - try { - AlignedBuffer aligned_buf; - capnp::FlatArrayMessageReader cmsg(aligned_buf.align(torque_bytes.data(), torque_bytes.size())); - auto torque = cmsg.getRoot().getLiveTorqueParameters(); - // don't add for non-torque cars - if (torque.getUseParams()) { - int torque_perc = torque.getCalPerc(); - if (torque_perc < 100) { - desc += tr(" Steering torque response calibration is %1% complete.").arg(torque_perc); - } else { - desc += tr(" Steering torque response calibration is complete."); - } - } - } catch (kj::Exception) { - qInfo() << "invalid LiveTorqueParameters"; - } - } - - desc += "\n\n"; - desc += tr("openpilot is continuously calibrating, resetting is rarely required. " - "Resetting calibration will restart openpilot if the car is powered on."); - resetCalibBtn->setDescription(desc); -} - -void DevicePanel::reboot() { - if (!uiState()->engaged()) { - if (ConfirmationDialog::confirm(tr("Are you sure you want to reboot?"), tr("Reboot"), this)) { - // Check engaged again in case it changed while the dialog was open - if (!uiState()->engaged()) { - params.putBool("DoReboot", true); - } - } - } else { - ConfirmationDialog::alert(tr("Disengage to Reboot"), this); - } -} - -void DevicePanel::poweroff() { - if (!uiState()->engaged()) { - if (ConfirmationDialog::confirm(tr("Are you sure you want to power off?"), tr("Power Off"), this)) { - // Check engaged again in case it changed while the dialog was open - if (!uiState()->engaged()) { - params.putBool("DoShutdown", true); - } - } - } else { - ConfirmationDialog::alert(tr("Disengage to Power Off"), this); - } -} - -void SettingsWindow::showEvent(QShowEvent *event) { - setCurrentPanel(0); -} - -void SettingsWindow::setCurrentPanel(int index, const QString ¶m) { - if (!param.isEmpty()) { - // Check if param ends with "Panel" to determine if it's a panel name - if (param.endsWith("Panel")) { - QString panelName = param; - panelName.chop(5); // Remove "Panel" suffix - - // Find the panel by name - for (int i = 0; i < nav_btns->buttons().size(); i++) { - if (nav_btns->buttons()[i]->text() == tr(panelName.toStdString().c_str())) { - index = i; - break; - } - } - } else { - emit expandToggleDescription(param); - emit scrollToToggle(param); - } - } - - panel_widget->setCurrentIndex(index); - nav_btns->buttons()[index]->setChecked(true); -} - -SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) { - - // setup two main layouts - sidebar_widget = new QWidget; - QVBoxLayout *sidebar_layout = new QVBoxLayout(sidebar_widget); - panel_widget = new QStackedWidget(); - - // close button - QPushButton *close_btn = new QPushButton(tr("×")); - close_btn->setStyleSheet(R"( - QPushButton { - font-size: 140px; - padding-bottom: 20px; - border-radius: 100px; - background-color: #292929; - font-weight: 400; - } - QPushButton:pressed { - background-color: #3B3B3B; - } - )"); - close_btn->setFixedSize(200, 200); - sidebar_layout->addSpacing(45); - sidebar_layout->addWidget(close_btn, 0, Qt::AlignCenter); - QObject::connect(close_btn, &QPushButton::clicked, this, &SettingsWindow::closeSettings); - - // setup panels - DevicePanel *device = new DevicePanel(this); - QObject::connect(device, &DevicePanel::reviewTrainingGuide, this, &SettingsWindow::reviewTrainingGuide); - QObject::connect(device, &DevicePanel::showDriverView, this, &SettingsWindow::showDriverView); - - TogglesPanel *toggles = new TogglesPanel(this); - QObject::connect(this, &SettingsWindow::expandToggleDescription, toggles, &TogglesPanel::expandToggleDescription); - QObject::connect(this, &SettingsWindow::scrollToToggle, toggles, &TogglesPanel::scrollToToggle); - - auto networking = new Networking(this); - QObject::connect(uiState()->prime_state, &PrimeState::changed, networking, &Networking::setPrimeType); - - QList> panels = { - {tr("Device"), device}, - {tr("Network"), networking}, - {tr("Toggles"), toggles}, - {tr("Software"), new SoftwarePanel(this)}, - {tr("Firehose"), new FirehosePanel(this)}, - {tr("Developer"), new DeveloperPanel(this)}, - }; - - nav_btns = new QButtonGroup(this); - for (auto &[name, panel] : panels) { - QPushButton *btn = new QPushButton(name); - btn->setCheckable(true); - btn->setChecked(nav_btns->buttons().size() == 0); - btn->setStyleSheet(R"( - QPushButton { - color: grey; - border: none; - background: none; - font-size: 65px; - font-weight: 500; - } - QPushButton:checked { - color: white; - } - QPushButton:pressed { - color: #ADADAD; - } - )"); - btn->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); - nav_btns->addButton(btn); - sidebar_layout->addWidget(btn, 0, Qt::AlignRight); - - const int lr_margin = name != tr("Network") ? 50 : 0; // Network panel handles its own margins - panel->setContentsMargins(lr_margin, 25, lr_margin, 25); - - ScrollView *panel_frame = new ScrollView(panel, this); - panel_widget->addWidget(panel_frame); - - QObject::connect(btn, &QPushButton::clicked, [=, w = panel_frame]() { - btn->setChecked(true); - panel_widget->setCurrentWidget(w); - }); - } - sidebar_layout->setContentsMargins(50, 50, 100, 50); - - // main settings layout, sidebar + main panel - QHBoxLayout *main_layout = new QHBoxLayout(this); - - sidebar_widget->setFixedWidth(500); - main_layout->addWidget(sidebar_widget); - main_layout->addWidget(panel_widget); - - setStyleSheet(R"( - * { - color: white; - font-size: 50px; - } - SettingsWindow { - background-color: black; - } - QStackedWidget, ScrollView { - background-color: #292929; - border-radius: 30px; - } - )"); -} diff --git a/selfdrive/ui/qt/offroad/settings.h b/selfdrive/ui/qt/offroad/settings.h deleted file mode 100644 index d52cf16bb..000000000 --- a/selfdrive/ui/qt/offroad/settings.h +++ /dev/null @@ -1,106 +0,0 @@ -#pragma once - -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "selfdrive/ui/ui.h" -#include "selfdrive/ui/qt/util.h" -#include "selfdrive/ui/qt/widgets/controls.h" - -// ********** settings window + top-level panels ********** -class SettingsWindow : public QFrame { - Q_OBJECT - -public: - explicit SettingsWindow(QWidget *parent = 0); - void setCurrentPanel(int index, const QString ¶m = ""); - -protected: - void showEvent(QShowEvent *event) override; - -signals: - void closeSettings(); - void reviewTrainingGuide(); - void showDriverView(); - void expandToggleDescription(const QString ¶m); - void scrollToToggle(const QString ¶m); - -private: - QPushButton *sidebar_alert_widget; - QWidget *sidebar_widget; - QButtonGroup *nav_btns; - QStackedWidget *panel_widget; -}; - -class DevicePanel : public ListWidget { - Q_OBJECT -public: - explicit DevicePanel(SettingsWindow *parent); - -signals: - void reviewTrainingGuide(); - void showDriverView(); - -private slots: - void poweroff(); - void reboot(); - void updateCalibDescription(); - -private: - Params params; - ButtonControl *pair_device; - ButtonControl *resetCalibBtn; -}; - -class TogglesPanel : public ListWidget { - Q_OBJECT -public: - explicit TogglesPanel(SettingsWindow *parent); - void showEvent(QShowEvent *event) override; - -public slots: - void expandToggleDescription(const QString ¶m); - void scrollToToggle(const QString ¶m); - -private slots: - void updateState(const UIState &s); - -private: - Params params; - std::map toggles; - ButtonParamControl *long_personality_setting; - - void updateToggles(); -}; - -class SoftwarePanel : public ListWidget { - Q_OBJECT -public: - explicit SoftwarePanel(QWidget* parent = nullptr); - -private: - void showEvent(QShowEvent *event) override; - void updateLabels(); - void checkForUpdates(); - - bool is_onroad = false; - - QLabel *onroadLbl; - LabelControl *versionLbl; - ButtonControl *installBtn; - ButtonControl *downloadBtn; - ButtonControl *targetBranchBtn; - - Params params; - ParamWatcher *fs_watch; -}; - -// Forward declaration -class FirehosePanel; diff --git a/selfdrive/ui/qt/offroad/software_settings.cc b/selfdrive/ui/qt/offroad/software_settings.cc deleted file mode 100644 index 9bc3fad3c..000000000 --- a/selfdrive/ui/qt/offroad/software_settings.cc +++ /dev/null @@ -1,156 +0,0 @@ -#include "selfdrive/ui/qt/offroad/settings.h" - -#include -#include -#include - -#include -#include - -#include "common/params.h" -#include "common/util.h" -#include "selfdrive/ui/ui.h" -#include "selfdrive/ui/qt/util.h" -#include "selfdrive/ui/qt/widgets/controls.h" -#include "selfdrive/ui/qt/widgets/input.h" -#include "system/hardware/hw.h" - - -void SoftwarePanel::checkForUpdates() { - std::system("pkill -SIGUSR1 -f system.updated.updated"); -} - -SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) { - onroadLbl = new QLabel(tr("Updates are only downloaded while the car is off.")); - onroadLbl->setStyleSheet("font-size: 50px; font-weight: 400; text-align: left; padding-top: 30px; padding-bottom: 30px;"); - addItem(onroadLbl); - - // current version - versionLbl = new LabelControl(tr("Current Version"), ""); - addItem(versionLbl); - - // download update btn - downloadBtn = new ButtonControl(tr("Download"), tr("CHECK")); - connect(downloadBtn, &ButtonControl::clicked, [=]() { - downloadBtn->setEnabled(false); - if (downloadBtn->text() == tr("CHECK")) { - checkForUpdates(); - } else { - std::system("pkill -SIGHUP -f system.updated.updated"); - } - }); - addItem(downloadBtn); - - // install update btn - installBtn = new ButtonControl(tr("Install Update"), tr("INSTALL")); - connect(installBtn, &ButtonControl::clicked, [=]() { - installBtn->setEnabled(false); - params.putBool("DoReboot", true); - }); - addItem(installBtn); - - // branch selecting - targetBranchBtn = new ButtonControl(tr("Target Branch"), tr("SELECT")); - connect(targetBranchBtn, &ButtonControl::clicked, [=]() { - auto current = params.get("GitBranch"); - QStringList branches = QString::fromStdString(params.get("UpdaterAvailableBranches")).split(","); - for (QString b : {current.c_str(), "devel-staging", "devel", "nightly", "nightly-dev", "master"}) { - auto i = branches.indexOf(b); - if (i >= 0) { - branches.removeAt(i); - branches.insert(0, b); - } - } - - QString cur = QString::fromStdString(params.get("UpdaterTargetBranch")); - QString selection = MultiOptionDialog::getSelection(tr("Select a branch"), branches, cur, this); - if (!selection.isEmpty()) { - params.put("UpdaterTargetBranch", selection.toStdString()); - targetBranchBtn->setValue(QString::fromStdString(params.get("UpdaterTargetBranch"))); - checkForUpdates(); - } - }); - if (!params.getBool("IsTestedBranch")) { - addItem(targetBranchBtn); - } - - // uninstall button - auto uninstallBtn = new ButtonControl(tr("Uninstall %1").arg(getBrand()), tr("UNINSTALL")); - connect(uninstallBtn, &ButtonControl::clicked, [&]() { - if (ConfirmationDialog::confirm(tr("Are you sure you want to uninstall?"), tr("Uninstall"), this)) { - params.putBool("DoUninstall", true); - } - }); - addItem(uninstallBtn); - - fs_watch = new ParamWatcher(this); - QObject::connect(fs_watch, &ParamWatcher::paramChanged, [=](const QString ¶m_name, const QString ¶m_value) { - updateLabels(); - }); - - connect(uiState(), &UIState::offroadTransition, [=](bool offroad) { - is_onroad = !offroad; - updateLabels(); - }); - - updateLabels(); -} - -void SoftwarePanel::showEvent(QShowEvent *event) { - // nice for testing on PC - installBtn->setEnabled(true); - - updateLabels(); -} - -void SoftwarePanel::updateLabels() { - // add these back in case the files got removed - fs_watch->addParam("LastUpdateTime"); - fs_watch->addParam("UpdateFailedCount"); - fs_watch->addParam("UpdaterState"); - fs_watch->addParam("UpdateAvailable"); - - if (!isVisible()) { - return; - } - - // updater only runs offroad - onroadLbl->setVisible(is_onroad); - downloadBtn->setVisible(!is_onroad); - - // download update - QString updater_state = QString::fromStdString(params.get("UpdaterState")); - bool failed = std::atoi(params.get("UpdateFailedCount").c_str()) > 0; - if (updater_state != "idle") { - downloadBtn->setEnabled(false); - downloadBtn->setValue(updater_state); - } else { - if (failed) { - downloadBtn->setText(tr("CHECK")); - downloadBtn->setValue(tr("failed to check for update")); - } else if (params.getBool("UpdaterFetchAvailable")) { - downloadBtn->setText(tr("DOWNLOAD")); - downloadBtn->setValue(tr("update available")); - } else { - QString lastUpdate = tr("never"); - auto tm = params.get("LastUpdateTime"); - if (!tm.empty()) { - lastUpdate = timeAgo(QDateTime::fromString(QString::fromStdString(tm + "Z"), Qt::ISODate)); - } - downloadBtn->setText(tr("CHECK")); - downloadBtn->setValue(tr("up to date, last checked %1").arg(lastUpdate)); - } - downloadBtn->setEnabled(true); - } - targetBranchBtn->setValue(QString::fromStdString(params.get("UpdaterTargetBranch"))); - - // current + new versions - versionLbl->setText(QString::fromStdString(params.get("UpdaterCurrentDescription"))); - versionLbl->setDescription(QString::fromStdString(params.get("UpdaterCurrentReleaseNotes"))); - - installBtn->setVisible(!is_onroad && params.getBool("UpdateAvailable")); - installBtn->setValue(QString::fromStdString(params.get("UpdaterNewDescription"))); - installBtn->setDescription(QString::fromStdString(params.get("UpdaterNewReleaseNotes"))); - - update(); -} diff --git a/selfdrive/ui/qt/onroad/alerts.cc b/selfdrive/ui/qt/onroad/alerts.cc deleted file mode 100644 index 0236e20f1..000000000 --- a/selfdrive/ui/qt/onroad/alerts.cc +++ /dev/null @@ -1,112 +0,0 @@ -#include "selfdrive/ui/qt/onroad/alerts.h" - -#include -#include - -#include "selfdrive/ui/qt/util.h" - -void OnroadAlerts::updateState(const UIState &s) { - Alert a = getAlert(*(s.sm), s.scene.started_frame); - if (!alert.equal(a)) { - alert = a; - update(); - } -} - -void OnroadAlerts::clear() { - alert = {}; - update(); -} - -OnroadAlerts::Alert OnroadAlerts::getAlert(const SubMaster &sm, uint64_t started_frame) { - const cereal::SelfdriveState::Reader &ss = sm["selfdriveState"].getSelfdriveState(); - const uint64_t selfdrive_frame = sm.rcv_frame("selfdriveState"); - - Alert a = {}; - if (selfdrive_frame >= started_frame) { // Don't get old alert. - a = {ss.getAlertText1().cStr(), ss.getAlertText2().cStr(), - ss.getAlertType().cStr(), ss.getAlertSize(), ss.getAlertStatus()}; - } - - if (!sm.updated("selfdriveState") && (sm.frame - started_frame) > 5 * UI_FREQ) { - const int SELFDRIVE_STATE_TIMEOUT = 5; - const int ss_missing = (nanos_since_boot() - sm.rcv_time("selfdriveState")) / 1e9; - - // Handle selfdrive timeout - if (selfdrive_frame < started_frame) { - // car is started, but selfdriveState hasn't been seen at all - a = {tr("openpilot Unavailable"), tr("Waiting to start"), - "selfdriveWaiting", cereal::SelfdriveState::AlertSize::MID, - cereal::SelfdriveState::AlertStatus::NORMAL}; - } else if (ss_missing > SELFDRIVE_STATE_TIMEOUT && !Hardware::PC()) { - // car is started, but selfdrive is lagging or died - if (ss.getEnabled() && (ss_missing - SELFDRIVE_STATE_TIMEOUT) < 10) { - a = {tr("TAKE CONTROL IMMEDIATELY"), tr("System Unresponsive"), - "selfdriveUnresponsive", cereal::SelfdriveState::AlertSize::FULL, - cereal::SelfdriveState::AlertStatus::CRITICAL}; - } else { - a = {tr("System Unresponsive"), tr("Reboot Device"), - "selfdriveUnresponsivePermanent", cereal::SelfdriveState::AlertSize::MID, - cereal::SelfdriveState::AlertStatus::NORMAL}; - } - } - } - return a; -} - -void OnroadAlerts::paintEvent(QPaintEvent *event) { - if (alert.size == cereal::SelfdriveState::AlertSize::NONE) { - return; - } - static std::map alert_heights = { - {cereal::SelfdriveState::AlertSize::SMALL, 271}, - {cereal::SelfdriveState::AlertSize::MID, 420}, - {cereal::SelfdriveState::AlertSize::FULL, height()}, - }; - int h = alert_heights[alert.size]; - - int margin = 40; - int radius = 30; - if (alert.size == cereal::SelfdriveState::AlertSize::FULL) { - margin = 0; - radius = 0; - } - QRect r = QRect(0 + margin, height() - h + margin, width() - margin*2, h - margin*2); - - QPainter p(this); - - // draw background + gradient - p.setPen(Qt::NoPen); - p.setCompositionMode(QPainter::CompositionMode_SourceOver); - p.setBrush(QBrush(alert_colors[alert.status])); - p.drawRoundedRect(r, radius, radius); - - QLinearGradient g(0, r.y(), 0, r.bottom()); - g.setColorAt(0, QColor::fromRgbF(0, 0, 0, 0.05)); - g.setColorAt(1, QColor::fromRgbF(0, 0, 0, 0.35)); - - p.setCompositionMode(QPainter::CompositionMode_DestinationOver); - p.setBrush(QBrush(g)); - p.drawRoundedRect(r, radius, radius); - p.setCompositionMode(QPainter::CompositionMode_SourceOver); - - // text - const QPoint c = r.center(); - p.setPen(QColor(0xff, 0xff, 0xff)); - p.setRenderHint(QPainter::TextAntialiasing); - if (alert.size == cereal::SelfdriveState::AlertSize::SMALL) { - p.setFont(InterFont(74, QFont::DemiBold)); - p.drawText(r, Qt::AlignCenter, alert.text1); - } else if (alert.size == cereal::SelfdriveState::AlertSize::MID) { - p.setFont(InterFont(88, QFont::Bold)); - p.drawText(QRect(0, c.y() - 125, width(), 150), Qt::AlignHCenter | Qt::AlignTop, alert.text1); - p.setFont(InterFont(66)); - p.drawText(QRect(0, c.y() + 21, width(), 90), Qt::AlignHCenter, alert.text2); - } else if (alert.size == cereal::SelfdriveState::AlertSize::FULL) { - bool l = alert.text1.length() > 15; - p.setFont(InterFont(l ? 132 : 177, QFont::Bold)); - p.drawText(QRect(0, r.y() + (l ? 240 : 270), width(), 600), Qt::AlignHCenter | Qt::TextWordWrap, alert.text1); - p.setFont(InterFont(88)); - p.drawText(QRect(0, r.height() - (l ? 361 : 420), width(), 300), Qt::AlignHCenter | Qt::TextWordWrap, alert.text2); - } -} diff --git a/selfdrive/ui/qt/onroad/alerts.h b/selfdrive/ui/qt/onroad/alerts.h deleted file mode 100644 index de38d8ffc..000000000 --- a/selfdrive/ui/qt/onroad/alerts.h +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once - -#include - -#include "selfdrive/ui/ui.h" - -class OnroadAlerts : public QWidget { - Q_OBJECT - -public: - OnroadAlerts(QWidget *parent = 0) : QWidget(parent) {} - void updateState(const UIState &s); - void clear(); - -protected: - struct Alert { - QString text1; - QString text2; - QString type; - cereal::SelfdriveState::AlertSize size; - cereal::SelfdriveState::AlertStatus status; - - bool equal(const Alert &other) const { - return text1 == other.text1 && text2 == other.text2 && type == other.type; - } - }; - - const QMap alert_colors = { - {cereal::SelfdriveState::AlertStatus::NORMAL, QColor(0x15, 0x15, 0x15, 0xf1)}, - {cereal::SelfdriveState::AlertStatus::USER_PROMPT, QColor(0xDA, 0x6F, 0x25, 0xf1)}, - {cereal::SelfdriveState::AlertStatus::CRITICAL, QColor(0xC9, 0x22, 0x31, 0xf1)}, - }; - - void paintEvent(QPaintEvent*) override; - OnroadAlerts::Alert getAlert(const SubMaster &sm, uint64_t started_frame); - - QColor bg; - Alert alert = {}; -}; diff --git a/selfdrive/ui/qt/onroad/annotated_camera.cc b/selfdrive/ui/qt/onroad/annotated_camera.cc deleted file mode 100644 index f504ad69f..000000000 --- a/selfdrive/ui/qt/onroad/annotated_camera.cc +++ /dev/null @@ -1,157 +0,0 @@ - -#include "selfdrive/ui/qt/onroad/annotated_camera.h" - -#include -#include -#include - -#include "common/swaglog.h" -#include "selfdrive/ui/qt/util.h" - -// Window that shows camera view and variety of info drawn on top -AnnotatedCameraWidget::AnnotatedCameraWidget(VisionStreamType type, QWidget *parent) - : fps_filter(UI_FREQ, 3, 1. / UI_FREQ), CameraWidget("camerad", type, parent) { - pm = std::make_unique(std::vector{"uiDebug"}); - - main_layout = new QVBoxLayout(this); - main_layout->setMargin(UI_BORDER_SIZE); - main_layout->setSpacing(0); - - experimental_btn = new ExperimentalButton(this); - main_layout->addWidget(experimental_btn, 0, Qt::AlignTop | Qt::AlignRight); -} - -void AnnotatedCameraWidget::updateState(const UIState &s) { - // update engageability/experimental mode button - experimental_btn->updateState(s); - dmon.updateState(s); -} - -void AnnotatedCameraWidget::initializeGL() { - CameraWidget::initializeGL(); - qInfo() << "OpenGL version:" << QString((const char*)glGetString(GL_VERSION)); - qInfo() << "OpenGL vendor:" << QString((const char*)glGetString(GL_VENDOR)); - qInfo() << "OpenGL renderer:" << QString((const char*)glGetString(GL_RENDERER)); - qInfo() << "OpenGL language version:" << QString((const char*)glGetString(GL_SHADING_LANGUAGE_VERSION)); - - prev_draw_t = millis_since_boot(); - setBackgroundColor(bg_colors[STATUS_DISENGAGED]); -} - -mat4 AnnotatedCameraWidget::calcFrameMatrix() { - // Project point at "infinity" to compute x and y offsets - // to ensure this ends up in the middle of the screen - // for narrow come and a little lower for wide cam. - // TODO: use proper perspective transform? - - // Select intrinsic matrix and calibration based on camera type - auto *s = uiState(); - bool wide_cam = active_stream_type == VISION_STREAM_WIDE_ROAD; - const auto &intrinsic_matrix = wide_cam ? ECAM_INTRINSIC_MATRIX : FCAM_INTRINSIC_MATRIX; - const auto &calibration = wide_cam ? s->scene.view_from_wide_calib : s->scene.view_from_calib; - - // Compute the calibration transformation matrix - const auto calib_transform = intrinsic_matrix * calibration; - - float zoom = wide_cam ? 2.0 : 1.1; - Eigen::Vector3f inf(1000., 0., 0.); - auto Kep = calib_transform * inf; - - int w = width(), h = height(); - float center_x = intrinsic_matrix(0, 2); - float center_y = intrinsic_matrix(1, 2); - - float max_x_offset = center_x * zoom - w / 2 - 5; - float max_y_offset = center_y * zoom - h / 2 - 5; - float x_offset = std::clamp((Kep.x() / Kep.z() - center_x) * zoom, -max_x_offset, max_x_offset); - float y_offset = std::clamp((Kep.y() / Kep.z() - center_y) * zoom, -max_y_offset, max_y_offset); - - // Apply transformation such that video pixel coordinates match video - // 1) Put (0, 0) in the middle of the video - // 2) Apply same scaling as video - // 3) Put (0, 0) in top left corner of video - Eigen::Matrix3f video_transform =(Eigen::Matrix3f() << - zoom, 0.0f, (w / 2 - x_offset) - (center_x * zoom), - 0.0f, zoom, (h / 2 - y_offset) - (center_y * zoom), - 0.0f, 0.0f, 1.0f).finished(); - - model.setTransform(video_transform * calib_transform); - - float zx = zoom * 2 * center_x / w; - float zy = zoom * 2 * center_y / h; - return mat4{{ - zx, 0.0, 0.0, -x_offset / w * 2, - 0.0, zy, 0.0, y_offset / h * 2, - 0.0, 0.0, 1.0, 0.0, - 0.0, 0.0, 0.0, 1.0, - }}; -} - -void AnnotatedCameraWidget::paintGL() { - UIState *s = uiState(); - SubMaster &sm = *(s->sm); - const double start_draw_t = millis_since_boot(); - - // draw camera frame - { - std::lock_guard lk(frame_lock); - - if (frames.empty()) { - if (skip_frame_count > 0) { - skip_frame_count--; - qDebug() << "skipping frame, not ready"; - return; - } - } else { - // skip drawing up to this many frames if we're - // missing camera frames. this smooths out the - // transitions from the narrow and wide cameras - skip_frame_count = 5; - } - - // Wide or narrow cam dependent on speed - bool has_wide_cam = available_streams.count(VISION_STREAM_WIDE_ROAD); - if (has_wide_cam) { - float v_ego = sm["carState"].getCarState().getVEgo(); - if ((v_ego < 10) || available_streams.size() == 1) { - wide_cam_requested = true; - } else if (v_ego > 15) { - wide_cam_requested = false; - } - wide_cam_requested = wide_cam_requested && sm["selfdriveState"].getSelfdriveState().getExperimentalMode(); - } - CameraWidget::setStreamType(wide_cam_requested ? VISION_STREAM_WIDE_ROAD : VISION_STREAM_ROAD); - CameraWidget::setFrameId(sm["modelV2"].getModelV2().getFrameId()); - CameraWidget::paintGL(); - } - - QPainter painter(this); - painter.setRenderHint(QPainter::Antialiasing); - painter.setPen(Qt::NoPen); - - model.draw(painter, rect()); - dmon.draw(painter, rect()); - hud.updateState(*s); - hud.draw(painter, rect()); - - double cur_draw_t = millis_since_boot(); - double dt = cur_draw_t - prev_draw_t; - double fps = fps_filter.update(1. / dt * 1000); - if (fps < 15) { - LOGW("slow frame rate: %.2f fps", fps); - } - prev_draw_t = cur_draw_t; - - // publish debug msg - MessageBuilder msg; - auto m = msg.initEvent().initUiDebug(); - m.setDrawTimeMillis(cur_draw_t - start_draw_t); - pm->send("uiDebug", msg); -} - -void AnnotatedCameraWidget::showEvent(QShowEvent *event) { - CameraWidget::showEvent(event); - - ui_update_params(uiState()); - prev_draw_t = millis_since_boot(); -} diff --git a/selfdrive/ui/qt/onroad/annotated_camera.h b/selfdrive/ui/qt/onroad/annotated_camera.h deleted file mode 100644 index d205579f6..000000000 --- a/selfdrive/ui/qt/onroad/annotated_camera.h +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once - -#include -#include -#include "selfdrive/ui/qt/onroad/hud.h" -#include "selfdrive/ui/qt/onroad/buttons.h" -#include "selfdrive/ui/qt/onroad/driver_monitoring.h" -#include "selfdrive/ui/qt/onroad/model.h" -#include "selfdrive/ui/qt/widgets/cameraview.h" - -class AnnotatedCameraWidget : public CameraWidget { - Q_OBJECT - -public: - explicit AnnotatedCameraWidget(VisionStreamType type, QWidget* parent = 0); - void updateState(const UIState &s); - -private: - QVBoxLayout *main_layout; - ExperimentalButton *experimental_btn; - DriverMonitorRenderer dmon; - HudRenderer hud; - ModelRenderer model; - std::unique_ptr pm; - - int skip_frame_count = 0; - bool wide_cam_requested = false; - -protected: - void paintGL() override; - void initializeGL() override; - void showEvent(QShowEvent *event) override; - mat4 calcFrameMatrix() override; - - double prev_draw_t = 0; - FirstOrderFilter fps_filter; -}; diff --git a/selfdrive/ui/qt/onroad/buttons.cc b/selfdrive/ui/qt/onroad/buttons.cc deleted file mode 100644 index 32e58c9db..000000000 --- a/selfdrive/ui/qt/onroad/buttons.cc +++ /dev/null @@ -1,49 +0,0 @@ -#include "selfdrive/ui/qt/onroad/buttons.h" - -#include - -#include "selfdrive/ui/qt/util.h" - -void drawIcon(QPainter &p, const QPoint ¢er, const QPixmap &img, const QBrush &bg, float opacity) { - p.setRenderHint(QPainter::Antialiasing); - p.setOpacity(1.0); // bg dictates opacity of ellipse - p.setPen(Qt::NoPen); - p.setBrush(bg); - p.drawEllipse(center, btn_size / 2, btn_size / 2); - p.setOpacity(opacity); - p.drawPixmap(center - QPoint(img.width() / 2, img.height() / 2), img); - p.setOpacity(1.0); -} - -// ExperimentalButton -ExperimentalButton::ExperimentalButton(QWidget *parent) : experimental_mode(false), engageable(false), QPushButton(parent) { - setFixedSize(btn_size, btn_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); -} - -void ExperimentalButton::changeMode() { - const auto cp = (*uiState()->sm)["carParams"].getCarParams(); - bool can_change = hasLongitudinalControl(cp) && params.getBool("ExperimentalModeConfirmed"); - if (can_change) { - params.putBool("ExperimentalMode", !experimental_mode); - } -} - -void ExperimentalButton::updateState(const UIState &s) { - const auto cs = (*s.sm)["selfdriveState"].getSelfdriveState(); - bool eng = cs.getEngageable() || cs.getEnabled(); - if ((cs.getExperimentalMode() != experimental_mode) || (eng != engageable)) { - engageable = eng; - experimental_mode = cs.getExperimentalMode(); - update(); - } -} - -void ExperimentalButton::paintEvent(QPaintEvent *event) { - QPainter p(this); - QPixmap img = experimental_mode ? experimental_img : engage_img; - drawIcon(p, QPoint(btn_size / 2, btn_size / 2), img, QColor(0, 0, 0, 166), (isDown() || !engageable) ? 0.6 : 1.0); -} diff --git a/selfdrive/ui/qt/onroad/buttons.h b/selfdrive/ui/qt/onroad/buttons.h deleted file mode 100644 index 9c91bc3c7..000000000 --- a/selfdrive/ui/qt/onroad/buttons.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -#include - -#include "selfdrive/ui/ui.h" - -const int btn_size = 192; -const int img_size = (btn_size / 4) * 3; - -class ExperimentalButton : public QPushButton { - Q_OBJECT - -public: - explicit ExperimentalButton(QWidget *parent = 0); - void updateState(const UIState &s); - -private: - void paintEvent(QPaintEvent *event) override; - void changeMode(); - - Params params; - QPixmap engage_img; - QPixmap experimental_img; - bool experimental_mode; - bool engageable; -}; - -void drawIcon(QPainter &p, const QPoint ¢er, const QPixmap &img, const QBrush &bg, float opacity); diff --git a/selfdrive/ui/qt/onroad/driver_monitoring.cc b/selfdrive/ui/qt/onroad/driver_monitoring.cc deleted file mode 100644 index 49f2c950b..000000000 --- a/selfdrive/ui/qt/onroad/driver_monitoring.cc +++ /dev/null @@ -1,107 +0,0 @@ -#include "selfdrive/ui/qt/onroad/driver_monitoring.h" -#include -#include - -#include "selfdrive/ui/qt/onroad/buttons.h" -#include "selfdrive/ui/qt/util.h" - -// Default 3D coordinates for face keypoints -static constexpr vec3 DEFAULT_FACE_KPTS_3D[] = { - {-5.98, -51.20, 8.00}, {-17.64, -49.14, 8.00}, {-23.81, -46.40, 8.00}, {-29.98, -40.91, 8.00}, {-32.04, -37.49, 8.00}, - {-34.10, -32.00, 8.00}, {-36.16, -21.03, 8.00}, {-36.16, 6.40, 8.00}, {-35.47, 10.51, 8.00}, {-32.73, 19.43, 8.00}, - {-29.30, 26.29, 8.00}, {-24.50, 33.83, 8.00}, {-19.01, 41.37, 8.00}, {-14.21, 46.17, 8.00}, {-12.16, 47.54, 8.00}, - {-4.61, 49.60, 8.00}, {4.99, 49.60, 8.00}, {12.53, 47.54, 8.00}, {14.59, 46.17, 8.00}, {19.39, 41.37, 8.00}, - {24.87, 33.83, 8.00}, {29.67, 26.29, 8.00}, {33.10, 19.43, 8.00}, {35.84, 10.51, 8.00}, {36.53, 6.40, 8.00}, - {36.53, -21.03, 8.00}, {34.47, -32.00, 8.00}, {32.42, -37.49, 8.00}, {30.36, -40.91, 8.00}, {24.19, -46.40, 8.00}, - {18.02, -49.14, 8.00}, {6.36, -51.20, 8.00}, {-5.98, -51.20, 8.00}, -}; - -// Colors used for drawing based on monitoring state -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/icons/driver_face.png", {img_size + 5, img_size + 5}); -} - -void DriverMonitorRenderer::updateState(const UIState &s) { - auto &sm = *(s.sm); - is_visible = sm["selfdriveState"].getSelfdriveState().getAlertSize() == cereal::SelfdriveState::AlertSize::NONE && - sm.rcv_frame("driverStateV2") > s.scene.started_frame; - if (!is_visible) return; - - auto dm_state = sm["driverMonitoringState"].getDriverMonitoringState(); - is_active = dm_state.getIsActiveMode(); - is_rhd = dm_state.getIsRHD(); - dm_fade_state = std::clamp(dm_fade_state + 0.2f * (0.5f - is_active), 0.0f, 1.0f); - - const auto &driverstate = sm["driverStateV2"].getDriverStateV2(); - const auto driver_orient = is_rhd ? driverstate.getRightDriverData().getFaceOrientation() : driverstate.getLeftDriverData().getFaceOrientation(); - - for (int i = 0; i < 3; ++i) { - float v_this = (i == 0 ? (driver_orient[i] < 0 ? 0.7 : 0.9) : 0.4) * driver_orient[i]; - driver_pose_diff[i] = std::abs(driver_pose_vals[i] - v_this); - driver_pose_vals[i] = 0.8f * v_this + (1 - 0.8) * driver_pose_vals[i]; - driver_pose_sins[i] = std::sin(driver_pose_vals[i] * (1.0f - dm_fade_state)); - driver_pose_coss[i] = std::cos(driver_pose_vals[i] * (1.0f - dm_fade_state)); - } - - auto [sin_y, sin_x, sin_z] = driver_pose_sins; - auto [cos_y, cos_x, cos_z] = driver_pose_coss; - - // Rotation matrix for transforming face keypoints based on driver's head orientation - const mat3 r_xyz = {{ - cos_x * cos_z, cos_x * sin_z, -sin_x, - -sin_y * sin_x * cos_z - cos_y * sin_z, -sin_y * sin_x * sin_z + cos_y * cos_z, -sin_y * cos_x, - cos_y * sin_x * cos_z - sin_y * sin_z, cos_y * sin_x * sin_z + sin_y * cos_z, cos_y * cos_x, - }}; - - // Transform vertices - for (int i = 0; i < face_kpts_draw.size(); ++i) { - vec3 kpt = matvecmul3(r_xyz, DEFAULT_FACE_KPTS_3D[i]); - face_kpts_draw[i] = {{kpt.v[0], kpt.v[1], kpt.v[2] * (1.0f - dm_fade_state) + 8 * dm_fade_state}}; - } -} - -void DriverMonitorRenderer::draw(QPainter &painter, const QRect &surface_rect) { - if (!is_visible) return; - - painter.save(); - - int offset = UI_BORDER_SIZE + btn_size / 2; - float x = is_rhd ? surface_rect.width() - offset : offset; - float y = surface_rect.height() - offset; - float opacity = is_active ? 0.65f : 0.2f; - - drawIcon(painter, QPoint(x, y), dm_img, QColor(0, 0, 0, 70), opacity); - - QPointF keypoints[std::size(DEFAULT_FACE_KPTS_3D)]; - for (int i = 0; i < std::size(keypoints); ++i) { - const auto &v = face_kpts_draw[i].v; - float kp = (v[2] - 8) / 120.0f + 1.0f; - keypoints[i] = QPointF(v[0] * kp + x, v[1] * kp + y); - } - - painter.setPen(QPen(QColor::fromRgbF(1.0, 1.0, 1.0, opacity), 5.2, Qt::SolidLine, Qt::RoundCap)); - painter.drawPolyline(keypoints, std::size(keypoints)); - - // tracking arcs - const int arc_l = 133; - const float arc_t_default = 6.7f; - const float arc_t_extend = 12.0f; - QColor arc_color = uiState()->engaged() ? DMON_ENGAGED_COLOR : DMON_DISENGAGED_COLOR; - arc_color.setAlphaF(0.4 * (1.0f - dm_fade_state)); - - float delta_x = -driver_pose_sins[1] * arc_l / 2.0f; - float delta_y = -driver_pose_sins[0] * arc_l / 2.0f; - - // Draw horizontal tracking arc - painter.setPen(QPen(arc_color, arc_t_default + arc_t_extend * std::min(1.0, driver_pose_diff[1] * 5.0), Qt::SolidLine, Qt::RoundCap)); - painter.drawArc(QRectF(std::min(x + delta_x, x), y - arc_l / 2, std::abs(delta_x), arc_l), (driver_pose_sins[1] > 0 ? 90 : -90) * 16, 180 * 16); - - // Draw vertical tracking arc - painter.setPen(QPen(arc_color, arc_t_default + arc_t_extend * std::min(1.0, driver_pose_diff[0] * 5.0), Qt::SolidLine, Qt::RoundCap)); - painter.drawArc(QRectF(x - arc_l / 2, std::min(y + delta_y, y), arc_l, std::abs(delta_y)), (driver_pose_sins[0] > 0 ? 0 : 180) * 16, 180 * 16); - - painter.restore(); -} diff --git a/selfdrive/ui/qt/onroad/driver_monitoring.h b/selfdrive/ui/qt/onroad/driver_monitoring.h deleted file mode 100644 index 47db151c8..000000000 --- a/selfdrive/ui/qt/onroad/driver_monitoring.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#include -#include -#include "selfdrive/ui/ui.h" - -class DriverMonitorRenderer { -public: - DriverMonitorRenderer(); - void updateState(const UIState &s); - void draw(QPainter &painter, const QRect &surface_rect); - -private: - float driver_pose_vals[3] = {}; - float driver_pose_diff[3] = {}; - float driver_pose_sins[3] = {}; - float driver_pose_coss[3] = {}; - bool is_visible = false; - bool is_active = false; - bool is_rhd = false; - float dm_fade_state = 1.0; - QPixmap dm_img; - std::vector face_kpts_draw; -}; diff --git a/selfdrive/ui/qt/onroad/hud.cc b/selfdrive/ui/qt/onroad/hud.cc deleted file mode 100644 index 4cfa3d0e3..000000000 --- a/selfdrive/ui/qt/onroad/hud.cc +++ /dev/null @@ -1,112 +0,0 @@ -#include "selfdrive/ui/qt/onroad/hud.h" - -#include - -#include "selfdrive/ui/qt/util.h" - -constexpr int SET_SPEED_NA = 255; - -HudRenderer::HudRenderer() {} - -void HudRenderer::updateState(const UIState &s) { - is_metric = s.scene.is_metric; - status = s.status; - - const SubMaster &sm = *(s.sm); - if (sm.rcv_frame("carState") < s.scene.started_frame) { - is_cruise_set = false; - set_speed = SET_SPEED_NA; - speed = 0.0; - return; - } - - const auto &controls_state = sm["controlsState"].getControlsState(); - const auto &car_state = sm["carState"].getCarState(); - - // Handle older routes where vCruiseCluster is not set - set_speed = car_state.getVCruiseCluster() == 0.0 ? controls_state.getVCruiseDEPRECATED() : car_state.getVCruiseCluster(); - is_cruise_set = set_speed > 0 && set_speed != SET_SPEED_NA; - is_cruise_available = set_speed != -1; - - if (is_cruise_set && !is_metric) { - set_speed *= KM_TO_MILE; - } - - // Handle older routes where vEgoCluster is not set - v_ego_cluster_seen = v_ego_cluster_seen || car_state.getVEgoCluster() != 0.0; - float v_ego = v_ego_cluster_seen ? car_state.getVEgoCluster() : car_state.getVEgo(); - speed = std::max(0.0f, v_ego * (is_metric ? MS_TO_KPH : MS_TO_MPH)); -} - -void HudRenderer::draw(QPainter &p, const QRect &surface_rect) { - p.save(); - - // Draw header gradient - QLinearGradient bg(0, UI_HEADER_HEIGHT - (UI_HEADER_HEIGHT / 2.5), 0, UI_HEADER_HEIGHT); - bg.setColorAt(0, QColor::fromRgbF(0, 0, 0, 0.45)); - bg.setColorAt(1, QColor::fromRgbF(0, 0, 0, 0)); - p.fillRect(0, 0, surface_rect.width(), UI_HEADER_HEIGHT, bg); - - - if (is_cruise_available) { - drawSetSpeed(p, surface_rect); - } - drawCurrentSpeed(p, surface_rect); - - p.restore(); -} - -void HudRenderer::drawSetSpeed(QPainter &p, const QRect &surface_rect) { - // Draw outer box + border to contain set speed - const QSize default_size = {172, 204}; - QSize set_speed_size = is_metric ? QSize(200, 204) : default_size; - QRect set_speed_rect(QPoint(60 + (default_size.width() - set_speed_size.width()) / 2, 45), set_speed_size); - - // Draw set speed box - p.setPen(QPen(QColor(255, 255, 255, 75), 6)); - p.setBrush(QColor(0, 0, 0, 166)); - p.drawRoundedRect(set_speed_rect, 32, 32); - - // Colors based on status - QColor max_color = QColor(0xa6, 0xa6, 0xa6, 0xff); - QColor set_speed_color = QColor(0x72, 0x72, 0x72, 0xff); - if (is_cruise_set) { - set_speed_color = QColor(255, 255, 255); - if (status == STATUS_DISENGAGED) { - max_color = QColor(255, 255, 255); - } else if (status == STATUS_OVERRIDE) { - max_color = QColor(0x91, 0x9b, 0x95, 0xff); - } else { - max_color = QColor(0x80, 0xd8, 0xa6, 0xff); - } - } - - // Draw "MAX" text - p.setFont(InterFont(40, QFont::DemiBold)); - p.setPen(max_color); - p.drawText(set_speed_rect.adjusted(0, 27, 0, 0), Qt::AlignTop | Qt::AlignHCenter, tr("MAX")); - - // Draw set speed - QString setSpeedStr = is_cruise_set ? QString::number(std::nearbyint(set_speed)) : "–"; - p.setFont(InterFont(90, QFont::Bold)); - p.setPen(set_speed_color); - p.drawText(set_speed_rect.adjusted(0, 77, 0, 0), Qt::AlignTop | Qt::AlignHCenter, setSpeedStr); -} - -void HudRenderer::drawCurrentSpeed(QPainter &p, const QRect &surface_rect) { - QString speedStr = QString::number(std::nearbyint(speed)); - - p.setFont(InterFont(176, QFont::Bold)); - drawText(p, surface_rect.center().x(), 210, speedStr); - - p.setFont(InterFont(66)); - drawText(p, surface_rect.center().x(), 290, is_metric ? tr("km/h") : tr("mph"), 200); -} - -void HudRenderer::drawText(QPainter &p, int x, int y, const QString &text, int alpha) { - QRect real_rect = p.fontMetrics().boundingRect(text); - real_rect.moveCenter({x, y - real_rect.height() / 2}); - - p.setPen(QColor(0xff, 0xff, 0xff, alpha)); - p.drawText(real_rect.x(), real_rect.bottom(), text); -} diff --git a/selfdrive/ui/qt/onroad/hud.h b/selfdrive/ui/qt/onroad/hud.h deleted file mode 100644 index b2ac379db..000000000 --- a/selfdrive/ui/qt/onroad/hud.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -#include -#include "selfdrive/ui/ui.h" - -class HudRenderer : public QObject { - Q_OBJECT - -public: - HudRenderer(); - void updateState(const UIState &s); - void draw(QPainter &p, const QRect &surface_rect); - -private: - void drawSetSpeed(QPainter &p, const QRect &surface_rect); - void drawCurrentSpeed(QPainter &p, const QRect &surface_rect); - void drawText(QPainter &p, int x, int y, const QString &text, int alpha = 255); - - float speed = 0; - float set_speed = 0; - bool is_cruise_set = false; - bool is_cruise_available = true; - bool is_metric = false; - bool v_ego_cluster_seen = false; - int status = STATUS_DISENGAGED; -}; diff --git a/selfdrive/ui/qt/onroad/model.cc b/selfdrive/ui/qt/onroad/model.cc deleted file mode 100644 index 52902abdc..000000000 --- a/selfdrive/ui/qt/onroad/model.cc +++ /dev/null @@ -1,250 +0,0 @@ -#include "selfdrive/ui/qt/onroad/model.h" - -constexpr int CLIP_MARGIN = 500; -constexpr float MIN_DRAW_DISTANCE = 10.0; -constexpr float MAX_DRAW_DISTANCE = 100.0; - -static int get_path_length_idx(const cereal::XYZTData::Reader &line, const float path_height) { - const auto &line_x = line.getX(); - int max_idx = 0; - for (int i = 1; i < line_x.size() && line_x[i] <= path_height; ++i) { - max_idx = i; - } - return max_idx; -} - -void ModelRenderer::draw(QPainter &painter, const QRect &surface_rect) { - auto *s = uiState(); - auto &sm = *(s->sm); - // Check if data is up-to-date - if (sm.rcv_frame("liveCalibration") < s->scene.started_frame || - sm.rcv_frame("modelV2") < s->scene.started_frame) { - return; - } - - clip_region = surface_rect.adjusted(-CLIP_MARGIN, -CLIP_MARGIN, CLIP_MARGIN, CLIP_MARGIN); - experimental_mode = sm["selfdriveState"].getSelfdriveState().getExperimentalMode(); - longitudinal_control = sm["carParams"].getCarParams().getOpenpilotLongitudinalControl(); - path_offset_z = sm["liveCalibration"].getLiveCalibration().getHeight()[0]; - - painter.save(); - - const auto &model = sm["modelV2"].getModelV2(); - const auto &radar_state = sm["radarState"].getRadarState(); - const auto &lead_one = radar_state.getLeadOne(); - - update_model(model, lead_one); - drawLaneLines(painter); - drawPath(painter, model, surface_rect.height()); - - if (longitudinal_control && sm.alive("radarState")) { - update_leads(radar_state, model.getPosition()); - const auto &lead_two = radar_state.getLeadTwo(); - if (lead_one.getStatus()) { - drawLead(painter, lead_one, lead_vertices[0], surface_rect); - } - if (lead_two.getStatus() && (std::abs(lead_one.getDRel() - lead_two.getDRel()) > 3.0)) { - drawLead(painter, lead_two, lead_vertices[1], surface_rect); - } - } - - painter.restore(); -} - -void ModelRenderer::update_leads(const cereal::RadarState::Reader &radar_state, const cereal::XYZTData::Reader &line) { - for (int i = 0; i < 2; ++i) { - const auto &lead_data = (i == 0) ? radar_state.getLeadOne() : radar_state.getLeadTwo(); - if (lead_data.getStatus()) { - float z = line.getZ()[get_path_length_idx(line, lead_data.getDRel())]; - mapToScreen(lead_data.getDRel(), -lead_data.getYRel(), z + path_offset_z, &lead_vertices[i]); - } - } -} - -void ModelRenderer::update_model(const cereal::ModelDataV2::Reader &model, const cereal::RadarState::LeadData::Reader &lead) { - const auto &model_position = model.getPosition(); - float max_distance = std::clamp(*(model_position.getX().end() - 1), MIN_DRAW_DISTANCE, MAX_DRAW_DISTANCE); - - // update lane lines - const auto &lane_lines = model.getLaneLines(); - const auto &line_probs = model.getLaneLineProbs(); - int max_idx = get_path_length_idx(lane_lines[0], max_distance); - for (int i = 0; i < std::size(lane_line_vertices); i++) { - lane_line_probs[i] = line_probs[i]; - mapLineToPolygon(lane_lines[i], 0.025 * lane_line_probs[i], 0, &lane_line_vertices[i], max_idx); - } - - // update road edges - const auto &road_edges = model.getRoadEdges(); - const auto &edge_stds = model.getRoadEdgeStds(); - for (int i = 0; i < std::size(road_edge_vertices); i++) { - road_edge_stds[i] = edge_stds[i]; - mapLineToPolygon(road_edges[i], 0.025, 0, &road_edge_vertices[i], max_idx); - } - - // update path - if (lead.getStatus()) { - const float lead_d = lead.getDRel() * 2.; - max_distance = std::clamp((float)(lead_d - fmin(lead_d * 0.35, 10.)), 0.0f, max_distance); - } - max_idx = get_path_length_idx(model_position, max_distance); - mapLineToPolygon(model_position, 0.9, path_offset_z, &track_vertices, max_idx, false); -} - -void ModelRenderer::drawLaneLines(QPainter &painter) { - // lanelines - for (int i = 0; i < std::size(lane_line_vertices); ++i) { - painter.setBrush(QColor::fromRgbF(1.0, 1.0, 1.0, std::clamp(lane_line_probs[i], 0.0, 0.7))); - painter.drawPolygon(lane_line_vertices[i]); - } - - // road edges - for (int i = 0; i < std::size(road_edge_vertices); ++i) { - painter.setBrush(QColor::fromRgbF(1.0, 0, 0, std::clamp(1.0 - road_edge_stds[i], 0.0, 1.0))); - painter.drawPolygon(road_edge_vertices[i]); - } -} - -void ModelRenderer::drawPath(QPainter &painter, const cereal::ModelDataV2::Reader &model, int height) { - QLinearGradient bg(0, height, 0, 0); - if (experimental_mode) { - // The first half of track_vertices are the points for the right side of the path - const auto &acceleration = model.getAcceleration().getX(); - const int max_len = std::min(track_vertices.length() / 2, acceleration.size()); - - for (int i = 0; i < max_len; ++i) { - // Some points are out of frame - int track_idx = max_len - i - 1; // flip idx to start from bottom right - if (track_vertices[track_idx].y() < 0 || track_vertices[track_idx].y() > height) continue; - - // Flip so 0 is bottom of frame - float lin_grad_point = (height - track_vertices[track_idx].y()) / height; - - // speed up: 120, slow down: 0 - float path_hue = fmax(fmin(60 + acceleration[i] * 35, 120), 0); - // FIXME: painter.drawPolygon can be slow if hue is not rounded - path_hue = int(path_hue * 100 + 0.5) / 100; - - float saturation = fmin(fabs(acceleration[i] * 1.5), 1); - float lightness = util::map_val(saturation, 0.0f, 1.0f, 0.95f, 0.62f); // lighter when grey - float alpha = util::map_val(lin_grad_point, 0.75f / 2.f, 0.75f, 0.4f, 0.0f); // matches previous alpha fade - bg.setColorAt(lin_grad_point, QColor::fromHslF(path_hue / 360., saturation, lightness, alpha)); - - // Skip a point, unless next is last - i += (i + 2) < max_len ? 1 : 0; - } - - } else { - updatePathGradient(bg); - } - - painter.setBrush(bg); - painter.drawPolygon(track_vertices); -} - -void ModelRenderer::updatePathGradient(QLinearGradient &bg) { - static const QColor throttle_colors[] = { - QColor::fromHslF(148. / 360., 0.94, 0.51, 0.4), - QColor::fromHslF(112. / 360., 1.0, 0.68, 0.35), - QColor::fromHslF(112. / 360., 1.0, 0.68, 0.0)}; - - static const QColor no_throttle_colors[] = { - QColor::fromHslF(148. / 360., 0.0, 0.95, 0.4), - QColor::fromHslF(112. / 360., 0.0, 0.95, 0.35), - QColor::fromHslF(112. / 360., 0.0, 0.95, 0.0), - }; - - // Transition speed; 0.1 corresponds to 0.5 seconds at UI_FREQ - constexpr float transition_speed = 0.1f; - - // Start transition if throttle state changes - bool allow_throttle = (*uiState()->sm)["longitudinalPlan"].getLongitudinalPlan().getAllowThrottle() || !longitudinal_control; - if (allow_throttle != prev_allow_throttle) { - prev_allow_throttle = allow_throttle; - // Invert blend factor for a smooth transition when the state changes mid-animation - blend_factor = std::max(1.0f - blend_factor, 0.0f); - } - - const QColor *begin_colors = allow_throttle ? no_throttle_colors : throttle_colors; - const QColor *end_colors = allow_throttle ? throttle_colors : no_throttle_colors; - if (blend_factor < 1.0f) { - blend_factor = std::min(blend_factor + transition_speed, 1.0f); - } - - // Set gradient colors by blending the start and end colors - bg.setColorAt(0.0f, blendColors(begin_colors[0], end_colors[0], blend_factor)); - bg.setColorAt(0.5f, blendColors(begin_colors[1], end_colors[1], blend_factor)); - bg.setColorAt(1.0f, blendColors(begin_colors[2], end_colors[2], blend_factor)); -} - -QColor ModelRenderer::blendColors(const QColor &start, const QColor &end, float t) { - if (t == 1.0f) return end; - return QColor::fromRgbF( - (1 - t) * start.redF() + t * end.redF(), - (1 - t) * start.greenF() + t * end.greenF(), - (1 - t) * start.blueF() + t * end.blueF(), - (1 - t) * start.alphaF() + t * end.alphaF()); -} - -void ModelRenderer::drawLead(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data, - const QPointF &vd, const QRect &surface_rect) { - const float speedBuff = 10.; - const float leadBuff = 40.; - const float d_rel = lead_data.getDRel(); - const float v_rel = lead_data.getVRel(); - - float fillAlpha = 0; - if (d_rel < leadBuff) { - fillAlpha = 255 * (1.0 - (d_rel / leadBuff)); - if (v_rel < 0) { - fillAlpha += 255 * (-1 * (v_rel / speedBuff)); - } - fillAlpha = (int)(fmin(fillAlpha, 255)); - } - - float sz = std::clamp((25 * 30) / (d_rel / 3 + 30), 15.0f, 30.0f) * 2.35; - float x = std::clamp(vd.x(), 0.f, surface_rect.width() - sz / 2); - float y = std::min(vd.y(), surface_rect.height() - sz * 0.6); - - float g_xo = sz / 5; - float g_yo = sz / 10; - - QPointF glow[] = {{x + (sz * 1.35) + g_xo, y + sz + g_yo}, {x, y - g_yo}, {x - (sz * 1.35) - g_xo, y + sz + g_yo}}; - painter.setBrush(QColor(218, 202, 37, 255)); - painter.drawPolygon(glow, std::size(glow)); - - // chevron - QPointF chevron[] = {{x + (sz * 1.25), y + sz}, {x, y}, {x - (sz * 1.25), y + sz}}; - painter.setBrush(QColor(201, 34, 49, fillAlpha)); - painter.drawPolygon(chevron, std::size(chevron)); -} - -// Projects a point in car to space to the corresponding point in full frame image space. -bool ModelRenderer::mapToScreen(float in_x, float in_y, float in_z, QPointF *out) { - Eigen::Vector3f input(in_x, in_y, in_z); - auto pt = car_space_transform * input; - *out = QPointF(pt.x() / pt.z(), pt.y() / pt.z()); - return clip_region.contains(*out); -} - -void ModelRenderer::mapLineToPolygon(const cereal::XYZTData::Reader &line, float y_off, float z_off, - QPolygonF *pvd, int max_idx, bool allow_invert) { - const auto line_x = line.getX(), line_y = line.getY(), line_z = line.getZ(); - QPointF left, right; - pvd->clear(); - for (int i = 0; i <= max_idx; i++) { - // highly negative x positions are drawn above the frame and cause flickering, clip to zy plane of camera - if (line_x[i] < 0) continue; - - bool l = mapToScreen(line_x[i], line_y[i] - y_off, line_z[i] + z_off, &left); - bool r = mapToScreen(line_x[i], line_y[i] + y_off, line_z[i] + z_off, &right); - if (l && r) { - // For wider lines the drawn polygon will "invert" when going over a hill and cause artifacts - if (!allow_invert && pvd->size() && left.y() > pvd->back().y()) { - continue; - } - pvd->push_back(left); - pvd->push_front(right); - } - } -} diff --git a/selfdrive/ui/qt/onroad/model.h b/selfdrive/ui/qt/onroad/model.h deleted file mode 100644 index 79547e4b8..000000000 --- a/selfdrive/ui/qt/onroad/model.h +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once - -#include -#include - -#include "selfdrive/ui/ui.h" - -class ModelRenderer { -public: - ModelRenderer() {} - void setTransform(const Eigen::Matrix3f &transform) { car_space_transform = transform; } - void draw(QPainter &painter, const QRect &surface_rect); - -private: - bool mapToScreen(float in_x, float in_y, float in_z, QPointF *out); - void mapLineToPolygon(const cereal::XYZTData::Reader &line, float y_off, float z_off, - QPolygonF *pvd, int max_idx, bool allow_invert = true); - void drawLead(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data, const QPointF &vd, const QRect &surface_rect); - void update_leads(const cereal::RadarState::Reader &radar_state, const cereal::XYZTData::Reader &line); - void update_model(const cereal::ModelDataV2::Reader &model, const cereal::RadarState::LeadData::Reader &lead); - void drawLaneLines(QPainter &painter); - void drawPath(QPainter &painter, const cereal::ModelDataV2::Reader &model, int height); - void updatePathGradient(QLinearGradient &bg); - QColor blendColors(const QColor &start, const QColor &end, float t); - - bool longitudinal_control = false; - bool experimental_mode = false; - float blend_factor = 1.0f; - bool prev_allow_throttle = true; - float lane_line_probs[4] = {}; - float road_edge_stds[2] = {}; - float path_offset_z = 1.22f; - QPolygonF track_vertices; - QPolygonF lane_line_vertices[4] = {}; - QPolygonF road_edge_vertices[2] = {}; - QPointF lead_vertices[2] = {}; - Eigen::Matrix3f car_space_transform = Eigen::Matrix3f::Zero(); - QRectF clip_region; -}; diff --git a/selfdrive/ui/qt/onroad/onroad_home.cc b/selfdrive/ui/qt/onroad/onroad_home.cc deleted file mode 100644 index 080f9bd50..000000000 --- a/selfdrive/ui/qt/onroad/onroad_home.cc +++ /dev/null @@ -1,65 +0,0 @@ -#include "selfdrive/ui/qt/onroad/onroad_home.h" - -#include -#include - -#include "selfdrive/ui/qt/util.h" - -OnroadWindow::OnroadWindow(QWidget *parent) : QWidget(parent) { - QVBoxLayout *main_layout = new QVBoxLayout(this); - main_layout->setMargin(UI_BORDER_SIZE); - QStackedLayout *stacked_layout = new QStackedLayout; - stacked_layout->setStackingMode(QStackedLayout::StackAll); - main_layout->addLayout(stacked_layout); - - nvg = new AnnotatedCameraWidget(VISION_STREAM_ROAD, this); - - QWidget * split_wrapper = new QWidget; - split = new QHBoxLayout(split_wrapper); - split->setContentsMargins(0, 0, 0, 0); - split->setSpacing(0); - split->addWidget(nvg); - - if (getenv("DUAL_CAMERA_VIEW")) { - CameraWidget *arCam = new CameraWidget("camerad", VISION_STREAM_ROAD, this); - split->insertWidget(0, arCam); - } - - stacked_layout->addWidget(split_wrapper); - - alerts = new OnroadAlerts(this); - alerts->setAttribute(Qt::WA_TransparentForMouseEvents, true); - stacked_layout->addWidget(alerts); - - // setup stacking order - alerts->raise(); - - setAttribute(Qt::WA_OpaquePaintEvent); - QObject::connect(uiState(), &UIState::uiUpdate, this, &OnroadWindow::updateState); - QObject::connect(uiState(), &UIState::offroadTransition, this, &OnroadWindow::offroadTransition); -} - -void OnroadWindow::updateState(const UIState &s) { - if (!s.scene.started) { - return; - } - - alerts->updateState(s); - nvg->updateState(s); - - QColor bgColor = bg_colors[s.status]; - if (bg != bgColor) { - // repaint border - bg = bgColor; - update(); - } -} - -void OnroadWindow::offroadTransition(bool offroad) { - alerts->clear(); -} - -void OnroadWindow::paintEvent(QPaintEvent *event) { - QPainter p(this); - p.fillRect(rect(), QColor(bg.red(), bg.green(), bg.blue(), 255)); -} diff --git a/selfdrive/ui/qt/onroad/onroad_home.h b/selfdrive/ui/qt/onroad/onroad_home.h deleted file mode 100644 index c321d2d44..000000000 --- a/selfdrive/ui/qt/onroad/onroad_home.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include "selfdrive/ui/qt/onroad/alerts.h" -#include "selfdrive/ui/qt/onroad/annotated_camera.h" - -class OnroadWindow : public QWidget { - Q_OBJECT - -public: - OnroadWindow(QWidget* parent = 0); - -private: - void paintEvent(QPaintEvent *event); - OnroadAlerts *alerts; - AnnotatedCameraWidget *nvg; - QColor bg = bg_colors[STATUS_DISENGAGED]; - QHBoxLayout* split; - -private slots: - void offroadTransition(bool offroad); - void updateState(const UIState &s); -}; diff --git a/selfdrive/ui/qt/prime_state.cc b/selfdrive/ui/qt/prime_state.cc deleted file mode 100644 index f12daf1e3..000000000 --- a/selfdrive/ui/qt/prime_state.cc +++ /dev/null @@ -1,48 +0,0 @@ -#include "selfdrive/ui/qt/prime_state.h" - -#include - -#include "selfdrive/ui/qt/api.h" -#include "selfdrive/ui/qt/request_repeater.h" -#include "selfdrive/ui/qt/util.h" - -PrimeState::PrimeState(QObject* parent) : QObject(parent) { - const char *env_prime_type = std::getenv("PRIME_TYPE"); - auto type = env_prime_type ? env_prime_type : Params().get("PrimeType"); - - if (!type.empty()) { - prime_type = static_cast(std::atoi(type.c_str())); - } - - if (auto dongleId = getDongleId()) { - QString url = CommaApi::BASE_URL + "/v1.1/devices/" + *dongleId + "/"; - RequestRepeater* repeater = new RequestRepeater(this, url, "ApiCache_Device", 5); - QObject::connect(repeater, &RequestRepeater::requestDone, this, &PrimeState::handleReply); - } - - // Emit the initial state change - QTimer::singleShot(1, [this]() { emit changed(prime_type); }); -} - -void PrimeState::handleReply(const QString& response, bool success) { - if (!success) return; - - QJsonDocument doc = QJsonDocument::fromJson(response.toUtf8()); - if (doc.isNull()) { - qDebug() << "JSON Parse failed on getting pairing and PrimeState status"; - return; - } - - QJsonObject json = doc.object(); - bool is_paired = json["is_paired"].toBool(); - auto type = static_cast(json["prime_type"].toInt()); - setType(is_paired ? type : PrimeState::PRIME_TYPE_UNPAIRED); -} - -void PrimeState::setType(PrimeState::Type type) { - if (type != prime_type) { - prime_type = type; - Params().put("PrimeType", std::to_string(prime_type)); - emit changed(prime_type); - } -} diff --git a/selfdrive/ui/qt/prime_state.h b/selfdrive/ui/qt/prime_state.h deleted file mode 100644 index 0e2e3bb04..000000000 --- a/selfdrive/ui/qt/prime_state.h +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once - -#include - -class PrimeState : public QObject { - Q_OBJECT - -public: - - enum Type { - PRIME_TYPE_UNKNOWN = -2, - PRIME_TYPE_UNPAIRED = -1, - PRIME_TYPE_NONE = 0, - PRIME_TYPE_MAGENTA = 1, - PRIME_TYPE_LITE = 2, - PRIME_TYPE_BLUE = 3, - PRIME_TYPE_MAGENTA_NEW = 4, - PRIME_TYPE_PURPLE = 5, - }; - - PrimeState(QObject *parent); - void setType(PrimeState::Type type); - inline PrimeState::Type currentType() const { return prime_type; } - inline bool isSubscribed() const { return prime_type > PrimeState::PRIME_TYPE_NONE; } - -signals: - void changed(PrimeState::Type prime_type); - -private: - void handleReply(const QString &response, bool success); - - PrimeState::Type prime_type = PrimeState::PRIME_TYPE_UNKNOWN; -}; diff --git a/selfdrive/ui/qt/qt_window.cc b/selfdrive/ui/qt/qt_window.cc deleted file mode 100644 index cdd817ae2..000000000 --- a/selfdrive/ui/qt/qt_window.cc +++ /dev/null @@ -1,36 +0,0 @@ -#include "selfdrive/ui/qt/qt_window.h" - -void setMainWindow(QWidget *w) { - const float scale = util::getenv("SCALE", 1.0f); - const QSize sz = QGuiApplication::primaryScreen()->size(); - - if (Hardware::PC() && scale == 1.0 && !(sz - DEVICE_SCREEN_SIZE).isValid()) { - w->setMinimumSize(QSize(640, 480)); // allow resize smaller than fullscreen - w->setMaximumSize(DEVICE_SCREEN_SIZE); - w->resize(sz); - } else { - w->setFixedSize(DEVICE_SCREEN_SIZE * scale); - } - w->show(); - -#ifdef __TICI__ - QPlatformNativeInterface *native = QGuiApplication::platformNativeInterface(); - wl_surface *s = reinterpret_cast(native->nativeResourceForWindow("surface", w->windowHandle())); - wl_surface_set_buffer_transform(s, WL_OUTPUT_TRANSFORM_270); - wl_surface_commit(s); - - w->setWindowState(Qt::WindowFullScreen); - w->setVisible(true); - - // ensure we have a valid eglDisplay, otherwise the ui will silently fail - void *egl = native->nativeResourceForWindow("egldisplay", w->windowHandle()); - assert(egl != nullptr); -#endif -} - - -extern "C" { - void set_main_window(void *w) { - setMainWindow((QWidget*)w); - } -} diff --git a/selfdrive/ui/qt/qt_window.h b/selfdrive/ui/qt/qt_window.h deleted file mode 100644 index b9783477e..000000000 --- a/selfdrive/ui/qt/qt_window.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include - -#include -#include -#include - -#ifdef __TICI__ -#include -#include -#include -#endif - -#include "system/hardware/hw.h" - -const QString ASSET_PATH = ":/"; -const QSize DEVICE_SCREEN_SIZE = {2160, 1080}; - -void setMainWindow(QWidget *w); diff --git a/selfdrive/ui/qt/request_repeater.cc b/selfdrive/ui/qt/request_repeater.cc deleted file mode 100644 index 7aa731898..000000000 --- a/selfdrive/ui/qt/request_repeater.cc +++ /dev/null @@ -1,27 +0,0 @@ -#include "selfdrive/ui/qt/request_repeater.h" - -RequestRepeater::RequestRepeater(QObject *parent, const QString &requestURL, const QString &cacheKey, - int period, bool while_onroad) : HttpRequest(parent) { - timer = new QTimer(this); - timer->setTimerType(Qt::VeryCoarseTimer); - QObject::connect(timer, &QTimer::timeout, [=]() { - if ((!uiState()->scene.started || while_onroad) && device()->isAwake() && !active()) { - sendRequest(requestURL); - } - }); - - timer->start(period * 1000); - - if (!cacheKey.isEmpty()) { - prevResp = QString::fromStdString(params.get(cacheKey.toStdString())); - if (!prevResp.isEmpty()) { - QTimer::singleShot(500, [=]() { emit requestDone(prevResp, true, QNetworkReply::NoError); }); - } - QObject::connect(this, &HttpRequest::requestDone, [=](const QString &resp, bool success) { - if (success && resp != prevResp) { - params.put(cacheKey.toStdString(), resp.toStdString()); - prevResp = resp; - } - }); - } -} diff --git a/selfdrive/ui/qt/request_repeater.h b/selfdrive/ui/qt/request_repeater.h deleted file mode 100644 index c0e275827..000000000 --- a/selfdrive/ui/qt/request_repeater.h +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once - -#include "common/util.h" -#include "selfdrive/ui/qt/api.h" -#include "selfdrive/ui/ui.h" - -class RequestRepeater : public HttpRequest { -public: - RequestRepeater(QObject *parent, const QString &requestURL, const QString &cacheKey = "", int period = 0, bool while_onroad=false); - -private: - Params params; - QTimer *timer; - QString prevResp; -}; diff --git a/selfdrive/ui/qt/sidebar.cc b/selfdrive/ui/qt/sidebar.cc deleted file mode 100644 index e9c6a2f7c..000000000 --- a/selfdrive/ui/qt/sidebar.cc +++ /dev/null @@ -1,165 +0,0 @@ -#include "selfdrive/ui/qt/sidebar.h" - -#include - -#include "selfdrive/ui/qt/util.h" - -void Sidebar::drawMetric(QPainter &p, const QPair &label, QColor c, int y) { - const QRect rect = {30, y, 240, 126}; - - p.setPen(Qt::NoPen); - p.setBrush(QBrush(c)); - p.setClipRect(rect.x() + 4, rect.y(), 18, rect.height(), Qt::ClipOperation::ReplaceClip); - p.drawRoundedRect(QRect(rect.x() + 4, rect.y() + 4, 100, 118), 18, 18); - p.setClipping(false); - - QPen pen = QPen(QColor(0xff, 0xff, 0xff, 0x55)); - pen.setWidth(2); - p.setPen(pen); - p.setBrush(Qt::NoBrush); - p.drawRoundedRect(rect, 20, 20); - - p.setPen(QColor(0xff, 0xff, 0xff)); - p.setFont(InterFont(35, QFont::DemiBold)); - p.drawText(rect.adjusted(22, 0, 0, 0), Qt::AlignCenter, label.first + "\n" + label.second); -} - -Sidebar::Sidebar(QWidget *parent) : QFrame(parent), onroad(false), flag_pressed(false), settings_pressed(false), mic_indicator_pressed(false) { - home_img = loadPixmap("../assets/images/button_home.png", home_btn.size()); - flag_img = loadPixmap("../assets/images/button_flag.png", home_btn.size()); - settings_img = loadPixmap("../assets/images/button_settings.png", settings_btn.size(), Qt::IgnoreAspectRatio); - mic_img = loadPixmap("../assets/icons/microphone.png", QSize(30, 30)); - link_img = loadPixmap("../assets/icons/link.png", QSize(60, 60)); - - connect(this, &Sidebar::valueChanged, [=] { update(); }); - - setAttribute(Qt::WA_OpaquePaintEvent); - setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); - setFixedWidth(300); - - QObject::connect(uiState(), &UIState::uiUpdate, this, &Sidebar::updateState); - - pm = std::make_unique(std::vector{"bookmarkButton"}); -} - -void Sidebar::mousePressEvent(QMouseEvent *event) { - if (onroad && home_btn.contains(event->pos())) { - flag_pressed = true; - update(); - } else if (settings_btn.contains(event->pos())) { - settings_pressed = true; - update(); - } else if (recording_audio && mic_indicator_btn.contains(event->pos())) { - mic_indicator_pressed = true; - update(); - } -} - -void Sidebar::mouseReleaseEvent(QMouseEvent *event) { - if (flag_pressed || settings_pressed || mic_indicator_pressed) { - flag_pressed = settings_pressed = mic_indicator_pressed = false; - update(); - } - if (onroad && home_btn.contains(event->pos())) { - MessageBuilder msg; - msg.initEvent().initBookmarkButton(); - pm->send("bookmarkButton", msg); - } else if (settings_btn.contains(event->pos())) { - emit openSettings(); - } else if (recording_audio && mic_indicator_btn.contains(event->pos())) { - emit openSettings(2, "RecordAudio"); - } -} - -void Sidebar::offroadTransition(bool offroad) { - onroad = !offroad; - update(); -} - -void Sidebar::updateState(const UIState &s) { - if (!isVisible()) return; - - auto &sm = *(s.sm); - - networking = networking ? networking : window()->findChild(""); - bool tethering_on = networking && networking->wifi->tethering_on; - auto deviceState = sm["deviceState"].getDeviceState(); - setProperty("netType", tethering_on ? "Hotspot": network_type[deviceState.getNetworkType()]); - int strength = tethering_on ? 4 : (int)deviceState.getNetworkStrength(); - setProperty("netStrength", strength > 0 ? strength + 1 : 0); - - ItemStatus connectStatus; - auto last_ping = deviceState.getLastAthenaPingTime(); - if (last_ping == 0) { - connectStatus = ItemStatus{{tr("CONNECT"), tr("OFFLINE")}, warning_color}; - } else { - connectStatus = nanos_since_boot() - last_ping < 80e9 - ? ItemStatus{{tr("CONNECT"), tr("ONLINE")}, good_color} - : ItemStatus{{tr("CONNECT"), tr("ERROR")}, danger_color}; - } - setProperty("connectStatus", QVariant::fromValue(connectStatus)); - - ItemStatus tempStatus = {{tr("TEMP"), tr("HIGH")}, danger_color}; - auto ts = deviceState.getThermalStatus(); - if (ts == cereal::DeviceState::ThermalStatus::GREEN) { - tempStatus = {{tr("TEMP"), tr("GOOD")}, good_color}; - } else if (ts == cereal::DeviceState::ThermalStatus::YELLOW) { - tempStatus = {{tr("TEMP"), tr("OK")}, warning_color}; - } - setProperty("tempStatus", QVariant::fromValue(tempStatus)); - - ItemStatus pandaStatus = {{tr("VEHICLE"), tr("ONLINE")}, good_color}; - if (s.scene.pandaType == cereal::PandaState::PandaType::UNKNOWN) { - pandaStatus = {{tr("NO"), tr("PANDA")}, danger_color}; - } - setProperty("pandaStatus", QVariant::fromValue(pandaStatus)); - - setProperty("recordingAudio", s.scene.recording_audio); -} - -void Sidebar::paintEvent(QPaintEvent *event) { - QPainter p(this); - p.setPen(Qt::NoPen); - p.setRenderHint(QPainter::Antialiasing); - - p.fillRect(rect(), QColor(57, 57, 57)); - - // buttons - p.setOpacity(settings_pressed ? 0.65 : 1.0); - p.drawPixmap(settings_btn.x(), settings_btn.y(), settings_img); - p.setOpacity(onroad && flag_pressed ? 0.65 : 1.0); - p.drawPixmap(home_btn.x(), home_btn.y(), onroad ? flag_img : home_img); - if (recording_audio) { - p.setBrush(danger_color); - p.setOpacity(mic_indicator_pressed ? 0.65 : 1.0); - p.drawRoundedRect(mic_indicator_btn, mic_indicator_btn.height() / 2, mic_indicator_btn.height() / 2); - int icon_x = mic_indicator_btn.x() + (mic_indicator_btn.width() - mic_img.width()) / 2; - int icon_y = mic_indicator_btn.y() + (mic_indicator_btn.height() - mic_img.height()) / 2; - p.drawPixmap(icon_x, icon_y, mic_img); - } - p.setOpacity(1.0); - - // network - int x = 58; - const QColor gray(0x54, 0x54, 0x54); - for (int i = 0; i < 5; ++i) { - p.setBrush(i < net_strength ? Qt::white : gray); - p.drawEllipse(x, 196, 27, 27); - x += 37; - } - - p.setFont(InterFont(35)); - p.setPen(QColor(0xff, 0xff, 0xff)); - const QRect r = QRect(58, 247, width() - 100, 50); - - if (net_type == "Hotspot") { - p.drawPixmap(r.x(), r.y() + (r.height() - link_img.height()) / 2, link_img); - } else { - p.drawText(r, Qt::AlignLeft | Qt::AlignVCenter, net_type); - } - - // metrics - drawMetric(p, temp_status.first, temp_status.second, 338); - drawMetric(p, panda_status.first, panda_status.second, 496); - drawMetric(p, connect_status.first, connect_status.second, 654); -} diff --git a/selfdrive/ui/qt/sidebar.h b/selfdrive/ui/qt/sidebar.h deleted file mode 100644 index 6a2b7b695..000000000 --- a/selfdrive/ui/qt/sidebar.h +++ /dev/null @@ -1,66 +0,0 @@ -#pragma once - -#include - -#include -#include - -#include "selfdrive/ui/ui.h" -#include "selfdrive/ui/qt/network/networking.h" - -typedef QPair, QColor> ItemStatus; -Q_DECLARE_METATYPE(ItemStatus); - -class Sidebar : public QFrame { - Q_OBJECT - Q_PROPERTY(ItemStatus connectStatus MEMBER connect_status NOTIFY valueChanged); - Q_PROPERTY(ItemStatus pandaStatus MEMBER panda_status NOTIFY valueChanged); - Q_PROPERTY(ItemStatus tempStatus MEMBER temp_status NOTIFY valueChanged); - Q_PROPERTY(QString netType MEMBER net_type NOTIFY valueChanged); - Q_PROPERTY(int netStrength MEMBER net_strength NOTIFY valueChanged); - Q_PROPERTY(bool recordingAudio MEMBER recording_audio NOTIFY valueChanged); - -public: - explicit Sidebar(QWidget* parent = 0); - -signals: - void openSettings(int index = 0, const QString ¶m = ""); - void valueChanged(); - -public slots: - void offroadTransition(bool offroad); - void updateState(const UIState &s); - -protected: - void paintEvent(QPaintEvent *event) override; - void mousePressEvent(QMouseEvent *event) override; - void mouseReleaseEvent(QMouseEvent *event) override; - void drawMetric(QPainter &p, const QPair &label, QColor c, int y); - - QPixmap home_img, flag_img, settings_img, mic_img, link_img; - bool onroad, recording_audio, flag_pressed, settings_pressed, mic_indicator_pressed; - const QMap network_type = { - {cereal::DeviceState::NetworkType::NONE, tr("--")}, - {cereal::DeviceState::NetworkType::WIFI, tr("Wi-Fi")}, - {cereal::DeviceState::NetworkType::ETHERNET, tr("ETH")}, - {cereal::DeviceState::NetworkType::CELL2_G, tr("2G")}, - {cereal::DeviceState::NetworkType::CELL3_G, tr("3G")}, - {cereal::DeviceState::NetworkType::CELL4_G, tr("LTE")}, - {cereal::DeviceState::NetworkType::CELL5_G, tr("5G")} - }; - - const QRect home_btn = QRect(60, 860, 180, 180); - const QRect settings_btn = QRect(50, 35, 200, 117); - const QRect mic_indicator_btn = QRect(158, 252, 75, 40); - const QColor good_color = QColor(255, 255, 255); - const QColor warning_color = QColor(218, 202, 37); - const QColor danger_color = QColor(201, 34, 49); - - ItemStatus connect_status, panda_status, temp_status; - QString net_type; - int net_strength = 0; - -private: - std::unique_ptr pm; - Networking *networking = nullptr; -}; diff --git a/selfdrive/ui/qt/util.cc b/selfdrive/ui/qt/util.cc deleted file mode 100644 index 493c20397..000000000 --- a/selfdrive/ui/qt/util.cc +++ /dev/null @@ -1,228 +0,0 @@ -#include "selfdrive/ui/qt/util.h" - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "common/swaglog.h" -#include "common/util.h" -#include "system/hardware/hw.h" - -QString getVersion() { - static QString version = QString::fromStdString(Params().get("Version")); - return version; -} - -QString getBrand() { - return QObject::tr("openpilot"); -} - -QString getUserAgent() { - return "openpilot-" + getVersion(); -} - -std::optional getDongleId() { - std::string id = Params().get("DongleId"); - - if (!id.empty() && (id != "UnregisteredDevice")) { - return QString::fromStdString(id); - } else { - return {}; - } -} - -QMap getSupportedLanguages() { - QFile f(":/languages.json"); - f.open(QIODevice::ReadOnly | QIODevice::Text); - QString val = f.readAll(); - - QJsonObject obj = QJsonDocument::fromJson(val.toUtf8()).object(); - QMap map; - for (auto key : obj.keys()) { - map[key] = obj[key].toString(); - } - return map; -} - -QString timeAgo(const QDateTime &date) { - if (!util::system_time_valid()) { - return date.date().toString(); - } - - int diff = date.secsTo(QDateTime::currentDateTimeUtc()); - - QString s; - if (diff < 60) { - s = QObject::tr("now"); - } else if (diff < 60 * 60) { - int minutes = diff / 60; - s = QObject::tr("%n minute(s) ago", "", minutes); - } else if (diff < 60 * 60 * 24) { - int hours = diff / (60 * 60); - s = QObject::tr("%n hour(s) ago", "", hours); - } else if (diff < 3600 * 24 * 7) { - int days = diff / (60 * 60 * 24); - s = QObject::tr("%n day(s) ago", "", days); - } else { - s = date.date().toString(); - } - - return s; -} - -void setQtSurfaceFormat() { - QSurfaceFormat fmt; -#ifdef __APPLE__ - fmt.setVersion(3, 2); - fmt.setProfile(QSurfaceFormat::OpenGLContextProfile::CoreProfile); - fmt.setRenderableType(QSurfaceFormat::OpenGL); -#else - fmt.setRenderableType(QSurfaceFormat::OpenGLES); -#endif - fmt.setSamples(16); - fmt.setStencilBufferSize(1); - QSurfaceFormat::setDefaultFormat(fmt); -} - -void sigTermHandler(int s) { - std::signal(s, SIG_DFL); - qApp->quit(); -} - -void initApp(int argc, char *argv[], bool disable_hidpi) { - Hardware::set_display_power(true); - Hardware::set_brightness(65); - - // setup signal handlers to exit gracefully - std::signal(SIGINT, sigTermHandler); - std::signal(SIGTERM, sigTermHandler); - - QString app_dir; -#ifdef __APPLE__ - // Get the devicePixelRatio, and scale accordingly to maintain 1:1 rendering - QApplication tmp(argc, argv); - app_dir = QCoreApplication::applicationDirPath(); - if (disable_hidpi) { - qputenv("QT_SCALE_FACTOR", QString::number(1.0 / tmp.devicePixelRatio()).toLocal8Bit()); - } -#else - app_dir = QFileInfo(util::readlink("/proc/self/exe").c_str()).path(); -#endif - - qputenv("QT_DBL_CLICK_DIST", QByteArray::number(150)); - // ensure the current dir matches the exectuable's directory - QDir::setCurrent(app_dir); - - setQtSurfaceFormat(); -} - -void swagLogMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) { - static std::map levels = { - {QtMsgType::QtDebugMsg, CLOUDLOG_DEBUG}, - {QtMsgType::QtInfoMsg, CLOUDLOG_INFO}, - {QtMsgType::QtWarningMsg, CLOUDLOG_WARNING}, - {QtMsgType::QtCriticalMsg, CLOUDLOG_ERROR}, - {QtMsgType::QtSystemMsg, CLOUDLOG_ERROR}, - {QtMsgType::QtFatalMsg, CLOUDLOG_CRITICAL}, - }; - - std::string file, function; - if (context.file != nullptr) file = context.file; - if (context.function != nullptr) function = context.function; - - auto bts = msg.toUtf8(); - cloudlog_e(levels[type], file.c_str(), context.line, function.c_str(), "%s", bts.constData()); -} - - -QWidget* topWidget(QWidget* widget) { - while (widget->parentWidget() != nullptr) widget=widget->parentWidget(); - return widget; -} - -QPixmap loadPixmap(const QString &fileName, const QSize &size, Qt::AspectRatioMode aspectRatioMode) { - if (size.isEmpty()) { - return QPixmap(fileName); - } else { - return QPixmap(fileName).scaled(size, aspectRatioMode, Qt::SmoothTransformation); - } -} - -static QHash load_bootstrap_icons() { - QHash icons; - - QFile f(":/bootstrap-icons.svg"); - if (f.open(QIODevice::ReadOnly | QIODevice::Text)) { - QDomDocument xml; - xml.setContent(&f); - QDomNode n = xml.documentElement().firstChild(); - while (!n.isNull()) { - QDomElement e = n.toElement(); - if (!e.isNull() && e.hasAttribute("id")) { - QString svg_str; - QTextStream stream(&svg_str); - n.save(stream, 0); - svg_str.replace("", ""); - icons[e.attribute("id")] = svg_str.toUtf8(); - } - n = n.nextSibling(); - } - } - return icons; -} - -QPixmap bootstrapPixmap(const QString &id) { - static QHash icons = load_bootstrap_icons(); - - QPixmap pixmap; - if (auto it = icons.find(id); it != icons.end()) { - pixmap.loadFromData(it.value(), "svg"); - } - return pixmap; -} - -bool hasLongitudinalControl(const cereal::CarParams::Reader &car_params) { - // Using the experimental longitudinal toggle, returns whether longitudinal control - // will be active without needing a restart of openpilot - return car_params.getAlphaLongitudinalAvailable() - ? Params().getBool("AlphaLongitudinalEnabled") - : car_params.getOpenpilotLongitudinalControl(); -} - -// ParamWatcher - -ParamWatcher::ParamWatcher(QObject *parent) : QObject(parent) { - watcher = new QFileSystemWatcher(this); - QObject::connect(watcher, &QFileSystemWatcher::fileChanged, this, &ParamWatcher::fileChanged); -} - -void ParamWatcher::fileChanged(const QString &path) { - auto param_name = QFileInfo(path).fileName(); - auto param_value = QString::fromStdString(params.get(param_name.toStdString())); - - auto it = params_hash.find(param_name); - bool content_changed = (it == params_hash.end()) || (it.value() != param_value); - params_hash[param_name] = param_value; - // emit signal when the content changes. - if (content_changed) { - emit paramChanged(param_name, param_value); - } -} - -void ParamWatcher::addParam(const QString ¶m_name) { - watcher->addPath(QString::fromStdString(params.getParamPath(param_name.toStdString()))); -} diff --git a/selfdrive/ui/qt/util.h b/selfdrive/ui/qt/util.h deleted file mode 100644 index 2bf1a70a6..000000000 --- a/selfdrive/ui/qt/util.h +++ /dev/null @@ -1,54 +0,0 @@ -#pragma once - -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "cereal/gen/cpp/car.capnp.h" -#include "common/params.h" - -QString getVersion(); -QString getBrand(); -QString getUserAgent(); -std::optional getDongleId(); -QMap getSupportedLanguages(); -void setQtSurfaceFormat(); -void sigTermHandler(int s); -QString timeAgo(const QDateTime &date); -void swagLogMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg); -void initApp(int argc, char *argv[], bool disable_hidpi = true); -QWidget* topWidget(QWidget* widget); -QPixmap loadPixmap(const QString &fileName, const QSize &size = {}, Qt::AspectRatioMode aspectRatioMode = Qt::KeepAspectRatio); -QPixmap bootstrapPixmap(const QString &id); -bool hasLongitudinalControl(const cereal::CarParams::Reader &car_params); - -struct InterFont : public QFont { - InterFont(int pixel_size, QFont::Weight weight = QFont::Normal) : QFont("Inter") { - setPixelSize(pixel_size); - setWeight(weight); - } -}; - -class ParamWatcher : public QObject { - Q_OBJECT - -public: - ParamWatcher(QObject *parent); - void addParam(const QString ¶m_name); - -signals: - void paramChanged(const QString ¶m_name, const QString ¶m_value); - -private: - void fileChanged(const QString &path); - - QFileSystemWatcher *watcher; - QHash params_hash; - Params params; -}; diff --git a/selfdrive/ui/qt/widgets/cameraview.cc b/selfdrive/ui/qt/widgets/cameraview.cc deleted file mode 100644 index 5f533cd09..000000000 --- a/selfdrive/ui/qt/widgets/cameraview.cc +++ /dev/null @@ -1,365 +0,0 @@ -#include "selfdrive/ui/qt/widgets/cameraview.h" - -#ifdef __APPLE__ -#include -#else -#include -#endif - -#include -#include - -namespace { - -const char frame_vertex_shader[] = -#ifdef __APPLE__ - "#version 330 core\n" -#else - "#version 300 es\n" -#endif - "layout(location = 0) in vec4 aPosition;\n" - "layout(location = 1) in vec2 aTexCoord;\n" - "uniform mat4 uTransform;\n" - "out vec2 vTexCoord;\n" - "void main() {\n" - " gl_Position = uTransform * aPosition;\n" - " vTexCoord = aTexCoord;\n" - "}\n"; - -const char frame_fragment_shader[] = -#ifdef __TICI__ - "#version 300 es\n" - "#extension GL_OES_EGL_image_external_essl3 : enable\n" - "precision mediump float;\n" - "uniform samplerExternalOES uTexture;\n" - "in vec2 vTexCoord;\n" - "out vec4 colorOut;\n" - "void main() {\n" - " colorOut = texture(uTexture, vTexCoord);\n" - // gamma to improve worst case visibility when dark - " colorOut.rgb = pow(colorOut.rgb, vec3(1.0/1.28));\n" - "}\n"; -#else -#ifdef __APPLE__ - "#version 330 core\n" -#else - "#version 300 es\n" - "precision mediump float;\n" -#endif - "uniform sampler2D uTextureY;\n" - "uniform sampler2D uTextureUV;\n" - "in vec2 vTexCoord;\n" - "out vec4 colorOut;\n" - "void main() {\n" - " float y = texture(uTextureY, vTexCoord).r;\n" - " vec2 uv = texture(uTextureUV, vTexCoord).rg - 0.5;\n" - " float r = y + 1.402 * uv.y;\n" - " float g = y - 0.344 * uv.x - 0.714 * uv.y;\n" - " float b = y + 1.772 * uv.x;\n" - " colorOut = vec4(r, g, b, 1.0);\n" - "}\n"; -#endif - -} // namespace - -CameraWidget::CameraWidget(std::string stream_name, VisionStreamType type, QWidget* parent) : - stream_name(stream_name), active_stream_type(type), requested_stream_type(type), QOpenGLWidget(parent) { - setAttribute(Qt::WA_OpaquePaintEvent); - qRegisterMetaType>("availableStreams"); - QObject::connect(this, &CameraWidget::vipcThreadConnected, this, &CameraWidget::vipcConnected, Qt::BlockingQueuedConnection); - QObject::connect(this, &CameraWidget::vipcThreadFrameReceived, this, &CameraWidget::vipcFrameReceived, Qt::QueuedConnection); - QObject::connect(this, &CameraWidget::vipcAvailableStreamsUpdated, this, &CameraWidget::availableStreamsUpdated, Qt::QueuedConnection); - QObject::connect(QApplication::instance(), &QCoreApplication::aboutToQuit, this, &CameraWidget::stopVipcThread); -} - -CameraWidget::~CameraWidget() { - makeCurrent(); - stopVipcThread(); - if (isValid()) { - glDeleteVertexArrays(1, &frame_vao); - glDeleteBuffers(1, &frame_vbo); - glDeleteBuffers(1, &frame_ibo); -#ifndef __TICI__ - glDeleteTextures(2, textures); -#endif - } - doneCurrent(); -} - -// Qt uses device-independent pixels, depending on platform this may be -// different to what OpenGL uses -int CameraWidget::glWidth() { - return width() * devicePixelRatio(); -} - -int CameraWidget::glHeight() { - return height() * devicePixelRatio(); -} - -void CameraWidget::initializeGL() { - initializeOpenGLFunctions(); - - program = std::make_unique(context()); - bool ret = program->addShaderFromSourceCode(QOpenGLShader::Vertex, frame_vertex_shader); - assert(ret); - ret = program->addShaderFromSourceCode(QOpenGLShader::Fragment, frame_fragment_shader); - assert(ret); - - program->link(); - GLint frame_pos_loc = program->attributeLocation("aPosition"); - GLint frame_texcoord_loc = program->attributeLocation("aTexCoord"); - - auto [x1, x2, y1, y2] = requested_stream_type == VISION_STREAM_DRIVER ? std::tuple(0.f, 1.f, 1.f, 0.f) : std::tuple(1.f, 0.f, 1.f, 0.f); - const uint8_t frame_indicies[] = {0, 1, 2, 0, 2, 3}; - const float frame_coords[4][4] = { - {-1.0, -1.0, x2, y1}, // bl - {-1.0, 1.0, x2, y2}, // tl - { 1.0, 1.0, x1, y2}, // tr - { 1.0, -1.0, x1, y1}, // br - }; - - glGenVertexArrays(1, &frame_vao); - glBindVertexArray(frame_vao); - glGenBuffers(1, &frame_vbo); - glBindBuffer(GL_ARRAY_BUFFER, frame_vbo); - glBufferData(GL_ARRAY_BUFFER, sizeof(frame_coords), frame_coords, GL_STATIC_DRAW); - glEnableVertexAttribArray(frame_pos_loc); - glVertexAttribPointer(frame_pos_loc, 2, GL_FLOAT, GL_FALSE, - sizeof(frame_coords[0]), (const void *)0); - glEnableVertexAttribArray(frame_texcoord_loc); - glVertexAttribPointer(frame_texcoord_loc, 2, GL_FLOAT, GL_FALSE, - sizeof(frame_coords[0]), (const void *)(sizeof(float) * 2)); - glGenBuffers(1, &frame_ibo); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, frame_ibo); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(frame_indicies), frame_indicies, GL_STATIC_DRAW); - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindVertexArray(0); - - glUseProgram(program->programId()); - -#ifdef __TICI__ - glUniform1i(program->uniformLocation("uTexture"), 0); -#else - glGenTextures(2, textures); - glUniform1i(program->uniformLocation("uTextureY"), 0); - glUniform1i(program->uniformLocation("uTextureUV"), 1); -#endif -} - -void CameraWidget::showEvent(QShowEvent *event) { - if (!vipc_thread) { - clearFrames(); - vipc_thread = new QThread(); - connect(vipc_thread, &QThread::started, [=]() { vipcThread(); }); - connect(vipc_thread, &QThread::finished, vipc_thread, &QObject::deleteLater); - vipc_thread->start(); - } -} - -void CameraWidget::stopVipcThread() { - makeCurrent(); - if (vipc_thread) { - vipc_thread->requestInterruption(); - vipc_thread->quit(); - vipc_thread->wait(); - vipc_thread = nullptr; - } - -#ifdef __TICI__ - EGLDisplay egl_display = eglGetCurrentDisplay(); - assert(egl_display != EGL_NO_DISPLAY); - for (auto &pair : egl_images) { - eglDestroyImageKHR(egl_display, pair.second); - assert(eglGetError() == EGL_SUCCESS); - } - egl_images.clear(); -#endif -} - -void CameraWidget::availableStreamsUpdated(std::set streams) { - available_streams = streams; -} - -mat4 CameraWidget::calcFrameMatrix() { - // Scale the frame to fit the widget while maintaining the aspect ratio. - float widget_aspect_ratio = (float)width() / height(); - float frame_aspect_ratio = (float)stream_width / stream_height; - float zx = std::min(frame_aspect_ratio / widget_aspect_ratio, 1.0f); - float zy = std::min(widget_aspect_ratio / frame_aspect_ratio, 1.0f); - - return mat4{{ - zx, 0.0, 0.0, 0.0, - 0.0, zy, 0.0, 0.0, - 0.0, 0.0, 1.0, 0.0, - 0.0, 0.0, 0.0, 1.0, - }}; -} - -void CameraWidget::paintGL() { - glClearColor(bg.redF(), bg.greenF(), bg.blueF(), bg.alphaF()); - glClear(GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT); - - std::lock_guard lk(frame_lock); - if (frames.empty()) return; - - int frame_idx = frames.size() - 1; - - // Always draw latest frame until sync logic is more stable - // for (frame_idx = 0; frame_idx < frames.size() - 1; frame_idx++) { - // if (frames[frame_idx].first == draw_frame_id) break; - // } - - // Log duplicate/dropped frames - if (frames[frame_idx].first == prev_frame_id) { - qDebug() << "Drawing same frame twice" << frames[frame_idx].first; - } else if (frames[frame_idx].first != prev_frame_id + 1) { - qDebug() << "Skipped frame" << frames[frame_idx].first; - } - prev_frame_id = frames[frame_idx].first; - VisionBuf *frame = frames[frame_idx].second; - assert(frame != nullptr); - - auto frame_mat = calcFrameMatrix(); - - glViewport(0, 0, glWidth(), glHeight()); - glBindVertexArray(frame_vao); - glUseProgram(program->programId()); - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - -#ifdef __TICI__ - // no frame copy - glActiveTexture(GL_TEXTURE0); - glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, egl_images[frame->idx]); - assert(glGetError() == GL_NO_ERROR); -#else - // fallback to copy - glPixelStorei(GL_UNPACK_ROW_LENGTH, stream_stride); - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, textures[0]); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, stream_width, stream_height, GL_RED, GL_UNSIGNED_BYTE, frame->y); - assert(glGetError() == GL_NO_ERROR); - - glPixelStorei(GL_UNPACK_ROW_LENGTH, stream_stride/2); - glActiveTexture(GL_TEXTURE0 + 1); - glBindTexture(GL_TEXTURE_2D, textures[1]); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, stream_width/2, stream_height/2, GL_RG, GL_UNSIGNED_BYTE, frame->uv); - assert(glGetError() == GL_NO_ERROR); -#endif - - glUniformMatrix4fv(program->uniformLocation("uTransform"), 1, GL_TRUE, frame_mat.v); - glEnableVertexAttribArray(0); - glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_BYTE, (const void *)0); - glDisableVertexAttribArray(0); - glBindVertexArray(0); - glBindTexture(GL_TEXTURE_2D, 0); - glActiveTexture(GL_TEXTURE0); - glPixelStorei(GL_UNPACK_ALIGNMENT, 4); - glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); -} - -void CameraWidget::vipcConnected(VisionIpcClient *vipc_client) { - makeCurrent(); - stream_width = vipc_client->buffers[0].width; - stream_height = vipc_client->buffers[0].height; - stream_stride = vipc_client->buffers[0].stride; - -#ifdef __TICI__ - EGLDisplay egl_display = eglGetCurrentDisplay(); - assert(egl_display != EGL_NO_DISPLAY); - for (auto &pair : egl_images) { - eglDestroyImageKHR(egl_display, pair.second); - } - egl_images.clear(); - - for (int i = 0; i < vipc_client->num_buffers; i++) { // import buffers into OpenGL - int fd = dup(vipc_client->buffers[i].fd); // eglDestroyImageKHR will close, so duplicate - EGLint img_attrs[] = { - EGL_WIDTH, (int)vipc_client->buffers[i].width, - EGL_HEIGHT, (int)vipc_client->buffers[i].height, - EGL_LINUX_DRM_FOURCC_EXT, DRM_FORMAT_NV12, - EGL_DMA_BUF_PLANE0_FD_EXT, fd, - EGL_DMA_BUF_PLANE0_OFFSET_EXT, 0, - EGL_DMA_BUF_PLANE0_PITCH_EXT, (int)vipc_client->buffers[i].stride, - EGL_DMA_BUF_PLANE1_FD_EXT, fd, - EGL_DMA_BUF_PLANE1_OFFSET_EXT, (int)vipc_client->buffers[i].uv_offset, - EGL_DMA_BUF_PLANE1_PITCH_EXT, (int)vipc_client->buffers[i].stride, - EGL_NONE - }; - egl_images[i] = eglCreateImageKHR(egl_display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, 0, img_attrs); - assert(eglGetError() == EGL_SUCCESS); - } -#else - glBindTexture(GL_TEXTURE_2D, textures[0]); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, stream_width, stream_height, 0, GL_RED, GL_UNSIGNED_BYTE, nullptr); - assert(glGetError() == GL_NO_ERROR); - - glBindTexture(GL_TEXTURE_2D, textures[1]); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RG8, stream_width/2, stream_height/2, 0, GL_RG, GL_UNSIGNED_BYTE, nullptr); - assert(glGetError() == GL_NO_ERROR); -#endif -} - -void CameraWidget::vipcFrameReceived() { - update(); -} - -void CameraWidget::vipcThread() { - VisionStreamType cur_stream = requested_stream_type; - std::unique_ptr vipc_client; - VisionIpcBufExtra meta_main = {0}; - - while (!QThread::currentThread()->isInterruptionRequested()) { - if (!vipc_client || cur_stream != requested_stream_type) { - clearFrames(); - qDebug().nospace() << "connecting to stream " << requested_stream_type << ", was connected to " << cur_stream; - cur_stream = requested_stream_type; - vipc_client.reset(new VisionIpcClient(stream_name, cur_stream, false)); - } - active_stream_type = cur_stream; - - if (!vipc_client->connected) { - clearFrames(); - auto streams = VisionIpcClient::getAvailableStreams(stream_name, false); - if (streams.empty()) { - QThread::msleep(100); - continue; - } - emit vipcAvailableStreamsUpdated(streams); - - if (!vipc_client->connect(false)) { - QThread::msleep(100); - continue; - } - emit vipcThreadConnected(vipc_client.get()); - } - - if (VisionBuf *buf = vipc_client->recv(&meta_main, 1000)) { - { - std::lock_guard lk(frame_lock); - frames.push_back(std::make_pair(meta_main.frame_id, buf)); - while (frames.size() > FRAME_BUFFER_SIZE) { - frames.pop_front(); - } - } - emit vipcThreadFrameReceived(); - } else { - if (!isVisible()) { - vipc_client->connected = false; - } - } - } -} - -void CameraWidget::clearFrames() { - std::lock_guard lk(frame_lock); - frames.clear(); - available_streams.clear(); -} diff --git a/selfdrive/ui/qt/widgets/cameraview.h b/selfdrive/ui/qt/widgets/cameraview.h deleted file mode 100644 index 598603a08..000000000 --- a/selfdrive/ui/qt/widgets/cameraview.h +++ /dev/null @@ -1,89 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#ifdef __TICI__ -#define EGL_EGLEXT_PROTOTYPES -#define EGL_NO_X11 -#define GL_TEXTURE_EXTERNAL_OES 0x8D65 -#include -#include -#include -#endif - -#include "msgq/visionipc/visionipc_client.h" -#include "selfdrive/ui/ui.h" - -const int FRAME_BUFFER_SIZE = 5; - -class CameraWidget : public QOpenGLWidget, protected QOpenGLFunctions { - Q_OBJECT - -public: - using QOpenGLWidget::QOpenGLWidget; - explicit CameraWidget(std::string stream_name, VisionStreamType stream_type, QWidget* parent = nullptr); - ~CameraWidget(); - void setBackgroundColor(const QColor &color) { bg = color; } - void setFrameId(int frame_id) { draw_frame_id = frame_id; } - void setStreamType(VisionStreamType type) { requested_stream_type = type; } - VisionStreamType getStreamType() { return active_stream_type; } - void stopVipcThread(); - -signals: - void clicked(); - void vipcThreadConnected(VisionIpcClient *); - void vipcThreadFrameReceived(); - void vipcAvailableStreamsUpdated(std::set); - -protected: - void paintGL() override; - void initializeGL() override; - void showEvent(QShowEvent *event) override; - void mouseReleaseEvent(QMouseEvent *event) override { emit clicked(); } - virtual mat4 calcFrameMatrix(); - void vipcThread(); - void clearFrames(); - - int glWidth(); - int glHeight(); - - GLuint frame_vao, frame_vbo, frame_ibo; - GLuint textures[2]; - std::unique_ptr program; - QColor bg = QColor("#000000"); - -#ifdef __TICI__ - std::map egl_images; -#endif - - std::string stream_name; - int stream_width = 0; - int stream_height = 0; - int stream_stride = 0; - std::atomic active_stream_type; - std::atomic requested_stream_type; - std::set available_streams; - QThread *vipc_thread = nullptr; - std::recursive_mutex frame_lock; - std::deque> frames; - uint32_t draw_frame_id = 0; - uint32_t prev_frame_id = 0; - -protected slots: - void vipcConnected(VisionIpcClient *vipc_client); - void vipcFrameReceived(); - void availableStreamsUpdated(std::set streams); -}; - -Q_DECLARE_METATYPE(std::set); diff --git a/selfdrive/ui/qt/widgets/controls.cc b/selfdrive/ui/qt/widgets/controls.cc deleted file mode 100644 index 40dda971f..000000000 --- a/selfdrive/ui/qt/widgets/controls.cc +++ /dev/null @@ -1,141 +0,0 @@ -#include "selfdrive/ui/qt/widgets/controls.h" - -#include -#include - -AbstractControl::AbstractControl(const QString &title, const QString &desc, const QString &icon, QWidget *parent) : QFrame(parent) { - QVBoxLayout *main_layout = new QVBoxLayout(this); - main_layout->setMargin(0); - - hlayout = new QHBoxLayout; - hlayout->setMargin(0); - hlayout->setSpacing(20); - - // left icon - icon_label = new QLabel(this); - hlayout->addWidget(icon_label); - if (!icon.isEmpty()) { - icon_pixmap = QPixmap(icon).scaledToWidth(80, Qt::SmoothTransformation); - icon_label->setPixmap(icon_pixmap); - icon_label->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); - } - icon_label->setVisible(!icon.isEmpty()); - - // title - title_label = new QPushButton(title); - title_label->setFixedHeight(120); - title_label->setStyleSheet("font-size: 50px; font-weight: 400; text-align: left; border: none;"); - hlayout->addWidget(title_label, 1); - - // value next to control button - value = new ElidedLabel(); - value->setAlignment(Qt::AlignRight | Qt::AlignVCenter); - value->setStyleSheet("color: #aaaaaa"); - hlayout->addWidget(value); - - main_layout->addLayout(hlayout); - - // description - description = new QLabel(desc); - description->setContentsMargins(40, 20, 40, 20); - description->setStyleSheet("font-size: 40px; color: grey"); - description->setWordWrap(true); - description->setVisible(false); - main_layout->addWidget(description); - - connect(title_label, &QPushButton::clicked, [=]() { - if (!description->isVisible()) { - emit showDescriptionEvent(); - } - - if (!description->text().isEmpty()) { - description->setVisible(!description->isVisible()); - } - }); - - main_layout->addStretch(); -} - -void AbstractControl::hideEvent(QHideEvent *e) { - if (description != nullptr) { - description->hide(); - } -} - -// controls - -ButtonControl::ButtonControl(const QString &title, const QString &text, const QString &desc, QWidget *parent) : AbstractControl(title, desc, "", parent) { - btn.setText(text); - btn.setStyleSheet(R"( - QPushButton { - padding: 0; - border-radius: 50px; - font-size: 35px; - font-weight: 500; - color: #E4E4E4; - background-color: #393939; - } - QPushButton:pressed { - background-color: #4a4a4a; - } - QPushButton:disabled { - color: #33E4E4E4; - } - )"); - btn.setFixedSize(250, 100); - QObject::connect(&btn, &QPushButton::clicked, this, &ButtonControl::clicked); - hlayout->addWidget(&btn); -} - -// ElidedLabel - -ElidedLabel::ElidedLabel(QWidget *parent) : ElidedLabel({}, parent) {} - -ElidedLabel::ElidedLabel(const QString &text, QWidget *parent) : QLabel(text.trimmed(), parent) { - setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); - setMinimumWidth(1); -} - -void ElidedLabel::resizeEvent(QResizeEvent* event) { - QLabel::resizeEvent(event); - lastText_ = elidedText_ = ""; -} - -void ElidedLabel::paintEvent(QPaintEvent *event) { - const QString curText = text(); - if (curText != lastText_) { - elidedText_ = fontMetrics().elidedText(curText, Qt::ElideRight, contentsRect().width()); - lastText_ = curText; - } - - QPainter painter(this); - drawFrame(&painter); - QStyleOption opt; - opt.initFrom(this); - style()->drawItemText(&painter, contentsRect(), alignment(), opt.palette, isEnabled(), elidedText_, foregroundRole()); -} - -// ParamControl - -ParamControl::ParamControl(const QString ¶m, const QString &title, const QString &desc, const QString &icon, QWidget *parent) - : ToggleControl(title, desc, icon, false, parent) { - key = param.toStdString(); - QObject::connect(this, &ParamControl::toggleFlipped, this, &ParamControl::toggleClicked); -} - -void ParamControl::toggleClicked(bool state) { - auto do_confirm = [this]() { - QString content("

" + title_label->text() + "


" - "

" + getDescription() + "

"); - return ConfirmationDialog(content, tr("Enable"), tr("Cancel"), true, this).exec(); - }; - - bool confirmed = store_confirm && params.getBool(key + "Confirmed"); - if (!confirm || confirmed || !state || do_confirm()) { - if (store_confirm && state) params.putBool(key + "Confirmed", true); - params.putBool(key, state); - setIcon(state); - } else { - toggle.togglePosition(); - } -} diff --git a/selfdrive/ui/qt/widgets/controls.h b/selfdrive/ui/qt/widgets/controls.h deleted file mode 100644 index 3342de532..000000000 --- a/selfdrive/ui/qt/widgets/controls.h +++ /dev/null @@ -1,319 +0,0 @@ -#pragma once - -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "common/params.h" -#include "selfdrive/ui/qt/widgets/input.h" -#include "selfdrive/ui/qt/widgets/toggle.h" - -class ElidedLabel : public QLabel { - Q_OBJECT - -public: - explicit ElidedLabel(QWidget *parent = 0); - explicit ElidedLabel(const QString &text, QWidget *parent = 0); - -signals: - void clicked(); - -protected: - void paintEvent(QPaintEvent *event) override; - void resizeEvent(QResizeEvent* event) override; - void mouseReleaseEvent(QMouseEvent *event) override { - if (rect().contains(event->pos())) { - emit clicked(); - } - } - QString lastText_, elidedText_; -}; - - -class AbstractControl : public QFrame { - Q_OBJECT - -public: - void setDescription(const QString &desc) { - if (description) description->setText(desc); - } - - void setTitle(const QString &title) { - title_label->setText(title); - } - - void setValue(const QString &val) { - value->setText(val); - } - - const QString getDescription() { - return description->text(); - } - - QLabel *icon_label; - QPixmap icon_pixmap; - -public slots: - void showDescription() { - description->setVisible(true); - } - -signals: - void showDescriptionEvent(); - -protected: - AbstractControl(const QString &title, const QString &desc = "", const QString &icon = "", QWidget *parent = nullptr); - void hideEvent(QHideEvent *e) override; - - QHBoxLayout *hlayout; - QPushButton *title_label; - -private: - ElidedLabel *value; - QLabel *description = nullptr; -}; - -// widget to display a value -class LabelControl : public AbstractControl { - Q_OBJECT - -public: - LabelControl(const QString &title, const QString &text = "", const QString &desc = "", QWidget *parent = nullptr) : AbstractControl(title, desc, "", parent) { - label.setText(text); - label.setAlignment(Qt::AlignRight | Qt::AlignVCenter); - hlayout->addWidget(&label); - } - void setText(const QString &text) { label.setText(text); } - -private: - ElidedLabel label; -}; - -// widget for a button with a label -class ButtonControl : public AbstractControl { - Q_OBJECT - -public: - ButtonControl(const QString &title, const QString &text, const QString &desc = "", QWidget *parent = nullptr); - inline void setText(const QString &text) { btn.setText(text); } - inline QString text() const { return btn.text(); } - -signals: - void clicked(); - -public slots: - void setEnabled(bool enabled) { btn.setEnabled(enabled); } - -private: - QPushButton btn; -}; - -class ToggleControl : public AbstractControl { - Q_OBJECT - -public: - ToggleControl(const QString &title, const QString &desc = "", const QString &icon = "", const bool state = false, QWidget *parent = nullptr) : AbstractControl(title, desc, icon, parent) { - toggle.setFixedSize(150, 100); - if (state) { - toggle.togglePosition(); - } - hlayout->addWidget(&toggle); - QObject::connect(&toggle, &Toggle::stateChanged, this, &ToggleControl::toggleFlipped); - } - - void setEnabled(bool enabled) { - toggle.setEnabled(enabled); - toggle.update(); - } - -signals: - void toggleFlipped(bool state); - -protected: - Toggle toggle; -}; - -// widget to toggle params -class ParamControl : public ToggleControl { - Q_OBJECT - -public: - ParamControl(const QString ¶m, const QString &title, const QString &desc, const QString &icon, QWidget *parent = nullptr); - void setConfirmation(bool _confirm, bool _store_confirm) { - confirm = _confirm; - store_confirm = _store_confirm; - } - - void setActiveIcon(const QString &icon) { - active_icon_pixmap = QPixmap(icon).scaledToWidth(80, Qt::SmoothTransformation); - } - - void refresh() { - bool state = params.getBool(key); - if (state != toggle.on) { - toggle.togglePosition(); - setIcon(state); - } - } - - void showEvent(QShowEvent *event) override { - refresh(); - } - -private: - void toggleClicked(bool state); - void setIcon(bool state) { - if (state && !active_icon_pixmap.isNull()) { - icon_label->setPixmap(active_icon_pixmap); - } else if (!icon_pixmap.isNull()) { - icon_label->setPixmap(icon_pixmap); - } - } - - std::string key; - Params params; - QPixmap active_icon_pixmap; - bool confirm = false; - bool store_confirm = false; -}; - -class MultiButtonControl : public AbstractControl { - Q_OBJECT -public: - 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 { - border-radius: 50px; - font-size: 40px; - font-weight: 500; - height:100px; - padding: 0 25 0 25; - color: #E4E4E4; - background-color: #393939; - } - QPushButton:pressed { - background-color: #4a4a4a; - } - QPushButton:checked:enabled { - background-color: #33Ab4C; - } - QPushButton:checked:disabled { - background-color: #9933Ab4C; - } - QPushButton:disabled { - color: #33E4E4E4; - } - )"; - - 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 == 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), this, &MultiButtonControl::buttonClicked); - } - - void setEnabled(bool enable) { - for (auto btn : button_group->buttons()) { - btn->setEnabled(enable); - } - } - - void setCheckedButton(int id) { - 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); - } - - void showEvent(QShowEvent *event) override { - refresh(); - } - -private: - std::string key; - Params params; -}; - -class ListWidget : public QWidget { - Q_OBJECT - public: - explicit ListWidget(QWidget *parent = 0) : QWidget(parent), outer_layout(this) { - outer_layout.setMargin(0); - outer_layout.setSpacing(0); - outer_layout.addLayout(&inner_layout); - inner_layout.setMargin(0); - inner_layout.setSpacing(25); // default spacing is 25 - outer_layout.addStretch(1); - } - inline void addItem(QWidget *w) { inner_layout.addWidget(w); } - inline void addItem(QLayout *layout) { inner_layout.addLayout(layout); } - inline void setSpacing(int spacing) { inner_layout.setSpacing(spacing); } - -private: - void paintEvent(QPaintEvent *) override { - QPainter p(this); - p.setPen(Qt::gray); - for (int i = 0; i < inner_layout.count() - 1; ++i) { - QWidget *widget = inner_layout.itemAt(i)->widget(); - if (widget == nullptr || widget->isVisible()) { - QRect r = inner_layout.itemAt(i)->geometry(); - int bottom = r.bottom() + inner_layout.spacing() / 2; - p.drawLine(r.left() + 40, bottom, r.right() - 40, bottom); - } - } - } - QVBoxLayout outer_layout; - QVBoxLayout inner_layout; -}; - -// convenience class for wrapping layouts -class LayoutWidget : public QWidget { - Q_OBJECT - -public: - LayoutWidget(QLayout *l, QWidget *parent = nullptr) : QWidget(parent) { - setLayout(l); - } -}; diff --git a/selfdrive/ui/qt/widgets/input.cc b/selfdrive/ui/qt/widgets/input.cc deleted file mode 100644 index 0cbf14931..000000000 --- a/selfdrive/ui/qt/widgets/input.cc +++ /dev/null @@ -1,336 +0,0 @@ -#include "selfdrive/ui/qt/widgets/input.h" - -#include -#include - -#include "system/hardware/hw.h" -#include "selfdrive/ui/qt/util.h" -#include "selfdrive/ui/qt/qt_window.h" -#include "selfdrive/ui/qt/widgets/scrollview.h" - - -DialogBase::DialogBase(QWidget *parent) : QDialog(parent) { - Q_ASSERT(parent != nullptr); - parent->installEventFilter(this); - - setStyleSheet(R"( - * { - outline: none; - color: white; - font-family: Inter; - } - DialogBase { - background-color: black; - } - QPushButton { - height: 160; - font-size: 55px; - font-weight: 400; - border-radius: 10px; - color: white; - background-color: #333333; - } - QPushButton:pressed { - background-color: #444444; - } - )"); -} - -bool DialogBase::eventFilter(QObject *o, QEvent *e) { - if (o == parent() && e->type() == QEvent::Hide) { - reject(); - } - return QDialog::eventFilter(o, e); -} - -int DialogBase::exec() { - setMainWindow(this); - return QDialog::exec(); -} - -InputDialog::InputDialog(const QString &title, QWidget *parent, const QString &subtitle, bool secret) : DialogBase(parent) { - main_layout = new QVBoxLayout(this); - main_layout->setContentsMargins(50, 55, 50, 50); - main_layout->setSpacing(0); - - // build header - QHBoxLayout *header_layout = new QHBoxLayout(); - - QVBoxLayout *vlayout = new QVBoxLayout; - header_layout->addLayout(vlayout); - label = new QLabel(title, this); - label->setStyleSheet("font-size: 90px; font-weight: bold;"); - vlayout->addWidget(label, 1, Qt::AlignTop | Qt::AlignLeft); - - if (!subtitle.isEmpty()) { - sublabel = new QLabel(subtitle, this); - sublabel->setStyleSheet("font-size: 55px; font-weight: light; color: #BDBDBD;"); - vlayout->addWidget(sublabel, 1, Qt::AlignTop | Qt::AlignLeft); - } - - QPushButton* cancel_btn = new QPushButton(tr("Cancel")); - cancel_btn->setFixedSize(386, 125); - cancel_btn->setStyleSheet(R"( - QPushButton { - font-size: 48px; - border-radius: 10px; - color: #E4E4E4; - background-color: #333333; - } - QPushButton:pressed { - background-color: #444444; - } - )"); - header_layout->addWidget(cancel_btn, 0, Qt::AlignRight); - QObject::connect(cancel_btn, &QPushButton::clicked, this, &InputDialog::reject); - QObject::connect(cancel_btn, &QPushButton::clicked, this, &InputDialog::cancel); - - main_layout->addLayout(header_layout); - - // text box - main_layout->addStretch(2); - - QWidget *textbox_widget = new QWidget; - textbox_widget->setObjectName("textbox"); - QHBoxLayout *textbox_layout = new QHBoxLayout(textbox_widget); - textbox_layout->setContentsMargins(50, 0, 50, 0); - - textbox_widget->setStyleSheet(R"( - #textbox { - margin-left: 50px; - margin-right: 50px; - border-radius: 0; - border-bottom: 3px solid #BDBDBD; - } - * { - border: none; - font-size: 80px; - font-weight: light; - background-color: transparent; - } - )"); - - line = new QLineEdit(); - line->setStyleSheet("lineedit-password-character: 8226; lineedit-password-mask-delay: 1500;"); - textbox_layout->addWidget(line, 1); - - if (secret) { - eye_btn = new QPushButton(); - eye_btn->setCheckable(true); - eye_btn->setFixedSize(150, 120); - QObject::connect(eye_btn, &QPushButton::toggled, [=](bool checked) { - if (checked) { - 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 + "icons/eye_open.svg")); - eye_btn->setIconSize(QSize(81, 44)); - line->setEchoMode(QLineEdit::Normal); - } - }); - eye_btn->toggle(); - eye_btn->setChecked(false); - textbox_layout->addWidget(eye_btn); - } - - main_layout->addWidget(textbox_widget, 0, Qt::AlignBottom); - main_layout->addSpacing(25); - - k = new Keyboard(this); - QObject::connect(k, &Keyboard::emitEnter, this, &InputDialog::handleEnter); - QObject::connect(k, &Keyboard::emitBackspace, this, [=]() { - line->backspace(); - }); - QObject::connect(k, &Keyboard::emitKey, this, [=](const QString &key) { - line->insert(key.left(1)); - }); - - main_layout->addWidget(k, 2, Qt::AlignBottom); -} - -QString InputDialog::getText(const QString &prompt, QWidget *parent, const QString &subtitle, - bool secret, int minLength, const QString &defaultText) { - InputDialog d(prompt, parent, subtitle, secret); - d.line->setText(defaultText); - d.setMinLength(minLength); - const int ret = d.exec(); - return ret ? d.text() : QString(); -} - -QString InputDialog::text() { - return line->text(); -} - -void InputDialog::show() { - setMainWindow(this); -} - -void InputDialog::handleEnter() { - if (line->text().length() >= minLength) { - done(QDialog::Accepted); - emitText(line->text()); - } else { - setMessage(tr("Need at least %n character(s)!", "", minLength), false); - } -} - -void InputDialog::setMessage(const QString &message, bool clearInputField) { - label->setText(message); - if (clearInputField) { - line->setText(""); - } -} - -void InputDialog::setMinLength(int length) { - minLength = length; -} - -// ConfirmationDialog - -ConfirmationDialog::ConfirmationDialog(const QString &prompt_text, const QString &confirm_text, const QString &cancel_text, - const bool rich, QWidget *parent) : DialogBase(parent) { - QFrame *container = new QFrame(this); - container->setStyleSheet(R"( - QFrame { background-color: #1B1B1B; color: #C9C9C9; } - #confirm_btn { background-color: #465BEA; } - #confirm_btn:pressed { background-color: #3049F4; } - )"); - QVBoxLayout *main_layout = new QVBoxLayout(container); - main_layout->setContentsMargins(32, rich ? 32 : 120, 32, 32); - - QLabel *prompt = new QLabel(prompt_text, this); - prompt->setWordWrap(true); - prompt->setAlignment(rich ? Qt::AlignLeft : Qt::AlignHCenter); - prompt->setStyleSheet((rich ? "font-size: 42px; font-weight: light;" : "font-size: 70px; font-weight: bold;") + QString(" margin: 45px;")); - main_layout->addWidget(rich ? (QWidget*)new ScrollView(prompt, this) : (QWidget*)prompt, 1, Qt::AlignTop); - - // cancel + confirm buttons - QHBoxLayout *btn_layout = new QHBoxLayout(); - btn_layout->setSpacing(30); - main_layout->addLayout(btn_layout); - - if (cancel_text.length()) { - QPushButton* cancel_btn = new QPushButton(cancel_text); - btn_layout->addWidget(cancel_btn); - QObject::connect(cancel_btn, &QPushButton::clicked, this, &ConfirmationDialog::reject); - } - - if (confirm_text.length()) { - QPushButton* confirm_btn = new QPushButton(confirm_text); - confirm_btn->setObjectName("confirm_btn"); - btn_layout->addWidget(confirm_btn); - QObject::connect(confirm_btn, &QPushButton::clicked, this, &ConfirmationDialog::accept); - } - - QVBoxLayout *outer_layout = new QVBoxLayout(this); - int margin = rich ? 100 : 200; - outer_layout->setContentsMargins(margin, margin, margin, margin); - outer_layout->addWidget(container); -} - -bool ConfirmationDialog::alert(const QString &prompt_text, QWidget *parent) { - ConfirmationDialog d(prompt_text, tr("Ok"), "", false, parent); - return d.exec(); -} - -bool ConfirmationDialog::confirm(const QString &prompt_text, const QString &confirm_text, QWidget *parent) { - ConfirmationDialog d(prompt_text, confirm_text, tr("Cancel"), false, parent); - return d.exec(); -} - -bool ConfirmationDialog::rich(const QString &prompt_text, QWidget *parent) { - ConfirmationDialog d(prompt_text, tr("Ok"), "", true, parent); - return d.exec(); -} - -// MultiOptionDialog - -MultiOptionDialog::MultiOptionDialog(const QString &prompt_text, const QStringList &l, const QString ¤t, QWidget *parent) : DialogBase(parent) { - QFrame *container = new QFrame(this); - container->setStyleSheet(R"( - QFrame { background-color: #1B1B1B; } - #confirm_btn[enabled="false"] { background-color: #2B2B2B; } - #confirm_btn:enabled { background-color: #465BEA; } - #confirm_btn:enabled:pressed { background-color: #3049F4; } - )"); - - QVBoxLayout *main_layout = new QVBoxLayout(container); - main_layout->setContentsMargins(55, 50, 55, 50); - - QLabel *title = new QLabel(prompt_text, this); - title->setStyleSheet("font-size: 70px; font-weight: 500;"); - main_layout->addWidget(title, 0, Qt::AlignLeft | Qt::AlignTop); - main_layout->addSpacing(25); - - QWidget *listWidget = new QWidget(this); - QVBoxLayout *listLayout = new QVBoxLayout(listWidget); - listLayout->setSpacing(20); - listWidget->setStyleSheet(R"( - QPushButton { - height: 135; - padding: 0px 50px; - text-align: left; - font-size: 55px; - font-weight: 300; - border-radius: 10px; - background-color: #4F4F4F; - } - QPushButton:checked { background-color: #465BEA; } - )"); - - QButtonGroup *group = new QButtonGroup(listWidget); - group->setExclusive(true); - - QPushButton *confirm_btn = new QPushButton(tr("Select")); - confirm_btn->setObjectName("confirm_btn"); - confirm_btn->setEnabled(false); - - for (const QString &s : l) { - QPushButton *selectionLabel = new QPushButton(s); - selectionLabel->setCheckable(true); - selectionLabel->setChecked(s == current); - QObject::connect(selectionLabel, &QPushButton::toggled, [=](bool checked) { - if (checked) selection = s; - if (selection != current) { - confirm_btn->setEnabled(true); - } else { - confirm_btn->setEnabled(false); - } - }); - - group->addButton(selectionLabel); - listLayout->addWidget(selectionLabel); - } - // add stretch to keep buttons spaced correctly - listLayout->addStretch(1); - - ScrollView *scroll_view = new ScrollView(listWidget, this); - scroll_view->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); - - main_layout->addWidget(scroll_view); - main_layout->addSpacing(35); - - // cancel + confirm buttons - QHBoxLayout *blayout = new QHBoxLayout; - main_layout->addLayout(blayout); - blayout->setSpacing(50); - - QPushButton *cancel_btn = new QPushButton(tr("Cancel")); - QObject::connect(cancel_btn, &QPushButton::clicked, this, &ConfirmationDialog::reject); - QObject::connect(confirm_btn, &QPushButton::clicked, this, &ConfirmationDialog::accept); - blayout->addWidget(cancel_btn); - blayout->addWidget(confirm_btn); - - QVBoxLayout *outer_layout = new QVBoxLayout(this); - outer_layout->setContentsMargins(50, 50, 50, 50); - outer_layout->addWidget(container); -} - -QString MultiOptionDialog::getSelection(const QString &prompt_text, const QStringList &l, const QString ¤t, QWidget *parent) { - MultiOptionDialog d(prompt_text, l, current, parent); - if (d.exec()) { - return d.selection; - } - return ""; -} diff --git a/selfdrive/ui/qt/widgets/input.h b/selfdrive/ui/qt/widgets/input.h deleted file mode 100644 index 089e54e4a..000000000 --- a/selfdrive/ui/qt/widgets/input.h +++ /dev/null @@ -1,71 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -#include "selfdrive/ui/qt/widgets/keyboard.h" - - -class DialogBase : public QDialog { - Q_OBJECT - -protected: - DialogBase(QWidget *parent); - bool eventFilter(QObject *o, QEvent *e) override; - -public slots: - int exec() override; -}; - -class InputDialog : public DialogBase { - Q_OBJECT - -public: - explicit InputDialog(const QString &title, QWidget *parent, const QString &subtitle = "", bool secret = false); - static QString getText(const QString &title, QWidget *parent, const QString &subtitle = "", - bool secret = false, int minLength = -1, const QString &defaultText = ""); - QString text(); - void setMessage(const QString &message, bool clearInputField = true); - void setMinLength(int length); - void show(); - -private: - int minLength; - QLineEdit *line; - Keyboard *k; - QLabel *label; - QLabel *sublabel; - QVBoxLayout *main_layout; - QPushButton *eye_btn; - -private slots: - void handleEnter(); - -signals: - void cancel(); - void emitText(const QString &text); -}; - -class ConfirmationDialog : public DialogBase { - Q_OBJECT - -public: - explicit ConfirmationDialog(const QString &prompt_text, const QString &confirm_text, - const QString &cancel_text, const bool rich, QWidget* parent); - static bool alert(const QString &prompt_text, QWidget *parent); - static bool confirm(const QString &prompt_text, const QString &confirm_text, QWidget *parent); - static bool rich(const QString &prompt_text, QWidget *parent); -}; - -class MultiOptionDialog : public DialogBase { - Q_OBJECT - -public: - explicit MultiOptionDialog(const QString &prompt_text, const QStringList &l, const QString ¤t, QWidget *parent); - static QString getSelection(const QString &prompt_text, const QStringList &l, const QString ¤t, QWidget *parent); - QString selection; -}; diff --git a/selfdrive/ui/qt/widgets/keyboard.cc b/selfdrive/ui/qt/widgets/keyboard.cc deleted file mode 100644 index 9ead27b8d..000000000 --- a/selfdrive/ui/qt/widgets/keyboard.cc +++ /dev/null @@ -1,182 +0,0 @@ -#include "selfdrive/ui/qt/widgets/keyboard.h" - -#include - -#include -#include -#include -#include -#include - -const QString BACKSPACE_KEY = "⌫"; -const QString ENTER_KEY = "→"; -const QString SHIFT_KEY = "⇧"; -const QString CAPS_LOCK_KEY = "⇪"; - -const QMap KEY_STRETCH = {{" ", 3}, {ENTER_KEY, 2}}; - -const QStringList CONTROL_BUTTONS = {SHIFT_KEY, CAPS_LOCK_KEY, "ABC", "#+=", "123", BACKSPACE_KEY, ENTER_KEY}; - -const float key_spacing_vertical = 20; -const float key_spacing_horizontal = 15; - -KeyButton::KeyButton(const QString &text, QWidget *parent) : QPushButton(text, parent) { - setAttribute(Qt::WA_AcceptTouchEvents); - setFocusPolicy(Qt::NoFocus); -} - -bool KeyButton::event(QEvent *event) { - if (event->type() == QEvent::TouchBegin || event->type() == QEvent::TouchEnd) { - QTouchEvent *touchEvent = static_cast(event); - if (!touchEvent->touchPoints().empty()) { - const QEvent::Type mouseType = event->type() == QEvent::TouchBegin ? QEvent::MouseButtonPress : QEvent::MouseButtonRelease; - QMouseEvent mouseEvent(mouseType, touchEvent->touchPoints().front().pos(), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); - QPushButton::event(&mouseEvent); - event->accept(); - parentWidget()->update(); - return true; - } - } - return QPushButton::event(event); -} - -KeyboardLayout::KeyboardLayout(QWidget* parent, const std::vector>& layout) : QWidget(parent) { - QVBoxLayout* main_layout = new QVBoxLayout(this); - main_layout->setMargin(0); - main_layout->setSpacing(0); - - QButtonGroup* btn_group = new QButtonGroup(this); - QObject::connect(btn_group, SIGNAL(buttonClicked(QAbstractButton*)), parent, SLOT(handleButton(QAbstractButton*))); - - for (const auto &s : layout) { - QHBoxLayout *hlayout = new QHBoxLayout; - hlayout->setSpacing(0); - - if (main_layout->count() == 1) { - hlayout->addSpacing(90); - } - - for (const QString &p : s) { - KeyButton* btn = new KeyButton(p); - if (p == BACKSPACE_KEY) { - btn->setAutoRepeat(true); - } else if (p == ENTER_KEY) { - btn->setStyleSheet(R"( - QPushButton { - background-color: #465BEA; - } - QPushButton:pressed { - background-color: #444444; - } - )"); - } - btn->setFixedHeight(135 + key_spacing_vertical); - btn_group->addButton(btn); - hlayout->addWidget(btn, KEY_STRETCH.value(p, 1)); - } - - if (main_layout->count() == 1) { - hlayout->addSpacing(90); - } - - main_layout->addLayout(hlayout); - } - - setStyleSheet(QString(R"( - QPushButton { - font-size: 75px; - margin-left: %1px; - margin-right: %1px; - margin-top: %2px; - margin-bottom: %2px; - padding: 0px; - border-radius: 10px; - color: #dddddd; - background-color: #444444; - } - QPushButton:pressed { - background-color: #333333; - } - )").arg(key_spacing_vertical / 2).arg(key_spacing_horizontal / 2)); -} - -Keyboard::Keyboard(QWidget *parent) : QFrame(parent) { - main_layout = new QStackedLayout(this); - main_layout->setMargin(0); - - // lowercase - std::vector> 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}, - {"123", "/", "-", " ", ".", ENTER_KEY}, - }; - main_layout->addWidget(new KeyboardLayout(this, lowercase)); - - // uppercase - std::vector> uppercase = { - {"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}, - {"123", "/", "-", " ", ".", ENTER_KEY}, - }; - main_layout->addWidget(new KeyboardLayout(this, uppercase)); - - // numbers + specials - std::vector> numbers = { - {"1", "2", "3", "4", "5", "6", "7", "8", "9", "0"}, - {"-", "/", ":", ";", "(", ")", "$", "&&", "@", "\""}, - {"#+=", ".", ",", "?", "!", "`", BACKSPACE_KEY}, - {"ABC", " ", ".", ENTER_KEY}, - }; - main_layout->addWidget(new KeyboardLayout(this, numbers)); - - // extra specials - std::vector> specials = { - {"[", "]", "{", "}", "#", "%", "^", "*", "+", "="}, - {"_", "\\", "|", "~", "<", ">", "€", "£", "¥", "•"}, - {"123", ".", ",", "?", "!", "'", BACKSPACE_KEY}, - {"ABC", " ", ".", ENTER_KEY}, - }; - main_layout->addWidget(new KeyboardLayout(this, specials)); - - main_layout->setCurrentIndex(0); -} - -void Keyboard::handleCapsPress() { - shift_state = (shift_state + 1) % 3; - bool is_uppercase = shift_state > 0; - main_layout->setCurrentIndex(is_uppercase); - - for (KeyButton* btn : main_layout->currentWidget()->findChildren()) { - if (btn->text() == SHIFT_KEY || btn->text() == CAPS_LOCK_KEY) { - btn->setText(shift_state == 2 ? CAPS_LOCK_KEY : SHIFT_KEY); - btn->setStyleSheet(is_uppercase ? "background-color: #465BEA;" : ""); - } - } -} - -void Keyboard::handleButton(QAbstractButton* btn) { - const QString &key = btn->text(); - if (CONTROL_BUTTONS.contains(key)) { - if (key == "ABC" || key == "123" || key == "#+=") { - int index = (key == "ABC") ? 0 : (key == "123" ? 2 : 3); - main_layout->setCurrentIndex(index); - shift_state = 0; - } else if (key == SHIFT_KEY || key == CAPS_LOCK_KEY) { - handleCapsPress(); - } else if (key == ENTER_KEY) { - main_layout->setCurrentIndex(0); - shift_state = 0; - emit emitEnter(); - } else if (key == BACKSPACE_KEY) { - emit emitBackspace(); - } - } else { - if (shift_state == 1 && "A" <= key && key <= "Z") { - main_layout->setCurrentIndex(0); - shift_state = 0; - } - emit emitKey(key); - } -} diff --git a/selfdrive/ui/qt/widgets/keyboard.h b/selfdrive/ui/qt/widgets/keyboard.h deleted file mode 100644 index e61617283..000000000 --- a/selfdrive/ui/qt/widgets/keyboard.h +++ /dev/null @@ -1,42 +0,0 @@ -#pragma once - -#include - -#include -#include -#include - -class KeyButton : public QPushButton { - Q_OBJECT - -public: - KeyButton(const QString &text, QWidget *parent = 0); - bool event(QEvent *event) override; -}; - -class KeyboardLayout : public QWidget { - Q_OBJECT - -public: - explicit KeyboardLayout(QWidget* parent, const std::vector>& layout); -}; - -class Keyboard : public QFrame { - Q_OBJECT - -public: - explicit Keyboard(QWidget *parent = 0); - -private: - QStackedLayout* main_layout; - int shift_state = 0; - -private slots: - void handleButton(QAbstractButton* m_button); - void handleCapsPress(); - -signals: - void emitKey(const QString &s); - void emitBackspace(); - void emitEnter(); -}; diff --git a/selfdrive/ui/qt/widgets/offroad_alerts.cc b/selfdrive/ui/qt/widgets/offroad_alerts.cc deleted file mode 100644 index 3a4828a2a..000000000 --- a/selfdrive/ui/qt/widgets/offroad_alerts.cc +++ /dev/null @@ -1,138 +0,0 @@ -#include "selfdrive/ui/qt/widgets/offroad_alerts.h" - -#include -#include -#include -#include - -#include -#include -#include - -#include "common/util.h" -#include "system/hardware/hw.h" -#include "selfdrive/ui/qt/widgets/scrollview.h" - -AbstractAlert::AbstractAlert(bool hasRebootBtn, QWidget *parent) : QFrame(parent) { - QVBoxLayout *main_layout = new QVBoxLayout(this); - main_layout->setMargin(50); - main_layout->setSpacing(30); - - QWidget *widget = new QWidget; - scrollable_layout = new QVBoxLayout(widget); - widget->setStyleSheet("background-color: transparent;"); - main_layout->addWidget(new ScrollView(widget)); - - // bottom footer, dismiss + reboot buttons - QHBoxLayout *footer_layout = new QHBoxLayout(); - main_layout->addLayout(footer_layout); - - QPushButton *dismiss_btn = new QPushButton(tr("Close")); - dismiss_btn->setFixedSize(400, 125); - footer_layout->addWidget(dismiss_btn, 0, Qt::AlignBottom | Qt::AlignLeft); - QObject::connect(dismiss_btn, &QPushButton::clicked, this, &AbstractAlert::dismiss); - - action_btn = new QPushButton(); - action_btn->setVisible(false); - action_btn->setFixedHeight(125); - footer_layout->addWidget(action_btn, 0, Qt::AlignBottom | Qt::AlignRight); - QObject::connect(action_btn, &QPushButton::clicked, [=]() { - if (!alerts["Offroad_ExcessiveActuation"]->text().isEmpty()) { - params.remove("Offroad_ExcessiveActuation"); - } else { - params.putBool("SnoozeUpdate", true); - } - }); - QObject::connect(action_btn, &QPushButton::clicked, this, &AbstractAlert::dismiss); - action_btn->setStyleSheet("color: white; background-color: #4F4F4F; padding-left: 60px; padding-right: 60px;"); - - if (hasRebootBtn) { - QPushButton *rebootBtn = new QPushButton(tr("Reboot and Update")); - rebootBtn->setFixedSize(600, 125); - footer_layout->addWidget(rebootBtn, 0, Qt::AlignBottom | Qt::AlignRight); - QObject::connect(rebootBtn, &QPushButton::clicked, [=]() { Hardware::reboot(); }); - } - - setStyleSheet(R"( - * { - font-size: 48px; - color: white; - } - QFrame { - border-radius: 30px; - background-color: #393939; - } - QPushButton { - color: black; - font-weight: 500; - border-radius: 30px; - background-color: white; - } - )"); -} - -int OffroadAlert::refresh() { - // build widgets for each offroad alert on first refresh - if (alerts.empty()) { - QString json = util::read_file("../selfdrived/alerts_offroad.json").c_str(); - QJsonObject obj = QJsonDocument::fromJson(json.toUtf8()).object(); - - // descending sort labels by severity - std::vector> sorted; - for (auto it = obj.constBegin(); it != obj.constEnd(); ++it) { - sorted.push_back({it.key().toStdString(), it.value()["severity"].toInt()}); - } - std::sort(sorted.begin(), sorted.end(), [=](auto &l, auto &r) { return l.second > r.second; }); - - for (auto &[key, severity] : sorted) { - QLabel *l = new QLabel(this); - alerts[key] = l; - l->setMargin(60); - l->setWordWrap(true); - l->setStyleSheet(QString("background-color: %1").arg(severity ? "#E22C2C" : "#292929")); - scrollable_layout->addWidget(l); - } - scrollable_layout->addStretch(1); - } - - int alertCount = 0; - for (const auto &[key, label] : alerts) { - QString text; - std::string bytes = params.get(key); - if (bytes.size()) { - auto doc_par = QJsonDocument::fromJson(bytes.c_str()); - text = tr(doc_par["text"].toString().toUtf8().data()); - auto extra = doc_par["extra"].toString(); - if (!extra.isEmpty()) { - text = text.arg(extra); - } - } - label->setText(text); - label->setVisible(!text.isEmpty()); - alertCount += !text.isEmpty(); - } - - action_btn->setVisible(!alerts["Offroad_ExcessiveActuation"]->text().isEmpty() || !alerts["Offroad_ConnectivityNeeded"]->text().isEmpty()); - if (!alerts["Offroad_ExcessiveActuation"]->text().isEmpty()) { - action_btn->setText(tr("Acknowledge Excessive Actuation")); - } else { - action_btn->setText(tr("Snooze Update")); - } - - return alertCount; -} - -UpdateAlert::UpdateAlert(QWidget *parent) : AbstractAlert(true, parent) { - releaseNotes = new QLabel(this); - releaseNotes->setWordWrap(true); - releaseNotes->setAlignment(Qt::AlignTop); - scrollable_layout->addWidget(releaseNotes); -} - -bool UpdateAlert::refresh() { - bool updateAvailable = params.getBool("UpdateAvailable"); - if (updateAvailable) { - releaseNotes->setText(params.get("UpdaterNewReleaseNotes").c_str()); - } - return updateAvailable; -} diff --git a/selfdrive/ui/qt/widgets/offroad_alerts.h b/selfdrive/ui/qt/widgets/offroad_alerts.h deleted file mode 100644 index 2dcf4f9d8..000000000 --- a/selfdrive/ui/qt/widgets/offroad_alerts.h +++ /dev/null @@ -1,44 +0,0 @@ -#pragma once - -#include -#include - -#include -#include -#include - -#include "common/params.h" - -class AbstractAlert : public QFrame { - Q_OBJECT - -protected: - AbstractAlert(bool hasRebootBtn, QWidget *parent = nullptr); - - QPushButton *action_btn; - QVBoxLayout *scrollable_layout; - Params params; - std::map alerts; - -signals: - void dismiss(); -}; - -class UpdateAlert : public AbstractAlert { - Q_OBJECT - -public: - UpdateAlert(QWidget *parent = 0); - bool refresh(); - -private: - QLabel *releaseNotes = nullptr; -}; - -class OffroadAlert : public AbstractAlert { - Q_OBJECT - -public: - explicit OffroadAlert(QWidget *parent = 0) : AbstractAlert(false, parent) {} - int refresh(); -}; diff --git a/selfdrive/ui/qt/widgets/prime.cc b/selfdrive/ui/qt/widgets/prime.cc deleted file mode 100644 index ee820c46a..000000000 --- a/selfdrive/ui/qt/widgets/prime.cc +++ /dev/null @@ -1,265 +0,0 @@ -#include "selfdrive/ui/qt/widgets/prime.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "selfdrive/ui/qt/request_repeater.h" -#include "selfdrive/ui/qt/util.h" -#include "selfdrive/ui/qt/qt_window.h" -#include "selfdrive/ui/qt/widgets/wifi.h" - -using qrcodegen::QrCode; - -PairingQRWidget::PairingQRWidget(QWidget* parent) : QWidget(parent) { - timer = new QTimer(this); - connect(timer, &QTimer::timeout, this, &PairingQRWidget::refresh); -} - -void PairingQRWidget::showEvent(QShowEvent *event) { - refresh(); - timer->start(5 * 60 * 1000); - device()->setOffroadBrightness(100); -} - -void PairingQRWidget::hideEvent(QHideEvent *event) { - timer->stop(); - device()->setOffroadBrightness(BACKLIGHT_OFFROAD); -} - -void PairingQRWidget::refresh() { - QString pairToken = CommaApi::create_jwt({{"pair", true}}); - QString qrString = "https://connect.comma.ai/?pair=" + pairToken; - this->updateQrCode(qrString); - update(); -} - -void PairingQRWidget::updateQrCode(const QString &text) { - QrCode qr = QrCode::encodeText(text.toUtf8().data(), QrCode::Ecc::LOW); - qint32 sz = qr.getSize(); - QImage im(sz, sz, QImage::Format_RGB32); - - QRgb black = qRgb(0, 0, 0); - QRgb white = qRgb(255, 255, 255); - for (int y = 0; y < sz; y++) { - for (int x = 0; x < sz; x++) { - im.setPixel(x, y, qr.getModule(x, y) ? black : white); - } - } - - // Integer division to prevent anti-aliasing - int final_sz = ((width() / sz) - 1) * sz; - img = QPixmap::fromImage(im.scaled(final_sz, final_sz, Qt::KeepAspectRatio), Qt::MonoOnly); -} - -void PairingQRWidget::paintEvent(QPaintEvent *e) { - QPainter p(this); - p.fillRect(rect(), Qt::white); - - QSize s = (size() - img.size()) / 2; - p.drawPixmap(s.width(), s.height(), img); -} - - -PairingPopup::PairingPopup(QWidget *parent) : DialogBase(parent) { - QHBoxLayout *hlayout = new QHBoxLayout(this); - hlayout->setContentsMargins(0, 0, 0, 0); - hlayout->setSpacing(0); - - setStyleSheet("PairingPopup { background-color: #E0E0E0; }"); - - // text - QVBoxLayout *vlayout = new QVBoxLayout(); - vlayout->setContentsMargins(85, 70, 50, 70); - vlayout->setSpacing(50); - hlayout->addLayout(vlayout, 1); - { - QPushButton *close = new QPushButton(QIcon(":/icons/close.svg"), "", this); - close->setIconSize(QSize(80, 80)); - close->setStyleSheet("border: none;"); - vlayout->addWidget(close, 0, Qt::AlignLeft); - QObject::connect(close, &QPushButton::clicked, this, &QDialog::reject); - - vlayout->addSpacing(30); - - QLabel *title = new QLabel(tr("Pair your device to your comma account"), this); - title->setStyleSheet("font-size: 75px; color: black;"); - title->setWordWrap(true); - vlayout->addWidget(title); - - QLabel *instructions = new QLabel(QString(R"( -
    -
  1. %1
  2. -
  3. %2
  4. -
  5. %3
  6. -
- )").arg(tr("Go to https://connect.comma.ai on your phone")) - .arg(tr("Click \"add new device\" and scan the QR code on the right")) - .arg(tr("Bookmark connect.comma.ai to your home screen to use it like an app")), this); - - instructions->setStyleSheet("font-size: 47px; font-weight: bold; color: black;"); - instructions->setWordWrap(true); - vlayout->addWidget(instructions); - - vlayout->addStretch(); - } - - // QR code - PairingQRWidget *qr = new PairingQRWidget(this); - hlayout->addWidget(qr, 1); -} - -int PairingPopup::exec() { - if (!util::system_time_valid()) { - ConfirmationDialog::alert(tr("Please connect to Wi-Fi to complete initial pairing"), parentWidget()); - return QDialog::Rejected; - } - return DialogBase::exec(); -} - - -PrimeUserWidget::PrimeUserWidget(QWidget *parent) : QFrame(parent) { - setObjectName("primeWidget"); - QVBoxLayout *mainLayout = new QVBoxLayout(this); - mainLayout->setContentsMargins(56, 40, 56, 40); - mainLayout->setSpacing(20); - - QLabel *subscribed = new QLabel(tr("✓ SUBSCRIBED")); - subscribed->setStyleSheet("font-size: 41px; font-weight: bold; color: #86FF4E;"); - mainLayout->addWidget(subscribed); - - QLabel *commaPrime = new QLabel(tr("comma prime")); - commaPrime->setStyleSheet("font-size: 75px; font-weight: bold;"); - mainLayout->addWidget(commaPrime); -} - - -PrimeAdWidget::PrimeAdWidget(QWidget* parent) : QFrame(parent) { - QVBoxLayout *main_layout = new QVBoxLayout(this); - main_layout->setContentsMargins(80, 90, 80, 60); - main_layout->setSpacing(0); - - QLabel *upgrade = new QLabel(tr("Upgrade Now")); - upgrade->setStyleSheet("font-size: 75px; font-weight: bold;"); - main_layout->addWidget(upgrade, 0, Qt::AlignTop); - main_layout->addSpacing(50); - - QLabel *description = new QLabel(tr("Become a comma prime member at connect.comma.ai")); - description->setStyleSheet("font-size: 56px; font-weight: light; color: white;"); - description->setWordWrap(true); - main_layout->addWidget(description, 0, Qt::AlignTop); - - main_layout->addStretch(); - - QLabel *features = new QLabel(tr("PRIME FEATURES:")); - features->setStyleSheet("font-size: 41px; font-weight: bold; color: #E5E5E5;"); - main_layout->addWidget(features, 0, Qt::AlignBottom); - main_layout->addSpacing(30); - - QVector bullets = {tr("Remote access"), tr("24/7 LTE connectivity"), tr("1 year of drive storage"), tr("Remote snapshots")}; - for (auto &b : bullets) { - const QString check = " "; - QLabel *l = new QLabel(check + b); - l->setAlignment(Qt::AlignLeft); - l->setStyleSheet("font-size: 50px; margin-bottom: 15px;"); - main_layout->addWidget(l, 0, Qt::AlignBottom); - } - - setStyleSheet(R"( - PrimeAdWidget { - border-radius: 10px; - background-color: #333333; - } - )"); -} - - -SetupWidget::SetupWidget(QWidget* parent) : QFrame(parent) { - mainLayout = new QStackedWidget; - - // Unpaired, registration prompt layout - - QFrame* finishRegistration = new QFrame; - finishRegistration->setObjectName("primeWidget"); - QVBoxLayout* finishRegistrationLayout = new QVBoxLayout(finishRegistration); - finishRegistrationLayout->setSpacing(38); - finishRegistrationLayout->setContentsMargins(64, 48, 64, 48); - - QLabel* registrationTitle = new QLabel(tr("Finish Setup")); - registrationTitle->setStyleSheet("font-size: 75px; font-weight: bold;"); - finishRegistrationLayout->addWidget(registrationTitle); - - QLabel* registrationDescription = new QLabel(tr("Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer.")); - registrationDescription->setWordWrap(true); - registrationDescription->setStyleSheet("font-size: 50px; font-weight: light;"); - finishRegistrationLayout->addWidget(registrationDescription); - - finishRegistrationLayout->addStretch(); - - QPushButton* pair = new QPushButton(tr("Pair device")); - pair->setStyleSheet(R"( - QPushButton { - font-size: 55px; - font-weight: 500; - border-radius: 10px; - background-color: #465BEA; - padding: 64px; - } - QPushButton:pressed { - background-color: #3049F4; - } - )"); - finishRegistrationLayout->addWidget(pair); - - popup = new PairingPopup(this); - QObject::connect(pair, &QPushButton::clicked, popup, &PairingPopup::exec); - - mainLayout->addWidget(finishRegistration); - - // build stacked layout - QVBoxLayout *outer_layout = new QVBoxLayout(this); - outer_layout->setContentsMargins(0, 0, 0, 0); - outer_layout->addWidget(mainLayout); - - QWidget *content = new QWidget; - QVBoxLayout *content_layout = new QVBoxLayout(content); - content_layout->setContentsMargins(0, 0, 0, 0); - content_layout->setSpacing(30); - - WiFiPromptWidget *wifi_prompt = new WiFiPromptWidget; - QObject::connect(wifi_prompt, &WiFiPromptWidget::openSettings, this, &SetupWidget::openSettings); - content_layout->addWidget(wifi_prompt); - content_layout->addStretch(); - - mainLayout->addWidget(content); - - mainLayout->setCurrentIndex(1); - - setStyleSheet(R"( - #primeWidget { - border-radius: 10px; - background-color: #333333; - } - )"); - - // Retain size while hidden - QSizePolicy sp_retain = sizePolicy(); - sp_retain.setRetainSizeWhenHidden(true); - setSizePolicy(sp_retain); - - QObject::connect(uiState()->prime_state, &PrimeState::changed, [this](PrimeState::Type type) { - if (type == PrimeState::PRIME_TYPE_UNPAIRED) { - mainLayout->setCurrentIndex(0); // Display "Pair your device" widget - } else { - popup->reject(); - mainLayout->setCurrentIndex(1); // Display Wi-Fi prompt widget - } - }); -} diff --git a/selfdrive/ui/qt/widgets/prime.h b/selfdrive/ui/qt/widgets/prime.h deleted file mode 100644 index 266a90a92..000000000 --- a/selfdrive/ui/qt/widgets/prime.h +++ /dev/null @@ -1,70 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -#include "selfdrive/ui/qt/widgets/input.h" - -// pairing QR code -class PairingQRWidget : public QWidget { - Q_OBJECT - -public: - explicit PairingQRWidget(QWidget* parent = 0); - void paintEvent(QPaintEvent*) override; - -private: - QPixmap img; - QTimer *timer; - void updateQrCode(const QString &text); - void showEvent(QShowEvent *event) override; - void hideEvent(QHideEvent *event) override; - -private slots: - void refresh(); -}; - - -// pairing popup widget -class PairingPopup : public DialogBase { - Q_OBJECT - -public: - explicit PairingPopup(QWidget* parent); - int exec() override; -}; - - -// widget for paired users with prime -class PrimeUserWidget : public QFrame { - Q_OBJECT - -public: - explicit PrimeUserWidget(QWidget* parent = 0); -}; - - -// widget for paired users without prime -class PrimeAdWidget : public QFrame { - Q_OBJECT -public: - explicit PrimeAdWidget(QWidget* parent = 0); -}; - - -// container widget -class SetupWidget : public QFrame { - Q_OBJECT - -public: - explicit SetupWidget(QWidget* parent = 0); - -signals: - void openSettings(int index = 0, const QString ¶m = ""); - -private: - PairingPopup *popup; - QStackedWidget *mainLayout; -}; diff --git a/selfdrive/ui/qt/widgets/scrollview.cc b/selfdrive/ui/qt/widgets/scrollview.cc deleted file mode 100644 index 978bf83a6..000000000 --- a/selfdrive/ui/qt/widgets/scrollview.cc +++ /dev/null @@ -1,49 +0,0 @@ -#include "selfdrive/ui/qt/widgets/scrollview.h" - -#include -#include - -// TODO: disable horizontal scrolling and resize - -ScrollView::ScrollView(QWidget *w, QWidget *parent) : QScrollArea(parent) { - setWidget(w); - setWidgetResizable(true); - setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - setStyleSheet("background-color: transparent; border:none"); - - QString style = R"( - QScrollBar:vertical { - border: none; - background: transparent; - width: 10px; - margin: 0; - } - QScrollBar::handle:vertical { - min-height: 0px; - border-radius: 5px; - background-color: white; - } - QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { - height: 0px; - } - QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { - background: none; - } - )"; - verticalScrollBar()->setStyleSheet(style); - horizontalScrollBar()->setStyleSheet(style); - - QScroller *scroller = QScroller::scroller(this->viewport()); - QScrollerProperties sp = scroller->scrollerProperties(); - - sp.setScrollMetric(QScrollerProperties::VerticalOvershootPolicy, QVariant::fromValue(QScrollerProperties::OvershootAlwaysOff)); - sp.setScrollMetric(QScrollerProperties::HorizontalOvershootPolicy, QVariant::fromValue(QScrollerProperties::OvershootAlwaysOff)); - sp.setScrollMetric(QScrollerProperties::MousePressEventDelay, 0.01); - scroller->grabGesture(this->viewport(), QScroller::LeftMouseButtonGesture); - scroller->setScrollerProperties(sp); -} - -void ScrollView::hideEvent(QHideEvent *e) { - verticalScrollBar()->setValue(0); -} diff --git a/selfdrive/ui/qt/widgets/scrollview.h b/selfdrive/ui/qt/widgets/scrollview.h deleted file mode 100644 index 024331aa3..000000000 --- a/selfdrive/ui/qt/widgets/scrollview.h +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once - -#include - -class ScrollView : public QScrollArea { - Q_OBJECT - -public: - explicit ScrollView(QWidget *w = nullptr, QWidget *parent = nullptr); -protected: - void hideEvent(QHideEvent *e) override; -}; diff --git a/selfdrive/ui/qt/widgets/ssh_keys.cc b/selfdrive/ui/qt/widgets/ssh_keys.cc deleted file mode 100644 index 26743952d..000000000 --- a/selfdrive/ui/qt/widgets/ssh_keys.cc +++ /dev/null @@ -1,64 +0,0 @@ -#include "selfdrive/ui/qt/widgets/ssh_keys.h" - -#include "common/params.h" -#include "selfdrive/ui/qt/api.h" -#include "selfdrive/ui/qt/widgets/input.h" - -SshControl::SshControl() : - ButtonControl(tr("SSH Keys"), "", tr("Warning: This grants SSH access to all public keys in your GitHub settings. Never enter a GitHub username " - "other than your own. A comma employee will NEVER ask you to add their GitHub username.")) { - - QObject::connect(this, &ButtonControl::clicked, [=]() { - if (text() == tr("ADD")) { - QString username = InputDialog::getText(tr("Enter your GitHub username"), this); - if (username.length() > 0) { - setText(tr("LOADING")); - setEnabled(false); - getUserKeys(username); - } - } else { - params.remove("GithubUsername"); - params.remove("GithubSshKeys"); - refresh(); - } - }); - - refresh(); -} - -void SshControl::refresh() { - QString param = QString::fromStdString(params.get("GithubSshKeys")); - if (param.length()) { - setValue(QString::fromStdString(params.get("GithubUsername"))); - setText(tr("REMOVE")); - } else { - setValue(""); - setText(tr("ADD")); - } - setEnabled(true); -} - -void SshControl::getUserKeys(const QString &username) { - HttpRequest *request = new HttpRequest(this, false); - QObject::connect(request, &HttpRequest::requestDone, [=](const QString &resp, bool success) { - if (success) { - if (!resp.isEmpty()) { - params.put("GithubUsername", username.toStdString()); - params.put("GithubSshKeys", resp.toStdString()); - } else { - ConfirmationDialog::alert(tr("Username '%1' has no keys on GitHub").arg(username), this); - } - } else { - if (request->timeout()) { - ConfirmationDialog::alert(tr("Request timed out"), this); - } else { - ConfirmationDialog::alert(tr("Username '%1' doesn't exist on GitHub").arg(username), this); - } - } - - refresh(); - request->deleteLater(); - }); - - request->sendRequest("https://github.com/" + username + ".keys"); -} diff --git a/selfdrive/ui/qt/widgets/ssh_keys.h b/selfdrive/ui/qt/widgets/ssh_keys.h deleted file mode 100644 index 920bd651e..000000000 --- a/selfdrive/ui/qt/widgets/ssh_keys.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -#include - -#include "system/hardware/hw.h" -#include "selfdrive/ui/qt/widgets/controls.h" - -// SSH enable toggle -class SshToggle : public ToggleControl { - Q_OBJECT - -public: - SshToggle() : ToggleControl(tr("Enable SSH"), "", "", Hardware::get_ssh_enabled()) { - QObject::connect(this, &SshToggle::toggleFlipped, [=](bool state) { - Hardware::set_ssh_enabled(state); - }); - } -}; - -// SSH key management widget -class SshControl : public ButtonControl { - Q_OBJECT - -public: - SshControl(); - -private: - Params params; - - void refresh(); - void getUserKeys(const QString &username); -}; diff --git a/selfdrive/ui/qt/widgets/toggle.cc b/selfdrive/ui/qt/widgets/toggle.cc deleted file mode 100644 index 82302ad5b..000000000 --- a/selfdrive/ui/qt/widgets/toggle.cc +++ /dev/null @@ -1,83 +0,0 @@ -#include "selfdrive/ui/qt/widgets/toggle.h" - -#include - -Toggle::Toggle(QWidget *parent) : QAbstractButton(parent), -_height(80), -_height_rect(60), -on(false), -_anim(new QPropertyAnimation(this, "offset_circle", this)) -{ - _radius = _height / 2; - _x_circle = _radius; - _y_circle = _radius; - _y_rect = (_height - _height_rect)/2; - circleColor = QColor(0xffffff); // placeholder - green = QColor(0xffffff); // placeholder - setEnabled(true); -} - -void Toggle::paintEvent(QPaintEvent *e) { - this->setFixedHeight(_height); - QPainter p(this); - p.setPen(Qt::NoPen); - p.setRenderHint(QPainter::Antialiasing, true); - - // Draw toggle background left - p.setBrush(green); - p.drawRoundedRect(QRect(0, _y_rect, _x_circle + _radius, _height_rect), _height_rect/2, _height_rect/2); - - // Draw toggle background right - p.setBrush(QColor(0x393939)); - p.drawRoundedRect(QRect(_x_circle - _radius, _y_rect, width() - (_x_circle - _radius), _height_rect), _height_rect/2, _height_rect/2); - - // Draw toggle circle - p.setBrush(circleColor); - p.drawEllipse(QRectF(_x_circle - _radius, _y_circle - _radius, 2 * _radius, 2 * _radius)); -} - -void Toggle::mouseReleaseEvent(QMouseEvent *e) { - if (!enabled) { - return; - } - const int left = _radius; - const int right = width() - _radius; - if ((_x_circle != left && _x_circle != right) || !this->rect().contains(e->localPos().toPoint())) { - // If mouse release isn't in rect or animation is running, don't parse touch events - return; - } - if (e->button() & Qt::LeftButton) { - togglePosition(); - emit stateChanged(on); - } -} - -void Toggle::togglePosition() { - on = !on; - const int left = _radius; - const int right = width() - _radius; - _anim->setStartValue(on ? left + immediateOffset : right - immediateOffset); - _anim->setEndValue(on ? right : left); - _anim->setDuration(animation_duration); - _anim->start(); - repaint(); -} - -void Toggle::enterEvent(QEvent *e) { - QAbstractButton::enterEvent(e); -} - -bool Toggle::getEnabled() { - return enabled; -} - -void Toggle::setEnabled(bool value) { - enabled = value; - if (value) { - circleColor.setRgb(0xfafafa); - green.setRgb(0x33ab4c); - } else { - circleColor.setRgb(0x888888); - green.setRgb(0x227722); - } -} diff --git a/selfdrive/ui/qt/widgets/toggle.h b/selfdrive/ui/qt/widgets/toggle.h deleted file mode 100644 index e7263a008..000000000 --- a/selfdrive/ui/qt/widgets/toggle.h +++ /dev/null @@ -1,44 +0,0 @@ -#pragma once - -#include -#include -#include - -class Toggle : public QAbstractButton { - Q_OBJECT - Q_PROPERTY(int offset_circle READ offset_circle WRITE set_offset_circle CONSTANT) - -public: - Toggle(QWidget* parent = nullptr); - void togglePosition(); - bool on; - int animation_duration = 150; - int immediateOffset = 0; - int offset_circle() const { - return _x_circle; - } - - void set_offset_circle(int o) { - _x_circle = o; - update(); - } - bool getEnabled(); - void setEnabled(bool value); - -protected: - void paintEvent(QPaintEvent*) override; - void mouseReleaseEvent(QMouseEvent*) override; - void enterEvent(QEvent*) override; - -private: - QColor circleColor; - QColor green; - bool enabled = true; - int _x_circle, _y_circle; - int _height, _radius; - int _height_rect, _y_rect; - QPropertyAnimation *_anim = nullptr; - -signals: - void stateChanged(bool new_state); -}; diff --git a/selfdrive/ui/qt/widgets/wifi.cc b/selfdrive/ui/qt/widgets/wifi.cc deleted file mode 100644 index d7eb8beeb..000000000 --- a/selfdrive/ui/qt/widgets/wifi.cc +++ /dev/null @@ -1,45 +0,0 @@ -#include "selfdrive/ui/qt/widgets/wifi.h" - -#include -#include -#include -#include - -WiFiPromptWidget::WiFiPromptWidget(QWidget *parent) : QFrame(parent) { - // Setup Firehose Mode - QVBoxLayout *main_layout = new QVBoxLayout(this); - main_layout->setContentsMargins(56, 40, 56, 40); - main_layout->setSpacing(42); - - QLabel *title = new QLabel(tr("🔥 Firehose Mode 🔥")); - title->setStyleSheet("font-size: 64px; font-weight: 500;"); - main_layout->addWidget(title); - - QLabel *desc = new QLabel(tr("Maximize your training data uploads to improve openpilot's driving models.")); - desc->setStyleSheet("font-size: 40px; font-weight: 400;"); - desc->setWordWrap(true); - main_layout->addWidget(desc); - - QPushButton *settings_btn = new QPushButton(tr("Open")); - connect(settings_btn, &QPushButton::clicked, [=]() { emit openSettings(1, "FirehosePanel"); }); - settings_btn->setStyleSheet(R"( - QPushButton { - font-size: 48px; - font-weight: 500; - border-radius: 10px; - background-color: #465BEA; - padding: 32px; - } - QPushButton:pressed { - background-color: #3049F4; - } - )"); - main_layout->addWidget(settings_btn); - - setStyleSheet(R"( - WiFiPromptWidget { - background-color: #333333; - border-radius: 10px; - } - )"); -} \ No newline at end of file diff --git a/selfdrive/ui/qt/widgets/wifi.h b/selfdrive/ui/qt/widgets/wifi.h deleted file mode 100644 index 3e68a15b7..000000000 --- a/selfdrive/ui/qt/widgets/wifi.h +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -#include -#include - -class WiFiPromptWidget : public QFrame { - Q_OBJECT - -public: - explicit WiFiPromptWidget(QWidget* parent = 0); - -signals: - void openSettings(int index = 0, const QString ¶m = ""); -}; diff --git a/selfdrive/ui/qt/window.cc b/selfdrive/ui/qt/window.cc deleted file mode 100644 index 6b579fcc5..000000000 --- a/selfdrive/ui/qt/window.cc +++ /dev/null @@ -1,98 +0,0 @@ -#include "selfdrive/ui/qt/window.h" - -#include - -#include "system/hardware/hw.h" - -MainWindow::MainWindow(QWidget *parent) : QWidget(parent) { - main_layout = new QStackedLayout(this); - main_layout->setMargin(0); - - homeWindow = new HomeWindow(this); - main_layout->addWidget(homeWindow); - QObject::connect(homeWindow, &HomeWindow::openSettings, this, &MainWindow::openSettings); - QObject::connect(homeWindow, &HomeWindow::closeSettings, this, &MainWindow::closeSettings); - - settingsWindow = new SettingsWindow(this); - main_layout->addWidget(settingsWindow); - QObject::connect(settingsWindow, &SettingsWindow::closeSettings, this, &MainWindow::closeSettings); - QObject::connect(settingsWindow, &SettingsWindow::reviewTrainingGuide, [=]() { - onboardingWindow->showTrainingGuide(); - main_layout->setCurrentWidget(onboardingWindow); - }); - QObject::connect(settingsWindow, &SettingsWindow::showDriverView, [=] { - homeWindow->showDriverView(true); - }); - - onboardingWindow = new OnboardingWindow(this); - main_layout->addWidget(onboardingWindow); - QObject::connect(onboardingWindow, &OnboardingWindow::onboardingDone, [=]() { - main_layout->setCurrentWidget(homeWindow); - }); - if (!onboardingWindow->completed()) { - main_layout->setCurrentWidget(onboardingWindow); - } - - QObject::connect(uiState(), &UIState::offroadTransition, [=](bool offroad) { - if (!offroad) { - closeSettings(); - } - }); - QObject::connect(device(), &Device::interactiveTimeout, [=]() { - if (main_layout->currentWidget() == settingsWindow) { - closeSettings(); - } - }); - - // load fonts - QFontDatabase::addApplicationFont("../assets/fonts/Inter-Black.ttf"); - QFontDatabase::addApplicationFont("../assets/fonts/Inter-Bold.ttf"); - QFontDatabase::addApplicationFont("../assets/fonts/Inter-ExtraBold.ttf"); - QFontDatabase::addApplicationFont("../assets/fonts/Inter-ExtraLight.ttf"); - QFontDatabase::addApplicationFont("../assets/fonts/Inter-Medium.ttf"); - QFontDatabase::addApplicationFont("../assets/fonts/Inter-Regular.ttf"); - QFontDatabase::addApplicationFont("../assets/fonts/Inter-SemiBold.ttf"); - QFontDatabase::addApplicationFont("../assets/fonts/Inter-Thin.ttf"); - QFontDatabase::addApplicationFont("../assets/fonts/JetBrainsMono-Medium.ttf"); - - // no outline to prevent the focus rectangle - setStyleSheet(R"( - * { - font-family: Inter; - outline: none; - } - )"); - setAttribute(Qt::WA_NoSystemBackground); -} - -void MainWindow::openSettings(int index, const QString ¶m) { - main_layout->setCurrentWidget(settingsWindow); - settingsWindow->setCurrentPanel(index, param); -} - -void MainWindow::closeSettings() { - main_layout->setCurrentWidget(homeWindow); - - if (uiState()->scene.started) { - homeWindow->showSidebar(false); - } -} - -bool MainWindow::eventFilter(QObject *obj, QEvent *event) { - bool ignore = false; - switch (event->type()) { - case QEvent::TouchBegin: - case QEvent::TouchUpdate: - case QEvent::TouchEnd: - case QEvent::MouseButtonPress: - case QEvent::MouseMove: { - // ignore events when device is awakened by resetInteractiveTimeout - ignore = !device()->isAwake(); - device()->resetInteractiveTimeout(); - break; - } - default: - break; - } - return ignore; -} diff --git a/selfdrive/ui/qt/window.h b/selfdrive/ui/qt/window.h deleted file mode 100644 index 05b61e1f7..000000000 --- a/selfdrive/ui/qt/window.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -#include -#include - -#include "selfdrive/ui/qt/home.h" -#include "selfdrive/ui/qt/offroad/onboarding.h" -#include "selfdrive/ui/qt/offroad/settings.h" - -class MainWindow : public QWidget { - Q_OBJECT - -public: - explicit MainWindow(QWidget *parent = 0); - -private: - bool eventFilter(QObject *obj, QEvent *event) override; - void openSettings(int index = 0, const QString ¶m = ""); - void closeSettings(); - - QStackedLayout *main_layout; - HomeWindow *homeWindow; - SettingsWindow *settingsWindow; - OnboardingWindow *onboardingWindow; -}; diff --git a/selfdrive/ui/tests/create_test_translations.sh b/selfdrive/ui/tests/create_test_translations.sh deleted file mode 100755 index 1587a8820..000000000 --- a/selfdrive/ui/tests/create_test_translations.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env bash - -set -e - -UI_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"/.. -TEST_TEXT="(WRAPPED_SOURCE_TEXT)" -TEST_TS_FILE=$UI_DIR/translations/test_en.ts -TEST_QM_FILE=$UI_DIR/translations/test_en.qm - -# translation strings -UNFINISHED="<\/translation>" -TRANSLATED="$TEST_TEXT<\/translation>" - -mkdir -p $UI_DIR/translations -rm -f $TEST_TS_FILE $TEST_QM_FILE -lupdate -recursive "$UI_DIR" -ts $TEST_TS_FILE -sed -i "s/$UNFINISHED/$TRANSLATED/" $TEST_TS_FILE -lrelease $TEST_TS_FILE diff --git a/selfdrive/ui/tests/test_runner.cc b/selfdrive/ui/tests/test_runner.cc deleted file mode 100644 index 4bde92169..000000000 --- a/selfdrive/ui/tests/test_runner.cc +++ /dev/null @@ -1,26 +0,0 @@ -#define CATCH_CONFIG_RUNNER -#include "catch2/catch.hpp" - -#include -#include -#include -#include - -int main(int argc, char **argv) { - // unit tests for Qt - QApplication app(argc, argv); - - QString language_file = "test_en"; - // FIXME: pytest-cpp considers this print as a test case - qDebug() << "Loading language:" << language_file; - - QTranslator translator; - QString translationsPath = QDir::cleanPath(qApp->applicationDirPath() + "/../translations"); - if (!translator.load(language_file, translationsPath)) { - qDebug() << "Failed to load translation file!"; - } - app.installTranslator(&translator); - - const int res = Catch::Session().run(argc, argv); - return (res < 0xff ? res : 0xff); -} diff --git a/selfdrive/ui/tests/test_translations.cc b/selfdrive/ui/tests/test_translations.cc deleted file mode 100644 index fcefc5784..000000000 --- a/selfdrive/ui/tests/test_translations.cc +++ /dev/null @@ -1,48 +0,0 @@ -#include "catch2/catch.hpp" - -#include "common/params.h" -#include "selfdrive/ui/qt/window.h" - -const QString TEST_TEXT = "(WRAPPED_SOURCE_TEXT)"; // what each string should be translated to -QRegExp RE_NUM("\\d*"); - -QStringList getParentWidgets(QWidget* widget){ - QStringList parentWidgets; - while (widget->parentWidget() != Q_NULLPTR) { - widget = widget->parentWidget(); - parentWidgets.append(widget->metaObject()->className()); - } - return parentWidgets; -} - -template -void checkWidgetTrWrap(MainWindow &w) { - for (auto widget : w.findChildren()) { - const QString text = widget->text(); - bool isNumber = RE_NUM.exactMatch(text); - bool wrapped = text.contains(TEST_TEXT); - QString parentWidgets = getParentWidgets(widget).join("->"); - - if (!text.isEmpty() && !isNumber && !wrapped) { - FAIL(("\"" + text + "\" must be wrapped. Parent widgets: " + parentWidgets).toStdString()); - } - - // warn if source string wrapped, but UI adds text - // TODO: add way to ignore this - if (wrapped && text != TEST_TEXT) { - WARN(("\"" + text + "\" is dynamic and needs a custom retranslate function. Parent widgets: " + parentWidgets).toStdString()); - } - } -} - -// Tests all strings in the UI are wrapped with tr() -TEST_CASE("UI: test all strings wrapped") { - Params().remove("LanguageSetting"); - Params().remove("HardwareSerial"); - Params().remove("DongleId"); - qputenv("TICI", "1"); - - MainWindow w; - checkWidgetTrWrap(w); - checkWidgetTrWrap(w); -} diff --git a/selfdrive/ui/tests/test_translations.py b/selfdrive/ui/tests/test_translations.py index edd9a3041..5308a44ea 100644 --- a/selfdrive/ui/tests/test_translations.py +++ b/selfdrive/ui/tests/test_translations.py @@ -6,8 +6,7 @@ import xml.etree.ElementTree as ET import string import requests from parameterized import parameterized_class - -from openpilot.selfdrive.ui.update_translations import TRANSLATIONS_DIR, LANGUAGES_FILE +from openpilot.system.ui.lib.multilang import TRANSLATIONS_DIR, LANGUAGES_FILE with open(LANGUAGES_FILE) as f: translation_files = json.load(f) @@ -17,6 +16,7 @@ LOCATION_TAG = " - - - - AbstractAlert - - Close - إغلاق - - - Reboot and Update - إعادة التشغيل والتحديث - - - - AdvancedNetworking - - Back - السابق - - - Enable Tethering - تمكين الربط - - - Tethering Password - كلمة مرور الربط - - - EDIT - تعديل - - - Enter new tethering password - أدخل كلمة مرور الربط الجديدة - - - IP Address - عنوان IP - - - Enable Roaming - تمكين التجوال - - - APN Setting - إعدادات APN - - - Enter APN - إدخال APN - - - leave blank for automatic configuration - اتركه فارغاً من أجل التكوين التلقائي - - - Cellular Metered - محدود بالاتصال الخلوي - - - Hidden Network - شبكة مخفية - - - CONNECT - الاتصال - - - Enter SSID - أدخل SSID - - - Enter password - أدخل كلمة المرور - - - for "%1" - من أجل "%1" - - - Prevent large data uploads when on a metered cellular connection - - - - default - - - - metered - - - - unmetered - - - - Wi-Fi Network Metered - - - - Prevent large data uploads when on a metered Wi-Fi connection - - - - - ConfirmationDialog - - Ok - موافق - - - Cancel - إلغاء - - - - DeclinePage - - You must accept the Terms and Conditions in order to use openpilot. - يجب عليك قبول الشروط والأحكام من أجل استخدام openpilot. - - - Back - السابق - - - Decline, uninstall %1 - رفض، إلغاء التثبيت %1 - - - - DeveloperPanel - - Joystick Debug Mode - وضع تصحيح أخطاء عصا التحكم - - - Longitudinal Maneuver Mode - وضع المناورة الطولية - - - openpilot Longitudinal Control (Alpha) - التحكم الطولي openpilot (ألفا) - - - WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB). - تحذير: التحكم الطولي في openpilot في المرحلة ألفا لهذه السيارة، وسيقوم بتعطيل مكابح الطوارئ الآلية (AEB). - - - On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha. - في هذه السيارة يعمل openpilot افتراضياً بالشكل المدمج في التحكم التكيفي في السرعة بدلاً من التحكم الطولي. قم بتمكين هذا الخيار من أجل الانتقال إلى التحكم الطولي. يوصى بتمكين الوضع التجريبي عند استخدام وضع التحكم الطولي ألفا من openpilot. - - - Enable ADB - تمكين ADB - - - ADB (Android Debug Bridge) allows connecting to your device over USB or over the network. See https://docs.comma.ai/how-to/connect-to-comma for more info. - أداة ADB (Android Debug Bridge) تسمح بالاتصال بجهازك عبر USB أو عبر الشبكة. راجع هذا الرابط: https://docs.comma.ai/how-to/connect-to-comma لمزيد من المعلومات. - - - - DevicePanel - - Dongle ID - معرف دونجل - - - N/A - غير متاح - - - Serial - الرقم التسلسلي - - - Driver Camera - كاميرة السائق - - - PREVIEW - معاينة - - - Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off) - قم بمعاينة الكاميرا المواجهة للسائق للتأكد من أن نظام مراقبة السائق يتمتع برؤية جيدة. (يجب أن تكون السيارة متوقفة) - - - Reset Calibration - إعادة ضبط المعايرة - - - RESET - إعادة الضبط - - - Are you sure you want to reset calibration? - هل أنت متأكد أنك تريد إعادة ضبط المعايرة؟ - - - Review Training Guide - مراجعة دليل التدريب - - - REVIEW - مراجعة - - - Review the rules, features, and limitations of openpilot - مراجعة الأدوار والميزات والقيود في openpilot - - - Are you sure you want to review the training guide? - هل أنت متأكد أنك تريد مراجعة دليل التدريب؟ - - - Regulatory - التنظيمية - - - VIEW - عرض - - - Change Language - تغيير اللغة - - - CHANGE - تغيير - - - Select a language - اختر لغة - - - Reboot - إعادة التشغيل - - - Power Off - إيقاف التشغيل - - - Your device is pointed %1° %2 and %3° %4. - يشير جهازك إلى %1 درجة %2، و%3 درجة %4. - - - down - نحو الأسفل - - - up - نحو الأعلى - - - left - نحو اليسار - - - right - نحو اليمين - - - Are you sure you want to reboot? - هل أنت متأكد أنك تريد إعادة التشغيل؟ - - - Disengage to Reboot - فك الارتباط من أجل إعادة التشغيل - - - Are you sure you want to power off? - هل أنت متأكد أنك تريد إيقاف التشغيل؟ - - - Disengage to Power Off - فك الارتباط من أجل إيقاف التشغيل - - - Reset - إعادة الضبط - - - Review - مراجعة - - - Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer. - اقرن جهازك بجهاز (connect.comma.ai) واحصل على عرضك من comma prime. - - - Pair Device - إقران الجهاز - - - PAIR - إقران - - - Disengage to Reset Calibration - - - - openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. - - - - openpilot is continuously calibrating, resetting is rarely required. Resetting calibration will restart openpilot if the car is powered on. - - - - - -Steering lag calibration is %1% complete. - - - - - -Steering lag calibration is complete. - - - - Steering torque response calibration is %1% complete. - - - - Steering torque response calibration is complete. - - - - - DriverViewWindow - - camera starting - بدء تشغيل الكاميرا - - - - ExperimentalModeButton - - EXPERIMENTAL MODE ON - تشغيل الوضع التجريبي - - - CHILL MODE ON - تشغيل وضع الراحة - - - - FirehosePanel - - openpilot learns to drive by watching humans, like you, drive. - -Firehose Mode allows you to maximize your training data uploads to improve openpilot's driving models. More data means bigger models, which means better Experimental Mode. - يتعلم تطبيق openpilot كيفية القيادة من خلال مشاهدة البشر، مثلك، أثناء القيادة. - -يتيح لك وضع خرطوم الحريق زيادة تحميلات بيانات التدريب لتحسين نماذج القيادة في OpenPilot. كلما زادت البيانات، زادت النماذج، مما يعني وضعًا تجريبيًا أفضل. - - - Firehose Mode: ACTIVE - وضع خرطوم الحريق: نشط - - - ACTIVE - نشط - - - For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.<br><br>Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.<br><br><br><b>Frequently Asked Questions</b><br><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><br><i>Do all of my segments get pulled in Firehose Mode?</i> No, we selectively pull a subset of your segments.<br><br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><br><i>Does it matter which software I run?</i> Yes, only upstream openpilot (and particular forks) are able to be used for training. - للحصول على أقصى فعالية، أحضر جهازك إلى الداخل واتصل بمحول USB-C جيد وشبكة Wi-Fi أسبوعياً.<br><br>يمكن أن يعمل وضع خرطوم الحريق أيضاً أثناء القيادة إذا كنت متصلاً بنقطة اتصال أو ببطاقة SIM غير محدودة.<br><br><br><b>الأسئلة المتكررة</b><br><br><i>هل يهم كيف أو أين أقود؟</i> لا، فقط قد كما تفعل عادة.<br><br><i>هل يتم سحب كل مقاطع رحلاتي في وضع خرطوم الحريق؟</i> لا، نقوم بسحب مجموعة مختارة من مقاطع رحلاتك.<br><br><i>ما هو محول USB-C الجيد؟</i> أي شاحن سريع للهاتف أو اللابتوب يجب أن يكون مناسباً.<br><br><i>هل يهم أي برنامج أستخدم؟</i> نعم، فقط النسخة الأصلية من openpilot (وأفرع معينة) يمكن استخدامها للتدريب. - - - <b>%n segment(s)</b> of your driving is in the training dataset so far. - - حتى الآن، يوجد </b>%n مقطع<b>%n من قيادتك في مجموعة بيانات التدريب. - حتى الآن، يوجد </b>%n مقطع<b>%n من قيادتك في مجموعة بيانات التدريب. - حتى الآن، يوجد </b>%n مقطع<b>%n من قيادتك في مجموعة بيانات التدريب. - حتى الآن، يوجد </b>%n مقطع<b>%n من قيادتك في مجموعة بيانات التدريب. - حتى الآن، يوجد </b>%n مقطع<b>%n من قيادتك في مجموعة بيانات التدريب. - حتى الآن، يوجد </b>%n مقطع<b>%n من قيادتك في مجموعة بيانات التدريب. - - - - <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to an unmetered network - - - - Firehose Mode - - - - - HudRenderer - - km/h - كم/س - - - mph - ميل/س - - - MAX - MAX - - - - InputDialog - - Cancel - إلغاء - - - Need at least %n character(s)! - - تحتاج إلى حرف %n على الأقل! - تحتاج إلى حرف %n على الأقل! - تحتاج إلى حرفين %n على الأقل! - تحتاج إلى %n أحرف على الأقل! - تحتاج إلى %n أحرف على الأقل! - تحتاج إلى %n حرف على الأقل! - - - - - MultiOptionDialog - - Select - اختيار - - - Cancel - إلغاء - - - - Networking - - Advanced - متقدم - - - Enter password - أدخل كلمة المرور - - - for "%1" - من أجل "%1" - - - Wrong password - كلمة مرور خاطئة - - - - OffroadAlert - - Device temperature too high. System cooling down before starting. Current internal component temperature: %1 - درجة حرارة الجهاز مرتفعة جداً. يقوم النظام بالتبريد قبل البدء. درجة الحرارة الحالية للمكونات الداخلية: %1 - - - Immediately connect to the internet to check for updates. If you do not connect to the internet, openpilot won't engage in %1 - اتصل فوراً بالإنترنت للتحقق من وجود تحديثات. إذا لم تكم متصلاً بالإنترنت فإن openpilot لن يساهم في %1 - - - Connect to internet to check for updates. openpilot won't automatically start until it connects to internet to check for updates. - اتصل بالإنترنت للتحقق من وجود تحديثات. لا يعمل openpilot تلقائياً إلا إذا اتصل بالإنترنت من أجل التحقق من التحديثات. - - - Unable to download updates -%1 - غير قادر على تحميل التحديثات -%1 - - - Taking camera snapshots. System won't start until finished. - التقاط لقطات كاميرا. لن يبدأ النظام حتى تنتهي هذه العملية. - - - An update to your device's operating system is downloading in the background. You will be prompted to update when it's ready to install. - يتم تنزيل تحديث لنظام تشغيل جهازك في الخلفية. سيطلَب منك التحديث عندما يصبح جاهزاً للتثبيت. - - - openpilot was unable to identify your car. Your car is either unsupported or its ECUs are not recognized. Please submit a pull request to add the firmware versions to the proper vehicle. Need help? Join discord.comma.ai. - لم يكن openpilot قادراً على تحديد سيارتك. إما أن تكون سيارتك غير مدعومة أو أنه لم يتم التعرف على وحدة التحكم الإلكتروني (ECUs) فيها. يرجى تقديم طلب سحب من أجل إضافة نسخ برمجيات ثابتة إلى السيارة المناسبة. هل تحتاج إلى أي مساعدة؟ لا تتردد في التواصل مع doscord.comma.ai. - - - openpilot detected a change in the device's mounting position. Ensure the device is fully seated in the mount and the mount is firmly secured to the windshield. - لقد اكتشف openpilot تغييراً في موقع تركيب الجهاز. تأكد من تثبيت الجهاز بشكل كامل في موقعه وتثبيته بإحكام على الزجاج الأمامي. - - - Device failed to register with the comma.ai backend. It will not connect or upload to comma.ai servers, and receives no support from comma.ai. If this is a device purchased at comma.ai/shop, open a ticket at https://comma.ai/support. - - - - Acknowledge Excessive Actuation - - - - Snooze Update - تأخير التحديث - - - openpilot detected excessive %1 actuation on your last drive. Please contact support at https://comma.ai/support and share your device's Dongle ID for troubleshooting. - - - - - OffroadHome - - UPDATE - تحديث - - - ALERTS - التنبهات - - - ALERT - تنبيه - - - - OnroadAlerts - - openpilot Unavailable - openpilot غير متوفر - - - TAKE CONTROL IMMEDIATELY - تحكم على الفور - - - Reboot Device - إعادة التشغيل - - - Waiting to start - في انتظار البدء - - - System Unresponsive - النظام لا يستجيب - - - - PairingPopup - - Pair your device to your comma account - اقرن جهازك مع حسابك على comma - - - Go to https://connect.comma.ai on your phone - انتقل إلى https://connect.comma.ai على جوالك - - - Click "add new device" and scan the QR code on the right - انقر "،إضافة جهاز جديد"، وامسح رمز الاستجابة السريعة (QR) على اليمين - - - Bookmark connect.comma.ai to your home screen to use it like an app - اجعل لـconnect.comma.ai إشارة مرجعية على شاشتك الرئيسية من أجل استخدامه مثل أي تطبيق - - - Please connect to Wi-Fi to complete initial pairing - يرجى الاتصال بشبكة الواي فاي لإكمال الاقتران الأولي - - - - ParamControl - - Enable - تمكين - - - Cancel - إلغاء - - - - PrimeAdWidget - - Upgrade Now - الترقية الآن - - - Become a comma prime member at connect.comma.ai - كن عضوًا في comma prime على connect.comma.ai - - - PRIME FEATURES: - الميزات الأساسية: - - - Remote access - التحكم عن بعد - - - 24/7 LTE connectivity - اتصال LTE على مدار الساعة 24/7 - - - 1 year of drive storage - سنة واحدة من تخزين القرص - - - Remote snapshots - لقطات عن بُعد - - - - PrimeUserWidget - - ✓ SUBSCRIBED - ✓ مشترك - - - comma prime - comma prime - - - - QObject - - openpilot - openpilot - - - %n minute(s) ago - - منذ %n دقيقة - منذ %n دقيقة - منذ دقيقتين %n - منذ %n دقائق - منذ %n دقائق - منذ %n دقيقة - - - - %n hour(s) ago - - منذ %n ساعة - منذ %n ساعة - منذ ساعتين %n - منذ %n ساعات - منذ %n ساعات - منذ %n ساعة - - - - %n day(s) ago - - منذ %n يوم - منذ %n يوم - منذ يومين %n - منذ %n أيام - منذ %n أيام - منذ %n يوم - - - - now - الآن - - - - SettingsWindow - - × - × - - - Device - الجهاز - - - Network - الشبكة - - - Toggles - المثبتتات - - - Software - البرنامج - - - Developer - المطور - - - Firehose - خرطوم الحريق - - - - SetupWidget - - Finish Setup - إنهاء الإعداد - - - Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer. - اقرن جهازك بجهاز (connect.comma.ai) واحصل على عرضك من comma prime. - - - Pair device - اقتران الجهاز - - - - Sidebar - - CONNECT - الاتصال - - - OFFLINE - غير متصل - - - ONLINE - متصل - - - ERROR - خطأ - - - TEMP - درجة الحرارة - - - HIGH - مرتفع - - - GOOD - جيد - - - OK - موافق - - - VEHICLE - المركبة - - - NO - لا - - - PANDA - PANDA - - - -- - -- - - - Wi-Fi - Wi-Fi - - - ETH - ETH - - - 2G - 2G - - - 3G - 3G - - - LTE - LTE - - - 5G - 5G - - - - SoftwarePanel - - UNINSTALL - إلغاء التثبيت - - - Uninstall %1 - إلغاء التثبيت %1 - - - Are you sure you want to uninstall? - هل أنت متأكد أنك تريد إلغاء التثبيت؟ - - - CHECK - التحقق - - - Updates are only downloaded while the car is off. - يتم تحميل التحديثات فقط عندما تكون السيارة متوقفة. - - - Current Version - النسخة الحالية - - - Download - تنزيل - - - Install Update - تثبيت التحديث - - - INSTALL - تثبيت - - - Target Branch - فرع الهدف - - - SELECT - اختيار - - - Select a branch - اختر فرعاً - - - Uninstall - إلغاء التثبيت - - - failed to check for update - فشل التحقق من التحديث - - - DOWNLOAD - تنزيل - - - update available - يتوفر تحديث - - - never - إطلاقاً - - - up to date, last checked %1 - أحدث نسخة، آخر تحقق %1 - - - - SshControl - - SSH Keys - مفاتيح SSH - - - Warning: This grants SSH access to all public keys in your GitHub settings. Never enter a GitHub username other than your own. A comma employee will NEVER ask you to add their GitHub username. - تنبيه: هذا يمنح SSH إمكانية الوصول إلى جميع المفاتيح العامة في إعدادات GitHub. لا تقم بإدخال اسم مستخدم GitHub بدلاً من اسمك. لن تطلب منك comma employee إطلاقاً أن تضيف اسم مستخدم GitHub الخاص بهم. - - - ADD - إضافة - - - Enter your GitHub username - ادخل اسم المستخدم GitHub الخاص بك - - - LOADING - يتم التحميل - - - REMOVE - إزالة - - - Username '%1' has no keys on GitHub - لا يحتوي اسم المستخدم '%1' أي مفاتيح على GitHub - - - Request timed out - انتهى وقت الطلب - - - Username '%1' doesn't exist on GitHub - اسم المستخدم '%1' غير موجود على GitHub - - - - SshToggle - - Enable SSH - تمكين SSH - - - - TermsPage - - Decline - رفض - - - Agree - أوافق - - - Welcome to openpilot - مرحباً بكم في openpilot - - - You must accept the Terms and Conditions to use openpilot. Read the latest terms at <span style='color: #465BEA;'>https://comma.ai/terms</span> before continuing. - يجب عليك قبول الشروط والأحكام لاستخدام openpilot. اقرأ أحدث الشروط على <span style='color: #465BEA;'>https://comma.ai/terms</span> قبل الاستمرار. - - - - TogglesPanel - - Enable openpilot - تمكين openpilot - - - Enable Lane Departure Warnings - قم بتمكين تحذيرات مغادرة المسار - - - 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). - تلقي التنبيهات من أجل الالتفاف للعودة إلى المسار عندما تنحرف سيارتك فوق الخط المحدد للمسار دون تشغيل إشارة الانعطاف عند القيادة لمسافة تزيد عن 31 ميل/سا (50 كم/سا). - - - Use Metric System - استخدام النظام المتري - - - Display speed in km/h instead of mph. - عرض السرعة بواحدات كم/سا بدلاً من ميل/سا. - - - Record and Upload Driver Camera - تسجيل وتحميل كاميرا السائق - - - Upload data from the driver facing camera and help improve the driver monitoring algorithm. - تحميل البيانات من الكاميرا المواجهة للسائق، والمساعدة في تحسين خوارزمية مراقبة السائق. - - - Disengage on Accelerator Pedal - فك الارتباط عن دواسة الوقود - - - When enabled, pressing the accelerator pedal will disengage openpilot. - عند تمكين هذه الميزة، فإن الضغط على دواسة الوقود سيؤدي إلى فك ارتباط openpilot. - - - Experimental Mode - الوضع التجريبي - - - Aggressive - الهجومي - - - Standard - القياسي - - - Relaxed - الراحة - - - Driving Personality - شخصية القيادة - - - openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: - يتم وضع openpilot بشكل قياسي في <b>وضعية الراحة</b>. يمكن الوضع التجريبي <b>ميزات المستوى ألفا</b> التي لا تكون جاهزة في وضع الراحة: - - - End-to-End Longitudinal Control - التحكم الطولي من طرف إلى طرف - - - Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. - دع نظام القيادة يتحكم بالوقود والمكابح. سيقوم openpilot بالقيادة كما لو أنه كائن بشري، بما في ذلك التوقف عند الإشارة الحمراء، وإشارات التوقف. وبما أن نمط القيادة يحدد سرعة القيادة، فإن السرعة المضبوطة تشكل الحد الأقصى فقط. هذه خاصية الجودة ألفا، فيجب توقع حدوث الأخطاء. - - - New Driving Visualization - تصور القيادة الديد - - - Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. - الوضع التجريبي غير متوفر حالياً في هذه السيارة نظراً لاستخدام رصيد التحكم التكيفي بالسرعة من أجل التحكم الطولي. - - - openpilot longitudinal control may come in a future update. - قد يتم الحصول على التحكم الطولي في openpilot في عمليات التحديث المستقبلية. - - - An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. - يمكن اختبار نسخة ألفا من التحكم الطولي من openpilot، مع الوضع التجريبي، لكن على الفروع غير المطلقة. - - - Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. - تمكين التحكم الطولي من openpilot (ألفا) للسماح بالوضع التجريبي. - - - Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. - يوصى بالمعيار. في الوضع العدواني، سيتبع الطيار المفتوح السيارات الرائدة بشكل أقرب ويكون أكثر عدوانية مع البنزين والفرامل. في الوضع المريح، سيبقى openpilot بعيدًا عن السيارات الرائدة. في السيارات المدعومة، يمكنك التنقل بين هذه الشخصيات باستخدام زر مسافة عجلة القيادة. - - - The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. - ستتحول واجهة القيادة إلى الكاميرا الواسعة المواجهة للطريق عند السرعات المنخفضة لعرض بعض المنعطفات بشكل أفضل. كما سيتم عرض شعار وضع التجريبي في الزاوية العلوية اليمنى. - - - Always-On Driver Monitoring - مراقبة السائق المستمرة - - - Enable driver monitoring even when openpilot is not engaged. - تمكين مراقبة السائق حتى عندما لا يكون نظام OpenPilot مُفعّلاً. - - - Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. - - - - Changing this setting will restart openpilot if the car is powered on. - - - - Record and Upload Microphone Audio - - - - Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect. - - - - - WiFiPromptWidget - - Open - انفتح - - - Maximize your training data uploads to improve openpilot's driving models. - قم بزيادة تحميلات بيانات التدريب الخاصة بك لتحسين نماذج القيادة الخاصة بـ openpilot. - - - <span style='font-family: "Noto Color Emoji";'>🔥</span> Firehose Mode <span style='font-family: Noto Color Emoji;'>🔥</span> - <span style='font-family: "Noto Color Emoji";'>🔥</span> وضع خرطوم الحريق <span style='font-family: Noto Color Emoji;'>🔥</span> - - - - WifiUI - - Scanning for networks... - يتم البحث عن شبكات... - - - CONNECTING... - يتم الاتصال... - - - FORGET - نسيان هذه الشبكة - - - Forget Wi-Fi Network "%1"? - هل تريد نسيان شبكة الواي فاي "%1"؟ - - - Forget - نسيان - - - diff --git a/selfdrive/ui/translations/create_badges.py b/selfdrive/ui/translations/create_badges.py deleted file mode 100755 index 3e14c3325..000000000 --- a/selfdrive/ui/translations/create_badges.py +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/env python3 -import json -import os -import requests -import xml.etree.ElementTree as ET - -from openpilot.common.basedir import BASEDIR -from openpilot.selfdrive.ui.tests.test_translations import UNFINISHED_TRANSLATION_TAG -from openpilot.selfdrive.ui.update_translations import LANGUAGES_FILE, TRANSLATIONS_DIR - -TRANSLATION_TAG = " 90 else (204, 55, 27)}" - - # Download badge - badge_label = f"LANGUAGE {name}" - badge_message = f"{percent_finished}% complete" - if unfinished_translations != 0: - badge_message += f" ({unfinished_translations} unfinished)" - - r = requests.get(f"{SHIELDS_URL}/{badge_label}-{badge_message}-{color}", timeout=10) - assert r.status_code == 200, "Error downloading badge" - content_svg = r.content.decode("utf-8") - - xml = ET.fromstring(content_svg) - assert "width" in xml.attrib - max_badge_width = max(max_badge_width, int(xml.attrib["width"])) - - # Make tag ids in each badge unique to combine them into one svg - for tag in ("r", "s"): - content_svg = content_svg.replace(f'id="{tag}"', f'id="{tag}{idx}"') - content_svg = content_svg.replace(f'"url(#{tag})"', f'"url(#{tag}{idx})"') - - badge_svg.extend([f'', content_svg, ""]) - - badge_svg.insert(0, '') - badge_svg.append("") - - with open(os.path.join(BASEDIR, "translation_badge.svg"), "w") as badge_f: - badge_f.write("\n".join(badge_svg)) diff --git a/selfdrive/ui/translations/de.ts b/selfdrive/ui/translations/de.ts deleted file mode 100644 index 28b07029e..000000000 --- a/selfdrive/ui/translations/de.ts +++ /dev/null @@ -1,1072 +0,0 @@ - - - - - AbstractAlert - - Close - Schließen - - - Reboot and Update - Aktualisieren und neu starten - - - - AdvancedNetworking - - Back - Zurück - - - Enable Tethering - Tethering aktivieren - - - Tethering Password - Tethering Passwort - - - EDIT - ÄNDERN - - - Enter new tethering password - Neues tethering Passwort eingeben - - - IP Address - IP Adresse - - - Enable Roaming - Roaming aktivieren - - - APN Setting - APN Einstellungen - - - Enter APN - APN eingeben - - - leave blank for automatic configuration - für automatische Konfiguration leer lassen - - - Cellular Metered - Getaktete Verbindung - - - Hidden Network - Verborgenes Netzwerk - - - CONNECT - VERBINDEN - - - Enter SSID - SSID eingeben - - - Enter password - Passwort eingeben - - - for "%1" - für "%1" - - - Prevent large data uploads when on a metered cellular connection - - - - default - - - - metered - - - - unmetered - - - - Wi-Fi Network Metered - - - - Prevent large data uploads when on a metered Wi-Fi connection - - - - - ConfirmationDialog - - Ok - Ok - - - Cancel - Abbrechen - - - - DeclinePage - - You must accept the Terms and Conditions in order to use openpilot. - Du musst die Nutzungsbedingungen akzeptieren, um Openpilot zu benutzen. - - - Back - Zurück - - - Decline, uninstall %1 - Ablehnen, deinstallieren %1 - - - - DeveloperPanel - - Joystick Debug Mode - Joystick Debug-Modus - - - Longitudinal Maneuver Mode - Längsmanöver-Modus - - - openpilot Longitudinal Control (Alpha) - openpilot Längsregelung (Alpha) - - - WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB). - WARNUNG: Die openpilot Längsregelung befindet sich für dieses Fahrzeug im Alpha-Stadium und deaktiviert das automatische Notbremsen (AEB). - - - On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha. - Bei diesem Fahrzeug verwendet openpilot standardmäßig den eingebauten Tempomaten anstelle der openpilot Längsregelung. Aktiviere diese Option, um auf die openpilot Längsregelung umzuschalten. Es wird empfohlen, den experimentellen Modus zu aktivieren, wenn die openpilot Längsregelung (Alpha) aktiviert wird. - - - Enable ADB - ADB aktivieren - - - ADB (Android Debug Bridge) allows connecting to your device over USB or over the network. See https://docs.comma.ai/how-to/connect-to-comma for more info. - ADB (Android Debug Bridge) ermöglicht die Verbindung zu deinem Gerät über USB oder Netzwerk. Siehe https://docs.comma.ai/how-to/connect-to-comma für weitere Informationen. - - - - DevicePanel - - Dongle ID - Dongle ID - - - N/A - Nicht verfügbar - - - Serial - Seriennummer - - - Driver Camera - Fahrerkamera - - - PREVIEW - VORSCHAU - - - Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off) - Vorschau der auf den Fahrer gerichteten Kamera, um sicherzustellen, dass die Fahrerüberwachung eine gute Sicht hat. (Fahrzeug muss aus sein) - - - Reset Calibration - Neu kalibrieren - - - RESET - RESET - - - Are you sure you want to reset calibration? - Bist du sicher, dass du die Kalibrierung zurücksetzen möchtest? - - - Review Training Guide - Trainingsanleitung wiederholen - - - REVIEW - TRAINING - - - Review the rules, features, and limitations of openpilot - Wiederhole die Regeln, Fähigkeiten und Limitierungen von Openpilot - - - Are you sure you want to review the training guide? - Bist du sicher, dass du die Trainingsanleitung wiederholen möchtest? - - - Regulatory - Rechtliche Hinweise - - - VIEW - ANSEHEN - - - Change Language - Sprache ändern - - - CHANGE - ÄNDERN - - - Select a language - Sprache wählen - - - Reboot - Neustart - - - Power Off - Ausschalten - - - Your device is pointed %1° %2 and %3° %4. - Deine Geräteausrichtung ist %1° %2 und %3° %4. - - - down - unten - - - up - oben - - - left - links - - - right - rechts - - - Are you sure you want to reboot? - Bist du sicher, dass du das Gerät neu starten möchtest? - - - Disengage to Reboot - Für Neustart deaktivieren - - - Are you sure you want to power off? - Bist du sicher, dass du das Gerät ausschalten möchtest? - - - Disengage to Power Off - Zum Ausschalten deaktivieren - - - Reset - Zurücksetzen - - - Review - Überprüfen - - - Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer. - Koppele dein Gerät mit Comma Connect (connect.comma.ai) und sichere dir dein Comma Prime Angebot. - - - Pair Device - Gerät koppeln - - - PAIR - KOPPELN - - - Disengage to Reset Calibration - - - - openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. - - - - openpilot is continuously calibrating, resetting is rarely required. Resetting calibration will restart openpilot if the car is powered on. - - - - - -Steering lag calibration is %1% complete. - - - - - -Steering lag calibration is complete. - - - - Steering torque response calibration is %1% complete. - - - - Steering torque response calibration is complete. - - - - - DriverViewWindow - - camera starting - Kamera startet - - - - ExperimentalModeButton - - EXPERIMENTAL MODE ON - EXPERIMENTELLER MODUS AN - - - CHILL MODE ON - ENTSPANNTER MODUS AN - - - - FirehosePanel - - openpilot learns to drive by watching humans, like you, drive. - -Firehose Mode allows you to maximize your training data uploads to improve openpilot's driving models. More data means bigger models, which means better Experimental Mode. - openpilot lernt das Fahren, indem es Menschen wie dir beim Fahren zuschaut. - -Der Firehose-Modus ermöglicht es dir, deine Trainingsdaten-Uploads zu maximieren, um die Fahrmodelle von openpilot zu verbessern. Mehr Daten bedeuten größere Modelle, was zu einem besseren Experimentellen Modus führt. - - - Firehose Mode: ACTIVE - Firehose-Modus: AKTIV - - - ACTIVE - AKTIV - - - For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.<br><br>Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.<br><br><br><b>Frequently Asked Questions</b><br><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><br><i>Do all of my segments get pulled in Firehose Mode?</i> No, we selectively pull a subset of your segments.<br><br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><br><i>Does it matter which software I run?</i> Yes, only upstream openpilot (and particular forks) are able to be used for training. - Für maximale Effektivität bring dein Gerät jede Woche nach drinnen und verbinde es mit einem guten USB-C-Adapter und WLAN.<br><br>Der Firehose-Modus funktioniert auch während der Fahrt, wenn das Gerät mit einem Hotspot oder einer ungedrosselten SIM-Karte verbunden ist.<br><br><br><b>Häufig gestellte Fragen</b><br><br><i>Spielt es eine Rolle, wie oder wo ich fahre?</i> Nein, fahre einfach wie gewohnt.<br><br><i>Werden im Firehose-Modus alle meine Segmente hochgeladen?</i> Nein, wir wählen selektiv nur einen Teil deiner Segmente aus.<br><br><i>Welcher USB-C-Adapter ist gut?</i> Jedes Schnellladegerät für Handy oder Laptop sollte ausreichen.<br><br><i>Spielt es eine Rolle, welche Software ich nutze?</i> Ja, nur das offizielle Upstream‑openpilot (und bestimmte Forks) kann für das Training verwendet werden. - - - <b>%n segment(s)</b> of your driving is in the training dataset so far. - - <b>%n Segment</b> deiner Fahrten ist bisher im Trainingsdatensatz. - <b>%n Segmente</b> deiner Fahrten sind bisher im Trainingsdatensatz. - - - - <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to an unmetered network - <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INAKTIV</span>: Verbinde dich mit einem ungedrosselten Netzwerk - - - Firehose Mode - - - - - HudRenderer - - km/h - km/h - - - mph - mph - - - MAX - MAX - - - - InputDialog - - Cancel - Abbrechen - - - Need at least %n character(s)! - - Mindestens %n Buchstabe benötigt! - Mindestens %n Buchstaben benötigt! - - - - - MultiOptionDialog - - Select - Auswählen - - - Cancel - Abbrechen - - - - Networking - - Advanced - Erweitert - - - Enter password - Passwort eingeben - - - for "%1" - für "%1" - - - Wrong password - Falsches Passwort - - - - OffroadAlert - - Immediately connect to the internet to check for updates. If you do not connect to the internet, openpilot won't engage in %1 - Stelle sofort eine Internetverbindung her, um nach Updates zu suchen. Wenn du keine Verbindung herstellst, kann openpilot in %1 nicht mehr aktiviert werden. - - - Connect to internet to check for updates. openpilot won't automatically start until it connects to internet to check for updates. - Verbinde dich mit dem Internet, um nach Updates zu suchen. openpilot startet nicht automatisch, bis eine Internetverbindung besteht und nach Updates gesucht wurde. - - - Unable to download updates -%1 - Updates konnten nicht heruntergeladen werden -%1 - - - Taking camera snapshots. System won't start until finished. - Kamera-Snapshots werden aufgenommen. Das System startet erst, wenn dies abgeschlossen ist. - - - An update to your device's operating system is downloading in the background. You will be prompted to update when it's ready to install. - Ein Update für das Betriebssystem deines Geräts wird im Hintergrund heruntergeladen. Du wirst aufgefordert, das Update zu installieren, sobald es bereit ist. - - - openpilot was unable to identify your car. Your car is either unsupported or its ECUs are not recognized. Please submit a pull request to add the firmware versions to the proper vehicle. Need help? Join discord.comma.ai. - openpilot konnte dein Auto nicht identifizieren. Dein Auto wird entweder nicht unterstützt oder die Steuergeräte (ECUs) werden nicht erkannt. Bitte reiche einen Pull Request ein, um die Firmware-Versionen für das richtige Fahrzeug hinzuzufügen. Hilfe findest du auf discord.comma.ai. - - - openpilot detected a change in the device's mounting position. Ensure the device is fully seated in the mount and the mount is firmly secured to the windshield. - openpilot hat eine Änderung der Montageposition des Geräts erkannt. Stelle sicher, dass das Gerät vollständig in der Halterung sitzt und die Halterung fest an der Windschutzscheibe befestigt ist. - - - Device temperature too high. System cooling down before starting. Current internal component temperature: %1 - Gerätetemperatur zu hoch. Das System kühlt ab, bevor es startet. Aktuelle interne Komponententemperatur: %1 - - - Device failed to register with the comma.ai backend. It will not connect or upload to comma.ai servers, and receives no support from comma.ai. If this is a device purchased at comma.ai/shop, open a ticket at https://comma.ai/support. - - - - Acknowledge Excessive Actuation - - - - Snooze Update - Update pausieren - - - openpilot detected excessive %1 actuation on your last drive. Please contact support at https://comma.ai/support and share your device's Dongle ID for troubleshooting. - - - - - OffroadHome - - UPDATE - Aktualisieren - - - ALERTS - HINWEISE - - - ALERT - HINWEIS - - - - OnroadAlerts - - openpilot Unavailable - openpilot nicht verfügbar - - - TAKE CONTROL IMMEDIATELY - ÜBERNIMM SOFORT DIE KONTROLLE - - - Reboot Device - Gerät neu starten - - - Waiting to start - Warten auf Start - - - System Unresponsive - System reagiert nicht - - - - PairingPopup - - Pair your device to your comma account - Verbinde dein Gerät mit deinem comma Konto - - - Go to https://connect.comma.ai on your phone - Gehe zu https://connect.comma.ai auf deinem Handy - - - Click "add new device" and scan the QR code on the right - Klicke auf "neues Gerät hinzufügen" und scanne den QR code rechts - - - Bookmark connect.comma.ai to your home screen to use it like an app - Füge connect.comma.ai als Lesezeichen auf deinem Homescreen hinzu um es wie eine App zu verwenden - - - Please connect to Wi-Fi to complete initial pairing - Bitte verbinde dich mit WLAN, um die Koppelung abzuschließen. - - - - ParamControl - - Cancel - Abbrechen - - - Enable - Aktivieren - - - - PrimeAdWidget - - Upgrade Now - Jetzt abonieren - - - Become a comma prime member at connect.comma.ai - Werde Comma Prime Mitglied auf connect.comma.ai - - - PRIME FEATURES: - PRIME FUNKTIONEN: - - - Remote access - Fernzugriff - - - 24/7 LTE connectivity - 24/7 LTE-Verbindung - - - 1 year of drive storage - Fahrdaten-Speicherung für 1 Jahr - - - Remote snapshots - Remote-Snapshots - - - - PrimeUserWidget - - ✓ SUBSCRIBED - ✓ ABBONIERT - - - comma prime - comma prime - - - - QObject - - openpilot - openpilot - - - %n minute(s) ago - - vor %n Minute - vor %n Minuten - - - - %n hour(s) ago - - vor %n Stunde - vor %n Stunden - - - - %n day(s) ago - - vor %n Tag - vor %n Tagen - - - - now - jetzt - - - - SettingsWindow - - × - x - - - Device - Gerät - - - Network - Netzwerk - - - Toggles - Schalter - - - Software - Software - - - Developer - Entwickler - - - Firehose - Firehose - - - - SetupWidget - - Finish Setup - Einrichtung beenden - - - Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer. - Koppele dein Gerät mit Comma Connect (connect.comma.ai) und sichere dir dein Comma Prime Angebot. - - - Pair device - Gerät koppeln - - - - Sidebar - - CONNECT - This is a brand/service name for comma connect, don't translate - CONNECT - - - OFFLINE - OFFLINE - - - ONLINE - ONLINE - - - ERROR - FEHLER - - - TEMP - TEMP - - - HIGH - HOCH - - - GOOD - GUT - - - OK - OK - - - VEHICLE - FAHRZEUG - - - NO - KEIN - - - PANDA - PANDA - - - -- - -- - - - Wi-Fi - WLAN - - - ETH - LAN - - - 2G - 2G - - - 3G - 3G - - - LTE - LTE - - - 5G - 5G - - - - SoftwarePanel - - UNINSTALL - Too long for UI - DEINSTALL - - - Uninstall %1 - Deinstalliere %1 - - - Are you sure you want to uninstall? - Bist du sicher, dass du Openpilot entfernen möchtest? - - - CHECK - ÜBERPRÜFEN - - - Updates are only downloaded while the car is off. - Updates werden nur heruntergeladen, wenn das Auto aus ist. - - - Current Version - Aktuelle Version - - - Download - Download - - - Install Update - Update installieren - - - INSTALL - INSTALLIEREN - - - Target Branch - Ziel Branch - - - SELECT - AUSWÄHLEN - - - Select a branch - Wähle einen Branch - - - Uninstall - Deinstallieren - - - failed to check for update - Update-Prüfung fehlgeschlagen - - - up to date, last checked %1 - Auf dem neuesten Stand, zuletzt geprüft am %1 - - - DOWNLOAD - HERUNTERLADEN - - - update available - Update verfügbar - - - never - nie - - - - SshControl - - SSH Keys - SSH Schlüssel - - - Warning: This grants SSH access to all public keys in your GitHub settings. Never enter a GitHub username other than your own. A comma employee will NEVER ask you to add their GitHub username. - Warnung: Dies ermöglicht SSH zugriff für alle öffentlichen Schlüssel in deinen Github Einstellungen. Gib niemals einen anderen Benutzernamen, als deinen Eigenen an. Comma Angestellte fragen dich niemals danach ihren Github Benutzernamen hinzuzufügen. - - - ADD - HINZUFÜGEN - - - Enter your GitHub username - Gib deinen GitHub Benutzernamen ein - - - LOADING - LADEN - - - REMOVE - LÖSCHEN - - - Username '%1' has no keys on GitHub - Benutzername '%1' hat keine Schlüssel auf GitHub - - - Request timed out - Zeitüberschreitung der Anforderung - - - Username '%1' doesn't exist on GitHub - Benutzername '%1' existiert nicht auf GitHub - - - - SshToggle - - Enable SSH - SSH aktivieren - - - - TermsPage - - Decline - Ablehnen - - - Agree - Zustimmen - - - Welcome to openpilot - Willkommen bei openpilot - - - You must accept the Terms and Conditions to use openpilot. Read the latest terms at <span style='color: #465BEA;'>https://comma.ai/terms</span> before continuing. - Du musst die Nutzungsbedingungen akzeptieren, um openpilot zu verwenden. Lies die aktuellen Bedingungen unter <span style='color: #465BEA;'>https://comma.ai/terms</span>, bevor du fortfährst. - - - - TogglesPanel - - Enable openpilot - Openpilot aktivieren - - - Enable Lane Departure Warnings - Spurverlassenswarnungen aktivieren - - - 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). - Erhalte Warnungen, zurück in die Spur zu lenken, wenn dein Auto über eine erkannte Fahrstreifenmarkierung ohne aktivierten Blinker mit mehr als 50 km/h fährt. - - - Use Metric System - Benutze das metrische System - - - Display speed in km/h instead of mph. - Zeige die Geschwindigkeit in km/h anstatt von mph. - - - Record and Upload Driver Camera - Fahrerkamera aufnehmen und hochladen - - - Upload data from the driver facing camera and help improve the driver monitoring algorithm. - Lade Daten der Fahreraufmerksamkeitsüberwachungskamera hoch, um die Fahreraufmerksamkeitsüberwachungsalgorithmen zu verbessern. - - - When enabled, pressing the accelerator pedal will disengage openpilot. - Wenn aktiviert, deaktiviert sich Openpilot sobald das Gaspedal betätigt wird. - - - Experimental Mode - Experimenteller Modus - - - Disengage on Accelerator Pedal - Bei Gasbetätigung ausschalten - - - openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: - Openpilot fährt standardmäßig im <b>entspannten Modus</b>. Der Experimentelle Modus aktiviert<b>Alpha-level Funktionen</b>, die noch nicht für den entspannten Modus bereit sind. Die experimentellen Funktionen sind die Folgenden: - - - Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. - Lass das Fahrmodell Gas und Bremse kontrollieren. Openpilot wird so fahren, wie es dies von einem Menschen erwarten würde; inklusive des Anhaltens für Ampeln und Stoppschildern. Da das Fahrmodell entscheidet wie schnell es fährt stellt die gesetzte Geschwindigkeit lediglich das obere Limit dar. Dies ist ein Alpha-level Funktion. Fehler sind zu erwarten. - - - New Driving Visualization - Neue Fahrvisualisierung - - - Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. - Der experimentelle Modus ist momentan für dieses Auto nicht verfügbar da es den eingebauten adaptiven Tempomaten des Autos benutzt. - - - Aggressive - Aggressiv - - - Standard - Standard - - - Relaxed - Entspannt - - - Driving Personality - Fahrstil - - - End-to-End Longitudinal Control - Ende-zu-Ende Längsregelung - - - openpilot longitudinal control may come in a future update. - Die openpilot Längsregelung könnte in einem zukünftigen Update verfügbar sein. - - - An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. - Eine Alpha-Version der openpilot Längsregelung kann zusammen mit dem Experimentellen Modus auf non-stable Branches getestet werden. - - - Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. - Aktiviere den Schalter für openpilot Längsregelung (Alpha), um den Experimentellen Modus zu erlauben. - - - Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. - Standard wird empfohlen. Im aggressiven Modus folgt openpilot vorausfahrenden Fahrzeugen enger und ist beim Gasgeben und Bremsen aggressiver. Im entspannten Modus hält openpilot mehr Abstand zu vorausfahrenden Fahrzeugen. Bei unterstützten Fahrzeugen kannst du mit der Abstandstaste am Lenkrad zwischen diesen Fahrstilen wechseln. - - - The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. - Die Fahrvisualisierung wechselt bei niedrigen Geschwindigkeiten auf die nach vorne gerichtete Weitwinkelkamera, um Kurven besser darzustellen. Das Logo des Experimentellen Modus wird außerdem oben rechts angezeigt. - - - Always-On Driver Monitoring - Dauerhaft aktive Fahrerüberwachung - - - Enable driver monitoring even when openpilot is not engaged. - Fahrerüberwachung auch aktivieren, wenn openpilot nicht aktiv ist. - - - Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. - - - - Changing this setting will restart openpilot if the car is powered on. - - - - Record and Upload Microphone Audio - - - - Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect. - - - - - WiFiPromptWidget - - Open - Öffnen - - - Maximize your training data uploads to improve openpilot's driving models. - Maximiere deine Trainingsdaten-Uploads, um die Fahrmodelle von openpilot zu verbessern. - - - <span style='font-family: "Noto Color Emoji";'>🔥</span> Firehose Mode <span style='font-family: Noto Color Emoji;'>🔥</span> - <span style='font-family: "Noto Color Emoji";'>🔥</span> Firehose-Modus <span style='font-family: Noto Color Emoji;'>🔥</span> - - - - WifiUI - - Scanning for networks... - Suche nach Netzwerken... - - - CONNECTING... - VERBINDEN... - - - FORGET - VERGESSEN - - - Forget Wi-Fi Network "%1"? - WLAN Netzwerk "%1" vergessen? - - - Forget - Vergessen - - - diff --git a/selfdrive/ui/translations/en.ts b/selfdrive/ui/translations/en.ts deleted file mode 100644 index fbccbedb2..000000000 --- a/selfdrive/ui/translations/en.ts +++ /dev/null @@ -1,48 +0,0 @@ - - - - - FirehosePanel - - <b>%n segment(s)</b> of your driving is in the training dataset so far. - - <b>%n segment</b> of your driving is in the training dataset so far. - <b>%n segments</b> of your driving are in the training dataset so far. - - - - - InputDialog - - Need at least %n character(s)! - - Need at least %n character! - Need at least %n characters! - - - - - QObject - - %n minute(s) ago - - %n minute ago - %n minutes ago - - - - %n hour(s) ago - - %n hour ago - %n hours ago - - - - %n day(s) ago - - %n day ago - %n days ago - - - - diff --git a/selfdrive/ui/translations/es.ts b/selfdrive/ui/translations/es.ts deleted file mode 100644 index e8d57dddb..000000000 --- a/selfdrive/ui/translations/es.ts +++ /dev/null @@ -1,1074 +0,0 @@ - - - - - AbstractAlert - - Close - Cerrar - - - Reboot and Update - Reiniciar y Actualizar - - - - AdvancedNetworking - - Back - Volver - - - Enable Tethering - Activar Tether - - - Tethering Password - Contraseña de Tethering - - - EDIT - EDITAR - - - Enter new tethering password - Nueva contraseña de tethering - - - IP Address - Dirección IP - - - Enable Roaming - Activar Roaming - - - APN Setting - Configuración de APN - - - Enter APN - Insertar APN - - - leave blank for automatic configuration - dejar en blanco para configuración automática - - - Cellular Metered - Plano de datos limitado - - - Hidden Network - Red Oculta - - - CONNECT - CONECTAR - - - Enter SSID - Ingrese SSID - - - Enter password - Ingrese contraseña - - - for "%1" - para "%1" - - - Prevent large data uploads when on a metered cellular connection - Evite cargas de grandes cantidades de datos cuando utilice una conexión celular medida - - - default - por defecto - - - metered - medido - - - unmetered - sin medidor - - - Wi-Fi Network Metered - Red Wi-Fi medida - - - Prevent large data uploads when on a metered Wi-Fi connection - Evite cargas de grandes cantidades de datos cuando esté en una conexión Wi-Fi medida - - - - ConfirmationDialog - - Ok - OK - - - Cancel - Cancelar - - - - DeclinePage - - You must accept the Terms and Conditions in order to use openpilot. - Debe aceptar los términos y condiciones para poder utilizar openpilot. - - - Back - Atrás - - - Decline, uninstall %1 - Rechazar, desinstalar %1 - - - - DeveloperPanel - - Joystick Debug Mode - Modo de depuración de joystick - - - Longitudinal Maneuver Mode - Modo de maniobra longitudinal - - - openpilot Longitudinal Control (Alpha) - Control longitudinal de openpilot (fase experimental) - - - WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB). - AVISO: el control longitudinal de openpilot está en fase experimental para este automóvil y desactivará el Frenado Automático de Emergencia (AEB). - - - On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha. - En este automóvil, openpilot se configura de manera predeterminada con el Autocrucero Adaptativo (ACC) incorporado en el automóvil en lugar del control longitudinal de openpilot. Habilita esta opción para cambiar al control longitudinal de openpilot. Se recomienda activar el modo experimental al habilitar el control longitudinal de openpilot (aún en fase experimental). - - - Enable ADB - Activar ADB - - - ADB (Android Debug Bridge) allows connecting to your device over USB or over the network. See https://docs.comma.ai/how-to/connect-to-comma for more info. - ADB (Android Debug Bridge) permite conectar a su dispositivo por USB o por red. Visite https://docs.comma.ai/how-to/connect-to-comma para más información. - - - - DevicePanel - - Dongle ID - Dongle ID - - - N/A - N/A - - - Serial - Serial - - - Pair Device - Emparejar Dispositivo - - - PAIR - EMPAREJAR - - - Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer. - Empareja tu dispositivo con comma connect (connect.comma.ai) y reclama tu oferta de comma prime. - - - Driver Camera - Cámara del conductor - - - PREVIEW - VISUALIZAR - - - Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off) - Previsualizar la cámara del conductor para garantizar que la monitorización del sistema tenga buena visibilidad (el vehículo tiene que estar apagado) - - - Reset Calibration - Formatear Calibración - - - RESET - REINICIAR - - - Are you sure you want to reset calibration? - ¿Seguro que quiere formatear la calibración? - - - Reset - Formatear - - - Review Training Guide - Revisar la Guía de Entrenamiento - - - REVIEW - REVISAR - - - Review the rules, features, and limitations of openpilot - Revisar las reglas, características y limitaciones de openpilot - - - Are you sure you want to review the training guide? - ¿Seguro que quiere revisar la guía de entrenamiento? - - - Review - Revisar - - - Regulatory - Regulador - - - VIEW - VER - - - Change Language - Cambiar Idioma - - - CHANGE - CAMBIAR - - - Select a language - Seleccione el idioma - - - Reboot - Reiniciar - - - Power Off - Apagar - - - Your device is pointed %1° %2 and %3° %4. - Su dispositivo está apuntando %1° %2 y %3° %4. - - - down - abajo - - - up - arriba - - - left - izquierda - - - right - derecha - - - Are you sure you want to reboot? - ¿Seguro qué quiere reiniciar? - - - Disengage to Reboot - Desactivar para Reiniciar - - - Are you sure you want to power off? - ¿Seguro qué quiere apagar? - - - Disengage to Power Off - Desactivar para apagar - - - Disengage to Reset Calibration - Desactivar para restablecer la calibración - - - openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. - openpilot requiere que el dispositivo esté montado dentro de los 4° hacia la izquierda o la derecha y dentro de los 5° hacia arriba o 9° hacia abajo. - - - openpilot is continuously calibrating, resetting is rarely required. Resetting calibration will restart openpilot if the car is powered on. - Openpilot se calibra continuamente, por lo que rara vez es necesario reiniciarlo. Restablecer la calibración reiniciará Openpilot si el automóvil está encendido. - - - - -Steering lag calibration is %1% complete. - - -La calibración del retraso de dirección está %1% completada. - - - - -Steering lag calibration is complete. - - -La calibración del retraso de la dirección está completa. - - - Steering torque response calibration is %1% complete. - La calibración de la respuesta del par de dirección está %1% completada. - - - Steering torque response calibration is complete. - La calibración de la respuesta del par de dirección está completa. - - - - DriverViewWindow - - camera starting - iniciando cámara - - - - ExperimentalModeButton - - EXPERIMENTAL MODE ON - MODO EXPERIMENTAL - - - CHILL MODE ON - MODO CHILL - - - - FirehosePanel - - openpilot learns to drive by watching humans, like you, drive. - -Firehose Mode allows you to maximize your training data uploads to improve openpilot's driving models. More data means bigger models, which means better Experimental Mode. - openpilot aprende a conducir observando a humanos, como tú, conducir. - -El Modo Firehose te permite maximizar las subidas de datos de entrenamiento para mejorar los modelos de conducción de openpilot. Más datos significan modelos más grandes, lo que significa un mejor Modo Experimental. - - - Firehose Mode: ACTIVE - Modo Firehose: ACTIVO - - - ACTIVE - ACTIVO - - - For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.<br><br>Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.<br><br><br><b>Frequently Asked Questions</b><br><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><br><i>Do all of my segments get pulled in Firehose Mode?</i> No, we selectively pull a subset of your segments.<br><br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><br><i>Does it matter which software I run?</i> Yes, only upstream openpilot (and particular forks) are able to be used for training. - Para máxima efectividad, traiga su dispositivo adentro y conéctelo a un buen adaptador USB-C y Wi-Fi semanalmente.<br><br>El Modo Firehose también puede funcionar mientras conduce si está conectado a un punto de acceso o tarjeta SIM ilimitada.<br><br><br><b>Preguntas Frecuentes</b><br><br><i>¿Importa cómo o dónde conduzco?</i> No, solo conduzca como lo haría normalmente.<br><br><i>¿Se extraen todos mis segmentos en el Modo Firehose?</i> No, seleccionamos selectivamente un subconjunto de sus segmentos.<br><br><i>¿Qué es un buen adaptador USB-C?</i> Cualquier cargador rápido de teléfono o portátil debería funcionar.<br><br><i>¿Importa qué software ejecuto?</i> Sí, solo el openpilot original (y forks específicos) pueden usarse para el entrenamiento. - - - <b>%n segment(s)</b> of your driving is in the training dataset so far. - - <b>%n segmento</b> de tu conducción está en el conjunto de datos de entrenamiento hasta ahora. - <b>%n segmentos</b> de tu conducción están en el conjunto de datos de entrenamiento hasta ahora. - - - - <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to an unmetered network - <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVO</span>: conéctate a una red sin límite de datos - - - Firehose Mode - Modo manguera contra incendios - - - - HudRenderer - - km/h - km/h - - - mph - mph - - - MAX - MAX - - - - InputDialog - - Cancel - Cancelar - - - Need at least %n character(s)! - - ¡Necesita mínimo %n caracter! - ¡Necesita mínimo %n caracteres! - - - - - MultiOptionDialog - - Select - Seleccionar - - - Cancel - Cancelar - - - - Networking - - Advanced - Avanzado - - - Enter password - Ingresar contraseña - - - for "%1" - para "%1" - - - Wrong password - Contraseña incorrecta - - - - OffroadAlert - - Device temperature too high. System cooling down before starting. Current internal component temperature: %1 - La temperatura del dispositivo es muy alta. El sistema se está enfriando antes de iniciar. Temperatura actual del componente interno: %1 - - - Immediately connect to the internet to check for updates. If you do not connect to the internet, openpilot won't engage in %1 - Conéctese inmediatamente al internet para buscar actualizaciones. Si no se conecta al internet, openpilot no iniciará en %1 - - - Connect to internet to check for updates. openpilot won't automatically start until it connects to internet to check for updates. - Conectese al internet para buscar actualizaciones. openpilot no iniciará automáticamente hasta conectarse al internet para buscar actualizaciones. - - - Unable to download updates -%1 - Incapaz de descargar actualizaciones. -%1 - - - Taking camera snapshots. System won't start until finished. - Tomando capturas de las cámaras. El sistema no se iniciará hasta que finalice. - - - An update to your device's operating system is downloading in the background. You will be prompted to update when it's ready to install. - Se está descargando una actualización del sistema operativo de su dispositivo en segundo plano. Se le pedirá que actualice cuando esté listo para instalarse. - - - openpilot was unable to identify your car. Your car is either unsupported or its ECUs are not recognized. Please submit a pull request to add the firmware versions to the proper vehicle. Need help? Join discord.comma.ai. - openpilot no pudo identificar su automóvil. Su automóvil no es compatible o no se reconocen sus ECU. Por favor haga un pull request para agregar las versiones de firmware del vehículo adecuado. ¿Necesita ayuda? Únase a discord.comma.ai. - - - openpilot detected a change in the device's mounting position. Ensure the device is fully seated in the mount and the mount is firmly secured to the windshield. - openpilot detectó un cambio en la posición de montaje del dispositivo. Asegúrese de que el dispositivo esté completamente asentado en el soporte y que el soporte esté firmemente asegurado al parabrisas. - - - Device failed to register with the comma.ai backend. It will not connect or upload to comma.ai servers, and receives no support from comma.ai. If this is a device purchased at comma.ai/shop, open a ticket at https://comma.ai/support. - - - - Acknowledge Excessive Actuation - - - - Snooze Update - Posponer Actualización - - - openpilot detected excessive %1 actuation on your last drive. Please contact support at https://comma.ai/support and share your device's Dongle ID for troubleshooting. - - - - - OffroadHome - - UPDATE - ACTUALIZAR - - - ALERTS - ALERTAS - - - ALERT - ALERTA - - - - OnroadAlerts - - openpilot Unavailable - openpilot no disponible - - - TAKE CONTROL IMMEDIATELY - TOME CONTROL INMEDIATAMENTE - - - Reboot Device - Reiniciar Dispositivo - - - Waiting to start - Esperando para iniciar - - - System Unresponsive - Systema no responde - - - - PairingPopup - - Pair your device to your comma account - Empareje su dispositivo con su cuenta de comma - - - Go to https://connect.comma.ai on your phone - Vaya a https://connect.comma.ai en su teléfono - - - Click "add new device" and scan the QR code on the right - Seleccione "agregar nuevo dispositivo" y escanee el código QR a la derecha - - - Bookmark connect.comma.ai to your home screen to use it like an app - Añada connect.comma.ai a su pantalla de inicio para usarlo como una aplicación - - - Please connect to Wi-Fi to complete initial pairing - Conéctese a Wi-Fi para completar el emparejamiento inicial - - - - ParamControl - - Enable - Activar - - - Cancel - Cancelar - - - - PrimeAdWidget - - Upgrade Now - Actualizar Ahora - - - Become a comma prime member at connect.comma.ai - Hazte miembro de comma prime en connect.comma.ai - - - PRIME FEATURES: - BENEFICIOS PRIME: - - - Remote access - Acceso remoto - - - 24/7 LTE connectivity - Conectividad LTE 24/7 - - - 1 year of drive storage - 1 año de almacenamiento - - - Remote snapshots - Capturas remotas - - - - PrimeUserWidget - - ✓ SUBSCRIBED - ✓ SUSCRITO - - - comma prime - comma prime - - - - QObject - - openpilot - openpilot - - - now - ahora - - - %n minute(s) ago - - hace %n min - hace %n mins - - - - %n hour(s) ago - - hace %n hora - hace %n horas - - - - %n day(s) ago - - hace %n día - hace %n días - - - - - SettingsWindow - - × - × - - - Device - Dispositivo - - - Network - Red - - - Toggles - Ajustes - - - Software - Software - - - Developer - Desarrollador - - - Firehose - Firehose - - - - SetupWidget - - Finish Setup - Terminar configuración - - - Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer. - Empareje su dispositivo con comma connect (connect.comma.ai) y reclame su oferta de comma prime. - - - Pair device - Emparejar dispositivo - - - - Sidebar - - CONNECT - CONNECT - - - OFFLINE - OFFLINE - - - ONLINE - EN LÍNEA - - - ERROR - ERROR - - - TEMP - TEMP - - - HIGH - ALTA - - - GOOD - BUENA - - - OK - OK - - - VEHICLE - VEHÍCULO - - - NO - SIN - - - PANDA - PANDA - - - -- - -- - - - Wi-Fi - Wi-Fi - - - ETH - ETH - - - 2G - 2G - - - 3G - 3G - - - LTE - LTE - - - 5G - 5G - - - - SoftwarePanel - - Updates are only downloaded while the car is off. - Actualizaciones solo se descargan con el auto apagado. - - - Current Version - Versión Actual - - - Download - Descargar - - - CHECK - VERIFICAR - - - Install Update - Actualizar - - - INSTALL - INSTALAR - - - Target Branch - Rama objetivo - - - SELECT - SELECCIONAR - - - Select a branch - Selecione una rama - - - Uninstall %1 - Desinstalar %1 - - - UNINSTALL - DESINSTALAR - - - Are you sure you want to uninstall? - ¿Seguro qué desea desinstalar? - - - Uninstall - Desinstalar - - - failed to check for update - no se pudo buscar actualizaciones - - - DOWNLOAD - DESCARGAR - - - update available - actualización disponible - - - never - nunca - - - up to date, last checked %1 - actualizado, último chequeo %1 - - - - SshControl - - SSH Keys - Clave SSH - - - Warning: This grants SSH access to all public keys in your GitHub settings. Never enter a GitHub username other than your own. A comma employee will NEVER ask you to add their GitHub username. - Aviso: Esto otorga acceso SSH a todas las claves públicas en su Github. Nunca ingrese un nombre de usuario de Github que no sea suyo. Un empleado de comma NUNCA le pedirá que añada un usuario de Github que no sea el suyo. - - - ADD - AÑADIR - - - Enter your GitHub username - Ingrese su usuario de GitHub - - - LOADING - CARGANDO - - - REMOVE - ELIMINAR - - - Username '%1' has no keys on GitHub - El usuario "%1” no tiene claves en GitHub - - - Request timed out - Solicitud expirada - - - Username '%1' doesn't exist on GitHub - El usuario '%1' no existe en Github - - - - SshToggle - - Enable SSH - Habilitar SSH - - - - TermsPage - - Decline - Rechazar - - - Agree - Aceptar - - - Welcome to openpilot - Bienvenido a openpilot - - - You must accept the Terms and Conditions to use openpilot. Read the latest terms at <span style='color: #465BEA;'>https://comma.ai/terms</span> before continuing. - Debe aceptar los Términos y Condiciones para usar openpilot. Lea los términos más recientes en <span style='color: #465BEA;'>https://comma.ai/terms</span> antes de continuar. - - - - TogglesPanel - - Enable openpilot - Activar openpilot - - - Experimental Mode - Modo Experimental - - - Disengage on Accelerator Pedal - Desactivar con el Acelerador - - - When enabled, pressing the accelerator pedal will disengage openpilot. - Cuando esté activado, presionar el acelerador deshabilitará openpilot. - - - Enable Lane Departure Warnings - Activar Avisos de Salida de Carril - - - 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). - Recibir alertas para volver al carril cuando su vehículo se salga fuera del carril sin que esté activada la señal de giro y esté conduciendo por encima de 50 km/h (31 mph). - - - Always-On Driver Monitoring - Monitoreo Permanente del Conductor - - - Enable driver monitoring even when openpilot is not engaged. - Habilitar el monitoreo del conductor incluso cuando Openpilot no esté activado. - - - Record and Upload Driver Camera - Grabar y Subir Cámara del Conductor - - - Upload data from the driver facing camera and help improve the driver monitoring algorithm. - Subir datos de la cámara del conductor para ayudar a mejorar el algoritmo de monitoreo del conductor. - - - Use Metric System - Usar Sistema Métrico - - - Display speed in km/h instead of mph. - Mostrar velocidad en km/h en vez de mph. - - - Aggressive - Agresivo - - - Standard - Estándar - - - Relaxed - Relajado - - - Driving Personality - Personalidad de conducción - - - Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. - Se recomienda el modo estándar. En el modo agresivo, openpilot seguirá más cerca a los autos delante suyo y será más agresivo con el acelerador y el freno. En modo relajado, openpilot se mantendrá más alejado de los autos delante suyo. En automóviles compatibles, puede recorrer estas personalidades con el botón de distancia del volante. - - - openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: - openpilot por defecto conduce en <b>modo chill</b>. El modo Experimental activa <b>funcionalidades en fase experimental</b>, que no están listas para el modo chill. Las funcionalidades del modo expeimental están listados abajo: - - - End-to-End Longitudinal Control - Control Longitudinal de Punta a Punta - - - Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. - Dajar que el modelo de conducción controle la aceleración y el frenado. openpilot conducirá como piensa que lo haría una persona, incluiyendo parar en los semáforos en rojo y las señales de alto. Dado que el modelo decide la velocidad de conducción, la velocidad de crucero establecida solo actuará como el límite superior. Este recurso aún está en fase experimental; deberían esperarse errores. - - - New Driving Visualization - Nueva Visualización de la conducción - - - The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. - La visualización de la conducción cambiará a la cámara que enfoca la carretera a velocidades bajas para mostrar mejor los giros. El logo del modo experimental también se mostrará en la esquina superior derecha. - - - Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. - El modo Experimental no está disponible actualmente para este auto, ya que el ACC default del auto está siendo usado para el control longitudinal. - - - openpilot longitudinal control may come in a future update. - El control longitudinal de openpilot podrá llegar en futuras actualizaciones. - - - An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. - Se puede probar una versión experimental del control longitudinal openpilot, junto con el modo Experimental, en ramas no liberadas. - - - Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. - Activar el control longitudinal (fase experimental) para permitir el modo Experimental. - - - Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. - Utilice el sistema openpilot para el control de crucero adaptativo y la asistencia al conductor para mantenerse en el carril. Se requiere su atención en todo momento para utilizar esta función. - - - Changing this setting will restart openpilot if the car is powered on. - Cambiar esta configuración reiniciará Openpilot si el automóvil está encendido. - - - Record and Upload Microphone Audio - Grabar y cargar audio de micrófono - - - Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect. - Graba y almacena el audio del micrófono mientras conduces. El audio se incluirá en el video de la cámara del tablero en comma connect. - - - - WiFiPromptWidget - - Open - Abrir - - - Maximize your training data uploads to improve openpilot's driving models. - Maximice sus cargas de datos de entrenamiento para mejorar los modelos de conducción de openpilot. - - - <span style='font-family: "Noto Color Emoji";'>🔥</span> Firehose Mode <span style='font-family: Noto Color Emoji;'>🔥</span> - <span style='font-family: "Noto Color Emoji";'>🔥</span> Modo Firehose <span style='font-family: Noto Color Emoji;'>🔥</span> - - - - WifiUI - - Scanning for networks... - Buscando redes... - - - CONNECTING... - CONECTANDO... - - - FORGET - OLVIDAR - - - Forget Wi-Fi Network "%1"? - ¿Olvidar la Red de Wi-Fi "%1"? - - - Forget - Olvidar - - - diff --git a/selfdrive/ui/translations/fr.ts b/selfdrive/ui/translations/fr.ts deleted file mode 100644 index 4efb92d0c..000000000 --- a/selfdrive/ui/translations/fr.ts +++ /dev/null @@ -1,1068 +0,0 @@ - - - - - AbstractAlert - - Close - Fermer - - - Reboot and Update - Redémarrer et mettre à jour - - - - AdvancedNetworking - - Back - Retour - - - Enable Tethering - Activer le partage de connexion - - - Tethering Password - Mot de passe du partage de connexion - - - EDIT - MODIFIER - - - Enter new tethering password - Entrez le nouveau mot de passe du partage de connexion - - - IP Address - Adresse IP - - - Enable Roaming - Activer l'itinérance - - - APN Setting - Paramètre APN - - - Enter APN - Entrer le nom du point d'accès - - - leave blank for automatic configuration - laisser vide pour une configuration automatique - - - Cellular Metered - Connexion cellulaire limitée - - - Hidden Network - Réseau Caché - - - CONNECT - CONNECTER - - - Enter SSID - Entrer le SSID - - - Enter password - Entrer le mot de passe - - - for "%1" - pour "%1" - - - Prevent large data uploads when on a metered cellular connection - - - - default - - - - metered - - - - unmetered - - - - Wi-Fi Network Metered - - - - Prevent large data uploads when on a metered Wi-Fi connection - - - - - ConfirmationDialog - - Ok - Ok - - - Cancel - Annuler - - - - DeclinePage - - You must accept the Terms and Conditions in order to use openpilot. - Vous devez accepter les conditions générales pour utiliser openpilot. - - - Back - Retour - - - Decline, uninstall %1 - Refuser, désinstaller %1 - - - - DeveloperPanel - - Joystick Debug Mode - Mode débogage au joystick - - - Longitudinal Maneuver Mode - Mode manœuvre longitudinale - - - openpilot Longitudinal Control (Alpha) - Contrôle longitudinal openpilot (Alpha) - - - WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB). - ATTENTION : le contrôle longitudinal openpilot est en alpha pour cette voiture et désactivera le freinage d'urgence automatique (AEB). - - - On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha. - Sur cette voiture, openpilot utilise par défaut le régulateur de vitesse adaptatif intégré à la voiture plutôt que le contrôle longitudinal d'openpilot. Activez ceci pour passer au contrôle longitudinal openpilot. Il est recommandé d'activer le mode expérimental lors de l'activation du contrôle longitudinal openpilot alpha. - - - Enable ADB - - - - ADB (Android Debug Bridge) allows connecting to your device over USB or over the network. See https://docs.comma.ai/how-to/connect-to-comma for more info. - - - - - DevicePanel - - Dongle ID - Dongle ID - - - N/A - N/A - - - Serial - N° de série - - - Driver Camera - Caméra conducteur - - - PREVIEW - APERÇU - - - Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off) - Aperçu de la caméra orientée vers le conducteur pour assurer une bonne visibilité de la surveillance du conducteur. (véhicule doit être éteint) - - - Reset Calibration - Réinitialiser la calibration - - - RESET - RÉINITIALISER - - - Are you sure you want to reset calibration? - Êtes-vous sûr de vouloir réinitialiser la calibration ? - - - Reset - Réinitialiser - - - Review Training Guide - Revoir le guide de formation - - - REVIEW - REVOIR - - - Review the rules, features, and limitations of openpilot - Revoir les règles, fonctionnalités et limitations d'openpilot - - - Are you sure you want to review the training guide? - Êtes-vous sûr de vouloir revoir le guide de formation ? - - - Review - Revoir - - - Regulatory - Réglementaire - - - VIEW - VOIR - - - Change Language - Changer de langue - - - CHANGE - CHANGER - - - Select a language - Choisir une langue - - - Reboot - Redémarrer - - - Power Off - Éteindre - - - Your device is pointed %1° %2 and %3° %4. - Votre appareil est orienté %1° %2 et %3° %4. - - - down - bas - - - up - haut - - - left - gauche - - - right - droite - - - Are you sure you want to reboot? - Êtes-vous sûr de vouloir redémarrer ? - - - Disengage to Reboot - Désengager pour redémarrer - - - Are you sure you want to power off? - Êtes-vous sûr de vouloir éteindre ? - - - Disengage to Power Off - Désengager pour éteindre - - - Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer. - Associez votre appareil avec comma connect (connect.comma.ai) et profitez de l'offre comma prime. - - - Pair Device - Associer l'appareil - - - PAIR - ASSOCIER - - - Disengage to Reset Calibration - - - - openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. - - - - openpilot is continuously calibrating, resetting is rarely required. Resetting calibration will restart openpilot if the car is powered on. - - - - - -Steering lag calibration is %1% complete. - - - - - -Steering lag calibration is complete. - - - - Steering torque response calibration is %1% complete. - - - - Steering torque response calibration is complete. - - - - - DriverViewWindow - - camera starting - démarrage de la caméra - - - - ExperimentalModeButton - - EXPERIMENTAL MODE ON - MODE EXPÉRIMENTAL ACTIVÉ - - - CHILL MODE ON - MODE DÉTENTE ACTIVÉ - - - - FirehosePanel - - openpilot learns to drive by watching humans, like you, drive. - -Firehose Mode allows you to maximize your training data uploads to improve openpilot's driving models. More data means bigger models, which means better Experimental Mode. - - - - Firehose Mode: ACTIVE - - - - ACTIVE - - - - For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.<br><br>Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.<br><br><br><b>Frequently Asked Questions</b><br><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><br><i>Do all of my segments get pulled in Firehose Mode?</i> No, we selectively pull a subset of your segments.<br><br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><br><i>Does it matter which software I run?</i> Yes, only upstream openpilot (and particular forks) are able to be used for training. - - - - <b>%n segment(s)</b> of your driving is in the training dataset so far. - - - - - - - <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to an unmetered network - - - - Firehose Mode - - - - - HudRenderer - - km/h - km/h - - - mph - mi/h - - - MAX - MAX - - - - InputDialog - - Cancel - Annuler - - - Need at least %n character(s)! - - Besoin d'au moins %n caractère ! - Besoin d'au moins %n caractères ! - - - - - MultiOptionDialog - - Select - Sélectionner - - - Cancel - Annuler - - - - Networking - - Advanced - Avancé - - - Enter password - Entrer le mot de passe - - - for "%1" - pour "%1" - - - Wrong password - Mot de passe incorrect - - - - OffroadAlert - - Device temperature too high. System cooling down before starting. Current internal component temperature: %1 - Température de l'appareil trop élevée. Le système doit refroidir avant de démarrer. Température actuelle de l'appareil : %1 - - - Immediately connect to the internet to check for updates. If you do not connect to the internet, openpilot won't engage in %1 - Connectez-vous immédiatement à internet pour vérifier les mises à jour. Si vous ne vous connectez pas à internet, openpilot ne s'engagera pas dans %1 - - - Connect to internet to check for updates. openpilot won't automatically start until it connects to internet to check for updates. - Connectez l'appareil à internet pour vérifier les mises à jour. openpilot ne démarrera pas automatiquement tant qu'il ne se connecte pas à internet pour vérifier les mises à jour. - - - Unable to download updates -%1 - Impossible de télécharger les mises à jour -%1 - - - Taking camera snapshots. System won't start until finished. - Capture de clichés photo. Le système ne démarrera pas tant qu'il n'est pas terminé. - - - An update to your device's operating system is downloading in the background. You will be prompted to update when it's ready to install. - Une mise à jour du système d'exploitation de votre appareil est en cours de téléchargement en arrière-plan. Vous serez invité à effectuer la mise à jour lorsqu'elle sera prête à être installée. - - - openpilot was unable to identify your car. Your car is either unsupported or its ECUs are not recognized. Please submit a pull request to add the firmware versions to the proper vehicle. Need help? Join discord.comma.ai. - openpilot n'a pas pu identifier votre voiture. Votre voiture n'est pas supportée ou ses ECUs ne sont pas reconnues. Veuillez soumettre un pull request pour ajouter les versions de firmware au véhicule approprié. Besoin d'aide ? Rejoignez discord.comma.ai. - - - openpilot detected a change in the device's mounting position. Ensure the device is fully seated in the mount and the mount is firmly secured to the windshield. - openpilot a détecté un changement dans la position de montage de l'appareil. Assurez-vous que l'appareil est totalement inséré dans le support et que le support est fermement fixé au pare-brise. - - - Device failed to register with the comma.ai backend. It will not connect or upload to comma.ai servers, and receives no support from comma.ai. If this is a device purchased at comma.ai/shop, open a ticket at https://comma.ai/support. - - - - Acknowledge Excessive Actuation - - - - Snooze Update - Reporter la mise à jour - - - openpilot detected excessive %1 actuation on your last drive. Please contact support at https://comma.ai/support and share your device's Dongle ID for troubleshooting. - - - - - OffroadHome - - UPDATE - MISE À JOUR - - - ALERTS - ALERTES - - - ALERT - ALERTE - - - - OnroadAlerts - - openpilot Unavailable - openpilot indisponible - - - TAKE CONTROL IMMEDIATELY - REPRENEZ LE CONTRÔLE IMMÉDIATEMENT - - - Reboot Device - Redémarrer l'appareil - - - Waiting to start - En attente de démarrage - - - System Unresponsive - Système inopérant - - - - PairingPopup - - Pair your device to your comma account - Associez votre appareil à votre compte comma - - - Go to https://connect.comma.ai on your phone - Allez sur https://connect.comma.ai sur votre téléphone - - - Click "add new device" and scan the QR code on the right - Cliquez sur "ajouter un nouvel appareil" et scannez le code QR à droite - - - Bookmark connect.comma.ai to your home screen to use it like an app - Ajoutez connect.comma.ai à votre écran d'accueil pour l'utiliser comme une application - - - Please connect to Wi-Fi to complete initial pairing - Connectez-vous au Wi-Fi pour terminer l'appairage initial - - - - ParamControl - - Enable - Activer - - - Cancel - Annuler - - - - PrimeAdWidget - - Upgrade Now - Mettre à niveau - - - Become a comma prime member at connect.comma.ai - Devenez membre comma prime sur connect.comma.ai - - - PRIME FEATURES: - FONCTIONNALITÉS PRIME : - - - Remote access - Accès à distance - - - 24/7 LTE connectivity - Connexion LTE 24/7 - - - 1 year of drive storage - 1 an de stockage de trajets - - - Remote snapshots - Captures à distance - - - - PrimeUserWidget - - ✓ SUBSCRIBED - ✓ ABONNÉ - - - comma prime - comma prime - - - - QObject - - openpilot - openpilot - - - %n minute(s) ago - - il y a %n minute - il y a %n minutes - - - - %n hour(s) ago - - il y a %n heure - il y a %n heures - - - - %n day(s) ago - - il y a %n jour - il y a %n jours - - - - now - maintenant - - - - SettingsWindow - - × - × - - - Device - Appareil - - - Network - Réseau - - - Toggles - Options - - - Software - Logiciel - - - Developer - Dév. - - - Firehose - - - - - SetupWidget - - Finish Setup - Terminer l'installation - - - Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer. - Associez votre appareil avec comma connect (connect.comma.ai) et profitez de l'offre comma prime. - - - Pair device - Associer l'appareil - - - - Sidebar - - CONNECT - CONNECTER - - - OFFLINE - HORS LIGNE - - - ONLINE - EN LIGNE - - - ERROR - ERREUR - - - TEMP - TEMP - - - HIGH - HAUT - - - GOOD - BON - - - OK - OK - - - VEHICLE - VÉHICULE - - - NO - NON - - - PANDA - PANDA - - - -- - -- - - - Wi-Fi - Wi-Fi - - - ETH - ETH - - - 2G - 2G - - - 3G - 3G - - - LTE - LTE - - - 5G - 5G - - - - SoftwarePanel - - Updates are only downloaded while the car is off. - Les MàJ sont téléchargées uniquement si la voiture est éteinte. - - - Current Version - Version actuelle - - - Download - Télécharger - - - CHECK - VÉRIFIER - - - Install Update - Installer la mise à jour - - - INSTALL - INSTALLER - - - Target Branch - Branche cible - - - SELECT - SÉLECTIONNER - - - Select a branch - Sélectionner une branche - - - Uninstall %1 - Désinstaller %1 - - - UNINSTALL - DÉSINSTALLER - - - Are you sure you want to uninstall? - Êtes-vous sûr de vouloir désinstaller ? - - - Uninstall - Désinstaller - - - failed to check for update - échec de la vérification de la mise à jour - - - DOWNLOAD - TÉLÉCHARGER - - - update available - mise à jour disponible - - - never - jamais - - - up to date, last checked %1 - à jour, dernière vérification %1 - - - - SshControl - - SSH Keys - Clés SSH - - - Warning: This grants SSH access to all public keys in your GitHub settings. Never enter a GitHub username other than your own. A comma employee will NEVER ask you to add their GitHub username. - Attention : Ceci accorde l'accès SSH à toutes les clés publiques de vos paramètres GitHub. N'entrez jamais un nom d'utilisateur GitHub autre que le vôtre. Un employé de comma ne vous demandera JAMAIS d'ajouter son nom d'utilisateur GitHub. - - - ADD - AJOUTER - - - Enter your GitHub username - Entrez votre nom d'utilisateur GitHub - - - LOADING - CHARGEMENT - - - REMOVE - SUPPRIMER - - - Username '%1' has no keys on GitHub - L'utilisateur '%1' n'a pas de clés sur GitHub - - - Request timed out - Délai de la demande dépassé - - - Username '%1' doesn't exist on GitHub - L'utilisateur '%1' n'existe pas sur GitHub - - - - SshToggle - - Enable SSH - Activer SSH - - - - TermsPage - - Decline - Refuser - - - Agree - Accepter - - - Welcome to openpilot - - - - You must accept the Terms and Conditions to use openpilot. Read the latest terms at <span style='color: #465BEA;'>https://comma.ai/terms</span> before continuing. - - - - - TogglesPanel - - Enable openpilot - Activer openpilot - - - Experimental Mode - Mode expérimental - - - Disengage on Accelerator Pedal - Désengager avec la pédale d'accélérateur - - - When enabled, pressing the accelerator pedal will disengage openpilot. - Lorsqu'il est activé, appuyer sur la pédale d'accélérateur désengagera openpilot. - - - Enable Lane Departure Warnings - Activer les avertissements de sortie de voie - - - 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). - Recevez des alertes pour revenir dans la voie lorsque votre véhicule dérive au-delà d'une ligne de voie détectée sans clignotant activé en roulant à plus de 31 mph (50 km/h). - - - Record and Upload Driver Camera - Enregistrer et télécharger la caméra conducteur - - - Upload data from the driver facing camera and help improve the driver monitoring algorithm. - Publiez les données de la caméra orientée vers le conducteur et aidez à améliorer l'algorithme de surveillance du conducteur. - - - Use Metric System - Utiliser le système métrique - - - Display speed in km/h instead of mph. - Afficher la vitesse en km/h au lieu de mph. - - - Aggressive - Aggressif - - - Standard - Standard - - - Relaxed - Détendu - - - Driving Personality - Personnalité de conduite - - - openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: - Par défaut, openpilot conduit en <b>mode détente</b>. Le mode expérimental permet d'activer des <b>fonctionnalités alpha</b> qui ne sont pas prêtes pour le mode détente. Les fonctionnalités expérimentales sont listées ci-dessous : - - - Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. - Laissez le modèle de conduite contrôler l'accélérateur et les freins. openpilot conduira comme il pense qu'un humain le ferait, y compris s'arrêter aux feux rouges et aux panneaux stop. Comme le modèle de conduite décide de la vitesse à adopter, la vitesse définie ne servira que de limite supérieure. Cette fonctionnalité est de qualité alpha ; des erreurs sont à prévoir. - - - New Driving Visualization - Nouvelle visualisation de la conduite - - - Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. - Le mode expérimental est actuellement indisponible pour cette voiture car le régulateur de vitesse adaptatif d'origine est utilisé pour le contrôle longitudinal. - - - openpilot longitudinal control may come in a future update. - Le contrôle longitudinal openpilot pourrait être disponible dans une future mise à jour. - - - An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. - Une version alpha du contrôle longitudinal openpilot peut être testée, avec le mode expérimental, sur des branches non publiées. - - - End-to-End Longitudinal Control - Contrôle longitudinal de bout en bout - - - Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. - Activer le contrôle longitudinal d'openpilot (en alpha) pour autoriser le mode expérimental. - - - Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. - Le mode Standard est recommandé. En mode Agressif, openpilot suivra les véhicules de plus près et sera plus dynamique avec l'accélérateur et le frein. En mode Détendu, openpilot maintiendra une distance plus importante avec les véhicules qui précèdent. Sur les véhicules compatibles, vous pouvez alterner entre ces personnalités à l'aide du bouton de distance au volant. - - - The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. - La visualisation de la conduite passera sur la caméra grand angle dirigée vers la route à faible vitesse afin de mieux montrer certains virages. Le logo du mode expérimental s'affichera également dans le coin supérieur droit. - - - Always-On Driver Monitoring - Surveillance continue du conducteur - - - Enable driver monitoring even when openpilot is not engaged. - Activer la surveillance conducteur lorsque openpilot n'est pas actif. - - - Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. - - - - Changing this setting will restart openpilot if the car is powered on. - - - - Record and Upload Microphone Audio - - - - Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect. - - - - - WiFiPromptWidget - - Open - - - - Maximize your training data uploads to improve openpilot's driving models. - - - - <span style='font-family: "Noto Color Emoji";'>🔥</span> Firehose Mode <span style='font-family: Noto Color Emoji;'>🔥</span> - - - - - WifiUI - - Scanning for networks... - Recherche de réseaux... - - - CONNECTING... - CONNEXION... - - - FORGET - OUBLIER - - - Forget Wi-Fi Network "%1"? - Oublier le réseau Wi-Fi "%1" ? - - - Forget - Oublier - - - diff --git a/selfdrive/ui/translations/ja.ts b/selfdrive/ui/translations/ja.ts deleted file mode 100644 index 4307cee91..000000000 --- a/selfdrive/ui/translations/ja.ts +++ /dev/null @@ -1,1069 +0,0 @@ - - - - - AbstractAlert - - Close - 閉じる - - - Reboot and Update - 再起動してアップデート - - - - AdvancedNetworking - - Back - 戻る - - - Enable Tethering - テザリング有効 - - - Tethering Password - テザリングパスワード - - - EDIT - 編集 - - - Enter new tethering password - 新しいテザリングパスワードを入力 - - - IP Address - IPアドレス - - - Enable Roaming - ローミング有効 - - - APN Setting - APN設定 - - - Enter APN - APNを入力 - - - leave blank for automatic configuration - 自動で設定するには空白のままにしてください - - - Cellular Metered - 従量制通信設定 - - - Hidden Network - ネットワーク非表示 - - - CONNECT - 接続 - - - Enter SSID - SSIDを入力 - - - Enter password - パスワードを入力 - - - for "%1" - [%1] - - - Prevent large data uploads when on a metered cellular connection - モバイルデータ回線を使用しているときは大容量データをアップロードしません - - - default - 標準設定 - - - metered - 従量制 - - - unmetered - 定額制 - - - Wi-Fi Network Metered - 従量制のWi-Fiネットワーク - - - Prevent large data uploads when on a metered Wi-Fi connection - 通信制限のあるWi-Fi接続では大容量データをアップロードしません - - - - ConfirmationDialog - - Ok - OK - - - Cancel - キャンセル - - - - DeclinePage - - You must accept the Terms and Conditions in order to use openpilot. - openpilotを使用するためには利用規約に同意する必要があります - - - Back - 戻る - - - Decline, uninstall %1 - 同意しない(%1をアンインストール) - - - - DeveloperPanel - - Joystick Debug Mode - ジョイスティックデバッグモード - - - Longitudinal Maneuver Mode - アクセル制御マニューバー - - - openpilot Longitudinal Control (Alpha) - openpilotアクセル制御(Alpha) - - - WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB). - この車ではopenpilotのアクセル制御はアルファ版であり、自動緊急ブレーキ(AEB)が無効化されます。 - - - On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha. - この車では、openpilotは車両内蔵のACC(アダプティブクルーズコントロール)をデフォルトとして使用し、openpilotのアクセル制御は無効化されています。アクセル制御をopenpilotに切り替えるにはこの設定を有効にしてください。また同時にExperimentalモードを推奨します。 - - - Enable ADB - ADBを有効にする - - - ADB (Android Debug Bridge) allows connecting to your device over USB or over the network. See https://docs.comma.ai/how-to/connect-to-comma for more info. - ADB(Android Debug Bridge)により、USBまたはネットワーク経由でデバイスに接続できます。詳細は、https://docs.comma.ai/how-to/connect-to-comma を参照してください。 - - - - DevicePanel - - Dongle ID - ドングルID - - - N/A - 該当なし - - - Serial - シリアル番号 - - - Driver Camera - 車内カメラ - - - PREVIEW - プレビュー - - - Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off) - 車内カメラでドライバー監視システムのカメラ画像を確認できます。(車両のパワーOFF時の機能です) - - - Reset Calibration - キャリブレーションリセット - - - RESET - リセット - - - Are you sure you want to reset calibration? - キャリブレーションをリセットしますか? - - - Review Training Guide - トレーニングガイドを見る - - - REVIEW - 確認 - - - Review the rules, features, and limitations of openpilot - openpilotのルール、機能、および制限を確認してください - - - Are you sure you want to review the training guide? - トレーニングガイドを始めてもよろしいですか? - - - Regulatory - 規約 - - - VIEW - 確認 - - - Change Language - 多言語対応 - - - CHANGE - 変更 - - - Select a language - 言語を選択 - - - Reboot - 再起動 - - - Power Off - パワーオフ - - - Your device is pointed %1° %2 and %3° %4. - このデバイスは%2 %1°、%4 %3°の向きに設置されています。 - - - down - - - - up - - - - left - - - - right - - - - Are you sure you want to reboot? - 再起動してもよろしいですか? - - - Disengage to Reboot - 再起動するには車を一旦停止してください - - - Are you sure you want to power off? - パワーオフしてもよろしいですか? - - - Disengage to Power Off - パワーオフするには車を一旦停止してください - - - Reset - リセット - - - Review - 見る - - - Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer. - デバイスをcommaコネクト(connect.comma.ai)でペアリングしてcommaプライムの特典を受け取ってください。 - - - Pair Device - デバイスのペアリング - - - PAIR - OK - - - Disengage to Reset Calibration - キャリブレーションをリセットするには運転支援を解除して下さい。 - - - openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. - openpilotの本体は左右4°以内、上5°下9°以内の角度で取付ける必要があります。 - - - openpilot is continuously calibrating, resetting is rarely required. Resetting calibration will restart openpilot if the car is powered on. - openpilot は継続的にキャリブレーションを行っており、リセットが必要になることはほとんどありません。キャリブレーションをリセットすると、車の電源が入っている場合はopenpilotが再起動します。 - - - - -Steering lag calibration is %1% complete. - - -ステアリング遅延のキャリブレーションが%1%完了。 - - - - -Steering lag calibration is complete. - - -ステアリング遅延のキャリブレーション完了。 - - - Steering torque response calibration is %1% complete. - ステアリングトルク応答のキャリブレーションが%1%完了。 - - - Steering torque response calibration is complete. - ステアリングトルク応答のキャリブレーション完了。 - - - - DriverViewWindow - - camera starting - カメラ起動中 - - - - ExperimentalModeButton - - EXPERIMENTAL MODE ON - EXPERIMENTALモード - - - CHILL MODE ON - CHILLモード - - - - FirehosePanel - - openpilot learns to drive by watching humans, like you, drive. - -Firehose Mode allows you to maximize your training data uploads to improve openpilot's driving models. More data means bigger models, which means better Experimental Mode. - openpilotは人間であるあなたの運転から学び、AI学習します。 - -Firehoseモードを有効にすると学習データを最大限アップロードし、openpilotの運転モデルを改善することができます。より多くのデータはより大きなモデルとなり、Experimentalモードの精度を向上させます。 - - - Firehose Mode: ACTIVE - Firehoseモード: 作動中 - - - ACTIVE - 動作中 - - - For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.<br><br>Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.<br><br><br><b>Frequently Asked Questions</b><br><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><br><i>Do all of my segments get pulled in Firehose Mode?</i> No, we selectively pull a subset of your segments.<br><br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><br><i>Does it matter which software I run?</i> Yes, only upstream openpilot (and particular forks) are able to be used for training. - 最大の効果を得るためにはデバイスを屋内に持ち込み、大容量のUSB-C充電器とWi-Fiに毎週接続してください。<br><br>Firehoseモードは公衆無線LANや大容量契約のSIMカードに接続していれば、運転中でも動作します。<br><br><br><b>よくある質問(FAQ)</b><br><br><i>運転のやり方や走る場所は重要ですか?</i> いいえ、普段どおりに運転するだけで大丈夫です。<br><br><i>Firehoseモードでは全てのデータがアップロードされますか?</i> いいえ、アップロードするデータを選ぶことができます。<br><br><i>大容量のUSB-C充電器とは何ですか?</i> スマートフォンやノートパソコンを高速に充電できるものを使って下さい。<br><br><i>どのフォークを使うかは重要ですか?</i>はい、トレーニングには公式のopenpilot(および特定のフォーク)のみが使用できます。 - - - <b>%n segment(s)</b> of your driving is in the training dataset so far. - - あなたの運転の<b>%nセグメント</b>がこれまでのトレーニングデータに含まれています。 - - - - <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to an unmetered network - <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>動作停止</span>: 大容量のネットワークに接続してください - - - Firehose Mode - Firehoseモード - - - - HudRenderer - - km/h - km/h - - - mph - mph - - - MAX - 最大速度 - - - - InputDialog - - Cancel - キャンセル - - - Need at least %n character(s)! - - %n文字以上にして下さい! - - - - - MultiOptionDialog - - Select - 選択 - - - Cancel - キャンセル - - - - Networking - - Advanced - 詳細 - - - Enter password - パスワードを入力 - - - for "%1" - [%1] - - - Wrong password - パスワードが違います - - - - OffroadAlert - - Immediately connect to the internet to check for updates. If you do not connect to the internet, openpilot won't engage in %1 - インターネットへ接続してアップデートを確認してください。未接続のままではopenpilotを使用できなくなります。あと[%1] - - - Connect to internet to check for updates. openpilot won't automatically start until it connects to internet to check for updates. - インターネットに接続してアップデートを確認してください。接続するまでopenpilotは使用できません。 - - - Unable to download updates -%1 - 更新をダウンロードできませんでした -%1 - - - Taking camera snapshots. System won't start until finished. - スナップショットを撮影中です。完了するまでシステムは起動しません。 - - - An update to your device's operating system is downloading in the background. You will be prompted to update when it's ready to install. - オペレーティングシステムがバックグラウンドでダウンロードされています。インストールの準備が整うと更新を促されます。 - - - openpilot was unable to identify your car. Your car is either unsupported or its ECUs are not recognized. Please submit a pull request to add the firmware versions to the proper vehicle. Need help? Join discord.comma.ai. - openpilotが車両を識別できませんでした。車が未対応またはECUが認識されていない可能性があります。該当車両のファームウェアバージョンを追加するためにプルリクエストしてください。サポートが必要な場合は discord.comma.ai に参加することができます。 - - - openpilot detected a change in the device's mounting position. Ensure the device is fully seated in the mount and the mount is firmly secured to the windshield. - openpilotがデバイスの取り付け位置にずれを検出しました。デバイスの固定とマウントがフロントガラスにしっかりと取り付けられていることを確認してください。 - - - Device temperature too high. System cooling down before starting. Current internal component temperature: %1 - デバイスの温度が高すぎるためシステム起動前の冷却中です。現在のデバイス内部温度: %1 - - - Device failed to register with the comma.ai backend. It will not connect or upload to comma.ai servers, and receives no support from comma.ai. If this is a device purchased at comma.ai/shop, open a ticket at https://comma.ai/support. - 製品のcomma.aiへの登録に失敗しました。このデバイスはcomma.aiのサーバーに接続したりアップロードを行ったりすることはできず、comma.aiからのサポートも受けられません。もしcomma.ai/shopで購入した場合は、https://comma.ai/support にてサポートチケットをご提出ください。 - - - Acknowledge Excessive Actuation - 過剰な作動の検知 - - - Snooze Update - また後で更新する - - - openpilot detected excessive %1 actuation on your last drive. Please contact support at https://comma.ai/support and share your device's Dongle ID for troubleshooting. - - - - - OffroadHome - - UPDATE - 更新 - - - ALERTS - 警告 - - - ALERT - 警告 - - - - OnroadAlerts - - openpilot Unavailable - openpilotは使用できません - - - TAKE CONTROL IMMEDIATELY - 直ちに車の運転に戻って下さい - - - Reboot Device - デバイスを再起動してください - - - Waiting to start - 始動を待機しています - - - System Unresponsive - システムが応答しません - - - - PairingPopup - - Pair your device to your comma account - デバイスとcommaアカウントを連携して下さい - - - Go to https://connect.comma.ai on your phone - スマートフォンで https://connect.comma.ai にアクセスしてください - - - Click "add new device" and scan the QR code on the right - 「add new device」を押して右側のQRコードをスキャンしてください - - - Bookmark connect.comma.ai to your home screen to use it like an app - connect.comma.aiのサイトをホーム画面に追加して、アプリのように使うことができます。 - - - Please connect to Wi-Fi to complete initial pairing - 最初にペアリングするため、Wi-Fiに接続してください - - - - ParamControl - - Cancel - キャンセル - - - Enable - 有効にする - - - - PrimeAdWidget - - Upgrade Now - 今すぐアップグレード - - - Become a comma prime member at connect.comma.ai - connect.comma.ai からプライム会員に登録できます - - - PRIME FEATURES: - 特典: - - - Remote access - リモートアクセス - - - 24/7 LTE connectivity - 24時間365日のLTE接続 - - - 1 year of drive storage - 1年間分のドライブストレージ - - - Remote snapshots - リモートスナップショット - - - - PrimeUserWidget - - ✓ SUBSCRIBED - ✓ 有効です - - - comma prime - commaプライム - - - - QObject - - openpilot - openpilot - - - %n minute(s) ago - - %n分前 - - - - %n hour(s) ago - - %n時間前 - - - - %n day(s) ago - - %n日前 - - - - now - たった今 - - - - SettingsWindow - - × - × - - - Device - デバイス - - - Network - ネット - - - Toggles - 機能 - - - Software - ソフト - - - Developer - 開発 - - - Firehose - データ学習 - - - - SetupWidget - - Finish Setup - セットアップの完了 - - - Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer. - デバイスをcommaコネクト(connect.comma.ai)でペアリングしてcommaプライムの特典を受け取ってください。 - - - Pair device - デバイスのペアリング - - - - Sidebar - - CONNECT - 接続 - - - OFFLINE - オフライン - - - ONLINE - オンライン - - - ERROR - エラー - - - TEMP - 温度 - - - HIGH - 高温 - - - GOOD - 最適 - - - OK - OK - - - VEHICLE - 車両 - - - NO - NO - - - PANDA - PANDA - - - -- - -- - - - Wi-Fi - Wi-Fi - - - ETH - ETH - - - 2G - 2G - - - 3G - 3G - - - LTE - LTE - - - 5G - 5G - - - - SoftwarePanel - - Updates are only downloaded while the car is off. - 車の電源がオフの間のみアップデートがダウンロードできます。 - - - Current Version - 現在のバージョン - - - Download - ダウンロード - - - Install Update - アップデート - - - INSTALL - インストール - - - Target Branch - 対象のブランチ - - - SELECT - 選択 - - - Select a branch - ブランチを選択 - - - UNINSTALL - 実行 - - - Uninstall %1 - %1をアンインストール - - - Are you sure you want to uninstall? - アンインストールしてもよろしいですか? - - - CHECK - 確認 - - - Uninstall - アンインストール - - - failed to check for update - アップデートの確認に失敗しました。 - - - up to date, last checked %1 - 最新の状態です。最終確認日時:%1 - - - DOWNLOAD - ダウンロード - - - update available - アップデートが利用可能です - - - never - 無効 - - - - SshControl - - SSH Keys - SSH 鍵 - - - Warning: This grants SSH access to all public keys in your GitHub settings. Never enter a GitHub username other than your own. A comma employee will NEVER ask you to add their GitHub username. - 警告: これはGitHubの設定にあるすべての公開鍵への SSH アクセスを許可するものです。自分以外のGitHubユーザー名を入力しないでください。commaのスタッフがGitHubのユーザー名を追加するようお願いすることはありません。 - - - ADD - 追加 - - - Enter your GitHub username - GitHubのユーザー名を入力してください - - - LOADING - 読み込み中 - - - REMOVE - 削除 - - - Username '%1' has no keys on GitHub - ユーザー名“%1”は GitHub に公開鍵がありません - - - Request timed out - リクエストタイムアウト - - - Username '%1' doesn't exist on GitHub - ユーザー名”%1”は GitHub に存在しません - - - - SshToggle - - Enable SSH - SSHの有効化 - - - - TermsPage - - Decline - 拒否 - - - Agree - 同意 - - - Welcome to openpilot - openpilotへようこそ - - - You must accept the Terms and Conditions to use openpilot. Read the latest terms at <span style='color: #465BEA;'>https://comma.ai/terms</span> before continuing. - openpilotを使用するには利用規約に同意する必要があります。続行する前に最新の規約を以下でご確認ください: <span style='color: #465BEA;'>https://comma.ai/terms</span> - - - - TogglesPanel - - Enable openpilot - openpilotを有効化 - - - Enable Lane Departure Warnings - 車線逸脱警報の有効化 - - - 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). - 時速31マイル(50km)以上のスピードで走行中、ウインカーを作動させずに検出したレーン上に車両が触れた場合、手動で車線内に戻るように警告を行います。 - - - Use Metric System - メートル法の使用 - - - Display speed in km/h instead of mph. - 速度は mph ではなく km/h で表示されます。 - - - Record and Upload Driver Camera - 車内カメラの録画とアップロード - - - Upload data from the driver facing camera and help improve the driver monitoring algorithm. - 車内カメラの映像をアップロードし、ドライバー監視システムのアルゴリズムの向上に役立てます。 - - - Disengage on Accelerator Pedal - アクセルを踏むと運転サポートを中断 - - - When enabled, pressing the accelerator pedal will disengage openpilot. - この機能を有効化すると、openpilotを利用中にアクセルを踏むとopenpilotによる運転サポートを中断します。 - - - Experimental Mode - Experimentalモード - - - openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: - openpilotは標準ではゆっくりとくつろげる運転を提供します。このExperimental(実験)モードを有効にすると、以下のアグレッシブな開発中の機能を利用する事ができます。 - - - Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. - openpilotにアクセルとブレーキを任せます。openpilotは赤信号や一時停止サインでの停止を含み、人間と同じように考えて運転を行います。openpilotが運転速度を決定するため、あなたが設定する速度は上限速度になります。この機能は実験段階のため、openpilotの運転ミスに常に備えて注意してください。 - - - New Driving Visualization - 新しい運転画面 - - - Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. - 車両の標準ACC(アダプティブ・クルーズ・コントロール)がアクセル制御に使用されているため、現在Experimentalモードは利用できません。 - - - Aggressive - アグレッシブ - - - Standard - 標準 - - - Relaxed - リラックス - - - Driving Personality - 運転傾向 - - - End-to-End Longitudinal Control - End-to-Endアクセル制御 - - - openpilot longitudinal control may come in a future update. - openpilotのアクセル制御は将来のアップデートで利用できる可能性があります。 - - - An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. - openpilotのアルファ版アクセル制御は、Experimentalモードと共に非リリースのブランチでテストすることができます。 - - - Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. - openpilotのアクセル制御機能(アルファ)を有効にして、Experimentalモードを許可してください。 - - - Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. - 標準モードが推奨されます。アグレッシブモードではopenpilotは先行車に近づいて追従し、アクセルとブレーキがより強気になります。リラックスモードではopenpilotは先行車から距離を取って走行します。サポートされている車両ではステアリングホイールの距離ボタンでこれらのモードを切り替えることができます。 - - - The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. - 運転時の画面効果として、低速時にカーブをより良く表示するために道路用の広角カメラに切り替わります。またExperimentalモードのロゴが右上隅に表示されます。 - - - Always-On Driver Monitoring - 運転者の常時モニタリング - - - Enable driver monitoring even when openpilot is not engaged. - openpilotが作動していない場合でも運転者モニタリングを有効にする。 - - - Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. - openpilotシステムを使用してアダプティブクルーズコントロールおよび車線維持支援を行います。システム使用中は常にドライバーが事故を起こさないように注意を払ってください。 - - - Changing this setting will restart openpilot if the car is powered on. - この設定を変更すると車の電源が入っている場合はopenpilotが再起動します。 - - - Record and Upload Microphone Audio - マイク音声の録音とアップロード - - - Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect. - 運転中にマイク音声を録音・保存します。音声は comma connect のドライブレコーダー映像に含まれます。 - - - - WiFiPromptWidget - - Open - 開く - - - Maximize your training data uploads to improve openpilot's driving models. - openpilotの運転モデルを改善するために、大容量の学習データをアップロードして下さい。 - - - <span style='font-family: "Noto Color Emoji";'>🔥</span> Firehose Mode <span style='font-family: Noto Color Emoji;'>🔥</span> - <span style='font-family: "Noto Color Emoji";'>🔥</span> Firehoseモード <span style='font-family: Noto Color Emoji;'>🔥</span> - - - - WifiUI - - Scanning for networks... - ネットワークをスキャン中... - - - CONNECTING... - 接続中... - - - FORGET - 削除 - - - Forget Wi-Fi Network "%1"? - Wi-Fiネットワーク%1を削除してもよろしいですか? - - - Forget - 削除 - - - diff --git a/selfdrive/ui/translations/ko.ts b/selfdrive/ui/translations/ko.ts deleted file mode 100644 index 9ab43dd9b..000000000 --- a/selfdrive/ui/translations/ko.ts +++ /dev/null @@ -1,1069 +0,0 @@ - - - - - AbstractAlert - - Close - 닫기 - - - Reboot and Update - 업데이트 및 재부팅 - - - - AdvancedNetworking - - Back - 뒤로 - - - Enable Tethering - 테더링 사용 - - - Tethering Password - 테더링 비밀번호 - - - EDIT - 편집 - - - Enter new tethering password - 새 테더링 비밀번호를 입력하세요 - - - IP Address - IP 주소 - - - Enable Roaming - 로밍 사용 - - - APN Setting - APN 설정 - - - Enter APN - APN 입력 - - - leave blank for automatic configuration - 자동 설정하려면 빈 칸으로 두세요 - - - Cellular Metered - 모바일 데이터 종량제 - - - Hidden Network - 숨겨진 네트워크 - - - CONNECT - 연결됨 - - - Enter SSID - SSID 입력 - - - Enter password - 비밀번호를 입력하세요 - - - for "%1" - "%1"에 접속하려면 비밀번호가 필요합니다 - - - Prevent large data uploads when on a metered cellular connection - 모바일 데이터 종량제 사용 시 대용량 데이터 업로드 방지 - - - default - 기본 - - - metered - 종량제 - - - unmetered - 무제한 - - - Wi-Fi Network Metered - 제한된 Wi-Fi 네트워크 - - - Prevent large data uploads when on a metered Wi-Fi connection - 제한된 Wi-Fi 사용 시 대용량 데이터 업로드 방지 - - - - ConfirmationDialog - - Ok - 확인 - - - Cancel - 취소 - - - - DeclinePage - - You must accept the Terms and Conditions in order to use openpilot. - 오픈파일럿을 사용하려면 이용약관에 동의해야 합니다. - - - Back - 뒤로 - - - Decline, uninstall %1 - 거절, %1 제거 - - - - DeveloperPanel - - Joystick Debug Mode - 조이스틱 디버그 모드 - - - Longitudinal Maneuver Mode - 가감속 제어 조작 모드 - - - openpilot Longitudinal Control (Alpha) - 오픈파일럿 가감속 제어 (알파) - - - WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB). - 경고: 오픈파일럿 가감속 제어는 알파 기능으로 차량의 자동긴급제동(AEB)기능이 작동하지 않습니다. - - - On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha. - 이 차량에서 오픈파일럿은 오픈파일럿 가감속 제어 대신 기본적으로 차량의 ACC로 가감속을 제어합니다. 오픈파일럿 가감속 제어로 전환하려면 이 기능을 활성화하세요. 오픈파일럿 가감속 제어 알파 기능을 활성화하는 경우 실험 모드 활성화를 권장합니다. - - - Enable ADB - ADB 사용 - - - ADB (Android Debug Bridge) allows connecting to your device over USB or over the network. See https://docs.comma.ai/how-to/connect-to-comma for more info. - ADB (안드로이드 디버그 브릿지) USB 또는 네트워크를 통해 장치에 연결할 수 있습니다. 자세한 내용은 https://docs.comma.ai/how-to/connect-to-comma를 참조하세요. - - - - DevicePanel - - Dongle ID - 동글 ID - - - N/A - N/A - - - Serial - 시리얼 - - - Driver Camera - 운전자 카메라 - - - PREVIEW - 미리보기 - - - Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off) - 운전자 모니터링이 잘 되는지 확인하기 위해 후면 카메라를 미리 봅니다. (차량 시동이 꺼져 있어야 합니다) - - - Reset Calibration - 캘리브레이션 - - - RESET - 초기화 - - - Are you sure you want to reset calibration? - 캘리브레이션을 초기화하시겠습니까? - - - Review Training Guide - 트레이닝 가이드 - - - REVIEW - 다시보기 - - - Review the rules, features, and limitations of openpilot - 오픈파일럿의 규칙, 기능 및 제한 다시 확인 - - - Are you sure you want to review the training guide? - 트레이닝 가이드를 다시 확인하시겠습니까? - - - Regulatory - 규제 - - - VIEW - 보기 - - - Change Language - 언어 변경 - - - CHANGE - 변경 - - - Select a language - 언어를 선택하세요 - - - Reboot - 재부팅 - - - Power Off - 전원 끄기 - - - Your device is pointed %1° %2 and %3° %4. - 사용자의 장치는 %2 %1° 및 %4 %3° 의 방향으로 장착되어 있습니다. - - - down - 아래로 - - - up - 위로 - - - left - 좌측으로 - - - right - 우측으로 - - - Are you sure you want to reboot? - 재부팅하시겠습니까? - - - Disengage to Reboot - 재부팅하려면 연결을 해제하세요 - - - Are you sure you want to power off? - 전원을 끄시겠습니까? - - - Disengage to Power Off - 전원을 끄려면 연결을 해제하세요 - - - Reset - 초기화 - - - Review - 다시보기 - - - Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer. - 장치를 comma connect (connect.comma.ai)에서 동기화하고 comma prime 무료 이용권을 사용하세요. - - - Pair Device - 장치 동기화 - - - PAIR - 동기화 - - - Disengage to Reset Calibration - 캘리브레이션을 재설정하려면 해제하세요 - - - openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. - 오픈파일럿 장치는 좌우측으로는 4° 또는 위로는 5° 아래로는 9° 이내에 장착해야합니다. - - - openpilot is continuously calibrating, resetting is rarely required. Resetting calibration will restart openpilot if the car is powered on. - 오픈파일럿은 지속적으로 갤리브레이션되어 재설정이 거의 필요하지 않습니다. 차량과 연결된 경우 캘리브레이션 재설정이 오픈파일럿을 재시작합니다. - - - - -Steering lag calibration is %1% complete. - - -조향 지연 캘리브레이션이 %1% 진행되었습니다. - - - - -Steering lag calibration is complete. - - -조향 지연 캘리브레이션이 완료되었습니다. - - - Steering torque response calibration is %1% complete. - 조향 토크 응답 캘리브레이션이 %1% 진행되었습니다. - - - Steering torque response calibration is complete. - 조향 토크 응답 캘리브레이션이 완료되었습니다. - - - - DriverViewWindow - - camera starting - 카메라 시작 중 - - - - ExperimentalModeButton - - EXPERIMENTAL MODE ON - 실험 모드 사용 - - - CHILL MODE ON - 안정 모드 사용 - - - - FirehosePanel - - openpilot learns to drive by watching humans, like you, drive. - -Firehose Mode allows you to maximize your training data uploads to improve openpilot's driving models. More data means bigger models, which means better Experimental Mode. - 오픈파일럿은 여러분과 같은 사람이 운전하는 모습을 보면서 운전하는 법을 배웁니다. - -파이어호스 모드를 사용하면 학습 데이터 업로드를 최대화하여 오픈파일럿의 주행 모델을 개선할 수 있습니다. 더 많은 데이터는 더 큰 모델을 의미하며, 이는 더 나은 실험 모드를 의미합니다. - - - Firehose Mode: ACTIVE - 파이어호스 모드: 활성화 - - - ACTIVE - 활성 상태 - - - For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.<br><br>Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.<br><br><br><b>Frequently Asked Questions</b><br><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><br><i>Do all of my segments get pulled in Firehose Mode?</i> No, we selectively pull a subset of your segments.<br><br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><br><i>Does it matter which software I run?</i> Yes, only upstream openpilot (and particular forks) are able to be used for training. - 최대한의 효과를 얻으려면 매주 장치를 실내로 가져와 좋은 USB-C 충전기와 Wi-Fi에 연결하세요.<br><br>파이어호스 모드는 핫스팟 또는 무제한 네트워크에 연결된 경우 주행 중에도 작동할 수 있습니다.<br><br><br><b>자주 묻는 질문</b><br><br><i>운전 방법이나 장소가 중요한가요?</i> 아니요, 평소처럼 운전하시면 됩니다.<br><br><i>파이어호스 모드에서 제 모든 구간을 가져오나요?<br><br><i> 아니요, 저희는 여러분의 구간 중 일부를 선별적으로 가져옵니다.<br><br><i>좋은 USB-C 충전기는 무엇인가요?</i> 휴대폰이나 노트북충전이 가능한 고속 충전기이면 괜찮습니다.<br><br><i>어떤 소프트웨어를 실행하는지가 중요한가요?</i> 예, 오직 공식 오픈파일럿의 특정 포크만 트레이닝에 사용할 수 있습니다. - - - <b>%n segment(s)</b> of your driving is in the training dataset so far. - - <b>%n 구간</b> 의 운전이 지금까지의 학습 데이터셋에 포함되어 있습니다. - - - - <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to an unmetered network - <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>비활성 상태</span>: 무제한 네트워크에 연결 하세요 - - - Firehose Mode - 파이어호스 모드 - - - - HudRenderer - - km/h - km/h - - - mph - mph - - - MAX - MAX - - - - InputDialog - - Cancel - 취소 - - - Need at least %n character(s)! - - 최소 %n자 이상이어야 합니다! - - - - - MultiOptionDialog - - Select - 선택 - - - Cancel - 취소 - - - - Networking - - Advanced - 고급 설정 - - - Enter password - 비밀번호를 입력하세요 - - - for "%1" - "%1"에 접속하려면 비밀번호가 필요합니다 - - - Wrong password - 비밀번호가 틀렸습니다 - - - - OffroadAlert - - Immediately connect to the internet to check for updates. If you do not connect to the internet, openpilot won't engage in %1 - 즉시 인터넷에 연결하여 업데이트를 확인하세요. 인터넷에 연결되어 있지 않으면 %1 이후에는 오픈파일럿이 활성화되지 않습니다. - - - Connect to internet to check for updates. openpilot won't automatically start until it connects to internet to check for updates. - 업데이트 확인을 위해 인터넷 연결이 필요합니다. 오픈파일럿은 업데이트 확인을 위해 인터넷에 연결될 때까지 자동으로 시작되지 않습니다. - - - Unable to download updates -%1 - 업데이트를 다운로드할 수 없습니다 -%1 - - - Taking camera snapshots. System won't start until finished. - 카메라 스냅샷 찍기가 완료될 때까지 시스템이 시작되지 않습니다. - - - An update to your device's operating system is downloading in the background. You will be prompted to update when it's ready to install. - 백그라운드에서 운영 체제에 대한 업데이트가 다운로드되고 있습니다. 설치가 준비되면 업데이트 메시지가 표시됩니다. - - - openpilot was unable to identify your car. Your car is either unsupported or its ECUs are not recognized. Please submit a pull request to add the firmware versions to the proper vehicle. Need help? Join discord.comma.ai. - 오픈파일럿이 차량을 식별할 수 없습니다. 지원되지 않는 차량이거나 ECU가 인식되지 않습니다. 해당 차량에 맞는 펌웨어 버전을 추가하려면 PR을 제출하세요. 도움이 필요하시면 discord.comma.ai에 참여하세요. - - - openpilot detected a change in the device's mounting position. Ensure the device is fully seated in the mount and the mount is firmly secured to the windshield. - 오픈파일럿 장치의 장착 위치가 변경되었습니다. 장치가 마운트에 완전히 장착되고 마운트가 앞유리에 단단히 고정되었는지 확인하세요. - - - Device temperature too high. System cooling down before starting. Current internal component temperature: %1 - 장치 온도가 너무 높습니다. 시작하기 전에 시스템을 냉각하고 있습니다. 현재 내부 구성 요소 온도: %1 - - - Device failed to register with the comma.ai backend. It will not connect or upload to comma.ai servers, and receives no support from comma.ai. If this is a device purchased at comma.ai/shop, open a ticket at https://comma.ai/support. - 장치를 comma.ai 백엔드에 등록하지 못했습니다. comma.ai 서버에 연결하거나 업로드하지 않으며 comma.ai로부터 지원을받지 않습니다. comma.ai shop에서 구매 한 장치 인 경우 https://comma.ai/support에서 티켓을 여십시오. - - - Acknowledge Excessive Actuation - 과도한 작동을 인정하십시오 - - - Snooze Update - 업데이트 일시 중지 - - - openpilot detected excessive %1 actuation on your last drive. Please contact support at https://comma.ai/support and share your device's Dongle ID for troubleshooting. - - - - - OffroadHome - - UPDATE - 업데이트 - - - ALERTS - 알림 - - - ALERT - 알림 - - - - OnroadAlerts - - openpilot Unavailable - 오픈파일럿을 사용할수없습니다 - - - TAKE CONTROL IMMEDIATELY - 핸들을 잡아주세요 - - - Reboot Device - 장치를 재부팅하세요 - - - Waiting to start - 시작을 기다리는중 - - - System Unresponsive - 시스템이 응답하지않습니다 - - - - PairingPopup - - Pair your device to your comma account - 장치를 comma 계정에 동기화합니다 - - - Go to https://connect.comma.ai on your phone - https://connect.comma.ai에 접속하세요 - - - Click "add new device" and scan the QR code on the right - "새 장치 추가"를 클릭하고 오른쪽 QR 코드를 스캔하세요 - - - Bookmark connect.comma.ai to your home screen to use it like an app - connect.comma.ai를 앱처럼 사용하려면 홈 화면에 바로가기를 만드세요 - - - Please connect to Wi-Fi to complete initial pairing - 초기 동기화를 완료하려면 Wi-Fi에 연결하세요. - - - - ParamControl - - Cancel - 취소 - - - Enable - 활성화 - - - - PrimeAdWidget - - Upgrade Now - 지금 업그레이드하세요 - - - Become a comma prime member at connect.comma.ai - connect.comma.ai에서 comma prime 사용자로 등록하세요 - - - PRIME FEATURES: - PRIME 기능: - - - Remote access - 원격 접속 - - - 24/7 LTE connectivity - 항상 LTE 연결 - - - 1 year of drive storage - 1년간 주행 로그 저장 - - - Remote snapshots - 원격 스냅샷 - - - - PrimeUserWidget - - ✓ SUBSCRIBED - ✓ 구독함 - - - comma prime - comma prime - - - - QObject - - openpilot - 오픈파일럿 - - - %n minute(s) ago - - %n 분 전 - - - - %n hour(s) ago - - %n 시간 전 - - - - %n day(s) ago - - %n 일 전 - - - - now - now - - - - SettingsWindow - - × - × - - - Device - 장치 - - - Network - 네트워크 - - - Toggles - 토글 - - - Software - 소프트웨어 - - - Developer - 개발자 - - - Firehose - 파이어호스 - - - - SetupWidget - - Finish Setup - 설정 완료 - - - Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer. - 장치를 comma connect (connect.comma.ai)에서 동기화하고 comma prime 무료 이용권을 사용하세요. - - - Pair device - 장치 동기화 - - - - Sidebar - - CONNECT - 커넥트 - - - OFFLINE - 연결 안됨 - - - ONLINE - 온라인 - - - ERROR - 오류 - - - TEMP - 온도 - - - HIGH - 높음 - - - GOOD - 좋음 - - - OK - OK - - - VEHICLE - 차량 - - - NO - NO - - - PANDA - PANDA - - - -- - -- - - - Wi-Fi - Wi-Fi - - - ETH - LAN - - - 2G - 2G - - - 3G - 3G - - - LTE - LTE - - - 5G - 5G - - - - SoftwarePanel - - Updates are only downloaded while the car is off. - 업데이트는 차량 시동이 꺼졌을 때 다운로드됩니다. - - - Current Version - 현재 버전 - - - Download - 다운로드 - - - Install Update - 업데이트 설치 - - - INSTALL - 설치 - - - Target Branch - 대상 브랜치 - - - SELECT - 선택 - - - Select a branch - 브랜치 선택 - - - UNINSTALL - 제거 - - - Uninstall %1 - %1 제거 - - - Are you sure you want to uninstall? - 제거하시겠습니까? - - - CHECK - 확인 - - - Uninstall - 제거 - - - failed to check for update - 업데이트를 확인하지 못했습니다 - - - up to date, last checked %1 - 최신 버전입니다. 마지막 확인: %1 - - - DOWNLOAD - 다운로드 - - - update available - 업데이트 가능 - - - never - 업데이트 안함 - - - - SshControl - - SSH Keys - SSH 키 - - - Warning: This grants SSH access to all public keys in your GitHub settings. Never enter a GitHub username other than your own. A comma employee will NEVER ask you to add their GitHub username. - 경고: 이 설정은 GitHub에 등록된 모든 공용 키에 대해 SSH 액세스 권한을 부여합니다. 본인의 GitHub 사용자 아이디 이외에는 입력하지 마십시오. comma에서는 GitHub 아이디를 추가하라는 요청을 하지 않습니다. - - - ADD - 추가 - - - Enter your GitHub username - GitHub 사용자 ID - - - LOADING - 로딩 중 - - - REMOVE - 삭제 - - - Username '%1' has no keys on GitHub - 사용자 '%1'의 GitHub에 키가 등록되어 있지 않습니다 - - - Request timed out - 요청 시간 초과 - - - Username '%1' doesn't exist on GitHub - GitHub 사용자 '%1'를 찾지 못했습니다 - - - - SshToggle - - Enable SSH - SSH 사용 - - - - TermsPage - - Decline - 거절 - - - Agree - 동의 - - - Welcome to openpilot - 오픈파일럿에 오신 것을 환영합니다. - - - You must accept the Terms and Conditions to use openpilot. Read the latest terms at <span style='color: #465BEA;'>https://comma.ai/terms</span> before continuing. - 오픈파일럿을 사용하려면 이용약관에 동의해야 합니다. 최신 약관은 <span style='color: #465BEA;'>https://comma.ai/terms</span> 에서 최신 약관을 읽은 후 계속하세요. - - - - TogglesPanel - - Enable openpilot - 오픈파일럿 사용 - - - Enable Lane Departure Warnings - 차선 이탈 경고 활성화 - - - 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). - 차량이 50km/h(31mph) 이상의 속도로 주행할 때 방향지시등이 켜지지 않은 상태에서 차선을 벗어나면 경고합니다. - - - Use Metric System - 미터법 사용 - - - Display speed in km/h instead of mph. - mph 대신 km/h로 속도를 표시합니다. - - - Record and Upload Driver Camera - 운전자 카메라 녹화 및 업로드 - - - Upload data from the driver facing camera and help improve the driver monitoring algorithm. - 운전자 카메라의 영상 데이터를 업로드하여 운전자 모니터링 알고리즘을 개선합니다. - - - Disengage on Accelerator Pedal - 가속페달 조작 시 해제 - - - When enabled, pressing the accelerator pedal will disengage openpilot. - 활성화된 경우 가속 페달을 밟으면 오픈파일럿이 해제됩니다. - - - Experimental Mode - 실험 모드 - - - openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: - 오픈파일럿은 기본적으로 <b>안정 모드</b>로 주행합니다. 실험 모드는 안정화되지 않은 <b>알파 수준의 기능</b>을 활성화합니다. 실험 모드의 기능은 아래와 같습니다: - - - Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. - 주행모델이 가감속을 제어하도록 합니다. 오픈파일럿은 빨간불과 정지신호를 보고 정지하는것을 포함하여 사람이 운전하는 방식대로 작동하며 주행 모델이 속도를 결정하므로 설정 속도는 최대 제한 속도로만 작동합니다. 이는 알파 수준의 기능이며 오류가 발생할수있으니 사용에 주의해야 합니다. - - - New Driving Visualization - 새로운 주행 시각화 - - - Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. - 차량의 ACC가 가감속 제어에 사용되기 때문에, 이 차량에서는 실험 모드를 사용할 수 없습니다. - - - openpilot longitudinal control may come in a future update. - 오픈파일럿 가감속 제어는 향후 업데이트에서 지원될 수 있습니다. - - - Aggressive - 공격적 - - - Standard - 표준 - - - Relaxed - 편안한 - - - Driving Personality - 주행 모드 - - - An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. - 오픈파일럿 가감속 제어 알파 버전은 비 릴리즈 브랜치에서 실험 모드와 함께 테스트할 수 있습니다. - - - Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. - 실험 모드를 사용하려면 오픈파일럿 E2E 가감속 제어 (알파) 토글을 활성화하세요. - - - End-to-End Longitudinal Control - E2E 가감속 제어 - - - Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. - 표준 모드를 권장합니다. 공격적 모드의 오픈파일럿은 선두 차량을 더 가까이 따라가고 가감속제어를 사용하여 더욱 공격적으로 움직입니다. 편안한 모드의 오픈파일럿은 선두 차량으로부터 더 멀리 떨어져 있습니다. 지원되는 차량에서는 차간거리 버튼을 사용하여 이러한 특성을 순환할 수 있습니다. - - - The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. - 운전 시각화는 일부 회전을 더 잘 보여주기 위해 저속에서 도로를 향한 광각 카메라로 전환됩니다. 우측 상단에 실험 모드 로고가 표시됩니다. - - - Always-On Driver Monitoring - 상시 운전자 모니터링 - - - Enable driver monitoring even when openpilot is not engaged. - 오픈파일럿이 활성화되지 않은 경우에도 드라이버 모니터링을 활성화합니다. - - - Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. - ACC 및 차선 유지 지원을 위해 오픈파일럿 시스템을 사용하십시오. 이 기능을 사용하려면 항상주의를 기울여야합니다. - - - Changing this setting will restart openpilot if the car is powered on. - 이 설정을 변경하면 차량이 재가동된후 오픈파일럿이 시작됩니다. - - - Record and Upload Microphone Audio - 마이크 오디오 녹음 및 업로드 - - - Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect. - 운전 중에 마이크 오디오를 녹음하고 저장하십시오. 오디오는 comma connect의 대시캠 비디오에 포함됩니다. - - - - WiFiPromptWidget - - Open - 열기 - - - Maximize your training data uploads to improve openpilot's driving models. - 오픈파일럿의 주행 모델 개선을 위해 학습 데이터 업로드를 최대화하세요. - - - <span style='font-family: "Noto Color Emoji";'>🔥</span> Firehose Mode <span style='font-family: Noto Color Emoji;'>🔥</span> - <span style='font-family: "Noto Color Emoji";'>🔥</span> 파이어호스 모드 <span style='font-family: Noto Color Emoji;'>🔥</span> - - - - WifiUI - - Scanning for networks... - 네트워크 검색 중... - - - CONNECTING... - 연결 중... - - - FORGET - 삭제 - - - Forget Wi-Fi Network "%1"? - Wi-Fi "%1"에 자동으로 연결하지 않겠습니까? - - - Forget - 삭제 - - - diff --git a/selfdrive/ui/translations/nl.ts b/selfdrive/ui/translations/nl.ts deleted file mode 100644 index 10651a416..000000000 --- a/selfdrive/ui/translations/nl.ts +++ /dev/null @@ -1,1120 +0,0 @@ - - - - - AbstractAlert - - Close - Sluit - - - Snooze Update - Update uitstellen - - - Reboot and Update - Opnieuw Opstarten en Updaten - - - - AdvancedNetworking - - Back - Terug - - - Enable Tethering - Tethering Inschakelen - - - Tethering Password - Tethering Wachtwoord - - - EDIT - AANPASSEN - - - Enter new tethering password - Voer nieuw tethering wachtwoord in - - - IP Address - IP Adres - - - Enable Roaming - Roaming Inschakelen - - - APN Setting - APN Instelling - - - Enter APN - Voer APN in - - - leave blank for automatic configuration - laat leeg voor automatische configuratie - - - Cellular Metered - - - - Prevent large data uploads when on a metered connection - - - - - AnnotatedCameraWidget - - km/h - km/u - - - mph - mph - - - MAX - MAX - - - SPEED - SPEED - - - LIMIT - LIMIT - - - - ConfirmationDialog - - Ok - Ok - - - Cancel - Annuleren - - - - DeclinePage - - You must accept the Terms and Conditions in order to use openpilot. - U moet de Algemene Voorwaarden accepteren om openpilot te gebruiken. - - - Back - Terug - - - Decline, uninstall %1 - Afwijzen, verwijder %1 - - - - DevicePanel - - Dongle ID - Dongle ID - - - N/A - Nvt - - - Serial - Serienummer - - - Driver Camera - Bestuurders Camera - - - PREVIEW - BEKIJKEN - - - Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off) - Bekijk de naar de bestuurder gerichte camera om ervoor te zorgen dat het monitoren van de bestuurder goed zicht heeft. (Voertuig moet uitgschakeld zijn) - - - Reset Calibration - Kalibratie Resetten - - - RESET - RESET - - - Are you sure you want to reset calibration? - Weet u zeker dat u de kalibratie wilt resetten? - - - Review Training Guide - Doorloop de Training Opnieuw - - - REVIEW - BEKIJKEN - - - Review the rules, features, and limitations of openpilot - Bekijk de regels, functies en beperkingen van openpilot - - - Are you sure you want to review the training guide? - Weet u zeker dat u de training opnieuw wilt doorlopen? - - - Regulatory - Regelgeving - - - VIEW - BEKIJKEN - - - Change Language - Taal Wijzigen - - - CHANGE - WIJZIGEN - - - Select a language - Selecteer een taal - - - Reboot - Opnieuw Opstarten - - - Power Off - Uitschakelen - - - openpilot requires the device to be mounted within 4° left or right and within 5° up or 8° down. openpilot is continuously calibrating, resetting is rarely required. - openpilot vereist dat het apparaat binnen 4° links of rechts en binnen 5° omhoog of 8° omlaag wordt gemonteerd. openpilot kalibreert continu, resetten is zelden nodig. - - - Your device is pointed %1° %2 and %3° %4. - Uw apparaat is gericht op %1° %2 en %3° %4. - - - down - omlaag - - - up - omhoog - - - left - links - - - right - rechts - - - Are you sure you want to reboot? - Weet u zeker dat u opnieuw wilt opstarten? - - - Disengage to Reboot - Deactiveer openpilot om opnieuw op te starten - - - Are you sure you want to power off? - Weet u zeker dat u wilt uitschakelen? - - - Disengage to Power Off - Deactiveer openpilot om uit te schakelen - - - - DriveStats - - Drives - Ritten - - - Hours - Uren - - - ALL TIME - TOTAAL - - - PAST WEEK - AFGELOPEN WEEK - - - KM - Km - - - Miles - Mijl - - - - DriverViewScene - - camera starting - Camera wordt gestart - - - - InputDialog - - Cancel - Annuleren - - - Need at least %n character(s)! - - Heeft minstens %n karakter nodig! - Heeft minstens %n karakters nodig! - - - - - Installer - - Installing... - Installeren... - - - Receiving objects: - Objecten ontvangen: - - - Resolving deltas: - Deltas verwerken: - - - Updating files: - Bestanden bijwerken: - - - - MapETA - - eta - eta - - - min - min - - - hr - uur - - - km - km - - - mi - mi - - - - MapInstructions - - km - km - - - m - m - - - mi - mi - - - ft - ft - - - - MapPanel - - Current Destination - Huidige Bestemming - - - CLEAR - LEEGMAKEN - - - Recent Destinations - Recente Bestemmingen - - - Try the Navigation Beta - Probeer de Navigatie Bèta - - - Get turn-by-turn directions displayed and more with a comma -prime subscription. Sign up now: https://connect.comma.ai - Krijg stapsgewijze routebeschrijving en meer met een comma -prime abonnement. Meld u nu aan: https://connect.comma.ai - - - No home -location set - Geen thuislocatie -ingesteld - - - No work -location set - Geen werklocatie -ingesteld - - - no recent destinations - geen recente bestemmingen - - - - MapWindow - - Map Loading - Kaart wordt geladen - - - Waiting for GPS - Wachten op GPS - - - - MultiOptionDialog - - Select - Selecteer - - - Cancel - Annuleren - - - - Networking - - Advanced - Geavanceerd - - - Enter password - Voer wachtwoord in - - - for "%1" - voor "%1" - - - Wrong password - Verkeerd wachtwoord - - - - OffroadHome - - UPDATE - UPDATE - - - ALERTS - WAARSCHUWINGEN - - - ALERT - WAARSCHUWING - - - - PairingPopup - - Pair your device to your comma account - Koppel uw apparaat aan uw comma-account - - - Go to https://connect.comma.ai on your phone - Ga naar https://connect.comma.ai op uw telefoon - - - Click "add new device" and scan the QR code on the right - Klik op "add new device" en scan de QR-code aan de rechterkant - - - Bookmark connect.comma.ai to your home screen to use it like an app - Voeg connect.comma.ai toe op uw startscherm om het als een app te gebruiken - - - - PrimeAdWidget - - Upgrade Now - Upgrade nu - - - Become a comma prime member at connect.comma.ai - Word een comma prime lid op connect.comma.ai - - - PRIME FEATURES: - PRIME BEVAT: - - - Remote access - Toegang op afstand - - - 1 year of storage - 1 jaar lang opslag - - - Developer perks - Voordelen voor ontwikkelaars - - - - PrimeUserWidget - - ✓ SUBSCRIBED - ✓ GEABONNEERD - - - comma prime - comma prime - - - CONNECT.COMMA.AI - CONNECT.COMMA.AI - - - COMMA POINTS - COMMA PUNTEN - - - - QObject - - Reboot - Opnieuw Opstarten - - - Exit - Afsluiten - - - dashcam - dashcam - - - openpilot - openpilot - - - %n minute(s) ago - - %n minuut geleden - %n minuten geleden - - - - %n hour(s) ago - - %n uur geleden - %n uur geleden - - - - %n day(s) ago - - %n dag geleden - %n dagen geleden - - - - - Reset - - Reset failed. Reboot to try again. - Opnieuw instellen mislukt. Start opnieuw op om opnieuw te proberen. - - - Are you sure you want to reset your device? - Weet u zeker dat u uw apparaat opnieuw wilt instellen? - - - Resetting device... - Apparaat opnieuw instellen... - - - System Reset - Systeemreset - - - System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot. - Systeemreset geactiveerd. Druk op bevestigen om alle inhoud en instellingen te wissen. Druk op Annuleren om het opstarten te hervatten. - - - Cancel - Annuleren - - - Reboot - Opnieuw Opstarten - - - Confirm - Bevestigen - - - Unable to mount data partition. Press confirm to reset your device. - Kan gegevenspartitie niet koppelen. Druk op bevestigen om uw apparaat te resetten. - - - - RichTextDialog - - Ok - Ok - - - - SettingsWindow - - × - × - - - Device - Apparaat - - - Network - Netwerk - - - Toggles - Opties - - - Software - Software - - - Navigation - Navigatie - - - - Setup - - WARNING: Low Voltage - WAARCHUWING: Lage Spanning - - - Power your device in a car with a harness or proceed at your own risk. - Voorzie uw apparaat van stroom in een auto met een harnas (car harness) of ga op eigen risico verder. - - - Power off - Uitschakelen - - - Continue - Doorgaan - - - Getting Started - Aan de slag - - - Before we get on the road, let’s finish installation and cover some details. - Laten we, voordat we op pad gaan, de installatie afronden en enkele details bespreken. - - - Connect to Wi-Fi - Maak verbinding met Wi-Fi - - - Back - Terug - - - Continue without Wi-Fi - Doorgaan zonder Wi-Fi - - - Waiting for internet - Wachten op internet - - - Choose Software to Install - Kies Software om te Installeren - - - Dashcam - Dashcam - - - Custom Software - Andere Software - - - Enter URL - Voer URL in - - - for Custom Software - voor Andere Software - - - Downloading... - Downloaden... - - - Download Failed - Downloaden Mislukt - - - Ensure the entered URL is valid, and the device’s internet connection is good. - Zorg ervoor dat de ingevoerde URL geldig is en dat de internetverbinding van het apparaat goed is. - - - Reboot device - Apparaat opnieuw opstarten - - - Start over - Begin opnieuw - - - - SetupWidget - - Finish Setup - Installatie voltooien - - - Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer. - Koppel uw apparaat met comma connect (connect.comma.ai) en claim uw comma prime-aanbieding. - - - Pair device - Apparaat koppelen - - - - Sidebar - - CONNECT - VERBINDING - - - OFFLINE - OFFLINE - - - ONLINE - ONLINE - - - ERROR - FOUT - - - TEMP - TEMP - - - HIGH - HOOG - - - GOOD - GOED - - - OK - OK - - - VEHICLE - VOERTUIG - - - NO - GEEN - - - PANDA - PANDA - - - GPS - GPS - - - SEARCH - ZOEKEN - - - -- - -- - - - Wi-Fi - Wi-Fi - - - ETH - ETH - - - 2G - 2G - - - 3G - 3G - - - LTE - 4G - - - 5G - 5G - - - - SoftwarePanel - - Git Branch - Git Branch - - - Git Commit - Git Commit - - - OS Version - OS Versie - - - Version - Versie - - - Last Update Check - Laatste Updatecontrole - - - The last time openpilot successfully checked for an update. The updater only runs while the car is off. - De laatste keer dat openpilot met succes heeft gecontroleerd op een update. De updater werkt alleen als de auto is uitgeschakeld. - - - Check for Update - Controleer op Updates - - - CHECKING - CONTROLEER - - - Switch Branch - Branch Verwisselen - - - ENTER - INVOEREN - - - The new branch will be pulled the next time the updater runs. - Tijdens de volgende update wordt de nieuwe branch opgehaald. - - - Enter branch name - Voer branch naam in - - - Uninstall %1 - Verwijder %1 - - - UNINSTALL - VERWIJDER - - - Are you sure you want to uninstall? - Weet u zeker dat u de installatie ongedaan wilt maken? - - - failed to fetch update - ophalen van update mislukt - - - CHECK - CONTROLEER - - - Updates are only downloaded while the car is off. - - - - Current Version - - - - Download - - - - Install Update - - - - INSTALL - - - - Target Branch - - - - SELECT - - - - Select a branch - - - - - SshControl - - SSH Keys - SSH Sleutels - - - Warning: This grants SSH access to all public keys in your GitHub settings. Never enter a GitHub username other than your own. A comma employee will NEVER ask you to add their GitHub username. - Waarschuwing: dit geeft SSH toegang tot alle openbare sleutels in uw GitHub-instellingen. Voer nooit een andere GitHub-gebruikersnaam in dan die van uzelf. Een medewerker van comma zal u NOOIT vragen om zijn GitHub-gebruikersnaam toe te voegen. - - - ADD - TOEVOEGEN - - - Enter your GitHub username - Voer uw GitHub gebruikersnaam in - - - LOADING - LADEN - - - REMOVE - VERWIJDEREN - - - Username '%1' has no keys on GitHub - Gebruikersnaam '%1' heeft geen SSH sleutels op GitHub - - - Request timed out - Time-out van aanvraag - - - Username '%1' doesn't exist on GitHub - Gebruikersnaam '%1' bestaat niet op GitHub - - - - SshToggle - - Enable SSH - SSH Inschakelen - - - - TermsPage - - Terms & Conditions - Algemene Voorwaarden - - - Decline - Afwijzen - - - Scroll to accept - Scroll om te accepteren - - - Agree - Akkoord - - - - TogglesPanel - - Enable openpilot - openpilot Inschakelen - - - Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. - Gebruik het openpilot-systeem voor adaptieve cruisecontrol en rijstrookassistentie. Uw aandacht is te allen tijde vereist om deze functie te gebruiken. Het wijzigen van deze instelling wordt van kracht wanneer de auto wordt uitgeschakeld. - - - Enable Lane Departure Warnings - Waarschuwingen bij Verlaten Rijstrook Inschakelen - - - 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). - Ontvang waarschuwingen om terug naar de rijstrook te sturen wanneer uw voertuig over een gedetecteerde rijstrookstreep drijft zonder dat de richtingaanwijzer wordt geactiveerd terwijl u harder rijdt dan 50 km/u (31 mph). - - - Use Metric System - Gebruik Metrisch Systeem - - - Display speed in km/h instead of mph. - Geef snelheid weer in km/u in plaats van mph. - - - Record and Upload Driver Camera - Opnemen en Uploaden van de Bestuurders Camera - - - Upload data from the driver facing camera and help improve the driver monitoring algorithm. - Upload gegevens van de bestuurders camera en help het algoritme voor het monitoren van de bestuurder te verbeteren. - - - Disengage on Accelerator Pedal - Deactiveren Met Gaspedaal - - - When enabled, pressing the accelerator pedal will disengage openpilot. - Indien ingeschakeld, zal het indrukken van het gaspedaal openpilot deactiveren. - - - Show ETA in 24h Format - Toon verwachte aankomsttijd in 24-uurs formaat - - - Use 24h format instead of am/pm - Gebruik 24-uurs formaat in plaats van AM en PM - - - Show Map on Left Side of UI - Toon kaart aan linkerkant van het scherm - - - Show map on left side when in split screen view. - Toon kaart links in gesplitste schermweergave. - - - openpilot Longitudinal Control - openpilot Longitudinale Controle - - - openpilot will disable the car's radar and will take over control of gas and brakes. Warning: this disables AEB! - openpilot zal de radar van de auto uitschakelen en de controle over gas en remmen overnemen. Waarschuwing: hierdoor wordt AEB (automatische noodrem) uitgeschakeld! - - - 🌮 End-to-end longitudinal (extremely alpha) 🌮 - - - - Experimental openpilot Longitudinal Control - - - - <b>WARNING: openpilot longitudinal control is experimental for this car and will disable AEB.</b> - - - - Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would. Super experimental. - - - - openpilot longitudinal control is not currently available for this car. - - - - Enable experimental longitudinal control to enable this. - - - - - Updater - - Update Required - Update Vereist - - - An operating system update is required. Connect your device to Wi-Fi for the fastest update experience. The download size is approximately 1GB. - Een update van het besturingssysteem is vereist. Verbind je apparaat met Wi-Fi voor de snelste update-ervaring. De downloadgrootte is ongeveer 1 GB. - - - Connect to Wi-Fi - Maak verbinding met Wi-Fi - - - Install - Installeer - - - Back - Terug - - - Loading... - Aan het laden... - - - Reboot - Opnieuw Opstarten - - - Update failed - Update mislukt - - - - WifiUI - - Scanning for networks... - Scannen naar netwerken... - - - CONNECTING... - VERBINDEN... - - - FORGET - VERGETEN - - - Forget Wi-Fi Network "%1"? - Vergeet Wi-Fi Netwerk "%1"? - - - diff --git a/selfdrive/ui/translations/pl.ts b/selfdrive/ui/translations/pl.ts deleted file mode 100644 index 4f8b03ef5..000000000 --- a/selfdrive/ui/translations/pl.ts +++ /dev/null @@ -1,1124 +0,0 @@ - - - - - AbstractAlert - - Close - Zamknij - - - Snooze Update - Zaktualizuj później - - - Reboot and Update - Uruchom ponownie i zaktualizuj - - - - AdvancedNetworking - - Back - Wróć - - - Enable Tethering - Włącz hotspot osobisty - - - Tethering Password - Hasło do hotspotu - - - EDIT - EDYTUJ - - - Enter new tethering password - Wprowadź nowe hasło do hotspotu - - - IP Address - Adres IP - - - Enable Roaming - Włącz roaming danych - - - APN Setting - Ustawienia APN - - - Enter APN - Wprowadź APN - - - leave blank for automatic configuration - Pozostaw puste, aby użyć domyślnej konfiguracji - - - Cellular Metered - - - - Prevent large data uploads when on a metered connection - - - - - AnnotatedCameraWidget - - km/h - km/h - - - mph - mph - - - MAX - MAX - - - SPEED - PRĘDKOŚĆ - - - LIMIT - OGRANICZENIE - - - - ConfirmationDialog - - Ok - Ok - - - Cancel - Anuluj - - - - DeclinePage - - You must accept the Terms and Conditions in order to use openpilot. - Aby korzystać z openpilota musisz zaakceptować regulamin. - - - Back - Wróć - - - Decline, uninstall %1 - Odrzuć, odinstaluj %1 - - - - DevicePanel - - Dongle ID - ID adaptera - - - N/A - N/A - - - Serial - Numer seryjny - - - Driver Camera - Kamera kierowcy - - - PREVIEW - PODGLĄD - - - Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off) - Wyświetl podgląd z kamery skierowanej na kierowcę, aby upewnić się, że monitoring kierowcy ma dobry zakres widzenia. (pojazd musi być wyłączony) - - - Reset Calibration - Zresetuj kalibrację - - - RESET - ZRESETUJ - - - Are you sure you want to reset calibration? - Czy na pewno chcesz zresetować kalibrację? - - - Review Training Guide - Zapoznaj się z samouczkiem - - - REVIEW - ZAPOZNAJ SIĘ - - - Review the rules, features, and limitations of openpilot - Zapoznaj się z zasadami, funkcjami i ograniczeniami openpilota - - - Are you sure you want to review the training guide? - Czy na pewno chcesz się zapoznać z samouczkiem? - - - Regulatory - Regulacja - - - VIEW - WIDOK - - - Change Language - Zmień język - - - CHANGE - ZMIEŃ - - - Select a language - Wybierz język - - - Reboot - Uruchom ponownie - - - Power Off - Wyłącz - - - openpilot requires the device to be mounted within 4° left or right and within 5° up or 8° down. openpilot is continuously calibrating, resetting is rarely required. - openpilot wymaga, aby urządzenie było zamontowane z maksymalnym odchyłem 4° poziomo, 5° w górę oraz 8° w dół. openpilot jest ciągle kalibrowany, rzadko konieczne jest resetowania urządzenia. - - - Your device is pointed %1° %2 and %3° %4. - Twoje urządzenie jest skierowane %1° %2 oraz %3° %4. - - - down - w dół - - - up - w górę - - - left - w lewo - - - right - w prawo - - - Are you sure you want to reboot? - Czy na pewno chcesz uruchomić ponownie urządzenie? - - - Disengage to Reboot - Aby uruchomić ponownie, odłącz sterowanie - - - Are you sure you want to power off? - Czy na pewno chcesz wyłączyć urządzenie? - - - Disengage to Power Off - Aby wyłączyć urządzenie, odłącz sterowanie - - - - DriveStats - - Drives - Przejazdy - - - Hours - Godziny - - - ALL TIME - CAŁKOWICIE - - - PAST WEEK - OSTATNI TYDZIEŃ - - - KM - KM - - - Miles - Mile - - - - DriverViewScene - - camera starting - uruchamianie kamery - - - - InputDialog - - Cancel - Anuluj - - - Need at least %n character(s)! - - Wpisana wartość powinna składać się przynajmniej z %n znaku! - Wpisana wartość powinna skłądać się przynajmniej z %n znaków! - Wpisana wartość powinna skłądać się przynajmniej z %n znaków! - - - - - Installer - - Installing... - Instalowanie... - - - Receiving objects: - Odbieranie obiektów: - - - Resolving deltas: - Rozwiązywanie różnic: - - - Updating files: - Aktualizacja plików: - - - - MapETA - - eta - przewidywany czas - - - min - min - - - hr - godz - - - km - km - - - mi - mi - - - - MapInstructions - - km - km - - - m - m - - - mi - mi - - - ft - ft - - - - MapPanel - - Current Destination - Miejsce docelowe - - - CLEAR - WYCZYŚĆ - - - Recent Destinations - Ostatnie miejsca docelowe - - - Try the Navigation Beta - Wypróbuj nawigację w wersji beta - - - Get turn-by-turn directions displayed and more with a comma -prime subscription. Sign up now: https://connect.comma.ai - Odblokuj nawigację zakręt po zakęcie i wiele więcej subskrybując -comma prime. Zarejestruj się teraz: https://connect.comma.ai - - - No home -location set - Lokalizacja domu -nie została ustawiona - - - No work -location set - Miejsce pracy -nie zostało ustawione - - - no recent destinations - brak ostatnich miejsc docelowych - - - - MapWindow - - Map Loading - Ładowanie Mapy - - - Waiting for GPS - Oczekiwanie na sygnał GPS - - - - MultiOptionDialog - - Select - Wybierz - - - Cancel - Anuluj - - - - Networking - - Advanced - Zaawansowane - - - Enter password - Wprowadź hasło - - - for "%1" - do "%1" - - - Wrong password - Niepoprawne hasło - - - - OffroadHome - - UPDATE - UAKTUALNIJ - - - ALERTS - ALERTY - - - ALERT - ALERT - - - - PairingPopup - - Pair your device to your comma account - Sparuj swoje urzadzenie ze swoim kontem comma - - - Go to https://connect.comma.ai on your phone - Wejdź na stronę https://connect.comma.ai na swoim telefonie - - - Click "add new device" and scan the QR code on the right - Kliknij "add new device" i zeskanuj kod QR znajdujący się po prawej stronie - - - Bookmark connect.comma.ai to your home screen to use it like an app - Dodaj connect.comma.ai do zakładek na swoim ekranie początkowym, aby korzystać z niej jak z aplikacji - - - - PrimeAdWidget - - Upgrade Now - Uaktualnij teraz - - - Become a comma prime member at connect.comma.ai - Zostań członkiem comma prime na connect.comma.ai - - - PRIME FEATURES: - FUNKCJE PRIME: - - - Remote access - Zdalny dostęp - - - 1 year of storage - 1 rok przechowywania danych - - - Developer perks - Udogodnienia dla programistów - - - - PrimeUserWidget - - ✓ SUBSCRIBED - ✓ ZASUBSKRYBOWANO - - - comma prime - comma prime - - - CONNECT.COMMA.AI - CONNECT.COMMA.AI - - - COMMA POINTS - COMMA POINTS - - - - QObject - - Reboot - Uruchom Ponownie - - - Exit - Wyjdź - - - dashcam - wideorejestrator - - - openpilot - openpilot - - - %n minute(s) ago - - %n minutę temu - %n minuty temu - %n minut temu - - - - %n hour(s) ago - - % godzinę temu - %n godziny temu - %n godzin temu - - - - %n day(s) ago - - %n dzień temu - %n dni temu - %n dni temu - - - - - Reset - - Reset failed. Reboot to try again. - Wymazywanie zakończone niepowodzeniem. Aby spróbować ponownie, uruchom ponownie urządzenie. - - - Are you sure you want to reset your device? - Czy na pewno chcesz wymazać urządzenie? - - - Resetting device... - Wymazywanie urządzenia... - - - System Reset - Przywróć do ustawień fabrycznych - - - System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot. - Przywracanie do ustawień fabrycznych. Wciśnij potwierdź, aby usunąć wszystkie dane oraz ustawienia. Wciśnij anuluj, aby wznowić uruchamianie. - - - Cancel - Anuluj - - - Reboot - Uruchom ponownie - - - Confirm - Potwiedź - - - Unable to mount data partition. Press confirm to reset your device. - Partycja nie została zamontowana poprawnie. Wciśnij potwierdź, aby uruchomić ponownie urządzenie. - - - - RichTextDialog - - Ok - Ok - - - - SettingsWindow - - × - x - - - Device - Urządzenie - - - Network - Sieć - - - Toggles - Przełączniki - - - Software - Oprogramowanie - - - Navigation - Nawigacja - - - - Setup - - WARNING: Low Voltage - OSTRZEŻENIE: Niskie Napięcie - - - Power your device in a car with a harness or proceed at your own risk. - Podłącz swoje urządzenie do zasilania poprzez podłączenienie go do pojazdu lub kontynuuj na własną odpowiedzialność. - - - Power off - Wyłącz - - - Continue - Kontynuuj - - - Getting Started - Zacznij - - - Before we get on the road, let’s finish installation and cover some details. - Zanim ruszysz w drogę, dokończ instalację i podaj kilka szczegółów. - - - Connect to Wi-Fi - Połącz z Wi-Fi - - - Back - Wróć - - - Continue without Wi-Fi - Kontynuuj bez połączenia z Wif-Fi - - - Waiting for internet - Oczekiwanie na połączenie sieciowe - - - Choose Software to Install - Wybierz oprogramowanie do instalacji - - - Dashcam - Wideorejestrator - - - Custom Software - Własne oprogramowanie - - - Enter URL - Wprowadź adres URL - - - for Custom Software - do własnego oprogramowania - - - Downloading... - Pobieranie... - - - Download Failed - Pobieranie nie powiodło się - - - Ensure the entered URL is valid, and the device’s internet connection is good. - Upewnij się, że wpisany adres URL jest poprawny, a połączenie internetowe działa poprawnie. - - - Reboot device - Uruchom ponownie - - - Start over - Zacznij od początku - - - - SetupWidget - - Finish Setup - Zakończ konfigurację - - - Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer. - Sparuj swoje urządzenie z comma connect (connect.comma.ai) i wybierz swoją ofertę comma prime. - - - Pair device - Sparuj urządzenie - - - - Sidebar - - CONNECT - POŁĄCZENIE - - - OFFLINE - OFFLINE - - - ONLINE - ONLINE - - - ERROR - BŁĄD - - - TEMP - TEMP - - - HIGH - WYSOKA - - - GOOD - DOBRA - - - OK - OK - - - VEHICLE - POJAZD - - - NO - BRAK - - - PANDA - PANDA - - - GPS - GPS - - - SEARCH - SZUKAJ - - - -- - -- - - - Wi-Fi - Wi-FI - - - ETH - ETH - - - 2G - 2G - - - 3G - 3G - - - LTE - LTE - - - 5G - 5G - - - - SoftwarePanel - - Git Branch - Gałąź Git - - - Git Commit - Git commit - - - OS Version - Wersja systemu - - - Version - Wersja - - - Last Update Check - Ostatnie sprawdzenie aktualizacji - - - The last time openpilot successfully checked for an update. The updater only runs while the car is off. - Ostatni raz kiedy openpilot znalazł aktualizację. Aktualizator może być uruchomiony wyłącznie wtedy, kiedy pojazd jest wyłączony. - - - Check for Update - Sprawdź uaktualnienia - - - CHECKING - SPRAWDZANIE - - - Switch Branch - Zmień gąłąź - - - ENTER - WPROWADŹ - - - The new branch will be pulled the next time the updater runs. - Nowa gałąź będzie pobrana przy następnym uruchomieniu aktualizatora. - - - Enter branch name - Wprowadź nazwę gałęzi - - - Uninstall %1 - Odinstaluj %1 - - - UNINSTALL - ODINSTALUJ - - - Are you sure you want to uninstall? - Czy na pewno chcesz odinstalować? - - - failed to fetch update - pobieranie aktualizacji zakończone niepowodzeniem - - - CHECK - SPRAWDŹ - - - Updates are only downloaded while the car is off. - - - - Current Version - - - - Download - - - - Install Update - - - - INSTALL - - - - Target Branch - - - - SELECT - - - - Select a branch - - - - - SshControl - - SSH Keys - Klucze SSH - - - Warning: This grants SSH access to all public keys in your GitHub settings. Never enter a GitHub username other than your own. A comma employee will NEVER ask you to add their GitHub username. - Ostrzeżenie: To spowoduje przekazanie dostępu do wszystkich Twoich publicznych kuczy z ustawień GitHuba. Nigdy nie wprowadzaj nazwy użytkownika innej niż swoja. Pracownik comma NIGDY nie poprosi o dodanie swojej nazwy uzytkownika. - - - ADD - DODAJ - - - Enter your GitHub username - Wpisz swoją nazwę użytkownika GitHub - - - LOADING - ŁADOWANIE - - - REMOVE - USUŃ - - - Username '%1' has no keys on GitHub - Użytkownik '%1' nie posiada żadnych kluczy na GitHubie - - - Request timed out - Limit czasu rządania - - - Username '%1' doesn't exist on GitHub - Użytkownik '%1' nie istnieje na GitHubie - - - - SshToggle - - Enable SSH - Włącz SSH - - - - TermsPage - - Terms & Conditions - Regulamin - - - Decline - Odrzuć - - - Scroll to accept - Przewiń w dół, aby zaakceptować - - - Agree - Zaakceptuj - - - - TogglesPanel - - Enable openpilot - Włącz openpilota - - - Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. - Użyj openpilota do zachowania bezpiecznego odstępu między pojazdami i do asystowania w utrzymywaniu pasa ruchu. Twoja pełna uwaga jest wymagana przez cały czas korzystania z tej funkcji. Ustawienie to może być wdrożone wyłącznie wtedy, gdy pojazd jest wyłączony. - - - Enable Lane Departure Warnings - Włącz ostrzeganie przed zmianą pasa ruchu - - - 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). - Otrzymuj alerty o powrocie na właściwy pas, kiedy Twój pojazd przekroczy linię bez włączonego kierunkowskazu jadąc powyżej 50 km/h (31 mph). - - - Use Metric System - Korzystaj z systemu metrycznego - - - Display speed in km/h instead of mph. - Wyświetl prędkość w km/h zamiast mph. - - - Record and Upload Driver Camera - Nagraj i prześlij nagranie z kamery kierowcy - - - Upload data from the driver facing camera and help improve the driver monitoring algorithm. - Prześlij dane z kamery skierowanej na kierowcę i pomóż poprawiać algorytm monitorowania kierowcy. - - - Disengage on Accelerator Pedal - Odłącz poprzez naciśnięcie gazu - - - When enabled, pressing the accelerator pedal will disengage openpilot. - Po włączeniu, naciśnięcie na pedał gazu odłączy openpilota. - - - Show ETA in 24h Format - Pokaż oczekiwany czas dojazdu w formacie 24-godzinnym - - - Use 24h format instead of am/pm - Korzystaj z formatu 24-godzinnego zamiast 12-godzinnego - - - Show Map on Left Side of UI - Pokaż mapę po lewej stronie ekranu - - - Show map on left side when in split screen view. - Pokaż mapę po lewej stronie kiedy ekran jest podzielony. - - - openpilot Longitudinal Control - Kontrola wzdłużna openpilota - - - openpilot will disable the car's radar and will take over control of gas and brakes. Warning: this disables AEB! - openpilot wyłączy radar samochodu i przejmie kontrolę nad gazem i hamulcem. Ostrzeżenie: wyłączony zostanie system AEB! - - - 🌮 End-to-end longitudinal (extremely alpha) 🌮 - - - - Experimental openpilot Longitudinal Control - - - - <b>WARNING: openpilot longitudinal control is experimental for this car and will disable AEB.</b> - - - - Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would. Super experimental. - - - - openpilot longitudinal control is not currently available for this car. - - - - Enable experimental longitudinal control to enable this. - - - - - Updater - - Update Required - Wymagana Aktualizacja - - - An operating system update is required. Connect your device to Wi-Fi for the fastest update experience. The download size is approximately 1GB. - Wymagana aktualizacja systemu operacyjnego. Aby przyspieszyć proces aktualizacji połącz swoje urzeądzenie do Wi-Fi. Rozmiar pobieranej paczki wynosi około 1GB. - - - Connect to Wi-Fi - Połącz się z Wi-Fi - - - Install - Zainstaluj - - - Back - Wróć - - - Loading... - Ładowanie... - - - Reboot - Uruchom ponownie - - - Update failed - Aktualizacja nie powiodła się - - - - WifiUI - - Scanning for networks... - Wyszukiwanie sieci... - - - CONNECTING... - ŁĄCZENIE... - - - FORGET - ZAPOMNIJ - - - Forget Wi-Fi Network "%1"? - Czy chcesz zapomnieć sieć "%1"? - - - diff --git a/selfdrive/ui/translations/pt-BR.ts b/selfdrive/ui/translations/pt-BR.ts deleted file mode 100644 index 77aa5e07c..000000000 --- a/selfdrive/ui/translations/pt-BR.ts +++ /dev/null @@ -1,1074 +0,0 @@ - - - - - AbstractAlert - - Close - Fechar - - - Reboot and Update - Reiniciar e Atualizar - - - - AdvancedNetworking - - Back - Voltar - - - Enable Tethering - Ativar Tether - - - Tethering Password - Senha Tethering - - - EDIT - EDITAR - - - Enter new tethering password - Insira nova senha tethering - - - IP Address - Endereço IP - - - Enable Roaming - Ativar Roaming - - - APN Setting - APN Config - - - Enter APN - Insira APN - - - leave blank for automatic configuration - deixe em branco para configuração automática - - - Cellular Metered - Plano de Dados Limitado - - - Hidden Network - Rede Oculta - - - CONNECT - CONECTE - - - Enter SSID - Digite o SSID - - - Enter password - Insira a senha - - - for "%1" - para "%1" - - - Prevent large data uploads when on a metered cellular connection - Previna o envio de grandes volumes de dados em conexões de celular com franquia de limite de dados - - - default - padrão - - - metered - limitada - - - unmetered - ilimitada - - - Wi-Fi Network Metered - Rede Wi-Fi com Franquia - - - Prevent large data uploads when on a metered Wi-Fi connection - Previna o envio de grandes volumes de dados em conexões Wi-Fi com franquia de limite de dados - - - - ConfirmationDialog - - Ok - OK - - - Cancel - Cancelar - - - - DeclinePage - - You must accept the Terms and Conditions in order to use openpilot. - Você precisa aceitar os Termos e Condições para utilizar openpilot. - - - Back - Voltar - - - Decline, uninstall %1 - Rejeitar, desintalar %1 - - - - DeveloperPanel - - Joystick Debug Mode - Modo Joystick Debug - - - Longitudinal Maneuver Mode - Modo Longitudinal Maneuver - - - openpilot Longitudinal Control (Alpha) - Controle Longitudinal openpilot (Embrionário) - - - WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB). - AVISO: o controle longitudinal openpilot está em estado embrionário para este carro e desativará a Frenagem Automática de Emergência (AEB). - - - On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha. - Neste carro, o openpilot tem como padrão o ACC embutido do carro em vez do controle longitudinal do openpilot. Habilite isso para alternar para o controle longitudinal openpilot. Recomenda-se ativar o modo Experimental ao ativar o embrionário controle longitudinal openpilot. - - - Enable ADB - Habilitar ADB - - - ADB (Android Debug Bridge) allows connecting to your device over USB or over the network. See https://docs.comma.ai/how-to/connect-to-comma for more info. - ADB (Android Debug Bridge) permite conectar ao seu dispositivo por meio do USB ou através da rede. Veja https://docs.comma.ai/how-to/connect-to-comma para maiores informações. - - - - DevicePanel - - Dongle ID - Dongle ID - - - N/A - N/A - - - Serial - Serial - - - Driver Camera - Câmera do Motorista - - - PREVIEW - VER - - - Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off) - Pré-visualizar a câmera voltada para o motorista para garantir que o monitoramento do sistema tenha uma boa visibilidade (veículo precisa estar desligado) - - - Reset Calibration - Reinicializar Calibragem - - - RESET - RESET - - - Are you sure you want to reset calibration? - Tem certeza que quer resetar a calibragem? - - - Review Training Guide - Revisar Guia de Treinamento - - - REVIEW - REVISAR - - - Review the rules, features, and limitations of openpilot - Revisar regras, aprimoramentos e limitações do openpilot - - - Are you sure you want to review the training guide? - Tem certeza que quer rever o treinamento? - - - Regulatory - Regulatório - - - VIEW - VER - - - Change Language - Alterar Idioma - - - CHANGE - ALTERAR - - - Select a language - Selecione o Idioma - - - Reboot - Reiniciar - - - Power Off - Desligar - - - Your device is pointed %1° %2 and %3° %4. - Seu dispositivo está montado %1° %2 e %3° %4. - - - down - baixo - - - up - cima - - - left - esquerda - - - right - direita - - - Are you sure you want to reboot? - Tem certeza que quer reiniciar? - - - Disengage to Reboot - Desacione para Reiniciar - - - Are you sure you want to power off? - Tem certeza que quer desligar? - - - Disengage to Power Off - Desacione para Desligar - - - Reset - Resetar - - - Review - Revisar - - - Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer. - Pareie seu dispositivo com comma connect (connect.comma.ai) e reivindique sua oferta de comma prime. - - - Pair Device - Parear Dispositivo - - - PAIR - PAREAR - - - Disengage to Reset Calibration - Desacione para Resetar a Calibração - - - openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. - O openpilot exige que o dispositivo seja montado dentro de 4° para a esquerda ou direita e dentro de 5° para cima ou 9° para baixo. - - - openpilot is continuously calibrating, resetting is rarely required. Resetting calibration will restart openpilot if the car is powered on. - O openpilot está em constante calibração, raramente sendo necessário redefini-lo. Redefinir a calibração reiniciará o openpilot se o carro estiver ligado. - - - - -Steering lag calibration is %1% complete. - - -A calibração do atraso da direção está %1% concluída. - - - - -Steering lag calibration is complete. - - -A calibração do atraso da direção foi concluída. - - - Steering torque response calibration is %1% complete. - A calibração da resposta de torque da direção está %1% concluída. - - - Steering torque response calibration is complete. - A calibração da resposta do torque da direção foi concluída. - - - - DriverViewWindow - - camera starting - câmera iniciando - - - - ExperimentalModeButton - - EXPERIMENTAL MODE ON - MODO EXPERIMENTAL ON - - - CHILL MODE ON - MODO CHILL ON - - - - FirehosePanel - - openpilot learns to drive by watching humans, like you, drive. - -Firehose Mode allows you to maximize your training data uploads to improve openpilot's driving models. More data means bigger models, which means better Experimental Mode. - O openpilot aprende a dirigir observando humanos, como você, dirigirem. - -O Modo Firehose permite maximizar o envio de dados de treinamento para melhorar os modelos de direção do openpilot. Mais dados significam modelos maiores, o que resulta em um Modo Experimental melhor. - - - Firehose Mode: ACTIVE - Modo Firehose: ATIVO - - - ACTIVE - ATIVO - - - For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.<br><br>Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.<br><br><br><b>Frequently Asked Questions</b><br><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><br><i>Do all of my segments get pulled in Firehose Mode?</i> No, we selectively pull a subset of your segments.<br><br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><br><i>Does it matter which software I run?</i> Yes, only upstream openpilot (and particular forks) are able to be used for training. - Para maior eficácia, leve seu dispositivo para dentro de casa e conecte-o a um bom adaptador USB-C e Wi-Fi semanalmente.<br><br>O Modo Firehose também pode funcionar enquanto você dirige, se estiver conectado a um hotspot ou a um chip SIM com dados ilimitados.<br><br><br><b>Perguntas Frequentes</b><br><br><i>Importa como ou onde eu dirijo?</i> Não, basta dirigir normalmente.<br><br><i>Todos os meus segmentos são enviados no Modo Firehose?</i> Não, selecionamos apenas um subconjunto dos seus segmentos.<br><br><i>Qual é um bom adaptador USB-C?</i> Qualquer carregador rápido de telefone ou laptop deve ser suficiente.<br><br><i>Importa qual software eu uso?</i> Sim, apenas o openpilot oficial (e alguns forks específicos) podem ser usados para treinamento. - - - <b>%n segment(s)</b> of your driving is in the training dataset so far. - - <b>%n segmento</b> da sua direção está no conjunto de dados de treinamento até agora. - <b>%n segmentos</b> da sua direção estão no conjunto de dados de treinamento até agora. - - - - <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to an unmetered network - <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INATIVO</span>: conecte-se a uma rede sem limite <br> de dados - - - Firehose Mode - Modo Firehose - - - - HudRenderer - - km/h - km/h - - - mph - mph - - - MAX - LIMITE - - - - InputDialog - - Cancel - Cancelar - - - Need at least %n character(s)! - - Necessita no mínimo %n caractere! - Necessita no mínimo %n caracteres! - - - - - MultiOptionDialog - - Select - Selecione - - - Cancel - Cancelar - - - - Networking - - Advanced - Avançado - - - Enter password - Insira a senha - - - for "%1" - para "%1" - - - Wrong password - Senha incorreta - - - - OffroadAlert - - Immediately connect to the internet to check for updates. If you do not connect to the internet, openpilot won't engage in %1 - Conecte-se imediatamente à internet para verificar se há atualizações. Se você não se conectar à internet em %1 não será possível acionar o openpilot. - - - Connect to internet to check for updates. openpilot won't automatically start until it connects to internet to check for updates. - Conecte-se à internet para verificar se há atualizações. O openpilot não será iniciado automaticamente até que ele se conecte à internet para verificar se há atualizações. - - - Unable to download updates -%1 - Não é possível baixar atualizações -%1 - - - Taking camera snapshots. System won't start until finished. - Tirando fotos da câmera. O sistema não será iniciado até terminar. - - - An update to your device's operating system is downloading in the background. You will be prompted to update when it's ready to install. - Uma atualização para o sistema operacional do seu dispositivo está sendo baixada em segundo plano. Você será solicitado a atualizar quando estiver pronto para instalar. - - - openpilot was unable to identify your car. Your car is either unsupported or its ECUs are not recognized. Please submit a pull request to add the firmware versions to the proper vehicle. Need help? Join discord.comma.ai. - O openpilot não conseguiu identificar o seu carro. Seu carro não é suportado ou seus ECUs não são reconhecidos. Envie um pull request para adicionar as versões de firmware ao veículo adequado. Precisa de ajuda? Junte-se discord.comma.ai. - - - openpilot detected a change in the device's mounting position. Ensure the device is fully seated in the mount and the mount is firmly secured to the windshield. - O openpilot detectou uma mudança na posição de montagem do dispositivo. Verifique se o dispositivo está totalmente encaixado no suporte e se o suporte está firmemente preso ao para-brisa. - - - Device temperature too high. System cooling down before starting. Current internal component temperature: %1 - Temperatura do dispositivo muito alta. O sistema está sendo resfriado antes de iniciar. A temperatura atual do componente interno é: %1 - - - Device failed to register with the comma.ai backend. It will not connect or upload to comma.ai servers, and receives no support from comma.ai. If this is a device purchased at comma.ai/shop, open a ticket at https://comma.ai/support. - O dispositivo não conseguiu se registrar no backend da comma.ai. Ele não se conecta nem faz upload para os servidores da comma.ai e não recebe suporte da comma.ai. Se este for um dispositivo adquirido em comma.ai/shop, abra um ticket em https://comma.ai/support. - - - Acknowledge Excessive Actuation - Reconhecer Atuação Excessiva - - - Snooze Update - Adiar Atualização - - - openpilot detected excessive %1 actuation on your last drive. Please contact support at https://comma.ai/support and share your device's Dongle ID for troubleshooting. - openpilot detectou atuação excessiva de %1 na sua última condução. Entre em contato com o suporte em https://comma.ai/support e compartilhe o Dongle ID do seu dispositivo para solução de problemas. - - - - OffroadHome - - UPDATE - ATUALIZAÇÃO - - - ALERTS - ALERTAS - - - ALERT - ALERTA - - - - OnroadAlerts - - openpilot Unavailable - openpilot Indisponível - - - TAKE CONTROL IMMEDIATELY - ASSUMA IMEDIATAMENTE - - - Reboot Device - Reinicie o Dispositivo - - - Waiting to start - Aguardando para iniciar - - - System Unresponsive - Sistema sem Resposta - - - - PairingPopup - - Pair your device to your comma account - Pareie seu dispositivo à sua conta comma - - - Go to https://connect.comma.ai on your phone - navegue até https://connect.comma.ai no seu telefone - - - Click "add new device" and scan the QR code on the right - Clique "add new device" e escaneie o QR code a seguir - - - Bookmark connect.comma.ai to your home screen to use it like an app - Salve connect.comma.ai como sua página inicial para utilizar como um app - - - Please connect to Wi-Fi to complete initial pairing - Por favor conecte ao Wi-Fi para completar o pareamento inicial - - - - ParamControl - - Cancel - Cancelar - - - Enable - Ativar - - - - PrimeAdWidget - - Upgrade Now - Atualizar Agora - - - Become a comma prime member at connect.comma.ai - Seja um membro comma prime em connect.comma.ai - - - PRIME FEATURES: - BENEFÍCIOS PRIME: - - - Remote access - Acesso remoto (proxy comma) - - - 24/7 LTE connectivity - Conectividade LTE (só nos EUA) - - - 1 year of drive storage - 1 ano de dados em nuvem - - - Remote snapshots - Captura remota - - - - PrimeUserWidget - - ✓ SUBSCRIBED - ✓ INSCRITO - - - comma prime - comma prime - - - - QObject - - openpilot - openpilot - - - %n minute(s) ago - - há %n minuto - há %n minutos - - - - %n hour(s) ago - - há %n hora - há %n horas - - - - %n day(s) ago - - há %n dia - há %n dias - - - - now - agora - - - - SettingsWindow - - × - × - - - Device - Dispositivo - - - Network - Rede - - - Toggles - Ajustes - - - Software - Software - - - Developer - Desenvdor - - - Firehose - Firehose - - - - SetupWidget - - Finish Setup - Concluir - - - Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer. - Pareie seu dispositivo com comma connect (connect.comma.ai) e reivindique sua oferta de comma prime. - - - Pair device - Parear dispositivo - - - - Sidebar - - CONNECT - CONEXÃO - - - OFFLINE - OFFLINE - - - ONLINE - ONLINE - - - ERROR - ERRO - - - TEMP - TEMP - - - HIGH - ALTA - - - GOOD - BOA - - - OK - OK - - - VEHICLE - VEÍCULO - - - NO - SEM - - - PANDA - PANDA - - - -- - -- - - - Wi-Fi - Wi-Fi - - - ETH - ETH - - - 2G - 2G - - - 3G - 3G - - - LTE - LTE - - - 5G - 5G - - - - SoftwarePanel - - Updates are only downloaded while the car is off. - Atualizações baixadas durante o motor desligado. - - - Current Version - Versão Atual - - - Download - Download - - - Install Update - Instalar Atualização - - - INSTALL - INSTALAR - - - Target Branch - Alterar Branch - - - SELECT - SELECIONE - - - Select a branch - Selecione uma branch - - - UNINSTALL - REMOVER - - - Uninstall %1 - Desinstalar o %1 - - - Are you sure you want to uninstall? - Tem certeza que quer desinstalar? - - - CHECK - VERIFICAR - - - Uninstall - Desinstalar - - - failed to check for update - falha ao verificar por atualizações - - - up to date, last checked %1 - atualizado, última verificação %1 - - - DOWNLOAD - BAIXAR - - - update available - atualização disponível - - - never - nunca - - - - SshControl - - SSH Keys - Chave SSH - - - Warning: This grants SSH access to all public keys in your GitHub settings. Never enter a GitHub username other than your own. A comma employee will NEVER ask you to add their GitHub username. - Aviso: isso concede acesso SSH a todas as chaves públicas nas configurações do GitHub. Nunca insira um nome de usuário do GitHub que não seja o seu. Um funcionário da comma NUNCA pedirá que você adicione seu nome de usuário do GitHub. - - - ADD - ADICIONAR - - - Enter your GitHub username - Insira seu nome de usuário do GitHub - - - LOADING - CARREGANDO - - - REMOVE - REMOVER - - - Username '%1' has no keys on GitHub - Usuário "%1” não possui chaves no GitHub - - - Request timed out - A solicitação expirou - - - Username '%1' doesn't exist on GitHub - Usuário '%1' não existe no GitHub - - - - SshToggle - - Enable SSH - Habilitar SSH - - - - TermsPage - - Decline - Declinar - - - Agree - Concordo - - - Welcome to openpilot - Bem vindo ao openpilot - - - You must accept the Terms and Conditions to use openpilot. Read the latest terms at <span style='color: #465BEA;'>https://comma.ai/terms</span> before continuing. - Você deve aceitar os Termos e Condições para usar o openpilot. Leia os termos mais recentes em <span style='color: #465BEA;'>https://comma.ai/terms</span> antes de continuar. - - - - TogglesPanel - - Enable openpilot - Ativar openpilot - - - Enable Lane Departure Warnings - Ativar Avisos de Saída de Faixa - - - 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). - Receba alertas para voltar para a pista se o seu veículo sair da faixa e a seta não tiver sido acionada previamente quando em velocidades superiores a 50 km/h. - - - Use Metric System - Usar Sistema Métrico - - - Display speed in km/h instead of mph. - Exibir velocidade em km/h invés de mph. - - - Record and Upload Driver Camera - Gravar e Upload Câmera Motorista - - - Upload data from the driver facing camera and help improve the driver monitoring algorithm. - Upload dados da câmera voltada para o motorista e ajude a melhorar o algoritmo de monitoramentor. - - - Disengage on Accelerator Pedal - Desacionar com Pedal do Acelerador - - - When enabled, pressing the accelerator pedal will disengage openpilot. - Quando ativado, pressionar o pedal do acelerador desacionará o openpilot. - - - Experimental Mode - Modo Experimental - - - openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: - openpilot por padrão funciona em <b>modo chill</b>. modo Experimental ativa <b>recursos de nível-embrionário</b> que não estão prontos para o modo chill. Recursos experimentais estão listados abaixo: - - - Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. - Deixe o modelo de IA controlar o acelerador e os freios. O openpilot irá dirigir como pensa que um humano faria, incluindo parar em sinais vermelhos e sinais de parada. Uma vez que o modelo de condução decide a velocidade a conduzir, a velocidade definida apenas funcionará como um limite superior. Este é um recurso de qualidade embrionária; erros devem ser esperados. - - - New Driving Visualization - Nova Visualização de Condução - - - Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. - O modo Experimental está atualmente indisponível para este carro já que o ACC original do carro é usado para controle longitudinal. - - - openpilot longitudinal control may come in a future update. - O controle longitudinal openpilot poderá vir em uma atualização futura. - - - Aggressive - Disputa - - - Standard - Neutro - - - Relaxed - Calmo - - - Driving Personality - Temperamento de Direção - - - An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. - Uma versão embrionária do controle longitudinal openpilot pode ser testada em conjunto com o modo Experimental, em branches que não sejam de produção. - - - Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. - Habilite o controle longitudinal (embrionário) openpilot para permitir o modo Experimental. - - - End-to-End Longitudinal Control - Controle Longitudinal de Ponta a Ponta - - - Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. - Neutro é o recomendado. No modo disputa o openpilot seguirá o carro da frente mais de perto e será mais agressivo com a aceleração e frenagem. No modo calmo o openpilot se manterá mais longe do carro da frente. Em carros compatíveis, você pode alternar esses temperamentos com o botão de distância do volante. - - - The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. - A visualização de condução fará a transição para a câmera grande angular voltada para a estrada em baixas velocidades para mostrar melhor algumas curvas. O logotipo do modo Experimental também será mostrado no canto superior direito. - - - Always-On Driver Monitoring - Monitoramento do Motorista Sempre Ativo - - - Enable driver monitoring even when openpilot is not engaged. - Habilite o monitoramento do motorista mesmo quando o openpilot não estiver acionado. - - - Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. - Use o sistema openpilot para controle de cruzeiro adaptativo e assistência ao motorista de manutenção de faixa. Sua atenção é necessária o tempo todo para usar esse recurso. - - - Changing this setting will restart openpilot if the car is powered on. - Alterar esta configuração fará com que o openpilot reinicie se o carro estiver ligado. - - - Record and Upload Microphone Audio - Gravar e Fazer Upload do Áudio do Microfone - - - Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect. - Grave e armazene o áudio do microfone enquanto estiver dirigindo. O áudio será incluído ao vídeo dashcam no comma connect. - - - - WiFiPromptWidget - - Open - Abrir - - - Maximize your training data uploads to improve openpilot's driving models. - Maximize seus envios de dados de treinamento para melhorar os modelos de direção do openpilot. - - - <span style='font-family: "Noto Color Emoji";'>🔥</span> Firehose Mode <span style='font-family: Noto Color Emoji;'>🔥</span> - <span style='font-family: "Noto Color Emoji";'>🔥</span> Modo Firehose <span style='font-family: Noto Color Emoji;'>🔥</span> - - - - WifiUI - - Scanning for networks... - Procurando redes... - - - CONNECTING... - CONECTANDO... - - - FORGET - ESQUECER - - - Forget Wi-Fi Network "%1"? - Esquecer Rede Wi-Fi "%1"? - - - Forget - Esquecer - - - diff --git a/selfdrive/ui/translations/th.ts b/selfdrive/ui/translations/th.ts deleted file mode 100644 index 9bd682208..000000000 --- a/selfdrive/ui/translations/th.ts +++ /dev/null @@ -1,1065 +0,0 @@ - - - - - AbstractAlert - - Close - ปิด - - - Reboot and Update - รีบูตและอัปเดต - - - - AdvancedNetworking - - Back - ย้อนกลับ - - - Enable Tethering - ปล่อยฮอตสปอต - - - Tethering Password - รหัสผ่านฮอตสปอต - - - EDIT - แก้ไข - - - Enter new tethering password - ป้อนรหัสผ่านฮอตสปอตใหม่ - - - IP Address - หมายเลขไอพี - - - Enable Roaming - เปิดใช้งานโรมมิ่ง - - - APN Setting - ตั้งค่า APN - - - Enter APN - ป้อนค่า APN - - - leave blank for automatic configuration - เว้นว่างเพื่อตั้งค่าอัตโนมัติ - - - Cellular Metered - ลดการส่งข้อมูลผ่านเซลลูล่าร์ - - - Hidden Network - เครือข่ายที่ซ่อนอยู่ - - - CONNECT - เชื่อมต่อ - - - Enter SSID - ป้อนค่า SSID - - - Enter password - ใส่รหัสผ่าน - - - for "%1" - สำหรับ "%1" - - - Prevent large data uploads when on a metered cellular connection - - - - default - - - - metered - - - - unmetered - - - - Wi-Fi Network Metered - - - - Prevent large data uploads when on a metered Wi-Fi connection - - - - - ConfirmationDialog - - Ok - ตกลง - - - Cancel - ยกเลิก - - - - DeclinePage - - You must accept the Terms and Conditions in order to use openpilot. - คุณต้องยอมรับเงื่อนไขและข้อตกลง เพื่อใช้งาน openpilot - - - Back - ย้อนกลับ - - - Decline, uninstall %1 - ปฏิเสธ และถอนการติดตั้ง %1 - - - - DeveloperPanel - - Joystick Debug Mode - โหมดดีบักจอยสติ๊ก - - - Longitudinal Maneuver Mode - โหมดการควบคุมการเร่ง/เบรค - - - openpilot Longitudinal Control (Alpha) - ระบบควบคุมการเร่ง/เบรคโดย openpilot (Alpha) - - - WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB). - คำเตือน: การควบคุมการเร่ง/เบรคโดย openpilot สำหรับรถคันนี้ยังอยู่ในสถานะ alpha และระบบเบรคฉุกเฉินอัตโนมัติ (AEB) จะถูกปิด - - - On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha. - โดยปกติสำหรับรถคันนี้ openpilot จะควบคุมการเร่ง/เบรคด้วยระบบ ACC จากโรงงาน แทนการควยคุมโดย openpilot เปิดสวิตซ์นี้เพื่อให้ openpilot ควบคุมการเร่ง/เบรค แนะนำให้เปิดโหมดทดลองเมื่อต้องการให้ openpilot ควบคุมการเร่ง/เบรค ซึ่งอยู่ในสถานะ alpha - - - Enable ADB - เปิด ADB - - - ADB (Android Debug Bridge) allows connecting to your device over USB or over the network. See https://docs.comma.ai/how-to/connect-to-comma for more info. - ADB (Android Debug Bridge) อนุญาตให้เชื่อมต่ออุปกรณ์ของคุณผ่าน USB หรือผ่านเครือข่าย ดูข้อมูลเพิ่มเติมที่ https://docs.comma.ai/how-to/connect-to-comma - - - - DevicePanel - - Dongle ID - Dongle ID - - - N/A - ไม่มี - - - Serial - ซีเรียล - - - Driver Camera - กล้องฝั่งคนขับ - - - PREVIEW - แสดงภาพ - - - Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off) - ดูภาพตัวอย่างกล้องที่หันเข้าหาคนขับเพื่อให้แน่ใจว่าการตรวจสอบคนขับมีทัศนวิสัยที่ดี (รถต้องดับเครื่องยนต์) - - - Reset Calibration - รีเซ็ตการคาลิเบรท - - - RESET - รีเซ็ต - - - Are you sure you want to reset calibration? - คุณแน่ใจหรือไม่ว่าต้องการรีเซ็ตการคาลิเบรท? - - - Review Training Guide - ทบทวนคู่มือการใช้งาน - - - REVIEW - ทบทวน - - - Review the rules, features, and limitations of openpilot - ตรวจสอบกฎ คุณสมบัติ และข้อจำกัดของ openpilot - - - Are you sure you want to review the training guide? - คุณแน่ใจหรือไม่ว่าต้องการทบทวนคู่มือการใช้งาน? - - - Regulatory - ระเบียบข้อบังคับ - - - VIEW - ดู - - - Change Language - เปลี่ยนภาษา - - - CHANGE - เปลี่ยน - - - Select a language - เลือกภาษา - - - Reboot - รีบูต - - - Power Off - ปิดเครื่อง - - - Your device is pointed %1° %2 and %3° %4. - อุปกรณ์ของคุณเอียงไปทาง %2 %1° และ %4 %3° - - - down - ด้านล่าง - - - up - ด้านบน - - - left - ด้านซ้าย - - - right - ด้านขวา - - - Are you sure you want to reboot? - คุณแน่ใจหรือไม่ว่าต้องการรีบูต? - - - Disengage to Reboot - ยกเลิกระบบช่วยขับเพื่อรีบูต - - - Are you sure you want to power off? - คุณแน่ใจหรือไม่ว่าต้องการปิดเครื่อง? - - - Disengage to Power Off - ยกเลิกระบบช่วยขับเพื่อปิดเครื่อง - - - Reset - รีเซ็ต - - - Review - ทบทวน - - - Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer. - จับคู่อุปกรณ์ของคุณกับ comma connect (connect.comma.ai) และรับข้อเสนอ comma prime ของคุณ - - - Pair Device - จับคู่อุปกรณ์ - - - PAIR - จับคู่ - - - Disengage to Reset Calibration - - - - openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. - - - - openpilot is continuously calibrating, resetting is rarely required. Resetting calibration will restart openpilot if the car is powered on. - - - - - -Steering lag calibration is %1% complete. - - - - - -Steering lag calibration is complete. - - - - Steering torque response calibration is %1% complete. - - - - Steering torque response calibration is complete. - - - - - DriverViewWindow - - camera starting - กำลังเปิดกล้อง - - - - ExperimentalModeButton - - EXPERIMENTAL MODE ON - คุณกำลังใช้โหมดทดลอง - - - CHILL MODE ON - คุณกำลังใช้โหมดชิล - - - - FirehosePanel - - openpilot learns to drive by watching humans, like you, drive. - -Firehose Mode allows you to maximize your training data uploads to improve openpilot's driving models. More data means bigger models, which means better Experimental Mode. - openpilot เรียนรู้วิธีขับรถจากการเฝ้าดูการขับขี่ของมนุษย์เช่นคุณ - -โหมดสายยางดับเพลิงช่วยให้คุณอัปโหลดข้อมูลการฝึกฝนได้มากที่สุด เพื่อนำไปพัฒนาโมเดลการขับขี่ของ openpilot ข้อมูลที่มากขึ้นหมายถึงโมเดลที่ใหญ่ขึ้น และนั่นหมายถึงโหมดทดลองที่ดีขึ้น - - - Firehose Mode: ACTIVE - โหมดสายยางดับเพลิง: เปิดใช้งาน - - - ACTIVE - เปิดใช้งาน - - - For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.<br><br>Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.<br><br><br><b>Frequently Asked Questions</b><br><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><br><i>Do all of my segments get pulled in Firehose Mode?</i> No, we selectively pull a subset of your segments.<br><br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><br><i>Does it matter which software I run?</i> Yes, only upstream openpilot (and particular forks) are able to be used for training. - เพื่อประสิทธิภาพสูงสุด ควรนำอุปกรณ์เข้ามาข้างใน เชื่อมต่อกับอะแดปเตอร์ USB-C คุณภาพดี และ Wi-Fi สัปดาห์ละครั้ง<br><br>โหมดสายยางดับเพลิงยังสามารถทำงานระหว่างขับรถได้ หากเชื่อมต่อกับฮอตสปอตหรือซิมการ์ดที่มีเน็ตไม่จำกัด<br><br><br><b>คำถามที่พบบ่อย</b><br><br><i>วิธีการขับหรือสถานที่ขับขี่มีผลหรือไม่?</i>ไม่มีผล แค่ขับขี่ตามปกติของคุณ<br><br><i>เซกเมนต์ทั้งหมดของฉันจะถูกดึงข้อมูลในโหมดสายยางดับเพลิงหรือไม่?</i>ไม่ใช่ เราจะเลือกดึงข้อมูลเพียงบางส่วนจากเซกเมนต์ของคุณ<br><br><i>อะแดปเตอร์ USB-C แบบไหนดี?</i>ที่ชาร์จเร็วของโทรศัพท์หรือแล็ปท็อปแบบใดก็ได้ สามารถใช้ได้<br><br><i>ซอฟต์แวร์ที่ใช้มีผลหรือไม่?</i>มีผล เฉพาะ openpilot ตัวหลัก (และ fork เฉพาะบางตัว) เท่านั้น ที่สามารถนำข้อมูลไปใช้ฝึกฝนโมเดลได้ - - - <b>%n segment(s)</b> of your driving is in the training dataset so far. - - มีการขับขี่ของคุณ <b>%n เซกเมนต์</b> อยู่ในชุดข้อมูลการฝึกฝนแล้วในขณะนี้ - - - - <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to an unmetered network - <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>ไม่เปิดใช้งาน</span>: เชื่อมต่อกับเครือข่ายที่ไม่จำกัดข้อมูล - - - Firehose Mode - - - - - HudRenderer - - km/h - กม./ชม. - - - mph - ไมล์/ชม. - - - MAX - สูงสุด - - - - InputDialog - - Cancel - ยกเลิก - - - Need at least %n character(s)! - - ต้องการอย่างน้อย %n ตัวอักษร! - - - - - MultiOptionDialog - - Select - เลือก - - - Cancel - ยกเลิก - - - - Networking - - Advanced - ขั้นสูง - - - Enter password - ใส่รหัสผ่าน - - - for "%1" - สำหรับ "%1" - - - Wrong password - รหัสผ่านผิด - - - - OffroadAlert - - Device temperature too high. System cooling down before starting. Current internal component temperature: %1 - อุณหภูมิของอุปกรณ์สูงเกินไป ระบบกำลังทำความเย็นก่อนเริ่ม อุณหภูมิของชิ้นส่วนภายในปัจจุบัน: %1 - - - Immediately connect to the internet to check for updates. If you do not connect to the internet, openpilot won't engage in %1 - กรุณาเชื่อมต่ออินเตอร์เน็ตเพื่อตรวจสอบอัปเดทเดี๋ยวนี้ ถ้าคุณไม่เชื่อมต่ออินเตอร์เน็ต openpilot จะไม่ทำงานในอีก %1 - - - Connect to internet to check for updates. openpilot won't automatically start until it connects to internet to check for updates. - กรุณาเชื่อมต่ออินเตอร์เน็ตเพื่อตรวจสอบอัปเดท openpilot จะไม่เริ่มทำงานอัตโนมัติจนกว่าจะได้เชื่อมต่อกับอินเตอร์เน็ตเพื่อตรวจสอบอัปเดท - - - Unable to download updates -%1 - ไม่สามารถดาวน์โหลดอัพเดทได้ -%1 - - - Taking camera snapshots. System won't start until finished. - กล้องกำลังถ่ายภาพ ระบบจะไม่เริ่มทำงานจนกว่าจะเสร็จ - - - An update to your device's operating system is downloading in the background. You will be prompted to update when it's ready to install. - กำลังดาวน์โหลดอัปเดทสำหรับระบบปฏิบัติการอยู่เบื้องหลัง คุณจะได้รับการแจ้งเตือนเมื่อระบบพร้อมสำหรับการติดตั้ง - - - openpilot was unable to identify your car. Your car is either unsupported or its ECUs are not recognized. Please submit a pull request to add the firmware versions to the proper vehicle. Need help? Join discord.comma.ai. - openpilot ไม่สามารถระบุรถยนต์ของคุณได้ ระบบอาจไม่รองรับรถยนต์ของคุณหรือไม่รู้จัก ECU กรุณาส่ง pull request เพื่อเพิ่มรุ่นของเฟิร์มแวร์ให้กับรถยนต์ที่เหมาะสม หากต้องการความช่วยเหลือให้เข้าร่วม discord.comma.ai - - - openpilot detected a change in the device's mounting position. Ensure the device is fully seated in the mount and the mount is firmly secured to the windshield. - openpilot ตรวจพบการเปลี่ยนแปลงของตำแหน่งที่ติดตั้ง กรุณาตรวจสอบว่าได้เลื่อนอุปกรณ์เข้ากับจุดติดตั้งจนสุดแล้ว และจุดติดตั้งได้ยึดติดกับกระจกหน้าอย่างแน่นหนา - - - Device failed to register with the comma.ai backend. It will not connect or upload to comma.ai servers, and receives no support from comma.ai. If this is a device purchased at comma.ai/shop, open a ticket at https://comma.ai/support. - - - - Acknowledge Excessive Actuation - - - - Snooze Update - เลื่อนการอัปเดต - - - openpilot detected excessive %1 actuation on your last drive. Please contact support at https://comma.ai/support and share your device's Dongle ID for troubleshooting. - - - - - OffroadHome - - UPDATE - อัปเดต - - - ALERTS - การแจ้งเตือน - - - ALERT - การแจ้งเตือน - - - - OnroadAlerts - - openpilot Unavailable - openpilot ไม่สามารถใช้งานได้ - - - TAKE CONTROL IMMEDIATELY - เข้าควบคุมรถเดี๋ยวนี้ - - - Reboot Device - รีบูตอุปกรณ์ - - - Waiting to start - รอเริ่มทำงาน - - - System Unresponsive - ระบบไม่ตอบสนอง - - - - PairingPopup - - Pair your device to your comma account - จับคู่อุปกรณ์ของคุณกับบัญชี comma ของคุณ - - - Go to https://connect.comma.ai on your phone - ไปที่ https://connect.comma.ai ด้วยโทรศัพท์ของคุณ - - - Click "add new device" and scan the QR code on the right - กดที่ "add new device" และสแกนคิวอาร์โค้ดทางด้านขวา - - - Bookmark connect.comma.ai to your home screen to use it like an app - จดจำ connect.comma.ai โดยการเพิ่มไปยังหน้าจอโฮม เพื่อใช้งานเหมือนเป็นแอปพลิเคชัน - - - Please connect to Wi-Fi to complete initial pairing - กรุณาเชื่อมต่อ Wi-Fi เพื่อทำการจับคู่ครั้งแรกให้เสร็จสิ้น - - - - ParamControl - - Enable - เปิดใช้งาน - - - Cancel - ยกเลิก - - - - PrimeAdWidget - - Upgrade Now - อัพเกรดเดี๋ยวนี้ - - - Become a comma prime member at connect.comma.ai - สมัครสมาชิก comma prime ได้ที่ connect.comma.ai - - - PRIME FEATURES: - คุณสมบัติของ PRIME: - - - Remote access - การเข้าถึงระยะไกล - - - 24/7 LTE connectivity - การเชื่อมต่อ LTE แบบ 24/7 - - - 1 year of drive storage - จัดเก็บข้อมูลการขับขี่นาน 1 ปี - - - Remote snapshots - ภาพถ่ายระยะไกล - - - - PrimeUserWidget - - ✓ SUBSCRIBED - ✓ สมัครสำเร็จ - - - comma prime - comma prime - - - - QObject - - openpilot - openpilot - - - %n minute(s) ago - - %n นาทีที่แล้ว - - - - %n hour(s) ago - - %n ชั่วโมงที่แล้ว - - - - %n day(s) ago - - %n วันที่แล้ว - - - - now - ตอนนี้ - - - - SettingsWindow - - × - × - - - Device - อุปกรณ์ - - - Network - เครือข่าย - - - Toggles - ตัวเลือก - - - Software - ซอฟต์แวร์ - - - Developer - นักพัฒนา - - - Firehose - สายยางดับเพลิง - - - - SetupWidget - - Finish Setup - ตั้งค่าเสร็จสิ้น - - - Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer. - จับคู่อุปกรณ์ของคุณกับ comma connect (connect.comma.ai) และรับข้อเสนอ comma prime ของคุณ - - - Pair device - จับคู่อุปกรณ์ - - - - Sidebar - - CONNECT - เชื่อมต่อ - - - OFFLINE - ออฟไลน์ - - - ONLINE - ออนไลน์ - - - ERROR - เกิดข้อผิดพลาด - - - TEMP - อุณหภูมิ - - - HIGH - สูง - - - GOOD - ดี - - - OK - พอใช้ - - - VEHICLE - รถยนต์ - - - NO - ไม่พบ - - - PANDA - PANDA - - - -- - -- - - - Wi-Fi - Wi-Fi - - - ETH - ETH - - - 2G - 2G - - - 3G - 3G - - - LTE - LTE - - - 5G - 5G - - - - SoftwarePanel - - Uninstall %1 - ถอนการติดตั้ง %1 - - - UNINSTALL - ถอนการติดตั้ง - - - Are you sure you want to uninstall? - คุณแน่ใจหรือไม่ว่าต้องการถอนการติดตั้ง? - - - CHECK - ตรวจสอบ - - - Updates are only downloaded while the car is off. - ตัวอัปเดตจะดำเนินการดาวน์โหลดเมื่อรถดับเครื่องยนต์อยู่เท่านั้น - - - Current Version - เวอร์ชั่นปัจจุบัน - - - Download - ดาวน์โหลด - - - Install Update - ติดตั้งตัวอัปเดต - - - INSTALL - ติดตั้ง - - - Target Branch - Branch ที่เลือก - - - SELECT - เลือก - - - Select a branch - เลือก Branch - - - Uninstall - ถอนการติดตั้ง - - - failed to check for update - ไม่สามารถตรวจสอบอัปเดตได้ - - - DOWNLOAD - ดาวน์โหลด - - - update available - มีอัปเดตใหม่ - - - never - ไม่เคย - - - up to date, last checked %1 - ล่าสุดแล้ว ตรวจสอบครั้งสุดท้ายเมื่อ %1 - - - - SshControl - - SSH Keys - คีย์ SSH - - - Warning: This grants SSH access to all public keys in your GitHub settings. Never enter a GitHub username other than your own. A comma employee will NEVER ask you to add their GitHub username. - คำเตือน: สิ่งนี้ให้สิทธิ์ SSH เข้าถึงคีย์สาธารณะทั้งหมดใน GitHub ของคุณ อย่าป้อนชื่อผู้ใช้ GitHub อื่นนอกเหนือจากของคุณเอง พนักงาน comma จะไม่ขอให้คุณเพิ่มชื่อผู้ใช้ GitHub ของพวกเขา - - - ADD - เพิ่ม - - - Enter your GitHub username - ป้อนชื่อผู้ใช้ GitHub ของคุณ - - - LOADING - กำลังโหลด - - - REMOVE - ลบ - - - Username '%1' has no keys on GitHub - ชื่อผู้ใช้ '%1' ไม่มีคีย์บน GitHub - - - Request timed out - ตรวจสอบไม่สำเร็จ เนื่องจากใช้เวลามากเกินไป - - - Username '%1' doesn't exist on GitHub - ไม่พบชื่อผู้ใช้ '%1' บน GitHub - - - - SshToggle - - Enable SSH - เปิดใช้งาน SSH - - - - TermsPage - - Decline - ปฏิเสธ - - - Agree - ยอมรับ - - - Welcome to openpilot - ยินดีต้อนรับสู่ openpilot - - - You must accept the Terms and Conditions to use openpilot. Read the latest terms at <span style='color: #465BEA;'>https://comma.ai/terms</span> before continuing. - คุณต้องยอมรับข้อกำหนดและเงื่อนไขเพื่อใช้งาน openpilot อ่านข้อกำหนดล่าสุดได้ที่ <span style='color: #465BEA;'>https://comma.ai/terms</span> ก่อนดำเนินการต่อ - - - - TogglesPanel - - Enable openpilot - เปิดใช้งาน openpilot - - - Enable Lane Departure Warnings - เปิดใช้งานการเตือนการออกนอกเลน - - - 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). - รับการแจ้งเตือนให้เลี้ยวกลับเข้าเลนเมื่อรถของคุณตรวจพบการข้ามช่องจราจรโดยไม่เปิดสัญญาณไฟเลี้ยวในขณะขับขี่ที่ความเร็วเกิน 31 ไมล์ต่อชั่วโมง (50 กม./ชม) - - - Use Metric System - ใช้ระบบเมตริก - - - Display speed in km/h instead of mph. - แสดงความเร็วเป็น กม./ชม. แทน ไมล์/ชั่วโมง - - - Record and Upload Driver Camera - บันทึกและอัปโหลดภาพจากกล้องคนขับ - - - Upload data from the driver facing camera and help improve the driver monitoring algorithm. - อัปโหลดข้อมูลจากกล้องที่หันหน้าไปทางคนขับ และช่วยปรับปรุงอัลกอริธึมการตรวจสอบผู้ขับขี่ - - - Disengage on Accelerator Pedal - ยกเลิกระบบช่วยขับเมื่อเหยียบคันเร่ง - - - When enabled, pressing the accelerator pedal will disengage openpilot. - เมื่อเปิดใช้งาน การกดแป้นคันเร่งจะเป็นการยกเลิกระบบช่วยขับโดย openpilot - - - Experimental Mode - โหมดทดลอง - - - openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: - โดยปกติ openpilot จะขับใน<b>โหมดชิล</b> เปิดโหมดทดลองเพื่อใช้<b>ความสามารถในขั้นพัฒนา</b> ซึ่งยังไม่พร้อมสำหรับโหมดชิล ความสามารถในขั้นพัฒนามีดังนี้: - - - Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. - ให้ openpilot ควบคุมการเร่ง/เบรค โดย openpilot จะขับอย่างที่มนุษย์คิด รวมถึงการหยุดที่ไฟแดง และป้ายหยุดรถ เนื่องจาก openpilot จะกำหนดความเร็วในการขับด้วยตัวเอง การตั้งความเร็วจะเป็นเพียงการกำหนดความเร็วสูงสูดเท่านั้น ความสามารถนี้ยังอยู่ในขั้นพัฒนา อาจเกิดข้อผิดพลาดขึ้นได้ - - - New Driving Visualization - การแสดงภาพการขับขี่แบบใหม่ - - - Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. - ขณะนี้โหมดทดลองไม่สามารถใช้งานได้ในรถคันนี้ เนื่องจากเปิดใช้ระบบควบคุมการเร่ง/เบรคของรถที่ติดตั้งจากโรงงานอยู่ - - - openpilot longitudinal control may come in a future update. - ระบบควบคุมการเร่ง/เบรคโดย openpilot อาจมาในการอัปเดตในอนาคต - - - Aggressive - ดุดัน - - - Standard - มาตรฐาน - - - Relaxed - ผ่อนคลาย - - - Driving Personality - บุคลิกการขับขี่ - - - An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. - ระบบควบคุมการเร่ง/เบรคโดย openpilot เวอร์ชัน alpha สามารถทดสอบได้พร้อมกับโหมดการทดลอง บน branch ที่กำลังพัฒนา - - - End-to-End Longitudinal Control - ควบคุมเร่ง/เบรคแบบ End-to-End - - - Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. - เปิดระบบควบคุมการเร่ง/เบรคโดย openpilot (alpha) เพื่อเปิดใช้งานโหมดทดลอง - - - Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. - แนะนำให้ใช้แบบมาตรฐาน ในโหมดดุดัน openpilot จะตามรถคันหน้าใกล้ขึ้นและเร่งและเบรคแบบดุดันมากขึ้น ในโหมดผ่อนคลาย openpilot จะอยู่ห่างจากรถคันหน้ามากขึ้น ในรถรุ่นที่รองรับคุณสามารถเปลี่ยนบุคลิกไปแบบต่าง ๆ โดยใช้ปุ่มปรับระยะห่างบนพวงมาลัย - - - The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. - การแสดงภาพการขับขี่จะเปลี่ยนไปใช้กล้องมุมกว้างที่หันหน้าไปทางถนนเมื่ออยู่ในความเร็วต่ำ เพื่อแสดงภาพการเลี้ยวที่ดีขึ้น โลโก้โหมดการทดลองจะแสดงที่มุมบนขวาด้วย - - - Always-On Driver Monitoring - การเฝ้าระวังผู้ขับขี่ตลอดเวลา - - - Enable driver monitoring even when openpilot is not engaged. - เปิดใช้งานการเฝ้าระวังผู้ขับขี่แม้เมื่อ openpilot ไม่ได้เข้าควบคุมอยู่ - - - Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. - - - - Changing this setting will restart openpilot if the car is powered on. - - - - Record and Upload Microphone Audio - - - - Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect. - - - - - WiFiPromptWidget - - Open - เปิด - - - Maximize your training data uploads to improve openpilot's driving models. - อัปโหลดข้อมูลการฝึกฝนให้ได้มากที่สุด เพื่อพัฒนาโมเดลการขับขี่ของ openpilot - - - <span style='font-family: "Noto Color Emoji";'>🔥</span> Firehose Mode <span style='font-family: Noto Color Emoji;'>🔥</span> - <span style='font-family: "Noto Color Emoji";'>🔥</span> โหมดสายยางดับเพลิง <span style='font-family: Noto Color Emoji;'>🔥</span> - - - - WifiUI - - Scanning for networks... - กำลังสแกนหาเครือข่าย... - - - CONNECTING... - กำลังเชื่อมต่อ... - - - FORGET - เลิกใช้ - - - Forget Wi-Fi Network "%1"? - เลิกใช้เครือข่าย Wi-Fi "%1"? - - - Forget - เลิกใช้ - - - diff --git a/selfdrive/ui/translations/tr.ts b/selfdrive/ui/translations/tr.ts deleted file mode 100644 index 7c6c692d5..000000000 --- a/selfdrive/ui/translations/tr.ts +++ /dev/null @@ -1,1062 +0,0 @@ - - - - - AbstractAlert - - Close - Kapat - - - Reboot and Update - Güncelle ve Yeniden başlat - - - - AdvancedNetworking - - Back - Geri dön - - - Enable Tethering - Kişisel erişim noktasını aç - - - Tethering Password - Kişisel erişim noktasının parolası - - - EDIT - DÜZENLE - - - Enter new tethering password - Erişim noktasına yeni bir sonraki başlatılışında çekilir. parola belirleyin. - - - IP Address - IP Adresi - - - Enable Roaming - Hücresel veri aç - - - APN Setting - APN Ayarları - - - Enter APN - APN Gir - - - leave blank for automatic configuration - otomatik yapılandırma için boş bırakın - - - Cellular Metered - - - - Hidden Network - - - - CONNECT - BAĞLANTI - - - Enter SSID - APN Gir - - - Enter password - Parolayı girin - - - for "%1" - için "%1" - - - Prevent large data uploads when on a metered cellular connection - - - - default - - - - metered - - - - unmetered - - - - Wi-Fi Network Metered - - - - Prevent large data uploads when on a metered Wi-Fi connection - - - - - ConfirmationDialog - - Ok - Tamam - - - Cancel - Vazgeç - - - - DeclinePage - - You must accept the Terms and Conditions in order to use openpilot. - Openpilotu kullanmak için Kullanıcı Koşullarını kabul etmelisiniz. - - - Back - Geri - - - Decline, uninstall %1 - Reddet, Kurulumu kaldır. %1 - - - - DeveloperPanel - - Joystick Debug Mode - - - - Longitudinal Maneuver Mode - - - - openpilot Longitudinal Control (Alpha) - - - - WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB). - - - - On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha. - - - - Enable ADB - - - - ADB (Android Debug Bridge) allows connecting to your device over USB or over the network. See https://docs.comma.ai/how-to/connect-to-comma for more info. - - - - - DevicePanel - - Dongle ID - Adaptör ID - - - N/A - N/A - - - Serial - Seri Numara - - - Driver Camera - Sürücü Kamerası - - - PREVIEW - ÖN İZLEME - - - Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off) - Sürücü kamerasının görüş açısını test etmek için kamerayı önizleyin (Araç kapalı olmalıdır.) - - - Reset Calibration - Kalibrasyonu sıfırla - - - RESET - SIFIRLA - - - Are you sure you want to reset calibration? - Kalibrasyon ayarını sıfırlamak istediğinizden emin misiniz? - - - Review Training Guide - Eğitim kılavuzunu inceleyin - - - REVIEW - GÖZDEN GEÇİR - - - Review the rules, features, and limitations of openpilot - openpilot sisteminin kurallarını ve sınırlamalarını gözden geçirin. - - - Are you sure you want to review the training guide? - Eğitim kılavuzunu incelemek istediğinizden emin misiniz? - - - Regulatory - Mevzuat - - - VIEW - BAK - - - Change Language - Dili değiştir - - - CHANGE - DEĞİŞTİR - - - Select a language - Dil seçin - - - Reboot - Yeniden başlat - - - Power Off - Sistemi kapat - - - Your device is pointed %1° %2 and %3° %4. - Cihazınız %1° %2 ve %3° %4 yönünde ayarlı - - - down - aşağı - - - up - yukarı - - - left - sol - - - right - sağ - - - Are you sure you want to reboot? - Cihazı Tekrar başlatmak istediğinizden eminmisiniz? - - - Disengage to Reboot - Bağlantıyı kes ve Cihazı Yeniden başlat - - - Are you sure you want to power off? - Cihazı kapatmak istediğizden eminmisiniz? - - - Disengage to Power Off - Bağlantıyı kes ve Cihazı kapat - - - Reset - - - - Review - - - - Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer. - Cihazınızı comma connect (connect.comma.ai) ile eşleştirin ve comma prime aboneliğine göz atın. - - - Pair Device - - - - PAIR - - - - Disengage to Reset Calibration - - - - openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. - - - - openpilot is continuously calibrating, resetting is rarely required. Resetting calibration will restart openpilot if the car is powered on. - - - - - -Steering lag calibration is %1% complete. - - - - - -Steering lag calibration is complete. - - - - Steering torque response calibration is %1% complete. - - - - Steering torque response calibration is complete. - - - - - DriverViewWindow - - camera starting - kamera başlatılıyor - - - - ExperimentalModeButton - - EXPERIMENTAL MODE ON - - - - CHILL MODE ON - - - - - FirehosePanel - - openpilot learns to drive by watching humans, like you, drive. - -Firehose Mode allows you to maximize your training data uploads to improve openpilot's driving models. More data means bigger models, which means better Experimental Mode. - - - - Firehose Mode: ACTIVE - - - - ACTIVE - - - - For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.<br><br>Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.<br><br><br><b>Frequently Asked Questions</b><br><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><br><i>Do all of my segments get pulled in Firehose Mode?</i> No, we selectively pull a subset of your segments.<br><br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><br><i>Does it matter which software I run?</i> Yes, only upstream openpilot (and particular forks) are able to be used for training. - - - - <b>%n segment(s)</b> of your driving is in the training dataset so far. - - - - - - <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to an unmetered network - - - - Firehose Mode - - - - - HudRenderer - - km/h - km/h - - - mph - mph - - - MAX - MAX - - - - InputDialog - - Cancel - Kapat - - - Need at least %n character(s)! - - En az %n karakter gerekli! - - - - - MultiOptionDialog - - Select - Seç - - - Cancel - İptal et - - - - Networking - - Advanced - Gelişmiş Seçenekler - - - Enter password - Parolayı girin - - - for "%1" - için "%1" - - - Wrong password - Yalnış parola - - - - OffroadAlert - - Device temperature too high. System cooling down before starting. Current internal component temperature: %1 - - - - Immediately connect to the internet to check for updates. If you do not connect to the internet, openpilot won't engage in %1 - - - - Connect to internet to check for updates. openpilot won't automatically start until it connects to internet to check for updates. - - - - Unable to download updates -%1 - - - - Taking camera snapshots. System won't start until finished. - - - - An update to your device's operating system is downloading in the background. You will be prompted to update when it's ready to install. - - - - openpilot was unable to identify your car. Your car is either unsupported or its ECUs are not recognized. Please submit a pull request to add the firmware versions to the proper vehicle. Need help? Join discord.comma.ai. - - - - openpilot detected a change in the device's mounting position. Ensure the device is fully seated in the mount and the mount is firmly secured to the windshield. - - - - Device failed to register with the comma.ai backend. It will not connect or upload to comma.ai servers, and receives no support from comma.ai. If this is a device purchased at comma.ai/shop, open a ticket at https://comma.ai/support. - - - - Acknowledge Excessive Actuation - - - - Snooze Update - Güncellemeyi sessize al - - - openpilot detected excessive %1 actuation on your last drive. Please contact support at https://comma.ai/support and share your device's Dongle ID for troubleshooting. - - - - - OffroadHome - - UPDATE - GÜNCELLE - - - ALERTS - UYARILAR - - - ALERT - UYARI - - - - OnroadAlerts - - openpilot Unavailable - - - - TAKE CONTROL IMMEDIATELY - - - - Reboot Device - - - - Waiting to start - - - - System Unresponsive - - - - - PairingPopup - - Pair your device to your comma account - comma.ai hesabınız ile cihazı eşleştirin - - - Go to https://connect.comma.ai on your phone - Telefonuzdan https://connect.comma.ai sitesine gidin - - - Click "add new device" and scan the QR code on the right - Yeni cihaz eklemek için sağdaki QR kodunu okutun - - - Bookmark connect.comma.ai to your home screen to use it like an app - Uygulama gibi kullanmak için connect.comma.ai sitesini yer işaretlerine ekleyin. - - - Please connect to Wi-Fi to complete initial pairing - - - - - ParamControl - - Enable - - - - Cancel - - - - - PrimeAdWidget - - Upgrade Now - Hemen yükselt - - - Become a comma prime member at connect.comma.ai - connect.comma.ai üzerinden comma prime üyesi olun - - - PRIME FEATURES: - PRIME ABONELİĞİNİN ÖZELLİKLERİ: - - - Remote access - Uzaktan erişim - - - 24/7 LTE connectivity - - - - 1 year of drive storage - - - - Remote snapshots - - - - - PrimeUserWidget - - ✓ SUBSCRIBED - ✓ ABONE - - - comma prime - comma prime - - - - QObject - - openpilot - openpilot - - - %n minute(s) ago - - %n dakika önce - - - - %n hour(s) ago - - %n saat önce - - - - %n day(s) ago - - %n gün önce - - - - now - - - - - SettingsWindow - - × - x - - - Device - Cihaz - - - Network - - - - Toggles - Değiştirme - - - Software - Yazılım - - - Developer - - - - Firehose - - - - - SetupWidget - - Finish Setup - Kurulumu bitir - - - Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer. - Cihazınızı comma connect (connect.comma.ai) ile eşleştirin ve comma prime aboneliğine göz atın. - - - Pair device - Cihazı eşleştirme - - - - Sidebar - - CONNECT - BAĞLANTI - - - OFFLINE - ÇEVRİMDIŞI - - - ONLINE - ÇEVRİMİÇİ - - - ERROR - HATA - - - TEMP - SICAKLIK - - - HIGH - YÜKSEK - - - GOOD - İYİ - - - OK - TAMAM - - - VEHICLE - ARAÇ - - - NO - HAYIR - - - PANDA - PANDA - - - -- - -- - - - Wi-Fi - Wi-FI - - - ETH - ETH - - - 2G - 2G - - - 3G - 3G - - - LTE - LTE - - - 5G - 5G - - - - SoftwarePanel - - Uninstall %1 - Kaldır %1 - - - UNINSTALL - KALDIR - - - Are you sure you want to uninstall? - Kaldırmak istediğinden eminmisin? - - - CHECK - KONTROL ET - - - Updates are only downloaded while the car is off. - - - - Current Version - - - - Download - - - - Install Update - - - - INSTALL - - - - Target Branch - - - - SELECT - - - - Select a branch - - - - Uninstall - - - - failed to check for update - - - - DOWNLOAD - - - - update available - - - - never - - - - up to date, last checked %1 - - - - - SshControl - - SSH Keys - SSH Anahtarları - - - Warning: This grants SSH access to all public keys in your GitHub settings. Never enter a GitHub username other than your own. A comma employee will NEVER ask you to add their GitHub username. - UYARI: Bu, GitHub ayarlarınızdaki tüm ortak anahtarlara SSH erişimi sağlar. Asla kendi kullanıcı adınız dışında bir sonraki başlatılışında çekilir. GitHub kullanıcı adı girmeyin. - - - ADD - EKLE - - - Enter your GitHub username - Github kullanıcı adınızı giriniz - - - LOADING - YÜKLENİYOR - - - REMOVE - KALDIR - - - Username '%1' has no keys on GitHub - Kullanısının '%1' Github erişim anahtarı yok - - - Request timed out - İstek zaman aşımına uğradı - - - Username '%1' doesn't exist on GitHub - Github kullanıcısı %1 bulunamadı - - - - SshToggle - - Enable SSH - SSH aç - - - - TermsPage - - Decline - Reddet - - - Agree - Kabul et - - - Welcome to openpilot - - - - You must accept the Terms and Conditions to use openpilot. Read the latest terms at <span style='color: #465BEA;'>https://comma.ai/terms</span> before continuing. - - - - - TogglesPanel - - Enable openpilot - openpilot'u aktifleştir - - - Enable Lane Departure Warnings - Şerit ihlali uyarı alın - - - 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). - 50 km/s (31 mph) hızın üzerinde sürüş sırasında aracınız dönüş sinyali vermeden algılanan bir sonraki başlatılışında çekilir. şerit çizgisi ihlalinde şeride geri dönmek için uyarılar alın. - - - Use Metric System - Metrik sistemi kullan - - - Display speed in km/h instead of mph. - Hızı mph yerine km/h şeklinde görüntüleyin. - - - Record and Upload Driver Camera - Sürücü kamerasını kayıt et. - - - Upload data from the driver facing camera and help improve the driver monitoring algorithm. - Sürücüye bakan kamera verisini yükleyin ve Cihazın algoritmasını geliştirmemize yardımcı olun. - - - When enabled, pressing the accelerator pedal will disengage openpilot. - Aktifleştirilirse eğer gaz pedalına basınca openpilot devre dışı kalır. - - - Experimental Mode - - - - Disengage on Accelerator Pedal - - - - Aggressive - - - - Standard - - - - Relaxed - - - - Driving Personality - - - - openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: - - - - End-to-End Longitudinal Control - - - - Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. - - - - New Driving Visualization - - - - Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. - - - - openpilot longitudinal control may come in a future update. - - - - An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. - - - - Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. - - - - Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. - - - - The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. - - - - Always-On Driver Monitoring - - - - Enable driver monitoring even when openpilot is not engaged. - - - - Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. - - - - Changing this setting will restart openpilot if the car is powered on. - - - - Record and Upload Microphone Audio - - - - Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect. - - - - - WiFiPromptWidget - - Open - - - - Maximize your training data uploads to improve openpilot's driving models. - - - - <span style='font-family: "Noto Color Emoji";'>🔥</span> Firehose Mode <span style='font-family: Noto Color Emoji;'>🔥</span> - - - - - WifiUI - - Scanning for networks... - Ağ aranıyor... - - - CONNECTING... - BAĞLANILIYOR... - - - FORGET - UNUT - - - Forget Wi-Fi Network "%1"? - Wi-Fi ağını unut "%1"? - - - Forget - - - - diff --git a/selfdrive/ui/translations/zh-CHS.ts b/selfdrive/ui/translations/zh-CHS.ts deleted file mode 100644 index 9481e62f1..000000000 --- a/selfdrive/ui/translations/zh-CHS.ts +++ /dev/null @@ -1,1069 +0,0 @@ - - - - - AbstractAlert - - Close - 关闭 - - - Reboot and Update - 重启并更新 - - - - AdvancedNetworking - - Back - 返回 - - - Enable Tethering - 启用WiFi热点 - - - Tethering Password - WiFi热点密码 - - - EDIT - 编辑 - - - Enter new tethering password - 输入新的WiFi热点密码 - - - IP Address - IP 地址 - - - Enable Roaming - 启用数据漫游 - - - APN Setting - APN 设置 - - - Enter APN - 输入 APN - - - leave blank for automatic configuration - 留空以自动配置 - - - Cellular Metered - 按流量计费的手机移动网络 - - - Hidden Network - 隐藏的网络 - - - CONNECT - 连线 - - - Enter SSID - 输入 SSID - - - Enter password - 输入密码 - - - for "%1" - 网络名称:"%1" - - - Prevent large data uploads when on a metered cellular connection - 在按流量计费的移动网络上,防止上传大数据 - - - default - 默认 - - - metered - 按流量计费 - - - unmetered - 不按流量计费 - - - Wi-Fi Network Metered - 按流量计费的 WLAN 网络 - - - Prevent large data uploads when on a metered Wi-Fi connection - 在按流量计费的 WLAN 网络上,防止上传大数据 - - - - ConfirmationDialog - - Ok - 好的 - - - Cancel - 取消 - - - - DeclinePage - - You must accept the Terms and Conditions in order to use openpilot. - 您必须接受条款和条件以使用openpilot。 - - - Back - 返回 - - - Decline, uninstall %1 - 拒绝并卸载%1 - - - - DeveloperPanel - - Joystick Debug Mode - 摇杆调试模式 - - - Longitudinal Maneuver Mode - 纵向操控测试模式 - - - openpilot Longitudinal Control (Alpha) - openpilot纵向控制(Alpha 版) - - - WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB). - 警告:此车辆的 openpilot 纵向控制功能目前处于Alpha版本,使用此功能将会停用自动紧急制动(AEB)功能。 - - - On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha. - 在这辆车上,openpilot 默认使用车辆内建的主动巡航控制(ACC),而非 openpilot 的纵向控制。启用此项功能可切换至 openpilot 的纵向控制。当启用 openpilot 纵向控制 Alpha 版本时,建议同时启用实验性模式(Experimental mode)。 - - - Enable ADB - 启用 ADB - - - ADB (Android Debug Bridge) allows connecting to your device over USB or over the network. See https://docs.comma.ai/how-to/connect-to-comma for more info. - ADB(Android调试桥接)允许通过USB或网络连接到您的设备。更多信息请参见 [https://docs.comma.ai/how-to/connect-to-comma](https://docs.comma.ai/how-to/connect-to-comma)。 - - - - DevicePanel - - Dongle ID - 设备ID(Dongle ID) - - - N/A - N/A - - - Serial - 序列号 - - - Driver Camera - 驾驶员摄像头 - - - PREVIEW - 预览 - - - Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off) - 打开并预览驾驶员摄像头,以确保驾驶员监控具有良好视野。(仅熄火时可用) - - - Reset Calibration - 重置设备校准 - - - RESET - 重置 - - - Are you sure you want to reset calibration? - 您确定要重置设备校准吗? - - - Review Training Guide - 新手指南 - - - REVIEW - 查看 - - - Review the rules, features, and limitations of openpilot - 查看 openpilot 的使用规则,以及其功能和限制 - - - Are you sure you want to review the training guide? - 您确定要查看新手指南吗? - - - Regulatory - 监管信息 - - - VIEW - 查看 - - - Change Language - 切换语言 - - - CHANGE - 切换 - - - Select a language - 选择语言 - - - Reboot - 重启 - - - Power Off - 关机 - - - Your device is pointed %1° %2 and %3° %4. - 您的设备校准为%1° %2、%3° %4。 - - - down - 朝下 - - - up - 朝上 - - - left - 朝左 - - - right - 朝右 - - - Are you sure you want to reboot? - 您确定要重新启动吗? - - - Disengage to Reboot - 取消openpilot以重新启动 - - - Are you sure you want to power off? - 您确定要关机吗? - - - Disengage to Power Off - 取消openpilot以关机 - - - Reset - 重置 - - - Review - 预览 - - - Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer. - 将您的设备与comma connect (connect.comma.ai)配对并领取您的comma prime优惠。 - - - Pair Device - 配对设备 - - - PAIR - 配对 - - - Disengage to Reset Calibration - 解除以重置校准 - - - openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. - openpilot 要求设备的安装角度:左右偏移需在 4° 以内,上下俯仰角度需在向上 5° 至向下 9° 的范围内。 - - - openpilot is continuously calibrating, resetting is rarely required. Resetting calibration will restart openpilot if the car is powered on. - openpilot 会持续进行校准,因此很少需要重置。如果车辆电源已开启,重置校准会重新启动 openpilot。 - - - - -Steering lag calibration is %1% complete. - - -转向延迟校准已完成 %1%。 - - - - -Steering lag calibration is complete. - - -转向延迟校准已完成。 - - - Steering torque response calibration is %1% complete. - 转向扭矩响应校准已完成 %1%。 - - - Steering torque response calibration is complete. - 转向扭矩响应校准已完成。 - - - - DriverViewWindow - - camera starting - 正在启动相机 - - - - ExperimentalModeButton - - EXPERIMENTAL MODE ON - 试验模式运行 - - - CHILL MODE ON - 轻松模式运行 - - - - FirehosePanel - - openpilot learns to drive by watching humans, like you, drive. - -Firehose Mode allows you to maximize your training data uploads to improve openpilot's driving models. More data means bigger models, which means better Experimental Mode. - openpilot 通过观察人类驾驶(包括您)来学习如何驾驶。 - -“Firehose 模式”允许您最大化上传训练数据,以改进 openpilot 的驾驶模型。更多数据意味着更强大的模型,也就意味着更优秀的“实验模式”。 - - - Firehose Mode: ACTIVE - Firehose 模式:激活中 - - - ACTIVE - 激活中 - - - For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.<br><br>Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.<br><br><br><b>Frequently Asked Questions</b><br><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><br><i>Do all of my segments get pulled in Firehose Mode?</i> No, we selectively pull a subset of your segments.<br><br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><br><i>Does it matter which software I run?</i> Yes, only upstream openpilot (and particular forks) are able to be used for training. - 为了达到最佳效果,请每周将您的设备带回室内,并连接到优质的 USB-C 充电器和 Wi-Fi。<br><br>Firehose 模式在行驶时也能运行,但需连接到移动热点或使用不限流量的 SIM 卡。<br><br><br><b>常见问题</b><br><br><i>我开车的方式或地点有影响吗?</i>不会,请像平常一样驾驶即可。<br><br><i>Firehose 模式会上传所有的驾驶片段吗?</i>不会,我们会选择性地上传部分片段。<br><br><i>什么是好的 USB-C 充电器?</i>任何快速手机或笔记本电脑充电器都应该适用。<br><br><i>我使用的软件版本有影响吗?</i>有的,只有官方 openpilot(以及特定的分支)可以用于训练。 - - - <b>%n segment(s)</b> of your driving is in the training dataset so far. - - <b>目前已有 %n 段</b> 您的驾驶数据被纳入训练数据集。 - - - - <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to an unmetered network - <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>闲置</span>:请连接到不限流量的网络 - - - Firehose Mode - Firehose 模式 - - - - HudRenderer - - km/h - km/h - - - mph - mph - - - MAX - 最高定速 - - - - InputDialog - - Cancel - 取消 - - - Need at least %n character(s)! - - 至少需要 %n 个字符! - - - - - MultiOptionDialog - - Select - 选择 - - - Cancel - 取消 - - - - Networking - - Advanced - 高级 - - - Enter password - 输入密码 - - - for "%1" - 网络名称:"%1" - - - Wrong password - 密码错误 - - - - OffroadAlert - - Immediately connect to the internet to check for updates. If you do not connect to the internet, openpilot won't engage in %1 - 请立即连接网络检查更新。如果不连接网络,openpilot 将在 %1 后便无法使用 - - - Connect to internet to check for updates. openpilot won't automatically start until it connects to internet to check for updates. - 请连接至互联网以检查更新。在连接至互联网并完成更新检查之前,openpilot 将不会自动启动。 - - - Unable to download updates -%1 - 无法下载更新 -%1 - - - Taking camera snapshots. System won't start until finished. - 正在使用相机拍摄中。在完成之前,系统将无法启动。 - - - An update to your device's operating system is downloading in the background. You will be prompted to update when it's ready to install. - 一个针对您设备的操作系统更新正在后台下载中。当更新准备好安装时,您将收到提示进行更新。 - - - openpilot was unable to identify your car. Your car is either unsupported or its ECUs are not recognized. Please submit a pull request to add the firmware versions to the proper vehicle. Need help? Join discord.comma.ai. - openpilot 无法识别您的车辆。您的车辆可能未被支持,或是其电控单元 (ECU) 未被识别。请提交一个 Pull Request 为您的车辆添加正确的固件版本。需要帮助吗?请加入 discord.comma.ai。 - - - openpilot detected a change in the device's mounting position. Ensure the device is fully seated in the mount and the mount is firmly secured to the windshield. - openpilot 检测到设备的安装位置发生变化。请确保设备完全安装在支架上,并确保支架牢固地固定在挡风玻璃上。 - - - Device temperature too high. System cooling down before starting. Current internal component temperature: %1 - 设备温度过高。系统正在冷却中,等冷却完毕后才会启动。目前内部组件温度:%1 - - - Device failed to register with the comma.ai backend. It will not connect or upload to comma.ai servers, and receives no support from comma.ai. If this is a device purchased at comma.ai/shop, open a ticket at https://comma.ai/support. - 设备未能注册到 comma.ai 后端。该设备将无法连接或上传数据到 comma.ai 服务器,也无法获得 comma.ai 的支持。如果该设备是在 comma.ai/shop 购买的,请访问 https://comma.ai/support 提交工单。 - - - Acknowledge Excessive Actuation - 确认过度作动 - - - Snooze Update - 推迟更新 - - - openpilot detected excessive %1 actuation on your last drive. Please contact support at https://comma.ai/support and share your device's Dongle ID for troubleshooting. - openpilot 在您上一次驾驶中,检测到过度的 %1 作动。请访问 https://comma.ai/support 联系客服,并提供您设备的 Dongle ID 以便进行故障排查。 - - - - OffroadHome - - UPDATE - 更新 - - - ALERTS - 警报 - - - ALERT - 警报 - - - - OnroadAlerts - - openpilot Unavailable - 无法使用 openpilot - - - TAKE CONTROL IMMEDIATELY - 立即接管 - - - Reboot Device - 重启设备 - - - Waiting to start - 等待开始 - - - System Unresponsive - 系统无响应 - - - - PairingPopup - - Pair your device to your comma account - 将您的设备与comma账号配对 - - - Go to https://connect.comma.ai on your phone - 在手机上访问 https://connect.comma.ai - - - Click "add new device" and scan the QR code on the right - 点击“添加新设备”,扫描右侧二维码 - - - Bookmark connect.comma.ai to your home screen to use it like an app - 将 connect.comma.ai 收藏到您的主屏幕,以便像应用程序一样使用它 - - - Please connect to Wi-Fi to complete initial pairing - 请连接 Wi-Fi 以完成初始配对 - - - - ParamControl - - Cancel - 取消 - - - Enable - 启用 - - - - PrimeAdWidget - - Upgrade Now - 现在升级 - - - Become a comma prime member at connect.comma.ai - 打开connect.comma.ai以注册comma prime会员 - - - PRIME FEATURES: - comma prime特权: - - - Remote access - 远程访问 - - - 24/7 LTE connectivity - 全天候 LTE 连接 - - - 1 year of drive storage - 一年的行驶记录储存空间 - - - Remote snapshots - 远程快照 - - - - PrimeUserWidget - - ✓ SUBSCRIBED - ✓ 已订阅 - - - comma prime - comma prime - - - - QObject - - openpilot - openpilot - - - %n minute(s) ago - - %n 分钟前 - - - - %n hour(s) ago - - %n 小时前 - - - - %n day(s) ago - - %n 天前 - - - - now - 现在 - - - - SettingsWindow - - × - × - - - Device - 设备 - - - Network - 网络 - - - Toggles - 设定 - - - Software - 软件 - - - Developer - 开发人员 - - - Firehose - Firehose - - - - SetupWidget - - Finish Setup - 完成设置 - - - Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer. - 将您的设备与comma connect (connect.comma.ai)配对并领取您的comma prime优惠。 - - - Pair device - 配对设备 - - - - Sidebar - - CONNECT - CONNECT - - - OFFLINE - 离线 - - - ONLINE - 在线 - - - ERROR - 连接出错 - - - TEMP - 设备温度 - - - HIGH - 过热 - - - GOOD - 良好 - - - OK - 一般 - - - VEHICLE - 车辆连接 - - - NO - - - - PANDA - PANDA - - - -- - -- - - - Wi-Fi - Wi-Fi - - - ETH - 以太网 - - - 2G - 2G - - - 3G - 3G - - - LTE - LTE - - - 5G - 5G - - - - SoftwarePanel - - Updates are only downloaded while the car is off. - 车辆熄火时才能下载升级文件。 - - - Current Version - 当前版本 - - - Download - 下载 - - - Install Update - 安装更新 - - - INSTALL - 安装 - - - Target Branch - 目标分支 - - - SELECT - 选择 - - - Select a branch - 选择分支 - - - UNINSTALL - 卸载 - - - Uninstall %1 - 卸载 %1 - - - Are you sure you want to uninstall? - 您确定要卸载吗? - - - CHECK - 查看 - - - Uninstall - 卸载 - - - failed to check for update - 检查更新失败 - - - up to date, last checked %1 - 已经是最新版本,上次检查时间为 %1 - - - DOWNLOAD - 下载 - - - update available - 有可用的更新 - - - never - 从未更新 - - - - SshControl - - SSH Keys - SSH密钥 - - - Warning: This grants SSH access to all public keys in your GitHub settings. Never enter a GitHub username other than your own. A comma employee will NEVER ask you to add their GitHub username. - 警告:这将授予SSH访问权限给您GitHub设置中的所有公钥。切勿输入您自己以外的GitHub用户名。comma员工永远不会要求您添加他们的GitHub用户名。 - - - ADD - 添加 - - - Enter your GitHub username - 输入您的GitHub用户名 - - - LOADING - 正在加载 - - - REMOVE - 删除 - - - Username '%1' has no keys on GitHub - 用户名“%1”在GitHub上没有密钥 - - - Request timed out - 请求超时 - - - Username '%1' doesn't exist on GitHub - GitHub上不存在用户名“%1” - - - - SshToggle - - Enable SSH - 启用SSH - - - - TermsPage - - Decline - 拒绝 - - - Agree - 同意 - - - Welcome to openpilot - 欢迎使用 openpilot - - - You must accept the Terms and Conditions to use openpilot. Read the latest terms at <span style='color: #465BEA;'>https://comma.ai/terms</span> before continuing. - 您必须接受《条款与条件》才能使用 openpilot。在继续之前,请先阅读最新条款:<span style='color: #465BEA;'>https://comma.ai/terms</span>。 - - - - TogglesPanel - - Enable openpilot - 启用openpilot - - - Enable Lane Departure Warnings - 启用车道偏离警告 - - - 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). - 车速超过31mph(50km/h)时,若检测到车辆越过车道线且未打转向灯,系统将发出警告以提醒您返回车道。 - - - Use Metric System - 使用公制单位 - - - Display speed in km/h instead of mph. - 显示车速时,以km/h代替mph。 - - - Record and Upload Driver Camera - 录制并上传驾驶员摄像头 - - - Upload data from the driver facing camera and help improve the driver monitoring algorithm. - 上传驾驶员摄像头的数据,帮助改进驾驶员监控算法。 - - - Disengage on Accelerator Pedal - 踩油门时取消控制 - - - When enabled, pressing the accelerator pedal will disengage openpilot. - 启用后,踩下油门踏板将取消openpilot。 - - - Experimental Mode - 测试模式 - - - openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: - openpilot 默认 <b>轻松模式</b>驾驶车辆。试验模式启用一些轻松模式之外的 <b>试验性功能</b>。试验性功能包括: - - - Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. - 允许驾驶模型控制加速和制动,openpilot将模仿人类驾驶车辆,包括在红灯和停车让行标识前停车。鉴于驾驶模型确定行驶车速,所设定的车速仅作为上限。此功能尚处于早期测试状态,有可能会出现操作错误。 - - - New Driving Visualization - 新驾驶视角 - - - Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. - 由于此车辆使用自带的ACC纵向控制,当前无法使用试验模式。 - - - openpilot longitudinal control may come in a future update. - openpilot纵向控制可能会在未来的更新中提供。 - - - Aggressive - 积极 - - - Standard - 标准 - - - Relaxed - 舒适 - - - Driving Personality - 驾驶风格 - - - An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. - 在正式(release)版本以外的分支上,可以测试 openpilot 纵向控制的 Alpha 版本以及实验模式。 - - - Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. - 启用 openpilot 纵向控制(alpha)开关以允许实验模式。 - - - End-to-End Longitudinal Control - 端到端纵向控制 - - - Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. - 推荐使用标准模式。在积极模式下,openpilot 会更靠近前方车辆,并在油门和刹车方面更加激进。在放松模式下,openpilot 会与前方车辆保持更远距离。在支持的车型上,你可以使用方向盘上的距离按钮来循环切换这些驾驶风格。 - - - The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. - 在低速时,驾驶可视化将转换为道路朝向的广角摄像头,以更好地展示某些转弯。测试模式标志也将显示在右上角。 - - - Always-On Driver Monitoring - 驾驶员监控常开 - - - Enable driver monitoring even when openpilot is not engaged. - 即使在openpilot未激活时也启用驾驶员监控。 - - - Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. - openpilot 系统提供“自适应巡航”和“车道保持”驾驶辅助功能。使用此功能时,您需要时刻保持专注。 - - - Changing this setting will restart openpilot if the car is powered on. - 如果车辆已通电,更改此设置将会重新启动 openpilot。 - - - Record and Upload Microphone Audio - 录制并上传麦克风音频 - - - Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect. - 在驾驶时录制并存储麦克风音频。该音频将会包含在 comma connect 的行车记录仪视频中。 - - - - WiFiPromptWidget - - Open - 开启 - - - Maximize your training data uploads to improve openpilot's driving models. - 最大化您的训练数据上传,以改善 openpilot 的驾驶模型。 - - - <span style='font-family: "Noto Color Emoji";'>🔥</span> Firehose Mode <span style='font-family: Noto Color Emoji;'>🔥</span> - <span style='font-family: "Noto Color Emoji";'>🔥</span> Firehose 模式 <span style='font-family: Noto Color Emoji;'>🔥</span> - - - - WifiUI - - Scanning for networks... - 正在扫描网络…… - - - CONNECTING... - 正在连接…… - - - FORGET - 忽略 - - - Forget Wi-Fi Network "%1"? - 忽略WiFi网络 "%1"? - - - Forget - 忽略 - - - diff --git a/selfdrive/ui/translations/zh-CHT.ts b/selfdrive/ui/translations/zh-CHT.ts deleted file mode 100644 index c3ef55d3d..000000000 --- a/selfdrive/ui/translations/zh-CHT.ts +++ /dev/null @@ -1,1069 +0,0 @@ - - - - - AbstractAlert - - Close - 關閉 - - - Reboot and Update - 重啟並更新 - - - - AdvancedNetworking - - Back - 回上頁 - - - Enable Tethering - 啟用網路分享 - - - Tethering Password - 網路分享密碼 - - - EDIT - 編輯 - - - Enter new tethering password - 輸入新的網路分享密碼 - - - IP Address - IP 地址 - - - Enable Roaming - 啟用漫遊 - - - APN Setting - APN 設置 - - - Enter APN - 輸入 APN - - - leave blank for automatic configuration - 留空白將自動配置 - - - Cellular Metered - 計費的行動網路 - - - Hidden Network - 隱藏的網路 - - - CONNECT - 連線 - - - Enter SSID - 輸入 SSID - - - Enter password - 輸入密碼 - - - for "%1" - 給 "%1" - - - Prevent large data uploads when on a metered cellular connection - 在使用計費行動網路時,防止上傳大量數據 - - - default - 預設 - - - metered - 計費 - - - unmetered - 非計費 - - - Wi-Fi Network Metered - 計費 Wi-Fi 網路 - - - Prevent large data uploads when on a metered Wi-Fi connection - 在使用計費 Wi-Fi 網路時,防止上傳大量數據 - - - - ConfirmationDialog - - Ok - 確定 - - - Cancel - 取消 - - - - DeclinePage - - You must accept the Terms and Conditions in order to use openpilot. - 您必須先接受條款和條件才能使用 openpilot。 - - - Back - 回上頁 - - - Decline, uninstall %1 - 拒絕並解除安裝 %1 - - - - DeveloperPanel - - Joystick Debug Mode - 搖桿調試模式 - - - Longitudinal Maneuver Mode - 縱向操控測試模式 - - - openpilot Longitudinal Control (Alpha) - openpilot 縱向控制 (Alpha 版) - - - WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB). - 警告:此車輛的 openpilot 縱向控制功能目前處於 Alpha 版本,使用此功能將會停用自動緊急煞車(AEB)功能。 - - - On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha. - 在這輛車上,openpilot 預設使用車輛內建的主動巡航控制(ACC),而非 openpilot 的縱向控制。啟用此項功能可切換至 openpilot 的縱向控制。當啟用 openpilot 縱向控制 Alpha 版本時,建議同時啟用實驗性模式(Experimental mode)。 - - - Enable ADB - 啟用 ADB - - - ADB (Android Debug Bridge) allows connecting to your device over USB or over the network. See https://docs.comma.ai/how-to/connect-to-comma for more info. - ADB(Android 調試橋接)允許通過 USB 或網絡連接到您的設備。更多信息請參見 [https://docs.comma.ai/how-to/connect-to-comma](https://docs.comma.ai/how-to/connect-to-comma)。 - - - - DevicePanel - - Dongle ID - Dongle ID - - - N/A - 無法使用 - - - Serial - 序號 - - - Driver Camera - 駕駛員監控鏡頭 - - - PREVIEW - 預覽 - - - Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off) - 預覽駕駛員監控鏡頭畫面,以確保其具有良好視野。(僅在熄火時可用) - - - Reset Calibration - 重設校準 - - - RESET - 重設 - - - Are you sure you want to reset calibration? - 您確定要重設校準嗎? - - - Review Training Guide - 觀看使用教學 - - - REVIEW - 觀看 - - - Review the rules, features, and limitations of openpilot - 觀看 openpilot 的使用規則、功能和限制 - - - Are you sure you want to review the training guide? - 您確定要觀看使用教學嗎? - - - Regulatory - 法規/監管 - - - VIEW - 觀看 - - - Change Language - 更改語言 - - - CHANGE - 更改 - - - Select a language - 選擇語言 - - - Reboot - 重新啟動 - - - Power Off - 關機 - - - Your device is pointed %1° %2 and %3° %4. - 你的裝置目前朝%2 %1° 以及朝%4 %3° 。 - - - down - - - - up - - - - left - - - - right - - - - Are you sure you want to reboot? - 您確定要重新啟動嗎? - - - Disengage to Reboot - 請先取消控車才能重新啟動 - - - Are you sure you want to power off? - 您確定您要關機嗎? - - - Disengage to Power Off - 請先取消控車才能關機 - - - Reset - 重設 - - - Review - 回顧 - - - Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer. - 將您的裝置與 comma connect (connect.comma.ai) 配對並領取您的 comma 高級會員優惠。 - - - Pair Device - 配對裝置 - - - PAIR - 配對 - - - Disengage to Reset Calibration - 解除以重設校準 - - - openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. - openpilot 要求裝置的安裝角度,左右偏移須在 4° 以內,上下角度則須介於仰角 5° 至俯角 9° 之間。 - - - openpilot is continuously calibrating, resetting is rarely required. Resetting calibration will restart openpilot if the car is powered on. - openpilot 會持續進行校準,因此很少需要重設。若車輛電源開啟,重設校準將會重新啟動 openpilot。 - - - - -Steering lag calibration is %1% complete. - - -轉向延遲校準已完成 %1%。 - - - - -Steering lag calibration is complete. - - -轉向延遲校準已完成。 - - - Steering torque response calibration is %1% complete. - 轉向扭矩反應校準已完成 %1%。 - - - Steering torque response calibration is complete. - 轉向扭矩反應校準已完成。 - - - - DriverViewWindow - - camera starting - 開啟相機中 - - - - ExperimentalModeButton - - EXPERIMENTAL MODE ON - 實驗模式 ON - - - CHILL MODE ON - 輕鬆模式 ON - - - - FirehosePanel - - openpilot learns to drive by watching humans, like you, drive. - -Firehose Mode allows you to maximize your training data uploads to improve openpilot's driving models. More data means bigger models, which means better Experimental Mode. - openpilot 透過觀察人類駕駛(包括您)來學習如何駕駛。 - -「Firehose 模式」可讓您最大化上傳訓練數據,以改進 openpilot 的駕駛模型。更多數據代表更強大的模型,也就意味著更優秀的「實驗模式」。 - - - Firehose Mode: ACTIVE - Firehose 模式:啟用中 - - - ACTIVE - 啟用中 - - - For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.<br><br>Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.<br><br><br><b>Frequently Asked Questions</b><br><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><br><i>Do all of my segments get pulled in Firehose Mode?</i> No, we selectively pull a subset of your segments.<br><br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><br><i>Does it matter which software I run?</i> Yes, only upstream openpilot (and particular forks) are able to be used for training. - 為了達到最佳效果,請每週將您的裝置帶回室內,並連接至優質的 USB-C 充電器與 Wi-Fi。<br><br>訓練資料上傳模式在行駛時也能運作,但需連接至行動熱點或使用不限流量的 SIM 卡。<br><br><br><b>常見問題</b><br><br><i>我開車的方式或地點有影響嗎?</i> 不會,請像平常一樣駕駛即可。<br><br><i>Firehose 模式會上傳所有的駕駛片段嗎?</i>不會,我們會選擇性地上傳部分片段。<br><br><i>什麼是好的 USB-C 充電器?</i>任何快速手機或筆電充電器都應該適用。<br><br><i>我使用的軟體版本有影響嗎?</i>有的,只有官方 openpilot(以及特定的分支)可以用於訓練。 - - - <b>%n segment(s)</b> of your driving is in the training dataset so far. - - <b>目前已有 %n 段</b> 您的駕駛數據被納入訓練資料集。 - - - - <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to an unmetered network - <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>閒置中</span>:請連接到不按流量計費的網絡 - - - Firehose Mode - Firehose 模式 - - - - HudRenderer - - km/h - km/h - - - mph - mph - - - MAX - 最高 - - - - InputDialog - - Cancel - 取消 - - - Need at least %n character(s)! - - 需要至少 %n 個字元! - - - - - MultiOptionDialog - - Select - 選擇 - - - Cancel - 取消 - - - - Networking - - Advanced - 進階 - - - Enter password - 輸入密碼 - - - for "%1" - 給 "%1" - - - Wrong password - 密碼錯誤 - - - - OffroadAlert - - Immediately connect to the internet to check for updates. If you do not connect to the internet, openpilot won't engage in %1 - 請立即連接網路檢查更新。如果不連接網路,openpilot 將在 %1 後便無法使用 - - - Connect to internet to check for updates. openpilot won't automatically start until it connects to internet to check for updates. - 請連接至網際網路以檢查更新。在連接至網際網路並完成更新檢查之前,openpilot 將不會自動啟動。 - - - Unable to download updates -%1 - 無法下載更新 -%1 - - - Taking camera snapshots. System won't start until finished. - 正在使用相機拍攝中。在完成之前,系統將無法啟動。 - - - An update to your device's operating system is downloading in the background. You will be prompted to update when it's ready to install. - 一個有關操作系統的更新正在後台下載中。當更新準備好安裝時,您將收到提示進行更新。 - - - openpilot was unable to identify your car. Your car is either unsupported or its ECUs are not recognized. Please submit a pull request to add the firmware versions to the proper vehicle. Need help? Join discord.comma.ai. - openpilot 無法識別您的車輛。您的車輛可能未被支援,或是其電控單元 (ECU) 未被識別。請提交一個 Pull Request 為您的車輛添加正確的韌體版本。需要幫助嗎?請加入 discord.comma.ai 。 - - - openpilot detected a change in the device's mounting position. Ensure the device is fully seated in the mount and the mount is firmly secured to the windshield. - openpilot 偵測到裝置的安裝位置發生變化。請確保裝置完全安裝在支架上,並確保支架牢固地固定在擋風玻璃上。 - - - Device temperature too high. System cooling down before starting. Current internal component temperature: %1 - 裝置溫度過高。系統正在冷卻中,等冷卻完畢後才會啟動。目前內部組件溫度:%1 - - - Device failed to register with the comma.ai backend. It will not connect or upload to comma.ai servers, and receives no support from comma.ai. If this is a device purchased at comma.ai/shop, open a ticket at https://comma.ai/support. - 裝置註冊 comma.ai 後端失敗。此裝置將無法連線或上傳資料至 comma.ai 伺服器,也無法獲得 comma.ai 的支援。若此裝置購自 comma.ai/shop,請至 https://comma.ai/support 建立支援請求。 - - - Acknowledge Excessive Actuation - 確認過度作動 - - - Snooze Update - 延後更新 - - - openpilot detected excessive %1 actuation on your last drive. Please contact support at https://comma.ai/support and share your device's Dongle ID for troubleshooting. - openpilot 在您上次的駕駛中,偵測到過度的 %1 作動。請至 https://comma.ai/support 聯絡客服,並提供您裝置的 Dongle ID 以進行故障排除。 - - - - OffroadHome - - UPDATE - 更新 - - - ALERTS - 提醒 - - - ALERT - 提醒 - - - - OnroadAlerts - - openpilot Unavailable - 無法使用 openpilot - - - TAKE CONTROL IMMEDIATELY - 立即接管 - - - Reboot Device - 請重新啟裝置 - - - Waiting to start - 等待開始 - - - System Unresponsive - 系統無回應 - - - - PairingPopup - - Pair your device to your comma account - 將裝置與您的 comma 帳號配對 - - - Go to https://connect.comma.ai on your phone - 用手機連至 https://connect.comma.ai - - - Click "add new device" and scan the QR code on the right - 點選 "add new device" 後掃描右邊的二維碼 - - - Bookmark connect.comma.ai to your home screen to use it like an app - 將 connect.comma.ai 加入您的主螢幕,以便像手機 App 一樣使用它 - - - Please connect to Wi-Fi to complete initial pairing - 請連接 Wi-Fi 以完成初始配對 - - - - ParamControl - - Cancel - 取消 - - - Enable - 啟用 - - - - PrimeAdWidget - - Upgrade Now - 馬上升級 - - - Become a comma prime member at connect.comma.ai - 成為 connect.comma.ai 的高級會員 - - - PRIME FEATURES: - 高級會員特點: - - - Remote access - 遠端存取 - - - 24/7 LTE connectivity - 24/7 LTE 連線 - - - 1 year of drive storage - 一年的行駛記錄儲存空間 - - - Remote snapshots - 遠端快照 - - - - PrimeUserWidget - - ✓ SUBSCRIBED - ✓ 已訂閱 - - - comma prime - comma 高級會員 - - - - QObject - - openpilot - openpilot - - - %n minute(s) ago - - %n 分鐘前 - - - - %n hour(s) ago - - %n 小時前 - - - - %n day(s) ago - - %n 天前 - - - - now - 現在 - - - - SettingsWindow - - × - × - - - Device - 裝置 - - - Network - 網路 - - - Toggles - 設定 - - - Software - 軟體 - - - Developer - 開發人員 - - - Firehose - Firehose - - - - SetupWidget - - Finish Setup - 完成設置 - - - Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer. - 將您的裝置與 comma connect (connect.comma.ai) 配對並領取您的 comma 高級會員優惠。 - - - Pair device - 配對裝置 - - - - Sidebar - - CONNECT - 雲端服務 - - - OFFLINE - 已離線 - - - ONLINE - 已連線 - - - ERROR - 錯誤 - - - TEMP - 溫度 - - - HIGH - 偏高 - - - GOOD - 正常 - - - OK - 一般 - - - VEHICLE - 車輛通訊 - - - NO - 未連線 - - - PANDA - 車輛通訊 - - - -- - -- - - - Wi-Fi - Wi-Fi - - - ETH - ETH - - - 2G - 2G - - - 3G - 3G - - - LTE - LTE - - - 5G - 5G - - - - SoftwarePanel - - Updates are only downloaded while the car is off. - 系統更新只會在熄火時下載。 - - - Current Version - 當前版本 - - - Download - 下載 - - - Install Update - 安裝更新 - - - INSTALL - 安裝 - - - Target Branch - 目標分支 - - - SELECT - 選取 - - - Select a branch - 選取一個分支 - - - UNINSTALL - 解除安裝 - - - Uninstall %1 - 解除安裝 %1 - - - Are you sure you want to uninstall? - 您確定您要解除安裝嗎? - - - CHECK - 檢查 - - - Uninstall - 解除安裝 - - - failed to check for update - 檢查更新失敗 - - - up to date, last checked %1 - 已經是最新版本,上次檢查時間為 %1 - - - DOWNLOAD - 下載 - - - update available - 有可用的更新 - - - never - 從未更新 - - - - SshControl - - SSH Keys - SSH 金鑰 - - - Warning: This grants SSH access to all public keys in your GitHub settings. Never enter a GitHub username other than your own. A comma employee will NEVER ask you to add their GitHub username. - 警告:這將授權給 GitHub 帳號中所有公鑰 SSH 訪問權限。切勿輸入非您自己的 GitHub 使用者名稱。comma 員工「永遠不會」要求您添加他們的 GitHub 使用者名稱。 - - - ADD - 新增 - - - Enter your GitHub username - 請輸入您 GitHub 的使用者名稱 - - - LOADING - 載入中 - - - REMOVE - 移除 - - - Username '%1' has no keys on GitHub - GitHub 用戶 '%1' 沒有設定任何金鑰 - - - Request timed out - 請求超時 - - - Username '%1' doesn't exist on GitHub - GitHub 用戶 '%1' 不存在 - - - - SshToggle - - Enable SSH - 啟用 SSH 服務 - - - - TermsPage - - Decline - 拒絕 - - - Agree - 接受 - - - Welcome to openpilot - 歡迎使用 openpilot - - - You must accept the Terms and Conditions to use openpilot. Read the latest terms at <span style='color: #465BEA;'>https://comma.ai/terms</span> before continuing. - 您必須接受《條款與條件》才能使用 openpilot。在繼續之前,請先閱讀最新條款:<span style='color: #465BEA;'>https://comma.ai/terms</span>。 - - - - TogglesPanel - - Enable openpilot - 啟用 openpilot - - - Enable Lane Departure Warnings - 啟用車道偏離警告 - - - 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). - 車速在時速 50 公里 (31 英里) 以上且未打方向燈的情況下,如果偵測到車輛駛出目前車道線時,發出車道偏離警告。 - - - Use Metric System - 使用公制單位 - - - Display speed in km/h instead of mph. - 啟用後,速度單位顯示將從 mp/h 改為 km/h。 - - - Record and Upload Driver Camera - 記錄並上傳駕駛監控影像 - - - Upload data from the driver facing camera and help improve the driver monitoring algorithm. - 上傳駕駛監控的錄影來協助我們提升駕駛監控的準確率。 - - - Disengage on Accelerator Pedal - 油門取消控車 - - - When enabled, pressing the accelerator pedal will disengage openpilot. - 啟用後,踩踏油門將會取消 openpilot 控制。 - - - Experimental Mode - 實驗模式 - - - openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: - openpilot 預設以 <b>輕鬆模式</b> 駕駛。 實驗模式啟用了尚未準備好進入輕鬆模式的 <b>alpha 級功能</b>。實驗功能如下: - - - Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. - 讓駕駛模型來控制油門及煞車。openpilot將會模擬人類的駕駛行為,包含在看見紅燈及停止標示時停車。由於車速將由駕駛模型決定,因此您設定的時速將成為速度上限。本功能仍在早期實驗階段,請預期模型有犯錯的可能性。 - - - New Driving Visualization - 新的駕駛視覺介面 - - - Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. - 因車輛使用內建ACC系統,無法在本車輛上啟動實驗模式。 - - - openpilot longitudinal control may come in a future update. - openpilot 縱向控制可能會在未來的更新中提供。 - - - Aggressive - 積極 - - - Standard - 標準 - - - Relaxed - 舒適 - - - Driving Personality - 駕駛風格 - - - An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. - 在正式 (release) 版以外的分支上可以測試 openpilot 縱向控制的 Alpha 版本以及實驗模式。 - - - Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. - 啟用 openpilot 縱向控制(alpha)切換以允許實驗模式。 - - - End-to-End Longitudinal Control - 端到端縱向控制 - - - Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with your steering wheel distance button. - 建議使用標準模式。在積極模式下,openpilot 會更接近前車並更積極地使用油門和剎車。在輕鬆模式下,openpilot 會與前車保持較遠距離。對於支援的汽車,您可以使用方向盤上的距離按鈕來切換這些駕駛風格。 - - - The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. - 在低速時,駕駛可視化將切換至道路朝向的廣角攝影機,以更好地顯示某些彎道。在右上角還會顯示「實驗模式」的標誌。 - - - Always-On Driver Monitoring - 駕駛監控常開 - - - Enable driver monitoring even when openpilot is not engaged. - 即使在openpilot未激活時也啟用駕駛監控。 - - - Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. - openpilot 系統提供「主動式巡航」與「車道維持」等駕駛輔助功能。使用這些功能時,您必須隨時保持專注。 - - - Changing this setting will restart openpilot if the car is powered on. - 若車輛電源為開啟狀態,變更此設定將會重新啟動 openpilot。 - - - Record and Upload Microphone Audio - 錄製並上傳麥克風音訊 - - - Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect. - 在駕駛時錄製並儲存麥克風音訊。此音訊將會收錄在 comma connect 的行車記錄器影片中。 - - - - WiFiPromptWidget - - Open - 開啟 - - - Maximize your training data uploads to improve openpilot's driving models. - 最大化您的訓練數據上傳,以改善 openpilot 的駕駛模型。 - - - <span style='font-family: "Noto Color Emoji";'>🔥</span> Firehose Mode <span style='font-family: Noto Color Emoji;'>🔥</span> - <span style='font-family: "Noto Color Emoji";'>🔥</span> Firehose 模式 <span style='font-family: Noto Color Emoji;'>🔥</span> - - - - WifiUI - - Scanning for networks... - 掃描無線網路中... - - - CONNECTING... - 連線中... - - - FORGET - 清除 - - - Forget Wi-Fi Network "%1"? - 清除 Wi-Fi 網路 "%1"? - - - Forget - 清除 - - - diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc deleted file mode 100644 index ed851a41c..000000000 --- a/selfdrive/ui/ui.cc +++ /dev/null @@ -1,199 +0,0 @@ -#include "selfdrive/ui/ui.h" - -#include -#include - -#include - -#include "common/transformations/orientation.hpp" -#include "common/swaglog.h" -#include "common/util.h" -#include "system/hardware/hw.h" - -#define BACKLIGHT_DT 0.05 -#define BACKLIGHT_TS 10.00 - -static void update_sockets(UIState *s) { - s->sm->update(0); -} - -static void update_state(UIState *s) { - SubMaster &sm = *(s->sm); - UIScene &scene = s->scene; - - if (sm.updated("liveCalibration")) { - auto list2rot = [](const capnp::List::Reader &rpy_list) ->Eigen::Matrix3f { - return euler2rot({rpy_list[0], rpy_list[1], rpy_list[2]}).cast(); - }; - - auto live_calib = sm["liveCalibration"].getLiveCalibration(); - if (live_calib.getCalStatus() == cereal::LiveCalibrationData::Status::CALIBRATED) { - auto device_from_calib = list2rot(live_calib.getRpyCalib()); - auto wide_from_device = list2rot(live_calib.getWideFromDeviceEuler()); - s->scene.view_from_calib = VIEW_FROM_DEVICE * device_from_calib; - s->scene.view_from_wide_calib = VIEW_FROM_DEVICE * wide_from_device * device_from_calib; - } else { - s->scene.view_from_calib = s->scene.view_from_wide_calib = VIEW_FROM_DEVICE; - } - } - if (sm.updated("pandaStates")) { - auto pandaStates = sm["pandaStates"].getPandaStates(); - if (pandaStates.size() > 0) { - scene.pandaType = pandaStates[0].getPandaType(); - - if (scene.pandaType != cereal::PandaState::PandaType::UNKNOWN) { - scene.ignition = false; - for (const auto& pandaState : pandaStates) { - scene.ignition |= pandaState.getIgnitionLine() || pandaState.getIgnitionCan(); - } - } - } - } else if ((s->sm->frame - s->sm->rcv_frame("pandaStates")) > 5*UI_FREQ) { - scene.pandaType = cereal::PandaState::PandaType::UNKNOWN; - } - if (sm.updated("wideRoadCameraState")) { - auto cam_state = sm["wideRoadCameraState"].getWideRoadCameraState(); - scene.light_sensor = std::max(100.0f - cam_state.getExposureValPercent(), 0.0f); - } else if (!sm.allAliveAndValid({"wideRoadCameraState"})) { - scene.light_sensor = -1; - } - scene.started = sm["deviceState"].getDeviceState().getStarted() && scene.ignition; - - auto params = Params(); - scene.recording_audio = params.getBool("RecordAudio") && scene.started; -} - -void ui_update_params(UIState *s) { - auto params = Params(); - s->scene.is_metric = params.getBool("IsMetric"); -} - -void UIState::updateStatus() { - if (scene.started && sm->updated("selfdriveState")) { - auto ss = (*sm)["selfdriveState"].getSelfdriveState(); - auto state = ss.getState(); - if (state == cereal::SelfdriveState::OpenpilotState::PRE_ENABLED || state == cereal::SelfdriveState::OpenpilotState::OVERRIDING) { - status = STATUS_OVERRIDE; - } else { - status = ss.getEnabled() ? STATUS_ENGAGED : STATUS_DISENGAGED; - } - } - - if (engaged() != engaged_prev) { - engaged_prev = engaged(); - emit engagedChanged(engaged()); - } - - // Handle onroad/offroad transition - if (scene.started != started_prev || sm->frame == 1) { - if (scene.started) { - status = STATUS_DISENGAGED; - scene.started_frame = sm->frame; - } - started_prev = scene.started; - emit offroadTransition(!scene.started); - } -} - -UIState::UIState(QObject *parent) : QObject(parent) { - sm = std::make_unique(std::vector{ - "modelV2", "controlsState", "liveCalibration", "radarState", "deviceState", - "pandaStates", "carParams", "driverMonitoringState", "carState", "driverStateV2", - "wideRoadCameraState", "managerState", "selfdriveState", "longitudinalPlan", - }); - prime_state = new PrimeState(this); - language = QString::fromStdString(Params().get("LanguageSetting")); - - // update timer - timer = new QTimer(this); - QObject::connect(timer, &QTimer::timeout, this, &UIState::update); - timer->start(1000 / UI_FREQ); -} - -void UIState::update() { - update_sockets(this); - update_state(this); - updateStatus(); - - emit uiUpdate(*this); -} - -Device::Device(QObject *parent) : brightness_filter(BACKLIGHT_OFFROAD, BACKLIGHT_TS, BACKLIGHT_DT), QObject(parent) { - setAwake(true); - resetInteractiveTimeout(); - - QObject::connect(uiState(), &UIState::uiUpdate, this, &Device::update); -} - -void Device::update(const UIState &s) { - updateBrightness(s); - updateWakefulness(s); -} - -void Device::setAwake(bool on) { - if (on != awake) { - awake = on; - Hardware::set_display_power(awake); - LOGD("setting display power %d", awake); - emit displayPowerChanged(awake); - } -} - -void Device::resetInteractiveTimeout(int timeout) { - if (timeout == -1) { - timeout = (ignition_on ? 10 : 30); - } - interactive_timeout = timeout * UI_FREQ; -} - -void Device::updateBrightness(const UIState &s) { - float clipped_brightness = offroad_brightness; - if (s.scene.started && s.scene.light_sensor >= 0) { - clipped_brightness = s.scene.light_sensor; - - // CIE 1931 - https://www.photonstophotos.net/GeneralTopics/Exposure/Psychometric_Lightness_and_Gamma.htm - if (clipped_brightness <= 8) { - clipped_brightness = (clipped_brightness / 903.3); - } else { - clipped_brightness = std::pow((clipped_brightness + 16.0) / 116.0, 3.0); - } - - // Scale back to 10% to 100% - clipped_brightness = std::clamp(100.0f * clipped_brightness, 10.0f, 100.0f); - } - - int brightness = brightness_filter.update(clipped_brightness); - if (!awake) { - brightness = 0; - } - - if (brightness != last_brightness) { - if (!brightness_future.isRunning()) { - brightness_future = QtConcurrent::run(Hardware::set_brightness, brightness); - last_brightness = brightness; - } - } -} - -void Device::updateWakefulness(const UIState &s) { - bool ignition_just_turned_off = !s.scene.ignition && ignition_on; - ignition_on = s.scene.ignition; - - if (ignition_just_turned_off) { - resetInteractiveTimeout(); - } else if (interactive_timeout > 0 && --interactive_timeout == 0) { - emit interactiveTimeout(); - } - - setAwake(s.scene.ignition || interactive_timeout > 0); -} - -UIState *uiState() { - static UIState ui_state; - return &ui_state; -} - -Device *device() { - static Device _device; - return &_device; -} diff --git a/selfdrive/ui/ui.h b/selfdrive/ui/ui.h deleted file mode 100644 index b3c482aaf..000000000 --- a/selfdrive/ui/ui.h +++ /dev/null @@ -1,132 +0,0 @@ -#pragma once - -#include -#include -#include - -#include -#include -#include - -#include "cereal/messaging/messaging.h" -#include "common/mat.h" -#include "common/params.h" -#include "common/util.h" -#include "system/hardware/hw.h" -#include "selfdrive/ui/qt/prime_state.h" - -const int UI_BORDER_SIZE = 30; -const int UI_HEADER_HEIGHT = 420; - -const int UI_FREQ = 20; // Hz -const int BACKLIGHT_OFFROAD = 50; - -const Eigen::Matrix3f VIEW_FROM_DEVICE = (Eigen::Matrix3f() << - 0.0, 1.0, 0.0, - 0.0, 0.0, 1.0, - 1.0, 0.0, 0.0).finished(); - -const Eigen::Matrix3f FCAM_INTRINSIC_MATRIX = (Eigen::Matrix3f() << - 2648.0, 0.0, 1928.0 / 2, - 0.0, 2648.0, 1208.0 / 2, - 0.0, 0.0, 1.0).finished(); - -// tici ecam focal probably wrong? magnification is not consistent across frame -// Need to retrain model before this can be changed -const Eigen::Matrix3f ECAM_INTRINSIC_MATRIX = (Eigen::Matrix3f() << - 567.0, 0.0, 1928.0 / 2, - 0.0, 567.0, 1208.0 / 2, - 0.0, 0.0, 1.0).finished(); - -typedef enum UIStatus { - STATUS_DISENGAGED, - STATUS_OVERRIDE, - STATUS_ENGAGED, -} UIStatus; - -const QColor bg_colors [] = { - [STATUS_DISENGAGED] = QColor(0x17, 0x33, 0x49, 0xc8), - [STATUS_OVERRIDE] = QColor(0x91, 0x9b, 0x95, 0xf1), - [STATUS_ENGAGED] = QColor(0x17, 0x86, 0x44, 0xf1), -}; - -typedef struct UIScene { - Eigen::Matrix3f view_from_calib = VIEW_FROM_DEVICE; - Eigen::Matrix3f view_from_wide_calib = VIEW_FROM_DEVICE; - cereal::PandaState::PandaType pandaType; - - cereal::LongitudinalPersonality personality; - - float light_sensor = -1; - bool started, ignition, is_metric, recording_audio; - uint64_t started_frame; -} UIScene; - -class UIState : public QObject { - Q_OBJECT - -public: - UIState(QObject* parent = 0); - void updateStatus(); - inline bool engaged() const { - return scene.started && (*sm)["selfdriveState"].getSelfdriveState().getEnabled(); - } - - std::unique_ptr sm; - UIStatus status; - UIScene scene = {}; - QString language; - PrimeState *prime_state; - -signals: - void uiUpdate(const UIState &s); - void offroadTransition(bool offroad); - void engagedChanged(bool engaged); - -private slots: - void update(); - -private: - QTimer *timer; - bool started_prev = false; - bool engaged_prev = false; -}; - -UIState *uiState(); - -// device management class -class Device : public QObject { - Q_OBJECT - -public: - Device(QObject *parent = 0); - bool isAwake() { return awake; } - void setOffroadBrightness(int brightness) { - offroad_brightness = std::clamp(brightness, 0, 100); - } - -private: - bool awake = false; - int interactive_timeout = 0; - bool ignition_on = false; - - int offroad_brightness = BACKLIGHT_OFFROAD; - int last_brightness = 0; - FirstOrderFilter brightness_filter; - QFuture brightness_future; - - void updateBrightness(const UIState &s); - void updateWakefulness(const UIState &s); - void setAwake(bool on); - -signals: - void displayPowerChanged(bool on); - void interactiveTimeout(); - -public slots: - void resetInteractiveTimeout(int timeout = -1); - void update(const UIState &s); -}; - -Device *device(); -void ui_update_params(UIState *s); diff --git a/selfdrive/ui/update_translations.py b/selfdrive/ui/update_translations.py index 643d24601..e91820f24 100755 --- a/selfdrive/ui/update_translations.py +++ b/selfdrive/ui/update_translations.py @@ -1,48 +1,38 @@ #!/usr/bin/env python3 -import argparse -import json +from itertools import chain import os - -from openpilot.common.basedir import BASEDIR -from openpilot.system.ui.lib.multilang import UI_DIR, TRANSLATIONS_DIR, LANGUAGES_FILE - -TRANSLATIONS_INCLUDE_FILE = os.path.join(TRANSLATIONS_DIR, "alerts_generated.h") -PLURAL_ONLY = ["en"] # base language, only create entries for strings with plural forms +from openpilot.system.ui.lib.multilang import SYSTEM_UI_DIR, UI_DIR, TRANSLATIONS_DIR, multilang -def generate_translations_include(): - # offroad alerts - # TODO translate events from openpilot.selfdrive/controls/lib/events.py - content = "// THIS IS AN AUTOGENERATED FILE, PLEASE EDIT alerts_offroad.json\n" - with open(os.path.join(BASEDIR, "selfdrive/selfdrived/alerts_offroad.json")) as f: - for alert in json.load(f).values(): - content += f'QT_TRANSLATE_NOOP("OffroadAlert", R"({alert["text"]})");\n' +def update_translations(): + files = [] + for root, _, filenames in chain(os.walk(SYSTEM_UI_DIR), + os.walk(os.path.join(UI_DIR, "widgets")), + os.walk(os.path.join(UI_DIR, "layouts")), + os.walk(os.path.join(UI_DIR, "onroad"))): + for filename in filenames: + if filename.endswith(".py"): + files.append(os.path.join(root, filename)) - with open(TRANSLATIONS_INCLUDE_FILE, "w") as f: - f.write(content) + # Create main translation file + cmd = ("xgettext -L Python --keyword=tr --keyword=trn:1,2 --keyword=tr_noop --from-code=UTF-8 " + + "--flag=tr:1:python-brace-format --flag=trn:1:python-brace-format --flag=trn:2:python-brace-format " + + "-o translations/app.pot {}").format(" ".join(files)) + ret = os.system(cmd) + assert ret == 0 -def update_translations(vanish: bool = False, translation_files: None | list[str] = None, translations_dir: str = TRANSLATIONS_DIR): - if translation_files is None: - with open(LANGUAGES_FILE) as f: - translation_files = json.load(f).values() - - for file in translation_files: - tr_file = os.path.join(translations_dir, f"{file}.ts") - args = f"lupdate -locations none -recursive {UI_DIR} -ts {tr_file} -I {BASEDIR}" - if vanish: - args += " -no-obsolete" - if file in PLURAL_ONLY: - args += " -pluralonly" - ret = os.system(args) - assert ret == 0 + # Generate/update translation files for each language + for name in multilang.languages.values(): + if os.path.exists(os.path.join(TRANSLATIONS_DIR, f"app_{name}.po")): + cmd = f"msgmerge --update --no-fuzzy-matching --backup=none --sort-output translations/app_{name}.po translations/app.pot" + ret = os.system(cmd) + assert ret == 0 + else: + cmd = f"msginit -l {name} --no-translator --input translations/app.pot --output-file translations/app_{name}.po" + ret = os.system(cmd) + assert ret == 0 if __name__ == "__main__": - parser = argparse.ArgumentParser(description="Update translation files for UI", - formatter_class=argparse.ArgumentDefaultsHelpFormatter) - parser.add_argument("--vanish", action="store_true", help="Remove translations with source text no longer found") - args = parser.parse_args() - - generate_translations_include() - update_translations(args.vanish) + update_translations() diff --git a/selfdrive/ui/update_translations_raylib.py b/selfdrive/ui/update_translations_raylib.py deleted file mode 100755 index e91820f24..000000000 --- a/selfdrive/ui/update_translations_raylib.py +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env python3 -from itertools import chain -import os -from openpilot.system.ui.lib.multilang import SYSTEM_UI_DIR, UI_DIR, TRANSLATIONS_DIR, multilang - - -def update_translations(): - files = [] - for root, _, filenames in chain(os.walk(SYSTEM_UI_DIR), - os.walk(os.path.join(UI_DIR, "widgets")), - os.walk(os.path.join(UI_DIR, "layouts")), - os.walk(os.path.join(UI_DIR, "onroad"))): - for filename in filenames: - if filename.endswith(".py"): - files.append(os.path.join(root, filename)) - - # Create main translation file - cmd = ("xgettext -L Python --keyword=tr --keyword=trn:1,2 --keyword=tr_noop --from-code=UTF-8 " + - "--flag=tr:1:python-brace-format --flag=trn:1:python-brace-format --flag=trn:2:python-brace-format " + - "-o translations/app.pot {}").format(" ".join(files)) - - ret = os.system(cmd) - assert ret == 0 - - # Generate/update translation files for each language - for name in multilang.languages.values(): - if os.path.exists(os.path.join(TRANSLATIONS_DIR, f"app_{name}.po")): - cmd = f"msgmerge --update --no-fuzzy-matching --backup=none --sort-output translations/app_{name}.po translations/app.pot" - ret = os.system(cmd) - assert ret == 0 - else: - cmd = f"msginit -l {name} --no-translator --input translations/app.pot --output-file translations/app_{name}.po" - ret = os.system(cmd) - assert ret == 0 - - -if __name__ == "__main__": - update_translations() diff --git a/system/manager/process_config.py b/system/manager/process_config.py index 5c02227ca..940e7f912 100644 --- a/system/manager/process_config.py +++ b/system/manager/process_config.py @@ -80,7 +80,6 @@ procs = [ PythonProcess("dmonitoringmodeld", "selfdrive.modeld.dmonitoringmodeld", driverview, enabled=(WEBCAM or not PC)), PythonProcess("sensord", "system.sensord.sensord", only_onroad, enabled=not PC), - # NativeProcess("ui", "selfdrive/ui", ["./ui"], always_run, enabled=False), PythonProcess("ui", "selfdrive.ui.ui", always_run), PythonProcess("soundd", "selfdrive.ui.soundd", only_onroad), PythonProcess("locationd", "selfdrive.locationd.locationd", only_onroad), diff --git a/third_party/qt5/larch64/bin/lrelease b/third_party/qt5/larch64/bin/lrelease deleted file mode 100755 index 5891f8585..000000000 --- a/third_party/qt5/larch64/bin/lrelease +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:56ddfc4ead01eaf43cba41e2095ec4f52309c53a60ba9e351d8dbd900151228f -size 546824 diff --git a/third_party/qt5/larch64/bin/lupdate b/third_party/qt5/larch64/bin/lupdate deleted file mode 100755 index 0055aae22..000000000 --- a/third_party/qt5/larch64/bin/lupdate +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7af679aa708031f6d19baabbd48e92d23462c6ff287caccdcb5c6324168d985d -size 1095744 diff --git a/tools/cabana/utils/api.cc b/tools/cabana/utils/api.cc index 99e904c49..2ed461fdf 100644 --- a/tools/cabana/utils/api.cc +++ b/tools/cabana/utils/api.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/qt/api.h" +#include "tools/cabana/utils/api.h" #include #include @@ -13,9 +13,10 @@ #include #include +#include "common/params.h" #include "common/util.h" #include "system/hardware/hw.h" -#include "selfdrive/ui/qt/util.h" +#include "tools/cabana/utils/util.h" QString getVersion() { static QString version = QString::fromStdString(Params().get("Version")); From 4861d150566bf58d1ce205ba20ce575596226178 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 23 Oct 2025 00:45:51 -0700 Subject: [PATCH 241/910] reduce ui scons imports --- selfdrive/ui/SConscript | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index a9b40f0d2..37bf4974d 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -1,9 +1,6 @@ import os import json -Import('env', 'arch', 'common', 'messaging', 'visionipc', 'transformations') - -raylib_env = env.Clone() - +Import('env', 'arch', 'common') # compile gettext .po -> .mo translations with open(File("translations/languages.json").abspath) as f: @@ -14,13 +11,14 @@ po_sources = [src for src in po_sources if os.path.exists(File(src).abspath)] mo_targets = [src.replace(".po", ".mo") for src in po_sources] mo_build = [] for src, tgt in zip(po_sources, mo_targets): - mo_build.append(raylib_env.Command(tgt, src, "msgfmt -o $TARGET $SOURCE")) -mo_alias = raylib_env.Alias('mo', mo_build) -raylib_env.AlwaysBuild(mo_alias) + mo_build.append(env.Command(tgt, src, "msgfmt -o $TARGET $SOURCE")) +mo_alias = env.Alias('mo', mo_build) +env.AlwaysBuild(mo_alias) if GetOption('extras'): # build installers if arch != "Darwin": + raylib_env = env.Clone() raylib_env['LIBPATH'] += [f'#third_party/raylib/{arch}/'] raylib_env['LINKFLAGS'].append('-Wl,-strip-debug') From 485c7b2725081f9375b789c6b617458fd86d681d Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 23 Oct 2025 00:54:31 -0700 Subject: [PATCH 242/910] multilib: relative paths (#36439) * relative * clean up --- selfdrive/ui/translations/app.pot | 473 ++++++++++++------------ selfdrive/ui/translations/app_ar.po | 473 ++++++++++++------------ selfdrive/ui/translations/app_de.po | 473 ++++++++++++------------ selfdrive/ui/translations/app_en.po | 473 ++++++++++++------------ selfdrive/ui/translations/app_es.po | 473 ++++++++++++------------ selfdrive/ui/translations/app_fr.po | 473 ++++++++++++------------ selfdrive/ui/translations/app_ja.po | 473 ++++++++++++------------ selfdrive/ui/translations/app_ko.po | 473 ++++++++++++------------ selfdrive/ui/translations/app_pt-BR.po | 473 ++++++++++++------------ selfdrive/ui/translations/app_th.po | 473 ++++++++++++------------ selfdrive/ui/translations/app_tr.po | 473 ++++++++++++------------ selfdrive/ui/translations/app_zh-CHS.po | 473 ++++++++++++------------ selfdrive/ui/translations/app_zh-CHT.po | 473 ++++++++++++------------ selfdrive/ui/update_translations.py | 11 +- 14 files changed, 2958 insertions(+), 3202 deletions(-) diff --git a/selfdrive/ui/translations/app.pot b/selfdrive/ui/translations/app.pot index e6a0e880d..abb6940a5 100644 --- a/selfdrive/ui/translations/app.pot +++ b/selfdrive/ui/translations/app.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-22 19:09-0700\n" +"POT-Creation-Date: 2025-10-23 00:51-0700\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -18,488 +18,469 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" -#: /home/batman/openpilot/system/ui/widgets/html_render.py:263 -#: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:93 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: system/ui/widgets/html_render.py:263 system/ui/widgets/confirm_dialog.py:93 +#: selfdrive/ui/layouts/sidebar.py:127 #, python-format msgid "OK" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:23 -#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:35 -#: /home/batman/openpilot/system/ui/widgets/keyboard.py:81 -#: /home/batman/openpilot/system/ui/widgets/network.py:318 +#: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35 +#: system/ui/widgets/keyboard.py:81 system/ui/widgets/network.py:318 #, python-format msgid "Cancel" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/option_dialog.py:36 #, python-format msgid "Select" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:74 -#: /home/batman/openpilot/system/ui/widgets/network.py:95 +#: system/ui/widgets/network.py:74 system/ui/widgets/network.py:95 #, python-format msgid "Advanced" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:99 -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:147 +#: system/ui/widgets/network.py:99 selfdrive/ui/layouts/onboarding.py:147 #, python-format msgid "Back" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:120 #, python-format msgid "Enable Tethering" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:123 -#: /home/batman/openpilot/system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:123 system/ui/widgets/network.py:139 #, python-format msgid "EDIT" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:124 +#: system/ui/widgets/network.py:124 #, python-format msgid "Tethering Password" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:129 +#: system/ui/widgets/network.py:129 #, python-format msgid "Enable Roaming" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:134 +#: system/ui/widgets/network.py:134 #, python-format msgid "Cellular Metered" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:135 +#: system/ui/widgets/network.py:135 #, python-format msgid "Prevent large data uploads when on a metered cellular connection" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:139 #, python-format msgid "APN Setting" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:142 #, python-format msgid "default" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:142 #, python-format msgid "metered" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:142 #, python-format msgid "unmetered" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:144 #, python-format msgid "Wi-Fi Network Metered" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:144 #, python-format msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:150 +#: system/ui/widgets/network.py:150 #, python-format msgid "IP Address" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:155 +#: system/ui/widgets/network.py:155 #, python-format msgid "Hidden Network" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:155 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:73 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:134 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:136 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:138 +#: system/ui/widgets/network.py:155 selfdrive/ui/layouts/sidebar.py:73 +#: selfdrive/ui/layouts/sidebar.py:134 selfdrive/ui/layouts/sidebar.py:136 +#: selfdrive/ui/layouts/sidebar.py:138 #, python-format msgid "CONNECT" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:204 #, python-format msgid "Enter APN" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:204 #, python-format msgid "leave blank for automatic configuration" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:237 -#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 #, python-format msgid "Enter password" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:237 -#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 #, python-format msgid "for \"{}\"" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:241 +#: system/ui/widgets/network.py:241 #, python-format msgid "Enter SSID" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:254 +#: system/ui/widgets/network.py:254 #, python-format msgid "Enter new tethering password" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:310 +#: system/ui/widgets/network.py:310 #, python-format msgid "Scanning Wi-Fi networks..." msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:314 #, python-format msgid "Wrong password" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:318 -#: /home/batman/openpilot/system/ui/widgets/network.py:451 +#: system/ui/widgets/network.py:318 system/ui/widgets/network.py:451 #, python-format msgid "Forget" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:319 +#: system/ui/widgets/network.py:319 #, python-format msgid "Forget Wi-Fi Network \"{}\"?" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:369 +#: system/ui/widgets/network.py:369 #, python-format msgid "CONNECTING..." msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:373 +#: system/ui/widgets/network.py:373 #, python-format msgid "FORGETTING..." msgstr "" -#: /home/batman/openpilot/system/ui/widgets/list_view.py:123 -#: /home/batman/openpilot/system/ui/widgets/list_view.py:160 +#: system/ui/widgets/list_view.py:123 system/ui/widgets/list_view.py:160 #, python-format msgid "Error" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:103 +#: selfdrive/ui/widgets/pairing_dialog.py:103 #, python-format msgid "Pair your device to your comma account" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:128 +#: selfdrive/ui/widgets/pairing_dialog.py:128 #, python-format msgid "Go to https://connect.comma.ai on your phone" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:129 +#: selfdrive/ui/widgets/pairing_dialog.py:129 #, python-format msgid "Click \"add new device\" and scan the QR code on the right" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:130 +#: selfdrive/ui/widgets/pairing_dialog.py:130 #, python-format msgid "Bookmark connect.comma.ai to your home screen to use it like an app" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:161 +#: selfdrive/ui/widgets/pairing_dialog.py:161 #, python-format msgid "QR Code Error" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:29 +#: selfdrive/ui/widgets/ssh_key.py:29 msgid "LOADING" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:30 +#: selfdrive/ui/widgets/ssh_key.py:30 msgid "ADD" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:31 +#: selfdrive/ui/widgets/ssh_key.py:31 msgid "REMOVE" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:89 +#: selfdrive/ui/widgets/ssh_key.py:89 #, python-format msgid "Enter your GitHub username" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:114 +#: selfdrive/ui/widgets/ssh_key.py:114 #, python-format msgid "No SSH keys found" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:123 +#: selfdrive/ui/widgets/ssh_key.py:123 #, python-format msgid "Request timed out" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:126 +#: selfdrive/ui/widgets/ssh_key.py:126 #, python-format msgid "No SSH keys found for user '{}'" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:33 +#: selfdrive/ui/widgets/prime.py:33 #, python-format msgid "Upgrade Now" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:38 +#: selfdrive/ui/widgets/prime.py:38 #, python-format msgid "Become a comma prime member at connect.comma.ai" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:44 +#: selfdrive/ui/widgets/prime.py:44 #, python-format msgid "PRIME FEATURES:" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#: selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote access" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#: selfdrive/ui/widgets/prime.py:47 #, python-format msgid "24/7 LTE connectivity" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#: selfdrive/ui/widgets/prime.py:47 #, python-format msgid "1 year of drive storage" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#: selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote snapshots" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:62 +#: selfdrive/ui/widgets/prime.py:62 #, python-format msgid "✓ SUBSCRIBED" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:63 +#: selfdrive/ui/widgets/prime.py:63 #, python-format msgid "comma prime" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/exp_mode_button.py:50 +#: selfdrive/ui/widgets/exp_mode_button.py:50 #, python-format msgid "EXPERIMENTAL MODE ON" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/exp_mode_button.py:50 +#: selfdrive/ui/widgets/exp_mode_button.py:50 #, python-format msgid "CHILL MODE ON" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 +#: selfdrive/ui/widgets/offroad_alerts.py:104 #, python-format msgid "Close" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 +#: selfdrive/ui/widgets/offroad_alerts.py:106 #, python-format msgid "Snooze Update" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 +#: selfdrive/ui/widgets/offroad_alerts.py:109 #, python-format msgid "Acknowledge Excessive Actuation" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 +#: selfdrive/ui/widgets/offroad_alerts.py:112 #, python-format msgid "Reboot and Update" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:320 +#: selfdrive/ui/widgets/offroad_alerts.py:320 #, python-format msgid "No release notes available." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:19 +#: selfdrive/ui/widgets/setup.py:19 #, python-format msgid "Pair device" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:20 +#: selfdrive/ui/widgets/setup.py:20 #, python-format msgid "Open" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:22 +#: selfdrive/ui/widgets/setup.py:22 #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:44 +#: selfdrive/ui/widgets/setup.py:44 #, python-format msgid "Finish Setup" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:48 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:24 +#: selfdrive/ui/widgets/setup.py:48 selfdrive/ui/layouts/settings/device.py:24 #, python-format msgid "" "Pair your device with comma connect (connect.comma.ai) and claim your comma " "prime offer." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:75 +#: selfdrive/ui/widgets/setup.py:75 #, python-format msgid "" "Maximize your training data uploads to improve openpilot's driving models." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:91 +#: selfdrive/ui/widgets/setup.py:91 #, python-format msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/home.py:155 +#: selfdrive/ui/layouts/home.py:155 #, python-format msgid "UPDATE" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/home.py:169 +#: selfdrive/ui/layouts/home.py:169 #, python-format msgid "{} ALERT" msgid_plural "{} ALERTS" msgstr[0] "" msgstr[1] "" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:43 +#: selfdrive/ui/layouts/sidebar.py:43 msgid "--" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:44 +#: selfdrive/ui/layouts/sidebar.py:44 msgid "Wi-Fi" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:45 +#: selfdrive/ui/layouts/sidebar.py:45 msgid "ETH" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:46 +#: selfdrive/ui/layouts/sidebar.py:46 msgid "2G" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:47 +#: selfdrive/ui/layouts/sidebar.py:47 msgid "3G" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:48 +#: selfdrive/ui/layouts/sidebar.py:48 msgid "LTE" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:49 +#: selfdrive/ui/layouts/sidebar.py:49 msgid "5G" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:71 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:125 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:127 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:129 +#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 +#: selfdrive/ui/layouts/sidebar.py:127 selfdrive/ui/layouts/sidebar.py:129 msgid "TEMP" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:71 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 msgid "GOOD" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:72 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:144 msgid "VEHICLE" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:72 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:136 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:136 +#: selfdrive/ui/layouts/sidebar.py:144 msgid "ONLINE" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:73 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: selfdrive/ui/layouts/sidebar.py:73 selfdrive/ui/layouts/sidebar.py:134 msgid "OFFLINE" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:117 +#: selfdrive/ui/layouts/sidebar.py:117 msgid "Unknown" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:129 +#: selfdrive/ui/layouts/sidebar.py:129 msgid "HIGH" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:138 +#: selfdrive/ui/layouts/sidebar.py:138 msgid "ERROR" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:142 +#: selfdrive/ui/layouts/sidebar.py:142 msgid "NO" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:142 +#: selfdrive/ui/layouts/sidebar.py:142 msgid "PANDA" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:111 +#: selfdrive/ui/layouts/onboarding.py:111 #, python-format msgid "Welcome to openpilot" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:112 +#: selfdrive/ui/layouts/onboarding.py:112 #, python-format msgid "" "You must accept the Terms and Conditions to use openpilot. Read the latest " "terms at https://comma.ai/terms before continuing." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:115 +#: selfdrive/ui/layouts/onboarding.py:115 #, python-format msgid "Decline" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:116 +#: selfdrive/ui/layouts/onboarding.py:116 #, python-format msgid "Agree" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:145 +#: selfdrive/ui/layouts/onboarding.py:145 #, python-format msgid "You must accept the Terms and Conditions in order to use openpilot." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:148 +#: selfdrive/ui/layouts/onboarding.py:148 #, python-format msgid "Decline, uninstall openpilot" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:18 +#: selfdrive/ui/layouts/settings/firehose.py:18 msgid "Firehose Mode" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:20 +#: selfdrive/ui/layouts/settings/firehose.py:20 msgid "" "openpilot learns to drive by watching humans, like you, drive.\n" "\n" @@ -508,7 +489,7 @@ msgid "" "better Experimental Mode." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:25 +#: selfdrive/ui/layouts/settings/firehose.py:25 msgid "" "For maximum effectiveness, bring your device inside and connect to a good " "USB-C adapter and Wi-Fi weekly.\n" @@ -532,37 +513,37 @@ msgid "" "particular forks) are able to be used for training." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:111 +#: selfdrive/ui/layouts/settings/firehose.py:111 #, python-format msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." msgstr[0] "" msgstr[1] "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:138 +#: selfdrive/ui/layouts/settings/firehose.py:138 #, python-format msgid "ACTIVE" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:140 +#: selfdrive/ui/layouts/settings/firehose.py:140 #, python-format msgid "INACTIVE: connect to an unmetered network" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:15 +#: selfdrive/ui/layouts/settings/developer.py:15 msgid "" "ADB (Android Debug Bridge) allows connecting to your device over USB or over " "the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:19 +#: selfdrive/ui/layouts/settings/developer.py:19 msgid "" "Warning: This grants SSH access to all public keys in your GitHub settings. " "Never enter a GitHub username other than your own. A comma employee will " "NEVER ask you to add their GitHub username." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:23 +#: selfdrive/ui/layouts/settings/developer.py:23 msgid "" "WARNING: openpilot longitudinal control is in alpha for this car and will " "disable Automatic Emergency Braking (AEB).

On this car, openpilot " @@ -573,394 +554,394 @@ msgid "" "powered on." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:39 +#: selfdrive/ui/layouts/settings/developer.py:39 #, python-format msgid "Enable ADB" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:48 +#: selfdrive/ui/layouts/settings/developer.py:48 #, python-format msgid "Enable SSH" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:53 +#: selfdrive/ui/layouts/settings/developer.py:53 #, python-format msgid "SSH Keys" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:56 +#: selfdrive/ui/layouts/settings/developer.py:56 #, python-format msgid "Joystick Debug Mode" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:64 +#: selfdrive/ui/layouts/settings/developer.py:64 #, python-format msgid "Longitudinal Maneuver Mode" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:71 +#: selfdrive/ui/layouts/settings/developer.py:71 #, python-format msgid "openpilot Longitudinal Control (Alpha)" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:166 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:228 +#: selfdrive/ui/layouts/settings/developer.py:166 +#: selfdrive/ui/layouts/settings/toggles.py:228 #, python-format msgid "Enable" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:20 +#: selfdrive/ui/layouts/settings/software.py:20 #, python-format msgid "never" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:31 +#: selfdrive/ui/layouts/settings/software.py:31 #, python-format msgid "now" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:34 +#: selfdrive/ui/layouts/settings/software.py:34 #, python-format msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "" msgstr[1] "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:37 +#: selfdrive/ui/layouts/settings/software.py:37 #, python-format msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "" msgstr[1] "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:40 +#: selfdrive/ui/layouts/settings/software.py:40 #, python-format msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "" msgstr[1] "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:48 +#: selfdrive/ui/layouts/settings/software.py:48 #, python-format msgid "Updates are only downloaded while the car is off." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:49 +#: selfdrive/ui/layouts/settings/software.py:49 #, python-format msgid "Current Version" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:50 +#: selfdrive/ui/layouts/settings/software.py:50 #, python-format msgid "Download" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:50 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:107 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:118 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:147 +#: selfdrive/ui/layouts/settings/software.py:50 +#: selfdrive/ui/layouts/settings/software.py:107 +#: selfdrive/ui/layouts/settings/software.py:118 +#: selfdrive/ui/layouts/settings/software.py:147 #, python-format msgid "CHECK" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:53 +#: selfdrive/ui/layouts/settings/software.py:53 #, python-format msgid "Install Update" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:53 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:136 +#: selfdrive/ui/layouts/settings/software.py:53 +#: selfdrive/ui/layouts/settings/software.py:136 #, python-format msgid "INSTALL" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:61 +#: selfdrive/ui/layouts/settings/software.py:61 #, python-format msgid "Target Branch" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:61 +#: selfdrive/ui/layouts/settings/software.py:61 #, python-format msgid "SELECT" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:72 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:163 +#: selfdrive/ui/layouts/settings/software.py:72 +#: selfdrive/ui/layouts/settings/software.py:163 #, python-format msgid "Uninstall" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:72 +#: selfdrive/ui/layouts/settings/software.py:72 #, python-format msgid "UNINSTALL" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:106 +#: selfdrive/ui/layouts/settings/software.py:106 #, python-format msgid "failed to check for update" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:109 +#: selfdrive/ui/layouts/settings/software.py:109 #, python-format msgid "update available" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:110 +#: selfdrive/ui/layouts/settings/software.py:110 #, python-format msgid "DOWNLOAD" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:115 +#: selfdrive/ui/layouts/settings/software.py:115 #, python-format msgid "up to date, last checked {}" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:117 +#: selfdrive/ui/layouts/settings/software.py:117 #, python-format msgid "up to date, last checked never" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:163 +#: selfdrive/ui/layouts/settings/software.py:163 #, python-format msgid "Are you sure you want to uninstall?" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:183 +#: selfdrive/ui/layouts/settings/software.py:183 #, python-format msgid "Select a branch" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:25 +#: selfdrive/ui/layouts/settings/device.py:25 msgid "" "Preview the driver facing camera to ensure that driver monitoring has good " "visibility. (vehicle must be off)" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:26 +#: selfdrive/ui/layouts/settings/device.py:26 msgid "" "openpilot requires the device to be mounted within 4° left or right and " "within 5° up or 9° down." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:27 +#: selfdrive/ui/layouts/settings/device.py:27 msgid "Review the rules, features, and limitations of openpilot" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:48 +#: selfdrive/ui/layouts/settings/device.py:48 #, python-format msgid "Pair Device" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:48 +#: selfdrive/ui/layouts/settings/device.py:48 #, python-format msgid "PAIR" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:51 +#: selfdrive/ui/layouts/settings/device.py:51 #, python-format msgid "Reset Calibration" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:51 +#: selfdrive/ui/layouts/settings/device.py:51 #, python-format msgid "RESET" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:175 +#: selfdrive/ui/layouts/settings/device.py:55 +#: selfdrive/ui/layouts/settings/device.py:175 #, python-format msgid "Reboot" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:187 +#: selfdrive/ui/layouts/settings/device.py:55 +#: selfdrive/ui/layouts/settings/device.py:187 #, python-format msgid "Power Off" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:59 +#: selfdrive/ui/layouts/settings/device.py:59 #, python-format msgid "Dongle ID" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:59 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:60 +#: selfdrive/ui/layouts/settings/device.py:59 +#: selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "N/A" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:60 +#: selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "Serial" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:62 +#: selfdrive/ui/layouts/settings/device.py:62 #, python-format msgid "Driver Camera" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:62 +#: selfdrive/ui/layouts/settings/device.py:62 #, python-format msgid "PREVIEW" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:65 +#: selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "Review Training Guide" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:65 +#: selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "REVIEW" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:67 +#: selfdrive/ui/layouts/settings/device.py:67 #, python-format msgid "Regulatory" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:67 +#: selfdrive/ui/layouts/settings/device.py:67 #, python-format msgid "VIEW" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:68 +#: selfdrive/ui/layouts/settings/device.py:68 #, python-format msgid "Change Language" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:68 +#: selfdrive/ui/layouts/settings/device.py:68 #, python-format msgid "CHANGE" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:91 +#: selfdrive/ui/layouts/settings/device.py:91 #, python-format msgid "Select a language" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:103 +#: selfdrive/ui/layouts/settings/device.py:103 #, python-format msgid "Disengage to Reset Calibration" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:119 +#: selfdrive/ui/layouts/settings/device.py:119 #, python-format msgid "Are you sure you want to reset calibration?" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:119 +#: selfdrive/ui/layouts/settings/device.py:119 #, python-format msgid "Reset" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#: selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#: selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid "down" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#: selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid "up" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:134 +#: selfdrive/ui/layouts/settings/device.py:134 #, python-format msgid "left" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:134 +#: selfdrive/ui/layouts/settings/device.py:134 #, python-format msgid "right" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:146 +#: selfdrive/ui/layouts/settings/device.py:146 #, python-format msgid "

Steering lag calibration is {}% complete." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:148 +#: selfdrive/ui/layouts/settings/device.py:148 #, python-format msgid "

Steering lag calibration is complete." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:158 +#: selfdrive/ui/layouts/settings/device.py:158 #, python-format msgid " Steering torque response calibration is {}% complete." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:160 +#: selfdrive/ui/layouts/settings/device.py:160 #, python-format msgid " Steering torque response calibration is complete." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:165 +#: selfdrive/ui/layouts/settings/device.py:165 #, python-format msgid "" "openpilot is continuously calibrating, resetting is rarely required. " "Resetting calibration will restart openpilot if the car is powered on." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:172 +#: selfdrive/ui/layouts/settings/device.py:172 #, python-format msgid "Disengage to Reboot" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:175 +#: selfdrive/ui/layouts/settings/device.py:175 #, python-format msgid "Are you sure you want to reboot?" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:184 +#: selfdrive/ui/layouts/settings/device.py:184 #, python-format msgid "Disengage to Power Off" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:187 +#: selfdrive/ui/layouts/settings/device.py:187 #, python-format msgid "Are you sure you want to power off?" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:62 +#: selfdrive/ui/layouts/settings/settings.py:62 msgid "Device" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:63 +#: selfdrive/ui/layouts/settings/settings.py:63 msgid "Network" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:64 +#: selfdrive/ui/layouts/settings/settings.py:64 msgid "Toggles" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:65 +#: selfdrive/ui/layouts/settings/settings.py:65 msgid "Software" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:66 +#: selfdrive/ui/layouts/settings/settings.py:66 msgid "Firehose" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:67 +#: selfdrive/ui/layouts/settings/settings.py:67 msgid "Developer" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:17 +#: selfdrive/ui/layouts/settings/toggles.py:17 msgid "" "Use the openpilot system for adaptive cruise control and lane keep driver " "assistance. Your attention is required at all times to use this feature." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:20 +#: selfdrive/ui/layouts/settings/toggles.py:20 msgid "When enabled, pressing the accelerator pedal will disengage openpilot." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:22 +#: selfdrive/ui/layouts/settings/toggles.py:22 msgid "" "Standard is recommended. In aggressive mode, openpilot will follow lead cars " "closer and be more aggressive with the gas and brake. In relaxed mode " @@ -968,99 +949,99 @@ msgid "" "cycle through these personalities with your steering wheel distance button." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:27 +#: selfdrive/ui/layouts/settings/toggles.py:27 msgid "" "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)." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:30 +#: selfdrive/ui/layouts/settings/toggles.py:30 msgid "Enable driver monitoring even when openpilot is not engaged." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +#: selfdrive/ui/layouts/settings/toggles.py:31 msgid "" "Upload data from the driver facing camera and help improve the driver " "monitoring algorithm." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:32 +#: selfdrive/ui/layouts/settings/toggles.py:32 msgid "Display speed in km/h instead of mph." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +#: selfdrive/ui/layouts/settings/toggles.py:33 msgid "" "Record and store microphone audio while driving. The audio will be included " "in the dashcam video in comma connect." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:46 +#: selfdrive/ui/layouts/settings/toggles.py:46 #, python-format msgid "Enable openpilot" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:52 +#: selfdrive/ui/layouts/settings/toggles.py:52 #, python-format msgid "Experimental Mode" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:58 +#: selfdrive/ui/layouts/settings/toggles.py:58 #, python-format msgid "Disengage on Accelerator Pedal" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:64 +#: selfdrive/ui/layouts/settings/toggles.py:64 #, python-format msgid "Enable Lane Departure Warnings" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:70 +#: selfdrive/ui/layouts/settings/toggles.py:70 #, python-format msgid "Always-On Driver Monitoring" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:76 +#: selfdrive/ui/layouts/settings/toggles.py:76 #, python-format msgid "Record and Upload Driver Camera" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:82 +#: selfdrive/ui/layouts/settings/toggles.py:82 #, python-format msgid "Record and Upload Microphone Audio" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:88 +#: selfdrive/ui/layouts/settings/toggles.py:88 #, python-format msgid "Use Metric System" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:96 +#: selfdrive/ui/layouts/settings/toggles.py:96 #, python-format msgid "Driving Personality" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#: selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Aggressive" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#: selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Standard" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#: selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Relaxed" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:125 +#: selfdrive/ui/layouts/settings/toggles.py:125 #, python-format msgid "Changing this setting will restart openpilot if the car is powered on." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:158 +#: selfdrive/ui/layouts/settings/toggles.py:158 #, python-format msgid "" "openpilot defaults to driving in chill mode. Experimental mode enables alpha-" @@ -1076,74 +1057,74 @@ msgid "" "corner." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:181 +#: selfdrive/ui/layouts/settings/toggles.py:181 #, python-format msgid "" "Experimental mode is currently unavailable on this car since the car's stock " "ACC is used for longitudinal control." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:183 +#: selfdrive/ui/layouts/settings/toggles.py:183 #, python-format msgid "openpilot longitudinal control may come in a future update." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:186 +#: selfdrive/ui/layouts/settings/toggles.py:186 #, python-format msgid "" "An alpha version of openpilot longitudinal control can be tested, along with " "Experimental mode, on non-release branches." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:189 +#: selfdrive/ui/layouts/settings/toggles.py:189 #, python-format msgid "" "Enable the openpilot longitudinal control (alpha) toggle to allow " "Experimental mode." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:148 +#: selfdrive/ui/onroad/hud_renderer.py:148 #, python-format msgid "MAX" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:177 +#: selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "km/h" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:177 +#: selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "mph" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:34 +#: selfdrive/ui/onroad/driver_camera_dialog.py:34 #, python-format msgid "camera starting" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:51 +#: selfdrive/ui/onroad/alert_renderer.py:51 #, python-format msgid "openpilot Unavailable" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:52 +#: selfdrive/ui/onroad/alert_renderer.py:52 #, python-format msgid "Waiting to start" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:58 +#: selfdrive/ui/onroad/alert_renderer.py:58 #, python-format msgid "TAKE CONTROL IMMEDIATELY" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:59 -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:65 +#: selfdrive/ui/onroad/alert_renderer.py:59 +#: selfdrive/ui/onroad/alert_renderer.py:65 #, python-format msgid "System Unresponsive" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:66 +#: selfdrive/ui/onroad/alert_renderer.py:66 #, python-format msgid "Reboot Device" msgstr "" diff --git a/selfdrive/ui/translations/app_ar.po b/selfdrive/ui/translations/app_ar.po index 5860c94dd..608389fc0 100644 --- a/selfdrive/ui/translations/app_ar.po +++ b/selfdrive/ui/translations/app_ar.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-22 18:57-0700\n" +"POT-Creation-Date: 2025-10-23 00:50-0700\n" "PO-Revision-Date: 2025-10-22 16:32-0700\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" @@ -18,48 +18,48 @@ msgstr "" "Plural-Forms: nplurals=6; plural=n==0?0:n==1?1:n==2?2:(n%100>=3 && " "n%100<=10)?3:(n%100>=11 && n%100<=99)?4:5;\n" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:160 +#: selfdrive/ui/layouts/settings/device.py:160 #, python-format msgid " Steering torque response calibration is complete." msgstr " اكتملت معايرة استجابة عزم التوجيه." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:158 +#: selfdrive/ui/layouts/settings/device.py:158 #, python-format msgid " Steering torque response calibration is {}% complete." msgstr " اكتملت معايرة استجابة عزم التوجيه بنسبة {}٪." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#: selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr " جهازك موجه بمقدار {:.1f}° {} و {:.1f}° {}." -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:43 +#: selfdrive/ui/layouts/sidebar.py:43 msgid "--" msgstr "--" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#: selfdrive/ui/widgets/prime.py:47 #, python-format msgid "1 year of drive storage" msgstr "سنة واحدة من تخزين القيادة" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#: selfdrive/ui/widgets/prime.py:47 #, python-format msgid "24/7 LTE connectivity" msgstr "اتصال LTE على مدار الساعة" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:46 +#: selfdrive/ui/layouts/sidebar.py:46 msgid "2G" msgstr "2G" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:47 +#: selfdrive/ui/layouts/sidebar.py:47 msgid "3G" msgstr "3G" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:49 +#: selfdrive/ui/layouts/sidebar.py:49 msgid "5G" msgstr "5G" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:23 +#: selfdrive/ui/layouts/settings/developer.py:23 msgid "" "WARNING: openpilot longitudinal control is in alpha for this car and will " "disable Automatic Emergency Braking (AEB).

On this car, openpilot " @@ -76,22 +76,22 @@ msgstr "" "التجربة عند تفعيل نسخة ألفا من التحكم الطولي. تغيير هذا الإعداد سيعيد تشغيل " "openpilot إذا كانت السيارة قيد التشغيل." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:148 +#: selfdrive/ui/layouts/settings/device.py:148 #, python-format msgid "

Steering lag calibration is complete." msgstr "

اكتملت معايرة تأخر التوجيه." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:146 +#: selfdrive/ui/layouts/settings/device.py:146 #, python-format msgid "

Steering lag calibration is {}% complete." msgstr "

اكتملت معايرة تأخر التوجيه بنسبة {}٪." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:138 +#: selfdrive/ui/layouts/settings/firehose.py:138 #, python-format msgid "ACTIVE" msgstr "نشط" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:15 +#: selfdrive/ui/layouts/settings/developer.py:15 msgid "" "ADB (Android Debug Bridge) allows connecting to your device over USB or over " "the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." @@ -99,42 +99,41 @@ msgstr "" "يتيح ADB (Android Debug Bridge) الاتصال بجهازك عبر USB أو عبر الشبكة. راجع " "https://docs.comma.ai/how-to/connect-to-comma لمزيد من المعلومات." -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:30 +#: selfdrive/ui/widgets/ssh_key.py:30 msgid "ADD" msgstr "إضافة" -#: /home/batman/openpilot/system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:139 #, python-format msgid "APN Setting" msgstr "إعداد APN" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 +#: selfdrive/ui/widgets/offroad_alerts.py:109 #, python-format msgid "Acknowledge Excessive Actuation" msgstr "تأكيد التشغيل المفرط" -#: /home/batman/openpilot/system/ui/widgets/network.py:74 -#: /home/batman/openpilot/system/ui/widgets/network.py:95 +#: system/ui/widgets/network.py:74 system/ui/widgets/network.py:95 #, python-format msgid "Advanced" msgstr "متقدم" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#: selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Aggressive" msgstr "عدواني" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:116 +#: selfdrive/ui/layouts/onboarding.py:116 #, python-format msgid "Agree" msgstr "موافقة" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:70 +#: selfdrive/ui/layouts/settings/toggles.py:70 #, python-format msgid "Always-On Driver Monitoring" msgstr "مراقبة السائق دائماً" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:186 +#: selfdrive/ui/layouts/settings/toggles.py:186 #, python-format msgid "" "An alpha version of openpilot longitudinal control can be tested, along with " @@ -143,285 +142,277 @@ msgstr "" "يمكن اختبار نسخة ألفا من التحكم الطولي لـ openpilot، مع وضع التجربة، على " "الفروع غير الإصدارية." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:187 +#: selfdrive/ui/layouts/settings/device.py:187 #, python-format msgid "Are you sure you want to power off?" msgstr "هل أنت متأكد أنك تريد إيقاف التشغيل؟" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:175 +#: selfdrive/ui/layouts/settings/device.py:175 #, python-format msgid "Are you sure you want to reboot?" msgstr "هل أنت متأكد أنك تريد إعادة التشغيل؟" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:119 +#: selfdrive/ui/layouts/settings/device.py:119 #, python-format msgid "Are you sure you want to reset calibration?" msgstr "هل أنت متأكد أنك تريد إعادة ضبط المعايرة؟" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:163 +#: selfdrive/ui/layouts/settings/software.py:163 #, python-format msgid "Are you sure you want to uninstall?" msgstr "هل أنت متأكد أنك تريد إلغاء التثبيت؟" -#: /home/batman/openpilot/system/ui/widgets/network.py:99 -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:147 +#: system/ui/widgets/network.py:99 selfdrive/ui/layouts/onboarding.py:147 #, python-format msgid "Back" msgstr "رجوع" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:38 +#: selfdrive/ui/widgets/prime.py:38 #, python-format msgid "Become a comma prime member at connect.comma.ai" msgstr "انضم إلى comma prime عبر connect.comma.ai" -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:130 +#: selfdrive/ui/widgets/pairing_dialog.py:130 #, python-format msgid "Bookmark connect.comma.ai to your home screen to use it like an app" msgstr "ثبّت connect.comma.ai على شاشتك الرئيسية لاستخدامه كتطبيق" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:68 +#: selfdrive/ui/layouts/settings/device.py:68 #, python-format msgid "CHANGE" msgstr "تغيير" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:50 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:107 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:118 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:147 +#: selfdrive/ui/layouts/settings/software.py:50 +#: selfdrive/ui/layouts/settings/software.py:107 +#: selfdrive/ui/layouts/settings/software.py:118 +#: selfdrive/ui/layouts/settings/software.py:147 #, python-format msgid "CHECK" msgstr "تحقق" -#: /home/batman/openpilot/selfdrive/ui/widgets/exp_mode_button.py:50 +#: selfdrive/ui/widgets/exp_mode_button.py:50 #, python-format msgid "CHILL MODE ON" msgstr "وضع الهدوء مُفعل" -#: /home/batman/openpilot/system/ui/widgets/network.py:155 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:73 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:134 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:136 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:138 +#: system/ui/widgets/network.py:155 selfdrive/ui/layouts/sidebar.py:73 +#: selfdrive/ui/layouts/sidebar.py:134 selfdrive/ui/layouts/sidebar.py:136 +#: selfdrive/ui/layouts/sidebar.py:138 #, python-format msgid "CONNECT" msgstr "CONNECT" -#: /home/batman/openpilot/system/ui/widgets/network.py:369 +#: system/ui/widgets/network.py:369 #, python-format msgid "CONNECTING..." msgstr "CONNECTING..." -#: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:23 -#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:35 -#: /home/batman/openpilot/system/ui/widgets/keyboard.py:81 -#: /home/batman/openpilot/system/ui/widgets/network.py:318 +#: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35 +#: system/ui/widgets/keyboard.py:81 system/ui/widgets/network.py:318 #, python-format msgid "Cancel" msgstr "إلغاء" -#: /home/batman/openpilot/system/ui/widgets/network.py:134 +#: system/ui/widgets/network.py:134 #, python-format msgid "Cellular Metered" msgstr "خلوي بتعرفة محدودة" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:68 +#: selfdrive/ui/layouts/settings/device.py:68 #, python-format msgid "Change Language" msgstr "تغيير اللغة" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:125 +#: selfdrive/ui/layouts/settings/toggles.py:125 #, python-format msgid "Changing this setting will restart openpilot if the car is powered on." msgstr "" "سيؤدي تغيير هذا الإعداد إلى إعادة تشغيل openpilot إذا كانت السيارة قيد " "التشغيل." -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:129 +#: selfdrive/ui/widgets/pairing_dialog.py:129 #, python-format msgid "Click \"add new device\" and scan the QR code on the right" msgstr "اضغط \"إضافة جهاز جديد\" ثم امسح رمز QR على اليمين" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 +#: selfdrive/ui/widgets/offroad_alerts.py:104 #, python-format msgid "Close" msgstr "إغلاق" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:49 +#: selfdrive/ui/layouts/settings/software.py:49 #, python-format msgid "Current Version" msgstr "الإصدار الحالي" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:110 +#: selfdrive/ui/layouts/settings/software.py:110 #, python-format msgid "DOWNLOAD" msgstr "تنزيل" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:115 +#: selfdrive/ui/layouts/onboarding.py:115 #, python-format msgid "Decline" msgstr "رفض" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:148 +#: selfdrive/ui/layouts/onboarding.py:148 #, python-format msgid "Decline, uninstall openpilot" msgstr "رفض، وإلغاء تثبيت openpilot" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:67 +#: selfdrive/ui/layouts/settings/settings.py:67 msgid "Developer" msgstr "المطور" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:62 +#: selfdrive/ui/layouts/settings/settings.py:62 msgid "Device" msgstr "الجهاز" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:58 +#: selfdrive/ui/layouts/settings/toggles.py:58 #, python-format msgid "Disengage on Accelerator Pedal" msgstr "فصل عند الضغط على دواسة الوقود" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:184 +#: selfdrive/ui/layouts/settings/device.py:184 #, python-format msgid "Disengage to Power Off" msgstr "افصل لإيقاف التشغيل" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:172 +#: selfdrive/ui/layouts/settings/device.py:172 #, python-format msgid "Disengage to Reboot" msgstr "افصل لإعادة التشغيل" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:103 +#: selfdrive/ui/layouts/settings/device.py:103 #, python-format msgid "Disengage to Reset Calibration" msgstr "افصل لإعادة ضبط المعايرة" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:32 +#: selfdrive/ui/layouts/settings/toggles.py:32 msgid "Display speed in km/h instead of mph." msgstr "عرض السرعة بالكيلومتر/ساعة بدلاً من الميل/ساعة." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:59 +#: selfdrive/ui/layouts/settings/device.py:59 #, python-format msgid "Dongle ID" msgstr "معرّف الدونجل" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:50 +#: selfdrive/ui/layouts/settings/software.py:50 #, python-format msgid "Download" msgstr "تنزيل" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:62 +#: selfdrive/ui/layouts/settings/device.py:62 #, python-format msgid "Driver Camera" msgstr "كاميرا السائق" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:96 +#: selfdrive/ui/layouts/settings/toggles.py:96 #, python-format msgid "Driving Personality" msgstr "شخصية القيادة" -#: /home/batman/openpilot/system/ui/widgets/network.py:123 -#: /home/batman/openpilot/system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:123 system/ui/widgets/network.py:139 #, python-format msgid "EDIT" msgstr "تعديل" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:138 +#: selfdrive/ui/layouts/sidebar.py:138 msgid "ERROR" msgstr "خطأ" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:45 +#: selfdrive/ui/layouts/sidebar.py:45 msgid "ETH" msgstr "ETH" -#: /home/batman/openpilot/selfdrive/ui/widgets/exp_mode_button.py:50 +#: selfdrive/ui/widgets/exp_mode_button.py:50 #, python-format msgid "EXPERIMENTAL MODE ON" msgstr "وضع التجربة مُفعل" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:166 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:228 +#: selfdrive/ui/layouts/settings/developer.py:166 +#: selfdrive/ui/layouts/settings/toggles.py:228 #, python-format msgid "Enable" msgstr "تمكين" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:39 +#: selfdrive/ui/layouts/settings/developer.py:39 #, python-format msgid "Enable ADB" msgstr "تمكين ADB" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:64 +#: selfdrive/ui/layouts/settings/toggles.py:64 #, python-format msgid "Enable Lane Departure Warnings" msgstr "تمكين تحذيرات مغادرة المسار" -#: /home/batman/openpilot/system/ui/widgets/network.py:129 +#: system/ui/widgets/network.py:129 #, python-format msgid "Enable Roaming" msgstr "تمكين التجوال" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:48 +#: selfdrive/ui/layouts/settings/developer.py:48 #, python-format msgid "Enable SSH" msgstr "تمكين SSH" -#: /home/batman/openpilot/system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:120 #, python-format msgid "Enable Tethering" msgstr "تمكين الربط" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:30 +#: selfdrive/ui/layouts/settings/toggles.py:30 msgid "Enable driver monitoring even when openpilot is not engaged." msgstr "تمكين مراقبة السائق حتى عندما لا يكون openpilot مُشغلاً." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:46 +#: selfdrive/ui/layouts/settings/toggles.py:46 #, python-format msgid "Enable openpilot" msgstr "تمكين openpilot" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:189 +#: selfdrive/ui/layouts/settings/toggles.py:189 #, python-format msgid "" "Enable the openpilot longitudinal control (alpha) toggle to allow " "Experimental mode." msgstr "فعّل تبديل التحكم الطولي (ألفا) لـ openpilot للسماح بوضع التجربة." -#: /home/batman/openpilot/system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:204 #, python-format msgid "Enter APN" msgstr "أدخل APN" -#: /home/batman/openpilot/system/ui/widgets/network.py:241 +#: system/ui/widgets/network.py:241 #, python-format msgid "Enter SSID" msgstr "أدخل SSID" -#: /home/batman/openpilot/system/ui/widgets/network.py:254 +#: system/ui/widgets/network.py:254 #, python-format msgid "Enter new tethering password" msgstr "أدخل كلمة مرور الربط الجديدة" -#: /home/batman/openpilot/system/ui/widgets/network.py:237 -#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 #, python-format msgid "Enter password" msgstr "أدخل كلمة المرور" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:89 +#: selfdrive/ui/widgets/ssh_key.py:89 #, python-format msgid "Enter your GitHub username" msgstr "أدخل اسم مستخدم GitHub الخاص بك" -#: /home/batman/openpilot/system/ui/widgets/list_view.py:123 -#: /home/batman/openpilot/system/ui/widgets/list_view.py:160 +#: system/ui/widgets/list_view.py:123 system/ui/widgets/list_view.py:160 #, python-format msgid "Error" msgstr "خطأ" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:52 +#: selfdrive/ui/layouts/settings/toggles.py:52 #, python-format msgid "Experimental Mode" msgstr "وضع التجربة" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:181 +#: selfdrive/ui/layouts/settings/toggles.py:181 #, python-format msgid "" "Experimental mode is currently unavailable on this car since the car's stock " @@ -430,25 +421,25 @@ msgstr "" "وضع التجربة غير متاح حالياً في هذه السيارة لأن نظام ACC الأصلي يُستخدم للتحكم " "الطولي." -#: /home/batman/openpilot/system/ui/widgets/network.py:373 +#: system/ui/widgets/network.py:373 #, python-format msgid "FORGETTING..." msgstr "جارٍ النسيان..." -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:44 +#: selfdrive/ui/widgets/setup.py:44 #, python-format msgid "Finish Setup" msgstr "إنهاء الإعداد" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:66 +#: selfdrive/ui/layouts/settings/settings.py:66 msgid "Firehose" msgstr "Firehose" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:18 +#: selfdrive/ui/layouts/settings/firehose.py:18 msgid "Firehose Mode" msgstr "وضع Firehose" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:25 +#: selfdrive/ui/layouts/settings/firehose.py:25 msgid "" "For maximum effectiveness, bring your device inside and connect to a good " "USB-C adapter and Wi-Fi weekly.\n" @@ -490,174 +481,168 @@ msgstr "" "هل يهم أي برنامج أشغّل؟ نعم، فقط openpilot الأصلي (وبعض التفرعات المحددة) " "يمكن استخدامه للتدريب." -#: /home/batman/openpilot/system/ui/widgets/network.py:318 -#: /home/batman/openpilot/system/ui/widgets/network.py:451 +#: system/ui/widgets/network.py:318 system/ui/widgets/network.py:451 #, python-format msgid "Forget" msgstr "نسيان" -#: /home/batman/openpilot/system/ui/widgets/network.py:319 +#: system/ui/widgets/network.py:319 #, python-format msgid "Forget Wi-Fi Network \"{}\"?" msgstr "هل تريد نسيان شبكة Wi‑Fi \"{}\"؟" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:71 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 msgid "GOOD" msgstr "جيد" -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:128 +#: selfdrive/ui/widgets/pairing_dialog.py:128 #, python-format msgid "Go to https://connect.comma.ai on your phone" msgstr "اذهب إلى https://connect.comma.ai على هاتفك" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:129 +#: selfdrive/ui/layouts/sidebar.py:129 msgid "HIGH" msgstr "مرتفع" -#: /home/batman/openpilot/system/ui/widgets/network.py:155 +#: system/ui/widgets/network.py:155 #, python-format msgid "Hidden Network" msgstr "شبكة مخفية" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:140 +#: selfdrive/ui/layouts/settings/firehose.py:140 #, python-format msgid "INACTIVE: connect to an unmetered network" msgstr "غير نشط: اتصل بشبكة غير محدودة التعرفة" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:53 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:136 +#: selfdrive/ui/layouts/settings/software.py:53 +#: selfdrive/ui/layouts/settings/software.py:136 #, python-format msgid "INSTALL" msgstr "تثبيت" -#: /home/batman/openpilot/system/ui/widgets/network.py:150 +#: system/ui/widgets/network.py:150 #, python-format msgid "IP Address" msgstr "عنوان IP" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:53 +#: selfdrive/ui/layouts/settings/software.py:53 #, python-format msgid "Install Update" msgstr "تثبيت التحديث" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:56 +#: selfdrive/ui/layouts/settings/developer.py:56 #, python-format msgid "Joystick Debug Mode" msgstr "وضع تصحيح عصا التحكم" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:29 +#: selfdrive/ui/widgets/ssh_key.py:29 msgid "LOADING" msgstr "جارٍ التحميل" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:48 +#: selfdrive/ui/layouts/sidebar.py:48 msgid "LTE" msgstr "LTE" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:64 +#: selfdrive/ui/layouts/settings/developer.py:64 #, python-format msgid "Longitudinal Maneuver Mode" msgstr "وضع المناورة الطولية" -#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:148 +#: selfdrive/ui/onroad/hud_renderer.py:148 #, python-format msgid "MAX" msgstr "أقصى" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:75 +#: selfdrive/ui/widgets/setup.py:75 #, python-format msgid "" "Maximize your training data uploads to improve openpilot's driving models." msgstr "زد من تحميل بيانات التدريب لتحسين نماذج قيادة openpilot." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:59 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:60 +#: selfdrive/ui/layouts/settings/device.py:59 +#: selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "N/A" msgstr "غير متوفر" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:142 +#: selfdrive/ui/layouts/sidebar.py:142 msgid "NO" msgstr "لا" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:63 +#: selfdrive/ui/layouts/settings/settings.py:63 msgid "Network" msgstr "الشبكة" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:114 +#: selfdrive/ui/widgets/ssh_key.py:114 #, python-format msgid "No SSH keys found" msgstr "لم يتم العثور على مفاتيح SSH" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:126 +#: selfdrive/ui/widgets/ssh_key.py:126 #, python-format msgid "No SSH keys found for user '{}'" msgstr "لم يتم العثور على مفاتيح SSH للمستخدم '{}'" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:320 +#: selfdrive/ui/widgets/offroad_alerts.py:320 #, python-format msgid "No release notes available." msgstr "لا توجد ملاحظات إصدار متاحة." -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:73 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: selfdrive/ui/layouts/sidebar.py:73 selfdrive/ui/layouts/sidebar.py:134 msgid "OFFLINE" msgstr "غير متصل" -#: /home/batman/openpilot/system/ui/widgets/html_render.py:263 -#: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:93 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: system/ui/widgets/html_render.py:263 system/ui/widgets/confirm_dialog.py:93 +#: selfdrive/ui/layouts/sidebar.py:127 #, python-format msgid "OK" msgstr "موافق" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:72 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:136 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:136 +#: selfdrive/ui/layouts/sidebar.py:144 msgid "ONLINE" msgstr "متصل" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:20 +#: selfdrive/ui/widgets/setup.py:20 #, python-format msgid "Open" msgstr "فتح" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:48 +#: selfdrive/ui/layouts/settings/device.py:48 #, python-format msgid "PAIR" msgstr "إقران" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:142 +#: selfdrive/ui/layouts/sidebar.py:142 msgid "PANDA" msgstr "PANDA" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:62 +#: selfdrive/ui/layouts/settings/device.py:62 #, python-format msgid "PREVIEW" msgstr "معاينة" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:44 +#: selfdrive/ui/widgets/prime.py:44 #, python-format msgid "PRIME FEATURES:" msgstr "ميزات PRIME:" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:48 +#: selfdrive/ui/layouts/settings/device.py:48 #, python-format msgid "Pair Device" msgstr "إقران الجهاز" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:19 +#: selfdrive/ui/widgets/setup.py:19 #, python-format msgid "Pair device" msgstr "إقران الجهاز" -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:103 +#: selfdrive/ui/widgets/pairing_dialog.py:103 #, python-format msgid "Pair your device to your comma account" msgstr "قم بإقران جهازك بحساب comma" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:48 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:24 +#: selfdrive/ui/widgets/setup.py:48 selfdrive/ui/layouts/settings/device.py:24 #, python-format msgid "" "Pair your device with comma connect (connect.comma.ai) and claim your comma " @@ -665,28 +650,28 @@ msgid "" msgstr "" "أقرِن جهازك مع comma connect (connect.comma.ai) واحصل على عرض comma prime." -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:91 +#: selfdrive/ui/widgets/setup.py:91 #, python-format msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "يرجى الاتصال بشبكة Wi‑Fi لإكمال الاقتران الأولي" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:187 +#: selfdrive/ui/layouts/settings/device.py:55 +#: selfdrive/ui/layouts/settings/device.py:187 #, python-format msgid "Power Off" msgstr "إيقاف التشغيل" -#: /home/batman/openpilot/system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:144 #, python-format msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "منع رفع البيانات الكبيرة عند الاتصال بشبكة Wi‑Fi محدودة التعرفة" -#: /home/batman/openpilot/system/ui/widgets/network.py:135 +#: system/ui/widgets/network.py:135 #, python-format msgid "Prevent large data uploads when on a metered cellular connection" msgstr "منع رفع البيانات الكبيرة عند الاتصال الخلوي محدود التعرفة" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:25 +#: selfdrive/ui/layouts/settings/device.py:25 msgid "" "Preview the driver facing camera to ensure that driver monitoring has good " "visibility. (vehicle must be off)" @@ -694,42 +679,42 @@ msgstr "" "عاين كاميرا مواجهة السائق للتأكد من أن مراقبة السائق تتم برؤية جيدة. (يجب أن " "تكون المركبة متوقفة)" -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:161 +#: selfdrive/ui/widgets/pairing_dialog.py:161 #, python-format msgid "QR Code Error" msgstr "خطأ في رمز QR" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:31 +#: selfdrive/ui/widgets/ssh_key.py:31 msgid "REMOVE" msgstr "إزالة" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:51 +#: selfdrive/ui/layouts/settings/device.py:51 #, python-format msgid "RESET" msgstr "إعادة ضبط" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:65 +#: selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "REVIEW" msgstr "مراجعة" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:175 +#: selfdrive/ui/layouts/settings/device.py:55 +#: selfdrive/ui/layouts/settings/device.py:175 #, python-format msgid "Reboot" msgstr "إعادة التشغيل" -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:66 +#: selfdrive/ui/onroad/alert_renderer.py:66 #, python-format msgid "Reboot Device" msgstr "إعادة تشغيل الجهاز" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 +#: selfdrive/ui/widgets/offroad_alerts.py:112 #, python-format msgid "Reboot and Update" msgstr "إعادة التشغيل والتحديث" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:27 +#: selfdrive/ui/layouts/settings/toggles.py:27 msgid "" "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 " @@ -738,17 +723,17 @@ msgstr "" "استقبال تنبيهات للتوجيه للعودة إلى المسار عند انحراف المركبة فوق خط المسار " "المُكتشف بدون إشارة انعطاف مفعّلة أثناء القيادة فوق 31 ميل/س (50 كم/س)." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:76 +#: selfdrive/ui/layouts/settings/toggles.py:76 #, python-format msgid "Record and Upload Driver Camera" msgstr "تسجيل ورفع فيديو كاميرا السائق" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:82 +#: selfdrive/ui/layouts/settings/toggles.py:82 #, python-format msgid "Record and Upload Microphone Audio" msgstr "تسجيل ورفع صوت الميكروفون" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +#: selfdrive/ui/layouts/settings/toggles.py:33 msgid "" "Record and store microphone audio while driving. The audio will be included " "in the dashcam video in comma connect." @@ -756,100 +741,100 @@ msgstr "" "تسجيل وتخزين صوت الميكروفون أثناء القيادة. سيُدرج الصوت في فيديو الكاميرا " "الأمامية في comma connect." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:67 +#: selfdrive/ui/layouts/settings/device.py:67 #, python-format msgid "Regulatory" msgstr "لوائح" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#: selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Relaxed" msgstr "مسترخٍ" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#: selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote access" msgstr "وصول عن بُعد" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#: selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote snapshots" msgstr "لقطات عن بُعد" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:123 +#: selfdrive/ui/widgets/ssh_key.py:123 #, python-format msgid "Request timed out" msgstr "انتهت مهلة الطلب" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:119 +#: selfdrive/ui/layouts/settings/device.py:119 #, python-format msgid "Reset" msgstr "إعادة ضبط" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:51 +#: selfdrive/ui/layouts/settings/device.py:51 #, python-format msgid "Reset Calibration" msgstr "إعادة ضبط المعايرة" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:65 +#: selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "Review Training Guide" msgstr "مراجعة دليل التدريب" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:27 +#: selfdrive/ui/layouts/settings/device.py:27 msgid "Review the rules, features, and limitations of openpilot" msgstr "مراجعة قواعد وميزات وحدود openpilot" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:61 +#: selfdrive/ui/layouts/settings/software.py:61 #, python-format msgid "SELECT" msgstr "اختيار" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:53 +#: selfdrive/ui/layouts/settings/developer.py:53 #, python-format msgid "SSH Keys" msgstr "مفاتيح SSH" -#: /home/batman/openpilot/system/ui/widgets/network.py:310 +#: system/ui/widgets/network.py:310 #, python-format msgid "Scanning Wi-Fi networks..." msgstr "جارٍ مسح شبكات Wi‑Fi..." -#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/option_dialog.py:36 #, python-format msgid "Select" msgstr "اختيار" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:183 +#: selfdrive/ui/layouts/settings/software.py:183 #, python-format msgid "Select a branch" msgstr "اختر فرعاً" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:91 +#: selfdrive/ui/layouts/settings/device.py:91 #, python-format msgid "Select a language" msgstr "اختر لغة" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:60 +#: selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "Serial" msgstr "الرقم التسلسلي" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 +#: selfdrive/ui/widgets/offroad_alerts.py:106 #, python-format msgid "Snooze Update" msgstr "تأجيل التحديث" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:65 +#: selfdrive/ui/layouts/settings/settings.py:65 msgid "Software" msgstr "البرمجيات" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#: selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Standard" msgstr "قياسي" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:22 +#: selfdrive/ui/layouts/settings/toggles.py:22 msgid "" "Standard is recommended. In aggressive mode, openpilot will follow lead cars " "closer and be more aggressive with the gas and brake. In relaxed mode " @@ -861,81 +846,79 @@ msgstr "" "عن السيارات الأمامية. في السيارات المدعومة، يمكنك التنقل بين هذه الشخصيات " "بزر مسافة المقود." -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:59 -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:65 +#: selfdrive/ui/onroad/alert_renderer.py:59 +#: selfdrive/ui/onroad/alert_renderer.py:65 #, python-format msgid "System Unresponsive" msgstr "النظام لا يستجيب" -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:58 +#: selfdrive/ui/onroad/alert_renderer.py:58 #, python-format msgid "TAKE CONTROL IMMEDIATELY" msgstr "تولَّ السيطرة فوراً" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:71 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:125 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:127 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:129 +#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 +#: selfdrive/ui/layouts/sidebar.py:127 selfdrive/ui/layouts/sidebar.py:129 msgid "TEMP" msgstr "الحرارة" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:61 +#: selfdrive/ui/layouts/settings/software.py:61 #, python-format msgid "Target Branch" msgstr "الفرع المستهدف" -#: /home/batman/openpilot/system/ui/widgets/network.py:124 +#: system/ui/widgets/network.py:124 #, python-format msgid "Tethering Password" msgstr "كلمة مرور الربط" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:64 +#: selfdrive/ui/layouts/settings/settings.py:64 msgid "Toggles" msgstr "مفاتيح التبديل" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:72 +#: selfdrive/ui/layouts/settings/software.py:72 #, python-format msgid "UNINSTALL" msgstr "إلغاء التثبيت" -#: /home/batman/openpilot/selfdrive/ui/layouts/home.py:155 +#: selfdrive/ui/layouts/home.py:155 #, python-format msgid "UPDATE" msgstr "تحديث" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:72 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:163 +#: selfdrive/ui/layouts/settings/software.py:72 +#: selfdrive/ui/layouts/settings/software.py:163 #, python-format msgid "Uninstall" msgstr "إلغاء التثبيت" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:117 +#: selfdrive/ui/layouts/sidebar.py:117 msgid "Unknown" msgstr "غير معروف" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:48 +#: selfdrive/ui/layouts/settings/software.py:48 #, python-format msgid "Updates are only downloaded while the car is off." msgstr "يتم تنزيل التحديثات فقط عندما تكون السيارة متوقفة." -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:33 +#: selfdrive/ui/widgets/prime.py:33 #, python-format msgid "Upgrade Now" msgstr "الترقية الآن" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +#: selfdrive/ui/layouts/settings/toggles.py:31 msgid "" "Upload data from the driver facing camera and help improve the driver " "monitoring algorithm." msgstr "" "ارفع بيانات من كاميرا مواجهة السائق وساعد في تحسين خوارزمية مراقبة السائق." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:88 +#: selfdrive/ui/layouts/settings/toggles.py:88 #, python-format msgid "Use Metric System" msgstr "استخدام النظام المتري" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:17 +#: selfdrive/ui/layouts/settings/toggles.py:17 msgid "" "Use the openpilot system for adaptive cruise control and lane keep driver " "assistance. Your attention is required at all times to use this feature." @@ -943,22 +926,21 @@ msgstr "" "استخدم نظام openpilot للتحكم الذكي بالسرعة والمساعدة على البقاء داخل المسار. " "يتطلب استخدام هذه الميزة انتباهك الكامل في جميع الأوقات." -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:72 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:144 msgid "VEHICLE" msgstr "المركبة" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:67 +#: selfdrive/ui/layouts/settings/device.py:67 #, python-format msgid "VIEW" msgstr "عرض" -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:52 +#: selfdrive/ui/onroad/alert_renderer.py:52 #, python-format msgid "Waiting to start" msgstr "بانتظار البدء" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:19 +#: selfdrive/ui/layouts/settings/developer.py:19 msgid "" "Warning: This grants SSH access to all public keys in your GitHub settings. " "Never enter a GitHub username other than your own. A comma employee will " @@ -968,35 +950,35 @@ msgstr "" "بك. لا تُدخل مطلقاً اسم مستخدم GitHub غير اسمك. لن يطلب منك موظف في comma أبداً " "إضافة اسم مستخدمهم." -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:111 +#: selfdrive/ui/layouts/onboarding.py:111 #, python-format msgid "Welcome to openpilot" msgstr "مرحباً بك في openpilot" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:20 +#: selfdrive/ui/layouts/settings/toggles.py:20 msgid "When enabled, pressing the accelerator pedal will disengage openpilot." msgstr "عند التمكين، سيؤدي الضغط على دواسة الوقود إلى فصل openpilot." -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:44 +#: selfdrive/ui/layouts/sidebar.py:44 msgid "Wi-Fi" msgstr "Wi‑Fi" -#: /home/batman/openpilot/system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:144 #, python-format msgid "Wi-Fi Network Metered" msgstr "شبكة Wi‑Fi محدودة التعرفة" -#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:314 #, python-format msgid "Wrong password" msgstr "كلمة مرور خاطئة" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:145 +#: selfdrive/ui/layouts/onboarding.py:145 #, python-format msgid "You must accept the Terms and Conditions in order to use openpilot." msgstr "يجب عليك قبول الشروط والأحكام لاستخدام openpilot." -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:112 +#: selfdrive/ui/layouts/onboarding.py:112 #, python-format msgid "" "You must accept the Terms and Conditions to use openpilot. Read the latest " @@ -1005,83 +987,82 @@ msgstr "" "يجب عليك قبول الشروط والأحكام لاستخدام openpilot. اقرأ أحدث الشروط على " "https://comma.ai/terms قبل المتابعة." -#: /home/batman/openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:34 +#: selfdrive/ui/onroad/driver_camera_dialog.py:34 #, python-format msgid "camera starting" msgstr "بدء تشغيل الكاميرا" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:63 +#: selfdrive/ui/widgets/prime.py:63 #, python-format msgid "comma prime" msgstr "comma prime" -#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:142 #, python-format msgid "default" msgstr "افتراضي" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#: selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid "down" msgstr "أسفل" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:106 +#: selfdrive/ui/layouts/settings/software.py:106 #, python-format msgid "failed to check for update" msgstr "فشل التحقق من وجود تحديث" -#: /home/batman/openpilot/system/ui/widgets/network.py:237 -#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 #, python-format msgid "for \"{}\"" msgstr "لـ \"{}\"" -#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:177 +#: selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "km/h" msgstr "كم/س" -#: /home/batman/openpilot/system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:204 #, python-format msgid "leave blank for automatic configuration" msgstr "اتركه فارغاً للإعداد التلقائي" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:134 +#: selfdrive/ui/layouts/settings/device.py:134 #, python-format msgid "left" msgstr "يسار" -#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:142 #, python-format msgid "metered" msgstr "محدود التعرفة" -#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:177 +#: selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "mph" msgstr "ميل/س" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:20 +#: selfdrive/ui/layouts/settings/software.py:20 #, python-format msgid "never" msgstr "أبداً" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:31 +#: selfdrive/ui/layouts/settings/software.py:31 #, python-format msgid "now" msgstr "الآن" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:71 +#: selfdrive/ui/layouts/settings/developer.py:71 #, python-format msgid "openpilot Longitudinal Control (Alpha)" msgstr "التحكم الطولي لـ openpilot (ألفا)" -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:51 +#: selfdrive/ui/onroad/alert_renderer.py:51 #, python-format msgid "openpilot Unavailable" msgstr "openpilot غير متاح" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:158 +#: selfdrive/ui/layouts/settings/toggles.py:158 #, python-format msgid "" "openpilot defaults to driving in chill mode. Experimental mode enables alpha-" @@ -1106,7 +1087,7 @@ msgstr "" "السرعات المنخفضة لإظهار بعض المنعطفات بشكل أفضل. كما سيظهر شعار وضع التجربة " "في الزاوية العلوية اليمنى." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:165 +#: selfdrive/ui/layouts/settings/device.py:165 #, python-format msgid "" "openpilot is continuously calibrating, resetting is rarely required. " @@ -1115,7 +1096,7 @@ msgstr "" "يقوم openpilot بالمعايرة بشكل مستمر، ونادراً ما تتطلب إعادة الضبط. ستؤدي " "إعادة ضبط المعايرة إلى إعادة تشغيل openpilot إذا كانت السيارة قيد التشغيل." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:20 +#: selfdrive/ui/layouts/settings/firehose.py:20 msgid "" "openpilot learns to drive by watching humans, like you, drive.\n" "\n" @@ -1128,12 +1109,12 @@ msgstr "" "يتيح وضع Firehose زيادة تحميل بيانات التدريب لتحسين نماذج قيادة openpilot. " "المزيد من البيانات يعني نماذج أكبر، مما يعني وضع تجربة أفضل." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:183 +#: selfdrive/ui/layouts/settings/toggles.py:183 #, python-format msgid "openpilot longitudinal control may come in a future update." msgstr "قد يأتي التحكم الطولي لـ openpilot في تحديث مستقبلي." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:26 +#: selfdrive/ui/layouts/settings/device.py:26 msgid "" "openpilot requires the device to be mounted within 4° left or right and " "within 5° up or 9° down." @@ -1141,37 +1122,37 @@ msgstr "" "يتطلب openpilot تركيب الجهاز ضمن 4° يساراً أو يميناً وضمن 5° للأعلى أو 9° " "للأسفل." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:134 +#: selfdrive/ui/layouts/settings/device.py:134 #, python-format msgid "right" msgstr "يمين" -#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:142 #, python-format msgid "unmetered" msgstr "غير محدود" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#: selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid "up" msgstr "أعلى" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:117 +#: selfdrive/ui/layouts/settings/software.py:117 #, python-format msgid "up to date, last checked never" msgstr "محدّث، آخر تحقق: أبداً" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:115 +#: selfdrive/ui/layouts/settings/software.py:115 #, python-format msgid "up to date, last checked {}" msgstr "محدّث، آخر تحقق {}" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:109 +#: selfdrive/ui/layouts/settings/software.py:109 #, python-format msgid "update available" msgstr "تحديث متاح" -#: /home/batman/openpilot/selfdrive/ui/layouts/home.py:169 +#: selfdrive/ui/layouts/home.py:169 #, python-format msgid "{} ALERT" msgid_plural "{} ALERTS" @@ -1182,7 +1163,7 @@ msgstr[3] "{} تنبيهات" msgstr[4] "{} تنبيهات" msgstr[5] "{} تنبيه" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:40 +#: selfdrive/ui/layouts/settings/software.py:40 #, python-format msgid "{} day ago" msgid_plural "{} days ago" @@ -1193,7 +1174,7 @@ msgstr[3] "قبل {} أيام" msgstr[4] "قبل {} أيام" msgstr[5] "قبل {} يوم" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:37 +#: selfdrive/ui/layouts/settings/software.py:37 #, python-format msgid "{} hour ago" msgid_plural "{} hours ago" @@ -1204,7 +1185,7 @@ msgstr[3] "قبل {} ساعات" msgstr[4] "قبل {} ساعات" msgstr[5] "قبل {} ساعة" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:34 +#: selfdrive/ui/layouts/settings/software.py:34 #, python-format msgid "{} minute ago" msgid_plural "{} minutes ago" @@ -1215,7 +1196,7 @@ msgstr[3] "قبل {} دقائق" msgstr[4] "قبل {} دقائق" msgstr[5] "قبل {} دقيقة" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:111 +#: selfdrive/ui/layouts/settings/firehose.py:111 #, python-format msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." @@ -1226,12 +1207,12 @@ msgstr[3] "{} مقاطع من قيادتك ضمن مجموعة بيانات ال msgstr[4] "{} مقاطع من قيادتك ضمن مجموعة بيانات التدريب حتى الآن." msgstr[5] "{} مقطع من قيادتك ضمن مجموعة بيانات التدريب حتى الآن." -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:62 +#: selfdrive/ui/widgets/prime.py:62 #, python-format msgid "✓ SUBSCRIBED" msgstr "✓ مشترك" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:22 +#: selfdrive/ui/widgets/setup.py:22 #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "🔥 وضع Firehose 🔥" diff --git a/selfdrive/ui/translations/app_de.po b/selfdrive/ui/translations/app_de.po index cac0faf88..f32c27a9e 100644 --- a/selfdrive/ui/translations/app_de.po +++ b/selfdrive/ui/translations/app_de.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-22 18:57-0700\n" +"POT-Creation-Date: 2025-10-23 00:50-0700\n" "PO-Revision-Date: 2025-10-20 16:35-0700\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" @@ -17,48 +17,48 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:160 +#: selfdrive/ui/layouts/settings/device.py:160 #, python-format msgid " Steering torque response calibration is complete." msgstr " Die Lenkmoment-Reaktionskalibrierung ist abgeschlossen." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:158 +#: selfdrive/ui/layouts/settings/device.py:158 #, python-format msgid " Steering torque response calibration is {}% complete." msgstr " Die Lenkmoment-Reaktionskalibrierung ist zu {}% abgeschlossen." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#: selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr " Ihr Gerät ist um {:.1f}° {} und {:.1f}° {} ausgerichtet." -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:43 +#: selfdrive/ui/layouts/sidebar.py:43 msgid "--" msgstr "--" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#: selfdrive/ui/widgets/prime.py:47 #, python-format msgid "1 year of drive storage" msgstr "1 Jahr Fahrtdatenspeicherung" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#: selfdrive/ui/widgets/prime.py:47 #, python-format msgid "24/7 LTE connectivity" msgstr "24/7 LTE‑Verbindung" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:46 +#: selfdrive/ui/layouts/sidebar.py:46 msgid "2G" msgstr "2G" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:47 +#: selfdrive/ui/layouts/sidebar.py:47 msgid "3G" msgstr "3G" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:49 +#: selfdrive/ui/layouts/sidebar.py:49 msgid "5G" msgstr "5G" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:23 +#: selfdrive/ui/layouts/settings/developer.py:23 msgid "" "WARNING: openpilot longitudinal control is in alpha for this car and will " "disable Automatic Emergency Braking (AEB).

On this car, openpilot " @@ -76,22 +76,22 @@ msgstr "" "Experimentalmodus wird empfohlen, wenn Sie die openpilot-Längsregelung " "(Alpha) aktivieren." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:148 +#: selfdrive/ui/layouts/settings/device.py:148 #, python-format msgid "

Steering lag calibration is complete." msgstr "

Kalibrierung der Lenkverzögerung abgeschlossen." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:146 +#: selfdrive/ui/layouts/settings/device.py:146 #, python-format msgid "

Steering lag calibration is {}% complete." msgstr "

Kalibrierung der Lenkverzögerung zu {}% abgeschlossen." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:138 +#: selfdrive/ui/layouts/settings/firehose.py:138 #, python-format msgid "ACTIVE" msgstr "AKTIV" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:15 +#: selfdrive/ui/layouts/settings/developer.py:15 msgid "" "ADB (Android Debug Bridge) allows connecting to your device over USB or over " "the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." @@ -100,42 +100,41 @@ msgstr "" "USB oder über das Netzwerk. Siehe https://docs.comma.ai/how-to/connect-to-" "comma für weitere Informationen." -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:30 +#: selfdrive/ui/widgets/ssh_key.py:30 msgid "ADD" msgstr "HINZUFÜGEN" -#: /home/batman/openpilot/system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:139 #, python-format msgid "APN Setting" msgstr "APN‑Einstellung" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 +#: selfdrive/ui/widgets/offroad_alerts.py:109 #, python-format msgid "Acknowledge Excessive Actuation" msgstr "Übermäßige Betätigung bestätigen" -#: /home/batman/openpilot/system/ui/widgets/network.py:74 -#: /home/batman/openpilot/system/ui/widgets/network.py:95 +#: system/ui/widgets/network.py:74 system/ui/widgets/network.py:95 #, python-format msgid "Advanced" msgstr "Erweitert" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#: selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Aggressive" msgstr "Aggressiv" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:116 +#: selfdrive/ui/layouts/onboarding.py:116 #, python-format msgid "Agree" msgstr "Zustimmen" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:70 +#: selfdrive/ui/layouts/settings/toggles.py:70 #, python-format msgid "Always-On Driver Monitoring" msgstr "Immer aktive Fahrerüberwachung" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:186 +#: selfdrive/ui/layouts/settings/toggles.py:186 #, python-format msgid "" "An alpha version of openpilot longitudinal control can be tested, along with " @@ -144,243 +143,237 @@ msgstr "" "Eine Alpha-Version der openpilot-Längsregelung kann zusammen mit dem " "Experimentalmodus auf Nicht-Release-Zweigen getestet werden." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:187 +#: selfdrive/ui/layouts/settings/device.py:187 #, python-format msgid "Are you sure you want to power off?" msgstr "Sind Sie sicher, dass Sie ausschalten möchten?" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:175 +#: selfdrive/ui/layouts/settings/device.py:175 #, python-format msgid "Are you sure you want to reboot?" msgstr "Sind Sie sicher, dass Sie neu starten möchten?" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:119 +#: selfdrive/ui/layouts/settings/device.py:119 #, python-format msgid "Are you sure you want to reset calibration?" msgstr "Sind Sie sicher, dass Sie die Kalibrierung zurücksetzen möchten?" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:163 +#: selfdrive/ui/layouts/settings/software.py:163 #, python-format msgid "Are you sure you want to uninstall?" msgstr "Sind Sie sicher, dass Sie deinstallieren möchten?" -#: /home/batman/openpilot/system/ui/widgets/network.py:99 -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:147 +#: system/ui/widgets/network.py:99 selfdrive/ui/layouts/onboarding.py:147 #, python-format msgid "Back" msgstr "Zurück" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:38 +#: selfdrive/ui/widgets/prime.py:38 #, python-format msgid "Become a comma prime member at connect.comma.ai" msgstr "Werden Sie comma prime Mitglied auf connect.comma.ai" -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:130 +#: selfdrive/ui/widgets/pairing_dialog.py:130 #, python-format msgid "Bookmark connect.comma.ai to your home screen to use it like an app" msgstr "" "Fügen Sie connect.comma.ai Ihrem Startbildschirm hinzu, um es wie eine App " "zu verwenden" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:68 +#: selfdrive/ui/layouts/settings/device.py:68 #, python-format msgid "CHANGE" msgstr "ÄNDERN" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:50 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:107 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:118 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:147 +#: selfdrive/ui/layouts/settings/software.py:50 +#: selfdrive/ui/layouts/settings/software.py:107 +#: selfdrive/ui/layouts/settings/software.py:118 +#: selfdrive/ui/layouts/settings/software.py:147 #, python-format msgid "CHECK" msgstr "PRÜFEN" -#: /home/batman/openpilot/selfdrive/ui/widgets/exp_mode_button.py:50 +#: selfdrive/ui/widgets/exp_mode_button.py:50 #, python-format msgid "CHILL MODE ON" msgstr "CHILL‑MODUS AKTIV" -#: /home/batman/openpilot/system/ui/widgets/network.py:155 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:73 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:134 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:136 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:138 +#: system/ui/widgets/network.py:155 selfdrive/ui/layouts/sidebar.py:73 +#: selfdrive/ui/layouts/sidebar.py:134 selfdrive/ui/layouts/sidebar.py:136 +#: selfdrive/ui/layouts/sidebar.py:138 #, python-format msgid "CONNECT" msgstr "VERBINDUNG" -#: /home/batman/openpilot/system/ui/widgets/network.py:369 +#: system/ui/widgets/network.py:369 #, python-format msgid "CONNECTING..." msgstr "VERBINDUNG" -#: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:23 -#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:35 -#: /home/batman/openpilot/system/ui/widgets/keyboard.py:81 -#: /home/batman/openpilot/system/ui/widgets/network.py:318 +#: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35 +#: system/ui/widgets/keyboard.py:81 system/ui/widgets/network.py:318 #, python-format msgid "Cancel" msgstr "Abbrechen" -#: /home/batman/openpilot/system/ui/widgets/network.py:134 +#: system/ui/widgets/network.py:134 #, python-format msgid "Cellular Metered" msgstr "Getaktete Mobilfunkverbindung" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:68 +#: selfdrive/ui/layouts/settings/device.py:68 #, python-format msgid "Change Language" msgstr "Sprache ändern" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:125 +#: selfdrive/ui/layouts/settings/toggles.py:125 #, python-format msgid "Changing this setting will restart openpilot if the car is powered on." msgstr "" " Durch Ändern dieser Einstellung wird openpilot neu gestartet, wenn das Auto " "eingeschaltet ist." -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:129 +#: selfdrive/ui/widgets/pairing_dialog.py:129 #, python-format msgid "Click \"add new device\" and scan the QR code on the right" msgstr "Klicken Sie auf \"add new device\" und scannen Sie den QR‑Code rechts" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 +#: selfdrive/ui/widgets/offroad_alerts.py:104 #, python-format msgid "Close" msgstr "Schließen" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:49 +#: selfdrive/ui/layouts/settings/software.py:49 #, python-format msgid "Current Version" msgstr "Aktuelle Version" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:110 +#: selfdrive/ui/layouts/settings/software.py:110 #, python-format msgid "DOWNLOAD" msgstr "HERUNTERLADEN" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:115 +#: selfdrive/ui/layouts/onboarding.py:115 #, python-format msgid "Decline" msgstr "Ablehnen" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:148 +#: selfdrive/ui/layouts/onboarding.py:148 #, python-format msgid "Decline, uninstall openpilot" msgstr "Ablehnen, openpilot deinstallieren" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:67 +#: selfdrive/ui/layouts/settings/settings.py:67 msgid "Developer" msgstr "Entwickler" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:62 +#: selfdrive/ui/layouts/settings/settings.py:62 msgid "Device" msgstr "Gerät" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:58 +#: selfdrive/ui/layouts/settings/toggles.py:58 #, python-format msgid "Disengage on Accelerator Pedal" msgstr "Beim Gaspedal deaktivieren" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:184 +#: selfdrive/ui/layouts/settings/device.py:184 #, python-format msgid "Disengage to Power Off" msgstr "Zum Ausschalten deaktivieren" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:172 +#: selfdrive/ui/layouts/settings/device.py:172 #, python-format msgid "Disengage to Reboot" msgstr "Zum Neustart deaktivieren" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:103 +#: selfdrive/ui/layouts/settings/device.py:103 #, python-format msgid "Disengage to Reset Calibration" msgstr "Zum Zurücksetzen der Kalibrierung deaktivieren" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:32 +#: selfdrive/ui/layouts/settings/toggles.py:32 msgid "Display speed in km/h instead of mph." msgstr "Geschwindigkeit in km/h statt mph anzeigen." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:59 +#: selfdrive/ui/layouts/settings/device.py:59 #, python-format msgid "Dongle ID" msgstr "Dongle-ID" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:50 +#: selfdrive/ui/layouts/settings/software.py:50 #, python-format msgid "Download" msgstr "Herunterladen" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:62 +#: selfdrive/ui/layouts/settings/device.py:62 #, python-format msgid "Driver Camera" msgstr "Fahrerkamera" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:96 +#: selfdrive/ui/layouts/settings/toggles.py:96 #, python-format msgid "Driving Personality" msgstr "Fahrstil" -#: /home/batman/openpilot/system/ui/widgets/network.py:123 -#: /home/batman/openpilot/system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:123 system/ui/widgets/network.py:139 #, python-format msgid "EDIT" msgstr "BEARBEITEN" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:138 +#: selfdrive/ui/layouts/sidebar.py:138 msgid "ERROR" msgstr "FEHLER" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:45 +#: selfdrive/ui/layouts/sidebar.py:45 msgid "ETH" msgstr "ETH" -#: /home/batman/openpilot/selfdrive/ui/widgets/exp_mode_button.py:50 +#: selfdrive/ui/widgets/exp_mode_button.py:50 #, python-format msgid "EXPERIMENTAL MODE ON" msgstr "EXPERIMENTALMODUS AKTIV" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:166 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:228 +#: selfdrive/ui/layouts/settings/developer.py:166 +#: selfdrive/ui/layouts/settings/toggles.py:228 #, python-format msgid "Enable" msgstr "Aktivieren" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:39 +#: selfdrive/ui/layouts/settings/developer.py:39 #, python-format msgid "Enable ADB" msgstr "ADB aktivieren" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:64 +#: selfdrive/ui/layouts/settings/toggles.py:64 #, python-format msgid "Enable Lane Departure Warnings" msgstr "Spurverlassenswarnungen aktivieren" -#: /home/batman/openpilot/system/ui/widgets/network.py:129 +#: system/ui/widgets/network.py:129 #, python-format msgid "Enable Roaming" msgstr "openpilot aktivieren" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:48 +#: selfdrive/ui/layouts/settings/developer.py:48 #, python-format msgid "Enable SSH" msgstr "SSH aktivieren" -#: /home/batman/openpilot/system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:120 #, python-format msgid "Enable Tethering" msgstr "Spurverlassenswarnungen aktivieren" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:30 +#: selfdrive/ui/layouts/settings/toggles.py:30 msgid "Enable driver monitoring even when openpilot is not engaged." msgstr "Fahrerüberwachung auch aktivieren, wenn openpilot nicht aktiv ist." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:46 +#: selfdrive/ui/layouts/settings/toggles.py:46 #, python-format msgid "Enable openpilot" msgstr "openpilot aktivieren" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:189 +#: selfdrive/ui/layouts/settings/toggles.py:189 #, python-format msgid "" "Enable the openpilot longitudinal control (alpha) toggle to allow " @@ -389,44 +382,42 @@ msgstr "" "Den Schalter für die openpilot-Längsregelung (Alpha) aktivieren, um den " "Experimentalmodus zu erlauben." -#: /home/batman/openpilot/system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:204 #, python-format msgid "Enter APN" msgstr "APN eingeben" -#: /home/batman/openpilot/system/ui/widgets/network.py:241 +#: system/ui/widgets/network.py:241 #, python-format msgid "Enter SSID" msgstr "SSID eingeben" -#: /home/batman/openpilot/system/ui/widgets/network.py:254 +#: system/ui/widgets/network.py:254 #, python-format msgid "Enter new tethering password" msgstr "Neues Tethering‑Passwort eingeben" -#: /home/batman/openpilot/system/ui/widgets/network.py:237 -#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 #, python-format msgid "Enter password" msgstr "Passwort eingeben" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:89 +#: selfdrive/ui/widgets/ssh_key.py:89 #, python-format msgid "Enter your GitHub username" msgstr "Geben Sie Ihren GitHub‑Benutzernamen ein" -#: /home/batman/openpilot/system/ui/widgets/list_view.py:123 -#: /home/batman/openpilot/system/ui/widgets/list_view.py:160 +#: system/ui/widgets/list_view.py:123 system/ui/widgets/list_view.py:160 #, python-format msgid "Error" msgstr "Fehler" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:52 +#: selfdrive/ui/layouts/settings/toggles.py:52 #, python-format msgid "Experimental Mode" msgstr "Experimentalmodus" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:181 +#: selfdrive/ui/layouts/settings/toggles.py:181 #, python-format msgid "" "Experimental mode is currently unavailable on this car since the car's stock " @@ -435,25 +426,25 @@ msgstr "" "Der Experimentalmodus ist derzeit auf diesem Fahrzeug nicht verfügbar, da " "der serienmäßige ACC für die Längsregelung verwendet wird." -#: /home/batman/openpilot/system/ui/widgets/network.py:373 +#: system/ui/widgets/network.py:373 #, python-format msgid "FORGETTING..." msgstr "WIRD VERGESSEN..." -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:44 +#: selfdrive/ui/widgets/setup.py:44 #, python-format msgid "Finish Setup" msgstr "Einrichtung abschließen" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:66 +#: selfdrive/ui/layouts/settings/settings.py:66 msgid "Firehose" msgstr "Firehose" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:18 +#: selfdrive/ui/layouts/settings/firehose.py:18 msgid "Firehose Mode" msgstr "Firehose‑Modus" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:25 +#: selfdrive/ui/layouts/settings/firehose.py:25 msgid "" "For maximum effectiveness, bring your device inside and connect to a good " "USB-C adapter and Wi-Fi weekly.\n" @@ -498,81 +489,79 @@ msgstr "" "Upstream‑openpilot (und bestimmte Forks) können für das Training verwendet " "werden." -#: /home/batman/openpilot/system/ui/widgets/network.py:318 -#: /home/batman/openpilot/system/ui/widgets/network.py:451 +#: system/ui/widgets/network.py:318 system/ui/widgets/network.py:451 #, python-format msgid "Forget" msgstr "Vergessen" -#: /home/batman/openpilot/system/ui/widgets/network.py:319 +#: system/ui/widgets/network.py:319 #, python-format msgid "Forget Wi-Fi Network \"{}\"?" msgstr "WLAN‑Netz „{}“ vergessen?" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:71 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 msgid "GOOD" msgstr "GUT" -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:128 +#: selfdrive/ui/widgets/pairing_dialog.py:128 #, python-format msgid "Go to https://connect.comma.ai on your phone" msgstr "Gehen Sie auf Ihrem Telefon zu https://connect.comma.ai" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:129 +#: selfdrive/ui/layouts/sidebar.py:129 msgid "HIGH" msgstr "HOCH" -#: /home/batman/openpilot/system/ui/widgets/network.py:155 +#: system/ui/widgets/network.py:155 #, python-format msgid "Hidden Network" msgstr "Netzwerk" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:140 +#: selfdrive/ui/layouts/settings/firehose.py:140 #, python-format msgid "INACTIVE: connect to an unmetered network" msgstr "INAKTIV: Mit einem unlimitierten Netzwerk verbinden" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:53 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:136 +#: selfdrive/ui/layouts/settings/software.py:53 +#: selfdrive/ui/layouts/settings/software.py:136 #, python-format msgid "INSTALL" msgstr "INSTALLIEREN" -#: /home/batman/openpilot/system/ui/widgets/network.py:150 +#: system/ui/widgets/network.py:150 #, python-format msgid "IP Address" msgstr "IP‑Adresse" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:53 +#: selfdrive/ui/layouts/settings/software.py:53 #, python-format msgid "Install Update" msgstr "Update installieren" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:56 +#: selfdrive/ui/layouts/settings/developer.py:56 #, python-format msgid "Joystick Debug Mode" msgstr "Joystick‑Debugmodus" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:29 +#: selfdrive/ui/widgets/ssh_key.py:29 msgid "LOADING" msgstr "LADEN" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:48 +#: selfdrive/ui/layouts/sidebar.py:48 msgid "LTE" msgstr "LTE" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:64 +#: selfdrive/ui/layouts/settings/developer.py:64 #, python-format msgid "Longitudinal Maneuver Mode" msgstr "Längsmanövermodus" -#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:148 +#: selfdrive/ui/onroad/hud_renderer.py:148 #, python-format msgid "MAX" msgstr "MAX" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:75 +#: selfdrive/ui/widgets/setup.py:75 #, python-format msgid "" "Maximize your training data uploads to improve openpilot's driving models." @@ -580,94 +569,90 @@ msgstr "" "Maximieren Sie Ihre Trainingsdaten‑Uploads, um die Fahrmodelle von openpilot " "zu verbessern." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:59 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:60 +#: selfdrive/ui/layouts/settings/device.py:59 +#: selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "N/A" msgstr "k. A." -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:142 +#: selfdrive/ui/layouts/sidebar.py:142 msgid "NO" msgstr "KEIN" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:63 +#: selfdrive/ui/layouts/settings/settings.py:63 msgid "Network" msgstr "Netzwerk" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:114 +#: selfdrive/ui/widgets/ssh_key.py:114 #, python-format msgid "No SSH keys found" msgstr "Keine SSH‑Schlüssel gefunden" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:126 +#: selfdrive/ui/widgets/ssh_key.py:126 #, python-format msgid "No SSH keys found for user '{}'" msgstr "Keine SSH‑Schlüssel für Benutzer '{username}' gefunden" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:320 +#: selfdrive/ui/widgets/offroad_alerts.py:320 #, python-format msgid "No release notes available." msgstr "Keine Versionshinweise verfügbar." -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:73 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: selfdrive/ui/layouts/sidebar.py:73 selfdrive/ui/layouts/sidebar.py:134 msgid "OFFLINE" msgstr "OFFLINE" -#: /home/batman/openpilot/system/ui/widgets/html_render.py:263 -#: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:93 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: system/ui/widgets/html_render.py:263 system/ui/widgets/confirm_dialog.py:93 +#: selfdrive/ui/layouts/sidebar.py:127 #, python-format msgid "OK" msgstr "OK" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:72 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:136 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:136 +#: selfdrive/ui/layouts/sidebar.py:144 msgid "ONLINE" msgstr "ONLINE" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:20 +#: selfdrive/ui/widgets/setup.py:20 #, python-format msgid "Open" msgstr "Öffnen" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:48 +#: selfdrive/ui/layouts/settings/device.py:48 #, python-format msgid "PAIR" msgstr "KOPPELN" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:142 +#: selfdrive/ui/layouts/sidebar.py:142 msgid "PANDA" msgstr "PANDA" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:62 +#: selfdrive/ui/layouts/settings/device.py:62 #, python-format msgid "PREVIEW" msgstr "VORSCHAU" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:44 +#: selfdrive/ui/widgets/prime.py:44 #, python-format msgid "PRIME FEATURES:" msgstr "PRIME‑FUNKTIONEN:" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:48 +#: selfdrive/ui/layouts/settings/device.py:48 #, python-format msgid "Pair Device" msgstr "Gerät koppeln" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:19 +#: selfdrive/ui/widgets/setup.py:19 #, python-format msgid "Pair device" msgstr "Gerät koppeln" -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:103 +#: selfdrive/ui/widgets/pairing_dialog.py:103 #, python-format msgid "Pair your device to your comma account" msgstr "Koppeln Sie Ihr Gerät mit Ihrem comma‑Konto" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:48 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:24 +#: selfdrive/ui/widgets/setup.py:48 selfdrive/ui/layouts/settings/device.py:24 #, python-format msgid "" "Pair your device with comma connect (connect.comma.ai) and claim your comma " @@ -676,28 +661,28 @@ msgstr "" "Koppeln Sie Ihr Gerät mit comma connect (connect.comma.ai) und lösen Sie Ihr " "comma‑prime‑Angebot ein." -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:91 +#: selfdrive/ui/widgets/setup.py:91 #, python-format msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "Bitte mit WLAN verbinden, um das erste Koppeln abzuschließen" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:187 +#: selfdrive/ui/layouts/settings/device.py:55 +#: selfdrive/ui/layouts/settings/device.py:187 #, python-format msgid "Power Off" msgstr "Ausschalten" -#: /home/batman/openpilot/system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:144 #, python-format msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:135 +#: system/ui/widgets/network.py:135 #, python-format msgid "Prevent large data uploads when on a metered cellular connection" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:25 +#: selfdrive/ui/layouts/settings/device.py:25 msgid "" "Preview the driver facing camera to ensure that driver monitoring has good " "visibility. (vehicle must be off)" @@ -705,42 +690,42 @@ msgstr "" "Vorschau der Fahrer‑Kamera, um sicherzustellen, dass die Fahrerüberwachung " "gute Sicht hat. (Fahrzeug muss ausgeschaltet sein)" -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:161 +#: selfdrive/ui/widgets/pairing_dialog.py:161 #, python-format msgid "QR Code Error" msgstr "QR‑Code‑Fehler" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:31 +#: selfdrive/ui/widgets/ssh_key.py:31 msgid "REMOVE" msgstr "ENTFERNEN" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:51 +#: selfdrive/ui/layouts/settings/device.py:51 #, python-format msgid "RESET" msgstr "ZURÜCKSETZEN" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:65 +#: selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "REVIEW" msgstr "ANSEHEN" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:175 +#: selfdrive/ui/layouts/settings/device.py:55 +#: selfdrive/ui/layouts/settings/device.py:175 #, python-format msgid "Reboot" msgstr "Neustart" -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:66 +#: selfdrive/ui/onroad/alert_renderer.py:66 #, python-format msgid "Reboot Device" msgstr "Gerät neu starten" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 +#: selfdrive/ui/widgets/offroad_alerts.py:112 #, python-format msgid "Reboot and Update" msgstr "Neustarten und aktualisieren" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:27 +#: selfdrive/ui/layouts/settings/toggles.py:27 msgid "" "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 " @@ -750,17 +735,17 @@ msgstr "" "ohne Blinker über eine erkannte Spurlinie driftet und über 31 mph (50 km/h) " "fährt." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:76 +#: selfdrive/ui/layouts/settings/toggles.py:76 #, python-format msgid "Record and Upload Driver Camera" msgstr "Fahrerkamera aufzeichnen und hochladen" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:82 +#: selfdrive/ui/layouts/settings/toggles.py:82 #, python-format msgid "Record and Upload Microphone Audio" msgstr "Mikrofonton aufzeichnen und hochladen" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +#: selfdrive/ui/layouts/settings/toggles.py:33 msgid "" "Record and store microphone audio while driving. The audio will be included " "in the dashcam video in comma connect." @@ -768,101 +753,101 @@ msgstr "" "Mikrofonton während der Fahrt aufzeichnen und speichern. Die Audiospur wird " "im Dashcam‑Video in comma connect enthalten sein." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:67 +#: selfdrive/ui/layouts/settings/device.py:67 #, python-format msgid "Regulatory" msgstr "Vorschriften" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#: selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Relaxed" msgstr "Entspannt" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#: selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote access" msgstr "Fernzugriff" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#: selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote snapshots" msgstr "Remote‑Schnappschüsse" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:123 +#: selfdrive/ui/widgets/ssh_key.py:123 #, python-format msgid "Request timed out" msgstr "Zeitüberschreitung bei der Anfrage" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:119 +#: selfdrive/ui/layouts/settings/device.py:119 #, python-format msgid "Reset" msgstr "Zurücksetzen" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:51 +#: selfdrive/ui/layouts/settings/device.py:51 #, python-format msgid "Reset Calibration" msgstr "Kalibrierung zurücksetzen" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:65 +#: selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "Review Training Guide" msgstr "Trainingsanleitung ansehen" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:27 +#: selfdrive/ui/layouts/settings/device.py:27 msgid "Review the rules, features, and limitations of openpilot" msgstr "" "Überprüfen Sie die Regeln, Funktionen und Einschränkungen von openpilot" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:61 +#: selfdrive/ui/layouts/settings/software.py:61 #, python-format msgid "SELECT" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:53 +#: selfdrive/ui/layouts/settings/developer.py:53 #, python-format msgid "SSH Keys" msgstr "SSH‑Schlüssel" -#: /home/batman/openpilot/system/ui/widgets/network.py:310 +#: system/ui/widgets/network.py:310 #, python-format msgid "Scanning Wi-Fi networks..." msgstr "WLAN‑Netzwerke werden gesucht..." -#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/option_dialog.py:36 #, python-format msgid "Select" msgstr "Auswählen" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:183 +#: selfdrive/ui/layouts/settings/software.py:183 #, python-format msgid "Select a branch" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:91 +#: selfdrive/ui/layouts/settings/device.py:91 #, python-format msgid "Select a language" msgstr "Sprache auswählen" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:60 +#: selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "Serial" msgstr "Seriennummer" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 +#: selfdrive/ui/widgets/offroad_alerts.py:106 #, python-format msgid "Snooze Update" msgstr "Update verschieben" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:65 +#: selfdrive/ui/layouts/settings/settings.py:65 msgid "Software" msgstr "Software" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#: selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Standard" msgstr "Standard" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:22 +#: selfdrive/ui/layouts/settings/toggles.py:22 msgid "" "Standard is recommended. In aggressive mode, openpilot will follow lead cars " "closer and be more aggressive with the gas and brake. In relaxed mode " @@ -875,69 +860,67 @@ msgstr "" "unterstützten Fahrzeugen können Sie mit der Abstandstaste am Lenkrad " "zwischen diesen Profilen wechseln." -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:59 -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:65 +#: selfdrive/ui/onroad/alert_renderer.py:59 +#: selfdrive/ui/onroad/alert_renderer.py:65 #, python-format msgid "System Unresponsive" msgstr "System reagiert nicht" -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:58 +#: selfdrive/ui/onroad/alert_renderer.py:58 #, python-format msgid "TAKE CONTROL IMMEDIATELY" msgstr "SOFORT DIE KONTROLLE ÜBERNEHMEN" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:71 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:125 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:127 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:129 +#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 +#: selfdrive/ui/layouts/sidebar.py:127 selfdrive/ui/layouts/sidebar.py:129 msgid "TEMP" msgstr "TEMP" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:61 +#: selfdrive/ui/layouts/settings/software.py:61 #, python-format msgid "Target Branch" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:124 +#: system/ui/widgets/network.py:124 #, python-format msgid "Tethering Password" msgstr "Tethering‑Passwort" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:64 +#: selfdrive/ui/layouts/settings/settings.py:64 msgid "Toggles" msgstr "Schalter" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:72 +#: selfdrive/ui/layouts/settings/software.py:72 #, python-format msgid "UNINSTALL" msgstr "DEINSTALLIEREN" -#: /home/batman/openpilot/selfdrive/ui/layouts/home.py:155 +#: selfdrive/ui/layouts/home.py:155 #, python-format msgid "UPDATE" msgstr "UPDATE" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:72 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:163 +#: selfdrive/ui/layouts/settings/software.py:72 +#: selfdrive/ui/layouts/settings/software.py:163 #, python-format msgid "Uninstall" msgstr "Deinstallieren" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:117 +#: selfdrive/ui/layouts/sidebar.py:117 msgid "Unknown" msgstr "Unbekannt" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:48 +#: selfdrive/ui/layouts/settings/software.py:48 #, python-format msgid "Updates are only downloaded while the car is off." msgstr "Updates werden nur heruntergeladen, wenn das Auto aus ist." -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:33 +#: selfdrive/ui/widgets/prime.py:33 #, python-format msgid "Upgrade Now" msgstr "Jetzt abonnieren" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +#: selfdrive/ui/layouts/settings/toggles.py:31 msgid "" "Upload data from the driver facing camera and help improve the driver " "monitoring algorithm." @@ -945,12 +928,12 @@ msgstr "" "Daten von der Fahrer‑Kamera hochladen und den Fahrerüberwachungs‑Algorithmus " "verbessern." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:88 +#: selfdrive/ui/layouts/settings/toggles.py:88 #, python-format msgid "Use Metric System" msgstr "Metersystem verwenden" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:17 +#: selfdrive/ui/layouts/settings/toggles.py:17 msgid "" "Use the openpilot system for adaptive cruise control and lane keep driver " "assistance. Your attention is required at all times to use this feature." @@ -959,22 +942,21 @@ msgstr "" "Spurhalteassistenz. Ihre Aufmerksamkeit ist jederzeit erforderlich, um diese " "Funktion zu nutzen." -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:72 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:144 msgid "VEHICLE" msgstr "FAHRZEUG" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:67 +#: selfdrive/ui/layouts/settings/device.py:67 #, python-format msgid "VIEW" msgstr "ANSEHEN" -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:52 +#: selfdrive/ui/onroad/alert_renderer.py:52 #, python-format msgid "Waiting to start" msgstr "Warten auf Start" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:19 +#: selfdrive/ui/layouts/settings/developer.py:19 msgid "" "Warning: This grants SSH access to all public keys in your GitHub settings. " "Never enter a GitHub username other than your own. A comma employee will " @@ -985,36 +967,36 @@ msgstr "" "als Ihren eigenen ein. Ein comma‑Mitarbeiter wird Sie NIEMALS bitten, seinen " "GitHub‑Benutzernamen hinzuzufügen." -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:111 +#: selfdrive/ui/layouts/onboarding.py:111 #, python-format msgid "Welcome to openpilot" msgstr "Willkommen bei openpilot" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:20 +#: selfdrive/ui/layouts/settings/toggles.py:20 msgid "When enabled, pressing the accelerator pedal will disengage openpilot." msgstr "Wenn aktiviert, deaktiviert das Drücken des Gaspedals openpilot." -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:44 +#: selfdrive/ui/layouts/sidebar.py:44 msgid "Wi-Fi" msgstr "WLAN" -#: /home/batman/openpilot/system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:144 #, python-format msgid "Wi-Fi Network Metered" msgstr "Getaktetes WLAN‑Netzwerk" -#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:314 #, python-format msgid "Wrong password" msgstr "Falsches Passwort" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:145 +#: selfdrive/ui/layouts/onboarding.py:145 #, python-format msgid "You must accept the Terms and Conditions in order to use openpilot." msgstr "" "Sie müssen die Nutzungsbedingungen akzeptieren, um openpilot zu verwenden." -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:112 +#: selfdrive/ui/layouts/onboarding.py:112 #, python-format msgid "" "You must accept the Terms and Conditions to use openpilot. Read the latest " @@ -1024,83 +1006,82 @@ msgstr "" "Lesen Sie die aktuellen Bedingungen unter https://comma.ai/terms, bevor Sie " "fortfahren." -#: /home/batman/openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:34 +#: selfdrive/ui/onroad/driver_camera_dialog.py:34 #, python-format msgid "camera starting" msgstr "Kamera startet" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:63 +#: selfdrive/ui/widgets/prime.py:63 #, python-format msgid "comma prime" msgstr "comma prime" -#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:142 #, python-format msgid "default" msgstr "Standard" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#: selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid "down" msgstr "unten" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:106 +#: selfdrive/ui/layouts/settings/software.py:106 #, python-format msgid "failed to check for update" msgstr "Überprüfung auf Updates fehlgeschlagen" -#: /home/batman/openpilot/system/ui/widgets/network.py:237 -#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 #, python-format msgid "for \"{}\"" msgstr "für „{}“" -#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:177 +#: selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "km/h" msgstr "km/h" -#: /home/batman/openpilot/system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:204 #, python-format msgid "leave blank for automatic configuration" msgstr "für automatische Konfiguration leer lassen" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:134 +#: selfdrive/ui/layouts/settings/device.py:134 #, python-format msgid "left" msgstr "links" -#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:142 #, python-format msgid "metered" msgstr "getaktet" -#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:177 +#: selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "mph" msgstr "mph" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:20 +#: selfdrive/ui/layouts/settings/software.py:20 #, python-format msgid "never" msgstr "nie" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:31 +#: selfdrive/ui/layouts/settings/software.py:31 #, python-format msgid "now" msgstr "jetzt" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:71 +#: selfdrive/ui/layouts/settings/developer.py:71 #, python-format msgid "openpilot Longitudinal Control (Alpha)" msgstr "openpilot Längsregelung (Alpha)" -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:51 +#: selfdrive/ui/onroad/alert_renderer.py:51 #, python-format msgid "openpilot Unavailable" msgstr "openpilot nicht verfügbar" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:158 +#: selfdrive/ui/layouts/settings/toggles.py:158 #, python-format msgid "" "openpilot defaults to driving in chill mode. Experimental mode enables alpha-" @@ -1128,7 +1109,7 @@ msgstr "" "manche Kurven besser zu zeigen. Das Experimentalmodus‑Logo wird außerdem " "oben rechts angezeigt." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:165 +#: selfdrive/ui/layouts/settings/device.py:165 #, python-format msgid "" "openpilot is continuously calibrating, resetting is rarely required. " @@ -1137,7 +1118,7 @@ msgstr "" " Durch Ändern dieser Einstellung wird openpilot neu gestartet, wenn das Auto " "eingeschaltet ist." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:20 +#: selfdrive/ui/layouts/settings/firehose.py:20 msgid "" "openpilot learns to drive by watching humans, like you, drive.\n" "\n" @@ -1151,12 +1132,12 @@ msgstr "" "maximieren, um die Fahrmodelle von openpilot zu verbessern. Mehr Daten " "bedeuten größere Modelle – und damit einen besseren Experimentalmodus." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:183 +#: selfdrive/ui/layouts/settings/toggles.py:183 #, python-format msgid "openpilot longitudinal control may come in a future update." msgstr "Die openpilot‑Längsregelung könnte in einem zukünftigen Update kommen." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:26 +#: selfdrive/ui/layouts/settings/device.py:26 msgid "" "openpilot requires the device to be mounted within 4° left or right and " "within 5° up or 9° down." @@ -1164,77 +1145,77 @@ msgstr "" "openpilot erfordert, dass das Gerät innerhalb von 4° nach links oder rechts " "und innerhalb von 5° nach oben oder 9° nach unten montiert ist." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:134 +#: selfdrive/ui/layouts/settings/device.py:134 #, python-format msgid "right" msgstr "rechts" -#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:142 #, python-format msgid "unmetered" msgstr "unbegrenzt" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#: selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid "up" msgstr "oben" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:117 +#: selfdrive/ui/layouts/settings/software.py:117 #, python-format msgid "up to date, last checked never" msgstr "Aktuell, zuletzt geprüft: nie" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:115 +#: selfdrive/ui/layouts/settings/software.py:115 #, python-format msgid "up to date, last checked {}" msgstr "Aktuell, zuletzt geprüft: {}" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:109 +#: selfdrive/ui/layouts/settings/software.py:109 #, python-format msgid "update available" msgstr "Update verfügbar" -#: /home/batman/openpilot/selfdrive/ui/layouts/home.py:169 +#: selfdrive/ui/layouts/home.py:169 #, python-format msgid "{} ALERT" msgid_plural "{} ALERTS" msgstr[0] "{} WARNUNG" msgstr[1] "{} WARNUNGEN" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:40 +#: selfdrive/ui/layouts/settings/software.py:40 #, python-format msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "vor {} Tag" msgstr[1] "vor {} Tagen" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:37 +#: selfdrive/ui/layouts/settings/software.py:37 #, python-format msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "vor {} Stunde" msgstr[1] "vor {} Stunden" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:34 +#: selfdrive/ui/layouts/settings/software.py:34 #, python-format msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "vor {} Minute" msgstr[1] "vor {} Minuten" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:111 +#: selfdrive/ui/layouts/settings/firehose.py:111 #, python-format msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." msgstr[0] "{} Segment Ihrer Fahrten ist bisher im Trainingsdatensatz." msgstr[1] "{} Segmente Ihrer Fahrten sind bisher im Trainingsdatensatz." -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:62 +#: selfdrive/ui/widgets/prime.py:62 #, python-format msgid "✓ SUBSCRIBED" msgstr "✓ ABONNIERT" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:22 +#: selfdrive/ui/widgets/setup.py:22 #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "🔥 Firehose‑Modus 🔥" diff --git a/selfdrive/ui/translations/app_en.po b/selfdrive/ui/translations/app_en.po index 66fecf358..6fbb537af 100644 --- a/selfdrive/ui/translations/app_en.po +++ b/selfdrive/ui/translations/app_en.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-22 18:57-0700\n" +"POT-Creation-Date: 2025-10-23 00:50-0700\n" "PO-Revision-Date: 2025-10-21 18:18-0700\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" @@ -17,48 +17,48 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:160 +#: selfdrive/ui/layouts/settings/device.py:160 #, python-format msgid " Steering torque response calibration is complete." msgstr " Steering torque response calibration is complete." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:158 +#: selfdrive/ui/layouts/settings/device.py:158 #, python-format msgid " Steering torque response calibration is {}% complete." msgstr " Steering torque response calibration is {}% complete." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#: selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr " Your device is pointed {:.1f}° {} and {:.1f}° {}." -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:43 +#: selfdrive/ui/layouts/sidebar.py:43 msgid "--" msgstr "--" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#: selfdrive/ui/widgets/prime.py:47 #, python-format msgid "1 year of drive storage" msgstr "1 year of drive storage" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#: selfdrive/ui/widgets/prime.py:47 #, python-format msgid "24/7 LTE connectivity" msgstr "24/7 LTE connectivity" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:46 +#: selfdrive/ui/layouts/sidebar.py:46 msgid "2G" msgstr "2G" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:47 +#: selfdrive/ui/layouts/sidebar.py:47 msgid "3G" msgstr "3G" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:49 +#: selfdrive/ui/layouts/sidebar.py:49 msgid "5G" msgstr "5G" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:23 +#: selfdrive/ui/layouts/settings/developer.py:23 msgid "" "WARNING: openpilot longitudinal control is in alpha for this car and will " "disable Automatic Emergency Braking (AEB).

On this car, openpilot " @@ -76,22 +76,22 @@ msgstr "" "control alpha. Changing this setting will restart openpilot if the car is " "powered on." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:148 +#: selfdrive/ui/layouts/settings/device.py:148 #, python-format msgid "

Steering lag calibration is complete." msgstr "

Steering lag calibration is complete." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:146 +#: selfdrive/ui/layouts/settings/device.py:146 #, python-format msgid "

Steering lag calibration is {}% complete." msgstr "

Steering lag calibration is {}% complete." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:138 +#: selfdrive/ui/layouts/settings/firehose.py:138 #, python-format msgid "ACTIVE" msgstr "ACTIVE" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:15 +#: selfdrive/ui/layouts/settings/developer.py:15 msgid "" "ADB (Android Debug Bridge) allows connecting to your device over USB or over " "the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." @@ -99,42 +99,41 @@ msgstr "" "ADB (Android Debug Bridge) allows connecting to your device over USB or over " "the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:30 +#: selfdrive/ui/widgets/ssh_key.py:30 msgid "ADD" msgstr "ADD" -#: /home/batman/openpilot/system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:139 #, python-format msgid "APN Setting" msgstr "APN Setting" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 +#: selfdrive/ui/widgets/offroad_alerts.py:109 #, python-format msgid "Acknowledge Excessive Actuation" msgstr "Acknowledge Excessive Actuation" -#: /home/batman/openpilot/system/ui/widgets/network.py:74 -#: /home/batman/openpilot/system/ui/widgets/network.py:95 +#: system/ui/widgets/network.py:74 system/ui/widgets/network.py:95 #, python-format msgid "Advanced" msgstr "Advanced" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#: selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Aggressive" msgstr "Aggressive" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:116 +#: selfdrive/ui/layouts/onboarding.py:116 #, python-format msgid "Agree" msgstr "Agree" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:70 +#: selfdrive/ui/layouts/settings/toggles.py:70 #, python-format msgid "Always-On Driver Monitoring" msgstr "Always-On Driver Monitoring" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:186 +#: selfdrive/ui/layouts/settings/toggles.py:186 #, python-format msgid "" "An alpha version of openpilot longitudinal control can be tested, along with " @@ -143,239 +142,233 @@ msgstr "" "An alpha version of openpilot longitudinal control can be tested, along with " "Experimental mode, on non-release branches." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:187 +#: selfdrive/ui/layouts/settings/device.py:187 #, python-format msgid "Are you sure you want to power off?" msgstr "Are you sure you want to power off?" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:175 +#: selfdrive/ui/layouts/settings/device.py:175 #, python-format msgid "Are you sure you want to reboot?" msgstr "Are you sure you want to reboot?" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:119 +#: selfdrive/ui/layouts/settings/device.py:119 #, python-format msgid "Are you sure you want to reset calibration?" msgstr "Are you sure you want to reset calibration?" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:163 +#: selfdrive/ui/layouts/settings/software.py:163 #, python-format msgid "Are you sure you want to uninstall?" msgstr "Are you sure you want to uninstall?" -#: /home/batman/openpilot/system/ui/widgets/network.py:99 -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:147 +#: system/ui/widgets/network.py:99 selfdrive/ui/layouts/onboarding.py:147 #, python-format msgid "Back" msgstr "Back" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:38 +#: selfdrive/ui/widgets/prime.py:38 #, python-format msgid "Become a comma prime member at connect.comma.ai" msgstr "Become a comma prime member at connect.comma.ai" -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:130 +#: selfdrive/ui/widgets/pairing_dialog.py:130 #, python-format msgid "Bookmark connect.comma.ai to your home screen to use it like an app" msgstr "Bookmark connect.comma.ai to your home screen to use it like an app" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:68 +#: selfdrive/ui/layouts/settings/device.py:68 #, python-format msgid "CHANGE" msgstr "CHANGE" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:50 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:107 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:118 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:147 +#: selfdrive/ui/layouts/settings/software.py:50 +#: selfdrive/ui/layouts/settings/software.py:107 +#: selfdrive/ui/layouts/settings/software.py:118 +#: selfdrive/ui/layouts/settings/software.py:147 #, python-format msgid "CHECK" msgstr "CHECK" -#: /home/batman/openpilot/selfdrive/ui/widgets/exp_mode_button.py:50 +#: selfdrive/ui/widgets/exp_mode_button.py:50 #, python-format msgid "CHILL MODE ON" msgstr "CHILL MODE ON" -#: /home/batman/openpilot/system/ui/widgets/network.py:155 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:73 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:134 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:136 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:138 +#: system/ui/widgets/network.py:155 selfdrive/ui/layouts/sidebar.py:73 +#: selfdrive/ui/layouts/sidebar.py:134 selfdrive/ui/layouts/sidebar.py:136 +#: selfdrive/ui/layouts/sidebar.py:138 #, python-format msgid "CONNECT" msgstr "CONNECT" -#: /home/batman/openpilot/system/ui/widgets/network.py:369 +#: system/ui/widgets/network.py:369 #, python-format msgid "CONNECTING..." msgstr "CONNECTING..." -#: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:23 -#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:35 -#: /home/batman/openpilot/system/ui/widgets/keyboard.py:81 -#: /home/batman/openpilot/system/ui/widgets/network.py:318 +#: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35 +#: system/ui/widgets/keyboard.py:81 system/ui/widgets/network.py:318 #, python-format msgid "Cancel" msgstr "Cancel" -#: /home/batman/openpilot/system/ui/widgets/network.py:134 +#: system/ui/widgets/network.py:134 #, python-format msgid "Cellular Metered" msgstr "Cellular Metered" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:68 +#: selfdrive/ui/layouts/settings/device.py:68 #, python-format msgid "Change Language" msgstr "Change Language" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:125 +#: selfdrive/ui/layouts/settings/toggles.py:125 #, python-format msgid "Changing this setting will restart openpilot if the car is powered on." msgstr "Changing this setting will restart openpilot if the car is powered on." -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:129 +#: selfdrive/ui/widgets/pairing_dialog.py:129 #, python-format msgid "Click \"add new device\" and scan the QR code on the right" msgstr "Click \"add new device\" and scan the QR code on the right" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 +#: selfdrive/ui/widgets/offroad_alerts.py:104 #, python-format msgid "Close" msgstr "Close" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:49 +#: selfdrive/ui/layouts/settings/software.py:49 #, python-format msgid "Current Version" msgstr "Current Version" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:110 +#: selfdrive/ui/layouts/settings/software.py:110 #, python-format msgid "DOWNLOAD" msgstr "DOWNLOAD" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:115 +#: selfdrive/ui/layouts/onboarding.py:115 #, python-format msgid "Decline" msgstr "Decline" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:148 +#: selfdrive/ui/layouts/onboarding.py:148 #, python-format msgid "Decline, uninstall openpilot" msgstr "Decline, uninstall openpilot" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:67 +#: selfdrive/ui/layouts/settings/settings.py:67 msgid "Developer" msgstr "Developer" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:62 +#: selfdrive/ui/layouts/settings/settings.py:62 msgid "Device" msgstr "Device" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:58 +#: selfdrive/ui/layouts/settings/toggles.py:58 #, python-format msgid "Disengage on Accelerator Pedal" msgstr "Disengage on Accelerator Pedal" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:184 +#: selfdrive/ui/layouts/settings/device.py:184 #, python-format msgid "Disengage to Power Off" msgstr "Disengage to Power Off" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:172 +#: selfdrive/ui/layouts/settings/device.py:172 #, python-format msgid "Disengage to Reboot" msgstr "Disengage to Reboot" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:103 +#: selfdrive/ui/layouts/settings/device.py:103 #, python-format msgid "Disengage to Reset Calibration" msgstr "Disengage to Reset Calibration" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:32 +#: selfdrive/ui/layouts/settings/toggles.py:32 msgid "Display speed in km/h instead of mph." msgstr "Display speed in km/h instead of mph." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:59 +#: selfdrive/ui/layouts/settings/device.py:59 #, python-format msgid "Dongle ID" msgstr "Dongle ID" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:50 +#: selfdrive/ui/layouts/settings/software.py:50 #, python-format msgid "Download" msgstr "Download" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:62 +#: selfdrive/ui/layouts/settings/device.py:62 #, python-format msgid "Driver Camera" msgstr "Driver Camera" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:96 +#: selfdrive/ui/layouts/settings/toggles.py:96 #, python-format msgid "Driving Personality" msgstr "Driving Personality" -#: /home/batman/openpilot/system/ui/widgets/network.py:123 -#: /home/batman/openpilot/system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:123 system/ui/widgets/network.py:139 #, python-format msgid "EDIT" msgstr "EDIT" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:138 +#: selfdrive/ui/layouts/sidebar.py:138 msgid "ERROR" msgstr "ERROR" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:45 +#: selfdrive/ui/layouts/sidebar.py:45 msgid "ETH" msgstr "ETH" -#: /home/batman/openpilot/selfdrive/ui/widgets/exp_mode_button.py:50 +#: selfdrive/ui/widgets/exp_mode_button.py:50 #, python-format msgid "EXPERIMENTAL MODE ON" msgstr "EXPERIMENTAL MODE ON" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:166 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:228 +#: selfdrive/ui/layouts/settings/developer.py:166 +#: selfdrive/ui/layouts/settings/toggles.py:228 #, python-format msgid "Enable" msgstr "Enable" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:39 +#: selfdrive/ui/layouts/settings/developer.py:39 #, python-format msgid "Enable ADB" msgstr "Enable ADB" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:64 +#: selfdrive/ui/layouts/settings/toggles.py:64 #, python-format msgid "Enable Lane Departure Warnings" msgstr "Enable Lane Departure Warnings" -#: /home/batman/openpilot/system/ui/widgets/network.py:129 +#: system/ui/widgets/network.py:129 #, python-format msgid "Enable Roaming" msgstr "Enable Roaming" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:48 +#: selfdrive/ui/layouts/settings/developer.py:48 #, python-format msgid "Enable SSH" msgstr "Enable SSH" -#: /home/batman/openpilot/system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:120 #, python-format msgid "Enable Tethering" msgstr "Enable Tethering" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:30 +#: selfdrive/ui/layouts/settings/toggles.py:30 msgid "Enable driver monitoring even when openpilot is not engaged." msgstr "Enable driver monitoring even when openpilot is not engaged." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:46 +#: selfdrive/ui/layouts/settings/toggles.py:46 #, python-format msgid "Enable openpilot" msgstr "Enable openpilot" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:189 +#: selfdrive/ui/layouts/settings/toggles.py:189 #, python-format msgid "" "Enable the openpilot longitudinal control (alpha) toggle to allow " @@ -384,44 +377,42 @@ msgstr "" "Enable the openpilot longitudinal control (alpha) toggle to allow " "Experimental mode." -#: /home/batman/openpilot/system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:204 #, python-format msgid "Enter APN" msgstr "Enter APN" -#: /home/batman/openpilot/system/ui/widgets/network.py:241 +#: system/ui/widgets/network.py:241 #, python-format msgid "Enter SSID" msgstr "Enter SSID" -#: /home/batman/openpilot/system/ui/widgets/network.py:254 +#: system/ui/widgets/network.py:254 #, python-format msgid "Enter new tethering password" msgstr "Enter new tethering password" -#: /home/batman/openpilot/system/ui/widgets/network.py:237 -#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 #, python-format msgid "Enter password" msgstr "Enter password" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:89 +#: selfdrive/ui/widgets/ssh_key.py:89 #, python-format msgid "Enter your GitHub username" msgstr "Enter your GitHub username" -#: /home/batman/openpilot/system/ui/widgets/list_view.py:123 -#: /home/batman/openpilot/system/ui/widgets/list_view.py:160 +#: system/ui/widgets/list_view.py:123 system/ui/widgets/list_view.py:160 #, python-format msgid "Error" msgstr "Error" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:52 +#: selfdrive/ui/layouts/settings/toggles.py:52 #, python-format msgid "Experimental Mode" msgstr "Experimental Mode" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:181 +#: selfdrive/ui/layouts/settings/toggles.py:181 #, python-format msgid "" "Experimental mode is currently unavailable on this car since the car's stock " @@ -430,25 +421,25 @@ msgstr "" "Experimental mode is currently unavailable on this car since the car's stock " "ACC is used for longitudinal control." -#: /home/batman/openpilot/system/ui/widgets/network.py:373 +#: system/ui/widgets/network.py:373 #, python-format msgid "FORGETTING..." msgstr "FORGETTING..." -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:44 +#: selfdrive/ui/widgets/setup.py:44 #, python-format msgid "Finish Setup" msgstr "Finish Setup" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:66 +#: selfdrive/ui/layouts/settings/settings.py:66 msgid "Firehose" msgstr "Firehose" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:18 +#: selfdrive/ui/layouts/settings/firehose.py:18 msgid "Firehose Mode" msgstr "Firehose Mode" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:25 +#: selfdrive/ui/layouts/settings/firehose.py:25 msgid "" "For maximum effectiveness, bring your device inside and connect to a good " "USB-C adapter and Wi-Fi weekly.\n" @@ -492,175 +483,169 @@ msgstr "" "Does it matter which software I run? Yes, only upstream openpilot (and " "particular forks) are able to be used for training." -#: /home/batman/openpilot/system/ui/widgets/network.py:318 -#: /home/batman/openpilot/system/ui/widgets/network.py:451 +#: system/ui/widgets/network.py:318 system/ui/widgets/network.py:451 #, python-format msgid "Forget" msgstr "Forget" -#: /home/batman/openpilot/system/ui/widgets/network.py:319 +#: system/ui/widgets/network.py:319 #, python-format msgid "Forget Wi-Fi Network \"{}\"?" msgstr "Forget Wi-Fi Network \"{}\"?" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:71 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 msgid "GOOD" msgstr "GOOD" -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:128 +#: selfdrive/ui/widgets/pairing_dialog.py:128 #, python-format msgid "Go to https://connect.comma.ai on your phone" msgstr "Go to https://connect.comma.ai on your phone" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:129 +#: selfdrive/ui/layouts/sidebar.py:129 msgid "HIGH" msgstr "HIGH" -#: /home/batman/openpilot/system/ui/widgets/network.py:155 +#: system/ui/widgets/network.py:155 #, python-format msgid "Hidden Network" msgstr "Hidden Network" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:140 +#: selfdrive/ui/layouts/settings/firehose.py:140 #, python-format msgid "INACTIVE: connect to an unmetered network" msgstr "INACTIVE: connect to an unmetered network" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:53 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:136 +#: selfdrive/ui/layouts/settings/software.py:53 +#: selfdrive/ui/layouts/settings/software.py:136 #, python-format msgid "INSTALL" msgstr "INSTALL" -#: /home/batman/openpilot/system/ui/widgets/network.py:150 +#: system/ui/widgets/network.py:150 #, python-format msgid "IP Address" msgstr "IP Address" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:53 +#: selfdrive/ui/layouts/settings/software.py:53 #, python-format msgid "Install Update" msgstr "Install Update" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:56 +#: selfdrive/ui/layouts/settings/developer.py:56 #, python-format msgid "Joystick Debug Mode" msgstr "Joystick Debug Mode" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:29 +#: selfdrive/ui/widgets/ssh_key.py:29 msgid "LOADING" msgstr "LOADING" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:48 +#: selfdrive/ui/layouts/sidebar.py:48 msgid "LTE" msgstr "LTE" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:64 +#: selfdrive/ui/layouts/settings/developer.py:64 #, python-format msgid "Longitudinal Maneuver Mode" msgstr "Longitudinal Maneuver Mode" -#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:148 +#: selfdrive/ui/onroad/hud_renderer.py:148 #, python-format msgid "MAX" msgstr "MAX" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:75 +#: selfdrive/ui/widgets/setup.py:75 #, python-format msgid "" "Maximize your training data uploads to improve openpilot's driving models." msgstr "" "Maximize your training data uploads to improve openpilot's driving models." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:59 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:60 +#: selfdrive/ui/layouts/settings/device.py:59 +#: selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "N/A" msgstr "N/A" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:142 +#: selfdrive/ui/layouts/sidebar.py:142 msgid "NO" msgstr "NO" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:63 +#: selfdrive/ui/layouts/settings/settings.py:63 msgid "Network" msgstr "Network" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:114 +#: selfdrive/ui/widgets/ssh_key.py:114 #, python-format msgid "No SSH keys found" msgstr "No SSH keys found" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:126 +#: selfdrive/ui/widgets/ssh_key.py:126 #, python-format msgid "No SSH keys found for user '{}'" msgstr "No SSH keys found for user '{}'" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:320 +#: selfdrive/ui/widgets/offroad_alerts.py:320 #, python-format msgid "No release notes available." msgstr "No release notes available." -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:73 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: selfdrive/ui/layouts/sidebar.py:73 selfdrive/ui/layouts/sidebar.py:134 msgid "OFFLINE" msgstr "OFFLINE" -#: /home/batman/openpilot/system/ui/widgets/html_render.py:263 -#: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:93 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: system/ui/widgets/html_render.py:263 system/ui/widgets/confirm_dialog.py:93 +#: selfdrive/ui/layouts/sidebar.py:127 #, python-format msgid "OK" msgstr "OK" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:72 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:136 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:136 +#: selfdrive/ui/layouts/sidebar.py:144 msgid "ONLINE" msgstr "ONLINE" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:20 +#: selfdrive/ui/widgets/setup.py:20 #, python-format msgid "Open" msgstr "Open" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:48 +#: selfdrive/ui/layouts/settings/device.py:48 #, python-format msgid "PAIR" msgstr "PAIR" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:142 +#: selfdrive/ui/layouts/sidebar.py:142 msgid "PANDA" msgstr "PANDA" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:62 +#: selfdrive/ui/layouts/settings/device.py:62 #, python-format msgid "PREVIEW" msgstr "PREVIEW" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:44 +#: selfdrive/ui/widgets/prime.py:44 #, python-format msgid "PRIME FEATURES:" msgstr "PRIME FEATURES:" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:48 +#: selfdrive/ui/layouts/settings/device.py:48 #, python-format msgid "Pair Device" msgstr "Pair Device" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:19 +#: selfdrive/ui/widgets/setup.py:19 #, python-format msgid "Pair device" msgstr "Pair device" -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:103 +#: selfdrive/ui/widgets/pairing_dialog.py:103 #, python-format msgid "Pair your device to your comma account" msgstr "Pair your device to your comma account" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:48 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:24 +#: selfdrive/ui/widgets/setup.py:48 selfdrive/ui/layouts/settings/device.py:24 #, python-format msgid "" "Pair your device with comma connect (connect.comma.ai) and claim your comma " @@ -669,28 +654,28 @@ msgstr "" "Pair your device with comma connect (connect.comma.ai) and claim your comma " "prime offer." -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:91 +#: selfdrive/ui/widgets/setup.py:91 #, python-format msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "Please connect to Wi-Fi to complete initial pairing" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:187 +#: selfdrive/ui/layouts/settings/device.py:55 +#: selfdrive/ui/layouts/settings/device.py:187 #, python-format msgid "Power Off" msgstr "Power Off" -#: /home/batman/openpilot/system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:144 #, python-format msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "Prevent large data uploads when on a metered Wi-Fi connection" -#: /home/batman/openpilot/system/ui/widgets/network.py:135 +#: system/ui/widgets/network.py:135 #, python-format msgid "Prevent large data uploads when on a metered cellular connection" msgstr "Prevent large data uploads when on a metered cellular connection" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:25 +#: selfdrive/ui/layouts/settings/device.py:25 msgid "" "Preview the driver facing camera to ensure that driver monitoring has good " "visibility. (vehicle must be off)" @@ -698,42 +683,42 @@ msgstr "" "Preview the driver facing camera to ensure that driver monitoring has good " "visibility. (vehicle must be off)" -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:161 +#: selfdrive/ui/widgets/pairing_dialog.py:161 #, python-format msgid "QR Code Error" msgstr "QR Code Error" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:31 +#: selfdrive/ui/widgets/ssh_key.py:31 msgid "REMOVE" msgstr "REMOVE" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:51 +#: selfdrive/ui/layouts/settings/device.py:51 #, python-format msgid "RESET" msgstr "RESET" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:65 +#: selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "REVIEW" msgstr "REVIEW" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:175 +#: selfdrive/ui/layouts/settings/device.py:55 +#: selfdrive/ui/layouts/settings/device.py:175 #, python-format msgid "Reboot" msgstr "Reboot" -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:66 +#: selfdrive/ui/onroad/alert_renderer.py:66 #, python-format msgid "Reboot Device" msgstr "Reboot Device" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 +#: selfdrive/ui/widgets/offroad_alerts.py:112 #, python-format msgid "Reboot and Update" msgstr "Reboot and Update" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:27 +#: selfdrive/ui/layouts/settings/toggles.py:27 msgid "" "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 " @@ -743,17 +728,17 @@ msgstr "" "detected lane line without a turn signal activated while driving over 31 mph " "(50 km/h)." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:76 +#: selfdrive/ui/layouts/settings/toggles.py:76 #, python-format msgid "Record and Upload Driver Camera" msgstr "Record and Upload Driver Camera" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:82 +#: selfdrive/ui/layouts/settings/toggles.py:82 #, python-format msgid "Record and Upload Microphone Audio" msgstr "Record and Upload Microphone Audio" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +#: selfdrive/ui/layouts/settings/toggles.py:33 msgid "" "Record and store microphone audio while driving. The audio will be included " "in the dashcam video in comma connect." @@ -761,100 +746,100 @@ msgstr "" "Record and store microphone audio while driving. The audio will be included " "in the dashcam video in comma connect." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:67 +#: selfdrive/ui/layouts/settings/device.py:67 #, python-format msgid "Regulatory" msgstr "Regulatory" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#: selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Relaxed" msgstr "Relaxed" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#: selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote access" msgstr "Remote access" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#: selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote snapshots" msgstr "Remote snapshots" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:123 +#: selfdrive/ui/widgets/ssh_key.py:123 #, python-format msgid "Request timed out" msgstr "Request timed out" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:119 +#: selfdrive/ui/layouts/settings/device.py:119 #, python-format msgid "Reset" msgstr "Reset" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:51 +#: selfdrive/ui/layouts/settings/device.py:51 #, python-format msgid "Reset Calibration" msgstr "Reset Calibration" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:65 +#: selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "Review Training Guide" msgstr "Review Training Guide" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:27 +#: selfdrive/ui/layouts/settings/device.py:27 msgid "Review the rules, features, and limitations of openpilot" msgstr "Review the rules, features, and limitations of openpilot" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:61 +#: selfdrive/ui/layouts/settings/software.py:61 #, python-format msgid "SELECT" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:53 +#: selfdrive/ui/layouts/settings/developer.py:53 #, python-format msgid "SSH Keys" msgstr "SSH Keys" -#: /home/batman/openpilot/system/ui/widgets/network.py:310 +#: system/ui/widgets/network.py:310 #, python-format msgid "Scanning Wi-Fi networks..." msgstr "Scanning Wi-Fi networks..." -#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/option_dialog.py:36 #, python-format msgid "Select" msgstr "Select" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:183 +#: selfdrive/ui/layouts/settings/software.py:183 #, python-format msgid "Select a branch" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:91 +#: selfdrive/ui/layouts/settings/device.py:91 #, python-format msgid "Select a language" msgstr "Select a language" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:60 +#: selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "Serial" msgstr "Serial" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 +#: selfdrive/ui/widgets/offroad_alerts.py:106 #, python-format msgid "Snooze Update" msgstr "Snooze Update" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:65 +#: selfdrive/ui/layouts/settings/settings.py:65 msgid "Software" msgstr "Software" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#: selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Standard" msgstr "Standard" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:22 +#: selfdrive/ui/layouts/settings/toggles.py:22 msgid "" "Standard is recommended. In aggressive mode, openpilot will follow lead cars " "closer and be more aggressive with the gas and brake. In relaxed mode " @@ -866,69 +851,67 @@ msgstr "" "openpilot will stay further away from lead cars. On supported cars, you can " "cycle through these personalities with your steering wheel distance button." -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:59 -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:65 +#: selfdrive/ui/onroad/alert_renderer.py:59 +#: selfdrive/ui/onroad/alert_renderer.py:65 #, python-format msgid "System Unresponsive" msgstr "System Unresponsive" -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:58 +#: selfdrive/ui/onroad/alert_renderer.py:58 #, python-format msgid "TAKE CONTROL IMMEDIATELY" msgstr "TAKE CONTROL IMMEDIATELY" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:71 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:125 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:127 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:129 +#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 +#: selfdrive/ui/layouts/sidebar.py:127 selfdrive/ui/layouts/sidebar.py:129 msgid "TEMP" msgstr "TEMP" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:61 +#: selfdrive/ui/layouts/settings/software.py:61 #, python-format msgid "Target Branch" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:124 +#: system/ui/widgets/network.py:124 #, python-format msgid "Tethering Password" msgstr "Tethering Password" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:64 +#: selfdrive/ui/layouts/settings/settings.py:64 msgid "Toggles" msgstr "Toggles" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:72 +#: selfdrive/ui/layouts/settings/software.py:72 #, python-format msgid "UNINSTALL" msgstr "UNINSTALL" -#: /home/batman/openpilot/selfdrive/ui/layouts/home.py:155 +#: selfdrive/ui/layouts/home.py:155 #, python-format msgid "UPDATE" msgstr "UPDATE" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:72 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:163 +#: selfdrive/ui/layouts/settings/software.py:72 +#: selfdrive/ui/layouts/settings/software.py:163 #, python-format msgid "Uninstall" msgstr "Uninstall" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:117 +#: selfdrive/ui/layouts/sidebar.py:117 msgid "Unknown" msgstr "Unknown" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:48 +#: selfdrive/ui/layouts/settings/software.py:48 #, python-format msgid "Updates are only downloaded while the car is off." msgstr "Updates are only downloaded while the car is off." -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:33 +#: selfdrive/ui/widgets/prime.py:33 #, python-format msgid "Upgrade Now" msgstr "Upgrade Now" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +#: selfdrive/ui/layouts/settings/toggles.py:31 msgid "" "Upload data from the driver facing camera and help improve the driver " "monitoring algorithm." @@ -936,12 +919,12 @@ msgstr "" "Upload data from the driver facing camera and help improve the driver " "monitoring algorithm." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:88 +#: selfdrive/ui/layouts/settings/toggles.py:88 #, python-format msgid "Use Metric System" msgstr "Use Metric System" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:17 +#: selfdrive/ui/layouts/settings/toggles.py:17 msgid "" "Use the openpilot system for adaptive cruise control and lane keep driver " "assistance. Your attention is required at all times to use this feature." @@ -949,22 +932,21 @@ msgstr "" "Use the openpilot system for adaptive cruise control and lane keep driver " "assistance. Your attention is required at all times to use this feature." -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:72 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:144 msgid "VEHICLE" msgstr "VEHICLE" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:67 +#: selfdrive/ui/layouts/settings/device.py:67 #, python-format msgid "VIEW" msgstr "VIEW" -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:52 +#: selfdrive/ui/onroad/alert_renderer.py:52 #, python-format msgid "Waiting to start" msgstr "Waiting to start" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:19 +#: selfdrive/ui/layouts/settings/developer.py:19 msgid "" "Warning: This grants SSH access to all public keys in your GitHub settings. " "Never enter a GitHub username other than your own. A comma employee will " @@ -974,35 +956,35 @@ msgstr "" "Never enter a GitHub username other than your own. A comma employee will " "NEVER ask you to add their GitHub username." -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:111 +#: selfdrive/ui/layouts/onboarding.py:111 #, python-format msgid "Welcome to openpilot" msgstr "Welcome to openpilot" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:20 +#: selfdrive/ui/layouts/settings/toggles.py:20 msgid "When enabled, pressing the accelerator pedal will disengage openpilot." msgstr "When enabled, pressing the accelerator pedal will disengage openpilot." -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:44 +#: selfdrive/ui/layouts/sidebar.py:44 msgid "Wi-Fi" msgstr "Wi-Fi" -#: /home/batman/openpilot/system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:144 #, python-format msgid "Wi-Fi Network Metered" msgstr "Wi-Fi Network Metered" -#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:314 #, python-format msgid "Wrong password" msgstr "Wrong password" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:145 +#: selfdrive/ui/layouts/onboarding.py:145 #, python-format msgid "You must accept the Terms and Conditions in order to use openpilot." msgstr "You must accept the Terms and Conditions in order to use openpilot." -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:112 +#: selfdrive/ui/layouts/onboarding.py:112 #, python-format msgid "" "You must accept the Terms and Conditions to use openpilot. Read the latest " @@ -1011,83 +993,82 @@ msgstr "" "You must accept the Terms and Conditions to use openpilot. Read the latest " "terms at https://comma.ai/terms before continuing." -#: /home/batman/openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:34 +#: selfdrive/ui/onroad/driver_camera_dialog.py:34 #, python-format msgid "camera starting" msgstr "camera starting" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:63 +#: selfdrive/ui/widgets/prime.py:63 #, python-format msgid "comma prime" msgstr "comma prime" -#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:142 #, python-format msgid "default" msgstr "default" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#: selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid "down" msgstr "down" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:106 +#: selfdrive/ui/layouts/settings/software.py:106 #, python-format msgid "failed to check for update" msgstr "failed to check for update" -#: /home/batman/openpilot/system/ui/widgets/network.py:237 -#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 #, python-format msgid "for \"{}\"" msgstr "for \"{}\"" -#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:177 +#: selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "km/h" msgstr "km/h" -#: /home/batman/openpilot/system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:204 #, python-format msgid "leave blank for automatic configuration" msgstr "leave blank for automatic configuration" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:134 +#: selfdrive/ui/layouts/settings/device.py:134 #, python-format msgid "left" msgstr "left" -#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:142 #, python-format msgid "metered" msgstr "metered" -#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:177 +#: selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "mph" msgstr "mph" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:20 +#: selfdrive/ui/layouts/settings/software.py:20 #, python-format msgid "never" msgstr "never" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:31 +#: selfdrive/ui/layouts/settings/software.py:31 #, python-format msgid "now" msgstr "now" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:71 +#: selfdrive/ui/layouts/settings/developer.py:71 #, python-format msgid "openpilot Longitudinal Control (Alpha)" msgstr "openpilot Longitudinal Control (Alpha)" -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:51 +#: selfdrive/ui/onroad/alert_renderer.py:51 #, python-format msgid "openpilot Unavailable" msgstr "openpilot Unavailable" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:158 +#: selfdrive/ui/layouts/settings/toggles.py:158 #, python-format msgid "" "openpilot defaults to driving in chill mode. Experimental mode enables alpha-" @@ -1114,7 +1095,7 @@ msgstr "" "some turns. The Experimental mode logo will also be shown in the top right " "corner." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:165 +#: selfdrive/ui/layouts/settings/device.py:165 #, python-format msgid "" "openpilot is continuously calibrating, resetting is rarely required. " @@ -1123,7 +1104,7 @@ msgstr "" "openpilot is continuously calibrating, resetting is rarely required. " "Resetting calibration will restart openpilot if the car is powered on." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:20 +#: selfdrive/ui/layouts/settings/firehose.py:20 msgid "" "openpilot learns to drive by watching humans, like you, drive.\n" "\n" @@ -1137,12 +1118,12 @@ msgstr "" "openpilot's driving models. More data means bigger models, which means " "better Experimental Mode." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:183 +#: selfdrive/ui/layouts/settings/toggles.py:183 #, python-format msgid "openpilot longitudinal control may come in a future update." msgstr "openpilot longitudinal control may come in a future update." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:26 +#: selfdrive/ui/layouts/settings/device.py:26 msgid "" "openpilot requires the device to be mounted within 4° left or right and " "within 5° up or 9° down." @@ -1150,77 +1131,77 @@ msgstr "" "openpilot requires the device to be mounted within 4° left or right and " "within 5° up or 9° down." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:134 +#: selfdrive/ui/layouts/settings/device.py:134 #, python-format msgid "right" msgstr "right" -#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:142 #, python-format msgid "unmetered" msgstr "unmetered" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#: selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid "up" msgstr "up" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:117 +#: selfdrive/ui/layouts/settings/software.py:117 #, python-format msgid "up to date, last checked never" msgstr "up to date, last checked never" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:115 +#: selfdrive/ui/layouts/settings/software.py:115 #, python-format msgid "up to date, last checked {}" msgstr "up to date, last checked {}" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:109 +#: selfdrive/ui/layouts/settings/software.py:109 #, python-format msgid "update available" msgstr "update available" -#: /home/batman/openpilot/selfdrive/ui/layouts/home.py:169 +#: selfdrive/ui/layouts/home.py:169 #, python-format msgid "{} ALERT" msgid_plural "{} ALERTS" msgstr[0] "{} ALERT" msgstr[1] "{} ALERTS" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:40 +#: selfdrive/ui/layouts/settings/software.py:40 #, python-format msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "{} day ago" msgstr[1] "{} days ago" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:37 +#: selfdrive/ui/layouts/settings/software.py:37 #, python-format msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "{} hour ago" msgstr[1] "{} hours ago" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:34 +#: selfdrive/ui/layouts/settings/software.py:34 #, python-format msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "{} minute ago" msgstr[1] "{} minutes ago" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:111 +#: selfdrive/ui/layouts/settings/firehose.py:111 #, python-format msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." msgstr[0] "{} segment of your driving is in the training dataset so far." msgstr[1] "{} segments of your driving is in the training dataset so far." -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:62 +#: selfdrive/ui/widgets/prime.py:62 #, python-format msgid "✓ SUBSCRIBED" msgstr "✓ SUBSCRIBED" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:22 +#: selfdrive/ui/widgets/setup.py:22 #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "🔥 Firehose Mode 🔥" diff --git a/selfdrive/ui/translations/app_es.po b/selfdrive/ui/translations/app_es.po index 04dffbf3e..59b9e6dfd 100644 --- a/selfdrive/ui/translations/app_es.po +++ b/selfdrive/ui/translations/app_es.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-22 18:57-0700\n" +"POT-Creation-Date: 2025-10-23 00:50-0700\n" "PO-Revision-Date: 2025-10-20 16:35-0700\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" @@ -17,48 +17,48 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:160 +#: selfdrive/ui/layouts/settings/device.py:160 #, python-format msgid " Steering torque response calibration is complete." msgstr " La calibración de respuesta de par de dirección está completa." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:158 +#: selfdrive/ui/layouts/settings/device.py:158 #, python-format msgid " Steering torque response calibration is {}% complete." msgstr " La calibración de respuesta de par de dirección está {}% completa." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#: selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr " Tu dispositivo está orientado {:.1f}° {} y {:.1f}° {}." -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:43 +#: selfdrive/ui/layouts/sidebar.py:43 msgid "--" msgstr "--" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#: selfdrive/ui/widgets/prime.py:47 #, python-format msgid "1 year of drive storage" msgstr "1 año de almacenamiento de conducción" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#: selfdrive/ui/widgets/prime.py:47 #, python-format msgid "24/7 LTE connectivity" msgstr "Conectividad LTE 24/7" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:46 +#: selfdrive/ui/layouts/sidebar.py:46 msgid "2G" msgstr "2G" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:47 +#: selfdrive/ui/layouts/sidebar.py:47 msgid "3G" msgstr "3G" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:49 +#: selfdrive/ui/layouts/sidebar.py:49 msgid "5G" msgstr "5G" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:23 +#: selfdrive/ui/layouts/settings/developer.py:23 msgid "" "WARNING: openpilot longitudinal control is in alpha for this car and will " "disable Automatic Emergency Braking (AEB).

On this car, openpilot " @@ -75,22 +75,22 @@ msgstr "" "cambiar al control longitudinal de openpilot. Se recomienda activar el modo " "Experimental al habilitar el control longitudinal de openpilot (alpha)." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:148 +#: selfdrive/ui/layouts/settings/device.py:148 #, python-format msgid "

Steering lag calibration is complete." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:146 +#: selfdrive/ui/layouts/settings/device.py:146 #, python-format msgid "

Steering lag calibration is {}% complete." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:138 +#: selfdrive/ui/layouts/settings/firehose.py:138 #, python-format msgid "ACTIVE" msgstr "ACTIVO" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:15 +#: selfdrive/ui/layouts/settings/developer.py:15 msgid "" "ADB (Android Debug Bridge) allows connecting to your device over USB or over " "the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." @@ -99,42 +99,41 @@ msgstr "" "red. Consulta https://docs.comma.ai/how-to/connect-to-comma para más " "información." -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:30 +#: selfdrive/ui/widgets/ssh_key.py:30 msgid "ADD" msgstr "AÑADIR" -#: /home/batman/openpilot/system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:139 #, python-format msgid "APN Setting" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 +#: selfdrive/ui/widgets/offroad_alerts.py:109 #, python-format msgid "Acknowledge Excessive Actuation" msgstr "Reconocer actuación excesiva" -#: /home/batman/openpilot/system/ui/widgets/network.py:74 -#: /home/batman/openpilot/system/ui/widgets/network.py:95 +#: system/ui/widgets/network.py:74 system/ui/widgets/network.py:95 #, python-format msgid "Advanced" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#: selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Aggressive" msgstr "Agresivo" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:116 +#: selfdrive/ui/layouts/onboarding.py:116 #, python-format msgid "Agree" msgstr "Aceptar" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:70 +#: selfdrive/ui/layouts/settings/toggles.py:70 #, python-format msgid "Always-On Driver Monitoring" msgstr "Supervisión del conductor siempre activa" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:186 +#: selfdrive/ui/layouts/settings/toggles.py:186 #, python-format msgid "" "An alpha version of openpilot longitudinal control can be tested, along with " @@ -143,244 +142,238 @@ msgstr "" "Se puede probar una versión alpha del control longitudinal de openpilot, " "junto con el modo Experimental, en ramas que no son de lanzamiento." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:187 +#: selfdrive/ui/layouts/settings/device.py:187 #, python-format msgid "Are you sure you want to power off?" msgstr "¿Seguro que quieres apagar?" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:175 +#: selfdrive/ui/layouts/settings/device.py:175 #, python-format msgid "Are you sure you want to reboot?" msgstr "¿Seguro que quieres reiniciar?" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:119 +#: selfdrive/ui/layouts/settings/device.py:119 #, python-format msgid "Are you sure you want to reset calibration?" msgstr "¿Seguro que quieres restablecer la calibración?" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:163 +#: selfdrive/ui/layouts/settings/software.py:163 #, python-format msgid "Are you sure you want to uninstall?" msgstr "¿Seguro que quieres desinstalar?" -#: /home/batman/openpilot/system/ui/widgets/network.py:99 -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:147 +#: system/ui/widgets/network.py:99 selfdrive/ui/layouts/onboarding.py:147 #, python-format msgid "Back" msgstr "Atrás" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:38 +#: selfdrive/ui/widgets/prime.py:38 #, python-format msgid "Become a comma prime member at connect.comma.ai" msgstr "Hazte miembro de comma prime en connect.comma.ai" -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:130 +#: selfdrive/ui/widgets/pairing_dialog.py:130 #, python-format msgid "Bookmark connect.comma.ai to your home screen to use it like an app" msgstr "" "Añade connect.comma.ai a tu pantalla de inicio para usarlo como una app" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:68 +#: selfdrive/ui/layouts/settings/device.py:68 #, python-format msgid "CHANGE" msgstr "CAMBIAR" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:50 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:107 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:118 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:147 +#: selfdrive/ui/layouts/settings/software.py:50 +#: selfdrive/ui/layouts/settings/software.py:107 +#: selfdrive/ui/layouts/settings/software.py:118 +#: selfdrive/ui/layouts/settings/software.py:147 #, python-format msgid "CHECK" msgstr "COMPROBAR" -#: /home/batman/openpilot/selfdrive/ui/widgets/exp_mode_button.py:50 +#: selfdrive/ui/widgets/exp_mode_button.py:50 #, python-format msgid "CHILL MODE ON" msgstr "MODO CHILL ACTIVADO" -#: /home/batman/openpilot/system/ui/widgets/network.py:155 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:73 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:134 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:136 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:138 +#: system/ui/widgets/network.py:155 selfdrive/ui/layouts/sidebar.py:73 +#: selfdrive/ui/layouts/sidebar.py:134 selfdrive/ui/layouts/sidebar.py:136 +#: selfdrive/ui/layouts/sidebar.py:138 #, python-format msgid "CONNECT" msgstr "CONECTAR" -#: /home/batman/openpilot/system/ui/widgets/network.py:369 +#: system/ui/widgets/network.py:369 #, python-format msgid "CONNECTING..." msgstr "CONECTAR" -#: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:23 -#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:35 -#: /home/batman/openpilot/system/ui/widgets/keyboard.py:81 -#: /home/batman/openpilot/system/ui/widgets/network.py:318 +#: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35 +#: system/ui/widgets/keyboard.py:81 system/ui/widgets/network.py:318 #, python-format msgid "Cancel" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:134 +#: system/ui/widgets/network.py:134 #, python-format msgid "Cellular Metered" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:68 +#: selfdrive/ui/layouts/settings/device.py:68 #, python-format msgid "Change Language" msgstr "Cambiar idioma" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:125 +#: selfdrive/ui/layouts/settings/toggles.py:125 #, python-format msgid "Changing this setting will restart openpilot if the car is powered on." msgstr "" " Cambiar esta configuración reiniciará openpilot si el coche está encendido." -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:129 +#: selfdrive/ui/widgets/pairing_dialog.py:129 #, python-format msgid "Click \"add new device\" and scan the QR code on the right" msgstr "" "Haz clic en \"añadir nuevo dispositivo\" y escanea el código QR de la derecha" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 +#: selfdrive/ui/widgets/offroad_alerts.py:104 #, python-format msgid "Close" msgstr "Cerrar" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:49 +#: selfdrive/ui/layouts/settings/software.py:49 #, python-format msgid "Current Version" msgstr "Versión actual" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:110 +#: selfdrive/ui/layouts/settings/software.py:110 #, python-format msgid "DOWNLOAD" msgstr "DESCARGAR" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:115 +#: selfdrive/ui/layouts/onboarding.py:115 #, python-format msgid "Decline" msgstr "Rechazar" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:148 +#: selfdrive/ui/layouts/onboarding.py:148 #, python-format msgid "Decline, uninstall openpilot" msgstr "Rechazar, desinstalar openpilot" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:67 +#: selfdrive/ui/layouts/settings/settings.py:67 msgid "Developer" msgstr "Desarrollador" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:62 +#: selfdrive/ui/layouts/settings/settings.py:62 msgid "Device" msgstr "Dispositivo" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:58 +#: selfdrive/ui/layouts/settings/toggles.py:58 #, python-format msgid "Disengage on Accelerator Pedal" msgstr "Desactivar con el pedal del acelerador" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:184 +#: selfdrive/ui/layouts/settings/device.py:184 #, python-format msgid "Disengage to Power Off" msgstr "Desactivar para apagar" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:172 +#: selfdrive/ui/layouts/settings/device.py:172 #, python-format msgid "Disengage to Reboot" msgstr "Desactivar para reiniciar" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:103 +#: selfdrive/ui/layouts/settings/device.py:103 #, python-format msgid "Disengage to Reset Calibration" msgstr "Desactivar para restablecer la calibración" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:32 +#: selfdrive/ui/layouts/settings/toggles.py:32 msgid "Display speed in km/h instead of mph." msgstr "Mostrar la velocidad en km/h en lugar de mph." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:59 +#: selfdrive/ui/layouts/settings/device.py:59 #, python-format msgid "Dongle ID" msgstr "ID del dongle" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:50 +#: selfdrive/ui/layouts/settings/software.py:50 #, python-format msgid "Download" msgstr "Descargar" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:62 +#: selfdrive/ui/layouts/settings/device.py:62 #, python-format msgid "Driver Camera" msgstr "Cámara del conductor" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:96 +#: selfdrive/ui/layouts/settings/toggles.py:96 #, python-format msgid "Driving Personality" msgstr "Estilo de conducción" -#: /home/batman/openpilot/system/ui/widgets/network.py:123 -#: /home/batman/openpilot/system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:123 system/ui/widgets/network.py:139 #, python-format msgid "EDIT" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:138 +#: selfdrive/ui/layouts/sidebar.py:138 msgid "ERROR" msgstr "ERROR" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:45 +#: selfdrive/ui/layouts/sidebar.py:45 msgid "ETH" msgstr "ETH" -#: /home/batman/openpilot/selfdrive/ui/widgets/exp_mode_button.py:50 +#: selfdrive/ui/widgets/exp_mode_button.py:50 #, python-format msgid "EXPERIMENTAL MODE ON" msgstr "MODO EXPERIMENTAL ACTIVADO" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:166 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:228 +#: selfdrive/ui/layouts/settings/developer.py:166 +#: selfdrive/ui/layouts/settings/toggles.py:228 #, python-format msgid "Enable" msgstr "Activar" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:39 +#: selfdrive/ui/layouts/settings/developer.py:39 #, python-format msgid "Enable ADB" msgstr "Activar ADB" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:64 +#: selfdrive/ui/layouts/settings/toggles.py:64 #, python-format msgid "Enable Lane Departure Warnings" msgstr "Activar advertencias de salida de carril" -#: /home/batman/openpilot/system/ui/widgets/network.py:129 +#: system/ui/widgets/network.py:129 #, python-format msgid "Enable Roaming" msgstr "Activar openpilot" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:48 +#: selfdrive/ui/layouts/settings/developer.py:48 #, python-format msgid "Enable SSH" msgstr "Activar SSH" -#: /home/batman/openpilot/system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:120 #, python-format msgid "Enable Tethering" msgstr "Activar advertencias de salida de carril" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:30 +#: selfdrive/ui/layouts/settings/toggles.py:30 msgid "Enable driver monitoring even when openpilot is not engaged." msgstr "" "Activar la supervisión del conductor incluso cuando openpilot no esté " "activado." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:46 +#: selfdrive/ui/layouts/settings/toggles.py:46 #, python-format msgid "Enable openpilot" msgstr "Activar openpilot" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:189 +#: selfdrive/ui/layouts/settings/toggles.py:189 #, python-format msgid "" "Enable the openpilot longitudinal control (alpha) toggle to allow " @@ -389,44 +382,42 @@ msgstr "" "Activa el interruptor de control longitudinal de openpilot (alpha) para " "permitir el modo Experimental." -#: /home/batman/openpilot/system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:204 #, python-format msgid "Enter APN" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:241 +#: system/ui/widgets/network.py:241 #, python-format msgid "Enter SSID" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:254 +#: system/ui/widgets/network.py:254 #, python-format msgid "Enter new tethering password" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:237 -#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 #, python-format msgid "Enter password" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:89 +#: selfdrive/ui/widgets/ssh_key.py:89 #, python-format msgid "Enter your GitHub username" msgstr "Introduce tu nombre de usuario de GitHub" -#: /home/batman/openpilot/system/ui/widgets/list_view.py:123 -#: /home/batman/openpilot/system/ui/widgets/list_view.py:160 +#: system/ui/widgets/list_view.py:123 system/ui/widgets/list_view.py:160 #, python-format msgid "Error" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:52 +#: selfdrive/ui/layouts/settings/toggles.py:52 #, python-format msgid "Experimental Mode" msgstr "Modo experimental" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:181 +#: selfdrive/ui/layouts/settings/toggles.py:181 #, python-format msgid "" "Experimental mode is currently unavailable on this car since the car's stock " @@ -435,25 +426,25 @@ msgstr "" "El modo experimental no está disponible actualmente en este coche, ya que se " "usa el ACC de fábrica para el control longitudinal." -#: /home/batman/openpilot/system/ui/widgets/network.py:373 +#: system/ui/widgets/network.py:373 #, python-format msgid "FORGETTING..." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:44 +#: selfdrive/ui/widgets/setup.py:44 #, python-format msgid "Finish Setup" msgstr "Finalizar configuración" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:66 +#: selfdrive/ui/layouts/settings/settings.py:66 msgid "Firehose" msgstr "Firehose" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:18 +#: selfdrive/ui/layouts/settings/firehose.py:18 msgid "Firehose Mode" msgstr "Modo Firehose" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:25 +#: selfdrive/ui/layouts/settings/firehose.py:25 msgid "" "For maximum effectiveness, bring your device inside and connect to a good " "USB-C adapter and Wi-Fi weekly.\n" @@ -496,81 +487,79 @@ msgstr "" "¿Importa qué software ejecuto? Sí, solo openpilot upstream (y forks " "particulares) pueden usarse para entrenamiento." -#: /home/batman/openpilot/system/ui/widgets/network.py:318 -#: /home/batman/openpilot/system/ui/widgets/network.py:451 +#: system/ui/widgets/network.py:318 system/ui/widgets/network.py:451 #, python-format msgid "Forget" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:319 +#: system/ui/widgets/network.py:319 #, python-format msgid "Forget Wi-Fi Network \"{}\"?" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:71 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 msgid "GOOD" msgstr "BUENO" -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:128 +#: selfdrive/ui/widgets/pairing_dialog.py:128 #, python-format msgid "Go to https://connect.comma.ai on your phone" msgstr "Ve a https://connect.comma.ai en tu teléfono" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:129 +#: selfdrive/ui/layouts/sidebar.py:129 msgid "HIGH" msgstr "ALTO" -#: /home/batman/openpilot/system/ui/widgets/network.py:155 +#: system/ui/widgets/network.py:155 #, python-format msgid "Hidden Network" msgstr "Red" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:140 +#: selfdrive/ui/layouts/settings/firehose.py:140 #, python-format msgid "INACTIVE: connect to an unmetered network" msgstr "INACTIVO: conéctate a una red sin límites" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:53 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:136 +#: selfdrive/ui/layouts/settings/software.py:53 +#: selfdrive/ui/layouts/settings/software.py:136 #, python-format msgid "INSTALL" msgstr "INSTALAR" -#: /home/batman/openpilot/system/ui/widgets/network.py:150 +#: system/ui/widgets/network.py:150 #, python-format msgid "IP Address" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:53 +#: selfdrive/ui/layouts/settings/software.py:53 #, python-format msgid "Install Update" msgstr "Instalar actualización" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:56 +#: selfdrive/ui/layouts/settings/developer.py:56 #, python-format msgid "Joystick Debug Mode" msgstr "Modo de depuración de joystick" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:29 +#: selfdrive/ui/widgets/ssh_key.py:29 msgid "LOADING" msgstr "CARGANDO" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:48 +#: selfdrive/ui/layouts/sidebar.py:48 msgid "LTE" msgstr "LTE" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:64 +#: selfdrive/ui/layouts/settings/developer.py:64 #, python-format msgid "Longitudinal Maneuver Mode" msgstr "Modo de maniobra longitudinal" -#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:148 +#: selfdrive/ui/onroad/hud_renderer.py:148 #, python-format msgid "MAX" msgstr "MÁX" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:75 +#: selfdrive/ui/widgets/setup.py:75 #, python-format msgid "" "Maximize your training data uploads to improve openpilot's driving models." @@ -578,94 +567,90 @@ msgstr "" "Maximiza tus cargas de datos de entrenamiento para mejorar los modelos de " "conducción de openpilot." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:59 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:60 +#: selfdrive/ui/layouts/settings/device.py:59 +#: selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "N/A" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:142 +#: selfdrive/ui/layouts/sidebar.py:142 msgid "NO" msgstr "NO" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:63 +#: selfdrive/ui/layouts/settings/settings.py:63 msgid "Network" msgstr "Red" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:114 +#: selfdrive/ui/widgets/ssh_key.py:114 #, python-format msgid "No SSH keys found" msgstr "No se encontraron claves SSH" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:126 +#: selfdrive/ui/widgets/ssh_key.py:126 #, python-format msgid "No SSH keys found for user '{}'" msgstr "No se encontraron claves SSH para el usuario '{username}'" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:320 +#: selfdrive/ui/widgets/offroad_alerts.py:320 #, python-format msgid "No release notes available." msgstr "No hay notas de versión disponibles." -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:73 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: selfdrive/ui/layouts/sidebar.py:73 selfdrive/ui/layouts/sidebar.py:134 msgid "OFFLINE" msgstr "SIN CONEXIÓN" -#: /home/batman/openpilot/system/ui/widgets/html_render.py:263 -#: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:93 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: system/ui/widgets/html_render.py:263 system/ui/widgets/confirm_dialog.py:93 +#: selfdrive/ui/layouts/sidebar.py:127 #, python-format msgid "OK" msgstr "OK" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:72 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:136 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:136 +#: selfdrive/ui/layouts/sidebar.py:144 msgid "ONLINE" msgstr "EN LÍNEA" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:20 +#: selfdrive/ui/widgets/setup.py:20 #, python-format msgid "Open" msgstr "Abrir" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:48 +#: selfdrive/ui/layouts/settings/device.py:48 #, python-format msgid "PAIR" msgstr "EMPAREJAR" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:142 +#: selfdrive/ui/layouts/sidebar.py:142 msgid "PANDA" msgstr "PANDA" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:62 +#: selfdrive/ui/layouts/settings/device.py:62 #, python-format msgid "PREVIEW" msgstr "VISTA PREVIA" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:44 +#: selfdrive/ui/widgets/prime.py:44 #, python-format msgid "PRIME FEATURES:" msgstr "FUNCIONES PRIME:" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:48 +#: selfdrive/ui/layouts/settings/device.py:48 #, python-format msgid "Pair Device" msgstr "Emparejar dispositivo" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:19 +#: selfdrive/ui/widgets/setup.py:19 #, python-format msgid "Pair device" msgstr "Emparejar dispositivo" -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:103 +#: selfdrive/ui/widgets/pairing_dialog.py:103 #, python-format msgid "Pair your device to your comma account" msgstr "Empareja tu dispositivo con tu cuenta de comma" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:48 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:24 +#: selfdrive/ui/widgets/setup.py:48 selfdrive/ui/layouts/settings/device.py:24 #, python-format msgid "" "Pair your device with comma connect (connect.comma.ai) and claim your comma " @@ -674,28 +659,28 @@ msgstr "" "Empareja tu dispositivo con comma connect (connect.comma.ai) y reclama tu " "oferta de comma prime." -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:91 +#: selfdrive/ui/widgets/setup.py:91 #, python-format msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "Conéctate a Wi‑Fi para completar el emparejamiento inicial" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:187 +#: selfdrive/ui/layouts/settings/device.py:55 +#: selfdrive/ui/layouts/settings/device.py:187 #, python-format msgid "Power Off" msgstr "Apagar" -#: /home/batman/openpilot/system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:144 #, python-format msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:135 +#: system/ui/widgets/network.py:135 #, python-format msgid "Prevent large data uploads when on a metered cellular connection" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:25 +#: selfdrive/ui/layouts/settings/device.py:25 msgid "" "Preview the driver facing camera to ensure that driver monitoring has good " "visibility. (vehicle must be off)" @@ -704,42 +689,42 @@ msgstr "" "supervisión del conductor tenga buena visibilidad. (el vehículo debe estar " "apagado)" -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:161 +#: selfdrive/ui/widgets/pairing_dialog.py:161 #, python-format msgid "QR Code Error" msgstr "Error de código QR" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:31 +#: selfdrive/ui/widgets/ssh_key.py:31 msgid "REMOVE" msgstr "ELIMINAR" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:51 +#: selfdrive/ui/layouts/settings/device.py:51 #, python-format msgid "RESET" msgstr "RESTABLECER" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:65 +#: selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "REVIEW" msgstr "REVISAR" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:175 +#: selfdrive/ui/layouts/settings/device.py:55 +#: selfdrive/ui/layouts/settings/device.py:175 #, python-format msgid "Reboot" msgstr "Reiniciar" -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:66 +#: selfdrive/ui/onroad/alert_renderer.py:66 #, python-format msgid "Reboot Device" msgstr "Reiniciar dispositivo" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 +#: selfdrive/ui/widgets/offroad_alerts.py:112 #, python-format msgid "Reboot and Update" msgstr "Reiniciar y actualizar" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:27 +#: selfdrive/ui/layouts/settings/toggles.py:27 msgid "" "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 " @@ -749,17 +734,17 @@ msgstr "" "línea de carril detectada sin la direccional activada mientras conduces a " "más de 31 mph (50 km/h)." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:76 +#: selfdrive/ui/layouts/settings/toggles.py:76 #, python-format msgid "Record and Upload Driver Camera" msgstr "Grabar y subir cámara del conductor" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:82 +#: selfdrive/ui/layouts/settings/toggles.py:82 #, python-format msgid "Record and Upload Microphone Audio" msgstr "Grabar y subir audio del micrófono" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +#: selfdrive/ui/layouts/settings/toggles.py:33 msgid "" "Record and store microphone audio while driving. The audio will be included " "in the dashcam video in comma connect." @@ -767,100 +752,100 @@ msgstr "" "Grabar y almacenar audio del micrófono mientras conduces. El audio se " "incluirá en el video de la dashcam en comma connect." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:67 +#: selfdrive/ui/layouts/settings/device.py:67 #, python-format msgid "Regulatory" msgstr "Reglamentario" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#: selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Relaxed" msgstr "Relajado" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#: selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote access" msgstr "Acceso remoto" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#: selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote snapshots" msgstr "Capturas remotas" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:123 +#: selfdrive/ui/widgets/ssh_key.py:123 #, python-format msgid "Request timed out" msgstr "Se agotó el tiempo de espera de la solicitud" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:119 +#: selfdrive/ui/layouts/settings/device.py:119 #, python-format msgid "Reset" msgstr "Restablecer" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:51 +#: selfdrive/ui/layouts/settings/device.py:51 #, python-format msgid "Reset Calibration" msgstr "Restablecer calibración" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:65 +#: selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "Review Training Guide" msgstr "Revisar guía de entrenamiento" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:27 +#: selfdrive/ui/layouts/settings/device.py:27 msgid "Review the rules, features, and limitations of openpilot" msgstr "Revisa las reglas, funciones y limitaciones de openpilot" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:61 +#: selfdrive/ui/layouts/settings/software.py:61 #, python-format msgid "SELECT" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:53 +#: selfdrive/ui/layouts/settings/developer.py:53 #, python-format msgid "SSH Keys" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:310 +#: system/ui/widgets/network.py:310 #, python-format msgid "Scanning Wi-Fi networks..." msgstr "" -#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/option_dialog.py:36 #, python-format msgid "Select" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:183 +#: selfdrive/ui/layouts/settings/software.py:183 #, python-format msgid "Select a branch" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:91 +#: selfdrive/ui/layouts/settings/device.py:91 #, python-format msgid "Select a language" msgstr "Selecciona un idioma" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:60 +#: selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "Serial" msgstr "Número de serie" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 +#: selfdrive/ui/widgets/offroad_alerts.py:106 #, python-format msgid "Snooze Update" msgstr "Posponer actualización" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:65 +#: selfdrive/ui/layouts/settings/settings.py:65 msgid "Software" msgstr "Software" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#: selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Standard" msgstr "Estándar" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:22 +#: selfdrive/ui/layouts/settings/toggles.py:22 msgid "" "Standard is recommended. In aggressive mode, openpilot will follow lead cars " "closer and be more aggressive with the gas and brake. In relaxed mode " @@ -873,69 +858,67 @@ msgstr "" "coches compatibles, puedes cambiar entre estas personalidades con el botón " "de distancia del volante." -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:59 -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:65 +#: selfdrive/ui/onroad/alert_renderer.py:59 +#: selfdrive/ui/onroad/alert_renderer.py:65 #, python-format msgid "System Unresponsive" msgstr "Sistema sin respuesta" -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:58 +#: selfdrive/ui/onroad/alert_renderer.py:58 #, python-format msgid "TAKE CONTROL IMMEDIATELY" msgstr "TOME EL CONTROL INMEDIATAMENTE" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:71 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:125 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:127 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:129 +#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 +#: selfdrive/ui/layouts/sidebar.py:127 selfdrive/ui/layouts/sidebar.py:129 msgid "TEMP" msgstr "TEMP" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:61 +#: selfdrive/ui/layouts/settings/software.py:61 #, python-format msgid "Target Branch" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:124 +#: system/ui/widgets/network.py:124 #, python-format msgid "Tethering Password" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:64 +#: selfdrive/ui/layouts/settings/settings.py:64 msgid "Toggles" msgstr "Interruptores" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:72 +#: selfdrive/ui/layouts/settings/software.py:72 #, python-format msgid "UNINSTALL" msgstr "DESINSTALAR" -#: /home/batman/openpilot/selfdrive/ui/layouts/home.py:155 +#: selfdrive/ui/layouts/home.py:155 #, python-format msgid "UPDATE" msgstr "ACTUALIZAR" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:72 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:163 +#: selfdrive/ui/layouts/settings/software.py:72 +#: selfdrive/ui/layouts/settings/software.py:163 #, python-format msgid "Uninstall" msgstr "Desinstalar" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:117 +#: selfdrive/ui/layouts/sidebar.py:117 msgid "Unknown" msgstr "Desconocido" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:48 +#: selfdrive/ui/layouts/settings/software.py:48 #, python-format msgid "Updates are only downloaded while the car is off." msgstr "Las actualizaciones solo se descargan cuando el coche está apagado." -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:33 +#: selfdrive/ui/widgets/prime.py:33 #, python-format msgid "Upgrade Now" msgstr "Mejorar ahora" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +#: selfdrive/ui/layouts/settings/toggles.py:31 msgid "" "Upload data from the driver facing camera and help improve the driver " "monitoring algorithm." @@ -943,12 +926,12 @@ msgstr "" "Sube datos de la cámara orientada al conductor y ayuda a mejorar el " "algoritmo de supervisión del conductor." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:88 +#: selfdrive/ui/layouts/settings/toggles.py:88 #, python-format msgid "Use Metric System" msgstr "Usar sistema métrico" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:17 +#: selfdrive/ui/layouts/settings/toggles.py:17 msgid "" "Use the openpilot system for adaptive cruise control and lane keep driver " "assistance. Your attention is required at all times to use this feature." @@ -957,22 +940,21 @@ msgstr "" "mantenimiento de carril. Tu atención se requiere en todo momento para usar " "esta función." -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:72 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:144 msgid "VEHICLE" msgstr "VEHÍCULO" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:67 +#: selfdrive/ui/layouts/settings/device.py:67 #, python-format msgid "VIEW" msgstr "VER" -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:52 +#: selfdrive/ui/onroad/alert_renderer.py:52 #, python-format msgid "Waiting to start" msgstr "Esperando para iniciar" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:19 +#: selfdrive/ui/layouts/settings/developer.py:19 msgid "" "Warning: This grants SSH access to all public keys in your GitHub settings. " "Never enter a GitHub username other than your own. A comma employee will " @@ -983,37 +965,37 @@ msgstr "" "que no sea el tuyo. Un empleado de comma NUNCA te pedirá que agregues su " "nombre de usuario de GitHub." -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:111 +#: selfdrive/ui/layouts/onboarding.py:111 #, python-format msgid "Welcome to openpilot" msgstr "Bienvenido a openpilot" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:20 +#: selfdrive/ui/layouts/settings/toggles.py:20 msgid "When enabled, pressing the accelerator pedal will disengage openpilot." msgstr "" "Cuando está activado, al presionar el pedal del acelerador se desactivará " "openpilot." -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:44 +#: selfdrive/ui/layouts/sidebar.py:44 msgid "Wi-Fi" msgstr "Wi‑Fi" -#: /home/batman/openpilot/system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:144 #, python-format msgid "Wi-Fi Network Metered" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:314 #, python-format msgid "Wrong password" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:145 +#: selfdrive/ui/layouts/onboarding.py:145 #, python-format msgid "You must accept the Terms and Conditions in order to use openpilot." msgstr "Debes aceptar los Términos y Condiciones para poder usar openpilot." -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:112 +#: selfdrive/ui/layouts/onboarding.py:112 #, python-format msgid "" "You must accept the Terms and Conditions to use openpilot. Read the latest " @@ -1022,83 +1004,82 @@ msgstr "" "Debes aceptar los Términos y Condiciones para usar openpilot. Lee los " "términos más recientes en https://comma.ai/terms antes de continuar." -#: /home/batman/openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:34 +#: selfdrive/ui/onroad/driver_camera_dialog.py:34 #, python-format msgid "camera starting" msgstr "iniciando cámara" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:63 +#: selfdrive/ui/widgets/prime.py:63 #, python-format msgid "comma prime" msgstr "comma prime" -#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:142 #, python-format msgid "default" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#: selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid "down" msgstr "abajo" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:106 +#: selfdrive/ui/layouts/settings/software.py:106 #, python-format msgid "failed to check for update" msgstr "Error al buscar actualizaciones" -#: /home/batman/openpilot/system/ui/widgets/network.py:237 -#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 #, python-format msgid "for \"{}\"" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:177 +#: selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "km/h" msgstr "km/h" -#: /home/batman/openpilot/system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:204 #, python-format msgid "leave blank for automatic configuration" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:134 +#: selfdrive/ui/layouts/settings/device.py:134 #, python-format msgid "left" msgstr "izquierda" -#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:142 #, python-format msgid "metered" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:177 +#: selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "mph" msgstr "mph" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:20 +#: selfdrive/ui/layouts/settings/software.py:20 #, python-format msgid "never" msgstr "nunca" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:31 +#: selfdrive/ui/layouts/settings/software.py:31 #, python-format msgid "now" msgstr "ahora" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:71 +#: selfdrive/ui/layouts/settings/developer.py:71 #, python-format msgid "openpilot Longitudinal Control (Alpha)" msgstr "Control longitudinal de openpilot (Alpha)" -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:51 +#: selfdrive/ui/onroad/alert_renderer.py:51 #, python-format msgid "openpilot Unavailable" msgstr "openpilot no disponible" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:158 +#: selfdrive/ui/layouts/settings/toggles.py:158 #, python-format msgid "" "openpilot defaults to driving in chill mode. Experimental mode enables alpha-" @@ -1127,7 +1108,7 @@ msgstr "" "mostrar mejor algunos giros. El logotipo del modo Experimental también se " "mostrará en la esquina superior derecha." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:165 +#: selfdrive/ui/layouts/settings/device.py:165 #, python-format msgid "" "openpilot is continuously calibrating, resetting is rarely required. " @@ -1135,7 +1116,7 @@ msgid "" msgstr "" " Cambiar esta configuración reiniciará openpilot si el coche está encendido." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:20 +#: selfdrive/ui/layouts/settings/firehose.py:20 msgid "" "openpilot learns to drive by watching humans, like you, drive.\n" "\n" @@ -1149,14 +1130,14 @@ msgstr "" "para mejorar los modelos de conducción de openpilot. Más datos significan " "modelos más grandes, lo que significa un mejor Modo Experimental." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:183 +#: selfdrive/ui/layouts/settings/toggles.py:183 #, python-format msgid "openpilot longitudinal control may come in a future update." msgstr "" "El control longitudinal de openpilot podría llegar en una actualización " "futura." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:26 +#: selfdrive/ui/layouts/settings/device.py:26 msgid "" "openpilot requires the device to be mounted within 4° left or right and " "within 5° up or 9° down." @@ -1164,65 +1145,65 @@ msgstr "" "openpilot requiere que el dispositivo esté montado dentro de 4° a izquierda " "o derecha y dentro de 5° hacia arriba o 9° hacia abajo." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:134 +#: selfdrive/ui/layouts/settings/device.py:134 #, python-format msgid "right" msgstr "derecha" -#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:142 #, python-format msgid "unmetered" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#: selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid "up" msgstr "arriba" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:117 +#: selfdrive/ui/layouts/settings/software.py:117 #, python-format msgid "up to date, last checked never" msgstr "actualizado, última comprobación: nunca" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:115 +#: selfdrive/ui/layouts/settings/software.py:115 #, python-format msgid "up to date, last checked {}" msgstr "actualizado, última comprobación: {}" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:109 +#: selfdrive/ui/layouts/settings/software.py:109 #, python-format msgid "update available" msgstr "actualización disponible" -#: /home/batman/openpilot/selfdrive/ui/layouts/home.py:169 +#: selfdrive/ui/layouts/home.py:169 #, python-format msgid "{} ALERT" msgid_plural "{} ALERTS" msgstr[0] "{} ALERTA" msgstr[1] "{} ALERTAS" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:40 +#: selfdrive/ui/layouts/settings/software.py:40 #, python-format msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "hace {} día" msgstr[1] "hace {} días" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:37 +#: selfdrive/ui/layouts/settings/software.py:37 #, python-format msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "hace {} hora" msgstr[1] "hace {} horas" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:34 +#: selfdrive/ui/layouts/settings/software.py:34 #, python-format msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "hace {} minuto" msgstr[1] "hace {} minutos" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:111 +#: selfdrive/ui/layouts/settings/firehose.py:111 #, python-format msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." @@ -1233,12 +1214,12 @@ msgstr[1] "" "{} segmentos de tu conducción están en el conjunto de entrenamiento hasta " "ahora." -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:62 +#: selfdrive/ui/widgets/prime.py:62 #, python-format msgid "✓ SUBSCRIBED" msgstr "✓ SUSCRITO" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:22 +#: selfdrive/ui/widgets/setup.py:22 #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "🔥 Modo Firehose 🔥" diff --git a/selfdrive/ui/translations/app_fr.po b/selfdrive/ui/translations/app_fr.po index 4b0370403..f883d4d48 100644 --- a/selfdrive/ui/translations/app_fr.po +++ b/selfdrive/ui/translations/app_fr.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-22 18:57-0700\n" +"POT-Creation-Date: 2025-10-23 00:50-0700\n" "PO-Revision-Date: 2025-10-20 18:19-0700\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" @@ -17,48 +17,48 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:160 +#: selfdrive/ui/layouts/settings/device.py:160 #, python-format msgid " Steering torque response calibration is complete." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:158 +#: selfdrive/ui/layouts/settings/device.py:158 #, python-format msgid " Steering torque response calibration is {}% complete." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#: selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:43 +#: selfdrive/ui/layouts/sidebar.py:43 msgid "--" msgstr "--" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#: selfdrive/ui/widgets/prime.py:47 #, python-format msgid "1 year of drive storage" msgstr "1 an de stockage de trajets" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#: selfdrive/ui/widgets/prime.py:47 #, python-format msgid "24/7 LTE connectivity" msgstr "Connexion LTE 24/7" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:46 +#: selfdrive/ui/layouts/sidebar.py:46 msgid "2G" msgstr "2G" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:47 +#: selfdrive/ui/layouts/sidebar.py:47 msgid "3G" msgstr "3G" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:49 +#: selfdrive/ui/layouts/sidebar.py:49 msgid "5G" msgstr "5G" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:23 +#: selfdrive/ui/layouts/settings/developer.py:23 msgid "" "WARNING: openpilot longitudinal control is in alpha for this car and will " "disable Automatic Emergency Braking (AEB).

On this car, openpilot " @@ -76,22 +76,22 @@ msgstr "" "est recommandé d'activer le mode expérimental lors de l'activation du " "contrôle longitudinal openpilot alpha." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:148 +#: selfdrive/ui/layouts/settings/device.py:148 #, python-format msgid "

Steering lag calibration is complete." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:146 +#: selfdrive/ui/layouts/settings/device.py:146 #, python-format msgid "

Steering lag calibration is {}% complete." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:138 +#: selfdrive/ui/layouts/settings/firehose.py:138 #, python-format msgid "ACTIVE" msgstr "ACTIF" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:15 +#: selfdrive/ui/layouts/settings/developer.py:15 msgid "" "ADB (Android Debug Bridge) allows connecting to your device over USB or over " "the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." @@ -100,42 +100,41 @@ msgstr "" "le réseau. Voir https://docs.comma.ai/how-to/connect-to-comma pour plus " "d'informations." -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:30 +#: selfdrive/ui/widgets/ssh_key.py:30 msgid "ADD" msgstr "AJOUTER" -#: /home/batman/openpilot/system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:139 #, python-format msgid "APN Setting" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 +#: selfdrive/ui/widgets/offroad_alerts.py:109 #, python-format msgid "Acknowledge Excessive Actuation" msgstr "Accuser réception d'actionnement excessif" -#: /home/batman/openpilot/system/ui/widgets/network.py:74 -#: /home/batman/openpilot/system/ui/widgets/network.py:95 +#: system/ui/widgets/network.py:74 system/ui/widgets/network.py:95 #, python-format msgid "Advanced" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#: selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Aggressive" msgstr "Agressif" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:116 +#: selfdrive/ui/layouts/onboarding.py:116 #, python-format msgid "Agree" msgstr "Accepter" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:70 +#: selfdrive/ui/layouts/settings/toggles.py:70 #, python-format msgid "Always-On Driver Monitoring" msgstr "Surveillance continue du conducteur" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:186 +#: selfdrive/ui/layouts/settings/toggles.py:186 #, python-format msgid "" "An alpha version of openpilot longitudinal control can be tested, along with " @@ -144,245 +143,239 @@ msgstr "" "Une version alpha du contrôle longitudinal openpilot peut être testée, avec " "le mode expérimental, sur des branches non publiées." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:187 +#: selfdrive/ui/layouts/settings/device.py:187 #, python-format msgid "Are you sure you want to power off?" msgstr "Êtes-vous sûr de vouloir éteindre ?" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:175 +#: selfdrive/ui/layouts/settings/device.py:175 #, python-format msgid "Are you sure you want to reboot?" msgstr "Êtes-vous sûr de vouloir redémarrer ?" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:119 +#: selfdrive/ui/layouts/settings/device.py:119 #, python-format msgid "Are you sure you want to reset calibration?" msgstr "Êtes-vous sûr de vouloir réinitialiser la calibration ?" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:163 +#: selfdrive/ui/layouts/settings/software.py:163 #, python-format msgid "Are you sure you want to uninstall?" msgstr "Êtes-vous sûr de vouloir désinstaller ?" -#: /home/batman/openpilot/system/ui/widgets/network.py:99 -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:147 +#: system/ui/widgets/network.py:99 selfdrive/ui/layouts/onboarding.py:147 #, python-format msgid "Back" msgstr "Retour" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:38 +#: selfdrive/ui/widgets/prime.py:38 #, python-format msgid "Become a comma prime member at connect.comma.ai" msgstr "Devenez membre comma prime sur connect.comma.ai" -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:130 +#: selfdrive/ui/widgets/pairing_dialog.py:130 #, python-format msgid "Bookmark connect.comma.ai to your home screen to use it like an app" msgstr "" "Ajoutez connect.comma.ai à votre écran d'accueil pour l'utiliser comme une " "application" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:68 +#: selfdrive/ui/layouts/settings/device.py:68 #, python-format msgid "CHANGE" msgstr "CHANGER" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:50 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:107 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:118 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:147 +#: selfdrive/ui/layouts/settings/software.py:50 +#: selfdrive/ui/layouts/settings/software.py:107 +#: selfdrive/ui/layouts/settings/software.py:118 +#: selfdrive/ui/layouts/settings/software.py:147 #, python-format msgid "CHECK" msgstr "VÉRIFIER" -#: /home/batman/openpilot/selfdrive/ui/widgets/exp_mode_button.py:50 +#: selfdrive/ui/widgets/exp_mode_button.py:50 #, python-format msgid "CHILL MODE ON" msgstr "MODE CHILL ACTIVÉ" -#: /home/batman/openpilot/system/ui/widgets/network.py:155 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:73 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:134 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:136 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:138 +#: system/ui/widgets/network.py:155 selfdrive/ui/layouts/sidebar.py:73 +#: selfdrive/ui/layouts/sidebar.py:134 selfdrive/ui/layouts/sidebar.py:136 +#: selfdrive/ui/layouts/sidebar.py:138 #, python-format msgid "CONNECT" msgstr "CONNECTER" -#: /home/batman/openpilot/system/ui/widgets/network.py:369 +#: system/ui/widgets/network.py:369 #, python-format msgid "CONNECTING..." msgstr "CONNECTER" -#: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:23 -#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:35 -#: /home/batman/openpilot/system/ui/widgets/keyboard.py:81 -#: /home/batman/openpilot/system/ui/widgets/network.py:318 +#: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35 +#: system/ui/widgets/keyboard.py:81 system/ui/widgets/network.py:318 #, python-format msgid "Cancel" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:134 +#: system/ui/widgets/network.py:134 #, python-format msgid "Cellular Metered" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:68 +#: selfdrive/ui/layouts/settings/device.py:68 #, python-format msgid "Change Language" msgstr "Changer la langue" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:125 +#: selfdrive/ui/layouts/settings/toggles.py:125 #, python-format msgid "Changing this setting will restart openpilot if the car is powered on." msgstr "" " La modification de ce réglage redémarrera openpilot si la voiture est sous " "tension." -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:129 +#: selfdrive/ui/widgets/pairing_dialog.py:129 #, python-format msgid "Click \"add new device\" and scan the QR code on the right" msgstr "Cliquez sur \"add new device\" et scannez le code QR à droite" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 +#: selfdrive/ui/widgets/offroad_alerts.py:104 #, python-format msgid "Close" msgstr "Fermer" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:49 +#: selfdrive/ui/layouts/settings/software.py:49 #, python-format msgid "Current Version" msgstr "Version actuelle" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:110 +#: selfdrive/ui/layouts/settings/software.py:110 #, python-format msgid "DOWNLOAD" msgstr "TÉLÉCHARGER" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:115 +#: selfdrive/ui/layouts/onboarding.py:115 #, python-format msgid "Decline" msgstr "Refuser" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:148 +#: selfdrive/ui/layouts/onboarding.py:148 #, python-format msgid "Decline, uninstall openpilot" msgstr "Refuser, désinstaller openpilot" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:67 +#: selfdrive/ui/layouts/settings/settings.py:67 msgid "Developer" msgstr "Développeur" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:62 +#: selfdrive/ui/layouts/settings/settings.py:62 msgid "Device" msgstr "Appareil" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:58 +#: selfdrive/ui/layouts/settings/toggles.py:58 #, python-format msgid "Disengage on Accelerator Pedal" msgstr "Désengager à l'appui sur l'accélérateur" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:184 +#: selfdrive/ui/layouts/settings/device.py:184 #, python-format msgid "Disengage to Power Off" msgstr "Désengager pour éteindre" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:172 +#: selfdrive/ui/layouts/settings/device.py:172 #, python-format msgid "Disengage to Reboot" msgstr "Désengager pour redémarrer" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:103 +#: selfdrive/ui/layouts/settings/device.py:103 #, python-format msgid "Disengage to Reset Calibration" msgstr "Désengager pour réinitialiser la calibration" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:32 +#: selfdrive/ui/layouts/settings/toggles.py:32 msgid "Display speed in km/h instead of mph." msgstr "Afficher la vitesse en km/h au lieu de mph." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:59 +#: selfdrive/ui/layouts/settings/device.py:59 #, python-format msgid "Dongle ID" msgstr "ID du dongle" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:50 +#: selfdrive/ui/layouts/settings/software.py:50 #, python-format msgid "Download" msgstr "Télécharger" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:62 +#: selfdrive/ui/layouts/settings/device.py:62 #, python-format msgid "Driver Camera" msgstr "Caméra conducteur" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:96 +#: selfdrive/ui/layouts/settings/toggles.py:96 #, python-format msgid "Driving Personality" msgstr "Personnalité de conduite" -#: /home/batman/openpilot/system/ui/widgets/network.py:123 -#: /home/batman/openpilot/system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:123 system/ui/widgets/network.py:139 #, python-format msgid "EDIT" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:138 +#: selfdrive/ui/layouts/sidebar.py:138 msgid "ERROR" msgstr "ERREUR" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:45 +#: selfdrive/ui/layouts/sidebar.py:45 msgid "ETH" msgstr "ETH" -#: /home/batman/openpilot/selfdrive/ui/widgets/exp_mode_button.py:50 +#: selfdrive/ui/widgets/exp_mode_button.py:50 #, python-format msgid "EXPERIMENTAL MODE ON" msgstr "MODE EXPÉRIMENTAL ACTIVÉ" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:166 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:228 +#: selfdrive/ui/layouts/settings/developer.py:166 +#: selfdrive/ui/layouts/settings/toggles.py:228 #, python-format msgid "Enable" msgstr "Activer" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:39 +#: selfdrive/ui/layouts/settings/developer.py:39 #, python-format msgid "Enable ADB" msgstr "Activer ADB" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:64 +#: selfdrive/ui/layouts/settings/toggles.py:64 #, python-format msgid "Enable Lane Departure Warnings" msgstr "Activer les alertes de sortie de voie" -#: /home/batman/openpilot/system/ui/widgets/network.py:129 +#: system/ui/widgets/network.py:129 #, python-format msgid "Enable Roaming" msgstr "Activer openpilot" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:48 +#: selfdrive/ui/layouts/settings/developer.py:48 #, python-format msgid "Enable SSH" msgstr "Activer SSH" -#: /home/batman/openpilot/system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:120 #, python-format msgid "Enable Tethering" msgstr "Activer les alertes de sortie de voie" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:30 +#: selfdrive/ui/layouts/settings/toggles.py:30 msgid "Enable driver monitoring even when openpilot is not engaged." msgstr "" "Activer la surveillance du conducteur même lorsque openpilot n'est pas " "engagé." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:46 +#: selfdrive/ui/layouts/settings/toggles.py:46 #, python-format msgid "Enable openpilot" msgstr "Activer openpilot" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:189 +#: selfdrive/ui/layouts/settings/toggles.py:189 #, python-format msgid "" "Enable the openpilot longitudinal control (alpha) toggle to allow " @@ -391,44 +384,42 @@ msgstr "" "Activez l'option de contrôle longitudinal openpilot (alpha) pour autoriser " "le mode expérimental." -#: /home/batman/openpilot/system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:204 #, python-format msgid "Enter APN" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:241 +#: system/ui/widgets/network.py:241 #, python-format msgid "Enter SSID" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:254 +#: system/ui/widgets/network.py:254 #, python-format msgid "Enter new tethering password" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:237 -#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 #, python-format msgid "Enter password" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:89 +#: selfdrive/ui/widgets/ssh_key.py:89 #, python-format msgid "Enter your GitHub username" msgstr "Entrez votre nom d'utilisateur GitHub" -#: /home/batman/openpilot/system/ui/widgets/list_view.py:123 -#: /home/batman/openpilot/system/ui/widgets/list_view.py:160 +#: system/ui/widgets/list_view.py:123 system/ui/widgets/list_view.py:160 #, python-format msgid "Error" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:52 +#: selfdrive/ui/layouts/settings/toggles.py:52 #, python-format msgid "Experimental Mode" msgstr "Mode expérimental" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:181 +#: selfdrive/ui/layouts/settings/toggles.py:181 #, python-format msgid "" "Experimental mode is currently unavailable on this car since the car's stock " @@ -437,25 +428,25 @@ msgstr "" "Le mode expérimental est actuellement indisponible sur cette voiture car " "l'ACC d'origine est utilisé pour le contrôle longitudinal." -#: /home/batman/openpilot/system/ui/widgets/network.py:373 +#: system/ui/widgets/network.py:373 #, python-format msgid "FORGETTING..." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:44 +#: selfdrive/ui/widgets/setup.py:44 #, python-format msgid "Finish Setup" msgstr "Terminer la configuration" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:66 +#: selfdrive/ui/layouts/settings/settings.py:66 msgid "Firehose" msgstr "Firehose" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:18 +#: selfdrive/ui/layouts/settings/firehose.py:18 msgid "Firehose Mode" msgstr "Mode Firehose" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:25 +#: selfdrive/ui/layouts/settings/firehose.py:25 msgid "" "For maximum effectiveness, bring your device inside and connect to a good " "USB-C adapter and Wi-Fi weekly.\n" @@ -499,81 +490,79 @@ msgstr "" "Le logiciel utilisé importe-t-il ? Oui, seul openpilot amont (et certains " "forks) peut être utilisé pour l'entraînement." -#: /home/batman/openpilot/system/ui/widgets/network.py:318 -#: /home/batman/openpilot/system/ui/widgets/network.py:451 +#: system/ui/widgets/network.py:318 system/ui/widgets/network.py:451 #, python-format msgid "Forget" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:319 +#: system/ui/widgets/network.py:319 #, python-format msgid "Forget Wi-Fi Network \"{}\"?" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:71 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 msgid "GOOD" msgstr "BON" -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:128 +#: selfdrive/ui/widgets/pairing_dialog.py:128 #, python-format msgid "Go to https://connect.comma.ai on your phone" msgstr "Allez sur https://connect.comma.ai sur votre téléphone" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:129 +#: selfdrive/ui/layouts/sidebar.py:129 msgid "HIGH" msgstr "ÉLEVÉ" -#: /home/batman/openpilot/system/ui/widgets/network.py:155 +#: system/ui/widgets/network.py:155 #, python-format msgid "Hidden Network" msgstr "Réseau" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:140 +#: selfdrive/ui/layouts/settings/firehose.py:140 #, python-format msgid "INACTIVE: connect to an unmetered network" msgstr "INACTIF : connectez-vous à un réseau non limité" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:53 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:136 +#: selfdrive/ui/layouts/settings/software.py:53 +#: selfdrive/ui/layouts/settings/software.py:136 #, python-format msgid "INSTALL" msgstr "INSTALLER" -#: /home/batman/openpilot/system/ui/widgets/network.py:150 +#: system/ui/widgets/network.py:150 #, python-format msgid "IP Address" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:53 +#: selfdrive/ui/layouts/settings/software.py:53 #, python-format msgid "Install Update" msgstr "Installer la mise à jour" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:56 +#: selfdrive/ui/layouts/settings/developer.py:56 #, python-format msgid "Joystick Debug Mode" msgstr "Mode débogage joystick" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:29 +#: selfdrive/ui/widgets/ssh_key.py:29 msgid "LOADING" msgstr "CHARGEMENT" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:48 +#: selfdrive/ui/layouts/sidebar.py:48 msgid "LTE" msgstr "LTE" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:64 +#: selfdrive/ui/layouts/settings/developer.py:64 #, python-format msgid "Longitudinal Maneuver Mode" msgstr "Mode de manœuvre longitudinale" -#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:148 +#: selfdrive/ui/onroad/hud_renderer.py:148 #, python-format msgid "MAX" msgstr "MAX" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:75 +#: selfdrive/ui/widgets/setup.py:75 #, python-format msgid "" "Maximize your training data uploads to improve openpilot's driving models." @@ -581,94 +570,90 @@ msgstr "" "Maximisez vos envois de données d'entraînement pour améliorer les modèles de " "conduite d'openpilot." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:59 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:60 +#: selfdrive/ui/layouts/settings/device.py:59 +#: selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "N/A" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:142 +#: selfdrive/ui/layouts/sidebar.py:142 msgid "NO" msgstr "NON" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:63 +#: selfdrive/ui/layouts/settings/settings.py:63 msgid "Network" msgstr "Réseau" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:114 +#: selfdrive/ui/widgets/ssh_key.py:114 #, python-format msgid "No SSH keys found" msgstr "Aucune clé SSH trouvée" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:126 +#: selfdrive/ui/widgets/ssh_key.py:126 #, python-format msgid "No SSH keys found for user '{}'" msgstr "Aucune clé SSH trouvée pour l'utilisateur '{username}'" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:320 +#: selfdrive/ui/widgets/offroad_alerts.py:320 #, python-format msgid "No release notes available." msgstr "Aucune note de version disponible." -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:73 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: selfdrive/ui/layouts/sidebar.py:73 selfdrive/ui/layouts/sidebar.py:134 msgid "OFFLINE" msgstr "HORS LIGNE" -#: /home/batman/openpilot/system/ui/widgets/html_render.py:263 -#: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:93 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: system/ui/widgets/html_render.py:263 system/ui/widgets/confirm_dialog.py:93 +#: selfdrive/ui/layouts/sidebar.py:127 #, python-format msgid "OK" msgstr "OK" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:72 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:136 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:136 +#: selfdrive/ui/layouts/sidebar.py:144 msgid "ONLINE" msgstr "EN LIGNE" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:20 +#: selfdrive/ui/widgets/setup.py:20 #, python-format msgid "Open" msgstr "Ouvrir" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:48 +#: selfdrive/ui/layouts/settings/device.py:48 #, python-format msgid "PAIR" msgstr "ASSOCIER" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:142 +#: selfdrive/ui/layouts/sidebar.py:142 msgid "PANDA" msgstr "PANDA" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:62 +#: selfdrive/ui/layouts/settings/device.py:62 #, python-format msgid "PREVIEW" msgstr "APERÇU" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:44 +#: selfdrive/ui/widgets/prime.py:44 #, python-format msgid "PRIME FEATURES:" msgstr "FONCTIONNALITÉS PRIME :" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:48 +#: selfdrive/ui/layouts/settings/device.py:48 #, python-format msgid "Pair Device" msgstr "Associer l'appareil" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:19 +#: selfdrive/ui/widgets/setup.py:19 #, python-format msgid "Pair device" msgstr "Associer l'appareil" -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:103 +#: selfdrive/ui/widgets/pairing_dialog.py:103 #, python-format msgid "Pair your device to your comma account" msgstr "Associez votre appareil à votre compte comma" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:48 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:24 +#: selfdrive/ui/widgets/setup.py:48 selfdrive/ui/layouts/settings/device.py:24 #, python-format msgid "" "Pair your device with comma connect (connect.comma.ai) and claim your comma " @@ -677,28 +662,28 @@ msgstr "" "Associez votre appareil à comma connect (connect.comma.ai) et réclamez votre " "offre comma prime." -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:91 +#: selfdrive/ui/widgets/setup.py:91 #, python-format msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "Veuillez vous connecter au Wi‑Fi pour terminer l'association initiale" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:187 +#: selfdrive/ui/layouts/settings/device.py:55 +#: selfdrive/ui/layouts/settings/device.py:187 #, python-format msgid "Power Off" msgstr "Éteindre" -#: /home/batman/openpilot/system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:144 #, python-format msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:135 +#: system/ui/widgets/network.py:135 #, python-format msgid "Prevent large data uploads when on a metered cellular connection" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:25 +#: selfdrive/ui/layouts/settings/device.py:25 msgid "" "Preview the driver facing camera to ensure that driver monitoring has good " "visibility. (vehicle must be off)" @@ -707,42 +692,42 @@ msgstr "" "surveillance du conducteur a une bonne visibilité. (le véhicule doit être " "éteint)" -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:161 +#: selfdrive/ui/widgets/pairing_dialog.py:161 #, python-format msgid "QR Code Error" msgstr "Erreur de code QR" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:31 +#: selfdrive/ui/widgets/ssh_key.py:31 msgid "REMOVE" msgstr "SUPPRIMER" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:51 +#: selfdrive/ui/layouts/settings/device.py:51 #, python-format msgid "RESET" msgstr "RÉINITIALISER" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:65 +#: selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "REVIEW" msgstr "CONSULTER" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:175 +#: selfdrive/ui/layouts/settings/device.py:55 +#: selfdrive/ui/layouts/settings/device.py:175 #, python-format msgid "Reboot" msgstr "Redémarrer" -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:66 +#: selfdrive/ui/onroad/alert_renderer.py:66 #, python-format msgid "Reboot Device" msgstr "Redémarrer l'appareil" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 +#: selfdrive/ui/widgets/offroad_alerts.py:112 #, python-format msgid "Reboot and Update" msgstr "Redémarrer et mettre à jour" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:27 +#: selfdrive/ui/layouts/settings/toggles.py:27 msgid "" "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 " @@ -752,17 +737,17 @@ msgstr "" "une ligne de voie détectée sans clignotant activé en roulant au-delà de 31 " "mph (50 km/h)." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:76 +#: selfdrive/ui/layouts/settings/toggles.py:76 #, python-format msgid "Record and Upload Driver Camera" msgstr "Enregistrer et téléverser la caméra conducteur" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:82 +#: selfdrive/ui/layouts/settings/toggles.py:82 #, python-format msgid "Record and Upload Microphone Audio" msgstr "Enregistrer et téléverser l'audio du microphone" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +#: selfdrive/ui/layouts/settings/toggles.py:33 msgid "" "Record and store microphone audio while driving. The audio will be included " "in the dashcam video in comma connect." @@ -770,100 +755,100 @@ msgstr "" "Enregistrer et stocker l'audio du microphone pendant la conduite. L'audio " "sera inclus dans la vidéo dashcam dans comma connect." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:67 +#: selfdrive/ui/layouts/settings/device.py:67 #, python-format msgid "Regulatory" msgstr "Réglementaire" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#: selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Relaxed" msgstr "Détendu" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#: selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote access" msgstr "Accès à distance" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#: selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote snapshots" msgstr "Captures à distance" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:123 +#: selfdrive/ui/widgets/ssh_key.py:123 #, python-format msgid "Request timed out" msgstr "Délai de la requête dépassé" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:119 +#: selfdrive/ui/layouts/settings/device.py:119 #, python-format msgid "Reset" msgstr "Réinitialiser" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:51 +#: selfdrive/ui/layouts/settings/device.py:51 #, python-format msgid "Reset Calibration" msgstr "Réinitialiser la calibration" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:65 +#: selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "Review Training Guide" msgstr "Consulter le guide d'entraînement" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:27 +#: selfdrive/ui/layouts/settings/device.py:27 msgid "Review the rules, features, and limitations of openpilot" msgstr "Consultez les règles, fonctionnalités et limitations d'openpilot" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:61 +#: selfdrive/ui/layouts/settings/software.py:61 #, python-format msgid "SELECT" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:53 +#: selfdrive/ui/layouts/settings/developer.py:53 #, python-format msgid "SSH Keys" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:310 +#: system/ui/widgets/network.py:310 #, python-format msgid "Scanning Wi-Fi networks..." msgstr "" -#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/option_dialog.py:36 #, python-format msgid "Select" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:183 +#: selfdrive/ui/layouts/settings/software.py:183 #, python-format msgid "Select a branch" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:91 +#: selfdrive/ui/layouts/settings/device.py:91 #, python-format msgid "Select a language" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:60 +#: selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "Serial" msgstr "Numéro de série" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 +#: selfdrive/ui/widgets/offroad_alerts.py:106 #, python-format msgid "Snooze Update" msgstr "Reporter la mise à jour" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:65 +#: selfdrive/ui/layouts/settings/settings.py:65 msgid "Software" msgstr "Logiciel" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#: selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Standard" msgstr "Standard" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:22 +#: selfdrive/ui/layouts/settings/toggles.py:22 msgid "" "Standard is recommended. In aggressive mode, openpilot will follow lead cars " "closer and be more aggressive with the gas and brake. In relaxed mode " @@ -876,70 +861,68 @@ msgstr "" "tête. Sur les voitures compatibles, vous pouvez parcourir ces personnalités " "avec le bouton de distance du volant." -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:59 -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:65 +#: selfdrive/ui/onroad/alert_renderer.py:59 +#: selfdrive/ui/onroad/alert_renderer.py:65 #, python-format msgid "System Unresponsive" msgstr "Système non réactif" -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:58 +#: selfdrive/ui/onroad/alert_renderer.py:58 #, python-format msgid "TAKE CONTROL IMMEDIATELY" msgstr "REPRENEZ IMMÉDIATEMENT LE CONTRÔLE" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:71 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:125 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:127 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:129 +#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 +#: selfdrive/ui/layouts/sidebar.py:127 selfdrive/ui/layouts/sidebar.py:129 msgid "TEMP" msgstr "TEMPÉRATURE" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:61 +#: selfdrive/ui/layouts/settings/software.py:61 #, python-format msgid "Target Branch" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:124 +#: system/ui/widgets/network.py:124 #, python-format msgid "Tethering Password" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:64 +#: selfdrive/ui/layouts/settings/settings.py:64 msgid "Toggles" msgstr "Options" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:72 +#: selfdrive/ui/layouts/settings/software.py:72 #, python-format msgid "UNINSTALL" msgstr "DÉSINSTALLER" -#: /home/batman/openpilot/selfdrive/ui/layouts/home.py:155 +#: selfdrive/ui/layouts/home.py:155 #, python-format msgid "UPDATE" msgstr "METTRE À JOUR" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:72 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:163 +#: selfdrive/ui/layouts/settings/software.py:72 +#: selfdrive/ui/layouts/settings/software.py:163 #, python-format msgid "Uninstall" msgstr "Désinstaller" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:117 +#: selfdrive/ui/layouts/sidebar.py:117 msgid "Unknown" msgstr "Inconnu" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:48 +#: selfdrive/ui/layouts/settings/software.py:48 #, python-format msgid "Updates are only downloaded while the car is off." msgstr "" "Les mises à jour ne sont téléchargées que lorsque la voiture est éteinte." -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:33 +#: selfdrive/ui/widgets/prime.py:33 #, python-format msgid "Upgrade Now" msgstr "Mettre à niveau maintenant" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +#: selfdrive/ui/layouts/settings/toggles.py:31 msgid "" "Upload data from the driver facing camera and help improve the driver " "monitoring algorithm." @@ -947,12 +930,12 @@ msgstr "" "Téléverser les données de la caméra orientée conducteur et aider à améliorer " "l'algorithme de surveillance du conducteur." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:88 +#: selfdrive/ui/layouts/settings/toggles.py:88 #, python-format msgid "Use Metric System" msgstr "Utiliser le système métrique" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:17 +#: selfdrive/ui/layouts/settings/toggles.py:17 msgid "" "Use the openpilot system for adaptive cruise control and lane keep driver " "assistance. Your attention is required at all times to use this feature." @@ -961,22 +944,21 @@ msgstr "" "voie. Votre attention est requise en permanence pour utiliser cette " "fonctionnalité." -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:72 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:144 msgid "VEHICLE" msgstr "VÉHICULE" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:67 +#: selfdrive/ui/layouts/settings/device.py:67 #, python-format msgid "VIEW" msgstr "VOIR" -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:52 +#: selfdrive/ui/onroad/alert_renderer.py:52 #, python-format msgid "Waiting to start" msgstr "En attente de démarrage" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:19 +#: selfdrive/ui/layouts/settings/developer.py:19 msgid "" "Warning: This grants SSH access to all public keys in your GitHub settings. " "Never enter a GitHub username other than your own. A comma employee will " @@ -987,36 +969,36 @@ msgstr "" "le vôtre. Un employé comma ne vous demandera JAMAIS d'ajouter son nom " "d'utilisateur GitHub." -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:111 +#: selfdrive/ui/layouts/onboarding.py:111 #, python-format msgid "Welcome to openpilot" msgstr "Bienvenue sur openpilot" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:20 +#: selfdrive/ui/layouts/settings/toggles.py:20 msgid "When enabled, pressing the accelerator pedal will disengage openpilot." msgstr "" "Lorsque activé, appuyer sur la pédale d'accélérateur désengagera openpilot." -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:44 +#: selfdrive/ui/layouts/sidebar.py:44 msgid "Wi-Fi" msgstr "Wi‑Fi" -#: /home/batman/openpilot/system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:144 #, python-format msgid "Wi-Fi Network Metered" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:314 #, python-format msgid "Wrong password" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:145 +#: selfdrive/ui/layouts/onboarding.py:145 #, python-format msgid "You must accept the Terms and Conditions in order to use openpilot." msgstr "Vous devez accepter les conditions générales pour utiliser openpilot." -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:112 +#: selfdrive/ui/layouts/onboarding.py:112 #, python-format msgid "" "You must accept the Terms and Conditions to use openpilot. Read the latest " @@ -1025,83 +1007,82 @@ msgstr "" "Vous devez accepter les conditions générales pour utiliser openpilot. Lisez " "les dernières conditions sur https://comma.ai/terms avant de continuer." -#: /home/batman/openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:34 +#: selfdrive/ui/onroad/driver_camera_dialog.py:34 #, python-format msgid "camera starting" msgstr "démarrage de la caméra" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:63 +#: selfdrive/ui/widgets/prime.py:63 #, python-format msgid "comma prime" msgstr "comma prime" -#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:142 #, python-format msgid "default" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#: selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid "down" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:106 +#: selfdrive/ui/layouts/settings/software.py:106 #, python-format msgid "failed to check for update" msgstr "échec de la vérification de mise à jour" -#: /home/batman/openpilot/system/ui/widgets/network.py:237 -#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 #, python-format msgid "for \"{}\"" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:177 +#: selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "km/h" msgstr "km/h" -#: /home/batman/openpilot/system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:204 #, python-format msgid "leave blank for automatic configuration" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:134 +#: selfdrive/ui/layouts/settings/device.py:134 #, python-format msgid "left" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:142 #, python-format msgid "metered" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:177 +#: selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "mph" msgstr "mph" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:20 +#: selfdrive/ui/layouts/settings/software.py:20 #, python-format msgid "never" msgstr "jamais" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:31 +#: selfdrive/ui/layouts/settings/software.py:31 #, python-format msgid "now" msgstr "maintenant" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:71 +#: selfdrive/ui/layouts/settings/developer.py:71 #, python-format msgid "openpilot Longitudinal Control (Alpha)" msgstr "Contrôle longitudinal openpilot (Alpha)" -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:51 +#: selfdrive/ui/onroad/alert_renderer.py:51 #, python-format msgid "openpilot Unavailable" msgstr "openpilot indisponible" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:158 +#: selfdrive/ui/layouts/settings/toggles.py:158 #, python-format msgid "" "openpilot defaults to driving in chill mode. Experimental mode enables alpha-" @@ -1129,7 +1110,7 @@ msgstr "" "pour mieux montrer certains virages. Le logo du mode expérimental sera " "également affiché en haut à droite." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:165 +#: selfdrive/ui/layouts/settings/device.py:165 #, python-format msgid "" "openpilot is continuously calibrating, resetting is rarely required. " @@ -1138,7 +1119,7 @@ msgstr "" " La modification de ce réglage redémarrera openpilot si la voiture est sous " "tension." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:20 +#: selfdrive/ui/layouts/settings/firehose.py:20 msgid "" "openpilot learns to drive by watching humans, like you, drive.\n" "\n" @@ -1154,14 +1135,14 @@ msgstr "" "données signifie des modèles plus grands, ce qui signifie un meilleur Mode " "expérimental." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:183 +#: selfdrive/ui/layouts/settings/toggles.py:183 #, python-format msgid "openpilot longitudinal control may come in a future update." msgstr "" "Le contrôle longitudinal openpilot pourra arriver dans une future mise à " "jour." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:26 +#: selfdrive/ui/layouts/settings/device.py:26 msgid "" "openpilot requires the device to be mounted within 4° left or right and " "within 5° up or 9° down." @@ -1169,65 +1150,65 @@ msgstr "" "openpilot exige que l'appareil soit monté à moins de 4° à gauche ou à droite " "et à moins de 5° vers le haut ou 9° vers le bas." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:134 +#: selfdrive/ui/layouts/settings/device.py:134 #, python-format msgid "right" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:142 #, python-format msgid "unmetered" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#: selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid "up" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:117 +#: selfdrive/ui/layouts/settings/software.py:117 #, python-format msgid "up to date, last checked never" msgstr "à jour, dernière vérification jamais" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:115 +#: selfdrive/ui/layouts/settings/software.py:115 #, python-format msgid "up to date, last checked {}" msgstr "à jour, dernière vérification {}" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:109 +#: selfdrive/ui/layouts/settings/software.py:109 #, python-format msgid "update available" msgstr "mise à jour disponible" -#: /home/batman/openpilot/selfdrive/ui/layouts/home.py:169 +#: selfdrive/ui/layouts/home.py:169 #, python-format msgid "{} ALERT" msgid_plural "{} ALERTS" msgstr[0] "{} ALERTE" msgstr[1] "{} ALERTES" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:40 +#: selfdrive/ui/layouts/settings/software.py:40 #, python-format msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "il y a {} jour" msgstr[1] "il y a {} jours" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:37 +#: selfdrive/ui/layouts/settings/software.py:37 #, python-format msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "il y a {} heure" msgstr[1] "il y a {} heures" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:34 +#: selfdrive/ui/layouts/settings/software.py:34 #, python-format msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "il y a {} minute" msgstr[1] "il y a {} minutes" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:111 +#: selfdrive/ui/layouts/settings/firehose.py:111 #, python-format msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." @@ -1238,12 +1219,12 @@ msgstr[1] "" "{} segments de votre conduite sont dans l'ensemble d'entraînement jusqu'à " "présent." -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:62 +#: selfdrive/ui/widgets/prime.py:62 #, python-format msgid "✓ SUBSCRIBED" msgstr "✓ ABONNÉ" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:22 +#: selfdrive/ui/widgets/setup.py:22 #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "🔥 Mode Firehose 🔥" diff --git a/selfdrive/ui/translations/app_ja.po b/selfdrive/ui/translations/app_ja.po index 0ae886963..ca8aac151 100644 --- a/selfdrive/ui/translations/app_ja.po +++ b/selfdrive/ui/translations/app_ja.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-22 18:57-0700\n" +"POT-Creation-Date: 2025-10-23 00:50-0700\n" "PO-Revision-Date: 2025-10-22 16:32-0700\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" @@ -17,48 +17,48 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:160 +#: selfdrive/ui/layouts/settings/device.py:160 #, python-format msgid " Steering torque response calibration is complete." msgstr " ステアリングトルク応答のキャリブレーションが完了しました。" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:158 +#: selfdrive/ui/layouts/settings/device.py:158 #, python-format msgid " Steering torque response calibration is {}% complete." msgstr " ステアリングトルク応答のキャリブレーションは{}%完了しました。" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#: selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr " デバイスは{:.1f}°{}、{:.1f}°{}の向きです。" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:43 +#: selfdrive/ui/layouts/sidebar.py:43 msgid "--" msgstr "--" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#: selfdrive/ui/widgets/prime.py:47 #, python-format msgid "1 year of drive storage" msgstr "走行データを1年間保存" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#: selfdrive/ui/widgets/prime.py:47 #, python-format msgid "24/7 LTE connectivity" msgstr "24時間365日のLTE接続" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:46 +#: selfdrive/ui/layouts/sidebar.py:46 msgid "2G" msgstr "2G" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:47 +#: selfdrive/ui/layouts/sidebar.py:47 msgid "3G" msgstr "3G" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:49 +#: selfdrive/ui/layouts/sidebar.py:49 msgid "5G" msgstr "5G" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:23 +#: selfdrive/ui/layouts/settings/developer.py:23 msgid "" "WARNING: openpilot longitudinal control is in alpha for this car and will " "disable Automatic Emergency Braking (AEB).

On this car, openpilot " @@ -75,22 +75,22 @@ msgstr "" "実験モードの有効化を推奨します。この設定を変更すると、車が起動中の場合は" "openpilotが再起動します。" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:148 +#: selfdrive/ui/layouts/settings/device.py:148 #, python-format msgid "

Steering lag calibration is complete." msgstr "

ステアリング遅延のキャリブレーションが完了しました。" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:146 +#: selfdrive/ui/layouts/settings/device.py:146 #, python-format msgid "

Steering lag calibration is {}% complete." msgstr "

ステアリング遅延のキャリブレーションは{}%完了しました。" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:138 +#: selfdrive/ui/layouts/settings/firehose.py:138 #, python-format msgid "ACTIVE" msgstr "アクティブ" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:15 +#: selfdrive/ui/layouts/settings/developer.py:15 msgid "" "ADB (Android Debug Bridge) allows connecting to your device over USB or over " "the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." @@ -99,42 +99,41 @@ msgstr "" "に接続できます。詳しくは https://docs.comma.ai/how-to/connect-to-comma を参照" "してください。" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:30 +#: selfdrive/ui/widgets/ssh_key.py:30 msgid "ADD" msgstr "追加" -#: /home/batman/openpilot/system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:139 #, python-format msgid "APN Setting" msgstr "APN設定" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 +#: selfdrive/ui/widgets/offroad_alerts.py:109 #, python-format msgid "Acknowledge Excessive Actuation" msgstr "過度な作動を承認" -#: /home/batman/openpilot/system/ui/widgets/network.py:74 -#: /home/batman/openpilot/system/ui/widgets/network.py:95 +#: system/ui/widgets/network.py:74 system/ui/widgets/network.py:95 #, python-format msgid "Advanced" msgstr "詳細設定" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#: selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Aggressive" msgstr "アグレッシブ" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:116 +#: selfdrive/ui/layouts/onboarding.py:116 #, python-format msgid "Agree" msgstr "同意する" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:70 +#: selfdrive/ui/layouts/settings/toggles.py:70 #, python-format msgid "Always-On Driver Monitoring" msgstr "常時ドライバーモニタリング" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:186 +#: selfdrive/ui/layouts/settings/toggles.py:186 #, python-format msgid "" "An alpha version of openpilot longitudinal control can be tested, along with " @@ -143,239 +142,233 @@ msgstr "" "openpilotの縦制御アルファ版は、実験モードと併せて非リリースブランチでテストで" "きます。" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:187 +#: selfdrive/ui/layouts/settings/device.py:187 #, python-format msgid "Are you sure you want to power off?" msgstr "本当に電源をオフにしますか?" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:175 +#: selfdrive/ui/layouts/settings/device.py:175 #, python-format msgid "Are you sure you want to reboot?" msgstr "本当に再起動しますか?" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:119 +#: selfdrive/ui/layouts/settings/device.py:119 #, python-format msgid "Are you sure you want to reset calibration?" msgstr "本当にキャリブレーションをリセットしますか?" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:163 +#: selfdrive/ui/layouts/settings/software.py:163 #, python-format msgid "Are you sure you want to uninstall?" msgstr "本当にアンインストールしますか?" -#: /home/batman/openpilot/system/ui/widgets/network.py:99 -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:147 +#: system/ui/widgets/network.py:99 selfdrive/ui/layouts/onboarding.py:147 #, python-format msgid "Back" msgstr "戻る" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:38 +#: selfdrive/ui/widgets/prime.py:38 #, python-format msgid "Become a comma prime member at connect.comma.ai" msgstr "connect.comma.aiで comma prime に加入" -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:130 +#: selfdrive/ui/widgets/pairing_dialog.py:130 #, python-format msgid "Bookmark connect.comma.ai to your home screen to use it like an app" msgstr "connect.comma.aiをホーム画面に追加してアプリのように使いましょう" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:68 +#: selfdrive/ui/layouts/settings/device.py:68 #, python-format msgid "CHANGE" msgstr "変更" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:50 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:107 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:118 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:147 +#: selfdrive/ui/layouts/settings/software.py:50 +#: selfdrive/ui/layouts/settings/software.py:107 +#: selfdrive/ui/layouts/settings/software.py:118 +#: selfdrive/ui/layouts/settings/software.py:147 #, python-format msgid "CHECK" msgstr "確認" -#: /home/batman/openpilot/selfdrive/ui/widgets/exp_mode_button.py:50 +#: selfdrive/ui/widgets/exp_mode_button.py:50 #, python-format msgid "CHILL MODE ON" msgstr "チルモードON" -#: /home/batman/openpilot/system/ui/widgets/network.py:155 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:73 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:134 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:136 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:138 +#: system/ui/widgets/network.py:155 selfdrive/ui/layouts/sidebar.py:73 +#: selfdrive/ui/layouts/sidebar.py:134 selfdrive/ui/layouts/sidebar.py:136 +#: selfdrive/ui/layouts/sidebar.py:138 #, python-format msgid "CONNECT" msgstr "接続" -#: /home/batman/openpilot/system/ui/widgets/network.py:369 +#: system/ui/widgets/network.py:369 #, python-format msgid "CONNECTING..." msgstr "接続中..." -#: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:23 -#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:35 -#: /home/batman/openpilot/system/ui/widgets/keyboard.py:81 -#: /home/batman/openpilot/system/ui/widgets/network.py:318 +#: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35 +#: system/ui/widgets/keyboard.py:81 system/ui/widgets/network.py:318 #, python-format msgid "Cancel" msgstr "キャンセル" -#: /home/batman/openpilot/system/ui/widgets/network.py:134 +#: system/ui/widgets/network.py:134 #, python-format msgid "Cellular Metered" msgstr "従量課金の携帯回線" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:68 +#: selfdrive/ui/layouts/settings/device.py:68 #, python-format msgid "Change Language" msgstr "言語を変更" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:125 +#: selfdrive/ui/layouts/settings/toggles.py:125 #, python-format msgid "Changing this setting will restart openpilot if the car is powered on." msgstr "車が起動中の場合、この設定を変更するとopenpilotが再起動します。" -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:129 +#: selfdrive/ui/widgets/pairing_dialog.py:129 #, python-format msgid "Click \"add new device\" and scan the QR code on the right" msgstr "\"add new device\"を押して右側のQRコードをスキャン" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 +#: selfdrive/ui/widgets/offroad_alerts.py:104 #, python-format msgid "Close" msgstr "閉じる" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:49 +#: selfdrive/ui/layouts/settings/software.py:49 #, python-format msgid "Current Version" msgstr "現在のバージョン" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:110 +#: selfdrive/ui/layouts/settings/software.py:110 #, python-format msgid "DOWNLOAD" msgstr "ダウンロード" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:115 +#: selfdrive/ui/layouts/onboarding.py:115 #, python-format msgid "Decline" msgstr "拒否する" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:148 +#: selfdrive/ui/layouts/onboarding.py:148 #, python-format msgid "Decline, uninstall openpilot" msgstr "拒否してopenpilotをアンインストール" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:67 +#: selfdrive/ui/layouts/settings/settings.py:67 msgid "Developer" msgstr "開発者" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:62 +#: selfdrive/ui/layouts/settings/settings.py:62 msgid "Device" msgstr "デバイス" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:58 +#: selfdrive/ui/layouts/settings/toggles.py:58 #, python-format msgid "Disengage on Accelerator Pedal" msgstr "アクセルで解除" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:184 +#: selfdrive/ui/layouts/settings/device.py:184 #, python-format msgid "Disengage to Power Off" msgstr "解除して電源オフ" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:172 +#: selfdrive/ui/layouts/settings/device.py:172 #, python-format msgid "Disengage to Reboot" msgstr "解除して再起動" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:103 +#: selfdrive/ui/layouts/settings/device.py:103 #, python-format msgid "Disengage to Reset Calibration" msgstr "解除してキャリブレーションをリセット" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:32 +#: selfdrive/ui/layouts/settings/toggles.py:32 msgid "Display speed in km/h instead of mph." msgstr "速度をmphではなくkm/hで表示します。" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:59 +#: selfdrive/ui/layouts/settings/device.py:59 #, python-format msgid "Dongle ID" msgstr "ドングルID" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:50 +#: selfdrive/ui/layouts/settings/software.py:50 #, python-format msgid "Download" msgstr "ダウンロード" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:62 +#: selfdrive/ui/layouts/settings/device.py:62 #, python-format msgid "Driver Camera" msgstr "ドライバーカメラ" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:96 +#: selfdrive/ui/layouts/settings/toggles.py:96 #, python-format msgid "Driving Personality" msgstr "走行性格" -#: /home/batman/openpilot/system/ui/widgets/network.py:123 -#: /home/batman/openpilot/system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:123 system/ui/widgets/network.py:139 #, python-format msgid "EDIT" msgstr "編集" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:138 +#: selfdrive/ui/layouts/sidebar.py:138 msgid "ERROR" msgstr "エラー" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:45 +#: selfdrive/ui/layouts/sidebar.py:45 msgid "ETH" msgstr "ETH" -#: /home/batman/openpilot/selfdrive/ui/widgets/exp_mode_button.py:50 +#: selfdrive/ui/widgets/exp_mode_button.py:50 #, python-format msgid "EXPERIMENTAL MODE ON" msgstr "実験モードON" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:166 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:228 +#: selfdrive/ui/layouts/settings/developer.py:166 +#: selfdrive/ui/layouts/settings/toggles.py:228 #, python-format msgid "Enable" msgstr "有効化" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:39 +#: selfdrive/ui/layouts/settings/developer.py:39 #, python-format msgid "Enable ADB" msgstr "ADBを有効化" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:64 +#: selfdrive/ui/layouts/settings/toggles.py:64 #, python-format msgid "Enable Lane Departure Warnings" msgstr "車線逸脱警報を有効化" -#: /home/batman/openpilot/system/ui/widgets/network.py:129 +#: system/ui/widgets/network.py:129 #, python-format msgid "Enable Roaming" msgstr "ローミングを有効化" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:48 +#: selfdrive/ui/layouts/settings/developer.py:48 #, python-format msgid "Enable SSH" msgstr "SSHを有効化" -#: /home/batman/openpilot/system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:120 #, python-format msgid "Enable Tethering" msgstr "テザリングを有効化" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:30 +#: selfdrive/ui/layouts/settings/toggles.py:30 msgid "Enable driver monitoring even when openpilot is not engaged." msgstr "openpilotが未作動でもドライバーモニタリングを有効にします。" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:46 +#: selfdrive/ui/layouts/settings/toggles.py:46 #, python-format msgid "Enable openpilot" msgstr "openpilotを有効化" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:189 +#: selfdrive/ui/layouts/settings/toggles.py:189 #, python-format msgid "" "Enable the openpilot longitudinal control (alpha) toggle to allow " @@ -383,44 +376,42 @@ msgid "" msgstr "" "openpilot縦制御(アルファ)のトグルを有効にすると実験モードが使用できます。" -#: /home/batman/openpilot/system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:204 #, python-format msgid "Enter APN" msgstr "APNを入力" -#: /home/batman/openpilot/system/ui/widgets/network.py:241 +#: system/ui/widgets/network.py:241 #, python-format msgid "Enter SSID" msgstr "SSIDを入力" -#: /home/batman/openpilot/system/ui/widgets/network.py:254 +#: system/ui/widgets/network.py:254 #, python-format msgid "Enter new tethering password" msgstr "新しいテザリングのパスワードを入力" -#: /home/batman/openpilot/system/ui/widgets/network.py:237 -#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 #, python-format msgid "Enter password" msgstr "パスワードを入力" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:89 +#: selfdrive/ui/widgets/ssh_key.py:89 #, python-format msgid "Enter your GitHub username" msgstr "GitHubユーザー名を入力" -#: /home/batman/openpilot/system/ui/widgets/list_view.py:123 -#: /home/batman/openpilot/system/ui/widgets/list_view.py:160 +#: system/ui/widgets/list_view.py:123 system/ui/widgets/list_view.py:160 #, python-format msgid "Error" msgstr "エラー" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:52 +#: selfdrive/ui/layouts/settings/toggles.py:52 #, python-format msgid "Experimental Mode" msgstr "実験モード" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:181 +#: selfdrive/ui/layouts/settings/toggles.py:181 #, python-format msgid "" "Experimental mode is currently unavailable on this car since the car's stock " @@ -428,25 +419,25 @@ msgid "" msgstr "" "この車では縦制御に純正ACCを使用するため、現在実験モードは利用できません。" -#: /home/batman/openpilot/system/ui/widgets/network.py:373 +#: system/ui/widgets/network.py:373 #, python-format msgid "FORGETTING..." msgstr "削除中..." -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:44 +#: selfdrive/ui/widgets/setup.py:44 #, python-format msgid "Finish Setup" msgstr "セットアップを完了" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:66 +#: selfdrive/ui/layouts/settings/settings.py:66 msgid "Firehose" msgstr "Firehose" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:18 +#: selfdrive/ui/layouts/settings/firehose.py:18 msgid "Firehose Mode" msgstr "Firehoseモード" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:25 +#: selfdrive/ui/layouts/settings/firehose.py:25 msgid "" "For maximum effectiveness, bring your device inside and connect to a good " "USB-C adapter and Wi-Fi weekly.\n" @@ -489,175 +480,169 @@ msgstr "" "どのソフトウェアを使うかは重要ですか? はい。学習に使えるのは上流のopenpilot" "(および特定のフォーク)のみです。" -#: /home/batman/openpilot/system/ui/widgets/network.py:318 -#: /home/batman/openpilot/system/ui/widgets/network.py:451 +#: system/ui/widgets/network.py:318 system/ui/widgets/network.py:451 #, python-format msgid "Forget" msgstr "削除" -#: /home/batman/openpilot/system/ui/widgets/network.py:319 +#: system/ui/widgets/network.py:319 #, python-format msgid "Forget Wi-Fi Network \"{}\"?" msgstr "Wi‑Fiネットワーク「{}」を削除しますか?" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:71 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 msgid "GOOD" msgstr "良好" -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:128 +#: selfdrive/ui/widgets/pairing_dialog.py:128 #, python-format msgid "Go to https://connect.comma.ai on your phone" msgstr "スマートフォンで https://connect.comma.ai にアクセス" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:129 +#: selfdrive/ui/layouts/sidebar.py:129 msgid "HIGH" msgstr "高温" -#: /home/batman/openpilot/system/ui/widgets/network.py:155 +#: system/ui/widgets/network.py:155 #, python-format msgid "Hidden Network" msgstr "非公開ネットワーク" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:140 +#: selfdrive/ui/layouts/settings/firehose.py:140 #, python-format msgid "INACTIVE: connect to an unmetered network" msgstr "非アクティブ:非従量のネットワークに接続してください" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:53 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:136 +#: selfdrive/ui/layouts/settings/software.py:53 +#: selfdrive/ui/layouts/settings/software.py:136 #, python-format msgid "INSTALL" msgstr "インストール" -#: /home/batman/openpilot/system/ui/widgets/network.py:150 +#: system/ui/widgets/network.py:150 #, python-format msgid "IP Address" msgstr "IPアドレス" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:53 +#: selfdrive/ui/layouts/settings/software.py:53 #, python-format msgid "Install Update" msgstr "アップデートをインストール" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:56 +#: selfdrive/ui/layouts/settings/developer.py:56 #, python-format msgid "Joystick Debug Mode" msgstr "ジョイスティックデバッグモード" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:29 +#: selfdrive/ui/widgets/ssh_key.py:29 msgid "LOADING" msgstr "読み込み中" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:48 +#: selfdrive/ui/layouts/sidebar.py:48 msgid "LTE" msgstr "LTE" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:64 +#: selfdrive/ui/layouts/settings/developer.py:64 #, python-format msgid "Longitudinal Maneuver Mode" msgstr "縦制御マヌーバーモード" -#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:148 +#: selfdrive/ui/onroad/hud_renderer.py:148 #, python-format msgid "MAX" msgstr "最大" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:75 +#: selfdrive/ui/widgets/setup.py:75 #, python-format msgid "" "Maximize your training data uploads to improve openpilot's driving models." msgstr "" "学習データのアップロードを最大化してopenpilotの運転モデルを改善しましょう。" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:59 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:60 +#: selfdrive/ui/layouts/settings/device.py:59 +#: selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "N/A" msgstr "該当なし" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:142 +#: selfdrive/ui/layouts/sidebar.py:142 msgid "NO" msgstr "いいえ" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:63 +#: selfdrive/ui/layouts/settings/settings.py:63 msgid "Network" msgstr "ネットワーク" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:114 +#: selfdrive/ui/widgets/ssh_key.py:114 #, python-format msgid "No SSH keys found" msgstr "SSH鍵が見つかりません" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:126 +#: selfdrive/ui/widgets/ssh_key.py:126 #, python-format msgid "No SSH keys found for user '{}'" msgstr "ユーザー'{}'のSSH鍵が見つかりません" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:320 +#: selfdrive/ui/widgets/offroad_alerts.py:320 #, python-format msgid "No release notes available." msgstr "リリースノートはありません。" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:73 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: selfdrive/ui/layouts/sidebar.py:73 selfdrive/ui/layouts/sidebar.py:134 msgid "OFFLINE" msgstr "オフライン" -#: /home/batman/openpilot/system/ui/widgets/html_render.py:263 -#: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:93 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: system/ui/widgets/html_render.py:263 system/ui/widgets/confirm_dialog.py:93 +#: selfdrive/ui/layouts/sidebar.py:127 #, python-format msgid "OK" msgstr "OK" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:72 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:136 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:136 +#: selfdrive/ui/layouts/sidebar.py:144 msgid "ONLINE" msgstr "オンライン" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:20 +#: selfdrive/ui/widgets/setup.py:20 #, python-format msgid "Open" msgstr "開く" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:48 +#: selfdrive/ui/layouts/settings/device.py:48 #, python-format msgid "PAIR" msgstr "ペアリング" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:142 +#: selfdrive/ui/layouts/sidebar.py:142 msgid "PANDA" msgstr "PANDA" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:62 +#: selfdrive/ui/layouts/settings/device.py:62 #, python-format msgid "PREVIEW" msgstr "プレビュー" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:44 +#: selfdrive/ui/widgets/prime.py:44 #, python-format msgid "PRIME FEATURES:" msgstr "prime の特典:" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:48 +#: selfdrive/ui/layouts/settings/device.py:48 #, python-format msgid "Pair Device" msgstr "デバイスをペアリング" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:19 +#: selfdrive/ui/widgets/setup.py:19 #, python-format msgid "Pair device" msgstr "デバイスをペアリング" -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:103 +#: selfdrive/ui/widgets/pairing_dialog.py:103 #, python-format msgid "Pair your device to your comma account" msgstr "デバイスをあなたの comma アカウントにペアリング" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:48 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:24 +#: selfdrive/ui/widgets/setup.py:48 selfdrive/ui/layouts/settings/device.py:24 #, python-format msgid "" "Pair your device with comma connect (connect.comma.ai) and claim your comma " @@ -666,28 +651,28 @@ msgstr "" "デバイスを comma connect(connect.comma.ai)とペアリングして、comma prime 特" "典を受け取りましょう。" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:91 +#: selfdrive/ui/widgets/setup.py:91 #, python-format msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "初回ペアリングを完了するにはWi‑Fiに接続してください" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:187 +#: selfdrive/ui/layouts/settings/device.py:55 +#: selfdrive/ui/layouts/settings/device.py:187 #, python-format msgid "Power Off" msgstr "電源オフ" -#: /home/batman/openpilot/system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:144 #, python-format msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "従量課金のWi‑Fi接続時は大きなデータのアップロードを抑制" -#: /home/batman/openpilot/system/ui/widgets/network.py:135 +#: system/ui/widgets/network.py:135 #, python-format msgid "Prevent large data uploads when on a metered cellular connection" msgstr "従量課金の携帯回線接続時は大きなデータのアップロードを抑制" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:25 +#: selfdrive/ui/layouts/settings/device.py:25 msgid "" "Preview the driver facing camera to ensure that driver monitoring has good " "visibility. (vehicle must be off)" @@ -695,42 +680,42 @@ msgstr "" "ドライバー向きカメラのプレビューでモニタリングの視界を確認します。(車両は停" "止状態である必要があります)" -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:161 +#: selfdrive/ui/widgets/pairing_dialog.py:161 #, python-format msgid "QR Code Error" msgstr "QRコードエラー" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:31 +#: selfdrive/ui/widgets/ssh_key.py:31 msgid "REMOVE" msgstr "削除" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:51 +#: selfdrive/ui/layouts/settings/device.py:51 #, python-format msgid "RESET" msgstr "リセット" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:65 +#: selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "REVIEW" msgstr "確認" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:175 +#: selfdrive/ui/layouts/settings/device.py:55 +#: selfdrive/ui/layouts/settings/device.py:175 #, python-format msgid "Reboot" msgstr "再起動" -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:66 +#: selfdrive/ui/onroad/alert_renderer.py:66 #, python-format msgid "Reboot Device" msgstr "デバイスを再起動" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 +#: selfdrive/ui/widgets/offroad_alerts.py:112 #, python-format msgid "Reboot and Update" msgstr "再起動して更新" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:27 +#: selfdrive/ui/layouts/settings/toggles.py:27 msgid "" "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 " @@ -739,17 +724,17 @@ msgstr "" "時速31mph(50km/h)を超えて走行中にウインカーを出さず検出された車線を外れた場" "合、車線内に戻るよう警告を受け取ります。" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:76 +#: selfdrive/ui/layouts/settings/toggles.py:76 #, python-format msgid "Record and Upload Driver Camera" msgstr "ドライバーカメラを記録してアップロード" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:82 +#: selfdrive/ui/layouts/settings/toggles.py:82 #, python-format msgid "Record and Upload Microphone Audio" msgstr "マイク音声を記録してアップロード" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +#: selfdrive/ui/layouts/settings/toggles.py:33 msgid "" "Record and store microphone audio while driving. The audio will be included " "in the dashcam video in comma connect." @@ -757,100 +742,100 @@ msgstr "" "走行中にマイク音声を記録・保存します。音声は comma connect のドライブレコー" "ダー動画に含まれます。" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:67 +#: selfdrive/ui/layouts/settings/device.py:67 #, python-format msgid "Regulatory" msgstr "規制情報" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#: selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Relaxed" msgstr "リラックス" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#: selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote access" msgstr "リモートアクセス" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#: selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote snapshots" msgstr "リモートスナップショット" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:123 +#: selfdrive/ui/widgets/ssh_key.py:123 #, python-format msgid "Request timed out" msgstr "リクエストがタイムアウトしました" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:119 +#: selfdrive/ui/layouts/settings/device.py:119 #, python-format msgid "Reset" msgstr "リセット" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:51 +#: selfdrive/ui/layouts/settings/device.py:51 #, python-format msgid "Reset Calibration" msgstr "キャリブレーションをリセット" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:65 +#: selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "Review Training Guide" msgstr "トレーニングガイドを確認" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:27 +#: selfdrive/ui/layouts/settings/device.py:27 msgid "Review the rules, features, and limitations of openpilot" msgstr "openpilotのルール、機能、制限を確認" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:61 +#: selfdrive/ui/layouts/settings/software.py:61 #, python-format msgid "SELECT" msgstr "選択" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:53 +#: selfdrive/ui/layouts/settings/developer.py:53 #, python-format msgid "SSH Keys" msgstr "SSH鍵" -#: /home/batman/openpilot/system/ui/widgets/network.py:310 +#: system/ui/widgets/network.py:310 #, python-format msgid "Scanning Wi-Fi networks..." msgstr "Wi‑Fiネットワークを検索中..." -#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/option_dialog.py:36 #, python-format msgid "Select" msgstr "選択" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:183 +#: selfdrive/ui/layouts/settings/software.py:183 #, python-format msgid "Select a branch" msgstr "ブランチを選択" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:91 +#: selfdrive/ui/layouts/settings/device.py:91 #, python-format msgid "Select a language" msgstr "言語を選択" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:60 +#: selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "Serial" msgstr "シリアル" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 +#: selfdrive/ui/widgets/offroad_alerts.py:106 #, python-format msgid "Snooze Update" msgstr "更新を後で通知" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:65 +#: selfdrive/ui/layouts/settings/settings.py:65 msgid "Software" msgstr "ソフトウェア" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#: selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Standard" msgstr "スタンダード" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:22 +#: selfdrive/ui/layouts/settings/toggles.py:22 msgid "" "Standard is recommended. In aggressive mode, openpilot will follow lead cars " "closer and be more aggressive with the gas and brake. In relaxed mode " @@ -861,69 +846,67 @@ msgstr "" "リラックスでは前走車との距離を保ちます。対応車種ではステアリングの車間ボタン" "でこれらの性格を切り替えられます。" -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:59 -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:65 +#: selfdrive/ui/onroad/alert_renderer.py:59 +#: selfdrive/ui/onroad/alert_renderer.py:65 #, python-format msgid "System Unresponsive" msgstr "システムが応答しません" -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:58 +#: selfdrive/ui/onroad/alert_renderer.py:58 #, python-format msgid "TAKE CONTROL IMMEDIATELY" msgstr "すぐに手動介入してください" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:71 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:125 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:127 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:129 +#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 +#: selfdrive/ui/layouts/sidebar.py:127 selfdrive/ui/layouts/sidebar.py:129 msgid "TEMP" msgstr "温度" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:61 +#: selfdrive/ui/layouts/settings/software.py:61 #, python-format msgid "Target Branch" msgstr "対象ブランチ" -#: /home/batman/openpilot/system/ui/widgets/network.py:124 +#: system/ui/widgets/network.py:124 #, python-format msgid "Tethering Password" msgstr "テザリングのパスワード" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:64 +#: selfdrive/ui/layouts/settings/settings.py:64 msgid "Toggles" msgstr "トグル" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:72 +#: selfdrive/ui/layouts/settings/software.py:72 #, python-format msgid "UNINSTALL" msgstr "アンインストール" -#: /home/batman/openpilot/selfdrive/ui/layouts/home.py:155 +#: selfdrive/ui/layouts/home.py:155 #, python-format msgid "UPDATE" msgstr "更新" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:72 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:163 +#: selfdrive/ui/layouts/settings/software.py:72 +#: selfdrive/ui/layouts/settings/software.py:163 #, python-format msgid "Uninstall" msgstr "アンインストール" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:117 +#: selfdrive/ui/layouts/sidebar.py:117 msgid "Unknown" msgstr "不明" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:48 +#: selfdrive/ui/layouts/settings/software.py:48 #, python-format msgid "Updates are only downloaded while the car is off." msgstr "アップデートは車両の電源が切れている間のみダウンロードされます。" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:33 +#: selfdrive/ui/widgets/prime.py:33 #, python-format msgid "Upgrade Now" msgstr "今すぐアップグレード" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +#: selfdrive/ui/layouts/settings/toggles.py:31 msgid "" "Upload data from the driver facing camera and help improve the driver " "monitoring algorithm." @@ -931,12 +914,12 @@ msgstr "" "ドライバー向きカメラのデータをアップロードしてモニタリングアルゴリズムの改善" "に協力してください。" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:88 +#: selfdrive/ui/layouts/settings/toggles.py:88 #, python-format msgid "Use Metric System" msgstr "メートル法を使用" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:17 +#: selfdrive/ui/layouts/settings/toggles.py:17 msgid "" "Use the openpilot system for adaptive cruise control and lane keep driver " "assistance. Your attention is required at all times to use this feature." @@ -944,22 +927,21 @@ msgstr "" "ACCと車線維持支援にopenpilotを使用します。本機能の使用中は常に注意が必要で" "す。" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:72 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:144 msgid "VEHICLE" msgstr "車両" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:67 +#: selfdrive/ui/layouts/settings/device.py:67 #, python-format msgid "VIEW" msgstr "表示" -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:52 +#: selfdrive/ui/onroad/alert_renderer.py:52 #, python-format msgid "Waiting to start" msgstr "開始待機中" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:19 +#: selfdrive/ui/layouts/settings/developer.py:19 msgid "" "Warning: This grants SSH access to all public keys in your GitHub settings. " "Never enter a GitHub username other than your own. A comma employee will " @@ -969,35 +951,35 @@ msgstr "" "GitHubユーザー名を絶対に入力しないでください。comma の従業員が自分のGitHub" "ユーザー名を追加するよう求めることは決してありません。" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:111 +#: selfdrive/ui/layouts/onboarding.py:111 #, python-format msgid "Welcome to openpilot" msgstr "openpilotへようこそ" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:20 +#: selfdrive/ui/layouts/settings/toggles.py:20 msgid "When enabled, pressing the accelerator pedal will disengage openpilot." msgstr "有効にすると、アクセルを踏むとopenpilotが解除されます。" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:44 +#: selfdrive/ui/layouts/sidebar.py:44 msgid "Wi-Fi" msgstr "Wi‑Fi" -#: /home/batman/openpilot/system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:144 #, python-format msgid "Wi-Fi Network Metered" msgstr "Wi‑Fiネットワーク(従量課金)" -#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:314 #, python-format msgid "Wrong password" msgstr "パスワードが違います" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:145 +#: selfdrive/ui/layouts/onboarding.py:145 #, python-format msgid "You must accept the Terms and Conditions in order to use openpilot." msgstr "openpilotを使用するには、利用規約に同意する必要があります。" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:112 +#: selfdrive/ui/layouts/onboarding.py:112 #, python-format msgid "" "You must accept the Terms and Conditions to use openpilot. Read the latest " @@ -1006,83 +988,82 @@ msgstr "" "openpilotを使用するには利用規約に同意する必要があります。続行する前に " "https://comma.ai/terms の最新の規約をお読みください。" -#: /home/batman/openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:34 +#: selfdrive/ui/onroad/driver_camera_dialog.py:34 #, python-format msgid "camera starting" msgstr "カメラを起動中" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:63 +#: selfdrive/ui/widgets/prime.py:63 #, python-format msgid "comma prime" msgstr "comma prime" -#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:142 #, python-format msgid "default" msgstr "既定" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#: selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid "down" msgstr "下" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:106 +#: selfdrive/ui/layouts/settings/software.py:106 #, python-format msgid "failed to check for update" msgstr "アップデートの確認に失敗しました" -#: /home/batman/openpilot/system/ui/widgets/network.py:237 -#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 #, python-format msgid "for \"{}\"" msgstr "「{}」向け" -#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:177 +#: selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "km/h" msgstr "km/h" -#: /home/batman/openpilot/system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:204 #, python-format msgid "leave blank for automatic configuration" msgstr "自動設定の場合は空欄のままにしてください" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:134 +#: selfdrive/ui/layouts/settings/device.py:134 #, python-format msgid "left" msgstr "左" -#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:142 #, python-format msgid "metered" msgstr "従量" -#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:177 +#: selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "mph" msgstr "mph" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:20 +#: selfdrive/ui/layouts/settings/software.py:20 #, python-format msgid "never" msgstr "なし" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:31 +#: selfdrive/ui/layouts/settings/software.py:31 #, python-format msgid "now" msgstr "今" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:71 +#: selfdrive/ui/layouts/settings/developer.py:71 #, python-format msgid "openpilot Longitudinal Control (Alpha)" msgstr "openpilot 縦制御(アルファ)" -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:51 +#: selfdrive/ui/onroad/alert_renderer.py:51 #, python-format msgid "openpilot Unavailable" msgstr "openpilotは利用できません" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:158 +#: selfdrive/ui/layouts/settings/toggles.py:158 #, python-format msgid "" "openpilot defaults to driving in chill mode. Experimental mode enables alpha-" @@ -1107,7 +1088,7 @@ msgstr "" "り、一部の曲がりをより良く表示します。画面右上には実験モードのロゴも表示され" "ます。" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:165 +#: selfdrive/ui/layouts/settings/device.py:165 #, python-format msgid "" "openpilot is continuously calibrating, resetting is rarely required. " @@ -1117,7 +1098,7 @@ msgstr "" "稀です。車が起動中にキャリブレーションをリセットするとopenpilotが再起動しま" "す。" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:20 +#: selfdrive/ui/layouts/settings/firehose.py:20 msgid "" "openpilot learns to drive by watching humans, like you, drive.\n" "\n" @@ -1131,12 +1112,12 @@ msgstr "" "デルを改善できます。データが増えるほどモデルが大きくなり、実験モードがより良" "くなります。" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:183 +#: selfdrive/ui/layouts/settings/toggles.py:183 #, python-format msgid "openpilot longitudinal control may come in a future update." msgstr "openpilotの縦制御は将来のアップデートで提供される可能性があります。" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:26 +#: selfdrive/ui/layouts/settings/device.py:26 msgid "" "openpilot requires the device to be mounted within 4° left or right and " "within 5° up or 9° down." @@ -1144,73 +1125,73 @@ msgstr "" "openpilotでは、デバイスの取り付け角度が左右±4°、上方向5°以内、下方向9°以内で" "ある必要があります。" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:134 +#: selfdrive/ui/layouts/settings/device.py:134 #, python-format msgid "right" msgstr "右" -#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:142 #, python-format msgid "unmetered" msgstr "非従量" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#: selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid "up" msgstr "上" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:117 +#: selfdrive/ui/layouts/settings/software.py:117 #, python-format msgid "up to date, last checked never" msgstr "最新です。最終確認: なし" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:115 +#: selfdrive/ui/layouts/settings/software.py:115 #, python-format msgid "up to date, last checked {}" msgstr "最新です。最終確認: {}" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:109 +#: selfdrive/ui/layouts/settings/software.py:109 #, python-format msgid "update available" msgstr "更新があります" -#: /home/batman/openpilot/selfdrive/ui/layouts/home.py:169 +#: selfdrive/ui/layouts/home.py:169 #, python-format msgid "{} ALERT" msgid_plural "{} ALERTS" msgstr[0] "{}件のアラート" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:40 +#: selfdrive/ui/layouts/settings/software.py:40 #, python-format msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "{}日前" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:37 +#: selfdrive/ui/layouts/settings/software.py:37 #, python-format msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "{}時間前" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:34 +#: selfdrive/ui/layouts/settings/software.py:34 #, python-format msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "{}分前" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:111 +#: selfdrive/ui/layouts/settings/firehose.py:111 #, python-format msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." msgstr[0] "" "これまでにあなたの走行の{}セグメントが学習データセットに含まれています。" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:62 +#: selfdrive/ui/widgets/prime.py:62 #, python-format msgid "✓ SUBSCRIBED" msgstr "✓ 登録済み" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:22 +#: selfdrive/ui/widgets/setup.py:22 #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "🔥 Firehoseモード 🔥" diff --git a/selfdrive/ui/translations/app_ko.po b/selfdrive/ui/translations/app_ko.po index c07d44901..5a3e891b8 100644 --- a/selfdrive/ui/translations/app_ko.po +++ b/selfdrive/ui/translations/app_ko.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-22 18:57-0700\n" +"POT-Creation-Date: 2025-10-23 00:50-0700\n" "PO-Revision-Date: 2025-10-22 16:32-0700\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" @@ -17,48 +17,48 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:160 +#: selfdrive/ui/layouts/settings/device.py:160 #, python-format msgid " Steering torque response calibration is complete." msgstr " 스티어링 토크 응답 보정이 완료되었습니다." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:158 +#: selfdrive/ui/layouts/settings/device.py:158 #, python-format msgid " Steering torque response calibration is {}% complete." msgstr " 스티어링 토크 응답 보정이 {}% 완료되었습니다." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#: selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr " 장치는 {:.1f}° {} 및 {:.1f}° {} 방향을 가리키고 있습니다." -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:43 +#: selfdrive/ui/layouts/sidebar.py:43 msgid "--" msgstr "--" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#: selfdrive/ui/widgets/prime.py:47 #, python-format msgid "1 year of drive storage" msgstr "주행 데이터 1년 보관" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#: selfdrive/ui/widgets/prime.py:47 #, python-format msgid "24/7 LTE connectivity" msgstr "연중무휴 LTE 연결" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:46 +#: selfdrive/ui/layouts/sidebar.py:46 msgid "2G" msgstr "2G" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:47 +#: selfdrive/ui/layouts/sidebar.py:47 msgid "3G" msgstr "3G" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:49 +#: selfdrive/ui/layouts/sidebar.py:49 msgid "5G" msgstr "5G" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:23 +#: selfdrive/ui/layouts/settings/developer.py:23 msgid "" "WARNING: openpilot longitudinal control is in alpha for this car and will " "disable Automatic Emergency Braking (AEB).

On this car, openpilot " @@ -74,22 +74,22 @@ msgstr "" "정을 켜세요. 종방향 제어 알파를 켤 때는 실험 모드 사용을 권장합니다. 차량 전" "원이 켜져 있는 경우 이 설정을 변경하면 openpilot이 재시작됩니다." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:148 +#: selfdrive/ui/layouts/settings/device.py:148 #, python-format msgid "

Steering lag calibration is complete." msgstr "

스티어링 지연 보정이 완료되었습니다." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:146 +#: selfdrive/ui/layouts/settings/device.py:146 #, python-format msgid "

Steering lag calibration is {}% complete." msgstr "

스티어링 지연 보정이 {}% 완료되었습니다." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:138 +#: selfdrive/ui/layouts/settings/firehose.py:138 #, python-format msgid "ACTIVE" msgstr "활성" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:15 +#: selfdrive/ui/layouts/settings/developer.py:15 msgid "" "ADB (Android Debug Bridge) allows connecting to your device over USB or over " "the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." @@ -98,42 +98,41 @@ msgstr "" "습니다. 자세한 내용은 https://docs.comma.ai/how-to/connect-to-comma 를 참고하" "세요." -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:30 +#: selfdrive/ui/widgets/ssh_key.py:30 msgid "ADD" msgstr "추가" -#: /home/batman/openpilot/system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:139 #, python-format msgid "APN Setting" msgstr "APN 설정" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 +#: selfdrive/ui/widgets/offroad_alerts.py:109 #, python-format msgid "Acknowledge Excessive Actuation" msgstr "과도한 작동을 확인" -#: /home/batman/openpilot/system/ui/widgets/network.py:74 -#: /home/batman/openpilot/system/ui/widgets/network.py:95 +#: system/ui/widgets/network.py:74 system/ui/widgets/network.py:95 #, python-format msgid "Advanced" msgstr "고급" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#: selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Aggressive" msgstr "공격적" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:116 +#: selfdrive/ui/layouts/onboarding.py:116 #, python-format msgid "Agree" msgstr "동의" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:70 +#: selfdrive/ui/layouts/settings/toggles.py:70 #, python-format msgid "Always-On Driver Monitoring" msgstr "항상 켜짐 운전자 모니터링" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:186 +#: selfdrive/ui/layouts/settings/toggles.py:186 #, python-format msgid "" "An alpha version of openpilot longitudinal control can be tested, along with " @@ -142,283 +141,275 @@ msgstr "" "openpilot 종방향 제어 알파 버전은 실험 모드와 함께 비릴리스 브랜치에서 테스트" "할 수 있습니다." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:187 +#: selfdrive/ui/layouts/settings/device.py:187 #, python-format msgid "Are you sure you want to power off?" msgstr "정말 전원을 끄시겠습니까?" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:175 +#: selfdrive/ui/layouts/settings/device.py:175 #, python-format msgid "Are you sure you want to reboot?" msgstr "정말 재시작하시겠습니까?" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:119 +#: selfdrive/ui/layouts/settings/device.py:119 #, python-format msgid "Are you sure you want to reset calibration?" msgstr "정말 보정을 재설정하시겠습니까?" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:163 +#: selfdrive/ui/layouts/settings/software.py:163 #, python-format msgid "Are you sure you want to uninstall?" msgstr "정말 제거하시겠습니까?" -#: /home/batman/openpilot/system/ui/widgets/network.py:99 -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:147 +#: system/ui/widgets/network.py:99 selfdrive/ui/layouts/onboarding.py:147 #, python-format msgid "Back" msgstr "뒤로" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:38 +#: selfdrive/ui/widgets/prime.py:38 #, python-format msgid "Become a comma prime member at connect.comma.ai" msgstr "connect.comma.ai에서 comma prime 회원이 되세요" -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:130 +#: selfdrive/ui/widgets/pairing_dialog.py:130 #, python-format msgid "Bookmark connect.comma.ai to your home screen to use it like an app" msgstr "connect.comma.ai를 홈 화면에 추가하여 앱처럼 사용하세요" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:68 +#: selfdrive/ui/layouts/settings/device.py:68 #, python-format msgid "CHANGE" msgstr "변경" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:50 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:107 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:118 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:147 +#: selfdrive/ui/layouts/settings/software.py:50 +#: selfdrive/ui/layouts/settings/software.py:107 +#: selfdrive/ui/layouts/settings/software.py:118 +#: selfdrive/ui/layouts/settings/software.py:147 #, python-format msgid "CHECK" msgstr "확인" -#: /home/batman/openpilot/selfdrive/ui/widgets/exp_mode_button.py:50 +#: selfdrive/ui/widgets/exp_mode_button.py:50 #, python-format msgid "CHILL MODE ON" msgstr "칠 모드 켜짐" -#: /home/batman/openpilot/system/ui/widgets/network.py:155 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:73 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:134 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:136 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:138 +#: system/ui/widgets/network.py:155 selfdrive/ui/layouts/sidebar.py:73 +#: selfdrive/ui/layouts/sidebar.py:134 selfdrive/ui/layouts/sidebar.py:136 +#: selfdrive/ui/layouts/sidebar.py:138 #, python-format msgid "CONNECT" msgstr "연결" -#: /home/batman/openpilot/system/ui/widgets/network.py:369 +#: system/ui/widgets/network.py:369 #, python-format msgid "CONNECTING..." msgstr "연결 중..." -#: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:23 -#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:35 -#: /home/batman/openpilot/system/ui/widgets/keyboard.py:81 -#: /home/batman/openpilot/system/ui/widgets/network.py:318 +#: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35 +#: system/ui/widgets/keyboard.py:81 system/ui/widgets/network.py:318 #, python-format msgid "Cancel" msgstr "취소" -#: /home/batman/openpilot/system/ui/widgets/network.py:134 +#: system/ui/widgets/network.py:134 #, python-format msgid "Cellular Metered" msgstr "종량제 셀룰러" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:68 +#: selfdrive/ui/layouts/settings/device.py:68 #, python-format msgid "Change Language" msgstr "언어 변경" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:125 +#: selfdrive/ui/layouts/settings/toggles.py:125 #, python-format msgid "Changing this setting will restart openpilot if the car is powered on." msgstr "차량 전원이 켜져 있으면 이 설정을 변경할 때 openpilot이 재시작됩니다." -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:129 +#: selfdrive/ui/widgets/pairing_dialog.py:129 #, python-format msgid "Click \"add new device\" and scan the QR code on the right" msgstr "\"add new device\"를 눌러 오른쪽의 QR 코드를 스캔하세요" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 +#: selfdrive/ui/widgets/offroad_alerts.py:104 #, python-format msgid "Close" msgstr "닫기" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:49 +#: selfdrive/ui/layouts/settings/software.py:49 #, python-format msgid "Current Version" msgstr "현재 버전" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:110 +#: selfdrive/ui/layouts/settings/software.py:110 #, python-format msgid "DOWNLOAD" msgstr "다운로드" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:115 +#: selfdrive/ui/layouts/onboarding.py:115 #, python-format msgid "Decline" msgstr "거부" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:148 +#: selfdrive/ui/layouts/onboarding.py:148 #, python-format msgid "Decline, uninstall openpilot" msgstr "거부하고 openpilot 제거" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:67 +#: selfdrive/ui/layouts/settings/settings.py:67 msgid "Developer" msgstr "개발자" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:62 +#: selfdrive/ui/layouts/settings/settings.py:62 msgid "Device" msgstr "장치" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:58 +#: selfdrive/ui/layouts/settings/toggles.py:58 #, python-format msgid "Disengage on Accelerator Pedal" msgstr "가속 페달로 해제" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:184 +#: selfdrive/ui/layouts/settings/device.py:184 #, python-format msgid "Disengage to Power Off" msgstr "해제 후 전원 끄기" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:172 +#: selfdrive/ui/layouts/settings/device.py:172 #, python-format msgid "Disengage to Reboot" msgstr "해제 후 재시작" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:103 +#: selfdrive/ui/layouts/settings/device.py:103 #, python-format msgid "Disengage to Reset Calibration" msgstr "해제 후 보정 재설정" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:32 +#: selfdrive/ui/layouts/settings/toggles.py:32 msgid "Display speed in km/h instead of mph." msgstr "속도를 mph 대신 km/h로 표시합니다." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:59 +#: selfdrive/ui/layouts/settings/device.py:59 #, python-format msgid "Dongle ID" msgstr "동글 ID" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:50 +#: selfdrive/ui/layouts/settings/software.py:50 #, python-format msgid "Download" msgstr "다운로드" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:62 +#: selfdrive/ui/layouts/settings/device.py:62 #, python-format msgid "Driver Camera" msgstr "운전자 카메라" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:96 +#: selfdrive/ui/layouts/settings/toggles.py:96 #, python-format msgid "Driving Personality" msgstr "주행 성향" -#: /home/batman/openpilot/system/ui/widgets/network.py:123 -#: /home/batman/openpilot/system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:123 system/ui/widgets/network.py:139 #, python-format msgid "EDIT" msgstr "편집" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:138 +#: selfdrive/ui/layouts/sidebar.py:138 msgid "ERROR" msgstr "오류" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:45 +#: selfdrive/ui/layouts/sidebar.py:45 msgid "ETH" msgstr "ETH" -#: /home/batman/openpilot/selfdrive/ui/widgets/exp_mode_button.py:50 +#: selfdrive/ui/widgets/exp_mode_button.py:50 #, python-format msgid "EXPERIMENTAL MODE ON" msgstr "실험 모드 켜짐" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:166 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:228 +#: selfdrive/ui/layouts/settings/developer.py:166 +#: selfdrive/ui/layouts/settings/toggles.py:228 #, python-format msgid "Enable" msgstr "사용" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:39 +#: selfdrive/ui/layouts/settings/developer.py:39 #, python-format msgid "Enable ADB" msgstr "ADB 사용" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:64 +#: selfdrive/ui/layouts/settings/toggles.py:64 #, python-format msgid "Enable Lane Departure Warnings" msgstr "차선 이탈 경고 사용" -#: /home/batman/openpilot/system/ui/widgets/network.py:129 +#: system/ui/widgets/network.py:129 #, python-format msgid "Enable Roaming" msgstr "로밍 사용" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:48 +#: selfdrive/ui/layouts/settings/developer.py:48 #, python-format msgid "Enable SSH" msgstr "SSH 사용" -#: /home/batman/openpilot/system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:120 #, python-format msgid "Enable Tethering" msgstr "테더링 사용" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:30 +#: selfdrive/ui/layouts/settings/toggles.py:30 msgid "Enable driver monitoring even when openpilot is not engaged." msgstr "openpilot이 작동 중이 아닐 때도 운전자 모니터링을 사용합니다." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:46 +#: selfdrive/ui/layouts/settings/toggles.py:46 #, python-format msgid "Enable openpilot" msgstr "openpilot 사용" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:189 +#: selfdrive/ui/layouts/settings/toggles.py:189 #, python-format msgid "" "Enable the openpilot longitudinal control (alpha) toggle to allow " "Experimental mode." msgstr "실험 모드를 사용하려면 openpilot 종방향 제어(알파) 토글을 켜세요." -#: /home/batman/openpilot/system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:204 #, python-format msgid "Enter APN" msgstr "APN 입력" -#: /home/batman/openpilot/system/ui/widgets/network.py:241 +#: system/ui/widgets/network.py:241 #, python-format msgid "Enter SSID" msgstr "SSID 입력" -#: /home/batman/openpilot/system/ui/widgets/network.py:254 +#: system/ui/widgets/network.py:254 #, python-format msgid "Enter new tethering password" msgstr "새 테더링 비밀번호 입력" -#: /home/batman/openpilot/system/ui/widgets/network.py:237 -#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 #, python-format msgid "Enter password" msgstr "비밀번호 입력" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:89 +#: selfdrive/ui/widgets/ssh_key.py:89 #, python-format msgid "Enter your GitHub username" msgstr "GitHub 사용자 이름 입력" -#: /home/batman/openpilot/system/ui/widgets/list_view.py:123 -#: /home/batman/openpilot/system/ui/widgets/list_view.py:160 +#: system/ui/widgets/list_view.py:123 system/ui/widgets/list_view.py:160 #, python-format msgid "Error" msgstr "오류" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:52 +#: selfdrive/ui/layouts/settings/toggles.py:52 #, python-format msgid "Experimental Mode" msgstr "실험 모드" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:181 +#: selfdrive/ui/layouts/settings/toggles.py:181 #, python-format msgid "" "Experimental mode is currently unavailable on this car since the car's stock " @@ -427,25 +418,25 @@ msgstr "" "이 차량은 종방향 제어에 순정 ACC를 사용하므로 현재 실험 모드를 사용할 수 없습" "니다." -#: /home/batman/openpilot/system/ui/widgets/network.py:373 +#: system/ui/widgets/network.py:373 #, python-format msgid "FORGETTING..." msgstr "삭제 중..." -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:44 +#: selfdrive/ui/widgets/setup.py:44 #, python-format msgid "Finish Setup" msgstr "설정 완료" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:66 +#: selfdrive/ui/layouts/settings/settings.py:66 msgid "Firehose" msgstr "Firehose" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:18 +#: selfdrive/ui/layouts/settings/firehose.py:18 msgid "Firehose Mode" msgstr "Firehose 모드" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:25 +#: selfdrive/ui/layouts/settings/firehose.py:25 msgid "" "For maximum effectiveness, bring your device inside and connect to a good " "USB-C adapter and Wi-Fi weekly.\n" @@ -488,174 +479,168 @@ msgstr "" "어떤 소프트웨어를 실행하는지가 중요한가요? 예. 학습에는 업스트림 " "openpilot(및 일부 포크)만 사용할 수 있습니다." -#: /home/batman/openpilot/system/ui/widgets/network.py:318 -#: /home/batman/openpilot/system/ui/widgets/network.py:451 +#: system/ui/widgets/network.py:318 system/ui/widgets/network.py:451 #, python-format msgid "Forget" msgstr "삭제" -#: /home/batman/openpilot/system/ui/widgets/network.py:319 +#: system/ui/widgets/network.py:319 #, python-format msgid "Forget Wi-Fi Network \"{}\"?" msgstr "Wi‑Fi 네트워크 \"{}\"를 삭제하시겠습니까?" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:71 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 msgid "GOOD" msgstr "양호" -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:128 +#: selfdrive/ui/widgets/pairing_dialog.py:128 #, python-format msgid "Go to https://connect.comma.ai on your phone" msgstr "휴대폰에서 https://connect.comma.ai 에 접속하세요" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:129 +#: selfdrive/ui/layouts/sidebar.py:129 msgid "HIGH" msgstr "높음" -#: /home/batman/openpilot/system/ui/widgets/network.py:155 +#: system/ui/widgets/network.py:155 #, python-format msgid "Hidden Network" msgstr "숨겨진 네트워크" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:140 +#: selfdrive/ui/layouts/settings/firehose.py:140 #, python-format msgid "INACTIVE: connect to an unmetered network" msgstr "비활성: 비종량제 네트워크에 연결하세요" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:53 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:136 +#: selfdrive/ui/layouts/settings/software.py:53 +#: selfdrive/ui/layouts/settings/software.py:136 #, python-format msgid "INSTALL" msgstr "설치" -#: /home/batman/openpilot/system/ui/widgets/network.py:150 +#: system/ui/widgets/network.py:150 #, python-format msgid "IP Address" msgstr "IP 주소" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:53 +#: selfdrive/ui/layouts/settings/software.py:53 #, python-format msgid "Install Update" msgstr "업데이트 설치" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:56 +#: selfdrive/ui/layouts/settings/developer.py:56 #, python-format msgid "Joystick Debug Mode" msgstr "조이스틱 디버그 모드" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:29 +#: selfdrive/ui/widgets/ssh_key.py:29 msgid "LOADING" msgstr "로딩 중" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:48 +#: selfdrive/ui/layouts/sidebar.py:48 msgid "LTE" msgstr "LTE" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:64 +#: selfdrive/ui/layouts/settings/developer.py:64 #, python-format msgid "Longitudinal Maneuver Mode" msgstr "종방향 매뉴버 모드" -#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:148 +#: selfdrive/ui/onroad/hud_renderer.py:148 #, python-format msgid "MAX" msgstr "최대" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:75 +#: selfdrive/ui/widgets/setup.py:75 #, python-format msgid "" "Maximize your training data uploads to improve openpilot's driving models." msgstr "학습 데이터 업로드를 최대화하여 openpilot의 주행 모델을 개선하세요." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:59 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:60 +#: selfdrive/ui/layouts/settings/device.py:59 +#: selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "N/A" msgstr "해당 없음" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:142 +#: selfdrive/ui/layouts/sidebar.py:142 msgid "NO" msgstr "아니오" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:63 +#: selfdrive/ui/layouts/settings/settings.py:63 msgid "Network" msgstr "네트워크" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:114 +#: selfdrive/ui/widgets/ssh_key.py:114 #, python-format msgid "No SSH keys found" msgstr "SSH 키를 찾을 수 없습니다" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:126 +#: selfdrive/ui/widgets/ssh_key.py:126 #, python-format msgid "No SSH keys found for user '{}'" msgstr "사용자 '{}'의 SSH 키를 찾을 수 없습니다" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:320 +#: selfdrive/ui/widgets/offroad_alerts.py:320 #, python-format msgid "No release notes available." msgstr "릴리스 노트가 없습니다." -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:73 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: selfdrive/ui/layouts/sidebar.py:73 selfdrive/ui/layouts/sidebar.py:134 msgid "OFFLINE" msgstr "오프라인" -#: /home/batman/openpilot/system/ui/widgets/html_render.py:263 -#: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:93 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: system/ui/widgets/html_render.py:263 system/ui/widgets/confirm_dialog.py:93 +#: selfdrive/ui/layouts/sidebar.py:127 #, python-format msgid "OK" msgstr "확인" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:72 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:136 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:136 +#: selfdrive/ui/layouts/sidebar.py:144 msgid "ONLINE" msgstr "온라인" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:20 +#: selfdrive/ui/widgets/setup.py:20 #, python-format msgid "Open" msgstr "열기" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:48 +#: selfdrive/ui/layouts/settings/device.py:48 #, python-format msgid "PAIR" msgstr "페어링" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:142 +#: selfdrive/ui/layouts/sidebar.py:142 msgid "PANDA" msgstr "PANDA" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:62 +#: selfdrive/ui/layouts/settings/device.py:62 #, python-format msgid "PREVIEW" msgstr "미리보기" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:44 +#: selfdrive/ui/widgets/prime.py:44 #, python-format msgid "PRIME FEATURES:" msgstr "prime 기능:" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:48 +#: selfdrive/ui/layouts/settings/device.py:48 #, python-format msgid "Pair Device" msgstr "장치 페어링" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:19 +#: selfdrive/ui/widgets/setup.py:19 #, python-format msgid "Pair device" msgstr "장치 페어링" -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:103 +#: selfdrive/ui/widgets/pairing_dialog.py:103 #, python-format msgid "Pair your device to your comma account" msgstr "장치를 귀하의 comma 계정에 페어링하세요" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:48 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:24 +#: selfdrive/ui/widgets/setup.py:48 selfdrive/ui/layouts/settings/device.py:24 #, python-format msgid "" "Pair your device with comma connect (connect.comma.ai) and claim your comma " @@ -664,28 +649,28 @@ msgstr "" "장치를 comma connect(connect.comma.ai)와 페어링하고 comma prime 혜택을 받으세" "요." -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:91 +#: selfdrive/ui/widgets/setup.py:91 #, python-format msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "초기 페어링을 완료하려면 Wi‑Fi에 연결하세요" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:187 +#: selfdrive/ui/layouts/settings/device.py:55 +#: selfdrive/ui/layouts/settings/device.py:187 #, python-format msgid "Power Off" msgstr "전원 끄기" -#: /home/batman/openpilot/system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:144 #, python-format msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "종량제 Wi‑Fi 연결 시 대용량 업로드 방지" -#: /home/batman/openpilot/system/ui/widgets/network.py:135 +#: system/ui/widgets/network.py:135 #, python-format msgid "Prevent large data uploads when on a metered cellular connection" msgstr "종량제 셀룰러 연결 시 대용량 업로드 방지" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:25 +#: selfdrive/ui/layouts/settings/device.py:25 msgid "" "Preview the driver facing camera to ensure that driver monitoring has good " "visibility. (vehicle must be off)" @@ -693,42 +678,42 @@ msgstr "" "운전자 모니터링의 가시성을 확인하기 위해 운전자 카메라를 미리 봅니다. (차량" "은 꺼져 있어야 합니다)" -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:161 +#: selfdrive/ui/widgets/pairing_dialog.py:161 #, python-format msgid "QR Code Error" msgstr "QR 코드 오류" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:31 +#: selfdrive/ui/widgets/ssh_key.py:31 msgid "REMOVE" msgstr "제거" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:51 +#: selfdrive/ui/layouts/settings/device.py:51 #, python-format msgid "RESET" msgstr "재설정" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:65 +#: selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "REVIEW" msgstr "검토" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:175 +#: selfdrive/ui/layouts/settings/device.py:55 +#: selfdrive/ui/layouts/settings/device.py:175 #, python-format msgid "Reboot" msgstr "재시작" -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:66 +#: selfdrive/ui/onroad/alert_renderer.py:66 #, python-format msgid "Reboot Device" msgstr "장치 재시작" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 +#: selfdrive/ui/widgets/offroad_alerts.py:112 #, python-format msgid "Reboot and Update" msgstr "재시작 및 업데이트" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:27 +#: selfdrive/ui/layouts/settings/toggles.py:27 msgid "" "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 " @@ -737,17 +722,17 @@ msgstr "" "시속 31mph(50km/h) 이상에서 방향지시등 없이 감지된 차선 밖으로 벗어나면 차선" "으로 복귀하라는 경고를 받습니다." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:76 +#: selfdrive/ui/layouts/settings/toggles.py:76 #, python-format msgid "Record and Upload Driver Camera" msgstr "운전자 카메라 기록 및 업로드" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:82 +#: selfdrive/ui/layouts/settings/toggles.py:82 #, python-format msgid "Record and Upload Microphone Audio" msgstr "마이크 오디오 기록 및 업로드" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +#: selfdrive/ui/layouts/settings/toggles.py:33 msgid "" "Record and store microphone audio while driving. The audio will be included " "in the dashcam video in comma connect." @@ -755,100 +740,100 @@ msgstr "" "주행 중 마이크 오디오를 기록하고 저장합니다. 오디오는 comma connect의 대시캠 " "영상에 포함됩니다." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:67 +#: selfdrive/ui/layouts/settings/device.py:67 #, python-format msgid "Regulatory" msgstr "규제 정보" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#: selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Relaxed" msgstr "편안함" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#: selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote access" msgstr "원격 액세스" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#: selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote snapshots" msgstr "원격 스냅샷" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:123 +#: selfdrive/ui/widgets/ssh_key.py:123 #, python-format msgid "Request timed out" msgstr "요청 시간이 초과되었습니다" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:119 +#: selfdrive/ui/layouts/settings/device.py:119 #, python-format msgid "Reset" msgstr "재설정" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:51 +#: selfdrive/ui/layouts/settings/device.py:51 #, python-format msgid "Reset Calibration" msgstr "보정 재설정" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:65 +#: selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "Review Training Guide" msgstr "학습 가이드 검토" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:27 +#: selfdrive/ui/layouts/settings/device.py:27 msgid "Review the rules, features, and limitations of openpilot" msgstr "openpilot의 규칙, 기능 및 제한을 검토" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:61 +#: selfdrive/ui/layouts/settings/software.py:61 #, python-format msgid "SELECT" msgstr "선택" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:53 +#: selfdrive/ui/layouts/settings/developer.py:53 #, python-format msgid "SSH Keys" msgstr "SSH 키" -#: /home/batman/openpilot/system/ui/widgets/network.py:310 +#: system/ui/widgets/network.py:310 #, python-format msgid "Scanning Wi-Fi networks..." msgstr "Wi‑Fi 네트워크 검색 중..." -#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/option_dialog.py:36 #, python-format msgid "Select" msgstr "선택" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:183 +#: selfdrive/ui/layouts/settings/software.py:183 #, python-format msgid "Select a branch" msgstr "브랜치 선택" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:91 +#: selfdrive/ui/layouts/settings/device.py:91 #, python-format msgid "Select a language" msgstr "언어 선택" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:60 +#: selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "Serial" msgstr "시리얼" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 +#: selfdrive/ui/widgets/offroad_alerts.py:106 #, python-format msgid "Snooze Update" msgstr "업데이트 나중에 알림" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:65 +#: selfdrive/ui/layouts/settings/settings.py:65 msgid "Software" msgstr "소프트웨어" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#: selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Standard" msgstr "표준" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:22 +#: selfdrive/ui/layouts/settings/toggles.py:22 msgid "" "Standard is recommended. In aggressive mode, openpilot will follow lead cars " "closer and be more aggressive with the gas and brake. In relaxed mode " @@ -859,69 +844,67 @@ msgstr "" "극적입니다. 편안함 모드에서는 앞차와 거리를 더 둡니다. 지원 차량에서는 스티어" "링의 차간 버튼으로 이 성향들을 전환할 수 있습니다." -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:59 -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:65 +#: selfdrive/ui/onroad/alert_renderer.py:59 +#: selfdrive/ui/onroad/alert_renderer.py:65 #, python-format msgid "System Unresponsive" msgstr "시스템 응답 없음" -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:58 +#: selfdrive/ui/onroad/alert_renderer.py:58 #, python-format msgid "TAKE CONTROL IMMEDIATELY" msgstr "즉시 수동 조작하세요" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:71 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:125 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:127 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:129 +#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 +#: selfdrive/ui/layouts/sidebar.py:127 selfdrive/ui/layouts/sidebar.py:129 msgid "TEMP" msgstr "온도" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:61 +#: selfdrive/ui/layouts/settings/software.py:61 #, python-format msgid "Target Branch" msgstr "대상 브랜치" -#: /home/batman/openpilot/system/ui/widgets/network.py:124 +#: system/ui/widgets/network.py:124 #, python-format msgid "Tethering Password" msgstr "테더링 비밀번호" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:64 +#: selfdrive/ui/layouts/settings/settings.py:64 msgid "Toggles" msgstr "토글" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:72 +#: selfdrive/ui/layouts/settings/software.py:72 #, python-format msgid "UNINSTALL" msgstr "제거" -#: /home/batman/openpilot/selfdrive/ui/layouts/home.py:155 +#: selfdrive/ui/layouts/home.py:155 #, python-format msgid "UPDATE" msgstr "업데이트" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:72 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:163 +#: selfdrive/ui/layouts/settings/software.py:72 +#: selfdrive/ui/layouts/settings/software.py:163 #, python-format msgid "Uninstall" msgstr "제거" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:117 +#: selfdrive/ui/layouts/sidebar.py:117 msgid "Unknown" msgstr "알 수 없음" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:48 +#: selfdrive/ui/layouts/settings/software.py:48 #, python-format msgid "Updates are only downloaded while the car is off." msgstr "업데이트는 차량 전원이 꺼져 있을 때만 다운로드됩니다." -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:33 +#: selfdrive/ui/widgets/prime.py:33 #, python-format msgid "Upgrade Now" msgstr "지금 업그레이드" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +#: selfdrive/ui/layouts/settings/toggles.py:31 msgid "" "Upload data from the driver facing camera and help improve the driver " "monitoring algorithm." @@ -929,12 +912,12 @@ msgstr "" "운전자 방향 카메라 데이터를 업로드하여 운전자 모니터링 알고리즘 개선에 도움" "을 주세요." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:88 +#: selfdrive/ui/layouts/settings/toggles.py:88 #, python-format msgid "Use Metric System" msgstr "미터법 사용" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:17 +#: selfdrive/ui/layouts/settings/toggles.py:17 msgid "" "Use the openpilot system for adaptive cruise control and lane keep driver " "assistance. Your attention is required at all times to use this feature." @@ -942,22 +925,21 @@ msgstr "" "ACC 및 차선 유지 보조에 openpilot을 사용합니다. 이 기능을 사용할 때는 항상 주" "의가 필요합니다." -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:72 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:144 msgid "VEHICLE" msgstr "차량" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:67 +#: selfdrive/ui/layouts/settings/device.py:67 #, python-format msgid "VIEW" msgstr "보기" -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:52 +#: selfdrive/ui/onroad/alert_renderer.py:52 #, python-format msgid "Waiting to start" msgstr "시작 대기 중" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:19 +#: selfdrive/ui/layouts/settings/developer.py:19 msgid "" "Warning: This grants SSH access to all public keys in your GitHub settings. " "Never enter a GitHub username other than your own. A comma employee will " @@ -967,35 +949,35 @@ msgstr "" "아닌 GitHub 사용자 이름을 절대 입력하지 마세요. comma 직원이 본인의 GitHub 사" "용자 이름 추가를 요구하는 일은 결코 없습니다." -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:111 +#: selfdrive/ui/layouts/onboarding.py:111 #, python-format msgid "Welcome to openpilot" msgstr "openpilot에 오신 것을 환영합니다" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:20 +#: selfdrive/ui/layouts/settings/toggles.py:20 msgid "When enabled, pressing the accelerator pedal will disengage openpilot." msgstr "이 옵션을 켜면 가속 페달을 밟을 때 openpilot이 해제됩니다." -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:44 +#: selfdrive/ui/layouts/sidebar.py:44 msgid "Wi-Fi" msgstr "Wi‑Fi" -#: /home/batman/openpilot/system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:144 #, python-format msgid "Wi-Fi Network Metered" msgstr "Wi‑Fi 네트워크 종량제" -#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:314 #, python-format msgid "Wrong password" msgstr "비밀번호가 올바르지 않습니다" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:145 +#: selfdrive/ui/layouts/onboarding.py:145 #, python-format msgid "You must accept the Terms and Conditions in order to use openpilot." msgstr "openpilot을 사용하려면 약관에 동의해야 합니다." -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:112 +#: selfdrive/ui/layouts/onboarding.py:112 #, python-format msgid "" "You must accept the Terms and Conditions to use openpilot. Read the latest " @@ -1004,83 +986,82 @@ msgstr "" "openpilot을 사용하려면 약관에 동의해야 합니다. 계속하기 전에 https://comma." "ai/terms 에서 최신 약관을 읽어주세요." -#: /home/batman/openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:34 +#: selfdrive/ui/onroad/driver_camera_dialog.py:34 #, python-format msgid "camera starting" msgstr "카메라 시작 중" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:63 +#: selfdrive/ui/widgets/prime.py:63 #, python-format msgid "comma prime" msgstr "comma prime" -#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:142 #, python-format msgid "default" msgstr "기본값" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#: selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid "down" msgstr "아래" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:106 +#: selfdrive/ui/layouts/settings/software.py:106 #, python-format msgid "failed to check for update" msgstr "업데이트 확인 실패" -#: /home/batman/openpilot/system/ui/widgets/network.py:237 -#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 #, python-format msgid "for \"{}\"" msgstr "\"{}\"용" -#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:177 +#: selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "km/h" msgstr "km/h" -#: /home/batman/openpilot/system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:204 #, python-format msgid "leave blank for automatic configuration" msgstr "자동 구성을 사용하려면 비워 두세요" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:134 +#: selfdrive/ui/layouts/settings/device.py:134 #, python-format msgid "left" msgstr "왼쪽" -#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:142 #, python-format msgid "metered" msgstr "종량제" -#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:177 +#: selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "mph" msgstr "mph" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:20 +#: selfdrive/ui/layouts/settings/software.py:20 #, python-format msgid "never" msgstr "없음" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:31 +#: selfdrive/ui/layouts/settings/software.py:31 #, python-format msgid "now" msgstr "지금" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:71 +#: selfdrive/ui/layouts/settings/developer.py:71 #, python-format msgid "openpilot Longitudinal Control (Alpha)" msgstr "openpilot 종방향 제어(알파)" -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:51 +#: selfdrive/ui/onroad/alert_renderer.py:51 #, python-format msgid "openpilot Unavailable" msgstr "openpilot 사용 불가" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:158 +#: selfdrive/ui/layouts/settings/toggles.py:158 #, python-format msgid "" "openpilot defaults to driving in chill mode. Experimental mode enables alpha-" @@ -1104,7 +1085,7 @@ msgstr "" "새로운 주행 시각화
저속에서는 도로 방향의 광각 카메라로 전환되어 일" "부 회전을 더 잘 보여줍니다. 화면 오른쪽 위에는 실험 모드 로고도 표시됩니다." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:165 +#: selfdrive/ui/layouts/settings/device.py:165 #, python-format msgid "" "openpilot is continuously calibrating, resetting is rarely required. " @@ -1113,7 +1094,7 @@ msgstr "" "openpilot은 지속적으로 보정을 진행하므로 재설정이 필요한 경우는 드뭅니다. 차" "량 전원이 켜져 있을 때 보정을 재설정하면 openpilot이 재시작됩니다." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:20 +#: selfdrive/ui/layouts/settings/firehose.py:20 msgid "" "openpilot learns to drive by watching humans, like you, drive.\n" "\n" @@ -1127,83 +1108,83 @@ msgstr "" "할 수 있게 해줍니다. 데이터가 많을수록 모델은 커지고, 실험 모드는 더 좋아집니" "다." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:183 +#: selfdrive/ui/layouts/settings/toggles.py:183 #, python-format msgid "openpilot longitudinal control may come in a future update." msgstr "openpilot 종방향 제어는 향후 업데이트에서 제공될 수 있습니다." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:26 +#: selfdrive/ui/layouts/settings/device.py:26 msgid "" "openpilot requires the device to be mounted within 4° left or right and " "within 5° up or 9° down." msgstr "openpilot은 장치를 좌우 4°, 위쪽 5°, 아래쪽 9° 이내로 장착해야 합니다." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:134 +#: selfdrive/ui/layouts/settings/device.py:134 #, python-format msgid "right" msgstr "오른쪽" -#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:142 #, python-format msgid "unmetered" msgstr "비종량제" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#: selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid "up" msgstr "위" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:117 +#: selfdrive/ui/layouts/settings/software.py:117 #, python-format msgid "up to date, last checked never" msgstr "최신입니다. 마지막 확인: 없음" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:115 +#: selfdrive/ui/layouts/settings/software.py:115 #, python-format msgid "up to date, last checked {}" msgstr "최신입니다. 마지막 확인: {}" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:109 +#: selfdrive/ui/layouts/settings/software.py:109 #, python-format msgid "update available" msgstr "업데이트 가능" -#: /home/batman/openpilot/selfdrive/ui/layouts/home.py:169 +#: selfdrive/ui/layouts/home.py:169 #, python-format msgid "{} ALERT" msgid_plural "{} ALERTS" msgstr[0] "{}건의 알림" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:40 +#: selfdrive/ui/layouts/settings/software.py:40 #, python-format msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "{}일 전" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:37 +#: selfdrive/ui/layouts/settings/software.py:37 #, python-format msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "{}시간 전" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:34 +#: selfdrive/ui/layouts/settings/software.py:34 #, python-format msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "{}분 전" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:111 +#: selfdrive/ui/layouts/settings/firehose.py:111 #, python-format msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." msgstr[0] "현재까지 귀하의 주행 {}세그먼트가 학습 데이터셋에 포함되었습니다." -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:62 +#: selfdrive/ui/widgets/prime.py:62 #, python-format msgid "✓ SUBSCRIBED" msgstr "✓ 구독됨" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:22 +#: selfdrive/ui/widgets/setup.py:22 #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "🔥 Firehose 모드 🔥" diff --git a/selfdrive/ui/translations/app_pt-BR.po b/selfdrive/ui/translations/app_pt-BR.po index a14304bb8..8a388d0da 100644 --- a/selfdrive/ui/translations/app_pt-BR.po +++ b/selfdrive/ui/translations/app_pt-BR.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-22 18:57-0700\n" +"POT-Creation-Date: 2025-10-23 00:50-0700\n" "PO-Revision-Date: 2025-10-21 00:00-0700\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" @@ -17,48 +17,48 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:160 +#: selfdrive/ui/layouts/settings/device.py:160 #, python-format msgid " Steering torque response calibration is complete." msgstr " A calibração da resposta de torque da direção foi concluída." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:158 +#: selfdrive/ui/layouts/settings/device.py:158 #, python-format msgid " Steering torque response calibration is {}% complete." msgstr " A calibração da resposta de torque da direção está {}% concluída." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#: selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr " Seu dispositivo está apontado {:.1f}° {} e {:.1f}° {}." -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:43 +#: selfdrive/ui/layouts/sidebar.py:43 msgid "--" msgstr "--" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#: selfdrive/ui/widgets/prime.py:47 #, python-format msgid "1 year of drive storage" msgstr "1 ano de armazenamento de condução" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#: selfdrive/ui/widgets/prime.py:47 #, python-format msgid "24/7 LTE connectivity" msgstr "Conectividade LTE 24/7" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:46 +#: selfdrive/ui/layouts/sidebar.py:46 msgid "2G" msgstr "2G" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:47 +#: selfdrive/ui/layouts/sidebar.py:47 msgid "3G" msgstr "3G" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:49 +#: selfdrive/ui/layouts/sidebar.py:49 msgid "5G" msgstr "5G" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:23 +#: selfdrive/ui/layouts/settings/developer.py:23 msgid "" "WARNING: openpilot longitudinal control is in alpha for this car and will " "disable Automatic Emergency Braking (AEB).

On this car, openpilot " @@ -75,22 +75,22 @@ msgstr "" "longitudinal do openpilot. Recomenda-se ativar o Modo Experimental ao ativar " "o controle longitudinal do openpilot em alpha." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:148 +#: selfdrive/ui/layouts/settings/device.py:148 #, python-format msgid "

Steering lag calibration is complete." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:146 +#: selfdrive/ui/layouts/settings/device.py:146 #, python-format msgid "

Steering lag calibration is {}% complete." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:138 +#: selfdrive/ui/layouts/settings/firehose.py:138 #, python-format msgid "ACTIVE" msgstr "ATIVO" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:15 +#: selfdrive/ui/layouts/settings/developer.py:15 msgid "" "ADB (Android Debug Bridge) allows connecting to your device over USB or over " "the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." @@ -99,42 +99,41 @@ msgstr "" "pela rede. Veja https://docs.comma.ai/how-to/connect-to-comma para mais " "informações." -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:30 +#: selfdrive/ui/widgets/ssh_key.py:30 msgid "ADD" msgstr "ADICIONAR" -#: /home/batman/openpilot/system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:139 #, python-format msgid "APN Setting" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 +#: selfdrive/ui/widgets/offroad_alerts.py:109 #, python-format msgid "Acknowledge Excessive Actuation" msgstr "Reconhecer Atuação Excessiva" -#: /home/batman/openpilot/system/ui/widgets/network.py:74 -#: /home/batman/openpilot/system/ui/widgets/network.py:95 +#: system/ui/widgets/network.py:74 system/ui/widgets/network.py:95 #, python-format msgid "Advanced" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#: selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Aggressive" msgstr "Agressivo" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:116 +#: selfdrive/ui/layouts/onboarding.py:116 #, python-format msgid "Agree" msgstr "Concordo" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:70 +#: selfdrive/ui/layouts/settings/toggles.py:70 #, python-format msgid "Always-On Driver Monitoring" msgstr "Monitoramento de Motorista Sempre Ativo" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:186 +#: selfdrive/ui/layouts/settings/toggles.py:186 #, python-format msgid "" "An alpha version of openpilot longitudinal control can be tested, along with " @@ -143,241 +142,235 @@ msgstr "" "Uma versão alpha do controle longitudinal do openpilot pode ser testada, " "junto com o Modo Experimental, em ramificações fora de release." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:187 +#: selfdrive/ui/layouts/settings/device.py:187 #, python-format msgid "Are you sure you want to power off?" msgstr "Tem certeza de que deseja desligar?" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:175 +#: selfdrive/ui/layouts/settings/device.py:175 #, python-format msgid "Are you sure you want to reboot?" msgstr "Tem certeza de que deseja reiniciar?" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:119 +#: selfdrive/ui/layouts/settings/device.py:119 #, python-format msgid "Are you sure you want to reset calibration?" msgstr "Tem certeza de que deseja redefinir a calibração?" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:163 +#: selfdrive/ui/layouts/settings/software.py:163 #, python-format msgid "Are you sure you want to uninstall?" msgstr "Tem certeza de que deseja desinstalar?" -#: /home/batman/openpilot/system/ui/widgets/network.py:99 -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:147 +#: system/ui/widgets/network.py:99 selfdrive/ui/layouts/onboarding.py:147 #, python-format msgid "Back" msgstr "Voltar" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:38 +#: selfdrive/ui/widgets/prime.py:38 #, python-format msgid "Become a comma prime member at connect.comma.ai" msgstr "Torne-se membro comma prime em connect.comma.ai" -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:130 +#: selfdrive/ui/widgets/pairing_dialog.py:130 #, python-format msgid "Bookmark connect.comma.ai to your home screen to use it like an app" msgstr "Adicione connect.comma.ai à tela inicial para usá-lo como um app" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:68 +#: selfdrive/ui/layouts/settings/device.py:68 #, python-format msgid "CHANGE" msgstr "ALTERAR" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:50 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:107 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:118 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:147 +#: selfdrive/ui/layouts/settings/software.py:50 +#: selfdrive/ui/layouts/settings/software.py:107 +#: selfdrive/ui/layouts/settings/software.py:118 +#: selfdrive/ui/layouts/settings/software.py:147 #, python-format msgid "CHECK" msgstr "VERIFICAR" -#: /home/batman/openpilot/selfdrive/ui/widgets/exp_mode_button.py:50 +#: selfdrive/ui/widgets/exp_mode_button.py:50 #, python-format msgid "CHILL MODE ON" msgstr "MODO CHILL ATIVO" -#: /home/batman/openpilot/system/ui/widgets/network.py:155 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:73 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:134 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:136 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:138 +#: system/ui/widgets/network.py:155 selfdrive/ui/layouts/sidebar.py:73 +#: selfdrive/ui/layouts/sidebar.py:134 selfdrive/ui/layouts/sidebar.py:136 +#: selfdrive/ui/layouts/sidebar.py:138 #, python-format msgid "CONNECT" msgstr "CONECTAR" -#: /home/batman/openpilot/system/ui/widgets/network.py:369 +#: system/ui/widgets/network.py:369 #, python-format msgid "CONNECTING..." msgstr "CONECTAR" -#: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:23 -#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:35 -#: /home/batman/openpilot/system/ui/widgets/keyboard.py:81 -#: /home/batman/openpilot/system/ui/widgets/network.py:318 +#: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35 +#: system/ui/widgets/keyboard.py:81 system/ui/widgets/network.py:318 #, python-format msgid "Cancel" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:134 +#: system/ui/widgets/network.py:134 #, python-format msgid "Cellular Metered" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:68 +#: selfdrive/ui/layouts/settings/device.py:68 #, python-format msgid "Change Language" msgstr "Alterar Idioma" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:125 +#: selfdrive/ui/layouts/settings/toggles.py:125 #, python-format msgid "Changing this setting will restart openpilot if the car is powered on." msgstr "" " Alterar esta configuração reiniciará o openpilot se o carro estiver ligado." -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:129 +#: selfdrive/ui/widgets/pairing_dialog.py:129 #, python-format msgid "Click \"add new device\" and scan the QR code on the right" msgstr "Toque em \"adicionar novo dispositivo\" e escaneie o QR code à direita" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 +#: selfdrive/ui/widgets/offroad_alerts.py:104 #, python-format msgid "Close" msgstr "Fechar" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:49 +#: selfdrive/ui/layouts/settings/software.py:49 #, python-format msgid "Current Version" msgstr "Versão Atual" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:110 +#: selfdrive/ui/layouts/settings/software.py:110 #, python-format msgid "DOWNLOAD" msgstr "BAIXAR" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:115 +#: selfdrive/ui/layouts/onboarding.py:115 #, python-format msgid "Decline" msgstr "Recusar" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:148 +#: selfdrive/ui/layouts/onboarding.py:148 #, python-format msgid "Decline, uninstall openpilot" msgstr "Recusar, desinstalar o openpilot" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:67 +#: selfdrive/ui/layouts/settings/settings.py:67 msgid "Developer" msgstr "Desenvolvedor" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:62 +#: selfdrive/ui/layouts/settings/settings.py:62 msgid "Device" msgstr "Dispositivo" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:58 +#: selfdrive/ui/layouts/settings/toggles.py:58 #, python-format msgid "Disengage on Accelerator Pedal" msgstr "Desativar ao pressionar o acelerador" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:184 +#: selfdrive/ui/layouts/settings/device.py:184 #, python-format msgid "Disengage to Power Off" msgstr "Desativar para Desligar" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:172 +#: selfdrive/ui/layouts/settings/device.py:172 #, python-format msgid "Disengage to Reboot" msgstr "Desativar para Reiniciar" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:103 +#: selfdrive/ui/layouts/settings/device.py:103 #, python-format msgid "Disengage to Reset Calibration" msgstr "Desativar para Redefinir Calibração" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:32 +#: selfdrive/ui/layouts/settings/toggles.py:32 msgid "Display speed in km/h instead of mph." msgstr "Exibir velocidade em km/h em vez de mph." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:59 +#: selfdrive/ui/layouts/settings/device.py:59 #, python-format msgid "Dongle ID" msgstr "ID do Dongle" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:50 +#: selfdrive/ui/layouts/settings/software.py:50 #, python-format msgid "Download" msgstr "Baixar" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:62 +#: selfdrive/ui/layouts/settings/device.py:62 #, python-format msgid "Driver Camera" msgstr "Câmera do Motorista" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:96 +#: selfdrive/ui/layouts/settings/toggles.py:96 #, python-format msgid "Driving Personality" msgstr "Personalidade de Condução" -#: /home/batman/openpilot/system/ui/widgets/network.py:123 -#: /home/batman/openpilot/system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:123 system/ui/widgets/network.py:139 #, python-format msgid "EDIT" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:138 +#: selfdrive/ui/layouts/sidebar.py:138 msgid "ERROR" msgstr "ERRO" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:45 +#: selfdrive/ui/layouts/sidebar.py:45 msgid "ETH" msgstr "ETH" -#: /home/batman/openpilot/selfdrive/ui/widgets/exp_mode_button.py:50 +#: selfdrive/ui/widgets/exp_mode_button.py:50 #, python-format msgid "EXPERIMENTAL MODE ON" msgstr "MODO EXPERIMENTAL ATIVO" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:166 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:228 +#: selfdrive/ui/layouts/settings/developer.py:166 +#: selfdrive/ui/layouts/settings/toggles.py:228 #, python-format msgid "Enable" msgstr "Ativar" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:39 +#: selfdrive/ui/layouts/settings/developer.py:39 #, python-format msgid "Enable ADB" msgstr "Ativar ADB" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:64 +#: selfdrive/ui/layouts/settings/toggles.py:64 #, python-format msgid "Enable Lane Departure Warnings" msgstr "Ativar alertas de saída de faixa" -#: /home/batman/openpilot/system/ui/widgets/network.py:129 +#: system/ui/widgets/network.py:129 #, python-format msgid "Enable Roaming" msgstr "Ativar openpilot" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:48 +#: selfdrive/ui/layouts/settings/developer.py:48 #, python-format msgid "Enable SSH" msgstr "Ativar SSH" -#: /home/batman/openpilot/system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:120 #, python-format msgid "Enable Tethering" msgstr "Ativar alertas de saída de faixa" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:30 +#: selfdrive/ui/layouts/settings/toggles.py:30 msgid "Enable driver monitoring even when openpilot is not engaged." msgstr "" "Ativar monitoramento do motorista mesmo quando o openpilot não está engajado." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:46 +#: selfdrive/ui/layouts/settings/toggles.py:46 #, python-format msgid "Enable openpilot" msgstr "Ativar openpilot" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:189 +#: selfdrive/ui/layouts/settings/toggles.py:189 #, python-format msgid "" "Enable the openpilot longitudinal control (alpha) toggle to allow " @@ -386,44 +379,42 @@ msgstr "" "Ative a opção de controle longitudinal do openpilot (alpha) para permitir o " "Modo Experimental." -#: /home/batman/openpilot/system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:204 #, python-format msgid "Enter APN" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:241 +#: system/ui/widgets/network.py:241 #, python-format msgid "Enter SSID" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:254 +#: system/ui/widgets/network.py:254 #, python-format msgid "Enter new tethering password" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:237 -#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 #, python-format msgid "Enter password" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:89 +#: selfdrive/ui/widgets/ssh_key.py:89 #, python-format msgid "Enter your GitHub username" msgstr "Digite seu nome de usuário do GitHub" -#: /home/batman/openpilot/system/ui/widgets/list_view.py:123 -#: /home/batman/openpilot/system/ui/widgets/list_view.py:160 +#: system/ui/widgets/list_view.py:123 system/ui/widgets/list_view.py:160 #, python-format msgid "Error" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:52 +#: selfdrive/ui/layouts/settings/toggles.py:52 #, python-format msgid "Experimental Mode" msgstr "Modo Experimental" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:181 +#: selfdrive/ui/layouts/settings/toggles.py:181 #, python-format msgid "" "Experimental mode is currently unavailable on this car since the car's stock " @@ -432,25 +423,25 @@ msgstr "" "O Modo Experimental está indisponível neste carro pois o ACC original do " "carro é usado para controle longitudinal." -#: /home/batman/openpilot/system/ui/widgets/network.py:373 +#: system/ui/widgets/network.py:373 #, python-format msgid "FORGETTING..." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:44 +#: selfdrive/ui/widgets/setup.py:44 #, python-format msgid "Finish Setup" msgstr "Concluir Configuração" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:66 +#: selfdrive/ui/layouts/settings/settings.py:66 msgid "Firehose" msgstr "Firehose" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:18 +#: selfdrive/ui/layouts/settings/firehose.py:18 msgid "Firehose Mode" msgstr "Modo Firehose" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:25 +#: selfdrive/ui/layouts/settings/firehose.py:25 msgid "" "For maximum effectiveness, bring your device inside and connect to a good " "USB-C adapter and Wi-Fi weekly.\n" @@ -493,81 +484,79 @@ msgstr "" "Importa qual software eu executo? Sim, apenas o openpilot upstream (e forks " "específicos) podem ser usados para treinamento." -#: /home/batman/openpilot/system/ui/widgets/network.py:318 -#: /home/batman/openpilot/system/ui/widgets/network.py:451 +#: system/ui/widgets/network.py:318 system/ui/widgets/network.py:451 #, python-format msgid "Forget" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:319 +#: system/ui/widgets/network.py:319 #, python-format msgid "Forget Wi-Fi Network \"{}\"?" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:71 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 msgid "GOOD" msgstr "BOM" -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:128 +#: selfdrive/ui/widgets/pairing_dialog.py:128 #, python-format msgid "Go to https://connect.comma.ai on your phone" msgstr "Acesse https://connect.comma.ai no seu telefone" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:129 +#: selfdrive/ui/layouts/sidebar.py:129 msgid "HIGH" msgstr "ALTO" -#: /home/batman/openpilot/system/ui/widgets/network.py:155 +#: system/ui/widgets/network.py:155 #, python-format msgid "Hidden Network" msgstr "Rede" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:140 +#: selfdrive/ui/layouts/settings/firehose.py:140 #, python-format msgid "INACTIVE: connect to an unmetered network" msgstr "INATIVO: conecte a uma rede sem franquia" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:53 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:136 +#: selfdrive/ui/layouts/settings/software.py:53 +#: selfdrive/ui/layouts/settings/software.py:136 #, python-format msgid "INSTALL" msgstr "INSTALAR" -#: /home/batman/openpilot/system/ui/widgets/network.py:150 +#: system/ui/widgets/network.py:150 #, python-format msgid "IP Address" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:53 +#: selfdrive/ui/layouts/settings/software.py:53 #, python-format msgid "Install Update" msgstr "Instalar Atualização" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:56 +#: selfdrive/ui/layouts/settings/developer.py:56 #, python-format msgid "Joystick Debug Mode" msgstr "Modo de Depuração do Joystick" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:29 +#: selfdrive/ui/widgets/ssh_key.py:29 msgid "LOADING" msgstr "CARREGANDO" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:48 +#: selfdrive/ui/layouts/sidebar.py:48 msgid "LTE" msgstr "LTE" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:64 +#: selfdrive/ui/layouts/settings/developer.py:64 #, python-format msgid "Longitudinal Maneuver Mode" msgstr "Modo de Manobra Longitudinal" -#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:148 +#: selfdrive/ui/onroad/hud_renderer.py:148 #, python-format msgid "MAX" msgstr "MÁX" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:75 +#: selfdrive/ui/widgets/setup.py:75 #, python-format msgid "" "Maximize your training data uploads to improve openpilot's driving models." @@ -575,94 +564,90 @@ msgstr "" "Maximize seus envios de dados de treinamento para melhorar os modelos de " "condução do openpilot." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:59 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:60 +#: selfdrive/ui/layouts/settings/device.py:59 +#: selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "N/A" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:142 +#: selfdrive/ui/layouts/sidebar.py:142 msgid "NO" msgstr "NÃO" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:63 +#: selfdrive/ui/layouts/settings/settings.py:63 msgid "Network" msgstr "Rede" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:114 +#: selfdrive/ui/widgets/ssh_key.py:114 #, python-format msgid "No SSH keys found" msgstr "Nenhuma chave SSH encontrada" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:126 +#: selfdrive/ui/widgets/ssh_key.py:126 #, python-format msgid "No SSH keys found for user '{}'" msgstr "Nenhuma chave SSH encontrada para o usuário '{username}'" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:320 +#: selfdrive/ui/widgets/offroad_alerts.py:320 #, python-format msgid "No release notes available." msgstr "Sem notas de versão disponíveis." -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:73 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: selfdrive/ui/layouts/sidebar.py:73 selfdrive/ui/layouts/sidebar.py:134 msgid "OFFLINE" msgstr "OFFLINE" -#: /home/batman/openpilot/system/ui/widgets/html_render.py:263 -#: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:93 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: system/ui/widgets/html_render.py:263 system/ui/widgets/confirm_dialog.py:93 +#: selfdrive/ui/layouts/sidebar.py:127 #, python-format msgid "OK" msgstr "OK" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:72 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:136 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:136 +#: selfdrive/ui/layouts/sidebar.py:144 msgid "ONLINE" msgstr "ONLINE" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:20 +#: selfdrive/ui/widgets/setup.py:20 #, python-format msgid "Open" msgstr "Abrir" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:48 +#: selfdrive/ui/layouts/settings/device.py:48 #, python-format msgid "PAIR" msgstr "EMPARELHAR" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:142 +#: selfdrive/ui/layouts/sidebar.py:142 msgid "PANDA" msgstr "PANDA" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:62 +#: selfdrive/ui/layouts/settings/device.py:62 #, python-format msgid "PREVIEW" msgstr "PRÉVIA" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:44 +#: selfdrive/ui/widgets/prime.py:44 #, python-format msgid "PRIME FEATURES:" msgstr "RECURSOS PRIME:" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:48 +#: selfdrive/ui/layouts/settings/device.py:48 #, python-format msgid "Pair Device" msgstr "Emparelhar Dispositivo" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:19 +#: selfdrive/ui/widgets/setup.py:19 #, python-format msgid "Pair device" msgstr "Emparelhar dispositivo" -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:103 +#: selfdrive/ui/widgets/pairing_dialog.py:103 #, python-format msgid "Pair your device to your comma account" msgstr "Emparelhe seu dispositivo à sua conta comma" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:48 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:24 +#: selfdrive/ui/widgets/setup.py:48 selfdrive/ui/layouts/settings/device.py:24 #, python-format msgid "" "Pair your device with comma connect (connect.comma.ai) and claim your comma " @@ -671,28 +656,28 @@ msgstr "" "Emparelhe seu dispositivo com o comma connect (connect.comma.ai) e resgate " "sua oferta comma prime." -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:91 +#: selfdrive/ui/widgets/setup.py:91 #, python-format msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "Conecte-se ao Wi‑Fi para concluir o emparelhamento inicial" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:187 +#: selfdrive/ui/layouts/settings/device.py:55 +#: selfdrive/ui/layouts/settings/device.py:187 #, python-format msgid "Power Off" msgstr "Desligar" -#: /home/batman/openpilot/system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:144 #, python-format msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:135 +#: system/ui/widgets/network.py:135 #, python-format msgid "Prevent large data uploads when on a metered cellular connection" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:25 +#: selfdrive/ui/layouts/settings/device.py:25 msgid "" "Preview the driver facing camera to ensure that driver monitoring has good " "visibility. (vehicle must be off)" @@ -701,42 +686,42 @@ msgstr "" "monitoramento do motorista tenha boa visibilidade. (veículo deve estar " "desligado)" -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:161 +#: selfdrive/ui/widgets/pairing_dialog.py:161 #, python-format msgid "QR Code Error" msgstr "Erro no QR Code" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:31 +#: selfdrive/ui/widgets/ssh_key.py:31 msgid "REMOVE" msgstr "REMOVER" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:51 +#: selfdrive/ui/layouts/settings/device.py:51 #, python-format msgid "RESET" msgstr "REDEFINIR" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:65 +#: selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "REVIEW" msgstr "REVISAR" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:175 +#: selfdrive/ui/layouts/settings/device.py:55 +#: selfdrive/ui/layouts/settings/device.py:175 #, python-format msgid "Reboot" msgstr "Reiniciar" -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:66 +#: selfdrive/ui/onroad/alert_renderer.py:66 #, python-format msgid "Reboot Device" msgstr "Reiniciar Dispositivo" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 +#: selfdrive/ui/widgets/offroad_alerts.py:112 #, python-format msgid "Reboot and Update" msgstr "Reiniciar e Atualizar" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:27 +#: selfdrive/ui/layouts/settings/toggles.py:27 msgid "" "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 " @@ -745,17 +730,17 @@ msgstr "" "Receba alertas para voltar à faixa quando seu veículo cruzar uma linha de " "faixa detectada sem seta ativada ao dirigir acima de 31 mph (50 km/h)." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:76 +#: selfdrive/ui/layouts/settings/toggles.py:76 #, python-format msgid "Record and Upload Driver Camera" msgstr "Gravar e Enviar Câmera do Motorista" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:82 +#: selfdrive/ui/layouts/settings/toggles.py:82 #, python-format msgid "Record and Upload Microphone Audio" msgstr "Gravar e Enviar Áudio do Microfone" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +#: selfdrive/ui/layouts/settings/toggles.py:33 msgid "" "Record and store microphone audio while driving. The audio will be included " "in the dashcam video in comma connect." @@ -763,100 +748,100 @@ msgstr "" "Grave e armazene o áudio do microfone enquanto dirige. O áudio será incluído " "no vídeo da dashcam no comma connect." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:67 +#: selfdrive/ui/layouts/settings/device.py:67 #, python-format msgid "Regulatory" msgstr "Regulatório" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#: selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Relaxed" msgstr "Relaxado" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#: selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote access" msgstr "Acesso remoto" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#: selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote snapshots" msgstr "Capturas remotas" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:123 +#: selfdrive/ui/widgets/ssh_key.py:123 #, python-format msgid "Request timed out" msgstr "Tempo da solicitação esgotado" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:119 +#: selfdrive/ui/layouts/settings/device.py:119 #, python-format msgid "Reset" msgstr "Redefinir" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:51 +#: selfdrive/ui/layouts/settings/device.py:51 #, python-format msgid "Reset Calibration" msgstr "Redefinir Calibração" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:65 +#: selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "Review Training Guide" msgstr "Revisar Guia de Treinamento" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:27 +#: selfdrive/ui/layouts/settings/device.py:27 msgid "Review the rules, features, and limitations of openpilot" msgstr "Revise as regras, recursos e limitações do openpilot" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:61 +#: selfdrive/ui/layouts/settings/software.py:61 #, python-format msgid "SELECT" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:53 +#: selfdrive/ui/layouts/settings/developer.py:53 #, python-format msgid "SSH Keys" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:310 +#: system/ui/widgets/network.py:310 #, python-format msgid "Scanning Wi-Fi networks..." msgstr "" -#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/option_dialog.py:36 #, python-format msgid "Select" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:183 +#: selfdrive/ui/layouts/settings/software.py:183 #, python-format msgid "Select a branch" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:91 +#: selfdrive/ui/layouts/settings/device.py:91 #, python-format msgid "Select a language" msgstr "Selecione um idioma" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:60 +#: selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "Serial" msgstr "Serial" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 +#: selfdrive/ui/widgets/offroad_alerts.py:106 #, python-format msgid "Snooze Update" msgstr "Adiar Atualização" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:65 +#: selfdrive/ui/layouts/settings/settings.py:65 msgid "Software" msgstr "Software" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#: selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Standard" msgstr "Padrão" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:22 +#: selfdrive/ui/layouts/settings/toggles.py:22 msgid "" "Standard is recommended. In aggressive mode, openpilot will follow lead cars " "closer and be more aggressive with the gas and brake. In relaxed mode " @@ -869,69 +854,67 @@ msgstr "" "compatíveis, você pode alternar essas personalidades com o botão de " "distância do volante." -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:59 -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:65 +#: selfdrive/ui/onroad/alert_renderer.py:59 +#: selfdrive/ui/onroad/alert_renderer.py:65 #, python-format msgid "System Unresponsive" msgstr "Sistema sem resposta" -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:58 +#: selfdrive/ui/onroad/alert_renderer.py:58 #, python-format msgid "TAKE CONTROL IMMEDIATELY" msgstr "ASSUMA O CONTROLE IMEDIATAMENTE" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:71 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:125 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:127 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:129 +#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 +#: selfdrive/ui/layouts/sidebar.py:127 selfdrive/ui/layouts/sidebar.py:129 msgid "TEMP" msgstr "TEMP" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:61 +#: selfdrive/ui/layouts/settings/software.py:61 #, python-format msgid "Target Branch" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:124 +#: system/ui/widgets/network.py:124 #, python-format msgid "Tethering Password" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:64 +#: selfdrive/ui/layouts/settings/settings.py:64 msgid "Toggles" msgstr "Alternâncias" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:72 +#: selfdrive/ui/layouts/settings/software.py:72 #, python-format msgid "UNINSTALL" msgstr "DESINSTALAR" -#: /home/batman/openpilot/selfdrive/ui/layouts/home.py:155 +#: selfdrive/ui/layouts/home.py:155 #, python-format msgid "UPDATE" msgstr "ATUALIZAR" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:72 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:163 +#: selfdrive/ui/layouts/settings/software.py:72 +#: selfdrive/ui/layouts/settings/software.py:163 #, python-format msgid "Uninstall" msgstr "Desinstalar" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:117 +#: selfdrive/ui/layouts/sidebar.py:117 msgid "Unknown" msgstr "Desconhecido" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:48 +#: selfdrive/ui/layouts/settings/software.py:48 #, python-format msgid "Updates are only downloaded while the car is off." msgstr "Atualizações são baixadas apenas com o carro desligado." -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:33 +#: selfdrive/ui/widgets/prime.py:33 #, python-format msgid "Upgrade Now" msgstr "Atualizar Agora" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +#: selfdrive/ui/layouts/settings/toggles.py:31 msgid "" "Upload data from the driver facing camera and help improve the driver " "monitoring algorithm." @@ -939,12 +922,12 @@ msgstr "" "Envie dados da câmera voltada para o motorista e ajude a melhorar o " "algoritmo de monitoramento do motorista." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:88 +#: selfdrive/ui/layouts/settings/toggles.py:88 #, python-format msgid "Use Metric System" msgstr "Usar Sistema Métrico" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:17 +#: selfdrive/ui/layouts/settings/toggles.py:17 msgid "" "Use the openpilot system for adaptive cruise control and lane keep driver " "assistance. Your attention is required at all times to use this feature." @@ -953,22 +936,21 @@ msgstr "" "de permanência em faixa. Sua atenção é necessária o tempo todo para usar " "este recurso." -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:72 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:144 msgid "VEHICLE" msgstr "VEÍCULO" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:67 +#: selfdrive/ui/layouts/settings/device.py:67 #, python-format msgid "VIEW" msgstr "VER" -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:52 +#: selfdrive/ui/onroad/alert_renderer.py:52 #, python-format msgid "Waiting to start" msgstr "Aguardando para iniciar" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:19 +#: selfdrive/ui/layouts/settings/developer.py:19 msgid "" "Warning: This grants SSH access to all public keys in your GitHub settings. " "Never enter a GitHub username other than your own. A comma employee will " @@ -979,36 +961,36 @@ msgstr "" "seja o seu. Um funcionário da comma NUNCA pedirá para você adicionar o nome " "de usuário do GitHub dele." -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:111 +#: selfdrive/ui/layouts/onboarding.py:111 #, python-format msgid "Welcome to openpilot" msgstr "Bem-vindo ao openpilot" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:20 +#: selfdrive/ui/layouts/settings/toggles.py:20 msgid "When enabled, pressing the accelerator pedal will disengage openpilot." msgstr "" "Quando ativado, pressionar o pedal do acelerador desengajará o openpilot." -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:44 +#: selfdrive/ui/layouts/sidebar.py:44 msgid "Wi-Fi" msgstr "Wi‑Fi" -#: /home/batman/openpilot/system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:144 #, python-format msgid "Wi-Fi Network Metered" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:314 #, python-format msgid "Wrong password" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:145 +#: selfdrive/ui/layouts/onboarding.py:145 #, python-format msgid "You must accept the Terms and Conditions in order to use openpilot." msgstr "Você deve aceitar os Termos e Condições para usar o openpilot." -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:112 +#: selfdrive/ui/layouts/onboarding.py:112 #, python-format msgid "" "You must accept the Terms and Conditions to use openpilot. Read the latest " @@ -1017,83 +999,82 @@ msgstr "" "Você deve aceitar os Termos e Condições para usar o openpilot. Leia os " "termos mais recentes em https://comma.ai/terms antes de continuar." -#: /home/batman/openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:34 +#: selfdrive/ui/onroad/driver_camera_dialog.py:34 #, python-format msgid "camera starting" msgstr "câmera iniciando" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:63 +#: selfdrive/ui/widgets/prime.py:63 #, python-format msgid "comma prime" msgstr "comma prime" -#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:142 #, python-format msgid "default" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#: selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid "down" msgstr "para baixo" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:106 +#: selfdrive/ui/layouts/settings/software.py:106 #, python-format msgid "failed to check for update" msgstr "falha ao verificar atualização" -#: /home/batman/openpilot/system/ui/widgets/network.py:237 -#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 #, python-format msgid "for \"{}\"" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:177 +#: selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "km/h" msgstr "km/h" -#: /home/batman/openpilot/system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:204 #, python-format msgid "leave blank for automatic configuration" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:134 +#: selfdrive/ui/layouts/settings/device.py:134 #, python-format msgid "left" msgstr "à esquerda" -#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:142 #, python-format msgid "metered" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:177 +#: selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "mph" msgstr "mph" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:20 +#: selfdrive/ui/layouts/settings/software.py:20 #, python-format msgid "never" msgstr "nunca" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:31 +#: selfdrive/ui/layouts/settings/software.py:31 #, python-format msgid "now" msgstr "agora" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:71 +#: selfdrive/ui/layouts/settings/developer.py:71 #, python-format msgid "openpilot Longitudinal Control (Alpha)" msgstr "Controle Longitudinal do openpilot (Alpha)" -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:51 +#: selfdrive/ui/onroad/alert_renderer.py:51 #, python-format msgid "openpilot Unavailable" msgstr "openpilot Indisponível" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:158 +#: selfdrive/ui/layouts/settings/toggles.py:158 #, python-format msgid "" "openpilot defaults to driving in chill mode. Experimental mode enables alpha-" @@ -1121,7 +1102,7 @@ msgstr "" "curvas. O logotipo do Modo Experimental também será exibido no canto " "superior direito." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:165 +#: selfdrive/ui/layouts/settings/device.py:165 #, python-format msgid "" "openpilot is continuously calibrating, resetting is rarely required. " @@ -1129,7 +1110,7 @@ msgid "" msgstr "" " Alterar esta configuração reiniciará o openpilot se o carro estiver ligado." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:20 +#: selfdrive/ui/layouts/settings/firehose.py:20 msgid "" "openpilot learns to drive by watching humans, like you, drive.\n" "\n" @@ -1143,13 +1124,13 @@ msgstr "" "melhorar os modelos de condução do openpilot. Mais dados significam modelos " "maiores, o que significa um Modo Experimental melhor." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:183 +#: selfdrive/ui/layouts/settings/toggles.py:183 #, python-format msgid "openpilot longitudinal control may come in a future update." msgstr "" "o controle longitudinal do openpilot pode vir em uma atualização futura." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:26 +#: selfdrive/ui/layouts/settings/device.py:26 msgid "" "openpilot requires the device to be mounted within 4° left or right and " "within 5° up or 9° down." @@ -1157,65 +1138,65 @@ msgstr "" "o openpilot requer que o dispositivo seja montado dentro de 4° para a " "esquerda ou direita e dentro de 5° para cima ou 9° para baixo." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:134 +#: selfdrive/ui/layouts/settings/device.py:134 #, python-format msgid "right" msgstr "à direita" -#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:142 #, python-format msgid "unmetered" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#: selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid "up" msgstr "para cima" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:117 +#: selfdrive/ui/layouts/settings/software.py:117 #, python-format msgid "up to date, last checked never" msgstr "atualizado, última verificação: nunca" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:115 +#: selfdrive/ui/layouts/settings/software.py:115 #, python-format msgid "up to date, last checked {}" msgstr "atualizado, última verificação: {}" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:109 +#: selfdrive/ui/layouts/settings/software.py:109 #, python-format msgid "update available" msgstr "atualização disponível" -#: /home/batman/openpilot/selfdrive/ui/layouts/home.py:169 +#: selfdrive/ui/layouts/home.py:169 #, python-format msgid "{} ALERT" msgid_plural "{} ALERTS" msgstr[0] "{} ALERTA" msgstr[1] "{} ALERTAS" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:40 +#: selfdrive/ui/layouts/settings/software.py:40 #, python-format msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "{} dia atrás" msgstr[1] "{} dias atrás" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:37 +#: selfdrive/ui/layouts/settings/software.py:37 #, python-format msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "{} hora atrás" msgstr[1] "{} horas atrás" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:34 +#: selfdrive/ui/layouts/settings/software.py:34 #, python-format msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "{} minuto atrás" msgstr[1] "{} minutos atrás" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:111 +#: selfdrive/ui/layouts/settings/firehose.py:111 #, python-format msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." @@ -1224,12 +1205,12 @@ msgstr[0] "" msgstr[1] "" "{} segmentos da sua condução estão no conjunto de treinamento até agora." -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:62 +#: selfdrive/ui/widgets/prime.py:62 #, python-format msgid "✓ SUBSCRIBED" msgstr "✓ ASSINADO" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:22 +#: selfdrive/ui/widgets/setup.py:22 #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "🔥 Modo Firehose 🔥" diff --git a/selfdrive/ui/translations/app_th.po b/selfdrive/ui/translations/app_th.po index 87420ed07..f2e56f288 100644 --- a/selfdrive/ui/translations/app_th.po +++ b/selfdrive/ui/translations/app_th.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-22 18:57-0700\n" +"POT-Creation-Date: 2025-10-23 00:50-0700\n" "PO-Revision-Date: 2025-10-22 16:32-0700\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" @@ -17,48 +17,48 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:160 +#: selfdrive/ui/layouts/settings/device.py:160 #, python-format msgid " Steering torque response calibration is complete." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:158 +#: selfdrive/ui/layouts/settings/device.py:158 #, python-format msgid " Steering torque response calibration is {}% complete." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#: selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:43 +#: selfdrive/ui/layouts/sidebar.py:43 msgid "--" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#: selfdrive/ui/widgets/prime.py:47 #, python-format msgid "1 year of drive storage" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#: selfdrive/ui/widgets/prime.py:47 #, python-format msgid "24/7 LTE connectivity" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:46 +#: selfdrive/ui/layouts/sidebar.py:46 msgid "2G" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:47 +#: selfdrive/ui/layouts/sidebar.py:47 msgid "3G" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:49 +#: selfdrive/ui/layouts/sidebar.py:49 msgid "5G" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:23 +#: selfdrive/ui/layouts/settings/developer.py:23 msgid "" "WARNING: openpilot longitudinal control is in alpha for this car and will " "disable Automatic Emergency Braking (AEB).

On this car, openpilot " @@ -69,371 +69,362 @@ msgid "" "powered on." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:148 +#: selfdrive/ui/layouts/settings/device.py:148 #, python-format msgid "

Steering lag calibration is complete." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:146 +#: selfdrive/ui/layouts/settings/device.py:146 #, python-format msgid "

Steering lag calibration is {}% complete." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:138 +#: selfdrive/ui/layouts/settings/firehose.py:138 #, python-format msgid "ACTIVE" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:15 +#: selfdrive/ui/layouts/settings/developer.py:15 msgid "" "ADB (Android Debug Bridge) allows connecting to your device over USB or over " "the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:30 +#: selfdrive/ui/widgets/ssh_key.py:30 msgid "ADD" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:139 #, python-format msgid "APN Setting" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 +#: selfdrive/ui/widgets/offroad_alerts.py:109 #, python-format msgid "Acknowledge Excessive Actuation" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:74 -#: /home/batman/openpilot/system/ui/widgets/network.py:95 +#: system/ui/widgets/network.py:74 system/ui/widgets/network.py:95 #, python-format msgid "Advanced" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#: selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Aggressive" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:116 +#: selfdrive/ui/layouts/onboarding.py:116 #, python-format msgid "Agree" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:70 +#: selfdrive/ui/layouts/settings/toggles.py:70 #, python-format msgid "Always-On Driver Monitoring" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:186 +#: selfdrive/ui/layouts/settings/toggles.py:186 #, python-format msgid "" "An alpha version of openpilot longitudinal control can be tested, along with " "Experimental mode, on non-release branches." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:187 +#: selfdrive/ui/layouts/settings/device.py:187 #, python-format msgid "Are you sure you want to power off?" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:175 +#: selfdrive/ui/layouts/settings/device.py:175 #, python-format msgid "Are you sure you want to reboot?" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:119 +#: selfdrive/ui/layouts/settings/device.py:119 #, python-format msgid "Are you sure you want to reset calibration?" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:163 +#: selfdrive/ui/layouts/settings/software.py:163 #, python-format msgid "Are you sure you want to uninstall?" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:99 -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:147 +#: system/ui/widgets/network.py:99 selfdrive/ui/layouts/onboarding.py:147 #, python-format msgid "Back" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:38 +#: selfdrive/ui/widgets/prime.py:38 #, python-format msgid "Become a comma prime member at connect.comma.ai" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:130 +#: selfdrive/ui/widgets/pairing_dialog.py:130 #, python-format msgid "Bookmark connect.comma.ai to your home screen to use it like an app" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:68 +#: selfdrive/ui/layouts/settings/device.py:68 #, python-format msgid "CHANGE" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:50 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:107 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:118 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:147 +#: selfdrive/ui/layouts/settings/software.py:50 +#: selfdrive/ui/layouts/settings/software.py:107 +#: selfdrive/ui/layouts/settings/software.py:118 +#: selfdrive/ui/layouts/settings/software.py:147 #, python-format msgid "CHECK" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/exp_mode_button.py:50 +#: selfdrive/ui/widgets/exp_mode_button.py:50 #, python-format msgid "CHILL MODE ON" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:155 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:73 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:134 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:136 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:138 +#: system/ui/widgets/network.py:155 selfdrive/ui/layouts/sidebar.py:73 +#: selfdrive/ui/layouts/sidebar.py:134 selfdrive/ui/layouts/sidebar.py:136 +#: selfdrive/ui/layouts/sidebar.py:138 #, python-format msgid "CONNECT" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:369 +#: system/ui/widgets/network.py:369 #, python-format msgid "CONNECTING..." msgstr "" -#: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:23 -#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:35 -#: /home/batman/openpilot/system/ui/widgets/keyboard.py:81 -#: /home/batman/openpilot/system/ui/widgets/network.py:318 +#: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35 +#: system/ui/widgets/keyboard.py:81 system/ui/widgets/network.py:318 #, python-format msgid "Cancel" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:134 +#: system/ui/widgets/network.py:134 #, python-format msgid "Cellular Metered" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:68 +#: selfdrive/ui/layouts/settings/device.py:68 #, python-format msgid "Change Language" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:125 +#: selfdrive/ui/layouts/settings/toggles.py:125 #, python-format msgid "Changing this setting will restart openpilot if the car is powered on." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:129 +#: selfdrive/ui/widgets/pairing_dialog.py:129 #, python-format msgid "Click \"add new device\" and scan the QR code on the right" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 +#: selfdrive/ui/widgets/offroad_alerts.py:104 #, python-format msgid "Close" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:49 +#: selfdrive/ui/layouts/settings/software.py:49 #, python-format msgid "Current Version" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:110 +#: selfdrive/ui/layouts/settings/software.py:110 #, python-format msgid "DOWNLOAD" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:115 +#: selfdrive/ui/layouts/onboarding.py:115 #, python-format msgid "Decline" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:148 +#: selfdrive/ui/layouts/onboarding.py:148 #, python-format msgid "Decline, uninstall openpilot" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:67 +#: selfdrive/ui/layouts/settings/settings.py:67 msgid "Developer" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:62 +#: selfdrive/ui/layouts/settings/settings.py:62 msgid "Device" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:58 +#: selfdrive/ui/layouts/settings/toggles.py:58 #, python-format msgid "Disengage on Accelerator Pedal" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:184 +#: selfdrive/ui/layouts/settings/device.py:184 #, python-format msgid "Disengage to Power Off" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:172 +#: selfdrive/ui/layouts/settings/device.py:172 #, python-format msgid "Disengage to Reboot" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:103 +#: selfdrive/ui/layouts/settings/device.py:103 #, python-format msgid "Disengage to Reset Calibration" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:32 +#: selfdrive/ui/layouts/settings/toggles.py:32 msgid "Display speed in km/h instead of mph." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:59 +#: selfdrive/ui/layouts/settings/device.py:59 #, python-format msgid "Dongle ID" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:50 +#: selfdrive/ui/layouts/settings/software.py:50 #, python-format msgid "Download" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:62 +#: selfdrive/ui/layouts/settings/device.py:62 #, python-format msgid "Driver Camera" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:96 +#: selfdrive/ui/layouts/settings/toggles.py:96 #, python-format msgid "Driving Personality" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:123 -#: /home/batman/openpilot/system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:123 system/ui/widgets/network.py:139 #, python-format msgid "EDIT" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:138 +#: selfdrive/ui/layouts/sidebar.py:138 msgid "ERROR" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:45 +#: selfdrive/ui/layouts/sidebar.py:45 msgid "ETH" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/exp_mode_button.py:50 +#: selfdrive/ui/widgets/exp_mode_button.py:50 #, python-format msgid "EXPERIMENTAL MODE ON" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:166 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:228 +#: selfdrive/ui/layouts/settings/developer.py:166 +#: selfdrive/ui/layouts/settings/toggles.py:228 #, python-format msgid "Enable" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:39 +#: selfdrive/ui/layouts/settings/developer.py:39 #, python-format msgid "Enable ADB" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:64 +#: selfdrive/ui/layouts/settings/toggles.py:64 #, python-format msgid "Enable Lane Departure Warnings" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:129 +#: system/ui/widgets/network.py:129 #, python-format msgid "Enable Roaming" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:48 +#: selfdrive/ui/layouts/settings/developer.py:48 #, python-format msgid "Enable SSH" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:120 #, python-format msgid "Enable Tethering" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:30 +#: selfdrive/ui/layouts/settings/toggles.py:30 msgid "Enable driver monitoring even when openpilot is not engaged." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:46 +#: selfdrive/ui/layouts/settings/toggles.py:46 #, python-format msgid "Enable openpilot" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:189 +#: selfdrive/ui/layouts/settings/toggles.py:189 #, python-format msgid "" "Enable the openpilot longitudinal control (alpha) toggle to allow " "Experimental mode." msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:204 #, python-format msgid "Enter APN" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:241 +#: system/ui/widgets/network.py:241 #, python-format msgid "Enter SSID" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:254 +#: system/ui/widgets/network.py:254 #, python-format msgid "Enter new tethering password" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:237 -#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 #, python-format msgid "Enter password" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:89 +#: selfdrive/ui/widgets/ssh_key.py:89 #, python-format msgid "Enter your GitHub username" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/list_view.py:123 -#: /home/batman/openpilot/system/ui/widgets/list_view.py:160 +#: system/ui/widgets/list_view.py:123 system/ui/widgets/list_view.py:160 #, python-format msgid "Error" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:52 +#: selfdrive/ui/layouts/settings/toggles.py:52 #, python-format msgid "Experimental Mode" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:181 +#: selfdrive/ui/layouts/settings/toggles.py:181 #, python-format msgid "" "Experimental mode is currently unavailable on this car since the car's stock " "ACC is used for longitudinal control." msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:373 +#: system/ui/widgets/network.py:373 #, python-format msgid "FORGETTING..." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:44 +#: selfdrive/ui/widgets/setup.py:44 #, python-format msgid "Finish Setup" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:66 +#: selfdrive/ui/layouts/settings/settings.py:66 msgid "Firehose" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:18 +#: selfdrive/ui/layouts/settings/firehose.py:18 msgid "Firehose Mode" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:25 +#: selfdrive/ui/layouts/settings/firehose.py:25 msgid "" "For maximum effectiveness, bring your device inside and connect to a good " "USB-C adapter and Wi-Fi weekly.\n" @@ -457,359 +448,353 @@ msgid "" "particular forks) are able to be used for training." msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:318 -#: /home/batman/openpilot/system/ui/widgets/network.py:451 +#: system/ui/widgets/network.py:318 system/ui/widgets/network.py:451 #, python-format msgid "Forget" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:319 +#: system/ui/widgets/network.py:319 #, python-format msgid "Forget Wi-Fi Network \"{}\"?" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:71 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 msgid "GOOD" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:128 +#: selfdrive/ui/widgets/pairing_dialog.py:128 #, python-format msgid "Go to https://connect.comma.ai on your phone" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:129 +#: selfdrive/ui/layouts/sidebar.py:129 msgid "HIGH" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:155 +#: system/ui/widgets/network.py:155 #, python-format msgid "Hidden Network" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:140 +#: selfdrive/ui/layouts/settings/firehose.py:140 #, python-format msgid "INACTIVE: connect to an unmetered network" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:53 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:136 +#: selfdrive/ui/layouts/settings/software.py:53 +#: selfdrive/ui/layouts/settings/software.py:136 #, python-format msgid "INSTALL" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:150 +#: system/ui/widgets/network.py:150 #, python-format msgid "IP Address" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:53 +#: selfdrive/ui/layouts/settings/software.py:53 #, python-format msgid "Install Update" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:56 +#: selfdrive/ui/layouts/settings/developer.py:56 #, python-format msgid "Joystick Debug Mode" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:29 +#: selfdrive/ui/widgets/ssh_key.py:29 msgid "LOADING" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:48 +#: selfdrive/ui/layouts/sidebar.py:48 msgid "LTE" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:64 +#: selfdrive/ui/layouts/settings/developer.py:64 #, python-format msgid "Longitudinal Maneuver Mode" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:148 +#: selfdrive/ui/onroad/hud_renderer.py:148 #, python-format msgid "MAX" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:75 +#: selfdrive/ui/widgets/setup.py:75 #, python-format msgid "" "Maximize your training data uploads to improve openpilot's driving models." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:59 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:60 +#: selfdrive/ui/layouts/settings/device.py:59 +#: selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "N/A" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:142 +#: selfdrive/ui/layouts/sidebar.py:142 msgid "NO" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:63 +#: selfdrive/ui/layouts/settings/settings.py:63 msgid "Network" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:114 +#: selfdrive/ui/widgets/ssh_key.py:114 #, python-format msgid "No SSH keys found" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:126 +#: selfdrive/ui/widgets/ssh_key.py:126 #, python-format msgid "No SSH keys found for user '{}'" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:320 +#: selfdrive/ui/widgets/offroad_alerts.py:320 #, python-format msgid "No release notes available." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:73 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: selfdrive/ui/layouts/sidebar.py:73 selfdrive/ui/layouts/sidebar.py:134 msgid "OFFLINE" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/html_render.py:263 -#: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:93 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: system/ui/widgets/html_render.py:263 system/ui/widgets/confirm_dialog.py:93 +#: selfdrive/ui/layouts/sidebar.py:127 #, python-format msgid "OK" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:72 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:136 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:136 +#: selfdrive/ui/layouts/sidebar.py:144 msgid "ONLINE" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:20 +#: selfdrive/ui/widgets/setup.py:20 #, python-format msgid "Open" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:48 +#: selfdrive/ui/layouts/settings/device.py:48 #, python-format msgid "PAIR" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:142 +#: selfdrive/ui/layouts/sidebar.py:142 msgid "PANDA" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:62 +#: selfdrive/ui/layouts/settings/device.py:62 #, python-format msgid "PREVIEW" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:44 +#: selfdrive/ui/widgets/prime.py:44 #, python-format msgid "PRIME FEATURES:" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:48 +#: selfdrive/ui/layouts/settings/device.py:48 #, python-format msgid "Pair Device" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:19 +#: selfdrive/ui/widgets/setup.py:19 #, python-format msgid "Pair device" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:103 +#: selfdrive/ui/widgets/pairing_dialog.py:103 #, python-format msgid "Pair your device to your comma account" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:48 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:24 +#: selfdrive/ui/widgets/setup.py:48 selfdrive/ui/layouts/settings/device.py:24 #, python-format msgid "" "Pair your device with comma connect (connect.comma.ai) and claim your comma " "prime offer." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:91 +#: selfdrive/ui/widgets/setup.py:91 #, python-format msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:187 +#: selfdrive/ui/layouts/settings/device.py:55 +#: selfdrive/ui/layouts/settings/device.py:187 #, python-format msgid "Power Off" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:144 #, python-format msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:135 +#: system/ui/widgets/network.py:135 #, python-format msgid "Prevent large data uploads when on a metered cellular connection" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:25 +#: selfdrive/ui/layouts/settings/device.py:25 msgid "" "Preview the driver facing camera to ensure that driver monitoring has good " "visibility. (vehicle must be off)" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:161 +#: selfdrive/ui/widgets/pairing_dialog.py:161 #, python-format msgid "QR Code Error" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:31 +#: selfdrive/ui/widgets/ssh_key.py:31 msgid "REMOVE" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:51 +#: selfdrive/ui/layouts/settings/device.py:51 #, python-format msgid "RESET" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:65 +#: selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "REVIEW" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:175 +#: selfdrive/ui/layouts/settings/device.py:55 +#: selfdrive/ui/layouts/settings/device.py:175 #, python-format msgid "Reboot" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:66 +#: selfdrive/ui/onroad/alert_renderer.py:66 #, python-format msgid "Reboot Device" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 +#: selfdrive/ui/widgets/offroad_alerts.py:112 #, python-format msgid "Reboot and Update" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:27 +#: selfdrive/ui/layouts/settings/toggles.py:27 msgid "" "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)." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:76 +#: selfdrive/ui/layouts/settings/toggles.py:76 #, python-format msgid "Record and Upload Driver Camera" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:82 +#: selfdrive/ui/layouts/settings/toggles.py:82 #, python-format msgid "Record and Upload Microphone Audio" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +#: selfdrive/ui/layouts/settings/toggles.py:33 msgid "" "Record and store microphone audio while driving. The audio will be included " "in the dashcam video in comma connect." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:67 +#: selfdrive/ui/layouts/settings/device.py:67 #, python-format msgid "Regulatory" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#: selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Relaxed" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#: selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote access" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#: selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote snapshots" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:123 +#: selfdrive/ui/widgets/ssh_key.py:123 #, python-format msgid "Request timed out" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:119 +#: selfdrive/ui/layouts/settings/device.py:119 #, python-format msgid "Reset" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:51 +#: selfdrive/ui/layouts/settings/device.py:51 #, python-format msgid "Reset Calibration" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:65 +#: selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "Review Training Guide" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:27 +#: selfdrive/ui/layouts/settings/device.py:27 msgid "Review the rules, features, and limitations of openpilot" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:61 +#: selfdrive/ui/layouts/settings/software.py:61 #, python-format msgid "SELECT" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:53 +#: selfdrive/ui/layouts/settings/developer.py:53 #, python-format msgid "SSH Keys" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:310 +#: system/ui/widgets/network.py:310 #, python-format msgid "Scanning Wi-Fi networks..." msgstr "" -#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/option_dialog.py:36 #, python-format msgid "Select" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:183 +#: selfdrive/ui/layouts/settings/software.py:183 #, python-format msgid "Select a branch" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:91 +#: selfdrive/ui/layouts/settings/device.py:91 #, python-format msgid "Select a language" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:60 +#: selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "Serial" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 +#: selfdrive/ui/widgets/offroad_alerts.py:106 #, python-format msgid "Snooze Update" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:65 +#: selfdrive/ui/layouts/settings/settings.py:65 msgid "Software" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#: selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Standard" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:22 +#: selfdrive/ui/layouts/settings/toggles.py:22 msgid "" "Standard is recommended. In aggressive mode, openpilot will follow lead cars " "closer and be more aggressive with the gas and brake. In relaxed mode " @@ -817,219 +802,215 @@ msgid "" "cycle through these personalities with your steering wheel distance button." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:59 -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:65 +#: selfdrive/ui/onroad/alert_renderer.py:59 +#: selfdrive/ui/onroad/alert_renderer.py:65 #, python-format msgid "System Unresponsive" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:58 +#: selfdrive/ui/onroad/alert_renderer.py:58 #, python-format msgid "TAKE CONTROL IMMEDIATELY" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:71 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:125 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:127 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:129 +#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 +#: selfdrive/ui/layouts/sidebar.py:127 selfdrive/ui/layouts/sidebar.py:129 msgid "TEMP" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:61 +#: selfdrive/ui/layouts/settings/software.py:61 #, python-format msgid "Target Branch" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:124 +#: system/ui/widgets/network.py:124 #, python-format msgid "Tethering Password" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:64 +#: selfdrive/ui/layouts/settings/settings.py:64 msgid "Toggles" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:72 +#: selfdrive/ui/layouts/settings/software.py:72 #, python-format msgid "UNINSTALL" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/home.py:155 +#: selfdrive/ui/layouts/home.py:155 #, python-format msgid "UPDATE" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:72 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:163 +#: selfdrive/ui/layouts/settings/software.py:72 +#: selfdrive/ui/layouts/settings/software.py:163 #, python-format msgid "Uninstall" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:117 +#: selfdrive/ui/layouts/sidebar.py:117 msgid "Unknown" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:48 +#: selfdrive/ui/layouts/settings/software.py:48 #, python-format msgid "Updates are only downloaded while the car is off." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:33 +#: selfdrive/ui/widgets/prime.py:33 #, python-format msgid "Upgrade Now" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +#: selfdrive/ui/layouts/settings/toggles.py:31 msgid "" "Upload data from the driver facing camera and help improve the driver " "monitoring algorithm." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:88 +#: selfdrive/ui/layouts/settings/toggles.py:88 #, python-format msgid "Use Metric System" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:17 +#: selfdrive/ui/layouts/settings/toggles.py:17 msgid "" "Use the openpilot system for adaptive cruise control and lane keep driver " "assistance. Your attention is required at all times to use this feature." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:72 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:144 msgid "VEHICLE" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:67 +#: selfdrive/ui/layouts/settings/device.py:67 #, python-format msgid "VIEW" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:52 +#: selfdrive/ui/onroad/alert_renderer.py:52 #, python-format msgid "Waiting to start" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:19 +#: selfdrive/ui/layouts/settings/developer.py:19 msgid "" "Warning: This grants SSH access to all public keys in your GitHub settings. " "Never enter a GitHub username other than your own. A comma employee will " "NEVER ask you to add their GitHub username." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:111 +#: selfdrive/ui/layouts/onboarding.py:111 #, python-format msgid "Welcome to openpilot" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:20 +#: selfdrive/ui/layouts/settings/toggles.py:20 msgid "When enabled, pressing the accelerator pedal will disengage openpilot." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:44 +#: selfdrive/ui/layouts/sidebar.py:44 msgid "Wi-Fi" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:144 #, python-format msgid "Wi-Fi Network Metered" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:314 #, python-format msgid "Wrong password" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:145 +#: selfdrive/ui/layouts/onboarding.py:145 #, python-format msgid "You must accept the Terms and Conditions in order to use openpilot." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:112 +#: selfdrive/ui/layouts/onboarding.py:112 #, python-format msgid "" "You must accept the Terms and Conditions to use openpilot. Read the latest " "terms at https://comma.ai/terms before continuing." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:34 +#: selfdrive/ui/onroad/driver_camera_dialog.py:34 #, python-format msgid "camera starting" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:63 +#: selfdrive/ui/widgets/prime.py:63 #, python-format msgid "comma prime" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:142 #, python-format msgid "default" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#: selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid "down" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:106 +#: selfdrive/ui/layouts/settings/software.py:106 #, python-format msgid "failed to check for update" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:237 -#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 #, python-format msgid "for \"{}\"" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:177 +#: selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "km/h" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:204 #, python-format msgid "leave blank for automatic configuration" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:134 +#: selfdrive/ui/layouts/settings/device.py:134 #, python-format msgid "left" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:142 #, python-format msgid "metered" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:177 +#: selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "mph" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:20 +#: selfdrive/ui/layouts/settings/software.py:20 #, python-format msgid "never" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:31 +#: selfdrive/ui/layouts/settings/software.py:31 #, python-format msgid "now" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:71 +#: selfdrive/ui/layouts/settings/developer.py:71 #, python-format msgid "openpilot Longitudinal Control (Alpha)" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:51 +#: selfdrive/ui/onroad/alert_renderer.py:51 #, python-format msgid "openpilot Unavailable" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:158 +#: selfdrive/ui/layouts/settings/toggles.py:158 #, python-format msgid "" "openpilot defaults to driving in chill mode. Experimental mode enables alpha-" @@ -1045,14 +1026,14 @@ msgid "" "corner." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:165 +#: selfdrive/ui/layouts/settings/device.py:165 #, python-format msgid "" "openpilot is continuously calibrating, resetting is rarely required. " "Resetting calibration will restart openpilot if the car is powered on." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:20 +#: selfdrive/ui/layouts/settings/firehose.py:20 msgid "" "openpilot learns to drive by watching humans, like you, drive.\n" "\n" @@ -1061,88 +1042,88 @@ msgid "" "better Experimental Mode." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:183 +#: selfdrive/ui/layouts/settings/toggles.py:183 #, python-format msgid "openpilot longitudinal control may come in a future update." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:26 +#: selfdrive/ui/layouts/settings/device.py:26 msgid "" "openpilot requires the device to be mounted within 4° left or right and " "within 5° up or 9° down." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:134 +#: selfdrive/ui/layouts/settings/device.py:134 #, python-format msgid "right" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:142 #, python-format msgid "unmetered" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#: selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid "up" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:117 +#: selfdrive/ui/layouts/settings/software.py:117 #, python-format msgid "up to date, last checked never" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:115 +#: selfdrive/ui/layouts/settings/software.py:115 #, python-format msgid "up to date, last checked {}" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:109 +#: selfdrive/ui/layouts/settings/software.py:109 #, python-format msgid "update available" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/home.py:169 +#: selfdrive/ui/layouts/home.py:169 #, python-format msgid "{} ALERT" msgid_plural "{} ALERTS" msgstr[0] "" msgstr[1] "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:40 +#: selfdrive/ui/layouts/settings/software.py:40 #, python-format msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "" msgstr[1] "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:37 +#: selfdrive/ui/layouts/settings/software.py:37 #, python-format msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "" msgstr[1] "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:34 +#: selfdrive/ui/layouts/settings/software.py:34 #, python-format msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "" msgstr[1] "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:111 +#: selfdrive/ui/layouts/settings/firehose.py:111 #, python-format msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." msgstr[0] "" msgstr[1] "" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:62 +#: selfdrive/ui/widgets/prime.py:62 #, python-format msgid "✓ SUBSCRIBED" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:22 +#: selfdrive/ui/widgets/setup.py:22 #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "" diff --git a/selfdrive/ui/translations/app_tr.po b/selfdrive/ui/translations/app_tr.po index 022621f57..10191234a 100644 --- a/selfdrive/ui/translations/app_tr.po +++ b/selfdrive/ui/translations/app_tr.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-22 18:57-0700\n" +"POT-Creation-Date: 2025-10-23 00:50-0700\n" "PO-Revision-Date: 2025-10-20 18:19-0700\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" @@ -17,48 +17,48 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:160 +#: selfdrive/ui/layouts/settings/device.py:160 #, python-format msgid " Steering torque response calibration is complete." msgstr " Direksiyon tork tepkisi kalibrasyonu tamamlandı." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:158 +#: selfdrive/ui/layouts/settings/device.py:158 #, python-format msgid " Steering torque response calibration is {}% complete." msgstr " Direksiyon tork tepkisi kalibrasyonu {}% tamamlandı." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#: selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr " Cihazınız {:.1f}° {} ve {:.1f}° {} yönünde konumlandırılmış." -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:43 +#: selfdrive/ui/layouts/sidebar.py:43 msgid "--" msgstr "--" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#: selfdrive/ui/widgets/prime.py:47 #, python-format msgid "1 year of drive storage" msgstr "1 yıl sürüş depolaması" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#: selfdrive/ui/widgets/prime.py:47 #, python-format msgid "24/7 LTE connectivity" msgstr "7/24 LTE bağlantısı" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:46 +#: selfdrive/ui/layouts/sidebar.py:46 msgid "2G" msgstr "2G" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:47 +#: selfdrive/ui/layouts/sidebar.py:47 msgid "3G" msgstr "3G" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:49 +#: selfdrive/ui/layouts/sidebar.py:49 msgid "5G" msgstr "5G" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:23 +#: selfdrive/ui/layouts/settings/developer.py:23 msgid "" "WARNING: openpilot longitudinal control is in alpha for this car and will " "disable Automatic Emergency Braking (AEB).

On this car, openpilot " @@ -75,22 +75,22 @@ msgstr "" "etkinleştirin. openpilot boylamsal kontrol alfayı etkinleştirirken Deneysel " "modu etkinleştirmeniz önerilir." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:148 +#: selfdrive/ui/layouts/settings/device.py:148 #, python-format msgid "

Steering lag calibration is complete." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:146 +#: selfdrive/ui/layouts/settings/device.py:146 #, python-format msgid "

Steering lag calibration is {}% complete." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:138 +#: selfdrive/ui/layouts/settings/firehose.py:138 #, python-format msgid "ACTIVE" msgstr "AKTİF" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:15 +#: selfdrive/ui/layouts/settings/developer.py:15 msgid "" "ADB (Android Debug Bridge) allows connecting to your device over USB or over " "the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." @@ -99,42 +99,41 @@ msgstr "" "sağlar. Daha fazla bilgi için https://docs.comma.ai/how-to/connect-to-comma " "adresine bakın." -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:30 +#: selfdrive/ui/widgets/ssh_key.py:30 msgid "ADD" msgstr "EKLE" -#: /home/batman/openpilot/system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:139 #, python-format msgid "APN Setting" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 +#: selfdrive/ui/widgets/offroad_alerts.py:109 #, python-format msgid "Acknowledge Excessive Actuation" msgstr "Aşırı Müdahaleyi Onayla" -#: /home/batman/openpilot/system/ui/widgets/network.py:74 -#: /home/batman/openpilot/system/ui/widgets/network.py:95 +#: system/ui/widgets/network.py:74 system/ui/widgets/network.py:95 #, python-format msgid "Advanced" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#: selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Aggressive" msgstr "Agresif" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:116 +#: selfdrive/ui/layouts/onboarding.py:116 #, python-format msgid "Agree" msgstr "Kabul et" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:70 +#: selfdrive/ui/layouts/settings/toggles.py:70 #, python-format msgid "Always-On Driver Monitoring" msgstr "Sürekli Sürücü İzleme" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:186 +#: selfdrive/ui/layouts/settings/toggles.py:186 #, python-format msgid "" "An alpha version of openpilot longitudinal control can be tested, along with " @@ -143,241 +142,235 @@ msgstr "" "openpilot boylamsal kontrolünün alfa sürümü, Deneysel mod ile birlikte, " "yayın dışı dallarda test edilebilir." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:187 +#: selfdrive/ui/layouts/settings/device.py:187 #, python-format msgid "Are you sure you want to power off?" msgstr "Kapatmak istediğinizden emin misiniz?" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:175 +#: selfdrive/ui/layouts/settings/device.py:175 #, python-format msgid "Are you sure you want to reboot?" msgstr "Yeniden başlatmak istediğinizden emin misiniz?" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:119 +#: selfdrive/ui/layouts/settings/device.py:119 #, python-format msgid "Are you sure you want to reset calibration?" msgstr "Kalibrasyonu sıfırlamak istediğinizden emin misiniz?" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:163 +#: selfdrive/ui/layouts/settings/software.py:163 #, python-format msgid "Are you sure you want to uninstall?" msgstr "Kaldırmak istediğinizden emin misiniz?" -#: /home/batman/openpilot/system/ui/widgets/network.py:99 -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:147 +#: system/ui/widgets/network.py:99 selfdrive/ui/layouts/onboarding.py:147 #, python-format msgid "Back" msgstr "Geri" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:38 +#: selfdrive/ui/widgets/prime.py:38 #, python-format msgid "Become a comma prime member at connect.comma.ai" msgstr "connect.comma.ai adresinde comma prime üyesi olun" -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:130 +#: selfdrive/ui/widgets/pairing_dialog.py:130 #, python-format msgid "Bookmark connect.comma.ai to your home screen to use it like an app" msgstr "" "connect.comma.ai'yi ana ekranınıza ekleyerek bir uygulama gibi kullanın" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:68 +#: selfdrive/ui/layouts/settings/device.py:68 #, python-format msgid "CHANGE" msgstr "DEĞİŞTİR" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:50 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:107 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:118 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:147 +#: selfdrive/ui/layouts/settings/software.py:50 +#: selfdrive/ui/layouts/settings/software.py:107 +#: selfdrive/ui/layouts/settings/software.py:118 +#: selfdrive/ui/layouts/settings/software.py:147 #, python-format msgid "CHECK" msgstr "KONTROL ET" -#: /home/batman/openpilot/selfdrive/ui/widgets/exp_mode_button.py:50 +#: selfdrive/ui/widgets/exp_mode_button.py:50 #, python-format msgid "CHILL MODE ON" msgstr "CHILL MODU AÇIK" -#: /home/batman/openpilot/system/ui/widgets/network.py:155 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:73 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:134 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:136 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:138 +#: system/ui/widgets/network.py:155 selfdrive/ui/layouts/sidebar.py:73 +#: selfdrive/ui/layouts/sidebar.py:134 selfdrive/ui/layouts/sidebar.py:136 +#: selfdrive/ui/layouts/sidebar.py:138 #, python-format msgid "CONNECT" msgstr "BAĞLAN" -#: /home/batman/openpilot/system/ui/widgets/network.py:369 +#: system/ui/widgets/network.py:369 #, python-format msgid "CONNECTING..." msgstr "BAĞLAN" -#: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:23 -#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:35 -#: /home/batman/openpilot/system/ui/widgets/keyboard.py:81 -#: /home/batman/openpilot/system/ui/widgets/network.py:318 +#: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35 +#: system/ui/widgets/keyboard.py:81 system/ui/widgets/network.py:318 #, python-format msgid "Cancel" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:134 +#: system/ui/widgets/network.py:134 #, python-format msgid "Cellular Metered" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:68 +#: selfdrive/ui/layouts/settings/device.py:68 #, python-format msgid "Change Language" msgstr "Dili Değiştir" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:125 +#: selfdrive/ui/layouts/settings/toggles.py:125 #, python-format msgid "Changing this setting will restart openpilot if the car is powered on." msgstr "" " Bu ayarı değiştirmek, araç çalışıyorsa openpilot'u yeniden başlatacaktır." -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:129 +#: selfdrive/ui/widgets/pairing_dialog.py:129 #, python-format msgid "Click \"add new device\" and scan the QR code on the right" msgstr "\"yeni cihaz ekle\"ye tıklayın ve sağdaki QR kodunu tarayın" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 +#: selfdrive/ui/widgets/offroad_alerts.py:104 #, python-format msgid "Close" msgstr "Kapat" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:49 +#: selfdrive/ui/layouts/settings/software.py:49 #, python-format msgid "Current Version" msgstr "Geçerli Sürüm" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:110 +#: selfdrive/ui/layouts/settings/software.py:110 #, python-format msgid "DOWNLOAD" msgstr "İNDİR" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:115 +#: selfdrive/ui/layouts/onboarding.py:115 #, python-format msgid "Decline" msgstr "Reddet" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:148 +#: selfdrive/ui/layouts/onboarding.py:148 #, python-format msgid "Decline, uninstall openpilot" msgstr "Reddet, openpilot'u kaldır" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:67 +#: selfdrive/ui/layouts/settings/settings.py:67 msgid "Developer" msgstr "Geliştirici" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:62 +#: selfdrive/ui/layouts/settings/settings.py:62 msgid "Device" msgstr "Cihaz" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:58 +#: selfdrive/ui/layouts/settings/toggles.py:58 #, python-format msgid "Disengage on Accelerator Pedal" msgstr "Gaz Pedalında Devreden Çık" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:184 +#: selfdrive/ui/layouts/settings/device.py:184 #, python-format msgid "Disengage to Power Off" msgstr "Kapatmak için Devreden Çıkın" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:172 +#: selfdrive/ui/layouts/settings/device.py:172 #, python-format msgid "Disengage to Reboot" msgstr "Yeniden Başlatmak için Devreden Çıkın" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:103 +#: selfdrive/ui/layouts/settings/device.py:103 #, python-format msgid "Disengage to Reset Calibration" msgstr "Kalibrasyonu Sıfırlamak için Devreden Çıkın" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:32 +#: selfdrive/ui/layouts/settings/toggles.py:32 msgid "Display speed in km/h instead of mph." msgstr "Hızı mph yerine km/h olarak göster." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:59 +#: selfdrive/ui/layouts/settings/device.py:59 #, python-format msgid "Dongle ID" msgstr "Dongle ID" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:50 +#: selfdrive/ui/layouts/settings/software.py:50 #, python-format msgid "Download" msgstr "İndir" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:62 +#: selfdrive/ui/layouts/settings/device.py:62 #, python-format msgid "Driver Camera" msgstr "Sürücü Kamerası" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:96 +#: selfdrive/ui/layouts/settings/toggles.py:96 #, python-format msgid "Driving Personality" msgstr "Sürüş Kişiliği" -#: /home/batman/openpilot/system/ui/widgets/network.py:123 -#: /home/batman/openpilot/system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:123 system/ui/widgets/network.py:139 #, python-format msgid "EDIT" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:138 +#: selfdrive/ui/layouts/sidebar.py:138 msgid "ERROR" msgstr "HATA" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:45 +#: selfdrive/ui/layouts/sidebar.py:45 msgid "ETH" msgstr "ETH" -#: /home/batman/openpilot/selfdrive/ui/widgets/exp_mode_button.py:50 +#: selfdrive/ui/widgets/exp_mode_button.py:50 #, python-format msgid "EXPERIMENTAL MODE ON" msgstr "DENEYSEL MOD AÇIK" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:166 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:228 +#: selfdrive/ui/layouts/settings/developer.py:166 +#: selfdrive/ui/layouts/settings/toggles.py:228 #, python-format msgid "Enable" msgstr "Etkinleştir" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:39 +#: selfdrive/ui/layouts/settings/developer.py:39 #, python-format msgid "Enable ADB" msgstr "ADB'yi Etkinleştir" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:64 +#: selfdrive/ui/layouts/settings/toggles.py:64 #, python-format msgid "Enable Lane Departure Warnings" msgstr "Şerit Terk Uyarılarını Etkinleştir" -#: /home/batman/openpilot/system/ui/widgets/network.py:129 +#: system/ui/widgets/network.py:129 #, python-format msgid "Enable Roaming" msgstr "openpilot'u etkinleştir" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:48 +#: selfdrive/ui/layouts/settings/developer.py:48 #, python-format msgid "Enable SSH" msgstr "SSH'yi Etkinleştir" -#: /home/batman/openpilot/system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:120 #, python-format msgid "Enable Tethering" msgstr "Şerit Terk Uyarılarını Etkinleştir" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:30 +#: selfdrive/ui/layouts/settings/toggles.py:30 msgid "Enable driver monitoring even when openpilot is not engaged." msgstr "openpilot devrede değilken bile sürücü izlemesini etkinleştir." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:46 +#: selfdrive/ui/layouts/settings/toggles.py:46 #, python-format msgid "Enable openpilot" msgstr "openpilot'u etkinleştir" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:189 +#: selfdrive/ui/layouts/settings/toggles.py:189 #, python-format msgid "" "Enable the openpilot longitudinal control (alpha) toggle to allow " @@ -385,44 +378,42 @@ msgid "" msgstr "" "Deneysel modu etkinleştirmek için openpilot boylamsal kontrolünü (alfa) açın." -#: /home/batman/openpilot/system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:204 #, python-format msgid "Enter APN" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:241 +#: system/ui/widgets/network.py:241 #, python-format msgid "Enter SSID" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:254 +#: system/ui/widgets/network.py:254 #, python-format msgid "Enter new tethering password" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:237 -#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 #, python-format msgid "Enter password" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:89 +#: selfdrive/ui/widgets/ssh_key.py:89 #, python-format msgid "Enter your GitHub username" msgstr "GitHub kullanıcı adınızı girin" -#: /home/batman/openpilot/system/ui/widgets/list_view.py:123 -#: /home/batman/openpilot/system/ui/widgets/list_view.py:160 +#: system/ui/widgets/list_view.py:123 system/ui/widgets/list_view.py:160 #, python-format msgid "Error" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:52 +#: selfdrive/ui/layouts/settings/toggles.py:52 #, python-format msgid "Experimental Mode" msgstr "Deneysel Mod" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:181 +#: selfdrive/ui/layouts/settings/toggles.py:181 #, python-format msgid "" "Experimental mode is currently unavailable on this car since the car's stock " @@ -431,25 +422,25 @@ msgstr "" "Bu araçta boylamsal kontrol için stok ACC kullanıldığından şu anda Deneysel " "mod kullanılamıyor." -#: /home/batman/openpilot/system/ui/widgets/network.py:373 +#: system/ui/widgets/network.py:373 #, python-format msgid "FORGETTING..." msgstr "" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:44 +#: selfdrive/ui/widgets/setup.py:44 #, python-format msgid "Finish Setup" msgstr "Kurulumu Bitir" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:66 +#: selfdrive/ui/layouts/settings/settings.py:66 msgid "Firehose" msgstr "Firehose" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:18 +#: selfdrive/ui/layouts/settings/firehose.py:18 msgid "Firehose Mode" msgstr "Firehose Modu" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:25 +#: selfdrive/ui/layouts/settings/firehose.py:25 msgid "" "For maximum effectiveness, bring your device inside and connect to a good " "USB-C adapter and Wi-Fi weekly.\n" @@ -493,81 +484,79 @@ msgstr "" "Hangi yazılımı çalıştırdığım önemli mi? Evet, yalnızca upstream openpilot " "(ve bazı fork'lar) eğitim için kullanılabilir." -#: /home/batman/openpilot/system/ui/widgets/network.py:318 -#: /home/batman/openpilot/system/ui/widgets/network.py:451 +#: system/ui/widgets/network.py:318 system/ui/widgets/network.py:451 #, python-format msgid "Forget" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:319 +#: system/ui/widgets/network.py:319 #, python-format msgid "Forget Wi-Fi Network \"{}\"?" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:71 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 msgid "GOOD" msgstr "İYİ" -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:128 +#: selfdrive/ui/widgets/pairing_dialog.py:128 #, python-format msgid "Go to https://connect.comma.ai on your phone" msgstr "Telefonunuzda https://connect.comma.ai adresine gidin" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:129 +#: selfdrive/ui/layouts/sidebar.py:129 msgid "HIGH" msgstr "YÜKSEK" -#: /home/batman/openpilot/system/ui/widgets/network.py:155 +#: system/ui/widgets/network.py:155 #, python-format msgid "Hidden Network" msgstr "Ağ" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:140 +#: selfdrive/ui/layouts/settings/firehose.py:140 #, python-format msgid "INACTIVE: connect to an unmetered network" msgstr "PASİF: sınırsız bir ağa bağlanın" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:53 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:136 +#: selfdrive/ui/layouts/settings/software.py:53 +#: selfdrive/ui/layouts/settings/software.py:136 #, python-format msgid "INSTALL" msgstr "YÜKLE" -#: /home/batman/openpilot/system/ui/widgets/network.py:150 +#: system/ui/widgets/network.py:150 #, python-format msgid "IP Address" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:53 +#: selfdrive/ui/layouts/settings/software.py:53 #, python-format msgid "Install Update" msgstr "Güncellemeyi Yükle" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:56 +#: selfdrive/ui/layouts/settings/developer.py:56 #, python-format msgid "Joystick Debug Mode" msgstr "Joystick Hata Ayıklama Modu" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:29 +#: selfdrive/ui/widgets/ssh_key.py:29 msgid "LOADING" msgstr "YÜKLENİYOR" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:48 +#: selfdrive/ui/layouts/sidebar.py:48 msgid "LTE" msgstr "LTE" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:64 +#: selfdrive/ui/layouts/settings/developer.py:64 #, python-format msgid "Longitudinal Maneuver Mode" msgstr "Boylamsal Manevra Modu" -#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:148 +#: selfdrive/ui/onroad/hud_renderer.py:148 #, python-format msgid "MAX" msgstr "MAKS" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:75 +#: selfdrive/ui/widgets/setup.py:75 #, python-format msgid "" "Maximize your training data uploads to improve openpilot's driving models." @@ -575,94 +564,90 @@ msgstr "" "openpilot'un sürüş modellerini iyileştirmek için eğitim veri yüklemelerinizi " "en üst düzeye çıkarın." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:59 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:60 +#: selfdrive/ui/layouts/settings/device.py:59 +#: selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "N/A" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:142 +#: selfdrive/ui/layouts/sidebar.py:142 msgid "NO" msgstr "HAYIR" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:63 +#: selfdrive/ui/layouts/settings/settings.py:63 msgid "Network" msgstr "Ağ" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:114 +#: selfdrive/ui/widgets/ssh_key.py:114 #, python-format msgid "No SSH keys found" msgstr "SSH anahtarı bulunamadı" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:126 +#: selfdrive/ui/widgets/ssh_key.py:126 #, python-format msgid "No SSH keys found for user '{}'" msgstr "'{username}' için SSH anahtarı bulunamadı" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:320 +#: selfdrive/ui/widgets/offroad_alerts.py:320 #, python-format msgid "No release notes available." msgstr "Sürüm notu mevcut değil." -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:73 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: selfdrive/ui/layouts/sidebar.py:73 selfdrive/ui/layouts/sidebar.py:134 msgid "OFFLINE" msgstr "ÇEVRİMDIŞI" -#: /home/batman/openpilot/system/ui/widgets/html_render.py:263 -#: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:93 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: system/ui/widgets/html_render.py:263 system/ui/widgets/confirm_dialog.py:93 +#: selfdrive/ui/layouts/sidebar.py:127 #, python-format msgid "OK" msgstr "OK" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:72 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:136 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:136 +#: selfdrive/ui/layouts/sidebar.py:144 msgid "ONLINE" msgstr "ÇEVRİMİÇİ" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:20 +#: selfdrive/ui/widgets/setup.py:20 #, python-format msgid "Open" msgstr "Aç" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:48 +#: selfdrive/ui/layouts/settings/device.py:48 #, python-format msgid "PAIR" msgstr "EŞLE" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:142 +#: selfdrive/ui/layouts/sidebar.py:142 msgid "PANDA" msgstr "PANDA" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:62 +#: selfdrive/ui/layouts/settings/device.py:62 #, python-format msgid "PREVIEW" msgstr "ÖNİZLEME" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:44 +#: selfdrive/ui/widgets/prime.py:44 #, python-format msgid "PRIME FEATURES:" msgstr "PRIME ÖZELLİKLERİ:" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:48 +#: selfdrive/ui/layouts/settings/device.py:48 #, python-format msgid "Pair Device" msgstr "Cihazı Eşle" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:19 +#: selfdrive/ui/widgets/setup.py:19 #, python-format msgid "Pair device" msgstr "Cihazı eşle" -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:103 +#: selfdrive/ui/widgets/pairing_dialog.py:103 #, python-format msgid "Pair your device to your comma account" msgstr "Cihazınızı comma hesabınızla eşleştirin" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:48 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:24 +#: selfdrive/ui/widgets/setup.py:48 selfdrive/ui/layouts/settings/device.py:24 #, python-format msgid "" "Pair your device with comma connect (connect.comma.ai) and claim your comma " @@ -671,28 +656,28 @@ msgstr "" "Cihazınızı comma connect (connect.comma.ai) ile eşleştirin ve comma prime " "teklifinizi alın." -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:91 +#: selfdrive/ui/widgets/setup.py:91 #, python-format msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "İlk eşleştirmeyi tamamlamak için lütfen Wi‑Fi'a bağlanın" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:187 +#: selfdrive/ui/layouts/settings/device.py:55 +#: selfdrive/ui/layouts/settings/device.py:187 #, python-format msgid "Power Off" msgstr "Kapat" -#: /home/batman/openpilot/system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:144 #, python-format msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:135 +#: system/ui/widgets/network.py:135 #, python-format msgid "Prevent large data uploads when on a metered cellular connection" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:25 +#: selfdrive/ui/layouts/settings/device.py:25 msgid "" "Preview the driver facing camera to ensure that driver monitoring has good " "visibility. (vehicle must be off)" @@ -700,42 +685,42 @@ msgstr "" "Sürücü izleme görünürlüğünün iyi olduğundan emin olmak için sürücüye bakan " "kamerayı önizleyin. (araç kapalı olmalıdır)" -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:161 +#: selfdrive/ui/widgets/pairing_dialog.py:161 #, python-format msgid "QR Code Error" msgstr "QR Kod Hatası" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:31 +#: selfdrive/ui/widgets/ssh_key.py:31 msgid "REMOVE" msgstr "KALDIR" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:51 +#: selfdrive/ui/layouts/settings/device.py:51 #, python-format msgid "RESET" msgstr "SIFIRLA" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:65 +#: selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "REVIEW" msgstr "GÖZDEN GEÇİR" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:175 +#: selfdrive/ui/layouts/settings/device.py:55 +#: selfdrive/ui/layouts/settings/device.py:175 #, python-format msgid "Reboot" msgstr "Yeniden Başlat" -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:66 +#: selfdrive/ui/onroad/alert_renderer.py:66 #, python-format msgid "Reboot Device" msgstr "Cihazı Yeniden Başlat" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 +#: selfdrive/ui/widgets/offroad_alerts.py:112 #, python-format msgid "Reboot and Update" msgstr "Yeniden Başlat ve Güncelle" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:27 +#: selfdrive/ui/layouts/settings/toggles.py:27 msgid "" "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 " @@ -744,17 +729,17 @@ msgstr "" "Araç 31 mph (50 km/h) üzerindeyken sinyal verilmeden algılanan şerit " "çizgisini aştığınızda şeride geri dönmeniz için uyarılar alın." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:76 +#: selfdrive/ui/layouts/settings/toggles.py:76 #, python-format msgid "Record and Upload Driver Camera" msgstr "Sürücü Kamerasını Kaydet ve Yükle" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:82 +#: selfdrive/ui/layouts/settings/toggles.py:82 #, python-format msgid "Record and Upload Microphone Audio" msgstr "Mikrofon Sesini Kaydet ve Yükle" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +#: selfdrive/ui/layouts/settings/toggles.py:33 msgid "" "Record and store microphone audio while driving. The audio will be included " "in the dashcam video in comma connect." @@ -762,101 +747,101 @@ msgstr "" "Sürüş sırasında mikrofon sesini kaydedip saklayın. Ses, comma connect'teki " "ön kamera videosuna dahil edilecektir." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:67 +#: selfdrive/ui/layouts/settings/device.py:67 #, python-format msgid "Regulatory" msgstr "Mevzuat" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#: selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Relaxed" msgstr "Rahat" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#: selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote access" msgstr "Uzaktan erişim" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#: selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote snapshots" msgstr "Uzaktan anlık görüntüler" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:123 +#: selfdrive/ui/widgets/ssh_key.py:123 #, python-format msgid "Request timed out" msgstr "İstek zaman aşımına uğradı" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:119 +#: selfdrive/ui/layouts/settings/device.py:119 #, python-format msgid "Reset" msgstr "Sıfırla" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:51 +#: selfdrive/ui/layouts/settings/device.py:51 #, python-format msgid "Reset Calibration" msgstr "Kalibrasyonu Sıfırla" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:65 +#: selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "Review Training Guide" msgstr "Eğitim Kılavuzunu İncele" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:27 +#: selfdrive/ui/layouts/settings/device.py:27 msgid "Review the rules, features, and limitations of openpilot" msgstr "" "openpilot'un kurallarını, özelliklerini ve sınırlamalarını gözden geçirin" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:61 +#: selfdrive/ui/layouts/settings/software.py:61 #, python-format msgid "SELECT" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:53 +#: selfdrive/ui/layouts/settings/developer.py:53 #, python-format msgid "SSH Keys" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:310 +#: system/ui/widgets/network.py:310 #, python-format msgid "Scanning Wi-Fi networks..." msgstr "" -#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/option_dialog.py:36 #, python-format msgid "Select" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:183 +#: selfdrive/ui/layouts/settings/software.py:183 #, python-format msgid "Select a branch" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:91 +#: selfdrive/ui/layouts/settings/device.py:91 #, python-format msgid "Select a language" msgstr "Bir dil seçin" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:60 +#: selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "Serial" msgstr "Seri" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 +#: selfdrive/ui/widgets/offroad_alerts.py:106 #, python-format msgid "Snooze Update" msgstr "Güncellemeyi Ertele" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:65 +#: selfdrive/ui/layouts/settings/settings.py:65 msgid "Software" msgstr "Yazılım" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#: selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Standard" msgstr "Standart" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:22 +#: selfdrive/ui/layouts/settings/toggles.py:22 msgid "" "Standard is recommended. In aggressive mode, openpilot will follow lead cars " "closer and be more aggressive with the gas and brake. In relaxed mode " @@ -868,69 +853,67 @@ msgstr "" "araçlardan daha uzak durur. Desteklenen araçlarda bu kişilikler arasında " "direksiyon mesafe düğmesiyle geçiş yapabilirsiniz." -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:59 -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:65 +#: selfdrive/ui/onroad/alert_renderer.py:59 +#: selfdrive/ui/onroad/alert_renderer.py:65 #, python-format msgid "System Unresponsive" msgstr "Sistem Yanıt Vermiyor" -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:58 +#: selfdrive/ui/onroad/alert_renderer.py:58 #, python-format msgid "TAKE CONTROL IMMEDIATELY" msgstr "HEMEN KONTROLÜ DEVRALIN" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:71 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:125 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:127 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:129 +#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 +#: selfdrive/ui/layouts/sidebar.py:127 selfdrive/ui/layouts/sidebar.py:129 msgid "TEMP" msgstr "TEMP" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:61 +#: selfdrive/ui/layouts/settings/software.py:61 #, python-format msgid "Target Branch" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:124 +#: system/ui/widgets/network.py:124 #, python-format msgid "Tethering Password" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:64 +#: selfdrive/ui/layouts/settings/settings.py:64 msgid "Toggles" msgstr "Seçenekler" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:72 +#: selfdrive/ui/layouts/settings/software.py:72 #, python-format msgid "UNINSTALL" msgstr "KALDIR" -#: /home/batman/openpilot/selfdrive/ui/layouts/home.py:155 +#: selfdrive/ui/layouts/home.py:155 #, python-format msgid "UPDATE" msgstr "GÜNCELLE" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:72 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:163 +#: selfdrive/ui/layouts/settings/software.py:72 +#: selfdrive/ui/layouts/settings/software.py:163 #, python-format msgid "Uninstall" msgstr "Kaldır" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:117 +#: selfdrive/ui/layouts/sidebar.py:117 msgid "Unknown" msgstr "Bilinmiyor" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:48 +#: selfdrive/ui/layouts/settings/software.py:48 #, python-format msgid "Updates are only downloaded while the car is off." msgstr "Güncellemeler yalnızca araç kapalıyken indirilir." -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:33 +#: selfdrive/ui/widgets/prime.py:33 #, python-format msgid "Upgrade Now" msgstr "Şimdi Yükselt" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +#: selfdrive/ui/layouts/settings/toggles.py:31 msgid "" "Upload data from the driver facing camera and help improve the driver " "monitoring algorithm." @@ -938,12 +921,12 @@ msgstr "" "Sürücüye bakan kameradan veri yükleyin ve sürücü izleme algoritmasını " "geliştirmeye yardımcı olun." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:88 +#: selfdrive/ui/layouts/settings/toggles.py:88 #, python-format msgid "Use Metric System" msgstr "Metrik Sistemi Kullan" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:17 +#: selfdrive/ui/layouts/settings/toggles.py:17 msgid "" "Use the openpilot system for adaptive cruise control and lane keep driver " "assistance. Your attention is required at all times to use this feature." @@ -952,22 +935,21 @@ msgstr "" "sistemini kullanın. Bu özelliği kullanırken her zaman dikkatli olmanız " "gerekir." -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:72 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:144 msgid "VEHICLE" msgstr "ARAÇ" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:67 +#: selfdrive/ui/layouts/settings/device.py:67 #, python-format msgid "VIEW" msgstr "GÖRÜNTÜLE" -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:52 +#: selfdrive/ui/onroad/alert_renderer.py:52 #, python-format msgid "Waiting to start" msgstr "Başlatma bekleniyor" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:19 +#: selfdrive/ui/layouts/settings/developer.py:19 msgid "" "Warning: This grants SSH access to all public keys in your GitHub settings. " "Never enter a GitHub username other than your own. A comma employee will " @@ -977,36 +959,36 @@ msgstr "" "Kendi adınız dışında asla bir GitHub kullanıcı adı girmeyin. Bir comma " "çalışanı sizden asla GitHub kullanıcı adlarını eklemenizi İSTEMEZ." -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:111 +#: selfdrive/ui/layouts/onboarding.py:111 #, python-format msgid "Welcome to openpilot" msgstr "openpilot'a hoş geldiniz" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:20 +#: selfdrive/ui/layouts/settings/toggles.py:20 msgid "When enabled, pressing the accelerator pedal will disengage openpilot." msgstr "" "Etkinleştirildiğinde, gaz pedalına basmak openpilot'u devreden çıkarır." -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:44 +#: selfdrive/ui/layouts/sidebar.py:44 msgid "Wi-Fi" msgstr "Wi‑Fi" -#: /home/batman/openpilot/system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:144 #, python-format msgid "Wi-Fi Network Metered" msgstr "" -#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:314 #, python-format msgid "Wrong password" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:145 +#: selfdrive/ui/layouts/onboarding.py:145 #, python-format msgid "You must accept the Terms and Conditions in order to use openpilot." msgstr "openpilot'u kullanmak için Şartlar ve Koşulları kabul etmelisiniz." -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:112 +#: selfdrive/ui/layouts/onboarding.py:112 #, python-format msgid "" "You must accept the Terms and Conditions to use openpilot. Read the latest " @@ -1015,83 +997,82 @@ msgstr "" "openpilot'u kullanmak için Şartlar ve Koşulları kabul etmelisiniz. Devam " "etmeden önce en güncel şartları https://comma.ai/terms adresinde okuyun." -#: /home/batman/openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:34 +#: selfdrive/ui/onroad/driver_camera_dialog.py:34 #, python-format msgid "camera starting" msgstr "kamera başlatılıyor" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:63 +#: selfdrive/ui/widgets/prime.py:63 #, python-format msgid "comma prime" msgstr "comma prime" -#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:142 #, python-format msgid "default" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#: selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid "down" msgstr "aşağı" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:106 +#: selfdrive/ui/layouts/settings/software.py:106 #, python-format msgid "failed to check for update" msgstr "güncelleme kontrolü başarısız" -#: /home/batman/openpilot/system/ui/widgets/network.py:237 -#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 #, python-format msgid "for \"{}\"" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:177 +#: selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "km/h" msgstr "km/h" -#: /home/batman/openpilot/system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:204 #, python-format msgid "leave blank for automatic configuration" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:134 +#: selfdrive/ui/layouts/settings/device.py:134 #, python-format msgid "left" msgstr "sol" -#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:142 #, python-format msgid "metered" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:177 +#: selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "mph" msgstr "mph" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:20 +#: selfdrive/ui/layouts/settings/software.py:20 #, python-format msgid "never" msgstr "asla" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:31 +#: selfdrive/ui/layouts/settings/software.py:31 #, python-format msgid "now" msgstr "şimdi" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:71 +#: selfdrive/ui/layouts/settings/developer.py:71 #, python-format msgid "openpilot Longitudinal Control (Alpha)" msgstr "openpilot Boylamsal Kontrol (Alfa)" -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:51 +#: selfdrive/ui/onroad/alert_renderer.py:51 #, python-format msgid "openpilot Unavailable" msgstr "openpilot Kullanılamıyor" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:158 +#: selfdrive/ui/layouts/settings/toggles.py:158 #, python-format msgid "" "openpilot defaults to driving in chill mode. Experimental mode enables alpha-" @@ -1118,7 +1099,7 @@ msgstr "" "göstermek için yola bakan geniş açılı kameraya geçer. Deneysel mod logosu " "sağ üst köşede de gösterilecektir." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:165 +#: selfdrive/ui/layouts/settings/device.py:165 #, python-format msgid "" "openpilot is continuously calibrating, resetting is rarely required. " @@ -1126,7 +1107,7 @@ msgid "" msgstr "" " Bu ayarı değiştirmek, araç çalışıyorsa openpilot'u yeniden başlatacaktır." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:20 +#: selfdrive/ui/layouts/settings/firehose.py:20 msgid "" "openpilot learns to drive by watching humans, like you, drive.\n" "\n" @@ -1140,12 +1121,12 @@ msgstr "" "yüklemelerinizi en üst düzeye çıkarmanıza olanak tanır. Daha fazla veri, " "daha büyük modeller demektir; bu da daha iyi Deneysel Mod anlamına gelir." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:183 +#: selfdrive/ui/layouts/settings/toggles.py:183 #, python-format msgid "openpilot longitudinal control may come in a future update." msgstr "openpilot boylamsal kontrolü gelecekteki bir güncellemede gelebilir." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:26 +#: selfdrive/ui/layouts/settings/device.py:26 msgid "" "openpilot requires the device to be mounted within 4° left or right and " "within 5° up or 9° down." @@ -1153,77 +1134,77 @@ msgstr "" "openpilot, cihazın sağa/sola 4° ve yukarı 5° veya aşağı 9° içinde monte " "edilmesini gerektirir." -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:134 +#: selfdrive/ui/layouts/settings/device.py:134 #, python-format msgid "right" msgstr "sağ" -#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:142 #, python-format msgid "unmetered" msgstr "" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#: selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid "up" msgstr "yukarı" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:117 +#: selfdrive/ui/layouts/settings/software.py:117 #, python-format msgid "up to date, last checked never" msgstr "güncel, son kontrol asla" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:115 +#: selfdrive/ui/layouts/settings/software.py:115 #, python-format msgid "up to date, last checked {}" msgstr "güncel, son kontrol {}" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:109 +#: selfdrive/ui/layouts/settings/software.py:109 #, python-format msgid "update available" msgstr "güncelleme mevcut" -#: /home/batman/openpilot/selfdrive/ui/layouts/home.py:169 +#: selfdrive/ui/layouts/home.py:169 #, python-format msgid "{} ALERT" msgid_plural "{} ALERTS" msgstr[0] "{} UYARI" msgstr[1] "{} UYARILAR" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:40 +#: selfdrive/ui/layouts/settings/software.py:40 #, python-format msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "{} gün önce" msgstr[1] "{} gün önce" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:37 +#: selfdrive/ui/layouts/settings/software.py:37 #, python-format msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "{} saat önce" msgstr[1] "{} saat önce" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:34 +#: selfdrive/ui/layouts/settings/software.py:34 #, python-format msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "{} dakika önce" msgstr[1] "{} dakika önce" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:111 +#: selfdrive/ui/layouts/settings/firehose.py:111 #, python-format msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." msgstr[0] "{} segment sürüşünüz eğitim veri setinde." msgstr[1] "{} segment sürüşünüz eğitim veri setinde." -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:62 +#: selfdrive/ui/widgets/prime.py:62 #, python-format msgid "✓ SUBSCRIBED" msgstr "✓ ABONE" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:22 +#: selfdrive/ui/widgets/setup.py:22 #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "🔥 Firehose Modu 🔥" diff --git a/selfdrive/ui/translations/app_zh-CHS.po b/selfdrive/ui/translations/app_zh-CHS.po index d09add97d..16e436947 100644 --- a/selfdrive/ui/translations/app_zh-CHS.po +++ b/selfdrive/ui/translations/app_zh-CHS.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-22 19:09-0700\n" +"POT-Creation-Date: 2025-10-23 00:50-0700\n" "PO-Revision-Date: 2025-10-22 16:32-0700\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" @@ -17,48 +17,48 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:160 +#: selfdrive/ui/layouts/settings/device.py:160 #, python-format msgid " Steering torque response calibration is complete." msgstr " 转向扭矩响应校准完成。" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:158 +#: selfdrive/ui/layouts/settings/device.py:158 #, python-format msgid " Steering torque response calibration is {}% complete." msgstr " 转向扭矩响应校准已完成 {}%。" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#: selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr " 您的设备朝向 {:.1f}° {} 与 {:.1f}° {}。" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:43 +#: selfdrive/ui/layouts/sidebar.py:43 msgid "--" msgstr "--" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#: selfdrive/ui/widgets/prime.py:47 #, python-format msgid "1 year of drive storage" msgstr "1 年行驶数据存储" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#: selfdrive/ui/widgets/prime.py:47 #, python-format msgid "24/7 LTE connectivity" msgstr "全天候 LTE 连接" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:46 +#: selfdrive/ui/layouts/sidebar.py:46 msgid "2G" msgstr "2G" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:47 +#: selfdrive/ui/layouts/sidebar.py:47 msgid "3G" msgstr "3G" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:49 +#: selfdrive/ui/layouts/sidebar.py:49 msgid "5G" msgstr "5G" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:23 +#: selfdrive/ui/layouts/settings/developer.py:23 msgid "" "WARNING: openpilot longitudinal control is in alpha for this car and will " "disable Automatic Emergency Braking (AEB).

On this car, openpilot " @@ -73,22 +73,22 @@ msgstr "" "制。启用此选项可切换为 openpilot 纵向控制。建议同时启用实验模式。若车辆通电," "更改此设置将会重启 openpilot。" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:148 +#: selfdrive/ui/layouts/settings/device.py:148 #, python-format msgid "

Steering lag calibration is complete." msgstr "

转向延迟校准完成。" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:146 +#: selfdrive/ui/layouts/settings/device.py:146 #, python-format msgid "

Steering lag calibration is {}% complete." msgstr "

转向延迟校准已完成 {}%。" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:138 +#: selfdrive/ui/layouts/settings/firehose.py:138 #, python-format msgid "ACTIVE" msgstr "已启用" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:15 +#: selfdrive/ui/layouts/settings/developer.py:15 msgid "" "ADB (Android Debug Bridge) allows connecting to your device over USB or over " "the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." @@ -96,350 +96,341 @@ msgstr "" "ADB(Android 调试桥)可通过 USB 或网络连接到您的设备。详见 https://docs." "comma.ai/how-to/connect-to-comma。" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:30 +#: selfdrive/ui/widgets/ssh_key.py:30 msgid "ADD" msgstr "添加" -#: /home/batman/openpilot/system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:139 #, python-format msgid "APN Setting" msgstr "APN 设置" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 +#: selfdrive/ui/widgets/offroad_alerts.py:109 #, python-format msgid "Acknowledge Excessive Actuation" msgstr "确认过度作动" -#: /home/batman/openpilot/system/ui/widgets/network.py:74 -#: /home/batman/openpilot/system/ui/widgets/network.py:95 +#: system/ui/widgets/network.py:74 system/ui/widgets/network.py:95 #, python-format msgid "Advanced" msgstr "高级" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#: selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Aggressive" msgstr "激进" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:116 +#: selfdrive/ui/layouts/onboarding.py:116 #, python-format msgid "Agree" msgstr "同意" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:70 +#: selfdrive/ui/layouts/settings/toggles.py:70 #, python-format msgid "Always-On Driver Monitoring" msgstr "始终启用驾驶员监控" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:186 +#: selfdrive/ui/layouts/settings/toggles.py:186 #, python-format msgid "" "An alpha version of openpilot longitudinal control can be tested, along with " "Experimental mode, on non-release branches." msgstr "openpilot 纵向控制的 alpha 版本可在非发布分支搭配实验模式进行测试。" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:187 +#: selfdrive/ui/layouts/settings/device.py:187 #, python-format msgid "Are you sure you want to power off?" msgstr "确定要关机吗?" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:175 +#: selfdrive/ui/layouts/settings/device.py:175 #, python-format msgid "Are you sure you want to reboot?" msgstr "确定要重启吗?" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:119 +#: selfdrive/ui/layouts/settings/device.py:119 #, python-format msgid "Are you sure you want to reset calibration?" msgstr "确定要重置校准吗?" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:163 +#: selfdrive/ui/layouts/settings/software.py:163 #, python-format msgid "Are you sure you want to uninstall?" msgstr "确定要卸载吗?" -#: /home/batman/openpilot/system/ui/widgets/network.py:99 -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:147 +#: system/ui/widgets/network.py:99 selfdrive/ui/layouts/onboarding.py:147 #, python-format msgid "Back" msgstr "返回" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:38 +#: selfdrive/ui/widgets/prime.py:38 #, python-format msgid "Become a comma prime member at connect.comma.ai" msgstr "前往 connect.comma.ai 成为 comma prime 会员" -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:130 +#: selfdrive/ui/widgets/pairing_dialog.py:130 #, python-format msgid "Bookmark connect.comma.ai to your home screen to use it like an app" msgstr "将 connect.comma.ai 添加到主屏幕,像应用一样使用" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:68 +#: selfdrive/ui/layouts/settings/device.py:68 #, python-format msgid "CHANGE" msgstr "更改" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:50 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:107 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:118 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:147 +#: selfdrive/ui/layouts/settings/software.py:50 +#: selfdrive/ui/layouts/settings/software.py:107 +#: selfdrive/ui/layouts/settings/software.py:118 +#: selfdrive/ui/layouts/settings/software.py:147 #, python-format msgid "CHECK" msgstr "检查" -#: /home/batman/openpilot/selfdrive/ui/widgets/exp_mode_button.py:50 +#: selfdrive/ui/widgets/exp_mode_button.py:50 #, python-format msgid "CHILL MODE ON" msgstr "安稳模式已开启" -#: /home/batman/openpilot/system/ui/widgets/network.py:155 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:73 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:134 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:136 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:138 +#: system/ui/widgets/network.py:155 selfdrive/ui/layouts/sidebar.py:73 +#: selfdrive/ui/layouts/sidebar.py:134 selfdrive/ui/layouts/sidebar.py:136 +#: selfdrive/ui/layouts/sidebar.py:138 #, python-format msgid "CONNECT" msgstr "CONNECT" -#: /home/batman/openpilot/system/ui/widgets/network.py:369 +#: system/ui/widgets/network.py:369 #, python-format msgid "CONNECTING..." msgstr "CONNECTING..." -#: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:23 -#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:35 -#: /home/batman/openpilot/system/ui/widgets/keyboard.py:81 -#: /home/batman/openpilot/system/ui/widgets/network.py:318 +#: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35 +#: system/ui/widgets/keyboard.py:81 system/ui/widgets/network.py:318 #, python-format msgid "Cancel" msgstr "取消" -#: /home/batman/openpilot/system/ui/widgets/network.py:134 +#: system/ui/widgets/network.py:134 #, python-format msgid "Cellular Metered" msgstr "蜂窝计量" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:68 +#: selfdrive/ui/layouts/settings/device.py:68 #, python-format msgid "Change Language" msgstr "更改语言" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:125 +#: selfdrive/ui/layouts/settings/toggles.py:125 #, python-format msgid "Changing this setting will restart openpilot if the car is powered on." msgstr "若车辆通电,更改此设置将重启 openpilot。" -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:129 +#: selfdrive/ui/widgets/pairing_dialog.py:129 #, python-format msgid "Click \"add new device\" and scan the QR code on the right" msgstr "点击“添加新设备”,扫描右侧二维码" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 +#: selfdrive/ui/widgets/offroad_alerts.py:104 #, python-format msgid "Close" msgstr "关闭" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:49 +#: selfdrive/ui/layouts/settings/software.py:49 #, python-format msgid "Current Version" msgstr "当前版本" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:110 +#: selfdrive/ui/layouts/settings/software.py:110 #, python-format msgid "DOWNLOAD" msgstr "下载" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:115 +#: selfdrive/ui/layouts/onboarding.py:115 #, python-format msgid "Decline" msgstr "拒绝" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:148 +#: selfdrive/ui/layouts/onboarding.py:148 #, python-format msgid "Decline, uninstall openpilot" msgstr "拒绝并卸载 openpilot" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:67 +#: selfdrive/ui/layouts/settings/settings.py:67 msgid "Developer" msgstr "开发者" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:62 +#: selfdrive/ui/layouts/settings/settings.py:62 msgid "Device" msgstr "设备" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:58 +#: selfdrive/ui/layouts/settings/toggles.py:58 #, python-format msgid "Disengage on Accelerator Pedal" msgstr "踩下加速踏板时脱离" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:184 +#: selfdrive/ui/layouts/settings/device.py:184 #, python-format msgid "Disengage to Power Off" msgstr "脱离以关机" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:172 +#: selfdrive/ui/layouts/settings/device.py:172 #, python-format msgid "Disengage to Reboot" msgstr "脱离以重启" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:103 +#: selfdrive/ui/layouts/settings/device.py:103 #, python-format msgid "Disengage to Reset Calibration" msgstr "脱离以重置校准" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:32 +#: selfdrive/ui/layouts/settings/toggles.py:32 msgid "Display speed in km/h instead of mph." msgstr "以 km/h 显示速度(非 mph)。" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:59 +#: selfdrive/ui/layouts/settings/device.py:59 #, python-format msgid "Dongle ID" msgstr "Dongle ID" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:50 +#: selfdrive/ui/layouts/settings/software.py:50 #, python-format msgid "Download" msgstr "下载" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:62 +#: selfdrive/ui/layouts/settings/device.py:62 #, python-format msgid "Driver Camera" msgstr "车内摄像头" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:96 +#: selfdrive/ui/layouts/settings/toggles.py:96 #, python-format msgid "Driving Personality" msgstr "驾驶风格" -#: /home/batman/openpilot/system/ui/widgets/network.py:123 -#: /home/batman/openpilot/system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:123 system/ui/widgets/network.py:139 #, python-format msgid "EDIT" msgstr "编辑" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:138 +#: selfdrive/ui/layouts/sidebar.py:138 msgid "ERROR" msgstr "错误" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:45 +#: selfdrive/ui/layouts/sidebar.py:45 msgid "ETH" msgstr "ETH" -#: /home/batman/openpilot/selfdrive/ui/widgets/exp_mode_button.py:50 +#: selfdrive/ui/widgets/exp_mode_button.py:50 #, python-format msgid "EXPERIMENTAL MODE ON" msgstr "实验模式已开启" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:166 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:228 +#: selfdrive/ui/layouts/settings/developer.py:166 +#: selfdrive/ui/layouts/settings/toggles.py:228 #, python-format msgid "Enable" msgstr "启用" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:39 +#: selfdrive/ui/layouts/settings/developer.py:39 #, python-format msgid "Enable ADB" msgstr "启用 ADB" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:64 +#: selfdrive/ui/layouts/settings/toggles.py:64 #, python-format msgid "Enable Lane Departure Warnings" msgstr "启用车道偏离警示" -#: /home/batman/openpilot/system/ui/widgets/network.py:129 +#: system/ui/widgets/network.py:129 #, python-format msgid "Enable Roaming" msgstr "启用漫游" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:48 +#: selfdrive/ui/layouts/settings/developer.py:48 #, python-format msgid "Enable SSH" msgstr "启用 SSH" -#: /home/batman/openpilot/system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:120 #, python-format msgid "Enable Tethering" msgstr "启用网络共享" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:30 +#: selfdrive/ui/layouts/settings/toggles.py:30 msgid "Enable driver monitoring even when openpilot is not engaged." msgstr "即使未启用 openpilot 也启用驾驶员监控。" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:46 +#: selfdrive/ui/layouts/settings/toggles.py:46 #, python-format msgid "Enable openpilot" msgstr "启用 openpilot" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:189 +#: selfdrive/ui/layouts/settings/toggles.py:189 #, python-format msgid "" "Enable the openpilot longitudinal control (alpha) toggle to allow " "Experimental mode." msgstr "启用 openpilot 纵向控制(alpha)开关,以使用实验模式。" -#: /home/batman/openpilot/system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:204 #, python-format msgid "Enter APN" msgstr "输入 APN" -#: /home/batman/openpilot/system/ui/widgets/network.py:241 +#: system/ui/widgets/network.py:241 #, python-format msgid "Enter SSID" msgstr "输入 SSID" -#: /home/batman/openpilot/system/ui/widgets/network.py:254 +#: system/ui/widgets/network.py:254 #, python-format msgid "Enter new tethering password" msgstr "输入新的网络共享密码" -#: /home/batman/openpilot/system/ui/widgets/network.py:237 -#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 #, python-format msgid "Enter password" msgstr "输入密码" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:89 +#: selfdrive/ui/widgets/ssh_key.py:89 #, python-format msgid "Enter your GitHub username" msgstr "输入您的 GitHub 用户名" -#: /home/batman/openpilot/system/ui/widgets/list_view.py:123 -#: /home/batman/openpilot/system/ui/widgets/list_view.py:160 +#: system/ui/widgets/list_view.py:123 system/ui/widgets/list_view.py:160 #, python-format msgid "Error" msgstr "错误" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:52 +#: selfdrive/ui/layouts/settings/toggles.py:52 #, python-format msgid "Experimental Mode" msgstr "实验模式" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:181 +#: selfdrive/ui/layouts/settings/toggles.py:181 #, python-format msgid "" "Experimental mode is currently unavailable on this car since the car's stock " "ACC is used for longitudinal control." msgstr "此车型当前无法使用实验模式,因为纵向控制使用的是原厂 ACC。" -#: /home/batman/openpilot/system/ui/widgets/network.py:373 +#: system/ui/widgets/network.py:373 #, python-format msgid "FORGETTING..." msgstr "正在遗忘..." -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:44 +#: selfdrive/ui/widgets/setup.py:44 #, python-format msgid "Finish Setup" msgstr "完成设置" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:66 +#: selfdrive/ui/layouts/settings/settings.py:66 msgid "Firehose" msgstr "Firehose" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:18 +#: selfdrive/ui/layouts/settings/firehose.py:18 msgid "Firehose Mode" msgstr "Firehose 模式" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:25 +#: selfdrive/ui/layouts/settings/firehose.py:25 msgid "" "For maximum effectiveness, bring your device inside and connect to a good " "USB-C adapter and Wi-Fi weekly.\n" @@ -477,174 +468,168 @@ msgstr "" "\n" "我跑什么软件有区别吗?有,只有上游 openpilot(及特定分支)可用于训练。" -#: /home/batman/openpilot/system/ui/widgets/network.py:318 -#: /home/batman/openpilot/system/ui/widgets/network.py:451 +#: system/ui/widgets/network.py:318 system/ui/widgets/network.py:451 #, python-format msgid "Forget" msgstr "忘记" -#: /home/batman/openpilot/system/ui/widgets/network.py:319 +#: system/ui/widgets/network.py:319 #, python-format msgid "Forget Wi-Fi Network \"{}\"?" msgstr "要忘记 Wi‑Fi 网络“{}”吗?" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:71 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 msgid "GOOD" msgstr "良好" -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:128 +#: selfdrive/ui/widgets/pairing_dialog.py:128 #, python-format msgid "Go to https://connect.comma.ai on your phone" msgstr "在手机上前往 https://connect.comma.ai" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:129 +#: selfdrive/ui/layouts/sidebar.py:129 msgid "HIGH" msgstr "高" -#: /home/batman/openpilot/system/ui/widgets/network.py:155 +#: system/ui/widgets/network.py:155 #, python-format msgid "Hidden Network" msgstr "隐藏网络" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:140 +#: selfdrive/ui/layouts/settings/firehose.py:140 #, python-format msgid "INACTIVE: connect to an unmetered network" msgstr "未启用:请连接不限流量网络" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:53 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:136 +#: selfdrive/ui/layouts/settings/software.py:53 +#: selfdrive/ui/layouts/settings/software.py:136 #, python-format msgid "INSTALL" msgstr "安装" -#: /home/batman/openpilot/system/ui/widgets/network.py:150 +#: system/ui/widgets/network.py:150 #, python-format msgid "IP Address" msgstr "IP 地址" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:53 +#: selfdrive/ui/layouts/settings/software.py:53 #, python-format msgid "Install Update" msgstr "安装更新" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:56 +#: selfdrive/ui/layouts/settings/developer.py:56 #, python-format msgid "Joystick Debug Mode" msgstr "摇杆调试模式" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:29 +#: selfdrive/ui/widgets/ssh_key.py:29 msgid "LOADING" msgstr "加载中" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:48 +#: selfdrive/ui/layouts/sidebar.py:48 msgid "LTE" msgstr "LTE" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:64 +#: selfdrive/ui/layouts/settings/developer.py:64 #, python-format msgid "Longitudinal Maneuver Mode" msgstr "纵向操作模式" -#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:148 +#: selfdrive/ui/onroad/hud_renderer.py:148 #, python-format msgid "MAX" msgstr "最大" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:75 +#: selfdrive/ui/widgets/setup.py:75 #, python-format msgid "" "Maximize your training data uploads to improve openpilot's driving models." msgstr "最大化上传训练数据,以改进 openpilot 的驾驶模型。" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:59 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:60 +#: selfdrive/ui/layouts/settings/device.py:59 +#: selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "N/A" msgstr "无" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:142 +#: selfdrive/ui/layouts/sidebar.py:142 msgid "NO" msgstr "否" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:63 +#: selfdrive/ui/layouts/settings/settings.py:63 msgid "Network" msgstr "网络" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:114 +#: selfdrive/ui/widgets/ssh_key.py:114 #, python-format msgid "No SSH keys found" msgstr "未找到 SSH 密钥" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:126 +#: selfdrive/ui/widgets/ssh_key.py:126 #, python-format msgid "No SSH keys found for user '{}'" msgstr "未找到用户“{}”的 SSH 密钥" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:320 +#: selfdrive/ui/widgets/offroad_alerts.py:320 #, python-format msgid "No release notes available." msgstr "暂无发行说明。" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:73 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: selfdrive/ui/layouts/sidebar.py:73 selfdrive/ui/layouts/sidebar.py:134 msgid "OFFLINE" msgstr "离线" -#: /home/batman/openpilot/system/ui/widgets/html_render.py:263 -#: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:93 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: system/ui/widgets/html_render.py:263 system/ui/widgets/confirm_dialog.py:93 +#: selfdrive/ui/layouts/sidebar.py:127 #, python-format msgid "OK" msgstr "确定" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:72 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:136 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:136 +#: selfdrive/ui/layouts/sidebar.py:144 msgid "ONLINE" msgstr "在线" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:20 +#: selfdrive/ui/widgets/setup.py:20 #, python-format msgid "Open" msgstr "打开" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:48 +#: selfdrive/ui/layouts/settings/device.py:48 #, python-format msgid "PAIR" msgstr "配对" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:142 +#: selfdrive/ui/layouts/sidebar.py:142 msgid "PANDA" msgstr "PANDA" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:62 +#: selfdrive/ui/layouts/settings/device.py:62 #, python-format msgid "PREVIEW" msgstr "预览" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:44 +#: selfdrive/ui/widgets/prime.py:44 #, python-format msgid "PRIME FEATURES:" msgstr "PRIME 功能:" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:48 +#: selfdrive/ui/layouts/settings/device.py:48 #, python-format msgid "Pair Device" msgstr "配对设备" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:19 +#: selfdrive/ui/widgets/setup.py:19 #, python-format msgid "Pair device" msgstr "配对设备" -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:103 +#: selfdrive/ui/widgets/pairing_dialog.py:103 #, python-format msgid "Pair your device to your comma account" msgstr "将设备配对到您的 comma 账号" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:48 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:24 +#: selfdrive/ui/widgets/setup.py:48 selfdrive/ui/layouts/settings/device.py:24 #, python-format msgid "" "Pair your device with comma connect (connect.comma.ai) and claim your comma " @@ -652,69 +637,69 @@ msgid "" msgstr "" "将设备与 comma connect(connect.comma.ai)配对,领取您的 comma prime 优惠。" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:91 +#: selfdrive/ui/widgets/setup.py:91 #, python-format msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "请连接 Wi‑Fi 以完成初始配对" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:187 +#: selfdrive/ui/layouts/settings/device.py:55 +#: selfdrive/ui/layouts/settings/device.py:187 #, python-format msgid "Power Off" msgstr "关机" -#: /home/batman/openpilot/system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:144 #, python-format msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "在计量制 Wi‑Fi 连接时避免大量上传" -#: /home/batman/openpilot/system/ui/widgets/network.py:135 +#: system/ui/widgets/network.py:135 #, python-format msgid "Prevent large data uploads when on a metered cellular connection" msgstr "在计量制蜂窝网络时避免大量上传" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:25 +#: selfdrive/ui/layouts/settings/device.py:25 msgid "" "Preview the driver facing camera to ensure that driver monitoring has good " "visibility. (vehicle must be off)" msgstr "预览车内摄像头以确保驾驶员监控视野良好。(车辆必须熄火)" -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:161 +#: selfdrive/ui/widgets/pairing_dialog.py:161 #, python-format msgid "QR Code Error" msgstr "二维码错误" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:31 +#: selfdrive/ui/widgets/ssh_key.py:31 msgid "REMOVE" msgstr "移除" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:51 +#: selfdrive/ui/layouts/settings/device.py:51 #, python-format msgid "RESET" msgstr "重置" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:65 +#: selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "REVIEW" msgstr "查看" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:175 +#: selfdrive/ui/layouts/settings/device.py:55 +#: selfdrive/ui/layouts/settings/device.py:175 #, python-format msgid "Reboot" msgstr "重启" -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:66 +#: selfdrive/ui/onroad/alert_renderer.py:66 #, python-format msgid "Reboot Device" msgstr "重启设备" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 +#: selfdrive/ui/widgets/offroad_alerts.py:112 #, python-format msgid "Reboot and Update" msgstr "重启并更新" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:27 +#: selfdrive/ui/layouts/settings/toggles.py:27 msgid "" "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 " @@ -723,117 +708,117 @@ msgstr "" "当车辆以超过 31 mph(50 km/h)行驶且未打转向灯越过检测到的车道线时,接收引导" "回车道的警报。" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:76 +#: selfdrive/ui/layouts/settings/toggles.py:76 #, python-format msgid "Record and Upload Driver Camera" msgstr "录制并上传车内摄像头" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:82 +#: selfdrive/ui/layouts/settings/toggles.py:82 #, python-format msgid "Record and Upload Microphone Audio" msgstr "录制并上传麦克风音频" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +#: selfdrive/ui/layouts/settings/toggles.py:33 msgid "" "Record and store microphone audio while driving. The audio will be included " "in the dashcam video in comma connect." msgstr "" "行驶时录制并保存麦克风音频。音频将包含在 comma connect 的行车记录视频中。" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:67 +#: selfdrive/ui/layouts/settings/device.py:67 #, python-format msgid "Regulatory" msgstr "法规" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#: selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Relaxed" msgstr "从容" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#: selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote access" msgstr "远程访问" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#: selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote snapshots" msgstr "远程快照" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:123 +#: selfdrive/ui/widgets/ssh_key.py:123 #, python-format msgid "Request timed out" msgstr "请求超时" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:119 +#: selfdrive/ui/layouts/settings/device.py:119 #, python-format msgid "Reset" msgstr "重置" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:51 +#: selfdrive/ui/layouts/settings/device.py:51 #, python-format msgid "Reset Calibration" msgstr "重置校准" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:65 +#: selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "Review Training Guide" msgstr "查看训练指南" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:27 +#: selfdrive/ui/layouts/settings/device.py:27 msgid "Review the rules, features, and limitations of openpilot" msgstr "查看 openpilot 的规则、功能与限制" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:61 +#: selfdrive/ui/layouts/settings/software.py:61 #, python-format msgid "SELECT" msgstr "选择" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:53 +#: selfdrive/ui/layouts/settings/developer.py:53 #, python-format msgid "SSH Keys" msgstr "SSH 密钥" -#: /home/batman/openpilot/system/ui/widgets/network.py:310 +#: system/ui/widgets/network.py:310 #, python-format msgid "Scanning Wi-Fi networks..." msgstr "正在扫描 Wi‑Fi 网络…" -#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/option_dialog.py:36 #, python-format msgid "Select" msgstr "选择" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:183 +#: selfdrive/ui/layouts/settings/software.py:183 #, python-format msgid "Select a branch" msgstr "选择分支" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:91 +#: selfdrive/ui/layouts/settings/device.py:91 #, python-format msgid "Select a language" msgstr "选择语言" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:60 +#: selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "Serial" msgstr "序列号" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 +#: selfdrive/ui/widgets/offroad_alerts.py:106 #, python-format msgid "Snooze Update" msgstr "延后更新" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:65 +#: selfdrive/ui/layouts/settings/settings.py:65 msgid "Software" msgstr "软件" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#: selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Standard" msgstr "标准" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:22 +#: selfdrive/ui/layouts/settings/toggles.py:22 msgid "" "Standard is recommended. In aggressive mode, openpilot will follow lead cars " "closer and be more aggressive with the gas and brake. In relaxed mode " @@ -844,80 +829,78 @@ msgstr "" "容模式下,会与前车保持更远距离。在支持的车型上,可用方向盘距离按钮切换这些风" "格。" -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:59 -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:65 +#: selfdrive/ui/onroad/alert_renderer.py:59 +#: selfdrive/ui/onroad/alert_renderer.py:65 #, python-format msgid "System Unresponsive" msgstr "系统无响应" -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:58 +#: selfdrive/ui/onroad/alert_renderer.py:58 #, python-format msgid "TAKE CONTROL IMMEDIATELY" msgstr "请立即接管控制" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:71 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:125 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:127 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:129 +#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 +#: selfdrive/ui/layouts/sidebar.py:127 selfdrive/ui/layouts/sidebar.py:129 msgid "TEMP" msgstr "温度" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:61 +#: selfdrive/ui/layouts/settings/software.py:61 #, python-format msgid "Target Branch" msgstr "目标分支" -#: /home/batman/openpilot/system/ui/widgets/network.py:124 +#: system/ui/widgets/network.py:124 #, python-format msgid "Tethering Password" msgstr "网络共享密码" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:64 +#: selfdrive/ui/layouts/settings/settings.py:64 msgid "Toggles" msgstr "切换" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:72 +#: selfdrive/ui/layouts/settings/software.py:72 #, python-format msgid "UNINSTALL" msgstr "卸载" -#: /home/batman/openpilot/selfdrive/ui/layouts/home.py:155 +#: selfdrive/ui/layouts/home.py:155 #, python-format msgid "UPDATE" msgstr "更新" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:72 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:163 +#: selfdrive/ui/layouts/settings/software.py:72 +#: selfdrive/ui/layouts/settings/software.py:163 #, python-format msgid "Uninstall" msgstr "卸载" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:117 +#: selfdrive/ui/layouts/sidebar.py:117 msgid "Unknown" msgstr "未知" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:48 +#: selfdrive/ui/layouts/settings/software.py:48 #, python-format msgid "Updates are only downloaded while the car is off." msgstr "仅在车辆熄火时下载更新。" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:33 +#: selfdrive/ui/widgets/prime.py:33 #, python-format msgid "Upgrade Now" msgstr "立即升级" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +#: selfdrive/ui/layouts/settings/toggles.py:31 msgid "" "Upload data from the driver facing camera and help improve the driver " "monitoring algorithm." msgstr "上传车内摄像头数据,帮助改进驾驶员监控算法。" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:88 +#: selfdrive/ui/layouts/settings/toggles.py:88 #, python-format msgid "Use Metric System" msgstr "使用公制" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:17 +#: selfdrive/ui/layouts/settings/toggles.py:17 msgid "" "Use the openpilot system for adaptive cruise control and lane keep driver " "assistance. Your attention is required at all times to use this feature." @@ -925,22 +908,21 @@ msgstr "" "使用 openpilot 进行自适应巡航与车道保持辅助。使用此功能时,您必须始终保持专" "注。" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:72 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:144 msgid "VEHICLE" msgstr "车辆" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:67 +#: selfdrive/ui/layouts/settings/device.py:67 #, python-format msgid "VIEW" msgstr "查看" -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:52 +#: selfdrive/ui/onroad/alert_renderer.py:52 #, python-format msgid "Waiting to start" msgstr "等待开始" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:19 +#: selfdrive/ui/layouts/settings/developer.py:19 msgid "" "Warning: This grants SSH access to all public keys in your GitHub settings. " "Never enter a GitHub username other than your own. A comma employee will " @@ -949,35 +931,35 @@ msgstr "" "警告:这将授予对您 GitHub 设置中所有公钥的 SSH 访问权限。请勿输入非您本人的 " "GitHub 用户名。comma 员工绝不会要求您添加他们的用户名。" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:111 +#: selfdrive/ui/layouts/onboarding.py:111 #, python-format msgid "Welcome to openpilot" msgstr "欢迎使用 openpilot" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:20 +#: selfdrive/ui/layouts/settings/toggles.py:20 msgid "When enabled, pressing the accelerator pedal will disengage openpilot." msgstr "启用后,踩下加速踏板将会脱离 openpilot。" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:44 +#: selfdrive/ui/layouts/sidebar.py:44 msgid "Wi-Fi" msgstr "Wi‑Fi" -#: /home/batman/openpilot/system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:144 #, python-format msgid "Wi-Fi Network Metered" msgstr "Wi‑Fi 计量网络" -#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:314 #, python-format msgid "Wrong password" msgstr "密码错误" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:145 +#: selfdrive/ui/layouts/onboarding.py:145 #, python-format msgid "You must accept the Terms and Conditions in order to use openpilot." msgstr "您必须接受条款与条件才能使用 openpilot。" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:112 +#: selfdrive/ui/layouts/onboarding.py:112 #, python-format msgid "" "You must accept the Terms and Conditions to use openpilot. Read the latest " @@ -986,83 +968,82 @@ msgstr "" "您必须接受条款与条件才能使用 openpilot。继续前请阅读 https://comma.ai/terms " "上的最新条款。" -#: /home/batman/openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:34 +#: selfdrive/ui/onroad/driver_camera_dialog.py:34 #, python-format msgid "camera starting" msgstr "相机启动中" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:63 +#: selfdrive/ui/widgets/prime.py:63 #, python-format msgid "comma prime" msgstr "comma prime" -#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:142 #, python-format msgid "default" msgstr "默认" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#: selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid "down" msgstr "下" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:106 +#: selfdrive/ui/layouts/settings/software.py:106 #, python-format msgid "failed to check for update" msgstr "检查更新失败" -#: /home/batman/openpilot/system/ui/widgets/network.py:237 -#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 #, python-format msgid "for \"{}\"" msgstr "用于“{}”" -#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:177 +#: selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "km/h" msgstr "公里/时" -#: /home/batman/openpilot/system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:204 #, python-format msgid "leave blank for automatic configuration" msgstr "留空以自动配置" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:134 +#: selfdrive/ui/layouts/settings/device.py:134 #, python-format msgid "left" msgstr "左" -#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:142 #, python-format msgid "metered" msgstr "计量" -#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:177 +#: selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "mph" msgstr "英里/时" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:20 +#: selfdrive/ui/layouts/settings/software.py:20 #, python-format msgid "never" msgstr "从不" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:31 +#: selfdrive/ui/layouts/settings/software.py:31 #, python-format msgid "now" msgstr "现在" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:71 +#: selfdrive/ui/layouts/settings/developer.py:71 #, python-format msgid "openpilot Longitudinal Control (Alpha)" msgstr "openpilot 纵向控制(Alpha)" -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:51 +#: selfdrive/ui/onroad/alert_renderer.py:51 #, python-format msgid "openpilot Unavailable" msgstr "openpilot 无法使用" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:158 +#: selfdrive/ui/layouts/settings/toggles.py:158 #, python-format msgid "" "openpilot defaults to driving in chill mode. Experimental mode enables alpha-" @@ -1084,7 +1065,7 @@ msgstr "" "视化
在低速时,驾驶可视化将切换至面向道路的广角摄像头以更好显示部分转" "弯。右上角也会显示实验模式图标。" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:165 +#: selfdrive/ui/layouts/settings/device.py:165 #, python-format msgid "" "openpilot is continuously calibrating, resetting is rarely required. " @@ -1093,7 +1074,7 @@ msgstr "" "openpilot 持续进行校准,通常无需重置。若车辆通电,重置校准将会重启 " "openpilot。" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:20 +#: selfdrive/ui/layouts/settings/firehose.py:20 msgid "" "openpilot learns to drive by watching humans, like you, drive.\n" "\n" @@ -1106,88 +1087,88 @@ msgstr "" "Firehose 模式可让您最大化上传训练数据,以改进 openpilot 的驾驶模型。更多数据" "意味着更大的模型,也意味着更好的实验模式。" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:183 +#: selfdrive/ui/layouts/settings/toggles.py:183 #, python-format msgid "openpilot longitudinal control may come in a future update." msgstr "openpilot 纵向控制可能会在未来更新中提供。" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:26 +#: selfdrive/ui/layouts/settings/device.py:26 msgid "" "openpilot requires the device to be mounted within 4° left or right and " "within 5° up or 9° down." msgstr "openpilot 要求设备安装在左右 4°、上 5° 或下 9° 以内。" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:134 +#: selfdrive/ui/layouts/settings/device.py:134 #, python-format msgid "right" msgstr "右" -#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:142 #, python-format msgid "unmetered" msgstr "不限流量" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#: selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid "up" msgstr "上" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:117 +#: selfdrive/ui/layouts/settings/software.py:117 #, python-format msgid "up to date, last checked never" msgstr "已是最新,最后检查:从未" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:115 +#: selfdrive/ui/layouts/settings/software.py:115 #, python-format msgid "up to date, last checked {}" msgstr "已是最新,最后检查:{}" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:109 +#: selfdrive/ui/layouts/settings/software.py:109 #, python-format msgid "update available" msgstr "有可用更新" -#: /home/batman/openpilot/selfdrive/ui/layouts/home.py:169 +#: selfdrive/ui/layouts/home.py:169 #, python-format msgid "{} ALERT" msgid_plural "{} ALERTS" msgstr[0] "{} 条警报" msgstr[1] "{} 条警报" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:40 +#: selfdrive/ui/layouts/settings/software.py:40 #, python-format msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "{} 天前" msgstr[1] "{} 天前" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:37 +#: selfdrive/ui/layouts/settings/software.py:37 #, python-format msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "{} 小时前" msgstr[1] "{} 小时前" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:34 +#: selfdrive/ui/layouts/settings/software.py:34 #, python-format msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "{} 分钟前" msgstr[1] "{} 分钟前" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:111 +#: selfdrive/ui/layouts/settings/firehose.py:111 #, python-format msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." msgstr[0] "目前已有 {} 个您的驾驶片段被纳入训练数据集。" msgstr[1] "目前已有 {} 个您的驾驶片段被纳入训练数据集。" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:62 +#: selfdrive/ui/widgets/prime.py:62 #, python-format msgid "✓ SUBSCRIBED" msgstr "✓ 已订阅" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:22 +#: selfdrive/ui/widgets/setup.py:22 #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "🔥 Firehose 模式 🔥" diff --git a/selfdrive/ui/translations/app_zh-CHT.po b/selfdrive/ui/translations/app_zh-CHT.po index 1e9e2c094..85cfb7740 100644 --- a/selfdrive/ui/translations/app_zh-CHT.po +++ b/selfdrive/ui/translations/app_zh-CHT.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-22 19:09-0700\n" +"POT-Creation-Date: 2025-10-23 00:50-0700\n" "PO-Revision-Date: 2025-10-22 16:32-0700\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" @@ -17,48 +17,48 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:160 +#: selfdrive/ui/layouts/settings/device.py:160 #, python-format msgid " Steering torque response calibration is complete." msgstr " 轉向扭矩回應校正完成。" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:158 +#: selfdrive/ui/layouts/settings/device.py:158 #, python-format msgid " Steering torque response calibration is {}% complete." msgstr " 轉向扭矩回應校正已完成 {}%。" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#: selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr " 您的裝置朝向 {:.1f}° {} 與 {:.1f}° {}。" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:43 +#: selfdrive/ui/layouts/sidebar.py:43 msgid "--" msgstr "--" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#: selfdrive/ui/widgets/prime.py:47 #, python-format msgid "1 year of drive storage" msgstr "1 年行駛資料儲存" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#: selfdrive/ui/widgets/prime.py:47 #, python-format msgid "24/7 LTE connectivity" msgstr "全年無休 LTE 連線" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:46 +#: selfdrive/ui/layouts/sidebar.py:46 msgid "2G" msgstr "2G" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:47 +#: selfdrive/ui/layouts/sidebar.py:47 msgid "3G" msgstr "3G" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:49 +#: selfdrive/ui/layouts/sidebar.py:49 msgid "5G" msgstr "5G" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:23 +#: selfdrive/ui/layouts/settings/developer.py:23 msgid "" "WARNING: openpilot longitudinal control is in alpha for this car and will " "disable Automatic Emergency Braking (AEB).

On this car, openpilot " @@ -73,22 +73,22 @@ msgstr "" "制。啟用此選項可切換為 openpilot 縱向控制。建議同時啟用實驗模式。若車輛通電," "變更此設定將會重新啟動 openpilot。" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:148 +#: selfdrive/ui/layouts/settings/device.py:148 #, python-format msgid "

Steering lag calibration is complete." msgstr "

轉向延遲校正完成。" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:146 +#: selfdrive/ui/layouts/settings/device.py:146 #, python-format msgid "

Steering lag calibration is {}% complete." msgstr "

轉向延遲校正已完成 {}%。" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:138 +#: selfdrive/ui/layouts/settings/firehose.py:138 #, python-format msgid "ACTIVE" msgstr "啟用" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:15 +#: selfdrive/ui/layouts/settings/developer.py:15 msgid "" "ADB (Android Debug Bridge) allows connecting to your device over USB or over " "the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." @@ -96,350 +96,341 @@ msgstr "" "ADB (Android Debug Bridge) 可透過 USB 或網路連線至您的裝置。詳見 https://" "docs.comma.ai/how-to/connect-to-comma。" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:30 +#: selfdrive/ui/widgets/ssh_key.py:30 msgid "ADD" msgstr "新增" -#: /home/batman/openpilot/system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:139 #, python-format msgid "APN Setting" msgstr "APN 設定" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 +#: selfdrive/ui/widgets/offroad_alerts.py:109 #, python-format msgid "Acknowledge Excessive Actuation" msgstr "確認過度作動" -#: /home/batman/openpilot/system/ui/widgets/network.py:74 -#: /home/batman/openpilot/system/ui/widgets/network.py:95 +#: system/ui/widgets/network.py:74 system/ui/widgets/network.py:95 #, python-format msgid "Advanced" msgstr "進階" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#: selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Aggressive" msgstr "積極" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:116 +#: selfdrive/ui/layouts/onboarding.py:116 #, python-format msgid "Agree" msgstr "同意" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:70 +#: selfdrive/ui/layouts/settings/toggles.py:70 #, python-format msgid "Always-On Driver Monitoring" msgstr "持續啟用駕駛監控" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:186 +#: selfdrive/ui/layouts/settings/toggles.py:186 #, python-format msgid "" "An alpha version of openpilot longitudinal control can be tested, along with " "Experimental mode, on non-release branches." msgstr "openpilot 縱向控制的 alpha 版本可於非發行分支搭配實驗模式進行測試。" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:187 +#: selfdrive/ui/layouts/settings/device.py:187 #, python-format msgid "Are you sure you want to power off?" msgstr "確定要關機嗎?" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:175 +#: selfdrive/ui/layouts/settings/device.py:175 #, python-format msgid "Are you sure you want to reboot?" msgstr "確定要重新啟動嗎?" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:119 +#: selfdrive/ui/layouts/settings/device.py:119 #, python-format msgid "Are you sure you want to reset calibration?" msgstr "確定要重設校正嗎?" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:163 +#: selfdrive/ui/layouts/settings/software.py:163 #, python-format msgid "Are you sure you want to uninstall?" msgstr "確定要解除安裝嗎?" -#: /home/batman/openpilot/system/ui/widgets/network.py:99 -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:147 +#: system/ui/widgets/network.py:99 selfdrive/ui/layouts/onboarding.py:147 #, python-format msgid "Back" msgstr "返回" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:38 +#: selfdrive/ui/widgets/prime.py:38 #, python-format msgid "Become a comma prime member at connect.comma.ai" msgstr "前往 connect.comma.ai 成為 comma prime 會員" -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:130 +#: selfdrive/ui/widgets/pairing_dialog.py:130 #, python-format msgid "Bookmark connect.comma.ai to your home screen to use it like an app" msgstr "將 connect.comma.ai 加到主畫面,像 App 一樣使用" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:68 +#: selfdrive/ui/layouts/settings/device.py:68 #, python-format msgid "CHANGE" msgstr "變更" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:50 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:107 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:118 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:147 +#: selfdrive/ui/layouts/settings/software.py:50 +#: selfdrive/ui/layouts/settings/software.py:107 +#: selfdrive/ui/layouts/settings/software.py:118 +#: selfdrive/ui/layouts/settings/software.py:147 #, python-format msgid "CHECK" msgstr "檢查" -#: /home/batman/openpilot/selfdrive/ui/widgets/exp_mode_button.py:50 +#: selfdrive/ui/widgets/exp_mode_button.py:50 #, python-format msgid "CHILL MODE ON" msgstr "安穩模式已開啟" -#: /home/batman/openpilot/system/ui/widgets/network.py:155 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:73 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:134 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:136 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:138 +#: system/ui/widgets/network.py:155 selfdrive/ui/layouts/sidebar.py:73 +#: selfdrive/ui/layouts/sidebar.py:134 selfdrive/ui/layouts/sidebar.py:136 +#: selfdrive/ui/layouts/sidebar.py:138 #, python-format msgid "CONNECT" msgstr "CONNECT" -#: /home/batman/openpilot/system/ui/widgets/network.py:369 +#: system/ui/widgets/network.py:369 #, python-format msgid "CONNECTING..." msgstr "CONNECTING..." -#: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:23 -#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:35 -#: /home/batman/openpilot/system/ui/widgets/keyboard.py:81 -#: /home/batman/openpilot/system/ui/widgets/network.py:318 +#: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35 +#: system/ui/widgets/keyboard.py:81 system/ui/widgets/network.py:318 #, python-format msgid "Cancel" msgstr "取消" -#: /home/batman/openpilot/system/ui/widgets/network.py:134 +#: system/ui/widgets/network.py:134 #, python-format msgid "Cellular Metered" msgstr "行動網路計量" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:68 +#: selfdrive/ui/layouts/settings/device.py:68 #, python-format msgid "Change Language" msgstr "變更語言" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:125 +#: selfdrive/ui/layouts/settings/toggles.py:125 #, python-format msgid "Changing this setting will restart openpilot if the car is powered on." msgstr "若車輛通電,變更此設定將重新啟動 openpilot。" -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:129 +#: selfdrive/ui/widgets/pairing_dialog.py:129 #, python-format msgid "Click \"add new device\" and scan the QR code on the right" msgstr "點選「新增裝置」,掃描右側 QR 碼" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 +#: selfdrive/ui/widgets/offroad_alerts.py:104 #, python-format msgid "Close" msgstr "關閉" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:49 +#: selfdrive/ui/layouts/settings/software.py:49 #, python-format msgid "Current Version" msgstr "目前版本" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:110 +#: selfdrive/ui/layouts/settings/software.py:110 #, python-format msgid "DOWNLOAD" msgstr "下載" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:115 +#: selfdrive/ui/layouts/onboarding.py:115 #, python-format msgid "Decline" msgstr "拒絕" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:148 +#: selfdrive/ui/layouts/onboarding.py:148 #, python-format msgid "Decline, uninstall openpilot" msgstr "拒絕並解除安裝 openpilot" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:67 +#: selfdrive/ui/layouts/settings/settings.py:67 msgid "Developer" msgstr "開發人員" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:62 +#: selfdrive/ui/layouts/settings/settings.py:62 msgid "Device" msgstr "裝置" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:58 +#: selfdrive/ui/layouts/settings/toggles.py:58 #, python-format msgid "Disengage on Accelerator Pedal" msgstr "踩下加速踏板時脫離" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:184 +#: selfdrive/ui/layouts/settings/device.py:184 #, python-format msgid "Disengage to Power Off" msgstr "脫離以關機" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:172 +#: selfdrive/ui/layouts/settings/device.py:172 #, python-format msgid "Disengage to Reboot" msgstr "脫離以重新啟動" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:103 +#: selfdrive/ui/layouts/settings/device.py:103 #, python-format msgid "Disengage to Reset Calibration" msgstr "脫離以重設校正" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:32 +#: selfdrive/ui/layouts/settings/toggles.py:32 msgid "Display speed in km/h instead of mph." msgstr "以 km/h 顯示速度(非 mph)。" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:59 +#: selfdrive/ui/layouts/settings/device.py:59 #, python-format msgid "Dongle ID" msgstr "Dongle ID" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:50 +#: selfdrive/ui/layouts/settings/software.py:50 #, python-format msgid "Download" msgstr "下載" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:62 +#: selfdrive/ui/layouts/settings/device.py:62 #, python-format msgid "Driver Camera" msgstr "車內鏡頭" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:96 +#: selfdrive/ui/layouts/settings/toggles.py:96 #, python-format msgid "Driving Personality" msgstr "駕駛風格" -#: /home/batman/openpilot/system/ui/widgets/network.py:123 -#: /home/batman/openpilot/system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:123 system/ui/widgets/network.py:139 #, python-format msgid "EDIT" msgstr "編輯" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:138 +#: selfdrive/ui/layouts/sidebar.py:138 msgid "ERROR" msgstr "錯誤" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:45 +#: selfdrive/ui/layouts/sidebar.py:45 msgid "ETH" msgstr "ETH" -#: /home/batman/openpilot/selfdrive/ui/widgets/exp_mode_button.py:50 +#: selfdrive/ui/widgets/exp_mode_button.py:50 #, python-format msgid "EXPERIMENTAL MODE ON" msgstr "實驗模式已開啟" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:166 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:228 +#: selfdrive/ui/layouts/settings/developer.py:166 +#: selfdrive/ui/layouts/settings/toggles.py:228 #, python-format msgid "Enable" msgstr "啟用" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:39 +#: selfdrive/ui/layouts/settings/developer.py:39 #, python-format msgid "Enable ADB" msgstr "啟用 ADB" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:64 +#: selfdrive/ui/layouts/settings/toggles.py:64 #, python-format msgid "Enable Lane Departure Warnings" msgstr "啟用偏離車道警示" -#: /home/batman/openpilot/system/ui/widgets/network.py:129 +#: system/ui/widgets/network.py:129 #, python-format msgid "Enable Roaming" msgstr "啟用漫遊" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:48 +#: selfdrive/ui/layouts/settings/developer.py:48 #, python-format msgid "Enable SSH" msgstr "啟用 SSH" -#: /home/batman/openpilot/system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:120 #, python-format msgid "Enable Tethering" msgstr "啟用網路共享" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:30 +#: selfdrive/ui/layouts/settings/toggles.py:30 msgid "Enable driver monitoring even when openpilot is not engaged." msgstr "即使未啟動 openpilot 亦啟用駕駛監控。" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:46 +#: selfdrive/ui/layouts/settings/toggles.py:46 #, python-format msgid "Enable openpilot" msgstr "啟用 openpilot" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:189 +#: selfdrive/ui/layouts/settings/toggles.py:189 #, python-format msgid "" "Enable the openpilot longitudinal control (alpha) toggle to allow " "Experimental mode." msgstr "啟用 openpilot 縱向控制(alpha)切換,以使用實驗模式。" -#: /home/batman/openpilot/system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:204 #, python-format msgid "Enter APN" msgstr "輸入 APN" -#: /home/batman/openpilot/system/ui/widgets/network.py:241 +#: system/ui/widgets/network.py:241 #, python-format msgid "Enter SSID" msgstr "輸入 SSID" -#: /home/batman/openpilot/system/ui/widgets/network.py:254 +#: system/ui/widgets/network.py:254 #, python-format msgid "Enter new tethering password" msgstr "輸入新的網路共享密碼" -#: /home/batman/openpilot/system/ui/widgets/network.py:237 -#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 #, python-format msgid "Enter password" msgstr "輸入密碼" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:89 +#: selfdrive/ui/widgets/ssh_key.py:89 #, python-format msgid "Enter your GitHub username" msgstr "輸入您的 GitHub 使用者名稱" -#: /home/batman/openpilot/system/ui/widgets/list_view.py:123 -#: /home/batman/openpilot/system/ui/widgets/list_view.py:160 +#: system/ui/widgets/list_view.py:123 system/ui/widgets/list_view.py:160 #, python-format msgid "Error" msgstr "錯誤" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:52 +#: selfdrive/ui/layouts/settings/toggles.py:52 #, python-format msgid "Experimental Mode" msgstr "實驗模式" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:181 +#: selfdrive/ui/layouts/settings/toggles.py:181 #, python-format msgid "" "Experimental mode is currently unavailable on this car since the car's stock " "ACC is used for longitudinal control." msgstr "此車款目前無法使用實驗模式,因為縱向控制使用的是原廠 ACC。" -#: /home/batman/openpilot/system/ui/widgets/network.py:373 +#: system/ui/widgets/network.py:373 #, python-format msgid "FORGETTING..." msgstr "正在遺忘..." -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:44 +#: selfdrive/ui/widgets/setup.py:44 #, python-format msgid "Finish Setup" msgstr "完成設定" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:66 +#: selfdrive/ui/layouts/settings/settings.py:66 msgid "Firehose" msgstr "Firehose" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:18 +#: selfdrive/ui/layouts/settings/firehose.py:18 msgid "Firehose Mode" msgstr "Firehose 模式" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:25 +#: selfdrive/ui/layouts/settings/firehose.py:25 msgid "" "For maximum effectiveness, bring your device inside and connect to a good " "USB-C adapter and Wi-Fi weekly.\n" @@ -477,174 +468,168 @@ msgstr "" "\n" "我跑什麼軟體有差嗎?有,只有上游 openpilot(及特定分支)可用於訓練。" -#: /home/batman/openpilot/system/ui/widgets/network.py:318 -#: /home/batman/openpilot/system/ui/widgets/network.py:451 +#: system/ui/widgets/network.py:318 system/ui/widgets/network.py:451 #, python-format msgid "Forget" msgstr "忘記" -#: /home/batman/openpilot/system/ui/widgets/network.py:319 +#: system/ui/widgets/network.py:319 #, python-format msgid "Forget Wi-Fi Network \"{}\"?" msgstr "要忘記 Wi‑Fi 網路「{}」嗎?" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:71 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 msgid "GOOD" msgstr "良好" -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:128 +#: selfdrive/ui/widgets/pairing_dialog.py:128 #, python-format msgid "Go to https://connect.comma.ai on your phone" msgstr "在手機上前往 https://connect.comma.ai" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:129 +#: selfdrive/ui/layouts/sidebar.py:129 msgid "HIGH" msgstr "高" -#: /home/batman/openpilot/system/ui/widgets/network.py:155 +#: system/ui/widgets/network.py:155 #, python-format msgid "Hidden Network" msgstr "隱藏網路" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:140 +#: selfdrive/ui/layouts/settings/firehose.py:140 #, python-format msgid "INACTIVE: connect to an unmetered network" msgstr "未啟用:請連接不限流量網路" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:53 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:136 +#: selfdrive/ui/layouts/settings/software.py:53 +#: selfdrive/ui/layouts/settings/software.py:136 #, python-format msgid "INSTALL" msgstr "安裝" -#: /home/batman/openpilot/system/ui/widgets/network.py:150 +#: system/ui/widgets/network.py:150 #, python-format msgid "IP Address" msgstr "IP 位址" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:53 +#: selfdrive/ui/layouts/settings/software.py:53 #, python-format msgid "Install Update" msgstr "安裝更新" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:56 +#: selfdrive/ui/layouts/settings/developer.py:56 #, python-format msgid "Joystick Debug Mode" msgstr "搖桿除錯模式" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:29 +#: selfdrive/ui/widgets/ssh_key.py:29 msgid "LOADING" msgstr "載入中" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:48 +#: selfdrive/ui/layouts/sidebar.py:48 msgid "LTE" msgstr "LTE" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:64 +#: selfdrive/ui/layouts/settings/developer.py:64 #, python-format msgid "Longitudinal Maneuver Mode" msgstr "縱向操作模式" -#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:148 +#: selfdrive/ui/onroad/hud_renderer.py:148 #, python-format msgid "MAX" msgstr "最大" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:75 +#: selfdrive/ui/widgets/setup.py:75 #, python-format msgid "" "Maximize your training data uploads to improve openpilot's driving models." msgstr "最大化上傳訓練資料,以改進 openpilot 的駕駛模型。" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:59 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:60 +#: selfdrive/ui/layouts/settings/device.py:59 +#: selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "N/A" msgstr "無" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:142 +#: selfdrive/ui/layouts/sidebar.py:142 msgid "NO" msgstr "否" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:63 +#: selfdrive/ui/layouts/settings/settings.py:63 msgid "Network" msgstr "網路" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:114 +#: selfdrive/ui/widgets/ssh_key.py:114 #, python-format msgid "No SSH keys found" msgstr "找不到 SSH 金鑰" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:126 +#: selfdrive/ui/widgets/ssh_key.py:126 #, python-format msgid "No SSH keys found for user '{}'" msgstr "找不到使用者 '{}' 的 SSH 金鑰" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:320 +#: selfdrive/ui/widgets/offroad_alerts.py:320 #, python-format msgid "No release notes available." msgstr "無可用發行說明。" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:73 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: selfdrive/ui/layouts/sidebar.py:73 selfdrive/ui/layouts/sidebar.py:134 msgid "OFFLINE" msgstr "離線" -#: /home/batman/openpilot/system/ui/widgets/html_render.py:263 -#: /home/batman/openpilot/system/ui/widgets/confirm_dialog.py:93 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: system/ui/widgets/html_render.py:263 system/ui/widgets/confirm_dialog.py:93 +#: selfdrive/ui/layouts/sidebar.py:127 #, python-format msgid "OK" msgstr "確定" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:72 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:136 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:136 +#: selfdrive/ui/layouts/sidebar.py:144 msgid "ONLINE" msgstr "線上" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:20 +#: selfdrive/ui/widgets/setup.py:20 #, python-format msgid "Open" msgstr "開啟" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:48 +#: selfdrive/ui/layouts/settings/device.py:48 #, python-format msgid "PAIR" msgstr "配對" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:142 +#: selfdrive/ui/layouts/sidebar.py:142 msgid "PANDA" msgstr "PANDA" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:62 +#: selfdrive/ui/layouts/settings/device.py:62 #, python-format msgid "PREVIEW" msgstr "預覽" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:44 +#: selfdrive/ui/widgets/prime.py:44 #, python-format msgid "PRIME FEATURES:" msgstr "PRIME 功能:" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:48 +#: selfdrive/ui/layouts/settings/device.py:48 #, python-format msgid "Pair Device" msgstr "配對裝置" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:19 +#: selfdrive/ui/widgets/setup.py:19 #, python-format msgid "Pair device" msgstr "配對裝置" -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:103 +#: selfdrive/ui/widgets/pairing_dialog.py:103 #, python-format msgid "Pair your device to your comma account" msgstr "將裝置配對至您的 comma 帳號" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:48 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:24 +#: selfdrive/ui/widgets/setup.py:48 selfdrive/ui/layouts/settings/device.py:24 #, python-format msgid "" "Pair your device with comma connect (connect.comma.ai) and claim your comma " @@ -652,69 +637,69 @@ msgid "" msgstr "" "將裝置與 comma connect(connect.comma.ai)配對,領取您的 comma prime 優惠。" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:91 +#: selfdrive/ui/widgets/setup.py:91 #, python-format msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "請連線至 Wi‑Fi 以完成初始化配對" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:187 +#: selfdrive/ui/layouts/settings/device.py:55 +#: selfdrive/ui/layouts/settings/device.py:187 #, python-format msgid "Power Off" msgstr "關機" -#: /home/batman/openpilot/system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:144 #, python-format msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "在計量制 Wi‑Fi 連線時避免大量上傳" -#: /home/batman/openpilot/system/ui/widgets/network.py:135 +#: system/ui/widgets/network.py:135 #, python-format msgid "Prevent large data uploads when on a metered cellular connection" msgstr "在計量制行動網路時避免大量上傳" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:25 +#: selfdrive/ui/layouts/settings/device.py:25 msgid "" "Preview the driver facing camera to ensure that driver monitoring has good " "visibility. (vehicle must be off)" msgstr "預覽車內鏡頭以確保駕駛監控視野良好。(車輛須熄火)" -#: /home/batman/openpilot/selfdrive/ui/widgets/pairing_dialog.py:161 +#: selfdrive/ui/widgets/pairing_dialog.py:161 #, python-format msgid "QR Code Error" msgstr "QR 碼錯誤" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:31 +#: selfdrive/ui/widgets/ssh_key.py:31 msgid "REMOVE" msgstr "移除" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:51 +#: selfdrive/ui/layouts/settings/device.py:51 #, python-format msgid "RESET" msgstr "重設" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:65 +#: selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "REVIEW" msgstr "檢視" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:55 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:175 +#: selfdrive/ui/layouts/settings/device.py:55 +#: selfdrive/ui/layouts/settings/device.py:175 #, python-format msgid "Reboot" msgstr "重新啟動" -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:66 +#: selfdrive/ui/onroad/alert_renderer.py:66 #, python-format msgid "Reboot Device" msgstr "重新啟動裝置" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 +#: selfdrive/ui/widgets/offroad_alerts.py:112 #, python-format msgid "Reboot and Update" msgstr "重新啟動並更新" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:27 +#: selfdrive/ui/layouts/settings/toggles.py:27 msgid "" "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 " @@ -723,117 +708,117 @@ msgstr "" "當車輛以超過 31 mph(50 km/h)行駛且未打方向燈越過偵測到的車道線時,接收轉向" "回車道的警示。" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:76 +#: selfdrive/ui/layouts/settings/toggles.py:76 #, python-format msgid "Record and Upload Driver Camera" msgstr "錄製並上傳車內鏡頭" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:82 +#: selfdrive/ui/layouts/settings/toggles.py:82 #, python-format msgid "Record and Upload Microphone Audio" msgstr "錄製並上傳麥克風音訊" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +#: selfdrive/ui/layouts/settings/toggles.py:33 msgid "" "Record and store microphone audio while driving. The audio will be included " "in the dashcam video in comma connect." msgstr "" "行車時錄製並儲存麥克風音訊。音訊將包含在 comma connect 的行車紀錄影片中。" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:67 +#: selfdrive/ui/layouts/settings/device.py:67 #, python-format msgid "Regulatory" msgstr "法規" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#: selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Relaxed" msgstr "從容" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#: selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote access" msgstr "遠端存取" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:47 +#: selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote snapshots" msgstr "遠端擷圖" -#: /home/batman/openpilot/selfdrive/ui/widgets/ssh_key.py:123 +#: selfdrive/ui/widgets/ssh_key.py:123 #, python-format msgid "Request timed out" msgstr "要求逾時" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:119 +#: selfdrive/ui/layouts/settings/device.py:119 #, python-format msgid "Reset" msgstr "重設" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:51 +#: selfdrive/ui/layouts/settings/device.py:51 #, python-format msgid "Reset Calibration" msgstr "重設校正" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:65 +#: selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "Review Training Guide" msgstr "檢視訓練指南" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:27 +#: selfdrive/ui/layouts/settings/device.py:27 msgid "Review the rules, features, and limitations of openpilot" msgstr "檢視 openpilot 的規則、功能與限制" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:61 +#: selfdrive/ui/layouts/settings/software.py:61 #, python-format msgid "SELECT" msgstr "選取" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:53 +#: selfdrive/ui/layouts/settings/developer.py:53 #, python-format msgid "SSH Keys" msgstr "SSH 金鑰" -#: /home/batman/openpilot/system/ui/widgets/network.py:310 +#: system/ui/widgets/network.py:310 #, python-format msgid "Scanning Wi-Fi networks..." msgstr "正在掃描 Wi‑Fi 網路…" -#: /home/batman/openpilot/system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/option_dialog.py:36 #, python-format msgid "Select" msgstr "選取" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:183 +#: selfdrive/ui/layouts/settings/software.py:183 #, python-format msgid "Select a branch" msgstr "選取分支" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:91 +#: selfdrive/ui/layouts/settings/device.py:91 #, python-format msgid "Select a language" msgstr "選取語言" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:60 +#: selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "Serial" msgstr "序號" -#: /home/batman/openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 +#: selfdrive/ui/widgets/offroad_alerts.py:106 #, python-format msgid "Snooze Update" msgstr "延後更新" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:65 +#: selfdrive/ui/layouts/settings/settings.py:65 msgid "Software" msgstr "軟體" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#: selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Standard" msgstr "標準" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:22 +#: selfdrive/ui/layouts/settings/toggles.py:22 msgid "" "Standard is recommended. In aggressive mode, openpilot will follow lead cars " "closer and be more aggressive with the gas and brake. In relaxed mode " @@ -844,102 +829,99 @@ msgstr "" "從容模式下,會與前車保持更遠距離。於支援車款,可用方向盤距離按鈕切換這些風" "格。" -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:59 -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:65 +#: selfdrive/ui/onroad/alert_renderer.py:59 +#: selfdrive/ui/onroad/alert_renderer.py:65 #, python-format msgid "System Unresponsive" msgstr "系統無回應" -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:58 +#: selfdrive/ui/onroad/alert_renderer.py:58 #, python-format msgid "TAKE CONTROL IMMEDIATELY" msgstr "請立刻接手控制" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:71 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:125 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:127 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:129 +#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 +#: selfdrive/ui/layouts/sidebar.py:127 selfdrive/ui/layouts/sidebar.py:129 msgid "TEMP" msgstr "溫度" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:61 +#: selfdrive/ui/layouts/settings/software.py:61 #, python-format msgid "Target Branch" msgstr "目標分支" -#: /home/batman/openpilot/system/ui/widgets/network.py:124 +#: system/ui/widgets/network.py:124 #, python-format msgid "Tethering Password" msgstr "網路共享密碼" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/settings.py:64 +#: selfdrive/ui/layouts/settings/settings.py:64 msgid "Toggles" msgstr "切換" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:72 +#: selfdrive/ui/layouts/settings/software.py:72 #, python-format msgid "UNINSTALL" msgstr "解除安裝" -#: /home/batman/openpilot/selfdrive/ui/layouts/home.py:155 +#: selfdrive/ui/layouts/home.py:155 #, python-format msgid "UPDATE" msgstr "更新" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:72 -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:163 +#: selfdrive/ui/layouts/settings/software.py:72 +#: selfdrive/ui/layouts/settings/software.py:163 #, python-format msgid "Uninstall" msgstr "解除安裝" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:117 +#: selfdrive/ui/layouts/sidebar.py:117 msgid "Unknown" msgstr "未知" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:48 +#: selfdrive/ui/layouts/settings/software.py:48 #, python-format msgid "Updates are only downloaded while the car is off." msgstr "僅在車輛熄火時下載更新。" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:33 +#: selfdrive/ui/widgets/prime.py:33 #, python-format msgid "Upgrade Now" msgstr "立即升級" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +#: selfdrive/ui/layouts/settings/toggles.py:31 msgid "" "Upload data from the driver facing camera and help improve the driver " "monitoring algorithm." msgstr "上傳車內鏡頭資料,協助改善駕駛監控演算法。" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:88 +#: selfdrive/ui/layouts/settings/toggles.py:88 #, python-format msgid "Use Metric System" msgstr "使用公制" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:17 +#: selfdrive/ui/layouts/settings/toggles.py:17 msgid "" "Use the openpilot system for adaptive cruise control and lane keep driver " "assistance. Your attention is required at all times to use this feature." msgstr "" "使用 openpilot 進行 ACC 與車道維持輔助。使用此功能時,您必須始終保持專注。" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:72 -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:144 msgid "VEHICLE" msgstr "車輛" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:67 +#: selfdrive/ui/layouts/settings/device.py:67 #, python-format msgid "VIEW" msgstr "檢視" -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:52 +#: selfdrive/ui/onroad/alert_renderer.py:52 #, python-format msgid "Waiting to start" msgstr "等待開始" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:19 +#: selfdrive/ui/layouts/settings/developer.py:19 msgid "" "Warning: This grants SSH access to all public keys in your GitHub settings. " "Never enter a GitHub username other than your own. A comma employee will " @@ -948,35 +930,35 @@ msgstr "" "警告:這將授予對您 GitHub 設定中所有公開金鑰的 SSH 存取權。請勿輸入非您本人" "的 GitHub 帳號。comma 員工絕不會要求您新增他們的帳號。" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:111 +#: selfdrive/ui/layouts/onboarding.py:111 #, python-format msgid "Welcome to openpilot" msgstr "歡迎使用 openpilot" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:20 +#: selfdrive/ui/layouts/settings/toggles.py:20 msgid "When enabled, pressing the accelerator pedal will disengage openpilot." msgstr "啟用後,踩下加速踏板將會脫離 openpilot。" -#: /home/batman/openpilot/selfdrive/ui/layouts/sidebar.py:44 +#: selfdrive/ui/layouts/sidebar.py:44 msgid "Wi-Fi" msgstr "Wi‑Fi" -#: /home/batman/openpilot/system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:144 #, python-format msgid "Wi-Fi Network Metered" msgstr "Wi‑Fi 計量網路" -#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:314 #, python-format msgid "Wrong password" msgstr "密碼錯誤" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:145 +#: selfdrive/ui/layouts/onboarding.py:145 #, python-format msgid "You must accept the Terms and Conditions in order to use openpilot." msgstr "您必須接受條款與細則才能使用 openpilot。" -#: /home/batman/openpilot/selfdrive/ui/layouts/onboarding.py:112 +#: selfdrive/ui/layouts/onboarding.py:112 #, python-format msgid "" "You must accept the Terms and Conditions to use openpilot. Read the latest " @@ -985,83 +967,82 @@ msgstr "" "您必須接受條款與細則才能使用 openpilot。繼續前請閱讀 https://comma.ai/terms " "上的最新條款。" -#: /home/batman/openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:34 +#: selfdrive/ui/onroad/driver_camera_dialog.py:34 #, python-format msgid "camera starting" msgstr "相機啟動中" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:63 +#: selfdrive/ui/widgets/prime.py:63 #, python-format msgid "comma prime" msgstr "comma prime" -#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:142 #, python-format msgid "default" msgstr "預設" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#: selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid "down" msgstr "下" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:106 +#: selfdrive/ui/layouts/settings/software.py:106 #, python-format msgid "failed to check for update" msgstr "檢查更新失敗" -#: /home/batman/openpilot/system/ui/widgets/network.py:237 -#: /home/batman/openpilot/system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 #, python-format msgid "for \"{}\"" msgstr "適用於「{}」" -#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:177 +#: selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "km/h" msgstr "公里/時" -#: /home/batman/openpilot/system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:204 #, python-format msgid "leave blank for automatic configuration" msgstr "留空以自動設定" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:134 +#: selfdrive/ui/layouts/settings/device.py:134 #, python-format msgid "left" msgstr "左" -#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:142 #, python-format msgid "metered" msgstr "計量" -#: /home/batman/openpilot/selfdrive/ui/onroad/hud_renderer.py:177 +#: selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "mph" msgstr "英里/時" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:20 +#: selfdrive/ui/layouts/settings/software.py:20 #, python-format msgid "never" msgstr "從不" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:31 +#: selfdrive/ui/layouts/settings/software.py:31 #, python-format msgid "now" msgstr "現在" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/developer.py:71 +#: selfdrive/ui/layouts/settings/developer.py:71 #, python-format msgid "openpilot Longitudinal Control (Alpha)" msgstr "openpilot 縱向控制(Alpha)" -#: /home/batman/openpilot/selfdrive/ui/onroad/alert_renderer.py:51 +#: selfdrive/ui/onroad/alert_renderer.py:51 #, python-format msgid "openpilot Unavailable" msgstr "openpilot 無法使用" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:158 +#: selfdrive/ui/layouts/settings/toggles.py:158 #, python-format msgid "" "openpilot defaults to driving in chill mode. Experimental mode enables alpha-" @@ -1083,7 +1064,7 @@ msgstr "" "駕駛視覺化
在低速時,駕駛視覺化將切換至面向道路的廣角鏡頭以更好呈現部" "分轉彎。右上角亦會顯示實驗模式圖示。" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:165 +#: selfdrive/ui/layouts/settings/device.py:165 #, python-format msgid "" "openpilot is continuously calibrating, resetting is rarely required. " @@ -1092,7 +1073,7 @@ msgstr "" "openpilot 會持續校正,通常不需重設。若車輛通電,重設校正將重新啟動 " "openpilot。" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:20 +#: selfdrive/ui/layouts/settings/firehose.py:20 msgid "" "openpilot learns to drive by watching humans, like you, drive.\n" "\n" @@ -1105,88 +1086,88 @@ msgstr "" "Firehose 模式可讓您最大化上傳訓練資料,以改進 openpilot 的駕駛模型。更多資料" "代表更大的模型,也就代表更好的實驗模式。" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/toggles.py:183 +#: selfdrive/ui/layouts/settings/toggles.py:183 #, python-format msgid "openpilot longitudinal control may come in a future update." msgstr "openpilot 縱向控制可能於未來更新提供。" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:26 +#: selfdrive/ui/layouts/settings/device.py:26 msgid "" "openpilot requires the device to be mounted within 4° left or right and " "within 5° up or 9° down." msgstr "openpilot 要求裝置安裝在左右 4°、上 5° 或下 9° 以內。" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:134 +#: selfdrive/ui/layouts/settings/device.py:134 #, python-format msgid "right" msgstr "右" -#: /home/batman/openpilot/system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:142 #, python-format msgid "unmetered" msgstr "不限流量" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/device.py:133 +#: selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid "up" msgstr "上" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:117 +#: selfdrive/ui/layouts/settings/software.py:117 #, python-format msgid "up to date, last checked never" msgstr "已為最新,最後檢查:從未" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:115 +#: selfdrive/ui/layouts/settings/software.py:115 #, python-format msgid "up to date, last checked {}" msgstr "已為最新,最後檢查:{}" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:109 +#: selfdrive/ui/layouts/settings/software.py:109 #, python-format msgid "update available" msgstr "有可用更新" -#: /home/batman/openpilot/selfdrive/ui/layouts/home.py:169 +#: selfdrive/ui/layouts/home.py:169 #, python-format msgid "{} ALERT" msgid_plural "{} ALERTS" msgstr[0] "{} 則警示" msgstr[1] "{} 則警示" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:40 +#: selfdrive/ui/layouts/settings/software.py:40 #, python-format msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "{} 天前" msgstr[1] "{} 天前" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:37 +#: selfdrive/ui/layouts/settings/software.py:37 #, python-format msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "{} 小時前" msgstr[1] "{} 小時前" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/software.py:34 +#: selfdrive/ui/layouts/settings/software.py:34 #, python-format msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "{} 分鐘前" msgstr[1] "{} 分鐘前" -#: /home/batman/openpilot/selfdrive/ui/layouts/settings/firehose.py:111 +#: selfdrive/ui/layouts/settings/firehose.py:111 #, python-format msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." msgstr[0] "目前已有 {} 個您的駕駛片段納入訓練資料集。" msgstr[1] "目前已有 {} 個您的駕駛片段納入訓練資料集。" -#: /home/batman/openpilot/selfdrive/ui/widgets/prime.py:62 +#: selfdrive/ui/widgets/prime.py:62 #, python-format msgid "✓ SUBSCRIBED" msgstr "✓ 已訂閱" -#: /home/batman/openpilot/selfdrive/ui/widgets/setup.py:22 +#: selfdrive/ui/widgets/setup.py:22 #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "🔥 Firehose 模式 🔥" diff --git a/selfdrive/ui/update_translations.py b/selfdrive/ui/update_translations.py index e91820f24..779beb5ba 100755 --- a/selfdrive/ui/update_translations.py +++ b/selfdrive/ui/update_translations.py @@ -1,8 +1,11 @@ #!/usr/bin/env python3 from itertools import chain import os +from openpilot.common.basedir import BASEDIR from openpilot.system.ui.lib.multilang import SYSTEM_UI_DIR, UI_DIR, TRANSLATIONS_DIR, multilang +POT_FILE = os.path.join(TRANSLATIONS_DIR, "app.pot") + def update_translations(): files = [] @@ -12,12 +15,12 @@ def update_translations(): os.walk(os.path.join(UI_DIR, "onroad"))): for filename in filenames: if filename.endswith(".py"): - files.append(os.path.join(root, filename)) + files.append(os.path.relpath(os.path.join(root, filename), BASEDIR)) # Create main translation file cmd = ("xgettext -L Python --keyword=tr --keyword=trn:1,2 --keyword=tr_noop --from-code=UTF-8 " + "--flag=tr:1:python-brace-format --flag=trn:1:python-brace-format --flag=trn:2:python-brace-format " + - "-o translations/app.pot {}").format(" ".join(files)) + f"-D {BASEDIR} -o {POT_FILE} {' '.join(files)}") ret = os.system(cmd) assert ret == 0 @@ -25,11 +28,11 @@ def update_translations(): # Generate/update translation files for each language for name in multilang.languages.values(): if os.path.exists(os.path.join(TRANSLATIONS_DIR, f"app_{name}.po")): - cmd = f"msgmerge --update --no-fuzzy-matching --backup=none --sort-output translations/app_{name}.po translations/app.pot" + cmd = f"msgmerge --update --no-fuzzy-matching --backup=none --sort-output {TRANSLATIONS_DIR}/app_{name}.po {POT_FILE}" ret = os.system(cmd) assert ret == 0 else: - cmd = f"msginit -l {name} --no-translator --input translations/app.pot --output-file translations/app_{name}.po" + cmd = f"msginit -l {name} --no-translator --input {POT_FILE} --output-file {TRANSLATIONS_DIR}/app_{name}.po" ret = os.system(cmd) assert ret == 0 From ab234c72a31af11eb63f9f1d4304db1eaa44a996 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 23 Oct 2025 00:55:31 -0700 Subject: [PATCH 243/910] wait slightly longer to take screenshot --- selfdrive/ui/tests/test_ui/raylib_screenshots.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py index 29fae5b61..3080b55b9 100755 --- a/selfdrive/ui/tests/test_ui/raylib_screenshots.py +++ b/selfdrive/ui/tests/test_ui/raylib_screenshots.py @@ -25,7 +25,7 @@ AlertStatus = log.SelfdriveState.AlertStatus TEST_DIR = pathlib.Path(__file__).parent TEST_OUTPUT_DIR = TEST_DIR / "raylib_report" SCREENSHOTS_DIR = TEST_OUTPUT_DIR / "screenshots" -UI_DELAY = 0.2 +UI_DELAY = 0.5 BRANCH_NAME = "this-is-a-really-super-mega-ultra-max-extreme-ultimate-long-branch-name" VERSION = f"0.10.1 / {BRANCH_NAME} / 7864838 / Oct 03" From 6486ab6cab719be265fb022082eec5014b645792 Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Thu, 23 Oct 2025 17:09:14 -0500 Subject: [PATCH 244/910] raylib: fix crash when toggling advanced network toggles (#36443) use get_state() --- system/ui/widgets/network.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/system/ui/widgets/network.py b/system/ui/widgets/network.py index e705c55f7..e9d7a1b09 100644 --- a/system/ui/widgets/network.py +++ b/system/ui/widgets/network.py @@ -175,14 +175,14 @@ class AdvancedNetworkSettings(Widget): self._wifi_metered_action.selected_button = int(metered) if metered in (MeteredType.UNKNOWN, MeteredType.YES, MeteredType.NO) else 0 def _toggle_tethering(self): - checked = self._tethering_action.state + checked = self._tethering_action.get_state() self._tethering_action.set_enabled(False) if checked: self._wifi_metered_action.set_enabled(False) self._wifi_manager.set_tethering_active(checked) def _toggle_roaming(self): - roaming_state = self._roaming_action.state + roaming_state = self._roaming_action.get_state() self._params.put_bool("GsmRoaming", roaming_state) self._wifi_manager.update_gsm_settings(roaming_state, self._params.get("GsmApn") or "", self._params.get_bool("GsmMetered")) @@ -206,7 +206,7 @@ class AdvancedNetworkSettings(Widget): gui_app.set_modal_overlay(self._keyboard, update_apn) def _toggle_cellular_metered(self): - metered = self._cellular_metered_action.state + metered = self._cellular_metered_action.get_state() self._params.put_bool("GsmMetered", metered) self._wifi_manager.update_gsm_settings(self._params.get_bool("GsmRoaming"), self._params.get("GsmApn") or "", metered) From dc889587ced887e8f0380a6adc8ce12797eef3fa Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Thu, 23 Oct 2025 22:25:54 -0700 Subject: [PATCH 245/910] bump version to 0.10.2 --- RELEASES.md | 3 +++ common/version.h | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/RELEASES.md b/RELEASES.md index 558d32209..9ab60e6cb 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,3 +1,6 @@ +Version 0.10.2 (2025-11-23) +======================== + Version 0.10.1 (2025-09-08) ======================== * New driving model #36276 diff --git a/common/version.h b/common/version.h index 669f30321..ef2067078 100644 --- a/common/version.h +++ b/common/version.h @@ -1 +1 @@ -#define COMMA_VERSION "0.10.1" +#define COMMA_VERSION "0.10.2" From 53ff5413cda11d15ec412a103c0472b29063702c Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Fri, 24 Oct 2025 23:50:32 +0800 Subject: [PATCH 246/910] ui: auto-size on PC if screen is smaller than tici (#36452) auto-scale on PC to fit screen --- system/ui/lib/application.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 68e1fdc44..4ed59f194 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -140,7 +140,12 @@ class GuiApplication: self._fonts: dict[FontWeight, rl.Font] = {} self._width = width self._height = height - self._scale = SCALE + + if PC and SCALE == 1.0: + self._scale = self._calculate_auto_scale() + else: + 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 @@ -460,5 +465,17 @@ class GuiApplication: cloudlog.error(f"FPS dropped critically below {fps}. Shutting down UI.") os._exit(1) + def _calculate_auto_scale(self) -> float: + # Create temporary window to query monitor info + rl.init_window(1, 1, "") + w, h = rl.get_monitor_width(0), rl.get_monitor_height(0) + rl.close_window() + + if w == 0 or h == 0 or (w >= self._width and h >= self._height): + return 1.0 + + # Apply 0.95 factor for window decorations/taskbar margin + return max(0.3, min(w / self._width, h / self._height) * 0.95) + gui_app = GuiApplication(2160, 1080) From 40a1af97b91f372f27f37d715d790cc5eaf183ec Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Fri, 24 Oct 2025 21:12:45 -0500 Subject: [PATCH 247/910] ui: only auto scale on PC if SCALE env not set (#36455) only use auto scale if SCALE env not set --- system/ui/lib/application.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 4ed59f194..54a18d96d 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -141,7 +141,7 @@ class GuiApplication: self._width = width self._height = height - if PC and SCALE == 1.0: + if PC and os.getenv("SCALE") is None: self._scale = self._calculate_auto_scale() else: self._scale = SCALE From f2db7f7665426d93060ecb3f314187bfccd02833 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Sat, 25 Oct 2025 10:13:34 +0800 Subject: [PATCH 248/910] ui: cache emoji font to avoid repeated loading (#36451) cache font to avoid repleated loads --- system/ui/lib/emoji.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/system/ui/lib/emoji.py b/system/ui/lib/emoji.py index 54f742952..37228e2d4 100644 --- a/system/ui/lib/emoji.py +++ b/system/ui/lib/emoji.py @@ -6,6 +6,7 @@ import pyray as rl from openpilot.system.ui.lib.application import FONT_DIR +_emoji_font: ImageFont.FreeTypeFont | None = None _cache: dict[str, rl.Texture] = {} EMOJI_REGEX = re.compile( @@ -32,6 +33,12 @@ EMOJI_REGEX = re.compile( flags=re.UNICODE ) +def _load_emoji_font() -> ImageFont.FreeTypeFont | None: + global _emoji_font + if _emoji_font is None: + _emoji_font = ImageFont.truetype(str(FONT_DIR.joinpath("NotoColorEmoji.ttf")), 109) + return _emoji_font + def find_emoji(text): return [(m.start(), m.end(), m.group()) for m in EMOJI_REGEX.finditer(text)] @@ -39,8 +46,7 @@ def emoji_tex(emoji): if emoji not in _cache: img = Image.new("RGBA", (128, 128), (0, 0, 0, 0)) draw = ImageDraw.Draw(img) - font = ImageFont.truetype(FONT_DIR.joinpath("NotoColorEmoji.ttf"), 109) - draw.text((0, 0), emoji, font=font, embedded_color=True) + draw.text((0, 0), emoji, font=_load_emoji_font(), embedded_color=True) with io.BytesIO() as buffer: img.save(buffer, format="PNG") l = buffer.tell() From 7da36b2470a4c575dcdc41d4d161db1564974bd1 Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Fri, 24 Oct 2025 21:15:14 -0500 Subject: [PATCH 249/910] multilang: fix missing translations in developer panel (#36445) fix: translate description strings in DeveloperLayout settings --- selfdrive/ui/layouts/settings/developer.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/selfdrive/ui/layouts/settings/developer.py b/selfdrive/ui/layouts/settings/developer.py index 4b8f08796..0419729a4 100644 --- a/selfdrive/ui/layouts/settings/developer.py +++ b/selfdrive/ui/layouts/settings/developer.py @@ -37,7 +37,7 @@ class DeveloperLayout(Widget): # Build items and keep references for callbacks/state updates self._adb_toggle = toggle_item( lambda: tr("Enable ADB"), - description=lambda: DESCRIPTIONS["enable_adb"], + description=lambda: tr(DESCRIPTIONS["enable_adb"]), initial_state=self._params.get_bool("AdbEnabled"), callback=self._on_enable_adb, enabled=ui_state.is_offroad, @@ -50,7 +50,7 @@ class DeveloperLayout(Widget): initial_state=self._params.get_bool("SshEnabled"), callback=self._on_enable_ssh, ) - self._ssh_keys = ssh_key_item(lambda: tr("SSH Keys"), description=lambda: DESCRIPTIONS["ssh_key"]) + self._ssh_keys = ssh_key_item(lambda: tr("SSH Keys"), description=lambda: tr(DESCRIPTIONS["ssh_key"])) self._joystick_toggle = toggle_item( lambda: tr("Joystick Debug Mode"), @@ -69,7 +69,7 @@ class DeveloperLayout(Widget): self._alpha_long_toggle = toggle_item( lambda: tr("openpilot Longitudinal Control (Alpha)"), - description=lambda: DESCRIPTIONS["alpha_longitudinal"], + description=lambda: tr(DESCRIPTIONS["alpha_longitudinal"]), initial_state=self._params.get_bool("AlphaLongitudinalEnabled"), callback=self._on_alpha_long_enabled, enabled=lambda: not ui_state.engaged, From 534f096bb84aab7ea5c5bcb7811a60d7fe6cb272 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Sat, 25 Oct 2025 10:27:03 +0800 Subject: [PATCH 250/910] ui: reset cached height when description changes (#36454) reset cached height when description changes --- system/ui/widgets/list_view.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/system/ui/widgets/list_view.py b/system/ui/widgets/list_view.py index e5f234ed3..ed087c3f1 100644 --- a/system/ui/widgets/list_view.py +++ b/system/ui/widgets/list_view.py @@ -330,6 +330,8 @@ class ListItem(Widget): # do callback first in case receiver changes description if self.description_visible and self.description_opened_callback is not None: self.description_opened_callback() + # Call _update_state to catch any description changes + self._update_state() content_width = int(self._rect.width - ITEM_PADDING * 2) self._rect.height = self.get_item_height(self._font, content_width) From c8c1b0f7819ee7c1adfc7fc47df65529c361607d Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Sat, 25 Oct 2025 10:27:33 +0800 Subject: [PATCH 251/910] ui: clear available camera streams after going offroad (#36441) clear available camera streams after going offroad --- selfdrive/ui/onroad/cameraview.py | 1 + 1 file changed, 1 insertion(+) diff --git a/selfdrive/ui/onroad/cameraview.py b/selfdrive/ui/onroad/cameraview.py index 5098b6a06..98dd00eba 100644 --- a/selfdrive/ui/onroad/cameraview.py +++ b/selfdrive/ui/onroad/cameraview.py @@ -114,6 +114,7 @@ class CameraView(Widget): # which drains the VisionIpcClient SubSocket for us. Re-connecting is not enough # and only clears internal buffers, not the message queue. self.frame = None + self.available_streams.clear() if self.client: del self.client self.client = VisionIpcClient(self._name, self._stream_type, conflate=True) From ad903aeaa13978ee95a96aeb628cd1bb27f473c2 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Sat, 25 Oct 2025 10:28:58 +0800 Subject: [PATCH 252/910] ui: simplify draw_border (#36440) simplify draw_border --- selfdrive/ui/onroad/augmented_road_view.py | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/selfdrive/ui/onroad/augmented_road_view.py b/selfdrive/ui/onroad/augmented_road_view.py index 661496a03..05e2f094a 100644 --- a/selfdrive/ui/onroad/augmented_road_view.py +++ b/selfdrive/ui/onroad/augmented_road_view.py @@ -112,26 +112,13 @@ class AugmentedRoadView(CameraView): pass def _draw_border(self, rect: rl.Rectangle): - rl.begin_scissor_mode(int(rect.x), int(rect.y), int(rect.width), int(rect.height)) + rl.draw_rectangle_lines_ex(rect, UI_BORDER_SIZE, rl.BLACK) border_roundness = 0.12 border_color = BORDER_COLORS.get(ui_state.status, BORDER_COLORS[UIStatus.DISENGAGED]) border_rect = rl.Rectangle(rect.x + UI_BORDER_SIZE, rect.y + UI_BORDER_SIZE, rect.width - 2 * UI_BORDER_SIZE, rect.height - 2 * UI_BORDER_SIZE) rl.draw_rectangle_rounded_lines_ex(border_rect, border_roundness, 10, UI_BORDER_SIZE, border_color) - # black bg around colored border - black_bg_thickness = UI_BORDER_SIZE - black_bg_rect = rl.Rectangle( - border_rect.x - UI_BORDER_SIZE, - border_rect.y - UI_BORDER_SIZE, - border_rect.width + 2 * UI_BORDER_SIZE, - border_rect.height + 2 * UI_BORDER_SIZE, - ) - edge_offset = (black_bg_rect.height - border_rect.height) / 2 # distance between rect edges - roundness_out = (border_roundness * border_rect.height + 2 * edge_offset) / max(1.0, black_bg_rect.height) - rl.draw_rectangle_rounded_lines_ex(black_bg_rect, roundness_out, 10, black_bg_thickness, rl.BLACK) - rl.end_scissor_mode() - def _switch_stream_if_needed(self, sm): if sm['selfdriveState'].experimentalMode and WIDE_CAM in self.available_streams: v_ego = sm['carState'].vEgo From 6061476d8e14e0a6f6646074ef88a7b55c536cb2 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 24 Oct 2025 19:43:46 -0700 Subject: [PATCH 253/910] fix spinner (#36458) --- system/ui/lib/multilang.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/system/ui/lib/multilang.py b/system/ui/lib/multilang.py index 03d519e60..bb9461864 100644 --- a/system/ui/lib/multilang.py +++ b/system/ui/lib/multilang.py @@ -1,10 +1,14 @@ import os import json import gettext -from openpilot.common.params import Params from openpilot.common.basedir import BASEDIR from openpilot.common.swaglog import cloudlog +try: + from openpilot.common.params import Params +except ImportError: + Params = None + SYSTEM_UI_DIR = os.path.join(BASEDIR, "system", "ui") UI_DIR = os.path.join(BASEDIR, "selfdrive", "ui") TRANSLATIONS_DIR = os.path.join(UI_DIR, "translations") @@ -22,7 +26,7 @@ UNIFONT_LANGUAGES = [ class Multilang: def __init__(self): - self._params = Params() + self._params = Params() if Params is not None else None self._language: str = "en" self.languages = {} self.codes = {} @@ -66,9 +70,10 @@ class Multilang: self.languages = json.load(f) self.codes = {v: k for k, v in self.languages.items()} - lang = str(self._params.get("LanguageSetting")).removeprefix("main_") - if lang in self.codes: - self._language = lang + if self._params is not None: + lang = str(self._params.get("LanguageSetting")).removeprefix("main_") + if lang in self.codes: + self._language = lang multilang = Multilang() From 954b567b9ba0f3d1ae57d6aa7797fa86dd92ec6e Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 24 Oct 2025 20:45:56 -0700 Subject: [PATCH 254/910] merge a bunch of misc stuff into common.utils (#36463) just utils --- common/dict_helpers.py | 9 --- common/git.py | 2 +- common/retry.py | 30 --------- common/run.py | 28 -------- common/tests/test_file_helpers.py | 2 +- common/{file_helpers.py => utils.py} | 64 ++++++++++++++++++- selfdrive/debug/qlog_size.py | 2 +- .../pandad/tests/test_pandad_loopback.py | 2 +- selfdrive/ui/soundd.py | 2 +- system/athena/athenad.py | 2 +- system/hardware/hardwared.py | 2 +- system/loggerd/uploader.py | 2 +- system/micd.py | 2 +- system/qcomgpsd/qcomgpsd.py | 2 +- system/statsd.py | 2 +- system/ui/setup.py | 2 +- tools/clip/run.py | 2 +- tools/lib/filereader.py | 2 +- tools/lib/url_file.py | 2 +- 19 files changed, 77 insertions(+), 84 deletions(-) delete mode 100644 common/dict_helpers.py delete mode 100644 common/retry.py delete mode 100644 common/run.py rename common/{file_helpers.py => utils.py} (51%) diff --git a/common/dict_helpers.py b/common/dict_helpers.py deleted file mode 100644 index 62cff63b5..000000000 --- a/common/dict_helpers.py +++ /dev/null @@ -1,9 +0,0 @@ -# remove all keys that end in DEPRECATED -def strip_deprecated_keys(d): - for k in list(d.keys()): - if isinstance(k, str): - if k.endswith('DEPRECATED'): - d.pop(k) - elif isinstance(d[k], dict): - strip_deprecated_keys(d[k]) - return d diff --git a/common/git.py b/common/git.py index 4406bf96b..2296fa708 100644 --- a/common/git.py +++ b/common/git.py @@ -1,6 +1,6 @@ from functools import cache import subprocess -from openpilot.common.run import run_cmd, run_cmd_default +from openpilot.common.utils import run_cmd, run_cmd_default @cache diff --git a/common/retry.py b/common/retry.py deleted file mode 100644 index 9bd4ac952..000000000 --- a/common/retry.py +++ /dev/null @@ -1,30 +0,0 @@ -import time -import functools - -from openpilot.common.swaglog import cloudlog - - -def retry(attempts=3, delay=1.0, ignore_failure=False): - def decorator(func): - @functools.wraps(func) - def wrapper(*args, **kwargs): - for _ in range(attempts): - try: - return func(*args, **kwargs) - except Exception: - cloudlog.exception(f"{func.__name__} failed, trying again") - time.sleep(delay) - - if ignore_failure: - cloudlog.error(f"{func.__name__} failed after retry") - else: - raise Exception(f"{func.__name__} failed after retry") - return wrapper - return decorator - - -if __name__ == "__main__": - @retry(attempts=10) - def abc(): - raise ValueError("abc failed :(") - abc() diff --git a/common/run.py b/common/run.py deleted file mode 100644 index 75395ead1..000000000 --- a/common/run.py +++ /dev/null @@ -1,28 +0,0 @@ -import subprocess -from contextlib import contextmanager -from subprocess import Popen, PIPE, TimeoutExpired - - -def run_cmd(cmd: list[str], cwd=None, env=None) -> str: - return subprocess.check_output(cmd, encoding='utf8', cwd=cwd, env=env).strip() - - -def run_cmd_default(cmd: list[str], default: str = "", cwd=None, env=None) -> str: - try: - return run_cmd(cmd, cwd=cwd, env=env) - except subprocess.CalledProcessError: - return default - - -@contextmanager -def managed_proc(cmd: list[str], env: dict[str, str]): - proc = Popen(cmd, env=env, stdout=PIPE, stderr=PIPE) - try: - yield proc - finally: - if proc.poll() is None: - proc.terminate() - try: - proc.wait(timeout=5) - except TimeoutExpired: - proc.kill() diff --git a/common/tests/test_file_helpers.py b/common/tests/test_file_helpers.py index a9977c236..c7fe1984c 100644 --- a/common/tests/test_file_helpers.py +++ b/common/tests/test_file_helpers.py @@ -1,7 +1,7 @@ import os from uuid import uuid4 -from openpilot.common.file_helpers import atomic_write_in_dir +from openpilot.common.utils import atomic_write_in_dir class TestFileHelpers: diff --git a/common/file_helpers.py b/common/utils.py similarity index 51% rename from common/file_helpers.py rename to common/utils.py index b0d889f16..89c0601f0 100644 --- a/common/file_helpers.py +++ b/common/utils.py @@ -2,9 +2,14 @@ import io import os import tempfile import contextlib +import subprocess +import time +import functools +from subprocess import Popen, PIPE, TimeoutExpired import zstandard as zstd +from openpilot.common.swaglog import cloudlog -LOG_COMPRESSION_LEVEL = 10 # little benefit up to level 15. level ~17 is a small step change +LOG_COMPRESSION_LEVEL = 10 # little benefit up to level 15. level ~17 is a small step change class CallbackReader: @@ -27,7 +32,7 @@ class CallbackReader: @contextlib.contextmanager -def atomic_write_in_dir(path: str, mode: str = 'w', buffering: int = -1, encoding: str = None, newline: str = None, +def atomic_write_in_dir(path: str, mode: str = 'w', buffering: int = -1, encoding: str | None = None, newline: str | None = None, overwrite: bool = False): """Write to a file atomically using a temporary file in the same directory as the destination file.""" dir_name = os.path.dirname(path) @@ -56,3 +61,58 @@ def get_upload_stream(filepath: str, should_compress: bool) -> tuple[io.Buffered compressed_size = compressed_stream.tell() compressed_stream.seek(0) return compressed_stream, compressed_size + + +# remove all keys that end in DEPRECATED +def strip_deprecated_keys(d): + for k in list(d.keys()): + if isinstance(k, str): + if k.endswith('DEPRECATED'): + d.pop(k) + elif isinstance(d[k], dict): + strip_deprecated_keys(d[k]) + return d + + +def run_cmd(cmd: list[str], cwd=None, env=None) -> str: + return subprocess.check_output(cmd, encoding='utf8', cwd=cwd, env=env).strip() + + +def run_cmd_default(cmd: list[str], default: str = "", cwd=None, env=None) -> str: + try: + return run_cmd(cmd, cwd=cwd, env=env) + except subprocess.CalledProcessError: + return default + + +@contextlib.contextmanager +def managed_proc(cmd: list[str], env: dict[str, str]): + proc = Popen(cmd, env=env, stdout=PIPE, stderr=PIPE) + try: + yield proc + finally: + if proc.poll() is None: + proc.terminate() + try: + proc.wait(timeout=5) + except TimeoutExpired: + proc.kill() + + +def retry(attempts=3, delay=1.0, ignore_failure=False): + def decorator(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + for _ in range(attempts): + try: + return func(*args, **kwargs) + except Exception: + cloudlog.exception(f"{func.__name__} failed, trying again") + time.sleep(delay) + + if ignore_failure: + cloudlog.error(f"{func.__name__} failed after retry") + else: + raise Exception(f"{func.__name__} failed after retry") + return wrapper + return decorator diff --git a/selfdrive/debug/qlog_size.py b/selfdrive/debug/qlog_size.py index 6d494b6f7..2b54cfeeb 100755 --- a/selfdrive/debug/qlog_size.py +++ b/selfdrive/debug/qlog_size.py @@ -6,7 +6,7 @@ from collections import defaultdict import matplotlib.pyplot as plt from cereal.services import SERVICE_LIST -from openpilot.common.file_helpers import LOG_COMPRESSION_LEVEL +from openpilot.common.utils import LOG_COMPRESSION_LEVEL from openpilot.tools.lib.logreader import LogReader from tqdm import tqdm diff --git a/selfdrive/pandad/tests/test_pandad_loopback.py b/selfdrive/pandad/tests/test_pandad_loopback.py index bf1c55712..eff70d254 100644 --- a/selfdrive/pandad/tests/test_pandad_loopback.py +++ b/selfdrive/pandad/tests/test_pandad_loopback.py @@ -9,7 +9,7 @@ from pprint import pprint import cereal.messaging as messaging from cereal import car, log from opendbc.car.can_definitions import CanData -from openpilot.common.retry import retry +from openpilot.common.utils import retry from openpilot.common.params import Params from openpilot.common.timeout import Timeout from openpilot.selfdrive.pandad import can_list_to_can_capnp diff --git a/selfdrive/ui/soundd.py b/selfdrive/ui/soundd.py index 44c463b18..44116a232 100644 --- a/selfdrive/ui/soundd.py +++ b/selfdrive/ui/soundd.py @@ -8,7 +8,7 @@ from cereal import car, messaging from openpilot.common.basedir import BASEDIR from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.realtime import Ratekeeper -from openpilot.common.retry import retry +from openpilot.common.utils import retry from openpilot.common.swaglog import cloudlog from openpilot.system import micd diff --git a/system/athena/athenad.py b/system/athena/athenad.py index 6ed53b759..50c4f5408 100755 --- a/system/athena/athenad.py +++ b/system/athena/athenad.py @@ -31,7 +31,7 @@ import cereal.messaging as messaging from cereal import log from cereal.services import SERVICE_LIST from openpilot.common.api import Api -from openpilot.common.file_helpers import CallbackReader, get_upload_stream +from openpilot.common.utils import CallbackReader, get_upload_stream from openpilot.common.params import Params from openpilot.common.realtime import set_core_affinity from openpilot.system.hardware import HARDWARE, PC diff --git a/system/hardware/hardwared.py b/system/hardware/hardwared.py index 1048acfe0..1dce99273 100755 --- a/system/hardware/hardwared.py +++ b/system/hardware/hardwared.py @@ -12,7 +12,7 @@ import psutil import cereal.messaging as messaging from cereal import log from cereal.services import SERVICE_LIST -from openpilot.common.dict_helpers import strip_deprecated_keys +from openpilot.common.utils import strip_deprecated_keys from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.params import Params from openpilot.common.realtime import DT_HW diff --git a/system/loggerd/uploader.py b/system/loggerd/uploader.py index bc1957250..5b6234e1d 100755 --- a/system/loggerd/uploader.py +++ b/system/loggerd/uploader.py @@ -12,7 +12,7 @@ from collections.abc import Iterator from cereal import log import cereal.messaging as messaging from openpilot.common.api import Api -from openpilot.common.file_helpers import get_upload_stream +from openpilot.common.utils import get_upload_stream from openpilot.common.params import Params from openpilot.common.realtime import set_core_affinity from openpilot.system.hardware.hw import Paths diff --git a/system/micd.py b/system/micd.py index b3558a15a..9b3ccc8d2 100755 --- a/system/micd.py +++ b/system/micd.py @@ -5,7 +5,7 @@ import threading from cereal import messaging from openpilot.common.realtime import Ratekeeper -from openpilot.common.retry import retry +from openpilot.common.utils import retry from openpilot.common.swaglog import cloudlog RATE = 10 diff --git a/system/qcomgpsd/qcomgpsd.py b/system/qcomgpsd/qcomgpsd.py index 819b17f11..59f5ac0b5 100755 --- a/system/qcomgpsd/qcomgpsd.py +++ b/system/qcomgpsd/qcomgpsd.py @@ -16,7 +16,7 @@ from struct import unpack_from, calcsize, pack from cereal import log import cereal.messaging as messaging from openpilot.common.gpio import gpio_init, gpio_set -from openpilot.common.retry import retry +from openpilot.common.utils import retry from openpilot.common.time_helpers import system_time_valid from openpilot.system.hardware.tici.pins import GPIO from openpilot.common.swaglog import cloudlog diff --git a/system/statsd.py b/system/statsd.py index d60064fc9..c4216f5e7 100755 --- a/system/statsd.py +++ b/system/statsd.py @@ -13,7 +13,7 @@ from cereal.messaging import SubMaster from openpilot.system.hardware.hw import Paths from openpilot.common.swaglog import cloudlog from openpilot.system.hardware import HARDWARE -from openpilot.common.file_helpers import atomic_write_in_dir +from openpilot.common.utils import atomic_write_in_dir from openpilot.system.version import get_build_metadata from openpilot.system.loggerd.config import STATS_DIR_FILE_LIMIT, STATS_SOCKET, STATS_FLUSH_TIME_S diff --git a/system/ui/setup.py b/system/ui/setup.py index 5dbf59748..da8c8d81f 100755 --- a/system/ui/setup.py +++ b/system/ui/setup.py @@ -11,7 +11,7 @@ import shutil import pyray as rl from cereal import log -from openpilot.common.run import run_cmd +from openpilot.common.utils import run_cmd from openpilot.system.hardware import HARDWARE from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE diff --git a/tools/clip/run.py b/tools/clip/run.py index 8fa0e8eda..9045a4381 100755 --- a/tools/clip/run.py +++ b/tools/clip/run.py @@ -17,7 +17,7 @@ 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.common.run import managed_proc +from openpilot.common.utils import managed_proc from openpilot.tools.lib.route import Route from openpilot.tools.lib.logreader import LogReader diff --git a/tools/lib/filereader.py b/tools/lib/filereader.py index 02f5fd1b9..ee9ee294b 100644 --- a/tools/lib/filereader.py +++ b/tools/lib/filereader.py @@ -2,7 +2,7 @@ import os import posixpath import socket from functools import cache -from openpilot.common.retry import retry +from openpilot.common.utils import retry from urllib.parse import urlparse from openpilot.tools.lib.url_file import URLFile diff --git a/tools/lib/url_file.py b/tools/lib/url_file.py index e80ba1399..31c1e0ff1 100644 --- a/tools/lib/url_file.py +++ b/tools/lib/url_file.py @@ -7,7 +7,7 @@ from urllib3 import PoolManager, Retry from urllib3.response import BaseHTTPResponse from urllib3.util import Timeout -from openpilot.common.file_helpers import atomic_write_in_dir +from openpilot.common.utils import atomic_write_in_dir from openpilot.system.hardware.hw import Paths # Cache chunk size From 17152484c2f3e41a9ae892a83bf27761552ceb0e Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 24 Oct 2025 20:54:13 -0700 Subject: [PATCH 255/910] selfdrive_tests -> tests --- .github/workflows/{selfdrive_tests.yaml => tests.yaml} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename .github/workflows/{selfdrive_tests.yaml => tests.yaml} (97%) diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/tests.yaml similarity index 97% rename from .github/workflows/selfdrive_tests.yaml rename to .github/workflows/tests.yaml index 3f60fab73..5ca020424 100644 --- a/.github/workflows/selfdrive_tests.yaml +++ b/.github/workflows/tests.yaml @@ -1,4 +1,4 @@ -name: selfdrive +name: tests on: push: @@ -14,7 +14,7 @@ on: type: string concurrency: - group: selfdrive-tests-ci-run-${{ inputs.run_number }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && github.run_id || github.head_ref || github.ref }}-${{ github.workflow }}-${{ github.event_name }} + group: tests-ci-run-${{ inputs.run_number }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && github.run_id || github.head_ref || github.ref }}-${{ github.workflow }}-${{ github.event_name }} cancel-in-progress: true env: From 538ec25ad97079a186aa537927e286212751b76d Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 24 Oct 2025 21:07:04 -0700 Subject: [PATCH 256/910] gc unused stuff in HW abstraction layer (#36465) * gc unused stuff in HW abstraction layer * lil more --- system/hardware/base.h | 8 -------- system/hardware/tici/hardware.h | 19 ------------------- tools/replay/framereader.cc | 2 +- 3 files changed, 1 insertion(+), 28 deletions(-) diff --git a/system/hardware/base.h b/system/hardware/base.h index df9700a01..3eded659a 100644 --- a/system/hardware/base.h +++ b/system/hardware/base.h @@ -10,7 +10,6 @@ // no-op base hw class class HardwareNone { public: - static std::string get_os_version() { return ""; } static std::string get_name() { return ""; } static cereal::InitData::DeviceType get_device_type() { return cereal::InitData::DeviceType::UNKNOWN; } static int get_voltage() { return 0; } @@ -22,14 +21,7 @@ public: return {}; } - static void reboot() {} - static void poweroff() {} - static void set_brightness(int percent) {} static void set_ir_power(int percentage) {} - static void set_display_power(bool on) {} - - static bool get_ssh_enabled() { return false; } - static void set_ssh_enabled(bool enabled) {} static bool PC() { return false; } static bool TICI() { return false; } diff --git a/system/hardware/tici/hardware.h b/system/hardware/tici/hardware.h index ed8a7e7d1..8a0c06694 100644 --- a/system/hardware/tici/hardware.h +++ b/system/hardware/tici/hardware.h @@ -13,12 +13,6 @@ class HardwareTici : public HardwareNone { public: - static bool TICI() { return true; } - static bool AGNOS() { return true; } - static std::string get_os_version() { - return "AGNOS " + util::read_file("/VERSION"); - } - static std::string get_name() { std::string model = util::read_file("/sys/firmware/devicetree/base/model"); return util::strip(model.substr(std::string("comma ").size())); @@ -56,16 +50,6 @@ public: return serial; } - static void reboot() { std::system("sudo reboot"); } - static void poweroff() { std::system("sudo poweroff"); } - static void set_brightness(int percent) { - float max = std::stof(util::read_file("/sys/class/backlight/panel0-backlight/max_brightness")); - std::ofstream("/sys/class/backlight/panel0-backlight/brightness") << int(percent * (max / 100.0f)) << "\n"; - } - static void set_display_power(bool on) { - std::ofstream("/sys/class/backlight/panel0-backlight/bl_power") << (on ? "0" : "4") << "\n"; - } - static void set_ir_power(int percent) { auto device = get_device_type(); if (device == cereal::InitData::DeviceType::TICI || @@ -104,7 +88,4 @@ public: return ret; } - - static bool get_ssh_enabled() { return Params().getBool("SshEnabled"); } - static void set_ssh_enabled(bool enabled) { Params().putBool("SshEnabled", enabled); } }; diff --git a/tools/replay/framereader.cc b/tools/replay/framereader.cc index e9cd09044..a5f0f748c 100644 --- a/tools/replay/framereader.cc +++ b/tools/replay/framereader.cc @@ -40,7 +40,7 @@ struct DecoderManager { std::unique_ptr decoder; #ifndef __APPLE__ - if (Hardware::TICI() && hw_decoder) { + if (!Hardware::PC() && hw_decoder) { decoder = std::make_unique(); } else #endif From c1cb971bcaf622681aefd1fc87f95f808d758821 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 24 Oct 2025 21:34:37 -0700 Subject: [PATCH 257/910] hardwared: disable power save when screen is on (#36466) --- system/hardware/hardwared.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/system/hardware/hardwared.py b/system/hardware/hardwared.py index 1dce99273..4d52d7841 100755 --- a/system/hardware/hardwared.py +++ b/system/hardware/hardwared.py @@ -195,6 +195,7 @@ def hardware_thread(end_event, hw_queue) -> None: should_start_prev = False in_car = False engaged_prev = False + pwrsave = False offroad_cycle_count = 0 params = Params() @@ -341,7 +342,6 @@ def hardware_thread(end_event, hw_queue) -> None: if should_start != should_start_prev or (count == 0): params.put_bool("IsEngaged", False) engaged_prev = False - HARDWARE.set_power_save(not should_start) if sm.updated['selfdriveState']: engaged = sm['selfdriveState'].enabled @@ -355,6 +355,11 @@ def hardware_thread(end_event, hw_queue) -> None: except Exception: pass + should_pwrsave = not onroad_conditions["ignition"] and msg.deviceState.screenBrightnessPercent < 1e-3 + if should_pwrsave != pwrsave or (count == 0): + HARDWARE.set_power_save(should_pwrsave) + pwrsave = should_pwrsave + if should_start: off_ts = None if started_ts is None: From 7909716c1faa077a0fe6e2fabfc852d1b57f4410 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 24 Oct 2025 21:57:45 -0700 Subject: [PATCH 258/910] ui: realtime scheduling (#36467) * ui: realtime scheduling * try this * update cpu --------- Co-authored-by: Comma Device --- selfdrive/test/test_onroad.py | 2 +- selfdrive/ui/ui.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/selfdrive/test/test_onroad.py b/selfdrive/test/test_onroad.py index 0c3bdea4c..a84248385 100644 --- a/selfdrive/test/test_onroad.py +++ b/selfdrive/test/test_onroad.py @@ -42,7 +42,7 @@ PROCS = { "./encoderd": 13.0, "./camerad": 10.0, "selfdrive.controls.plannerd": 8.0, - "selfdrive.ui.ui": 24.0, + "selfdrive.ui.ui": 63.0, "system.sensord.sensord": 13.0, "selfdrive.controls.radard": 2.0, "selfdrive.modeld.modeld": 22.0, diff --git a/selfdrive/ui/ui.py b/selfdrive/ui/ui.py index 3eb72ec10..f2ca4e770 100755 --- a/selfdrive/ui/ui.py +++ b/selfdrive/ui/ui.py @@ -1,13 +1,14 @@ #!/usr/bin/env python3 import pyray as rl + +from openpilot.common.realtime import config_realtime_process from openpilot.system.ui.lib.application import gui_app from openpilot.selfdrive.ui.layouts.main import MainLayout from openpilot.selfdrive.ui.ui_state import ui_state def main(): - # TODO: https://github.com/commaai/agnos-builder/pull/490 - # os.nice(-20) + config_realtime_process([1, 2], 1) gui_app.init_window("UI") main_layout = MainLayout() From fa373af9b5992a125cd97fd1d1f4767ac8e94604 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Sat, 25 Oct 2025 13:00:38 +0800 Subject: [PATCH 259/910] ui: cleanup .gitignore (#36471) cleanup .gitignore --- selfdrive/ui/.gitignore | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/selfdrive/ui/.gitignore b/selfdrive/ui/.gitignore index 5e7b02a1a..945928f61 100644 --- a/selfdrive/ui/.gitignore +++ b/selfdrive/ui/.gitignore @@ -1,14 +1 @@ -moc_* -*.moc - -translations/test_en.* - -ui -mui -watch3 installer/installers/* -qt/setup/setup -qt/setup/reset -qt/setup/wifi -qt/setup/updater -translations/alerts_generated.h From 2beb0ffad118a9e4cbe33a440fb43fd2ff00e66b Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Sat, 25 Oct 2025 13:00:45 +0800 Subject: [PATCH 260/910] ui: move INF_POINT to constant (#36470) move INF_POINT to constant --- selfdrive/ui/onroad/augmented_road_view.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/onroad/augmented_road_view.py b/selfdrive/ui/onroad/augmented_road_view.py index 05e2f094a..fa0528444 100644 --- a/selfdrive/ui/onroad/augmented_road_view.py +++ b/selfdrive/ui/onroad/augmented_road_view.py @@ -27,6 +27,7 @@ BORDER_COLORS = { WIDE_CAM_MAX_SPEED = 10.0 # m/s (22 mph) ROAD_CAM_MIN_SPEED = 15.0 # m/s (34 mph) +INF_POINT = np.array([1000.0, 0.0, 0.0]) class AugmentedRoadView(CameraView): @@ -176,9 +177,8 @@ class AugmentedRoadView(CameraView): zoom = 2.0 if is_wide_camera else 1.1 # Calculate transforms for vanishing point - inf_point = np.array([1000.0, 0.0, 0.0]) calib_transform = intrinsic @ calibration - kep = calib_transform @ inf_point + kep = calib_transform @ INF_POINT # Calculate center points and dimensions x, y = self._content_rect.x, self._content_rect.y From 5e2f1427046c02669e0fbf766176f05e0873169f Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 24 Oct 2025 22:02:25 -0700 Subject: [PATCH 261/910] increase CPU budget --- selfdrive/test/test_onroad.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/test/test_onroad.py b/selfdrive/test/test_onroad.py index a84248385..f90627e10 100644 --- a/selfdrive/test/test_onroad.py +++ b/selfdrive/test/test_onroad.py @@ -32,7 +32,7 @@ CPU usage budget TEST_DURATION = 25 LOG_OFFSET = 8 -MAX_TOTAL_CPU = 315. # total for all 8 cores +MAX_TOTAL_CPU = 350. # total for all 8 cores PROCS = { # Baseline CPU usage by process "selfdrive.controls.controlsd": 16.0, From e0cabc117475d0dbe68862ee62bd7b8dbe0e3f28 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 24 Oct 2025 23:56:59 -0700 Subject: [PATCH 262/910] raylib: only load glyphs for unifont (#36475) * again llm is terrible * Revert "again llm is terrible" This reverts commit 423dd289ae5701e2f5bb034efd9329175fc275cc. * try this --- system/ui/lib/application.py | 39 +++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 54a18d96d..f09344a9e 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -14,7 +14,7 @@ from typing import NamedTuple from importlib.resources import as_file, files from openpilot.common.swaglog import cloudlog from openpilot.system.hardware import HARDWARE, PC, TICI -from openpilot.system.ui.lib.multilang import TRANSLATIONS_DIR, multilang +from openpilot.system.ui.lib.multilang import TRANSLATIONS_DIR, UNIFONT_LANGUAGES, multilang from openpilot.common.realtime import Ratekeeper _DEFAULT_FPS = int(os.getenv("FPS", 20 if TICI else 60)) @@ -363,33 +363,48 @@ class GuiApplication: # Create a character set from our keyboard layouts from openpilot.system.ui.widgets.keyboard import KEYBOARD_LAYOUTS - all_chars = set() + base_chars = set() for layout in KEYBOARD_LAYOUTS.values(): - all_chars.update(key for row in layout for key in row) - all_chars |= set("–‑✓×°§•") + base_chars.update(key for row in layout for key in row) + base_chars |= set("–‑✓×°§•") # Load only the characters used in translations + unifont_chars = set(base_chars) for language, code in multilang.languages.items(): - all_chars |= set(language) + unifont_chars |= set(language) try: with open(os.path.join(TRANSLATIONS_DIR, f"app_{code}.po")) as f: - all_chars |= set(f.read()) + lang_chars = set(f.read()) + if code in UNIFONT_LANGUAGES: + unifont_chars |= lang_chars + else: + base_chars |= lang_chars except FileNotFoundError: cloudlog.warning(f"Translation file for language '{code}' not found when loading fonts.") - all_chars = "".join(all_chars) - cloudlog.debug(f"Loading fonts with {len(all_chars)} glyphs.") + base_chars = "".join(base_chars) + cloudlog.debug(f"Loading fonts with {len(base_chars)} glyphs.") - codepoint_count = rl.ffi.new("int *", 1) - codepoints = rl.load_codepoints(all_chars, codepoint_count) + unifont_chars = "".join(unifont_chars) + cloudlog.debug(f"Loading unifont with {len(unifont_chars)} glyphs.") + + base_codepoint_count = rl.ffi.new("int *", 1) + base_codepoints = rl.load_codepoints(base_chars, base_codepoint_count) + + unifont_codepoint_count = rl.ffi.new("int *", 1) + unifont_codepoints = rl.load_codepoints(unifont_chars, unifont_codepoint_count) for font_weight_file in FontWeight: with as_file(FONT_DIR.joinpath(font_weight_file)) as fspath: - font = rl.load_font_ex(fspath.as_posix(), 200, codepoints, codepoint_count[0]) + if font_weight_file == FontWeight.UNIFONT: + font = rl.load_font_ex(fspath.as_posix(), 200, unifont_codepoints, unifont_codepoint_count[0]) + else: + font = rl.load_font_ex(fspath.as_posix(), 200, base_codepoints, base_codepoint_count[0]) rl.set_texture_filter(font.texture, rl.TextureFilter.TEXTURE_FILTER_BILINEAR) self._fonts[font_weight_file] = font - rl.unload_codepoints(codepoints) + rl.unload_codepoints(base_codepoints) + rl.unload_codepoints(unifont_codepoints) rl.gui_set_font(self._fonts[FontWeight.NORMAL]) def _set_styles(self): From e92e59ca78e34781ad14c5e7b36b0da96a0d883d Mon Sep 17 00:00:00 2001 From: eFini Date: Sun, 26 Oct 2025 00:17:59 +0800 Subject: [PATCH 263/910] multilang: add gettext package (#36476) needed for gettext --- tools/install_ubuntu_dependencies.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/install_ubuntu_dependencies.sh b/tools/install_ubuntu_dependencies.sh index 97e06bcc6..fdd21067e 100755 --- a/tools/install_ubuntu_dependencies.sh +++ b/tools/install_ubuntu_dependencies.sh @@ -66,7 +66,8 @@ function install_ubuntu_common_requirements() { libqt5svg5-dev \ libqt5serialbus5-dev \ libqt5x11extras5-dev \ - libqt5opengl5-dev + libqt5opengl5-dev \ + gettext } # Install Ubuntu 24.04 LTS packages From 94ca077e69e152ca849a27d1582b4885b1f994af Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 25 Oct 2025 12:27:01 -0700 Subject: [PATCH 264/910] ui: add startup profiling (#36482) * ui: add startup profiling * lil more --- system/ui/lib/application.py | 82 +++++++++++++++++++++++++----------- 1 file changed, 57 insertions(+), 25 deletions(-) diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index f09344a9e..f7450cbd7 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -6,6 +6,7 @@ import signal import sys import pyray as rl import threading +from contextlib import contextmanager from collections.abc import Callable from collections import deque from dataclasses import dataclass @@ -170,37 +171,68 @@ class GuiApplication: self._window_close_requested = True def init_window(self, title: str, fps: int = _DEFAULT_FPS): - def _close(sig, frame): - self.close() - sys.exit(0) - signal.signal(signal.SIGINT, _close) - atexit.register(self.close) + with self._startup_profile_context(): + def _close(sig, frame): + self.close() + sys.exit(0) + signal.signal(signal.SIGINT, _close) + atexit.register(self.close) - HARDWARE.set_display_power(True) - HARDWARE.set_screen_brightness(65) + HARDWARE.set_display_power(True) + HARDWARE.set_screen_brightness(65) - self._set_log_callback() - rl.set_trace_log_level(rl.TraceLogLevel.LOG_WARNING) + self._set_log_callback() + rl.set_trace_log_level(rl.TraceLogLevel.LOG_WARNING) - flags = rl.ConfigFlags.FLAG_MSAA_4X_HINT - if ENABLE_VSYNC: - flags |= rl.ConfigFlags.FLAG_VSYNC_HINT - rl.set_config_flags(flags) + 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) + 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() - self._patch_text_functions() + self._target_fps = fps + self._set_styles() + self._load_fonts() + self._patch_text_functions() - if not PC: - self._mouse.start() + if not PC: + self._mouse.start() + + @contextmanager + def _startup_profile_context(self): + if "PROFILE_STARTUP" not in os.environ: + yield + return + + import cProfile + import io + import pstats + + profiler = cProfile.Profile() + start_time = time.monotonic() + profiler.enable() + + # do the init + yield + + profiler.disable() + elapsed_ms = (time.monotonic() - start_time) * 1e3 + + stats_stream = io.StringIO() + pstats.Stats(profiler, stream=stats_stream).sort_stats("cumtime").print_stats(25) + print("\n=== Startup profile ===") + print(stats_stream.getvalue().rstrip()) + + green = "\033[92m" + reset = "\033[0m" + print(f"{green}UI window ready in {elapsed_ms:.1f} ms{reset}") + sys.exit(0) def set_modal_overlay(self, overlay, callback: Callable | None = None): if self._modal_overlay.overlay is not None: From f0dd0b5c8c32045f93f0a22523ed29b2f45f160b Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sun, 26 Oct 2025 00:47:07 -0700 Subject: [PATCH 265/910] raylib ui: assert system time valid (#36486) * assert system time valid * nl --- selfdrive/ui/lib/api_helpers.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/selfdrive/ui/lib/api_helpers.py b/selfdrive/ui/lib/api_helpers.py index b83efedb6..8ed1c22a6 100644 --- a/selfdrive/ui/lib/api_helpers.py +++ b/selfdrive/ui/lib/api_helpers.py @@ -1,12 +1,16 @@ import time from functools import lru_cache from openpilot.common.api import Api +from openpilot.common.time_helpers import system_time_valid TOKEN_EXPIRY_HOURS = 2 @lru_cache(maxsize=1) def _get_token(dongle_id: str, t: int): + if not system_time_valid(): + raise RuntimeError("System time is not valid, cannot generate token") + return Api(dongle_id).get_token(expiry_hours=TOKEN_EXPIRY_HOURS) From 03cb3e9dc035ecdf7d699ab635651c8ffcb985b8 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 26 Oct 2025 12:15:34 -0700 Subject: [PATCH 266/910] make ruff happy :) --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 8cb877c35..f2d8c5cad 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -235,7 +235,6 @@ lint.ignore = [ "B027", "B024", "NPY002", # new numpy random syntax is worse - "UP038", # (x, y) -> x|y for isinstance ] line-length = 160 target-version ="py311" From 0d4b0ee116d92f3b017cd3978c4c643b09a02c59 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 26 Oct 2025 13:20:11 -0700 Subject: [PATCH 267/910] pre-process fonts for raylib (#36489) * pre-process fonts for raylib * it's fast! * raylib processing * build with scons * padding * happy ruff * all exported * cleanup * more pad --- selfdrive/assets/.gitignore | 2 + selfdrive/assets/fonts/process.py | 124 ++++++++++++++++++++++++++++++ selfdrive/ui/SConscript | 16 ++++ system/ui/lib/application.py | 64 +++------------ 4 files changed, 154 insertions(+), 52 deletions(-) create mode 100755 selfdrive/assets/fonts/process.py diff --git a/selfdrive/assets/.gitignore b/selfdrive/assets/.gitignore index 1f90a2a93..fffd4b4ed 100644 --- a/selfdrive/assets/.gitignore +++ b/selfdrive/assets/.gitignore @@ -1,2 +1,4 @@ *.cc +fonts/*.fnt +fonts/*.png translations_assets.qrc diff --git a/selfdrive/assets/fonts/process.py b/selfdrive/assets/fonts/process.py new file mode 100755 index 000000000..f3633f371 --- /dev/null +++ b/selfdrive/assets/fonts/process.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python3 +from pathlib import Path +import json + +import pyray as rl + +FONT_DIR = Path(__file__).resolve().parent +SELFDRIVE_DIR = FONT_DIR.parents[1] +TRANSLATIONS_DIR = SELFDRIVE_DIR / "ui" / "translations" +LANGUAGES_FILE = TRANSLATIONS_DIR / "languages.json" + +FONT_SIZE = 200 +GLYPH_PADDING = 6 +EXTRA_CHARS = "–‑✓×°§•€£¥" +UNIFONT_LANGUAGES = {"ar", "th", "zh-CHT", "zh-CHS", "ko", "ja"} + + +def _languages(): + if not LANGUAGES_FILE.exists(): + return {} + with LANGUAGES_FILE.open(encoding="utf-8") as f: + return json.load(f) + + +def _char_sets(): + base = set(map(chr, range(32, 127))) | set(EXTRA_CHARS) + unifont = set(base) + + for language, code in _languages().items(): + unifont.update(language) + po_path = TRANSLATIONS_DIR / f"app_{code}.po" + try: + chars = set(po_path.read_text(encoding="utf-8")) + except FileNotFoundError: + continue + (unifont if code in UNIFONT_LANGUAGES else base).update(chars) + + return tuple(sorted(ord(c) for c in base)), tuple(sorted(ord(c) for c in unifont)) + + +def _glyph_metrics(glyphs, rects, codepoints): + entries = [] + min_offset_y, max_extent = None, 0 + for idx, codepoint in enumerate(codepoints): + glyph = glyphs[idx] + rect = rects[idx] + width = int(round(rect.width)) + height = int(round(rect.height)) + offset_y = int(round(glyph.offsetY)) + min_offset_y = offset_y if min_offset_y is None else min(min_offset_y, offset_y) + max_extent = max(max_extent, offset_y + height) + entries.append({ + "id": codepoint, + "x": int(round(rect.x)), + "y": int(round(rect.y)), + "width": width, + "height": height, + "xoffset": int(round(glyph.offsetX)), + "yoffset": offset_y, + "xadvance": int(round(glyph.advanceX)), + }) + + if min_offset_y is None: + raise RuntimeError("No glyphs were generated") + + line_height = int(round(max_extent - min_offset_y)) + base = int(round(max_extent)) + return entries, line_height, base + + +def _write_bmfont(path: Path, face: str, atlas_name: str, line_height: int, base: int, atlas_size, entries): + lines = [ + f"info face=\"{face}\" size=-{FONT_SIZE} bold=0 italic=0 charset=\"\" unicode=1 stretchH=100 smooth=0 aa=1 padding=0,0,0,0 spacing=0,0 outline=0", + f"common lineHeight={line_height} base={base} scaleW={atlas_size[0]} scaleH={atlas_size[1]} pages=1 packed=0 alphaChnl=0 redChnl=4 greenChnl=4 blueChnl=4", + f"page id=0 file=\"{atlas_name}\"", + f"chars count={len(entries)}", + ] + for entry in entries: + lines.append( + ("char id={id:<4} x={x:<5} y={y:<5} width={width:<5} height={height:<5} " + + "xoffset={xoffset:<5} yoffset={yoffset:<5} xadvance={xadvance:<5} page=0 chnl=15").format(**entry) + ) + path.write_text("\n".join(lines) + "\n") + + +def _process_font(font_path: Path, codepoints: tuple[int, ...]): + print(f"Processing {font_path.name}...") + data = font_path.read_bytes() + file_buf = rl.ffi.new("unsigned char[]", data) + cp_buffer = rl.ffi.new("int[]", codepoints) + cp_ptr = rl.ffi.cast("int *", cp_buffer) + glyphs = rl.load_font_data(rl.ffi.cast("unsigned char *", file_buf), len(data), FONT_SIZE, cp_ptr, len(codepoints), rl.FontType.FONT_DEFAULT) + if glyphs == rl.ffi.NULL: + raise RuntimeError("raylib failed to load font data") + + rects_ptr = rl.ffi.new("Rectangle **") + image = rl.gen_image_font_atlas(glyphs, rects_ptr, len(codepoints), FONT_SIZE, GLYPH_PADDING, 0) + if image.width == 0 or image.height == 0: + raise RuntimeError("raylib returned an empty atlas") + + rects = rects_ptr[0] + atlas_name = f"{font_path.stem}.png" + atlas_path = FONT_DIR / atlas_name + entries, line_height, base = _glyph_metrics(glyphs, rects, codepoints) + + if not rl.export_image(image, atlas_path.as_posix()): + raise RuntimeError("Failed to export atlas image") + + _write_bmfont(FONT_DIR / f"{font_path.stem}.fnt", font_path.stem, atlas_name, line_height, base, (image.width, image.height), entries) + + +def main(): + base_cp, unifont_cp = _char_sets() + fonts = sorted(FONT_DIR.glob("*.ttf")) + sorted(FONT_DIR.glob("*.otf")) + for font in fonts: + if "emoji" in font.name.lower(): + continue + glyphs = unifont_cp if font.stem.lower().startswith("unifont") else base_cp + _process_font(font, glyphs) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index 37bf4974d..8a5f5793a 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -1,7 +1,23 @@ import os +import re import json +from pathlib import Path Import('env', 'arch', 'common') +# build the fonts +generator = File("#selfdrive/assets/fonts/process.py") +source_files = Glob("#selfdrive/assets/fonts/*.ttf") + Glob("#selfdrive/assets/fonts/*.otf") +output_files = [ + (f.abspath.split('.')[0] + ".fnt", f.abspath.split('.')[0] + ".png") + for f in source_files + if "NotoColor" not in f.name +] +env.Command( + target=output_files, + source=[generator, source_files], + action=f"python3 {generator}", +) + # compile gettext .po -> .mo translations with open(File("translations/languages.json").abspath) as f: languages = json.loads(f.read()) diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index f7450cbd7..4d0ae5fa4 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -15,7 +15,7 @@ from typing import NamedTuple from importlib.resources import as_file, files from openpilot.common.swaglog import cloudlog from openpilot.system.hardware import HARDWARE, PC, TICI -from openpilot.system.ui.lib.multilang import TRANSLATIONS_DIR, UNIFONT_LANGUAGES, multilang +from openpilot.system.ui.lib.multilang import multilang from openpilot.common.realtime import Ratekeeper _DEFAULT_FPS = int(os.getenv("FPS", 20 if TICI else 60)) @@ -43,16 +43,16 @@ FONT_DIR = ASSETS_DIR.joinpath("fonts") class FontWeight(StrEnum): - THIN = "Inter-Thin.ttf" - EXTRA_LIGHT = "Inter-ExtraLight.ttf" - LIGHT = "Inter-Light.ttf" - NORMAL = "Inter-Regular.ttf" - MEDIUM = "Inter-Medium.ttf" - SEMI_BOLD = "Inter-SemiBold.ttf" - BOLD = "Inter-Bold.ttf" - EXTRA_BOLD = "Inter-ExtraBold.ttf" - BLACK = "Inter-Black.ttf" - UNIFONT = "unifont.otf" + THIN = "Inter-Thin.fnt" + EXTRA_LIGHT = "Inter-ExtraLight.fnt" + LIGHT = "Inter-Light.fnt" + NORMAL = "Inter-Regular.fnt" + MEDIUM = "Inter-Medium.fnt" + SEMI_BOLD = "Inter-SemiBold.fnt" + BOLD = "Inter-Bold.fnt" + EXTRA_BOLD = "Inter-ExtraBold.fnt" + BLACK = "Inter-Black.fnt" + UNIFONT = "unifont.fnt" def font_fallback(font: rl.Font) -> rl.Font: @@ -392,51 +392,11 @@ class GuiApplication: return self._height def _load_fonts(self): - # Create a character set from our keyboard layouts - from openpilot.system.ui.widgets.keyboard import KEYBOARD_LAYOUTS - - base_chars = set() - for layout in KEYBOARD_LAYOUTS.values(): - base_chars.update(key for row in layout for key in row) - base_chars |= set("–‑✓×°§•") - - # Load only the characters used in translations - unifont_chars = set(base_chars) - for language, code in multilang.languages.items(): - unifont_chars |= set(language) - try: - with open(os.path.join(TRANSLATIONS_DIR, f"app_{code}.po")) as f: - lang_chars = set(f.read()) - if code in UNIFONT_LANGUAGES: - unifont_chars |= lang_chars - else: - base_chars |= lang_chars - except FileNotFoundError: - cloudlog.warning(f"Translation file for language '{code}' not found when loading fonts.") - - base_chars = "".join(base_chars) - cloudlog.debug(f"Loading fonts with {len(base_chars)} glyphs.") - - unifont_chars = "".join(unifont_chars) - cloudlog.debug(f"Loading unifont with {len(unifont_chars)} glyphs.") - - base_codepoint_count = rl.ffi.new("int *", 1) - base_codepoints = rl.load_codepoints(base_chars, base_codepoint_count) - - unifont_codepoint_count = rl.ffi.new("int *", 1) - unifont_codepoints = rl.load_codepoints(unifont_chars, unifont_codepoint_count) - for font_weight_file in FontWeight: with as_file(FONT_DIR.joinpath(font_weight_file)) as fspath: - if font_weight_file == FontWeight.UNIFONT: - font = rl.load_font_ex(fspath.as_posix(), 200, unifont_codepoints, unifont_codepoint_count[0]) - else: - font = rl.load_font_ex(fspath.as_posix(), 200, base_codepoints, base_codepoint_count[0]) + font = rl.load_font(fspath.as_posix()) rl.set_texture_filter(font.texture, rl.TextureFilter.TEXTURE_FILTER_BILINEAR) self._fonts[font_weight_file] = font - - rl.unload_codepoints(base_codepoints) - rl.unload_codepoints(unifont_codepoints) rl.gui_set_font(self._fonts[FontWeight.NORMAL]) def _set_styles(self): From cf5bb4e16ecb541036d218b754bc048e08436f11 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 26 Oct 2025 13:47:47 -0700 Subject: [PATCH 268/910] reduce unifont impact on init time (#36490) --- selfdrive/assets/fonts/process.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/selfdrive/assets/fonts/process.py b/selfdrive/assets/fonts/process.py index f3633f371..06bd4e8f0 100755 --- a/selfdrive/assets/fonts/process.py +++ b/selfdrive/assets/fonts/process.py @@ -9,7 +9,6 @@ SELFDRIVE_DIR = FONT_DIR.parents[1] TRANSLATIONS_DIR = SELFDRIVE_DIR / "ui" / "translations" LANGUAGES_FILE = TRANSLATIONS_DIR / "languages.json" -FONT_SIZE = 200 GLYPH_PADDING = 6 EXTRA_CHARS = "–‑✓×°§•€£¥" UNIFONT_LANGUAGES = {"ar", "th", "zh-CHT", "zh-CHS", "ko", "ja"} @@ -68,9 +67,9 @@ def _glyph_metrics(glyphs, rects, codepoints): return entries, line_height, base -def _write_bmfont(path: Path, face: str, atlas_name: str, line_height: int, base: int, atlas_size, entries): +def _write_bmfont(path: Path, font_size: int, face: str, atlas_name: str, line_height: int, base: int, atlas_size, entries): lines = [ - f"info face=\"{face}\" size=-{FONT_SIZE} bold=0 italic=0 charset=\"\" unicode=1 stretchH=100 smooth=0 aa=1 padding=0,0,0,0 spacing=0,0 outline=0", + f"info face=\"{face}\" size=-{font_size} bold=0 italic=0 charset=\"\" unicode=1 stretchH=100 smooth=0 aa=1 padding=0,0,0,0 spacing=0,0 outline=0", f"common lineHeight={line_height} base={base} scaleW={atlas_size[0]} scaleH={atlas_size[1]} pages=1 packed=0 alphaChnl=0 redChnl=4 greenChnl=4 blueChnl=4", f"page id=0 file=\"{atlas_name}\"", f"chars count={len(entries)}", @@ -85,16 +84,21 @@ def _write_bmfont(path: Path, face: str, atlas_name: str, line_height: int, base def _process_font(font_path: Path, codepoints: tuple[int, ...]): print(f"Processing {font_path.name}...") + + font_size = { + "unifont.otf": 24, # unifont is huge + }.get(font_path.name, 200) + data = font_path.read_bytes() file_buf = rl.ffi.new("unsigned char[]", data) cp_buffer = rl.ffi.new("int[]", codepoints) cp_ptr = rl.ffi.cast("int *", cp_buffer) - glyphs = rl.load_font_data(rl.ffi.cast("unsigned char *", file_buf), len(data), FONT_SIZE, cp_ptr, len(codepoints), rl.FontType.FONT_DEFAULT) + glyphs = rl.load_font_data(rl.ffi.cast("unsigned char *", file_buf), len(data), font_size, cp_ptr, len(codepoints), rl.FontType.FONT_DEFAULT) if glyphs == rl.ffi.NULL: raise RuntimeError("raylib failed to load font data") rects_ptr = rl.ffi.new("Rectangle **") - image = rl.gen_image_font_atlas(glyphs, rects_ptr, len(codepoints), FONT_SIZE, GLYPH_PADDING, 0) + image = rl.gen_image_font_atlas(glyphs, rects_ptr, len(codepoints), font_size, GLYPH_PADDING, 0) if image.width == 0 or image.height == 0: raise RuntimeError("raylib returned an empty atlas") @@ -106,7 +110,7 @@ def _process_font(font_path: Path, codepoints: tuple[int, ...]): if not rl.export_image(image, atlas_path.as_posix()): raise RuntimeError("Failed to export atlas image") - _write_bmfont(FONT_DIR / f"{font_path.stem}.fnt", font_path.stem, atlas_name, line_height, base, (image.width, image.height), entries) + _write_bmfont(FONT_DIR / f"{font_path.stem}.fnt", font_size, font_path.stem, atlas_name, line_height, base, (image.width, image.height), entries) def main(): From a974deeb592f9e8f82120cdd62857ca51b28d20a Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 26 Oct 2025 14:13:53 -0700 Subject: [PATCH 269/910] ui: replace close button text with icon (#36492) --- selfdrive/ui/layouts/settings/settings.py | 25 +++++++++++++++-------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/selfdrive/ui/layouts/settings/settings.py b/selfdrive/ui/layouts/settings/settings.py index f13383fa5..72d3a4baf 100644 --- a/selfdrive/ui/layouts/settings/settings.py +++ b/selfdrive/ui/layouts/settings/settings.py @@ -14,13 +14,10 @@ from openpilot.system.ui.lib.wifi_manager import WifiManager from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.network import NetworkUI -# Settings close button -SETTINGS_CLOSE_TEXT = "×" -SETTINGS_CLOSE_TEXT_Y_OFFSET = 8 # The '×' character isn't quite vertically centered in the font so we need to offset it a bit to fully center it - # Constants SIDEBAR_WIDTH = 500 CLOSE_BTN_SIZE = 200 +CLOSE_ICON_SIZE = 70 NAV_BTN_HEIGHT = 110 PANEL_MARGIN = 50 @@ -68,6 +65,7 @@ class SettingsLayout(Widget): } self._font_medium = gui_app.font(FontWeight.MEDIUM) + self._close_icon = gui_app.texture("icons/close2.png", CLOSE_ICON_SIZE, CLOSE_ICON_SIZE) # Callbacks self._close_callback: Callable | None = None @@ -97,12 +95,21 @@ class SettingsLayout(Widget): close_color = CLOSE_BTN_PRESSED if pressed else CLOSE_BTN_COLOR rl.draw_rectangle_rounded(close_btn_rect, 1.0, 20, close_color) - close_text_size = measure_text_cached(self._font_medium, SETTINGS_CLOSE_TEXT, 140) - close_text_pos = rl.Vector2( - close_btn_rect.x + (close_btn_rect.width - close_text_size.x) / 2, - close_btn_rect.y + (close_btn_rect.height - close_text_size.y) / 2 - SETTINGS_CLOSE_TEXT_Y_OFFSET, + icon_color = rl.Color(255, 255, 255, 255) if not pressed else rl.Color(220, 220, 220, 255) + icon_dest = rl.Rectangle( + close_btn_rect.x + (close_btn_rect.width - self._close_icon.width) / 2, + close_btn_rect.y + (close_btn_rect.height - self._close_icon.height) / 2, + self._close_icon.width, + self._close_icon.height, + ) + rl.draw_texture_pro( + self._close_icon, + rl.Rectangle(0, 0, self._close_icon.width, self._close_icon.height), + icon_dest, + rl.Vector2(0, 0), + 0, + icon_color, ) - rl.draw_text_ex(self._font_medium, SETTINGS_CLOSE_TEXT, close_text_pos, 140, 0, TEXT_SELECTED) # Store close button rect for click detection self._close_btn_rect = close_btn_rect From 2d0340cefd1568ec3c8c903e280183e90468e4ab Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 26 Oct 2025 14:18:48 -0700 Subject: [PATCH 270/910] ui: stop loading unused fonts (#36493) Co-authored-by: Comma Device --- system/ui/lib/application.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 4d0ae5fa4..39f27ea1a 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -43,15 +43,11 @@ FONT_DIR = ASSETS_DIR.joinpath("fonts") class FontWeight(StrEnum): - THIN = "Inter-Thin.fnt" - EXTRA_LIGHT = "Inter-ExtraLight.fnt" LIGHT = "Inter-Light.fnt" NORMAL = "Inter-Regular.fnt" MEDIUM = "Inter-Medium.fnt" SEMI_BOLD = "Inter-SemiBold.fnt" BOLD = "Inter-Bold.fnt" - EXTRA_BOLD = "Inter-ExtraBold.fnt" - BLACK = "Inter-Black.fnt" UNIFONT = "unifont.fnt" From 6c85e2c697703446c334a38c0635ae2213a0c630 Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Sun, 26 Oct 2025 18:30:57 -0700 Subject: [PATCH 271/910] ModemManager restart (#36433) * res * limit * not needed * comments + explicit --- system/hardware/hardwared.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/system/hardware/hardwared.py b/system/hardware/hardwared.py index 4d52d7841..8ddc4da2f 100755 --- a/system/hardware/hardwared.py +++ b/system/hardware/hardwared.py @@ -103,8 +103,8 @@ def hw_state_thread(end_event, hw_queue): modem_version = None modem_configured = False - modem_restarted = False modem_missing_count = 0 + modem_restart_count = 0 while not end_event.is_set(): # these are expensive calls. update every 10s @@ -121,16 +121,18 @@ def hw_state_thread(end_event, hw_queue): if modem_version is not None: cloudlog.event("modem version", version=modem_version) - else: - if not modem_restarted: - # TODO: we may be able to remove this with a MM update - # ModemManager's probing on startup can fail - # rarely, restart the service to probe again. - modem_missing_count += 1 - if modem_missing_count > 3: - modem_restarted = True - cloudlog.event("restarting ModemManager") - os.system("sudo systemctl restart --no-block ModemManager") + + if AGNOS and modem_restart_count < 3 and HARDWARE.get_modem_version() is None: + # TODO: we may be able to remove this with a MM update + # ModemManager's probing on startup can fail + # rarely, restart the service to probe again. + # Also, AT commands sometimes timeout resulting in ModemManager not + # trying to use this modem anymore. + modem_missing_count += 1 + if (modem_missing_count % 4) == 0: + modem_restart_count += 1 + cloudlog.event("restarting ModemManager") + os.system("sudo systemctl restart --no-block ModemManager") tx, rx = HARDWARE.get_modem_data_usage() From ff6ed7055ddf765052e917034cfe87ea83971c48 Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Mon, 27 Oct 2025 15:28:09 -0500 Subject: [PATCH 272/910] ci: include assets path for UI label and preview (#36499) * workflow: include 'selfdrive/assets/**' path for triggering UI preview * ui: include 'selfdrive/assets/**' path in labeler configuration --- .github/labeler.yaml | 2 +- .github/workflows/raylib_ui_preview.yaml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/labeler.yaml b/.github/labeler.yaml index 127a04e9e..63d41d5b7 100644 --- a/.github/labeler.yaml +++ b/.github/labeler.yaml @@ -12,7 +12,7 @@ simulation: ui: - changed-files: - - any-glob-to-all-files: '{selfdrive/ui/**,system/ui/**}' + - any-glob-to-all-files: '{selfdrive/assets/**,selfdrive/ui/**,system/ui/**}' tools: - changed-files: diff --git a/.github/workflows/raylib_ui_preview.yaml b/.github/workflows/raylib_ui_preview.yaml index ff9655d15..18880e8a1 100644 --- a/.github/workflows/raylib_ui_preview.yaml +++ b/.github/workflows/raylib_ui_preview.yaml @@ -8,6 +8,7 @@ on: branches: - 'master' paths: + - 'selfdrive/assets/**' - 'selfdrive/ui/**' - 'system/ui/**' workflow_dispatch: From 6efe4e19987ee979f20c7a9b02fd86994d30b9b6 Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Mon, 27 Oct 2025 15:28:27 -0500 Subject: [PATCH 273/910] ci: fix selfdrive_tests weekly run and badge (#36500) --- .github/workflows/ci_weekly_run.yaml | 4 ++-- README.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci_weekly_run.yaml b/.github/workflows/ci_weekly_run.yaml index d8bf91116..acd24de16 100644 --- a/.github/workflows/ci_weekly_run.yaml +++ b/.github/workflows/ci_weekly_run.yaml @@ -11,7 +11,7 @@ concurrency: cancel-in-progress: true jobs: - selfdrive_tests: - uses: commaai/openpilot/.github/workflows/selfdrive_tests.yaml@master + tests: + uses: commaai/openpilot/.github/workflows/tests.yaml@master with: run_number: ${{ inputs.run_number }} diff --git a/README.md b/README.md index 9f1819afb..19a30599b 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Quick start: `bash <(curl -fsSL openpilot.comma.ai)` -[![openpilot tests](https://github.com/commaai/openpilot/actions/workflows/selfdrive_tests.yaml/badge.svg)](https://github.com/commaai/openpilot/actions/workflows/selfdrive_tests.yaml) +[![openpilot tests](https://github.com/commaai/openpilot/actions/workflows/tests.yaml/badge.svg)](https://github.com/commaai/openpilot/actions/workflows/tests.yaml) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) [![X Follow](https://img.shields.io/twitter/follow/comma_ai)](https://x.com/comma_ai) [![Discord](https://img.shields.io/discord/469524606043160576)](https://discord.comma.ai) @@ -74,7 +74,7 @@ Safety and Testing ---- * openpilot observes [ISO26262](https://en.wikipedia.org/wiki/ISO_26262) guidelines, see [SAFETY.md](docs/SAFETY.md) for more details. -* openpilot has software-in-the-loop [tests](.github/workflows/selfdrive_tests.yaml) that run on every commit. +* openpilot has software-in-the-loop [tests](.github/workflows/tests.yaml) that run on every commit. * The code enforcing the safety model lives in panda and is written in C, see [code rigor](https://github.com/commaai/panda#code-rigor) for more details. * panda has software-in-the-loop [safety tests](https://github.com/commaai/panda/tree/master/tests/safety). * Internally, we have a hardware-in-the-loop Jenkins test suite that builds and unit tests the various processes. From e03673485bbb1aac87a096a2e6b727702967068d Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Tue, 28 Oct 2025 04:31:55 +0800 Subject: [PATCH 274/910] ui: unify cache key in AugmentedRoadView (#36477) unified cache key --- selfdrive/ui/onroad/augmented_road_view.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/selfdrive/ui/onroad/augmented_road_view.py b/selfdrive/ui/onroad/augmented_road_view.py index fa0528444..259551954 100644 --- a/selfdrive/ui/onroad/augmented_road_view.py +++ b/selfdrive/ui/onroad/augmented_road_view.py @@ -39,9 +39,7 @@ class AugmentedRoadView(CameraView): self.view_from_calib = view_frame_from_device_frame.copy() self.view_from_wide_calib = view_frame_from_device_frame.copy() - self._last_calib_time: float = 0 - self._last_rect_dims = (0.0, 0.0) - self._last_stream_type = stream_type + self._matrix_cache_key = (0, 0.0, 0.0, stream_type) self._cached_matrix: np.ndarray | None = None self._content_rect = rl.Rectangle() @@ -161,12 +159,13 @@ class AugmentedRoadView(CameraView): def _calc_frame_matrix(self, rect: rl.Rectangle) -> np.ndarray: # Check if we can use cached matrix - calib_time = ui_state.sm.recv_frame['liveCalibration'] - current_dims = (self._content_rect.width, self._content_rect.height) - if (self._last_calib_time == calib_time and - self._last_rect_dims == current_dims and - self._last_stream_type == self.stream_type and - self._cached_matrix is not None): + cache_key = ( + ui_state.sm.recv_frame['liveCalibration'], + self._content_rect.width, + self._content_rect.height, + self.stream_type + ) + if cache_key == self._matrix_cache_key and self._cached_matrix is not None: return self._cached_matrix # Get camera configuration @@ -201,9 +200,7 @@ class AugmentedRoadView(CameraView): x_offset, y_offset = 0, 0 # Cache the computed transformation matrix to avoid recalculations - self._last_calib_time = calib_time - self._last_rect_dims = current_dims - self._last_stream_type = self.stream_type + self._matrix_cache_key = cache_key self._cached_matrix = np.array([ [zoom * 2 * cx / w, 0, -x_offset / w * 2], [0, zoom * 2 * cy / h, -y_offset / h * 2], From debc9bf7cfd139a5b69432bd43686cb6aa354332 Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Mon, 27 Oct 2025 15:32:15 -0500 Subject: [PATCH 275/910] screenshots: reuse alert setup function (#36473) ui: refactor onroad alert setup functions for improved reusability --- .../ui/tests/test_ui/raylib_screenshots.py | 58 +++++-------------- 1 file changed, 14 insertions(+), 44 deletions(-) diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py index 3080b55b9..ef3c9fe02 100755 --- a/selfdrive/ui/tests/test_ui/raylib_screenshots.py +++ b/selfdrive/ui/tests/test_ui/raylib_screenshots.py @@ -177,68 +177,38 @@ def setup_onroad_sidebar(click, pm: PubMaster): click(100, 100) # open sidebar -def setup_onroad_small_alert(click, pm: PubMaster): +def setup_onroad_alert(click, pm: PubMaster, size: log.SelfdriveState.AlertSize, text1: str, text2: str, status: log.SelfdriveState.AlertStatus): setup_onroad(click, pm) alert = messaging.new_message('selfdriveState') - alert.selfdriveState.alertSize = AlertSize.small - alert.selfdriveState.alertText1 = "Small Alert" - alert.selfdriveState.alertText2 = "This is a small alert" - alert.selfdriveState.alertStatus = AlertStatus.normal + ss = alert.selfdriveState + ss.alertSize = size + ss.alertText1 = text1 + ss.alertText2 = text2 + ss.alertStatus = status for _ in range(5): pm.send('selfdriveState', alert) alert.clear_write_flag() time.sleep(0.05) +def setup_onroad_small_alert(click, pm: PubMaster): + setup_onroad_alert(click, pm, AlertSize.small, "Small Alert", "This is a small alert", AlertStatus.normal) + + def setup_onroad_medium_alert(click, pm: PubMaster): - setup_onroad(click, pm) - alert = messaging.new_message('selfdriveState') - alert.selfdriveState.alertSize = AlertSize.mid - alert.selfdriveState.alertText1 = "Medium Alert" - alert.selfdriveState.alertText2 = "This is a medium alert" - alert.selfdriveState.alertStatus = AlertStatus.userPrompt - for _ in range(5): - pm.send('selfdriveState', alert) - alert.clear_write_flag() - time.sleep(0.05) + setup_onroad_alert(click, pm, AlertSize.mid, "Medium Alert", "This is a medium alert", AlertStatus.userPrompt) def setup_onroad_full_alert(click, pm: PubMaster): - setup_onroad(click, pm) - alert = messaging.new_message('selfdriveState') - alert.selfdriveState.alertSize = AlertSize.full - alert.selfdriveState.alertText1 = "DISENGAGE IMMEDIATELY" - alert.selfdriveState.alertText2 = "Driver Distracted" - alert.selfdriveState.alertStatus = AlertStatus.critical - for _ in range(5): - pm.send('selfdriveState', alert) - alert.clear_write_flag() - time.sleep(0.05) + setup_onroad_alert(click, pm, AlertSize.full, "DISENGAGE IMMEDIATELY", "Driver Distracted", AlertStatus.critical) def setup_onroad_full_alert_multiline(click, pm: PubMaster): - setup_onroad(click, pm) - alert = messaging.new_message('selfdriveState') - alert.selfdriveState.alertSize = AlertSize.full - alert.selfdriveState.alertText1 = "Reverse\nGear" - alert.selfdriveState.alertStatus = AlertStatus.normal - for _ in range(5): - pm.send('selfdriveState', alert) - alert.clear_write_flag() - time.sleep(0.05) + setup_onroad_alert(click, pm, AlertSize.full, "Reverse\nGear", "", AlertStatus.normal) def setup_onroad_full_alert_long_text(click, pm: PubMaster): - setup_onroad(click, pm) - alert = messaging.new_message('selfdriveState') - alert.selfdriveState.alertSize = AlertSize.full - alert.selfdriveState.alertText1 = "TAKE CONTROL IMMEDIATELY" - alert.selfdriveState.alertText2 = "Calibration Invalid: Remount Device & Recalibrate" - alert.selfdriveState.alertStatus = AlertStatus.userPrompt - for _ in range(5): - pm.send('selfdriveState', alert) - alert.clear_write_flag() - time.sleep(0.05) + setup_onroad_alert(click, pm, AlertSize.full, "TAKE CONTROL IMMEDIATELY", "Calibration Invalid: Remount Device & Recalibrate", AlertStatus.userPrompt) CASES = { From 4e88245745c98054c15ae6e922cf7788cf93aba7 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 27 Oct 2025 13:34:16 -0700 Subject: [PATCH 276/910] raylib: rename set_callbacks (#36462) rn --- system/ui/lib/wifi_manager.py | 2 +- system/ui/widgets/network.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index 5594742cb..b6305a03f 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -186,7 +186,7 @@ class WifiManager: threading.Thread(target=worker, daemon=True).start() - def set_callbacks(self, need_auth: Callable[[str], None] | None = None, + def add_callbacks(self, need_auth: Callable[[str], None] | None = None, activated: Callable[[], None] | None = None, forgotten: Callable[[], None] | None = None, networks_updated: Callable[[list[Network]], None] | None = None, diff --git a/system/ui/widgets/network.py b/system/ui/widgets/network.py index e9d7a1b09..592c9de97 100644 --- a/system/ui/widgets/network.py +++ b/system/ui/widgets/network.py @@ -110,7 +110,7 @@ class AdvancedNetworkSettings(Widget): def __init__(self, wifi_manager: WifiManager): super().__init__() self._wifi_manager = wifi_manager - self._wifi_manager.set_callbacks(networks_updated=self._on_network_updated) + self._wifi_manager.add_callbacks(networks_updated=self._on_network_updated) self._params = Params() self._keyboard = Keyboard(max_text_size=MAX_PASSWORD_LENGTH, min_text_size=MIN_PASSWORD_LENGTH, show_password_toggle=True) @@ -285,7 +285,7 @@ class WifiManagerUI(Widget): self._networks_buttons: dict[str, Button] = {} self._forget_networks_buttons: dict[str, Button] = {} - self._wifi_manager.set_callbacks(need_auth=self._on_need_auth, + self._wifi_manager.add_callbacks(need_auth=self._on_need_auth, activated=self._on_activated, forgotten=self._on_forgotten, networks_updated=self._on_network_updated, From 1dadb3fcc92135b8b800746dbac0a00f88d8d36d Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Mon, 27 Oct 2025 15:34:35 -0500 Subject: [PATCH 277/910] multilang: fix missing translation for longitudinal personality toggle description (#36446) fix: add translation wrapper for longitudinal personality toggle description --- selfdrive/ui/layouts/settings/toggles.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/layouts/settings/toggles.py b/selfdrive/ui/layouts/settings/toggles.py index e4441edec..3a1265e0f 100644 --- a/selfdrive/ui/layouts/settings/toggles.py +++ b/selfdrive/ui/layouts/settings/toggles.py @@ -94,7 +94,7 @@ class TogglesLayout(Widget): self._long_personality_setting = multiple_button_item( lambda: tr("Driving Personality"), - DESCRIPTIONS["LongitudinalPersonality"], + lambda: tr(DESCRIPTIONS["LongitudinalPersonality"]), buttons=[lambda: tr("Aggressive"), lambda: tr("Standard"), lambda: tr("Relaxed")], button_width=255, callback=self._set_longitudinal_personality, From e754b738ada0388b78308675712c9c99fe5d174d Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 27 Oct 2025 15:15:48 -0700 Subject: [PATCH 278/910] raylib: fix prime state thread (#36504) fix --- selfdrive/ui/lib/prime_state.py | 1 - selfdrive/ui/ui_state.py | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/lib/prime_state.py b/selfdrive/ui/lib/prime_state.py index 30ad0f763..fc72b4f9c 100644 --- a/selfdrive/ui/lib/prime_state.py +++ b/selfdrive/ui/lib/prime_state.py @@ -33,7 +33,6 @@ class PrimeState: self._running = False self._thread = None - self.start() def _load_initial_state(self) -> PrimeType: prime_type_str = os.getenv("PRIME_TYPE") or self._params.get("PrimeType") diff --git a/selfdrive/ui/ui_state.py b/selfdrive/ui/ui_state.py index dab01f924..5d770fb91 100644 --- a/selfdrive/ui/ui_state.py +++ b/selfdrive/ui/ui_state.py @@ -98,6 +98,7 @@ class UIState: return not self.started def update(self) -> None: + self.prime_state.start() # start thread after manager forks ui self.sm.update(0) self._update_state() self._update_status() From 2d6df2e1259dbb2a8408ac286502c19a08ce1976 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 27 Oct 2025 19:59:35 -0700 Subject: [PATCH 279/910] raylib: minor tweaks (#36507) * try * generic * check * why was this here?! --- selfdrive/ui/__init__.py | 1 + selfdrive/ui/onroad/augmented_road_view.py | 3 ++- selfdrive/ui/onroad/driver_state.py | 3 ++- selfdrive/ui/ui_state.py | 1 - selfdrive/ui/widgets/pairing_dialog.py | 13 +------------ selfdrive/ui/widgets/prime.py | 2 +- selfdrive/ui/widgets/setup.py | 2 +- system/ui/lib/application.py | 2 ++ system/ui/widgets/button.py | 12 ++++++++++++ 9 files changed, 22 insertions(+), 17 deletions(-) diff --git a/selfdrive/ui/__init__.py b/selfdrive/ui/__init__.py index e69de29bb..b07e842f1 100644 --- a/selfdrive/ui/__init__.py +++ b/selfdrive/ui/__init__.py @@ -0,0 +1 @@ +UI_BORDER_SIZE = 30 diff --git a/selfdrive/ui/onroad/augmented_road_view.py b/selfdrive/ui/onroad/augmented_road_view.py index 259551954..1f202141c 100644 --- a/selfdrive/ui/onroad/augmented_road_view.py +++ b/selfdrive/ui/onroad/augmented_road_view.py @@ -3,7 +3,8 @@ import numpy as np import pyray as rl from cereal import log, messaging from msgq.visionipc import VisionStreamType -from openpilot.selfdrive.ui.ui_state import ui_state, UIStatus, UI_BORDER_SIZE +from openpilot.selfdrive.ui import UI_BORDER_SIZE +from openpilot.selfdrive.ui.ui_state import ui_state, UIStatus from openpilot.selfdrive.ui.onroad.alert_renderer import AlertRenderer from openpilot.selfdrive.ui.onroad.driver_state import DriverStateRenderer from openpilot.selfdrive.ui.onroad.hud_renderer import HudRenderer diff --git a/selfdrive/ui/onroad/driver_state.py b/selfdrive/ui/onroad/driver_state.py index 8aaef1bcf..7b3181d1a 100644 --- a/selfdrive/ui/onroad/driver_state.py +++ b/selfdrive/ui/onroad/driver_state.py @@ -2,7 +2,8 @@ import numpy as np import pyray as rl from cereal import log from dataclasses import dataclass -from openpilot.selfdrive.ui.ui_state import ui_state, UI_BORDER_SIZE +from openpilot.selfdrive.ui import UI_BORDER_SIZE +from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.widgets import Widget diff --git a/selfdrive/ui/ui_state.py b/selfdrive/ui/ui_state.py index 5d770fb91..a947e5406 100644 --- a/selfdrive/ui/ui_state.py +++ b/selfdrive/ui/ui_state.py @@ -12,7 +12,6 @@ from openpilot.selfdrive.ui.lib.prime_state import PrimeState from openpilot.system.ui.lib.application import gui_app from openpilot.system.hardware import HARDWARE -UI_BORDER_SIZE = 30 BACKLIGHT_OFFROAD = 50 diff --git a/selfdrive/ui/widgets/pairing_dialog.py b/selfdrive/ui/widgets/pairing_dialog.py index 85b42d1a7..f960cf723 100644 --- a/selfdrive/ui/widgets/pairing_dialog.py +++ b/selfdrive/ui/widgets/pairing_dialog.py @@ -11,21 +11,10 @@ from openpilot.system.ui.lib.application import FontWeight, gui_app from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.lib.wrap_text import wrap_text from openpilot.system.ui.lib.text_measure import measure_text_cached +from openpilot.system.ui.widgets.button import IconButton from openpilot.selfdrive.ui.ui_state import ui_state -class IconButton(Widget): - def __init__(self, texture: rl.Texture): - super().__init__() - self._texture = texture - - def _render(self, rect: rl.Rectangle): - color = rl.Color(180, 180, 180, 150) if self.is_pressed else rl.WHITE - draw_x = rect.x + (rect.width - self._texture.width) / 2 - draw_y = rect.y + (rect.height - self._texture.height) / 2 - rl.draw_texture(self._texture, int(draw_x), int(draw_y), color) - - class PairingDialog(Widget): """Dialog for device pairing with QR code.""" diff --git a/selfdrive/ui/widgets/prime.py b/selfdrive/ui/widgets/prime.py index 49a0e56cd..e98e4c1e1 100644 --- a/selfdrive/ui/widgets/prime.py +++ b/selfdrive/ui/widgets/prime.py @@ -34,7 +34,7 @@ class PrimeWidget(Widget): # Description with wrapping desc_y = y + 140 - font = gui_app.font(FontWeight.LIGHT) + font = gui_app.font(FontWeight.NORMAL) wrapped_text = "\n".join(wrap_text(font, tr("Become a comma prime member at connect.comma.ai"), 56, int(w))) text_size = measure_text_cached(font, wrapped_text, 56) rl.draw_text_ex(font, wrapped_text, rl.Vector2(x, desc_y), 56, 0, rl.WHITE) diff --git a/selfdrive/ui/widgets/setup.py b/selfdrive/ui/widgets/setup.py index fcc5a1410..3c9406688 100644 --- a/selfdrive/ui/widgets/setup.py +++ b/selfdrive/ui/widgets/setup.py @@ -46,7 +46,7 @@ class SetupWidget(Widget): # Description desc = tr("Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer.") - light_font = gui_app.font(FontWeight.LIGHT) + light_font = gui_app.font(FontWeight.NORMAL) wrapped = wrap_text(light_font, desc, 50, int(w)) for line in wrapped: rl.draw_text_ex(light_font, line, rl.Vector2(x, y), 50, 0, rl.WHITE) diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 39f27ea1a..8ec417159 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -274,6 +274,8 @@ class GuiApplication: rl.image_resize(image, new_width, new_height) else: rl.image_resize(image, width, height) + else: + assert keep_aspect_ratio, "Cannot resize without specifying width and height" return image def _load_texture_from_image(self, image: rl.Image) -> rl.Texture: diff --git a/system/ui/widgets/button.py b/system/ui/widgets/button.py index 5b9c51886..df7a52d1c 100644 --- a/system/ui/widgets/button.py +++ b/system/ui/widgets/button.py @@ -169,3 +169,15 @@ class ButtonRadio(Button): icon_y = self._rect.y + (self._rect.height - self._icon.height) / 2 icon_x = self._rect.x + self._rect.width - self._icon.width - self._text_padding - ICON_PADDING rl.draw_texture_v(self._icon, rl.Vector2(icon_x, icon_y), rl.WHITE if self.enabled else rl.Color(255, 255, 255, 100)) + + +class IconButton(Widget): + def __init__(self, texture: rl.Texture): + super().__init__() + self._texture = texture + + def _render(self, rect: rl.Rectangle): + color = rl.Color(180, 180, 180, 150) if self.is_pressed else rl.WHITE + draw_x = rect.x + (rect.width - self._texture.width) / 2 + draw_y = rect.y + (rect.height - self._texture.height) / 2 + rl.draw_texture(self._texture, int(draw_x), int(draw_y), color) From 73ed45f9d7071058d5f4c75b97825e6648b7697b Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Mon, 27 Oct 2025 22:00:01 -0500 Subject: [PATCH 280/910] ui screenshots: add screenshot for unifont rendering (#36506) * ui: add homescreen setup for unifont language setting * fix params --- selfdrive/ui/tests/test_ui/raylib_screenshots.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py index ef3c9fe02..f36ad1bad 100755 --- a/selfdrive/ui/tests/test_ui/raylib_screenshots.py +++ b/selfdrive/ui/tests/test_ui/raylib_screenshots.py @@ -216,6 +216,7 @@ CASES = { "homescreen_paired": setup_homescreen, "homescreen_prime": setup_homescreen, "homescreen_update_available": setup_homescreen_update_available, + "homescreen_unifont": setup_homescreen, "settings_device": setup_settings, "settings_network": setup_settings_network, "settings_network_advanced": setup_settings_network_advanced, @@ -300,6 +301,8 @@ def create_screenshots(): params.put("PrimeType", 0) # NONE elif name == "homescreen_prime": params.put("PrimeType", 2) # LITE + elif name == "homescreen_unifont": + params.put("LanguageSetting", "zh-CHT") # Traditional Chinese t.test_ui(name, setup) From 8a77534d02960fc238075aff74cc50be9f20f041 Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Mon, 27 Oct 2025 22:47:41 -0700 Subject: [PATCH 281/910] fix zipapp with multilang (#36511) * fix * fix * fix * more --- release/pack.py | 2 +- selfdrive/ui/tests/test_translations.py | 4 ++-- selfdrive/ui/update_translations.py | 2 +- system/ui/lib/application.py | 5 +++-- system/ui/lib/multilang.py | 11 ++++++----- 5 files changed, 13 insertions(+), 11 deletions(-) diff --git a/release/pack.py b/release/pack.py index 1cb1a47a4..92ff68fe7 100755 --- a/release/pack.py +++ b/release/pack.py @@ -12,7 +12,7 @@ from openpilot.common.basedir import BASEDIR DIRS = ['cereal', 'openpilot'] -EXTS = ['.png', '.py', '.ttf', '.capnp'] +EXTS = ['.png', '.py', '.ttf', '.capnp', '.json', '.fnt', '.mo'] INTERPRETER = '/usr/bin/env python3' diff --git a/selfdrive/ui/tests/test_translations.py b/selfdrive/ui/tests/test_translations.py index 5308a44ea..3177814f9 100644 --- a/selfdrive/ui/tests/test_translations.py +++ b/selfdrive/ui/tests/test_translations.py @@ -8,7 +8,7 @@ import requests from parameterized import parameterized_class from openpilot.system.ui.lib.multilang import TRANSLATIONS_DIR, LANGUAGES_FILE -with open(LANGUAGES_FILE) as f: +with open(str(LANGUAGES_FILE)) as f: translation_files = json.load(f) UNFINISHED_TRANSLATION_TAG = " Date: Wed, 29 Oct 2025 04:09:49 +0800 Subject: [PATCH 282/910] ui: skip rendering when screen is off (#36510) * skip rendering when screen is off * continue and rename * revert that * flip --------- Co-authored-by: Adeeb Shihadeh --- selfdrive/ui/ui.py | 5 ++--- selfdrive/ui/ui_state.py | 1 + system/ui/lib/application.py | 15 +++++++++++++-- system/ui/reset.py | 9 ++++----- system/ui/setup.py | 7 +++---- system/ui/updater.py | 7 +++---- 6 files changed, 26 insertions(+), 18 deletions(-) diff --git a/selfdrive/ui/ui.py b/selfdrive/ui/ui.py index f2ca4e770..4a1f03fb8 100755 --- a/selfdrive/ui/ui.py +++ b/selfdrive/ui/ui.py @@ -13,10 +13,9 @@ def main(): gui_app.init_window("UI") main_layout = MainLayout() main_layout.set_rect(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) - for showing_dialog in gui_app.render(): + for should_render in gui_app.render(): ui_state.update() - - if not showing_dialog: + if should_render: main_layout.render() diff --git a/selfdrive/ui/ui_state.py b/selfdrive/ui/ui_state.py index a947e5406..b08b8ef28 100644 --- a/selfdrive/ui/ui_state.py +++ b/selfdrive/ui/ui_state.py @@ -253,6 +253,7 @@ class Device: self._awake = on cloudlog.debug(f"setting display power {int(on)}") HARDWARE.set_display_power(on) + gui_app.set_should_render(on) # Global instance diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 23fa8a677..698ed3650 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -156,6 +156,8 @@ class GuiApplication: self._mouse = MouseState(self._scale) self._mouse_events: list[MouseEvent] = [] + self._should_render = True + # Debug variables self._mouse_history: deque[MousePos] = deque(maxlen=MOUSE_THREAD_RATE) @@ -237,6 +239,9 @@ class GuiApplication: self._modal_overlay = ModalOverlay(overlay=overlay, callback=callback) + def set_should_render(self, should_render: bool): + self._should_render = should_render + def texture(self, asset_path: str, width: int | None = None, height: int | None = None, alpha_premultiply=False, keep_aspect_ratio=True): cache_key = f"{asset_path}_{width}_{height}_{alpha_premultiply}{keep_aspect_ratio}" @@ -322,6 +327,12 @@ class GuiApplication: # Store all mouse events for the current frame self._mouse_events = self._mouse.get_events() + # Skip rendering when screen is off + if not self._should_render: + time.sleep(1 / self._target_fps) + yield False + continue + if self._render_texture: rl.begin_texture_mode(self._render_texture) rl.clear_background(rl.BLACK) @@ -344,9 +355,9 @@ class GuiApplication: self._modal_overlay = ModalOverlay() if original_modal.callback is not None: original_modal.callback(result) - yield True - else: yield False + else: + yield True if self._render_texture: rl.end_texture_mode() diff --git a/system/ui/reset.py b/system/ui/reset.py index 8f6466ce4..3922c27aa 100755 --- a/system/ui/reset.py +++ b/system/ui/reset.py @@ -126,11 +126,10 @@ def main(): if mode == ResetMode.FORMAT: reset.start_reset() - for showing_dialog in gui_app.render(): - if showing_dialog: - continue - if not reset.render(rl.Rectangle(45, 200, gui_app.width - 90, gui_app.height - 245)): - break + for should_render in gui_app.render(): + if should_render: + if not reset.render(rl.Rectangle(45, 200, gui_app.width - 90, gui_app.height - 245)): + break if __name__ == "__main__": diff --git a/system/ui/setup.py b/system/ui/setup.py index da8c8d81f..f6d2853fa 100755 --- a/system/ui/setup.py +++ b/system/ui/setup.py @@ -432,10 +432,9 @@ def main(): try: gui_app.init_window("Setup", 20) setup = Setup() - for showing_dialog in gui_app.render(): - if showing_dialog: - continue - setup.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) + for should_render in gui_app.render(): + if should_render: + setup.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) setup.close() except Exception as e: print(f"Setup error: {e}") diff --git a/system/ui/updater.py b/system/ui/updater.py index 5dd5a69c6..2e1a8687e 100755 --- a/system/ui/updater.py +++ b/system/ui/updater.py @@ -161,10 +161,9 @@ def main(): try: gui_app.init_window("System Update") updater = Updater(updater_path, manifest_path) - for showing_dialog in gui_app.render(): - if showing_dialog: - continue - updater.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) + for should_render in gui_app.render(): + if should_render: + updater.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) finally: # Make sure we clean up even if there's an error gui_app.close() From 5d142326f5eac6567787ae2fad2ab4ee22e693ea Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Wed, 29 Oct 2025 05:47:17 +0800 Subject: [PATCH 283/910] ui: remove unused get_width() method (#36512) remove unused get_width() method --- system/ui/widgets/list_view.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/system/ui/widgets/list_view.py b/system/ui/widgets/list_view.py index ed087c3f1..20a5f7791 100644 --- a/system/ui/widgets/list_view.py +++ b/system/ui/widgets/list_view.py @@ -172,10 +172,6 @@ class TextAction(ItemAction): def set_text(self, text: str | Callable[[], str]): self._text_source = text - def get_width(self) -> int: - text_width = measure_text_cached(self._font, self.text, ITEM_TEXT_FONT_SIZE).x - return int(text_width + TEXT_PADDING) - class DualButtonAction(ItemAction): def __init__(self, left_text: str | Callable[[], str], right_text: str | Callable[[], str], left_callback: Callable = None, From 47d0a95fd6d6c40411f9d24ae9ddfedf8de6e124 Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Tue, 28 Oct 2025 16:49:33 -0500 Subject: [PATCH 284/910] font: remove unifont anti-aliasing and reduce font size to 16 (#36508) remove unifont anti-aliasing and reduce font size to 16 Co-authored-by: Shane Smiskol --- selfdrive/assets/fonts/process.py | 2 +- system/ui/lib/application.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/selfdrive/assets/fonts/process.py b/selfdrive/assets/fonts/process.py index 06bd4e8f0..a0d01af14 100755 --- a/selfdrive/assets/fonts/process.py +++ b/selfdrive/assets/fonts/process.py @@ -86,7 +86,7 @@ def _process_font(font_path: Path, codepoints: tuple[int, ...]): print(f"Processing {font_path.name}...") font_size = { - "unifont.otf": 24, # unifont is huge + "unifont.otf": 16, # unifont is only 16x8 or 16x16 pixels per glyph }.get(font_path.name, 200) data = font_path.read_bytes() diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 698ed3650..8e45191bf 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -405,7 +405,8 @@ class GuiApplication: with as_file(FONT_DIR) as fspath: fnt_path = fspath / font_weight_file font = rl.load_font(fnt_path.as_posix()) - rl.set_texture_filter(font.texture, rl.TextureFilter.TEXTURE_FILTER_BILINEAR) + if font_weight_file != FontWeight.UNIFONT: + rl.set_texture_filter(font.texture, rl.TextureFilter.TEXTURE_FILTER_BILINEAR) self._fonts[font_weight_file] = font rl.gui_set_font(self._fonts[FontWeight.NORMAL]) From 2e636458a6c78a019532b912e936a43d8b2bd817 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 28 Oct 2025 16:47:22 -0700 Subject: [PATCH 285/910] op adb: forward all openpilot service ports (#36518) * op adb: forward all openpilot service ports * cleanup --- tools/scripts/adb_ssh.sh | 41 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/tools/scripts/adb_ssh.sh b/tools/scripts/adb_ssh.sh index 2fe2873a3..ad6569372 100755 --- a/tools/scripts/adb_ssh.sh +++ b/tools/scripts/adb_ssh.sh @@ -1,7 +1,42 @@ #!/usr/bin/env bash -set -e +set -euo pipefail -# this is a little nicer than "adb shell" since -# "adb shell" doesn't do full terminal emulation +# Forward all openpilot service ports +mapfile -t SERVICE_PORTS < <(python3 - <<'PY' +from cereal.services import SERVICE_LIST + +FNV_PRIME = 0x100000001b3 +FNV_OFFSET_BASIS = 0xcbf29ce484222325 +START_PORT = 8023 +MAX_PORT = 65535 +PORT_RANGE = MAX_PORT - START_PORT +MASK = 0xffffffffffffffff + +def fnv1a(endpoint: str) -> int: + h = FNV_OFFSET_BASIS + for b in endpoint.encode(): + h ^= b + h = (h * FNV_PRIME) & MASK + return h + +ports = set() +for name in SERVICE_LIST.keys(): + port = START_PORT + fnv1a(name) % PORT_RANGE + ports.add((name, port)) + +for name, port in sorted(ports): + print(f"{name} {port}") +PY +) + +for entry in "${SERVICE_PORTS[@]}"; do + name="${entry% *}" + port="${entry##* }" + adb forward "tcp:${port}" "tcp:${port}" > /dev/null +done + +# Forward SSH port first for interactive shell access. adb forward tcp:2222 tcp:22 + +# SSH! ssh comma@localhost -p 2222 "$@" From 9f20eb8ce612fadfa7331e0d50e5619eaaf853b8 Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Tue, 28 Oct 2025 19:15:43 -0700 Subject: [PATCH 286/910] setup: handle incompatible versions (#36520) check --- system/ui/setup.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/system/ui/setup.py b/system/ui/setup.py index f6d2853fa..0045b4541 100755 --- a/system/ui/setup.py +++ b/system/ui/setup.py @@ -4,6 +4,7 @@ import re import threading import time import urllib.request +import urllib.error from urllib.parse import urlparse from enum import IntEnum import shutil @@ -418,6 +419,10 @@ class Setup(Widget): time.sleep(0.1) gui_app.request_close() + except urllib.error.HTTPError as e: + if e.code == 409: + error_msg = e.read().decode("utf-8") + self.download_failed(self.download_url, error_msg) 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) From 002a22a09766628beeb262715a334aa40b7bfc24 Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Tue, 28 Oct 2025 23:05:44 -0700 Subject: [PATCH 287/910] AGNOS 14.5 (#36523) * stage * updater * prod --- launch_env.sh | 2 +- system/hardware/tici/agnos.json | 20 +++++------ system/hardware/tici/all-partitions.json | 44 ++++++++++++------------ system/hardware/tici/updater_magic | 4 +-- 4 files changed, 35 insertions(+), 35 deletions(-) diff --git a/launch_env.sh b/launch_env.sh index 9bbe0916f..3715ad248 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="14.3" + export AGNOS_VERSION="14.5" fi export STAGING_ROOT="/data/safe_staging" diff --git a/system/hardware/tici/agnos.json b/system/hardware/tici/agnos.json index 61ba1d38e..d6a6ab51c 100644 --- a/system/hardware/tici/agnos.json +++ b/system/hardware/tici/agnos.json @@ -56,28 +56,28 @@ }, { "name": "boot", - "url": "https://commadist.azureedge.net/agnosupdate/boot-273ba209936c5697bea478d1bc21dbc4ede1588508d33f5d2e7ad5d5c581d925.img.xz", - "hash": "273ba209936c5697bea478d1bc21dbc4ede1588508d33f5d2e7ad5d5c581d925", - "hash_raw": "273ba209936c5697bea478d1bc21dbc4ede1588508d33f5d2e7ad5d5c581d925", + "url": "https://commadist.azureedge.net/agnosupdate/boot-90bd687e9e407834d4ee1b07f3d05527dfae0ff09c0cacd64cfd6097f6b10e2c.img.xz", + "hash": "90bd687e9e407834d4ee1b07f3d05527dfae0ff09c0cacd64cfd6097f6b10e2c", + "hash_raw": "90bd687e9e407834d4ee1b07f3d05527dfae0ff09c0cacd64cfd6097f6b10e2c", "size": 17496064, "sparse": false, "full_check": true, "has_ab": true, - "ondevice_hash": "562c9137843994995188f21f5ec3bac408ac7dee030627aef701853814a1d33e" + "ondevice_hash": "35014c39b55010ac955c10f808b088e74259147c7a8cbf989b3dff7d95a1e8ae" }, { "name": "system", - "url": "https://commadist.azureedge.net/agnosupdate/system-71b51d24f70d6831f597d369d4b37f78262258096bc3d662826275c4f1fbd42c.img.xz", - "hash": "2e41ebf1cf7e3801fa447c1e133d6d1b928546c412c36a843f5bd344557b9908", - "hash_raw": "71b51d24f70d6831f597d369d4b37f78262258096bc3d662826275c4f1fbd42c", + "url": "https://commadist.azureedge.net/agnosupdate/system-54ca532116840b8532ef3c5a3e9ef4b073ed8c6b66cb5e30ad299e28fff35502.img.xz", + "hash": "6e2f82c249f6feacdfcf20679e9e4876d161753a94d4068fe26b5c41191bda24", + "hash_raw": "54ca532116840b8532ef3c5a3e9ef4b073ed8c6b66cb5e30ad299e28fff35502", "size": 4718592000, "sparse": true, "full_check": false, "has_ab": true, - "ondevice_hash": "1d65fd6d8bb4b0c6f9920e0b5c49c7743b5949f8057676511765f1900ab6a771", + "ondevice_hash": "f3a50de42734f747611f6df00087b3a6ec3620bed07134d327a864c1182f0df8", "alt": { - "hash": "71b51d24f70d6831f597d369d4b37f78262258096bc3d662826275c4f1fbd42c", - "url": "https://commadist.azureedge.net/agnosupdate/system-71b51d24f70d6831f597d369d4b37f78262258096bc3d662826275c4f1fbd42c.img", + "hash": "54ca532116840b8532ef3c5a3e9ef4b073ed8c6b66cb5e30ad299e28fff35502", + "url": "https://commadist.azureedge.net/agnosupdate/system-54ca532116840b8532ef3c5a3e9ef4b073ed8c6b66cb5e30ad299e28fff35502.img", "size": 4718592000 } } diff --git a/system/hardware/tici/all-partitions.json b/system/hardware/tici/all-partitions.json index 434c0567d..c51453f4c 100644 --- a/system/hardware/tici/all-partitions.json +++ b/system/hardware/tici/all-partitions.json @@ -339,62 +339,62 @@ }, { "name": "boot", - "url": "https://commadist.azureedge.net/agnosupdate/boot-273ba209936c5697bea478d1bc21dbc4ede1588508d33f5d2e7ad5d5c581d925.img.xz", - "hash": "273ba209936c5697bea478d1bc21dbc4ede1588508d33f5d2e7ad5d5c581d925", - "hash_raw": "273ba209936c5697bea478d1bc21dbc4ede1588508d33f5d2e7ad5d5c581d925", + "url": "https://commadist.azureedge.net/agnosupdate/boot-90bd687e9e407834d4ee1b07f3d05527dfae0ff09c0cacd64cfd6097f6b10e2c.img.xz", + "hash": "90bd687e9e407834d4ee1b07f3d05527dfae0ff09c0cacd64cfd6097f6b10e2c", + "hash_raw": "90bd687e9e407834d4ee1b07f3d05527dfae0ff09c0cacd64cfd6097f6b10e2c", "size": 17496064, "sparse": false, "full_check": true, "has_ab": true, - "ondevice_hash": "562c9137843994995188f21f5ec3bac408ac7dee030627aef701853814a1d33e" + "ondevice_hash": "35014c39b55010ac955c10f808b088e74259147c7a8cbf989b3dff7d95a1e8ae" }, { "name": "system", - "url": "https://commadist.azureedge.net/agnosupdate/system-71b51d24f70d6831f597d369d4b37f78262258096bc3d662826275c4f1fbd42c.img.xz", - "hash": "2e41ebf1cf7e3801fa447c1e133d6d1b928546c412c36a843f5bd344557b9908", - "hash_raw": "71b51d24f70d6831f597d369d4b37f78262258096bc3d662826275c4f1fbd42c", + "url": "https://commadist.azureedge.net/agnosupdate/system-54ca532116840b8532ef3c5a3e9ef4b073ed8c6b66cb5e30ad299e28fff35502.img.xz", + "hash": "6e2f82c249f6feacdfcf20679e9e4876d161753a94d4068fe26b5c41191bda24", + "hash_raw": "54ca532116840b8532ef3c5a3e9ef4b073ed8c6b66cb5e30ad299e28fff35502", "size": 4718592000, "sparse": true, "full_check": false, "has_ab": true, - "ondevice_hash": "1d65fd6d8bb4b0c6f9920e0b5c49c7743b5949f8057676511765f1900ab6a771", + "ondevice_hash": "f3a50de42734f747611f6df00087b3a6ec3620bed07134d327a864c1182f0df8", "alt": { - "hash": "71b51d24f70d6831f597d369d4b37f78262258096bc3d662826275c4f1fbd42c", - "url": "https://commadist.azureedge.net/agnosupdate/system-71b51d24f70d6831f597d369d4b37f78262258096bc3d662826275c4f1fbd42c.img", + "hash": "54ca532116840b8532ef3c5a3e9ef4b073ed8c6b66cb5e30ad299e28fff35502", + "url": "https://commadist.azureedge.net/agnosupdate/system-54ca532116840b8532ef3c5a3e9ef4b073ed8c6b66cb5e30ad299e28fff35502.img", "size": 4718592000 } }, { "name": "userdata_90", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_90-8ed6bf12f84d64770aca750daacbd1578d199cc470ecd81ace255cd9cf5065e8.img.xz", - "hash": "4b1aae460d6a81d27691e2aa4fd96634eca607cac65305b5147350f1578beebd", - "hash_raw": "8ed6bf12f84d64770aca750daacbd1578d199cc470ecd81ace255cd9cf5065e8", + "url": "https://commadist.azureedge.net/agnosupdate/userdata_90-7b58b8c6935e998b93fae25aa358dc808e90b032c004b7f7ec6e3a60986cd41d.img.xz", + "hash": "5115d8a8bc4ad8eebaf0e3176cc68799089c72f64504f7238f411cbaf5d18497", + "hash_raw": "7b58b8c6935e998b93fae25aa358dc808e90b032c004b7f7ec6e3a60986cd41d", "size": 96636764160, "sparse": true, "full_check": true, "has_ab": false, - "ondevice_hash": "be4365d37920853fb05fbffa793f7818890a41e4869c36ab16439a5f18d8d945" + "ondevice_hash": "b0ee797f751bc776bcb161a479cfb7f339bbf92f0cb207b053797daef0ac61d8" }, { "name": "userdata_89", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_89-11fe1e0f066bb8639f6bbbf2c71f19472f1ae436b96c92fe2d21c456dce1bf88.img.xz", - "hash": "517273cd69a34ae523e6075baa79dd46fe5a940e376faba50f26ec0b74a27610", - "hash_raw": "11fe1e0f066bb8639f6bbbf2c71f19472f1ae436b96c92fe2d21c456dce1bf88", + "url": "https://commadist.azureedge.net/agnosupdate/userdata_89-a5a3e2fe9a8bf83ea37aabafdd7fd5f6e274f8ef3d1fb46ce56766cec8f13215.img.xz", + "hash": "c0348854c1f93a0557c9ce85f709c8d19f1d11df0c3ba6cd4aa01ada3d2293f3", + "hash_raw": "a5a3e2fe9a8bf83ea37aabafdd7fd5f6e274f8ef3d1fb46ce56766cec8f13215", "size": 95563022336, "sparse": true, "full_check": true, "has_ab": false, - "ondevice_hash": "498a5bc3ed091663543d2882831b45e3f92743df458a901cf6f64e791e518cb2" + "ondevice_hash": "2def7bc3fed2cdcbaf4869fd2f68ad1d62f6d89b423d44c7114faf22f449c29f" }, { "name": "userdata_30", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_30-a60a00504f28f72179c44dfa539ef447c8e12a614aa23cef29e4b04c394505f1.img.xz", - "hash": "c32098db26bf2aefd1fa6184e3ac5db304adb35bedc6693b8729046c40aad7fb", - "hash_raw": "a60a00504f28f72179c44dfa539ef447c8e12a614aa23cef29e4b04c394505f1", + "url": "https://commadist.azureedge.net/agnosupdate/userdata_30-b86b949d9ed1978e792fccf59c1484238890e2cb904a501db64ed96bb91df4f9.img.xz", + "hash": "e0cee51c596d77e265b3bae937e9a3b4f317d0e80c091f03ca1bfbbdadbc6536", + "hash_raw": "b86b949d9ed1978e792fccf59c1484238890e2cb904a501db64ed96bb91df4f9", "size": 32212254720, "sparse": true, "full_check": true, "has_ab": false, - "ondevice_hash": "cf16e217c88d92bc99439cd3e8e24e6a1776bfc4b175044643bfc8b42968bd38" + "ondevice_hash": "38d7e4d3105102ca5a8dedb2764cb16f6e36766bbbb9d87e557e0d727ec37a58" } ] \ No newline at end of file diff --git a/system/hardware/tici/updater_magic b/system/hardware/tici/updater_magic index 248797350..b4dfa9be2 100755 --- a/system/hardware/tici/updater_magic +++ b/system/hardware/tici/updater_magic @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ffc9893e48b10096062f5fd1a14016addf7adb969f20f31ff26e68579992283c -size 20780744 +oid sha256:7990262878becdf2eaed40ffcc96835a6fc6bc4bdf52f4df88e8b6fcadd1bff8 +size 13664323 From af24fd68420615d4c6095f1f9f3aa92f627170b4 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Fri, 31 Oct 2025 00:24:22 +0800 Subject: [PATCH 288/910] remove qrcode library from third_party (#36528) --- third_party/qrcode/QrCode.cc | 862 ---------------------------------- third_party/qrcode/QrCode.hpp | 556 ---------------------- 2 files changed, 1418 deletions(-) delete mode 100644 third_party/qrcode/QrCode.cc delete mode 100644 third_party/qrcode/QrCode.hpp diff --git a/third_party/qrcode/QrCode.cc b/third_party/qrcode/QrCode.cc deleted file mode 100644 index b9de86215..000000000 --- a/third_party/qrcode/QrCode.cc +++ /dev/null @@ -1,862 +0,0 @@ -/* - * QR Code generator library (C++) - * - * Copyright (c) Project Nayuki. (MIT License) - * https://www.nayuki.io/page/qr-code-generator-library - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - The Software is provided "as is", without warranty of any kind, express or - * implied, including but not limited to the warranties of merchantability, - * fitness for a particular purpose and noninfringement. In no event shall the - * authors or copyright holders be liable for any claim, damages or other - * liability, whether in an action of contract, tort or otherwise, arising from, - * out of or in connection with the Software or the use or other dealings in the - * Software. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include "QrCode.hpp" - -using std::int8_t; -using std::uint8_t; -using std::size_t; -using std::vector; - - -namespace qrcodegen { - -QrSegment::Mode::Mode(int mode, int cc0, int cc1, int cc2) : - modeBits(mode) { - numBitsCharCount[0] = cc0; - numBitsCharCount[1] = cc1; - numBitsCharCount[2] = cc2; -} - - -int QrSegment::Mode::getModeBits() const { - return modeBits; -} - - -int QrSegment::Mode::numCharCountBits(int ver) const { - return numBitsCharCount[(ver + 7) / 17]; -} - - -const QrSegment::Mode QrSegment::Mode::NUMERIC (0x1, 10, 12, 14); -const QrSegment::Mode QrSegment::Mode::ALPHANUMERIC(0x2, 9, 11, 13); -const QrSegment::Mode QrSegment::Mode::BYTE (0x4, 8, 16, 16); -const QrSegment::Mode QrSegment::Mode::KANJI (0x8, 8, 10, 12); -const QrSegment::Mode QrSegment::Mode::ECI (0x7, 0, 0, 0); - - -QrSegment QrSegment::makeBytes(const vector &data) { - if (data.size() > static_cast(INT_MAX)) - throw std::length_error("Data too long"); - BitBuffer bb; - for (uint8_t b : data) - bb.appendBits(b, 8); - return QrSegment(Mode::BYTE, static_cast(data.size()), std::move(bb)); -} - - -QrSegment QrSegment::makeNumeric(const char *digits) { - BitBuffer bb; - int accumData = 0; - int accumCount = 0; - int charCount = 0; - for (; *digits != '\0'; digits++, charCount++) { - char c = *digits; - if (c < '0' || c > '9') - throw std::domain_error("String contains non-numeric characters"); - accumData = accumData * 10 + (c - '0'); - accumCount++; - if (accumCount == 3) { - bb.appendBits(static_cast(accumData), 10); - accumData = 0; - accumCount = 0; - } - } - if (accumCount > 0) // 1 or 2 digits remaining - bb.appendBits(static_cast(accumData), accumCount * 3 + 1); - return QrSegment(Mode::NUMERIC, charCount, std::move(bb)); -} - - -QrSegment QrSegment::makeAlphanumeric(const char *text) { - BitBuffer bb; - int accumData = 0; - int accumCount = 0; - int charCount = 0; - for (; *text != '\0'; text++, charCount++) { - const char *temp = std::strchr(ALPHANUMERIC_CHARSET, *text); - if (temp == nullptr) - throw std::domain_error("String contains unencodable characters in alphanumeric mode"); - accumData = accumData * 45 + static_cast(temp - ALPHANUMERIC_CHARSET); - accumCount++; - if (accumCount == 2) { - bb.appendBits(static_cast(accumData), 11); - accumData = 0; - accumCount = 0; - } - } - if (accumCount > 0) // 1 character remaining - bb.appendBits(static_cast(accumData), 6); - return QrSegment(Mode::ALPHANUMERIC, charCount, std::move(bb)); -} - - -vector QrSegment::makeSegments(const char *text) { - // Select the most efficient segment encoding automatically - vector result; - if (*text == '\0'); // Leave result empty - else if (isNumeric(text)) - result.push_back(makeNumeric(text)); - else if (isAlphanumeric(text)) - result.push_back(makeAlphanumeric(text)); - else { - vector bytes; - for (; *text != '\0'; text++) - bytes.push_back(static_cast(*text)); - result.push_back(makeBytes(bytes)); - } - return result; -} - - -QrSegment QrSegment::makeEci(long assignVal) { - BitBuffer bb; - if (assignVal < 0) - throw std::domain_error("ECI assignment value out of range"); - else if (assignVal < (1 << 7)) - bb.appendBits(static_cast(assignVal), 8); - else if (assignVal < (1 << 14)) { - bb.appendBits(2, 2); - bb.appendBits(static_cast(assignVal), 14); - } else if (assignVal < 1000000L) { - bb.appendBits(6, 3); - bb.appendBits(static_cast(assignVal), 21); - } else - throw std::domain_error("ECI assignment value out of range"); - return QrSegment(Mode::ECI, 0, std::move(bb)); -} - - -QrSegment::QrSegment(Mode md, int numCh, const std::vector &dt) : - mode(md), - numChars(numCh), - data(dt) { - if (numCh < 0) - throw std::domain_error("Invalid value"); -} - - -QrSegment::QrSegment(Mode md, int numCh, std::vector &&dt) : - mode(md), - numChars(numCh), - data(std::move(dt)) { - if (numCh < 0) - throw std::domain_error("Invalid value"); -} - - -int QrSegment::getTotalBits(const vector &segs, int version) { - int result = 0; - for (const QrSegment &seg : segs) { - int ccbits = seg.mode.numCharCountBits(version); - if (seg.numChars >= (1L << ccbits)) - return -1; // The segment's length doesn't fit the field's bit width - if (4 + ccbits > INT_MAX - result) - return -1; // The sum will overflow an int type - result += 4 + ccbits; - if (seg.data.size() > static_cast(INT_MAX - result)) - return -1; // The sum will overflow an int type - result += static_cast(seg.data.size()); - } - return result; -} - - -bool QrSegment::isAlphanumeric(const char *text) { - for (; *text != '\0'; text++) { - if (std::strchr(ALPHANUMERIC_CHARSET, *text) == nullptr) - return false; - } - return true; -} - - -bool QrSegment::isNumeric(const char *text) { - for (; *text != '\0'; text++) { - char c = *text; - if (c < '0' || c > '9') - return false; - } - return true; -} - - -QrSegment::Mode QrSegment::getMode() const { - return mode; -} - - -int QrSegment::getNumChars() const { - return numChars; -} - - -const std::vector &QrSegment::getData() const { - return data; -} - - -const char *QrSegment::ALPHANUMERIC_CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:"; - - - -int QrCode::getFormatBits(Ecc ecl) { - switch (ecl) { - case Ecc::LOW : return 1; - case Ecc::MEDIUM : return 0; - case Ecc::QUARTILE: return 3; - case Ecc::HIGH : return 2; - default: throw std::logic_error("Assertion error"); - } -} - - -QrCode QrCode::encodeText(const char *text, Ecc ecl) { - vector segs = QrSegment::makeSegments(text); - return encodeSegments(segs, ecl); -} - - -QrCode QrCode::encodeBinary(const vector &data, Ecc ecl) { - vector segs{QrSegment::makeBytes(data)}; - return encodeSegments(segs, ecl); -} - - -QrCode QrCode::encodeSegments(const vector &segs, Ecc ecl, - int minVersion, int maxVersion, int mask, bool boostEcl) { - if (!(MIN_VERSION <= minVersion && minVersion <= maxVersion && maxVersion <= MAX_VERSION) || mask < -1 || mask > 7) - throw std::invalid_argument("Invalid value"); - - // Find the minimal version number to use - int version, dataUsedBits; - for (version = minVersion; ; version++) { - int dataCapacityBits = getNumDataCodewords(version, ecl) * 8; // Number of data bits available - dataUsedBits = QrSegment::getTotalBits(segs, version); - if (dataUsedBits != -1 && dataUsedBits <= dataCapacityBits) - break; // This version number is found to be suitable - if (version >= maxVersion) { // All versions in the range could not fit the given data - std::ostringstream sb; - if (dataUsedBits == -1) - sb << "Segment too long"; - else { - sb << "Data length = " << dataUsedBits << " bits, "; - sb << "Max capacity = " << dataCapacityBits << " bits"; - } - throw data_too_long(sb.str()); - } - } - if (dataUsedBits == -1) - throw std::logic_error("Assertion error"); - - // Increase the error correction level while the data still fits in the current version number - for (Ecc newEcl : vector{Ecc::MEDIUM, Ecc::QUARTILE, Ecc::HIGH}) { // From low to high - if (boostEcl && dataUsedBits <= getNumDataCodewords(version, newEcl) * 8) - ecl = newEcl; - } - - // Concatenate all segments to create the data bit string - BitBuffer bb; - for (const QrSegment &seg : segs) { - bb.appendBits(static_cast(seg.getMode().getModeBits()), 4); - bb.appendBits(static_cast(seg.getNumChars()), seg.getMode().numCharCountBits(version)); - bb.insert(bb.end(), seg.getData().begin(), seg.getData().end()); - } - if (bb.size() != static_cast(dataUsedBits)) - throw std::logic_error("Assertion error"); - - // Add terminator and pad up to a byte if applicable - size_t dataCapacityBits = static_cast(getNumDataCodewords(version, ecl)) * 8; - if (bb.size() > dataCapacityBits) - throw std::logic_error("Assertion error"); - bb.appendBits(0, std::min(4, static_cast(dataCapacityBits - bb.size()))); - bb.appendBits(0, (8 - static_cast(bb.size() % 8)) % 8); - if (bb.size() % 8 != 0) - throw std::logic_error("Assertion error"); - - // Pad with alternating bytes until data capacity is reached - for (uint8_t padByte = 0xEC; bb.size() < dataCapacityBits; padByte ^= 0xEC ^ 0x11) - bb.appendBits(padByte, 8); - - // Pack bits into bytes in big endian - vector dataCodewords(bb.size() / 8); - for (size_t i = 0; i < bb.size(); i++) - dataCodewords[i >> 3] |= (bb.at(i) ? 1 : 0) << (7 - (i & 7)); - - // Create the QR Code object - return QrCode(version, ecl, dataCodewords, mask); -} - - -QrCode::QrCode(int ver, Ecc ecl, const vector &dataCodewords, int msk) : - // Initialize fields and check arguments - version(ver), - errorCorrectionLevel(ecl) { - if (ver < MIN_VERSION || ver > MAX_VERSION) - throw std::domain_error("Version value out of range"); - if (msk < -1 || msk > 7) - throw std::domain_error("Mask value out of range"); - size = ver * 4 + 17; - size_t sz = static_cast(size); - modules = vector >(sz, vector(sz)); // Initially all white - isFunction = vector >(sz, vector(sz)); - - // Compute ECC, draw modules - drawFunctionPatterns(); - const vector allCodewords = addEccAndInterleave(dataCodewords); - drawCodewords(allCodewords); - - // Do masking - if (msk == -1) { // Automatically choose best mask - long minPenalty = LONG_MAX; - for (int i = 0; i < 8; i++) { - applyMask(i); - drawFormatBits(i); - long penalty = getPenaltyScore(); - if (penalty < minPenalty) { - msk = i; - minPenalty = penalty; - } - applyMask(i); // Undoes the mask due to XOR - } - } - if (msk < 0 || msk > 7) - throw std::logic_error("Assertion error"); - this->mask = msk; - applyMask(msk); // Apply the final choice of mask - drawFormatBits(msk); // Overwrite old format bits - - isFunction.clear(); - isFunction.shrink_to_fit(); -} - - -int QrCode::getVersion() const { - return version; -} - - -int QrCode::getSize() const { - return size; -} - - -QrCode::Ecc QrCode::getErrorCorrectionLevel() const { - return errorCorrectionLevel; -} - - -int QrCode::getMask() const { - return mask; -} - - -bool QrCode::getModule(int x, int y) const { - return 0 <= x && x < size && 0 <= y && y < size && module(x, y); -} - - -std::string QrCode::toSvgString(int border) const { - if (border < 0) - throw std::domain_error("Border must be non-negative"); - if (border > INT_MAX / 2 || border * 2 > INT_MAX - size) - throw std::overflow_error("Border too large"); - - std::ostringstream sb; - sb << "\n"; - sb << "\n"; - sb << "\n"; - sb << "\t\n"; - sb << "\t\n"; - sb << "\n"; - return sb.str(); -} - - -void QrCode::drawFunctionPatterns() { - // Draw horizontal and vertical timing patterns - for (int i = 0; i < size; i++) { - setFunctionModule(6, i, i % 2 == 0); - setFunctionModule(i, 6, i % 2 == 0); - } - - // Draw 3 finder patterns (all corners except bottom right; overwrites some timing modules) - drawFinderPattern(3, 3); - drawFinderPattern(size - 4, 3); - drawFinderPattern(3, size - 4); - - // Draw numerous alignment patterns - const vector alignPatPos = getAlignmentPatternPositions(); - size_t numAlign = alignPatPos.size(); - for (size_t i = 0; i < numAlign; i++) { - for (size_t j = 0; j < numAlign; j++) { - // Don't draw on the three finder corners - if (!((i == 0 && j == 0) || (i == 0 && j == numAlign - 1) || (i == numAlign - 1 && j == 0))) - drawAlignmentPattern(alignPatPos.at(i), alignPatPos.at(j)); - } - } - - // Draw configuration data - drawFormatBits(0); // Dummy mask value; overwritten later in the constructor - drawVersion(); -} - - -void QrCode::drawFormatBits(int msk) { - // Calculate error correction code and pack bits - int data = getFormatBits(errorCorrectionLevel) << 3 | msk; // errCorrLvl is uint2, msk is uint3 - int rem = data; - for (int i = 0; i < 10; i++) - rem = (rem << 1) ^ ((rem >> 9) * 0x537); - int bits = (data << 10 | rem) ^ 0x5412; // uint15 - if (bits >> 15 != 0) - throw std::logic_error("Assertion error"); - - // Draw first copy - for (int i = 0; i <= 5; i++) - setFunctionModule(8, i, getBit(bits, i)); - setFunctionModule(8, 7, getBit(bits, 6)); - setFunctionModule(8, 8, getBit(bits, 7)); - setFunctionModule(7, 8, getBit(bits, 8)); - for (int i = 9; i < 15; i++) - setFunctionModule(14 - i, 8, getBit(bits, i)); - - // Draw second copy - for (int i = 0; i < 8; i++) - setFunctionModule(size - 1 - i, 8, getBit(bits, i)); - for (int i = 8; i < 15; i++) - setFunctionModule(8, size - 15 + i, getBit(bits, i)); - setFunctionModule(8, size - 8, true); // Always black -} - - -void QrCode::drawVersion() { - if (version < 7) - return; - - // Calculate error correction code and pack bits - int rem = version; // version is uint6, in the range [7, 40] - for (int i = 0; i < 12; i++) - rem = (rem << 1) ^ ((rem >> 11) * 0x1F25); - long bits = static_cast(version) << 12 | rem; // uint18 - if (bits >> 18 != 0) - throw std::logic_error("Assertion error"); - - // Draw two copies - for (int i = 0; i < 18; i++) { - bool bit = getBit(bits, i); - int a = size - 11 + i % 3; - int b = i / 3; - setFunctionModule(a, b, bit); - setFunctionModule(b, a, bit); - } -} - - -void QrCode::drawFinderPattern(int x, int y) { - for (int dy = -4; dy <= 4; dy++) { - for (int dx = -4; dx <= 4; dx++) { - int dist = std::max(std::abs(dx), std::abs(dy)); // Chebyshev/infinity norm - int xx = x + dx, yy = y + dy; - if (0 <= xx && xx < size && 0 <= yy && yy < size) - setFunctionModule(xx, yy, dist != 2 && dist != 4); - } - } -} - - -void QrCode::drawAlignmentPattern(int x, int y) { - for (int dy = -2; dy <= 2; dy++) { - for (int dx = -2; dx <= 2; dx++) - setFunctionModule(x + dx, y + dy, std::max(std::abs(dx), std::abs(dy)) != 1); - } -} - - -void QrCode::setFunctionModule(int x, int y, bool isBlack) { - size_t ux = static_cast(x); - size_t uy = static_cast(y); - modules .at(uy).at(ux) = isBlack; - isFunction.at(uy).at(ux) = true; -} - - -bool QrCode::module(int x, int y) const { - return modules.at(static_cast(y)).at(static_cast(x)); -} - - -vector QrCode::addEccAndInterleave(const vector &data) const { - if (data.size() != static_cast(getNumDataCodewords(version, errorCorrectionLevel))) - throw std::invalid_argument("Invalid argument"); - - // Calculate parameter numbers - int numBlocks = NUM_ERROR_CORRECTION_BLOCKS[static_cast(errorCorrectionLevel)][version]; - int blockEccLen = ECC_CODEWORDS_PER_BLOCK [static_cast(errorCorrectionLevel)][version]; - int rawCodewords = getNumRawDataModules(version) / 8; - int numShortBlocks = numBlocks - rawCodewords % numBlocks; - int shortBlockLen = rawCodewords / numBlocks; - - // Split data into blocks and append ECC to each block - vector > blocks; - const vector rsDiv = reedSolomonComputeDivisor(blockEccLen); - for (int i = 0, k = 0; i < numBlocks; i++) { - vector dat(data.cbegin() + k, data.cbegin() + (k + shortBlockLen - blockEccLen + (i < numShortBlocks ? 0 : 1))); - k += static_cast(dat.size()); - const vector ecc = reedSolomonComputeRemainder(dat, rsDiv); - if (i < numShortBlocks) - dat.push_back(0); - dat.insert(dat.end(), ecc.cbegin(), ecc.cend()); - blocks.push_back(std::move(dat)); - } - - // Interleave (not concatenate) the bytes from every block into a single sequence - vector result; - for (size_t i = 0; i < blocks.at(0).size(); i++) { - for (size_t j = 0; j < blocks.size(); j++) { - // Skip the padding byte in short blocks - if (i != static_cast(shortBlockLen - blockEccLen) || j >= static_cast(numShortBlocks)) - result.push_back(blocks.at(j).at(i)); - } - } - if (result.size() != static_cast(rawCodewords)) - throw std::logic_error("Assertion error"); - return result; -} - - -void QrCode::drawCodewords(const vector &data) { - if (data.size() != static_cast(getNumRawDataModules(version) / 8)) - throw std::invalid_argument("Invalid argument"); - - size_t i = 0; // Bit index into the data - // Do the funny zigzag scan - for (int right = size - 1; right >= 1; right -= 2) { // Index of right column in each column pair - if (right == 6) - right = 5; - for (int vert = 0; vert < size; vert++) { // Vertical counter - for (int j = 0; j < 2; j++) { - size_t x = static_cast(right - j); // Actual x coordinate - bool upward = ((right + 1) & 2) == 0; - size_t y = static_cast(upward ? size - 1 - vert : vert); // Actual y coordinate - if (!isFunction.at(y).at(x) && i < data.size() * 8) { - modules.at(y).at(x) = getBit(data.at(i >> 3), 7 - static_cast(i & 7)); - i++; - } - // If this QR Code has any remainder bits (0 to 7), they were assigned as - // 0/false/white by the constructor and are left unchanged by this method - } - } - } - if (i != data.size() * 8) - throw std::logic_error("Assertion error"); -} - - -void QrCode::applyMask(int msk) { - if (msk < 0 || msk > 7) - throw std::domain_error("Mask value out of range"); - size_t sz = static_cast(size); - for (size_t y = 0; y < sz; y++) { - for (size_t x = 0; x < sz; x++) { - bool invert; - switch (msk) { - case 0: invert = (x + y) % 2 == 0; break; - case 1: invert = y % 2 == 0; break; - case 2: invert = x % 3 == 0; break; - case 3: invert = (x + y) % 3 == 0; break; - case 4: invert = (x / 3 + y / 2) % 2 == 0; break; - case 5: invert = x * y % 2 + x * y % 3 == 0; break; - case 6: invert = (x * y % 2 + x * y % 3) % 2 == 0; break; - case 7: invert = ((x + y) % 2 + x * y % 3) % 2 == 0; break; - default: throw std::logic_error("Assertion error"); - } - modules.at(y).at(x) = modules.at(y).at(x) ^ (invert & !isFunction.at(y).at(x)); - } - } -} - - -long QrCode::getPenaltyScore() const { - long result = 0; - - // Adjacent modules in row having same color, and finder-like patterns - for (int y = 0; y < size; y++) { - bool runColor = false; - int runX = 0; - std::array runHistory = {}; - for (int x = 0; x < size; x++) { - if (module(x, y) == runColor) { - runX++; - if (runX == 5) - result += PENALTY_N1; - else if (runX > 5) - result++; - } else { - finderPenaltyAddHistory(runX, runHistory); - if (!runColor) - result += finderPenaltyCountPatterns(runHistory) * PENALTY_N3; - runColor = module(x, y); - runX = 1; - } - } - result += finderPenaltyTerminateAndCount(runColor, runX, runHistory) * PENALTY_N3; - } - // Adjacent modules in column having same color, and finder-like patterns - for (int x = 0; x < size; x++) { - bool runColor = false; - int runY = 0; - std::array runHistory = {}; - for (int y = 0; y < size; y++) { - if (module(x, y) == runColor) { - runY++; - if (runY == 5) - result += PENALTY_N1; - else if (runY > 5) - result++; - } else { - finderPenaltyAddHistory(runY, runHistory); - if (!runColor) - result += finderPenaltyCountPatterns(runHistory) * PENALTY_N3; - runColor = module(x, y); - runY = 1; - } - } - result += finderPenaltyTerminateAndCount(runColor, runY, runHistory) * PENALTY_N3; - } - - // 2*2 blocks of modules having same color - for (int y = 0; y < size - 1; y++) { - for (int x = 0; x < size - 1; x++) { - bool color = module(x, y); - if ( color == module(x + 1, y) && - color == module(x, y + 1) && - color == module(x + 1, y + 1)) - result += PENALTY_N2; - } - } - - // Balance of black and white modules - int black = 0; - for (const vector &row : modules) { - for (bool color : row) { - if (color) - black++; - } - } - int total = size * size; // Note that size is odd, so black/total != 1/2 - // Compute the smallest integer k >= 0 such that (45-5k)% <= black/total <= (55+5k)% - int k = static_cast((std::abs(black * 20L - total * 10L) + total - 1) / total) - 1; - result += k * PENALTY_N4; - return result; -} - - -vector QrCode::getAlignmentPatternPositions() const { - if (version == 1) - return vector(); - else { - int numAlign = version / 7 + 2; - int step = (version == 32) ? 26 : - (version*4 + numAlign*2 + 1) / (numAlign*2 - 2) * 2; - vector result; - for (int i = 0, pos = size - 7; i < numAlign - 1; i++, pos -= step) - result.insert(result.begin(), pos); - result.insert(result.begin(), 6); - return result; - } -} - - -int QrCode::getNumRawDataModules(int ver) { - if (ver < MIN_VERSION || ver > MAX_VERSION) - throw std::domain_error("Version number out of range"); - int result = (16 * ver + 128) * ver + 64; - if (ver >= 2) { - int numAlign = ver / 7 + 2; - result -= (25 * numAlign - 10) * numAlign - 55; - if (ver >= 7) - result -= 36; - } - if (!(208 <= result && result <= 29648)) - throw std::logic_error("Assertion error"); - return result; -} - - -int QrCode::getNumDataCodewords(int ver, Ecc ecl) { - return getNumRawDataModules(ver) / 8 - - ECC_CODEWORDS_PER_BLOCK [static_cast(ecl)][ver] - * NUM_ERROR_CORRECTION_BLOCKS[static_cast(ecl)][ver]; -} - - -vector QrCode::reedSolomonComputeDivisor(int degree) { - if (degree < 1 || degree > 255) - throw std::domain_error("Degree out of range"); - // Polynomial coefficients are stored from highest to lowest power, excluding the leading term which is always 1. - // For example the polynomial x^3 + 255x^2 + 8x + 93 is stored as the uint8 array {255, 8, 93}. - vector result(static_cast(degree)); - result.at(result.size() - 1) = 1; // Start off with the monomial x^0 - - // Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{degree-1}), - // and drop the highest monomial term which is always 1x^degree. - // Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D). - uint8_t root = 1; - for (int i = 0; i < degree; i++) { - // Multiply the current product by (x - r^i) - for (size_t j = 0; j < result.size(); j++) { - result.at(j) = reedSolomonMultiply(result.at(j), root); - if (j + 1 < result.size()) - result.at(j) ^= result.at(j + 1); - } - root = reedSolomonMultiply(root, 0x02); - } - return result; -} - - -vector QrCode::reedSolomonComputeRemainder(const vector &data, const vector &divisor) { - vector result(divisor.size()); - for (uint8_t b : data) { // Polynomial division - uint8_t factor = b ^ result.at(0); - result.erase(result.begin()); - result.push_back(0); - for (size_t i = 0; i < result.size(); i++) - result.at(i) ^= reedSolomonMultiply(divisor.at(i), factor); - } - return result; -} - - -uint8_t QrCode::reedSolomonMultiply(uint8_t x, uint8_t y) { - // Russian peasant multiplication - int z = 0; - for (int i = 7; i >= 0; i--) { - z = (z << 1) ^ ((z >> 7) * 0x11D); - z ^= ((y >> i) & 1) * x; - } - if (z >> 8 != 0) - throw std::logic_error("Assertion error"); - return static_cast(z); -} - - -int QrCode::finderPenaltyCountPatterns(const std::array &runHistory) const { - int n = runHistory.at(1); - if (n > size * 3) - throw std::logic_error("Assertion error"); - bool core = n > 0 && runHistory.at(2) == n && runHistory.at(3) == n * 3 && runHistory.at(4) == n && runHistory.at(5) == n; - return (core && runHistory.at(0) >= n * 4 && runHistory.at(6) >= n ? 1 : 0) - + (core && runHistory.at(6) >= n * 4 && runHistory.at(0) >= n ? 1 : 0); -} - - -int QrCode::finderPenaltyTerminateAndCount(bool currentRunColor, int currentRunLength, std::array &runHistory) const { - if (currentRunColor) { // Terminate black run - finderPenaltyAddHistory(currentRunLength, runHistory); - currentRunLength = 0; - } - currentRunLength += size; // Add white border to final run - finderPenaltyAddHistory(currentRunLength, runHistory); - return finderPenaltyCountPatterns(runHistory); -} - - -void QrCode::finderPenaltyAddHistory(int currentRunLength, std::array &runHistory) const { - if (runHistory.at(0) == 0) - currentRunLength += size; // Add white border to initial run - std::copy_backward(runHistory.cbegin(), runHistory.cend() - 1, runHistory.end()); - runHistory.at(0) = currentRunLength; -} - - -bool QrCode::getBit(long x, int i) { - return ((x >> i) & 1) != 0; -} - - -/*---- Tables of constants ----*/ - -const int QrCode::PENALTY_N1 = 3; -const int QrCode::PENALTY_N2 = 3; -const int QrCode::PENALTY_N3 = 40; -const int QrCode::PENALTY_N4 = 10; - - -const int8_t QrCode::ECC_CODEWORDS_PER_BLOCK[4][41] = { - // Version: (note that index 0 is for padding, and is set to an illegal value) - //0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level - {-1, 7, 10, 15, 20, 26, 18, 20, 24, 30, 18, 20, 24, 26, 30, 22, 24, 28, 30, 28, 28, 28, 28, 30, 30, 26, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // Low - {-1, 10, 16, 26, 18, 24, 16, 18, 22, 22, 26, 30, 22, 22, 24, 24, 28, 28, 26, 26, 26, 26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28}, // Medium - {-1, 13, 22, 18, 26, 18, 24, 18, 22, 20, 24, 28, 26, 24, 20, 30, 24, 28, 28, 26, 30, 28, 30, 30, 30, 30, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // Quartile - {-1, 17, 28, 22, 16, 22, 28, 26, 26, 24, 28, 24, 28, 22, 24, 24, 30, 28, 28, 26, 28, 30, 24, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // High -}; - -const int8_t QrCode::NUM_ERROR_CORRECTION_BLOCKS[4][41] = { - // Version: (note that index 0 is for padding, and is set to an illegal value) - //0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level - {-1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25}, // Low - {-1, 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49}, // Medium - {-1, 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68}, // Quartile - {-1, 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81}, // High -}; - - -data_too_long::data_too_long(const std::string &msg) : - std::length_error(msg) {} - - - -BitBuffer::BitBuffer() - : std::vector() {} - - -void BitBuffer::appendBits(std::uint32_t val, int len) { - if (len < 0 || len > 31 || val >> len != 0) - throw std::domain_error("Value out of range"); - for (int i = len - 1; i >= 0; i--) // Append bit by bit - this->push_back(((val >> i) & 1) != 0); -} - -} diff --git a/third_party/qrcode/QrCode.hpp b/third_party/qrcode/QrCode.hpp deleted file mode 100644 index 7341e4102..000000000 --- a/third_party/qrcode/QrCode.hpp +++ /dev/null @@ -1,556 +0,0 @@ -/* - * QR Code generator library (C++) - * - * Copyright (c) Project Nayuki. (MIT License) - * https://www.nayuki.io/page/qr-code-generator-library - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - The Software is provided "as is", without warranty of any kind, express or - * implied, including but not limited to the warranties of merchantability, - * fitness for a particular purpose and noninfringement. In no event shall the - * authors or copyright holders be liable for any claim, damages or other - * liability, whether in an action of contract, tort or otherwise, arising from, - * out of or in connection with the Software or the use or other dealings in the - * Software. - */ - -#pragma once - -#include -#include -#include -#include -#include - - -namespace qrcodegen { - -/* - * A segment of character/binary/control data in a QR Code symbol. - * Instances of this class are immutable. - * The mid-level way to create a segment is to take the payload data - * and call a static factory function such as QrSegment::makeNumeric(). - * The low-level way to create a segment is to custom-make the bit buffer - * and call the QrSegment() constructor with appropriate values. - * This segment class imposes no length restrictions, but QR Codes have restrictions. - * Even in the most favorable conditions, a QR Code can only hold 7089 characters of data. - * Any segment longer than this is meaningless for the purpose of generating QR Codes. - */ -class QrSegment final { - - /*---- Public helper enumeration ----*/ - - /* - * Describes how a segment's data bits are interpreted. Immutable. - */ - public: class Mode final { - - /*-- Constants --*/ - - public: static const Mode NUMERIC; - public: static const Mode ALPHANUMERIC; - public: static const Mode BYTE; - public: static const Mode KANJI; - public: static const Mode ECI; - - - /*-- Fields --*/ - - // The mode indicator bits, which is a uint4 value (range 0 to 15). - private: int modeBits; - - // Number of character count bits for three different version ranges. - private: int numBitsCharCount[3]; - - - /*-- Constructor --*/ - - private: Mode(int mode, int cc0, int cc1, int cc2); - - - /*-- Methods --*/ - - /* - * (Package-private) Returns the mode indicator bits, which is an unsigned 4-bit value (range 0 to 15). - */ - public: int getModeBits() const; - - /* - * (Package-private) Returns the bit width of the character count field for a segment in - * this mode in a QR Code at the given version number. The result is in the range [0, 16]. - */ - public: int numCharCountBits(int ver) const; - - }; - - - - /*---- Static factory functions (mid level) ----*/ - - /* - * Returns a segment representing the given binary data encoded in - * byte mode. All input byte vectors are acceptable. Any text string - * can be converted to UTF-8 bytes and encoded as a byte mode segment. - */ - public: static QrSegment makeBytes(const std::vector &data); - - - /* - * Returns a segment representing the given string of decimal digits encoded in numeric mode. - */ - public: static QrSegment makeNumeric(const char *digits); - - - /* - * Returns a segment representing the given text string encoded in alphanumeric mode. - * The characters allowed are: 0 to 9, A to Z (uppercase only), space, - * dollar, percent, asterisk, plus, hyphen, period, slash, colon. - */ - public: static QrSegment makeAlphanumeric(const char *text); - - - /* - * Returns a list of zero or more segments to represent the given text string. The result - * may use various segment modes and switch modes to optimize the length of the bit stream. - */ - public: static std::vector makeSegments(const char *text); - - - /* - * Returns a segment representing an Extended Channel Interpretation - * (ECI) designator with the given assignment value. - */ - public: static QrSegment makeEci(long assignVal); - - - /*---- Public static helper functions ----*/ - - /* - * Tests whether the given string can be encoded as a segment in alphanumeric mode. - * A string is encodable iff each character is in the following set: 0 to 9, A to Z - * (uppercase only), space, dollar, percent, asterisk, plus, hyphen, period, slash, colon. - */ - public: static bool isAlphanumeric(const char *text); - - - /* - * Tests whether the given string can be encoded as a segment in numeric mode. - * A string is encodable iff each character is in the range 0 to 9. - */ - public: static bool isNumeric(const char *text); - - - - /*---- Instance fields ----*/ - - /* The mode indicator of this segment. Accessed through getMode(). */ - private: Mode mode; - - /* The length of this segment's unencoded data. Measured in characters for - * numeric/alphanumeric/kanji mode, bytes for byte mode, and 0 for ECI mode. - * Always zero or positive. Not the same as the data's bit length. - * Accessed through getNumChars(). */ - private: int numChars; - - /* The data bits of this segment. Accessed through getData(). */ - private: std::vector data; - - - /*---- Constructors (low level) ----*/ - - /* - * Creates a new QR Code segment with the given attributes and data. - * The character count (numCh) must agree with the mode and the bit buffer length, - * but the constraint isn't checked. The given bit buffer is copied and stored. - */ - public: QrSegment(Mode md, int numCh, const std::vector &dt); - - - /* - * Creates a new QR Code segment with the given parameters and data. - * The character count (numCh) must agree with the mode and the bit buffer length, - * but the constraint isn't checked. The given bit buffer is moved and stored. - */ - public: QrSegment(Mode md, int numCh, std::vector &&dt); - - - /*---- Methods ----*/ - - /* - * Returns the mode field of this segment. - */ - public: Mode getMode() const; - - - /* - * Returns the character count field of this segment. - */ - public: int getNumChars() const; - - - /* - * Returns the data bits of this segment. - */ - public: const std::vector &getData() const; - - - // (Package-private) Calculates the number of bits needed to encode the given segments at - // the given version. Returns a non-negative number if successful. Otherwise returns -1 if a - // segment has too many characters to fit its length field, or the total bits exceeds INT_MAX. - public: static int getTotalBits(const std::vector &segs, int version); - - - /*---- Private constant ----*/ - - /* The set of all legal characters in alphanumeric mode, where - * each character value maps to the index in the string. */ - private: static const char *ALPHANUMERIC_CHARSET; - -}; - - - -/* - * A QR Code symbol, which is a type of two-dimension barcode. - * Invented by Denso Wave and described in the ISO/IEC 18004 standard. - * Instances of this class represent an immutable square grid of black and white cells. - * The class provides static factory functions to create a QR Code from text or binary data. - * The class covers the QR Code Model 2 specification, supporting all versions (sizes) - * from 1 to 40, all 4 error correction levels, and 4 character encoding modes. - * - * Ways to create a QR Code object: - * - High level: Take the payload data and call QrCode::encodeText() or QrCode::encodeBinary(). - * - Mid level: Custom-make the list of segments and call QrCode::encodeSegments(). - * - Low level: Custom-make the array of data codeword bytes (including - * segment headers and final padding, excluding error correction codewords), - * supply the appropriate version number, and call the QrCode() constructor. - * (Note that all ways require supplying the desired error correction level.) - */ -class QrCode final { - - /*---- Public helper enumeration ----*/ - - /* - * The error correction level in a QR Code symbol. - */ - public: enum class Ecc { - LOW = 0 , // The QR Code can tolerate about 7% erroneous codewords - MEDIUM , // The QR Code can tolerate about 15% erroneous codewords - QUARTILE, // The QR Code can tolerate about 25% erroneous codewords - HIGH , // The QR Code can tolerate about 30% erroneous codewords - }; - - - // Returns a value in the range 0 to 3 (unsigned 2-bit integer). - private: static int getFormatBits(Ecc ecl); - - - - /*---- Static factory functions (high level) ----*/ - - /* - * Returns a QR Code representing the given Unicode text string at the given error correction level. - * As a conservative upper bound, this function is guaranteed to succeed for strings that have 2953 or fewer - * UTF-8 code units (not Unicode code points) if the low error correction level is used. The smallest possible - * QR Code version is automatically chosen for the output. The ECC level of the result may be higher than - * the ecl argument if it can be done without increasing the version. - */ - public: static QrCode encodeText(const char *text, Ecc ecl); - - - /* - * Returns a QR Code representing the given binary data at the given error correction level. - * This function always encodes using the binary segment mode, not any text mode. The maximum number of - * bytes allowed is 2953. The smallest possible QR Code version is automatically chosen for the output. - * The ECC level of the result may be higher than the ecl argument if it can be done without increasing the version. - */ - public: static QrCode encodeBinary(const std::vector &data, Ecc ecl); - - - /*---- Static factory functions (mid level) ----*/ - - /* - * Returns a QR Code representing the given segments with the given encoding parameters. - * The smallest possible QR Code version within the given range is automatically - * chosen for the output. Iff boostEcl is true, then the ECC level of the result - * may be higher than the ecl argument if it can be done without increasing the - * version. The mask number is either between 0 to 7 (inclusive) to force that - * mask, or -1 to automatically choose an appropriate mask (which may be slow). - * This function allows the user to create a custom sequence of segments that switches - * between modes (such as alphanumeric and byte) to encode text in less space. - * This is a mid-level API; the high-level API is encodeText() and encodeBinary(). - */ - public: static QrCode encodeSegments(const std::vector &segs, Ecc ecl, - int minVersion=1, int maxVersion=40, int mask=-1, bool boostEcl=true); // All optional parameters - - - - /*---- Instance fields ----*/ - - // Immutable scalar parameters: - - /* The version number of this QR Code, which is between 1 and 40 (inclusive). - * This determines the size of this barcode. */ - private: int version; - - /* The width and height of this QR Code, measured in modules, between - * 21 and 177 (inclusive). This is equal to version * 4 + 17. */ - private: int size; - - /* The error correction level used in this QR Code. */ - private: Ecc errorCorrectionLevel; - - /* The index of the mask pattern used in this QR Code, which is between 0 and 7 (inclusive). - * Even if a QR Code is created with automatic masking requested (mask = -1), - * the resulting object still has a mask value between 0 and 7. */ - private: int mask; - - // Private grids of modules/pixels, with dimensions of size*size: - - // The modules of this QR Code (false = white, true = black). - // Immutable after constructor finishes. Accessed through getModule(). - private: std::vector > modules; - - // Indicates function modules that are not subjected to masking. Discarded when constructor finishes. - private: std::vector > isFunction; - - - - /*---- Constructor (low level) ----*/ - - /* - * Creates a new QR Code with the given version number, - * error correction level, data codeword bytes, and mask number. - * This is a low-level API that most users should not use directly. - * A mid-level API is the encodeSegments() function. - */ - public: QrCode(int ver, Ecc ecl, const std::vector &dataCodewords, int msk); - - - - /*---- Public instance methods ----*/ - - /* - * Returns this QR Code's version, in the range [1, 40]. - */ - public: int getVersion() const; - - - /* - * Returns this QR Code's size, in the range [21, 177]. - */ - public: int getSize() const; - - - /* - * Returns this QR Code's error correction level. - */ - public: Ecc getErrorCorrectionLevel() const; - - - /* - * Returns this QR Code's mask, in the range [0, 7]. - */ - public: int getMask() const; - - - /* - * Returns the color of the module (pixel) at the given coordinates, which is false - * for white or true for black. The top left corner has the coordinates (x=0, y=0). - * If the given coordinates are out of bounds, then false (white) is returned. - */ - public: bool getModule(int x, int y) const; - - - /* - * Returns a string of SVG code for an image depicting this QR Code, with the given number - * of border modules. The string always uses Unix newlines (\n), regardless of the platform. - */ - public: std::string toSvgString(int border) const; - - - - /*---- Private helper methods for constructor: Drawing function modules ----*/ - - // Reads this object's version field, and draws and marks all function modules. - private: void drawFunctionPatterns(); - - - // Draws two copies of the format bits (with its own error correction code) - // based on the given mask and this object's error correction level field. - private: void drawFormatBits(int msk); - - - // Draws two copies of the version bits (with its own error correction code), - // based on this object's version field, iff 7 <= version <= 40. - private: void drawVersion(); - - - // Draws a 9*9 finder pattern including the border separator, - // with the center module at (x, y). Modules can be out of bounds. - private: void drawFinderPattern(int x, int y); - - - // Draws a 5*5 alignment pattern, with the center module - // at (x, y). All modules must be in bounds. - private: void drawAlignmentPattern(int x, int y); - - - // Sets the color of a module and marks it as a function module. - // Only used by the constructor. Coordinates must be in bounds. - private: void setFunctionModule(int x, int y, bool isBlack); - - - // Returns the color of the module at the given coordinates, which must be in range. - private: bool module(int x, int y) const; - - - /*---- Private helper methods for constructor: Codewords and masking ----*/ - - // Returns a new byte string representing the given data with the appropriate error correction - // codewords appended to it, based on this object's version and error correction level. - private: std::vector addEccAndInterleave(const std::vector &data) const; - - - // Draws the given sequence of 8-bit codewords (data and error correction) onto the entire - // data area of this QR Code. Function modules need to be marked off before this is called. - private: void drawCodewords(const std::vector &data); - - - // XORs the codeword modules in this QR Code with the given mask pattern. - // The function modules must be marked and the codeword bits must be drawn - // before masking. Due to the arithmetic of XOR, calling applyMask() with - // the same mask value a second time will undo the mask. A final well-formed - // QR Code needs exactly one (not zero, two, etc.) mask applied. - private: void applyMask(int msk); - - - // Calculates and returns the penalty score based on state of this QR Code's current modules. - // This is used by the automatic mask choice algorithm to find the mask pattern that yields the lowest score. - private: long getPenaltyScore() const; - - - - /*---- Private helper functions ----*/ - - // Returns an ascending list of positions of alignment patterns for this version number. - // Each position is in the range [0,177), and are used on both the x and y axes. - // This could be implemented as lookup table of 40 variable-length lists of unsigned bytes. - private: std::vector getAlignmentPatternPositions() const; - - - // Returns the number of data bits that can be stored in a QR Code of the given version number, after - // all function modules are excluded. This includes remainder bits, so it might not be a multiple of 8. - // The result is in the range [208, 29648]. This could be implemented as a 40-entry lookup table. - private: static int getNumRawDataModules(int ver); - - - // Returns the number of 8-bit data (i.e. not error correction) codewords contained in any - // QR Code of the given version number and error correction level, with remainder bits discarded. - // This stateless pure function could be implemented as a (40*4)-cell lookup table. - private: static int getNumDataCodewords(int ver, Ecc ecl); - - - // Returns a Reed-Solomon ECC generator polynomial for the given degree. This could be - // implemented as a lookup table over all possible parameter values, instead of as an algorithm. - private: static std::vector reedSolomonComputeDivisor(int degree); - - - // Returns the Reed-Solomon error correction codeword for the given data and divisor polynomials. - private: static std::vector reedSolomonComputeRemainder(const std::vector &data, const std::vector &divisor); - - - // Returns the product of the two given field elements modulo GF(2^8/0x11D). - // All inputs are valid. This could be implemented as a 256*256 lookup table. - private: static std::uint8_t reedSolomonMultiply(std::uint8_t x, std::uint8_t y); - - - // Can only be called immediately after a white run is added, and - // returns either 0, 1, or 2. A helper function for getPenaltyScore(). - private: int finderPenaltyCountPatterns(const std::array &runHistory) const; - - - // Must be called at the end of a line (row or column) of modules. A helper function for getPenaltyScore(). - private: int finderPenaltyTerminateAndCount(bool currentRunColor, int currentRunLength, std::array &runHistory) const; - - - // Pushes the given value to the front and drops the last value. A helper function for getPenaltyScore(). - private: void finderPenaltyAddHistory(int currentRunLength, std::array &runHistory) const; - - - // Returns true iff the i'th bit of x is set to 1. - private: static bool getBit(long x, int i); - - - /*---- Constants and tables ----*/ - - // The minimum version number supported in the QR Code Model 2 standard. - public: static constexpr int MIN_VERSION = 1; - - // The maximum version number supported in the QR Code Model 2 standard. - public: static constexpr int MAX_VERSION = 40; - - - // For use in getPenaltyScore(), when evaluating which mask is best. - private: static const int PENALTY_N1; - private: static const int PENALTY_N2; - private: static const int PENALTY_N3; - private: static const int PENALTY_N4; - - - private: static const std::int8_t ECC_CODEWORDS_PER_BLOCK[4][41]; - private: static const std::int8_t NUM_ERROR_CORRECTION_BLOCKS[4][41]; - -}; - - - -/*---- Public exception class ----*/ - -/* - * Thrown when the supplied data does not fit any QR Code version. Ways to handle this exception include: - * - Decrease the error correction level if it was greater than Ecc::LOW. - * - If the encodeSegments() function was called with a maxVersion argument, then increase - * it if it was less than QrCode::MAX_VERSION. (This advice does not apply to the other - * factory functions because they search all versions up to QrCode::MAX_VERSION.) - * - Split the text data into better or optimal segments in order to reduce the number of bits required. - * - Change the text or binary data to be shorter. - * - Change the text to fit the character set of a particular segment mode (e.g. alphanumeric). - * - Propagate the error upward to the caller/user. - */ -class data_too_long : public std::length_error { - - public: explicit data_too_long(const std::string &msg); - -}; - - - -/* - * An appendable sequence of bits (0s and 1s). Mainly used by QrSegment. - */ -class BitBuffer final : public std::vector { - - /*---- Constructor ----*/ - - // Creates an empty bit buffer (length 0). - public: BitBuffer(); - - - - /*---- Method ----*/ - - // Appends the given number of low-order bits of the given value - // to this buffer. Requires 0 <= len <= 31 and val < 2^len. - public: void appendBits(std::uint32_t val, int len); - -}; - -} From fc4e5007fd55784a5aaf471744f431baa11b118f Mon Sep 17 00:00:00 2001 From: felsager <76905857+felsager@users.noreply.github.com> Date: Thu, 30 Oct 2025 13:29:38 -0700 Subject: [PATCH 289/910] latcontrol_torque: make feed-forward jerk independent of individual platform lag (#36334) --- selfdrive/controls/lib/latcontrol_torque.py | 19 ++++++++++++------- selfdrive/test/process_replay/ref_commit | 2 +- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/selfdrive/controls/lib/latcontrol_torque.py b/selfdrive/controls/lib/latcontrol_torque.py index 443fd1851..1e3d80c2d 100644 --- a/selfdrive/controls/lib/latcontrol_torque.py +++ b/selfdrive/controls/lib/latcontrol_torque.py @@ -27,8 +27,10 @@ INTERP_SPEEDS = [1, 1.5, 2.0, 3.0, 5, 7.5, 10, 15, 30] KP_INTERP = [250, 120, 65, 30, 11.5, 5.5, 3.5, 2.0, KP] LP_FILTER_CUTOFF_HZ = 1.2 +JERK_LOOKAHEAD_SECONDS = 0.19 +JERK_GAIN = 0.3 LAT_ACCEL_REQUEST_BUFFER_SECONDS = 1.0 -VERSION = 0 +VERSION = 0 # bump this when changing controller class LatControlTorque(LatControl): def __init__(self, CP, CI, dt): @@ -39,10 +41,12 @@ class LatControlTorque(LatControl): self.pid = PIDController([INTERP_SPEEDS, KP_INTERP], KI, KD, rate=1/self.dt) self.update_limits() self.steering_angle_deadzone_deg = self.torque_params.steeringAngleDeadzoneDeg + self.lookahead_frames = int(JERK_LOOKAHEAD_SECONDS / self.dt) self.lat_accel_request_buffer_len = int(LAT_ACCEL_REQUEST_BUFFER_SECONDS / self.dt) self.lat_accel_request_buffer = deque([0.] * self.lat_accel_request_buffer_len , maxlen=self.lat_accel_request_buffer_len) - self.previous_measurement = 0.0 + self.jerk_filter = FirstOrderFilter(0.0, 1 / (2 * np.pi * LP_FILTER_CUTOFF_HZ), self.dt) self.measurement_rate_filter = FirstOrderFilter(0.0, 1 / (2 * np.pi * LP_FILTER_CUTOFF_HZ), self.dt) + self.previous_measurement = 0.0 def update_live_torque_params(self, latAccelFactor, latAccelOffset, friction): self.torque_params.latAccelFactor = latAccelFactor @@ -68,25 +72,26 @@ class LatControlTorque(LatControl): delay_frames = int(np.clip(lat_delay / self.dt, 1, self.lat_accel_request_buffer_len)) expected_lateral_accel = self.lat_accel_request_buffer[-delay_frames] - # TODO factor out lateral jerk from error to later replace it with delay independent alternative + lookahead_idx = int(np.clip(-delay_frames + self.lookahead_frames, -self.lat_accel_request_buffer_len+1, -2)) + raw_lateral_jerk = (self.lat_accel_request_buffer[lookahead_idx+1] - self.lat_accel_request_buffer[lookahead_idx-1]) / (2 * self.dt) + desired_lateral_jerk = self.jerk_filter.update(raw_lateral_jerk) future_desired_lateral_accel = desired_curvature * CS.vEgo ** 2 self.lat_accel_request_buffer.append(future_desired_lateral_accel) gravity_adjusted_future_lateral_accel = future_desired_lateral_accel - roll_compensation - desired_lateral_jerk = (future_desired_lateral_accel - expected_lateral_accel) / lat_delay + setpoint = expected_lateral_accel measurement = measured_curvature * CS.vEgo ** 2 measurement_rate = self.measurement_rate_filter.update((measurement - self.previous_measurement) / self.dt) self.previous_measurement = measurement - setpoint = lat_delay * desired_lateral_jerk + expected_lateral_accel - error = setpoint - measurement + error = setpoint - measurement + JERK_GAIN * desired_lateral_jerk # do error correction in lateral acceleration space, convert at end to handle non-linear torque responses correctly pid_log.error = float(error) ff = gravity_adjusted_future_lateral_accel # latAccelOffset corrects roll compensation bias from device roll misalignment relative to car roll ff -= self.torque_params.latAccelOffset - # TODO jerk is weighted by lat_delay for legacy reasons, but should be made independent of it + # TODO remove lateral jerk from feed forward - moving it from error means jerk is not scaled by low speed factor ff += get_friction(error, lateral_accel_deadzone, FRICTION_THRESHOLD, self.torque_params) freeze_integrator = steer_limited_by_safety or CS.steeringPressed or CS.vEgo < 5 diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index cdd4301fc..37d1e8f8d 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -b508f43fb0481bce0859c9b6ab4f45ee690b8dab \ No newline at end of file +9a7d8dd0660ba6a344ac61be89b2e832e6756184 \ No newline at end of file From 76c5cb6d8737c11235b8d4843a3a921645faea8b Mon Sep 17 00:00:00 2001 From: felsager <76905857+felsager@users.noreply.github.com> Date: Thu, 30 Oct 2025 13:34:44 -0700 Subject: [PATCH 290/910] latcontrol_torque: retune torque controller (#36392) --- selfdrive/controls/lib/latcontrol_torque.py | 17 ++++++----------- selfdrive/test/process_replay/ref_commit | 2 +- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/selfdrive/controls/lib/latcontrol_torque.py b/selfdrive/controls/lib/latcontrol_torque.py index 1e3d80c2d..f36aabc1d 100644 --- a/selfdrive/controls/lib/latcontrol_torque.py +++ b/selfdrive/controls/lib/latcontrol_torque.py @@ -21,8 +21,8 @@ from openpilot.common.pid import PIDController # to be overcome to move it at all, this is compensated for too. KP = 1.0 -KI = 0.3 -KD = 0.0 +KI = 0.1 +KD = 0.3 INTERP_SPEEDS = [1, 1.5, 2.0, 3.0, 5, 7.5, 10, 15, 30] KP_INTERP = [250, 120, 65, 30, 11.5, 5.5, 3.5, 2.0, KP] @@ -30,7 +30,7 @@ LP_FILTER_CUTOFF_HZ = 1.2 JERK_LOOKAHEAD_SECONDS = 0.19 JERK_GAIN = 0.3 LAT_ACCEL_REQUEST_BUFFER_SECONDS = 1.0 -VERSION = 0 # bump this when changing controller +VERSION = 1 # bump this when changing controller class LatControlTorque(LatControl): def __init__(self, CP, CI, dt): @@ -84,22 +84,17 @@ class LatControlTorque(LatControl): measurement_rate = self.measurement_rate_filter.update((measurement - self.previous_measurement) / self.dt) self.previous_measurement = measurement - error = setpoint - measurement + JERK_GAIN * desired_lateral_jerk + error = setpoint - measurement # do error correction in lateral acceleration space, convert at end to handle non-linear torque responses correctly pid_log.error = float(error) ff = gravity_adjusted_future_lateral_accel # latAccelOffset corrects roll compensation bias from device roll misalignment relative to car roll ff -= self.torque_params.latAccelOffset - # TODO remove lateral jerk from feed forward - moving it from error means jerk is not scaled by low speed factor - ff += get_friction(error, lateral_accel_deadzone, FRICTION_THRESHOLD, self.torque_params) + ff += get_friction(error + JERK_GAIN * desired_lateral_jerk, lateral_accel_deadzone, FRICTION_THRESHOLD, self.torque_params) freeze_integrator = steer_limited_by_safety or CS.steeringPressed or CS.vEgo < 5 - output_lataccel = self.pid.update(pid_log.error, - -measurement_rate, - feedforward=ff, - speed=CS.vEgo, - freeze_integrator=freeze_integrator) + output_lataccel = self.pid.update(pid_log.error, -measurement_rate, CS.vEgo, ff, freeze_integrator) output_torque = self.torque_from_lateral_accel(output_lataccel, self.torque_params) pid_log.active = True diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 37d1e8f8d..f313edbc6 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -9a7d8dd0660ba6a344ac61be89b2e832e6756184 \ No newline at end of file +d74c33b02938cafb3422f3500cfd083bd965e637 From c4a0e5704685e5cfb49c250a0941a05fd568f90f Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 30 Oct 2025 15:52:56 -0700 Subject: [PATCH 291/910] ui: add debug toggle (#36529) * ui: add debug toggle * initial state --- common/params_keys.h | 1 + selfdrive/ui/layouts/settings/developer.py | 21 +++++++++++++++++---- system/ui/lib/application.py | 12 ++++++++++-- 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/common/params_keys.h b/common/params_keys.h index f6e8d781c..badc16214 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -108,6 +108,7 @@ inline static std::unordered_map keys = { {"RecordFront", {PERSISTENT, BOOL}}, {"RecordFrontLock", {PERSISTENT, BOOL}}, // for the internal fleet {"SecOCKey", {PERSISTENT | DONT_LOG, STRING}}, + {"ShowDebugInfo", {PERSISTENT, BOOL}}, {"RouteCount", {PERSISTENT, INT, "0"}}, {"SnoozeUpdate", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}}, {"SshEnabled", {PERSISTENT, BOOL}}, diff --git a/selfdrive/ui/layouts/settings/developer.py b/selfdrive/ui/layouts/settings/developer.py index 0419729a4..cb7b0f9e9 100644 --- a/selfdrive/ui/layouts/settings/developer.py +++ b/selfdrive/ui/layouts/settings/developer.py @@ -75,16 +75,23 @@ class DeveloperLayout(Widget): enabled=lambda: not ui_state.engaged, ) - items = [ + self._ui_debug_toggle = toggle_item( + lambda: tr("UI Debug Mode"), + description="", + initial_state=self._params.get_bool("ShowDebugInfo"), + callback=self._on_enable_ui_debug, + ) + self._on_enable_ui_debug(self._params.get_bool("ShowDebugInfo")) + + self._scroller = Scroller([ self._adb_toggle, self._ssh_toggle, self._ssh_keys, self._joystick_toggle, self._long_maneuver_toggle, self._alpha_long_toggle, - ] - - self._scroller = Scroller(items, line_separator=True, spacing=0) + self._ui_debug_toggle, + ], line_separator=True, spacing=0) # Toggles should be not available to change in onroad state ui_state.add_offroad_transition_callback(self._update_toggles) @@ -130,9 +137,15 @@ class DeveloperLayout(Widget): ("JoystickDebugMode", self._joystick_toggle), ("LongitudinalManeuverMode", self._long_maneuver_toggle), ("AlphaLongitudinalEnabled", self._alpha_long_toggle), + ("ShowDebugInfo", self._ui_debug_toggle), ): item.action_item.set_state(self._params.get_bool(key)) + def _on_enable_ui_debug(self, state: bool): + self._params.put_bool("ShowDebugInfo", state) + gui_app.set_show_touches(state) + gui_app.set_show_fps(state) + def _on_enable_adb(self, state: bool): self._params.put_bool("AdbEnabled", state) diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 8e45191bf..bdd103c5c 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -160,6 +160,14 @@ class GuiApplication: # Debug variables self._mouse_history: deque[MousePos] = deque(maxlen=MOUSE_THREAD_RATE) + self._show_touches = SHOW_TOUCHES + self._show_fps = SHOW_FPS + + def set_show_touches(self, show: bool): + self._show_touches = show + + def set_show_fps(self, show: bool): + self._show_fps = show @property def target_fps(self): @@ -367,10 +375,10 @@ class GuiApplication: dst_rect = rl.Rectangle(0, 0, float(self._scaled_width), float(self._scaled_height)) rl.draw_texture_pro(self._render_texture.texture, src_rect, dst_rect, rl.Vector2(0, 0), 0.0, rl.WHITE) - if SHOW_FPS: + if self._show_fps: rl.draw_fps(10, 10) - if SHOW_TOUCHES: + if self._show_touches: for mouse_event in self._mouse_events: if mouse_event.left_pressed: self._mouse_history.clear() From f57617c944afb2217b2737a42f445a6b1908c755 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 30 Oct 2025 16:03:28 -0700 Subject: [PATCH 292/910] expose more state from gui_app --- system/ui/lib/application.py | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index bdd103c5c..6f67139c5 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -14,11 +14,11 @@ from enum import StrEnum from typing import NamedTuple from importlib.resources import as_file, files from openpilot.common.swaglog import cloudlog -from openpilot.system.hardware import HARDWARE, PC, TICI +from openpilot.system.hardware import HARDWARE, PC from openpilot.system.ui.lib.multilang import multilang from openpilot.common.realtime import Ratekeeper -_DEFAULT_FPS = int(os.getenv("FPS", 20 if TICI else 60)) +_DEFAULT_FPS = int(os.getenv("FPS", {'tizi': 20}.get(HARDWARE.get_device_type(), 60))) 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 @@ -149,12 +149,15 @@ class GuiApplication: self._textures: dict[str, rl.Texture] = {} self._target_fps: int = _DEFAULT_FPS self._last_fps_log_time: float = time.monotonic() + self._frame = 0 self._window_close_requested = False self._trace_log_callback = None self._modal_overlay = ModalOverlay() + self._modal_overlay_shown = False self._mouse = MouseState(self._scale) self._mouse_events: list[MouseEvent] = [] + self._last_mouse_event: MouseEvent = MouseEvent(MousePos(0, 0), 0, False, False, False, 0.0) self._should_render = True @@ -163,6 +166,10 @@ class GuiApplication: self._show_touches = SHOW_TOUCHES self._show_fps = SHOW_FPS + @property + def frame(self): + return self._frame + def set_show_touches(self, show: bool): self._show_touches = show @@ -325,6 +332,10 @@ class GuiApplication: def mouse_events(self) -> list[MouseEvent]: return self._mouse_events + @property + def last_mouse_event(self) -> MouseEvent: + return self._last_mouse_event + def render(self): try: while not (self._window_close_requested or rl.window_should_close()): @@ -334,6 +345,8 @@ class GuiApplication: # Store all mouse events for the current frame self._mouse_events = self._mouse.get_events() + if len(self._mouse_events) > 0: + self._last_mouse_event = self._mouse_events[-1] # Skip rendering when screen is off if not self._should_render: @@ -357,6 +370,11 @@ class GuiApplication: else: raise Exception + # Send show event to Widget + if not self._modal_overlay_shown and hasattr(self._modal_overlay.overlay, 'show_event'): + self._modal_overlay.overlay.show_event() + self._modal_overlay_shown = True + if result >= 0: # Clear the overlay and execute the callback original_modal = self._modal_overlay @@ -365,6 +383,7 @@ class GuiApplication: original_modal.callback(result) yield False else: + self._modal_overlay_shown = False yield True if self._render_texture: @@ -394,6 +413,7 @@ class GuiApplication: rl.end_drawing() self._monitor_fps() + self._frame += 1 except KeyboardInterrupt: pass From 62aef9cd34a216a970670b707f906b2b080afbf6 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Fri, 31 Oct 2025 23:16:55 +0800 Subject: [PATCH 293/910] tools: update README (#36531) update readme --- tools/camerastream/README.md | 4 ++-- tools/replay/README.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/camerastream/README.md b/tools/camerastream/README.md index 7dbafb4be..2f7498a07 100644 --- a/tools/camerastream/README.md +++ b/tools/camerastream/README.md @@ -39,7 +39,7 @@ Decode the stream with `compressed_vipc.py`: To actually display the stream, run `watch3` in separate terminal: -```cd ~/openpilot/selfdrive/ui/ && ./watch3``` +```cd ~/openpilot/selfdrive/ui/ && ./watch3.py``` ## compressed_vipc.py usage ``` @@ -62,5 +62,5 @@ options: ## Example: ``` cd ~/openpilot/tools/camerastream && ./compressed_vipc.py comma-ffffffff --cams 0 -cd ~/openpilot/selfdrive/ui/ && ./watch3 +cd ~/openpilot/selfdrive/ui/ && ./watch3.py ``` diff --git a/tools/replay/README.md b/tools/replay/README.md index 72216e2cc..794c08f6a 100644 --- a/tools/replay/README.md +++ b/tools/replay/README.md @@ -88,7 +88,7 @@ To visualize the replay within the openpilot UI, run the following commands: ```bash tools/replay/replay -cd selfdrive/ui && ./ui +cd selfdrive/ui && ./ui.py ``` ## Work with plotjuggler @@ -110,7 +110,7 @@ simply replay a route using the `--dcam` and `--ecam` flags: cd tools/replay && ./replay --demo --dcam --ecam # then start watch3 -cd selfdrive/ui && ./watch3 +cd selfdrive/ui && ./watch3.py ``` ![](https://i.imgur.com/IeaOdAb.png) From c7b115b68e9a7fbee55a7fbd01ebc8e28d4588b2 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sun, 2 Nov 2025 04:30:08 -0800 Subject: [PATCH 294/910] raylib: fix text measure with emojis (#36546) fix --- system/ui/lib/text_measure.py | 1 + 1 file changed, 1 insertion(+) diff --git a/system/ui/lib/text_measure.py b/system/ui/lib/text_measure.py index ea6fa6336..544ba5b87 100644 --- a/system/ui/lib/text_measure.py +++ b/system/ui/lib/text_measure.py @@ -20,6 +20,7 @@ def measure_text_cached(font: rl.Font, text: str, font_size: int, spacing: int = for start, end, _ in emoji: non_emoji_text += text[last_index:start] last_index = end + non_emoji_text += text[last_index:] else: non_emoji_text = text From 525b6e48e99a00f95cf5fe12fce71c9434219583 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sun, 2 Nov 2025 04:32:29 -0800 Subject: [PATCH 295/910] raylib: fix word wrap (#36545) * fix word wrap underestimating width * and that --- system/ui/lib/wrap_text.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/system/ui/lib/wrap_text.py b/system/ui/lib/wrap_text.py index d51975087..745d37b46 100644 --- a/system/ui/lib/wrap_text.py +++ b/system/ui/lib/wrap_text.py @@ -67,8 +67,6 @@ def wrap_text(font: rl.Font, text: str, font_size: int, max_width: int) -> list[ lines: list[str] = [] current_line: list[str] = [] - current_width = 0 - space_width = int(measure_text_cached(font, " ", font_size).x) for word in words: word_width = int(measure_text_cached(font, word, font_size).x) @@ -79,28 +77,23 @@ def wrap_text(font: rl.Font, text: str, font_size: int, max_width: int) -> list[ if current_line: lines.append(" ".join(current_line)) current_line = [] - current_width = 0 # Break the long word into parts lines.extend(_break_long_word(font, word, font_size, max_width)) continue - # Calculate width if we add this word - needed_width = current_width - if current_line: # Need space before word - needed_width += space_width - needed_width += word_width + # Measure the actual joined string to get accurate width (accounts for kerning, etc.) + test_line = " ".join(current_line + [word]) if current_line else word + test_width = int(measure_text_cached(font, test_line, font_size).x) # Check if word fits on current line - if needed_width <= max_width: + if test_width <= max_width: current_line.append(word) - current_width = needed_width else: # Start new line with this word if current_line: lines.append(" ".join(current_line)) current_line = [word] - current_width = word_width # Add remaining words if current_line: From 5ea5f6f267301d5bc83bdc17004807740608abd0 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 2 Nov 2025 21:48:33 -0800 Subject: [PATCH 296/910] ui: timeout touch points (#36550) --- system/ui/lib/application.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 6f67139c5..d1d74d3ac 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -24,6 +24,7 @@ FPS_DROP_THRESHOLD = 0.9 # FPS drop threshold for triggering a warning FPS_CRITICAL_THRESHOLD = 0.5 # Critical threshold for triggering strict actions MOUSE_THREAD_RATE = 140 # touch controller runs at 140Hz MAX_TOUCH_SLOTS = 2 +TOUCH_HISTORY_TIMEOUT = 3.0 # Seconds before touch points fade out ENABLE_VSYNC = os.getenv("ENABLE_VSYNC", "0") == "1" SHOW_FPS = os.getenv("SHOW_FPS") == "1" @@ -69,6 +70,12 @@ class MousePos(NamedTuple): y: float +class MousePosWithTime(NamedTuple): + x: float + y: float + t: float + + class MouseEvent(NamedTuple): pos: MousePos slot: int @@ -162,7 +169,7 @@ class GuiApplication: self._should_render = True # Debug variables - self._mouse_history: deque[MousePos] = deque(maxlen=MOUSE_THREAD_RATE) + self._mouse_history: deque[MousePosWithTime] = deque(maxlen=MOUSE_THREAD_RATE) self._show_touches = SHOW_TOUCHES self._show_fps = SHOW_FPS @@ -398,10 +405,16 @@ class GuiApplication: rl.draw_fps(10, 10) if self._show_touches: + current_time = time.monotonic() + for mouse_event in self._mouse_events: if mouse_event.left_pressed: self._mouse_history.clear() - self._mouse_history.append(mouse_event.pos) + self._mouse_history.append(MousePosWithTime(mouse_event.pos.x, mouse_event.pos.y, current_time)) + + # Remove old touch points that exceed the timeout + while self._mouse_history and (current_time - self._mouse_history[0].t) > TOUCH_HISTORY_TIMEOUT: + self._mouse_history.popleft() if self._mouse_history: mouse_pos = self._mouse_history[-1] From 9bf904e8a644a6d642c387b05f6a66fcc305e802 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Tue, 4 Nov 2025 00:55:05 +0800 Subject: [PATCH 297/910] ui: scale mouse positions in touch history (#36530) scale mouse position in touch history --- system/ui/lib/application.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index d1d74d3ac..97aaaa27b 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -410,7 +410,7 @@ class GuiApplication: for mouse_event in self._mouse_events: if mouse_event.left_pressed: self._mouse_history.clear() - self._mouse_history.append(MousePosWithTime(mouse_event.pos.x, mouse_event.pos.y, current_time)) + self._mouse_history.append(MousePosWithTime(mouse_event.pos.x * self._scale, mouse_event.pos.y * self._scale, current_time)) # Remove old touch points that exceed the timeout while self._mouse_history and (current_time - self._mouse_history[0].t) > TOUCH_HISTORY_TIMEOUT: From 177c7f1cf327a54a005f30e0d12d1b6954dc2648 Mon Sep 17 00:00:00 2001 From: felsager Date: Mon, 3 Nov 2025 10:31:22 -0800 Subject: [PATCH 298/910] Revert "latcontrol_torque: retune torque controller (#36392)" This reverts commit 76c5cb6d8737c11235b8d4843a3a921645faea8b. --- selfdrive/controls/lib/latcontrol_torque.py | 17 +++++++++++------ selfdrive/test/process_replay/ref_commit | 2 +- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/selfdrive/controls/lib/latcontrol_torque.py b/selfdrive/controls/lib/latcontrol_torque.py index f36aabc1d..1e3d80c2d 100644 --- a/selfdrive/controls/lib/latcontrol_torque.py +++ b/selfdrive/controls/lib/latcontrol_torque.py @@ -21,8 +21,8 @@ from openpilot.common.pid import PIDController # to be overcome to move it at all, this is compensated for too. KP = 1.0 -KI = 0.1 -KD = 0.3 +KI = 0.3 +KD = 0.0 INTERP_SPEEDS = [1, 1.5, 2.0, 3.0, 5, 7.5, 10, 15, 30] KP_INTERP = [250, 120, 65, 30, 11.5, 5.5, 3.5, 2.0, KP] @@ -30,7 +30,7 @@ LP_FILTER_CUTOFF_HZ = 1.2 JERK_LOOKAHEAD_SECONDS = 0.19 JERK_GAIN = 0.3 LAT_ACCEL_REQUEST_BUFFER_SECONDS = 1.0 -VERSION = 1 # bump this when changing controller +VERSION = 0 # bump this when changing controller class LatControlTorque(LatControl): def __init__(self, CP, CI, dt): @@ -84,17 +84,22 @@ class LatControlTorque(LatControl): measurement_rate = self.measurement_rate_filter.update((measurement - self.previous_measurement) / self.dt) self.previous_measurement = measurement - error = setpoint - measurement + error = setpoint - measurement + JERK_GAIN * desired_lateral_jerk # do error correction in lateral acceleration space, convert at end to handle non-linear torque responses correctly pid_log.error = float(error) ff = gravity_adjusted_future_lateral_accel # latAccelOffset corrects roll compensation bias from device roll misalignment relative to car roll ff -= self.torque_params.latAccelOffset - ff += get_friction(error + JERK_GAIN * desired_lateral_jerk, lateral_accel_deadzone, FRICTION_THRESHOLD, self.torque_params) + # TODO remove lateral jerk from feed forward - moving it from error means jerk is not scaled by low speed factor + ff += get_friction(error, lateral_accel_deadzone, FRICTION_THRESHOLD, self.torque_params) freeze_integrator = steer_limited_by_safety or CS.steeringPressed or CS.vEgo < 5 - output_lataccel = self.pid.update(pid_log.error, -measurement_rate, CS.vEgo, ff, freeze_integrator) + output_lataccel = self.pid.update(pid_log.error, + -measurement_rate, + feedforward=ff, + speed=CS.vEgo, + freeze_integrator=freeze_integrator) output_torque = self.torque_from_lateral_accel(output_lataccel, self.torque_params) pid_log.active = True diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index f313edbc6..37d1e8f8d 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -d74c33b02938cafb3422f3500cfd083bd965e637 +9a7d8dd0660ba6a344ac61be89b2e832e6756184 \ No newline at end of file From 736e1fa7b772b0a936e3138cbb25c469ce2ab312 Mon Sep 17 00:00:00 2001 From: felsager Date: Mon, 3 Nov 2025 10:31:27 -0800 Subject: [PATCH 299/910] Revert "latcontrol_torque: make feed-forward jerk independent of individual platform lag (#36334)" This reverts commit fc4e5007fd55784a5aaf471744f431baa11b118f. --- selfdrive/controls/lib/latcontrol_torque.py | 19 +++++++------------ selfdrive/test/process_replay/ref_commit | 2 +- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/selfdrive/controls/lib/latcontrol_torque.py b/selfdrive/controls/lib/latcontrol_torque.py index 1e3d80c2d..443fd1851 100644 --- a/selfdrive/controls/lib/latcontrol_torque.py +++ b/selfdrive/controls/lib/latcontrol_torque.py @@ -27,10 +27,8 @@ INTERP_SPEEDS = [1, 1.5, 2.0, 3.0, 5, 7.5, 10, 15, 30] KP_INTERP = [250, 120, 65, 30, 11.5, 5.5, 3.5, 2.0, KP] LP_FILTER_CUTOFF_HZ = 1.2 -JERK_LOOKAHEAD_SECONDS = 0.19 -JERK_GAIN = 0.3 LAT_ACCEL_REQUEST_BUFFER_SECONDS = 1.0 -VERSION = 0 # bump this when changing controller +VERSION = 0 class LatControlTorque(LatControl): def __init__(self, CP, CI, dt): @@ -41,12 +39,10 @@ class LatControlTorque(LatControl): self.pid = PIDController([INTERP_SPEEDS, KP_INTERP], KI, KD, rate=1/self.dt) self.update_limits() self.steering_angle_deadzone_deg = self.torque_params.steeringAngleDeadzoneDeg - self.lookahead_frames = int(JERK_LOOKAHEAD_SECONDS / self.dt) self.lat_accel_request_buffer_len = int(LAT_ACCEL_REQUEST_BUFFER_SECONDS / self.dt) self.lat_accel_request_buffer = deque([0.] * self.lat_accel_request_buffer_len , maxlen=self.lat_accel_request_buffer_len) - self.jerk_filter = FirstOrderFilter(0.0, 1 / (2 * np.pi * LP_FILTER_CUTOFF_HZ), self.dt) - self.measurement_rate_filter = FirstOrderFilter(0.0, 1 / (2 * np.pi * LP_FILTER_CUTOFF_HZ), self.dt) self.previous_measurement = 0.0 + self.measurement_rate_filter = FirstOrderFilter(0.0, 1 / (2 * np.pi * LP_FILTER_CUTOFF_HZ), self.dt) def update_live_torque_params(self, latAccelFactor, latAccelOffset, friction): self.torque_params.latAccelFactor = latAccelFactor @@ -72,26 +68,25 @@ class LatControlTorque(LatControl): delay_frames = int(np.clip(lat_delay / self.dt, 1, self.lat_accel_request_buffer_len)) expected_lateral_accel = self.lat_accel_request_buffer[-delay_frames] - lookahead_idx = int(np.clip(-delay_frames + self.lookahead_frames, -self.lat_accel_request_buffer_len+1, -2)) - raw_lateral_jerk = (self.lat_accel_request_buffer[lookahead_idx+1] - self.lat_accel_request_buffer[lookahead_idx-1]) / (2 * self.dt) - desired_lateral_jerk = self.jerk_filter.update(raw_lateral_jerk) + # TODO factor out lateral jerk from error to later replace it with delay independent alternative future_desired_lateral_accel = desired_curvature * CS.vEgo ** 2 self.lat_accel_request_buffer.append(future_desired_lateral_accel) gravity_adjusted_future_lateral_accel = future_desired_lateral_accel - roll_compensation - setpoint = expected_lateral_accel + desired_lateral_jerk = (future_desired_lateral_accel - expected_lateral_accel) / lat_delay measurement = measured_curvature * CS.vEgo ** 2 measurement_rate = self.measurement_rate_filter.update((measurement - self.previous_measurement) / self.dt) self.previous_measurement = measurement - error = setpoint - measurement + JERK_GAIN * desired_lateral_jerk + setpoint = lat_delay * desired_lateral_jerk + expected_lateral_accel + error = setpoint - measurement # do error correction in lateral acceleration space, convert at end to handle non-linear torque responses correctly pid_log.error = float(error) ff = gravity_adjusted_future_lateral_accel # latAccelOffset corrects roll compensation bias from device roll misalignment relative to car roll ff -= self.torque_params.latAccelOffset - # TODO remove lateral jerk from feed forward - moving it from error means jerk is not scaled by low speed factor + # TODO jerk is weighted by lat_delay for legacy reasons, but should be made independent of it ff += get_friction(error, lateral_accel_deadzone, FRICTION_THRESHOLD, self.torque_params) freeze_integrator = steer_limited_by_safety or CS.steeringPressed or CS.vEgo < 5 diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 37d1e8f8d..cdd4301fc 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -9a7d8dd0660ba6a344ac61be89b2e832e6756184 \ No newline at end of file +b508f43fb0481bce0859c9b6ab4f45ee690b8dab \ No newline at end of file From 2cc4885a2e848339f56716c9a5bbd3e14577cc67 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 3 Nov 2025 11:17:38 -0800 Subject: [PATCH 300/910] raylib: fix window freezing (#36517) fix window freezing --- system/ui/lib/application.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 97aaaa27b..bca836334 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -357,6 +357,8 @@ class GuiApplication: # Skip rendering when screen is off if not self._should_render: + if PC: + rl.poll_input_events() time.sleep(1 / self._target_fps) yield False continue From 137d4b89b4174504d3e3b9842f1c65cc45ec4d9f Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Tue, 4 Nov 2025 03:20:08 +0800 Subject: [PATCH 301/910] ui: fix icon vertical positioning using width instead of height (#36542) fix icon vertical positioning using width instead of height --- system/ui/widgets/list_view.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/ui/widgets/list_view.py b/system/ui/widgets/list_view.py index 20a5f7791..cfb8ab58a 100644 --- a/system/ui/widgets/list_view.py +++ b/system/ui/widgets/list_view.py @@ -354,7 +354,7 @@ class ListItem(Widget): if self.title: # Draw icon if present if self.icon: - rl.draw_texture(self._icon_texture, int(content_x), int(self._rect.y + (ITEM_BASE_HEIGHT - self._icon_texture.width) // 2), rl.WHITE) + rl.draw_texture(self._icon_texture, int(content_x), int(self._rect.y + (ITEM_BASE_HEIGHT - self._icon_texture.height) // 2), rl.WHITE) text_x += ICON_SIZE + ITEM_PADDING # Draw main text From 1c0b08710560806122a62fdd2b0bfff79c4095d6 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Tue, 4 Nov 2025 03:20:24 +0800 Subject: [PATCH 302/910] ui: fix keyboard.reset() to properly clear all interaction state (#36541) fix keyboard.reset() to properly clear all interaction state --- system/ui/widgets/keyboard.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/system/ui/widgets/keyboard.py b/system/ui/widgets/keyboard.py index b0e103568..4ec92f507 100644 --- a/system/ui/widgets/keyboard.py +++ b/system/ui/widgets/keyboard.py @@ -251,6 +251,10 @@ class Keyboard(Widget): if min_text_size is not None: self._min_text_size = min_text_size self._render_return_status = -1 + self._last_shift_press_time = 0 + self._backspace_pressed = False + self._backspace_press_time = 0.0 + self._backspace_last_repeat = 0.0 self.clear() From 9ce9920ff7503e16f5ea6eefcbf4ab1697546299 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Tue, 4 Nov 2025 03:21:20 +0800 Subject: [PATCH 303/910] ui: fix label text eliding to account for icon width (#36539) fix label text eliding to account for icon width --- system/ui/widgets/label.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/system/ui/widgets/label.py b/system/ui/widgets/label.py index 425920810..59d6a5947 100644 --- a/system/ui/widgets/label.py +++ b/system/ui/widgets/label.py @@ -145,6 +145,8 @@ class Label(Widget): # Elide text to fit within the rectangle text_size = measure_text_cached(self._font, text, self._font_size) content_width = self._rect.width - self._text_padding * 2 + if self._icon: + content_width -= self._icon.width + ICON_PADDING if text_size.x > content_width: _ellipsis = "..." left, right = 0, len(text) From 350b846d3a3b6d9677a4da332895d61fbd1b67ca Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Tue, 4 Nov 2025 03:21:41 +0800 Subject: [PATCH 304/910] ui: fix vertical centering for multi-line labels (#36538) fix vertical centering for multi-line labels --- system/ui/widgets/label.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/system/ui/widgets/label.py b/system/ui/widgets/label.py index 59d6a5947..7d7680256 100644 --- a/system/ui/widgets/label.py +++ b/system/ui/widgets/label.py @@ -175,7 +175,8 @@ class Label(Widget): text_size = self._text_size[0] if self._text_size else rl.Vector2(0.0, 0.0) if self._text_alignment_vertical == rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE: - text_pos = rl.Vector2(self._rect.x, (self._rect.y + (self._rect.height - text_size.y) // 2)) + total_text_height = sum(ts.y for ts in self._text_size) or self._font_size * FONT_SCALE + text_pos = rl.Vector2(self._rect.x, (self._rect.y + (self._rect.height - total_text_height) // 2)) else: text_pos = rl.Vector2(self._rect.x, self._rect.y) From 215ef168036b2a5fc5427150b19d810457a2ebc3 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Tue, 4 Nov 2025 03:22:42 +0800 Subject: [PATCH 305/910] ui: fix LineSeparator horizontal centering issue (#36533) fix LineSeparator horizontal centering issue --- system/ui/widgets/scroller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/ui/widgets/scroller.py b/system/ui/widgets/scroller.py index f19a6fbfd..a843010d5 100644 --- a/system/ui/widgets/scroller.py +++ b/system/ui/widgets/scroller.py @@ -18,7 +18,7 @@ class LineSeparator(Widget): def _render(self, _): rl.draw_line(int(self._rect.x) + LINE_PADDING, int(self._rect.y), - int(self._rect.x + self._rect.width) - LINE_PADDING * 2, int(self._rect.y), + int(self._rect.x + self._rect.width) - LINE_PADDING, int(self._rect.y), LINE_COLOR) From c7494aed0fc7ea87a291784d8e8c854f679eee2b Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 3 Nov 2025 14:31:45 -0800 Subject: [PATCH 306/910] ui: move to GPU core (#36553) * ui: move to GPU core * we're on the big boy core now --- selfdrive/test/test_onroad.py | 2 +- selfdrive/ui/ui.py | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/selfdrive/test/test_onroad.py b/selfdrive/test/test_onroad.py index f90627e10..69d920c1a 100644 --- a/selfdrive/test/test_onroad.py +++ b/selfdrive/test/test_onroad.py @@ -42,7 +42,7 @@ PROCS = { "./encoderd": 13.0, "./camerad": 10.0, "selfdrive.controls.plannerd": 8.0, - "selfdrive.ui.ui": 63.0, + "selfdrive.ui.ui": 40.0, "system.sensord.sensord": 13.0, "selfdrive.controls.radard": 2.0, "selfdrive.modeld.modeld": 22.0, diff --git a/selfdrive/ui/ui.py b/selfdrive/ui/ui.py index 4a1f03fb8..222a25b87 100755 --- a/selfdrive/ui/ui.py +++ b/selfdrive/ui/ui.py @@ -1,14 +1,16 @@ #!/usr/bin/env python3 +import os import pyray as rl -from openpilot.common.realtime import config_realtime_process +from openpilot.common.realtime import config_realtime_process, set_core_affinity from openpilot.system.ui.lib.application import gui_app from openpilot.selfdrive.ui.layouts.main import MainLayout from openpilot.selfdrive.ui.ui_state import ui_state def main(): - config_realtime_process([1, 2], 1) + cores = {7, } + config_realtime_process(0, 1) gui_app.init_window("UI") main_layout = MainLayout() @@ -18,6 +20,13 @@ def main(): if should_render: main_layout.render() + # reaffine after power save offlines our core + if os.sched_getaffinity(0) != cores: + try: + set_core_affinity(list(cores)) + except OSError: + pass + if __name__ == "__main__": main() From ecdcb5d0c6ad3e67ab2f378828b991d20c96eae8 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 3 Nov 2025 14:36:19 -0800 Subject: [PATCH 307/910] tici: affine DRM IRQ to same core as ui (#36554) --- system/hardware/tici/hardware.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/hardware/tici/hardware.py b/system/hardware/tici/hardware.py index 36e65ad91..ff2622e96 100644 --- a/system/hardware/tici/hardware.py +++ b/system/hardware/tici/hardware.py @@ -419,12 +419,12 @@ class Tici(HardwareBase): sudo_write("f", "/proc/irq/default_smp_affinity") # move these off the default core - affine_irq(1, "msm_drm") # display affine_irq(1, "msm_vidc") # encoders affine_irq(1, "i2c_geni") # sensors # *** GPU config *** # https://github.com/commaai/agnos-kernel-sdm845/blob/master/arch/arm64/boot/dts/qcom/sdm845-gpu.dtsi#L216 + affine_irq(7, "msm_drm") # display sudo_write("1", "/sys/class/kgsl/kgsl-3d0/min_pwrlevel") sudo_write("1", "/sys/class/kgsl/kgsl-3d0/max_pwrlevel") sudo_write("1", "/sys/class/kgsl/kgsl-3d0/force_bus_on") From cbc8f98682d6ff7534993cfd05b023d644e5c180 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 3 Nov 2025 16:20:36 -0800 Subject: [PATCH 308/910] ui: fix RuntimeError on exit on PC --- system/ui/lib/wifi_manager.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index b6305a03f..4cf2ccebc 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -746,8 +746,10 @@ class WifiManager: def stop(self): if not self._exit: self._exit = True - self._scan_thread.join() - self._state_thread.join() + if self._scan_thread.is_alive(): + self._scan_thread.join() + if self._state_thread.is_alive(): + self._state_thread.join() self._router_main.close() self._router_main.conn.close() From e8a11591a8b423ad3730846c1b39a4441b4e8d03 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 3 Nov 2025 21:45:46 -0800 Subject: [PATCH 309/910] ui: add render loop profiling (#36558) --- system/ui/lib/application.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index bca836334..f9d7b0c9c 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -31,6 +31,7 @@ SHOW_FPS = os.getenv("SHOW_FPS") == "1" SHOW_TOUCHES = os.getenv("SHOW_TOUCHES") == "1" STRICT_MODE = os.getenv("STRICT_MODE") == "1" SCALE = float(os.getenv("SCALE", "1.0")) +PROFILE_RENDER = int(os.getenv("PROFILE_RENDER", "0")) DEFAULT_TEXT_SIZE = 60 DEFAULT_TEXT_COLOR = rl.WHITE @@ -172,6 +173,9 @@ class GuiApplication: self._mouse_history: deque[MousePosWithTime] = deque(maxlen=MOUSE_THREAD_RATE) self._show_touches = SHOW_TOUCHES self._show_fps = SHOW_FPS + self._profile_render_frames = PROFILE_RENDER + self._render_profiler = None + self._render_profile_start_time = None @property def frame(self): @@ -345,6 +349,12 @@ class GuiApplication: def render(self): try: + if self._profile_render_frames > 0: + import cProfile + self._render_profiler = cProfile.Profile() + self._render_profile_start_time = time.monotonic() + self._render_profiler.enable() + while not (self._window_close_requested or rl.window_should_close()): if PC: # Thread is not used on PC, need to manually add mouse events @@ -429,6 +439,9 @@ class GuiApplication: rl.end_drawing() self._monitor_fps() self._frame += 1 + + if self._profile_render_frames > 0 and self._frame >= self._profile_render_frames: + self._output_render_profile() except KeyboardInterrupt: pass @@ -526,6 +539,25 @@ class GuiApplication: cloudlog.error(f"FPS dropped critically below {fps}. Shutting down UI.") os._exit(1) + def _output_render_profile(self): + import io + import pstats + + self._render_profiler.disable() + elapsed_ms = (time.monotonic() - self._render_profile_start_time) * 1e3 + avg_frame_time = elapsed_ms / self._frame if self._frame > 0 else 0 + + stats_stream = io.StringIO() + pstats.Stats(self._render_profiler, stream=stats_stream).sort_stats("cumtime").print_stats(25) + print("\n=== Render loop profile ===") + print(stats_stream.getvalue().rstrip()) + + green = "\033[92m" + reset = "\033[0m" + print(f"\n{green}Rendered {self._frame} frames in {elapsed_ms:.1f} ms{reset}") + print(f"{green}Average frame time: {avg_frame_time:.2f} ms ({1000/avg_frame_time:.1f} FPS){reset}") + sys.exit(0) + def _calculate_auto_scale(self) -> float: # Create temporary window to query monitor info rl.init_window(1, 1, "") From 5198b1b079c37742c1050f02ce0aa6dd42b038b9 Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Mon, 3 Nov 2025 22:45:00 -0800 Subject: [PATCH 310/910] support ECDSA (#36555) * keys * remove * remove * too small --- common/api.py | 16 +++++++++++++--- system/athena/registration.py | 17 +++++++---------- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/common/api.py b/common/api.py index 005655b21..656c05ed5 100644 --- a/common/api.py +++ b/common/api.py @@ -7,11 +7,14 @@ from openpilot.system.version import get_version API_HOST = os.getenv('API_HOST', 'https://api.commadotai.com') + # name : jwt signature algorithm +KEYS = {"id_rsa" : "RS256", + "id_ecdsa" : "ES256"} + class Api: def __init__(self, dongle_id): self.dongle_id = dongle_id - with open(Paths.persist_root()+'/comma/id_rsa') as f: - self.private_key = f.read() + self.jwt_algorithm, self.private_key, _ = get_key_pair() def get(self, *args, **kwargs): return self.request('GET', *args, **kwargs) @@ -32,7 +35,7 @@ class Api: } if payload_extra is not None: payload.update(payload_extra) - token = jwt.encode(payload, self.private_key, algorithm='RS256') + token = jwt.encode(payload, self.private_key, algorithm=self.jwt_algorithm) if isinstance(token, bytes): token = token.decode('utf8') return token @@ -46,3 +49,10 @@ def api_get(endpoint, method='GET', timeout=None, access_token=None, **params): headers['User-Agent'] = "openpilot-" + get_version() return requests.request(method, API_HOST + "/" + endpoint, timeout=timeout, headers=headers, params=params) + +def get_key_pair(): + for key in KEYS: + if os.path.isfile(Paths.persist_root() + f'/comma/{key}') and os.path.isfile(Paths.persist_root() + f'/comma/{key}.pub'): + with open(Paths.persist_root() + f'/comma/{key}') as private, open(Paths.persist_root() + f'/comma/{key}.pub') as public: + return KEYS[key], private.read(), public.read() + return None, None, None diff --git a/system/athena/registration.py b/system/athena/registration.py index 80c087188..81aee1ebf 100755 --- a/system/athena/registration.py +++ b/system/athena/registration.py @@ -5,7 +5,7 @@ import jwt from pathlib import Path from datetime import datetime, timedelta, UTC -from openpilot.common.api import api_get +from openpilot.common.api import api_get, get_key_pair from openpilot.common.params import Params from openpilot.common.spinner import Spinner from openpilot.selfdrive.selfdrived.alertmanager import set_offroad_alert @@ -39,20 +39,17 @@ def register(show_spinner=False) -> str | None: with open(Paths.persist_root()+"/comma/dongle_id") as f: dongle_id = f.read().strip() - pubkey = Path(Paths.persist_root()+"/comma/id_rsa.pub") - if not pubkey.is_file(): + # Create registration token, in the future, this key will make JWTs directly + jwt_algo, private_key, public_key = get_key_pair() + + if not public_key: dongle_id = UNREGISTERED_DONGLE_ID - cloudlog.warning(f"missing public key: {pubkey}") + cloudlog.warning("missing public key") elif dongle_id is None: if show_spinner: spinner = Spinner() spinner.update("registering device") - # Create registration token, in the future, this key will make JWTs directly - with open(Paths.persist_root()+"/comma/id_rsa.pub") as f1, open(Paths.persist_root()+"/comma/id_rsa") as f2: - public_key = f1.read() - private_key = f2.read() - # Block until we get the imei serial = HARDWARE.get_serial() start_time = time.monotonic() @@ -72,7 +69,7 @@ def register(show_spinner=False) -> str | None: start_time = time.monotonic() while True: try: - register_token = jwt.encode({'register': True, 'exp': datetime.now(UTC).replace(tzinfo=None) + timedelta(hours=1)}, private_key, algorithm='RS256') + register_token = jwt.encode({'register': True, 'exp': datetime.now(UTC).replace(tzinfo=None) + timedelta(hours=1)}, private_key, algorithm=jwt_algo) cloudlog.info("getting pilotauth") resp = api_get("v2/pilotauth/", method='POST', timeout=15, imei=imei1, imei2=imei2, serial=serial, public_key=public_key, register_token=register_token) From 38eb400e41c18bfdb6fbde00f1217661aec22e81 Mon Sep 17 00:00:00 2001 From: ZwX1616 Date: Tue, 4 Nov 2025 17:00:15 -0700 Subject: [PATCH 311/910] monitoring: account for OS cam distribution shift (#36567) * this should match * roughly matching FPR at 2 to 1 cost --- selfdrive/monitoring/helpers.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/selfdrive/monitoring/helpers.py b/selfdrive/monitoring/helpers.py index c2e5bc3fe..83904e2fb 100644 --- a/selfdrive/monitoring/helpers.py +++ b/selfdrive/monitoring/helpers.py @@ -9,6 +9,7 @@ from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.params import Params from openpilot.common.stat_live import RunningStatFilter from openpilot.common.transformations.camera import DEVICE_CAMERAS +from openpilot.system.hardware import HARDWARE EventName = log.OnroadEvent.EventName @@ -34,7 +35,10 @@ class DRIVER_MONITOR_SETTINGS: self._SG_THRESHOLD = 0.9 self._BLINK_THRESHOLD = 0.865 - self._EE_THRESH11 = 0.4 + if HARDWARE.get_device_type() == 'mici': + self._EE_THRESH11 = 0.75 + else: + self._EE_THRESH11 = 0.4 self._EE_THRESH12 = 15.0 self._EE_MAX_OFFSET1 = 0.06 self._EE_MIN_OFFSET1 = 0.025 From 36e53c7394730be54c8e2e28c811d8e16d5e54ea Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Tue, 4 Nov 2025 19:18:16 -0800 Subject: [PATCH 312/910] bump panda --- panda | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panda b/panda index 615009cf0..1c4403556 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 615009cf0f8fb8f3feadac160fbb0a07e4de171b +Subproject commit 1c440355633370320268ba6a3ca003dc34eb6697 From 0a44b48e216067212f578f94a4a082455c461b67 Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Wed, 5 Nov 2025 13:35:56 -0500 Subject: [PATCH 313/910] gitignore: add raylib test UI screenshots report path (#36570) ui: update .gitignore to include raylib_report --- selfdrive/ui/tests/.gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/selfdrive/ui/tests/.gitignore b/selfdrive/ui/tests/.gitignore index 91898ac59..d926a7ae8 100644 --- a/selfdrive/ui/tests/.gitignore +++ b/selfdrive/ui/tests/.gitignore @@ -1,3 +1,4 @@ test test_translations -test_ui/report_1 \ No newline at end of file +test_ui/report_1 +test_ui/raylib_report From ee8970dc4289b2b2fc6c6d78d1fa30e64d02d372 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 5 Nov 2025 16:23:33 -0800 Subject: [PATCH 314/910] ui: add route-based profiler (#36576) * ui: add route-based profiler * cleanup * this is stupid --- .gitignore | 1 + selfdrive/ui/tests/profile_onroad.py | 112 +++++++++++++++++++++++++++ tools/lib/tests/test_logreader.py | 1 + 3 files changed, 114 insertions(+) create mode 100755 selfdrive/ui/tests/profile_onroad.py diff --git a/.gitignore b/.gitignore index b700df0c8..e4992a3d0 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,7 @@ a.out *.vcd *.mo *_pyx.cpp +*.stats config.json clcache compile_commands.json diff --git a/selfdrive/ui/tests/profile_onroad.py b/selfdrive/ui/tests/profile_onroad.py new file mode 100755 index 000000000..0294125ce --- /dev/null +++ b/selfdrive/ui/tests/profile_onroad.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python3 +import os +import time +import cProfile +import pyray as rl +import numpy as np + +from msgq.visionipc import VisionIpcServer, VisionStreamType +from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.selfdrive.ui.layouts.main import MainLayout +from openpilot.system.ui.lib.application import gui_app +from openpilot.tools.lib.logreader import LogReader +from openpilot.tools.plotjuggler.juggle import DEMO_ROUTE + +FPS = 60 + + +def chunk_messages_by_time(messages): + dt_ns = 1e9 / FPS + chunks = [] + current_services = {} + next_time = messages[0].logMonoTime + dt_ns if messages else 0 + + for msg in messages: + if msg.logMonoTime >= next_time: + chunks.append(current_services) + current_services = {} + next_time += dt_ns * ((msg.logMonoTime - next_time) // dt_ns + 1) + current_services[msg.which()] = msg + + if current_services: + chunks.append(current_services) + return chunks + + +def patch_submaster(message_chunks): + def mock_update(timeout=None): + sm = ui_state.sm + sm.updated = dict.fromkeys(sm.services, False) + current_time = time.monotonic() + for service, msg in message_chunks[sm.frame].items(): + if service in sm.data: + sm.seen[service] = True + sm.updated[service] = True + + msg_builder = msg.as_builder() + sm.data[service] = getattr(msg_builder, service) + sm.logMonoTime[service] = msg.logMonoTime + sm.recv_time[service] = current_time + sm.recv_frame[service] = sm.frame + sm.valid[service] = True + sm.frame += 1 + ui_state.sm.update = mock_update + + +if __name__ == "__main__": + import argparse + parser = argparse.ArgumentParser(description='Profile openpilot UI rendering and state updates') + parser.add_argument('route', type=str, nargs='?', default=DEMO_ROUTE + "/1", + help='Route to use for profiling') + parser.add_argument('--loop', type=int, default=1, + help='Number of times to loop the log (default: 1)') + parser.add_argument('--output', type=str, default='cachegrind.out.ui', + help='Output file prefix (default: cachegrind.out.ui)') + parser.add_argument('--max-seconds', type=float, default=None, + help='Maximum seconds of messages to process (default: all)') + parser.add_argument('--headless', action='store_true', + help='Run in headless mode without GPU (for CI/testing)') + args = parser.parse_args() + + print(f"Loading log from {args.route}...") + lr = LogReader(args.route, sort_by_time=True) + messages = list(lr) * args.loop + + print("Chunking messages...") + message_chunks = chunk_messages_by_time(messages) + if args.max_seconds: + message_chunks = message_chunks[:int(args.max_seconds * FPS)] + + print("Initializing UI with GPU rendering...") + + if args.headless: + os.environ['SDL_VIDEODRIVER'] = 'dummy' + + gui_app.init_window("UI Profiling") + main_layout = MainLayout() + main_layout.set_rect(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) + + print("Running...") + patch_submaster(message_chunks) + + W, H = 1928, 1208 + vipc = VisionIpcServer("camerad") + vipc.create_buffers(VisionStreamType.VISION_STREAM_ROAD, 5, 1928, 1208) + vipc.start_listener() + yuv_buffer_size = W * H + (W // 2) * (H // 2) * 2 + yuv_data = np.random.randint(0, 256, yuv_buffer_size, dtype=np.uint8).tobytes() + with cProfile.Profile() as pr: + for should_render in gui_app.render(): + if ui_state.sm.frame >= len(message_chunks): + break + if ui_state.sm.frame % 3 == 0: + eof = int((ui_state.sm.frame % 3) * 0.05 * 1e9) + vipc.send(VisionStreamType.VISION_STREAM_ROAD, yuv_data, ui_state.sm.frame % 3, eof, eof) + ui_state.update() + if should_render: + main_layout.render() + pr.dump_stats(f'{args.output}_deterministic.stats') + + rl.close_window() + print("\nProfiling complete!") + print(f" run: python -m pstats {args.output}_deterministic.stats") diff --git a/tools/lib/tests/test_logreader.py b/tools/lib/tests/test_logreader.py index 8d0870171..ee75a8b1c 100644 --- a/tools/lib/tests/test_logreader.py +++ b/tools/lib/tests/test_logreader.py @@ -72,6 +72,7 @@ class TestLogReader: (f"https://useradmin.comma.ai/?onebox={TEST_ROUTE.replace('/', '|')}", ALL_SEGS), (f"https://useradmin.comma.ai/?onebox={TEST_ROUTE.replace('/', '%7C')}", ALL_SEGS), ]) + @pytest.mark.skip("this got flaky. internet tests are stupid.") def test_indirect_parsing(self, identifier, expected): parsed = parse_indirect(identifier) sr = SegmentRange(parsed) From dc5f5eaf65186ecc447dc192363aeebd5be73ef8 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 5 Nov 2025 16:34:19 -0800 Subject: [PATCH 315/910] make github LFS work if you want it --- SConstruct | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SConstruct b/SConstruct index 648afdae5..094503cfa 100644 --- a/SConstruct +++ b/SConstruct @@ -22,7 +22,7 @@ AddOption('--ccflags', action='store', type='string', default='', help='pass arb AddOption('--minimal', action='store_false', dest='extras', - default=os.path.exists(File('#.lfsconfig').abspath), # minimal by default on release branch (where there's no LFS) + default=os.path.exists(File('#.gitattributes').abspath), # minimal by default on release branch (where there's no LFS) help='the minimum build to run openpilot. no tests, tools, etc.') # Detect platform From 89919c8832df73904a873c0205c44faae85e7af7 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 5 Nov 2025 18:55:50 -0800 Subject: [PATCH 316/910] this is not a good api --- system/ubloxd/ubloxd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/ubloxd/ubloxd.py b/system/ubloxd/ubloxd.py index 84a926dd7..6882ad095 100755 --- a/system/ubloxd/ubloxd.py +++ b/system/ubloxd/ubloxd.py @@ -498,7 +498,7 @@ def main(): sock = messaging.sub_sock('ubloxRaw', timeout=100, conflate=False) while True: - msg = messaging.recv_one_or_none(sock) + msg = messaging.recv_one(sock) if msg is None: continue From 2d31b422c84b1ebba9e7aa5eedc228bb7dd41593 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 5 Nov 2025 23:01:10 -0800 Subject: [PATCH 317/910] ui: prep for 60fps (#36585) --- .gitmodules | 2 +- launch_env.sh | 9 +++++++++ selfdrive/ui/ui.py | 4 ++-- system/hardware/tici/hardware.py | 3 ++- tinygrad_repo | 2 +- 5 files changed, 15 insertions(+), 5 deletions(-) diff --git a/.gitmodules b/.gitmodules index ad6530de9..54c739398 100644 --- a/.gitmodules +++ b/.gitmodules @@ -15,4 +15,4 @@ url = ../../commaai/teleoprtc [submodule "tinygrad"] path = tinygrad_repo - url = https://github.com/tinygrad/tinygrad.git + url = https://github.com/commaai/tinygrad.git diff --git a/launch_env.sh b/launch_env.sh index 3715ad248..a7ac013c5 100755 --- a/launch_env.sh +++ b/launch_env.sh @@ -6,6 +6,15 @@ export NUMEXPR_NUM_THREADS=1 export OPENBLAS_NUM_THREADS=1 export VECLIB_MAXIMUM_THREADS=1 +# models get lower priority than ui +# - ui is ~5ms +# - modeld is 20ms +# - DM is 10ms +# in order to run ui at 60fps (16.67ms), we need to allow +# it to preempt the model workloads. we have enough +# headroom for this until ui is moved to the CPU. +export QCOM_PRIORITY=12 + if [ -z "$AGNOS_VERSION" ]; then export AGNOS_VERSION="14.5" fi diff --git a/selfdrive/ui/ui.py b/selfdrive/ui/ui.py index 222a25b87..33d05f89e 100755 --- a/selfdrive/ui/ui.py +++ b/selfdrive/ui/ui.py @@ -9,8 +9,8 @@ from openpilot.selfdrive.ui.ui_state import ui_state def main(): - cores = {7, } - config_realtime_process(0, 1) + cores = {5, } + config_realtime_process(0, 51) gui_app.init_window("UI") main_layout = MainLayout() diff --git a/system/hardware/tici/hardware.py b/system/hardware/tici/hardware.py index ff2622e96..9791f15f3 100644 --- a/system/hardware/tici/hardware.py +++ b/system/hardware/tici/hardware.py @@ -424,7 +424,8 @@ class Tici(HardwareBase): # *** GPU config *** # https://github.com/commaai/agnos-kernel-sdm845/blob/master/arch/arm64/boot/dts/qcom/sdm845-gpu.dtsi#L216 - affine_irq(7, "msm_drm") # display + affine_irq(5, "fts_ts") # touch + affine_irq(5, "msm_drm") # display sudo_write("1", "/sys/class/kgsl/kgsl-3d0/min_pwrlevel") sudo_write("1", "/sys/class/kgsl/kgsl-3d0/max_pwrlevel") sudo_write("1", "/sys/class/kgsl/kgsl-3d0/force_bus_on") diff --git a/tinygrad_repo b/tinygrad_repo index a6fd96f62..7296c74cb 160000 --- a/tinygrad_repo +++ b/tinygrad_repo @@ -1 +1 @@ -Subproject commit a6fd96f62050efd4a2fe7c885d1a11f87e3c5b0a +Subproject commit 7296c74cbd2666da7dce95d7ca6dab5340653a5c From 10100e34e191d32c70eba513529175fec80a9ed9 Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Thu, 6 Nov 2025 01:04:17 -0800 Subject: [PATCH 318/910] bump raylib --- third_party/raylib/build.sh | 2 +- third_party/raylib/larch64/libraylib.a | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/third_party/raylib/build.sh b/third_party/raylib/build.sh index 0b5024448..0a9fffc1d 100755 --- a/third_party/raylib/build.sh +++ b/third_party/raylib/build.sh @@ -30,7 +30,7 @@ fi cd raylib_repo -COMMIT=${1:-aa6ade09ac4bfb2847a356535f2d9f87e49ab089} +COMMIT=${1:-d6ced9338be75fd92b2ac72cdb87a4fb252b3a89} git fetch origin $COMMIT git reset --hard $COMMIT git clean -xdff . diff --git a/third_party/raylib/larch64/libraylib.a b/third_party/raylib/larch64/libraylib.a index 954aa0d48..e7a435b93 100644 --- a/third_party/raylib/larch64/libraylib.a +++ b/third_party/raylib/larch64/libraylib.a @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8bb734fb8733e1762081945f4f3ddf8d3f7379a0ea4790ee925803cd00129ccb -size 3156548 +oid sha256:873a985904830f64e90b404f560f2502ba30b8744afff98dd9972d5feb66c58b +size 2002068 From e5a7deb6ad53c24bc5dfac32259bedabf15afd4d Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Thu, 6 Nov 2025 01:22:37 -0800 Subject: [PATCH 319/910] AGNOS 14.6 (#36586) * stage * ver * prod --- launch_env.sh | 2 +- system/hardware/tici/agnos.json | 12 ++++---- system/hardware/tici/all-partitions.json | 36 ++++++++++++------------ 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/launch_env.sh b/launch_env.sh index a7ac013c5..ee4d860e9 100755 --- a/launch_env.sh +++ b/launch_env.sh @@ -16,7 +16,7 @@ export VECLIB_MAXIMUM_THREADS=1 export QCOM_PRIORITY=12 if [ -z "$AGNOS_VERSION" ]; then - export AGNOS_VERSION="14.5" + export AGNOS_VERSION="14.6" fi export STAGING_ROOT="/data/safe_staging" diff --git a/system/hardware/tici/agnos.json b/system/hardware/tici/agnos.json index d6a6ab51c..e664f31e8 100644 --- a/system/hardware/tici/agnos.json +++ b/system/hardware/tici/agnos.json @@ -67,17 +67,17 @@ }, { "name": "system", - "url": "https://commadist.azureedge.net/agnosupdate/system-54ca532116840b8532ef3c5a3e9ef4b073ed8c6b66cb5e30ad299e28fff35502.img.xz", - "hash": "6e2f82c249f6feacdfcf20679e9e4876d161753a94d4068fe26b5c41191bda24", - "hash_raw": "54ca532116840b8532ef3c5a3e9ef4b073ed8c6b66cb5e30ad299e28fff35502", + "url": "https://commadist.azureedge.net/agnosupdate/system-88820a52e0abff08143c13c00aa3982ae00a8b5c676d131f91ff51add76ea4bf.img.xz", + "hash": "8e28707820902c887904ae86911a02caedb02a6f8780ad75e00e841fdda0b88e", + "hash_raw": "88820a52e0abff08143c13c00aa3982ae00a8b5c676d131f91ff51add76ea4bf", "size": 4718592000, "sparse": true, "full_check": false, "has_ab": true, - "ondevice_hash": "f3a50de42734f747611f6df00087b3a6ec3620bed07134d327a864c1182f0df8", + "ondevice_hash": "e18c6f54c9d9dec4b00361403749517f5bb509e6bccb7c114cbc0916ed32477e", "alt": { - "hash": "54ca532116840b8532ef3c5a3e9ef4b073ed8c6b66cb5e30ad299e28fff35502", - "url": "https://commadist.azureedge.net/agnosupdate/system-54ca532116840b8532ef3c5a3e9ef4b073ed8c6b66cb5e30ad299e28fff35502.img", + "hash": "88820a52e0abff08143c13c00aa3982ae00a8b5c676d131f91ff51add76ea4bf", + "url": "https://commadist.azureedge.net/agnosupdate/system-88820a52e0abff08143c13c00aa3982ae00a8b5c676d131f91ff51add76ea4bf.img", "size": 4718592000 } } diff --git a/system/hardware/tici/all-partitions.json b/system/hardware/tici/all-partitions.json index c51453f4c..04a690e32 100644 --- a/system/hardware/tici/all-partitions.json +++ b/system/hardware/tici/all-partitions.json @@ -350,51 +350,51 @@ }, { "name": "system", - "url": "https://commadist.azureedge.net/agnosupdate/system-54ca532116840b8532ef3c5a3e9ef4b073ed8c6b66cb5e30ad299e28fff35502.img.xz", - "hash": "6e2f82c249f6feacdfcf20679e9e4876d161753a94d4068fe26b5c41191bda24", - "hash_raw": "54ca532116840b8532ef3c5a3e9ef4b073ed8c6b66cb5e30ad299e28fff35502", + "url": "https://commadist.azureedge.net/agnosupdate/system-88820a52e0abff08143c13c00aa3982ae00a8b5c676d131f91ff51add76ea4bf.img.xz", + "hash": "8e28707820902c887904ae86911a02caedb02a6f8780ad75e00e841fdda0b88e", + "hash_raw": "88820a52e0abff08143c13c00aa3982ae00a8b5c676d131f91ff51add76ea4bf", "size": 4718592000, "sparse": true, "full_check": false, "has_ab": true, - "ondevice_hash": "f3a50de42734f747611f6df00087b3a6ec3620bed07134d327a864c1182f0df8", + "ondevice_hash": "e18c6f54c9d9dec4b00361403749517f5bb509e6bccb7c114cbc0916ed32477e", "alt": { - "hash": "54ca532116840b8532ef3c5a3e9ef4b073ed8c6b66cb5e30ad299e28fff35502", - "url": "https://commadist.azureedge.net/agnosupdate/system-54ca532116840b8532ef3c5a3e9ef4b073ed8c6b66cb5e30ad299e28fff35502.img", + "hash": "88820a52e0abff08143c13c00aa3982ae00a8b5c676d131f91ff51add76ea4bf", + "url": "https://commadist.azureedge.net/agnosupdate/system-88820a52e0abff08143c13c00aa3982ae00a8b5c676d131f91ff51add76ea4bf.img", "size": 4718592000 } }, { "name": "userdata_90", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_90-7b58b8c6935e998b93fae25aa358dc808e90b032c004b7f7ec6e3a60986cd41d.img.xz", - "hash": "5115d8a8bc4ad8eebaf0e3176cc68799089c72f64504f7238f411cbaf5d18497", - "hash_raw": "7b58b8c6935e998b93fae25aa358dc808e90b032c004b7f7ec6e3a60986cd41d", + "url": "https://commadist.azureedge.net/agnosupdate/userdata_90-fa4b3568883ccaab51696ef7a9e000fccd6f1f2ce0e13b7ec5c7422a251b777e.img.xz", + "hash": "f3666af71ab0c07c754871700cb64915312ddb2ef5d66223e696481a8bfeea6a", + "hash_raw": "fa4b3568883ccaab51696ef7a9e000fccd6f1f2ce0e13b7ec5c7422a251b777e", "size": 96636764160, "sparse": true, "full_check": true, "has_ab": false, - "ondevice_hash": "b0ee797f751bc776bcb161a479cfb7f339bbf92f0cb207b053797daef0ac61d8" + "ondevice_hash": "36330a8d454370db7e24a5967ec82490a214303b5b117bc2e53fa23097c44e8a" }, { "name": "userdata_89", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_89-a5a3e2fe9a8bf83ea37aabafdd7fd5f6e274f8ef3d1fb46ce56766cec8f13215.img.xz", - "hash": "c0348854c1f93a0557c9ce85f709c8d19f1d11df0c3ba6cd4aa01ada3d2293f3", - "hash_raw": "a5a3e2fe9a8bf83ea37aabafdd7fd5f6e274f8ef3d1fb46ce56766cec8f13215", + "url": "https://commadist.azureedge.net/agnosupdate/userdata_89-c6b25bd390e42e2d6ab6c18725fce059f904c2a6a36ff6dec054a38e7a930419.img.xz", + "hash": "02cdc0ce26a96c39f9b5077c53fb02afccfaae7d02a4f1ea5cbb2a3da33299f3", + "hash_raw": "c6b25bd390e42e2d6ab6c18725fce059f904c2a6a36ff6dec054a38e7a930419", "size": 95563022336, "sparse": true, "full_check": true, "has_ab": false, - "ondevice_hash": "2def7bc3fed2cdcbaf4869fd2f68ad1d62f6d89b423d44c7114faf22f449c29f" + "ondevice_hash": "1f70fe65585c2d2561819bd92bbc1255fceeacb3507b67c9d596797a06b936e0" }, { "name": "userdata_30", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_30-b86b949d9ed1978e792fccf59c1484238890e2cb904a501db64ed96bb91df4f9.img.xz", - "hash": "e0cee51c596d77e265b3bae937e9a3b4f317d0e80c091f03ca1bfbbdadbc6536", - "hash_raw": "b86b949d9ed1978e792fccf59c1484238890e2cb904a501db64ed96bb91df4f9", + "url": "https://commadist.azureedge.net/agnosupdate/userdata_30-ef5138ccb96cd0acb5d2e04d1b38fe2595c28e1beff7f610993c19f811f85d3c.img.xz", + "hash": "759a2db9bc8ee3cdc3d6a6c4fcbbaaed13daeda40d2969bbb9bf11718f24c521", + "hash_raw": "ef5138ccb96cd0acb5d2e04d1b38fe2595c28e1beff7f610993c19f811f85d3c", "size": 32212254720, "sparse": true, "full_check": true, "has_ab": false, - "ondevice_hash": "38d7e4d3105102ca5a8dedb2764cb16f6e36766bbbb9d87e557e0d727ec37a58" + "ondevice_hash": "23a7eae03e4c3c204a87d83f02017805f061f5df88e25c482525afd18b66a243" } ] \ No newline at end of file From b6bcc8cca32f9df8e63afe7c98f5d738b5383da5 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 6 Nov 2025 09:42:23 -0800 Subject: [PATCH 320/910] ui: fix running on macOS --- selfdrive/ui/ui.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/selfdrive/ui/ui.py b/selfdrive/ui/ui.py index 33d05f89e..a4b99825e 100755 --- a/selfdrive/ui/ui.py +++ b/selfdrive/ui/ui.py @@ -2,6 +2,7 @@ import os import pyray as rl +from openpilot.system.hardware import TICI from openpilot.common.realtime import config_realtime_process, set_core_affinity from openpilot.system.ui.lib.application import gui_app from openpilot.selfdrive.ui.layouts.main import MainLayout @@ -21,7 +22,7 @@ def main(): main_layout.render() # reaffine after power save offlines our core - if os.sched_getaffinity(0) != cores: + if TICI and os.sched_getaffinity(0) != cores: try: set_core_affinity(list(cores)) except OSError: From fb34601d5a414e832a68e4357dc5b1653f0807cb Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Thu, 6 Nov 2025 20:46:04 -0800 Subject: [PATCH 321/910] Revert "bump panda" (#36594) This reverts commit 36e53c7394730be54c8e2e28c811d8e16d5e54ea. --- panda | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panda b/panda index 1c4403556..615009cf0 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 1c440355633370320268ba6a3ca003dc34eb6697 +Subproject commit 615009cf0f8fb8f3feadac160fbb0a07e4de171b From 2dcb67091ff3644fee09d974d954ada148ed501c Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Thu, 6 Nov 2025 23:51:17 -0500 Subject: [PATCH 322/910] remove unused MAX_POINTS constant from model_renderer.py (#36593) --- selfdrive/ui/onroad/model_renderer.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/selfdrive/ui/onroad/model_renderer.py b/selfdrive/ui/onroad/model_renderer.py index 1e4fbbb78..b9f601f8f 100644 --- a/selfdrive/ui/onroad/model_renderer.py +++ b/selfdrive/ui/onroad/model_renderer.py @@ -15,8 +15,6 @@ CLIP_MARGIN = 500 MIN_DRAW_DISTANCE = 10.0 MAX_DRAW_DISTANCE = 100.0 -MAX_POINTS = 200 - THROTTLE_COLORS = [ rl.Color(13, 248, 122, 102), # HSLF(148/360, 0.94, 0.51, 0.4) rl.Color(114, 255, 92, 89), # HSLF(112/360, 1.0, 0.68, 0.35) From 16336410558feeb4799236e915611ce7fe3d1193 Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Thu, 6 Nov 2025 21:00:26 -0800 Subject: [PATCH 323/910] bump raylib (#36596) this --- third_party/raylib/build.sh | 4 ++-- third_party/raylib/larch64/libraylib.a | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/third_party/raylib/build.sh b/third_party/raylib/build.sh index 0a9fffc1d..ab6bbb977 100755 --- a/third_party/raylib/build.sh +++ b/third_party/raylib/build.sh @@ -30,7 +30,7 @@ fi cd raylib_repo -COMMIT=${1:-d6ced9338be75fd92b2ac72cdb87a4fb252b3a89} +COMMIT=${1:-97dc6a9f1da2b5bbca6fee86b28ac79f7b28b573} git fetch origin $COMMIT git reset --hard $COMMIT git clean -xdff . @@ -57,7 +57,7 @@ if [ -f /TICI ]; then cd raylib_python_repo - BINDINGS_COMMIT="ab0191f445272ca66758f9dd345b7395518d6a77" + BINDINGS_COMMIT="a0710d95af3c12fd7f4b639589be9a13dad93cb6" git fetch origin $BINDINGS_COMMIT git reset --hard $BINDINGS_COMMIT git clean -xdff . diff --git a/third_party/raylib/larch64/libraylib.a b/third_party/raylib/larch64/libraylib.a index e7a435b93..a0f7734d2 100644 --- a/third_party/raylib/larch64/libraylib.a +++ b/third_party/raylib/larch64/libraylib.a @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:873a985904830f64e90b404f560f2502ba30b8744afff98dd9972d5feb66c58b -size 2002068 +oid sha256:f97b6b4ca09ccf8a3e2cca1c8dadffadf12ffad6e25a4c91898e5bb34c8c243f +size 2001812 From 890b1cf512b9a09d47731d254338ac9456e03fb5 Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Fri, 7 Nov 2025 02:54:52 -0800 Subject: [PATCH 324/910] AGNOS 14.7 (#36597) * stage * prod --- launch_env.sh | 2 +- system/hardware/tici/agnos.json | 12 ++++---- system/hardware/tici/all-partitions.json | 36 ++++++++++++------------ 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/launch_env.sh b/launch_env.sh index ee4d860e9..6fc51c5b8 100755 --- a/launch_env.sh +++ b/launch_env.sh @@ -16,7 +16,7 @@ export VECLIB_MAXIMUM_THREADS=1 export QCOM_PRIORITY=12 if [ -z "$AGNOS_VERSION" ]; then - export AGNOS_VERSION="14.6" + export AGNOS_VERSION="14.7" fi export STAGING_ROOT="/data/safe_staging" diff --git a/system/hardware/tici/agnos.json b/system/hardware/tici/agnos.json index e664f31e8..1143f4f08 100644 --- a/system/hardware/tici/agnos.json +++ b/system/hardware/tici/agnos.json @@ -67,17 +67,17 @@ }, { "name": "system", - "url": "https://commadist.azureedge.net/agnosupdate/system-88820a52e0abff08143c13c00aa3982ae00a8b5c676d131f91ff51add76ea4bf.img.xz", - "hash": "8e28707820902c887904ae86911a02caedb02a6f8780ad75e00e841fdda0b88e", - "hash_raw": "88820a52e0abff08143c13c00aa3982ae00a8b5c676d131f91ff51add76ea4bf", + "url": "https://commadist.azureedge.net/agnosupdate/system-2714d2d2cad6957060c4cfa61fcaa2a3826238278e849e28fa6241c18b18a268.img.xz", + "hash": "fbc85e68a9c94fd520953c6fea64b1ec070ce12c09787de637fc0cc5cbb91846", + "hash_raw": "2714d2d2cad6957060c4cfa61fcaa2a3826238278e849e28fa6241c18b18a268", "size": 4718592000, "sparse": true, "full_check": false, "has_ab": true, - "ondevice_hash": "e18c6f54c9d9dec4b00361403749517f5bb509e6bccb7c114cbc0916ed32477e", + "ondevice_hash": "dfd29ca36003da518c8f0e91476c2210c3e61f2e2d8e96c1abb8f04d0c2c62a3", "alt": { - "hash": "88820a52e0abff08143c13c00aa3982ae00a8b5c676d131f91ff51add76ea4bf", - "url": "https://commadist.azureedge.net/agnosupdate/system-88820a52e0abff08143c13c00aa3982ae00a8b5c676d131f91ff51add76ea4bf.img", + "hash": "2714d2d2cad6957060c4cfa61fcaa2a3826238278e849e28fa6241c18b18a268", + "url": "https://commadist.azureedge.net/agnosupdate/system-2714d2d2cad6957060c4cfa61fcaa2a3826238278e849e28fa6241c18b18a268.img", "size": 4718592000 } } diff --git a/system/hardware/tici/all-partitions.json b/system/hardware/tici/all-partitions.json index 04a690e32..5082647a1 100644 --- a/system/hardware/tici/all-partitions.json +++ b/system/hardware/tici/all-partitions.json @@ -350,51 +350,51 @@ }, { "name": "system", - "url": "https://commadist.azureedge.net/agnosupdate/system-88820a52e0abff08143c13c00aa3982ae00a8b5c676d131f91ff51add76ea4bf.img.xz", - "hash": "8e28707820902c887904ae86911a02caedb02a6f8780ad75e00e841fdda0b88e", - "hash_raw": "88820a52e0abff08143c13c00aa3982ae00a8b5c676d131f91ff51add76ea4bf", + "url": "https://commadist.azureedge.net/agnosupdate/system-2714d2d2cad6957060c4cfa61fcaa2a3826238278e849e28fa6241c18b18a268.img.xz", + "hash": "fbc85e68a9c94fd520953c6fea64b1ec070ce12c09787de637fc0cc5cbb91846", + "hash_raw": "2714d2d2cad6957060c4cfa61fcaa2a3826238278e849e28fa6241c18b18a268", "size": 4718592000, "sparse": true, "full_check": false, "has_ab": true, - "ondevice_hash": "e18c6f54c9d9dec4b00361403749517f5bb509e6bccb7c114cbc0916ed32477e", + "ondevice_hash": "dfd29ca36003da518c8f0e91476c2210c3e61f2e2d8e96c1abb8f04d0c2c62a3", "alt": { - "hash": "88820a52e0abff08143c13c00aa3982ae00a8b5c676d131f91ff51add76ea4bf", - "url": "https://commadist.azureedge.net/agnosupdate/system-88820a52e0abff08143c13c00aa3982ae00a8b5c676d131f91ff51add76ea4bf.img", + "hash": "2714d2d2cad6957060c4cfa61fcaa2a3826238278e849e28fa6241c18b18a268", + "url": "https://commadist.azureedge.net/agnosupdate/system-2714d2d2cad6957060c4cfa61fcaa2a3826238278e849e28fa6241c18b18a268.img", "size": 4718592000 } }, { "name": "userdata_90", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_90-fa4b3568883ccaab51696ef7a9e000fccd6f1f2ce0e13b7ec5c7422a251b777e.img.xz", - "hash": "f3666af71ab0c07c754871700cb64915312ddb2ef5d66223e696481a8bfeea6a", - "hash_raw": "fa4b3568883ccaab51696ef7a9e000fccd6f1f2ce0e13b7ec5c7422a251b777e", + "url": "https://commadist.azureedge.net/agnosupdate/userdata_90-43fd7a1632a23a878d19f5878eb983e3fb763eb4be1aa0a1c7deb4a06bf732de.img.xz", + "hash": "67ec07f8180f666b9b5de1c8859d0f73240b88bdc97b24c6890f6d07f356c2d6", + "hash_raw": "43fd7a1632a23a878d19f5878eb983e3fb763eb4be1aa0a1c7deb4a06bf732de", "size": 96636764160, "sparse": true, "full_check": true, "has_ab": false, - "ondevice_hash": "36330a8d454370db7e24a5967ec82490a214303b5b117bc2e53fa23097c44e8a" + "ondevice_hash": "aadc8097899fcacb8037395f38bb452fa68f50d87ce31c40671fb8eff8e2eac2" }, { "name": "userdata_89", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_89-c6b25bd390e42e2d6ab6c18725fce059f904c2a6a36ff6dec054a38e7a930419.img.xz", - "hash": "02cdc0ce26a96c39f9b5077c53fb02afccfaae7d02a4f1ea5cbb2a3da33299f3", - "hash_raw": "c6b25bd390e42e2d6ab6c18725fce059f904c2a6a36ff6dec054a38e7a930419", + "url": "https://commadist.azureedge.net/agnosupdate/userdata_89-42ec6bf06ed94ff99a760d944e82f618583fa6576f80d1ca4c40cfe5949e2775.img.xz", + "hash": "e24e0a66f4de2b3bc23adc7995b78b3d547b881ede9456b2abfdcd77657bd71e", + "hash_raw": "42ec6bf06ed94ff99a760d944e82f618583fa6576f80d1ca4c40cfe5949e2775", "size": 95563022336, "sparse": true, "full_check": true, "has_ab": false, - "ondevice_hash": "1f70fe65585c2d2561819bd92bbc1255fceeacb3507b67c9d596797a06b936e0" + "ondevice_hash": "c3644225eb21fba5ff65971954b1d17986ca1b6bb44cae6729dab7d1181904bc" }, { "name": "userdata_30", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_30-ef5138ccb96cd0acb5d2e04d1b38fe2595c28e1beff7f610993c19f811f85d3c.img.xz", - "hash": "759a2db9bc8ee3cdc3d6a6c4fcbbaaed13daeda40d2969bbb9bf11718f24c521", - "hash_raw": "ef5138ccb96cd0acb5d2e04d1b38fe2595c28e1beff7f610993c19f811f85d3c", + "url": "https://commadist.azureedge.net/agnosupdate/userdata_30-8cb7da131c91753c7e53206f1ad55f0a1d9857dfb779f87e274992ba40c627eb.img.xz", + "hash": "e6990b2dfbaca7f45ab336079bcfc9d6bf198c5b05aa692277507f50f041cae8", + "hash_raw": "8cb7da131c91753c7e53206f1ad55f0a1d9857dfb779f87e274992ba40c627eb", "size": 32212254720, "sparse": true, "full_check": true, "has_ab": false, - "ondevice_hash": "23a7eae03e4c3c204a87d83f02017805f061f5df88e25c482525afd18b66a243" + "ondevice_hash": "3301335a96fcce7adbc56e85ef0f8b526480604444f2d33106cdc7c0ffe8a055" } ] \ No newline at end of file From 1262fca36b757b62f4f5d7a23ff55d1287a41c4f Mon Sep 17 00:00:00 2001 From: ZwX1616 Date: Fri, 7 Nov 2025 15:18:45 -0800 Subject: [PATCH 325/910] add check driver camera alert (#36577) * add event * missing arg * creation_delay is wrong * add logging * set offroad alert * Update selfdrive/selfdrived/alerts_offroad.json Co-authored-by: Shane Smiskol * rm onard * add details * rename to DM * log rename * no poss --------- Co-authored-by: Shane Smiskol --- cereal/log.capnp | 1 + common/params_keys.h | 1 + selfdrive/monitoring/helpers.py | 27 ++++++++++++++++++++++-- selfdrive/monitoring/test_monitoring.py | 2 +- selfdrive/selfdrived/alerts_offroad.json | 4 ++++ 5 files changed, 32 insertions(+), 3 deletions(-) diff --git a/cereal/log.capnp b/cereal/log.capnp index 6cd8196ae..981cfd468 100644 --- a/cereal/log.capnp +++ b/cereal/log.capnp @@ -2224,6 +2224,7 @@ struct DriverMonitoringState @0xb83cda094a1da284 { hiStdCount @14 :UInt32; isActiveMode @16 :Bool; isRHD @4 :Bool; + uncertainCount @19 :UInt32; isPreviewDEPRECATED @15 :Bool; rhdCheckedDEPRECATED @5 :Bool; diff --git a/common/params_keys.h b/common/params_keys.h index badc16214..8d4c8d9e4 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -97,6 +97,7 @@ inline static std::unordered_map keys = { {"Offroad_TemperatureTooHigh", {CLEAR_ON_MANAGER_START, JSON}}, {"Offroad_UnregisteredHardware", {CLEAR_ON_MANAGER_START, JSON}}, {"Offroad_UpdateFailed", {CLEAR_ON_MANAGER_START, JSON}}, + {"Offroad_DriverMonitoringUncertain", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, JSON}}, {"OnroadCycleRequested", {CLEAR_ON_MANAGER_START, BOOL}}, {"OpenpilotEnabledToggle", {PERSISTENT, BOOL, "1"}}, {"PandaHeartbeatLost", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}}, diff --git a/selfdrive/monitoring/helpers.py b/selfdrive/monitoring/helpers.py index 83904e2fb..02d8ff5c7 100644 --- a/selfdrive/monitoring/helpers.py +++ b/selfdrive/monitoring/helpers.py @@ -4,6 +4,7 @@ import numpy as np from cereal import car, log import cereal.messaging as messaging from openpilot.selfdrive.selfdrived.events import Events +from openpilot.selfdrive.selfdrived.alertmanager import set_offroad_alert from openpilot.common.realtime import DT_DMON from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.params import Params @@ -57,6 +58,9 @@ class DRIVER_MONITOR_SETTINGS: self._YAW_MAX_OFFSET = 0.289 self._YAW_MIN_OFFSET = -0.0246 + self._DCAM_UNCERTAIN_ALERT_THRESHOLD = 0.1 + self._DCAM_UNCERTAIN_ALERT_COUNT = int(60 / self._DT_DMON) + self._DCAM_UNCERTAIN_RESET_COUNT = int(20 / self._DT_DMON) self._POSESTD_THRESHOLD = 0.3 self._HI_STD_FALLBACK_TIME = int(10 / self._DT_DMON) # fall back to wheel touch if model is uncertain for 10s self._DISTRACTED_FILTER_TS = 0.25 # 0.6Hz @@ -158,6 +162,9 @@ class DriverMonitoring: self.hi_stds = 0 self.threshold_pre = self.settings._DISTRACTED_PRE_TIME_TILL_TERMINAL / self.settings._DISTRACTED_TIME self.threshold_prompt = self.settings._DISTRACTED_PROMPT_TIME_TILL_TERMINAL / self.settings._DISTRACTED_TIME + self.dcam_uncertain_cnt = 0 + self.dcam_uncertain_alerted = False # once per drive + self.dcam_reset_cnt = 0 self.params = Params() self.too_distracted = self.params.get_bool("DriverTooDistracted") @@ -245,7 +252,7 @@ class DriverMonitoring: return distracted_types - def _update_states(self, driver_state, cal_rpy, car_speed, op_engaged): + def _update_states(self, driver_state, cal_rpy, car_speed, op_engaged, standstill): rhd_pred = driver_state.wheelOnRightProb # calibrates only when there's movement and either face detected if car_speed > self.settings._WHEELPOS_CALIB_MIN_SPEED and (driver_state.leftDriverData.faceProb > self.settings._FACE_THRESHOLD or @@ -296,6 +303,16 @@ class DriverMonitoring: self.pose.yaw_offseter.filtered_stat.n > self.settings._POSE_OFFSET_MIN_COUNT self.ee1_calibrated = self.ee1_offseter.filtered_stat.n > self.settings._POSE_OFFSET_MIN_COUNT + if self.face_detected and not self.driver_distracted: + if model_std_max > self.settings._DCAM_UNCERTAIN_ALERT_THRESHOLD: + if not standstill: + self.dcam_uncertain_cnt += 1 + self.dcam_reset_cnt = 0 + else: + self.dcam_reset_cnt += 1 + if self.dcam_reset_cnt > self.settings._DCAM_UNCERTAIN_RESET_COUNT: + self.dcam_uncertain_cnt = 0 + self.is_model_uncertain = self.hi_stds > self.settings._HI_STD_FALLBACK_TIME self._set_timers(self.face_detected and not self.is_model_uncertain) if self.face_detected and not self.pose.low_std and not self.driver_distracted: @@ -372,6 +389,10 @@ class DriverMonitoring: if alert is not None: self.current_events.add(alert) + if self.dcam_uncertain_cnt > self.settings._DCAM_UNCERTAIN_ALERT_COUNT and not self.dcam_uncertain_alerted: + set_offroad_alert("Offroad_DriverMonitoringUncertain", True) + self.dcam_uncertain_alerted = True + def get_state_packet(self, valid=True): # build driverMonitoringState packet @@ -393,6 +414,7 @@ class DriverMonitoring: "hiStdCount": self.hi_stds, "isActiveMode": self.active_monitoring_mode, "isRHD": self.wheel_on_right, + "uncertainCount": self.dcam_uncertain_cnt, } return dat @@ -408,7 +430,8 @@ class DriverMonitoring: driver_state=sm['driverStateV2'], cal_rpy=sm['liveCalibration'].rpyCalib, car_speed=sm['carState'].vEgo, - op_engaged=sm['selfdriveState'].enabled + op_engaged=sm['selfdriveState'].enabled, + standstill=sm['carState'].standstill, ) # Update distraction events diff --git a/selfdrive/monitoring/test_monitoring.py b/selfdrive/monitoring/test_monitoring.py index 1cc710188..1f8babe02 100644 --- a/selfdrive/monitoring/test_monitoring.py +++ b/selfdrive/monitoring/test_monitoring.py @@ -53,7 +53,7 @@ class TestMonitoring: DM = DriverMonitoring() events = [] for idx in range(len(msgs)): - DM._update_states(msgs[idx], [0, 0, 0], 0, engaged[idx]) + DM._update_states(msgs[idx], [0, 0, 0], 0, engaged[idx], standstill[idx]) # cal_rpy and car_speed don't matter here # evaluate events at 10Hz for tests diff --git a/selfdrive/selfdrived/alerts_offroad.json b/selfdrive/selfdrived/alerts_offroad.json index 3d97135f6..b52dfa4d8 100644 --- a/selfdrive/selfdrived/alerts_offroad.json +++ b/selfdrive/selfdrived/alerts_offroad.json @@ -37,6 +37,10 @@ "text": "openpilot detected a change in the device's mounting position. Ensure the device is fully seated in the mount and the mount is firmly secured to the windshield.", "severity": 0 }, + "Offroad_DriverMonitoringUncertain": { + "text": "openpilot detected poor visibility for driver monitoring. Ensure the device has a clear view of the driver. This can be checked using Settings -> Device -> Driver Camera Preview. Extreme lighting conditions and/or unconventional mounting positions may also trigger this alert.", + "severity": 0 + }, "Offroad_ExcessiveActuation": { "text": "openpilot detected excessive %1 actuation on your last drive. Please contact support at https://comma.ai/support and share your device's Dongle ID for troubleshooting.", "severity": 1, From d4185a5d571583f02a2683ff635a964313ea9a25 Mon Sep 17 00:00:00 2001 From: Jason Young <46612682+jyoung8607@users.noreply.github.com> Date: Fri, 7 Nov 2025 17:37:40 -0800 Subject: [PATCH 326/910] docs: car porting (#36590) * checkpoint * door states, notes * updates * not worth it yet * wordsmith * more * more reverse engineering script content * Revise stationary ignition-only test steps Updated the steps for stationary ignition-only tests to include closing the driver's door and fastening the seatbelt before pressing the accelerator and brake pedals. * fix numbering --- docs/car-porting/car-state-signals.md | 65 +++++++++++++++++++ docs/car-porting/reverse-engineering.md | 85 +++++++++++++++++++++++++ 2 files changed, 150 insertions(+) create mode 100644 docs/car-porting/car-state-signals.md create mode 100644 docs/car-porting/reverse-engineering.md diff --git a/docs/car-porting/car-state-signals.md b/docs/car-porting/car-state-signals.md new file mode 100644 index 000000000..669bd0ee2 --- /dev/null +++ b/docs/car-porting/car-state-signals.md @@ -0,0 +1,65 @@ +# CarState signals + +## Required for basic lateral control + +* `brakePressed` +* `cruiseState` +* `doorOpen` +* `espDisabled` +* `gasPressed` +* `gearShifter` +* `leftBlinker` / `rightBlinker` +* `seatbeltUnlatched` +* `standstill` +* `steeringAngleDeg` +* `steeringPressed` +* `steeringTorque` +* `steerFaultPermanent` +* `steerFaultTemporary` +* `vCruise` +* `wheelSpeeds.[fl|fr|rl|rr]`: Speed of each of the car's four wheels, in m/s. The car's CAN bus often broadcasts the +speed in kph, so the helper function `parse_wheel_speeds` performs this conversion by default. + +## Recommended / Required for openpilot longitudinal control + +* `accFaulted` +* `espActive` +* `parkingBrake` + +## Application Dependent + +* `blockPcmEnable` +* `buttonEnable` +* `brakeHoldActive` +* `carFaultedNonCritical` +* `invalidLkasSetting` +* `lowSpeedAlert` +* `regenBraking` +* `steeringAngleOffsetDeg` +* `steeringDisengage` +* `steeringTorqueEps` +* `stockLkas` +* `vCruiseCluster` +* `vEgoCluster` +* `vehicleSensorsInvalid` + +## Automatically populated + +* `buttonEvents` + +These values are populated automatically by `parse_wheel_speeds`: + +* `aEgo`: Acceleration of the ego vehicle, Kalman filtered derivative of `vEgo`. +* `vEgo`: Speed of the ego vehicle, Kalman filtered from `vEgoRaw`. +* `vEgoRaw`: Speed of the ego vehicle, based on the average of all four wheel speeds, unfiltered. + +## Optional + +* `brake` +* `charging` +* `fuelGauge` +* `leftBlindspot` / `rightBlindspot` +* `steeringRateDeg` +* `stockAeb` +* `stockFcw` +* `yawRate` diff --git a/docs/car-porting/reverse-engineering.md b/docs/car-porting/reverse-engineering.md new file mode 100644 index 000000000..128ec8e77 --- /dev/null +++ b/docs/car-porting/reverse-engineering.md @@ -0,0 +1,85 @@ +# Stimulus-Response Tests + +These are example test drives that can help identify the CAN bus messaging necessary for ADAS control. Each scripted +test should be done in a separate route (ignition cycle). These tests are a guide, not necessarily exhaustive. + +While testing, constant power to the comma device is highly recommended, using [comma power](https://comma.ai/shop/comma-power) if +necessary to make sure all test activity is fully captured and for ease of uploading. If constant power isn't +available, keep the ignition on for at least one minute after your test to make sure power loss doesn't result +in loss of the last minute of testing data. + +## Stationary ignition-only tests, part 1 + +1. Ignition on, but don't start engine, remain in Park +2. Open and close each door in a defined order: driver, passenger, rear left, rear right +3. Re-enter the vehicle, close the driver's door, and fasten the driver's seatbelt +4. Slowly press and release the accelerator pedal 3 times +5. Slowly press and release the brake pedal 3 times +6. Hold the brake and move the gearshift to reverse, then neutral, then drive, then sport/eco/etc if applicable +7. Return to Park, ignition off + +Brake-pressed information may show up in several messages and signals, both as on/off states and as a percentage or +pressure. It may reflect a switch on the driver's brake pedal, or a pressure-threshold state, or signals to turn on +the rear brake lights. Start by identifying all the potential signals, and confirm while driving with ACC later. + +Locate signals for all four door states if possible, but some cars only expose the driver's door state on the ADAS bus. +Driver/passenger door signals may or may not change positions for LHD vs RHD cars. For cars where only the driver's +door signal is available, the same signal may follow the driver. + +## Stationary ignition-only tests, part 2 + +1. Ignition on, but don't start engine, remain in Park +2. Press each ACC button in a defined order: main switch on/off, set, resume, cancel, accel, decel, gap adjust +3. Set the left turn signal for about five seconds +4. Operate the left turn signal one time in its touch-to-pass mode +5. Set the right turn signal for about five seconds +6. Operate the right turn signal one time in its touch-to-pass mode +7. Set the hazard / emergency indicator switch for about five seconds +8. Ignition off + +Your vehicle may have a momentary-press main ACC switch or a physical toggle that remains set. Actual ACC engagement +isn't necessary for purposes of detecting the ACC button presses. + +## Steering angle and steering torque tests + +Power steering should be available. On ICE cars, engine RPM may be present. + +1. Ignition on, start engine if applicable, remain in Park +2. Rotate the steering wheel as follows, with a few seconds pause between each step + * Start as close to exact center as possible + * Turn to 45 degrees right and hold + * Turn to 90 degrees right and hold + * Turn to 180 degrees right and hold + * Turn to full lock right and hold, with firm pressure against lock + * Release the wheel and allow it to bounce back slightly from lock + * Turn to 180 degrees left and hold + * Return to center and release +3. Ignition off + +Performing the full test to the right, followed by an abbreviated test to the left, helps give additional confirmation +of signal scale, and sign/direction for both the steering wheel angle and driver input torque signals. + +## Low speed / parking lot driving tests + +Before this test, drive to a place like an empty parking lot where you are free to drive in a series of curves. + +1. Ignition on, start engine if applicable, prepare to drive +2. Slowly (10-20mph at most) drive a figure-8 if possible, or at least one sharp left and one sharp right. +3. Come to a complete stop +4. When and where safe, drive in reverse for a short distance (10-15 feet) +5. Park the car in a safe place, ignition off + +## High speed / highway driving tests + +Select a place and time where you can safely set cruise control at normal travel speeds with little interference from +traffic ahead, and safely test the response of your factory lane guidance system. + +1. Ignition on, start engine if applicable, prepare to drive +2. When safely able, engage adaptive cruise control below 50 mph +3. When safely able, use the ACC buttons to accelerate to 50mph, then 55mph, then 60mph +4. Disengage adaptive cruise +5. When safely able, allow your factory lane guidance to prevent lane departures, 2-3 times on both the left and right + +The series of setpoints can be adjusted to local traffic regulations, and of course metric units. The specific cruise +setpoints are useful for locating the ACC HUD signals later, and confirming their precise scaling. When the car reaches +and holds the setpoint, that can also provide additional confirmation of wheel speed scaling. From 8fceb9d957e7c16045a0ed655862ec3be485df19 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Tue, 11 Nov 2025 05:58:23 +0800 Subject: [PATCH 327/910] cabana: replace deprecated Qt and OpenSSL functions (#36605) replace deprecated functions --- tools/cabana/SConscript | 1 - tools/cabana/chart/chart.cc | 8 +++++--- tools/cabana/signalview.cc | 12 ++++++------ tools/cabana/utils/api.cc | 37 +++++++++++++++++++++++-------------- tools/cabana/videowidget.cc | 2 +- 5 files changed, 35 insertions(+), 25 deletions(-) diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index e3674452d..d4cfc6728 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -50,7 +50,6 @@ qt_flags = [ "-DQT_MESSAGELOGCONTEXT", ] qt_env['CXXFLAGS'] += qt_flags -qt_env['CXXFLAGS'] += ["-Wno-deprecated-declarations"] qt_env['LIBPATH'] += ['#selfdrive/ui', ] qt_env['LIBS'] = qt_libs diff --git a/tools/cabana/chart/chart.cc b/tools/cabana/chart/chart.cc index 505b2b805..bc2380e55 100644 --- a/tools/cabana/chart/chart.cc +++ b/tools/cabana/chart/chart.cc @@ -324,7 +324,8 @@ void ChartView::updateSeries(const cabana::Signal *sig, const MessageEventsMap * if (!can->liveStreaming()) { s.segment_tree.build(s.vals); } - s.series->replace(QVector::fromStdVector(series_type == SeriesType::StepLine ? s.step_vals : s.vals)); + const auto &points = series_type == SeriesType::StepLine ? s.step_vals : s.vals; + s.series->replace(QVector(points.cbegin(), points.cend())); } } updateAxisY(); @@ -382,7 +383,7 @@ void ChartView::updateAxisY() { QFontMetrics fm(axis_y->labelsFont()); for (int i = 0; i < tick_count; i++) { qreal value = min_y + (i * (max_y - min_y) / (tick_count - 1)); - max_label_width = std::max(max_label_width, fm.width(QString::number(value, 'f', n))); + max_label_width = std::max(max_label_width, fm.horizontalAdvance(QString::number(value, 'f', n))); } int title_spacing = unit.isEmpty() ? 0 : QFontMetrics(axis_y->titleFont()).size(Qt::TextSingleLine, unit).height(); @@ -838,7 +839,8 @@ void ChartView::setSeriesType(SeriesType type) { } for (auto &s : sigs) { s.series = createSeries(series_type, s.sig->color); - s.series->replace(QVector::fromStdVector(series_type == SeriesType::StepLine ? s.step_vals : s.vals)); + const auto &points = series_type == SeriesType::StepLine ? s.step_vals : s.vals; + s.series->replace(QVector(points.cbegin(), points.cend())); } updateSeriesPoints(); updateTitle(); diff --git a/tools/cabana/signalview.cc b/tools/cabana/signalview.cc index 35537d75e..0a0c07a15 100644 --- a/tools/cabana/signalview.cc +++ b/tools/cabana/signalview.cc @@ -265,7 +265,7 @@ QSize SignalItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QMo text += item->sig->type == cabana::Signal::Type::Multiplexor ? QString(" M ") : QString(" m%1 ").arg(item->sig->multiplex_value); spacing += (option.widget->style()->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1) * 2; } - width = std::min(option.widget->size().width() / 3.0, option.fontMetrics.width(text) + spacing); + width = std::min(option.widget->size().width() / 3.0, option.fontMetrics.horizontalAdvance(text) + spacing); } return {width, option.fontMetrics.height() + option.widget->style()->pixelMetric(QStyle::PM_FocusFrameVMargin) * 2}; } @@ -308,7 +308,7 @@ void SignalItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op // multiplexer indicator if (item->sig->type != cabana::Signal::Type::Normal) { QString indicator = item->sig->type == cabana::Signal::Type::Multiplexor ? QString(" M ") : QString(" m%1 ").arg(item->sig->multiplex_value); - QRect indicator_rect{rect.x(), rect.y(), option.fontMetrics.width(indicator), rect.height()}; + QRect indicator_rect{rect.x(), rect.y(), option.fontMetrics.horizontalAdvance(indicator), rect.height()}; painter->setBrush(Qt::gray); painter->setPen(Qt::NoPen); painter->drawRoundedRect(indicator_rect, 3, 3); @@ -342,13 +342,13 @@ void SignalItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op painter->drawText(rect, Qt::AlignLeft | Qt::AlignTop, max); painter->drawText(rect, Qt::AlignLeft | Qt::AlignBottom, min); QFontMetrics fm(minmax_font); - value_adjust = std::max(fm.width(min), fm.width(max)) + 5; + value_adjust = std::max(fm.horizontalAdvance(min), fm.horizontalAdvance(max)) + 5; } else if (!item->sparkline.isEmpty() && item->sig->type == cabana::Signal::Type::Multiplexed) { // display freq of multiplexed signal painter->setFont(label_font); QString freq = QString("%1 hz").arg(item->sparkline.freq(), 0, 'g', 2); painter->drawText(rect.adjusted(5, 0, 0, 0), Qt::AlignLeft | Qt::AlignVCenter, freq); - value_adjust = QFontMetrics(label_font).width(freq) + 10; + value_adjust = QFontMetrics(label_font).horizontalAdvance(freq) + 10; } // signal value painter->setFont(option.font); @@ -622,13 +622,13 @@ void SignalView::updateState(const std::set *msgs) { double value = 0; if (item->sig->getValue(last_msg.dat.data(), last_msg.dat.size(), &value)) { item->sig_val = item->sig->formatValue(value); - max_value_width = std::max(max_value_width, fontMetrics().width(item->sig_val)); + max_value_width = std::max(max_value_width, fontMetrics().horizontalAdvance(item->sig_val)); } } auto [first_visible, last_visible] = visibleSignalRange(); if (first_visible.isValid() && last_visible.isValid()) { - const static int min_max_width = QFontMetrics(delegate->minmax_font).width("-000.00") + 5; + const static int min_max_width = QFontMetrics(delegate->minmax_font).horizontalAdvance("-000.00") + 5; int available_width = value_column_width - delegate->button_size.width(); int value_width = std::min(max_value_width + min_max_width, available_width / 2); QSize size(available_width - value_width, diff --git a/tools/cabana/utils/api.cc b/tools/cabana/utils/api.cc index 2ed461fdf..89379a843 100644 --- a/tools/cabana/utils/api.cc +++ b/tools/cabana/utils/api.cc @@ -1,7 +1,7 @@ #include "tools/cabana/utils/api.h" +#include #include -#include #include #include @@ -39,29 +39,38 @@ std::optional getDongleId() { namespace CommaApi { -RSA *get_rsa_private_key() { - static std::unique_ptr rsa_private(nullptr, RSA_free); - if (!rsa_private) { +EVP_PKEY *get_private_key() { + static std::unique_ptr pkey(nullptr, EVP_PKEY_free); + if (!pkey) { FILE *fp = fopen(Path::rsa_file().c_str(), "rb"); if (!fp) { - qDebug() << "No RSA private key found, please run manager.py or registration.py"; + qDebug() << "No private key found, please run manager.py or registration.py"; return nullptr; } - rsa_private.reset(PEM_read_RSAPrivateKey(fp, NULL, NULL, NULL)); + pkey.reset(PEM_read_PrivateKey(fp, nullptr, nullptr, nullptr)); fclose(fp); } - return rsa_private.get(); + return pkey.get(); } QByteArray rsa_sign(const QByteArray &data) { - RSA *rsa_private = get_rsa_private_key(); - if (!rsa_private) return {}; + EVP_PKEY *pkey = get_private_key(); + if (!pkey) return {}; - QByteArray sig(RSA_size(rsa_private), Qt::Uninitialized); - unsigned int sig_len; - int ret = RSA_sign(NID_sha256, (unsigned char*)data.data(), data.size(), (unsigned char*)sig.data(), &sig_len, rsa_private); - assert(ret == 1); - assert(sig.size() == sig_len); + EVP_MD_CTX *mdctx = EVP_MD_CTX_new(); + if (!mdctx) return {}; + + QByteArray sig(EVP_PKEY_size(pkey), Qt::Uninitialized); + size_t sig_len = sig.size(); + + int ret = EVP_DigestSignInit(mdctx, nullptr, EVP_sha256(), nullptr, pkey); + ret &= EVP_DigestSignUpdate(mdctx, data.data(), data.size()); + ret &= EVP_DigestSignFinal(mdctx, (unsigned char*)sig.data(), &sig_len); + + EVP_MD_CTX_free(mdctx); + + if (ret != 1) return {}; + sig.resize(sig_len); return sig; } diff --git a/tools/cabana/videowidget.cc b/tools/cabana/videowidget.cc index c857c9ffb..55018f28f 100644 --- a/tools/cabana/videowidget.cc +++ b/tools/cabana/videowidget.cc @@ -134,7 +134,7 @@ void VideoWidget::createSpeedDropdown(QToolBar *toolbar) { QFont font = speed_btn->font(); font.setBold(true); speed_btn->setFont(font); - speed_btn->setMinimumWidth(speed_btn->fontMetrics().width("0.05x ") + style()->pixelMetric(QStyle::PM_MenuButtonIndicator)); + speed_btn->setMinimumWidth(speed_btn->fontMetrics().horizontalAdvance("0.05x ") + style()->pixelMetric(QStyle::PM_MenuButtonIndicator)); } QWidget *VideoWidget::createCameraWidget() { From 3099f4f12da7576d8dd4f7689bbaa62a69c5c8e3 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Tue, 11 Nov 2025 10:05:55 +0800 Subject: [PATCH 328/910] ui: cache the version text to avoid redundant Params.get calls every frame (#36601) cache the version text to avoid redundant Params.get calls every frame --- selfdrive/ui/layouts/home.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/layouts/home.py b/selfdrive/ui/layouts/home.py index 34a7558cd..7f477d424 100644 --- a/selfdrive/ui/layouts/home.py +++ b/selfdrive/ui/layouts/home.py @@ -45,6 +45,7 @@ class HomeLayout(Widget): self.update_available = False self.alert_count = 0 + self._version_text = "" self._prev_update_available = False self._prev_alerts_present = False @@ -178,7 +179,7 @@ class HomeLayout(Widget): version_rect = rl.Rectangle(self.header_rect.x + self.header_rect.width - version_text_width, self.header_rect.y, version_text_width, self.header_rect.height) - gui_label(version_rect, self._get_version_text(), 48, rl.WHITE, alignment=rl.GuiTextAlignment.TEXT_ALIGN_RIGHT) + gui_label(version_rect, self._version_text, 48, rl.WHITE, alignment=rl.GuiTextAlignment.TEXT_ALIGN_RIGHT) def _render_home_content(self): self._render_left_column() @@ -209,7 +210,7 @@ class HomeLayout(Widget): self._setup_widget.render(setup_rect) def _refresh(self): - # TODO: implement _update_state with a timer + self._version_text = self._get_version_text() update_available = self.update_alert.refresh() alert_count = self.offroad_alert.refresh() alerts_present = alert_count > 0 From ed42cfe6994ecd6ef197bb8a84e4cfc365f57b2f Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Tue, 11 Nov 2025 10:08:02 +0800 Subject: [PATCH 329/910] ui: refactor GuiApplication.render into smaller helper methods (#36569) refactor render into smaller helper method --- system/ui/lib/application.py | 85 ++++++++++++++++++++---------------- 1 file changed, 47 insertions(+), 38 deletions(-) diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index f9d7b0c9c..3c5d3a693 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -381,28 +381,9 @@ class GuiApplication: rl.clear_background(rl.BLACK) # Handle modal overlay rendering and input processing - if self._modal_overlay.overlay: - if hasattr(self._modal_overlay.overlay, 'render'): - result = self._modal_overlay.overlay.render(rl.Rectangle(0, 0, self.width, self.height)) - elif callable(self._modal_overlay.overlay): - result = self._modal_overlay.overlay() - else: - raise Exception - - # Send show event to Widget - if not self._modal_overlay_shown and hasattr(self._modal_overlay.overlay, 'show_event'): - self._modal_overlay.overlay.show_event() - self._modal_overlay_shown = True - - if result >= 0: - # Clear the overlay and execute the callback - original_modal = self._modal_overlay - self._modal_overlay = ModalOverlay() - if original_modal.callback is not None: - original_modal.callback(result) + if self._handle_modal_overlay(): yield False else: - self._modal_overlay_shown = False yield True if self._render_texture: @@ -417,24 +398,7 @@ class GuiApplication: rl.draw_fps(10, 10) if self._show_touches: - current_time = time.monotonic() - - for mouse_event in self._mouse_events: - if mouse_event.left_pressed: - self._mouse_history.clear() - self._mouse_history.append(MousePosWithTime(mouse_event.pos.x * self._scale, mouse_event.pos.y * self._scale, current_time)) - - # Remove old touch points that exceed the timeout - while self._mouse_history and (current_time - self._mouse_history[0].t) > TOUCH_HISTORY_TIMEOUT: - self._mouse_history.popleft() - - if self._mouse_history: - mouse_pos = self._mouse_history[-1] - rl.draw_circle(int(mouse_pos.x), int(mouse_pos.y), 15, rl.RED) - for idx, mouse_pos in enumerate(self._mouse_history): - perc = idx / len(self._mouse_history) - color = rl.Color(min(int(255 * (1.5 - perc)), 255), int(min(255 * (perc + 0.5), 255)), 50, 255) - rl.draw_circle(int(mouse_pos.x), int(mouse_pos.y), 5, color) + self._draw_touch_points() rl.end_drawing() self._monitor_fps() @@ -456,6 +420,31 @@ class GuiApplication: def height(self): return self._height + def _handle_modal_overlay(self) -> bool: + if self._modal_overlay.overlay: + if hasattr(self._modal_overlay.overlay, 'render'): + result = self._modal_overlay.overlay.render(rl.Rectangle(0, 0, self.width, self.height)) + elif callable(self._modal_overlay.overlay): + result = self._modal_overlay.overlay() + else: + raise Exception + + # Send show event to Widget + if not self._modal_overlay_shown and hasattr(self._modal_overlay.overlay, 'show_event'): + self._modal_overlay.overlay.show_event() + self._modal_overlay_shown = True + + if result >= 0: + # Clear the overlay and execute the callback + original_modal = self._modal_overlay + self._modal_overlay = ModalOverlay() + if original_modal.callback is not None: + original_modal.callback(result) + return True + else: + self._modal_overlay_shown = False + return False + def _load_fonts(self): for font_weight_file in FontWeight: with as_file(FONT_DIR) as fspath: @@ -539,6 +528,26 @@ class GuiApplication: cloudlog.error(f"FPS dropped critically below {fps}. Shutting down UI.") os._exit(1) + def _draw_touch_points(self): + current_time = time.monotonic() + + for mouse_event in self._mouse_events: + if mouse_event.left_pressed: + self._mouse_history.clear() + self._mouse_history.append(MousePosWithTime(mouse_event.pos.x * self._scale, mouse_event.pos.y * self._scale, current_time)) + + # Remove old touch points that exceed the timeout + while self._mouse_history and (current_time - self._mouse_history[0].t) > TOUCH_HISTORY_TIMEOUT: + self._mouse_history.popleft() + + if self._mouse_history: + mouse_pos = self._mouse_history[-1] + rl.draw_circle(int(mouse_pos.x), int(mouse_pos.y), 15, rl.RED) + for idx, mouse_pos in enumerate(self._mouse_history): + perc = idx / len(self._mouse_history) + color = rl.Color(min(int(255 * (1.5 - perc)), 255), int(min(255 * (perc + 0.5), 255)), 50, 255) + rl.draw_circle(int(mouse_pos.x), int(mouse_pos.y), 5, color) + def _output_render_profile(self): import io import pstats From 85404c184b410a5cd8af73d89ba964e7c81addab Mon Sep 17 00:00:00 2001 From: Trey Moen <50057480+greatgitsby@users.noreply.github.com> Date: Mon, 10 Nov 2025 16:08:55 -1000 Subject: [PATCH 330/910] fix: badges (#36566) * re-add * need to validate * ok looks good * oops * lint --- .github/workflows/badges.yaml | 2 +- selfdrive/ui/translations/create_badges.py | 108 +++++++++++++++++++++ selfdrive/ui/update_translations.py | 1 + 3 files changed, 110 insertions(+), 1 deletion(-) create mode 100755 selfdrive/ui/translations/create_badges.py diff --git a/.github/workflows/badges.yaml b/.github/workflows/badges.yaml index 63ee736dc..cd30e4f37 100644 --- a/.github/workflows/badges.yaml +++ b/.github/workflows/badges.yaml @@ -23,7 +23,7 @@ jobs: - uses: ./.github/workflows/setup-with-retry - name: Push badges run: | - ${{ env.RUN }} "scons -j$(nproc) && python3 selfdrive/ui/translations/create_badges.py" + ${{ env.RUN }} "python3 selfdrive/ui/translations/create_badges.py" rm .gitattributes diff --git a/selfdrive/ui/translations/create_badges.py b/selfdrive/ui/translations/create_badges.py new file mode 100755 index 000000000..2948c4857 --- /dev/null +++ b/selfdrive/ui/translations/create_badges.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python3 +import json +import os +import requests +import xml.etree.ElementTree as ET + +from openpilot.common.basedir import BASEDIR +from openpilot.selfdrive.ui.update_translations import LANGUAGES_FILE, TRANSLATIONS_DIR + +BADGE_HEIGHT = 20 + 8 +SHIELDS_URL = "https://img.shields.io/badge" + +def parse_po_file(file_path): + """ + Parse a .po file and count total and unfinished translations. + Returns: (total_translations, unfinished_translations) + """ + with open(file_path) as f: + content = f.read() + + total_translations = 0 + unfinished_translations = 0 + + # Split into entries (separated by blank lines) + entries = content.split('\n\n') + + for entry in entries: + # Skip header entry (contains Project-Id-Version) + if 'Project-Id-Version' in entry: + continue + + # Check if this entry has a msgid (translation entry) + # After skipping header, any entry with msgid " is a translation + # (both msgid "content" and msgid "" for multiline contain msgid ") + if 'msgid "' not in entry: + continue + + total_translations += 1 + + # Check if msgstr is empty (unfinished translation) + if 'msgstr ""' in entry: + # Check if there are continuation lines with content after msgstr "" + lines = entry.split('\n') + msgstr_idx = None + for i, line in enumerate(lines): + if line.strip().startswith('msgstr ""'): + msgstr_idx = i + break + + if msgstr_idx is not None: + # Check if any continuation lines have content + has_content = False + for line in lines[msgstr_idx + 1:]: + stripped = line.strip() + # Continuation line with content + if stripped.startswith('"') and len(stripped) > 2: + has_content = True + break + # End of entry + if stripped.startswith(('msgid', '#')) or not stripped: + break + + if not has_content: + unfinished_translations += 1 + + return (total_translations, unfinished_translations) + +if __name__ == "__main__": + with open(LANGUAGES_FILE) as f: + translation_files = json.load(f) + + badge_svg = [] + max_badge_width = 0 # keep track of max width to set parent element + for idx, (name, file) in enumerate(translation_files.items()): + po_file_path = os.path.join(str(TRANSLATIONS_DIR), f"app_{file}.po") + + total_translations, unfinished_translations = parse_po_file(po_file_path) + + percent_finished = int(100 - (unfinished_translations / total_translations * 100.)) if total_translations > 0 else 0 + color = f"rgb{(94, 188, 0) if percent_finished == 100 else (248, 255, 50) if percent_finished > 90 else (204, 55, 27)}" + + # Download badge + badge_label = f"LANGUAGE {name}" + badge_message = f"{percent_finished}% complete" + if unfinished_translations != 0: + badge_message += f" ({unfinished_translations} unfinished)" + + r = requests.get(f"{SHIELDS_URL}/{badge_label}-{badge_message}-{color}", timeout=10) + assert r.status_code == 200, "Error downloading badge" + content_svg = r.content.decode("utf-8") + + xml = ET.fromstring(content_svg) + assert "width" in xml.attrib + max_badge_width = max(max_badge_width, int(xml.attrib["width"])) + + # Make tag ids in each badge unique to combine them into one svg + for tag in ("r", "s"): + content_svg = content_svg.replace(f'id="{tag}"', f'id="{tag}{idx}"') + content_svg = content_svg.replace(f'"url(#{tag})"', f'"url(#{tag}{idx})"') + + badge_svg.extend([f'', content_svg, ""]) + + badge_svg.insert(0, '') + badge_svg.append("") + + with open(os.path.join(BASEDIR, "translation_badge.svg"), "w") as badge_f: + badge_f.write("\n".join(badge_svg)) diff --git a/selfdrive/ui/update_translations.py b/selfdrive/ui/update_translations.py index b692552fb..bded80b2e 100755 --- a/selfdrive/ui/update_translations.py +++ b/selfdrive/ui/update_translations.py @@ -4,6 +4,7 @@ import os from openpilot.common.basedir import BASEDIR from openpilot.system.ui.lib.multilang import SYSTEM_UI_DIR, UI_DIR, TRANSLATIONS_DIR, multilang +LANGUAGES_FILE = os.path.join(str(TRANSLATIONS_DIR), "languages.json") POT_FILE = os.path.join(str(TRANSLATIONS_DIR), "app.pot") From 124eb427581c2be9b271e0561b1601d1eebcff9c Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Tue, 11 Nov 2025 10:10:50 +0800 Subject: [PATCH 331/910] ui: fix CameraView crash caused by stale frame (#36563) fix CameraView crash from stale frame --- selfdrive/ui/onroad/cameraview.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/selfdrive/ui/onroad/cameraview.py b/selfdrive/ui/onroad/cameraview.py index 98dd00eba..87db7cc63 100644 --- a/selfdrive/ui/onroad/cameraview.py +++ b/selfdrive/ui/onroad/cameraview.py @@ -155,6 +155,8 @@ class CameraView(Widget): if self.shader and self.shader.id: rl.unload_shader(self.shader) + self.frame = None + self.available_streams.clear() self.client = None def __del__(self): @@ -191,6 +193,9 @@ class CameraView(Widget): if buffer: self._texture_needs_update = True self.frame = buffer + elif not self.client.is_connected(): + # ensure we clear the displayed frame when the connection is lost + self.frame = None if not self.frame: self._draw_placeholder(rect) From 9689de426bd0dad14e33620ce27693f4b6834371 Mon Sep 17 00:00:00 2001 From: Trey Moen <50057480+greatgitsby@users.noreply.github.com> Date: Mon, 10 Nov 2025 16:38:49 -1000 Subject: [PATCH 332/910] chore: adb rules (#36544) * chore: adb rules * i think 51 is common, lets use our own --- tools/install_ubuntu_dependencies.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tools/install_ubuntu_dependencies.sh b/tools/install_ubuntu_dependencies.sh index fdd21067e..5c2131d4b 100755 --- a/tools/install_ubuntu_dependencies.sh +++ b/tools/install_ubuntu_dependencies.sh @@ -116,6 +116,11 @@ SUBSYSTEM=="usb", ATTRS{idVendor}=="3801", ATTRS{idProduct}=="ddcc", MODE="0666" SUBSYSTEM=="usb", ATTRS{idVendor}=="3801", ATTRS{idProduct}=="ddee", MODE="0666" SUBSYSTEM=="usb", ATTRS{idVendor}=="bbaa", ATTRS{idProduct}=="ddcc", MODE="0666" SUBSYSTEM=="usb", ATTRS{idVendor}=="bbaa", ATTRS{idProduct}=="ddee", MODE="0666" +EOF + + # Setup adb udev rules + $SUDO tee /etc/udev/rules.d/50-comma-adb.rules > /dev/null < Date: Mon, 10 Nov 2025 19:57:59 -0800 Subject: [PATCH 333/910] enable ADB in release --- selfdrive/ui/layouts/settings/developer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/layouts/settings/developer.py b/selfdrive/ui/layouts/settings/developer.py index cb7b0f9e9..9ea1019f5 100644 --- a/selfdrive/ui/layouts/settings/developer.py +++ b/selfdrive/ui/layouts/settings/developer.py @@ -108,7 +108,7 @@ class DeveloperLayout(Widget): # Hide non-release toggles on release builds # TODO: we can do an onroad cycle, but alpha long toggle requires a deinit function to re-enable radar and not fault - for item in (self._adb_toggle, self._joystick_toggle, self._long_maneuver_toggle, self._alpha_long_toggle): + for item in (self._joystick_toggle, self._long_maneuver_toggle, self._alpha_long_toggle): item.set_visible(not self._is_release) # CP gating From dad7bb53a2db74c1eeab64c8396c31ec63c62904 Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Mon, 10 Nov 2025 22:24:01 -0800 Subject: [PATCH 334/910] ui: let ui_state set brightness --- system/ui/lib/application.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 3c5d3a693..ddee0f41b 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -202,9 +202,6 @@ class GuiApplication: signal.signal(signal.SIGINT, _close) atexit.register(self.close) - HARDWARE.set_display_power(True) - HARDWARE.set_screen_brightness(65) - self._set_log_callback() rl.set_trace_log_level(rl.TraceLogLevel.LOG_WARNING) From 6a257fe2defaea5f4da94414b8c6d1a220311556 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Wed, 12 Nov 2025 08:10:37 +0800 Subject: [PATCH 335/910] ui: increase profile output from 25 to 100 functions (#36607) increase profile output from 20 to 100 functions --- system/ui/lib/application.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index ddee0f41b..af254f4b5 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -32,6 +32,7 @@ SHOW_TOUCHES = os.getenv("SHOW_TOUCHES") == "1" STRICT_MODE = os.getenv("STRICT_MODE") == "1" SCALE = float(os.getenv("SCALE", "1.0")) PROFILE_RENDER = int(os.getenv("PROFILE_RENDER", "0")) +PROFILE_STATS = int(os.getenv("PROFILE_STATS", "100")) # Number of functions to show in profile output DEFAULT_TEXT_SIZE = 60 DEFAULT_TEXT_COLOR = rl.WHITE @@ -554,7 +555,7 @@ class GuiApplication: avg_frame_time = elapsed_ms / self._frame if self._frame > 0 else 0 stats_stream = io.StringIO() - pstats.Stats(self._render_profiler, stream=stats_stream).sort_stats("cumtime").print_stats(25) + pstats.Stats(self._render_profiler, stream=stats_stream).sort_stats("cumtime").print_stats(PROFILE_STATS) print("\n=== Render loop profile ===") print(stats_stream.getvalue().rstrip()) From 9ee66008dbcbc67e744db99d6c19f38282d885d5 Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Tue, 11 Nov 2025 22:59:54 -0800 Subject: [PATCH 336/910] AGNOS 15 (#36611) * stage * production --- launch_env.sh | 2 +- system/hardware/tici/agnos.json | 44 +++++++-------- system/hardware/tici/all-partitions.json | 68 ++++++++++++------------ 3 files changed, 57 insertions(+), 57 deletions(-) diff --git a/launch_env.sh b/launch_env.sh index 6fc51c5b8..0fba08bb2 100755 --- a/launch_env.sh +++ b/launch_env.sh @@ -16,7 +16,7 @@ export VECLIB_MAXIMUM_THREADS=1 export QCOM_PRIORITY=12 if [ -z "$AGNOS_VERSION" ]; then - export AGNOS_VERSION="14.7" + export AGNOS_VERSION="15" fi export STAGING_ROOT="/data/safe_staging" diff --git a/system/hardware/tici/agnos.json b/system/hardware/tici/agnos.json index 1143f4f08..58c3d2a4e 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-98e0642e2af77596e64d107b2c525a36e54d41d879268fd2fcab9255a2b29723.img.xz", - "hash": "98e0642e2af77596e64d107b2c525a36e54d41d879268fd2fcab9255a2b29723", - "hash_raw": "98e0642e2af77596e64d107b2c525a36e54d41d879268fd2fcab9255a2b29723", + "url": "https://commadist.azureedge.net/agnosupdate/xbl-dd45c0febdf0e022dab82ed0219370a86e8e6c0dfabfe29f3dab7eb1174d6bc6.img.xz", + "hash": "dd45c0febdf0e022dab82ed0219370a86e8e6c0dfabfe29f3dab7eb1174d6bc6", + "hash_raw": "dd45c0febdf0e022dab82ed0219370a86e8e6c0dfabfe29f3dab7eb1174d6bc6", "size": 3282256, "sparse": false, "full_check": true, "has_ab": true, - "ondevice_hash": "907c705f72ebcbd3030e03da9ef4c65a3d599e056a79aa9e7c369fdff8e54dc4" + "ondevice_hash": "d47a08914d2376557b03f1231b7233508222c04b57d781f9daf77c63eab92c2e" }, { "name": "xbl_config", - "url": "https://commadist.azureedge.net/agnosupdate/xbl_config-4d8add65e80b3e5ca49a64fac76025ee3a57a1523abd9caa407aa8c5fb721b0f.img.xz", - "hash": "4d8add65e80b3e5ca49a64fac76025ee3a57a1523abd9caa407aa8c5fb721b0f", - "hash_raw": "4d8add65e80b3e5ca49a64fac76025ee3a57a1523abd9caa407aa8c5fb721b0f", + "url": "https://commadist.azureedge.net/agnosupdate/xbl_config-1074ae051df159ba6dba988d8f6ba2cfc304ed1466cce0db531df6f7b1e44aa9.img.xz", + "hash": "1074ae051df159ba6dba988d8f6ba2cfc304ed1466cce0db531df6f7b1e44aa9", + "hash_raw": "1074ae051df159ba6dba988d8f6ba2cfc304ed1466cce0db531df6f7b1e44aa9", "size": 98124, "sparse": false, "full_check": true, "has_ab": true, - "ondevice_hash": "46c472f52fb97a4836d08d0e790f5c8512651f520ce004bc3bbc6a143fc7a3c2" + "ondevice_hash": "e7d04d9f040c9c040cdf013335d0b6d6e9346311458baeb2461b193e954f5f1c" }, { "name": "abl", @@ -34,25 +34,25 @@ }, { "name": "aop", - "url": "https://commadist.azureedge.net/agnosupdate/aop-d8add1d4c1b6b443debf7bb80040e88a12140d248a328650d65ceaa0df04c1b7.img.xz", - "hash": "d8add1d4c1b6b443debf7bb80040e88a12140d248a328650d65ceaa0df04c1b7", - "hash_raw": "d8add1d4c1b6b443debf7bb80040e88a12140d248a328650d65ceaa0df04c1b7", + "url": "https://commadist.azureedge.net/agnosupdate/aop-4d925c9248672e4a69a236991983375008c44997a854ee7846d1b5fd7c787788.img.xz", + "hash": "4d925c9248672e4a69a236991983375008c44997a854ee7846d1b5fd7c787788", + "hash_raw": "4d925c9248672e4a69a236991983375008c44997a854ee7846d1b5fd7c787788", "size": 184364, "sparse": false, "full_check": true, "has_ab": true, - "ondevice_hash": "e320da0d3f73aa09277a8be740c59f9cc605d2098b46a842c93ea2ac0ac97cb0" + "ondevice_hash": "3aa0a79149ec57f4bc8c38f7bbdf4f6630dd659e49a111ce6258d2d06a07c8e5" }, { "name": "devcfg", - "url": "https://commadist.azureedge.net/agnosupdate/devcfg-7e8a836cf75a9097b1c78960d36f883699fcc3858d8a1d28338f889f6af25cc8.img.xz", - "hash": "7e8a836cf75a9097b1c78960d36f883699fcc3858d8a1d28338f889f6af25cc8", - "hash_raw": "7e8a836cf75a9097b1c78960d36f883699fcc3858d8a1d28338f889f6af25cc8", + "url": "https://commadist.azureedge.net/agnosupdate/devcfg-2f374581243910db92f62bb13bd66ec8e3d56d434997ba007ded06d2d6cc8585.img.xz", + "hash": "2f374581243910db92f62bb13bd66ec8e3d56d434997ba007ded06d2d6cc8585", + "hash_raw": "2f374581243910db92f62bb13bd66ec8e3d56d434997ba007ded06d2d6cc8585", "size": 40336, "sparse": false, "full_check": true, "has_ab": true, - "ondevice_hash": "7e9412d154036216e56c2346d24455dd45f56d6de4c9e8837597f22d59c83d93" + "ondevice_hash": "3d7bb33588491a2a40091a7e1cf6cb65e6dd503f69b640aba484d723f1ad47e8" }, { "name": "boot", @@ -67,17 +67,17 @@ }, { "name": "system", - "url": "https://commadist.azureedge.net/agnosupdate/system-2714d2d2cad6957060c4cfa61fcaa2a3826238278e849e28fa6241c18b18a268.img.xz", - "hash": "fbc85e68a9c94fd520953c6fea64b1ec070ce12c09787de637fc0cc5cbb91846", - "hash_raw": "2714d2d2cad6957060c4cfa61fcaa2a3826238278e849e28fa6241c18b18a268", + "url": "https://commadist.azureedge.net/agnosupdate/system-8757f4a9d2489585249970142578029ab1dfdc5851da75fd703d2376b6f2a26b.img.xz", + "hash": "e9e99988d78c7287f29ad840130f65d5a11fa2301463d5298f1072399406f889", + "hash_raw": "8757f4a9d2489585249970142578029ab1dfdc5851da75fd703d2376b6f2a26b", "size": 4718592000, "sparse": true, "full_check": false, "has_ab": true, - "ondevice_hash": "dfd29ca36003da518c8f0e91476c2210c3e61f2e2d8e96c1abb8f04d0c2c62a3", + "ondevice_hash": "21d3726fcdd39d126c9ecf05ccc43a104c8486b929045a63bf7e3ac8a8bb7a50", "alt": { - "hash": "2714d2d2cad6957060c4cfa61fcaa2a3826238278e849e28fa6241c18b18a268", - "url": "https://commadist.azureedge.net/agnosupdate/system-2714d2d2cad6957060c4cfa61fcaa2a3826238278e849e28fa6241c18b18a268.img", + "hash": "8757f4a9d2489585249970142578029ab1dfdc5851da75fd703d2376b6f2a26b", + "url": "https://commadist.azureedge.net/agnosupdate/system-8757f4a9d2489585249970142578029ab1dfdc5851da75fd703d2376b6f2a26b.img", "size": 4718592000 } } diff --git a/system/hardware/tici/all-partitions.json b/system/hardware/tici/all-partitions.json index 5082647a1..bac2dfc59 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-98e0642e2af77596e64d107b2c525a36e54d41d879268fd2fcab9255a2b29723.img.xz", - "hash": "98e0642e2af77596e64d107b2c525a36e54d41d879268fd2fcab9255a2b29723", - "hash_raw": "98e0642e2af77596e64d107b2c525a36e54d41d879268fd2fcab9255a2b29723", + "url": "https://commadist.azureedge.net/agnosupdate/xbl-dd45c0febdf0e022dab82ed0219370a86e8e6c0dfabfe29f3dab7eb1174d6bc6.img.xz", + "hash": "dd45c0febdf0e022dab82ed0219370a86e8e6c0dfabfe29f3dab7eb1174d6bc6", + "hash_raw": "dd45c0febdf0e022dab82ed0219370a86e8e6c0dfabfe29f3dab7eb1174d6bc6", "size": 3282256, "sparse": false, "full_check": true, "has_ab": true, - "ondevice_hash": "907c705f72ebcbd3030e03da9ef4c65a3d599e056a79aa9e7c369fdff8e54dc4" + "ondevice_hash": "d47a08914d2376557b03f1231b7233508222c04b57d781f9daf77c63eab92c2e" }, { "name": "xbl_config", - "url": "https://commadist.azureedge.net/agnosupdate/xbl_config-4d8add65e80b3e5ca49a64fac76025ee3a57a1523abd9caa407aa8c5fb721b0f.img.xz", - "hash": "4d8add65e80b3e5ca49a64fac76025ee3a57a1523abd9caa407aa8c5fb721b0f", - "hash_raw": "4d8add65e80b3e5ca49a64fac76025ee3a57a1523abd9caa407aa8c5fb721b0f", + "url": "https://commadist.azureedge.net/agnosupdate/xbl_config-1074ae051df159ba6dba988d8f6ba2cfc304ed1466cce0db531df6f7b1e44aa9.img.xz", + "hash": "1074ae051df159ba6dba988d8f6ba2cfc304ed1466cce0db531df6f7b1e44aa9", + "hash_raw": "1074ae051df159ba6dba988d8f6ba2cfc304ed1466cce0db531df6f7b1e44aa9", "size": 98124, "sparse": false, "full_check": true, "has_ab": true, - "ondevice_hash": "46c472f52fb97a4836d08d0e790f5c8512651f520ce004bc3bbc6a143fc7a3c2" + "ondevice_hash": "e7d04d9f040c9c040cdf013335d0b6d6e9346311458baeb2461b193e954f5f1c" }, { "name": "abl", @@ -163,14 +163,14 @@ }, { "name": "aop", - "url": "https://commadist.azureedge.net/agnosupdate/aop-d8add1d4c1b6b443debf7bb80040e88a12140d248a328650d65ceaa0df04c1b7.img.xz", - "hash": "d8add1d4c1b6b443debf7bb80040e88a12140d248a328650d65ceaa0df04c1b7", - "hash_raw": "d8add1d4c1b6b443debf7bb80040e88a12140d248a328650d65ceaa0df04c1b7", + "url": "https://commadist.azureedge.net/agnosupdate/aop-4d925c9248672e4a69a236991983375008c44997a854ee7846d1b5fd7c787788.img.xz", + "hash": "4d925c9248672e4a69a236991983375008c44997a854ee7846d1b5fd7c787788", + "hash_raw": "4d925c9248672e4a69a236991983375008c44997a854ee7846d1b5fd7c787788", "size": 184364, "sparse": false, "full_check": true, "has_ab": true, - "ondevice_hash": "e320da0d3f73aa09277a8be740c59f9cc605d2098b46a842c93ea2ac0ac97cb0" + "ondevice_hash": "3aa0a79149ec57f4bc8c38f7bbdf4f6630dd659e49a111ce6258d2d06a07c8e5" }, { "name": "bluetooth", @@ -207,14 +207,14 @@ }, { "name": "devcfg", - "url": "https://commadist.azureedge.net/agnosupdate/devcfg-7e8a836cf75a9097b1c78960d36f883699fcc3858d8a1d28338f889f6af25cc8.img.xz", - "hash": "7e8a836cf75a9097b1c78960d36f883699fcc3858d8a1d28338f889f6af25cc8", - "hash_raw": "7e8a836cf75a9097b1c78960d36f883699fcc3858d8a1d28338f889f6af25cc8", + "url": "https://commadist.azureedge.net/agnosupdate/devcfg-2f374581243910db92f62bb13bd66ec8e3d56d434997ba007ded06d2d6cc8585.img.xz", + "hash": "2f374581243910db92f62bb13bd66ec8e3d56d434997ba007ded06d2d6cc8585", + "hash_raw": "2f374581243910db92f62bb13bd66ec8e3d56d434997ba007ded06d2d6cc8585", "size": 40336, "sparse": false, "full_check": true, "has_ab": true, - "ondevice_hash": "7e9412d154036216e56c2346d24455dd45f56d6de4c9e8837597f22d59c83d93" + "ondevice_hash": "3d7bb33588491a2a40091a7e1cf6cb65e6dd503f69b640aba484d723f1ad47e8" }, { "name": "devinfo", @@ -350,51 +350,51 @@ }, { "name": "system", - "url": "https://commadist.azureedge.net/agnosupdate/system-2714d2d2cad6957060c4cfa61fcaa2a3826238278e849e28fa6241c18b18a268.img.xz", - "hash": "fbc85e68a9c94fd520953c6fea64b1ec070ce12c09787de637fc0cc5cbb91846", - "hash_raw": "2714d2d2cad6957060c4cfa61fcaa2a3826238278e849e28fa6241c18b18a268", + "url": "https://commadist.azureedge.net/agnosupdate/system-8757f4a9d2489585249970142578029ab1dfdc5851da75fd703d2376b6f2a26b.img.xz", + "hash": "e9e99988d78c7287f29ad840130f65d5a11fa2301463d5298f1072399406f889", + "hash_raw": "8757f4a9d2489585249970142578029ab1dfdc5851da75fd703d2376b6f2a26b", "size": 4718592000, "sparse": true, "full_check": false, "has_ab": true, - "ondevice_hash": "dfd29ca36003da518c8f0e91476c2210c3e61f2e2d8e96c1abb8f04d0c2c62a3", + "ondevice_hash": "21d3726fcdd39d126c9ecf05ccc43a104c8486b929045a63bf7e3ac8a8bb7a50", "alt": { - "hash": "2714d2d2cad6957060c4cfa61fcaa2a3826238278e849e28fa6241c18b18a268", - "url": "https://commadist.azureedge.net/agnosupdate/system-2714d2d2cad6957060c4cfa61fcaa2a3826238278e849e28fa6241c18b18a268.img", + "hash": "8757f4a9d2489585249970142578029ab1dfdc5851da75fd703d2376b6f2a26b", + "url": "https://commadist.azureedge.net/agnosupdate/system-8757f4a9d2489585249970142578029ab1dfdc5851da75fd703d2376b6f2a26b.img", "size": 4718592000 } }, { "name": "userdata_90", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_90-43fd7a1632a23a878d19f5878eb983e3fb763eb4be1aa0a1c7deb4a06bf732de.img.xz", - "hash": "67ec07f8180f666b9b5de1c8859d0f73240b88bdc97b24c6890f6d07f356c2d6", - "hash_raw": "43fd7a1632a23a878d19f5878eb983e3fb763eb4be1aa0a1c7deb4a06bf732de", + "url": "https://commadist.azureedge.net/agnosupdate/userdata_90-1d461d8be17827735a28c2588bb9fcad27d4b80fba15cd2740f3a04c8f29cc90.img.xz", + "hash": "763c7366049b3c0ad71bd19abbbf5c68d2c43597d4da5dafad890507ff489899", + "hash_raw": "1d461d8be17827735a28c2588bb9fcad27d4b80fba15cd2740f3a04c8f29cc90", "size": 96636764160, "sparse": true, "full_check": true, "has_ab": false, - "ondevice_hash": "aadc8097899fcacb8037395f38bb452fa68f50d87ce31c40671fb8eff8e2eac2" + "ondevice_hash": "90a265b8756b18caf1be4b8dc9b8b3104898170104ed87ec3274f77acc6c28e3" }, { "name": "userdata_89", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_89-42ec6bf06ed94ff99a760d944e82f618583fa6576f80d1ca4c40cfe5949e2775.img.xz", - "hash": "e24e0a66f4de2b3bc23adc7995b78b3d547b881ede9456b2abfdcd77657bd71e", - "hash_raw": "42ec6bf06ed94ff99a760d944e82f618583fa6576f80d1ca4c40cfe5949e2775", + "url": "https://commadist.azureedge.net/agnosupdate/userdata_89-ec37fcfb7d707d26d5fbc64994e20cfdbb73a27eeedfe37778559824a2032a27.img.xz", + "hash": "de475b604b63fbeb1841c6564fb8eb496da46c9a9564ec73e5d7c8045fc88ebc", + "hash_raw": "ec37fcfb7d707d26d5fbc64994e20cfdbb73a27eeedfe37778559824a2032a27", "size": 95563022336, "sparse": true, "full_check": true, "has_ab": false, - "ondevice_hash": "c3644225eb21fba5ff65971954b1d17986ca1b6bb44cae6729dab7d1181904bc" + "ondevice_hash": "03f6cbddc3bfbd2d0cd316d87d488434a03095c12870c8c6fe3bc4a2946ff0ef" }, { "name": "userdata_30", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_30-8cb7da131c91753c7e53206f1ad55f0a1d9857dfb779f87e274992ba40c627eb.img.xz", - "hash": "e6990b2dfbaca7f45ab336079bcfc9d6bf198c5b05aa692277507f50f041cae8", - "hash_raw": "8cb7da131c91753c7e53206f1ad55f0a1d9857dfb779f87e274992ba40c627eb", + "url": "https://commadist.azureedge.net/agnosupdate/userdata_30-3501f34c28f0e5ffe224f192b4a3a35a00a039980ca29a5c35d31449f3e918d6.img.xz", + "hash": "5bda2cb099b14f4944b476995d84dcb943af1858a57fdd62d5920b6e7b74fb80", + "hash_raw": "3501f34c28f0e5ffe224f192b4a3a35a00a039980ca29a5c35d31449f3e918d6", "size": 32212254720, "sparse": true, "full_check": true, "has_ab": false, - "ondevice_hash": "3301335a96fcce7adbc56e85ef0f8b526480604444f2d33106cdc7c0ffe8a055" + "ondevice_hash": "d1da6f8d928093dec15590b5c1740c0062031d0068a11962bdb28dca2104d8c6" } ] \ No newline at end of file From 3d08a5048b39092ac8e25fe569d37cc04473f344 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Thu, 13 Nov 2025 06:22:14 +0800 Subject: [PATCH 337/910] replay: Only send bookmarkButton message when --all flag is set (#36612) Only send BookmarkButton message when --all flag is set --- tools/replay/main.cc | 2 +- tools/replay/replay.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/replay/main.cc b/tools/replay/main.cc index f95098507..460901219 100644 --- a/tools/replay/main.cc +++ b/tools/replay/main.cc @@ -30,7 +30,7 @@ Options: --qcam Load qcamera --no-hw-decoder Disable HW video decoding --no-vipc Do not output video - --all Output all messages including uiDebug, userBookmark + --all Output all messages including bookmarkButton, uiDebug, userBookmark -h, --help Show this help message )"; diff --git a/tools/replay/replay.cc b/tools/replay/replay.cc index 7ecad8287..c9ab7e7e2 100644 --- a/tools/replay/replay.cc +++ b/tools/replay/replay.cc @@ -20,7 +20,7 @@ Replay::Replay(const std::string &route, std::vector allow, std::ve std::signal(SIGUSR1, interrupt_sleep_handler); if (!(flags_ & REPLAY_FLAG_ALL_SERVICES)) { - block.insert(block.end(), {"uiDebug", "userBookmark"}); + block.insert(block.end(), {"bookmarkButton", "uiDebug", "userBookmark"}); } setupServices(allow, block); setupSegmentManager(!allow.empty() || !block.empty()); From f93b3f51c9c9d37c7d50b656ed4651028d663f12 Mon Sep 17 00:00:00 2001 From: Trey Moen <50057480+greatgitsby@users.noreply.github.com> Date: Wed, 12 Nov 2025 18:04:20 -1000 Subject: [PATCH 338/910] fix: install missing x deps for building raylib from src (#36614) * fix: install missing x deps for building raylib from src * move here * cleaner --- third_party/raylib/build.sh | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/third_party/raylib/build.sh b/third_party/raylib/build.sh index ab6bbb977..368e27b44 100755 --- a/third_party/raylib/build.sh +++ b/third_party/raylib/build.sh @@ -1,6 +1,17 @@ #!/usr/bin/env bash set -e +SUDO="" + +# Use sudo if not root +if [[ ! $(id -u) -eq 0 ]]; then + if [[ -z $(which sudo) ]]; then + echo "Please install sudo or run as root" + exit 1 + fi + SUDO="sudo" +fi + DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" cd $DIR @@ -10,6 +21,13 @@ ARCHNAME=$(uname -m) if [ -f /TICI ]; then ARCHNAME="larch64" RAYLIB_PLATFORM="PLATFORM_COMMA" +elif [[ "$OSTYPE" == "linux"* ]]; then + # required dependencies on Linux PC + $SUDO apt install \ + libxcursor-dev \ + libxi-dev \ + libxinerama-dev \ + libxrandr-dev fi if [[ "$OSTYPE" == "darwin"* ]]; then From d72a01d7390efae4dc4b4dea30ccc921766a0b68 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 13 Nov 2025 14:58:16 -0800 Subject: [PATCH 339/910] raylib: fix texture wrapping filtering artifacts (#36618) fix wrapping artifacts --- system/ui/lib/application.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index af254f4b5..352a8cfa9 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -312,6 +312,8 @@ class GuiApplication: texture = rl.load_texture_from_image(image) # Set texture filtering to smooth the result rl.set_texture_filter(texture, rl.TextureFilter.TEXTURE_FILTER_BILINEAR) + # prevent artifacts from wrapping coordinates + rl.set_texture_wrap(texture, rl.TextureWrap.TEXTURE_WRAP_CLAMP) rl.unload_image(image) return texture From fc253fe1eec32f161bee57bd4a849fa3f5c602e2 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 13 Nov 2025 14:59:34 -0800 Subject: [PATCH 340/910] Don't resize images that are the same size --- system/ui/lib/application.py | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 352a8cfa9..e9f5484a1 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -287,22 +287,25 @@ class GuiApplication: rl.image_alpha_premultiply(image) if width is not None and height is not None: + same_dimensions = image.width == width and image.height == height + # Resize with aspect ratio preservation if requested - if keep_aspect_ratio: - orig_width = image.width - orig_height = image.height + if not same_dimensions: + if keep_aspect_ratio: + orig_width = image.width + orig_height = image.height - scale_width = width / orig_width - scale_height = height / orig_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) + # 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) + rl.image_resize(image, new_width, new_height) + else: + rl.image_resize(image, width, height) else: assert keep_aspect_ratio, "Cannot resize without specifying width and height" return image From 9c19ec84096dba2ef8b12f52e6fb3d0b7a44ccd3 Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Thu, 13 Nov 2025 15:09:33 -0800 Subject: [PATCH 341/910] bump raylib --- third_party/raylib/build.sh | 2 +- third_party/raylib/larch64/libraylib.a | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/third_party/raylib/build.sh b/third_party/raylib/build.sh index 368e27b44..7f2ce5951 100755 --- a/third_party/raylib/build.sh +++ b/third_party/raylib/build.sh @@ -48,7 +48,7 @@ fi cd raylib_repo -COMMIT=${1:-97dc6a9f1da2b5bbca6fee86b28ac79f7b28b573} +COMMIT=${1:-3425bd9d1fb292ede4d80f97a1f4f258f614cffc} git fetch origin $COMMIT git reset --hard $COMMIT git clean -xdff . diff --git a/third_party/raylib/larch64/libraylib.a b/third_party/raylib/larch64/libraylib.a index a0f7734d2..4e810c8b7 100644 --- a/third_party/raylib/larch64/libraylib.a +++ b/third_party/raylib/larch64/libraylib.a @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f97b6b4ca09ccf8a3e2cca1c8dadffadf12ffad6e25a4c91898e5bb34c8c243f -size 2001812 +oid sha256:91e9a07513e84f7b553da01b34b24e12fe7130131ef73ebdb3dac3b838db815b +size 2001860 From a1795f80ddbcbc287587a51cae19068d32a670d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Thu, 13 Nov 2025 17:08:14 -0800 Subject: [PATCH 342/910] Latest tinygrad (#36615) * Latest tinygrad * jit batch size * bump again * limit upcast * latest tgf * upstream tg --- .gitmodules | 2 +- selfdrive/modeld/SConscript | 6 +++--- tinygrad_repo | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.gitmodules b/.gitmodules index 54c739398..ad6530de9 100644 --- a/.gitmodules +++ b/.gitmodules @@ -15,4 +15,4 @@ url = ../../commaai/teleoprtc [submodule "tinygrad"] path = tinygrad_repo - url = https://github.com/commaai/tinygrad.git + url = https://github.com/tinygrad/tinygrad.git diff --git a/selfdrive/modeld/SConscript b/selfdrive/modeld/SConscript index f20855c2c..ae549f3a7 100644 --- a/selfdrive/modeld/SConscript +++ b/selfdrive/modeld/SConscript @@ -50,9 +50,9 @@ def tg_compile(flags, model_name): # Compile small models for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']: flags = { - 'larch64': 'DEV=QCOM', - 'Darwin': f'DEV=CPU HOME={os.path.expanduser("~")} IMAGE=0', # tinygrad calls brew which needs a $HOME in the env - }.get(arch, 'DEV=CPU CPU_LLVM=1 IMAGE=0') + 'larch64': 'DEV=QCOM FLOAT16=1 NOLOCALS=1 IMAGE=2 JIT_BATCH_SIZE=0', + 'Darwin': f'DEV=CPU HOME={os.path.expanduser("~")}', # tinygrad calls brew which needs a $HOME in the env + }.get(arch, 'DEV=CPU CPU_LLVM=1') tg_compile(flags, model_name) # Compile BIG model if USB GPU is available diff --git a/tinygrad_repo b/tinygrad_repo index 7296c74cb..547304c47 160000 --- a/tinygrad_repo +++ b/tinygrad_repo @@ -1 +1 @@ -Subproject commit 7296c74cbd2666da7dce95d7ca6dab5340653a5c +Subproject commit 547304c471b26ada0b34f400ccba67f3e1eb5965 From b778da1d7c4ebcfaec7d57787efec064f53a211d Mon Sep 17 00:00:00 2001 From: ZwX1616 Date: Fri, 14 Nov 2025 14:29:04 -0800 Subject: [PATCH 343/910] dmonitoringmodeld: clean up data structures (#36624) * update onnx * get meta * start * cast * deprecate notready * more * line too long * 2 --- cereal/log.capnp | 3 +- selfdrive/modeld/SConscript | 2 +- selfdrive/modeld/dmonitoringmodeld.py | 92 +++++++------------ .../modeld/models/dmonitoring_model.onnx | 4 +- selfdrive/monitoring/helpers.py | 41 ++++----- selfdrive/monitoring/test_monitoring.py | 2 +- selfdrive/test/process_replay/model_replay.py | 2 +- 7 files changed, 59 insertions(+), 87 deletions(-) diff --git a/cereal/log.capnp b/cereal/log.capnp index 981cfd468..86774b8d4 100644 --- a/cereal/log.capnp +++ b/cereal/log.capnp @@ -2166,7 +2166,8 @@ struct DriverStateV2 { leftBlinkProb @7 :Float32; rightBlinkProb @8 :Float32; sunglassesProb @9 :Float32; - notReadyProb @12 :List(Float32); + phoneProb @13 :Float32; + notReadyProbDEPRECATED @12 :List(Float32); occludedProbDEPRECATED @10 :Float32; readyProbDEPRECATED @11 :List(Float32); } diff --git a/selfdrive/modeld/SConscript b/selfdrive/modeld/SConscript index ae549f3a7..8b33a457f 100644 --- a/selfdrive/modeld/SConscript +++ b/selfdrive/modeld/SConscript @@ -32,7 +32,7 @@ lenvCython.Program('models/commonmodel_pyx.so', 'models/commonmodel_pyx.pyx', LI tinygrad_files = ["#"+x for x in glob.glob(env.Dir("#tinygrad_repo").relpath + "/**", recursive=True, root_dir=env.Dir("#").abspath) if 'pycache' not in x] # Get model metadata -for model_name in ['driving_vision', 'driving_policy']: +for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']: fn = File(f"models/{model_name}").abspath script_files = [File(Dir("#selfdrive/modeld").File("get_model_metadata.py").abspath)] cmd = f'python3 {Dir("#selfdrive/modeld").abspath}/get_model_metadata.py {fn}.onnx' diff --git a/selfdrive/modeld/dmonitoringmodeld.py b/selfdrive/modeld/dmonitoringmodeld.py index 2851a3e7d..dc2de6f99 100755 --- a/selfdrive/modeld/dmonitoringmodeld.py +++ b/selfdrive/modeld/dmonitoringmodeld.py @@ -7,7 +7,6 @@ from tinygrad.dtype import dtypes import math import time import pickle -import ctypes import numpy as np from pathlib import Path @@ -16,47 +15,16 @@ from cereal.messaging import PubMaster, SubMaster from msgq.visionipc import VisionIpcClient, VisionStreamType, VisionBuf from openpilot.common.swaglog import cloudlog from openpilot.common.realtime import config_realtime_process -from openpilot.common.transformations.model import dmonitoringmodel_intrinsics, DM_INPUT_SIZE +from openpilot.common.transformations.model import dmonitoringmodel_intrinsics from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye from openpilot.selfdrive.modeld.models.commonmodel_pyx import CLContext, MonitoringModelFrame from openpilot.selfdrive.modeld.parse_model_outputs import sigmoid from openpilot.selfdrive.modeld.runners.tinygrad_helpers import qcom_tensor_from_opencl_address -MODEL_WIDTH, MODEL_HEIGHT = DM_INPUT_SIZE -CALIB_LEN = 3 -FEATURE_LEN = 512 -OUTPUT_SIZE = 83 + FEATURE_LEN - PROCESS_NAME = "selfdrive.modeld.dmonitoringmodeld" SEND_RAW_PRED = os.getenv('SEND_RAW_PRED') MODEL_PKL_PATH = Path(__file__).parent / 'models/dmonitoring_model_tinygrad.pkl' - -# TODO: slice from meta -class DriverStateResult(ctypes.Structure): - _fields_ = [ - ("face_orientation", ctypes.c_float*3), - ("face_position", ctypes.c_float*3), - ("face_orientation_std", ctypes.c_float*3), - ("face_position_std", ctypes.c_float*3), - ("face_prob", ctypes.c_float), - ("_unused_a", ctypes.c_float*8), - ("left_eye_prob", ctypes.c_float), - ("_unused_b", ctypes.c_float*8), - ("right_eye_prob", ctypes.c_float), - ("left_blink_prob", ctypes.c_float), - ("right_blink_prob", ctypes.c_float), - ("sunglasses_prob", ctypes.c_float), - ("_unused_c", ctypes.c_float), - ("_unused_d", ctypes.c_float*4), - ("not_ready_prob", ctypes.c_float*2)] - - -class DMonitoringModelResult(ctypes.Structure): - _fields_ = [ - ("driver_state_lhd", DriverStateResult), - ("driver_state_rhd", DriverStateResult), - ("wheel_on_right_prob", ctypes.c_float), - ("features", ctypes.c_float*FEATURE_LEN)] +METADATA_PATH = Path(__file__).parent / 'models/dmonitoring_model_metadata.pkl' class ModelState: @@ -64,11 +32,14 @@ class ModelState: output: np.ndarray def __init__(self, cl_ctx): - assert ctypes.sizeof(DMonitoringModelResult) == OUTPUT_SIZE * ctypes.sizeof(ctypes.c_float) + with open(METADATA_PATH, 'rb') as f: + model_metadata = pickle.load(f) + self.input_shapes = model_metadata['input_shapes'] + self.output_slices = model_metadata['output_slices'] self.frame = MonitoringModelFrame(cl_ctx) self.numpy_inputs = { - 'calib': np.zeros((1, CALIB_LEN), dtype=np.float32), + 'calib': np.zeros(self.input_shapes['calib'], dtype=np.float32), } self.tensor_inputs = {k: Tensor(v, device='NPY').realize() for k,v in self.numpy_inputs.items()} @@ -84,9 +55,9 @@ class ModelState: if TICI: # The imgs tensors are backed by opencl memory, only need init once if 'input_img' not in self.tensor_inputs: - self.tensor_inputs['input_img'] = qcom_tensor_from_opencl_address(input_img_cl.mem_address, (1, MODEL_WIDTH*MODEL_HEIGHT), dtype=dtypes.uint8) + self.tensor_inputs['input_img'] = qcom_tensor_from_opencl_address(input_img_cl.mem_address, self.input_shapes['input_img'], dtype=dtypes.uint8) else: - self.tensor_inputs['input_img'] = Tensor(self.frame.buffer_from_cl(input_img_cl).reshape((1, MODEL_WIDTH*MODEL_HEIGHT)), dtype=dtypes.uint8).realize() + self.tensor_inputs['input_img'] = Tensor(self.frame.buffer_from_cl(input_img_cl).reshape(self.input_shapes['input_img']), dtype=dtypes.uint8).realize() output = self.model_run(**self.tensor_inputs).contiguous().realize().uop.base.buffer.numpy() @@ -95,31 +66,31 @@ class ModelState: return output, t2 - t1 -def fill_driver_state(msg, ds_result: DriverStateResult): - msg.faceOrientation = list(ds_result.face_orientation) - msg.faceOrientationStd = [math.exp(x) for x in ds_result.face_orientation_std] - msg.facePosition = list(ds_result.face_position[:2]) - msg.facePositionStd = [math.exp(x) for x in ds_result.face_position_std[:2]] - msg.faceProb = float(sigmoid(ds_result.face_prob)) - msg.leftEyeProb = float(sigmoid(ds_result.left_eye_prob)) - msg.rightEyeProb = float(sigmoid(ds_result.right_eye_prob)) - msg.leftBlinkProb = float(sigmoid(ds_result.left_blink_prob)) - msg.rightBlinkProb = float(sigmoid(ds_result.right_blink_prob)) - msg.sunglassesProb = float(sigmoid(ds_result.sunglasses_prob)) - msg.notReadyProb = [float(sigmoid(x)) for x in ds_result.not_ready_prob] +def fill_driver_state(msg, model_output, output_slices, ds_suffix): + face_descs = model_output[output_slices[f'face_descs_{ds_suffix}']] + face_descs_std = face_descs[-6:] + msg.faceOrientation = [float(x) for x in face_descs[:3]] + msg.faceOrientationStd = [math.exp(x) for x in face_descs_std[:3]] + msg.facePosition = [float(x) for x in face_descs[3:5]] + msg.facePositionStd = [math.exp(x) for x in face_descs_std[3:5]] + msg.faceProb = float(sigmoid(model_output[output_slices[f'face_prob_{ds_suffix}']][0])) + msg.leftEyeProb = float(sigmoid(model_output[output_slices[f'left_eye_prob_{ds_suffix}']][0])) + msg.rightEyeProb = float(sigmoid(model_output[output_slices[f'right_eye_prob_{ds_suffix}']][0])) + msg.leftBlinkProb = float(sigmoid(model_output[output_slices[f'left_blink_prob_{ds_suffix}']][0])) + msg.rightBlinkProb = float(sigmoid(model_output[output_slices[f'right_blink_prob_{ds_suffix}']][0])) + msg.sunglassesProb = float(sigmoid(model_output[output_slices[f'sunglasses_prob_{ds_suffix}']][0])) + msg.phoneProb = float(sigmoid(model_output[output_slices[f'using_phone_prob_{ds_suffix}']][0])) - -def get_driverstate_packet(model_output: np.ndarray, frame_id: int, location_ts: int, execution_time: float, gpu_execution_time: float): - model_result = ctypes.cast(model_output.ctypes.data, ctypes.POINTER(DMonitoringModelResult)).contents +def get_driverstate_packet(model_output: np.ndarray, output_slices: dict[str, slice], frame_id: int, location_ts: int, exec_time: float, gpu_exec_time: float): msg = messaging.new_message('driverStateV2', valid=True) ds = msg.driverStateV2 ds.frameId = frame_id - ds.modelExecutionTime = execution_time - ds.gpuExecutionTime = gpu_execution_time - ds.wheelOnRightProb = float(sigmoid(model_result.wheel_on_right_prob)) + ds.modelExecutionTime = exec_time + ds.gpuExecutionTime = gpu_exec_time + ds.wheelOnRightProb = float(sigmoid(model_output[output_slices['wheel_on_right']][0])) ds.rawPredictions = model_output.tobytes() if SEND_RAW_PRED else b'' - fill_driver_state(ds.leftDriverData, model_result.driver_state_lhd) - fill_driver_state(ds.rightDriverData, model_result.driver_state_rhd) + fill_driver_state(ds.leftDriverData, model_output, output_slices, 'lhd') + fill_driver_state(ds.rightDriverData, model_output, output_slices, 'rhd') return msg @@ -140,7 +111,7 @@ def main(): sm = SubMaster(["liveCalibration"]) pm = PubMaster(["driverStateV2"]) - calib = np.zeros(CALIB_LEN, dtype=np.float32) + calib = np.zeros(model.numpy_inputs['calib'].size, dtype=np.float32) model_transform = None while True: @@ -160,7 +131,8 @@ def main(): model_output, gpu_execution_time = model.run(buf, calib, model_transform) t2 = time.perf_counter() - pm.send("driverStateV2", get_driverstate_packet(model_output, vipc_client.frame_id, vipc_client.timestamp_sof, t2 - t1, gpu_execution_time)) + msg = get_driverstate_packet(model_output, model.output_slices, vipc_client.frame_id, vipc_client.timestamp_sof, t2 - t1, gpu_execution_time) + pm.send("driverStateV2", msg) if __name__ == "__main__": diff --git a/selfdrive/modeld/models/dmonitoring_model.onnx b/selfdrive/modeld/models/dmonitoring_model.onnx index 1b6a8c3e9..9b1c4a183 100644 --- a/selfdrive/modeld/models/dmonitoring_model.onnx +++ b/selfdrive/modeld/models/dmonitoring_model.onnx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3a53626ab84757813fb16a1441704f2ae7192bef88c331bdc2415be6981d204f -size 7191776 +oid sha256:3446bf8b22e50e47669a25bf32460ae8baf8547037f346753e19ecbfcf6d4e59 +size 6954368 diff --git a/selfdrive/monitoring/helpers.py b/selfdrive/monitoring/helpers.py index 02d8ff5c7..f405eba53 100644 --- a/selfdrive/monitoring/helpers.py +++ b/selfdrive/monitoring/helpers.py @@ -37,12 +37,12 @@ class DRIVER_MONITOR_SETTINGS: self._BLINK_THRESHOLD = 0.865 if HARDWARE.get_device_type() == 'mici': - self._EE_THRESH11 = 0.75 + self._PHONE_THRESH = 0.75 else: - self._EE_THRESH11 = 0.4 - self._EE_THRESH12 = 15.0 - self._EE_MAX_OFFSET1 = 0.06 - self._EE_MIN_OFFSET1 = 0.025 + self._PHONE_THRESH = 0.4 + self._PHONE_THRESH2 = 15.0 + self._PHONE_MAX_OFFSET = 0.06 + self._PHONE_MIN_OFFSET = 0.025 self._POSE_PITCH_THRESHOLD = 0.3133 self._POSE_PITCH_THRESHOLD_SLACK = 0.3237 @@ -84,7 +84,7 @@ class DistractedType: NOT_DISTRACTED = 0 DISTRACTED_POSE = 1 << 0 DISTRACTED_BLINK = 1 << 1 - DISTRACTED_E2E = 1 << 2 + DISTRACTED_PHONE = 1 << 2 class DriverPose: def __init__(self, max_trackable): @@ -142,9 +142,9 @@ class DriverMonitoring: self.wheelpos_learner = RunningStatFilter() self.pose = DriverPose(self.settings._POSE_OFFSET_MAX_COUNT) self.blink = DriverBlink() - self.eev1 = 0. - self.ee1_offseter = RunningStatFilter(max_trackable=self.settings._POSE_OFFSET_MAX_COUNT) - self.ee1_calibrated = False + self.phone_prob = 0. + self.phone_offseter = RunningStatFilter(max_trackable=self.settings._POSE_OFFSET_MAX_COUNT) + self.phone_calibrated = False self.always_on = always_on self.distracted_types = [] @@ -242,13 +242,13 @@ class DriverMonitoring: if (self.blink.left + self.blink.right)*0.5 > self.settings._BLINK_THRESHOLD: distracted_types.append(DistractedType.DISTRACTED_BLINK) - if self.ee1_calibrated: - ee1_dist = self.eev1 > max(min(self.ee1_offseter.filtered_stat.M, self.settings._EE_MAX_OFFSET1), self.settings._EE_MIN_OFFSET1) \ - * self.settings._EE_THRESH12 + if self.phone_calibrated: + using_phone = self.phone_prob > max(min(self.phone_offseter.filtered_stat.M, self.settings._PHONE_MAX_OFFSET), self.settings._PHONE_MIN_OFFSET) \ + * self.settings._PHONE_THRESH2 else: - ee1_dist = self.eev1 > self.settings._EE_THRESH11 - if ee1_dist: - distracted_types.append(DistractedType.DISTRACTED_E2E) + using_phone = self.phone_prob > self.settings._PHONE_THRESH + if using_phone: + distracted_types.append(DistractedType.DISTRACTED_PHONE) return distracted_types @@ -267,8 +267,7 @@ class DriverMonitoring: self.wheel_on_right = self.wheel_on_right_last driver_data = driver_state.rightDriverData if self.wheel_on_right else driver_state.leftDriverData if not all(len(x) > 0 for x in (driver_data.faceOrientation, driver_data.facePosition, - driver_data.faceOrientationStd, driver_data.facePositionStd, - driver_data.notReadyProb)): + driver_data.faceOrientationStd, driver_data.facePositionStd)): return self.face_detected = driver_data.faceProb > self.settings._FACE_THRESHOLD @@ -284,10 +283,10 @@ class DriverMonitoring: * (driver_data.sunglassesProb < self.settings._SG_THRESHOLD) self.blink.right = driver_data.rightBlinkProb * (driver_data.rightEyeProb > self.settings._EYE_THRESHOLD) \ * (driver_data.sunglassesProb < self.settings._SG_THRESHOLD) - self.eev1 = driver_data.notReadyProb[0] + self.phone_prob = driver_data.phoneProb self.distracted_types = self._get_distracted_types() - self.driver_distracted = (DistractedType.DISTRACTED_E2E in self.distracted_types or DistractedType.DISTRACTED_POSE in self.distracted_types + self.driver_distracted = (DistractedType.DISTRACTED_PHONE in self.distracted_types or DistractedType.DISTRACTED_POSE in self.distracted_types or DistractedType.DISTRACTED_BLINK in self.distracted_types) \ and driver_data.faceProb > self.settings._FACE_THRESHOLD and self.pose.low_std self.driver_distraction_filter.update(self.driver_distracted) @@ -297,11 +296,11 @@ class DriverMonitoring: if self.face_detected and car_speed > self.settings._POSE_CALIB_MIN_SPEED and self.pose.low_std and (not op_engaged or not self.driver_distracted): self.pose.pitch_offseter.push_and_update(self.pose.pitch) self.pose.yaw_offseter.push_and_update(self.pose.yaw) - self.ee1_offseter.push_and_update(self.eev1) + self.phone_offseter.push_and_update(self.phone_prob) self.pose.calibrated = self.pose.pitch_offseter.filtered_stat.n > self.settings._POSE_OFFSET_MIN_COUNT and \ self.pose.yaw_offseter.filtered_stat.n > self.settings._POSE_OFFSET_MIN_COUNT - self.ee1_calibrated = self.ee1_offseter.filtered_stat.n > self.settings._POSE_OFFSET_MIN_COUNT + self.phone_calibrated = self.phone_offseter.filtered_stat.n > self.settings._POSE_OFFSET_MIN_COUNT if self.face_detected and not self.driver_distracted: if model_std_max > self.settings._DCAM_UNCERTAIN_ALERT_THRESHOLD: diff --git a/selfdrive/monitoring/test_monitoring.py b/selfdrive/monitoring/test_monitoring.py index 1f8babe02..67234550f 100644 --- a/selfdrive/monitoring/test_monitoring.py +++ b/selfdrive/monitoring/test_monitoring.py @@ -25,7 +25,7 @@ def make_msg(face_detected, distracted=False, model_uncertain=False): ds.leftDriverData.faceOrientationStd = [1.*model_uncertain, 1.*model_uncertain, 1.*model_uncertain] ds.leftDriverData.facePositionStd = [1.*model_uncertain, 1.*model_uncertain] # TODO: test both separately when e2e is used - ds.leftDriverData.notReadyProb = [0., 0.] + ds.leftDriverData.phoneProb = 0. return ds diff --git a/selfdrive/test/process_replay/model_replay.py b/selfdrive/test/process_replay/model_replay.py index 59b8cf825..9ba599bac 100755 --- a/selfdrive/test/process_replay/model_replay.py +++ b/selfdrive/test/process_replay/model_replay.py @@ -77,7 +77,7 @@ def generate_report(proposed, master, tmp, commit): (lambda x: get_idx_if_non_empty(x.leftDriverData.faceProb), "leftDriverData.faceProb"), (lambda x: get_idx_if_non_empty(x.leftDriverData.faceOrientation, 0), "leftDriverData.faceOrientation0"), (lambda x: get_idx_if_non_empty(x.leftDriverData.leftBlinkProb), "leftDriverData.leftBlinkProb"), - (lambda x: get_idx_if_non_empty(x.leftDriverData.notReadyProb, 0), "leftDriverData.notReadyProb0"), + (lambda x: get_idx_if_non_empty(x.leftDriverData.phoneProb), "leftDriverData.phoneProb"), (lambda x: get_idx_if_non_empty(x.rightDriverData.faceProb), "rightDriverData.faceProb"), ], "driverStateV2") From 81be78cd4d48c8cd963fea12fe074689bb2185fe Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 14 Nov 2025 15:48:55 -0800 Subject: [PATCH 344/910] too aggressive for now --- .github/workflows/stale.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml index 823df2b58..1ecd114dc 100644 --- a/.github/workflows/stale.yaml +++ b/.github/workflows/stale.yaml @@ -5,8 +5,8 @@ on: workflow_dispatch: env: - DAYS_BEFORE_PR_CLOSE: 2 - DAYS_BEFORE_PR_STALE: 9 + DAYS_BEFORE_PR_CLOSE: 7 + DAYS_BEFORE_PR_STALE: 24 DAYS_BEFORE_PR_STALE_DRAFT: 30 jobs: From d0c6f845da33000c6f684d647e01afb6a34963ec Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 14 Nov 2025 17:33:14 -0800 Subject: [PATCH 345/910] ui: add burn in debug mode (#36625) * ui: add burn in debug mode * scary * lil less * lil cleanup * revert that * cleanup --- system/ui/README.md | 1 + system/ui/lib/application.py | 56 ++++++++++++++++++++++++++++++++- system/ui/lib/shader_polygon.py | 16 ++-------- 3 files changed, 59 insertions(+), 14 deletions(-) diff --git a/system/ui/README.md b/system/ui/README.md index b124ae4d8..3f2562aae 100644 --- a/system/ui/README.md +++ b/system/ui/README.md @@ -6,6 +6,7 @@ Quick start: * set `SHOW_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 +* set `BURN_IN=1` to get a burn-in heatmap version of the UI * 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 e9f5484a1..1d085a5a0 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -6,6 +6,7 @@ import signal import sys import pyray as rl import threading +import platform from contextlib import contextmanager from collections.abc import Callable from collections import deque @@ -34,6 +35,43 @@ SCALE = float(os.getenv("SCALE", "1.0")) PROFILE_RENDER = int(os.getenv("PROFILE_RENDER", "0")) PROFILE_STATS = int(os.getenv("PROFILE_STATS", "100")) # Number of functions to show in profile output +GL_VERSION = """ +#version 300 es +precision highp float; +""" +if platform.system() == "Darwin": + GL_VERSION = """ + #version 330 core + """ + +BURN_IN_MODE = "BURN_IN" in os.environ +BURN_IN_VERTEX_SHADER = GL_VERSION + """ +in vec3 vertexPosition; +in vec2 vertexTexCoord; +uniform mat4 mvp; +out vec2 fragTexCoord; +void main() { + fragTexCoord = vertexTexCoord; + gl_Position = mvp * vec4(vertexPosition, 1.0); +} +""" +BURN_IN_FRAGMENT_SHADER = GL_VERSION + """ +in vec2 fragTexCoord; +uniform sampler2D texture0; +out vec4 fragColor; +void main() { + vec4 sampled = texture(texture0, fragTexCoord); + float intensity = sampled.b; + // Map blue intensity to green -> yellow -> red to highlight burn-in risk. + vec3 start = vec3(0.0, 1.0, 0.0); + vec3 middle = vec3(1.0, 1.0, 0.0); + vec3 end = vec3(1.0, 0.0, 0.0); + vec3 gradient = mix(start, middle, clamp(intensity * 2.0, 0.0, 1.0)); + gradient = mix(gradient, end, clamp((intensity - 0.5) * 2.0, 0.0, 1.0)); + fragColor = vec4(gradient, sampled.a); +} +""" + DEFAULT_TEXT_SIZE = 60 DEFAULT_TEXT_COLOR = rl.WHITE @@ -155,6 +193,7 @@ class GuiApplication: self._scaled_width = int(self._width * self._scale) self._scaled_height = int(self._height * self._scale) self._render_texture: rl.RenderTexture | None = None + self._burn_in_shader: rl.Shader | None = None self._textures: dict[str, rl.Texture] = {} self._target_fps: int = _DEFAULT_FPS self._last_fps_log_time: float = time.monotonic() @@ -212,8 +251,10 @@ class GuiApplication: rl.set_config_flags(flags) rl.init_window(self._scaled_width, self._scaled_height, title) + needs_render_texture = self._scale != 1.0 or BURN_IN_MODE if self._scale != 1.0: rl.set_mouse_scale(1 / self._scale, 1 / self._scale) + if needs_render_texture: 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) @@ -222,6 +263,8 @@ class GuiApplication: self._set_styles() self._load_fonts() self._patch_text_functions() + if BURN_IN_MODE and self._burn_in_shader is None: + self._burn_in_shader = rl.load_shader_from_memory(BURN_IN_VERTEX_SHADER, BURN_IN_FRAGMENT_SHADER) if not PC: self._mouse.start() @@ -337,6 +380,10 @@ class GuiApplication: rl.unload_render_texture(self._render_texture) self._render_texture = None + if self._burn_in_shader: + rl.unload_shader(self._burn_in_shader) + self._burn_in_shader = None + if not PC: self._mouse.stop() @@ -395,7 +442,14 @@ class GuiApplication: 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) + texture = self._render_texture.texture + if texture: + if BURN_IN_MODE and self._burn_in_shader: + rl.begin_shader_mode(self._burn_in_shader) + rl.draw_texture_pro(texture, src_rect, dst_rect, rl.Vector2(0, 0), 0.0, rl.WHITE) + rl.end_shader_mode() + else: + rl.draw_texture_pro(texture, src_rect, dst_rect, rl.Vector2(0, 0), 0.0, rl.WHITE) if self._show_fps: rl.draw_fps(10, 10) diff --git a/system/ui/lib/shader_polygon.py b/system/ui/lib/shader_polygon.py index 28585b08b..3cc480b33 100644 --- a/system/ui/lib/shader_polygon.py +++ b/system/ui/lib/shader_polygon.py @@ -1,9 +1,8 @@ -import platform import pyray as rl import numpy as np from dataclasses import dataclass from typing import Any, Optional, cast -from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.lib.application import gui_app, GL_VERSION MAX_GRADIENT_COLORS = 20 # includes stops as well @@ -29,16 +28,7 @@ class Gradient: self.stops = [i / max(1, color_count - 1) for i in range(color_count)] -VERSION = """ -#version 300 es -precision highp float; -""" -if platform.system() == "Darwin": - VERSION = """ - #version 330 core - """ - -FRAGMENT_SHADER = VERSION + """ +FRAGMENT_SHADER = GL_VERSION + """ in vec2 fragTexCoord; out vec4 finalColor; @@ -83,7 +73,7 @@ void main() { """ # Default vertex shader -VERTEX_SHADER = VERSION + """ +VERTEX_SHADER = GL_VERSION + """ in vec3 vertexPosition; in vec2 vertexTexCoord; out vec2 fragTexCoord; From cb03d08397c98c5f693026350ad094c3eadb1209 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 16 Nov 2025 01:55:19 -0500 Subject: [PATCH 346/910] tools: add retry mechanism for API requests (#1480) --- tools/lib/api.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tools/lib/api.py b/tools/lib/api.py index c6e2d9891..f84fe7586 100644 --- a/tools/lib/api.py +++ b/tools/lib/api.py @@ -1,5 +1,6 @@ import os import requests +from requests.adapters import HTTPAdapter, Retry API_HOST = os.getenv('API_HOST', 'https://api.commadotai.com') # TODO: this should be merged into common.api @@ -11,6 +12,9 @@ class CommaApi: if token: self.session.headers['Authorization'] = 'JWT ' + token + retries = Retry(total=5, backoff_factor=1, status_forcelist=[500, 502, 503, 504]) + self.session.mount('https://', HTTPAdapter(max_retries=retries)) + def request(self, method, endpoint, **kwargs): with self.session.request(method, API_HOST + '/' + endpoint, **kwargs) as resp: resp_json = resp.json() From 16ca9c7413226e6967718d8e842d17d522aca597 Mon Sep 17 00:00:00 2001 From: infiniteCable2 Date: Sun, 16 Nov 2025 13:29:52 +0100 Subject: [PATCH 347/910] implement custom toggles --- selfdrive/ui/layouts/settings/ictoggles.py | 164 +++++++++++++++++++++ selfdrive/ui/layouts/settings/settings.py | 3 +- 2 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 selfdrive/ui/layouts/settings/ictoggles.py diff --git a/selfdrive/ui/layouts/settings/ictoggles.py b/selfdrive/ui/layouts/settings/ictoggles.py new file mode 100644 index 000000000..d50d818c1 --- /dev/null +++ b/selfdrive/ui/layouts/settings/ictoggles.py @@ -0,0 +1,164 @@ +from cereal import log +from openpilot.common.params import Params, UnknownKeyName +from openpilot.system.ui.widgets import Widget +from openpilot.system.ui.widgets.list_view import toggle_item +from openpilot.system.ui.widgets.scroller import Scroller +from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.lib.multilang import tr, tr_noop +from openpilot.selfdrive.ui.ui_state import ui_state + +# Description constants +DESCRIPTIONS = { + "EnableCurvatureController": tr_noop( + "Enables curvature PID post-processing additionally to QFK curvature offset" + ), + "EnableLongComfortMode": tr_noop( + "Enables longitudinal jerk and accel deviation limit control for safe and comfortable driving" + ), + "EnableSpeedLimitControl": tr_noop( + "Enables setting maximum speed by speed limit detection" + ), + "EnableSpeedLimitPredicative": tr_noop( + "Enables setting predicative speed limit" + ), + "EnableSLPredReactToSL": tr_noop( + "Enables reaction to speed limits as predicative speed limit" + ), + "EnableSLPredReactToCurves": tr_noop( + "Enables reaction to curves as predicative speed limit (Max Speed as per lateral ISO limits)" + ), + "BatteryDetails": tr_noop( + "Display battery detail panel" + ), + "ForceRHDForBSM": tr_noop( + "Switch BSM detection side to RHD. Passenger is on the right side." + ), + "EnableSmoothSteer": tr_noop( + "Enables S-curving on lateral control for smoother steering" + ), +} + + +class ICTogglesLayout(Widget): + def __init__(self): + super().__init__() + self._params = Params() + + # param, title, desc, icon, needs_restart + self._toggle_defs = { + "EnableCurvatureController": ( + lambda: tr("VW: Lateral Correction (Recommended)"), + DESCRIPTIONS["EnableCurvatureController"], + "chffr_wheel.png", + False, + ), + "EnableLongComfortMode": ( + lambda: tr("VW: Longitudinal Comfort Mode"), + DESCRIPTIONS["EnableCurvatureController"], + "chffr_wheel.png", + False, + ), + "EnableSpeedLimitControl": ( + lambda: tr("VW: Speed Limit Control"), + DESCRIPTIONS["EnableSpeedLimitControl"], + "speed_limit.png", + False, + ), + "EnableSpeedLimitPredicative": ( + lambda: tr("VW: Predicative Speed Limit (pACC)"), + DESCRIPTIONS["EnableSpeedLimitPredicative"], + "speed_limit.png", + False, + ), + "EnableSLPredReactToSL": ( + lambda: tr("VW: Predicative - Reaction to Speed Limits"), + DESCRIPTIONS["EnableSLPredReactToSL"], + "speed_limit.png", + False, + ), + "EnableSLPredReactToCurves": ( + lambda: tr("VW: Predicative - Reaction to Curves"), + DESCRIPTIONS["EnableSLPredReactToCurves"], + "speed_limit.png", + False, + ), + "BatteryDetails": ( + lambda: tr("VW MEB: Display Battery Details"), + DESCRIPTIONS["BatteryDetails"], + "capslock-fill.png", + False, + ), + "ForceRHDForBSM": ( + lambda: tr("VW: Force RHD for BSM"), + DESCRIPTIONS["ForceRHDForBSM"], + "chffr_wheel.png", + False, + ), + "EnableSmoothSteer": ( + lambda: tr("Steer Smoothing"), + DESCRIPTIONS["EnableSmoothSteer"], + "chffr_wheel.png", + False, + ), + } + + self._toggles = {} + self._locked_toggles = set() + for param, (title, desc, icon, needs_restart) in self._toggle_defs.items(): + toggle = toggle_item( + title, + desc, + self._params.get_bool(param), + callback=lambda state, p=param: self._toggle_callback(state, p), + icon=icon, + ) + + try: + locked = self._params.get_bool(param + "Lock") + except UnknownKeyName: + locked = False + toggle.action_item.set_enabled(not locked) + + # Make description callable for live translation + additional_desc = "" + if needs_restart and not locked: + additional_desc = tr("Changing this setting will restart openpilot if the car is powered on.") + toggle.set_description(lambda og_desc=toggle.description, add_desc=additional_desc: tr(og_desc) + (" " + tr(add_desc) if add_desc else "")) + + # track for engaged state updates + if locked: + self._locked_toggles.add(param) + + self._toggles[param] = toggle + + self._scroller = Scroller(list(self._toggles.values()), line_separator=True, spacing=0) + + ui_state.add_engaged_transition_callback(self._update_toggles) + + def _update_state(self): + return + + def show_event(self): + self._scroller.show_event() + self._update_toggles() + + def _update_toggles(self): + ui_state.update_params() + + # TODO: make a param control list item so we don't need to manage internal state as much here + # refresh toggles from params to mirror external changes + for param in self._toggle_defs: + self._toggles[param].action_item.set_state(self._params.get_bool(param)) + + # these toggles need restart, block while engaged + for toggle_def in self._toggle_defs: + if self._toggle_defs[toggle_def][3] and toggle_def not in self._locked_toggles: + self._toggles[toggle_def].action_item.set_enabled(not ui_state.engaged) + + def _render(self, rect): + self._scroller.render(rect) + + def _toggle_callback(self, state: bool, param: str): + self._params.put_bool(param, state) + if self._toggle_defs[param][3]: + self._params.put_bool("OnroadCycleRequested", True) diff --git a/selfdrive/ui/layouts/settings/settings.py b/selfdrive/ui/layouts/settings/settings.py index 79018f3b4..3e872095f 100644 --- a/selfdrive/ui/layouts/settings/settings.py +++ b/selfdrive/ui/layouts/settings/settings.py @@ -7,6 +7,7 @@ from openpilot.selfdrive.ui.layouts.settings.device import DeviceLayout from openpilot.selfdrive.ui.layouts.settings.firehose import FirehoseLayout from openpilot.selfdrive.ui.layouts.settings.software import SoftwareLayout from openpilot.selfdrive.ui.layouts.settings.toggles import TogglesLayout +from openpilot.selfdrive.ui.layouts.settings.ictoggles import ICTogglesLayout from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos from openpilot.system.ui.lib.multilang import tr, tr_noop from openpilot.system.ui.lib.text_measure import measure_text_cached @@ -59,7 +60,7 @@ class SettingsLayout(Widget): self._panels = { PanelType.DEVICE: PanelInfo(tr_noop("Device"), DeviceLayout()), PanelType.NETWORK: PanelInfo(tr_noop("Network"), NetworkUI(wifi_manager)), - PanelType.ICTOGGLES: PanelInfo(tr_noop("infiniteCableCustom"), TogglesLayout()), + PanelType.ICTOGGLES: PanelInfo(tr_noop("infiniteCableCustom"), ICTogglesLayout()), PanelType.TOGGLES: PanelInfo(tr_noop("Toggles"), TogglesLayout()), PanelType.SOFTWARE: PanelInfo(tr_noop("Software"), SoftwareLayout()), PanelType.FIREHOSE: PanelInfo(tr_noop("Firehose"), FirehoseLayout()), From b1ff19ba10e7fc980777e36a026578eb68caeea6 Mon Sep 17 00:00:00 2001 From: infiniteCable2 Date: Sun, 16 Nov 2025 13:30:20 +0100 Subject: [PATCH 348/910] Update opendbc_repo --- opendbc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc_repo b/opendbc_repo index 83f62bab3..d94108507 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 83f62bab3d3b0bd7dd3f7588afbb92c2eae82103 +Subproject commit d941085073b3678556730685ca00b2195bb4ac3d From 907689b247c6e8f8c18818fd7fa795d2c1118c91 Mon Sep 17 00:00:00 2001 From: infiniteCable2 Date: Sun, 16 Nov 2025 13:55:48 +0100 Subject: [PATCH 349/910] upstream bump --- opendbc_repo | 2 +- panda | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/opendbc_repo b/opendbc_repo index d94108507..01026ab3f 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit d941085073b3678556730685ca00b2195bb4ac3d +Subproject commit 01026ab3fad3deb80838809973ba675688ffbb78 diff --git a/panda b/panda index df8221737..f76da9eee 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit df8221737ac55eb3dc7d31bc7e54647767132bbf +Subproject commit f76da9eeec1df0975769493d2897072b7ae69d99 From f1730b80109f68790b50bde4386fb114536bd029 Mon Sep 17 00:00:00 2001 From: infiniteCable2 Date: Sun, 16 Nov 2025 14:03:01 +0100 Subject: [PATCH 350/910] fix vw meb ignition --- panda | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panda b/panda index f76da9eee..5bd386f4f 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit f76da9eeec1df0975769493d2897072b7ae69d99 +Subproject commit 5bd386f4ff36e778d142e0f3cdf7bbab61efb91a From a61e8735aef1d7f952082cd943086c34fa9d3369 Mon Sep 17 00:00:00 2001 From: infiniteCable2 Date: Sun, 16 Nov 2025 14:20:43 +0100 Subject: [PATCH 351/910] cleanup --- common/params_keys.h | 2 -- common/pid_mu.py | 32 ------------------- .../controls/lib/latcontrol_curvature.py | 28 ---------------- selfdrive/locationd/paramsd.py | 15 +++------ 4 files changed, 4 insertions(+), 73 deletions(-) delete mode 100644 common/pid_mu.py delete mode 100644 selfdrive/controls/lib/latcontrol_curvature.py diff --git a/common/params_keys.h b/common/params_keys.h index af8dec28f..f333697e3 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -131,10 +131,8 @@ inline static std::unordered_map keys = { {"Version", {PERSISTENT, STRING}}, // --- infiniteCable params --- // - {"AngleOffsetDegree", {PERSISTENT, FLOAT, "0.0"}}, {"DisableScreenTimer", {PERSISTENT, BOOL}}, {"DarkMode", {PERSISTENT, BOOL}}, - {"EnableAngleOffset", {PERSISTENT, BOOL}}, {"EnableCurvatureController", {PERSISTENT, BOOL, "1"}}, {"EnableLongComfortMode", {PERSISTENT, BOOL}}, {"EnableScreenEvent", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BOOL}}, diff --git a/common/pid_mu.py b/common/pid_mu.py deleted file mode 100644 index 49dd0a42c..000000000 --- a/common/pid_mu.py +++ /dev/null @@ -1,32 +0,0 @@ -from openpilot.common.pid import PIDController -import numpy as np - -class MultiplicativeUnwindPID(PIDController): - def __init__(self, k_p, k_i, k_f=0., k_d=0., pos_limit=1e308, neg_limit=-1e308, rate=100): - super().__init__(k_p, k_i, k_f=k_f, k_d=k_d, pos_limit=pos_limit, neg_limit=neg_limit, rate=rate) - - self.i_unwind_rate = 0.3 / rate - - def update(self, error, error_rate=0.0, speed=0.0, override=False, feedforward=0., freeze_integrator=False): - self.speed = speed - self.p = float(error) * self.k_p - self.f = feedforward * self.k_f - self.d = error_rate * self.k_d - - if override: - self.i *= (1.0 - self.i_unwind_rate) - if abs(self.i) < 1e-10: - self.i = 0.0 - else: - if not freeze_integrator: - i = self.i + error * self.k_i * self.i_rate - - # Don't allow windup if already clipping - test_control = self.p + i + self.d + self.f - i_upperbound = self.i if test_control > self.pos_limit else self.pos_limit - i_lowerbound = self.i if test_control < self.neg_limit else self.neg_limit - self.i = np.clip(i, i_lowerbound, i_upperbound) - - control = self.p + self.i + self.d + self.f - self.control = np.clip(control, self.neg_limit, self.pos_limit) - return self.control \ No newline at end of file diff --git a/selfdrive/controls/lib/latcontrol_curvature.py b/selfdrive/controls/lib/latcontrol_curvature.py deleted file mode 100644 index 0544085c6..000000000 --- a/selfdrive/controls/lib/latcontrol_curvature.py +++ /dev/null @@ -1,28 +0,0 @@ -import math - -from cereal import log -from openpilot.selfdrive.controls.lib.latcontrol import LatControl - -CURVATURE_SATURATION_THRESHOLD = 5e-4 # rad/m - - -class LatControlCurvature(LatControl): - def __init__(self, CP, CP_SP, CI): - super().__init__(CP, CP_SP, CI) - assert False - - def reset(self): - super().reset() - - def update(self, active, CS, VM, params, steer_limited_by_safety, desired_curvature, calibrated_pose, curvature_limited): - curvature_log = log.ControlsState.LateralCurvatureState.new_message() - actual_curvature = -VM.calc_curvature(math.radians(CS.steeringAngleDeg - params.angleOffsetDeg), CS.vEgo, params.roll) - output_curvature = desired_curvature - - curvature_log.active = active - curvature_log.output = float(output_curvature) - curvature_log.actualCurvature = float(actual_curvature) - curvature_log.desiredCurvature = float(output_curvature) - curvature_log.saturated = bool(self._check_saturation(steer_limited_by_safety, CS, False, curvature_limited)) if active else False - - return 0.0, 0.0, output_curvature, curvature_log diff --git a/selfdrive/locationd/paramsd.py b/selfdrive/locationd/paramsd.py index e6e315fae..531ea6018 100755 --- a/selfdrive/locationd/paramsd.py +++ b/selfdrive/locationd/paramsd.py @@ -19,7 +19,6 @@ ROLL_LOWERED_MAX = np.radians(8) ROLL_STD_MAX = np.radians(1.5) LATERAL_ACC_SENSOR_THRESHOLD = 4.0 OFFSET_MAX = 10.0 -OFFSET_HIGHER_MAX = 18.0 OFFSET_LOWERED_MAX = 8.0 MIN_ACTIVE_SPEED = 1.0 LOW_ACTIVE_SPEED = 10.0 @@ -27,16 +26,12 @@ LOW_ACTIVE_SPEED = 10.0 class VehicleParamsLearner: def __init__(self, CP: car.CarParams, steer_ratio: float, stiffness_factor: float, angle_offset: float, P_initial: np.ndarray | None = None): - params = Params() self.kf = CarKalman(GENERATED_DIR) self.x_initial = CarKalman.initial_x.copy() self.x_initial[States.STEER_RATIO] = steer_ratio self.x_initial[States.STIFFNESS] = stiffness_factor - set_manual_angle_offset = params.get_bool("EnableAngleOffset") - self.allow_higher_angle_offset = set_manual_angle_offset - manual_angle_offset_deg = float(params.get("AngleOffsetDegree") or 0.0) - self.x_initial[States.ANGLE_OFFSET] = np.radians(manual_angle_offset_deg) if set_manual_angle_offset else angle_offset + self.x_initial[States.ANGLE_OFFSET] = angle_offset self.P_initial = P_initial if P_initial is not None else CarKalman.P_initial self.kf.set_globals( @@ -155,9 +150,8 @@ class VehicleParamsLearner: sensors_valid = bool(abs(self.observed_speed * (x[States.YAW_RATE].item() + self.observed_yaw_rate)) < LATERAL_ACC_SENSOR_THRESHOLD) else: sensors_valid = True - avg_angle_offset_max = OFFSET_HIGHER_MAX if self.allow_higher_angle_offset else OFFSET_MAX - self.avg_offset_valid = check_valid_with_hysteresis(self.avg_offset_valid, self.avg_angle_offset, avg_angle_offset_max, OFFSET_LOWERED_MAX) - self.total_offset_valid = check_valid_with_hysteresis(self.total_offset_valid, self.angle_offset, avg_angle_offset_max, OFFSET_LOWERED_MAX) + self.avg_offset_valid = check_valid_with_hysteresis(self.avg_offset_valid, self.avg_angle_offset, OFFSET_MAX, OFFSET_LOWERED_MAX) + self.total_offset_valid = check_valid_with_hysteresis(self.total_offset_valid, self.angle_offset, OFFSET_MAX, OFFSET_LOWERED_MAX) self.roll_valid = check_valid_with_hysteresis(self.roll_valid, self.roll, ROLL_MAX, ROLL_LOWERED_MAX) msg = messaging.new_message('liveParameters') @@ -296,10 +290,9 @@ def main(): msg_dat = msg.to_bytes() if sm.frame % 1200 == 0: # once a minute params.put_nonblocking("LiveParametersV2", msg_dat) - params.put_nonblocking("AngleOffsetDegree", msg.liveParameters.angleOffsetDeg) pm.send('liveParameters', msg_dat) if __name__ == "__main__": - main() + main() \ No newline at end of file From 749c106794be7f0616bd696e5d313b40ff3b310b Mon Sep 17 00:00:00 2001 From: infiniteCable2 Date: Sun, 16 Nov 2025 15:14:39 +0100 Subject: [PATCH 352/910] battery panel --- selfdrive/ui/layouts/settings/settings.py | 2 +- selfdrive/ui/onroad/battery_details.py | 141 ++++++++++++++++++++++ selfdrive/ui/onroad/hud_renderer.py | 5 + 3 files changed, 147 insertions(+), 1 deletion(-) create mode 100644 selfdrive/ui/onroad/battery_details.py diff --git a/selfdrive/ui/layouts/settings/settings.py b/selfdrive/ui/layouts/settings/settings.py index 3e872095f..36183c509 100644 --- a/selfdrive/ui/layouts/settings/settings.py +++ b/selfdrive/ui/layouts/settings/settings.py @@ -60,8 +60,8 @@ class SettingsLayout(Widget): self._panels = { PanelType.DEVICE: PanelInfo(tr_noop("Device"), DeviceLayout()), PanelType.NETWORK: PanelInfo(tr_noop("Network"), NetworkUI(wifi_manager)), - PanelType.ICTOGGLES: PanelInfo(tr_noop("infiniteCableCustom"), ICTogglesLayout()), PanelType.TOGGLES: PanelInfo(tr_noop("Toggles"), TogglesLayout()), + PanelType.ICTOGGLES: PanelInfo(tr_noop("infiniteCable"), ICTogglesLayout()), PanelType.SOFTWARE: PanelInfo(tr_noop("Software"), SoftwareLayout()), PanelType.FIREHOSE: PanelInfo(tr_noop("Firehose"), FirehoseLayout()), PanelType.DEVELOPER: PanelInfo(tr_noop("Developer"), DeveloperLayout()), diff --git a/selfdrive/ui/onroad/battery_details.py b/selfdrive/ui/onroad/battery_details.py new file mode 100644 index 000000000..d72f0acc6 --- /dev/null +++ b/selfdrive/ui/onroad/battery_details.py @@ -0,0 +1,141 @@ +import pyray as rl +from dataclasses import dataclass + +from openpilot.common.params import Params +from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.system.ui.lib.application import gui_app, FontWeight +from openpilot.system.ui.widgets import Widget + + +@dataclass(frozen=True) +class BatteryPanelConfig: + scale_factor: float = 1.1 + panel_width_ratio: float = 0.7 # 70 % der Breite von rechts + panel_margin: int = 30 # Abstand zum Rand + line_height: int = 48 # Basis-Zeilenhöhe + label_width: int = 265 + text_margin: int = 25 # Abstand Label → Wert + +CONFIG = BatteryPanelConfig() + +class BatteryDetails(Widget): + def __init__(self, button_size: int, icon_size: int): + super().__init__() + self._params = Params() + + self._capacity: float = 0.0 + self._charge: float = 0.0 + self._soc: float = 0.0 + self._temperature: float = 0.0 + self._heaterActive: bool = False + self._voltage: float = 0.0 + self._current: float = 0.0 + self._power: float = 0.0 + + self._font: rl.Font = gui_app.font(FontWeight.MEDIUM) + self._panel_bg: rl.Color = rl.Color(0, 0, 0, 128) + self._text_color: rl.Color = rl.Color(255, 255, 255, 255) + + self._display_enabled: bool = False + + def _update_state(self) -> None: + self._display_enabled = self._params.get_bool("BatteryDetails") + if not self._display_enabled: + return + + sm = ui_state.sm + if sm.recv_frame["carState"] < ui_state.started_frame: + self._reset_values() + return + + car_state = sm["carState"] + + battery_data = car_state.batteryDetails + self._capacity = float(battery_data.capacity) + self._charge = float(battery_data.charge) + self._soc = float(battery_data.soc) + self._temperature = float(battery_data.temperature) + self._heater_active = bool(battery_data.heaterActive) + self._voltage = float(battery_data.voltage) + self._current = float(battery_data.current) + self._power = float(battery_data.power) + + def _reset_values(self) -> None: + self._capacity = 0.0 + self._charge = 0.0 + self._soc = 0.0 + self._temperature = 0.0 + self._heater_active = False + self._voltage = 0.0 + self._current = 0.0 + self._power = 0.0 + + def _render(self, rect: rl.Rectangle) -> None: + if not self._display_enabled: + return + + scale = CONFIG.scale_factor + base_line_h = CONFIG.line_height + line_h = int(base_line_h * scale) + + panel_width = int(rect.width * CONFIG.panel_width_ratio) + panel_height = line_h * 4 + + panel_margin = CONFIG.panel_margin + + x_start = int(rect.x + rect.width - panel_width - panel_margin) + y_start = int(rect.y + rect.height - panel_margin - panel_height) + + panel_rect = rl.Rectangle(x_start, y_start, panel_width, panel_height) + rl.draw_rectangle_rounded(panel_rect, 0.1, 8, self._panel_bg) + + label_width = CONFIG.label_width + text_margin = CONFIG.text_margin + column_spacing = panel_width // 2 - 20 + value_width = column_spacing - label_width - text_margin + + labels = [ + "Capacity:", "Charge:", "SoC:", "Temperature:", + "Heater Active:", "Voltage:", "Current:", "Power:", + ] + + values = [ + f"{self._capacity:.2f} Wh", + f"{self._charge:.2f} Wh", + f"{self._soc:.2f} %", + f"{self._temperature:.2f} °C", + "True" if self._heater_active else "False", + f"{self._voltage:.2f} V", + f"{self._current:.2f} A", + f"{self._power:.2f} kW", + ] + + rl.draw_text_ex + + for i, (label, value) in enumerate(zip(labels, values)): + column = i // 4 + row = i % 4 + + base_x = x_start + column * column_spacing + base_y = y_start + row * line_h + + label_pos = rl.Vector2(float(base_x), float(base_y)) + rl.draw_text_ex( + self._font, + label, + label_pos, + 40 * scale, + 0, + self._text_color, + ) + + value_x = base_x + label_width + text_margin + value_pos = rl.Vector2(float(value_x), float(base_y)) + rl.draw_text_ex( + self._font, + value, + value_pos, + 40 * scale, + 0, + self._text_color, + ) diff --git a/selfdrive/ui/onroad/hud_renderer.py b/selfdrive/ui/onroad/hud_renderer.py index a2459c27e..3f4a45384 100644 --- a/selfdrive/ui/onroad/hud_renderer.py +++ b/selfdrive/ui/onroad/hud_renderer.py @@ -2,6 +2,7 @@ import pyray as rl from dataclasses import dataclass from openpilot.common.constants import CV from openpilot.selfdrive.ui.onroad.exp_button import ExpButton +from openpilot.selfdrive.ui.onroad.battery_details import BatteryDetails from openpilot.selfdrive.ui.ui_state import ui_state, UIStatus from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.lib.multilang import tr @@ -71,6 +72,7 @@ class HudRenderer(Widget): self._font_medium: rl.Font = gui_app.font(FontWeight.MEDIUM) self._exp_button: ExpButton = ExpButton(UI_CONFIG.button_size, UI_CONFIG.wheel_icon_size) + self._battery_details = BatteryDetails() def _update_state(self) -> None: """Update HUD state based on car state and controls state.""" @@ -99,6 +101,8 @@ class HudRenderer(Widget): v_ego = v_ego_cluster if self.v_ego_cluster_seen else car_state.vEgo speed_conversion = CV.MS_TO_KPH if ui_state.is_metric else CV.MS_TO_MPH self.speed = max(0.0, v_ego * speed_conversion) + + self._battery_details.update() def _render(self, rect: rl.Rectangle) -> None: """Render HUD elements to the screen.""" @@ -119,6 +123,7 @@ class HudRenderer(Widget): button_x = rect.x + rect.width - UI_CONFIG.border_size - UI_CONFIG.button_size button_y = rect.y + UI_CONFIG.border_size + self._battery_details.render(rect) self._exp_button.render(rl.Rectangle(button_x, button_y, UI_CONFIG.button_size, UI_CONFIG.button_size)) def user_interacting(self) -> bool: From 461daae1861345d9bb8e2c0c7955ffaa5349afb5 Mon Sep 17 00:00:00 2001 From: infiniteCable2 Date: Sun, 16 Nov 2025 15:27:59 +0100 Subject: [PATCH 353/910] fix --- selfdrive/ui/onroad/battery_details.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/onroad/battery_details.py b/selfdrive/ui/onroad/battery_details.py index d72f0acc6..6e7bdd72c 100644 --- a/selfdrive/ui/onroad/battery_details.py +++ b/selfdrive/ui/onroad/battery_details.py @@ -19,7 +19,7 @@ class BatteryPanelConfig: CONFIG = BatteryPanelConfig() class BatteryDetails(Widget): - def __init__(self, button_size: int, icon_size: int): + def __init__(self) -> None: super().__init__() self._params = Params() From e3abdeaa647c748bf26ec86aba6c3377193e7929 Mon Sep 17 00:00:00 2001 From: infiniteCable2 Date: Sun, 16 Nov 2025 15:41:30 +0100 Subject: [PATCH 354/910] fix --- selfdrive/ui/onroad/hud_renderer.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/selfdrive/ui/onroad/hud_renderer.py b/selfdrive/ui/onroad/hud_renderer.py index 3f4a45384..c9726e41a 100644 --- a/selfdrive/ui/onroad/hud_renderer.py +++ b/selfdrive/ui/onroad/hud_renderer.py @@ -101,8 +101,6 @@ class HudRenderer(Widget): v_ego = v_ego_cluster if self.v_ego_cluster_seen else car_state.vEgo speed_conversion = CV.MS_TO_KPH if ui_state.is_metric else CV.MS_TO_MPH self.speed = max(0.0, v_ego * speed_conversion) - - self._battery_details.update() def _render(self, rect: rl.Rectangle) -> None: """Render HUD elements to the screen.""" From 9bd33c1e4ce49513a82f4c751cc6b2d737f4be53 Mon Sep 17 00:00:00 2001 From: infiniteCable2 Date: Sun, 16 Nov 2025 15:48:31 +0100 Subject: [PATCH 355/910] expand label width a little bit --- selfdrive/ui/onroad/battery_details.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/onroad/battery_details.py b/selfdrive/ui/onroad/battery_details.py index 6e7bdd72c..d83efe557 100644 --- a/selfdrive/ui/onroad/battery_details.py +++ b/selfdrive/ui/onroad/battery_details.py @@ -13,7 +13,7 @@ class BatteryPanelConfig: panel_width_ratio: float = 0.7 # 70 % der Breite von rechts panel_margin: int = 30 # Abstand zum Rand line_height: int = 48 # Basis-Zeilenhöhe - label_width: int = 265 + label_width: int = 280 text_margin: int = 25 # Abstand Label → Wert CONFIG = BatteryPanelConfig() From 4ab01b43d201d57386f342b45b4a4a0daa4d2c3b Mon Sep 17 00:00:00 2001 From: infiniteCable2 Date: Sun, 16 Nov 2025 15:53:15 +0100 Subject: [PATCH 356/910] Update battery_details.py --- selfdrive/ui/onroad/battery_details.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/onroad/battery_details.py b/selfdrive/ui/onroad/battery_details.py index d83efe557..712863917 100644 --- a/selfdrive/ui/onroad/battery_details.py +++ b/selfdrive/ui/onroad/battery_details.py @@ -13,7 +13,7 @@ class BatteryPanelConfig: panel_width_ratio: float = 0.7 # 70 % der Breite von rechts panel_margin: int = 30 # Abstand zum Rand line_height: int = 48 # Basis-Zeilenhöhe - label_width: int = 280 + label_width: int = 320 text_margin: int = 25 # Abstand Label → Wert CONFIG = BatteryPanelConfig() @@ -91,7 +91,7 @@ class BatteryDetails(Widget): label_width = CONFIG.label_width text_margin = CONFIG.text_margin - column_spacing = panel_width // 2 - 20 + column_spacing = panel_width // 2 - 40 value_width = column_spacing - label_width - text_margin labels = [ From a447476952c90fd4783d18840d4a7793b7fe3501 Mon Sep 17 00:00:00 2001 From: infiniteCable2 Date: Sun, 16 Nov 2025 16:02:46 +0100 Subject: [PATCH 357/910] coloring --- selfdrive/ui/onroad/battery_details.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/selfdrive/ui/onroad/battery_details.py b/selfdrive/ui/onroad/battery_details.py index 712863917..6f1b8dc53 100644 --- a/selfdrive/ui/onroad/battery_details.py +++ b/selfdrive/ui/onroad/battery_details.py @@ -34,7 +34,8 @@ class BatteryDetails(Widget): self._font: rl.Font = gui_app.font(FontWeight.MEDIUM) self._panel_bg: rl.Color = rl.Color(0, 0, 0, 128) - self._text_color: rl.Color = rl.Color(255, 255, 255, 255) + self._label_color: rl.Color = rl.Color(220, 220, 220, 255) + self._value_color: rl.Color = rl.Color(255, 255, 255, 255) self._display_enabled: bool = False @@ -126,7 +127,7 @@ class BatteryDetails(Widget): label_pos, 40 * scale, 0, - self._text_color, + self._label_color, ) value_x = base_x + label_width + text_margin @@ -137,5 +138,5 @@ class BatteryDetails(Widget): value_pos, 40 * scale, 0, - self._text_color, + self._value_color, ) From 187aa04ca9fd71f5bba0c3033701867c3ef56a1b Mon Sep 17 00:00:00 2001 From: infiniteCable2 Date: Sun, 16 Nov 2025 17:15:55 +0100 Subject: [PATCH 358/910] dark mode and inactivity onroad --- selfdrive/ui/layouts/settings/ictoggles.py | 20 ++++++++++++++++---- selfdrive/ui/ui_state.py | 11 ++++++++++- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/selfdrive/ui/layouts/settings/ictoggles.py b/selfdrive/ui/layouts/settings/ictoggles.py index d50d818c1..72a469dd5 100644 --- a/selfdrive/ui/layouts/settings/ictoggles.py +++ b/selfdrive/ui/layouts/settings/ictoggles.py @@ -36,6 +36,12 @@ DESCRIPTIONS = { "EnableSmoothSteer": tr_noop( "Enables S-curving on lateral control for smoother steering" ), + "DarkMode": tr_noop( + "Force brightness to a minimal value" + ), + "DisableScreenTimer": tr_noop( + "The onroad screen is turned of after 10 seconds. It will be temporarily enabled on alerts" + ), } @@ -94,10 +100,16 @@ class ICTogglesLayout(Widget): "chffr_wheel.png", False, ), - "EnableSmoothSteer": ( - lambda: tr("Steer Smoothing"), - DESCRIPTIONS["EnableSmoothSteer"], - "chffr_wheel.png", + "DarkMode": ( + lambda: tr("Dark Mode"), + DESCRIPTIONS["DarkMode"], + "eye_closed.png", + False, + ), + "DisableScreenTimer": ( + lambda: tr("Onroad Screen Timeout"), + DESCRIPTIONS["DisableScreenTimer"], + "eye_closed.png", False, ), } diff --git a/selfdrive/ui/ui_state.py b/selfdrive/ui/ui_state.py index b08b8ef28..3647643c0 100644 --- a/selfdrive/ui/ui_state.py +++ b/selfdrive/ui/ui_state.py @@ -73,6 +73,9 @@ class UIState: self.CP: car.CarParams | None = None self.light_sensor: float = -1.0 self._param_update_time: float = 0.0 + + self.dark_mode: bool = False + self.onroad_screen_timeout = False # Callbacks self._offroad_transition_callbacks: list[Callable[[], None]] = [] @@ -173,6 +176,8 @@ class UIState: else: self.has_longitudinal_control = self.CP.openpilotLongitudinalControl self._param_update_time = time.monotonic() + self.dark_mode = self.params.get_bool("DarkMode") + self.onroad_screen_timeout = self.params.get_bool("DisableScreenTimer") class Device: @@ -222,7 +227,11 @@ class Device: clipped_brightness = float(np.clip(100 * clipped_brightness, 10, 100)) + if ui_state.started and ui_state.dark_mode: + clipped_brightness = 1.0 + brightness = round(self._brightness_filter.update(clipped_brightness)) + if not self._awake: brightness = 0 @@ -246,7 +255,7 @@ class Device: callback() self._prev_timed_out = interaction_timeout - self._set_awake(ui_state.ignition or not interaction_timeout) + self._set_awake((ui_state.ignition and not ui_state.onroad_screen_timeout) or not interaction_timeout) def _set_awake(self, on: bool): if on != self._awake: From f96e7bc5f778640507403d0dcfa999861c9c1993 Mon Sep 17 00:00:00 2001 From: infiniteCable2 Date: Sun, 16 Nov 2025 17:22:23 +0100 Subject: [PATCH 359/910] cleanup and onroad trans wake --- common/params_keys.h | 1 - selfdrive/ui/ui_state.py | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/common/params_keys.h b/common/params_keys.h index f333697e3..0b51e31ff 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -135,7 +135,6 @@ inline static std::unordered_map keys = { {"DarkMode", {PERSISTENT, BOOL}}, {"EnableCurvatureController", {PERSISTENT, BOOL, "1"}}, {"EnableLongComfortMode", {PERSISTENT, BOOL}}, - {"EnableScreenEvent", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BOOL}}, {"EnableSmoothSteer", {PERSISTENT, BOOL}}, {"EnableSpeedLimitControl", {PERSISTENT, BOOL}}, {"EnableSpeedLimitPredicative", {PERSISTENT, BOOL}}, diff --git a/selfdrive/ui/ui_state.py b/selfdrive/ui/ui_state.py index 3647643c0..49c70bfb2 100644 --- a/selfdrive/ui/ui_state.py +++ b/selfdrive/ui/ui_state.py @@ -244,9 +244,10 @@ class Device: def _update_wakefulness(self): # Handle interactive timeout ignition_just_turned_off = not ui_state.ignition and self._ignition + ignition_just_turned_on = ui_state.ignition and not self._ignition self._ignition = ui_state.ignition - if ignition_just_turned_off or any(ev.left_down for ev in gui_app.mouse_events): + if ignition_just_turned_off or ignition_just_turned_on or any(ev.left_down for ev in gui_app.mouse_events): self.reset_interactive_timeout() interaction_timeout = time.monotonic() > self._interaction_time From 1e338331548962613a20fc1c51f270ace823f058 Mon Sep 17 00:00:00 2001 From: infiniteCable2 Date: Sun, 16 Nov 2025 19:19:36 +0100 Subject: [PATCH 360/910] wake for change onroad --- selfdrive/ui/ui_state.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/ui_state.py b/selfdrive/ui/ui_state.py index 49c70bfb2..0a37acc92 100644 --- a/selfdrive/ui/ui_state.py +++ b/selfdrive/ui/ui_state.py @@ -75,7 +75,10 @@ class UIState: self._param_update_time: float = 0.0 self.dark_mode: bool = False - self.onroad_screen_timeout = False + self.onroad_screen_timeout: bool = False + self.has_alert: bool = False + self.has_status_change: bool = False + self._status_prev: UIStatus = self.status # Callbacks self._offroad_transition_callbacks: list[Callable[[], None]] = [] @@ -146,6 +149,13 @@ class UIState: self.status = UIStatus.OVERRIDE else: self.status = UIStatus.ENGAGED if ss.enabled else UIStatus.DISENGAGED + + # detect status change + self.has_status_change = True if self.status != self._status_prev else False + self._status_prev = self.status + + # check for alert + self.has_alert = True if ss.alertSize != 0 else False # Check for engagement state changes if self.engaged != self._engaged_prev: @@ -256,7 +266,10 @@ class Device: callback() self._prev_timed_out = interaction_timeout - self._set_awake((ui_state.ignition and not ui_state.onroad_screen_timeout) or not interaction_timeout) + enable_screen_event = ui_state.has_alert or ui_state.has_status_change + wake_ignition = ui_state.ignition and not ui_state.onroad_screen_timeout + wake_event_onroad = ui_state.ignition and ui_state.onroad_screen_timeout and enable_screen_event + self._set_awake(wake_ignition or wake_event_onroad or not interaction_timeout) def _set_awake(self, on: bool): if on != self._awake: From 557e8b635ddcc7cb4dc9b154fe16f63f6efa86d6 Mon Sep 17 00:00:00 2001 From: infiniteCable2 Date: Sun, 16 Nov 2025 19:25:21 +0100 Subject: [PATCH 361/910] reset timeout --- selfdrive/ui/ui_state.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/ui_state.py b/selfdrive/ui/ui_state.py index 0a37acc92..b312fafc2 100644 --- a/selfdrive/ui/ui_state.py +++ b/selfdrive/ui/ui_state.py @@ -257,7 +257,8 @@ class Device: ignition_just_turned_on = ui_state.ignition and not self._ignition self._ignition = ui_state.ignition - if ignition_just_turned_off or ignition_just_turned_on or any(ev.left_down for ev in gui_app.mouse_events): + enable_screen_event = ui_state.has_alert or ui_state.has_status_change + if ignition_just_turned_off or ignition_just_turned_on or enable_screen_event or any(ev.left_down for ev in gui_app.mouse_events): self.reset_interactive_timeout() interaction_timeout = time.monotonic() > self._interaction_time @@ -266,7 +267,6 @@ class Device: callback() self._prev_timed_out = interaction_timeout - enable_screen_event = ui_state.has_alert or ui_state.has_status_change wake_ignition = ui_state.ignition and not ui_state.onroad_screen_timeout wake_event_onroad = ui_state.ignition and ui_state.onroad_screen_timeout and enable_screen_event self._set_awake(wake_ignition or wake_event_onroad or not interaction_timeout) From 5b9cf5c9fd0c6805018bb49fa768fe133ff0911a Mon Sep 17 00:00:00 2001 From: infiniteCable2 Date: Sun, 16 Nov 2025 19:28:48 +0100 Subject: [PATCH 362/910] should be ok like this --- selfdrive/ui/ui_state.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/ui_state.py b/selfdrive/ui/ui_state.py index b312fafc2..0ea202808 100644 --- a/selfdrive/ui/ui_state.py +++ b/selfdrive/ui/ui_state.py @@ -257,7 +257,7 @@ class Device: ignition_just_turned_on = ui_state.ignition and not self._ignition self._ignition = ui_state.ignition - enable_screen_event = ui_state.has_alert or ui_state.has_status_change + enable_screen_event = ui_state.ignition and (ui_state.has_alert or ui_state.has_status_change) if ignition_just_turned_off or ignition_just_turned_on or enable_screen_event or any(ev.left_down for ev in gui_app.mouse_events): self.reset_interactive_timeout() @@ -268,7 +268,7 @@ class Device: self._prev_timed_out = interaction_timeout wake_ignition = ui_state.ignition and not ui_state.onroad_screen_timeout - wake_event_onroad = ui_state.ignition and ui_state.onroad_screen_timeout and enable_screen_event + wake_event_onroad = ui_state.ignition and ui_state.onroad_screen_timeout self._set_awake(wake_ignition or wake_event_onroad or not interaction_timeout) def _set_awake(self, on: bool): From 8307202df60d9be06ac2340960d7d6cbdd979575 Mon Sep 17 00:00:00 2001 From: infiniteCable2 Date: Sun, 16 Nov 2025 19:31:32 +0100 Subject: [PATCH 363/910] Update ui_state.py --- selfdrive/ui/ui_state.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/selfdrive/ui/ui_state.py b/selfdrive/ui/ui_state.py index 0ea202808..eae6c6471 100644 --- a/selfdrive/ui/ui_state.py +++ b/selfdrive/ui/ui_state.py @@ -267,9 +267,7 @@ class Device: callback() self._prev_timed_out = interaction_timeout - wake_ignition = ui_state.ignition and not ui_state.onroad_screen_timeout - wake_event_onroad = ui_state.ignition and ui_state.onroad_screen_timeout - self._set_awake(wake_ignition or wake_event_onroad or not interaction_timeout) + self._set_awake((ui_state.ignition and not ui_state.onroad_screen_timeout) or not interaction_timeout) def _set_awake(self, on: bool): if on != self._awake: From a6d2297545ae41fd660443ce0a0136b31f172154 Mon Sep 17 00:00:00 2001 From: Alexandre Nobuharu Sato <66435071+AlexandreSato@users.noreply.github.com> Date: Sun, 16 Nov 2025 18:15:04 -0300 Subject: [PATCH 364/910] Multilang: update pt-BR translations (#36626) --- selfdrive/ui/translations/app_pt-BR.po | 132 +++++++++++++------------ 1 file changed, 68 insertions(+), 64 deletions(-) diff --git a/selfdrive/ui/translations/app_pt-BR.po b/selfdrive/ui/translations/app_pt-BR.po index 8a388d0da..84b53c6e8 100644 --- a/selfdrive/ui/translations/app_pt-BR.po +++ b/selfdrive/ui/translations/app_pt-BR.po @@ -15,7 +15,9 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" +"X-Language: pt_BR\n" +"X-Source-Language: C\n" #: selfdrive/ui/layouts/settings/device.py:160 #, python-format @@ -78,12 +80,12 @@ msgstr "" #: selfdrive/ui/layouts/settings/device.py:148 #, python-format msgid "

Steering lag calibration is complete." -msgstr "" +msgstr "

A calibração da latência da direção está concluída." #: selfdrive/ui/layouts/settings/device.py:146 #, python-format msgid "

Steering lag calibration is {}% complete." -msgstr "" +msgstr "

A calibração da latência da direção está {}% concluída." #: selfdrive/ui/layouts/settings/firehose.py:138 #, python-format @@ -106,7 +108,7 @@ msgstr "ADICIONAR" #: system/ui/widgets/network.py:139 #, python-format msgid "APN Setting" -msgstr "" +msgstr "Configuração de APN" #: selfdrive/ui/widgets/offroad_alerts.py:109 #, python-format @@ -116,7 +118,7 @@ msgstr "Reconhecer Atuação Excessiva" #: system/ui/widgets/network.py:74 system/ui/widgets/network.py:95 #, python-format msgid "Advanced" -msgstr "" +msgstr "Avançado" #: selfdrive/ui/layouts/settings/toggles.py:98 #, python-format @@ -205,18 +207,19 @@ msgstr "CONECTAR" #: system/ui/widgets/network.py:369 #, python-format msgid "CONNECTING..." -msgstr "CONECTAR" +msgstr "CONECTANDO..." -#: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35 -#: system/ui/widgets/keyboard.py:81 system/ui/widgets/network.py:318 +#: system/ui/widgets/confirm_dialog.py:23 +#: system/ui/widgets/option_dialog.py:35 system/ui/widgets/keyboard.py:81 +#: system/ui/widgets/network.py:318 #, python-format msgid "Cancel" -msgstr "" +msgstr "Cancelar" #: system/ui/widgets/network.py:134 #, python-format msgid "Cellular Metered" -msgstr "" +msgstr "Dados móveis limitados" #: selfdrive/ui/layouts/settings/device.py:68 #, python-format @@ -227,7 +230,7 @@ msgstr "Alterar Idioma" #, python-format msgid "Changing this setting will restart openpilot if the car is powered on." msgstr "" -" Alterar esta configuração reiniciará o openpilot se o carro estiver ligado." +"Alterar esta configuração reiniciará o openpilot se o carro estiver ligado." #: selfdrive/ui/widgets/pairing_dialog.py:129 #, python-format @@ -261,7 +264,7 @@ msgstr "Recusar, desinstalar o openpilot" #: selfdrive/ui/layouts/settings/settings.py:67 msgid "Developer" -msgstr "Desenvolvedor" +msgstr "Desenvolv" #: selfdrive/ui/layouts/settings/settings.py:62 msgid "Device" @@ -309,12 +312,12 @@ msgstr "Câmera do Motorista" #: selfdrive/ui/layouts/settings/toggles.py:96 #, python-format msgid "Driving Personality" -msgstr "Personalidade de Condução" +msgstr "Personalidade" #: system/ui/widgets/network.py:123 system/ui/widgets/network.py:139 #, python-format msgid "EDIT" -msgstr "" +msgstr "EDITAR" #: selfdrive/ui/layouts/sidebar.py:138 msgid "ERROR" @@ -382,22 +385,22 @@ msgstr "" #: system/ui/widgets/network.py:204 #, python-format msgid "Enter APN" -msgstr "" +msgstr "Digite APN" #: system/ui/widgets/network.py:241 #, python-format msgid "Enter SSID" -msgstr "" +msgstr "Digite SSID" #: system/ui/widgets/network.py:254 #, python-format msgid "Enter new tethering password" -msgstr "" +msgstr "Digite nova senha tethering" #: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 #, python-format msgid "Enter password" -msgstr "" +msgstr "Digite a senha" #: selfdrive/ui/widgets/ssh_key.py:89 #, python-format @@ -407,7 +410,7 @@ msgstr "Digite seu nome de usuário do GitHub" #: system/ui/widgets/list_view.py:123 system/ui/widgets/list_view.py:160 #, python-format msgid "Error" -msgstr "" +msgstr "Erro" #: selfdrive/ui/layouts/settings/toggles.py:52 #, python-format @@ -426,12 +429,12 @@ msgstr "" #: system/ui/widgets/network.py:373 #, python-format msgid "FORGETTING..." -msgstr "" +msgstr "ESQUECENDO..." #: selfdrive/ui/widgets/setup.py:44 #, python-format msgid "Finish Setup" -msgstr "Concluir Configuração" +msgstr "Configure" #: selfdrive/ui/layouts/settings/settings.py:66 msgid "Firehose" @@ -487,12 +490,12 @@ msgstr "" #: system/ui/widgets/network.py:318 system/ui/widgets/network.py:451 #, python-format msgid "Forget" -msgstr "" +msgstr "Esquecer" #: system/ui/widgets/network.py:319 #, python-format msgid "Forget Wi-Fi Network \"{}\"?" -msgstr "" +msgstr "Esquecer rede Wi-Fi \"{}\"?" #: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 msgid "GOOD" @@ -526,7 +529,7 @@ msgstr "INSTALAR" #: system/ui/widgets/network.py:150 #, python-format msgid "IP Address" -msgstr "" +msgstr "Endereço IP" #: selfdrive/ui/layouts/settings/software.py:53 #, python-format @@ -568,7 +571,7 @@ msgstr "" #: selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "N/A" -msgstr "" +msgstr "N/A" #: selfdrive/ui/layouts/sidebar.py:142 msgid "NO" @@ -670,12 +673,12 @@ msgstr "Desligar" #: system/ui/widgets/network.py:144 #, python-format msgid "Prevent large data uploads when on a metered Wi-Fi connection" -msgstr "" +msgstr "Evitar uploads grandes de dados em conexões Wi-Fi limitadas" #: system/ui/widgets/network.py:135 #, python-format msgid "Prevent large data uploads when on a metered cellular connection" -msgstr "" +msgstr "Evitar uploads grandes de dados em conexões móveis limitadas" #: selfdrive/ui/layouts/settings/device.py:25 msgid "" @@ -795,27 +798,27 @@ msgstr "Revise as regras, recursos e limitações do openpilot" #: selfdrive/ui/layouts/settings/software.py:61 #, python-format msgid "SELECT" -msgstr "" +msgstr "SELECIONAR" #: selfdrive/ui/layouts/settings/developer.py:53 #, python-format msgid "SSH Keys" -msgstr "" +msgstr "Chaves SSH" #: system/ui/widgets/network.py:310 #, python-format msgid "Scanning Wi-Fi networks..." -msgstr "" +msgstr "Procurando redes Wi-Fi..." #: system/ui/widgets/option_dialog.py:36 #, python-format msgid "Select" -msgstr "" +msgstr "Selecione" #: selfdrive/ui/layouts/settings/software.py:183 #, python-format msgid "Select a branch" -msgstr "" +msgstr "Selecione uma branch" #: selfdrive/ui/layouts/settings/device.py:91 #, python-format @@ -873,16 +876,16 @@ msgstr "TEMP" #: selfdrive/ui/layouts/settings/software.py:61 #, python-format msgid "Target Branch" -msgstr "" +msgstr "Branch Alvo" #: system/ui/widgets/network.py:124 #, python-format msgid "Tethering Password" -msgstr "" +msgstr "Senha Tethering" #: selfdrive/ui/layouts/settings/settings.py:64 msgid "Toggles" -msgstr "Alternâncias" +msgstr "Toggles" #: selfdrive/ui/layouts/settings/software.py:72 #, python-format @@ -978,12 +981,12 @@ msgstr "Wi‑Fi" #: system/ui/widgets/network.py:144 #, python-format msgid "Wi-Fi Network Metered" -msgstr "" +msgstr "Rede Wi-Fi limitada" #: system/ui/widgets/network.py:314 #, python-format msgid "Wrong password" -msgstr "" +msgstr "Senha errada" #: selfdrive/ui/layouts/onboarding.py:145 #, python-format @@ -1012,7 +1015,7 @@ msgstr "comma prime" #: system/ui/widgets/network.py:142 #, python-format msgid "default" -msgstr "" +msgstr "default" #: selfdrive/ui/layouts/settings/device.py:133 #, python-format @@ -1027,7 +1030,7 @@ msgstr "falha ao verificar atualização" #: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 #, python-format msgid "for \"{}\"" -msgstr "" +msgstr "para \"{}\"" #: selfdrive/ui/onroad/hud_renderer.py:177 #, python-format @@ -1037,7 +1040,7 @@ msgstr "km/h" #: system/ui/widgets/network.py:204 #, python-format msgid "leave blank for automatic configuration" -msgstr "" +msgstr "deixe em branco para configuração automática" #: selfdrive/ui/layouts/settings/device.py:134 #, python-format @@ -1047,7 +1050,7 @@ msgstr "à esquerda" #: system/ui/widgets/network.py:142 #, python-format msgid "metered" -msgstr "" +msgstr "limitados" #: selfdrive/ui/onroad/hud_renderer.py:177 #, python-format @@ -1077,30 +1080,30 @@ msgstr "openpilot Indisponível" #: selfdrive/ui/layouts/settings/toggles.py:158 #, python-format msgid "" -"openpilot defaults to driving in chill mode. Experimental mode enables alpha-" -"level features that aren't ready for chill mode. Experimental features are " -"listed below:

End-to-End Longitudinal Control


Let the driving " -"model control the gas and brakes. openpilot will drive as it thinks a human " -"would, including stopping for red lights and stop signs. Since the driving " -"model decides the speed to drive, the set speed will only act as an upper " -"bound. This is an alpha quality feature; mistakes should be expected." -"

New Driving Visualization


The driving visualization will " -"transition to the road-facing wide-angle camera at low speeds to better show " -"some turns. The Experimental mode logo will also be shown in the top right " -"corner." +"openpilot defaults to driving in chill mode. Experimental mode enables " +"alpha-level features that aren't ready for chill mode. Experimental features " +"are listed below:

End-to-End Longitudinal Control


Let the " +"driving model control the gas and brakes. openpilot will drive as it thinks " +"a human would, including stopping for red lights and stop signs. Since the " +"driving model decides the speed to drive, the set speed will only act as an " +"upper bound. This is an alpha quality feature; mistakes should be " +"expected.

New Driving Visualization


The driving visualization " +"will transition to the road-facing wide-angle camera at low speeds to better " +"show some turns. The Experimental mode logo will also be shown in the top " +"right corner." msgstr "" "o openpilot dirige por padrão no modo chill. O Modo Experimental habilita " "recursos em nível alpha que não estão prontos para o modo chill. Os recursos " -"experimentais são listados abaixo:

Controle Longitudinal End-to-End
Permita que o modelo de condução controle o acelerador e os freios. O " -"openpilot dirigirá como acha que um humano faria, incluindo parar em sinais " -"e semáforos vermelhos. Como o modelo decide a velocidade, a velocidade " -"definida atuará apenas como limite superior. Este é um recurso de qualidade " -"alpha; erros devem ser esperados.

Nova Visualização de Condução
A visualização de condução mudará para a câmera grande-angular " -"voltada para a estrada em baixas velocidades para mostrar melhor algumas " -"curvas. O logotipo do Modo Experimental também será exibido no canto " -"superior direito." +"experimentais são listados abaixo:

Controle Longitudinal " +"End-to-End


Permita que o modelo de condução controle o acelerador e " +"os freios. O openpilot dirigirá como acha que um humano faria, incluindo " +"parar em sinais e semáforos vermelhos. Como o modelo decide a velocidade, a " +"velocidade definida atuará apenas como limite superior. Este é um recurso de " +"qualidade alpha; erros devem ser esperados.

Nova Visualização de " +"Condução


A visualização de condução mudará para a câmera " +"grande-angular voltada para a estrada em baixas velocidades para mostrar " +"melhor algumas curvas. O logotipo do Modo Experimental também será exibido " +"no canto superior direito." #: selfdrive/ui/layouts/settings/device.py:165 #, python-format @@ -1108,7 +1111,8 @@ msgid "" "openpilot is continuously calibrating, resetting is rarely required. " "Resetting calibration will restart openpilot if the car is powered on." msgstr "" -" Alterar esta configuração reiniciará o openpilot se o carro estiver ligado." +"O openpilot está continuamente calibrando, resetar é raramente solicitado. " +"Alterar esta configuração reiniciará o openpilot se o carro estiver ligado." #: selfdrive/ui/layouts/settings/firehose.py:20 msgid "" @@ -1146,7 +1150,7 @@ msgstr "à direita" #: system/ui/widgets/network.py:142 #, python-format msgid "unmetered" -msgstr "" +msgstr "ilimitados" #: selfdrive/ui/layouts/settings/device.py:133 #, python-format From 2f8366cd2181e9c7d144cb4f66b6b5fb7800e7d5 Mon Sep 17 00:00:00 2001 From: infiniteCable2 Date: Mon, 17 Nov 2025 16:44:34 +0100 Subject: [PATCH 365/910] perf optimization param reading --- selfdrive/ui/onroad/battery_details.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/onroad/battery_details.py b/selfdrive/ui/onroad/battery_details.py index 6f1b8dc53..dbfa59945 100644 --- a/selfdrive/ui/onroad/battery_details.py +++ b/selfdrive/ui/onroad/battery_details.py @@ -1,6 +1,7 @@ import pyray as rl -from dataclasses import dataclass +import time +from dataclasses import dataclass from openpilot.common.params import Params from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.system.ui.lib.application import gui_app, FontWeight @@ -38,9 +39,14 @@ class BatteryDetails(Widget): self._value_color: rl.Color = rl.Color(255, 255, 255, 255) self._display_enabled: bool = False + self._param_update_time: float = 0.0 + + self.update_params() def _update_state(self) -> None: - self._display_enabled = self._params.get_bool("BatteryDetails") + if time.monotonic() - self._param_update_time > 2.0: + self.update_params() + if not self._display_enabled: return @@ -61,6 +67,10 @@ class BatteryDetails(Widget): self._current = float(battery_data.current) self._power = float(battery_data.power) + def _update_params(self) -> None: + self._param_update_time = time.monotonic() + self._display_enabled = self._params.get_bool("BatteryDetails") + def _reset_values(self) -> None: self._capacity = 0.0 self._charge = 0.0 From 1253917828fd5c141a38b228b414126f8454f427 Mon Sep 17 00:00:00 2001 From: infiniteCable2 Date: Mon, 17 Nov 2025 18:50:36 +0100 Subject: [PATCH 366/910] fix --- selfdrive/ui/onroad/battery_details.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/onroad/battery_details.py b/selfdrive/ui/onroad/battery_details.py index dbfa59945..13cbb9b22 100644 --- a/selfdrive/ui/onroad/battery_details.py +++ b/selfdrive/ui/onroad/battery_details.py @@ -41,11 +41,11 @@ class BatteryDetails(Widget): self._display_enabled: bool = False self._param_update_time: float = 0.0 - self.update_params() + self._update_params() def _update_state(self) -> None: if time.monotonic() - self._param_update_time > 2.0: - self.update_params() + self._update_params() if not self._display_enabled: return From a05a04b07aeadfab8c80f7f735c8beaef46fc1cf Mon Sep 17 00:00:00 2001 From: infiniteCable2 Date: Mon, 17 Nov 2025 20:30:26 +0100 Subject: [PATCH 367/910] scalings dbc --- opendbc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc_repo b/opendbc_repo index 01026ab3f..4929756df 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 01026ab3fad3deb80838809973ba675688ffbb78 +Subproject commit 4929756df557895afcb83698ee4709bd5e95bfa0 From cdb538d4a8f30152d5e151f124067f86517ba2a1 Mon Sep 17 00:00:00 2001 From: infiniteCable2 Date: Mon, 17 Nov 2025 20:32:46 +0100 Subject: [PATCH 368/910] Update opendbc_repo --- opendbc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc_repo b/opendbc_repo index 4929756df..8cf2a112a 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 4929756df557895afcb83698ee4709bd5e95bfa0 +Subproject commit 8cf2a112ad8f76db550476c04f66fcc52f30227c From fdf8edab6444c17a47b6805e0599b883f24ed0c9 Mon Sep 17 00:00:00 2001 From: infiniteCable2 Date: Mon, 17 Nov 2025 20:34:57 +0100 Subject: [PATCH 369/910] oops --- opendbc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc_repo b/opendbc_repo index 8cf2a112a..b4bd9b261 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 8cf2a112ad8f76db550476c04f66fcc52f30227c +Subproject commit b4bd9b26183aa22fd460c6f0a483f45d8a2a3c05 From afae53885d8041b23ac5439cc705299800bb25c1 Mon Sep 17 00:00:00 2001 From: infiniteCable2 Date: Mon, 17 Nov 2025 20:52:11 +0100 Subject: [PATCH 370/910] Update opendbc_repo --- opendbc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc_repo b/opendbc_repo index b4bd9b261..4f29eb6cc 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit b4bd9b26183aa22fd460c6f0a483f45d8a2a3c05 +Subproject commit 4f29eb6cc0c2133eef09bbd96c8341c2dc4669df From b8a845fe93374087135dfab6f526089ebf32eb17 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 17 Nov 2025 20:46:34 -0800 Subject: [PATCH 371/910] ui: add GRID debug helper (#36630) --- system/ui/README.md | 1 + system/ui/lib/application.py | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/system/ui/README.md b/system/ui/README.md index 3f2562aae..6e43c20d1 100644 --- a/system/ui/README.md +++ b/system/ui/README.md @@ -7,6 +7,7 @@ Quick start: * 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 * set `BURN_IN=1` to get a burn-in heatmap version of the UI +* set `GRID=50` to show a 50-pixel alignment grid overlay * 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 1d085a5a0..16b924e44 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -32,6 +32,7 @@ SHOW_FPS = os.getenv("SHOW_FPS") == "1" SHOW_TOUCHES = os.getenv("SHOW_TOUCHES") == "1" STRICT_MODE = os.getenv("STRICT_MODE") == "1" SCALE = float(os.getenv("SCALE", "1.0")) +GRID_SIZE = int(os.getenv("GRID", "0")) PROFILE_RENDER = int(os.getenv("PROFILE_RENDER", "0")) PROFILE_STATS = int(os.getenv("PROFILE_STATS", "100")) # Number of functions to show in profile output @@ -213,6 +214,7 @@ class GuiApplication: self._mouse_history: deque[MousePosWithTime] = deque(maxlen=MOUSE_THREAD_RATE) self._show_touches = SHOW_TOUCHES self._show_fps = SHOW_FPS + self._grid_size = GRID_SIZE self._profile_render_frames = PROFILE_RENDER self._render_profiler = None self._render_profile_start_time = None @@ -457,6 +459,9 @@ class GuiApplication: if self._show_touches: self._draw_touch_points() + if self._grid_size > 0: + self._draw_grid() + rl.end_drawing() self._monitor_fps() self._frame += 1 @@ -605,6 +610,19 @@ class GuiApplication: color = rl.Color(min(int(255 * (1.5 - perc)), 255), int(min(255 * (perc + 0.5), 255)), 50, 255) rl.draw_circle(int(mouse_pos.x), int(mouse_pos.y), 5, color) + def _draw_grid(self): + grid_color = rl.Color(60, 60, 60, 255) + # Draw vertical lines + x = 0 + while x <= self._scaled_width: + rl.draw_line(x, 0, x, self._scaled_height, grid_color) + x += self._grid_size + # Draw horizontal lines + y = 0 + while y <= self._scaled_height: + rl.draw_line(0, y, self._scaled_width, y, grid_color) + y += self._grid_size + def _output_render_profile(self): import io import pstats From f653c1c0c5bc58b5225fba138df831aba807d988 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 17 Nov 2025 21:31:10 -0800 Subject: [PATCH 372/910] ui: don't sleep on PC --- selfdrive/ui/ui_state.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/ui_state.py b/selfdrive/ui/ui_state.py index b08b8ef28..8871e5de2 100644 --- a/selfdrive/ui/ui_state.py +++ b/selfdrive/ui/ui_state.py @@ -10,7 +10,7 @@ from openpilot.common.params import Params from openpilot.common.swaglog import cloudlog from openpilot.selfdrive.ui.lib.prime_state import PrimeState from openpilot.system.ui.lib.application import gui_app -from openpilot.system.hardware import HARDWARE +from openpilot.system.hardware import HARDWARE, PC BACKLIGHT_OFFROAD = 50 @@ -246,7 +246,7 @@ class Device: callback() self._prev_timed_out = interaction_timeout - self._set_awake(ui_state.ignition or not interaction_timeout) + self._set_awake(ui_state.ignition or not interaction_timeout or PC) def _set_awake(self, on: bool): if on != self._awake: From 689f884810c5f0aa14d34ef8b9379bee04f16258 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 17 Nov 2025 21:37:40 -0800 Subject: [PATCH 373/910] DM test mode (#36631) --- selfdrive/monitoring/dmonitoringd.py | 8 +++-- selfdrive/monitoring/helpers.py | 45 ++++++++++++++++++---------- 2 files changed, 36 insertions(+), 17 deletions(-) diff --git a/selfdrive/monitoring/dmonitoringd.py b/selfdrive/monitoring/dmonitoringd.py index f137b406b..293904a8e 100755 --- a/selfdrive/monitoring/dmonitoringd.py +++ b/selfdrive/monitoring/dmonitoringd.py @@ -13,6 +13,7 @@ def dmonitoringd_thread(): sm = messaging.SubMaster(['driverStateV2', 'liveCalibration', 'carState', 'selfdriveState', 'modelV2'], poll='driverStateV2') DM = DriverMonitoring(rhd_saved=params.get_bool("IsRhdDetected"), always_on=params.get_bool("AlwaysOnDM")) + demo_mode=False # 20Hz <- dmonitoringmodeld while True: @@ -22,8 +23,10 @@ def dmonitoringd_thread(): continue valid = sm.all_checks() - if valid: - DM.run_step(sm) + if demo_mode and sm.valid['driverStateV2']: + DM.run_step(sm, demo=demo_mode) + elif valid: + DM.run_step(sm, demo=demo_mode) # publish dat = DM.get_state_packet(valid=valid) @@ -32,6 +35,7 @@ def dmonitoringd_thread(): # load live always-on toggle if sm['driverStateV2'].frameId % 40 == 1: DM.always_on = params.get_bool("AlwaysOnDM") + demo_mode = params.get_bool("IsDriverViewEnabled") # save rhd virtual toggle every 5 mins if (sm['driverStateV2'].frameId % 6000 == 0 and diff --git a/selfdrive/monitoring/helpers.py b/selfdrive/monitoring/helpers.py index f405eba53..463bf2b7f 100644 --- a/selfdrive/monitoring/helpers.py +++ b/selfdrive/monitoring/helpers.py @@ -211,8 +211,8 @@ class DriverMonitoring: self.step_change = self.settings._DT_DMON / self.settings._AWARENESS_TIME self.active_monitoring_mode = False - def _set_policy(self, model_data, car_speed): - bp = model_data.meta.disengagePredictions.brakeDisengageProbs[0] # brake disengage prob in next 2s + def _set_policy(self, brake_disengage_prob, car_speed): + bp = brake_disengage_prob k1 = max(-0.00156*((car_speed-16)**2)+0.6, 0.2) bp_normal = max(min(bp / k1, 0.5),0) self.pose.cfactor_pitch = np.interp(bp_normal, [0, 0.5], @@ -417,27 +417,42 @@ class DriverMonitoring: } return dat - def run_step(self, sm): - # Set strictness + def run_step(self, sm, demo=False): + if demo: + highway_speed = 30 + enabled = True + wrong_gear = False + standstill = False + driver_engaged = False + brake_disengage_prob = 1.0 + rpyCalib = [0., 0., 0.] + else: + highway_speed = sm['carState'].vEgo + enabled = sm['selfdriveState'].enabled + wrong_gear = sm['carState'].gearShifter not in (car.CarState.GearShifter.drive, car.CarState.GearShifter.low) + standstill = sm['carState'].standstill + driver_engaged = sm['carState'].steeringPressed or sm['carState'].gasPressed + brake_disengage_prob = sm['modelV2'].meta.disengagePredictions.brakeDisengageProbs[0] # brake disengage prob in next 2s + rpyCalib = sm['liveCalibration'].rpyCalib self._set_policy( - model_data=sm['modelV2'], - car_speed=sm['carState'].vEgo + brake_disengage_prob=brake_disengage_prob, + car_speed=highway_speed, ) # Parse data from dmonitoringmodeld self._update_states( driver_state=sm['driverStateV2'], - cal_rpy=sm['liveCalibration'].rpyCalib, - car_speed=sm['carState'].vEgo, - op_engaged=sm['selfdriveState'].enabled, - standstill=sm['carState'].standstill, + cal_rpy=rpyCalib, + car_speed=highway_speed, + op_engaged=enabled, + standstill=standstill, ) # Update distraction events self._update_events( - driver_engaged=sm['carState'].steeringPressed or sm['carState'].gasPressed, - op_engaged=sm['selfdriveState'].enabled, - standstill=sm['carState'].standstill, - wrong_gear=sm['carState'].gearShifter in [car.CarState.GearShifter.reverse, car.CarState.GearShifter.park], - car_speed=sm['carState'].vEgo + driver_engaged=driver_engaged, + op_engaged=enabled, + standstill=standstill, + wrong_gear=wrong_gear, + car_speed=highway_speed ) From d3cc32ddca577e17cc778a7a9e38ba09b350b298 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 17 Nov 2025 21:40:08 -0800 Subject: [PATCH 374/910] mici fcc --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f2d8c5cad..2faa4f8ac 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -177,7 +177,7 @@ quiet-level = 3 # if you've got a short variable name that's getting flagged, add it here ignore-words-list = "bu,ro,te,ue,alo,hda,ois,nam,nams,ned,som,parm,setts,inout,warmup,bumb,nd,sie,preints,whit,indexIn,ws,uint,grey,deque,stdio,amin,BA,LITE,atEnd,UIs,errorString,arange,FocusIn,od,tim,relA,hist,copyable,jupyter,thead,TGE,abl,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/*, *.po, uv.lock, *.onnx, ./cereal/gen/*, */c_generated_code/*, docs/assets/*, tools/plotjuggler/layouts/*" +skip = "./third_party/*, ./tinygrad/*, ./tinygrad_repo/*, ./msgq/*, ./panda/*, ./opendbc/*, ./opendbc_repo/*, ./rednose/*, ./rednose_repo/*, ./teleoprtc/*, ./teleoprtc_repo/*, *.po, uv.lock, *.onnx, ./cereal/gen/*, */c_generated_code/*, docs/assets/*, tools/plotjuggler/layouts/*, selfdrive/assets/offroad/mici_fcc.html" [tool.mypy] python_version = "3.11" From 16abf93be89bed5dd1dde93944a4eb1dda95895f Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 17 Nov 2025 22:40:33 -0800 Subject: [PATCH 375/910] reduce ruff noise with raylib --- pyproject.toml | 1 + system/ui/lib/shader_polygon.py | 4 ++-- system/ui/widgets/label.py | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 2faa4f8ac..3f3804ba6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -235,6 +235,7 @@ lint.ignore = [ "B027", "B024", "NPY002", # new numpy random syntax is worse + "UP045", "UP007", # these don't play nice with raylib atm ] line-length = 160 target-version ="py311" diff --git a/system/ui/lib/shader_polygon.py b/system/ui/lib/shader_polygon.py index 3cc480b33..7be6638af 100644 --- a/system/ui/lib/shader_polygon.py +++ b/system/ui/lib/shader_polygon.py @@ -152,7 +152,7 @@ class ShaderState: self.initialized = False -def _configure_shader_color(state: ShaderState, color: Optional[rl.Color], # noqa: UP045 +def _configure_shader_color(state: ShaderState, color: Optional[rl.Color], gradient: Gradient | None, origin_rect: rl.Rectangle): assert (color is not None) != (gradient is not None), "Either color or gradient must be provided" @@ -202,7 +202,7 @@ def triangulate(pts: np.ndarray) -> list[tuple[float, float]]: def draw_polygon(origin_rect: rl.Rectangle, points: np.ndarray, - color: Optional[rl.Color] = None, gradient: Gradient | None = None): # noqa: UP045 + color: Optional[rl.Color] = None, gradient: Gradient | None = None): """ Draw a ribbon polygon (two chains) with a triangle strip and gradient. diff --git a/system/ui/widgets/label.py b/system/ui/widgets/label.py index 7d7680256..8d33ac2fd 100644 --- a/system/ui/widgets/label.py +++ b/system/ui/widgets/label.py @@ -105,7 +105,7 @@ class Label(Widget): text_alignment_vertical: int = rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE, text_padding: int = 0, text_color: rl.Color = DEFAULT_TEXT_COLOR, - icon: Union[rl.Texture, None] = None, # noqa: UP007 + icon: Union[rl.Texture, None] = None, elide_right: bool = False, ): From 67cbeebc7b571898a75072f42dab39f77562775e Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 18 Nov 2025 10:10:32 -0800 Subject: [PATCH 376/910] sensord: magnetometer is only for tizi --- system/sensord/sensord.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/system/sensord/sensord.py b/system/sensord/sensord.py index 2b6467fa7..cc0366881 100755 --- a/system/sensord/sensord.py +++ b/system/sensord/sensord.py @@ -11,6 +11,7 @@ from openpilot.common.util import sudo_write from openpilot.common.realtime import config_realtime_process, Ratekeeper from openpilot.common.swaglog import cloudlog from openpilot.common.gpio import gpiochip_get_ro_value_fd, gpioevent_data +from openpilot.system.hardware import HARDWARE from openpilot.system.sensord.sensors.i2c_sensor import Sensor from openpilot.system.sensord.sensors.lsm6ds3_accel import LSM6DS3_Accel @@ -95,8 +96,11 @@ def main() -> None: (LSM6DS3_Accel(I2C_BUS_IMU), "accelerometer", True), (LSM6DS3_Gyro(I2C_BUS_IMU), "gyroscope", True), (LSM6DS3_Temp(I2C_BUS_IMU), "temperatureSensor", False), - (MMC5603NJ_Magn(I2C_BUS_IMU), "magnetometer", False), ] + if HARDWARE.get_device_type() == "tizi": + sensors_cfg.append( + (MMC5603NJ_Magn(I2C_BUS_IMU), "magnetometer", False), + ) # Reset sensors for sensor, _, _ in sensors_cfg: From 4ef0d3ee99b376dd6ca23ef4899903bb8ce9d413 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 18 Nov 2025 10:11:06 -0800 Subject: [PATCH 377/910] setup sound for DM test mode --- system/manager/process_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/manager/process_config.py b/system/manager/process_config.py index 940e7f912..8cf3e8c14 100644 --- a/system/manager/process_config.py +++ b/system/manager/process_config.py @@ -81,7 +81,7 @@ procs = [ PythonProcess("sensord", "system.sensord.sensord", only_onroad, enabled=not PC), PythonProcess("ui", "selfdrive.ui.ui", always_run), - PythonProcess("soundd", "selfdrive.ui.soundd", only_onroad), + PythonProcess("soundd", "selfdrive.ui.soundd", driverview), PythonProcess("locationd", "selfdrive.locationd.locationd", only_onroad), NativeProcess("_pandad", "selfdrive/pandad", ["./pandad"], always_run, enabled=False), PythonProcess("calibrationd", "selfdrive.locationd.calibrationd", only_onroad), From ad7f3d2b24dd8310b03028a02519225fe5beb267 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 18 Nov 2025 10:39:32 -0800 Subject: [PATCH 378/910] selfdrived: prep for mici (#36633) * selfdrived: prep for mici * tizi reverts * more revert * lil more: * invert it * cleanup --- selfdrive/selfdrived/events.py | 89 ++++++++++++++++++++++++++++-- selfdrive/selfdrived/selfdrived.py | 3 + 2 files changed, 88 insertions(+), 4 deletions(-) diff --git a/selfdrive/selfdrived/events.py b/selfdrive/selfdrived/events.py index c865cc94a..a9b1683c5 100755 --- a/selfdrive/selfdrived/events.py +++ b/selfdrive/selfdrived/events.py @@ -13,6 +13,7 @@ from openpilot.common.realtime import DT_CTRL from openpilot.selfdrive.locationd.calibrationd import MIN_SPEED_FILTER from openpilot.system.micd import SAMPLE_RATE, SAMPLE_BUFFER from openpilot.selfdrive.ui.feedback.feedbackd import FEEDBACK_MAX_DURATION +from openpilot.system.hardware import HARDWARE AlertSize = log.SelfdriveState.AlertSize AlertStatus = log.SelfdriveState.AlertStatus @@ -150,6 +151,8 @@ class NoEntryAlert(Alert): def __init__(self, alert_text_2: str, alert_text_1: str = "openpilot Unavailable", visual_alert: car.CarControl.HUDControl.VisualAlert=VisualAlert.none): + if HARDWARE.get_device_type() == 'mici': + alert_text_1, alert_text_2 = alert_text_2, alert_text_1 super().__init__(alert_text_1, alert_text_2, AlertStatus.normal, AlertSize.mid, Priority.LOW, visual_alert, AudibleAlert.refuse, 3.) @@ -195,8 +198,13 @@ class NormalPermanentAlert(Alert): class StartupAlert(Alert): def __init__(self, alert_text_1: str, alert_text_2: str = "Always keep hands on wheel and eyes on road", alert_status=AlertStatus.normal): + alert_size = AlertSize.mid + if HARDWARE.get_device_type() == 'mici': + if alert_text_2 == "Always keep hands on wheel and eyes on road": + alert_text_2 = "" + alert_size = AlertSize.small super().__init__(alert_text_1, alert_text_2, - alert_status, AlertSize.mid, + alert_status, alert_size, Priority.LOWER, VisualAlert.none, AudibleAlert.none, 5.), @@ -246,10 +254,19 @@ def below_steer_speed_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.S Priority.LOW, VisualAlert.none, AudibleAlert.prompt, 0.4) -def calibration_incomplete_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert: - first_word = 'Recalibration' if sm['liveCalibration'].calStatus == log.LiveCalibrationData.Status.recalibrating else 'Calibration' +def steer_saturated_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert: + steer_text2 = "Steer Left" if sm['carControl'].actuators.torque > 0 else "Steer Right" return Alert( - f"{first_word} in Progress: {sm['liveCalibration'].calPerc:.0f}%", + "Take Control", + steer_text2, + AlertStatus.userPrompt, AlertSize.mid, + Priority.LOW, VisualAlert.steerRequired, AudibleAlert.promptRepeat, 2.) + + +def calibration_incomplete_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert: + first_word = 'Recalibrating' if sm['liveCalibration'].calStatus == log.LiveCalibrationData.Status.recalibrating else 'Calibrating' + return Alert( + f"{first_word}: {sm['liveCalibration'].calPerc:.0f}%", f"Drive Above {get_display_speed(MIN_SPEED_FILTER, metric)}", AlertStatus.normal, AlertSize.mid, Priority.LOWEST, VisualAlert.none, AudibleAlert.none, .2) @@ -1013,6 +1030,70 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = { } +if HARDWARE.get_device_type() == 'mici': + EVENTS.update({ + EventName.preDriverDistracted: { + ET.PERMANENT: Alert( + "Pay Attention", + "", + AlertStatus.normal, AlertSize.small, + Priority.LOW, VisualAlert.none, AudibleAlert.none, 2), + }, + EventName.promptDriverDistracted: { + ET.PERMANENT: Alert( + "Pay Attention", + "Driver Distracted", + AlertStatus.userPrompt, AlertSize.mid, + Priority.MID, VisualAlert.steerRequired, AudibleAlert.promptDistracted, 1), + }, + EventName.resumeRequired: { + ET.WARNING: Alert( + "Press Resume", + "", + AlertStatus.userPrompt, AlertSize.small, + Priority.LOW, VisualAlert.none, AudibleAlert.none, .2), + }, + EventName.preLaneChangeLeft: { + ET.WARNING: Alert( + "Steer Left", + "Confirm Lane Change", + AlertStatus.normal, AlertSize.mid, + Priority.LOW, VisualAlert.none, AudibleAlert.none, .1), + }, + EventName.preLaneChangeRight: { + ET.WARNING: Alert( + "Steer Right", + "Confirm Lane Change", + AlertStatus.normal, AlertSize.mid, + Priority.LOW, VisualAlert.none, AudibleAlert.none, .1), + }, + EventName.laneChangeBlocked: { + ET.WARNING: Alert( + "Car in Blindspot", + "", + AlertStatus.userPrompt, AlertSize.small, + Priority.LOW, VisualAlert.none, AudibleAlert.prompt, .1), + }, + EventName.steerSaturated: { + ET.WARNING: steer_saturated_alert, + }, + EventName.calibrationIncomplete: { + ET.PERMANENT: calibration_incomplete_alert, + ET.SOFT_DISABLE: soft_disable_alert("Calibration Incomplete"), + ET.NO_ENTRY: NoEntryAlert("Calibrating"), + }, + EventName.reverseGear: { + ET.PERMANENT: Alert( + "Reverse", + "", + AlertStatus.normal, AlertSize.full, + Priority.LOWEST, VisualAlert.none, AudibleAlert.none, .2, creation_delay=0.5), + ET.USER_DISABLE: ImmediateDisableAlert("Reverse"), + ET.NO_ENTRY: NoEntryAlert("Reverse"), + }, + }) + + if __name__ == '__main__': # print all alerts by type and priority from cereal.services import SERVICE_LIST diff --git a/selfdrive/selfdrived/selfdrived.py b/selfdrive/selfdrived/selfdrived.py index e9c62fe32..997c7e377 100755 --- a/selfdrive/selfdrived/selfdrived.py +++ b/selfdrive/selfdrived/selfdrived.py @@ -22,6 +22,7 @@ from openpilot.selfdrive.selfdrived.state import StateMachine from openpilot.selfdrive.selfdrived.alertmanager import AlertManager, set_offroad_alert from openpilot.system.version import get_build_metadata +from openpilot.system.hardware import HARDWARE REPLAY = "REPLAY" in os.environ SIMULATION = "SIMULATION" in os.environ @@ -123,6 +124,8 @@ class SelfdriveD: # Determine startup event self.startup_event = EventName.startup if build_metadata.openpilot.comma_remote and build_metadata.tested_channel else EventName.startupMaster + if HARDWARE.get_device_type() == 'mici': + self.startup_event = None if not car_recognized: self.startup_event = EventName.startupNoCar elif car_recognized and self.CP.passive: From e1e41be1a994339412fe49c920c19af1a5520b05 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 18 Nov 2025 10:42:06 -0800 Subject: [PATCH 379/910] common: add BounceFilter --- common/filter_simple.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/common/filter_simple.py b/common/filter_simple.py index 9ea6fe307..212e1a8f4 100644 --- a/common/filter_simple.py +++ b/common/filter_simple.py @@ -15,3 +15,20 @@ class FirstOrderFilter: self.initialized = True self.x = x return self.x + + +class BounceFilter(FirstOrderFilter): + def __init__(self, x0, rc, dt, initialized=True, bounce=2): + self.velocity = FirstOrderFilter(0.0, 0.15, dt) + self.bounce = bounce + super().__init__(x0, rc, dt, initialized) + + def update(self, x): + super().update(x) + scale = self.dt / (1.0 / 60.0) # tuned at 60 fps + self.velocity.x += (x - self.x) * self.bounce * scale * self.dt + self.velocity.update(0.0) + if abs(self.velocity.x) < 1e-5: + self.velocity.x = 0.0 + self.x += self.velocity.x + return self.x From bca727a3cba9ad4237ef74cd1da54fccf192a488 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 18 Nov 2025 19:48:04 -0800 Subject: [PATCH 380/910] Fix strength check --- selfdrive/ui/layouts/sidebar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/layouts/sidebar.py b/selfdrive/ui/layouts/sidebar.py index d468442b1..050cd795b 100644 --- a/selfdrive/ui/layouts/sidebar.py +++ b/selfdrive/ui/layouts/sidebar.py @@ -116,7 +116,7 @@ class Sidebar(Widget): def _update_network_status(self, device_state): self._net_type = NETWORK_TYPES.get(device_state.networkType.raw, tr_noop("Unknown")) strength = device_state.networkStrength - self._net_strength = max(0, min(5, strength.raw + 1)) if strength > 0 else 0 + self._net_strength = max(0, min(5, strength.raw + 1)) if strength.raw > 0 else 0 def _update_temperature_status(self, device_state): thermal_status = device_state.thermalStatus From e449ffcc36827b44dd2b24bb29507acb4319b6a8 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 18 Nov 2025 21:09:20 -0800 Subject: [PATCH 381/910] Tuneup offroad alerts --- selfdrive/selfdrived/alerts_offroad.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/selfdrive/selfdrived/alerts_offroad.json b/selfdrive/selfdrived/alerts_offroad.json index b52dfa4d8..0fc11b963 100644 --- a/selfdrive/selfdrived/alerts_offroad.json +++ b/selfdrive/selfdrived/alerts_offroad.json @@ -26,7 +26,7 @@ "severity": 0 }, "Offroad_UnregisteredHardware": { - "text": "Device failed to register with the comma.ai backend. It will not connect or upload to comma.ai servers, and receives no support from comma.ai. If this is a device purchased at comma.ai/shop, open a ticket at https://comma.ai/support.", + "text": "Failed to register with comma.ai backend. It will not connect or upload to comma.ai servers, and receives no support from comma.ai. If this is a device purchased at comma.ai/shop, open a ticket at https://comma.ai/support.", "severity": 1 }, "Offroad_CarUnrecognized": { @@ -38,11 +38,11 @@ "severity": 0 }, "Offroad_DriverMonitoringUncertain": { - "text": "openpilot detected poor visibility for driver monitoring. Ensure the device has a clear view of the driver. This can be checked using Settings -> Device -> Driver Camera Preview. Extreme lighting conditions and/or unconventional mounting positions may also trigger this alert.", + "text": "Poor visibility detected for driver monitoring. Ensure the device has a clear view of the driver. This can be checked in the device settings. Extreme lighting conditions and/or unconventional mounting positions may also trigger this alert.", "severity": 0 }, "Offroad_ExcessiveActuation": { - "text": "openpilot detected excessive %1 actuation on your last drive. Please contact support at https://comma.ai/support and share your device's Dongle ID for troubleshooting.", + "text": "Excessive %1 actuation detected on your last drive. Please contact support at https://comma.ai/support and share your device's Dongle ID for troubleshooting.", "severity": 1, "_comment": "Set extra field to lateral or longitudinal." } From b73be441b3f471f69016becd047589417a267e92 Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Tue, 18 Nov 2025 21:56:17 -0800 Subject: [PATCH 382/910] bump updater --- system/hardware/tici/updater_magic | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system/hardware/tici/updater_magic b/system/hardware/tici/updater_magic index b4dfa9be2..ec586dbcb 100755 --- a/system/hardware/tici/updater_magic +++ b/system/hardware/tici/updater_magic @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7990262878becdf2eaed40ffcc96835a6fc6bc4bdf52f4df88e8b6fcadd1bff8 -size 13664323 +oid sha256:c44fb88b3b1643b6b44ae8ac9880348bd0257ff90f4084cbe889de91d71653fe +size 25111329 From 3aaf249236ee3b0e31e8c90fa3fe651f561b42ec Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 18 Nov 2025 22:27:45 -0800 Subject: [PATCH 383/910] comma four (#36639) * squash squash squash * scroller tici --- selfdrive/assets/fonts/process.py | 6 +- selfdrive/assets/icons/eyes_crossed.png | 3 + selfdrive/assets/icons/eyes_open.png | 3 + .../icons_mici/buttons/button_circle.png | 3 + .../buttons/button_circle_disabled.png | 3 + .../buttons/button_circle_hover.png | 3 + .../buttons/button_circle_pressed.png | 3 + .../icons_mici/buttons/button_circle_red.png | 3 + .../buttons/button_circle_red_hover.png | 3 + .../buttons/button_circle_red_pressed.png | 3 + .../icons_mici/buttons/button_rectangle.png | 3 + .../buttons/button_rectangle_disabled.png | 3 + .../buttons/button_rectangle_hover.png | 3 + .../buttons/button_rectangle_pressed.png | 3 + .../icons_mici/buttons/button_side_back.png | 3 + .../buttons/button_side_back_pressed.png | 3 + .../icons_mici/buttons/button_side_check.png | 3 + .../buttons/button_side_check_pressed.png | 3 + .../icons_mici/buttons/button_side_home.png | 3 + .../assets/icons_mici/buttons/slider_bg.png | 3 + .../buttons/toggle_dot_disabled.png | 3 + .../icons_mici/buttons/toggle_dot_enabled.png | 3 + .../icons_mici/buttons/toggle_dot_orange.png | 3 + .../buttons/toggle_pill_disabled.png | 3 + .../buttons/toggle_pill_enabled.png | 3 + .../assets/icons_mici/exclamation_point.png | 3 + .../assets/icons_mici/experimental_mode.png | 3 + selfdrive/assets/icons_mici/eye.png | 3 + selfdrive/assets/icons_mici/eye_crossed.png | 3 + selfdrive/assets/icons_mici/microphone.png | 3 + .../icons_mici/notifications/blue_large.png | 3 + .../icons_mici/notifications/blue_small.png | 3 + .../icons_mici/notifications/icons/cable.png | 3 + .../icons_mici/notifications/icons/camera.png | 3 + .../icons_mici/notifications/icons/close.png | 3 + .../notifications/icons/critical.png | 3 + .../icons_mici/notifications/icons/eye.png | 3 + .../icons_mici/notifications/icons/fan.png | 3 + .../notifications/icons/temperature.png | 3 + .../icons_mici/notifications/icons/wheel.png | 3 + .../icons_mici/notifications/normal_large.png | 3 + .../icons_mici/notifications/normal_small.png | 3 + .../icons_mici/notifications/orange_large.png | 3 + .../icons_mici/notifications/orange_small.png | 3 + .../icons_mici/notifications/red_large.png | 3 + .../icons_mici/notifications/red_small.png | 3 + .../icons_mici/offroad_alerts/big_alert.png | 3 + .../offroad_alerts/big_alert_pressed.png | 3 + .../icons_mici/offroad_alerts/green_wheel.png | 3 + .../offroad_alerts/medium_alert.png | 3 + .../offroad_alerts/medium_alert_pressed.png | 3 + .../offroad_alerts/orange_warning.png | 3 + .../icons_mici/offroad_alerts/red_warning.png | 3 + .../icons_mici/offroad_alerts/small_alert.png | 3 + .../offroad_alerts/small_alert_pressed.png | 3 + .../icons_mici/onroad/blind_spot_left.png | 3 + .../icons_mici/onroad/blind_spot_right.png | 3 + .../assets/icons_mici/onroad/bookmark.png | 3 + .../driver_monitoring/dm_background.png | 3 + .../onroad/driver_monitoring/dm_center.png | 3 + .../onroad/driver_monitoring/dm_cone.png | 3 + .../onroad/driver_monitoring/dm_person.png | 3 + .../assets/icons_mici/onroad/eye_fill.png | 3 + .../assets/icons_mici/onroad/eye_orange.png | 3 + .../assets/icons_mici/onroad/glasses.png | 3 + .../assets/icons_mici/onroad/onroad_fade.png | 3 + .../assets/icons_mici/onroad/sunglasses.png | 3 + .../icons_mici/onroad/turn_signal_left.png | 3 + .../icons_mici/onroad/turn_signal_right.png | 3 + selfdrive/assets/icons_mici/settings.png | 3 + .../assets/icons_mici/settings/comma_icon.png | 3 + .../icons_mici/settings/developer/adb.png | 3 + .../settings/developer/debug_mode.png | 3 + .../icons_mici/settings/developer/ssh.png | 3 + .../icons_mici/settings/developer_icon.png | 3 + .../icons_mici/settings/device/cameras.png | 3 + .../icons_mici/settings/device/cancel.png | 3 + .../settings/device/downloading.png | 3 + .../icons_mici/settings/device/fcc_logo.png | 3 + .../icons_mici/settings/device/info.png | 3 + .../icons_mici/settings/device/language.png | 3 + .../icons_mici/settings/device/lkas.png | 3 + .../icons_mici/settings/device/pair.png | 3 + .../icons_mici/settings/device/power.png | 3 + .../icons_mici/settings/device/reboot.png | 3 + .../icons_mici/settings/device/uninstall.png | 3 + .../icons_mici/settings/device/up_to_date.png | 3 + .../icons_mici/settings/device/update.png | 3 + .../icons_mici/settings/device_icon.png | 3 + .../icons_mici/settings/keyboard/back.png | 3 + .../settings/keyboard/backspace.png | 3 + .../settings/keyboard/caps_lock.png | 3 + .../settings/keyboard/caps_lower.png | 3 + .../settings/keyboard/caps_upper.png | 3 + .../icons_mici/settings/keyboard/confirm.png | 3 + .../settings/keyboard/keyboard_background.png | 3 + .../icons_mici/settings/keyboard/space.png | 3 + .../icons_mici/settings/manual_icon.png | 3 + .../settings/network/cell_strength_full.png | 3 + .../settings/network/cell_strength_high.png | 3 + .../settings/network/cell_strength_low.png | 3 + .../settings/network/cell_strength_medium.png | 3 + .../settings/network/cell_strength_none.png | 3 + .../icons_mici/settings/network/connect.png | 3 + .../settings/network/connect_disabled.png | 3 + .../settings/network/connect_pressed.png | 3 + .../settings/network/forget_pill.png | 3 + .../settings/network/forget_pill_pressed.png | 3 + .../settings/network/new/connect_button.png | 3 + .../network/new/connect_button_pressed.png | 3 + .../settings/network/new/forget_button.png | 3 + .../network/new/forget_button_pressed.png | 3 + .../network/new/full_connect_button.png | 3 + .../new/full_connect_button_pressed.png | 3 + .../icons_mici/settings/network/new/lock.png | 3 + .../icons_mici/settings/network/new/trash.png | 3 + .../settings/network/new/wifi_selected.png | 3 + .../icons_mici/settings/network/tethering.png | 3 + .../icons_mici/settings/network/trash.png | 3 + .../settings/network/wifi_strength_full.png | 3 + .../settings/network/wifi_strength_low.png | 3 + .../settings/network/wifi_strength_medium.png | 3 + .../settings/network/wifi_strength_none.png | 3 + .../settings/network/wifi_strength_slash.png | 3 + .../icons_mici/settings/toggles_icon.png | 3 + .../settings/vertical_scroll_indicator.png | 3 + selfdrive/assets/icons_mici/setup/arrow.png | 3 + selfdrive/assets/icons_mici/setup/back.png | 3 + .../assets/icons_mici/setup/back_new.png | 3 + .../assets/icons_mici/setup/green_button.png | 3 + .../icons_mici/setup/green_button_pressed.png | 3 + .../assets/icons_mici/setup/green_car.png | 3 + .../assets/icons_mici/setup/green_dm.png | 3 + .../assets/icons_mici/setup/green_info.png | 3 + .../assets/icons_mici/setup/green_pedal.png | 3 + .../icons_mici/setup/medium_button_bg.png | 3 + .../setup/medium_button_pressed_bg.png | 3 + selfdrive/assets/icons_mici/setup/reboot.png | 3 + .../assets/icons_mici/setup/red_warning.png | 3 + .../icons_mici/setup/reset/small_button.png | 3 + .../setup/reset/small_button_pressed.png | 3 + .../icons_mici/setup/reset/wide_button.png | 3 + .../setup/reset/wide_button_pressed.png | 3 + selfdrive/assets/icons_mici/setup/restore.png | 3 + .../setup/scroll_down_indicator.png | 3 + .../assets/icons_mici/setup/small_button.png | 3 + .../setup/small_button_disabled.png | 3 + .../icons_mici/setup/small_button_pressed.png | 3 + .../icons_mici/setup/small_red_pill.png | 3 + .../setup/small_red_pill_pressed.png | 3 + .../setup/small_slider/slider_arrow.png | 3 + .../small_slider/slider_arrow_outline.png | 3 + .../setup/small_slider/slider_bg.png | 3 + .../setup/small_slider/slider_bg_larger.png | 3 + .../slider_black_rounded_rectangle.png | 3 + .../slider_green_rounded_rectangle.png | 3 + .../setup/small_slider/slider_red_circle.png | 3 + .../icons_mici/setup/smaller_button.png | 3 + .../setup/smaller_button_disabled.png | 3 + .../setup/smaller_button_pressed.png | 3 + selfdrive/assets/icons_mici/setup/warning.png | 3 + .../assets/icons_mici/setup/widish_button.png | 3 + .../setup/widish_button_disabled.png | 3 + .../setup/widish_button_pressed.png | 3 + .../assets/icons_mici/turn_intent_left.png | 3 + .../assets/icons_mici/turn_intent_right.png | 3 + selfdrive/assets/icons_mici/wheel.png | 3 + .../assets/icons_mici/wheel_critical.png | 3 + selfdrive/assets/offroad/mici_fcc.html | 16 + selfdrive/assets/sounds/disengage.wav | 4 +- selfdrive/assets/sounds/disengage_tizi.wav | 3 + selfdrive/assets/sounds/engage.wav | 4 +- selfdrive/assets/sounds/engage_tizi.wav | 3 + selfdrive/ui/SConscript | 6 +- selfdrive/ui/installer/installer.cc | 62 +- selfdrive/ui/layouts/home.py | 2 - selfdrive/ui/layouts/settings/common.py | 5 + selfdrive/ui/layouts/settings/developer.py | 2 +- selfdrive/ui/layouts/settings/device.py | 2 +- selfdrive/ui/layouts/settings/software.py | 2 +- selfdrive/ui/layouts/settings/toggles.py | 2 +- selfdrive/ui/mici/layouts/__init__.py | 0 selfdrive/ui/mici/layouts/home.py | 272 +++++++ selfdrive/ui/mici/layouts/main.py | 149 ++++ selfdrive/ui/mici/layouts/offroad_alerts.py | 307 ++++++++ selfdrive/ui/mici/layouts/onboarding.py | 552 +++++++++++++ .../ui/mici/layouts/settings/developer.py | 151 ++++ selfdrive/ui/mici/layouts/settings/device.py | 378 +++++++++ .../ui/mici/layouts/settings/firehose.py | 223 ++++++ selfdrive/ui/mici/layouts/settings/network.py | 558 ++++++++++++++ .../ui/mici/layouts/settings/settings.py | 113 +++ selfdrive/ui/mici/layouts/settings/toggles.py | 95 +++ selfdrive/ui/mici/onroad/__init__.py | 12 + selfdrive/ui/mici/onroad/alert_renderer.py | 361 +++++++++ .../ui/mici/onroad/augmented_road_view.py | 358 +++++++++ selfdrive/ui/mici/onroad/cameraview.py | 390 ++++++++++ selfdrive/ui/mici/onroad/confidence_ball.py | 78 ++ .../ui/mici/onroad/driver_camera_dialog.py | 241 ++++++ selfdrive/ui/mici/onroad/driver_state.py | 227 ++++++ selfdrive/ui/mici/onroad/hud_renderer.py | 287 +++++++ selfdrive/ui/mici/onroad/model_renderer.py | 479 ++++++++++++ selfdrive/ui/mici/onroad/torque_bar.py | 253 ++++++ selfdrive/ui/mici/widgets/button.py | 375 +++++++++ selfdrive/ui/mici/widgets/dialog.py | 395 ++++++++++ selfdrive/ui/mici/widgets/pairing_dialog.py | 116 +++ selfdrive/ui/mici/widgets/side_button.py | 31 + selfdrive/ui/soundd.py | 12 +- selfdrive/ui/tests/profile_onroad.py | 9 +- .../ui/tests/test_ui/raylib_screenshots.py | 1 + selfdrive/ui/ui.py | 6 +- selfdrive/ui/ui_state.py | 21 +- system/ui/README.md | 1 + system/ui/lib/application.py | 32 +- system/ui/lib/scroll_panel2.py | 219 ++++++ system/ui/lib/shader_polygon.py | 4 +- system/ui/lib/text_measure.py | 3 +- system/ui/lib/wifi_manager.py | 5 +- system/ui/lib/wrap_text.py | 17 +- system/ui/mici_reset.py | 160 ++++ system/ui/mici_setup.py | 727 ++++++++++++++++++ system/ui/mici_updater.py | 200 +++++ system/ui/reset.py | 135 +--- system/ui/setup.py | 450 +---------- system/ui/spinner.py | 19 +- system/ui/text.py | 23 +- system/ui/tici_reset.py | 136 ++++ system/ui/tici_setup.py | 451 +++++++++++ system/ui/tici_updater.py | 173 +++++ system/ui/updater.py | 172 +---- system/ui/widgets/__init__.py | 218 +++++- system/ui/widgets/button.py | 119 ++- system/ui/widgets/confirm_dialog.py | 2 +- system/ui/widgets/label.py | 511 +++++++++++- system/ui/widgets/mici_keyboard.py | 388 ++++++++++ system/ui/widgets/network.py | 2 +- system/ui/widgets/option_dialog.py | 2 +- system/ui/widgets/scroller.py | 203 ++++- system/ui/widgets/scroller_tici.py | 90 +++ system/ui/widgets/slider.py | 183 +++++ 239 files changed, 10876 insertions(+), 839 deletions(-) create mode 100644 selfdrive/assets/icons/eyes_crossed.png create mode 100644 selfdrive/assets/icons/eyes_open.png create mode 100644 selfdrive/assets/icons_mici/buttons/button_circle.png create mode 100644 selfdrive/assets/icons_mici/buttons/button_circle_disabled.png create mode 100644 selfdrive/assets/icons_mici/buttons/button_circle_hover.png create mode 100644 selfdrive/assets/icons_mici/buttons/button_circle_pressed.png create mode 100644 selfdrive/assets/icons_mici/buttons/button_circle_red.png create mode 100644 selfdrive/assets/icons_mici/buttons/button_circle_red_hover.png create mode 100644 selfdrive/assets/icons_mici/buttons/button_circle_red_pressed.png create mode 100644 selfdrive/assets/icons_mici/buttons/button_rectangle.png create mode 100644 selfdrive/assets/icons_mici/buttons/button_rectangle_disabled.png create mode 100644 selfdrive/assets/icons_mici/buttons/button_rectangle_hover.png create mode 100644 selfdrive/assets/icons_mici/buttons/button_rectangle_pressed.png create mode 100644 selfdrive/assets/icons_mici/buttons/button_side_back.png create mode 100644 selfdrive/assets/icons_mici/buttons/button_side_back_pressed.png create mode 100644 selfdrive/assets/icons_mici/buttons/button_side_check.png create mode 100644 selfdrive/assets/icons_mici/buttons/button_side_check_pressed.png create mode 100644 selfdrive/assets/icons_mici/buttons/button_side_home.png create mode 100644 selfdrive/assets/icons_mici/buttons/slider_bg.png create mode 100644 selfdrive/assets/icons_mici/buttons/toggle_dot_disabled.png create mode 100644 selfdrive/assets/icons_mici/buttons/toggle_dot_enabled.png create mode 100644 selfdrive/assets/icons_mici/buttons/toggle_dot_orange.png create mode 100644 selfdrive/assets/icons_mici/buttons/toggle_pill_disabled.png create mode 100644 selfdrive/assets/icons_mici/buttons/toggle_pill_enabled.png create mode 100644 selfdrive/assets/icons_mici/exclamation_point.png create mode 100644 selfdrive/assets/icons_mici/experimental_mode.png create mode 100644 selfdrive/assets/icons_mici/eye.png create mode 100644 selfdrive/assets/icons_mici/eye_crossed.png create mode 100644 selfdrive/assets/icons_mici/microphone.png create mode 100644 selfdrive/assets/icons_mici/notifications/blue_large.png create mode 100644 selfdrive/assets/icons_mici/notifications/blue_small.png create mode 100644 selfdrive/assets/icons_mici/notifications/icons/cable.png create mode 100644 selfdrive/assets/icons_mici/notifications/icons/camera.png create mode 100644 selfdrive/assets/icons_mici/notifications/icons/close.png create mode 100644 selfdrive/assets/icons_mici/notifications/icons/critical.png create mode 100644 selfdrive/assets/icons_mici/notifications/icons/eye.png create mode 100644 selfdrive/assets/icons_mici/notifications/icons/fan.png create mode 100644 selfdrive/assets/icons_mici/notifications/icons/temperature.png create mode 100644 selfdrive/assets/icons_mici/notifications/icons/wheel.png create mode 100644 selfdrive/assets/icons_mici/notifications/normal_large.png create mode 100644 selfdrive/assets/icons_mici/notifications/normal_small.png create mode 100644 selfdrive/assets/icons_mici/notifications/orange_large.png create mode 100644 selfdrive/assets/icons_mici/notifications/orange_small.png create mode 100644 selfdrive/assets/icons_mici/notifications/red_large.png create mode 100644 selfdrive/assets/icons_mici/notifications/red_small.png create mode 100644 selfdrive/assets/icons_mici/offroad_alerts/big_alert.png create mode 100644 selfdrive/assets/icons_mici/offroad_alerts/big_alert_pressed.png create mode 100644 selfdrive/assets/icons_mici/offroad_alerts/green_wheel.png create mode 100644 selfdrive/assets/icons_mici/offroad_alerts/medium_alert.png create mode 100644 selfdrive/assets/icons_mici/offroad_alerts/medium_alert_pressed.png create mode 100644 selfdrive/assets/icons_mici/offroad_alerts/orange_warning.png create mode 100644 selfdrive/assets/icons_mici/offroad_alerts/red_warning.png create mode 100644 selfdrive/assets/icons_mici/offroad_alerts/small_alert.png create mode 100644 selfdrive/assets/icons_mici/offroad_alerts/small_alert_pressed.png create mode 100644 selfdrive/assets/icons_mici/onroad/blind_spot_left.png create mode 100644 selfdrive/assets/icons_mici/onroad/blind_spot_right.png create mode 100644 selfdrive/assets/icons_mici/onroad/bookmark.png create mode 100644 selfdrive/assets/icons_mici/onroad/driver_monitoring/dm_background.png create mode 100644 selfdrive/assets/icons_mici/onroad/driver_monitoring/dm_center.png create mode 100644 selfdrive/assets/icons_mici/onroad/driver_monitoring/dm_cone.png create mode 100644 selfdrive/assets/icons_mici/onroad/driver_monitoring/dm_person.png create mode 100644 selfdrive/assets/icons_mici/onroad/eye_fill.png create mode 100644 selfdrive/assets/icons_mici/onroad/eye_orange.png create mode 100644 selfdrive/assets/icons_mici/onroad/glasses.png create mode 100644 selfdrive/assets/icons_mici/onroad/onroad_fade.png create mode 100644 selfdrive/assets/icons_mici/onroad/sunglasses.png create mode 100644 selfdrive/assets/icons_mici/onroad/turn_signal_left.png create mode 100644 selfdrive/assets/icons_mici/onroad/turn_signal_right.png create mode 100644 selfdrive/assets/icons_mici/settings.png create mode 100644 selfdrive/assets/icons_mici/settings/comma_icon.png create mode 100644 selfdrive/assets/icons_mici/settings/developer/adb.png create mode 100644 selfdrive/assets/icons_mici/settings/developer/debug_mode.png create mode 100644 selfdrive/assets/icons_mici/settings/developer/ssh.png create mode 100644 selfdrive/assets/icons_mici/settings/developer_icon.png create mode 100644 selfdrive/assets/icons_mici/settings/device/cameras.png create mode 100644 selfdrive/assets/icons_mici/settings/device/cancel.png create mode 100644 selfdrive/assets/icons_mici/settings/device/downloading.png create mode 100644 selfdrive/assets/icons_mici/settings/device/fcc_logo.png create mode 100644 selfdrive/assets/icons_mici/settings/device/info.png create mode 100644 selfdrive/assets/icons_mici/settings/device/language.png create mode 100644 selfdrive/assets/icons_mici/settings/device/lkas.png create mode 100644 selfdrive/assets/icons_mici/settings/device/pair.png create mode 100644 selfdrive/assets/icons_mici/settings/device/power.png create mode 100644 selfdrive/assets/icons_mici/settings/device/reboot.png create mode 100644 selfdrive/assets/icons_mici/settings/device/uninstall.png create mode 100644 selfdrive/assets/icons_mici/settings/device/up_to_date.png create mode 100644 selfdrive/assets/icons_mici/settings/device/update.png create mode 100644 selfdrive/assets/icons_mici/settings/device_icon.png create mode 100644 selfdrive/assets/icons_mici/settings/keyboard/back.png create mode 100644 selfdrive/assets/icons_mici/settings/keyboard/backspace.png create mode 100644 selfdrive/assets/icons_mici/settings/keyboard/caps_lock.png create mode 100644 selfdrive/assets/icons_mici/settings/keyboard/caps_lower.png create mode 100644 selfdrive/assets/icons_mici/settings/keyboard/caps_upper.png create mode 100644 selfdrive/assets/icons_mici/settings/keyboard/confirm.png create mode 100644 selfdrive/assets/icons_mici/settings/keyboard/keyboard_background.png create mode 100644 selfdrive/assets/icons_mici/settings/keyboard/space.png create mode 100644 selfdrive/assets/icons_mici/settings/manual_icon.png create mode 100644 selfdrive/assets/icons_mici/settings/network/cell_strength_full.png create mode 100644 selfdrive/assets/icons_mici/settings/network/cell_strength_high.png create mode 100644 selfdrive/assets/icons_mici/settings/network/cell_strength_low.png create mode 100644 selfdrive/assets/icons_mici/settings/network/cell_strength_medium.png create mode 100644 selfdrive/assets/icons_mici/settings/network/cell_strength_none.png create mode 100644 selfdrive/assets/icons_mici/settings/network/connect.png create mode 100644 selfdrive/assets/icons_mici/settings/network/connect_disabled.png create mode 100644 selfdrive/assets/icons_mici/settings/network/connect_pressed.png create mode 100644 selfdrive/assets/icons_mici/settings/network/forget_pill.png create mode 100644 selfdrive/assets/icons_mici/settings/network/forget_pill_pressed.png create mode 100644 selfdrive/assets/icons_mici/settings/network/new/connect_button.png create mode 100644 selfdrive/assets/icons_mici/settings/network/new/connect_button_pressed.png create mode 100644 selfdrive/assets/icons_mici/settings/network/new/forget_button.png create mode 100644 selfdrive/assets/icons_mici/settings/network/new/forget_button_pressed.png create mode 100644 selfdrive/assets/icons_mici/settings/network/new/full_connect_button.png create mode 100644 selfdrive/assets/icons_mici/settings/network/new/full_connect_button_pressed.png create mode 100644 selfdrive/assets/icons_mici/settings/network/new/lock.png create mode 100644 selfdrive/assets/icons_mici/settings/network/new/trash.png create mode 100644 selfdrive/assets/icons_mici/settings/network/new/wifi_selected.png create mode 100644 selfdrive/assets/icons_mici/settings/network/tethering.png create mode 100644 selfdrive/assets/icons_mici/settings/network/trash.png create mode 100644 selfdrive/assets/icons_mici/settings/network/wifi_strength_full.png create mode 100644 selfdrive/assets/icons_mici/settings/network/wifi_strength_low.png create mode 100644 selfdrive/assets/icons_mici/settings/network/wifi_strength_medium.png create mode 100644 selfdrive/assets/icons_mici/settings/network/wifi_strength_none.png create mode 100644 selfdrive/assets/icons_mici/settings/network/wifi_strength_slash.png create mode 100644 selfdrive/assets/icons_mici/settings/toggles_icon.png create mode 100644 selfdrive/assets/icons_mici/settings/vertical_scroll_indicator.png create mode 100644 selfdrive/assets/icons_mici/setup/arrow.png create mode 100644 selfdrive/assets/icons_mici/setup/back.png create mode 100644 selfdrive/assets/icons_mici/setup/back_new.png create mode 100644 selfdrive/assets/icons_mici/setup/green_button.png create mode 100644 selfdrive/assets/icons_mici/setup/green_button_pressed.png create mode 100644 selfdrive/assets/icons_mici/setup/green_car.png create mode 100644 selfdrive/assets/icons_mici/setup/green_dm.png create mode 100644 selfdrive/assets/icons_mici/setup/green_info.png create mode 100644 selfdrive/assets/icons_mici/setup/green_pedal.png create mode 100644 selfdrive/assets/icons_mici/setup/medium_button_bg.png create mode 100644 selfdrive/assets/icons_mici/setup/medium_button_pressed_bg.png create mode 100644 selfdrive/assets/icons_mici/setup/reboot.png create mode 100644 selfdrive/assets/icons_mici/setup/red_warning.png create mode 100644 selfdrive/assets/icons_mici/setup/reset/small_button.png create mode 100644 selfdrive/assets/icons_mici/setup/reset/small_button_pressed.png create mode 100644 selfdrive/assets/icons_mici/setup/reset/wide_button.png create mode 100644 selfdrive/assets/icons_mici/setup/reset/wide_button_pressed.png create mode 100644 selfdrive/assets/icons_mici/setup/restore.png create mode 100644 selfdrive/assets/icons_mici/setup/scroll_down_indicator.png create mode 100644 selfdrive/assets/icons_mici/setup/small_button.png create mode 100644 selfdrive/assets/icons_mici/setup/small_button_disabled.png create mode 100644 selfdrive/assets/icons_mici/setup/small_button_pressed.png create mode 100644 selfdrive/assets/icons_mici/setup/small_red_pill.png create mode 100644 selfdrive/assets/icons_mici/setup/small_red_pill_pressed.png create mode 100644 selfdrive/assets/icons_mici/setup/small_slider/slider_arrow.png create mode 100644 selfdrive/assets/icons_mici/setup/small_slider/slider_arrow_outline.png create mode 100644 selfdrive/assets/icons_mici/setup/small_slider/slider_bg.png create mode 100644 selfdrive/assets/icons_mici/setup/small_slider/slider_bg_larger.png create mode 100644 selfdrive/assets/icons_mici/setup/small_slider/slider_black_rounded_rectangle.png create mode 100644 selfdrive/assets/icons_mici/setup/small_slider/slider_green_rounded_rectangle.png create mode 100644 selfdrive/assets/icons_mici/setup/small_slider/slider_red_circle.png create mode 100644 selfdrive/assets/icons_mici/setup/smaller_button.png create mode 100644 selfdrive/assets/icons_mici/setup/smaller_button_disabled.png create mode 100644 selfdrive/assets/icons_mici/setup/smaller_button_pressed.png create mode 100644 selfdrive/assets/icons_mici/setup/warning.png create mode 100644 selfdrive/assets/icons_mici/setup/widish_button.png create mode 100644 selfdrive/assets/icons_mici/setup/widish_button_disabled.png create mode 100644 selfdrive/assets/icons_mici/setup/widish_button_pressed.png create mode 100644 selfdrive/assets/icons_mici/turn_intent_left.png create mode 100644 selfdrive/assets/icons_mici/turn_intent_right.png create mode 100644 selfdrive/assets/icons_mici/wheel.png create mode 100644 selfdrive/assets/icons_mici/wheel_critical.png create mode 100644 selfdrive/assets/offroad/mici_fcc.html create mode 100644 selfdrive/assets/sounds/disengage_tizi.wav create mode 100644 selfdrive/assets/sounds/engage_tizi.wav create mode 100644 selfdrive/ui/layouts/settings/common.py create mode 100644 selfdrive/ui/mici/layouts/__init__.py create mode 100644 selfdrive/ui/mici/layouts/home.py create mode 100644 selfdrive/ui/mici/layouts/main.py create mode 100644 selfdrive/ui/mici/layouts/offroad_alerts.py create mode 100644 selfdrive/ui/mici/layouts/onboarding.py create mode 100644 selfdrive/ui/mici/layouts/settings/developer.py create mode 100644 selfdrive/ui/mici/layouts/settings/device.py create mode 100644 selfdrive/ui/mici/layouts/settings/firehose.py create mode 100644 selfdrive/ui/mici/layouts/settings/network.py create mode 100644 selfdrive/ui/mici/layouts/settings/settings.py create mode 100644 selfdrive/ui/mici/layouts/settings/toggles.py create mode 100644 selfdrive/ui/mici/onroad/__init__.py create mode 100644 selfdrive/ui/mici/onroad/alert_renderer.py create mode 100644 selfdrive/ui/mici/onroad/augmented_road_view.py create mode 100644 selfdrive/ui/mici/onroad/cameraview.py create mode 100644 selfdrive/ui/mici/onroad/confidence_ball.py create mode 100644 selfdrive/ui/mici/onroad/driver_camera_dialog.py create mode 100644 selfdrive/ui/mici/onroad/driver_state.py create mode 100644 selfdrive/ui/mici/onroad/hud_renderer.py create mode 100644 selfdrive/ui/mici/onroad/model_renderer.py create mode 100644 selfdrive/ui/mici/onroad/torque_bar.py create mode 100644 selfdrive/ui/mici/widgets/button.py create mode 100644 selfdrive/ui/mici/widgets/dialog.py create mode 100644 selfdrive/ui/mici/widgets/pairing_dialog.py create mode 100644 selfdrive/ui/mici/widgets/side_button.py create mode 100644 system/ui/lib/scroll_panel2.py create mode 100755 system/ui/mici_reset.py create mode 100755 system/ui/mici_setup.py create mode 100755 system/ui/mici_updater.py create mode 100755 system/ui/tici_reset.py create mode 100755 system/ui/tici_setup.py create mode 100755 system/ui/tici_updater.py create mode 100644 system/ui/widgets/mici_keyboard.py create mode 100644 system/ui/widgets/scroller_tici.py create mode 100644 system/ui/widgets/slider.py diff --git a/selfdrive/assets/fonts/process.py b/selfdrive/assets/fonts/process.py index a0d01af14..ddc8b3a86 100755 --- a/selfdrive/assets/fonts/process.py +++ b/selfdrive/assets/fonts/process.py @@ -10,7 +10,7 @@ TRANSLATIONS_DIR = SELFDRIVE_DIR / "ui" / "translations" LANGUAGES_FILE = TRANSLATIONS_DIR / "languages.json" GLYPH_PADDING = 6 -EXTRA_CHARS = "–‑✓×°§•€£¥" +EXTRA_CHARS = "–‑✓×°§•X⚙✕◀▶✔⌫⇧␣○●↳çêüñ–‑✓×°§•€£¥" UNIFONT_LANGUAGES = {"ar", "th", "zh-CHT", "zh-CHS", "ko", "ja"} @@ -68,6 +68,10 @@ def _glyph_metrics(glyphs, rects, codepoints): def _write_bmfont(path: Path, font_size: int, face: str, atlas_name: str, line_height: int, base: int, atlas_size, entries): + # TODO: why doesn't raylib calculate these metrics correctly? + if line_height != font_size: + print("using font size for line height", atlas_name) + line_height = font_size lines = [ f"info face=\"{face}\" size=-{font_size} bold=0 italic=0 charset=\"\" unicode=1 stretchH=100 smooth=0 aa=1 padding=0,0,0,0 spacing=0,0 outline=0", f"common lineHeight={line_height} base={base} scaleW={atlas_size[0]} scaleH={atlas_size[1]} pages=1 packed=0 alphaChnl=0 redChnl=4 greenChnl=4 blueChnl=4", diff --git a/selfdrive/assets/icons/eyes_crossed.png b/selfdrive/assets/icons/eyes_crossed.png new file mode 100644 index 000000000..af2122cd9 --- /dev/null +++ b/selfdrive/assets/icons/eyes_crossed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4def42b5faffc6a8f747c210d24c3a1a8a7f82891738ff7f3317091e63326ba5 +size 1083 diff --git a/selfdrive/assets/icons/eyes_open.png b/selfdrive/assets/icons/eyes_open.png new file mode 100644 index 000000000..ad9afc3a3 --- /dev/null +++ b/selfdrive/assets/icons/eyes_open.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:52019b72834e478588114584820313af866d2d7a737591a166c413ccaab6acf5 +size 931 diff --git a/selfdrive/assets/icons_mici/buttons/button_circle.png b/selfdrive/assets/icons_mici/buttons/button_circle.png new file mode 100644 index 000000000..b6f4cc9d1 --- /dev/null +++ b/selfdrive/assets/icons_mici/buttons/button_circle.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f92e5f0b7fc50c3b64bd18ecee8a8d518017b5461104de76dee6feb0f4f0d70d +size 7496 diff --git a/selfdrive/assets/icons_mici/buttons/button_circle_disabled.png b/selfdrive/assets/icons_mici/buttons/button_circle_disabled.png new file mode 100644 index 000000000..d2104df4e --- /dev/null +++ b/selfdrive/assets/icons_mici/buttons/button_circle_disabled.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:947aa3beb7eff6afb44101daf0aeaae7b7f31961c273df00eec0ca8359233c56 +size 5175 diff --git a/selfdrive/assets/icons_mici/buttons/button_circle_hover.png b/selfdrive/assets/icons_mici/buttons/button_circle_hover.png new file mode 100644 index 000000000..5cae15210 --- /dev/null +++ b/selfdrive/assets/icons_mici/buttons/button_circle_hover.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:20024203288f144633014422e16119278477099f24fba5c155a804a1864a26b4 +size 7511 diff --git a/selfdrive/assets/icons_mici/buttons/button_circle_pressed.png b/selfdrive/assets/icons_mici/buttons/button_circle_pressed.png new file mode 100644 index 000000000..45027a372 --- /dev/null +++ b/selfdrive/assets/icons_mici/buttons/button_circle_pressed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d378fdbb9d683d5c94536ebf9c466146721b1f65859eb38667d5c2e9589e54c3 +size 12590 diff --git a/selfdrive/assets/icons_mici/buttons/button_circle_red.png b/selfdrive/assets/icons_mici/buttons/button_circle_red.png new file mode 100644 index 000000000..68ae400b1 --- /dev/null +++ b/selfdrive/assets/icons_mici/buttons/button_circle_red.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b48d8a191979f27dae8a336f99d944008e2536f698c58cffa5f3dddc17429b45 +size 11451 diff --git a/selfdrive/assets/icons_mici/buttons/button_circle_red_hover.png b/selfdrive/assets/icons_mici/buttons/button_circle_red_hover.png new file mode 100644 index 000000000..3696334d5 --- /dev/null +++ b/selfdrive/assets/icons_mici/buttons/button_circle_red_hover.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:279c1d8f95eb9f4a3058dff76b0f316ce9eef7bc8f4296936ad25fd08703ce13 +size 10380 diff --git a/selfdrive/assets/icons_mici/buttons/button_circle_red_pressed.png b/selfdrive/assets/icons_mici/buttons/button_circle_red_pressed.png new file mode 100644 index 000000000..08b2e318d --- /dev/null +++ b/selfdrive/assets/icons_mici/buttons/button_circle_red_pressed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:19d53ff0cb49ffc43507e5bac11583bc9b3037c2e2ed5e12b96bfd97d71e397f +size 26113 diff --git a/selfdrive/assets/icons_mici/buttons/button_rectangle.png b/selfdrive/assets/icons_mici/buttons/button_rectangle.png new file mode 100644 index 000000000..230c537d6 --- /dev/null +++ b/selfdrive/assets/icons_mici/buttons/button_rectangle.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ffb293236f5f8f7da44b5a3c4c0b72e86c4e1fdb04f89c94507af008ff7de139 +size 8210 diff --git a/selfdrive/assets/icons_mici/buttons/button_rectangle_disabled.png b/selfdrive/assets/icons_mici/buttons/button_rectangle_disabled.png new file mode 100644 index 000000000..76e75d542 --- /dev/null +++ b/selfdrive/assets/icons_mici/buttons/button_rectangle_disabled.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bda53863c9a46c50a1e2920a76c2d2f1fe4df8a94b8d2e26f5d83eef3a9c3bd3 +size 3627 diff --git a/selfdrive/assets/icons_mici/buttons/button_rectangle_hover.png b/selfdrive/assets/icons_mici/buttons/button_rectangle_hover.png new file mode 100644 index 000000000..a9fd28cc3 --- /dev/null +++ b/selfdrive/assets/icons_mici/buttons/button_rectangle_hover.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6b55e43c50e805ac5e8357e5943374ed02d756cefa3aaffb58c568a0b125c30b +size 7750 diff --git a/selfdrive/assets/icons_mici/buttons/button_rectangle_pressed.png b/selfdrive/assets/icons_mici/buttons/button_rectangle_pressed.png new file mode 100644 index 000000000..779c219fc --- /dev/null +++ b/selfdrive/assets/icons_mici/buttons/button_rectangle_pressed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5528e9c041b824f005bf1ef6e49b2dbbc4ba10f994b0726d2a17a4fbf8c80f55 +size 21379 diff --git a/selfdrive/assets/icons_mici/buttons/button_side_back.png b/selfdrive/assets/icons_mici/buttons/button_side_back.png new file mode 100644 index 000000000..3d648d34f --- /dev/null +++ b/selfdrive/assets/icons_mici/buttons/button_side_back.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9df44871e9f5fa910622b0b92205b92a54d137dbdc3827b92e8622d85ff2e08e +size 5189 diff --git a/selfdrive/assets/icons_mici/buttons/button_side_back_pressed.png b/selfdrive/assets/icons_mici/buttons/button_side_back_pressed.png new file mode 100644 index 000000000..e431cb0c7 --- /dev/null +++ b/selfdrive/assets/icons_mici/buttons/button_side_back_pressed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:013b368b38b17d9b2ef6aaf0f498f672deed95888084b7287f42bdfba617cbb6 +size 10142 diff --git a/selfdrive/assets/icons_mici/buttons/button_side_check.png b/selfdrive/assets/icons_mici/buttons/button_side_check.png new file mode 100644 index 000000000..820b23606 --- /dev/null +++ b/selfdrive/assets/icons_mici/buttons/button_side_check.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8fd563eec78d5ce4a8204c2f596789e1090cb3e26a35b4ffeacee4ab61968538 +size 8303 diff --git a/selfdrive/assets/icons_mici/buttons/button_side_check_pressed.png b/selfdrive/assets/icons_mici/buttons/button_side_check_pressed.png new file mode 100644 index 000000000..6c38508af --- /dev/null +++ b/selfdrive/assets/icons_mici/buttons/button_side_check_pressed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0be8d5eddcd9f87acbf1daccf446be6218522120f64aee1ee0a3c0b31560f076 +size 15761 diff --git a/selfdrive/assets/icons_mici/buttons/button_side_home.png b/selfdrive/assets/icons_mici/buttons/button_side_home.png new file mode 100644 index 000000000..99c5ea650 --- /dev/null +++ b/selfdrive/assets/icons_mici/buttons/button_side_home.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3ec30f6ba49e7a7bc89e8369800ad71c8b57950bbf6b3f169fa944626a3ded59 +size 5258 diff --git a/selfdrive/assets/icons_mici/buttons/slider_bg.png b/selfdrive/assets/icons_mici/buttons/slider_bg.png new file mode 100644 index 000000000..9164f74ba --- /dev/null +++ b/selfdrive/assets/icons_mici/buttons/slider_bg.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1ca620a05e9e69351b9bbcfcf021dae11fde26be50d7f1a39257d319f6303616 +size 9779 diff --git a/selfdrive/assets/icons_mici/buttons/toggle_dot_disabled.png b/selfdrive/assets/icons_mici/buttons/toggle_dot_disabled.png new file mode 100644 index 000000000..0e21bc1b5 --- /dev/null +++ b/selfdrive/assets/icons_mici/buttons/toggle_dot_disabled.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:613af9ed79bb26c60fbd19c094214f0881736c0e293f6d000b530cde0478a273 +size 2470 diff --git a/selfdrive/assets/icons_mici/buttons/toggle_dot_enabled.png b/selfdrive/assets/icons_mici/buttons/toggle_dot_enabled.png new file mode 100644 index 000000000..5bb4d778f --- /dev/null +++ b/selfdrive/assets/icons_mici/buttons/toggle_dot_enabled.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:532bf0e8535e3f9bc13af13029a27d6c14ae788d52224b6c65623334f62fada0 +size 6048 diff --git a/selfdrive/assets/icons_mici/buttons/toggle_dot_orange.png b/selfdrive/assets/icons_mici/buttons/toggle_dot_orange.png new file mode 100644 index 000000000..bf8559ec8 --- /dev/null +++ b/selfdrive/assets/icons_mici/buttons/toggle_dot_orange.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1ca418dab8eab77569e3cc446deccdc5b468d79159711e6629d704eb531009d9 +size 6191 diff --git a/selfdrive/assets/icons_mici/buttons/toggle_pill_disabled.png b/selfdrive/assets/icons_mici/buttons/toggle_pill_disabled.png new file mode 100644 index 000000000..555c16e09 --- /dev/null +++ b/selfdrive/assets/icons_mici/buttons/toggle_pill_disabled.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7891a628bd9cedc1097114e89fcec4a50a88021c7d6c63f1329d087be9e1783e +size 3065 diff --git a/selfdrive/assets/icons_mici/buttons/toggle_pill_enabled.png b/selfdrive/assets/icons_mici/buttons/toggle_pill_enabled.png new file mode 100644 index 000000000..d95039da9 --- /dev/null +++ b/selfdrive/assets/icons_mici/buttons/toggle_pill_enabled.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ad31da78544edd18d0ac154670f22d1cd1ac57f50576002f04701d22c59502a8 +size 8257 diff --git a/selfdrive/assets/icons_mici/exclamation_point.png b/selfdrive/assets/icons_mici/exclamation_point.png new file mode 100644 index 000000000..246fc015e --- /dev/null +++ b/selfdrive/assets/icons_mici/exclamation_point.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b77579c099c688d1a27f356197fba9c2c8efcf4d391af580b4b29f0e70587919 +size 2086 diff --git a/selfdrive/assets/icons_mici/experimental_mode.png b/selfdrive/assets/icons_mici/experimental_mode.png new file mode 100644 index 000000000..e0138bfd6 --- /dev/null +++ b/selfdrive/assets/icons_mici/experimental_mode.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eb42b8d6259238beb26f286dc28fb2dc8d91b00fec1f7a7655296b5769439a15 +size 15690 diff --git a/selfdrive/assets/icons_mici/eye.png b/selfdrive/assets/icons_mici/eye.png new file mode 100644 index 000000000..db2953b69 --- /dev/null +++ b/selfdrive/assets/icons_mici/eye.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c9c7f03c2784eac6882eb59d5f1c547008c12f19a861d04e9ca3549edb41218c +size 1988 diff --git a/selfdrive/assets/icons_mici/eye_crossed.png b/selfdrive/assets/icons_mici/eye_crossed.png new file mode 100644 index 000000000..11197fcf5 --- /dev/null +++ b/selfdrive/assets/icons_mici/eye_crossed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5f54bdb8dbb94682ff474174b8e3c605dba867f1b6613a3efcc5fbbedc462a95 +size 1811 diff --git a/selfdrive/assets/icons_mici/microphone.png b/selfdrive/assets/icons_mici/microphone.png new file mode 100644 index 000000000..9718a6b13 --- /dev/null +++ b/selfdrive/assets/icons_mici/microphone.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:17b6fe530598cbad34bcf31d4f21f929b792aacedef51b3ffef1941c86017811 +size 7331 diff --git a/selfdrive/assets/icons_mici/notifications/blue_large.png b/selfdrive/assets/icons_mici/notifications/blue_large.png new file mode 100644 index 000000000..e4aa33a13 --- /dev/null +++ b/selfdrive/assets/icons_mici/notifications/blue_large.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2566b38173e6048f54ef62dd68a158b07747638f74f2724406cfef02ae38022b +size 14052 diff --git a/selfdrive/assets/icons_mici/notifications/blue_small.png b/selfdrive/assets/icons_mici/notifications/blue_small.png new file mode 100644 index 000000000..500f48e36 --- /dev/null +++ b/selfdrive/assets/icons_mici/notifications/blue_small.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7020a7ec5a1a31320a0cc2a381a79ed0d650bc220a9c60ae334c646868e12295 +size 10662 diff --git a/selfdrive/assets/icons_mici/notifications/icons/cable.png b/selfdrive/assets/icons_mici/notifications/icons/cable.png new file mode 100644 index 000000000..72b64f073 --- /dev/null +++ b/selfdrive/assets/icons_mici/notifications/icons/cable.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:028bb5611f41b0da7944f6bed42962cd894a57706a9940f124de7488a61b9b60 +size 1171 diff --git a/selfdrive/assets/icons_mici/notifications/icons/camera.png b/selfdrive/assets/icons_mici/notifications/icons/camera.png new file mode 100644 index 000000000..fbe70ee82 --- /dev/null +++ b/selfdrive/assets/icons_mici/notifications/icons/camera.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8667594a61ed4680b55ef981085cb84db7d1c86dd28ed998a7e8441f03b09193 +size 2000 diff --git a/selfdrive/assets/icons_mici/notifications/icons/close.png b/selfdrive/assets/icons_mici/notifications/icons/close.png new file mode 100644 index 000000000..e0f061a01 --- /dev/null +++ b/selfdrive/assets/icons_mici/notifications/icons/close.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:064cf235bfc30a08863a29ec7f94aa1f8cf7b6b68ee5eaad0a0c48740e9a0ce1 +size 2371 diff --git a/selfdrive/assets/icons_mici/notifications/icons/critical.png b/selfdrive/assets/icons_mici/notifications/icons/critical.png new file mode 100644 index 000000000..8acab1854 --- /dev/null +++ b/selfdrive/assets/icons_mici/notifications/icons/critical.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:77d7945ba3af1d0ecb4c52600fcfee80f9f8069f34dea4bce95fd0495ac6f80c +size 2596 diff --git a/selfdrive/assets/icons_mici/notifications/icons/eye.png b/selfdrive/assets/icons_mici/notifications/icons/eye.png new file mode 100644 index 000000000..11197fcf5 --- /dev/null +++ b/selfdrive/assets/icons_mici/notifications/icons/eye.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5f54bdb8dbb94682ff474174b8e3c605dba867f1b6613a3efcc5fbbedc462a95 +size 1811 diff --git a/selfdrive/assets/icons_mici/notifications/icons/fan.png b/selfdrive/assets/icons_mici/notifications/icons/fan.png new file mode 100644 index 000000000..017da6c8c --- /dev/null +++ b/selfdrive/assets/icons_mici/notifications/icons/fan.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:40ab18b92dda78353031c5df7b57db64a4a7f99820f696c4a4efe92cfa690109 +size 2465 diff --git a/selfdrive/assets/icons_mici/notifications/icons/temperature.png b/selfdrive/assets/icons_mici/notifications/icons/temperature.png new file mode 100644 index 000000000..09d0d798d --- /dev/null +++ b/selfdrive/assets/icons_mici/notifications/icons/temperature.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3b76a778921377508da133ad105247b238244fae86f9c0791f3ad05dd69802a6 +size 1975 diff --git a/selfdrive/assets/icons_mici/notifications/icons/wheel.png b/selfdrive/assets/icons_mici/notifications/icons/wheel.png new file mode 100644 index 000000000..ec4741f7f --- /dev/null +++ b/selfdrive/assets/icons_mici/notifications/icons/wheel.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:23424ed80a70ebfedb99f14e286a08dcf59167988683b81fa8a56b06e9b2051e +size 2638 diff --git a/selfdrive/assets/icons_mici/notifications/normal_large.png b/selfdrive/assets/icons_mici/notifications/normal_large.png new file mode 100644 index 000000000..df2598403 --- /dev/null +++ b/selfdrive/assets/icons_mici/notifications/normal_large.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b6aa200bbb3381eb0d6a72c412227cd99c8bde5e843f705b218c09ee98576804 +size 9796 diff --git a/selfdrive/assets/icons_mici/notifications/normal_small.png b/selfdrive/assets/icons_mici/notifications/normal_small.png new file mode 100644 index 000000000..f96de2e30 --- /dev/null +++ b/selfdrive/assets/icons_mici/notifications/normal_small.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0486257b2e7735ad68c2bde66eb7bc710862c989feef856f06b0bf5d5231e7db +size 7369 diff --git a/selfdrive/assets/icons_mici/notifications/orange_large.png b/selfdrive/assets/icons_mici/notifications/orange_large.png new file mode 100644 index 000000000..62f6dac63 --- /dev/null +++ b/selfdrive/assets/icons_mici/notifications/orange_large.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:32e3d36a95215cff4fdbe4af01c86408e647ff57dcc8724b517fda453b4b01e0 +size 15069 diff --git a/selfdrive/assets/icons_mici/notifications/orange_small.png b/selfdrive/assets/icons_mici/notifications/orange_small.png new file mode 100644 index 000000000..31fcf11a6 --- /dev/null +++ b/selfdrive/assets/icons_mici/notifications/orange_small.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0f754f98cd3f7104f3567bf02c4972277a8f5740757e245a2b3a60f9d1f61506 +size 11308 diff --git a/selfdrive/assets/icons_mici/notifications/red_large.png b/selfdrive/assets/icons_mici/notifications/red_large.png new file mode 100644 index 000000000..81cd5566f --- /dev/null +++ b/selfdrive/assets/icons_mici/notifications/red_large.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7f820799a20667a265d5ed9e0ebc6a46a6e898935f2fa920b66d001347b88704 +size 13410 diff --git a/selfdrive/assets/icons_mici/notifications/red_small.png b/selfdrive/assets/icons_mici/notifications/red_small.png new file mode 100644 index 000000000..00b358c81 --- /dev/null +++ b/selfdrive/assets/icons_mici/notifications/red_small.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0fb44ce698c02929ca0c4360cc493ad18a99bd8f5d75586a422961e6f17d1371 +size 10169 diff --git a/selfdrive/assets/icons_mici/offroad_alerts/big_alert.png b/selfdrive/assets/icons_mici/offroad_alerts/big_alert.png new file mode 100644 index 000000000..142367d0e --- /dev/null +++ b/selfdrive/assets/icons_mici/offroad_alerts/big_alert.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aeee7f049879caff52320fab5f286cf3fd6a52c820cd8e150ff242e53a14176f +size 14774 diff --git a/selfdrive/assets/icons_mici/offroad_alerts/big_alert_pressed.png b/selfdrive/assets/icons_mici/offroad_alerts/big_alert_pressed.png new file mode 100644 index 000000000..2ff01024d --- /dev/null +++ b/selfdrive/assets/icons_mici/offroad_alerts/big_alert_pressed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0b59ddada9c9e0e7972ead27396ebe6c10fd2352687b18ff6b476f61f74d80bf +size 46106 diff --git a/selfdrive/assets/icons_mici/offroad_alerts/green_wheel.png b/selfdrive/assets/icons_mici/offroad_alerts/green_wheel.png new file mode 100644 index 000000000..6a8351f6e --- /dev/null +++ b/selfdrive/assets/icons_mici/offroad_alerts/green_wheel.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:05f3626e790622a4ad90e982c4aacb612d0785a752339352a3187addf763e2e9 +size 13288 diff --git a/selfdrive/assets/icons_mici/offroad_alerts/medium_alert.png b/selfdrive/assets/icons_mici/offroad_alerts/medium_alert.png new file mode 100644 index 000000000..91a7b43c5 --- /dev/null +++ b/selfdrive/assets/icons_mici/offroad_alerts/medium_alert.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:46d08a8a08b42d466ff45d8ad6d2578e345dcbb1c06c126ad361873d9d35eaec +size 12877 diff --git a/selfdrive/assets/icons_mici/offroad_alerts/medium_alert_pressed.png b/selfdrive/assets/icons_mici/offroad_alerts/medium_alert_pressed.png new file mode 100644 index 000000000..09ca2d08d --- /dev/null +++ b/selfdrive/assets/icons_mici/offroad_alerts/medium_alert_pressed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:42ea275e5fe0a8a0e2fddb5a4a8487806fb22850115ea3646ee8f213d5fd6bb6 +size 37202 diff --git a/selfdrive/assets/icons_mici/offroad_alerts/orange_warning.png b/selfdrive/assets/icons_mici/offroad_alerts/orange_warning.png new file mode 100644 index 000000000..13af475c6 --- /dev/null +++ b/selfdrive/assets/icons_mici/offroad_alerts/orange_warning.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a877882a8dccb884bd35918f9f9b427a724a59e90a638e54f6fd5d0680ad173c +size 12137 diff --git a/selfdrive/assets/icons_mici/offroad_alerts/red_warning.png b/selfdrive/assets/icons_mici/offroad_alerts/red_warning.png new file mode 100644 index 000000000..83c3595b2 --- /dev/null +++ b/selfdrive/assets/icons_mici/offroad_alerts/red_warning.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ba944b208abed9b8b9752adb8017bd29cd2e98c89fb07ee5d0a595185c7564a5 +size 11898 diff --git a/selfdrive/assets/icons_mici/offroad_alerts/small_alert.png b/selfdrive/assets/icons_mici/offroad_alerts/small_alert.png new file mode 100644 index 000000000..0a50b6a1c --- /dev/null +++ b/selfdrive/assets/icons_mici/offroad_alerts/small_alert.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cfbf38672a893fd1d8fadf942354d2511e2436814a9af0e5c188cbb427fc6c70 +size 12281 diff --git a/selfdrive/assets/icons_mici/offroad_alerts/small_alert_pressed.png b/selfdrive/assets/icons_mici/offroad_alerts/small_alert_pressed.png new file mode 100644 index 000000000..865355ef0 --- /dev/null +++ b/selfdrive/assets/icons_mici/offroad_alerts/small_alert_pressed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a7102850bcfb075a285041cecb546559374a905403ab3b9814fa6097d2d822dd +size 34680 diff --git a/selfdrive/assets/icons_mici/onroad/blind_spot_left.png b/selfdrive/assets/icons_mici/onroad/blind_spot_left.png new file mode 100644 index 000000000..5d3b1e5d7 --- /dev/null +++ b/selfdrive/assets/icons_mici/onroad/blind_spot_left.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a23743d21bc8160e013625210654a55634e4ed58e60057b70e08761bac1c3680 +size 40406 diff --git a/selfdrive/assets/icons_mici/onroad/blind_spot_right.png b/selfdrive/assets/icons_mici/onroad/blind_spot_right.png new file mode 100644 index 000000000..67216078d --- /dev/null +++ b/selfdrive/assets/icons_mici/onroad/blind_spot_right.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:acbfa3e38f0b9f422f5c1335ce20013852df2892b813db176a51918adc83ad58 +size 40979 diff --git a/selfdrive/assets/icons_mici/onroad/bookmark.png b/selfdrive/assets/icons_mici/onroad/bookmark.png new file mode 100644 index 000000000..207182276 --- /dev/null +++ b/selfdrive/assets/icons_mici/onroad/bookmark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e0d00d743b01c49c2b739127e9916a229caf8c48346d6d168863b080ddcaa409 +size 11124 diff --git a/selfdrive/assets/icons_mici/onroad/driver_monitoring/dm_background.png b/selfdrive/assets/icons_mici/onroad/driver_monitoring/dm_background.png new file mode 100644 index 000000000..4d83ed5cd --- /dev/null +++ b/selfdrive/assets/icons_mici/onroad/driver_monitoring/dm_background.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f27352a18194a1c819e9eaea89cfc11d2964402df0a28efa3ba60ae2d972fe67 +size 13108 diff --git a/selfdrive/assets/icons_mici/onroad/driver_monitoring/dm_center.png b/selfdrive/assets/icons_mici/onroad/driver_monitoring/dm_center.png new file mode 100644 index 000000000..a8a68b372 --- /dev/null +++ b/selfdrive/assets/icons_mici/onroad/driver_monitoring/dm_center.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b5aee9f6cec03f1967014cd2ea2a23982b262e7d86dadca602ecfa8875b38101 +size 5875 diff --git a/selfdrive/assets/icons_mici/onroad/driver_monitoring/dm_cone.png b/selfdrive/assets/icons_mici/onroad/driver_monitoring/dm_cone.png new file mode 100644 index 000000000..ec2f94899 --- /dev/null +++ b/selfdrive/assets/icons_mici/onroad/driver_monitoring/dm_cone.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:26b3660dbd1e60b0ba98914afa7cb3a67151bb6990d218f55c901f243e38ff3e +size 3631 diff --git a/selfdrive/assets/icons_mici/onroad/driver_monitoring/dm_person.png b/selfdrive/assets/icons_mici/onroad/driver_monitoring/dm_person.png new file mode 100644 index 000000000..7aa7f0542 --- /dev/null +++ b/selfdrive/assets/icons_mici/onroad/driver_monitoring/dm_person.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:25d66e42a28a3367eb40724d28652889089aa762438b475645269e0319c46009 +size 1431 diff --git a/selfdrive/assets/icons_mici/onroad/eye_fill.png b/selfdrive/assets/icons_mici/onroad/eye_fill.png new file mode 100644 index 000000000..8f0e8ebfb --- /dev/null +++ b/selfdrive/assets/icons_mici/onroad/eye_fill.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:51af75afbaf30abeaae1c99c7ad3e25cf5d5c90a2d6c799aad353b3302384b0a +size 4829 diff --git a/selfdrive/assets/icons_mici/onroad/eye_orange.png b/selfdrive/assets/icons_mici/onroad/eye_orange.png new file mode 100644 index 000000000..b61b9b063 --- /dev/null +++ b/selfdrive/assets/icons_mici/onroad/eye_orange.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:88b2ecf3a9834d2b156bb632ec2090d7dc112e8ab61711ba645c03489d1c457f +size 29157 diff --git a/selfdrive/assets/icons_mici/onroad/glasses.png b/selfdrive/assets/icons_mici/onroad/glasses.png new file mode 100644 index 000000000..1ac4442f4 --- /dev/null +++ b/selfdrive/assets/icons_mici/onroad/glasses.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:28c95c8970648d40b35b94724936a9ab7a6f4cbca367a40f01b86f9abedc70e5 +size 1587 diff --git a/selfdrive/assets/icons_mici/onroad/onroad_fade.png b/selfdrive/assets/icons_mici/onroad/onroad_fade.png new file mode 100644 index 000000000..bc12e57e1 --- /dev/null +++ b/selfdrive/assets/icons_mici/onroad/onroad_fade.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d2a2cb4db429467783d7f721ffbed7838551e4aabf32771e73759c87b4a67bca +size 28880 diff --git a/selfdrive/assets/icons_mici/onroad/sunglasses.png b/selfdrive/assets/icons_mici/onroad/sunglasses.png new file mode 100644 index 000000000..15e502d61 --- /dev/null +++ b/selfdrive/assets/icons_mici/onroad/sunglasses.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4b520b8a00ca245f1dcccca4ddbf1b1b6f8da9fb8b6ac9ea351e735db61641e6 +size 1006 diff --git a/selfdrive/assets/icons_mici/onroad/turn_signal_left.png b/selfdrive/assets/icons_mici/onroad/turn_signal_left.png new file mode 100644 index 000000000..48f52ff9c --- /dev/null +++ b/selfdrive/assets/icons_mici/onroad/turn_signal_left.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0e845a211cf5d03f781efdd6eec4f8106e8dd85799ea59b51834a9099b479141 +size 30348 diff --git a/selfdrive/assets/icons_mici/onroad/turn_signal_right.png b/selfdrive/assets/icons_mici/onroad/turn_signal_right.png new file mode 100644 index 000000000..87ca979fb --- /dev/null +++ b/selfdrive/assets/icons_mici/onroad/turn_signal_right.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:009005539f14acc29a4f5510b4e9531d2ba3667133644f6e0069c12b08ba0fd9 +size 35370 diff --git a/selfdrive/assets/icons_mici/settings.png b/selfdrive/assets/icons_mici/settings.png new file mode 100644 index 000000000..e668ed1fe --- /dev/null +++ b/selfdrive/assets/icons_mici/settings.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:38a52171bdc6feb3ddfd2d9f9e59db3dabd09fa0aafbc9f81137c59bd03b7c26 +size 2321 diff --git a/selfdrive/assets/icons_mici/settings/comma_icon.png b/selfdrive/assets/icons_mici/settings/comma_icon.png new file mode 100644 index 000000000..72a7c8c8f --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/comma_icon.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:10f469a6f5d25d9e2b0b1aae51b4fbd06d2c7b8417613bb321c2a30bb7298dab +size 1392 diff --git a/selfdrive/assets/icons_mici/settings/developer/adb.png b/selfdrive/assets/icons_mici/settings/developer/adb.png new file mode 100644 index 000000000..b3a780146 --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/developer/adb.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:19b304727376ea30126e7aeb10d1193885ffa661ca5bdf4c09098e4412d2ab6a +size 2163 diff --git a/selfdrive/assets/icons_mici/settings/developer/debug_mode.png b/selfdrive/assets/icons_mici/settings/developer/debug_mode.png new file mode 100644 index 000000000..2849d2733 --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/developer/debug_mode.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8bbdcedeb5f6f5844a8f67751f5d47793fe5af5e25ac6e7fd1ffc5857a85d56e +size 2997 diff --git a/selfdrive/assets/icons_mici/settings/developer/ssh.png b/selfdrive/assets/icons_mici/settings/developer/ssh.png new file mode 100644 index 000000000..cd86937ae --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/developer/ssh.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c655994336b7da4ca986c6f27494bcab66e77f016ec9db8df271de53ed93e517 +size 1328 diff --git a/selfdrive/assets/icons_mici/settings/developer_icon.png b/selfdrive/assets/icons_mici/settings/developer_icon.png new file mode 100644 index 000000000..af16c0291 --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/developer_icon.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a1f058c5640bd763d2f6927432a1daff1587770ea0d06f2e351a28462e9d8335 +size 1743 diff --git a/selfdrive/assets/icons_mici/settings/device/cameras.png b/selfdrive/assets/icons_mici/settings/device/cameras.png new file mode 100644 index 000000000..c44c51127 --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/device/cameras.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:77a1281979f0b50f0e109ead56a88a33b81ef5901dd1a4537eb3fa048e0d90de +size 1345 diff --git a/selfdrive/assets/icons_mici/settings/device/cancel.png b/selfdrive/assets/icons_mici/settings/device/cancel.png new file mode 100644 index 000000000..6da29ad66 --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/device/cancel.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:be9669cff5fc8a0b587dee27d1afb1caa77ceff2f92ca0ce3f01d25659e96596 +size 1332 diff --git a/selfdrive/assets/icons_mici/settings/device/downloading.png b/selfdrive/assets/icons_mici/settings/device/downloading.png new file mode 100644 index 000000000..2db585698 --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/device/downloading.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a355a3960aea41b176be66d85a3e7bc83ec843bfb1e5bd4c7978f4ea41c5d1a2 +size 2756 diff --git a/selfdrive/assets/icons_mici/settings/device/fcc_logo.png b/selfdrive/assets/icons_mici/settings/device/fcc_logo.png new file mode 100644 index 000000000..f29b24fd0 --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/device/fcc_logo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3cac8546d19e75a9edcbc0721a887fd74c8a3c41bfe19e36186b2b2bcabdae98 +size 1817 diff --git a/selfdrive/assets/icons_mici/settings/device/info.png b/selfdrive/assets/icons_mici/settings/device/info.png new file mode 100644 index 000000000..cb1632069 --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/device/info.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2649d36259700d32a0edef878647e76492b1bec2fe34ac8ea806d4e7e4c57855 +size 2668 diff --git a/selfdrive/assets/icons_mici/settings/device/language.png b/selfdrive/assets/icons_mici/settings/device/language.png new file mode 100644 index 000000000..f6d57b313 --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/device/language.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4b982ac1b78b45487490d1dbbffed1f68735f6a35def502e882f706c30683aff +size 3664 diff --git a/selfdrive/assets/icons_mici/settings/device/lkas.png b/selfdrive/assets/icons_mici/settings/device/lkas.png new file mode 100644 index 000000000..186ea78fb --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/device/lkas.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ab6aeb6cba94acf948a0ad64a485db00bf1f3de1360ae4c57212f3f083b2bd24 +size 2554 diff --git a/selfdrive/assets/icons_mici/settings/device/pair.png b/selfdrive/assets/icons_mici/settings/device/pair.png new file mode 100644 index 000000000..f072b2363 --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/device/pair.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ed671f4ad1523f0e66498af39e6075a0c19842ae05eddd00871a6e48ed3685d7 +size 1594 diff --git a/selfdrive/assets/icons_mici/settings/device/power.png b/selfdrive/assets/icons_mici/settings/device/power.png new file mode 100644 index 000000000..a2de14a4e --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/device/power.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5b45645ad9ff27776fdb1caa27827c526cae57f8bd4e23bd1160cb0094121ff2 +size 2338 diff --git a/selfdrive/assets/icons_mici/settings/device/reboot.png b/selfdrive/assets/icons_mici/settings/device/reboot.png new file mode 100644 index 000000000..6c89cd9fc --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/device/reboot.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f24039f82d7399d02a155022de65b6dc3b8edcf17059a73a9fd3a9209e3f5575 +size 2360 diff --git a/selfdrive/assets/icons_mici/settings/device/uninstall.png b/selfdrive/assets/icons_mici/settings/device/uninstall.png new file mode 100644 index 000000000..f9173711e --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/device/uninstall.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:558ea538fb258079f9eb05fe048b2806c7635b9f0452af874b00cb8d79b45f9b +size 2421 diff --git a/selfdrive/assets/icons_mici/settings/device/up_to_date.png b/selfdrive/assets/icons_mici/settings/device/up_to_date.png new file mode 100644 index 000000000..ee925458d --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/device/up_to_date.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4510e65775c6001758ebcf4dc13e9fa561cce5159d1fd54fbb506f22d3c7bdf3 +size 3149 diff --git a/selfdrive/assets/icons_mici/settings/device/update.png b/selfdrive/assets/icons_mici/settings/device/update.png new file mode 100644 index 000000000..cc05931b0 --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/device/update.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c6137349218ea22adba44f46a096afe2efc35536b2251192ed0ea61be443a3c5 +size 2493 diff --git a/selfdrive/assets/icons_mici/settings/device_icon.png b/selfdrive/assets/icons_mici/settings/device_icon.png new file mode 100644 index 000000000..0caf0d07c --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/device_icon.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:db20bea98259b204be634ce0d9a23fbfdcfc73a324fc0aac0f9ac54e1c51556d +size 2443 diff --git a/selfdrive/assets/icons_mici/settings/keyboard/back.png b/selfdrive/assets/icons_mici/settings/keyboard/back.png new file mode 100644 index 000000000..fccc2484d --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/keyboard/back.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de48d508af786b39fb725022b179e31456f32551a49b96ae07b5f55bfb968699 +size 1814 diff --git a/selfdrive/assets/icons_mici/settings/keyboard/backspace.png b/selfdrive/assets/icons_mici/settings/keyboard/backspace.png new file mode 100644 index 000000000..342f8e28d --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/keyboard/backspace.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:116bbbd1509e6644f7b65b8dacd2402b0918785bd80207504a99ab7e13ab738f +size 2049 diff --git a/selfdrive/assets/icons_mici/settings/keyboard/caps_lock.png b/selfdrive/assets/icons_mici/settings/keyboard/caps_lock.png new file mode 100644 index 000000000..d63cc56fb --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/keyboard/caps_lock.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3e8c7fec57640de6bfa8d0ede977e40920a8e651b68ed14e3d6c1850e702f3e3 +size 1399 diff --git a/selfdrive/assets/icons_mici/settings/keyboard/caps_lower.png b/selfdrive/assets/icons_mici/settings/keyboard/caps_lower.png new file mode 100644 index 000000000..eb3893430 --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/keyboard/caps_lower.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b7dab3af28938e9c3ad7b6c3b60526bb76498b0103c7276d90c4bff3622f07d0 +size 1157 diff --git a/selfdrive/assets/icons_mici/settings/keyboard/caps_upper.png b/selfdrive/assets/icons_mici/settings/keyboard/caps_upper.png new file mode 100644 index 000000000..4a2cae6c8 --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/keyboard/caps_upper.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0c5a88a0e8e810115b6d497d3e230d866bd96a715ddac632f48c78b40e1df702 +size 1059 diff --git a/selfdrive/assets/icons_mici/settings/keyboard/confirm.png b/selfdrive/assets/icons_mici/settings/keyboard/confirm.png new file mode 100644 index 000000000..09b180e97 --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/keyboard/confirm.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:32ce109a9fe4814bb9bed88f67d85292791f4a6d7c162e07561920221ac38b2d +size 1411 diff --git a/selfdrive/assets/icons_mici/settings/keyboard/keyboard_background.png b/selfdrive/assets/icons_mici/settings/keyboard/keyboard_background.png new file mode 100644 index 000000000..8c2c068d4 --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/keyboard/keyboard_background.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:399e7ff9dea6710244827c91014f1a08d8ae989dce922928d6b7f7504b15ba79 +size 11321 diff --git a/selfdrive/assets/icons_mici/settings/keyboard/space.png b/selfdrive/assets/icons_mici/settings/keyboard/space.png new file mode 100644 index 000000000..778d1847d --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/keyboard/space.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9b04d17f3b0340a94210efa5c9547e0ac340dd6b6dd9ac1f81ba5eb3f89f405d +size 619 diff --git a/selfdrive/assets/icons_mici/settings/manual_icon.png b/selfdrive/assets/icons_mici/settings/manual_icon.png new file mode 100644 index 000000000..100b29da4 --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/manual_icon.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:957330e9fbc8c03f05dbef8097178a40efc0fc52a6faf7a9917f97046d9a5e99 +size 1559 diff --git a/selfdrive/assets/icons_mici/settings/network/cell_strength_full.png b/selfdrive/assets/icons_mici/settings/network/cell_strength_full.png new file mode 100644 index 000000000..4bf0cd872 --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/network/cell_strength_full.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6a981d5c5558859b283cb6321c84eec947f82fc2dea8dbdd19b66781e4d3f61f +size 1060 diff --git a/selfdrive/assets/icons_mici/settings/network/cell_strength_high.png b/selfdrive/assets/icons_mici/settings/network/cell_strength_high.png new file mode 100644 index 000000000..df6d00933 --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/network/cell_strength_high.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:58da16ede432cf89096c11dc0f4ea098735863fb09a1d655cb06de8a112bd263 +size 1205 diff --git a/selfdrive/assets/icons_mici/settings/network/cell_strength_low.png b/selfdrive/assets/icons_mici/settings/network/cell_strength_low.png new file mode 100644 index 000000000..c3323a9fe --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/network/cell_strength_low.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:031bbd50c34d8fd5e71bdc292ba3e50b28a13c56a48dc84117723f1b35b42f51 +size 1224 diff --git a/selfdrive/assets/icons_mici/settings/network/cell_strength_medium.png b/selfdrive/assets/icons_mici/settings/network/cell_strength_medium.png new file mode 100644 index 000000000..64ab947c5 --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/network/cell_strength_medium.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ccb5f2227c72dd28e40c9f19965abe007cbd7b47cdca924907dc9fad906f5c81 +size 1219 diff --git a/selfdrive/assets/icons_mici/settings/network/cell_strength_none.png b/selfdrive/assets/icons_mici/settings/network/cell_strength_none.png new file mode 100644 index 000000000..6cdef706b --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/network/cell_strength_none.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:92c195721fe2b4ca42176077bf4ca3484cdfc314e961f1431b2296476bcae891 +size 1178 diff --git a/selfdrive/assets/icons_mici/settings/network/connect.png b/selfdrive/assets/icons_mici/settings/network/connect.png new file mode 100644 index 000000000..f7beaf392 --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/network/connect.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e47a5cb8b9ec784b6b893463622b7967a3692979b8a5e46e3334a09f745f1f71 +size 5413 diff --git a/selfdrive/assets/icons_mici/settings/network/connect_disabled.png b/selfdrive/assets/icons_mici/settings/network/connect_disabled.png new file mode 100644 index 000000000..0563668be --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/network/connect_disabled.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:05ddc9456627c0773dddcb10469e78124f9788381e63cab163ce4c6407a000f4 +size 3201 diff --git a/selfdrive/assets/icons_mici/settings/network/connect_pressed.png b/selfdrive/assets/icons_mici/settings/network/connect_pressed.png new file mode 100644 index 000000000..aef242b6d --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/network/connect_pressed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cab1783614421ab86467c3686f4d11f439d6de2cce3a84801dec7e044c08c880 +size 9075 diff --git a/selfdrive/assets/icons_mici/settings/network/forget_pill.png b/selfdrive/assets/icons_mici/settings/network/forget_pill.png new file mode 100644 index 000000000..a80de0763 --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/network/forget_pill.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2645cc56688a6225ba7ba0b4021af6543657942f31892a4986061a2959511054 +size 12106 diff --git a/selfdrive/assets/icons_mici/settings/network/forget_pill_pressed.png b/selfdrive/assets/icons_mici/settings/network/forget_pill_pressed.png new file mode 100644 index 000000000..a1240481e --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/network/forget_pill_pressed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:42cea7a190be02027a1cc8d6daca435f4edfb5b9484c26a06e667a2346c91f0f +size 38471 diff --git a/selfdrive/assets/icons_mici/settings/network/new/connect_button.png b/selfdrive/assets/icons_mici/settings/network/new/connect_button.png new file mode 100644 index 000000000..eae5af77f --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/network/new/connect_button.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:04236fa0f2759a01c6e321ac7b1c86c7a039215a7953b1a23d250ecf2ef1fa87 +size 8563 diff --git a/selfdrive/assets/icons_mici/settings/network/new/connect_button_pressed.png b/selfdrive/assets/icons_mici/settings/network/new/connect_button_pressed.png new file mode 100644 index 000000000..0da6c384d --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/network/new/connect_button_pressed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4337098554af30c98ebd512e17ab08207db868ff34acca5f865fcbfc940286d3 +size 21123 diff --git a/selfdrive/assets/icons_mici/settings/network/new/forget_button.png b/selfdrive/assets/icons_mici/settings/network/new/forget_button.png new file mode 100644 index 000000000..541433be7 --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/network/new/forget_button.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6ccb5f2298389ae36df87de84d85440ee5a82c50e803c9bd362c9b89ea45aa69 +size 6611 diff --git a/selfdrive/assets/icons_mici/settings/network/new/forget_button_pressed.png b/selfdrive/assets/icons_mici/settings/network/new/forget_button_pressed.png new file mode 100644 index 000000000..26cc8b4fc --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/network/new/forget_button_pressed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d9f17c82b2f349d107d27c69418f054be1f1753f970c7d3d3520c1e65de00511 +size 12894 diff --git a/selfdrive/assets/icons_mici/settings/network/new/full_connect_button.png b/selfdrive/assets/icons_mici/settings/network/new/full_connect_button.png new file mode 100644 index 000000000..905170fd1 --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/network/new/full_connect_button.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ffd37d5e5d5980efa98fee1cd0e8ebbf4139149b41c099e7dc3d5bd402cffb92 +size 9072 diff --git a/selfdrive/assets/icons_mici/settings/network/new/full_connect_button_pressed.png b/selfdrive/assets/icons_mici/settings/network/new/full_connect_button_pressed.png new file mode 100644 index 000000000..88eb4ac2a --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/network/new/full_connect_button_pressed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1b1d58704f8808dcb5a7ce9d86bc4212477759e96ac2419475f16f9184ee6a42 +size 21892 diff --git a/selfdrive/assets/icons_mici/settings/network/new/lock.png b/selfdrive/assets/icons_mici/settings/network/new/lock.png new file mode 100644 index 000000000..0a0b18c7a --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/network/new/lock.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:40dbbb3000e1137ec11fe658fbfebae7cadfc91356953317335f9bb70fcb40d3 +size 1235 diff --git a/selfdrive/assets/icons_mici/settings/network/new/trash.png b/selfdrive/assets/icons_mici/settings/network/new/trash.png new file mode 100644 index 000000000..99e1a2e24 --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/network/new/trash.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:efabf98ed66fe4447c0f13c74aec681b084de780c551ce18258c79636d4123c5 +size 1524 diff --git a/selfdrive/assets/icons_mici/settings/network/new/wifi_selected.png b/selfdrive/assets/icons_mici/settings/network/new/wifi_selected.png new file mode 100644 index 000000000..2a3e83713 --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/network/new/wifi_selected.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:160f67162e075436200d6719e614ddf96caaa2b7c0a3943f728c2afef10aa4ad +size 2489 diff --git a/selfdrive/assets/icons_mici/settings/network/tethering.png b/selfdrive/assets/icons_mici/settings/network/tethering.png new file mode 100644 index 000000000..9e7b90be4 --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/network/tethering.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2907ce46d1b6e676402f390c530955b65e76baf0b77fafc0616c50b988b3994c +size 1609 diff --git a/selfdrive/assets/icons_mici/settings/network/trash.png b/selfdrive/assets/icons_mici/settings/network/trash.png new file mode 100644 index 000000000..99e1a2e24 --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/network/trash.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:efabf98ed66fe4447c0f13c74aec681b084de780c551ce18258c79636d4123c5 +size 1524 diff --git a/selfdrive/assets/icons_mici/settings/network/wifi_strength_full.png b/selfdrive/assets/icons_mici/settings/network/wifi_strength_full.png new file mode 100644 index 000000000..1a1655fdd --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/network/wifi_strength_full.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f2715ea698eccb3648ab96cbddf897ea1842acbc1eb9667bc6f34aba82d0896b +size 1976 diff --git a/selfdrive/assets/icons_mici/settings/network/wifi_strength_low.png b/selfdrive/assets/icons_mici/settings/network/wifi_strength_low.png new file mode 100644 index 000000000..4d64d8062 --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/network/wifi_strength_low.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:58d839402c6f002ba8d2217888190b338fc3ac13d372df0988fac7bf95b89302 +size 2111 diff --git a/selfdrive/assets/icons_mici/settings/network/wifi_strength_medium.png b/selfdrive/assets/icons_mici/settings/network/wifi_strength_medium.png new file mode 100644 index 000000000..2d53a20ce --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/network/wifi_strength_medium.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a9918724409dbfa1973a097a692c2f57e45cc2bc0ce71c498ef3e02aa82559d3 +size 2128 diff --git a/selfdrive/assets/icons_mici/settings/network/wifi_strength_none.png b/selfdrive/assets/icons_mici/settings/network/wifi_strength_none.png new file mode 100644 index 000000000..482a0e104 --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/network/wifi_strength_none.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3fcef95eb18e2db566b907ae99b8d8f450424b3b7823fdc24cdfe066ccf64378 +size 2141 diff --git a/selfdrive/assets/icons_mici/settings/network/wifi_strength_slash.png b/selfdrive/assets/icons_mici/settings/network/wifi_strength_slash.png new file mode 100644 index 000000000..38ddff84b --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/network/wifi_strength_slash.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:73e4ae4741a039f41d79827c40be6da83f8c6eb79e9103db2dfec718ca96efb7 +size 2512 diff --git a/selfdrive/assets/icons_mici/settings/toggles_icon.png b/selfdrive/assets/icons_mici/settings/toggles_icon.png new file mode 100644 index 000000000..ccb343e8e --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/toggles_icon.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0297535eb73bea71e87c363dc12385bb9163b81403797e50966b20259f725542 +size 2528 diff --git a/selfdrive/assets/icons_mici/settings/vertical_scroll_indicator.png b/selfdrive/assets/icons_mici/settings/vertical_scroll_indicator.png new file mode 100644 index 000000000..77d9a77d6 --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/vertical_scroll_indicator.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:88e6c50358f627fc714c1e9883143aeed00baabeab16132e16001aa1051e5eb8 +size 1272 diff --git a/selfdrive/assets/icons_mici/setup/arrow.png b/selfdrive/assets/icons_mici/setup/arrow.png new file mode 100644 index 000000000..403aaedfb --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/arrow.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7aee85c239edcb7be41f03a4982b136d9a46908b027f37c5d36c80bd20372b22 +size 847 diff --git a/selfdrive/assets/icons_mici/setup/back.png b/selfdrive/assets/icons_mici/setup/back.png new file mode 100644 index 000000000..554c63e09 --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/back.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f88a958dd51eaf2c3f39df72165f538110c1d1eb4dbcac1f7016563db126c762 +size 1693 diff --git a/selfdrive/assets/icons_mici/setup/back_new.png b/selfdrive/assets/icons_mici/setup/back_new.png new file mode 100644 index 000000000..c4834a564 --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/back_new.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7198352d23952d0f2fbc128f20523ea6f2f2b7e378aa495da748a0e34f192806 +size 1641 diff --git a/selfdrive/assets/icons_mici/setup/green_button.png b/selfdrive/assets/icons_mici/setup/green_button.png new file mode 100644 index 000000000..9708cfe28 --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/green_button.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:163ac31cb990bdddfe552efef9a68870404caadb1c40fa8a5042b5ae956e6b4c +size 24687 diff --git a/selfdrive/assets/icons_mici/setup/green_button_pressed.png b/selfdrive/assets/icons_mici/setup/green_button_pressed.png new file mode 100644 index 000000000..030ce61d5 --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/green_button_pressed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6e4614adb2d3d0e44c64a855c221ec462a7aee22fff26132ad551035141c1a53 +size 62056 diff --git a/selfdrive/assets/icons_mici/setup/green_car.png b/selfdrive/assets/icons_mici/setup/green_car.png new file mode 100644 index 000000000..867cadbbd --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/green_car.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ce8a34777e0b185f457b98845aa17fe6b5192ca46101463aecd21a9e04c0f0f0 +size 13281 diff --git a/selfdrive/assets/icons_mici/setup/green_dm.png b/selfdrive/assets/icons_mici/setup/green_dm.png new file mode 100644 index 000000000..d41edd4c2 --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/green_dm.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:78795eaa5e0be5fa369e172c02f5bd4b06d20f44363ccb8cbd02cb181b13e529 +size 14289 diff --git a/selfdrive/assets/icons_mici/setup/green_info.png b/selfdrive/assets/icons_mici/setup/green_info.png new file mode 100644 index 000000000..309e56e6e --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/green_info.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2b0b1777d5bed7149982af9f2abab3fab7b6c576e3d53cf2c459804c6ec9ca1e +size 3957 diff --git a/selfdrive/assets/icons_mici/setup/green_pedal.png b/selfdrive/assets/icons_mici/setup/green_pedal.png new file mode 100644 index 000000000..2dd18f489 --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/green_pedal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6cadcda59bc861a1e710e0a8ac67024bdcc44b5f9261abbf098ff11cefb1da51 +size 12209 diff --git a/selfdrive/assets/icons_mici/setup/medium_button_bg.png b/selfdrive/assets/icons_mici/setup/medium_button_bg.png new file mode 100644 index 000000000..e79dc2eb5 --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/medium_button_bg.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9e363a79dc35ca4c4e9efaa6a843d37ad219efa5299d3e538d8249affa230096 +size 7935 diff --git a/selfdrive/assets/icons_mici/setup/medium_button_pressed_bg.png b/selfdrive/assets/icons_mici/setup/medium_button_pressed_bg.png new file mode 100644 index 000000000..e52fb0c17 --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/medium_button_pressed_bg.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cc6fb48520143b6fa1f060d8212e6d929917ab616ce943b5fab5a60665f00da5 +size 18225 diff --git a/selfdrive/assets/icons_mici/setup/reboot.png b/selfdrive/assets/icons_mici/setup/reboot.png new file mode 100644 index 000000000..5633f2b49 --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/reboot.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:345bea231013aab33b98a134a05af22c2df7c166b60a1a81b6b4526da13209a5 +size 2293 diff --git a/selfdrive/assets/icons_mici/setup/red_warning.png b/selfdrive/assets/icons_mici/setup/red_warning.png new file mode 100644 index 000000000..ed0634079 --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/red_warning.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:448d3e7214a77b02b32020ddb440ccd8fe72e110493a51cc10901c8242e72ca8 +size 3185 diff --git a/selfdrive/assets/icons_mici/setup/reset/small_button.png b/selfdrive/assets/icons_mici/setup/reset/small_button.png new file mode 100644 index 000000000..e3f58b107 --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/reset/small_button.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7a198f13f30b3dbc09f30d7fd8033a0bc07a0da9b010b7ca6ed2678430c9e5b4 +size 6949 diff --git a/selfdrive/assets/icons_mici/setup/reset/small_button_pressed.png b/selfdrive/assets/icons_mici/setup/reset/small_button_pressed.png new file mode 100644 index 000000000..5b502e00a --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/reset/small_button_pressed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:75289d004709def2a2d6101a0330ec867895068ec3807aefc2a26d423d907a13 +size 13437 diff --git a/selfdrive/assets/icons_mici/setup/reset/wide_button.png b/selfdrive/assets/icons_mici/setup/reset/wide_button.png new file mode 100644 index 000000000..3892f6eb8 --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/reset/wide_button.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2452aaf59da18be1b74b475851d66e5c73c50aa49820419a288b1fdb7b42dee1 +size 9071 diff --git a/selfdrive/assets/icons_mici/setup/reset/wide_button_pressed.png b/selfdrive/assets/icons_mici/setup/reset/wide_button_pressed.png new file mode 100644 index 000000000..3a34af884 --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/reset/wide_button_pressed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6478f7c1c5ef2013e94fc4218ab370889883c5c12231ba3e0975874cb0b6fec9 +size 21893 diff --git a/selfdrive/assets/icons_mici/setup/restore.png b/selfdrive/assets/icons_mici/setup/restore.png new file mode 100644 index 000000000..6aa6c6b85 --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/restore.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9d6b99696163cac1867d46998af9e53e212b82641b33c93b51276671f400a5ac +size 2962 diff --git a/selfdrive/assets/icons_mici/setup/scroll_down_indicator.png b/selfdrive/assets/icons_mici/setup/scroll_down_indicator.png new file mode 100644 index 000000000..4d74d8607 --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/scroll_down_indicator.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:52535e34e27b0341f7690a72dc16555eeb6e032bc2c2cde0786469852fdf5987 +size 1267 diff --git a/selfdrive/assets/icons_mici/setup/small_button.png b/selfdrive/assets/icons_mici/setup/small_button.png new file mode 100644 index 000000000..1ee01aeac --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/small_button.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:13919cf5df3137fdffdb8cc53a1215f13bf478a780ca8614234b7af0cdc0e766 +size 5409 diff --git a/selfdrive/assets/icons_mici/setup/small_button_disabled.png b/selfdrive/assets/icons_mici/setup/small_button_disabled.png new file mode 100644 index 000000000..5028a8cd2 --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/small_button_disabled.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9728423bd5e3197ef02d62e4bae415e6694aab875ca8630ffc9f188c38e18e5f +size 4141 diff --git a/selfdrive/assets/icons_mici/setup/small_button_pressed.png b/selfdrive/assets/icons_mici/setup/small_button_pressed.png new file mode 100644 index 000000000..6e30f47fb --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/small_button_pressed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c032fd71ccfebe161827de420771e7927fe1ed799e615e24d458cfd79fead7f7 +size 7875 diff --git a/selfdrive/assets/icons_mici/setup/small_red_pill.png b/selfdrive/assets/icons_mici/setup/small_red_pill.png new file mode 100644 index 000000000..4a7db930a --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/small_red_pill.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b3a336afddad80dc91caca91d54bd29897ce491f180374edf9a5ba517cbc00e9 +size 8765 diff --git a/selfdrive/assets/icons_mici/setup/small_red_pill_pressed.png b/selfdrive/assets/icons_mici/setup/small_red_pill_pressed.png new file mode 100644 index 000000000..a8d51960c --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/small_red_pill_pressed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8eee9f10ca80a4e6100c00c02bb46aa5f253b14b086ab9982cfa85ee94eec162 +size 22512 diff --git a/selfdrive/assets/icons_mici/setup/small_slider/slider_arrow.png b/selfdrive/assets/icons_mici/setup/small_slider/slider_arrow.png new file mode 100644 index 000000000..bbf1d9625 --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/small_slider/slider_arrow.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8425c56cb413ba757c94febe0332ce472dbf1472236b03cc4e627746fb86d701 +size 1149 diff --git a/selfdrive/assets/icons_mici/setup/small_slider/slider_arrow_outline.png b/selfdrive/assets/icons_mici/setup/small_slider/slider_arrow_outline.png new file mode 100644 index 000000000..1515867a4 --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/small_slider/slider_arrow_outline.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f6394fa9dc03f83ac8599cf8179cdcc221c1b80b2c00d396b04bf2e3a7bfdca4 +size 1673 diff --git a/selfdrive/assets/icons_mici/setup/small_slider/slider_bg.png b/selfdrive/assets/icons_mici/setup/small_slider/slider_bg.png new file mode 100644 index 000000000..43c10a54a --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/small_slider/slider_bg.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:94a86fac6ffe8a8179812cf55350ab9ca6935f36244c6f679c1cf521a842316b +size 5723 diff --git a/selfdrive/assets/icons_mici/setup/small_slider/slider_bg_larger.png b/selfdrive/assets/icons_mici/setup/small_slider/slider_bg_larger.png new file mode 100644 index 000000000..11c3ae2d3 --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/small_slider/slider_bg_larger.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:950d55fd7294fb05c10ba9944537c02637776497c159e1b7d145c73f0f9d3253 +size 7119 diff --git a/selfdrive/assets/icons_mici/setup/small_slider/slider_black_rounded_rectangle.png b/selfdrive/assets/icons_mici/setup/small_slider/slider_black_rounded_rectangle.png new file mode 100644 index 000000000..683587a06 --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/small_slider/slider_black_rounded_rectangle.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:61281d3e3ef5ac5a8fe75405a93c2096bf235f090b27832e986444e3fb85715e +size 7427 diff --git a/selfdrive/assets/icons_mici/setup/small_slider/slider_green_rounded_rectangle.png b/selfdrive/assets/icons_mici/setup/small_slider/slider_green_rounded_rectangle.png new file mode 100644 index 000000000..9ebff76b5 --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/small_slider/slider_green_rounded_rectangle.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bcd08444c77b3e559876eeb88d17808f72496adc26e27c3c21c00ff410879447 +size 10966 diff --git a/selfdrive/assets/icons_mici/setup/small_slider/slider_red_circle.png b/selfdrive/assets/icons_mici/setup/small_slider/slider_red_circle.png new file mode 100644 index 000000000..541433be7 --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/small_slider/slider_red_circle.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6ccb5f2298389ae36df87de84d85440ee5a82c50e803c9bd362c9b89ea45aa69 +size 6611 diff --git a/selfdrive/assets/icons_mici/setup/smaller_button.png b/selfdrive/assets/icons_mici/setup/smaller_button.png new file mode 100644 index 000000000..9b4851c56 --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/smaller_button.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:89ca7e6bb01dfa78300126ce828cb2a64e7a2e68e1e9152de242f57a36d0e57a +size 8604 diff --git a/selfdrive/assets/icons_mici/setup/smaller_button_disabled.png b/selfdrive/assets/icons_mici/setup/smaller_button_disabled.png new file mode 100644 index 000000000..6514791de --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/smaller_button_disabled.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b3242a411b559f1d0308f189fe0d25b81d6c7d964ca418a0c599a1bab4bffcbb +size 5341 diff --git a/selfdrive/assets/icons_mici/setup/smaller_button_pressed.png b/selfdrive/assets/icons_mici/setup/smaller_button_pressed.png new file mode 100644 index 000000000..64235b3a2 --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/smaller_button_pressed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d354651c0c8107dcc5f599777d260f53ef1901123315785ed8190466166cdce8 +size 17554 diff --git a/selfdrive/assets/icons_mici/setup/warning.png b/selfdrive/assets/icons_mici/setup/warning.png new file mode 100644 index 000000000..806eea28b --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/warning.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3bc7a85a0672183d80817f337084060465e143362037955025c11bc8ac531076 +size 3247 diff --git a/selfdrive/assets/icons_mici/setup/widish_button.png b/selfdrive/assets/icons_mici/setup/widish_button.png new file mode 100644 index 000000000..529b7c80c --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/widish_button.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:74fc21132b1e761ea54ce64617730c6ee79d01668244ab555b3b89870cfea181 +size 7112 diff --git a/selfdrive/assets/icons_mici/setup/widish_button_disabled.png b/selfdrive/assets/icons_mici/setup/widish_button_disabled.png new file mode 100644 index 000000000..5028a8cd2 --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/widish_button_disabled.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9728423bd5e3197ef02d62e4bae415e6694aab875ca8630ffc9f188c38e18e5f +size 4141 diff --git a/selfdrive/assets/icons_mici/setup/widish_button_pressed.png b/selfdrive/assets/icons_mici/setup/widish_button_pressed.png new file mode 100644 index 000000000..1095d4fc2 --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/widish_button_pressed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0ff179f93f421edcb503ca5c22a12b37e3a2aaabc414bf90f57e20ff5255dd75 +size 15572 diff --git a/selfdrive/assets/icons_mici/turn_intent_left.png b/selfdrive/assets/icons_mici/turn_intent_left.png new file mode 100644 index 000000000..6c2c47e88 --- /dev/null +++ b/selfdrive/assets/icons_mici/turn_intent_left.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ead8287b7041c32456e13721c238a71933256ca3d2b7e649c8f8731585eb5de8 +size 906 diff --git a/selfdrive/assets/icons_mici/turn_intent_right.png b/selfdrive/assets/icons_mici/turn_intent_right.png new file mode 100644 index 000000000..03a7245e7 --- /dev/null +++ b/selfdrive/assets/icons_mici/turn_intent_right.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6fe0532f7040aae78baa85c4cca44f5c939adb6a6f15889e2ca036f4a493f848 +size 935 diff --git a/selfdrive/assets/icons_mici/wheel.png b/selfdrive/assets/icons_mici/wheel.png new file mode 100644 index 000000000..f122349b8 --- /dev/null +++ b/selfdrive/assets/icons_mici/wheel.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cc3ef0c8c3038d75f99df2c565a361107bc903944d1afe91de0cbed9f6ca062a +size 2725 diff --git a/selfdrive/assets/icons_mici/wheel_critical.png b/selfdrive/assets/icons_mici/wheel_critical.png new file mode 100644 index 000000000..c0e5e8619 --- /dev/null +++ b/selfdrive/assets/icons_mici/wheel_critical.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:12783dc05ea6dae2647ac3a3a7c8391d520c3f0cf2f458333a357ee9633eb6c4 +size 10909 diff --git a/selfdrive/assets/offroad/mici_fcc.html b/selfdrive/assets/offroad/mici_fcc.html new file mode 100644 index 000000000..e6e418912 --- /dev/null +++ b/selfdrive/assets/offroad/mici_fcc.html @@ -0,0 +1,16 @@ +

HVIN: comma four

+

FCC ID: 2BFC6-MICI

+

IC: 32232-MICI

+

Contains FCC ID: XMR2023EG916QGL

+

Contains IC: 10224A-023EG916QGL

+

+This device contains licence-exempt transmitter(s)/receiver(s) that comply with Innovation, Science and Economic Development +Canada's licence-exempt RSS(s) and complies with part 15 of the FCC Rules. Operation is subject to the following two conditions:
+1. This device may not cause harmful interference.
+2. This device must accept any interference received, including interference that may cause undesired operation of the device.
+

+L'émetteur/récepteur exempt de licence contenu dans le présent appareil est conforme aux CNR d'Innovation, Sciences +et Développement économique Canada applicables aux appareils radio exempts de licence. L'exploitation est autorisée +aux deux conditions suivantes :
+1. L'appareil ne doit pas produire de brouillage.
+2. L'appareil doit accepter tout brouillage radioélectrique subi, même si le brouillage est susceptible d'en compromettre
diff --git a/selfdrive/assets/sounds/disengage.wav b/selfdrive/assets/sounds/disengage.wav index f3b5f21a2..8983884b2 100644 --- a/selfdrive/assets/sounds/disengage.wav +++ b/selfdrive/assets/sounds/disengage.wav @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1f061777d66d8d856a5fcb17378416f959088885ed770181355195ad15de881b -size 68628 +oid sha256:c94582be9d921146b3c356e08a7352700c309cb407877c1180542811b2d637fa +size 48078 diff --git a/selfdrive/assets/sounds/disengage_tizi.wav b/selfdrive/assets/sounds/disengage_tizi.wav new file mode 100644 index 000000000..f3b5f21a2 --- /dev/null +++ b/selfdrive/assets/sounds/disengage_tizi.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1f061777d66d8d856a5fcb17378416f959088885ed770181355195ad15de881b +size 68628 diff --git a/selfdrive/assets/sounds/engage.wav b/selfdrive/assets/sounds/engage.wav index fc24a23c2..39d4c749c 100644 --- a/selfdrive/assets/sounds/engage.wav +++ b/selfdrive/assets/sounds/engage.wav @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6b54a85cc09b8ee79fce23d48209205d2e70510eed4c5a86fa400fe3f7caebfd -size 63120 +oid sha256:bc2b12bfe816a79307660b6b3d2de87a7643c6ccbfc9d1b33804645ad717682a +size 48078 diff --git a/selfdrive/assets/sounds/engage_tizi.wav b/selfdrive/assets/sounds/engage_tizi.wav new file mode 100644 index 000000000..fc24a23c2 --- /dev/null +++ b/selfdrive/assets/sounds/engage_tizi.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6b54a85cc09b8ee79fce23d48209205d2e70510eed4c5a86fa400fe3f7caebfd +size 63120 diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index 8a5f5793a..0de3e13c0 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -56,12 +56,16 @@ if GetOption('extras'): "ld -r -b binary -o $TARGET $SOURCE") inter = raylib_env.Command("installer/inter_ttf.o", "installer/inter-ascii.ttf", "ld -r -b binary -o $TARGET $SOURCE") + inter_bold = raylib_env.Command("installer/inter_bold.o", "../assets/fonts/Inter-Bold.ttf", + "ld -r -b binary -o $TARGET $SOURCE") + inter_light = raylib_env.Command("installer/inter_light.o", "../assets/fonts/Inter-Light.ttf", + "ld -r -b binary -o $TARGET $SOURCE") for name, branch in installers: d = {'BRANCH': f"'\"{branch}\"'"} if "internal" in name: d['INTERNAL'] = "1" obj = raylib_env.Object(f"installer/installers/installer_{name}.o", ["installer/installer.cc"], CPPDEFINES=d) - f = raylib_env.Program(f"installer/installers/installer_{name}", [obj, cont, inter], LIBS=raylib_libs) + f = raylib_env.Program(f"installer/installers/installer_{name}", [obj, cont, inter, inter_bold, inter_light], LIBS=raylib_libs) # keep installers small assert f[0].get_size() < 1900*1e3, f[0].get_size() diff --git a/selfdrive/ui/installer/installer.cc b/selfdrive/ui/installer/installer.cc index 5cb0a38e0..38dd1ce25 100644 --- a/selfdrive/ui/installer/installer.cc +++ b/selfdrive/ui/installer/installer.cc @@ -36,8 +36,17 @@ extern const uint8_t str_continue[] asm("_binary_selfdrive_ui_installer_continue extern const uint8_t str_continue_end[] asm("_binary_selfdrive_ui_installer_continue_openpilot_sh_end"); extern const uint8_t inter_ttf[] asm("_binary_selfdrive_ui_installer_inter_ascii_ttf_start"); extern const uint8_t inter_ttf_end[] asm("_binary_selfdrive_ui_installer_inter_ascii_ttf_end"); +extern const uint8_t inter_light_ttf[] asm("_binary_selfdrive_assets_fonts_Inter_Light_ttf_start"); +extern const uint8_t inter_light_ttf_end[] asm("_binary_selfdrive_assets_fonts_Inter_Light_ttf_end"); +extern const uint8_t inter_bold_ttf[] asm("_binary_selfdrive_assets_fonts_Inter_Bold_ttf_start"); +extern const uint8_t inter_bold_ttf_end[] asm("_binary_selfdrive_assets_fonts_Inter_Bold_ttf_end"); -Font font; +Font font_inter; +Font font_roman; +Font font_display; + +const bool tici_device = Hardware::get_device_type() == cereal::InitData::DeviceType::TICI || + Hardware::get_device_type() == cereal::InitData::DeviceType::TIZI; std::vector tici_prebuilt_branches = {"release3", "release-tizi", "release3-staging", "nightly", "nightly-dev"}; std::string migrated_branch; @@ -68,9 +77,13 @@ void run(const char* cmd) { void finishInstall() { BeginDrawing(); ClearBackground(BLACK); - const char *m = "Finishing install..."; - int text_width = MeasureText(m, FONT_SIZE); - DrawTextEx(font, m, (Vector2){(float)(GetScreenWidth() - text_width)/2 + FONT_SIZE, (float)(GetScreenHeight() - FONT_SIZE)/2}, FONT_SIZE, 0, WHITE); + if (tici_device) { + const char *m = "Finishing install..."; + int text_width = MeasureText(m, FONT_SIZE); + DrawTextEx(font_display, m, (Vector2){(float)(GetScreenWidth() - text_width)/2 + FONT_SIZE, (float)(GetScreenHeight() - FONT_SIZE)/2}, FONT_SIZE, 0, WHITE); + } else { + DrawTextEx(font_display, "finishing setup", (Vector2){8, 10}, 82, 0, WHITE); + } EndDrawing(); util::sleep_for(60 * 1000); } @@ -78,13 +91,21 @@ void finishInstall() { void renderProgress(int progress) { BeginDrawing(); ClearBackground(BLACK); - DrawTextEx(font, "Installing...", (Vector2){150, 290}, 110, 0, WHITE); - Rectangle bar = {150, 570, (float)GetScreenWidth() - 300, 72}; - DrawRectangleRec(bar, (Color){41, 41, 41, 255}); - progress = std::clamp(progress, 0, 100); - bar.width *= progress / 100.0f; - DrawRectangleRec(bar, (Color){70, 91, 234, 255}); - DrawTextEx(font, (std::to_string(progress) + "%").c_str(), (Vector2){150, 670}, 85, 0, WHITE); + if (tici_device) { + DrawTextEx(font_inter, "Installing...", (Vector2){150, 290}, 110, 0, WHITE); + Rectangle bar = {150, 570, (float)GetScreenWidth() - 300, 72}; + DrawRectangleRec(bar, (Color){41, 41, 41, 255}); + progress = std::clamp(progress, 0, 100); + bar.width *= progress / 100.0f; + DrawRectangleRec(bar, (Color){70, 91, 234, 255}); + DrawTextEx(font_inter, (std::to_string(progress) + "%").c_str(), (Vector2){150, 670}, 85, 0, WHITE); + } else { + DrawTextEx(font_display, "installing", (Vector2){8, 10}, 82, 0, WHITE); + const std::string percent_str = std::to_string(progress) + "%"; + DrawTextEx(font_roman, percent_str.c_str(), (Vector2){6, (float)(GetScreenHeight() - 128 + 18)}, 128, 0, + (Color){255, 255, 255, (unsigned char)(255 * 0.9 * 0.35)}); + } + EndDrawing(); } @@ -211,9 +232,18 @@ void cloneFinished(int exitCode) { } int main(int argc, char *argv[]) { - InitWindow(2160, 1080, "Installer"); - font = LoadFontFromMemory(".ttf", inter_ttf, inter_ttf_end - inter_ttf, FONT_SIZE, NULL, 0); - SetTextureFilter(font.texture, TEXTURE_FILTER_BILINEAR); + if (tici_device) { + InitWindow(2160, 1080, "Installer"); + } else { + InitWindow(536, 240, "Installer"); + } + + font_inter = LoadFontFromMemory(".ttf", inter_ttf, inter_ttf_end - inter_ttf, FONT_SIZE, NULL, 0); + font_roman = LoadFontFromMemory(".ttf", inter_light_ttf, inter_light_ttf_end - inter_light_ttf, FONT_SIZE, NULL, 0); + font_display = LoadFontFromMemory(".ttf", inter_bold_ttf, inter_bold_ttf_end - inter_bold_ttf, FONT_SIZE, NULL, 0); + SetTextureFilter(font_inter.texture, TEXTURE_FILTER_BILINEAR); + SetTextureFilter(font_roman.texture, TEXTURE_FILTER_BILINEAR); + SetTextureFilter(font_display.texture, TEXTURE_FILTER_BILINEAR); branchMigration(); @@ -226,6 +256,8 @@ int main(int argc, char *argv[]) { } CloseWindow(); - UnloadFont(font); + UnloadFont(font_inter); + UnloadFont(font_roman); + UnloadFont(font_display); return 0; } diff --git a/selfdrive/ui/layouts/home.py b/selfdrive/ui/layouts/home.py index 7f477d424..cd6ae600e 100644 --- a/selfdrive/ui/layouts/home.py +++ b/selfdrive/ui/layouts/home.py @@ -20,8 +20,6 @@ SPACING = 25 RIGHT_COLUMN_WIDTH = 750 REFRESH_INTERVAL = 10.0 -PRIME_BG_COLOR = rl.Color(51, 51, 51, 255) - class HomeLayoutState(IntEnum): HOME = 0 diff --git a/selfdrive/ui/layouts/settings/common.py b/selfdrive/ui/layouts/settings/common.py new file mode 100644 index 000000000..5e87a6447 --- /dev/null +++ b/selfdrive/ui/layouts/settings/common.py @@ -0,0 +1,5 @@ +from openpilot.selfdrive.ui.ui_state import ui_state + + +def restart_needed_callback(_): + ui_state.params.put_bool("OnroadCycleRequested", True) diff --git a/selfdrive/ui/layouts/settings/developer.py b/selfdrive/ui/layouts/settings/developer.py index 9ea1019f5..646c81750 100644 --- a/selfdrive/ui/layouts/settings/developer.py +++ b/selfdrive/ui/layouts/settings/developer.py @@ -3,7 +3,7 @@ from openpilot.selfdrive.ui.widgets.ssh_key import ssh_key_item from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.list_view import toggle_item -from openpilot.system.ui.widgets.scroller import Scroller +from openpilot.system.ui.widgets.scroller_tici import Scroller from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.lib.multilang import tr, tr_noop diff --git a/selfdrive/ui/layouts/settings/device.py b/selfdrive/ui/layouts/settings/device.py index f5f37fbd3..00ae6a188 100644 --- a/selfdrive/ui/layouts/settings/device.py +++ b/selfdrive/ui/layouts/settings/device.py @@ -17,7 +17,7 @@ from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog, alert_dial from openpilot.system.ui.widgets.html_render import HtmlModal from openpilot.system.ui.widgets.list_view import text_item, button_item, dual_button_item from openpilot.system.ui.widgets.option_dialog import MultiOptionDialog -from openpilot.system.ui.widgets.scroller import Scroller +from openpilot.system.ui.widgets.scroller_tici import Scroller # Description constants DESCRIPTIONS = { diff --git a/selfdrive/ui/layouts/settings/software.py b/selfdrive/ui/layouts/settings/software.py index 8166a8a9e..4b8b7015f 100644 --- a/selfdrive/ui/layouts/settings/software.py +++ b/selfdrive/ui/layouts/settings/software.py @@ -9,7 +9,7 @@ from openpilot.system.ui.widgets import Widget, DialogResult from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog from openpilot.system.ui.widgets.list_view import button_item, text_item, ListItem from openpilot.system.ui.widgets.option_dialog import MultiOptionDialog -from openpilot.system.ui.widgets.scroller import Scroller +from openpilot.system.ui.widgets.scroller_tici import Scroller # TODO: remove this. updater fails to respond on startup if time is not correct UPDATED_TIMEOUT = 10 # seconds to wait for updated to respond diff --git a/selfdrive/ui/layouts/settings/toggles.py b/selfdrive/ui/layouts/settings/toggles.py index 3a1265e0f..7fae2dfd2 100644 --- a/selfdrive/ui/layouts/settings/toggles.py +++ b/selfdrive/ui/layouts/settings/toggles.py @@ -2,7 +2,7 @@ from cereal import log from openpilot.common.params import Params, UnknownKeyName from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.list_view import multiple_button_item, toggle_item -from openpilot.system.ui.widgets.scroller import Scroller +from openpilot.system.ui.widgets.scroller_tici import Scroller from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.lib.multilang import tr, tr_noop diff --git a/selfdrive/ui/mici/layouts/__init__.py b/selfdrive/ui/mici/layouts/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/selfdrive/ui/mici/layouts/home.py b/selfdrive/ui/mici/layouts/home.py new file mode 100644 index 000000000..014fc0c45 --- /dev/null +++ b/selfdrive/ui/mici/layouts/home.py @@ -0,0 +1,272 @@ +import time + +from cereal import log +import pyray as rl +from collections.abc import Callable +from openpilot.system.ui.widgets.label import gui_label, MiciLabel +from openpilot.system.ui.widgets import Widget +from openpilot.system.ui.lib.application import gui_app, FontWeight, DEFAULT_TEXT_COLOR, MousePos +from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.system.ui.text import wrap_text +from openpilot.system.version import training_version + +HEAD_BUTTON_FONT_SIZE = 40 +HOME_PADDING = 8 + +RELEASE_BRANCH = "release3" + +NetworkType = log.DeviceState.NetworkType + +NETWORK_TYPES = { + NetworkType.none: "Offline", + NetworkType.wifi: "WiFi", + NetworkType.cell2G: "2G", + NetworkType.cell3G: "3G", + NetworkType.cell4G: "LTE", + NetworkType.cell5G: "5G", + NetworkType.ethernet: "Ethernet", +} + + +class DeviceStatus(Widget): + def __init__(self): + super().__init__() + self.set_rect(rl.Rectangle(0, 0, 300, 175)) + self._update_state() + self._version_text = self._get_version_text() + + self._do_welcome() + + def _do_welcome(self): + ui_state.params.put("CompletedTrainingVersion", training_version) + + def refresh(self): + self._update_state() + self._version_text = self._get_version_text() + + def _get_version_text(self) -> str: + brand = "openpilot" + description = ui_state.params.get("UpdaterCurrentDescription") + return f"{brand} {description}" if description else brand + + def _update_state(self): + # TODO: refresh function that can be called periodically, not at 60 fps, so we can update version + # update system status + self._system_status = "SYSTEM READY ✓" if ui_state.panda_type != log.PandaState.PandaType.unknown else "BOOTING UP..." + + # update network status + strength = ui_state.sm['deviceState'].networkStrength.raw + strength_text = "● " * strength + "○ " * (4 - strength) # ◌ also works + network_type = NETWORK_TYPES[ui_state.sm['deviceState'].networkType.raw] + self._network_status = f"{network_type} {strength_text}" + + def _render(self, _): + # draw status + status_rect = rl.Rectangle(self._rect.x, self._rect.y, self._rect.width, 40) + gui_label(status_rect, self._system_status, font_size=HEAD_BUTTON_FONT_SIZE, color=DEFAULT_TEXT_COLOR, + font_weight=FontWeight.BOLD, alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER) + + # draw network status + network_rect = rl.Rectangle(self._rect.x, self._rect.y + 60, self._rect.width, 40) + gui_label(network_rect, self._network_status, font_size=40, color=DEFAULT_TEXT_COLOR, + font_weight=FontWeight.MEDIUM, alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER) + + # draw version + version_font_size = 30 + version_rect = rl.Rectangle(self._rect.x, self._rect.y + 140, self._rect.width + 20, 40) + wrapped_text = '\n'.join(wrap_text(self._version_text, version_font_size, version_rect.width)) + gui_label(version_rect, wrapped_text, font_size=version_font_size, color=DEFAULT_TEXT_COLOR, + font_weight=FontWeight.MEDIUM, alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT) + + +class MiciHomeLayout(Widget): + def __init__(self): + super().__init__() + self._on_settings_click: Callable | None = None + + self._last_refresh = 0 + self._mouse_down_t: None | float = None + self._did_long_press = False + self._is_pressed_prev = False + + self._version_text = None + self._experimental_mode = False + + self._settings_txt = gui_app.texture("icons_mici/settings.png", 48, 48) + self._experimental_txt = gui_app.texture("icons_mici/experimental_mode.png", 48, 48) + self._mic_txt = gui_app.texture("icons_mici/microphone.png", 48, 48) + + self._net_type = NETWORK_TYPES.get(NetworkType.none) + self._net_strength = 0 + + self._wifi_slash_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_slash.png", 50, 44) + self._wifi_none_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_none.png", 50, 44) + self._wifi_low_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_low.png", 50, 44) + self._wifi_medium_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_medium.png", 50, 44) + self._wifi_full_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_full.png", 50, 44) + + self._cell_none_txt = gui_app.texture("icons_mici/settings/network/cell_strength_none.png", 55, 35) + self._cell_low_txt = gui_app.texture("icons_mici/settings/network/cell_strength_low.png", 55, 35) + self._cell_medium_txt = gui_app.texture("icons_mici/settings/network/cell_strength_medium.png", 55, 35) + self._cell_high_txt = gui_app.texture("icons_mici/settings/network/cell_strength_high.png", 55, 35) + self._cell_full_txt = gui_app.texture("icons_mici/settings/network/cell_strength_full.png", 55, 35) + + self._openpilot_label = MiciLabel("openpilot", font_size=96, color=rl.Color(255, 255, 255, int(255 * 0.9)), font_weight=FontWeight.DISPLAY) + self._version_label = MiciLabel("", font_size=36, font_weight=FontWeight.ROMAN) + self._large_version_label = MiciLabel("", font_size=64, color=rl.GRAY, font_weight=FontWeight.ROMAN) + self._date_label = MiciLabel("", font_size=36, color=rl.GRAY, font_weight=FontWeight.ROMAN) + self._branch_label = MiciLabel("", font_size=36, color=rl.GRAY, font_weight=FontWeight.ROMAN, elide_right=False, scroll=True) + self._version_commit_label = MiciLabel("", font_size=36, color=rl.GRAY, font_weight=FontWeight.ROMAN) + + def show_event(self): + self._version_text = self._get_version_text() + self._update_network_status(ui_state.sm['deviceState']) + self._update_params() + + def _update_params(self): + self._experimental_mode = ui_state.params.get_bool("ExperimentalMode") + + def _update_state(self): + if self.is_pressed and not self._is_pressed_prev: + self._mouse_down_t = time.monotonic() + elif not self.is_pressed and self._is_pressed_prev: + self._mouse_down_t = None + self._did_long_press = False + self._is_pressed_prev = self.is_pressed + + if self._mouse_down_t is not None: + if time.monotonic() - self._mouse_down_t > 0.5: + # long gating for experimental mode - only allow toggle if longitudinal control is available + if ui_state.has_longitudinal_control: + self._experimental_mode = not self._experimental_mode + ui_state.params.put("ExperimentalMode", self._experimental_mode) + self._mouse_down_t = None + self._did_long_press = True + + if rl.get_time() - self._last_refresh > 5.0: + device_state = ui_state.sm['deviceState'] + self._update_network_status(device_state) + + # Update version text + self._version_text = self._get_version_text() + self._last_refresh = rl.get_time() + self._update_params() + + def _update_network_status(self, device_state): + self._net_type = device_state.networkType + strength = device_state.networkStrength + self._net_strength = max(0, min(5, strength.raw + 1)) if strength.raw > 0 else 0 + + def set_callbacks(self, on_settings: Callable | None = None): + self._on_settings_click = on_settings + + def _handle_mouse_release(self, mouse_pos: MousePos): + if not self._did_long_press: + if self._on_settings_click: + self._on_settings_click() + self._did_long_press = False + + def _get_version_text(self) -> tuple[str, str, str, str] | None: + description = ui_state.params.get("UpdaterCurrentDescription") + + if description is not None and len(description) > 0: + # Expect "version / branch / commit / date"; be tolerant of other formats + try: + version, branch, commit, date = description.split(" / ") + return version, branch, commit, date + except Exception: + return None + + return None + + def _render(self, _): + # TODO: why is there extra space here to get it to be flush? + text_pos = rl.Vector2(self.rect.x - 2 + HOME_PADDING, self.rect.y - 16) + self._openpilot_label.set_position(text_pos.x, text_pos.y) + self._openpilot_label.render() + + if self._version_text is not None: + # release branch + if self._version_text[0] == RELEASE_BRANCH: + version_pos = rl.Vector2(text_pos.x, text_pos.y + self._openpilot_label.font_size + 16) + self._large_version_label.set_text(self._version_text[0]) + self._large_version_label.set_position(version_pos.x, version_pos.y) + self._large_version_label.render() + + else: + version_pos = rl.Rectangle(text_pos.x, text_pos.y + self._openpilot_label.font_size + 16, 100, 44) + self._version_label.set_text(self._version_text[0]) + self._version_label.set_position(version_pos.x, version_pos.y) + self._version_label.render() + + self._date_label.set_text(" " + self._version_text[3]) + self._date_label.set_position(version_pos.x + self._version_label.rect.width + 10, version_pos.y) + self._date_label.render() + + self._branch_label.set_width(gui_app.width - self._version_label.rect.width - self._date_label.rect.width - 32) + self._branch_label.set_text(" " + self._version_text[1]) + self._branch_label.set_position(version_pos.x + self._version_label.rect.width + self._date_label.rect.width + 20, version_pos.y) + self._branch_label.render() + + # 2nd line + self._version_commit_label.set_text(self._version_text[2]) + self._version_commit_label.set_position(version_pos.x, version_pos.y + self._date_label.font_size + 7) + self._version_commit_label.render() + + self._render_bottom_status_bar() + + def _render_bottom_status_bar(self): + # ***** Center-aligned bottom section icons ***** + + # TODO: refactor repeated icon drawing into a small loop + ITEM_SPACING = 18 + Y_CENTER = 24 + + last_x = self.rect.x + HOME_PADDING + + # Draw settings icon in bottom left corner + rl.draw_texture(self._settings_txt, int(last_x), int(self._rect.y + self.rect.height - self._settings_txt.height / 2 - Y_CENTER), + rl.Color(255, 255, 255, int(255 * 0.9))) + last_x = last_x + self._settings_txt.width + ITEM_SPACING + + # draw network + if self._net_type == NetworkType.wifi: + # There is no 1 + draw_net_txt = {0: self._wifi_none_txt, + 2: self._wifi_low_txt, + 3: self._wifi_medium_txt, + 4: self._wifi_full_txt, + 5: self._wifi_full_txt}.get(self._net_strength, self._wifi_low_txt) + rl.draw_texture(draw_net_txt, int(last_x), + int(self._rect.y + self.rect.height - draw_net_txt.height / 2 - Y_CENTER), rl.Color(255, 255, 255, int(255 * 0.9))) + last_x += draw_net_txt.width + ITEM_SPACING + + elif self._net_type in (NetworkType.cell2G, NetworkType.cell3G, NetworkType.cell4G, NetworkType.cell5G): + draw_net_txt = {0: self._cell_none_txt, + 2: self._cell_low_txt, + 3: self._cell_medium_txt, + 4: self._cell_high_txt, + 5: self._cell_full_txt}.get(self._net_strength, self._cell_none_txt) + rl.draw_texture(draw_net_txt, int(last_x), + int(self._rect.y + self.rect.height - draw_net_txt.height / 2 - Y_CENTER), rl.Color(255, 255, 255, int(255 * 0.9))) + last_x += draw_net_txt.width + ITEM_SPACING + + else: + # No network + # Offset by difference in height between slashless and slash icons to make center align match + rl.draw_texture(self._wifi_slash_txt, int(last_x), int(self._rect.y + self.rect.height - self._wifi_slash_txt.height / 2 - + (self._wifi_slash_txt.height - self._wifi_none_txt.height) / 2 - Y_CENTER), + rl.Color(255, 255, 255, 255)) + last_x += self._wifi_slash_txt.width + ITEM_SPACING + + # draw experimental icon + if self._experimental_mode: + rl.draw_texture(self._experimental_txt, int(last_x), + int(self._rect.y + self.rect.height - self._experimental_txt.height / 2 - Y_CENTER), rl.Color(255, 255, 255, 255)) + last_x += self._experimental_txt.width + ITEM_SPACING + + # draw microphone icon when recording audio is enabled + if ui_state.recording_audio: + rl.draw_texture(self._mic_txt, int(last_x), + int(self._rect.y + self.rect.height - self._mic_txt.height / 2 - Y_CENTER), rl.Color(255, 255, 255, 255)) + last_x += self._mic_txt.width + ITEM_SPACING diff --git a/selfdrive/ui/mici/layouts/main.py b/selfdrive/ui/mici/layouts/main.py new file mode 100644 index 000000000..b52f9ed39 --- /dev/null +++ b/selfdrive/ui/mici/layouts/main.py @@ -0,0 +1,149 @@ +import pyray as rl +from enum import IntEnum +import cereal.messaging as messaging +from openpilot.selfdrive.ui.mici.layouts.home import MiciHomeLayout +from openpilot.selfdrive.ui.mici.layouts.settings.settings import SettingsLayout +from openpilot.selfdrive.ui.mici.layouts.offroad_alerts import MiciOffroadAlerts +from openpilot.selfdrive.ui.mici.onroad.augmented_road_view import AugmentedRoadView +from openpilot.selfdrive.ui.ui_state import device, ui_state +from openpilot.selfdrive.ui.mici.layouts.onboarding import OnboardingWindow +from openpilot.system.ui.widgets import Widget +from openpilot.system.ui.widgets.scroller import Scroller +from openpilot.system.ui.lib.application import gui_app + + +ONROAD_DELAY = 2.5 # seconds + + +class MainState(IntEnum): + MAIN = 0 + SETTINGS = 1 + + +class MiciMainLayout(Widget): + def __init__(self): + super().__init__() + + self._pm = messaging.PubMaster(['bookmarkButton']) + + self._current_mode: MainState | None = None + self._prev_onroad = False + self._prev_standstill = False + self._onroad_time_delay: float | None = None + self._setup = False + + # Initialize widgets + self._home_layout = MiciHomeLayout() + self._alerts_layout = MiciOffroadAlerts() + self._settings_layout = SettingsLayout() + self._onroad_layout = AugmentedRoadView(bookmark_callback=self._on_bookmark_clicked) + + # Initialize widget rects + for widget in (self._home_layout, self._settings_layout, self._alerts_layout, self._onroad_layout): + # TODO: set parent rect and use it if never passed rect from render (like in Scroller) + widget.set_rect(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) + + self._scroller = Scroller([ + self._alerts_layout, + self._home_layout, + self._onroad_layout, + ], spacing=0, pad_start=0, pad_end=0) + self._scroller.set_reset_scroll_at_show(False) + + # Disable scrolling when onroad is interacting with bookmark + self._scroller.set_scrolling_enabled(lambda: not self._onroad_layout.is_swiping_left()) + + self._layouts = { + MainState.MAIN: self._scroller, + MainState.SETTINGS: self._settings_layout, + } + + # Set callbacks + self._setup_callbacks() + + # Start onboarding if terms or training not completed + self._onboarding_window = OnboardingWindow() + if not self._onboarding_window.completed: + gui_app.set_modal_overlay(self._onboarding_window) + + def _setup_callbacks(self): + self._home_layout.set_callbacks(on_settings=self._on_settings_clicked) + self._settings_layout.set_callbacks(on_close=self._on_settings_closed) + self._onroad_layout.set_click_callback(lambda: self._scroll_to(self._home_layout)) + device.add_interactive_timeout_callback(self._set_mode_for_started) + + def _scroll_to(self, layout: Widget): + layout_x = int(layout.rect.x) + self._scroller.scroll_to(layout_x, smooth=True) + + def _render(self, _): + # Initial show event + if self._current_mode is None: + self._set_mode(MainState.MAIN) + + if not self._setup: + if self._alerts_layout.active_alerts() > 0: + self._scroller.scroll_to(self._alerts_layout.rect.x) + else: + self._scroller.scroll_to(self._rect.width) + self._setup = True + + # Render + if self._current_mode == MainState.MAIN: + self._scroller.render(self._rect) + + elif self._current_mode == MainState.SETTINGS: + self._settings_layout.render(self._rect) + + self._handle_transitions() + + def _set_mode(self, mode: MainState): + if mode != self._current_mode: + if self._current_mode is not None: + self._layouts[self._current_mode].hide_event() + self._layouts[mode].show_event() + self._current_mode = mode + + def _handle_transitions(self): + if ui_state.started != self._prev_onroad: + self._prev_onroad = ui_state.started + + if ui_state.started: + self._onroad_time_delay = rl.get_time() + else: + self._set_mode_for_started(True) + + # delay so we show home for a bit after starting + if self._onroad_time_delay is not None and rl.get_time() - self._onroad_time_delay >= ONROAD_DELAY: + self._set_mode_for_started(True) + self._onroad_time_delay = None + + CS = ui_state.sm["carState"] + if not CS.standstill and self._prev_standstill: + self._set_mode(MainState.MAIN) + self._scroll_to(self._onroad_layout) + self._prev_standstill = CS.standstill + + def _set_mode_for_started(self, onroad_transition: bool = False): + if ui_state.started: + CS = ui_state.sm["carState"] + # Only go onroad if car starts or is not at a standstill + if not CS.standstill or onroad_transition: + self._set_mode(MainState.MAIN) + self._scroll_to(self._onroad_layout) + else: + # Stay in settings if car turns off while in settings + if not onroad_transition or self._current_mode != MainState.SETTINGS: + self._set_mode(MainState.MAIN) + self._scroll_to(self._home_layout) + + def _on_settings_clicked(self): + self._set_mode(MainState.SETTINGS) + + def _on_settings_closed(self): + self._set_mode(MainState.MAIN) + + def _on_bookmark_clicked(self): + user_bookmark = messaging.new_message('bookmarkButton') + user_bookmark.valid = True + self._pm.send('bookmarkButton', user_bookmark) diff --git a/selfdrive/ui/mici/layouts/offroad_alerts.py b/selfdrive/ui/mici/layouts/offroad_alerts.py new file mode 100644 index 000000000..2e9a8bee3 --- /dev/null +++ b/selfdrive/ui/mici/layouts/offroad_alerts.py @@ -0,0 +1,307 @@ +import pyray as rl +import re +import time +from dataclasses import dataclass +from enum import IntEnum +from openpilot.common.params import Params +from openpilot.selfdrive.selfdrived.alertmanager import OFFROAD_ALERTS +from openpilot.system.ui.widgets import Widget +from openpilot.system.ui.widgets.label import UnifiedLabel +from openpilot.system.ui.widgets.scroller import Scroller +from openpilot.system.ui.lib.application import gui_app, FontWeight +from openpilot.system.ui.lib.multilang import tr + +REFRESH_INTERVAL = 5.0 # seconds + + +class AlertSize(IntEnum): + SMALL = 0 + MEDIUM = 1 + BIG = 2 + + +@dataclass +class AlertData: + key: str + text: str + severity: int + visible: bool = False + + +class AlertItem(Widget): + # TODO: click should always go somewhere: home or specific settings pane + """Individual alert item widget with background image and text.""" + ALERT_WIDTH = 520 + ALERT_HEIGHT_SMALL = 212 + ALERT_HEIGHT_MED = 240 + ALERT_HEIGHT_BIG = 324 + ALERT_PADDING = 28 + ICON_SIZE = 64 + ICON_MARGIN = 12 + TEXT_COLOR = rl.Color(255, 255, 255, int(255 * 0.9)) + TITLE_BODY_SPACING = 24 + + def __init__(self, alert_data: AlertData): + super().__init__() + self.alert_data = alert_data + + # Load background textures + self._bg_small = gui_app.texture("icons_mici/offroad_alerts/small_alert.png", self.ALERT_WIDTH, self.ALERT_HEIGHT_SMALL) + self._bg_small_pressed = gui_app.texture("icons_mici/offroad_alerts/small_alert_pressed.png", self.ALERT_WIDTH, self.ALERT_HEIGHT_SMALL) + self._bg_medium = gui_app.texture("icons_mici/offroad_alerts/medium_alert.png", self.ALERT_WIDTH, self.ALERT_HEIGHT_MED) + self._bg_medium_pressed = gui_app.texture("icons_mici/offroad_alerts/medium_alert_pressed.png", self.ALERT_WIDTH, self.ALERT_HEIGHT_MED) + self._bg_big = gui_app.texture("icons_mici/offroad_alerts/big_alert.png", self.ALERT_WIDTH, self.ALERT_HEIGHT_BIG) + self._bg_big_pressed = gui_app.texture("icons_mici/offroad_alerts/big_alert_pressed.png", self.ALERT_WIDTH, self.ALERT_HEIGHT_BIG) + + # Load warning icons + self._icon_orange = gui_app.texture("icons_mici/offroad_alerts/orange_warning.png", self.ICON_SIZE, self.ICON_SIZE) + self._icon_red = gui_app.texture("icons_mici/offroad_alerts/red_warning.png", self.ICON_SIZE, self.ICON_SIZE) + self._icon_green = gui_app.texture("icons_mici/offroad_alerts/green_wheel.png", self.ICON_SIZE, self.ICON_SIZE) + + self._title_label = UnifiedLabel(text="", font_size=32, font_weight=FontWeight.SEMI_BOLD, text_color=self.TEXT_COLOR, + alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT, + alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP, line_height=0.95) + + self._body_label = UnifiedLabel(text="", font_size=28, font_weight=FontWeight.ROMAN, text_color=self.TEXT_COLOR, + alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT, + alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM, line_height=0.95) + + self._title_text = "" + self._body_text = "" + self._alert_size = AlertSize.SMALL + + self._update_content() + + def _split_text(self, text: str) -> tuple[str, str]: + """Split text into title (first sentence) and body (remaining text).""" + # Find the end of the first sentence (period, exclamation, or question mark followed by space or end) + match = re.search(r'[.!?](?:\s+|$)', text) + if match: + # Found a sentence boundary - split at the end of the sentence + title = text[:match.start()].strip() + body = text[match.end():].strip() + return title, body + else: + # No sentence boundary found, return full text as title + return "", text + + def _update_content(self): + """Update text and calculate height.""" + if not self.alert_data.visible or not self.alert_data.text: + self.set_visible(False) + return + + self.set_visible(True) + + # Split text into title and body + self._title_text, self._body_text = self._split_text(self.alert_data.text) + + # Calculate text width (alert width minus padding and icon space on right) + title_width = self.ALERT_WIDTH - (self.ALERT_PADDING * 2) - self.ICON_SIZE - self.ICON_MARGIN + body_width = self.ALERT_WIDTH - (self.ALERT_PADDING * 2) + + # Update labels + self._title_label.set_text(self._title_text) + self._body_label.set_text(self._body_text) + + # Calculate content height + title_height = self._title_label.get_content_height(title_width) if self._title_text else 0 + body_height = self._body_label.get_content_height(body_width) if self._body_text else 0 + spacing = self.TITLE_BODY_SPACING if (self._title_text and self._body_text) else 0 + total_text_height = title_height + spacing + body_height + + # Determine which background size to use based on content height + min_height_with_padding = total_text_height + (self.ALERT_PADDING * 2) + if min_height_with_padding > self.ALERT_HEIGHT_MED: + self._alert_size = AlertSize.BIG + height = self.ALERT_HEIGHT_BIG + elif min_height_with_padding > self.ALERT_HEIGHT_SMALL: + self._alert_size = AlertSize.MEDIUM + height = self.ALERT_HEIGHT_MED + else: + self._alert_size = AlertSize.SMALL + height = self.ALERT_HEIGHT_SMALL + + # Set rect size + self.set_rect(rl.Rectangle(0, 0, self.ALERT_WIDTH, height)) + + def update_alert_data(self, alert_data: AlertData): + """Update alert data and refresh display.""" + self.alert_data = alert_data + self._update_content() + + def _render(self, _): + if not self.alert_data.visible or not self.alert_data.text: + return + + # Choose background based on size + if self._alert_size == AlertSize.BIG: + bg_texture = self._bg_big_pressed if self.is_pressed else self._bg_big + elif self._alert_size == AlertSize.MEDIUM: + bg_texture = self._bg_medium_pressed if self.is_pressed else self._bg_medium + else: # AlertSize.SMALL + bg_texture = self._bg_small_pressed if self.is_pressed else self._bg_small + + # Draw background + rl.draw_texture(bg_texture, int(self._rect.x), int(self._rect.y), rl.WHITE) + + # Calculate text area (left side, avoiding icon on right) + title_width = self.ALERT_WIDTH - (self.ALERT_PADDING * 2) - self.ICON_SIZE - self.ICON_MARGIN + body_width = self.ALERT_WIDTH - (self.ALERT_PADDING * 2) + text_x = self._rect.x + self.ALERT_PADDING + text_y = self._rect.y + self.ALERT_PADDING + + # Draw title label + if self._title_text: + title_rect = rl.Rectangle( + text_x, + text_y, + title_width, + self._title_label.get_content_height(title_width), + ) + self._title_label.render(title_rect) + text_y += title_rect.height + self.TITLE_BODY_SPACING + + # Draw body label + if self._body_text: + body_rect = rl.Rectangle( + text_x, + text_y, + body_width, + self._rect.height - text_y + self._rect.y - self.ALERT_PADDING, + ) + self._body_label.render(body_rect) + + # Draw warning icon on the right side + # Use green icon for update alerts (severity = -1), red for high severity, orange for low severity + if self.alert_data.severity == -1: + icon_texture = self._icon_green + elif self.alert_data.severity > 0: + icon_texture = self._icon_red + else: + icon_texture = self._icon_orange + icon_x = self._rect.x + self.ALERT_WIDTH - self.ALERT_PADDING - self.ICON_SIZE + icon_y = self._rect.y + self.ALERT_PADDING + rl.draw_texture(icon_texture, int(icon_x), int(icon_y), rl.WHITE) + + +class MiciOffroadAlerts(Widget): + """Offroad alerts layout with vertical scrolling.""" + + def __init__(self): + super().__init__() + self.params = Params() + self.sorted_alerts: list[AlertData] = [] + self.alert_items: list[AlertItem] = [] + self._last_refresh = 0.0 + + # Create vertical scroller + self._scroller = Scroller([], horizontal=False, spacing=12, pad_start=0, pad_end=0, snap_items=False) + + # Create empty state label + self._empty_label = UnifiedLabel(tr("no alerts"), 65, FontWeight.DISPLAY, rl.WHITE, + alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER, + alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE) + + # Build initial alert list + self._build_alerts() + + def active_alerts(self) -> int: + return sum(alert.visible for alert in self.sorted_alerts) + + def scrolling(self): + return self._scroller.scroll_panel.is_touch_valid() + + def _build_alerts(self): + """Build sorted list of alerts from OFFROAD_ALERTS.""" + self.sorted_alerts = [] + + # Add UpdateAvailable alert at the top (severity = -1 to indicate special handling) + update_alert_data = AlertData(key="UpdateAvailable", text="", severity=-1) + self.sorted_alerts.append(update_alert_data) + update_alert_item = AlertItem(update_alert_data) + self.alert_items.append(update_alert_item) + self._scroller.add_widget(update_alert_item) + + # Add regular alerts sorted by severity + for key, config in sorted(OFFROAD_ALERTS.items(), key=lambda x: x[1].get("severity", 0), reverse=True): + severity = config.get("severity", 0) + alert_data = AlertData(key=key, text="", severity=severity) + self.sorted_alerts.append(alert_data) + + # Create alert item widget + alert_item = AlertItem(alert_data) + self.alert_items.append(alert_item) + self._scroller.add_widget(alert_item) + + def refresh(self) -> int: + """Refresh alerts from params and return active count.""" + active_count = 0 + + # Handle UpdateAvailable alert specially + update_available = self.params.get_bool("UpdateAvailable") + update_alert_data = next((alert_data for alert_data in self.sorted_alerts if alert_data.key == "UpdateAvailable"), None) + + if update_alert_data: + if update_available: + # Default text + update_alert_data.text = "update available. go to comma.ai/blog to read the release notes." + + # Get new version description and parse version and date + new_desc = self.params.get("UpdaterNewDescription") or "" + if new_desc: + # Parse description (format: "version / branch / commit / date") + parts = new_desc.split(" / ") + if len(parts) > 3: + version, date = parts[0], parts[3] + update_alert_data.text = f"update available\n openpilot {version}, {date}. go to comma.ai/blog to read the release notes." + + update_alert_data.visible = True + active_count += 1 + else: + update_alert_data.text = "" + update_alert_data.visible = False + + # Handle regular alerts + for alert_data in self.sorted_alerts: + if alert_data.key == "UpdateAvailable": + continue # Skip, already handled above + + text = "" + alert_json = self.params.get(alert_data.key) + + if alert_json: + text = alert_json.get("text", "").replace("%1", alert_json.get("extra", "")) + + alert_data.text = text + alert_data.visible = bool(text) + + if alert_data.visible: + active_count += 1 + + # Update alert items (they reference the same alert_data objects) + for alert_item in self.alert_items: + alert_item.update_alert_data(alert_item.alert_data) + + return active_count + + def show_event(self): + """Reset scroll position when shown and refresh alerts.""" + self._scroller.show_event() + self._last_refresh = time.monotonic() + self.refresh() + + def _update_state(self): + """Periodically refresh alerts.""" + # Refresh alerts periodically, not every frame + current_time = time.monotonic() + if current_time - self._last_refresh >= REFRESH_INTERVAL: + self.refresh() + self._last_refresh = current_time + + def _render(self, rect: rl.Rectangle): + """Render the alerts scroller or empty state.""" + if self.active_alerts() == 0: + self._empty_label.render(rect) + else: + self._scroller.render(rect) diff --git a/selfdrive/ui/mici/layouts/onboarding.py b/selfdrive/ui/mici/layouts/onboarding.py new file mode 100644 index 000000000..6036393aa --- /dev/null +++ b/selfdrive/ui/mici/layouts/onboarding.py @@ -0,0 +1,552 @@ +from enum import IntEnum +from collections.abc import Callable + +import pyray as rl +from openpilot.system.ui.lib.application import FontWeight, gui_app +from openpilot.system.ui.widgets import Widget +from openpilot.system.ui.widgets.button import SmallButton +from openpilot.system.ui.widgets.label import UnifiedLabel +from openpilot.system.ui.widgets.slider import SmallSlider +from openpilot.system.ui.mici_setup import TermsHeader, TermsPage as SetupTermsPage +from openpilot.selfdrive.ui.ui_state import ui_state, device +from openpilot.selfdrive.ui.mici.onroad.confidence_ball import ConfidenceBall +from openpilot.selfdrive.ui.mici.onroad.torque_bar import TorqueBar +from openpilot.selfdrive.ui.mici.onroad.driver_state import DriverStateRenderer +from openpilot.selfdrive.ui.mici.onroad.driver_camera_dialog import DriverCameraDialog +from openpilot.system.ui.widgets.label import gui_label +from openpilot.system.ui.lib.multilang import tr + + +class OnboardingState(IntEnum): + TERMS = 0 + ONBOARDING = 1 + DECLINE = 2 + + +class DriverCameraSetupDialog(DriverCameraDialog): + def __init__(self, confirm_callback: Callable): + super().__init__(no_escape=True) + self.driver_state_renderer = DriverStateRenderer(confirm_mode=True, confirm_callback=confirm_callback) + self.driver_state_renderer.set_rect(rl.Rectangle(0, 0, 200, 200)) + self.driver_state_renderer.load_icons() + + def _render(self, rect): + rl.begin_scissor_mode(int(rect.x), int(rect.y), int(rect.width), int(rect.height)) + self._camera_view._render(rect) + + if not self._camera_view.frame: + gui_label(rect, tr("camera starting"), font_size=64, font_weight=FontWeight.BOLD, + alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER) + rl.end_scissor_mode() + return -1 + + # Position dmoji on opposite side from driver + # TODO: we don't have design for RHD yet + is_rhd = False + driver_state_rect = ( + rect.x if is_rhd else rect.x + rect.width - self.driver_state_renderer.rect.width, + rect.y + (rect.height - self.driver_state_renderer.rect.height) / 2, + ) + self.driver_state_renderer.set_position(*driver_state_rect) + self.driver_state_renderer.render() + + rl.end_scissor_mode() + return -1 + + +class TrainingGuideIntro(SetupTermsPage): + def __init__(self, continue_callback): + super().__init__(continue_callback, continue_text="continue") + self._title_header = TermsHeader("welcome to openpilot", gui_app.texture("icons_mici/offroad_alerts/green_wheel.png", 60, 60)) + + self._dm_label = UnifiedLabel("Before we get on the road, let's review the " + + "functionality and limitations of openpilot.", 36, + FontWeight.ROMAN) + + @property + def _content_height(self): + return self._dm_label.rect.y + self._dm_label.rect.height - self._scroll_panel.get_offset() + + def _render_content(self, scroll_offset): + self._title_header.render(rl.Rectangle( + self._rect.x + 16, + self._rect.y + 16 + scroll_offset, + self._title_header.rect.width, + self._title_header.rect.height, + )) + + self._dm_label.render(rl.Rectangle( + self._rect.x + 16, + self._title_header.rect.y + self._title_header.rect.height + 16, + self._rect.width - 32, + self._dm_label.get_content_height(int(self._rect.width - 32)), + )) + + +class TrainingGuidePreDMTutorial(SetupTermsPage): + def __init__(self, continue_callback): + super().__init__(continue_callback, continue_text="continue") + self._title_header = TermsHeader("driver monitoring setup", gui_app.texture("icons_mici/setup/green_dm.png", 60, 60)) + + self._dm_label = UnifiedLabel("Next, we'll ensure comma four is mounted properly.\n\nIf it does not have a clear view of the driver, " + + "simply unplug and remount before continuing.\n\n" + + "NOTE: the driver camera will have a purple tint due to the IR illumination used for seeing at night.", 36, + FontWeight.ROMAN) + + def show_event(self): + super().show_event() + # Get driver monitoring model ready for next step + ui_state.params.put_bool("IsDriverViewEnabled", True) + + @property + def _content_height(self): + return self._dm_label.rect.y + self._dm_label.rect.height - self._scroll_panel.get_offset() + + def _render_content(self, scroll_offset): + self._title_header.render(rl.Rectangle( + self._rect.x + 16, + self._rect.y + 16 + scroll_offset, + self._title_header.rect.width, + self._title_header.rect.height, + )) + + self._dm_label.render(rl.Rectangle( + self._rect.x + 16, + self._title_header.rect.y + self._title_header.rect.height + 16, + self._rect.width - 32, + self._dm_label.get_content_height(int(self._rect.width - 32)), + )) + + +class TrainingGuideDMTutorial(Widget): + def __init__(self, continue_callback): + super().__init__() + self._title_header = TermsHeader("fill the circle to continue", gui_app.texture("icons_mici/setup/green_dm.png", 60, 60)) + + self._dialog = DriverCameraSetupDialog(continue_callback) + + # Disable driver monitoring model when device times out for inactivity + def inactivity_callback(): + ui_state.params.put_bool("IsDriverViewEnabled", False) + + device.add_interactive_timeout_callback(inactivity_callback) + + def show_event(self): + super().show_event() + self._dialog.show_event() + + def _update_state(self): + super()._update_state() + if device.awake: + ui_state.params.put_bool("IsDriverViewEnabled", True) + + def _render(self, _): + self._dialog.render(self._rect) + + rl.draw_rectangle_gradient_v(int(self._rect.x), int(self._rect.y + self._rect.height - self._title_header.rect.height * 1.5 - 32), + int(self._rect.width), int(self._title_header.rect.height * 1.5 + 32), + rl.BLANK, rl.Color(0, 0, 0, 150)) + self._title_header.render(rl.Rectangle( + self._rect.x + 16, + self._rect.y + self._rect.height - self._title_header.rect.height - 16, + self._title_header.rect.width, + self._title_header.rect.height, + )) + + +class TrainingGuideRecordFront(SetupTermsPage): + def __init__(self, continue_callback): + def on_back(): + ui_state.params.put_bool("RecordFront", False) + continue_callback() + + def on_continue(): + ui_state.params.put_bool("RecordFront", True) + continue_callback() + + super().__init__(on_continue, back_callback=on_back, back_text="no", continue_text="yes") + self._title_header = TermsHeader("improve driver monitoring", gui_app.texture("icons_mici/setup/green_dm.png", 60, 60)) + + self._dm_label = UnifiedLabel("Help improve driver monitoring by including your driving data in the training data set. " + + "Your preference can be changed at any time in Settings. Would you like to share your data?", 36, + FontWeight.ROMAN) + + def show_event(self): + super().show_event() + # Disable driver monitoring model after last step + ui_state.params.put_bool("IsDriverViewEnabled", False) + + @property + def _content_height(self): + return self._dm_label.rect.y + self._dm_label.rect.height - self._scroll_panel.get_offset() + + def _render_content(self, scroll_offset): + self._title_header.render(rl.Rectangle( + self._rect.x + 16, + self._rect.y + 16 + scroll_offset, + self._title_header.rect.width, + self._title_header.rect.height, + )) + + self._dm_label.render(rl.Rectangle( + self._rect.x + 16, + self._title_header.rect.y + self._title_header.rect.height + 16, + self._rect.width - 32, + self._dm_label.get_content_height(int(self._rect.width - 32)), + )) + + +class TrainingGuideAttentionNotice1(SetupTermsPage): + def __init__(self, continue_callback): + super().__init__(continue_callback, continue_text="continue") + self._title_header = TermsHeader("not a self driving car", gui_app.texture("icons_mici/setup/warning.png", 60, 60)) + self._warning_label = UnifiedLabel("THIS IS A DRIVER ASSISTANCE SYSTEM. A DRIVER ASSISTANCE SYSTEM IS NOT A SELF DRIVING CAR.", 36, + FontWeight.ROMAN) + + @property + def _content_height(self): + return self._warning_label.rect.y + self._warning_label.rect.height - self._scroll_panel.get_offset() + + def _render_content(self, scroll_offset): + self._title_header.render(rl.Rectangle( + self._rect.x + 16, + self._rect.y + 16 + scroll_offset, + self._title_header.rect.width, + self._title_header.rect.height, + )) + + self._warning_label.render(rl.Rectangle( + self._rect.x + 16, + self._title_header.rect.y + self._title_header.rect.height + 16, + self._rect.width - 32, + self._warning_label.get_content_height(int(self._rect.width - 32)), + )) + + +class TrainingGuideAttentionNotice2(SetupTermsPage): + def __init__(self, continue_callback): + super().__init__(continue_callback, continue_text="continue") + self._title_header = TermsHeader("attention is required", gui_app.texture("icons_mici/setup/warning.png", 60, 60)) + self._warning_label = UnifiedLabel("YOU MUST PAY ATTENTION AT ALL TIMES. YOU ARE FULLY RESPONSIBLE FOR DRIVING THE CAR.", 36, + FontWeight.ROMAN) + + @property + def _content_height(self): + return self._warning_label.rect.y + self._warning_label.rect.height - self._scroll_panel.get_offset() + + def _render_content(self, scroll_offset): + self._title_header.render(rl.Rectangle( + self._rect.x + 16, + self._rect.y + 16 + scroll_offset, + self._title_header.rect.width, + self._title_header.rect.height, + )) + + self._warning_label.render(rl.Rectangle( + self._rect.x + 16, + self._title_header.rect.y + self._title_header.rect.height + 16, + self._rect.width - 32, + self._warning_label.get_content_height(int(self._rect.width - 32)), + )) + + +class TrainingGuideDisengaging(SetupTermsPage): + def __init__(self, continue_callback): + super().__init__(continue_callback, continue_text="continue") + self._title_header = TermsHeader("disengaging openpilot", gui_app.texture("icons_mici/setup/green_pedal.png", 60, 60)) + self._warning_label = UnifiedLabel("You can disengage openpilot by either pressing the brake pedal or " + + "the cancel button on your steering wheel.", 36, + FontWeight.ROMAN) + + @property + def _content_height(self): + return self._warning_label.rect.y + self._warning_label.rect.height - self._scroll_panel.get_offset() + + def _render_content(self, scroll_offset): + self._title_header.render(rl.Rectangle( + self._rect.x + 16, + self._rect.y + 16 + scroll_offset, + self._title_header.rect.width, + self._title_header.rect.height, + )) + + self._warning_label.render(rl.Rectangle( + self._rect.x + 16, + self._title_header.rect.y + self._title_header.rect.height + 16, + self._rect.width - 32, + self._warning_label.get_content_height(int(self._rect.width - 32)), + )) + + +class TrainingGuideConfidenceBall(SetupTermsPage): + ANIMATION_PAUSE = 3.5 + + def __init__(self, continue_callback): + super().__init__(continue_callback, continue_text="continue") + self._confidence_ball = ConfidenceBall(demo=True) + self._start_time = 0.0 + + self._title_header = TermsHeader("confidence ball", gui_app.texture("icons_mici/setup/green_car.png", 60, 60)) + self._warning_label = UnifiedLabel("The ball on the right communicates how confident the driving " + + "model is about the road scene at any given time.", 36, + FontWeight.ROMAN) + + def show_event(self): + super().show_event() + self._start_time = rl.get_time() + + @property + def _content_height(self): + return self._warning_label.rect.y + self._warning_label.rect.height - self._scroll_panel.get_offset() + + def _render_content(self, scroll_offset): + self._title_header.render(rl.Rectangle( + self._rect.x + 16, + self._rect.y + 16 + scroll_offset, + self._title_header.rect.width, + self._title_header.rect.height, + )) + + # room for confidence ball + label_width = self._rect.width - 32 - 60 + self._warning_label.render(rl.Rectangle( + self._rect.x + 16, + self._title_header.rect.y + self._title_header.rect.height + 16, + label_width, + self._warning_label.get_content_height(int(label_width)), + )) + + duration = rl.get_time() - self._start_time + if duration > 5 + self.ANIMATION_PAUSE * 2: + # reset animation + self._start_time = rl.get_time() + if duration > 5 + self.ANIMATION_PAUSE: + self._confidence_ball.update_filter(0.1) + elif duration > 5: + self._confidence_ball.update_filter(0.4) + elif duration > 0.5: + self._confidence_ball.update_filter(0.9) + + self._confidence_ball.render(self._rect) + self._rect.width -= 60 + + +class TrainingGuideSteeringArc(SetupTermsPage): + ANIMATION_PAUSE = 2 + TORQUE_BAR_HEIGHT = 100 + + def __init__(self, continue_callback): + super().__init__(continue_callback, continue_text="finish") + self._torque_bar = TorqueBar(demo=True) + self._start_time = 0.0 + + self._title_header = TermsHeader("steering arc", gui_app.texture("icons_mici/offroad_alerts/green_wheel.png", 60, 60)) + self._warning_label = UnifiedLabel("All cars limit the amount of steering that openpilot is able to apply. While driving, the " + + "steering arc shows the current amount of force being applied in relation to the maximum available to openpilot. " + + "You may need to assist if you see the arc nearing its orange state.", 36, + FontWeight.ROMAN) + + def show_event(self): + super().show_event() + self._start_time = rl.get_time() + + @property + def _content_height(self): + return self._warning_label.rect.y + self._warning_label.rect.height - self._scroll_panel.get_offset() + self.TORQUE_BAR_HEIGHT + + def _render_content(self, scroll_offset): + self._title_header.render(rl.Rectangle( + self._rect.x + 16, + self._rect.y + 16 + scroll_offset, + self._title_header.rect.width, + self._title_header.rect.height, + )) + + self._warning_label.render(rl.Rectangle( + self._rect.x + 16, + self._title_header.rect.y + self._title_header.rect.height + 16, + self._rect.width - 32, + self._warning_label.get_content_height(int(self._rect.width - 32)), + )) + + duration = rl.get_time() - self._start_time + if duration > self.ANIMATION_PAUSE * 5: + # reset animation + self._start_time = rl.get_time() + elif duration > self.ANIMATION_PAUSE * 4: + self._torque_bar.update_filter(-1.0) + elif duration > self.ANIMATION_PAUSE * 3: + self._torque_bar.update_filter(-0.2) + elif duration > self.ANIMATION_PAUSE * 2: + self._torque_bar.update_filter(1.0) + elif duration > self.ANIMATION_PAUSE: + self._torque_bar.update_filter(0.7) + else: + self._torque_bar.update_filter(0.0) + + # background gradient for torque bar legibility + rl.draw_rectangle_gradient_v(int(self._rect.x), int(self._rect.y + self._rect.height * 0.6), + int(self._rect.width), int(self._rect.height * 0.2), + rl.BLANK, rl.Color(0, 0, 0, int(255 * 0.9))) + rl.draw_rectangle(int(self._rect.x), int(self._rect.y + self._rect.height * 0.8), + int(self._rect.width), int(self._rect.height * 0.2), + rl.Color(0, 0, 0, int(255 * 0.9))) + + # scroll torque bar once we get to the bottom of content + torque_y_offset = min(0.0, self._warning_label.rect.y + self._warning_label.rect.height - + self._rect.height + self.TORQUE_BAR_HEIGHT) + torque_rect = rl.Rectangle( + self._rect.x, + self._rect.y + torque_y_offset, + self._rect.width, + self._rect.height, + ) + self._torque_bar.render(torque_rect) + + +class TrainingGuide(Widget): + def __init__(self, completed_callback=None): + super().__init__() + self._completed_callback = completed_callback + self._step = 0 + + self._steps = [ + TrainingGuideIntro(continue_callback=self._advance_step), + TrainingGuideAttentionNotice1(continue_callback=self._advance_step), + TrainingGuideAttentionNotice2(continue_callback=self._advance_step), + TrainingGuidePreDMTutorial(continue_callback=self._advance_step), + TrainingGuideDMTutorial(continue_callback=self._advance_step), + TrainingGuideRecordFront(continue_callback=self._advance_step), + TrainingGuideDisengaging(continue_callback=self._advance_step), + TrainingGuideConfidenceBall(continue_callback=self._advance_step), + TrainingGuideSteeringArc(continue_callback=self._advance_step), + ] + + def _advance_step(self): + if self._step < len(self._steps) - 1: + self._step += 1 + self._steps[self._step].show_event() + else: + self._step = 0 + if self._completed_callback: + self._completed_callback() + + def _render(self, _): + if self._step < len(self._steps): + self._steps[self._step].render(self._rect) + return -1 + + +class DeclinePage(Widget): + def __init__(self, back_callback=None): + super().__init__() + self._uninstall_slider = SmallSlider("uninstall openpilot", self._on_uninstall) + + self._back_button = SmallButton("back") + self._back_button.set_click_callback(back_callback) + + self._warning_header = TermsHeader("you must accept the\nterms to use openpilot", + gui_app.texture("icons_mici/setup/red_warning.png", 66, 60)) + + def _on_uninstall(self): + ui_state.params.put_bool("DoUninstall", True) + gui_app.request_close() + + def _render(self, _): + self._warning_header.render(rl.Rectangle( + self._rect.x + 16, + self._rect.y + 16, + self._warning_header.rect.width, + self._warning_header.rect.height, + )) + + self._back_button.set_opacity(1 - self._uninstall_slider.slider_percentage) + self._back_button.render(rl.Rectangle( + self._rect.x + 8, + self._rect.y + self._rect.height - self._back_button.rect.height, + self._back_button.rect.width, + self._back_button.rect.height, + )) + + self._uninstall_slider.render(rl.Rectangle( + self._rect.x + self._rect.width - self._uninstall_slider.rect.width, + self._rect.y + self._rect.height - self._uninstall_slider.rect.height, + self._uninstall_slider.rect.width, + self._uninstall_slider.rect.height, + )) + + +class TermsPage(SetupTermsPage): + def __init__(self, on_accept=None, on_decline=None): + super().__init__(on_accept, on_decline, "decline") + + info_txt = gui_app.texture("icons_mici/setup/green_info.png", 60, 60) + self._title_header = TermsHeader("scroll down to read &\n accept terms", info_txt) + + self._terms_label = UnifiedLabel("You must accept the Terms and Conditions to use openpilot. " + + "Read the latest terms at https://comma.ai/terms before continuing.", 36, + FontWeight.ROMAN) + + @property + def _content_height(self): + return self._terms_label.rect.y + self._terms_label.rect.height - self._scroll_panel.get_offset() + + def _render_content(self, scroll_offset): + self._title_header.set_position(self._rect.x + 16, self._rect.y + 12 + scroll_offset) + self._title_header.render() + + self._terms_label.render(rl.Rectangle( + self._rect.x + 16, + self._title_header.rect.y + self._title_header.rect.height + self.ITEM_SPACING, + self._rect.width - 100, + self._terms_label.get_content_height(int(self._rect.width - 100)), + )) + + +class OnboardingWindow(Widget): + def __init__(self): + super().__init__() + self._current_terms_version = ui_state.params.get("TermsVersion") + self._current_training_version = ui_state.params.get("TrainingVersion") + self._accepted_terms: bool = ui_state.params.get("HasAcceptedTerms") == self._current_terms_version + self._training_done: bool = ui_state.params.get("CompletedTrainingVersion") == self._current_training_version + + self._state = OnboardingState.TERMS if not self._accepted_terms else OnboardingState.ONBOARDING + + self.set_rect(rl.Rectangle(0, 0, 458, gui_app.height)) + + # Windows + self._terms = TermsPage(on_accept=self._on_terms_accepted, on_decline=self._on_terms_declined) + self._training_guide = TrainingGuide(completed_callback=self._on_completed_training) + self._decline_page = DeclinePage(back_callback=self._on_decline_back) + + @property + def completed(self) -> bool: + return self._accepted_terms and self._training_done + + def _on_terms_declined(self): + self._state = OnboardingState.DECLINE + + def _on_decline_back(self): + self._state = OnboardingState.TERMS + + def close(self): + ui_state.params.put_bool("IsDriverViewEnabled", False) + gui_app.set_modal_overlay(None) + + def _on_terms_accepted(self): + ui_state.params.put("HasAcceptedTerms", self._current_terms_version) + self._state = OnboardingState.ONBOARDING + + def _on_completed_training(self): + ui_state.params.put("CompletedTrainingVersion", self._current_training_version) + self.close() + + def _render(self, _): + if self._state == OnboardingState.TERMS: + self._terms.render(self._rect) + elif self._state == OnboardingState.ONBOARDING: + self._training_guide.render(self._rect) + elif self._state == OnboardingState.DECLINE: + self._decline_page.render(self._rect) + return -1 diff --git a/selfdrive/ui/mici/layouts/settings/developer.py b/selfdrive/ui/mici/layouts/settings/developer.py new file mode 100644 index 000000000..8fc63e896 --- /dev/null +++ b/selfdrive/ui/mici/layouts/settings/developer.py @@ -0,0 +1,151 @@ +import pyray as rl +from collections.abc import Callable + +from openpilot.common.time_helpers import system_time_valid +from openpilot.system.ui.widgets.scroller import Scroller +from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigToggle, BigParamControl +from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigInputDialog +from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.widgets import NavWidget +from openpilot.selfdrive.ui.layouts.settings.common import restart_needed_callback +from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.selfdrive.ui.widgets.ssh_key import SshKeyAction + + +class DeveloperLayoutMici(NavWidget): + def __init__(self, back_callback: Callable): + super().__init__() + self.set_back_callback(back_callback) + + def github_username_callback(username: str): + if username: + ssh_keys = SshKeyAction() + ssh_keys._fetch_ssh_key(username) + if not ssh_keys._error_message: + self._ssh_keys_btn.set_value(username) + else: + dlg = BigDialog("", ssh_keys._error_message) + gui_app.set_modal_overlay(dlg) + + def ssh_keys_callback(): + github_username = ui_state.params.get("GithubUsername") or "" + dlg = BigInputDialog("enter GitHub username", github_username, confirm_callback=github_username_callback) + if not system_time_valid(): + dlg = BigDialog("Please connect to Wi-Fi to fetch your key", "") + gui_app.set_modal_overlay(dlg) + return + gui_app.set_modal_overlay(dlg) + + txt_ssh = gui_app.texture("icons_mici/settings/developer/ssh.png", 77, 44) + github_username = ui_state.params.get("GithubUsername") or "" + self._ssh_keys_btn = BigButton("SSH keys", "Not set" if not github_username else github_username, icon=txt_ssh) + self._ssh_keys_btn.set_click_callback(ssh_keys_callback) + + # adb, ssh, ssh keys, debug mode, joystick debug mode, longitudinal maneuver mode, ip address + # ******** Main Scroller ******** + self._adb_toggle = BigParamControl("enable ADB", "AdbEnabled") + self._ssh_toggle = BigParamControl("enable SSH", "SshEnabled") + self._joystick_toggle = BigToggle("joystick debug mode", + initial_state=ui_state.params.get_bool("JoystickDebugMode"), + toggle_callback=self._on_joystick_debug_mode) + self._long_maneuver_toggle = BigToggle("longitudinal maneuver mode", + initial_state=ui_state.params.get_bool("LongitudinalManeuverMode"), + toggle_callback=self._on_long_maneuver_mode) + self._alpha_long_toggle = BigToggle("alpha longitudinal", + initial_state=ui_state.params.get_bool("AlphaLongitudinalEnabled"), + toggle_callback=self._on_alpha_long_enabled) + self._debug_mode_toggle = BigParamControl("ui debug mode", "ShowDebugInfo", + toggle_callback=lambda checked: (gui_app.set_show_touches(checked), + gui_app.set_show_fps(checked))) + + self._scroller = Scroller([ + self._adb_toggle, + self._ssh_toggle, + self._ssh_keys_btn, + self._joystick_toggle, + self._long_maneuver_toggle, + self._alpha_long_toggle, + self._debug_mode_toggle, + ], snap_items=False) + + # Toggle lists + self._refresh_toggles = ( + ("AdbEnabled", self._adb_toggle), + ("SshEnabled", self._ssh_toggle), + ("JoystickDebugMode", self._joystick_toggle), + ("LongitudinalManeuverMode", self._long_maneuver_toggle), + ("AlphaLongitudinalEnabled", self._alpha_long_toggle), + ("ShowDebugInfo", self._debug_mode_toggle), + ) + onroad_blocked_toggles = (self._adb_toggle, self._joystick_toggle) + release_blocked_toggles = (self._joystick_toggle, self._long_maneuver_toggle, self._alpha_long_toggle) + engaged_blocked_toggles = (self._long_maneuver_toggle, self._alpha_long_toggle) + + # Hide non-release toggles on release builds + for item in release_blocked_toggles: + item.set_visible(not ui_state.is_release) + + # Disable toggles that require offroad + for item in onroad_blocked_toggles: + item.set_enabled(lambda: ui_state.is_offroad()) + + # Disable toggles that require not engaged + for item in engaged_blocked_toggles: + item.set_enabled(lambda: not ui_state.engaged) + + # Set initial state + if ui_state.params.get_bool("ShowDebugInfo"): + gui_app.set_show_touches(True) + gui_app.set_show_fps(True) + + ui_state.add_offroad_transition_callback(self._update_toggles) + + def show_event(self): + super().show_event() + self._scroller.show_event() + self._update_toggles() + + def _render(self, rect: rl.Rectangle): + self._scroller.render(rect) + + def _update_toggles(self): + ui_state.update_params() + + # CP gating + if ui_state.CP is not None: + alpha_avail = ui_state.CP.alphaLongitudinalAvailable + if not alpha_avail or ui_state.is_release: + self._alpha_long_toggle.set_visible(False) + ui_state.params.remove("AlphaLongitudinalEnabled") + else: + self._alpha_long_toggle.set_visible(True) + + long_man_enabled = ui_state.has_longitudinal_control and ui_state.is_offroad() + self._long_maneuver_toggle.set_enabled(long_man_enabled) + if not long_man_enabled: + self._long_maneuver_toggle.set_checked(False) + ui_state.params.put_bool("LongitudinalManeuverMode", False) + else: + self._long_maneuver_toggle.set_enabled(False) + self._alpha_long_toggle.set_visible(False) + + # Refresh toggles from params to mirror external changes + for key, item in self._refresh_toggles: + item.set_checked(ui_state.params.get_bool(key)) + + def _on_joystick_debug_mode(self, state: bool): + ui_state.params.put_bool("JoystickDebugMode", state) + ui_state.params.put_bool("LongitudinalManeuverMode", False) + self._long_maneuver_toggle.set_checked(False) + + def _on_long_maneuver_mode(self, state: bool): + ui_state.params.put_bool("LongitudinalManeuverMode", state) + ui_state.params.put_bool("JoystickDebugMode", False) + self._joystick_toggle.set_checked(False) + restart_needed_callback(state) + + def _on_alpha_long_enabled(self, state: bool): + # TODO: show confirmation dialog before enabling + ui_state.params.put_bool("AlphaLongitudinalEnabled", state) + restart_needed_callback(state) + self._update_toggles() diff --git a/selfdrive/ui/mici/layouts/settings/device.py b/selfdrive/ui/mici/layouts/settings/device.py new file mode 100644 index 000000000..de2e11caf --- /dev/null +++ b/selfdrive/ui/mici/layouts/settings/device.py @@ -0,0 +1,378 @@ +import os +import threading +import json +import pyray as rl +from enum import IntEnum +from collections.abc import Callable + +from openpilot.common.basedir import BASEDIR +from openpilot.common.params import Params +from openpilot.common.time_helpers import system_time_valid +from openpilot.system.ui.widgets.scroller import Scroller +from openpilot.system.ui.lib.scroll_panel2 import GuiScrollPanel2 +from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigCircleButton +from openpilot.selfdrive.ui.mici.widgets.dialog import BigMultiOptionDialog, BigDialog, BigConfirmationDialogV2 +from openpilot.selfdrive.ui.mici.widgets.pairing_dialog import PairingDialog +from openpilot.selfdrive.ui.mici.onroad.driver_camera_dialog import DriverCameraDialog +from openpilot.selfdrive.ui.mici.layouts.onboarding import TrainingGuide +from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos +from openpilot.system.ui.lib.multilang import tr +from openpilot.system.ui.widgets import Widget, NavWidget +from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.system.ui.widgets.label import MiciLabel +from openpilot.system.ui.widgets.html_render import HtmlModal, HtmlRenderer +from openpilot.system.athena.registration import UNREGISTERED_DONGLE_ID + + +class MiciFccModal(NavWidget): + BACK_TOUCH_AREA_PERCENTAGE = 0.1 + + def __init__(self, file_path: str | None = None, text: str | None = None): + super().__init__() + self.set_back_callback(lambda: gui_app.set_modal_overlay(None)) + self._content = HtmlRenderer(file_path=file_path, text=text) + self._scroll_panel = GuiScrollPanel2(horizontal=False) + self._fcc_logo = gui_app.texture("icons_mici/settings/device/fcc_logo.png", 76, 64) + + def _render(self, rect: rl.Rectangle): + content_height = self._content.get_total_height(int(rect.width)) + content_height += self._fcc_logo.height + 20 + + scroll_content_rect = rl.Rectangle(rect.x, rect.y, rect.width, content_height) + scroll_offset = self._scroll_panel.update(rect, scroll_content_rect.height) + + fcc_pos = rl.Vector2(rect.x + 20, rect.y + 20 + scroll_offset) + + scroll_content_rect.y += scroll_offset + self._fcc_logo.height + 20 + self._content.render(scroll_content_rect) + + rl.draw_texture_ex(self._fcc_logo, fcc_pos, 0.0, 1.0, rl.WHITE) + + return -1 + + +def _engaged_confirmation_callback(callback: Callable, action_text: str): + if not ui_state.engaged: + def confirm_callback(): + # Check engaged again in case it changed while the dialog was open + if not ui_state.engaged: + callback() + + red = False + if action_text == "power off": + icon = "icons_mici/settings/device/power.png" + red = True + elif action_text == "reboot": + icon = "icons_mici/settings/device/reboot.png" + elif action_text == "reset": + icon = "icons_mici/settings/device/lkas.png" + elif action_text == "uninstall": + icon = "icons_mici/settings/device/uninstall.png" + else: + # TODO: check + icon = "icons_mici/settings/comma_icon.png" + + dlg: BigConfirmationDialogV2 | BigDialog = BigConfirmationDialogV2(f"slide to\n{action_text.lower()}", icon, red=red, + exit_on_confirm=action_text == "reset", + confirm_callback=confirm_callback) + gui_app.set_modal_overlay(dlg) + else: + dlg = BigDialog(f"Disengage to {action_text}", "") + gui_app.set_modal_overlay(dlg) + + +class DeviceInfoLayoutMici(Widget): + def __init__(self): + super().__init__() + + self.set_rect(rl.Rectangle(0, 0, 360, 180)) + + params = Params() + header_color = rl.Color(255, 255, 255, int(255 * 0.9)) + subheader_color = rl.Color(255, 255, 255, int(255 * 0.9 * 0.65)) + max_width = int(self._rect.width - 20) + self._dongle_id_label = MiciLabel("device ID", 48, width=max_width, color=header_color, font_weight=FontWeight.DISPLAY) + self._dongle_id_text_label = MiciLabel(params.get("DongleId") or 'N/A', 32, width=max_width, color=subheader_color, font_weight=FontWeight.ROMAN) + + self._serial_number_label = MiciLabel("serial", 48, color=header_color, font_weight=FontWeight.DISPLAY) + self._serial_number_text_label = MiciLabel(params.get("HardwareSerial") or 'N/A', 32, width=max_width, color=subheader_color, font_weight=FontWeight.ROMAN) + + def _render(self, _): + self._dongle_id_label.set_position(self._rect.x + 20, self._rect.y - 10) + self._dongle_id_label.render() + + self._dongle_id_text_label.set_position(self._rect.x + 20, self._rect.y + 68 - 25) + self._dongle_id_text_label.render() + + self._serial_number_label.set_position(self._rect.x + 20, self._rect.y + 114 - 30) + self._serial_number_label.render() + + self._serial_number_text_label.set_position(self._rect.x + 20, self._rect.y + 161 - 25) + self._serial_number_text_label.render() + + +class UpdaterState(IntEnum): + IDLE = 0 + WAITING_FOR_UPDATER = 1 + UPDATER_RESPONDING = 2 + + +class PairBigButton(BigButton): + def __init__(self): + super().__init__("pair", "connect.comma.ai", "icons_mici/settings/comma_icon.png") + + def _update_state(self): + if ui_state.prime_state.is_paired(): + self.set_text("paired") + if ui_state.prime_state.is_prime(): + self.set_value("subscribed") + else: + self.set_value("upgrade to prime") + else: + self.set_text("pair") + self.set_value("connect.comma.ai") + + def _handle_mouse_release(self, mouse_pos: MousePos): + super()._handle_mouse_release(mouse_pos) + + # TODO: show ad dialog when clicked if not prime + if ui_state.prime_state.is_paired(): + return + dlg: BigDialog | PairingDialog + if not system_time_valid(): + dlg = BigDialog(tr("Please connect to Wi-Fi to complete initial pairing"), "") + elif UNREGISTERED_DONGLE_ID == (ui_state.params.get("DongleId") or UNREGISTERED_DONGLE_ID): + dlg = BigDialog(tr("Device must be registered with the comma.ai backend to pair"), "") + else: + dlg = PairingDialog() + gui_app.set_modal_overlay(dlg) + + +UPDATER_TIMEOUT = 10.0 # seconds to wait for updater to respond + + +class UpdateOpenpilotBigButton(BigButton): + def __init__(self): + self._txt_update_icon = gui_app.texture("icons_mici/settings/device/update.png", 64, 64) + self._txt_reboot_icon = gui_app.texture("icons_mici/settings/device/reboot.png", 64, 64) + self._txt_up_to_date_icon = gui_app.texture("icons_mici/settings/device/up_to_date.png", 64, 64) + super().__init__("update openpilot", "", self._txt_update_icon) + + self._waiting_for_updater_t: float | None = None + self._hide_value_t: float | None = None + self._state: UpdaterState = UpdaterState.IDLE + + ui_state.add_offroad_transition_callback(self.offroad_transition) + + def offroad_transition(self): + if ui_state.is_offroad(): + self.set_enabled(True) + + def _handle_mouse_release(self, mouse_pos: MousePos): + self.set_enabled(False) + self._state = UpdaterState.WAITING_FOR_UPDATER + self.set_icon(self._txt_update_icon) + + def run(): + if self.get_value() == "download update": + os.system("pkill -SIGHUP -f system.updated.updated") + elif self.get_value() == "update now": + ui_state.params.put_bool("DoReboot", True) + else: + os.system("pkill -SIGUSR1 -f system.updated.updated") + + threading.Thread(target=run, daemon=True).start() + + def set_value(self, value: str): + super().set_value(value) + if value: + self.set_text("") + else: + self.set_text("update openpilot") + + def _update_state(self): + if ui_state.started: + self.set_enabled(False) + return + + updater_state = ui_state.params.get("UpdaterState") or "" + failed_count = ui_state.params.get("UpdateFailedCount") + failed = False if failed_count is None else int(failed_count) > 0 + + if ui_state.params.get_bool("UpdateAvailable"): + self.set_rotate_icon(False) + self.set_enabled(True) + if self.get_value() != "update now": + self.set_value("update now") + self.set_icon(self._txt_reboot_icon) + + elif self._state == UpdaterState.WAITING_FOR_UPDATER: + self.set_rotate_icon(True) + if updater_state != "idle": + self._state = UpdaterState.UPDATER_RESPONDING + + # Recover from updater not responding (time invalid shortly after boot) + if self._waiting_for_updater_t is None: + self._waiting_for_updater_t = rl.get_time() + + if self._waiting_for_updater_t is not None and rl.get_time() - self._waiting_for_updater_t > UPDATER_TIMEOUT: + self.set_rotate_icon(False) + self.set_value("updater failed to respond") + self._state = UpdaterState.IDLE + self._hide_value_t = rl.get_time() + + elif self._state == UpdaterState.UPDATER_RESPONDING: + if updater_state == "idle": + self.set_rotate_icon(False) + self._state = UpdaterState.IDLE + self._hide_value_t = rl.get_time() + else: + if self.get_value() != updater_state: + self.set_value(updater_state) + + elif self._state == UpdaterState.IDLE: + self.set_rotate_icon(False) + if failed: + if self.get_value() != "failed to update": + self.set_value("failed to update") + + elif ui_state.params.get_bool("UpdaterFetchAvailable"): + self.set_enabled(True) + if self.get_value() != "download update": + self.set_value("download update") + + elif self._hide_value_t is not None: + self.set_enabled(True) + if self.get_value() == "checking...": + self.set_value("up to date") + self.set_icon(self._txt_up_to_date_icon) + + # Hide previous text after short amount of time (up to date or failed) + if rl.get_time() - self._hide_value_t > 3.0: + self._hide_value_t = None + self.set_value("") + self.set_icon(self._txt_update_icon) + else: + if self.get_value() != "": + self.set_value("") + + if self._state != UpdaterState.WAITING_FOR_UPDATER: + self._waiting_for_updater_t = None + + +class DeviceLayoutMici(NavWidget): + def __init__(self, back_callback: Callable): + super().__init__() + + self._fcc_dialog: HtmlModal | None = None + self._driver_camera: DriverCameraDialog | None = None + self._training_guide: TrainingGuide | None = None + + def power_off_callback(): + ui_state.params.put_bool("DoShutdown", True) + + def reboot_callback(): + ui_state.params.put_bool("DoReboot", True) + + def reset_calibration_callback(): + params = ui_state.params + params.remove("CalibrationParams") + params.remove("LiveTorqueParameters") + params.remove("LiveParameters") + params.remove("LiveParametersV2") + params.remove("LiveDelay") + params.put_bool("OnroadCycleRequested", True) + + def uninstall_openpilot_callback(): + ui_state.params.put_bool("DoUninstall", True) + + reset_calibration_btn = BigButton("reset calibration", "", "icons_mici/settings/device/lkas.png") + reset_calibration_btn.set_click_callback(lambda: _engaged_confirmation_callback(reset_calibration_callback, "reset")) + + uninstall_openpilot_btn = BigButton("uninstall openpilot", "", "icons_mici/settings/device/uninstall.png") + uninstall_openpilot_btn.set_click_callback(lambda: _engaged_confirmation_callback(uninstall_openpilot_callback, "uninstall")) + + reboot_btn = BigCircleButton("icons_mici/settings/device/reboot.png", red=False) + reboot_btn.set_click_callback(lambda: _engaged_confirmation_callback(reboot_callback, "reboot")) + + self._power_off_btn = BigCircleButton("icons_mici/settings/device/power.png", red=True) + self._power_off_btn.set_click_callback(lambda: _engaged_confirmation_callback(power_off_callback, "power off")) + + self._load_languages() + + def language_callback(): + def selected_language_callback(): + selected_language = dlg.get_selected_option() + ui_state.params.put("LanguageSetting", self._languages[selected_language]) + + current_language_name = ui_state.params.get("LanguageSetting") + current_language = next(name for name, lang in self._languages.items() if lang == current_language_name) + + dlg = BigMultiOptionDialog(list(self._languages), default=current_language, right_btn_callback=selected_language_callback) + gui_app.set_modal_overlay(dlg) + + # lang_button = BigButton("change language", "", "icons_mici/settings/device/language.png") + # lang_button.set_click_callback(language_callback) + + regulatory_btn = BigButton("regulatory info", "", "icons_mici/settings/device/info.png") + regulatory_btn.set_click_callback(self._on_regulatory) + + driver_cam_btn = BigButton("driver camera preview", "", "icons_mici/settings/device/cameras.png") + driver_cam_btn.set_click_callback(self._show_driver_camera) + driver_cam_btn.set_enabled(lambda: ui_state.is_offroad()) + + review_training_guide_btn = BigButton("review training guide", "", "icons_mici/settings/device/info.png") + review_training_guide_btn.set_click_callback(self._on_review_training_guide) + review_training_guide_btn.set_enabled(lambda: ui_state.is_offroad()) + + self._scroller = Scroller([ + DeviceInfoLayoutMici(), + UpdateOpenpilotBigButton(), + PairBigButton(), + review_training_guide_btn, + driver_cam_btn, + # lang_button, + reset_calibration_btn, + uninstall_openpilot_btn, + regulatory_btn, + reboot_btn, + self._power_off_btn, + ], snap_items=False) + + # Set up back navigation + self.set_back_callback(back_callback) + + # Hide power off button when onroad + ui_state.add_offroad_transition_callback(self._offroad_transition) + + def _on_regulatory(self): + if not self._fcc_dialog: + self._fcc_dialog = MiciFccModal(os.path.join(BASEDIR, "selfdrive/assets/offroad/mici_fcc.html")) + gui_app.set_modal_overlay(self._fcc_dialog, callback=setattr(self, '_fcc_dialog', None)) + + def _offroad_transition(self): + self._power_off_btn.set_visible(ui_state.is_offroad()) + + def _show_driver_camera(self): + if not self._driver_camera: + self._driver_camera = DriverCameraDialog() + gui_app.set_modal_overlay(self._driver_camera, callback=lambda result: setattr(self, '_driver_camera', None)) + + def _on_review_training_guide(self): + if not self._training_guide: + def completed_callback(): + gui_app.set_modal_overlay(None) + + self._training_guide = TrainingGuide(completed_callback=completed_callback) + gui_app.set_modal_overlay(self._training_guide, callback=lambda result: setattr(self, '_training_guide', None)) + + def _load_languages(self): + with open(os.path.join(BASEDIR, "selfdrive/ui/translations/languages.json")) as f: + self._languages = json.load(f) + + def show_event(self): + super().show_event() + self._scroller.show_event() + + def _render(self, rect: rl.Rectangle): + self._scroller.render(rect) diff --git a/selfdrive/ui/mici/layouts/settings/firehose.py b/selfdrive/ui/mici/layouts/settings/firehose.py new file mode 100644 index 000000000..303d976b0 --- /dev/null +++ b/selfdrive/ui/mici/layouts/settings/firehose.py @@ -0,0 +1,223 @@ +import threading +import time +import pyray as rl + +from openpilot.common.api import api_get +from openpilot.common.params import Params +from openpilot.common.swaglog import cloudlog +from openpilot.selfdrive.ui.lib.api_helpers import get_token +from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.system.athena.registration import UNREGISTERED_DONGLE_ID +from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE +from openpilot.system.ui.lib.wrap_text import wrap_text +from openpilot.system.ui.lib.scroll_panel2 import GuiScrollPanel2 +from openpilot.system.ui.lib.multilang import tr, trn, tr_noop +from openpilot.system.ui.widgets import NavWidget + + +TITLE = tr_noop("Firehose Mode") +DESCRIPTION = tr_noop( + "openpilot learns to drive by watching humans, like you, drive.\n\n" + + "Firehose Mode allows you to maximize your training data uploads to improve " + + "openpilot's driving models. More data means bigger models, which means better Experimental Mode." +) +INSTRUCTIONS_INTRO = tr_noop( + "For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.\n\n" + + "Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card." +) +FAQ_HEADER = tr_noop("Frequently Asked Questions") +FAQ_ITEMS = [ + (tr_noop("Does it matter how or where I drive?"), tr_noop("Nope, just drive as you normally would.")), + (tr_noop("Do all of my segments get pulled in Firehose Mode?"), tr_noop("No, we selectively pull a subset of your segments.")), + (tr_noop("What's a good USB-C adapter?"), tr_noop("Any fast phone or laptop charger should be fine.")), + (tr_noop("Does it matter which software I run?"), tr_noop("Yes, only upstream openpilot (and particular forks) are able to be used for training.")), +] + + +class FirehoseLayoutMici(NavWidget): + BACK_TOUCH_AREA_PERCENTAGE = 0.1 + + PARAM_KEY = "ApiCache_FirehoseStats" + GREEN = rl.Color(46, 204, 113, 255) + RED = rl.Color(231, 76, 60, 255) + GRAY = rl.Color(68, 68, 68, 255) + LIGHT_GRAY = rl.Color(228, 228, 228, 255) + UPDATE_INTERVAL = 30 # seconds + + def __init__(self, back_callback): + super().__init__() + self.set_back_callback(back_callback) + + self.params = Params() + self.segment_count = self._get_segment_count() + + self._scroll_panel = GuiScrollPanel2(horizontal=False) + self._content_height = 0 + + self._running = True + self._update_thread = threading.Thread(target=self._update_loop, daemon=True) + self._update_thread.start() + + def __del__(self): + self._running = False + try: + if self._update_thread and self._update_thread.is_alive(): + self._update_thread.join(timeout=1.0) + except Exception: + pass + + def show_event(self): + super().show_event() + self._scroll_panel.set_offset(0) + + def _get_segment_count(self) -> int: + stats = self.params.get(self.PARAM_KEY) + if not stats: + return 0 + try: + return int(stats.get("firehose", 0)) + except Exception: + cloudlog.exception(f"Failed to decode firehose stats: {stats}") + return 0 + + def _render(self, rect: rl.Rectangle): + # compute total content height for scrolling + content_height = self._measure_content_height(rect) + scroll_offset = self._scroll_panel.update(rect, content_height) + + # start drawing with offset + x = int(rect.x + 40) + y = int(rect.y + 40 + scroll_offset) + w = int(rect.width - 80) + + # Title + title_text = tr(TITLE) + title_font = gui_app.font(FontWeight.BOLD) + title_size = 64 + rl.draw_text_ex(title_font, title_text, rl.Vector2(x, y), title_size, 0, rl.WHITE) + y += int(title_size * FONT_SCALE) + 20 + + # Description + y = self._draw_wrapped_text(x, y, w, tr(DESCRIPTION), gui_app.font(FontWeight.ROMAN), 36, rl.WHITE) + y += 20 + + # Separator + rl.draw_rectangle(x, y, w, 2, self.GRAY) + y += 20 + + # Status + status_text, status_color = self._get_status() + y = self._draw_wrapped_text(x, y, w, status_text, gui_app.font(FontWeight.BOLD), 48, status_color) + y += 20 + + # Contribution count (if available) + if self.segment_count > 0: + contrib_text = trn("{} segment of your driving is in the training dataset so far.", + "{} segments of your driving is in the training dataset so far.", self.segment_count).format(self.segment_count) + y = self._draw_wrapped_text(x, y, w, contrib_text, gui_app.font(FontWeight.BOLD), 42, rl.WHITE) + y += 20 + + # Separator + rl.draw_rectangle(x, y, w, 2, self.GRAY) + y += 20 + + # Instructions intro + y = self._draw_wrapped_text(x, y, w, tr(INSTRUCTIONS_INTRO), gui_app.font(FontWeight.ROMAN), 32, self.LIGHT_GRAY) + y += 20 + + # FAQ Header + y = self._draw_wrapped_text(x, y, w, tr(FAQ_HEADER), gui_app.font(FontWeight.BOLD), 44, rl.WHITE) + y += 20 + + # FAQ Items + for question, answer in FAQ_ITEMS: + y = self._draw_wrapped_text(x, y, w, tr(question), gui_app.font(FontWeight.BOLD), 32, self.LIGHT_GRAY) + y = self._draw_wrapped_text(x, y, w, tr(answer), gui_app.font(FontWeight.ROMAN), 32, self.LIGHT_GRAY) + y += 20 + + # return value not used by NavWidget + return -1 + + def _draw_wrapped_text(self, x, y, width, text, font, font_size, color): + wrapped = wrap_text(font, text, font_size, width) + for line in wrapped: + rl.draw_text_ex(font, line, rl.Vector2(x, y), font_size, 0, color) + y += int(font_size * FONT_SCALE) + return y + + def _measure_content_height(self, rect: rl.Rectangle) -> int: + # Rough measurement using the same wrapping as rendering + w = int(rect.width - 80) + y = 40 + + # Title + title_size = 72 + y += int(title_size * FONT_SCALE) + 20 + + # Description + desc_lines = wrap_text(gui_app.font(FontWeight.ROMAN), tr(DESCRIPTION), 36, w) + y += int(len(desc_lines) * 36 * FONT_SCALE) + 20 + + # Separator + Status + y += 2 + 20 + status_text, _ = self._get_status() + status_lines = wrap_text(gui_app.font(FontWeight.BOLD), status_text, 48, w) + y += int(len(status_lines) * 48 * FONT_SCALE) + 20 + + # Contribution count + if self.segment_count > 0: + contrib_text = trn("{} segment of your driving is in the training dataset so far.", + "{} segments of your driving is in the training dataset so far.", self.segment_count).format(self.segment_count) + contrib_lines = wrap_text(gui_app.font(FontWeight.BOLD), contrib_text, 42, w) + y += int(len(contrib_lines) * 42 * FONT_SCALE) + 20 + + # Separator + Instructions + y += 2 + 20 + + # Instructions intro + intro_lines = wrap_text(gui_app.font(FontWeight.ROMAN), tr(INSTRUCTIONS_INTRO), 32, w) + y += int(len(intro_lines) * 32 * FONT_SCALE) + 20 + + # FAQ Header + faq_header_lines = wrap_text(gui_app.font(FontWeight.BOLD), tr(FAQ_HEADER), 44, w) + y += int(len(faq_header_lines) * 44 * FONT_SCALE) + 20 + + # FAQ Items + for question, answer in FAQ_ITEMS: + q_lines = wrap_text(gui_app.font(FontWeight.BOLD), tr(question), 32, w) + y += int(len(q_lines) * 32 * FONT_SCALE) + a_lines = wrap_text(gui_app.font(FontWeight.ROMAN), tr(answer), 32, w) + y += int(len(a_lines) * 32 * FONT_SCALE) + 20 + + # bottom padding + y += 40 + return y + + def _get_status(self) -> tuple[str, rl.Color]: + network_type = ui_state.sm["deviceState"].networkType + network_metered = ui_state.sm["deviceState"].networkMetered + + if not network_metered and network_type != 0: # Not metered and connected + return tr("ACTIVE"), self.GREEN + else: + return tr("INACTIVE: connect to an unmetered network"), self.RED + + def _fetch_firehose_stats(self): + try: + dongle_id = self.params.get("DongleId") + if not dongle_id or dongle_id == UNREGISTERED_DONGLE_ID: + return + identity_token = get_token(dongle_id) + response = api_get(f"v1/devices/{dongle_id}/firehose_stats", access_token=identity_token) + if response.status_code == 200: + data = response.json() + self.segment_count = data.get("firehose", 0) + self.params.put(self.PARAM_KEY, data) + except Exception as e: + cloudlog.error(f"Failed to fetch firehose stats: {e}") + + def _update_loop(self): + while self._running: + if not ui_state.started: + self._fetch_firehose_stats() + time.sleep(self.UPDATE_INTERVAL) diff --git a/selfdrive/ui/mici/layouts/settings/network.py b/selfdrive/ui/mici/layouts/settings/network.py new file mode 100644 index 000000000..a62c1d153 --- /dev/null +++ b/selfdrive/ui/mici/layouts/settings/network.py @@ -0,0 +1,558 @@ +import math +import numpy as np +import pyray as rl +from enum import IntEnum +from collections.abc import Callable + +from openpilot.common.swaglog import cloudlog +from openpilot.system.ui.widgets.scroller import Scroller +from openpilot.system.ui.widgets.label import UnifiedLabel +from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigMultiToggle, BigToggle +from openpilot.selfdrive.ui.mici.widgets.dialog import BigMultiOptionDialog, BigInputDialog, BigDialogOptionButton, BigConfirmationDialogV2 +from openpilot.system.ui.lib.application import gui_app, MousePos, FontWeight +from openpilot.system.ui.widgets import Widget, NavWidget +from openpilot.system.ui.lib.wifi_manager import WifiManager, Network, SecurityType, MeteredType + + +def normalize_ssid(ssid: str) -> str: + return ssid.replace("’", "'") # for iPhone hotspots + + +class NetworkPanelType(IntEnum): + NONE = 0 + WIFI = 1 + + +class LoadingAnimation(Widget): + def _render(self, _): + cx = int(self._rect.x + 70) + cy = int(self._rect.y + self._rect.height / 2 - 50) + + y_mag = 20 + anim_scale = 5 + spacing = 28 + + for i in range(3): + x = cx - spacing + i * spacing + y = int(cy + min(math.sin((rl.get_time() - i * 0.2) * anim_scale) * y_mag, 0)) + alpha = int(np.interp(cy - y, [0, y_mag], [255 * 0.45, 255 * 0.9])) + rl.draw_circle(x, y, 10, rl.Color(255, 255, 255, alpha)) + + +class WifiIcon(Widget): + def __init__(self): + super().__init__() + self.set_rect(rl.Rectangle(0, 0, 89, 64)) + + self._wifi_slash_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_slash.png", 89, 64) + self._wifi_none_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_none.png", 89, 64) + self._wifi_low_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_low.png", 89, 64) + self._wifi_medium_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_medium.png", 89, 64) + self._wifi_full_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_full.png", 89, 64) + self._lock_txt = gui_app.texture("icons_mici/settings/network/new/lock.png", 23, 32) + + self._network: Network | None = None + self._scale = 1.0 + + def set_current_network(self, network: Network): + self._network = network + + def set_scale(self, scale: float): + self._scale = scale + + def _render(self, _): + if self._network is None: + return + + # Determine which wifi strength icon to use + strength = round(self._network.strength / 100 * 4) + if strength == 4: + strength_icon = self._wifi_full_txt + elif strength == 3: + strength_icon = self._wifi_medium_txt + elif strength == 2: + strength_icon = self._wifi_low_txt + elif self._network.strength < 0: + strength_icon = self._wifi_slash_txt + else: + strength_icon = self._wifi_none_txt + + icon_x = int(self._rect.x + (self._rect.width - strength_icon.width * self._scale) // 2) + icon_y = int(self._rect.y + (self._rect.height - strength_icon.height * self._scale) // 2) + rl.draw_texture_ex(strength_icon, (icon_x, icon_y), 0.0, self._scale, rl.WHITE) + + # Render lock icon at lower right of wifi icon if secured + if self._network.security_type not in (SecurityType.OPEN, SecurityType.UNSUPPORTED): + lock_scale = self._scale * 1.1 + lock_x = int(icon_x + 1 + strength_icon.width * self._scale - self._lock_txt.width * lock_scale / 2) + lock_y = int(icon_y + 1 + strength_icon.height * self._scale - self._lock_txt.height * lock_scale / 2) + rl.draw_texture_ex(self._lock_txt, (lock_x, lock_y), 0.0, lock_scale, rl.WHITE) + + +class WifiItem(BigDialogOptionButton): + LEFT_MARGIN = 20 + + def __init__(self, network: Network): + super().__init__(network.ssid) + + self.set_rect(rl.Rectangle(0, 0, gui_app.width, 64)) + + self._selected_txt = gui_app.texture("icons_mici/settings/network/new/wifi_selected.png", 48, 96) + + self._network = network + self._wifi_icon = WifiIcon() + self._wifi_icon.set_current_network(network) + + def set_current_network(self, network: Network): + self._network = network + self._wifi_icon.set_current_network(network) + + def _render(self, _): + if self._network.is_connected: + selected_x = int(self._rect.x - self._selected_txt.width / 2) + selected_y = int(self._rect.y + (self._rect.height - self._selected_txt.height) / 2) + rl.draw_texture(self._selected_txt, selected_x, selected_y, rl.WHITE) + + self._wifi_icon.set_scale((1.0 if self._selected else 0.65) * 0.7) + self._wifi_icon.render(rl.Rectangle( + self._rect.x + self.LEFT_MARGIN, + self._rect.y, + self._rect.height, + self._rect.height + )) + + if self._selected: + self._label.set_font_size(74) + self._label.set_color(rl.Color(255, 255, 255, int(255 * 0.9))) + self._label.set_font_weight(FontWeight.DISPLAY) + else: + self._label.set_font_size(70) + self._label.set_color(rl.Color(255, 255, 255, int(255 * 0.58))) + self._label.set_font_weight(FontWeight.DISPLAY_REGULAR) + + label_offset = self.LEFT_MARGIN + self._wifi_icon.rect.width + 20 + label_rect = rl.Rectangle(self._rect.x + label_offset, self._rect.y, self._rect.width - label_offset, self._rect.height) + self._label.set_text(normalize_ssid(self._network.ssid)) + self._label.render(label_rect) + + +class ConnectButton(Widget): + def __init__(self): + super().__init__() + self._bg_txt = gui_app.texture("icons_mici/settings/network/new/connect_button.png", 410, 100) + self._bg_pressed_txt = gui_app.texture("icons_mici/settings/network/new/connect_button_pressed.png", 410, 100) + self._bg_full_txt = gui_app.texture("icons_mici/settings/network/new/full_connect_button.png", 520, 100) + self._bg_full_pressed_txt = gui_app.texture("icons_mici/settings/network/new/full_connect_button_pressed.png", 520, 100) + + self._full: bool = False + + self._label = UnifiedLabel("", 36, FontWeight.MEDIUM, rl.Color(255, 255, 255, int(255 * 0.9)), + alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER, + alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE) + + @property + def full(self) -> bool: + return self._full + + def set_full(self, full: bool): + self._full = full + self.set_rect(rl.Rectangle(0, 0, 520 if self._full else 410, 100)) + + def set_label(self, text: str): + self._label.set_text(text) + + def _render(self, _): + if self._full: + bg_txt = self._bg_full_pressed_txt if self.is_pressed and self.enabled else self._bg_full_txt + else: + bg_txt = self._bg_pressed_txt if self.is_pressed and self.enabled else self._bg_txt + + rl.draw_texture(bg_txt, int(self._rect.x), int(self._rect.y), rl.WHITE) + + self._label.set_text_color(rl.Color(255, 255, 255, int(255 * 0.9) if self.enabled else int(255 * 0.9 * 0.65))) + self._label.render(self._rect) + + +class ForgetButton(Widget): + HORIZONTAL_MARGIN = 8 + + def __init__(self, forget_network: Callable, open_network_manage_page): + super().__init__() + self._forget_network = forget_network + self._open_network_manage_page = open_network_manage_page + + self._bg_txt = gui_app.texture("icons_mici/settings/network/new/forget_button.png", 100, 100) + self._bg_pressed_txt = gui_app.texture("icons_mici/settings/network/new/forget_button_pressed.png", 100, 100) + self._trash_txt = gui_app.texture("icons_mici/settings/network/new/trash.png", 32, 36) + self.set_rect(rl.Rectangle(0, 0, 100 + self.HORIZONTAL_MARGIN * 2, 100)) + + def _handle_mouse_release(self, mouse_pos: MousePos): + super()._handle_mouse_release(mouse_pos) + dlg = BigConfirmationDialogV2("slide to forget", "icons_mici/settings/network/new/trash.png", red=True, + confirm_callback=self._forget_network) + gui_app.set_modal_overlay(dlg, callback=self._open_network_manage_page) + + def _render(self, _): + bg_txt = self._bg_pressed_txt if self.is_pressed else self._bg_txt + rl.draw_texture(bg_txt, int(self._rect.x + self.HORIZONTAL_MARGIN), int(self._rect.y), rl.WHITE) + + trash_x = int(self._rect.x + (self._rect.width - self._trash_txt.width) // 2) + trash_y = int(self._rect.y + (self._rect.height - self._trash_txt.height) // 2) + rl.draw_texture(self._trash_txt, trash_x, trash_y, rl.WHITE) + + +class NetworkInfoPage(NavWidget): + def __init__(self, wifi_manager, connect_callback: Callable, forget_callback: Callable, open_network_manage_page: Callable): + super().__init__() + self._wifi_manager = wifi_manager + + self.set_rect(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) + + self._wifi_icon = WifiIcon() + self._forget_btn = ForgetButton(lambda: forget_callback(self._network.ssid) if self._network is not None else None, + open_network_manage_page) + self._connect_btn = ConnectButton() + self._connect_btn.set_click_callback(lambda: connect_callback(self._network.ssid) if self._network is not None else None) + + self._title = UnifiedLabel("", 64, FontWeight.DISPLAY, rl.Color(255, 255, 255, int(255 * 0.9)), + alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE) + self._subtitle = UnifiedLabel("", 36, FontWeight.ROMAN, rl.Color(255, 255, 255, int(255 * 0.9 * 0.65)), + alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE) + + self.set_back_callback(lambda: gui_app.set_modal_overlay(None)) + + # State + self._network: Network | None = None + self._connecting: Callable[[], str | None] | None = None + + def update_networks(self, networks: dict[str, Network]): + # update current network from latest scan results + for ssid, network in networks.items(): + if self._network is not None and ssid == self._network.ssid: + self.set_current_network(network) + break + else: + # network disappeared, close page + gui_app.set_modal_overlay(None) + + def _update_state(self): + super()._update_state() + # Modal overlays stop main UI rendering, so we need to call here + self._wifi_manager.process_callbacks() + + if self._network is None: + return + + self._connect_btn.set_full(not self._network.is_saved and not self._is_connecting) + if self._is_connecting: + self._connect_btn.set_label("connecting...") + self._connect_btn.set_enabled(False) + elif self._network.is_connected: + self._connect_btn.set_label("connected") + self._connect_btn.set_enabled(False) + elif self._network.security_type == SecurityType.UNSUPPORTED: + self._connect_btn.set_label("connect") + self._connect_btn.set_enabled(False) + else: # saved or unknown + self._connect_btn.set_label("connect") + self._connect_btn.set_enabled(True) + + self._title.set_text(normalize_ssid(self._network.ssid)) + if self._network.security_type == SecurityType.OPEN: + self._subtitle.set_text("open") + elif self._network.security_type == SecurityType.UNSUPPORTED: + self._subtitle.set_text("unsupported") + else: + self._subtitle.set_text("secured") + + def set_current_network(self, network: Network): + self._network = network + self._wifi_icon.set_current_network(network) + + def set_connecting(self, is_connecting: Callable[[], str | None]): + self._connecting = is_connecting + + @property + def _is_connecting(self): + if self._connecting is None or self._network is None: + return False + is_connecting = self._connecting() == self._network.ssid + return is_connecting + + def _render(self, _): + self._wifi_icon.render(rl.Rectangle( + self._rect.x + 32, + self._rect.y + (self._rect.height - self._connect_btn.rect.height - self._wifi_icon.rect.height) / 2, + self._wifi_icon.rect.width, + self._wifi_icon.rect.height, + )) + + self._title.render(rl.Rectangle( + self._rect.x + self._wifi_icon.rect.width + 32 + 32, + self._rect.y + 32 - 16, + self._rect.width - (self._wifi_icon.rect.width + 32 + 32), + 64, + )) + + self._subtitle.render(rl.Rectangle( + self._rect.x + self._wifi_icon.rect.width + 32 + 32, + self._rect.y + 32 + 64 - 16, + self._rect.width - (self._wifi_icon.rect.width + 32 + 32), + 48, + )) + + self._connect_btn.render(rl.Rectangle( + self._rect.x + 8, + self._rect.y + self._rect.height - self._connect_btn.rect.height, + self._connect_btn.rect.width, + self._connect_btn.rect.height, + )) + + if not self._connect_btn.full: + self._forget_btn.render(rl.Rectangle( + self._rect.x + self._rect.width - self._forget_btn.rect.width, + self._rect.y + self._rect.height - self._forget_btn.rect.height, + self._forget_btn.rect.width, + self._forget_btn.rect.height, + )) + + return -1 + + +class WifiUIMici(BigMultiOptionDialog): + def __init__(self, wifi_manager: WifiManager, back_callback: Callable): + super().__init__([], None, None, right_btn_callback=None) + + # Set up back navigation + self.set_back_callback(back_callback) + + self._network_info_page = NetworkInfoPage(wifi_manager, self._connect_to_network, self._forget_network, self._open_network_manage_page) + self._network_info_page.set_connecting(lambda: self._connecting) + self._should_open_network_info_page = False # wait for scroll_to animation + + self._loading_animation = LoadingAnimation() + + self._wifi_manager = wifi_manager + self._connecting: str | None = None + self._networks: dict[str, Network] = {} + + self._wifi_manager.add_callbacks( + need_auth=self._on_need_auth, + activated=self._on_activated, + forgotten=self._on_forgotten, + networks_updated=self._on_network_updated, + disconnected=self._on_disconnected, + ) + + def show_event(self): + # Call super to prepare scroller; selection scroll is handled dynamically + super().show_event() + self._wifi_manager.set_active(True) + self._scroller.show_event() + + def hide_event(self): + super().hide_event() + self._wifi_manager.set_active(False) + + def _update_state(self): + super()._update_state() + if self._should_open_network_info_page: + self._should_open_network_info_page = False + self._open_network_manage_page() + + def _open_network_manage_page(self, result=None): + self._network_info_page.update_networks(self._networks) + gui_app.set_modal_overlay(self._network_info_page) + + def _forget_network(self, ssid: str): + network = self._networks.get(ssid) + if network is None: + cloudlog.warning(f"Trying to forget unknown network: {ssid}") + return + + self._wifi_manager.forget_connection(network.ssid) + + def _on_network_updated(self, networks: list[Network]): + self._networks = {network.ssid: network for network in networks} + self._update_buttons() + self._network_info_page.update_networks(self._networks) + + def _update_buttons(self): + for network in self._networks.values(): + # pop and re-insert to eliminate stuttering on update (prevents position lost for a frame) + network_button_idx = next((i for i, btn in enumerate(self._scroller._items) if btn.option == network.ssid), None) + if network_button_idx is not None: + network_button = self._scroller._items.pop(network_button_idx) + # Update network on existing button + network_button.set_current_network(network) + else: + network_button = WifiItem(network) + + def show_network_info_page(_network): + self._network_info_page.set_current_network(_network) + self._should_open_network_info_page = True + + network_button.set_click_callback(lambda _net=network,_button=network_button: _button._selected and show_network_info_page(_net)) + + self.add_button(network_button) + + # remove networks no longer present + self._scroller._items[:] = [btn for btn in self._scroller._items if btn.option in self._networks] + + def _connect_with_password(self, ssid: str, password: str): + if password: + self._connecting = ssid + self._wifi_manager.connect_to_network(ssid, password) + self._update_buttons() + + def _connect_to_network(self, ssid: str): + network = self._networks.get(ssid) + if network is None: + cloudlog.warning(f"Trying to connect to unknown network: {ssid}") + return + + if network.is_saved: + self._connecting = network.ssid + self._wifi_manager.activate_connection(network.ssid) + self._update_buttons() + elif network.security_type == SecurityType.OPEN: + self._connecting = network.ssid + self._wifi_manager.connect_to_network(network.ssid, "") + self._update_buttons() + else: + self._on_need_auth(network.ssid, False) + + def _on_need_auth(self, ssid, incorrect_password=True): + hint = "incorrect password..." if incorrect_password else "enter password..." + dlg = BigInputDialog(hint, "", minimum_length=8, + confirm_callback=lambda _password: self._connect_with_password(ssid, _password)) + # go back to the manage network page + gui_app.set_modal_overlay(dlg, self._open_network_manage_page) + + def _on_activated(self): + self._connecting = None + + def _on_forgotten(self): + self._connecting = None + + def _on_disconnected(self): + self._connecting = None + + def _render(self, _): + super()._render(_) + + if not self._networks: + self._loading_animation.render(self._rect) + + +class NetworkLayoutMici(NavWidget): + def __init__(self, back_callback: Callable): + super().__init__() + + self._current_panel = NetworkPanelType.WIFI + self.set_back_enabled(lambda: self._current_panel == NetworkPanelType.NONE) + + self._wifi_manager = WifiManager() + self._wifi_manager.set_active(False) + self._wifi_ui = WifiUIMici(self._wifi_manager, back_callback=lambda: self._switch_to_panel(NetworkPanelType.NONE)) + + self._wifi_manager.add_callbacks( + networks_updated=self._on_network_updated, + ) + + _tethering_icon = "icons_mici/settings/network/tethering.png" + + # ******** Tethering ******** + def tethering_toggle_callback(checked: bool): + self._tethering_toggle_btn.set_enabled(False) + self._network_metered_btn.set_enabled(False) + self._wifi_manager.set_tethering_active(checked) + + self._tethering_toggle_btn = BigToggle("enable tethering", "", toggle_callback=tethering_toggle_callback) + + def tethering_password_callback(password: str): + if password: + self._wifi_manager.set_tethering_password(password) + + def tethering_password_clicked(): + tethering_password = self._wifi_manager.tethering_password + dlg = BigInputDialog("enter password...", tethering_password, minimum_length=8, + confirm_callback=tethering_password_callback) + gui_app.set_modal_overlay(dlg) + + txt_tethering = gui_app.texture(_tethering_icon, 64, 53) + self._tethering_password_btn = BigButton("tethering password", "", txt_tethering) + self._tethering_password_btn.set_click_callback(tethering_password_clicked) + + # ******** IP Address ******** + self._ip_address_btn = BigButton("IP Address", "Not connected") + + # ******** Network Metered ******** + def network_metered_callback(value: str): + self._network_metered_btn.set_enabled(False) + metered = { + 'default': MeteredType.UNKNOWN, + 'metered': MeteredType.YES, + 'unmetered': MeteredType.NO + }.get(value, MeteredType.UNKNOWN) + self._wifi_manager.set_current_network_metered(metered) + + # TODO: signal for current network metered type when changing networks, this is wrong until you press it once + # TODO: disable when not connected + self._network_metered_btn = BigMultiToggle("network usage", ["default", "metered", "unmetered"], select_callback=network_metered_callback) + self._network_metered_btn.set_enabled(False) + + wifi_button = BigButton("wi-fi") + wifi_button.set_click_callback(lambda: self._switch_to_panel(NetworkPanelType.WIFI)) + + # Main scroller ---------------------------------- + self._scroller = Scroller([ + wifi_button, + self._network_metered_btn, + self._tethering_toggle_btn, + self._tethering_password_btn, + self._ip_address_btn, + ], snap_items=False) + + # Set up back navigation + self.set_back_callback(back_callback) + + def show_event(self): + super().show_event() + self._current_panel = NetworkPanelType.NONE + self._wifi_ui.show_event() + self._scroller.show_event() + + def hide_event(self): + super().hide_event() + self._wifi_ui.hide_event() + + def _on_network_updated(self, networks: list[Network]): + # Update tethering state + tethering_active = self._wifi_manager.is_tethering_active() + # TODO: use real signals (like activated/settings changed, etc.) to speed up re-enabling buttons + self._tethering_toggle_btn.set_enabled(True) + self._network_metered_btn.set_enabled(lambda: not tethering_active and bool(self._wifi_manager.ipv4_address)) + self._tethering_toggle_btn.set_checked(tethering_active) + + # Update IP address + self._ip_address_btn.set_value(self._wifi_manager.ipv4_address or "Not connected") + + # Update network metered + self._network_metered_btn.set_value( + { + MeteredType.UNKNOWN: 'default', + MeteredType.YES: 'metered', + MeteredType.NO: 'unmetered' + }.get(self._wifi_manager.current_network_metered, 'default')) + + def _switch_to_panel(self, panel_type: NetworkPanelType): + self._current_panel = panel_type + + def _render(self, rect: rl.Rectangle): + self._wifi_manager.process_callbacks() + + if self._current_panel == NetworkPanelType.WIFI: + self._wifi_ui.render(rect) + else: + self._scroller.render(rect) diff --git a/selfdrive/ui/mici/layouts/settings/settings.py b/selfdrive/ui/mici/layouts/settings/settings.py new file mode 100644 index 000000000..75238d581 --- /dev/null +++ b/selfdrive/ui/mici/layouts/settings/settings.py @@ -0,0 +1,113 @@ +import pyray as rl +from dataclasses import dataclass +from enum import IntEnum +from collections.abc import Callable + +from openpilot.common.params import Params +from openpilot.system.ui.widgets.scroller import Scroller +from openpilot.selfdrive.ui.mici.widgets.button import BigButton +from openpilot.selfdrive.ui.mici.layouts.settings.toggles import TogglesLayoutMici +from openpilot.selfdrive.ui.mici.layouts.settings.network import NetworkLayoutMici +from openpilot.selfdrive.ui.mici.layouts.settings.device import DeviceLayoutMici, PairBigButton +from openpilot.selfdrive.ui.mici.layouts.settings.developer import DeveloperLayoutMici +from openpilot.selfdrive.ui.mici.layouts.settings.firehose import FirehoseLayoutMici +from openpilot.system.ui.lib.application import gui_app, FontWeight +from openpilot.system.ui.widgets import Widget, NavWidget + + +class PanelType(IntEnum): + TOGGLES = 0 + NETWORK = 1 + DEVICE = 2 + DEVELOPER = 3 + USER_MANUAL = 4 + FIREHOSE = 5 + + +@dataclass +class PanelInfo: + name: str + instance: Widget + + +class SettingsLayout(NavWidget): + def __init__(self): + super().__init__() + self._params = Params() + self._current_panel = None # PanelType.DEVICE + + toggles_btn = BigButton("toggles", "", "icons_mici/settings/toggles_icon.png") + toggles_btn.set_click_callback(lambda: self._set_current_panel(PanelType.TOGGLES)) + network_btn = BigButton("network", "", "icons_mici/settings/network/wifi_strength_full.png") + network_btn.set_click_callback(lambda: self._set_current_panel(PanelType.NETWORK)) + device_btn = BigButton("device", "", "icons_mici/settings/device_icon.png") + device_btn.set_click_callback(lambda: self._set_current_panel(PanelType.DEVICE)) + developer_btn = BigButton("developer", "", "icons_mici/settings/developer_icon.png") + developer_btn.set_click_callback(lambda: self._set_current_panel(PanelType.DEVELOPER)) + + firehose_btn = BigButton("firehose", "", "icons_mici/settings/comma_icon.png") + firehose_btn.set_click_callback(lambda: self._set_current_panel(PanelType.FIREHOSE)) + + self._scroller = Scroller([ + toggles_btn, + network_btn, + device_btn, + PairBigButton(), + #BigDialogButton("manual", "", "icons_mici/settings/manual_icon.png", "Check out the mici user\nmanual at comma.ai/setup"), + firehose_btn, + developer_btn, + ], snap_items=False) + + # Set up back navigation + self.set_back_callback(self.close_settings) + self.set_back_enabled(lambda: self._current_panel is None) + + self._panels = { + PanelType.TOGGLES: PanelInfo("Toggles", TogglesLayoutMici(back_callback=lambda: self._set_current_panel(None))), + PanelType.NETWORK: PanelInfo("Network", NetworkLayoutMici(back_callback=lambda: self._set_current_panel(None))), + PanelType.DEVICE: PanelInfo("Device", DeviceLayoutMici(back_callback=lambda: self._set_current_panel(None))), + PanelType.DEVELOPER: PanelInfo("Developer", DeveloperLayoutMici(back_callback=lambda: self._set_current_panel(None))), + PanelType.FIREHOSE: PanelInfo("Firehose", FirehoseLayoutMici(back_callback=lambda: self._set_current_panel(None))), + } + + self._font_medium = gui_app.font(FontWeight.MEDIUM) + + # Callbacks + self._close_callback: Callable | None = None + + def show_event(self): + super().show_event() + self._set_current_panel(None) + self._scroller.show_event() + if self._current_panel is not None: + self._panels[self._current_panel].instance.show_event() + + def hide_event(self): + super().hide_event() + if self._current_panel is not None: + self._panels[self._current_panel].instance.hide_event() + + def set_callbacks(self, on_close: Callable): + self._close_callback = on_close + + def _render(self, rect: rl.Rectangle): + if self._current_panel is not None: + self._draw_current_panel() + else: + self._scroller.render(rect) + + def _draw_current_panel(self): + panel = self._panels[self._current_panel] + panel.instance.render(self._rect) + + def _set_current_panel(self, panel_type: PanelType | None): + if panel_type != self._current_panel: + if self._current_panel is not None: + self._panels[self._current_panel].instance.hide_event() + self._current_panel = panel_type + if self._current_panel is not None: + self._panels[self._current_panel].instance.show_event() + + def close_settings(self): + if self._close_callback: + self._close_callback() diff --git a/selfdrive/ui/mici/layouts/settings/toggles.py b/selfdrive/ui/mici/layouts/settings/toggles.py new file mode 100644 index 000000000..8efb516a4 --- /dev/null +++ b/selfdrive/ui/mici/layouts/settings/toggles.py @@ -0,0 +1,95 @@ +import pyray as rl +from collections.abc import Callable +from cereal import log + +from openpilot.system.ui.widgets.scroller import Scroller +from openpilot.selfdrive.ui.mici.widgets.button import BigParamControl, BigMultiParamToggle +from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.widgets import NavWidget +from openpilot.selfdrive.ui.layouts.settings.common import restart_needed_callback +from openpilot.selfdrive.ui.ui_state import ui_state + +PERSONALITY_TO_INT = log.LongitudinalPersonality.schema.enumerants + + +class TogglesLayoutMici(NavWidget): + def __init__(self, back_callback: Callable): + super().__init__() + self.set_back_callback(back_callback) + + self._personality_toggle = BigMultiParamToggle("driving personality", "LongitudinalPersonality", ["aggressive", "standard", "relaxed"]) + self._experimental_btn = BigParamControl("experimental mode", "ExperimentalMode") + is_metric_toggle = BigParamControl("use metric units", "IsMetric") + ldw_toggle = BigParamControl("lane departure warnings", "IsLdwEnabled") + always_on_dm_toggle = BigParamControl("always-on driver monitor", "AlwaysOnDM") + record_front = BigParamControl("record & upload driver camera", "RecordFront", toggle_callback=restart_needed_callback) + record_mic = BigParamControl("record & upload mic audio", "RecordAudio", toggle_callback=restart_needed_callback) + enable_openpilot = BigParamControl("enable openpilot", "OpenpilotEnabledToggle", toggle_callback=restart_needed_callback) + + self._scroller = Scroller([ + self._personality_toggle, + self._experimental_btn, + is_metric_toggle, + ldw_toggle, + always_on_dm_toggle, + record_front, + record_mic, + enable_openpilot, + ], snap_items=False) + + # Toggle lists + self._refresh_toggles = ( + ("ExperimentalMode", self._experimental_btn), + ("IsMetric", is_metric_toggle), + ("IsLdwEnabled", ldw_toggle), + ("AlwaysOnDM", always_on_dm_toggle), + ("RecordFront", record_front), + ("RecordAudio", record_mic), + ("OpenpilotEnabledToggle", enable_openpilot), + ) + + enable_openpilot.set_enabled(lambda: not ui_state.engaged) + record_front.set_enabled(False if ui_state.params.get_bool("RecordFrontLock") else (lambda: not ui_state.engaged)) + record_mic.set_enabled(lambda: not ui_state.engaged) + + if ui_state.params.get_bool("ShowDebugInfo"): + gui_app.set_show_touches(True) + gui_app.set_show_fps(True) + + ui_state.add_engaged_transition_callback(self._update_toggles) + + def _update_state(self): + super()._update_state() + + if ui_state.sm.updated["selfdriveState"]: + personality = PERSONALITY_TO_INT[ui_state.sm["selfdriveState"].personality] + if personality != ui_state.personality and ui_state.started: + self._personality_toggle.set_value(self._personality_toggle._options[personality]) + ui_state.personality = personality + + def show_event(self): + super().show_event() + self._scroller.show_event() + self._update_toggles() + + def _update_toggles(self): + ui_state.update_params() + + # CP gating for experimental mode + if ui_state.CP is not None: + if ui_state.has_longitudinal_control: + self._experimental_btn.set_enabled(True) + self._personality_toggle.set_enabled(True) + else: + # no long for now + self._experimental_btn.set_enabled(False) + self._experimental_btn.set_checked(False) + self._personality_toggle.set_enabled(False) + ui_state.params.remove("ExperimentalMode") + + # Refresh toggles from params to mirror external changes + for key, item in self._refresh_toggles: + item.set_checked(ui_state.params.get_bool(key)) + + def _render(self, rect: rl.Rectangle): + self._scroller.render(rect) diff --git a/selfdrive/ui/mici/onroad/__init__.py b/selfdrive/ui/mici/onroad/__init__.py new file mode 100644 index 000000000..bb45117b9 --- /dev/null +++ b/selfdrive/ui/mici/onroad/__init__.py @@ -0,0 +1,12 @@ +import pyray as rl + +SIDE_PANEL_WIDTH = 60 + + +def blend_colors(a: rl.Color, b: rl.Color, f: float) -> rl.Color: + h0, s0, v0 = (hsv0 := rl.color_to_hsv(a)).x, hsv0.y, hsv0.z + h1, s1, v1 = (hsv1 := rl.color_to_hsv(b)).x, hsv1.y, hsv1.z + dh = ((h1 - h0 + 180) % 360) - 180 # shortest hue delta + return rl.color_from_hsv((h0 + f * dh) % 360, + s0 + f * (s1 - s0), + v0 + f * (v1 - v0)) diff --git a/selfdrive/ui/mici/onroad/alert_renderer.py b/selfdrive/ui/mici/onroad/alert_renderer.py new file mode 100644 index 000000000..eb5555660 --- /dev/null +++ b/selfdrive/ui/mici/onroad/alert_renderer.py @@ -0,0 +1,361 @@ +import time +from enum import StrEnum +from typing import NamedTuple +import pyray as rl +import random +import string +from dataclasses import dataclass +from cereal import messaging, log, car +from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.common.filter_simple import BounceFilter, FirstOrderFilter +from openpilot.system.hardware import TICI +from openpilot.system.ui.lib.application import gui_app, FontWeight +from openpilot.system.ui.widgets import Widget +from openpilot.system.ui.widgets.label import UnifiedLabel + +AlertSize = log.SelfdriveState.AlertSize +AlertStatus = log.SelfdriveState.AlertStatus + +ALERT_MARGIN = 18 + +ALERT_FONT_SMALL = 66 - 50 +ALERT_FONT_BIG = 88 - 40 + +SELFDRIVE_STATE_TIMEOUT = 5 # Seconds +SELFDRIVE_UNRESPONSIVE_TIMEOUT = 10 # Seconds + +# Constants +ALERT_COLORS = { + AlertStatus.normal: rl.Color(0, 0, 0, 255), + AlertStatus.userPrompt: rl.Color(255, 115, 0, 255), + AlertStatus.critical: rl.Color(255, 0, 21, 255), +} + +TURN_SIGNAL_BLINK_PERIOD = 1 / (80 / 60) # Mazda heartbeat turn signal BPM + +DEBUG = False + + +class IconSide(StrEnum): + left = 'left' + right = 'right' + + +class IconLayout(NamedTuple): + texture: rl.Texture + side: IconSide + margin_x: int + margin_y: int + + +class AlertLayout(NamedTuple): + text_rect: rl.Rectangle + icon: IconLayout | None + + +@dataclass +class Alert: + text1: str = "" + text2: str = "" + size: int = 0 + status: int = 0 + visual_alert: int = car.CarControl.HUDControl.VisualAlert.none + alert_type: str = "" + + +# Pre-defined alert instances +ALERT_STARTUP_PENDING = Alert( + text1="openpilot Unavailable", + text2="Waiting to start", + size=AlertSize.mid, + status=AlertStatus.normal, +) + +ALERT_CRITICAL_TIMEOUT = Alert( + text1="TAKE CONTROL IMMEDIATELY", + text2="System Unresponsive", + size=AlertSize.full, + status=AlertStatus.critical, +) + +ALERT_CRITICAL_REBOOT = Alert( + text1="System Unresponsive", + text2="Reboot Device", + size=AlertSize.full, + status=AlertStatus.critical, +) + + +class AlertRenderer(Widget): + def __init__(self): + super().__init__() + self.font_regular: rl.Font = gui_app.font(FontWeight.MEDIUM) + self.font_roman: rl.Font = gui_app.font(FontWeight.ROMAN) + self.font_bold: rl.Font = gui_app.font(FontWeight.BOLD) + self.font_display: rl.Font = gui_app.font(FontWeight.DISPLAY) + + self._alert_text1_label = UnifiedLabel(text="", font_size=ALERT_FONT_BIG, font_weight=FontWeight.DISPLAY, line_height=0.86, + letter_spacing=-0.02) + self._alert_text2_label = UnifiedLabel(text="", font_size=ALERT_FONT_SMALL, font_weight=FontWeight.ROMAN, line_height=0.86, + letter_spacing=0.025) + + self._prev_alert: Alert | None = None + self._text_gen_time = 0 + self._alert_text2_gen = '' + + # animation filters + # TODO: use 0.1 but with proper alert height calculation + self._alert_y_filter = BounceFilter(0, 0.1, 1 / gui_app.target_fps) + self._alpha_filter = FirstOrderFilter(0, 0.05, 1 / gui_app.target_fps) + + self._turn_signal_timer = 0.0 + self._turn_signal_alpha_filter = FirstOrderFilter(0.0, 0.3, 1 / gui_app.target_fps) + self._last_icon_side: IconSide | None = None + + self._load_icons() + + def _load_icons(self): + self._txt_turn_signal_left = gui_app.texture('icons_mici/onroad/turn_signal_left.png', 100, 91) + self._txt_turn_signal_right = gui_app.texture('icons_mici/onroad/turn_signal_right.png', 100, 91) + self._txt_blind_spot_left = gui_app.texture('icons_mici/onroad/blind_spot_left.png', 108, 128) + self._txt_blind_spot_right = gui_app.texture('icons_mici/onroad/blind_spot_right.png', 108, 128) + + def get_alert(self, sm: messaging.SubMaster) -> Alert | None: + """Generate the current alert based on selfdrive state.""" + ss = sm['selfdriveState'] + + # Check if selfdriveState messages have stopped arriving + if not sm.updated['selfdriveState']: + recv_frame = sm.recv_frame['selfdriveState'] + time_since_onroad = time.monotonic() - ui_state.started_time + + # 1. Never received selfdriveState since going onroad + waiting_for_startup = recv_frame < ui_state.started_frame + if waiting_for_startup and time_since_onroad > 5: + return ALERT_STARTUP_PENDING + + # 2. Lost communication with selfdriveState after receiving it + if TICI and not waiting_for_startup: + ss_missing = time.monotonic() - sm.recv_time['selfdriveState'] + if ss_missing > SELFDRIVE_STATE_TIMEOUT: + if ss.enabled and (ss_missing - SELFDRIVE_STATE_TIMEOUT) < SELFDRIVE_UNRESPONSIVE_TIMEOUT: + return ALERT_CRITICAL_TIMEOUT + return ALERT_CRITICAL_REBOOT + + # No alert if size is none + if ss.alertSize == 0: + return None + + # Return current alert + ret = Alert(text1=ss.alertText1, text2=ss.alertText2, size=ss.alertSize.raw, status=ss.alertStatus.raw, + visual_alert=ss.alertHudVisual, alert_type=ss.alertType) + self._prev_alert = ret + return ret + + def will_render(self) -> tuple[Alert | None, bool]: + alert = self.get_alert(ui_state.sm) + return alert or self._prev_alert, alert is None + + def _icon_helper(self, alert: Alert) -> AlertLayout: + icon_side = None + txt_icon = None + icon_margin_x = 20 + icon_margin_y = 18 + + # alert_type format is "EventName/eventType" (e.g., "preLaneChangeLeft/warning") + event_name = alert.alert_type.split('/')[0] if alert.alert_type else '' + + if event_name == 'preLaneChangeLeft': + icon_side = IconSide.left + txt_icon = self._txt_turn_signal_left + icon_margin_x = 2 + icon_margin_y = 5 + + elif event_name == 'preLaneChangeRight': + icon_side = IconSide.right + txt_icon = self._txt_turn_signal_right + icon_margin_x = 2 + icon_margin_y = 5 + + elif event_name == 'laneChange': + icon_side = self._last_icon_side + txt_icon = self._txt_turn_signal_left if self._last_icon_side == 'left' else self._txt_turn_signal_right + icon_margin_x = 2 + icon_margin_y = 5 + + elif event_name == 'laneChangeBlocked': + CS = ui_state.sm['carState'] + if CS.leftBlinker: + icon_side = IconSide.left + elif CS.rightBlinker: + icon_side = IconSide.right + else: + icon_side = self._last_icon_side + txt_icon = self._txt_blind_spot_left if icon_side == 'left' else self._txt_blind_spot_right + icon_margin_x = 8 + icon_margin_y = 0 + + else: + self._turn_signal_timer = 0.0 + + self._last_icon_side = icon_side + + # create text rect based on icon presence + text_x = self._rect.x + ALERT_MARGIN + text_width = self._rect.width - ALERT_MARGIN + if icon_side == 'left': + text_x = self._rect.x + self._txt_turn_signal_right.width + 20 * 2 + text_width = self._rect.width - ALERT_MARGIN - self._txt_turn_signal_right.width - 20 * 2 + elif icon_side == 'right': + text_x = self._rect.x + ALERT_MARGIN + text_width = self._rect.width - ALERT_MARGIN - self._txt_turn_signal_right.width - 20 * 2 + + text_rect = rl.Rectangle( + text_x, + self._alert_y_filter.x, + text_width, + self._rect.height, + ) + icon_layout = IconLayout(txt_icon, icon_side, icon_margin_x, icon_margin_y) if txt_icon is not None and icon_side is not None else None + return AlertLayout(text_rect, icon_layout) + + def _render(self, rect: rl.Rectangle) -> bool: + alert = self.get_alert(ui_state.sm) + + # Animate fade and slide in/out + self._alert_y_filter.update(self._rect.y - 50 if alert is None else self._rect.y) + self._alpha_filter.update(0 if alert is None else 1) + + if alert is None: + # If still animating out, keep the previous alert + if self._alpha_filter.x > 0.01 and self._prev_alert is not None: + alert = self._prev_alert + else: + self._prev_alert = None + return False + + self._draw_background(alert) + + alert_layout = self._icon_helper(alert) + self._draw_text(alert, alert_layout) + self._draw_icons(alert_layout) + + return True + + def _draw_icons(self, alert_layout: AlertLayout) -> None: + if alert_layout.icon is None: + return + + if time.monotonic() - self._turn_signal_timer > TURN_SIGNAL_BLINK_PERIOD: + self._turn_signal_timer = time.monotonic() + self._turn_signal_alpha_filter.x = 255 * 2 + else: + self._turn_signal_alpha_filter.update(255 * 0.2) + + if alert_layout.icon.side == 'left': + pos_x = int(self._rect.x + alert_layout.icon.margin_x) + else: + pos_x = int(self._rect.x + self._rect.width - alert_layout.icon.margin_x - alert_layout.icon.texture.width) + + if alert_layout.icon.texture not in (self._txt_turn_signal_left, self._txt_turn_signal_right): + icon_alpha = 255 + else: + icon_alpha = int(min(self._turn_signal_alpha_filter.x, 255)) + + rl.draw_texture(alert_layout.icon.texture, pos_x, int(self._rect.y + alert_layout.icon.margin_y), + rl.Color(255, 255, 255, int(icon_alpha * self._alpha_filter.x))) + + def _draw_background(self, alert: Alert) -> None: + # draw top gradient for alert text at top + color = ALERT_COLORS.get(alert.status, ALERT_COLORS[AlertStatus.normal]) + color = rl.Color(color.r, color.g, color.b, int(255 * 0.90 * self._alpha_filter.x)) + translucent_color = rl.Color(color.r, color.g, color.b, int(0 * self._alpha_filter.x)) + + small_alert_height = round(self._rect.height * 0.583) # 140px at mici height + medium_alert_height = round(self._rect.height * 0.833) # 200px at mici height + + # alert_type format is "EventName/eventType" (e.g., "preLaneChangeLeft/warning") + event_name = alert.alert_type.split('/')[0] if alert.alert_type else '' + + if event_name == 'preLaneChangeLeft': + bg_height = small_alert_height + elif event_name == 'preLaneChangeRight': + bg_height = small_alert_height + elif event_name == 'laneChange': + bg_height = small_alert_height + elif event_name == 'laneChangeBlocked': + bg_height = medium_alert_height + else: + bg_height = int(self._rect.height) + + solid_height = round(bg_height * 0.2) + rl.draw_rectangle(int(self._rect.x), int(self._rect.y), int(self._rect.width), solid_height, color) + rl.draw_rectangle_gradient_v(int(self._rect.x), int(self._rect.y + solid_height), int(self._rect.width), + int(bg_height - solid_height), + color, translucent_color) + + def _draw_text(self, alert: Alert, alert_layout: AlertLayout) -> None: + icon_side = alert_layout.icon.side if alert_layout.icon is not None else None + + # TODO: hack + alert_text1 = alert.text1.lower().replace('calibrating: ', 'calibrating:\n') + can_draw_second_line = False + # TODO: there should be a common way to determine font size based on text length to maximize rect + if len(alert_text1) <= 12: + can_draw_second_line = True + font_size = 92 - 10 + elif len(alert_text1) <= 16: + can_draw_second_line = True + font_size = 70 + else: + font_size = 64 - 10 + + if icon_side is not None: + font_size -= 10 + + color = rl.Color(255, 255, 255, int(255 * 0.9 * self._alpha_filter.x)) + + text1_y_offset = 11 if font_size >= 70 else 4 + text_rect1 = rl.Rectangle( + alert_layout.text_rect.x, + alert_layout.text_rect.y - text1_y_offset, + alert_layout.text_rect.width, + alert_layout.text_rect.height, + ) + self._alert_text1_label.set_text(alert_text1) + self._alert_text1_label.set_text_color(color) + self._alert_text1_label.set_font_size(font_size) + self._alert_text1_label.set_alignment(rl.GuiTextAlignment.TEXT_ALIGN_LEFT if icon_side != 'left' else rl.GuiTextAlignment.TEXT_ALIGN_RIGHT) + self._alert_text1_label.render(text_rect1) + + alert_text2 = alert.text2.lower() + + # randomize chars and length for testing + if DEBUG: + if time.monotonic() - self._text_gen_time > 0.5: + self._alert_text2_gen = ''.join(random.choices(string.ascii_lowercase + ' ', k=random.randint(0, 40))) + self._text_gen_time = time.monotonic() + alert_text2 = self._alert_text2_gen or alert_text2 + + if can_draw_second_line and alert_text2: + last_line_h = self._alert_text1_label.rect.y + self._alert_text1_label.get_content_height(int(alert_layout.text_rect.width)) + last_line_h -= 4 + if len(alert_text2) > 18: + small_font_size = 36 + elif len(alert_text2) > 24: + small_font_size = 32 + else: + small_font_size = 40 + text_rect2 = rl.Rectangle( + alert_layout.text_rect.x, + last_line_h, + alert_layout.text_rect.width, + alert_layout.text_rect.height - last_line_h + ) + color = rl.Color(255, 255, 255, int(255 * 0.65 * self._alpha_filter.x)) + + self._alert_text2_label.set_text(alert_text2) + self._alert_text2_label.set_text_color(color) + self._alert_text2_label.set_font_size(small_font_size) + self._alert_text2_label.set_alignment(rl.GuiTextAlignment.TEXT_ALIGN_LEFT if icon_side != 'left' else rl.GuiTextAlignment.TEXT_ALIGN_RIGHT) + self._alert_text2_label.render(text_rect2) diff --git a/selfdrive/ui/mici/onroad/augmented_road_view.py b/selfdrive/ui/mici/onroad/augmented_road_view.py new file mode 100644 index 000000000..ab55f392f --- /dev/null +++ b/selfdrive/ui/mici/onroad/augmented_road_view.py @@ -0,0 +1,358 @@ +import numpy as np +import pyray as rl +from cereal import car, log +from msgq.visionipc import VisionStreamType +from openpilot.selfdrive.ui.ui_state import ui_state, UIStatus +from openpilot.selfdrive.ui.mici.onroad import SIDE_PANEL_WIDTH +from openpilot.selfdrive.ui.mici.onroad.alert_renderer import AlertRenderer +from openpilot.selfdrive.ui.mici.onroad.driver_state import DriverStateRenderer +from openpilot.selfdrive.ui.mici.onroad.hud_renderer import HudRenderer +from openpilot.selfdrive.ui.mici.onroad.model_renderer import ModelRenderer +from openpilot.selfdrive.ui.mici.onroad.confidence_ball import ConfidenceBall +from openpilot.selfdrive.ui.mici.onroad.cameraview import CameraView +from openpilot.system.ui.lib.application import FontWeight, gui_app, MousePos, MouseEvent +from openpilot.system.ui.widgets.label import UnifiedLabel +from openpilot.system.ui.widgets import Widget +from openpilot.common.filter_simple import BounceFilter +from openpilot.common.transformations.camera import DEVICE_CAMERAS, DeviceCameraConfig, view_frame_from_device_frame +from openpilot.common.transformations.orientation import rot_from_euler +from enum import IntEnum + +OpState = log.SelfdriveState.OpenpilotState +CALIBRATED = log.LiveCalibrationData.Status.calibrated +ROAD_CAM = VisionStreamType.VISION_STREAM_ROAD +WIDE_CAM = VisionStreamType.VISION_STREAM_WIDE_ROAD +DEFAULT_DEVICE_CAMERA = DEVICE_CAMERAS["tici", "ar0231"] + + +class BookmarkState(IntEnum): + HIDDEN = 0 + DRAGGING = 1 + TRIGGERED = 2 + +WIDE_CAM_MAX_SPEED = 5.0 # m/s (10 mph) +ROAD_CAM_MIN_SPEED = 10 # m/s (25 mph) + +CAM_Y_OFFSET = 20 + + +class BookmarkIcon(Widget): + PEEK_THRESHOLD = 50 # If icon peeks out this much, snap it fully visible + FULL_VISIBLE_OFFSET = 200 # How far onscreen when fully visible + HIDDEN_OFFSET = -50 # How far offscreen when hidden + + def __init__(self, bookmark_callback): + super().__init__() + self._bookmark_callback = bookmark_callback + self._icon = gui_app.texture("icons_mici/onroad/bookmark.png", 180, 180) + self._offset_filter = BounceFilter(0.0, 0.1, 1 / gui_app.target_fps) + + # State + self._interacting = False + self._state = BookmarkState.HIDDEN + self._swipe_start_x = 0.0 + self._swipe_current_x = 0.0 + self._is_swiping = False + self._is_swiping_left: bool = False + self._triggered_time: float = 0.0 + + def is_swiping_left(self) -> bool: + """Check if currently swiping left (for scroller to disable).""" + return self._is_swiping_left + + def interacting(self): + interacting, self._interacting = self._interacting, False + return interacting + + def _update_state(self): + if self._state == BookmarkState.DRAGGING: + # Allow pulling past activated position with rubber band effect + swipe_offset = self._swipe_start_x - self._swipe_current_x + swipe_offset = min(swipe_offset, self.FULL_VISIBLE_OFFSET + 50) + self._offset_filter.update(swipe_offset) + + elif self._state == BookmarkState.TRIGGERED: + # Continue animating to fully visible + self._offset_filter.update(self.FULL_VISIBLE_OFFSET) + # Stay in TRIGGERED state for 1 second + if rl.get_time() - self._triggered_time >= 1.5: + self._state = BookmarkState.HIDDEN + + elif self._state == BookmarkState.HIDDEN: + self._offset_filter.update(self.HIDDEN_OFFSET) + + if self._offset_filter.x < 1e-3: + self._interacting = False + + def _handle_mouse_event(self, mouse_event: MouseEvent): + if not ui_state.started: + return + + if mouse_event.left_pressed: + # Store relative position within widget + self._swipe_start_x = mouse_event.pos.x + self._swipe_current_x = mouse_event.pos.x + self._is_swiping = True + self._is_swiping_left = False + self._state = BookmarkState.DRAGGING + + elif mouse_event.left_down and self._is_swiping: + self._swipe_current_x = mouse_event.pos.x + swipe_offset = self._swipe_start_x - self._swipe_current_x + self._is_swiping_left = swipe_offset > 0 + if self._is_swiping_left: + self._interacting = True + + elif mouse_event.left_released: + if self._is_swiping: + swipe_distance = self._swipe_start_x - self._swipe_current_x + + # If peeking past threshold, transition to animating to fully visible and bookmark + if swipe_distance > self.PEEK_THRESHOLD: + self._state = BookmarkState.TRIGGERED + self._triggered_time = rl.get_time() + self._bookmark_callback() + else: + # Otherwise, transition back to hidden + self._state = BookmarkState.HIDDEN + + # Reset swipe state + self._is_swiping = False + self._is_swiping_left = False + + def _render(self, _): + """Render the bookmark icon.""" + if self._offset_filter.x > 0: + icon_x = self.rect.x + self.rect.width - round(self._offset_filter.x) + icon_y = self.rect.y + (self.rect.height - self._icon.height) / 2 # Vertically centered + rl.draw_texture(self._icon, int(icon_x), int(icon_y), rl.WHITE) + + +class AugmentedRoadView(CameraView): + def __init__(self, bookmark_callback=None, stream_type: VisionStreamType = VisionStreamType.VISION_STREAM_ROAD): + super().__init__("camerad", stream_type) + self._bookmark_callback = bookmark_callback + self._set_placeholder_color(rl.BLACK) + + self.device_camera: DeviceCameraConfig | None = None + self.view_from_calib = view_frame_from_device_frame.copy() + self.view_from_wide_calib = view_frame_from_device_frame.copy() + + self._last_calib_time: float = 0 + self._last_rect_dims = (0.0, 0.0) + self._last_stream_type = stream_type + self._cached_matrix: np.ndarray | None = None + self._content_rect = rl.Rectangle() + self._last_click_time = 0.0 + + # Bookmark icon with swipe gesture + self._bookmark_icon = BookmarkIcon(bookmark_callback) + + self._model_renderer = ModelRenderer() + self._hud_renderer = HudRenderer() + self._alert_renderer = AlertRenderer() + self._driver_state_renderer = DriverStateRenderer() + self._confidence_ball = ConfidenceBall() + self._offroad_label = UnifiedLabel("start the car to\nuse openpilot", 54, FontWeight.DISPLAY, + text_color=rl.Color(255, 255, 255, int(255 * 0.9)), + alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER, + alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE) + + self._fade_texture = gui_app.texture("icons_mici/onroad/onroad_fade.png") + + def is_swiping_left(self) -> bool: + """Check if currently swiping left (for scroller to disable).""" + return self._bookmark_icon.is_swiping_left() + + def _update_state(self): + super()._update_state() + + # update offroad label + if ui_state.panda_type == log.PandaState.PandaType.unknown: + self._offroad_label.set_text("system booting") + else: + self._offroad_label.set_text("start the car to\nuse openpilot") + + def _handle_mouse_release(self, mouse_pos: MousePos): + # Don't trigger click callback if bookmark was triggered + if not self._bookmark_icon.interacting(): + super()._handle_mouse_release(mouse_pos) + + def _render(self, _): + self._switch_stream_if_needed(ui_state.sm) + + # Update calibration before rendering + self._update_calibration() + + # Create inner content area with border padding + self._content_rect = rl.Rectangle( + self.rect.x, + self.rect.y, + self.rect.width - SIDE_PANEL_WIDTH, + self.rect.height, + ) + + # Enable scissor mode to clip all rendering within content rectangle boundaries + # This creates a rendering viewport that prevents graphics from drawing outside the border + rl.begin_scissor_mode( + int(self._content_rect.x), + int(self._content_rect.y), + int(self._content_rect.width), + int(self._content_rect.height) + ) + + # Render the base camera view + super()._render(self._content_rect) + + # Draw all UI overlays + self._model_renderer.render(self._content_rect) + + # Fade out bottom of overlays for looks + rl.draw_texture_ex(self._fade_texture, rl.Vector2(self._content_rect.x, self._content_rect.y), 0.0, 1.0, rl.WHITE) + + alert_to_render, not_animating_out = self._alert_renderer.will_render() + + # Hide DMoji when disengaged unless AlwaysOnDM is enabled + should_draw_dmoji = (not self._hud_renderer.drawing_top_icons() and ui_state.is_onroad() and + (ui_state.status != UIStatus.DISENGAGED or ui_state.always_on_dm)) + self._driver_state_renderer.set_should_draw(should_draw_dmoji) + self._driver_state_renderer.set_position(self._rect.x + 16, self._rect.y + 10) + self._driver_state_renderer.render() + + self._hud_renderer.set_can_draw_top_icons(alert_to_render is None) + self._hud_renderer.set_wheel_critical_icon(alert_to_render is not None and not not_animating_out and + alert_to_render.visual_alert == car.CarControl.HUDControl.VisualAlert.steerRequired) + # TODO: have alert renderer draw offroad mici label below + if ui_state.started: + self._alert_renderer.render(self._content_rect) + self._hud_renderer.render(self._content_rect) + + # Draw fake rounded border + rl.draw_rectangle_rounded_lines_ex(self._content_rect, 0.2 * 1.02, 10, 50, rl.BLACK) + + # End clipping region + rl.end_scissor_mode() + + # Custom UI extension point - add custom overlays here + # Use self._content_rect for positioning within camera bounds + self._confidence_ball.render(self.rect) + + self._bookmark_icon.render(self.rect) + + # Draw darkened background and text if not onroad + if not ui_state.started: + rl.draw_rectangle(int(self.rect.x), int(self.rect.y), int(self.rect.width), int(self.rect.height), rl.Color(0, 0, 0, 175)) + self._offroad_label.render(self._content_rect) + + def _switch_stream_if_needed(self, sm): + if sm['selfdriveState'].experimentalMode and WIDE_CAM in self.available_streams: + v_ego = sm['carState'].vEgo + if v_ego < WIDE_CAM_MAX_SPEED: + target = WIDE_CAM + elif v_ego > ROAD_CAM_MIN_SPEED: + target = ROAD_CAM + else: + # Hysteresis zone - keep current stream + target = self.stream_type + else: + target = ROAD_CAM + + if self.stream_type != target: + self.switch_stream(target) + + def _update_calibration(self): + # Update device camera if not already set + sm = ui_state.sm + if not self.device_camera and sm.seen['roadCameraState'] and sm.seen['deviceState']: + self.device_camera = DEVICE_CAMERAS[(str(sm['deviceState'].deviceType), str(sm['roadCameraState'].sensor))] + + # Check if live calibration data is available and valid + if not (sm.updated["liveCalibration"] and sm.valid['liveCalibration']): + return + + calib = sm['liveCalibration'] + if len(calib.rpyCalib) != 3 or calib.calStatus != CALIBRATED: + return + + # Update view_from_calib matrix + device_from_calib = rot_from_euler(calib.rpyCalib) + self.view_from_calib = view_frame_from_device_frame @ device_from_calib + + # Update wide calibration if available + if hasattr(calib, 'wideFromDeviceEuler') and len(calib.wideFromDeviceEuler) == 3: + wide_from_device = rot_from_euler(calib.wideFromDeviceEuler) + self.view_from_wide_calib = view_frame_from_device_frame @ wide_from_device @ device_from_calib + + def _calc_frame_matrix(self, rect: rl.Rectangle) -> np.ndarray: + # Get camera configuration + # TODO: cache with vEgo? + calib_time = ui_state.sm.recv_frame['liveCalibration'] + current_dims = (self._content_rect.width, self._content_rect.height) + device_camera = self.device_camera or DEFAULT_DEVICE_CAMERA + is_wide_camera = self.stream_type == WIDE_CAM + intrinsic = device_camera.ecam.intrinsics if is_wide_camera else device_camera.fcam.intrinsics + calibration = self.view_from_wide_calib if is_wide_camera else self.view_from_calib + if is_wide_camera: + zoom = 0.7 * 1.5 + else: + zoom = np.interp(ui_state.sm['carState'].vEgo, [10, 30], [0.8, 1.0]) + + # Calculate transforms for vanishing point + inf_point = np.array([1000.0, 0.0, 0.0]) + calib_transform = intrinsic @ calibration + kep = calib_transform @ inf_point + + # Calculate center points and dimensions + x, y = self._content_rect.x, self._content_rect.y + w, h = self._content_rect.width, self._content_rect.height + cx, cy = intrinsic[0, 2], intrinsic[1, 2] + + # Calculate max allowed offsets with margins + margin = 5 + max_x_offset = cx * zoom - w / 2 - margin + max_y_offset = cy * zoom - h / 2 - margin + + # Calculate and clamp offsets to prevent out-of-bounds issues + try: + if abs(kep[2]) > 1e-6: + x_offset = np.clip((kep[0] / kep[2] - cx) * zoom, -max_x_offset, max_x_offset) + y_offset = np.clip((kep[1] / kep[2] - cy) * zoom + CAM_Y_OFFSET, -max_y_offset, max_y_offset) + else: + x_offset, y_offset = 0, 0 + except (ZeroDivisionError, OverflowError): + x_offset, y_offset = 0, 0 + + # Cache the computed transformation matrix to avoid recalculations + self._last_calib_time = calib_time + self._last_rect_dims = current_dims + self._last_stream_type = self.stream_type + self._cached_matrix = np.array([ + [zoom * 2 * cx / w, 0, -x_offset / w * 2], + [0, zoom * 2 * cy / h, -y_offset / h * 2], + [0, 0, 1.0] + ]) + + video_transform = np.array([ + [zoom, 0.0, (w / 2 + x - x_offset) - (cx * zoom)], + [0.0, zoom, (h / 2 + y - y_offset) - (cy * zoom)], + [0.0, 0.0, 1.0] + ]) + self._model_renderer.set_transform(video_transform @ calib_transform) + + return self._cached_matrix + + +if __name__ == "__main__": + gui_app.init_window("OnRoad Camera View") + road_camera_view = AugmentedRoadView(ROAD_CAM) + print("***press space to switch camera view***") + try: + for _ in gui_app.render(): + ui_state.update() + if rl.is_key_released(rl.KeyboardKey.KEY_SPACE): + if WIDE_CAM in road_camera_view.available_streams: + stream = ROAD_CAM if road_camera_view.stream_type == WIDE_CAM else WIDE_CAM + road_camera_view.switch_stream(stream) + road_camera_view.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) + finally: + road_camera_view.close() diff --git a/selfdrive/ui/mici/onroad/cameraview.py b/selfdrive/ui/mici/onroad/cameraview.py new file mode 100644 index 000000000..f962210af --- /dev/null +++ b/selfdrive/ui/mici/onroad/cameraview.py @@ -0,0 +1,390 @@ +import platform +import numpy as np +import pyray as rl + +from msgq.visionipc import VisionIpcClient, VisionStreamType, VisionBuf +from openpilot.common.swaglog import cloudlog +from openpilot.system.hardware import TICI +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 +from openpilot.system.ui.widgets import Widget +from openpilot.selfdrive.ui.ui_state import ui_state, UIStatus + +CONNECTION_RETRY_INTERVAL = 0.2 # seconds between connection attempts + +VERSION = """ +#version 300 es +precision mediump float; +""" +if platform.system() == "Darwin": + VERSION = """ + #version 330 core + """ + + +VERTEX_SHADER = VERSION + """ +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; + uniform int engaged; + + void main() { + vec4 color = texture(texture0, fragTexCoord); + if (engaged == 1) { + float gray = dot(color.rgb, vec3(0.299, 0.587, 0.114)); // Luma + color.rgb = mix(vec3(gray), color.rgb, 0.2); // 20% saturation + color.rgb = clamp((color.rgb - 0.5) * 1.2 + 0.5, 0.0, 1.0); // +20% contrast + color.rgb = pow(color.rgb, vec3(1.0/1.28)); + fragColor = vec4(color.rgb, color.a); + } else { + fragColor = vec4(color.rgb * 0.85, color.a); // 85% opacity + } + } + """ +else: + FRAME_FRAGMENT_SHADER = VERSION + """ + in vec2 fragTexCoord; + uniform sampler2D texture0; + uniform sampler2D texture1; + out vec4 fragColor; + uniform int engaged; + + void main() { + float y = texture(texture0, fragTexCoord).r; + vec2 uv = texture(texture1, fragTexCoord).ra - 0.5; + vec3 rgb = vec3(y + 1.402*uv.y, y - 0.344*uv.x - 0.714*uv.y, y + 1.772*uv.x); + if (engaged == 1) { + float gray = dot(rgb, vec3(0.299, 0.587, 0.114)); + rgb = mix(vec3(gray), rgb, 0.2); // 20% saturation + rgb = clamp((rgb - 0.5) * 1.2 + 0.5, 0.0, 1.0); // +20% contrast + fragColor = vec4(rgb, 1.0); + } else { + fragColor = vec4(rgb * 0.85, 1.0); // 85% opacity + } + } + """ + + +class CameraView(Widget): + def __init__(self, name: str, stream_type: VisionStreamType): + super().__init__() + # TODO: implement a receiver and connect thread + self._name = name + # Primary stream + self.client = VisionIpcClient(name, stream_type, conflate=True) + self._stream_type = stream_type + self.available_streams: list[VisionStreamType] = [] + + # Target stream for switching + self._target_client: VisionIpcClient | None = None + self._target_stream_type: VisionStreamType | None = None + self._switching: bool = False + + self._texture_needs_update = True + self.last_connection_attempt: float = 0.0 + self.shader = rl.load_shader_from_memory(VERTEX_SHADER, FRAME_FRAGMENT_SHADER) + self._texture1_loc: int = rl.get_shader_location(self.shader, "texture1") if not TICI else -1 + self._engaged_loc = rl.get_shader_location(self.shader, "engaged") + self._engaged_val = rl.ffi.new("int[1]", [1]) + + 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 + + self._placeholder_color: rl.Color | 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) + + ui_state.add_offroad_transition_callback(self._offroad_transition) + + def _offroad_transition(self): + # Reconnect if not first time going onroad + if ui_state.is_onroad() and self.frame is not None: + # Prevent old frames from showing when going onroad. Qt has a separate thread + # which drains the VisionIpcClient SubSocket for us. Re-connecting is not enough + # and only clears internal buffers, not the message queue. + self.frame = None + self.available_streams.clear() + if self.client: + del self.client + self.client = VisionIpcClient(self._name, self._stream_type, conflate=True) + + def _set_placeholder_color(self, color: rl.Color): + """Set a placeholder color to be drawn when no frame is available.""" + self._placeholder_color = color + + def switch_stream(self, stream_type: VisionStreamType) -> None: + if self._stream_type == stream_type: + return + + if self._switching and self._target_stream_type == stream_type: + return + + cloudlog.debug(f'Preparing switch from {self._stream_type} to {stream_type}') + + if self._target_client: + del self._target_client + + self._target_stream_type = stream_type + self._target_client = VisionIpcClient(self._name, stream_type, conflate=True) + self._switching = True + + @property + def stream_type(self) -> VisionStreamType: + return self._stream_type + + 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) + + self.client = None + + def __del__(self): + self.close() + + def _calc_frame_matrix(self, rect: rl.Rectangle) -> np.ndarray: + if not self.frame: + return np.eye(3) + + # Calculate aspect ratios + widget_aspect_ratio = rect.width / rect.height + frame_aspect_ratio = self.frame.width / self.frame.height + + # Calculate scaling factors to maintain aspect ratio + zx = min(frame_aspect_ratio / widget_aspect_ratio, 1.0) + zy = min(widget_aspect_ratio / frame_aspect_ratio, 1.0) + + return np.array([ + [zx, 0.0, 0.0], + [0.0, zy, 0.0], + [0.0, 0.0, 1.0] + ]) + + def _render(self, rect: rl.Rectangle): + if self._switching: + self._handle_switch() + + if not self._ensure_connection(): + self._draw_placeholder(rect) + return + + # Try to get a new buffer without blocking + buffer = self.client.recv(timeout_ms=0) + if buffer: + self._texture_needs_update = True + self.frame = buffer + + if not self.frame: + self._draw_placeholder(rect) + return + + transform = self._calc_frame_matrix(rect) + src_rect = rl.Rectangle(0, 0, float(self.frame.width), float(self.frame.height)) + # Flip driver camera horizontally + if self._stream_type == VisionStreamType.VISION_STREAM_DRIVER: + src_rect.width = -src_rect.width + + # Calculate scale + scale_x = rect.width * transform[0, 0] # zx + scale_y = rect.height * transform[1, 1] # zy + + # Calculate base position (centered) + x_offset = rect.x + (rect.width - scale_x) / 2 + y_offset = rect.y + (rect.height - scale_y) / 2 + + x_offset += transform[0, 2] * rect.width / 2 + y_offset += transform[1, 2] * rect.height / 2 + + dst_rect = rl.Rectangle(x_offset, y_offset, scale_x, scale_y) + + # Render with appropriate method + if TICI: + self._render_egl(src_rect, dst_rect) + else: + self._render_textures(src_rect, dst_rect) + + def _draw_placeholder(self, rect: rl.Rectangle): + if self._placeholder_color: + rl.draw_rectangle_rec(rect, self._placeholder_color) + + 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) + self._update_texture_color_filtering() + 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 + if self._texture_needs_update: + 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)) + self._texture_needs_update = False + + # Render with shader + rl.begin_shader_mode(self.shader) + self._update_texture_color_filtering() + rl.set_shader_value_texture(self.shader, self._texture1_loc, 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 _update_texture_color_filtering(self): + self._engaged_val[0] = 1 if ui_state.status != UIStatus.DISENGAGED else 0 + rl.set_shader_value(self.shader, self._engaged_loc, self._engaged_val, rl.ShaderUniformDataType.SHADER_UNIFORM_INT) + + def _ensure_connection(self) -> bool: + if not self.client.is_connected(): + self.frame = None + self.available_streams.clear() + + # Throttle connection attempts + current_time = rl.get_time() + if current_time - self.last_connection_attempt < CONNECTION_RETRY_INTERVAL: + return False + self.last_connection_attempt = current_time + + if not self.client.connect(False) or not self.client.num_buffers: + return False + + cloudlog.debug(f"Connected to {self._name} stream: {self._stream_type}, buffers: {self.client.num_buffers}") + self._initialize_textures() + self.available_streams = self.client.available_streams(self._name, block=False) + + return True + + def _handle_switch(self) -> None: + """Check if target stream is ready and switch immediately.""" + if not self._target_client or not self._switching: + return + + # Try to connect target if needed + if not self._target_client.is_connected(): + if not self._target_client.connect(False) or not self._target_client.num_buffers: + return + + cloudlog.debug(f"Target stream connected: {self._target_stream_type}") + + # Check if target has frames ready + target_frame = self._target_client.recv(timeout_ms=0) + if target_frame: + self.frame = target_frame # Update current frame to target frame + self._complete_switch() + + def _complete_switch(self) -> None: + """Instantly switch to target stream.""" + cloudlog.debug(f"Switching to {self._target_stream_type}") + # Clean up current resources + if self.client: + del self.client + + # Switch to target + self.client = self._target_client + self._stream_type = self._target_stream_type + self._texture_needs_update = True + + # Reset state + self._target_client = None + self._target_stream_type = None + self._switching = False + + # Initialize textures for new stream + self._initialize_textures() + + def _initialize_textures(self): + 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)) + + 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("camera view") + road = CameraView("camerad", VisionStreamType.VISION_STREAM_ROAD) + for _ in gui_app.render(): + road.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) diff --git a/selfdrive/ui/mici/onroad/confidence_ball.py b/selfdrive/ui/mici/onroad/confidence_ball.py new file mode 100644 index 000000000..de792282c --- /dev/null +++ b/selfdrive/ui/mici/onroad/confidence_ball.py @@ -0,0 +1,78 @@ +import math +import pyray as rl +from openpilot.selfdrive.ui.mici.onroad import SIDE_PANEL_WIDTH +from openpilot.selfdrive.ui.ui_state import ui_state, UIStatus +from openpilot.system.ui.widgets import Widget +from openpilot.system.ui.lib.application import gui_app +from openpilot.common.filter_simple import FirstOrderFilter + + +def draw_circle_gradient(center_x: float, center_y: float, radius: int, + top: rl.Color, bottom: rl.Color) -> None: + # Draw a square with the gradient + rl.draw_rectangle_gradient_v(int(center_x - radius), int(center_y - radius), + radius * 2, radius * 2, + top, bottom) + + # Paint over square with a ring + outer_radius = math.ceil(radius * math.sqrt(2)) + 1 + rl.draw_ring(rl.Vector2(center_x, center_y), radius, outer_radius, + 0.0, 360.0, + 20, rl.BLACK) + + +class ConfidenceBall(Widget): + def __init__(self, demo: bool = False): + super().__init__() + self._demo = demo + self._confidence_filter = FirstOrderFilter(-0.5, 0.5, 1 / gui_app.target_fps) + + def update_filter(self, value: float): + self._confidence_filter.update(value) + + def _update_state(self): + if self._demo: + return + + # animate status dot in from bottom + if ui_state.status == UIStatus.DISENGAGED: + self._confidence_filter.update(-0.5) + else: + self._confidence_filter.update((1 - max(ui_state.sm['modelV2'].meta.disengagePredictions.brakeDisengageProbs or [1])) * + (1 - max(ui_state.sm['modelV2'].meta.disengagePredictions.steerOverrideProbs or [1]))) + + def _render(self, _): + content_rect = rl.Rectangle( + self.rect.x + self.rect.width - SIDE_PANEL_WIDTH, + self.rect.y, + SIDE_PANEL_WIDTH, + self.rect.height, + ) + + status_dot_radius = 24 + dot_height = (1 - self._confidence_filter.x) * (content_rect.height - 2 * status_dot_radius) + status_dot_radius + dot_height = self._rect.y + dot_height + + # confidence zones + if ui_state.status == UIStatus.ENGAGED or self._demo: + if self._confidence_filter.x > 0.5: + top_dot_color = rl.Color(0, 255, 204, 255) + bottom_dot_color = rl.Color(0, 255, 38, 255) + elif self._confidence_filter.x > 0.2: + top_dot_color = rl.Color(255, 200, 0, 255) + bottom_dot_color = rl.Color(255, 115, 0, 255) + else: + top_dot_color = rl.Color(255, 0, 21, 255) + bottom_dot_color = rl.Color(255, 0, 89, 255) + + elif ui_state.status == UIStatus.OVERRIDE: + top_dot_color = rl.Color(255, 255, 255, 255) + bottom_dot_color = rl.Color(82, 82, 82, 255) + + else: + top_dot_color = rl.Color(50, 50, 50, 255) + bottom_dot_color = rl.Color(13, 13, 13, 255) + + draw_circle_gradient(content_rect.x + content_rect.width - status_dot_radius, + dot_height, status_dot_radius, + top_dot_color, bottom_dot_color) diff --git a/selfdrive/ui/mici/onroad/driver_camera_dialog.py b/selfdrive/ui/mici/onroad/driver_camera_dialog.py new file mode 100644 index 000000000..f2fa5e8fe --- /dev/null +++ b/selfdrive/ui/mici/onroad/driver_camera_dialog.py @@ -0,0 +1,241 @@ +import pyray as rl +from cereal import log, messaging +from msgq.visionipc import VisionStreamType +from openpilot.selfdrive.ui.mici.onroad.cameraview import CameraView +from openpilot.selfdrive.ui.mici.onroad.driver_state import DriverStateRenderer +from openpilot.selfdrive.ui.ui_state import ui_state, device +from openpilot.selfdrive.selfdrived.events import EVENTS, ET +from openpilot.system.ui.lib.application import gui_app, FontWeight +from openpilot.system.ui.lib.multilang import tr +from openpilot.system.ui.widgets import NavWidget +from openpilot.system.ui.widgets.label import gui_label + +EventName = log.OnroadEvent.EventName + +EVENT_TO_INT = EventName.schema.enumerants + + +class DriverCameraDialog(NavWidget): + def __init__(self, no_escape=False): + super().__init__() + self._camera_view = CameraView("camerad", VisionStreamType.VISION_STREAM_DRIVER) + self._original_calc_frame_matrix = self._camera_view._calc_frame_matrix + self._camera_view._calc_frame_matrix = self._calc_driver_frame_matrix + self.driver_state_renderer = DriverStateRenderer(lines=True) + self.driver_state_renderer.set_rect(rl.Rectangle(0, 0, 200, 200)) + self.driver_state_renderer.load_icons() + self._pm = messaging.PubMaster(['selfdriveState']) + if not no_escape: + # TODO: this can grow unbounded, should be given some thought + device.add_interactive_timeout_callback(self.stop_dmonitoringmodeld) + self.set_back_callback(self._dismiss) + self.set_back_enabled(not no_escape) + + # Load eye icons + self._eye_fill_texture = None + self._eye_orange_texture = None + self._eye_size = 74 + self._glasses_texture = None + self._glasses_size = 171 + + self._load_eye_textures() + + def stop_dmonitoringmodeld(self): + ui_state.params.put_bool("IsDriverViewEnabled", False) + gui_app.set_modal_overlay(None) + + def show_event(self): + super().show_event() + ui_state.params.put_bool("IsDriverViewEnabled", True) + self._publish_alert_sound(None) + device.reset_interactive_timeout(300) + ui_state.params.remove("DriverTooDistracted") + + def hide_event(self): + super().hide_event() + device.reset_interactive_timeout() + + def _handle_mouse_release(self, _): + ui_state.params.remove("DriverTooDistracted") + + def _dismiss(self): + self.stop_dmonitoringmodeld() + + def close(self): + if self._camera_view: + self._camera_view.close() + + def _update_state(self): + if self._camera_view: + self._camera_view._update_state() + # Enable driver state renderer to show Dmoji in preview + self.driver_state_renderer.set_should_draw(True) + self.driver_state_renderer.set_force_active(True) + super()._update_state() + + def _render(self, rect): + rl.begin_scissor_mode(int(rect.x), int(rect.y), int(rect.width), int(rect.height)) + self._camera_view._render(rect) + + if not self._camera_view.frame: + gui_label(rect, tr("camera starting"), font_size=54, font_weight=FontWeight.BOLD, + alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER) + rl.end_scissor_mode() + self._publish_alert_sound(None) + return -1 + + self._draw_face_detection(rect) + + # Position dmoji on opposite side from driver + dm_state = ui_state.sm["driverMonitoringState"] + driver_state_rect = ( + rect.x if dm_state.isRHD else rect.x + rect.width - self.driver_state_renderer.rect.width, + rect.y + (rect.height - self.driver_state_renderer.rect.height) / 2, + ) + self.driver_state_renderer.set_position(*driver_state_rect) + self.driver_state_renderer.render() + + # Render driver monitoring alerts + self._render_dm_alerts(rect) + + rl.end_scissor_mode() + return -1 + + def _publish_alert_sound(self, dm_state): + """Publish selfdriveState with only alertSound field set""" + msg = messaging.new_message('selfdriveState') + if dm_state is not None and len(dm_state.events): + event_name = EVENT_TO_INT[dm_state.events[0].name] + if event_name is not None and event_name in EVENTS and ET.PERMANENT in EVENTS[event_name]: + msg.selfdriveState.alertSound = EVENTS[event_name][ET.PERMANENT].audible_alert + self._pm.send('selfdriveState', msg) + + def _render_dm_alerts(self, rect: rl.Rectangle): + """Render driver monitoring event names""" + dm_state = ui_state.sm["driverMonitoringState"] + self._publish_alert_sound(dm_state) + + gui_label(rl.Rectangle(rect.x + 2, rect.y + 2, rect.width, rect.height), + f"Awareness: {dm_state.awarenessStatus * 100:.0f}%", font_size=44, font_weight=FontWeight.MEDIUM, + alignment=rl.GuiTextAlignment.TEXT_ALIGN_RIGHT, + alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP, + color=rl.Color(0, 0, 0, 180)) + gui_label(rect, f"Awareness: {dm_state.awarenessStatus * 100:.0f}%", font_size=44, font_weight=FontWeight.MEDIUM, + alignment=rl.GuiTextAlignment.TEXT_ALIGN_RIGHT, + alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP, + color=rl.Color(255, 255, 255, int(255 * 0.9))) + + if not dm_state.events: + return + + # Show first event (only one should be active at a time) + event_name_str = str(dm_state.events[0].name).split('.')[-1] + alignment = rl.GuiTextAlignment.TEXT_ALIGN_RIGHT if dm_state.isRHD else rl.GuiTextAlignment.TEXT_ALIGN_LEFT + + shadow_rect = rl.Rectangle(rect.x + 2, rect.y + 2, rect.width, rect.height) + gui_label(shadow_rect, event_name_str, font_size=40, font_weight=FontWeight.BOLD, + alignment=alignment, + alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM, + color=rl.Color(0, 0, 0, 180)) + gui_label(rect, event_name_str, font_size=40, font_weight=FontWeight.BOLD, + alignment=alignment, + alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM, + color=rl.Color(255, 255, 255, int(255 * 0.9))) + + def _load_eye_textures(self): + """Lazy load eye textures""" + if self._eye_fill_texture is None: + self._eye_fill_texture = gui_app.texture("icons_mici/onroad/eye_fill.png", self._eye_size, self._eye_size) + if self._eye_orange_texture is None: + self._eye_orange_texture = gui_app.texture("icons_mici/onroad/eye_orange.png", self._eye_size, self._eye_size) + if self._glasses_texture is None: + self._glasses_texture = gui_app.texture("icons_mici/onroad/glasses.png", self._glasses_size, self._glasses_size) + + def _draw_face_detection(self, rect: rl.Rectangle) -> None: + driver_state = ui_state.sm["driverStateV2"] + is_rhd = driver_state.wheelOnRightProb > 0.5 + driver_data = driver_state.rightDriverData if is_rhd else driver_state.leftDriverData + face_detect = driver_data.faceProb > 0.7 + if not face_detect: + return + + # Get face position and orientation + face_x, face_y = driver_data.facePosition + face_std = max(driver_data.faceOrientationStd[0], driver_data.faceOrientationStd[1]) + alpha = 0.7 + if face_std > 0.15: + alpha = max(0.7 - (face_std - 0.15) * 3.5, 0.0) + + # use approx instead of distort_points + # TODO: replace with distort_points + tici_x = 1080.0 - 1714.0 * face_x + tici_y = -135.0 + (504.0 + abs(face_x) * 112.0) + (1205.0 - abs(face_x) * 724.0) * face_y + + # Tici coords are relative to center, scale offset + offset_x = (tici_x - 1080.0) * 1.25 + offset_y = (tici_y - 540.0) * 1.25 + + # Map to mici screen (scale from 2160x1080 to rect dimensions) + scale_x = rect.width / 2160.0 + scale_y = rect.height / 1080.0 + fbox_x = rect.x + rect.width / 2 + offset_x * scale_x + fbox_y = rect.y + rect.height / 2 + offset_y * scale_y + box_size = 50 + line_thickness = 3 + + line_color = rl.Color(255, 255, 255, int(alpha * 255)) + rl.draw_rectangle_rounded_lines_ex( + rl.Rectangle(fbox_x - box_size / 2, fbox_y - box_size / 2, box_size, box_size), + 35.0 / box_size / 2, + line_thickness, + line_thickness, + line_color, + ) + + # Draw eye indicators based on eye probabilities + eye_offset_x = 10 + eye_offset_y = 10 + eye_spacing = self._eye_size + 15 + + left_eye_x = rect.x + eye_offset_x + left_eye_y = rect.y + eye_offset_y + left_eye_prob = driver_data.leftEyeProb + + right_eye_x = rect.x + eye_offset_x + eye_spacing + right_eye_y = rect.y + eye_offset_y + right_eye_prob = driver_data.rightEyeProb + + # Draw eyes with opacity based on probability + for eye_x, eye_y, eye_prob in [(left_eye_x, left_eye_y, left_eye_prob), (right_eye_x, right_eye_y, right_eye_prob)]: + fill_opacity = eye_prob + orange_opacity = 1.0 - eye_prob + + rl.draw_texture_v(self._eye_orange_texture, (eye_x, eye_y), rl.Color(255, 255, 255, int(255 * orange_opacity))) + rl.draw_texture_v(self._eye_fill_texture, (eye_x, eye_y), rl.Color(255, 255, 255, int(255 * fill_opacity))) + + # Draw sunglasses indicator based on sunglasses probability + # Position glasses centered between the two eyes at top left + glasses_x = rect.x + eye_offset_x - 4 + glasses_y = rect.y + glasses_pos = rl.Vector2(glasses_x, glasses_y) + glasses_prob = driver_data.sunglassesProb + rl.draw_texture_v(self._glasses_texture, glasses_pos, rl.Color(70, 80, 161, int(255 * glasses_prob))) + + def _calc_driver_frame_matrix(self, rect: rl.Rectangle): + base = self._original_calc_frame_matrix(rect) + driver_view_ratio = 1.5 + base[0, 0] *= driver_view_ratio + base[1, 1] *= driver_view_ratio + return base + + +if __name__ == "__main__": + gui_app.init_window("Driver Camera View (mici)") + + driver_camera_view = DriverCameraDialog() + try: + for _ in gui_app.render(): + ui_state.update() + driver_camera_view.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) + finally: + driver_camera_view.close() diff --git a/selfdrive/ui/mici/onroad/driver_state.py b/selfdrive/ui/mici/onroad/driver_state.py new file mode 100644 index 000000000..369055846 --- /dev/null +++ b/selfdrive/ui/mici/onroad/driver_state.py @@ -0,0 +1,227 @@ +import pyray as rl +from collections.abc import Callable +import numpy as np +import math +from cereal import log +from openpilot.common.filter_simple import FirstOrderFilter +from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.widgets import Widget +from openpilot.selfdrive.ui.ui_state import ui_state + +AlertSize = log.SelfdriveState.AlertSize + +DEBUG = False + +LOOKING_CENTER_THRESHOLD_UPPER = math.radians(6) +LOOKING_CENTER_THRESHOLD_LOWER = math.radians(3) + + +class DriverStateRenderer(Widget): + BASE_SIZE = 60 + LINES_ANGLE_INCREMENT = 5 + LINES_STALE_ANGLES = 3.0 # seconds + + def __init__(self, lines: bool = False, confirm_mode: bool = False, confirm_callback: Callable | None = None): + super().__init__() + self.set_rect(rl.Rectangle(0, 0, self.BASE_SIZE, self.BASE_SIZE)) + self._lines = lines or confirm_mode + + # In confirm mode, user must fill out the circle to confirm some action in the UI + self._confirm_mode = confirm_mode + self._confirm_callback = confirm_callback + self._confirm_angles: dict[int, float] = {} # angle: timestamp + + # In line mode, track smoothed angles + assert 360 % self.LINES_ANGLE_INCREMENT == 0 + self._head_angles = {i * self.LINES_ANGLE_INCREMENT: FirstOrderFilter(0.0, 0.1, 1 / gui_app.target_fps) for i in range(360 // self.LINES_ANGLE_INCREMENT)} + + self._is_active = False + self._is_rhd = False + self._face_detected = False + self._should_draw = False + self._force_active = False + self._looking_center = False + + self._fade_filter = FirstOrderFilter(0.0, 0.05, 1 / gui_app.target_fps) + self._pitch_filter = FirstOrderFilter(0.0, 0.05, 1 / gui_app.target_fps, initialized=False) + self._yaw_filter = FirstOrderFilter(0.0, 0.05, 1 / gui_app.target_fps, initialized=False) + self._rotation_filter = FirstOrderFilter(0.0, 0.1, 1 / gui_app.target_fps, initialized=False) + self._looking_center_filter = FirstOrderFilter(0.0, 0.1, 1 / gui_app.target_fps) + + # Load the driver face icons + self.load_icons() + + def load_icons(self): + """Load or reload the driver face icon texture""" + self._dm_person = gui_app.texture("icons_mici/onroad/driver_monitoring/dm_person.png", self._rect.width, self._rect.height) + self._dm_cone = gui_app.texture("icons_mici/onroad/driver_monitoring/dm_cone.png", self._rect.width, self._rect.height) + center_size = round(36 / self.BASE_SIZE * self._rect.width) + self._dm_center = gui_app.texture("icons_mici/onroad/driver_monitoring/dm_center.png", center_size, center_size) + background_size = round(52 / self.BASE_SIZE * self._rect.width) + self._dm_background = gui_app.texture("icons_mici/onroad/driver_monitoring/dm_background.png", background_size, background_size) + + def set_should_draw(self, should_draw: bool): + self._should_draw = should_draw + + @property + def should_draw(self): + return (self._should_draw and ui_state.sm["selfdriveState"].alertSize == AlertSize.none and + ui_state.sm.recv_frame["driverStateV2"] > ui_state.started_frame) + + def set_force_active(self, force_active: bool): + """Force the dmoji to always appear active (green) regardless of actual state""" + self._force_active = force_active + + @property + def effective_active(self) -> bool: + """Returns True if dmoji should appear active (either actually active or forced)""" + return bool(self._force_active or self._is_active) + + def _render(self, _): + if DEBUG: + rl.draw_rectangle_lines_ex(self._rect, 1, rl.RED) + + rl.draw_texture(self._dm_background, + int(self._rect.x + (self._rect.width - self._dm_background.width) / 2), + int(self._rect.y + (self._rect.height - self._dm_background.height) / 2), + rl.Color(255, 255, 255, int(255 * self._fade_filter.x))) + + rl.draw_texture(self._dm_person, int(self._rect.x), int(self._rect.y), + rl.Color(255, 255, 255, int(255 * 0.9 * self._fade_filter.x))) + + if self.effective_active: + source_rect = rl.Rectangle(0, 0, self._dm_cone.width, self._dm_cone.height) + dest_rect = rl.Rectangle( + self._rect.x + self._rect.width / 2, + self._rect.y + self._rect.height / 2, + self._dm_cone.width, + self._dm_cone.height, + ) + + if not self._lines: + rl.draw_texture_pro( + self._dm_cone, + source_rect, + dest_rect, + rl.Vector2(dest_rect.width / 2, dest_rect.height / 2), + self._rotation_filter.x - 90, + rl.Color(255, 255, 255, int(255 * self._fade_filter.x * (1 - self._looking_center_filter.x))), + ) + + rl.draw_texture_ex( + self._dm_center, + (int(self._rect.x + (self._rect.width - self._dm_center.width) / 2), + int(self._rect.y + (self._rect.height - self._dm_center.height) / 2)), + 0, + 1.0, + rl.Color(255, 255, 255, int(255 * self._fade_filter.x * self._looking_center_filter.x)), + ) + + else: + # remove old angles + now = rl.get_time() + self._confirm_angles = {angle: t for angle, t in self._confirm_angles.items() if now - t < self.LINES_STALE_ANGLES} + + looking_center = self._looking_center_filter.x > 0.2 + for angle, f in self._head_angles.items(): + dst_from_current = ((angle - self._rotation_filter.x) % 360) - 180 + target = 1.0 if abs(dst_from_current) <= self.LINES_ANGLE_INCREMENT * 5 else 0.0 + if not self._face_detected: + target = 0.0 + + if self._confirm_mode: + # Extra careful to not add angles when looking near center + if target > 0 and not looking_center: + self._confirm_angles[angle] = now + + # User is looking at area already confirmed, reduce target to indicate where they are + if angle in self._confirm_angles and target == 0: + target = 0.65 + + # Reduce all line lengths when looking center + if self._looking_center: + target = np.interp(self._looking_center_filter.x, [0.0, 1.0], [target, 0.45]) + + f.update(target) + self._draw_line(angle, f, self._looking_center and angle not in self._confirm_angles) + + # if all lines placed, reset for next time and call callback + if self._confirm_mode: + if len(self._confirm_angles) >= 360 // self.LINES_ANGLE_INCREMENT: + self._confirm_angles = {} + if self._confirm_callback is not None: + self._confirm_callback() + + def _draw_line(self, angle: int, f: FirstOrderFilter, grey: bool): + line_length = self._rect.width / 6 + line_length = round(np.interp(f.x, [0.0, 1.0], [0, line_length])) + line_offset = self._rect.width / 2 - line_length * 2 # ensure line ends within rect + center_x = self._rect.x + self._rect.width / 2 + center_y = self._rect.y + self._rect.height / 2 + start_x = center_x + (line_offset + line_length) * math.cos(math.radians(angle)) + start_y = center_y + (line_offset + line_length) * math.sin(math.radians(angle)) + end_x = start_x + line_length * math.cos(math.radians(angle)) + end_y = start_y + line_length * math.sin(math.radians(angle)) + color = rl.Color(0, 255, 64, 255) + + if grey: + color = rl.Color(166, 166, 166, 255) + + if f.x > 0.01: + rl.draw_line_ex((start_x, start_y), (end_x, end_y), 12, color) + + def _update_state(self): + sm = ui_state.sm + + # Get monitoring state + dm_state = sm["driverMonitoringState"] + self._is_active = dm_state.isActiveMode + self._is_rhd = dm_state.isRHD + self._face_detected = dm_state.faceDetected + + driverstate = sm["driverStateV2"] + driver_data = driverstate.rightDriverData if self._is_rhd else driverstate.leftDriverData + driver_orient = driver_data.faceOrientation + + if len(driver_orient) != 3: + return + + pitch, yaw, roll = driver_orient + pitch = self._pitch_filter.update(pitch) + yaw = self._yaw_filter.update(yaw) + + # hysteresis on looking center + if abs(pitch) < LOOKING_CENTER_THRESHOLD_LOWER and abs(yaw) < LOOKING_CENTER_THRESHOLD_LOWER: + self._looking_center = True + elif abs(pitch) > LOOKING_CENTER_THRESHOLD_UPPER or abs(yaw) > LOOKING_CENTER_THRESHOLD_UPPER: + self._looking_center = False + self._looking_center_filter.update(1 if self._looking_center else 0) + + if DEBUG: + pitchd = math.degrees(pitch) + yawd = math.degrees(yaw) + rolld = math.degrees(roll) + + rl.draw_line_ex((0, 100), (200, 100), 3, rl.RED) + rl.draw_line_ex((0, 120), (200, 120), 3, rl.RED) + rl.draw_line_ex((0, 140), (200, 140), 3, rl.RED) + + pitch_x = 100 + pitchd + yaw_x = 100 + yawd + roll_x = 100 + rolld + rl.draw_circle(int(pitch_x), 100, 5, rl.GREEN) + rl.draw_circle(int(yaw_x), 120, 5, rl.GREEN) + rl.draw_circle(int(roll_x), 140, 5, rl.GREEN) + + # filter head rotation, handling wrap-around + rotation = math.degrees(math.atan2(pitch, yaw)) + angle_diff = rotation - self._rotation_filter.x + angle_diff = ((angle_diff + 180) % 360) - 180 + self._rotation_filter.update(self._rotation_filter.x + angle_diff) + + if not self.should_draw: + self._fade_filter.update(0.0) + elif not self.effective_active: + self._fade_filter.update(0.35) + else: + self._fade_filter.update(1.0) diff --git a/selfdrive/ui/mici/onroad/hud_renderer.py b/selfdrive/ui/mici/onroad/hud_renderer.py new file mode 100644 index 000000000..bb5171d6e --- /dev/null +++ b/selfdrive/ui/mici/onroad/hud_renderer.py @@ -0,0 +1,287 @@ +import pyray as rl +from dataclasses import dataclass +from openpilot.common.constants import CV +from openpilot.selfdrive.ui.mici.onroad.torque_bar import TorqueBar +from openpilot.selfdrive.ui.ui_state import ui_state, UIStatus +from openpilot.system.ui.lib.application import gui_app, FontWeight +from openpilot.system.ui.lib.multilang import tr +from openpilot.system.ui.lib.text_measure import measure_text_cached +from openpilot.system.ui.widgets import Widget +from openpilot.common.filter_simple import FirstOrderFilter +from cereal import log + +EventName = log.OnroadEvent.EventName + +# Constants +SET_SPEED_NA = 255 +KM_TO_MILE = 0.621371 +CRUISE_DISABLED_CHAR = '–' + +SET_SPEED_PERSISTENCE = 2.5 # seconds + + +@dataclass(frozen=True) +class FontSizes: + current_speed: int = 176 + speed_unit: int = 66 + max_speed: int = 36 + set_speed: int = 112 + + +@dataclass(frozen=True) +class Colors: + white: rl.Color = rl.WHITE + disengaged: rl.Color = rl.Color(145, 155, 149, 255) + override: rl.Color = rl.Color(145, 155, 149, 255) # Added + engaged: rl.Color = rl.Color(128, 216, 166, 255) + disengaged_bg: rl.Color = rl.Color(0, 0, 0, 153) + override_bg: rl.Color = rl.Color(145, 155, 149, 204) + engaged_bg: rl.Color = rl.Color(128, 216, 166, 204) + grey: rl.Color = rl.Color(166, 166, 166, 255) + dark_grey: rl.Color = rl.Color(114, 114, 114, 255) + black_translucent: rl.Color = rl.Color(0, 0, 0, 166) + white_translucent: rl.Color = rl.Color(255, 255, 255, 200) + border_translucent: rl.Color = rl.Color(255, 255, 255, 75) + header_gradient_start: rl.Color = rl.Color(0, 0, 0, 114) + header_gradient_end: rl.Color = rl.BLANK + + +FONT_SIZES = FontSizes() +COLORS = Colors() + + +class TurnIntent(Widget): + FADE_IN_ANGLE = 30 # degrees + + def __init__(self): + super().__init__() + self._pre = False + self._turn_intent_direction: int = 0 + + self._turn_intent_alpha_filter = FirstOrderFilter(0, 0.05, 1 / gui_app.target_fps) + self._turn_intent_rotation_filter = FirstOrderFilter(0, 0.1, 1 / gui_app.target_fps) + + self._txt_turn_intent_left: rl.Texture = gui_app.texture('icons_mici/turn_intent_left.png', 50, 19) + self._txt_turn_intent_right: rl.Texture = gui_app.texture('icons_mici/turn_intent_right.png', 50, 19) + + def _render(self, _): + if self._turn_intent_alpha_filter.x > 1e-2: + turn_intent_texture = self._txt_turn_intent_right if self._turn_intent_direction == 1 else self._txt_turn_intent_left + src_rect = rl.Rectangle(0, 0, turn_intent_texture.width, turn_intent_texture.height) + dest_rect = rl.Rectangle(self._rect.x + self._rect.width / 2, self._rect.y + self._rect.height / 2, + turn_intent_texture.width, turn_intent_texture.height) + + origin = (turn_intent_texture.width / 2, self._rect.height / 2) + color = rl.Color(255, 255, 255, int(255 * self._turn_intent_alpha_filter.x)) + rl.draw_texture_pro(turn_intent_texture, src_rect, dest_rect, origin, self._turn_intent_rotation_filter.x, color) + + def _update_state(self) -> None: + sm = ui_state.sm + + left = any(e.name == EventName.preLaneChangeLeft for e in sm['onroadEvents']) + right = any(e.name == EventName.preLaneChangeRight for e in sm['onroadEvents']) + if left or right: + # pre lane change + if not self._pre: + self._turn_intent_rotation_filter.x = self.FADE_IN_ANGLE if left else -self.FADE_IN_ANGLE + + self._pre = True + self._turn_intent_direction = -1 if left else 1 + self._turn_intent_alpha_filter.update(1) + self._turn_intent_rotation_filter.update(0) + elif any(e.name == EventName.laneChange for e in sm['onroadEvents']): + # fade out and rotate away + self._pre = False + self._turn_intent_alpha_filter.update(0) + + if self._turn_intent_direction == 0: + # unknown. missed pre frame? + self._turn_intent_rotation_filter.update(0) + else: + self._turn_intent_rotation_filter.update(self._turn_intent_direction * self.FADE_IN_ANGLE) + else: + # didn't complete lane change, just hide + self._pre = False + self._turn_intent_direction = 0 + self._turn_intent_alpha_filter.update(0) + self._turn_intent_rotation_filter.update(0) + + +class HudRenderer(Widget): + def __init__(self): + super().__init__() + """Initialize the HUD renderer.""" + self.is_cruise_set: bool = False + self.is_cruise_available: bool = True + self.set_speed: float = SET_SPEED_NA + self._set_speed_changed_time: float = 0 + self.speed: float = 0.0 + self.v_ego_cluster_seen: bool = False + self._engaged: bool = False + + self._can_draw_top_icons = True + self._show_wheel_critical = False + + self._font_bold: rl.Font = gui_app.font(FontWeight.BOLD) + self._font_medium: rl.Font = gui_app.font(FontWeight.MEDIUM) + self._font_semi_bold: rl.Font = gui_app.font(FontWeight.SEMI_BOLD) + self._font_display: rl.Font = gui_app.font(FontWeight.DISPLAY) + + self._turn_intent = TurnIntent() + self._torque_bar = TorqueBar() + + self._txt_wheel: rl.Texture = gui_app.texture('icons_mici/wheel.png', 50, 50) + self._txt_wheel_critical: rl.Texture = gui_app.texture('icons_mici/wheel_critical.png', 50, 50) + self._txt_exclamation_point: rl.Texture = gui_app.texture('icons_mici/exclamation_point.png', 44, 44) + + self._wheel_alpha_filter = FirstOrderFilter(0, 0.05, 1 / gui_app.target_fps) + self._wheel_y_filter = FirstOrderFilter(0, 0.1, 1 / gui_app.target_fps) + + self._set_speed_alpha_filter = FirstOrderFilter(0.0, 0.1, 1 / gui_app.target_fps) + + def set_wheel_critical_icon(self, critical: bool): + """Set the wheel icon to critical or normal state.""" + self._show_wheel_critical = critical + + def set_can_draw_top_icons(self, can_draw_top_icons: bool): + """Set whether to draw the top part of the HUD.""" + self._can_draw_top_icons = can_draw_top_icons + + def drawing_top_icons(self) -> bool: + # whether we're drawing any top icons currently + return bool(self._set_speed_alpha_filter.x > 1e-2) + + def _update_state(self) -> None: + """Update HUD state based on car state and controls state.""" + sm = ui_state.sm + if sm.recv_frame["carState"] < ui_state.started_frame: + self.is_cruise_set = False + self.set_speed = SET_SPEED_NA + self.speed = 0.0 + return + + controls_state = sm['controlsState'] + car_state = sm['carState'] + + v_cruise_cluster = car_state.vCruiseCluster + set_speed = ( + controls_state.vCruiseDEPRECATED if v_cruise_cluster == 0.0 else v_cruise_cluster + ) + engaged = sm['selfdriveState'].enabled + if (set_speed != self.set_speed and engaged) or (engaged and not self._engaged): + self._set_speed_changed_time = rl.get_time() + self._engaged = engaged + self.set_speed = set_speed + self.is_cruise_set = 0 < self.set_speed < SET_SPEED_NA + self.is_cruise_available = self.set_speed != -1 + + v_ego_cluster = car_state.vEgoCluster + self.v_ego_cluster_seen = self.v_ego_cluster_seen or v_ego_cluster != 0.0 + v_ego = v_ego_cluster if self.v_ego_cluster_seen else car_state.vEgo + speed_conversion = CV.MS_TO_KPH if ui_state.is_metric else CV.MS_TO_MPH + self.speed = max(0.0, v_ego * speed_conversion) + + def _render(self, rect: rl.Rectangle) -> None: + """Render HUD elements to the screen.""" + + if ui_state.sm['controlsState'].lateralControlState.which() != 'angleState': + self._torque_bar.render(rect) + + if self.is_cruise_set: + self._draw_set_speed(rect) + + self._draw_steering_wheel(rect) + + def _draw_steering_wheel(self, rect: rl.Rectangle) -> None: + wheel_txt = self._txt_wheel_critical if self._show_wheel_critical else self._txt_wheel + + if self._show_wheel_critical: + self._wheel_alpha_filter.update(255) + self._wheel_y_filter.update(0) + else: + if ui_state.status == UIStatus.DISENGAGED: + self._wheel_alpha_filter.update(0) + self._wheel_y_filter.update(wheel_txt.height / 2) + else: + self._wheel_alpha_filter.update(255 * 0.9) + self._wheel_y_filter.update(0) + + # pos + pos_x = int(rect.x + 21 + wheel_txt.width / 2) + pos_y = int(rect.y + rect.height - 14 - wheel_txt.height / 2 + self._wheel_y_filter.x) + rotation = -ui_state.sm['carState'].steeringAngleDeg + + turn_intent_margin = 25 + self._turn_intent.render(rl.Rectangle( + pos_x - wheel_txt.width / 2 - turn_intent_margin, + pos_y - wheel_txt.height / 2 - turn_intent_margin, + wheel_txt.width + turn_intent_margin * 2, + wheel_txt.height + turn_intent_margin * 2, + )) + + src_rect = rl.Rectangle(0, 0, wheel_txt.width, wheel_txt.height) + dest_rect = rl.Rectangle(pos_x, pos_y, wheel_txt.width, wheel_txt.height) + origin = (wheel_txt.width / 2, wheel_txt.height / 2) + + # color and draw + color = rl.Color(255, 255, 255, int(self._wheel_alpha_filter.x)) + rl.draw_texture_pro(wheel_txt, src_rect, dest_rect, origin, rotation, color) + + if self._show_wheel_critical: + # Draw exclamation point icon + EXCLAMATION_POINT_SPACING = 10 + exclamation_pos_x = pos_x - self._txt_exclamation_point.width / 2 + wheel_txt.width / 2 + EXCLAMATION_POINT_SPACING + exclamation_pos_y = pos_y - self._txt_exclamation_point.height / 2 + rl.draw_texture(self._txt_exclamation_point, int(exclamation_pos_x), int(exclamation_pos_y), rl.WHITE) + + def _draw_set_speed(self, rect: rl.Rectangle) -> None: + """Draw the MAX speed indicator box.""" + x = rect.x + y = rect.y + + alpha = self._set_speed_alpha_filter.update(0 < rl.get_time() - self._set_speed_changed_time < SET_SPEED_PERSISTENCE and + self._can_draw_top_icons and self._engaged) + + # draw drop shadow + circle_radius = 162 // 2 + rl.draw_circle_gradient(int(x + circle_radius), int(y + circle_radius), circle_radius, + rl.Color(0, 0, 0, int(255 / 2 * alpha)), rl.Color(0, 0, 0, 0)) + + set_speed_color = rl.Color(255, 255, 255, int(255 * 0.9 * alpha)) + max_color = rl.Color(255, 255, 255, int(255 * 0.9 * alpha)) + + set_speed = self.set_speed + if self.is_cruise_set and not ui_state.is_metric: + set_speed *= KM_TO_MILE + + set_speed_text = CRUISE_DISABLED_CHAR if not self.is_cruise_set else str(round(set_speed)) + rl.draw_text_ex( + self._font_display, + set_speed_text, + rl.Vector2(x + 13 + 4, y + 3 - 8 - 3 + 4), + FONT_SIZES.set_speed, + 0, + set_speed_color, + ) + + max_text = tr("MAX") + rl.draw_text_ex( + self._font_semi_bold, + max_text, + rl.Vector2(x + 25, y + FONT_SIZES.set_speed - 7 + 4), + FONT_SIZES.max_speed, + 0, + max_color, + ) + + def _draw_current_speed(self, rect: rl.Rectangle) -> None: + """Draw the current vehicle speed and unit.""" + speed_text = str(round(self.speed)) + speed_text_size = measure_text_cached(self._font_bold, speed_text, FONT_SIZES.current_speed) + speed_pos = rl.Vector2(rect.x + rect.width / 2 - speed_text_size.x / 2, 180 - speed_text_size.y / 2) + rl.draw_text_ex(self._font_bold, speed_text, speed_pos, FONT_SIZES.current_speed, 0, COLORS.white) + + unit_text = tr("km/h") if ui_state.is_metric else tr("mph") + unit_text_size = measure_text_cached(self._font_medium, unit_text, FONT_SIZES.speed_unit) + unit_pos = rl.Vector2(rect.x + rect.width / 2 - unit_text_size.x / 2, 290 - unit_text_size.y / 2) + rl.draw_text_ex(self._font_medium, unit_text, unit_pos, FONT_SIZES.speed_unit, 0, COLORS.white_translucent) diff --git a/selfdrive/ui/mici/onroad/model_renderer.py b/selfdrive/ui/mici/onroad/model_renderer.py new file mode 100644 index 000000000..3f1badfe8 --- /dev/null +++ b/selfdrive/ui/mici/onroad/model_renderer.py @@ -0,0 +1,479 @@ +import colorsys +import numpy as np +import pyray as rl +from cereal import messaging, car +from dataclasses import dataclass, field +from openpilot.common.params import Params +from openpilot.common.filter_simple import FirstOrderFilter +from openpilot.selfdrive.locationd.calibrationd import HEIGHT_INIT +from openpilot.selfdrive.ui.ui_state import ui_state, UIStatus +from openpilot.selfdrive.ui.mici.onroad import blend_colors +from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.lib.shader_polygon import draw_polygon, Gradient +from openpilot.system.ui.widgets import Widget + +CLIP_MARGIN = 500 +MIN_DRAW_DISTANCE = 10.0 +MAX_DRAW_DISTANCE = 100.0 + +THROTTLE_COLORS = [ + rl.Color(13, 248, 122, 102), # HSLF(148/360, 0.94, 0.51, 0.4) + rl.Color(114, 255, 92, 89), # HSLF(112/360, 1.0, 0.68, 0.35) + rl.Color(114, 255, 92, 0), # HSLF(112/360, 1.0, 0.68, 0.0) +] + +NO_THROTTLE_COLORS = [ + rl.Color(242, 242, 242, 102), # HSLF(148/360, 0.0, 0.95, 0.4) + rl.Color(242, 242, 242, 89), # HSLF(112/360, 0.0, 0.95, 0.35) + rl.Color(242, 242, 242, 0), # HSLF(112/360, 0.0, 0.95, 0.0) +] + +LANE_LINE_COLORS = { + UIStatus.DISENGAGED: rl.Color(200, 200, 200, 255), + UIStatus.OVERRIDE: rl.Color(255, 255, 255, 255), + UIStatus.ENGAGED: rl.Color(0, 255, 64, 255), +} + + +@dataclass +class ModelPoints: + raw_points: np.ndarray = field(default_factory=lambda: np.empty((0, 3), dtype=np.float32)) + projected_points: np.ndarray = field(default_factory=lambda: np.empty((0, 2), dtype=np.float32)) + + +@dataclass +class LeadVehicle: + glow: list[float] = field(default_factory=list) + chevron: list[float] = field(default_factory=list) + fill_alpha: int = 0 + + +class ModelRenderer(Widget): + def __init__(self): + super().__init__() + self._longitudinal_control = False + self._experimental_mode = False + self._blend_filter = FirstOrderFilter(1.0, 0.25, 1 / gui_app.target_fps) + self._prev_allow_throttle = True + self._lane_line_probs = np.zeros(4, dtype=np.float32) + self._road_edge_stds = np.zeros(2, dtype=np.float32) + self._lead_vehicles = [LeadVehicle(), LeadVehicle()] + self._path_offset_z = HEIGHT_INIT[0] + + # Initialize ModelPoints objects + self._path = ModelPoints() + self._lane_lines = [ModelPoints() for _ in range(4)] + self._road_edges = [ModelPoints() for _ in range(2)] + self._acceleration_x = np.empty((0,), dtype=np.float32) + + self._acceleration_x_filter = FirstOrderFilter(0.0, 0.1, 1 / gui_app.target_fps) + self._acceleration_x_filter2 = FirstOrderFilter(0.0, 1, 1 / gui_app.target_fps) + + self._torque_filter = FirstOrderFilter(0, 0.1, 1 / gui_app.target_fps) + self._ll_color_filter = FirstOrderFilter(0.0, 0.1, 1 / gui_app.target_fps) + + # Transform matrix (3x3 for car space to screen space) + self._car_space_transform = np.zeros((3, 3), dtype=np.float32) + self._transform_dirty = True + self._clip_region = None + + self._exp_gradient = Gradient( + start=(0.0, 1.0), # Bottom of path + end=(0.0, 0.0), # Top of path + colors=[], + stops=[], + ) + + # Get longitudinal control setting from car parameters + if car_params := Params().get("CarParams"): + cp = messaging.log_from_bytes(car_params, car.CarParams) + self._longitudinal_control = cp.openpilotLongitudinalControl + + def set_transform(self, transform: np.ndarray): + self._car_space_transform = transform.astype(np.float32) + self._transform_dirty = True + + def _render(self, rect: rl.Rectangle): + sm = ui_state.sm + + self._torque_filter.update(-ui_state.sm['carOutput'].actuatorsOutput.torque) + + # Check if data is up-to-date + if (sm.recv_frame["liveCalibration"] < ui_state.started_frame or + sm.recv_frame["modelV2"] < ui_state.started_frame): + return + + # Set up clipping region + self._clip_region = rl.Rectangle( + rect.x - CLIP_MARGIN, rect.y - CLIP_MARGIN, rect.width + 2 * CLIP_MARGIN, rect.height + 2 * CLIP_MARGIN + ) + + # Update state + self._experimental_mode = sm['selfdriveState'].experimentalMode + + live_calib = sm['liveCalibration'] + self._path_offset_z = live_calib.height[0] if live_calib.height else HEIGHT_INIT[0] + + if sm.updated['carParams']: + self._longitudinal_control = sm['carParams'].openpilotLongitudinalControl + + model = sm['modelV2'] + radar_state = sm['radarState'] if sm.valid['radarState'] else None + lead_one = radar_state.leadOne if radar_state else None + render_lead_indicator = self._longitudinal_control and radar_state is not None + + # Update model data when needed + model_updated = sm.updated['modelV2'] + if model_updated or sm.updated['radarState'] or self._transform_dirty: + if model_updated: + self._update_raw_points(model) + + path_x_array = self._path.raw_points[:, 0] + if path_x_array.size == 0: + return + + self._update_model(lead_one, path_x_array) + if render_lead_indicator: + self._update_leads(radar_state, path_x_array) + self._transform_dirty = False + + # Draw elements (hide when disengaged) + if ui_state.status != UIStatus.DISENGAGED: + self._draw_lane_lines() + self._draw_path(sm) + + # if render_lead_indicator and radar_state: + # self._draw_lead_indicator() + + def _update_raw_points(self, model): + """Update raw 3D points from model data""" + self._path.raw_points = np.array([model.position.x, model.position.y, model.position.z], dtype=np.float32).T + + for i, lane_line in enumerate(model.laneLines): + self._lane_lines[i].raw_points = np.array([lane_line.x, lane_line.y, lane_line.z], dtype=np.float32).T + + for i, road_edge in enumerate(model.roadEdges): + self._road_edges[i].raw_points = np.array([road_edge.x, road_edge.y, road_edge.z], dtype=np.float32).T + + self._lane_line_probs = np.array(model.laneLineProbs, dtype=np.float32) + self._road_edge_stds = np.array(model.roadEdgeStds, dtype=np.float32) + self._acceleration_x = np.array(model.acceleration.x, dtype=np.float32) + + def _update_leads(self, radar_state, path_x_array): + """Update positions of lead vehicles""" + self._lead_vehicles = [LeadVehicle(), LeadVehicle()] + leads = [radar_state.leadOne, radar_state.leadTwo] + + for i, lead_data in enumerate(leads): + if lead_data and lead_data.status: + d_rel, y_rel, v_rel = lead_data.dRel, lead_data.yRel, lead_data.vRel + idx = self._get_path_length_idx(path_x_array, d_rel) + + # Get z-coordinate from path at the lead vehicle position + z = self._path.raw_points[idx, 2] if idx < len(self._path.raw_points) else 0.0 + point = self._map_to_screen(d_rel, -y_rel, z + self._path_offset_z) + if point: + self._lead_vehicles[i] = self._update_lead_vehicle(d_rel, v_rel, point, self._rect) + + def _update_model(self, lead, path_x_array): + """Update model visualization data based on model message""" + max_distance = np.clip(path_x_array[-1], MIN_DRAW_DISTANCE, MAX_DRAW_DISTANCE) + max_idx = self._get_path_length_idx(self._lane_lines[0].raw_points[:, 0], max_distance) + + # Update lane lines using raw points + line_width_factor = 0.12 + for i, lane_line in enumerate(self._lane_lines): + if i in (1, 2): + line_width_factor = 0.16 + lane_line.projected_points = self._map_line_to_polygon( + lane_line.raw_points, line_width_factor * self._lane_line_probs[i], 0.0, max_idx + ) + + # Update road edges using raw points + for road_edge in self._road_edges: + road_edge.projected_points = self._map_line_to_polygon(road_edge.raw_points, line_width_factor, 0.0, max_idx) + + # Update path using raw points + if lead and lead.status: + lead_d = lead.dRel * 2.0 + max_distance = np.clip(lead_d - min(lead_d * 0.35, 10.0), 0.0, max_distance) + + soon_acceleration = self._acceleration_x[len(self._acceleration_x) // 4] if len(self._acceleration_x) > 0 else 0 + self._acceleration_x_filter.update(soon_acceleration) + self._acceleration_x_filter2.update(soon_acceleration) + + # make path width wider/thinner when initially braking/accelerating + if self._experimental_mode and False: + high_pass_acceleration = self._acceleration_x_filter.x - self._acceleration_x_filter2.x + y_off = np.interp(high_pass_acceleration, [-1, 0, 1], [0.9 * 2, 0.9, 0.9 / 2]) + else: + y_off = 0.9 + + max_idx = self._get_path_length_idx(path_x_array, max_distance) + self._path.projected_points = self._map_line_to_polygon( + self._path.raw_points, y_off, self._path_offset_z, max_idx, allow_invert=False + ) + + self._update_experimental_gradient() + + def _update_experimental_gradient(self): + """Pre-calculate experimental mode gradient colors""" + if not self._experimental_mode: + return + + max_len = min(len(self._path.projected_points) // 2, len(self._acceleration_x)) + + segment_colors = [] + gradient_stops = [] + + i = 0 + while i < max_len: + # Some points (screen space) are out of frame (rect space) + track_y = self._path.projected_points[i][1] + if track_y < self._rect.y or track_y > (self._rect.y + self._rect.height): + i += 1 + continue + + # Calculate color based on acceleration (0 is bottom, 1 is top) + lin_grad_point = 1 - (track_y - self._rect.y) / self._rect.height + + # speed up: 120, slow down: 0 + path_hue = np.clip(60 + self._acceleration_x[i] * 35, 0, 120) + + saturation = min(abs(self._acceleration_x[i] * 1.5), 1) + lightness = np.interp(saturation, [0.0, 1.0], [0.95, 0.62]) + alpha = np.interp(lin_grad_point, [0.75 / 2.0, 0.75], [0.4, 0.0]) + + # Use HSL to RGB conversion + color = self._hsla_to_color(path_hue / 360.0, saturation, lightness, alpha) + + gradient_stops.append(lin_grad_point) + segment_colors.append(color) + + # Skip a point, unless next is last + i += 1 + (1 if (i + 2) < max_len else 0) + + # Store the gradient in the path object + self._exp_gradient.colors = segment_colors + self._exp_gradient.stops = gradient_stops + + def _update_lead_vehicle(self, d_rel, v_rel, point, rect): + speed_buff, lead_buff = 10.0, 40.0 + + # Calculate fill alpha + fill_alpha = 0 + if d_rel < lead_buff: + fill_alpha = 255 * (1.0 - (d_rel / lead_buff)) + if v_rel < 0: + fill_alpha += 255 * (-1 * (v_rel / speed_buff)) + fill_alpha = min(fill_alpha, 255) + + # Calculate size and position + sz = np.clip((25 * 30) / (d_rel / 3 + 30), 15.0, 30.0) * 1 + x = np.clip(point[0], 0.0, rect.width - sz / 2) + y = min(point[1], rect.height - sz * 0.6) + + g_xo = sz / 5 + g_yo = sz / 10 + + glow = [(x + (sz * 1.35) + g_xo, y + sz + g_yo), (x, y - g_yo), (x - (sz * 1.35) - g_xo, y + sz + g_yo)] + chevron = [(x + (sz * 1.25), y + sz), (x, y), (x - (sz * 1.25), y + sz)] + + return LeadVehicle(glow=glow, chevron=chevron, fill_alpha=int(fill_alpha)) + + def _get_ll_color(self, prob: float, adjacent: bool, left: bool): + alpha = np.clip(prob, 0.0, 0.7) + if adjacent: + _base_color = LANE_LINE_COLORS.get(ui_state.status, LANE_LINE_COLORS[UIStatus.DISENGAGED]) + color = rl.Color(_base_color.r, _base_color.g, _base_color.b, int(alpha * 255)) + + # turn adjacent lls orange if torque is high + torque = self._torque_filter.x + high_torque = abs(torque) > 0.6 + if high_torque and (left == (torque > 0)): + color = blend_colors( + color, + rl.Color(255, 115, 0, int(alpha * 255)), # orange + np.interp(abs(torque), [0.6, 0.8], [0.0, 1.0]) + ) + else: + color = rl.Color(255, 255, 255, int(alpha * 255)) + + if ui_state.status == UIStatus.DISENGAGED: + color = rl.Color(0, 0, 0, int(alpha * 255)) + + return color + + def _draw_lane_lines(self): + """Draw lane lines and road edges""" + """Two closest lines should be green (lane line or road edges)""" + for i, lane_line in enumerate(self._lane_lines): + if lane_line.projected_points.size == 0: + continue + + color = self._get_ll_color(float(self._lane_line_probs[i]), i in (1, 2), i in (0, 1)) + draw_polygon(self._rect, lane_line.projected_points, color) + + for i, road_edge in enumerate(self._road_edges): + if road_edge.projected_points.size == 0: + continue + + # if closest lane lines are not confident, make road edges green + color = self._get_ll_color(float(1.0 - self._road_edge_stds[i]), float(self._lane_line_probs[i + 1]) < 0.25, i == 0) + draw_polygon(self._rect, road_edge.projected_points, color) + + def _draw_path(self, sm): + """Draw path with dynamic coloring based on mode and throttle state.""" + if not self._path.projected_points.size: + return + + allow_throttle = sm['longitudinalPlan'].allowThrottle or not self._longitudinal_control + self._blend_filter.update(int(allow_throttle)) + + if self._experimental_mode: + # Draw with acceleration coloring + if ui_state.status == UIStatus.DISENGAGED: + draw_polygon(self._rect, self._path.projected_points, rl.Color(0, 0, 0, 90)) + elif len(self._exp_gradient.colors) > 1: + draw_polygon(self._rect, self._path.projected_points, gradient=self._exp_gradient) + else: + draw_polygon(self._rect, self._path.projected_points, rl.Color(255, 255, 255, 30)) + else: + # Blend throttle/no throttle colors based on transition + blend_factor = round(self._blend_filter.x * 100) / 100 + blended_colors = self._blend_colors(NO_THROTTLE_COLORS, THROTTLE_COLORS, blend_factor) + gradient = Gradient( + start=(0.0, 1.0), # Bottom of path + end=(0.0, 0.0), # Top of path + colors=blended_colors, + stops=[0.0, 0.5, 1.0], + ) + + if ui_state.status == UIStatus.DISENGAGED: + draw_polygon(self._rect, self._path.projected_points, rl.Color(0, 0, 0, 90)) + else: + draw_polygon(self._rect, self._path.projected_points, gradient=gradient) + + def _draw_lead_indicator(self): + # Draw lead vehicles if available + for lead in self._lead_vehicles: + if not lead.glow or not lead.chevron: + continue + + rl.draw_triangle_fan(lead.glow, len(lead.glow), rl.Color(218, 202, 37, 255)) + rl.draw_triangle_fan(lead.chevron, len(lead.chevron), rl.Color(201, 34, 49, lead.fill_alpha)) + + @staticmethod + def _get_path_length_idx(pos_x_array: np.ndarray, path_height: float) -> int: + """Get the index corresponding to the given path height""" + if len(pos_x_array) == 0: + return 0 + indices = np.where(pos_x_array <= path_height)[0] + return indices[-1] if indices.size > 0 else 0 + + def _map_to_screen(self, in_x, in_y, in_z): + """Project a point in car space to screen space""" + input_pt = np.array([in_x, in_y, in_z]) + pt = self._car_space_transform @ input_pt + + if abs(pt[2]) < 1e-6: + return None + + x, y = pt[0] / pt[2], pt[1] / pt[2] + + clip = self._clip_region + if not (clip.x <= x <= clip.x + clip.width and clip.y <= y <= clip.y + clip.height): + return None + + return (x, y) + + def _map_line_to_polygon(self, line: np.ndarray, y_off: float, z_off: float, max_idx: int, allow_invert: bool = True) -> np.ndarray: + """Convert 3D line to 2D polygon for rendering.""" + if line.shape[0] == 0: + return np.empty((0, 2), dtype=np.float32) + + # Slice points and filter non-negative x-coordinates + points = line[:max_idx + 1] + points = points[points[:, 0] >= 0] + if points.shape[0] == 0: + return np.empty((0, 2), dtype=np.float32) + + N = points.shape[0] + # Generate left and right 3D points in one array using broadcasting + offsets = np.array([[0, -y_off, z_off], [0, y_off, z_off]], dtype=np.float32) + points_3d = points[None, :, :] + offsets[:, None, :] # Shape: 2xNx3 + points_3d = points_3d.reshape(2 * N, 3) # Shape: (2*N)x3 + + # Transform all points to projected space in one operation + proj = self._car_space_transform @ points_3d.T # Shape: 3x(2*N) + proj = proj.reshape(3, 2, N) + left_proj = proj[:, 0, :] + right_proj = proj[:, 1, :] + + # Filter points where z is sufficiently large + valid_proj = (np.abs(left_proj[2]) >= 1e-6) & (np.abs(right_proj[2]) >= 1e-6) + if not np.any(valid_proj): + return np.empty((0, 2), dtype=np.float32) + + # Compute screen coordinates + left_screen = left_proj[:2, valid_proj] / left_proj[2, valid_proj][None, :] + right_screen = right_proj[:2, valid_proj] / right_proj[2, valid_proj][None, :] + + # Define clip region bounds + clip = self._clip_region + x_min, x_max = clip.x, clip.x + clip.width + y_min, y_max = clip.y, clip.y + clip.height + + # Filter points within clip region + left_in_clip = ( + (left_screen[0] >= x_min) & (left_screen[0] <= x_max) & + (left_screen[1] >= y_min) & (left_screen[1] <= y_max) + ) + right_in_clip = ( + (right_screen[0] >= x_min) & (right_screen[0] <= x_max) & + (right_screen[1] >= y_min) & (right_screen[1] <= y_max) + ) + both_in_clip = left_in_clip & right_in_clip + + if not np.any(both_in_clip): + return np.empty((0, 2), dtype=np.float32) + + # Select valid and clipped points + left_screen = left_screen[:, both_in_clip] + right_screen = right_screen[:, both_in_clip] + + # Handle Y-coordinate inversion on hills + if not allow_invert and left_screen.shape[1] > 1: + y = left_screen[1, :] # y-coordinates + keep = y == np.minimum.accumulate(y) + if not np.any(keep): + return np.empty((0, 2), dtype=np.float32) + left_screen = left_screen[:, keep] + right_screen = right_screen[:, keep] + + return np.vstack((left_screen.T, right_screen[:, ::-1].T)).astype(np.float32) + + @staticmethod + def _hsla_to_color(h, s, l, a): + rgb = colorsys.hls_to_rgb(h, l, s) + return rl.Color( + int(rgb[0] * 255), + int(rgb[1] * 255), + int(rgb[2] * 255), + int(a * 255) + ) + + @staticmethod + def _blend_colors(begin_colors, end_colors, t): + if t >= 1.0: + return end_colors + if t <= 0.0: + return begin_colors + + inv_t = 1.0 - t + return [rl.Color( + int(inv_t * start.r + t * end.r), + int(inv_t * start.g + t * end.g), + int(inv_t * start.b + t * end.b), + int(inv_t * start.a + t * end.a) + ) for start, end in zip(begin_colors, end_colors, strict=True)] diff --git a/selfdrive/ui/mici/onroad/torque_bar.py b/selfdrive/ui/mici/onroad/torque_bar.py new file mode 100644 index 000000000..1f6dffe87 --- /dev/null +++ b/selfdrive/ui/mici/onroad/torque_bar.py @@ -0,0 +1,253 @@ +import math +import time +from functools import wraps +from collections import OrderedDict + +import numpy as np +import pyray as rl +from opendbc.car import ACCELERATION_DUE_TO_GRAVITY +from openpilot.selfdrive.ui.mici.onroad import blend_colors +from openpilot.selfdrive.ui.ui_state import ui_state, UIStatus +from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.lib.shader_polygon import draw_polygon, Gradient +from openpilot.system.ui.widgets import Widget +from openpilot.common.filter_simple import FirstOrderFilter + +# TODO: arc_bar_pts doesn't consider rounded end caps part of the angle span +TORQUE_ANGLE_SPAN = 12.7 + +DEBUG = False + + +def quantized_lru_cache(maxsize=128): + def decorator(func): + cache = OrderedDict() + @wraps(func) + def wrapper(cx, cy, r_mid, thickness, a0_deg, a1_deg, **kwargs): + # Quantize inputs: balanced for smoothness vs cache effectiveness + key = (round(cx), round(cy), round(r_mid), + round(thickness), # 1px precision for smoother height transitions + round(a0_deg * 10) / 10, # 0.1° precision for smoother angle transitions + round(a1_deg * 10) / 10, + tuple(sorted(kwargs.items()))) + + if key in cache: + cache.move_to_end(key) + else: + if len(cache) >= maxsize: + cache.popitem(last=False) + + result = func(cx, cy, r_mid, thickness, a0_deg, a1_deg, **kwargs) + cache[key] = result + return cache[key] + return wrapper + return decorator + + +@quantized_lru_cache(maxsize=256) +def arc_bar_pts(cx: float, cy: float, + r_mid: float, thickness: float, + a0_deg: float, a1_deg: float, + *, max_points: int = 100, cap_segs: int = 10, + cap_radius: float = 7, px_per_seg: float = 2.0) -> np.ndarray: + """Return Nx2 np.float32 points for a single closed polygon (rounded thick arc).""" + + def get_cap(left: bool, a_deg: float): + # end cap at a1: center (a1), sweep a1→a1+180 (skip endpoints to avoid dupes) + # quarter arc (outer corner) at a1 with fixed pixel radius cap_radius + + nx, ny = math.cos(math.radians(a_deg)), math.sin(math.radians(a_deg)) # outward normal + tx, ty = -ny, nx # tangent (CCW) + + mx, my = cx + nx * r_mid, cy + ny * r_mid # mid-point at a1 + if DEBUG: + rl.draw_circle(int(mx), int(my), 4, rl.PURPLE) + + ex = mx + nx * (half - cap_radius) + ey = my + ny * (half - cap_radius) + + if DEBUG: + rl.draw_circle(int(ex), int(ey), 2, rl.WHITE) + + # sweep 90° in the local (t,n) frame: from outer edge toward inside + if not left: + alpha = np.deg2rad(np.linspace(90, 0, cap_segs + 2))[1:-1] + else: + alpha = np.deg2rad(np.linspace(180, 90, cap_segs + 2))[1:-1] + cap_end = np.c_[ex + np.cos(alpha) * cap_radius * tx + np.sin(alpha) * cap_radius * nx, + ey + np.cos(alpha) * cap_radius * ty + np.sin(alpha) * cap_radius * ny] + + # bottom quarter (inner corner) at a1 + ex2 = mx + nx * (-half + cap_radius) + ey2 = my + ny * (-half + cap_radius) + if DEBUG: + rl.draw_circle(int(ex2), int(ey2), 2, rl.WHITE) + + if not left: + alpha2 = np.deg2rad(np.linspace(0, -90, cap_segs + 1))[:-1] # include 0 once, exclude -90 + else: + alpha2 = np.deg2rad(np.linspace(90 - 90 - 90, 0 - 90 - 90, cap_segs + 1))[:-1] + cap_end_bot = np.c_[ex2 + np.cos(alpha2) * cap_radius * tx + np.sin(alpha2) * cap_radius * nx, + ey2 + np.cos(alpha2) * cap_radius * ty + np.sin(alpha2) * cap_radius * ny] + + # append to the top quarter + if not left: + cap_end = np.vstack((cap_end, cap_end_bot)) + else: + cap_end = np.vstack((cap_end_bot, cap_end)) + + return cap_end + + if a1_deg < a0_deg: + a0_deg, a1_deg = a1_deg, a0_deg + half = thickness * 0.5 + + cap_radius = min(cap_radius, half) + + span = max(1e-3, a1_deg - a0_deg) + + # pick arc segment count from arc length, clamp to shader points[] budget + arc_len = r_mid * math.radians(span) + arc_segs = max(6, int(arc_len / px_per_seg)) + max_arc = (max_points - (4 * cap_segs + 3)) // 2 + arc_segs = max(6, min(arc_segs, max_arc)) + + # outer arc a0→a1 + ang_o = np.deg2rad(np.linspace(a0_deg, a1_deg, arc_segs + 1)) + outer = np.c_[cx + np.cos(ang_o) * (r_mid + half), + cy + np.sin(ang_o) * (r_mid + half)] + + # end cap at a1 + cap_end = get_cap(False, a1_deg) + + # inner arc a1→a0 + ang_i = np.deg2rad(np.linspace(a1_deg, a0_deg, arc_segs + 1)) + inner = np.c_[cx + np.cos(ang_i) * (r_mid - half), + cy + np.sin(ang_i) * (r_mid - half)] + + # start cap at a0 + cap_start = get_cap(True, a0_deg) + + pts = np.vstack((outer, cap_end, inner, cap_start, outer[:1])).astype(np.float32) + + if DEBUG: + n = len(pts) + idx = int(time.monotonic() * 12) % max(1, n) # speed: 12 pts/sec + for i, (x, y) in enumerate(pts): + j = (i - idx) % n # rotate the gradient + t = j / n + color = rl.Color(255, int(255 * (1 - t)), int(255 * t), 255) + rl.draw_circle(int(x), int(y), 2, color) + + return pts + + +class TorqueBar(Widget): + def __init__(self, demo: bool = False): + super().__init__() + self._demo = demo + self._torque_filter = FirstOrderFilter(0, 0.1, 1 / gui_app.target_fps) + self._torque_line_alpha_filter = FirstOrderFilter(0.0, 0.1, 1 / gui_app.target_fps) + + def update_filter(self, value: float): + """Update the torque filter value (for demo mode).""" + self._torque_filter.update(value) + + def _update_state(self): + if self._demo: + return + + # torque line + if ui_state.sm['controlsState'].lateralControlState.which() == 'angleState': + controls_state = ui_state.sm['controlsState'] + car_state = ui_state.sm['carState'] + live_parameters = ui_state.sm['liveParameters'] + lateral_acceleration = controls_state.curvature * car_state.vEgo ** 2 - live_parameters.roll * ACCELERATION_DUE_TO_GRAVITY + # TODO: pull from carparams + max_lateral_acceleration = 3 + + # from selfdrived + actual_lateral_accel = controls_state.curvature * car_state.vEgo ** 2 + desired_lateral_accel = controls_state.desiredCurvature * car_state.vEgo ** 2 + accel_diff = (desired_lateral_accel - actual_lateral_accel) + + self._torque_filter.update(min(max(lateral_acceleration / max_lateral_acceleration + accel_diff, -1), 1)) + else: + self._torque_filter.update(-ui_state.sm['carOutput'].actuatorsOutput.torque) + + def _render(self, rect: rl.Rectangle) -> None: + # adjust y pos with torque + torque_line_offset = np.interp(abs(self._torque_filter.x), [0.5, 1], [22, 26]) + torque_line_height = np.interp(abs(self._torque_filter.x), [0.5, 1], [14, 56]) + + # animate alpha and angle span + if not self._demo: + self._torque_line_alpha_filter.update(ui_state.status != UIStatus.DISENGAGED) + else: + self._torque_line_alpha_filter.update(1.0) + + torque_line_bg_alpha = np.interp(abs(self._torque_filter.x), [0.5, 1.0], [0.25, 0.5]) + torque_line_bg_color = rl.Color(255, 255, 255, int(255 * torque_line_bg_alpha * self._torque_line_alpha_filter.x)) + if ui_state.status != UIStatus.ENGAGED and not self._demo: + torque_line_bg_color = rl.Color(255, 255, 255, int(255 * 0.15 * self._torque_line_alpha_filter.x)) + + # draw curved line polygon torque bar + torque_line_radius = 1200 + top_angle = -90 + torque_bg_angle_span = self._torque_line_alpha_filter.x * TORQUE_ANGLE_SPAN + torque_start_angle = top_angle - torque_bg_angle_span / 2 + torque_end_angle = top_angle + torque_bg_angle_span / 2 + # centerline radius & center (you already have these values) + mid_r = torque_line_radius + torque_line_height / 2 + + cx = rect.x + rect.width / 2 + 8 # offset 8px to right of camera feed + cy = rect.y + rect.height + torque_line_radius - torque_line_offset + + # draw bg torque indicator line + bg_pts = arc_bar_pts(cx, cy, mid_r, torque_line_height, torque_start_angle, torque_end_angle) + draw_polygon(rect, bg_pts, color=torque_line_bg_color) + + # draw torque indicator line + a0s = top_angle + a1s = a0s + torque_bg_angle_span / 2 * self._torque_filter.x + sl_pts = arc_bar_pts(cx, cy, mid_r, torque_line_height, a0s, a1s) + + # draw beautiful gradient from center to 65% of the bg torque bar width + start_grad_pt = cx / rect.width + if self._torque_filter.x < 0: + end_grad_pt = (cx * (1 - 0.65) + (min(bg_pts[:, 0]) * 0.65)) / rect.width + else: + end_grad_pt = (cx * (1 - 0.65) + (max(bg_pts[:, 0]) * 0.65)) / rect.width + + # fade to orange as we approach max torque + start_color = blend_colors( + rl.Color(255, 255, 255, int(255 * 0.9 * self._torque_line_alpha_filter.x)), + rl.Color(255, 200, 0, int(255 * self._torque_line_alpha_filter.x)), # yellow + max(0, abs(self._torque_filter.x) - 0.75) * 4, + ) + end_color = blend_colors( + rl.Color(255, 255, 255, int(255 * 0.9 * self._torque_line_alpha_filter.x)), + rl.Color(255, 115, 0, int(255 * self._torque_line_alpha_filter.x)), # orange + max(0, abs(self._torque_filter.x) - 0.75) * 4, + ) + + if ui_state.status != UIStatus.ENGAGED and not self._demo: + start_color = end_color = rl.Color(255, 255, 255, int(255 * 0.35 * self._torque_line_alpha_filter.x)) + + gradient = Gradient( + start=(start_grad_pt, 0), + end=(end_grad_pt, 0), + colors=[ + start_color, + end_color, + ], + stops=[0.0, 1.0], + ) + + draw_polygon(rect, sl_pts, gradient=gradient) + + # draw center torque bar dot + if abs(self._torque_filter.x) < 0.5: + dot_y = self._rect.y + self._rect.height - torque_line_offset - torque_line_height / 2 + rl.draw_circle(int(cx), int(dot_y), 10 // 2, + rl.Color(182, 182, 182, int(255 * 0.9 * self._torque_line_alpha_filter.x))) diff --git a/selfdrive/ui/mici/widgets/button.py b/selfdrive/ui/mici/widgets/button.py new file mode 100644 index 000000000..be08e0fee --- /dev/null +++ b/selfdrive/ui/mici/widgets/button.py @@ -0,0 +1,375 @@ +import pyray as rl +from typing import Union +from enum import Enum +from collections.abc import Callable +from openpilot.system.ui.widgets import Widget +from openpilot.system.ui.widgets.label import MiciLabel +from openpilot.system.ui.widgets.scroller import DO_ZOOM +from openpilot.system.ui.lib.text_measure import measure_text_cached +from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos +from openpilot.common.filter_simple import BounceFilter + +try: + from openpilot.common.params import Params +except ImportError: + Params = None + +SCROLLING_SPEED_PX_S = 50 +COMPLICATION_SIZE = 36 +LABEL_COLOR = rl.WHITE +LABEL_HORIZONTAL_PADDING = 40 +COMPLICATION_GREY = rl.Color(0xAA, 0xAA, 0xAA, 255) +PRESSED_SCALE = 1.15 if DO_ZOOM else 1.07 + + +class ScrollState(Enum): + PRE_SCROLL = 0 + SCROLLING = 1 + POST_SCROLL = 2 + + +class BigCircleButton(Widget): + def __init__(self, icon: str, red: bool = False): + super().__init__() + self._red = red + + # State + self.set_rect(rl.Rectangle(0, 0, 180, 180)) + self._press_state_enabled = True + self._scale_filter = BounceFilter(1.0, 0.1, 1 / gui_app.target_fps) + + # Icons + self._txt_icon = gui_app.texture(icon, 64, 53) + self._txt_btn_disabled_bg = gui_app.texture("icons_mici/buttons/button_circle_disabled.png", 180, 180) + + self._txt_btn_bg = gui_app.texture("icons_mici/buttons/button_circle.png", 180, 180) + self._txt_btn_pressed_bg = gui_app.texture("icons_mici/buttons/button_circle_hover.png", 180, 180) + + self._txt_btn_red_bg = gui_app.texture("icons_mici/buttons/button_circle_red.png", 180, 180) + self._txt_btn_red_pressed_bg = gui_app.texture("icons_mici/buttons/button_circle_red_hover.png", 180, 180) + + def set_enable_pressed_state(self, pressed: bool): + self._press_state_enabled = pressed + + def _render(self, _): + # draw background + txt_bg = self._txt_btn_bg if not self._red else self._txt_btn_red_bg + if not self.enabled: + txt_bg = self._txt_btn_disabled_bg + elif self.is_pressed and self._press_state_enabled: + txt_bg = self._txt_btn_pressed_bg if not self._red else self._txt_btn_red_pressed_bg + + scale = self._scale_filter.update(PRESSED_SCALE if self.is_pressed and self._press_state_enabled else 1.0) + btn_x = self._rect.x + (self._rect.width * (1 - scale)) / 2 + btn_y = self._rect.y + (self._rect.height * (1 - scale)) / 2 + rl.draw_texture_ex(txt_bg, (btn_x, btn_y), 0, scale, rl.WHITE) + + # draw icon + icon_color = rl.WHITE if self.enabled else rl.Color(255, 255, 255, int(255 * 0.35)) + rl.draw_texture(self._txt_icon, int(self._rect.x + (self._rect.width - self._txt_icon.width) / 2), + int(self._rect.y + (self._rect.height - self._txt_icon.height) / 2), icon_color) + + +class BigCircleToggle(BigCircleButton): + def __init__(self, icon: str, toggle_callback: Callable = None): + super().__init__(icon, False) + self._toggle_callback = toggle_callback + + # State + self._checked = False + + # Icons + self._txt_toggle_enabled = gui_app.texture("icons_mici/buttons/toggle_dot_enabled.png", 66, 66) + self._txt_toggle_disabled = gui_app.texture("icons_mici/buttons/toggle_dot_disabled.png", 70, 70) # TODO: why discrepancy? + + def set_checked(self, checked: bool): + self._checked = checked + + def _handle_mouse_release(self, mouse_pos: MousePos): + super()._handle_mouse_release(mouse_pos) + + self._checked = not self._checked + if self._toggle_callback: + self._toggle_callback(self._checked) + + def _render(self, _): + super()._render(_) + + # draw status icon + rl.draw_texture(self._txt_toggle_enabled if self._checked else self._txt_toggle_disabled, + int(self._rect.x + (self._rect.width - self._txt_toggle_enabled.width) / 2), + int(self._rect.y + 5), rl.WHITE) + + +class BigButton(Widget): + """A lightweight stand-in for the Qt BigButton, drawn & updated each frame.""" + + def __init__(self, text: str, value: str = "", icon: Union[str, rl.Texture] = ""): + super().__init__() + self.set_rect(rl.Rectangle(0, 0, 402, 180)) + self.text = text + self.value = value + self.set_icon(icon) + + self._scale_filter = BounceFilter(1.0, 0.1, 1 / gui_app.target_fps) + + self._rotate_icon_t: float | None = None + + self._label_font = gui_app.font(FontWeight.DISPLAY) + self._value_font = gui_app.font(FontWeight.ROMAN) + + self._label = MiciLabel(text, font_size=self._get_label_font_size(), width=int(self._rect.width - LABEL_HORIZONTAL_PADDING * 2), + font_weight=FontWeight.DISPLAY, color=LABEL_COLOR, + alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM, wrap_text=True) + self._sub_label = MiciLabel(value, font_size=COMPLICATION_SIZE, width=int(self._rect.width - LABEL_HORIZONTAL_PADDING * 2), + font_weight=FontWeight.ROMAN, color=COMPLICATION_GREY, + alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM, wrap_text=True) + + self._load_images() + + # internal state + self._scroll_offset = 0 # in pixels + self._needs_scroll = measure_text_cached(self._label_font, text, self._get_label_font_size()).x + 25 > self._rect.width + self._scroll_timer = 0 + self._scroll_state = ScrollState.PRE_SCROLL + + def set_icon(self, icon: Union[str, rl.Texture]): + self._txt_icon = gui_app.texture(icon, 64, 64) if isinstance(icon, str) and len(icon) else icon + + def set_rotate_icon(self, rotate: bool): + if rotate and self._rotate_icon_t is not None: + return + self._rotate_icon_t = rl.get_time() if rotate else None + + def _load_images(self): + self._txt_default_bg = gui_app.texture("icons_mici/buttons/button_rectangle.png", 402, 180) + self._txt_pressed_bg = gui_app.texture("icons_mici/buttons/button_rectangle_pressed.png", 402, 180) + self._txt_disabled_bg = gui_app.texture("icons_mici/buttons/button_rectangle_disabled.png", 402, 180) + self._txt_hover_bg = gui_app.texture("icons_mici/buttons/button_rectangle_hover.png", 402, 180) + + def _get_label_font_size(self): + if len(self.text) < 12: + font_size = 64 + elif len(self.text) < 17: + font_size = 48 + elif len(self.text) < 20: + font_size = 42 + else: + font_size = 36 + + if self.value: + font_size -= 20 + + return font_size + + def set_text(self, text: str): + self.text = text + self._label.set_text(text) + + def set_value(self, value: str): + self.value = value + self._sub_label.set_text(value) + + def get_value(self) -> str: + return self.value + + def get_text(self): + return self.text + + def _update_state(self): + # hold on text for a bit, scroll, hold again, reset + if self._needs_scroll: + """`dt` should be seconds since last frame (rl.get_frame_time()).""" + # TODO: this comment is generated by GPT, prob wrong and misused + dt = rl.get_frame_time() + + self._scroll_timer += dt + if self._scroll_state == ScrollState.PRE_SCROLL: + if self._scroll_timer < 0.5: + return + self._scroll_state = ScrollState.SCROLLING + self._scroll_timer = 0 + + elif self._scroll_state == ScrollState.SCROLLING: + self._scroll_offset -= SCROLLING_SPEED_PX_S * dt + # reset when text has completely left the button + 50 px gap + # TODO: use global constant for 30+30 px gap + # TODO: add std Widget padding option integrated into the self._rect + full_len = measure_text_cached(self._label_font, self.text, self._get_label_font_size()).x + 30 + 30 + if self._scroll_offset < (self._rect.width - full_len): + self._scroll_state = ScrollState.POST_SCROLL + self._scroll_timer = 0 + + elif self._scroll_state == ScrollState.POST_SCROLL: + # wait for a bit before starting to scroll again + if self._scroll_timer < 0.75: + return + self._scroll_state = ScrollState.PRE_SCROLL + self._scroll_timer = 0 + self._scroll_offset = 0 + + def _render(self, _): + # draw _txt_default_bg + txt_bg = self._txt_default_bg + if not self.enabled: + txt_bg = self._txt_disabled_bg + elif self.is_pressed: + txt_bg = self._txt_hover_bg + + scale = self._scale_filter.update(PRESSED_SCALE if self.is_pressed else 1.0) + btn_x = self._rect.x + (self._rect.width * (1 - scale)) / 2 + btn_y = self._rect.y + (self._rect.height * (1 - scale)) / 2 + rl.draw_texture_ex(txt_bg, (btn_x, btn_y), 0, scale, rl.WHITE) + + # LABEL ------------------------------------------------------------------ + lx = self._rect.x + LABEL_HORIZONTAL_PADDING + ly = btn_y + self._rect.height - 33 # - 40# - self._get_label_font_size() / 2 + + if self.value: + self._sub_label.set_position(lx, ly) + ly -= self._sub_label.font_size + 9 + self._sub_label.render() + + label_color = LABEL_COLOR if self.enabled else rl.Color(255, 255, 255, int(255 * 0.35)) + self._label.set_color(label_color) + self._label.set_position(lx, ly) + self._label.render() + + # ICON ------------------------------------------------------------------- + if self._txt_icon: + rotation = 0 + if self._rotate_icon_t is not None: + rotation = (rl.get_time() - self._rotate_icon_t) * 180 + + # drop top right with 30px padding + x = self._rect.x + self._rect.width - 30 - self._txt_icon.width / 2 + y = self._rect.y + 30 + self._txt_icon.height / 2 + source_rec = rl.Rectangle(0, 0, self._txt_icon.width, self._txt_icon.height) + dest_rec = rl.Rectangle(int(x), int(y), self._txt_icon.width, self._txt_icon.height) + origin = rl.Vector2(self._txt_icon.width / 2, self._txt_icon.height / 2) + rl.draw_texture_pro(self._txt_icon, source_rec, dest_rec, origin, rotation, rl.WHITE) + + +class BigToggle(BigButton): + def __init__(self, text: str, value: str = "", initial_state: bool = False, toggle_callback: Callable = None): + super().__init__(text, value, "") + self._checked = initial_state + self._toggle_callback = toggle_callback + + self._label.set_font_size(48) + + def _load_images(self): + super()._load_images() + self._txt_enabled_toggle = gui_app.texture("icons_mici/buttons/toggle_pill_enabled.png", 84, 66) + self._txt_disabled_toggle = gui_app.texture("icons_mici/buttons/toggle_pill_disabled.png", 84, 66) + + def set_checked(self, checked: bool): + self._checked = checked + + def _handle_mouse_release(self, mouse_pos: MousePos): + super()._handle_mouse_release(mouse_pos) + self._checked = not self._checked + if self._toggle_callback: + self._toggle_callback(self._checked) + + def _draw_pill(self, x: float, y: float, checked: bool): + # draw toggle icon top right + if checked: + rl.draw_texture(self._txt_enabled_toggle, int(x), int(y), rl.WHITE) + else: + rl.draw_texture(self._txt_disabled_toggle, int(x), int(y), rl.WHITE) + + def _render(self, _): + super()._render(_) + + x = self._rect.x + self._rect.width - self._txt_enabled_toggle.width + y = self._rect.y + self._draw_pill(x, y, self._checked) + + +class BigMultiToggle(BigToggle): + def __init__(self, text: str, options: list[str], toggle_callback: Callable = None, + select_callback: Callable = None): + super().__init__(text, "", toggle_callback=toggle_callback) + assert len(options) > 0 + self._options = options + self._select_callback = select_callback + + self._label.set_width(int(self._rect.width - LABEL_HORIZONTAL_PADDING * 2 - self._txt_enabled_toggle.width)) + # TODO: why isn't this automatic? + self._label.set_font_size(self._get_label_font_size()) + + self.set_value(self._options[0]) + + def _get_label_font_size(self): + font_size = super()._get_label_font_size() + return font_size - 6 + + def _handle_mouse_release(self, mouse_pos: MousePos): + super()._handle_mouse_release(mouse_pos) + cur_idx = self._options.index(self.value) + new_idx = (cur_idx + 1) % len(self._options) + self.set_value(self._options[new_idx]) + if self._select_callback: + self._select_callback(self.value) + + def _render(self, _): + BigButton._render(self, _) + + checked_idx = self._options.index(self.value) + + x = self._rect.x + self._rect.width - self._txt_enabled_toggle.width + y = self._rect.y + + for i in range(len(self._options)): + self._draw_pill(x, y, checked_idx == i) + y += 35 + + +class BigMultiParamToggle(BigMultiToggle): + def __init__(self, text: str, param: str, options: list[str], toggle_callback: Callable = None, + select_callback: Callable = None): + super().__init__(text, options, toggle_callback, select_callback) + self._param = param + + self._params = Params() + self._load_value() + + def _load_value(self): + self.set_value(self._options[self._params.get(self._param) or 0]) + + def _handle_mouse_release(self, mouse_pos: MousePos): + super()._handle_mouse_release(mouse_pos) + new_idx = self._options.index(self.value) + self._params.put_nonblocking(self._param, new_idx) + + +class BigParamControl(BigToggle): + def __init__(self, text: str, param: str, toggle_callback: Callable = None): + super().__init__(text, "", toggle_callback=toggle_callback) + self.param = param + self.params = Params() + self.set_checked(self.params.get_bool(self.param, False)) + + def _handle_mouse_release(self, mouse_pos: MousePos): + super()._handle_mouse_release(mouse_pos) + self.params.put_bool(self.param, self._checked) + + def refresh(self): + self.set_checked(self.params.get_bool(self.param, False)) + + +# TODO: param control base class +class BigCircleParamControl(BigCircleToggle): + def __init__(self, icon: str, param: str, toggle_callback: Callable = None): + super().__init__(icon, toggle_callback) + self._param = param + self.params = Params() + self.set_checked(self.params.get_bool(self._param, False)) + + def _handle_mouse_release(self, mouse_pos: MousePos): + super()._handle_mouse_release(mouse_pos) + self.params.put_bool(self._param, self._checked) + + def refresh(self): + self.set_checked(self.params.get_bool(self._param, False)) diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py new file mode 100644 index 000000000..d64ab65ef --- /dev/null +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -0,0 +1,395 @@ +import abc +import math +import pyray as rl +from typing import Union +from collections.abc import Callable +from typing import cast +from openpilot.selfdrive.ui.mici.widgets.side_button import SideButton +from openpilot.system.ui.widgets import Widget, NavWidget, DialogResult +from openpilot.system.ui.widgets.label import UnifiedLabel, gui_label +from openpilot.system.ui.widgets.mici_keyboard import MiciKeyboard +from openpilot.system.ui.lib.text_measure import measure_text_cached +from openpilot.system.ui.lib.wrap_text import wrap_text +from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos +from openpilot.system.ui.widgets.scroller import Scroller +from openpilot.system.ui.widgets.slider import RedBigSlider, BigSlider +from openpilot.common.filter_simple import FirstOrderFilter +from openpilot.selfdrive.ui.mici.widgets.button import BigButton + +DEBUG = False + +PADDING = 20 + + +class BigDialogBase(NavWidget, abc.ABC): + def __init__(self, right_btn: str | None = None, right_btn_callback: Callable | None = None): + super().__init__() + self._ret = DialogResult.NO_ACTION + self.set_rect(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) + self.set_back_callback(lambda: setattr(self, '_ret', DialogResult.CANCEL)) + + self._right_btn = None + if right_btn: + def right_btn_callback_wrapper(): + gui_app.set_modal_overlay(None) + if right_btn_callback: + right_btn_callback() + + self._right_btn = SideButton(right_btn) + self._right_btn.set_click_callback(right_btn_callback_wrapper) + # move to right side + self._right_btn._rect.x = self._rect.x + self._rect.width - self._right_btn._rect.width + + def _render(self, _) -> DialogResult: + """ + Allows `gui_app.set_modal_overlay(BigDialog(...))`. + The overlay runner keeps calling until result != NO_ACTION. + """ + if self._right_btn: + self._right_btn.set_position(self._right_btn._rect.x, self._rect.y) + self._right_btn.render() + + return self._ret + + +class BigDialog(BigDialogBase): + def __init__(self, + title: str, + description: str, + right_btn: str | None = None, + right_btn_callback: Callable | None = None): + super().__init__(right_btn, right_btn_callback) + self._title = title + self._description = description + + def _render(self, _) -> DialogResult: + super()._render(_) + + # draw title + # TODO: we desperately need layouts + # TODO: coming up with these numbers manually is a pain and not scalable + # TODO: no clue what any of these numbers mean. VBox and HBox would remove all of this shite + max_width = self._rect.width - PADDING * 2 + if self._right_btn: + max_width -= self._right_btn._rect.width + + title_wrapped = '\n'.join(wrap_text(gui_app.font(FontWeight.BOLD), self._title, 50, int(max_width))) + title_size = measure_text_cached(gui_app.font(FontWeight.BOLD), title_wrapped, 50) + text_x_offset = 0 + title_rect = rl.Rectangle(int(self._rect.x + text_x_offset + PADDING), + int(self._rect.y + PADDING), + int(max_width), + int(title_size.y)) + gui_label(title_rect, title_wrapped, 50, font_weight=FontWeight.BOLD, + alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER) + + # draw description + desc_wrapped = '\n'.join(wrap_text(gui_app.font(FontWeight.MEDIUM), self._description, 30, int(max_width))) + desc_size = measure_text_cached(gui_app.font(FontWeight.MEDIUM), desc_wrapped, 30) + desc_rect = rl.Rectangle(int(self._rect.x + text_x_offset + PADDING), + int(self._rect.y + self._rect.height / 3), + int(max_width), + int(desc_size.y)) + # TODO: text align doesn't seem to work properly with newlines + gui_label(desc_rect, desc_wrapped, 30, font_weight=FontWeight.MEDIUM, + alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER) + + return self._ret + + +class BigConfirmationDialogV2(BigDialogBase): + def __init__(self, title: str, icon: str, red: bool = False, + exit_on_confirm: bool = True, + confirm_callback: Callable | None = None): + super().__init__() + self._confirm_callback = confirm_callback + self._exit_on_confirm = exit_on_confirm + + icon_txt = gui_app.texture(icon, 64, 53) + self._slider: BigSlider | RedBigSlider + if red: + self._slider = RedBigSlider(title, icon_txt, confirm_callback=self._on_confirm) + else: + self._slider = BigSlider(title, icon_txt, confirm_callback=self._on_confirm) + self._slider.set_enabled(lambda: not self._swiping_away) + + def _on_confirm(self): + if self._confirm_callback: + self._confirm_callback() + if self._exit_on_confirm: + self._ret = DialogResult.CONFIRM + + def _update_state(self): + super()._update_state() + if self._swiping_away and not self._slider.confirmed: + self._slider.reset() + + def _render(self, _) -> DialogResult: + self._slider.render(self._rect) + return self._ret + + +class BigInputDialog(BigDialogBase): + BACK_TOUCH_AREA_PERCENTAGE = 0.2 + BACKSPACE_RATE = 25 # hz + + def __init__(self, + hint: str, + default_text: str = "", + minimum_length: int = 1, + confirm_callback: Callable[[str], None] = None): + super().__init__(None, None) + self._hint_label = UnifiedLabel(hint, font_size=35, text_color=rl.Color(255, 255, 255, int(255 * 0.35)), + font_weight=FontWeight.MEDIUM) + self._keyboard = MiciKeyboard() + self._keyboard.set_text(default_text) + self._minimum_length = minimum_length + + self._backspace_held_time: float | None = None + + self._backspace_img = gui_app.texture("icons_mici/settings/keyboard/backspace.png", 44, 44) + self._backspace_img_alpha = FirstOrderFilter(0, 0.05, 1 / gui_app.target_fps) + + self._enter_img = gui_app.texture("icons_mici/settings/keyboard/confirm.png", 44, 44) + self._enter_img_alpha = FirstOrderFilter(0, 0.05, 1 / gui_app.target_fps) + + # rects for top buttons + self._top_left_button_rect = rl.Rectangle(0, 0, 0, 0) + self._top_right_button_rect = rl.Rectangle(0, 0, 0, 0) + + def confirm_callback_wrapper(): + self._ret = DialogResult.CONFIRM + if confirm_callback: + confirm_callback(self._keyboard.text()) + self._confirm_callback = confirm_callback_wrapper + + def _update_state(self): + super()._update_state() + + last_mouse_event = gui_app.last_mouse_event + if last_mouse_event.left_down and rl.check_collision_point_rec(last_mouse_event.pos, self._top_right_button_rect) and self._backspace_img_alpha.x > 1: + if self._backspace_held_time is None: + self._backspace_held_time = rl.get_time() + + if rl.get_time() - self._backspace_held_time > 0.5: + if gui_app.frame % round(gui_app.target_fps / self.BACKSPACE_RATE) == 0: + self._keyboard.backspace() + + else: + self._backspace_held_time = None + + def _render(self, _): + text_input_size = 35 + + # draw current text so far below everything. text floats left but always stays in view + text = self._keyboard.text() + candidate_char = self._keyboard.get_candidate_character() + text_size = measure_text_cached(gui_app.font(FontWeight.ROMAN), text + candidate_char or self._hint_label.text, text_input_size) + text_x = PADDING * 2 + self._enter_img.width + + # text needs to move left if we're at the end where right button is + text_rect = rl.Rectangle(text_x, + int(self._rect.y + PADDING), + # clip width to right button when in view + int(self._rect.width - text_x - PADDING * 2 - self._enter_img.width + 5), # TODO: why 5? + int(text_size.y)) + + # draw rounded background for text input + bg_block_margin = 5 + text_field_rect = rl.Rectangle(text_rect.x - bg_block_margin, text_rect.y - bg_block_margin, + text_rect.width + bg_block_margin * 2, text_input_size + bg_block_margin * 2) + + # draw text input + # push text left with a gradient on left side if too long + if text_size.x > text_rect.width: + text_x -= text_size.x - text_rect.width + + rl.begin_scissor_mode(int(text_rect.x), int(text_rect.y), int(text_rect.width), int(text_rect.height)) + rl.draw_text_ex(gui_app.font(FontWeight.ROMAN), text, rl.Vector2(text_x, text_rect.y), text_input_size, 0, rl.WHITE) + + # draw grayed out character user is hovering over + if candidate_char: + candidate_char_size = measure_text_cached(gui_app.font(FontWeight.ROMAN), candidate_char, text_input_size) + rl.draw_text_ex(gui_app.font(FontWeight.ROMAN), candidate_char, + rl.Vector2(min(text_x + text_size.x, text_rect.x + text_rect.width) - candidate_char_size.x, text_rect.y), + text_input_size, 0, rl.Color(255, 255, 255, 128)) + + rl.end_scissor_mode() + + # draw gradient on left side to indicate more text + if text_size.x > text_rect.width: + rl.draw_rectangle_gradient_h(int(text_rect.x), int(text_rect.y), 80, int(text_rect.height), + rl.BLACK, rl.BLANK) + + # draw cursor + if text: + blink_alpha = (math.sin(rl.get_time() * 6) + 1) / 2 + cursor_x = min(text_x + text_size.x + 3, text_rect.x + text_rect.width) + rl.draw_rectangle_rounded(rl.Rectangle(int(cursor_x), int(text_rect.y), 4, int(text_size.y)), + 1, 4, rl.Color(255, 255, 255, int(255 * blink_alpha))) + + # draw backspace icon with nice fade + self._backspace_img_alpha.update(255 * bool(text)) + if self._backspace_img_alpha.x > 1: + color = rl.Color(255, 255, 255, int(self._backspace_img_alpha.x)) + rl.draw_texture(self._backspace_img, int(self._rect.width - self._enter_img.width - 15), int(text_field_rect.y), color) + + if not text and self._hint_label.text and not candidate_char: + # draw description if no text entered yet and not drawing candidate char + self._hint_label.render(text_field_rect) + + # TODO: move to update state + # make rect take up entire area so it's easier to click + self._top_left_button_rect = rl.Rectangle(self._rect.x, self._rect.y, text_field_rect.x, self._rect.height - self._keyboard.get_keyboard_height()) + self._top_right_button_rect = rl.Rectangle(text_field_rect.x + text_field_rect.width, self._rect.y, + self._rect.width - (text_field_rect.x + text_field_rect.width), self._top_left_button_rect.height) + + self._enter_img_alpha.update(255 if (len(text) >= self._minimum_length) else 255 * 0.35) + if self._enter_img_alpha.x > 1: + color = rl.Color(255, 255, 255, int(self._enter_img_alpha.x)) + rl.draw_texture(self._enter_img, int(self._rect.x + 15), int(text_field_rect.y), color) + + # keyboard goes over everything + self._keyboard.render(self._rect) + + # draw debugging rect bounds + if DEBUG: + rl.draw_rectangle_lines_ex(text_field_rect, 1, rl.Color(100, 100, 100, 255)) + rl.draw_rectangle_lines_ex(text_rect, 1, rl.Color(0, 255, 0, 255)) + rl.draw_rectangle_lines_ex(self._top_right_button_rect, 1, rl.Color(0, 255, 0, 255)) + rl.draw_rectangle_lines_ex(self._top_left_button_rect, 1, rl.Color(0, 255, 0, 255)) + + return self._ret + + def _handle_mouse_press(self, mouse_pos: MousePos): + super()._handle_mouse_press(mouse_pos) + # TODO: need to track where press was so enter and back can activate on release rather than press + # or turn into icon widgets :eyes_open: + # handle backspace icon click + if rl.check_collision_point_rec(mouse_pos, self._top_right_button_rect) and self._backspace_img_alpha.x > 254: + self._keyboard.backspace() + elif rl.check_collision_point_rec(mouse_pos, self._top_left_button_rect) and self._enter_img_alpha.x > 254: + # handle enter icon click + self._confirm_callback() + + +class BigDialogOptionButton(Widget): + def __init__(self, option: str): + super().__init__() + self.option = option + self.set_rect(rl.Rectangle(0, 0, int(gui_app.width / 2 + 220), 64)) + + self._selected = False + + self._label = UnifiedLabel(option, font_size=70, text_color=rl.Color(255, 255, 255, int(255 * 0.58)), + font_weight=FontWeight.DISPLAY_REGULAR, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP) + + def set_selected(self, selected: bool): + self._selected = selected + + def _render(self, _): + if DEBUG: + rl.draw_rectangle_lines_ex(self._rect, 1, rl.Color(0, 255, 0, 255)) + + # FIXME: offset x by -45 because scroller centers horizontally + if self._selected: + self._label.set_font_size(74) + self._label.set_color(rl.Color(255, 255, 255, int(255 * 0.9))) + self._label.set_font_weight(FontWeight.DISPLAY) + else: + self._label.set_font_size(70) + self._label.set_color(rl.Color(255, 255, 255, int(255 * 0.58))) + self._label.set_font_weight(FontWeight.DISPLAY_REGULAR) + + self._label.render(self._rect) + + +class BigMultiOptionDialog(BigDialogBase): + BACK_TOUCH_AREA_PERCENTAGE = 0.1 + + def __init__(self, options: list[str], default: str | None, + right_btn: str | None = 'check', right_btn_callback: Callable[[], None] = None): + super().__init__(right_btn, right_btn_callback=right_btn_callback) + self._options = options + if default is not None: + assert default in options + + self._default_option: str = default or (options[0] if len(options) > 0 else "") + self._selected_option: str = self._default_option + self._last_selected_option: str = self._selected_option + + self._scroller = Scroller([], horizontal=False, pad_start=100, pad_end=100, spacing=0) + if self._right_btn is not None: + self._scroller.set_enabled(lambda: not cast(Widget, self._right_btn).is_pressed) + + for option in options: + self.add_button(BigDialogOptionButton(option)) + + def add_button(self, button: BigDialogOptionButton): + og_callback = button._click_callback + + def wrapped_callback(btn=button): + self._on_option_selected(btn.option) + if og_callback: + og_callback() + + button.set_click_callback(wrapped_callback) + self._scroller.add_widget(button) + + def show_event(self): + super().show_event() + self._scroller.show_event() + self._on_option_selected(self._default_option) + + def get_selected_option(self) -> str: + return self._selected_option + + def _on_option_selected(self, option: str): + y_pos = 0.0 + for btn in self._scroller._items: + if cast(BigDialogOptionButton, btn).option == option: + y_pos = btn.rect.y + + self._scroller.scroll_to(y_pos, smooth=True) + + def _selected_option_changed(self): + pass + + def _update_state(self): + super()._update_state() + + # get selection by whichever button is closest to center + center_y = self._rect.y + self._rect.height / 2 + closest_btn = (None, float('inf')) + for btn in self._scroller._items: + dist_y = abs((btn.rect.y + btn.rect.height / 2) - center_y) + if dist_y < closest_btn[1]: + closest_btn = (btn, dist_y) + + if closest_btn[0]: + for btn in self._scroller._items: + btn.set_selected(btn.option == closest_btn[0].option) + self._selected_option = closest_btn[0].option + + # Signal to subclasses if selection changed + if self._selected_option != self._last_selected_option: + self._selected_option_changed() + self._last_selected_option = self._selected_option + + def _render(self, _): + super()._render(_) + self._scroller.render(self._rect) + + return self._ret + + +class BigDialogButton(BigButton): + def __init__(self, text: str, value: str = "", icon: Union[str, rl.Texture] = "", description: str = ""): + super().__init__(text, value, icon) + self._description = description + + def _handle_mouse_release(self, mouse_pos: MousePos): + super()._handle_mouse_release(mouse_pos) + + dlg = BigDialog(self.text, self._description) + gui_app.set_modal_overlay(dlg) diff --git a/selfdrive/ui/mici/widgets/pairing_dialog.py b/selfdrive/ui/mici/widgets/pairing_dialog.py new file mode 100644 index 000000000..e064205d5 --- /dev/null +++ b/selfdrive/ui/mici/widgets/pairing_dialog.py @@ -0,0 +1,116 @@ +import pyray as rl +import qrcode +import numpy as np +import time + +from openpilot.common.api import Api +from openpilot.common.swaglog import cloudlog +from openpilot.common.params import Params +from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.system.ui.widgets import NavWidget +from openpilot.system.ui.lib.application import FontWeight, gui_app +from openpilot.system.ui.widgets.label import MiciLabel + + +class PairingDialog(NavWidget): + """Dialog for device pairing with QR code.""" + + QR_REFRESH_INTERVAL = 300 # 5 minutes in seconds + + def __init__(self): + super().__init__() + self.set_back_callback(lambda: gui_app.set_modal_overlay(None)) + self._params = Params() + self._qr_texture: rl.Texture | None = None + self._last_qr_generation = float("-inf") + + self._txt_pair = gui_app.texture("icons_mici/settings/device/pair.png", 84, 64) + self._pair_label = MiciLabel("pair with comma connect", 48, font_weight=FontWeight.BOLD, + color=rl.Color(255, 255, 255, int(255 * 0.9)), line_height=40, wrap_text=True) + + def _get_pairing_url(self) -> str: + try: + dongle_id = self._params.get("DongleId") or "" + token = Api(dongle_id).get_token({'pair': True}) + except Exception as e: + cloudlog.warning(f"Failed to get pairing token: {e}") + token = "" + return f"https://connect.comma.ai/?pair={token}" + + def _generate_qr_code(self) -> None: + try: + qr = qrcode.QRCode(version=1, error_correction=qrcode.constants.ERROR_CORRECT_L, box_size=10, border=0) + qr.add_data(self._get_pairing_url()) + qr.make(fit=True) + + pil_img = qr.make_image(fill_color="white", back_color="black").convert('RGBA') + img_array = np.array(pil_img, dtype=np.uint8) + + if self._qr_texture and self._qr_texture.id != 0: + rl.unload_texture(self._qr_texture) + + rl_image = rl.Image() + rl_image.data = rl.ffi.cast("void *", img_array.ctypes.data) + rl_image.width = pil_img.width + rl_image.height = pil_img.height + rl_image.mipmaps = 1 + rl_image.format = rl.PixelFormat.PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 + + self._qr_texture = rl.load_texture_from_image(rl_image) + except Exception as e: + cloudlog.warning(f"QR code generation failed: {e}") + self._qr_texture = None + + def _check_qr_refresh(self) -> None: + current_time = time.monotonic() + if current_time - self._last_qr_generation >= self.QR_REFRESH_INTERVAL: + self._generate_qr_code() + self._last_qr_generation = current_time + + def _update_state(self): + super()._update_state() + if ui_state.prime_state.is_paired(): + self._playing_dismiss_animation = True + + def _render(self, rect: rl.Rectangle) -> int: + self._check_qr_refresh() + + self._render_qr_code() + + label_x = self._rect.x + 8 + self._rect.height + 24 + self._pair_label.set_width(int(self._rect.width - label_x)) + self._pair_label.set_position(label_x, self._rect.y + 16) + self._pair_label.render() + + rl.draw_texture_ex(self._txt_pair, rl.Vector2(label_x, self._rect.y + self._rect.height - self._txt_pair.height - 16), + 0.0, 1.0, rl.Color(255, 255, 255, int(255 * 0.35))) + + return -1 + + def _render_qr_code(self) -> None: + if not self._qr_texture: + error_font = gui_app.font(FontWeight.BOLD) + rl.draw_text_ex( + error_font, "QR Code Error", rl.Vector2(self._rect.x + 20, self._rect.y + self._rect.height // 2 - 15), 30, 0.0, rl.RED + ) + return + + scale = self._rect.height / self._qr_texture.height + pos = rl.Vector2(self._rect.x + 8, self._rect.y) + rl.draw_texture_ex(self._qr_texture, pos, 0.0, scale, rl.WHITE) + + def __del__(self): + if self._qr_texture and self._qr_texture.id != 0: + rl.unload_texture(self._qr_texture) + + +if __name__ == "__main__": + gui_app.init_window("pairing device") + pairing = PairingDialog() + try: + for _ in gui_app.render(): + result = pairing.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) + if result != -1: + break + finally: + del pairing diff --git a/selfdrive/ui/mici/widgets/side_button.py b/selfdrive/ui/mici/widgets/side_button.py new file mode 100644 index 000000000..4803b6d20 --- /dev/null +++ b/selfdrive/ui/mici/widgets/side_button.py @@ -0,0 +1,31 @@ +import pyray as rl +from openpilot.system.ui.widgets import Widget +from openpilot.system.ui.lib.application import gui_app + +# --------------------------------------------------------------------------- +# Constants extracted from the original Qt style +# --------------------------------------------------------------------------- +# TODO: this should be corrected, but Scroller relies on this being incorrect :/ +WIDTH, HEIGHT = 112, 240 + + +class SideButton(Widget): + def __init__(self, btn_type: str): + super().__init__() + self.type = btn_type + self.set_rect(rl.Rectangle(0, 0, WIDTH, HEIGHT)) + + # load pre-rendered button images + if btn_type not in ("check", "back"): + btn_type = "back" + btn_img_path = f"icons_mici/buttons/button_side_{btn_type}.png" + btn_img_pressed_path = f"icons_mici/buttons/button_side_{btn_type}_pressed.png" + self._txt_btn, self._txt_btn_back = gui_app.texture(btn_img_path, 100, 224), gui_app.texture(btn_img_pressed_path, 100, 224) + + def _render(self, _) -> bool: + x = int(self._rect.x + 12) + y = int(self._rect.y + (self._rect.height - self._txt_btn.height) / 2) + rl.draw_texture(self._txt_btn if not self.is_pressed else self._txt_btn_back, + x, y, rl.WHITE) + + return False diff --git a/selfdrive/ui/soundd.py b/selfdrive/ui/soundd.py index 44116a232..d88410ada 100644 --- a/selfdrive/ui/soundd.py +++ b/selfdrive/ui/soundd.py @@ -12,6 +12,7 @@ from openpilot.common.utils import retry from openpilot.common.swaglog import cloudlog from openpilot.system import micd +from openpilot.system.hardware import HARDWARE SAMPLE_RATE = 48000 SAMPLE_BUFFER = 4096 # (approx 100ms) @@ -23,6 +24,10 @@ FILTER_DT = 1. / (micd.SAMPLE_RATE / micd.FFT_SAMPLES) AMBIENT_DB = 30 # DB where MIN_VOLUME is applied DB_SCALE = 30 # AMBIENT_DB + DB_SCALE is where MAX_VOLUME is applied +VOLUME_BASE = 20 +if HARDWARE.get_device_type() == "tizi": + VOLUME_BASE = 10 + AudibleAlert = car.CarControl.HUDControl.AudibleAlert @@ -39,6 +44,11 @@ sound_list: dict[int, tuple[str, int | None, float]] = { AudibleAlert.warningSoft: ("warning_soft.wav", None, MAX_VOLUME), AudibleAlert.warningImmediate: ("warning_immediate.wav", None, MAX_VOLUME), } +if HARDWARE.get_device_type() == "tizi": + sound_list.update({ + AudibleAlert.engage: ("engage_tizi.wav", 1, MAX_VOLUME), + AudibleAlert.disengage: ("disengage_tizi.wav", 1, MAX_VOLUME), + }) def check_selfdrive_timeout_alert(sm): ss_missing = time.monotonic() - sm.recv_time['selfdriveState'] @@ -122,7 +132,7 @@ class Soundd: def calculate_volume(self, weighted_db): volume = ((weighted_db - AMBIENT_DB) / DB_SCALE) * (MAX_VOLUME - MIN_VOLUME) + MIN_VOLUME - return math.pow(10, (np.clip(volume, MIN_VOLUME, MAX_VOLUME) - 1)) + return math.pow(VOLUME_BASE, (np.clip(volume, MIN_VOLUME, MAX_VOLUME) - 1)) @retry(attempts=10, delay=3) def get_stream(self, sd): diff --git a/selfdrive/ui/tests/profile_onroad.py b/selfdrive/ui/tests/profile_onroad.py index 0294125ce..b1fa4acc4 100755 --- a/selfdrive/ui/tests/profile_onroad.py +++ b/selfdrive/ui/tests/profile_onroad.py @@ -7,10 +7,9 @@ import numpy as np from msgq.visionipc import VisionIpcServer, VisionStreamType from openpilot.selfdrive.ui.ui_state import ui_state -from openpilot.selfdrive.ui.layouts.main import MainLayout +from openpilot.selfdrive.ui.mici.layouts.main import MiciMainLayout from openpilot.system.ui.lib.application import gui_app from openpilot.tools.lib.logreader import LogReader -from openpilot.tools.plotjuggler.juggle import DEMO_ROUTE FPS = 60 @@ -56,7 +55,7 @@ def patch_submaster(message_chunks): if __name__ == "__main__": import argparse parser = argparse.ArgumentParser(description='Profile openpilot UI rendering and state updates') - parser.add_argument('route', type=str, nargs='?', default=DEMO_ROUTE + "/1", + parser.add_argument('route', type=str, nargs='?', default="302bab07c1511180/00000006--0b9a7005f1/3", help='Route to use for profiling') parser.add_argument('--loop', type=int, default=1, help='Number of times to loop the log (default: 1)') @@ -82,8 +81,8 @@ if __name__ == "__main__": if args.headless: os.environ['SDL_VIDEODRIVER'] = 'dummy' - gui_app.init_window("UI Profiling") - main_layout = MainLayout() + gui_app.init_window("UI Profiling", fps=600) + main_layout = MiciMainLayout() main_layout.set_rect(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) print("Running...") diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py index f36ad1bad..3d476c431 100755 --- a/selfdrive/ui/tests/test_ui/raylib_screenshots.py +++ b/selfdrive/ui/tests/test_ui/raylib_screenshots.py @@ -246,6 +246,7 @@ CASES = { class TestUI: def __init__(self): os.environ["SCALE"] = os.getenv("SCALE", "1") + os.environ["BIG"] = "1" sys.modules["mouseinfo"] = False def setup(self): diff --git a/selfdrive/ui/ui.py b/selfdrive/ui/ui.py index a4b99825e..7fe0dfbbc 100755 --- a/selfdrive/ui/ui.py +++ b/selfdrive/ui/ui.py @@ -6,6 +6,7 @@ from openpilot.system.hardware import TICI from openpilot.common.realtime import config_realtime_process, set_core_affinity from openpilot.system.ui.lib.application import gui_app from openpilot.selfdrive.ui.layouts.main import MainLayout +from openpilot.selfdrive.ui.mici.layouts.main import MiciMainLayout from openpilot.selfdrive.ui.ui_state import ui_state @@ -14,7 +15,10 @@ def main(): config_realtime_process(0, 51) gui_app.init_window("UI") - main_layout = MainLayout() + if gui_app.big_ui(): + main_layout = MainLayout() + else: + main_layout = MiciMainLayout() main_layout.set_rect(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) for should_render in gui_app.render(): ui_state.update() diff --git a/selfdrive/ui/ui_state.py b/selfdrive/ui/ui_state.py index 8871e5de2..4a6ff9ebd 100644 --- a/selfdrive/ui/ui_state.py +++ b/selfdrive/ui/ui_state.py @@ -12,7 +12,7 @@ from openpilot.selfdrive.ui.lib.prime_state import PrimeState from openpilot.system.ui.lib.application import gui_app from openpilot.system.hardware import HARDWARE, PC -BACKLIGHT_OFFROAD = 50 +BACKLIGHT_OFFROAD = 65 if HARDWARE.get_device_type() == "mici" else 50 class UIStatus(Enum): @@ -36,6 +36,7 @@ class UIState: [ "modelV2", "controlsState", + "onroadEvents", "liveCalibration", "radarState", "deviceState", @@ -49,6 +50,10 @@ class UIState: "managerState", "selfdriveState", "longitudinalPlan", + "gpsLocationExternal", + "carOutput", + "carControl", + "liveParameters", "rawAudioData", ] ) @@ -64,6 +69,8 @@ class UIState: # Core state variables self.is_metric: bool = self.params.get_bool("IsMetric") + self.is_release = self.params.get_bool("IsReleaseBranch") + self.always_on_dm: bool = self.params.get_bool("AlwaysOnDM") self.started: bool = False self.ignition: bool = False self.recording_audio: bool = False @@ -133,6 +140,7 @@ class UIState: self.recording_audio = self.params.get_bool("RecordAudio") and self.started self.is_metric = self.params.get_bool("IsMetric") + self.always_on_dm = self.params.get_bool("AlwaysOnDM") def _update_status(self) -> None: if self.started and self.sm.updated["selfdriveState"]: @@ -181,16 +189,21 @@ class Device: self._interaction_time: float = -1 self._interactive_timeout_callbacks: list[Callable] = [] self._prev_timed_out = False - self._awake = False + self._awake: bool = True self._offroad_brightness: int = BACKLIGHT_OFFROAD self._last_brightness: int = 0 self._brightness_filter = FirstOrderFilter(BACKLIGHT_OFFROAD, 10.00, 1 / gui_app.target_fps) self._brightness_thread: threading.Thread | None = None + @property + def awake(self) -> bool: + return self._awake + def reset_interactive_timeout(self, timeout: int = -1) -> None: if timeout == -1: - timeout = 10 if ui_state.ignition else 30 + ignition_timeout = 10 if gui_app.big_ui() else 5 + timeout = ignition_timeout if ui_state.ignition else 30 self._interaction_time = time.monotonic() + timeout def add_interactive_timeout_callback(self, callback: Callable): @@ -220,7 +233,7 @@ class Device: else: clipped_brightness = ((clipped_brightness + 16.0) / 116.0) ** 3.0 - clipped_brightness = float(np.clip(100 * clipped_brightness, 10, 100)) + clipped_brightness = float(np.interp(clipped_brightness, [0, 1], [30, 100])) brightness = round(self._brightness_filter.update(clipped_brightness)) if not self._awake: diff --git a/system/ui/README.md b/system/ui/README.md index 6e43c20d1..21c8ab974 100644 --- a/system/ui/README.md +++ b/system/ui/README.md @@ -3,6 +3,7 @@ The user interfaces here are built with [raylib](https://www.raylib.com/). Quick start: +* set `BIG=1` to run the comma 3X UI (comma four UI runs by default) * set `SHOW_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 diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 16b924e44..e3370a5f7 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -27,6 +27,7 @@ MOUSE_THREAD_RATE = 140 # touch controller runs at 140Hz MAX_TOUCH_SLOTS = 2 TOUCH_HISTORY_TIMEOUT = 3.0 # Seconds before touch points fade out +BIG_UI = os.getenv("BIG", "0") == "1" ENABLE_VSYNC = os.getenv("ENABLE_VSYNC", "0") == "1" SHOW_FPS = os.getenv("SHOW_FPS") == "1" SHOW_TOUCHES = os.getenv("SHOW_TOUCHES") == "1" @@ -78,7 +79,7 @@ DEFAULT_TEXT_COLOR = rl.WHITE # Qt draws fonts accounting for ascent/descent differently, so compensate to match old styles # The real scales for the fonts below range from 1.212 to 1.266 -FONT_SCALE = 1.242 +FONT_SCALE = 1.242 if BIG_UI else 1.16 ASSETS_DIR = files("openpilot.selfdrive").joinpath("assets") FONT_DIR = ASSETS_DIR.joinpath("fonts") @@ -86,12 +87,17 @@ FONT_DIR = ASSETS_DIR.joinpath("fonts") class FontWeight(StrEnum): LIGHT = "Inter-Light.fnt" - NORMAL = "Inter-Regular.fnt" + NORMAL = "Inter-Regular.fnt" if BIG_UI else "Inter-Medium.fnt" MEDIUM = "Inter-Medium.fnt" - SEMI_BOLD = "Inter-SemiBold.fnt" BOLD = "Inter-Bold.fnt" + SEMI_BOLD = "Inter-SemiBold.fnt" UNIFONT = "unifont.fnt" + # Small UI fonts + DISPLAY_REGULAR = "Inter-Regular.fnt" + ROMAN = "Inter-Regular.fnt" + DISPLAY = "Inter-Bold.fnt" + def font_fallback(font: rl.Font) -> rl.Font: """Fall back to unifont for languages that require it.""" @@ -181,10 +187,10 @@ class MouseState: class GuiApplication: - def __init__(self, width: int, height: int): + def __init__(self, width: int | None = None, height: int | None = None): self._fonts: dict[FontWeight, rl.Font] = {} - self._width = width - self._height = height + self._width = width if width is not None else GuiApplication._default_width() + self._height = height if height is not None else GuiApplication._default_height() if PC and os.getenv("SCALE") is None: self._scale = self._calculate_auto_scale() @@ -654,5 +660,17 @@ class GuiApplication: # Apply 0.95 factor for window decorations/taskbar margin return max(0.3, min(w / self._width, h / self._height) * 0.95) + @staticmethod + def _default_width() -> int: + return 2160 if GuiApplication.big_ui() else 536 -gui_app = GuiApplication(2160, 1080) + @staticmethod + def _default_height() -> int: + return 1080 if GuiApplication.big_ui() else 240 + + @staticmethod + def big_ui() -> bool: + return HARDWARE.get_device_type() in ('tici', 'tizi') or BIG_UI + + +gui_app = GuiApplication() diff --git a/system/ui/lib/scroll_panel2.py b/system/ui/lib/scroll_panel2.py new file mode 100644 index 000000000..8d9caadfd --- /dev/null +++ b/system/ui/lib/scroll_panel2.py @@ -0,0 +1,219 @@ +import os +import math +import pyray as rl +from collections.abc import Callable +from enum import Enum +from typing import cast +from openpilot.system.ui.lib.application import gui_app, MouseEvent +from openpilot.system.hardware import TICI +from collections import deque + +MIN_VELOCITY = 2 # px/s, changes from auto scroll to steady state +MIN_VELOCITY_FOR_CLICKING = 2 * 60 # px/s, accepts clicks while auto scrolling below this velocity +MIN_DRAG_PIXELS = 12 +AUTO_SCROLL_TC_SNAP = 0.025 +AUTO_SCROLL_TC = 0.18 +BOUNCE_RETURN_RATE = 10.0 +REJECT_DECELERATION_FACTOR = 3 +MAX_SPEED = 10000.0 # px/s + +DEBUG = os.getenv("DEBUG_SCROLL", "0") == "1" + + +# from https://ariya.io/2011/10/flick-list-with-its-momentum-scrolling-and-deceleration +class ScrollState(Enum): + STEADY = 0 + PRESSED = 1 + MANUAL_SCROLL = 2 + AUTO_SCROLL = 3 + + +class GuiScrollPanel2: + def __init__(self, horizontal: bool = True, handle_out_of_bounds: bool = True) -> None: + self._horizontal = horizontal + self._handle_out_of_bounds = handle_out_of_bounds + self._AUTO_SCROLL_TC = AUTO_SCROLL_TC_SNAP if not self._handle_out_of_bounds else AUTO_SCROLL_TC + self._state = ScrollState.STEADY + self._offset: rl.Vector2 = rl.Vector2(0, 0) + self._initial_click_event: MouseEvent | None = None + self._previous_mouse_event: MouseEvent | None = None + self._velocity = 0.0 # pixels per second + self._velocity_buffer: deque[float] = deque(maxlen=12 if TICI else 6) + self._enabled: bool | Callable[[], bool] = True + + def set_enabled(self, enabled: bool | Callable[[], bool]) -> None: + self._enabled = enabled + + @property + def enabled(self) -> bool: + return self._enabled() if callable(self._enabled) else self._enabled + + def update(self, bounds: rl.Rectangle, content_size: float) -> float: + if DEBUG: + print('Old state:', self._state) + + bounds_size = bounds.width if self._horizontal else bounds.height + + for mouse_event in gui_app.mouse_events: + self._handle_mouse_event(mouse_event, bounds, bounds_size, content_size) + self._previous_mouse_event = mouse_event + + self._update_state(bounds_size, content_size) + + if DEBUG: + print('Velocity:', self._velocity) + print('Offset X:', self._offset.x, 'Y:', self._offset.y) + print('New state:', self._state) + print() + return self.get_offset() + + def _update_state(self, bounds_size: float, content_size: float) -> None: + """Runs per render frame, independent of mouse events. Updates auto-scrolling state and velocity.""" + if self._state == ScrollState.AUTO_SCROLL: + # simple exponential return if out of bounds + out_of_bounds = self.get_offset() > 0 or self.get_offset() < (bounds_size - content_size) + if out_of_bounds and self._handle_out_of_bounds: + if self.get_offset() < (bounds_size - content_size): # too far right + target = bounds_size - content_size + else: # too far left + target = 0.0 + + dt = rl.get_frame_time() or 1e-6 + factor = 1.0 - math.exp(-BOUNCE_RETURN_RATE * dt) + + dist = target - self.get_offset() + self.set_offset(self.get_offset() + dist * factor) # ease toward the edge + self._velocity *= (1.0 - factor) # damp any leftover fling + + # Steady once we are close enough to the target + if abs(dist) < 1 and abs(self._velocity) < MIN_VELOCITY: + self.set_offset(target) + self._state = ScrollState.STEADY + + elif abs(self._velocity) < MIN_VELOCITY: + self._velocity = 0.0 + self._state = ScrollState.STEADY + + # Update the offset based on the current velocity + dt = rl.get_frame_time() + self.set_offset(self.get_offset() + self._velocity * dt) # Adjust the offset based on velocity + alpha = 1 - (dt / (self._AUTO_SCROLL_TC + dt)) + self._velocity *= alpha + + def _handle_mouse_event(self, mouse_event: MouseEvent, bounds: rl.Rectangle, bounds_size: float, + content_size: float) -> None: + out_of_bounds = self.get_offset() > 0 or self.get_offset() < (bounds_size - content_size) + if DEBUG: + print('Mouse event:', mouse_event) + + mouse_pos = self._get_mouse_pos(mouse_event) + + if not self.enabled: + # Reset state if not enabled + self._state = ScrollState.STEADY + self._velocity = 0.0 + self._velocity_buffer.clear() + + elif self._state == ScrollState.STEADY: + if rl.check_collision_point_rec(mouse_event.pos, bounds): + if mouse_event.left_pressed: + self._state = ScrollState.PRESSED + self._initial_click_event = mouse_event + + elif self._state == ScrollState.PRESSED: + initial_click_pos = self._get_mouse_pos(cast(MouseEvent, self._initial_click_event)) + diff = abs(mouse_pos - initial_click_pos) + if mouse_event.left_released: + # Special handling for down and up clicks across two frames + # TODO: not sure what that means or if it's accurate anymore + if out_of_bounds: + self._state = ScrollState.AUTO_SCROLL + elif diff <= MIN_DRAG_PIXELS: + self._state = ScrollState.STEADY + else: + self._state = ScrollState.MANUAL_SCROLL + elif diff > MIN_DRAG_PIXELS: + self._state = ScrollState.MANUAL_SCROLL + + elif self._state == ScrollState.MANUAL_SCROLL: + if mouse_event.left_released: + # Touch rejection: when releasing finger after swiping and stopping, panel + # reports a few erroneous touch events with high velocity, try to ignore. + + # If velocity decelerates very quickly, assume user doesn't intend to auto scroll + high_decel = False + if len(self._velocity_buffer) > 2: + # We limit max to first half since final few velocities can surpass first few + abs_velocity_buffer = [(abs(v), i) for i, v in enumerate(self._velocity_buffer)] + max_idx = max(abs_velocity_buffer[:len(abs_velocity_buffer) // 2])[1] + min_idx = min(abs_velocity_buffer)[1] + if DEBUG: + print('min_idx:', min_idx, 'max_idx:', max_idx, 'velocity buffer:', self._velocity_buffer) + if (abs(self._velocity_buffer[min_idx]) * REJECT_DECELERATION_FACTOR < abs(self._velocity_buffer[max_idx]) and + max_idx < min_idx): + if DEBUG: + print('deceleration too high, going to STEADY') + high_decel = True + + # If final velocity is below some threshold, switch to steady state too + low_speed = abs(self._velocity) <= MIN_VELOCITY_FOR_CLICKING * 1.5 # plus some margin + + if out_of_bounds or not (high_decel or low_speed): + self._state = ScrollState.AUTO_SCROLL + else: + # TODO: we should just set velocity and let autoscroll go back to steady. delays one frame but who cares + self._velocity = 0.0 + self._state = ScrollState.STEADY + self._velocity_buffer.clear() + else: + # Update velocity for when we release the mouse button. + # Do not update velocity on the same frame the mouse was released + previous_mouse_pos = self._get_mouse_pos(cast(MouseEvent, self._previous_mouse_event)) + delta_x = mouse_pos - previous_mouse_pos + self._velocity = delta_x / (mouse_event.t - cast(MouseEvent, self._previous_mouse_event).t) + self._velocity = max(-MAX_SPEED, min(MAX_SPEED, self._velocity)) + self._velocity_buffer.append(self._velocity) + + # rubber-banding: reduce dragging when out of bounds + # TODO: this drifts when dragging quickly + if out_of_bounds: + delta_x *= 0.25 + + # Update the offset based on the mouse movement + # Use internal _offset directly to preserve precision (don't round via get_offset()) + # TODO: make get_offset return float + current_offset = self._offset.x if self._horizontal else self._offset.y + self.set_offset(current_offset + delta_x) + + elif self._state == ScrollState.AUTO_SCROLL: + if mouse_event.left_pressed: + # Decide whether to click or scroll (block click if moving too fast) + if abs(self._velocity) <= MIN_VELOCITY_FOR_CLICKING: + # Traveling slow enough, click + self._state = ScrollState.PRESSED + self._initial_click_event = mouse_event + else: + # Go straight into manual scrolling to block erroneous input + self._state = ScrollState.MANUAL_SCROLL + # Reset velocity for touch down and up events that happen in back-to-back frames + self._velocity = 0.0 + + def _get_mouse_pos(self, mouse_event: MouseEvent) -> float: + return mouse_event.pos.x if self._horizontal else mouse_event.pos.y + + def get_offset(self) -> int: + return round(self._offset.x if self._horizontal else self._offset.y) + + def set_offset(self, value: float) -> None: + if self._horizontal: + self._offset.x = value + else: + self._offset.y = value + + @property + def state(self) -> ScrollState: + return self._state + + def is_touch_valid(self) -> bool: + # MIN_VELOCITY_FOR_CLICKING is checked in auto-scroll state + return bool(self._state != ScrollState.MANUAL_SCROLL) diff --git a/system/ui/lib/shader_polygon.py b/system/ui/lib/shader_polygon.py index 7be6638af..94af35e15 100644 --- a/system/ui/lib/shader_polygon.py +++ b/system/ui/lib/shader_polygon.py @@ -191,7 +191,9 @@ def triangulate(pts: np.ndarray) -> list[tuple[float, float]]: # TODO: consider deduping close screenspace points # interleave points to produce a triangle strip - assert len(pts) % 2 == 0, "Interleaving expects even number of points" + # assert len(pts) % 2 == 0, "Interleaving expects even number of points" + if len(pts) % 2 != 0: + pts = pts[:-1] tri_strip = [] for i in range(len(pts) // 2): diff --git a/system/ui/lib/text_measure.py b/system/ui/lib/text_measure.py index 544ba5b87..dee4b419f 100644 --- a/system/ui/lib/text_measure.py +++ b/system/ui/lib/text_measure.py @@ -5,9 +5,10 @@ from openpilot.system.ui.lib.emoji import find_emoji _cache: dict[int, rl.Vector2] = {} -def measure_text_cached(font: rl.Font, text: str, font_size: int, spacing: int = 0) -> rl.Vector2: +def measure_text_cached(font: rl.Font, text: str, font_size: int, spacing: float = 0) -> rl.Vector2: """Caches text measurements to avoid redundant calculations.""" font = font_fallback(font) + spacing = round(spacing, 4) key = hash((font.texture.id, text, font_size, spacing)) if key in _cache: return _cache[key] diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index 4cf2ccebc..217ac5e89 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -35,7 +35,7 @@ except Exception: TETHERING_IP_ADDRESS = "192.168.43.1" DEFAULT_TETHERING_PASSWORD = "swagswagcomma" SIGNAL_QUEUE_SIZE = 10 -SCAN_PERIOD_SECONDS = 10 +SCAN_PERIOD_SECONDS = 5 class SecurityType(IntEnum): @@ -75,6 +75,7 @@ class Network: is_connected: bool security_type: SecurityType is_saved: bool + ip_address: str = "" # TODO: implement @classmethod def from_dbus(cls, ssid: str, aps: list["AccessPoint"], is_saved: bool) -> "Network": @@ -627,7 +628,7 @@ class WifiManager: known_connections = self._get_connections() networks = [Network.from_dbus(ssid, ap_list, ssid in known_connections) for ssid, ap_list in aps.items()] - networks.sort(key=lambda n: (-n.is_connected, -n.strength, n.ssid.lower())) + networks.sort(key=lambda n: (-n.is_connected, n.ssid.lower())) self._networks = networks self._update_ipv4_address() diff --git a/system/ui/lib/wrap_text.py b/system/ui/lib/wrap_text.py index 745d37b46..3fabfbb66 100644 --- a/system/ui/lib/wrap_text.py +++ b/system/ui/lib/wrap_text.py @@ -3,7 +3,7 @@ from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.lib.application import font_fallback -def _break_long_word(font: rl.Font, word: str, font_size: int, max_width: int) -> list[str]: +def _break_long_word(font: rl.Font, word: str, font_size: int, max_width: int, spacing: float = 0) -> list[str]: if not word: return [] @@ -11,7 +11,7 @@ def _break_long_word(font: rl.Font, word: str, font_size: int, max_width: int) - remaining = word while remaining: - if measure_text_cached(font, remaining, font_size).x <= max_width: + if measure_text_cached(font, remaining, font_size, spacing).x <= max_width: parts.append(remaining) break @@ -22,7 +22,7 @@ def _break_long_word(font: rl.Font, word: str, font_size: int, max_width: int) - while left <= right: mid = (left + right) // 2 substring = remaining[:mid] - width = measure_text_cached(font, substring, font_size).x + width = measure_text_cached(font, substring, font_size, spacing).x if width <= max_width: best_fit = mid @@ -40,9 +40,10 @@ def _break_long_word(font: rl.Font, word: str, font_size: int, max_width: int) - _cache: dict[int, list[str]] = {} -def wrap_text(font: rl.Font, text: str, font_size: int, max_width: int) -> list[str]: +def wrap_text(font: rl.Font, text: str, font_size: int, max_width: int, spacing: float = 0) -> list[str]: font = font_fallback(font) - key = hash((font.texture.id, text, font_size, max_width)) + spacing = round(spacing, 4) + key = hash((font.texture.id, text, font_size, max_width, spacing)) if key in _cache: return _cache[key] @@ -69,7 +70,7 @@ def wrap_text(font: rl.Font, text: str, font_size: int, max_width: int) -> list[ current_line: list[str] = [] for word in words: - word_width = int(measure_text_cached(font, word, font_size).x) + word_width = measure_text_cached(font, word, font_size, spacing).x # Check if word alone exceeds max width (need to break the word) if word_width > max_width: @@ -79,12 +80,12 @@ def wrap_text(font: rl.Font, text: str, font_size: int, max_width: int) -> list[ current_line = [] # Break the long word into parts - lines.extend(_break_long_word(font, word, font_size, max_width)) + lines.extend(_break_long_word(font, word, font_size, max_width, spacing)) continue # Measure the actual joined string to get accurate width (accounts for kerning, etc.) test_line = " ".join(current_line + [word]) if current_line else word - test_width = int(measure_text_cached(font, test_line, font_size).x) + test_width = measure_text_cached(font, test_line, font_size, spacing).x # Check if word fits on current line if test_width <= max_width: diff --git a/system/ui/mici_reset.py b/system/ui/mici_reset.py new file mode 100755 index 000000000..d9bb45d99 --- /dev/null +++ b/system/ui/mici_reset.py @@ -0,0 +1,160 @@ +#!/usr/bin/env python3 +import os +import sys +import threading +import time +from enum import IntEnum + +import pyray as rl + +from openpilot.system.hardware import PC +from openpilot.system.ui.lib.application import gui_app, FontWeight +from openpilot.system.ui.widgets import Widget +from openpilot.system.ui.widgets.slider import SmallSlider +from openpilot.system.ui.widgets.button import SmallButton, FullRoundedButton +from openpilot.system.ui.widgets.label import gui_label, gui_text_box + +USERDATA = "/dev/disk/by-partlabel/userdata" +TIMEOUT = 3*60 + + +class ResetMode(IntEnum): + USER_RESET = 0 # user initiated a factory reset from openpilot + RECOVER = 1 # userdata is corrupt for some reason, give a chance to recover + FORMAT = 2 # finish up a factory reset from a tool that doesn't flash an empty partition to userdata + + +class ResetState(IntEnum): + NONE = 0 + RESETTING = 1 + FAILED = 2 + + +class Reset(Widget): + def __init__(self, mode): + super().__init__() + self._mode = mode + self._previous_reset_state = None + self._reset_state = ResetState.NONE + + self._cancel_button = SmallButton("cancel") + self._cancel_button.set_click_callback(self._cancel_callback) + + self._reboot_button = FullRoundedButton("reboot") + self._reboot_button.set_click_callback(self._do_reboot) + + self._confirm_slider = SmallSlider("reset", self._confirm) + + self._render_status = True + + def _cancel_callback(self): + self._render_status = False + + def _do_reboot(self): + if PC: + return + + os.system("sudo reboot") + + def _do_erase(self): + if PC: + return + + # Removing data and formatting + rm = os.system("sudo rm -rf /data/*") + os.system(f"sudo umount {USERDATA}") + fmt = os.system(f"yes | sudo mkfs.ext4 {USERDATA}") + + if rm == 0 or fmt == 0: + os.system("sudo reboot") + else: + self._reset_state = ResetState.FAILED + + def start_reset(self): + self._reset_state = ResetState.RESETTING + threading.Timer(0.1, self._do_erase).start() + + def _update_state(self): + if self._reset_state != self._previous_reset_state: + self._previous_reset_state = self._reset_state + self._timeout_st = time.monotonic() + elif self._reset_state != ResetState.RESETTING and (time.monotonic() - self._timeout_st) > TIMEOUT: + exit(0) + + def _render(self, rect: rl.Rectangle): + label_rect = rl.Rectangle(rect.x + 8, rect.y + 8, rect.width, 50) + gui_label(label_rect, "factory reset", 48, font_weight=FontWeight.BOLD, + color=rl.Color(255, 255, 255, int(255 * 0.9))) + + text_rect = rl.Rectangle(rect.x + 8, rect.y + 56, rect.width - 8 * 2, rect.height - 80) + gui_text_box(text_rect, self._get_body_text(), 36, font_weight=FontWeight.ROMAN, line_scale=0.9) + + if self._reset_state != ResetState.RESETTING: + # fade out cancel button as slider is moved, set visible to prevent pressing invisible cancel + self._cancel_button.set_opacity(1.0 - self._confirm_slider.slider_percentage) + self._cancel_button.set_visible(self._confirm_slider.slider_percentage < 0.8) + + if self._mode == ResetMode.RECOVER: + self._cancel_button.set_text("reboot") + self._cancel_button.render(rl.Rectangle( + rect.x + 8, + rect.y + rect.height - self._cancel_button.rect.height, + self._cancel_button.rect.width, + self._cancel_button.rect.height)) + elif self._mode == ResetMode.USER_RESET and self._reset_state != ResetState.FAILED: + self._cancel_button.render(rl.Rectangle( + rect.x + 8, + rect.y + rect.height - self._cancel_button.rect.height, + self._cancel_button.rect.width, + self._cancel_button.rect.height)) + + if self._reset_state != ResetState.FAILED: + self._confirm_slider.render(rl.Rectangle( + rect.x + rect.width - self._confirm_slider.rect.width, + rect.y + rect.height - self._confirm_slider.rect.height, + self._confirm_slider.rect.width, + self._confirm_slider.rect.height)) + else: + self._reboot_button.render(rl.Rectangle( + rect.x + 8, + rect.y + rect.height - self._reboot_button.rect.height, + self._reboot_button.rect.width, + self._reboot_button.rect.height)) + + return self._render_status + + def _confirm(self): + self.start_reset() + + def _get_body_text(self): + if self._reset_state == ResetState.RESETTING: + return "Resetting device... This may take up to a minute." + if self._reset_state == ResetState.FAILED: + return "Reset failed. Reboot to try again." + if self._mode == ResetMode.RECOVER: + return "Unable to mount data partition. Partition may be corrupted." + return "All content and settings will be erased." + + +def main(): + mode = ResetMode.USER_RESET + if len(sys.argv) > 1: + if sys.argv[1] == '--recover': + mode = ResetMode.RECOVER + elif sys.argv[1] == "--format": + mode = ResetMode.FORMAT + + gui_app.init_window("System Reset") + reset = Reset(mode) + + if mode == ResetMode.FORMAT: + reset.start_reset() + + for should_render in gui_app.render(): + if should_render: + if not reset.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height)): + break + + +if __name__ == "__main__": + main() diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py new file mode 100755 index 000000000..4afd69bfa --- /dev/null +++ b/system/ui/mici_setup.py @@ -0,0 +1,727 @@ +#!/usr/bin/env python3 +from abc import abstractmethod +import os +import re +import threading +import time +import urllib.request +import urllib.error +from urllib.parse import urlparse +from enum import IntEnum +import shutil +from collections.abc import Callable + +import pyray as rl + +from cereal import log +from openpilot.common.utils import run_cmd +from openpilot.system.hardware import HARDWARE +from openpilot.system.ui.lib.application import gui_app, FontWeight +from openpilot.system.ui.lib.wifi_manager import WifiManager +from openpilot.system.ui.lib.scroll_panel2 import GuiScrollPanel2 +from openpilot.system.ui.widgets import Widget, DialogResult +from openpilot.system.ui.widgets.button import (IconButton, SmallButton, WideRoundedButton, SmallerRoundedButton, + SmallCircleIconButton, WidishRoundedButton, SmallRedPillButton, + FullRoundedButton) +from openpilot.system.ui.widgets.label import UnifiedLabel +from openpilot.system.ui.widgets.slider import LargerSlider +from openpilot.selfdrive.ui.mici.layouts.settings.network import WifiUIMici +from openpilot.selfdrive.ui.mici.widgets.dialog import BigInputDialog + +NetworkType = log.DeviceState.NetworkType + +OPENPILOT_URL = "https://openpilot.comma.ai" +USER_AGENT = f"AGNOSSetup-{HARDWARE.get_os_version()}" + +CONTINUE_PATH = "/data/continue.sh" +TMP_CONTINUE_PATH = "/data/continue.sh.new" +INSTALL_PATH = "/data/openpilot" +VALID_CACHE_PATH = "/data/.openpilot_cache" +INSTALLER_SOURCE_PATH = "/usr/comma/installer" +INSTALLER_DESTINATION_PATH = "/tmp/installer" +INSTALLER_URL_PATH = "/tmp/installer_url" + +CONTINUE = """#!/usr/bin/env bash + +cd /data/openpilot +exec ./launch_openpilot.sh +""" + + +class NetworkConnectivityMonitor: + def __init__(self, should_check: Callable[[], bool] | None = None, check_interval: float = 0.5): + self.network_connected = threading.Event() + self.wifi_connected = threading.Event() + self._should_check = should_check or (lambda: True) + self._check_interval = check_interval + self._stop_event = threading.Event() + self._thread: threading.Thread | None = None + + def start(self): + self._stop_event.clear() + if self._thread is None or not self._thread.is_alive(): + self._thread = threading.Thread(target=self._run, daemon=True) + self._thread.start() + + def stop(self): + if self._thread is not None: + self._stop_event.set() + self._thread.join() + self._thread = None + + def reset(self): + self.network_connected.clear() + self.wifi_connected.clear() + + def _run(self): + while not self._stop_event.is_set(): + if self._should_check(): + try: + request = urllib.request.Request(OPENPILOT_URL, method="HEAD") + urllib.request.urlopen(request, timeout=0.5) + self.network_connected.set() + if HARDWARE.get_network_type() == NetworkType.wifi: + self.wifi_connected.set() + except Exception: + self.reset() + else: + self.reset() + + if self._stop_event.wait(timeout=self._check_interval): + break + + +class SetupState(IntEnum): + GETTING_STARTED = 0 + NETWORK_SETUP = 1 + NETWORK_SETUP_CUSTOM_SOFTWARE = 8 + SOFTWARE_SELECTION = 2 + CUSTOM_SOFTWARE = 3 + DOWNLOADING = 4 + DOWNLOAD_FAILED = 5 + CUSTOM_SOFTWARE_WARNING = 6 + + +class StartPage(Widget): + def __init__(self): + super().__init__() + + self._title = UnifiedLabel("start", 64, text_color=rl.Color(255, 255, 255, int(255 * 0.9)), + font_weight=FontWeight.DISPLAY, alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER, + alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE) + + self._start_bg_txt = gui_app.texture("icons_mici/setup/green_button.png", 520, 224) + self._start_bg_pressed_txt = gui_app.texture("icons_mici/setup/green_button_pressed.png", 520, 224) + + def _render(self, rect: rl.Rectangle): + draw_x = rect.x + (rect.width - self._start_bg_txt.width) / 2 + draw_y = rect.y + (rect.height - self._start_bg_txt.height) / 2 + texture = self._start_bg_pressed_txt if self.is_pressed else self._start_bg_txt + rl.draw_texture(texture, int(draw_x), int(draw_y), rl.WHITE) + + self._title.render(rect) + + +class SoftwareSelectionPage(Widget): + def __init__(self, use_openpilot_callback: Callable, + use_custom_software_callback: Callable): + super().__init__() + + self._openpilot_slider = LargerSlider("slide to use\nopenpilot", use_openpilot_callback) + self._custom_software_slider = LargerSlider("slide to use\ncustom software", use_custom_software_callback, green=False) + + def reset(self): + self._openpilot_slider.reset() + self._custom_software_slider.reset() + + def _render(self, rect: rl.Rectangle): + self._openpilot_slider.set_opacity(1.0 - self._custom_software_slider.slider_percentage) + self._custom_software_slider.set_opacity(1.0 - self._openpilot_slider.slider_percentage) + + openpilot_rect = rl.Rectangle( + rect.x + (rect.width - self._openpilot_slider.rect.width) / 2, + rect.y, + self._openpilot_slider.rect.width, + rect.height / 2, + ) + self._openpilot_slider.render(openpilot_rect) + + custom_software_rect = rl.Rectangle( + rect.x + (rect.width - self._custom_software_slider.rect.width) / 2, + rect.y + rect.height / 2, + self._custom_software_slider.rect.width, + rect.height / 2, + ) + self._custom_software_slider.render(custom_software_rect) + + +class TermsHeader(Widget): + def __init__(self, text: str, icon_texture: rl.Texture): + super().__init__() + + self._title = UnifiedLabel(text, 36, text_color=rl.Color(255, 255, 255, int(255 * 0.9)), + font_weight=FontWeight.BOLD, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE, + line_height=0.8) + self._icon_texture = icon_texture + + self.set_rect(rl.Rectangle(0, 0, gui_app.width - 16 * 2, self._icon_texture.height)) + + def set_title(self, text: str): + self._title.set_text(text) + + def set_icon(self, icon_texture: rl.Texture): + self._icon_texture = icon_texture + + def _render(self, _): + rl.draw_texture_ex(self._icon_texture, rl.Vector2(self._rect.x, self._rect.y), + 0.0, 1.0, rl.WHITE) + + # May expand outside parent rect + title_content_height = self._title.get_content_height(int(self._rect.width - self._icon_texture.width - 16)) + title_rect = rl.Rectangle( + self._rect.x + self._icon_texture.width + 16, + self._rect.y + (self._rect.height - title_content_height) / 2, + self._rect.width - self._icon_texture.width - 16, + title_content_height, + ) + self._title.render(title_rect) + + +class TermsPage(Widget): + ITEM_SPACING = 20 + + def __init__(self, continue_callback: Callable, back_callback: Callable | None = None, + back_text: str = "back", continue_text: str = "accept"): + super().__init__() + + # TODO: use Scroller + self._scroll_panel = GuiScrollPanel2(horizontal=False) + + self._continue_text = continue_text + self._continue_button: WideRoundedButton | FullRoundedButton + if back_callback is not None: + self._continue_button = WideRoundedButton(continue_text) + else: + self._continue_button = FullRoundedButton(continue_text) + self._continue_button.set_enabled(False) + self._continue_button.set_opacity(0.0) + self._continue_button.set_touch_valid_callback(self._scroll_panel.is_touch_valid) + self._continue_button.set_click_callback(continue_callback) + + self._enable_back = back_callback is not None + self._back_button = SmallButton(back_text) + self._back_button.set_opacity(0.0) + self._back_button.set_touch_valid_callback(self._scroll_panel.is_touch_valid) + self._back_button.set_click_callback(back_callback) + + self._scroll_down_indicator = IconButton(gui_app.texture("icons_mici/setup/scroll_down_indicator.png", 64, 78)) + self._scroll_down_indicator.set_enabled(False) + + def reset(self): + self._scroll_panel.set_offset(0) + self._continue_button.set_enabled(False) + self._continue_button.set_opacity(0.0) + self._back_button.set_enabled(False) + self._back_button.set_opacity(0.0) + self._scroll_down_indicator.set_opacity(1.0) + + @property + @abstractmethod + def _content_height(self): + pass + + @property + def _scrolled_down_offset(self): + return -self._content_height + (self._continue_button.rect.height + 16 + 30) + + @abstractmethod + def _render_content(self, scroll_offset): + pass + + def _render(self, _): + scroll_offset = self._scroll_panel.update(self._rect, self._content_height + self._continue_button.rect.height + 16) + + if scroll_offset <= self._scrolled_down_offset: + # don't show back if not enabled + if self._enable_back: + self._back_button.set_enabled(True) + self._back_button.set_opacity(1.0, smooth=True) + self._continue_button.set_enabled(True) + self._continue_button.set_opacity(1.0, smooth=True) + self._scroll_down_indicator.set_opacity(0.0, smooth=True) + else: + self._back_button.set_enabled(False) + self._back_button.set_opacity(0.0, smooth=True) + self._continue_button.set_enabled(False) + self._continue_button.set_opacity(0.0, smooth=True) + self._scroll_down_indicator.set_opacity(1.0, smooth=True) + + # Render content + self._render_content(scroll_offset) + + # black gradient at top and bottom for scrolling content + rl.draw_rectangle_gradient_v(int(self._rect.x), int(self._rect.y), + int(self._rect.width), 20, rl.BLACK, rl.BLANK) + rl.draw_rectangle_gradient_v(int(self._rect.x), int(self._rect.y + self._rect.height - 20), + int(self._rect.width), 20, rl.BLANK, rl.BLACK) + + self._back_button.render(rl.Rectangle( + self._rect.x + 8, + self._rect.y + self._rect.height - self._back_button.rect.height, + self._back_button.rect.width, + self._back_button.rect.height, + )) + + continue_x = self._rect.x + 8 + if self._enable_back: + continue_x = self._rect.x + self._rect.width - self._continue_button.rect.width - 8 + self._continue_button.render(rl.Rectangle( + continue_x, + self._rect.y + self._rect.height - self._continue_button.rect.height, + self._continue_button.rect.width, + self._continue_button.rect.height, + )) + + self._scroll_down_indicator.render(rl.Rectangle( + self._rect.x + self._rect.width - self._scroll_down_indicator.rect.width - 8, + self._rect.y + self._rect.height - self._scroll_down_indicator.rect.height - 8, + self._scroll_down_indicator.rect.width, + self._scroll_down_indicator.rect.height, + )) + + +class CustomSoftwareWarningPage(TermsPage): + def __init__(self, continue_callback: Callable, back_callback: Callable): + super().__init__(continue_callback, back_callback) + + self._title_header = TermsHeader("use caution installing\n3rd party software", + gui_app.texture("icons_mici/setup/warning.png", 66, 60)) + self._body = UnifiedLabel("• It has not been tested by comma.\n" + + "• It may not comply with relevant safety standards.\n" + + "• It may cause damage to your device and/or vehicle.\n", 36, text_color=rl.Color(255, 255, 255, int(255 * 0.9)), + font_weight=FontWeight.ROMAN) + + self._restore_header = TermsHeader("how to backup &\nrestore", gui_app.texture("icons_mici/setup/restore.png", 60, 60)) + self._restore_body = UnifiedLabel("To restore your device to a factory state later, use https://flash.comma.ai", + 36, text_color=rl.Color(255, 255, 255, int(255 * 0.9)), + font_weight=FontWeight.ROMAN) + + @property + def _content_height(self): + return self._restore_body.rect.y + self._restore_body.rect.height - self._scroll_panel.get_offset() + + def _render_content(self, scroll_offset): + self._title_header.set_position(self._rect.x + 16, self._rect.y + 8 + scroll_offset) + self._title_header.render() + + body_rect = rl.Rectangle( + self._rect.x + 8, + self._title_header.rect.y + self._title_header.rect.height + self.ITEM_SPACING, + self._rect.width - 50, + self._body.get_content_height(int(self._rect.width - 50)), + ) + self._body.render(body_rect) + + self._restore_header.set_position(self._rect.x + 16, self._body.rect.y + self._body.rect.height + self.ITEM_SPACING) + self._restore_header.render() + + self._restore_body.render(rl.Rectangle( + self._rect.x + 8, + self._restore_header.rect.y + self._restore_header.rect.height + self.ITEM_SPACING, + self._rect.width - 50, + self._restore_body.get_content_height(int(self._rect.width - 50)), + )) + + +class DownloadingPage(Widget): + def __init__(self): + super().__init__() + + self._title_label = UnifiedLabel("downloading", 64, text_color=rl.Color(255, 255, 255, int(255 * 0.9)), + font_weight=FontWeight.DISPLAY) + self._progress_label = UnifiedLabel("", 128, text_color=rl.Color(255, 255, 255, int(255 * 0.9 * 0.35)), + font_weight=FontWeight.ROMAN, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM) + self._progress = 0 + + def set_progress(self, progress: int): + self._progress = progress + self._progress_label.set_text(f"{progress}%") + + def _render(self, rect: rl.Rectangle): + self._title_label.render(rl.Rectangle( + rect.x + 20, + rect.y + 10, + rect.width, + 64, + )) + + self._progress_label.render(rl.Rectangle( + rect.x + 20, + rect.y + 20, + rect.width, + rect.height, + )) + + +class FailedPage(Widget): + def __init__(self, reboot_callback: Callable, retry_callback: Callable, title: str = "download failed"): + super().__init__() + + self._title_label = UnifiedLabel(title, 64, text_color=rl.Color(255, 255, 255, int(255 * 0.9)), + font_weight=FontWeight.DISPLAY) + self._reason_label = UnifiedLabel("", 36, text_color=rl.Color(255, 255, 255, int(255 * 0.9 * 0.65)), + font_weight=FontWeight.ROMAN) + + self._reboot_button = SmallRedPillButton("reboot") + self._reboot_button.set_click_callback(reboot_callback) + + self._retry_button = WideRoundedButton("retry") + self._retry_button.set_click_callback(retry_callback) + + def set_reason(self, reason: str): + self._reason_label.set_text(reason) + + def _render(self, rect: rl.Rectangle): + self._title_label.render(rl.Rectangle( + rect.x + 8, + rect.y + 10, + rect.width, + 64, + )) + + self._reason_label.render(rl.Rectangle( + rect.x + 8, + rect.y + 10 + 64, + rect.width, + 36, + )) + + self._reboot_button.render(rl.Rectangle( + rect.x + 8, + rect.y + rect.height - self._reboot_button.rect.height, + self._reboot_button.rect.width, + self._reboot_button.rect.height, + )) + + self._retry_button.render(rl.Rectangle( + rect.x + 8 + self._reboot_button.rect.width + 8, + rect.y + rect.height - self._retry_button.rect.height, + self._retry_button.rect.width, + self._retry_button.rect.height, + )) + + +class NetworkSetupState(IntEnum): + MAIN = 0 + WIFI_PANEL = 1 + + +class NetworkSetupPage(Widget): + def __init__(self, wifi_manager, continue_callback: Callable, back_callback: Callable): + super().__init__() + self._wifi_ui = WifiUIMici(wifi_manager, back_callback=lambda: self.set_state(NetworkSetupState.MAIN)) + + self._no_wifi_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_slash.png", 58, 50) + self._wifi_full_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_full.png", 58, 50) + self._waiting_text = "waiting for internet..." + self._network_header = TermsHeader(self._waiting_text, self._no_wifi_txt) + + back_txt = gui_app.texture("icons_mici/setup/back_new.png", 37, 32) + self._back_button = SmallCircleIconButton(back_txt) + self._back_button.set_click_callback(back_callback) + + self._wifi_button = SmallerRoundedButton("wifi") + self._wifi_button.set_click_callback(lambda: self.set_state(NetworkSetupState.WIFI_PANEL)) + + self._continue_button = WidishRoundedButton("continue") + self._continue_button.set_enabled(False) + self._continue_button.set_click_callback(continue_callback) + + self._state = NetworkSetupState.MAIN + + def set_state(self, state: NetworkSetupState): + self._state = state + + def set_has_internet(self, has_internet: bool): + if has_internet: + self._network_header.set_title("connected to internet") + self._network_header.set_icon(self._wifi_full_txt) + self._continue_button.set_enabled(True) + else: + self._network_header.set_title(self._waiting_text) + self._network_header.set_icon(self._no_wifi_txt) + self._continue_button.set_enabled(False) + + def show_event(self): + super().show_event() + self._state = NetworkSetupState.MAIN + self._wifi_ui.show_event() + + def hide_event(self): + super().hide_event() + self._wifi_ui.hide_event() + + def _render(self, _): + if self._state == NetworkSetupState.MAIN: + self._network_header.render(rl.Rectangle( + self._rect.x + 16, + self._rect.y + 16, + self._rect.width - 32, + self._network_header.rect.height, + )) + + self._back_button.render(rl.Rectangle( + self._rect.x + 8, + self._rect.y + self._rect.height - self._back_button.rect.height, + self._back_button.rect.width, + self._back_button.rect.height, + )) + + self._wifi_button.render(rl.Rectangle( + self._rect.x + 8 + self._back_button.rect.width + 10, + self._rect.y + self._rect.height - self._wifi_button.rect.height, + self._wifi_button.rect.width, + self._wifi_button.rect.height, + )) + + self._continue_button.render(rl.Rectangle( + self._rect.x + self._rect.width - self._continue_button.rect.width - 8, + self._rect.y + self._rect.height - self._continue_button.rect.height, + self._continue_button.rect.width, + self._continue_button.rect.height, + )) + else: + self._wifi_ui.render(self._rect) + + +class Setup(Widget): + def __init__(self): + super().__init__() + self.state = SetupState.GETTING_STARTED + self.failed_url = "" + self.failed_reason = "" + self.download_url = "" + self.download_progress = 0 + self.download_thread = None + self._wifi_manager = WifiManager() + self._wifi_manager.set_active(True) + self._network_monitor = NetworkConnectivityMonitor( + lambda: self.state in (SetupState.NETWORK_SETUP, SetupState.NETWORK_SETUP_CUSTOM_SOFTWARE) + ) + + self._start_page = StartPage() + self._start_page.set_click_callback(self._getting_started_button_callback) + + self._network_setup_page = NetworkSetupPage(self._wifi_manager, self._network_setup_continue_button_callback, + self._network_setup_back_button_callback) + + self._software_selection_page = SoftwareSelectionPage(self._software_selection_continue_button_callback, + self._software_selection_custom_software_button_callback) + + self._download_failed_page = FailedPage(HARDWARE.reboot, self._download_failed_startover_button_callback) + + self._custom_software_warning_page = CustomSoftwareWarningPage(self._software_selection_custom_software_continue, + self._custom_software_warning_back_button_callback) + + self._downloading_page = DownloadingPage() + + def _update_state(self): + self._wifi_manager.process_callbacks() + + def _set_state(self, state: SetupState): + self.state = state + if self.state == SetupState.SOFTWARE_SELECTION: + self._software_selection_page.reset() + elif self.state == SetupState.CUSTOM_SOFTWARE_WARNING: + self._custom_software_warning_page.reset() + + if self.state in (SetupState.NETWORK_SETUP, SetupState.NETWORK_SETUP_CUSTOM_SOFTWARE): + self._network_setup_page.show_event() + self._network_monitor.reset() + self._network_monitor.start() + else: + self._network_setup_page.hide_event() + self._network_monitor.stop() + + def _render(self, rect: rl.Rectangle): + if self.state == SetupState.GETTING_STARTED: + self._start_page.render(rect) + elif self.state in (SetupState.NETWORK_SETUP, SetupState.NETWORK_SETUP_CUSTOM_SOFTWARE): + self.render_network_setup(rect) + elif self.state == SetupState.SOFTWARE_SELECTION: + self._software_selection_page.render(rect) + elif self.state == SetupState.CUSTOM_SOFTWARE_WARNING: + self._custom_software_warning_page.render(rect) + elif self.state == SetupState.CUSTOM_SOFTWARE: + self.render_custom_software() + elif self.state == SetupState.DOWNLOADING: + self.render_downloading(rect) + elif self.state == SetupState.DOWNLOAD_FAILED: + self._download_failed_page.render(rect) + + def _custom_software_warning_back_button_callback(self): + self._set_state(SetupState.SOFTWARE_SELECTION) + + def _custom_software_warning_continue_button_callback(self): + self._set_state(SetupState.CUSTOM_SOFTWARE) + + def _getting_started_button_callback(self): + self._set_state(SetupState.SOFTWARE_SELECTION) + + def _software_selection_back_button_callback(self): + self._set_state(SetupState.GETTING_STARTED) + + def _software_selection_continue_button_callback(self): + self.use_openpilot() + + def _software_selection_custom_software_button_callback(self): + self._set_state(SetupState.CUSTOM_SOFTWARE_WARNING) + + def _software_selection_custom_software_continue(self): + self._set_state(SetupState.NETWORK_SETUP_CUSTOM_SOFTWARE) + + def _download_failed_startover_button_callback(self): + self._set_state(SetupState.GETTING_STARTED) + + def _network_setup_back_button_callback(self): + self._set_state(SetupState.SOFTWARE_SELECTION) + + def _network_setup_continue_button_callback(self): + self._network_monitor.stop() + if self.state == SetupState.NETWORK_SETUP: + self.download(OPENPILOT_URL) + elif self.state == SetupState.NETWORK_SETUP_CUSTOM_SOFTWARE: + self._set_state(SetupState.CUSTOM_SOFTWARE) + + def close(self): + self._network_monitor.stop() + + def render_network_setup(self, rect: rl.Rectangle): + self._network_setup_page.render(rect) + self._network_setup_page.set_has_internet(self._network_monitor.network_connected.is_set()) + + def render_downloading(self, rect: rl.Rectangle): + self._downloading_page.set_progress(self.download_progress) + self._downloading_page.render(rect) + + def render_custom_software(self): + def handle_keyboard_result(text): + url = text.strip() + if url: + self.download(url) + + def handle_keyboard_exit(result): + if result == DialogResult.CANCEL: + self._set_state(SetupState.SOFTWARE_SELECTION) + + keyboard = BigInputDialog("custom software URL", confirm_callback=handle_keyboard_result) + gui_app.set_modal_overlay(keyboard, callback=handle_keyboard_exit) + + def use_openpilot(self): + if os.path.isdir(INSTALL_PATH) and os.path.isfile(VALID_CACHE_PATH): + os.remove(VALID_CACHE_PATH) + with open(TMP_CONTINUE_PATH, "w") as f: + f.write(CONTINUE) + run_cmd(["chmod", "+x", TMP_CONTINUE_PATH]) + shutil.move(TMP_CONTINUE_PATH, CONTINUE_PATH) + shutil.copyfile(INSTALLER_SOURCE_PATH, INSTALLER_DESTINATION_PATH) + + # give time for installer UI to take over + time.sleep(0.1) + gui_app.request_close() + else: + self._set_state(SetupState.NETWORK_SETUP) + + def download(self, url: str): + # autocomplete incomplete URLs + if re.match("^([^/.]+)/([^/]+)$", url): + url = f"https://installer.comma.ai/{url}" + + parsed = urlparse(url, scheme='https') + self.download_url = (urlparse(f"https://{url}") if not parsed.netloc else parsed).geturl() + + self._set_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 + + fd, tmpfile = tempfile.mkstemp(prefix="installer_") + + headers = {"User-Agent": USER_AGENT, + "X-openpilot-serial": HARDWARE.get_serial(), + "X-openpilot-device-type": HARDWARE.get_device_type()} + 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) + self._downloading_page.set_progress(self.download_progress) + + 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 + + # AGNOS might try to execute the installer before this process exits. + # Therefore, important to close the fd before renaming the installer. + os.close(fd) + os.rename(tmpfile, INSTALLER_DESTINATION_PATH) + + with open(INSTALLER_URL_PATH, "w") as f: + f.write(self.download_url) + + # give time for installer UI to take over + time.sleep(0.1) + gui_app.request_close() + + except urllib.error.HTTPError as e: + if e.code == 409: + error_msg = "Incompatible openpilot version" + self.download_failed(self.download_url, error_msg) + except Exception: + error_msg = "Invalid URL" + 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._download_failed_page.set_reason(reason) + self._set_state(SetupState.DOWNLOAD_FAILED) + + +def main(): + try: + gui_app.init_window("Setup") + setup = Setup() + for should_render in gui_app.render(): + if should_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/mici_updater.py b/system/ui/mici_updater.py new file mode 100755 index 000000000..2ae2f7cc1 --- /dev/null +++ b/system/ui/mici_updater.py @@ -0,0 +1,200 @@ +#!/usr/bin/env python3 +import sys +import subprocess +import threading +import pyray as rl +from enum import IntEnum + +from openpilot.system.hardware import HARDWARE +from openpilot.system.ui.lib.application import gui_app, FontWeight +from openpilot.system.ui.lib.text_measure import measure_text_cached +from openpilot.system.ui.lib.wifi_manager import WifiManager, Network +from openpilot.system.ui.widgets import Widget +from openpilot.system.ui.widgets.label import gui_text_box, gui_label, UnifiedLabel +from openpilot.system.ui.widgets.button import FullRoundedButton +from openpilot.system.ui.mici_setup import NetworkSetupPage, FailedPage, NetworkConnectivityMonitor + + +class Screen(IntEnum): + PROMPT = 0 + WIFI = 1 + PROGRESS = 2 + FAILED = 3 + + +class Updater(Widget): + def __init__(self, updater_path, manifest_path): + super().__init__() + self.updater = updater_path + self.manifest = manifest_path + self.current_screen = Screen.PROMPT + self._current_network_strength = -1 + + self.progress_value = 0 + self.progress_text = "loading" + self.process = None + self.update_thread = None + self._wifi_manager = WifiManager() + self._wifi_manager.set_active(True) + + self._network_setup_page = NetworkSetupPage(self._wifi_manager, self._network_setup_continue_callback, + self._network_setup_back_callback) + + self._wifi_manager.add_callbacks(networks_updated=self._on_network_updated) + self._network_monitor = NetworkConnectivityMonitor() + self._network_monitor.start() + + # Buttons + self._continue_button = FullRoundedButton("continue") + self._continue_button.set_click_callback(lambda: self.set_current_screen(Screen.WIFI)) + + self._title_label = UnifiedLabel("update required", 48, text_color=rl.Color(255, 115, 0, 255), + font_weight=FontWeight.DISPLAY) + self._subtitle_label = UnifiedLabel("The download size is approximately 1GB.", 36, + text_color=rl.Color(255, 255, 255, int(255 * 0.9)), + font_weight=FontWeight.ROMAN) + + self._update_failed_page = FailedPage(HARDWARE.reboot, self._update_failed_retry_callback, + title="update failed") + + def _network_setup_back_callback(self): + self.set_current_screen(Screen.PROMPT) + + def _network_setup_continue_callback(self): + self.install_update() + + def _update_failed_retry_callback(self): + self.set_current_screen(Screen.PROMPT) + + def _on_network_updated(self, networks: list[Network]): + self._current_network_strength = next((net.strength for net in networks if net.is_connected), -1) + + def set_current_screen(self, screen: Screen): + if self.current_screen != screen: + if screen == Screen.PROGRESS: + if self._network_setup_page: + self._network_setup_page.hide_event() + elif screen == Screen.WIFI: + if self._network_setup_page: + self._network_setup_page.show_event() + elif screen == Screen.PROMPT: + if self._network_setup_page: + self._network_setup_page.hide_event() + elif screen == Screen.FAILED: + if self._network_setup_page: + self._network_setup_page.hide_event() + + self.current_screen = screen + + def install_update(self): + self.set_current_screen(Screen.PROGRESS) + self.progress_value = 0 + self.progress_text = "downloading" + + # Start the update process in a separate thread + self.update_thread = threading.Thread(target=self._run_update_process) + self.update_thread.daemon = True + self.update_thread.start() + + def _run_update_process(self): + # TODO: just import it and run in a thread without a subprocess + cmd = [self.updater, "--swap", self.manifest] + self.process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, + text=True, bufsize=1, universal_newlines=True) + + for line in self.process.stdout: + parts = line.strip().split(":") + if len(parts) == 2: + self.progress_text = parts[0].lower() + try: + self.progress_value = int(float(parts[1])) + except ValueError: + pass + + exit_code = self.process.wait() + if exit_code == 0: + HARDWARE.reboot() + else: + self.set_current_screen(Screen.FAILED) + + def render_prompt_screen(self, rect: rl.Rectangle): + self._title_label.render(rl.Rectangle( + rect.x + 8, + rect.y - 5, + rect.width, + 48, + )) + + subtitle_width = rect.width - 16 + subtitle_height = self._subtitle_label.get_content_height(int(subtitle_width)) + self._subtitle_label.render(rl.Rectangle( + rect.x + 8, + rect.y + 48, + subtitle_width, + subtitle_height, + )) + + self._continue_button.render(rl.Rectangle( + rect.x + 8, + rect.y + rect.height - self._continue_button.rect.height, + self._continue_button.rect.width, + self._continue_button.rect.height, + )) + + def render_progress_screen(self, rect: rl.Rectangle): + title_rect = rl.Rectangle(self._rect.x + 6, self._rect.y - 5, self._rect.width - 12, self._rect.height - 8) + if ' ' in self.progress_text: + font_size = 62 + else: + font_size = 82 + gui_text_box(title_rect, self.progress_text, font_size, font_weight=FontWeight.DISPLAY, + color=rl.Color(255, 255, 255, int(255 * 0.9))) + + progress_value = f"{self.progress_value}%" + text_height = measure_text_cached(gui_app.font(FontWeight.ROMAN), progress_value, 128).y + progress_rect = rl.Rectangle(self._rect.x + 6, self._rect.y + self._rect.height - text_height + 18, + self._rect.width - 12, text_height) + gui_label(progress_rect, progress_value, 128, font_weight=FontWeight.ROMAN, + color=rl.Color(255, 255, 255, int(255 * 0.9 * 0.35))) + + def _update_state(self): + self._wifi_manager.process_callbacks() + + def _render(self, rect: rl.Rectangle): + if self.current_screen == Screen.PROMPT: + self.render_prompt_screen(rect) + elif self.current_screen == Screen.WIFI: + self._network_setup_page.set_has_internet(self._network_monitor.network_connected.is_set()) + self._network_setup_page.render(rect) + elif self.current_screen == Screen.PROGRESS: + self.render_progress_screen(rect) + elif self.current_screen == Screen.FAILED: + self._update_failed_page.render(rect) + + def close(self): + self._network_monitor.stop() + + +def main(): + if len(sys.argv) < 3: + print("Usage: updater.py ") + sys.exit(1) + + updater_path = sys.argv[1] + manifest_path = sys.argv[2] + + try: + gui_app.init_window("System Update") + updater = Updater(updater_path, manifest_path) + for should_render in gui_app.render(): + if should_render: + updater.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) + updater.close() + except Exception as e: + print(f"Updater error: {e}") + finally: + gui_app.close() + + +if __name__ == "__main__": + main() diff --git a/system/ui/reset.py b/system/ui/reset.py index 3922c27aa..c32504a5b 100755 --- a/system/ui/reset.py +++ b/system/ui/reset.py @@ -1,135 +1,14 @@ #!/usr/bin/env python3 -import os -import sys -import threading -import time -from enum import IntEnum - -import pyray as rl - -from openpilot.system.hardware import PC -from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE -from openpilot.system.ui.widgets import Widget -from openpilot.system.ui.widgets.button import Button, ButtonStyle -from openpilot.system.ui.widgets.label import gui_label, gui_text_box - -USERDATA = "/dev/disk/by-partlabel/userdata" -TIMEOUT = 3*60 - - -class ResetMode(IntEnum): - USER_RESET = 0 # user initiated a factory reset from openpilot - RECOVER = 1 # userdata is corrupt for some reason, give a chance to recover - FORMAT = 2 # finish up a factory reset from a tool that doesn't flash an empty partition to userdata - - -class ResetState(IntEnum): - NONE = 0 - CONFIRM = 1 - RESETTING = 2 - FAILED = 3 - - -class Reset(Widget): - def __init__(self, mode): - super().__init__() - self._mode = mode - self._previous_reset_state = None - self._reset_state = ResetState.NONE - self._cancel_button = Button("Cancel", self._cancel_callback) - self._confirm_button = Button("Confirm", self._confirm, button_style=ButtonStyle.PRIMARY) - self._reboot_button = Button("Reboot", lambda: os.system("sudo reboot")) - self._render_status = True - - def _cancel_callback(self): - self._render_status = False - - def _do_erase(self): - if PC: - return - - # Removing data and formatting - rm = os.system("sudo rm -rf /data/*") - os.system(f"sudo umount {USERDATA}") - fmt = os.system(f"yes | sudo mkfs.ext4 {USERDATA}") - - if rm == 0 or fmt == 0: - os.system("sudo reboot") - else: - self._reset_state = ResetState.FAILED - - def start_reset(self): - self._reset_state = ResetState.RESETTING - threading.Timer(0.1, self._do_erase).start() - - def _update_state(self): - if self._reset_state != self._previous_reset_state: - self._previous_reset_state = self._reset_state - self._timeout_st = time.monotonic() - elif self._reset_state != ResetState.RESETTING and (time.monotonic() - self._timeout_st) > TIMEOUT: - exit(0) - - def _render(self, rect: rl.Rectangle): - label_rect = rl.Rectangle(rect.x + 140, rect.y, rect.width - 280, 100 * FONT_SCALE) - gui_label(label_rect, "System Reset", 100, font_weight=FontWeight.BOLD) - - text_rect = rl.Rectangle(rect.x + 140, rect.y + 140, rect.width - 280, rect.height - 90 - 100 * FONT_SCALE) - gui_text_box(text_rect, self._get_body_text(), 90) - - button_height = 160 - button_spacing = 50 - button_top = rect.y + rect.height - button_height - button_width = (rect.width - button_spacing) / 2.0 - - if self._reset_state != ResetState.RESETTING: - if self._mode == ResetMode.RECOVER: - self._reboot_button.render(rl.Rectangle(rect.x, button_top, button_width, button_height)) - elif self._mode == ResetMode.USER_RESET: - self._cancel_button.render(rl.Rectangle(rect.x, button_top, button_width, button_height)) - - if self._reset_state != ResetState.FAILED: - self._confirm_button.render(rl.Rectangle(rect.x + button_width + 50, button_top, button_width, button_height)) - else: - self._reboot_button.render(rl.Rectangle(rect.x, button_top, rect.width, button_height)) - - return self._render_status - - def _confirm(self): - if self._reset_state == ResetState.CONFIRM: - self.start_reset() - else: - self._reset_state = ResetState.CONFIRM - - def _get_body_text(self): - if self._reset_state == ResetState.CONFIRM: - return "Are you sure you want to reset your device?" - if self._reset_state == ResetState.RESETTING: - return "Resetting device...\nThis may take up to a minute." - if self._reset_state == ResetState.FAILED: - return "Reset failed. Reboot to try again." - if self._mode == ResetMode.RECOVER: - return "Unable to mount data partition. Partition may be corrupted. Press confirm to erase and reset your device." - return "System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot." +from openpilot.system.ui.lib.application import gui_app +import openpilot.system.ui.tici_reset as tici_reset +import openpilot.system.ui.mici_reset as mici_reset def main(): - mode = ResetMode.USER_RESET - if len(sys.argv) > 1: - if sys.argv[1] == '--recover': - mode = ResetMode.RECOVER - elif sys.argv[1] == "--format": - mode = ResetMode.FORMAT - - gui_app.init_window("System Reset", 20) - reset = Reset(mode) - - if mode == ResetMode.FORMAT: - reset.start_reset() - - for should_render in gui_app.render(): - if should_render: - if not reset.render(rl.Rectangle(45, 200, gui_app.width - 90, gui_app.height - 245)): - break + if gui_app.big_ui(): + tici_reset.main() + else: + mici_reset.main() if __name__ == "__main__": diff --git a/system/ui/setup.py b/system/ui/setup.py index 0045b4541..23ffc26aa 100755 --- a/system/ui/setup.py +++ b/system/ui/setup.py @@ -1,450 +1,14 @@ #!/usr/bin/env python3 -import os -import re -import threading -import time -import urllib.request -import urllib.error -from urllib.parse import urlparse -from enum import IntEnum -import shutil - -import pyray as rl - -from cereal import log -from openpilot.common.utils import run_cmd -from openpilot.system.hardware import HARDWARE -from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel -from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE -from openpilot.system.ui.widgets import Widget -from openpilot.system.ui.widgets.button import Button, ButtonStyle, ButtonRadio -from openpilot.system.ui.widgets.keyboard import Keyboard -from openpilot.system.ui.widgets.label import Label -from openpilot.system.ui.widgets.network import WifiManagerUI, WifiManager - -NetworkType = log.DeviceState.NetworkType - -MARGIN = 50 -TITLE_FONT_SIZE = 90 -TITLE_FONT_WEIGHT = FontWeight.MEDIUM -NEXT_BUTTON_WIDTH = 310 -BODY_FONT_SIZE = 80 -BUTTON_HEIGHT = 160 -BUTTON_SPACING = 50 - -OPENPILOT_URL = "https://openpilot.comma.ai" -USER_AGENT = f"AGNOSSetup-{HARDWARE.get_os_version()}" - -CONTINUE_PATH = "/data/continue.sh" -TMP_CONTINUE_PATH = "/data/continue.sh.new" -INSTALL_PATH = "/data/openpilot" -VALID_CACHE_PATH = "/data/.openpilot_cache" -INSTALLER_SOURCE_PATH = "/usr/comma/installer" -INSTALLER_DESTINATION_PATH = "/tmp/installer" -INSTALLER_URL_PATH = "/tmp/installer_url" - -CONTINUE = """#!/usr/bin/env bash - -cd /data/openpilot -exec ./launch_openpilot.sh -""" - - -class SetupState(IntEnum): - LOW_VOLTAGE = 0 - GETTING_STARTED = 1 - NETWORK_SETUP = 2 - SOFTWARE_SELECTION = 3 - CUSTOM_SOFTWARE = 4 - DOWNLOADING = 5 - DOWNLOAD_FAILED = 6 - CUSTOM_SOFTWARE_WARNING = 7 - - -class Setup(Widget): - def __init__(self): - super().__init__() - 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_ui = WifiManagerUI(WifiManager()) - 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) - - self._low_voltage_title_label = Label("WARNING: Low Voltage", TITLE_FONT_SIZE, FontWeight.MEDIUM, rl.GuiTextAlignment.TEXT_ALIGN_LEFT, - text_color=rl.Color(255, 89, 79, 255), text_padding=20) - self._low_voltage_body_label = Label("Power your device in a car with a harness or proceed at your own risk.", BODY_FONT_SIZE, - text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT, text_padding=20) - self._low_voltage_continue_button = Button("Continue", self._low_voltage_continue_button_callback) - self._low_voltage_poweroff_button = Button("Power Off", HARDWARE.shutdown) - - self._getting_started_button = Button("", self._getting_started_button_callback, button_style=ButtonStyle.PRIMARY, border_radius=0) - self._getting_started_title_label = Label("Getting Started", TITLE_FONT_SIZE, FontWeight.BOLD, rl.GuiTextAlignment.TEXT_ALIGN_LEFT, text_padding=20) - self._getting_started_body_label = Label("Before we get on the road, let's finish installation and cover some details.", - BODY_FONT_SIZE, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT, text_padding=20) - - self._software_selection_openpilot_button = ButtonRadio("openpilot", self.checkmark, font_size=BODY_FONT_SIZE, text_padding=80) - self._software_selection_custom_software_button = ButtonRadio("Custom Software", self.checkmark, font_size=BODY_FONT_SIZE, text_padding=80) - self._software_selection_continue_button = Button("Continue", self._software_selection_continue_button_callback, - button_style=ButtonStyle.PRIMARY) - self._software_selection_continue_button.set_enabled(False) - self._software_selection_back_button = Button("Back", self._software_selection_back_button_callback) - self._software_selection_title_label = Label("Choose Software to Use", TITLE_FONT_SIZE, FontWeight.BOLD, rl.GuiTextAlignment.TEXT_ALIGN_LEFT, - text_padding=20) - - self._download_failed_reboot_button = Button("Reboot device", HARDWARE.reboot) - self._download_failed_startover_button = Button("Start over", self._download_failed_startover_button_callback, button_style=ButtonStyle.PRIMARY) - self._download_failed_title_label = Label("Download Failed", TITLE_FONT_SIZE, FontWeight.BOLD, rl.GuiTextAlignment.TEXT_ALIGN_LEFT, text_padding=20) - self._download_failed_url_label = Label("", 52, FontWeight.NORMAL, rl.GuiTextAlignment.TEXT_ALIGN_LEFT, text_padding=20) - self._download_failed_body_label = Label("", BODY_FONT_SIZE, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT, text_padding=20) - - self._network_setup_back_button = Button("Back", self._network_setup_back_button_callback) - self._network_setup_continue_button = Button("Waiting for internet", self._network_setup_continue_button_callback, - button_style=ButtonStyle.PRIMARY) - self._network_setup_continue_button.set_enabled(False) - self._network_setup_title_label = Label("Connect to Wi-Fi", TITLE_FONT_SIZE, FontWeight.BOLD, rl.GuiTextAlignment.TEXT_ALIGN_LEFT, text_padding=20) - - self._custom_software_warning_continue_button = Button("Scroll to continue", self._custom_software_warning_continue_button_callback, - button_style=ButtonStyle.PRIMARY) - self._custom_software_warning_continue_button.set_enabled(False) - self._custom_software_warning_back_button = Button("Back", self._custom_software_warning_back_button_callback) - self._custom_software_warning_title_label = Label("WARNING: Custom Software", 81, FontWeight.BOLD, rl.GuiTextAlignment.TEXT_ALIGN_LEFT, - text_color=rl.Color(255, 89, 79, 255), - text_padding=60) - self._custom_software_warning_body_label = Label("Use caution when installing third-party software.\n\n" - + "⚠️ It has not been tested by comma.\n\n" - + "⚠️ It may not comply with relevant safety standards.\n\n" - + "⚠️ It may cause damage to your device and/or vehicle.\n\n" - + "If you'd like to proceed, use https://flash.comma.ai " - + "to restore your device to a factory state later.", - 68, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT, text_padding=60) - self._custom_software_warning_body_scroll_panel = GuiScrollPanel() - - self._downloading_body_label = Label("Downloading...", TITLE_FONT_SIZE, FontWeight.MEDIUM, text_padding=20) - - 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_SOFTWARE_WARNING: - self.render_custom_software_warning(rect) - elif self.state == SetupState.CUSTOM_SOFTWARE: - self.render_custom_software() - elif self.state == SetupState.DOWNLOADING: - self.render_downloading(rect) - elif self.state == SetupState.DOWNLOAD_FAILED: - self.render_download_failed(rect) - - def _low_voltage_continue_button_callback(self): - self.state = SetupState.GETTING_STARTED - - def _custom_software_warning_back_button_callback(self): - self.state = SetupState.SOFTWARE_SELECTION - - def _custom_software_warning_continue_button_callback(self): - self.state = SetupState.NETWORK_SETUP - self.stop_network_check_thread.clear() - self.start_network_check() - - def _getting_started_button_callback(self): - self.state = SetupState.SOFTWARE_SELECTION - - def _software_selection_back_button_callback(self): - self.state = SetupState.GETTING_STARTED - - def _software_selection_continue_button_callback(self): - if self._software_selection_openpilot_button.selected: - self.use_openpilot() - else: - self.state = SetupState.CUSTOM_SOFTWARE_WARNING - - def _download_failed_startover_button_callback(self): - self.state = SetupState.GETTING_STARTED - - def _network_setup_back_button_callback(self): - self.state = SetupState.SOFTWARE_SELECTION - - def _network_setup_continue_button_callback(self): - self.stop_network_check_thread.set() - if self._software_selection_openpilot_button.selected: - self.download(OPENPILOT_URL) - else: - self.state = SetupState.CUSTOM_SOFTWARE - - def render_low_voltage(self, rect: rl.Rectangle): - rl.draw_texture(self.warning, int(rect.x + 150), int(rect.y + 110), rl.WHITE) - - self._low_voltage_title_label.render(rl.Rectangle(rect.x + 150, rect.y + 110 + 150 + 100, rect.width - 500 - 150, TITLE_FONT_SIZE * FONT_SCALE)) - self._low_voltage_body_label.render(rl.Rectangle(rect.x + 150, rect.y + 110 + 150 + 150, rect.width - 500, BODY_FONT_SIZE * FONT_SCALE * 3)) - - button_width = (rect.width - MARGIN * 3) / 2 - button_y = rect.height - MARGIN - BUTTON_HEIGHT - self._low_voltage_poweroff_button.render(rl.Rectangle(rect.x + MARGIN, button_y, button_width, BUTTON_HEIGHT)) - self._low_voltage_continue_button.render(rl.Rectangle(rect.x + MARGIN * 2 + button_width, button_y, button_width, BUTTON_HEIGHT)) - - def render_getting_started(self, rect: rl.Rectangle): - self._getting_started_title_label.render(rl.Rectangle(rect.x + 165, rect.y + 280, rect.width - 265, TITLE_FONT_SIZE * FONT_SCALE)) - self._getting_started_body_label.render(rl.Rectangle(rect.x + 165, rect.y + 280 + TITLE_FONT_SIZE * FONT_SCALE, rect.width - 500, - BODY_FONT_SIZE * FONT_SCALE * 3)) - - btn_rect = rl.Rectangle(rect.width - NEXT_BUTTON_WIDTH, 0, NEXT_BUTTON_WIDTH, rect.height) - self._getting_started_button.render(btn_rect) - 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) - - 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): - self._network_setup_title_label.render(rl.Rectangle(rect.x + MARGIN, rect.y + MARGIN, rect.width - MARGIN * 2, TITLE_FONT_SIZE * FONT_SCALE)) - - wifi_rect = rl.Rectangle(rect.x + MARGIN, rect.y + TITLE_FONT_SIZE * FONT_SCALE + MARGIN + 25, rect.width - MARGIN * 2, - rect.height - TITLE_FONT_SIZE * FONT_SCALE - 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 - - self._network_setup_back_button.render(rl.Rectangle(rect.x + MARGIN, button_y, button_width, BUTTON_HEIGHT)) - - # Check network connectivity status - continue_enabled = self.network_connected.is_set() - self._network_setup_continue_button.set_enabled(continue_enabled) - continue_text = ("Continue" if self.wifi_connected.is_set() else "Continue without Wi-Fi") if continue_enabled else "Waiting for internet" - self._network_setup_continue_button.set_text(continue_text) - self._network_setup_continue_button.render(rl.Rectangle(rect.x + MARGIN + button_width + BUTTON_SPACING, button_y, button_width, BUTTON_HEIGHT)) - - def render_software_selection(self, rect: rl.Rectangle): - self._software_selection_title_label.render(rl.Rectangle(rect.x + MARGIN, rect.y + MARGIN, rect.width - MARGIN * 2, TITLE_FONT_SIZE * FONT_SCALE)) - - radio_height = 230 - radio_spacing = 30 - - self._software_selection_continue_button.set_enabled(False) - - openpilot_rect = rl.Rectangle(rect.x + MARGIN, rect.y + TITLE_FONT_SIZE * FONT_SCALE + MARGIN * 2, rect.width - MARGIN * 2, radio_height) - self._software_selection_openpilot_button.render(openpilot_rect) - - if self._software_selection_openpilot_button.selected: - self._software_selection_continue_button.set_enabled(True) - self._software_selection_custom_software_button.selected = False - - custom_rect = rl.Rectangle(rect.x + MARGIN, rect.y + TITLE_FONT_SIZE * FONT_SCALE + MARGIN * 2 + radio_height + radio_spacing, rect.width - MARGIN * 2, - radio_height) - self._software_selection_custom_software_button.render(custom_rect) - - if self._software_selection_custom_software_button.selected: - self._software_selection_continue_button.set_enabled(True) - self._software_selection_openpilot_button.selected = False - - button_width = (rect.width - BUTTON_SPACING - MARGIN * 2) / 2 - button_y = rect.height - BUTTON_HEIGHT - MARGIN - - self._software_selection_back_button.render(rl.Rectangle(rect.x + MARGIN, button_y, button_width, BUTTON_HEIGHT)) - self._software_selection_continue_button.render(rl.Rectangle(rect.x + MARGIN + button_width + BUTTON_SPACING, button_y, button_width, BUTTON_HEIGHT)) - - def render_downloading(self, rect: rl.Rectangle): - self._downloading_body_label.render(rl.Rectangle(rect.x, rect.y + rect.height / 2 - TITLE_FONT_SIZE * FONT_SCALE / 2, rect.width, - TITLE_FONT_SIZE * FONT_SCALE)) - - def render_download_failed(self, rect: rl.Rectangle): - self._download_failed_title_label.render(rl.Rectangle(rect.x + 117, rect.y + 185, rect.width - 117, TITLE_FONT_SIZE * FONT_SCALE)) - self._download_failed_url_label.set_text(self.failed_url) - self._download_failed_url_label.render(rl.Rectangle(rect.x + 117, rect.y + 185 + TITLE_FONT_SIZE * FONT_SCALE + 67, rect.width - 117 - 100, 64)) - - self._download_failed_body_label.set_text(self.failed_reason) - self._download_failed_body_label.render(rl.Rectangle(rect.x + 117, rect.y, rect.width - 117 - 100, rect.height)) - - button_width = (rect.width - BUTTON_SPACING - MARGIN * 2) / 2 - button_y = rect.height - BUTTON_HEIGHT - MARGIN - self._download_failed_reboot_button.render(rl.Rectangle(rect.x + MARGIN, button_y, button_width, BUTTON_HEIGHT)) - self._download_failed_startover_button.render(rl.Rectangle(rect.x + MARGIN + button_width + BUTTON_SPACING, button_y, button_width, BUTTON_HEIGHT)) - - def render_custom_software_warning(self, rect: rl.Rectangle): - warn_rect = rl.Rectangle(rect.x, rect.y, rect.width, 1500) - offset = self._custom_software_warning_body_scroll_panel.update(rect, warn_rect) - - button_width = (rect.width - MARGIN * 3) / 2 - button_y = rect.height - MARGIN - BUTTON_HEIGHT - - rl.begin_scissor_mode(int(rect.x), int(rect.y), int(rect.width), int(button_y - BODY_FONT_SIZE * FONT_SCALE)) - y_offset = rect.y + offset - self._custom_software_warning_title_label.render(rl.Rectangle(rect.x + 50, y_offset + 150, rect.width - 265, TITLE_FONT_SIZE * FONT_SCALE)) - self._custom_software_warning_body_label.render(rl.Rectangle(rect.x + 50, y_offset + 200, rect.width - 50, BODY_FONT_SIZE * FONT_SCALE * 3)) - rl.end_scissor_mode() - - self._custom_software_warning_back_button.render(rl.Rectangle(rect.x + MARGIN, button_y, button_width, BUTTON_HEIGHT)) - self._custom_software_warning_continue_button.render(rl.Rectangle(rect.x + MARGIN * 2 + button_width, button_y, button_width, BUTTON_HEIGHT)) - if offset < (rect.height - warn_rect.height): - self._custom_software_warning_continue_button.set_enabled(True) - self._custom_software_warning_continue_button.set_text("Continue") - - def render_custom_software(self): - def handle_keyboard_result(result): - # 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 - - self.keyboard.reset(min_text_size=1) - self.keyboard.set_title("Enter URL", "for Custom Software") - gui_app.set_modal_overlay(self.keyboard, callback=handle_keyboard_result) - - def use_openpilot(self): - if os.path.isdir(INSTALL_PATH) and os.path.isfile(VALID_CACHE_PATH): - os.remove(VALID_CACHE_PATH) - with open(TMP_CONTINUE_PATH, "w") as f: - f.write(CONTINUE) - run_cmd(["chmod", "+x", TMP_CONTINUE_PATH]) - shutil.move(TMP_CONTINUE_PATH, CONTINUE_PATH) - shutil.copyfile(INSTALLER_SOURCE_PATH, INSTALLER_DESTINATION_PATH) - - # give time for installer UI to take over - time.sleep(0.1) - gui_app.request_close() - else: - self.state = SetupState.NETWORK_SETUP - self.stop_network_check_thread.clear() - self.start_network_check() - - def download(self, url: str): - # autocomplete incomplete URLs - if re.match("^([^/.]+)/([^/]+)$", url): - url = f"https://installer.comma.ai/{url}" - - parsed = urlparse(url, scheme='https') - self.download_url = (urlparse(f"https://{url}") if not parsed.netloc else parsed).geturl() - - 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 - - fd, tmpfile = tempfile.mkstemp(prefix="installer_") - - headers = {"User-Agent": USER_AGENT, - "X-openpilot-serial": HARDWARE.get_serial(), - "X-openpilot-device-type": HARDWARE.get_device_type()} - 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 - - # AGNOS might try to execute the installer before this process exits. - # Therefore, important to close the fd before renaming the installer. - os.close(fd) - os.rename(tmpfile, INSTALLER_DESTINATION_PATH) - - with open(INSTALLER_URL_PATH, "w") as f: - f.write(self.download_url) - - # give time for installer UI to take over - time.sleep(0.1) - gui_app.request_close() - - except urllib.error.HTTPError as e: - if e.code == 409: - error_msg = e.read().decode("utf-8") - self.download_failed(self.download_url, error_msg) - 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 +from openpilot.system.ui.lib.application import gui_app +import openpilot.system.ui.tici_setup as tici_setup +import openpilot.system.ui.mici_setup as mici_setup def main(): - try: - gui_app.init_window("Setup", 20) - setup = Setup() - for should_render in gui_app.render(): - if should_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 gui_app.big_ui(): + tici_setup.main() + else: + mici_setup.main() if __name__ == "__main__": diff --git a/system/ui/spinner.py b/system/ui/spinner.py index eb33f0834..2a48b3889 100755 --- a/system/ui/spinner.py +++ b/system/ui/spinner.py @@ -9,11 +9,20 @@ from openpilot.system.ui.text import wrap_text from openpilot.system.ui.widgets import Widget # Constants -PROGRESS_BAR_WIDTH = 1000 -PROGRESS_BAR_HEIGHT = 20 +if gui_app.big_ui(): + PROGRESS_BAR_WIDTH = 1000 + PROGRESS_BAR_HEIGHT = 20 + TEXTURE_SIZE = 360 + WRAPPED_SPACING = 50 + CENTERED_SPACING = 150 +else: + PROGRESS_BAR_WIDTH = 268 + PROGRESS_BAR_HEIGHT = 10 + TEXTURE_SIZE = 140 + WRAPPED_SPACING = 10 + CENTERED_SPACING = 20 DEGREES_PER_SECOND = 360.0 # one full rotation per second MARGIN_H = 100 -TEXTURE_SIZE = 360 FONT_SIZE = 96 LINE_HEIGHT = 104 DARKGRAY = (55, 55, 55, 255) @@ -43,12 +52,12 @@ class Spinner(Widget): def _render(self, rect: rl.Rectangle): if self._wrapped_lines: # Calculate total height required for spinner and text - spacing = 50 + spacing = WRAPPED_SPACING total_height = TEXTURE_SIZE + spacing + len(self._wrapped_lines) * LINE_HEIGHT center_y = (rect.height - total_height) / 2.0 + TEXTURE_SIZE / 2.0 else: # Center spinner vertically - spacing = 150 + spacing = CENTERED_SPACING center_y = rect.height / 2.0 y_pos = center_y + TEXTURE_SIZE / 2.0 + spacing diff --git a/system/ui/text.py b/system/ui/text.py index 707b30983..17e8a507c 100755 --- a/system/ui/text.py +++ b/system/ui/text.py @@ -3,17 +3,24 @@ import re import sys import pyray as rl from openpilot.system.hardware import HARDWARE, PC -from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.lib.application import BIG_UI, gui_app from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.button import Button, ButtonStyle -MARGIN = 50 -SPACING = 40 -FONT_SIZE = 72 -LINE_HEIGHT = 80 -BUTTON_SIZE = rl.Vector2(310, 160) +if BIG_UI: + MARGIN = 50 + SPACING = 40 + FONT_SIZE = 72 + LINE_HEIGHT = 80 + BUTTON_SIZE = rl.Vector2(310, 160) +else: + MARGIN = 20 + SPACING = 30 + FONT_SIZE = 25 + LINE_HEIGHT = 25 + BUTTON_SIZE = rl.Vector2(150, 80) DEMO_TEXT = """This is a sample text that will be wrapped and scrolled if necessary. The text is long enough to demonstrate scrolling and word wrapping.""" * 30 @@ -31,7 +38,7 @@ def wrap_text(text, font_size, max_width): continue indent = re.match(r"^\s*", paragraph).group() current_line = indent - words = re.split(r"(\s+)", paragraph[len(indent):]) + words = re.split(r"(\s+|-)", paragraph[len(indent):]) while len(words): word = words.pop(0) test_line = current_line + word + (words.pop(0) if words else "") @@ -57,7 +64,7 @@ class TextWindow(Widget): self._scroll_panel._offset_filter_y.x = -max(self._content_rect.height - self._textarea_rect.height, 0) button_text = "Exit" if PC else "Reboot" - self._button = Button(button_text, click_callback=self._on_button_clicked, button_style=ButtonStyle.TRANSPARENT_WHITE_BORDER) + self._button = Button(button_text, click_callback=self._on_button_clicked, button_style=ButtonStyle.TRANSPARENT_WHITE_BORDER, font_size=FONT_SIZE) @staticmethod def _on_button_clicked(): diff --git a/system/ui/tici_reset.py b/system/ui/tici_reset.py new file mode 100755 index 000000000..3922c27aa --- /dev/null +++ b/system/ui/tici_reset.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python3 +import os +import sys +import threading +import time +from enum import IntEnum + +import pyray as rl + +from openpilot.system.hardware import PC +from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE +from openpilot.system.ui.widgets import Widget +from openpilot.system.ui.widgets.button import Button, ButtonStyle +from openpilot.system.ui.widgets.label import gui_label, gui_text_box + +USERDATA = "/dev/disk/by-partlabel/userdata" +TIMEOUT = 3*60 + + +class ResetMode(IntEnum): + USER_RESET = 0 # user initiated a factory reset from openpilot + RECOVER = 1 # userdata is corrupt for some reason, give a chance to recover + FORMAT = 2 # finish up a factory reset from a tool that doesn't flash an empty partition to userdata + + +class ResetState(IntEnum): + NONE = 0 + CONFIRM = 1 + RESETTING = 2 + FAILED = 3 + + +class Reset(Widget): + def __init__(self, mode): + super().__init__() + self._mode = mode + self._previous_reset_state = None + self._reset_state = ResetState.NONE + self._cancel_button = Button("Cancel", self._cancel_callback) + self._confirm_button = Button("Confirm", self._confirm, button_style=ButtonStyle.PRIMARY) + self._reboot_button = Button("Reboot", lambda: os.system("sudo reboot")) + self._render_status = True + + def _cancel_callback(self): + self._render_status = False + + def _do_erase(self): + if PC: + return + + # Removing data and formatting + rm = os.system("sudo rm -rf /data/*") + os.system(f"sudo umount {USERDATA}") + fmt = os.system(f"yes | sudo mkfs.ext4 {USERDATA}") + + if rm == 0 or fmt == 0: + os.system("sudo reboot") + else: + self._reset_state = ResetState.FAILED + + def start_reset(self): + self._reset_state = ResetState.RESETTING + threading.Timer(0.1, self._do_erase).start() + + def _update_state(self): + if self._reset_state != self._previous_reset_state: + self._previous_reset_state = self._reset_state + self._timeout_st = time.monotonic() + elif self._reset_state != ResetState.RESETTING and (time.monotonic() - self._timeout_st) > TIMEOUT: + exit(0) + + def _render(self, rect: rl.Rectangle): + label_rect = rl.Rectangle(rect.x + 140, rect.y, rect.width - 280, 100 * FONT_SCALE) + gui_label(label_rect, "System Reset", 100, font_weight=FontWeight.BOLD) + + text_rect = rl.Rectangle(rect.x + 140, rect.y + 140, rect.width - 280, rect.height - 90 - 100 * FONT_SCALE) + gui_text_box(text_rect, self._get_body_text(), 90) + + button_height = 160 + button_spacing = 50 + button_top = rect.y + rect.height - button_height + button_width = (rect.width - button_spacing) / 2.0 + + if self._reset_state != ResetState.RESETTING: + if self._mode == ResetMode.RECOVER: + self._reboot_button.render(rl.Rectangle(rect.x, button_top, button_width, button_height)) + elif self._mode == ResetMode.USER_RESET: + self._cancel_button.render(rl.Rectangle(rect.x, button_top, button_width, button_height)) + + if self._reset_state != ResetState.FAILED: + self._confirm_button.render(rl.Rectangle(rect.x + button_width + 50, button_top, button_width, button_height)) + else: + self._reboot_button.render(rl.Rectangle(rect.x, button_top, rect.width, button_height)) + + return self._render_status + + def _confirm(self): + if self._reset_state == ResetState.CONFIRM: + self.start_reset() + else: + self._reset_state = ResetState.CONFIRM + + def _get_body_text(self): + if self._reset_state == ResetState.CONFIRM: + return "Are you sure you want to reset your device?" + if self._reset_state == ResetState.RESETTING: + return "Resetting device...\nThis may take up to a minute." + if self._reset_state == ResetState.FAILED: + return "Reset failed. Reboot to try again." + if self._mode == ResetMode.RECOVER: + return "Unable to mount data partition. Partition may be corrupted. Press confirm to erase and reset your device." + return "System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot." + + +def main(): + mode = ResetMode.USER_RESET + if len(sys.argv) > 1: + if sys.argv[1] == '--recover': + mode = ResetMode.RECOVER + elif sys.argv[1] == "--format": + mode = ResetMode.FORMAT + + gui_app.init_window("System Reset", 20) + reset = Reset(mode) + + if mode == ResetMode.FORMAT: + reset.start_reset() + + for should_render in gui_app.render(): + if should_render: + if not reset.render(rl.Rectangle(45, 200, gui_app.width - 90, gui_app.height - 245)): + break + + +if __name__ == "__main__": + main() diff --git a/system/ui/tici_setup.py b/system/ui/tici_setup.py new file mode 100755 index 000000000..bf64361be --- /dev/null +++ b/system/ui/tici_setup.py @@ -0,0 +1,451 @@ +#!/usr/bin/env python3 +import os +import re +import threading +import time +import urllib.request +import urllib.error +from urllib.parse import urlparse +from enum import IntEnum +import shutil + +import pyray as rl + +from cereal import log +from openpilot.common.utils import run_cmd +from openpilot.system.hardware import HARDWARE +from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel +from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE +from openpilot.system.ui.widgets import Widget +from openpilot.system.ui.widgets.button import Button, ButtonStyle, ButtonRadio +from openpilot.system.ui.widgets.keyboard import Keyboard +from openpilot.system.ui.widgets.label import Label +from openpilot.system.ui.widgets.network import WifiManagerUI, WifiManager + +NetworkType = log.DeviceState.NetworkType + +MARGIN = 50 +TITLE_FONT_SIZE = 90 +TITLE_FONT_WEIGHT = FontWeight.MEDIUM +NEXT_BUTTON_WIDTH = 310 +BODY_FONT_SIZE = 80 +BUTTON_HEIGHT = 160 +BUTTON_SPACING = 50 + +OPENPILOT_URL = "https://openpilot.comma.ai" +USER_AGENT = f"AGNOSSetup-{HARDWARE.get_os_version()}" + +CONTINUE_PATH = "/data/continue.sh" +TMP_CONTINUE_PATH = "/data/continue.sh.new" +INSTALL_PATH = "/data/openpilot" +VALID_CACHE_PATH = "/data/.openpilot_cache" +INSTALLER_SOURCE_PATH = "/usr/comma/installer" +INSTALLER_DESTINATION_PATH = "/tmp/installer" +INSTALLER_URL_PATH = "/tmp/installer_url" + +CONTINUE = """#!/usr/bin/env bash + +cd /data/openpilot +exec ./launch_openpilot.sh +""" + + +class SetupState(IntEnum): + LOW_VOLTAGE = 0 + GETTING_STARTED = 1 + NETWORK_SETUP = 2 + SOFTWARE_SELECTION = 3 + CUSTOM_SOFTWARE = 4 + DOWNLOADING = 5 + DOWNLOAD_FAILED = 6 + CUSTOM_SOFTWARE_WARNING = 7 + + +class Setup(Widget): + def __init__(self): + super().__init__() + 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_ui = WifiManagerUI(WifiManager()) + 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) + + self._low_voltage_title_label = Label("WARNING: Low Voltage", TITLE_FONT_SIZE, FontWeight.MEDIUM, rl.GuiTextAlignment.TEXT_ALIGN_LEFT, + text_color=rl.Color(255, 89, 79, 255), text_padding=20) + self._low_voltage_body_label = Label("Power your device in a car with a harness or proceed at your own risk.", BODY_FONT_SIZE, + text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT, text_padding=20) + self._low_voltage_continue_button = Button("Continue", self._low_voltage_continue_button_callback) + self._low_voltage_poweroff_button = Button("Power Off", HARDWARE.shutdown) + + self._getting_started_button = Button("", self._getting_started_button_callback, button_style=ButtonStyle.PRIMARY, border_radius=0) + self._getting_started_title_label = Label("Getting Started", TITLE_FONT_SIZE, FontWeight.BOLD, rl.GuiTextAlignment.TEXT_ALIGN_LEFT, text_padding=20) + self._getting_started_body_label = Label("Before we get on the road, let's finish installation and cover some details.", + BODY_FONT_SIZE, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT, text_padding=20) + + self._software_selection_openpilot_button = ButtonRadio("openpilot", self.checkmark, font_size=BODY_FONT_SIZE, text_padding=80) + self._software_selection_custom_software_button = ButtonRadio("Custom Software", self.checkmark, font_size=BODY_FONT_SIZE, text_padding=80) + self._software_selection_continue_button = Button("Continue", self._software_selection_continue_button_callback, + button_style=ButtonStyle.PRIMARY) + self._software_selection_continue_button.set_enabled(False) + self._software_selection_back_button = Button("Back", self._software_selection_back_button_callback) + self._software_selection_title_label = Label("Choose Software to Use", TITLE_FONT_SIZE, FontWeight.BOLD, rl.GuiTextAlignment.TEXT_ALIGN_LEFT, + text_padding=20) + + self._download_failed_reboot_button = Button("Reboot device", HARDWARE.reboot) + self._download_failed_startover_button = Button("Start over", self._download_failed_startover_button_callback, button_style=ButtonStyle.PRIMARY) + self._download_failed_title_label = Label("Download Failed", TITLE_FONT_SIZE, FontWeight.BOLD, rl.GuiTextAlignment.TEXT_ALIGN_LEFT, text_padding=20) + self._download_failed_url_label = Label("", 52, FontWeight.NORMAL, rl.GuiTextAlignment.TEXT_ALIGN_LEFT, text_padding=20) + self._download_failed_body_label = Label("", BODY_FONT_SIZE, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT, text_padding=20) + + self._network_setup_back_button = Button("Back", self._network_setup_back_button_callback) + self._network_setup_continue_button = Button("Waiting for internet", self._network_setup_continue_button_callback, + button_style=ButtonStyle.PRIMARY) + self._network_setup_continue_button.set_enabled(False) + self._network_setup_title_label = Label("Connect to Wi-Fi", TITLE_FONT_SIZE, FontWeight.BOLD, rl.GuiTextAlignment.TEXT_ALIGN_LEFT, text_padding=20) + + self._custom_software_warning_continue_button = Button("Scroll to continue", self._custom_software_warning_continue_button_callback, + button_style=ButtonStyle.PRIMARY) + self._custom_software_warning_continue_button.set_enabled(False) + self._custom_software_warning_back_button = Button("Back", self._custom_software_warning_back_button_callback) + self._custom_software_warning_title_label = Label("WARNING: Custom Software", 81, FontWeight.BOLD, rl.GuiTextAlignment.TEXT_ALIGN_LEFT, + text_color=rl.Color(255, 89, 79, 255), + text_padding=60) + self._custom_software_warning_body_label = Label("Use caution when installing third-party software.\n\n" + + "⚠️ It has not been tested by comma.\n\n" + + "⚠️ It may not comply with relevant safety standards.\n\n" + + "⚠️ It may cause damage to your device and/or vehicle.\n\n" + + "If you'd like to proceed, use https://flash.comma.ai " + + "to restore your device to a factory state later.", + 68, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT, text_padding=60) + self._custom_software_warning_body_scroll_panel = GuiScrollPanel() + + self._downloading_body_label = Label("Downloading...", TITLE_FONT_SIZE, FontWeight.MEDIUM, text_padding=20) + + 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_SOFTWARE_WARNING: + self.render_custom_software_warning(rect) + elif self.state == SetupState.CUSTOM_SOFTWARE: + self.render_custom_software() + elif self.state == SetupState.DOWNLOADING: + self.render_downloading(rect) + elif self.state == SetupState.DOWNLOAD_FAILED: + self.render_download_failed(rect) + + def _low_voltage_continue_button_callback(self): + self.state = SetupState.GETTING_STARTED + + def _custom_software_warning_back_button_callback(self): + self.state = SetupState.SOFTWARE_SELECTION + + def _custom_software_warning_continue_button_callback(self): + self.state = SetupState.NETWORK_SETUP + self.stop_network_check_thread.clear() + self.start_network_check() + + def _getting_started_button_callback(self): + self.state = SetupState.SOFTWARE_SELECTION + + def _software_selection_back_button_callback(self): + self.state = SetupState.GETTING_STARTED + + def _software_selection_continue_button_callback(self): + if self._software_selection_openpilot_button.selected: + self.use_openpilot() + else: + self.state = SetupState.CUSTOM_SOFTWARE_WARNING + + def _download_failed_startover_button_callback(self): + self.state = SetupState.GETTING_STARTED + + def _network_setup_back_button_callback(self): + self.state = SetupState.SOFTWARE_SELECTION + + def _network_setup_continue_button_callback(self): + self.stop_network_check_thread.set() + if self._software_selection_openpilot_button.selected: + self.download(OPENPILOT_URL) + else: + self.state = SetupState.CUSTOM_SOFTWARE + + def render_low_voltage(self, rect: rl.Rectangle): + rl.draw_texture(self.warning, int(rect.x + 150), int(rect.y + 110), rl.WHITE) + + self._low_voltage_title_label.render(rl.Rectangle(rect.x + 150, rect.y + 110 + 150 + 100, rect.width - 500 - 150, TITLE_FONT_SIZE * FONT_SCALE)) + self._low_voltage_body_label.render(rl.Rectangle(rect.x + 150, rect.y + 110 + 150 + 150, rect.width - 500, BODY_FONT_SIZE * FONT_SCALE * 3)) + + button_width = (rect.width - MARGIN * 3) / 2 + button_y = rect.height - MARGIN - BUTTON_HEIGHT + self._low_voltage_poweroff_button.render(rl.Rectangle(rect.x + MARGIN, button_y, button_width, BUTTON_HEIGHT)) + self._low_voltage_continue_button.render(rl.Rectangle(rect.x + MARGIN * 2 + button_width, button_y, button_width, BUTTON_HEIGHT)) + + def render_getting_started(self, rect: rl.Rectangle): + self._getting_started_title_label.render(rl.Rectangle(rect.x + 165, rect.y + 280, rect.width - 265, TITLE_FONT_SIZE * FONT_SCALE)) + self._getting_started_body_label.render(rl.Rectangle(rect.x + 165, rect.y + 280 + TITLE_FONT_SIZE * FONT_SCALE, rect.width - 500, + BODY_FONT_SIZE * FONT_SCALE * 3)) + + btn_rect = rl.Rectangle(rect.width - NEXT_BUTTON_WIDTH, 0, NEXT_BUTTON_WIDTH, rect.height) + self._getting_started_button.render(btn_rect) + 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) + + 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): + self._network_setup_title_label.render(rl.Rectangle(rect.x + MARGIN, rect.y + MARGIN, rect.width - MARGIN * 2, TITLE_FONT_SIZE * FONT_SCALE)) + + wifi_rect = rl.Rectangle(rect.x + MARGIN, rect.y + TITLE_FONT_SIZE * FONT_SCALE + MARGIN + 25, rect.width - MARGIN * 2, + rect.height - TITLE_FONT_SIZE * FONT_SCALE - 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 + + self._network_setup_back_button.render(rl.Rectangle(rect.x + MARGIN, button_y, button_width, BUTTON_HEIGHT)) + + # Check network connectivity status + continue_enabled = self.network_connected.is_set() + self._network_setup_continue_button.set_enabled(continue_enabled) + continue_text = ("Continue" if self.wifi_connected.is_set() else "Continue without Wi-Fi") if continue_enabled else "Waiting for internet" + self._network_setup_continue_button.set_text(continue_text) + self._network_setup_continue_button.render(rl.Rectangle(rect.x + MARGIN + button_width + BUTTON_SPACING, button_y, button_width, BUTTON_HEIGHT)) + + def render_software_selection(self, rect: rl.Rectangle): + self._software_selection_title_label.render(rl.Rectangle(rect.x + MARGIN, rect.y + MARGIN, rect.width - MARGIN * 2, TITLE_FONT_SIZE * FONT_SCALE)) + + radio_height = 230 + radio_spacing = 30 + + self._software_selection_continue_button.set_enabled(False) + + openpilot_rect = rl.Rectangle(rect.x + MARGIN, rect.y + TITLE_FONT_SIZE * FONT_SCALE + MARGIN * 2, rect.width - MARGIN * 2, radio_height) + self._software_selection_openpilot_button.render(openpilot_rect) + + if self._software_selection_openpilot_button.selected: + self._software_selection_continue_button.set_enabled(True) + self._software_selection_custom_software_button.selected = False + + custom_rect = rl.Rectangle(rect.x + MARGIN, rect.y + TITLE_FONT_SIZE * FONT_SCALE + MARGIN * 2 + radio_height + radio_spacing, rect.width - MARGIN * 2, + radio_height) + self._software_selection_custom_software_button.render(custom_rect) + + if self._software_selection_custom_software_button.selected: + self._software_selection_continue_button.set_enabled(True) + self._software_selection_openpilot_button.selected = False + + button_width = (rect.width - BUTTON_SPACING - MARGIN * 2) / 2 + button_y = rect.height - BUTTON_HEIGHT - MARGIN + + self._software_selection_back_button.render(rl.Rectangle(rect.x + MARGIN, button_y, button_width, BUTTON_HEIGHT)) + self._software_selection_continue_button.render(rl.Rectangle(rect.x + MARGIN + button_width + BUTTON_SPACING, button_y, button_width, BUTTON_HEIGHT)) + + def render_downloading(self, rect: rl.Rectangle): + self._downloading_body_label.render(rl.Rectangle(rect.x, rect.y + rect.height / 2 - TITLE_FONT_SIZE * FONT_SCALE / 2, rect.width, + TITLE_FONT_SIZE * FONT_SCALE)) + + def render_download_failed(self, rect: rl.Rectangle): + self._download_failed_title_label.render(rl.Rectangle(rect.x + 117, rect.y + 185, rect.width - 117, TITLE_FONT_SIZE * FONT_SCALE)) + self._download_failed_url_label.set_text(self.failed_url) + self._download_failed_url_label.render(rl.Rectangle(rect.x + 117, rect.y + 185 + TITLE_FONT_SIZE * FONT_SCALE + 67, rect.width - 117 - 100, 64)) + + self._download_failed_body_label.set_text(self.failed_reason) + self._download_failed_body_label.render(rl.Rectangle(rect.x + 117, rect.y, rect.width - 117 - 100, rect.height)) + + button_width = (rect.width - BUTTON_SPACING - MARGIN * 2) / 2 + button_y = rect.height - BUTTON_HEIGHT - MARGIN + self._download_failed_reboot_button.render(rl.Rectangle(rect.x + MARGIN, button_y, button_width, BUTTON_HEIGHT)) + self._download_failed_startover_button.render(rl.Rectangle(rect.x + MARGIN + button_width + BUTTON_SPACING, button_y, button_width, BUTTON_HEIGHT)) + + def render_custom_software_warning(self, rect: rl.Rectangle): + warn_rect = rl.Rectangle(rect.x, rect.y, rect.width, 1500) + offset = self._custom_software_warning_body_scroll_panel.update(rect, warn_rect) + + button_width = (rect.width - MARGIN * 3) / 2 + button_y = rect.height - MARGIN - BUTTON_HEIGHT + + rl.begin_scissor_mode(int(rect.x), int(rect.y), int(rect.width), int(button_y - BODY_FONT_SIZE * FONT_SCALE)) + y_offset = rect.y + offset + self._custom_software_warning_title_label.render(rl.Rectangle(rect.x + 50, y_offset + 150, rect.width - 265, TITLE_FONT_SIZE * FONT_SCALE)) + self._custom_software_warning_body_label.render(rl.Rectangle(rect.x + 50, y_offset + 400, rect.width - 50, BODY_FONT_SIZE * FONT_SCALE * 3)) + rl.end_scissor_mode() + + self._custom_software_warning_back_button.render(rl.Rectangle(rect.x + MARGIN, button_y, button_width, BUTTON_HEIGHT)) + self._custom_software_warning_continue_button.render(rl.Rectangle(rect.x + MARGIN * 2 + button_width, button_y, button_width, BUTTON_HEIGHT)) + if offset < (rect.height - warn_rect.height): + self._custom_software_warning_continue_button.set_enabled(True) + self._custom_software_warning_continue_button.set_text("Continue") + + def render_custom_software(self): + def handle_keyboard_result(result): + # 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 + + self.keyboard.reset(min_text_size=1) + self.keyboard.set_title("Enter URL", "for Custom Software") + gui_app.set_modal_overlay(self.keyboard, callback=handle_keyboard_result) + + def use_openpilot(self): + if os.path.isdir(INSTALL_PATH) and os.path.isfile(VALID_CACHE_PATH): + os.remove(VALID_CACHE_PATH) + with open(TMP_CONTINUE_PATH, "w") as f: + f.write(CONTINUE) + run_cmd(["chmod", "+x", TMP_CONTINUE_PATH]) + shutil.move(TMP_CONTINUE_PATH, CONTINUE_PATH) + shutil.copyfile(INSTALLER_SOURCE_PATH, INSTALLER_DESTINATION_PATH) + + # give time for installer UI to take over + time.sleep(0.1) + gui_app.request_close() + else: + self.state = SetupState.NETWORK_SETUP + self.stop_network_check_thread.clear() + self.start_network_check() + + def download(self, url: str): + # autocomplete incomplete URLs + if re.match("^([^/.]+)/([^/]+)$", url): + url = f"https://installer.comma.ai/{url}" + + parsed = urlparse(url, scheme='https') + self.download_url = (urlparse(f"https://{url}") if not parsed.netloc else parsed).geturl() + + 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 + + fd, tmpfile = tempfile.mkstemp(prefix="installer_") + + headers = {"User-Agent": USER_AGENT, + "X-openpilot-serial": HARDWARE.get_serial(), + "X-openpilot-device-type": HARDWARE.get_device_type()} + 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 + + # AGNOS might try to execute the installer before this process exits. + # Therefore, important to close the fd before renaming the installer. + os.close(fd) + os.rename(tmpfile, INSTALLER_DESTINATION_PATH) + + with open(INSTALLER_URL_PATH, "w") as f: + f.write(self.download_url) + + # give time for installer UI to take over + time.sleep(0.1) + gui_app.request_close() + + except urllib.error.HTTPError as e: + if e.code == 409: + error_msg = e.read().decode("utf-8") + self.download_failed(self.download_url, error_msg) + 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", 20) + setup = Setup() + for should_render in gui_app.render(): + if should_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/tici_updater.py b/system/ui/tici_updater.py new file mode 100755 index 000000000..2e1a8687e --- /dev/null +++ b/system/ui/tici_updater.py @@ -0,0 +1,173 @@ +#!/usr/bin/env python3 +import sys +import subprocess +import threading +import pyray as rl +from enum import IntEnum + +from openpilot.system.hardware import HARDWARE +from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE +from openpilot.system.ui.lib.wifi_manager import WifiManager +from openpilot.system.ui.widgets import Widget +from openpilot.system.ui.widgets.button import Button, ButtonStyle +from openpilot.system.ui.widgets.label import gui_text_box, gui_label +from openpilot.system.ui.widgets.network import WifiManagerUI + +# Constants +MARGIN = 50 +BUTTON_HEIGHT = 160 +BUTTON_WIDTH = 400 +PROGRESS_BAR_HEIGHT = 72 +TITLE_FONT_SIZE = 80 +BODY_FONT_SIZE = 65 +BACKGROUND_COLOR = rl.BLACK +PROGRESS_BG_COLOR = rl.Color(41, 41, 41, 255) +PROGRESS_COLOR = rl.Color(54, 77, 239, 255) + + +class Screen(IntEnum): + PROMPT = 0 + WIFI = 1 + PROGRESS = 2 + + +class Updater(Widget): + def __init__(self, updater_path, manifest_path): + super().__init__() + self.updater = updater_path + self.manifest = manifest_path + self.current_screen = Screen.PROMPT + + self.progress_value = 0 + self.progress_text = "Loading..." + self.show_reboot_button = False + self.process = None + self.update_thread = None + self.wifi_manager_ui = WifiManagerUI(WifiManager()) + + # Buttons + self._wifi_button = Button("Connect to Wi-Fi", click_callback=lambda: self.set_current_screen(Screen.WIFI)) + self._install_button = Button("Install", click_callback=self.install_update, button_style=ButtonStyle.PRIMARY) + self._back_button = Button("Back", click_callback=lambda: self.set_current_screen(Screen.PROMPT)) + self._reboot_button = Button("Reboot", click_callback=lambda: HARDWARE.reboot()) + + def set_current_screen(self, screen: Screen): + self.current_screen = screen + + def install_update(self): + self.set_current_screen(Screen.PROGRESS) + self.progress_value = 0 + self.progress_text = "Downloading..." + self.show_reboot_button = False + + # Start the update process in a separate thread + self.update_thread = threading.Thread(target=self._run_update_process) + self.update_thread.daemon = True + self.update_thread.start() + + def _run_update_process(self): + # TODO: just import it and run in a thread without a subprocess + cmd = [self.updater, "--swap", self.manifest] + self.process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, + text=True, bufsize=1, universal_newlines=True) + + for line in self.process.stdout: + parts = line.strip().split(":") + if len(parts) == 2: + self.progress_text = parts[0] + try: + self.progress_value = int(float(parts[1])) + except ValueError: + pass + + exit_code = self.process.wait() + if exit_code == 0: + HARDWARE.reboot() + else: + self.progress_text = "Update failed" + self.show_reboot_button = True + + def render_prompt_screen(self, rect: rl.Rectangle): + # Title + title_rect = rl.Rectangle(MARGIN + 50, 250, rect.width - MARGIN * 2 - 100, TITLE_FONT_SIZE * FONT_SCALE) + 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_rect = rl.Rectangle(MARGIN + 50, 250 + TITLE_FONT_SIZE * FONT_SCALE + 75, rect.width - MARGIN * 2 - 100, BODY_FONT_SIZE * FONT_SCALE * 4) + gui_text_box(desc_rect, desc_text, BODY_FONT_SIZE) + + # Buttons at the bottom + button_y = rect.height - MARGIN - BUTTON_HEIGHT + button_width = (rect.width - MARGIN * 3) // 2 + + # WiFi button + wifi_button_rect = rl.Rectangle(MARGIN, button_y, button_width, BUTTON_HEIGHT) + self._wifi_button.render(wifi_button_rect) + + # Install button + install_button_rect = rl.Rectangle(MARGIN * 2 + button_width, button_y, button_width, BUTTON_HEIGHT) + self._install_button.render(install_button_rect) + + def render_wifi_screen(self, rect: rl.Rectangle): + # Draw the Wi-Fi manager UI + wifi_rect = rl.Rectangle(rect.x + MARGIN, rect.y + MARGIN, rect.width - MARGIN * 2, + rect.height - BUTTON_HEIGHT - MARGIN * 3) + rl.draw_rectangle_rounded(wifi_rect, 0.035, 10, rl.Color(51, 51, 51, 255)) + wifi_content_rect = rl.Rectangle(wifi_rect.x + 50, wifi_rect.y, wifi_rect.width - 100, wifi_rect.height) + self.wifi_manager_ui.render(wifi_content_rect) + + back_button_rect = rl.Rectangle(MARGIN, rect.height - MARGIN - BUTTON_HEIGHT, BUTTON_WIDTH, BUTTON_HEIGHT) + self._back_button.render(back_button_rect) + + def render_progress_screen(self, rect: rl.Rectangle): + title_rect = rl.Rectangle(MARGIN + 100, 330, rect.width - MARGIN * 2 - 200, 100) + gui_label(title_rect, self.progress_text, 90, font_weight=FontWeight.SEMI_BOLD) + + # Progress bar + bar_rect = rl.Rectangle(MARGIN + 100, 330 + 100 + 100, rect.width - MARGIN * 2 - 200, PROGRESS_BAR_HEIGHT) + rl.draw_rectangle_rounded(bar_rect, 0.5, 10, PROGRESS_BG_COLOR) + + # Calculate the width of the progress chunk + progress_width = (bar_rect.width * self.progress_value) / 100 + if progress_width > 0: + progress_rect = rl.Rectangle(bar_rect.x, bar_rect.y, progress_width, bar_rect.height) + rl.draw_rectangle_rounded(progress_rect, 0.5, 10, PROGRESS_COLOR) + + # Show reboot button if needed + if self.show_reboot_button: + reboot_rect = rl.Rectangle(MARGIN + 100, rect.height - MARGIN - BUTTON_HEIGHT, BUTTON_WIDTH, BUTTON_HEIGHT) + self._reboot_button.render(reboot_rect) + + def _render(self, rect: rl.Rectangle): + if self.current_screen == Screen.PROMPT: + self.render_prompt_screen(rect) + elif self.current_screen == Screen.WIFI: + self.render_wifi_screen(rect) + elif self.current_screen == Screen.PROGRESS: + self.render_progress_screen(rect) + + +def main(): + if len(sys.argv) < 3: + print("Usage: updater.py ") + sys.exit(1) + + updater_path = sys.argv[1] + manifest_path = sys.argv[2] + + try: + gui_app.init_window("System Update") + updater = Updater(updater_path, manifest_path) + for should_render in gui_app.render(): + if should_render: + updater.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) + finally: + # Make sure we clean up even if there's an error + gui_app.close() + + +if __name__ == "__main__": + main() diff --git a/system/ui/updater.py b/system/ui/updater.py index 2e1a8687e..42d12d909 100755 --- a/system/ui/updater.py +++ b/system/ui/updater.py @@ -1,172 +1,14 @@ #!/usr/bin/env python3 -import sys -import subprocess -import threading -import pyray as rl -from enum import IntEnum - -from openpilot.system.hardware import HARDWARE -from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE -from openpilot.system.ui.lib.wifi_manager import WifiManager -from openpilot.system.ui.widgets import Widget -from openpilot.system.ui.widgets.button import Button, ButtonStyle -from openpilot.system.ui.widgets.label import gui_text_box, gui_label -from openpilot.system.ui.widgets.network import WifiManagerUI - -# Constants -MARGIN = 50 -BUTTON_HEIGHT = 160 -BUTTON_WIDTH = 400 -PROGRESS_BAR_HEIGHT = 72 -TITLE_FONT_SIZE = 80 -BODY_FONT_SIZE = 65 -BACKGROUND_COLOR = rl.BLACK -PROGRESS_BG_COLOR = rl.Color(41, 41, 41, 255) -PROGRESS_COLOR = rl.Color(54, 77, 239, 255) - - -class Screen(IntEnum): - PROMPT = 0 - WIFI = 1 - PROGRESS = 2 - - -class Updater(Widget): - def __init__(self, updater_path, manifest_path): - super().__init__() - self.updater = updater_path - self.manifest = manifest_path - self.current_screen = Screen.PROMPT - - self.progress_value = 0 - self.progress_text = "Loading..." - self.show_reboot_button = False - self.process = None - self.update_thread = None - self.wifi_manager_ui = WifiManagerUI(WifiManager()) - - # Buttons - self._wifi_button = Button("Connect to Wi-Fi", click_callback=lambda: self.set_current_screen(Screen.WIFI)) - self._install_button = Button("Install", click_callback=self.install_update, button_style=ButtonStyle.PRIMARY) - self._back_button = Button("Back", click_callback=lambda: self.set_current_screen(Screen.PROMPT)) - self._reboot_button = Button("Reboot", click_callback=lambda: HARDWARE.reboot()) - - def set_current_screen(self, screen: Screen): - self.current_screen = screen - - def install_update(self): - self.set_current_screen(Screen.PROGRESS) - self.progress_value = 0 - self.progress_text = "Downloading..." - self.show_reboot_button = False - - # Start the update process in a separate thread - self.update_thread = threading.Thread(target=self._run_update_process) - self.update_thread.daemon = True - self.update_thread.start() - - def _run_update_process(self): - # TODO: just import it and run in a thread without a subprocess - cmd = [self.updater, "--swap", self.manifest] - self.process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, - text=True, bufsize=1, universal_newlines=True) - - for line in self.process.stdout: - parts = line.strip().split(":") - if len(parts) == 2: - self.progress_text = parts[0] - try: - self.progress_value = int(float(parts[1])) - except ValueError: - pass - - exit_code = self.process.wait() - if exit_code == 0: - HARDWARE.reboot() - else: - self.progress_text = "Update failed" - self.show_reboot_button = True - - def render_prompt_screen(self, rect: rl.Rectangle): - # Title - title_rect = rl.Rectangle(MARGIN + 50, 250, rect.width - MARGIN * 2 - 100, TITLE_FONT_SIZE * FONT_SCALE) - 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_rect = rl.Rectangle(MARGIN + 50, 250 + TITLE_FONT_SIZE * FONT_SCALE + 75, rect.width - MARGIN * 2 - 100, BODY_FONT_SIZE * FONT_SCALE * 4) - gui_text_box(desc_rect, desc_text, BODY_FONT_SIZE) - - # Buttons at the bottom - button_y = rect.height - MARGIN - BUTTON_HEIGHT - button_width = (rect.width - MARGIN * 3) // 2 - - # WiFi button - wifi_button_rect = rl.Rectangle(MARGIN, button_y, button_width, BUTTON_HEIGHT) - self._wifi_button.render(wifi_button_rect) - - # Install button - install_button_rect = rl.Rectangle(MARGIN * 2 + button_width, button_y, button_width, BUTTON_HEIGHT) - self._install_button.render(install_button_rect) - - def render_wifi_screen(self, rect: rl.Rectangle): - # Draw the Wi-Fi manager UI - wifi_rect = rl.Rectangle(rect.x + MARGIN, rect.y + MARGIN, rect.width - MARGIN * 2, - rect.height - BUTTON_HEIGHT - MARGIN * 3) - rl.draw_rectangle_rounded(wifi_rect, 0.035, 10, rl.Color(51, 51, 51, 255)) - wifi_content_rect = rl.Rectangle(wifi_rect.x + 50, wifi_rect.y, wifi_rect.width - 100, wifi_rect.height) - self.wifi_manager_ui.render(wifi_content_rect) - - back_button_rect = rl.Rectangle(MARGIN, rect.height - MARGIN - BUTTON_HEIGHT, BUTTON_WIDTH, BUTTON_HEIGHT) - self._back_button.render(back_button_rect) - - def render_progress_screen(self, rect: rl.Rectangle): - title_rect = rl.Rectangle(MARGIN + 100, 330, rect.width - MARGIN * 2 - 200, 100) - gui_label(title_rect, self.progress_text, 90, font_weight=FontWeight.SEMI_BOLD) - - # Progress bar - bar_rect = rl.Rectangle(MARGIN + 100, 330 + 100 + 100, rect.width - MARGIN * 2 - 200, PROGRESS_BAR_HEIGHT) - rl.draw_rectangle_rounded(bar_rect, 0.5, 10, PROGRESS_BG_COLOR) - - # Calculate the width of the progress chunk - progress_width = (bar_rect.width * self.progress_value) / 100 - if progress_width > 0: - progress_rect = rl.Rectangle(bar_rect.x, bar_rect.y, progress_width, bar_rect.height) - rl.draw_rectangle_rounded(progress_rect, 0.5, 10, PROGRESS_COLOR) - - # Show reboot button if needed - if self.show_reboot_button: - reboot_rect = rl.Rectangle(MARGIN + 100, rect.height - MARGIN - BUTTON_HEIGHT, BUTTON_WIDTH, BUTTON_HEIGHT) - self._reboot_button.render(reboot_rect) - - def _render(self, rect: rl.Rectangle): - if self.current_screen == Screen.PROMPT: - self.render_prompt_screen(rect) - elif self.current_screen == Screen.WIFI: - self.render_wifi_screen(rect) - elif self.current_screen == Screen.PROGRESS: - self.render_progress_screen(rect) +from openpilot.system.ui.lib.application import gui_app +import openpilot.system.ui.tici_updater as tici_updater +import openpilot.system.ui.mici_updater as mici_updater def main(): - if len(sys.argv) < 3: - print("Usage: updater.py ") - sys.exit(1) - - updater_path = sys.argv[1] - manifest_path = sys.argv[2] - - try: - gui_app.init_window("System Update") - updater = Updater(updater_path, manifest_path) - for should_render in gui_app.render(): - if should_render: - updater.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) - finally: - # Make sure we clean up even if there's an error - gui_app.close() + if gui_app.big_ui(): + tici_updater.main() + else: + mici_updater.main() if __name__ == "__main__": diff --git a/system/ui/widgets/__init__.py b/system/ui/widgets/__init__.py index 562cde39e..95858ec1b 100644 --- a/system/ui/widgets/__init__.py +++ b/system/ui/widgets/__init__.py @@ -2,7 +2,15 @@ import abc import pyray as rl from enum import IntEnum from collections.abc import Callable -from openpilot.system.ui.lib.application import gui_app, MousePos, MAX_TOUCH_SLOTS +from openpilot.common.filter_simple import BounceFilter, FirstOrderFilter +from openpilot.system.ui.lib.application import gui_app, MousePos, MAX_TOUCH_SLOTS, MouseEvent + +try: + from openpilot.selfdrive.ui.ui_state import device +except ImportError: + class Device: + awake = True + device = Device() # type: ignore class DialogResult(IntEnum): @@ -23,6 +31,7 @@ class Widget(abc.ABC): self._touch_valid_callback: Callable[[], bool] | None = None self._click_callback: Callable[[], None] | None = None self._multi_touch = False + self.__was_awake = True @property def rect(self) -> rl.Rectangle: @@ -71,7 +80,7 @@ class Widget(abc.ABC): def set_position(self, x: float, y: float) -> None: changed = (self._rect.x != x or self._rect.y != y) - self._rect.x, self._rect.y = x, y + self._rect = rl.Rectangle(x, y, self._rect.width, self._rect.height) if changed: self._update_layout_rects() @@ -94,7 +103,7 @@ class Widget(abc.ABC): ret = self._render(self._rect) # Keep track of whether mouse down started within the widget's rectangle - if self.enabled: + if self.enabled and self.__was_awake: for mouse_event in gui_app.mouse_events: if not self._multi_touch and mouse_event.slot != 0: continue @@ -106,6 +115,7 @@ class Widget(abc.ABC): self._handle_mouse_press(mouse_event.pos) self.__is_pressed[mouse_event.slot] = True self.__tracking_is_pressed[mouse_event.slot] = True + self._handle_mouse_event(mouse_event) # Callback such as scroll panel signifies user is scrolling elif not self._touch_valid(): @@ -113,6 +123,7 @@ class Widget(abc.ABC): self.__tracking_is_pressed[mouse_event.slot] = False elif mouse_event.left_released: + self._handle_mouse_event(mouse_event) if self.__is_pressed[mouse_event.slot] and rl.check_collision_point_rec(mouse_event.pos, self._hit_rect): self._handle_mouse_release(mouse_event.pos) self.__is_pressed[mouse_event.slot] = False @@ -122,10 +133,14 @@ class Widget(abc.ABC): elif rl.check_collision_point_rec(mouse_event.pos, self._hit_rect): if self.__tracking_is_pressed[mouse_event.slot]: self.__is_pressed[mouse_event.slot] = True + self._handle_mouse_event(mouse_event) # Mouse/touch left our rect but may come back into focus later elif not rl.check_collision_point_rec(mouse_event.pos, self._hit_rect): self.__is_pressed[mouse_event.slot] = False + self._handle_mouse_event(mouse_event) + + self.__was_awake = device.awake return ret @@ -149,9 +164,206 @@ class Widget(abc.ABC): self._click_callback() return False + def _handle_mouse_event(self, mouse_event: MouseEvent) -> None: + """Optionally handle mouse events. This is called before rendering.""" + # Default implementation does nothing, can be overridden by subclasses + def show_event(self): """Optionally handle show event. Parent must manually call this""" def hide_event(self): """Optionally handle hide event. Parent must manually call this""" + +SWIPE_AWAY_THRESHOLD = 80 # px to dismiss after releasing +START_DISMISSING_THRESHOLD = 40 # px to start dismissing while dragging +BLOCK_SWIPE_AWAY_THRESHOLD = 60 # px horizontal movement to block swipe away + +NAV_BAR_MARGIN = 6 +NAV_BAR_WIDTH = 205 +NAV_BAR_HEIGHT = 8 + +DISMISS_PUSH_OFFSET = 50 + NAV_BAR_MARGIN + NAV_BAR_HEIGHT # px extra to push down when dismissing +DISMISS_TIME_SECONDS = 1.5 + + +class NavBar(Widget): + def __init__(self): + super().__init__() + self.set_rect(rl.Rectangle(0, 0, NAV_BAR_WIDTH, NAV_BAR_HEIGHT)) + self._alpha = 1.0 + self._alpha_filter = FirstOrderFilter(1.0, 0.1, 1 / gui_app.target_fps) + self._fade_time = 0.0 + + def set_alpha(self, alpha: float) -> None: + self._alpha = alpha + self._fade_time = rl.get_time() + + def show_event(self): + super().show_event() + self._alpha = 1.0 + self._alpha_filter.x = 1.0 + self._fade_time = rl.get_time() + + def _render(self, _): + if rl.get_time() - self._fade_time > DISMISS_TIME_SECONDS: + self._alpha = 0.0 + alpha = self._alpha_filter.update(self._alpha) + + # white bar with black border + rl.draw_rectangle_rounded(self._rect, 1.0, 6, rl.Color(255, 255, 255, int(255 * 0.9 * alpha))) + rl.draw_rectangle_rounded_lines_ex(self._rect, 1.0, 6, 2, rl.Color(0, 0, 0, int(255 * 0.3 * alpha))) + + +class NavWidget(Widget, abc.ABC): + """ + A full screen widget that supports back navigation by swiping down from the top. + """ + BACK_TOUCH_AREA_PERCENTAGE = 0.65 + + def __init__(self): + super().__init__() + self._back_callback: Callable[[], None] | None = None + self._back_button_start_pos: MousePos | None = None + self._swiping_away = False # currently swiping away + self._can_swipe_away = True # swipe away is blocked after certain horizontal movement + + self._pos_filter = BounceFilter(0.0, 0.1, 1 / gui_app.target_fps, bounce=1) + self._playing_dismiss_animation = False + self._trigger_animate_in = False + self._back_enabled: bool | Callable[[], bool] = True + self._nav_bar = NavBar() + + self._nav_bar_y_filter = FirstOrderFilter(0.0, 0.1, 1 / gui_app.target_fps) + + self._set_up = False + + @property + def back_enabled(self) -> bool: + return self._back_enabled() if callable(self._back_enabled) else self._back_enabled + + def set_back_enabled(self, enabled: bool | Callable[[], bool]) -> None: + self._back_enabled = enabled + + def set_back_callback(self, callback: Callable[[], None]) -> None: + self._back_callback = callback + + def _handle_mouse_event(self, mouse_event: MouseEvent) -> None: + super()._handle_mouse_event(mouse_event) + + if not self.back_enabled: + self._back_button_start_pos = None + self._swiping_away = False + self._can_swipe_away = True + return + + if mouse_event.left_pressed: + # user is able to swipe away if starting near top of screen, or anywhere if scroller is at top + self._pos_filter.update_alpha(0.04) + in_dismiss_area = mouse_event.pos.y < self._rect.height * self.BACK_TOUCH_AREA_PERCENTAGE + + scroller_at_top = False + # TODO: -20? snapping in WiFi dialog can make offset not be positive at the top + if hasattr(self, '_scroller'): + scroller_at_top = self._scroller.scroll_panel.get_offset() >= -20 and not self._scroller._horizontal + elif hasattr(self, '_scroll_panel'): + scroller_at_top = self._scroll_panel.get_offset() >= -20 and not self._scroll_panel._horizontal + + if in_dismiss_area or scroller_at_top: + self._can_swipe_away = True + self._back_button_start_pos = mouse_event.pos + + elif mouse_event.left_down: + if self._back_button_start_pos is not None: + # block swiping away if too much horizontal or upward movement + horizontal_movement = abs(mouse_event.pos.x - self._back_button_start_pos.x) > BLOCK_SWIPE_AWAY_THRESHOLD + upward_movement = mouse_event.pos.y - self._back_button_start_pos.y < -BLOCK_SWIPE_AWAY_THRESHOLD + if not self._swiping_away and (horizontal_movement or upward_movement): + self._can_swipe_away = False + self._back_button_start_pos = None + + # block horizontal swiping if now swiping away + if self._can_swipe_away: + if mouse_event.pos.y - self._back_button_start_pos.y > START_DISMISSING_THRESHOLD: # type: ignore + self._swiping_away = True + + elif mouse_event.left_released: + self._pos_filter.update_alpha(0.1) + # if far enough, trigger back navigation callback + if self._back_button_start_pos is not None: + if mouse_event.pos.y - self._back_button_start_pos.y > SWIPE_AWAY_THRESHOLD: + self._playing_dismiss_animation = True + + self._back_button_start_pos = None + self._swiping_away = False + + def _update_state(self): + super()._update_state() + + # Disable self's scroller while swiping away + if not self._set_up: + self._set_up = True + if hasattr(self, '_scroller'): + original_enabled = self._scroller._enabled + self._scroller.set_enabled(lambda: not self._swiping_away and (original_enabled() if callable(original_enabled) else + original_enabled)) + elif hasattr(self, '_scroll_panel'): + original_enabled = self._scroll_panel.enabled + self._scroll_panel.set_enabled(lambda: not self._swiping_away and (original_enabled() if callable(original_enabled) else + original_enabled)) + + if self._trigger_animate_in: + self._pos_filter.x = self._rect.height + self._nav_bar_y_filter.x = -NAV_BAR_MARGIN - NAV_BAR_HEIGHT + self._trigger_animate_in = False + + new_y = 0.0 + + if self._back_button_start_pos is not None: + last_mouse_event = gui_app.last_mouse_event + # push entire widget as user drags it away + new_y = max(last_mouse_event.pos.y - self._back_button_start_pos.y, 0) + if new_y < SWIPE_AWAY_THRESHOLD: + new_y /= 2 # resistance until mouse release would dismiss widget + + if self._swiping_away: + self._nav_bar.set_alpha(1.0) + + if self._playing_dismiss_animation: + new_y = self._rect.height + DISMISS_PUSH_OFFSET + + new_y = round(self._pos_filter.update(new_y)) + if abs(new_y) < 1 and self._pos_filter.velocity.x == 0.0: + new_y = self._pos_filter.x = 0.0 + + if new_y > self._rect.height + DISMISS_PUSH_OFFSET - 10: + if self._back_callback is not None: + self._back_callback() + + self._playing_dismiss_animation = False + self._back_button_start_pos = None + self._swiping_away = False + + self.set_position(self._rect.x, new_y) + + def render(self, rect: rl.Rectangle = None) -> bool | int | None: + ret = super().render(rect) + + if self.back_enabled: + bar_x = self._rect.x + (self._rect.width - self._nav_bar.rect.width) / 2 + if self._back_button_start_pos is not None or self._playing_dismiss_animation: + self._nav_bar_y_filter.x = NAV_BAR_MARGIN + self._pos_filter.x + else: + self._nav_bar_y_filter.update(NAV_BAR_MARGIN) + + self._nav_bar.set_position(bar_x, round(self._nav_bar_y_filter.x)) + self._nav_bar.render() + + return ret + + def show_event(self): + super().show_event() + # FIXME: we don't know the height of the rect at first show_event since it's before the first render :( + # so we need this hacky bool for now + self._trigger_animate_in = True + self._nav_bar.show_event() diff --git a/system/ui/widgets/button.py b/system/ui/widgets/button.py index df7a52d1c..34b2a51a4 100644 --- a/system/ui/widgets/button.py +++ b/system/ui/widgets/button.py @@ -3,9 +3,10 @@ from enum import IntEnum import pyray as rl -from openpilot.system.ui.lib.application import FontWeight, MousePos +from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos from openpilot.system.ui.widgets import Widget -from openpilot.system.ui.widgets.label import Label +from openpilot.system.ui.widgets.label import Label, UnifiedLabel +from openpilot.common.filter_simple import FirstOrderFilter class ButtonStyle(IntEnum): @@ -175,9 +176,121 @@ class IconButton(Widget): def __init__(self, texture: rl.Texture): super().__init__() self._texture = texture + self._opacity_filter = FirstOrderFilter(1.0, 0.1, 1 / gui_app.target_fps) + self.set_rect(rl.Rectangle(0, 0, self._texture.width, self._texture.height)) + + def set_opacity(self, opacity: float, smooth: bool = False): + if smooth: + self._opacity_filter.update(opacity) + else: + self._opacity_filter.x = opacity def _render(self, rect: rl.Rectangle): - color = rl.Color(180, 180, 180, 150) if self.is_pressed else rl.WHITE + color = rl.Color(180, 180, 180, int(150 * self._opacity_filter.x)) if self.is_pressed else rl.WHITE + if not self.enabled: + color = rl.Color(255, 255, 255, int(255 * 0.9 * 0.35 * self._opacity_filter.x)) draw_x = rect.x + (rect.width - self._texture.width) / 2 draw_y = rect.y + (rect.height - self._texture.height) / 2 rl.draw_texture(self._texture, int(draw_x), int(draw_y), color) + + +class SmallCircleIconButton(Widget): + def __init__(self, icon_txt: rl.Texture): + super().__init__() + self.set_rect(rl.Rectangle(0, 0, 100, 100)) + self._opacity_filter = FirstOrderFilter(1.0, 0.1, 1 / gui_app.target_fps) + self._icon_bg_txt = gui_app.texture("icons_mici/setup/small_button.png", 100, 100) + self._icon_bg_pressed_txt = gui_app.texture("icons_mici/setup/small_button_pressed.png", 100, 100) + self._icon_txt = icon_txt + + def set_opacity(self, opacity: float, smooth: bool = False): + if smooth: + self._opacity_filter.update(opacity) + else: + self._opacity_filter.x = opacity + + def _render(self, _): + bg_txt = self._icon_bg_pressed_txt if self.is_pressed else self._icon_bg_txt + white = rl.Color(255, 255, 255, int(255 * self._opacity_filter.x)) + rl.draw_texture(bg_txt, int(self.rect.x), int(self.rect.y), white) + icon_x = self.rect.x + (self.rect.width - self._icon_txt.width) / 2 + icon_y = self.rect.y + (self.rect.height - self._icon_txt.height) / 2 + rl.draw_texture(self._icon_txt, int(icon_x), int(icon_y), white) + + +class SmallButton(Widget): + def __init__(self, text: str): + super().__init__() + self._opacity_filter = FirstOrderFilter(1.0, 0.1, 1 / gui_app.target_fps) + + self._load_assets() + + self._label = UnifiedLabel(text, 36, font_weight=FontWeight.MEDIUM, + text_color=rl.Color(255, 255, 255, int(255 * 0.9)), + alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER, + alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE) + + self._bg_disabled_txt = None + + def _load_assets(self): + self.set_rect(rl.Rectangle(0, 0, 194, 100)) + self._bg_txt = gui_app.texture("icons_mici/setup/reset/small_button.png", 194, 100) + self._bg_pressed_txt = gui_app.texture("icons_mici/setup/reset/small_button_pressed.png", 194, 100) + + def set_text(self, text: str): + self._label.set_text(text) + + def set_opacity(self, opacity: float, smooth: bool = False): + if smooth: + self._opacity_filter.update(opacity) + else: + self._opacity_filter.x = opacity + + def _render(self, _): + if not self.enabled and self._bg_disabled_txt is not None: + rl.draw_texture(self._bg_disabled_txt, int(self.rect.x), int(self.rect.y), rl.Color(255, 255, 255, int(255 * self._opacity_filter.x))) + elif self.is_pressed: + rl.draw_texture(self._bg_pressed_txt, int(self.rect.x), int(self.rect.y), rl.Color(255, 255, 255, int(255 * self._opacity_filter.x))) + else: + rl.draw_texture(self._bg_txt, int(self.rect.x), int(self.rect.y), rl.Color(255, 255, 255, int(255 * self._opacity_filter.x))) + + opacity = 0.9 if self.enabled else 0.35 + self._label.set_color(rl.Color(255, 255, 255, int(255 * opacity * self._opacity_filter.x))) + self._label.render(self._rect) + + +class SmallRedPillButton(SmallButton): + def _load_assets(self): + self.set_rect(rl.Rectangle(0, 0, 194, 100)) + self._bg_txt = gui_app.texture("icons_mici/setup/small_red_pill.png", 194, 100) + self._bg_pressed_txt = gui_app.texture("icons_mici/setup/small_red_pill_pressed.png", 194, 100) + + +class SmallerRoundedButton(SmallButton): + def _load_assets(self): + self.set_rect(rl.Rectangle(0, 0, 150, 100)) + self._bg_txt = gui_app.texture("icons_mici/setup/smaller_button.png", 150, 100) + self._bg_disabled_txt = gui_app.texture("icons_mici/setup/smaller_button_disabled.png", 150, 100) + self._bg_pressed_txt = gui_app.texture("icons_mici/setup/smaller_button_pressed.png", 150, 100) + + +class WideRoundedButton(SmallButton): + def _load_assets(self): + self.set_rect(rl.Rectangle(0, 0, 316, 100)) + self._bg_txt = gui_app.texture("icons_mici/setup/medium_button_bg.png", 316, 100) + self._bg_pressed_txt = gui_app.texture("icons_mici/setup/medium_button_pressed_bg.png", 316, 100) + + +class WidishRoundedButton(SmallButton): + def _load_assets(self): + self.set_rect(rl.Rectangle(0, 0, 250, 100)) + self._bg_txt = gui_app.texture("icons_mici/setup/widish_button.png", 250, 100) + self._bg_pressed_txt = gui_app.texture("icons_mici/setup/widish_button_pressed.png", 250, 100) + self._bg_disabled_txt = gui_app.texture("icons_mici/setup/widish_button_disabled.png", 250, 100) + + +class FullRoundedButton(SmallButton): + def _load_assets(self): + self.set_rect(rl.Rectangle(0, 0, 520, 100)) + self._bg_txt = gui_app.texture("icons_mici/setup/reset/wide_button.png", 520, 100) + self._bg_pressed_txt = gui_app.texture("icons_mici/setup/reset/wide_button_pressed.png", 520, 100) diff --git a/system/ui/widgets/confirm_dialog.py b/system/ui/widgets/confirm_dialog.py index 8c5ae0aa0..97618660b 100644 --- a/system/ui/widgets/confirm_dialog.py +++ b/system/ui/widgets/confirm_dialog.py @@ -6,7 +6,7 @@ from openpilot.system.ui.widgets.button import ButtonStyle, Button from openpilot.system.ui.widgets.label import Label from openpilot.system.ui.widgets.html_render import HtmlRenderer, ElementType from openpilot.system.ui.widgets import Widget -from openpilot.system.ui.widgets.scroller import Scroller +from openpilot.system.ui.widgets.scroller_tici import Scroller OUTER_MARGIN = 200 RICH_OUTER_MARGIN = 100 diff --git a/system/ui/widgets/label.py b/system/ui/widgets/label.py index 8d33ac2fd..35e2708e6 100644 --- a/system/ui/widgets/label.py +++ b/system/ui/widgets/label.py @@ -1,14 +1,15 @@ +from enum import IntEnum from collections.abc import Callable from itertools import zip_longest from typing import Union import pyray as rl from openpilot.system.ui.lib.application import gui_app, FontWeight, DEFAULT_TEXT_SIZE, DEFAULT_TEXT_COLOR, FONT_SCALE +from openpilot.system.ui.widgets import Widget from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.lib.utils import GuiStyleContext from openpilot.system.ui.lib.emoji import find_emoji, emoji_tex from openpilot.system.ui.lib.wrap_text import wrap_text -from openpilot.system.ui.widgets import Widget ICON_PADDING = 15 @@ -20,6 +21,171 @@ def _resolve_value(value, default=""): return value if value is not None else default +class ScrollState(IntEnum): + STARTING = 0 + SCROLLING = 1 + + +# TODO: merge anything new here to master +class MiciLabel(Widget): + def __init__(self, + text: str, + font_size: int = DEFAULT_TEXT_SIZE, + width: int = None, + 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_TOP, + spacing: int = 0, + line_height: int = None, + elide_right: bool = True, + wrap_text: bool = False, + scroll: bool = False): + super().__init__() + self.text = text + self.wrapped_text: list[str] = [] + self.font_size = font_size + self.width = width + self.color = color + self.font_weight = font_weight + self.alignment = alignment + self.alignment_vertical = alignment_vertical + self.spacing = spacing + self.line_height = line_height if line_height is not None else font_size + self.elide_right = elide_right + self.wrap_text = wrap_text + self._height = 0 + + # Scroll state + self.scroll = scroll + self._needs_scroll = False + self._scroll_offset = 0 + self._scroll_pause_t: float | None = None + self._scroll_state: ScrollState = ScrollState.STARTING + + assert not (self.scroll and self.wrap_text), "Cannot enable both scroll and wrap_text" + assert not (self.scroll and self.elide_right), "Cannot enable both scroll and elide_right" + + self.set_text(text) + + @property + def text_height(self): + return self._height + + def set_font_size(self, font_size: int): + self.font_size = font_size + self.set_text(self.text) + + def set_width(self, width: int): + self.width = width + self._rect.width = width + self.set_text(self.text) + + def set_text(self, txt: str): + self.text = txt + text_size = measure_text_cached(gui_app.font(self.font_weight), self.text, self.font_size, self.spacing) + if self.width is not None: + self._rect.width = self.width + else: + self._rect.width = text_size.x + + if self.wrap_text: + self.wrapped_text = wrap_text(gui_app.font(self.font_weight), self.text, self.font_size, int(self._rect.width)) + self._height = len(self.wrapped_text) * self.line_height + elif self.scroll: + self._needs_scroll = self.scroll and text_size.x > self._rect.width + self._rect.height = text_size.y + + def set_color(self, color: rl.Color): + self.color = color + + def set_font_weight(self, font_weight: FontWeight): + self.font_weight = font_weight + self.set_text(self.text) + + def _render(self, rect: rl.Rectangle): + # Only scissor when we know there is a single scrolling line + if self._needs_scroll: + rl.begin_scissor_mode(int(rect.x), int(rect.y), int(rect.width), int(rect.height)) + + font = gui_app.font(self.font_weight) + + text_y_offset = 0 + # Draw the text in the specified rectangle + lines = self.wrapped_text or [self.text] + if self.alignment_vertical == rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM: + lines = lines[::-1] + + for display_text in lines: + text_size = measure_text_cached(font, display_text, self.font_size, self.spacing) + + # Elide text to fit within the rectangle + if self.elide_right and text_size.x > rect.width: + ellipsis = "..." + left, right = 0, len(display_text) + while left < right: + mid = (left + right) // 2 + candidate = display_text[:mid] + ellipsis + candidate_size = measure_text_cached(font, candidate, self.font_size, self.spacing) + if candidate_size.x <= rect.width: + left = mid + 1 + else: + right = mid + display_text = display_text[: left - 1] + ellipsis if left > 0 else ellipsis + text_size = measure_text_cached(font, display_text, self.font_size, self.spacing) + + # Handle scroll state + elif self.scroll and self._needs_scroll: + if self._scroll_state == ScrollState.STARTING: + if self._scroll_pause_t is None: + self._scroll_pause_t = rl.get_time() + 2.0 + if rl.get_time() >= self._scroll_pause_t: + self._scroll_state = ScrollState.SCROLLING + self._scroll_pause_t = None + + elif self._scroll_state == ScrollState.SCROLLING: + self._scroll_offset -= 0.8 / 60. * gui_app.target_fps + # don't fully hide + if self._scroll_offset <= -text_size.x - self._rect.width / 3: + self._scroll_offset = 0 + self._scroll_state = ScrollState.STARTING + self._scroll_pause_t = None + + # Calculate horizontal position based on alignment + text_x = rect.x + { + rl.GuiTextAlignment.TEXT_ALIGN_LEFT: 0, + rl.GuiTextAlignment.TEXT_ALIGN_CENTER: (rect.width - text_size.x) / 2, + rl.GuiTextAlignment.TEXT_ALIGN_RIGHT: rect.width - text_size.x, + }.get(self.alignment, 0) + self._scroll_offset + + # Calculate vertical position based on alignment + text_y = rect.y + { + rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP: 0, + rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE: (rect.height - text_size.y) / 2, + rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM: rect.height - text_size.y, + }.get(self.alignment_vertical, 0) + text_y += text_y_offset + + rl.draw_text_ex(font, display_text, rl.Vector2(round(text_x), text_y), self.font_size, self.spacing, self.color) + # Draw 2nd instance for scrolling + if self._needs_scroll and self._scroll_state != ScrollState.STARTING: + text2_scroll_offset = text_size.x + self._rect.width / 3 + rl.draw_text_ex(font, display_text, rl.Vector2(round(text_x + text2_scroll_offset), text_y), self.font_size, self.spacing, self.color) + if self.alignment_vertical == rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM: + text_y_offset -= self.line_height + else: + text_y_offset += self.line_height + + if self._needs_scroll: + # draw black fade on left and right + fade_width = 20 + rl.draw_rectangle_gradient_h(int(rect.x + rect.width - fade_width), int(rect.y), fade_width, int(rect.height), rl.Color(0, 0, 0, 0), rl.BLACK) + if self._scroll_state != ScrollState.STARTING: + rl.draw_rectangle_gradient_h(int(rect.x), int(rect.y), fade_width, int(rect.height), rl.BLACK, rl.Color(0, 0, 0, 0)) + + rl.end_scissor_mode() + + # TODO: This should be a Widget class def gui_label( rect: rl.Rectangle, @@ -65,6 +231,7 @@ def gui_label( }.get(alignment_vertical, 0) # Draw the text in the specified rectangle + # TODO: add wrapping and proper centering for multiline text rl.draw_text_ex(font, display_text, rl.Vector2(text_x, text_y), font_size, 0, color) @@ -76,11 +243,12 @@ def gui_text_box( alignment: int = rl.GuiTextAlignment.TEXT_ALIGN_LEFT, alignment_vertical: int = rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP, font_weight: FontWeight = FontWeight.NORMAL, + line_scale: float = 1.0, ): styles = [ (rl.GuiControl.DEFAULT, rl.GuiControlProperty.TEXT_COLOR_NORMAL, rl.color_to_int(color)), (rl.GuiControl.DEFAULT, rl.GuiDefaultProperty.TEXT_SIZE, round(font_size * FONT_SCALE)), - (rl.GuiControl.DEFAULT, rl.GuiDefaultProperty.TEXT_LINE_SPACING, round(font_size * FONT_SCALE)), + (rl.GuiControl.DEFAULT, rl.GuiDefaultProperty.TEXT_LINE_SPACING, round(font_size * FONT_SCALE * line_scale)), (rl.GuiControl.DEFAULT, rl.GuiControlProperty.TEXT_ALIGNMENT, alignment), (rl.GuiControl.DEFAULT, rl.GuiDefaultProperty.TEXT_ALIGNMENT_VERTICAL, alignment_vertical), (rl.GuiControl.DEFAULT, rl.GuiDefaultProperty.TEXT_WRAP_MODE, rl.GuiTextWrapMode.TEXT_WRAP_WORD) @@ -107,6 +275,7 @@ class Label(Widget): text_color: rl.Color = DEFAULT_TEXT_COLOR, icon: Union[rl.Texture, None] = None, elide_right: bool = False, + line_scale=1.0, ): super().__init__() @@ -119,6 +288,7 @@ class Label(Widget): self._text_color = text_color self._icon = icon self._elide_right = elide_right + self._line_scale = line_scale self._text = text self.set_text(text) @@ -217,4 +387,339 @@ class Label(Widget): line_pos.x += self._font_size * FONT_SCALE prev_index = end rl.draw_text_ex(self._font, text[prev_index:], line_pos, self._font_size, 0, self._text_color) - text_pos.y += text_size.y or self._font_size * FONT_SCALE + text_pos.y += (text_size.y or self._font_size * FONT_SCALE) * self._line_scale + + +class UnifiedLabel(Widget): + """ + Unified label widget that combines functionality from gui_label, gui_text_box, Label, and MiciLabel. + + Supports: + - Emoji rendering + - Text wrapping + - Automatic eliding (single-line or multiline) + - Proper multiline vertical alignment + - Height calculation for layout purposes + """ + def __init__(self, + text: str | Callable[[], str], + font_size: int = DEFAULT_TEXT_SIZE, + font_weight: FontWeight = FontWeight.NORMAL, + text_color: rl.Color = DEFAULT_TEXT_COLOR, + alignment: int = rl.GuiTextAlignment.TEXT_ALIGN_LEFT, + alignment_vertical: int = rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP, + text_padding: int = 0, + max_width: int | None = None, + elide: bool = True, + wrap_text: bool = True, + line_height: float = 1.0, + letter_spacing: float = 0.0): + super().__init__() + self._text = text + self._font_size = font_size + self._font_weight = font_weight + self._font = gui_app.font(self._font_weight) + self._text_color = text_color + self._alignment = alignment + self._alignment_vertical = alignment_vertical + self._text_padding = text_padding + self._max_width = max_width + self._elide = elide + self._wrap_text = wrap_text + self._line_height = line_height * 0.9 + self._letter_spacing = letter_spacing # 0.1 = 10% + self._spacing_pixels = font_size * letter_spacing + + # Cached data + self._cached_text: str | None = None + self._cached_wrapped_lines: list[str] = [] + self._cached_line_sizes: list[rl.Vector2] = [] + self._cached_line_emojis: list[list[tuple[int, int, str]]] = [] + self._cached_total_height: float | None = None + self._cached_width: int = -1 + + # If max_width is set, initialize rect size for Scroller support + if max_width is not None: + self._rect.width = max_width + self._rect.height = self.get_content_height(max_width) + + def set_text(self, text: str | Callable[[], str]): + """Update the text content.""" + self._text = text + self._cached_text = None # Invalidate cache + + @property + def text(self) -> str: + """Get the current text content.""" + return str(_resolve_value(self._text)) + + def set_text_color(self, color: rl.Color): + """Update the text color.""" + self._text_color = color + + def set_color(self, color: rl.Color): + """Update the text color (alias for set_text_color).""" + self.set_text_color(color) + + def set_font_size(self, size: int): + """Update the font size.""" + self._font_size = size + self._spacing_pixels = size * self._letter_spacing # Recalculate spacing + self._cached_text = None # Invalidate cache + + def set_letter_spacing(self, letter_spacing: float): + """Update letter spacing (as percentage, e.g., 0.1 = 10%).""" + self._letter_spacing = letter_spacing + self._spacing_pixels = self._font_size * letter_spacing + self._cached_text = None # Invalidate cache + + def set_font_weight(self, font_weight: FontWeight): + """Update the font weight.""" + if self._font_weight != font_weight: + self._font_weight = font_weight + self._font = gui_app.font(self._font_weight) + self._cached_text = None # Invalidate cache + + def set_alignment(self, alignment: int): + """Update the horizontal text alignment.""" + self._alignment = alignment + + def set_alignment_vertical(self, alignment_vertical: int): + """Update the vertical text alignment.""" + self._alignment_vertical = alignment_vertical + + def set_max_width(self, max_width: int | None): + """Set the maximum width constraint for wrapping/eliding.""" + if self._max_width != max_width: + self._max_width = max_width + self._cached_text = None # Invalidate cache + # Update rect size for Scroller support + if max_width is not None: + self._rect.width = max_width + self._rect.height = self.get_content_height(max_width) + + def _update_text_cache(self, available_width: int): + """Update cached text processing data.""" + text = self.text + + # Check if cache is still valid + if (self._cached_text == text and + self._cached_width == available_width and + self._cached_wrapped_lines): + return + + self._cached_text = text + self._cached_width = available_width + + # Determine wrapping width + content_width = available_width - (self._text_padding * 2) + if content_width <= 0: + content_width = 1 + + # Wrap text if enabled + if self._wrap_text: + self._cached_wrapped_lines = wrap_text(self._font, text, self._font_size, content_width, self._spacing_pixels) + else: + # Split by newlines but don't wrap + self._cached_wrapped_lines = text.split('\n') if text else [""] + + # Elide lines if needed (for width constraint) + self._cached_wrapped_lines = [self._elide_line(line, content_width) for line in self._cached_wrapped_lines] + + # Process each line: measure and find emojis + self._cached_line_sizes = [] + self._cached_line_emojis = [] + + for line in self._cached_wrapped_lines: + emojis = find_emoji(line) + self._cached_line_emojis.append(emojis) + # Empty lines should still have height (use font size as line height) + if not line: + size = rl.Vector2(0, self._font_size * FONT_SCALE) + else: + size = measure_text_cached(self._font, line, self._font_size, self._spacing_pixels) + self._cached_line_sizes.append(size) + + # Calculate total height + # Each line contributes its measured height * line_height (matching Label's behavior) + # This includes spacing to the next line + if self._cached_line_sizes: + # Match the rendering logic: first line doesn't get line_height scaling + total_height = 0.0 + for idx, size in enumerate(self._cached_line_sizes): + if idx == 0: + total_height += size.y + else: + total_height += size.y * self._line_height + self._cached_total_height = total_height + else: + self._cached_total_height = 0.0 + + def _elide_line(self, line: str, max_width: int, force: bool = False) -> str: + """Elide a single line if it exceeds max_width. If force is True, always elide even if it fits.""" + if not self._elide and not force: + return line + + text_size = measure_text_cached(self._font, line, self._font_size, self._spacing_pixels) + if text_size.x <= max_width and not force: + return line + + ellipsis = "..." + # If force=True and line fits, just append ellipsis without truncating + if force and text_size.x <= max_width: + ellipsis_size = measure_text_cached(self._font, ellipsis, self._font_size, self._spacing_pixels) + if text_size.x + ellipsis_size.x <= max_width: + return line + ellipsis + # If line + ellipsis doesn't fit, need to truncate + # Fall through to binary search below + + left, right = 0, len(line) + while left < right: + mid = (left + right) // 2 + candidate = line[:mid] + ellipsis + candidate_size = measure_text_cached(self._font, candidate, self._font_size, self._spacing_pixels) + if candidate_size.x <= max_width: + left = mid + 1 + else: + right = mid + return line[:left - 1] + ellipsis if left > 0 else ellipsis + + def get_content_height(self, max_width: int) -> float: + """ + Returns the height needed for text at given max_width. + Similar to HtmlRenderer.get_total_height(). + """ + # Use max_width if provided, otherwise use self._max_width or a default + width = max_width if max_width > 0 else (self._max_width if self._max_width else 1000) + self._update_text_cache(width) + + if self._cached_total_height is not None: + return self._cached_total_height + return 0.0 + + def _render(self, rect: rl.Rectangle): + """Render the label.""" + if rect.width <= 0 or rect.height <= 0: + return + + # Determine available width + available_width = rect.width + if self._max_width is not None: + available_width = min(available_width, self._max_width) + + # Update text cache + self._update_text_cache(int(available_width)) + + if not self._cached_wrapped_lines: + return + + # Calculate which lines fit in the available height + visible_lines: list[str] = [] + visible_sizes: list[rl.Vector2] = [] + visible_emojis: list[list[tuple[int, int, str]]] = [] + + current_height = 0.0 + broke_early = False + for line, size, emojis in zip( + self._cached_wrapped_lines, + self._cached_line_sizes, + self._cached_line_emojis, + strict=True): + + # Calculate height needed for this line + # Each line contributes its height * line_height (matching Label's behavior) + line_height_needed = size.y * self._line_height + + # Check if this line fits + if current_height + line_height_needed > rect.height: + # This line doesn't fit + if len(visible_lines) == 0: + # First line doesn't fit by height - still show it (will be clipped by scissor if needed) + # Continue to add this line below + pass + else: + # We have visible lines and this one doesn't fit - mark that we broke early + broke_early = True + break + + visible_lines.append(line) + visible_sizes.append(size) + visible_emojis.append(emojis) + + current_height += line_height_needed + + # If we broke early (there are more lines that don't fit) and elide is enabled, elide the last visible line + if broke_early and len(visible_lines) > 0 and self._elide: + content_width = int(available_width - (self._text_padding * 2)) + if content_width <= 0: + content_width = 1 + + last_line_idx = len(visible_lines) - 1 + last_line = visible_lines[last_line_idx] + # Force elide the last line to show "..." even if it fits in width (to indicate more content) + elided = self._elide_line(last_line, content_width, force=True) + visible_lines[last_line_idx] = elided + visible_sizes[last_line_idx] = measure_text_cached(self._font, elided, self._font_size, self._spacing_pixels) + + if not visible_lines: + return + + # Calculate total visible text block height + # First line is not changed by line_height scaling + total_visible_height = 0.0 + for idx, size in enumerate(visible_sizes): + if idx == 0: + total_visible_height += size.y + else: + total_visible_height += size.y * self._line_height + + # Calculate vertical alignment offset + if self._alignment_vertical == rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP: + start_y = rect.y + elif self._alignment_vertical == rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM: + start_y = rect.y + rect.height - total_visible_height + else: # TEXT_ALIGN_MIDDLE + start_y = rect.y + (rect.height - total_visible_height) / 2 + + # Render each line + current_y = start_y + for idx, (line, size, emojis) in enumerate(zip(visible_lines, visible_sizes, visible_emojis, strict=True)): + # Calculate horizontal position + if self._alignment == rl.GuiTextAlignment.TEXT_ALIGN_LEFT: + line_x = rect.x + self._text_padding + elif self._alignment == rl.GuiTextAlignment.TEXT_ALIGN_CENTER: + line_x = rect.x + (rect.width - size.x) / 2 + elif self._alignment == rl.GuiTextAlignment.TEXT_ALIGN_RIGHT: + line_x = rect.x + rect.width - size.x - self._text_padding + else: + line_x = rect.x + self._text_padding + + # Render line with emojis + line_pos = rl.Vector2(line_x, current_y) + prev_index = 0 + + for start, end, emoji in emojis: + # Draw text before emoji + text_before = line[prev_index:start] + if text_before: + rl.draw_text_ex(self._font, text_before, line_pos, self._font_size, self._spacing_pixels, self._text_color) + width_before = measure_text_cached(self._font, text_before, self._font_size, self._spacing_pixels) + line_pos.x += width_before.x + + # Draw emoji + tex = emoji_tex(emoji) + emoji_scale = self._font_size / tex.height * FONT_SCALE + rl.draw_texture_ex(tex, line_pos, 0.0, emoji_scale, self._text_color) + # Emoji width is font_size * FONT_SCALE (as per measure_text_cached) + line_pos.x += self._font_size * FONT_SCALE + prev_index = end + + # Draw remaining text after last emoji + text_after = line[prev_index:] + if text_after: + rl.draw_text_ex(self._font, text_after, line_pos, self._font_size, self._spacing_pixels, self._text_color) + + # Move to next line (if not last line) + if idx < len(visible_lines) - 1: + # Use current line's height * line_height for spacing to next line + current_y += size.y * self._line_height diff --git a/system/ui/widgets/mici_keyboard.py b/system/ui/widgets/mici_keyboard.py new file mode 100644 index 000000000..2eb24cdb9 --- /dev/null +++ b/system/ui/widgets/mici_keyboard.py @@ -0,0 +1,388 @@ +from enum import IntEnum +import pyray as rl +import numpy as np +from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos, MouseEvent +from openpilot.system.ui.lib.text_measure import measure_text_cached +from openpilot.system.ui.widgets import Widget +from openpilot.common.filter_simple import BounceFilter + +CHAR_FONT_SIZE = 42 +CHAR_NEAR_FONT_SIZE = CHAR_FONT_SIZE * 2 +SELECTED_CHAR_FONT_SIZE = 128 +CHAR_CAPS_FONT_SIZE = 38 # TODO: implement this +NUMBER_LAYER_SWITCH_FONT_SIZE = 24 +KEYBOARD_COLUMN_PADDING = 33 +KEYBOARD_ROW_PADDING = {0: 44, 1: 33, 2: 44} # TODO: 2 should be 116 with extra control keys added in + +KEY_TOUCH_AREA_OFFSET = 10 # px +KEY_DRAG_HYSTERESIS = 5 # px +KEY_MIN_ANIMATION_TIME = 0.075 # s + +DEBUG = False +ANIMATION_SCALE = 0.65 + + +def zip_repeat(a, b): + la, lb = len(a), len(b) + for i in range(max(la, lb)): + yield (a[i] if i < la else a[-1], + b[i] if i < lb else b[-1]) + + +def fast_euclidean_distance(dx, dy): + # https://en.wikibooks.org/wiki/Algorithms/Distance_approximations + max_d, min_d = abs(dx), abs(dy) + if max_d < min_d: + max_d, min_d = min_d, max_d + return 0.941246 * max_d + 0.41 * min_d + + +class Key(Widget): + def __init__(self, char: str): + super().__init__() + self.char = char + self._font = gui_app.font(FontWeight.SEMI_BOLD) + self._x_filter = BounceFilter(0.0, 0.1 * ANIMATION_SCALE, 1 / gui_app.target_fps) + self._y_filter = BounceFilter(0.0, 0.1 * ANIMATION_SCALE, 1 / gui_app.target_fps) + self._size_filter = BounceFilter(CHAR_FONT_SIZE, 0.1 * ANIMATION_SCALE, 1 / gui_app.target_fps) + self._alpha_filter = BounceFilter(1.0, 0.075 * ANIMATION_SCALE, 1 / gui_app.target_fps) + + self._color = rl.Color(255, 255, 255, 255) + + self._position_initialized = False + self.original_position = rl.Vector2(0, 0) + + def set_position(self, x: float, y: float, smooth: bool = True): + # TODO: swipe up from NavWidget has the keys lag behind other elements a bit + if not self._position_initialized: + self._x_filter.x = x + self._y_filter.x = y + # keep track of original position so dragging around feels consistent. also move touch area down a bit + self.original_position = rl.Vector2(x, y + KEY_TOUCH_AREA_OFFSET) + self._position_initialized = True + + if not smooth: + self._x_filter.x = x + self._y_filter.x = y + + self._rect.x = self._x_filter.update(x) + self._rect.y = self._y_filter.update(y) + + def set_alpha(self, alpha: float): + self._alpha_filter.update(alpha) + + def get_position(self) -> tuple[float, float]: + return self._rect.x, self._rect.y + + def _update_state(self): + self._color.a = min(int(255 * self._alpha_filter.x), 255) + + def _render(self, _): + # center char at rect position + text_size = measure_text_cached(self._font, self.char, self._get_font_size()) + x = self._rect.x + self._rect.width / 2 - text_size.x / 2 + y = self._rect.y + self._rect.height / 2 - text_size.y / 2 + rl.draw_text_ex(self._font, self.char, (x, y), self._get_font_size(), 0, self._color) + + if DEBUG: + rl.draw_circle(int(self._rect.x), int(self._rect.y), 5, rl.RED) # Debug: draw circle around key + rl.draw_rectangle_lines_ex(self._rect, 2, rl.RED) + + def set_font_size(self, size: float): + self._size_filter.update(size) + + def _get_font_size(self) -> int: + return int(round(self._size_filter.x)) + + +class SmallKey(Key): + def __init__(self, chars: str): + super().__init__(chars) + self._size_filter.x = NUMBER_LAYER_SWITCH_FONT_SIZE + + def set_font_size(self, size: float): + self._size_filter.update(size * (NUMBER_LAYER_SWITCH_FONT_SIZE / CHAR_FONT_SIZE)) + + +class IconKey(Key): + def __init__(self, icon: str, vertical_align: str = "center", char: str = ""): + super().__init__(char) + self._icon = gui_app.texture(icon, 38, 38) + self._vertical_align = vertical_align + + def set_icon(self, icon: str): + self._icon = gui_app.texture(icon, 38, 38) + + def _render(self, _): + scale = np.interp(self._size_filter.x, [CHAR_FONT_SIZE, CHAR_NEAR_FONT_SIZE], [1, 1.5]) + + if self._vertical_align == "center": + dest_rec = rl.Rectangle(self._rect.x + (self._rect.width - self._icon.width * scale) / 2, + self._rect.y + (self._rect.height - self._icon.height * scale) / 2, + self._icon.width * scale, self._icon.height * scale) + src_rec = rl.Rectangle(0, 0, self._icon.width, self._icon.height) + rl.draw_texture_pro(self._icon, src_rec, dest_rec, rl.Vector2(0, 0), 0, self._color) + + rl.draw_texture_pro(self._icon, src_rec, dest_rec, rl.Vector2(0, 0), 0, self._color) + + elif self._vertical_align == "bottom": + dest_rec = rl.Rectangle(self._rect.x + (self._rect.width - self._icon.width * scale) / 2, self._rect.y, + self._icon.width * scale, self._icon.height * scale) + src_rec = rl.Rectangle(0, 0, self._icon.width, self._icon.height) + rl.draw_texture_pro(self._icon, src_rec, dest_rec, rl.Vector2(0, 0), 0, self._color) + + if DEBUG: + rl.draw_circle(int(self._rect.x), int(self._rect.y), 5, rl.RED) # Debug: draw circle around key + rl.draw_rectangle_lines_ex(self._rect, 2, rl.RED) + + +class CapsState(IntEnum): + LOWER = 0 + UPPER = 1 + LOCK = 2 + + +class MiciKeyboard(Widget): + def __init__(self): + super().__init__() + + lower_chars = [ + "qwertyuiop", + "asdfghjkl", + "zxcvbnm", + ] + upper_chars = ["".join([char.upper() for char in row]) for row in lower_chars] + special_chars = [ + "1234567890", + "-/:;()$&@\"", + "~.,?!'#%", + ] + super_special_chars = [ + "1234567890", + "`[]{}^*+=_", + "\\|<>¥€£•", + ] + + self._lower_keys = [[Key(char) for char in row] for row in lower_chars] + self._upper_keys = [[Key(char) for char in row] for row in upper_chars] + self._special_keys = [[Key(char) for char in row] for row in special_chars] + self._super_special_keys = [[Key(char) for char in row] for row in super_special_chars] + + # control keys + self._space_key = IconKey("icons_mici/settings/keyboard/space.png", char=" ", vertical_align="bottom") + self._caps_key = IconKey("icons_mici/settings/keyboard/caps_lower.png") + # these two are in different places on some layouts + self._123_key, self._123_key2 = SmallKey("123"), SmallKey("123") + self._abc_key = SmallKey("abc") + self._super_special_key = SmallKey("#+=") + + # insert control keys + for keys in (self._lower_keys, self._upper_keys): + keys[2].insert(0, self._caps_key) + keys[2].append(self._123_key) + + for keys in (self._lower_keys, self._upper_keys, self._special_keys, self._super_special_keys): + keys[1].append(self._space_key) + + for keys in (self._special_keys, self._super_special_keys): + keys[2].append(self._abc_key) + + self._special_keys[2].insert(0, self._super_special_key) + self._super_special_keys[2].insert(0, self._123_key2) + + # set initial keys + self._current_keys: list[list[Key]] = [] + self._set_keys(self._lower_keys) + self._caps_state = CapsState.LOWER + self._initialized = False + + self._load_images() + + self._closest_key: tuple[Key | None, float] = None, float('inf') + self._selected_key_t: float | None = None # time key was initially selected + self._unselect_key_t: float | None = None # time to unselect key after release + self._dragging_on_keyboard = False + + self._text: str = "" + + self._bg_scale_filter = BounceFilter(1.0, 0.1 * ANIMATION_SCALE, 1 / gui_app.target_fps) + + def get_candidate_character(self) -> str: + # return str of character about to be added to text + key = self._closest_key[0] + return key.char if key is not None and key.__class__ is Key and self._dragging_on_keyboard else "" + + def get_keyboard_height(self) -> int: + return int(self._txt_bg.height) + + def _load_images(self): + self._txt_bg = gui_app.texture("icons_mici/settings/keyboard/keyboard_background.png", 520, 170, keep_aspect_ratio=False) + + def _set_keys(self, keys: list[list[Key]]): + # inherit previous keys' positions to fix switching animation + for current_row, row in zip(self._current_keys, keys, strict=False): + # not all layouts have the same number of keys + for current_key, key in zip_repeat(current_row, row): + current_pos = current_key.get_position() + key.set_position(current_pos[0], current_pos[1], smooth=False) + + self._current_keys = keys + + def set_text(self, text: str): + self._text = text + + def text(self) -> str: + return self._text + + def _handle_mouse_event(self, mouse_event: MouseEvent) -> None: + keyboard_pos_y = self._rect.y + self._rect.height - self._txt_bg.height + if mouse_event.left_pressed: + if mouse_event.pos.y > keyboard_pos_y: + self._dragging_on_keyboard = True + elif mouse_event.left_released: + self._dragging_on_keyboard = False + + if mouse_event.left_down and self._dragging_on_keyboard: + self._closest_key = self._get_closest_key() + if self._selected_key_t is None: + self._selected_key_t = rl.get_time() + + # unselect key temporarily if mouse goes above keyboard + if mouse_event.pos.y <= keyboard_pos_y: + self._closest_key = (None, float('inf')) + + if DEBUG: + print('HANDLE MOUSE EVENT', mouse_event, self._closest_key[0].char if self._closest_key[0] else 'None') + + def _get_closest_key(self) -> tuple[Key | None, float]: + closest_key: tuple[Key | None, float] = (None, float('inf')) + for row in self._current_keys: + for key in row: + mouse_pos = gui_app.last_mouse_event.pos + # approximate distance for comparison is accurate enough + dist = abs(key.original_position.x - mouse_pos.x) + abs(key.original_position.y - mouse_pos.y) + if dist < closest_key[1]: + if self._closest_key[0] is None or key is self._closest_key[0] or dist < self._closest_key[1] - KEY_DRAG_HYSTERESIS: + closest_key = (key, dist) + return closest_key + + def _set_uppercase(self, cycle: bool): + self._set_keys(self._upper_keys if cycle else self._lower_keys) + if not cycle: + self._caps_state = CapsState.LOWER + self._caps_key.set_icon("icons_mici/settings/keyboard/caps_lower.png") + else: + if self._caps_state == CapsState.LOWER: + self._caps_state = CapsState.UPPER + self._caps_key.set_icon("icons_mici/settings/keyboard/caps_upper.png") + elif self._caps_state == CapsState.UPPER: + self._caps_state = CapsState.LOCK + self._caps_key.set_icon("icons_mici/settings/keyboard/caps_lock.png") + else: + self._set_uppercase(False) + + def _handle_mouse_release(self, mouse_pos: MousePos): + if self._closest_key[0] is not None: + if self._closest_key[0] == self._caps_key: + self._set_uppercase(True) + elif self._closest_key[0] in (self._123_key, self._123_key2): + self._set_keys(self._special_keys) + elif self._closest_key[0] == self._abc_key: + self._set_uppercase(False) + elif self._closest_key[0] == self._super_special_key: + self._set_keys(self._super_special_keys) + else: + self._text += self._closest_key[0].char + + # Reset caps state + if self._caps_state == CapsState.UPPER: + self._set_uppercase(False) + + # ensure minimum selected animation time + key_selected_dt = rl.get_time() - (self._selected_key_t or 0) + cur_t = rl.get_time() + self._unselect_key_t = cur_t + KEY_MIN_ANIMATION_TIME if (key_selected_dt < KEY_MIN_ANIMATION_TIME) else cur_t + + def backspace(self): + if self._text: + self._text = self._text[:-1] + + def space(self): + self._text += ' ' + + def _update_state(self): + # unselect key after animation plays + if self._unselect_key_t is not None and rl.get_time() > self._unselect_key_t: + self._closest_key = (None, float('inf')) + self._unselect_key_t = None + self._selected_key_t = None + + def _lay_out_keys(self, bg_x, bg_y, keys: list[list[Key]]): + key_rect = rl.Rectangle(bg_x, bg_y, self._txt_bg.width, self._txt_bg.height) + for row_idx, row in enumerate(keys): + padding = KEYBOARD_ROW_PADDING[row_idx] + step_y = (key_rect.height - 2 * KEYBOARD_COLUMN_PADDING) / (len(keys) - 1) + for key_idx, key in enumerate(row): + key_x = key_rect.x + padding + key_idx * ((key_rect.width - 2 * padding) / (len(row) - 1)) + key_y = key_rect.y + KEYBOARD_COLUMN_PADDING + row_idx * step_y + + if self._closest_key[0] is None: + key.set_alpha(1.0) + key.set_font_size(CHAR_FONT_SIZE) + elif key == self._closest_key[0]: + # push key up with a max and inward so user can see key easier + key_y = max(key_y - 120, 40) + key_x += np.interp(key_x, [self._rect.x, self._rect.x + self._rect.width], [100, -100]) + key.set_alpha(1.0) + key.set_font_size(SELECTED_CHAR_FONT_SIZE) + + # draw black circle behind selected key + rl.draw_circle_gradient(int(key_x + key.rect.width / 2), int(key_y + key.rect.height / 2), + SELECTED_CHAR_FONT_SIZE, rl.Color(0, 0, 0, 225), rl.BLANK) + else: + # move other keys away from selected key a bit + dx = key.original_position.x - self._closest_key[0].original_position.x + dy = key.original_position.y - self._closest_key[0].original_position.y + distance_from_selected_key = fast_euclidean_distance(dx, dy) + + inv = 1 / (distance_from_selected_key or 1.0) + ux = dx * inv + uy = dy * inv + + # NOTE: hardcode to 20 to get entire keyboard to move + push_pixels = np.interp(distance_from_selected_key, [0, 250], [20, 0]) + key_x += ux * push_pixels + key_y += uy * push_pixels + + # TODO: slow enough to use an approximation or nah? also caching might work + font_size = np.interp(distance_from_selected_key, [0, 150], [CHAR_NEAR_FONT_SIZE, CHAR_FONT_SIZE]) + + key_alpha = np.interp(distance_from_selected_key, [0, 100], [1.0, 0.35]) + key.set_alpha(key_alpha) + key.set_font_size(font_size) + + # TODO: I like the push amount, so we should clip the pos inside the keyboard rect + key.set_position(key_x, key_y) + + def _render(self, _): + # draw bg + bg_x = self._rect.x + (self._rect.width - self._txt_bg.width) / 2 + bg_y = self._rect.y + self._rect.height - self._txt_bg.height + + scale = self._bg_scale_filter.update(1.0307692307692307 if self._closest_key[0] is not None else 1.0) + src_rec = rl.Rectangle(0, 0, self._txt_bg.width, self._txt_bg.height) + dest_rec = rl.Rectangle(self._rect.x + self._rect.width / 2 - self._txt_bg.width * scale / 2, bg_y, + self._txt_bg.width * scale, self._txt_bg.height) + + rl.draw_texture_pro(self._txt_bg, src_rec, dest_rec, rl.Vector2(0, 0), 0.0, rl.WHITE) + + # draw keys + if not self._initialized: + for keys in (self._lower_keys, self._upper_keys, self._special_keys, self._super_special_keys): + self._lay_out_keys(bg_x, bg_y, keys) + self._initialized = True + + self._lay_out_keys(bg_x, bg_y, self._current_keys) + for row in self._current_keys: + for key in row: + key.render() diff --git a/system/ui/widgets/network.py b/system/ui/widgets/network.py index 592c9de97..f41a04c24 100644 --- a/system/ui/widgets/network.py +++ b/system/ui/widgets/network.py @@ -12,7 +12,7 @@ from openpilot.system.ui.widgets.button import ButtonStyle, Button from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog from openpilot.system.ui.widgets.keyboard import Keyboard from openpilot.system.ui.widgets.label import gui_label -from openpilot.system.ui.widgets.scroller import Scroller +from openpilot.system.ui.widgets.scroller_tici import Scroller from openpilot.system.ui.widgets.list_view import ButtonAction, ListItem, MultipleButtonAction, ToggleAction, button_item, text_item # These are only used for AdvancedNetworkSettings, standalone apps just need WifiManagerUI diff --git a/system/ui/widgets/option_dialog.py b/system/ui/widgets/option_dialog.py index 3b2201164..62578d1cf 100644 --- a/system/ui/widgets/option_dialog.py +++ b/system/ui/widgets/option_dialog.py @@ -4,7 +4,7 @@ from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.widgets import Widget, DialogResult from openpilot.system.ui.widgets.button import Button, ButtonStyle from openpilot.system.ui.widgets.label import gui_label -from openpilot.system.ui.widgets.scroller import Scroller +from openpilot.system.ui.widgets.scroller_tici import Scroller # Constants MARGIN = 50 diff --git a/system/ui/widgets/scroller.py b/system/ui/widgets/scroller.py index a843010d5..fb7f635be 100644 --- a/system/ui/widgets/scroller.py +++ b/system/ui/widgets/scroller.py @@ -1,10 +1,21 @@ import pyray as rl -from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel +import numpy as np +from collections.abc import Callable + +from openpilot.common.filter_simple import FirstOrderFilter, BounceFilter +from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.lib.scroll_panel2 import GuiScrollPanel2, ScrollState from openpilot.system.ui.widgets import Widget -ITEM_SPACING = 40 +ITEM_SPACING = 20 LINE_COLOR = rl.GRAY LINE_PADDING = 40 +ANIMATION_SCALE = 0.6 + +MIN_ZOOM_ANIMATION_TIME = 0.075 # seconds +DO_ZOOM = False +DO_JELLO = False +SCROLL_BAR = False class LineSeparator(Widget): @@ -23,21 +34,131 @@ class LineSeparator(Widget): class Scroller(Widget): - def __init__(self, items: list[Widget], spacing: int = ITEM_SPACING, line_separator: bool = False, pad_end: bool = True): + def __init__(self, items: list[Widget], horizontal: bool = True, snap_items: bool = True, spacing: int = ITEM_SPACING, + line_separator: bool = False, pad_start: int = ITEM_SPACING, pad_end: int = ITEM_SPACING): super().__init__() self._items: list[Widget] = [] + self._horizontal = horizontal + self._snap_items = snap_items self._spacing = spacing self._line_separator = LineSeparator() if line_separator else None + self._pad_start = pad_start self._pad_end = pad_end - self.scroll_panel = GuiScrollPanel() + self._reset_scroll_at_show = True + + self._scrolling_to: float | None = None + self._scroll_filter = FirstOrderFilter(0.0, 0.1, 1 / gui_app.target_fps) + self._zoom_filter = FirstOrderFilter(1.0, 0.2, 1 / gui_app.target_fps) + self._zoom_out_t: float = 0.0 + + self._item_pos_filter = BounceFilter(0.0, 0.05, 1 / gui_app.target_fps) + + # when not pressed, snap to closest item to be center + self._scroll_snap_filter = FirstOrderFilter(0.0, 0.05, 1 / gui_app.target_fps) + + self.scroll_panel = GuiScrollPanel2(self._horizontal, handle_out_of_bounds=not self._snap_items) + self._scroll_enabled: bool | Callable[[], bool] = True + + self._txt_scroll_indicator = gui_app.texture("icons_mici/settings/vertical_scroll_indicator.png", 40, 80) for item in items: self.add_widget(item) + def set_reset_scroll_at_show(self, scroll: bool): + self._reset_scroll_at_show = scroll + + def scroll_to(self, pos: float, smooth: bool = False): + # already there + if abs(pos) < 1: + return + + # FIXME: the padding correction doesn't seem correct + scroll_offset = self.scroll_panel.get_offset() - pos + self._pad_end + if smooth: + self._scrolling_to = scroll_offset + else: + self.scroll_panel.set_offset(scroll_offset) + + @property + def is_auto_scrolling(self) -> bool: + return self._scrolling_to is not None + def add_widget(self, item: Widget) -> None: self._items.append(item) - item.set_touch_valid_callback(self.scroll_panel.is_touch_valid) + item.set_touch_valid_callback(lambda: self.scroll_panel.is_touch_valid() and self.enabled) + + def set_scrolling_enabled(self, enabled: bool | Callable[[], bool]) -> None: + """Set whether scrolling is enabled (does not affect widget enabled state).""" + self._scroll_enabled = enabled + + def _update_state(self): + if DO_ZOOM: + if self._scrolling_to is not None or self.scroll_panel.state != ScrollState.STEADY: + self._zoom_out_t = rl.get_time() + MIN_ZOOM_ANIMATION_TIME + self._zoom_filter.update(0.85) + else: + if self._zoom_out_t is not None: + if rl.get_time() > self._zoom_out_t: + self._zoom_filter.update(1.0) + else: + self._zoom_filter.update(0.85) + + # Cancel auto-scroll if user starts manually scrolling + if self._scrolling_to is not None and (self.scroll_panel.state == ScrollState.PRESSED or self.scroll_panel.state == ScrollState.MANUAL_SCROLL): + self._scrolling_to = None + + if self._scrolling_to is not None: + self._scroll_filter.update(self._scrolling_to) + self.scroll_panel.set_offset(self._scroll_filter.x) + + if abs(self._scroll_filter.x - self._scrolling_to) < 1: + self.scroll_panel.set_offset(self._scrolling_to) + self._scrolling_to = None + else: + # keep current scroll position up to date + self._scroll_filter.x = self.scroll_panel.get_offset() + + def _get_scroll(self, visible_items: list[Widget], content_size: float) -> float: + scroll_enabled = self._scroll_enabled() if callable(self._scroll_enabled) else self._scroll_enabled + self.scroll_panel.set_enabled(scroll_enabled and self.enabled) + self.scroll_panel.update(self._rect, content_size) + if not self._snap_items: + return self.scroll_panel.get_offset() + + # Snap closest item to center + center_pos = self._rect.x + self._rect.width / 2 if self._horizontal else self._rect.y + self._rect.height / 2 + closest_delta_pos = float('inf') + scroll_snap_idx: int | None = None + for idx, item in enumerate(visible_items): + if self._horizontal: + delta_pos = (item.rect.x + item.rect.width / 2) - center_pos + else: + delta_pos = (item.rect.y + item.rect.height / 2) - center_pos + if abs(delta_pos) < abs(closest_delta_pos): + closest_delta_pos = delta_pos + scroll_snap_idx = idx + + if scroll_snap_idx is not None: + snap_item = visible_items[scroll_snap_idx] + if self.is_pressed: + # no snapping until released + self._scroll_snap_filter.x = 0 + else: + # TODO: this doesn't handle two small buttons at the edges well + if self._horizontal: + snap_delta_pos = (center_pos - (snap_item.rect.x + snap_item.rect.width / 2)) / 10 + snap_delta_pos = min(snap_delta_pos, -self.scroll_panel.get_offset() / 10) + snap_delta_pos = max(snap_delta_pos, (self._rect.width - self.scroll_panel.get_offset() - content_size) / 10) + else: + snap_delta_pos = (center_pos - (snap_item.rect.y + snap_item.rect.height / 2)) / 10 + snap_delta_pos = min(snap_delta_pos, -self.scroll_panel.get_offset() / 10) + snap_delta_pos = max(snap_delta_pos, (self._rect.height - self.scroll_panel.get_offset() - content_size) / 10) + self._scroll_snap_filter.update(snap_delta_pos) + + self.scroll_panel.set_offset(self.scroll_panel.get_offset() + self._scroll_snap_filter.x) + + return self.scroll_panel.get_offset() def _render(self, _): # TODO: don't draw items that are not in the viewport @@ -49,38 +170,78 @@ class Scroller(Widget): for i in range(1, len(visible_items)): visible_items.insert(l - i, self._line_separator) - content_height = sum(item.rect.height for item in visible_items) + self._spacing * (len(visible_items)) - if not self._pad_end: - content_height -= self._spacing - scroll = self.scroll_panel.update(self._rect, rl.Rectangle(0, 0, self._rect.width, content_height)) + content_size = sum(item.rect.width if self._horizontal else item.rect.height for item in visible_items) + content_size += self._spacing * (len(visible_items) - 1) + content_size += self._pad_start + self._pad_end + + scroll_offset = self._get_scroll(visible_items, content_size) rl.begin_scissor_mode(int(self._rect.x), int(self._rect.y), int(self._rect.width), int(self._rect.height)) - cur_height = 0 - for idx, item in enumerate(visible_items): - if not item.is_visible: - continue + self._item_pos_filter.update(scroll_offset) - # Nicely lay out items vertically - x = self._rect.x - y = self._rect.y + cur_height + self._spacing * (idx != 0) - cur_height += item.rect.height + self._spacing * (idx != 0) + cur_pos = 0 + for idx, item in enumerate(visible_items): + spacing = self._spacing if (idx > 0) else self._pad_start + # Nicely lay out items horizontally/vertically + if self._horizontal: + x = self._rect.x + cur_pos + spacing + y = self._rect.y + (self._rect.height - item.rect.height) / 2 + cur_pos += item.rect.width + spacing + else: + x = self._rect.x + (self._rect.width - item.rect.width) / 2 + y = self._rect.y + cur_pos + spacing + cur_pos += item.rect.height + spacing # Consider scroll - y += scroll + if self._horizontal: + x += scroll_offset + else: + y += scroll_offset + + # Add some jello effect when scrolling + if DO_JELLO: + if self._horizontal: + cx = self._rect.x + self._rect.width / 2 + jello_offset = scroll_offset - np.interp(x + item.rect.width / 2, + [self._rect.x, cx, self._rect.x + self._rect.width], + [self._item_pos_filter.x, scroll_offset, self._item_pos_filter.x]) + x -= np.clip(jello_offset, -20, 20) + else: + cy = self._rect.y + self._rect.height / 2 + jello_offset = scroll_offset - np.interp(y + item.rect.height / 2, + [self._rect.y, cy, self._rect.y + self._rect.height], + [self._item_pos_filter.x, scroll_offset, self._item_pos_filter.x]) + y -= np.clip(jello_offset, -20, 20) # Update item state - item.set_position(x, y) + item.set_position(round(x), round(y)) # round to prevent jumping when settling item.set_parent_rect(self._rect) + + # Scale each element around its own origin when scrolling + scale = self._zoom_filter.x + rl.rl_push_matrix() + rl.rl_scalef(scale, scale, 1.0) + rl.rl_translatef((1 - scale) * (x + item.rect.width / 2) / scale, + (1 - scale) * (y + item.rect.height / 2) / scale, 0) item.render() + rl.rl_pop_matrix() + + # Draw scroll indicator + if SCROLL_BAR and not self._horizontal and len(visible_items) > 0: + _real_content_size = content_size - self._rect.height + self._txt_scroll_indicator.height + scroll_bar_y = -scroll_offset / _real_content_size * self._rect.height + scroll_bar_y = min(max(scroll_bar_y, self._rect.y), self._rect.y + self._rect.height - self._txt_scroll_indicator.height) + rl.draw_texture_ex(self._txt_scroll_indicator, rl.Vector2(self._rect.x, scroll_bar_y), 0, 1.0, rl.WHITE) rl.end_scissor_mode() def show_event(self): super().show_event() - # Reset to top - self.scroll_panel.set_offset(0) + if self._reset_scroll_at_show: + self.scroll_to(self.scroll_panel.get_offset()) + for item in self._items: item.show_event() diff --git a/system/ui/widgets/scroller_tici.py b/system/ui/widgets/scroller_tici.py new file mode 100644 index 000000000..a843010d5 --- /dev/null +++ b/system/ui/widgets/scroller_tici.py @@ -0,0 +1,90 @@ +import pyray as rl +from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel +from openpilot.system.ui.widgets import Widget + +ITEM_SPACING = 40 +LINE_COLOR = rl.GRAY +LINE_PADDING = 40 + + +class LineSeparator(Widget): + def __init__(self, height: int = 1): + super().__init__() + self._rect = rl.Rectangle(0, 0, 0, height) + + def set_parent_rect(self, parent_rect: rl.Rectangle) -> None: + super().set_parent_rect(parent_rect) + self._rect.width = parent_rect.width + + def _render(self, _): + rl.draw_line(int(self._rect.x) + LINE_PADDING, int(self._rect.y), + int(self._rect.x + self._rect.width) - LINE_PADDING, int(self._rect.y), + LINE_COLOR) + + +class Scroller(Widget): + def __init__(self, items: list[Widget], spacing: int = ITEM_SPACING, line_separator: bool = False, pad_end: bool = True): + super().__init__() + self._items: list[Widget] = [] + self._spacing = spacing + self._line_separator = LineSeparator() if line_separator else None + self._pad_end = pad_end + + self.scroll_panel = GuiScrollPanel() + + for item in items: + self.add_widget(item) + + def add_widget(self, item: Widget) -> None: + self._items.append(item) + item.set_touch_valid_callback(self.scroll_panel.is_touch_valid) + + def _render(self, _): + # TODO: don't draw items that are not in the viewport + visible_items = [item for item in self._items if item.is_visible] + + # Add line separator between items + if self._line_separator is not None: + l = len(visible_items) + for i in range(1, len(visible_items)): + visible_items.insert(l - i, self._line_separator) + + content_height = sum(item.rect.height for item in visible_items) + self._spacing * (len(visible_items)) + if not self._pad_end: + content_height -= self._spacing + scroll = self.scroll_panel.update(self._rect, rl.Rectangle(0, 0, self._rect.width, content_height)) + + rl.begin_scissor_mode(int(self._rect.x), int(self._rect.y), + int(self._rect.width), int(self._rect.height)) + + cur_height = 0 + for idx, item in enumerate(visible_items): + if not item.is_visible: + continue + + # Nicely lay out items vertically + x = self._rect.x + y = self._rect.y + cur_height + self._spacing * (idx != 0) + cur_height += item.rect.height + self._spacing * (idx != 0) + + # Consider scroll + y += scroll + + # Update item state + item.set_position(x, y) + item.set_parent_rect(self._rect) + item.render() + + rl.end_scissor_mode() + + def show_event(self): + super().show_event() + # Reset to top + self.scroll_panel.set_offset(0) + for item in self._items: + item.show_event() + + def hide_event(self): + super().hide_event() + for item in self._items: + item.hide_event() diff --git a/system/ui/widgets/slider.py b/system/ui/widgets/slider.py new file mode 100644 index 000000000..b17d8f3b7 --- /dev/null +++ b/system/ui/widgets/slider.py @@ -0,0 +1,183 @@ +from collections.abc import Callable + +import pyray as rl + +from openpilot.system.ui.lib.application import gui_app, FontWeight +from openpilot.system.ui.widgets import Widget +from openpilot.system.ui.widgets.label import UnifiedLabel +from openpilot.common.filter_simple import FirstOrderFilter + + +class SmallSlider(Widget): + HORIZONTAL_PADDING = 8 + CONFIRM_DELAY = 0.2 + + def __init__(self, title: str, confirm_callback: Callable | None = None): + # TODO: unify this with BigConfirmationDialogV2 + super().__init__() + self._confirm_callback = confirm_callback + + self._font = gui_app.font(FontWeight.DISPLAY) + + self._load_assets() + + self._drag_threshold = -self._rect.width // 2 + + # State + self._opacity = 1.0 + self._confirmed_time = 0.0 + self._confirm_callback_called = False # we keep dialog open by default, only call once + self._start_x_circle = 0.0 + self._scroll_x_circle = 0.0 + self._scroll_x_circle_filter = FirstOrderFilter(0, 0.05, 1 / gui_app.target_fps) + + self._is_dragging_circle = False + + self._label = UnifiedLabel(title, font_size=36, font_weight=FontWeight.MEDIUM, text_color=rl.Color(255, 255, 255, int(255 * 0.65)), + alignment=rl.GuiTextAlignment.TEXT_ALIGN_RIGHT, + alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE, line_height=0.9) + + def _load_assets(self): + self.set_rect(rl.Rectangle(0, 0, 316 + self.HORIZONTAL_PADDING * 2, 100)) + + self._bg_txt = gui_app.texture("icons_mici/setup/small_slider/slider_bg.png", 316, 100) + self._circle_bg_txt = gui_app.texture("icons_mici/setup/small_slider/slider_red_circle.png", 100, 100) + self._circle_arrow_txt = gui_app.texture("icons_mici/setup/small_slider/slider_arrow.png", 37, 32) + + @property + def confirmed(self) -> bool: + return self._confirmed_time > 0.0 + + def reset(self): + # reset all slider state + self._is_dragging_circle = False + self._confirmed_time = 0.0 + self._confirm_callback_called = False + + def set_opacity(self, opacity: float): + self._opacity = opacity + + @property + def slider_percentage(self): + activated_pos = -self._bg_txt.width + self._circle_bg_txt.width + return min(max(-self._scroll_x_circle_filter.x / abs(activated_pos), 0.0), 1.0) + + def _on_confirm(self): + if self._confirm_callback: + self._confirm_callback() + + def _handle_mouse_event(self, mouse_event): + super()._handle_mouse_event(mouse_event) + + if mouse_event.left_pressed: + # touch rect goes to the padding + circle_button_rect = rl.Rectangle( + self._rect.x + (self._rect.width - self._circle_bg_txt.width) + self._scroll_x_circle_filter.x - self.HORIZONTAL_PADDING * 2, + self._rect.y, + self._circle_bg_txt.width + self.HORIZONTAL_PADDING * 2, + self._rect.height, + ) + if rl.check_collision_point_rec(mouse_event.pos, circle_button_rect): + self._start_x_circle = mouse_event.pos.x + self._is_dragging_circle = True + + elif mouse_event.left_released: + # swiped to left + if self._scroll_x_circle_filter.x < self._drag_threshold: + self._confirmed_time = rl.get_time() + + self._is_dragging_circle = False + + if self._is_dragging_circle: + self._scroll_x_circle = mouse_event.pos.x - self._start_x_circle + + def _update_state(self): + super()._update_state() + # TODO: this math can probably be cleaned up to remove duplicate stuff + activated_pos = int(-self._bg_txt.width + self._circle_bg_txt.width) + self._scroll_x_circle = max(min(self._scroll_x_circle, 0), activated_pos) + + if self._confirmed_time > 0: + # swiped left to confirm + self._scroll_x_circle_filter.update(activated_pos) + + # activate once animation completes, small threshold for small floats + if self._scroll_x_circle_filter.x < (activated_pos + 1): + if not self._confirm_callback_called and (rl.get_time() - self._confirmed_time) >= self.CONFIRM_DELAY: + self._on_confirm() + self._confirm_callback_called = True + + elif not self._is_dragging_circle: + # reset back to right + self._scroll_x_circle_filter.update(0) + else: + # not activated yet, keep movement 1:1 + self._scroll_x_circle_filter.x = self._scroll_x_circle + + def _render(self, _): + # TODO: iOS text shimmering animation + + white = rl.Color(255, 255, 255, int(255 * self._opacity)) + + bg_txt_x = self._rect.x + (self._rect.width - self._bg_txt.width) / 2 + bg_txt_y = self._rect.y + (self._rect.height - self._bg_txt.height) / 2 + rl.draw_texture_ex(self._bg_txt, rl.Vector2(bg_txt_x, bg_txt_y), 0.0, 1.0, white) + + btn_x = bg_txt_x + self._bg_txt.width - self._circle_bg_txt.width + self._scroll_x_circle_filter.x + btn_y = self._rect.y + (self._rect.height - self._circle_bg_txt.height) / 2 + + if self._confirmed_time == 0.0 or self._scroll_x_circle > 0: + self._label.set_text_color(rl.Color(255, 255, 255, int(255 * 0.65 * (1.0 - self.slider_percentage) * self._opacity))) + label_rect = rl.Rectangle( + self._rect.x + 20, + self._rect.y, + self._rect.width - self._circle_bg_txt.width - 20 * 3, + self._rect.height, + ) + self._label.render(label_rect) + + # circle and arrow + rl.draw_texture_ex(self._circle_bg_txt, rl.Vector2(btn_x, btn_y), 0.0, 1.0, white) + + arrow_x = btn_x + (self._circle_bg_txt.width - self._circle_arrow_txt.width) / 2 + arrow_y = btn_y + (self._circle_bg_txt.height - self._circle_arrow_txt.height) / 2 + rl.draw_texture_ex(self._circle_arrow_txt, rl.Vector2(arrow_x, arrow_y), 0.0, 1.0, white) + + +class LargerSlider(SmallSlider): + def __init__(self, title: str, confirm_callback: Callable | None = None, green: bool = True): + self._green = green + super().__init__(title, confirm_callback=confirm_callback) + + def _load_assets(self): + self.set_rect(rl.Rectangle(0, 0, 520 + self.HORIZONTAL_PADDING * 2, 115)) + + self._bg_txt = gui_app.texture("icons_mici/setup/small_slider/slider_bg_larger.png", 520, 115) + circle_fn = "slider_green_rounded_rectangle" if self._green else "slider_black_rounded_rectangle" + self._circle_bg_txt = gui_app.texture(f"icons_mici/setup/small_slider/{circle_fn}.png", 180, 115) + self._circle_arrow_txt = gui_app.texture("icons_mici/setup/small_slider/slider_arrow.png", 64, 55) + + +class BigSlider(SmallSlider): + def __init__(self, title: str, icon: rl.Texture, confirm_callback: Callable | None = None): + self._icon = icon + super().__init__(title, confirm_callback=confirm_callback) + self._label = UnifiedLabel(title, font_size=48, font_weight=FontWeight.DISPLAY, text_color=rl.Color(255, 255, 255, int(255 * 0.65)), + alignment=rl.GuiTextAlignment.TEXT_ALIGN_RIGHT, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE, + line_height=0.875) + + def _load_assets(self): + self.set_rect(rl.Rectangle(0, 0, 520 + self.HORIZONTAL_PADDING * 2, 180)) + + self._bg_txt = gui_app.texture("icons_mici/buttons/slider_bg.png", 520, 180) + self._circle_bg_txt = gui_app.texture("icons_mici/buttons/button_circle.png", 180, 180) + self._circle_arrow_txt = self._icon + + +class RedBigSlider(BigSlider): + def _load_assets(self): + self.set_rect(rl.Rectangle(0, 0, 520 + self.HORIZONTAL_PADDING * 2, 180)) + + self._bg_txt = gui_app.texture("icons_mici/buttons/slider_bg.png", 520, 180) + self._circle_bg_txt = gui_app.texture("icons_mici/buttons/button_circle_red.png", 180, 180) + self._circle_arrow_txt = self._icon From e899f46727ee1106a4061bc9f4a092c1ceae3ca0 Mon Sep 17 00:00:00 2001 From: Trey Moen <50057480+greatgitsby@users.noreply.github.com> Date: Tue, 18 Nov 2025 22:35:32 -0800 Subject: [PATCH 384/910] fix: block update if not connected (#36641) --- selfdrive/ui/mici/layouts/settings/device.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/selfdrive/ui/mici/layouts/settings/device.py b/selfdrive/ui/mici/layouts/settings/device.py index de2e11caf..b5e0ea838 100644 --- a/selfdrive/ui/mici/layouts/settings/device.py +++ b/selfdrive/ui/mici/layouts/settings/device.py @@ -169,6 +169,11 @@ class UpdateOpenpilotBigButton(BigButton): self.set_enabled(True) def _handle_mouse_release(self, mouse_pos: MousePos): + if not system_time_valid(): + dlg = BigDialog(tr("Please connect to Wi-Fi to update"), "") + gui_app.set_modal_overlay(dlg) + return + self.set_enabled(False) self._state = UpdaterState.WAITING_FOR_UPDATER self.set_icon(self._txt_update_icon) From 85b05998edb907810fff86d22c740435fbbc44e0 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 18 Nov 2025 22:47:59 -0800 Subject: [PATCH 385/910] small release --- RELEASES.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/RELEASES.md b/RELEASES.md index 9ab60e6cb..58044dc69 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,5 +1,6 @@ -Version 0.10.2 (2025-11-23) +Version 0.10.2 (2025-11-19) ======================== +* comma four support Version 0.10.1 (2025-09-08) ======================== From b0b6bf87024a3975f9e4f2f733307b620a2ce795 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 18 Nov 2025 23:37:54 -0800 Subject: [PATCH 386/910] driving model -> openpilot --- selfdrive/ui/mici/layouts/onboarding.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/mici/layouts/onboarding.py b/selfdrive/ui/mici/layouts/onboarding.py index 6036393aa..a3f859208 100644 --- a/selfdrive/ui/mici/layouts/onboarding.py +++ b/selfdrive/ui/mici/layouts/onboarding.py @@ -287,8 +287,8 @@ class TrainingGuideConfidenceBall(SetupTermsPage): self._start_time = 0.0 self._title_header = TermsHeader("confidence ball", gui_app.texture("icons_mici/setup/green_car.png", 60, 60)) - self._warning_label = UnifiedLabel("The ball on the right communicates how confident the driving " + - "model is about the road scene at any given time.", 36, + self._warning_label = UnifiedLabel("The ball on the right communicates how confident openpilot " + + "is about the road scene at any given time.", 36, FontWeight.ROMAN) def show_event(self): From 85301e3a67dcbb1abbf80df8dbf6c222e2981f0a Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 18 Nov 2025 23:52:00 -0800 Subject: [PATCH 387/910] Remove unused mici images (#36644) remove uused images --- selfdrive/assets/icons_mici/buttons/button_circle_pressed.png | 3 --- .../assets/icons_mici/buttons/button_circle_red_pressed.png | 3 --- selfdrive/assets/icons_mici/buttons/button_side_home.png | 3 --- selfdrive/assets/icons_mici/buttons/toggle_dot_orange.png | 3 --- selfdrive/assets/icons_mici/eye.png | 3 --- selfdrive/assets/icons_mici/eye_crossed.png | 3 --- selfdrive/assets/icons_mici/notifications/blue_large.png | 3 --- selfdrive/assets/icons_mici/notifications/blue_small.png | 3 --- selfdrive/assets/icons_mici/notifications/icons/cable.png | 3 --- selfdrive/assets/icons_mici/notifications/icons/camera.png | 3 --- selfdrive/assets/icons_mici/notifications/icons/close.png | 3 --- selfdrive/assets/icons_mici/notifications/icons/critical.png | 3 --- selfdrive/assets/icons_mici/notifications/icons/eye.png | 3 --- selfdrive/assets/icons_mici/notifications/icons/fan.png | 3 --- .../assets/icons_mici/notifications/icons/temperature.png | 3 --- selfdrive/assets/icons_mici/notifications/icons/wheel.png | 3 --- selfdrive/assets/icons_mici/notifications/normal_large.png | 3 --- selfdrive/assets/icons_mici/notifications/normal_small.png | 3 --- selfdrive/assets/icons_mici/notifications/orange_large.png | 3 --- selfdrive/assets/icons_mici/notifications/orange_small.png | 3 --- selfdrive/assets/icons_mici/notifications/red_large.png | 3 --- selfdrive/assets/icons_mici/notifications/red_small.png | 3 --- selfdrive/assets/icons_mici/onroad/sunglasses.png | 3 --- selfdrive/assets/icons_mici/settings/developer/adb.png | 3 --- selfdrive/assets/icons_mici/settings/developer/debug_mode.png | 3 --- selfdrive/assets/icons_mici/settings/device/cancel.png | 3 --- selfdrive/assets/icons_mici/settings/device/downloading.png | 3 --- selfdrive/assets/icons_mici/settings/keyboard/back.png | 3 --- selfdrive/assets/icons_mici/settings/network/connect.png | 3 --- .../assets/icons_mici/settings/network/connect_disabled.png | 3 --- .../assets/icons_mici/settings/network/connect_pressed.png | 3 --- selfdrive/assets/icons_mici/settings/network/forget_pill.png | 3 --- .../assets/icons_mici/settings/network/forget_pill_pressed.png | 3 --- selfdrive/assets/icons_mici/settings/network/trash.png | 3 --- selfdrive/assets/icons_mici/setup/arrow.png | 3 --- selfdrive/assets/icons_mici/setup/back.png | 3 --- selfdrive/assets/icons_mici/setup/reboot.png | 3 --- selfdrive/assets/icons_mici/setup/small_button_disabled.png | 3 --- .../icons_mici/setup/small_slider/slider_arrow_outline.png | 3 --- 39 files changed, 117 deletions(-) delete mode 100644 selfdrive/assets/icons_mici/buttons/button_circle_pressed.png delete mode 100644 selfdrive/assets/icons_mici/buttons/button_circle_red_pressed.png delete mode 100644 selfdrive/assets/icons_mici/buttons/button_side_home.png delete mode 100644 selfdrive/assets/icons_mici/buttons/toggle_dot_orange.png delete mode 100644 selfdrive/assets/icons_mici/eye.png delete mode 100644 selfdrive/assets/icons_mici/eye_crossed.png delete mode 100644 selfdrive/assets/icons_mici/notifications/blue_large.png delete mode 100644 selfdrive/assets/icons_mici/notifications/blue_small.png delete mode 100644 selfdrive/assets/icons_mici/notifications/icons/cable.png delete mode 100644 selfdrive/assets/icons_mici/notifications/icons/camera.png delete mode 100644 selfdrive/assets/icons_mici/notifications/icons/close.png delete mode 100644 selfdrive/assets/icons_mici/notifications/icons/critical.png delete mode 100644 selfdrive/assets/icons_mici/notifications/icons/eye.png delete mode 100644 selfdrive/assets/icons_mici/notifications/icons/fan.png delete mode 100644 selfdrive/assets/icons_mici/notifications/icons/temperature.png delete mode 100644 selfdrive/assets/icons_mici/notifications/icons/wheel.png delete mode 100644 selfdrive/assets/icons_mici/notifications/normal_large.png delete mode 100644 selfdrive/assets/icons_mici/notifications/normal_small.png delete mode 100644 selfdrive/assets/icons_mici/notifications/orange_large.png delete mode 100644 selfdrive/assets/icons_mici/notifications/orange_small.png delete mode 100644 selfdrive/assets/icons_mici/notifications/red_large.png delete mode 100644 selfdrive/assets/icons_mici/notifications/red_small.png delete mode 100644 selfdrive/assets/icons_mici/onroad/sunglasses.png delete mode 100644 selfdrive/assets/icons_mici/settings/developer/adb.png delete mode 100644 selfdrive/assets/icons_mici/settings/developer/debug_mode.png delete mode 100644 selfdrive/assets/icons_mici/settings/device/cancel.png delete mode 100644 selfdrive/assets/icons_mici/settings/device/downloading.png delete mode 100644 selfdrive/assets/icons_mici/settings/keyboard/back.png delete mode 100644 selfdrive/assets/icons_mici/settings/network/connect.png delete mode 100644 selfdrive/assets/icons_mici/settings/network/connect_disabled.png delete mode 100644 selfdrive/assets/icons_mici/settings/network/connect_pressed.png delete mode 100644 selfdrive/assets/icons_mici/settings/network/forget_pill.png delete mode 100644 selfdrive/assets/icons_mici/settings/network/forget_pill_pressed.png delete mode 100644 selfdrive/assets/icons_mici/settings/network/trash.png delete mode 100644 selfdrive/assets/icons_mici/setup/arrow.png delete mode 100644 selfdrive/assets/icons_mici/setup/back.png delete mode 100644 selfdrive/assets/icons_mici/setup/reboot.png delete mode 100644 selfdrive/assets/icons_mici/setup/small_button_disabled.png delete mode 100644 selfdrive/assets/icons_mici/setup/small_slider/slider_arrow_outline.png diff --git a/selfdrive/assets/icons_mici/buttons/button_circle_pressed.png b/selfdrive/assets/icons_mici/buttons/button_circle_pressed.png deleted file mode 100644 index 45027a372..000000000 --- a/selfdrive/assets/icons_mici/buttons/button_circle_pressed.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d378fdbb9d683d5c94536ebf9c466146721b1f65859eb38667d5c2e9589e54c3 -size 12590 diff --git a/selfdrive/assets/icons_mici/buttons/button_circle_red_pressed.png b/selfdrive/assets/icons_mici/buttons/button_circle_red_pressed.png deleted file mode 100644 index 08b2e318d..000000000 --- a/selfdrive/assets/icons_mici/buttons/button_circle_red_pressed.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:19d53ff0cb49ffc43507e5bac11583bc9b3037c2e2ed5e12b96bfd97d71e397f -size 26113 diff --git a/selfdrive/assets/icons_mici/buttons/button_side_home.png b/selfdrive/assets/icons_mici/buttons/button_side_home.png deleted file mode 100644 index 99c5ea650..000000000 --- a/selfdrive/assets/icons_mici/buttons/button_side_home.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3ec30f6ba49e7a7bc89e8369800ad71c8b57950bbf6b3f169fa944626a3ded59 -size 5258 diff --git a/selfdrive/assets/icons_mici/buttons/toggle_dot_orange.png b/selfdrive/assets/icons_mici/buttons/toggle_dot_orange.png deleted file mode 100644 index bf8559ec8..000000000 --- a/selfdrive/assets/icons_mici/buttons/toggle_dot_orange.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1ca418dab8eab77569e3cc446deccdc5b468d79159711e6629d704eb531009d9 -size 6191 diff --git a/selfdrive/assets/icons_mici/eye.png b/selfdrive/assets/icons_mici/eye.png deleted file mode 100644 index db2953b69..000000000 --- a/selfdrive/assets/icons_mici/eye.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c9c7f03c2784eac6882eb59d5f1c547008c12f19a861d04e9ca3549edb41218c -size 1988 diff --git a/selfdrive/assets/icons_mici/eye_crossed.png b/selfdrive/assets/icons_mici/eye_crossed.png deleted file mode 100644 index 11197fcf5..000000000 --- a/selfdrive/assets/icons_mici/eye_crossed.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5f54bdb8dbb94682ff474174b8e3c605dba867f1b6613a3efcc5fbbedc462a95 -size 1811 diff --git a/selfdrive/assets/icons_mici/notifications/blue_large.png b/selfdrive/assets/icons_mici/notifications/blue_large.png deleted file mode 100644 index e4aa33a13..000000000 --- a/selfdrive/assets/icons_mici/notifications/blue_large.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2566b38173e6048f54ef62dd68a158b07747638f74f2724406cfef02ae38022b -size 14052 diff --git a/selfdrive/assets/icons_mici/notifications/blue_small.png b/selfdrive/assets/icons_mici/notifications/blue_small.png deleted file mode 100644 index 500f48e36..000000000 --- a/selfdrive/assets/icons_mici/notifications/blue_small.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7020a7ec5a1a31320a0cc2a381a79ed0d650bc220a9c60ae334c646868e12295 -size 10662 diff --git a/selfdrive/assets/icons_mici/notifications/icons/cable.png b/selfdrive/assets/icons_mici/notifications/icons/cable.png deleted file mode 100644 index 72b64f073..000000000 --- a/selfdrive/assets/icons_mici/notifications/icons/cable.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:028bb5611f41b0da7944f6bed42962cd894a57706a9940f124de7488a61b9b60 -size 1171 diff --git a/selfdrive/assets/icons_mici/notifications/icons/camera.png b/selfdrive/assets/icons_mici/notifications/icons/camera.png deleted file mode 100644 index fbe70ee82..000000000 --- a/selfdrive/assets/icons_mici/notifications/icons/camera.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8667594a61ed4680b55ef981085cb84db7d1c86dd28ed998a7e8441f03b09193 -size 2000 diff --git a/selfdrive/assets/icons_mici/notifications/icons/close.png b/selfdrive/assets/icons_mici/notifications/icons/close.png deleted file mode 100644 index e0f061a01..000000000 --- a/selfdrive/assets/icons_mici/notifications/icons/close.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:064cf235bfc30a08863a29ec7f94aa1f8cf7b6b68ee5eaad0a0c48740e9a0ce1 -size 2371 diff --git a/selfdrive/assets/icons_mici/notifications/icons/critical.png b/selfdrive/assets/icons_mici/notifications/icons/critical.png deleted file mode 100644 index 8acab1854..000000000 --- a/selfdrive/assets/icons_mici/notifications/icons/critical.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:77d7945ba3af1d0ecb4c52600fcfee80f9f8069f34dea4bce95fd0495ac6f80c -size 2596 diff --git a/selfdrive/assets/icons_mici/notifications/icons/eye.png b/selfdrive/assets/icons_mici/notifications/icons/eye.png deleted file mode 100644 index 11197fcf5..000000000 --- a/selfdrive/assets/icons_mici/notifications/icons/eye.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5f54bdb8dbb94682ff474174b8e3c605dba867f1b6613a3efcc5fbbedc462a95 -size 1811 diff --git a/selfdrive/assets/icons_mici/notifications/icons/fan.png b/selfdrive/assets/icons_mici/notifications/icons/fan.png deleted file mode 100644 index 017da6c8c..000000000 --- a/selfdrive/assets/icons_mici/notifications/icons/fan.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:40ab18b92dda78353031c5df7b57db64a4a7f99820f696c4a4efe92cfa690109 -size 2465 diff --git a/selfdrive/assets/icons_mici/notifications/icons/temperature.png b/selfdrive/assets/icons_mici/notifications/icons/temperature.png deleted file mode 100644 index 09d0d798d..000000000 --- a/selfdrive/assets/icons_mici/notifications/icons/temperature.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3b76a778921377508da133ad105247b238244fae86f9c0791f3ad05dd69802a6 -size 1975 diff --git a/selfdrive/assets/icons_mici/notifications/icons/wheel.png b/selfdrive/assets/icons_mici/notifications/icons/wheel.png deleted file mode 100644 index ec4741f7f..000000000 --- a/selfdrive/assets/icons_mici/notifications/icons/wheel.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:23424ed80a70ebfedb99f14e286a08dcf59167988683b81fa8a56b06e9b2051e -size 2638 diff --git a/selfdrive/assets/icons_mici/notifications/normal_large.png b/selfdrive/assets/icons_mici/notifications/normal_large.png deleted file mode 100644 index df2598403..000000000 --- a/selfdrive/assets/icons_mici/notifications/normal_large.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b6aa200bbb3381eb0d6a72c412227cd99c8bde5e843f705b218c09ee98576804 -size 9796 diff --git a/selfdrive/assets/icons_mici/notifications/normal_small.png b/selfdrive/assets/icons_mici/notifications/normal_small.png deleted file mode 100644 index f96de2e30..000000000 --- a/selfdrive/assets/icons_mici/notifications/normal_small.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0486257b2e7735ad68c2bde66eb7bc710862c989feef856f06b0bf5d5231e7db -size 7369 diff --git a/selfdrive/assets/icons_mici/notifications/orange_large.png b/selfdrive/assets/icons_mici/notifications/orange_large.png deleted file mode 100644 index 62f6dac63..000000000 --- a/selfdrive/assets/icons_mici/notifications/orange_large.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:32e3d36a95215cff4fdbe4af01c86408e647ff57dcc8724b517fda453b4b01e0 -size 15069 diff --git a/selfdrive/assets/icons_mici/notifications/orange_small.png b/selfdrive/assets/icons_mici/notifications/orange_small.png deleted file mode 100644 index 31fcf11a6..000000000 --- a/selfdrive/assets/icons_mici/notifications/orange_small.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0f754f98cd3f7104f3567bf02c4972277a8f5740757e245a2b3a60f9d1f61506 -size 11308 diff --git a/selfdrive/assets/icons_mici/notifications/red_large.png b/selfdrive/assets/icons_mici/notifications/red_large.png deleted file mode 100644 index 81cd5566f..000000000 --- a/selfdrive/assets/icons_mici/notifications/red_large.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7f820799a20667a265d5ed9e0ebc6a46a6e898935f2fa920b66d001347b88704 -size 13410 diff --git a/selfdrive/assets/icons_mici/notifications/red_small.png b/selfdrive/assets/icons_mici/notifications/red_small.png deleted file mode 100644 index 00b358c81..000000000 --- a/selfdrive/assets/icons_mici/notifications/red_small.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0fb44ce698c02929ca0c4360cc493ad18a99bd8f5d75586a422961e6f17d1371 -size 10169 diff --git a/selfdrive/assets/icons_mici/onroad/sunglasses.png b/selfdrive/assets/icons_mici/onroad/sunglasses.png deleted file mode 100644 index 15e502d61..000000000 --- a/selfdrive/assets/icons_mici/onroad/sunglasses.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4b520b8a00ca245f1dcccca4ddbf1b1b6f8da9fb8b6ac9ea351e735db61641e6 -size 1006 diff --git a/selfdrive/assets/icons_mici/settings/developer/adb.png b/selfdrive/assets/icons_mici/settings/developer/adb.png deleted file mode 100644 index b3a780146..000000000 --- a/selfdrive/assets/icons_mici/settings/developer/adb.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:19b304727376ea30126e7aeb10d1193885ffa661ca5bdf4c09098e4412d2ab6a -size 2163 diff --git a/selfdrive/assets/icons_mici/settings/developer/debug_mode.png b/selfdrive/assets/icons_mici/settings/developer/debug_mode.png deleted file mode 100644 index 2849d2733..000000000 --- a/selfdrive/assets/icons_mici/settings/developer/debug_mode.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8bbdcedeb5f6f5844a8f67751f5d47793fe5af5e25ac6e7fd1ffc5857a85d56e -size 2997 diff --git a/selfdrive/assets/icons_mici/settings/device/cancel.png b/selfdrive/assets/icons_mici/settings/device/cancel.png deleted file mode 100644 index 6da29ad66..000000000 --- a/selfdrive/assets/icons_mici/settings/device/cancel.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:be9669cff5fc8a0b587dee27d1afb1caa77ceff2f92ca0ce3f01d25659e96596 -size 1332 diff --git a/selfdrive/assets/icons_mici/settings/device/downloading.png b/selfdrive/assets/icons_mici/settings/device/downloading.png deleted file mode 100644 index 2db585698..000000000 --- a/selfdrive/assets/icons_mici/settings/device/downloading.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a355a3960aea41b176be66d85a3e7bc83ec843bfb1e5bd4c7978f4ea41c5d1a2 -size 2756 diff --git a/selfdrive/assets/icons_mici/settings/keyboard/back.png b/selfdrive/assets/icons_mici/settings/keyboard/back.png deleted file mode 100644 index fccc2484d..000000000 --- a/selfdrive/assets/icons_mici/settings/keyboard/back.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:de48d508af786b39fb725022b179e31456f32551a49b96ae07b5f55bfb968699 -size 1814 diff --git a/selfdrive/assets/icons_mici/settings/network/connect.png b/selfdrive/assets/icons_mici/settings/network/connect.png deleted file mode 100644 index f7beaf392..000000000 --- a/selfdrive/assets/icons_mici/settings/network/connect.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e47a5cb8b9ec784b6b893463622b7967a3692979b8a5e46e3334a09f745f1f71 -size 5413 diff --git a/selfdrive/assets/icons_mici/settings/network/connect_disabled.png b/selfdrive/assets/icons_mici/settings/network/connect_disabled.png deleted file mode 100644 index 0563668be..000000000 --- a/selfdrive/assets/icons_mici/settings/network/connect_disabled.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:05ddc9456627c0773dddcb10469e78124f9788381e63cab163ce4c6407a000f4 -size 3201 diff --git a/selfdrive/assets/icons_mici/settings/network/connect_pressed.png b/selfdrive/assets/icons_mici/settings/network/connect_pressed.png deleted file mode 100644 index aef242b6d..000000000 --- a/selfdrive/assets/icons_mici/settings/network/connect_pressed.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cab1783614421ab86467c3686f4d11f439d6de2cce3a84801dec7e044c08c880 -size 9075 diff --git a/selfdrive/assets/icons_mici/settings/network/forget_pill.png b/selfdrive/assets/icons_mici/settings/network/forget_pill.png deleted file mode 100644 index a80de0763..000000000 --- a/selfdrive/assets/icons_mici/settings/network/forget_pill.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2645cc56688a6225ba7ba0b4021af6543657942f31892a4986061a2959511054 -size 12106 diff --git a/selfdrive/assets/icons_mici/settings/network/forget_pill_pressed.png b/selfdrive/assets/icons_mici/settings/network/forget_pill_pressed.png deleted file mode 100644 index a1240481e..000000000 --- a/selfdrive/assets/icons_mici/settings/network/forget_pill_pressed.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:42cea7a190be02027a1cc8d6daca435f4edfb5b9484c26a06e667a2346c91f0f -size 38471 diff --git a/selfdrive/assets/icons_mici/settings/network/trash.png b/selfdrive/assets/icons_mici/settings/network/trash.png deleted file mode 100644 index 99e1a2e24..000000000 --- a/selfdrive/assets/icons_mici/settings/network/trash.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:efabf98ed66fe4447c0f13c74aec681b084de780c551ce18258c79636d4123c5 -size 1524 diff --git a/selfdrive/assets/icons_mici/setup/arrow.png b/selfdrive/assets/icons_mici/setup/arrow.png deleted file mode 100644 index 403aaedfb..000000000 --- a/selfdrive/assets/icons_mici/setup/arrow.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7aee85c239edcb7be41f03a4982b136d9a46908b027f37c5d36c80bd20372b22 -size 847 diff --git a/selfdrive/assets/icons_mici/setup/back.png b/selfdrive/assets/icons_mici/setup/back.png deleted file mode 100644 index 554c63e09..000000000 --- a/selfdrive/assets/icons_mici/setup/back.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f88a958dd51eaf2c3f39df72165f538110c1d1eb4dbcac1f7016563db126c762 -size 1693 diff --git a/selfdrive/assets/icons_mici/setup/reboot.png b/selfdrive/assets/icons_mici/setup/reboot.png deleted file mode 100644 index 5633f2b49..000000000 --- a/selfdrive/assets/icons_mici/setup/reboot.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:345bea231013aab33b98a134a05af22c2df7c166b60a1a81b6b4526da13209a5 -size 2293 diff --git a/selfdrive/assets/icons_mici/setup/small_button_disabled.png b/selfdrive/assets/icons_mici/setup/small_button_disabled.png deleted file mode 100644 index 5028a8cd2..000000000 --- a/selfdrive/assets/icons_mici/setup/small_button_disabled.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9728423bd5e3197ef02d62e4bae415e6694aab875ca8630ffc9f188c38e18e5f -size 4141 diff --git a/selfdrive/assets/icons_mici/setup/small_slider/slider_arrow_outline.png b/selfdrive/assets/icons_mici/setup/small_slider/slider_arrow_outline.png deleted file mode 100644 index 1515867a4..000000000 --- a/selfdrive/assets/icons_mici/setup/small_slider/slider_arrow_outline.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f6394fa9dc03f83ac8599cf8179cdcc221c1b80b2c00d396b04bf2e3a7bfdca4 -size 1673 From b89c717643d27060280733a47468908ccd801068 Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Wed, 19 Nov 2025 00:23:42 -0800 Subject: [PATCH 388/910] AGNOS 15.1 (#36638) * stage * staging * prod --- launch_env.sh | 2 +- system/hardware/tici/agnos.json | 12 ++++---- system/hardware/tici/all-partitions.json | 36 ++++++++++++------------ 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/launch_env.sh b/launch_env.sh index 0fba08bb2..fcbee2ff8 100755 --- a/launch_env.sh +++ b/launch_env.sh @@ -16,7 +16,7 @@ export VECLIB_MAXIMUM_THREADS=1 export QCOM_PRIORITY=12 if [ -z "$AGNOS_VERSION" ]; then - export AGNOS_VERSION="15" + export AGNOS_VERSION="15.1" fi export STAGING_ROOT="/data/safe_staging" diff --git a/system/hardware/tici/agnos.json b/system/hardware/tici/agnos.json index 58c3d2a4e..5a2a092aa 100644 --- a/system/hardware/tici/agnos.json +++ b/system/hardware/tici/agnos.json @@ -67,17 +67,17 @@ }, { "name": "system", - "url": "https://commadist.azureedge.net/agnosupdate/system-8757f4a9d2489585249970142578029ab1dfdc5851da75fd703d2376b6f2a26b.img.xz", - "hash": "e9e99988d78c7287f29ad840130f65d5a11fa2301463d5298f1072399406f889", - "hash_raw": "8757f4a9d2489585249970142578029ab1dfdc5851da75fd703d2376b6f2a26b", + "url": "https://commadist.azureedge.net/agnosupdate/system-d9d476b466186014e7ae4b8232bc6fc5e79b122421bdc12ff4eb02d1c3f37818.img.xz", + "hash": "a068d4d692ec770884f0a15e1a6d7aba52385ecae138f6d43fb0a9b1643ed5cd", + "hash_raw": "d9d476b466186014e7ae4b8232bc6fc5e79b122421bdc12ff4eb02d1c3f37818", "size": 4718592000, "sparse": true, "full_check": false, "has_ab": true, - "ondevice_hash": "21d3726fcdd39d126c9ecf05ccc43a104c8486b929045a63bf7e3ac8a8bb7a50", + "ondevice_hash": "6ffa02f7113badc122742f33efebc5d17f1cd61dd6358f3e130c162707dbfaf4", "alt": { - "hash": "8757f4a9d2489585249970142578029ab1dfdc5851da75fd703d2376b6f2a26b", - "url": "https://commadist.azureedge.net/agnosupdate/system-8757f4a9d2489585249970142578029ab1dfdc5851da75fd703d2376b6f2a26b.img", + "hash": "d9d476b466186014e7ae4b8232bc6fc5e79b122421bdc12ff4eb02d1c3f37818", + "url": "https://commadist.azureedge.net/agnosupdate/system-d9d476b466186014e7ae4b8232bc6fc5e79b122421bdc12ff4eb02d1c3f37818.img", "size": 4718592000 } } diff --git a/system/hardware/tici/all-partitions.json b/system/hardware/tici/all-partitions.json index bac2dfc59..3abf66cdd 100644 --- a/system/hardware/tici/all-partitions.json +++ b/system/hardware/tici/all-partitions.json @@ -350,51 +350,51 @@ }, { "name": "system", - "url": "https://commadist.azureedge.net/agnosupdate/system-8757f4a9d2489585249970142578029ab1dfdc5851da75fd703d2376b6f2a26b.img.xz", - "hash": "e9e99988d78c7287f29ad840130f65d5a11fa2301463d5298f1072399406f889", - "hash_raw": "8757f4a9d2489585249970142578029ab1dfdc5851da75fd703d2376b6f2a26b", + "url": "https://commadist.azureedge.net/agnosupdate/system-d9d476b466186014e7ae4b8232bc6fc5e79b122421bdc12ff4eb02d1c3f37818.img.xz", + "hash": "a068d4d692ec770884f0a15e1a6d7aba52385ecae138f6d43fb0a9b1643ed5cd", + "hash_raw": "d9d476b466186014e7ae4b8232bc6fc5e79b122421bdc12ff4eb02d1c3f37818", "size": 4718592000, "sparse": true, "full_check": false, "has_ab": true, - "ondevice_hash": "21d3726fcdd39d126c9ecf05ccc43a104c8486b929045a63bf7e3ac8a8bb7a50", + "ondevice_hash": "6ffa02f7113badc122742f33efebc5d17f1cd61dd6358f3e130c162707dbfaf4", "alt": { - "hash": "8757f4a9d2489585249970142578029ab1dfdc5851da75fd703d2376b6f2a26b", - "url": "https://commadist.azureedge.net/agnosupdate/system-8757f4a9d2489585249970142578029ab1dfdc5851da75fd703d2376b6f2a26b.img", + "hash": "d9d476b466186014e7ae4b8232bc6fc5e79b122421bdc12ff4eb02d1c3f37818", + "url": "https://commadist.azureedge.net/agnosupdate/system-d9d476b466186014e7ae4b8232bc6fc5e79b122421bdc12ff4eb02d1c3f37818.img", "size": 4718592000 } }, { "name": "userdata_90", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_90-1d461d8be17827735a28c2588bb9fcad27d4b80fba15cd2740f3a04c8f29cc90.img.xz", - "hash": "763c7366049b3c0ad71bd19abbbf5c68d2c43597d4da5dafad890507ff489899", - "hash_raw": "1d461d8be17827735a28c2588bb9fcad27d4b80fba15cd2740f3a04c8f29cc90", + "url": "https://commadist.azureedge.net/agnosupdate/userdata_90-f9ea618ac97a86da49733ce66cd5e3aa19aa917666ee90de301cd746664e4d22.img.xz", + "hash": "dfc6812e76bd1583ed77a86eedf48cafdc306037d2a85c5d0aa7cdb23033b736", + "hash_raw": "f9ea618ac97a86da49733ce66cd5e3aa19aa917666ee90de301cd746664e4d22", "size": 96636764160, "sparse": true, "full_check": true, "has_ab": false, - "ondevice_hash": "90a265b8756b18caf1be4b8dc9b8b3104898170104ed87ec3274f77acc6c28e3" + "ondevice_hash": "ff95f994e9ed6504632f4b7c6daecef582f0a4e5261b8240d4474f16059faef4" }, { "name": "userdata_89", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_89-ec37fcfb7d707d26d5fbc64994e20cfdbb73a27eeedfe37778559824a2032a27.img.xz", - "hash": "de475b604b63fbeb1841c6564fb8eb496da46c9a9564ec73e5d7c8045fc88ebc", - "hash_raw": "ec37fcfb7d707d26d5fbc64994e20cfdbb73a27eeedfe37778559824a2032a27", + "url": "https://commadist.azureedge.net/agnosupdate/userdata_89-393956e255c277b895bdb98bf65cfa3907e4b57822740ff82f857ac4e1a2f11e.img.xz", + "hash": "b5e2f05d31fc18fff18e82dcebfc2bf04de624baeca0511b93e50b3198b8a9ab", + "hash_raw": "393956e255c277b895bdb98bf65cfa3907e4b57822740ff82f857ac4e1a2f11e", "size": 95563022336, "sparse": true, "full_check": true, "has_ab": false, - "ondevice_hash": "03f6cbddc3bfbd2d0cd316d87d488434a03095c12870c8c6fe3bc4a2946ff0ef" + "ondevice_hash": "db64c6abc72bfcddc1682c73cc73c7230ed2f6e835d292fd38d054a9d242b8fc" }, { "name": "userdata_30", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_30-3501f34c28f0e5ffe224f192b4a3a35a00a039980ca29a5c35d31449f3e918d6.img.xz", - "hash": "5bda2cb099b14f4944b476995d84dcb943af1858a57fdd62d5920b6e7b74fb80", - "hash_raw": "3501f34c28f0e5ffe224f192b4a3a35a00a039980ca29a5c35d31449f3e918d6", + "url": "https://commadist.azureedge.net/agnosupdate/userdata_30-a4b3e2a2fc3612a37322b7b1a4c5737765841dc3b8d6d3bb58b1e5a271023068.img.xz", + "hash": "ecec713cf7d8f1f616f122a16b138931f818290447e36a5925da6a4fc0fc7bf3", + "hash_raw": "a4b3e2a2fc3612a37322b7b1a4c5737765841dc3b8d6d3bb58b1e5a271023068", "size": 32212254720, "sparse": true, "full_check": true, "has_ab": false, - "ondevice_hash": "d1da6f8d928093dec15590b5c1740c0062031d0068a11962bdb28dca2104d8c6" + "ondevice_hash": "48fefa5a1880a4fd3dd50e1f9ddee297122053556816baca310d495129bc8893" } ] \ No newline at end of file From 1398bdb10ee11116ae41550add445cf72c52ed2a Mon Sep 17 00:00:00 2001 From: YassineYousfi Date: Wed, 19 Nov 2025 09:23:13 -0800 Subject: [PATCH 389/910] dmonitoringmodeld: follow same pattern as modeld (#36636) * dmonitoringmodeld: follow same pattern as modeld * lint * oops * rename --- selfdrive/modeld/dmonitoringmodeld.py | 61 ++++++++++++++++----------- 1 file changed, 37 insertions(+), 24 deletions(-) diff --git a/selfdrive/modeld/dmonitoringmodeld.py b/selfdrive/modeld/dmonitoringmodeld.py index dc2de6f99..fca762c69 100755 --- a/selfdrive/modeld/dmonitoringmodeld.py +++ b/selfdrive/modeld/dmonitoringmodeld.py @@ -4,7 +4,6 @@ from openpilot.system.hardware import TICI os.environ['DEV'] = 'QCOM' if TICI else 'CPU' from tinygrad.tensor import Tensor from tinygrad.dtype import dtypes -import math import time import pickle import numpy as np @@ -18,7 +17,7 @@ from openpilot.common.realtime import config_realtime_process from openpilot.common.transformations.model import dmonitoringmodel_intrinsics from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye from openpilot.selfdrive.modeld.models.commonmodel_pyx import CLContext, MonitoringModelFrame -from openpilot.selfdrive.modeld.parse_model_outputs import sigmoid +from openpilot.selfdrive.modeld.parse_model_outputs import sigmoid, safe_exp from openpilot.selfdrive.modeld.runners.tinygrad_helpers import qcom_tensor_from_opencl_address PROCESS_NAME = "selfdrive.modeld.dmonitoringmodeld" @@ -34,7 +33,7 @@ class ModelState: def __init__(self, cl_ctx): with open(METADATA_PATH, 'rb') as f: model_metadata = pickle.load(f) - self.input_shapes = model_metadata['input_shapes'] + self.input_shapes = model_metadata['input_shapes'] self.output_slices = model_metadata['output_slices'] self.frame = MonitoringModelFrame(cl_ctx) @@ -65,32 +64,43 @@ class ModelState: t2 = time.perf_counter() return output, t2 - t1 +def slice_outputs(model_outputs, output_slices): + return {k: model_outputs[np.newaxis, v] for k,v in output_slices.items()} -def fill_driver_state(msg, model_output, output_slices, ds_suffix): - face_descs = model_output[output_slices[f'face_descs_{ds_suffix}']] - face_descs_std = face_descs[-6:] - msg.faceOrientation = [float(x) for x in face_descs[:3]] - msg.faceOrientationStd = [math.exp(x) for x in face_descs_std[:3]] - msg.facePosition = [float(x) for x in face_descs[3:5]] - msg.facePositionStd = [math.exp(x) for x in face_descs_std[3:5]] - msg.faceProb = float(sigmoid(model_output[output_slices[f'face_prob_{ds_suffix}']][0])) - msg.leftEyeProb = float(sigmoid(model_output[output_slices[f'left_eye_prob_{ds_suffix}']][0])) - msg.rightEyeProb = float(sigmoid(model_output[output_slices[f'right_eye_prob_{ds_suffix}']][0])) - msg.leftBlinkProb = float(sigmoid(model_output[output_slices[f'left_blink_prob_{ds_suffix}']][0])) - msg.rightBlinkProb = float(sigmoid(model_output[output_slices[f'right_blink_prob_{ds_suffix}']][0])) - msg.sunglassesProb = float(sigmoid(model_output[output_slices[f'sunglasses_prob_{ds_suffix}']][0])) - msg.phoneProb = float(sigmoid(model_output[output_slices[f'using_phone_prob_{ds_suffix}']][0])) +def parse_model_output(model_output): + parsed = {} + parsed['wheel_on_right'] = sigmoid(model_output['wheel_on_right']) + for ds_suffix in ['lhd', 'rhd']: + face_descs = model_output[f'face_descs_{ds_suffix}'] + parsed[f'face_descs_{ds_suffix}'] = face_descs[:, :-6] + parsed[f'face_descs_{ds_suffix}_std'] = safe_exp(face_descs[:, -6:]) + for key in ['face_prob', 'left_eye_prob', 'right_eye_prob','left_blink_prob', 'right_blink_prob', 'sunglasses_prob', 'using_phone_prob']: + parsed[f'{key}_{ds_suffix}'] = sigmoid(model_output[f'{key}_{ds_suffix}']) + return parsed -def get_driverstate_packet(model_output: np.ndarray, output_slices: dict[str, slice], frame_id: int, location_ts: int, exec_time: float, gpu_exec_time: float): +def fill_driver_data(msg, model_output, ds_suffix): + msg.faceOrientation = model_output[f'face_descs_{ds_suffix}'][0, :3].tolist() + msg.faceOrientationStd = model_output[f'face_descs_{ds_suffix}_std'][0, :3].tolist() + msg.facePosition = model_output[f'face_descs_{ds_suffix}'][0, 3:5].tolist() + msg.facePositionStd = model_output[f'face_descs_{ds_suffix}_std'][0, 3:5].tolist() + msg.faceProb = model_output[f'face_prob_{ds_suffix}'][0, 0].item() + msg.leftEyeProb = model_output[f'left_eye_prob_{ds_suffix}'][0, 0].item() + msg.rightEyeProb = model_output[f'right_eye_prob_{ds_suffix}'][0, 0].item() + msg.leftBlinkProb = model_output[f'left_blink_prob_{ds_suffix}'][0, 0].item() + msg.rightBlinkProb = model_output[f'right_blink_prob_{ds_suffix}'][0, 0].item() + msg.sunglassesProb = model_output[f'sunglasses_prob_{ds_suffix}'][0, 0].item() + msg.phoneProb = model_output[f'using_phone_prob_{ds_suffix}'][0, 0].item() + +def get_driverstate_packet(model_output, frame_id: int, location_ts: int, exec_time: float, gpu_exec_time: float): msg = messaging.new_message('driverStateV2', valid=True) ds = msg.driverStateV2 ds.frameId = frame_id ds.modelExecutionTime = exec_time ds.gpuExecutionTime = gpu_exec_time - ds.wheelOnRightProb = float(sigmoid(model_output[output_slices['wheel_on_right']][0])) - ds.rawPredictions = model_output.tobytes() if SEND_RAW_PRED else b'' - fill_driver_state(ds.leftDriverData, model_output, output_slices, 'lhd') - fill_driver_state(ds.rightDriverData, model_output, output_slices, 'rhd') + ds.rawPredictions = model_output['raw_pred'] + ds.wheelOnRightProb = model_output['wheel_on_right'][0, 0].item() + fill_driver_data(ds.leftDriverData, model_output, 'lhd') + fill_driver_data(ds.rightDriverData, model_output, 'rhd') return msg @@ -130,8 +140,11 @@ def main(): t1 = time.perf_counter() model_output, gpu_execution_time = model.run(buf, calib, model_transform) t2 = time.perf_counter() - - msg = get_driverstate_packet(model_output, model.output_slices, vipc_client.frame_id, vipc_client.timestamp_sof, t2 - t1, gpu_execution_time) + raw_pred = model_output.tobytes() if SEND_RAW_PRED else b'' + model_output = slice_outputs(model_output, model.output_slices) + model_output = parse_model_output(model_output) + model_output['raw_pred'] = raw_pred + msg = get_driverstate_packet(model_output, vipc_client.frame_id, vipc_client.timestamp_sof, t2 - t1, gpu_execution_time) pm.send("driverStateV2", msg) From 0d9b4cdaedf9b7ac09b15d38fe12a63e120f4bcf Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 19 Nov 2025 10:38:58 -0800 Subject: [PATCH 390/910] tmp bump this up --- selfdrive/test/test_onroad.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/test/test_onroad.py b/selfdrive/test/test_onroad.py index 69d920c1a..27cc17624 100644 --- a/selfdrive/test/test_onroad.py +++ b/selfdrive/test/test_onroad.py @@ -179,7 +179,7 @@ class TestOnroad: def test_manager_starting_time(self): st = self.ts['managerState']['t'][0] - assert (st - self.manager_st) < 12.5, f"manager.py took {st - self.manager_st}s to publish the first 'managerState' msg" + assert (st - self.manager_st) < 15.0, f"manager.py took {st - self.manager_st}s to publish the first 'managerState' msg" def test_cloudlog_size(self): msgs = self.msgs['logMessage'] From eeddfc058a6bd38ad76f1186999a5ad5936c4fb0 Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Wed, 19 Nov 2025 13:08:39 -0600 Subject: [PATCH 391/910] ui(mici): remove duplicate draw_texture_pro call in mici_keyboard (#36647) Remove duplicate draw_texture_pro call in IconKey class --- system/ui/widgets/mici_keyboard.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/system/ui/widgets/mici_keyboard.py b/system/ui/widgets/mici_keyboard.py index 2eb24cdb9..a4f4c7d09 100644 --- a/system/ui/widgets/mici_keyboard.py +++ b/system/ui/widgets/mici_keyboard.py @@ -123,8 +123,6 @@ class IconKey(Key): src_rec = rl.Rectangle(0, 0, self._icon.width, self._icon.height) rl.draw_texture_pro(self._icon, src_rec, dest_rec, rl.Vector2(0, 0), 0, self._color) - rl.draw_texture_pro(self._icon, src_rec, dest_rec, rl.Vector2(0, 0), 0, self._color) - elif self._vertical_align == "bottom": dest_rec = rl.Rectangle(self._rect.x + (self._rect.width - self._icon.width * scale) / 2, self._rect.y, self._icon.width * scale, self._icon.height * scale) From 9d0ab68f3bcc33b356514e8262d45346960f316c Mon Sep 17 00:00:00 2001 From: YassineYousfi Date: Wed, 19 Nov 2025 11:14:04 -0800 Subject: [PATCH 392/910] dm: settings w device type (#36650) * dm: settings w device type * lint * fix --- selfdrive/monitoring/helpers.py | 11 +++-------- selfdrive/monitoring/test_monitoring.py | 3 ++- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/selfdrive/monitoring/helpers.py b/selfdrive/monitoring/helpers.py index 463bf2b7f..208fc1676 100644 --- a/selfdrive/monitoring/helpers.py +++ b/selfdrive/monitoring/helpers.py @@ -21,7 +21,7 @@ EventName = log.OnroadEvent.EventName # ****************************************************************************************** class DRIVER_MONITOR_SETTINGS: - def __init__(self): + def __init__(self, device_type): self._DT_DMON = DT_DMON # ref (page15-16): https://eur-lex.europa.eu/legal-content/EN/TXT/PDF/?uri=CELEX:42018X1947&rid=2 self._AWARENESS_TIME = 30. # passive wheeltouch total timeout @@ -36,10 +36,7 @@ class DRIVER_MONITOR_SETTINGS: self._SG_THRESHOLD = 0.9 self._BLINK_THRESHOLD = 0.865 - if HARDWARE.get_device_type() == 'mici': - self._PHONE_THRESH = 0.75 - else: - self._PHONE_THRESH = 0.4 + self._PHONE_THRESH = 0.75 if device_type == 'mici' else 0.4 self._PHONE_THRESH2 = 15.0 self._PHONE_MAX_OFFSET = 0.06 self._PHONE_MIN_OFFSET = 0.025 @@ -133,10 +130,8 @@ def face_orientation_from_net(angles_desc, pos_desc, rpy_calib): class DriverMonitoring: def __init__(self, rhd_saved=False, settings=None, always_on=False): - if settings is None: - settings = DRIVER_MONITOR_SETTINGS() # init policy settings - self.settings = settings + self.settings = settings if settings is not None else DRIVER_MONITOR_SETTINGS(device_type=HARDWARE.get_device_type()) # init driver status self.wheelpos_learner = RunningStatFilter() diff --git a/selfdrive/monitoring/test_monitoring.py b/selfdrive/monitoring/test_monitoring.py index 67234550f..6ea9b8028 100644 --- a/selfdrive/monitoring/test_monitoring.py +++ b/selfdrive/monitoring/test_monitoring.py @@ -3,9 +3,10 @@ import numpy as np from cereal import log from openpilot.common.realtime import DT_DMON from openpilot.selfdrive.monitoring.helpers import DriverMonitoring, DRIVER_MONITOR_SETTINGS +from openpilot.system.hardware import HARDWARE EventName = log.OnroadEvent.EventName -dm_settings = DRIVER_MONITOR_SETTINGS() +dm_settings = DRIVER_MONITOR_SETTINGS(device_type=HARDWARE.get_device_type()) TEST_TIMESPAN = 120 # seconds DISTRACTED_SECONDS_TO_ORANGE = dm_settings._DISTRACTED_TIME - dm_settings._DISTRACTED_PROMPT_TIME_TILL_TERMINAL + 1 From c8effae4ebd71ec7b4ad3412904a160d720a3e30 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 19 Nov 2025 14:18:48 -0800 Subject: [PATCH 393/910] ui/wifi: fix no attribute error (#36653) --- system/ui/lib/wifi_manager.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index 217ac5e89..28bd58f22 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -138,6 +138,8 @@ class WifiManager: self._nm = DBusAddress(NM_PATH, bus_name=NM, interface=NM_IFACE) except FileNotFoundError: cloudlog.exception("Failed to connect to system D-Bus") + self._router_main = None + self._conn_monitor = None self._exit = True # Store wifi device path @@ -752,6 +754,8 @@ class WifiManager: if self._state_thread.is_alive(): self._state_thread.join() - self._router_main.close() - self._router_main.conn.close() - self._conn_monitor.close() + if self._router_main is not None: + self._router_main.close() + self._router_main.conn.close() + if self._conn_monitor is not None: + self._conn_monitor.close() From 3a001dd71cf7d80e9c505368b9089bc8e53672e0 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 19 Nov 2025 14:42:03 -0800 Subject: [PATCH 394/910] bump msgq --- msgq_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/msgq_repo b/msgq_repo index 89096d90d..a16cf1f60 160000 --- a/msgq_repo +++ b/msgq_repo @@ -1 +1 @@ -Subproject commit 89096d90d2f0f71be63a4af0152fe3b2aa55cf9d +Subproject commit a16cf1f608538d14f66bd6142230d8728f2d0abc From f0d8ebd85140e906b85a4e5c84833d84303d9a3f Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 19 Nov 2025 15:03:28 -0800 Subject: [PATCH 395/910] mici training guide tuneups (#36652) * bump up size * lil more * rm param * 5m timeout and 100% brightness * set parasm: --- common/params_keys.h | 2 - selfdrive/ui/layouts/onboarding.py | 11 +++-- selfdrive/ui/mici/layouts/onboarding.py | 44 ++++++++++++------- .../ui/tests/test_ui/raylib_screenshots.py | 5 +++ selfdrive/ui/ui_state.py | 5 ++- system/manager/manager.py | 4 +- system/ui/mici_setup.py | 5 +++ system/version.py | 6 --- 8 files changed, 48 insertions(+), 34 deletions(-) diff --git a/common/params_keys.h b/common/params_keys.h index 8d4c8d9e4..bf410825f 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -113,8 +113,6 @@ inline static std::unordered_map keys = { {"RouteCount", {PERSISTENT, INT, "0"}}, {"SnoozeUpdate", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}}, {"SshEnabled", {PERSISTENT, BOOL}}, - {"TermsVersion", {PERSISTENT, STRING}}, - {"TrainingVersion", {PERSISTENT, STRING}}, {"UbloxAvailable", {PERSISTENT, BOOL}}, {"UpdateAvailable", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BOOL}}, {"UpdateFailedCount", {CLEAR_ON_MANAGER_START, INT}}, diff --git a/selfdrive/ui/layouts/onboarding.py b/selfdrive/ui/layouts/onboarding.py index df259a8fb..5d61c1c95 100644 --- a/selfdrive/ui/layouts/onboarding.py +++ b/selfdrive/ui/layouts/onboarding.py @@ -11,6 +11,7 @@ from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.button import Button, ButtonStyle from openpilot.system.ui.widgets.label import Label from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.system.version import terms_version, training_version DEBUG = False @@ -169,10 +170,8 @@ class DeclinePage(Widget): class OnboardingWindow(Widget): def __init__(self): super().__init__() - self._current_terms_version = ui_state.params.get("TermsVersion") - self._current_training_version = ui_state.params.get("TrainingVersion") - self._accepted_terms: bool = ui_state.params.get("HasAcceptedTerms") == self._current_terms_version - self._training_done: bool = ui_state.params.get("CompletedTrainingVersion") == self._current_training_version + self._accepted_terms: bool = ui_state.params.get("HasAcceptedTerms") == terms_version + self._training_done: bool = ui_state.params.get("CompletedTrainingVersion") == training_version self._state = OnboardingState.TERMS if not self._accepted_terms else OnboardingState.ONBOARDING @@ -192,13 +191,13 @@ class OnboardingWindow(Widget): self._state = OnboardingState.TERMS def _on_terms_accepted(self): - ui_state.params.put("HasAcceptedTerms", self._current_terms_version) + ui_state.params.put("HasAcceptedTerms", terms_version) self._state = OnboardingState.ONBOARDING if self._training_done: gui_app.set_modal_overlay(None) def _on_completed_training(self): - ui_state.params.put("CompletedTrainingVersion", self._current_training_version) + ui_state.params.put("CompletedTrainingVersion", training_version) gui_app.set_modal_overlay(None) def _render(self, _): diff --git a/selfdrive/ui/mici/layouts/onboarding.py b/selfdrive/ui/mici/layouts/onboarding.py index a3f859208..d140a1744 100644 --- a/selfdrive/ui/mici/layouts/onboarding.py +++ b/selfdrive/ui/mici/layouts/onboarding.py @@ -15,6 +15,7 @@ from openpilot.selfdrive.ui.mici.onroad.driver_state import DriverStateRenderer from openpilot.selfdrive.ui.mici.onroad.driver_camera_dialog import DriverCameraDialog from openpilot.system.ui.widgets.label import gui_label from openpilot.system.ui.lib.multilang import tr +from openpilot.system.version import terms_version, training_version class OnboardingState(IntEnum): @@ -60,7 +61,7 @@ class TrainingGuideIntro(SetupTermsPage): self._title_header = TermsHeader("welcome to openpilot", gui_app.texture("icons_mici/offroad_alerts/green_wheel.png", 60, 60)) self._dm_label = UnifiedLabel("Before we get on the road, let's review the " + - "functionality and limitations of openpilot.", 36, + "functionality and limitations of openpilot.", 42, FontWeight.ROMAN) @property @@ -90,7 +91,7 @@ class TrainingGuidePreDMTutorial(SetupTermsPage): self._dm_label = UnifiedLabel("Next, we'll ensure comma four is mounted properly.\n\nIf it does not have a clear view of the driver, " + "simply unplug and remount before continuing.\n\n" + - "NOTE: the driver camera will have a purple tint due to the IR illumination used for seeing at night.", 36, + "NOTE: the driver camera will have a purple tint due to the IR illumination used for seeing at night.", 42, FontWeight.ROMAN) def show_event(self): @@ -123,7 +124,14 @@ class TrainingGuideDMTutorial(Widget): super().__init__() self._title_header = TermsHeader("fill the circle to continue", gui_app.texture("icons_mici/setup/green_dm.png", 60, 60)) - self._dialog = DriverCameraSetupDialog(continue_callback) + self._original_continue_callback = continue_callback + + # Wrap the continue callback to restore settings + def wrapped_continue_callback(): + self._restore_settings() + continue_callback() + + self._dialog = DriverCameraSetupDialog(wrapped_continue_callback) # Disable driver monitoring model when device times out for inactivity def inactivity_callback(): @@ -135,6 +143,13 @@ class TrainingGuideDMTutorial(Widget): super().show_event() self._dialog.show_event() + device.set_offroad_brightness(100) + device.reset_interactive_timeout(300) # 5 minutes + + def _restore_settings(self): + device.set_offroad_brightness(None) + device.reset_interactive_timeout() + def _update_state(self): super()._update_state() if device.awake: @@ -168,7 +183,7 @@ class TrainingGuideRecordFront(SetupTermsPage): self._title_header = TermsHeader("improve driver monitoring", gui_app.texture("icons_mici/setup/green_dm.png", 60, 60)) self._dm_label = UnifiedLabel("Help improve driver monitoring by including your driving data in the training data set. " + - "Your preference can be changed at any time in Settings. Would you like to share your data?", 36, + "Your preference can be changed at any time in Settings. Would you like to share your data?", 42, FontWeight.ROMAN) def show_event(self): @@ -200,7 +215,7 @@ class TrainingGuideAttentionNotice1(SetupTermsPage): def __init__(self, continue_callback): super().__init__(continue_callback, continue_text="continue") self._title_header = TermsHeader("not a self driving car", gui_app.texture("icons_mici/setup/warning.png", 60, 60)) - self._warning_label = UnifiedLabel("THIS IS A DRIVER ASSISTANCE SYSTEM. A DRIVER ASSISTANCE SYSTEM IS NOT A SELF DRIVING CAR.", 36, + self._warning_label = UnifiedLabel("THIS IS A DRIVER ASSISTANCE SYSTEM. A DRIVER ASSISTANCE SYSTEM IS NOT A SELF-DRIVING CAR.", 42, FontWeight.ROMAN) @property @@ -227,7 +242,8 @@ class TrainingGuideAttentionNotice2(SetupTermsPage): def __init__(self, continue_callback): super().__init__(continue_callback, continue_text="continue") self._title_header = TermsHeader("attention is required", gui_app.texture("icons_mici/setup/warning.png", 60, 60)) - self._warning_label = UnifiedLabel("YOU MUST PAY ATTENTION AT ALL TIMES. YOU ARE FULLY RESPONSIBLE FOR DRIVING THE CAR.", 36, + self._warning_label = UnifiedLabel("1. You must pay attention at all times.\n\n2. You must be ready to take over at any time."+ + "\n\n3. You are fully responsible for driving the car.", 42, FontWeight.ROMAN) @property @@ -255,7 +271,7 @@ class TrainingGuideDisengaging(SetupTermsPage): super().__init__(continue_callback, continue_text="continue") self._title_header = TermsHeader("disengaging openpilot", gui_app.texture("icons_mici/setup/green_pedal.png", 60, 60)) self._warning_label = UnifiedLabel("You can disengage openpilot by either pressing the brake pedal or " + - "the cancel button on your steering wheel.", 36, + "the cancel button on your steering wheel.", 42, FontWeight.ROMAN) @property @@ -288,7 +304,7 @@ class TrainingGuideConfidenceBall(SetupTermsPage): self._title_header = TermsHeader("confidence ball", gui_app.texture("icons_mici/setup/green_car.png", 60, 60)) self._warning_label = UnifiedLabel("The ball on the right communicates how confident openpilot " + - "is about the road scene at any given time.", 36, + "is about the road scene at any given time.", 42, FontWeight.ROMAN) def show_event(self): @@ -343,7 +359,7 @@ class TrainingGuideSteeringArc(SetupTermsPage): self._title_header = TermsHeader("steering arc", gui_app.texture("icons_mici/offroad_alerts/green_wheel.png", 60, 60)) self._warning_label = UnifiedLabel("All cars limit the amount of steering that openpilot is able to apply. While driving, the " + "steering arc shows the current amount of force being applied in relation to the maximum available to openpilot. " + - "You may need to assist if you see the arc nearing its orange state.", 36, + "You may need to assist if you see the arc nearing its orange state.", 42, FontWeight.ROMAN) def show_event(self): @@ -506,10 +522,8 @@ class TermsPage(SetupTermsPage): class OnboardingWindow(Widget): def __init__(self): super().__init__() - self._current_terms_version = ui_state.params.get("TermsVersion") - self._current_training_version = ui_state.params.get("TrainingVersion") - self._accepted_terms: bool = ui_state.params.get("HasAcceptedTerms") == self._current_terms_version - self._training_done: bool = ui_state.params.get("CompletedTrainingVersion") == self._current_training_version + self._accepted_terms: bool = ui_state.params.get("HasAcceptedTerms") == terms_version + self._training_done: bool = ui_state.params.get("CompletedTrainingVersion") == training_version self._state = OnboardingState.TERMS if not self._accepted_terms else OnboardingState.ONBOARDING @@ -535,11 +549,11 @@ class OnboardingWindow(Widget): gui_app.set_modal_overlay(None) def _on_terms_accepted(self): - ui_state.params.put("HasAcceptedTerms", self._current_terms_version) + ui_state.params.put("HasAcceptedTerms", terms_version) self._state = OnboardingState.ONBOARDING def _on_completed_training(self): - ui_state.params.put("CompletedTrainingVersion", self._current_training_version) + ui_state.params.put("CompletedTrainingVersion", training_version) self.close() def _render(self, _): diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py index 3d476c431..481ac111b 100755 --- a/selfdrive/ui/tests/test_ui/raylib_screenshots.py +++ b/selfdrive/ui/tests/test_ui/raylib_screenshots.py @@ -18,6 +18,7 @@ from openpilot.common.prefix import OpenpilotPrefix from openpilot.selfdrive.test.helpers import with_processes from openpilot.selfdrive.selfdrived.alertmanager import set_offroad_alert from openpilot.system.updated.updated import parse_release_notes +from openpilot.system.version import terms_version, training_version AlertSize = log.SelfdriveState.AlertSize AlertStatus = log.SelfdriveState.AlertStatus @@ -298,6 +299,10 @@ def create_screenshots(): params.put("UpdaterCurrentDescription", VERSION) params.put("UpdaterNewDescription", VERSION) + # Set terms and training version (to skip onboarding) + params.put("HasAcceptedTerms", terms_version) + params.put("CompletedTrainingVersion", training_version) + if name == "homescreen_paired": params.put("PrimeType", 0) # NONE elif name == "homescreen_prime": diff --git a/selfdrive/ui/ui_state.py b/selfdrive/ui/ui_state.py index 4a6ff9ebd..ef0696a22 100644 --- a/selfdrive/ui/ui_state.py +++ b/selfdrive/ui/ui_state.py @@ -217,8 +217,9 @@ class Device: self._update_brightness() self._update_wakefulness() - def set_offroad_brightness(self, brightness: int): - # TODO: not yet used, should be used in prime widget for QR code, etc. + def set_offroad_brightness(self, brightness: int | None): + if brightness is None: + brightness = BACKLIGHT_OFFROAD self._offroad_brightness = min(max(brightness, 0), 100) def _update_brightness(self): diff --git a/system/manager/manager.py b/system/manager/manager.py index 36055d863..8db13346e 100755 --- a/system/manager/manager.py +++ b/system/manager/manager.py @@ -17,7 +17,7 @@ from openpilot.system.manager.process import ensure_running from openpilot.system.manager.process_config import managed_processes from openpilot.system.athena.registration import register, UNREGISTERED_DONGLE_ID from openpilot.common.swaglog import cloudlog, add_file_handler -from openpilot.system.version import get_build_metadata, terms_version, training_version +from openpilot.system.version import get_build_metadata from openpilot.system.hardware.hw import Paths @@ -54,8 +54,6 @@ def manager_init() -> None: # set params serial = HARDWARE.get_serial() params.put("Version", build_metadata.openpilot.version) - params.put("TermsVersion", terms_version) - params.put("TrainingVersion", training_version) params.put("GitCommit", build_metadata.openpilot.git_commit) params.put("GitCommitDate", build_metadata.openpilot.git_commit_date) params.put("GitBranch", build_metadata.channel) diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 4afd69bfa..d7395f9b7 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -18,6 +18,7 @@ from openpilot.common.utils import run_cmd from openpilot.system.hardware import HARDWARE from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.lib.wifi_manager import WifiManager +from openpilot.selfdrive.ui.ui_state import device from openpilot.system.ui.lib.scroll_panel2 import GuiScrollPanel2 from openpilot.system.ui.widgets import Widget, DialogResult from openpilot.system.ui.widgets.button import (IconButton, SmallButton, WideRoundedButton, SmallerRoundedButton, @@ -225,6 +226,10 @@ class TermsPage(Widget): self._back_button.set_opacity(0.0) self._scroll_down_indicator.set_opacity(1.0) + def show_event(self): + super().show_event() + device.reset_interactive_timeout(300) + @property @abstractmethod def _content_height(self): diff --git a/system/version.py b/system/version.py index f59509715..1f01b181c 100755 --- a/system/version.py +++ b/system/version.py @@ -157,10 +157,4 @@ def get_build_metadata(path: str = BASEDIR) -> BuildMetadata: if __name__ == "__main__": - from openpilot.common.params import Params - - params = Params() - params.put("TermsVersion", terms_version) - params.put("TrainingVersion", training_version) - print(get_build_metadata()) From a29fdbd02407d41ecbcc69d151bb4837bfba3cbc Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 19 Nov 2025 15:43:49 -0800 Subject: [PATCH 396/910] enhance dcam a bit for onboarding (#36655) --- selfdrive/ui/mici/onroad/cameraview.py | 28 +++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/selfdrive/ui/mici/onroad/cameraview.py b/selfdrive/ui/mici/onroad/cameraview.py index f962210af..0f425b10d 100644 --- a/selfdrive/ui/mici/onroad/cameraview.py +++ b/selfdrive/ui/mici/onroad/cameraview.py @@ -47,6 +47,7 @@ if TICI: uniform samplerExternalOES texture0; out vec4 fragColor; uniform int engaged; + uniform int enhance_driver; void main() { vec4 color = texture(texture0, fragTexCoord); @@ -57,8 +58,16 @@ if TICI: color.rgb = pow(color.rgb, vec3(1.0/1.28)); fragColor = vec4(color.rgb, color.a); } else { - fragColor = vec4(color.rgb * 0.85, color.a); // 85% opacity + color.rgb *= 0.85; // 85% opacity } + if (enhance_driver == 1) { + float brightness = 1.1; + color.rgb = color.rgb + 0.15; + color.rgb = clamp((color.rgb - 0.5) * (brightness * 0.8) + 0.5, 0.0, 1.0); + color.rgb = color.rgb * color.rgb * (3.0 - 2.0 * color.rgb); + color.rgb = pow(color.rgb, vec3(0.8)); + } + fragColor = vec4(color.rgb, color.a); } """ else: @@ -68,6 +77,7 @@ else: uniform sampler2D texture1; out vec4 fragColor; uniform int engaged; + uniform int enhance_driver; void main() { float y = texture(texture0, fragTexCoord).r; @@ -77,10 +87,19 @@ else: float gray = dot(rgb, vec3(0.299, 0.587, 0.114)); rgb = mix(vec3(gray), rgb, 0.2); // 20% saturation rgb = clamp((rgb - 0.5) * 1.2 + 0.5, 0.0, 1.0); // +20% contrast - fragColor = vec4(rgb, 1.0); } else { - fragColor = vec4(rgb * 0.85, 1.0); // 85% opacity + rgb *= 0.85; // 85% opacity } + // TODO: the images out of camerad need some more correction and + // the ui should apply a gamma curve for the device display + if (enhance_driver == 1) { + float brightness = 1.1; + rgb = rgb + 0.15; + rgb = clamp((rgb - 0.5) * (brightness * 0.8) + 0.5, 0.0, 1.0); + rgb = rgb * rgb * (3.0 - 2.0 * rgb); + rgb = pow(rgb, vec3(0.8)); + } + fragColor = vec4(rgb, 1.0); } """ @@ -106,6 +125,8 @@ class CameraView(Widget): self._texture1_loc: int = rl.get_shader_location(self.shader, "texture1") if not TICI else -1 self._engaged_loc = rl.get_shader_location(self.shader, "engaged") self._engaged_val = rl.ffi.new("int[1]", [1]) + self._enhance_driver_loc = rl.get_shader_location(self.shader, "enhance_driver") + self._enhance_driver_val = rl.ffi.new("int[1]", [1 if stream_type == VisionStreamType.VISION_STREAM_DRIVER else 0]) self.frame: VisionBuf | None = None self.texture_y: rl.Texture | None = None @@ -300,6 +321,7 @@ class CameraView(Widget): def _update_texture_color_filtering(self): self._engaged_val[0] = 1 if ui_state.status != UIStatus.DISENGAGED else 0 rl.set_shader_value(self.shader, self._engaged_loc, self._engaged_val, rl.ShaderUniformDataType.SHADER_UNIFORM_INT) + rl.set_shader_value(self.shader, self._enhance_driver_loc, self._enhance_driver_val, rl.ShaderUniformDataType.SHADER_UNIFORM_INT) def _ensure_connection(self) -> bool: if not self.client.is_connected(): From 5c10e7f6cf7fe037cd573064ddde4889c50e3d40 Mon Sep 17 00:00:00 2001 From: Trey Moen <50057480+greatgitsby@users.noreply.github.com> Date: Wed, 19 Nov 2025 22:20:47 -0800 Subject: [PATCH 397/910] fix: openpilot unavailable with replay (#36658) fix: openpilot unavailable --- selfdrive/ui/mici/onroad/driver_camera_dialog.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/selfdrive/ui/mici/onroad/driver_camera_dialog.py b/selfdrive/ui/mici/onroad/driver_camera_dialog.py index f2fa5e8fe..2f7f10b95 100644 --- a/selfdrive/ui/mici/onroad/driver_camera_dialog.py +++ b/selfdrive/ui/mici/onroad/driver_camera_dialog.py @@ -24,7 +24,6 @@ class DriverCameraDialog(NavWidget): self.driver_state_renderer = DriverStateRenderer(lines=True) self.driver_state_renderer.set_rect(rl.Rectangle(0, 0, 200, 200)) self.driver_state_renderer.load_icons() - self._pm = messaging.PubMaster(['selfdriveState']) if not no_escape: # TODO: this can grow unbounded, should be given some thought device.add_interactive_timeout_callback(self.stop_dmonitoringmodeld) @@ -50,10 +49,12 @@ class DriverCameraDialog(NavWidget): self._publish_alert_sound(None) device.reset_interactive_timeout(300) ui_state.params.remove("DriverTooDistracted") + self._pm = messaging.PubMaster(['selfdriveState']) def hide_event(self): super().hide_event() device.reset_interactive_timeout() + self._pm = None def _handle_mouse_release(self, _): ui_state.params.remove("DriverTooDistracted") From 281f75270af1a10a3347b126c05cb79cf6cfbab1 Mon Sep 17 00:00:00 2001 From: infiniteCable2 Date: Thu, 20 Nov 2025 19:16:30 +0100 Subject: [PATCH 398/910] fix plattform detection --- opendbc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc_repo b/opendbc_repo index 4f29eb6cc..f7145f36d 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 4f29eb6cc0c2133eef09bbd96c8341c2dc4669df +Subproject commit f7145f36dfccb256c26d1b64a754b30a06307b70 From fa56d539a7cb29b695d101f2d90554989b0ff82c Mon Sep 17 00:00:00 2001 From: YassineYousfi Date: Thu, 20 Nov 2025 10:28:19 -0800 Subject: [PATCH 399/910] dm: phone offseter class + log stats (#36656) * dm: phone offseter class + log stats * lint:/ --- cereal/log.capnp | 2 ++ selfdrive/monitoring/helpers.py | 37 ++++++++++++++++++++------------- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/cereal/log.capnp b/cereal/log.capnp index 86774b8d4..5fce83957 100644 --- a/cereal/log.capnp +++ b/cereal/log.capnp @@ -2226,6 +2226,8 @@ struct DriverMonitoringState @0xb83cda094a1da284 { isActiveMode @16 :Bool; isRHD @4 :Bool; uncertainCount @19 :UInt32; + phoneProbOffset @20 :Float32; + phoneProbalidCount @21 :UInt32; isPreviewDEPRECATED @15 :Bool; rhdCheckedDEPRECATED @5 :Bool; diff --git a/selfdrive/monitoring/helpers.py b/selfdrive/monitoring/helpers.py index 208fc1676..4b216a496 100644 --- a/selfdrive/monitoring/helpers.py +++ b/selfdrive/monitoring/helpers.py @@ -98,6 +98,12 @@ class DriverPose: self.cfactor_pitch = 1. self.cfactor_yaw = 1. +class DriverPhone: + def __init__(self, max_trackable): + self.prob = 0. + self.prob_offseter = RunningStatFilter(max_trackable=max_trackable) + self.prob_calibrated = False + class DriverBlink: def __init__(self): self.left = 0. @@ -136,10 +142,8 @@ class DriverMonitoring: # init driver status self.wheelpos_learner = RunningStatFilter() self.pose = DriverPose(self.settings._POSE_OFFSET_MAX_COUNT) + self.phone = DriverPhone(self.settings._POSE_OFFSET_MAX_COUNT) self.blink = DriverBlink() - self.phone_prob = 0. - self.phone_offseter = RunningStatFilter(max_trackable=self.settings._POSE_OFFSET_MAX_COUNT) - self.phone_calibrated = False self.always_on = always_on self.distracted_types = [] @@ -237,11 +241,11 @@ class DriverMonitoring: if (self.blink.left + self.blink.right)*0.5 > self.settings._BLINK_THRESHOLD: distracted_types.append(DistractedType.DISTRACTED_BLINK) - if self.phone_calibrated: - using_phone = self.phone_prob > max(min(self.phone_offseter.filtered_stat.M, self.settings._PHONE_MAX_OFFSET), self.settings._PHONE_MIN_OFFSET) \ - * self.settings._PHONE_THRESH2 + if self.phone.prob_calibrated: + using_phone = self.phone.prob > max(min(self.phone_offseter.filtered_stat.M, self.settings._PHONE_MAX_OFFSET), self.settings._PHONE_MIN_OFFSET) \ + * self.settings._PHONE_THRESH2 else: - using_phone = self.phone_prob > self.settings._PHONE_THRESH + using_phone = self.phone.prob > self.settings._PHONE_THRESH if using_phone: distracted_types.append(DistractedType.DISTRACTED_PHONE) @@ -275,14 +279,15 @@ class DriverMonitoring: model_std_max = max(self.pose.pitch_std, self.pose.yaw_std) self.pose.low_std = model_std_max < self.settings._POSESTD_THRESHOLD self.blink.left = driver_data.leftBlinkProb * (driver_data.leftEyeProb > self.settings._EYE_THRESHOLD) \ - * (driver_data.sunglassesProb < self.settings._SG_THRESHOLD) + * (driver_data.sunglassesProb < self.settings._SG_THRESHOLD) self.blink.right = driver_data.rightBlinkProb * (driver_data.rightEyeProb > self.settings._EYE_THRESHOLD) \ - * (driver_data.sunglassesProb < self.settings._SG_THRESHOLD) - self.phone_prob = driver_data.phoneProb + * (driver_data.sunglassesProb < self.settings._SG_THRESHOLD) + self.phone.prob = driver_data.phoneProb self.distracted_types = self._get_distracted_types() - self.driver_distracted = (DistractedType.DISTRACTED_PHONE in self.distracted_types or DistractedType.DISTRACTED_POSE in self.distracted_types - or DistractedType.DISTRACTED_BLINK in self.distracted_types) \ + self.driver_distracted = (DistractedType.DISTRACTED_PHONE in self.distracted_types + or DistractedType.DISTRACTED_POSE in self.distracted_types + or DistractedType.DISTRACTED_BLINK in self.distracted_types) \ and driver_data.faceProb > self.settings._FACE_THRESHOLD and self.pose.low_std self.driver_distraction_filter.update(self.driver_distracted) @@ -291,11 +296,11 @@ class DriverMonitoring: if self.face_detected and car_speed > self.settings._POSE_CALIB_MIN_SPEED and self.pose.low_std and (not op_engaged or not self.driver_distracted): self.pose.pitch_offseter.push_and_update(self.pose.pitch) self.pose.yaw_offseter.push_and_update(self.pose.yaw) - self.phone_offseter.push_and_update(self.phone_prob) + self.phone.prob_offseter.push_and_update(self.phone.prob) self.pose.calibrated = self.pose.pitch_offseter.filtered_stat.n > self.settings._POSE_OFFSET_MIN_COUNT and \ - self.pose.yaw_offseter.filtered_stat.n > self.settings._POSE_OFFSET_MIN_COUNT - self.phone_calibrated = self.phone_offseter.filtered_stat.n > self.settings._POSE_OFFSET_MIN_COUNT + self.pose.yaw_offseter.filtered_stat.n > self.settings._POSE_OFFSET_MIN_COUNT + self.phone.prob_calibrated = self.phone.prob_offseter.filtered_stat.n > self.settings._POSE_OFFSET_MIN_COUNT if self.face_detected and not self.driver_distracted: if model_std_max > self.settings._DCAM_UNCERTAIN_ALERT_THRESHOLD: @@ -401,6 +406,8 @@ class DriverMonitoring: "posePitchValidCount": self.pose.pitch_offseter.filtered_stat.n, "poseYawOffset": self.pose.yaw_offseter.filtered_stat.mean(), "poseYawValidCount": self.pose.yaw_offseter.filtered_stat.n, + "phoneProbOffset": self.phone.prob_offseter.filtered_stat.mean(), + "phoneProbalidCount": self.phone.prob_offseter.filtered_stat.n, "stepChange": self.step_change, "awarenessActive": self.awareness_active, "awarenessPassive": self.awareness_passive, From 38697ac6282f3b88a45295692c41229fc7ce1c71 Mon Sep 17 00:00:00 2001 From: YassineYousfi Date: Thu, 20 Nov 2025 12:30:28 -0800 Subject: [PATCH 400/910] Fix typo in phoneProbValidCount field name (#36662) * Fix typo in phoneProbValidCount field name * Fix typo in phoneProbValidCount key --- cereal/log.capnp | 2 +- selfdrive/monitoring/helpers.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cereal/log.capnp b/cereal/log.capnp index 5fce83957..686771e28 100644 --- a/cereal/log.capnp +++ b/cereal/log.capnp @@ -2227,7 +2227,7 @@ struct DriverMonitoringState @0xb83cda094a1da284 { isRHD @4 :Bool; uncertainCount @19 :UInt32; phoneProbOffset @20 :Float32; - phoneProbalidCount @21 :UInt32; + phoneProbValidCount @21 :UInt32; isPreviewDEPRECATED @15 :Bool; rhdCheckedDEPRECATED @5 :Bool; diff --git a/selfdrive/monitoring/helpers.py b/selfdrive/monitoring/helpers.py index 4b216a496..4d9e2e7b0 100644 --- a/selfdrive/monitoring/helpers.py +++ b/selfdrive/monitoring/helpers.py @@ -407,7 +407,7 @@ class DriverMonitoring: "poseYawOffset": self.pose.yaw_offseter.filtered_stat.mean(), "poseYawValidCount": self.pose.yaw_offseter.filtered_stat.n, "phoneProbOffset": self.phone.prob_offseter.filtered_stat.mean(), - "phoneProbalidCount": self.phone.prob_offseter.filtered_stat.n, + "phoneProbValidCount": self.phone.prob_offseter.filtered_stat.n, "stepChange": self.step_change, "awarenessActive": self.awareness_active, "awarenessPassive": self.awareness_passive, From be700bc8250b894c7a63916d02fcbb63aa24e03e Mon Sep 17 00:00:00 2001 From: ZwX1616 Date: Thu, 20 Nov 2025 13:42:20 -0800 Subject: [PATCH 401/910] fix rhd behavior during preview/onboarding (#36657) * rhd learning not required for demo * fix switching and saving --------- Co-authored-by: Comma Device --- selfdrive/monitoring/dmonitoringd.py | 2 +- selfdrive/monitoring/helpers.py | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/selfdrive/monitoring/dmonitoringd.py b/selfdrive/monitoring/dmonitoringd.py index 293904a8e..1dc256d46 100755 --- a/selfdrive/monitoring/dmonitoringd.py +++ b/selfdrive/monitoring/dmonitoringd.py @@ -38,7 +38,7 @@ def dmonitoringd_thread(): demo_mode = params.get_bool("IsDriverViewEnabled") # save rhd virtual toggle every 5 mins - if (sm['driverStateV2'].frameId % 6000 == 0 and + if (sm['driverStateV2'].frameId % 6000 == 0 and not demo_mode and DM.wheelpos_learner.filtered_stat.n > DM.settings._WHEELPOS_FILTER_MIN_COUNT and DM.wheel_on_right == (DM.wheelpos_learner.filtered_stat.M > DM.settings._WHEELPOS_THRESHOLD)): params.put_bool_nonblocking("IsRhdDetected", DM.wheel_on_right) diff --git a/selfdrive/monitoring/helpers.py b/selfdrive/monitoring/helpers.py index 4d9e2e7b0..9b9282bf4 100644 --- a/selfdrive/monitoring/helpers.py +++ b/selfdrive/monitoring/helpers.py @@ -251,18 +251,18 @@ class DriverMonitoring: return distracted_types - def _update_states(self, driver_state, cal_rpy, car_speed, op_engaged, standstill): + def _update_states(self, driver_state, cal_rpy, car_speed, op_engaged, standstill, demo_mode=False): rhd_pred = driver_state.wheelOnRightProb # calibrates only when there's movement and either face detected if car_speed > self.settings._WHEELPOS_CALIB_MIN_SPEED and (driver_state.leftDriverData.faceProb > self.settings._FACE_THRESHOLD or driver_state.rightDriverData.faceProb > self.settings._FACE_THRESHOLD): self.wheelpos_learner.push_and_update(rhd_pred) - if self.wheelpos_learner.filtered_stat.n > self.settings._WHEELPOS_FILTER_MIN_COUNT: + if self.wheelpos_learner.filtered_stat.n > self.settings._WHEELPOS_FILTER_MIN_COUNT or demo_mode: self.wheel_on_right = self.wheelpos_learner.filtered_stat.M > self.settings._WHEELPOS_THRESHOLD else: self.wheel_on_right = self.wheel_on_right_default # use default/saved if calibration is unfinished # make sure no switching when engaged - if op_engaged and self.wheel_on_right_last is not None and self.wheel_on_right_last != self.wheel_on_right: + if op_engaged and self.wheel_on_right_last is not None and self.wheel_on_right_last != self.wheel_on_right and not demo_mode: self.wheel_on_right = self.wheel_on_right_last driver_data = driver_state.rightDriverData if self.wheel_on_right else driver_state.leftDriverData if not all(len(x) > 0 for x in (driver_data.faceOrientation, driver_data.facePosition, @@ -448,6 +448,7 @@ class DriverMonitoring: car_speed=highway_speed, op_engaged=enabled, standstill=standstill, + demo_mode=demo, ) # Update distraction events From 6f89473f33d901d17fac4f5ce81f16cd0f07d75b Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 20 Nov 2025 13:50:35 -0800 Subject: [PATCH 402/910] Revert "fix: openpilot unavailable with replay (#36658)" This reverts commit 5c10e7f6cf7fe037cd573064ddde4889c50e3d40. --- selfdrive/ui/mici/onroad/driver_camera_dialog.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/selfdrive/ui/mici/onroad/driver_camera_dialog.py b/selfdrive/ui/mici/onroad/driver_camera_dialog.py index 2f7f10b95..f2fa5e8fe 100644 --- a/selfdrive/ui/mici/onroad/driver_camera_dialog.py +++ b/selfdrive/ui/mici/onroad/driver_camera_dialog.py @@ -24,6 +24,7 @@ class DriverCameraDialog(NavWidget): self.driver_state_renderer = DriverStateRenderer(lines=True) self.driver_state_renderer.set_rect(rl.Rectangle(0, 0, 200, 200)) self.driver_state_renderer.load_icons() + self._pm = messaging.PubMaster(['selfdriveState']) if not no_escape: # TODO: this can grow unbounded, should be given some thought device.add_interactive_timeout_callback(self.stop_dmonitoringmodeld) @@ -49,12 +50,10 @@ class DriverCameraDialog(NavWidget): self._publish_alert_sound(None) device.reset_interactive_timeout(300) ui_state.params.remove("DriverTooDistracted") - self._pm = messaging.PubMaster(['selfdriveState']) def hide_event(self): super().hide_event() device.reset_interactive_timeout() - self._pm = None def _handle_mouse_release(self, _): ui_state.params.remove("DriverTooDistracted") From b6bfa13b704cafd13bdc910806d5a1784cb6a166 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 20 Nov 2025 14:20:37 -0800 Subject: [PATCH 403/910] onboarding touchups, thanks nabeel! --- selfdrive/ui/mici/layouts/onboarding.py | 68 +++++++++++++++++++++++-- 1 file changed, 63 insertions(+), 5 deletions(-) diff --git a/selfdrive/ui/mici/layouts/onboarding.py b/selfdrive/ui/mici/layouts/onboarding.py index d140a1744..66a0919b0 100644 --- a/selfdrive/ui/mici/layouts/onboarding.py +++ b/selfdrive/ui/mici/layouts/onboarding.py @@ -183,7 +183,7 @@ class TrainingGuideRecordFront(SetupTermsPage): self._title_header = TermsHeader("improve driver monitoring", gui_app.texture("icons_mici/setup/green_dm.png", 60, 60)) self._dm_label = UnifiedLabel("Help improve driver monitoring by including your driving data in the training data set. " + - "Your preference can be changed at any time in Settings. Would you like to share your data?", 42, + "Your preference can be changed at any time in Settings.\n\nWould you like to share your data?", 42, FontWeight.ROMAN) def show_event(self): @@ -242,7 +242,7 @@ class TrainingGuideAttentionNotice2(SetupTermsPage): def __init__(self, continue_callback): super().__init__(continue_callback, continue_text="continue") self._title_header = TermsHeader("attention is required", gui_app.texture("icons_mici/setup/warning.png", 60, 60)) - self._warning_label = UnifiedLabel("1. You must pay attention at all times.\n\n2. You must be ready to take over at any time."+ + self._warning_label = UnifiedLabel("1. You must pay attention at all times.\n\n2. You must be ready to take over at any time." + "\n\n3. You are fully responsible for driving the car.", 42, FontWeight.ROMAN) @@ -265,13 +265,69 @@ class TrainingGuideAttentionNotice2(SetupTermsPage): self._warning_label.get_content_height(int(self._rect.width - 32)), )) +class TrainingGuideEngaging(SetupTermsPage): + def __init__(self, continue_callback): + super().__init__(continue_callback, continue_text="continue") + self._title_header = TermsHeader("engaging openpilot", gui_app.texture("icons_mici/offroad_alerts/green_wheel.png", 60, 60)) + self._warning_label = UnifiedLabel("You can engage openpilot using your car's cruise control inputs.\n\n" + + "These are usually located on either the steering wheel or on a lever near the wheel.", 42, + FontWeight.ROMAN) + + @property + def _content_height(self): + return self._warning_label.rect.y + self._warning_label.rect.height - self._scroll_panel.get_offset() + + def _render_content(self, scroll_offset): + self._title_header.render(rl.Rectangle( + self._rect.x + 16, + self._rect.y + 16 + scroll_offset, + self._title_header.rect.width, + self._title_header.rect.height, + )) + + self._warning_label.render(rl.Rectangle( + self._rect.x + 16, + self._title_header.rect.y + self._title_header.rect.height + 16, + self._rect.width - 32, + self._warning_label.get_content_height(int(self._rect.width - 32)), + )) + + +class TrainingGuideEnd(SetupTermsPage): + def __init__(self, continue_callback): + super().__init__(continue_callback, continue_text="finish") + self._title_header = TermsHeader("training complete!", gui_app.texture("icons_mici/setup/green_info.png", 60, 60)) + self._warning_label = UnifiedLabel("You have completed the openpilot training.\n\n" + + "This guide can be revisited at any time in Settings.", 42, + FontWeight.ROMAN) + + @property + def _content_height(self): + return self._warning_label.rect.y + self._warning_label.rect.height - self._scroll_panel.get_offset() + + def _render_content(self, scroll_offset): + self._title_header.render(rl.Rectangle( + self._rect.x + 16, + self._rect.y + 16 + scroll_offset, + self._title_header.rect.width, + self._title_header.rect.height, + )) + + self._warning_label.render(rl.Rectangle( + self._rect.x + 16, + self._title_header.rect.y + self._title_header.rect.height + 16, + self._rect.width - 32, + self._warning_label.get_content_height(int(self._rect.width - 32)), + )) + + class TrainingGuideDisengaging(SetupTermsPage): def __init__(self, continue_callback): super().__init__(continue_callback, continue_text="continue") self._title_header = TermsHeader("disengaging openpilot", gui_app.texture("icons_mici/setup/green_pedal.png", 60, 60)) self._warning_label = UnifiedLabel("You can disengage openpilot by either pressing the brake pedal or " + - "the cancel button on your steering wheel.", 42, + "the cruise control cancel button.", 42, FontWeight.ROMAN) @property @@ -352,7 +408,7 @@ class TrainingGuideSteeringArc(SetupTermsPage): TORQUE_BAR_HEIGHT = 100 def __init__(self, continue_callback): - super().__init__(continue_callback, continue_text="finish") + super().__init__(continue_callback, continue_text="continue") self._torque_bar = TorqueBar(demo=True) self._start_time = 0.0 @@ -433,9 +489,11 @@ class TrainingGuide(Widget): TrainingGuidePreDMTutorial(continue_callback=self._advance_step), TrainingGuideDMTutorial(continue_callback=self._advance_step), TrainingGuideRecordFront(continue_callback=self._advance_step), + TrainingGuideEngaging(continue_callback=self._advance_step), TrainingGuideDisengaging(continue_callback=self._advance_step), TrainingGuideConfidenceBall(continue_callback=self._advance_step), TrainingGuideSteeringArc(continue_callback=self._advance_step), + TrainingGuideEnd(continue_callback=self._advance_step), ] def _advance_step(self): @@ -497,7 +555,7 @@ class TermsPage(SetupTermsPage): super().__init__(on_accept, on_decline, "decline") info_txt = gui_app.texture("icons_mici/setup/green_info.png", 60, 60) - self._title_header = TermsHeader("scroll down to read &\n accept terms", info_txt) + self._title_header = TermsHeader("terms & conditions", info_txt) self._terms_label = UnifiedLabel("You must accept the Terms and Conditions to use openpilot. " + "Read the latest terms at https://comma.ai/terms before continuing.", 36, From a46af06baaf9a654211b9420963f02ad9f7a3d22 Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Thu, 20 Nov 2025 14:28:48 -0800 Subject: [PATCH 404/910] document `MAGIC_DEBUG=1` --- system/ui/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/system/ui/README.md b/system/ui/README.md index 21c8ab974..f81cb5573 100644 --- a/system/ui/README.md +++ b/system/ui/README.md @@ -9,6 +9,7 @@ Quick start: * set `SCALE=1.5` to scale the entire UI by 1.5x * set `BURN_IN=1` to get a burn-in heatmap version of the UI * set `GRID=50` to show a 50-pixel alignment grid overlay +* set `MAGIC_DEBUG=1` to show every dropped frames (only on device) * https://www.raylib.com/cheatsheet/cheatsheet.html * https://electronstudio.github.io/raylib-python-cffi/README.html#quickstart From 61fffb95787f7ce02bc5b6ed9b0eaa572aea83c8 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Fri, 21 Nov 2025 06:41:08 +0800 Subject: [PATCH 405/910] ui: avoid rendering off-viewport items in Scroller (#36659) avoid rendering off-viewport items in Scroller --- system/ui/widgets/scroller.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/system/ui/widgets/scroller.py b/system/ui/widgets/scroller.py index fb7f635be..9a04e8425 100644 --- a/system/ui/widgets/scroller.py +++ b/system/ui/widgets/scroller.py @@ -161,7 +161,6 @@ class Scroller(Widget): return self.scroll_panel.get_offset() def _render(self, _): - # TODO: don't draw items that are not in the viewport visible_items = [item for item in self._items if item.is_visible] # Add line separator between items @@ -219,6 +218,10 @@ class Scroller(Widget): item.set_position(round(x), round(y)) # round to prevent jumping when settling item.set_parent_rect(self._rect) + # Skip rendering if not in viewport + if not rl.check_collision_recs(item.rect, self._rect): + continue + # Scale each element around its own origin when scrolling scale = self._zoom_filter.x rl.rl_push_matrix() From 4378c4b8bbad1168b06af3de7438f64291bc0d8d Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 20 Nov 2025 14:55:16 -0800 Subject: [PATCH 406/910] ramp up IR faster for driver view (#36663) * ramp up IR faster for driver view * set it --- selfdrive/pandad/pandad.cc | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/selfdrive/pandad/pandad.cc b/selfdrive/pandad/pandad.cc index 2931eb4ac..a76cbc46e 100644 --- a/selfdrive/pandad/pandad.cc +++ b/selfdrive/pandad/pandad.cc @@ -365,14 +365,19 @@ void process_panda_state(std::vector &pandas, PubMaster *pm, bool engag } void process_peripheral_state(Panda *panda, PubMaster *pm, bool no_fan_control) { + static Params params; static SubMaster sm({"deviceState", "driverCameraState"}); static uint64_t last_driver_camera_t = 0; static uint16_t prev_fan_speed = 999; static int ir_pwr = 0; static int prev_ir_pwr = 999; + static uint32_t prev_frame_id = UINT32_MAX; + static bool driver_view = false; + // TODO: can we merge these? static FirstOrderFilter integ_lines_filter(0, 30.0, 0.05); + static FirstOrderFilter integ_lines_filter_driver_view(0, 5.0, 0.05); { sm.update(0); @@ -389,7 +394,15 @@ void process_peripheral_state(Panda *panda, PubMaster *pm, bool no_fan_control) auto event = sm["driverCameraState"]; int cur_integ_lines = event.getDriverCameraState().getIntegLines(); - cur_integ_lines = integ_lines_filter.update(cur_integ_lines); + // reset the filter when camerad restarts + if (event.getDriverCameraState().getFrameId() < prev_frame_id) { + integ_lines_filter.reset(0); + integ_lines_filter_driver_view.reset(0); + driver_view = params.getBool("IsDriverViewEnabled"); + } + prev_frame_id = event.getDriverCameraState().getFrameId(); + + cur_integ_lines = (driver_view ? integ_lines_filter_driver_view : integ_lines_filter).update(cur_integ_lines); last_driver_camera_t = event.getLogMonoTime(); if (cur_integ_lines <= CUTOFF_IL) { From f19ff793f58b22c052059e92595bd607df44621e Mon Sep 17 00:00:00 2001 From: YassineYousfi Date: Thu, 20 Nov 2025 16:27:58 -0800 Subject: [PATCH 407/910] dm more typo --- selfdrive/monitoring/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/monitoring/helpers.py b/selfdrive/monitoring/helpers.py index 9b9282bf4..7697e68b9 100644 --- a/selfdrive/monitoring/helpers.py +++ b/selfdrive/monitoring/helpers.py @@ -242,7 +242,7 @@ class DriverMonitoring: distracted_types.append(DistractedType.DISTRACTED_BLINK) if self.phone.prob_calibrated: - using_phone = self.phone.prob > max(min(self.phone_offseter.filtered_stat.M, self.settings._PHONE_MAX_OFFSET), self.settings._PHONE_MIN_OFFSET) \ + using_phone = self.phone.prob > max(min(self.phone.prob_offseter.filtered_stat.M, self.settings._PHONE_MAX_OFFSET), self.settings._PHONE_MIN_OFFSET) \ * self.settings._PHONE_THRESH2 else: using_phone = self.phone.prob > self.settings._PHONE_THRESH From 5151bb8bf2dee8ab1cc8452bfae07c912e45407d Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Thu, 20 Nov 2025 17:06:24 -0800 Subject: [PATCH 408/910] installer: use release-mici for comma four --- selfdrive/ui/installer/installer.cc | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/selfdrive/ui/installer/installer.cc b/selfdrive/ui/installer/installer.cc index 38dd1ce25..072fa4e24 100644 --- a/selfdrive/ui/installer/installer.cc +++ b/selfdrive/ui/installer/installer.cc @@ -66,6 +66,12 @@ void branchMigration() { } else if (BRANCH_STR == "release3-staging") { migrated_branch = "release-tizi-staging"; } + } else if (device_type == cereal::InitData::DeviceType::MICI) { + if (BRANCH_STR == "release3") { + migrated_branch = "release-mici"; + } else if (BRANCH_STR == "release3-staging") { + migrated_branch = "release-mici-staging"; + } } } From 8bc6becce1aa00eaa91b9916ad2db12725422cee Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 20 Nov 2025 18:54:54 -0800 Subject: [PATCH 409/910] simplify mici onboarding (#36666) * simplify mici onboarding * shorter * dead * cleanup --- selfdrive/ui/mici/layouts/onboarding.py | 293 +----------------------- 1 file changed, 9 insertions(+), 284 deletions(-) diff --git a/selfdrive/ui/mici/layouts/onboarding.py b/selfdrive/ui/mici/layouts/onboarding.py index 66a0919b0..afc7bfce1 100644 --- a/selfdrive/ui/mici/layouts/onboarding.py +++ b/selfdrive/ui/mici/layouts/onboarding.py @@ -9,8 +9,6 @@ from openpilot.system.ui.widgets.label import UnifiedLabel from openpilot.system.ui.widgets.slider import SmallSlider from openpilot.system.ui.mici_setup import TermsHeader, TermsPage as SetupTermsPage from openpilot.selfdrive.ui.ui_state import ui_state, device -from openpilot.selfdrive.ui.mici.onroad.confidence_ball import ConfidenceBall -from openpilot.selfdrive.ui.mici.onroad.torque_bar import TorqueBar from openpilot.selfdrive.ui.mici.onroad.driver_state import DriverStateRenderer from openpilot.selfdrive.ui.mici.onroad.driver_camera_dialog import DriverCameraDialog from openpilot.system.ui.widgets.label import gui_label @@ -55,43 +53,13 @@ class DriverCameraSetupDialog(DriverCameraDialog): return -1 -class TrainingGuideIntro(SetupTermsPage): - def __init__(self, continue_callback): - super().__init__(continue_callback, continue_text="continue") - self._title_header = TermsHeader("welcome to openpilot", gui_app.texture("icons_mici/offroad_alerts/green_wheel.png", 60, 60)) - - self._dm_label = UnifiedLabel("Before we get on the road, let's review the " + - "functionality and limitations of openpilot.", 42, - FontWeight.ROMAN) - - @property - def _content_height(self): - return self._dm_label.rect.y + self._dm_label.rect.height - self._scroll_panel.get_offset() - - def _render_content(self, scroll_offset): - self._title_header.render(rl.Rectangle( - self._rect.x + 16, - self._rect.y + 16 + scroll_offset, - self._title_header.rect.width, - self._title_header.rect.height, - )) - - self._dm_label.render(rl.Rectangle( - self._rect.x + 16, - self._title_header.rect.y + self._title_header.rect.height + 16, - self._rect.width - 32, - self._dm_label.get_content_height(int(self._rect.width - 32)), - )) - - class TrainingGuidePreDMTutorial(SetupTermsPage): def __init__(self, continue_callback): super().__init__(continue_callback, continue_text="continue") self._title_header = TermsHeader("driver monitoring setup", gui_app.texture("icons_mici/setup/green_dm.png", 60, 60)) self._dm_label = UnifiedLabel("Next, we'll ensure comma four is mounted properly.\n\nIf it does not have a clear view of the driver, " + - "simply unplug and remount before continuing.\n\n" + - "NOTE: the driver camera will have a purple tint due to the IR illumination used for seeing at night.", 42, + "unplug and remount before continuing.", 42, FontWeight.ROMAN) def show_event(self): @@ -182,8 +150,7 @@ class TrainingGuideRecordFront(SetupTermsPage): super().__init__(on_continue, back_callback=on_back, back_text="no", continue_text="yes") self._title_header = TermsHeader("improve driver monitoring", gui_app.texture("icons_mici/setup/green_dm.png", 60, 60)) - self._dm_label = UnifiedLabel("Help improve driver monitoring by including your driving data in the training data set. " + - "Your preference can be changed at any time in Settings.\n\nWould you like to share your data?", 42, + self._dm_label = UnifiedLabel("Do you want to upload driver camera data to improve driver monitoring?", 42, FontWeight.ROMAN) def show_event(self): @@ -211,11 +178,14 @@ class TrainingGuideRecordFront(SetupTermsPage): )) -class TrainingGuideAttentionNotice1(SetupTermsPage): +class TrainingGuideAttentionNotice(SetupTermsPage): def __init__(self, continue_callback): super().__init__(continue_callback, continue_text="continue") - self._title_header = TermsHeader("not a self driving car", gui_app.texture("icons_mici/setup/warning.png", 60, 60)) - self._warning_label = UnifiedLabel("THIS IS A DRIVER ASSISTANCE SYSTEM. A DRIVER ASSISTANCE SYSTEM IS NOT A SELF-DRIVING CAR.", 42, + self._title_header = TermsHeader("driver assistance", gui_app.texture("icons_mici/setup/warning.png", 60, 60)) + self._warning_label = UnifiedLabel("1. openpilot is a driver assistance system.\n\n" + + "2. You must pay attention at all times.\n\n" + + "3. You must be ready to take over at any time.\n\n" + + "4. You are fully responsible for driving the car.", 42, FontWeight.ROMAN) @property @@ -238,244 +208,6 @@ class TrainingGuideAttentionNotice1(SetupTermsPage): )) -class TrainingGuideAttentionNotice2(SetupTermsPage): - def __init__(self, continue_callback): - super().__init__(continue_callback, continue_text="continue") - self._title_header = TermsHeader("attention is required", gui_app.texture("icons_mici/setup/warning.png", 60, 60)) - self._warning_label = UnifiedLabel("1. You must pay attention at all times.\n\n2. You must be ready to take over at any time." + - "\n\n3. You are fully responsible for driving the car.", 42, - FontWeight.ROMAN) - - @property - def _content_height(self): - return self._warning_label.rect.y + self._warning_label.rect.height - self._scroll_panel.get_offset() - - def _render_content(self, scroll_offset): - self._title_header.render(rl.Rectangle( - self._rect.x + 16, - self._rect.y + 16 + scroll_offset, - self._title_header.rect.width, - self._title_header.rect.height, - )) - - self._warning_label.render(rl.Rectangle( - self._rect.x + 16, - self._title_header.rect.y + self._title_header.rect.height + 16, - self._rect.width - 32, - self._warning_label.get_content_height(int(self._rect.width - 32)), - )) - -class TrainingGuideEngaging(SetupTermsPage): - def __init__(self, continue_callback): - super().__init__(continue_callback, continue_text="continue") - self._title_header = TermsHeader("engaging openpilot", gui_app.texture("icons_mici/offroad_alerts/green_wheel.png", 60, 60)) - self._warning_label = UnifiedLabel("You can engage openpilot using your car's cruise control inputs.\n\n" + - "These are usually located on either the steering wheel or on a lever near the wheel.", 42, - FontWeight.ROMAN) - - @property - def _content_height(self): - return self._warning_label.rect.y + self._warning_label.rect.height - self._scroll_panel.get_offset() - - def _render_content(self, scroll_offset): - self._title_header.render(rl.Rectangle( - self._rect.x + 16, - self._rect.y + 16 + scroll_offset, - self._title_header.rect.width, - self._title_header.rect.height, - )) - - self._warning_label.render(rl.Rectangle( - self._rect.x + 16, - self._title_header.rect.y + self._title_header.rect.height + 16, - self._rect.width - 32, - self._warning_label.get_content_height(int(self._rect.width - 32)), - )) - - -class TrainingGuideEnd(SetupTermsPage): - def __init__(self, continue_callback): - super().__init__(continue_callback, continue_text="finish") - self._title_header = TermsHeader("training complete!", gui_app.texture("icons_mici/setup/green_info.png", 60, 60)) - self._warning_label = UnifiedLabel("You have completed the openpilot training.\n\n" + - "This guide can be revisited at any time in Settings.", 42, - FontWeight.ROMAN) - - @property - def _content_height(self): - return self._warning_label.rect.y + self._warning_label.rect.height - self._scroll_panel.get_offset() - - def _render_content(self, scroll_offset): - self._title_header.render(rl.Rectangle( - self._rect.x + 16, - self._rect.y + 16 + scroll_offset, - self._title_header.rect.width, - self._title_header.rect.height, - )) - - self._warning_label.render(rl.Rectangle( - self._rect.x + 16, - self._title_header.rect.y + self._title_header.rect.height + 16, - self._rect.width - 32, - self._warning_label.get_content_height(int(self._rect.width - 32)), - )) - - - -class TrainingGuideDisengaging(SetupTermsPage): - def __init__(self, continue_callback): - super().__init__(continue_callback, continue_text="continue") - self._title_header = TermsHeader("disengaging openpilot", gui_app.texture("icons_mici/setup/green_pedal.png", 60, 60)) - self._warning_label = UnifiedLabel("You can disengage openpilot by either pressing the brake pedal or " + - "the cruise control cancel button.", 42, - FontWeight.ROMAN) - - @property - def _content_height(self): - return self._warning_label.rect.y + self._warning_label.rect.height - self._scroll_panel.get_offset() - - def _render_content(self, scroll_offset): - self._title_header.render(rl.Rectangle( - self._rect.x + 16, - self._rect.y + 16 + scroll_offset, - self._title_header.rect.width, - self._title_header.rect.height, - )) - - self._warning_label.render(rl.Rectangle( - self._rect.x + 16, - self._title_header.rect.y + self._title_header.rect.height + 16, - self._rect.width - 32, - self._warning_label.get_content_height(int(self._rect.width - 32)), - )) - - -class TrainingGuideConfidenceBall(SetupTermsPage): - ANIMATION_PAUSE = 3.5 - - def __init__(self, continue_callback): - super().__init__(continue_callback, continue_text="continue") - self._confidence_ball = ConfidenceBall(demo=True) - self._start_time = 0.0 - - self._title_header = TermsHeader("confidence ball", gui_app.texture("icons_mici/setup/green_car.png", 60, 60)) - self._warning_label = UnifiedLabel("The ball on the right communicates how confident openpilot " + - "is about the road scene at any given time.", 42, - FontWeight.ROMAN) - - def show_event(self): - super().show_event() - self._start_time = rl.get_time() - - @property - def _content_height(self): - return self._warning_label.rect.y + self._warning_label.rect.height - self._scroll_panel.get_offset() - - def _render_content(self, scroll_offset): - self._title_header.render(rl.Rectangle( - self._rect.x + 16, - self._rect.y + 16 + scroll_offset, - self._title_header.rect.width, - self._title_header.rect.height, - )) - - # room for confidence ball - label_width = self._rect.width - 32 - 60 - self._warning_label.render(rl.Rectangle( - self._rect.x + 16, - self._title_header.rect.y + self._title_header.rect.height + 16, - label_width, - self._warning_label.get_content_height(int(label_width)), - )) - - duration = rl.get_time() - self._start_time - if duration > 5 + self.ANIMATION_PAUSE * 2: - # reset animation - self._start_time = rl.get_time() - if duration > 5 + self.ANIMATION_PAUSE: - self._confidence_ball.update_filter(0.1) - elif duration > 5: - self._confidence_ball.update_filter(0.4) - elif duration > 0.5: - self._confidence_ball.update_filter(0.9) - - self._confidence_ball.render(self._rect) - self._rect.width -= 60 - - -class TrainingGuideSteeringArc(SetupTermsPage): - ANIMATION_PAUSE = 2 - TORQUE_BAR_HEIGHT = 100 - - def __init__(self, continue_callback): - super().__init__(continue_callback, continue_text="continue") - self._torque_bar = TorqueBar(demo=True) - self._start_time = 0.0 - - self._title_header = TermsHeader("steering arc", gui_app.texture("icons_mici/offroad_alerts/green_wheel.png", 60, 60)) - self._warning_label = UnifiedLabel("All cars limit the amount of steering that openpilot is able to apply. While driving, the " + - "steering arc shows the current amount of force being applied in relation to the maximum available to openpilot. " + - "You may need to assist if you see the arc nearing its orange state.", 42, - FontWeight.ROMAN) - - def show_event(self): - super().show_event() - self._start_time = rl.get_time() - - @property - def _content_height(self): - return self._warning_label.rect.y + self._warning_label.rect.height - self._scroll_panel.get_offset() + self.TORQUE_BAR_HEIGHT - - def _render_content(self, scroll_offset): - self._title_header.render(rl.Rectangle( - self._rect.x + 16, - self._rect.y + 16 + scroll_offset, - self._title_header.rect.width, - self._title_header.rect.height, - )) - - self._warning_label.render(rl.Rectangle( - self._rect.x + 16, - self._title_header.rect.y + self._title_header.rect.height + 16, - self._rect.width - 32, - self._warning_label.get_content_height(int(self._rect.width - 32)), - )) - - duration = rl.get_time() - self._start_time - if duration > self.ANIMATION_PAUSE * 5: - # reset animation - self._start_time = rl.get_time() - elif duration > self.ANIMATION_PAUSE * 4: - self._torque_bar.update_filter(-1.0) - elif duration > self.ANIMATION_PAUSE * 3: - self._torque_bar.update_filter(-0.2) - elif duration > self.ANIMATION_PAUSE * 2: - self._torque_bar.update_filter(1.0) - elif duration > self.ANIMATION_PAUSE: - self._torque_bar.update_filter(0.7) - else: - self._torque_bar.update_filter(0.0) - - # background gradient for torque bar legibility - rl.draw_rectangle_gradient_v(int(self._rect.x), int(self._rect.y + self._rect.height * 0.6), - int(self._rect.width), int(self._rect.height * 0.2), - rl.BLANK, rl.Color(0, 0, 0, int(255 * 0.9))) - rl.draw_rectangle(int(self._rect.x), int(self._rect.y + self._rect.height * 0.8), - int(self._rect.width), int(self._rect.height * 0.2), - rl.Color(0, 0, 0, int(255 * 0.9))) - - # scroll torque bar once we get to the bottom of content - torque_y_offset = min(0.0, self._warning_label.rect.y + self._warning_label.rect.height - - self._rect.height + self.TORQUE_BAR_HEIGHT) - torque_rect = rl.Rectangle( - self._rect.x, - self._rect.y + torque_y_offset, - self._rect.width, - self._rect.height, - ) - self._torque_bar.render(torque_rect) - - class TrainingGuide(Widget): def __init__(self, completed_callback=None): super().__init__() @@ -483,17 +215,10 @@ class TrainingGuide(Widget): self._step = 0 self._steps = [ - TrainingGuideIntro(continue_callback=self._advance_step), - TrainingGuideAttentionNotice1(continue_callback=self._advance_step), - TrainingGuideAttentionNotice2(continue_callback=self._advance_step), + TrainingGuideAttentionNotice(continue_callback=self._advance_step), TrainingGuidePreDMTutorial(continue_callback=self._advance_step), TrainingGuideDMTutorial(continue_callback=self._advance_step), TrainingGuideRecordFront(continue_callback=self._advance_step), - TrainingGuideEngaging(continue_callback=self._advance_step), - TrainingGuideDisengaging(continue_callback=self._advance_step), - TrainingGuideConfidenceBall(continue_callback=self._advance_step), - TrainingGuideSteeringArc(continue_callback=self._advance_step), - TrainingGuideEnd(continue_callback=self._advance_step), ] def _advance_step(self): From a49c68806ad0f6198298e47a177dadb4f8fa7837 Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Thu, 20 Nov 2025 19:28:21 -0800 Subject: [PATCH 410/910] ui: fix confidence ball clipping (#36667) fix --- selfdrive/ui/mici/onroad/confidence_ball.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/mici/onroad/confidence_ball.py b/selfdrive/ui/mici/onroad/confidence_ball.py index de792282c..a5c95470f 100644 --- a/selfdrive/ui/mici/onroad/confidence_ball.py +++ b/selfdrive/ui/mici/onroad/confidence_ball.py @@ -16,7 +16,7 @@ def draw_circle_gradient(center_x: float, center_y: float, radius: int, # Paint over square with a ring outer_radius = math.ceil(radius * math.sqrt(2)) + 1 - rl.draw_ring(rl.Vector2(center_x, center_y), radius, outer_radius, + rl.draw_ring(rl.Vector2(int(center_x), int(center_y)), radius, outer_radius, 0.0, 360.0, 20, rl.BLACK) From 04eac60983141a4a615bda33b37515f5d8fa1c51 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 21 Nov 2025 14:03:02 -0500 Subject: [PATCH 411/910] msgq: point back to comma's (#1498) --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index b9f0336a0..cd6cf2168 100644 --- a/.gitmodules +++ b/.gitmodules @@ -6,7 +6,7 @@ url = https://github.com/sunnypilot/opendbc.git [submodule "msgq"] path = msgq_repo - url = https://github.com/sunnypilot/msgq.git + url = https://github.com/commaai/msgq.git [submodule "rednose_repo"] path = rednose_repo url = https://github.com/commaai/rednose.git From f43479aac5f64b24e1e3b28a7dd7a2567e826d31 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 21 Nov 2025 14:10:33 -0500 Subject: [PATCH 412/910] ci: update workflow file name for prebuilt jobs (#1499) --- .github/workflows/sunnypilot-build-prebuilt.yaml | 4 ++-- .github/workflows/sunnypilot-master-dev-prep.yaml | 6 +++--- .github/workflows/wait-for-action/action.yaml | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/sunnypilot-build-prebuilt.yaml b/.github/workflows/sunnypilot-build-prebuilt.yaml index 12fb01cbd..d3ad2d241 100644 --- a/.github/workflows/sunnypilot-build-prebuilt.yaml +++ b/.github/workflows/sunnypilot-build-prebuilt.yaml @@ -22,7 +22,7 @@ on: workflow_dispatch: inputs: wait_for_tests: - description: 'Wait for selfdrive_tests to finish' + description: 'Wait for tests to finish' required: false type: boolean default: false @@ -99,7 +99,7 @@ jobs: - name: Wait for Tests uses: ./.github/workflows/wait-for-action # Path to where you place the action with: - workflow: selfdrive_tests.yaml # The workflow file to monitor + workflow: tests.yaml # The workflow file to monitor github-token: ${{ secrets.GITHUB_TOKEN }} should-wait-for-start: ${{ github.event_name == 'push' && 'true' || 'false' }} diff --git a/.github/workflows/sunnypilot-master-dev-prep.yaml b/.github/workflows/sunnypilot-master-dev-prep.yaml index e93e778aa..e7a966374 100644 --- a/.github/workflows/sunnypilot-master-dev-prep.yaml +++ b/.github/workflows/sunnypilot-master-dev-prep.yaml @@ -57,7 +57,7 @@ jobs: || (contains(github.event_name, 'pull_request') && ((github.event.action == 'labeled' && (github.event.label.name == vars.PREBUILT_PR_LABEL || github.event.label.name == 'trust-fork-pr') && contains(github.event.pull_request.labels.*.name, vars.PREBUILT_PR_LABEL)))) ) with: - workflow: selfdrive_tests.yaml # The workflow file to monitor + workflow: tests.yaml # The workflow file to monitor github-token: ${{ secrets.GITHUB_TOKEN }} - name: Configure Git @@ -201,13 +201,13 @@ jobs: if: steps.push-changes.outputs.has_changes == 'true' run: | echo "Triggering selfdrive tests..." - gh workflow run selfdrive_tests.yaml --ref "${{ inputs.target_branch || env.DEFAULT_TARGET_BRANCH }}" + gh workflow run tests.yaml --ref "${{ inputs.target_branch || env.DEFAULT_TARGET_BRANCH }}" echo "Sleeping for 120s to give plenty of time for the action to start and then we wait" sleep 120 echo "Getting latest run ID..." - RUN_ID=$(gh run list --workflow=selfdrive_tests.yaml --branch="${{ inputs.target_branch || env.DEFAULT_TARGET_BRANCH }}" --limit=1 --json databaseId --jq '.[0].databaseId') + RUN_ID=$(gh run list --workflow=tests.yaml --branch="${{ inputs.target_branch || env.DEFAULT_TARGET_BRANCH }}" --limit=1 --json databaseId --jq '.[0].databaseId') echo "Watching run ID: $RUN_ID" gh run watch "$RUN_ID" diff --git a/.github/workflows/wait-for-action/action.yaml b/.github/workflows/wait-for-action/action.yaml index 9cde4cf07..01bc61461 100644 --- a/.github/workflows/wait-for-action/action.yaml +++ b/.github/workflows/wait-for-action/action.yaml @@ -4,7 +4,7 @@ inputs: workflow: description: 'The workflow file name to monitor' required: true - default: 'selfdrive_tests.yaml' + default: 'tests.yaml' branch: description: 'The branch to monitor (defaults to current branch)' required: false From 4d2ad45be6bcede183faa5f88fb4738f117a7b39 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Nov 2025 14:22:23 -0500 Subject: [PATCH 413/910] [bot] Update Python packages (#1483) Update Python packages Co-authored-by: github-actions[bot] --- docs/CARS.md | 703 ++++++++++++++++++++++++++------------------------- opendbc_repo | 2 +- 2 files changed, 353 insertions(+), 352 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index 695c2589e..4c1e90e18 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -4,349 +4,351 @@ 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. -# 339 Supported Cars +# 341 Supported Cars |Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Hardware Needed
 |Video|Setup Video| |---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| -|Acura|ILX 2016-18|Technology Plus Package or AcuraWatch Plus|openpilot|26 mph|25 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Acura|ILX 2019|All|openpilot|26 mph|25 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Acura|MDX 2025|All except Type S|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch 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
||| -|Acura|RDX 2016-18|AcuraWatch Plus or Advance Package|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Acura|RDX 2019-21|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Audi|A3 2014-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Audi|A3 Sportback e-tron 2017-18|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Audi|Q2 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Audi|Q3 2019-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Audi|RS3 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Audi|S3 2015-17|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Chevrolet|Bolt EUV 2022-23|Premier or Premier Redline Trim without Super Cruise Package|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 comma 3X
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Chevrolet|Bolt EV 2022-23|2LT Trim with Adaptive Cruise Control Package|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 comma 3X
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Chevrolet|Bolt EV Non-ACC 2017|Adaptive Cruise Control (ACC)|Stock|24 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 comma 3X
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Chevrolet|Bolt EV Non-ACC 2018-21|Adaptive Cruise Control (ACC)|Stock|24 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 comma 3X
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Chevrolet|Equinox 2019-22|Adaptive Cruise Control (ACC)|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 comma 3X
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Chevrolet|Malibu Non-ACC 2016-23|Adaptive Cruise Control (ACC)|Stock|24 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 comma 3X
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Chevrolet|Silverado 1500 2020-21|Safety Package II|openpilot available[1](#footnotes)|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 comma 3X
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Chevrolet|Trailblazer 2021-22|Adaptive Cruise Control (ACC)|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 comma 3X
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Chrysler|Pacifica 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Chrysler|Pacifica 2019-20|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Chrysler|Pacifica 2021-23|All|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Chrysler|Pacifica Hybrid 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Chrysler|Pacifica Hybrid 2019-25|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Acura|ILX 2016-18|Technology Plus Package or AcuraWatch Plus|openpilot|26 mph|25 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Acura|ILX 2019|All|openpilot|26 mph|25 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Acura|MDX 2025|All except Type S|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Acura|RDX 2016-18|AcuraWatch Plus or Advance Package|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Acura|RDX 2019-21|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Acura|TLX 2021|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Audi|A3 2014-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Audi|A3 Sportback e-tron 2017-18|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Audi|Q2 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Audi|Q3 2019-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Audi|RS3 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Audi|S3 2015-17|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Chevrolet|Bolt EUV 2022-23|Premier or Premier Redline Trim without Super Cruise Package|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 harness box
- 1 mount
Buy Here
||| +|Chevrolet|Bolt EV 2022-23|2LT Trim with Adaptive Cruise Control Package|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 harness box
- 1 mount
Buy Here
||| +|Chevrolet|Bolt EV Non-ACC 2017|Adaptive Cruise Control (ACC)|Stock|24 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 harness box
- 1 mount
Buy Here
||| +|Chevrolet|Bolt EV Non-ACC 2018-21|Adaptive Cruise Control (ACC)|Stock|24 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 harness box
- 1 mount
Buy Here
||| +|Chevrolet|Equinox 2019-22|Adaptive Cruise Control (ACC)|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 harness box
- 1 mount
Buy Here
||| +|Chevrolet|Malibu Non-ACC 2016-23|Adaptive Cruise Control (ACC)|Stock|24 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 harness box
- 1 mount
Buy Here
||| +|Chevrolet|Silverado 1500 2020-21|Safety Package II|openpilot available[1](#footnotes)|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 harness box
- 1 mount
Buy Here
||| +|Chevrolet|Trailblazer 2021-22|Adaptive Cruise Control (ACC)|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 harness box
- 1 mount
Buy Here
||| +|Chrysler|Pacifica 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Chrysler|Pacifica 2019-20|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Chrysler|Pacifica 2021-23|All|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Chrysler|Pacifica Hybrid 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Chrysler|Pacifica Hybrid 2019-25|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |comma|body|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|None||| -|CUPRA|Ateca 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Dodge|Durango 2020-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Ford|Bronco Sport 2021-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Ford|Escape 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Ford|Escape 2023-24|Co-Pilot360 Assist+|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Ford|Escape Hybrid 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Ford|Escape Hybrid 2023-24|Co-Pilot360 Assist+|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Ford|Escape Plug-in Hybrid 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Ford|Escape Plug-in Hybrid 2023-24|Co-Pilot360 Assist+|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Ford|Expedition 2022-24|Co-Pilot360 Assist 2.0|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Ford|Explorer 2020-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Ford|Explorer Hybrid 2020-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Ford|F-150 2021-23|Co-Pilot360 Assist 2.0|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Ford|F-150 Hybrid 2021-23|Co-Pilot360 Assist 2.0|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Ford|Focus 2018[3](#footnotes)|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Ford|Focus Hybrid 2018[3](#footnotes)|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Ford|Kuga 2020-23|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Ford|Kuga Hybrid 2020-23|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Ford|Kuga Hybrid 2024|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Ford|Kuga Plug-in Hybrid 2020-23|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Ford|Kuga Plug-in Hybrid 2024|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Ford|Maverick 2022|LARIAT Luxury|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Ford|Maverick 2023-24|Co-Pilot360 Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Ford|Maverick Hybrid 2022|LARIAT Luxury|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Ford|Maverick Hybrid 2023-24|Co-Pilot360 Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Ford|Mustang Mach-E 2021-24|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Ford|Ranger 2024|Adaptive Cruise Control with Lane Centering|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Genesis|G70 2018|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Genesis|G70 2019-21|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Genesis|G70 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Genesis|G80 2017|All|Stock|19 mph|37 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai J connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Genesis|G80 2018-19|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Genesis|G80 (2.5T Advanced Trim, with HDA II) 2024[6](#footnotes)|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai P connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Genesis|G90 2017-20|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Genesis|GV60 (Advanced Trim) 2023[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Genesis|GV60 (Performance Trim) 2022-23[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Genesis|GV70 (2.5T Trim, without HDA II) 2022-24[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Genesis|GV70 (3.5T Trim, without HDA II) 2022-23[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai M connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Genesis|GV70 Electrified (Australia Only) 2022[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai Q connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Genesis|GV70 Electrified (with HDA II) 2023-24[6](#footnotes)|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai Q connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Genesis|GV80 2023[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai M connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|GMC|Sierra 1500 2020-21|Driver Alert Package II|openpilot available[1](#footnotes)|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 comma 3X
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Honda|Accord 2018-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Honda|Accord 2023-25|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch 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
||| -|Honda|Accord Hybrid 2018-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Honda|Accord Hybrid 2023-25|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch 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
||| -|Honda|City (Brazil only) 2023|All|openpilot available[1](#footnotes)|0 mph|14 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Honda|Civic 2016-18|Honda Sensing|openpilot|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Honda|Civic 2019-21|All|openpilot available[1](#footnotes)|0 mph|2 mph[5](#footnotes)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Honda|Civic 2022-24|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Honda|Civic Hatchback 2017-18|Honda Sensing|openpilot available[1](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Honda|Civic Hatchback 2019-21|All|openpilot available[1](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Honda|Civic Hatchback 2022-24|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Honda|Civic Hatchback Hybrid 2025-26|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Honda|Civic Hatchback Hybrid (Europe only) 2023|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Honda|Civic Hybrid 2025|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Honda|Clarity 2018-21|Honda Sensing|openpilot|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Nidec connector + Honda Clarity Proxy Board
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Honda|CR-V 2015-16|Touring Trim|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Honda|CR-V 2017-22|Honda Sensing|openpilot available[1](#footnotes)|0 mph|15 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Honda|CR-V 2023-25|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch 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
||| -|Honda|CR-V Hybrid 2017-22|Honda Sensing|openpilot available[1](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Honda|CR-V Hybrid 2023-25|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch 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
||| -|Honda|e 2020|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Honda|Fit 2018-20|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Honda|Freed 2020|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Honda|HR-V 2019-22|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Honda|HR-V 2023-25|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Honda|Insight 2019-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Honda|Inspire 2018|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Honda|N-Box 2018|All|openpilot available[1](#footnotes)|0 mph|11 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Honda|Odyssey 2018-20|Honda Sensing|openpilot|26 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Honda|Odyssey 2021-25|All|openpilot available[1](#footnotes)|0 mph|43 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Honda|Passport 2019-25|All|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Honda|Pilot 2016-22|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Honda|Pilot 2023-25|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch C 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
||| -|Honda|Ridgeline 2017-25|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Azera 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Azera Hybrid 2019|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Azera Hybrid 2020|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Custin 2023|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Elantra 2017-18|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai B connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Elantra 2019|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai G connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Elantra 2021-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Elantra GT 2017-20|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Elantra Hybrid 2021-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Elantra Non-SCC 2022|No Smart Cruise Control (Non-SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Genesis 2015-16|Smart Cruise Control (SCC)|Stock|19 mph|37 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai J connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|i30 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Ioniq 5 (Southeast Asia and Europe only) 2022-24[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai Q connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Ioniq 5 (with HDA II) 2022-24[6](#footnotes)|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai Q connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Ioniq 5 (without HDA II) 2022-24[6](#footnotes)|Highway Driving Assist|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Ioniq 6 (with HDA II) 2023-24[6](#footnotes)|Highway Driving Assist II|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai P connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Ioniq Electric 2019|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Ioniq Electric 2020|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Ioniq Hybrid 2017-19|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Ioniq Hybrid 2020-22|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Ioniq Plug-in Hybrid 2019|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Ioniq Plug-in Hybrid 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Kona 2020|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|6 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai B connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Kona 2022-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai O connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Kona Electric 2018-21|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai G connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Kona Electric 2022-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai O connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Kona Electric (with HDA II, Korea only) 2023[6](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai R connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Kona Electric Non-SCC 2019|No Smart Cruise Control (Non-SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai G connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Kona Hybrid 2020|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai I connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Kona Non-SCC 2019|No Smart Cruise Control (Non-SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai B connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Nexo 2021|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Palisade 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Santa Cruz 2022-24[6](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Santa Fe 2019-20|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai D connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Santa Fe 2021-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Santa Fe Hybrid 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Santa Fe Plug-in Hybrid 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Sonata 2018-19|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Sonata 2020-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Sonata Hybrid 2020-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Staria 2023[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Tucson 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai L connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Tucson 2022[6](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Tucson 2023-24[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Tucson Diesel 2019|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Tucson Hybrid 2022-24[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Tucson Plug-in Hybrid 2024[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Veloster 2019-20|Smart Cruise Control (SCC)|Stock|5 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai E connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Jeep|Grand Cherokee 2016-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Jeep|Grand Cherokee 2019-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Carnival 2022-24[6](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Carnival (China only) 2023[6](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Ceed 2019-21|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Ceed Plug-in Hybrid Non-SCC 2022|No Smart Cruise Control (Non-SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai I connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|EV6 (Southeast Asia only) 2022-24[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai P connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|EV6 (with HDA II) 2022-24[6](#footnotes)|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai P connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|EV6 (without HDA II) 2022-24[6](#footnotes)|Highway Driving Assist|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Forte 2019-21|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|6 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai G connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Forte 2022-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Forte Non-SCC 2019|No Smart Cruise Control (Non-SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai G connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|K5 2021-24|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|K5 Hybrid 2020-22|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|K8 Hybrid (with HDA II) 2023[6](#footnotes)|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai Q connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Niro EV 2019|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Niro EV 2020|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Niro EV 2021|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Niro EV 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Niro EV (with HDA II) 2025[6](#footnotes)|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai R connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Niro EV (without HDA II) 2023-25[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Niro Hybrid 2018|Smart Cruise Control (SCC)|Stock|10 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai C connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Niro Hybrid 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai D connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Niro Hybrid 2022|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Niro Hybrid 2023[6](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Niro Plug-in Hybrid 2018-19|All|Stock|10 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai C connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Niro Plug-in Hybrid 2020|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai D connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Niro Plug-in Hybrid 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai D connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Niro Plug-in Hybrid 2022|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Optima 2017|Advanced Smart Cruise Control|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai B connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Optima 2019-20|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai G connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Optima Hybrid 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Seltos 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Sorento 2018|Advanced Smart Cruise Control & LKAS|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Sorento 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Sorento 2021-23[6](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Sorento Hybrid 2021-23[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Sorento Plug-in Hybrid 2022-23[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Sportage 2023-24[6](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Sportage Hybrid 2023[6](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Stinger 2018-20|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Stinger 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Telluride 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 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
||| -|Lexus|CT Hybrid 2017-18|Lexus Safety System+|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Lexus|ES 2017-18|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Lexus|ES 2019-25|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Lexus|ES Hybrid 2017-18|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Lexus|ES Hybrid 2019-25|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Lexus|GS F 2016|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Lexus|IS 2017-19|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Lexus|IS 2022-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Lexus|LC 2024-25|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Lexus|NX 2018-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Lexus|NX 2020-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Lexus|NX Hybrid 2018-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Lexus|NX Hybrid 2020-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Lexus|RC 2018-20|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Lexus|RC 2023|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Lexus|RX 2016|Lexus Safety System+|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Lexus|RX 2017-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Lexus|RX 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Lexus|RX Hybrid 2016|Lexus Safety System+|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Lexus|RX Hybrid 2017-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Lexus|RX Hybrid 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Lexus|UX Hybrid 2019-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Lincoln|Aviator 2020-24|Co-Pilot360 Plus|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Lincoln|Aviator Plug-in Hybrid 2020-24|Co-Pilot360 Plus|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|MAN|eTGE 2020-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|MAN|TGE 2017-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Mazda|CX-5 2022-25|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Mazda connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Mazda|CX-9 2021-23|All|Stock|0 mph|28 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Mazda connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Nissan[7](#footnotes)|Altima 2019-20|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan B connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Nissan[7](#footnotes)|Leaf 2018-23|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Nissan[7](#footnotes)|Rogue 2018-20|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Nissan[7](#footnotes)|X-Trail 2017|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Ram|1500 2019-24|Adaptive Cruise Control (ACC)|Stock|32 mph|1 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Ram connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Ram|2500 2020-24|Adaptive Cruise Control (ACC)|Stock|0 mph|36 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ram connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Ram|3500 2019-22|Adaptive Cruise Control (ACC)|Stock|0 mph|36 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ram connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Rivian|R1S 2022-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Rivian A connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Rivian|R1T 2022-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Rivian A connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|SEAT|Ateca 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|SEAT|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Subaru|Ascent 2019-21|All[8](#footnotes)|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| -|Subaru|Crosstrek 2018-19|EyeSight Driver Assistance[8](#footnotes)|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| -|Subaru|Crosstrek 2020-23|EyeSight Driver Assistance[8](#footnotes)|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| -|Subaru|Forester 2017-18|EyeSight Driver Assistance[8](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| -|Subaru|Forester 2019-21|All[8](#footnotes)|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| -|Subaru|Impreza 2017-19|EyeSight Driver Assistance[8](#footnotes)|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| -|Subaru|Impreza 2020-22|EyeSight Driver Assistance[8](#footnotes)|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| -|Subaru|Legacy 2015-18|EyeSight Driver Assistance[8](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| -|Subaru|Legacy 2020-22|All[8](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Subaru B connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| -|Subaru|Outback 2015-17|EyeSight Driver Assistance[8](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| -|Subaru|Outback 2018-19|EyeSight Driver Assistance[8](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| -|Subaru|Outback 2020-22|All[8](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Subaru B connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| -|Subaru|XV 2018-19|EyeSight Driver Assistance[8](#footnotes)|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| -|Subaru|XV 2020-21|EyeSight Driver Assistance[8](#footnotes)|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| -|Škoda|Fabia 2022-23[15](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[17](#footnotes)||| -|Škoda|Kamiq 2021-23[13,15](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[17](#footnotes)||| -|Škoda|Karoq 2019-23[15](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Škoda|Kodiaq 2017-23[15](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Škoda|Octavia 2015-19[15](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Škoda|Octavia RS 2016[15](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Škoda|Octavia Scout 2017-19[15](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Škoda|Scala 2020-23[15](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[17](#footnotes)||| -|Škoda|Superb 2015-22[15](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Tesla[11](#footnotes)|Model 3 (with HW3) 2019-23[10](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Tesla A connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Tesla[11](#footnotes)|Model 3 (with HW4) 2024-25[10](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Tesla B connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Tesla[11](#footnotes)|Model Y (with HW3) 2020-23[10](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Tesla A connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Tesla[11](#footnotes)|Model Y (with HW4) 2024-25[10](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Tesla B connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Alphard 2019-20|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Alphard Hybrid 2021|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Avalon 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Avalon 2017-18|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Avalon 2019-21|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Avalon 2022|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Avalon Hybrid 2019-21|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Avalon Hybrid 2022|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|C-HR 2017-20|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|C-HR 2021|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|C-HR Hybrid 2017-20|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|C-HR Hybrid 2021-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Camry 2018-20|All|Stock|0 mph[12](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Camry 2021-24|All|openpilot|0 mph[12](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Camry Hybrid 2018-20|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Camry Hybrid 2021-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Corolla 2017-19|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Corolla 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Corolla Cross (Non-US only) 2020-23|All|openpilot|17 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Corolla Cross Hybrid (Non-US only) 2020-22|All|openpilot|17 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Corolla Hatchback 2019-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Corolla Hybrid 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Corolla Hybrid (South America only) 2020-23|All|openpilot|17 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Highlander 2017-19|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Highlander 2020-23|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Highlander Hybrid 2017-19|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Highlander Hybrid 2020-23|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Mirai 2021|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Prius 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Prius 2017-20|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Prius 2021-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Prius Prime 2017-20|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Prius Prime 2021-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Prius v 2017|Toyota Safety Sense P|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|RAV4 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|RAV4 2017-18|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|RAV4 2019-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|RAV4 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|RAV4 2023-25|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|RAV4 Hybrid 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|RAV4 Hybrid 2017-18|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|RAV4 Hybrid 2019-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|RAV4 Hybrid 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|RAV4 Hybrid 2023-25|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Sienna 2018-20|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|Arteon 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|Arteon eHybrid 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|Arteon R 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|Arteon Shooting Brake 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|Atlas 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|Atlas Cross Sport 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|California 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|Caravelle 2020|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|CC 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|Crafter 2017-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|e-Crafter 2018-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|e-Golf 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|Golf 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|Golf Alltrack 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|Golf GTD 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|Golf GTE 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|Golf GTI 2015-21|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|Golf R 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|Golf SportsVan 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|Grand California 2019-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|Jetta 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|Jetta GLI 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|Passat 2015-22[14](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|Passat Alltrack 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|Passat GTE 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|Polo 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[17](#footnotes)||| -|Volkswagen|Polo GTI 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[17](#footnotes)||| -|Volkswagen|T-Cross 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[17](#footnotes)||| -|Volkswagen|T-Roc 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|Taos 2022-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|Teramont 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|Teramont Cross Sport 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|Teramont X 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|Tiguan 2018-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|Tiguan eHybrid 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|Touran 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|CUPRA|Ateca 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Dodge|Durango 2020-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Ford|Bronco Sport 2021-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Ford|Escape 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Ford|Escape 2023-24|Co-Pilot360 Assist+|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Ford|Escape Hybrid 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Ford|Escape Hybrid 2023-24|Co-Pilot360 Assist+|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Ford|Escape Plug-in Hybrid 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Ford|Escape Plug-in Hybrid 2023-24|Co-Pilot360 Assist+|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Ford|Expedition 2022-24|Co-Pilot360 Assist 2.0|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Ford|Explorer 2020-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Ford|Explorer Hybrid 2020-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Ford|F-150 2021-23|Co-Pilot360 Assist 2.0|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Ford|F-150 Hybrid 2021-23|Co-Pilot360 Assist 2.0|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Ford|Focus 2018[3](#footnotes)|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Ford|Focus Hybrid 2018[3](#footnotes)|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Ford|Kuga 2020-23|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Ford|Kuga Hybrid 2020-23|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Ford|Kuga Hybrid 2024|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Ford|Kuga Plug-in Hybrid 2020-23|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Ford|Kuga Plug-in Hybrid 2024|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Ford|Maverick 2022|LARIAT Luxury|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Ford|Maverick 2023-24|Co-Pilot360 Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Ford|Maverick Hybrid 2022|LARIAT Luxury|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Ford|Maverick Hybrid 2023-24|Co-Pilot360 Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Ford|Mustang Mach-E 2021-24|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Ford|Ranger 2024|Adaptive Cruise Control with Lane Centering|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Genesis|G70 2018|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Genesis|G70 2019-21|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Genesis|G70 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Genesis|G80 2017|All|Stock|19 mph|37 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai J connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Genesis|G80 2018-19|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Genesis|G80 (2.5T Advanced Trim, with HDA II) 2024|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai P connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Genesis|G90 2017-20|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Genesis|GV60 (Advanced Trim) 2023|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Genesis|GV60 (Performance Trim) 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Genesis|GV70 (2.5T Trim, without HDA II) 2022-24|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Genesis|GV70 (3.5T Trim, without HDA II) 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai M connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Genesis|GV70 Electrified (Australia Only) 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai Q connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Genesis|GV70 Electrified (with HDA II) 2023-24|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai Q connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Genesis|GV80 2023|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai M connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|GMC|Sierra 1500 2020-21|Driver Alert Package II|openpilot available[1](#footnotes)|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Accord 2018-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Accord 2023-25|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Accord Hybrid 2018-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Accord Hybrid 2023-25|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|City (Brazil only) 2023|All|openpilot available[1](#footnotes)|0 mph|14 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Civic 2016-18|Honda Sensing|openpilot|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Civic 2019-21|All|openpilot available[1](#footnotes)|0 mph|2 mph[5](#footnotes)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Civic 2022-24|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Civic Hatchback 2017-18|Honda Sensing|openpilot available[1](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Civic Hatchback 2019-21|All|openpilot available[1](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Civic Hatchback 2022-24|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Civic Hatchback Hybrid 2025-26|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Civic Hatchback Hybrid (Europe only) 2023|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Civic Hybrid 2025-26|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Clarity 2018-21|Honda Sensing|openpilot|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Nidec connector + Honda Clarity Proxy Board
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|CR-V 2015-16|Touring Trim|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|CR-V 2017-22|Honda Sensing|openpilot available[1](#footnotes)|0 mph|15 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|CR-V 2023-26|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|CR-V Hybrid 2017-22|Honda Sensing|openpilot available[1](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|CR-V Hybrid 2023-25|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|e 2020|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Fit 2018-20|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Freed 2020|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|HR-V 2019-22|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|HR-V 2023-25|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Insight 2019-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Inspire 2018|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|N-Box 2018|All|openpilot available[1](#footnotes)|0 mph|11 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Odyssey 2018-20|Honda Sensing|openpilot|26 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Odyssey 2021-26|All|openpilot available[1](#footnotes)|0 mph|43 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Passport 2019-25|All|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Passport 2026|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Pilot 2016-22|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Pilot 2023-25|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Ridgeline 2017-25|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Azera 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Azera Hybrid 2019|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Azera Hybrid 2020|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Custin 2023|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Elantra 2017-18|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai B connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Elantra 2019|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai G connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Elantra 2021-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Elantra GT 2017-20|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Elantra Hybrid 2021-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Elantra Non-SCC 2022|No Smart Cruise Control (Non-SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Genesis 2015-16|Smart Cruise Control (SCC)|Stock|19 mph|37 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai J connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|i30 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Ioniq 5 (Southeast Asia and Europe only) 2022-24|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai Q connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Ioniq 5 (with HDA II) 2022-24|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai Q connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Ioniq 5 (without HDA II) 2022-24|Highway Driving Assist|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Ioniq 6 (with HDA II) 2023-24|Highway Driving Assist II|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai P connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Ioniq Electric 2019|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Ioniq Electric 2020|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Ioniq Hybrid 2017-19|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Ioniq Hybrid 2020-22|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Ioniq Plug-in Hybrid 2019|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Ioniq Plug-in Hybrid 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Kona 2020|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|6 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai B connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Kona 2022-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai O connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Kona Electric 2018-21|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai G connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Kona Electric 2022-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai O connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Kona Electric (with HDA II, Korea only) 2023|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai R connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Kona Electric Non-SCC 2019|No Smart Cruise Control (Non-SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai G connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Kona Hybrid 2020|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai I connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Kona Non-SCC 2019|No Smart Cruise Control (Non-SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai B connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Nexo 2021|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Palisade 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Santa Cruz 2022-24|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Santa Fe 2019-20|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai D connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Santa Fe 2021-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Santa Fe Hybrid 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Santa Fe Plug-in Hybrid 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Sonata 2018-19|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Sonata 2020-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Sonata Hybrid 2020-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Staria 2023|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Tucson 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai L connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Tucson 2022|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Tucson 2023-24|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Tucson Diesel 2019|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Tucson Hybrid 2022-24|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Tucson Plug-in Hybrid 2024|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Veloster 2019-20|Smart Cruise Control (SCC)|Stock|5 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai E connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Jeep|Grand Cherokee 2016-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Jeep|Grand Cherokee 2019-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Carnival 2022-24|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Carnival (China only) 2023|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Ceed 2019-21|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Ceed Plug-in Hybrid Non-SCC 2022|No Smart Cruise Control (Non-SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai I connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|EV6 (Southeast Asia only) 2022-24|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai P connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|EV6 (with HDA II) 2022-24|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai P connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|EV6 (without HDA II) 2022-24|Highway Driving Assist|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Forte 2019-21|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|6 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai G connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Forte 2022-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Forte Non-SCC 2019|No Smart Cruise Control (Non-SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai G connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|K5 2021-24|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|K5 Hybrid 2020-22|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|K8 Hybrid (with HDA II) 2023|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai Q connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Niro EV 2019|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Niro EV 2020|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Niro EV 2021|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Niro EV 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Niro EV (with HDA II) 2025|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai R connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Niro EV (without HDA II) 2023-25|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Niro Hybrid 2018|Smart Cruise Control (SCC)|Stock|10 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Niro Hybrid 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai D connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Niro Hybrid 2022|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Niro Hybrid 2023|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Niro Plug-in Hybrid 2018-19|All|Stock|10 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Niro Plug-in Hybrid 2020|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai D connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Niro Plug-in Hybrid 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai D connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Niro Plug-in Hybrid 2022|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Optima 2017|Advanced Smart Cruise Control|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai B connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Optima 2019-20|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai G connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Optima Hybrid 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Seltos 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Sorento 2018|Advanced Smart Cruise Control & LKAS|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Sorento 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Sorento 2021-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Sorento Hybrid 2021-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Sorento Plug-in Hybrid 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Sportage 2023-24|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Sportage Hybrid 2023|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Stinger 2018-20|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Stinger 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Telluride 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lexus|CT Hybrid 2017-18|Lexus Safety System+|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lexus|ES 2017-18|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lexus|ES 2019-25|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lexus|ES Hybrid 2017-18|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lexus|ES Hybrid 2019-25|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lexus|GS F 2016|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lexus|IS 2017-19|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lexus|IS 2022-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lexus|LC 2024-25|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lexus|NX 2018-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lexus|NX 2020-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lexus|NX Hybrid 2018-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lexus|NX Hybrid 2020-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lexus|RC 2018-20|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lexus|RC 2023|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lexus|RX 2016|Lexus Safety System+|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lexus|RX 2017-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lexus|RX 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lexus|RX Hybrid 2016|Lexus Safety System+|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lexus|RX Hybrid 2017-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lexus|RX Hybrid 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lexus|UX Hybrid 2019-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lincoln|Aviator 2020-24|Co-Pilot360 Plus|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lincoln|Aviator Plug-in Hybrid 2020-24|Co-Pilot360 Plus|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|MAN|eTGE 2020-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|MAN|TGE 2017-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Mazda|CX-5 2022-25|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Mazda connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Mazda|CX-9 2021-23|All|Stock|0 mph|28 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Mazda connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Nissan[6](#footnotes)|Altima 2019-20, 2024|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan B connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Nissan[6](#footnotes)|Leaf 2018-23|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Nissan[6](#footnotes)|Rogue 2018-20|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Nissan[6](#footnotes)|X-Trail 2017|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Ram|1500 2019-24|Adaptive Cruise Control (ACC)|Stock|32 mph|1 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Ram connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Ram|2500 2020-24|Adaptive Cruise Control (ACC)|Stock|0 mph|36 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Ram connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Ram|3500 2019-22|Adaptive Cruise Control (ACC)|Stock|0 mph|36 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Ram connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Rivian|R1S 2022-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Rivian A connector
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Rivian|R1T 2022-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Rivian A connector
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|SEAT|Ateca 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|SEAT|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Subaru|Ascent 2019-21|All[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|Crosstrek 2018-19|EyeSight Driver Assistance[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|Crosstrek 2020-23|EyeSight Driver Assistance[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|Forester 2017-18|EyeSight Driver Assistance[7](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|Forester 2019-21|All[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|Impreza 2017-19|EyeSight Driver Assistance[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|Impreza 2020-22|EyeSight Driver Assistance[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|Legacy 2015-18|EyeSight Driver Assistance[7](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|Legacy 2020-22|All[7](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru B connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|Outback 2015-17|EyeSight Driver Assistance[7](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|Outback 2018-19|EyeSight Driver Assistance[7](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|Outback 2020-22|All[7](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru B connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|XV 2018-19|EyeSight Driver Assistance[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|XV 2020-21|EyeSight Driver Assistance[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Škoda|Fabia 2022-23[14](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[16](#footnotes)||| +|Škoda|Kamiq 2021-23[12,14](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[16](#footnotes)||| +|Škoda|Karoq 2019-23[14](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Škoda|Kodiaq 2017-23[14](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Škoda|Octavia 2015-19[14](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Škoda|Octavia RS 2016[14](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Škoda|Octavia Scout 2017-19[14](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Škoda|Scala 2020-23[14](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[16](#footnotes)||| +|Škoda|Superb 2015-22[14](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Tesla[10](#footnotes)|Model 3 (with HW3) 2019-23[9](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Tesla A connector
- 1 USB-C coupler
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Tesla[10](#footnotes)|Model 3 (with HW4) 2024-25[9](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Tesla B connector
- 1 USB-C coupler
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Tesla[10](#footnotes)|Model Y (with HW3) 2020-23[9](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Tesla A connector
- 1 USB-C coupler
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Tesla[10](#footnotes)|Model Y (with HW4) 2024-25[9](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Tesla B connector
- 1 USB-C coupler
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Toyota|Alphard 2019-20|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Alphard Hybrid 2021|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Avalon 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Avalon 2017-18|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Avalon 2019-21|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Avalon 2022|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Avalon Hybrid 2019-21|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Avalon Hybrid 2022|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|C-HR 2017-20|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|C-HR 2021|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|C-HR Hybrid 2017-20|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|C-HR Hybrid 2021-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Camry 2018-20|All|Stock|0 mph[11](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Camry 2021-24|All|openpilot|0 mph[11](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Camry Hybrid 2018-20|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Camry Hybrid 2021-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Corolla 2017-19|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Corolla 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Corolla Cross (Non-US only) 2020-23|All|openpilot|17 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Corolla Cross Hybrid (Non-US only) 2020-22|All|openpilot|17 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Corolla Hatchback 2019-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Corolla Hybrid 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Corolla Hybrid (South America only) 2020-23|All|openpilot|17 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Highlander 2017-19|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Highlander 2020-23|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Highlander Hybrid 2017-19|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Highlander Hybrid 2020-23|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Mirai 2021|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Prius 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Prius 2017-20|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Prius 2021-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Prius Prime 2017-20|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Prius Prime 2021-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Prius v 2017|Toyota Safety Sense P|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|RAV4 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|RAV4 2017-18|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|RAV4 2019-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|RAV4 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|RAV4 2023-25|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|RAV4 Hybrid 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|RAV4 Hybrid 2017-18|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|RAV4 Hybrid 2019-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|RAV4 Hybrid 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|RAV4 Hybrid 2023-25|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Sienna 2018-20|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Volkswagen|Arteon 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Arteon eHybrid 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Arteon R 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Arteon Shooting Brake 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Atlas 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Atlas Cross Sport 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|California 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Caravelle 2020|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|CC 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Crafter 2017-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|e-Crafter 2018-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|e-Golf 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Golf 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Golf Alltrack 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Golf GTD 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Golf GTE 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Golf GTI 2015-21|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Golf R 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Golf SportsVan 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Grand California 2019-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Jetta 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Jetta GLI 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Passat 2015-22[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Passat Alltrack 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Passat GTE 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Polo 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[16](#footnotes)||| +|Volkswagen|Polo GTI 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[16](#footnotes)||| +|Volkswagen|T-Cross 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[16](#footnotes)||| +|Volkswagen|T-Roc 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Taos 2022-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Teramont 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Teramont Cross Sport 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Teramont X 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Tiguan 2018-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Tiguan eHybrid 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Touran 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
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`.
@@ -354,18 +356,17 @@ A supported vehicle is one that just works when you install a comma device. All 3Refers only to the Focus Mk4 (C519) available in Europe/China/Taiwan/Australasia, not the Focus Mk3 (C346) in North and South America/Southeast Asia.
4See more setup details for GM.
52019 Honda Civic 1.6L Diesel Sedan does not have ALC below 12mph.
-6Requires a CAN FD panda kit if not using comma 3X for this CAN FD car.
-7See more setup details for Nissan.
-8In the non-US market, openpilot requires the car to come equipped with EyeSight with Lane Keep Assistance.
-9Enabling longitudinal control (alpha) will disable all EyeSight functionality, including AEB, LDW, and RAB.
-10Some 2023 model years have HW4. To check which hardware type your vehicle has, look for Autopilot computer under Software -> Additional Vehicle Information on your vehicle's touchscreen. See this page for more information.
-11See more setup details for Tesla.
-12openpilot operates above 28mph for Camry 4CYL L, 4CYL LE and 4CYL SE which don't have Full-Speed Range Dynamic Radar Cruise Control.
-13Not including the China market Kamiq, which is based on the (currently) unsupported PQ34 platform.
-14Refers only to the MQB-based European B8 Passat, not the NMS Passat in the USA/China/Mideast markets.
-15Some Škoda vehicles are equipped with heated windshields, which are known to block GPS signal needed for some comma 3X functionality.
-16Only available for vehicles using a gateway (J533) harness. At this time, vehicles using a camera harness are limited to using stock ACC.
-17Model-years 2022 and beyond may have a combined CAN gateway and BCM, which is supported by openpilot in software, but doesn't yet have a harness available from the comma store.
+6See more setup details for Nissan.
+7In the non-US market, openpilot requires the car to come equipped with EyeSight with Lane Keep Assistance.
+8Enabling longitudinal control (alpha) will disable all EyeSight functionality, including AEB, LDW, and RAB.
+9Some 2023 model years have HW4. To check which hardware type your vehicle has, look for Autopilot computer under Software -> Additional Vehicle Information on your vehicle's touchscreen. See this page for more information.
+10See more setup details for Tesla.
+11openpilot operates above 28mph for Camry 4CYL L, 4CYL LE and 4CYL SE which don't have Full-Speed Range Dynamic Radar Cruise Control.
+12Not including the China market Kamiq, which is based on the (currently) unsupported PQ34 platform.
+13Refers only to the MQB-based European B8 Passat, not the NMS Passat in the USA/China/Mideast markets.
+14Some Škoda vehicles are equipped with heated windshields, which are known to block GPS signal needed for some comma four functionality.
+15Only available for vehicles using a gateway (J533) harness. At this time, vehicles using a camera harness are limited to using stock ACC.
+16Model-years 2022 and beyond may have a combined CAN gateway and BCM, which is supported by openpilot in software, but doesn't yet have a harness available from the comma store.
## Community Maintained Cars Although they're not upstream, the community has openpilot running on other makes and models. See the 'Community Supported Models' section of each make [on our wiki](https://wiki.comma.ai/). diff --git a/opendbc_repo b/opendbc_repo index 62f6f9e4d..61bf5a90c 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 62f6f9e4d4ff4c424586c114c6ae3dd33629e4af +Subproject commit 61bf5a90c5c1917b657b8dd50c4d95e437413170 From a981f78e2f0143890595c0425aa4f0727780408a Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 21 Nov 2025 11:23:54 -0800 Subject: [PATCH 414/910] use release branch from system.version --- selfdrive/ui/mici/layouts/home.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/selfdrive/ui/mici/layouts/home.py b/selfdrive/ui/mici/layouts/home.py index 014fc0c45..75fdfe3e9 100644 --- a/selfdrive/ui/mici/layouts/home.py +++ b/selfdrive/ui/mici/layouts/home.py @@ -8,13 +8,11 @@ from openpilot.system.ui.widgets import Widget from openpilot.system.ui.lib.application import gui_app, FontWeight, DEFAULT_TEXT_COLOR, MousePos from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.system.ui.text import wrap_text -from openpilot.system.version import training_version +from openpilot.system.version import training_version, RELEASE_BRANCHES HEAD_BUTTON_FONT_SIZE = 40 HOME_PADDING = 8 -RELEASE_BRANCH = "release3" - NetworkType = log.DeviceState.NetworkType NETWORK_TYPES = { @@ -187,9 +185,9 @@ class MiciHomeLayout(Widget): if self._version_text is not None: # release branch - if self._version_text[0] == RELEASE_BRANCH: + if self._version_text[1] in RELEASE_BRANCHES: version_pos = rl.Vector2(text_pos.x, text_pos.y + self._openpilot_label.font_size + 16) - self._large_version_label.set_text(self._version_text[0]) + self._large_version_label.set_text("release") self._large_version_label.set_position(version_pos.x, version_pos.y) self._large_version_label.render() From 52d5eabc2ee269b128b63506cc5e7db8110a02cf Mon Sep 17 00:00:00 2001 From: infiniteCable2 Date: Fri, 21 Nov 2025 20:50:40 +0100 Subject: [PATCH 415/910] ictoggles for mici --- selfdrive/ui/layouts/settings/ictoggles.py | 2 +- .../ui/mici/layouts/settings/ictoggles.py | 75 +++++++++++++++++++ .../ui/mici/layouts/settings/settings.py | 6 ++ 3 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 selfdrive/ui/mici/layouts/settings/ictoggles.py diff --git a/selfdrive/ui/layouts/settings/ictoggles.py b/selfdrive/ui/layouts/settings/ictoggles.py index 72a469dd5..52cf27b67 100644 --- a/selfdrive/ui/layouts/settings/ictoggles.py +++ b/selfdrive/ui/layouts/settings/ictoggles.py @@ -60,7 +60,7 @@ class ICTogglesLayout(Widget): ), "EnableLongComfortMode": ( lambda: tr("VW: Longitudinal Comfort Mode"), - DESCRIPTIONS["EnableCurvatureController"], + DESCRIPTIONS["EnableLongComfortMode"], "chffr_wheel.png", False, ), diff --git a/selfdrive/ui/mici/layouts/settings/ictoggles.py b/selfdrive/ui/mici/layouts/settings/ictoggles.py new file mode 100644 index 000000000..38d2e08d3 --- /dev/null +++ b/selfdrive/ui/mici/layouts/settings/ictoggles.py @@ -0,0 +1,75 @@ +import pyray as rl +from collections.abc import Callable +from cereal import log + +from openpilot.system.ui.widgets.scroller import Scroller +from openpilot.selfdrive.ui.mici.widgets.button import BigParamControl, BigMultiParamToggle +from openpilot.system.ui.lib.application import gui_app +from openpilot.selfdrive.ui.layouts.settings.common import restart_needed_callback +from openpilot.selfdrive.ui.ui_state import ui_state + + +class TogglesLayoutMici(NavWidget): + def __init__(self, back_callback: Callable): + super().__init__() + self.set_back_callback(back_callback) + + enable_curvature_correction = BigParamControl("VW: Lateral Correction (Recommended)", "EnableCurvatureController") + enable_long_comfort_mode = BigParamControl("VW: Longitudinal Comfort Mode", "EnableLongComfortMode") + enable_sl_control = BigParamControl("VW: Speed Limit Control", "EnableSpeedLimitControl") + enable_sl_pred_control = BigParamControl("VW: Predicative Speed Limit (pACC)", "EnableSpeedLimitPredicative") + enable_sl_pred_sl = BigParamControl("VW: Predicative - Reaction to Speed Limits", "EnableSLPredReactToSL") + enable_sl_pred_curve = BigParamControl("VW: Predicative - Reaction to Curves", "EnableSLPredReactToCurves") + force_rhd_bsm = BigParamControl("VW: Force RHD for BSM", "ForceRHDForBSM") + enable_dark_mode = BigParamControl("Dark Mode", "DarkMode") + enable_onroad_screen_timer = BigParamControl("Onroad Screen Timeout", "DisableScreenTimer") + + + self._scroller = Scroller([ + enable_curvature_correction, + enable_long_comfort_mode, + enable_sl_control, + enable_sl_pred_control, + enable_sl_pred_sl, + enable_sl_pred_curve, + force_rhd_bsm, + enable_dark_mode, + enable_onroad_screen_timer, + ], snap_items=False) + + # Toggle lists + self._refresh_toggles = ( + ("EnableCurvatureController", enable_curvature_correction), + ("EnableLongComfortMode", enable_long_comfort_mode), + ("EnableSpeedLimitControl", enable_sl_control), + ("EnableSpeedLimitPredicative", enable_sl_pred_control), + ("EnableSLPredReactToSL", enable_sl_pred_sl), + ("EnableSLPredReactToCurves", enable_sl_pred_curve), + ("ForceRHDForBSM", force_rhd_bsm), + ("DarkMode", enable_dark_mode), + ("DisableScreenTimer", enable_onroad_screen_timer), + ) + + if ui_state.params.get_bool("ShowDebugInfo"): + gui_app.set_show_touches(True) + gui_app.set_show_fps(True) + + ui_state.add_engaged_transition_callback(self._update_toggles) + + def _update_state(self): + super()._update_state() + + def show_event(self): + super().show_event() + self._scroller.show_event() + self._update_toggles() + + def _update_toggles(self): + ui_state.update_params() + + # Refresh toggles from params to mirror external changes + for key, item in self._refresh_toggles: + item.set_checked(ui_state.params.get_bool(key)) + + def _render(self, rect: rl.Rectangle): + self._scroller.render(rect) diff --git a/selfdrive/ui/mici/layouts/settings/settings.py b/selfdrive/ui/mici/layouts/settings/settings.py index 75238d581..b1b6f92d1 100644 --- a/selfdrive/ui/mici/layouts/settings/settings.py +++ b/selfdrive/ui/mici/layouts/settings/settings.py @@ -7,6 +7,7 @@ from openpilot.common.params import Params from openpilot.system.ui.widgets.scroller import Scroller from openpilot.selfdrive.ui.mici.widgets.button import BigButton from openpilot.selfdrive.ui.mici.layouts.settings.toggles import TogglesLayoutMici +from openpilot.selfdrive.ui.mici.layouts.settings.ictoggles import ICTogglesLayoutMici from openpilot.selfdrive.ui.mici.layouts.settings.network import NetworkLayoutMici from openpilot.selfdrive.ui.mici.layouts.settings.device import DeviceLayoutMici, PairBigButton from openpilot.selfdrive.ui.mici.layouts.settings.developer import DeveloperLayoutMici @@ -22,6 +23,7 @@ class PanelType(IntEnum): DEVELOPER = 3 USER_MANUAL = 4 FIREHOSE = 5 + ICTOGGLES = 6 @dataclass @@ -38,6 +40,8 @@ class SettingsLayout(NavWidget): toggles_btn = BigButton("toggles", "", "icons_mici/settings/toggles_icon.png") toggles_btn.set_click_callback(lambda: self._set_current_panel(PanelType.TOGGLES)) + ictoggles_btn = BigButton("ictoggles", "", "icons_mici/settings/toggles_icon.png") + ictoggles_btn.set_click_callback(lambda: self._set_current_panel(PanelType.ICTOGGLES)) network_btn = BigButton("network", "", "icons_mici/settings/network/wifi_strength_full.png") network_btn.set_click_callback(lambda: self._set_current_panel(PanelType.NETWORK)) device_btn = BigButton("device", "", "icons_mici/settings/device_icon.png") @@ -50,6 +54,7 @@ class SettingsLayout(NavWidget): self._scroller = Scroller([ toggles_btn, + ictoggles_btn, network_btn, device_btn, PairBigButton(), @@ -64,6 +69,7 @@ class SettingsLayout(NavWidget): self._panels = { PanelType.TOGGLES: PanelInfo("Toggles", TogglesLayoutMici(back_callback=lambda: self._set_current_panel(None))), + PanelType.ICTOGGLES: PanelInfo("infiniteCable", ICTogglesLayoutMici(back_callback=lambda: self._set_current_panel(None))), PanelType.NETWORK: PanelInfo("Network", NetworkLayoutMici(back_callback=lambda: self._set_current_panel(None))), PanelType.DEVICE: PanelInfo("Device", DeviceLayoutMici(back_callback=lambda: self._set_current_panel(None))), PanelType.DEVELOPER: PanelInfo("Developer", DeveloperLayoutMici(back_callback=lambda: self._set_current_panel(None))), From 552be4e760d157f2f72a15bc65f9ae0345dc45f1 Mon Sep 17 00:00:00 2001 From: infiniteCable2 Date: Fri, 21 Nov 2025 20:52:22 +0100 Subject: [PATCH 416/910] Update opendbc_repo --- opendbc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc_repo b/opendbc_repo index f7145f36d..1f60a57f5 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit f7145f36dfccb256c26d1b64a754b30a06307b70 +Subproject commit 1f60a57f57b3dd237529236711b4b86cef82f3a3 From 98e2ce61b517ffd673b1b1f925115b909fd27d60 Mon Sep 17 00:00:00 2001 From: infiniteCable2 Date: Fri, 21 Nov 2025 20:59:45 +0100 Subject: [PATCH 417/910] fix --- selfdrive/ui/mici/layouts/settings/ictoggles.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/mici/layouts/settings/ictoggles.py b/selfdrive/ui/mici/layouts/settings/ictoggles.py index 38d2e08d3..9cbdf17d3 100644 --- a/selfdrive/ui/mici/layouts/settings/ictoggles.py +++ b/selfdrive/ui/mici/layouts/settings/ictoggles.py @@ -9,7 +9,7 @@ from openpilot.selfdrive.ui.layouts.settings.common import restart_needed_callba from openpilot.selfdrive.ui.ui_state import ui_state -class TogglesLayoutMici(NavWidget): +class TogglesLayoutMici(): def __init__(self, back_callback: Callable): super().__init__() self.set_back_callback(back_callback) From 8184cd8a6ab72a4c6a57971f330ab739c21020a9 Mon Sep 17 00:00:00 2001 From: Nayan Date: Fri, 21 Nov 2025 14:59:54 -0500 Subject: [PATCH 418/910] ui: optimize `gui_app` & spinner (#1474) remove gui_app_sp & use upstream gui_app Co-authored-by: Jason Wen --- system/ui/spinner.py | 4 +--- system/ui/sunnypilot/lib/application.py | 24 ------------------------ 2 files changed, 1 insertion(+), 27 deletions(-) delete mode 100644 system/ui/sunnypilot/lib/application.py diff --git a/system/ui/spinner.py b/system/ui/spinner.py index dc4923081..33f4543c3 100755 --- a/system/ui/spinner.py +++ b/system/ui/spinner.py @@ -8,8 +8,6 @@ from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.text import wrap_text from openpilot.system.ui.widgets import Widget -from openpilot.system.ui.sunnypilot.lib.application import gui_app_sp - # Constants if gui_app.big_ui(): PROGRESS_BAR_WIDTH = 1000 @@ -37,7 +35,7 @@ def clamp(value, min_value, max_value): class Spinner(Widget): def __init__(self): super().__init__() - self._comma_texture = gui_app_sp.sp_texture("images/spinner_sunnypilot.png", TEXTURE_SIZE, TEXTURE_SIZE) + self._comma_texture = gui_app.texture("../../sunnypilot/selfdrive/assets/images/spinner_sunnypilot.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 diff --git a/system/ui/sunnypilot/lib/application.py b/system/ui/sunnypilot/lib/application.py deleted file mode 100644 index 7440d224c..000000000 --- a/system/ui/sunnypilot/lib/application.py +++ /dev/null @@ -1,24 +0,0 @@ -from openpilot.system.ui.lib.application import GuiApplication -from importlib.resources import as_file, files - -ASSETS_DIR_SP = files("openpilot.sunnypilot.selfdrive").joinpath("assets") - - -class GuiApplicationSP(GuiApplication): - - def __init__(self, width: int, height: int): - super().__init__(width, height) - - def sp_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_SP.joinpath(asset_path)) as fspath: - image_obj = self._load_image_from_path(fspath.as_posix(), width, height, alpha_premultiply, keep_aspect_ratio) - texture_obj = self._load_texture_from_image(image_obj) - self._textures[cache_key] = texture_obj - return texture_obj - - -gui_app_sp = GuiApplicationSP(2160, 1080) From 0c0741a087c77f5b51e1f04b0f8d25655bb63425 Mon Sep 17 00:00:00 2001 From: infiniteCable2 Date: Fri, 21 Nov 2025 21:02:34 +0100 Subject: [PATCH 419/910] fix --- selfdrive/ui/mici/layouts/settings/ictoggles.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/mici/layouts/settings/ictoggles.py b/selfdrive/ui/mici/layouts/settings/ictoggles.py index 9cbdf17d3..1d26cc816 100644 --- a/selfdrive/ui/mici/layouts/settings/ictoggles.py +++ b/selfdrive/ui/mici/layouts/settings/ictoggles.py @@ -9,7 +9,7 @@ from openpilot.selfdrive.ui.layouts.settings.common import restart_needed_callba from openpilot.selfdrive.ui.ui_state import ui_state -class TogglesLayoutMici(): +class ICTogglesLayoutMici(): def __init__(self, back_callback: Callable): super().__init__() self.set_back_callback(back_callback) From 988e1da97afa678f91872806b3c9c5d6549b308b Mon Sep 17 00:00:00 2001 From: infiniteCable2 Date: Fri, 21 Nov 2025 21:20:41 +0100 Subject: [PATCH 420/910] Update ictoggles.py --- selfdrive/ui/mici/layouts/settings/ictoggles.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/selfdrive/ui/mici/layouts/settings/ictoggles.py b/selfdrive/ui/mici/layouts/settings/ictoggles.py index 1d26cc816..e3bde0cc3 100644 --- a/selfdrive/ui/mici/layouts/settings/ictoggles.py +++ b/selfdrive/ui/mici/layouts/settings/ictoggles.py @@ -5,11 +5,12 @@ from cereal import log from openpilot.system.ui.widgets.scroller import Scroller from openpilot.selfdrive.ui.mici.widgets.button import BigParamControl, BigMultiParamToggle from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.widgets import NavWidget from openpilot.selfdrive.ui.layouts.settings.common import restart_needed_callback from openpilot.selfdrive.ui.ui_state import ui_state -class ICTogglesLayoutMici(): +class ICTogglesLayoutMici(NavWidget): def __init__(self, back_callback: Callable): super().__init__() self.set_back_callback(back_callback) From 6e8a7e8c0770ee4f9fe7c0bffd472c82801bd48f Mon Sep 17 00:00:00 2001 From: infiniteCable2 Date: Fri, 21 Nov 2025 21:24:29 +0100 Subject: [PATCH 421/910] here it is, tici scroller --- selfdrive/ui/mici/layouts/settings/ictoggles.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/mici/layouts/settings/ictoggles.py b/selfdrive/ui/mici/layouts/settings/ictoggles.py index e3bde0cc3..bc8bca136 100644 --- a/selfdrive/ui/mici/layouts/settings/ictoggles.py +++ b/selfdrive/ui/mici/layouts/settings/ictoggles.py @@ -2,7 +2,7 @@ import pyray as rl from collections.abc import Callable from cereal import log -from openpilot.system.ui.widgets.scroller import Scroller +from openpilot.system.ui.widgets.scroller_tici import Scroller from openpilot.selfdrive.ui.mici.widgets.button import BigParamControl, BigMultiParamToggle from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.widgets import NavWidget From 2191246c11ac5903590f5186a976cca4a946b7ed Mon Sep 17 00:00:00 2001 From: infiniteCable <75014343+infiniteCable@users.noreply.github.com> Date: Fri, 21 Nov 2025 21:33:10 +0100 Subject: [PATCH 422/910] Update ictoggles.py wrong oopsi --- selfdrive/ui/mici/layouts/settings/ictoggles.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/mici/layouts/settings/ictoggles.py b/selfdrive/ui/mici/layouts/settings/ictoggles.py index bc8bca136..e3bde0cc3 100644 --- a/selfdrive/ui/mici/layouts/settings/ictoggles.py +++ b/selfdrive/ui/mici/layouts/settings/ictoggles.py @@ -2,7 +2,7 @@ import pyray as rl from collections.abc import Callable from cereal import log -from openpilot.system.ui.widgets.scroller_tici import Scroller +from openpilot.system.ui.widgets.scroller import Scroller from openpilot.selfdrive.ui.mici.widgets.button import BigParamControl, BigMultiParamToggle from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.widgets import NavWidget From b690471b66f8af683823a0febb7c2da23a42adca Mon Sep 17 00:00:00 2001 From: infiniteCable <75014343+infiniteCable@users.noreply.github.com> Date: Fri, 21 Nov 2025 21:33:46 +0100 Subject: [PATCH 423/910] Update ictoggles.py... --- selfdrive/ui/layouts/settings/ictoggles.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/layouts/settings/ictoggles.py b/selfdrive/ui/layouts/settings/ictoggles.py index 52cf27b67..729745850 100644 --- a/selfdrive/ui/layouts/settings/ictoggles.py +++ b/selfdrive/ui/layouts/settings/ictoggles.py @@ -2,7 +2,7 @@ from cereal import log from openpilot.common.params import Params, UnknownKeyName from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.list_view import toggle_item -from openpilot.system.ui.widgets.scroller import Scroller +from openpilot.system.ui.widgets.scroller_tici import Scroller from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.lib.multilang import tr, tr_noop from openpilot.selfdrive.ui.ui_state import ui_state From 0ba5cbea914ed437f760e8644ac6800aacbe5a4a Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 21 Nov 2025 15:55:18 -0500 Subject: [PATCH 424/910] tools: add new Pythons configs to compile & run big/small UIs (#1500) * init * tools: add new Pythons configs to compile & build big/small UIs * Revert "init" This reverts commit 2e0704ace51b5a42a5d4a25664e5f46103f40e3f. * change --- .run/Build_BIG_UI.run.xml | 26 ++++++++++++++++++++++++++ .run/Build_SMALL_UI.run.xml | 23 +++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 .run/Build_BIG_UI.run.xml create mode 100644 .run/Build_SMALL_UI.run.xml diff --git a/.run/Build_BIG_UI.run.xml b/.run/Build_BIG_UI.run.xml new file mode 100644 index 000000000..b58b1bf1b --- /dev/null +++ b/.run/Build_BIG_UI.run.xml @@ -0,0 +1,26 @@ + + + + + \ No newline at end of file diff --git a/.run/Build_SMALL_UI.run.xml b/.run/Build_SMALL_UI.run.xml new file mode 100644 index 000000000..6c231c83f --- /dev/null +++ b/.run/Build_SMALL_UI.run.xml @@ -0,0 +1,23 @@ + + + + + \ No newline at end of file From ebc11fdbc83b1400e4829444869b5e6a89f8477c Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 21 Nov 2025 13:44:22 -0800 Subject: [PATCH 425/910] make "update available" alert clickable (#36670) * click to update * that's it * lil more --- selfdrive/ui/mici/layouts/home.py | 31 +++++++++------------ selfdrive/ui/mici/layouts/offroad_alerts.py | 10 ++++--- system/version.py | 2 +- 3 files changed, 20 insertions(+), 23 deletions(-) diff --git a/selfdrive/ui/mici/layouts/home.py b/selfdrive/ui/mici/layouts/home.py index 75fdfe3e9..6102265a8 100644 --- a/selfdrive/ui/mici/layouts/home.py +++ b/selfdrive/ui/mici/layouts/home.py @@ -185,27 +185,22 @@ class MiciHomeLayout(Widget): if self._version_text is not None: # release branch - if self._version_text[1] in RELEASE_BRANCHES: - version_pos = rl.Vector2(text_pos.x, text_pos.y + self._openpilot_label.font_size + 16) - self._large_version_label.set_text("release") - self._large_version_label.set_position(version_pos.x, version_pos.y) - self._large_version_label.render() + release_branch = self._version_text[1] in RELEASE_BRANCHES + version_pos = rl.Rectangle(text_pos.x, text_pos.y + self._openpilot_label.font_size + 16, 100, 44) + self._version_label.set_text(self._version_text[0]) + self._version_label.set_position(version_pos.x, version_pos.y) + self._version_label.render() - else: - version_pos = rl.Rectangle(text_pos.x, text_pos.y + self._openpilot_label.font_size + 16, 100, 44) - self._version_label.set_text(self._version_text[0]) - self._version_label.set_position(version_pos.x, version_pos.y) - self._version_label.render() + self._date_label.set_text(" " + self._version_text[3]) + self._date_label.set_position(version_pos.x + self._version_label.rect.width + 10, version_pos.y) + self._date_label.render() - self._date_label.set_text(" " + self._version_text[3]) - self._date_label.set_position(version_pos.x + self._version_label.rect.width + 10, version_pos.y) - self._date_label.render() - - self._branch_label.set_width(gui_app.width - self._version_label.rect.width - self._date_label.rect.width - 32) - self._branch_label.set_text(" " + self._version_text[1]) - self._branch_label.set_position(version_pos.x + self._version_label.rect.width + self._date_label.rect.width + 20, version_pos.y) - self._branch_label.render() + self._branch_label.set_width(gui_app.width - self._version_label.rect.width - self._date_label.rect.width - 32) + self._branch_label.set_text(" " + ("release" if release_branch else self._version_text[1])) + self._branch_label.set_position(version_pos.x + self._version_label.rect.width + self._date_label.rect.width + 20, version_pos.y) + self._branch_label.render() + if not release_branch: # 2nd line self._version_commit_label.set_text(self._version_text[2]) self._version_commit_label.set_position(version_pos.x, version_pos.y + self._date_label.font_size + 7) diff --git a/selfdrive/ui/mici/layouts/offroad_alerts.py b/selfdrive/ui/mici/layouts/offroad_alerts.py index 2e9a8bee3..60f64b31b 100644 --- a/selfdrive/ui/mici/layouts/offroad_alerts.py +++ b/selfdrive/ui/mici/layouts/offroad_alerts.py @@ -5,6 +5,7 @@ from dataclasses import dataclass from enum import IntEnum from openpilot.common.params import Params from openpilot.selfdrive.selfdrived.alertmanager import OFFROAD_ALERTS +from openpilot.system.hardware import HARDWARE from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.label import UnifiedLabel from openpilot.system.ui.widgets.scroller import Scroller @@ -220,6 +221,7 @@ class MiciOffroadAlerts(Widget): update_alert_data = AlertData(key="UpdateAvailable", text="", severity=-1) self.sorted_alerts.append(update_alert_data) update_alert_item = AlertItem(update_alert_data) + update_alert_item.set_click_callback(lambda: HARDWARE.reboot()) self.alert_items.append(update_alert_item) self._scroller.add_widget(update_alert_item) @@ -244,18 +246,18 @@ class MiciOffroadAlerts(Widget): if update_alert_data: if update_available: - # Default text - update_alert_data.text = "update available. go to comma.ai/blog to read the release notes." + version_string = "" # Get new version description and parse version and date new_desc = self.params.get("UpdaterNewDescription") or "" if new_desc: - # Parse description (format: "version / branch / commit / date") + # format: "version / branch / commit / date" parts = new_desc.split(" / ") if len(parts) > 3: version, date = parts[0], parts[3] - update_alert_data.text = f"update available\n openpilot {version}, {date}. go to comma.ai/blog to read the release notes." + version_string = f"\nopenpilot {version}, {date}\n" + update_alert_data.text = f"Update available {version_string}. Click to update. Read the release notes at blog.comma.ai." update_alert_data.visible = True active_count += 1 else: diff --git a/system/version.py b/system/version.py index 1f01b181c..0cea616d2 100755 --- a/system/version.py +++ b/system/version.py @@ -10,7 +10,7 @@ from openpilot.common.basedir import BASEDIR from openpilot.common.swaglog import cloudlog from openpilot.common.git import get_commit, get_origin, get_branch, get_short_branch, get_commit_date -RELEASE_BRANCHES = ['release-tizi-staging', 'release-tici', 'release-tizi', 'nightly'] +RELEASE_BRANCHES = ['release-tizi-staging', 'release-mici-staging', 'release-tizi', 'release-mici', 'nightly'] TESTED_BRANCHES = RELEASE_BRANCHES + ['devel-staging', 'nightly-dev'] BUILD_METADATA_FILENAME = "build.json" From 4f13a0f77520d3e04a93f6ae68b00ab26f243fb5 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 21 Nov 2025 17:23:53 -0500 Subject: [PATCH 426/910] ci: fix upstream merge conflicts with prebuilts (#1503) --- SConstruct | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SConstruct b/SConstruct index 48dd04470..f5d5ec6fb 100644 --- a/SConstruct +++ b/SConstruct @@ -178,7 +178,8 @@ Export('envCython', 'np_version') Export('env', 'arch') # Setup cache dir -cache_dir = '/data/scons_cache' if arch == "larch64" else '/tmp/scons_cache' +default_cache_dir = '/data/scons_cache' if arch == "larch64" else '/tmp/scons_cache' +cache_dir = ARGUMENTS.get('cache_dir', default_cache_dir) CacheDir(cache_dir) Clean(["."], cache_dir) From be8c5491b14b86dcf2c5401defba4dabe60c1a18 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 21 Nov 2025 14:25:21 -0800 Subject: [PATCH 427/910] even shorter --- selfdrive/ui/mici/layouts/onboarding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/mici/layouts/onboarding.py b/selfdrive/ui/mici/layouts/onboarding.py index afc7bfce1..52dbb785d 100644 --- a/selfdrive/ui/mici/layouts/onboarding.py +++ b/selfdrive/ui/mici/layouts/onboarding.py @@ -150,7 +150,7 @@ class TrainingGuideRecordFront(SetupTermsPage): super().__init__(on_continue, back_callback=on_back, back_text="no", continue_text="yes") self._title_header = TermsHeader("improve driver monitoring", gui_app.texture("icons_mici/setup/green_dm.png", 60, 60)) - self._dm_label = UnifiedLabel("Do you want to upload driver camera data to improve driver monitoring?", 42, + self._dm_label = UnifiedLabel("Do you want to upload driver camera data?", 42, FontWeight.ROMAN) def show_event(self): From d0c3972cc714664bb5869dccef5feda2b3022ae6 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 21 Nov 2025 18:55:01 -0800 Subject: [PATCH 428/910] update release branches (#36671) * update release branches * Update README.md --- README.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 19a30599b..a77a80935 100644 --- a/README.md +++ b/README.md @@ -49,13 +49,17 @@ To use openpilot in a car, you need four things: We have detailed instructions for [how to install the harness and device in a car](https://comma.ai/setup). Note that it's possible to run openpilot on [other hardware](https://blog.comma.ai/self-driving-car-for-free/), although it's not plug-and-play. + ### Branches -| branch | URL | description | -|------------------|----------------------------------------|-------------------------------------------------------------------------------------| -| `release3` | openpilot.comma.ai | This is openpilot's release branch. | -| `release3-staging` | openpilot-test.comma.ai | This is the staging branch for releases. Use it to get new releases slightly early. | -| `nightly` | openpilot-nightly.comma.ai | This is the bleeding edge development branch. Do not expect this to be stable. | -| `nightly-dev` | installer.comma.ai/commaai/nightly-dev | Same as nightly, but includes experimental development features for some cars. | + +Running `master` and other branches directly is supported, but it's recommended to run one of the following prebuilt branches: + +| comma four branch | comma 3X branch | URL | description | +|------------------------|------------------------|----------------------------------------|-------------------------------------------------------------------------------------| +| `release-mici` | `release-tizi` | openpilot.comma.ai | This is openpilot's release branch. | +| `release-mici-staging` | `release-tizi-staging` | openpilot-test.comma.ai | This is the staging branch for releases. Use it to get new releases slightly early. | +| `nightly` | `nightly` | openpilot-nightly.comma.ai | This is the bleeding edge development branch. Do not expect this to be stable. | +| `nightly-dev` | `nightly-dev` | installer.comma.ai/commaai/nightly-dev | Same as nightly, but includes experimental development features for some cars. | To start developing openpilot ------ From d92d2cb68340243b9649941ed242622ae5509d28 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 21 Nov 2025 22:28:51 -0500 Subject: [PATCH 429/910] ui: `GuiApplicationExt` (#1501) * ui: `GuiApplicationExt` * add to readme --- system/ui/README.md | 1 + system/ui/lib/application.py | 4 +++- system/ui/sunnypilot/lib/application.py | 15 +++++++++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 system/ui/sunnypilot/lib/application.py diff --git a/system/ui/README.md b/system/ui/README.md index f81cb5573..796d29862 100644 --- a/system/ui/README.md +++ b/system/ui/README.md @@ -10,6 +10,7 @@ Quick start: * set `BURN_IN=1` to get a burn-in heatmap version of the UI * set `GRID=50` to show a 50-pixel alignment grid overlay * set `MAGIC_DEBUG=1` to show every dropped frames (only on device) +* set `SUNNYPILOT_UI=0` to run the stock UI instead of the sunnypilot UI * 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 e3370a5f7..727112783 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -19,6 +19,8 @@ from openpilot.system.hardware import HARDWARE, PC from openpilot.system.ui.lib.multilang import multilang from openpilot.common.realtime import Ratekeeper +from openpilot.system.ui.sunnypilot.lib.application import GuiApplicationExt + _DEFAULT_FPS = int(os.getenv("FPS", {'tizi': 20}.get(HARDWARE.get_device_type(), 60))) FPS_LOG_INTERVAL = 5 # Seconds between logging FPS drops FPS_DROP_THRESHOLD = 0.9 # FPS drop threshold for triggering a warning @@ -186,7 +188,7 @@ class MouseState: self._prev_mouse_event[slot] = ev -class GuiApplication: +class GuiApplication(GuiApplicationExt): def __init__(self, width: int | None = None, height: int | None = None): self._fonts: dict[FontWeight, rl.Font] = {} self._width = width if width is not None else GuiApplication._default_width() diff --git a/system/ui/sunnypilot/lib/application.py b/system/ui/sunnypilot/lib/application.py new file mode 100644 index 000000000..b39bf31e9 --- /dev/null +++ b/system/ui/sunnypilot/lib/application.py @@ -0,0 +1,15 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +import os + +SUNNYPILOT_UI = os.getenv("SUNNYPILOT_UI", "1") == "1" + + +class GuiApplicationExt: + @staticmethod + def sunnypilot_ui() -> bool: + return SUNNYPILOT_UI From 457b6634fd0f3144170e050db370b24dc5401a64 Mon Sep 17 00:00:00 2001 From: Nayan Date: Fri, 21 Nov 2025 23:37:03 -0500 Subject: [PATCH 430/910] ui: sunnypilot toggle style (#1475) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * param to control stock vs sp ui * init styles * SP Toggles * Lint * optimizations * sp raylib preview * fix callback * fix ui preview * better padding * this * listitem -> listitemsp * add show_description method * remove padding from line separator. like, WHY? 😩😩 * ui: `GuiApplicationExt` * add to readme * use gui_app.sunnypilot_ui() * lint * no fancy toggles :( * mici scroller - no touchy --------- Co-authored-by: Jason Wen --- selfdrive/ui/layouts/settings/developer.py | 3 + selfdrive/ui/layouts/settings/toggles.py | 3 + .../ui/tests/test_ui/raylib_screenshots.py | 2 +- system/ui/sunnypilot/lib/styles.py | 50 +++++++++ system/ui/sunnypilot/widgets/list_view.py | 106 ++++++++++++++++++ system/ui/sunnypilot/widgets/toggle.py | 56 +++++++++ system/ui/widgets/network.py | 3 + 7 files changed, 222 insertions(+), 1 deletion(-) create mode 100644 system/ui/sunnypilot/lib/styles.py create mode 100644 system/ui/sunnypilot/widgets/list_view.py create mode 100644 system/ui/sunnypilot/widgets/toggle.py diff --git a/selfdrive/ui/layouts/settings/developer.py b/selfdrive/ui/layouts/settings/developer.py index 646c81750..f47cd310c 100644 --- a/selfdrive/ui/layouts/settings/developer.py +++ b/selfdrive/ui/layouts/settings/developer.py @@ -9,6 +9,9 @@ from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.lib.multilang import tr, tr_noop from openpilot.system.ui.widgets import DialogResult +if gui_app.sunnypilot_ui(): + from openpilot.system.ui.sunnypilot.widgets.list_view import toggle_item_sp as toggle_item + # Description constants DESCRIPTIONS = { 'enable_adb': tr_noop( diff --git a/selfdrive/ui/layouts/settings/toggles.py b/selfdrive/ui/layouts/settings/toggles.py index 7fae2dfd2..b7f176e24 100644 --- a/selfdrive/ui/layouts/settings/toggles.py +++ b/selfdrive/ui/layouts/settings/toggles.py @@ -9,6 +9,9 @@ from openpilot.system.ui.lib.multilang import tr, tr_noop from openpilot.system.ui.widgets import DialogResult from openpilot.selfdrive.ui.ui_state import ui_state +if gui_app.sunnypilot_ui(): + from openpilot.system.ui.sunnypilot.widgets.list_view import toggle_item_sp as toggle_item + PERSONALITY_TO_INT = log.LongitudinalPersonality.schema.enumerants # Description constants diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py index 481ac111b..d3de616cc 100755 --- a/selfdrive/ui/tests/test_ui/raylib_screenshots.py +++ b/selfdrive/ui/tests/test_ui/raylib_screenshots.py @@ -149,7 +149,7 @@ def setup_experimental_mode_description(click, pm: PubMaster): def setup_openpilot_long_confirmation_dialog(click, pm: PubMaster): setup_settings_developer(click, pm) - click(2000, 960) # toggle openpilot longitudinal control + click(650, 960) # toggle openpilot longitudinal control def setup_onroad(click, pm: PubMaster): diff --git a/system/ui/sunnypilot/lib/styles.py b/system/ui/sunnypilot/lib/styles.py new file mode 100644 index 000000000..a232ac192 --- /dev/null +++ b/system/ui/sunnypilot/lib/styles.py @@ -0,0 +1,50 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +from dataclasses import dataclass + +import pyray as rl + + +@dataclass +class Base: + # Widget/Control Base Dimensions + ITEM_BASE_HEIGHT = 170 + ITEM_PADDING = 20 + ITEM_TEXT_FONT_SIZE = 50 + ITEM_DESC_FONT_SIZE = 40 + ITEM_DESC_V_OFFSET = 150 + + # Toggle Control + TOGGLE_HEIGHT = 120 + TOGGLE_WIDTH = int(TOGGLE_HEIGHT * 1.75) + TOGGLE_BG_HEIGHT = TOGGLE_HEIGHT - 20 + + +@dataclass +class DefaultStyleSP(Base): + # Base Colors + BASE_BG_COLOR = rl.Color(57, 57, 57, 255) # Grey + ON_BG_COLOR = rl.Color(28, 101, 186, 255) # Blue + OFF_BG_COLOR = rl.Color(70, 70, 70, 255) # Lighter Grey + ON_HOVER_BG_COLOR = rl.Color(17, 78, 150, 255) # Dark Blue + OFF_HOVER_BG_COLOR = rl.Color(21, 21, 21, 255) # Dark gray + DISABLED_ON_BG_COLOR = rl.Color(37, 70, 107, 255) # Dull Blue + DISABLED_OFF_BG_COLOR = rl.Color(39, 39, 39, 255) # Grey + ITEM_TEXT_COLOR = rl.WHITE + ITEM_DISABLED_TEXT_COLOR = rl.Color(88, 88, 88, 255) + ITEM_DESC_TEXT_COLOR = rl.Color(128, 128, 128, 255) + + # Toggle Control + TOGGLE_ON_COLOR = ON_BG_COLOR + TOGGLE_OFF_COLOR = OFF_BG_COLOR + TOGGLE_KNOB_COLOR = rl.WHITE + TOGGLE_DISABLED_ON_COLOR = DISABLED_ON_BG_COLOR + TOGGLE_DISABLED_OFF_COLOR = DISABLED_OFF_BG_COLOR + TOGGLE_DISABLED_KNOB_COLOR = rl.Color(88, 88, 88, 255) # Lighter Grey + + +style = DefaultStyleSP diff --git a/system/ui/sunnypilot/widgets/list_view.py b/system/ui/sunnypilot/widgets/list_view.py new file mode 100644 index 000000000..dcf8f6019 --- /dev/null +++ b/system/ui/sunnypilot/widgets/list_view.py @@ -0,0 +1,106 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +from collections.abc import Callable + +import pyray as rl +from openpilot.system.ui.lib.text_measure import measure_text_cached +from openpilot.system.ui.sunnypilot.widgets.toggle import ToggleSP +from openpilot.system.ui.widgets.list_view import ListItem, ToggleAction, ItemAction +from openpilot.system.ui.sunnypilot.lib.styles import style + + +class ToggleActionSP(ToggleAction): + def __init__(self, initial_state: bool = False, width: int = style.TOGGLE_WIDTH, enabled: bool | Callable[[], bool] = True, + callback: Callable[[bool], None] | None = None, param: str | None = None): + ToggleAction.__init__(self, initial_state, width, enabled, callback) + self.toggle = ToggleSP(initial_state=initial_state, callback=callback, param=param) + + +class ListItemSP(ListItem): + def __init__(self, title: str | Callable[[], str] = "", icon: str | None = None, description: str | Callable[[], str] | None = None, + description_visible: bool = False, callback: Callable | None = None, + action_item: ItemAction | None = None): + ListItem.__init__(self, title, icon, description, description_visible, callback, action_item) + + def show_description(self, show: bool): + self._set_description_visible(show) + + def get_right_item_rect(self, item_rect: rl.Rectangle) -> rl.Rectangle: + if not self.action_item: + return rl.Rectangle(0, 0, 0, 0) + + right_width = self.action_item.rect.width + if right_width == 0: # Full width action (like DualButtonAction) + return rl.Rectangle(item_rect.x + style.ITEM_PADDING, item_rect.y, + item_rect.width - (style.ITEM_PADDING * 2), style.ITEM_BASE_HEIGHT) + + action_width = self.action_item.rect.width + if isinstance(self.action_item, ToggleAction): + action_x = item_rect.x + else: + action_x = item_rect.x + item_rect.width - action_width + action_y = item_rect.y + return rl.Rectangle(action_x, action_y, action_width, style.ITEM_BASE_HEIGHT) + + def _render(self, _): + content_x = self._rect.x + style.ITEM_PADDING + text_x = content_x + left_action_item = isinstance(self.action_item, ToggleAction) + + if left_action_item: + left_rect = rl.Rectangle( + content_x, + self._rect.y + (style.ITEM_BASE_HEIGHT - style.TOGGLE_HEIGHT) // 2, + style.TOGGLE_WIDTH, + style.TOGGLE_HEIGHT + ) + text_x = left_rect.x + left_rect.width + style.ITEM_PADDING * 1.5 + + # Draw title + if self.title: + text_size = measure_text_cached(self._font, self.title, style.ITEM_TEXT_FONT_SIZE) + item_y = self._rect.y + (style.ITEM_BASE_HEIGHT - text_size.y) // 2 + rl.draw_text_ex(self._font, self.title, rl.Vector2(text_x, item_y), style.ITEM_TEXT_FONT_SIZE, 0, style.ITEM_TEXT_COLOR) + + # Render toggle and handle callback + if self.action_item.render(left_rect) and self.action_item.enabled: + if self.callback: + self.callback() + + else: + if self.title: + # Draw main text + text_size = measure_text_cached(self._font, self.title, style.ITEM_TEXT_FONT_SIZE) + item_y = self._rect.y + (style.ITEM_BASE_HEIGHT - text_size.y) // 2 + rl.draw_text_ex(self._font, self.title, rl.Vector2(text_x, item_y), style.ITEM_TEXT_FONT_SIZE, 0, style.ITEM_TEXT_COLOR) + + # Draw right item if present + if self.action_item: + right_rect = self.get_right_item_rect(self._rect) + right_rect.y = self._rect.y + if self.action_item.render(right_rect) and self.action_item.enabled: + # Right item was clicked/activated + if self.callback: + self.callback() + + # Draw description if visible + if self.description_visible: + content_width = int(self._rect.width - style.ITEM_PADDING * 2) + description_height = self._html_renderer.get_total_height(content_width) + description_rect = rl.Rectangle( + self._rect.x + style.ITEM_PADDING, + self._rect.y + style.ITEM_DESC_V_OFFSET, + content_width, + description_height + ) + self._html_renderer.render(description_rect) + + +def toggle_item_sp(title: str | Callable[[], str], description: str | Callable[[], str] | None = None, initial_state: bool = False, + callback: Callable | None = None, icon: str = "", enabled: bool | Callable[[], bool] = True, param: str | None = None) -> ListItemSP: + action = ToggleActionSP(initial_state=initial_state, enabled=enabled, callback=callback, param=param) + return ListItemSP(title=title, description=description, action_item=action, icon=icon, callback=callback) diff --git a/system/ui/sunnypilot/widgets/toggle.py b/system/ui/sunnypilot/widgets/toggle.py new file mode 100644 index 000000000..46b390f77 --- /dev/null +++ b/system/ui/sunnypilot/widgets/toggle.py @@ -0,0 +1,56 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +from collections.abc import Callable + +import pyray as rl +from openpilot.common.params import Params +from openpilot.system.ui.lib.application import MousePos +from openpilot.system.ui.widgets.toggle import Toggle +from openpilot.system.ui.sunnypilot.lib.styles import style + +KNOB_PADDING = 5 +KNOB_RADIUS = style.TOGGLE_BG_HEIGHT / 2 - KNOB_PADDING + + +class ToggleSP(Toggle): + def __init__(self, initial_state=False, callback: Callable[[bool], None] | None = None, param: str | None = None): + self.param_key = param + self.params = Params() + if self.param_key: + initial_state = self.params.get_bool(self.param_key) + Toggle.__init__(self, initial_state, callback) + + def _handle_mouse_release(self, mouse_pos: MousePos): + super()._handle_mouse_release(mouse_pos) + if self._enabled and self.param_key: + self.params.put_bool(self.param_key, self._state) + + def _render(self, rect: rl.Rectangle): + self.update() + self._rect.y -= style.ITEM_PADDING / 2 + if self._enabled: + bg_color = self._blend_color(style.TOGGLE_OFF_COLOR, style.TOGGLE_ON_COLOR, self._progress) + knob_color = style.TOGGLE_KNOB_COLOR + else: + bg_color = self._blend_color(style.TOGGLE_DISABLED_OFF_COLOR, style.TOGGLE_DISABLED_ON_COLOR, self._progress) + knob_color = style.TOGGLE_DISABLED_KNOB_COLOR + + # Draw background + bg_rect = rl.Rectangle(self._rect.x, self._rect.y, style.TOGGLE_WIDTH, style.TOGGLE_BG_HEIGHT) + + # Draw actual background + rl.draw_rectangle_rounded(bg_rect, 1.0, 10, bg_color) + + left_edge = bg_rect.x + KNOB_PADDING + right_edge = bg_rect.x + bg_rect.width - KNOB_PADDING + + knob_travel_distance = right_edge - left_edge - 2 * KNOB_RADIUS + min_knob_x = left_edge + KNOB_RADIUS + knob_x = min_knob_x + knob_travel_distance * self._progress + knob_y = self._rect.y + style.TOGGLE_BG_HEIGHT / 2 + + rl.draw_circle(int(knob_x), int(knob_y), KNOB_RADIUS, knob_color) diff --git a/system/ui/widgets/network.py b/system/ui/widgets/network.py index f41a04c24..fa47d3553 100644 --- a/system/ui/widgets/network.py +++ b/system/ui/widgets/network.py @@ -15,6 +15,9 @@ from openpilot.system.ui.widgets.label import gui_label from openpilot.system.ui.widgets.scroller_tici import Scroller from openpilot.system.ui.widgets.list_view import ButtonAction, ListItem, MultipleButtonAction, ToggleAction, button_item, text_item +if gui_app.sunnypilot_ui(): + from openpilot.system.ui.sunnypilot.widgets.list_view import ToggleActionSP as ToggleAction + # These are only used for AdvancedNetworkSettings, standalone apps just need WifiManagerUI try: from openpilot.common.params import Params From 3904405c5d02222774d7022c1532125c9b128524 Mon Sep 17 00:00:00 2001 From: infiniteCable2 Date: Sun, 23 Nov 2025 15:35:58 +0100 Subject: [PATCH 431/910] Update opendbc_repo --- opendbc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc_repo b/opendbc_repo index 1f60a57f5..7c55504b9 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 1f60a57f57b3dd237529236711b4b86cef82f3a3 +Subproject commit 7c55504b96508d180f4ad135e20a9d083004c5fc From 3cd55260d97f695b69b8b8ef5c3fcfe47409f234 Mon Sep 17 00:00:00 2001 From: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Sun, 23 Nov 2025 12:22:23 -0800 Subject: [PATCH 432/910] modeld: remove SNPE accelerator (#1476) * commaai/openpilot:d05cb31e2e916fba41ba8167030945f427fd811b * bump opendbc * bump opendbc * bump opendbc * bump opendbc * bump opendbc * sunnypilot: remove Qt * cabana: revert to stock Qt * commaai/openpilot:5198b1b079c37742c1050f02ce0aa6dd42b038b9 * commaai/openpilot:954b567b9ba0f3d1ae57d6aa7797fa86dd92ec6e * commaai/openpilot:7534b2a160faa683412c04c1254440e338931c5e * sum more * bump opendbc * not yet * should've been symlink'ed * raylib says wut * quiet mode back * more fixes * no more * too extra red diff on the side * need to bring this back * too extra * let's update docs here * Revert "let's update docs here" This reverts commit 51fe03cd5121e6fdf14657b2c33852c34922b851. * del snpe * reset min for safety catch as we killed snpe * uhhh --------- Co-authored-by: Jason Wen --- SConstruct | 10 - sunnypilot/modeld/SConscript | 17 +- sunnypilot/modeld/runners/__init__.py | 5 - sunnypilot/modeld/runners/run.h | 1 - sunnypilot/modeld/runners/snpemodel.cc | 116 ---- sunnypilot/modeld/runners/snpemodel.h | 52 -- sunnypilot/modeld/runners/snpemodel.pxd | 9 - sunnypilot/modeld/runners/snpemodel_pyx.pyx | 17 - sunnypilot/models/fetcher.py | 2 +- sunnypilot/models/helpers.py | 4 +- .../libPlatformValidatorShared.so | 3 - .../snpe/aarch64-ubuntu-gcc7.5/libSNPE.so | 3 - .../aarch64-ubuntu-gcc7.5/libcalculator.so | 3 - .../snpe/aarch64-ubuntu-gcc7.5/libhta.so | 3 - .../libsnpe_dsp_domains_v2.so | 3 - third_party/snpe/dsp/libcalculator_skel.so | 3 - .../dsp/libsnpe_dsp_v65_domains_v2_skel.so | 3 - .../dsp/libsnpe_dsp_v66_domains_v2_skel.so | 3 - .../dsp/libsnpe_dsp_v68_domains_v3_skel.so | 3 - third_party/snpe/include/DiagLog/IDiagLog.hpp | 84 --- third_party/snpe/include/DiagLog/Options.hpp | 79 --- .../snpe/include/DlContainer/IDlContainer.hpp | 191 ------- third_party/snpe/include/DlSystem/DlEnums.hpp | 234 -------- third_party/snpe/include/DlSystem/DlError.hpp | 259 --------- .../snpe/include/DlSystem/DlOptional.hpp | 225 -------- .../snpe/include/DlSystem/DlVersion.hpp | 78 --- .../include/DlSystem/IBufferAttributes.hpp | 86 --- .../include/DlSystem/IOBufferDataTypeMap.hpp | 127 ----- third_party/snpe/include/DlSystem/ITensor.hpp | 146 ----- .../snpe/include/DlSystem/ITensorFactory.hpp | 92 --- .../snpe/include/DlSystem/ITensorItr.hpp | 182 ------ .../snpe/include/DlSystem/ITensorItrImpl.hpp | 42 -- third_party/snpe/include/DlSystem/IUDL.hpp | 105 ---- .../snpe/include/DlSystem/IUserBuffer.hpp | 358 ------------ .../include/DlSystem/IUserBufferFactory.hpp | 81 --- .../snpe/include/DlSystem/PlatformConfig.hpp | 230 -------- .../snpe/include/DlSystem/RuntimeList.hpp | 154 ----- third_party/snpe/include/DlSystem/String.hpp | 104 ---- .../snpe/include/DlSystem/StringList.hpp | 107 ---- .../snpe/include/DlSystem/TensorMap.hpp | 120 ---- .../snpe/include/DlSystem/TensorShape.hpp | 203 ------- .../snpe/include/DlSystem/TensorShapeMap.hpp | 127 ----- .../snpe/include/DlSystem/UDLContext.hpp | 243 -------- third_party/snpe/include/DlSystem/UDLFunc.hpp | 87 --- .../snpe/include/DlSystem/UserBufferMap.hpp | 122 ---- .../snpe/include/DlSystem/UserMemoryMap.hpp | 129 ----- .../snpe/include/DlSystem/ZdlExportDefine.hpp | 13 - .../PlatformValidator/PlatformValidator.hpp | 118 ---- .../include/SNPE/ApplicationBufferMap.hpp | 101 ---- third_party/snpe/include/SNPE/PSNPE.hpp | 205 ------- .../snpe/include/SNPE/RuntimeConfigList.hpp | 85 --- third_party/snpe/include/SNPE/SNPE.hpp | 258 --------- third_party/snpe/include/SNPE/SNPEBuilder.hpp | 306 ---------- third_party/snpe/include/SNPE/SNPEFactory.hpp | 220 ------- .../snpe/include/SNPE/UserBufferList.hpp | 49 -- third_party/snpe/include/SnpeUdo/UdoBase.h | 537 ------------------ third_party/snpe/include/SnpeUdo/UdoImpl.h | 343 ----------- third_party/snpe/include/SnpeUdo/UdoImplCpu.h | 44 -- third_party/snpe/include/SnpeUdo/UdoImplDsp.h | 207 ------- third_party/snpe/include/SnpeUdo/UdoImplGpu.h | 112 ---- third_party/snpe/include/SnpeUdo/UdoReg.h | 108 ---- third_party/snpe/include/SnpeUdo/UdoShared.h | 48 -- third_party/snpe/larch64 | 1 - third_party/snpe/x86_64 | 1 - .../snpe/x86_64-linux-clang/libHtpPrepare.so | 3 - .../snpe/x86_64-linux-clang/libSNPE.so | 3 - third_party/snpe/x86_64-linux-clang/libomp.so | 3 - 67 files changed, 4 insertions(+), 7016 deletions(-) delete mode 100644 sunnypilot/modeld/runners/snpemodel.cc delete mode 100644 sunnypilot/modeld/runners/snpemodel.h delete mode 100644 sunnypilot/modeld/runners/snpemodel.pxd delete mode 100644 sunnypilot/modeld/runners/snpemodel_pyx.pyx delete mode 100644 third_party/snpe/aarch64-ubuntu-gcc7.5/libPlatformValidatorShared.so delete mode 100644 third_party/snpe/aarch64-ubuntu-gcc7.5/libSNPE.so delete mode 100644 third_party/snpe/aarch64-ubuntu-gcc7.5/libcalculator.so delete mode 100644 third_party/snpe/aarch64-ubuntu-gcc7.5/libhta.so delete mode 100644 third_party/snpe/aarch64-ubuntu-gcc7.5/libsnpe_dsp_domains_v2.so delete mode 100644 third_party/snpe/dsp/libcalculator_skel.so delete mode 100644 third_party/snpe/dsp/libsnpe_dsp_v65_domains_v2_skel.so delete mode 100644 third_party/snpe/dsp/libsnpe_dsp_v66_domains_v2_skel.so delete mode 100644 third_party/snpe/dsp/libsnpe_dsp_v68_domains_v3_skel.so delete mode 100644 third_party/snpe/include/DiagLog/IDiagLog.hpp delete mode 100644 third_party/snpe/include/DiagLog/Options.hpp delete mode 100644 third_party/snpe/include/DlContainer/IDlContainer.hpp delete mode 100644 third_party/snpe/include/DlSystem/DlEnums.hpp delete mode 100644 third_party/snpe/include/DlSystem/DlError.hpp delete mode 100644 third_party/snpe/include/DlSystem/DlOptional.hpp delete mode 100644 third_party/snpe/include/DlSystem/DlVersion.hpp delete mode 100644 third_party/snpe/include/DlSystem/IBufferAttributes.hpp delete mode 100644 third_party/snpe/include/DlSystem/IOBufferDataTypeMap.hpp delete mode 100644 third_party/snpe/include/DlSystem/ITensor.hpp delete mode 100644 third_party/snpe/include/DlSystem/ITensorFactory.hpp delete mode 100644 third_party/snpe/include/DlSystem/ITensorItr.hpp delete mode 100644 third_party/snpe/include/DlSystem/ITensorItrImpl.hpp delete mode 100644 third_party/snpe/include/DlSystem/IUDL.hpp delete mode 100644 third_party/snpe/include/DlSystem/IUserBuffer.hpp delete mode 100644 third_party/snpe/include/DlSystem/IUserBufferFactory.hpp delete mode 100644 third_party/snpe/include/DlSystem/PlatformConfig.hpp delete mode 100644 third_party/snpe/include/DlSystem/RuntimeList.hpp delete mode 100644 third_party/snpe/include/DlSystem/String.hpp delete mode 100644 third_party/snpe/include/DlSystem/StringList.hpp delete mode 100644 third_party/snpe/include/DlSystem/TensorMap.hpp delete mode 100644 third_party/snpe/include/DlSystem/TensorShape.hpp delete mode 100644 third_party/snpe/include/DlSystem/TensorShapeMap.hpp delete mode 100644 third_party/snpe/include/DlSystem/UDLContext.hpp delete mode 100644 third_party/snpe/include/DlSystem/UDLFunc.hpp delete mode 100644 third_party/snpe/include/DlSystem/UserBufferMap.hpp delete mode 100644 third_party/snpe/include/DlSystem/UserMemoryMap.hpp delete mode 100644 third_party/snpe/include/DlSystem/ZdlExportDefine.hpp delete mode 100644 third_party/snpe/include/PlatformValidator/PlatformValidator.hpp delete mode 100644 third_party/snpe/include/SNPE/ApplicationBufferMap.hpp delete mode 100644 third_party/snpe/include/SNPE/PSNPE.hpp delete mode 100644 third_party/snpe/include/SNPE/RuntimeConfigList.hpp delete mode 100644 third_party/snpe/include/SNPE/SNPE.hpp delete mode 100644 third_party/snpe/include/SNPE/SNPEBuilder.hpp delete mode 100644 third_party/snpe/include/SNPE/SNPEFactory.hpp delete mode 100644 third_party/snpe/include/SNPE/UserBufferList.hpp delete mode 100644 third_party/snpe/include/SnpeUdo/UdoBase.h delete mode 100644 third_party/snpe/include/SnpeUdo/UdoImpl.h delete mode 100644 third_party/snpe/include/SnpeUdo/UdoImplCpu.h delete mode 100644 third_party/snpe/include/SnpeUdo/UdoImplDsp.h delete mode 100644 third_party/snpe/include/SnpeUdo/UdoImplGpu.h delete mode 100644 third_party/snpe/include/SnpeUdo/UdoReg.h delete mode 100644 third_party/snpe/include/SnpeUdo/UdoShared.h delete mode 120000 third_party/snpe/larch64 delete mode 120000 third_party/snpe/x86_64 delete mode 100644 third_party/snpe/x86_64-linux-clang/libHtpPrepare.so delete mode 100644 third_party/snpe/x86_64-linux-clang/libSNPE.so delete mode 100755 third_party/snpe/x86_64-linux-clang/libomp.so diff --git a/SConstruct b/SConstruct index f5d5ec6fb..b2abae8d1 100644 --- a/SConstruct +++ b/SConstruct @@ -75,7 +75,6 @@ env = Environment( "#third_party/acados/include/hpipm/include", "#third_party/catch2/include", "#third_party/libyuv/include", - "#third_party/snpe/include", ], LIBPATH=[ "#common", @@ -101,7 +100,6 @@ if arch == "larch64": "/usr/local/lib", "/system/vendor/lib64", "/usr/lib/aarch64-linux-gnu", - "#third_party/snpe/larch64", ]) arch_flags = ["-D__TICI__", "-mcpu=cortex-a57", "-DQCOM2"] env.Append(CCFLAGS=arch_flags) @@ -125,14 +123,6 @@ else: "/usr/local/lib", ]) - if arch == "x86_64": - env.Append(LIBPATH=[ - f"#third_party/snpe/{arch}" - ]) - env.Append(RPATH=[ - Dir(f"#third_party/snpe/{arch}").abspath, - ]) - # Sanitizers and extra CCFLAGS from CLI if GetOption('asan'): env.Append(CCFLAGS=["-fsanitize=address", "-fno-omit-frame-pointer"]) diff --git a/sunnypilot/modeld/SConscript b/sunnypilot/modeld/SConscript index dc6f6700d..dfab658b2 100644 --- a/sunnypilot/modeld/SConscript +++ b/sunnypilot/modeld/SConscript @@ -1,5 +1,3 @@ -import glob - Import('env', 'envCython', 'arch', 'cereal', 'messaging', 'common', 'visionipc', 'transformations') lenv = env.Clone() lenvCython = envCython.Clone() @@ -23,12 +21,6 @@ thneed_src_qcom = thneed_src_common + ["thneed/thneed_qcom2.cc"] thneed_src_pc = thneed_src_common + ["thneed/thneed_pc.cc"] thneed_src = thneed_src_qcom if arch == "larch64" else thneed_src_pc -# SNPE except on Mac and ARM Linux -snpe_lib = [] -if arch != "Darwin" and arch != "aarch64": - common_src += ['runners/snpemodel.cc'] - snpe_lib += ['SNPE'] - # OpenCL is a framework on Mac if arch == "Darwin": frameworks += ['OpenCL'] @@ -40,20 +32,13 @@ for pathdef, fn in {'TRANSFORM': 'transforms/transform.cl', 'LOADYUV': 'transfor for xenv in (lenv, lenvCython): xenv['CXXFLAGS'].append(f'-D{pathdef}_PATH=\\"{File(fn).abspath}\\"') -# Compile cython -snpe_rpath_qcom = "/data/pythonpath/third_party/snpe/larch64" -snpe_rpath_pc = f"{Dir('#').abspath}/third_party/snpe/x86_64-linux-clang" -snpe_rpath = lenvCython['RPATH'] + [snpe_rpath_qcom if arch == "larch64" else snpe_rpath_pc] - cython_libs = envCython["LIBS"] + libs -snpemodel_lib = lenv.Library('snpemodel', ['runners/snpemodel.cc']) commonmodel_lib = lenv.Library('commonmodel', common_src) lenvCython.Program('runners/runmodel_pyx.so', 'runners/runmodel_pyx.pyx', LIBS=cython_libs, FRAMEWORKS=frameworks) -lenvCython.Program('runners/snpemodel_pyx.so', 'runners/snpemodel_pyx.pyx', LIBS=[snpemodel_lib, snpe_lib, *cython_libs], FRAMEWORKS=frameworks, RPATH=snpe_rpath) lenvCython.Program('models/commonmodel_pyx.so', 'models/commonmodel_pyx.pyx', LIBS=[commonmodel_lib, *cython_libs], FRAMEWORKS=frameworks) if arch == "larch64": thneed_lib = env.SharedLibrary('thneed', thneed_src, LIBS=[common, 'OpenCL', 'dl']) thneedmodel_lib = env.Library('thneedmodel', ['runners/thneedmodel.cc']) - lenvCython.Program('runners/thneedmodel_pyx.so', 'runners/thneedmodel_pyx.pyx', LIBS=envCython["LIBS"]+[thneedmodel_lib, thneed_lib, common, 'dl', 'OpenCL']) \ No newline at end of file + lenvCython.Program('runners/thneedmodel_pyx.so', 'runners/thneedmodel_pyx.pyx', LIBS=envCython["LIBS"]+[thneedmodel_lib, thneed_lib, common, 'dl', 'OpenCL']) diff --git a/sunnypilot/modeld/runners/__init__.py b/sunnypilot/modeld/runners/__init__.py index 3f4507e4e..d105685bd 100644 --- a/sunnypilot/modeld/runners/__init__.py +++ b/sunnypilot/modeld/runners/__init__.py @@ -4,20 +4,15 @@ from openpilot.sunnypilot.modeld.runners.runmodel_pyx import RunModel, Runtime assert Runtime USE_THNEED = int(os.getenv('USE_THNEED', str(int(TICI)))) -USE_SNPE = int(os.getenv('USE_SNPE', str(int(TICI)))) class ModelRunner(RunModel): THNEED = 'THNEED' - SNPE = 'SNPE' ONNX = 'ONNX' def __new__(cls, paths, *args, **kwargs): if ModelRunner.THNEED in paths and USE_THNEED: from openpilot.sunnypilot.modeld.runners.thneedmodel_pyx import ThneedModel as Runner runner_type = ModelRunner.THNEED - elif ModelRunner.SNPE in paths and USE_SNPE: - from openpilot.sunnypilot.modeld.runners.snpemodel_pyx import SNPEModel as Runner - runner_type = ModelRunner.SNPE elif ModelRunner.ONNX in paths: from openpilot.sunnypilot.modeld.runners.onnxmodel import ONNXModel as Runner runner_type = ModelRunner.ONNX diff --git a/sunnypilot/modeld/runners/run.h b/sunnypilot/modeld/runners/run.h index 84d99fd10..55e703ef9 100644 --- a/sunnypilot/modeld/runners/run.h +++ b/sunnypilot/modeld/runners/run.h @@ -1,4 +1,3 @@ #pragma once #include "sunnypilot/modeld/runners/runmodel.h" -#include "sunnypilot/modeld/runners/snpemodel.h" diff --git a/sunnypilot/modeld/runners/snpemodel.cc b/sunnypilot/modeld/runners/snpemodel.cc deleted file mode 100644 index ce06ba39f..000000000 --- a/sunnypilot/modeld/runners/snpemodel.cc +++ /dev/null @@ -1,116 +0,0 @@ -#pragma clang diagnostic ignored "-Wexceptions" - -#include "sunnypilot/modeld/runners/snpemodel.h" - -#include -#include -#include -#include -#include - -#include "common/util.h" -#include "common/timing.h" - -void PrintErrorStringAndExit() { - std::cerr << zdl::DlSystem::getLastErrorString() << std::endl; - std::exit(EXIT_FAILURE); -} - -SNPEModel::SNPEModel(const std::string path, float *_output, size_t _output_size, int runtime, bool _use_tf8, cl_context context) { - output = _output; - output_size = _output_size; - use_tf8 = _use_tf8; - -#ifdef QCOM2 - if (runtime == USE_GPU_RUNTIME) { - snpe_runtime = zdl::DlSystem::Runtime_t::GPU; - } else if (runtime == USE_DSP_RUNTIME) { - snpe_runtime = zdl::DlSystem::Runtime_t::DSP; - } else { - snpe_runtime = zdl::DlSystem::Runtime_t::CPU; - } - assert(zdl::SNPE::SNPEFactory::isRuntimeAvailable(snpe_runtime)); -#endif - model_data = util::read_file(path); - assert(model_data.size() > 0); - - // load model - std::unique_ptr container = zdl::DlContainer::IDlContainer::open((uint8_t*)model_data.data(), model_data.size()); - if (!container) { PrintErrorStringAndExit(); } - LOGW("loaded model with size: %lu", model_data.size()); - - // create model runner - zdl::SNPE::SNPEBuilder snpe_builder(container.get()); - while (!snpe) { -#ifdef QCOM2 - snpe = snpe_builder.setOutputLayers({}) - .setRuntimeProcessor(snpe_runtime) - .setUseUserSuppliedBuffers(true) - .setPerformanceProfile(zdl::DlSystem::PerformanceProfile_t::HIGH_PERFORMANCE) - .build(); -#else - snpe = snpe_builder.setOutputLayers({}) - .setUseUserSuppliedBuffers(true) - .setPerformanceProfile(zdl::DlSystem::PerformanceProfile_t::HIGH_PERFORMANCE) - .build(); -#endif - if (!snpe) std::cerr << zdl::DlSystem::getLastErrorString() << std::endl; - } - - // create output buffer - zdl::DlSystem::UserBufferEncodingFloat ub_encoding_float; - zdl::DlSystem::IUserBufferFactory &ub_factory = zdl::SNPE::SNPEFactory::getUserBufferFactory(); - - const auto &output_tensor_names_opt = snpe->getOutputTensorNames(); - if (!output_tensor_names_opt) throw std::runtime_error("Error obtaining output tensor names"); - const auto &output_tensor_names = *output_tensor_names_opt; - assert(output_tensor_names.size() == 1); - const char *output_tensor_name = output_tensor_names.at(0); - const zdl::DlSystem::TensorShape &buffer_shape = snpe->getInputOutputBufferAttributes(output_tensor_name)->getDims(); - if (output_size != 0) { - assert(output_size == buffer_shape[1]); - } else { - output_size = buffer_shape[1]; - } - std::vector output_strides = {output_size * sizeof(float), sizeof(float)}; - output_buffer = ub_factory.createUserBuffer(output, output_size * sizeof(float), output_strides, &ub_encoding_float); - output_map.add(output_tensor_name, output_buffer.get()); -} - -void SNPEModel::addInput(const std::string name, float *buffer, int size) { - const int idx = inputs.size(); - const auto &input_tensor_names_opt = snpe->getInputTensorNames(); - if (!input_tensor_names_opt) throw std::runtime_error("Error obtaining input tensor names"); - const auto &input_tensor_names = *input_tensor_names_opt; - const char *input_tensor_name = input_tensor_names.at(idx); - const bool input_tf8 = use_tf8 && strcmp(input_tensor_name, "input_img") == 0; // TODO: This is a terrible hack, get rid of this name check both here and in onnx_runner.py - LOGW("adding index %d: %s", idx, input_tensor_name); - - zdl::DlSystem::UserBufferEncodingFloat ub_encoding_float; - zdl::DlSystem::UserBufferEncodingTf8 ub_encoding_tf8(0, 1./255); // network takes 0-1 - zdl::DlSystem::IUserBufferFactory &ub_factory = zdl::SNPE::SNPEFactory::getUserBufferFactory(); - zdl::DlSystem::UserBufferEncoding *input_encoding = input_tf8 ? (zdl::DlSystem::UserBufferEncoding*)&ub_encoding_tf8 : (zdl::DlSystem::UserBufferEncoding*)&ub_encoding_float; - - const auto &buffer_shape_opt = snpe->getInputDimensions(input_tensor_name); - const zdl::DlSystem::TensorShape &buffer_shape = *buffer_shape_opt; - size_t size_of_input = input_tf8 ? sizeof(uint8_t) : sizeof(float); - std::vector strides(buffer_shape.rank()); - strides[strides.size() - 1] = size_of_input; - size_t product = 1; - for (size_t i = 0; i < buffer_shape.rank(); i++) product *= buffer_shape[i]; - size_t stride = strides[strides.size() - 1]; - for (size_t i = buffer_shape.rank() - 1; i > 0; i--) { - stride *= buffer_shape[i]; - strides[i-1] = stride; - } - - auto input_buffer = ub_factory.createUserBuffer(buffer, product*size_of_input, strides, input_encoding); - input_map.add(input_tensor_name, input_buffer.get()); - inputs.push_back(std::unique_ptr(new SNPEModelInput(name, buffer, size, std::move(input_buffer)))); -} - -void SNPEModel::execute() { - if (!snpe->execute(input_map, output_map)) { - PrintErrorStringAndExit(); - } -} diff --git a/sunnypilot/modeld/runners/snpemodel.h b/sunnypilot/modeld/runners/snpemodel.h deleted file mode 100644 index bd7662421..000000000 --- a/sunnypilot/modeld/runners/snpemodel.h +++ /dev/null @@ -1,52 +0,0 @@ -#pragma once -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "sunnypilot/modeld/runners/runmodel.h" - -struct SNPEModelInput : public ModelInput { - std::unique_ptr snpe_buffer; - - SNPEModelInput(const std::string _name, float *_buffer, int _size, std::unique_ptr _snpe_buffer) : ModelInput(_name, _buffer, _size), snpe_buffer(std::move(_snpe_buffer)) {} - void setBuffer(float *_buffer, int _size) { - ModelInput::setBuffer(_buffer, _size); - assert(snpe_buffer->setBufferAddress(_buffer) == true); - } -}; - -class SNPEModel : public RunModel { -public: - SNPEModel(const std::string path, float *_output, size_t _output_size, int runtime, bool use_tf8 = false, cl_context context = NULL); - void addInput(const std::string name, float *buffer, int size); - void execute(); - -private: - std::string model_data; - -#ifdef QCOM2 - zdl::DlSystem::Runtime_t snpe_runtime; -#endif - - // snpe model stuff - std::unique_ptr snpe; - zdl::DlSystem::UserBufferMap input_map; - zdl::DlSystem::UserBufferMap output_map; - std::unique_ptr output_buffer; - - bool use_tf8; - float *output; - size_t output_size; -}; diff --git a/sunnypilot/modeld/runners/snpemodel.pxd b/sunnypilot/modeld/runners/snpemodel.pxd deleted file mode 100644 index f19501d37..000000000 --- a/sunnypilot/modeld/runners/snpemodel.pxd +++ /dev/null @@ -1,9 +0,0 @@ -# distutils: language = c++ - -from libcpp.string cimport string - -from msgq.visionipc.visionipc cimport cl_context - -cdef extern from "sunnypilot/modeld/runners/snpemodel.h": - cdef cppclass SNPEModel: - SNPEModel(string, float*, size_t, int, bool, cl_context) diff --git a/sunnypilot/modeld/runners/snpemodel_pyx.pyx b/sunnypilot/modeld/runners/snpemodel_pyx.pyx deleted file mode 100644 index 056ba9d4f..000000000 --- a/sunnypilot/modeld/runners/snpemodel_pyx.pyx +++ /dev/null @@ -1,17 +0,0 @@ -# distutils: language = c++ -# cython: c_string_encoding=ascii, language_level=3 - -import os -from libcpp cimport bool -from libcpp.string cimport string - -from .snpemodel cimport SNPEModel as cppSNPEModel -from openpilot.sunnypilot.modeld.models.commonmodel_pyx cimport CLContext -from openpilot.sunnypilot.modeld.runners.runmodel_pyx cimport RunModel -from openpilot.sunnypilot.modeld.runners.runmodel cimport RunModel as cppRunModel - -os.environ['ADSP_LIBRARY_PATH'] = "/data/pythonpath/third_party/snpe/dsp/" - -cdef class SNPEModel(RunModel): - def __cinit__(self, string path, float[:] output, int runtime, bool use_tf8, CLContext context): - self.model = new cppSNPEModel(path, &output[0], len(output), runtime, use_tf8, context.context) diff --git a/sunnypilot/models/fetcher.py b/sunnypilot/models/fetcher.py index 1e451a7e3..a917d6cbb 100644 --- a/sunnypilot/models/fetcher.py +++ b/sunnypilot/models/fetcher.py @@ -116,7 +116,7 @@ class ModelCache: class ModelFetcher: """Handles fetching and caching of model data from remote source""" - MODEL_URL = "https://raw.githubusercontent.com/sunnypilot/sunnypilot-docs/refs/heads/gh-pages/docs/driving_models_v9.json" + MODEL_URL = "https://raw.githubusercontent.com/sunnypilot/sunnypilot-docs/refs/heads/gh-pages/docs/driving_models_v10.json" def __init__(self, params: Params): self.params = params diff --git a/sunnypilot/models/helpers.py b/sunnypilot/models/helpers.py index 51a3a2459..a58118537 100644 --- a/sunnypilot/models/helpers.py +++ b/sunnypilot/models/helpers.py @@ -19,8 +19,8 @@ from openpilot.system.hardware.hw import Paths from pathlib import Path # see the README.md for more details on the model selector versioning -CURRENT_SELECTOR_VERSION = 11 -REQUIRED_MIN_SELECTOR_VERSION = 11 +CURRENT_SELECTOR_VERSION = 12 +REQUIRED_MIN_SELECTOR_VERSION = 12 USE_ONNX = os.getenv('USE_ONNX', PC) diff --git a/third_party/snpe/aarch64-ubuntu-gcc7.5/libPlatformValidatorShared.so b/third_party/snpe/aarch64-ubuntu-gcc7.5/libPlatformValidatorShared.so deleted file mode 100644 index a1c6fed91..000000000 --- a/third_party/snpe/aarch64-ubuntu-gcc7.5/libPlatformValidatorShared.so +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fb3b1fd29d958e9a3a6625eac9fac9e7cd6eb40309b285ad973324761db0b4c9 -size 1202792 diff --git a/third_party/snpe/aarch64-ubuntu-gcc7.5/libSNPE.so b/third_party/snpe/aarch64-ubuntu-gcc7.5/libSNPE.so deleted file mode 100644 index 54c0c32e4..000000000 --- a/third_party/snpe/aarch64-ubuntu-gcc7.5/libSNPE.so +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:161a5d0bf7347465b53ae49690a38fbacf03d606ef204147b3b148a5f59188da -size 9008016 diff --git a/third_party/snpe/aarch64-ubuntu-gcc7.5/libcalculator.so b/third_party/snpe/aarch64-ubuntu-gcc7.5/libcalculator.so deleted file mode 100644 index 215420302..000000000 --- a/third_party/snpe/aarch64-ubuntu-gcc7.5/libcalculator.so +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7e0e66c12a1eb3b5b4b2b2694831ca51e5132818f400dad789adbcc30e0e0793 -size 14032 diff --git a/third_party/snpe/aarch64-ubuntu-gcc7.5/libhta.so b/third_party/snpe/aarch64-ubuntu-gcc7.5/libhta.so deleted file mode 100644 index 1d81abd3a..000000000 --- a/third_party/snpe/aarch64-ubuntu-gcc7.5/libhta.so +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:784f2e80fa3534cf7934c5ecbbda37db633104380f2b41d896b4721ad6006eb2 -size 2420312 diff --git a/third_party/snpe/aarch64-ubuntu-gcc7.5/libsnpe_dsp_domains_v2.so b/third_party/snpe/aarch64-ubuntu-gcc7.5/libsnpe_dsp_domains_v2.so deleted file mode 100644 index 40e3d5af7..000000000 --- a/third_party/snpe/aarch64-ubuntu-gcc7.5/libsnpe_dsp_domains_v2.so +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:33afe465e74bbe2c409350d2ca8e86cfadf050ceb8feac75a86adc19ff1f9c48 -size 26016 diff --git a/third_party/snpe/dsp/libcalculator_skel.so b/third_party/snpe/dsp/libcalculator_skel.so deleted file mode 100644 index 9e9e9221b..000000000 --- a/third_party/snpe/dsp/libcalculator_skel.so +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7bee94d38195478ffdd0ce15292a2dfa7813377f4c3b7d99c270e05079d1746b -size 20828 diff --git a/third_party/snpe/dsp/libsnpe_dsp_v65_domains_v2_skel.so b/third_party/snpe/dsp/libsnpe_dsp_v65_domains_v2_skel.so deleted file mode 100644 index 3f1b7a8b8..000000000 --- a/third_party/snpe/dsp/libsnpe_dsp_v65_domains_v2_skel.so +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4e040c87072aa915c47859c8c7f74076b98c6919b83ddd5dc4bb555870d586a7 -size 1813866 diff --git a/third_party/snpe/dsp/libsnpe_dsp_v66_domains_v2_skel.so b/third_party/snpe/dsp/libsnpe_dsp_v66_domains_v2_skel.so deleted file mode 100644 index aa42b5e83..000000000 --- a/third_party/snpe/dsp/libsnpe_dsp_v66_domains_v2_skel.so +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:838bc58ac0094ba9593cf7c44ab6e8a94ae3dbbe176e320d9fbe86ceead53c5e -size 1817962 diff --git a/third_party/snpe/dsp/libsnpe_dsp_v68_domains_v3_skel.so b/third_party/snpe/dsp/libsnpe_dsp_v68_domains_v3_skel.so deleted file mode 100644 index 9423da1b9..000000000 --- a/third_party/snpe/dsp/libsnpe_dsp_v68_domains_v3_skel.so +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:77d025d59521e13e4ed012f0d9d2b684cdb858208d70426078df51219eaeb9bd -size 10098673 diff --git a/third_party/snpe/include/DiagLog/IDiagLog.hpp b/third_party/snpe/include/DiagLog/IDiagLog.hpp deleted file mode 100644 index 018b56725..000000000 --- a/third_party/snpe/include/DiagLog/IDiagLog.hpp +++ /dev/null @@ -1,84 +0,0 @@ -//============================================================================= -// -// Copyright (c) 2015, 2020 Qualcomm Technologies, Inc. -// All Rights Reserved. -// Confidential and Proprietary - Qualcomm Technologies, Inc. -// -//============================================================================= -#ifndef __IDIAGLOG_HPP_ -#define __IDIAGLOG_HPP_ - -#include - -#include "DiagLog/Options.hpp" -#include "DlSystem/String.hpp" -#include "DlSystem/ZdlExportDefine.hpp" - -namespace zdl -{ -namespace DiagLog -{ - -/** @addtogroup c_plus_plus_apis C++ -@{ */ - -/// @brief . -/// -/// Interface for controlling logging for zdl components. - -class ZDL_EXPORT IDiagLog -{ -public: - - /// @brief . - /// - /// Sets the options after initialization occurs. - /// - /// @param[in] loggingOptions The options to set up diagnostic logging. - /// - /// @return False if the options could not be set. Ensure logging is not started. - virtual bool setOptions(const Options& loggingOptions) = 0; - - /// @brief . - /// - /// Gets the curent options for the diag logger. - /// - /// @return Diag log options object. - virtual Options getOptions() = 0; - - /// @brief . - /// - /// Allows for setting the log mask once diag logging has started - /// - /// @return True if the level was set successfully, false if a failure occurred. - virtual bool setDiagLogMask(const std::string& mask) = 0; - - /// @brief . - /// - /// Allows for setting the log mask once diag logging has started - /// - /// @return True if the level was set successfully, false if a failure occurred. - virtual bool setDiagLogMask(const zdl::DlSystem::String& mask) = 0; - - /// @brief . - /// - /// Enables logging for zdl components. - /// - /// Logging should be started prior to the instantiation of zdl components - /// to ensure all events are captured. - /// - /// @return False if diagnostic logging could not be started. - virtual bool start(void) = 0; - - /// @brief Disables logging for zdl components. - virtual bool stop(void) = 0; - - virtual ~IDiagLog() {}; -}; - -} // DiagLog namespace -} // zdl namespace - -/** @} */ /* end_addtogroup c_plus_plus_apis C++ */ - -#endif diff --git a/third_party/snpe/include/DiagLog/Options.hpp b/third_party/snpe/include/DiagLog/Options.hpp deleted file mode 100644 index 798fa3f12..000000000 --- a/third_party/snpe/include/DiagLog/Options.hpp +++ /dev/null @@ -1,79 +0,0 @@ -//============================================================================= -// -// Copyright (c) 2015, 2020 Qualcomm Technologies, Inc. -// All Rights Reserved. -// Confidential and Proprietary - Qualcomm Technologies, Inc. -// -//============================================================================= -#ifndef __DIAGLOG_OPTIONS_HPP_ -#define __DIAGLOG_OPTIONS_HPP_ - -#include -#include "DlSystem/ZdlExportDefine.hpp" - -namespace zdl -{ -namespace DiagLog -{ -/** @addtogroup c_plus_plus_apis C++ -@{ */ - -/// @brief . -/// -/// Options for setting up diagnostic logging for zdl components. -class ZDL_EXPORT Options -{ -public: - Options() : - DiagLogMask(""), - LogFileDirectory("diaglogs"), - LogFileName("DiagLog"), - LogFileRotateCount(20), - LogFileReplace(true) - { - // Solves the empty string problem with multiple std libs - DiagLogMask.reserve(1); - } - - /// @brief . - /// - /// Enables diag logging only on the specified area mask (DNN_RUNTIME=ON | OFF) - std::string DiagLogMask; - - /// @brief . - /// - /// The path to the directory where log files will be written. - /// The path may be relative or absolute. Relative paths are interpreted - /// from the current working directory. - /// Default value is "diaglogs" - std::string LogFileDirectory; - - /// @brief . - /// - //// The name used for log files. If this value is empty then BaseName will be - /// used as the default file name. - /// Default value is "DiagLog" - std::string LogFileName; - - /// @brief . - /// - /// The maximum number of log files to create. If set to 0 no log rotation - /// will be used and the log file name specified will be used each time, overwriting - /// any existing log file that may exist. - /// Default value is 20 - uint32_t LogFileRotateCount; - - /// @brief - /// - /// If the log file already exists, control whether it will be replaced - /// (existing contents truncated), or appended. - /// Default value is true - bool LogFileReplace; -}; -/** @} */ /* end_addtogroup c_plus_plus_apis C++ */ - -} // DiagLog namespace -} // zdl namespace - - -#endif diff --git a/third_party/snpe/include/DlContainer/IDlContainer.hpp b/third_party/snpe/include/DlContainer/IDlContainer.hpp deleted file mode 100644 index 4e29b39bb..000000000 --- a/third_party/snpe/include/DlContainer/IDlContainer.hpp +++ /dev/null @@ -1,191 +0,0 @@ -//============================================================================= -// -// Copyright (c) 2015-2020 Qualcomm Technologies, Inc. -// All Rights Reserved. -// Confidential and Proprietary - Qualcomm Technologies, Inc. -// -//============================================================================= - -#ifndef ZEROTH_IDNC_CONTAINER_HPP -#define ZEROTH_IDNC_CONTAINER_HPP - -#include -#include -#include -#include -#include - -#include "DlSystem/ZdlExportDefine.hpp" -#include "DlSystem/String.hpp" - -namespace zdl { -namespace DlContainer { - -/** @addtogroup c_plus_plus_apis C++ -@{ */ - -class IDlContainer; -class dlc_error; - -/** - * The structure of a record in a DL container. - */ -struct ZDL_EXPORT DlcRecord -{ - /// Name of the record. - std::string name; - /// Byte blob holding the data for the record. - std::vector data; - - DlcRecord(); - DlcRecord( DlcRecord&& other ) - : name(std::move(other.name)) - , data(std::move(other.data)) - {} - DlcRecord(const std::string& new_name) - : name(new_name) - , data() - { - if(name.empty()) - { - name.reserve(1); - } - } - DlcRecord(const DlcRecord&) = delete; -}; - -// The maximum length of any record name. -extern const uint32_t RECORD_NAME_MAX_SIZE; -// The maximum size of the record payload (bytes). -extern const uint32_t RECORD_DATA_MAX_SIZE; -// The maximum number of records in an archive at one time. -extern const uint32_t ARCHIVE_MAX_RECORDS; - -/** - * Represents a container for a neural network model which can - * be used to load the model into the SNPE runtime. - */ -class ZDL_EXPORT IDlContainer -{ -public: - /** - * Initializes a container from a container archive file. - * - * @param[in] filename Container archive file path. - * - * @return A pointer to the initialized container - */ - static std::unique_ptr - open(const std::string &filename) noexcept; - - /** - * Initializes a container from a container archive file. - * - * @param[in] filename Container archive file path. - * - * @return A pointer to the initialized container - */ - static std::unique_ptr - open(const zdl::DlSystem::String &filename) noexcept; - - /** - * Initializes a container from a byte buffer. - * - * @param[in] buffer Byte buffer holding the contents of an archive - * file. - * - * @return A pointer to the initialized container - */ - static std::unique_ptr - open(const std::vector &buffer) noexcept; - - /** - * Initializes a container from a byte buffer. - * - * @param[in] buffer Byte buffer holding the contents of an archive - * file. - * - * @param[in] size Size of the byte buffer. - * - * @return A pointer to the initialized container - */ - static std::unique_ptr - open(const uint8_t* buffer, const size_t size) noexcept; - - -/** @} */ /* end_addtogroup c_plus_plus_apis C++ */ - - /** - * Get the record catalog for a container. - * - * @param[out] catalog Buffer that will hold the record names on - * return. - */ - virtual void getCatalog(std::set &catalog) const = 0; - - /** - * Get the record catalog for a container. - * - * @param[out] catalog Buffer that will hold the record names on - * return. - */ - virtual void getCatalog(std::set &catalog) const = 0; - - /** - * Get a record from a container by name. - * - * @param[in] name Name of the record to fetch. - * @param[out] record The passed in record will be populated with the - * record data on return. Note that the caller - * will own the data in the record and is - * responsible for freeing it if needed. - */ - virtual void getRecord(const std::string &name, DlcRecord &record) const = 0; - - /** - * Get a record from a container by name. - * - * @param[in] name Name of the record to fetch. - * @param[out] record The passed in record will be populated with the - * record data on return. Note that the caller - * will own the data in the record and is - * responsible for freeing it if needed. - */ - virtual void getRecord(const zdl::DlSystem::String &name, DlcRecord &record) const = 0; - - /** - * Save the container to an archive on disk. This function will save the - * container if the filename is different from the file that it was opened - * from, or if at least one record was modified since the container was - * opened. - * - * It will truncate any existing file at the target path. - * - * @param filename Container archive file path. - * - * @return indication of success/failure - */ - virtual bool save(const std::string &filename) = 0; - - /** - * Save the container to an archive on disk. This function will save the - * container if the filename is different from the file that it was opened - * from, or if at least one record was modified since the container was - * opened. - * - * It will truncate any existing file at the target path. - * - * @param filename Container archive file path. - * - * @return indication of success/failure - */ - virtual bool save (const zdl::DlSystem::String &filename) = 0; - - virtual ~IDlContainer() {} -}; - -} // ns DlContainer -} // ns zdl - - -#endif diff --git a/third_party/snpe/include/DlSystem/DlEnums.hpp b/third_party/snpe/include/DlSystem/DlEnums.hpp deleted file mode 100644 index c9b3ef970..000000000 --- a/third_party/snpe/include/DlSystem/DlEnums.hpp +++ /dev/null @@ -1,234 +0,0 @@ -//============================================================================== -// -// Copyright (c) 2014-2021 Qualcomm Technologies, Inc. -// All Rights Reserved. -// Confidential and Proprietary - Qualcomm Technologies, Inc. -// -//============================================================================== - -#ifndef _DL_ENUMS_HPP_ -#define _DL_ENUMS_HPP_ - -#include "DlSystem/ZdlExportDefine.hpp" - - -namespace zdl { -namespace DlSystem -{ -/** @addtogroup c_plus_plus_apis C++ -@{ */ - -/** - * Enumeration of supported target runtimes. - */ -enum class Runtime_t -{ - /// Run the processing on Snapdragon CPU. - /// Data: float 32bit - /// Math: float 32bit - CPU_FLOAT32 = 0, - - /// Run the processing on the Adreno GPU. - /// Data: float 16bit - /// Math: float 32bit - GPU_FLOAT32_16_HYBRID = 1, - - /// Run the processing on the Hexagon DSP. - /// Data: 8bit fixed point Tensorflow style format - /// Math: 8bit fixed point Tensorflow style format - DSP_FIXED8_TF = 2, - - /// Run the processing on the Adreno GPU. - /// Data: float 16bit - /// Math: float 16bit - GPU_FLOAT16 = 3, - - /// Run the processing on Snapdragon AIX+HVX. - /// Data: 8bit fixed point Tensorflow style format - /// Math: 8bit fixed point Tensorflow style format - AIP_FIXED8_TF = 5, - AIP_FIXED_TF = AIP_FIXED8_TF, - - /// Default legacy enum to retain backward compatibility. - /// CPU = CPU_FLOAT32 - CPU = CPU_FLOAT32, - - /// Default legacy enum to retain backward compatibility. - /// GPU = GPU_FLOAT32_16_HYBRID - GPU = GPU_FLOAT32_16_HYBRID, - - /// Default legacy enum to retain backward compatibility. - /// DSP = DSP_FIXED8_TF - DSP = DSP_FIXED8_TF, - - /// Special value indicating the property is unset. - UNSET = -1 -}; - -/** - * Enumeration of runtime available check options. - */ -enum class RuntimeCheckOption_t -{ - /// Perform standard runtime available check - DEFAULT = 0, - /// Perform standard runtime available check - NORMAL_CHECK = 0, - /// Perform basic runtime available check, may be runtime specific - BASIC_CHECK = 1, - /// Perform unsignedPD runtime available check - UNSIGNEDPD_CHECK = 2, -}; - -/** - * Enumeration of various performance profiles that can be requested. - */ -enum class PerformanceProfile_t -{ - /// Run in a standard mode. - /// This mode will be deprecated in the future and replaced with BALANCED. - DEFAULT = 0, - /// Run in a balanced mode. - BALANCED = 0, - - /// Run in high performance mode - HIGH_PERFORMANCE = 1, - - /// Run in a power sensitive mode, at the expense of performance. - POWER_SAVER = 2, - - /// Use system settings. SNPE makes no calls to any performance related APIs. - SYSTEM_SETTINGS = 3, - - /// Run in sustained high performance mode - SUSTAINED_HIGH_PERFORMANCE = 4, - - /// Run in burst mode - BURST = 5, - - /// Run in lower clock than POWER_SAVER, at the expense of performance. - LOW_POWER_SAVER = 6, - - /// Run in higher clock and provides better performance than POWER_SAVER. - HIGH_POWER_SAVER = 7, - - /// Run in lower balanced mode - LOW_BALANCED = 8, -}; - -/** - * Enumeration of various profilngLevels that can be requested. - */ -enum class ProfilingLevel_t -{ - /// No profiling. - /// Collects no runtime stats in the DiagLog - OFF = 0, - - /// Basic profiling - /// Collects some runtime stats in the DiagLog - BASIC = 1, - - /// Detailed profiling - /// Collects more runtime stats in the DiagLog, including per-layer statistics - /// Performance may be impacted - DETAILED = 2, - - /// Moderate profiling - /// Collects more runtime stats in the DiagLog, no per-layer statistics - MODERATE = 3 -}; - -/** - * Enumeration of various execution priority hints. - */ -enum class ExecutionPriorityHint_t -{ - /// Normal priority - NORMAL = 0, - - /// Higher than normal priority - HIGH = 1, - - /// Lower priority - LOW = 2 - -}; - -/** @} */ /* end_addtogroup c_plus_plus_apis C++*/ - -/** - * Enumeration that lists the supported image encoding formats. - */ -enum class ImageEncoding_t -{ - /// For unknown image type. Also used as a default value for ImageEncoding_t. - UNKNOWN = 0, - - /// The RGB format consists of 3 bytes per pixel: one byte for - /// Red, one for Green, and one for Blue. The byte ordering is - /// endian independent and is always in RGB byte order. - RGB = 1, - - /// The ARGB32 format consists of 4 bytes per pixel: one byte for - /// Red, one for Green, one for Blue, and one for the alpha channel. - /// The alpha channel is ignored. The byte ordering depends on the - /// underlying CPU. For little endian CPUs, the byte order is BGRA. - /// For big endian CPUs, the byte order is ARGB. - ARGB32 = 2, - - /// The RGBA format consists of 4 bytes per pixel: one byte for - /// Red, one for Green, one for Blue, and one for the alpha channel. - /// The alpha channel is ignored. The byte ordering is endian independent - /// and is always in RGBA byte order. - RGBA = 3, - - /// The GRAYSCALE format is for 8-bit grayscale. - GRAYSCALE = 4, - - /// NV21 is the Android version of YUV. The Chrominance is down - /// sampled and has a subsampling ratio of 4:2:0. Note that this - /// image format has 3 channels, but the U and V channels - /// are subsampled. For every four Y pixels there is one U and one V pixel. @newpage - NV21 = 5, - - /// The BGR format consists of 3 bytes per pixel: one byte for - /// Red, one for Green and one for Blue. The byte ordering is - /// endian independent and is always BGR byte order. - BGR = 6 -}; - -/** - * Enumeration that lists the supported LogLevels that can be set by users. - */ -enum class LogLevel_t -{ - /// Enumeration variable to be used by user to set logging level to FATAL. - LOG_FATAL = 0, - - /// Enumeration variable to be used by user to set logging level to ERROR. - LOG_ERROR = 1, - - /// Enumeration variable to be used by user to set logging level to WARN. - LOG_WARN = 2, - - /// Enumeration variable to be used by user to set logging level to INFO. - LOG_INFO = 3, - - /// Enumeration variable to be used by user to set logging level to VERBOSE. - LOG_VERBOSE = 4 -}; - -typedef enum : int -{ - UNSPECIFIED = 0, - FLOATING_POINT_32 = 1, - FLOATING_POINT_16 = 2, - FIXED_POINT_8 = 3, - FIXED_POINT_16 = 4 -} IOBufferDataType_t; - -}} // namespaces end - - -#endif diff --git a/third_party/snpe/include/DlSystem/DlError.hpp b/third_party/snpe/include/DlSystem/DlError.hpp deleted file mode 100644 index 57592f01b..000000000 --- a/third_party/snpe/include/DlSystem/DlError.hpp +++ /dev/null @@ -1,259 +0,0 @@ -//============================================================================== -// -// Copyright (c) 2016-2021 Qualcomm Technologies, Inc. -// All Rights Reserved. -// Confidential and Proprietary - Qualcomm Technologies, Inc. -// -//============================================================================== - -#ifndef _DL_ERROR_HPP_ -#define _DL_ERROR_HPP_ - -#include -#include // numeric_limits - -#include "DlSystem/ZdlExportDefine.hpp" - -namespace zdl { -namespace DlSystem { - -// clang and arm gcc different in how ZDL_EXPORT is used with enum class -#if !defined (__clang__) -enum class ErrorCode : uint32_t ZDL_EXPORT { -#else -enum class ZDL_EXPORT ErrorCode : uint32_t { -#endif // ARM64V8A - NONE = 0, - - // System config errors - SNPE_CONFIG_MISSING_PARAM = 100, - SNPE_CONFIG_INVALID_PARAM = 101, - SNPE_CONFIG_MISSING_FILE = 102, - SNPE_CONFIG_NNCONFIG_NOT_SET = 103, - SNPE_CONFIG_NNCONFIG_INVALID = 104, - SNPE_CONFIG_WRONG_INPUT_NAME = 105, - SNPE_CONFIG_INCORRECT_INPUT_DIMENSIONS = 106, - SNPE_CONFIG_DIMENSIONS_MODIFICATION_NOT_SUPPORTED = 107, - SNPE_CONFIG_BOTH_OUTPUT_LAYER_TENSOR_NAMES_SET = 108, - - SNPE_CONFIG_NNCONFIG_ONLY_TENSOR_SUPPORTED = 120, - SNPE_CONFIG_NNCONFIG_ONLY_USER_BUFFER_SUPPORTED = 121, - - // DlSystem errors - SNPE_DLSYSTEM_MISSING_BUFFER = 200, - SNPE_DLSYSTEM_TENSOR_CAST_FAILED = 201, - SNPE_DLSYSTEM_FIXED_POINT_PARAM_INVALID = 202, - SNPE_DLSYSTEM_SIZE_MISMATCH = 203, - SNPE_DLSYSTEM_NAME_NOT_FOUND = 204, - SNPE_DLSYSTEM_VALUE_MISMATCH = 205, - SNPE_DLSYSTEM_INSERT_FAILED = 206, - SNPE_DLSYSTEM_TENSOR_FILE_READ_FAILED = 207, - SNPE_DLSYSTEM_DIAGLOG_FAILURE = 208, - SNPE_DLSYSTEM_LAYER_NOT_SET = 209, - SNPE_DLSYSTEM_WRONG_NUMBER_INPUT_BUFFERS = 210, - SNPE_DLSYSTEM_RUNTIME_TENSOR_SHAPE_MISMATCH = 211, - SNPE_DLSYSTEM_TENSOR_MISSING = 212, - SNPE_DLSYSTEM_TENSOR_ITERATION_UNSUPPORTED = 213, - SNPE_DLSYSTEM_BUFFER_MANAGER_MISSING = 214, - SNPE_DLSYSTEM_RUNTIME_BUFFER_SOURCE_UNSUPPORTED = 215, - SNPE_DLSYSTEM_BUFFER_CAST_FAILED = 216, - SNPE_DLSYSTEM_WRONG_TRANSITION_TYPE = 217, - SNPE_DLSYSTEM_LAYER_ALREADY_REGISTERED = 218, - SNPE_DLSYSTEM_TENSOR_DIM_INVALID = 219, - - SNPE_DLSYSTEM_BUFFERENCODING_UNKNOWN = 240, - SNPE_DLSYSTEM_BUFFER_INVALID_PARAM = 241, - - // DlContainer errors - SNPE_DLCONTAINER_MODEL_PARSING_FAILED = 300, - SNPE_DLCONTAINER_UNKNOWN_LAYER_CODE = 301, - SNPE_DLCONTAINER_MISSING_LAYER_PARAM = 302, - SNPE_DLCONTAINER_LAYER_PARAM_NOT_SUPPORTED = 303, - SNPE_DLCONTAINER_LAYER_PARAM_INVALID = 304, - SNPE_DLCONTAINER_TENSOR_DATA_MISSING = 305, - SNPE_DLCONTAINER_MODEL_LOAD_FAILED = 306, - SNPE_DLCONTAINER_MISSING_RECORDS = 307, - SNPE_DLCONTAINER_INVALID_RECORD = 308, - SNPE_DLCONTAINER_WRITE_FAILURE = 309, - SNPE_DLCONTAINER_READ_FAILURE = 310, - SNPE_DLCONTAINER_BAD_CONTAINER = 311, - SNPE_DLCONTAINER_BAD_DNN_FORMAT_VERSION = 312, - SNPE_DLCONTAINER_UNKNOWN_AXIS_ANNOTATION = 313, - SNPE_DLCONTAINER_UNKNOWN_SHUFFLE_TYPE = 314, - SNPE_DLCONTAINER_TEMP_FILE_FAILURE = 315, - - // Network errors - SNPE_NETWORK_EMPTY_NETWORK = 400, - SNPE_NETWORK_CREATION_FAILED = 401, - SNPE_NETWORK_PARTITION_FAILED = 402, - SNPE_NETWORK_NO_OUTPUT_DEFINED = 403, - SNPE_NETWORK_MISMATCH_BETWEEN_NAMES_AND_DIMS = 404, - SNPE_NETWORK_MISSING_INPUT_NAMES = 405, - SNPE_NETWORK_MISSING_OUTPUT_NAMES = 406, - SNPE_NETWORK_EXECUTION_FAILED = 407, - - // Host runtime errors - SNPE_HOST_RUNTIME_TARGET_UNAVAILABLE = 500, - - // CPU runtime errors - SNPE_CPU_LAYER_NOT_SUPPORTED = 600, - SNPE_CPU_LAYER_PARAM_NOT_SUPPORTED = 601, - SNPE_CPU_LAYER_PARAM_INVALID = 602, - SNPE_CPU_LAYER_PARAM_COMBINATION_INVALID = 603, - SNPE_CPU_BUFFER_NOT_FOUND = 604, - SNPE_CPU_NETWORK_NOT_SUPPORTED = 605, - SNPE_CPU_UDO_OPERATION_FAILED = 606, - - // CPU fixed-point runtime errors - SNPE_CPU_FXP_LAYER_NOT_SUPPORTED = 700, - SNPE_CPU_FXP_LAYER_PARAM_NOT_SUPPORTED = 701, - SNPE_CPU_FXP_LAYER_PARAM_INVALID = 702, - - // GPU runtime errors - SNPE_GPU_LAYER_NOT_SUPPORTED = 800, - SNPE_GPU_LAYER_PARAM_NOT_SUPPORTED = 801, - SNPE_GPU_LAYER_PARAM_INVALID = 802, - SNPE_GPU_LAYER_PARAM_COMBINATION_INVALID = 803, - SNPE_GPU_KERNEL_COMPILATION_FAILED = 804, - SNPE_GPU_CONTEXT_NOT_SET = 805, - SNPE_GPU_KERNEL_NOT_SET = 806, - SNPE_GPU_KERNEL_PARAM_INVALID = 807, - SNPE_GPU_OPENCL_CHECK_FAILED = 808, - SNPE_GPU_OPENCL_FUNCTION_ERROR = 809, - SNPE_GPU_BUFFER_NOT_FOUND = 810, - SNPE_GPU_TENSOR_DIM_INVALID = 811, - SNPE_GPU_MEMORY_FLAGS_INVALID = 812, - SNPE_GPU_UNEXPECTED_NUMBER_OF_IO = 813, - SNPE_GPU_LAYER_PROXY_ERROR = 814, - SNPE_GPU_BUFFER_IN_USE = 815, - SNPE_GPU_BUFFER_MODIFICATION_ERROR = 816, - SNPE_GPU_DATA_ARRANGEMENT_INVALID = 817, - SNPE_GPU_UDO_OPERATION_FAILED = 818, - // DSP runtime errors - SNPE_DSP_LAYER_NOT_SUPPORTED = 900, - SNPE_DSP_LAYER_PARAM_NOT_SUPPORTED = 901, - SNPE_DSP_LAYER_PARAM_INVALID = 902, - SNPE_DSP_LAYER_PARAM_COMBINATION_INVALID = 903, - SNPE_DSP_STUB_NOT_PRESENT = 904, - SNPE_DSP_LAYER_NAME_TRUNCATED = 905, - SNPE_DSP_LAYER_INPUT_BUFFER_NAME_TRUNCATED = 906, - SNPE_DSP_LAYER_OUTPUT_BUFFER_NAME_TRUNCATED = 907, - SNPE_DSP_RUNTIME_COMMUNICATION_ERROR = 908, - SNPE_DSP_RUNTIME_INVALID_PARAM_ERROR = 909, - SNPE_DSP_RUNTIME_SYSTEM_ERROR = 910, - SNPE_DSP_RUNTIME_CRASHED_ERROR = 911, - SNPE_DSP_BUFFER_SIZE_ERROR = 912, - SNPE_DSP_UDO_EXECUTE_ERROR = 913, - SNPE_DSP_UDO_LIB_NOT_REGISTERED_ERROR = 914, - SNPE_DSP_UDO_INVALID_QUANTIZATION_TYPE_ERROR = 915, - SNPE_DSP_RUNTIME_INVALID_RPC_DRIVER = 916, - SNPE_DSP_RUNTIME_RPC_PERMISSION_ERROR = 917, - SNPE_DSP_RUNTIME_DSP_FILE_OPEN_ERROR = 918, - - // Model validataion errors - SNPE_MODEL_VALIDATION_LAYER_NOT_SUPPORTED = 1000, - SNPE_MODEL_VALIDATION_LAYER_PARAM_NOT_SUPPORTED = 1001, - SNPE_MODEL_VALIDATION_LAYER_PARAM_INVALID = 1002, - SNPE_MODEL_VALIDATION_LAYER_PARAM_MISSING = 1003, - SNPE_MODEL_VALIDATION_LAYER_PARAM_COMBINATION_INVALID = 1004, - SNPE_MODEL_VALIDATION_LAYER_ORDERING_INVALID = 1005, - SNPE_MODEL_VALIDATION_INVALID_CONSTRAINT = 1006, - SNPE_MODEL_VALIDATION_MISSING_BUFFER = 1007, - SNPE_MODEL_VALIDATION_BUFFER_REUSE_NOT_SUPPORTED = 1008, - SNPE_MODEL_VALIDATION_LAYER_COULD_NOT_BE_ASSIGNED = 1009, - SNPE_MODEL_VALIDATION_UDO_LAYER_FAILED = 1010, - - // UDL errors - SNPE_UDL_LAYER_EMPTY_UDL_NETWORK = 1100, - SNPE_UDL_LAYER_PARAM_INVALID = 1101, - SNPE_UDL_LAYER_INSTANCE_MISSING = 1102, - SNPE_UDL_LAYER_SETUP_FAILED = 1103, - SNPE_UDL_EXECUTE_FAILED = 1104, - SNPE_UDL_BUNDLE_INVALID = 1105, - SNPE_UDO_REGISTRATION_FAILED = 1106, - SNPE_UDO_GET_PACKAGE_FAILED = 1107, - SNPE_UDO_GET_IMPLEMENTATION_FAILED = 1108, - - // Dependent library errors - SNPE_STD_LIBRARY_ERROR = 1200, - - // Unknown exception (catch (...)), Has no component attached to this - SNPE_UNKNOWN_EXCEPTION = 1210, - - // Storage Errors - SNPE_STORAGE_INVALID_KERNEL_REPO = 1300, - - // AIP runtime errors - SNPE_AIP_LAYER_NOT_SUPPORTED = 1400, - SNPE_AIP_LAYER_PARAM_NOT_SUPPORTED = 1401, - SNPE_AIP_LAYER_PARAM_INVALID = 1402, - SNPE_AIP_LAYER_PARAM_COMBINATION_INVALID = 1403, - SNPE_AIP_STUB_NOT_PRESENT = 1404, - SNPE_AIP_LAYER_NAME_TRUNCATED = 1405, - SNPE_AIP_LAYER_INPUT_BUFFER_NAME_TRUNCATED = 1406, - SNPE_AIP_LAYER_OUTPUT_BUFFER_NAME_TRUNCATED = 1407, - SNPE_AIP_RUNTIME_COMMUNICATION_ERROR = 1408, - SNPE_AIP_RUNTIME_INVALID_PARAM_ERROR = 1409, - SNPE_AIP_RUNTIME_SYSTEM_ERROR = 1410, - SNPE_AIP_RUNTIME_TENSOR_MISSING = 1411, - SNPE_AIP_RUNTIME_TENSOR_SHAPE_MISMATCH = 1412, - SNPE_AIP_RUNTIME_BAD_AIX_RECORD = 1413, - - // DlCaching errors - SNPE_DLCACHING_INVALID_METADATA = 1500, - SNPE_DLCACHING_INVALID_INITBLOB = 1501, - - // Infrastructure Errors - SNPE_INFRA_CLUSTERMGR_INSTANCE_INVALID = 1600, - SNPE_INFRA_CLUSTERMGR_EXECUTE_SYNC_FAILED = 1601, - - // Memory Errors - SNPE_MEMORY_CORRUPTION_ERROR = 1700 - -}; - - -/** @addtogroup c_plus_plus_apis C++ -@{ */ - -/** - * Returns the error code of the last error encountered. - * - * @return The error code. - * - * @note The returned error code is significant only when the return - * value of the call indicated an error. - */ -ZDL_EXPORT ErrorCode getLastErrorCode(); - -/** - * Returns the error string of the last error encountered. - * - * @return The error string. - * - * @note The returned error string is significant only when the return - * value of the call indicated an error. - */ -ZDL_EXPORT const char* getLastErrorString(); - -/** - * Returns the info string of the last error encountered. - */ -ZDL_EXPORT const char* getLastInfoString(); - -/** - * Returns the uint32_t representation of the error code enum. - * - * @param[in] code The error code to be converted. - * - * @return uint32_t representation of the error code. - */ -ZDL_EXPORT uint32_t enumToUInt32(zdl::DlSystem::ErrorCode code); - -/** @} */ /* end_addtogroup c_plus_plus_apis C++ */ - -} // DlSystem -} // zdl - -#endif // _DL_ERROR_HPP_ - diff --git a/third_party/snpe/include/DlSystem/DlOptional.hpp b/third_party/snpe/include/DlSystem/DlOptional.hpp deleted file mode 100644 index 4f83e6b4e..000000000 --- a/third_party/snpe/include/DlSystem/DlOptional.hpp +++ /dev/null @@ -1,225 +0,0 @@ -//============================================================================== -// -// Copyright (c) 2016, 2020 Qualcomm Technologies, Inc. -// All Rights Reserved. -// Confidential and Proprietary - Qualcomm Technologies, Inc. -// -//============================================================================== - -#ifndef _DL_SYSTEM_OPTIONAL_HPP_ -#define _DL_SYSTEM_OPTIONAL_HPP_ - -#include -#include -#include - -#include "DlSystem/ZdlExportDefine.hpp" - -namespace zdl { -namespace DlSystem { - -template - -/** @addtogroup c_plus_plus_apis C++ -@{ */ - -/** - * @brief . - * - * Class to manage a value that may or may not exist. The boolean value - * of the Optional class is true if the object contains a value and false - * if it does not contain a value. - * - * The class must be evaluated and confirmed as true (containing a value) - * before being dereferenced. - */ -class ZDL_EXPORT Optional final { -public: - enum class LIFECYCLE { - NONE = 0, - REFERENCE_OWNED = 1, - POINTER_OWNED = 2, - POINTER_NOT_OWNED = 3 - }; - - struct ReferenceCount { - size_t count = 0; - - void increment() { count++; } - - size_t decrement() { - if (count > 0) { - count--; - } - return count; - } - }; - - using U = typename std::remove_pointer::type; - - /** - * The default constructor is set to not have any value, and is - * therefore evaluated as false. - */ - // Do not explicit it so we can return {} - Optional() { - m_Type = LIFECYCLE::NONE; - } - - /** - * Construct an Optional class using an object. - * @param[in] Reference to an object v - * @param[out] Optional instance of object v - */ - template - Optional (const T& v, typename std::enable_if::value>::type* = 0) - : m_Type(LIFECYCLE::REFERENCE_OWNED) { - try { - m_StoragePtr = new T(v); - } catch (...) { - m_StoragePtr = nullptr; - m_Type = LIFECYCLE::NONE; - } - } - - template - Optional(U* v, LIFECYCLE type, typename std::enable_if::value>::type* = 0) - : m_Type(type) { - switch (m_Type) { - case LIFECYCLE::POINTER_OWNED: - m_StoragePtr = v; - m_Count = new ReferenceCount(); - m_Count->increment(); - break; - case LIFECYCLE::POINTER_NOT_OWNED: - m_StoragePtr = v; - break; - case LIFECYCLE::REFERENCE_OWNED: - throw std::bad_exception(); - case LIFECYCLE::NONE: - break; - } - } - - Optional(const Optional &other) : m_Type(other.m_Type), m_Count(other.m_Count) { - if (isReference()) { - m_StoragePtr = new U(*other.m_StoragePtr); - } else if (isPointer()) { - m_StoragePtr = other.m_StoragePtr; - if (isOwned()) { - m_Count->increment(); - } - } - } - - Optional& operator=(const Optional& other) noexcept { - Optional tmp(other); - swap(std::move(tmp)); - return *this; - } - - Optional(Optional&& other) noexcept { - swap(std::move(other)); - } - - Optional& operator=(Optional&& other) noexcept { - swap(std::move(other)); - return *this; - } - - ~Optional() { - if (isOwned()) { - if (isReference() || (isPointer() && m_Count->decrement() == 0)) { - delete m_StoragePtr; - delete m_Count; - } - } - } - - /** - * Boolean value of Optional class is only true when there exists a value. - */ - operator bool() const noexcept { return isValid(); } - - bool operator!() const noexcept { return !isValid(); } - - /** - * Get reference of Optional object - * @warning User must validate Optional has value before. - */ - const T& operator*() { return this->GetReference(); } - - /** - * Get reference of Optional object - * @warning User must validate Optional has value before. - */ - const T& operator*() const { return this->GetReference(); } - - operator T&() { return this->GetReference(); } - - T operator->() { - T self = this->GetReference(); - return self; - } -private: - void swap(Optional&& other) { - m_Type = other.m_Type; - m_StoragePtr = other.m_StoragePtr; - m_Count = other.m_Count; - - other.m_Type = LIFECYCLE::NONE; - other.m_StoragePtr = nullptr; - other.m_Count = nullptr; - } - - template - typename std::enable_if::value, const Q&>::type GetReference() const noexcept { - if (!isReference()) std::terminate(); - return *static_cast(m_StoragePtr); - } - - template - typename std::enable_if::value, const Q&>::type GetReference() const noexcept { - if (!isPointer()) std::terminate(); - return static_cast(m_StoragePtr); - } - - template - typename std::enable_if::value, Q&>::type GetReference() noexcept { - if (!isReference()) std::terminate(); - return *m_StoragePtr; - } - - template - typename std::enable_if::value, Q&>::type GetReference() noexcept { - if (!isPointer()) std::terminate(); - return m_StoragePtr; - } - - bool isPointer() const { - return m_Type == LIFECYCLE::POINTER_OWNED || m_Type == LIFECYCLE::POINTER_NOT_OWNED; - } - - bool isOwned() const { - return m_Type == LIFECYCLE::REFERENCE_OWNED || m_Type == LIFECYCLE::POINTER_OWNED; - } - - bool isReference() const { - return m_Type == LIFECYCLE::REFERENCE_OWNED; - } - - bool isValid() const { - return m_Type != LIFECYCLE::NONE; - } - - U* m_StoragePtr = nullptr; - LIFECYCLE m_Type; - ReferenceCount *m_Count = nullptr; -}; - -} // ns DlSystem -} // ns zdl - -/** @} */ /* end_addtogroup c_plus_plus_apis C++ */ - -#endif // _DL_SYSTEM_OPTIONAL_HPP_ diff --git a/third_party/snpe/include/DlSystem/DlVersion.hpp b/third_party/snpe/include/DlSystem/DlVersion.hpp deleted file mode 100644 index beb8d75f2..000000000 --- a/third_party/snpe/include/DlSystem/DlVersion.hpp +++ /dev/null @@ -1,78 +0,0 @@ -//============================================================================== -// -// Copyright (c) 2014-2015 Qualcomm Technologies, Inc. -// All Rights Reserved. -// Confidential and Proprietary - Qualcomm Technologies, Inc. -// -//============================================================================== - - -#ifndef _DL_VERSION_HPP_ -#define _DL_VERSION_HPP_ - -#include "ZdlExportDefine.hpp" -#include -#include -#include "DlSystem/String.hpp" - - -namespace zdl { -namespace DlSystem -{ - class Version_t; -}} - - -namespace zdl { namespace DlSystem -{ -/** @addtogroup c_plus_plus_apis C++ -@{ */ - -/** - * A class that contains the different portions of a version number. - */ -class ZDL_EXPORT Version_t -{ -public: - /// Holds the major version number. Changes in this value indicate - /// major changes that break backward compatibility. - int32_t Major; - - /// Holds the minor version number. Changes in this value indicate - /// minor changes made to library that are backwards compatible - /// (such as additions to the interface). - int32_t Minor; - - /// Holds the teeny version number. Changes in this value indicate - /// changes such as bug fixes and patches made to the library that - /// do not affect the interface. - int32_t Teeny; - - /// This string holds information about the build version. - /// - std::string Build; - - static zdl::DlSystem::Version_t fromString(const std::string &stringValue); - - static zdl::DlSystem::Version_t fromString(const zdl::DlSystem::String &stringValue); - - /** - * @brief Returns a string in the form Major.Minor.Teeny.Build - * - * @return A formatted string holding the version information. - */ - const std::string toString() const; - - /** - * @brief Returns a string in the form Major.Minor.Teeny.Build - * - * @return A formatted string holding the version information. - */ - const zdl::DlSystem::String asString() const; -}; - -}} - -/** @} */ /* end_addtogroup c_plus_plus_apis */ - -#endif diff --git a/third_party/snpe/include/DlSystem/IBufferAttributes.hpp b/third_party/snpe/include/DlSystem/IBufferAttributes.hpp deleted file mode 100644 index f893e0a23..000000000 --- a/third_party/snpe/include/DlSystem/IBufferAttributes.hpp +++ /dev/null @@ -1,86 +0,0 @@ -//============================================================================== -// -// Copyright (c) 2017-2019 Qualcomm Technologies, Inc. -// All Rights Reserved. -// Confidential and Proprietary - Qualcomm Technologies, Inc. -// -//============================================================================== - -#ifndef _IBUFFER_ATTRIBUTES_HPP -#define _IBUFFER_ATTRIBUTES_HPP -#include "IUserBuffer.hpp" -#include "TensorShape.hpp" -#include "ZdlExportDefine.hpp" - -namespace zdl { - namespace DlSystem { - class UserBufferEncoding; - } -} - -namespace zdl { -namespace DlSystem { - -/** - * @brief IBufferAttributes returns a buffer's dimension and alignment - * requirements, along with info on its encoding type - */ -class ZDL_EXPORT IBufferAttributes { -public: - - /** - * @brief Gets the buffer's element size, in bytes - * - * This can be used to compute the memory size required - * to back this buffer. - * - * @return Element size, in bytes - */ - virtual size_t getElementSize() const noexcept = 0; - - /** - * @brief Gets the element's encoding type - * - * @return encoding type - */ - virtual zdl::DlSystem::UserBufferEncoding::ElementType_t getEncodingType() const noexcept = 0; - - /** - * @brief Gets the number of elements in each dimension - * - * @return Dimension size, in terms of number of elements - */ - virtual const TensorShape getDims() const noexcept = 0; - - /** - * @brief Gets the alignment requirement of each dimension - * - * Alignment per each dimension is expressed as an multiple, for - * example, if one particular dimension can accept multiples of 8, - * the alignment will be 8. - * - * @return Alignment in each dimension, in terms of multiple of - * number of elements - */ - virtual const TensorShape getAlignments() const noexcept = 0; - - /** - * @brief Gets the buffer encoding returned from the network responsible - * for generating this buffer. Depending on the encoding type, this will - * be an instance of an encoding type specific derived class. - * - * @return Derived user buffer encoding object. - */ - virtual zdl::DlSystem::UserBufferEncoding* getEncoding() const noexcept = 0; - - virtual ~IBufferAttributes() {} -}; - - - -/** @} */ /* end_addtogroup c_plus_plus_apis C++ */ - -} -} - -#endif diff --git a/third_party/snpe/include/DlSystem/IOBufferDataTypeMap.hpp b/third_party/snpe/include/DlSystem/IOBufferDataTypeMap.hpp deleted file mode 100644 index ac33c84af..000000000 --- a/third_party/snpe/include/DlSystem/IOBufferDataTypeMap.hpp +++ /dev/null @@ -1,127 +0,0 @@ -//============================================================================= -// -// Copyright (c) 2021-2022 Qualcomm Technologies, Inc. -// All Rights Reserved. -// Confidential and Proprietary - Qualcomm Technologies, Inc. -// -//============================================================================= - - -#ifndef DL_SYSTEM_IOBUFFER_DATATYPE_MAP_HPP -#define DL_SYSTEM_IOBUFFER_DATATYPE_MAP_HPP - -#include -#include -#include "DlSystem/DlEnums.hpp" - -namespace DlSystem -{ - // Forward declaration of IOBufferDataTypeMapImpl implementation. - class IOBufferDataTypeMapImpl; -} - -namespace zdl -{ -namespace DlSystem -{ -/** @addtogroup c_plus_plus_apis C++ -@{ */ - -/** - * @brief . - * - * The IoBufferDataTypeMap class definition - */ -class ZDL_EXPORT IOBufferDataTypeMap final -{ -public: - - /** - * @brief . - * - * Creates a new Buffer Data type map - * - */ - IOBufferDataTypeMap(); - - /** - * @brief Adds a name and the corresponding buffer data type - * to the map - * - * @param[name] name The name of the buffer - * @param[bufferDataType] buffer Data Type of the buffer - * - * @note If a buffer with the same name already exists, no new - * buffer is added. - */ - void add(const char* name, zdl::DlSystem::IOBufferDataType_t bufferDataType); - - /** - * @brief Removes a buffer name from the map - * - * @param[name] name The name of the buffer - * - */ - void remove(const char* name); - - /** - * @brief Returns the type of the named buffer - * - * @param[name] name The name of the buffer - * - * @return The type of the buffer, or UNSPECIFIED if the buffer does not exist - * - */ - zdl::DlSystem::IOBufferDataType_t getBufferDataType(const char* name); - - /** - * @brief Returns the type of the first buffer - * - * @return The type of the first buffer, or UNSPECIFIED if the map is empty. - * - */ - zdl::DlSystem::IOBufferDataType_t getBufferDataType(); - - /** - * @brief Returns the size of the buffer type map. - * - * @return The size of the map - * - */ - size_t size(); - - /** - * @brief Checks the existence of the named buffer in the map - * - * @return True if the named buffer exists, false otherwise. - * - */ - bool find(const char* name); - - /** - * @brief Resets the map - * - */ - void clear(); - - /** - * @brief Checks whether the map is empty - * - * @return True if the map is empty, false otherwise. - * - */ - bool empty(); - - /** - * @brief Destroys the map - * - */ - ~IOBufferDataTypeMap(); - -private: - std::shared_ptr<::DlSystem::IOBufferDataTypeMapImpl> m_IOBufferDataTypeMapImpl; -}; -} - -} -#endif diff --git a/third_party/snpe/include/DlSystem/ITensor.hpp b/third_party/snpe/include/DlSystem/ITensor.hpp deleted file mode 100644 index 2f006c857..000000000 --- a/third_party/snpe/include/DlSystem/ITensor.hpp +++ /dev/null @@ -1,146 +0,0 @@ -//============================================================================= -// -// Copyright (c) 2015-2020 Qualcomm Technologies, Inc. -// All Rights Reserved. -// Confidential and Proprietary - Qualcomm Technologies, Inc. -// -//============================================================================= - -#ifndef _ITENSOR_HPP_ -#define _ITENSOR_HPP_ - -#include "ITensorItr.hpp" -#include "ITensorItrImpl.hpp" -#include "TensorShape.hpp" -#include "ZdlExportDefine.hpp" -#include -#include -#include - -namespace zdl { -namespace DlSystem -{ - class ITensor; -}} - -namespace zdl { namespace DlSystem -{ -/** @addtogroup c_plus_plus_apis C++ -@{ */ - -/** - * Represents a tensor which holds n-dimensional data. It is important to - * understand how the tensor data is represented in memory - * relative to the tensor dimensions. Tensors store data in - * memory in row-major order (i.e. the last tensor dimension is - * the fastest varying one). For example, if you have a two - * dimensional tensor with 3 rows and 2 columns (i.e. the tensor - * dimensions are 3,2 as returned in tensor dimension vectors) - * with the following data in terms rows and columns: - * - * | 1 2 |
- * | 3 4 |
- * | 5 6 |
- * - * This data would be stored in memory as 1,2,3,4,5,6. - */ -class ZDL_EXPORT ITensor -{ -public: - - typedef zdl::DlSystem::ITensorItr iterator; - typedef zdl::DlSystem::ITensorItr const_iterator; - - virtual ~ITensor() {} - - /** - * Returns a tensor iterator pointing to the beginning - * of the data in the tensor. - * - * @return A tensor iterator that points to the first data - * element in the tensor. - */ - virtual iterator begin() = 0; - - /** - * Returns the const version of a tensor iterator - * pointing to the beginning of the data in the tensor. - * - * @return A tensor const iterator that points to the first data - * element in the tensor. - */ - virtual const_iterator cbegin() const = 0; - - /** - * Returns a tensor iterator pointing to the end of the - * data in the tensor. This tensor should not be - * dereferenced. - * - * @return A tensor iterator that points to the end of the data - * (one past the last element) in the tensor. - */ - virtual iterator end() = 0; - - /** - * Returns the const version of a tensor iterator - * pointing to the end of the data in the tensor. This - * tensor should not be dereferenced. - * - * @return A tensor const iterator that points to the end of the - * data (one past the last element) in the tensor. - */ - virtual const_iterator cend() const = 0; - - /** - * @brief Gets the shape of this tensor. - * - * The last element of the vector represents the fastest varying - * dimension and the zeroth element represents the slowest - * varying dimension, etc. - * - * @return A shape class holding the tensor dimensions. - */ - virtual TensorShape getShape() const = 0; - - /** - * Returns the element size of the data in the tensor - * (discounting strides). This is how big a buffer would - * need to be to hold the tensor data contiguously in - * memory. - * - * @return The size of the tensor (in elements). - */ - virtual size_t getSize() const = 0; - - /** - * @brief Serializes the tensor to an output stream. - * - * @param[in] output The output stream to which to write the tensor - * - * @throw std::runtime_error If the stream is ever in a bad - * state before the tensor is fully serialized. - */ - virtual void serialize(std::ostream &output) const = 0; - - friend iterator; - friend const_iterator; - - virtual bool isQuantized() {return false;} - virtual float GetDelta() {return NAN;}; - virtual float GetOffset() {return NAN;}; - -protected: - - /** - * Returns the tensor iterator implementation. - * - * @return A pointer to the tensor iterator implementation. - */ - virtual std::unique_ptr<::DlSystem::ITensorItrImpl> getItrImpl() const = 0; -}; - -}} - -/** @} */ /* end_addtogroup c_plus_plus_apis C++ */ - -#endif diff --git a/third_party/snpe/include/DlSystem/ITensorFactory.hpp b/third_party/snpe/include/DlSystem/ITensorFactory.hpp deleted file mode 100644 index 57d2c8ea9..000000000 --- a/third_party/snpe/include/DlSystem/ITensorFactory.hpp +++ /dev/null @@ -1,92 +0,0 @@ -//============================================================================= -// -// Copyright (c) 2015-2016 Qualcomm Technologies, Inc. -// All Rights Reserved. -// Confidential and Proprietary - Qualcomm Technologies, Inc. -// -//============================================================================= - -#ifndef _ITENSOR_FACTORY_HPP -#define _ITENSOR_FACTORY_HPP - -#include "ITensor.hpp" -#include "TensorShape.hpp" -#include "ZdlExportDefine.hpp" -#include - -namespace zdl { - namespace DlSystem - { - class ITensor; - class TensorShape; - } -} - -namespace zdl { namespace DlSystem -{ - -/** @addtogroup c_plus_plus_apis C++ -@{ */ - -/** - * Factory interface class to create ITensor objects. - */ -class ZDL_EXPORT ITensorFactory -{ -public: - virtual ~ITensorFactory() = default; - - /** - * Creates a new ITensor with uninitialized data. - * - * The strides for the tensor will match the tensor dimensions - * (i.e., the tensor data is contiguous in memory). - * - * @param[in] shape The dimensions for the tensor in which the last - * element of the vector represents the fastest varying - * dimension and the zeroth element represents the slowest - * varying, etc. - * - * @return A pointer to the created tensor or nullptr if creating failed. - */ - virtual std::unique_ptr - createTensor(const TensorShape &shape) noexcept = 0; - - /** - * Creates a new ITensor by loading it from a file. - * - * @param[in] input The input stream from which to read the tensor - * data. - * - * @return A pointer to the created tensor or nullptr if creating failed. - * - */ - virtual std::unique_ptr createTensor(std::istream &input) noexcept = 0; - - /** - * Create a new ITensor with specific data. - * (i.e. the tensor data is contiguous in memory). This tensor is - * primarily used to create a tensor where tensor size can't be - * computed directly from dimension. One such example is - * NV21-formatted image, or any YUV formatted image - * - * @param[in] shape The dimensions for the tensor in which the last - * element of the vector represents the fastest varying - * dimension and the zeroth element represents the slowest - * varying, etc. - * - * @param[in] data The actual data with which the Tensor object is filled. - * - * @param[in] dataSize The size of data - * - * @return A pointer to the created tensor - */ - virtual std::unique_ptr - createTensor(const TensorShape &shape, const unsigned char *data, size_t dataSize) noexcept = 0; -}; - -}} - -/** @} */ /* end_addtogroup c_plus_plus_apis C++ */ - -#endif diff --git a/third_party/snpe/include/DlSystem/ITensorItr.hpp b/third_party/snpe/include/DlSystem/ITensorItr.hpp deleted file mode 100644 index 4ce12a9f1..000000000 --- a/third_party/snpe/include/DlSystem/ITensorItr.hpp +++ /dev/null @@ -1,182 +0,0 @@ -//============================================================================= -// -// Copyright (c) 2015 Qualcomm Technologies, Inc. -// All Rights Reserved. -// Confidential and Proprietary - Qualcomm Technologies, Inc. -// -//============================================================================= - -#ifndef _ITENSOR_ITR_HPP_ -#define _ITENSOR_ITR_HPP_ - -#include "ZdlExportDefine.hpp" -#include "ITensorItrImpl.hpp" - -#include -#include -#include - -namespace zdl { -namespace DlSystem -{ - template class ITensorItr; - class ITensor; - void ZDL_EXPORT fill(ITensorItr first, ITensorItr end, float val); - template OutItr ZDL_EXPORT copy(InItr first, InItr last, OutItr result) - { - return std::copy(first, last, result); - } -}} -namespace DlSystem -{ - class ITensorItrImpl; -} - -namespace zdl { namespace DlSystem -{ - -/** @addtogroup c_plus_plus_apis C++ -@{ */ - -/** - * A bidirectional iterator (with limited random access - * capabilities) for the zdl::DlSystem::ITensor class. - * - * This is a standard bidrectional iterator and is compatible - * with standard algorithm functions that operate on bidirectional - * access iterators (e.g., std::copy, std::fill, etc.). It uses a - * template parameter to create const and non-const iterators - * from the same code. Iterators are easiest to declare via the - * typedefs iterator and const_iterator in the ITensor class - * (e.g., zdl::DlSystem::ITensor::iterator). - * - * Note that if the tensor the iterator is traversing was - * created with nondefault (i.e., nontrivial) strides, the - * iterator will obey the strides when traversing the tensor - * data. - * - * Also note that nontrivial strides dramatically affect the - * performance of the iterator (on the order of 20x slower). - */ -template -class ZDL_EXPORT ITensorItr : public std::iterator -{ -public: - - typedef typename std::conditional::type VALUE_REF; - - ITensorItr() = delete; - virtual ~ITensorItr() {} - - ITensorItr(std::unique_ptr<::DlSystem::ITensorItrImpl> impl, - bool isTrivial = false, - float* data = nullptr) - : m_Impl(impl->clone()) - , m_IsTrivial(isTrivial) - , m_Data(data) - , m_DataStart(data) {} - - ITensorItr(const ITensorItr& itr) - : m_Impl(itr.m_Impl->clone()), - m_IsTrivial(itr.m_IsTrivial), - m_Data(itr.m_Data), - m_DataStart(itr.m_DataStart) {} - - zdl::DlSystem::ITensorItr& operator=(const ITensorItr& other) - { - if (this == &other) return *this; - m_Impl = std::move(other.m_Impl->clone()); - m_IsTrivial = other.m_IsTrivial; - m_Data = other.m_Data; - m_DataStart = other.m_DataStart; - return *this; - } - - inline zdl::DlSystem::ITensorItr& operator++() - { - if (m_IsTrivial) m_Data++; else m_Impl->increment(); - return *this; - } - inline zdl::DlSystem::ITensorItr operator++(int) - { - ITensorItr tmp(*this); - operator++(); - return tmp; - } - inline zdl::DlSystem::ITensorItr& operator--() - { - if (m_IsTrivial) m_Data--; else m_Impl->decrement(); - return *this; - } - inline zdl::DlSystem::ITensorItr operator--(int) - { - ITensorItr tmp(*this); - operator--(); - return tmp; - } - inline zdl::DlSystem::ITensorItr& operator+=(int rhs) - { - if (m_IsTrivial) m_Data += rhs; else m_Impl->increment(rhs); - return *this; - } - inline friend zdl::DlSystem::ITensorItr operator+(zdl::DlSystem::ITensorItr lhs, int rhs) - { lhs += rhs; return lhs; } - inline zdl::DlSystem::ITensorItr& operator-=(int rhs) - { - if (m_IsTrivial) m_Data -= rhs; else m_Impl->decrement(rhs); - return *this; - } - inline friend zdl::DlSystem::ITensorItr operator-(zdl::DlSystem::ITensorItr lhs, int rhs) - { lhs -= rhs; return lhs; } - - inline size_t operator-(const zdl::DlSystem::ITensorItr& rhs) - { - if (m_IsTrivial) return (m_Data - m_DataStart) - (rhs.m_Data - rhs.m_DataStart); - return m_Impl->getPosition() - rhs.m_Impl->getPosition(); - } - - inline friend bool operator<(const ITensorItr& lhs, const ITensorItr& rhs) - { - if (lhs.m_IsTrivial) return lhs.m_Data < rhs.m_Data; - return lhs.m_Impl->dataPointer() < rhs.m_Impl->dataPointer(); - } - inline friend bool operator>(const ITensorItr& lhs, const ITensorItr& rhs) - { return rhs < lhs; } - inline friend bool operator<=(const ITensorItr& lhs, const ITensorItr& rhs) - { return !(lhs > rhs); } - inline friend bool operator>=(const ITensorItr& lhs, const ITensorItr& rhs) - { return !(lhs < rhs); } - - inline bool operator==(const ITensorItr& rhs) const - { - if (m_IsTrivial) return m_Data == rhs.m_Data; - return m_Impl->dataPointer() == rhs.m_Impl->dataPointer(); - } - inline bool operator!=(const ITensorItr& rhs) const - { return !operator==(rhs); } - - inline VALUE_REF operator[](size_t idx) - { - if (m_IsTrivial) return *(m_DataStart + idx); - return m_Impl->getReferenceAt(idx); - } - inline VALUE_REF operator*() - { if (m_IsTrivial) return *m_Data; else return m_Impl->getReference(); } - inline VALUE_REF operator->() - { return *(*this); } - inline float* dataPointer() const - { if (m_IsTrivial) return m_Data; else return m_Impl->dataPointer(); } - - -protected: - std::unique_ptr<::DlSystem::ITensorItrImpl> m_Impl; - bool m_IsTrivial = false; - float* m_Data = nullptr; - float* m_DataStart = nullptr; -}; - -}} - -/** @} */ /* end_addtogroup c_plus_plus_apis C++ */ - -#endif diff --git a/third_party/snpe/include/DlSystem/ITensorItrImpl.hpp b/third_party/snpe/include/DlSystem/ITensorItrImpl.hpp deleted file mode 100644 index 069e7a1d6..000000000 --- a/third_party/snpe/include/DlSystem/ITensorItrImpl.hpp +++ /dev/null @@ -1,42 +0,0 @@ -//============================================================================= -// -// Copyright (c) 2015-2020 Qualcomm Technologies, Inc. -// All Rights Reserved. -// Confidential and Proprietary - Qualcomm Technologies, Inc. -// -//============================================================================= - -#ifndef _ITENSOR_ITR_IMPL_HPP_ -#define _ITENSOR_ITR_IMPL_HPP_ - -#include "ZdlExportDefine.hpp" - -#include -#include - -namespace DlSystem -{ - class ITensorItrImpl; -} - -class ZDL_EXPORT DlSystem::ITensorItrImpl -{ -public: - ITensorItrImpl() {} - virtual ~ITensorItrImpl() {} - - virtual float getValue() const = 0; - virtual float& getReference() = 0; - virtual float& getReferenceAt(size_t idx) = 0; - virtual float* dataPointer() const = 0; - virtual void increment(int incVal = 1) = 0; - virtual void decrement(int decVal = 1) = 0; - virtual size_t getPosition() = 0; - virtual std::unique_ptr clone() = 0; - -private: - ITensorItrImpl& operator=(const ITensorItrImpl& other) = delete; - ITensorItrImpl(const ITensorItrImpl& other) = delete; -}; - -#endif diff --git a/third_party/snpe/include/DlSystem/IUDL.hpp b/third_party/snpe/include/DlSystem/IUDL.hpp deleted file mode 100644 index a171ac7e8..000000000 --- a/third_party/snpe/include/DlSystem/IUDL.hpp +++ /dev/null @@ -1,105 +0,0 @@ -//============================================================================= -// -// Copyright (c) 2016-2021 Qualcomm Technologies, Inc. -// All Rights Reserved. -// Confidential and Proprietary - Qualcomm Technologies, Inc. -// -//============================================================================= - -#ifndef _DL_SYSTEM_IUDL_HPP_ -#define _DL_SYSTEM_IUDL_HPP_ - -#include "ZdlExportDefine.hpp" - -namespace zdl { -namespace DlSystem { - -/** - * NOTE: DEPRECATED, MAY BE REMOVED IN THE FUTURE. - * - * @brief . - * - * Base class user concrete UDL implementation. - * - * All functions are marked as: - * - * - virtual - * - noexcept - * - * User should make sure no exceptions are propagated outside of - * their module. Errors can be communicated via return values. - */ -class ZDL_EXPORT IUDL { -public: - /** - * NOTE: DEPRECATED, MAY BE REMOVED IN THE FUTURE. - * - * @brief . - * - * Destructor - */ - virtual ~IUDL() = default; - - /** - * NOTE: DEPRECATED, MAY BE REMOVED IN THE FUTURE. - * - * @brief Sets up the user's environment. - * This is called by the SNPE framework to allow the user the - * opportunity to setup anything which is needed for running - * user defined layers. - * - * @param cookie User provided opaque data returned by the SNPE - * runtime - * - * @param insz How many elements in input size array - * @param indim Pointer to a buffer that holds input dimension - * array - * @param indimsz Input dimension size array of the buffer - * 'indim'. Corresponds to indim - * - * @param outsz How many elements in output size array - * @param outdim Pointer to a buffer that holds output - * dimension array - * @param outdimsz Output dimension size of the buffer 'oudim'. - * Corresponds to indim - * - * @return true on success, false otherwise - */ - virtual bool setup(void *cookie, - size_t insz, const size_t **indim, const size_t *indimsz, - size_t outsz, const size_t **outdim, const size_t *outdimsz) = 0; - - /** - * NOTE: DEPRECATED, MAY BE REMOVED IN THE FUTURE. - * - * @brief Close the instance. Invoked by the SNPE - * framework to allow the user the opportunity to release any resources - * allocated during setup. - * - * @param cookie - User provided opaque data returned by the SNPE runtime - */ - virtual void close(void *cookie) noexcept = 0; - - /** - * NOTE: DEPRECATED, MAY BE REMOVED IN THE FUTURE. - * - * @brief Execute the user defined layer - * - * @param cookie User provided opaque data returned by the SNPE - * runtime - * - * @param input Const pointer to a float buffer that contains - * the input - * - * @param output Float pointer to a buffer that would hold - * the user defined layer's output. This buffer - * is allocated and owned by SNPE runtime. - */ - virtual bool execute(void *cookie, const float **input, float **output) = 0; -}; - -} // ns DlSystem - -} // ns zdl - -#endif // _DL_SYSTEM_IUDL_HPP_ diff --git a/third_party/snpe/include/DlSystem/IUserBuffer.hpp b/third_party/snpe/include/DlSystem/IUserBuffer.hpp deleted file mode 100644 index ea491a545..000000000 --- a/third_party/snpe/include/DlSystem/IUserBuffer.hpp +++ /dev/null @@ -1,358 +0,0 @@ -//============================================================================== -// -// Copyright (c) 2017-2020 Qualcomm Technologies, Inc. -// All Rights Reserved. -// Confidential and Proprietary - Qualcomm Technologies, Inc. -// -//============================================================================== - -#ifndef _IUSER_BUFFER_HPP -#define _IUSER_BUFFER_HPP - -#include "TensorShape.hpp" -#include "ZdlExportDefine.hpp" -#include - -namespace zdl { -namespace DlSystem { - -/** @addtogroup c_plus_plus_apis C++ -@{ */ - - -/** - * @brief . - * - * A base class buffer encoding type - */ -class ZDL_EXPORT UserBufferEncoding { -public: - - /** - * @brief . - * - * An enum class of all supported element types in a IUserBuffer - */ - enum class ElementType_t - { - /// Unknown element type. - UNKNOWN = 0, - - /// Each element is presented by float. - FLOAT = 1, - - /// Each element is presented by an unsigned int. - UNSIGNED8BIT = 2, - - /// Each element is presented by an 8-bit quantized value. - TF8 = 10, - - /// Each element is presented by an 16-bit quantized value. - TF16 = 11 - }; - - /** - * @brief Retrieves the size of the element, in bytes. - * - * @return Size of the element, in bytes. - */ - virtual size_t getElementSize() const noexcept = 0; - - /** - * @brief Retrieves the element type - * - * @return Element type - */ - ElementType_t getElementType() const noexcept {return m_ElementType;}; - - virtual ~UserBufferEncoding() {} - -protected: - UserBufferEncoding(ElementType_t elementType) : m_ElementType(elementType) {}; -private: - const ElementType_t m_ElementType; -}; - -/** - * @brief . - * - * A base class buffer source type - * - * @note User buffer from CPU support all kinds of runtimes; - * User buffer from GLBUFFER support only GPU runtime. - */ -class ZDL_EXPORT UserBufferSource { -public: - enum class SourceType_t - { - /// Unknown buffer source type. - UNKNOWN = 0, - - /// The network inputs are from CPU buffer. - CPU = 1, - - /// The network inputs are from OpenGL buffer. - GLBUFFER = 2 - }; - - /** - * @brief Retrieves the source type - * - * @return Source type - */ - SourceType_t getSourceType() const noexcept {return m_SourceType;}; - -protected: - UserBufferSource(SourceType_t sourceType): m_SourceType(sourceType) {}; -private: - const SourceType_t m_SourceType; -}; - -/** - * @brief . - * - * An source type where input data is delivered from OpenGL buffer - */ -class ZDL_EXPORT UserBufferSourceGLBuffer : public UserBufferSource{ -public: - UserBufferSourceGLBuffer() : UserBufferSource(SourceType_t::GLBUFFER) {}; -}; - -/** - * @brief . - * - * An encoding type where each element is represented by an unsigned int - */ -class ZDL_EXPORT UserBufferEncodingUnsigned8Bit : public UserBufferEncoding { -public: - UserBufferEncodingUnsigned8Bit() : UserBufferEncoding(ElementType_t::UNSIGNED8BIT) {}; - size_t getElementSize() const noexcept override; - -protected: - UserBufferEncodingUnsigned8Bit(ElementType_t elementType) : UserBufferEncoding(elementType) {}; - -}; - -/** - * @brief . - * - * An encoding type where each element is represented by a float - */ -class ZDL_EXPORT UserBufferEncodingFloat : public UserBufferEncoding { -public: - UserBufferEncodingFloat() : UserBufferEncoding(ElementType_t::FLOAT) {}; - size_t getElementSize() const noexcept override; - -}; - -/** - * @brief . - * - * An encoding type where each element is represented by tf8, which is an - * 8-bit quantizd value, which has an exact representation of 0.0 - */ -class ZDL_EXPORT UserBufferEncodingTfN : public UserBufferEncoding { -public: - UserBufferEncodingTfN() = delete; - UserBufferEncodingTfN(uint64_t stepFor0, float stepSize, uint8_t bWidth=8): - UserBufferEncoding(getTypeFromWidth(bWidth)), - bitWidth(bWidth), - m_StepExactly0(stepFor0), - m_QuantizedStepSize(stepSize){}; - - UserBufferEncodingTfN(const zdl::DlSystem::UserBufferEncoding &ubEncoding) : UserBufferEncoding(ubEncoding.getElementType()){ - const zdl::DlSystem::UserBufferEncodingTfN* ubEncodingTfN - = dynamic_cast (&ubEncoding); - if (ubEncodingTfN) { - m_StepExactly0 = ubEncodingTfN->getStepExactly0(); - m_QuantizedStepSize = ubEncodingTfN->getQuantizedStepSize(); - bitWidth = ubEncodingTfN->bitWidth; - } - } - - size_t getElementSize() const noexcept override; - /** - * @brief Sets the step value that represents 0 - * - * @param[in] stepExactly0 The step value that represents 0 - * - */ - void setStepExactly0(uint64_t stepExactly0) { - m_StepExactly0 = stepExactly0; - } - - /** - * @brief Sets the float value that each step represents - * - * @param[in] quantizedStepSize The float value of each step size - * - */ - void setQuantizedStepSize(const float quantizedStepSize) { - m_QuantizedStepSize = quantizedStepSize; - } - - /** - * @brief Retrieves the step that represents 0.0 - * - * @return Step value - */ - uint64_t getStepExactly0() const { - return m_StepExactly0; - } - - /** - * Calculates the minimum floating point value that - * can be represented with this encoding. - * - * @return Minimum representable floating point value - */ - float getMin() const { - return static_cast(m_QuantizedStepSize * (0 - (double)m_StepExactly0)); - } - - /** - * Calculates the maximum floating point value that - * can be represented with this encoding. - * - * @return Maximum representable floating point value - */ - float getMax() const{ - return static_cast(m_QuantizedStepSize * (pow(2,bitWidth)-1 - (double)m_StepExactly0)); - }; - - /** - * @brief Retrieves the step size - * - * @return Step size - */ - float getQuantizedStepSize() const { - return m_QuantizedStepSize; - } - - ElementType_t getTypeFromWidth(uint8_t width); - - uint8_t bitWidth; -protected: - uint64_t m_StepExactly0; - float m_QuantizedStepSize; -}; - - -class ZDL_EXPORT UserBufferEncodingTf8 : public UserBufferEncodingTfN { -public: - UserBufferEncodingTf8() = delete; - UserBufferEncodingTf8(unsigned char stepFor0, float stepSize) : - UserBufferEncodingTfN(stepFor0, stepSize) {}; - - UserBufferEncodingTf8(const zdl::DlSystem::UserBufferEncoding &ubEncoding) : UserBufferEncodingTfN(ubEncoding){} - -/** - * @brief Sets the step value that represents 0 - * - * @param[in] stepExactly0 The step value that represents 0 - * - */ - - void setStepExactly0(const unsigned char stepExactly0) { - UserBufferEncodingTfN::m_StepExactly0 = stepExactly0; - } - -/** - * @brief Retrieves the step that represents 0.0 - * - * @return Step value - */ - - unsigned char getStepExactly0() const { - return UserBufferEncodingTfN::m_StepExactly0; - } - -}; - - -/** - * @brief UserBuffer contains a pointer and info on how to walk it and interpret its content. - */ -class ZDL_EXPORT IUserBuffer { -public: - virtual ~IUserBuffer() = default; - - /** - * @brief Retrieves the total number of bytes between elements in each dimension if - * the buffer were to be interpreted as a multi-dimensional array. - * - * @return Number of bytes between elements in each dimension. - * e.g. A tightly packed tensor of floats with dimensions [4, 3, 2] would - * return strides of [24, 8, 4]. - */ - virtual const TensorShape& getStrides() const = 0; - - /** - * @brief Retrieves the size of the buffer, in bytes. - * - * @return Size of the underlying buffer, in bytes. - */ - virtual size_t getSize() const = 0; - - /** - * @brief Retrieves the size of the inference data in the buffer, in bytes. - * - * The inference results from a dynamic-sized model may not be exactly the same size - * as the UserBuffer provided to SNPE. This function can be used to get the amount - * of output inference data, which may be less or greater than the size of the UserBuffer. - * - * If the inference results fit in the UserBuffer, getOutputSize() would be less than - * or equal to getSize(). But if the inference results were more than the capacity of - * the provided UserBuffer, the results would be truncated to fit the UserBuffer. But, - * getOutputSize() would be greater than getSize(), which indicates a bigger buffer - * needs to be provided to SNPE to hold all of the inference results. - * - * @return Size required for the buffer to hold all inference results, which can be less - * or more than the size of the buffer, in bytes. - */ - virtual size_t getOutputSize() const = 0; - - /** - * @brief Changes the underlying memory that backs the UserBuffer. - * - * This can be used to avoid creating multiple UserBuffer objects - * when the only thing that differs is the memory location. - * - * @param[in] buffer Pointer to the memory location - * - * @return Whether the set succeeds. - */ - virtual bool setBufferAddress(void *buffer) noexcept = 0; - - /** - * @brief Gets a const reference to the data encoding object of - * the underlying buffer - * - * This is necessary when the UserBuffer is filled by SNPE with - * data types such as TF8, where the caller needs to know the quantization - * parameters in order to interpret the data properly - * - * @return A read-only encoding object - */ - virtual const UserBufferEncoding& getEncoding() const noexcept = 0; - - /** - * @brief Gets a reference to the data encoding object of - * the underlying buffer - * - * This is necessary when the UserBuffer is re-used, and the encoding - * parameters can change. For example, each input can be quantized with - * different step sizes. - * - * @return Data encoding meta-data - */ - virtual UserBufferEncoding& getEncoding() noexcept = 0; - -}; - -/** @} */ /* end_addtogroup c_plus_plus_apis C++ */ - -} -} - -#endif diff --git a/third_party/snpe/include/DlSystem/IUserBufferFactory.hpp b/third_party/snpe/include/DlSystem/IUserBufferFactory.hpp deleted file mode 100644 index 59803fbd8..000000000 --- a/third_party/snpe/include/DlSystem/IUserBufferFactory.hpp +++ /dev/null @@ -1,81 +0,0 @@ -//============================================================================= -// -// Copyright (c) 2017 Qualcomm Technologies, Inc. -// All Rights Reserved. -// Confidential and Proprietary - Qualcomm Technologies, Inc. -// -//============================================================================= - -#ifndef _IUSERBUFFER_FACTORY_HPP -#define _IUSERBUFFER_FACTORY_HPP - -#include "IUserBuffer.hpp" -#include "TensorShape.hpp" -#include "ZdlExportDefine.hpp" -#include "DlEnums.hpp" -namespace zdl { - namespace DlSystem { - class IUserBuffer; - - class TensorShape; - } -} - -namespace zdl { -namespace DlSystem { - -/** @addtogroup c_plus_plus_apis C++ -@{ */ - -/** -* Factory interface class to create IUserBuffer objects. -*/ -class ZDL_EXPORT IUserBufferFactory { -public: - virtual ~IUserBufferFactory() = default; - - /** - * @brief Creates a UserBuffer - * - * @param[in] buffer Pointer to the buffer that the caller supplies - * - * @param[in] bufSize Buffer size, in bytes - * - * @param[in] strides Total number of bytes between elements in each dimension. - * E.g. A tightly packed tensor of floats with dimensions [4, 3, 2] would have strides of [24, 8, 4]. - * - * @param[in] userBufferEncoding Reference to an UserBufferEncoding object - * - * @note Caller has to ensure that memory pointed to by buffer stays accessible - * for the lifetime of the object created - */ - virtual std::unique_ptr - createUserBuffer(void *buffer, size_t bufSize, const zdl::DlSystem::TensorShape &strides, zdl::DlSystem::UserBufferEncoding* userBufferEncoding) noexcept = 0; - - /** - * @brief Creates a UserBuffer - * - * @param[in] buffer Pointer to the buffer that the caller supplies - * - * @param[in] bufSize Buffer size, in bytes - * - * @param[in] strides Total number of bytes between elements in each dimension. - * E.g. A tightly packed tensor of floats with dimensions [4, 3, 2] would have strides of [24, 8, 4]. - * - * @param[in] userBufferEncoding Reference to an UserBufferEncoding object - * - * @param[in] userBufferSource Reference to an UserBufferSource object - * - * @note Caller has to ensure that memory pointed to by buffer stays accessible - * for the lifetime of the object created - */ - virtual std::unique_ptr - createUserBuffer(void *buffer, size_t bufSize, const zdl::DlSystem::TensorShape &strides, zdl::DlSystem::UserBufferEncoding* userBufferEncoding, zdl::DlSystem::UserBufferSource* userBufferSource) noexcept = 0; -}; -/** @} */ /* end_addtogroup c_plus_plus_apis C++ */ - -} -} - - -#endif diff --git a/third_party/snpe/include/DlSystem/PlatformConfig.hpp b/third_party/snpe/include/DlSystem/PlatformConfig.hpp deleted file mode 100644 index c7b85f43c..000000000 --- a/third_party/snpe/include/DlSystem/PlatformConfig.hpp +++ /dev/null @@ -1,230 +0,0 @@ -//============================================================================= -// -// Copyright (c) 2017-2018,2021 Qualcomm Technologies, Inc. -// All Rights Reserved. -// Confidential and Proprietary - Qualcomm Technologies, Inc. -// -//============================================================================= - -#ifndef _DL_SYSTEM_PLATFORM_CONFIG_HPP_ -#define _DL_SYSTEM_PLATFORM_CONFIG_HPP_ - -#include "DlSystem/ZdlExportDefine.hpp" -#include - -namespace zdl{ -namespace DlSystem -{ - -/** @addtogroup c_plus_plus_apis C++ -@{ */ - - -/** - * @brief . - * - * A structure OpenGL configuration - * - * @note When certain OpenGL context and display are provided to UserGLConfig for using - * GPU buffer as input directly, the user MUST ensure the particular OpenGL - * context and display remain vaild throughout the execution of neural network models. - */ -struct ZDL_EXPORT UserGLConfig -{ - /// Holds user EGL context. - /// - void* userGLContext = nullptr; - - /// Holds user EGL display. - void* userGLDisplay = nullptr; -}; - -/** - * @brief . - * - * A structure Gpu configuration - */ -struct ZDL_EXPORT UserGpuConfig{ - /// Holds user OpenGL configuration. - /// - UserGLConfig userGLConfig; -}; - -/** - * @brief . - * - * A class user platform configuration - */ -class ZDL_EXPORT PlatformConfig -{ -public: - - /** - * @brief . - * - * An enum class of all supported platform types - */ - enum class PlatformType_t - { - /// Unknown platform type. - UNKNOWN = 0, - - /// Snapdragon CPU. - CPU = 1, - - /// Adreno GPU. - GPU = 2, - - /// Hexagon DSP. - DSP = 3 - }; - - /** - * @brief . - * - * A union class user platform configuration information - */ - union PlatformConfigInfo - { - /// Holds user GPU Configuration. - /// - UserGpuConfig userGpuConfig; - - PlatformConfigInfo(){}; - }; - - PlatformConfig() : m_PlatformType(PlatformType_t::UNKNOWN), - m_PlatformOptions("") {}; - - /** - * @brief Retrieves the platform type - * - * @return Platform type - */ - PlatformType_t getPlatformType() const {return m_PlatformType;}; - - /** - * @brief Indicates whther the plaform configuration is valid. - * - * @return True if the platform configuration is valid; false otherwise. - */ - bool isValid() const {return (PlatformType_t::UNKNOWN != m_PlatformType);}; - - /** - * @brief Retrieves the Gpu configuration - * - * @param[out] userGpuConfig The passed in userGpuConfig populated with the Gpu configuration on return. - * - * @return True if Gpu configuration was retrieved; false otherwise. - */ - bool getUserGpuConfig(UserGpuConfig& userGpuConfig) const - { - if(m_PlatformType == PlatformType_t::GPU) - { - userGpuConfig = m_PlatformConfigInfo.userGpuConfig; - return true; - } - else - { - return false; - } - } - - /** - * @brief Sets the Gpu configuration - * - * @param[in] userGpuConfig Gpu Configuration - * - * @return True if Gpu configuration was successfully set; false otherwise. - */ - bool setUserGpuConfig(UserGpuConfig& userGpuConfig) - { - if((userGpuConfig.userGLConfig.userGLContext != nullptr) && (userGpuConfig.userGLConfig.userGLDisplay != nullptr)) - { - switch (m_PlatformType) - { - case PlatformType_t::GPU: - m_PlatformConfigInfo.userGpuConfig = userGpuConfig; - return true; - case PlatformType_t::UNKNOWN: - m_PlatformType = PlatformType_t::GPU; - m_PlatformConfigInfo.userGpuConfig = userGpuConfig; - return true; - default: - return false; - } - } - else - return false; - } - - /** - * @brief Sets the platform options - * - * @param[in] options Options as a string in the form of "keyword:options" - * - * @return True if options are pass validation; otherwise false. If false, the options are not updated. - */ - bool setPlatformOptions(std::string options) { - std::string oldOptions = m_PlatformOptions; - m_PlatformOptions = options; - if (isOptionsValid()) { - return true; - } else { - m_PlatformOptions = oldOptions; - return false; - } - } - - /** - * @brief Indicates whther the plaform configuration is valid. - * - * @return True if the platform configuration is valid; false otherwise. - */ - bool isOptionsValid() const; - - /** - * @brief Gets the platform options - * - * @return Options as a string - */ - std::string getPlatformOptions() const { return m_PlatformOptions; } - - /** - * @brief Sets the platform options - * - * @param[in] optionName Name of platform options" - * @param[in] value Value of specified optionName - * - * @return If true, add "optionName:value" to platform options if optionName don't exist, otherwise update the - * value of specified optionName. - * If false, the platform options will not be changed. - */ - bool setPlatformOptionValue(const std::string& optionName, const std::string& value); - - /** - * @brief Removes the platform options - * - * @param[in] optionName Name of platform options" - * @param[in] value Value of specified optionName - * - * @return If true, removed "optionName:value" to platform options if optionName don't exist, do nothing. - * If false, the platform options will not be changed. - */ - bool removePlatformOptionValue(const std::string& optionName, const std::string& value); - - static void SetIsUserGLBuffer(bool isUserGLBuffer); - static bool GetIsUserGLBuffer(); - -private: - PlatformType_t m_PlatformType; - PlatformConfigInfo m_PlatformConfigInfo; - std::string m_PlatformOptions; - static bool m_IsUserGLBuffer; -}; - -/** @} */ /* end_addtogroup c_plus_plus_apis C++ */ - -}} //namespace end - -#endif diff --git a/third_party/snpe/include/DlSystem/RuntimeList.hpp b/third_party/snpe/include/DlSystem/RuntimeList.hpp deleted file mode 100644 index 1088a5b31..000000000 --- a/third_party/snpe/include/DlSystem/RuntimeList.hpp +++ /dev/null @@ -1,154 +0,0 @@ -//============================================================================= -// -// Copyright (c) 2019 Qualcomm Technologies, Inc. -// All Rights Reserved. -// Confidential and Proprietary - Qualcomm Technologies, Inc. -// -//============================================================================= - -#include "ZdlExportDefine.hpp" -#include "DlSystem/DlEnums.hpp" -#include "DlSystem/StringList.hpp" -#include -#include - -#ifndef DL_SYSTEM_RUNTIME_LIST_HPP -#define DL_SYSTEM_RUNTIME_LIST_HPP - -namespace DlSystem -{ - // Forward declaration of Runtime List implementation. - class RuntimeListImpl; -} - -namespace zdl -{ -namespace DlSystem -{ - -/** @addtogroup c_plus_plus_apis C++ -@{ */ - -/** - * @brief . - * - * A class representing list of runtimes - */ -class ZDL_EXPORT RuntimeList final -{ -public: - - /** - * @brief . - * - * Creates a new runtime list - * - */ - RuntimeList(); - - /** - * @brief . - * - * copy constructor. - * @param[in] other object to copy. - */ - RuntimeList(const RuntimeList& other); - - /** - * @brief . - * - * constructor with single Runtime_t object - * @param[in] Runtime_t object - */ - RuntimeList(const zdl::DlSystem::Runtime_t& runtime); - - /** - * @brief . - * - * assignment operator. - */ - RuntimeList& operator=(const RuntimeList& other); - - /** - * @brief . - * - * subscript operator. - */ - Runtime_t& operator[](size_t index); - - /** - * @brief Adds runtime to the end of the runtime list - * order of precedence is former followed by latter entry - * - * @param[in] runtime to add - * - * Ruturns false If the runtime already exists - */ - bool add(const zdl::DlSystem::Runtime_t& runtime); - - /** - * @brief Removes the runtime from the list - * - * @param[in] runtime to be removed - * - * @note If the runtime is not found, nothing is done. - */ - void remove(const zdl::DlSystem::Runtime_t runtime) noexcept; - - /** - * @brief Returns the number of runtimes in the list - */ - size_t size() const noexcept; - - /** - * @brief Returns true if the list is empty - */ - bool empty() const noexcept; - - /** - * @brief . - * - * Removes all runtime from the list - */ - void clear() noexcept; - - /** - * @brief . - * - * Returns a StringList of names from the runtime list in - * order of precedence - */ - zdl::DlSystem::StringList getRuntimeListNames() const; - - /** - * @brief . - * - * @param[in] runtime string - * Returns a Runtime enum corresponding to the in param string - * - */ - static zdl::DlSystem::Runtime_t stringToRuntime(const char* runtimeStr); - - /** - * @brief . - * - * @param[in] runtime - * Returns a string corresponding to the in param runtime enum - * - */ - static const char* runtimeToString(const zdl::DlSystem::Runtime_t runtime); - - ~RuntimeList(); - -private: - void deepCopy(const RuntimeList &other); - std::unique_ptr<::DlSystem::RuntimeListImpl> m_RuntimeListImpl; -}; - -} // DlSystem namespace -} // zdl namespace - -/** @} */ /* end_addtogroup c_plus_plus_apis C++ */ - -#endif // DL_SYSTEM_RUNTIME_LIST_HPP - diff --git a/third_party/snpe/include/DlSystem/String.hpp b/third_party/snpe/include/DlSystem/String.hpp deleted file mode 100644 index c1eba3bad..000000000 --- a/third_party/snpe/include/DlSystem/String.hpp +++ /dev/null @@ -1,104 +0,0 @@ -//============================================================================= -// -// Copyright (c) 2017, 2020 Qualcomm Technologies, Inc. -// All Rights Reserved. -// Confidential and Proprietary - Qualcomm Technologies, Inc. -// -//============================================================================= - -#ifndef PLATFORM_STANDARD_STRING_HPP -#define PLATFORM_STANDARD_STRING_HPP - -#include -#include -#include -#include "DlSystem/ZdlExportDefine.hpp" - -namespace zdl -{ -namespace DlSystem -{ -/** @addtogroup c_plus_plus_apis C++ -@{ */ - -/** - * @brief . - * - * Class for wrapping char * as a really stripped down std::string replacement. - */ -class ZDL_EXPORT String final -{ -public: - String() = delete; - - /** - * Construct a string from std::string reference. - * @param str Reference to a std::string - */ - explicit String(const std::string& str); - - /** - * Construct a string from char* reference. - * @param a char* - */ - explicit String(const char* str); - - /** - * move constructor. - */ - String(String&& other) noexcept; - - /** - * copy constructor. - */ - String(const String& other) = delete; - - /** - * assignment operator. - */ - String& operator=(const String&) = delete; - - /** - * move assignment operator. - */ - String& operator=(String&&) = delete; - - /** - * class comparators - */ - bool operator<(const String& rhs) const noexcept; - bool operator>(const String& rhs) const noexcept; - bool operator<=(const String& rhs) const noexcept; - bool operator>=(const String& rhs) const noexcept; - bool operator==(const String& rhs) const noexcept; - bool operator!=(const String& rhs) const noexcept; - - /** - * class comparators against std::string - */ - bool operator<(const std::string& rhs) const noexcept; - bool operator>(const std::string& rhs) const noexcept; - bool operator<=(const std::string& rhs) const noexcept; - bool operator>=(const std::string& rhs) const noexcept; - bool operator==(const std::string& rhs) const noexcept; - bool operator!=(const std::string& rhs) const noexcept; - - const char* c_str() const noexcept; - - ~String(); -private: - - char* m_string; -}; - -/** - * overloaded << operator - */ -ZDL_EXPORT std::ostream& operator<<(std::ostream& os, const String& str) noexcept; - -} // DlSystem namespace -} // zdl namespace - -/** @} */ /* end_addtogroup c_plus_plus_apis C++ */ - -#endif // PLATFORM_STANDARD_STRING_HPP diff --git a/third_party/snpe/include/DlSystem/StringList.hpp b/third_party/snpe/include/DlSystem/StringList.hpp deleted file mode 100644 index a9a7d5ed8..000000000 --- a/third_party/snpe/include/DlSystem/StringList.hpp +++ /dev/null @@ -1,107 +0,0 @@ -//============================================================================= -// -// Copyright (c) 2016 Qualcomm Technologies, Inc. -// All Rights Reserved. -// Confidential and Proprietary - Qualcomm Technologies, Inc. -// -//============================================================================= -#include -#include "ZdlExportDefine.hpp" - -#ifndef DL_SYSTEM_STRINGLIST_HPP -#define DL_SYSTEM_STRINGLIST_HPP - -namespace zdl -{ -namespace DlSystem -{ -/** @addtogroup c_plus_plus_apis C++ -@{ */ - -/** - * @brief . - * - * Class for holding an order list of null-terminated ASCII strings. - */ -class ZDL_EXPORT StringList final -{ -public: - StringList() {} - - /** - * Construct a string list with some pre-allocated memory. - * @warning Contents of the list will be uninitialized - * @param[in] length Number of elements for which to pre-allocate space. - */ - explicit StringList(size_t length); - - /** - * Append a string to the list. - * @param[in] str Null-terminated ASCII string to append to the list. - */ - void append(const char* str); - - /** - * Returns the string at the indicated position, - * or an empty string if the positions is greater than the size - * of the list. - * @param[in] idx Position in the list of the desired string - */ - const char* at(size_t idx) const noexcept; - - /** - * Pointer to the first string in the list. - * Can be used to iterate through the list. - */ - const char** begin() const noexcept; - - /** - * Pointer to one after the last string in the list. - * Can be used to iterate through the list. - */ - const char** end() const noexcept; - - /** - * Return the number of valid string pointers held by this list. - */ - size_t size() const noexcept; - - - /** - * assignment operator. - */ - StringList& operator=(const StringList&) noexcept; - - /** - * copy constructor. - * @param[in] other object to copy. - */ - StringList(const StringList& other); - - /** - * move constructor. - * @param[in] other object to move. - */ - StringList(StringList&& other) noexcept; - - ~StringList(); -private: - void copy(const StringList& other); - - void resize(size_t length); - - void clear(); - - static const char* s_Empty; - const char** m_Strings = nullptr; - const char** m_End = nullptr; - size_t m_Size = 0; -}; - -} // DlSystem namespace -} // zdl namespace - -/** @} */ /* end_addtogroup c_plus_plus_apis C++ */ - -#endif // DL_SYSTEM_STRINGLIST_HPP - diff --git a/third_party/snpe/include/DlSystem/TensorMap.hpp b/third_party/snpe/include/DlSystem/TensorMap.hpp deleted file mode 100644 index feecec666..000000000 --- a/third_party/snpe/include/DlSystem/TensorMap.hpp +++ /dev/null @@ -1,120 +0,0 @@ -//============================================================================= -// -// Copyright (c) 2016 Qualcomm Technologies, Inc. -// All Rights Reserved. -// Confidential and Proprietary - Qualcomm Technologies, Inc. -// -//============================================================================= -#include -#include "ZdlExportDefine.hpp" -#include "ITensor.hpp" -#include "StringList.hpp" - -#ifndef DL_SYSTEM_TENSOR_MAP_HPP -#define DL_SYSTEM_TENSOR_MAP_HPP - -namespace DlSystem -{ - // Forward declaration of tensor map implementation. - class TensorMapImpl; -} - -namespace zdl -{ -namespace DlSystem -{ - -/** @addtogroup c_plus_plus_apis C++ -@{ */ - -/** - * @brief . - * - * A class representing the map of tensor. - */ -class ZDL_EXPORT TensorMap final -{ -public: - - /** - * @brief . - * - * Creates a new empty tensor map - */ - TensorMap(); - - /** - * copy constructor. - * @param[in] other object to copy. - */ - TensorMap(const TensorMap& other); - - /** - * assignment operator. - */ - TensorMap& operator=(const TensorMap& other); - - /** - * @brief Adds a name and the corresponding tensor pointer - * to the map - * - * @param[in] name The name of the tensor - * @param[out] tensor The pointer to the tensor - * - * @note If a tensor with the same name already exists, the - * tensor is replaced with the existing tensor. - */ - void add(const char *name, zdl::DlSystem::ITensor *tensor); - - /** - * @brief Removes a mapping of tensor and its name by its name - * - * @param[in] name The name of tensor to be removed - * - * @note If no tensor with the specified name is found, nothing - * is done. - */ - void remove(const char *name) noexcept; - - /** - * @brief Returns the number of tensors in the map - */ - size_t size() const noexcept; - - /** - * @brief . - * - * Removes all tensors from the map - */ - void clear() noexcept; - - /** - * @brief Returns the tensor given its name. - * - * @param[in] name The name of the tensor to get. - * - * @return nullptr if no tensor with the specified name is - * found; otherwise, a valid pointer to the tensor. - */ - zdl::DlSystem::ITensor* getTensor(const char *name) const noexcept; - - /** - * @brief . - * - * Returns the names of all tensors - */ - zdl::DlSystem::StringList getTensorNames() const; - - ~TensorMap(); -private: - void swap(const TensorMap &other); - std::unique_ptr<::DlSystem::TensorMapImpl> m_TensorMapImpl; -}; - -} // DlSystem namespace -} // zdl namespace - -/** @} */ /* end_addtogroup c_plus_plus_apis C++ */ - -#endif // DL_SYSTEM_TENSOR_MAP_HPP - diff --git a/third_party/snpe/include/DlSystem/TensorShape.hpp b/third_party/snpe/include/DlSystem/TensorShape.hpp deleted file mode 100644 index 99583dccb..000000000 --- a/third_party/snpe/include/DlSystem/TensorShape.hpp +++ /dev/null @@ -1,203 +0,0 @@ -//============================================================================= -// -// Copyright (c) 2016 Qualcomm Technologies, Inc. -// All Rights Reserved. -// Confidential and Proprietary - Qualcomm Technologies, Inc. -// -//============================================================================= -#include -#include -#include -#include -#include "ZdlExportDefine.hpp" - -#ifndef DL_SYSTEM_TENSOR_SHAPE_HPP -#define DL_SYSTEM_TENSOR_SHAPE_HPP - -namespace DlSystem -{ - // Forward declaration of tensor shape implementation. - class TensorShapeImpl; -} - -namespace zdl -{ -namespace DlSystem -{ - -/** @addtogroup c_plus_plus_apis C++ -@{ */ - -/** - * @brief . - * - * Convenient typedef to represent dimension - */ -using Dimension = size_t; - -/** - * @brief . - * - * A class representing the shape of tensor. It is used at the - * time of creation of tensor. - */ -class ZDL_EXPORT TensorShape final -{ -public: - - /** - * @brief . - * - * Creates a new shape with a list of dims specified in - * initializer list fashion. - * - * @param[in] dims The dimensions are specified in which the last - * element of the vector represents the fastest varying - * dimension and the zeroth element represents the slowest - * varying, etc. - * - */ - TensorShape(std::initializer_list dims); - - /** - * @brief . - * - * Creates a new shape with a list of dims specified in array - * - * @param[in] dims The dimensions are specified in which the last - * element of the vector represents the fastest varying - * dimension and the zeroth element represents the slowest - * varying, etc. - * - * @param[in] size Size of the array. - * - */ - TensorShape(const Dimension *dims, size_t size); - - /** - * @brief . - * - * Creates a new shape with a vector of dims specified in - * vector fashion. - * - * @param[in] dims The dimensions are specified in which the last - * element of the vector represents the fastest varying - * dimension and the zeroth element represents the slowest - * varying, etc. - * - */ - TensorShape(std::vector dims); - - /** - * @brief . - * - * copy constructor. - * @param[in] other object to copy. - */ - TensorShape(const TensorShape& other); - - /** - * @brief . - * - * assignment operator. - */ - TensorShape& operator=(const TensorShape& other); - - /** - * @brief . - * - * Creates a new shape with no dims. It can be extended later - * by invoking concatenate. - */ - TensorShape(); - - /** - * @brief . - * - * Concatenates additional dimensions specified in - * initializer list fashion to the existing dimensions. - * - * @param[in] dims The dimensions are specified in which the last - * element of the vector represents the fastest varying - * dimension and the zeroth element represents the slowest - * varying, etc. - * - */ - void concatenate(std::initializer_list dims); - - /** - * @brief . - * - * Concatenates additional dimensions specified in - * the array to the existing dimensions. - * - * @param[in] dims The dimensions are specified in which the last - * element of the vector represents the fastest varying - * dimension and the zeroth element represents the slowest - * varying, etc. - * - * @param[in] size Size of the array. - * - */ - void concatenate(const Dimension *dims, size_t size); - - /** - * @brief . - * - * Concatenates an additional dimension to the existing - * dimensions. - * - * @param[in] dim The dimensions are specified in which the last element - * of the vector represents the fastest varying dimension and the - * zeroth element represents the slowest varying, etc. - * - */ - void concatenate(const Dimension &dim); - - /** - * @brief . - * - * Retrieves a single dimension, based on its index. - * - * @return The value of dimension - * - * @throws std::out_of_range if the index is >= the number of - * dimensions (or rank). - */ - Dimension& operator[](size_t index); - Dimension& operator[](size_t index) const; - - /** - * @brief . - * - * Retrieves the rank i.e. number of dimensions. - * - * @return The rank - */ - size_t rank() const; - - /** - * @brief . - * - * Retrieves a pointer to the first dimension of shape - * - * @return nullptr if no dimension exists; otherwise, points to - * the first dimension. - * - */ - const Dimension* getDimensions() const; - - ~TensorShape(); - -private: - void swap(const TensorShape &other); - std::unique_ptr<::DlSystem::TensorShapeImpl> m_TensorShapeImpl; -}; - -} // DlSystem namespace -} // zdl namespace - -/** @} */ /* end_addtogroup c_plus_plus_apis C++ */ - -#endif // DL_SYSTEM_TENSOR_SHAPE_HPP - diff --git a/third_party/snpe/include/DlSystem/TensorShapeMap.hpp b/third_party/snpe/include/DlSystem/TensorShapeMap.hpp deleted file mode 100644 index cef946e2e..000000000 --- a/third_party/snpe/include/DlSystem/TensorShapeMap.hpp +++ /dev/null @@ -1,127 +0,0 @@ -//============================================================================= -// -// Copyright (c) 2017-2020 Qualcomm Technologies, Inc. -// All Rights Reserved. -// Confidential and Proprietary - Qualcomm Technologies, Inc. -// -//============================================================================= -#include -#include -#include -#include "ZdlExportDefine.hpp" -#include "DlSystem/TensorShape.hpp" -#include "DlSystem/StringList.hpp" - -#ifndef DL_SYSTEM_TENSOR_SHAPE_MAP_HPP -#define DL_SYSTEM_TENSOR_SHAPE_MAP_HPP - -namespace DlSystem -{ - // Forward declaration of tensor shape map implementation. - class TensorShapeMapImpl; -} - -namespace zdl -{ -namespace DlSystem -{ - -/** @addtogroup c_plus_plus_apis C++ -@{ */ - -/** - * @brief . - * - * A class representing the map of names and tensorshapes. - */ -class ZDL_EXPORT TensorShapeMap final -{ -public: - - /** - * @brief . - * - * Creates a new tensor shape map - * - */ - TensorShapeMap(); - - /** - * @brief . - * - * copy constructor. - * @param[in] other object to copy. - */ - TensorShapeMap(const TensorShapeMap& other); - - /** - * @brief . - * - * assignment operator. - */ - TensorShapeMap& operator=(const TensorShapeMap& other); - - /** - * @brief Adds a name and the corresponding tensor pointer - * to the map - * - * @param[in] name The name of the tensor - * @param[out] tensor The pointer to the tensor - * - * @note If a tensor with the same name already exists, no new - * tensor is added. - */ - void add(const char *name, const zdl::DlSystem::TensorShape& tensorShape); - - /** - * @brief Removes a mapping of tensor and its name by its name - * - * @param[in] name The name of tensor to be removed - * - * @note If no tensor with the specified name is found, nothing - * is done. - */ - void remove(const char *name) noexcept; - - /** - * @brief Returns the number of tensors in the map - */ - size_t size() const noexcept; - - /** - * @brief . - * - * Removes all tensors from the map - */ - void clear() noexcept; - - /** - * @brief Returns the tensor given its name. - * - * @param[in] name The name of the tensor to get. - * - * @return nullptr if no tensor with the specified name is - * found; otherwise, a valid pointer to the tensor. - */ - zdl::DlSystem::TensorShape getTensorShape(const char *name) const noexcept; - - /** - * @brief . - * - * Returns the names of all tensor shapes - */ - zdl::DlSystem::StringList getTensorShapeNames() const; - - ~TensorShapeMap(); -private: - void swap(const TensorShapeMap &other); - std::unique_ptr<::DlSystem::TensorShapeMapImpl> m_TensorShapeMapImpl; -}; - -} // DlSystem namespace -} // zdl namespace - -/** @} */ /* end_addtogroup c_plus_plus_apis C++ */ - -#endif // DL_SYSTEM_TENSOR_SHAPE_MAP_HPP - diff --git a/third_party/snpe/include/DlSystem/UDLContext.hpp b/third_party/snpe/include/DlSystem/UDLContext.hpp deleted file mode 100644 index 21fce1b04..000000000 --- a/third_party/snpe/include/DlSystem/UDLContext.hpp +++ /dev/null @@ -1,243 +0,0 @@ -//============================================================================== -// -// Copyright (c) 2016-2021 Qualcomm Technologies, Inc. -// All Rights Reserved. -// Confidential and Proprietary - Qualcomm Technologies, Inc. -// -//============================================================================== - -#ifndef UDL_CONTEXT_HPP -#define UDL_CONTEXT_HPP - -#include // memset -#include - -#include "ZdlExportDefine.hpp" - -namespace zdl { namespace DlSystem { - -/** - * NOTE: DEPRECATED, MAY BE REMOVED IN THE FUTURE. - * - * @brief . - * - * UDLContext holds the user defined layer context which - * consists of a layer name, layer ID, blob and blob size. - * - * An instance of UDLContext is passed as an argument to the - * UDLFactoryFunc provided by the user every time the SNPE - * runtime encounters an unknown layer descriptor. The instance - * of a UDLContext is created by the SNPE runtime and is - * consumed by the user's factory function. The user should - * obtain a copy of this class and should not assume any - * prolonged object lifetime beyond the UDLFactoryFunction. - */ -class ZDL_EXPORT UDLContext final { -public: - /** - * @brief Constructor - * - * @param[in] name name of the layer - * - * @param[in] type layer type - * - * @param[in] id identifier for the layer - * - * @param[in] id Blob/bytes as packed by the user code as part of - * the Python converter script - */ - UDLContext(const std::string& name, - const std::string& type, - int32_t id, - const std::string& blob) : - m_Name(name), m_Type(type), m_Size(blob.size()), m_Id(id) { - // FIXME not dealing with alloc error - m_Buffer = new uint8_t[m_Size]; - std::memcpy(m_Buffer, blob.data(), m_Size); - } - - /** - * @brief . - * - * Empty constructor is useful for - * creating an empty UDLContext and then run copy constructor - * from a fully initialized one. - */ - explicit UDLContext() {} - - /** - * @brief . - * - * destructor Deallocates any internal allocated memory - */ - ~UDLContext() { release(); } - - /** - * @brief . - * - * Deallocate any internally allocated memory - */ - void release() { - if (m_Buffer && m_Size) - std::memset(m_Buffer, 0, m_Size); - delete []m_Buffer; - m_Buffer = nullptr; - m_Size = 0; - } - - /** - * @brief . - * - * Copy Constructor - makes a copy from ctx - * - * @param[in] ctx Source UDLContext to copy from - */ - UDLContext(const UDLContext& ctx) : m_Name(ctx.m_Name), - m_Type(ctx.m_Type), - m_Id(ctx.m_Id) { - std::tuple cpy = ctx.getCopy(); - // current compiler does not support get - m_Buffer = std::get<0>(cpy); - m_Size = std::get<1>(cpy); - } - - /** - * @brief - * - * Assignment operator - makes a copy from ctx - * - * @param[in] ctx Source UDLContext to copy from - * - * @return this - */ - UDLContext& operator=(const UDLContext& ctx) { - UDLContext c (ctx); - this->swap(c); // non throwing swap - return *this; - } - - /** - * @brief . - * - * Move Constructor - Move internals from ctx into this - * - * @param[in] ctx Source UDLContext to move from - */ - UDLContext(UDLContext&& ctx) : - m_Name(std::move(ctx.m_Name)), - m_Type(std::move(ctx.m_Type)), - m_Buffer(ctx.m_Buffer), - m_Size(ctx.m_Size), - m_Id(ctx.m_Id) { - ctx.clear(); - } - - /** - * @brief . - * - * Assignment move - Move assignment operator from ctx - * - * @param[in] ctx Source UDLContext to move from - * - * @return this - */ - UDLContext& operator=(UDLContext&& ctx) { - m_Name = std::move(ctx.m_Name); - m_Type = std::move(ctx.m_Type); - m_Buffer = ctx.m_Buffer; - m_Size = ctx.m_Size; - m_Id = ctx.m_Id; - ctx.clear(); - return *this; - } - - /** - * @brief . - * - * Obtain the name of the layer - * - * @return const reference to the name of the layer - */ - const std::string& getName() const noexcept { return m_Name; } - - /** - * @brief . - * - * Obtain the type of the layer - * - * @return const reference to the type of the layer - */ - const std::string& getType() const noexcept { return m_Type; } - - /** - * @brief . - * - * Obtain the Id of the layer - * - * @return The id of the layer - */ - int32_t getId() const noexcept { return m_Id; } - - /** - * @brief . - * - * Obtain the size of the blob - * - * @return Size of the internal blob - */ - size_t getSize() const noexcept { return m_Size; } - - /** - * @brief . - * - * Get a const pointer to the internal blob - * - * @return Const pointer to the internal blob - */ - const uint8_t* getBlob() const noexcept { return m_Buffer; } - - /** - * @brief . - * - * Get a copy of the blob/size into a tuple - * - * @return A tuple with a pointer to a copy of the blob and a - * size - */ - std::tuple getCopy() const { - uint8_t* buf = new uint8_t[m_Size]; - // FIXME missing memcpy - std::memcpy(buf, m_Buffer, m_Size); - return std::make_tuple(buf, m_Size); - } - - /** - * @brief . - * - * Set zeros in the internals members - */ - void clear() { - m_Name.clear(); - m_Type.clear(); - m_Buffer = 0; - m_Size = 0; - m_Id = -1; - } -private: - void swap(UDLContext& c) noexcept { - std::swap(m_Name, c.m_Name); - std::swap(m_Type, c.m_Type); - std::swap(m_Id, c.m_Id); - std::swap(m_Buffer, c.m_Buffer); - std::swap(m_Size, c.m_Size); - } - std::string m_Name; // name of the layer instance - std::string m_Type; // The actual layer type - uint8_t* m_Buffer = nullptr; - size_t m_Size = 0; - int32_t m_Id = -1; -}; - -}} - -#endif /* UDL_CONTEXT_HPP */ diff --git a/third_party/snpe/include/DlSystem/UDLFunc.hpp b/third_party/snpe/include/DlSystem/UDLFunc.hpp deleted file mode 100644 index 6a95eef17..000000000 --- a/third_party/snpe/include/DlSystem/UDLFunc.hpp +++ /dev/null @@ -1,87 +0,0 @@ -//============================================================================== -// -// Copyright (c) 2015-2021 Qualcomm Technologies, Inc. -// All Rights Reserved. -// Confidential and Proprietary - Qualcomm Technologies, Inc. -// -//============================================================================== - -#ifndef _UDL_FUNC_HPP_ -#define _UDL_FUNC_HPP_ - -#include - -#include "ZdlExportDefine.hpp" -#include - -namespace zdl { - namespace DlSystem { - class UDLContext; - } -} - -namespace zdl { namespace DlSystem { -/** - * NOTE: DEPRECATED, MAY BE REMOVED IN THE FUTURE. - * - * @brief . - * - * Definition of UDLFactoyFunc, using/typedef and default FactoryFunction - * UDLBundle - a simple way to bundle func and cookie into one type - */ - - -/** - * @brief . - * - * Convenient typedef for user defined layer creation factory - * - * @param[out] void* Cookie - a user opaque data that was passed during SNPE's runtime's - * CreateInstance. SNPE's runtime is passing this back to the user. - * - * @param[out] DlSystem::UDLContext* - The specific Layer Description context what is passe - * SNPE runtime. - * - * @return IUDL* - a Concrete instance of IUDL derivative - */ -using UDLFactoryFunc = std::function; - -/** - * NOTE: DEPRECATED, MAY BE REMOVED IN THE FUTURE. - * - * @brief . - * - * default UDL factory implementation - * - * @param[out] DlSystem::UDLContext* - The specific Layer Description context what is passe - * SNPE runtime. - * - * @param[out] void* Cookie - a user opaque data that was passed during SNPE's runtime's - * CreateInstance. SNPE's runtime is passing this back to the user. - * - * @return IUDL* - nullptr to indicate SNPE's runtime that there is no specific - * implementation for UDL. When SNPE's runtime sees nullptr as a return - * value from the factory, it will halt execution if model has an unknown layer - * - */ -inline ZDL_EXPORT zdl::DlSystem::IUDL* DefaultUDLFunc(void*, const zdl::DlSystem::UDLContext*) { return nullptr; } - -/** - * NOTE: DEPRECATED, MAY BE REMOVED IN THE FUTURE. - * - * @brief . - * - * Simple struct to bundle 2 elements. - * A user defined cookie that would be returned for each - * IUDL call. The user can place anything there and the - * SNPE runtime will provide it back - */ -struct ZDL_EXPORT UDLBundle { - void *cookie = nullptr; - UDLFactoryFunc func = DefaultUDLFunc; -}; - -}} - - -#endif // _UDL_FUNC_HPP_ diff --git a/third_party/snpe/include/DlSystem/UserBufferMap.hpp b/third_party/snpe/include/DlSystem/UserBufferMap.hpp deleted file mode 100644 index a03ddfe1d..000000000 --- a/third_party/snpe/include/DlSystem/UserBufferMap.hpp +++ /dev/null @@ -1,122 +0,0 @@ -//============================================================================= -// -// Copyright (c) 2017 Qualcomm Technologies, Inc. -// All Rights Reserved. -// Confidential and Proprietary - Qualcomm Technologies, Inc. -// -//============================================================================= -#include -#include "ZdlExportDefine.hpp" -#include "StringList.hpp" - -#ifndef DL_SYSTEM_USER_BUFFER_MAP_HPP -#define DL_SYSTEM_USER_BUFFER_MAP_HPP - -namespace DlSystem -{ - // Forward declaration of UserBuffer map implementation. - class UserBufferMapImpl; -} - -namespace zdl -{ -namespace DlSystem -{ -class IUserBuffer; - -/** @addtogroup c_plus_plus_apis C++ -@{ */ - -/** - * @brief . - * - * A class representing the map of UserBuffer. - */ -class ZDL_EXPORT UserBufferMap final -{ -public: - - /** - * @brief . - * - * Creates a new empty UserBuffer map - */ - UserBufferMap(); - - /** - * copy constructor. - * @param[in] other object to copy. - */ - UserBufferMap(const UserBufferMap& other); - - /** - * assignment operator. - */ - UserBufferMap& operator=(const UserBufferMap& other); - - /** - * @brief Adds a name and the corresponding UserBuffer pointer - * to the map - * - * @param[in] name The name of the UserBuffer - * @param[in] userBuffer The pointer to the UserBuffer - * - * @note If a UserBuffer with the same name already exists, the new - * UserBuffer pointer would be updated. - */ - void add(const char *name, zdl::DlSystem::IUserBuffer *buffer); - - /** - * @brief Removes a mapping of one UserBuffer and its name by its name - * - * @param[in] name The name of UserBuffer to be removed - * - * @note If no UserBuffer with the specified name is found, nothing - * is done. - */ - void remove(const char *name) noexcept; - - /** - * @brief Returns the number of UserBuffers in the map - */ - size_t size() const noexcept; - - /** - * @brief . - * - * Removes all UserBuffers from the map - */ - void clear() noexcept; - - /** - * @brief Returns the UserBuffer given its name. - * - * @param[in] name The name of the UserBuffer to get. - * - * @return nullptr if no UserBuffer with the specified name is - * found; otherwise, a valid pointer to the UserBuffer. - */ - zdl::DlSystem::IUserBuffer* getUserBuffer(const char *name) const noexcept; - - /** - * @brief . - * - * Returns the names of all UserBuffers - * - * @return A list of UserBuffer names. - */ - zdl::DlSystem::StringList getUserBufferNames() const; - - ~UserBufferMap(); -private: - void swap(const UserBufferMap &other); - std::unique_ptr<::DlSystem::UserBufferMapImpl> m_UserBufferMapImpl; -}; -/** @} */ /* end_addtogroup c_plus_plus_apis C++ */ - -} // DlSystem namespace -} // zdl namespace - - -#endif // DL_SYSTEM_TENSOR_MAP_HPP - diff --git a/third_party/snpe/include/DlSystem/UserMemoryMap.hpp b/third_party/snpe/include/DlSystem/UserMemoryMap.hpp deleted file mode 100644 index 3685a8dda..000000000 --- a/third_party/snpe/include/DlSystem/UserMemoryMap.hpp +++ /dev/null @@ -1,129 +0,0 @@ -//============================================================================= -// -// Copyright (c) 2021 Qualcomm Technologies, Inc. -// All Rights Reserved. -// Confidential and Proprietary - Qualcomm Technologies, Inc. -// -//============================================================================= -#include -#include "ZdlExportDefine.hpp" -#include "StringList.hpp" - -#ifndef DL_SYSTEM_USER_MEMORY_MAP_HPP -#define DL_SYSTEM_USER_MEMORY_MAP_HPP - -namespace DlSystem -{ - // Forward declaration of UserMemory map implementation. - class UserMemoryMapImpl; -} - -namespace zdl -{ -namespace DlSystem -{ -class IUserBuffer; - -/** @addtogroup c_plus_plus_apis C++ -@{ */ - -/** - * @brief . - * - * A class representing the map of UserMemory. - */ -class ZDL_EXPORT UserMemoryMap final -{ -public: - - /** - * @brief . - * - * Creates a new empty UserMemory map - */ - UserMemoryMap(); - - /** - * copy constructor. - * @param[in] other object to copy. - */ - UserMemoryMap(const UserMemoryMap& other); - - /** - * assignment operator. - */ - UserMemoryMap& operator=(const UserMemoryMap& other); - - /** - * @brief Adds a name and the corresponding buffer address - * to the map - * - * @param[in] name The name of the UserMemory - * @param[in] address The pointer to the Buffer Memory - * - * @note If a UserBuffer with the same name already exists, the new - * address would be updated. - */ - void add(const char *name, void *address); - - /** - * @brief Removes a mapping of one Buffer address and its name by its name - * - * @param[in] name The name of Memory address to be removed - * - * @note If no UserBuffer with the specified name is found, nothing - * is done. - */ - void remove(const char *name) noexcept; - - /** - * @brief Returns the number of User Memory addresses in the map - */ - size_t size() const noexcept; - - /** - * @brief . - * - * Removes all User Memory from the map - */ - void clear() noexcept; - - /** - * @brief . - * - * Returns the names of all User Memory - * - * @return A list of Buffer names. - */ - zdl::DlSystem::StringList getUserBufferNames() const; - - /** - * @brief Returns the no of UserMemory addresses mapped to the buffer - * - * @param[in] name The name of the UserMemory - * - */ - size_t getUserMemoryAddressCount(const char *name) const noexcept; - - /** - * @brief Returns address at a specified index corresponding to a UserMemory buffer name - * - * @param[in] name The name of the buffer - * @param[in] index The index in the list of addresses - * - */ - void* getUserMemoryAddressAtIndex(const char *name, uint32_t index) const noexcept; - - ~UserMemoryMap(); -private: - void swap(const UserMemoryMap &other); - std::unique_ptr<::DlSystem::UserMemoryMapImpl> m_UserMemoryMapImpl; -}; -/** @} */ /* end_addtogroup c_plus_plus_apis C++ */ - -} // DlSystem namespace -} // zdl namespace - - -#endif // DL_SYSTEM_TENSOR_MAP_HPP - diff --git a/third_party/snpe/include/DlSystem/ZdlExportDefine.hpp b/third_party/snpe/include/DlSystem/ZdlExportDefine.hpp deleted file mode 100644 index 92eb786d3..000000000 --- a/third_party/snpe/include/DlSystem/ZdlExportDefine.hpp +++ /dev/null @@ -1,13 +0,0 @@ -//============================================================================= -// -// Copyright (c) 2015, 2020 Qualcomm Technologies, Inc. -// All Rights Reserved. -// Confidential and Proprietary - Qualcomm Technologies, Inc. -// -//============================================================================= - -#pragma once - -#ifndef ZDL_EXPORT -#define ZDL_EXPORT -#endif diff --git a/third_party/snpe/include/PlatformValidator/PlatformValidator.hpp b/third_party/snpe/include/PlatformValidator/PlatformValidator.hpp deleted file mode 100644 index 66097ba56..000000000 --- a/third_party/snpe/include/PlatformValidator/PlatformValidator.hpp +++ /dev/null @@ -1,118 +0,0 @@ -// ============================================================================= -// -// Copyright (c) 2018-2020 Qualcomm Technologies, Inc. -// All Rights Reserved. -// Confidential and Proprietary - Qualcomm Technologies, Inc. -// -// ============================================================================= - -#ifndef SNPE_PLATFORMVALIDATOR_HPP -#define SNPE_PLATFORMVALIDATOR_HPP - -#include "DlSystem/DlEnums.hpp" -#include "DlSystem/ZdlExportDefine.hpp" - -#define DO_PRAGMA(s) _Pragma(#s) -#define NO_WARNING "-Wunused-variable" - -#ifdef __clang__ -#define SNPE_DISABLE_WARNINGS(clang_warning,gcc_warning) \ -_Pragma("clang diagnostic push") \ -DO_PRAGMA(clang diagnostic ignored clang_warning) - -#define SNPE_ENABLE_WARNINGS \ -_Pragma("clang diagnostic pop") - -#elif defined __GNUC__ -#define SNPE_DISABLE_WARNINGS(clang_warning,gcc_warning) \ -_Pragma("GCC diagnostic push") \ -DO_PRAGMA(GCC diagnostic ignored gcc_warning) - -#define SNPE_ENABLE_WARNINGS \ -_Pragma("GCC diagnostic pop") - -#else -#define SNPE_DISABLE_WARNINGS(...) -#define SNPE_ENABLE_WARNINGS -#endif - -SNPE_DISABLE_WARNINGS("-Wdelete-non-virtual-dtor","-Wdelete-non-virtual-dtor") -#include -#include -SNPE_ENABLE_WARNINGS - -namespace zdl -{ - namespace SNPE - { - class PlatformValidator; - - class IPlatformValidatorRuntime; - } -} - -/** @addtogroup c_plus_plus_apis C++ -@{ */ - -/** -* The class for checking SNPE compatibility/capability of a device. -* -*/ - -class ZDL_EXPORT zdl::SNPE::PlatformValidator -{ -public: - /** - * @brief Default Constructor of the PlatformValidator Class - * - * @return A new instance of a PlatformValidator object - * that can be used to check the SNPE compatibility - * of a device - */ - PlatformValidator(); - - ~PlatformValidator(); - - /** - * @brief Sets the runtime processor for compatibility check - * - * @return Void - */ - void setRuntime(zdl::DlSystem::Runtime_t runtime); - - /** - * @brief Checks if the Runtime prerequisites for SNPE are available. - * - * @return True if the Runtime prerequisites are available, else false. - */ - bool isRuntimeAvailable(); - - /** - * @brief Returns the core version for the Runtime selected. - * - * @return String which contains the actual core version value - */ - std::string getCoreVersion(); - - /** - * @brief Returns the library version for the Runtime selected. - * - * @return String which contains the actual lib version value - */ - std::string getLibVersion(); - - /** - * @brief Runs a small program on the runtime and Checks if SNPE is supported for Runtime. - * - * @return If True, the device is ready for SNPE execution, else not. - */ - - bool runtimeCheck(); - -private: - zdl::DlSystem::Runtime_t m_runtimeType; - std::unique_ptr m_platformValidatorRuntime; -}; -/** @} */ /* end_addtogroup c_plus_plus_apis C++ */ - -#endif //SNPE_PLATFORMVALIDATOR_HPP diff --git a/third_party/snpe/include/SNPE/ApplicationBufferMap.hpp b/third_party/snpe/include/SNPE/ApplicationBufferMap.hpp deleted file mode 100644 index 380a3e0e6..000000000 --- a/third_party/snpe/include/SNPE/ApplicationBufferMap.hpp +++ /dev/null @@ -1,101 +0,0 @@ -//============================================================================== -// -// Copyright (c) 2019 Qualcomm Technologies, Inc. -// All Rights Reserved. -// Confidential and Proprietary - Qualcomm Technologies, Inc. -// -//============================================================================== - -#ifndef PSNPE_APPLICATIONBUFFERMAP_HPP -#define PSNPE_APPLICATIONBUFFERMAP_HPP -#include -#include -#include - -#include "DlSystem/UserBufferMap.hpp" -#include "DlSystem/ZdlExportDefine.hpp" - -namespace zdl -{ -namespace PSNPE -{ -/** @addtogroup c_plus_plus_apis C++ -@{ */ - -/** - * @brief . - * - * A class representing the UserBufferMap of Input and Output asynchronous mode. - */ - -class ZDL_EXPORT ApplicationBufferMap final -{ - - public: - /** - * @brief Adds a name and the corresponding buffer - * to the map - * - * @param[in] name The name of the UserBuffer - * @param[in] buffer The vector of the uint8_t data - * - * @note If a UserBuffer with the same name already exists, the new - * UserBuffer pointer would be updated. - */ - void add(const char* name, std::vector& buff) noexcept; - void add(const char* name, std::vector& buff) noexcept; - /** - * @brief Removes a mapping of one UserBuffer and its name by its name - * - * @param[in] name The name of UserBuffer to be removed - * - * @note If no UserBuffer with the specified name is found, nothing - * is done. - */ - void remove(const char* name) noexcept; - - /** - * @brief Returns the number of UserBuffers in the map - */ - size_t size() const noexcept; - - /** - * @brief . - * - * Removes all UserBuffers from the map - */ - void clear() noexcept; - - /** - * @brief Returns the UserBuffer given its name. - * - * @param[in] name The name of the UserBuffer to get. - * - * @return nullptr if no UserBuffer with the specified name is - * found; otherwise, a valid pointer to the UserBuffer. - */ - const std::vector& getUserBuffer(const char* name) const; - const std::vector& operator[](const char* name) const; - /** - * @brief . - * - * Returns the names of all UserAsyncBufferMap - * - * @return A list of UserBuffer names. - */ - zdl::DlSystem::StringList getUserBufferNames() const; - const std::unordered_map>& getUserBuffer() const; - explicit ApplicationBufferMap(); - ~ApplicationBufferMap(); - explicit ApplicationBufferMap( - const std::unordered_map> buffer); - - private: - std::unordered_map> m_UserMap; -}; - -/** @} */ /* end_addtogroup c_plus_plus_apis C++ */ -} // namespace PSNPE -} // namespace zdl - -#endif // PSNPE_APPLICATIONBUFFERMAP_HPP diff --git a/third_party/snpe/include/SNPE/PSNPE.hpp b/third_party/snpe/include/SNPE/PSNPE.hpp deleted file mode 100644 index a823c0c7f..000000000 --- a/third_party/snpe/include/SNPE/PSNPE.hpp +++ /dev/null @@ -1,205 +0,0 @@ -// ============================================================================= -// -// Copyright (c) 2019-2021 Qualcomm Technologies, Inc. -// All Rights Reserved. -// Confidential and Proprietary - Qualcomm Technologies, Inc. -// -// ============================================================================= - -#ifndef PSNPE_HPP -#define PSNPE_HPP - -#include -#include -#include "SNPE/SNPE.hpp" -#include "DlSystem/UserBufferMap.hpp" -#include "DlContainer/IDlContainer.hpp" -#include "DlSystem/DlEnums.hpp" -#include "DlSystem/ZdlExportDefine.hpp" - -#include "UserBufferList.hpp" -#include "RuntimeConfigList.hpp" -#include "ApplicationBufferMap.hpp" - -namespace zdl -{ -namespace PSNPE -{ - -/** @addtogroup c_plus_plus_apis C++ -@{ */ - -/** - *@ brief build snpe instance in serial or parallel - * - */ -enum ZDL_EXPORT BuildMode { - SERIAL = 0, - PARALLEL = 1 -}; -/** - * @brief Input and output transmission mode - */ -enum ZDL_EXPORT InputOutputTransmissionMode -{ - sync = 0, - outputAsync = 1, - inputOutputAsync = 2 -}; - -/** - * @brief A structure representing parameters of callback function of Async Output mode - */ -struct ZDL_EXPORT OutputAsyncCallbackParam -{ - size_t dataIndex; - bool executeStatus; - std::string errorMsg; - OutputAsyncCallbackParam(size_t _index,bool _status, const std::string& _errorMsg = std::string()) - : dataIndex(_index),executeStatus(_status), errorMsg(_errorMsg){}; -}; -/** - * @brief A structure representing parameters of callback function of Async Input/Output mode - */ -struct ZDL_EXPORT InputOutputAsyncCallbackParam -{ - size_t dataIndex; - const ApplicationBufferMap& outputMap; - bool executeStatus; - std::string errorMsg; - InputOutputAsyncCallbackParam(size_t _index, const ApplicationBufferMap& output_map,bool _status, - const std::string _ErrorMsg = std::string()) - : dataIndex(_index) - , outputMap(output_map) - , executeStatus(_status) - , errorMsg(_ErrorMsg){}; -}; -/** - * @brief This callback is called when the output data is ready, only use for Output Async mode - */ -using OutputAsyncCallbackFunc = std::function; -/** - * @brief This callback is called when the output data is ready, only use for Output-Input Async mode - */ -using InputOutputAsyncCallbackFunc = std::function; -/** - * @brief This callback is called when the input data is ready,only use for Output-Input Async mode - */ -using InputOutputAsyncInputCallback = std::function(const std::vector &, - const zdl::DlSystem::StringList &)>; -/** - * @brief . - * - * A structure PSNPE configuration - * - */ -struct ZDL_EXPORT BuildConfig final -{ - BuildMode buildMode = BuildMode::SERIAL; ///< Specify build in serial mode or parallel mode - zdl::DlContainer::IDlContainer* container;///< The opened container ptr - zdl::DlSystem::StringList outputBufferNames;///< Specify the output layer name - zdl::DlSystem::StringList outputTensors;///< Specify the output layer name - RuntimeConfigList runtimeConfigList;///< The runtime config list for PSNPE, @see RuntimeConfig - size_t inputThreadNumbers = 1;///< Specify the number of threads used in the execution phase to process input data, only used in inputOutputAsync mode - size_t outputThreadNumbers = 1;///< Specify the number of threads used in the execution phase to process output data, only used in inputOutputAsync and outputAsync mode - OutputAsyncCallbackFunc outputCallback;///< The callback to deal with output data ,only used in outputAsync mode - InputOutputAsyncCallbackFunc inputOutputCallback;///< The callback to deal with output data ,only used in inputOutputAsync mode - InputOutputAsyncInputCallback inputOutputInputCallback;///< The callback to deal with input data ,only used in inputOutputAsync mode - InputOutputTransmissionMode inputOutputTransmissionMode = InputOutputTransmissionMode::sync;///< Specify execution mode - zdl::DlSystem::ProfilingLevel_t profilingLevel = zdl::DlSystem::ProfilingLevel_t::OFF;///< Specify profiling level for Diaglog - uint64_t encode[2] = {0, 0}; - bool enableInitCache = false; - std::string platformOptions; - std::string diaglogOutputDir = "./diaglogs/"; ///< Specify a diaglog output directory to save the generated Diaglog files. -}; -/** - * @brief . - * - * The class for executing SNPE instances in parallel. - */ -class ZDL_EXPORT PSNPE final -{ - public: - ~PSNPE(); - - explicit PSNPE() noexcept :m_TransmissionMode(InputOutputTransmissionMode::sync){}; - - /** - * @brief Build snpe instances. - * - */ - bool build(BuildConfig& buildConfig) noexcept; - - /** - * @brief Execute snpe instances in Async Output mode and Sync mode - * - * @param[in] inputBufferList A list of user buffers that contains the input data - * - * @param[in,out] outputBufferList A list of user buffers that will hold the output data - * - */ - bool execute(UserBufferList& inputBufferList, UserBufferList& outputBufferList) noexcept; - - /** - * @brief Execute snpe instances in Async Input/Output mode - * - * @param[in]inputMap A map of input buffers that contains input data. The names of buffers - * need to be matched with names retrived through getInputTensorNames() - * - * @param dataIndex Index of the input data - * - * @param isTF8buff Whether prefer to using 8 bit quantized element for inference - * - * @return True if executed successfully; flase, otherwise. - */ - bool executeInputOutputAsync(const std::vector& inputMap, size_t dataIndex, bool isTF8buff) noexcept; - bool executeInputOutputAsync(const std::vector& inputMap, size_t dataIndex, bool isTF8buff,bool isTF8Outputbuff) noexcept; - /** - * @brief Returns the input layer names of the network. - * - * @return StringList which contains the input layer names - */ - const zdl::DlSystem::StringList getInputTensorNames() const noexcept; - - /** - * @brief Returns the output layer names of the network. - * - * @return StringList which contains the output layer names - */ - const zdl::DlSystem::StringList getOutputTensorNames() const noexcept; - - /** - * @brief Returns the input tensor dimensions of the network. - * - * @return TensorShape which contains the dimensions. - */ - const zdl::DlSystem::TensorShape getInputDimensions() const noexcept; - - const zdl::DlSystem::TensorShape getInputDimensions(const char *name) const noexcept; - - /** - * @brief Returns attributes of buffers. - * - * @see zdl::SNPE - * - * @return BufferAttributes of input/output tensor named. - */ - const zdl::DlSystem::TensorShape getBufferAttributesDims(const char *name) const noexcept; - - zdl::DlSystem::Optional getInputOutputBufferAttributes(const char *name) const noexcept; - bool registerIonBuffers(const zdl::DlSystem::UserMemoryMap& ionBufferMap) const noexcept; - bool deregisterIonBuffers(const zdl::DlSystem::StringList& ionBufferNames) const noexcept; - - const char* getLastErrorString(); - - private: - PSNPE(const PSNPE&) = delete; - PSNPE& operator=(const PSNPE&) = delete; - zdl::PSNPE::InputOutputTransmissionMode m_TransmissionMode; - -}; - -/** @} */ /* end_addtogroup c_plus_plus_apis C++ */ -} // namespace PSNPE -} // namespace zdl -#endif // PSNPE_HPP diff --git a/third_party/snpe/include/SNPE/RuntimeConfigList.hpp b/third_party/snpe/include/SNPE/RuntimeConfigList.hpp deleted file mode 100644 index 837dba092..000000000 --- a/third_party/snpe/include/SNPE/RuntimeConfigList.hpp +++ /dev/null @@ -1,85 +0,0 @@ -//============================================================================== -// -// Copyright (c) 2019-2020 Qualcomm Technologies, Inc. -// All Rights Reserved. -// Confidential and Proprietary - Qualcomm Technologies, Inc. -// -//============================================================================== -#ifndef PSNPE_RUNTIMECONFIGLIST_HPP -#define PSNPE_RUNTIMECONFIGLIST_HPP - -#include -#include "DlContainer/IDlContainer.hpp" -#include "DlSystem/DlEnums.hpp" -#include "DlSystem/RuntimeList.hpp" -#include "DlSystem/TensorShapeMap.hpp" -#include "DlSystem/ZdlExportDefine.hpp" -namespace zdl { -namespace PSNPE { - -/** @addtogroup c_plus_plus_apis C++ -@{ */ - -/** - * @brief . - * - * The structure for configuring a BulkSNPE runtime - * - */ -struct ZDL_EXPORT RuntimeConfig final { - zdl::DlSystem::Runtime_t runtime; - zdl::DlSystem::RuntimeList runtimeList; - zdl::DlSystem::PerformanceProfile_t perfProfile; - zdl::DlSystem::TensorShapeMap inputDimensionsMap; - bool enableCPUFallback; - RuntimeConfig() - : runtime{zdl::DlSystem::Runtime_t::CPU_FLOAT32}, - perfProfile{zdl::DlSystem::PerformanceProfile_t::HIGH_PERFORMANCE}, - enableCPUFallback{false} {} - RuntimeConfig(const RuntimeConfig& other) { - runtime = other.runtime; - runtimeList = other.runtimeList; - perfProfile = other.perfProfile; - enableCPUFallback = other.enableCPUFallback; - inputDimensionsMap = other.inputDimensionsMap; - } - - RuntimeConfig& operator=(const RuntimeConfig& other) { - this->runtimeList = other.runtimeList; - this->runtime = other.runtime; - this->perfProfile = other.perfProfile; - this->enableCPUFallback = other.enableCPUFallback; - this->inputDimensionsMap = other.inputDimensionsMap; - return *this; - } - - ~RuntimeConfig() {} -}; - -/** - * @brief . - * - * The class for creating a RuntimeConfig container. - * - */ -class ZDL_EXPORT RuntimeConfigList final { - public: - RuntimeConfigList(); - RuntimeConfigList(const size_t size); - void push_back(const RuntimeConfig& runtimeConfig); - RuntimeConfig& operator[](const size_t index); - RuntimeConfigList& operator=(const RuntimeConfigList& other); - size_t size() const noexcept; - size_t capacity() const noexcept; - void clear() noexcept; - ~RuntimeConfigList() = default; - - private: - void swap(const RuntimeConfigList& other); - std::vector m_runtimeConfigs; -}; -/** @} */ /* end_addtogroup c_plus_plus_apis C++ */ - -} // namespace PSNPE -} // namespace zdl -#endif // PSNPE_RUNTIMECONFIGLIST_HPP diff --git a/third_party/snpe/include/SNPE/SNPE.hpp b/third_party/snpe/include/SNPE/SNPE.hpp deleted file mode 100644 index 5ab45b82e..000000000 --- a/third_party/snpe/include/SNPE/SNPE.hpp +++ /dev/null @@ -1,258 +0,0 @@ -//============================================================================== -// -// Copyright (c) 2015-2021 Qualcomm Technologies, Inc. -// All Rights Reserved. -// Confidential and Proprietary - Qualcomm Technologies, Inc. -// -//============================================================================== - -#ifndef _SNPE_SNPE_HPP_ -#define _SNPE_SNPE_HPP_ - -#include "DlSystem/DlOptional.hpp" -#include "DlSystem/DlVersion.hpp" -#include "DlSystem/IBufferAttributes.hpp" -#include "DlSystem/ITensor.hpp" -#include "DlSystem/TensorShape.hpp" -#include "DlSystem/TensorMap.hpp" -#include "DlSystem/String.hpp" -#include "DlSystem/StringList.hpp" -#include "DlSystem/IUserBuffer.hpp" -#include "DlSystem/UserBufferMap.hpp" -#include "DlSystem/UserMemoryMap.hpp" -#include "DlSystem/ZdlExportDefine.hpp" - -namespace zdl { - namespace SNPE - { - class SnpeRuntime; - } -} -namespace zdl { - namespace DiagLog - { - class IDiagLog; - } -} - -namespace zdl { namespace SNPE { -/** @addtogroup c_plus_plus_apis C++ -@{ */ - -/** - * @brief . - * - * The SNPE interface class definition - */ -class ZDL_EXPORT SNPE final -{ -public: - - // keep this undocumented to be hidden in doxygen using HIDE_UNDOC_MEMBERS - explicit SNPE(std::unique_ptr&& runtime) noexcept; - ~SNPE(); - - /** - * @brief Gets the names of input tensors to the network - * - * To support multiple input scenarios, where multiple tensors are - * passed through execute() in a TensorMap, each tensor needs to - * be uniquely named. The names of tensors can be retrieved - * through this function. - * - * In the case of a single input, one name will be returned. - * - * @note Note that because the returned value is an Optional list, - * the list must be verified as boolean true value before being - * dereferenced. - * - * @return An Optional List of input tensor names. - * - * @see zdl::DlSystem::Optional - */ - zdl::DlSystem::Optional - getInputTensorNames() const noexcept; - - /** - * @brief Gets the names of output tensors to the network - * - * @return List of output tensor names. - */ - zdl::DlSystem::Optional - getOutputTensorNames() const noexcept; - - /** - * @brief Gets the name of output tensor from the input layer name - * - * @return Output tensor name. - */ - zdl::DlSystem::StringList - getOutputTensorNamesByLayerName(const char *name) const noexcept; - - /** - * @brief Processes the input data and returns the output - * - * @param[in] A map of tensors that contains the input data for - * each input. The names of tensors needs to be - * matched with names retrieved through - * getInputTensorNames() - * - * @param[in,out] An empty map of tensors that will contain the output - * data of potentially multiple layers (the key - * in the map is the layer name) upon return - * - * @note output tensormap has to be empty. To forward propagate - * and get results in user-supplied tensors, use - * executeWithSuppliedOutputTensors. - */ - bool execute(const zdl::DlSystem::TensorMap &input, - zdl::DlSystem::TensorMap &output) noexcept; - - /** - * @brief Processes the input data and returns the output - * - * @param[in] A single tensor contains the input data. - * - * @param[in,out] An empty map of tensors that will contain the output - * data of potentially multiple layers (the key - * in the map is the layer name) upon return - * - * @note output tensormap has to be empty. - */ - bool execute(const zdl::DlSystem::ITensor *input, - zdl::DlSystem::TensorMap &output) noexcept; - - /** - * @brief Processes the input data and returns the output, using - * user-supplied buffers - * - * @param[in] A map of UserBuffers that contains the input data for - * each input. The names of UserBuffers needs to be - * matched with names retrieved through - * getInputTensorNames() - * - * @param[in,out] A map of UserBuffers that will hold the output - * data of potentially multiple layers (the key - * in the map is the UserBuffer name) - * - * @note input and output UserBuffer maps must be fully pre-populated. with - * dimensions matching what the network expects. - * For example, if there are 5 output UserBuffers they all have to be - * present in map. - * - * Caller must guarantee that for the duration of execute(), the buffer - * stored in UserBuffer would remain valid. For more detail on buffer - * ownership and lifetime requirements, please refer to zdl::DlSystem::UserBuffer - * documentation. - */ - bool execute(const zdl::DlSystem::UserBufferMap &input, - const zdl::DlSystem::UserBufferMap &output) noexcept; - - - /** - * @brief Regiter Client ION Buffers - * @param[in] A UserMemoryMap of virtual addresses - * - */ - bool registerIonBuffers(const zdl::DlSystem::UserMemoryMap& ionBufferMap) noexcept; - - /** - * @brief Regiter Client ION Buffers - * @param[in] A StringList of ION Buffer names - * - */ - bool deregisterIonBuffers(const zdl::DlSystem::StringList& ionBufferNames) noexcept; - - /** - * @brief Returns the version string embedded at model conversion - * time. - * - * @return Model version string, which is a free-form string - * supplied at the time of the conversion - * - */ - zdl::DlSystem::String getModelVersion() const noexcept; - - /** - * @brief Returns the dimensions of the input data to the model in the - * form of TensorShape. The dimensions in TensorShape corresponds to - * what the tensor dimensions would need to be for an input tensor to - * the model. - * - * @param[in] layer input name. - * - * @note Note that this function only makes sense for networks - * that have a fixed input size. For networks in which the - * input size varies with each call of Execute(), this - * function should not be used. - * - * @note Because the returned type is an Optional instance, it must - * be verified as a boolean true value before being dereferenced. - * - * @return An Optional instance of TensorShape that maintains dimensions, - * matching the tensor dimensions for input to the model, - * where the last entry is the fastest varying dimension, etc. - * - * @see zdl::DlSystem::ITensor - * @see zdl::DlSystem::TensorShape - * @see zdl::DlSystem::Optional - */ - zdl::DlSystem::Optional - getInputDimensions() const noexcept; - zdl::DlSystem::Optional - getInputDimensions(const char *name) const noexcept; - - /** - * @brief Gets the output layer(s) for the network. - * - * Note that the output layers returned by this function may be - * different than those specified when the network was created - * via the zdl::SNPE::SNPEBuilder. For example, if the - * network was created in debug mode with no explicit output - * layers specified, this will contain all layers. - * - * @note Note that because the returned value is an Optional StringList, - * the list must be verified as a boolean true value before being - * dereferenced. - * - * @return A List of output layer names. - * - * @see zdl::DlSystem::Optional - */ - zdl::DlSystem::Optional - getOutputLayerNames() const noexcept; - - /** - * @brief Returns attributes of buffers used to feed input tensors and receive result from output tensors. - * - * @param[in] Tensor name. - * - * @return BufferAttributes of input/output tensor named - */ - zdl::DlSystem::Optional getInputOutputBufferAttributes(const char *name) const noexcept; - - /** - * @brief . - * - * Get the diagnostic logging interface - * - * @note Note that because the returned type is an Optional instance, - * it must be verified as a boolean true value before being - * dereferenced. - * - * @see zdl::DlSystem::Optional - */ - zdl::DlSystem::Optional - getDiagLogInterface() noexcept; - -private: - SNPE(const SNPE&) = delete; - SNPE& operator=(const SNPE&) = delete; - - std::unique_ptr m_Runtime; -}; - -/** @} */ /* end_addtogroup c_plus_plus_apis C++ */ -}} - -#endif diff --git a/third_party/snpe/include/SNPE/SNPEBuilder.hpp b/third_party/snpe/include/SNPE/SNPEBuilder.hpp deleted file mode 100644 index 5fc7846ce..000000000 --- a/third_party/snpe/include/SNPE/SNPEBuilder.hpp +++ /dev/null @@ -1,306 +0,0 @@ -//============================================================================== -// -// Copyright (c) 2017-2021 Qualcomm Technologies, Inc. -// All Rights Reserved. -// Confidential and Proprietary - Qualcomm Technologies, Inc. -// -//============================================================================== - -#ifndef _SNPE_BUILDER_HPP_ -#define _SNPE_BUILDER_HPP_ - -#include "SNPE/SNPE.hpp" -#include "DlSystem/DlEnums.hpp" -#include "DlSystem/UDLFunc.hpp" -#include "DlSystem/DlOptional.hpp" -#include "DlSystem/TensorShapeMap.hpp" -#include "DlSystem/PlatformConfig.hpp" -#include "DlSystem/IOBufferDataTypeMap.hpp" -#include "DlSystem/RuntimeList.hpp" - -namespace zdl { - namespace DlContainer - { - class IDlContainer; - } -} - -struct SNPEBuilderImpl; - - -namespace zdl { namespace SNPE { -/** @addtogroup c_plus_plus_apis C++ -@{ */ - -/** - * The builder class for creating SNPE objects. - * Not meant to be extended. - */ -class ZDL_EXPORT SNPEBuilder final -{ -private: - std::unique_ptr<::SNPEBuilderImpl> m_Impl; -public: - - /** - * @brief Constructor of NeuralNetwork Builder with a supplied model. - * - * @param[in] container A container holding the model. - * - * @return A new instance of a SNPEBuilder object - * that can be used to configure and build - * an instance of SNPE. - * - */ - explicit SNPEBuilder( - zdl::DlContainer::IDlContainer* container); - ~SNPEBuilder(); - - /** - * NOTE: DEPRECATED, MAY BE REMOVED IN THE FUTURE. Please use - * setRuntimeProcessorOrder() - * - * @brief Sets the runtime processor. - * - * @param[in] targetRuntimeProcessor The target runtime. - * - * @return The current instance of SNPEBuilder. - */ - SNPEBuilder& setRuntimeProcessor( - zdl::DlSystem::Runtime_t targetRuntimeProcessor); - - /** - * @brief Requests a performance profile. - * - * @param[in] targetRuntimeProfile The target performance profile. - * - * @return The current instance of SNPEBuilder. - */ - SNPEBuilder& setPerformanceProfile( - zdl::DlSystem::PerformanceProfile_t performanceProfile); - - /** - * @brief Sets the profiling level. Default profiling level for - * SNPEBuilder is off. Off and basic only applies to DSP runtime. - * - * @param[in] profilingLevel The target profiling level. - * - * @return The current instance of SNPEBuilder. - */ - SNPEBuilder& setProfilingLevel( - zdl::DlSystem::ProfilingLevel_t profilingLevel); - - /** - * @brief Sets a preference for execution priority. - * - * This allows the caller to give coarse hint to SNPE runtime - * about the priority of the network. SNPE runtime is free to use - * this information to co-ordinate between different workloads - * that may or may not extend beyond SNPE. - * - * @param[in] ExecutionPriorityHint_t The target performance profile. - * - * @return The current instance of SNPEBuilder. - */ - SNPEBuilder& setExecutionPriorityHint( - zdl::DlSystem::ExecutionPriorityHint_t priority); - - /** - * @brief Sets the layers that will generate output. - * - * @param[in] outputLayerNames List of layer names to - * output. An empty list will - * result in only the final - * layer of the model being - * the output layer. The list - * will be copied. - * - * @return The current instance of SNPEBuilder. - */ - SNPEBuilder& setOutputLayers( - const zdl::DlSystem::StringList& outputLayerNames); - - /** - * @brief Sets the output tensor names. - * - * @param[in] outputTensorNames List of tensor names to - * output. An empty list will - * result in producing output for the final - * output tensor of the model. - * The list will be copied. - * - * @return The current instance of SNPEBuilder. - */ - SNPEBuilder& setOutputTensors( - const zdl::DlSystem::StringList& outputTensorNames); - - /** - * @brief Passes in a User-defined layer. - * - * @param udlBundle Bundle of udl factory function and a cookie - * - * @return The current instance of SNPEBuilder. - */ - SNPEBuilder& setUdlBundle( - zdl::DlSystem::UDLBundle udlBundle); - - /** - * @brief Sets whether this neural network will perform inference with - * input from user-supplied buffers, and write output to user-supplied - * buffers. Default behaviour is to use tensors created by - * ITensorFactory. - * - * @param[in] bufferMode Whether to use user-supplied buffer or not. - * - * @return The current instance of SNPEBuilder. - */ - SNPEBuilder& setUseUserSuppliedBuffers( - bool bufferMode); - - /** - * @brief Sets the debug mode of the runtime. - * - * @param[in] debugMode This enables debug mode for the runtime. It - * does two things. For an empty - * outputLayerNames list, all layers will be - * output. It might also disable some internal - * runtime optimizations (e.g., some networks - * might be optimized by combining layers, - * etc.). - * - * @return The current instance of SNPEBuilder. - */ - SNPEBuilder& setDebugMode( - bool debugMode); - - /** - * NOTE: DEPRECATED, MAY BE REMOVED IN THE FUTURE. Please use - * setRuntimeProcessorOrder() - * - * @brief Sets the mode of CPU fallback functionality. - * - * @param[in] mode This flag enables/disables the functionality - * of CPU fallback. When the CPU fallback - * functionality is enabled, layers in model that - * violates runtime constraints will run on CPU - * while the rest of non-violating layers will - * run on the chosen runtime processor. In - * disabled mode, models with layers violating - * runtime constraints will NOT run on the chosen - * runtime processor and will result in runtime - * exception. By default, the functionality is - * enabled. - * - * @return The current instance of SNPEBuilder. - */ - SNPEBuilder& setCPUFallbackMode( - bool mode); - - - /** - * @brief Sets network's input dimensions to enable resizing of - * the spatial dimensions of each layer for fully convolutional networks, - * and the batch dimension for all networks. - * - * @param[in] tensorShapeMap The map of input names and their new dimensions. - * The new dimensions overwrite the input dimensions - * embedded in the model and then resize each layer - * of the model. If the model contains - * layers whose dimensions cannot be resized e.g FullyConnected, - * exception will be thrown when SNPE instance is actually built. - * In general the batch dimension is always resizable. - * After resizing of layers' dimensions in model based - * on new input dimensions, the new model is revalidated - * against all runtime constraints, whose failures may - * result in cpu fallback situation. - * - * @return The current instance of SNPEBuilder. - */ - SNPEBuilder& setInputDimensions(const zdl::DlSystem::TensorShapeMap& inputDimensionsMap); - - /** - * @brief Sets the mode of init caching functionality. - * - * @param[in] mode This flag enables/disables the functionality of init caching. - * When init caching functionality is enabled, a set of init caches - * will be created during network building/initialization process - * and will be added to DLC container. If such DLC container is saved - * by the user, in subsequent network building/initialization processes - * these init caches will be loaded from the DLC so as to reduce initialization time. - * In disable mode, no init caches will be added to DLC container. - * - * @return The current instance of SNPEBuilder. - */ - SNPEBuilder& setInitCacheMode( - bool cacheMode); - - /** - * @brief Returns an instance of SNPE based on the current parameters. - * - * @return A new instance of a SNPE object that can be used - * to execute models or null if any errors occur. - */ - std::unique_ptr build() noexcept; - - /** - * @brief Sets the platform configuration. - * - * @param[in] platformConfig The platform configuration. - * - * @return The current instance of SNPEBuilder. - */ - SNPEBuilder& setPlatformConfig(const zdl::DlSystem::PlatformConfig& platformConfig); - - /** - * @brief Sets network's runtime order of precedence. Example: - * CPU_FLOAT32, GPU_FLOAT16, AIP_FIXED8_TF - * Note:- setRuntimeProcessor() or setCPUFallbackMode() will be silently ignored when - * setRuntimeProcessorOrder() is invoked - * - * @param[in] runtimeList The list of runtime in order of precedence - * - * @return The current instance of SNPEBuilder. - */ - SNPEBuilder& setRuntimeProcessorOrder(const zdl::DlSystem::RuntimeList& runtimeList); - - /** - * @brief Sets the unconsumed tensors as output - * - * @param[in] setOutput This enables unconsumed tensors (i.e) - * outputs which are not inputs to any - * layer (basically dead ends) to be marked - * for output - * - * @return The current instance of SNPEBuilder. - */ - SNPEBuilder& setUnconsumedTensorsAsOutputs( - bool setOutput); - - /** - * @brief Execution terminated when exceeding time limit. - * Only valid for dsp runtime currently. - * - * @param[in] timeout Time limit value - * - * @return The current instance of SNPEBuilder. - */ - SNPEBuilder& setTimeOut( - uint64_t timeout); - - - /** - * @brief Sets the datatype of the buffer. - * Only valid for dsp runtime currently. - * - * @param[in] Map of the buffer names and the datatype that needs to be set. - * - * @return The current instance of SNPEBuilder. - */ - SNPEBuilder& setBufferDataType(const zdl::DlSystem::IOBufferDataTypeMap& dataTypeMap); - -}; -/** @} */ /* end_addtogroup c_plus_plus_apis C++ */ - -}} - -#endif diff --git a/third_party/snpe/include/SNPE/SNPEFactory.hpp b/third_party/snpe/include/SNPE/SNPEFactory.hpp deleted file mode 100644 index 2d78e81b1..000000000 --- a/third_party/snpe/include/SNPE/SNPEFactory.hpp +++ /dev/null @@ -1,220 +0,0 @@ -//============================================================================== -// -// Copyright (c) 2015-2021 Qualcomm Technologies, Inc. -// All Rights Reserved. -// Confidential and Proprietary - Qualcomm Technologies, Inc. -// -//============================================================================== - -#ifndef _SNPE_FACTORY_HPP_ -#define _SNPE_FACTORY_HPP_ - -#include "SNPE/SNPE.hpp" -#include "DlSystem/DlEnums.hpp" -#include "DlSystem/UDLFunc.hpp" -#include "DlSystem/ZdlExportDefine.hpp" -#include "DlSystem/DlOptional.hpp" - -namespace zdl { - namespace DlSystem - { - class ITensorFactory; - class IUserBufferFactory; - } - namespace DlContainer - { - class IDlContainer; - } -} - - - -namespace zdl { namespace SNPE { -/** @addtogroup c_plus_plus_apis C++ -@{ */ - -/** - * The factory class for creating SNPE objects. - * - */ -class ZDL_EXPORT SNPEFactory -{ -public: - - /** - * Indicates whether the supplied runtime is available on the - * current platform. - * - * @param[in] runtime The target runtime to check. - * - * @return True if the supplied runtime is available; false, - * otherwise. - */ - static bool isRuntimeAvailable(zdl::DlSystem::Runtime_t runtime); - - /** - * Indicates whether the supplied runtime is available on the - * current platform. - * - * @param[in] runtime The target runtime to check. - * - * @param[in] option Extent to perform runtime available check. - * - * @return True if the supplied runtime is available; false, - * otherwise. - */ - static bool isRuntimeAvailable(zdl::DlSystem::Runtime_t runtime, - zdl::DlSystem::RuntimeCheckOption_t option); - - /** - * Gets a reference to the tensor factory. - * - * @return A reference to the tensor factory. - */ - static zdl::DlSystem::ITensorFactory& getTensorFactory(); - - /** - * Gets a reference to the UserBuffer factory. - * - * @return A reference to the UserBuffer factory. - */ - static zdl::DlSystem::IUserBufferFactory& getUserBufferFactory(); - - /** - * Gets the version of the SNPE library. - * - * @return Version of the SNPE library. - * - */ - static zdl::DlSystem::Version_t getLibraryVersion(); - - /** - * Set the SNPE storage location for all SNPE instances in this - * process. Note that this may only be called once, and if so - * must be called before creating any SNPE instances. - * - * @param[in] storagePath Absolute path to a directory which SNPE may - * use for caching and other storage purposes. - * - * @return True if the supplied path was succesfully set as - * the SNPE storage location, false otherwise. - */ - static bool setSNPEStorageLocation(const char* storagePath); - - /** - * @brief Register a user-defined op package with SNPE. - * - * @param[in] regLibraryPath Path to the registration library - * that allows clients to register a set of operations that are - * part of the package, and share op info with SNPE - * - * @return True if successful, False otherwise. - */ - static bool addOpPackage( const std::string& regLibraryPath ); - - /** - * Indicates whether the OpenGL and OpenCL interoperability is supported - * on GPU platform. - * - * @return True if the OpenGL and OpenCl interop is supported; false, - * otherwise. - */ - static bool isGLCLInteropSupported(); - - static const char* getLastError(); - - /** - * Initializes logging with the specified log level. - * initializeLogging with level, is used on Android platforms - * and after successful initialization, SNPE - * logs are printed in android logcat logs. - * - * It is recommended to initializeLogging before creating any - * SNPE instances, in order to capture information related to - * core initialization. If this is called again after first - * time initialization, subsequent calls are ignored. - * Also, Logging can be re-initialized after a call to - * terminateLogging API by calling initializeLogging again. - * - * A typical usage of Logging life cycle can be - * initializeLogging() - * any other SNPE API like isRuntimeAvailable() - * * setLogLevel() - optional - can be called anytime - * between initializeLogging & terminateLogging - * SNPE instance creation, inference, destroy - * terminateLogging(). - * - * Please note, enabling logging can have performance impact. - * - * @param[in] LogLevel_t Log level (LOG_INFO, LOG_WARN, etc.). - * - * @return True if successful, False otherwise. - */ - static bool initializeLogging(const zdl::DlSystem::LogLevel_t& level); - - /** - * Initializes logging with the specified log level and log path. - * initializeLogging with level & log path, is used on non Android - * platforms and after successful initialization, SNPE - * logs are printed in std output & into log files created in the - * log path. - * - * It is recommended to initializeLogging before creating any - * SNPE instances, in order to capture information related to - * core initialization. If this is called again after first - * time initialization, subsequent calls are ignored. - * Also, Logging can be re-initialized after a call to - * terminateLogging API by calling initializeLogging again. - * - * A typical usage of Logging life cycle can be - * initializeLogging() - * any other SNPE API like isRuntimeAvailable() - * * setLogLevel() - optional - can be called anytime - * between initializeLogging & terminateLogging - * SNPE instance creation, inference, destroy - * terminateLogging() - * - * Please note, enabling logging can have performance impact - * - * @param[in] LogLevel_t Log level (LOG_INFO, LOG_WARN, etc.). - * - * @param[in] Path of directory to store logs. - * If path is empty, the default path is "./Log". - * For android, the log path is ignored. - * - * @return True if successful, False otherwise. - */ - static bool initializeLogging(const zdl::DlSystem::LogLevel_t& level, const std::string& logPath); - - /** - * Updates the current logging level with the specified level. - * setLogLevel is optional, called anytime after initializeLogging - * and before terminateLogging, to update the log level set. - * Log levels can be updated multiple times by calling setLogLevel - * A call to setLogLevel() is ignored if it is made before - * initializeLogging() or after terminateLogging() - * - * @param[in] LogLevel_t Log level (LOG_INFO, LOG_WARN, etc.). - * - * @return True if successful, False otherwise. - */ - static bool setLogLevel(const zdl::DlSystem::LogLevel_t& level); - - /** - * Terminates logging. - * - * It is recommended to terminateLogging after initializeLogging - * in order to disable logging information. - * If this is called before initialization or after first time termination, - * calls are ignored. - * - * @return True if successful, False otherwise. - */ - static bool terminateLogging(void); -}; - -/** @} */ /* end_addtogroup c_plus_plus_apis C++ */ -}} - - -#endif diff --git a/third_party/snpe/include/SNPE/UserBufferList.hpp b/third_party/snpe/include/SNPE/UserBufferList.hpp deleted file mode 100644 index a660bca0f..000000000 --- a/third_party/snpe/include/SNPE/UserBufferList.hpp +++ /dev/null @@ -1,49 +0,0 @@ -//============================================================================== -// -// Copyright (c) 2019 Qualcomm Technologies, Inc. -// All Rights Reserved. -// Confidential and Proprietary - Qualcomm Technologies, Inc. -// -//============================================================================== -#ifndef PSNPE_USERBUFFERLIST_HPP -#define PSNPE_USERBUFFERLIST_HPP - -#include -#include "DlSystem/UserBufferMap.hpp" -#include "DlSystem/ZdlExportDefine.hpp" - -namespace zdl { -namespace PSNPE -{ - -/** @addtogroup c_plus_plus_apis C++ -@{ */ -/** -* @brief . -* -* The class for creating a UserBufferMap container. -* -*/ -class ZDL_EXPORT UserBufferList final -{ -public: - UserBufferList(); - UserBufferList(const size_t size); - void push_back(const zdl::DlSystem::UserBufferMap &userBufferMap); - zdl::DlSystem::UserBufferMap& operator[](const size_t index); - UserBufferList& operator =(const UserBufferList &other); - size_t size() const noexcept; - size_t capacity() const noexcept; - void clear() noexcept; - ~UserBufferList() = default; - -private: - void swap(const UserBufferList &other); - std::vector m_userBufferMaps; - -}; -/** @} */ /* end_addtogroup c_plus_plus_apis C++ */ - -} // namespace PSNPE -} // namespace zdl -#endif //PSNPE_USERBUFFERLIST_HPP diff --git a/third_party/snpe/include/SnpeUdo/UdoBase.h b/third_party/snpe/include/SnpeUdo/UdoBase.h deleted file mode 100644 index 7b2567920..000000000 --- a/third_party/snpe/include/SnpeUdo/UdoBase.h +++ /dev/null @@ -1,537 +0,0 @@ -//============================================================================== -// -// Copyright (c) 2019-2021 Qualcomm Technologies, Inc. -// All Rights Reserved. -// Confidential and Proprietary - Qualcomm Technologies, Inc. -// -//============================================================================== - -#ifndef SNPE_UDO_BASE_H -#define SNPE_UDO_BASE_H - -#include - -// Provide values to use for API version. -#define API_VERSION_MAJOR 1 -#define API_VERSION_MINOR 6 -#define API_VERSION_TEENY 0 - -/** @addtogroup c_plus_plus_apis C++ -@{ */ - -// Defines a bitmask of enum values. -typedef uint32_t SnpeUdo_Bitmask_t; -typedef SnpeUdo_Bitmask_t Udo_Bitmask_t; - -// A string of characters, rather than an array of bytes. -// Assumed to be UTF-8. -typedef char* SnpeUdo_String_t; -typedef SnpeUdo_String_t Udo_String_t; - -// The maximum allowable length of a SnpeUdo_String_t in bytes, -// including null terminator. SNPE will truncate strings longer -// than this. -#define SNPE_UDO_MAX_STRING_SIZE 1024 - -/** - * An enum which holds the various error types. - * The error types are divided to classes : - * 0 - 99 : generic errors - * 100 - 200 : errors related to configuration - * - */ -typedef enum -{ - /// No Error - SNPE_UDO_NO_ERROR = 0, UDO_NO_ERROR = 0, - /// Unsupported value for core type - SNPE_UDO_WRONG_CORE = 1, UDO_WRONG_CORE = 1, - /// Invalid attribute/argument passed into UDO API - SNPE_UDO_INVALID_ARGUMENT = 2, UDO_INVALID_ARGUMENT = 2, - /// Unsupported feature error - SNPE_UDO_UNSUPPORTED_FEATURE = 3, UDO_UNSUPPORTED_FEATURE = 3, - /// Error relating to memory allocation - SNPE_UDO_MEM_ALLOC_ERROR = 4, UDO_MEM_ALLOC_ERROR = 4, - /* Configuration Specific errors */ - /// No op with given attributes available in library - SNPE_UDO_WRONG_OPERATION = 100, UDO_WRONG_OPERATION = 100, - /// Unsupported value for core type in UDO configuration - SNPE_UDO_WRONG_CORE_TYPE = 101, UDO_WRONG_CORE_TYPE = 101, - /// Wrong number of params in UDO definition - SNPE_UDO_WRONG_NUM_OF_PARAMS = 102, UDO_WRONG_NUM_OF_PARAMS = 102, - /// Wrong number of dimensions for tensor(s) in UDO definition - SNPE_UDO_WRONG_NUM_OF_DIMENSIONS = 103, UDO_WRONG_NUM_OF_DIMENSIONS = 103, - /// Wrong number of input tensors in UDO definition - SNPE_UDO_WRONG_NUM_OF_INPUTS = 104, UDO_WRONG_NUM_OF_INPUTS = 104, - /// Wrong number of output tensors in UDO definition - SNPE_UDO_WRONG_NUM_OF_OUTPUTS = 105, UDO_WRONG_NUM_OF_OUTPUTS = 105, - SNPE_UDO_PROGRAM_CACHE_NOT_FOUND = 106, UDO_PROGRAM_CACHE_NOT_FOUND = 106, - SNPE_UDO_UNKNOWN_ERROR = 0xFFFFFFFF, UDO_UNKNOWN_ERROR = 0xFFFFFFFF -} SnpeUdo_ErrorType_t; - -typedef SnpeUdo_ErrorType_t Udo_ErrorType_t; - -/** - * An enum which holds the various data types. - * Designed to be used as single values or combined into a bitfield parameter - * (0x1, 0x2, 0x4, etc) - * \n FIXED_XX types are targeted for data in tensors. - * \n UINT / INT types are targeted for scalar params - */ -typedef enum -{ - /// data type: 16-bit floating point - SNPE_UDO_DATATYPE_FLOAT_16 = 0x01, UDO_DATATYPE_FLOAT_16 = 0x01, - /// data type: 32-bit floating point - SNPE_UDO_DATATYPE_FLOAT_32 = 0x02, UDO_DATATYPE_FLOAT_32 = 0x02, - /// data type: 4-bit fixed point - SNPE_UDO_DATATYPE_FIXED_4 = 0x04, UDO_DATATYPE_FIXED_4 = 0x04, - /// data type: 8-bit fixed point - SNPE_UDO_DATATYPE_FIXED_8 = 0x08, UDO_DATATYPE_FIXED_8 = 0x08, - /// data type: 16-bit fixed point - SNPE_UDO_DATATYPE_FIXED_16 = 0x10, UDO_DATATYPE_FIXED_16 = 0x10, - /// data type: 32-bit fixed point - SNPE_UDO_DATATYPE_FIXED_32 = 0x20, UDO_DATATYPE_FIXED_32 = 0x20, - /// data type: 8-bit unsigned integer - SNPE_UDO_DATATYPE_UINT_8 = 0x100, UDO_DATATYPE_UINT_8 = 0x100, - /// data type: 16-bit unsigned integer - SNPE_UDO_DATATYPE_UINT_16 = 0x200, UDO_DATATYPE_UINT_16 = 0x200, - /// data type: 32-bit unsigned integer - SNPE_UDO_DATATYPE_UINT_32 = 0x400, UDO_DATATYPE_UINT_32 = 0x400, - /// data type: 8-bit signed integer - SNPE_UDO_DATATYPE_INT_8 = 0x1000, UDO_DATATYPE_INT_8 = 0x1000, - /// data type: 16-bit signed integer - SNPE_UDO_DATATYPE_INT_16 = 0x2000, UDO_DATATYPE_INT_16 = 0x2000, - /// data type: 32-bit signed integer - SNPE_UDO_DATATYPE_INT_32 = 0x4000, UDO_DATATYPE_INT_32 = 0x4000, - SNPE_UDO_DATATYPE_LAST = 0xFFFFFFFF, UDO_DATATYPE_LAST = 0xFFFFFFFF -} SnpeUdo_DataType_t; - -typedef SnpeUdo_DataType_t Udo_DataType_t; - -/** - * An enum which holds the various layouts. - * Designed to be used as single values or combined into a bitfield parameter - * (0x1, 0x2, 0x4, etc) - */ -typedef enum -{ - /// data layout (4D): NHWC (batch-height-width-channel) - SNPE_UDO_LAYOUT_NHWC = 0x01, UDO_LAYOUT_NHWC = 0x01, - /// data layout (4D): NCHW (batch-channel-height-width) - SNPE_UDO_LAYOUT_NCHW = 0x02, UDO_LAYOUT_NCHW = 0x02, - /// data layout (5D): NDHWC (batch-dimension-height-width-channel) - SNPE_UDO_LAYOUT_NDHWC = 0x04, UDO_LAYOUT_NDHWC = 0x04, - SNPE_UDO_LAYOUT_GPU_OPTIMAL1 = 0x08, UDO_LAYOUT_GPU_OPTIMAL1 = 0x08, - SNPE_UDO_LAYOUT_GPU_OPTIMAL2 = 0x10, UDO_LAYOUT_GPU_OPTIMAL2 = 0x10, - SNPE_UDO_LAYOUT_DSP_OPTIMAL1 = 0x11, UDO_LAYOUT_DSP_OPTIMAL1 = 0x11, - SNPE_UDO_LAYOUT_DSP_OPTIMAL2 = 0x12, UDO_LAYOUT_DSP_OPTIMAL2 = 0x12, - // Indicates no data will be allocated for this tensor. - // Used to specify optional inputs/outputs positionally. - SNPE_UDO_LAYOUT_NULL = 0x13, UDO_LAYOUT_NULL = 0x13, - SNPE_UDO_LAYOUT_LAST = 0xFFFFFFFF, UDO_LAYOUT_LAST = 0xFFFFFFFF -} SnpeUdo_TensorLayout_t; - -typedef SnpeUdo_TensorLayout_t Udo_TensorLayout_t; - -/** - * An enum which holds the UDO library Core type . - * Designed to be used as single values or combined into a bitfield parameter - * (0x1, 0x2, 0x4, etc) - */ -typedef enum -{ - /// Library target IP Core is undefined - SNPE_UDO_CORETYPE_UNDEFINED = 0x00, UDO_CORETYPE_UNDEFINED = 0x00, - /// Library target IP Core is CPU - SNPE_UDO_CORETYPE_CPU = 0x01, UDO_CORETYPE_CPU = 0x01, - /// Library target IP Core is GPU - SNPE_UDO_CORETYPE_GPU = 0x02, UDO_CORETYPE_GPU = 0x02, - /// Library target IP Core is DSP - SNPE_UDO_CORETYPE_DSP = 0x04, UDO_CORETYPE_DSP = 0x04, - SNPE_UDO_CORETYPE_LAST = 0xFFFFFFFF, UDO_CORETYPE_LAST = 0xFFFFFFFF -} SnpeUdo_CoreType_t; - -typedef SnpeUdo_CoreType_t Udo_CoreType_t; - -/** - * An enum to specify the parameter type : Scalar or Tensor - */ -typedef enum -{ - /// UDO static param type: scalar - SNPE_UDO_PARAMTYPE_SCALAR = 0x00, UDO_PARAMTYPE_SCALAR = 0x00, - /// UDO static param type: string - SNPE_UDO_PARAMTYPE_STRING = 0x01, UDO_PARAMTYPE_STRING = 0x01, - /// UDO static param type: tensor - SNPE_UDO_PARAMTYPE_TENSOR = 0x02, UDO_PARAMTYPE_TENSOR = 0x02, - SNPE_UDO_PARAMTYPE_LAST = 0xFFFFFFFF, UDO_PARAMTYPE_LAST = 0xFFFFFFFF -} SnpeUdo_ParamType_t; - -typedef SnpeUdo_ParamType_t Udo_ParamType_t; - -/** - * An enum to specify quantization type - */ -typedef enum -{ - /// Tensor Quantization type: NONE. Signifies unquantized tensor data - SNPE_UDO_QUANTIZATION_NONE = 0x00, UDO_QUANTIZATION_NONE = 0x00, - /// Tensor Quantization type: Tensorflow-style - SNPE_UDO_QUANTIZATION_TF = 0x01, UDO_QUANTIZATION_TF = 0x01, - SNPE_UDO_QUANTIZATION_QMN = 0x02, UDO_QUANTIZATION_QMN = 0x02, - SNPE_UDO_QUANTIZATION_LAST = 0xFFFFFFFF, UDO_QUANTIZATION_LAST = 0xFFFFFFFF -} SnpeUdo_QuantizationType_t; - -typedef SnpeUdo_QuantizationType_t Udo_QuantizationType_t; - -/** - * @brief A struct which is used to provide a version number using 3 values : major, minor, teeny - * - */ -typedef struct -{ - /// version field: major - for backward-incompatible changes - uint32_t major; - /// version field: minor - for backward-compatible feature updates - uint32_t minor; - /// version field: teeny - for minor bug-fixes and clean-up - uint32_t teeny; -} SnpeUdo_Version_t; - -typedef SnpeUdo_Version_t Udo_Version_t; - -/** - * @brief A struct returned from version query, contains the Library version and API version - * - */ -typedef struct -{ - /// Version of UDO library. Controlled by users - SnpeUdo_Version_t libVersion; - /// Version of SNPE UDO API used in compiling library. Determined by SNPE - SnpeUdo_Version_t apiVersion; -} SnpeUdo_LibVersion_t; - -/** - * @brief A struct returned from version query, contains the package version - * - */ -typedef struct -{ - /// Version of UDO API used in package. - Udo_Version_t apiVersion; -} Udo_PkgVersion_t; - -/** - * @brief A union to hold the value of a generic type. Allows defining a parameter struct - * in a generic way, with a "value" location that holds the data regardless of the type. - * - */ -typedef union -{ - /// value type: float - float floatValue; - /// value type: unsigned 32-bit integer - uint32_t uint32Value; - /// value type: signed 32-bit integer - int32_t int32Value; - /// value type: unsigned 16-bit integer - uint16_t uint16Value; - /// value type: signed 16-bit integer - int16_t int16Value; - /// value type: unsigned 8-bit integer - uint8_t uint8Value; - /// value type: signed 8-bit integer - int8_t int8Value; -} SnpeUdo_Value_t; - -typedef SnpeUdo_Value_t Udo_Value_t; - -/** - * @brief A struct which defines a scalar parameter : name, data type, and union of values - * - */ -typedef struct -{ - /// The parameter data type : float, int, etc. - SnpeUdo_DataType_t dataType; - /// a union of specified type which holds the data - SnpeUdo_Value_t dataValue; -} SnpeUdo_ScalarParam_t; - -typedef SnpeUdo_ScalarParam_t Udo_ScalarParam_t; - -/** - * @brief A struct which defines the quantization parameters in case of Tensorflow style quantization - * - */ -typedef struct -{ - /// minimum value of the quantization range of data - float minValue; - /// maximum value of the quantization range of data - float maxValue; -} SnpeUdo_TFQuantize_t; - -typedef SnpeUdo_TFQuantize_t Udo_TFQuantize_t; - -/** - * @brief A struct which defines the quantization type, and union of supported quantization structs - * - */ -typedef struct -{ - /// quantization type (only TF-style currently supported) - SnpeUdo_QuantizationType_t quantizeType; - union - { - /// TF-style min-max quantization ranges - SnpeUdo_TFQuantize_t TFParams; - }; -} SnpeUdo_QuantizeParams_t; - -typedef SnpeUdo_QuantizeParams_t Udo_QuantizeParams_t; - -/** - * @brief A struct which defines the datatype associated with a specified core-type - * This should be used to denote the datatypes for a single tensor info, depending - * on the intended execution core. - * - */ -typedef struct -{ - /// The IP Core - SnpeUdo_CoreType_t coreType; - /// The associated datatype for this coreType - SnpeUdo_DataType_t dataType; -} SnpeUdo_PerCoreDatatype_t; - -typedef SnpeUdo_PerCoreDatatype_t Udo_PerCoreDatatype_t; - -/** - * @brief A struct which defines a tensor parameter : name, data type, layout, quantization, more. - * Also holds a pointer to the tensor data. - * - */ -typedef struct -{ - /// The maximum allowable dimensions of the tensor. The memory held in - /// _tensorData_ is guaranteed to be large enough for this. - uint32_t* maxDimensions; - /// The current dimensions of the tensor. An operation may modify the current - /// dimensions of its output, to indicate cases where the output has been - /// "resized". - /// Note that for static parameters, the current and max dimensions must - /// match. - uint32_t* currDimensions; - /// Quantization params applicable to the tensor. Currently only supports - /// Tensorflow quantization style. - SnpeUdo_QuantizeParams_t quantizeParams; - /// Number of dimensions to the tensor: 3D, 4D, etc. - uint32_t tensorRank; - /// The parameter data type: float, int, etc. - SnpeUdo_DataType_t dataType; - /// The tensor layout type: NCHW, NHWC, etc. - SnpeUdo_TensorLayout_t layout; - /// Opaque pointer to tensor data. User may be required to re-interpret the pointer - /// based on core-specific definitions. - void* tensorData; -} SnpeUdo_TensorParam_t; - -typedef SnpeUdo_TensorParam_t Udo_TensorParam_t; - -/** - * @brief A struct which defines tensor information for activation tensors only - * - * It describes an activation tensor object using its name, the intended layout and the datatype - * it will take depending on the intended runtime core. The repeated field indicates that - * that the tensor info describes several input/output activation tensors, which all share the - * aforementioned properties. - */ -typedef struct -{ - /// The tensor name - SnpeUdo_String_t tensorName; - /// The tensor layout type: NCHW, NHWC, etc. - SnpeUdo_TensorLayout_t layout; - /// The per core datatype: {SNPE_UDO_DATATYPE, SNPE_UDO_CORE_TYPE} - SnpeUdo_PerCoreDatatype_t* perCoreDatatype; - /// A boolean field indicating that this tensorinfo will be repeated e.x for ops such as Concat or Split - bool repeated; - /// A boolean field indicating whether input is static or not. - bool isStatic; -} SnpeUdo_TensorInfo_t; - -typedef SnpeUdo_TensorInfo_t Udo_TensorInfo_t; - -/** - * @brief struct which defines a UDO parameter - a union of scalar, tensor and string parameters - * - */ -typedef struct -{ - /// Type is scalar or tensor - SnpeUdo_ParamType_t paramType; - /// The param name, for example : "offset", "activation_type" - SnpeUdo_String_t paramName; - union - { - /// scalar param value - SnpeUdo_ScalarParam_t scalarParam; - /// tensor param value - SnpeUdo_TensorParam_t tensorParam; - /// string param value - SnpeUdo_String_t stringParam; - }; -} SnpeUdo_Param_t; - -typedef SnpeUdo_Param_t Udo_Param_t; - -/** - * @brief A struct which defines Operation information which is specific for IP core (CPU, GPU, DSP ...) - * - */ -typedef struct -{ - /// The IP Core - SnpeUdo_CoreType_t udoCoreType; - /// Bitmask, defines supported internal calculation types (like FLOAT_32, etc) - /// Based on SnpeUdo_DataType - SnpeUdo_Bitmask_t operationCalculationTypes; -} SnpeUdo_OpCoreInfo_t; - -typedef SnpeUdo_OpCoreInfo_t Udo_OpCoreInfo_t; - -/** - * @brief A struct which defines the common and core-specific Operation information - * - */ -typedef struct -{ - /// Operation type - SnpeUdo_String_t operationType; - /// A bitmask describing which IP Cores (CPU, GPU, DSP ...) support this operation - /// Translated based on SnpeUdo_CoreType - SnpeUdo_Bitmask_t supportedByCores; - /// Number of static parameters defined by the op - uint32_t numOfStaticParams; - /// Array of static parameters. Can be scalar or tensor params - SnpeUdo_Param_t* staticParams; - /// Number of input tensors this op receives - uint32_t numOfInputs; - /// Array of input tensor names to this operation - SnpeUdo_String_t* inputNames; - /// Number of output tensors this op receives - uint32_t numOfOutputs; - /// Array of output tensor names to this operation - SnpeUdo_String_t* outputNames; - /// Number of cores that the op can execute on - uint32_t numOfCoreInfo; - /// Array of per-core information entries - SnpeUdo_OpCoreInfo_t* opPerCoreInfo; - /// Array of input tensor infos for this operation - SnpeUdo_TensorInfo_t* inputInfos; - /// Array of output tensor infos for this operation - SnpeUdo_TensorInfo_t* outputInfos; -} SnpeUdo_OperationInfo_t; - -typedef SnpeUdo_OperationInfo_t Udo_OperationInfo_t; - -/** - * @brief A struct which provides the implementation library info : type, name - * - */ -typedef struct -{ - /// Defines the IP Core that this implementation library is targeting - SnpeUdo_CoreType_t udoCoreType; - /// library name. will be looked at in the standard library path - SnpeUdo_String_t libraryName; -} SnpeUdo_LibraryInfo_t; - -typedef SnpeUdo_LibraryInfo_t Udo_LibraryInfo_t; - -/** - * @brief A struct returned by the registration library and contains information on the UDO package : - * name, operations, libraries, etc. - * - */ -typedef struct -{ - /// A string containing the package name - SnpeUdo_String_t packageName; - /// A bitmask describing supported IP cores (CPU, GPU, DSP ...) - /// Translated based on SnpeUdo_CoreType - SnpeUdo_Bitmask_t supportedCoreTypes; - /// The number of implementation libraries in the package - uint32_t numOfImplementationLib; - /// Array of implementation libraries names/types - SnpeUdo_LibraryInfo_t* implementationLib; - /// A string containing all operation types separated by space - SnpeUdo_String_t operationsString; - /// Number of supported operations - uint32_t numOfOperations; - /// Array of Operation info structs. Each entry describes one - /// Operation (name, params, inputs, outputs) - SnpeUdo_OperationInfo_t* operationsInfo; -} SnpeUdo_RegInfo_t; - -typedef SnpeUdo_RegInfo_t Udo_RegInfo_t; - -/** -* @brief A struct returned by the implementation library and contains information on the -* specific library: name, IP Core, operations, etc. -* -*/ -typedef struct -{ - /// Defines the IP Core that this implementation library is targeting - SnpeUdo_CoreType_t udoCoreType; - /// A string containing the package name - SnpeUdo_String_t packageName; - /// A string containing all operation types separated by space - SnpeUdo_String_t operationsString; - /// Number of supported operations - uint32_t numOfOperations; -} SnpeUdo_ImpInfo_t; - -typedef SnpeUdo_ImpInfo_t Udo_ImpInfo_t; - -/** - * @brief This struct defines an operation. It is used for validation - * or creation of an operation. - * In case of using it for creation, the static params which are tensors - * contain pointers to the real data (weights, for example), and input/output - * tensors also include pointers to the buffers used. - */ -typedef struct -{ - /// The IP Core that the operation is defined for - CPU, GPU, DSP... - SnpeUdo_CoreType_t udoCoreType; - /// Operation type - SnpeUdo_String_t operationType; - /// The number of static parameters provided in the staticParams array. - /// this number has to match the number provided by the UDO Registration library information - uint32_t numOfStaticParams; - /// Array of static parameters - SnpeUdo_Param_t* staticParams; - /// The number of input parameters provided in inputs array. - /// this number has to match the number provided by the UDO Registration library information - uint32_t numOfInputs; - /// Array of input tensors, providing layout, data type, sizes, etc - /// When used to create an operation, also contains the initial location of the data - SnpeUdo_TensorParam_t* inputs; - /// The number of output parameters provided in inputs array. - /// this number has to match the number provided by the UDO Registration library information - uint32_t numOfOutputs; - /// Array of output tensors, providing layout, data type, sizes, etc - /// When used to create an operation, also contains the initial location of the data - SnpeUdo_TensorParam_t* outputs; -} SnpeUdo_OpDefinition_t; - -typedef SnpeUdo_OpDefinition_t Udo_OpDefinition_t; - -/** @} */ /* end_addtogroup c_plus_plus_apis C++ */ - -#endif //SNPE_UDO_BASE_H diff --git a/third_party/snpe/include/SnpeUdo/UdoImpl.h b/third_party/snpe/include/SnpeUdo/UdoImpl.h deleted file mode 100644 index a0cb648cf..000000000 --- a/third_party/snpe/include/SnpeUdo/UdoImpl.h +++ /dev/null @@ -1,343 +0,0 @@ -//============================================================================== -// -// Copyright (c) 2019-2021 Qualcomm Technologies, Inc. -// All Rights Reserved. -// Confidential and Proprietary - Qualcomm Technologies, Inc. -// -//============================================================================== - -#ifndef SNPE_UDO_IMPL_H -#define SNPE_UDO_IMPL_H - -#include - -#include "SnpeUdo/UdoShared.h" - -#ifdef __cplusplus -extern "C" -{ -#endif - -/** @addtogroup c_plus_plus_apis C++ -@{ */ - -typedef struct _SnpeUdo_OpFactory_t* SnpeUdo_OpFactory_t; -typedef struct _SnpeUdo_Operation_t* SnpeUdo_Operation_t; - -typedef SnpeUdo_OpFactory_t Udo_OpFactory_t; -typedef SnpeUdo_Operation_t Udo_Operation_t; - -/** - * @brief Initialize the shared library's data structures. Calling any other - * library function before this one will result in error. - * - * @param[in] globalInfrastructure Global core-specific infrastructure to be - * used by operations created in this library. The definition and - * semantics of this object will be defined in the corresponding - * implementation header for the core type. - * @return Error code - */ -SnpeUdo_ErrorType_t -SnpeUdo_initImplLibrary(void* globalInfrastructure); - -typedef SnpeUdo_ErrorType_t -(*SnpeUdo_InitImplLibraryFunction_t)(void*); - -/** - * @brief A function to query the API version of the UDO implementation library. - * The function populates a SnpeUdo_LibVersion_t struct, which contains a SnpeUdo_Version_t - * struct for API version and library version. - * - * @param[in, out] version A pointer to struct which contains major, minor, teeny information for - * library and api versions. - * - * @return Error code - */ -SnpeUdo_ErrorType_t -SnpeUdo_getImplVersion(SnpeUdo_LibVersion_t** version); - -typedef SnpeUdo_ErrorType_t -(*SnpeUdo_getImplVersion_t)(SnpeUdo_LibVersion_t** version); - -/** - * @brief Release the shared library's data structures, and invalidate any - * handles returned by the library. The behavior of any outstanding - * asynchronous calls made to this library when this function is called - * are undefined. All library functions (except SnpeUdo_initImplLibrary) will - * return an error after this function has been successfully called. - * - * It should be possible to call SnpeUdo_initImplLibrary after calling this - * function, and re-initialize the library. - * - * @return Error code - */ -SnpeUdo_ErrorType_t -SnpeUdo_terminateImplLibrary(void); - -typedef SnpeUdo_ErrorType_t -(*SnpeUdo_TerminateImplLibraryFunction_t)(void); - - -/** - * @brief A function to query info on the UDO implementation library. - * The function populates a structure which contains information about - * operations that are part of this library - * - * @param[in, out] implementationInfo A pointer to struct which contains information - * on the operations - * - * @return error code - * - */ -SnpeUdo_ErrorType_t -SnpeUdo_getImpInfo(SnpeUdo_ImpInfo_t** implementationInfo); - -typedef SnpeUdo_ErrorType_t -(*SnpeUdo_GetImpInfoFunction_t)(SnpeUdo_ImpInfo_t** implementationInfo); - -typedef SnpeUdo_GetImpInfoFunction_t Udo_GetImpInfoFunction_t; - -/** - * @brief A function to create an operation factory. - * The function receives the operation type, and an array of static parameters, - * and returns operation factory handler - * - * @param[in] udoCoreType The Core type to create the operation on. An error will - * be returned if this does not match the core type of the library. - * - * @param[in] perFactoryInfrastructure CreateOpFactory infrastructure appropriate to this - * core type. The definition and semantics of this object will be defined - * in the corresponding implementation header for the core type. - * - * @param[in] operationType A string containing Operation type. for example "MY_CONV" - * - * @param[in] numOfStaticParams The number of static parameters. - * - * @param[in] staticParams Array of static parameters - * - * @param[in,out] opFactory Handler to Operation Factory, to be used when creating operations - * - * @return Error Code - */ -SnpeUdo_ErrorType_t -SnpeUdo_createOpFactory(SnpeUdo_CoreType_t udoCoreType, - void* perFactoryInfrastructure, - SnpeUdo_String_t operationType, - uint32_t numOfStaticParams, - SnpeUdo_Param_t* staticParams, - SnpeUdo_OpFactory_t* opFactory); - -typedef SnpeUdo_ErrorType_t -(*SnpeUdo_CreateOpFactoryFunction_t)(SnpeUdo_CoreType_t, - void*, - SnpeUdo_String_t, - uint32_t, - SnpeUdo_Param_t*, - SnpeUdo_OpFactory_t*); - -typedef SnpeUdo_CreateOpFactoryFunction_t Udo_CreateOpFactoryFunction_t; - -/** - * @brief A function to release the resources allocated for an operation factory - * created by this library. - * - * @param[in] factory The operation factory to release. Upon success this handle will be invalidated. - * - * @return Error Code - */ -SnpeUdo_ErrorType_t -SnpeUdo_releaseOpFactory(SnpeUdo_OpFactory_t opFactory); - -typedef SnpeUdo_ErrorType_t -(*SnpeUdo_ReleaseOpFactoryFunction_t)(SnpeUdo_OpFactory_t); - -typedef SnpeUdo_ReleaseOpFactoryFunction_t Udo_ReleaseOpFactoryFunction_t; - -/** - * @brief A function to create an operation from the factory. - * The function receives array of inputs and array of outputs, and creates an operation - * instance, returning the operation instance handler. - * - * @param[in] opFactory OpFactory instance containing the parameters for this operation. - * - * @param[in] perOpInfrastructure Per-Op infrastructure for this operation. The definition - * and semantics of this object will be defined in the implementation header - * appropriate to this core type. - * - * @param[in] numOfInputs The number of input tensors this operation will receive. - * - * @param[in] inputs Array of input tensors, providing both the sizes and initial - * location of the data. - * - * @param[in] numOfOutputs Number of output tensors this operation will produce. - * - * @param[in] outputs Array of output tensors, providing both the sizes and - * initial location of the data. - * - * @param[in,out] operation Handle for newly created operation instance. - * - * @return Error Code - */ -SnpeUdo_ErrorType_t -SnpeUdo_createOperation(SnpeUdo_OpFactory_t opFactory, - void* perOpInfrastructure, - uint32_t numOfInputs, - SnpeUdo_TensorParam_t* inputs, - uint32_t numOfOutputs, - SnpeUdo_TensorParam_t* outputs, - SnpeUdo_Operation_t* operation); - -typedef SnpeUdo_ErrorType_t -(*SnpeUdo_CreateOperationFunction_t)(SnpeUdo_OpFactory_t, - void*, - uint32_t, - SnpeUdo_TensorParam_t*, - uint32_t, - SnpeUdo_TensorParam_t*, - SnpeUdo_Operation_t*); - -typedef SnpeUdo_CreateOperationFunction_t Udo_CreateOperationFunction_t; - -/** - * @brief A pointer to notification function. - * - * The notification function supports the non-blocking (e.g. asynchronous) execution use-case. - * In case an "executeUdoOp" function is called with "blocking" set to zero, and a - * notify function, this function will be called by the implementation library at the - * end of execution. The implementation library will pass the notify function the ID - * that was provided to it when "executeUdoOp" was called. - * - * @param[in] ID 32-bit value, that was provided to executeUdoOp by the calling entity. - * Can be used to track the notifications, in case of multiple execute calls issued. - * - * @return Error code - * - */ -typedef SnpeUdo_ErrorType_t -(*SnpeUdo_ExternalNotify_t)(const uint32_t ID); - -typedef SnpeUdo_ExternalNotify_t Udo_ExternalNotify_t; - -/** - * @brief Operation execution function. - * - * Calling this function will run the operation on set of inputs, generating a set of outputs. - * The call can be blocking (synchronous) or non-blocking (asynchronous). To support the - * non-blocking mode, the calling entity can pass an ID and a notification function. - * At the end of the execution this notification function would be called, passing it the ID. - * NOTE: Asynchronous execution mode not supported in this release. - * - * @param[in] operation handle to the operation on which execute is invoked - * @param[in] blocking flag to indicate execution mode. - * If set, execution is blocking, - * e.g SnpeUdo_executeOp call does not return until execution is done. - * If not set, SnpeUdo_executeOp returns immediately, and the - * library will call the notification function (if set) when execution is done. - * - * @param[in] ID 32-bit number that can be used by the calling entity to track execution - * in case of non-blocking execution. - * For example, it can be a sequence number, increased by one on each call. - * - * @param[in] notifyFunc Pointer to notification function. if the pointer is set, and execution is - * non-blocking, the library will call this function at end of execution, - * passing the number provided as ID - * - * @return Error code - * - */ -SnpeUdo_ErrorType_t -SnpeUdo_executeOp(SnpeUdo_Operation_t operation, - bool blocking, - const uint32_t ID, - SnpeUdo_ExternalNotify_t notifyFunc); - -typedef SnpeUdo_ErrorType_t -(*SnpeUdo_ExecuteOpFunction_t)(SnpeUdo_Operation_t, - bool, - const uint32_t, - SnpeUdo_ExternalNotify_t); - -typedef SnpeUdo_ExecuteOpFunction_t Udo_ExecuteOpFunction_t; - -/** - * @brief A function to setting the inputs & outputs. part of SnpeUdo_Operation struct, - * returned from creation of a new operation instance. - * Not supported in this release. - * - * This function allows the calling entity to change some of the inputs and outputs - * between calls to execute. - * Note that the change is limited to changing the pointer to the tensor data only. - * Any other change may be rejected by the implementation library, causing - * immediate invalidation of the operation instance - * - * @param[in] operation Operation on which IO tensors are set - * - * @param[in] inputs array of tensor parameters. The calling entity may provide a subset of the - * operation inputs, providing only those that it wants to change. - * - * @param[in] outputs array of tensor parameters. The calling entity may provide a subset of the - * operation outputs, providing only those that it wants to change. - * - * @return Error code - * - */ -SnpeUdo_ErrorType_t -SnpeUdo_setOpIO(SnpeUdo_Operation_t operation, - SnpeUdo_TensorParam_t* inputs, - SnpeUdo_TensorParam_t* outputs); - -typedef SnpeUdo_ErrorType_t -(*SnpeUdo_SetOpIOFunction_t)(SnpeUdo_Operation_t, - SnpeUdo_TensorParam_t*, - SnpeUdo_TensorParam_t*); - -typedef SnpeUdo_SetOpIOFunction_t Udo_SetOpIOFunction_t; - -/** - * @brief A function to return execution times. - * - * This function can be called to query the operation execution times on the IP core - * on which the operation is run. The time is provided in micro-seconds - * - * @param[in] operation Handle to operation whose execution time is being profiled - * - * @param[in,out] executionTime pointer to a uint32 value.This function writes the operation - * execution time in usec into this value. - * - * @return Error code - * - */ -SnpeUdo_ErrorType_t -SnpeUdo_profileOp(SnpeUdo_Operation_t operation, uint32_t *executionTime); - -typedef SnpeUdo_ErrorType_t -(*SnpeUdo_ProfileOpFunction_t)(SnpeUdo_Operation_t, uint32_t*); - -typedef SnpeUdo_ProfileOpFunction_t Udo_ProfileOpFunction_t; - -/** - * @brief A function to release the operation instance - * \n When it is called, the implementation library needs to release all resources - * allocated for this operation instance. - * \n Note that all function pointers which are part of SnpeUdo_Operation become - * invalid once releaseUdoOp call returns. - * - * @param[in] operation Handle to operation to be released - * @return Error code - * - */ -SnpeUdo_ErrorType_t -SnpeUdo_releaseOp(SnpeUdo_Operation_t operation); - -typedef SnpeUdo_ErrorType_t -(*SnpeUdo_ReleaseOpFunction_t)(SnpeUdo_Operation_t); - -typedef SnpeUdo_ReleaseOpFunction_t Udo_ReleaseOpFunction_t; - -/** @} */ /* end_addtogroup c_plus_plus_apis C++ */ - -#ifdef __cplusplus -} // extern "C" -#endif - -#endif //SNPE_UDO_IMPL_H diff --git a/third_party/snpe/include/SnpeUdo/UdoImplCpu.h b/third_party/snpe/include/SnpeUdo/UdoImplCpu.h deleted file mode 100644 index 3bbe0638e..000000000 --- a/third_party/snpe/include/SnpeUdo/UdoImplCpu.h +++ /dev/null @@ -1,44 +0,0 @@ -//============================================================================== -// -// Copyright (c) 2019-2020 Qualcomm Technologies, Inc. -// All Rights Reserved. -// Confidential and Proprietary - Qualcomm Technologies, Inc. -// -//============================================================================== - -// Header to be used by a CPU UDO Implementation library - -#ifndef SNPE_UDO_IMPL_CPU_H -#define SNPE_UDO_IMPL_CPU_H - -#include - -/** @addtogroup c_plus_plus_apis C++ -@{ */ - -/** - * @brief This struct provides the infrastructure needed by a developer of - * CPU UDO Implementation library. - * - * The framework/runtime which loads the CPU UDO implementation library provides - * this infrastructure data to the loaded library at the time of op factory creation. - * as an opaque pointer. It contains hooks for the UDO library to invoke supported - * functionality at the time of execution - * - * @param getData function pointer to retrieve raw tensor data from opaque pointer - * passed into the UDO when creating an instance. - * @param getDataSize function pointer to retrieve tensor data size from opaque pointer - */ - -typedef struct -{ - /// function pointer to retrieve raw tensor data from opaque pointer - /// passed into the UDO when creating an instance. - float* (*getData)(void*); - /// function pointer to retrieve tensor data size from opaque pointer - size_t (*getDataSize) (void*); -} SnpeUdo_CpuInfrastructure_t; - -/** @} */ /* end_addtogroup c_plus_plus_apis C++ */ - -#endif // SNPE_UDO_IMPL_CPU_H \ No newline at end of file diff --git a/third_party/snpe/include/SnpeUdo/UdoImplDsp.h b/third_party/snpe/include/SnpeUdo/UdoImplDsp.h deleted file mode 100644 index b97a64932..000000000 --- a/third_party/snpe/include/SnpeUdo/UdoImplDsp.h +++ /dev/null @@ -1,207 +0,0 @@ -//============================================================================== -// -// Copyright (c) 2019-2021 Qualcomm Technologies, Inc. -// All Rights Reserved. -// Confidential and Proprietary - Qualcomm Technologies, Inc. -// -//============================================================================== - -//============================================================================== -/* - * THIS HEADER FILE IS COPIED FROM HEXAGON-NN PROJECT - * - */ -//============================================================================== - - -// Header to be used by a DSP Hexnn UDO Implementation library - -#ifndef SNPE_UDO_IMPL_DSP_H -#define SNPE_UDO_IMPL_DSP_H -#include -#include "SnpeUdo/UdoImpl.h" - -/** @addtogroup c_plus_plus_apis C++ -@{ */ - -/** - * @brief A function to validate that a set of params is supported by an operation - * This function is HexNN specific, use case is when registration library is not in use. - * Optional function. - * - * @param[in] operationType Operation type - * @param[in] numOfStaticParams Number of static params defined by the op - * @param[in] staticParams Array of static params to the op - * @return Error code, indicating if the operation can be created on this set of configuration or not. - * - */ - -SnpeUdo_ErrorType_t -SnpeUdo_validateOperation (SnpeUdo_String_t operationType, - uint32_t numOfStaticParams, - const SnpeUdo_Param_t* staticParams); - -typedef SnpeUdo_ErrorType_t (*SnpeUdo_ValidateOperationFunction_t) (SnpeUdo_String_t, - uint32_t, - const SnpeUdo_Param_t*); - -typedef SnpeUdo_ValidateOperationFunction_t Udo_ValidateOperationFunction_t; - -// enum used for indicating input/outout tensor data layouts on DSP, plain vs d32 -typedef enum { - SNPE_UDO_DSP_TENSOR_LAYOUT_PLAIN = 0x00, UDO_DSP_TENSOR_LAYOUT_PLAIN = 0x00, - SNPE_UDO_DSP_TENSOR_LAYOUT_D32 = 0x01, UDO_DSP_TENSOR_LAYOUT_D32 = 0x01 -} SnpeUdo_HexNNTensorLayout_t; - -typedef SnpeUdo_HexNNTensorLayout_t Udo_HexNNTensorLayout_t; - -/** - * @brief A function to query numbers of inputs and outputs, - * quantization type of each input and each output as arrays, - * and data layout (plain vs d32) of each input and each output as arrays - * of an operation. - * inputsQuantTypes and inputsLayouts should point to arrays of size numOfInputs - * outputsQuantTypes and outputsLayouts should point to arrays of size numOfOutputs - * - * Note: inputsLayouts and inputsLayouts can point to NULL, in this case, it is - * assumed all inputs and/or outputs have plain data layouts, i.e. no D32 - * - * @param[in] operationType Operation type - * @param[in] numOfStaticParams Number of static params defined by the op - * @param[in] staticParams Array of static params to the op - * @param[in,out] numOfInputs Number of input tensors to the op - * @param[in,out] inputsQuantTypes Array of Quantization info for each input tensor - * @param[in,out] inputsLayouts Array of layout type for each input tensor - * @param[in,out] numOfOutputs Number of output tensors to the op - * @param[in,out] outputsQuantTypes Array of Quantization info for each output tensor - * @param[in,out] outputsLayouts Array of layout type for each output tensor - * @return error code, indicating status of query - */ - -SnpeUdo_ErrorType_t -SnpeUdo_queryOperation (SnpeUdo_String_t operationType, - uint32_t numOfStaticParams, - const SnpeUdo_Param_t* staticParams, - uint32_t* numOfInputs, - SnpeUdo_QuantizationType_t** inputsQuantTypes, - SnpeUdo_HexNNTensorLayout_t** inputsLayouts, - uint32_t* numOfOutputs, - SnpeUdo_QuantizationType_t** outputsQuantTypes, - SnpeUdo_HexNNTensorLayout_t** outputsLayouts); - -typedef SnpeUdo_ErrorType_t (*SnpeUdo_QueryOperationFunction_t) (SnpeUdo_String_t, - uint32_t, - const SnpeUdo_Param_t*, - uint32_t*, - SnpeUdo_QuantizationType_t**, - SnpeUdo_HexNNTensorLayout_t**, - uint32_t*, - SnpeUdo_QuantizationType_t**, - SnpeUdo_HexNNTensorLayout_t**); - -typedef SnpeUdo_QueryOperationFunction_t Udo_QueryOperationFunction_t; - -// Global infrastructure functions supported by Hexagon-NN v2 -typedef void (*workerThread_t) (void* perOpInfrastructure, void* userData); -typedef int (*udoSetOutputTensorSize_t) (void* perOpInfrastructure, uint32_t outIdx, uint32_t size); -typedef int (*udoGetInputD32Paddings_t) (void* perOpInfrastructure, uint32_t inIdx, - uint32_t* heightPadBefore, uint32_t* heightPadAfter, - uint32_t* widthPadBefore, uint32_t* widthPadAfter, - uint32_t* depthPadBefore, uint32_t* depthPadAfter); -typedef int (*udoSetOutputD32ShapeSizePaddings_t) (void* perOpInfrastructure, uint32_t outIdx, - uint32_t batch, - uint32_t height, uint32_t heightPadBefore, uint32_t heightPadAfter, - uint32_t width, uint32_t widthPadBefore, uint32_t widthPadAfter, - uint32_t depth, uint32_t depthPadBefore, uint32_t depthPadAfter, - SnpeUdo_DataType_t dataType); -typedef void* (*udoMemalign_t) (size_t n, size_t size); -typedef void* (*udoMalloc_t) (size_t size); -typedef void* (*udoCalloc_t) (size_t n, size_t size); -typedef void (*udoFree_t) (void* ptr); -typedef uint32_t (*udoGetVtcmSize_t) (void* perOpInfrastructure); -typedef void* (*udoGetVtcmPtr_t) (void* perOpInfrastructure); -typedef uint32_t (*udoVtcmIsReal_t) (void* perOpInfrastructure); -typedef void (*udoRunWorkerThreads_t) (void* perOpInfrastructure, uint32_t nThreads, workerThread_t w, void* userData); - -typedef struct hexNNv2GlobalInfra { - udoSetOutputTensorSize_t udoSetOutputTensorSize; - udoGetInputD32Paddings_t udoGetInputD32Paddings; - udoSetOutputD32ShapeSizePaddings_t udoSetOutputD32ShapeSizePaddings; - udoMemalign_t udoMemalign; - udoMalloc_t udoMalloc; - udoCalloc_t udoCalloc; - udoFree_t udoFree; - udoGetVtcmSize_t udoGetVtcmSize; - udoGetVtcmPtr_t udoGetVtcmPtr; - udoVtcmIsReal_t udoVtcmIsReal; - udoRunWorkerThreads_t udoRunWorkerThreads; -} SnpeUdo_HexNNv2GlobalInfra_t; - -typedef SnpeUdo_HexNNv2GlobalInfra_t Udo_HexNNv2GlobalInfra_t; - -// hexnn types -typedef enum hexnnInfraType { - UDO_INFRA_HEXNN_V2, - UDO_INFRA_HEXNN_V3 // reserved, do not use -} SnpeUdo_HexNNInfraType_t; - -typedef SnpeUdo_HexNNInfraType_t Udo_HexNNInfraType_t; - -typedef struct { - Udo_CreateOpFactoryFunction_t create_op_factory; - Udo_CreateOperationFunction_t create_operation; - Udo_ExecuteOpFunction_t execute_op; - Udo_ReleaseOpFunction_t release_op; - Udo_ReleaseOpFactoryFunction_t release_op_factory; - Udo_ValidateOperationFunction_t validate_op; - Udo_QueryOperationFunction_t query_op; -} udo_func_package_t; - -/** - * @brief Infrastructures needed by a developer of DSP Hexnn UDO Implementation library. - * - * The framework/runtime which loads the Hexnn UDO implementation library provides - * this infrastructure to the loaded library by calling "SnpeUdo_initImplLibrary" - * function, and passing it (cast to void*). The Hexnn UDO library is expected - * to cast it back to this structure. - * - */ -typedef struct dspGlobalInfrastructure { - SnpeUdo_Version_t dspInfraVersion; // api version - SnpeUdo_HexNNInfraType_t infraType; - SnpeUdo_HexNNv2GlobalInfra_t hexNNv2Infra; -} SnpeUdo_DspGlobalInfrastructure_t; - -typedef SnpeUdo_DspGlobalInfrastructure_t Udo_DspGlobalInfrastructure_t; - -/** - * hexnn v2 per op factory infrastructure - * - * The framework/runtime passes per op factory infrastructure as a void pointer - * to HexNN UDO implementation library by calling function "SnpeUdo_createOpFactory". - * UDO implementation library is expected to cast it back to this following struct. - * - */ -typedef struct hexnnv2OpFactoryInfra { - unsigned long graphId; -} SnpeUdo_HexNNv2OpFactoryInfra_t; - -typedef SnpeUdo_HexNNv2OpFactoryInfra_t Udo_HexNNv2OpFactoryInfra_t; - -/** - * hexnn v2 per operation infrastructure - * - * The framework/runtime passes per operation infrastructure as a void pointer - * to HexNN UDO implementation library by calling function "SnpeUdo_createOperation". - * UDO implementation library is expected to cast it to the following type and save it. - * - * This is needed to be passed back into some functions from global infrastructure. - * - */ -typedef void* SnpeUdo_HexNNv2OpInfra_t; - -typedef SnpeUdo_HexNNv2OpInfra_t Udo_HexNNv2OpInfra_t; - -/** @} */ /* end_addtogroup c_plus_plus_apis C++ */ - -#endif // SNPE_UDO_IMPL_DSP_H diff --git a/third_party/snpe/include/SnpeUdo/UdoImplGpu.h b/third_party/snpe/include/SnpeUdo/UdoImplGpu.h deleted file mode 100644 index 1af654d11..000000000 --- a/third_party/snpe/include/SnpeUdo/UdoImplGpu.h +++ /dev/null @@ -1,112 +0,0 @@ -//============================================================================== -// -// Copyright (c) 2019-2020 Qualcomm Technologies, Inc. -// All Rights Reserved. -// Confidential and Proprietary - Qualcomm Technologies, Inc. -// -//============================================================================== - -// Header to be used by a GPU UDO Implementation library - -#ifndef SNPE_UDO_IMPL_GPU_H -#define SNPE_UDO_IMPL_GPU_H - -#include "CL/cl.h" -#include "SnpeUdo/UdoBase.h" - -/** @addtogroup c_plus_plus_apis C++ -@{ */ - -/** - * This header defines version 0.0.0 of the GPU UDO Infrastructure. - * It defines the interpretation of the global and per-OpFactory infrastructure pointers - * as well as the interpretation of tensorData pointers. - * - * The per-Operation infrastructure pointer is defined to be null, and should not be used. - * - * The SnpeUdoTensorParam_t struct below provides the interpretation for - * the tensorData opaque pointer for SnpeUdoTensorParams representing inputs or outputs. - * - * The tensorData opaque pointer populated in SnpeUdoScalarParam_t structs should be interpreted - * as a host-readable data pointer. - * - */ - -/** - * @brief Function to retrieve opencl program from Program Cache repository. - * @param programCache is opaque pointer to Program Cache repository provided by - * SNPE GPU UDO runtime. - * @param programName is name associated with opencl program for UDO. - * @param program is pointer to opencl program which will be populated with - * valid opencl program if found in Program Cache repository. - * @return SnpeUdo_ErrorType_t is error type returned. SNPE_UDO_NO_ERROR is returned - * on success. - */ -typedef SnpeUdo_ErrorType_t (*SnpeUdo_getProgram_t) - (void* programCache, const char* programName, cl_program* program); - -/** - * @brief Function to store valid opencl program in Program Cache repository. - * @param programCache is opaque pointer to Program Cache repository provided by - * SNPE GPU UDO runtime. - * @param programName is name associated with opencl program for UDO. - * @param program is valid opencl program after program is built. - * @return SnpeUdo_ErrorType_t is error type returned. SNPE_UDO_NO_ERROR is returned - * on success. - * */ -typedef SnpeUdo_ErrorType_t (*SnpeUdo_storeProgram_t) - (void* programCache, const char * programName, cl_program program); - -/** - * @brief Global Infrastructure Definition for GPU UDO Implementations. - */ -typedef struct { - // Infrastructure definition version. This header is 0.0.0 - SnpeUdo_Version_t gpuInfraVersion; - SnpeUdo_getProgram_t SnpeUdo_getProgram; - SnpeUdo_storeProgram_t SnpeUdo_storeProgram; -} SnpeUdo_GpuInfrastructure_t; - -/** - * @brief Per OpFactory Infrastructure Definition for GPU UDO Implementations. - * @note This version of the infrastructure definition guarantees that the same - * Per OpFactory infrastructure pointer will be provided to all OpFactories - * in the same network. - */ -typedef struct -{ - cl_context context; - cl_command_queue commandQueue; - void* programCache; -} SnpeUdo_GpuOpFactoryInfrastructure_t; - -/** - * @brief Opaque tensorData definition for operation inputs and outputs. - * - * The following is a list of all SnpeUdoTensorLayout_t values supported by the - * GPU UDO implementation, and how the parameters of the struct should be - * interpreted in each case: - * - * SNPE_UDO_LAYOUT_NHWC: - * mem shall be single-element array, pointing to a cl buffer memory object. - * the dimensions of this object match the dimensions specified in the encompassing - * SnpeUdoTensorParam_t's currDimensions. - * - * memCount shall be 1. - * - * paddedRank and paddedDimensions are undefined and shall be ignored by the UDO - * implementation. - * - */ -typedef struct -{ - cl_mem* mem; - uint32_t memCount; - uint32_t paddedRank; - uint32_t* paddedDimensions; - -} SnpeUdo_GpuTensorData_t; - -/** @} */ /* end_addtogroup c_plus_plus_apis C++ */ - -#endif // SNPE_UDO_IMPL_GPU_H diff --git a/third_party/snpe/include/SnpeUdo/UdoReg.h b/third_party/snpe/include/SnpeUdo/UdoReg.h deleted file mode 100644 index a5d239883..000000000 --- a/third_party/snpe/include/SnpeUdo/UdoReg.h +++ /dev/null @@ -1,108 +0,0 @@ -//============================================================================== -// -// Copyright (c) 2019-2020 Qualcomm Technologies, Inc. -// All Rights Reserved. -// Confidential and Proprietary - Qualcomm Technologies, Inc. -// -//============================================================================== - -#ifndef SNPE_UDO_REG_H -#define SNPE_UDO_REG_H - -#include "SnpeUdo/UdoShared.h" - -#ifdef __cplusplus -extern "C" -{ -#endif - -/** @addtogroup c_plus_plus_apis C++ -@{ */ - -/** - * @brief Initialize the shared library's data structures. Calling any other - * library function before this one will result in an error being returned. - * - * @return Error code - */ -SnpeUdo_ErrorType_t -SnpeUdo_initRegLibrary(void); - -typedef SnpeUdo_ErrorType_t -(*SnpeUdo_InitRegLibraryFunction_t)(void); - -/** - * @brief A function to query the API version of the UDO registration library. - * The function populates a SnpeUdo_LibVersion_t struct, which contains a SnpeUdo_Version_t - * struct for API version and library version. - * - * @param[in, out] version A pointer to struct which contains major, minor, teeny information for - * library and api versions. - * - * @return Error code - */ -SnpeUdo_ErrorType_t -SnpeUdo_getRegLibraryVersion(SnpeUdo_LibVersion_t** version); - -typedef SnpeUdo_ErrorType_t -(*SnpeUdo_getRegLibraryVersion_t)(SnpeUdo_LibVersion_t** version); - -/** - * @brief Release the shared library's data structures, and invalidate any - * handles returned by the library. The behavior of any outstanding - * asynchronous calls made to this library when this function is called - * are undefined. All library functions (except SnpeUdo_InitRegLibrary) will - * return an error after this function has been successfully called. - * - * It should be possible to call SnpeUdo_InitRegLibrary after calling this - * function, and re-initialize the library. - * - * @return Error code - */ -SnpeUdo_ErrorType_t -SnpeUdo_terminateRegLibrary(void); - -typedef SnpeUdo_ErrorType_t -(*SnpeUdo_TerminateRegLibraryFunction_t)(void); - - -/** - * @brief A function to query the info on the UDO set. - * The function populates a structure which contains information about - * the package and operations contained in it. - * - * @param[in, out] registrationInfo A struct which contains information on the set of UDOs - * - * @return Error code - * - */ -SnpeUdo_ErrorType_t -SnpeUdo_getRegInfo(SnpeUdo_RegInfo_t** registrationInfo); - -typedef SnpeUdo_ErrorType_t -(*SnpeUdo_GetRegInfoFunction_t)(SnpeUdo_RegInfo_t** registrationInfo); - -/** - * @brief A function to validate that a set of params is supported by an operation - * The function receives an operation definition struct, and returns if this configuration is - * supported (e.g. if an operation can be created using this configuration) - * - * @param[in] opDefinition A struct of SnpeUdo_OpDefinition type, containing the information needed to - * validate that an operation can be created with this configuration. - * - * @return Error code, indicating is the operation can be created on this set or not. - * - */ -SnpeUdo_ErrorType_t -SnpeUdo_validateOperation(SnpeUdo_OpDefinition_t* opDefinition); - -typedef SnpeUdo_ErrorType_t -(*SnpeUdo_ValidateOperationFunction_t)(SnpeUdo_OpDefinition_t* opDefinition); - -/** @} */ /* end_addtogroup c_plus_plus_apis C++ */ - -#ifdef __cplusplus -} // extern "C" -#endif - -#endif //SNPE_UDO_REG_H diff --git a/third_party/snpe/include/SnpeUdo/UdoShared.h b/third_party/snpe/include/SnpeUdo/UdoShared.h deleted file mode 100644 index 418d9db9e..000000000 --- a/third_party/snpe/include/SnpeUdo/UdoShared.h +++ /dev/null @@ -1,48 +0,0 @@ -//============================================================================== -// -// Copyright (c) 2019-2021 Qualcomm Technologies, Inc. -// All Rights Reserved. -// Confidential and Proprietary - Qualcomm Technologies, Inc. -// -//============================================================================== - -#ifndef SNPE_UDO_SHARED_H -#define SNPE_UDO_SHARED_H - -#include "SnpeUdo/UdoBase.h" - -#ifdef __cplusplus -extern "C" -{ -#endif - -/** @addtogroup c_plus_plus_apis C++ -@{ */ - -/** - * @brief A function to return the various versions as they relate to the UDO - * The function returns a struct containing the the following: - * libVersion: the version of the implementation library compiled for the UDO. Set by user - * apiVersion: the version of the UDO API used in compiling the implementation library. - * Set by SNPE - * - * @param[in, out] version A pointer to Version struct of type SnpeUdo_LibVersion_t - * - * @return Error code - * - */ -SnpeUdo_ErrorType_t -SnpeUdo_getVersion (SnpeUdo_LibVersion_t** version); - -typedef SnpeUdo_ErrorType_t -(*SnpeUdo_GetVersionFunction_t) (SnpeUdo_LibVersion_t** version); - -typedef SnpeUdo_GetVersionFunction_t Udo_GetVersionFunction_t; - -#ifdef __cplusplus -} // extern "C" -#endif - -/** @} */ /* end_addtogroup c_plus_plus_apis C++ */ - -#endif // SNPE_UDO_SHARED_H diff --git a/third_party/snpe/larch64 b/third_party/snpe/larch64 deleted file mode 120000 index 2e2a1c315..000000000 --- a/third_party/snpe/larch64 +++ /dev/null @@ -1 +0,0 @@ -aarch64-ubuntu-gcc7.5 \ No newline at end of file diff --git a/third_party/snpe/x86_64 b/third_party/snpe/x86_64 deleted file mode 120000 index 700025efa..000000000 --- a/third_party/snpe/x86_64 +++ /dev/null @@ -1 +0,0 @@ -x86_64-linux-clang \ No newline at end of file diff --git a/third_party/snpe/x86_64-linux-clang/libHtpPrepare.so b/third_party/snpe/x86_64-linux-clang/libHtpPrepare.so deleted file mode 100644 index 20d1377a0..000000000 --- a/third_party/snpe/x86_64-linux-clang/libHtpPrepare.so +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c4c33680010a8c7b2eef33e809844c0ad1d5ab0f0ec37abd49d924204670b357 -size 13556072 diff --git a/third_party/snpe/x86_64-linux-clang/libSNPE.so b/third_party/snpe/x86_64-linux-clang/libSNPE.so deleted file mode 100644 index 0367b1106..000000000 --- a/third_party/snpe/x86_64-linux-clang/libSNPE.so +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:024891d2e4e4e265a1e7e72b27bad41ee6ae077d2197f45959bb32f8071dc8cf -size 5350008 diff --git a/third_party/snpe/x86_64-linux-clang/libomp.so b/third_party/snpe/x86_64-linux-clang/libomp.so deleted file mode 100755 index db98cba16..000000000 --- a/third_party/snpe/x86_64-linux-clang/libomp.so +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f4784aa68e7ebdbe97db732eab87618b0a5fb73abeab4daed5476e01829b0e6e -size 759208 From 8d7549b6c882e3e78c4c13dd2e4ccb3b7a8f2aef Mon Sep 17 00:00:00 2001 From: infiniteCable <75014343+infiniteCable@users.noreply.github.com> Date: Sun, 23 Nov 2025 23:05:57 +0100 Subject: [PATCH 433/910] Update ictoggles.py fix toggle text bsm --- selfdrive/ui/layouts/settings/ictoggles.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/layouts/settings/ictoggles.py b/selfdrive/ui/layouts/settings/ictoggles.py index 729745850..7df294e8a 100644 --- a/selfdrive/ui/layouts/settings/ictoggles.py +++ b/selfdrive/ui/layouts/settings/ictoggles.py @@ -31,7 +31,7 @@ DESCRIPTIONS = { "Display battery detail panel" ), "ForceRHDForBSM": tr_noop( - "Switch BSM detection side to RHD. Passenger is on the right side." + "Switch BSM detection side to RHD. Driver is on the right side." ), "EnableSmoothSteer": tr_noop( "Enables S-curving on lateral control for smoother steering" From 9edc36ca66e9ab533d80d32329f9e4b3f272d06b Mon Sep 17 00:00:00 2001 From: Nayan Date: Mon, 24 Nov 2025 11:18:12 -0500 Subject: [PATCH 434/910] ui: fix scroll panel mouse wheel behavior (#1506) fix scroller. yay Co-authored-by: Jason Wen --- system/ui/lib/scroll_panel.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/system/ui/lib/scroll_panel.py b/system/ui/lib/scroll_panel.py index a5b9fc70d..6dd9ceaad 100644 --- a/system/ui/lib/scroll_panel.py +++ b/system/ui/lib/scroll_panel.py @@ -41,8 +41,12 @@ class GuiScrollPanel: if DEBUG: rl.draw_rectangle_lines(0, 0, abs(int(self._velocity_filter_y.x)), 10, rl.RED) - # Handle mouse wheel - self._offset_filter_y.x += rl.get_mouse_wheel_move() * MOUSE_WHEEL_SCROLL_SPEED + # Handle mouse wheel only when the mouse cursor is over this panel + mouse_wheel = rl.get_mouse_wheel_move() + if mouse_wheel != 0: + mouse_pos = rl.get_mouse_position() + if rl.check_collision_point_rec(mouse_pos, bounds): + self._offset_filter_y.x += mouse_wheel * MOUSE_WHEEL_SCROLL_SPEED max_scroll_distance = max(0, content.height - bounds.height) if self._scroll_state == ScrollState.IDLE: From 844f4cbc745e01621ae89f05c8ea5f415ef1aa74 Mon Sep 17 00:00:00 2001 From: Nayan Date: Mon, 24 Nov 2025 14:43:33 -0500 Subject: [PATCH 435/910] ui: sunnypilot panels (#1477) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * param to control stock vs sp ui * init styles * SP Toggles * Lint * optimizations * Panels. With Icons. And Scroller. * patience, grasshopper * more patience, grasshopper * sp raylib preview * fix callback * fix ui preview * add ui previews * better padding * this * listitem -> listitemsp * add show_description method * remove padding from line separator. like, WHY? 😩😩 * scroller -> scroller_tici * scroller -> scroller_tici * ui: `GuiApplicationExt` * add to readme * use gui_app.sunnypilot_ui() * use gui_app.sunnypilot_ui() * lint * no fancy toggles :( * match them * mici scroller - no touchy * no * more * size adjustments * fix scroller. yay * more * loopy loop * update brand name * ui preview fixes * Revert "update brand name" This reverts commit ad44c32fc713aa6b8f6e6ab50adef7210e064a0e. * fix sidebar scroller reset * fix developer click * more * more * try this out * one more time * bruh --------- Co-authored-by: Jason Wen --- selfdrive/ui/layouts/main.py | 3 + .../ui/sunnypilot/layouts/settings/cruise.py | 30 +++ .../ui/sunnypilot/layouts/settings/device.py | 12 ++ .../ui/sunnypilot/layouts/settings/display.py | 30 +++ .../ui/sunnypilot/layouts/settings/models.py | 30 +++ .../sunnypilot/layouts/settings/navigation.py | 30 +++ .../ui/sunnypilot/layouts/settings/osm.py | 30 +++ .../sunnypilot/layouts/settings/settings.py | 202 ++++++++++++++++++ .../sunnypilot/layouts/settings/steering.py | 30 +++ .../sunnypilot/layouts/settings/sunnylink.py | 30 +++ .../ui/sunnypilot/layouts/settings/trips.py | 30 +++ .../ui/sunnypilot/layouts/settings/vehicle.py | 30 +++ .../ui/sunnypilot/layouts/settings/visuals.py | 30 +++ .../ui/tests/test_ui/raylib_screenshots.py | 142 ++++++++---- .../assets/offroad/icon_firehose.png | 3 + .../selfdrive/assets/offroad/icon_home.png | 3 + system/ui/sunnypilot/lib/styles.py | 3 +- 17 files changed, 631 insertions(+), 37 deletions(-) create mode 100644 selfdrive/ui/sunnypilot/layouts/settings/cruise.py create mode 100644 selfdrive/ui/sunnypilot/layouts/settings/device.py create mode 100644 selfdrive/ui/sunnypilot/layouts/settings/display.py create mode 100644 selfdrive/ui/sunnypilot/layouts/settings/models.py create mode 100644 selfdrive/ui/sunnypilot/layouts/settings/navigation.py create mode 100644 selfdrive/ui/sunnypilot/layouts/settings/osm.py create mode 100644 selfdrive/ui/sunnypilot/layouts/settings/settings.py create mode 100644 selfdrive/ui/sunnypilot/layouts/settings/steering.py create mode 100644 selfdrive/ui/sunnypilot/layouts/settings/sunnylink.py create mode 100644 selfdrive/ui/sunnypilot/layouts/settings/trips.py create mode 100644 selfdrive/ui/sunnypilot/layouts/settings/vehicle.py create mode 100644 selfdrive/ui/sunnypilot/layouts/settings/visuals.py create mode 100644 sunnypilot/selfdrive/assets/offroad/icon_firehose.png create mode 100644 sunnypilot/selfdrive/assets/offroad/icon_home.png diff --git a/selfdrive/ui/layouts/main.py b/selfdrive/ui/layouts/main.py index 702854f98..a020a63b5 100644 --- a/selfdrive/ui/layouts/main.py +++ b/selfdrive/ui/layouts/main.py @@ -10,6 +10,9 @@ from openpilot.selfdrive.ui.ui_state import device, ui_state from openpilot.system.ui.widgets import Widget from openpilot.selfdrive.ui.layouts.onboarding import OnboardingWindow +if gui_app.sunnypilot_ui(): + from openpilot.selfdrive.ui.sunnypilot.layouts.settings.settings import SettingsLayoutSP as SettingsLayout + class MainState(IntEnum): HOME = 0 diff --git a/selfdrive/ui/sunnypilot/layouts/settings/cruise.py b/selfdrive/ui/sunnypilot/layouts/settings/cruise.py new file mode 100644 index 000000000..9439c588d --- /dev/null +++ b/selfdrive/ui/sunnypilot/layouts/settings/cruise.py @@ -0,0 +1,30 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +from openpilot.common.params import Params +from openpilot.system.ui.widgets.scroller_tici import Scroller +from openpilot.system.ui.widgets import Widget + + +class CruiseLayout(Widget): + def __init__(self): + super().__init__() + + self._params = Params() + items = self._initialize_items() + self._scroller = Scroller(items, line_separator=True, spacing=0) + + def _initialize_items(self): + + items = [ + ] + return items + + def _render(self, rect): + self._scroller.render(rect) + + def show_event(self): + self._scroller.show_event() diff --git a/selfdrive/ui/sunnypilot/layouts/settings/device.py b/selfdrive/ui/sunnypilot/layouts/settings/device.py new file mode 100644 index 000000000..081969cf1 --- /dev/null +++ b/selfdrive/ui/sunnypilot/layouts/settings/device.py @@ -0,0 +1,12 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +from openpilot.selfdrive.ui.layouts.settings.device import DeviceLayout + + +class DeviceLayoutSP(DeviceLayout): + def __init__(self): + DeviceLayout.__init__(self) diff --git a/selfdrive/ui/sunnypilot/layouts/settings/display.py b/selfdrive/ui/sunnypilot/layouts/settings/display.py new file mode 100644 index 000000000..d1d0a661b --- /dev/null +++ b/selfdrive/ui/sunnypilot/layouts/settings/display.py @@ -0,0 +1,30 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +from openpilot.common.params import Params +from openpilot.system.ui.widgets.scroller_tici import Scroller +from openpilot.system.ui.widgets import Widget + + +class DisplayLayout(Widget): + def __init__(self): + super().__init__() + + self._params = Params() + items = self._initialize_items() + self._scroller = Scroller(items, line_separator=True, spacing=0) + + def _initialize_items(self): + items = [ + + ] + return items + + def _render(self, rect): + self._scroller.render(rect) + + def show_event(self): + self._scroller.show_event() diff --git a/selfdrive/ui/sunnypilot/layouts/settings/models.py b/selfdrive/ui/sunnypilot/layouts/settings/models.py new file mode 100644 index 000000000..add437b12 --- /dev/null +++ b/selfdrive/ui/sunnypilot/layouts/settings/models.py @@ -0,0 +1,30 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +from openpilot.common.params import Params +from openpilot.system.ui.widgets.scroller_tici import Scroller +from openpilot.system.ui.widgets import Widget + + +class ModelsLayout(Widget): + def __init__(self): + super().__init__() + + self._params = Params() + items = self._initialize_items() + self._scroller = Scroller(items, line_separator=True, spacing=0) + + def _initialize_items(self): + items = [ + + ] + return items + + def _render(self, rect): + self._scroller.render(rect) + + def show_event(self): + self._scroller.show_event() diff --git a/selfdrive/ui/sunnypilot/layouts/settings/navigation.py b/selfdrive/ui/sunnypilot/layouts/settings/navigation.py new file mode 100644 index 000000000..1f44775bb --- /dev/null +++ b/selfdrive/ui/sunnypilot/layouts/settings/navigation.py @@ -0,0 +1,30 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +from openpilot.common.params import Params +from openpilot.system.ui.widgets.scroller_tici import Scroller +from openpilot.system.ui.widgets import Widget + + +class NavigationLayout(Widget): + def __init__(self): + super().__init__() + + self._params = Params() + items = self._initialize_items() + self._scroller = Scroller(items, line_separator=True, spacing=0) + + def _initialize_items(self): + items = [ + + ] + return items + + def _render(self, rect): + self._scroller.render(rect) + + def show_event(self): + self._scroller.show_event() diff --git a/selfdrive/ui/sunnypilot/layouts/settings/osm.py b/selfdrive/ui/sunnypilot/layouts/settings/osm.py new file mode 100644 index 000000000..d57a0de9d --- /dev/null +++ b/selfdrive/ui/sunnypilot/layouts/settings/osm.py @@ -0,0 +1,30 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +from openpilot.common.params import Params +from openpilot.system.ui.widgets.scroller_tici import Scroller +from openpilot.system.ui.widgets import Widget + + +class OSMLayout(Widget): + def __init__(self): + super().__init__() + + self._params = Params() + items = self._initialize_items() + self._scroller = Scroller(items, line_separator=True, spacing=0) + + def _initialize_items(self): + items = [ + + ] + return items + + def _render(self, rect): + self._scroller.render(rect) + + def show_event(self): + self._scroller.show_event() diff --git a/selfdrive/ui/sunnypilot/layouts/settings/settings.py b/selfdrive/ui/sunnypilot/layouts/settings/settings.py new file mode 100644 index 000000000..bf174de90 --- /dev/null +++ b/selfdrive/ui/sunnypilot/layouts/settings/settings.py @@ -0,0 +1,202 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +from dataclasses import dataclass +from enum import IntEnum + +import pyray as rl +from openpilot.selfdrive.ui.layouts.settings import settings as OP +from openpilot.selfdrive.ui.layouts.settings.developer import DeveloperLayout +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.device import DeviceLayoutSP +from openpilot.selfdrive.ui.layouts.settings.firehose import FirehoseLayout +from openpilot.selfdrive.ui.layouts.settings.software import SoftwareLayout +from openpilot.selfdrive.ui.layouts.settings.toggles import TogglesLayout +from openpilot.system.ui.lib.application import gui_app, MousePos +from openpilot.system.ui.lib.multilang import tr_noop +from openpilot.system.ui.sunnypilot.lib.styles import style +from openpilot.system.ui.widgets.scroller_tici import Scroller +from openpilot.system.ui.lib.text_measure import measure_text_cached +from openpilot.system.ui.widgets.network import NetworkUI +from openpilot.system.ui.lib.wifi_manager import WifiManager +from openpilot.system.ui.widgets import Widget +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.models import ModelsLayout +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.sunnylink import SunnylinkLayout +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.osm import OSMLayout +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.trips import TripsLayout +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle import VehicleLayout +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.steering import SteeringLayout +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.cruise import CruiseLayout +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.visuals import VisualsLayout +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.display import DisplayLayout + +# from openpilot.selfdrive.ui.sunnypilot.layouts.settings.navigation import NavigationLayout + +OP.PANEL_COLOR = rl.Color(10, 10, 10, 255) +ICON_SIZE = 70 + +OP.PanelType = IntEnum( # type: ignore + "PanelType", + [es.name for es in OP.PanelType] + [ + "SUNNYLINK", + "MODELS", + "STEERING", + "CRUISE", + "VISUALS", + "DISPLAY", + "OSM", + "NAVIGATION", + "TRIPS", + "VEHICLE", + ], + start=0, +) + + +@dataclass +class PanelInfo(OP.PanelInfo): + icon: str = "" + + +class NavButton(Widget): + def __init__(self, parent, p_type, p_info): + super().__init__() + self.parent = parent + self.panel_type = p_type + self.panel_info = p_info + + def _render(self, rect): + is_selected = self.panel_type == self.parent._current_panel + text_color = OP.TEXT_SELECTED if is_selected else OP.TEXT_NORMAL + content_x = rect.x + 90 + text_size = measure_text_cached(self.parent._font_medium, self.panel_info.name, 65) + + # Draw background if selected + if is_selected: + self.container_rect = rl.Rectangle( + content_x - 50, rect.y, OP.SIDEBAR_WIDTH - 50, OP.NAV_BTN_HEIGHT + ) + rl.draw_rectangle_rounded(self.container_rect, 0.2, 5, OP.CLOSE_BTN_COLOR) + + if self.panel_info.icon: + icon_texture = gui_app.texture(self.panel_info.icon, ICON_SIZE, ICON_SIZE, keep_aspect_ratio=True) + rl.draw_texture(icon_texture, int(content_x), int(rect.y + (OP.NAV_BTN_HEIGHT - icon_texture.height) / 2), + rl.WHITE) + content_x += ICON_SIZE + 20 + + # Draw button text (right-aligned) + text_pos = rl.Vector2( + content_x, + rect.y + (OP.NAV_BTN_HEIGHT - text_size.y) / 2 + ) + rl.draw_text_ex(self.parent._font_medium, self.panel_info.name, text_pos, 55, 0, text_color) + + # Store button rect for click detection + self.panel_info.button_rect = rect + + +class SettingsLayoutSP(OP.SettingsLayout): + def __init__(self): + OP.SettingsLayout.__init__(self) + self._nav_items: list[Widget] = [] + + # Create sidebar scroller + self._sidebar_scroller = Scroller([], spacing=0, line_separator=False, pad_end=False) + + # Panel configuration + wifi_manager = WifiManager() + wifi_manager.set_active(False) + + self._panels = { + OP.PanelType.DEVICE: PanelInfo(tr_noop("Device"), DeviceLayoutSP(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_home.png"), + OP.PanelType.NETWORK: PanelInfo(tr_noop("Network"), NetworkUI(wifi_manager), icon="icons/network.png"), + OP.PanelType.SUNNYLINK: PanelInfo(tr_noop("sunnylink"), SunnylinkLayout(), icon="icons/shell.png"), + OP.PanelType.TOGGLES: PanelInfo(tr_noop("Toggles"), TogglesLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_toggle.png"), + OP.PanelType.SOFTWARE: PanelInfo(tr_noop("Software"), SoftwareLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_software.png"), + OP.PanelType.MODELS: PanelInfo(tr_noop("Models"), ModelsLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_models.png"), + OP.PanelType.STEERING: PanelInfo(tr_noop("Steering"), SteeringLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_lateral.png"), + OP.PanelType.CRUISE: PanelInfo(tr_noop("Cruise"), CruiseLayout(), icon="icons/speed_limit.png"), + OP.PanelType.VISUALS: PanelInfo(tr_noop("Visuals"), VisualsLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_visuals.png"), + OP.PanelType.DISPLAY: PanelInfo(tr_noop("Display"), DisplayLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_display.png"), + OP.PanelType.OSM: PanelInfo(tr_noop("OSM"), OSMLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_map.png"), + # OP.PanelType.NAVIGATION: PanelInfo(tr_noop("Navigation"), NavigationLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_map.png"), + OP.PanelType.TRIPS: PanelInfo(tr_noop("Trips"), TripsLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_trips.png"), + OP.PanelType.VEHICLE: PanelInfo(tr_noop("Vehicle"), VehicleLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_vehicle.png"), + OP.PanelType.FIREHOSE: PanelInfo(tr_noop("Firehose"), FirehoseLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_firehose.png"), + OP.PanelType.DEVELOPER: PanelInfo(tr_noop("Developer"), DeveloperLayout(), icon="icons/shell.png"), + } + + def _draw_sidebar(self, rect: rl.Rectangle): + rl.draw_rectangle_rec(rect, OP.SIDEBAR_COLOR) + + # Close button + close_btn_rect = rl.Rectangle( + rect.x + style.ITEM_PADDING * 3, rect.y + style.ITEM_PADDING * 2, style.CLOSE_BTN_SIZE, style.CLOSE_BTN_SIZE + ) + + pressed = (rl.is_mouse_button_down(rl.MouseButton.MOUSE_BUTTON_LEFT) and + rl.check_collision_point_rec(rl.get_mouse_position(), close_btn_rect)) + close_color = OP.CLOSE_BTN_PRESSED if pressed else OP.CLOSE_BTN_COLOR + rl.draw_rectangle_rounded(close_btn_rect, 1.0, 20, close_color) + + icon_color = rl.Color(255, 255, 255, 255) if not pressed else rl.Color(220, 220, 220, 255) + icon_dest = rl.Rectangle( + close_btn_rect.x + (close_btn_rect.width - self._close_icon.width) / 2, + close_btn_rect.y + (close_btn_rect.height - self._close_icon.height) / 2, + self._close_icon.width, + self._close_icon.height, + ) + rl.draw_texture_pro( + self._close_icon, + rl.Rectangle(0, 0, self._close_icon.width, self._close_icon.height), + icon_dest, + rl.Vector2(0, 0), + 0, + icon_color, + ) + + # Store close button rect for click detection + self._close_btn_rect = close_btn_rect + + # Navigation buttons with scroller + if not self._nav_items: + for panel_type, panel_info in self._panels.items(): + nav_button = NavButton(self, panel_type, panel_info) + nav_button.rect.width = rect.width - 100 # Full width minus padding + nav_button.rect.height = OP.NAV_BTN_HEIGHT + self._nav_items.append(nav_button) + self._sidebar_scroller.add_widget(nav_button) + + # Draw navigation section with scroller + nav_rect = rl.Rectangle( + rect.x, + self._close_btn_rect.height + style.ITEM_PADDING * 4, # Starting Y position for nav items + rect.width, + rect.height - 300 # Remaining height after close button + ) + + if self._nav_items: + self._sidebar_scroller.render(nav_rect) + return + + def _handle_mouse_release(self, mouse_pos: MousePos) -> bool: + # Check close button + if rl.check_collision_point_rec(mouse_pos, self._close_btn_rect): + if self._close_callback: + self._close_callback() + return True + + # Check navigation buttons + for panel_type, panel_info in self._panels.items(): + if rl.check_collision_point_rec(mouse_pos, panel_info.button_rect) and self._sidebar_scroller.scroll_panel.is_touch_valid(): + self.set_current_panel(panel_type) + return True + + return False + + def show_event(self): + super().show_event() + self._panels[self._current_panel].instance.show_event() + self._sidebar_scroller.show_event() diff --git a/selfdrive/ui/sunnypilot/layouts/settings/steering.py b/selfdrive/ui/sunnypilot/layouts/settings/steering.py new file mode 100644 index 000000000..ac67eb916 --- /dev/null +++ b/selfdrive/ui/sunnypilot/layouts/settings/steering.py @@ -0,0 +1,30 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +from openpilot.common.params import Params +from openpilot.system.ui.widgets.scroller_tici import Scroller +from openpilot.system.ui.widgets import Widget + + +class SteeringLayout(Widget): + def __init__(self): + super().__init__() + + self._params = Params() + items = self._initialize_items() + self._scroller = Scroller(items, line_separator=True, spacing=0) + + def _initialize_items(self): + items = [ + + ] + return items + + def _render(self, rect): + self._scroller.render(rect) + + def show_event(self): + self._scroller.show_event() diff --git a/selfdrive/ui/sunnypilot/layouts/settings/sunnylink.py b/selfdrive/ui/sunnypilot/layouts/settings/sunnylink.py new file mode 100644 index 000000000..b1e12c17a --- /dev/null +++ b/selfdrive/ui/sunnypilot/layouts/settings/sunnylink.py @@ -0,0 +1,30 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +from openpilot.common.params import Params +from openpilot.system.ui.widgets.scroller_tici import Scroller +from openpilot.system.ui.widgets import Widget + + +class SunnylinkLayout(Widget): + def __init__(self): + super().__init__() + + self._params = Params() + items = self._initialize_items() + self._scroller = Scroller(items, line_separator=True, spacing=0) + + def _initialize_items(self): + items = [ + + ] + return items + + def _render(self, rect): + self._scroller.render(rect) + + def show_event(self): + self._scroller.show_event() diff --git a/selfdrive/ui/sunnypilot/layouts/settings/trips.py b/selfdrive/ui/sunnypilot/layouts/settings/trips.py new file mode 100644 index 000000000..a318cebeb --- /dev/null +++ b/selfdrive/ui/sunnypilot/layouts/settings/trips.py @@ -0,0 +1,30 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +from openpilot.common.params import Params +from openpilot.system.ui.widgets.scroller_tici import Scroller +from openpilot.system.ui.widgets import Widget + + +class TripsLayout(Widget): + def __init__(self): + super().__init__() + + self._params = Params() + items = self._initialize_items() + self._scroller = Scroller(items, line_separator=True, spacing=0) + + def _initialize_items(self): + items = [ + + ] + return items + + def _render(self, rect): + self._scroller.render(rect) + + def show_event(self): + self._scroller.show_event() diff --git a/selfdrive/ui/sunnypilot/layouts/settings/vehicle.py b/selfdrive/ui/sunnypilot/layouts/settings/vehicle.py new file mode 100644 index 000000000..d04816a41 --- /dev/null +++ b/selfdrive/ui/sunnypilot/layouts/settings/vehicle.py @@ -0,0 +1,30 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +from openpilot.common.params import Params +from openpilot.system.ui.widgets.scroller_tici import Scroller +from openpilot.system.ui.widgets import Widget + + +class VehicleLayout(Widget): + def __init__(self): + super().__init__() + + self._params = Params() + items = self._initialize_items() + self._scroller = Scroller(items, line_separator=True, spacing=0) + + def _initialize_items(self): + items = [ + + ] + return items + + def _render(self, rect): + self._scroller.render(rect) + + def show_event(self): + self._scroller.show_event() diff --git a/selfdrive/ui/sunnypilot/layouts/settings/visuals.py b/selfdrive/ui/sunnypilot/layouts/settings/visuals.py new file mode 100644 index 000000000..1036af3e5 --- /dev/null +++ b/selfdrive/ui/sunnypilot/layouts/settings/visuals.py @@ -0,0 +1,30 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +from openpilot.common.params import Params +from openpilot.system.ui.widgets.scroller_tici import Scroller +from openpilot.system.ui.widgets import Widget + + +class VisualsLayout(Widget): + def __init__(self): + super().__init__() + + self._params = Params() + items = self._initialize_items() + self._scroller = Scroller(items, line_separator=True, spacing=0) + + def _initialize_items(self): + items = [ + + ] + return items + + def _render(self, rect): + self._scroller.render(rect) + + def show_event(self): + self._scroller.show_event() diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py index d3de616cc..cd383e4bc 100755 --- a/selfdrive/ui/tests/test_ui/raylib_screenshots.py +++ b/selfdrive/ui/tests/test_ui/raylib_screenshots.py @@ -41,47 +41,47 @@ def put_update_params(params: Params): params.put("UpdaterTargetBranch", BRANCH_NAME) -def setup_homescreen(click, pm: PubMaster): +def setup_homescreen(click, pm: PubMaster, scroll=None): pass -def setup_homescreen_update_available(click, pm: PubMaster): +def setup_homescreen_update_available(click, pm: PubMaster, scroll=None): params = Params() params.put_bool("UpdateAvailable", True) put_update_params(params) setup_offroad_alert(click, pm) -def setup_settings(click, pm: PubMaster): +def setup_settings(click, pm: PubMaster, scroll=None): click(100, 100) -def close_settings(click, pm: PubMaster): - click(240, 216) +def close_settings(click, pm: PubMaster, scroll=None): + click(140, 120) -def setup_settings_network(click, pm: PubMaster): +def setup_settings_network(click, pm: PubMaster, scroll=None): setup_settings(click, pm) click(278, 450) -def setup_settings_network_advanced(click, pm: PubMaster): - setup_settings_network(click, pm) +def setup_settings_network_advanced(click, pm: PubMaster, scroll=None): + setup_settings_network(click, pm, scroll=scroll) click(1880, 100) -def setup_settings_toggles(click, pm: PubMaster): +def setup_settings_toggles(click, pm: PubMaster, scroll=None): setup_settings(click, pm) - click(278, 600) + click(278, 620) -def setup_settings_software(click, pm: PubMaster): +def setup_settings_software(click, pm: PubMaster, scroll=None): put_update_params(Params()) setup_settings(click, pm) - click(278, 720) + click(278, 730) -def setup_settings_software_download(click, pm: PubMaster): +def setup_settings_software_download(click, pm: PubMaster, scroll=None): params = Params() # setup_settings_software but with "DOWNLOAD" button to test long text params.put("UpdaterState", "idle") @@ -89,13 +89,13 @@ def setup_settings_software_download(click, pm: PubMaster): setup_settings_software(click, pm) -def setup_settings_software_release_notes(click, pm: PubMaster): - setup_settings_software(click, pm) +def setup_settings_software_release_notes(click, pm: PubMaster, scroll=None): + setup_settings_software(click, pm, scroll=scroll) click(588, 110) # expand description for current version -def setup_settings_software_branch_switcher(click, pm: PubMaster): - setup_settings_software(click, pm) +def setup_settings_software_branch_switcher(click, pm: PubMaster, scroll=None): + setup_settings_software(click, pm, scroll=scroll) params = Params() params.put("UpdaterAvailableBranches", f"master,nightly,release,{BRANCH_NAME}") params.put("GitBranch", BRANCH_NAME) # should be on top @@ -103,30 +103,32 @@ def setup_settings_software_branch_switcher(click, pm: PubMaster): click(1984, 449) -def setup_settings_firehose(click, pm: PubMaster): +def setup_settings_firehose(click, pm: PubMaster, scroll=None): setup_settings(click, pm) - click(278, 845) + scroll(-1000, 278, 950) + click(278, 850) -def setup_settings_developer(click, pm: PubMaster): +def setup_settings_developer(click, pm: PubMaster, scroll=None): CP = car.CarParams() CP.alphaLongitudinalAvailable = True # show alpha long control toggle Params().put("CarParamsPersistent", CP.to_bytes()) setup_settings(click, pm) + scroll(-1000, 278, 950) click(278, 950) -def setup_keyboard(click, pm: PubMaster): - setup_settings_developer(click, pm) +def setup_keyboard(click, pm: PubMaster, scroll=None): + setup_settings_developer(click, pm, scroll=scroll) click(1930, 470) -def setup_pair_device(click, pm: PubMaster): +def setup_pair_device(click, pm: PubMaster, scroll=None): click(1950, 800) -def setup_offroad_alert(click, pm: PubMaster): +def setup_offroad_alert(click, pm: PubMaster, scroll=None): put_update_params(Params()) set_offroad_alert("Offroad_TemperatureTooHigh", True, extra_text='99C') set_offroad_alert("Offroad_ExcessiveActuation", True, extra_text='longitudinal') @@ -137,22 +139,73 @@ def setup_offroad_alert(click, pm: PubMaster): close_settings(click, pm) -def setup_confirmation_dialog(click, pm: PubMaster): +def setup_confirmation_dialog(click, pm: PubMaster, scroll=None): setup_settings(click, pm) click(1985, 791) # reset calibration -def setup_experimental_mode_description(click, pm: PubMaster): +def setup_experimental_mode_description(click, pm: PubMaster, scroll=None): setup_settings_toggles(click, pm) click(1200, 280) # expand description for experimental mode -def setup_openpilot_long_confirmation_dialog(click, pm: PubMaster): - setup_settings_developer(click, pm) +def setup_openpilot_long_confirmation_dialog(click, pm: PubMaster, scroll=None): + setup_settings_developer(click, pm, scroll=scroll) click(650, 960) # toggle openpilot longitudinal control -def setup_onroad(click, pm: PubMaster): +def setup_settings_sunnylink(click, pm: PubMaster, scroll=None): + setup_settings(click, pm) + click(278, 510) + + +def setup_settings_models(click, pm: PubMaster, scroll=None): + setup_settings(click, pm) + click(278, 840) + + +def setup_settings_steering(click, pm: PubMaster, scroll=None): + setup_settings(click, pm) + click(278, 950) + + +def setup_settings_cruise(click, pm: PubMaster, scroll=None): + setup_settings(click, pm) + click(278, 1017) + scroll(-140, 278, 950) + + +def setup_settings_visuals(click, pm: PubMaster, scroll=None): + setup_settings(click, pm) + scroll(-1000, 278, 950) + click(278, 330) + + +def setup_settings_display(click, pm: PubMaster, scroll=None): + setup_settings(click, pm) + scroll(-1000, 278, 950) + click(278, 420) + + +def setup_settings_osm(click, pm: PubMaster, scroll=None): + setup_settings(click, pm) + scroll(-1000, 278, 950) + click(278, 520) + + +def setup_settings_trips(click, pm: PubMaster, scroll=None): + setup_settings(click, pm) + scroll(-1000, 278, 950) + click(278, 630) + + +def setup_settings_vehicle(click, pm: PubMaster, scroll=None): + setup_settings(click, pm) + scroll(-1000, 278, 950) + click(278, 750) + + +def setup_onroad(click, pm: PubMaster, scroll=None): ds = messaging.new_message('deviceState') ds.deviceState.started = True @@ -173,7 +226,7 @@ def setup_onroad(click, pm: PubMaster): time.sleep(0.05) -def setup_onroad_sidebar(click, pm: PubMaster): +def setup_onroad_sidebar(click, pm: PubMaster, scroll=None): setup_onroad(click, pm) click(100, 100) # open sidebar @@ -192,23 +245,23 @@ def setup_onroad_alert(click, pm: PubMaster, size: log.SelfdriveState.AlertSize, time.sleep(0.05) -def setup_onroad_small_alert(click, pm: PubMaster): +def setup_onroad_small_alert(click, pm: PubMaster, scroll=None): setup_onroad_alert(click, pm, AlertSize.small, "Small Alert", "This is a small alert", AlertStatus.normal) -def setup_onroad_medium_alert(click, pm: PubMaster): +def setup_onroad_medium_alert(click, pm: PubMaster, scroll=None): setup_onroad_alert(click, pm, AlertSize.mid, "Medium Alert", "This is a medium alert", AlertStatus.userPrompt) -def setup_onroad_full_alert(click, pm: PubMaster): +def setup_onroad_full_alert(click, pm: PubMaster, scroll=None): setup_onroad_alert(click, pm, AlertSize.full, "DISENGAGE IMMEDIATELY", "Driver Distracted", AlertStatus.critical) -def setup_onroad_full_alert_multiline(click, pm: PubMaster): +def setup_onroad_full_alert_multiline(click, pm: PubMaster, scroll=None): setup_onroad_alert(click, pm, AlertSize.full, "Reverse\nGear", "", AlertStatus.normal) -def setup_onroad_full_alert_long_text(click, pm: PubMaster): +def setup_onroad_full_alert_long_text(click, pm: PubMaster, scroll=None): setup_onroad_alert(click, pm, AlertSize.full, "TAKE CONTROL IMMEDIATELY", "Calibration Invalid: Remount Device & Recalibrate", AlertStatus.userPrompt) @@ -243,6 +296,19 @@ CASES = { "onroad_full_alert_long_text": setup_onroad_full_alert_long_text, } +# sunnypilot cases +CASES.update({ + "settings_sunnylink": setup_settings_sunnylink, + "settings_models": setup_settings_models, + "settings_steering": setup_settings_steering, + "settings_cruise": setup_settings_cruise, + "settings_visuals": setup_settings_visuals, + "settings_display": setup_settings_display, + "settings_osm": setup_settings_osm, + "settings_trips": setup_settings_trips, + "settings_vehicle": setup_settings_vehicle, +}) + class TestUI: def __init__(self): @@ -276,11 +342,15 @@ class TestUI: time.sleep(0.01) pyautogui.mouseUp(self.ui.left + x, self.ui.top + y, *args, **kwargs) + def scroll(self, clicks, x, y, *args, **kwargs): + pyautogui.scroll(clicks, self.ui.left + x, self.ui.top + y, *args, **kwargs) + time.sleep(UI_DELAY) + @with_processes(["ui"]) def test_ui(self, name, setup_case): self.setup() time.sleep(UI_DELAY) # wait for UI to start - setup_case(self.click, self.pm) + setup_case(self.click, self.pm, self.scroll) self.screenshot(name) diff --git a/sunnypilot/selfdrive/assets/offroad/icon_firehose.png b/sunnypilot/selfdrive/assets/offroad/icon_firehose.png new file mode 100644 index 000000000..d579937f3 --- /dev/null +++ b/sunnypilot/selfdrive/assets/offroad/icon_firehose.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a258a022f0ab90368327d899ed4fb85b47dd0e35c93508e35851d9c3184528c0 +size 36382 diff --git a/sunnypilot/selfdrive/assets/offroad/icon_home.png b/sunnypilot/selfdrive/assets/offroad/icon_home.png new file mode 100644 index 000000000..1229af846 --- /dev/null +++ b/sunnypilot/selfdrive/assets/offroad/icon_home.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8623dbc5c7dd043a91d98777cb423cfd116014ed6390af6d7d00c1f8dea3c6e8 +size 1181 diff --git a/system/ui/sunnypilot/lib/styles.py b/system/ui/sunnypilot/lib/styles.py index a232ac192..4880ad58d 100644 --- a/system/ui/sunnypilot/lib/styles.py +++ b/system/ui/sunnypilot/lib/styles.py @@ -17,6 +17,7 @@ class Base: ITEM_TEXT_FONT_SIZE = 50 ITEM_DESC_FONT_SIZE = 40 ITEM_DESC_V_OFFSET = 150 + CLOSE_BTN_SIZE = 160 # Toggle Control TOGGLE_HEIGHT = 120 @@ -29,7 +30,7 @@ class DefaultStyleSP(Base): # Base Colors BASE_BG_COLOR = rl.Color(57, 57, 57, 255) # Grey ON_BG_COLOR = rl.Color(28, 101, 186, 255) # Blue - OFF_BG_COLOR = rl.Color(70, 70, 70, 255) # Lighter Grey + OFF_BG_COLOR = BASE_BG_COLOR ON_HOVER_BG_COLOR = rl.Color(17, 78, 150, 255) # Dark Blue OFF_HOVER_BG_COLOR = rl.Color(21, 21, 21, 255) # Dark gray DISABLED_ON_BG_COLOR = rl.Color(37, 70, 107, 255) # Dull Blue From 4aa796412b01c95f1ae5bf7bc54f6a5c209b0c4d Mon Sep 17 00:00:00 2001 From: infiniteCable2 Date: Mon, 24 Nov 2025 20:59:49 +0100 Subject: [PATCH 436/910] add custom toggle tab to sunnypilot ui --- selfdrive/ui/sunnypilot/layouts/settings/settings.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/selfdrive/ui/sunnypilot/layouts/settings/settings.py b/selfdrive/ui/sunnypilot/layouts/settings/settings.py index bf174de90..902213d21 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/settings.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/settings.py @@ -14,6 +14,7 @@ from openpilot.selfdrive.ui.sunnypilot.layouts.settings.device import DeviceLayo from openpilot.selfdrive.ui.layouts.settings.firehose import FirehoseLayout from openpilot.selfdrive.ui.layouts.settings.software import SoftwareLayout from openpilot.selfdrive.ui.layouts.settings.toggles import TogglesLayout +from openpilot.selfdrive.ui.layouts.settings.ictoggles import ICTogglesLayout from openpilot.system.ui.lib.application import gui_app, MousePos from openpilot.system.ui.lib.multilang import tr_noop from openpilot.system.ui.sunnypilot.lib.styles import style @@ -114,6 +115,7 @@ class SettingsLayoutSP(OP.SettingsLayout): OP.PanelType.NETWORK: PanelInfo(tr_noop("Network"), NetworkUI(wifi_manager), icon="icons/network.png"), OP.PanelType.SUNNYLINK: PanelInfo(tr_noop("sunnylink"), SunnylinkLayout(), icon="icons/shell.png"), OP.PanelType.TOGGLES: PanelInfo(tr_noop("Toggles"), TogglesLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_toggle.png"), + OP.PanelType.ICTOGGLES: PanelInfo(tr_noop("infiniteCable"), ICTogglesLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_toggle.png"), OP.PanelType.SOFTWARE: PanelInfo(tr_noop("Software"), SoftwareLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_software.png"), OP.PanelType.MODELS: PanelInfo(tr_noop("Models"), ModelsLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_models.png"), OP.PanelType.STEERING: PanelInfo(tr_noop("Steering"), SteeringLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_lateral.png"), From a3cbde0de1ce731b63105745a5185a925a95a112 Mon Sep 17 00:00:00 2001 From: infiniteCable2 Date: Mon, 24 Nov 2025 21:07:53 +0100 Subject: [PATCH 437/910] sunnypilot style :) --- selfdrive/ui/layouts/settings/ictoggles.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/selfdrive/ui/layouts/settings/ictoggles.py b/selfdrive/ui/layouts/settings/ictoggles.py index 7df294e8a..11d9f5ea5 100644 --- a/selfdrive/ui/layouts/settings/ictoggles.py +++ b/selfdrive/ui/layouts/settings/ictoggles.py @@ -7,6 +7,9 @@ from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.lib.multilang import tr, tr_noop from openpilot.selfdrive.ui.ui_state import ui_state +if gui_app.sunnypilot_ui(): + from openpilot.system.ui.sunnypilot.widgets.list_view import toggle_item_sp as toggle_item + # Description constants DESCRIPTIONS = { "EnableCurvatureController": tr_noop( From c67afb45aeb7b0e5e37f48adb8f0c1da1edeaf2f Mon Sep 17 00:00:00 2001 From: Bruce Wayne Date: Mon, 24 Nov 2025 14:20:20 -0800 Subject: [PATCH 438/910] dead test --- selfdrive/modeld/tests/__init__.py | 0 selfdrive/modeld/tests/test_modeld.py | 102 -------------------------- 2 files changed, 102 deletions(-) delete mode 100644 selfdrive/modeld/tests/__init__.py delete mode 100644 selfdrive/modeld/tests/test_modeld.py diff --git a/selfdrive/modeld/tests/__init__.py b/selfdrive/modeld/tests/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/selfdrive/modeld/tests/test_modeld.py b/selfdrive/modeld/tests/test_modeld.py deleted file mode 100644 index 6927c9e47..000000000 --- a/selfdrive/modeld/tests/test_modeld.py +++ /dev/null @@ -1,102 +0,0 @@ -import numpy as np -import random - -import cereal.messaging as messaging -from msgq.visionipc import VisionIpcServer, VisionStreamType -from opendbc.car.car_helpers import get_demo_car_params -from openpilot.common.params import Params -from openpilot.common.transformations.camera import DEVICE_CAMERAS -from openpilot.common.realtime import DT_MDL -from openpilot.system.manager.process_config import managed_processes -from openpilot.selfdrive.test.process_replay.vision_meta import meta_from_camera_state - -CAM = DEVICE_CAMERAS[("tici", "ar0231")].fcam -IMG = np.zeros(int(CAM.width*CAM.height*(3/2)), dtype=np.uint8) -IMG_BYTES = IMG.flatten().tobytes() - - -class TestModeld: - - def setup_method(self): - self.vipc_server = VisionIpcServer("camerad") - self.vipc_server.create_buffers(VisionStreamType.VISION_STREAM_ROAD, 40, CAM.width, CAM.height) - self.vipc_server.create_buffers(VisionStreamType.VISION_STREAM_DRIVER, 40, CAM.width, CAM.height) - self.vipc_server.create_buffers(VisionStreamType.VISION_STREAM_WIDE_ROAD, 40, CAM.width, CAM.height) - self.vipc_server.start_listener() - Params().put("CarParams", get_demo_car_params().to_bytes()) - - self.sm = messaging.SubMaster(['modelV2', 'cameraOdometry']) - self.pm = messaging.PubMaster(['roadCameraState', 'wideRoadCameraState', 'liveCalibration']) - - managed_processes['modeld'].start() - self.pm.wait_for_readers_to_update("roadCameraState", 10) - - def teardown_method(self): - managed_processes['modeld'].stop() - del self.vipc_server - - def _send_frames(self, frame_id, cams=None): - if cams is None: - cams = ('roadCameraState', 'wideRoadCameraState') - - cs = None - for cam in cams: - msg = messaging.new_message(cam) - cs = getattr(msg, cam) - cs.frameId = frame_id - cs.timestampSof = int((frame_id * DT_MDL) * 1e9) - cs.timestampEof = int(cs.timestampSof + (DT_MDL * 1e9)) - cam_meta = meta_from_camera_state(cam) - - self.pm.send(msg.which(), msg) - self.vipc_server.send(cam_meta.stream, IMG_BYTES, cs.frameId, - cs.timestampSof, cs.timestampEof) - return cs - - def _wait(self): - self.sm.update(5000) - if self.sm['modelV2'].frameId != self.sm['cameraOdometry'].frameId: - self.sm.update(1000) - - def test_modeld(self): - for n in range(1, 500): - cs = self._send_frames(n) - self._wait() - - mdl = self.sm['modelV2'] - assert mdl.frameId == n - assert mdl.frameIdExtra == n - assert mdl.timestampEof == cs.timestampEof - assert mdl.frameAge == 0 - assert mdl.frameDropPerc == 0 - - odo = self.sm['cameraOdometry'] - assert odo.frameId == n - assert odo.timestampEof == cs.timestampEof - - def test_dropped_frames(self): - """ - modeld should only run on consecutive road frames - """ - frame_id = -1 - road_frames = list() - for n in range(1, 50): - if (random.random() < 0.1) and n > 3: - cams = random.choice([(), ('wideRoadCameraState', )]) - self._send_frames(n, cams) - else: - self._send_frames(n) - road_frames.append(n) - self._wait() - - if len(road_frames) < 3 or road_frames[-1] - road_frames[-2] == 1: - frame_id = road_frames[-1] - - mdl = self.sm['modelV2'] - odo = self.sm['cameraOdometry'] - assert mdl.frameId == frame_id - assert mdl.frameIdExtra == frame_id - assert odo.frameId == frame_id - if n != frame_id: - assert not self.sm.updated['modelV2'] - assert not self.sm.updated['cameraOdometry'] From c53e2134e2516d5f015286caf7b7bcc1dedd4d09 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Mon, 24 Nov 2025 23:33:40 +0100 Subject: [PATCH 439/910] sunnylink: centralize key pair handling in sunnylink registration (#1510) * refactor(api): centralize key pair handling in SunnyLink registration - Replaced manual key reading with `BaseApi.get_key_pair` for consistency. - Simplifies code and improves maintainability of key management. * cleanup --- sunnypilot/sunnylink/api.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/sunnypilot/sunnylink/api.py b/sunnypilot/sunnylink/api.py index 330011eb7..26c3f3462 100644 --- a/sunnypilot/sunnylink/api.py +++ b/sunnypilot/sunnylink/api.py @@ -3,7 +3,6 @@ import os import random import time from datetime import datetime, timedelta -from pathlib import Path import jwt from openpilot.common.api.base import BaseApi @@ -81,23 +80,19 @@ class SunnylinkApi(BaseApi): if sunnylink_dongle_id not in (None, UNREGISTERED_SUNNYLINK_DONGLE_ID): return sunnylink_dongle_id - privkey_path = Path(f"{Paths.persist_root()}/comma/id_rsa") - pubkey_path = Path(f"{Paths.persist_root()}/comma/id_rsa.pub") + jwt_algo, private_key, public_key = BaseApi.get_key_pair() start_time = time.monotonic() successful_registration = False - if not pubkey_path.is_file(): + if not public_key: sunnylink_dongle_id = UNREGISTERED_SUNNYLINK_DONGLE_ID self._status_update("Public key not found, setting dongle ID to unregistered.") else: Params().put("LastSunnylinkPingTime", 0) # Reset the last ping time to 0 if we are trying to register - with pubkey_path.open() as f1, privkey_path.open() as f2: - public_key = f1.read() - private_key = f2.read() backoff = 1 while True: - register_token = jwt.encode({'register': True, 'exp': datetime.utcnow() + timedelta(hours=1)}, private_key, algorithm='RS256') + register_token = jwt.encode({'register': True, 'exp': datetime.utcnow() + timedelta(hours=1)}, private_key, algorithm=jwt_algo) try: if verbose or time.monotonic() - start_time < timeout / 2: self._status_update("Registering device to sunnylink...") From f01391a7d9b5d0b7a56a346d85c7de42302656b7 Mon Sep 17 00:00:00 2001 From: felsager <76905857+felsager@users.noreply.github.com> Date: Tue, 25 Nov 2025 10:23:02 -0800 Subject: [PATCH 440/910] latcontrol_torque: delay independent jerk and lower kp and lower friction threshold (#36619) --- opendbc_repo | 2 +- selfdrive/controls/lib/latcontrol_torque.py | 35 +++++++++------------ selfdrive/test/process_replay/ref_commit | 2 +- 3 files changed, 17 insertions(+), 22 deletions(-) diff --git a/opendbc_repo b/opendbc_repo index b59f8bdcc..6171d1a97 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit b59f8bdcca8d375b4a5a652d2f2d2ec9cd3503d3 +Subproject commit 6171d1a976b632c4804e90e74a78370532a2f297 diff --git a/selfdrive/controls/lib/latcontrol_torque.py b/selfdrive/controls/lib/latcontrol_torque.py index 443fd1851..0ba38736d 100644 --- a/selfdrive/controls/lib/latcontrol_torque.py +++ b/selfdrive/controls/lib/latcontrol_torque.py @@ -20,15 +20,17 @@ from openpilot.common.pid import PIDController # Additionally, there is friction in the steering wheel that needs # to be overcome to move it at all, this is compensated for too. -KP = 1.0 -KI = 0.3 -KD = 0.0 +KP = 0.8 +KI = 0.15 + INTERP_SPEEDS = [1, 1.5, 2.0, 3.0, 5, 7.5, 10, 15, 30] KP_INTERP = [250, 120, 65, 30, 11.5, 5.5, 3.5, 2.0, KP] LP_FILTER_CUTOFF_HZ = 1.2 +JERK_LOOKAHEAD_SECONDS = 0.19 +JERK_GAIN = 0.3 LAT_ACCEL_REQUEST_BUFFER_SECONDS = 1.0 -VERSION = 0 +VERSION = 1 class LatControlTorque(LatControl): def __init__(self, CP, CI, dt): @@ -36,13 +38,13 @@ class LatControlTorque(LatControl): self.torque_params = CP.lateralTuning.torque.as_builder() self.torque_from_lateral_accel = CI.torque_from_lateral_accel() self.lateral_accel_from_torque = CI.lateral_accel_from_torque() - self.pid = PIDController([INTERP_SPEEDS, KP_INTERP], KI, KD, rate=1/self.dt) + self.pid = PIDController([INTERP_SPEEDS, KP_INTERP], KI, rate=1/self.dt) self.update_limits() self.steering_angle_deadzone_deg = self.torque_params.steeringAngleDeadzoneDeg self.lat_accel_request_buffer_len = int(LAT_ACCEL_REQUEST_BUFFER_SECONDS / self.dt) self.lat_accel_request_buffer = deque([0.] * self.lat_accel_request_buffer_len , maxlen=self.lat_accel_request_buffer_len) - self.previous_measurement = 0.0 - self.measurement_rate_filter = FirstOrderFilter(0.0, 1 / (2 * np.pi * LP_FILTER_CUTOFF_HZ), self.dt) + self.lookahead_frames = int(JERK_LOOKAHEAD_SECONDS / self.dt) + self.jerk_filter = FirstOrderFilter(0.0, 1 / (2 * np.pi * LP_FILTER_CUTOFF_HZ), self.dt) def update_live_torque_params(self, latAccelFactor, latAccelOffset, friction): self.torque_params.latAccelFactor = latAccelFactor @@ -68,17 +70,15 @@ class LatControlTorque(LatControl): delay_frames = int(np.clip(lat_delay / self.dt, 1, self.lat_accel_request_buffer_len)) expected_lateral_accel = self.lat_accel_request_buffer[-delay_frames] - # TODO factor out lateral jerk from error to later replace it with delay independent alternative + lookahead_idx = int(np.clip(-delay_frames + self.lookahead_frames, -self.lat_accel_request_buffer_len+1, -2)) + raw_lateral_jerk = (self.lat_accel_request_buffer[lookahead_idx+1] - self.lat_accel_request_buffer[lookahead_idx-1]) / (2 * self.dt) + desired_lateral_jerk = self.jerk_filter.update(raw_lateral_jerk) future_desired_lateral_accel = desired_curvature * CS.vEgo ** 2 self.lat_accel_request_buffer.append(future_desired_lateral_accel) gravity_adjusted_future_lateral_accel = future_desired_lateral_accel - roll_compensation - desired_lateral_jerk = (future_desired_lateral_accel - expected_lateral_accel) / lat_delay + setpoint = expected_lateral_accel measurement = measured_curvature * CS.vEgo ** 2 - measurement_rate = self.measurement_rate_filter.update((measurement - self.previous_measurement) / self.dt) - self.previous_measurement = measurement - - setpoint = lat_delay * desired_lateral_jerk + expected_lateral_accel error = setpoint - measurement # do error correction in lateral acceleration space, convert at end to handle non-linear torque responses correctly @@ -86,15 +86,10 @@ class LatControlTorque(LatControl): ff = gravity_adjusted_future_lateral_accel # latAccelOffset corrects roll compensation bias from device roll misalignment relative to car roll ff -= self.torque_params.latAccelOffset - # TODO jerk is weighted by lat_delay for legacy reasons, but should be made independent of it - ff += get_friction(error, lateral_accel_deadzone, FRICTION_THRESHOLD, self.torque_params) + ff += get_friction(error + JERK_GAIN * desired_lateral_jerk, lateral_accel_deadzone, FRICTION_THRESHOLD, self.torque_params) freeze_integrator = steer_limited_by_safety or CS.steeringPressed or CS.vEgo < 5 - output_lataccel = self.pid.update(pid_log.error, - -measurement_rate, - feedforward=ff, - speed=CS.vEgo, - freeze_integrator=freeze_integrator) + output_lataccel = self.pid.update(pid_log.error, speed=CS.vEgo, feedforward=ff, freeze_integrator=freeze_integrator) output_torque = self.torque_from_lateral_accel(output_lataccel, self.torque_params) pid_log.active = True diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index cdd4301fc..4a58e321f 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -b508f43fb0481bce0859c9b6ab4f45ee690b8dab \ No newline at end of file +e0ad86508edb61b3eaa1b84662c515d2c3368295 \ No newline at end of file From 42b2e1534b06a409efdd01a8284d10236e03bdad Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 25 Nov 2025 16:56:53 -0500 Subject: [PATCH 441/910] ci: disable macos builds (#1514) * ci: fix macos builds * Revert "ci: fix macos builds" This reverts commit 433ca0d7f62ce49c2422b9549e1d92818f58cb71. * disable --- .github/workflows/tests.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index aab16ffbb..8d6449cb4 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -107,6 +107,7 @@ jobs: build_mac: name: build macOS + if: false # temp disable since gcc-arm-embedded install is getting stuck due to checksum mismatch runs-on: ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-macos-8x14' || 'macos-latest' }} steps: - uses: actions/checkout@v4 From ad88c306e912d21d96ea5462d87d5448630422d8 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 25 Nov 2025 17:51:03 -0500 Subject: [PATCH 442/910] ui: reduce sidebar `scroll` in UI Preview (#1513) * ui: increase `scroll` delay in UI Preview * sleep for clicks too * don't lol * try 1 secs * try this out * wait a sec before doing a screenshot * nah --- selfdrive/ui/tests/test_ui/raylib_screenshots.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py index cd383e4bc..d9254b58c 100755 --- a/selfdrive/ui/tests/test_ui/raylib_screenshots.py +++ b/selfdrive/ui/tests/test_ui/raylib_screenshots.py @@ -105,7 +105,7 @@ def setup_settings_software_branch_switcher(click, pm: PubMaster, scroll=None): def setup_settings_firehose(click, pm: PubMaster, scroll=None): setup_settings(click, pm) - scroll(-1000, 278, 950) + scroll(-878, 278, 950) click(278, 850) @@ -115,7 +115,7 @@ def setup_settings_developer(click, pm: PubMaster, scroll=None): Params().put("CarParamsPersistent", CP.to_bytes()) setup_settings(click, pm) - scroll(-1000, 278, 950) + scroll(-878, 278, 950) click(278, 950) @@ -177,31 +177,31 @@ def setup_settings_cruise(click, pm: PubMaster, scroll=None): def setup_settings_visuals(click, pm: PubMaster, scroll=None): setup_settings(click, pm) - scroll(-1000, 278, 950) + scroll(-878, 278, 950) click(278, 330) def setup_settings_display(click, pm: PubMaster, scroll=None): setup_settings(click, pm) - scroll(-1000, 278, 950) + scroll(-878, 278, 950) click(278, 420) def setup_settings_osm(click, pm: PubMaster, scroll=None): setup_settings(click, pm) - scroll(-1000, 278, 950) + scroll(-878, 278, 950) click(278, 520) def setup_settings_trips(click, pm: PubMaster, scroll=None): setup_settings(click, pm) - scroll(-1000, 278, 950) + scroll(-878, 278, 950) click(278, 630) def setup_settings_vehicle(click, pm: PubMaster, scroll=None): setup_settings(click, pm) - scroll(-1000, 278, 950) + scroll(-878, 278, 950) click(278, 750) From 1e95e1cc0131bbf98517ca8f694a997d8ca6fe40 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 25 Nov 2025 18:04:21 -0500 Subject: [PATCH 443/910] Revert "ui: reduce sidebar `scroll` in UI Preview" (#1516) Revert "ui: reduce sidebar `scroll` in UI Preview (#1513)" This reverts commit ad88c306e912d21d96ea5462d87d5448630422d8. --- selfdrive/ui/tests/test_ui/raylib_screenshots.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py index d9254b58c..cd383e4bc 100755 --- a/selfdrive/ui/tests/test_ui/raylib_screenshots.py +++ b/selfdrive/ui/tests/test_ui/raylib_screenshots.py @@ -105,7 +105,7 @@ def setup_settings_software_branch_switcher(click, pm: PubMaster, scroll=None): def setup_settings_firehose(click, pm: PubMaster, scroll=None): setup_settings(click, pm) - scroll(-878, 278, 950) + scroll(-1000, 278, 950) click(278, 850) @@ -115,7 +115,7 @@ def setup_settings_developer(click, pm: PubMaster, scroll=None): Params().put("CarParamsPersistent", CP.to_bytes()) setup_settings(click, pm) - scroll(-878, 278, 950) + scroll(-1000, 278, 950) click(278, 950) @@ -177,31 +177,31 @@ def setup_settings_cruise(click, pm: PubMaster, scroll=None): def setup_settings_visuals(click, pm: PubMaster, scroll=None): setup_settings(click, pm) - scroll(-878, 278, 950) + scroll(-1000, 278, 950) click(278, 330) def setup_settings_display(click, pm: PubMaster, scroll=None): setup_settings(click, pm) - scroll(-878, 278, 950) + scroll(-1000, 278, 950) click(278, 420) def setup_settings_osm(click, pm: PubMaster, scroll=None): setup_settings(click, pm) - scroll(-878, 278, 950) + scroll(-1000, 278, 950) click(278, 520) def setup_settings_trips(click, pm: PubMaster, scroll=None): setup_settings(click, pm) - scroll(-878, 278, 950) + scroll(-1000, 278, 950) click(278, 630) def setup_settings_vehicle(click, pm: PubMaster, scroll=None): setup_settings(click, pm) - scroll(-878, 278, 950) + scroll(-1000, 278, 950) click(278, 750) From da35e601018d3b7a0a06a0716f0cf433d555d208 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 25 Nov 2025 18:10:40 -0500 Subject: [PATCH 444/910] ui: display mouse coordinates with debug mode (#1512) * ui: add debug to display mouse coordinates * cleanup * use mouse x and y directly * even less * try this out * ui: increase `scroll` delay in UI Preview * sleep for clicks too * don't lol * try 1 secs * try this out * wait a sec before doing a screenshot * nah --- selfdrive/ui/layouts/settings/developer.py | 1 + system/ui/lib/application.py | 5 +++++ system/ui/sunnypilot/lib/application.py | 25 ++++++++++++++++++++++ 3 files changed, 31 insertions(+) diff --git a/selfdrive/ui/layouts/settings/developer.py b/selfdrive/ui/layouts/settings/developer.py index f47cd310c..e3e462c54 100644 --- a/selfdrive/ui/layouts/settings/developer.py +++ b/selfdrive/ui/layouts/settings/developer.py @@ -148,6 +148,7 @@ class DeveloperLayout(Widget): self._params.put_bool("ShowDebugInfo", state) gui_app.set_show_touches(state) gui_app.set_show_fps(state) + gui_app.set_show_mouse_coords(state) def _on_enable_adb(self, state: bool): self._params.put_bool("AdbEnabled", state) diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 727112783..e4850e9cb 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -227,6 +227,8 @@ class GuiApplication(GuiApplicationExt): self._render_profiler = None self._render_profile_start_time = None + GuiApplicationExt.__init__(self) + @property def frame(self): return self._frame @@ -467,6 +469,9 @@ class GuiApplication(GuiApplicationExt): if self._show_touches: self._draw_touch_points() + if self._show_mouse_coords: + self._draw_mouse_coordinates(gui_app.font(FontWeight.SEMI_BOLD)) + if self._grid_size > 0: self._draw_grid() diff --git a/system/ui/sunnypilot/lib/application.py b/system/ui/sunnypilot/lib/application.py index b39bf31e9..481886e37 100644 --- a/system/ui/sunnypilot/lib/application.py +++ b/system/ui/sunnypilot/lib/application.py @@ -6,10 +6,35 @@ See the LICENSE.md file in the root directory for more details. """ import os +import pyray as rl + +SHOW_MOUSE_COORDS = os.getenv("SHOW_MOUSE_COORDS") == "1" SUNNYPILOT_UI = os.getenv("SUNNYPILOT_UI", "1") == "1" class GuiApplicationExt: + def __init__(self): + self._show_mouse_coords = SHOW_MOUSE_COORDS + @staticmethod def sunnypilot_ui() -> bool: return SUNNYPILOT_UI + + def _draw_mouse_coordinates(self, font): + coords_text = f"X:{int(rl.get_mouse_x())}, Y:{int(rl.get_mouse_y())}" + + green_color = rl.Color(0, 159, 47, 255) # Match the green color of FPS counter + + # Calculate text width to position it at the right edge; estimate width based on text length + # Each character is approximately 10-12 pixels wide at font size 20 + estimated_text_width = len(coords_text) * 11 + + # Position text at the top right corner, 10px from the top + screen_width = self._scaled_width if self._scale != 1.0 else self._width + text_pos = rl.Vector2(screen_width - estimated_text_width - 10, 6) + + # Draw the text + rl.draw_text_ex(font, coords_text, text_pos, 20, 0, green_color) + + def set_show_mouse_coords(self, show: bool): + self._show_mouse_coords = show From c229eb4f3848072958c6125c1c084a54075bb14a Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 25 Nov 2025 18:37:09 -0500 Subject: [PATCH 445/910] ui: reimplement sunnypilot branding with Raylib (#1517) * ui: sunnypilot branding * fr?? --- selfdrive/ui/layouts/home.py | 2 +- selfdrive/ui/layouts/onboarding.py | 8 +++---- selfdrive/ui/layouts/settings/developer.py | 11 +++++---- selfdrive/ui/layouts/settings/device.py | 8 +++---- selfdrive/ui/layouts/settings/firehose.py | 2 +- selfdrive/ui/layouts/settings/toggles.py | 24 +++++++++---------- selfdrive/ui/mici/layouts/home.py | 4 ++-- selfdrive/ui/mici/layouts/offroad_alerts.py | 2 +- selfdrive/ui/mici/layouts/onboarding.py | 8 +++---- selfdrive/ui/mici/layouts/settings/device.py | 6 ++--- .../ui/mici/layouts/settings/firehose.py | 2 +- selfdrive/ui/mici/layouts/settings/toggles.py | 2 +- selfdrive/ui/mici/onroad/alert_renderer.py | 2 +- .../ui/mici/onroad/augmented_road_view.py | 4 ++-- selfdrive/ui/onroad/alert_renderer.py | 2 +- .../ui/tests/test_ui/raylib_screenshots.py | 2 +- system/ui/lib/wifi_manager.py | 2 +- system/ui/mici_setup.py | 2 +- 18 files changed, 47 insertions(+), 46 deletions(-) diff --git a/selfdrive/ui/layouts/home.py b/selfdrive/ui/layouts/home.py index cd6ae600e..c99c8fe12 100644 --- a/selfdrive/ui/layouts/home.py +++ b/selfdrive/ui/layouts/home.py @@ -227,6 +227,6 @@ class HomeLayout(Widget): self._prev_alerts_present = alerts_present def _get_version_text(self) -> str: - brand = "openpilot" + brand = "sunnypilot" description = self.params.get("UpdaterCurrentDescription") return f"{brand} {description}" if description else brand diff --git a/selfdrive/ui/layouts/onboarding.py b/selfdrive/ui/layouts/onboarding.py index 5d61c1c95..b19cebb26 100644 --- a/selfdrive/ui/layouts/onboarding.py +++ b/selfdrive/ui/layouts/onboarding.py @@ -109,8 +109,8 @@ class TermsPage(Widget): self._on_accept = on_accept self._on_decline = on_decline - self._title = Label(tr("Welcome to openpilot"), font_size=90, font_weight=FontWeight.BOLD, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT) - self._desc = Label(tr("You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing."), + self._title = Label(tr("Welcome to sunnypilot"), font_size=90, font_weight=FontWeight.BOLD, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT) + self._desc = Label(tr("You must accept the Terms and Conditions to use sunnypilot. Read the latest terms at https://comma.ai/terms before continuing."), font_size=90, font_weight=FontWeight.MEDIUM, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT) self._decline_btn = Button(tr("Decline"), click_callback=on_decline) @@ -143,10 +143,10 @@ class TermsPage(Widget): class DeclinePage(Widget): def __init__(self, back_callback=None): super().__init__() - self._text = Label(tr("You must accept the Terms and Conditions in order to use openpilot."), + self._text = Label(tr("You must accept the Terms and Conditions in order to use sunnypilot."), font_size=90, font_weight=FontWeight.MEDIUM, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT) self._back_btn = Button(tr("Back"), click_callback=back_callback) - self._uninstall_btn = Button(tr("Decline, uninstall openpilot"), button_style=ButtonStyle.DANGER, + self._uninstall_btn = Button(tr("Decline, uninstall sunnypilot"), button_style=ButtonStyle.DANGER, click_callback=self._on_uninstall_clicked) def _on_uninstall_clicked(self): diff --git a/selfdrive/ui/layouts/settings/developer.py b/selfdrive/ui/layouts/settings/developer.py index e3e462c54..2ff94aa52 100644 --- a/selfdrive/ui/layouts/settings/developer.py +++ b/selfdrive/ui/layouts/settings/developer.py @@ -23,10 +23,11 @@ DESCRIPTIONS = { "other than your own. A comma employee will NEVER ask you to add their GitHub username." ), 'alpha_longitudinal': tr_noop( - "WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB).

" + - "On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. " + - "Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha. " + - "Changing this setting will restart openpilot if the car is powered on." + "WARNING: sunnypilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB).

" + + "On this car, sunnypilot defaults to the car's built-in ACC instead of sunnypilot's longitudinal control. " + + "Enable this to switch to sunnypilot longitudinal control. " + + "Enabling Experimental mode is recommended when enabling sunnypilot longitudinal control alpha. " + + "Changing this setting will restart sunnypilot if the car is powered on." ), } @@ -71,7 +72,7 @@ class DeveloperLayout(Widget): ) self._alpha_long_toggle = toggle_item( - lambda: tr("openpilot Longitudinal Control (Alpha)"), + lambda: tr("sunnypilot Longitudinal Control (Alpha)"), description=lambda: tr(DESCRIPTIONS["alpha_longitudinal"]), initial_state=self._params.get_bool("AlphaLongitudinalEnabled"), callback=self._on_alpha_long_enabled, diff --git a/selfdrive/ui/layouts/settings/device.py b/selfdrive/ui/layouts/settings/device.py index 00ae6a188..078623c88 100644 --- a/selfdrive/ui/layouts/settings/device.py +++ b/selfdrive/ui/layouts/settings/device.py @@ -23,8 +23,8 @@ from openpilot.system.ui.widgets.scroller_tici import Scroller DESCRIPTIONS = { 'pair_device': tr_noop("Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer."), 'driver_camera': tr_noop("Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)"), - 'reset_calibration': tr_noop("openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down."), - 'review_guide': tr_noop("Review the rules, features, and limitations of openpilot"), + 'reset_calibration': tr_noop("sunnypilot requires the device to be mounted within 4° left or right and within 5° up or 9° down."), + 'review_guide': tr_noop("Review the rules, features, and limitations of sunnypilot"), } @@ -162,8 +162,8 @@ class DeviceLayout(Widget): cloudlog.exception("invalid LiveTorqueParameters") desc += "

" - desc += tr("openpilot is continuously calibrating, resetting is rarely required. " + - "Resetting calibration will restart openpilot if the car is powered on.") + desc += tr("sunnypilot is continuously calibrating, resetting is rarely required. " + + "Resetting calibration will restart sunnypilot if the car is powered on.") self._reset_calib_btn.set_description(desc) diff --git a/selfdrive/ui/layouts/settings/firehose.py b/selfdrive/ui/layouts/settings/firehose.py index bbd4aef53..5ab82fd8f 100644 --- a/selfdrive/ui/layouts/settings/firehose.py +++ b/selfdrive/ui/layouts/settings/firehose.py @@ -17,7 +17,7 @@ from openpilot.selfdrive.ui.lib.api_helpers import get_token TITLE = tr_noop("Firehose Mode") DESCRIPTION = tr_noop( - "openpilot learns to drive by watching humans, like you, drive.\n\n" + "sunnypilot learns to drive by watching humans, like you, drive.\n\n" + "Firehose Mode allows you to maximize your training data uploads to improve " + "openpilot's driving models. More data means bigger models, which means better Experimental Mode." ) diff --git a/selfdrive/ui/layouts/settings/toggles.py b/selfdrive/ui/layouts/settings/toggles.py index b7f176e24..cd233aa3a 100644 --- a/selfdrive/ui/layouts/settings/toggles.py +++ b/selfdrive/ui/layouts/settings/toggles.py @@ -17,20 +17,20 @@ PERSONALITY_TO_INT = log.LongitudinalPersonality.schema.enumerants # Description constants DESCRIPTIONS = { "OpenpilotEnabledToggle": tr_noop( - "Use the openpilot system for adaptive cruise control and lane keep driver assistance. " + + "Use the sunnypilot system for adaptive cruise control and lane keep driver assistance. " + "Your attention is required at all times to use this feature." ), - "DisengageOnAccelerator": tr_noop("When enabled, pressing the accelerator pedal will disengage openpilot."), + "DisengageOnAccelerator": tr_noop("When enabled, pressing the accelerator pedal will disengage sunnypilot."), "LongitudinalPersonality": tr_noop( - "Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. " + - "In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with " + + "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." ), "IsLdwEnabled": tr_noop( "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)." ), - "AlwaysOnDM": tr_noop("Enable driver monitoring even when openpilot is not engaged."), + "AlwaysOnDM": tr_noop("Enable driver monitoring even when sunnypilot is not engaged."), 'RecordFront': tr_noop("Upload data from the driver facing camera and help improve the driver monitoring algorithm."), "IsMetric": tr_noop("Display speed in km/h instead of mph."), "RecordAudio": tr_noop("Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect."), @@ -46,7 +46,7 @@ class TogglesLayout(Widget): # param, title, desc, icon, needs_restart self._toggle_defs = { "OpenpilotEnabledToggle": ( - lambda: tr("Enable openpilot"), + lambda: tr("Enable sunnypilot"), DESCRIPTIONS["OpenpilotEnabledToggle"], "chffr_wheel.png", True, @@ -125,7 +125,7 @@ class TogglesLayout(Widget): # Make description callable for live translation additional_desc = "" if needs_restart and not locked: - additional_desc = tr("Changing this setting will restart openpilot if the car is powered on.") + additional_desc = tr("Changing this setting will restart sunnypilot if the car is powered on.") toggle.set_description(lambda og_desc=toggle.description, add_desc=additional_desc: tr(og_desc) + (" " + tr(add_desc) if add_desc else "")) # track for engaged state updates @@ -158,10 +158,10 @@ class TogglesLayout(Widget): ui_state.update_params() e2e_description = tr( - "openpilot defaults to driving in chill mode. Experimental mode enables alpha-level features that aren't ready for chill mode. " + + "sunnypilot defaults to driving in chill mode. Experimental mode enables alpha-level features that aren't ready for chill mode. " + "Experimental features are listed below:
" + "

End-to-End Longitudinal Control


" + - "Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. " + + "Let the driving model control the gas and brakes. sunnypilot will drive as it thinks a human would, including stopping for red lights and stop signs. " + "Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; " + "mistakes should be expected.
" + "

New Driving Visualization


" + @@ -183,13 +183,13 @@ class TogglesLayout(Widget): unavailable = tr("Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control.") - long_desc = unavailable + " " + tr("openpilot longitudinal control may come in a future update.") + long_desc = unavailable + " " + tr("sunnypilot longitudinal control may come in a future update.") if ui_state.CP.alphaLongitudinalAvailable: if self._is_release: - long_desc = unavailable + " " + tr("An alpha version of openpilot longitudinal control can be tested, along with " + + long_desc = unavailable + " " + tr("An alpha version of sunnypilot longitudinal control can be tested, along with " + "Experimental mode, on non-release branches.") else: - long_desc = tr("Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode.") + long_desc = tr("Enable the sunnypilot longitudinal control (alpha) toggle to allow Experimental mode.") self._toggles["ExperimentalMode"].set_description("" + long_desc + "

" + e2e_description) else: diff --git a/selfdrive/ui/mici/layouts/home.py b/selfdrive/ui/mici/layouts/home.py index 014fc0c45..fd4de9a71 100644 --- a/selfdrive/ui/mici/layouts/home.py +++ b/selfdrive/ui/mici/layouts/home.py @@ -45,7 +45,7 @@ class DeviceStatus(Widget): self._version_text = self._get_version_text() def _get_version_text(self) -> str: - brand = "openpilot" + brand = "sunnypilot" description = ui_state.params.get("UpdaterCurrentDescription") return f"{brand} {description}" if description else brand @@ -111,7 +111,7 @@ class MiciHomeLayout(Widget): self._cell_high_txt = gui_app.texture("icons_mici/settings/network/cell_strength_high.png", 55, 35) self._cell_full_txt = gui_app.texture("icons_mici/settings/network/cell_strength_full.png", 55, 35) - self._openpilot_label = MiciLabel("openpilot", font_size=96, color=rl.Color(255, 255, 255, int(255 * 0.9)), font_weight=FontWeight.DISPLAY) + self._openpilot_label = MiciLabel("sunnypilot", font_size=96, color=rl.Color(255, 255, 255, int(255 * 0.9)), font_weight=FontWeight.DISPLAY) self._version_label = MiciLabel("", font_size=36, font_weight=FontWeight.ROMAN) self._large_version_label = MiciLabel("", font_size=64, color=rl.GRAY, font_weight=FontWeight.ROMAN) self._date_label = MiciLabel("", font_size=36, color=rl.GRAY, font_weight=FontWeight.ROMAN) diff --git a/selfdrive/ui/mici/layouts/offroad_alerts.py b/selfdrive/ui/mici/layouts/offroad_alerts.py index 2e9a8bee3..cdde40d4a 100644 --- a/selfdrive/ui/mici/layouts/offroad_alerts.py +++ b/selfdrive/ui/mici/layouts/offroad_alerts.py @@ -254,7 +254,7 @@ class MiciOffroadAlerts(Widget): parts = new_desc.split(" / ") if len(parts) > 3: version, date = parts[0], parts[3] - update_alert_data.text = f"update available\n openpilot {version}, {date}. go to comma.ai/blog to read the release notes." + update_alert_data.text = f"update available\n sunnypilot {version}, {date}. go to comma.ai/blog to read the release notes." update_alert_data.visible = True active_count += 1 diff --git a/selfdrive/ui/mici/layouts/onboarding.py b/selfdrive/ui/mici/layouts/onboarding.py index afc7bfce1..5f20cf7c7 100644 --- a/selfdrive/ui/mici/layouts/onboarding.py +++ b/selfdrive/ui/mici/layouts/onboarding.py @@ -182,7 +182,7 @@ class TrainingGuideAttentionNotice(SetupTermsPage): def __init__(self, continue_callback): super().__init__(continue_callback, continue_text="continue") self._title_header = TermsHeader("driver assistance", gui_app.texture("icons_mici/setup/warning.png", 60, 60)) - self._warning_label = UnifiedLabel("1. openpilot is a driver assistance system.\n\n" + + self._warning_label = UnifiedLabel("1. sunnypilot is a driver assistance system.\n\n" + "2. You must pay attention at all times.\n\n" + "3. You must be ready to take over at any time.\n\n" + "4. You are fully responsible for driving the car.", 42, @@ -239,12 +239,12 @@ class TrainingGuide(Widget): class DeclinePage(Widget): def __init__(self, back_callback=None): super().__init__() - self._uninstall_slider = SmallSlider("uninstall openpilot", self._on_uninstall) + self._uninstall_slider = SmallSlider("uninstall sunnypilot", self._on_uninstall) self._back_button = SmallButton("back") self._back_button.set_click_callback(back_callback) - self._warning_header = TermsHeader("you must accept the\nterms to use openpilot", + self._warning_header = TermsHeader("you must accept the\nterms to use sunnypilot", gui_app.texture("icons_mici/setup/red_warning.png", 66, 60)) def _on_uninstall(self): @@ -282,7 +282,7 @@ class TermsPage(SetupTermsPage): info_txt = gui_app.texture("icons_mici/setup/green_info.png", 60, 60) self._title_header = TermsHeader("terms & conditions", info_txt) - self._terms_label = UnifiedLabel("You must accept the Terms and Conditions to use openpilot. " + + self._terms_label = UnifiedLabel("You must accept the Terms and Conditions to use sunnypilot. " + "Read the latest terms at https://comma.ai/terms before continuing.", 36, FontWeight.ROMAN) diff --git a/selfdrive/ui/mici/layouts/settings/device.py b/selfdrive/ui/mici/layouts/settings/device.py index b5e0ea838..1d5e4989e 100644 --- a/selfdrive/ui/mici/layouts/settings/device.py +++ b/selfdrive/ui/mici/layouts/settings/device.py @@ -156,7 +156,7 @@ class UpdateOpenpilotBigButton(BigButton): self._txt_update_icon = gui_app.texture("icons_mici/settings/device/update.png", 64, 64) self._txt_reboot_icon = gui_app.texture("icons_mici/settings/device/reboot.png", 64, 64) self._txt_up_to_date_icon = gui_app.texture("icons_mici/settings/device/up_to_date.png", 64, 64) - super().__init__("update openpilot", "", self._txt_update_icon) + super().__init__("update sunnypilot", "", self._txt_update_icon) self._waiting_for_updater_t: float | None = None self._hide_value_t: float | None = None @@ -193,7 +193,7 @@ class UpdateOpenpilotBigButton(BigButton): if value: self.set_text("") else: - self.set_text("update openpilot") + self.set_text("update sunnypilot") def _update_state(self): if ui_state.started: @@ -294,7 +294,7 @@ class DeviceLayoutMici(NavWidget): reset_calibration_btn = BigButton("reset calibration", "", "icons_mici/settings/device/lkas.png") reset_calibration_btn.set_click_callback(lambda: _engaged_confirmation_callback(reset_calibration_callback, "reset")) - uninstall_openpilot_btn = BigButton("uninstall openpilot", "", "icons_mici/settings/device/uninstall.png") + uninstall_openpilot_btn = BigButton("uninstall sunnypilot", "", "icons_mici/settings/device/uninstall.png") uninstall_openpilot_btn.set_click_callback(lambda: _engaged_confirmation_callback(uninstall_openpilot_callback, "uninstall")) reboot_btn = BigCircleButton("icons_mici/settings/device/reboot.png", red=False) diff --git a/selfdrive/ui/mici/layouts/settings/firehose.py b/selfdrive/ui/mici/layouts/settings/firehose.py index 303d976b0..18731d675 100644 --- a/selfdrive/ui/mici/layouts/settings/firehose.py +++ b/selfdrive/ui/mici/layouts/settings/firehose.py @@ -17,7 +17,7 @@ from openpilot.system.ui.widgets import NavWidget TITLE = tr_noop("Firehose Mode") DESCRIPTION = tr_noop( - "openpilot learns to drive by watching humans, like you, drive.\n\n" + "sunnypilot learns to drive by watching humans, like you, drive.\n\n" + "Firehose Mode allows you to maximize your training data uploads to improve " + "openpilot's driving models. More data means bigger models, which means better Experimental Mode." ) diff --git a/selfdrive/ui/mici/layouts/settings/toggles.py b/selfdrive/ui/mici/layouts/settings/toggles.py index 8efb516a4..82a78ce37 100644 --- a/selfdrive/ui/mici/layouts/settings/toggles.py +++ b/selfdrive/ui/mici/layouts/settings/toggles.py @@ -24,7 +24,7 @@ class TogglesLayoutMici(NavWidget): always_on_dm_toggle = BigParamControl("always-on driver monitor", "AlwaysOnDM") record_front = BigParamControl("record & upload driver camera", "RecordFront", toggle_callback=restart_needed_callback) record_mic = BigParamControl("record & upload mic audio", "RecordAudio", toggle_callback=restart_needed_callback) - enable_openpilot = BigParamControl("enable openpilot", "OpenpilotEnabledToggle", toggle_callback=restart_needed_callback) + enable_openpilot = BigParamControl("enable sunnypilot", "OpenpilotEnabledToggle", toggle_callback=restart_needed_callback) self._scroller = Scroller([ self._personality_toggle, diff --git a/selfdrive/ui/mici/onroad/alert_renderer.py b/selfdrive/ui/mici/onroad/alert_renderer.py index eb5555660..b7f009807 100644 --- a/selfdrive/ui/mici/onroad/alert_renderer.py +++ b/selfdrive/ui/mici/onroad/alert_renderer.py @@ -65,7 +65,7 @@ class Alert: # Pre-defined alert instances ALERT_STARTUP_PENDING = Alert( - text1="openpilot Unavailable", + text1="sunnypilot Unavailable", text2="Waiting to start", size=AlertSize.mid, status=AlertStatus.normal, diff --git a/selfdrive/ui/mici/onroad/augmented_road_view.py b/selfdrive/ui/mici/onroad/augmented_road_view.py index ab55f392f..64d57e895 100644 --- a/selfdrive/ui/mici/onroad/augmented_road_view.py +++ b/selfdrive/ui/mici/onroad/augmented_road_view.py @@ -153,7 +153,7 @@ class AugmentedRoadView(CameraView): self._alert_renderer = AlertRenderer() self._driver_state_renderer = DriverStateRenderer() self._confidence_ball = ConfidenceBall() - self._offroad_label = UnifiedLabel("start the car to\nuse openpilot", 54, FontWeight.DISPLAY, + self._offroad_label = UnifiedLabel("start the car to\nuse sunnypilot", 54, FontWeight.DISPLAY, text_color=rl.Color(255, 255, 255, int(255 * 0.9)), alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE) @@ -171,7 +171,7 @@ class AugmentedRoadView(CameraView): if ui_state.panda_type == log.PandaState.PandaType.unknown: self._offroad_label.set_text("system booting") else: - self._offroad_label.set_text("start the car to\nuse openpilot") + self._offroad_label.set_text("start the car to\nuse sunnypilot") def _handle_mouse_release(self, mouse_pos: MousePos): # Don't trigger click callback if bookmark was triggered diff --git a/selfdrive/ui/onroad/alert_renderer.py b/selfdrive/ui/onroad/alert_renderer.py index a81fbfc44..1e5f99cc6 100644 --- a/selfdrive/ui/onroad/alert_renderer.py +++ b/selfdrive/ui/onroad/alert_renderer.py @@ -48,7 +48,7 @@ class Alert: # Pre-defined alert instances ALERT_STARTUP_PENDING = Alert( - text1=tr("openpilot Unavailable"), + text1=tr("sunnypilot Unavailable"), text2=tr("Waiting to start"), size=AlertSize.mid, status=AlertStatus.normal, diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py index cd383e4bc..e64398d22 100755 --- a/selfdrive/ui/tests/test_ui/raylib_screenshots.py +++ b/selfdrive/ui/tests/test_ui/raylib_screenshots.py @@ -151,7 +151,7 @@ def setup_experimental_mode_description(click, pm: PubMaster, scroll=None): def setup_openpilot_long_confirmation_dialog(click, pm: PubMaster, scroll=None): setup_settings_developer(click, pm, scroll=scroll) - click(650, 960) # toggle openpilot longitudinal control + click(650, 960) # toggle sunnypilot longitudinal control def setup_settings_sunnylink(click, pm: PubMaster, scroll=None): diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index 28bd58f22..cd809f269 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -383,7 +383,7 @@ class WifiManager: 'connection': { 'type': ('s', '802-11-wireless'), 'uuid': ('s', str(uuid.uuid4())), - 'id': ('s', f'openpilot connection {ssid}'), + 'id': ('s', f'sunnypilot connection {ssid}'), 'autoconnect-retries': ('i', 0), }, '802-11-wireless': { diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index d7395f9b7..316e6c4a8 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -701,7 +701,7 @@ class Setup(Widget): except urllib.error.HTTPError as e: if e.code == 409: - error_msg = "Incompatible openpilot version" + error_msg = "Incompatible sunnypilot version" self.download_failed(self.download_url, error_msg) except Exception: error_msg = "Invalid URL" From 49178539f31f57c87986ed37af96017f6805be86 Mon Sep 17 00:00:00 2001 From: YassineYousfi Date: Tue, 25 Nov 2025 18:52:35 -0800 Subject: [PATCH 446/910] dm: DriverProb (#36687) * wip * ci * fix --- selfdrive/monitoring/dmonitoringd.py | 4 ++-- selfdrive/monitoring/helpers.py | 15 +++++++++------ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/selfdrive/monitoring/dmonitoringd.py b/selfdrive/monitoring/dmonitoringd.py index 1dc256d46..1ac2c2dcb 100755 --- a/selfdrive/monitoring/dmonitoringd.py +++ b/selfdrive/monitoring/dmonitoringd.py @@ -39,8 +39,8 @@ def dmonitoringd_thread(): # save rhd virtual toggle every 5 mins if (sm['driverStateV2'].frameId % 6000 == 0 and not demo_mode and - DM.wheelpos_learner.filtered_stat.n > DM.settings._WHEELPOS_FILTER_MIN_COUNT and - DM.wheel_on_right == (DM.wheelpos_learner.filtered_stat.M > DM.settings._WHEELPOS_THRESHOLD)): + DM.wheelpos.prob_offseter.filtered_stat.n > DM.settings._WHEELPOS_FILTER_MIN_COUNT and + DM.wheel_on_right == (DM.wheelpos.prob_offseter.filtered_stat.M > DM.settings._WHEELPOS_THRESHOLD)): params.put_bool_nonblocking("IsRhdDetected", DM.wheel_on_right) def main(): diff --git a/selfdrive/monitoring/helpers.py b/selfdrive/monitoring/helpers.py index 7697e68b9..5b5e16dde 100644 --- a/selfdrive/monitoring/helpers.py +++ b/selfdrive/monitoring/helpers.py @@ -98,7 +98,7 @@ class DriverPose: self.cfactor_pitch = 1. self.cfactor_yaw = 1. -class DriverPhone: +class DriverProb: def __init__(self, max_trackable): self.prob = 0. self.prob_offseter = RunningStatFilter(max_trackable=max_trackable) @@ -140,9 +140,9 @@ class DriverMonitoring: self.settings = settings if settings is not None else DRIVER_MONITOR_SETTINGS(device_type=HARDWARE.get_device_type()) # init driver status - self.wheelpos_learner = RunningStatFilter() + self.wheelpos = DriverProb(-1) self.pose = DriverPose(self.settings._POSE_OFFSET_MAX_COUNT) - self.phone = DriverPhone(self.settings._POSE_OFFSET_MAX_COUNT) + self.phone = DriverProb(self.settings._POSE_OFFSET_MAX_COUNT) self.blink = DriverBlink() self.always_on = always_on @@ -256,9 +256,12 @@ class DriverMonitoring: # calibrates only when there's movement and either face detected if car_speed > self.settings._WHEELPOS_CALIB_MIN_SPEED and (driver_state.leftDriverData.faceProb > self.settings._FACE_THRESHOLD or driver_state.rightDriverData.faceProb > self.settings._FACE_THRESHOLD): - self.wheelpos_learner.push_and_update(rhd_pred) - if self.wheelpos_learner.filtered_stat.n > self.settings._WHEELPOS_FILTER_MIN_COUNT or demo_mode: - self.wheel_on_right = self.wheelpos_learner.filtered_stat.M > self.settings._WHEELPOS_THRESHOLD + self.wheelpos.prob_offseter.push_and_update(rhd_pred) + + self.wheelpos.prob_calibrated = self.wheelpos.prob_offseter.filtered_stat.n > self.settings._WHEELPOS_FILTER_MIN_COUNT + + if self.wheelpos.prob_calibrated or demo_mode: + self.wheel_on_right = self.wheelpos.prob_offseter.filtered_stat.M > self.settings._WHEELPOS_THRESHOLD else: self.wheel_on_right = self.wheel_on_right_default # use default/saved if calibration is unfinished # make sure no switching when engaged From dd51bf2021859c478aeb9f4bd167f8c2a47858fa Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 27 Nov 2025 00:46:33 -0800 Subject: [PATCH 447/910] De-duplicate firehose layout (#36701) * consistent name * dedup * FMT * not sure why two --- selfdrive/ui/layouts/settings/firehose.py | 87 ++----------------- .../ui/mici/layouts/settings/firehose.py | 39 +++++---- .../ui/mici/layouts/settings/settings.py | 4 +- 3 files changed, 31 insertions(+), 99 deletions(-) diff --git a/selfdrive/ui/layouts/settings/firehose.py b/selfdrive/ui/layouts/settings/firehose.py index bbd4aef53..9b9b51b18 100644 --- a/selfdrive/ui/layouts/settings/firehose.py +++ b/selfdrive/ui/layouts/settings/firehose.py @@ -1,19 +1,10 @@ import pyray as rl -import time -import threading -from openpilot.common.api import api_get -from openpilot.common.params import Params -from openpilot.common.swaglog import cloudlog -from openpilot.selfdrive.ui.ui_state import ui_state -from openpilot.system.athena.registration import UNREGISTERED_DONGLE_ID -from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE +from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.lib.multilang import tr, trn, tr_noop from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel -from openpilot.system.ui.lib.wrap_text import wrap_text -from openpilot.system.ui.widgets import Widget -from openpilot.selfdrive.ui.lib.api_helpers import get_token +from openpilot.selfdrive.ui.mici.layouts.settings.firehose import FirehoseLayoutBase TITLE = tr_noop("Firehose Mode") DESCRIPTION = tr_noop( @@ -32,50 +23,17 @@ INSTRUCTIONS = tr_noop( ) -class FirehoseLayout(Widget): - PARAM_KEY = "ApiCache_FirehoseStats" - GREEN = rl.Color(46, 204, 113, 255) - RED = rl.Color(231, 76, 60, 255) - GRAY = rl.Color(68, 68, 68, 255) - LIGHT_GRAY = rl.Color(228, 228, 228, 255) - UPDATE_INTERVAL = 30 # seconds - +class FirehoseLayout(FirehoseLayoutBase): def __init__(self): super().__init__() - self.params = Params() - self.segment_count = self._get_segment_count() - self.scroll_panel = GuiScrollPanel() - self._content_height = 0 - - self.running = True - self.update_thread = threading.Thread(target=self._update_loop, daemon=True) - self.update_thread.start() - self.last_update_time = 0 - - def show_event(self): - self.scroll_panel.set_offset(0) - - def _get_segment_count(self) -> int: - stats = self.params.get(self.PARAM_KEY) - if not stats: - return 0 - try: - return int(stats.get("firehose", 0)) - except Exception: - cloudlog.exception(f"Failed to decode firehose stats: {stats}") - return 0 - - def __del__(self): - self.running = False - if self.update_thread and self.update_thread.is_alive(): - self.update_thread.join(timeout=1.0) + self._scroll_panel = GuiScrollPanel() def _render(self, rect: rl.Rectangle): # Calculate content dimensions content_rect = rl.Rectangle(rect.x, rect.y, rect.width, self._content_height) # Handle scrolling and render with clipping - scroll_offset = self.scroll_panel.update(rect, content_rect) + scroll_offset = self._scroll_panel.update(rect, content_rect) rl.begin_scissor_mode(int(rect.x), int(rect.y), int(rect.width), int(rect.height)) self._content_height = self._render_content(rect, scroll_offset) rl.end_scissor_mode() @@ -107,9 +65,9 @@ class FirehoseLayout(Widget): y += 20 + 20 # Contribution count (if available) - if self.segment_count > 0: + if self._segment_count > 0: contrib_text = trn("{} segment of your driving is in the training dataset so far.", - "{} segments of your driving is in the training dataset so far.", self.segment_count).format(self.segment_count) + "{} segments of your driving is in the training dataset so far.", self._segment_count).format(self._segment_count) y = self._draw_wrapped_text(x, y, w, contrib_text, gui_app.font(FontWeight.BOLD), 52, rl.WHITE) y += 20 + 20 @@ -121,7 +79,7 @@ class FirehoseLayout(Widget): y = self._draw_wrapped_text(x, y, w, tr(INSTRUCTIONS), gui_app.font(FontWeight.NORMAL), 40, self.LIGHT_GRAY) # bottom margin + remove effect of scroll offset - return int(round(y - self.scroll_panel.offset + 40)) + return int(round(y - self._scroll_panel.offset + 40)) def _draw_wrapped_text(self, x, y, width, text, font, font_size, color): wrapped = wrap_text(font, text, font_size, width) @@ -129,32 +87,3 @@ class FirehoseLayout(Widget): rl.draw_text_ex(font, line, rl.Vector2(x, y), font_size, 0, color) y += font_size * FONT_SCALE return round(y) - - def _get_status(self) -> tuple[str, rl.Color]: - network_type = ui_state.sm["deviceState"].networkType - network_metered = ui_state.sm["deviceState"].networkMetered - - if not network_metered and network_type != 0: # Not metered and connected - return tr("ACTIVE"), self.GREEN - else: - return tr("INACTIVE: connect to an unmetered network"), self.RED - - def _fetch_firehose_stats(self): - try: - dongle_id = self.params.get("DongleId") - if not dongle_id or dongle_id == UNREGISTERED_DONGLE_ID: - return - identity_token = get_token(dongle_id) - response = api_get(f"v1/devices/{dongle_id}/firehose_stats", access_token=identity_token) - if response.status_code == 200: - data = response.json() - self.segment_count = data.get("firehose", 0) - self.params.put(self.PARAM_KEY, data) - except Exception as e: - cloudlog.error(f"Failed to fetch firehose stats: {e}") - - def _update_loop(self): - while self.running: - if not ui_state.started: - self._fetch_firehose_stats() - time.sleep(self.UPDATE_INTERVAL) diff --git a/selfdrive/ui/mici/layouts/settings/firehose.py b/selfdrive/ui/mici/layouts/settings/firehose.py index 303d976b0..f2f8bcb1c 100644 --- a/selfdrive/ui/mici/layouts/settings/firehose.py +++ b/selfdrive/ui/mici/layouts/settings/firehose.py @@ -12,8 +12,7 @@ from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE from openpilot.system.ui.lib.wrap_text import wrap_text from openpilot.system.ui.lib.scroll_panel2 import GuiScrollPanel2 from openpilot.system.ui.lib.multilang import tr, trn, tr_noop -from openpilot.system.ui.widgets import NavWidget - +from openpilot.system.ui.widgets import Widget, NavWidget TITLE = tr_noop("Firehose Mode") DESCRIPTION = tr_noop( @@ -34,9 +33,7 @@ FAQ_ITEMS = [ ] -class FirehoseLayoutMici(NavWidget): - BACK_TOUCH_AREA_PERCENTAGE = 0.1 - +class FirehoseLayoutBase(Widget): PARAM_KEY = "ApiCache_FirehoseStats" GREEN = rl.Color(46, 204, 113, 255) RED = rl.Color(231, 76, 60, 255) @@ -44,12 +41,10 @@ class FirehoseLayoutMici(NavWidget): LIGHT_GRAY = rl.Color(228, 228, 228, 255) UPDATE_INTERVAL = 30 # seconds - def __init__(self, back_callback): + def __init__(self): super().__init__() - self.set_back_callback(back_callback) - - self.params = Params() - self.segment_count = self._get_segment_count() + self._params = Params() + self._segment_count = self._get_segment_count() self._scroll_panel = GuiScrollPanel2(horizontal=False) self._content_height = 0 @@ -71,7 +66,7 @@ class FirehoseLayoutMici(NavWidget): self._scroll_panel.set_offset(0) def _get_segment_count(self) -> int: - stats = self.params.get(self.PARAM_KEY) + stats = self._params.get(self.PARAM_KEY) if not stats: return 0 try: @@ -111,9 +106,9 @@ class FirehoseLayoutMici(NavWidget): y += 20 # Contribution count (if available) - if self.segment_count > 0: + if self._segment_count > 0: contrib_text = trn("{} segment of your driving is in the training dataset so far.", - "{} segments of your driving is in the training dataset so far.", self.segment_count).format(self.segment_count) + "{} segments of your driving is in the training dataset so far.", self._segment_count).format(self._segment_count) y = self._draw_wrapped_text(x, y, w, contrib_text, gui_app.font(FontWeight.BOLD), 42, rl.WHITE) y += 20 @@ -165,9 +160,9 @@ class FirehoseLayoutMici(NavWidget): y += int(len(status_lines) * 48 * FONT_SCALE) + 20 # Contribution count - if self.segment_count > 0: + if self._segment_count > 0: contrib_text = trn("{} segment of your driving is in the training dataset so far.", - "{} segments of your driving is in the training dataset so far.", self.segment_count).format(self.segment_count) + "{} segments of your driving is in the training dataset so far.", self._segment_count).format(self._segment_count) contrib_lines = wrap_text(gui_app.font(FontWeight.BOLD), contrib_text, 42, w) y += int(len(contrib_lines) * 42 * FONT_SCALE) + 20 @@ -204,15 +199,15 @@ class FirehoseLayoutMici(NavWidget): def _fetch_firehose_stats(self): try: - dongle_id = self.params.get("DongleId") + dongle_id = self._params.get("DongleId") if not dongle_id or dongle_id == UNREGISTERED_DONGLE_ID: return identity_token = get_token(dongle_id) response = api_get(f"v1/devices/{dongle_id}/firehose_stats", access_token=identity_token) if response.status_code == 200: data = response.json() - self.segment_count = data.get("firehose", 0) - self.params.put(self.PARAM_KEY, data) + self._segment_count = data.get("firehose", 0) + self._params.put(self.PARAM_KEY, data) except Exception as e: cloudlog.error(f"Failed to fetch firehose stats: {e}") @@ -221,3 +216,11 @@ class FirehoseLayoutMici(NavWidget): if not ui_state.started: self._fetch_firehose_stats() time.sleep(self.UPDATE_INTERVAL) + + +class FirehoseLayout(FirehoseLayoutBase, NavWidget): + BACK_TOUCH_AREA_PERCENTAGE = 0.1 + + def __init__(self, back_callback): + super().__init__() + self.set_back_callback(back_callback) diff --git a/selfdrive/ui/mici/layouts/settings/settings.py b/selfdrive/ui/mici/layouts/settings/settings.py index 75238d581..a45277774 100644 --- a/selfdrive/ui/mici/layouts/settings/settings.py +++ b/selfdrive/ui/mici/layouts/settings/settings.py @@ -10,7 +10,7 @@ from openpilot.selfdrive.ui.mici.layouts.settings.toggles import TogglesLayoutMi from openpilot.selfdrive.ui.mici.layouts.settings.network import NetworkLayoutMici from openpilot.selfdrive.ui.mici.layouts.settings.device import DeviceLayoutMici, PairBigButton from openpilot.selfdrive.ui.mici.layouts.settings.developer import DeveloperLayoutMici -from openpilot.selfdrive.ui.mici.layouts.settings.firehose import FirehoseLayoutMici +from openpilot.selfdrive.ui.mici.layouts.settings.firehose import FirehoseLayout from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.widgets import Widget, NavWidget @@ -67,7 +67,7 @@ class SettingsLayout(NavWidget): PanelType.NETWORK: PanelInfo("Network", NetworkLayoutMici(back_callback=lambda: self._set_current_panel(None))), PanelType.DEVICE: PanelInfo("Device", DeviceLayoutMici(back_callback=lambda: self._set_current_panel(None))), PanelType.DEVELOPER: PanelInfo("Developer", DeveloperLayoutMici(back_callback=lambda: self._set_current_panel(None))), - PanelType.FIREHOSE: PanelInfo("Firehose", FirehoseLayoutMici(back_callback=lambda: self._set_current_panel(None))), + PanelType.FIREHOSE: PanelInfo("Firehose", FirehoseLayout(back_callback=lambda: self._set_current_panel(None))), } self._font_medium = gui_app.font(FontWeight.MEDIUM) From 302e448b9378b980e64b49a8c16a89dd9d5fc843 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 27 Nov 2025 00:52:50 -0800 Subject: [PATCH 448/910] Revert "De-duplicate firehose layout (#36701)" This reverts commit dd51bf2021859c478aeb9f4bd167f8c2a47858fa. --- selfdrive/ui/layouts/settings/firehose.py | 87 +++++++++++++++++-- .../ui/mici/layouts/settings/firehose.py | 39 ++++----- .../ui/mici/layouts/settings/settings.py | 4 +- 3 files changed, 99 insertions(+), 31 deletions(-) diff --git a/selfdrive/ui/layouts/settings/firehose.py b/selfdrive/ui/layouts/settings/firehose.py index 9b9b51b18..bbd4aef53 100644 --- a/selfdrive/ui/layouts/settings/firehose.py +++ b/selfdrive/ui/layouts/settings/firehose.py @@ -1,10 +1,19 @@ import pyray as rl +import time +import threading -from openpilot.system.ui.lib.application import gui_app, FontWeight +from openpilot.common.api import api_get +from openpilot.common.params import Params +from openpilot.common.swaglog import cloudlog +from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.system.athena.registration import UNREGISTERED_DONGLE_ID +from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE from openpilot.system.ui.lib.multilang import tr, trn, tr_noop from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel -from openpilot.selfdrive.ui.mici.layouts.settings.firehose import FirehoseLayoutBase +from openpilot.system.ui.lib.wrap_text import wrap_text +from openpilot.system.ui.widgets import Widget +from openpilot.selfdrive.ui.lib.api_helpers import get_token TITLE = tr_noop("Firehose Mode") DESCRIPTION = tr_noop( @@ -23,17 +32,50 @@ INSTRUCTIONS = tr_noop( ) -class FirehoseLayout(FirehoseLayoutBase): +class FirehoseLayout(Widget): + PARAM_KEY = "ApiCache_FirehoseStats" + GREEN = rl.Color(46, 204, 113, 255) + RED = rl.Color(231, 76, 60, 255) + GRAY = rl.Color(68, 68, 68, 255) + LIGHT_GRAY = rl.Color(228, 228, 228, 255) + UPDATE_INTERVAL = 30 # seconds + def __init__(self): super().__init__() - self._scroll_panel = GuiScrollPanel() + self.params = Params() + self.segment_count = self._get_segment_count() + self.scroll_panel = GuiScrollPanel() + self._content_height = 0 + + self.running = True + self.update_thread = threading.Thread(target=self._update_loop, daemon=True) + self.update_thread.start() + self.last_update_time = 0 + + def show_event(self): + self.scroll_panel.set_offset(0) + + def _get_segment_count(self) -> int: + stats = self.params.get(self.PARAM_KEY) + if not stats: + return 0 + try: + return int(stats.get("firehose", 0)) + except Exception: + cloudlog.exception(f"Failed to decode firehose stats: {stats}") + return 0 + + def __del__(self): + self.running = False + if self.update_thread and self.update_thread.is_alive(): + self.update_thread.join(timeout=1.0) def _render(self, rect: rl.Rectangle): # Calculate content dimensions content_rect = rl.Rectangle(rect.x, rect.y, rect.width, self._content_height) # Handle scrolling and render with clipping - scroll_offset = self._scroll_panel.update(rect, content_rect) + scroll_offset = self.scroll_panel.update(rect, content_rect) rl.begin_scissor_mode(int(rect.x), int(rect.y), int(rect.width), int(rect.height)) self._content_height = self._render_content(rect, scroll_offset) rl.end_scissor_mode() @@ -65,9 +107,9 @@ class FirehoseLayout(FirehoseLayoutBase): y += 20 + 20 # Contribution count (if available) - if self._segment_count > 0: + if self.segment_count > 0: contrib_text = trn("{} segment of your driving is in the training dataset so far.", - "{} segments of your driving is in the training dataset so far.", self._segment_count).format(self._segment_count) + "{} segments of your driving is in the training dataset so far.", self.segment_count).format(self.segment_count) y = self._draw_wrapped_text(x, y, w, contrib_text, gui_app.font(FontWeight.BOLD), 52, rl.WHITE) y += 20 + 20 @@ -79,7 +121,7 @@ class FirehoseLayout(FirehoseLayoutBase): y = self._draw_wrapped_text(x, y, w, tr(INSTRUCTIONS), gui_app.font(FontWeight.NORMAL), 40, self.LIGHT_GRAY) # bottom margin + remove effect of scroll offset - return int(round(y - self._scroll_panel.offset + 40)) + return int(round(y - self.scroll_panel.offset + 40)) def _draw_wrapped_text(self, x, y, width, text, font, font_size, color): wrapped = wrap_text(font, text, font_size, width) @@ -87,3 +129,32 @@ class FirehoseLayout(FirehoseLayoutBase): rl.draw_text_ex(font, line, rl.Vector2(x, y), font_size, 0, color) y += font_size * FONT_SCALE return round(y) + + def _get_status(self) -> tuple[str, rl.Color]: + network_type = ui_state.sm["deviceState"].networkType + network_metered = ui_state.sm["deviceState"].networkMetered + + if not network_metered and network_type != 0: # Not metered and connected + return tr("ACTIVE"), self.GREEN + else: + return tr("INACTIVE: connect to an unmetered network"), self.RED + + def _fetch_firehose_stats(self): + try: + dongle_id = self.params.get("DongleId") + if not dongle_id or dongle_id == UNREGISTERED_DONGLE_ID: + return + identity_token = get_token(dongle_id) + response = api_get(f"v1/devices/{dongle_id}/firehose_stats", access_token=identity_token) + if response.status_code == 200: + data = response.json() + self.segment_count = data.get("firehose", 0) + self.params.put(self.PARAM_KEY, data) + except Exception as e: + cloudlog.error(f"Failed to fetch firehose stats: {e}") + + def _update_loop(self): + while self.running: + if not ui_state.started: + self._fetch_firehose_stats() + time.sleep(self.UPDATE_INTERVAL) diff --git a/selfdrive/ui/mici/layouts/settings/firehose.py b/selfdrive/ui/mici/layouts/settings/firehose.py index f2f8bcb1c..303d976b0 100644 --- a/selfdrive/ui/mici/layouts/settings/firehose.py +++ b/selfdrive/ui/mici/layouts/settings/firehose.py @@ -12,7 +12,8 @@ from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE from openpilot.system.ui.lib.wrap_text import wrap_text from openpilot.system.ui.lib.scroll_panel2 import GuiScrollPanel2 from openpilot.system.ui.lib.multilang import tr, trn, tr_noop -from openpilot.system.ui.widgets import Widget, NavWidget +from openpilot.system.ui.widgets import NavWidget + TITLE = tr_noop("Firehose Mode") DESCRIPTION = tr_noop( @@ -33,7 +34,9 @@ FAQ_ITEMS = [ ] -class FirehoseLayoutBase(Widget): +class FirehoseLayoutMici(NavWidget): + BACK_TOUCH_AREA_PERCENTAGE = 0.1 + PARAM_KEY = "ApiCache_FirehoseStats" GREEN = rl.Color(46, 204, 113, 255) RED = rl.Color(231, 76, 60, 255) @@ -41,10 +44,12 @@ class FirehoseLayoutBase(Widget): LIGHT_GRAY = rl.Color(228, 228, 228, 255) UPDATE_INTERVAL = 30 # seconds - def __init__(self): + def __init__(self, back_callback): super().__init__() - self._params = Params() - self._segment_count = self._get_segment_count() + self.set_back_callback(back_callback) + + self.params = Params() + self.segment_count = self._get_segment_count() self._scroll_panel = GuiScrollPanel2(horizontal=False) self._content_height = 0 @@ -66,7 +71,7 @@ class FirehoseLayoutBase(Widget): self._scroll_panel.set_offset(0) def _get_segment_count(self) -> int: - stats = self._params.get(self.PARAM_KEY) + stats = self.params.get(self.PARAM_KEY) if not stats: return 0 try: @@ -106,9 +111,9 @@ class FirehoseLayoutBase(Widget): y += 20 # Contribution count (if available) - if self._segment_count > 0: + if self.segment_count > 0: contrib_text = trn("{} segment of your driving is in the training dataset so far.", - "{} segments of your driving is in the training dataset so far.", self._segment_count).format(self._segment_count) + "{} segments of your driving is in the training dataset so far.", self.segment_count).format(self.segment_count) y = self._draw_wrapped_text(x, y, w, contrib_text, gui_app.font(FontWeight.BOLD), 42, rl.WHITE) y += 20 @@ -160,9 +165,9 @@ class FirehoseLayoutBase(Widget): y += int(len(status_lines) * 48 * FONT_SCALE) + 20 # Contribution count - if self._segment_count > 0: + if self.segment_count > 0: contrib_text = trn("{} segment of your driving is in the training dataset so far.", - "{} segments of your driving is in the training dataset so far.", self._segment_count).format(self._segment_count) + "{} segments of your driving is in the training dataset so far.", self.segment_count).format(self.segment_count) contrib_lines = wrap_text(gui_app.font(FontWeight.BOLD), contrib_text, 42, w) y += int(len(contrib_lines) * 42 * FONT_SCALE) + 20 @@ -199,15 +204,15 @@ class FirehoseLayoutBase(Widget): def _fetch_firehose_stats(self): try: - dongle_id = self._params.get("DongleId") + dongle_id = self.params.get("DongleId") if not dongle_id or dongle_id == UNREGISTERED_DONGLE_ID: return identity_token = get_token(dongle_id) response = api_get(f"v1/devices/{dongle_id}/firehose_stats", access_token=identity_token) if response.status_code == 200: data = response.json() - self._segment_count = data.get("firehose", 0) - self._params.put(self.PARAM_KEY, data) + self.segment_count = data.get("firehose", 0) + self.params.put(self.PARAM_KEY, data) except Exception as e: cloudlog.error(f"Failed to fetch firehose stats: {e}") @@ -216,11 +221,3 @@ class FirehoseLayoutBase(Widget): if not ui_state.started: self._fetch_firehose_stats() time.sleep(self.UPDATE_INTERVAL) - - -class FirehoseLayout(FirehoseLayoutBase, NavWidget): - BACK_TOUCH_AREA_PERCENTAGE = 0.1 - - def __init__(self, back_callback): - super().__init__() - self.set_back_callback(back_callback) diff --git a/selfdrive/ui/mici/layouts/settings/settings.py b/selfdrive/ui/mici/layouts/settings/settings.py index a45277774..75238d581 100644 --- a/selfdrive/ui/mici/layouts/settings/settings.py +++ b/selfdrive/ui/mici/layouts/settings/settings.py @@ -10,7 +10,7 @@ from openpilot.selfdrive.ui.mici.layouts.settings.toggles import TogglesLayoutMi from openpilot.selfdrive.ui.mici.layouts.settings.network import NetworkLayoutMici from openpilot.selfdrive.ui.mici.layouts.settings.device import DeviceLayoutMici, PairBigButton from openpilot.selfdrive.ui.mici.layouts.settings.developer import DeveloperLayoutMici -from openpilot.selfdrive.ui.mici.layouts.settings.firehose import FirehoseLayout +from openpilot.selfdrive.ui.mici.layouts.settings.firehose import FirehoseLayoutMici from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.widgets import Widget, NavWidget @@ -67,7 +67,7 @@ class SettingsLayout(NavWidget): PanelType.NETWORK: PanelInfo("Network", NetworkLayoutMici(back_callback=lambda: self._set_current_panel(None))), PanelType.DEVICE: PanelInfo("Device", DeviceLayoutMici(back_callback=lambda: self._set_current_panel(None))), PanelType.DEVELOPER: PanelInfo("Developer", DeveloperLayoutMici(back_callback=lambda: self._set_current_panel(None))), - PanelType.FIREHOSE: PanelInfo("Firehose", FirehoseLayout(back_callback=lambda: self._set_current_panel(None))), + PanelType.FIREHOSE: PanelInfo("Firehose", FirehoseLayoutMici(back_callback=lambda: self._set_current_panel(None))), } self._font_medium = gui_app.font(FontWeight.MEDIUM) From 50a797b0be47b5bb26540c4efb2957a72a45e58e Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 27 Nov 2025 01:00:03 -0800 Subject: [PATCH 449/910] De-duplicate firehose layout (#36703) * Reapply "De-duplicate firehose layout (#36701)" This reverts commit 302e448b9378b980e64b49a8c16a89dd9d5fc843. * fix * was here --- selfdrive/ui/layouts/settings/firehose.py | 84 ++----------------- .../ui/mici/layouts/settings/firehose.py | 39 +++++---- .../ui/mici/layouts/settings/settings.py | 4 +- 3 files changed, 30 insertions(+), 97 deletions(-) diff --git a/selfdrive/ui/layouts/settings/firehose.py b/selfdrive/ui/layouts/settings/firehose.py index bbd4aef53..ea83e962e 100644 --- a/selfdrive/ui/layouts/settings/firehose.py +++ b/selfdrive/ui/layouts/settings/firehose.py @@ -1,19 +1,11 @@ import pyray as rl -import time -import threading -from openpilot.common.api import api_get -from openpilot.common.params import Params -from openpilot.common.swaglog import cloudlog -from openpilot.selfdrive.ui.ui_state import ui_state -from openpilot.system.athena.registration import UNREGISTERED_DONGLE_ID from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE from openpilot.system.ui.lib.multilang import tr, trn, tr_noop from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel from openpilot.system.ui.lib.wrap_text import wrap_text -from openpilot.system.ui.widgets import Widget -from openpilot.selfdrive.ui.lib.api_helpers import get_token +from openpilot.selfdrive.ui.mici.layouts.settings.firehose import FirehoseLayoutBase TITLE = tr_noop("Firehose Mode") DESCRIPTION = tr_noop( @@ -32,50 +24,17 @@ INSTRUCTIONS = tr_noop( ) -class FirehoseLayout(Widget): - PARAM_KEY = "ApiCache_FirehoseStats" - GREEN = rl.Color(46, 204, 113, 255) - RED = rl.Color(231, 76, 60, 255) - GRAY = rl.Color(68, 68, 68, 255) - LIGHT_GRAY = rl.Color(228, 228, 228, 255) - UPDATE_INTERVAL = 30 # seconds - +class FirehoseLayout(FirehoseLayoutBase): def __init__(self): super().__init__() - self.params = Params() - self.segment_count = self._get_segment_count() - self.scroll_panel = GuiScrollPanel() - self._content_height = 0 - - self.running = True - self.update_thread = threading.Thread(target=self._update_loop, daemon=True) - self.update_thread.start() - self.last_update_time = 0 - - def show_event(self): - self.scroll_panel.set_offset(0) - - def _get_segment_count(self) -> int: - stats = self.params.get(self.PARAM_KEY) - if not stats: - return 0 - try: - return int(stats.get("firehose", 0)) - except Exception: - cloudlog.exception(f"Failed to decode firehose stats: {stats}") - return 0 - - def __del__(self): - self.running = False - if self.update_thread and self.update_thread.is_alive(): - self.update_thread.join(timeout=1.0) + self._scroll_panel = GuiScrollPanel() def _render(self, rect: rl.Rectangle): # Calculate content dimensions content_rect = rl.Rectangle(rect.x, rect.y, rect.width, self._content_height) # Handle scrolling and render with clipping - scroll_offset = self.scroll_panel.update(rect, content_rect) + scroll_offset = self._scroll_panel.update(rect, content_rect) rl.begin_scissor_mode(int(rect.x), int(rect.y), int(rect.width), int(rect.height)) self._content_height = self._render_content(rect, scroll_offset) rl.end_scissor_mode() @@ -107,9 +66,9 @@ class FirehoseLayout(Widget): y += 20 + 20 # Contribution count (if available) - if self.segment_count > 0: + if self._segment_count > 0: contrib_text = trn("{} segment of your driving is in the training dataset so far.", - "{} segments of your driving is in the training dataset so far.", self.segment_count).format(self.segment_count) + "{} segments of your driving is in the training dataset so far.", self._segment_count).format(self._segment_count) y = self._draw_wrapped_text(x, y, w, contrib_text, gui_app.font(FontWeight.BOLD), 52, rl.WHITE) y += 20 + 20 @@ -121,7 +80,7 @@ class FirehoseLayout(Widget): y = self._draw_wrapped_text(x, y, w, tr(INSTRUCTIONS), gui_app.font(FontWeight.NORMAL), 40, self.LIGHT_GRAY) # bottom margin + remove effect of scroll offset - return int(round(y - self.scroll_panel.offset + 40)) + return int(round(y - self._scroll_panel.offset + 40)) def _draw_wrapped_text(self, x, y, width, text, font, font_size, color): wrapped = wrap_text(font, text, font_size, width) @@ -129,32 +88,3 @@ class FirehoseLayout(Widget): rl.draw_text_ex(font, line, rl.Vector2(x, y), font_size, 0, color) y += font_size * FONT_SCALE return round(y) - - def _get_status(self) -> tuple[str, rl.Color]: - network_type = ui_state.sm["deviceState"].networkType - network_metered = ui_state.sm["deviceState"].networkMetered - - if not network_metered and network_type != 0: # Not metered and connected - return tr("ACTIVE"), self.GREEN - else: - return tr("INACTIVE: connect to an unmetered network"), self.RED - - def _fetch_firehose_stats(self): - try: - dongle_id = self.params.get("DongleId") - if not dongle_id or dongle_id == UNREGISTERED_DONGLE_ID: - return - identity_token = get_token(dongle_id) - response = api_get(f"v1/devices/{dongle_id}/firehose_stats", access_token=identity_token) - if response.status_code == 200: - data = response.json() - self.segment_count = data.get("firehose", 0) - self.params.put(self.PARAM_KEY, data) - except Exception as e: - cloudlog.error(f"Failed to fetch firehose stats: {e}") - - def _update_loop(self): - while self.running: - if not ui_state.started: - self._fetch_firehose_stats() - time.sleep(self.UPDATE_INTERVAL) diff --git a/selfdrive/ui/mici/layouts/settings/firehose.py b/selfdrive/ui/mici/layouts/settings/firehose.py index 303d976b0..f2f8bcb1c 100644 --- a/selfdrive/ui/mici/layouts/settings/firehose.py +++ b/selfdrive/ui/mici/layouts/settings/firehose.py @@ -12,8 +12,7 @@ from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE from openpilot.system.ui.lib.wrap_text import wrap_text from openpilot.system.ui.lib.scroll_panel2 import GuiScrollPanel2 from openpilot.system.ui.lib.multilang import tr, trn, tr_noop -from openpilot.system.ui.widgets import NavWidget - +from openpilot.system.ui.widgets import Widget, NavWidget TITLE = tr_noop("Firehose Mode") DESCRIPTION = tr_noop( @@ -34,9 +33,7 @@ FAQ_ITEMS = [ ] -class FirehoseLayoutMici(NavWidget): - BACK_TOUCH_AREA_PERCENTAGE = 0.1 - +class FirehoseLayoutBase(Widget): PARAM_KEY = "ApiCache_FirehoseStats" GREEN = rl.Color(46, 204, 113, 255) RED = rl.Color(231, 76, 60, 255) @@ -44,12 +41,10 @@ class FirehoseLayoutMici(NavWidget): LIGHT_GRAY = rl.Color(228, 228, 228, 255) UPDATE_INTERVAL = 30 # seconds - def __init__(self, back_callback): + def __init__(self): super().__init__() - self.set_back_callback(back_callback) - - self.params = Params() - self.segment_count = self._get_segment_count() + self._params = Params() + self._segment_count = self._get_segment_count() self._scroll_panel = GuiScrollPanel2(horizontal=False) self._content_height = 0 @@ -71,7 +66,7 @@ class FirehoseLayoutMici(NavWidget): self._scroll_panel.set_offset(0) def _get_segment_count(self) -> int: - stats = self.params.get(self.PARAM_KEY) + stats = self._params.get(self.PARAM_KEY) if not stats: return 0 try: @@ -111,9 +106,9 @@ class FirehoseLayoutMici(NavWidget): y += 20 # Contribution count (if available) - if self.segment_count > 0: + if self._segment_count > 0: contrib_text = trn("{} segment of your driving is in the training dataset so far.", - "{} segments of your driving is in the training dataset so far.", self.segment_count).format(self.segment_count) + "{} segments of your driving is in the training dataset so far.", self._segment_count).format(self._segment_count) y = self._draw_wrapped_text(x, y, w, contrib_text, gui_app.font(FontWeight.BOLD), 42, rl.WHITE) y += 20 @@ -165,9 +160,9 @@ class FirehoseLayoutMici(NavWidget): y += int(len(status_lines) * 48 * FONT_SCALE) + 20 # Contribution count - if self.segment_count > 0: + if self._segment_count > 0: contrib_text = trn("{} segment of your driving is in the training dataset so far.", - "{} segments of your driving is in the training dataset so far.", self.segment_count).format(self.segment_count) + "{} segments of your driving is in the training dataset so far.", self._segment_count).format(self._segment_count) contrib_lines = wrap_text(gui_app.font(FontWeight.BOLD), contrib_text, 42, w) y += int(len(contrib_lines) * 42 * FONT_SCALE) + 20 @@ -204,15 +199,15 @@ class FirehoseLayoutMici(NavWidget): def _fetch_firehose_stats(self): try: - dongle_id = self.params.get("DongleId") + dongle_id = self._params.get("DongleId") if not dongle_id or dongle_id == UNREGISTERED_DONGLE_ID: return identity_token = get_token(dongle_id) response = api_get(f"v1/devices/{dongle_id}/firehose_stats", access_token=identity_token) if response.status_code == 200: data = response.json() - self.segment_count = data.get("firehose", 0) - self.params.put(self.PARAM_KEY, data) + self._segment_count = data.get("firehose", 0) + self._params.put(self.PARAM_KEY, data) except Exception as e: cloudlog.error(f"Failed to fetch firehose stats: {e}") @@ -221,3 +216,11 @@ class FirehoseLayoutMici(NavWidget): if not ui_state.started: self._fetch_firehose_stats() time.sleep(self.UPDATE_INTERVAL) + + +class FirehoseLayout(FirehoseLayoutBase, NavWidget): + BACK_TOUCH_AREA_PERCENTAGE = 0.1 + + def __init__(self, back_callback): + super().__init__() + self.set_back_callback(back_callback) diff --git a/selfdrive/ui/mici/layouts/settings/settings.py b/selfdrive/ui/mici/layouts/settings/settings.py index 75238d581..a45277774 100644 --- a/selfdrive/ui/mici/layouts/settings/settings.py +++ b/selfdrive/ui/mici/layouts/settings/settings.py @@ -10,7 +10,7 @@ from openpilot.selfdrive.ui.mici.layouts.settings.toggles import TogglesLayoutMi from openpilot.selfdrive.ui.mici.layouts.settings.network import NetworkLayoutMici from openpilot.selfdrive.ui.mici.layouts.settings.device import DeviceLayoutMici, PairBigButton from openpilot.selfdrive.ui.mici.layouts.settings.developer import DeveloperLayoutMici -from openpilot.selfdrive.ui.mici.layouts.settings.firehose import FirehoseLayoutMici +from openpilot.selfdrive.ui.mici.layouts.settings.firehose import FirehoseLayout from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.widgets import Widget, NavWidget @@ -67,7 +67,7 @@ class SettingsLayout(NavWidget): PanelType.NETWORK: PanelInfo("Network", NetworkLayoutMici(back_callback=lambda: self._set_current_panel(None))), PanelType.DEVICE: PanelInfo("Device", DeviceLayoutMici(back_callback=lambda: self._set_current_panel(None))), PanelType.DEVELOPER: PanelInfo("Developer", DeveloperLayoutMici(back_callback=lambda: self._set_current_panel(None))), - PanelType.FIREHOSE: PanelInfo("Firehose", FirehoseLayoutMici(back_callback=lambda: self._set_current_panel(None))), + PanelType.FIREHOSE: PanelInfo("Firehose", FirehoseLayout(back_callback=lambda: self._set_current_panel(None))), } self._font_medium = gui_app.font(FontWeight.MEDIUM) From 946fd3f3879fd7b15a6a296d9303971bff8a222b Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 27 Nov 2025 01:00:33 -0800 Subject: [PATCH 450/910] NavWidget: draw black above top of widget when dismissing (#36702) draw rec --- system/ui/widgets/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/system/ui/widgets/__init__.py b/system/ui/widgets/__init__.py index 95858ec1b..18a63a736 100644 --- a/system/ui/widgets/__init__.py +++ b/system/ui/widgets/__init__.py @@ -359,6 +359,10 @@ class NavWidget(Widget, abc.ABC): self._nav_bar.set_position(bar_x, round(self._nav_bar_y_filter.x)) self._nav_bar.render() + # draw black above widget when dismissing + if self._rect.y > 0: + rl.draw_rectangle(int(self._rect.x), 0, int(self._rect.width), int(self._rect.y), rl.BLACK) + return ret def show_event(self): From 26261387f8b4188951d107567a067d22cf87e5e3 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 27 Nov 2025 01:37:16 -0800 Subject: [PATCH 451/910] Fix raylib ui spamming API calls (#36700) * intern * start * move * common caching * use constant for slep * works * add gating back * clean up * more * match cache logic * hate this circular * not needed since sync * no need for lock? * even qt had something like _load_initial_state for tests, keep * clean up * clean up * clean up * loading json as string works, else it will fail to parse json, catch that and log, and next api call will overwrite * move over firehose * clean up * fix test * no * flip * more * match qt * consistent * clean up * cmt * fix test! --- common/params_keys.h | 2 +- common/tests/test_params.py | 4 +- selfdrive/ui/lib/api_helpers.py | 93 ++++++++++++++++++- selfdrive/ui/lib/prime_state.py | 75 ++++----------- .../ui/mici/layouts/settings/firehose.py | 65 ++++--------- 5 files changed, 131 insertions(+), 108 deletions(-) diff --git a/common/params_keys.h b/common/params_keys.h index bf410825f..fc7f410e4 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -10,7 +10,7 @@ inline static std::unordered_map keys = { {"AdbEnabled", {PERSISTENT, BOOL}}, {"AlwaysOnDM", {PERSISTENT, BOOL}}, {"ApiCache_Device", {PERSISTENT, STRING}}, - {"ApiCache_FirehoseStats", {PERSISTENT, JSON}}, + {"ApiCache_FirehoseStats", {PERSISTENT, STRING}}, {"AssistNowToken", {PERSISTENT, STRING}}, {"AthenadPid", {PERSISTENT, INT}}, {"AthenadUploadQueue", {PERSISTENT, JSON}}, diff --git a/common/tests/test_params.py b/common/tests/test_params.py index 592bf2c4b..e0f213e02 100644 --- a/common/tests/test_params.py +++ b/common/tests/test_params.py @@ -123,8 +123,8 @@ class TestParams: def test_params_get_type(self): # json - self.params.put("ApiCache_FirehoseStats", {"a": 0}) - assert self.params.get("ApiCache_FirehoseStats") == {"a": 0} + self.params.put("LiveParameters", {"a": 0}) + assert self.params.get("LiveParameters") == {"a": 0} # int self.params.put("BootCount", 1441) diff --git a/selfdrive/ui/lib/api_helpers.py b/selfdrive/ui/lib/api_helpers.py index 8ed1c22a6..31e844dd0 100644 --- a/selfdrive/ui/lib/api_helpers.py +++ b/selfdrive/ui/lib/api_helpers.py @@ -1,7 +1,13 @@ import time +import threading +from collections.abc import Callable from functools import lru_cache -from openpilot.common.api import Api + +from openpilot.common.api import Api, api_get +from openpilot.common.params import Params +from openpilot.common.swaglog import cloudlog from openpilot.common.time_helpers import system_time_valid +from openpilot.system.athena.registration import UNREGISTERED_DONGLE_ID TOKEN_EXPIRY_HOURS = 2 @@ -16,3 +22,88 @@ def _get_token(dongle_id: str, t: int): def get_token(dongle_id: str): return _get_token(dongle_id, int(time.monotonic() / (TOKEN_EXPIRY_HOURS / 2 * 60 * 60))) + + +class RequestRepeater: + API_TIMEOUT = 10.0 # seconds for API requests + SLEEP_INTERVAL = 0.5 # seconds to sleep between checks in the worker thread + + def __init__(self, dongle_id: str, request_route: str, period: int, cache_key: str | None = None): + self._dongle_id = dongle_id + self._request_route = request_route + self._period = period # seconds + self._cache_key = cache_key + + self._request_done_callbacks: list[Callable[[str, bool], None]] = [] + self._prev_response_text = None + self._running = False + self._thread = None + self._params = Params() + + if self._cache_key is not None: + # Cache successful responses to params + def cache_response(response: str, success: bool): + if success and response != self._prev_response_text: + self._params.put(self._cache_key, response) + self._prev_response_text = response + + self.add_request_done_callback(cache_response) + + def add_request_done_callback(self, callback: Callable[[str, bool], None]): + self._request_done_callbacks.append(callback) + + def _do_callbacks(self, response_text: str, success: bool): + for callback in self._request_done_callbacks: + try: + callback(response_text, success) + except Exception as e: + cloudlog.error(f"RequestRepeater callback error: {e}") + + def load_cache(self): + # call callbacks with cached response + if self._cache_key is not None: + self._prev_response_text = self._params.get(self._cache_key) + if self._prev_response_text: + self._do_callbacks(self._prev_response_text, True) + + def start(self): + if self._thread and self._thread.is_alive(): + return + self._running = True + self._thread = threading.Thread(target=self._worker_thread, daemon=True) + self._thread.start() + + def stop(self): + self._running = False + if self._thread and self._thread.is_alive(): + self._thread.join(timeout=1.0) + + def _worker_thread(self): + # Avoid circular imports + from openpilot.selfdrive.ui.ui_state import ui_state, device + + while self._running: + # Don't run when device is asleep or onroad + if not ui_state.started and device.awake: + self._send_request() + + for _ in range(int(self._period / self.SLEEP_INTERVAL)): + if not self._running: + break + time.sleep(self.SLEEP_INTERVAL) + + def _send_request(self): + if not self._dongle_id or self._dongle_id == UNREGISTERED_DONGLE_ID: + return + + try: + identity_token = get_token(self._dongle_id) + response = api_get(self._request_route, timeout=self.API_TIMEOUT, access_token=identity_token) + self._do_callbacks(response.text, 200 <= response.status_code < 300) + + except Exception as e: + cloudlog.error(f"Failed to send request to {self._request_route}: {e}") + self._do_callbacks("", False) + + def __del__(self): + self.stop() diff --git a/selfdrive/ui/lib/prime_state.py b/selfdrive/ui/lib/prime_state.py index fc72b4f9c..f3adebf4b 100644 --- a/selfdrive/ui/lib/prime_state.py +++ b/selfdrive/ui/lib/prime_state.py @@ -1,13 +1,10 @@ from enum import IntEnum import os -import threading -import time +import json -from openpilot.common.api import api_get from openpilot.common.params import Params from openpilot.common.swaglog import cloudlog -from openpilot.system.athena.registration import UNREGISTERED_DONGLE_ID -from openpilot.selfdrive.ui.lib.api_helpers import get_token +from openpilot.selfdrive.ui.lib.api_helpers import RequestRepeater class PrimeType(IntEnum): @@ -22,17 +19,14 @@ class PrimeType(IntEnum): class PrimeState: - FETCH_INTERVAL = 5.0 # seconds between API calls - API_TIMEOUT = 10.0 # seconds for API requests - SLEEP_INTERVAL = 0.5 # seconds to sleep between checks in the worker thread - def __init__(self): self._params = Params() - self._lock = threading.Lock() self.prime_type: PrimeType = self._load_initial_state() - self._running = False - self._thread = None + dongle_id = self._params.get("DongleId") + self._request_repeater = RequestRepeater(dongle_id, f"v1.1/devices/{dongle_id}", 5, "ApiCache_Device") + self._request_repeater.add_request_done_callback(self._handle_reply) + self._request_repeater.load_cache() # sets prime_type from API response cache def _load_initial_state(self) -> PrimeType: prime_type_str = os.getenv("PRIME_TYPE") or self._params.get("PrimeType") @@ -43,61 +37,32 @@ class PrimeState: pass return PrimeType.UNKNOWN - def _fetch_prime_status(self) -> None: - dongle_id = self._params.get("DongleId") - if not dongle_id or dongle_id == UNREGISTERED_DONGLE_ID: + def _handle_reply(self, response: str, success: bool): + if not success: return try: - identity_token = get_token(dongle_id) - response = api_get(f"v1.1/devices/{dongle_id}", timeout=self.API_TIMEOUT, access_token=identity_token) - if response.status_code == 200: - data = response.json() - is_paired = data.get("is_paired", False) - prime_type = data.get("prime_type", 0) - self.set_type(PrimeType(prime_type) if is_paired else PrimeType.UNPAIRED) + data = json.loads(response) + is_paired = data.get("is_paired", False) + prime_type = data.get("prime_type", 0) + self.set_type(PrimeType(prime_type) if is_paired else PrimeType.UNPAIRED) except Exception as e: cloudlog.error(f"Failed to fetch prime status: {e}") def set_type(self, prime_type: PrimeType) -> None: - with self._lock: - if prime_type != self.prime_type: - self.prime_type = prime_type - self._params.put("PrimeType", int(prime_type)) - cloudlog.info(f"Prime type updated to {prime_type}") - - def _worker_thread(self) -> None: - while self._running: - self._fetch_prime_status() - - for _ in range(int(self.FETCH_INTERVAL / self.SLEEP_INTERVAL)): - if not self._running: - break - time.sleep(self.SLEEP_INTERVAL) + if prime_type != self.prime_type: + self.prime_type = prime_type + self._params.put("PrimeType", int(prime_type)) + cloudlog.info(f"Prime type updated to {prime_type}") def start(self) -> None: - if self._thread and self._thread.is_alive(): - return - self._running = True - self._thread = threading.Thread(target=self._worker_thread, daemon=True) - self._thread.start() - - def stop(self) -> None: - self._running = False - if self._thread and self._thread.is_alive(): - self._thread.join(timeout=1.0) + self._request_repeater.start() def get_type(self) -> PrimeType: - with self._lock: - return self.prime_type + return self.prime_type def is_prime(self) -> bool: - with self._lock: - return bool(self.prime_type > PrimeType.NONE) + return bool(self.prime_type > PrimeType.NONE) def is_paired(self) -> bool: - with self._lock: - return self.prime_type > PrimeType.UNPAIRED - - def __del__(self): - self.stop() + return self.prime_type > PrimeType.UNPAIRED diff --git a/selfdrive/ui/mici/layouts/settings/firehose.py b/selfdrive/ui/mici/layouts/settings/firehose.py index f2f8bcb1c..5cc43322d 100644 --- a/selfdrive/ui/mici/layouts/settings/firehose.py +++ b/selfdrive/ui/mici/layouts/settings/firehose.py @@ -1,18 +1,15 @@ -import threading -import time import pyray as rl +import json -from openpilot.common.api import api_get from openpilot.common.params import Params from openpilot.common.swaglog import cloudlog -from openpilot.selfdrive.ui.lib.api_helpers import get_token from openpilot.selfdrive.ui.ui_state import ui_state -from openpilot.system.athena.registration import UNREGISTERED_DONGLE_ID from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE from openpilot.system.ui.lib.wrap_text import wrap_text from openpilot.system.ui.lib.scroll_panel2 import GuiScrollPanel2 from openpilot.system.ui.lib.multilang import tr, trn, tr_noop from openpilot.system.ui.widgets import Widget, NavWidget +from openpilot.selfdrive.ui.lib.api_helpers import RequestRepeater TITLE = tr_noop("Firehose Mode") DESCRIPTION = tr_noop( @@ -34,47 +31,37 @@ FAQ_ITEMS = [ class FirehoseLayoutBase(Widget): - PARAM_KEY = "ApiCache_FirehoseStats" GREEN = rl.Color(46, 204, 113, 255) RED = rl.Color(231, 76, 60, 255) GRAY = rl.Color(68, 68, 68, 255) LIGHT_GRAY = rl.Color(228, 228, 228, 255) - UPDATE_INTERVAL = 30 # seconds def __init__(self): super().__init__() - self._params = Params() - self._segment_count = self._get_segment_count() - + self._segment_count = 0 self._scroll_panel = GuiScrollPanel2(horizontal=False) self._content_height = 0 - self._running = True - self._update_thread = threading.Thread(target=self._update_loop, daemon=True) - self._update_thread.start() + dongle_id = Params().get("DongleId") + self._request_repeater = RequestRepeater(dongle_id, f"v1/devices/{dongle_id}/firehose_stats", 30, "ApiCache_FirehoseStats") + self._request_repeater.add_request_done_callback(self._handle_reply) + self._request_repeater.load_cache() + self._request_repeater.start() + + def _handle_reply(self, response: str, success: bool): + if not success: + return - def __del__(self): - self._running = False try: - if self._update_thread and self._update_thread.is_alive(): - self._update_thread.join(timeout=1.0) - except Exception: - pass + data = json.loads(response) + self._segment_count = data.get("firehose", 0) + except Exception as e: + cloudlog.error(f"Failed to fetch firehose stats: {e}") def show_event(self): super().show_event() self._scroll_panel.set_offset(0) - def _get_segment_count(self) -> int: - stats = self._params.get(self.PARAM_KEY) - if not stats: - return 0 - try: - return int(stats.get("firehose", 0)) - except Exception: - cloudlog.exception(f"Failed to decode firehose stats: {stats}") - return 0 - def _render(self, rect: rl.Rectangle): # compute total content height for scrolling content_height = self._measure_content_height(rect) @@ -197,26 +184,6 @@ class FirehoseLayoutBase(Widget): else: return tr("INACTIVE: connect to an unmetered network"), self.RED - def _fetch_firehose_stats(self): - try: - dongle_id = self._params.get("DongleId") - if not dongle_id or dongle_id == UNREGISTERED_DONGLE_ID: - return - identity_token = get_token(dongle_id) - response = api_get(f"v1/devices/{dongle_id}/firehose_stats", access_token=identity_token) - if response.status_code == 200: - data = response.json() - self._segment_count = data.get("firehose", 0) - self._params.put(self.PARAM_KEY, data) - except Exception as e: - cloudlog.error(f"Failed to fetch firehose stats: {e}") - - def _update_loop(self): - while self._running: - if not ui_state.started: - self._fetch_firehose_stats() - time.sleep(self.UPDATE_INTERVAL) - class FirehoseLayout(FirehoseLayoutBase, NavWidget): BACK_TOUCH_AREA_PERCENTAGE = 0.1 From b8d55987c25598e871e771c4cf100032e55b5145 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Thu, 27 Nov 2025 17:50:36 +0800 Subject: [PATCH 452/910] ui: extract and optimize mouse event processing (#36564) * extract and optimize mouse event processing * rm slot * merge mici mastere * add mouse --------- Co-authored-by: Shane Smiskol --- system/ui/widgets/__init__.py | 77 +++++++++++++++++++---------------- 1 file changed, 42 insertions(+), 35 deletions(-) diff --git a/system/ui/widgets/__init__.py b/system/ui/widgets/__init__.py index 18a63a736..546c682f3 100644 --- a/system/ui/widgets/__init__.py +++ b/system/ui/widgets/__init__.py @@ -104,46 +104,53 @@ class Widget(abc.ABC): # Keep track of whether mouse down started within the widget's rectangle if self.enabled and self.__was_awake: - for mouse_event in gui_app.mouse_events: - if not self._multi_touch and mouse_event.slot != 0: - continue - - # Ignores touches/presses that start outside our rect - # Allows touch to leave the rect and come back in focus if mouse did not release - if mouse_event.left_pressed and self._touch_valid(): - if rl.check_collision_point_rec(mouse_event.pos, self._hit_rect): - self._handle_mouse_press(mouse_event.pos) - self.__is_pressed[mouse_event.slot] = True - self.__tracking_is_pressed[mouse_event.slot] = True - self._handle_mouse_event(mouse_event) - - # Callback such as scroll panel signifies user is scrolling - elif not self._touch_valid(): - self.__is_pressed[mouse_event.slot] = False - self.__tracking_is_pressed[mouse_event.slot] = False - - elif mouse_event.left_released: - self._handle_mouse_event(mouse_event) - if self.__is_pressed[mouse_event.slot] and rl.check_collision_point_rec(mouse_event.pos, self._hit_rect): - self._handle_mouse_release(mouse_event.pos) - self.__is_pressed[mouse_event.slot] = False - self.__tracking_is_pressed[mouse_event.slot] = False - - # Mouse/touch is still within our rect - elif rl.check_collision_point_rec(mouse_event.pos, self._hit_rect): - if self.__tracking_is_pressed[mouse_event.slot]: - self.__is_pressed[mouse_event.slot] = True - self._handle_mouse_event(mouse_event) - - # Mouse/touch left our rect but may come back into focus later - elif not rl.check_collision_point_rec(mouse_event.pos, self._hit_rect): - self.__is_pressed[mouse_event.slot] = False - self._handle_mouse_event(mouse_event) + self._process_mouse_events() self.__was_awake = device.awake return ret + def _process_mouse_events(self) -> None: + hit_rect = self._hit_rect + touch_valid = self._touch_valid() + + for mouse_event in gui_app.mouse_events: + if not self._multi_touch and mouse_event.slot != 0: + continue + + mouse_in_rect = rl.check_collision_point_rec(mouse_event.pos, hit_rect) + # Ignores touches/presses that start outside our rect + # Allows touch to leave the rect and come back in focus if mouse did not release + if mouse_event.left_pressed and touch_valid: + if mouse_in_rect: + self._handle_mouse_press(mouse_event.pos) + self.__is_pressed[mouse_event.slot] = True + self.__tracking_is_pressed[mouse_event.slot] = True + self._handle_mouse_event(mouse_event) + + # Callback such as scroll panel signifies user is scrolling + elif not touch_valid: + self.__is_pressed[mouse_event.slot] = False + self.__tracking_is_pressed[mouse_event.slot] = False + + elif mouse_event.left_released: + self._handle_mouse_event(mouse_event) + if self.__is_pressed[mouse_event.slot] and mouse_in_rect: + self._handle_mouse_release(mouse_event.pos) + self.__is_pressed[mouse_event.slot] = False + self.__tracking_is_pressed[mouse_event.slot] = False + + # Mouse/touch is still within our rect + elif mouse_in_rect: + if self.__tracking_is_pressed[mouse_event.slot]: + self.__is_pressed[mouse_event.slot] = True + self._handle_mouse_event(mouse_event) + + # Mouse/touch left our rect but may come back into focus later + elif not mouse_in_rect: + self.__is_pressed[mouse_event.slot] = False + self._handle_mouse_event(mouse_event) + @abc.abstractmethod def _render(self, rect: rl.Rectangle) -> bool | int | None: """Render the widget within the given rectangle.""" From ae6ada41621566424305bb4e71537eb1d8496e8b Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Thu, 27 Nov 2025 03:52:25 -0600 Subject: [PATCH 453/910] lint: Add PLE rule to ruff (#36595) * update linting rules to include new PLE (pylint error) rule * fix lint error --- pyproject.toml | 2 +- system/updated/casync/casync.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 3f3804ba6..3f3c8a72b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -225,7 +225,7 @@ lint.select = [ "TRY203", "TRY400", "TRY401", # try/excepts "RUF008", "RUF100", "TID251", - "PLR1704", + "PLE", "PLR1704", ] lint.ignore = [ "E741", diff --git a/system/updated/casync/casync.py b/system/updated/casync/casync.py index 7a3303a9e..79ac26f1c 100755 --- a/system/updated/casync/casync.py +++ b/system/updated/casync/casync.py @@ -99,7 +99,7 @@ class DirectoryTarChunkReader(BinaryChunkReader): create_casync_tar_package(pathlib.Path(path), pathlib.Path(cache_file)) self.f = open(cache_file, "rb") - return super().__init__(self.f) + super().__init__(self.f) def __del__(self): self.f.close() From ae534ddeab171f89a4a18c6998bb5b7666b495e1 Mon Sep 17 00:00:00 2001 From: Logesh R Date: Thu, 27 Nov 2025 02:07:56 -0800 Subject: [PATCH 454/910] docs: Fix "Turn the speed blue" tutorial for Raylib UI (#36591) * docs: Fix "Turn the speed blue" tutorial for Raylib UI * just * obv * not replay --------- Co-authored-by: Shane Smiskol --- docs/how-to/turn-the-speed-blue.md | 37 ++++++++++++++---------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/docs/how-to/turn-the-speed-blue.md b/docs/how-to/turn-the-speed-blue.md index eb6e75afa..644c35e0a 100644 --- a/docs/how-to/turn-the-speed-blue.md +++ b/docs/how-to/turn-the-speed-blue.md @@ -31,7 +31,7 @@ We'll run the `replay` tool with the demo route to get data streaming for testin tools/replay/replay --demo # in terminal 2 -selfdrive/ui/ui +./selfdrive/ui/ui.py ``` The openpilot UI should launch and show a replay of the demo route. @@ -43,39 +43,36 @@ If you have your own comma device, you can replace `--demo` with one of your own Now let’s update the speed display color in the UI. -Search for the function responsible for rendering UI text: +Search for the function responsible for rendering the current speed: ```bash -git grep "drawText" selfdrive/ui/qt/onroad/hud.cc +git grep "_draw_current_speed" selfdrive/ui/onroad/hud_renderer.py ``` -You’ll find the relevant code inside `selfdrive/ui/qt/onroad/hud.cc`, in this function: +You'll find the relevant code inside `selfdrive/ui/onroad/hud_renderer.py`, in this function: -```cpp -void HudRenderer::drawText(QPainter &p, int x, int y, const QString &text, int alpha) { - QRect real_rect = p.fontMetrics().boundingRect(text); - real_rect.moveCenter({x, y - real_rect.height() / 2}); - - p.setPen(QColor(0xff, 0xff, 0xff, alpha)); // <- this sets the speed text color - p.drawText(real_rect.x(), real_rect.bottom(), text); -} +```python +def _draw_current_speed(self, rect: rl.Rectangle) -> None: + """Draw the current vehicle speed and unit.""" + speed_text = str(round(self.speed)) + speed_text_size = measure_text_cached(self._font_bold, speed_text, FONT_SIZES.current_speed) + speed_pos = rl.Vector2(rect.x + rect.width / 2 - speed_text_size.x / 2, 180 - speed_text_size.y / 2) + rl.draw_text_ex(self._font_bold, speed_text, speed_pos, FONT_SIZES.current_speed, 0, COLORS.white) # <- this sets the speed text color ``` -Change the `QColor(...)` line to make it **blue** instead of white. A nice soft blue is `#8080FF`, which translates to: +Change `COLORS.white` to make it **blue** instead of white. A nice soft blue is `#8080FF`, which you can change inline: ```diff -- p.setPen(QColor(0xff, 0xff, 0xff, alpha)); -+ p.setPen(QColor(0x80, 0x80, 0xFF, alpha)); +- rl.draw_text_ex(self._font_bold, speed_text, speed_pos, FONT_SIZES.current_speed, 0, COLORS.white) ++ rl.draw_text_ex(self._font_bold, speed_text, speed_pos, FONT_SIZES.current_speed, 0, rl.Color(0x80, 0x80, 0xFF, 255)) ``` -This change will tint all speed-related UI text to blue with the same transparency (`alpha`). - --- -## 4. Rebuild the UI +## 4. Re-run the UI -After making changes, rebuild Openpilot so your new UI is compiled: +After making changes, re-run the UI to see your new UI: ```bash -scons -j$(nproc) && selfdrive/ui/ui +./selfdrive/ui/ui.py ``` ![](https://blog.comma.ai/img/blue_speed_ui.png) From 4bd6fb09954e4df5472131f19f695bb32415728a Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Thu, 27 Nov 2025 18:17:16 +0800 Subject: [PATCH 455/910] ui: fix unconditional rl.end_scissor_mode() call in MiciLabel (#36660) fix incorrect end_scissor_mode usage --- system/ui/widgets/label.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/ui/widgets/label.py b/system/ui/widgets/label.py index 35e2708e6..b6e67d03a 100644 --- a/system/ui/widgets/label.py +++ b/system/ui/widgets/label.py @@ -183,7 +183,7 @@ class MiciLabel(Widget): if self._scroll_state != ScrollState.STARTING: rl.draw_rectangle_gradient_h(int(rect.x), int(rect.y), fade_width, int(rect.height), rl.BLACK, rl.Color(0, 0, 0, 0)) - rl.end_scissor_mode() + rl.end_scissor_mode() # TODO: This should be a Widget class From 4ef82c4119ce8e8b7f360d2e21434ac441b25009 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Thu, 27 Nov 2025 18:20:49 +0800 Subject: [PATCH 456/910] ui: optimize matrix operations in scroller rendering (#36668) optimize matrix operations --- system/ui/widgets/scroller.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/system/ui/widgets/scroller.py b/system/ui/widgets/scroller.py index 9a04e8425..3cb6a1e18 100644 --- a/system/ui/widgets/scroller.py +++ b/system/ui/widgets/scroller.py @@ -224,12 +224,15 @@ class Scroller(Widget): # Scale each element around its own origin when scrolling scale = self._zoom_filter.x - rl.rl_push_matrix() - rl.rl_scalef(scale, scale, 1.0) - rl.rl_translatef((1 - scale) * (x + item.rect.width / 2) / scale, - (1 - scale) * (y + item.rect.height / 2) / scale, 0) - item.render() - rl.rl_pop_matrix() + if scale != 1.0: + rl.rl_push_matrix() + rl.rl_scalef(scale, scale, 1.0) + rl.rl_translatef((1 - scale) * (x + item.rect.width / 2) / scale, + (1 - scale) * (y + item.rect.height / 2) / scale, 0) + item.render() + rl.rl_pop_matrix() + else: + item.render() # Draw scroll indicator if SCROLL_BAR and not self._horizontal and len(visible_items) > 0: From 394f580f161195f135c2b5c3723db9c7bab06147 Mon Sep 17 00:00:00 2001 From: Najib Muhammad Date: Thu, 27 Nov 2025 11:26:29 +0100 Subject: [PATCH 457/910] fix the CI Weekly Report workflow so it does not fail on forks (#36664) --- .github/workflows/ci_weekly_report.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci_weekly_report.yaml b/.github/workflows/ci_weekly_report.yaml index 9821283cb..37a46b209 100644 --- a/.github/workflows/ci_weekly_report.yaml +++ b/.github/workflows/ci_weekly_report.yaml @@ -38,7 +38,7 @@ jobs: report: needs: [ci_matrix_run] runs-on: ubuntu-latest - if: always() + if: always() && github.repository == 'commaai/openpilot' steps: - name: Get job results uses: actions/github-script@v7 From 630e14fd7f61a1f2d8a5acad2d98b7dc304cddbd Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Thu, 27 Nov 2025 18:28:13 +0800 Subject: [PATCH 458/910] ui: avoid unnecessary text cache invalidation in UnifiedLabel (#36676) avoid unnecessary text cache invalidation in UnifiedLabel --- system/ui/widgets/label.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/system/ui/widgets/label.py b/system/ui/widgets/label.py index b6e67d03a..c90b111de 100644 --- a/system/ui/widgets/label.py +++ b/system/ui/widgets/label.py @@ -446,7 +446,7 @@ class UnifiedLabel(Widget): def set_text(self, text: str | Callable[[], str]): """Update the text content.""" self._text = text - self._cached_text = None # Invalidate cache + # No need to update cache here, will be done on next render if needed @property def text(self) -> str: @@ -463,15 +463,17 @@ class UnifiedLabel(Widget): def set_font_size(self, size: int): """Update the font size.""" - self._font_size = size - self._spacing_pixels = size * self._letter_spacing # Recalculate spacing - self._cached_text = None # Invalidate cache + if self._font_size != size: + self._font_size = size + self._spacing_pixels = size * self._letter_spacing # Recalculate spacing + self._cached_text = None # Invalidate cache def set_letter_spacing(self, letter_spacing: float): """Update letter spacing (as percentage, e.g., 0.1 = 10%).""" - self._letter_spacing = letter_spacing - self._spacing_pixels = self._font_size * letter_spacing - self._cached_text = None # Invalidate cache + if self._letter_spacing != letter_spacing: + self._letter_spacing = letter_spacing + self._spacing_pixels = self._font_size * letter_spacing + self._cached_text = None # Invalidate cache def set_font_weight(self, font_weight: FontWeight): """Update the font weight.""" From d0489062b5e2e41cc2f9bf5c4400530abcd92489 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Thu, 27 Nov 2025 18:28:51 +0800 Subject: [PATCH 459/910] ui: remove unused members and variables (#36677) remove unused members and variables --- selfdrive/ui/mici/onroad/alert_renderer.py | 4 ---- selfdrive/ui/mici/onroad/hud_renderer.py | 12 ------------ 2 files changed, 16 deletions(-) diff --git a/selfdrive/ui/mici/onroad/alert_renderer.py b/selfdrive/ui/mici/onroad/alert_renderer.py index eb5555660..bdf85acc3 100644 --- a/selfdrive/ui/mici/onroad/alert_renderer.py +++ b/selfdrive/ui/mici/onroad/alert_renderer.py @@ -89,10 +89,6 @@ ALERT_CRITICAL_REBOOT = Alert( class AlertRenderer(Widget): def __init__(self): super().__init__() - self.font_regular: rl.Font = gui_app.font(FontWeight.MEDIUM) - self.font_roman: rl.Font = gui_app.font(FontWeight.ROMAN) - self.font_bold: rl.Font = gui_app.font(FontWeight.BOLD) - self.font_display: rl.Font = gui_app.font(FontWeight.DISPLAY) self._alert_text1_label = UnifiedLabel(text="", font_size=ALERT_FONT_BIG, font_weight=FontWeight.DISPLAY, line_height=0.86, letter_spacing=-0.02) diff --git a/selfdrive/ui/mici/onroad/hud_renderer.py b/selfdrive/ui/mici/onroad/hud_renderer.py index bb5171d6e..524eb1163 100644 --- a/selfdrive/ui/mici/onroad/hud_renderer.py +++ b/selfdrive/ui/mici/onroad/hud_renderer.py @@ -31,19 +31,7 @@ class FontSizes: @dataclass(frozen=True) class Colors: white: rl.Color = rl.WHITE - disengaged: rl.Color = rl.Color(145, 155, 149, 255) - override: rl.Color = rl.Color(145, 155, 149, 255) # Added - engaged: rl.Color = rl.Color(128, 216, 166, 255) - disengaged_bg: rl.Color = rl.Color(0, 0, 0, 153) - override_bg: rl.Color = rl.Color(145, 155, 149, 204) - engaged_bg: rl.Color = rl.Color(128, 216, 166, 204) - grey: rl.Color = rl.Color(166, 166, 166, 255) - dark_grey: rl.Color = rl.Color(114, 114, 114, 255) - black_translucent: rl.Color = rl.Color(0, 0, 0, 166) white_translucent: rl.Color = rl.Color(255, 255, 255, 200) - border_translucent: rl.Color = rl.Color(255, 255, 255, 75) - header_gradient_start: rl.Color = rl.Color(0, 0, 0, 114) - header_gradient_end: rl.Color = rl.BLANK FONT_SIZES = FontSizes() From 0a0fadb16a29cfd0565e50928ee2119ac884e479 Mon Sep 17 00:00:00 2001 From: Calvin Park Date: Thu, 27 Nov 2025 05:44:13 -0500 Subject: [PATCH 460/910] Skip onboarding on PC (#36688) * Skip onboarding on PC * do this instead --------- Co-authored-by: Shane Smiskol --- selfdrive/ui/mici/onroad/driver_state.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/selfdrive/ui/mici/onroad/driver_state.py b/selfdrive/ui/mici/onroad/driver_state.py index 369055846..080083c3e 100644 --- a/selfdrive/ui/mici/onroad/driver_state.py +++ b/selfdrive/ui/mici/onroad/driver_state.py @@ -3,6 +3,7 @@ from collections.abc import Callable import numpy as np import math from cereal import log +from openpilot.system.hardware import PC from openpilot.common.filter_simple import FirstOrderFilter from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.widgets import Widget @@ -217,7 +218,10 @@ class DriverStateRenderer(Widget): rotation = math.degrees(math.atan2(pitch, yaw)) angle_diff = rotation - self._rotation_filter.x angle_diff = ((angle_diff + 180) % 360) - 180 - self._rotation_filter.update(self._rotation_filter.x + angle_diff) + if PC and self._confirm_mode: + self._rotation_filter.x += 2 + else: + self._rotation_filter.update(self._rotation_filter.x + angle_diff) if not self.should_draw: self._fade_filter.update(0.0) From f8d0f22344de88cf33c74981ec2164d141345f36 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Thu, 27 Nov 2025 19:22:15 +0800 Subject: [PATCH 461/910] ui: ensure auto-scroll stops correctly near target (#36686) ensure auto-scroll stops correctly near target --- system/ui/lib/scroll_panel2.py | 1 + 1 file changed, 1 insertion(+) diff --git a/system/ui/lib/scroll_panel2.py b/system/ui/lib/scroll_panel2.py index 8d9caadfd..8677c32f8 100644 --- a/system/ui/lib/scroll_panel2.py +++ b/system/ui/lib/scroll_panel2.py @@ -88,6 +88,7 @@ class GuiScrollPanel2: # Steady once we are close enough to the target if abs(dist) < 1 and abs(self._velocity) < MIN_VELOCITY: self.set_offset(target) + self._velocity = 0.0 self._state = ScrollState.STEADY elif abs(self._velocity) < MIN_VELOCITY: From 3959200a5b6ea17845b9c6e8f516f532c2123efd Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 27 Nov 2025 03:25:30 -0800 Subject: [PATCH 462/910] Scroll panel 2: use float for offset (#36705) * use float internally * use it everywhere -- this fixes all the problemS?! * round it everywhere * this looks so much better than before * rm --- selfdrive/ui/mici/layouts/settings/device.py | 2 +- selfdrive/ui/mici/layouts/settings/firehose.py | 2 +- system/ui/lib/scroll_panel2.py | 6 +++--- system/ui/mici_setup.py | 2 +- system/ui/widgets/scroller.py | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/device.py b/selfdrive/ui/mici/layouts/settings/device.py index b5e0ea838..988c823a9 100644 --- a/selfdrive/ui/mici/layouts/settings/device.py +++ b/selfdrive/ui/mici/layouts/settings/device.py @@ -39,7 +39,7 @@ class MiciFccModal(NavWidget): content_height += self._fcc_logo.height + 20 scroll_content_rect = rl.Rectangle(rect.x, rect.y, rect.width, content_height) - scroll_offset = self._scroll_panel.update(rect, scroll_content_rect.height) + scroll_offset = round(self._scroll_panel.update(rect, scroll_content_rect.height)) fcc_pos = rl.Vector2(rect.x + 20, rect.y + 20 + scroll_offset) diff --git a/selfdrive/ui/mici/layouts/settings/firehose.py b/selfdrive/ui/mici/layouts/settings/firehose.py index 5cc43322d..ff75e6f8e 100644 --- a/selfdrive/ui/mici/layouts/settings/firehose.py +++ b/selfdrive/ui/mici/layouts/settings/firehose.py @@ -65,7 +65,7 @@ class FirehoseLayoutBase(Widget): def _render(self, rect: rl.Rectangle): # compute total content height for scrolling content_height = self._measure_content_height(rect) - scroll_offset = self._scroll_panel.update(rect, content_height) + scroll_offset = round(self._scroll_panel.update(rect, content_height)) # start drawing with offset x = int(rect.x + 40) diff --git a/system/ui/lib/scroll_panel2.py b/system/ui/lib/scroll_panel2.py index 8677c32f8..ab93be945 100644 --- a/system/ui/lib/scroll_panel2.py +++ b/system/ui/lib/scroll_panel2.py @@ -8,7 +8,7 @@ from openpilot.system.ui.lib.application import gui_app, MouseEvent from openpilot.system.hardware import TICI from collections import deque -MIN_VELOCITY = 2 # px/s, changes from auto scroll to steady state +MIN_VELOCITY = 10 # px/s, changes from auto scroll to steady state MIN_VELOCITY_FOR_CLICKING = 2 * 60 # px/s, accepts clicks while auto scrolling below this velocity MIN_DRAG_PIXELS = 12 AUTO_SCROLL_TC_SNAP = 0.025 @@ -202,8 +202,8 @@ class GuiScrollPanel2: def _get_mouse_pos(self, mouse_event: MouseEvent) -> float: return mouse_event.pos.x if self._horizontal else mouse_event.pos.y - def get_offset(self) -> int: - return round(self._offset.x if self._horizontal else self._offset.y) + def get_offset(self) -> float: + return self._offset.x if self._horizontal else self._offset.y def set_offset(self, value: float) -> None: if self._horizontal: diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index d7395f9b7..9792c51e7 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -244,7 +244,7 @@ class TermsPage(Widget): pass def _render(self, _): - scroll_offset = self._scroll_panel.update(self._rect, self._content_height + self._continue_button.rect.height + 16) + scroll_offset = round(self._scroll_panel.update(self._rect, self._content_height + self._continue_button.rect.height + 16)) if scroll_offset <= self._scrolled_down_offset: # don't show back if not enabled diff --git a/system/ui/widgets/scroller.py b/system/ui/widgets/scroller.py index 3cb6a1e18..72f76d90c 100644 --- a/system/ui/widgets/scroller.py +++ b/system/ui/widgets/scroller.py @@ -124,7 +124,7 @@ class Scroller(Widget): self.scroll_panel.set_enabled(scroll_enabled and self.enabled) self.scroll_panel.update(self._rect, content_size) if not self._snap_items: - return self.scroll_panel.get_offset() + return round(self.scroll_panel.get_offset()) # Snap closest item to center center_pos = self._rect.x + self._rect.width / 2 if self._horizontal else self._rect.y + self._rect.height / 2 From ce596424cfb9b3b680e2255e701f0f32e2a65edd Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 27 Nov 2025 04:16:38 -0800 Subject: [PATCH 463/910] Fix steering arc artifacts (#36707) * fix arc artifacts * works but how * also this * Revert "also this" This reverts commit e8d5ed9af15568dcb178dd6da7a14d2c6191010e. * clean up * nl * clean up * more * print * print --- selfdrive/ui/mici/onroad/torque_bar.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/selfdrive/ui/mici/onroad/torque_bar.py b/selfdrive/ui/mici/onroad/torque_bar.py index 1f6dffe87..d7c9f27a9 100644 --- a/selfdrive/ui/mici/onroad/torque_bar.py +++ b/selfdrive/ui/mici/onroad/torque_bar.py @@ -130,6 +130,9 @@ def arc_bar_pts(cx: float, cy: float, pts = np.vstack((outer, cap_end, inner, cap_start, outer[:1])).astype(np.float32) + # Rotate to start from middle of cap for proper triangulation + pts = np.roll(pts, cap_segs, axis=0) + if DEBUG: n = len(pts) idx = int(time.monotonic() * 12) % max(1, n) # speed: 12 pts/sec From 10524353916f42908557a1711efd2a5b66169c7b Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Thu, 27 Nov 2025 21:58:53 +0800 Subject: [PATCH 464/910] ui: speed up `mici/AugmentedRoadView` by optimizing _calc_frame_matrix caching (#36669) speed up AugmentedRoadView by optimizing _calc_frame_matrix caching --- .../ui/mici/onroad/augmented_road_view.py | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/selfdrive/ui/mici/onroad/augmented_road_view.py b/selfdrive/ui/mici/onroad/augmented_road_view.py index ab55f392f..f1f4e66f5 100644 --- a/selfdrive/ui/mici/onroad/augmented_road_view.py +++ b/selfdrive/ui/mici/onroad/augmented_road_view.py @@ -138,9 +138,7 @@ class AugmentedRoadView(CameraView): self.view_from_calib = view_frame_from_device_frame.copy() self.view_from_wide_calib = view_frame_from_device_frame.copy() - self._last_calib_time: float = 0 - self._last_rect_dims = (0.0, 0.0) - self._last_stream_type = stream_type + self._matrix_cache_key = (0, 0, 0, 0, stream_type) self._cached_matrix: np.ndarray | None = None self._content_rect = rl.Rectangle() self._last_click_time = 0.0 @@ -284,10 +282,19 @@ class AugmentedRoadView(CameraView): self.view_from_wide_calib = view_frame_from_device_frame @ wide_from_device @ device_from_calib def _calc_frame_matrix(self, rect: rl.Rectangle) -> np.ndarray: + v_ego_quantized = round(ui_state.sm['carState'].vEgo, 1) + cache_key = ( + ui_state.sm.recv_frame['liveCalibration'], + int(self._content_rect.width), + int(self._content_rect.height), + self.stream_type, + v_ego_quantized + ) + + if cache_key == self._matrix_cache_key and self._cached_matrix is not None: + return self._cached_matrix + # Get camera configuration - # TODO: cache with vEgo? - calib_time = ui_state.sm.recv_frame['liveCalibration'] - current_dims = (self._content_rect.width, self._content_rect.height) device_camera = self.device_camera or DEFAULT_DEVICE_CAMERA is_wide_camera = self.stream_type == WIDE_CAM intrinsic = device_camera.ecam.intrinsics if is_wide_camera else device_camera.fcam.intrinsics @@ -323,9 +330,7 @@ class AugmentedRoadView(CameraView): x_offset, y_offset = 0, 0 # Cache the computed transformation matrix to avoid recalculations - self._last_calib_time = calib_time - self._last_rect_dims = current_dims - self._last_stream_type = self.stream_type + self._matrix_cache_key = cache_key self._cached_matrix = np.array([ [zoom * 2 * cx / w, 0, -x_offset / w * 2], [0, zoom * 2 * cy / h, -y_offset / h * 2], From 5499fb26befbc7ee20ae79309231ac8e41f5dc99 Mon Sep 17 00:00:00 2001 From: infiniteCable2 Date: Thu, 27 Nov 2025 18:38:26 +0100 Subject: [PATCH 465/910] Delete settings.cc --- selfdrive/ui/qt/offroad/settings.cc | 713 ---------------------------- 1 file changed, 713 deletions(-) delete mode 100644 selfdrive/ui/qt/offroad/settings.cc diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc deleted file mode 100644 index 930d1d815..000000000 --- a/selfdrive/ui/qt/offroad/settings.cc +++ /dev/null @@ -1,713 +0,0 @@ -#include -#include -#include -#include -#include - -#include - -#include "common/watchdog.h" -#include "common/util.h" -#include "selfdrive/ui/qt/network/networking.h" -#include "selfdrive/ui/qt/offroad/settings.h" -#include "selfdrive/ui/qt/qt_window.h" -#include "selfdrive/ui/qt/widgets/prime.h" -#include "selfdrive/ui/qt/widgets/scrollview.h" -#include "selfdrive/ui/qt/offroad/developer_panel.h" -#include "selfdrive/ui/qt/offroad/firehose.h" -#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h" - -InfiniteCableTogglesPanel::InfiniteCableTogglesPanel(SettingsWindow *parent) : ListWidget(parent) { - std::vector> toggle_defs{ - { - "EnableCurvatureController", - tr("VW: Lateral Correction (Recommended)"), - tr("Enables curvature PID post-processing additionally to QFK curvature offset
"), - "../assets/icons/chffr_wheel.png", - false, - }, - { - "EnableLongComfortMode", - tr("VW: Longitudinal Comfort Mode"), - tr("Enables longitudinal jerk and accel deviation limit control for safe and comfortable driving
"), - "../assets/icons/chffr_wheel.png", - false, - }, - { - "EnableSpeedLimitControl", - tr("VW: Speed Limit Control"), - tr("Enables setting maximum speed by speed limit detection
"), - "../assets/icons/speed_limit.png", - false, - }, - { - "EnableSpeedLimitPredicative", - tr("VW: Predicative Speed Limit (pACC)"), - tr("Enables setting predicative speed limit
"), - "../assets/icons/speed_limit.png", - false, - }, - { - "EnableSLPredReactToSL", - tr("VW: Predicative - Reaction to Speed Limits"), - tr("Enables reaction to speed limits as predicative speed limit
"), - "../assets/icons/speed_limit.png", - false, - }, - { - "EnableSLPredReactToCurves", - tr("VW: Predicative - Reaction to Curves"), - tr("Enables reaction to curves as predicative speed limit (Max Speed as per lateral ISO limits)
"), - "../assets/icons/speed_limit.png", - false, - }, - { - "BatteryDetails", - tr("VW MEB: Display Battery Details"), - tr("Display battery detail panel"), - "../assets/icons/capslock-fill.png", - false, - }, - { - "ForceRHDForBSM", - tr("VW: Force RHD for BSM"), - tr("Switch BSM detection side to RHD. Driver is on the right side."), - "../assets/icons/eye_closed.png", - false, - }, - { - "EnableSmoothSteer", - tr("Steer Smoothing"), - tr("Enables S-curving on lateral control for smoother steering
"), - "../assets/icons/chffr_wheel.png", - false, - }, - { - "DarkMode", - tr("Dark Mode"), - tr("Force brightness to a minimal value"), - "../assets/icons/eye_closed.png", - false, - }, - { - "DisableScreenTimer", - tr("Onroad Screen Timeout"), - tr("The onroad screen is turned of after 10 seconds. It will be temporarily enabled on alerts"), - "../assets/icons/eye_closed.png", - false, - }, - { - "EnableAngleOffset", - tr("Enable Steer Angle Offset (Testing)"), - tr("Enables the use of manual steer angle offset selection as start value for params detection."), - "../assets/icons/eye_closed.png", - false, - }, - }; - - for (auto &[param, title, desc, icon, needs_restart] : toggle_defs) { - auto toggle = new ParamControl(param, title, desc, icon, this); - - bool locked = params.getBool((param + "Lock").toStdString()); - toggle->setEnabled(!locked); - - if (needs_restart && !locked) { - toggle->setDescription(toggle->getDescription() + tr(" Changing this setting will restart openpilot if the car is powered on.")); - - QObject::connect(uiState(), &UIState::engagedChanged, [toggle](bool engaged) { - toggle->setEnabled(!engaged); - }); - - QObject::connect(toggle, &ParamControl::toggleFlipped, [=](bool state) { - params.putBool("OnroadCycleRequested", true); - }); - } - - addItem(toggle); - toggles[param.toStdString()] = toggle; - } - - steer_offset_control = new OptionControlSP( - "AngleOffsetDegree", - tr("Steering Angle Offset"), - tr("Adjust steering angle offset params learner start value manually (in degrees). Taking effect only for onroad transition. Is refreshed with the actual learned value and saved."), - "", {-1800, 1800}, 10, false, nullptr, true, true); - - float stored_val = QString::fromStdString(params.get("AngleOffsetDegree")).toFloat(); - steer_offset_control->setLabel(QString::number(stored_val, 'f', 1) + "°"); - steer_offset_control->showDescription(); - addItem(steer_offset_control); - - bool enable_angle_offset = params.getBool("EnableAngleOffset"); - steer_offset_control->setVisible(enable_angle_offset); - - connect(uiStateSP(), &UIStateSP::uiUpdate, this, [=]() { - bool enable_steer_offset_control = params.getBool("EnableAngleOffset"); - steer_offset_control->setVisible(enable_steer_offset_control); - }); - - QObject::connect(steer_offset_control, &OptionControlSP::updateLabels, [=]() { - float val = QString::fromStdString(params.get("AngleOffsetDegree")).toFloat(); - steer_offset_control->setLabel(QString::number(val, 'f', 1) + "°"); - }); -} - -void InfiniteCableTogglesPanel::showEvent(QShowEvent *event) { - ListWidget::showEvent(event); - if (steer_offset_control) { - float val = QString::fromStdString(params.get("AngleOffsetDegree")).toFloat(); - steer_offset_control->setLabel(QString::number(val, 'f', 1) + "°"); - } -} - -void InfiniteCableTogglesPanel::expandToggleDescription(const QString ¶m) { - toggles[param.toStdString()]->showDescription(); -} - -void InfiniteCableTogglesPanel::scrollToToggle(const QString ¶m) { - if (auto it = toggles.find(param.toStdString()); it != toggles.end()) { - auto scroll_area = qobject_cast(parent()->parent()); - if (scroll_area) { - scroll_area->ensureWidgetVisible(it->second); - } - } -} - -TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { - // param, title, desc, icon, restart needed - std::vector> toggle_defs{ - { - "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."), - "../assets/icons/chffr_wheel.png", - true, - }, - { - "ExperimentalMode", - tr("Experimental Mode"), - "", - "../assets/icons/experimental_white.svg", - false, - }, - { - "DisengageOnAccelerator", - tr("Disengage on Accelerator Pedal"), - tr("When enabled, pressing the accelerator pedal will disengage sunnypilot."), - "../assets/icons/disengage_on_accelerator.svg", - false, - }, - { - "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/icons/warning.png", - false, - }, - { - "AlwaysOnDM", - tr("Always-On Driver Monitoring"), - tr("Enable driver monitoring even when sunnypilot is not engaged."), - "../assets/icons/monitoring.png", - false, - }, - { - "RecordFront", - tr("Record and Upload Driver Camera"), - tr("Upload data from the driver facing camera and help improve the driver monitoring algorithm."), - "../assets/icons/monitoring.png", - true, - }, - { - "RecordAudio", - tr("Record and Upload Microphone Audio"), - tr("Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect."), - "../assets/icons/microphone.png", - true, - }, - { - "IsMetric", - tr("Use Metric System"), - tr("Display speed in km/h instead of mph."), - "../assets/icons/metric.png", - false, - }, - }; - - - std::vector longi_button_texts{tr("Aggressive"), tr("Standard"), tr("Relaxed")}; - long_personality_setting = new ButtonParamControl("LongitudinalPersonality", tr("Driving Personality"), - 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/icons/speed_limit.png", - longi_button_texts); - - // set up uiState update for personality setting - QObject::connect(uiState(), &UIState::uiUpdate, this, &TogglesPanel::updateState); - - for (auto &[param, title, desc, icon, needs_restart] : toggle_defs) { - auto toggle = new ParamControl(param, title, desc, icon, this); - - bool locked = params.getBool((param + "Lock").toStdString()); - toggle->setEnabled(!locked); - - if (needs_restart && !locked) { - toggle->setDescription(toggle->getDescription() + tr(" Changing this setting will restart openpilot if the car is powered on.")); - - QObject::connect(uiState(), &UIState::engagedChanged, [toggle](bool engaged) { - toggle->setEnabled(!engaged); - }); - - QObject::connect(toggle, &ParamControl::toggleFlipped, [=](bool state) { - params.putBool("OnroadCycleRequested", true); - }); - } - - addItem(toggle); - toggles[param.toStdString()] = toggle; - - // insert longitudinal personality after NDOG toggle - if (param == "DisengageOnAccelerator") { - addItem(long_personality_setting); - } - } - - // Toggles with confirmation dialogs -#ifndef SUNNYPILOT - toggles["ExperimentalMode"]->setActiveIcon("../assets/icons/experimental.svg"); -#endif - toggles["ExperimentalMode"]->setConfirmation(true, true); -} - -void TogglesPanel::updateState(const UIState &s) { - const SubMaster &sm = *(s.sm); - - if (sm.updated("selfdriveState")) { - auto personality = sm["selfdriveState"].getSelfdriveState().getPersonality(); - if (personality != s.scene.personality && s.scene.started && isVisible()) { - long_personality_setting->setCheckedButton(static_cast(personality)); - } - uiState()->scene.personality = personality; - } -} - -void TogglesPanel::expandToggleDescription(const QString ¶m) { - toggles[param.toStdString()]->showDescription(); -} - -void TogglesPanel::scrollToToggle(const QString ¶m) { - if (auto it = toggles.find(param.toStdString()); it != toggles.end()) { - auto scroll_area = qobject_cast(parent()->parent()); - if (scroll_area) { - scroll_area->ensureWidgetVisible(it->second); - } - } -} - -void TogglesPanel::showEvent(QShowEvent *event) { - updateToggles(); -} - -void TogglesPanel::updateToggles() { - auto experimental_mode_toggle = toggles["ExperimentalMode"]; - const QString e2e_description = QString("%1
" - "

%2


" - "%3
" - "

%4


" - "%5
") - .arg(tr("sunnypilot defaults to driving in chill mode. Experimental mode enables alpha-level features that aren't ready for chill mode. Experimental features are listed below:")) - .arg(tr("End-to-End Longitudinal Control")) - .arg(tr("Let the driving model control the gas and brakes. sunnypilot will drive as it thinks a human would, including stopping for red lights and stop signs. " - "Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; " - "mistakes should be expected.")) - .arg(tr("New Driving Visualization")) - .arg(tr("The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner.")); - - const bool is_release = params.getBool("IsReleaseBranch"); - auto cp_bytes = params.get("CarParamsPersistent"); - if (!cp_bytes.empty()) { - AlignedBuffer aligned_buf; - capnp::FlatArrayMessageReader cmsg(aligned_buf.align(cp_bytes.data(), cp_bytes.size())); - cereal::CarParams::Reader CP = cmsg.getRoot(); - - if (hasLongitudinalControl(CP)) { - // normal description and toggle - experimental_mode_toggle->setEnabled(true); - experimental_mode_toggle->setDescription(e2e_description); - long_personality_setting->setEnabled(true); - } else { - // no long for now - experimental_mode_toggle->setEnabled(false); - long_personality_setting->setEnabled(false); - params.remove("ExperimentalMode"); - - const QString unavailable = tr("Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control."); - - QString long_desc = unavailable + " " + \ - tr("sunnypilot longitudinal control may come in a future update."); - if (CP.getAlphaLongitudinalAvailable()) { - if (is_release) { - long_desc = unavailable + " " + tr("An alpha version of sunnypilot longitudinal control can be tested, along with Experimental mode, on non-release branches."); - } else { - long_desc = tr("Enable the sunnypilot longitudinal control (alpha) toggle to allow Experimental mode."); - } - } - experimental_mode_toggle->setDescription("" + long_desc + "

" + e2e_description); - } - - experimental_mode_toggle->refresh(); - } else { - experimental_mode_toggle->setDescription(e2e_description); - } -} - -DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) { - setSpacing(50); - addItem(new LabelControl(tr("Dongle ID"), getDongleId().value_or(tr("N/A")))); - addItem(new LabelControl(tr("Serial"), params.get("HardwareSerial").c_str())); - - pair_device = new ButtonControl(tr("Pair Device"), tr("PAIR"), - tr("Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer.")); - connect(pair_device, &ButtonControl::clicked, [=]() { - PairingPopup popup(this); - popup.exec(); - }); - addItem(pair_device); - - QObject::connect(uiState()->prime_state, &PrimeState::changed, [this] (PrimeState::Type type) { - pair_device->setVisible(type == PrimeState::PRIME_TYPE_UNPAIRED); - }); - -#ifndef SUNNYPILOT - // offroad-only buttons - - auto dcamBtn = new ButtonControl(tr("Driver Camera"), tr("PREVIEW"), - tr("Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)")); - connect(dcamBtn, &ButtonControl::clicked, [=]() { emit showDriverView(); }); - addItem(dcamBtn); -#endif - - resetCalibBtn = new ButtonControl(tr("Reset Calibration"), tr("RESET"), ""); - connect(resetCalibBtn, &ButtonControl::showDescriptionEvent, this, &DevicePanel::updateCalibDescription); - connect(resetCalibBtn, &ButtonControl::clicked, [&]() { - if (!uiState()->engaged()) { - if (ConfirmationDialog::confirm(tr("Are you sure you want to reset calibration?"), tr("Reset"), this)) { - // Check engaged again in case it changed while the dialog was open - if (!uiState()->engaged()) { - params.remove("CalibrationParams"); - params.remove("LiveTorqueParameters"); - params.remove("LiveParameters"); - params.remove("LiveParametersV2"); - params.remove("LiveDelay"); - params.putBool("OnroadCycleRequested", true); - updateCalibDescription(); - } - } - } else { - ConfirmationDialog::alert(tr("Disengage to Reset Calibration"), this); - } - }); - addItem(resetCalibBtn); - -#ifndef SUNNYPILOT - auto retrainingBtn = new ButtonControl(tr("Review Training Guide"), tr("REVIEW"), tr("Review the rules, features, and limitations of sunnypilot")); - connect(retrainingBtn, &ButtonControl::clicked, [=]() { - if (ConfirmationDialog::confirm(tr("Are you sure you want to review the training guide?"), tr("Review"), this)) { - emit reviewTrainingGuide(); - } - }); - addItem(retrainingBtn); - - if (Hardware::TICI()) { - auto regulatoryBtn = new ButtonControl(tr("Regulatory"), tr("VIEW"), ""); - connect(regulatoryBtn, &ButtonControl::clicked, [=]() { - const std::string txt = util::read_file("../assets/offroad/fcc.html"); - ConfirmationDialog::rich(QString::fromStdString(txt), this); - }); - addItem(regulatoryBtn); - } - - auto translateBtn = new ButtonControl(tr("Change Language"), tr("CHANGE"), ""); - connect(translateBtn, &ButtonControl::clicked, [=]() { - QMap langs = getSupportedLanguages(); - QString selection = MultiOptionDialog::getSelection(tr("Select a language"), langs.keys(), langs.key(uiState()->language), this); - if (!selection.isEmpty()) { - // put language setting, exit Qt UI, and trigger fast restart - params.put("LanguageSetting", langs[selection].toStdString()); - qApp->exit(18); - watchdog_kick(0); - } - }); - addItem(translateBtn); -#endif - - QObject::connect(uiState(), &UIState::offroadTransition, [=](bool offroad) { - for (auto btn : findChildren()) { - if (btn != pair_device && btn != resetCalibBtn) { - btn->setEnabled(offroad); - } - } - }); - -#ifndef SUNNYPILOT - // power buttons - QHBoxLayout *power_layout = new QHBoxLayout(); - power_layout->setSpacing(30); - - QPushButton *reboot_btn = new QPushButton(tr("Reboot")); - reboot_btn->setObjectName("reboot_btn"); - power_layout->addWidget(reboot_btn); - QObject::connect(reboot_btn, &QPushButton::clicked, this, &DevicePanel::reboot); - - QPushButton *poweroff_btn = new QPushButton(tr("Power Off")); - poweroff_btn->setObjectName("poweroff_btn"); - power_layout->addWidget(poweroff_btn); - QObject::connect(poweroff_btn, &QPushButton::clicked, this, &DevicePanel::poweroff); - - if (!Hardware::PC()) { - connect(uiState(), &UIState::offroadTransition, poweroff_btn, &QPushButton::setVisible); - } - - setStyleSheet(R"( - #reboot_btn { height: 120px; border-radius: 15px; background-color: #393939; } - #reboot_btn:pressed { background-color: #4a4a4a; } - #poweroff_btn { height: 120px; border-radius: 15px; background-color: #E22C2C; } - #poweroff_btn:pressed { background-color: #FF2424; } - )"); - addItem(power_layout); -#endif -} - -void DevicePanel::updateCalibDescription() { - QString desc = tr("sunnypilot requires the device to be mounted within 4° left or right and within 5° up or 9° down."); - std::string calib_bytes = params.get("CalibrationParams"); - if (!calib_bytes.empty()) { - try { - AlignedBuffer aligned_buf; - capnp::FlatArrayMessageReader cmsg(aligned_buf.align(calib_bytes.data(), calib_bytes.size())); - auto calib = cmsg.getRoot().getLiveCalibration(); - if (calib.getCalStatus() != cereal::LiveCalibrationData::Status::UNCALIBRATED) { - double pitch = calib.getRpyCalib()[1] * (180 / M_PI); - double yaw = calib.getRpyCalib()[2] * (180 / M_PI); - desc += tr(" Your device is pointed %1° %2 and %3° %4.") - .arg(QString::number(std::abs(pitch), 'g', 1), pitch > 0 ? tr("down") : tr("up"), - QString::number(std::abs(yaw), 'g', 1), yaw > 0 ? tr("left") : tr("right")); - } - } catch (kj::Exception) { - qInfo() << "invalid CalibrationParams"; - } - } - - int lag_perc = 0; - std::string lag_bytes = params.get("LiveDelay"); - if (!lag_bytes.empty()) { - try { - AlignedBuffer aligned_buf; - capnp::FlatArrayMessageReader cmsg(aligned_buf.align(lag_bytes.data(), lag_bytes.size())); - lag_perc = cmsg.getRoot().getLiveDelay().getCalPerc(); - } catch (kj::Exception) { - qInfo() << "invalid LiveDelay"; - } - } - if (lag_perc < 100) { - desc += tr("\n\nSteering lag calibration is %1% complete.").arg(lag_perc); - } else { - desc += tr("\n\nSteering lag calibration is complete."); - } - - std::string torque_bytes = params.get("LiveTorqueParameters"); - if (!torque_bytes.empty()) { - try { - AlignedBuffer aligned_buf; - capnp::FlatArrayMessageReader cmsg(aligned_buf.align(torque_bytes.data(), torque_bytes.size())); - auto torque = cmsg.getRoot().getLiveTorqueParameters(); - // don't add for non-torque cars - if (torque.getUseParams()) { - int torque_perc = torque.getCalPerc(); - if (torque_perc < 100) { - desc += tr(" Steering torque response calibration is %1% complete.").arg(torque_perc); - } else { - desc += tr(" Steering torque response calibration is complete."); - } - } - } catch (kj::Exception) { - qInfo() << "invalid LiveTorqueParameters"; - } - } - - desc += "\n\n"; - desc += tr("openpilot is continuously calibrating, resetting is rarely required. " - "Resetting calibration will restart openpilot if the car is powered on."); - resetCalibBtn->setDescription(desc); -} - -void DevicePanel::reboot() { - if (!uiState()->engaged()) { - if (ConfirmationDialog::confirm(tr("Are you sure you want to reboot?"), tr("Reboot"), this)) { - // Check engaged again in case it changed while the dialog was open - if (!uiState()->engaged()) { - params.putBool("DoReboot", true); - } - } - } else { - ConfirmationDialog::alert(tr("Disengage to Reboot"), this); - } -} - -void DevicePanel::poweroff() { - if (!uiState()->engaged()) { - if (ConfirmationDialog::confirm(tr("Are you sure you want to power off?"), tr("Power Off"), this)) { - // Check engaged again in case it changed while the dialog was open - if (!uiState()->engaged()) { - params.putBool("DoShutdown", true); - } - } - } else { - ConfirmationDialog::alert(tr("Disengage to Power Off"), this); - } -} - -void SettingsWindow::showEvent(QShowEvent *event) { - setCurrentPanel(0); -} - -void SettingsWindow::setCurrentPanel(int index, const QString ¶m) { - if (!param.isEmpty()) { - // Check if param ends with "Panel" to determine if it's a panel name - 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; -#ifdef SUNNYPILOT - panel_trimmed = nav_btns->buttons()[i]->text().trimmed() == tr(panelName.toStdString().c_str()); -#endif - if ((nav_btns->buttons()[i]->text() == tr(panelName.toStdString().c_str())) || panel_trimmed) { - index = i; - break; - } - } - } else { - emit expandToggleDescription(param); - emit scrollToToggle(param); - } - } - - panel_widget->setCurrentIndex(index); - nav_btns->buttons()[index]->setChecked(true); -} - -SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) { -#ifndef SUNNYPILOT - // setup two main layouts - sidebar_widget = new QWidget; - QVBoxLayout *sidebar_layout = new QVBoxLayout(sidebar_widget); - panel_widget = new QStackedWidget(); - - // close button - QPushButton *close_btn = new QPushButton(tr("×")); - close_btn->setStyleSheet(R"( - QPushButton { - font-size: 140px; - padding-bottom: 20px; - border-radius: 100px; - background-color: #292929; - font-weight: 400; - } - QPushButton:pressed { - background-color: #3B3B3B; - } - )"); - close_btn->setFixedSize(200, 200); - sidebar_layout->addSpacing(45); - sidebar_layout->addWidget(close_btn, 0, Qt::AlignCenter); - QObject::connect(close_btn, &QPushButton::clicked, this, &SettingsWindow::closeSettings); - - // setup panels - DevicePanel *device = new DevicePanel(this); - QObject::connect(device, &DevicePanel::reviewTrainingGuide, this, &SettingsWindow::reviewTrainingGuide); - QObject::connect(device, &DevicePanel::showDriverView, this, &SettingsWindow::showDriverView); - - InfiniteCableTogglesPanel *ictoggles = new InfiniteCableTogglesPanel(this); - QObject::connect(this, &SettingsWindow::expandToggleDescription, ictoggles, &InfiniteCableTogglesPanel::expandToggleDescription); - QObject::connect(this, &SettingsWindow::scrollToToggle, ictoggles, &InfiniteCableTogglesPanel::scrollToToggle); - - TogglesPanel *toggles = new TogglesPanel(this); - QObject::connect(this, &SettingsWindow::expandToggleDescription, toggles, &TogglesPanel::expandToggleDescription); - QObject::connect(this, &SettingsWindow::scrollToToggle, toggles, &TogglesPanel::scrollToToggle); - - auto networking = new Networking(this); - QObject::connect(uiState()->prime_state, &PrimeState::changed, networking, &Networking::setPrimeType); - - QList> panels = { - {tr("Device"), device}, - {tr("Network"), networking}, - {tr("infiniteCableCustom"), ictoggles}, - {tr("Toggles"), toggles}, - {tr("Software"), new SoftwarePanel(this)}, - {tr("Firehose"), new FirehosePanel(this)}, - {tr("Developer"), new DeveloperPanel(this)}, - }; - - nav_btns = new QButtonGroup(this); - for (auto &[name, panel] : panels) { - QPushButton *btn = new QPushButton(name); - btn->setCheckable(true); - btn->setChecked(nav_btns->buttons().size() == 0); - btn->setStyleSheet(R"( - QPushButton { - color: grey; - border: none; - background: none; - font-size: 65px; - font-weight: 500; - } - QPushButton:checked { - color: white; - } - QPushButton:pressed { - color: #ADADAD; - } - )"); - btn->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); - nav_btns->addButton(btn); - sidebar_layout->addWidget(btn, 0, Qt::AlignRight); - - const int lr_margin = name != tr("Network") ? 50 : 0; // Network panel handles its own margins - panel->setContentsMargins(lr_margin, 25, lr_margin, 25); - - ScrollView *panel_frame = new ScrollView(panel, this); - panel_widget->addWidget(panel_frame); - - QObject::connect(btn, &QPushButton::clicked, [=, w = panel_frame]() { - btn->setChecked(true); - panel_widget->setCurrentWidget(w); - }); - } - sidebar_layout->setContentsMargins(50, 50, 100, 50); - - // main settings layout, sidebar + main panel - QHBoxLayout *main_layout = new QHBoxLayout(this); - - sidebar_widget->setFixedWidth(500); - main_layout->addWidget(sidebar_widget); - main_layout->addWidget(panel_widget); - - setStyleSheet(R"( - * { - color: white; - font-size: 50px; - } - SettingsWindow { - background-color: black; - } - QStackedWidget, ScrollView { - background-color: #292929; - border-radius: 30px; - } - )"); -#endif -} From 4914445415f1b5ae5adc61ff672323a05ec3f8e5 Mon Sep 17 00:00:00 2001 From: Nayan Date: Fri, 28 Nov 2025 01:02:31 -0500 Subject: [PATCH 466/910] ui: sunnypilot MultiButtonControl (#1478) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * commaai/openpilot:d05cb31e2e916fba41ba8167030945f427fd811b * bump opendbc * bump opendbc * bump opendbc * bump opendbc * bump opendbc * sunnypilot: remove Qt * cabana: revert to stock Qt * commaai/openpilot:5198b1b079c37742c1050f02ce0aa6dd42b038b9 * commaai/openpilot:954b567b9ba0f3d1ae57d6aa7797fa86dd92ec6e * commaai/openpilot:7534b2a160faa683412c04c1254440e338931c5e * sum more * bump opendbc * not yet * should've been symlink'ed * raylib says wut * quiet mode back * more fixes * no more * too extra red diff on the side * need to bring this back * too extra * let's update docs here * Revert "let's update docs here" This reverts commit 51fe03cd5121e6fdf14657b2c33852c34922b851. * param to control stock vs sp ui * init styles * SP Toggles * Lint * optimizations * multi-button * Lint * param to control stock vs sp ui * init styles * SP Toggles * Lint * optimizations * Panels. With Icons. And Scroller. * patience, grasshopper * more patience, grasshopper * sp raylib preview * fix callback * fix ui preview * add ui previews * better padding * this * support for next line multi-button * uhh * disabled colors * listitem -> listitemsp * listitem -> listitemsp * add show_description method * remove padding from line separator. like, WHY? 😩😩 * scroller -> scroller_tici * scroller -> scroller_tici * ui: `GuiApplicationExt` * add to readme * use gui_app.sunnypilot_ui() * use gui_app.sunnypilot_ui() * use in toggles panel * ugh. no * better & animated * lint * cleanup * lint. LINT * slight * default no inline ty <3 * style change * move stuff around --------- Co-authored-by: Jason Wen --- selfdrive/ui/layouts/settings/toggles.py | 3 +- system/ui/sunnypilot/lib/styles.py | 9 ++ system/ui/sunnypilot/widgets/list_view.py | 108 +++++++++++++++++++--- system/ui/widgets/network.py | 2 + 4 files changed, 108 insertions(+), 14 deletions(-) diff --git a/selfdrive/ui/layouts/settings/toggles.py b/selfdrive/ui/layouts/settings/toggles.py index cd233aa3a..f5f3a4e9c 100644 --- a/selfdrive/ui/layouts/settings/toggles.py +++ b/selfdrive/ui/layouts/settings/toggles.py @@ -11,6 +11,7 @@ from openpilot.selfdrive.ui.ui_state import ui_state if gui_app.sunnypilot_ui(): from openpilot.system.ui.sunnypilot.widgets.list_view import toggle_item_sp as toggle_item + from openpilot.system.ui.sunnypilot.widgets.list_view import multiple_button_item_sp as multiple_button_item PERSONALITY_TO_INT = log.LongitudinalPersonality.schema.enumerants @@ -99,7 +100,7 @@ class TogglesLayout(Widget): lambda: tr("Driving Personality"), lambda: tr(DESCRIPTIONS["LongitudinalPersonality"]), buttons=[lambda: tr("Aggressive"), lambda: tr("Standard"), lambda: tr("Relaxed")], - button_width=255, + button_width=300, callback=self._set_longitudinal_personality, selected_index=self._params.get("LongitudinalPersonality", return_default=True), icon="speed_limit.png" diff --git a/system/ui/sunnypilot/lib/styles.py b/system/ui/sunnypilot/lib/styles.py index 4880ad58d..b942f79d3 100644 --- a/system/ui/sunnypilot/lib/styles.py +++ b/system/ui/sunnypilot/lib/styles.py @@ -24,6 +24,10 @@ class Base: TOGGLE_WIDTH = int(TOGGLE_HEIGHT * 1.75) TOGGLE_BG_HEIGHT = TOGGLE_HEIGHT - 20 + # Button Control + BUTTON_WIDTH = 300 + BUTTON_HEIGHT = 120 + @dataclass class DefaultStyleSP(Base): @@ -47,5 +51,10 @@ class DefaultStyleSP(Base): TOGGLE_DISABLED_OFF_COLOR = DISABLED_OFF_BG_COLOR TOGGLE_DISABLED_KNOB_COLOR = rl.Color(88, 88, 88, 255) # Lighter Grey + # Multi Button Control + MBC_TRANSPARENT = rl.Color(255, 255, 255, 0) + MBC_BG_CHECKED_ENABLED = rl.Color(0x69, 0x68, 0x68, 0xFF) + MBC_DISABLED = rl.Color(0xFF, 0xFF, 0xFF, 0x33) + style = DefaultStyleSP diff --git a/system/ui/sunnypilot/widgets/list_view.py b/system/ui/sunnypilot/widgets/list_view.py index dcf8f6019..4ecb9a488 100644 --- a/system/ui/sunnypilot/widgets/list_view.py +++ b/system/ui/sunnypilot/widgets/list_view.py @@ -7,9 +7,11 @@ See the LICENSE.md file in the root directory for more details. from collections.abc import Callable import pyray as rl +from openpilot.common.params import Params +from openpilot.system.ui.lib.application import MousePos from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.sunnypilot.widgets.toggle import ToggleSP -from openpilot.system.ui.widgets.list_view import ListItem, ToggleAction, ItemAction +from openpilot.system.ui.widgets.list_view import ListItem, ToggleAction, ItemAction, MultipleButtonAction, _resolve_value from openpilot.system.ui.sunnypilot.lib.styles import style @@ -20,11 +22,74 @@ class ToggleActionSP(ToggleAction): self.toggle = ToggleSP(initial_state=initial_state, callback=callback, param=param) +class MultipleButtonActionSP(MultipleButtonAction): + def __init__(self, buttons: list[str | Callable[[], str]], button_width: int, selected_index: int = 0, callback: Callable = None, + param: str | None = None): + MultipleButtonAction.__init__(self, buttons, button_width, selected_index, callback) + self.param_key = param + self.params = Params() + if self.param_key: + self.selected_button = int(self.params.get(self.param_key, return_default=True)) + self._anim_x: float | None = None + + def _render(self, rect: rl.Rectangle): + + button_y = rect.y + (rect.height - style.BUTTON_HEIGHT) / 2 + + total_width = len(self.buttons) * self.button_width + track_rect = rl.Rectangle(rect.x, button_y, total_width, style.BUTTON_HEIGHT) + + bg_color = style.MBC_TRANSPARENT + text_color = style.ITEM_TEXT_COLOR if self.enabled else style.MBC_DISABLED + highlight_color = style.MBC_BG_CHECKED_ENABLED if self.enabled else style.MBC_DISABLED + + # background + rl.draw_rectangle_rounded(track_rect, 0.2, 20, bg_color) + + # border + border_color = style.MBC_BG_CHECKED_ENABLED if self.enabled else style.MBC_DISABLED + rl.draw_rectangle_rounded_lines_ex(track_rect, 0.2, 20, 2, border_color) + + # highlight with animation + target_x = rect.x + self.selected_button * self.button_width + if not self._anim_x: + self._anim_x = target_x + self._anim_x += (target_x - self._anim_x) * 0.2 + + highlight_rect = rl.Rectangle(self._anim_x, button_y, self.button_width, style.BUTTON_HEIGHT) + rl.draw_rectangle_rounded(highlight_rect, 0.2, 20, highlight_color) + + # text + for i, _text in enumerate(self.buttons): + button_x = rect.x + i * self.button_width + + text = _resolve_value(_text, "") + text_size = measure_text_cached(self._font, text, 40) + text_x = button_x + (self.button_width - text_size.x) / 2 + text_y = button_y + (style.BUTTON_HEIGHT - text_size.y) / 2 + + rl.draw_text_ex(self._font, text, rl.Vector2(text_x, text_y), 40, 0, text_color) + + def _handle_mouse_release(self, mouse_pos: MousePos): + MultipleButtonAction._handle_mouse_release(self, mouse_pos) + if self.param_key: + self.params.put(self.param_key, self.selected_button) + + class ListItemSP(ListItem): def __init__(self, title: str | Callable[[], str] = "", icon: str | None = None, description: str | Callable[[], str] | None = None, description_visible: bool = False, callback: Callable | None = None, - action_item: ItemAction | None = None): + action_item: ItemAction | None = None, inline: bool = True): ListItem.__init__(self, title, icon, description, description_visible, callback, action_item) + self.inline = inline + if not self.inline: + self._rect.height += style.ITEM_BASE_HEIGHT/1.75 + + def get_item_height(self, font: rl.Font, max_width: int) -> float: + height = super().get_item_height(font, max_width) + if not self.inline: + height = height + style.ITEM_BASE_HEIGHT/1.75 + return height def show_description(self, show: bool): self._set_description_visible(show) @@ -33,6 +98,10 @@ class ListItemSP(ListItem): if not self.action_item: return rl.Rectangle(0, 0, 0, 0) + if not self.inline: + action_y = item_rect.y + self._text_size.y + style.ITEM_PADDING * 3 + return rl.Rectangle(item_rect.x + style.ITEM_PADDING, action_y, item_rect.width - (style.ITEM_PADDING * 2), style.BUTTON_HEIGHT) + right_width = self.action_item.rect.width if right_width == 0: # Full width action (like DualButtonAction) return rl.Rectangle(item_rect.x + style.ITEM_PADDING, item_rect.y, @@ -47,6 +116,13 @@ class ListItemSP(ListItem): return rl.Rectangle(action_x, action_y, action_width, style.ITEM_BASE_HEIGHT) def _render(self, _): + if not self.is_visible: + return + + # Don't draw items that are not in parent's viewport + if (self._rect.y + self.rect.height) <= self._parent_rect.y or self._rect.y >= (self._parent_rect.y + self._parent_rect.height): + return + content_x = self._rect.x + style.ITEM_PADDING text_x = content_x left_action_item = isinstance(self.action_item, ToggleAction) @@ -62,8 +138,8 @@ class ListItemSP(ListItem): # Draw title if self.title: - text_size = measure_text_cached(self._font, self.title, style.ITEM_TEXT_FONT_SIZE) - item_y = self._rect.y + (style.ITEM_BASE_HEIGHT - text_size.y) // 2 + self._text_size = measure_text_cached(self._font, self.title, style.ITEM_TEXT_FONT_SIZE) + item_y = self._rect.y + (style.ITEM_BASE_HEIGHT - self._text_size.y) // 2 rl.draw_text_ex(self._font, self.title, rl.Vector2(text_x, item_y), style.ITEM_TEXT_FONT_SIZE, 0, style.ITEM_TEXT_COLOR) # Render toggle and handle callback @@ -74,14 +150,13 @@ class ListItemSP(ListItem): else: if self.title: # Draw main text - text_size = measure_text_cached(self._font, self.title, style.ITEM_TEXT_FONT_SIZE) - item_y = self._rect.y + (style.ITEM_BASE_HEIGHT - text_size.y) // 2 + self._text_size = measure_text_cached(self._font, self.title, style.ITEM_TEXT_FONT_SIZE) + item_y = self._rect.y + (style.ITEM_BASE_HEIGHT - self._text_size.y) // 2 if self.inline else self._rect.y + style.ITEM_PADDING * 1.5 rl.draw_text_ex(self._font, self.title, rl.Vector2(text_x, item_y), style.ITEM_TEXT_FONT_SIZE, 0, style.ITEM_TEXT_COLOR) # Draw right item if present if self.action_item: right_rect = self.get_right_item_rect(self._rect) - right_rect.y = self._rect.y if self.action_item.render(right_rect) and self.action_item.enabled: # Right item was clicked/activated if self.callback: @@ -91,12 +166,12 @@ class ListItemSP(ListItem): if self.description_visible: content_width = int(self._rect.width - style.ITEM_PADDING * 2) description_height = self._html_renderer.get_total_height(content_width) - description_rect = rl.Rectangle( - self._rect.x + style.ITEM_PADDING, - self._rect.y + style.ITEM_DESC_V_OFFSET, - content_width, - description_height - ) + + desc_y = self._rect.y + style.ITEM_DESC_V_OFFSET + if not self.inline and self.action_item: + desc_y = self.action_item.rect.y + style.ITEM_DESC_V_OFFSET - style.ITEM_PADDING * 1.75 + + description_rect = rl.Rectangle(self._rect.x + style.ITEM_PADDING, desc_y, content_width, description_height) self._html_renderer.render(description_rect) @@ -104,3 +179,10 @@ def toggle_item_sp(title: str | Callable[[], str], description: str | Callable[[ callback: Callable | None = None, icon: str = "", enabled: bool | Callable[[], bool] = True, param: str | None = None) -> ListItemSP: action = ToggleActionSP(initial_state=initial_state, enabled=enabled, callback=callback, param=param) return ListItemSP(title=title, description=description, action_item=action, icon=icon, callback=callback) + + +def multiple_button_item_sp(title: str | Callable[[], str], description: str | Callable[[], str], buttons: list[str | Callable[[], str]], + selected_index: int = 0, button_width: int = style.BUTTON_WIDTH, callback: Callable = None, + icon: str = "", param: str | None = None, inline: bool = False) -> ListItemSP: + action = MultipleButtonActionSP(buttons, button_width, selected_index, callback=callback, param=param) + return ListItemSP(title=title, description=description, icon=icon, action_item=action, inline=inline) diff --git a/system/ui/widgets/network.py b/system/ui/widgets/network.py index fa47d3553..71755cc28 100644 --- a/system/ui/widgets/network.py +++ b/system/ui/widgets/network.py @@ -16,7 +16,9 @@ from openpilot.system.ui.widgets.scroller_tici import Scroller from openpilot.system.ui.widgets.list_view import ButtonAction, ListItem, MultipleButtonAction, ToggleAction, button_item, text_item if gui_app.sunnypilot_ui(): + from openpilot.system.ui.sunnypilot.widgets.list_view import ListItemSP as ListItem from openpilot.system.ui.sunnypilot.widgets.list_view import ToggleActionSP as ToggleAction + from openpilot.system.ui.sunnypilot.widgets.list_view import MultipleButtonActionSP as MultipleButtonAction # These are only used for AdvancedNetworkSettings, standalone apps just need WifiManagerUI try: From f07a40deb4f66c9bbd8299a7d7edefd1c4a2831d Mon Sep 17 00:00:00 2001 From: Jason Young <46612682+jyoung8607@users.noreply.github.com> Date: Fri, 28 Nov 2025 13:08:07 -0500 Subject: [PATCH 467/910] regen CARS.md (#36711) --- docs/CARS.md | 671 +++++++++++++++++++++++++-------------------------- 1 file changed, 335 insertions(+), 336 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index 3ea12f651..223f452e1 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -8,331 +8,331 @@ A supported vehicle is one that just works when you install a comma device. All |Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Hardware Needed
 |Video|Setup Video| |---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| -|Acura|ILX 2016-18|Technology Plus Package or AcuraWatch Plus|openpilot|26 mph|25 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Acura|ILX 2019|All|openpilot|26 mph|25 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Acura|MDX 2025|All except Type S|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch 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
||| -|Acura|RDX 2016-18|AcuraWatch Plus or Advance Package|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Acura|RDX 2019-21|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Acura|TLX 2021|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Audi|A3 2014-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Audi|A3 Sportback e-tron 2017-18|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Audi|Q2 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Audi|Q3 2019-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Audi|RS3 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Audi|S3 2015-17|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Chevrolet|Bolt EUV 2022-23|Premier or Premier Redline Trim without Super Cruise Package|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 comma 3X
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Chevrolet|Bolt EV 2022-23|2LT Trim with Adaptive Cruise Control Package|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 comma 3X
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Chevrolet|Equinox 2019-22|Adaptive Cruise Control (ACC)|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 comma 3X
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Chevrolet|Silverado 1500 2020-21|Safety Package II|openpilot available[1](#footnotes)|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 comma 3X
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Chevrolet|Trailblazer 2021-22|Adaptive Cruise Control (ACC)|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 comma 3X
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Chrysler|Pacifica 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Chrysler|Pacifica 2019-20|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Chrysler|Pacifica 2021-23|All|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Chrysler|Pacifica Hybrid 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Chrysler|Pacifica Hybrid 2019-25|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Acura|ILX 2016-18|Technology Plus Package or AcuraWatch Plus|openpilot|26 mph|25 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Acura|ILX 2019|All|openpilot|26 mph|25 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Acura|MDX 2025|All except Type S|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Acura|RDX 2016-18|AcuraWatch Plus or Advance Package|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Acura|RDX 2019-21|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Acura|TLX 2021|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Audi|A3 2014-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Audi|A3 Sportback e-tron 2017-18|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Audi|Q2 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Audi|Q3 2019-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Audi|RS3 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Audi|S3 2015-17|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Chevrolet|Bolt EUV 2022-23|Premier or Premier Redline Trim, without Super Cruise Package|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 harness box
- 1 mount
Buy Here
||| +|Chevrolet|Bolt EV 2022-23|2LT Trim with Adaptive Cruise Control Package|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 harness box
- 1 mount
Buy Here
||| +|Chevrolet|Equinox 2019-22|Adaptive Cruise Control (ACC)|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 harness box
- 1 mount
Buy Here
||| +|Chevrolet|Silverado 1500 2020-21|Safety Package II|openpilot available[1](#footnotes)|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 harness box
- 1 mount
Buy Here
||| +|Chevrolet|Trailblazer 2021-22|Adaptive Cruise Control (ACC)|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 harness box
- 1 mount
Buy Here
||| +|Chrysler|Pacifica 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Chrysler|Pacifica 2019-20|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Chrysler|Pacifica 2021-23|All|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Chrysler|Pacifica Hybrid 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Chrysler|Pacifica Hybrid 2019-25|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |comma|body|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|None||| -|CUPRA|Ateca 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Dodge|Durango 2020-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Ford|Bronco Sport 2021-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Ford|Escape 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Ford|Escape 2023-24|Co-Pilot360 Assist+|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Ford|Escape Hybrid 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Ford|Escape Hybrid 2023-24|Co-Pilot360 Assist+|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Ford|Escape Plug-in Hybrid 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Ford|Escape Plug-in Hybrid 2023-24|Co-Pilot360 Assist+|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Ford|Expedition 2022-24|Co-Pilot360 Assist 2.0|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Ford|Explorer 2020-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Ford|Explorer Hybrid 2020-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Ford|F-150 2021-23|Co-Pilot360 Assist 2.0|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Ford|F-150 Hybrid 2021-23|Co-Pilot360 Assist 2.0|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Ford|Focus 2018[3](#footnotes)|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Ford|Focus Hybrid 2018[3](#footnotes)|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Ford|Kuga 2020-23|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Ford|Kuga Hybrid 2020-23|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Ford|Kuga Hybrid 2024|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Ford|Kuga Plug-in Hybrid 2020-23|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Ford|Kuga Plug-in Hybrid 2024|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Ford|Maverick 2022|LARIAT Luxury|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Ford|Maverick 2023-24|Co-Pilot360 Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Ford|Maverick Hybrid 2022|LARIAT Luxury|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Ford|Maverick Hybrid 2023-24|Co-Pilot360 Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Ford|Mustang Mach-E 2021-24|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Ford|Ranger 2024|Adaptive Cruise Control with Lane Centering|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Genesis|G70 2018|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Genesis|G70 2019-21|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Genesis|G70 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Genesis|G80 2017|All|Stock|19 mph|37 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai J connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Genesis|G80 2018-19|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Genesis|G80 (2.5T Advanced Trim, with HDA II) 2024[6](#footnotes)|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai P connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Genesis|G90 2017-20|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Genesis|GV60 (Advanced Trim) 2023[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Genesis|GV60 (Performance Trim) 2022-23[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Genesis|GV70 (2.5T Trim, without HDA II) 2022-24[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Genesis|GV70 (3.5T Trim, without HDA II) 2022-23[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai M connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Genesis|GV70 Electrified (Australia Only) 2022[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai Q connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Genesis|GV70 Electrified (with HDA II) 2023-24[6](#footnotes)|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai Q connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Genesis|GV80 2023[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai M connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|GMC|Sierra 1500 2020-21|Driver Alert Package II|openpilot available[1](#footnotes)|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 comma 3X
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Honda|Accord 2018-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Honda|Accord 2023-25|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch 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
||| -|Honda|Accord Hybrid 2018-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Honda|Accord Hybrid 2023-25|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch 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
||| -|Honda|City (Brazil only) 2023|All|openpilot available[1](#footnotes)|0 mph|14 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Honda|Civic 2016-18|Honda Sensing|openpilot|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Honda|Civic 2019-21|All|openpilot available[1](#footnotes)|0 mph|2 mph[5](#footnotes)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Honda|Civic 2022-24|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Honda|Civic Hatchback 2017-18|Honda Sensing|openpilot available[1](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Honda|Civic Hatchback 2019-21|All|openpilot available[1](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Honda|Civic Hatchback 2022-24|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Honda|Civic Hatchback Hybrid 2025-26|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Honda|Civic Hatchback Hybrid (Europe only) 2023|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Honda|Civic Hybrid 2025|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Honda|CR-V 2015-16|Touring Trim|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Honda|CR-V 2017-22|Honda Sensing|openpilot available[1](#footnotes)|0 mph|15 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Honda|CR-V 2023-25|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch 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
||| -|Honda|CR-V Hybrid 2017-22|Honda Sensing|openpilot available[1](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Honda|CR-V Hybrid 2023-25|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch 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
||| -|Honda|e 2020|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Honda|Fit 2018-20|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Honda|Freed 2020|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Honda|HR-V 2019-22|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Honda|HR-V 2023-25|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Honda|Insight 2019-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Honda|Inspire 2018|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Honda|N-Box 2018|All|openpilot available[1](#footnotes)|0 mph|11 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Honda|Odyssey 2018-20|Honda Sensing|openpilot|26 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Honda|Odyssey 2021-25|All|openpilot available[1](#footnotes)|0 mph|43 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Honda|Passport 2019-25|All|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Honda|Passport 2026|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch 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
||| -|Honda|Pilot 2016-22|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Honda|Pilot 2023-25|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch C 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
||| -|Honda|Ridgeline 2017-25|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Azera 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Azera Hybrid 2019|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Azera Hybrid 2020|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Custin 2023|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Elantra 2017-18|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai B connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Elantra 2019|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai G connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Elantra 2021-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Elantra GT 2017-20|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Elantra Hybrid 2021-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Genesis 2015-16|Smart Cruise Control (SCC)|Stock|19 mph|37 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai J connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|i30 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Ioniq 5 (Southeast Asia and Europe only) 2022-24[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai Q connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Ioniq 5 (with HDA II) 2022-24[6](#footnotes)|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai Q connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Ioniq 5 (without HDA II) 2022-24[6](#footnotes)|Highway Driving Assist|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Ioniq 6 (with HDA II) 2023-24[6](#footnotes)|Highway Driving Assist II|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai P connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Ioniq Electric 2019|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Ioniq Electric 2020|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Ioniq Hybrid 2017-19|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Ioniq Hybrid 2020-22|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Ioniq Plug-in Hybrid 2019|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Ioniq Plug-in Hybrid 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Kona 2020|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|6 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai B connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Kona Electric 2018-21|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai G connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Kona Electric 2022-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai O connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Kona Electric (with HDA II, Korea only) 2023[6](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai R connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Kona Hybrid 2020|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai I connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Nexo 2021|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Palisade 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Santa Cruz 2022-24[6](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Santa Fe 2019-20|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai D connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Santa Fe 2021-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Santa Fe Hybrid 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Santa Fe Plug-in Hybrid 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Sonata 2018-19|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Sonata 2020-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Sonata Hybrid 2020-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Staria 2023[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Tucson 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai L connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Tucson 2022[6](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Tucson 2023-24[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Tucson Diesel 2019|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Tucson Hybrid 2022-24[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Tucson Plug-in Hybrid 2024[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Hyundai|Veloster 2019-20|Smart Cruise Control (SCC)|Stock|5 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai E connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Jeep|Grand Cherokee 2016-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Jeep|Grand Cherokee 2019-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Carnival 2022-24[6](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Carnival (China only) 2023[6](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Ceed 2019-21|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|EV6 (Southeast Asia only) 2022-24[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai P connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|EV6 (with HDA II) 2022-24[6](#footnotes)|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai P connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|EV6 (without HDA II) 2022-24[6](#footnotes)|Highway Driving Assist|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Forte 2019-21|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|6 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai G connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Forte 2022-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|K5 2021-24|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|K5 Hybrid 2020-22|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|K8 Hybrid (with HDA II) 2023[6](#footnotes)|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai Q connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Niro EV 2019|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Niro EV 2020|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Niro EV 2021|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Niro EV 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Niro EV (with HDA II) 2025[6](#footnotes)|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai R connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Niro EV (without HDA II) 2023-25[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Niro Hybrid 2018|Smart Cruise Control (SCC)|Stock|10 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai C connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Niro Hybrid 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai D connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Niro Hybrid 2022|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Niro Hybrid 2023[6](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Niro Plug-in Hybrid 2018-19|All|Stock|10 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai C connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Niro Plug-in Hybrid 2020|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai D connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Niro Plug-in Hybrid 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai D connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Niro Plug-in Hybrid 2022|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Optima 2017|Advanced Smart Cruise Control|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai B connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Optima 2019-20|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai G connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Optima Hybrid 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Seltos 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Sorento 2018|Advanced Smart Cruise Control & LKAS|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Sorento 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Sorento 2021-23[6](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Sorento Hybrid 2021-23[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Sorento Plug-in Hybrid 2022-23[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Sportage 2023-24[6](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Sportage Hybrid 2023[6](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Stinger 2018-20|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Stinger 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Kia|Telluride 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 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
||| -|Lexus|CT Hybrid 2017-18|Lexus Safety System+|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Lexus|ES 2017-18|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Lexus|ES 2019-25|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Lexus|ES Hybrid 2017-18|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Lexus|ES Hybrid 2019-25|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Lexus|GS F 2016|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Lexus|IS 2017-19|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Lexus|IS 2022-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Lexus|LC 2024-25|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Lexus|NX 2018-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Lexus|NX 2020-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Lexus|NX Hybrid 2018-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Lexus|NX Hybrid 2020-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Lexus|RC 2018-20|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Lexus|RC 2023|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Lexus|RX 2016|Lexus Safety System+|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Lexus|RX 2017-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Lexus|RX 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Lexus|RX Hybrid 2016|Lexus Safety System+|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Lexus|RX Hybrid 2017-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Lexus|RX Hybrid 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Lexus|UX Hybrid 2019-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Lincoln|Aviator 2020-24|Co-Pilot360 Plus|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Lincoln|Aviator Plug-in Hybrid 2020-24|Co-Pilot360 Plus|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|MAN|eTGE 2020-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|MAN|TGE 2017-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Mazda|CX-5 2022-25|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Mazda connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Mazda|CX-9 2021-23|All|Stock|0 mph|28 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Mazda connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Nissan[7](#footnotes)|Altima 2019-20|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan B connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Nissan[7](#footnotes)|Leaf 2018-23|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Nissan[7](#footnotes)|Rogue 2018-20|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Nissan[7](#footnotes)|X-Trail 2017|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Ram|1500 2019-24|Adaptive Cruise Control (ACC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ram connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Rivian|R1S 2022-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Rivian A connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Rivian|R1T 2022-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Rivian A connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|SEAT|Ateca 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|SEAT|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Subaru|Ascent 2019-21|All[8](#footnotes)|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| -|Subaru|Crosstrek 2018-19|EyeSight Driver Assistance[8](#footnotes)|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| -|Subaru|Crosstrek 2020-23|EyeSight Driver Assistance[8](#footnotes)|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| -|Subaru|Forester 2019-21|All[8](#footnotes)|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| -|Subaru|Impreza 2017-19|EyeSight Driver Assistance[8](#footnotes)|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| -|Subaru|Impreza 2020-22|EyeSight Driver Assistance[8](#footnotes)|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| -|Subaru|Legacy 2020-22|All[8](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Subaru B connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| -|Subaru|Outback 2020-22|All[8](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Subaru B connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| -|Subaru|XV 2018-19|EyeSight Driver Assistance[8](#footnotes)|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| -|Subaru|XV 2020-21|EyeSight Driver Assistance[8](#footnotes)|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| -|Škoda|Fabia 2022-23[15](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[17](#footnotes)||| -|Škoda|Kamiq 2021-23[13,15](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[17](#footnotes)||| -|Škoda|Karoq 2019-23[15](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Škoda|Kodiaq 2017-23[15](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Škoda|Octavia 2015-19[15](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Škoda|Octavia RS 2016[15](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Škoda|Octavia Scout 2017-19[15](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Škoda|Scala 2020-23[15](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[17](#footnotes)||| -|Škoda|Superb 2015-22[15](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Tesla[11](#footnotes)|Model 3 (with HW3) 2019-23[10](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Tesla A connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Tesla[11](#footnotes)|Model 3 (with HW4) 2024-25[10](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Tesla B connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Tesla[11](#footnotes)|Model Y (with HW3) 2020-23[10](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Tesla A connector
- 1 USB-C coupler
- 1 comma 3X
- 1 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-25[10](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Tesla B connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Alphard 2019-20|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Alphard Hybrid 2021|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Avalon 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Avalon 2017-18|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Avalon 2019-21|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Avalon 2022|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Avalon Hybrid 2019-21|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Avalon Hybrid 2022|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|C-HR 2017-20|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|C-HR 2021|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|C-HR Hybrid 2017-20|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|C-HR Hybrid 2021-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Camry 2018-20|All|Stock|0 mph[12](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Camry 2021-24|All|openpilot|0 mph[12](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Camry Hybrid 2018-20|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Camry Hybrid 2021-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Corolla 2017-19|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Corolla 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Corolla Cross (Non-US only) 2020-23|All|openpilot|17 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Corolla Cross Hybrid (Non-US only) 2020-22|All|openpilot|17 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Corolla Hatchback 2019-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Corolla Hybrid 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Corolla Hybrid (South America only) 2020-23|All|openpilot|17 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Highlander 2017-19|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Highlander 2020-23|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Highlander Hybrid 2017-19|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Highlander Hybrid 2020-23|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Mirai 2021|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Prius 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Prius 2017-20|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Prius 2021-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Prius Prime 2017-20|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Prius Prime 2021-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Prius v 2017|Toyota Safety Sense P|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|RAV4 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|RAV4 2017-18|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|RAV4 2019-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|RAV4 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|RAV4 2023-25|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|RAV4 Hybrid 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|RAV4 Hybrid 2017-18|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|RAV4 Hybrid 2019-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|RAV4 Hybrid 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|RAV4 Hybrid 2023-25|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Toyota|Sienna 2018-20|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|Arteon 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|Arteon eHybrid 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|Arteon R 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|Arteon Shooting Brake 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|Atlas 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|Atlas Cross Sport 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|California 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|Caravelle 2020|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|CC 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|Crafter 2017-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|e-Crafter 2018-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|e-Golf 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|Golf 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|Golf Alltrack 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|Golf GTD 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|Golf GTE 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|Golf GTI 2015-21|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|Golf R 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|Golf SportsVan 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|Grand California 2019-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|Jetta 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|Jetta GLI 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|Passat 2015-22[14](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|Passat Alltrack 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|Passat GTE 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|Polo 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[17](#footnotes)||| -|Volkswagen|Polo GTI 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[17](#footnotes)||| -|Volkswagen|T-Cross 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[17](#footnotes)||| -|Volkswagen|T-Roc 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|Taos 2022-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|Teramont 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|Teramont Cross Sport 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|Teramont X 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|Tiguan 2018-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|Tiguan eHybrid 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| -|Volkswagen|Touran 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|CUPRA|Ateca 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Dodge|Durango 2020-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Ford|Bronco Sport 2021-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Ford|Escape 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Ford|Escape 2023-24|Co-Pilot360 Assist+|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Ford|Escape Hybrid 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Ford|Escape Hybrid 2023-24|Co-Pilot360 Assist+|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Ford|Escape Plug-in Hybrid 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Ford|Escape Plug-in Hybrid 2023-24|Co-Pilot360 Assist+|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Ford|Expedition 2022-24|Co-Pilot360 Assist 2.0|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Ford|Explorer 2020-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Ford|Explorer Hybrid 2020-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Ford|F-150 2021-23|Co-Pilot360 Assist 2.0|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Ford|F-150 Hybrid 2021-23|Co-Pilot360 Assist 2.0|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Ford|Focus 2018[3](#footnotes)|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Ford|Focus Hybrid 2018[3](#footnotes)|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Ford|Kuga 2020-23|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Ford|Kuga Hybrid 2020-23|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Ford|Kuga Hybrid 2024|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Ford|Kuga Plug-in Hybrid 2020-23|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Ford|Kuga Plug-in Hybrid 2024|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Ford|Maverick 2022|LARIAT Luxury|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Ford|Maverick 2023-24|Co-Pilot360 Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Ford|Maverick Hybrid 2022|LARIAT Luxury|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Ford|Maverick Hybrid 2023-24|Co-Pilot360 Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Ford|Mustang Mach-E 2021-24|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Ford|Ranger 2024|Adaptive Cruise Control with Lane Centering|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Genesis|G70 2018|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Genesis|G70 2019-21|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Genesis|G70 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Genesis|G80 2017|All|Stock|19 mph|37 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai J connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Genesis|G80 2018-19|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Genesis|G80 (2.5T Advanced Trim, with HDA II) 2024|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai P connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Genesis|G90 2017-20|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Genesis|GV60 (Advanced Trim) 2023|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Genesis|GV60 (Performance Trim) 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Genesis|GV70 (2.5T Trim, without HDA II) 2022-24|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Genesis|GV70 (3.5T Trim, without HDA II) 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai M connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Genesis|GV70 Electrified (Australia Only) 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai Q connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Genesis|GV70 Electrified (with HDA II) 2023-24|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai Q connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Genesis|GV80 2023|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai M connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|GMC|Sierra 1500 2020-21|Driver Alert Package II|openpilot available[1](#footnotes)|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Accord 2018-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Accord 2023-25|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Accord Hybrid 2018-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Accord Hybrid 2023-25|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|City (Brazil only) 2023|All|openpilot available[1](#footnotes)|0 mph|14 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Civic 2016-18|Honda Sensing|openpilot|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Civic 2019-21|All|openpilot available[1](#footnotes)|0 mph|2 mph[5](#footnotes)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Civic 2022-24|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Civic Hatchback 2017-18|Honda Sensing|openpilot available[1](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Civic Hatchback 2019-21|All|openpilot available[1](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Civic Hatchback 2022-24|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Civic Hatchback Hybrid 2025-26|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Civic Hatchback Hybrid (Europe only) 2023|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Civic Hybrid 2025-26|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|CR-V 2015-16|Touring Trim|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|CR-V 2017-22|Honda Sensing|openpilot available[1](#footnotes)|0 mph|15 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|CR-V 2023-26|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|CR-V Hybrid 2017-22|Honda Sensing|openpilot available[1](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|CR-V Hybrid 2023-25|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|e 2020|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Fit 2018-20|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Freed 2020|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|HR-V 2019-22|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|HR-V 2023-25|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Insight 2019-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Inspire 2018|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|N-Box 2018|All|openpilot available[1](#footnotes)|0 mph|11 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Odyssey 2018-20|Honda Sensing|openpilot|26 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Odyssey 2021-26|All|openpilot available[1](#footnotes)|0 mph|43 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Passport 2019-25|All|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Passport 2026|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Pilot 2016-22|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Pilot 2023-25|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Ridgeline 2017-25|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Azera 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Azera Hybrid 2019|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Azera Hybrid 2020|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Custin 2023|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Elantra 2017-18|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai B connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Elantra 2019|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai G connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Elantra 2021-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Elantra GT 2017-20|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Elantra Hybrid 2021-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Genesis 2015-16|Smart Cruise Control (SCC)|Stock|19 mph|37 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai J connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|i30 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Ioniq 5 (Southeast Asia and Europe only) 2022-24|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai Q connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Ioniq 5 (with HDA II) 2022-24|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai Q connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Ioniq 5 (without HDA II) 2022-24|Highway Driving Assist|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Ioniq 6 (with HDA II) 2023-24|Highway Driving Assist II|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai P connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Ioniq Electric 2019|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Ioniq Electric 2020|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Ioniq Hybrid 2017-19|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Ioniq Hybrid 2020-22|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Ioniq Plug-in Hybrid 2019|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Ioniq Plug-in Hybrid 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Kona 2020|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|6 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai B connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Kona Electric 2018-21|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai G connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Kona Electric 2022-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai O connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Kona Electric (with HDA II, Korea only) 2023|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai R connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Kona Hybrid 2020|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai I connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Nexo 2021|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Palisade 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Santa Cruz 2022-24|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Santa Fe 2019-20|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai D connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Santa Fe 2021-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Santa Fe Hybrid 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Santa Fe Plug-in Hybrid 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Sonata 2018-19|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Sonata 2020-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Sonata Hybrid 2020-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Staria 2023|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Tucson 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai L connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Tucson 2022|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Tucson 2023-24|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Tucson Diesel 2019|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Tucson Hybrid 2022-24|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Tucson Plug-in Hybrid 2024|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Veloster 2019-20|Smart Cruise Control (SCC)|Stock|5 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai E connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Jeep|Grand Cherokee 2016-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Jeep|Grand Cherokee 2019-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Carnival 2022-24|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Carnival (China only) 2023|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Ceed 2019-21|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|EV6 (Southeast Asia only) 2022-24|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai P connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|EV6 (with HDA II) 2022-24|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai P connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|EV6 (without HDA II) 2022-24|Highway Driving Assist|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Forte 2019-21|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|6 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai G connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Forte 2022-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|K5 2021-24|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|K5 Hybrid 2020-22|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|K8 Hybrid (with HDA II) 2023|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai Q connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Niro EV 2019|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Niro EV 2020|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Niro EV 2021|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Niro EV 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Niro EV (with HDA II) 2025|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai R connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Niro EV (without HDA II) 2023-25|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Niro Hybrid 2018|Smart Cruise Control (SCC)|Stock|10 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Niro Hybrid 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai D connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Niro Hybrid 2022|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Niro Hybrid 2023|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Niro Plug-in Hybrid 2018-19|All|Stock|10 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Niro Plug-in Hybrid 2020|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai D connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Niro Plug-in Hybrid 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai D connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Niro Plug-in Hybrid 2022|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Optima 2017|Advanced Smart Cruise Control|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai B connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Optima 2019-20|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai G connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Optima Hybrid 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Seltos 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Sorento 2018|Advanced Smart Cruise Control & LKAS|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Sorento 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Sorento 2021-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Sorento Hybrid 2021-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Sorento Plug-in Hybrid 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Sportage 2023-24|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Sportage Hybrid 2023|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Stinger 2018-20|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Stinger 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Telluride 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lexus|CT Hybrid 2017-18|Lexus Safety System+|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lexus|ES 2017-18|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lexus|ES 2019-25|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lexus|ES Hybrid 2017-18|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lexus|ES Hybrid 2019-25|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lexus|GS F 2016|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lexus|IS 2017-19|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lexus|IS 2022-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lexus|LC 2024-25|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lexus|NX 2018-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lexus|NX 2020-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lexus|NX Hybrid 2018-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lexus|NX Hybrid 2020-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lexus|RC 2018-20|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lexus|RC 2023|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lexus|RX 2016|Lexus Safety System+|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lexus|RX 2017-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lexus|RX 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lexus|RX Hybrid 2016|Lexus Safety System+|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lexus|RX Hybrid 2017-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lexus|RX Hybrid 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lexus|UX Hybrid 2019-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lincoln|Aviator 2020-24|Co-Pilot360 Plus|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lincoln|Aviator Plug-in Hybrid 2020-24|Co-Pilot360 Plus|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|MAN|eTGE 2020-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|MAN|TGE 2017-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Mazda|CX-5 2022-25|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Mazda connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Mazda|CX-9 2021-23|All|Stock|0 mph|28 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Mazda connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Nissan[6](#footnotes)|Altima 2019-20, 2024|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan B connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Nissan[6](#footnotes)|Leaf 2018-23|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Nissan[6](#footnotes)|Rogue 2018-20|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Nissan[6](#footnotes)|X-Trail 2017|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Ram|1500 2019-24|Adaptive Cruise Control (ACC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Ram connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Rivian|R1S 2022-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Rivian A connector
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Rivian|R1T 2022-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Rivian A connector
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|SEAT|Ateca 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|SEAT|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Subaru|Ascent 2019-21|All[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|Crosstrek 2018-19|EyeSight Driver Assistance[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|Crosstrek 2020-23|EyeSight Driver Assistance[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|Forester 2019-21|All[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|Impreza 2017-19|EyeSight Driver Assistance[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|Impreza 2020-22|EyeSight Driver Assistance[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|Legacy 2020-22|All[7](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru B connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|Outback 2020-22|All[7](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru B connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|XV 2018-19|EyeSight Driver Assistance[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|XV 2020-21|EyeSight Driver Assistance[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Škoda|Fabia 2022-23[14](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[16](#footnotes)||| +|Škoda|Kamiq 2021-23[12,14](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[16](#footnotes)||| +|Škoda|Karoq 2019-23[14](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Škoda|Kodiaq 2017-23[14](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Škoda|Octavia 2015-19[14](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Škoda|Octavia RS 2016[14](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Škoda|Octavia Scout 2017-19[14](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Škoda|Scala 2020-23[14](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[16](#footnotes)||| +|Škoda|Superb 2015-22[14](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Tesla[10](#footnotes)|Model 3 (with HW3) 2019-23[9](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Tesla A connector
- 1 USB-C coupler
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Tesla[10](#footnotes)|Model 3 (with HW4) 2024-25[9](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Tesla B connector
- 1 USB-C coupler
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Tesla[10](#footnotes)|Model Y (with HW3) 2020-23[9](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Tesla A connector
- 1 USB-C coupler
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Tesla[10](#footnotes)|Model Y (with HW4) 2024-25[9](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Tesla B connector
- 1 USB-C coupler
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Toyota|Alphard 2019-20|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Alphard Hybrid 2021|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Avalon 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Avalon 2017-18|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Avalon 2019-21|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Avalon 2022|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Avalon Hybrid 2019-21|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Avalon Hybrid 2022|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|C-HR 2017-20|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|C-HR 2021|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|C-HR Hybrid 2017-20|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|C-HR Hybrid 2021-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Camry 2018-20|All|Stock|0 mph[11](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Camry 2021-24|All|openpilot|0 mph[11](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Camry Hybrid 2018-20|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Camry Hybrid 2021-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Corolla 2017-19|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Corolla 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Corolla Cross (Non-US only) 2020-23|All|openpilot|17 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Corolla Cross Hybrid (Non-US only) 2020-22|All|openpilot|17 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Corolla Hatchback 2019-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Corolla Hybrid 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Corolla Hybrid (South America only) 2020-23|All|openpilot|17 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Highlander 2017-19|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Highlander 2020-23|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Highlander Hybrid 2017-19|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Highlander Hybrid 2020-23|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Mirai 2021|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Prius 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Prius 2017-20|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Prius 2021-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Prius Prime 2017-20|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Prius Prime 2021-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Prius v 2017|Toyota Safety Sense P|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|RAV4 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|RAV4 2017-18|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|RAV4 2019-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|RAV4 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|RAV4 2023-25|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|RAV4 Hybrid 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|RAV4 Hybrid 2017-18|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|RAV4 Hybrid 2019-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|RAV4 Hybrid 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|RAV4 Hybrid 2023-25|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Sienna 2018-20|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Volkswagen|Arteon 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Arteon eHybrid 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Arteon R 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Arteon Shooting Brake 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Atlas 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Atlas Cross Sport 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|California 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Caravelle 2020|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|CC 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Crafter 2017-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|e-Crafter 2018-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|e-Golf 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Golf 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Golf Alltrack 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Golf GTD 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Golf GTE 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Golf GTI 2015-21|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Golf R 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Golf SportsVan 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Grand California 2019-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Jetta 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Jetta GLI 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Passat 2015-22[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Passat Alltrack 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Passat GTE 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Polo 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[16](#footnotes)||| +|Volkswagen|Polo GTI 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[16](#footnotes)||| +|Volkswagen|T-Cross 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[16](#footnotes)||| +|Volkswagen|T-Roc 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Taos 2022-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Teramont 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Teramont Cross Sport 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Teramont X 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Tiguan 2018-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Tiguan eHybrid 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Touran 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
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`.
@@ -340,18 +340,17 @@ A supported vehicle is one that just works when you install a comma device. All 3Refers only to the Focus Mk4 (C519) available in Europe/China/Taiwan/Australasia, not the Focus Mk3 (C346) in North and South America/Southeast Asia.
4See more setup details for GM.
52019 Honda Civic 1.6L Diesel Sedan does not have ALC below 12mph.
-6Requires a CAN FD panda kit if not using comma 3X for this CAN FD car.
-7See more setup details for Nissan.
-8In the non-US market, openpilot requires the car to come equipped with EyeSight with Lane Keep Assistance.
-9Enabling longitudinal control (alpha) will disable all EyeSight functionality, including AEB, LDW, and RAB.
-10Some 2023 model years have HW4. To check which hardware type your vehicle has, look for Autopilot computer under Software -> Additional Vehicle Information on your vehicle's touchscreen. See this page for more information.
-11See more setup details for Tesla.
-12openpilot operates above 28mph for Camry 4CYL L, 4CYL LE and 4CYL SE which don't have Full-Speed Range Dynamic Radar Cruise Control.
-13Not including the China market Kamiq, which is based on the (currently) unsupported PQ34 platform.
-14Refers only to the MQB-based European B8 Passat, not the NMS Passat in the USA/China/Mideast markets.
-15Some Škoda vehicles are equipped with heated windshields, which are known to block GPS signal needed for some comma 3X functionality.
-16Only available for vehicles using a gateway (J533) harness. At this time, vehicles using a camera harness are limited to using stock ACC.
-17Model-years 2022 and beyond may have a combined CAN gateway and BCM, which is supported by openpilot in software, but doesn't yet have a harness available from the comma store.
+6See more setup details for Nissan.
+7In the non-US market, openpilot requires the car to come equipped with EyeSight with Lane Keep Assistance.
+8Enabling longitudinal control (alpha) will disable all EyeSight functionality, including AEB, LDW, and RAB.
+9Some 2023 model years have HW4. To check which hardware type your vehicle has, look for Autopilot computer under Software -> Additional Vehicle Information on your vehicle's touchscreen. See this page for more information.
+10See more setup details for Tesla.
+11openpilot operates above 28mph for Camry 4CYL L, 4CYL LE and 4CYL SE which don't have Full-Speed Range Dynamic Radar Cruise Control.
+12Not including the China market Kamiq, which is based on the (currently) unsupported PQ34 platform.
+13Refers only to the MQB-based European B8 Passat, not the NMS Passat in the USA/China/Mideast markets.
+14Some Škoda vehicles are equipped with heated windshields, which are known to block GPS signal needed for some comma four functionality.
+15Only available for vehicles using a gateway (J533) harness. At this time, vehicles using a camera harness are limited to using stock ACC.
+16Model-years 2022 and beyond may have a combined CAN gateway and BCM, which is supported by openpilot in software, but doesn't yet have a harness available from the comma store.
## Community Maintained Cars Although they're not upstream, the community has openpilot running on other makes and models. See the 'Community Supported Models' section of each make [on our wiki](https://wiki.comma.ai/). From 1d6d0fb85c94c663be087683eb690a7578a18ad9 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sat, 29 Nov 2025 02:19:30 +0100 Subject: [PATCH 468/910] ci: prevent build OOM by throttling locationd concurrency (#1527) * ci: add conditional swap creation to prebuilt workflow - Dynamically creates an 8GB swap file on systems with less than 8GB RAM. - Ensures builds complete reliably on low-memory environments. - Introduced cleanup step to remove swap after workflow execution. * Nice save * clean * reduce swap size in prebuilt workflow - Adjusted swap file size from 8GB to 4GB to optimize resource usage. * remove swap creation from prebuilt workflow - Simplified workflow by removing dynamic swap file creation and cleanup. - Adjusted resource management to rely on existing system resources. * update sunnypilot build workflow to use explicit script calls - Replaced `op` commands with explicit `/data/openpilot/tools/op.sh` script calls for better reliability and clarity. * update sunnypilot build workflow to use systemd for process management - Replaced `/data/openpilot/tools/op.sh` script calls with `systemctl` commands for improved compatibility and reliability. - Ensures consistent resource management during start and stop operations. * Splitting a bit the build then? * split build steps in sunnypilot prebuilt workflow - Added separate build steps for `modeld`, `modeld_v2`, and `locationd` with descriptive messages. - Improves logging and clarity during the build process. * limit locationd parallel processes aiming to help with resource consumption * typo * adding op's location d AND bumping to 4 cores for them * Apply suggestion from @sunnyhaibin --------- Co-authored-by: Jason Wen --- .github/workflows/sunnypilot-build-prebuilt.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/sunnypilot-build-prebuilt.yaml b/.github/workflows/sunnypilot-build-prebuilt.yaml index d3ad2d241..8af877963 100644 --- a/.github/workflows/sunnypilot-build-prebuilt.yaml +++ b/.github/workflows/sunnypilot-build-prebuilt.yaml @@ -180,6 +180,15 @@ jobs: ./release/release_files.py | sort | uniq | rsync -rRl${RUNNER_DEBUG:+v} --files-from=- . $BUILD_DIR/ cd $BUILD_DIR sed -i '/from .board.jungle import PandaJungle, PandaJungleDFU/s/^/#/' panda/__init__.py + echo "Building sunnypilot's modeld..." + scons -j$(nproc) cache_dir=${{env.SCONS_CACHE_DIR}} --minimal sunnypilot/modeld + echo "Building sunnypilot's modeld_v2..." + scons -j$(nproc) cache_dir=${{env.SCONS_CACHE_DIR}} --minimal sunnypilot/modeld_v2 + echo "Building sunnypilot's locationd..." + scons -j4 cache_dir=${{env.SCONS_CACHE_DIR}} --minimal sunnypilot/selfdrive/locationd + echo "Building openpilot's locationd..." + scons -j4 cache_dir=${{env.SCONS_CACHE_DIR}} --minimal selfdrive/locationd + echo "Building rest of sunnypilot" scons -j$(nproc) cache_dir=${{env.SCONS_CACHE_DIR}} --minimal touch ${BUILD_DIR}/prebuilt if [[ "${{ runner.debug }}" == "1" ]]; then From d3532d7d6f84d410e4be0efb9cb61a4b60f08b96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Fri, 28 Nov 2025 17:25:54 -0800 Subject: [PATCH 469/910] URLFile: catch more (#36712) * catch * linter has a point --- tools/lib/url_file.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tools/lib/url_file.py b/tools/lib/url_file.py index 31c1e0ff1..f988fa9db 100644 --- a/tools/lib/url_file.py +++ b/tools/lib/url_file.py @@ -6,6 +6,8 @@ from hashlib import sha256 from urllib3 import PoolManager, Retry from urllib3.response import BaseHTTPResponse from urllib3.util import Timeout +from urllib3.exceptions import MaxRetryError + from openpilot.common.utils import atomic_write_in_dir from openpilot.system.hardware.hw import Paths @@ -61,7 +63,10 @@ class URLFile: pass def _request(self, method: str, url: str, headers: dict[str, str] | None = None) -> BaseHTTPResponse: - return URLFile.pool_manager().request(method, url, timeout=self._timeout, headers=headers) + try: + return URLFile.pool_manager().request(method, url, timeout=self._timeout, headers=headers) + except MaxRetryError as e: + raise URLFileException(f"Failed to {method} {url}: {e}") from e def get_length_online(self) -> int: response = self._request('HEAD', self._url) From 9595a6f246505df380642d19aad26b2871aea755 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 28 Nov 2025 20:46:09 -0500 Subject: [PATCH 470/910] ui Qt: remove leftover files (#1530) --- selfdrive/ui/qt/offroad/offroad_home.cc | 158 ------------------------ selfdrive/ui/qt/offroad/offroad_home.h | 59 --------- 2 files changed, 217 deletions(-) delete mode 100644 selfdrive/ui/qt/offroad/offroad_home.cc delete mode 100644 selfdrive/ui/qt/offroad/offroad_home.h diff --git a/selfdrive/ui/qt/offroad/offroad_home.cc b/selfdrive/ui/qt/offroad/offroad_home.cc deleted file mode 100644 index 9ca41e556..000000000 --- a/selfdrive/ui/qt/offroad/offroad_home.cc +++ /dev/null @@ -1,158 +0,0 @@ -/** - * Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. - * - * This file is part of sunnypilot and is licensed under the MIT License. - * See the LICENSE.md file in the root directory for more details. - */ - -#include "selfdrive/ui/qt/offroad/offroad_home.h" - -#include "selfdrive/ui/qt/offroad/experimental_mode.h" -#include "selfdrive/ui/qt/util.h" -#include "selfdrive/ui/qt/widgets/prime.h" - -// OffroadHome: the offroad home page - -OffroadHome::OffroadHome(QWidget* parent) : QFrame(parent) { - QVBoxLayout* main_layout = new QVBoxLayout(this); - main_layout->setContentsMargins(40, 40, 40, 40); - - // top header - header_layout = new QHBoxLayout(); - header_layout->setContentsMargins(0, 0, 0, 0); - header_layout->setSpacing(16); - - update_notif = new QPushButton(tr("UPDATE")); - update_notif->setVisible(false); - update_notif->setStyleSheet("background-color: #364DEF;"); - QObject::connect(update_notif, &QPushButton::clicked, [=]() { center_layout->setCurrentIndex(1); }); - header_layout->addWidget(update_notif, 0, Qt::AlignHCenter | Qt::AlignLeft); - - alert_notif = new QPushButton(); - alert_notif->setVisible(false); - alert_notif->setStyleSheet("background-color: #E22C2C;"); - QObject::connect(alert_notif, &QPushButton::clicked, [=] { center_layout->setCurrentIndex(2); }); - header_layout->addWidget(alert_notif, 0, Qt::AlignHCenter | Qt::AlignLeft); - - version = new ElidedLabel(); - header_layout->addWidget(version, 0, Qt::AlignHCenter | Qt::AlignRight); - - main_layout->addLayout(header_layout); - - // main content - main_layout->addSpacing(25); - center_layout = new QStackedLayout(); - - QWidget *home_widget = new QWidget(this); - { - home_layout = new QHBoxLayout(home_widget); - home_layout->setContentsMargins(0, 0, 0, 0); - home_layout->setSpacing(30); - -#ifndef SUNNYPILOT - // left: PrimeAdWidget - QStackedWidget *left_widget = new QStackedWidget(this); - QVBoxLayout *left_prime_layout = new QVBoxLayout(); - left_prime_layout->setContentsMargins(0, 0, 0, 0); - QWidget *prime_user = new PrimeUserWidget(); - prime_user->setStyleSheet(R"( - border-radius: 10px; - background-color: #333333; - )"); - left_prime_layout->addWidget(prime_user); - left_prime_layout->addStretch(); - left_widget->addWidget(new LayoutWidget(left_prime_layout)); - left_widget->addWidget(new PrimeAdWidget); - left_widget->setStyleSheet("border-radius: 10px;"); - - connect(uiState()->prime_state, &PrimeState::changed, [left_widget]() { - left_widget->setCurrentIndex(uiState()->prime_state->isSubscribed() ? 0 : 1); - }); - - home_layout->addWidget(left_widget, 1); -#endif - - // right: ExperimentalModeButton, SetupWidget - QWidget* right_widget = new QWidget(this); - QVBoxLayout* right_column = new QVBoxLayout(right_widget); - right_column->setContentsMargins(0, 0, 0, 0); - right_widget->setFixedWidth(750); - right_column->setSpacing(30); - - ExperimentalModeButton *experimental_mode = new ExperimentalModeButton(this); - QObject::connect(experimental_mode, &ExperimentalModeButton::openSettings, this, &OffroadHome::openSettings); - right_column->addWidget(experimental_mode, 1); - - SetupWidget *setup_widget = new SetupWidget; - QObject::connect(setup_widget, &SetupWidget::openSettings, this, &OffroadHome::openSettings); - right_column->addWidget(setup_widget, 1); - - home_layout->addWidget(right_widget, 1); - } - center_layout->addWidget(home_widget); - - // add update & alerts widgets - update_widget = new UpdateAlert(); - QObject::connect(update_widget, &UpdateAlert::dismiss, [=]() { center_layout->setCurrentIndex(0); }); - center_layout->addWidget(update_widget); - alerts_widget = new OffroadAlert(); - QObject::connect(alerts_widget, &OffroadAlert::dismiss, [=]() { center_layout->setCurrentIndex(0); }); - center_layout->addWidget(alerts_widget); - - main_layout->addLayout(center_layout, 1); - - // set up refresh timer - timer = new QTimer(this); - timer->callOnTimeout(this, &OffroadHome::refresh); - - setStyleSheet(R"( - * { - color: white; - } - OffroadHome { - background-color: black; - } - OffroadHome > QPushButton { - padding: 15px 30px; - border-radius: 5px; - font-size: 40px; - font-weight: 500; - } - OffroadHome > QLabel { - font-size: 55px; - } - )"); -} - -void OffroadHome::showEvent(QShowEvent *event) { - refresh(); - timer->start(10 * 1000); -} - -void OffroadHome::hideEvent(QHideEvent *event) { - timer->stop(); -} - -void OffroadHome::refresh() { - version->setText(getBrand() + " " + QString::fromStdString(params.get("UpdaterCurrentDescription"))); - - bool updateAvailable = update_widget->refresh(); - int alerts = alerts_widget->refresh(); - - // pop-up new notification - int idx = center_layout->currentIndex(); - if (!updateAvailable && !alerts) { - idx = 0; - } else if (updateAvailable && (!update_notif->isVisible() || (!alerts && idx == 2))) { - idx = 1; - } else if (alerts && (!alert_notif->isVisible() || (!updateAvailable && idx == 1))) { - idx = 2; - } - center_layout->setCurrentIndex(idx); - - update_notif->setVisible(updateAvailable); - alert_notif->setVisible(alerts); - if (alerts) { - alert_notif->setText(QString::number(alerts) + (alerts > 1 ? tr(" ALERTS") : tr(" ALERT"))); - } -} diff --git a/selfdrive/ui/qt/offroad/offroad_home.h b/selfdrive/ui/qt/offroad/offroad_home.h deleted file mode 100644 index cac37d58c..000000000 --- a/selfdrive/ui/qt/offroad/offroad_home.h +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. - * - * This file is part of sunnypilot and is licensed under the MIT License. - * See the LICENSE.md file in the root directory for more details. - */ - -#pragma once - -#include "common/params.h" -#include "selfdrive/ui/qt/body.h" -#include "selfdrive/ui/qt/widgets/offroad_alerts.h" - -#ifdef SUNNYPILOT -#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h" -#include "selfdrive/ui/sunnypilot/qt/onroad/onroad_home.h" -#include "selfdrive/ui/sunnypilot/qt/sidebar.h" -#include "selfdrive/ui/sunnypilot/qt/widgets/prime.h" -#define OnroadWindow OnroadWindowSP -#define LayoutWidget LayoutWidgetSP -#define Sidebar SidebarSP -#define ElidedLabel ElidedLabelSP -#define SetupWidget SetupWidgetSP -#else -#include "selfdrive/ui/qt/widgets/controls.h" -#include "selfdrive/ui/qt/onroad/onroad_home.h" -#include "selfdrive/ui/qt/sidebar.h" -#include "selfdrive/ui/qt/widgets/prime.h" -#endif - -class OffroadHome : public QFrame { - Q_OBJECT - -public: - explicit OffroadHome(QWidget* parent = 0); - - signals: - void openSettings(int index = 0, const QString ¶m = ""); - -protected: - QHBoxLayout *home_layout; - QHBoxLayout *header_layout; - - void showEvent(QShowEvent *event) override; - void refresh(); - -private: - void hideEvent(QHideEvent *event) override; - - Params params; - - QTimer* timer; - ElidedLabel* version; - QStackedLayout* center_layout; - UpdateAlert *update_widget; - OffroadAlert* alerts_widget; - QPushButton* alert_notif; - QPushButton* update_notif; -}; From aa5a7ecb31c5f04f0bb4759e663f025be5550c42 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 29 Nov 2025 02:24:12 -0500 Subject: [PATCH 471/910] ci: no parallelism for LiveLocationKalman compile (#1531) * ci: no parallelism for locationd compile * just LLK * bump to 2 --- .../workflows/sunnypilot-build-prebuilt.yaml | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/sunnypilot-build-prebuilt.yaml b/.github/workflows/sunnypilot-build-prebuilt.yaml index 8af877963..50456f93d 100644 --- a/.github/workflows/sunnypilot-build-prebuilt.yaml +++ b/.github/workflows/sunnypilot-build-prebuilt.yaml @@ -6,10 +6,10 @@ env: CI_DIR: ${{ github.workspace }}/release/ci SCONS_CACHE_DIR: ${{ github.workspace }}/release/ci/scons_cache PUBLIC_REPO_URL: "https://github.com/sunnypilot/sunnypilot" - + # Branch configurations STAGING_SOURCE_BRANCH: 'master' - + # Runtime configuration SOURCE_BRANCH: "${{ github.head_ref || github.ref_name }}" @@ -75,7 +75,7 @@ jobs: cancel="$(echo "$CONFIG" | jq -r '.cancel_publish_in_progress')"; echo "cancel_publish_in_progress=$( [ "$cancel" = "null" ] && echo "true" || echo $cancel)" >> $GITHUB_OUTPUT echo "publish_concurrency_group=publish-${BRANCH}$( [ "$cancel" = "null" ] || [ "$cancel" = "true" ] || echo "${{ github.sha }}" )" >> $GITHUB_OUTPUT - + is_stable_branch="$(echo "$CONFIG" | jq -r '.stable_branch // false')"; echo "is_stable_branch=$is_stable_branch" >> $GITHUB_OUTPUT @@ -85,7 +85,7 @@ jobs: fi echo "build=$BUILD" >> $GITHUB_OUTPUT cat $GITHUB_OUTPUT - + validate_tests: runs-on: ubuntu-24.04 needs: [ prepare_strategy ] @@ -119,7 +119,7 @@ jobs: needs.prepare_strategy.result == 'success' && (needs.validate_tests.result == 'success' || needs.validate_tests.result == 'skipped') && (!contains(github.event_name, 'pull_request') || - (github.event.action == 'labeled' && github.event.label.name == 'prebuilt')) + (github.event.action == 'labeled' && github.event.label.name == 'prebuilt')) }} steps: - uses: actions/checkout@v4 @@ -134,7 +134,7 @@ jobs: with: path: ${{env.SCONS_CACHE_DIR}} key: scons-${{ runner.os }}-${{ runner.arch }}-${{ env.SOURCE_BRANCH }}-${{ github.sha }} - # Note: GitHub Actions enforces cache isolation between different build sources (PR builds, workflow dispatches, etc.) + # Note: GitHub Actions enforces cache isolation between different build sources (PR builds, workflow dispatches, etc.) # for security. Only caches from the default branch are shared across all builds. This is by design and cannot be overridden. restore-keys: | scons-${{ runner.os }}-${{ runner.arch }}-${{ env.SOURCE_BRANCH }} @@ -148,7 +148,7 @@ jobs: echo "version=${{ needs.prepare_strategy.outputs.version }}" >> $GITHUB_OUTPUT echo "extra_version_identifier=${{ needs.prepare_strategy.outputs.extra_version_identifier }}" >> $GITHUB_OUTPUT echo "commit_sha=${{ github.sha }}" >> $GITHUB_OUTPUT - + # Set up common environment source /etc/profile; export UV_PROJECT_ENVIRONMENT=${HOME}/venv @@ -185,9 +185,9 @@ jobs: echo "Building sunnypilot's modeld_v2..." scons -j$(nproc) cache_dir=${{env.SCONS_CACHE_DIR}} --minimal sunnypilot/modeld_v2 echo "Building sunnypilot's locationd..." - scons -j4 cache_dir=${{env.SCONS_CACHE_DIR}} --minimal sunnypilot/selfdrive/locationd + scons -j2 cache_dir=${{env.SCONS_CACHE_DIR}} --minimal sunnypilot/selfdrive/locationd echo "Building openpilot's locationd..." - scons -j4 cache_dir=${{env.SCONS_CACHE_DIR}} --minimal selfdrive/locationd + scons -j$(nproc) cache_dir=${{env.SCONS_CACHE_DIR}} --minimal selfdrive/locationd echo "Building rest of sunnypilot" scons -j$(nproc) cache_dir=${{env.SCONS_CACHE_DIR}} --minimal touch ${BUILD_DIR}/prebuilt @@ -250,8 +250,8 @@ jobs: if: always() run: | PYTHONPATH=$PYTHONPATH:${{ github.workspace }}/ ${{ github.workspace }}/scripts/manage-powersave.py --enable - - + + publish: concurrency: # We do a bit of a hack here to avoid canceling the publishing job if a new commit comes in while we're publishing by adding the sha to the group name. @@ -302,7 +302,7 @@ jobs: echo "1. Go to: ${{ github.server_url }}/${{ github.repository }}/settings/variables/actions/AUTO_DEPLOY_PREBUILT_BRANCHES" echo "2. Current value: ${{ vars.AUTO_DEPLOY_PREBUILT_BRANCHES }}" echo "3. Update as needed (JSON array with no spaces)" - + - name: Tag ${{ needs.prepare_strategy.outputs.environment }} if: ${{ needs.prepare_strategy.outputs.is_stable_branch == 'true' && (github.event_name != 'push' || !startsWith(github.ref, 'refs/tags/')) }} run: | @@ -311,7 +311,7 @@ jobs: git push -f origin ${TAG} notify: - needs: + needs: - prepare_strategy - build - publish @@ -340,7 +340,7 @@ jobs: ${{ vars.DISCOURSE_GENERAL_UPDATE_NOTICE }} EOF ) - + { echo 'content< Date: Fri, 28 Nov 2025 23:29:13 -0800 Subject: [PATCH 472/910] mici: split wifi and network settings (#36715) * split * clean up * better --- .../mici/layouts/settings/network/__init__.py | 129 ++++++++++++++++++ .../{network.py => network/wifi_ui.py} | 125 +---------------- 2 files changed, 131 insertions(+), 123 deletions(-) create mode 100644 selfdrive/ui/mici/layouts/settings/network/__init__.py rename selfdrive/ui/mici/layouts/settings/{network.py => network/wifi_ui.py} (78%) diff --git a/selfdrive/ui/mici/layouts/settings/network/__init__.py b/selfdrive/ui/mici/layouts/settings/network/__init__.py new file mode 100644 index 000000000..017dead1b --- /dev/null +++ b/selfdrive/ui/mici/layouts/settings/network/__init__.py @@ -0,0 +1,129 @@ +import pyray as rl +from enum import IntEnum +from collections.abc import Callable + +from openpilot.system.ui.widgets.scroller import Scroller +from openpilot.selfdrive.ui.mici.layouts.settings.network.wifi_ui import WifiUIMici +from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigMultiToggle, BigToggle +from openpilot.selfdrive.ui.mici.widgets.dialog import BigInputDialog +from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.widgets import NavWidget +from openpilot.system.ui.lib.wifi_manager import WifiManager, Network, MeteredType + + +class NetworkPanelType(IntEnum): + NONE = 0 + WIFI = 1 + + +class NetworkLayoutMici(NavWidget): + def __init__(self, back_callback: Callable): + super().__init__() + + self._current_panel = NetworkPanelType.WIFI + self.set_back_enabled(lambda: self._current_panel == NetworkPanelType.NONE) + + self._wifi_manager = WifiManager() + self._wifi_manager.set_active(False) + self._wifi_ui = WifiUIMici(self._wifi_manager, back_callback=lambda: self._switch_to_panel(NetworkPanelType.NONE)) + + self._wifi_manager.add_callbacks( + networks_updated=self._on_network_updated, + ) + + _tethering_icon = "icons_mici/settings/network/tethering.png" + + # ******** Tethering ******** + def tethering_toggle_callback(checked: bool): + self._tethering_toggle_btn.set_enabled(False) + self._network_metered_btn.set_enabled(False) + self._wifi_manager.set_tethering_active(checked) + + self._tethering_toggle_btn = BigToggle("enable tethering", "", toggle_callback=tethering_toggle_callback) + + def tethering_password_callback(password: str): + if password: + self._wifi_manager.set_tethering_password(password) + + def tethering_password_clicked(): + tethering_password = self._wifi_manager.tethering_password + dlg = BigInputDialog("enter password...", tethering_password, minimum_length=8, + confirm_callback=tethering_password_callback) + gui_app.set_modal_overlay(dlg) + + txt_tethering = gui_app.texture(_tethering_icon, 64, 53) + self._tethering_password_btn = BigButton("tethering password", "", txt_tethering) + self._tethering_password_btn.set_click_callback(tethering_password_clicked) + + # ******** IP Address ******** + self._ip_address_btn = BigButton("IP Address", "Not connected") + + # ******** Network Metered ******** + def network_metered_callback(value: str): + self._network_metered_btn.set_enabled(False) + metered = { + 'default': MeteredType.UNKNOWN, + 'metered': MeteredType.YES, + 'unmetered': MeteredType.NO + }.get(value, MeteredType.UNKNOWN) + self._wifi_manager.set_current_network_metered(metered) + + # TODO: signal for current network metered type when changing networks, this is wrong until you press it once + # TODO: disable when not connected + self._network_metered_btn = BigMultiToggle("network usage", ["default", "metered", "unmetered"], select_callback=network_metered_callback) + self._network_metered_btn.set_enabled(False) + + wifi_button = BigButton("wi-fi") + wifi_button.set_click_callback(lambda: self._switch_to_panel(NetworkPanelType.WIFI)) + + # Main scroller ---------------------------------- + self._scroller = Scroller([ + wifi_button, + self._network_metered_btn, + self._tethering_toggle_btn, + self._tethering_password_btn, + self._ip_address_btn, + ], snap_items=False) + + # Set up back navigation + self.set_back_callback(back_callback) + + def show_event(self): + super().show_event() + self._current_panel = NetworkPanelType.NONE + self._wifi_ui.show_event() + self._scroller.show_event() + + def hide_event(self): + super().hide_event() + self._wifi_ui.hide_event() + + def _on_network_updated(self, networks: list[Network]): + # Update tethering state + tethering_active = self._wifi_manager.is_tethering_active() + # TODO: use real signals (like activated/settings changed, etc.) to speed up re-enabling buttons + self._tethering_toggle_btn.set_enabled(True) + self._network_metered_btn.set_enabled(lambda: not tethering_active and bool(self._wifi_manager.ipv4_address)) + self._tethering_toggle_btn.set_checked(tethering_active) + + # Update IP address + self._ip_address_btn.set_value(self._wifi_manager.ipv4_address or "Not connected") + + # Update network metered + self._network_metered_btn.set_value( + { + MeteredType.UNKNOWN: 'default', + MeteredType.YES: 'metered', + MeteredType.NO: 'unmetered' + }.get(self._wifi_manager.current_network_metered, 'default')) + + def _switch_to_panel(self, panel_type: NetworkPanelType): + self._current_panel = panel_type + + def _render(self, rect: rl.Rectangle): + self._wifi_manager.process_callbacks() + + if self._current_panel == NetworkPanelType.WIFI: + self._wifi_ui.render(rect) + else: + self._scroller.render(rect) diff --git a/selfdrive/ui/mici/layouts/settings/network.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py similarity index 78% rename from selfdrive/ui/mici/layouts/settings/network.py rename to selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index a62c1d153..2ab46d169 100644 --- a/selfdrive/ui/mici/layouts/settings/network.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -1,28 +1,20 @@ import math import numpy as np import pyray as rl -from enum import IntEnum from collections.abc import Callable from openpilot.common.swaglog import cloudlog -from openpilot.system.ui.widgets.scroller import Scroller from openpilot.system.ui.widgets.label import UnifiedLabel -from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigMultiToggle, BigToggle from openpilot.selfdrive.ui.mici.widgets.dialog import BigMultiOptionDialog, BigInputDialog, BigDialogOptionButton, BigConfirmationDialogV2 from openpilot.system.ui.lib.application import gui_app, MousePos, FontWeight from openpilot.system.ui.widgets import Widget, NavWidget -from openpilot.system.ui.lib.wifi_manager import WifiManager, Network, SecurityType, MeteredType +from openpilot.system.ui.lib.wifi_manager import WifiManager, Network, SecurityType def normalize_ssid(ssid: str) -> str: return ssid.replace("’", "'") # for iPhone hotspots -class NetworkPanelType(IntEnum): - NONE = 0 - WIFI = 1 - - class LoadingAnimation(Widget): def _render(self, _): cx = int(self._rect.x + 70) @@ -392,7 +384,7 @@ class WifiUIMici(BigMultiOptionDialog): self._network_info_page.set_current_network(_network) self._should_open_network_info_page = True - network_button.set_click_callback(lambda _net=network,_button=network_button: _button._selected and show_network_info_page(_net)) + network_button.set_click_callback(lambda _net=network, _button=network_button: _button._selected and show_network_info_page(_net)) self.add_button(network_button) @@ -443,116 +435,3 @@ class WifiUIMici(BigMultiOptionDialog): if not self._networks: self._loading_animation.render(self._rect) - - -class NetworkLayoutMici(NavWidget): - def __init__(self, back_callback: Callable): - super().__init__() - - self._current_panel = NetworkPanelType.WIFI - self.set_back_enabled(lambda: self._current_panel == NetworkPanelType.NONE) - - self._wifi_manager = WifiManager() - self._wifi_manager.set_active(False) - self._wifi_ui = WifiUIMici(self._wifi_manager, back_callback=lambda: self._switch_to_panel(NetworkPanelType.NONE)) - - self._wifi_manager.add_callbacks( - networks_updated=self._on_network_updated, - ) - - _tethering_icon = "icons_mici/settings/network/tethering.png" - - # ******** Tethering ******** - def tethering_toggle_callback(checked: bool): - self._tethering_toggle_btn.set_enabled(False) - self._network_metered_btn.set_enabled(False) - self._wifi_manager.set_tethering_active(checked) - - self._tethering_toggle_btn = BigToggle("enable tethering", "", toggle_callback=tethering_toggle_callback) - - def tethering_password_callback(password: str): - if password: - self._wifi_manager.set_tethering_password(password) - - def tethering_password_clicked(): - tethering_password = self._wifi_manager.tethering_password - dlg = BigInputDialog("enter password...", tethering_password, minimum_length=8, - confirm_callback=tethering_password_callback) - gui_app.set_modal_overlay(dlg) - - txt_tethering = gui_app.texture(_tethering_icon, 64, 53) - self._tethering_password_btn = BigButton("tethering password", "", txt_tethering) - self._tethering_password_btn.set_click_callback(tethering_password_clicked) - - # ******** IP Address ******** - self._ip_address_btn = BigButton("IP Address", "Not connected") - - # ******** Network Metered ******** - def network_metered_callback(value: str): - self._network_metered_btn.set_enabled(False) - metered = { - 'default': MeteredType.UNKNOWN, - 'metered': MeteredType.YES, - 'unmetered': MeteredType.NO - }.get(value, MeteredType.UNKNOWN) - self._wifi_manager.set_current_network_metered(metered) - - # TODO: signal for current network metered type when changing networks, this is wrong until you press it once - # TODO: disable when not connected - self._network_metered_btn = BigMultiToggle("network usage", ["default", "metered", "unmetered"], select_callback=network_metered_callback) - self._network_metered_btn.set_enabled(False) - - wifi_button = BigButton("wi-fi") - wifi_button.set_click_callback(lambda: self._switch_to_panel(NetworkPanelType.WIFI)) - - # Main scroller ---------------------------------- - self._scroller = Scroller([ - wifi_button, - self._network_metered_btn, - self._tethering_toggle_btn, - self._tethering_password_btn, - self._ip_address_btn, - ], snap_items=False) - - # Set up back navigation - self.set_back_callback(back_callback) - - def show_event(self): - super().show_event() - self._current_panel = NetworkPanelType.NONE - self._wifi_ui.show_event() - self._scroller.show_event() - - def hide_event(self): - super().hide_event() - self._wifi_ui.hide_event() - - def _on_network_updated(self, networks: list[Network]): - # Update tethering state - tethering_active = self._wifi_manager.is_tethering_active() - # TODO: use real signals (like activated/settings changed, etc.) to speed up re-enabling buttons - self._tethering_toggle_btn.set_enabled(True) - self._network_metered_btn.set_enabled(lambda: not tethering_active and bool(self._wifi_manager.ipv4_address)) - self._tethering_toggle_btn.set_checked(tethering_active) - - # Update IP address - self._ip_address_btn.set_value(self._wifi_manager.ipv4_address or "Not connected") - - # Update network metered - self._network_metered_btn.set_value( - { - MeteredType.UNKNOWN: 'default', - MeteredType.YES: 'metered', - MeteredType.NO: 'unmetered' - }.get(self._wifi_manager.current_network_metered, 'default')) - - def _switch_to_panel(self, panel_type: NetworkPanelType): - self._current_panel = panel_type - - def _render(self, rect: rl.Rectangle): - self._wifi_manager.process_callbacks() - - if self._current_panel == NetworkPanelType.WIFI: - self._wifi_ui.render(rect) - else: - self._scroller.render(rect) From 65f18c363be1bae0f9835d38a01eecab64c7e6ef Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 28 Nov 2025 23:41:55 -0800 Subject: [PATCH 473/910] Mici advanced network settings (#36716) * add back * forgot * clean up --- .../mici/layouts/settings/network/__init__.py | 53 ++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/selfdrive/ui/mici/layouts/settings/network/__init__.py b/selfdrive/ui/mici/layouts/settings/network/__init__.py index 017dead1b..8c27d87d4 100644 --- a/selfdrive/ui/mici/layouts/settings/network/__init__.py +++ b/selfdrive/ui/mici/layouts/settings/network/__init__.py @@ -4,8 +4,10 @@ from collections.abc import Callable from openpilot.system.ui.widgets.scroller import Scroller from openpilot.selfdrive.ui.mici.layouts.settings.network.wifi_ui import WifiUIMici -from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigMultiToggle, BigToggle +from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigMultiToggle, BigToggle, BigParamControl from openpilot.selfdrive.ui.mici.widgets.dialog import BigInputDialog +from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.selfdrive.ui.lib.prime_state import PrimeType from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.widgets import NavWidget from openpilot.system.ui.lib.wifi_manager import WifiManager, Network, MeteredType @@ -76,18 +78,47 @@ class NetworkLayoutMici(NavWidget): wifi_button = BigButton("wi-fi") wifi_button.set_click_callback(lambda: self._switch_to_panel(NetworkPanelType.WIFI)) + # ******** Advanced settings ******** + # ******** Roaming toggle ******** + self._roaming_btn = BigParamControl("enable roaming", "GsmRoaming", toggle_callback=self._toggle_roaming) + + # ******** APN settings ******** + self._apn_btn = BigButton("apn settings", "edit") + self._apn_btn.set_click_callback(self._edit_apn) + + # ******** Cellular metered toggle ******** + self._cellular_metered_btn = BigParamControl("cellular metered", "GsmMetered", toggle_callback=self._toggle_cellular_metered) + # Main scroller ---------------------------------- self._scroller = Scroller([ wifi_button, self._network_metered_btn, self._tethering_toggle_btn, self._tethering_password_btn, + # /* Advanced settings + self._roaming_btn, + self._apn_btn, + self._cellular_metered_btn, + # */ self._ip_address_btn, ], snap_items=False) + # Set initial config + roaming_enabled = ui_state.params.get_bool("GsmRoaming") + metered = ui_state.params.get_bool("GsmMetered") + self._wifi_manager.update_gsm_settings(roaming_enabled, ui_state.params.get("GsmApn") or "", metered) + # Set up back navigation self.set_back_callback(back_callback) + def _update_state(self): + # If not using prime SIM, show GSM settings and enable IPv4 forwarding + show_cell_settings = ui_state.prime_state.get_type() in (PrimeType.NONE, PrimeType.LITE) + self._wifi_manager.set_ipv4_forward(show_cell_settings) + self._roaming_btn.set_visible(show_cell_settings) + self._apn_btn.set_visible(show_cell_settings) + self._cellular_metered_btn.set_visible(show_cell_settings) + def show_event(self): super().show_event() self._current_panel = NetworkPanelType.NONE @@ -98,6 +129,26 @@ class NetworkLayoutMici(NavWidget): super().hide_event() self._wifi_ui.hide_event() + def _toggle_roaming(self, checked: bool): + self._wifi_manager.update_gsm_settings(checked, ui_state.params.get("GsmApn") or "", ui_state.params.get_bool("GsmMetered")) + + def _edit_apn(self): + def update_apn(apn: str): + apn = apn.strip() + if apn == "": + ui_state.params.remove("GsmApn") + else: + ui_state.params.put("GsmApn", apn) + + self._wifi_manager.update_gsm_settings(ui_state.params.get_bool("GsmRoaming"), apn, ui_state.params.get_bool("GsmMetered")) + + current_apn = ui_state.params.get("GsmApn") or "" + dlg = BigInputDialog("enter APN", current_apn, minimum_length=0, confirm_callback=update_apn) + gui_app.set_modal_overlay(dlg) + + def _toggle_cellular_metered(self, checked: bool): + self._wifi_manager.update_gsm_settings(ui_state.params.get_bool("GsmRoaming"), ui_state.params.get("GsmApn") or "", checked) + def _on_network_updated(self, networks: list[Network]): # Update tethering state tethering_active = self._wifi_manager.is_tethering_active() From 99ed90d459527b6d0ffc0bbd664309142a0b2202 Mon Sep 17 00:00:00 2001 From: Nayan Date: Sat, 29 Nov 2025 03:10:59 -0500 Subject: [PATCH 474/910] ui: Option Control (#1479) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * param to control stock vs sp ui * init styles * SP Toggles * Lint * optimizations * Panels. With Icons. And Scroller. * patience, grasshopper * more patience, grasshopper * sp raylib preview * fix callback * fix ui preview * add ui previews * Option Control * Need this * better padding * this * listitem -> listitemsp * add show_description method * remove padding from line separator. like, WHY? 😩😩 * simplify * I. SAID. SIMPLIFY. * AAARGGGGGG..... * option control value fix * forgot about the setter * scroller -> scroller_tici * scroller -> scroller_tici * ui: `GuiApplicationExt` * add to readme * use gui_app.sunnypilot_ui() * use gui_app.sunnypilot_ui() * ugh. no * new style. old style. * lint * rename * old but gold <3 --------- Co-authored-by: discountchubbs Co-authored-by: Jason Wen Co-authored-by: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> --- system/ui/sunnypilot/lib/styles.py | 9 + system/ui/sunnypilot/widgets/list_view.py | 14 ++ .../ui/sunnypilot/widgets/option_control.py | 165 ++++++++++++++++++ 3 files changed, 188 insertions(+) create mode 100644 system/ui/sunnypilot/widgets/option_control.py diff --git a/system/ui/sunnypilot/lib/styles.py b/system/ui/sunnypilot/lib/styles.py index b942f79d3..0fb4a5c30 100644 --- a/system/ui/sunnypilot/lib/styles.py +++ b/system/ui/sunnypilot/lib/styles.py @@ -56,5 +56,14 @@ class DefaultStyleSP(Base): MBC_BG_CHECKED_ENABLED = rl.Color(0x69, 0x68, 0x68, 0xFF) MBC_DISABLED = rl.Color(0xFF, 0xFF, 0xFF, 0x33) + # Option Control + OPTION_CONTROL_CONTAINER_BG = OFF_BG_COLOR + OPTION_CONTROL_BTN_ENABLED = rl.Color(88, 88, 88, 255) + OPTION_CONTROL_BTN_PRESSED = rl.Color(0x69, 0x68, 0x68, 0xFF) + OPTION_CONTROL_BTN_DISABLED = DISABLED_OFF_BG_COLOR + OPTION_CONTROL_TEXT_ENABLED = rl.WHITE + OPTION_CONTROL_TEXT_PRESSED = rl.WHITE + OPTION_CONTROL_TEXT_DISABLED = ITEM_DISABLED_TEXT_COLOR + style = DefaultStyleSP diff --git a/system/ui/sunnypilot/widgets/list_view.py b/system/ui/sunnypilot/widgets/list_view.py index 4ecb9a488..955adaa73 100644 --- a/system/ui/sunnypilot/widgets/list_view.py +++ b/system/ui/sunnypilot/widgets/list_view.py @@ -13,6 +13,7 @@ from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.sunnypilot.widgets.toggle import ToggleSP from openpilot.system.ui.widgets.list_view import ListItem, ToggleAction, ItemAction, MultipleButtonAction, _resolve_value from openpilot.system.ui.sunnypilot.lib.styles import style +from openpilot.system.ui.sunnypilot.widgets.option_control import OptionControlSP, LABEL_WIDTH class ToggleActionSP(ToggleAction): @@ -186,3 +187,16 @@ def multiple_button_item_sp(title: str | Callable[[], str], description: str | C icon: str = "", param: str | None = None, inline: bool = False) -> ListItemSP: action = MultipleButtonActionSP(buttons, button_width, selected_index, callback=callback, param=param) return ListItemSP(title=title, description=description, icon=icon, action_item=action, inline=inline) + + +def option_item_sp(title: str | Callable[[], str], param: str, + min_value: int, max_value: int, description: str | Callable[[], str] | None = None, + value_change_step: int = 1, on_value_changed: Callable[[int], None] | None = None, + enabled: bool | Callable[[], bool] = True, + icon: str = "", label_width: int = LABEL_WIDTH, value_map: dict[int, int] | None = None, + use_float_scaling: bool = False, label_callback: Callable[[int], str] | None = None) -> ListItemSP: + action = OptionControlSP( + param, min_value, max_value, value_change_step, + enabled, on_value_changed, value_map, label_width, use_float_scaling, label_callback + ) + return ListItemSP(title=title, description=description, action_item=action, icon=icon) diff --git a/system/ui/sunnypilot/widgets/option_control.py b/system/ui/sunnypilot/widgets/option_control.py new file mode 100644 index 000000000..91e9650eb --- /dev/null +++ b/system/ui/sunnypilot/widgets/option_control.py @@ -0,0 +1,165 @@ +import pyray as rl +from collections.abc import Callable +from openpilot.common.params import Params +from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos +from openpilot.system.ui.lib.text_measure import measure_text_cached +from openpilot.system.ui.sunnypilot.lib.styles import style +from openpilot.system.ui.widgets.list_view import ItemAction + +# Dimensions and styling constants +BUTTON_WIDTH = 150 +BUTTON_HEIGHT = 150 +LABEL_WIDTH = 350 +BUTTON_SPACING = 25 +VALUE_FONT_SIZE = 50 +BUTTON_FONT_SIZE = 60 +CONTAINER_PADDING = 20 + + +class OptionControlSP(ItemAction): + def __init__(self, param: str, min_value: int, max_value: int, + value_change_step: int = 1, enabled: bool | Callable[[], bool] = True, + on_value_changed: Callable[[int], None] | None = None, + value_map: dict[int, int] | None = None, + label_width: int = LABEL_WIDTH, + use_float_scaling: bool = False, label_callback: Callable[[int], str] | None = None): + + super().__init__(enabled=enabled) + self.params = Params() + self.param_key = param + self.min_value = min_value + self.max_value = max_value + self.value_change_step = value_change_step + self._minus_enabled = enabled + self._plus_enabled = enabled + self.on_value_changed = on_value_changed + self.value_map = value_map + self.label_width = label_width + self.use_float_scaling = use_float_scaling + self.current_value = min_value + self.label_callback = label_callback + if self.value_map: + for key in self.value_map: + if self.value_map[key] == self.params.get(self.param_key, return_default=True): + self.current_value = int(key) + break + else: + self.current_value = int(self.params.get(self.param_key, return_default=True)) + + # Initialize font and button styles + self._font = gui_app.font(FontWeight.MEDIUM) + + # Layout rectangles for components + self.minus_btn_rect = rl.Rectangle(0, 0, 0, 0) + self.plus_btn_rect = rl.Rectangle(0, 0, 0, 0) + + def get_value(self) -> int: + """Get the current value of the control""" + return self.current_value + + def set_value(self, value: int): + """Set the control to a specific value""" + if self.min_value <= value <= self.max_value: + self.current_value = value + if self.value_map: + self.params.put(self.param_key, self.value_map[value]) + else: + if self.use_float_scaling: + self.params.put(self.param_key, value / 100.0) + else: + self.params.put(self.param_key, value) + if self.on_value_changed: + self.on_value_changed(value) + + def get_displayed_value(self) -> str: + """Get the displayed value, handling value mapping if present""" + value = self.current_value + + if callable(self.label_callback): + if self.value_map: + return self.label_callback(self.value_map[value]) + else: + return self.label_callback(value) + + if self.value_map: + # Use the value map to get the display string + if value in self.value_map: + return str(self.value_map[value]) # Return the display string + + # If using float scaling, format as float + if self.use_float_scaling: + return f"{value / 100.0:.2f}" + + return str(value) + + def _render(self, rect: rl.Rectangle): + if self._rect.width == 0 or self._rect.height == 0 or not self.is_visible: + return + + control_width = (BUTTON_WIDTH * 2) + self.label_width + (BUTTON_SPACING * 2) + total_width = control_width + (CONTAINER_PADDING * 2) + self._rect.width = total_width + + start_x = self._rect.x + self._rect.width - control_width - (CONTAINER_PADDING * 2) + component_y = rect.y + (rect.height - BUTTON_HEIGHT) / 2 + self.container_rect = rl.Rectangle(start_x, component_y, total_width, BUTTON_HEIGHT) + + # background + rl.draw_rectangle_rounded(self.container_rect, 0.2, 20, style.OPTION_CONTROL_CONTAINER_BG) + + # minus button + self.minus_btn_rect = rl.Rectangle(self.container_rect.x, component_y, BUTTON_WIDTH + CONTAINER_PADDING, + BUTTON_HEIGHT) + + # label + label_x = self.container_rect.x + CONTAINER_PADDING + BUTTON_WIDTH + BUTTON_SPACING + self.label_rect = rl.Rectangle(label_x, component_y, self.label_width, BUTTON_HEIGHT) + + # plus button + plus_x = label_x + self.label_width + BUTTON_SPACING + self.plus_btn_rect = rl.Rectangle(plus_x, component_y, BUTTON_WIDTH + CONTAINER_PADDING, BUTTON_HEIGHT) + + self._minus_enabled = self.enabled and self.current_value > self.min_value + self._plus_enabled = self.enabled and self.current_value < self.max_value + + self._render_button(self.minus_btn_rect, "-", self._minus_enabled) + self._render_value_label() + self._render_button(self.plus_btn_rect, "+", self._plus_enabled) + + def _render_button(self, rect: rl.Rectangle, text: str, enabled: bool): + mouse_pos = rl.get_mouse_position() + is_pressed = (rl.check_collision_point_rec(mouse_pos, rect) and + self._touch_valid() and rl.is_mouse_button_down(rl.MouseButton.MOUSE_BUTTON_LEFT)) + + text_color = style.ITEM_TEXT_COLOR if enabled else style.ITEM_DISABLED_TEXT_COLOR + + # highlight + if enabled and is_pressed: + rl.draw_rectangle_rounded(rect, 0.2, 20, style.OPTION_CONTROL_BTN_PRESSED) + + # button text + text_size = measure_text_cached(self._font, text, BUTTON_FONT_SIZE) + text_x = rect.x + (rect.width - text_size.x) / 2 + text_y = rect.y + (rect.height - text_size.y) / 2 + rl.draw_text_ex(self._font, text, rl.Vector2(text_x, text_y), BUTTON_FONT_SIZE, 0, text_color) + + def _render_value_label(self): + """Render the current value label""" + text = self.get_displayed_value() + text_color = style.ITEM_TEXT_COLOR if self.enabled else style.ITEM_DISABLED_TEXT_COLOR + + text_size = measure_text_cached(self._font, text, VALUE_FONT_SIZE) + text_x = self.label_rect.x + (self.label_rect.width - text_size.x) / 2 + text_y = self.label_rect.y + (self.label_rect.height - text_size.y) / 2 + + rl.draw_text_ex(self._font, text, rl.Vector2(text_x, text_y), VALUE_FONT_SIZE, 0, text_color) + + def _handle_mouse_release(self, mouse_pos: MousePos): + if self._minus_enabled and rl.check_collision_point_rec(mouse_pos, self.minus_btn_rect): + self.current_value -= self.value_change_step + self.current_value = max(self.min_value, self.current_value) + elif self._plus_enabled and rl.check_collision_point_rec(mouse_pos, self.plus_btn_rect): + self.current_value += self.value_change_step + self.current_value = min(self.max_value, self.current_value) + + self.set_value(self.current_value) From bee820f8ed5af568dd0ae1ba584a86c62dd6237e Mon Sep 17 00:00:00 2001 From: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Sat, 29 Nov 2025 00:23:03 -0800 Subject: [PATCH 475/910] ui: `InputDialogSP` (#1484) * dialog txt * compare vs what used to be done before InputDialog * rm * final --------- Co-authored-by: Jason Wen --- system/ui/sunnypilot/widgets/__init__.py | 0 system/ui/sunnypilot/widgets/input_dialog.py | 41 ++++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 system/ui/sunnypilot/widgets/__init__.py create mode 100644 system/ui/sunnypilot/widgets/input_dialog.py diff --git a/system/ui/sunnypilot/widgets/__init__.py b/system/ui/sunnypilot/widgets/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/system/ui/sunnypilot/widgets/input_dialog.py b/system/ui/sunnypilot/widgets/input_dialog.py new file mode 100644 index 000000000..88ab0b1a6 --- /dev/null +++ b/system/ui/sunnypilot/widgets/input_dialog.py @@ -0,0 +1,41 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +from collections.abc import Callable + +from openpilot.common.params import Params +from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.lib.multilang import tr +from openpilot.system.ui.widgets import DialogResult +from openpilot.system.ui.widgets.keyboard import Keyboard + + +class InputDialogSP: + def __init__(self, title: str, sub_title: str | None = None, current_text: str = "", param: str | None = None, + callback: Callable[[DialogResult, str], None] | None = None, + min_text_size: int = 0, password_mode: bool = False): + self.callback = callback + self.current_text = current_text + self.keyboard = Keyboard(max_text_size=255, min_text_size=min_text_size, password_mode=password_mode) + self.param = param + self._params = Params() + self.sub_title = sub_title + self.title = title + + def show(self): + self.keyboard.reset(min_text_size=self.keyboard._min_text_size) + self.keyboard.set_title(tr(self.title), *(tr(self.sub_title),) if self.sub_title else ()) + self.keyboard.set_text(self.current_text) + + def internal_callback(result: DialogResult): + text = self.keyboard.text if result == DialogResult.CONFIRM else "" + if result == DialogResult.CONFIRM: + if self.param: + self._params.put(self.param, text) + if self.callback: + self.callback(result, text) + + gui_app.set_modal_overlay(self.keyboard, internal_callback) From 3e29a0ccfead6ef5e08584e0a5a37911543026db Mon Sep 17 00:00:00 2001 From: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Sat, 29 Nov 2025 00:30:18 -0800 Subject: [PATCH 476/910] ui: `ProgressBarAction` (#1492) * raylib: progress bar * freaking test dir * easier to see * smoother updating * final --------- Co-authored-by: Jason Wen --- system/ui/sunnypilot/widgets/progress_bar.py | 57 ++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 system/ui/sunnypilot/widgets/progress_bar.py diff --git a/system/ui/sunnypilot/widgets/progress_bar.py b/system/ui/sunnypilot/widgets/progress_bar.py new file mode 100644 index 000000000..76f424341 --- /dev/null +++ b/system/ui/sunnypilot/widgets/progress_bar.py @@ -0,0 +1,57 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +import pyray as rl +from openpilot.system.ui.lib.application import gui_app, FontWeight +from openpilot.system.ui.lib.text_measure import measure_text_cached +from openpilot.system.ui.widgets.list_view import ListItem, ItemAction + + +class ProgressBarAction(ItemAction): + def __init__(self, width=600): + super().__init__(width=width) + self.progress = 0.0 + self.text = "" + self.show_progress = False + self.text_color = rl.GRAY + self._font = gui_app.font(FontWeight.NORMAL) + + def update(self, progress, text, show_progress=False, text_color=rl.GRAY): + self.progress = progress + self.text = text + self.show_progress = show_progress + self.text_color = text_color + + def _render(self, rect: rl.Rectangle): + font_size = 40 + text_size = measure_text_cached(self._font, self.text, font_size) + padding = 30 + bar_width = text_size.x + 2 * padding + text_x = (bar_width - text_size.x) / 2 + + if self.show_progress and len(parts := self.text.split(' - ', 1)) == 2: + prefix = parts[0] + max_prefix_w = measure_text_cached(self._font, "100%", font_size).x + current_prefix_w = measure_text_cached(self._font, prefix, font_size).x + + bar_width = (text_size.x - current_prefix_w + max_prefix_w) + 2 * padding + text_x = padding + (max_prefix_w - current_prefix_w) + + bar_height = 60 + bar_rect = rl.Rectangle(rect.x + rect.width - bar_width, rect.y + (rect.height - bar_height) / 2, bar_width, bar_height) + + if self.show_progress: + inner_rect = rl.Rectangle(bar_rect.x + 4, bar_rect.y + 4, bar_rect.width - 8, bar_rect.height - 8) + if inner_rect.width > 0: + fill_width = max(0, min(inner_rect.width, inner_rect.width * (self.progress / 100.0))) + rl.draw_rectangle_rounded(rl.Rectangle(inner_rect.x, inner_rect.y, fill_width, inner_rect.height), 0.2, 10, rl.Color(30, 121, 232, 255)) + + rl.draw_text_ex(self._font, self.text, rl.Vector2(bar_rect.x + text_x, bar_rect.y + (bar_height - text_size.y) / 2), font_size, 0, self.text_color) + + +def progress_item(title): + action = ProgressBarAction() + return ListItem(title=title, action_item=action) From a4454721ea604d5dd1e4c53995ac4130d1b8034c Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sat, 29 Nov 2025 09:52:01 +0100 Subject: [PATCH 477/910] sunnylink: dynamic param metadata (#1522) * feat(params): add support for parameter metadata retrieval - Introduced `getKeyMetadata` method for accessing metadata associated with params. - Enhanced `getParamsAllKeysV1` to include metadata parsing and optional dynamic enum generation. - Extended unit tests to verify metadata parsing, enum mapping, and edge cases. * Revert "feat(params): add support for parameter metadata retrieval" This reverts commit 865b695ff966bfab7dab4d211fb2d91f06fe92db. * update: integrate params metadata management and unit tests - Added `update_params_metadata.py` to manage and update parameters metadata. - Enhanced `getParamsAllKeysV1` to include metadata for params. - Created comprehensive tests (`test_params_metadata.py`, `test_params_sync.py`) to validate metadata integrity and params consistency. * update: improve params metadata readability and enhance enums - Renamed params titles for clarity and consistency. - Added enum options and mappings to selected params for better usability. * update: enhance params metadata with improved enum structures - Replaced plain enum lists with detailed objects (`value`, `label`) for clarity. - Standardized parameter options for consistency across metadata. * update: add validation constraints to params metadata - Introduced `min`, `max`, and `step` attributes for improved parameter range validation. - Enhances user input handling and ensures consistency in metadata. * lint * more lint stuff and permissions * does this suffice? * more lint * update: refine params type hinting and remove unused shebang - Adjusted type annotation in `params_dict` for better compatibility. - Removed unnecessary shebang from `test_params_metadata.py`. * update: expand test coverage for params metadata validation - Added detailed test cases to ensure metadata consistency (`options`, `constraints`, `titles`). - Validates API response alignment with `params_metadata.json`. * the finals * names --------- Co-authored-by: Jason Wen --- sunnypilot/sunnylink/athena/sunnylinkd.py | 32 +- sunnypilot/sunnylink/params_metadata.json | 1100 +++++++++++++++++ .../sunnylink/tests/test_params_metadata.py | 86 ++ .../sunnylink/tests/test_params_sync.py | 202 +++ .../sunnylink/tools/update_params_metadata.py | 56 + 5 files changed, 1469 insertions(+), 7 deletions(-) create mode 100644 sunnypilot/sunnylink/params_metadata.json create mode 100644 sunnypilot/sunnylink/tests/test_params_metadata.py create mode 100644 sunnypilot/sunnylink/tests/test_params_sync.py create mode 100755 sunnypilot/sunnylink/tools/update_params_metadata.py diff --git a/sunnypilot/sunnylink/athena/sunnylinkd.py b/sunnypilot/sunnylink/athena/sunnylinkd.py index 1e3713c7e..a57b6ef55 100755 --- a/sunnypilot/sunnylink/athena/sunnylinkd.py +++ b/sunnypilot/sunnylink/athena/sunnylinkd.py @@ -1,5 +1,10 @@ #!/usr/bin/env python3 +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" from __future__ import annotations import base64 @@ -32,9 +37,11 @@ LOCAL_PORT_WHITELIST = {8022} SUNNYLINK_LOG_ATTR_NAME = "user.sunny.upload" SUNNYLINK_RECONNECT_TIMEOUT_S = 70 # FYI changing this will also would require a change on sidebar.cc DISALLOW_LOG_UPLOAD = threading.Event() +METADATA_PATH = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "params_metadata.json") params = Params() + def handle_long_poll(ws: WebSocket, exit_event: threading.Event | None) -> None: cloudlog.info("sunnylinkd.handle_long_poll started") sm = messaging.SubMaster(['deviceState']) @@ -180,16 +187,30 @@ def getParamsAllKeys() -> list[str]: @dispatcher.add_method def getParamsAllKeysV1() -> dict[str, str]: + try: + with open(METADATA_PATH) as f: + metadata = json.load(f) + except Exception: + cloudlog.exception("sunnylinkd.getParamsAllKeysV1.exception") + metadata = {} + available_keys: list[str] = [k.decode('utf-8') for k in Params().all_keys()] - params_dict: dict[str, list[dict[str, str | bool | int | None]]] = {"params": []} + params_dict: dict[str, list[dict[str, str | bool | int | object | dict | None]]] = {"params": []} for key in available_keys: value = get_param_as_byte(key, get_default=True) - params_dict["params"].append({ + + param_entry = { "key": key, "type": int(params.get_type(key).value), "default_value": base64.b64encode(value).decode('utf-8') if value else None, - }) + } + + if key in metadata: + meta_copy = metadata[key].copy() + param_entry["_extra"] = meta_copy + + params_dict["params"].append(param_entry) return {"keys": json.dumps(params_dict.get("params", []))} @@ -238,10 +259,7 @@ def startLocalProxy(global_end_event: threading.Event, remote_ws_uri: str, local cloudlog.debug("athena.startLocalProxy.starting") ws = create_connection( - remote_ws_uri, - header={"Authorization": f"Bearer {sunnylink_api.get_token()}"}, - enable_multithread=True, - sslopt={"cert_reqs": ssl.CERT_NONE} + remote_ws_uri, header={"Authorization": f"Bearer {sunnylink_api.get_token()}"}, enable_multithread=True, sslopt={"cert_reqs": ssl.CERT_NONE} ) return start_local_proxy_shim(global_end_event, local_port, ws) diff --git a/sunnypilot/sunnylink/params_metadata.json b/sunnypilot/sunnylink/params_metadata.json new file mode 100644 index 000000000..83ec3c7af --- /dev/null +++ b/sunnypilot/sunnylink/params_metadata.json @@ -0,0 +1,1100 @@ +{ + "AccessToken": { + "title": "AccessTokenIsNice", + "description": "" + }, + "AdbEnabled": { + "title": "Enable ADB", + "description": "" + }, + "AlphaLongitudinalEnabled": { + "title": "Alpha Longitudinal", + "description": "" + }, + "AlwaysOnDM": { + "title": "Always-on Driver Monitor", + "description": "" + }, + "ApiCache_Device": { + "title": "Api Cache Device", + "description": "" + }, + "ApiCache_DriveStats": { + "title": "Api Cache Drive Stats", + "description": "" + }, + "ApiCache_FirehoseStats": { + "title": "Firehose Mode Stats", + "description": "" + }, + "AssistNowToken": { + "title": "Assist Now Token", + "description": "" + }, + "AthenadPid": { + "title": "Athenad Pid", + "description": "" + }, + "AthenadRecentlyViewedRoutes": { + "title": "Athenad Recently Viewed Routes", + "description": "" + }, + "AthenadUploadQueue": { + "title": "Athenad Upload Queue", + "description": "" + }, + "AutoLaneChangeBsmDelay": { + "title": "Auto Lane Change BSM Delay", + "description": "" + }, + "AutoLaneChangeTimer": { + "title": "Auto Lane Change Timer", + "description": "", + "options": [ + { + "value": -1, + "label": "Off" + }, + { + "value": 0, + "label": "Nudge" + }, + { + "value": 1, + "label": "Nudgeless" + }, + { + "value": 2, + "label": "0.5s" + }, + { + "value": 3, + "label": "1s" + }, + { + "value": 4, + "label": "2s" + }, + { + "value": 5, + "label": "3s" + } + ] + }, + "BackupManager_CreateBackup": { + "title": "Create Backup", + "description": "" + }, + "BackupManager_RestoreVersion": { + "title": "Restore Version", + "description": "" + }, + "BlindSpot": { + "title": "Blind Spot Detection", + "description": "" + }, + "BlinkerMinLateralControlSpeed": { + "title": "Blinker Min Lateral Control Speed", + "description": "" + }, + "BlinkerPauseLateralControl": { + "title": "Blinker Pause Lateral Control", + "description": "" + }, + "BootCount": { + "title": "Boot Count", + "description": "" + }, + "Brightness": { + "title": "Screen Brightness", + "description": "" + }, + "CalibrationParams": { + "title": "Calibration Params", + "description": "" + }, + "CameraDebugExpGain": { + "title": "Camera Debug Exp Gain", + "description": "" + }, + "CameraDebugExpTime": { + "title": "Camera Debug Exp Time", + "description": "" + }, + "CarBatteryCapacity": { + "title": "Car Battery Capacity", + "description": "" + }, + "CarParams": { + "title": "Car Params", + "description": "" + }, + "CarParamsCache": { + "title": "Car Params Cache", + "description": "" + }, + "CarParamsPersistent": { + "title": "Car Params Persistent", + "description": "" + }, + "CarParamsPrevRoute": { + "title": "Car Params Prev Route", + "description": "" + }, + "CarParamsSP": { + "title": "Car Params Sp", + "description": "" + }, + "CarParamsSPCache": { + "title": "Car Params Sp Cache", + "description": "" + }, + "CarParamsSPPersistent": { + "title": "Car Params Sp Persistent", + "description": "" + }, + "CarPlatformBundle": { + "title": "Car Platform Bundle", + "description": "" + }, + "ChevronInfo": { + "title": "Chevron Info", + "description": "" + }, + "CompletedTrainingVersion": { + "title": "Completed Training Version", + "description": "" + }, + "ControlsReady": { + "title": "Controls Ready", + "description": "" + }, + "CurrentBootlog": { + "title": "Current Bootlog", + "description": "" + }, + "CurrentRoute": { + "title": "Current Route", + "description": "" + }, + "CustomAccIncrementsEnabled": { + "title": "Custom ACC Increments Enabled", + "description": "" + }, + "CustomAccLongPressIncrement": { + "title": "Custom ACC Long Press Increment", + "description": "", + "min": 1, + "max": 10, + "step": 1 + }, + "CustomAccShortPressIncrement": { + "title": "Custom ACC Short Press Increment", + "description": "", + "min": 1, + "max": 10, + "step": 1 + }, + "CustomTorqueParams": { + "title": "Custom Torque Params", + "description": "" + }, + "DevUIInfo": { + "title": "Developer UI Info", + "description": "" + }, + "DeviceBootMode": { + "title": "Device Boot Mode", + "description": "", + "options": [ + { + "value": 0, + "label": "Standard" + }, + { + "value": 1, + "label": "Always Offroad" + } + ] + }, + "DisableLogging": { + "title": "Disable Logging", + "description": "" + }, + "DisablePowerDown": { + "title": "Disable Power Down", + "description": "" + }, + "DisableUpdates": { + "title": "Disable Updates", + "description": "" + }, + "DisengageOnAccelerator": { + "title": "Disengage On Accelerator", + "description": "" + }, + "DoReboot": { + "title": "Reboot", + "description": "" + }, + "DoShutdown": { + "title": "Power Off", + "description": "" + }, + "DoUninstall": { + "title": "Uninstall sunnypilot", + "description": "" + }, + "DongleId": { + "title": "Device ID", + "description": "" + }, + "DriverTooDistracted": { + "title": "Driver Too Distracted", + "description": "" + }, + "DynamicExperimentalControl": { + "title": "Dynamic Experimental Control", + "description": "" + }, + "EnableCopyparty": { + "title": "Enable Copyparty", + "description": "" + }, + "EnableGithubRunner": { + "title": "Enable GitHub Runner", + "description": "" + }, + "EnableSunnylinkUploader": { + "title": "Enable sunnylink Uploader", + "description": "" + }, + "EnforceTorqueControl": { + "title": "Enforce Torque Control", + "description": "" + }, + "ExperimentalMode": { + "title": "Experimental Mode", + "description": "" + }, + "ExperimentalModeConfirmed": { + "title": "Experimental Mode Confirmed", + "description": "" + }, + "FirmwareQueryDone": { + "title": "Firmware Query Done", + "description": "" + }, + "ForcePowerDown": { + "title": "Force Power Down", + "description": "" + }, + "GitBranch": { + "title": "Git Branch", + "description": "" + }, + "GitCommit": { + "title": "Git Commit", + "description": "" + }, + "GitCommitDate": { + "title": "Git Commit Date", + "description": "" + }, + "GitDiff": { + "title": "Git Diff", + "description": "" + }, + "GitRemote": { + "title": "Git Remote", + "description": "" + }, + "GithubRunnerSufficientVoltage": { + "title": "Github Runner Sufficient Voltage", + "description": "" + }, + "GithubSshKeys": { + "title": "Github Ssh Keys", + "description": "" + }, + "GithubUsername": { + "title": "GitHub Username", + "description": "" + }, + "GreenLightAlert": { + "title": "Green Light Alert", + "description": "" + }, + "GsmApn": { + "title": "GSM APN", + "description": "" + }, + "GsmMetered": { + "title": "Gsm Metered", + "description": "" + }, + "GsmRoaming": { + "title": "GSM Roaming", + "description": "" + }, + "HardwareSerial": { + "title": "Serial Number", + "description": "" + }, + "HasAcceptedTerms": { + "title": "Has Accepted Terms", + "description": "" + }, + "HideVEgoUI": { + "title": "Hide vEgo UI", + "description": "" + }, + "HyundaiLongitudinalTuning": { + "title": "Hyundai Longitudinal Tuning", + "description": "", + "options": [ + { + "value": 0, + "label": "Off" + }, + { + "value": 1, + "label": "Dynamic" + }, + { + "value": 2, + "label": "Predictive" + } + ] + }, + "InstallDate": { + "title": "Install Date", + "description": "" + }, + "IntelligentCruiseButtonManagement": { + "title": "Intelligent Cruise Button Management", + "description": "" + }, + "InteractivityTimeout": { + "title": "Interactivity Timeout", + "description": "" + }, + "IsDevelopmentBranch": { + "title": "Is Development Branch", + "description": "" + }, + "IsDriverViewEnabled": { + "title": "Is Driver View Enabled", + "description": "" + }, + "IsEngaged": { + "title": "Is Engaged", + "description": "" + }, + "IsLdwEnabled": { + "title": "Lane Departure Warnings", + "description": "" + }, + "IsMetric": { + "title": "Use Metric Units", + "description": "" + }, + "IsOffroad": { + "title": "Is Offroad", + "description": "" + }, + "IsOnroad": { + "title": "Is Onroad", + "description": "" + }, + "IsReleaseBranch": { + "title": "Is Release Branch", + "description": "" + }, + "IsReleaseSpBranch": { + "title": "Is Release Sp Branch", + "description": "" + }, + "IsRhdDetected": { + "title": "Is Rhd Detected", + "description": "" + }, + "IsTakingSnapshot": { + "title": "Is Taking Snapshot", + "description": "" + }, + "IsTestedBranch": { + "title": "Is Tested Branch", + "description": "" + }, + "JoystickDebugMode": { + "title": "Joystick Debug Mode", + "description": "" + }, + "LagdToggle": { + "title": "LaGD Toggle", + "description": "" + }, + "LagdToggleDelay": { + "title": "LaGD Toggle Delay", + "description": "" + }, + "LagdValueCache": { + "title": "LaGD Value Cache", + "description": "" + }, + "LaneTurnDesire": { + "title": "Lane Turn Desire", + "description": "" + }, + "LaneTurnValue": { + "title": "Lane Turn Value", + "description": "", + "min": 0, + "max": 20, + "step": 1 + }, + "LanguageSetting": { + "title": "Language", + "description": "" + }, + "LastAthenaPingTime": { + "title": "Last Athena Ping Time", + "description": "" + }, + "LastGPSPosition": { + "title": "Last Gps Position", + "description": "" + }, + "LastGPSPositionLLK": { + "title": "Last GPS Position LLK", + "description": "" + }, + "LastManagerExitReason": { + "title": "Last Manager Exit Reason", + "description": "" + }, + "LastOffroadStatusPacket": { + "title": "Last Offroad Status Packet", + "description": "" + }, + "LastPowerDropDetected": { + "title": "Last Power Drop Detected", + "description": "" + }, + "LastSunnylinkPingTime": { + "title": "Last sunnylink Ping Time", + "description": "" + }, + "LastUpdateException": { + "title": "Last Update Exception", + "description": "" + }, + "LastUpdateRouteCount": { + "title": "Last Update Route Count", + "description": "" + }, + "LastUpdateTime": { + "title": "Last Update Time", + "description": "" + }, + "LastUpdateUptimeOnroad": { + "title": "Last Update Uptime Onroad", + "description": "" + }, + "LeadDepartAlert": { + "title": "Lead Depart Alert", + "description": "" + }, + "LiveDelay": { + "title": "Live Delay", + "description": "" + }, + "LiveParameters": { + "title": "Live Parameters", + "description": "" + }, + "LiveParametersV2": { + "title": "Live Parameters V2", + "description": "" + }, + "LiveTorqueParameters": { + "title": "Live Torque Parameters", + "description": "" + }, + "LiveTorqueParamsRelaxedToggle": { + "title": "Live Torque Params Relaxed Toggle", + "description": "" + }, + "LiveTorqueParamsToggle": { + "title": "Live Torque Params Toggle", + "description": "" + }, + "LocationFilterInitialState": { + "title": "Location Filter Initial State", + "description": "" + }, + "LongitudinalManeuverMode": { + "title": "Longitudinal Maneuver Mode", + "description": "" + }, + "LongitudinalPersonality": { + "title": "Driving Personality", + "description": "", + "options": [ + { + "value": 0, + "label": "Aggressive" + }, + { + "value": 1, + "label": "Standard" + }, + { + "value": 2, + "label": "Relaxed" + } + ] + }, + "Mads": { + "title": "MADS Enabled", + "description": "" + }, + "MadsMainCruiseAllowed": { + "title": "MADS Main Cruise Allowed", + "description": "" + }, + "MadsSteeringMode": { + "title": "MADS Steering Mode", + "description": "", + "options": [ + { + "value": 0, + "label": "Remain Active" + }, + { + "value": 1, + "label": "Pause" + }, + { + "value": 2, + "label": "Disengage" + } + ] + }, + "MadsUnifiedEngagementMode": { + "title": "MADS Unified Engagement Mode", + "description": "" + }, + "MapAdvisorySpeedLimit": { + "title": "Map Advisory Speed Limit", + "description": "" + }, + "MapSpeedLimit": { + "title": "Map Speed Limit", + "description": "" + }, + "MapTargetVelocities": { + "title": "Map Target Velocities", + "description": "" + }, + "MapdVersion": { + "title": "Mapd Version", + "description": "" + }, + "MaxTimeOffroad": { + "title": "Max Time Offroad", + "description": "" + }, + "ModelManager_ActiveBundle": { + "title": "Model Manager Active Bundle", + "description": "" + }, + "ModelManager_ClearCache": { + "title": "Model Manager Clear Cache", + "description": "" + }, + "ModelManager_DownloadIndex": { + "title": "Model Manager Download Index", + "description": "" + }, + "ModelManager_Favs": { + "title": "Model Manager Favorites", + "description": "" + }, + "ModelManager_LastSyncTime": { + "title": "Model Manager Last Sync Time", + "description": "" + }, + "ModelManager_ModelsCache": { + "title": "Model Manager Models Cache", + "description": "" + }, + "ModelRunnerTypeCache": { + "title": "Model Runner Type Cache", + "description": "" + }, + "NetworkMetered": { + "title": "Network Usage", + "description": "", + "options": [ + { + "value": 0, + "label": "Default" + }, + { + "value": 1, + "label": "Metered" + }, + { + "value": 2, + "label": "Unmetered" + } + ] + }, + "NeuralNetworkLateralControl": { + "title": "Neural Network Lateral Control", + "description": "" + }, + "NextMapSpeedLimit": { + "title": "Next Map Speed Limit", + "description": "" + }, + "OSMDownloadBounds": { + "title": "OSM Download Bounds", + "description": "" + }, + "OSMDownloadLocations": { + "title": "OSM Download Locations", + "description": "" + }, + "OSMDownloadProgress": { + "title": "OSM Download Progress", + "description": "" + }, + "ObdMultiplexingChanged": { + "title": "Obd Multiplexing Changed", + "description": "" + }, + "ObdMultiplexingEnabled": { + "title": "Obd Multiplexing Enabled", + "description": "" + }, + "OffroadMode": { + "title": "Offroad Mode", + "description": "" + }, + "Offroad_CarUnrecognized": { + "title": "Offroad Car Unrecognized", + "description": "" + }, + "Offroad_ConnectivityNeeded": { + "title": "Offroad Connectivity Needed", + "description": "" + }, + "Offroad_ConnectivityNeededPrompt": { + "title": "Offroad Connectivity Needed Prompt", + "description": "" + }, + "Offroad_DriverMonitoringUncertain": { + "title": "Offroad Driver Monitoring Uncertain", + "description": "" + }, + "Offroad_ExcessiveActuation": { + "title": "Offroad Excessive Actuation", + "description": "" + }, + "Offroad_IsTakingSnapshot": { + "title": "Offroad Is Taking Snapshot", + "description": "" + }, + "Offroad_NeosUpdate": { + "title": "Offroad Neos Update", + "description": "" + }, + "Offroad_NoFirmware": { + "title": "Offroad No Firmware", + "description": "" + }, + "Offroad_OSMUpdateRequired": { + "title": "Offroad OSM Update Required", + "description": "" + }, + "Offroad_Recalibration": { + "title": "Offroad Recalibration", + "description": "" + }, + "Offroad_TemperatureTooHigh": { + "title": "Offroad Temperature Too High", + "description": "" + }, + "Offroad_TiciSupport": { + "title": "Offroad Tici Support", + "description": "" + }, + "Offroad_UnregisteredHardware": { + "title": "Offroad Unregistered Hardware", + "description": "" + }, + "Offroad_UpdateFailed": { + "title": "Offroad Update Failed", + "description": "" + }, + "OnroadCycleRequested": { + "title": "Onroad Cycle Requested", + "description": "" + }, + "OnroadScreenOffBrightness": { + "title": "Onroad Screen Off Brightness", + "description": "", + "min": 0, + "max": 100, + "step": 5 + }, + "OnroadScreenOffControl": { + "title": "Onroad Screen Off Control", + "description": "" + }, + "OnroadScreenOffTimer": { + "title": "Onroad Screen Off Timer", + "description": "", + "min": 0, + "max": 60, + "step": 1 + }, + "OnroadUploads": { + "title": "Onroad Uploads", + "description": "" + }, + "OpenpilotEnabledToggle": { + "title": "Enable sunnypilot", + "description": "" + }, + "OsmDbUpdatesCheck": { + "title": "OSM DB Updates Check", + "description": "" + }, + "OsmDownloadedDate": { + "title": "OSM Downloaded Date", + "description": "" + }, + "OsmLocal": { + "title": "OSM Local", + "description": "" + }, + "OsmLocationName": { + "title": "OSM Location Name", + "description": "" + }, + "OsmLocationTitle": { + "title": "OSM Location Title", + "description": "" + }, + "OsmLocationUrl": { + "title": "OSM Location URL", + "description": "" + }, + "OsmStateName": { + "title": "OSM State Name", + "description": "" + }, + "OsmStateTitle": { + "title": "OSM State Title", + "description": "" + }, + "OsmWayTest": { + "title": "OSM Way Test", + "description": "" + }, + "PandaHeartbeatLost": { + "title": "Panda Heartbeat Lost", + "description": "" + }, + "PandaSignatures": { + "title": "Panda Signatures", + "description": "" + }, + "PandaSomResetTriggered": { + "title": "Panda Som Reset Triggered", + "description": "" + }, + "PrimeType": { + "title": "Prime Type", + "description": "" + }, + "QuickBootToggle": { + "title": "Quick Boot", + "description": "" + }, + "QuietMode": { + "title": "Quiet Mode", + "description": "" + }, + "RainbowMode": { + "title": "Rainbow Mode", + "description": "" + }, + "RecordAudio": { + "title": "Record & Upload Mic Audio", + "description": "" + }, + "RecordAudioFeedback": { + "title": "Record Audio Feedback", + "description": "" + }, + "RecordFront": { + "title": "Record & Upload Driver Camera", + "description": "" + }, + "RecordFrontLock": { + "title": "Record Front Lock", + "description": "" + }, + "RoadName": { + "title": "Road Name", + "description": "" + }, + "RoadNameToggle": { + "title": "Road Name Toggle", + "description": "" + }, + "RouteCount": { + "title": "Route Count", + "description": "" + }, + "SecOCKey": { + "title": "Sec Oc Key", + "description": "" + }, + "ShowAdvancedControls": { + "title": "Show Advanced Controls", + "description": "" + }, + "ShowDebugInfo": { + "title": "UI Debug Mode", + "description": "" + }, + "ShowTurnSignals": { + "title": "Show Turn Signals", + "description": "" + }, + "SmartCruiseControlMap": { + "title": "Smart Cruise Control Map", + "description": "" + }, + "SmartCruiseControlVision": { + "title": "Smart Cruise Control Vision", + "description": "" + }, + "SnoozeUpdate": { + "title": "Snooze Update", + "description": "" + }, + "SpeedLimitMode": { + "title": "Speed Limit Mode", + "description": "", + "options": [ + { + "value": 0, + "label": "Off" + }, + { + "value": 1, + "label": "Information" + }, + { + "value": 2, + "label": "Warning" + }, + { + "value": 3, + "label": "Assist" + } + ] + }, + "SpeedLimitOffsetType": { + "title": "Speed Limit Offset Type", + "description": "", + "options": [ + { + "value": 0, + "label": "Off" + }, + { + "value": 1, + "label": "Fixed" + }, + { + "value": 2, + "label": "Percentage" + } + ] + }, + "SpeedLimitPolicy": { + "title": "Speed Limit Policy", + "description": "", + "options": [ + { + "value": 0, + "label": "Car State Only" + }, + { + "value": 1, + "label": "Map Data Only" + }, + { + "value": 2, + "label": "Car State Priority" + }, + { + "value": 3, + "label": "Map Data Priority" + }, + { + "value": 4, + "label": "Combined" + } + ] + }, + "SpeedLimitValueOffset": { + "title": "Speed Limit Value Offset", + "description": "", + "min": -30, + "max": 30, + "step": 1 + }, + "SshEnabled": { + "title": "Enable SSH", + "description": "" + }, + "StandstillTimer": { + "title": "Standstill Timer", + "description": "" + }, + "SubaruStopAndGo": { + "title": "Subaru Stop and Go", + "description": "" + }, + "SubaruStopAndGoManualParkingBrake": { + "title": "Subaru Stop and Go Manual Parking Brake", + "description": "" + }, + "SunnylinkCache_Roles": { + "title": "sunnylink Cache Roles", + "description": "" + }, + "SunnylinkCache_Users": { + "title": "sunnylink Cache Users", + "description": "" + }, + "SunnylinkDongleId": { + "title": "sunnylink Dongle ID", + "description": "" + }, + "SunnylinkEnabled": { + "title": "sunnylink Enabled", + "description": "" + }, + "SunnylinkTempFault": { + "title": "sunnylink Temp Fault", + "description": "" + }, + "SunnylinkdPid": { + "title": "Sunnylinkd Pid", + "description": "" + }, + "TermsVersion": { + "title": "Terms Version", + "description": "" + }, + "TeslaCoopSteering": { + "title": "Tesla Coop Steering", + "description": "" + }, + "TorqueParamsOverrideEnabled": { + "title": "Torque Params Override Enabled", + "description": "" + }, + "TorqueParamsOverrideFriction": { + "title": "Torque Params Override Friction", + "description": "", + "min": 0.0, + "max": 1.0, + "step": 0.01 + }, + "TorqueParamsOverrideLatAccelFactor": { + "title": "Torque Params Override Lat Accel Factor", + "description": "", + "min": 0.1, + "max": 5.0, + "step": 0.1 + }, + "TrainingVersion": { + "title": "Training Version", + "description": "" + }, + "TrueVEgoUI": { + "title": "True vEgo UI", + "description": "" + }, + "UbloxAvailable": { + "title": "Ublox Available", + "description": "" + }, + "UpdateAvailable": { + "title": "Update Available", + "description": "" + }, + "UpdateFailedCount": { + "title": "Update Failed Count", + "description": "" + }, + "UpdaterAvailableBranches": { + "title": "Updater Available Branches", + "description": "" + }, + "UpdaterCurrentDescription": { + "title": "Updater Current Description", + "description": "" + }, + "UpdaterCurrentReleaseNotes": { + "title": "Updater Current Release Notes", + "description": "" + }, + "UpdaterFetchAvailable": { + "title": "Updater Fetch Available", + "description": "" + }, + "UpdaterLastFetchTime": { + "title": "Updater Last Fetch Time", + "description": "" + }, + "UpdaterNewDescription": { + "title": "Updater New Description", + "description": "" + }, + "UpdaterNewReleaseNotes": { + "title": "Updater New Release Notes", + "description": "" + }, + "UpdaterState": { + "title": "Updater State", + "description": "" + }, + "UpdaterTargetBranch": { + "title": "Updater Target Branch", + "description": "" + }, + "UptimeOffroad": { + "title": "Uptime Offroad", + "description": "" + }, + "UptimeOnroad": { + "title": "Uptime Onroad", + "description": "" + }, + "Version": { + "title": "openpilot Version", + "description": "" + } +} diff --git a/sunnypilot/sunnylink/tests/test_params_metadata.py b/sunnypilot/sunnylink/tests/test_params_metadata.py new file mode 100644 index 000000000..f4f1fbc4b --- /dev/null +++ b/sunnypilot/sunnylink/tests/test_params_metadata.py @@ -0,0 +1,86 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +import json + +from openpilot.sunnypilot.sunnylink.athena.sunnylinkd import getParamsAllKeysV1, METADATA_PATH + + +def test_get_params_all_keys_v1(): + """ + Test the getParamsAllKeysV1 API endpoint. + + Why: + This endpoint is used by the UI (and potentially external tools) to fetch the list of + available parameters along with their metadata (titles, descriptions, options, constraints). + We need to ensure it returns the correct structure and that the metadata from + params_metadata.json is correctly merged into the response. + + Expected: + - The response should contain a "keys" field which is a JSON string of a list of parameters. + - Each parameter object should have "key", "type", "default_value", and optionally "_extra". + - The "_extra" field should contain the rich metadata (title, options, min/max, etc.) matching + the source of truth (params_metadata.json). + """ + response = getParamsAllKeysV1() + assert "keys" in response + + keys_json = response["keys"] + params_list = json.loads(keys_json) + + assert isinstance(params_list, list) + assert len(params_list) > 0 + + # Check structure of first item + first_param = params_list[0] + assert "key" in first_param + assert "type" in first_param + assert "default_value" in first_param + + if "_extra" in first_param: + assert isinstance(first_param["_extra"], dict) + assert "default" not in first_param["_extra"] + assert "type" not in first_param["_extra"] + + # Load the source of truth + with open(METADATA_PATH) as f: + metadata = json.load(f) + + # Verify that the API response matches the metadata file for a few sample keys + # This ensures the plumbing is working without being brittle to content changes + + # 1. Check a key that should have metadata + keys_with_metadata = [k for k in params_list if k["key"] in metadata] + assert len(keys_with_metadata) > 0, "No parameters found that match metadata keys" + + for param in keys_with_metadata[:5]: # Check first 5 matches + key = param["key"] + expected_meta = metadata[key] + + assert "_extra" in param, f"Parameter {key} should have _extra field" + actual_meta = param["_extra"] + + # Verify all fields in JSON are present in the API response + for meta_key, meta_val in expected_meta.items(): + assert meta_key in actual_meta, f"Missing {meta_key} in API response for {key}" + assert actual_meta[meta_key] == meta_val, f"Mismatch for {key}.{meta_key}: expected {meta_val}, got {actual_meta[meta_key]}" + + # 2. Check that we are correctly serving options if they exist + params_with_options = [k for k in keys_with_metadata if "options" in k.get("_extra", {})] + if params_with_options: + param = params_with_options[0] + key = param["key"] + assert isinstance(param["_extra"]["options"], list), f"Options for {key} should be a list" + assert param["_extra"]["options"] == metadata[key]["options"] + + # 3. Check that we are correctly serving numeric constraints if they exist + params_with_constraints = [k for k in keys_with_metadata if "min" in k.get("_extra", {})] + if params_with_constraints: + param = params_with_constraints[0] + key = param["key"] + assert param["_extra"]["min"] == metadata[key]["min"] + assert param["_extra"]["max"] == metadata[key]["max"] + assert param["_extra"]["step"] == metadata[key]["step"] diff --git a/sunnypilot/sunnylink/tests/test_params_sync.py b/sunnypilot/sunnylink/tests/test_params_sync.py new file mode 100644 index 000000000..26bdca42d --- /dev/null +++ b/sunnypilot/sunnylink/tests/test_params_sync.py @@ -0,0 +1,202 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +import json +import os +import pytest + +from openpilot.common.params import Params +from openpilot.sunnypilot.sunnylink.athena.sunnylinkd import METADATA_PATH + + +def test_metadata_json_exists(): + """ + Test that the params_metadata.json file exists at the expected path. + + Why: + The metadata file is the source of truth for parameter descriptions, options, and constraints. + If it's missing, the UI will not be able to display rich information for parameters. + + Expected: + The file should exist at sunnypilot/sunnylink/params_metadata.json. + """ + assert os.path.exists(METADATA_PATH), f"Metadata file not found at {METADATA_PATH}" + + +def test_metadata_json_valid(): + """ + Test that the params_metadata.json file contains valid JSON. + + Why: + Invalid JSON will cause the metadata loading to fail, potentially crashing the UI or + resulting in missing metadata. + + Expected: + The file content should be parseable as a JSON object (dictionary). + """ + with open(METADATA_PATH) as f: + try: + data = json.load(f) + except json.JSONDecodeError: + pytest.fail("Metadata file is not valid JSON") + + assert isinstance(data, dict), "Metadata root must be a dictionary" + + +def test_all_params_have_metadata(): + """ + Test that every parameter in the codebase has a corresponding entry in params_metadata.json. + + Why: + We want to ensure 100% coverage of parameter metadata. Any parameter added to the codebase + should also be documented in the metadata file. + + Expected: + There should be no parameters in Params() that are missing from the metadata file. + If this fails, run 'python3 sunnypilot/sunnylink/tools/update_params_metadata.py'. + """ + params = Params() + all_keys = [k.decode('utf-8') for k in params.all_keys()] + + with open(METADATA_PATH) as f: + metadata = json.load(f) + + missing_keys = [key for key in all_keys if key not in metadata] + + if missing_keys: + pytest.fail( + f"The following parameters are missing from metadata: {missing_keys}. " + + "Please run 'python3 sunnypilot/sunnylink/tools/update_params_metadata.py' to update." + ) + + +def test_metadata_keys_exist_in_params(): + """ + Test that all keys in params_metadata.json actually exist in the codebase. + + Why: + We want to avoid stale metadata for parameters that have been removed or renamed. + This keeps the metadata file clean and relevant. + + Expected: + There should be no keys in the metadata file that are not present in Params(). + This prints a warning rather than failing, as it's less critical than missing metadata. + """ + params = Params() + all_keys = {k.decode('utf-8') for k in params.all_keys()} + + with open(METADATA_PATH) as f: + metadata = json.load(f) + + extra_keys = [key for key in metadata.keys() if key not in all_keys] + + if extra_keys: + print(f"Warning: The following keys in metadata do not exist in Params: {extra_keys}") + + +def test_no_default_titles(): + """ + Test that no parameter has a title that is identical to its key. + + Why: + The default behavior of the update script is to set the title equal to the key. + We want to force developers to provide human-readable, descriptive titles for all parameters. + + Expected: + No parameter metadata should have 'title' == 'key'. + """ + with open(METADATA_PATH) as f: + metadata = json.load(f) + + default_title_keys = [key for key, meta in metadata.items() if meta.get("title") == key] + + if default_title_keys: + pytest.fail( + f"The following parameters have default titles (title == key): {default_title_keys}. " + + "Please update 'params_metadata.json' with descriptive titles." + ) + + +def test_options_structure(): + """ + Test that the 'options' field in metadata follows the correct structure. + + Why: + The UI expects 'options' to be a list of objects with 'value' and 'label' keys. + Incorrect structure will break the UI rendering for dropdowns/toggles. + + Expected: + If 'options' is present, it must be a list of dicts, and each dict must have 'value' and 'label'. + """ + with open(METADATA_PATH) as f: + metadata = json.load(f) + + for key, meta in metadata.items(): + if "options" in meta: + options = meta["options"] + assert isinstance(options, list), f"Options for {key} must be a list" + for option in options: + assert isinstance(option, dict), f"Option in {key} must be a dictionary" + assert "value" in option, f"Option in {key} must have a 'value' key" + assert "label" in option, f"Option in {key} must have a 'label' key" + + +def test_numeric_constraints(): + """ + Test that numeric parameters have valid 'min', 'max', and 'step' constraints. + + Why: + The UI uses these constraints to validate user input and render sliders/steppers. + Missing or invalid constraints can lead to UI bugs or invalid parameter values. + + Expected: + If any of min/max/step is present, ALL of them must be present. + They must be numbers (int/float), and min must be less than max. + """ + with open(METADATA_PATH) as f: + metadata = json.load(f) + + for key, meta in metadata.items(): + if "min" in meta or "max" in meta or "step" in meta: + assert "min" in meta, f"Numeric param {key} must have 'min'" + assert "max" in meta, f"Numeric param {key} must have 'max'" + assert "step" in meta, f"Numeric param {key} must have 'step'" + + assert isinstance(meta["min"], (int, float)), f"Min for {key} must be number" + assert isinstance(meta["max"], (int, float)), f"Max for {key} must be number" + assert isinstance(meta["step"], (int, float)), f"Step for {key} must be number" + assert meta["min"] < meta["max"], f"Min must be less than max for {key}" + + +def test_known_params_metadata(): + """ + Test specific known parameters to ensure they have the expected rich metadata. + + Why: + This acts as a spot check to ensure that our rich metadata population logic is working correctly + and that critical parameters (like LongitudinalPersonality) have their options and constraints preserved. + + Expected: + 'LongitudinalPersonality' should have 3 options (Aggressive, Standard, Relaxed). + 'CustomAccLongPressIncrement' should have min=1, max=10, step=1. + """ + with open(METADATA_PATH) as f: + metadata = json.load(f) + + # Check an enum-like param + lp = metadata.get("LongitudinalPersonality") + assert lp is not None + assert "options" in lp + assert len(lp["options"]) == 3 + assert lp["options"][0]["label"] == "Aggressive" + assert lp["options"][0]["value"] == 0 + + # Check a numeric param + acc_long = metadata.get("CustomAccLongPressIncrement") + assert acc_long is not None + assert acc_long["min"] == 1 + assert acc_long["max"] == 10 + assert acc_long["step"] == 1 diff --git a/sunnypilot/sunnylink/tools/update_params_metadata.py b/sunnypilot/sunnylink/tools/update_params_metadata.py new file mode 100755 index 000000000..ac2ef556e --- /dev/null +++ b/sunnypilot/sunnylink/tools/update_params_metadata.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +import json +import os + +from openpilot.common.params import Params + +METADATA_PATH = os.path.join(os.path.dirname(__file__), "../params_metadata.json") + + +def main(): + params = Params() + all_keys = params.all_keys() + + if os.path.exists(METADATA_PATH): + with open(METADATA_PATH) as f: + try: + data = json.load(f) + except json.JSONDecodeError: + data = {} + else: + data = {} + + # Add new keys + for key in all_keys: + key_str = key.decode("utf-8") + if key_str not in data: + print(f"Adding new key: {key_str}") + data[key_str] = { + "title": key_str, + "description": "", + } + + # Remove deleted keys + # keys_to_remove = [k for k in data.keys() if k.encode("utf-8") not in all_keys] + # for k in keys_to_remove: + # print(f"Removing deleted key: {k}") + # del data[k] + + # Sort keys + sorted_data = dict(sorted(data.items())) + + with open(METADATA_PATH, "w") as f: + json.dump(sorted_data, f, indent=2) + f.write("\n") + + print(f"Updated {METADATA_PATH}") + + +if __name__ == "__main__": + main() From d8c316faef4bf26f5e433ff480cad8cce7f7fbc2 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 29 Nov 2025 00:56:43 -0800 Subject: [PATCH 478/910] Fix wifi settings NavWidget --- selfdrive/ui/mici/layouts/settings/network/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/selfdrive/ui/mici/layouts/settings/network/__init__.py b/selfdrive/ui/mici/layouts/settings/network/__init__.py index 8c27d87d4..d085fdf55 100644 --- a/selfdrive/ui/mici/layouts/settings/network/__init__.py +++ b/selfdrive/ui/mici/layouts/settings/network/__init__.py @@ -112,6 +112,8 @@ class NetworkLayoutMici(NavWidget): self.set_back_callback(back_callback) def _update_state(self): + super()._update_state() + # If not using prime SIM, show GSM settings and enable IPv4 forwarding show_cell_settings = ui_state.prime_state.get_type() in (PrimeType.NONE, PrimeType.LITE) self._wifi_manager.set_ipv4_forward(show_cell_settings) From 023b842e3cb346b6740a223a9ae91abc1d515b5c Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 29 Nov 2025 03:58:58 -0500 Subject: [PATCH 479/910] controlsd_ext: use time.monotonic to check params intervals (#1481) * controlsd_ext: use time.monotonic to check params intervals * test * Revert "test" This reverts commit 151ac3bc6812cdd1693c1a7650f44bb3bcd8910e. --- selfdrive/controls/controlsd.py | 36 +++++-------------- .../selfdrive/controls/controlsd_ext.py | 20 ++++++++--- 2 files changed, 24 insertions(+), 32 deletions(-) diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index eaaf5a51e..9d0e5c9f1 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -1,7 +1,5 @@ #!/usr/bin/env python3 import math -import threading -import time from numbers import Number from cereal import car, log @@ -22,8 +20,6 @@ from openpilot.selfdrive.controls.lib.longcontrol import LongControl from openpilot.selfdrive.modeld.modeld import LAT_SMOOTH_SECONDS from openpilot.selfdrive.locationd.helpers import PoseCalibrator, Pose -from openpilot.sunnypilot.livedelay.helpers import get_lat_delay -from openpilot.sunnypilot.modeld.modeld_base import ModelStateBase from openpilot.sunnypilot.selfdrive.controls.controlsd_ext import ControlsExt State = log.SelfdriveState.OpenpilotState @@ -33,7 +29,7 @@ LaneChangeDirection = log.LaneChangeDirection ACTUATOR_FIELDS = tuple(car.CarControl.Actuators.schema.fields.keys()) -class Controls(ControlsExt, ModelStateBase): +class Controls(ControlsExt): def __init__(self) -> None: self.params = Params() cloudlog.info("controlsd is waiting for CarParams") @@ -42,7 +38,6 @@ class Controls(ControlsExt, ModelStateBase): # Initialize sunnypilot controlsd extension and base model state ControlsExt.__init__(self, self.CP, self.params) - ModelStateBase.__init__(self) self.CI = interfaces[self.CP.carFingerprint](self.CP, self.CP_SP) @@ -231,30 +226,15 @@ class Controls(ControlsExt, ModelStateBase): cc_send.carControl = CC self.pm.send('carControl', cc_send) - def params_thread(self, evt): - while not evt.is_set(): - self.get_params_sp() - - if self.CP.lateralTuning.which() == 'torque': - self.lat_delay = get_lat_delay(self.params, self.sm["liveDelay"].lateralDelay) - - time.sleep(0.1) - def run(self): rk = Ratekeeper(100, print_delay_threshold=None) - e = threading.Event() - t = threading.Thread(target=self.params_thread, args=(e,)) - try: - t.start() - while True: - self.update() - CC, lac_log = self.state_control() - self.publish(CC, lac_log) - self.run_ext(self.sm, self.pm) - rk.monitor_time() - finally: - e.set() - t.join() + while True: + self.update() + CC, lac_log = self.state_control() + self.publish(CC, lac_log) + self.get_params_sp(self.sm) + self.run_ext(self.sm, self.pm) + rk.monitor_time() def main(): diff --git a/sunnypilot/selfdrive/controls/controlsd_ext.py b/sunnypilot/selfdrive/controls/controlsd_ext.py index 8caeeaeab..3f6053d15 100644 --- a/sunnypilot/selfdrive/controls/controlsd_ext.py +++ b/sunnypilot/selfdrive/controls/controlsd_ext.py @@ -4,21 +4,27 @@ Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. This file is part of sunnypilot and is licensed under the MIT License. See the LICENSE.md file in the root directory for more details. """ +import time + import cereal.messaging as messaging from cereal import log, custom from opendbc.car import structs from openpilot.common.params import Params from openpilot.common.swaglog import cloudlog +from openpilot.sunnypilot import PARAMS_UPDATE_PERIOD +from openpilot.sunnypilot.livedelay.helpers import get_lat_delay +from openpilot.sunnypilot.modeld.modeld_base import ModelStateBase from openpilot.sunnypilot.selfdrive.controls.lib.blinker_pause_lateral import BlinkerPauseLateral -class ControlsExt: +class ControlsExt(ModelStateBase): def __init__(self, CP: structs.CarParams, params: Params): + ModelStateBase.__init__(self) self.CP = CP self.params = params + self._param_update_time: float = 0.0 self.blinker_pause_lateral = BlinkerPauseLateral() - self.get_params_sp() cloudlog.info("controlsd_ext is waiting for CarParamsSP") self.CP_SP = messaging.log_from_bytes(params.get("CarParamsSP", block=True), custom.CarParamsSP) @@ -27,8 +33,14 @@ class ControlsExt: self.sm_services_ext = ['radarState', 'selfdriveStateSP'] self.pm_services_ext = ['carControlSP'] - def get_params_sp(self) -> None: - self.blinker_pause_lateral.get_params() + def get_params_sp(self, sm: messaging.SubMaster) -> None: + if time.monotonic() - self._param_update_time > PARAMS_UPDATE_PERIOD: + self.blinker_pause_lateral.get_params() + + if self.CP.lateralTuning.which() == 'torque': + self.lat_delay = get_lat_delay(self.params, sm["liveDelay"].lateralDelay) + + self._param_update_time = time.monotonic() def get_lat_active(self, sm: messaging.SubMaster) -> bool: if self.blinker_pause_lateral.update(sm['carState']): From ebca4fc90117b986f20868f62474a91a5e07151c Mon Sep 17 00:00:00 2001 From: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Sat, 29 Nov 2025 01:19:48 -0800 Subject: [PATCH 480/910] ui: fuzzy search helper (#1507) * ui: fuzzy search helper * final --------- Co-authored-by: Jason Wen --- .../widgets/helpers/fuzzy_search.py | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 system/ui/sunnypilot/widgets/helpers/fuzzy_search.py diff --git a/system/ui/sunnypilot/widgets/helpers/fuzzy_search.py b/system/ui/sunnypilot/widgets/helpers/fuzzy_search.py new file mode 100644 index 000000000..8d0f6bd59 --- /dev/null +++ b/system/ui/sunnypilot/widgets/helpers/fuzzy_search.py @@ -0,0 +1,40 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +import re +import unicodedata + + +def normalize(text: str) -> str: + return unicodedata.normalize('NFKD', text).encode('ascii', 'ignore').decode('utf-8').lower() + + +def search_from_list(query: str, items: list[str]) -> list[str]: + if not query: + return items + + normalized_query = normalize(query) + search_terms = [re.sub(r'[^a-z0-9]', '', term) for term in normalized_query.split() if term.strip()] + + results = [] + for item in items: + normalized_item = normalize(item) + item_with_spaces = re.sub(r'[^a-z0-9\s]', ' ', normalized_item) + item_stripped = re.sub(r'[^a-z0-9]', '', normalized_item) + + all_terms_match = True + for term in search_terms: + if not term: + continue + + if term not in item_with_spaces and term not in item_stripped: + all_terms_match = False + break + + if all_terms_match: + results.append(item) + + return results From d6de3572cad975cb24bf64c8978f3521e88d36cb Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 29 Nov 2025 01:26:17 -0800 Subject: [PATCH 481/910] UnifiedLabel: split render (#36719) * split * rect --- system/ui/widgets/label.py | 85 ++++++++++++++++++++------------------ 1 file changed, 44 insertions(+), 41 deletions(-) diff --git a/system/ui/widgets/label.py b/system/ui/widgets/label.py index c90b111de..432f21e59 100644 --- a/system/ui/widgets/label.py +++ b/system/ui/widgets/label.py @@ -599,13 +599,13 @@ class UnifiedLabel(Widget): return self._cached_total_height return 0.0 - def _render(self, rect: rl.Rectangle): + def _render(self, _): """Render the label.""" - if rect.width <= 0 or rect.height <= 0: + if self._rect.width <= 0 or self._rect.height <= 0: return # Determine available width - available_width = rect.width + available_width = self._rect.width if self._max_width is not None: available_width = min(available_width, self._max_width) @@ -633,7 +633,7 @@ class UnifiedLabel(Widget): line_height_needed = size.y * self._line_height # Check if this line fits - if current_height + line_height_needed > rect.height: + if current_height + line_height_needed > self._rect.height: # This line doesn't fit if len(visible_lines) == 0: # First line doesn't fit by height - still show it (will be clipped by scissor if needed) @@ -677,51 +677,54 @@ class UnifiedLabel(Widget): # Calculate vertical alignment offset if self._alignment_vertical == rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP: - start_y = rect.y + start_y = self._rect.y elif self._alignment_vertical == rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM: - start_y = rect.y + rect.height - total_visible_height + start_y = self._rect.y + self._rect.height - total_visible_height else: # TEXT_ALIGN_MIDDLE - start_y = rect.y + (rect.height - total_visible_height) / 2 + start_y = self._rect.y + (self._rect.height - total_visible_height) / 2 # Render each line current_y = start_y for idx, (line, size, emojis) in enumerate(zip(visible_lines, visible_sizes, visible_emojis, strict=True)): - # Calculate horizontal position - if self._alignment == rl.GuiTextAlignment.TEXT_ALIGN_LEFT: - line_x = rect.x + self._text_padding - elif self._alignment == rl.GuiTextAlignment.TEXT_ALIGN_CENTER: - line_x = rect.x + (rect.width - size.x) / 2 - elif self._alignment == rl.GuiTextAlignment.TEXT_ALIGN_RIGHT: - line_x = rect.x + rect.width - size.x - self._text_padding - else: - line_x = rect.x + self._text_padding - - # Render line with emojis - line_pos = rl.Vector2(line_x, current_y) - prev_index = 0 - - for start, end, emoji in emojis: - # Draw text before emoji - text_before = line[prev_index:start] - if text_before: - rl.draw_text_ex(self._font, text_before, line_pos, self._font_size, self._spacing_pixels, self._text_color) - width_before = measure_text_cached(self._font, text_before, self._font_size, self._spacing_pixels) - line_pos.x += width_before.x - - # Draw emoji - tex = emoji_tex(emoji) - emoji_scale = self._font_size / tex.height * FONT_SCALE - rl.draw_texture_ex(tex, line_pos, 0.0, emoji_scale, self._text_color) - # Emoji width is font_size * FONT_SCALE (as per measure_text_cached) - line_pos.x += self._font_size * FONT_SCALE - prev_index = end - - # Draw remaining text after last emoji - text_after = line[prev_index:] - if text_after: - rl.draw_text_ex(self._font, text_after, line_pos, self._font_size, self._spacing_pixels, self._text_color) + self._render_line(line, size, emojis, current_y) # Move to next line (if not last line) if idx < len(visible_lines) - 1: # Use current line's height * line_height for spacing to next line current_y += size.y * self._line_height + + def _render_line(self, line, size, emojis, current_y): + # Calculate horizontal position + if self._alignment == rl.GuiTextAlignment.TEXT_ALIGN_LEFT: + line_x = self._rect.x + self._text_padding + elif self._alignment == rl.GuiTextAlignment.TEXT_ALIGN_CENTER: + line_x = self._rect.x + (self._rect.width - size.x) / 2 + elif self._alignment == rl.GuiTextAlignment.TEXT_ALIGN_RIGHT: + line_x = self._rect.x + self._rect.width - size.x - self._text_padding + else: + line_x = self._rect.x + self._text_padding + + # Render line with emojis + line_pos = rl.Vector2(line_x, current_y) + prev_index = 0 + + for start, end, emoji in emojis: + # Draw text before emoji + text_before = line[prev_index:start] + if text_before: + rl.draw_text_ex(self._font, text_before, line_pos, self._font_size, self._spacing_pixels, self._text_color) + width_before = measure_text_cached(self._font, text_before, self._font_size, self._spacing_pixels) + line_pos.x += width_before.x + + # Draw emoji + tex = emoji_tex(emoji) + emoji_scale = self._font_size / tex.height * FONT_SCALE + rl.draw_texture_ex(tex, line_pos, 0.0, emoji_scale, self._text_color) + # Emoji width is font_size * FONT_SCALE (as per measure_text_cached) + line_pos.x += self._font_size * FONT_SCALE + prev_index = end + + # Draw remaining text after last emoji + text_after = line[prev_index:] + if text_after: + rl.draw_text_ex(self._font, text_after, line_pos, self._font_size, self._spacing_pixels, self._text_color) From cb718618d14b5b46aa2698d17194099ecd417de6 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 29 Nov 2025 02:12:09 -0800 Subject: [PATCH 482/910] fix multi option dialog text centering --- selfdrive/ui/mici/widgets/dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index d64ab65ef..950d71319 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -282,7 +282,7 @@ class BigDialogOptionButton(Widget): self._selected = False self._label = UnifiedLabel(option, font_size=70, text_color=rl.Color(255, 255, 255, int(255 * 0.58)), - font_weight=FontWeight.DISPLAY_REGULAR, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP) + font_weight=FontWeight.DISPLAY_REGULAR, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE) def set_selected(self, selected: bool): self._selected = selected From 088fc1cab1f4ccb6e2ba229e8cb3ef5a17fe0be8 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 29 Nov 2025 02:15:10 -0800 Subject: [PATCH 483/910] Unified label: add scrolling (#36717) * almost * works! * clean up * fix * trash * Revert "trash" This reverts commit 951d63382810d444fe08103f406a8c490cfcbe25. * fix some bugs and use * clean up * clean up * fix clipping * clean up * fix --- selfdrive/ui/mici/layouts/home.py | 6 +- .../mici/layouts/settings/network/wifi_ui.py | 6 +- selfdrive/ui/mici/widgets/dialog.py | 3 +- system/ui/widgets/label.py | 67 ++++++++++++++++++- 4 files changed, 76 insertions(+), 6 deletions(-) diff --git a/selfdrive/ui/mici/layouts/home.py b/selfdrive/ui/mici/layouts/home.py index 6102265a8..9152bdc7f 100644 --- a/selfdrive/ui/mici/layouts/home.py +++ b/selfdrive/ui/mici/layouts/home.py @@ -3,7 +3,7 @@ import time from cereal import log import pyray as rl from collections.abc import Callable -from openpilot.system.ui.widgets.label import gui_label, MiciLabel +from openpilot.system.ui.widgets.label import gui_label, MiciLabel, UnifiedLabel from openpilot.system.ui.widgets import Widget from openpilot.system.ui.lib.application import gui_app, FontWeight, DEFAULT_TEXT_COLOR, MousePos from openpilot.selfdrive.ui.ui_state import ui_state @@ -113,7 +113,7 @@ class MiciHomeLayout(Widget): self._version_label = MiciLabel("", font_size=36, font_weight=FontWeight.ROMAN) self._large_version_label = MiciLabel("", font_size=64, color=rl.GRAY, font_weight=FontWeight.ROMAN) self._date_label = MiciLabel("", font_size=36, color=rl.GRAY, font_weight=FontWeight.ROMAN) - self._branch_label = MiciLabel("", font_size=36, color=rl.GRAY, font_weight=FontWeight.ROMAN, elide_right=False, scroll=True) + self._branch_label = UnifiedLabel("", font_size=36, text_color=rl.GRAY, font_weight=FontWeight.ROMAN, scroll=True) self._version_commit_label = MiciLabel("", font_size=36, color=rl.GRAY, font_weight=FontWeight.ROMAN) def show_event(self): @@ -195,7 +195,7 @@ class MiciHomeLayout(Widget): self._date_label.set_position(version_pos.x + self._version_label.rect.width + 10, version_pos.y) self._date_label.render() - self._branch_label.set_width(gui_app.width - self._version_label.rect.width - self._date_label.rect.width - 32) + self._branch_label.set_max_width(gui_app.width - self._version_label.rect.width - self._date_label.rect.width - 32) self._branch_label.set_text(" " + ("release" if release_branch else self._version_text[1])) self._branch_label.set_position(version_pos.x + self._version_label.rect.width + self._date_label.rect.width + 20, version_pos.y) self._branch_label.render() diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index 2ab46d169..eec16d884 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -207,7 +207,7 @@ class NetworkInfoPage(NavWidget): self._connect_btn.set_click_callback(lambda: connect_callback(self._network.ssid) if self._network is not None else None) self._title = UnifiedLabel("", 64, FontWeight.DISPLAY, rl.Color(255, 255, 255, int(255 * 0.9)), - alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE) + alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE, scroll=True) self._subtitle = UnifiedLabel("", 36, FontWeight.ROMAN, rl.Color(255, 255, 255, int(255 * 0.9 * 0.65)), alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE) @@ -217,6 +217,10 @@ class NetworkInfoPage(NavWidget): self._network: Network | None = None self._connecting: Callable[[], str | None] | None = None + def show_event(self): + super().show_event() + self._title.reset_scroll() + def update_networks(self, networks: dict[str, Network]): # update current network from latest scan results for ssid, network in networks.items(): diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index 950d71319..b11056f99 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -282,7 +282,8 @@ class BigDialogOptionButton(Widget): self._selected = False self._label = UnifiedLabel(option, font_size=70, text_color=rl.Color(255, 255, 255, int(255 * 0.58)), - font_weight=FontWeight.DISPLAY_REGULAR, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE) + font_weight=FontWeight.DISPLAY_REGULAR, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE, + scroll=True) def set_selected(self, selected: bool): self._selected = selected diff --git a/system/ui/widgets/label.py b/system/ui/widgets/label.py index 432f21e59..fd0516a98 100644 --- a/system/ui/widgets/label.py +++ b/system/ui/widgets/label.py @@ -412,6 +412,7 @@ class UnifiedLabel(Widget): max_width: int | None = None, elide: bool = True, wrap_text: bool = True, + scroll: bool = False, line_height: float = 1.0, letter_spacing: float = 0.0): super().__init__() @@ -426,10 +427,23 @@ class UnifiedLabel(Widget): self._max_width = max_width self._elide = elide self._wrap_text = wrap_text + self._scroll = scroll self._line_height = line_height * 0.9 self._letter_spacing = letter_spacing # 0.1 = 10% self._spacing_pixels = font_size * letter_spacing + # Scroll state + self._scroll = scroll + self._needs_scroll = False + self._scroll_offset = 0 + self._scroll_pause_t: float | None = None + self._scroll_state: ScrollState = ScrollState.STARTING + + # Scroll mode does not support eliding or multiline wrapping + if self._scroll: + self._elide = False + self._wrap_text = False + # Cached data self._cached_text: str | None = None self._cached_wrapped_lines: list[str] = [] @@ -490,6 +504,12 @@ class UnifiedLabel(Widget): """Update the vertical text alignment.""" self._alignment_vertical = alignment_vertical + def reset_scroll(self): + """Reset scroll state to initial position.""" + self._scroll_offset = 0 + self._scroll_pause_t = None + self._scroll_state = ScrollState.STARTING + def set_max_width(self, max_width: int | None): """Set the maximum width constraint for wrapping/eliding.""" if self._max_width != max_width: @@ -528,6 +548,9 @@ class UnifiedLabel(Widget): # Elide lines if needed (for width constraint) self._cached_wrapped_lines = [self._elide_line(line, content_width) for line in self._cached_wrapped_lines] + if self._scroll: + self._cached_wrapped_lines = self._cached_wrapped_lines[:1] # Only first line for scrolling + # Process each line: measure and find emojis self._cached_line_sizes = [] self._cached_line_emojis = [] @@ -540,6 +563,11 @@ class UnifiedLabel(Widget): size = rl.Vector2(0, self._font_size * FONT_SCALE) else: size = measure_text_cached(self._font, line, self._font_size, self._spacing_pixels) + + # This is the only line + if self._scroll: + self._needs_scroll = size.x > content_width + self._cached_line_sizes.append(size) # Calculate total height @@ -683,17 +711,53 @@ class UnifiedLabel(Widget): else: # TEXT_ALIGN_MIDDLE start_y = self._rect.y + (self._rect.height - total_visible_height) / 2 + # Only scissor when we know there is a single scrolling line + if self._needs_scroll: + rl.begin_scissor_mode(int(self._rect.x), int(self._rect.y), int(self._rect.width), int(self._rect.height)) + # Render each line current_y = start_y for idx, (line, size, emojis) in enumerate(zip(visible_lines, visible_sizes, visible_emojis, strict=True)): + if self._needs_scroll: + if self._scroll_state == ScrollState.STARTING: + if self._scroll_pause_t is None: + self._scroll_pause_t = rl.get_time() + 2.0 + if rl.get_time() >= self._scroll_pause_t: + self._scroll_state = ScrollState.SCROLLING + self._scroll_pause_t = None + + elif self._scroll_state == ScrollState.SCROLLING: + self._scroll_offset -= 0.8 / 60. * gui_app.target_fps + # don't fully hide + if self._scroll_offset <= -size.x - self._rect.width / 3: + self._scroll_offset = 0 + self._scroll_state = ScrollState.STARTING + self._scroll_pause_t = None + else: + self.reset_scroll() + self._render_line(line, size, emojis, current_y) + # Draw 2nd instance for scrolling + if self._needs_scroll and self._scroll_state != ScrollState.STARTING: + text2_scroll_offset = size.x + self._rect.width / 3 + self._render_line(line, size, emojis, current_y, text2_scroll_offset) + # Move to next line (if not last line) if idx < len(visible_lines) - 1: # Use current line's height * line_height for spacing to next line current_y += size.y * self._line_height - def _render_line(self, line, size, emojis, current_y): + if self._needs_scroll: + # draw black fade on left and right + fade_width = 20 + rl.draw_rectangle_gradient_h(int(self._rect.x + self._rect.width - fade_width), int(self._rect.y), fade_width, int(self._rect.height), rl.BLANK, rl.BLACK) + if self._scroll_state != ScrollState.STARTING: + rl.draw_rectangle_gradient_h(int(self._rect.x), int(self._rect.y), fade_width, int(self._rect.height), rl.BLACK, rl.BLANK) + + rl.end_scissor_mode() + + def _render_line(self, line, size, emojis, current_y, x_offset=0.0): # Calculate horizontal position if self._alignment == rl.GuiTextAlignment.TEXT_ALIGN_LEFT: line_x = self._rect.x + self._text_padding @@ -703,6 +767,7 @@ class UnifiedLabel(Widget): line_x = self._rect.x + self._rect.width - size.x - self._text_padding else: line_x = self._rect.x + self._text_padding + line_x += self._scroll_offset + x_offset # Render line with emojis line_pos = rl.Vector2(line_x, current_y) From 22003fd10a48bc0f35c65f40fdec5977f2a362c2 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 29 Nov 2025 02:15:38 -0800 Subject: [PATCH 484/910] rl.BLANK --- selfdrive/ui/mici/onroad/hud_renderer.py | 2 +- system/ui/widgets/label.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/selfdrive/ui/mici/onroad/hud_renderer.py b/selfdrive/ui/mici/onroad/hud_renderer.py index 524eb1163..cb9f7c6fc 100644 --- a/selfdrive/ui/mici/onroad/hud_renderer.py +++ b/selfdrive/ui/mici/onroad/hud_renderer.py @@ -233,7 +233,7 @@ class HudRenderer(Widget): # draw drop shadow circle_radius = 162 // 2 rl.draw_circle_gradient(int(x + circle_radius), int(y + circle_radius), circle_radius, - rl.Color(0, 0, 0, int(255 / 2 * alpha)), rl.Color(0, 0, 0, 0)) + rl.Color(0, 0, 0, int(255 / 2 * alpha)), rl.BLANK) set_speed_color = rl.Color(255, 255, 255, int(255 * 0.9 * alpha)) max_color = rl.Color(255, 255, 255, int(255 * 0.9 * alpha)) diff --git a/system/ui/widgets/label.py b/system/ui/widgets/label.py index fd0516a98..91f05c355 100644 --- a/system/ui/widgets/label.py +++ b/system/ui/widgets/label.py @@ -179,9 +179,9 @@ class MiciLabel(Widget): if self._needs_scroll: # draw black fade on left and right fade_width = 20 - rl.draw_rectangle_gradient_h(int(rect.x + rect.width - fade_width), int(rect.y), fade_width, int(rect.height), rl.Color(0, 0, 0, 0), rl.BLACK) + rl.draw_rectangle_gradient_h(int(rect.x + rect.width - fade_width), int(rect.y), fade_width, int(rect.height), rl.BLANK, rl.BLACK) if self._scroll_state != ScrollState.STARTING: - rl.draw_rectangle_gradient_h(int(rect.x), int(rect.y), fade_width, int(rect.height), rl.BLACK, rl.Color(0, 0, 0, 0)) + rl.draw_rectangle_gradient_h(int(rect.x), int(rect.y), fade_width, int(rect.height), rl.BLACK, rl.BLANK) rl.end_scissor_mode() From 6c39f6bb53466f2e284dfff5d9e44d634855ac52 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Sat, 29 Nov 2025 18:21:15 +0800 Subject: [PATCH 485/910] ui: Fix scroll logic for non-scrollable content (bounds_size > content_size) to prevent jitter (#36693) * Fix scroll logic for non-scrollable content to prevent jitter * one thing --------- Co-authored-by: Shane Smiskol --- system/ui/lib/scroll_panel2.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/system/ui/lib/scroll_panel2.py b/system/ui/lib/scroll_panel2.py index ab93be945..00ef95cc8 100644 --- a/system/ui/lib/scroll_panel2.py +++ b/system/ui/lib/scroll_panel2.py @@ -67,16 +67,18 @@ class GuiScrollPanel2: print() return self.get_offset() + def _get_offset_bounds(self, bounds_size: float, content_size: float) -> tuple[float, float]: + """Returns (max_offset, min_offset) for the given bounds and content size.""" + return 0.0, min(0.0, bounds_size - content_size) + def _update_state(self, bounds_size: float, content_size: float) -> None: """Runs per render frame, independent of mouse events. Updates auto-scrolling state and velocity.""" if self._state == ScrollState.AUTO_SCROLL: + max_offset, min_offset = self._get_offset_bounds(bounds_size, content_size) # simple exponential return if out of bounds - out_of_bounds = self.get_offset() > 0 or self.get_offset() < (bounds_size - content_size) + out_of_bounds = self.get_offset() > max_offset or self.get_offset() < min_offset if out_of_bounds and self._handle_out_of_bounds: - if self.get_offset() < (bounds_size - content_size): # too far right - target = bounds_size - content_size - else: # too far left - target = 0.0 + target = max_offset if self.get_offset() > max_offset else min_offset dt = rl.get_frame_time() or 1e-6 factor = 1.0 - math.exp(-BOUNCE_RETURN_RATE * dt) @@ -103,7 +105,9 @@ class GuiScrollPanel2: def _handle_mouse_event(self, mouse_event: MouseEvent, bounds: rl.Rectangle, bounds_size: float, content_size: float) -> None: - out_of_bounds = self.get_offset() > 0 or self.get_offset() < (bounds_size - content_size) + max_offset, min_offset = self._get_offset_bounds(bounds_size, content_size) + # simple exponential return if out of bounds + out_of_bounds = self.get_offset() > max_offset or self.get_offset() < min_offset if DEBUG: print('Mouse event:', mouse_event) From 1b20567c986b4c02bc89872dec7a5e0fa6436006 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 29 Nov 2025 02:30:32 -0800 Subject: [PATCH 486/910] Mici keyboard: alpha filter for key bg (#36720) * filter * tune * fix --- system/ui/widgets/mici_keyboard.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/system/ui/widgets/mici_keyboard.py b/system/ui/widgets/mici_keyboard.py index a4f4c7d09..7459dc573 100644 --- a/system/ui/widgets/mici_keyboard.py +++ b/system/ui/widgets/mici_keyboard.py @@ -4,7 +4,7 @@ import numpy as np from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos, MouseEvent from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.widgets import Widget -from openpilot.common.filter_simple import BounceFilter +from openpilot.common.filter_simple import BounceFilter, FirstOrderFilter CHAR_FONT_SIZE = 42 CHAR_NEAR_FONT_SIZE = CHAR_FONT_SIZE * 2 @@ -204,6 +204,7 @@ class MiciKeyboard(Widget): self._text: str = "" self._bg_scale_filter = BounceFilter(1.0, 0.1 * ANIMATION_SCALE, 1 / gui_app.target_fps) + self._selected_key_filter = FirstOrderFilter(0.0, 0.075 * ANIMATION_SCALE, 1 / gui_app.target_fps) def get_candidate_character(self) -> str: # return str of character about to be added to text @@ -309,6 +310,9 @@ class MiciKeyboard(Widget): self._text += ' ' def _update_state(self): + # update selected key filter + self._selected_key_filter.update(self._closest_key[0] is not None) + # unselect key after animation plays if self._unselect_key_t is not None and rl.get_time() > self._unselect_key_t: self._closest_key = (None, float('inf')) @@ -335,8 +339,9 @@ class MiciKeyboard(Widget): key.set_font_size(SELECTED_CHAR_FONT_SIZE) # draw black circle behind selected key + circle_alpha = int(self._selected_key_filter.x * 225) rl.draw_circle_gradient(int(key_x + key.rect.width / 2), int(key_y + key.rect.height / 2), - SELECTED_CHAR_FONT_SIZE, rl.Color(0, 0, 0, 225), rl.BLANK) + SELECTED_CHAR_FONT_SIZE, rl.Color(0, 0, 0, circle_alpha), rl.BLANK) else: # move other keys away from selected key a bit dx = key.original_position.x - self._closest_key[0].original_position.x From f1c2b1df7f8f9854d4602ced70847b49af8bad5e Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Sat, 29 Nov 2025 19:14:23 +0800 Subject: [PATCH 487/910] ui: fix CameraView crash in mici due to stale frame (#36710) fix CameraView crash caused by stale frame --- selfdrive/ui/mici/onroad/cameraview.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/selfdrive/ui/mici/onroad/cameraview.py b/selfdrive/ui/mici/onroad/cameraview.py index 0f425b10d..995c4618f 100644 --- a/selfdrive/ui/mici/onroad/cameraview.py +++ b/selfdrive/ui/mici/onroad/cameraview.py @@ -198,6 +198,8 @@ class CameraView(Widget): if self.shader and self.shader.id: rl.unload_shader(self.shader) + self.frame = None + self.available_streams.clear() self.client = None def __del__(self): @@ -234,6 +236,9 @@ class CameraView(Widget): if buffer: self._texture_needs_update = True self.frame = buffer + elif not self.client.is_connected(): + # ensure we clear the displayed frame when the connection is lost + self.frame = None if not self.frame: self._draw_placeholder(rect) From 8de89463741b648f7cd2a625b0e688005168388e Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Sat, 29 Nov 2025 19:15:41 +0800 Subject: [PATCH 488/910] ui: skip _draw_set_speed when alpha is 0 (#36709) * Skip _draw_set_speed when alpha is 0 to reduce unnecessary draw calls * Update selfdrive/ui/mici/onroad/hud_renderer.py --------- Co-authored-by: Shane Smiskol --- selfdrive/ui/mici/onroad/hud_renderer.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/selfdrive/ui/mici/onroad/hud_renderer.py b/selfdrive/ui/mici/onroad/hud_renderer.py index cb9f7c6fc..76b443842 100644 --- a/selfdrive/ui/mici/onroad/hud_renderer.py +++ b/selfdrive/ui/mici/onroad/hud_renderer.py @@ -224,11 +224,13 @@ class HudRenderer(Widget): def _draw_set_speed(self, rect: rl.Rectangle) -> None: """Draw the MAX speed indicator box.""" - x = rect.x - y = rect.y - alpha = self._set_speed_alpha_filter.update(0 < rl.get_time() - self._set_speed_changed_time < SET_SPEED_PERSISTENCE and self._can_draw_top_icons and self._engaged) + if alpha < 1e-2: + return + + x = rect.x + y = rect.y # draw drop shadow circle_radius = 162 // 2 From 0f7498e214404b4eeafff2e62117a0e302f26ef7 Mon Sep 17 00:00:00 2001 From: Nayan Date: Sun, 30 Nov 2025 00:54:03 -0500 Subject: [PATCH 489/910] ui: `UIStateSP` (#1489) * introducing ui_state_sp for py * param to control stock vs sp ui * better * add ui_update callback * Revert "add ui_update callback" This reverts commit 4da32cc0097434aab0aa6a3c35465eabb23c8958. * ui: `GuiApplicationExt` * add to readme * use gui_app.sunnypilot_ui() * add cp_sp to ui_state_sp * fix ui crash * update params * more * slightly more * add directly to the list * nah * move around * rename * call before param time tracker is updated --------- Co-authored-by: Jason Wen --- selfdrive/ui/sunnypilot/ui_state.py | 26 ++++++++++++++++++++++++++ selfdrive/ui/ui_state.py | 9 +++++++-- 2 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 selfdrive/ui/sunnypilot/ui_state.py diff --git a/selfdrive/ui/sunnypilot/ui_state.py b/selfdrive/ui/sunnypilot/ui_state.py new file mode 100644 index 000000000..18e35508b --- /dev/null +++ b/selfdrive/ui/sunnypilot/ui_state.py @@ -0,0 +1,26 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +from cereal import messaging, custom +from openpilot.common.params import Params + + +class UIStateSP: + def __init__(self): + self.params = Params() + self.sm_services_ext = [ + "modelManagerSP", "selfdriveStateSP", "longitudinalPlanSP", "backupManagerSP", + "gpsLocation", "liveTorqueParameters", "carStateSP", "liveMapDataSP", "carParamsSP" + ] + + def update(self) -> None: + pass + + def update_params(self) -> None: + CP_SP_bytes = self.params.get("CarParamsSPPersistent") + if CP_SP_bytes is not None: + self.CP_SP = messaging.log_from_bytes(CP_SP_bytes, custom.CarParamsSP) + self.sunnylink_enabled = self.params.get_bool("SunnylinkEnabled") diff --git a/selfdrive/ui/ui_state.py b/selfdrive/ui/ui_state.py index ef0696a22..c78fdccb5 100644 --- a/selfdrive/ui/ui_state.py +++ b/selfdrive/ui/ui_state.py @@ -12,6 +12,8 @@ from openpilot.selfdrive.ui.lib.prime_state import PrimeState from openpilot.system.ui.lib.application import gui_app from openpilot.system.hardware import HARDWARE, PC +from openpilot.selfdrive.ui.sunnypilot.ui_state import UIStateSP + BACKLIGHT_OFFROAD = 65 if HARDWARE.get_device_type() == "mici" else 50 @@ -21,7 +23,7 @@ class UIStatus(Enum): OVERRIDE = "override" -class UIState: +class UIState(UIStateSP): _instance: 'UIState | None' = None def __new__(cls): @@ -31,6 +33,7 @@ class UIState: return cls._instance def _initialize(self): + UIStateSP.__init__(self) self.params = Params() self.sm = messaging.SubMaster( [ @@ -55,7 +58,7 @@ class UIState: "carControl", "liveParameters", "rawAudioData", - ] + ] + self.sm_services_ext ) self.prime_state = PrimeState() @@ -111,6 +114,7 @@ class UIState: if time.monotonic() - self._param_update_time > 5.0: self.update_params() device.update() + UIStateSP.update(self) def _update_state(self) -> None: # Handle panda states updates @@ -180,6 +184,7 @@ class UIState: self.has_longitudinal_control = self.params.get_bool("AlphaLongitudinalEnabled") else: self.has_longitudinal_control = self.CP.openpilotLongitudinalControl + UIStateSP.update_params(self) self._param_update_time = time.monotonic() From c68c914444866248a96a5b18d558f6dcac657f25 Mon Sep 17 00:00:00 2001 From: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Sat, 29 Nov 2025 22:21:15 -0800 Subject: [PATCH 490/910] ui: tree dialog widget (#1493) * param to control stock vs sp ui * init styles * SP Toggles * Lint * optimizations * sp raylib preview * fix callback * fix ui preview * dialog txt * compare vs what used to be done before InputDialog * merge origin raylib toggles * tree dialog * less trees for the planet * the heck * save the trees we got icons * Update process.py * Remove 'sunnypilot_ui' Removed 'sunnypilot_ui' parameter from params_keys.h * Update raylib_screenshots.py Removed the parameter setting for 'sunnypilot_ui' in the test. * ui: fuzzy search helper * better tree. fully dynamic and stuff * rm * more indent * only show if fav_param is used in the call * conditional for mypy * sunny's new x,y makes this even easier! * loathing loathing, unadulterated loathing, i loathe it all * more changes! * more changes! * Update BUTTON_DISABLED_BG_COLOR to a lighter shade * Update tree_dialog.py * final --------- Co-authored-by: nayan Co-authored-by: Jason Wen --- system/ui/sunnypilot/lib/styles.py | 5 + .../ui/sunnypilot/widgets/helpers/__init__.py | 0 .../sunnypilot/widgets/helpers/star_icon.py | 26 +++ system/ui/sunnypilot/widgets/tree_dialog.py | 186 ++++++++++++++++++ 4 files changed, 217 insertions(+) create mode 100644 system/ui/sunnypilot/widgets/helpers/__init__.py create mode 100644 system/ui/sunnypilot/widgets/helpers/star_icon.py create mode 100644 system/ui/sunnypilot/widgets/tree_dialog.py diff --git a/system/ui/sunnypilot/lib/styles.py b/system/ui/sunnypilot/lib/styles.py index 0fb4a5c30..eb254166e 100644 --- a/system/ui/sunnypilot/lib/styles.py +++ b/system/ui/sunnypilot/lib/styles.py @@ -65,5 +65,10 @@ class DefaultStyleSP(Base): OPTION_CONTROL_TEXT_PRESSED = rl.WHITE OPTION_CONTROL_TEXT_DISABLED = ITEM_DISABLED_TEXT_COLOR + # Tree Button Colors + BUTTON_PRIMARY_COLOR = rl.Color(70, 91, 234, 255) # Royal Blue + BUTTON_NEUTRAL_GRAY = rl.Color(51, 51, 51, 255) + BUTTON_DISABLED_BG_COLOR = rl.Color(30, 30, 30, 255) # Very Dark Grey + style = DefaultStyleSP diff --git a/system/ui/sunnypilot/widgets/helpers/__init__.py b/system/ui/sunnypilot/widgets/helpers/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/system/ui/sunnypilot/widgets/helpers/star_icon.py b/system/ui/sunnypilot/widgets/helpers/star_icon.py new file mode 100644 index 000000000..14666d49b --- /dev/null +++ b/system/ui/sunnypilot/widgets/helpers/star_icon.py @@ -0,0 +1,26 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +import math + +import pyray as rl + + +def draw_star(center_x, center_y, radius, is_filled, color): + center = rl.Vector2(center_x, center_y) + points = [] + + for i in range(10): + angle = -(i * 36 + 18) * math.pi / 180 + r = radius if i % 2 == 0 else radius / 2 + x = center_x + r * math.cos(angle) + y = center_y + r * math.sin(angle) + points.append(rl.Vector2(x, y)) + + for i in range(10): + if is_filled: + rl.draw_triangle(center, points[i], points[(i + 1) % 10], color) + rl.draw_line_ex(points[i], points[(i + 1) % 10], 2, color) diff --git a/system/ui/sunnypilot/widgets/tree_dialog.py b/system/ui/sunnypilot/widgets/tree_dialog.py new file mode 100644 index 000000000..aa2904424 --- /dev/null +++ b/system/ui/sunnypilot/widgets/tree_dialog.py @@ -0,0 +1,186 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +from dataclasses import dataclass, field + +import pyray as rl +from openpilot.common.params import Params +from openpilot.system.ui.lib.application import FontWeight, gui_app +from openpilot.system.ui.lib.multilang import tr +from openpilot.system.ui.widgets import DialogResult +from openpilot.system.ui.widgets.button import Button, ButtonStyle +from openpilot.system.ui.widgets.label import gui_label, Label +from openpilot.system.ui.widgets.option_dialog import MultiOptionDialog + +from openpilot.system.ui.sunnypilot.lib.styles import style +from openpilot.system.ui.sunnypilot.widgets.helpers.fuzzy_search import search_from_list +from openpilot.system.ui.sunnypilot.widgets.helpers.star_icon import draw_star +from openpilot.system.ui.sunnypilot.widgets.input_dialog import InputDialogSP + + +@dataclass +class TreeNode: + ref: str + data: dict = field(default_factory=dict) + + +@dataclass +class TreeFolder: + folder: str + nodes: list + + +class TreeItemWidget(Button): + def __init__(self, text, ref, is_folder=False, indent_level=0, click_callback=None, favorite_callback=None, is_favorite=False): + super().__init__(text, click_callback, button_style=ButtonStyle.NORMAL, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT, + text_padding=20 + indent_level * 30, elide_right=True) + self.text = text + self.ref = ref + self.is_folder = is_folder + self.indent_level = indent_level + self.is_favorite = is_favorite + self.selected = False + self._favorite_callback = favorite_callback + self.text_padding = 20 + indent_level * 30 + self.border_radius = 10 + + def _render(self, rect): + indent = 60 * self.indent_level if self.indent_level > 0 else 10 + self._rect = rl.Rectangle(rect.x + indent, rect.y, rect.width - indent, rect.height) + color = style.BUTTON_PRIMARY_COLOR if self.selected and not (self.ref == "search_bar" or self.is_folder) else style.BUTTON_DISABLED_BG_COLOR + roundness = self.border_radius / (min(self._rect.width, self._rect.height) / 2) + rl.draw_rectangle_rounded(self._rect, roundness, 10, color) + text_rect = rl.Rectangle(self._rect.x + self.text_padding + 20, self._rect.y, self._rect.width - self.text_padding - 20 - 90, self._rect.height) + self._label.render(text_rect) + + if not self.is_folder and self._favorite_callback: + draw_star(self._rect.x + self._rect.width - 90, self._rect.y + self._rect.height / 2, 40, self.is_favorite, + style.ON_BG_COLOR if self.is_favorite else rl.GRAY) + + def _handle_mouse_release(self, mouse_pos): + star_rect = rl.Rectangle(self._rect.x + self._rect.width - 90 - 40, self._rect.y + self._rect.height / 2 - 40, 80, 80) + if not self.is_folder and self._favorite_callback and rl.check_collision_point_rec(mouse_pos, star_rect): + self._favorite_callback() + return True + return super()._handle_mouse_release(mouse_pos) + + +class TreeOptionDialog(MultiOptionDialog): + def __init__(self, title, folders, current_ref="", fav_param="", option_font_weight=FontWeight.MEDIUM, search_prompt=None, + get_folders_fn=None, on_exit=None, display_func=None, search_funcs=None): + super().__init__(title, [], "", option_font_weight) + self.folders = folders + self.selection_ref = current_ref + self.fav_param = fav_param + self.expanded = set() + self.params = Params() + val = self.params.get(fav_param) if fav_param else None + self.favorites = set(val.split(';')) if val else set() + self.query = "" + self.search_prompt = search_prompt or tr("Search") + self.get_folders_fn = get_folders_fn + self.on_exit = on_exit + self.display_func = display_func or (lambda node: node.data.get('display_name', node.ref)) + self.search_funcs = search_funcs or [lambda node: node.data.get('display_name', ''), lambda node: node.data.get('short_name', '')] + self._build_visible_items() + self.cancel_rect = None + self.select_rect = None + + def _on_search_confirm(self, result, text): + if result == DialogResult.CONFIRM: + self.query = text + self._build_visible_items() + gui_app.set_modal_overlay(self, callback=self.on_exit) + + def _on_search_clicked(self): + InputDialogSP(tr("Enter search query"), current_text=self.query, callback=self._on_search_confirm).show() + + def _toggle_folder(self, folder): + if folder.folder: + if folder.folder in self.expanded: + self.expanded.remove(folder.folder) + else: + self.expanded.add(folder.folder) + if folder == self.folders[-1] and folder.folder in self.expanded: + self.scroller.scroll_panel.set_offset(self.scroller.scroll_panel.offset - 200) + self._build_visible_items(reset_scroll=False) + + def _select_node(self, node): + self.selection = self.display_func(node) + self.selection_ref = node.ref + + def _toggle_favorite(self, node): + self.favorites.remove(node.ref) if node.ref in self.favorites else self.favorites.add(node.ref) + if self.fav_param: + self.params.put(self.fav_param, ';'.join(self.favorites)) + if self.get_folders_fn: + self.folders = self.get_folders_fn(self.favorites) + self._build_visible_items(reset_scroll=False) + + def _build_visible_items(self, reset_scroll=True): + self.visible_items = [TreeItemWidget(self.query or self.search_prompt, "search_bar", False, 0, self._on_search_clicked)] + for folder in self.folders: + nodes = [node for node in folder.nodes if not self.query or search_from_list(self.query, [search_func(node) for search_func in self.search_funcs])] + if not nodes and self.query: + continue + expanded = folder.folder in self.expanded or not folder.folder or bool(self.query) + if folder.folder: + self.visible_items.append(TreeItemWidget(f"{'-' if expanded else '+'} {folder.folder}", "", True, 0, + lambda folder_ref=folder: self._toggle_folder(folder_ref))) + if expanded: + for node in nodes: + favorite_cb = (lambda node_ref=node: self._toggle_favorite(node_ref)) if self.fav_param and node.ref != "Default" else None + self.visible_items.append(TreeItemWidget(self.display_func(node), node.ref, False, 1 if folder.folder else 0, + lambda node_ref=node: self._select_node(node_ref), favorite_cb, node.ref in self.favorites)) + self.option_buttons = self.visible_items + self.options = [item.text for item in self.visible_items] + self.scroller._items = self.visible_items + if reset_scroll: + self.scroller.scroll_panel.set_offset(0) + + def _draw_button(self, button_rect, button_text, is_primary=False, is_enabled=True): + if is_primary and is_enabled: + button_color = style.BUTTON_PRIMARY_COLOR + elif not is_enabled: + button_color = style.BUTTON_NEUTRAL_GRAY + else: + button_color = style.BUTTON_DISABLED_BG_COLOR + roundness = 10 / (min(button_rect.width, button_rect.height) / 2) + rl.draw_rectangle_rounded(button_rect, roundness, 10, button_color) + label = Label(button_text, 60, FontWeight.NORMAL, rl.GuiTextAlignment.TEXT_ALIGN_CENTER, + text_color=rl.WHITE if is_enabled else rl.GRAY) + label.render(button_rect) + + def _render(self, rect): + dialog_content_rect = rl.Rectangle(rect.x + 50, rect.y + 50, rect.width - 100, rect.height - 100) + rl.draw_rectangle_rounded(dialog_content_rect, 0.02, 20, rl.BLACK) + gui_label(rl.Rectangle(dialog_content_rect.x + 50, dialog_content_rect.y + 50, dialog_content_rect.width - 100, 70), + self.title, 70, font_weight=FontWeight.BOLD) + + options_area_rect = rl.Rectangle(dialog_content_rect.x + 50, dialog_content_rect.y + 170, dialog_content_rect.width - 100, dialog_content_rect.height - 330) + for index, option_text in enumerate(self.options): + self.option_buttons[index].selected = (option_text == self.selection) + self.option_buttons[index].set_button_style(ButtonStyle.PRIMARY if option_text == self.selection else ButtonStyle.NORMAL) + self.option_buttons[index].set_rect(rl.Rectangle(0, 0, options_area_rect.width, 135)) + self.scroller.render(options_area_rect) + + button_width = (dialog_content_rect.width - 150) / 2 + button_y_position = dialog_content_rect.y + dialog_content_rect.height - 160 + self.cancel_rect = rl.Rectangle(dialog_content_rect.x + 50, button_y_position, button_width, 160) + self.select_rect = rl.Rectangle(dialog_content_rect.x + 100 + button_width, button_y_position, button_width, 160) + + self._draw_button(self.cancel_rect, tr("Cancel")) + self._draw_button(self.select_rect, tr("Select"), True, self.selection != self.current) + return self._result + + def _handle_mouse_release(self, mouse_pos): + if self.cancel_rect and rl.check_collision_point_rec(mouse_pos, self.cancel_rect): + self._set_result(DialogResult.CANCEL) + return True + if self.select_rect and rl.check_collision_point_rec(mouse_pos, self.select_rect) and self.selection != self.current: + self._set_result(DialogResult.CONFIRM) + return True + return super()._handle_mouse_release(mouse_pos) From 85a162dd4320f14cb8aba1bcd7ff427d9b753ce5 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sun, 30 Nov 2025 02:48:05 -0800 Subject: [PATCH 491/910] more scons nodes --- system/manager/build.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/manager/build.py b/system/manager/build.py index c88befd45..d79e7fd2a 100755 --- a/system/manager/build.py +++ b/system/manager/build.py @@ -14,7 +14,7 @@ from openpilot.system.version import get_build_metadata MAX_CACHE_SIZE = 4e9 if "CI" in os.environ else 2e9 CACHE_DIR = Path("/data/scons_cache" if AGNOS else "/tmp/scons_cache") -TOTAL_SCONS_NODES = 2280 +TOTAL_SCONS_NODES = 2705 MAX_BUILD_PROGRESS = 100 def build(spinner: Spinner, dirty: bool = False, minimal: bool = False) -> None: From cd7e3623334d6314848c4e484a53d456410cf410 Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Sun, 30 Nov 2025 15:34:37 -0600 Subject: [PATCH 492/910] ui: Add RECORD=1 for direct frame recording (#36729) * ui: add real-time video recording functionality with ffmpeg support * fix: record at consistent frame rate * add spaces * fix type * refactor: RECORD_FRAMES variable and related logic * fix: remove unnecessary texture check * support missing output extension * add wait for close with timeout * fix: ensure RECORD_OUTPUT has the correct file extension * flush on close and terminate if times out closing * ffmpeg hide banner * reduce ffmpeg spam * refactor: streamline ffmpeg arguments for video encoding * refactor: move size arg to variable and add yub420p conversion for native support * use render_width and render_height for size * fix: ensure even dimensions for video encoding when recording * rm itertools * simple * cleanup * docs --------- Co-authored-by: Adeeb Shihadeh --- system/ui/README.md | 1 + system/ui/lib/application.py | 54 +++++++++++++++++++++++++++++++++++- 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/system/ui/README.md b/system/ui/README.md index f81cb5573..79a4dd32e 100644 --- a/system/ui/README.md +++ b/system/ui/README.md @@ -10,6 +10,7 @@ Quick start: * set `BURN_IN=1` to get a burn-in heatmap version of the UI * set `GRID=50` to show a 50-pixel alignment grid overlay * set `MAGIC_DEBUG=1` to show every dropped frames (only on device) +* set `RECORD=1` to record the screen, output defaults to `output.mp4` but can be set with `RECORD_OUTPUT` * 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 e3370a5f7..79e68aa67 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -7,11 +7,13 @@ import sys import pyray as rl import threading import platform +import subprocess from contextlib import contextmanager from collections.abc import Callable from collections import deque from dataclasses import dataclass from enum import StrEnum +from pathlib import Path from typing import NamedTuple from importlib.resources import as_file, files from openpilot.common.swaglog import cloudlog @@ -36,6 +38,8 @@ SCALE = float(os.getenv("SCALE", "1.0")) GRID_SIZE = int(os.getenv("GRID", "0")) PROFILE_RENDER = int(os.getenv("PROFILE_RENDER", "0")) PROFILE_STATS = int(os.getenv("PROFILE_STATS", "100")) # Number of functions to show in profile output +RECORD = os.getenv("RECORD") == "1" +RECORD_OUTPUT = str(Path(os.getenv("RECORD_OUTPUT", "output")).with_suffix(".mp4")) GL_VERSION = """ #version 300 es @@ -197,10 +201,15 @@ class GuiApplication: else: self._scale = SCALE + # Scale, then ensure dimensions are even self._scaled_width = int(self._width * self._scale) self._scaled_height = int(self._height * self._scale) + self._scaled_width += self._scaled_width % 2 + self._scaled_height += self._scaled_height % 2 + self._render_texture: rl.RenderTexture | None = None self._burn_in_shader: rl.Shader | None = None + self._ffmpeg_proc: subprocess.Popen | None = None self._textures: dict[str, rl.Texture] = {} self._target_fps: int = _DEFAULT_FPS self._last_fps_log_time: float = time.monotonic() @@ -259,12 +268,33 @@ class GuiApplication: rl.set_config_flags(flags) rl.init_window(self._scaled_width, self._scaled_height, title) - needs_render_texture = self._scale != 1.0 or BURN_IN_MODE + + needs_render_texture = self._scale != 1.0 or BURN_IN_MODE or RECORD if self._scale != 1.0: rl.set_mouse_scale(1 / self._scale, 1 / self._scale) if needs_render_texture: self._render_texture = rl.load_render_texture(self._width, self._height) rl.set_texture_filter(self._render_texture.texture, rl.TextureFilter.TEXTURE_FILTER_BILINEAR) + + if RECORD: + ffmpeg_args = [ + 'ffmpeg', + '-v', 'warning', # Reduce ffmpeg log spam + '-stats', # Show encoding progress + '-f', 'rawvideo', # Input format + '-pix_fmt', 'rgba', # Input pixel format + '-s', f'{self._width}x{self._height}', # Input resolution + '-r', str(fps), # Input frame rate + '-i', 'pipe:0', # Input from stdin + '-vf', 'vflip,format=yuv420p', # Flip vertically and convert rgba to yuv420p + '-c:v', 'libx264', # Video codec + '-preset', 'ultrafast', # Encoding speed + '-y', # Overwrite existing file + '-f', 'mp4', # Output format + RECORD_OUTPUT, # Output file path + ] + self._ffmpeg_proc = subprocess.Popen(ffmpeg_args, stdin=subprocess.PIPE) + rl.set_target_fps(fps) self._target_fps = fps @@ -372,6 +402,16 @@ class GuiApplication: rl.unload_image(image) return texture + def close_ffmpeg(self): + if self._ffmpeg_proc is not None: + self._ffmpeg_proc.stdin.flush() + self._ffmpeg_proc.stdin.close() + try: + self._ffmpeg_proc.wait(timeout=5) + except subprocess.TimeoutExpired: + self._ffmpeg_proc.terminate() + self._ffmpeg_proc.wait() + def close(self): if not rl.is_window_ready(): return @@ -395,6 +435,8 @@ class GuiApplication: if not PC: self._mouse.stop() + self.close_ffmpeg() + rl.close_window() @property @@ -469,6 +511,15 @@ class GuiApplication: self._draw_grid() rl.end_drawing() + + if RECORD: + image = rl.load_image_from_texture(self._render_texture.texture) + data_size = image.width * image.height * 4 + data = bytes(rl.ffi.buffer(image.data, data_size)) + self._ffmpeg_proc.stdin.write(data) + self._ffmpeg_proc.stdin.flush() + rl.unload_image(image) + self._monitor_fps() self._frame += 1 @@ -594,6 +645,7 @@ class GuiApplication: # Strict mode: terminate UI if FPS drops too much if STRICT_MODE and fps < self._target_fps * FPS_CRITICAL_THRESHOLD: cloudlog.error(f"FPS dropped critically below {fps}. Shutting down UI.") + self.close_ffmpeg() os._exit(1) def _draw_touch_points(self): From 970afa96835de4b3e0c085599dbd82d2dd6d9c60 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 30 Nov 2025 14:19:37 -0800 Subject: [PATCH 493/910] bump to 0.10.3 --- RELEASES.md | 3 +++ common/version.h | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/RELEASES.md b/RELEASES.md index 58044dc69..fabe635c7 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,3 +1,6 @@ +Version 0.10.3 (2025-12-10) +======================== + Version 0.10.2 (2025-11-19) ======================== * comma four support diff --git a/common/version.h b/common/version.h index ef2067078..c489ecc57 100644 --- a/common/version.h +++ b/common/version.h @@ -1 +1 @@ -#define COMMA_VERSION "0.10.2" +#define COMMA_VERSION "0.10.3" From 6d04251517cadb8ad5001bc1e223f93133ce041f Mon Sep 17 00:00:00 2001 From: Trey Moen <50057480+greatgitsby@users.noreply.github.com> Date: Sun, 30 Nov 2025 14:43:29 -0800 Subject: [PATCH 494/910] esim: remove bootstrap and delete (#36732) init --- system/hardware/base.py | 11 ----------- system/hardware/esim.py | 36 +----------------------------------- system/hardware/tici/esim.py | 33 --------------------------------- 3 files changed, 1 insertion(+), 79 deletions(-) diff --git a/system/hardware/base.py b/system/hardware/base.py index 17d0ec161..4163b3379 100644 --- a/system/hardware/base.py +++ b/system/hardware/base.py @@ -65,10 +65,6 @@ class ThermalConfig: return ret class LPABase(ABC): - @abstractmethod - def bootstrap(self) -> None: - pass - @abstractmethod def list_profiles(self) -> list[Profile]: pass @@ -77,10 +73,6 @@ class LPABase(ABC): 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 @@ -93,9 +85,6 @@ class LPABase(ABC): def switch_profile(self, iccid: str) -> None: pass - def is_comma_profile(self, iccid: str) -> bool: - return any(iccid.startswith(prefix) for prefix in ('8985235',)) - class HardwareBase(ABC): @staticmethod def get_cmdline() -> dict[str, str]: diff --git a/system/hardware/esim.py b/system/hardware/esim.py index 909ad41e0..1c98bb1e4 100755 --- a/system/hardware/esim.py +++ b/system/hardware/esim.py @@ -3,55 +3,21 @@ import argparse import time from openpilot.system.hardware import HARDWARE -from openpilot.system.hardware.base import LPABase - - -def bootstrap(lpa: LPABase) -> None: - print('┌──────────────────────────────────────────────────────────────────────────────┐') - print('│ WARNING, PLEASE READ BEFORE PROCEEDING │') - print('│ │') - print('│ this is an irreversible operation that will remove the comma-provisioned │') - print('│ profile. │') - print('│ │') - print('│ after this operation, you must purchase a new eSIM from comma in order to │') - print('│ use the comma prime subscription again. │') - print('└──────────────────────────────────────────────────────────────────────────────┘') - print() - for severity in ('sure', '100% sure'): - print(f'are you {severity} you want to proceed? (y/N) ', end='') - confirm = input() - if confirm != 'y': - print('aborting') - exit(0) - lpa.bootstrap() if __name__ == '__main__': parser = argparse.ArgumentParser(prog='esim.py', description='manage eSIM profiles on your comma device', epilog='comma.ai') - parser.add_argument('--bootstrap', action='store_true', help='bootstrap the eUICC (required before downloading profiles)') 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() mutated = False lpa = HARDWARE.get_sim_lpa() - if args.bootstrap: - bootstrap(lpa) - mutated = True - elif args.switch: + if args.switch: lpa.switch_profile(args.switch) mutated = True - elif args.delete: - confirm = input('are you sure you want to delete this profile? (y/N) ') - if confirm == 'y': - lpa.delete_profile(args.delete) - mutated = True - else: - print('cancelled') - exit(0) elif args.download: lpa.download_profile(args.download[0], args.download[1]) elif args.nickname: diff --git a/system/hardware/tici/esim.py b/system/hardware/tici/esim.py index 391ba4553..889611769 100644 --- a/system/hardware/tici/esim.py +++ b/system/hardware/tici/esim.py @@ -31,16 +31,7 @@ class TiciLPA(LPABase): def get_active_profile(self) -> Profile | None: return next((p for p in self.list_profiles() if p.enabled), None) - 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_profile(self, qr: str, nickname: str | None = None) -> None: - self._check_bootstrapped() 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) @@ -55,7 +46,6 @@ class TiciLPA(LPABase): self._validate_successful(self._invoke('profile', 'nickname', iccid, nickname)) def switch_profile(self, iccid: str) -> None: - self._check_bootstrapped() self._validate_profile_exists(iccid) latest = self.get_active_profile() if latest and latest.iccid == iccid: @@ -63,33 +53,10 @@ class TiciLPA(LPABase): self._validate_successful(self._invoke('profile', 'enable', iccid)) self._process_notifications() - def bootstrap(self) -> None: - """ - find all comma-provisioned profiles and delete them. they conflict with user-provisioned profiles - and must be deleted. - - **note**: this is a **very** destructive operation. you **must** purchase a new comma SIM in order - to use comma prime again. - """ - if self._is_bootstrapped(): - return - - for p in self.list_profiles(): - if self.is_comma_profile(p.iccid): - self._disable_profile(p.iccid) - self.delete_profile(p.iccid) - def _disable_profile(self, iccid: str) -> None: self._validate_successful(self._invoke('profile', 'disable', iccid)) self._process_notifications() - def _check_bootstrapped(self) -> None: - assert self._is_bootstrapped(), 'eUICC is not bootstrapped, please bootstrap before performing this operation' - - def _is_bootstrapped(self) -> bool: - """ check if any comma provisioned profiles are on the eUICC """ - return not any(self.is_comma_profile(iccid) for iccid in (p.iccid for p in self.list_profiles())) - def _invoke(self, *cmd: str): proc = subprocess.Popen(['sudo', '-E', 'lpac'] + list(cmd), stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=self.env) try: From ff755ed4bfec129d183b9d1fb573704ac041cdaa Mon Sep 17 00:00:00 2001 From: MVL Date: Sun, 30 Nov 2025 18:04:17 -0500 Subject: [PATCH 495/910] Honda - Rename AcuraWatch Plus to AcuraWatch (#36726) * Rename AcuraWatch Plus to AcuraWatch * Rename AcuraWatch Plus to AcuraWatch --- docs/CARS.md | 2 +- selfdrive/car/CARS_template.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index 223f452e1..fcb44154b 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -368,7 +368,7 @@ If your car has the following packages or features, then it's a good candidate f | Make | Required Package/Features | | ---- | ------------------------- | -| Acura | Any car with AcuraWatch Plus will work. AcuraWatch Plus comes standard on many newer models. | +| Acura | Any car with AcuraWatch will work. AcuraWatch comes standard on many newer models. | | Ford | Any car with Lane Centering will likely work. | | Honda | Any car with Honda Sensing will work. Honda Sensing comes standard on many newer models. | | Subaru | Any car with EyeSight will work. EyeSight comes standard on many newer models. | diff --git a/selfdrive/car/CARS_template.md b/selfdrive/car/CARS_template.md index 463683fd3..cd352b2ed 100644 --- a/selfdrive/car/CARS_template.md +++ b/selfdrive/car/CARS_template.md @@ -42,7 +42,7 @@ If your car has the following packages or features, then it's a good candidate f | Make | Required Package/Features | | ---- | ------------------------- | -| Acura | Any car with AcuraWatch Plus will work. AcuraWatch Plus comes standard on many newer models. | +| Acura | Any car with AcuraWatch will work. AcuraWatch comes standard on many newer models. | | Ford | Any car with Lane Centering will likely work. | | Honda | Any car with Honda Sensing will work. Honda Sensing comes standard on many newer models. | | Subaru | Any car with EyeSight will work. EyeSight comes standard on many newer models. | From 7521fd11e24e93148577608b8b8ec2c13208a011 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 30 Nov 2025 15:08:32 -0800 Subject: [PATCH 496/910] common: rename atomic_write_in_dir -> atomic_write (#36733) rename --- common/tests/test_file_helpers.py | 6 +++--- common/utils.py | 2 +- system/statsd.py | 4 ++-- tools/lib/url_file.py | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/common/tests/test_file_helpers.py b/common/tests/test_file_helpers.py index c7fe1984c..c2b880f87 100644 --- a/common/tests/test_file_helpers.py +++ b/common/tests/test_file_helpers.py @@ -1,7 +1,7 @@ import os from uuid import uuid4 -from openpilot.common.utils import atomic_write_in_dir +from openpilot.common.utils import atomic_write class TestFileHelpers: @@ -15,5 +15,5 @@ class TestFileHelpers: assert f.read() == "test" os.remove(path) - def test_atomic_write_in_dir(self): - self.run_atomic_write_func(atomic_write_in_dir) + def test_atomic_write(self): + self.run_atomic_write_func(atomic_write) diff --git a/common/utils.py b/common/utils.py index 89c0601f0..684c7aeb7 100644 --- a/common/utils.py +++ b/common/utils.py @@ -32,7 +32,7 @@ class CallbackReader: @contextlib.contextmanager -def atomic_write_in_dir(path: str, mode: str = 'w', buffering: int = -1, encoding: str | None = None, newline: str | None = None, +def atomic_write(path: str, mode: str = 'w', buffering: int = -1, encoding: str | None = None, newline: str | None = None, overwrite: bool = False): """Write to a file atomically using a temporary file in the same directory as the destination file.""" dir_name = os.path.dirname(path) diff --git a/system/statsd.py b/system/statsd.py index c4216f5e7..33e9e9912 100755 --- a/system/statsd.py +++ b/system/statsd.py @@ -13,7 +13,7 @@ from cereal.messaging import SubMaster from openpilot.system.hardware.hw import Paths from openpilot.common.swaglog import cloudlog from openpilot.system.hardware import HARDWARE -from openpilot.common.utils import atomic_write_in_dir +from openpilot.common.utils import atomic_write from openpilot.system.version import get_build_metadata from openpilot.system.loggerd.config import STATS_DIR_FILE_LIMIT, STATS_SOCKET, STATS_FLUSH_TIME_S @@ -167,7 +167,7 @@ def main() -> NoReturn: if len(os.listdir(STATS_DIR)) < STATS_DIR_FILE_LIMIT: if len(result) > 0: stats_path = os.path.join(STATS_DIR, f"{boot_uid}_{idx}") - with atomic_write_in_dir(stats_path) as f: + with atomic_write(stats_path) as f: f.write(result) idx += 1 else: diff --git a/tools/lib/url_file.py b/tools/lib/url_file.py index f988fa9db..2bf3ba820 100644 --- a/tools/lib/url_file.py +++ b/tools/lib/url_file.py @@ -9,7 +9,7 @@ from urllib3.util import Timeout from urllib3.exceptions import MaxRetryError -from openpilot.common.utils import atomic_write_in_dir +from openpilot.common.utils import atomic_write from openpilot.system.hardware.hw import Paths # Cache chunk size @@ -88,7 +88,7 @@ class URLFile: self._length = self.get_length_online() if not self._force_download and self._length != -1: - with atomic_write_in_dir(file_length_path, mode="w", overwrite=True) as file_length: + with atomic_write(file_length_path, mode="w", overwrite=True) as file_length: file_length.write(str(self._length)) return self._length @@ -111,7 +111,7 @@ class URLFile: # If we don't have a file, download it if not os.path.exists(full_path): data = self.read_aux(ll=CHUNK_SIZE) - with atomic_write_in_dir(full_path, mode="wb", overwrite=True) as new_cached_file: + with atomic_write(full_path, mode="wb", overwrite=True) as new_cached_file: new_cached_file.write(data) else: with open(full_path, "rb") as cached_file: From 436e3dec3edbb29e15ca45a08e227881a91b9947 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 30 Nov 2025 15:14:31 -0800 Subject: [PATCH 497/910] manager: write power monitor flag atomically (#36734) --- common/utils.py | 2 +- system/manager/manager.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/common/utils.py b/common/utils.py index 684c7aeb7..71b29a0c4 100644 --- a/common/utils.py +++ b/common/utils.py @@ -33,7 +33,7 @@ class CallbackReader: @contextlib.contextmanager def atomic_write(path: str, mode: str = 'w', buffering: int = -1, encoding: str | None = None, newline: str | None = None, - overwrite: bool = False): + overwrite: bool = False): """Write to a file atomically using a temporary file in the same directory as the destination file.""" dir_name = os.path.dirname(path) diff --git a/system/manager/manager.py b/system/manager/manager.py index 8db13346e..2d80c78ff 100755 --- a/system/manager/manager.py +++ b/system/manager/manager.py @@ -9,6 +9,7 @@ import traceback from cereal import log import cereal.messaging as messaging import openpilot.system.sentry as sentry +from openpilot.common.utils import atomic_write from openpilot.common.params import Params, ParamKeyFlag from openpilot.common.text_window import TextWindow from openpilot.system.hardware import HARDWARE @@ -162,7 +163,7 @@ def manager_thread() -> None: # kick AGNOS power monitoring watchdog try: if sm.all_checks(['deviceState']): - with open("/var/tmp/power_watchdog", "w") as f: + with atomic_write("/var/tmp/power_watchdog", "w", overwrite=True) as f: f.write(str(time.monotonic())) except Exception: pass From 151d256dd619bece66895c7ef7c98e3bb6bdf4db Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 30 Nov 2025 15:29:40 -0800 Subject: [PATCH 498/910] add param for agnos power monitor --- common/params_keys.h | 1 + 1 file changed, 1 insertion(+) diff --git a/common/params_keys.h b/common/params_keys.h index fc7f410e4..c496ad285 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -71,6 +71,7 @@ inline static std::unordered_map keys = { {"LastGPSPosition", {PERSISTENT, STRING}}, {"LastManagerExitReason", {CLEAR_ON_MANAGER_START, STRING}}, {"LastOffroadStatusPacket", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, JSON}}, + {"LastAgnosPowerMonitorShutdown", {CLEAR_ON_MANAGER_START, STRING}}, {"LastPowerDropDetected", {CLEAR_ON_MANAGER_START, STRING}}, {"LastUpdateException", {CLEAR_ON_MANAGER_START, STRING}}, {"LastUpdateRouteCount", {PERSISTENT, INT, "0"}}, From 749e236bc0bfba1de800bc39acb1ef2356bb8756 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Mon, 1 Dec 2025 08:06:33 +0800 Subject: [PATCH 499/910] ui: fix EGL_BAD_MATCH error when running profile_onroad.py on device (#36608) fix failed to create EGL image:12297 error on device --- selfdrive/ui/tests/profile_onroad.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/tests/profile_onroad.py b/selfdrive/ui/tests/profile_onroad.py index b1fa4acc4..fde4f25ff 100755 --- a/selfdrive/ui/tests/profile_onroad.py +++ b/selfdrive/ui/tests/profile_onroad.py @@ -88,9 +88,9 @@ if __name__ == "__main__": print("Running...") patch_submaster(message_chunks) - W, H = 1928, 1208 + W, H = 2048, 1216 vipc = VisionIpcServer("camerad") - vipc.create_buffers(VisionStreamType.VISION_STREAM_ROAD, 5, 1928, 1208) + vipc.create_buffers(VisionStreamType.VISION_STREAM_ROAD, 5, W, H) vipc.start_listener() yuv_buffer_size = W * H + (W // 2) * (H // 2) * 2 yuv_data = np.random.randint(0, 256, yuv_buffer_size, dtype=np.uint8).tobytes() From 8ffe3f287e735864368f4096f5563d52ee3f08d1 Mon Sep 17 00:00:00 2001 From: Trey Moen <50057480+greatgitsby@users.noreply.github.com> Date: Sun, 30 Nov 2025 16:09:50 -0800 Subject: [PATCH 500/910] fix: openpilot build on ubuntu aarch64 (#36675) breaks on linux --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 3f3c8a72b..b2acf1c09 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -125,7 +125,7 @@ dev = [ tools = [ "metadrive-simulator @ https://github.com/commaai/metadrive/releases/download/MetaDrive-minimal-0.4.2.4/metadrive_simulator-0.4.2.4-py3-none-any.whl ; (platform_machine != 'aarch64')", - "dearpygui>=2.1.0", + "dearpygui>=2.1.0; (sys_platform != 'linux' or platform_machine != 'aarch64')", # not vended for linux aarch64 ] [project.urls] From 79fa8803b6499094d33abe788a9766ad2b003ead Mon Sep 17 00:00:00 2001 From: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Mon, 1 Dec 2025 06:22:49 -0800 Subject: [PATCH 501/910] ui: add padding above tree dialog buttons (#1533) add padding --- system/ui/sunnypilot/widgets/tree_dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/ui/sunnypilot/widgets/tree_dialog.py b/system/ui/sunnypilot/widgets/tree_dialog.py index aa2904424..6458ebf00 100644 --- a/system/ui/sunnypilot/widgets/tree_dialog.py +++ b/system/ui/sunnypilot/widgets/tree_dialog.py @@ -160,7 +160,7 @@ class TreeOptionDialog(MultiOptionDialog): gui_label(rl.Rectangle(dialog_content_rect.x + 50, dialog_content_rect.y + 50, dialog_content_rect.width - 100, 70), self.title, 70, font_weight=FontWeight.BOLD) - options_area_rect = rl.Rectangle(dialog_content_rect.x + 50, dialog_content_rect.y + 170, dialog_content_rect.width - 100, dialog_content_rect.height - 330) + options_area_rect = rl.Rectangle(dialog_content_rect.x + 50, dialog_content_rect.y + 170, dialog_content_rect.width - 100, dialog_content_rect.height - 380) for index, option_text in enumerate(self.options): self.option_buttons[index].selected = (option_text == self.selection) self.option_buttons[index].set_button_style(ButtonStyle.PRIMARY if option_text == self.selection else ButtonStyle.NORMAL) From 693c83f74c57c91fffd51ef1edf8cef74870391d Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Tue, 2 Dec 2025 05:32:21 +0800 Subject: [PATCH 502/910] replay: fix dangling pointers in logging calls (#36738) fix dangling pointers in logging calls --- tools/replay/replay.cc | 6 +++++- tools/replay/seg_mgr.cc | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/tools/replay/replay.cc b/tools/replay/replay.cc index c9ab7e7e2..fb13ead03 100644 --- a/tools/replay/replay.cc +++ b/tools/replay/replay.cc @@ -31,6 +31,8 @@ void Replay::setupServices(const std::vector &allow, const std::vec sockets_.resize(event_schema.getUnionFields().size(), nullptr); std::vector active_services; + active_services.reserve(services.size()); + for (const auto &[name, _] : services) { bool is_blocked = std::find(block.begin(), block.end(), name) != block.end(); bool is_allowed = allow.empty() || std::find(allow.begin(), allow.end(), name) != allow.end(); @@ -40,7 +42,9 @@ void Replay::setupServices(const std::vector &allow, const std::vec active_services.push_back(name.c_str()); } } - rInfo("active services: %s", join(active_services, ", ").c_str()); + + std::string services_str = join(active_services, ", "); + rInfo("active services: %s", services_str.c_str()); if (!sm_) { pm_ = std::make_unique(active_services); } diff --git a/tools/replay/seg_mgr.cc b/tools/replay/seg_mgr.cc index ee034fb08..f4e865d47 100644 --- a/tools/replay/seg_mgr.cc +++ b/tools/replay/seg_mgr.cc @@ -91,7 +91,8 @@ bool SegmentManager::mergeSegments(const SegmentMap::iterator &begin, const Segm auto &merged_events = merged_event_data->events; merged_events.reserve(total_event_count); - rDebug("merging segments: %s", join(segments_to_merge, ", ").c_str()); + std::string segments_str = join(segments_to_merge, ", "); + rDebug("merging segments: %s", segments_str.c_str()); for (int n : segments_to_merge) { const auto &events = segments_.at(n)->log->events; if (events.empty()) continue; From 859745ea8615817481ce8e5f25971f2f3c8f4a41 Mon Sep 17 00:00:00 2001 From: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Mon, 1 Dec 2025 14:31:37 -0800 Subject: [PATCH 503/910] ui: tree dialog improvements (#1537) * ui: highlight on pressed, and less indent * inherit MultiOptionDialog main buttons * align top level folders to the edge properly * lint * handle folder presses too --------- Co-authored-by: Jason Wen --- system/ui/sunnypilot/widgets/tree_dialog.py | 57 ++++++++------------- 1 file changed, 22 insertions(+), 35 deletions(-) diff --git a/system/ui/sunnypilot/widgets/tree_dialog.py b/system/ui/sunnypilot/widgets/tree_dialog.py index 6458ebf00..5685a6c27 100644 --- a/system/ui/sunnypilot/widgets/tree_dialog.py +++ b/system/ui/sunnypilot/widgets/tree_dialog.py @@ -11,8 +11,8 @@ from openpilot.common.params import Params from openpilot.system.ui.lib.application import FontWeight, gui_app from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.widgets import DialogResult -from openpilot.system.ui.widgets.button import Button, ButtonStyle -from openpilot.system.ui.widgets.label import gui_label, Label +from openpilot.system.ui.widgets.button import Button, ButtonStyle, BUTTON_PRESSED_BACKGROUND_COLORS +from openpilot.system.ui.widgets.label import gui_label from openpilot.system.ui.widgets.option_dialog import MultiOptionDialog from openpilot.system.ui.sunnypilot.lib.styles import style @@ -34,7 +34,7 @@ class TreeFolder: class TreeItemWidget(Button): - def __init__(self, text, ref, is_folder=False, indent_level=0, click_callback=None, favorite_callback=None, is_favorite=False): + def __init__(self, text, ref, is_folder=False, indent_level=0, click_callback=None, favorite_callback=None, is_favorite=False, is_expanded=False): super().__init__(text, click_callback, button_style=ButtonStyle.NORMAL, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT, text_padding=20 + indent_level * 30, elide_right=True) self.text = text @@ -46,14 +46,21 @@ class TreeItemWidget(Button): self._favorite_callback = favorite_callback self.text_padding = 20 + indent_level * 30 self.border_radius = 10 + self.is_expanded = is_expanded def _render(self, rect): - indent = 60 * self.indent_level if self.indent_level > 0 else 10 + indent = 60 * self.indent_level self._rect = rl.Rectangle(rect.x + indent, rect.y, rect.width - indent, rect.height) - color = style.BUTTON_PRIMARY_COLOR if self.selected and not (self.ref == "search_bar" or self.is_folder) else style.BUTTON_DISABLED_BG_COLOR + if self.is_pressed: + color = BUTTON_PRESSED_BACKGROUND_COLORS[self._button_style] + elif self.selected and self.ref != "search_bar": + color = style.BUTTON_PRIMARY_COLOR + else: + color = style.BUTTON_DISABLED_BG_COLOR roundness = self.border_radius / (min(self._rect.width, self._rect.height) / 2) rl.draw_rectangle_rounded(self._rect, roundness, 10, color) - text_rect = rl.Rectangle(self._rect.x + self.text_padding + 20, self._rect.y, self._rect.width - self.text_padding - 20 - 90, self._rect.height) + text_offset = self.text_padding + 20 - 15 if self.is_expanded and not self.is_folder and self.indent_level > 0 else self.text_padding + 20 + text_rect = rl.Rectangle(self._rect.x + text_offset, self._rect.y, self._rect.width - self.text_padding - 20 - 90, self._rect.height) self._label.render(text_rect) if not self.is_folder and self._favorite_callback: @@ -86,8 +93,6 @@ class TreeOptionDialog(MultiOptionDialog): self.display_func = display_func or (lambda node: node.data.get('display_name', node.ref)) self.search_funcs = search_funcs or [lambda node: node.data.get('display_name', ''), lambda node: node.data.get('short_name', '')] self._build_visible_items() - self.cancel_rect = None - self.select_rect = None def _on_search_confirm(self, result, text): if result == DialogResult.CONFIRM: @@ -134,26 +139,14 @@ class TreeOptionDialog(MultiOptionDialog): for node in nodes: favorite_cb = (lambda node_ref=node: self._toggle_favorite(node_ref)) if self.fav_param and node.ref != "Default" else None self.visible_items.append(TreeItemWidget(self.display_func(node), node.ref, False, 1 if folder.folder else 0, - lambda node_ref=node: self._select_node(node_ref), favorite_cb, node.ref in self.favorites)) + lambda node_ref=node: self._select_node(node_ref), + favorite_cb, node.ref in self.favorites, is_expanded=expanded)) self.option_buttons = self.visible_items self.options = [item.text for item in self.visible_items] self.scroller._items = self.visible_items if reset_scroll: self.scroller.scroll_panel.set_offset(0) - def _draw_button(self, button_rect, button_text, is_primary=False, is_enabled=True): - if is_primary and is_enabled: - button_color = style.BUTTON_PRIMARY_COLOR - elif not is_enabled: - button_color = style.BUTTON_NEUTRAL_GRAY - else: - button_color = style.BUTTON_DISABLED_BG_COLOR - roundness = 10 / (min(button_rect.width, button_rect.height) / 2) - rl.draw_rectangle_rounded(button_rect, roundness, 10, button_color) - label = Label(button_text, 60, FontWeight.NORMAL, rl.GuiTextAlignment.TEXT_ALIGN_CENTER, - text_color=rl.WHITE if is_enabled else rl.GRAY) - label.render(button_rect) - def _render(self, rect): dialog_content_rect = rl.Rectangle(rect.x + 50, rect.y + 50, rect.width - 100, rect.height - 100) rl.draw_rectangle_rounded(dialog_content_rect, 0.02, 20, rl.BLACK) @@ -169,18 +162,12 @@ class TreeOptionDialog(MultiOptionDialog): button_width = (dialog_content_rect.width - 150) / 2 button_y_position = dialog_content_rect.y + dialog_content_rect.height - 160 - self.cancel_rect = rl.Rectangle(dialog_content_rect.x + 50, button_y_position, button_width, 160) - self.select_rect = rl.Rectangle(dialog_content_rect.x + 100 + button_width, button_y_position, button_width, 160) - self._draw_button(self.cancel_rect, tr("Cancel")) - self._draw_button(self.select_rect, tr("Select"), True, self.selection != self.current) + cancel_rect = rl.Rectangle(dialog_content_rect.x + 50, button_y_position, button_width, 160) + self.cancel_button.render(cancel_rect) + + select_rect = rl.Rectangle(dialog_content_rect.x + 100 + button_width, button_y_position, button_width, 160) + self.select_button.set_enabled(self.selection != self.current) + self.select_button.render(select_rect) + return self._result - - def _handle_mouse_release(self, mouse_pos): - if self.cancel_rect and rl.check_collision_point_rec(mouse_pos, self.cancel_rect): - self._set_result(DialogResult.CANCEL) - return True - if self.select_rect and rl.check_collision_point_rec(mouse_pos, self.select_rect) and self.selection != self.current: - self._set_result(DialogResult.CONFIRM) - return True - return super()._handle_mouse_release(mouse_pos) From 9ee965d2e0690640ac047888b3718b177f17063a Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 1 Dec 2025 21:35:28 -0500 Subject: [PATCH 504/910] ui: overridable title and subtitle for search query in `TreeOptionDialog` (#1538) * ui: overridable title and subtitle for `TreeOptionDialog` * lint --- system/ui/sunnypilot/widgets/input_dialog.py | 11 ++++++----- system/ui/sunnypilot/widgets/tree_dialog.py | 15 +++++++++++++-- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/system/ui/sunnypilot/widgets/input_dialog.py b/system/ui/sunnypilot/widgets/input_dialog.py index 88ab0b1a6..ed67302fc 100644 --- a/system/ui/sunnypilot/widgets/input_dialog.py +++ b/system/ui/sunnypilot/widgets/input_dialog.py @@ -8,7 +8,6 @@ from collections.abc import Callable from openpilot.common.params import Params from openpilot.system.ui.lib.application import gui_app -from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.widgets import DialogResult from openpilot.system.ui.widgets.keyboard import Keyboard @@ -27,14 +26,16 @@ class InputDialogSP: def show(self): self.keyboard.reset(min_text_size=self.keyboard._min_text_size) - self.keyboard.set_title(tr(self.title), *(tr(self.sub_title),) if self.sub_title else ()) + if self.sub_title: + self.keyboard.set_title(self.title, self.sub_title) + else: + self.keyboard.set_title(self.title) self.keyboard.set_text(self.current_text) def internal_callback(result: DialogResult): text = self.keyboard.text if result == DialogResult.CONFIRM else "" - if result == DialogResult.CONFIRM: - if self.param: - self._params.put(self.param, text) + if result == DialogResult.CONFIRM and self.param: + self._params.put(self.param, text) if self.callback: self.callback(result, text) diff --git a/system/ui/sunnypilot/widgets/tree_dialog.py b/system/ui/sunnypilot/widgets/tree_dialog.py index 5685a6c27..4691f7f8f 100644 --- a/system/ui/sunnypilot/widgets/tree_dialog.py +++ b/system/ui/sunnypilot/widgets/tree_dialog.py @@ -77,7 +77,7 @@ class TreeItemWidget(Button): class TreeOptionDialog(MultiOptionDialog): def __init__(self, title, folders, current_ref="", fav_param="", option_font_weight=FontWeight.MEDIUM, search_prompt=None, - get_folders_fn=None, on_exit=None, display_func=None, search_funcs=None): + get_folders_fn=None, on_exit=None, display_func=None, search_funcs=None, search_title=None, search_subtitle=None): super().__init__(title, [], "", option_font_weight) self.folders = folders self.selection_ref = current_ref @@ -92,6 +92,17 @@ class TreeOptionDialog(MultiOptionDialog): self.on_exit = on_exit self.display_func = display_func or (lambda node: node.data.get('display_name', node.ref)) self.search_funcs = search_funcs or [lambda node: node.data.get('display_name', ''), lambda node: node.data.get('short_name', '')] + + # Default title & overridable subtitle for InputDialogSP + self.search_title = search_title or tr("Enter search query") + self.search_subtitle = search_subtitle + self.search_dialog = InputDialogSP( + self.search_title, + self.search_subtitle, + current_text=self.query, + callback=self._on_search_confirm, + ) + self._build_visible_items() def _on_search_confirm(self, result, text): @@ -101,7 +112,7 @@ class TreeOptionDialog(MultiOptionDialog): gui_app.set_modal_overlay(self, callback=self.on_exit) def _on_search_clicked(self): - InputDialogSP(tr("Enter search query"), current_text=self.query, callback=self._on_search_confirm).show() + self.search_dialog.show() def _toggle_folder(self, folder): if folder.folder: From f312c011e8b3d9d0285be5fdeafcdcf161f84b93 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 1 Dec 2025 23:33:26 -0500 Subject: [PATCH 505/910] ui: magnifying glass icon and new search bar style in `TreeDialog` (#1541) * ui: magnifying glass icon and new search bar style in `TreeDialog` * cleanup --- system/ui/sunnypilot/widgets/tree_dialog.py | 63 +++++++++++++++++++-- 1 file changed, 59 insertions(+), 4 deletions(-) diff --git a/system/ui/sunnypilot/widgets/tree_dialog.py b/system/ui/sunnypilot/widgets/tree_dialog.py index 4691f7f8f..a8947a9b1 100644 --- a/system/ui/sunnypilot/widgets/tree_dialog.py +++ b/system/ui/sunnypilot/widgets/tree_dialog.py @@ -92,6 +92,8 @@ class TreeOptionDialog(MultiOptionDialog): self.on_exit = on_exit self.display_func = display_func or (lambda node: node.data.get('display_name', node.ref)) self.search_funcs = search_funcs or [lambda node: node.data.get('display_name', ''), lambda node: node.data.get('short_name', '')] + self._search_rect = None + self._search_width = 0.475 # Default title & overridable subtitle for InputDialogSP self.search_title = search_title or tr("Enter search query") @@ -137,7 +139,7 @@ class TreeOptionDialog(MultiOptionDialog): self._build_visible_items(reset_scroll=False) def _build_visible_items(self, reset_scroll=True): - self.visible_items = [TreeItemWidget(self.query or self.search_prompt, "search_bar", False, 0, self._on_search_clicked)] + self.visible_items = [] for folder in self.folders: nodes = [node for node in folder.nodes if not self.query or search_from_list(self.query, [search_func(node) for search_func in self.search_funcs])] if not nodes and self.query: @@ -161,10 +163,57 @@ class TreeOptionDialog(MultiOptionDialog): def _render(self, rect): dialog_content_rect = rl.Rectangle(rect.x + 50, rect.y + 50, rect.width - 100, rect.height - 100) rl.draw_rectangle_rounded(dialog_content_rect, 0.02, 20, rl.BLACK) - gui_label(rl.Rectangle(dialog_content_rect.x + 50, dialog_content_rect.y + 50, dialog_content_rect.width - 100, 70), - self.title, 70, font_weight=FontWeight.BOLD) - options_area_rect = rl.Rectangle(dialog_content_rect.x + 50, dialog_content_rect.y + 170, dialog_content_rect.width - 100, dialog_content_rect.height - 380) + # Title on the left + title_rect = rl.Rectangle(dialog_content_rect.x + 50, dialog_content_rect.y + 50, dialog_content_rect.width * 0.5, 70) + gui_label(title_rect, self.title, 70, font_weight=FontWeight.BOLD) + + # Search bar on the top right + search_width = dialog_content_rect.width * self._search_width + search_height = 110 + search_x = dialog_content_rect.x + dialog_content_rect.width - 50 - search_width + search_y = dialog_content_rect.y + 40 # align roughly with title + + self._search_rect = rl.Rectangle(search_x, search_y, search_width, search_height) + + # Draw search field + inset = 4 + roundness = 0.3 + input_rect = rl.Rectangle(self._search_rect.x + inset, self._search_rect.y + inset, + self._search_rect.width - inset * 2, self._search_rect.height - inset * 2) + + # Transparent fill + border + rl.draw_rectangle_rounded(input_rect, roundness, 10, rl.Color(0, 0, 0, 0)) + rl.draw_rectangle_rounded_lines_ex(input_rect, roundness, 10, 3, rl.Color(150, 150, 150, 200)) + + # Magnifying glass icon + icon_color = rl.Color(180, 180, 180, 240) + cx = input_rect.x + 60 + cy = input_rect.y + input_rect.height / 2 - 5 + radius = min(input_rect.height * 0.28, 26) + + circle_thickness = 4 + for i in range(circle_thickness): + rl.draw_circle_lines(int(cx), int(cy), radius - i, icon_color) + + handle_thickness = 5 + inner_x = cx + radius * 0.65 + inner_y = cy + radius * 0.65 + outer_x = cx + radius * 1.45 + outer_y = cy + radius * 1.45 + + rl.draw_line_ex(rl.Vector2(inner_x, inner_y), rl.Vector2(outer_x, outer_y), handle_thickness, icon_color) + + # User text (query), placed after the icon if present + if self.query: + text_start_x = outer_x + 45 + text_rect = rl.Rectangle(text_start_x, input_rect.y, input_rect.x + input_rect.width - text_start_x - 10, input_rect.height) + gui_label(text_rect, self.query, 70, font_weight=FontWeight.MEDIUM) + + options_top = self._search_rect.y + self._search_rect.height + 40 + options_area_rect = rl.Rectangle(dialog_content_rect.x + 50, options_top, dialog_content_rect.width - 100, + dialog_content_rect.height - (options_top - dialog_content_rect.y) - 210) + for index, option_text in enumerate(self.options): self.option_buttons[index].selected = (option_text == self.selection) self.option_buttons[index].set_button_style(ButtonStyle.PRIMARY if option_text == self.selection else ButtonStyle.NORMAL) @@ -182,3 +231,9 @@ class TreeOptionDialog(MultiOptionDialog): self.select_button.render(select_rect) return self._result + + def _handle_mouse_release(self, mouse_pos): + if self._search_rect and rl.check_collision_point_rec(mouse_pos, self._search_rect): + self._on_search_clicked() + return True + return super()._handle_mouse_release(mouse_pos) From 7ba9876fa4586cf50c8110b0807f0bb3ca65e8ed Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 1 Dec 2025 23:44:01 -0500 Subject: [PATCH 506/910] ui: recreate search dialog with the latest query (#1542) --- system/ui/sunnypilot/widgets/tree_dialog.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/system/ui/sunnypilot/widgets/tree_dialog.py b/system/ui/sunnypilot/widgets/tree_dialog.py index a8947a9b1..fd2a576d5 100644 --- a/system/ui/sunnypilot/widgets/tree_dialog.py +++ b/system/ui/sunnypilot/widgets/tree_dialog.py @@ -98,12 +98,7 @@ class TreeOptionDialog(MultiOptionDialog): # Default title & overridable subtitle for InputDialogSP self.search_title = search_title or tr("Enter search query") self.search_subtitle = search_subtitle - self.search_dialog = InputDialogSP( - self.search_title, - self.search_subtitle, - current_text=self.query, - callback=self._on_search_confirm, - ) + self.search_dialog = None self._build_visible_items() @@ -114,6 +109,12 @@ class TreeOptionDialog(MultiOptionDialog): gui_app.set_modal_overlay(self, callback=self.on_exit) def _on_search_clicked(self): + self.search_dialog = InputDialogSP( + self.search_title, + self.search_subtitle, + current_text=self.query, + callback=self._on_search_confirm, + ) self.search_dialog.show() def _toggle_folder(self, folder): From dc654b439a946972b2f476465407fb2e743f576a Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Mon, 1 Dec 2025 20:48:04 -0800 Subject: [PATCH 507/910] Revert "Fix raylib ui spamming API calls (#36700)" (#36744) This reverts commit 26261387f8b4188951d107567a067d22cf87e5e3. --- common/params_keys.h | 2 +- common/tests/test_params.py | 4 +- selfdrive/ui/lib/api_helpers.py | 93 +------------------ selfdrive/ui/lib/prime_state.py | 75 +++++++++++---- .../ui/mici/layouts/settings/firehose.py | 65 +++++++++---- 5 files changed, 108 insertions(+), 131 deletions(-) diff --git a/common/params_keys.h b/common/params_keys.h index c496ad285..d6104e749 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -10,7 +10,7 @@ inline static std::unordered_map keys = { {"AdbEnabled", {PERSISTENT, BOOL}}, {"AlwaysOnDM", {PERSISTENT, BOOL}}, {"ApiCache_Device", {PERSISTENT, STRING}}, - {"ApiCache_FirehoseStats", {PERSISTENT, STRING}}, + {"ApiCache_FirehoseStats", {PERSISTENT, JSON}}, {"AssistNowToken", {PERSISTENT, STRING}}, {"AthenadPid", {PERSISTENT, INT}}, {"AthenadUploadQueue", {PERSISTENT, JSON}}, diff --git a/common/tests/test_params.py b/common/tests/test_params.py index e0f213e02..592bf2c4b 100644 --- a/common/tests/test_params.py +++ b/common/tests/test_params.py @@ -123,8 +123,8 @@ class TestParams: def test_params_get_type(self): # json - self.params.put("LiveParameters", {"a": 0}) - assert self.params.get("LiveParameters") == {"a": 0} + self.params.put("ApiCache_FirehoseStats", {"a": 0}) + assert self.params.get("ApiCache_FirehoseStats") == {"a": 0} # int self.params.put("BootCount", 1441) diff --git a/selfdrive/ui/lib/api_helpers.py b/selfdrive/ui/lib/api_helpers.py index 31e844dd0..8ed1c22a6 100644 --- a/selfdrive/ui/lib/api_helpers.py +++ b/selfdrive/ui/lib/api_helpers.py @@ -1,13 +1,7 @@ import time -import threading -from collections.abc import Callable from functools import lru_cache - -from openpilot.common.api import Api, api_get -from openpilot.common.params import Params -from openpilot.common.swaglog import cloudlog +from openpilot.common.api import Api from openpilot.common.time_helpers import system_time_valid -from openpilot.system.athena.registration import UNREGISTERED_DONGLE_ID TOKEN_EXPIRY_HOURS = 2 @@ -22,88 +16,3 @@ def _get_token(dongle_id: str, t: int): def get_token(dongle_id: str): return _get_token(dongle_id, int(time.monotonic() / (TOKEN_EXPIRY_HOURS / 2 * 60 * 60))) - - -class RequestRepeater: - API_TIMEOUT = 10.0 # seconds for API requests - SLEEP_INTERVAL = 0.5 # seconds to sleep between checks in the worker thread - - def __init__(self, dongle_id: str, request_route: str, period: int, cache_key: str | None = None): - self._dongle_id = dongle_id - self._request_route = request_route - self._period = period # seconds - self._cache_key = cache_key - - self._request_done_callbacks: list[Callable[[str, bool], None]] = [] - self._prev_response_text = None - self._running = False - self._thread = None - self._params = Params() - - if self._cache_key is not None: - # Cache successful responses to params - def cache_response(response: str, success: bool): - if success and response != self._prev_response_text: - self._params.put(self._cache_key, response) - self._prev_response_text = response - - self.add_request_done_callback(cache_response) - - def add_request_done_callback(self, callback: Callable[[str, bool], None]): - self._request_done_callbacks.append(callback) - - def _do_callbacks(self, response_text: str, success: bool): - for callback in self._request_done_callbacks: - try: - callback(response_text, success) - except Exception as e: - cloudlog.error(f"RequestRepeater callback error: {e}") - - def load_cache(self): - # call callbacks with cached response - if self._cache_key is not None: - self._prev_response_text = self._params.get(self._cache_key) - if self._prev_response_text: - self._do_callbacks(self._prev_response_text, True) - - def start(self): - if self._thread and self._thread.is_alive(): - return - self._running = True - self._thread = threading.Thread(target=self._worker_thread, daemon=True) - self._thread.start() - - def stop(self): - self._running = False - if self._thread and self._thread.is_alive(): - self._thread.join(timeout=1.0) - - def _worker_thread(self): - # Avoid circular imports - from openpilot.selfdrive.ui.ui_state import ui_state, device - - while self._running: - # Don't run when device is asleep or onroad - if not ui_state.started and device.awake: - self._send_request() - - for _ in range(int(self._period / self.SLEEP_INTERVAL)): - if not self._running: - break - time.sleep(self.SLEEP_INTERVAL) - - def _send_request(self): - if not self._dongle_id or self._dongle_id == UNREGISTERED_DONGLE_ID: - return - - try: - identity_token = get_token(self._dongle_id) - response = api_get(self._request_route, timeout=self.API_TIMEOUT, access_token=identity_token) - self._do_callbacks(response.text, 200 <= response.status_code < 300) - - except Exception as e: - cloudlog.error(f"Failed to send request to {self._request_route}: {e}") - self._do_callbacks("", False) - - def __del__(self): - self.stop() diff --git a/selfdrive/ui/lib/prime_state.py b/selfdrive/ui/lib/prime_state.py index f3adebf4b..fc72b4f9c 100644 --- a/selfdrive/ui/lib/prime_state.py +++ b/selfdrive/ui/lib/prime_state.py @@ -1,10 +1,13 @@ from enum import IntEnum import os -import json +import threading +import time +from openpilot.common.api import api_get from openpilot.common.params import Params from openpilot.common.swaglog import cloudlog -from openpilot.selfdrive.ui.lib.api_helpers import RequestRepeater +from openpilot.system.athena.registration import UNREGISTERED_DONGLE_ID +from openpilot.selfdrive.ui.lib.api_helpers import get_token class PrimeType(IntEnum): @@ -19,14 +22,17 @@ class PrimeType(IntEnum): class PrimeState: + FETCH_INTERVAL = 5.0 # seconds between API calls + API_TIMEOUT = 10.0 # seconds for API requests + SLEEP_INTERVAL = 0.5 # seconds to sleep between checks in the worker thread + def __init__(self): self._params = Params() + self._lock = threading.Lock() self.prime_type: PrimeType = self._load_initial_state() - dongle_id = self._params.get("DongleId") - self._request_repeater = RequestRepeater(dongle_id, f"v1.1/devices/{dongle_id}", 5, "ApiCache_Device") - self._request_repeater.add_request_done_callback(self._handle_reply) - self._request_repeater.load_cache() # sets prime_type from API response cache + self._running = False + self._thread = None def _load_initial_state(self) -> PrimeType: prime_type_str = os.getenv("PRIME_TYPE") or self._params.get("PrimeType") @@ -37,32 +43,61 @@ class PrimeState: pass return PrimeType.UNKNOWN - def _handle_reply(self, response: str, success: bool): - if not success: + def _fetch_prime_status(self) -> None: + dongle_id = self._params.get("DongleId") + if not dongle_id or dongle_id == UNREGISTERED_DONGLE_ID: return try: - data = json.loads(response) - is_paired = data.get("is_paired", False) - prime_type = data.get("prime_type", 0) - self.set_type(PrimeType(prime_type) if is_paired else PrimeType.UNPAIRED) + identity_token = get_token(dongle_id) + response = api_get(f"v1.1/devices/{dongle_id}", timeout=self.API_TIMEOUT, access_token=identity_token) + if response.status_code == 200: + data = response.json() + is_paired = data.get("is_paired", False) + prime_type = data.get("prime_type", 0) + self.set_type(PrimeType(prime_type) if is_paired else PrimeType.UNPAIRED) except Exception as e: cloudlog.error(f"Failed to fetch prime status: {e}") def set_type(self, prime_type: PrimeType) -> None: - if prime_type != self.prime_type: - self.prime_type = prime_type - self._params.put("PrimeType", int(prime_type)) - cloudlog.info(f"Prime type updated to {prime_type}") + with self._lock: + if prime_type != self.prime_type: + self.prime_type = prime_type + self._params.put("PrimeType", int(prime_type)) + cloudlog.info(f"Prime type updated to {prime_type}") + + def _worker_thread(self) -> None: + while self._running: + self._fetch_prime_status() + + for _ in range(int(self.FETCH_INTERVAL / self.SLEEP_INTERVAL)): + if not self._running: + break + time.sleep(self.SLEEP_INTERVAL) def start(self) -> None: - self._request_repeater.start() + if self._thread and self._thread.is_alive(): + return + self._running = True + self._thread = threading.Thread(target=self._worker_thread, daemon=True) + self._thread.start() + + def stop(self) -> None: + self._running = False + if self._thread and self._thread.is_alive(): + self._thread.join(timeout=1.0) def get_type(self) -> PrimeType: - return self.prime_type + with self._lock: + return self.prime_type def is_prime(self) -> bool: - return bool(self.prime_type > PrimeType.NONE) + with self._lock: + return bool(self.prime_type > PrimeType.NONE) def is_paired(self) -> bool: - return self.prime_type > PrimeType.UNPAIRED + with self._lock: + return self.prime_type > PrimeType.UNPAIRED + + def __del__(self): + self.stop() diff --git a/selfdrive/ui/mici/layouts/settings/firehose.py b/selfdrive/ui/mici/layouts/settings/firehose.py index ff75e6f8e..dcb4b6c84 100644 --- a/selfdrive/ui/mici/layouts/settings/firehose.py +++ b/selfdrive/ui/mici/layouts/settings/firehose.py @@ -1,15 +1,18 @@ +import threading +import time import pyray as rl -import json +from openpilot.common.api import api_get from openpilot.common.params import Params from openpilot.common.swaglog import cloudlog +from openpilot.selfdrive.ui.lib.api_helpers import get_token from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.system.athena.registration import UNREGISTERED_DONGLE_ID from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE from openpilot.system.ui.lib.wrap_text import wrap_text from openpilot.system.ui.lib.scroll_panel2 import GuiScrollPanel2 from openpilot.system.ui.lib.multilang import tr, trn, tr_noop from openpilot.system.ui.widgets import Widget, NavWidget -from openpilot.selfdrive.ui.lib.api_helpers import RequestRepeater TITLE = tr_noop("Firehose Mode") DESCRIPTION = tr_noop( @@ -31,37 +34,47 @@ FAQ_ITEMS = [ class FirehoseLayoutBase(Widget): + PARAM_KEY = "ApiCache_FirehoseStats" GREEN = rl.Color(46, 204, 113, 255) RED = rl.Color(231, 76, 60, 255) GRAY = rl.Color(68, 68, 68, 255) LIGHT_GRAY = rl.Color(228, 228, 228, 255) + UPDATE_INTERVAL = 30 # seconds def __init__(self): super().__init__() - self._segment_count = 0 + self._params = Params() + self._segment_count = self._get_segment_count() + self._scroll_panel = GuiScrollPanel2(horizontal=False) self._content_height = 0 - dongle_id = Params().get("DongleId") - self._request_repeater = RequestRepeater(dongle_id, f"v1/devices/{dongle_id}/firehose_stats", 30, "ApiCache_FirehoseStats") - self._request_repeater.add_request_done_callback(self._handle_reply) - self._request_repeater.load_cache() - self._request_repeater.start() - - def _handle_reply(self, response: str, success: bool): - if not success: - return + self._running = True + self._update_thread = threading.Thread(target=self._update_loop, daemon=True) + self._update_thread.start() + def __del__(self): + self._running = False try: - data = json.loads(response) - self._segment_count = data.get("firehose", 0) - except Exception as e: - cloudlog.error(f"Failed to fetch firehose stats: {e}") + if self._update_thread and self._update_thread.is_alive(): + self._update_thread.join(timeout=1.0) + except Exception: + pass def show_event(self): super().show_event() self._scroll_panel.set_offset(0) + def _get_segment_count(self) -> int: + stats = self._params.get(self.PARAM_KEY) + if not stats: + return 0 + try: + return int(stats.get("firehose", 0)) + except Exception: + cloudlog.exception(f"Failed to decode firehose stats: {stats}") + return 0 + def _render(self, rect: rl.Rectangle): # compute total content height for scrolling content_height = self._measure_content_height(rect) @@ -184,6 +197,26 @@ class FirehoseLayoutBase(Widget): else: return tr("INACTIVE: connect to an unmetered network"), self.RED + def _fetch_firehose_stats(self): + try: + dongle_id = self._params.get("DongleId") + if not dongle_id or dongle_id == UNREGISTERED_DONGLE_ID: + return + identity_token = get_token(dongle_id) + response = api_get(f"v1/devices/{dongle_id}/firehose_stats", access_token=identity_token) + if response.status_code == 200: + data = response.json() + self._segment_count = data.get("firehose", 0) + self._params.put(self.PARAM_KEY, data) + except Exception as e: + cloudlog.error(f"Failed to fetch firehose stats: {e}") + + def _update_loop(self): + while self._running: + if not ui_state.started: + self._fetch_firehose_stats() + time.sleep(self.UPDATE_INTERVAL) + class FirehoseLayout(FirehoseLayoutBase, NavWidget): BACK_TOUCH_AREA_PERCENTAGE = 0.1 From 04504d47f3215de63b6fc7240087a9e0ac5468a6 Mon Sep 17 00:00:00 2001 From: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Mon, 1 Dec 2025 21:02:33 -0800 Subject: [PATCH 508/910] ui: Platform Selector (#1508) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * param to control stock vs sp ui * init styles * SP Toggles * Lint * optimizations * Panels. With Icons. And Scroller. * patience, grasshopper * more patience, grasshopper * sp raylib preview * fix callback * fix ui preview * add ui previews * dialog txt * compare vs what used to be done before InputDialog * introducing ui_state_sp for py * param to control stock vs sp ui * better * add ui_update callback * better padding * this * listitem -> listitemsp * Revert "add ui_update callback" This reverts commit 4da32cc0097434aab0aa6a3c35465eabb23c8958. * add show_description method * remove padding from line separator. like, WHY? 😩😩 * scroller -> scroller_tici * scroller -> scroller_tici * ui: `GuiApplicationExt` * add to readme * use gui_app.sunnypilot_ui() * use gui_app.sunnypilot_ui() * use gui_app.sunnypilot_ui() * lint * no fancy toggles :( * match them * mici scroller - no touchy * no * more * size adjustments * fix scroller. yay * ui vehicle panel: platform selector * platform selector * bruh * ui_state_sp * huh * rebase * rebae * vic * # Conflicts: # system/ui/sunnypilot/lib/styles.py # system/ui/sunnypilot/widgets/helpers/fuzzy_search.py * loathing loathing, unadulterated loathing, i loathe it all * more changes! * Update styles.py * add padding * use symlink on sp side * use make from json and show all actual makes * all done! * Revert "all done!" This reverts commit 595c45f057d97bd317e4a0518755d393aa289d0c. * reimpl onroad/offroad confirmation * use global offroad directly * ui: highlight on pressed, and less indent * inherit MultiOptionDialog main buttons * align top level folders to the edge properly * lint * lint * handle folder presses too * ui: overridable title and subtitle for `TreeOptionDialog` * override TreeOptionDialog title and subtitle * lint * more * ui: magnifying glass icon and new search bar style in `TreeDialog` * cleanup * ui: recreate search dialog with the latest query * make model year but display as platform * move into settings directory * move into dir --------- Co-authored-by: nayan Co-authored-by: Jason Wen --- selfdrive/ui/sunnypilot/__init__.py | 0 selfdrive/ui/sunnypilot/layouts/__init__.py | 0 .../sunnypilot/layouts/settings/__init__.py | 0 .../ui/sunnypilot/layouts/settings/vehicle.py | 30 ---- .../layouts/settings/vehicle/__init__.py | 43 ++++++ .../settings/vehicle/platform_selector.py | 141 ++++++++++++++++++ system/ui/sunnypilot/lib/styles.py | 5 + system/ui/sunnypilot/widgets/list_view.py | 7 +- 8 files changed, 193 insertions(+), 33 deletions(-) create mode 100644 selfdrive/ui/sunnypilot/__init__.py create mode 100644 selfdrive/ui/sunnypilot/layouts/__init__.py create mode 100644 selfdrive/ui/sunnypilot/layouts/settings/__init__.py delete mode 100644 selfdrive/ui/sunnypilot/layouts/settings/vehicle.py create mode 100644 selfdrive/ui/sunnypilot/layouts/settings/vehicle/__init__.py create mode 100644 selfdrive/ui/sunnypilot/layouts/settings/vehicle/platform_selector.py diff --git a/selfdrive/ui/sunnypilot/__init__.py b/selfdrive/ui/sunnypilot/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/selfdrive/ui/sunnypilot/layouts/__init__.py b/selfdrive/ui/sunnypilot/layouts/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/selfdrive/ui/sunnypilot/layouts/settings/__init__.py b/selfdrive/ui/sunnypilot/layouts/settings/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/selfdrive/ui/sunnypilot/layouts/settings/vehicle.py b/selfdrive/ui/sunnypilot/layouts/settings/vehicle.py deleted file mode 100644 index d04816a41..000000000 --- a/selfdrive/ui/sunnypilot/layouts/settings/vehicle.py +++ /dev/null @@ -1,30 +0,0 @@ -""" -Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. - -This file is part of sunnypilot and is licensed under the MIT License. -See the LICENSE.md file in the root directory for more details. -""" -from openpilot.common.params import Params -from openpilot.system.ui.widgets.scroller_tici import Scroller -from openpilot.system.ui.widgets import Widget - - -class VehicleLayout(Widget): - def __init__(self): - super().__init__() - - self._params = Params() - items = self._initialize_items() - self._scroller = Scroller(items, line_separator=True, spacing=0) - - def _initialize_items(self): - items = [ - - ] - return items - - def _render(self, rect): - self._scroller.render(rect) - - def show_event(self): - self._scroller.show_event() diff --git a/selfdrive/ui/sunnypilot/layouts/settings/vehicle/__init__.py b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/__init__.py new file mode 100644 index 000000000..23b861f8c --- /dev/null +++ b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/__init__.py @@ -0,0 +1,43 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +from openpilot.system.ui.lib.multilang import tr +from openpilot.system.ui.widgets import Widget +from openpilot.system.ui.widgets.list_view import ButtonAction +from openpilot.system.ui.widgets.scroller_tici import Scroller + +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.platform_selector import PlatformSelector, LegendWidget +from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.system.ui.sunnypilot.widgets.list_view import ListItemSP + + +class VehicleLayout(Widget): + def __init__(self): + super().__init__() + self._platform_selector = PlatformSelector(self._update_brand_settings) + + self._vehicle_item = ListItemSP(title=self._platform_selector.text, action_item=ButtonAction(text=tr("Select")), + callback=self._platform_selector._on_clicked) + self._vehicle_item.title_color = self._platform_selector.color + self._legend_widget = LegendWidget(self._platform_selector) + + self.items = [self._vehicle_item, self._legend_widget] + self._scroller = Scroller(self.items, line_separator=True, spacing=0) + + def _update_brand_settings(self): + self._vehicle_item._title = self._platform_selector.text + self._vehicle_item.title_color = self._platform_selector.color + vehicle_text = tr("Remove") if ui_state.params.get("CarPlatformBundle") else tr("Select") + self._vehicle_item.action_item.set_text(vehicle_text) + + def _update_state(self): + self._update_brand_settings() + + def _render(self, rect): + self._scroller.render(rect) + + def show_event(self): + self._scroller.show_event() diff --git a/selfdrive/ui/sunnypilot/layouts/settings/vehicle/platform_selector.py b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/platform_selector.py new file mode 100644 index 000000000..60bc335ce --- /dev/null +++ b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/platform_selector.py @@ -0,0 +1,141 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +import json +import os +import pyray as rl +from collections.abc import Callable +from functools import partial + +from openpilot.common.basedir import BASEDIR +from openpilot.system.ui.lib.application import gui_app, FontWeight +from openpilot.system.ui.lib.multilang import tr +from openpilot.system.ui.widgets import DialogResult, Widget +from openpilot.system.ui.widgets.button import Button, ButtonStyle +from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog + +from openpilot.system.ui.sunnypilot.lib.styles import style +from openpilot.system.ui.sunnypilot.widgets.tree_dialog import TreeOptionDialog, TreeNode, TreeFolder +from openpilot.selfdrive.ui.ui_state import ui_state + +CAR_LIST_JSON_OUT = os.path.join(BASEDIR, "sunnypilot", "selfdrive", "car", "car_list.json") + + +class LegendWidget(Widget): + def __init__(self, platform_selector): + super().__init__() + self.set_rect(rl.Rectangle(0, 0, 0, 350)) + self._platform_selector = platform_selector + self._font = gui_app.font(FontWeight.NORMAL) + self._bold_font = gui_app.font(FontWeight.BOLD) + + def _render(self, rect): + x = rect.x + 20 + y = rect.y + 20 + rl.draw_text_ex(self._font, tr("Select vehicle to force fingerprint manually."), rl.Vector2(x, y), 40, 0, style.ITEM_DESC_TEXT_COLOR) + y += 80 + rl.draw_text_ex(self._font, tr("Colors represent vehicle fingerprint status:"), rl.Vector2(x, y), 40, 0, style.ITEM_DESC_TEXT_COLOR) + y += 80 + + items = [ + (style.GREEN, tr("Fingerprinted automatically")), + (style.BLUE, tr("Manually selected fingerprint")), + (style.YELLOW, tr("Not fingerprinted or manually selected")), + ] + for color, text in items: + p_color = self._platform_selector.color + is_active = p_color.r == color.r and p_color.g == color.g and p_color.b == color.b and p_color.a == color.a + rl.draw_rectangle(int(x), int(y + 5), 30, 30, color) + font = self._bold_font if is_active else self._font + text_color = rl.WHITE if is_active else style.ITEM_DESC_TEXT_COLOR + rl.draw_text_ex(font, f"- {text}", rl.Vector2(x + 50, y - 7), 40, 0, text_color) + y += 50 + + +class PlatformSelector(Button): + def __init__(self, on_platform_change: Callable[[], None] | None = None): + super().__init__(tr("Vehicle"), self._on_clicked, button_style=ButtonStyle.NORMAL) + self.set_rect(rl.Rectangle(0, 0, 0, 120)) + + with open(CAR_LIST_JSON_OUT) as car_list_json: + self._platforms = json.load(car_list_json) + + self._on_platform_change = on_platform_change + self.refresh() + + @property + def text(self): + return self._label._text + + def set_parent_rect(self, parent_rect): + super().set_parent_rect(parent_rect) + self._rect.width = parent_rect.width + + def _on_clicked(self): + if ui_state.params.get("CarPlatformBundle"): + ui_state.params.remove("CarPlatformBundle") + self.refresh() + if self._on_platform_change: + self._on_platform_change() + else: + self._show_platform_dialog() + + def _set_platform(self, platform_name): + if data := self._platforms.get(platform_name): + ui_state.params.put("CarPlatformBundle", {**data, "name": platform_name}) + self.refresh() + if self._on_platform_change: + self._on_platform_change() + + def _on_platform_selected(self, dialog, res): + if res == DialogResult.CONFIRM and dialog.selection_ref: + offroad_msg = tr("This setting will take effect immediately.") if ui_state.is_offroad else \ + tr("This setting will take effect once the device enters offroad state.") + + confirm_dialog = ConfirmDialog(offroad_msg, tr("Confirm")) + + callback = partial(self._confirm_platform, dialog.selection_ref) + gui_app.set_modal_overlay(confirm_dialog, callback=callback) + + def _confirm_platform(self, platform_name, res): + if res == DialogResult.CONFIRM: + self._set_platform(platform_name) + + def _show_platform_dialog(self): + platforms = sorted(self._platforms.keys()) + makes = sorted({self._platforms[p].get('make') for p in platforms}) + folders = [TreeFolder(make, [TreeNode(p, { + 'display_name': p, + 'search_tags': f"{p} {self._platforms[p].get('make')} {' '.join(map(str, self._platforms[p].get('year', [])))} {self._platforms[p].get('model', p)}" + }) for p in platforms if self._platforms[p].get('make') == make]) for make in makes] + dialog = TreeOptionDialog( + tr("Select a vehicle"), + folders, + search_title=tr("Search your vehicle"), + search_subtitle=tr("Enter model year (e.g., 2021) and model (Toyota Corolla):"), + search_funcs=[lambda node: node.data.get('display_name', ''), lambda node: node.data.get('search_tags', '')] + ) + callback = partial(self._on_platform_selected, dialog) + dialog.on_exit = callback + gui_app.set_modal_overlay(dialog, callback=callback) + + def refresh(self): + self.brand = "" + self.color = style.YELLOW + self._platform = tr("Unrecognized Vehicle") + self.set_text(tr("No vehicle selected")) + + if bundle := ui_state.params.get("CarPlatformBundle"): + self._platform = bundle.get("name", "") + self.brand = bundle.get("brand", "") + self.set_text(self._platform) + self.color = style.BLUE + elif ui_state.CP and ui_state.CP.carFingerprint != "MOCK": + self._platform = ui_state.CP.carFingerprint + self.brand = ui_state.CP.brand + self.set_text(self._platform) + self.color = style.GREEN + self.set_enabled(True) diff --git a/system/ui/sunnypilot/lib/styles.py b/system/ui/sunnypilot/lib/styles.py index eb254166e..68e68d41a 100644 --- a/system/ui/sunnypilot/lib/styles.py +++ b/system/ui/sunnypilot/lib/styles.py @@ -70,5 +70,10 @@ class DefaultStyleSP(Base): BUTTON_NEUTRAL_GRAY = rl.Color(51, 51, 51, 255) BUTTON_DISABLED_BG_COLOR = rl.Color(30, 30, 30, 255) # Very Dark Grey + # Vehicle Description Colors + GREEN = rl.Color(0, 241, 0, 255) + BLUE = rl.Color(0, 134, 233, 255) + YELLOW = rl.Color(255, 213, 0, 255) + style = DefaultStyleSP diff --git a/system/ui/sunnypilot/widgets/list_view.py b/system/ui/sunnypilot/widgets/list_view.py index 955adaa73..0c87a06a1 100644 --- a/system/ui/sunnypilot/widgets/list_view.py +++ b/system/ui/sunnypilot/widgets/list_view.py @@ -80,8 +80,9 @@ class MultipleButtonActionSP(MultipleButtonAction): class ListItemSP(ListItem): def __init__(self, title: str | Callable[[], str] = "", icon: str | None = None, description: str | Callable[[], str] | None = None, description_visible: bool = False, callback: Callable | None = None, - action_item: ItemAction | None = None, inline: bool = True): + action_item: ItemAction | None = None, inline: bool = True, title_color: rl.Color = style.ITEM_TEXT_COLOR): ListItem.__init__(self, title, icon, description, description_visible, callback, action_item) + self.title_color = title_color self.inline = inline if not self.inline: self._rect.height += style.ITEM_BASE_HEIGHT/1.75 @@ -141,7 +142,7 @@ class ListItemSP(ListItem): if self.title: self._text_size = measure_text_cached(self._font, self.title, style.ITEM_TEXT_FONT_SIZE) item_y = self._rect.y + (style.ITEM_BASE_HEIGHT - self._text_size.y) // 2 - rl.draw_text_ex(self._font, self.title, rl.Vector2(text_x, item_y), style.ITEM_TEXT_FONT_SIZE, 0, style.ITEM_TEXT_COLOR) + rl.draw_text_ex(self._font, self.title, rl.Vector2(text_x, item_y), style.ITEM_TEXT_FONT_SIZE, 0, self.title_color) # Render toggle and handle callback if self.action_item.render(left_rect) and self.action_item.enabled: @@ -153,7 +154,7 @@ class ListItemSP(ListItem): # Draw main text self._text_size = measure_text_cached(self._font, self.title, style.ITEM_TEXT_FONT_SIZE) item_y = self._rect.y + (style.ITEM_BASE_HEIGHT - self._text_size.y) // 2 if self.inline else self._rect.y + style.ITEM_PADDING * 1.5 - rl.draw_text_ex(self._font, self.title, rl.Vector2(text_x, item_y), style.ITEM_TEXT_FONT_SIZE, 0, style.ITEM_TEXT_COLOR) + rl.draw_text_ex(self._font, self.title, rl.Vector2(text_x, item_y), style.ITEM_TEXT_FONT_SIZE, 0, self.title_color) # Draw right item if present if self.action_item: From 62b7abcd917677cf2a8c923b3ba5c96874c5d6fb Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Mon, 1 Dec 2025 21:13:43 -0800 Subject: [PATCH 509/910] Fix raylib ui spamming API calls (#36745) fix --- selfdrive/ui/lib/prime_state.py | 4 +++- selfdrive/ui/mici/layouts/settings/firehose.py | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/selfdrive/ui/lib/prime_state.py b/selfdrive/ui/lib/prime_state.py index fc72b4f9c..e1ef387bf 100644 --- a/selfdrive/ui/lib/prime_state.py +++ b/selfdrive/ui/lib/prime_state.py @@ -67,8 +67,10 @@ class PrimeState: cloudlog.info(f"Prime type updated to {prime_type}") def _worker_thread(self) -> None: + from openpilot.selfdrive.ui.ui_state import ui_state, device while self._running: - self._fetch_prime_status() + if not ui_state.started and device._awake: + self._fetch_prime_status() for _ in range(int(self.FETCH_INTERVAL / self.SLEEP_INTERVAL)): if not self._running: diff --git a/selfdrive/ui/mici/layouts/settings/firehose.py b/selfdrive/ui/mici/layouts/settings/firehose.py index dcb4b6c84..10e52bb3b 100644 --- a/selfdrive/ui/mici/layouts/settings/firehose.py +++ b/selfdrive/ui/mici/layouts/settings/firehose.py @@ -6,7 +6,7 @@ from openpilot.common.api import api_get from openpilot.common.params import Params from openpilot.common.swaglog import cloudlog from openpilot.selfdrive.ui.lib.api_helpers import get_token -from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.selfdrive.ui.ui_state import ui_state, device from openpilot.system.athena.registration import UNREGISTERED_DONGLE_ID from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE from openpilot.system.ui.lib.wrap_text import wrap_text @@ -213,7 +213,7 @@ class FirehoseLayoutBase(Widget): def _update_loop(self): while self._running: - if not ui_state.started: + if not ui_state.started and device._awake: self._fetch_firehose_stats() time.sleep(self.UPDATE_INTERVAL) From 65e551c671e403e8830ffc33fec5c27e19d3a1e7 Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Mon, 1 Dec 2025 21:32:07 -0800 Subject: [PATCH 510/910] Handle invalid frame fd when creating EGL image (#36743) catch --- system/ui/lib/egl.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/system/ui/lib/egl.py b/system/ui/lib/egl.py index d119a8a83..69236482b 100644 --- a/system/ui/lib/egl.py +++ b/system/ui/lib/egl.py @@ -128,8 +128,12 @@ def init_egl() -> bool: 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) + try: + # Duplicate fd since EGL needs it + dup_fd = os.dup(fd) + except OSError as e: + cloudlog.exception(f"Failed to duplicate frame fd when creating EGL image: {e}") + return None # Create image attributes for EGL img_attrs = [ From cabfa7b735fd89e11beb142a663d15c0d67f9859 Mon Sep 17 00:00:00 2001 From: Trey Moen <50057480+greatgitsby@users.noreply.github.com> Date: Mon, 1 Dec 2025 22:45:11 -0800 Subject: [PATCH 511/910] Revert "esim: remove bootstrap and delete (#36732)" (#36747) This reverts commit 6d04251517cadb8ad5001bc1e223f93133ce041f. --- system/hardware/base.py | 11 +++++++++++ system/hardware/esim.py | 36 +++++++++++++++++++++++++++++++++++- system/hardware/tici/esim.py | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 1 deletion(-) diff --git a/system/hardware/base.py b/system/hardware/base.py index 4163b3379..17d0ec161 100644 --- a/system/hardware/base.py +++ b/system/hardware/base.py @@ -65,6 +65,10 @@ class ThermalConfig: return ret class LPABase(ABC): + @abstractmethod + def bootstrap(self) -> None: + pass + @abstractmethod def list_profiles(self) -> list[Profile]: pass @@ -73,6 +77,10 @@ class LPABase(ABC): 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 @@ -85,6 +93,9 @@ class LPABase(ABC): def switch_profile(self, iccid: str) -> None: pass + def is_comma_profile(self, iccid: str) -> bool: + return any(iccid.startswith(prefix) for prefix in ('8985235',)) + class HardwareBase(ABC): @staticmethod def get_cmdline() -> dict[str, str]: diff --git a/system/hardware/esim.py b/system/hardware/esim.py index 1c98bb1e4..909ad41e0 100755 --- a/system/hardware/esim.py +++ b/system/hardware/esim.py @@ -3,21 +3,55 @@ import argparse import time from openpilot.system.hardware import HARDWARE +from openpilot.system.hardware.base import LPABase + + +def bootstrap(lpa: LPABase) -> None: + print('┌──────────────────────────────────────────────────────────────────────────────┐') + print('│ WARNING, PLEASE READ BEFORE PROCEEDING │') + print('│ │') + print('│ this is an irreversible operation that will remove the comma-provisioned │') + print('│ profile. │') + print('│ │') + print('│ after this operation, you must purchase a new eSIM from comma in order to │') + print('│ use the comma prime subscription again. │') + print('└──────────────────────────────────────────────────────────────────────────────┘') + print() + for severity in ('sure', '100% sure'): + print(f'are you {severity} you want to proceed? (y/N) ', end='') + confirm = input() + if confirm != 'y': + print('aborting') + exit(0) + lpa.bootstrap() if __name__ == '__main__': parser = argparse.ArgumentParser(prog='esim.py', description='manage eSIM profiles on your comma device', epilog='comma.ai') + parser.add_argument('--bootstrap', action='store_true', help='bootstrap the eUICC (required before downloading profiles)') 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() mutated = False lpa = HARDWARE.get_sim_lpa() - if args.switch: + if args.bootstrap: + bootstrap(lpa) + mutated = True + elif args.switch: lpa.switch_profile(args.switch) mutated = True + elif args.delete: + confirm = input('are you sure you want to delete this profile? (y/N) ') + if confirm == 'y': + lpa.delete_profile(args.delete) + mutated = True + else: + print('cancelled') + exit(0) elif args.download: lpa.download_profile(args.download[0], args.download[1]) elif args.nickname: diff --git a/system/hardware/tici/esim.py b/system/hardware/tici/esim.py index 889611769..391ba4553 100644 --- a/system/hardware/tici/esim.py +++ b/system/hardware/tici/esim.py @@ -31,7 +31,16 @@ class TiciLPA(LPABase): def get_active_profile(self) -> Profile | None: return next((p for p in self.list_profiles() if p.enabled), None) + 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_profile(self, qr: str, nickname: str | None = None) -> None: + self._check_bootstrapped() 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) @@ -46,6 +55,7 @@ class TiciLPA(LPABase): self._validate_successful(self._invoke('profile', 'nickname', iccid, nickname)) def switch_profile(self, iccid: str) -> None: + self._check_bootstrapped() self._validate_profile_exists(iccid) latest = self.get_active_profile() if latest and latest.iccid == iccid: @@ -53,10 +63,33 @@ class TiciLPA(LPABase): self._validate_successful(self._invoke('profile', 'enable', iccid)) self._process_notifications() + def bootstrap(self) -> None: + """ + find all comma-provisioned profiles and delete them. they conflict with user-provisioned profiles + and must be deleted. + + **note**: this is a **very** destructive operation. you **must** purchase a new comma SIM in order + to use comma prime again. + """ + if self._is_bootstrapped(): + return + + for p in self.list_profiles(): + if self.is_comma_profile(p.iccid): + self._disable_profile(p.iccid) + self.delete_profile(p.iccid) + def _disable_profile(self, iccid: str) -> None: self._validate_successful(self._invoke('profile', 'disable', iccid)) self._process_notifications() + def _check_bootstrapped(self) -> None: + assert self._is_bootstrapped(), 'eUICC is not bootstrapped, please bootstrap before performing this operation' + + def _is_bootstrapped(self) -> bool: + """ check if any comma provisioned profiles are on the eUICC """ + return not any(self.is_comma_profile(iccid) for iccid in (p.iccid for p in self.list_profiles())) + def _invoke(self, *cmd: str): proc = subprocess.Popen(['sudo', '-E', 'lpac'] + list(cmd), stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=self.env) try: From fa18bb9261dff30f051d1e989dd253e90c78c322 Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Mon, 1 Dec 2025 22:55:14 -0800 Subject: [PATCH 512/910] ui: restart if crash (#36746) * simpler * mypy your are going to be replaced very soon --- system/manager/manager.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/system/manager/manager.py b/system/manager/manager.py index 2d80c78ff..15f8a2b79 100755 --- a/system/manager/manager.py +++ b/system/manager/manager.py @@ -155,6 +155,10 @@ def manager_thread() -> None: print(running) cloudlog.debug(running) + if 'ui' in managed_processes and managed_processes['ui'].proc is not None and not managed_processes['ui'].proc.is_alive(): + cloudlog.error(f'Restarting UI (exitcode {managed_processes["ui"].proc.exitcode})') + managed_processes['ui'].restart() + # send managerState msg = messaging.new_message('managerState', valid=True) msg.managerState.processes = [p.get_process_state_msg() for p in managed_processes.values()] From cfb0a1c18ce2c03f71085af251a600959aa08fae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Mon, 1 Dec 2025 23:11:03 -0800 Subject: [PATCH 513/910] URLFile multirange (#36740) * url file multirange * cleanup urlfile * time * fixup * raise * Diskfile --- tools/lib/filereader.py | 15 +++++++-- tools/lib/url_file.py | 74 ++++++++++++++++++++--------------------- 2 files changed, 49 insertions(+), 40 deletions(-) diff --git a/tools/lib/filereader.py b/tools/lib/filereader.py index ee9ee294b..f5418be81 100644 --- a/tools/lib/filereader.py +++ b/tools/lib/filereader.py @@ -1,4 +1,5 @@ import os +import io import posixpath import socket from functools import cache @@ -41,9 +42,17 @@ def file_exists(fn): return URLFile(fn).get_length_online() != -1 return os.path.exists(fn) +class DiskFile(io.BufferedReader): + def get_multi_range(self, ranges: list[tuple[int, int]]) -> list[bytes]: + parts = [] + for r in ranges: + self.seek(r[0]) + parts.append(self.read(r[1] - r[0])) + return parts -def FileReader(fn, debug=False): +def FileReader(fn): fn = resolve_name(fn) if fn.startswith(("http://", "https://")): - return URLFile(fn, debug=debug) - return open(fn, "rb") + return URLFile(fn) + else: + return DiskFile(open(fn, "rb")) diff --git a/tools/lib/url_file.py b/tools/lib/url_file.py index 2bf3ba820..01c6c5dc4 100644 --- a/tools/lib/url_file.py +++ b/tools/lib/url_file.py @@ -1,13 +1,11 @@ +import re import logging import os import socket -import time from hashlib import sha256 from urllib3 import PoolManager, Retry from urllib3.response import BaseHTTPResponse from urllib3.util import Timeout -from urllib3.exceptions import MaxRetryError - from openpilot.common.utils import atomic_write from openpilot.system.hardware.hw import Paths @@ -42,12 +40,11 @@ class URLFile: URLFile._pool_manager = PoolManager(num_pools=10, maxsize=100, socket_options=socket_options, retries=retries) return URLFile._pool_manager - def __init__(self, url: str, timeout: int = 10, debug: bool = False, cache: bool | None = None): + def __init__(self, url: str, timeout: int = 10, cache: bool | None = None): self._url = url self._timeout = Timeout(connect=timeout, read=timeout) self._pos = 0 self._length: int | None = None - self._debug = debug # True by default, false if FILEREADER_CACHE is defined, but can be overwritten by the cache input self._force_download = not int(os.environ.get("FILEREADER_CACHE", "0")) if cache is not None: @@ -63,10 +60,7 @@ class URLFile: pass def _request(self, method: str, url: str, headers: dict[str, str] | None = None) -> BaseHTTPResponse: - try: - return URLFile.pool_manager().request(method, url, timeout=self._timeout, headers=headers) - except MaxRetryError as e: - raise URLFileException(f"Failed to {method} {url}: {e}") from e + return URLFile.pool_manager().request(method, url, timeout=self._timeout, headers=headers) def get_length_online(self) -> int: response = self._request('HEAD', self._url) @@ -125,39 +119,45 @@ class URLFile: return response def read_aux(self, ll: int | None = None) -> bytes: - download_range = False - headers = {} - if self._pos != 0 or ll is not None: - if ll is None: - end = self.get_length() - 1 - else: - end = min(self._pos + ll, self.get_length()) - 1 - if self._pos >= end: - return b"" - headers['Range'] = f"bytes={self._pos}-{end}" - download_range = True + if ll is None: + length = self.get_length() + if length == -1: + raise URLFileException(f"Remote file is empty or doesn't exist: {self._url}") + end = length + else: + end = self._pos + ll + data = self.get_multi_range([(self._pos, end)]) + self._pos += len(data[0]) + return data[0] - if self._debug: - t1 = time.monotonic() + def get_multi_range(self, ranges: list[tuple[int, int]]) -> list[bytes]: + # HTTP range requests are inclusive + assert all(e > s for s, e in ranges), "Range end must be greater than start" + rs = [f"{s}-{e-1}" for s, e in ranges if e > s] - response = self._request('GET', self._url, headers=headers) - ret = response.data + r = self._request("GET", self._url, headers={"Range": "bytes=" + ",".join(rs)}) + if r.status not in [200, 206]: + raise URLFileException(f"Expected 206 or 200 response {r.status} ({self._url})") - if self._debug: - t2 = time.monotonic() - if t2 - t1 > 0.1: - print(f"get {self._url} {headers!r} {t2 - t1:.3f} slow") + ctype = (r.headers.get("content-type") or "").lower() + if "multipart/byteranges" not in ctype: + return [r.data,] - response_code = response.status - if response_code == 416: # Requested Range Not Satisfiable - raise URLFileException(f"Error, range out of bounds {response_code} {headers} ({self._url}): {repr(ret)[:500]}") - if download_range and response_code != 206: # Partial Content - raise URLFileException(f"Error, requested range but got unexpected response {response_code} {headers} ({self._url}): {repr(ret)[:500]}") - if (not download_range) and response_code != 200: # OK - raise URLFileException(f"Error {response_code} {headers} ({self._url}): {repr(ret)[:500]}") + m = re.search(r'boundary="?([^";]+)"?', ctype) + if not m: + raise URLFileException(f"Missing multipart boundary ({self._url})") + boundary = m.group(1).encode() - self._pos += len(ret) - return ret + parts = [] + for chunk in r.data.split(b"--" + boundary): + if b"\r\n\r\n" not in chunk: + continue + payload = chunk.split(b"\r\n\r\n", 1)[1].rstrip(b"\r\n") + if payload and payload != b"--": + parts.append(payload) + if len(parts) != len(ranges): + raise URLFileException(f"Expected {len(ranges)} parts, got {len(parts)} ({self._url})") + return parts def seek(self, pos: int) -> None: self._pos = pos From eda189c56441d3439ca26a89dae4b086d20e06bf Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 2 Dec 2025 02:29:29 -0500 Subject: [PATCH 514/910] ui: refine height calculations and action placement in `ListViewSP` (#1543) * ui: refine height calculations and action placement in `ListViewSP` * relative --- system/ui/sunnypilot/widgets/list_view.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/system/ui/sunnypilot/widgets/list_view.py b/system/ui/sunnypilot/widgets/list_view.py index 0c87a06a1..f05de8139 100644 --- a/system/ui/sunnypilot/widgets/list_view.py +++ b/system/ui/sunnypilot/widgets/list_view.py @@ -89,8 +89,13 @@ class ListItemSP(ListItem): def get_item_height(self, font: rl.Font, max_width: int) -> float: height = super().get_item_height(font, max_width) + + if self.description_visible: + height += style.ITEM_PADDING * 1.5 + if not self.inline: - height = height + style.ITEM_BASE_HEIGHT/1.75 + height += style.ITEM_BASE_HEIGHT / 1.75 + return height def show_description(self, show: bool): @@ -101,13 +106,18 @@ class ListItemSP(ListItem): return rl.Rectangle(0, 0, 0, 0) if not self.inline: - action_y = item_rect.y + self._text_size.y + style.ITEM_PADDING * 3 + has_description = bool(self.description) and self.description_visible + + if has_description: + action_y = item_rect.y + self._text_size.y + style.ITEM_PADDING * 3 + else: + action_y = item_rect.y + item_rect.height - style.BUTTON_HEIGHT - style.ITEM_PADDING * 1.5 + return rl.Rectangle(item_rect.x + style.ITEM_PADDING, action_y, item_rect.width - (style.ITEM_PADDING * 2), style.BUTTON_HEIGHT) right_width = self.action_item.rect.width - if right_width == 0: # Full width action (like DualButtonAction) - return rl.Rectangle(item_rect.x + style.ITEM_PADDING, item_rect.y, - item_rect.width - (style.ITEM_PADDING * 2), style.ITEM_BASE_HEIGHT) + if right_width == 0: + return rl.Rectangle(item_rect.x + style.ITEM_PADDING, item_rect.y, item_rect.width - (style.ITEM_PADDING * 2), style.ITEM_BASE_HEIGHT) action_width = self.action_item.rect.width if isinstance(self.action_item, ToggleAction): @@ -171,7 +181,7 @@ class ListItemSP(ListItem): desc_y = self._rect.y + style.ITEM_DESC_V_OFFSET if not self.inline and self.action_item: - desc_y = self.action_item.rect.y + style.ITEM_DESC_V_OFFSET - style.ITEM_PADDING * 1.75 + desc_y = self.action_item.rect.y + style.ITEM_DESC_V_OFFSET - style.ITEM_PADDING * 0.5 description_rect = rl.Rectangle(self._rect.x + style.ITEM_PADDING, desc_y, content_width, description_height) self._html_renderer.render(description_rect) From 138d637bbd1918175c3de28f9670880be8e5a528 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 2 Dec 2025 13:34:35 -0500 Subject: [PATCH 515/910] ui: couple Platform Selector refresh in Vehicle panel state updates (#1545) --- selfdrive/ui/sunnypilot/layouts/settings/vehicle/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/selfdrive/ui/sunnypilot/layouts/settings/vehicle/__init__.py b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/__init__.py index 23b861f8c..1b6c35df2 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/vehicle/__init__.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/__init__.py @@ -35,6 +35,7 @@ class VehicleLayout(Widget): def _update_state(self): self._update_brand_settings() + self._platform_selector.refresh() def _render(self, rect): self._scroller.render(rect) From 3c5841ff02966f973b7ff0e339efcb1b6af6d446 Mon Sep 17 00:00:00 2001 From: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Tue, 2 Dec 2025 11:44:50 -0800 Subject: [PATCH 516/910] ui: vehicle brand settings (#1509) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * commaai/openpilot:d05cb31e2e916fba41ba8167030945f427fd811b * bump opendbc * bump opendbc * bump opendbc * bump opendbc * bump opendbc * sunnypilot: remove Qt * cabana: revert to stock Qt * commaai/openpilot:5198b1b079c37742c1050f02ce0aa6dd42b038b9 * commaai/openpilot:954b567b9ba0f3d1ae57d6aa7797fa86dd92ec6e * commaai/openpilot:7534b2a160faa683412c04c1254440e338931c5e * sum more * bump opendbc * not yet * should've been symlink'ed * raylib says wut * quiet mode back * more fixes * no more * too extra red diff on the side * need to bring this back * too extra * let's update docs here * Revert "let's update docs here" This reverts commit 51fe03cd5121e6fdf14657b2c33852c34922b851. * param to control stock vs sp ui * init styles * SP Toggles * Lint * optimizations * multi-button * Lint * param to control stock vs sp ui * init styles * SP Toggles * Lint * optimizations * Panels. With Icons. And Scroller. * patience, grasshopper * more patience, grasshopper * sp raylib preview * fix callback * fix ui preview * add ui previews * dialog txt * compare vs what used to be done before InputDialog * introducing ui_state_sp for py * param to control stock vs sp ui * better * add ui_update callback * better padding * this * support for next line multi-button * uhh * disabled colors * listitem -> listitemsp * listitem -> listitemsp * Revert "add ui_update callback" This reverts commit 4da32cc0097434aab0aa6a3c35465eabb23c8958. * add show_description method * remove padding from line separator. like, WHY? 😩😩 * scroller -> scroller_tici * scroller -> scroller_tici * ui: `GuiApplicationExt` * add to readme * use gui_app.sunnypilot_ui() * use gui_app.sunnypilot_ui() * use gui_app.sunnypilot_ui() * lint * no fancy toggles :( * match them * mici scroller - no touchy * no * more * size adjustments * fix scroller. yay * ui vehicle panel: platform selector * some brands * platform selector * bruh * ui_state_sp * o * is_offroad() and format * huh * use in toggles panel * ugh. no * better & animated * lint * cleanup * lint. LINT * slight * rebase * no more * rebae * vic * v * # Conflicts: # system/ui/sunnypilot/lib/styles.py # system/ui/sunnypilot/widgets/helpers/fuzzy_search.py * loathing loathing, unadulterated loathing, i loathe it all * more changes! * Update styles.py * set visibility * Update hyundai.py * add padding * use symlink on sp side * use make from json and show all actual makes * all done! * Revert "all done!" This reverts commit 595c45f057d97bd317e4a0518755d393aa289d0c. * reimpl onroad/offroad confirmation * use global offroad directly * ui: highlight on pressed, and less indent * inherit MultiOptionDialog main buttons * align top level folders to the edge properly * lint * lint * handle folder presses too * ui: overridable title and subtitle for `TreeOptionDialog` * override TreeOptionDialog title and subtitle * lint * more * ui: magnifying glass icon and new search bar style in `TreeDialog` * cleanup * ui: recreate search dialog with the latest query * make model year but display as platform * move into settings directory * move into dir * sync * equality * use singleton directly * also use singleton directly * inherit from base class * include all brands * added refresh * always assume it's subaru * slight * split get brand * hyundai changes * tesla changes * do not allow while offroad * fix --------- Co-authored-by: Jason Wen Co-authored-by: nayan --- .../layouts/settings/vehicle/__init__.py | 23 ++++++++ .../settings/vehicle/brands/__init__.py | 0 .../layouts/settings/vehicle/brands/base.py | 16 +++++ .../layouts/settings/vehicle/brands/body.py | 15 +++++ .../settings/vehicle/brands/chrysler.py | 15 +++++ .../settings/vehicle/brands/factory.py | 45 ++++++++++++++ .../layouts/settings/vehicle/brands/ford.py | 15 +++++ .../layouts/settings/vehicle/brands/gm.py | 15 +++++ .../layouts/settings/vehicle/brands/honda.py | 15 +++++ .../settings/vehicle/brands/hyundai.py | 59 +++++++++++++++++++ .../layouts/settings/vehicle/brands/mazda.py | 15 +++++ .../layouts/settings/vehicle/brands/nissan.py | 15 +++++ .../layouts/settings/vehicle/brands/psa.py | 15 +++++ .../layouts/settings/vehicle/brands/rivian.py | 15 +++++ .../layouts/settings/vehicle/brands/subaru.py | 54 +++++++++++++++++ .../layouts/settings/vehicle/brands/tesla.py | 43 ++++++++++++++ .../layouts/settings/vehicle/brands/toyota.py | 15 +++++ .../settings/vehicle/brands/volkswagen.py | 15 +++++ .../settings/vehicle/platform_selector.py | 3 - 19 files changed, 405 insertions(+), 3 deletions(-) create mode 100644 selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/__init__.py create mode 100644 selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/base.py create mode 100644 selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/body.py create mode 100644 selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/chrysler.py create mode 100644 selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/factory.py create mode 100644 selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/ford.py create mode 100644 selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/gm.py create mode 100644 selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/honda.py create mode 100644 selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/hyundai.py create mode 100644 selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/mazda.py create mode 100644 selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/nissan.py create mode 100644 selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/psa.py create mode 100644 selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/rivian.py create mode 100644 selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/subaru.py create mode 100644 selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/tesla.py create mode 100644 selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/toyota.py create mode 100644 selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/volkswagen.py diff --git a/selfdrive/ui/sunnypilot/layouts/settings/vehicle/__init__.py b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/__init__.py index 1b6c35df2..86d9e6169 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/vehicle/__init__.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/__init__.py @@ -9,6 +9,7 @@ from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.list_view import ButtonAction from openpilot.system.ui.widgets.scroller_tici import Scroller +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.factory import BrandSettingsFactory from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.platform_selector import PlatformSelector, LegendWidget from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.system.ui.sunnypilot.widgets.list_view import ListItemSP @@ -17,6 +18,9 @@ from openpilot.system.ui.sunnypilot.widgets.list_view import ListItemSP class VehicleLayout(Widget): def __init__(self): super().__init__() + self._brand_settings = None + self._brand_items = [] + self._current_brand = None self._platform_selector = PlatformSelector(self._update_brand_settings) self._vehicle_item = ListItemSP(title=self._platform_selector.text, action_item=ButtonAction(text=tr("Select")), @@ -27,14 +31,33 @@ class VehicleLayout(Widget): self.items = [self._vehicle_item, self._legend_widget] self._scroller = Scroller(self.items, line_separator=True, spacing=0) + @staticmethod + def get_brand(): + if bundle := ui_state.params.get("CarPlatformBundle"): + return bundle.get("brand", "") + elif ui_state.CP and ui_state.CP.carFingerprint != "MOCK": + return ui_state.CP.brand + return "" + def _update_brand_settings(self): self._vehicle_item._title = self._platform_selector.text self._vehicle_item.title_color = self._platform_selector.color vehicle_text = tr("Remove") if ui_state.params.get("CarPlatformBundle") else tr("Select") self._vehicle_item.action_item.set_text(vehicle_text) + brand = self.get_brand() + if brand != self._current_brand: + self._current_brand = brand + self._brand_settings = BrandSettingsFactory.create_brand_settings(brand) + self._brand_items = self._brand_settings.items if self._brand_settings else [] + + self.items = [self._vehicle_item, self._legend_widget] + self._brand_items + self._scroller = Scroller(self.items, line_separator=True, spacing=0) + def _update_state(self): self._update_brand_settings() + if self._brand_settings: + self._brand_settings.update_settings() self._platform_selector.refresh() def _render(self, rect): diff --git a/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/__init__.py b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/base.py b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/base.py new file mode 100644 index 000000000..8d83fdf91 --- /dev/null +++ b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/base.py @@ -0,0 +1,16 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +import abc + + +class BrandSettings(abc.ABC): + def __init__(self): + self.items = [] + + @abc.abstractmethod + def update_settings(self) -> None: + """Update the settings based on the current vehicle brand.""" diff --git a/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/body.py b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/body.py new file mode 100644 index 000000000..d1c9ea5d6 --- /dev/null +++ b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/body.py @@ -0,0 +1,15 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.base import BrandSettings + + +class BodySettings(BrandSettings): + def __init__(self): + super().__init__() + + def update_settings(self): + pass diff --git a/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/chrysler.py b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/chrysler.py new file mode 100644 index 000000000..ad62dba56 --- /dev/null +++ b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/chrysler.py @@ -0,0 +1,15 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.base import BrandSettings + + +class ChryslerSettings(BrandSettings): + def __init__(self): + super().__init__() + + def update_settings(self): + pass diff --git a/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/factory.py b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/factory.py new file mode 100644 index 000000000..678732296 --- /dev/null +++ b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/factory.py @@ -0,0 +1,45 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.base import BrandSettings +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.body import BodySettings +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.chrysler import ChryslerSettings +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.ford import FordSettings +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.gm import GMSettings +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.honda import HondaSettings +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.hyundai import HyundaiSettings +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.mazda import MazdaSettings +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.nissan import NissanSettings +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.psa import PSASettings +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.rivian import RivianSettings +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.subaru import SubaruSettings +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.tesla import TeslaSettings +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.toyota import ToyotaSettings +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.volkswagen import VolkswagenSettings + + +class BrandSettingsFactory: + _BRAND_MAP: dict[str, type[BrandSettings]] = { + "body": BodySettings, + "chrysler": ChryslerSettings, + "ford": FordSettings, + "gm": GMSettings, + "honda": HondaSettings, + "hyundai": HyundaiSettings, + "mazda": MazdaSettings, + "nissan": NissanSettings, + "psa": PSASettings, + "rivian": RivianSettings, + "subaru": SubaruSettings, + "tesla": TeslaSettings, + "toyota": ToyotaSettings, + "volkswagen": VolkswagenSettings, + } + + @staticmethod + def create_brand_settings(brand: str) -> BrandSettings | None: + cls = BrandSettingsFactory._BRAND_MAP.get(brand) + return cls() if cls is not None else None diff --git a/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/ford.py b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/ford.py new file mode 100644 index 000000000..8871087e0 --- /dev/null +++ b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/ford.py @@ -0,0 +1,15 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.base import BrandSettings + + +class FordSettings(BrandSettings): + def __init__(self): + super().__init__() + + def update_settings(self): + pass diff --git a/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/gm.py b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/gm.py new file mode 100644 index 000000000..edcd17cdb --- /dev/null +++ b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/gm.py @@ -0,0 +1,15 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.base import BrandSettings + + +class GMSettings(BrandSettings): + def __init__(self): + super().__init__() + + def update_settings(self): + pass diff --git a/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/honda.py b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/honda.py new file mode 100644 index 000000000..fec68795a --- /dev/null +++ b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/honda.py @@ -0,0 +1,15 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.base import BrandSettings + + +class HondaSettings(BrandSettings): + def __init__(self): + super().__init__() + + def update_settings(self): + pass diff --git a/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/hyundai.py b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/hyundai.py new file mode 100644 index 000000000..f6849eb20 --- /dev/null +++ b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/hyundai.py @@ -0,0 +1,59 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.base import BrandSettings +from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.system.ui.lib.multilang import tr +from openpilot.system.ui.sunnypilot.widgets.list_view import multiple_button_item_sp +from opendbc.car.hyundai.values import CAR, CANFD_UNSUPPORTED_LONGITUDINAL_CAR, UNSUPPORTED_LONGITUDINAL_CAR + + +class HyundaiSettings(BrandSettings): + def __init__(self): + super().__init__() + self.alpha_long_available = False + + tuning_texts = [tr("Off"), tr("Dynamic"), tr("Predictive")] + self.longitudinal_tuning_item = multiple_button_item_sp(tr("Custom Longitudinal Tuning"), "", tuning_texts, + button_width=300, callback=self._on_tuning_selected, + param="HyundaiLongitudinalTuning", inline=False) + self.items = [self.longitudinal_tuning_item] + + @staticmethod + def _on_tuning_selected(index): + ui_state.params.put("HyundaiLongitudinalTuning", index) + + def update_settings(self): + self.alpha_long_available = False + bundle = ui_state.params.get("CarPlatformBundle") + if bundle: + platform = bundle.get("platform") + self.alpha_long_available = CAR[platform] not in (UNSUPPORTED_LONGITUDINAL_CAR | CANFD_UNSUPPORTED_LONGITUDINAL_CAR) + elif ui_state.CP: + self.alpha_long_available = ui_state.CP.alphaLongitudinalAvailable + + tuning_param = int(ui_state.params.get("HyundaiLongitudinalTuning") or "0") + long_enabled = ui_state.has_longitudinal_control + + long_tuning_descs = [ + tr("Your vehicle will use the Default longitudinal tuning."), + tr("Your vehicle will use the Dynamic longitudinal tuning."), + tr("Your vehicle will use the Predictive longitudinal tuning."), + ] + long_tuning_desc = long_tuning_descs[tuning_param] if tuning_param < len(long_tuning_descs) else long_tuning_descs[0] + + longitudinal_tuning_disabled = not ui_state.is_offroad() or not long_enabled + if longitudinal_tuning_disabled: + if not ui_state.is_offroad(): + long_tuning_desc = tr("This feature is unavailable while the car is onroad.") + elif not long_enabled: + long_tuning_desc = tr("This feature is unavailable because sunnypilot Longitudinal Control (Alpha) is not enabled.") + + self.longitudinal_tuning_item.action_item.set_enabled(not longitudinal_tuning_disabled) + self.longitudinal_tuning_item.set_description(long_tuning_desc) + self.longitudinal_tuning_item.show_description(True) + self.longitudinal_tuning_item.action_item.set_selected_button(tuning_param) + self.longitudinal_tuning_item.set_visible(self.alpha_long_available) diff --git a/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/mazda.py b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/mazda.py new file mode 100644 index 000000000..d354f0f34 --- /dev/null +++ b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/mazda.py @@ -0,0 +1,15 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.base import BrandSettings + + +class MazdaSettings(BrandSettings): + def __init__(self): + super().__init__() + + def update_settings(self): + pass diff --git a/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/nissan.py b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/nissan.py new file mode 100644 index 000000000..7b3446a1a --- /dev/null +++ b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/nissan.py @@ -0,0 +1,15 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.base import BrandSettings + + +class NissanSettings(BrandSettings): + def __init__(self): + super().__init__() + + def update_settings(self): + pass diff --git a/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/psa.py b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/psa.py new file mode 100644 index 000000000..6b767d332 --- /dev/null +++ b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/psa.py @@ -0,0 +1,15 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.base import BrandSettings + + +class PSASettings(BrandSettings): + def __init__(self): + super().__init__() + + def update_settings(self): + pass diff --git a/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/rivian.py b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/rivian.py new file mode 100644 index 000000000..876aa2d2e --- /dev/null +++ b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/rivian.py @@ -0,0 +1,15 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.base import BrandSettings + + +class RivianSettings(BrandSettings): + def __init__(self): + super().__init__() + + def update_settings(self): + pass diff --git a/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/subaru.py b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/subaru.py new file mode 100644 index 000000000..66e7ec1d5 --- /dev/null +++ b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/subaru.py @@ -0,0 +1,54 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.base import BrandSettings +from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.system.ui.lib.multilang import tr +from openpilot.system.ui.sunnypilot.widgets.list_view import toggle_item_sp +from opendbc.car.subaru.values import CAR, SubaruFlags + + +class SubaruSettings(BrandSettings): + def __init__(self): + super().__init__() + self.has_stop_and_go = False + + self.stop_and_go_toggle = toggle_item_sp(tr("Stop and Go (Beta)"), "", param="SubaruStopAndGo", callback=self._on_toggle_changed) + + self.stop_and_go_manual_parking_brake_toggle = toggle_item_sp(tr("Stop and Go for Manual Parking Brake (Beta)"), "", + param="SubaruStopAndGoManualParkingBrake", callback=self._on_toggle_changed) + + self.items = [self.stop_and_go_toggle, self.stop_and_go_manual_parking_brake_toggle] + + def _on_toggle_changed(self, _): + self.update_settings() + + def stop_and_go_disabled_msg(self): + if not self.has_stop_and_go: + return tr("This feature is currently not available on this platform.") + elif not ui_state.is_offroad(): + return tr("Enable \"Always Offroad\" in Device panel, or turn vehicle off to toggle.") + return "" + + def update_settings(self): + bundle = ui_state.params.get("CarPlatformBundle") + if bundle: + platform = bundle.get("platform") + config = CAR[platform].config + self.has_stop_and_go = not (config.flags & (SubaruFlags.GLOBAL_GEN2 | SubaruFlags.HYBRID)) + elif ui_state.CP: + self.has_stop_and_go = not (ui_state.CP.flags & (SubaruFlags.GLOBAL_GEN2 | SubaruFlags.HYBRID)) + + disabled_msg = self.stop_and_go_disabled_msg() + descriptions = [ + tr("Experimental feature to enable auto-resume during stop-and-go for certain supported Subaru platforms."), + tr("Experimental feature to enable stop and go for Subaru Global models with manual handbrake. " + + "Models with electric parking brake should keep this disabled. Thanks to martinl for this implementation!") + ] + + for toggle, desc in zip([self.stop_and_go_toggle, self.stop_and_go_manual_parking_brake_toggle], descriptions, strict=True): + toggle.action_item.set_enabled(self.has_stop_and_go and ui_state.is_offroad()) + toggle.set_description(f"{disabled_msg}

{desc}" if disabled_msg else desc) diff --git a/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/tesla.py b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/tesla.py new file mode 100644 index 000000000..46d536c65 --- /dev/null +++ b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/tesla.py @@ -0,0 +1,43 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.base import BrandSettings +from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.system.ui.lib.multilang import tr +from openpilot.system.ui.sunnypilot.widgets.list_view import toggle_item_sp + +COOP_STEERING_MIN_KMH = 23 +OEM_STEERING_MIN_KMH = 48 +KM_TO_MILE = 0.621371 + + +class TeslaSettings(BrandSettings): + def __init__(self): + super().__init__() + self.coop_steering_toggle = toggle_item_sp(tr("Cooperative Steering (Beta)"), "", param="TeslaCoopSteering") + self.items = [self.coop_steering_toggle] + + def update_settings(self): + is_metric = ui_state.is_metric + unit = "km/h" if is_metric else "mph" + + display_value_coop = COOP_STEERING_MIN_KMH if is_metric else round(COOP_STEERING_MIN_KMH * KM_TO_MILE) + display_value_oem = OEM_STEERING_MIN_KMH if is_metric else round(OEM_STEERING_MIN_KMH * KM_TO_MILE) + + coop_steering_disabled_msg = tr("Enable \"Always Offroad\" in Device panel, or turn vehicle off to toggle.") + coop_steering_warning = tr(f"Warning: May experience steering oscillations below {display_value_oem} {unit} during turns, " + + "recommend disabling this feature if you experience these.") + coop_steering_desc = ( + f"{coop_steering_warning}

" + + f"{tr('Allows the driver to provide limited steering input while openpilot is engaged.')}
" + + f"{tr(f'Only works above {display_value_coop} {unit}.')}" + ) + + if not ui_state.is_offroad(): + coop_steering_desc = f"{coop_steering_disabled_msg}

{coop_steering_desc}" + + self.coop_steering_toggle.set_description(coop_steering_desc) + self.coop_steering_toggle.action_item.set_enabled(ui_state.is_offroad()) diff --git a/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/toyota.py b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/toyota.py new file mode 100644 index 000000000..e061a8a22 --- /dev/null +++ b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/toyota.py @@ -0,0 +1,15 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.base import BrandSettings + + +class ToyotaSettings(BrandSettings): + def __init__(self): + super().__init__() + + def update_settings(self): + pass diff --git a/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/volkswagen.py b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/volkswagen.py new file mode 100644 index 000000000..a6d44c5e4 --- /dev/null +++ b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/volkswagen.py @@ -0,0 +1,15 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.base import BrandSettings + + +class VolkswagenSettings(BrandSettings): + def __init__(self): + super().__init__() + + def update_settings(self): + pass diff --git a/selfdrive/ui/sunnypilot/layouts/settings/vehicle/platform_selector.py b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/platform_selector.py index 60bc335ce..db9059527 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/vehicle/platform_selector.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/platform_selector.py @@ -123,19 +123,16 @@ class PlatformSelector(Button): gui_app.set_modal_overlay(dialog, callback=callback) def refresh(self): - self.brand = "" self.color = style.YELLOW self._platform = tr("Unrecognized Vehicle") self.set_text(tr("No vehicle selected")) if bundle := ui_state.params.get("CarPlatformBundle"): self._platform = bundle.get("name", "") - self.brand = bundle.get("brand", "") self.set_text(self._platform) self.color = style.BLUE elif ui_state.CP and ui_state.CP.carFingerprint != "MOCK": self._platform = ui_state.CP.carFingerprint - self.brand = ui_state.CP.brand self.set_text(self._platform) self.color = style.GREEN self.set_enabled(True) From ae402d3ac774e6ae06b58a6e920542b0daf4013f Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 2 Dec 2025 13:02:01 -0800 Subject: [PATCH 517/910] Revert "ui: speed up `mici/AugmentedRoadView` by optimizing _calc_frame_matrix caching" (#36749) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Revert "ui: speed up `mici/AugmentedRoadView` by optimizing _calc_frame_matri…" This reverts commit 10524353916f42908557a1711efd2a5b66169c7b. --- .../ui/mici/onroad/augmented_road_view.py | 23 ++++++++----------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/selfdrive/ui/mici/onroad/augmented_road_view.py b/selfdrive/ui/mici/onroad/augmented_road_view.py index f1f4e66f5..ab55f392f 100644 --- a/selfdrive/ui/mici/onroad/augmented_road_view.py +++ b/selfdrive/ui/mici/onroad/augmented_road_view.py @@ -138,7 +138,9 @@ class AugmentedRoadView(CameraView): self.view_from_calib = view_frame_from_device_frame.copy() self.view_from_wide_calib = view_frame_from_device_frame.copy() - self._matrix_cache_key = (0, 0, 0, 0, stream_type) + self._last_calib_time: float = 0 + self._last_rect_dims = (0.0, 0.0) + self._last_stream_type = stream_type self._cached_matrix: np.ndarray | None = None self._content_rect = rl.Rectangle() self._last_click_time = 0.0 @@ -282,19 +284,10 @@ class AugmentedRoadView(CameraView): self.view_from_wide_calib = view_frame_from_device_frame @ wide_from_device @ device_from_calib def _calc_frame_matrix(self, rect: rl.Rectangle) -> np.ndarray: - v_ego_quantized = round(ui_state.sm['carState'].vEgo, 1) - cache_key = ( - ui_state.sm.recv_frame['liveCalibration'], - int(self._content_rect.width), - int(self._content_rect.height), - self.stream_type, - v_ego_quantized - ) - - if cache_key == self._matrix_cache_key and self._cached_matrix is not None: - return self._cached_matrix - # Get camera configuration + # TODO: cache with vEgo? + calib_time = ui_state.sm.recv_frame['liveCalibration'] + current_dims = (self._content_rect.width, self._content_rect.height) device_camera = self.device_camera or DEFAULT_DEVICE_CAMERA is_wide_camera = self.stream_type == WIDE_CAM intrinsic = device_camera.ecam.intrinsics if is_wide_camera else device_camera.fcam.intrinsics @@ -330,7 +323,9 @@ class AugmentedRoadView(CameraView): x_offset, y_offset = 0, 0 # Cache the computed transformation matrix to avoid recalculations - self._matrix_cache_key = cache_key + self._last_calib_time = calib_time + self._last_rect_dims = current_dims + self._last_stream_type = self.stream_type self._cached_matrix = np.array([ [zoom * 2 * cx / w, 0, -x_offset / w * 2], [0, zoom * 2 * cy / h, -y_offset / h * 2], From ae6250e685d2e4fd659bac864572b77ab991de82 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Wed, 3 Dec 2025 05:09:24 +0800 Subject: [PATCH 518/910] ui/CameraView: use consistent 2-space indentation (#36748) use consistent 2-space indentation --- selfdrive/ui/onroad/cameraview.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/selfdrive/ui/onroad/cameraview.py b/selfdrive/ui/onroad/cameraview.py index 87db7cc63..881a916df 100644 --- a/selfdrive/ui/onroad/cameraview.py +++ b/selfdrive/ui/onroad/cameraview.py @@ -337,12 +337,12 @@ class CameraView(Widget): self._initialize_textures() def _initialize_textures(self): - 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)) + 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)) def _clear_textures(self): if self.texture_y and self.texture_y.id: From 63563c3561a88b7d08c679387aa5ae7609ebaf67 Mon Sep 17 00:00:00 2001 From: Chechulin Serhii <78239416+keefeere@users.noreply.github.com> Date: Tue, 2 Dec 2025 23:13:13 +0200 Subject: [PATCH 519/910] ui: fix - translate display text of updater_state (#36649) * Add updater_state translation * Move STATE_TO_DISPLAY_TEXT on top --- selfdrive/ui/layouts/settings/software.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/selfdrive/ui/layouts/settings/software.py b/selfdrive/ui/layouts/settings/software.py index 4b8b7015f..e0df8f270 100644 --- a/selfdrive/ui/layouts/settings/software.py +++ b/selfdrive/ui/layouts/settings/software.py @@ -14,6 +14,13 @@ from openpilot.system.ui.widgets.scroller_tici import Scroller # TODO: remove this. updater fails to respond on startup if time is not correct UPDATED_TIMEOUT = 10 # seconds to wait for updated to respond +# Mapping updater internal states to translated display strings +STATE_TO_DISPLAY_TEXT = { + "checking...": tr("checking..."), + "downloading...": tr("downloading..."), + "finalizing update...": tr("finalizing update..."), +} + def time_ago(date: datetime.datetime | None) -> str: if not date: @@ -100,7 +107,9 @@ class SoftwareLayout(Widget): # Updater responded self._waiting_for_updater = False self._download_btn.action_item.set_enabled(False) - self._download_btn.action_item.set_value(updater_state) + # Use the mapping, with a fallback to the original state string + display_text = STATE_TO_DISPLAY_TEXT.get(updater_state, updater_state) + self._download_btn.action_item.set_value(display_text) else: if failed_count > 0: self._download_btn.action_item.set_value(tr("failed to check for update")) From dc02a2d3859310e7861c1a22e63d52a4ccda9d2f Mon Sep 17 00:00:00 2001 From: YassineYousfi Date: Tue, 2 Dec 2025 15:17:59 -0800 Subject: [PATCH 520/910] dm: adjust cold start pose offsets (#36739) * dm: adjust cold start offsets and thresholds * change just offsets for now --- selfdrive/monitoring/helpers.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/selfdrive/monitoring/helpers.py b/selfdrive/monitoring/helpers.py index 5b5e16dde..1ed04e705 100644 --- a/selfdrive/monitoring/helpers.py +++ b/selfdrive/monitoring/helpers.py @@ -47,9 +47,9 @@ class DRIVER_MONITOR_SETTINGS: self._POSE_YAW_THRESHOLD = 0.4020 self._POSE_YAW_THRESHOLD_SLACK = 0.5042 self._POSE_YAW_THRESHOLD_STRICT = self._POSE_YAW_THRESHOLD - self._PITCH_NATURAL_OFFSET = 0.029 # initial value before offset is learned + self._PITCH_NATURAL_OFFSET = 0.011 # initial value before offset is learned self._PITCH_NATURAL_THRESHOLD = 0.449 - self._YAW_NATURAL_OFFSET = 0.097 # initial value before offset is learned + self._YAW_NATURAL_OFFSET = 0.075 # initial value before offset is learned self._PITCH_MAX_OFFSET = 0.124 self._PITCH_MIN_OFFSET = -0.0881 self._YAW_MAX_OFFSET = 0.289 @@ -234,8 +234,11 @@ class DriverMonitoring: self.settings._YAW_MIN_OFFSET), self.settings._YAW_MAX_OFFSET) pitch_error = 0 if pitch_error > 0 else abs(pitch_error) # no positive pitch limit yaw_error = abs(yaw_error) - if pitch_error > (self.settings._POSE_PITCH_THRESHOLD*self.pose.cfactor_pitch if self.pose.calibrated else self.settings._PITCH_NATURAL_THRESHOLD) or \ - yaw_error > self.settings._POSE_YAW_THRESHOLD*self.pose.cfactor_yaw: + + pitch_threshold = self.settings._POSE_PITCH_THRESHOLD * self.pose.cfactor_pitch if self.pose.calibrated else self.settings._PITCH_NATURAL_THRESHOLD + yaw_threshold = self.settings._POSE_YAW_THRESHOLD * self.pose.cfactor_yaw + + if pitch_error > pitch_threshold or yaw_error > yaw_threshold: distracted_types.append(DistractedType.DISTRACTED_POSE) if (self.blink.left + self.blink.right)*0.5 > self.settings._BLINK_THRESHOLD: From 5393308d035c123cdb5a0aabb93e20af664ffd9f Mon Sep 17 00:00:00 2001 From: Bruce Wayne Date: Tue, 2 Dec 2025 15:54:21 -0800 Subject: [PATCH 521/910] Logreader: print errors --- tools/lib/logreader.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/lib/logreader.py b/tools/lib/logreader.py index 8d84cdbd5..f9a90490b 100755 --- a/tools/lib/logreader.py +++ b/tools/lib/logreader.py @@ -181,6 +181,8 @@ def auto_source(identifier: str, sources: list[Source], default_mode: ReadMode) # We've found all files, return them if len(needed_seg_idxs) == 0: return cast(list[str], list(valid_files.values())) + else: + raise FileNotFoundError(f"Did not find {fn} for seg idxs {needed_seg_idxs} of {sr.route_name}") except Exception as e: exceptions[source.__name__] = e From e7d349bf36044cf4868fffd69f71a9193a08638d Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Tue, 2 Dec 2025 16:45:09 -0800 Subject: [PATCH 522/910] Revert "ui: restart if crash (#36746)" (#36754) This reverts commit fa18bb9261dff30f051d1e989dd253e90c78c322. --- system/manager/manager.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/system/manager/manager.py b/system/manager/manager.py index 15f8a2b79..2d80c78ff 100755 --- a/system/manager/manager.py +++ b/system/manager/manager.py @@ -155,10 +155,6 @@ def manager_thread() -> None: print(running) cloudlog.debug(running) - if 'ui' in managed_processes and managed_processes['ui'].proc is not None and not managed_processes['ui'].proc.is_alive(): - cloudlog.error(f'Restarting UI (exitcode {managed_processes["ui"].proc.exitcode})') - managed_processes['ui'].restart() - # send managerState msg = messaging.new_message('managerState', valid=True) msg.managerState.processes = [p.get_process_state_msg() for p in managed_processes.values()] From 5fd090616419035e4f18afd0b740a504afd1d726 Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Tue, 2 Dec 2025 17:10:24 -0800 Subject: [PATCH 523/910] allow restarting processes after crash (#36755) more --- system/manager/process.py | 7 ++++++- system/manager/process_config.py | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/system/manager/process.py b/system/manager/process.py index e6b6a44c4..1e2419826 100644 --- a/system/manager/process.py +++ b/system/manager/process.py @@ -67,6 +67,7 @@ class ManagerProcess(ABC): enabled = True name = "" shutting_down = False + restart_if_crash = False @abstractmethod def prepare(self) -> None: @@ -167,13 +168,14 @@ class NativeProcess(ManagerProcess): class PythonProcess(ManagerProcess): - def __init__(self, name, module, should_run, enabled=True, sigkill=False): + def __init__(self, name, module, should_run, enabled=True, sigkill=False, restart_if_crash=False): self.name = name self.module = module self.should_run = should_run self.enabled = enabled self.sigkill = sigkill self.launcher = launcher + self.restart_if_crash = restart_if_crash def prepare(self) -> None: if self.enabled: @@ -252,6 +254,9 @@ def ensure_running(procs: ValuesView[ManagerProcess], started: bool, params=None running = [] for p in procs: if p.enabled and p.name not in not_run and p.should_run(started, params, CP): + if p.restart_if_crash and p.proc is not None and not p.proc.is_alive(): + cloudlog.error(f'Restarting {p.name} (exitcode {p.proc.exitcode})') + p.restart() running.append(p) else: p.stop(block=False) diff --git a/system/manager/process_config.py b/system/manager/process_config.py index 8cf3e8c14..0b9918319 100644 --- a/system/manager/process_config.py +++ b/system/manager/process_config.py @@ -80,7 +80,7 @@ procs = [ PythonProcess("dmonitoringmodeld", "selfdrive.modeld.dmonitoringmodeld", driverview, enabled=(WEBCAM or not PC)), PythonProcess("sensord", "system.sensord.sensord", only_onroad, enabled=not PC), - PythonProcess("ui", "selfdrive.ui.ui", always_run), + PythonProcess("ui", "selfdrive.ui.ui", always_run, restart_if_crash=True), PythonProcess("soundd", "selfdrive.ui.soundd", driverview), PythonProcess("locationd", "selfdrive.locationd.locationd", only_onroad), NativeProcess("_pandad", "selfdrive/pandad", ["./pandad"], always_run, enabled=False), From f08dfa1c14f295209d134db0a8ef9b0de3dce1cb Mon Sep 17 00:00:00 2001 From: Nayan Date: Wed, 3 Dec 2025 00:20:40 -0500 Subject: [PATCH 524/910] ui: sunnylink client-side implementation (#1488) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * param to control stock vs sp ui * init styles * SP Toggles * Lint * optimizations * sp raylib preview * fix callback * fix ui preview * sunnylink state * introducing ui_state_sp for py * poll from ui_state_sp * cloudlog & ruff * param to control stock vs sp ui * better * better padding * this * listitem -> listitemsp * add show_description method * remove padding from line separator. like, WHY? 😩😩 * ui: `GuiApplicationExt` * add to readme * use gui_app.sunnypilot_ui() * use gui_app.sunnypilot_ui() * fetch only when connected to network * final --------- Co-authored-by: Jason Wen --- selfdrive/ui/sunnypilot/ui_state.py | 5 +- sunnypilot/sunnylink/sunnylink_state.py | 205 ++++++++++++++++++++++++ 2 files changed, 209 insertions(+), 1 deletion(-) create mode 100644 sunnypilot/sunnylink/sunnylink_state.py diff --git a/selfdrive/ui/sunnypilot/ui_state.py b/selfdrive/ui/sunnypilot/ui_state.py index 18e35508b..1db5ad613 100644 --- a/selfdrive/ui/sunnypilot/ui_state.py +++ b/selfdrive/ui/sunnypilot/ui_state.py @@ -6,6 +6,7 @@ See the LICENSE.md file in the root directory for more details. """ from cereal import messaging, custom from openpilot.common.params import Params +from openpilot.sunnypilot.sunnylink.sunnylink_state import SunnylinkState class UIStateSP: @@ -16,8 +17,10 @@ class UIStateSP: "gpsLocation", "liveTorqueParameters", "carStateSP", "liveMapDataSP", "carParamsSP" ] + self.sunnylink_state = SunnylinkState() + def update(self) -> None: - pass + self.sunnylink_state.start() def update_params(self) -> None: CP_SP_bytes = self.params.get("CarParamsSPPersistent") diff --git a/sunnypilot/sunnylink/sunnylink_state.py b/sunnypilot/sunnylink/sunnylink_state.py new file mode 100644 index 000000000..4d0b397e0 --- /dev/null +++ b/sunnypilot/sunnylink/sunnylink_state.py @@ -0,0 +1,205 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +from enum import IntEnum +import threading +import time +import json + +from cereal import messaging +from openpilot.common.params import Params +from openpilot.common.swaglog import cloudlog +from openpilot.sunnypilot.sunnylink.api import UNREGISTERED_SUNNYLINK_DONGLE_ID, SunnylinkApi + + +class RoleType(IntEnum): + READONLY = 0 + SPONSOR = 1 + ADMIN = 2 + + +class SponsorTier(IntEnum): + FREE = 0 + NOVICE = 1 + SUPPORTER = 2 + CONTRIBUTOR = 3 + BENEFACTOR = 4 + GUARDIAN = 5 + + +class User: + device_id: str + user_id: str + created_at: int + updated_at: int + token_hash: str + + def __init__(self, json_data): + self.device_id = json_data.get("device_id") + self.user_id = json_data.get("user_id") + self.created_at = json_data.get("created_at") + self.updated_at = json_data.get("updated_at") + self.token_hash = json_data.get("token_hash") + + +class Role: + role_type: str + role_tier: str + + def __init__(self, json_data): + self.role_type = json_data.get("role_type") + self.role_tier = json_data.get("role_tier") + + +def _parse_roles(roles: str) -> list[Role]: + lst_roles = [] + try: + roles_list = json.loads(roles) + for r in roles_list: + try: + role = Role(r) + lst_roles.append(role) + except Exception as e: + cloudlog.exception(f"Failed to parse role {r}: {e}") + return lst_roles + except Exception as e: + cloudlog.exception(f"Error parsing roles: {e}") + return [] + + +def _parse_users(users: str) -> list[User]: + lst_users = [] + try: + users_list = json.loads(users) + for u in users_list: + try: + user = User(u) + lst_users.append(user) + except Exception as e: + cloudlog.exception(f"Failed to parse user {u}: {e}") + return lst_users + except Exception as e: + cloudlog.exception(f"Error parsing users: {e}") + return [] + + +class SunnylinkState: + FETCH_INTERVAL = 5.0 # seconds between API calls + API_TIMEOUT = 10.0 # seconds for API requests + SLEEP_INTERVAL = 0.5 # seconds to sleep between checks in the worker thread + NOT_PAIRED_USERNAMES = ["unregisteredsponsor", "temporarysponsor"] + + def __init__(self): + self._params = Params() + self._lock = threading.Lock() + self._running = False + self._thread = None + self._sm = messaging.SubMaster(['deviceState']) + + self._roles: list[Role] = [] + self._users: list[User] = [] + self.sponsor_tier: SponsorTier = SponsorTier.FREE + self.sunnylink_dongle_id = self._params.get("SunnylinkDongleId") + self._api = SunnylinkApi(self.sunnylink_dongle_id) + + self._load_initial_state() + + def _load_initial_state(self) -> None: + roles_cache = self._params.get("SunnylinkCache_Roles") + users_cache = self._params.get("SunnylinkCache_Users") + if roles_cache is not None: + self._roles = _parse_roles(roles_cache) + self.sponsor_tier = self._get_highest_tier() + if users_cache is not None: + self._users = _parse_users(users_cache) + + def _get_highest_tier(self) -> SponsorTier: + role_tier = SponsorTier.FREE + for role in self._roles: + try: + if RoleType[role.role_type.upper()] == RoleType.SPONSOR: + role_tier = max(role_tier, SponsorTier[role.role_tier.upper()]) + except Exception as e: + cloudlog.exception(f"Error parsing role {role}: {e} for dongle id {self.sunnylink_dongle_id}") + return role_tier + + def _fetch_roles(self) -> None: + if not self.sunnylink_dongle_id or self.sunnylink_dongle_id == UNREGISTERED_SUNNYLINK_DONGLE_ID: + return + + try: + token = self._api.get_token() + response = self._api.api_get(f"device/{self.sunnylink_dongle_id}/roles", method='GET', access_token=token) + if response.status_code == 200: + self._roles = _parse_roles(response.text) + self._params.put("SunnylinkCache_Roles", response.text) + sponsor_tier = self._get_highest_tier() + with self._lock: + if sponsor_tier != self.sponsor_tier: + self.sponsor_tier = sponsor_tier + cloudlog.info(f"Sunnylink sponsor tier updated to {sponsor_tier.name}") + except Exception as e: + cloudlog.exception(f"Failed to fetch sunnylink roles: {e} for dongle id {self.sunnylink_dongle_id}") + + def _fetch_users(self) -> None: + if not self.sunnylink_dongle_id or self.sunnylink_dongle_id == UNREGISTERED_SUNNYLINK_DONGLE_ID: + return + + try: + token = self._api.get_token() + response = self._api.api_get(f"device/{self.sunnylink_dongle_id}/users", method='GET', access_token=token) + if response.status_code == 200: + users = response.text + self._params.put("SunnylinkCache_Users", users) + with self._lock: + _parse_users(users) + except Exception as e: + cloudlog.exception(f"Failed to fetch sunnylink users: {e} for dongle id {self.sunnylink_dongle_id}") + + def _worker_thread(self) -> None: + while self._running: + if self.is_connected(): + self._fetch_roles() + self._fetch_users() + + for _ in range(int(self.FETCH_INTERVAL / self.SLEEP_INTERVAL)): + if not self._running: + break + time.sleep(self.SLEEP_INTERVAL) + + def start(self) -> None: + if self._thread and self._thread.is_alive(): + return + self._running = True + self._thread = threading.Thread(target=self._worker_thread, daemon=True) + self._thread.start() + + def stop(self) -> None: + self._running = False + if self._thread and self._thread.is_alive(): + self._thread.join(timeout=1.0) + + def get_sponsor_tier(self) -> SponsorTier: + with self._lock: + return self.sponsor_tier + + def is_sponsor(self) -> bool: + with self._lock: + is_sponsor = any(role.role_type.upper() == RoleType.SPONSOR.name and role.role_tier.upper() != SponsorTier.FREE.name + for role in self._roles) + return is_sponsor + + def is_paired(self) -> bool: + with self._lock: + is_paired = any(user.user_id not in self.NOT_PAIRED_USERNAMES for user in self._users) + return is_paired + + def is_connected(self) -> bool: + network_type = self._sm["deviceState"].networkType + return bool(network_type != 0) + + def __del__(self): + self.stop() From a83c64ffbd410e610cbc8be3212fcbd3dad0537f Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Tue, 2 Dec 2025 23:56:19 -0600 Subject: [PATCH 525/910] ui: fix sidebar scroll in UI screenshots (#1519) * fix: delay between scroll clicks; add larger delay; fix cruise button * scroll more for cruise --------- Co-authored-by: Jason Wen --- .../ui/tests/test_ui/raylib_screenshots.py | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py index e64398d22..164358bfc 100755 --- a/selfdrive/ui/tests/test_ui/raylib_screenshots.py +++ b/selfdrive/ui/tests/test_ui/raylib_screenshots.py @@ -105,7 +105,7 @@ def setup_settings_software_branch_switcher(click, pm: PubMaster, scroll=None): def setup_settings_firehose(click, pm: PubMaster, scroll=None): setup_settings(click, pm) - scroll(-1000, 278, 950) + scroll(-20, 278, 950) click(278, 850) @@ -115,7 +115,7 @@ def setup_settings_developer(click, pm: PubMaster, scroll=None): Params().put("CarParamsPersistent", CP.to_bytes()) setup_settings(click, pm) - scroll(-1000, 278, 950) + scroll(-20, 278, 950) click(278, 950) @@ -171,37 +171,37 @@ def setup_settings_steering(click, pm: PubMaster, scroll=None): def setup_settings_cruise(click, pm: PubMaster, scroll=None): setup_settings(click, pm) - click(278, 1017) - scroll(-140, 278, 950) + scroll(-4, 278, 950) + click(278, 860) def setup_settings_visuals(click, pm: PubMaster, scroll=None): setup_settings(click, pm) - scroll(-1000, 278, 950) + scroll(-20, 278, 950) click(278, 330) def setup_settings_display(click, pm: PubMaster, scroll=None): setup_settings(click, pm) - scroll(-1000, 278, 950) + scroll(-20, 278, 950) click(278, 420) def setup_settings_osm(click, pm: PubMaster, scroll=None): setup_settings(click, pm) - scroll(-1000, 278, 950) + scroll(-20, 278, 950) click(278, 520) def setup_settings_trips(click, pm: PubMaster, scroll=None): setup_settings(click, pm) - scroll(-1000, 278, 950) + scroll(-20, 278, 950) click(278, 630) def setup_settings_vehicle(click, pm: PubMaster, scroll=None): setup_settings(click, pm) - scroll(-1000, 278, 950) + scroll(-20, 278, 950) click(278, 750) @@ -342,9 +342,14 @@ class TestUI: time.sleep(0.01) pyautogui.mouseUp(self.ui.left + x, self.ui.top + y, *args, **kwargs) - def scroll(self, clicks, x, y, *args, **kwargs): - pyautogui.scroll(clicks, self.ui.left + x, self.ui.top + y, *args, **kwargs) - time.sleep(UI_DELAY) + def scroll(self, clicks: int, x, y, *args, **kwargs): + if clicks == 0: + return + click = -1 if clicks < 0 else 1 # -1 = down, 1 = up + for _ in range(abs(clicks)): + pyautogui.scroll(click, self.ui.left + x, self.ui.top + y, *args, **kwargs) # scroll for individual clicks since we need to delay between clicks + time.sleep(0.01) # small delay between scroll clicks to work properly + time.sleep(2) # wait for scroll to fully settle @with_processes(["ui"]) def test_ui(self, name, setup_case): From c560ac43aa1531dabad415cd0cfecce9c652d616 Mon Sep 17 00:00:00 2001 From: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Tue, 2 Dec 2025 22:08:38 -0800 Subject: [PATCH 526/910] ui: `NetworkUISP` (#1535) * ui: network panel * refactor adding scan button without overriding upstream layout * trigger immediate network refresh instead of waiting --------- Co-authored-by: Jason Wen --- .../ui/sunnypilot/layouts/settings/network.py | 46 +++++++++++++++++++ .../sunnypilot/layouts/settings/settings.py | 4 +- 2 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 selfdrive/ui/sunnypilot/layouts/settings/network.py diff --git a/selfdrive/ui/sunnypilot/layouts/settings/network.py b/selfdrive/ui/sunnypilot/layouts/settings/network.py new file mode 100644 index 000000000..14f573c62 --- /dev/null +++ b/selfdrive/ui/sunnypilot/layouts/settings/network.py @@ -0,0 +1,46 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +import threading +import time +import pyray as rl + +from openpilot.system.ui.lib.multilang import tr +from openpilot.system.ui.widgets.button import Button, ButtonStyle +from openpilot.system.ui.widgets.network import NetworkUI, PanelType + + +class NetworkUISP(NetworkUI): + def __init__(self, wifi_manager): + super().__init__(wifi_manager) + + self.scan_button = Button(tr("Scan"), self._scan_clicked, button_style=ButtonStyle.NORMAL, font_size=60, border_radius=30) + self.scan_button.set_rect(rl.Rectangle(0, 0, 400, 100)) + + self._scanning = False + self._wifi_manager.add_callbacks(networks_updated=self._on_networks_updated) + + def _scan_clicked(self): + self._scanning = True + self.scan_button.set_text(tr("Scanning...")) + self.scan_button.set_enabled(False) + + threading.Thread(target=self._wifi_manager._update_networks, daemon=True).start() + self._wifi_manager._request_scan() + self._wifi_manager._last_network_update = time.monotonic() + + def _on_networks_updated(self, networks): + if self._scanning: + self._scanning = False + self.scan_button.set_text(tr("Scan")) + self.scan_button.set_enabled(True) + + def _render(self, rect: rl.Rectangle): + super()._render(rect) + + if self._current_panel == PanelType.WIFI: + self.scan_button.set_position(self._rect.x, self._rect.y + 20) + self.scan_button.render() diff --git a/selfdrive/ui/sunnypilot/layouts/settings/settings.py b/selfdrive/ui/sunnypilot/layouts/settings/settings.py index bf174de90..905338552 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/settings.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/settings.py @@ -19,10 +19,10 @@ from openpilot.system.ui.lib.multilang import tr_noop from openpilot.system.ui.sunnypilot.lib.styles import style from openpilot.system.ui.widgets.scroller_tici import Scroller from openpilot.system.ui.lib.text_measure import measure_text_cached -from openpilot.system.ui.widgets.network import NetworkUI from openpilot.system.ui.lib.wifi_manager import WifiManager from openpilot.system.ui.widgets import Widget from openpilot.selfdrive.ui.sunnypilot.layouts.settings.models import ModelsLayout +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.network import NetworkUISP from openpilot.selfdrive.ui.sunnypilot.layouts.settings.sunnylink import SunnylinkLayout from openpilot.selfdrive.ui.sunnypilot.layouts.settings.osm import OSMLayout from openpilot.selfdrive.ui.sunnypilot.layouts.settings.trips import TripsLayout @@ -111,7 +111,7 @@ class SettingsLayoutSP(OP.SettingsLayout): self._panels = { OP.PanelType.DEVICE: PanelInfo(tr_noop("Device"), DeviceLayoutSP(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_home.png"), - OP.PanelType.NETWORK: PanelInfo(tr_noop("Network"), NetworkUI(wifi_manager), icon="icons/network.png"), + OP.PanelType.NETWORK: PanelInfo(tr_noop("Network"), NetworkUISP(wifi_manager), icon="icons/network.png"), OP.PanelType.SUNNYLINK: PanelInfo(tr_noop("sunnylink"), SunnylinkLayout(), icon="icons/shell.png"), OP.PanelType.TOGGLES: PanelInfo(tr_noop("Toggles"), TogglesLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_toggle.png"), OP.PanelType.SOFTWARE: PanelInfo(tr_noop("Software"), SoftwareLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_software.png"), From b9c54e07fb721bd67bcec3f1ca5c29c70c0198d3 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 3 Dec 2025 01:18:00 -0500 Subject: [PATCH 527/910] Revert "ci: disable macos builds" (#1529) Revert "ci: disable macos builds (#1514)" This reverts commit 42b2e1534b06a409efdd01a8284d10236e03bdad. --- .github/workflows/tests.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 8d6449cb4..aab16ffbb 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -107,7 +107,6 @@ jobs: build_mac: name: build macOS - if: false # temp disable since gcc-arm-embedded install is getting stuck due to checksum mismatch runs-on: ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-macos-8x14' || 'macos-latest' }} steps: - uses: actions/checkout@v4 From bea05d4624671927b7e1a203e4daa0eb6d5b74a3 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 3 Dec 2025 02:29:33 -0500 Subject: [PATCH 528/910] ui: add pressed state and visual feedback for search button in `TreeOptionDialog` (#1547) * ui: add pressed state and visual feedback for search button in `TreeDialog` * less --- system/ui/sunnypilot/lib/styles.py | 3 +++ system/ui/sunnypilot/widgets/tree_dialog.py | 23 +++++++++++++++++---- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/system/ui/sunnypilot/lib/styles.py b/system/ui/sunnypilot/lib/styles.py index 68e68d41a..3597509dc 100644 --- a/system/ui/sunnypilot/lib/styles.py +++ b/system/ui/sunnypilot/lib/styles.py @@ -69,6 +69,9 @@ class DefaultStyleSP(Base): BUTTON_PRIMARY_COLOR = rl.Color(70, 91, 234, 255) # Royal Blue BUTTON_NEUTRAL_GRAY = rl.Color(51, 51, 51, 255) BUTTON_DISABLED_BG_COLOR = rl.Color(30, 30, 30, 255) # Very Dark Grey + TREE_DIALOG_TRANSPARENT = rl.Color(0, 0, 0, 0) + TREE_DIALOG_SEARCH_BUTTON_PRESSED = rl.Color(0x69, 0x68, 0x68, 0xFF) + TREE_DIALOG_SEARCH_BUTTON_BORDER = rl.Color(150, 150, 150, 200) # Vehicle Description Colors GREEN = rl.Color(0, 241, 0, 255) diff --git a/system/ui/sunnypilot/widgets/tree_dialog.py b/system/ui/sunnypilot/widgets/tree_dialog.py index fd2a576d5..cfc0a5caf 100644 --- a/system/ui/sunnypilot/widgets/tree_dialog.py +++ b/system/ui/sunnypilot/widgets/tree_dialog.py @@ -99,6 +99,7 @@ class TreeOptionDialog(MultiOptionDialog): self.search_title = search_title or tr("Enter search query") self.search_subtitle = search_subtitle self.search_dialog = None + self._search_pressed = False self._build_visible_items() @@ -183,9 +184,10 @@ class TreeOptionDialog(MultiOptionDialog): input_rect = rl.Rectangle(self._search_rect.x + inset, self._search_rect.y + inset, self._search_rect.width - inset * 2, self._search_rect.height - inset * 2) - # Transparent fill + border - rl.draw_rectangle_rounded(input_rect, roundness, 10, rl.Color(0, 0, 0, 0)) - rl.draw_rectangle_rounded_lines_ex(input_rect, roundness, 10, 3, rl.Color(150, 150, 150, 200)) + # Transparent fill (unpressed), white fill (pressed), border + fill_color = style.TREE_DIALOG_SEARCH_BUTTON_PRESSED if self._search_pressed else style.TREE_DIALOG_TRANSPARENT + rl.draw_rectangle_rounded(input_rect, roundness, 10, fill_color) + rl.draw_rectangle_rounded_lines_ex(input_rect, roundness, 10, 3, style.TREE_DIALOG_SEARCH_BUTTON_BORDER) # Magnifying glass icon icon_color = rl.Color(180, 180, 180, 240) @@ -233,8 +235,21 @@ class TreeOptionDialog(MultiOptionDialog): return self._result - def _handle_mouse_release(self, mouse_pos): + def _handle_mouse_press(self, mouse_pos): if self._search_rect and rl.check_collision_point_rec(mouse_pos, self._search_rect): + self._search_pressed = True + return True + return super()._handle_mouse_press(mouse_pos) + + def _handle_mouse_release(self, mouse_pos): + clicked_search = False + if self._search_rect and rl.check_collision_point_rec(mouse_pos, self._search_rect): + clicked_search = self._search_pressed + + self._search_pressed = False + + if clicked_search: self._on_search_clicked() return True + return super()._handle_mouse_release(mouse_pos) From 83dad85cdd800d79d77ff964da72103218c12ef9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Wed, 3 Dec 2025 12:55:33 -0800 Subject: [PATCH 529/910] Dark Souls Model (#36764) a4cf2707-3d69-49ea-af8b-f91cd3285249/400 --- selfdrive/modeld/models/driving_policy.onnx | 2 +- selfdrive/modeld/models/driving_vision.onnx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/modeld/models/driving_policy.onnx b/selfdrive/modeld/models/driving_policy.onnx index 1e764af9b..ec451ba73 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:c5a1f0655ddf266ed42ad1980389d96f47cc5e756da1fa3ca1477a920bb9b157 +oid sha256:e2929b07deb9fb1e492c7fa2832c51ac9e472bfe0b80730fdbbe263735866580 size 13926324 diff --git a/selfdrive/modeld/models/driving_vision.onnx b/selfdrive/modeld/models/driving_vision.onnx index 441c4a16a..e5ef27810 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:8f16d548ea4eb5d01518a9e90d4527cd97c31a84bcaf6f695dead8f0015fecc4 +oid sha256:2194eaee8a8c40f79a6f783d198991b1bf70a54b5885053e63789eab040a5228 size 46271942 From 7ea6cfcbdfbfe6c593c6479555fbb6d3f087b598 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 3 Dec 2025 20:00:19 -0800 Subject: [PATCH 530/910] remove unecessary function --- selfdrive/ui/mici/onroad/driver_camera_dialog.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/selfdrive/ui/mici/onroad/driver_camera_dialog.py b/selfdrive/ui/mici/onroad/driver_camera_dialog.py index f2fa5e8fe..9179e9e46 100644 --- a/selfdrive/ui/mici/onroad/driver_camera_dialog.py +++ b/selfdrive/ui/mici/onroad/driver_camera_dialog.py @@ -28,7 +28,7 @@ class DriverCameraDialog(NavWidget): if not no_escape: # TODO: this can grow unbounded, should be given some thought device.add_interactive_timeout_callback(self.stop_dmonitoringmodeld) - self.set_back_callback(self._dismiss) + self.set_back_callback(self.stop_dmonitoringmodeld) self.set_back_enabled(not no_escape) # Load eye icons @@ -58,9 +58,6 @@ class DriverCameraDialog(NavWidget): def _handle_mouse_release(self, _): ui_state.params.remove("DriverTooDistracted") - def _dismiss(self): - self.stop_dmonitoringmodeld() - def close(self): if self._camera_view: self._camera_view.close() From 9e55577cc775be2feef780e4a573b2227ddef339 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 3 Dec 2025 20:41:58 -0800 Subject: [PATCH 531/910] Clean up DM dialog CameraView bound method (#36770) * clean up * why not? * clean up --- .../ui/mici/onroad/driver_camera_dialog.py | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/selfdrive/ui/mici/onroad/driver_camera_dialog.py b/selfdrive/ui/mici/onroad/driver_camera_dialog.py index 9179e9e46..624a659eb 100644 --- a/selfdrive/ui/mici/onroad/driver_camera_dialog.py +++ b/selfdrive/ui/mici/onroad/driver_camera_dialog.py @@ -15,12 +15,19 @@ EventName = log.OnroadEvent.EventName EVENT_TO_INT = EventName.schema.enumerants +class DriverCameraView(CameraView): + def _calc_frame_matrix(self, rect: rl.Rectangle): + base = super()._calc_frame_matrix(rect) + driver_view_ratio = 1.5 + base[0, 0] *= driver_view_ratio + base[1, 1] *= driver_view_ratio + return base + + class DriverCameraDialog(NavWidget): def __init__(self, no_escape=False): super().__init__() - self._camera_view = CameraView("camerad", VisionStreamType.VISION_STREAM_DRIVER) - self._original_calc_frame_matrix = self._camera_view._calc_frame_matrix - self._camera_view._calc_frame_matrix = self._calc_driver_frame_matrix + self._camera_view = DriverCameraView("camerad", VisionStreamType.VISION_STREAM_DRIVER) self.driver_state_renderer = DriverStateRenderer(lines=True) self.driver_state_renderer.set_rect(rl.Rectangle(0, 0, 200, 200)) self.driver_state_renderer.load_icons() @@ -218,13 +225,6 @@ class DriverCameraDialog(NavWidget): glasses_prob = driver_data.sunglassesProb rl.draw_texture_v(self._glasses_texture, glasses_pos, rl.Color(70, 80, 161, int(255 * glasses_prob))) - def _calc_driver_frame_matrix(self, rect: rl.Rectangle): - base = self._original_calc_frame_matrix(rect) - driver_view_ratio = 1.5 - base[0, 0] *= driver_view_ratio - base[1, 1] *= driver_view_ratio - return base - if __name__ == "__main__": gui_app.init_window("Driver Camera View (mici)") From cc7dd066d211053bd24ab930955203a97ccb97fa Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 3 Dec 2025 21:55:05 -0800 Subject: [PATCH 532/910] ui: call modal hide_event (#36772) * start, not fully working since hide is called before last render * clean up --- system/ui/lib/application.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 79e68aa67..26e446612 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -339,6 +339,9 @@ class GuiApplication: def set_modal_overlay(self, overlay, callback: Callable | None = None): if self._modal_overlay.overlay is not None: + if hasattr(self._modal_overlay.overlay, 'hide_event'): + self._modal_overlay.overlay.hide_event() + if self._modal_overlay.callback is not None: self._modal_overlay.callback(-1) @@ -557,6 +560,8 @@ class GuiApplication: # Clear the overlay and execute the callback original_modal = self._modal_overlay self._modal_overlay = ModalOverlay() + if hasattr(original_modal.overlay, 'hide_event'): + original_modal.overlay.hide_event() if original_modal.callback is not None: original_modal.callback(result) return True From 4edbc7d0cf4dec949faf75cc5365d83ff5f6c388 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 3 Dec 2025 22:19:30 -0800 Subject: [PATCH 533/910] DriverCameraDialog: proper clean up (#36775) * fixes leak * wait can't do this, we need close after all * wait can't do this, we need close after all * clean up memory --- selfdrive/ui/mici/onroad/driver_camera_dialog.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/selfdrive/ui/mici/onroad/driver_camera_dialog.py b/selfdrive/ui/mici/onroad/driver_camera_dialog.py index 624a659eb..e5399b85d 100644 --- a/selfdrive/ui/mici/onroad/driver_camera_dialog.py +++ b/selfdrive/ui/mici/onroad/driver_camera_dialog.py @@ -34,8 +34,8 @@ class DriverCameraDialog(NavWidget): self._pm = messaging.PubMaster(['selfdriveState']) if not no_escape: # TODO: this can grow unbounded, should be given some thought - device.add_interactive_timeout_callback(self.stop_dmonitoringmodeld) - self.set_back_callback(self.stop_dmonitoringmodeld) + device.add_interactive_timeout_callback(lambda: gui_app.set_modal_overlay(None)) + self.set_back_callback(lambda: gui_app.set_modal_overlay(None)) self.set_back_enabled(not no_escape) # Load eye icons @@ -47,10 +47,6 @@ class DriverCameraDialog(NavWidget): self._load_eye_textures() - def stop_dmonitoringmodeld(self): - ui_state.params.put_bool("IsDriverViewEnabled", False) - gui_app.set_modal_overlay(None) - def show_event(self): super().show_event() ui_state.params.put_bool("IsDriverViewEnabled", True) @@ -60,11 +56,15 @@ class DriverCameraDialog(NavWidget): def hide_event(self): super().hide_event() + ui_state.params.put_bool("IsDriverViewEnabled", False) device.reset_interactive_timeout() def _handle_mouse_release(self, _): ui_state.params.remove("DriverTooDistracted") + def __del__(self): + self.close() + def close(self): if self._camera_view: self._camera_view.close() From 1f967668a545b34e2a3ffc85d5e56afdd837efd7 Mon Sep 17 00:00:00 2001 From: Nayan Date: Thu, 4 Dec 2025 01:51:08 -0500 Subject: [PATCH 534/910] ui: add sunnypilot font (#1552) add sp font --- selfdrive/assets/fonts/Audiowide-Regular.ttf | 3 +++ system/ui/lib/application.py | 1 + 2 files changed, 4 insertions(+) create mode 100644 selfdrive/assets/fonts/Audiowide-Regular.ttf diff --git a/selfdrive/assets/fonts/Audiowide-Regular.ttf b/selfdrive/assets/fonts/Audiowide-Regular.ttf new file mode 100644 index 000000000..1b6913947 --- /dev/null +++ b/selfdrive/assets/fonts/Audiowide-Regular.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:434a720871336d359378beff5ebff3f9fd654d958693d272c7c6f2e271c7e41c +size 47676 diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index e4850e9cb..60a110293 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -94,6 +94,7 @@ class FontWeight(StrEnum): BOLD = "Inter-Bold.fnt" SEMI_BOLD = "Inter-SemiBold.fnt" UNIFONT = "unifont.fnt" + AUDIOWIDE = "Audiowide-Regular.fnt" # Small UI fonts DISPLAY_REGULAR = "Inter-Regular.fnt" From 93f2076c7e7ac1274cfdc7b39567fd1319395e6b Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Thu, 4 Dec 2025 17:54:11 +0800 Subject: [PATCH 535/910] ui: fix crash caused by double shader unload in CameraView (#36778) fix double free isuue --- selfdrive/ui/mici/onroad/cameraview.py | 1 + 1 file changed, 1 insertion(+) diff --git a/selfdrive/ui/mici/onroad/cameraview.py b/selfdrive/ui/mici/onroad/cameraview.py index 995c4618f..f3e0ef409 100644 --- a/selfdrive/ui/mici/onroad/cameraview.py +++ b/selfdrive/ui/mici/onroad/cameraview.py @@ -197,6 +197,7 @@ class CameraView(Widget): # Clean up shader if self.shader and self.shader.id: rl.unload_shader(self.shader) + self.shader.id = 0 self.frame = None self.available_streams.clear() From 45b7d60263da75363a3a3fa8caff1532da1e384c Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 4 Dec 2025 01:56:38 -0800 Subject: [PATCH 536/910] ui: fix dialog memory leak (#36767) * weakref alternative * and here * clean up * fix * rm --- selfdrive/ui/mici/widgets/dialog.py | 10 ++++++++++ system/ui/widgets/__init__.py | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index b11056f99..26845765c 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -40,6 +40,11 @@ class BigDialogBase(NavWidget, abc.ABC): # move to right side self._right_btn._rect.x = self._rect.x + self._rect.width - self._right_btn._rect.width + def hide_event(self): + # Free reference to self to allow refcount to go to zero + super().hide_event() + self.set_back_callback(None) + def _render(self, _) -> DialogResult: """ Allows `gui_app.set_modal_overlay(BigDialog(...))`. @@ -163,6 +168,11 @@ class BigInputDialog(BigDialogBase): confirm_callback(self._keyboard.text()) self._confirm_callback = confirm_callback_wrapper + def hide_event(self): + # Free reference to self to allow refcount to go to zero + super().hide_event() + self._confirm_callback = None + def _update_state(self): super()._update_state() diff --git a/system/ui/widgets/__init__.py b/system/ui/widgets/__init__.py index 546c682f3..efa35bc01 100644 --- a/system/ui/widgets/__init__.py +++ b/system/ui/widgets/__init__.py @@ -252,7 +252,7 @@ class NavWidget(Widget, abc.ABC): def set_back_enabled(self, enabled: bool | Callable[[], bool]) -> None: self._back_enabled = enabled - def set_back_callback(self, callback: Callable[[], None]) -> None: + def set_back_callback(self, callback: Callable[[], None] | None) -> None: self._back_callback = callback def _handle_mouse_event(self, mouse_event: MouseEvent) -> None: From cd9b08492ec3c8b840edc53656ef70dc3760ae90 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 4 Dec 2025 02:00:45 -0800 Subject: [PATCH 537/910] ui: small TrainingGuide clean up --- selfdrive/ui/mici/layouts/onboarding.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/selfdrive/ui/mici/layouts/onboarding.py b/selfdrive/ui/mici/layouts/onboarding.py index 52dbb785d..a196d20c4 100644 --- a/selfdrive/ui/mici/layouts/onboarding.py +++ b/selfdrive/ui/mici/layouts/onboarding.py @@ -92,11 +92,10 @@ class TrainingGuideDMTutorial(Widget): super().__init__() self._title_header = TermsHeader("fill the circle to continue", gui_app.texture("icons_mici/setup/green_dm.png", 60, 60)) - self._original_continue_callback = continue_callback - # Wrap the continue callback to restore settings def wrapped_continue_callback(): - self._restore_settings() + device.set_offroad_brightness(None) + device.reset_interactive_timeout() continue_callback() self._dialog = DriverCameraSetupDialog(wrapped_continue_callback) @@ -114,10 +113,6 @@ class TrainingGuideDMTutorial(Widget): device.set_offroad_brightness(100) device.reset_interactive_timeout(300) # 5 minutes - def _restore_settings(self): - device.set_offroad_brightness(None) - device.reset_interactive_timeout() - def _update_state(self): super()._update_state() if device.awake: From 2947af42fc8f44b5bd0cdf290e01840c89a031b1 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Thu, 4 Dec 2025 18:23:37 +0800 Subject: [PATCH 538/910] ui: fix TraningGuide leak (#36763) * fix TraningGuide leak * other thing * this is truly the simplest way --------- Co-authored-by: Shane Smiskol --- selfdrive/ui/mici/layouts/onboarding.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/selfdrive/ui/mici/layouts/onboarding.py b/selfdrive/ui/mici/layouts/onboarding.py index a196d20c4..abf772ce5 100644 --- a/selfdrive/ui/mici/layouts/onboarding.py +++ b/selfdrive/ui/mici/layouts/onboarding.py @@ -1,6 +1,7 @@ from enum import IntEnum from collections.abc import Callable +import weakref import pyray as rl from openpilot.system.ui.lib.application import FontWeight, gui_app from openpilot.system.ui.widgets import Widget @@ -209,11 +210,17 @@ class TrainingGuide(Widget): self._completed_callback = completed_callback self._step = 0 + self_ref = weakref.ref(self) + + def on_continue(): + if obj := self_ref(): + obj._advance_step() + self._steps = [ - TrainingGuideAttentionNotice(continue_callback=self._advance_step), - TrainingGuidePreDMTutorial(continue_callback=self._advance_step), - TrainingGuideDMTutorial(continue_callback=self._advance_step), - TrainingGuideRecordFront(continue_callback=self._advance_step), + TrainingGuideAttentionNotice(continue_callback=on_continue), + TrainingGuidePreDMTutorial(continue_callback=on_continue), + TrainingGuideDMTutorial(continue_callback=on_continue), + TrainingGuideRecordFront(continue_callback=on_continue), ] def _advance_step(self): From f962a36fd8e0a186f0fe15232744e916857cc794 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 4 Dec 2025 02:39:41 -0800 Subject: [PATCH 539/910] Fix ui crashing replay/selfdrived (#36760) * fix * clean up * type hint --- selfdrive/ui/mici/onroad/driver_camera_dialog.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/selfdrive/ui/mici/onroad/driver_camera_dialog.py b/selfdrive/ui/mici/onroad/driver_camera_dialog.py index e5399b85d..9adb660d8 100644 --- a/selfdrive/ui/mici/onroad/driver_camera_dialog.py +++ b/selfdrive/ui/mici/onroad/driver_camera_dialog.py @@ -31,7 +31,7 @@ class DriverCameraDialog(NavWidget): self.driver_state_renderer = DriverStateRenderer(lines=True) self.driver_state_renderer.set_rect(rl.Rectangle(0, 0, 200, 200)) self.driver_state_renderer.load_icons() - self._pm = messaging.PubMaster(['selfdriveState']) + self._pm: messaging.PubMaster | None = None if not no_escape: # TODO: this can grow unbounded, should be given some thought device.add_interactive_timeout_callback(lambda: gui_app.set_modal_overlay(None)) @@ -53,6 +53,7 @@ class DriverCameraDialog(NavWidget): self._publish_alert_sound(None) device.reset_interactive_timeout(300) ui_state.params.remove("DriverTooDistracted") + self._pm = messaging.PubMaster(['selfdriveState']) def hide_event(self): super().hide_event() @@ -107,6 +108,9 @@ class DriverCameraDialog(NavWidget): def _publish_alert_sound(self, dm_state): """Publish selfdriveState with only alertSound field set""" + if self._pm is None: + return + msg = messaging.new_message('selfdriveState') if dm_state is not None and len(dm_state.events): event_name = EVENT_TO_INT[dm_state.events[0].name] From e72e5d4ebeb926a3b62ba3cd8c9cb0d1b3332524 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Thu, 4 Dec 2025 13:11:27 -0800 Subject: [PATCH 540/910] beeps in key (#36765) beeps in keyt --- selfdrive/assets/sounds/disengage.wav | 4 ++-- selfdrive/assets/sounds/engage.wav | 4 ++-- selfdrive/assets/sounds/make_beeps.py | 19 +++++++++++++++++++ 3 files changed, 23 insertions(+), 4 deletions(-) create mode 100644 selfdrive/assets/sounds/make_beeps.py diff --git a/selfdrive/assets/sounds/disengage.wav b/selfdrive/assets/sounds/disengage.wav index 8983884b2..7bfd97ad7 100644 --- a/selfdrive/assets/sounds/disengage.wav +++ b/selfdrive/assets/sounds/disengage.wav @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c94582be9d921146b3c356e08a7352700c309cb407877c1180542811b2d637fa -size 48078 +oid sha256:42bd04a57b527c787a0555503e02a203f7d672c12d448769a3f41f17befbf013 +size 48044 diff --git a/selfdrive/assets/sounds/engage.wav b/selfdrive/assets/sounds/engage.wav index 39d4c749c..8633b5ac2 100644 --- a/selfdrive/assets/sounds/engage.wav +++ b/selfdrive/assets/sounds/engage.wav @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bc2b12bfe816a79307660b6b3d2de87a7643c6ccbfc9d1b33804645ad717682a -size 48078 +oid sha256:b1e177499d9439367179cc57a6301b6162393972e3a136cc35c5fdac026bf10a +size 48044 diff --git a/selfdrive/assets/sounds/make_beeps.py b/selfdrive/assets/sounds/make_beeps.py new file mode 100644 index 000000000..6161e80e7 --- /dev/null +++ b/selfdrive/assets/sounds/make_beeps.py @@ -0,0 +1,19 @@ +import numpy as np +from scipy.io import wavfile + + +sr = 48000 +max_int16 = 2**15 - 1 + +def harmonic_beep(freq, duration_seconds): + n_total = int(sr * duration_seconds) + + signal = np.sin(2 * np.pi * freq * np.arange(n_total) / sr) + x = np.arange(n_total) + exp_scale = np.exp(-x/5.5e3) + return max_int16 * signal * exp_scale + +engage_beep = harmonic_beep(1661.219, 0.5) +wavfile.write("engage.wav", sr, engage_beep.astype(np.int16)) +disengage_beep = harmonic_beep(1318.51, 0.5) +wavfile.write("disengage.wav", sr, disengage_beep.astype(np.int16)) From d75d80b885de1e2f7339f29135aa86e444fdbeda Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 4 Dec 2025 17:53:33 -0500 Subject: [PATCH 541/910] ui: add right-aligned value display support in `ListItemSP` (#1554) * ui: add right-aligned value display support in `ListItemSP` * lint * styles --- system/ui/sunnypilot/lib/styles.py | 1 + system/ui/sunnypilot/widgets/list_view.py | 27 ++++++++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/system/ui/sunnypilot/lib/styles.py b/system/ui/sunnypilot/lib/styles.py index 3597509dc..c1f8c3926 100644 --- a/system/ui/sunnypilot/lib/styles.py +++ b/system/ui/sunnypilot/lib/styles.py @@ -17,6 +17,7 @@ class Base: ITEM_TEXT_FONT_SIZE = 50 ITEM_DESC_FONT_SIZE = 40 ITEM_DESC_V_OFFSET = 150 + ITEM_TEXT_VALUE_COLOR = rl.Color(170, 170, 170, 255) CLOSE_BTN_SIZE = 160 # Toggle Control diff --git a/system/ui/sunnypilot/widgets/list_view.py b/system/ui/sunnypilot/widgets/list_view.py index f05de8139..315840f7e 100644 --- a/system/ui/sunnypilot/widgets/list_view.py +++ b/system/ui/sunnypilot/widgets/list_view.py @@ -8,9 +8,10 @@ from collections.abc import Callable import pyray as rl from openpilot.common.params import Params -from openpilot.system.ui.lib.application import MousePos +from openpilot.system.ui.lib.application import gui_app, MousePos, FontWeight from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.sunnypilot.widgets.toggle import ToggleSP +from openpilot.system.ui.widgets.label import gui_label from openpilot.system.ui.widgets.list_view import ListItem, ToggleAction, ItemAction, MultipleButtonAction, _resolve_value from openpilot.system.ui.sunnypilot.lib.styles import style from openpilot.system.ui.sunnypilot.widgets.option_control import OptionControlSP, LABEL_WIDTH @@ -86,6 +87,17 @@ class ListItemSP(ListItem): self.inline = inline if not self.inline: self._rect.height += style.ITEM_BASE_HEIGHT/1.75 + self._right_value_source: str | Callable[[], str] | None = None + self._right_value_font = gui_app.font(FontWeight.NORMAL) + + def set_right_value(self, value: str | Callable[[], str]): + self._right_value_source = value + + @property + def right_value(self) -> str: + if self._right_value_source is None: + return "" + return str(_resolve_value(self._right_value_source, "")) def get_item_height(self, font: rl.Font, max_width: int) -> float: height = super().get_item_height(font, max_width) @@ -154,6 +166,19 @@ class ListItemSP(ListItem): item_y = self._rect.y + (style.ITEM_BASE_HEIGHT - self._text_size.y) // 2 rl.draw_text_ex(self._font, self.title, rl.Vector2(text_x, item_y), style.ITEM_TEXT_FONT_SIZE, 0, self.title_color) + value_text = self.right_value + if value_text: + # area from after the title to the right edge of the row + value_rect = rl.Rectangle( + text_x, # start at the beginning of the text area + self._rect.y, + self._rect.width - (text_x - self._rect.x) - style.ITEM_PADDING, + style.ITEM_BASE_HEIGHT, + ) + if value_rect.width > 0: + gui_label(value_rect, value_text, font_size=style.ITEM_TEXT_FONT_SIZE, color=style.ITEM_TEXT_VALUE_COLOR, font_weight=FontWeight.NORMAL, + alignment=rl.GuiTextAlignment.TEXT_ALIGN_RIGHT, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE) + # Render toggle and handle callback if self.action_item.render(left_rect) and self.action_item.enabled: if self.callback: From 224e2c271be888e80c1df9728cd448ca1e0a8702 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 4 Dec 2025 16:51:18 -0800 Subject: [PATCH 542/910] Revert "ui: fix dialog memory leak" (#36787) Revert "ui: fix dialog memory leak (#36767)" This reverts commit 45b7d60263da75363a3a3fa8caff1532da1e384c. --- selfdrive/ui/mici/widgets/dialog.py | 10 ---------- system/ui/widgets/__init__.py | 2 +- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index 26845765c..b11056f99 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -40,11 +40,6 @@ class BigDialogBase(NavWidget, abc.ABC): # move to right side self._right_btn._rect.x = self._rect.x + self._rect.width - self._right_btn._rect.width - def hide_event(self): - # Free reference to self to allow refcount to go to zero - super().hide_event() - self.set_back_callback(None) - def _render(self, _) -> DialogResult: """ Allows `gui_app.set_modal_overlay(BigDialog(...))`. @@ -168,11 +163,6 @@ class BigInputDialog(BigDialogBase): confirm_callback(self._keyboard.text()) self._confirm_callback = confirm_callback_wrapper - def hide_event(self): - # Free reference to self to allow refcount to go to zero - super().hide_event() - self._confirm_callback = None - def _update_state(self): super()._update_state() diff --git a/system/ui/widgets/__init__.py b/system/ui/widgets/__init__.py index efa35bc01..546c682f3 100644 --- a/system/ui/widgets/__init__.py +++ b/system/ui/widgets/__init__.py @@ -252,7 +252,7 @@ class NavWidget(Widget, abc.ABC): def set_back_enabled(self, enabled: bool | Callable[[], bool]) -> None: self._back_enabled = enabled - def set_back_callback(self, callback: Callable[[], None] | None) -> None: + def set_back_callback(self, callback: Callable[[], None]) -> None: self._back_callback = callback def _handle_mouse_event(self, mouse_event: MouseEvent) -> None: From c6818bd07f8cadb7a42880f4a814442575875c24 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 4 Dec 2025 21:41:58 -0500 Subject: [PATCH 543/910] ui: customizable value colors for `ButtonActionSP` and `ListViewSP` (#1555) ui: introduce customizable value colors for `ButtonActionSP` and `ListViewSP` --- selfdrive/ui/layouts/settings/device.py | 3 ++ selfdrive/ui/layouts/settings/software.py | 3 ++ system/ui/sunnypilot/lib/styles.py | 2 + system/ui/sunnypilot/widgets/list_view.py | 57 +++++++++++++++++++---- system/ui/widgets/network.py | 1 + 5 files changed, 56 insertions(+), 10 deletions(-) diff --git a/selfdrive/ui/layouts/settings/device.py b/selfdrive/ui/layouts/settings/device.py index 078623c88..8830ef946 100644 --- a/selfdrive/ui/layouts/settings/device.py +++ b/selfdrive/ui/layouts/settings/device.py @@ -19,6 +19,9 @@ from openpilot.system.ui.widgets.list_view import text_item, button_item, dual_b from openpilot.system.ui.widgets.option_dialog import MultiOptionDialog from openpilot.system.ui.widgets.scroller_tici import Scroller +if gui_app.sunnypilot_ui(): + from openpilot.system.ui.sunnypilot.widgets.list_view import button_item_sp as button_item + # Description constants DESCRIPTIONS = { 'pair_device': tr_noop("Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer."), diff --git a/selfdrive/ui/layouts/settings/software.py b/selfdrive/ui/layouts/settings/software.py index 4b8b7015f..c4f899d79 100644 --- a/selfdrive/ui/layouts/settings/software.py +++ b/selfdrive/ui/layouts/settings/software.py @@ -11,6 +11,9 @@ from openpilot.system.ui.widgets.list_view import button_item, text_item, ListIt from openpilot.system.ui.widgets.option_dialog import MultiOptionDialog from openpilot.system.ui.widgets.scroller_tici import Scroller +if gui_app.sunnypilot_ui(): + from openpilot.system.ui.sunnypilot.widgets.list_view import button_item_sp as button_item + # TODO: remove this. updater fails to respond on startup if time is not correct UPDATED_TIMEOUT = 10 # seconds to wait for updated to respond diff --git a/system/ui/sunnypilot/lib/styles.py b/system/ui/sunnypilot/lib/styles.py index c1f8c3926..3e1d2dc40 100644 --- a/system/ui/sunnypilot/lib/styles.py +++ b/system/ui/sunnypilot/lib/styles.py @@ -20,6 +20,8 @@ class Base: ITEM_TEXT_VALUE_COLOR = rl.Color(170, 170, 170, 255) CLOSE_BTN_SIZE = 160 + TEXT_PADDING = 20 + # Toggle Control TOGGLE_HEIGHT = 120 TOGGLE_WIDTH = int(TOGGLE_HEIGHT * 1.75) diff --git a/system/ui/sunnypilot/widgets/list_view.py b/system/ui/sunnypilot/widgets/list_view.py index 315840f7e..bd68d35a4 100644 --- a/system/ui/sunnypilot/widgets/list_view.py +++ b/system/ui/sunnypilot/widgets/list_view.py @@ -12,7 +12,8 @@ from openpilot.system.ui.lib.application import gui_app, MousePos, FontWeight from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.sunnypilot.widgets.toggle import ToggleSP from openpilot.system.ui.widgets.label import gui_label -from openpilot.system.ui.widgets.list_view import ListItem, ToggleAction, ItemAction, MultipleButtonAction, _resolve_value +from openpilot.system.ui.widgets.list_view import ListItem, ToggleAction, ItemAction, MultipleButtonAction, ButtonAction, \ + _resolve_value, BUTTON_WIDTH, BUTTON_HEIGHT, TEXT_PADDING from openpilot.system.ui.sunnypilot.lib.styles import style from openpilot.system.ui.sunnypilot.widgets.option_control import OptionControlSP, LABEL_WIDTH @@ -24,6 +25,34 @@ class ToggleActionSP(ToggleAction): self.toggle = ToggleSP(initial_state=initial_state, callback=callback, param=param) +class ButtonActionSP(ButtonAction): + def __init__(self, text: str | Callable[[], str], width: int = style.BUTTON_WIDTH, enabled: bool | Callable[[], bool] = True): + super().__init__(text=text, width=width, enabled=enabled) + self._value_color: rl.Color = style.ITEM_TEXT_VALUE_COLOR + + def set_value(self, value: str | Callable[[], str], color: rl.Color = style.ITEM_TEXT_VALUE_COLOR): + self._value_source = value + self._value_color = color + + def _render(self, rect: rl.Rectangle) -> bool: + """Duplicate of ButtonAction._render, with additional value rendering""" + self._button.set_text(self.text) + self._button.set_enabled(_resolve_value(self.enabled)) + button_rect = rl.Rectangle(rect.x + rect.width - BUTTON_WIDTH, rect.y + (rect.height - BUTTON_HEIGHT) / 2, BUTTON_WIDTH, BUTTON_HEIGHT) + self._button.render(button_rect) + + value_text = self.value + if value_text: + value_rect = rl.Rectangle(rect.x, rect.y, rect.width - BUTTON_WIDTH - TEXT_PADDING, rect.height) + gui_label(value_rect, value_text, font_size=style.ITEM_TEXT_FONT_SIZE, color=self._value_color, + font_weight=FontWeight.NORMAL, alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT, + alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE) + + pressed = self._pressed + self._pressed = False + return pressed + + class MultipleButtonActionSP(MultipleButtonAction): def __init__(self, buttons: list[str | Callable[[], str]], button_width: int, selected_index: int = 0, callback: Callable = None, param: str | None = None): @@ -89,9 +118,11 @@ class ListItemSP(ListItem): self._rect.height += style.ITEM_BASE_HEIGHT/1.75 self._right_value_source: str | Callable[[], str] | None = None self._right_value_font = gui_app.font(FontWeight.NORMAL) + self._right_value_color: rl.Color = style.ITEM_TEXT_VALUE_COLOR - def set_right_value(self, value: str | Callable[[], str]): + def set_right_value(self, value: str | Callable[[], str], color: rl.Color = style.ITEM_TEXT_VALUE_COLOR): self._right_value_source = value + self._right_value_color = color @property def right_value(self) -> str: @@ -127,17 +158,17 @@ class ListItemSP(ListItem): return rl.Rectangle(item_rect.x + style.ITEM_PADDING, action_y, item_rect.width - (style.ITEM_PADDING * 2), style.BUTTON_HEIGHT) - right_width = self.action_item.rect.width + right_width = self.action_item.get_width_hint() if right_width == 0: return rl.Rectangle(item_rect.x + style.ITEM_PADDING, item_rect.y, item_rect.width - (style.ITEM_PADDING * 2), style.ITEM_BASE_HEIGHT) - action_width = self.action_item.rect.width - if isinstance(self.action_item, ToggleAction): - action_x = item_rect.x - else: - action_x = item_rect.x + item_rect.width - action_width + content_width = item_rect.width - (style.ITEM_PADDING * 2) + title_width = measure_text_cached(self._font, self.title, style.ITEM_TEXT_FONT_SIZE).x + right_width = min(content_width - title_width, right_width) + + action_x = item_rect.x + item_rect.width - right_width action_y = item_rect.y - return rl.Rectangle(action_x, action_y, action_width, style.ITEM_BASE_HEIGHT) + return rl.Rectangle(action_x, action_y, right_width, style.ITEM_BASE_HEIGHT) def _render(self, _): if not self.is_visible: @@ -176,7 +207,7 @@ class ListItemSP(ListItem): style.ITEM_BASE_HEIGHT, ) if value_rect.width > 0: - gui_label(value_rect, value_text, font_size=style.ITEM_TEXT_FONT_SIZE, color=style.ITEM_TEXT_VALUE_COLOR, font_weight=FontWeight.NORMAL, + gui_label(value_rect, value_text, font_size=style.ITEM_TEXT_FONT_SIZE, color=self._right_value_color, font_weight=FontWeight.NORMAL, alignment=rl.GuiTextAlignment.TEXT_ALIGN_RIGHT, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE) # Render toggle and handle callback @@ -236,3 +267,9 @@ def option_item_sp(title: str | Callable[[], str], param: str, enabled, on_value_changed, value_map, label_width, use_float_scaling, label_callback ) return ListItemSP(title=title, description=description, action_item=action, icon=icon) + + +def button_item_sp(title: str | Callable[[], str], button_text: str | Callable[[], str], description: str | Callable[[], str] | None = None, + callback: Callable | None = None, enabled: bool | Callable[[], bool] = True) -> ListItemSP: + action = ButtonActionSP(text=button_text, enabled=enabled) + return ListItemSP(title=title, description=description, action_item=action, callback=callback) diff --git a/system/ui/widgets/network.py b/system/ui/widgets/network.py index 71755cc28..2aeefd544 100644 --- a/system/ui/widgets/network.py +++ b/system/ui/widgets/network.py @@ -16,6 +16,7 @@ from openpilot.system.ui.widgets.scroller_tici import Scroller from openpilot.system.ui.widgets.list_view import ButtonAction, ListItem, MultipleButtonAction, ToggleAction, button_item, text_item if gui_app.sunnypilot_ui(): + from openpilot.system.ui.sunnypilot.widgets.list_view import button_item_sp as button_item from openpilot.system.ui.sunnypilot.widgets.list_view import ListItemSP as ListItem from openpilot.system.ui.sunnypilot.widgets.list_view import ToggleActionSP as ToggleAction from openpilot.system.ui.sunnypilot.widgets.list_view import MultipleButtonActionSP as MultipleButtonAction From b5b170b65a09b0725bca77fa0775e27059c2bc7e Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 5 Dec 2025 00:30:48 -0500 Subject: [PATCH 544/910] ui: sunnypilot sponsor tier color mapping (#1556) * ui: sunnypilot sponsor tier color mapping * lint --- sunnypilot/sunnylink/sunnylink_state.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/sunnypilot/sunnylink/sunnylink_state.py b/sunnypilot/sunnylink/sunnylink_state.py index 4d0b397e0..13b5ad81e 100644 --- a/sunnypilot/sunnylink/sunnylink_state.py +++ b/sunnypilot/sunnylink/sunnylink_state.py @@ -8,11 +8,13 @@ from enum import IntEnum import threading import time import json +import pyray as rl from cereal import messaging from openpilot.common.params import Params from openpilot.common.swaglog import cloudlog from openpilot.sunnypilot.sunnylink.api import UNREGISTERED_SUNNYLINK_DONGLE_ID, SunnylinkApi +from openpilot.system.ui.sunnypilot.lib.styles import style class RoleType(IntEnum): @@ -201,5 +203,19 @@ class SunnylinkState: network_type = self._sm["deviceState"].networkType return bool(network_type != 0) + def get_sponsor_tier_color(self) -> rl.Color: + tier = self.get_sponsor_tier() + + if tier == SponsorTier.GUARDIAN: + return rl.Color(255, 215, 0, 255) + elif tier == SponsorTier.BENEFACTOR: + return rl.Color(60, 179, 113, 255) + elif tier == SponsorTier.CONTRIBUTOR: + return rl.Color(70, 130, 180, 255) + elif tier == SponsorTier.SUPPORTER: + return rl.Color(147, 112, 219, 255) + else: + return style.ITEM_TEXT_VALUE_COLOR + def __del__(self): self.stop() From 1d9bda65fea8da24a2804a5a3605fe924eeaed18 Mon Sep 17 00:00:00 2001 From: Nayan Date: Fri, 5 Dec 2025 01:01:55 -0500 Subject: [PATCH 545/910] ui: sunnylink panel (#1494) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * param to control stock vs sp ui * init styles * SP Toggles * Lint * optimizations * sp raylib preview * fix callback * fix ui preview * sunnylink state * introducing ui_state_sp for py * poll from ui_state_sp * cloudlog & ruff * param to control stock vs sp ui * better * better padding * this * listitem -> listitemsp * add show_description method * remove padding from line separator. like, WHY? 😩😩 * ui: `GuiApplicationExt` * add to readme * use gui_app.sunnypilot_ui() * use gui_app.sunnypilot_ui() * fetch only when connected to network * sponsor & pairing qr * init panel elements * backup & restore * fruit loops * update * enable, disable, enable, disable * handle layout updates * not needed * change it up * better * scroller -> scroller_tici * optimizations * remove Params * fix button disablement * ui_state_sp changes * keep enabled * add header text * dad jokes? * no * lint? Lint! * final touches * add sp font * use sp font * some * ui: add right-aligned value display support in `ListItem` (in another pr) * display sunnylink device id * display sunnylink device id and sponsor tiers * ui: add right-aligned value display support in `ListItemSP` * lint * styles * lint * ui: introduce customizable value colors for `ButtonActionSP` and `ListViewSP` * support * convert to str * disable if paired * colored sponsors * hide and disable pairing button if paired * texts * ui: sunnypilot sponsor tier color mapping * lint * dongle id for ui preview --------- Co-authored-by: Jason Wen Co-authored-by: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> --- .../sunnypilot/layouts/settings/settings.py | 2 +- .../sunnypilot/layouts/settings/sunnylink.py | 326 +++++++++++++++++- .../ui/tests/test_ui/raylib_screenshots.py | 1 + .../widgets/sunnylink_pairing_dialog.py | 137 ++++++++ 4 files changed, 459 insertions(+), 7 deletions(-) create mode 100644 system/ui/sunnypilot/widgets/sunnylink_pairing_dialog.py diff --git a/selfdrive/ui/sunnypilot/layouts/settings/settings.py b/selfdrive/ui/sunnypilot/layouts/settings/settings.py index 905338552..45c9b9348 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/settings.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/settings.py @@ -112,7 +112,7 @@ class SettingsLayoutSP(OP.SettingsLayout): self._panels = { OP.PanelType.DEVICE: PanelInfo(tr_noop("Device"), DeviceLayoutSP(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_home.png"), OP.PanelType.NETWORK: PanelInfo(tr_noop("Network"), NetworkUISP(wifi_manager), icon="icons/network.png"), - OP.PanelType.SUNNYLINK: PanelInfo(tr_noop("sunnylink"), SunnylinkLayout(), icon="icons/shell.png"), + OP.PanelType.SUNNYLINK: PanelInfo(tr_noop("sunnylink"), SunnylinkLayout(), icon="icons/wifi_strength_full.png"), OP.PanelType.TOGGLES: PanelInfo(tr_noop("Toggles"), TogglesLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_toggle.png"), OP.PanelType.SOFTWARE: PanelInfo(tr_noop("Software"), SoftwareLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_software.png"), OP.PanelType.MODELS: PanelInfo(tr_noop("Models"), ModelsLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_models.png"), diff --git a/selfdrive/ui/sunnypilot/layouts/settings/sunnylink.py b/selfdrive/ui/sunnypilot/layouts/settings/sunnylink.py index b1e12c17a..7125fdf3f 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/sunnylink.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/sunnylink.py @@ -4,27 +4,341 @@ Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. This file is part of sunnypilot and is licensed under the MIT License. See the LICENSE.md file in the root directory for more details. """ -from openpilot.common.params import Params -from openpilot.system.ui.widgets.scroller_tici import Scroller -from openpilot.system.ui.widgets import Widget +from cereal import custom +from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.sunnypilot.sunnylink.api import UNREGISTERED_SUNNYLINK_DONGLE_ID +from openpilot.system.ui.lib.application import gui_app, FontWeight +from openpilot.system.ui.lib.multilang import tr +from openpilot.system.ui.sunnypilot.widgets.sunnylink_pairing_dialog import SunnylinkPairingDialog +from openpilot.system.ui.widgets.button import ButtonStyle, Button +from openpilot.system.ui.widgets.confirm_dialog import alert_dialog, ConfirmDialog +from openpilot.system.ui.widgets.label import UnifiedLabel +from openpilot.system.ui.widgets.list_view import button_item, dual_button_item +from openpilot.system.ui.widgets.scroller_tici import Scroller, LineSeparator +from openpilot.system.ui.widgets import Widget, DialogResult +from openpilot.system.ui.sunnypilot.widgets.list_view import toggle_item_sp +import pyray as rl + +if gui_app.sunnypilot_ui(): + from openpilot.system.ui.sunnypilot.widgets.list_view import button_item_sp as button_item + + +class SunnylinkHeader(Widget): + def __init__(self): + super().__init__() + + self._title = UnifiedLabel( + text="🚀 sunnylink 🚀", + font_size=90, + font_weight=FontWeight.AUDIOWIDE, + text_color=rl.WHITE, + alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER, + alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP, + wrap_text=False, + elide=False + ) + + self._description = UnifiedLabel( + text=tr("For secure backup, restore, and remote configuration"), + font_size=40, + font_weight=FontWeight.LIGHT, + text_color=rl.Color(0, 255, 0, 255), # Green + alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER, + alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP, + wrap_text=True, + elide=False + ) + + self._sponsor_msg = UnifiedLabel( + text=tr("Sponsorship isn't required for basic backup/restore") + "\n" + + tr("Click the Sponsor button for more details"), + font_size=35, + font_weight=FontWeight.LIGHT, + text_color=rl.Color(255, 165, 0, 255), # Orange + alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER, + alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP, + wrap_text=True, + elide=False + ) + + self._padding = 20 + self._spacing = 10 + + def set_parent_rect(self, parent_rect: rl.Rectangle) -> None: + super().set_parent_rect(parent_rect) + + content_width = int(parent_rect.width - (self._padding * 2)) + + title_height = self._title.get_content_height(content_width) + desc_height = self._description.get_content_height(content_width) + sponsor_height = self._sponsor_msg.get_content_height(content_width) + + total_height = (self._padding + title_height + self._spacing + + desc_height + self._spacing + sponsor_height + self._padding) + + self._rect.width = parent_rect.width + self._rect.height = total_height + + def _render(self, rect: rl.Rectangle): + content_width = rect.width - (self._padding * 2) + current_y = rect.y + self._padding + + # Render title + title_height = self._title.get_content_height(int(content_width)) + title_rect = rl.Rectangle(rect.x + self._padding, current_y, content_width, title_height) + self._title.render(title_rect) + current_y += title_height + self._spacing + + # Render description + desc_height = self._description.get_content_height(int(content_width)) + desc_rect = rl.Rectangle(rect.x + self._padding, current_y, content_width, desc_height) + self._description.render(desc_rect) + current_y += desc_height + self._spacing + + # Render sponsor message + sponsor_height = self._sponsor_msg.get_content_height(int(content_width)) + sponsor_rect = rl.Rectangle(rect.x + self._padding, current_y, content_width, sponsor_height) + self._sponsor_msg.render(sponsor_rect) + + +class SunnylinkDescriptionItem(Widget): + def __init__(self): + super().__init__() + self._description = UnifiedLabel( + text="", + font_size=40, + font_weight=FontWeight.LIGHT, + text_color=rl.WHITE, + alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT, + alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP, + wrap_text=True, + elide=False, + ) + self._padding = 20 + + def set_parent_rect(self, parent_rect: rl.Rectangle) -> None: + super().set_parent_rect(parent_rect) + desc_height = self._description.get_content_height(int(parent_rect.width)) + self._padding * 2 + + self._rect.width = parent_rect.width + self._rect.height = desc_height + + def set_text(self, text: str): + self._description.set_text(text) + + def set_color(self, color: rl.Color): + self._description.set_text_color(color) + + def _render(self, rect: rl.Rectangle): + content_width = rect.width - (self._padding * 2) + + desc_height = self._description.get_content_height(int(content_width)) + desc_rect = rl.Rectangle(rect.x + self._padding, rect.y, content_width, desc_height) + self._description.render(desc_rect) class SunnylinkLayout(Widget): def __init__(self): super().__init__() - self._params = Params() + self._sunnylink_pairing_dialog: SunnylinkPairingDialog | None = None + self._restore_in_progress = False + self._backup_in_progress = False + self._sunnylink_enabled = ui_state.params.get("SunnylinkEnabled") + items = self._initialize_items() - self._scroller = Scroller(items, line_separator=True, spacing=0) + self._scroller = Scroller(items, line_separator=False, spacing=0) def _initialize_items(self): - items = [ + self._sunnylink_toggle = toggle_item_sp( + title=tr("Enable sunnylink"), + description=tr("This is the master switch, it will allow you to cutoff any sunnylink requests should you want to do that."), + param="SunnylinkEnabled", + callback=self._sunnylink_toggle_callback + ) + self._sunnylink_description = SunnylinkDescriptionItem() + self._sunnylink_description.set_visible(False) + + self._sponsor_btn = button_item( + title=tr("Sponsor Status"), + button_text=tr("SPONSOR"), + description=tr( + "Become a sponsor of sunnypilot to get early access to sunnylink features when they become available."), + callback=lambda: self._handle_pair_btn(False) + ) + self._pair_btn = button_item( + title=tr("Pair GitHub Account"), + button_text=tr("Not Paired"), + description=tr( + "Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink."), + callback=lambda: self._handle_pair_btn(True) + ) + self._sunnylink_uploader_toggle = toggle_item_sp( + title=tr("Enable sunnylink uploader (infrastructure test)"), + description=tr("Enable sunnylink uploader to allow sunnypilot to upload your driving data to sunnypilot servers. ") + + tr("(Only for highest tiers, and does NOT bring ANY benefit to you yet. We are just testing data volume.)"), + param="EnableSunnylinkUploader" + ) + self._sunnylink_backup_restore_buttons = dual_button_item( + description="", + left_text=tr("Backup Settings"), + right_text=tr("Restore Settings"), + left_callback=self._handle_backup_btn, + right_callback=self._handle_restore_btn + ) + self._backup_btn: Button = self._sunnylink_backup_restore_buttons.action_item.left_button # store for easy individual access + self._restore_btn: Button = self._sunnylink_backup_restore_buttons.action_item.right_button + self._backup_btn.set_button_style(ButtonStyle.NORMAL) + self._restore_btn.set_button_style(ButtonStyle.PRIMARY) + + items = [ + SunnylinkHeader(), + LineSeparator(), + self._sunnylink_toggle, + self._sunnylink_description, + LineSeparator(), + self._sponsor_btn, + LineSeparator(), + self._pair_btn, + LineSeparator(), + self._sunnylink_uploader_toggle, + LineSeparator(), + self._sunnylink_backup_restore_buttons ] return items + @staticmethod + def _get_sunnylink_dongle_id() -> str | None: + return str(ui_state.params.get("SunnylinkDongleId") or (lambda: tr("N/A"))) + + def _handle_pair_btn(self, sponsor_pairing: bool = False): + sunnylink_dongle_id = self._get_sunnylink_dongle_id() + if sunnylink_dongle_id == UNREGISTERED_SUNNYLINK_DONGLE_ID: + gui_app.set_modal_overlay(alert_dialog(message=tr("sunnylink Dongle ID not found. ") + + tr("This may be due to weak internet connection or sunnylink registration issue. ") + + tr("Please reboot and try again."))) + elif not self._sunnylink_pairing_dialog: + self._sunnylink_pairing_dialog = SunnylinkPairingDialog(sponsor_pairing) + gui_app.set_modal_overlay(self._sunnylink_pairing_dialog, callback=lambda result: setattr(self, '_sunnylink_pairing_dialog', None)) + + def _handle_backup_btn(self): + backup_dialog = ConfirmDialog(text=tr("Are you sure you want to backup your current sunnypilot settings?"), confirm_text="Backup") + gui_app.set_modal_overlay(backup_dialog, callback=self._backup_handler) + + def _handle_restore_btn(self): + self._restore_btn.set_enabled(False) + restore_dialog = ConfirmDialog(text=tr("Are you sure you want to restore the last backed up sunnypilot settings?"), confirm_text="Restore") + gui_app.set_modal_overlay(restore_dialog, callback=self._restore_handler) + + def _backup_handler(self, dialog_result: int): + if dialog_result == DialogResult.CONFIRM: + self._backup_in_progress = True + self._backup_btn.set_enabled(False) + ui_state.params.put_bool("BackupManager_CreateBackup", True) + + def _restore_handler(self, dialog_result: int): + if dialog_result == DialogResult.CONFIRM: + self._restore_in_progress = True + self._restore_btn.set_enabled(False) + ui_state.params.put("BackupManager_RestoreVersion", "latest") + + def handle_backup_restore_progress(self): + sunnylink_backup_manager = ui_state.sm["backupManagerSP"] + + backup_status = sunnylink_backup_manager.backupStatus + restore_status = sunnylink_backup_manager.restoreStatus + backup_progress = sunnylink_backup_manager.backupProgress + restore_progress = sunnylink_backup_manager.restoreProgress + + if self._backup_in_progress: + self._restore_btn.set_enabled(False) + self._backup_btn.set_enabled(False) + + if backup_status == custom.BackupManagerSP.Status.inProgress: + self._backup_in_progress = True + text = tr(f"Backing up {backup_progress}%") + self._backup_btn.set_text(text) + + elif backup_status == custom.BackupManagerSP.Status.failed: + self._backup_in_progress = False + self._backup_btn.set_enabled(not ui_state.is_onroad()) + self._backup_btn.set_text(tr("Backup Failed")) + + elif (backup_status == custom.BackupManagerSP.Status.completed or + (backup_status == custom.BackupManagerSP.Status.idle and backup_progress == 100.0)): + self._backup_in_progress = False + dialog = alert_dialog(tr("Settings backup completed.")) + gui_app.set_modal_overlay(dialog) + self._backup_btn.set_enabled(not ui_state.is_onroad()) + + elif self._restore_in_progress: + self._restore_btn.set_enabled(False) + self._backup_btn.set_enabled(False) + + if restore_status == custom.BackupManagerSP.Status.inProgress: + self._restore_in_progress = True + text = tr(f"Restoring {restore_progress}%") + self._restore_btn.set_text(text) + + elif restore_status == custom.BackupManagerSP.Status.failed: + self._restore_in_progress = False + self._restore_btn.set_enabled(not ui_state.is_onroad()) + self._restore_btn.set_text(tr("Restore Failed")) + dialog = alert_dialog(tr("Unable to restore the settings, try again later.")) + gui_app.set_modal_overlay(dialog) + + elif (restore_status == custom.BackupManagerSP.Status.completed or + (restore_status == custom.BackupManagerSP.Status.idle and restore_progress == 100.0)): + self._restore_in_progress = False + dialog = alert_dialog(tr("Settings restored. Confirm to restart the interface.")) + gui_app.set_modal_overlay(dialog, callback=lambda: gui_app.request_close()) + + else: + can_enable = self._sunnylink_enabled and not ui_state.is_onroad() + self._backup_btn.set_enabled(can_enable) + self._backup_btn.set_text(tr("Backup Settings")) + self._restore_btn.set_enabled(can_enable) + self._restore_btn.set_text(tr("Restore Settings")) + + def _sunnylink_toggle_callback(self, state: bool): + if state: + description = tr( + "Welcome back!! We're excited to see you've enabled sunnylink again!") + color = rl.Color(0, 255, 0, 255) # Green + else: + description = ("😢 " + tr("Not going to lie, it's sad to see you disabled sunnylink") + + tr(", but we'll be here when you're ready to come back.")) + color = rl.Color(255, 165, 0, 255) # Orange + self._sunnylink_description.set_text(description) + self._sunnylink_description.set_color(color) + self._sunnylink_description.set_visible(True) + self._sunnylink_toggle.show_description(False) + + def _update_state(self): + super()._update_state() + self._sunnylink_enabled = ui_state.params.get_bool("SunnylinkEnabled") + self._sunnylink_toggle.set_right_value(tr("Dongle ID") + ": " + self._get_sunnylink_dongle_id()) + self._sunnylink_toggle.action_item.set_enabled(not ui_state.is_onroad()) + self._sunnylink_toggle.action_item.set_state(self._sunnylink_enabled) + self._sunnylink_uploader_toggle.action_item.set_enabled(self._sunnylink_enabled) + self.handle_backup_restore_progress() + + sponsor_btn_text = tr("THANKS ♥") if ui_state.sunnylink_state.is_sponsor() else tr("SPONSOR") + tier_name = ui_state.sunnylink_state.get_sponsor_tier().name.capitalize() or tr("Not Sponsor") + self._sponsor_btn.action_item.set_text(sponsor_btn_text) + self._sponsor_btn.action_item.set_value(tier_name, ui_state.sunnylink_state.get_sponsor_tier_color()) + self._sponsor_btn.action_item.set_enabled(self._sunnylink_enabled and not ui_state.sunnylink_state.is_sponsor()) + + pair_btn_text = tr("Paired") if ui_state.sunnylink_state.is_paired() else tr("Not Paired") + self._pair_btn.action_item.set_text(pair_btn_text) + self._pair_btn.set_visible(lambda: self._sunnylink_enabled and not ui_state.sunnylink_state.is_paired()) + self._pair_btn.action_item.set_enabled(self._sunnylink_enabled and not ui_state.sunnylink_state.is_paired()) + def _render(self, rect): self._scroller.render(rect) def show_event(self): + super().show_event() self._scroller.show_event() + self._sunnylink_description.set_visible(False) diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py index 164358bfc..e209ab806 100755 --- a/selfdrive/ui/tests/test_ui/raylib_screenshots.py +++ b/selfdrive/ui/tests/test_ui/raylib_screenshots.py @@ -369,6 +369,7 @@ def create_screenshots(): with OpenpilotPrefix(): params = Params() params.put("DongleId", "123456789012345") + params.put("SunnylinkDongleId", "123456789012345") # Set branch name params.put("UpdaterCurrentDescription", VERSION) diff --git a/system/ui/sunnypilot/widgets/sunnylink_pairing_dialog.py b/system/ui/sunnypilot/widgets/sunnylink_pairing_dialog.py new file mode 100644 index 000000000..4fab79caf --- /dev/null +++ b/system/ui/sunnypilot/widgets/sunnylink_pairing_dialog.py @@ -0,0 +1,137 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +import base64 + +import pyray as rl +from openpilot.common.swaglog import cloudlog +from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.selfdrive.ui.widgets.pairing_dialog import PairingDialog +from openpilot.sunnypilot.sunnylink.api import SunnylinkApi, UNREGISTERED_SUNNYLINK_DONGLE_ID, API_HOST +from openpilot.system.ui.lib.application import FontWeight, gui_app +from openpilot.system.ui.lib.multilang import tr +from openpilot.system.ui.lib.wrap_text import wrap_text +from openpilot.system.ui.lib.text_measure import measure_text_cached + + +class SunnylinkPairingDialog(PairingDialog): + """Dialog for device pairing with QR code.""" + + QR_REFRESH_INTERVAL = 300 # 5 minutes in seconds + + def __init__(self, sponsor_pairing: bool = False): + PairingDialog.__init__(self) + self._sponsor_pairing = sponsor_pairing + + def _get_pairing_url(self) -> str: + qr_string = "https://github.com/sponsors/sunnyhaibin" + + if self._sponsor_pairing: + try: + sl_dongle_id = self.params.get("SunnylinkDongleId") or UNREGISTERED_SUNNYLINK_DONGLE_ID + token = SunnylinkApi(sl_dongle_id).get_token() + inner_string = f"1|{sl_dongle_id}|{token}" + payload_bytes = base64.b64encode(inner_string.encode('utf-8')).decode('utf-8') + qr_string = f"{API_HOST}/sso?state={payload_bytes}" + except Exception: + cloudlog.exception("Failed to get pairing token") + + return qr_string + + def _update_state(self): + if ui_state.sunnylink_state.is_paired(): + gui_app.set_modal_overlay(None) + + def _render(self, rect: rl.Rectangle) -> int: + rl.clear_background(rl.Color(224, 224, 224, 255)) + + self._check_qr_refresh() + + margin = 70 + content_rect = rl.Rectangle(rect.x + margin, rect.y + margin, rect.width - 2 * margin, rect.height - 2 * margin) + y = content_rect.y + + # Close button + close_size = 80 + pad = 20 + close_rect = rl.Rectangle(content_rect.x - pad, y - pad, close_size + pad * 2, close_size + pad * 2) + self._close_btn.render(close_rect) + + y += close_size + 40 + + # Title + title = tr("Pair your GitHub account") if self._sponsor_pairing else tr("Early Access: Become a sunnypilot Sponsor") + title_font = gui_app.font(FontWeight.NORMAL) + left_width = int(content_rect.width * 0.5 - 15) + + title_wrapped = wrap_text(title_font, title, 75, left_width) + rl.draw_text_ex(title_font, "\n".join(title_wrapped), rl.Vector2(content_rect.x, y), 75, 0.0, rl.BLACK) + y += len(title_wrapped) * 75 + 60 + + # Two columns: instructions and QR code + remaining_height = content_rect.height - (y - content_rect.y) + right_width = content_rect.width // 2 - 20 + + # Instructions + self._render_instructions(rl.Rectangle(content_rect.x, y, left_width, remaining_height)) + + # QR code + qr_size = min(right_width, content_rect.height) - 40 + qr_x = content_rect.x + left_width + 40 + (right_width - qr_size) // 2 + qr_y = content_rect.y + self._render_qr_code(rl.Rectangle(qr_x, qr_y, qr_size, qr_size)) + + return -1 + + def _render_instructions(self, rect: rl.Rectangle) -> None: + if self._sponsor_pairing: + instructions = [ + tr("Scan the QR code to login to your GitHub account"), + tr("Follow the prompts to complete the pairing process"), + tr("Re-enter the \"sunnylink\" panel to verify sponsorship status"), + tr("If sponsorship status was not updated, please contact a moderator on the community forum at https://community.sunnypilot.ai") + ] + else: + instructions = [ + tr("Scan the QR code to visit sunnyhaibin's GitHub Sponsors page"), + tr("Choose your sponsorship tier and confirm your support"), + tr("Join our Community Forum at https://community.sunnypilot.ai and reach out to a moderator if you have issues") + ] + + font = gui_app.font(FontWeight.BOLD) + y = rect.y + + for i, text in enumerate(instructions): + circle_radius = 25 + circle_x = rect.x + circle_radius + 15 + text_x = rect.x + circle_radius * 2 + 40 + text_width = rect.width - (circle_radius * 2 + 40) + + wrapped = wrap_text(font, text, 47, int(text_width)) + text_height = len(wrapped) * 47 + circle_y = y + text_height // 2 + + # Circle and number + rl.draw_circle(int(circle_x), int(circle_y), circle_radius, rl.Color(70, 70, 70, 255)) + number = str(i + 1) + number_size = measure_text_cached(font, number, 30) + rl.draw_text_ex(font, number, (int(circle_x - number_size.x // 2), int(circle_y - number_size.y // 2)), 30, 0, rl.WHITE) + + # Text + rl.draw_text_ex(font, "\n".join(wrapped), rl.Vector2(text_x, y), 47, 0.0, rl.BLACK) + y += text_height + 50 + + +if __name__ == "__main__": + gui_app.init_window("pairing device") + pairing = SunnylinkPairingDialog(sponsor_pairing=True) + try: + for _ in gui_app.render(): + result = pairing.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) + if result != -1: + break + finally: + del pairing From 57eca29970efd01ca17de7a6f7425a1e7ebac6dd Mon Sep 17 00:00:00 2001 From: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Fri, 5 Dec 2025 00:14:53 -0800 Subject: [PATCH 546/910] ui: Models panel (#1495) * param to control stock vs sp ui * init styles * SP Toggles * Lint * optimizations * sp raylib preview * fix callback * fix ui preview * dialog txt * compare vs what used to be done before InputDialog * introducing ui_state_sp for py * raylib: input dialog * raylib: SP Toggles * raylib: SP Panels * raylib: Option Control * init * param to control stock vs sp ui * better * tree dialog, progress bar widget cool stuff * merge origin raylib toggles * tree dialog * less trees for the planet * the heck * add ui_update callback * save the trees we got icons * Update process.py * yesssssssssss * utilize ON_COLOR constant form op system * Revert "add ui_update callback" This reverts commit 4da32cc0097434aab0aa6a3c35465eabb23c8958. * # Conflicts: # system/ui/sunnypilot/widgets/list_view.py # system/ui/sunnypilot/widgets/option_control.py * Merge remote-tracking branch 'openpilot/master' into nov-19-sync * ui: `GuiApplicationExt` * add to readme * scroller_tici :) * use gui_app.sunnypilot_ui() * # Conflicts: # selfdrive/ui/layouts/main.py # selfdrive/ui/sunnypilot/layouts/settings/cruise.py # selfdrive/ui/sunnypilot/layouts/settings/display.py # selfdrive/ui/sunnypilot/layouts/settings/models.py # selfdrive/ui/sunnypilot/layouts/settings/navigation.py # selfdrive/ui/sunnypilot/layouts/settings/osm.py # selfdrive/ui/sunnypilot/layouts/settings/steering.py # selfdrive/ui/sunnypilot/layouts/settings/sunnylink.py # selfdrive/ui/sunnypilot/layouts/settings/trips.py # selfdrive/ui/sunnypilot/layouts/settings/vehicle.py # selfdrive/ui/sunnypilot/layouts/settings/visuals.py # system/ui/sunnypilot/widgets/option_control.py * init value * Remove 'sunnypilot_ui' Removed 'sunnypilot_ui' parameter from params_keys.h * Update raylib_screenshots.py Removed the parameter setting for 'sunnypilot_ui' in the test. * easier to see * Update progress_bar.py * try something * adjust placement * more simple * smoothing updating components * ui: fuzzy search helper * ui_state_sp * description! * fuzzy af searching * better tree. fully dynamic and stuff * rm * rearrange * license * idk how maybe the merge * more indent * more indent * cleanup * temporaily revert ui_state_sp * only show if fav_param is used in the call * conditional for mypy * mypy * conditional for mypy * str concatenation to reduce line len * level * sunny's new x,y makes this even easier! * refreshing half a second seems legit. * loathing loathing, unadulterated loathing, i loathe it all * loathing loathing, unadulterated loathing, i loathe it all * # Conflicts: # system/ui/sunnypilot/lib/styles.py # system/ui/sunnypilot/widgets/tree_dialog.py * Update models.py * Change BUTTON_DISABLED_BG_COLOR to a lighter shade * I think this is it * Update tree_dialog.py * Update models.py * Update models.py * oops, angry f string * bool * Update ui_state.py * Update ui_state.py * wtf where'd the end quote go lol * some * more * quick test * Revert "quick test" This reverts commit fb97afa54c2b94129e772a23a789059adf01f609. * try this out * use sp's * cap * how weird? --------- Co-authored-by: nayan Co-authored-by: Jason Wen --- .../ui/sunnypilot/layouts/settings/models.py | 244 +++++++++++++++++- selfdrive/ui/sunnypilot/ui_state.py | 2 +- sunnypilot/models/manager.py | 4 + 3 files changed, 241 insertions(+), 9 deletions(-) diff --git a/selfdrive/ui/sunnypilot/layouts/settings/models.py b/selfdrive/ui/sunnypilot/layouts/settings/models.py index add437b12..43f0d25b4 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/models.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/models.py @@ -4,24 +4,252 @@ Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. This file is part of sunnypilot and is licensed under the MIT License. See the LICENSE.md file in the root directory for more details. """ -from openpilot.common.params import Params +import os +import re +import time +import pyray as rl + +from cereal import custom +from openpilot.common.constants import CV +from openpilot.selfdrive.ui.ui_state import device, ui_state +from openpilot.system.ui.lib.multilang import tr +from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.widgets import DialogResult, Widget +from openpilot.system.ui.widgets.confirm_dialog import alert_dialog, ConfirmDialog from openpilot.system.ui.widgets.scroller_tici import Scroller -from openpilot.system.ui.widgets import Widget +from openpilot.system.ui.widgets.toggle import ON_COLOR + +from openpilot.sunnypilot.models.runners.constants import CUSTOM_MODEL_PATH +from openpilot.system.ui.sunnypilot.lib.styles import style +from openpilot.system.ui.sunnypilot.widgets.list_view import ButtonActionSP, ListItemSP, toggle_item_sp, option_item_sp +from openpilot.system.ui.sunnypilot.widgets.progress_bar import progress_item +from openpilot.system.ui.sunnypilot.widgets.tree_dialog import TreeOptionDialog, TreeNode, TreeFolder + +if gui_app.sunnypilot_ui(): + from openpilot.system.ui.sunnypilot.widgets.list_view import button_item_sp as button_item + + +class ModelAction(ButtonActionSP): + def get_width_hint(self): + return super().get_width_hint() + 1 class ModelsLayout(Widget): def __init__(self): super().__init__() + self.model_manager = None + self.download_status = None + self.prev_download_status = None + self.model_dialog = None + self.last_cache_calc_time = 0 - self._params = Params() - items = self._initialize_items() - self._scroller = Scroller(items, line_separator=True, spacing=0) + self._initialize_items() + + self.clear_cache_item.action_item.set_value(f"{self._calculate_cache_size():.2f} MB") + for ctrl, key in [(self.lane_turn_value_control, "LaneTurnValue"), (self.delay_control, "LagdToggleDelay")]: + ctrl.action_item.set_value(int(float(ui_state.params.get(key, return_default=True)) * 100)) + + self._scroller = Scroller(self.items, line_separator=True, spacing=0) def _initialize_items(self): - items = [ + self.current_model_item = ListItemSP( + title=tr("Current Model"), + description="", + action_item=ModelAction(tr("SELECT")), + callback=self._handle_current_model_clicked + ) - ] - return items + self.supercombo_label = progress_item(tr("Driving Model")) + self.vision_label = progress_item(tr("Vision Model")) + self.policy_label = progress_item(tr("Policy Model")) + + self.refresh_item = button_item(tr("Refresh Model List"), tr("REFRESH"), "", + lambda: (ui_state.params.put("ModelManager_LastSyncTime", 0), + gui_app.set_modal_overlay(alert_dialog(tr("Fetching Latest Models"))))) + + self.clear_cache_item = ListItemSP( + title=tr("Clear Model Cache"), + description="", + action_item=ModelAction(tr("CLEAR")), + callback=self._clear_cache + ) + + self.cancel_download_item = button_item(tr("Cancel Download"), tr("Cancel"), "", lambda: ui_state.params.remove("ModelManager_DownloadIndex")) + + self.lane_turn_value_control = option_item_sp(tr("Adjust Lane Turn Speed"), "LaneTurnValue", 500, 2000, + tr("Set the maximum speed for lane turn desires. Default is 19 mph."), + int(round(100 / CV.MPH_TO_KPH)), None, True, "", style.BUTTON_WIDTH, None, True, + lambda v: f"{int(round(v / 100 * (CV.MPH_TO_KPH if ui_state.is_metric else 1)))}" + + f" {'km/h' if ui_state.is_metric else 'mph'}") + + self.lane_turn_desire_toggle = toggle_item_sp(tr("Use Lane Turn Desires"), + tr("If you're driving at 20 mph (32 km/h) or below and have your blinker on," + + " the car will plan a turn in that direction at the nearest drivable path. " + + "This prevents situations (like at red lights) where the car might plan the wrong turn direction."), + param="LaneTurnDesire") + + self.delay_control = option_item_sp(tr("Adjust Software Delay"), "LagdToggleDelay", 5, 50, + tr("Adjust the software delay when Live Learning Steer Delay is toggled off. The default software delay value is 0.2"), + 1, None, True, "", style.BUTTON_WIDTH, None, True, lambda v: f"{v / 100:.2f}s") + + self.lagd_toggle = toggle_item_sp(tr("Live Learning Steer Delay"), "", param="LagdToggle") + + self.items = [self.current_model_item, self.cancel_download_item, self.supercombo_label, self.vision_label, + self.policy_label, self.refresh_item, self.clear_cache_item, self.lane_turn_desire_toggle, + self.lane_turn_value_control, self.lagd_toggle, self.delay_control] + + def _update_lagd_description(self, lagd_toggle: bool): + desc = tr("Enable this for the car to learn and adapt its steering response time. Disable to use a fixed steering response time. " + + "Keeping this on provides the stock openpilot experience.") + if lagd_toggle: + desc += f"
{tr('Live Steer Delay:')} {ui_state.sm['liveDelay'].lateralDelay:.3f} s" + elif ui_state.CP: + sw = float(ui_state.params.get("LagdToggleDelay", "0.2")) + cp = ui_state.CP.steerActuatorDelay + desc += f"
{tr('Actuator Delay:')} {cp:.2f} s + {tr('Software Delay:')} {sw:.2f} s = {tr('Total Delay:')} {cp + sw:.2f} s" + self.lagd_toggle.set_description(desc) + + def _is_downloading(self): + return (self.model_manager and self.model_manager.selectedBundle and + self.model_manager.selectedBundle.status == custom.ModelManagerSP.DownloadStatus.downloading) + + @staticmethod + def _calculate_cache_size(): + cache_size = 0.0 + if os.path.exists(CUSTOM_MODEL_PATH): + cache_size = sum(os.path.getsize(os.path.join(CUSTOM_MODEL_PATH, file)) for file in os.listdir(CUSTOM_MODEL_PATH)) / (1024**2) + return cache_size + + def _clear_cache(self): + def _callback(response): + if response == DialogResult.CONFIRM: + ui_state.params.put_bool("ModelManager_ClearCache", True) + self.clear_cache_item.action_item.set_value(f"{self._calculate_cache_size():.2f} MB") + + gui_app.set_modal_overlay(ConfirmDialog(tr("This will delete ALL downloaded models from the cache except the currently active model. Are you sure?"), + tr("Clear Cache")), callback=_callback) + + def _handle_bundle_download_progress(self): + labels = {custom.ModelManagerSP.Model.Type.supercombo: self.supercombo_label, + custom.ModelManagerSP.Model.Type.vision: self.vision_label, + custom.ModelManagerSP.Model.Type.policy: self.policy_label} + for label in labels.values(): + label.set_visible(False) + self.cancel_download_item.set_visible(False) + + if not self.model_manager or (not self.model_manager.selectedBundle and not self.model_manager.activeBundle): + return + + bundle = self.model_manager.selectedBundle if self._is_downloading() or ( + self.model_manager.selectedBundle and self.model_manager.selectedBundle.status == custom.ModelManagerSP.DownloadStatus.failed + ) else self.model_manager.activeBundle + if not bundle: + return + + self.download_status = bundle.status + status_changed = self.prev_download_status != self.download_status + self.prev_download_status = self.download_status + + self.cancel_download_item.set_visible(bool(self.model_manager.selectedBundle) and bool(ui_state.params.get("ModelManager_DownloadIndex"))) + + if (current_time := time.monotonic()) - self.last_cache_calc_time > 0.5: + self.last_cache_calc_time = current_time + self.clear_cache_item.action_item.set_value(f"{self._calculate_cache_size():.2f} MB") + + if self.download_status == custom.ModelManagerSP.DownloadStatus.downloading: + device.reset_interactive_timeout() + + for model in bundle.models: + if label := labels.get(getattr(model.type, 'raw', model.type)): + label.set_visible(True) + p = model.artifact.downloadProgress + text, show, color = f"pending - {bundle.displayName}", False, rl.GRAY + if p.status == custom.ModelManagerSP.DownloadStatus.downloading: + text, show = f"{int(p.progress)}% - {bundle.displayName}", True + elif p.status in (custom.ModelManagerSP.DownloadStatus.downloaded, custom.ModelManagerSP.DownloadStatus.cached): + status_text = tr("from cache" if p.status == custom.ModelManagerSP.DownloadStatus.cached else "downloaded") + text, color = f"{bundle.displayName} - {status_text if status_changed else tr('ready')}", ON_COLOR + elif p.status == custom.ModelManagerSP.DownloadStatus.failed: + text, color = f"download failed - {bundle.displayName}", rl.RED + label.action_item.update(p.progress, text, show, color) + + @staticmethod + def _show_reset_params_dialog(): + def _callback(response): + if response == DialogResult.CONFIRM: + ui_state.params.remove("CalibrationParams") + ui_state.params.remove("LiveTorqueParameters") + msg = tr("Model download has started in the background. We suggest resetting calibration. Would you like to do that now?") + gui_app.set_modal_overlay(ConfirmDialog(msg, tr("Reset Calibration")), callback=_callback) + + def _on_model_selected(self, result): + if result != DialogResult.CONFIRM: + return + selected_ref = self.model_dialog.selection_ref + if selected_ref == "Default": + ui_state.params.remove("ModelManager_ActiveBundle") + self._show_reset_params_dialog() + elif selected_bundle := next((bundle for bundle in self.model_manager.availableBundles if bundle.ref == selected_ref), None): + ui_state.params.put("ModelManager_DownloadIndex", selected_bundle.index) + if self.model_manager.activeBundle and selected_bundle.generation != self.model_manager.activeBundle.generation: + self._show_reset_params_dialog() + self.model_dialog = None + + @staticmethod + def _bundle_to_node(bundle): + return TreeNode(bundle.ref, {'display_name': bundle.displayName, 'short_name': bundle.internalName}) + + def _get_folders(self, favorites): + bundles = self.model_manager.availableBundles + folders = {} + for bundle in bundles: + folders.setdefault(next((ov_ride.value for ov_ride in bundle.overrides if ov_ride.key == "folder"), ""), []).append(bundle) + + folders_list = [TreeFolder("", [TreeNode("Default", {'display_name': tr("Default Model"), 'short_name': "Default"})])] + for folder, folder_bundles in sorted(folders.items(), key=lambda x: max((bundle.index for bundle in x[1]), default=-1), reverse=True): + folder_bundles.sort(key=lambda bundle: bundle.index, reverse=True) + name = folder + (f" - (Updated: {m.group(1)})" if folder_bundles and (m := re.search(r'\(([^)]*)\)[^(]*$', folder_bundles[0].displayName)) else "") + folders_list.append(TreeFolder(name, [self._bundle_to_node(bundle) for bundle in folder_bundles])) + + if favorites and (fav_bundles := [bundle for bundle in bundles if bundle.ref in favorites]): + folders_list.insert(1, TreeFolder("Favorites", [self._bundle_to_node(bundle) for bundle in fav_bundles])) + return folders_list + + def _handle_current_model_clicked(self): + favs = ui_state.params.get("ModelManager_Favs") + favorites = set(favs.split(';')) if favs else set() + folders_list = self._get_folders(favorites) + + active_ref = self.model_manager.activeBundle.ref if self.model_manager.activeBundle else "Default" + self.model_dialog = TreeOptionDialog(tr("Select a Model"), folders_list, active_ref, "ModelManager_Favs", + get_folders_fn=self._get_folders, on_exit=self._on_model_selected) + gui_app.set_modal_overlay(self.model_dialog, callback=self._on_model_selected) + + def _update_state(self): + advanced_controls: bool = ui_state.params.get_bool("ShowAdvancedControls") + turn_desire: bool = ui_state.params.get_bool("LaneTurnDesire") + live_delay: bool = ui_state.params.get_bool("LagdToggle") + + self.lane_turn_desire_toggle.action_item.set_state(turn_desire) + self.lane_turn_value_control.set_visible(turn_desire and advanced_controls) + self.lagd_toggle.action_item.set_state(live_delay) + self.delay_control.set_visible(not live_delay and advanced_controls) + new_step = int(round(100 / CV.MPH_TO_KPH)) if ui_state.is_metric else 100 + if self.lane_turn_value_control.action_item.value_change_step != new_step: + self.lane_turn_value_control.action_item.value_change_step = new_step + + self._update_lagd_description(live_delay) + self.model_manager = ui_state.sm["modelManagerSP"] + self._handle_bundle_download_progress() + active_name = self.model_manager.activeBundle.internalName if self.model_manager and self.model_manager.activeBundle.ref else tr("Default Model") + self.current_model_item.action_item.set_value(active_name) + + if not ui_state.is_offroad(): + self.current_model_item.action_item.set_enabled(False) + self.current_model_item.set_description(tr("Only available when vehicle is off, or always offroad mode is on")) + else: + self.current_model_item.action_item.set_enabled(True) + self.current_model_item.set_description("") def _render(self, rect): self._scroller.render(rect) diff --git a/selfdrive/ui/sunnypilot/ui_state.py b/selfdrive/ui/sunnypilot/ui_state.py index 1db5ad613..af625ee61 100644 --- a/selfdrive/ui/sunnypilot/ui_state.py +++ b/selfdrive/ui/sunnypilot/ui_state.py @@ -14,7 +14,7 @@ class UIStateSP: self.params = Params() self.sm_services_ext = [ "modelManagerSP", "selfdriveStateSP", "longitudinalPlanSP", "backupManagerSP", - "gpsLocation", "liveTorqueParameters", "carStateSP", "liveMapDataSP", "carParamsSP" + "gpsLocation", "liveTorqueParameters", "carStateSP", "liveMapDataSP", "carParamsSP", "liveDelay" ] self.sunnylink_state = SunnylinkState() diff --git a/sunnypilot/models/manager.py b/sunnypilot/models/manager.py index c236643a0..2d3d670bf 100644 --- a/sunnypilot/models/manager.py +++ b/sunnypilot/models/manager.py @@ -63,6 +63,9 @@ class ModelManagerSP: f.write(chunk) bytes_downloaded += len(chunk) + if not self.params.get("ModelManager_DownloadIndex"): + raise Exception("Download cancelled") + if total_size > 0: progress = (bytes_downloaded / total_size) * 100 model.downloadProgress.status = custom.ModelManagerSP.DownloadStatus.downloading @@ -176,6 +179,7 @@ class ModelManagerSP: cloudlog.exception(e) finally: self.params.remove("ModelManager_DownloadIndex") + self.selected_bundle = None if self.params.get("ModelManager_ClearCache"): self.clear_model_cache() From d382cd08e5fd46e63b54400e6f74f284316ee494 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 5 Dec 2025 16:49:29 -0500 Subject: [PATCH 547/910] ui: adjust Sponsor and Pairing button behaviors in sunnylink panel (#1557) ui: adjust sponsor and pairing button behaviors in sunnyLink panel --- selfdrive/ui/sunnypilot/layouts/settings/sunnylink.py | 5 ++--- system/ui/sunnypilot/widgets/sunnylink_pairing_dialog.py | 4 +++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/selfdrive/ui/sunnypilot/layouts/settings/sunnylink.py b/selfdrive/ui/sunnypilot/layouts/settings/sunnylink.py index 7125fdf3f..316d4d224 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/sunnylink.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/sunnylink.py @@ -328,12 +328,11 @@ class SunnylinkLayout(Widget): tier_name = ui_state.sunnylink_state.get_sponsor_tier().name.capitalize() or tr("Not Sponsor") self._sponsor_btn.action_item.set_text(sponsor_btn_text) self._sponsor_btn.action_item.set_value(tier_name, ui_state.sunnylink_state.get_sponsor_tier_color()) - self._sponsor_btn.action_item.set_enabled(self._sunnylink_enabled and not ui_state.sunnylink_state.is_sponsor()) + self._sponsor_btn.action_item.set_enabled(self._sunnylink_enabled) pair_btn_text = tr("Paired") if ui_state.sunnylink_state.is_paired() else tr("Not Paired") self._pair_btn.action_item.set_text(pair_btn_text) - self._pair_btn.set_visible(lambda: self._sunnylink_enabled and not ui_state.sunnylink_state.is_paired()) - self._pair_btn.action_item.set_enabled(self._sunnylink_enabled and not ui_state.sunnylink_state.is_paired()) + self._pair_btn.action_item.set_enabled(self._sunnylink_enabled) def _render(self, rect): self._scroller.render(rect) diff --git a/system/ui/sunnypilot/widgets/sunnylink_pairing_dialog.py b/system/ui/sunnypilot/widgets/sunnylink_pairing_dialog.py index 4fab79caf..af6b9cf45 100644 --- a/system/ui/sunnypilot/widgets/sunnylink_pairing_dialog.py +++ b/system/ui/sunnypilot/widgets/sunnylink_pairing_dialog.py @@ -25,6 +25,7 @@ class SunnylinkPairingDialog(PairingDialog): def __init__(self, sponsor_pairing: bool = False): PairingDialog.__init__(self) self._sponsor_pairing = sponsor_pairing + self._is_paired_prev = ui_state.sunnylink_state.is_paired() def _get_pairing_url(self) -> str: qr_string = "https://github.com/sponsors/sunnyhaibin" @@ -42,7 +43,8 @@ class SunnylinkPairingDialog(PairingDialog): return qr_string def _update_state(self): - if ui_state.sunnylink_state.is_paired(): + is_paired = ui_state.sunnylink_state.is_paired() + if not self._is_paired_prev and is_paired: gui_app.set_modal_overlay(None) def _render(self, rect: rl.Rectangle) -> int: From 0965650f6109dc54f4a81c882b9c6511f3cc433a Mon Sep 17 00:00:00 2001 From: Robbe Derks Date: Fri, 5 Dec 2025 23:12:39 +0100 Subject: [PATCH 548/910] Bump panda (#36783) * panda bump * try this one * this breaks it? * still broken, right? * fixed? * second try --- panda | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panda b/panda index 615009cf0..1ffad74f8 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 615009cf0f8fb8f3feadac160fbb0a07e4de171b +Subproject commit 1ffad74f88e5683d9cd7c472e823928e28037e9e From d4d6134d3b825c21013f48e1c0af3867eae9652c Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 5 Dec 2025 18:18:58 -0800 Subject: [PATCH 549/910] UnifiedLabel: fix clipping descenders (#36793) * fix * can also do this * but then y is off. this is from font_scale I think * fix * cmt --- system/ui/widgets/label.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/system/ui/widgets/label.py b/system/ui/widgets/label.py index 91f05c355..97b293083 100644 --- a/system/ui/widgets/label.py +++ b/system/ui/widgets/label.py @@ -712,8 +712,9 @@ class UnifiedLabel(Widget): start_y = self._rect.y + (self._rect.height - total_visible_height) / 2 # Only scissor when we know there is a single scrolling line + # Pad a little since descenders like g or j may overflow below rect from font_scale if self._needs_scroll: - rl.begin_scissor_mode(int(self._rect.x), int(self._rect.y), int(self._rect.width), int(self._rect.height)) + rl.begin_scissor_mode(int(self._rect.x), int(self._rect.y - self._font_size / 2), int(self._rect.width), int(self._rect.height + self._font_size)) # Render each line current_y = start_y From 93c1c713a9ad4302520829c561698e6c2626254f Mon Sep 17 00:00:00 2001 From: Nayan Date: Fri, 5 Dec 2025 22:52:41 -0500 Subject: [PATCH 550/910] ui: toggle should only toggle toggle - don't toggle description with toggle. Yeah, that! (#1559) @sunnyhaibin, please! Co-authored-by: Jason Wen --- system/ui/sunnypilot/widgets/list_view.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/system/ui/sunnypilot/widgets/list_view.py b/system/ui/sunnypilot/widgets/list_view.py index bd68d35a4..ac646df5b 100644 --- a/system/ui/sunnypilot/widgets/list_view.py +++ b/system/ui/sunnypilot/widgets/list_view.py @@ -165,8 +165,10 @@ class ListItemSP(ListItem): content_width = item_rect.width - (style.ITEM_PADDING * 2) title_width = measure_text_cached(self._font, self.title, style.ITEM_TEXT_FONT_SIZE).x right_width = min(content_width - title_width, right_width) - - action_x = item_rect.x + item_rect.width - right_width + if isinstance(self.action_item, ToggleAction): + action_x = item_rect.x + else: + action_x = item_rect.x + item_rect.width - right_width action_y = item_rect.y return rl.Rectangle(action_x, action_y, right_width, style.ITEM_BASE_HEIGHT) From 5007437969ca25ea566d390a2853a022f7ac756e Mon Sep 17 00:00:00 2001 From: Nayan Date: Fri, 5 Dec 2025 22:58:53 -0500 Subject: [PATCH 551/910] ui: enforce fixed dimensions for `ToggleSP` (#1558) fix rect Co-authored-by: Jason Wen --- system/ui/sunnypilot/widgets/toggle.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/system/ui/sunnypilot/widgets/toggle.py b/system/ui/sunnypilot/widgets/toggle.py index 46b390f77..2924aec2b 100644 --- a/system/ui/sunnypilot/widgets/toggle.py +++ b/system/ui/sunnypilot/widgets/toggle.py @@ -24,6 +24,9 @@ class ToggleSP(Toggle): initial_state = self.params.get_bool(self.param_key) Toggle.__init__(self, initial_state, callback) + def set_rect(self, rect: rl.Rectangle): + self._rect = rl.Rectangle(rect.x, rect.y, style.TOGGLE_WIDTH, style.TOGGLE_HEIGHT) + def _handle_mouse_release(self, mouse_pos: MousePos): super()._handle_mouse_release(mouse_pos) if self._enabled and self.param_key: From 1807b193fafeca3c446560a7f9ddf95db66b1f74 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 7 Dec 2025 00:58:33 -0500 Subject: [PATCH 552/910] ui: preserve and update `current_ref` in `TreeOptionDialog` init (#1562) * ui: preserve and update `current_ref` in `TreeOptionDialog` init * do it in init instead --- system/ui/sunnypilot/widgets/tree_dialog.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/system/ui/sunnypilot/widgets/tree_dialog.py b/system/ui/sunnypilot/widgets/tree_dialog.py index cfc0a5caf..27a978153 100644 --- a/system/ui/sunnypilot/widgets/tree_dialog.py +++ b/system/ui/sunnypilot/widgets/tree_dialog.py @@ -78,7 +78,7 @@ class TreeItemWidget(Button): class TreeOptionDialog(MultiOptionDialog): def __init__(self, title, folders, current_ref="", fav_param="", option_font_weight=FontWeight.MEDIUM, search_prompt=None, get_folders_fn=None, on_exit=None, display_func=None, search_funcs=None, search_title=None, search_subtitle=None): - super().__init__(title, [], "", option_font_weight) + super().__init__(title, [], current_ref, option_font_weight) self.folders = folders self.selection_ref = current_ref self.fav_param = fav_param @@ -101,6 +101,19 @@ class TreeOptionDialog(MultiOptionDialog): self.search_dialog = None self._search_pressed = False + if current_ref: + found = False + for folder in folders: + for node in folder.nodes: + if node.ref == current_ref: + display = self.display_func(node) + self.selection = display + self.current = display + found = True + break + if found: + break + self._build_visible_items() def _on_search_confirm(self, result, text): @@ -156,6 +169,7 @@ class TreeOptionDialog(MultiOptionDialog): self.visible_items.append(TreeItemWidget(self.display_func(node), node.ref, False, 1 if folder.folder else 0, lambda node_ref=node: self._select_node(node_ref), favorite_cb, node.ref in self.favorites, is_expanded=expanded)) + self.option_buttons = self.visible_items self.options = [item.text for item in self.visible_items] self.scroller._items = self.visible_items From 96c2650ac4a43175214a6d23739c013b8bb92a89 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 7 Dec 2025 01:31:00 -0500 Subject: [PATCH 553/910] ui: improve `TreeOptionDialog` node selection and item handling (#1563) --- system/ui/sunnypilot/widgets/tree_dialog.py | 41 +++++++++++++++------ 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/system/ui/sunnypilot/widgets/tree_dialog.py b/system/ui/sunnypilot/widgets/tree_dialog.py index 27a978153..2dd5e3a2d 100644 --- a/system/ui/sunnypilot/widgets/tree_dialog.py +++ b/system/ui/sunnypilot/widgets/tree_dialog.py @@ -101,18 +101,22 @@ class TreeOptionDialog(MultiOptionDialog): self.search_dialog = None self._search_pressed = False - if current_ref: - found = False - for folder in folders: - for node in folder.nodes: - if node.ref == current_ref: - display = self.display_func(node) - self.selection = display - self.current = display - found = True - break - if found: + self.selection_node = None + # Try to match by ref, by display text, or fall back to "Default" when no ref is set + for folder in self.folders: + for node in folder.nodes: + display = self.display_func(node) + if ( + node.ref == current_ref or + display == current_ref or + (not current_ref and node.ref == "Default") + ): + self.selection = display + self.current = display + self.selection_node = node break + if self.selection_node is not None: + break self._build_visible_items() @@ -155,6 +159,17 @@ class TreeOptionDialog(MultiOptionDialog): def _build_visible_items(self, reset_scroll=True): self.visible_items = [] + + # Pinned selected item at the very top (if any) + if getattr(self, "selection_node", None) is not None: + node = self.selection_node + display = self.display_func(node) + self.selection = self.current = display + favorite_cb = (lambda node_ref=node: self._toggle_favorite(node_ref)) if self.fav_param and node.ref != "Default" else None + self.visible_items.append(TreeItemWidget(self.display_func(node), node.ref, False, 0, + lambda node_ref=node: self._select_node(node_ref), + favorite_cb, node.ref in self.favorites, is_expanded=True)) + for folder in self.folders: nodes = [node for node in folder.nodes if not self.query or search_from_list(self.query, [search_func(node) for search_func in self.search_funcs])] if not nodes and self.query: @@ -165,6 +180,10 @@ class TreeOptionDialog(MultiOptionDialog): lambda folder_ref=folder: self._toggle_folder(folder_ref))) if expanded: for node in nodes: + # Skip duplicate root-level item for the selected node + if self.selection_node is not None and node.ref == self.selection_node.ref and not folder.folder: + continue + favorite_cb = (lambda node_ref=node: self._toggle_favorite(node_ref)) if self.fav_param and node.ref != "Default" else None self.visible_items.append(TreeItemWidget(self.display_func(node), node.ref, False, 1 if folder.folder else 0, lambda node_ref=node: self._select_node(node_ref), From 323b793a832d605a8e64f51235b713d295ed0c58 Mon Sep 17 00:00:00 2001 From: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Sat, 6 Dec 2025 22:36:02 -0800 Subject: [PATCH 554/910] ui: software panel (#1518) * param to control stock vs sp ui * init styles * SP Toggles * Lint * optimizations * sp raylib preview * fix callback * fix ui preview * dialog txt * compare vs what used to be done before InputDialog * introducing ui_state_sp for py * raylib: input dialog * raylib: SP Toggles * raylib: SP Panels * raylib: Option Control * init * param to control stock vs sp ui * better * tree dialog, progress bar widget cool stuff * merge origin raylib toggles * tree dialog * less trees for the planet * the heck * add ui_update callback * save the trees we got icons * Update process.py * yesssssssssss * utilize ON_COLOR constant form op system * Revert "add ui_update callback" This reverts commit 4da32cc0097434aab0aa6a3c35465eabb23c8958. * # Conflicts: # system/ui/sunnypilot/widgets/list_view.py # system/ui/sunnypilot/widgets/option_control.py * Merge remote-tracking branch 'openpilot/master' into nov-19-sync * ui: `GuiApplicationExt` * add to readme * scroller_tici :) * use gui_app.sunnypilot_ui() * # Conflicts: # selfdrive/ui/layouts/main.py # selfdrive/ui/sunnypilot/layouts/settings/cruise.py # selfdrive/ui/sunnypilot/layouts/settings/display.py # selfdrive/ui/sunnypilot/layouts/settings/models.py # selfdrive/ui/sunnypilot/layouts/settings/navigation.py # selfdrive/ui/sunnypilot/layouts/settings/osm.py # selfdrive/ui/sunnypilot/layouts/settings/steering.py # selfdrive/ui/sunnypilot/layouts/settings/sunnylink.py # selfdrive/ui/sunnypilot/layouts/settings/trips.py # selfdrive/ui/sunnypilot/layouts/settings/vehicle.py # selfdrive/ui/sunnypilot/layouts/settings/visuals.py # system/ui/sunnypilot/widgets/option_control.py * init value * Remove 'sunnypilot_ui' Removed 'sunnypilot_ui' parameter from params_keys.h * Update raylib_screenshots.py Removed the parameter setting for 'sunnypilot_ui' in the test. * easier to see * Update progress_bar.py * try something * adjust placement * more simple * smoothing updating components * ui: fuzzy search helper * ui_state_sp * description! * fuzzy af searching * better tree. fully dynamic and stuff * rm * rearrange * license * idk how maybe the merge * more indent * more indent * cleanup * temporaily revert ui_state_sp * only show if fav_param is used in the call * conditional for mypy * mypy * conditional for mypy * str concatenation to reduce line len * level * sunny's new x,y makes this even easier! * refreshing half a second seems legit. * software stuffs * rm * add * loathing loathing, unadulterated loathing, i loathe it all * loathing loathing, unadulterated loathing, i loathe it all * # Conflicts: # system/ui/sunnypilot/lib/styles.py # system/ui/sunnypilot/widgets/tree_dialog.py * search * ds * hide on advanced controls * some * handle toggle confirmation * sunny, NO * nayan, NO !! * easier * move * move it! * add more * need to show current branch --------- Co-authored-by: nayan Co-authored-by: Jason Wen --- .../sunnypilot/layouts/settings/settings.py | 4 +- .../sunnypilot/layouts/settings/software.py | 96 +++++++++++++++++++ 2 files changed, 98 insertions(+), 2 deletions(-) create mode 100644 selfdrive/ui/sunnypilot/layouts/settings/software.py diff --git a/selfdrive/ui/sunnypilot/layouts/settings/settings.py b/selfdrive/ui/sunnypilot/layouts/settings/settings.py index 45c9b9348..4de2cacaf 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/settings.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/settings.py @@ -12,7 +12,7 @@ from openpilot.selfdrive.ui.layouts.settings import settings as OP from openpilot.selfdrive.ui.layouts.settings.developer import DeveloperLayout from openpilot.selfdrive.ui.sunnypilot.layouts.settings.device import DeviceLayoutSP from openpilot.selfdrive.ui.layouts.settings.firehose import FirehoseLayout -from openpilot.selfdrive.ui.layouts.settings.software import SoftwareLayout +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.software import SoftwareLayoutSP from openpilot.selfdrive.ui.layouts.settings.toggles import TogglesLayout from openpilot.system.ui.lib.application import gui_app, MousePos from openpilot.system.ui.lib.multilang import tr_noop @@ -114,7 +114,7 @@ class SettingsLayoutSP(OP.SettingsLayout): OP.PanelType.NETWORK: PanelInfo(tr_noop("Network"), NetworkUISP(wifi_manager), icon="icons/network.png"), OP.PanelType.SUNNYLINK: PanelInfo(tr_noop("sunnylink"), SunnylinkLayout(), icon="icons/wifi_strength_full.png"), OP.PanelType.TOGGLES: PanelInfo(tr_noop("Toggles"), TogglesLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_toggle.png"), - OP.PanelType.SOFTWARE: PanelInfo(tr_noop("Software"), SoftwareLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_software.png"), + OP.PanelType.SOFTWARE: PanelInfo(tr_noop("Software"), SoftwareLayoutSP(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_software.png"), OP.PanelType.MODELS: PanelInfo(tr_noop("Models"), ModelsLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_models.png"), OP.PanelType.STEERING: PanelInfo(tr_noop("Steering"), SteeringLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_lateral.png"), OP.PanelType.CRUISE: PanelInfo(tr_noop("Cruise"), CruiseLayout(), icon="icons/speed_limit.png"), diff --git a/selfdrive/ui/sunnypilot/layouts/settings/software.py b/selfdrive/ui/sunnypilot/layouts/settings/software.py new file mode 100644 index 000000000..b890dd5b5 --- /dev/null +++ b/selfdrive/ui/sunnypilot/layouts/settings/software.py @@ -0,0 +1,96 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +import os + +from openpilot.selfdrive.ui.layouts.settings.software import SoftwareLayout +from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.system.hardware import HARDWARE +from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.lib.multilang import tr, tr_noop +from openpilot.system.ui.widgets import DialogResult +from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog + +from openpilot.system.ui.sunnypilot.widgets.list_view import toggle_item_sp +from openpilot.system.ui.sunnypilot.widgets.tree_dialog import TreeOptionDialog, TreeNode, TreeFolder + + +DESCRIPTIONS = { + 'disable_updates_offroad': tr_noop( + "When enabled, automatic software updates will be off.
This requires a reboot to take effect." + ), + 'disable_updates_onroad': tr_noop( + "Please enable \"Always Offroad\" mode or turn off the vehicle to adjust these toggles." + ) +} + + +class SoftwareLayoutSP(SoftwareLayout): + def __init__(self): + super().__init__() + self.disable_updates_toggle = toggle_item_sp( + lambda: tr("Disable Updates"), + description="", + initial_state=ui_state.params.get_bool("DisableUpdates"), + callback=self._on_disable_updates_toggled, + ) + self._scroller.add_widget(self.disable_updates_toggle) + + def _handle_reboot(self, result): + if result == DialogResult.CONFIRM: + ui_state.params.put_bool("DisableUpdates", self.disable_updates_toggle.action_item.get_state()) + ui_state.params.put_bool("DoReboot", True) + else: + self.disable_updates_toggle.action_item.set_state(ui_state.params.get_bool("DisableUpdates")) + + def _on_disable_updates_toggled(self, enabled): + dialog = ConfirmDialog(tr("System reboot required for changes to take effect. Reboot now?"), tr("Reboot")) + gui_app.set_modal_overlay(dialog, callback=self._handle_reboot) + + def _on_select_branch(self): + current_git_branch = ui_state.params.get("GitBranch") or "" + branches_str = ui_state.params.get("UpdaterAvailableBranches") or "" + branches = [b for b in branches_str.split(",") if b] + current_target = ui_state.params.get("UpdaterTargetBranch") or "" + top_level_branches = [current_git_branch, "release-mici", "release-tizi", "staging", "dev", "master"] + + if HARDWARE.get_device_type() == "tici": + top_level_branches = ["release-tici", "staging-tici"] + branches = [b for b in branches if b.endswith("-tici")] + + top_level_nodes = [TreeNode(b, {'display_name': b}) for b in top_level_branches if b in branches] + remaining_branches = [b for b in branches if b not in top_level_branches] + prebuilt_nodes = [TreeNode(b, {'display_name': b}) for b in remaining_branches if b.endswith("-prebuilt")] + non_prebuilt_nodes = [TreeNode(b, {'display_name': b}) for b in remaining_branches if not b.endswith("-prebuilt")] + + folders = [ + TreeFolder("", top_level_nodes), + TreeFolder("Prebuilt Branches", prebuilt_nodes), + TreeFolder("Non-Prebuilt Branches", non_prebuilt_nodes), + ] + + def _on_branch_selected(result): + if result == DialogResult.CONFIRM and self._branch_dialog is not None: + selection = self._branch_dialog.selection_ref + if selection: + ui_state.params.put("UpdaterTargetBranch", selection) + self._branch_btn.action_item.set_value(selection) + os.system("pkill -SIGUSR1 -f system.updated.updated") + self._branch_dialog = None + + self._branch_dialog = TreeOptionDialog(tr("Select a branch"), folders, current_target, "", + on_exit=_on_branch_selected) + + gui_app.set_modal_overlay(self._branch_dialog, callback=_on_branch_selected) + + def _update_state(self): + super()._update_state() + show_advanced = ui_state.params.get_bool("ShowAdvancedControls") + self.disable_updates_toggle.action_item.set_enabled(ui_state.is_offroad()) + self.disable_updates_toggle.set_visible(show_advanced) + + disable_updates_desc = tr(DESCRIPTIONS["disable_updates_offroad"] if ui_state.is_offroad() else DESCRIPTIONS["disable_updates_onroad"]) + self.disable_updates_toggle.set_description(disable_updates_desc) From 239d690a4336c1dd6f8e9f202bc035ecbf236f1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=80=8C=20crwusiz=20=E3=80=8D?= <43285072+crwusiz@users.noreply.github.com> Date: Tue, 9 Dec 2025 09:32:56 +0900 Subject: [PATCH 555/910] Multilang: update kor translation (#36795) --- selfdrive/ui/translations/app_ko.po | 56 ++++++++++++++--------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/selfdrive/ui/translations/app_ko.po b/selfdrive/ui/translations/app_ko.po index 5a3e891b8..f12aebaeb 100644 --- a/selfdrive/ui/translations/app_ko.po +++ b/selfdrive/ui/translations/app_ko.po @@ -68,10 +68,10 @@ msgid "" "control alpha. Changing this setting will restart openpilot if the car is " "powered on." msgstr "" -"경고: 이 차량에서 openpilot의 종방향 제어는 알파 버전이며 자동 긴급 제동" -"(AEB)을 비활성화합니다.

이 차량에서는 openpilot 종방향 제어 대신 " -"차량 내장 ACC가 기본으로 사용됩니다. openpilot 종방향 제어로 전환하려면 이 설" -"정을 켜세요. 종방향 제어 알파를 켤 때는 실험 모드 사용을 권장합니다. 차량 전" +"경고: 이 차량에서 openpilot의 롱컨 제어는 알파 버전이며 자동 긴급 제동" +"(AEB)을 비활성화합니다.

이 차량에서는 openpilot 롱컨 제어 대신 " +"차량 내장 ACC가 기본으로 사용됩니다. openpilot 롱컨 제어로 전환하려면 이 설" +"정을 켜세요. 롱컨 제어 알파를 켤 때는 실험 모드 사용을 권장합니다. 차량 전" "원이 켜져 있는 경우 이 설정을 변경하면 openpilot이 재시작됩니다." #: selfdrive/ui/layouts/settings/device.py:148 @@ -130,7 +130,7 @@ msgstr "동의" #: selfdrive/ui/layouts/settings/toggles.py:70 #, python-format msgid "Always-On Driver Monitoring" -msgstr "항상 켜짐 운전자 모니터링" +msgstr "운전자 모니터링 항상 켜짐" #: selfdrive/ui/layouts/settings/toggles.py:186 #, python-format @@ -138,7 +138,7 @@ msgid "" "An alpha version of openpilot longitudinal control can be tested, along with " "Experimental mode, on non-release branches." msgstr "" -"openpilot 종방향 제어 알파 버전은 실험 모드와 함께 비릴리스 브랜치에서 테스트" +"openpilot 롱컨 제어 알파 버전은 실험 모드와 함께 비릴리스 브랜치에서 테스트" "할 수 있습니다." #: selfdrive/ui/layouts/settings/device.py:187 @@ -192,7 +192,7 @@ msgstr "확인" #: selfdrive/ui/widgets/exp_mode_button.py:50 #, python-format msgid "CHILL MODE ON" -msgstr "칠 모드 켜짐" +msgstr "안정적 모드 켜짐" #: system/ui/widgets/network.py:155 selfdrive/ui/layouts/sidebar.py:73 #: selfdrive/ui/layouts/sidebar.py:134 selfdrive/ui/layouts/sidebar.py:136 @@ -283,7 +283,7 @@ msgstr "해제 후 재시작" #: selfdrive/ui/layouts/settings/device.py:103 #, python-format msgid "Disengage to Reset Calibration" -msgstr "해제 후 보정 재설정" +msgstr "해제 후 캘리브레이션 재설정" #: selfdrive/ui/layouts/settings/toggles.py:32 msgid "Display speed in km/h instead of mph." @@ -372,7 +372,7 @@ msgstr "openpilot 사용" msgid "" "Enable the openpilot longitudinal control (alpha) toggle to allow " "Experimental mode." -msgstr "실험 모드를 사용하려면 openpilot 종방향 제어(알파) 토글을 켜세요." +msgstr "실험 모드를 사용하려면 openpilot 롱컨 제어(알파) 토글을 켜세요." #: system/ui/widgets/network.py:204 #, python-format @@ -415,7 +415,7 @@ msgid "" "Experimental mode is currently unavailable on this car since the car's stock " "ACC is used for longitudinal control." msgstr "" -"이 차량은 종방향 제어에 순정 ACC를 사용하므로 현재 실험 모드를 사용할 수 없습" +"이 차량은 롱컨 제어에 순정 ACC를 사용하므로 현재 실험 모드를 사용할 수 없습" "니다." #: system/ui/widgets/network.py:373 @@ -430,11 +430,11 @@ msgstr "설정 완료" #: selfdrive/ui/layouts/settings/settings.py:66 msgid "Firehose" -msgstr "Firehose" +msgstr "파이어호스" #: selfdrive/ui/layouts/settings/firehose.py:18 msgid "Firehose Mode" -msgstr "Firehose 모드" +msgstr "파이어호스 모드" #: selfdrive/ui/layouts/settings/firehose.py:25 msgid "" @@ -462,7 +462,7 @@ msgstr "" "최대의 효과를 위해 주 1회는 장치를 실내로 가져와 품질 좋은 USB‑C 어댑터와 " "Wi‑Fi에 연결하세요.\n" "\n" -"핫스팟이나 무제한 SIM에 연결되어 있다면 주행 중에도 Firehose 모드가 동작합니" +"핫스팟이나 무제한 SIM에 연결되어 있다면 주행 중에도 파이어호스 모드가 동작합니" "다.\n" "\n" "\n" @@ -470,7 +470,7 @@ msgstr "" "\n" "어떻게, 어디서 운전하는지가 중요한가요? 아니요. 평소처럼 운전하세요.\n" "\n" -"Firehose 모드에서 모든 세그먼트가 가져가지나요? 아니요. 일부 세그먼트만 선택" +"파이어호스 모드에서 모든 구간을 가져가지나요? 아니요. 일부 구간만 선택" "적으로 가져갑니다.\n" "\n" "좋은 USB‑C 어댑터는 무엇인가요? 빠른 휴대폰 또는 노트북 충전기면 충분합니" @@ -544,7 +544,7 @@ msgstr "LTE" #: selfdrive/ui/layouts/settings/developer.py:64 #, python-format msgid "Longitudinal Maneuver Mode" -msgstr "종방향 매뉴버 모드" +msgstr "롱컨 기동 모드" #: selfdrive/ui/onroad/hud_renderer.py:148 #, python-format @@ -623,7 +623,7 @@ msgstr "미리보기" #: selfdrive/ui/widgets/prime.py:44 #, python-format msgid "PRIME FEATURES:" -msgstr "prime 기능:" +msgstr "프라임 기능:" #: selfdrive/ui/layouts/settings/device.py:48 #, python-format @@ -646,7 +646,7 @@ msgid "" "Pair your device with comma connect (connect.comma.ai) and claim your comma " "prime offer." msgstr "" -"장치를 comma connect(connect.comma.ai)와 페어링하고 comma prime 혜택을 받으세" +"장치를 comma connect(connect.comma.ai)와 페어링하고 comma 프라임 혜택을 받으세" "요." #: selfdrive/ui/widgets/setup.py:91 @@ -748,7 +748,7 @@ msgstr "규제 정보" #: selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Relaxed" -msgstr "편안함" +msgstr "편안한" #: selfdrive/ui/widgets/prime.py:47 #, python-format @@ -773,7 +773,7 @@ msgstr "재설정" #: selfdrive/ui/layouts/settings/device.py:51 #, python-format msgid "Reset Calibration" -msgstr "보정 재설정" +msgstr "캘리브레이션 재설정" #: selfdrive/ui/layouts/settings/device.py:65 #, python-format @@ -841,7 +841,7 @@ msgid "" "cycle through these personalities with your steering wheel distance button." msgstr "" "표준을 권장합니다. 공격적 모드에서는 앞차를 더 가깝게 따라가고 가감속이 더 적" -"극적입니다. 편안함 모드에서는 앞차와 거리를 더 둡니다. 지원 차량에서는 스티어" +"극적입니다. 편안한 모드에서는 앞차와 거리를 더 둡니다. 지원 차량에서는 스티어" "링의 차간 버튼으로 이 성향들을 전환할 수 있습니다." #: selfdrive/ui/onroad/alert_renderer.py:59 @@ -892,7 +892,7 @@ msgstr "제거" #: selfdrive/ui/layouts/sidebar.py:117 msgid "Unknown" -msgstr "알 수 없음" +msgstr "알수없음" #: selfdrive/ui/layouts/settings/software.py:48 #, python-format @@ -994,7 +994,7 @@ msgstr "카메라 시작 중" #: selfdrive/ui/widgets/prime.py:63 #, python-format msgid "comma prime" -msgstr "comma prime" +msgstr "comma 프라임" #: system/ui/widgets/network.py:142 #, python-format @@ -1054,7 +1054,7 @@ msgstr "지금" #: selfdrive/ui/layouts/settings/developer.py:71 #, python-format msgid "openpilot Longitudinal Control (Alpha)" -msgstr "openpilot 종방향 제어(알파)" +msgstr "openpilot 롱컨 제어(알파)" #: selfdrive/ui/onroad/alert_renderer.py:51 #, python-format @@ -1076,9 +1076,9 @@ msgid "" "some turns. The Experimental mode logo will also be shown in the top right " "corner." msgstr "" -"openpilot은 기본적으로 칠 모드로 주행합니다. 실험 모드를 사용하면 칠 모드에 " +"openpilot은 기본적으로 안정적 모드로 주행합니다. 실험 모드를 사용하면 안정적 모드에 " "아직 준비되지 않은 알파 수준의 기능이 활성화됩니다. 실험 기능은 아래와 같습니" -"다:

엔드투엔드 종방향 제어


주행 모델이 가속과 제동을 제어합니" +"다:

엔드투엔드 롱컨 제어


주행 모델이 가속과 제동을 제어합니" "다. openpilot은 빨간 신호 및 정지 표지에서의 정지를 포함해 사람이 운전한다고 " "판단하는 방식으로 주행합니다. 주행 속도는 모델이 결정하므로 설정 속도는 상한" "으로만 동작합니다. 알파 품질 기능이므로 오작동이 발생할 수 있습니다.

" @@ -1111,7 +1111,7 @@ msgstr "" #: selfdrive/ui/layouts/settings/toggles.py:183 #, python-format msgid "openpilot longitudinal control may come in a future update." -msgstr "openpilot 종방향 제어는 향후 업데이트에서 제공될 수 있습니다." +msgstr "openpilot 롱컨 제어는 향후 업데이트에서 제공될 수 있습니다." #: selfdrive/ui/layouts/settings/device.py:26 msgid "" @@ -1177,7 +1177,7 @@ msgstr[0] "{}분 전" #, python-format msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." -msgstr[0] "현재까지 귀하의 주행 {}세그먼트가 학습 데이터셋에 포함되었습니다." +msgstr[0] "현재까지 귀하의 주행 {}구간이 학습 데이터셋에 포함되었습니다." #: selfdrive/ui/widgets/prime.py:62 #, python-format @@ -1187,4 +1187,4 @@ msgstr "✓ 구독됨" #: selfdrive/ui/widgets/setup.py:22 #, python-format msgid "🔥 Firehose Mode 🔥" -msgstr "🔥 Firehose 모드 🔥" +msgstr "🔥 파이어호스 모드 🔥" From a6645a1be189ee189517982939b1f964b8d4aa38 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Tue, 9 Dec 2025 08:36:35 +0800 Subject: [PATCH 556/910] cabana: add automatic session save/restore (#36736) adds auto session save/store --- tools/cabana/binaryview.cc | 5 +--- tools/cabana/chart/chartswidget.cc | 26 +++++++++++++++++++ tools/cabana/chart/chartswidget.h | 2 ++ tools/cabana/dbc/dbc.h | 6 +++++ tools/cabana/detailwidget.cc | 40 +++++++++++++++++++++++++----- tools/cabana/detailwidget.h | 7 +++++- tools/cabana/mainwin.cc | 36 +++++++++++++++++++++++++++ tools/cabana/mainwin.h | 2 ++ tools/cabana/settings.cc | 4 +++ tools/cabana/settings.h | 6 +++++ 10 files changed, 123 insertions(+), 11 deletions(-) diff --git a/tools/cabana/binaryview.cc b/tools/cabana/binaryview.cc index eb0af5b64..b5a68c6b2 100644 --- a/tools/cabana/binaryview.cc +++ b/tools/cabana/binaryview.cc @@ -275,16 +275,13 @@ void BinaryViewModel::refresh() { row_count = can->lastMessage(msg_id).dat.size(); items.resize(row_count * column_count); } - int valid_rows = std::min(can->lastMessage(msg_id).dat.size(), row_count); - for (int i = 0; i < valid_rows * column_count; ++i) { - items[i].valid = true; - } endResetModel(); updateState(); } void BinaryViewModel::updateItem(int row, int col, uint8_t val, const QColor &color) { auto &item = items[row * column_count + col]; + item.valid = true; if (item.val != val || item.bg_color != color) { item.val = val; item.bg_color = color; diff --git a/tools/cabana/chart/chartswidget.cc b/tools/cabana/chart/chartswidget.cc index 3e9e452b9..aba25dcf8 100644 --- a/tools/cabana/chart/chartswidget.cc +++ b/tools/cabana/chart/chartswidget.cc @@ -322,6 +322,32 @@ void ChartsWidget::splitChart(ChartView *src_chart) { } } +QStringList ChartsWidget::serializeChartIds() const { + QStringList chart_ids; + for (auto c : charts) { + QStringList ids; + for (const auto& s : c->sigs) + ids += QString("%1|%2").arg(s.msg_id.toString(), s.sig->name); + chart_ids += ids.join(','); + } + std::reverse(chart_ids.begin(), chart_ids.end()); + return chart_ids; +} + +void ChartsWidget::restoreChartsFromIds(const QStringList& chart_ids) { + for (const auto& chart_id : chart_ids) { + int index = 0; + for (const auto& part : chart_id.split(',')) { + const auto sig_parts = part.split('|'); + if (sig_parts.size() != 2) continue; + MessageId msg_id = MessageId::fromString(sig_parts[0]); + if (auto* msg = dbc()->msg(msg_id)) + if (auto* sig = msg->sig(sig_parts[1])) + showChart(msg_id, sig, true, index++ > 0); + } + } +} + void ChartsWidget::setColumnCount(int n) { n = std::clamp(n, 1, MAX_COLUMN_COUNT); if (column_count != n) { diff --git a/tools/cabana/chart/chartswidget.h b/tools/cabana/chart/chartswidget.h index 46e7f546b..f87b1276c 100644 --- a/tools/cabana/chart/chartswidget.h +++ b/tools/cabana/chart/chartswidget.h @@ -43,6 +43,8 @@ public: ChartsWidget(QWidget *parent = nullptr); void showChart(const MessageId &id, const cabana::Signal *sig, bool show, bool merge); inline bool hasSignal(const MessageId &id, const cabana::Signal *sig) { return findChart(id, sig) != nullptr; } + QStringList serializeChartIds() const; + void restoreChartsFromIds(const QStringList &chart_ids); public slots: void setColumnCount(int n); diff --git a/tools/cabana/dbc/dbc.h b/tools/cabana/dbc/dbc.h index d2b25bc5f..134d88a91 100644 --- a/tools/cabana/dbc/dbc.h +++ b/tools/cabana/dbc/dbc.h @@ -20,6 +20,12 @@ struct MessageId { return QString("%1:%2").arg(source).arg(QString::number(address, 16).toUpper()); } + inline static MessageId fromString(const QString &str) { + auto parts = str.split(':'); + if (parts.size() != 2) return {}; + return MessageId{.source = uint8_t(parts[0].toUInt()), .address = parts[1].toUInt(nullptr, 16)}; + } + bool operator==(const MessageId &other) const { return source == other.source && address == other.address; } diff --git a/tools/cabana/detailwidget.cc b/tools/cabana/detailwidget.cc index 4eda46f37..35492c8ef 100644 --- a/tools/cabana/detailwidget.cc +++ b/tools/cabana/detailwidget.cc @@ -118,10 +118,7 @@ void DetailWidget::showTabBarContextMenu(const QPoint &pt) { } } -void DetailWidget::setMessage(const MessageId &message_id) { - if (std::exchange(msg_id, message_id) == message_id) return; - - tabbar->blockSignals(true); +int DetailWidget::findOrAddTab(const MessageId& message_id) { int index = tabbar->count() - 1; for (/**/; index >= 0; --index) { if (tabbar->tabData(index).value() == message_id) break; @@ -131,6 +128,14 @@ void DetailWidget::setMessage(const MessageId &message_id) { tabbar->setTabData(index, QVariant::fromValue(message_id)); tabbar->setTabToolTip(index, msgName(message_id)); } + return index; +} + +void DetailWidget::setMessage(const MessageId &message_id) { + if (std::exchange(msg_id, message_id) == message_id) return; + + tabbar->blockSignals(true); + int index = findOrAddTab(message_id); tabbar->setCurrentIndex(index); tabbar->blockSignals(false); @@ -142,6 +147,29 @@ void DetailWidget::setMessage(const MessageId &message_id) { setUpdatesEnabled(true); } +std::pair DetailWidget::serializeMessageIds() const { + QStringList msgs; + for (int i = 0; i < tabbar->count(); ++i) { + MessageId id = tabbar->tabData(i).value(); + msgs.append(id.toString()); + } + return std::make_pair(msg_id.toString(), msgs); +} + +void DetailWidget::restoreTabs(const QString active_msg_id, const QStringList& msg_ids) { + tabbar->blockSignals(true); + for (const auto& str_id : msg_ids) { + MessageId id = MessageId::fromString(str_id); + if (dbc()->msg(id) != nullptr) + findOrAddTab(id); + } + tabbar->blockSignals(false); + + auto active_id = MessageId::fromString(active_msg_id); + if (dbc()->msg(active_id) != nullptr) + setMessage(active_id); +} + void DetailWidget::refresh() { QStringList warnings; auto msg = dbc()->msg(msg_id); @@ -244,13 +272,13 @@ CenterWidget::CenterWidget(QWidget *parent) : QWidget(parent) { main_layout->addWidget(welcome_widget = createWelcomeWidget()); } -void CenterWidget::setMessage(const MessageId &msg_id) { +DetailWidget* CenterWidget::ensureDetailWidget() { if (!detail_widget) { delete welcome_widget; welcome_widget = nullptr; layout()->addWidget(detail_widget = new DetailWidget(((MainWindow*)parentWidget())->charts_widget, this)); } - detail_widget->setMessage(msg_id); + return detail_widget; } void CenterWidget::clear() { diff --git a/tools/cabana/detailwidget.h b/tools/cabana/detailwidget.h index 6df164b44..0fe1535c7 100644 --- a/tools/cabana/detailwidget.h +++ b/tools/cabana/detailwidget.h @@ -34,9 +34,12 @@ public: DetailWidget(ChartsWidget *charts, QWidget *parent); void setMessage(const MessageId &message_id); void refresh(); + std::pair serializeMessageIds() const; + void restoreTabs(const QString active_msg_id, const QStringList &msg_ids); private: void createToolBar(); + int findOrAddTab(const MessageId& message_id); void showTabBarContextMenu(const QPoint &pt); void editMsg(); void removeMsg(); @@ -60,7 +63,9 @@ class CenterWidget : public QWidget { Q_OBJECT public: CenterWidget(QWidget *parent); - void setMessage(const MessageId &msg_id); + void setMessage(const MessageId &message_id) { ensureDetailWidget()->setMessage(message_id); } + DetailWidget* getDetailWidget() { return detail_widget; } + DetailWidget* ensureDetailWidget(); void clear(); private: diff --git a/tools/cabana/mainwin.cc b/tools/cabana/mainwin.cc index d65fc5b76..2d070acff 100644 --- a/tools/cabana/mainwin.cc +++ b/tools/cabana/mainwin.cc @@ -235,6 +235,8 @@ void MainWindow::DBCFileChanged() { title.push_back(tr("(%1) %2").arg(toString(dbc()->sources(f)), f->name())); } setWindowFilePath(title.join(" | ")); + + QTimer::singleShot(0, this, &::MainWindow::restoreSessionState); } void MainWindow::selectAndOpenStream() { @@ -563,6 +565,7 @@ void MainWindow::closeEvent(QCloseEvent *event) { settings.message_header_state = messages_widget->saveHeaderState(); } + saveSessionState(); QWidget::closeEvent(event); } @@ -607,6 +610,39 @@ void MainWindow::toggleFullScreen() { } } +void MainWindow::saveSessionState() { + settings.recent_dbc_file = ""; + settings.active_msg_id = ""; + settings.selected_msg_ids.clear(); + settings.active_charts.clear(); + + for (auto &f : dbc()->allDBCFiles()) + if (!f->isEmpty()) { settings.recent_dbc_file = f->filename; break; } + + if (auto *detail = center_widget->getDetailWidget()) { + auto [active_id, ids] = detail->serializeMessageIds(); + settings.active_msg_id = active_id; + settings.selected_msg_ids = ids; + } + if (charts_widget) + settings.active_charts = charts_widget->serializeChartIds(); +} + +void MainWindow::restoreSessionState() { + if (settings.recent_dbc_file.isEmpty() || dbc()->nonEmptyDBCCount() == 0) return; + + QString dbc_file; + for (auto& f : dbc()->allDBCFiles()) + if (!f->isEmpty()) { dbc_file = f->filename; break; } + if (dbc_file != settings.recent_dbc_file) return; + + if (!settings.selected_msg_ids.isEmpty()) + center_widget->ensureDetailWidget()->restoreTabs(settings.active_msg_id, settings.selected_msg_ids); + + if (charts_widget != nullptr && !settings.active_charts.empty()) + charts_widget->restoreChartsFromIds(settings.active_charts); +} + // HelpOverlay HelpOverlay::HelpOverlay(MainWindow *parent) : QWidget(parent) { setAttribute(Qt::WA_NoSystemBackground, true); diff --git a/tools/cabana/mainwin.h b/tools/cabana/mainwin.h index 9bc94c090..1da59f93e 100644 --- a/tools/cabana/mainwin.h +++ b/tools/cabana/mainwin.h @@ -72,6 +72,8 @@ protected: void updateLoadSaveMenus(); void createDockWidgets(); void eventsMerged(); + void saveSessionState(); + void restoreSessionState(); VideoWidget *video_widget = nullptr; QDockWidget *video_dock; diff --git a/tools/cabana/settings.cc b/tools/cabana/settings.cc index cccc9b6d9..e7b1129a3 100644 --- a/tools/cabana/settings.cc +++ b/tools/cabana/settings.cc @@ -41,6 +41,10 @@ void settings_op(SettingOperation op) { op(s, "log_path", settings.log_path); op(s, "drag_direction", (int &)settings.drag_direction); op(s, "suppress_defined_signals", settings.suppress_defined_signals); + op(s, "recent_dbc_file", settings.recent_dbc_file); + op(s, "active_msg_id", settings.active_msg_id); + op(s, "selected_msg_ids", settings.selected_msg_ids); + op(s, "active_charts", settings.active_charts); } Settings::Settings() { diff --git a/tools/cabana/settings.h b/tools/cabana/settings.h index e75c519ac..7ab50d149 100644 --- a/tools/cabana/settings.h +++ b/tools/cabana/settings.h @@ -46,6 +46,12 @@ public: QByteArray message_header_state; DragDirection drag_direction = MsbFirst; + // session data + QString recent_dbc_file; + QString active_msg_id; + QStringList selected_msg_ids; + QStringList active_charts; + signals: void changed(); }; From 4e74e0f755bf5e31791e699ebf89adfaa911a6ca Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Tue, 9 Dec 2025 08:36:55 +0800 Subject: [PATCH 557/910] cabana: fix UI hang when switching streams (#36735) fix UI hang when switching streams --- tools/cabana/mainwin.cc | 10 +++++++++- tools/cabana/mainwin.h | 1 + tools/replay/replay.cc | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/tools/cabana/mainwin.cc b/tools/cabana/mainwin.cc index 2d070acff..1ea3733ed 100644 --- a/tools/cabana/mainwin.cc +++ b/tools/cabana/mainwin.cc @@ -313,11 +313,19 @@ void MainWindow::loadFromClipboard(SourceSet s, bool close_all) { } void MainWindow::openStream(AbstractStream *stream, const QString &dbc_file) { + if (can) { + QObject::connect(can, &QObject::destroyed, this, [=]() { startStream(stream, dbc_file); }); + can->deleteLater(); + } else { + startStream(stream, dbc_file); + } +} + +void MainWindow::startStream(AbstractStream *stream, QString dbc_file) { center_widget->clear(); delete messages_widget; delete video_splitter; - delete can; can = stream; can->setParent(this); // take ownership can->start(); diff --git a/tools/cabana/mainwin.h b/tools/cabana/mainwin.h index 1da59f93e..92c2714ae 100644 --- a/tools/cabana/mainwin.h +++ b/tools/cabana/mainwin.h @@ -44,6 +44,7 @@ signals: void updateProgressBar(uint64_t cur, uint64_t total, bool success); protected: + void startStream(AbstractStream *stream, QString dbc_file); bool eventFilter(QObject *obj, QEvent *event) override; void remindSaveChanges(); void closeFile(SourceSet s = SOURCE_ALL); diff --git a/tools/replay/replay.cc b/tools/replay/replay.cc index fb13ead03..cc105dd10 100644 --- a/tools/replay/replay.cc +++ b/tools/replay/replay.cc @@ -63,7 +63,6 @@ void Replay::setupSegmentManager(bool has_filters) { } Replay::~Replay() { - seg_mgr_.reset(); if (stream_thread_.joinable()) { rInfo("shutdown: in progress..."); interruptStream([this]() { @@ -74,6 +73,7 @@ Replay::~Replay() { rInfo("shutdown: done"); } camera_server_.reset(); + seg_mgr_.reset(); } bool Replay::load() { From cce2e4d357e16ec401b39fe778e563d0394d052a Mon Sep 17 00:00:00 2001 From: Matt Purnell <65473602+mpurnell1@users.noreply.github.com> Date: Mon, 8 Dec 2025 18:38:35 -0600 Subject: [PATCH 558/910] tools: Handle smaller terminal sizes in replay (#36766) * Only show help if there's room for it * show less * wording --- tools/replay/consoleui.cc | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tools/replay/consoleui.cc b/tools/replay/consoleui.cc index a4f3677ff..4d43df2da 100644 --- a/tools/replay/consoleui.cc +++ b/tools/replay/consoleui.cc @@ -115,7 +115,12 @@ void ConsoleUI::initWindows() { w[Win::Log] = newwin(log_height - 2, max_width - 2 * BORDER_SIZE, 18, BORDER_SIZE); scrollok(w[Win::Log], true); } - w[Win::Help] = newwin(5, max_width - (2 * BORDER_SIZE), max_height - 6, BORDER_SIZE); + if (max_height >= 23) { + w[Win::Help] = newwin(5, max_width - (2 * BORDER_SIZE), max_height - 6, BORDER_SIZE); + } else if (max_height >= 17) { + w[Win::Help] = newwin(1, max_width - (2 * BORDER_SIZE), max_height - 1, BORDER_SIZE); + mvwprintw(w[Win::Help], 0, 0, "Expand screen vertically to list available commands"); + } // set the title bar wbkgd(w[Win::Title], A_REVERSE); @@ -124,7 +129,7 @@ void ConsoleUI::initWindows() { // show windows on the real screen refresh(); displayTimelineDesc(); - displayHelp(); + if (max_height >= 23) displayHelp(); updateSummary(); updateTimeline(); for (auto win : w) { From fadf7ff1e5f7187be532748b86a128709b3eab93 Mon Sep 17 00:00:00 2001 From: Chechulin Serhii <78239416+keefeere@users.noreply.github.com> Date: Tue, 9 Dec 2025 02:40:29 +0200 Subject: [PATCH 559/910] ui: feature Ukrainian translation (#36646) * Add Ukrainian lang * update_translations.py * Add Ukrainian strings * Small patch to display translated update states * Revert "Small patch to display translated update states" This reverts commit b0545f4e109f451a21e4e5884259dbb881d7a58e. * Revert "update_translations.py" This reverts commit 79eea20c33f1b1d542b62a782ab1b67bc9277026. * fix so these meaningless edits --- selfdrive/ui/translations/app_uk.po | 1258 ++++++++++++++++++++++ selfdrive/ui/translations/languages.json | 1 + 2 files changed, 1259 insertions(+) create mode 100644 selfdrive/ui/translations/app_uk.po diff --git a/selfdrive/ui/translations/app_uk.po b/selfdrive/ui/translations/app_uk.po new file mode 100644 index 000000000..cf78fb5a3 --- /dev/null +++ b/selfdrive/ui/translations/app_uk.po @@ -0,0 +1,1258 @@ +# Ukrainian translations for PACKAGE package. +# Copyright (C) 2025 THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# Automatically generated, 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-11-19 12:21+0200\n" +"PO-Revision-Date: 2025-11-19 13:27+0200\n" +"Last-Translator: KeeFeeRe \n" +"Language-Team: none\n" +"Language: uk\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " +"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" +"X-Generator: Poedit 3.8\n" + +#: selfdrive/ui/layouts/settings/device.py:160 +#, python-format +msgid " Steering torque response calibration is complete." +msgstr " Калібрування реакції крутного моменту керма завершено." + +#: selfdrive/ui/layouts/settings/device.py:158 +#, python-format +msgid " Steering torque response calibration is {}% complete." +msgstr "Калібрування реакції крутного моменту керма завершено на {}%." + +#: selfdrive/ui/layouts/settings/device.py:133 +#, python-format +msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." +msgstr " Ваш пристрій нахилено на {:.1f}° {} та {:.1f}° {}." + +#: selfdrive/ui/layouts/sidebar.py:43 +msgid "--" +msgstr "--" + +#: selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "1 year of drive storage" +msgstr "1 рік зберігання поїздок" + +#: selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "24/7 LTE connectivity" +msgstr "Підключення LTE 24/7" + +#: selfdrive/ui/layouts/sidebar.py:46 +msgid "2G" +msgstr "2G" + +#: selfdrive/ui/layouts/sidebar.py:47 +msgid "3G" +msgstr "3G" + +#: selfdrive/ui/layouts/sidebar.py:49 +msgid "5G" +msgstr "5G" + +#: selfdrive/ui/layouts/settings/developer.py:23 +msgid "" +"WARNING: openpilot longitudinal control is in alpha for this car and will " +"disable Automatic Emergency Braking (AEB).

On this car, openpilot " +"defaults to the car's built-in ACC instead of openpilot's longitudinal " +"control. Enable this to switch to openpilot longitudinal control. Enabling " +"Experimental mode is recommended when enabling openpilot longitudinal " +"control alpha. Changing this setting will restart openpilot if the car is " +"powered on." +msgstr "" +"ПОПЕРЕДЖЕННЯ: поздовжнє керування openpilot для цього автомобіля знаходиться " +"в стадії альфа-тестування і вимкне автоматичне екстрене гальмування (AEB)." + +#: selfdrive/ui/layouts/settings/device.py:148 +#, python-format +msgid "

Steering lag calibration is complete." +msgstr "

Калібрування затримки кермування завершено." + +#: selfdrive/ui/layouts/settings/device.py:146 +#, python-format +msgid "

Steering lag calibration is {}% complete." +msgstr "

Калібрування затримки кермування завершено на {}%." + +#: selfdrive/ui/layouts/settings/firehose.py:138 +#, python-format +msgid "ACTIVE" +msgstr "АКТИВНИЙ" + +#: selfdrive/ui/layouts/settings/developer.py:15 +msgid "" +"ADB (Android Debug Bridge) allows connecting to your device over USB or over " +"the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." +msgstr "" +"ADB (Android Debug Bridge) дозволяє підключатися до вашого пристрою через " +"USB або мережу. Дивіться https://docs.comma.ai/how-to/connect-to-comma для " +"отримання додаткової інформації." + +#: selfdrive/ui/widgets/ssh_key.py:30 +msgid "ADD" +msgstr "ДОДАТИ" + +#: system/ui/widgets/network.py:139 +#, python-format +msgid "APN Setting" +msgstr "Налаштування APN" + +#: selfdrive/ui/widgets/offroad_alerts.py:109 +#, python-format +msgid "Acknowledge Excessive Actuation" +msgstr "Визнайте надмірне спрацьовування" + +#: system/ui/widgets/network.py:74 system/ui/widgets/network.py:95 +#, python-format +msgid "Advanced" +msgstr "Розширені" + +#: selfdrive/ui/layouts/settings/toggles.py:98 +#, python-format +msgid "Aggressive" +msgstr "Агресивн." + +#: selfdrive/ui/layouts/onboarding.py:116 +#, python-format +msgid "Agree" +msgstr "Погодитися" + +#: selfdrive/ui/layouts/settings/toggles.py:70 +#, python-format +msgid "Always-On Driver Monitoring" +msgstr "Постійний моніторинг водія" + +#: selfdrive/ui/layouts/settings/toggles.py:186 +#, python-format +msgid "" +"An alpha version of openpilot longitudinal control can be tested, along with " +"Experimental mode, on non-release branches." +msgstr "" +"Альфа-версію поздовжнього керування openpilot можна протестувати разом з " +"експериментальним режимом на нерелізних гілках." + +#: selfdrive/ui/layouts/settings/device.py:187 +#, python-format +msgid "Are you sure you want to power off?" +msgstr "Ви впевнені, що хочете вимкнути?" + +#: selfdrive/ui/layouts/settings/device.py:175 +#, python-format +msgid "Are you sure you want to reboot?" +msgstr "Ви впевнені, що хочете перезавантажити?" + +#: selfdrive/ui/layouts/settings/device.py:119 +#, python-format +msgid "Are you sure you want to reset calibration?" +msgstr "Ви впевнені, що хочете скинути калібрування?" + +#: selfdrive/ui/layouts/settings/software.py:171 +#, python-format +msgid "Are you sure you want to uninstall?" +msgstr "Ви впевнені, що хочете видалити?" + +#: system/ui/widgets/network.py:99 +#: selfdrive/ui/layouts/onboarding.py:147 +#, python-format +msgid "Back" +msgstr "Назад" + +#: selfdrive/ui/widgets/prime.py:38 +#, python-format +msgid "Become a comma prime member at connect.comma.ai" +msgstr "Станьте членом comma prime на connect.comma.ai" + +#: selfdrive/ui/widgets/pairing_dialog.py:119 +#, python-format +msgid "Bookmark connect.comma.ai to your home screen to use it like an app" +msgstr "" +"Додайте connect.comma.ai до головного екрану, щоб використовувати його як " +"додаток." + +#: selfdrive/ui/layouts/settings/device.py:68 +#, python-format +msgid "CHANGE" +msgstr "ЗМІНИТИ" + +#: selfdrive/ui/layouts/settings/software.py:50 +#: selfdrive/ui/layouts/settings/software.py:115 +#: selfdrive/ui/layouts/settings/software.py:126 +#: selfdrive/ui/layouts/settings/software.py:155 +#, python-format +msgid "CHECK" +msgstr "ПЕРЕВІРИТИ" + +#: selfdrive/ui/widgets/exp_mode_button.py:50 +#, python-format +msgid "CHILL MODE ON" +msgstr "СПОКІЙНИЙ РЕЖИМ" + +#: system/ui/widgets/network.py:155 +#: selfdrive/ui/layouts/sidebar.py:73 +#: selfdrive/ui/layouts/sidebar.py:134 +#: selfdrive/ui/layouts/sidebar.py:136 +#: selfdrive/ui/layouts/sidebar.py:138 +#, python-format +msgid "CONNECT" +msgstr "CONNECT" + +#: system/ui/widgets/network.py:369 +#, python-format +msgid "CONNECTING..." +msgstr "ПІДКЛЮЧА..." + +#: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35 +#: system/ui/widgets/network.py:318 system/ui/widgets/keyboard.py:81 +#, python-format +msgid "Cancel" +msgstr "Скасувати" + +#: system/ui/widgets/network.py:134 +#, python-format +msgid "Cellular Metered" +msgstr "Лімітне стільникове з'єднання" + +#: selfdrive/ui/layouts/settings/device.py:68 +#, python-format +msgid "Change Language" +msgstr "Змінити мову" + +#: selfdrive/ui/layouts/settings/toggles.py:125 +#, python-format +msgid "Changing this setting will restart openpilot if the car is powered on." +msgstr "" +"Зміна цього параметра призведе до перезапуску openpilot, якщо автомобіль " +"увімкнено." + +#: selfdrive/ui/widgets/pairing_dialog.py:118 +#, python-format +msgid "Click \"add new device\" and scan the QR code on the right" +msgstr "Натисніть «додати новий пристрій» і відскануйте QR-код праворуч." + +#: selfdrive/ui/widgets/offroad_alerts.py:104 +#, python-format +msgid "Close" +msgstr "Закрити" + +#: selfdrive/ui/layouts/settings/software.py:49 +#, python-format +msgid "Current Version" +msgstr "Поточна версія" + +#: selfdrive/ui/layouts/settings/software.py:118 +#, python-format +msgid "DOWNLOAD" +msgstr "ВАНТАЖ" + +#: selfdrive/ui/layouts/onboarding.py:115 +#, python-format +msgid "Decline" +msgstr "Відхилити" + +#: selfdrive/ui/layouts/onboarding.py:148 +#, python-format +msgid "Decline, uninstall openpilot" +msgstr "Відхилити, видалити openpilot" + +#: selfdrive/ui/layouts/settings/settings.py:64 +msgid "Developer" +msgstr "Розробник" + +#: selfdrive/ui/layouts/settings/settings.py:59 +msgid "Device" +msgstr "Пристрій" + +#: selfdrive/ui/layouts/settings/toggles.py:58 +#, python-format +msgid "Disengage on Accelerator Pedal" +msgstr "Вимкнення при натисканні на педаль газу" + +#: selfdrive/ui/layouts/settings/device.py:184 +#, python-format +msgid "Disengage to Power Off" +msgstr "Вимкніть openpilot, щоб вимкнути пристрій" + +#: selfdrive/ui/layouts/settings/device.py:172 +#, python-format +msgid "Disengage to Reboot" +msgstr "Вимкніть openpilot, щоб перезавантажити" + +#: selfdrive/ui/layouts/settings/device.py:103 +#, python-format +msgid "Disengage to Reset Calibration" +msgstr "Деактивуйте для скидання калібрування" + +#: selfdrive/ui/layouts/settings/toggles.py:32 +msgid "Display speed in km/h instead of mph." +msgstr "Відображати швидкість у км/год замість миль/год." + +#: selfdrive/ui/layouts/settings/device.py:59 +#, python-format +msgid "Dongle ID" +msgstr "ID ключа" + +#: selfdrive/ui/layouts/settings/software.py:50 +#, python-format +msgid "Download" +msgstr "Завантажити" + +#: selfdrive/ui/layouts/settings/device.py:62 +#, python-format +msgid "Driver Camera" +msgstr "Камера водія" + +#: selfdrive/ui/layouts/settings/toggles.py:96 +#, python-format +msgid "Driving Personality" +msgstr "Стиль водіння" + +#: system/ui/widgets/network.py:123 system/ui/widgets/network.py:139 +#, python-format +msgid "EDIT" +msgstr "РЕДАГ." + +#: selfdrive/ui/layouts/sidebar.py:138 +msgid "ERROR" +msgstr "ПОМИЛКА" + +#: selfdrive/ui/layouts/sidebar.py:45 +msgid "ETH" +msgstr "ETH" + +#: selfdrive/ui/widgets/exp_mode_button.py:50 +#, python-format +msgid "EXPERIMENTAL MODE ON" +msgstr "ЕКСПЕРИМЕНТ. РЕЖИМ" + +#: selfdrive/ui/layouts/settings/toggles.py:228 +#: selfdrive/ui/layouts/settings/developer.py:166 +#, python-format +msgid "Enable" +msgstr "Увімкнути" + +#: selfdrive/ui/layouts/settings/developer.py:39 +#, python-format +msgid "Enable ADB" +msgstr "Увімкнути ADB" + +#: selfdrive/ui/layouts/settings/toggles.py:64 +#, python-format +msgid "Enable Lane Departure Warnings" +msgstr "Увімкнути попередження про виїзд зі смуги" + +#: system/ui/widgets/network.py:129 +#, python-format +msgid "Enable Roaming" +msgstr "Увімкнути роумінг" + +#: selfdrive/ui/layouts/settings/developer.py:48 +#, python-format +msgid "Enable SSH" +msgstr "Увімкнути SSH" + +#: system/ui/widgets/network.py:120 +#, python-format +msgid "Enable Tethering" +msgstr "Увімкнути точку доступу" + +#: selfdrive/ui/layouts/settings/toggles.py:30 +msgid "Enable driver monitoring even when openpilot is not engaged." +msgstr "Увімкнути моніторинг водія, навіть коли openpilot не ввімкнено." + +#: selfdrive/ui/layouts/settings/toggles.py:46 +#, python-format +msgid "Enable openpilot" +msgstr "Увімкнути openpilot" + +#: selfdrive/ui/layouts/settings/toggles.py:189 +#, python-format +msgid "" +"Enable the openpilot longitudinal control (alpha) toggle to allow " +"Experimental mode." +msgstr "" +"Увімкніть перемикач поздовжнього керування openpilot (альфа), щоб увімкнути " +"експериментальний режим." + +#: system/ui/widgets/network.py:204 +#, python-format +msgid "Enter APN" +msgstr "Введіть APN" + +#: system/ui/widgets/network.py:241 +#, python-format +msgid "Enter SSID" +msgstr "Введіть SSID" + +#: system/ui/widgets/network.py:254 +#, python-format +msgid "Enter new tethering password" +msgstr "Введіть новий пароль для модему" + +#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#, python-format +msgid "Enter password" +msgstr "Введіть пароль" + +#: selfdrive/ui/widgets/ssh_key.py:89 +#, python-format +msgid "Enter your GitHub username" +msgstr "Введіть ваш логін GitHub" + +#: system/ui/widgets/list_view.py:123 system/ui/widgets/list_view.py:160 +#, python-format +msgid "Error" +msgstr "Помилка" + +#: selfdrive/ui/layouts/settings/toggles.py:52 +#, python-format +msgid "Experimental Mode" +msgstr "Експериментальний режим" + +#: selfdrive/ui/layouts/settings/toggles.py:181 +#, python-format +msgid "" +"Experimental mode is currently unavailable on this car since the car's stock " +"ACC is used for longitudinal control." +msgstr "" +"Експериментальний режим наразі недоступний для цього автомобіля, оскільки " +"для поздовжнього керування використовується штатний адаптивний круїз-" +"контроль (ACC)." + +#: system/ui/widgets/network.py:373 +#, python-format +msgid "FORGETTING..." +msgstr "ЗАБУВАЮ..." + +#: selfdrive/ui/widgets/setup.py:44 +#, python-format +msgid "Finish Setup" +msgstr "Завершити налаштування" + +#: selfdrive/ui/layouts/settings/settings.py:63 +msgid "Firehose" +msgstr "Злива" + +#: selfdrive/ui/layouts/settings/firehose.py:18 +msgid "Firehose Mode" +msgstr "Режим зливи" + +#: selfdrive/ui/layouts/settings/firehose.py:25 +msgid "" +"For maximum effectiveness, bring your device inside and connect to a good " +"USB-C adapter and Wi-Fi weekly.\n" +"\n" +"Firehose Mode can also work while you're driving if connected to a hotspot " +"or unlimited SIM card.\n" +"\n" +"\n" +"Frequently Asked Questions\n" +"\n" +"Does it matter how or where I drive? Nope, just drive as you normally " +"would.\n" +"\n" +"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a " +"subset of your segments.\n" +"\n" +"What's a good USB-C adapter? Any fast phone or laptop charger should be " +"fine.\n" +"\n" +"Does it matter which software I run? Yes, only upstream openpilot (and " +"particular forks) are able to be used for training." +msgstr "" +"Для максимальної ефективності щотижня заносьте пристрій у приміщення та " +"підключайте його до якісного адаптера USB-C і Wi-Fi.\n" +"\n" +"Режим Зливи також може працювати під час руху, якщо пристрій підключено до " +"точки доступу або SIM-картки з необмеженим трафіком.\n" +"\n" +"\n" +"Поширені запитання\n" +"\n" +"Чи має значення, як і де я їду? Ні, просто їдьте, як зазвичай.\n" +"\n" +"Чи всі мої сегменти потрапляють у режим Зливи? Ні, ми вибірково вибираємо " +"підмножину ваших сегментів.\n" +"\n" +"Що таке хороший адаптер USB-C? Будь-який швидкий зарядний пристрій для " +"телефону або ноутбука підійде.\n" +"\n" +"Чи має значення, яке програмне забезпечення я використовую? Так, для " +"навчання можна використовувати тільки upstream openpilot (і певні його " +"форки)." + +#: system/ui/widgets/network.py:318 system/ui/widgets/network.py:451 +#, python-format +msgid "Forget" +msgstr "Заб-и" + +#: system/ui/widgets/network.py:319 +#, python-format +msgid "Forget Wi-Fi Network \"{}\"?" +msgstr "Забути мережу Wi-Fi \"{}\"?" + +#: selfdrive/ui/layouts/sidebar.py:71 +#: selfdrive/ui/layouts/sidebar.py:125 +msgid "GOOD" +msgstr "ДОБРА" + +#: selfdrive/ui/widgets/pairing_dialog.py:117 +#, python-format +msgid "Go to https://connect.comma.ai on your phone" +msgstr "Перейдіть на сайт https://connect.comma.ai на своєму телефоні." + +#: selfdrive/ui/layouts/sidebar.py:129 +msgid "HIGH" +msgstr "ВИСОКА" + +#: system/ui/widgets/network.py:155 +#, python-format +msgid "Hidden Network" +msgstr "Прихована мережа" + +#: selfdrive/ui/layouts/settings/firehose.py:140 +#, python-format +msgid "INACTIVE: connect to an unmetered network" +msgstr "НЕАКТИВНО: підключення до мережі без ліміту трафіку" + +#: selfdrive/ui/layouts/settings/software.py:53 +#: selfdrive/ui/layouts/settings/software.py:144 +#, python-format +msgid "INSTALL" +msgstr "ВСТАНОВ." + +#: system/ui/widgets/network.py:150 +#, python-format +msgid "IP Address" +msgstr "IP-адреса" + +#: selfdrive/ui/layouts/settings/software.py:53 +#, python-format +msgid "Install Update" +msgstr "Встановити оновлення" + +#: selfdrive/ui/layouts/settings/developer.py:56 +#, python-format +msgid "Joystick Debug Mode" +msgstr "Режим зневадження джойстика" + +#: selfdrive/ui/widgets/ssh_key.py:29 +msgid "LOADING" +msgstr "ЗАВАНТАЖЕННЯ" + +#: selfdrive/ui/layouts/sidebar.py:48 +msgid "LTE" +msgstr "LTE" + +#: selfdrive/ui/layouts/settings/developer.py:64 +#, python-format +msgid "Longitudinal Maneuver Mode" +msgstr "Режим поздовжнього маневрування" + +#: selfdrive/ui/onroad/hud_renderer.py:148 +#, python-format +msgid "MAX" +msgstr "МАКС" + +#: selfdrive/ui/widgets/setup.py:75 +#, python-format +msgid "" +"Maximize your training data uploads to improve openpilot's driving models." +msgstr "" +"Максимізуйте завантаження навчальних даних, щоб поліпшити моделі openpilot." + +#: selfdrive/ui/layouts/settings/device.py:59 +#: selfdrive/ui/layouts/settings/device.py:60 +#, python-format +msgid "N/A" +msgstr "Н/Д" + +#: selfdrive/ui/layouts/sidebar.py:142 +msgid "NO" +msgstr "НЕМАЄ" + +#: selfdrive/ui/layouts/settings/settings.py:60 +msgid "Network" +msgstr "Мережа" + +#: selfdrive/ui/widgets/ssh_key.py:114 +#, python-format +msgid "No SSH keys found" +msgstr "Не знайдено ключів SSH" + +#: selfdrive/ui/widgets/ssh_key.py:126 +#, python-format +msgid "No SSH keys found for user '{}'" +msgstr "Користувач '{}' не має ключів на GitHub" + +#: selfdrive/ui/widgets/offroad_alerts.py:320 +#, python-format +msgid "No release notes available." +msgstr "Інформація про випуск відсутня." + +#: selfdrive/ui/layouts/sidebar.py:73 +#: selfdrive/ui/layouts/sidebar.py:134 +msgid "OFFLINE" +msgstr "ОФЛАЙН" + +#: system/ui/widgets/confirm_dialog.py:93 system/ui/widgets/html_render.py:263 +#: selfdrive/ui/layouts/sidebar.py:127 +#, python-format +msgid "OK" +msgstr "OK" + +#: selfdrive/ui/layouts/sidebar.py:72 +#: selfdrive/ui/layouts/sidebar.py:136 +#: selfdrive/ui/layouts/sidebar.py:144 +msgid "ONLINE" +msgstr "ОНЛАЙН" + +#: selfdrive/ui/widgets/setup.py:20 +#, python-format +msgid "Open" +msgstr "ВІДКРИТИ" + +#: selfdrive/ui/layouts/settings/device.py:48 +#, python-format +msgid "PAIR" +msgstr "ПІДКЛЮЧИТИ" + +#: selfdrive/ui/layouts/sidebar.py:142 +msgid "PANDA" +msgstr "PANDA" + +#: selfdrive/ui/layouts/settings/device.py:62 +#, python-format +msgid "PREVIEW" +msgstr "ПОКАЖИ" + +#: selfdrive/ui/widgets/prime.py:44 +#, python-format +msgid "PRIME FEATURES:" +msgstr "XАРАКТЕРИСТИКИ PRIME:" + +#: selfdrive/ui/layouts/settings/device.py:48 +#, python-format +msgid "Pair Device" +msgstr "Підключити пристрій" + +#: selfdrive/ui/widgets/setup.py:19 +#, python-format +msgid "Pair device" +msgstr "Підключити пристрій" + +#: selfdrive/ui/widgets/pairing_dialog.py:92 +#, python-format +msgid "Pair your device to your comma account" +msgstr "Підключіть свій пристрій до обліковки comma connect" + +#: selfdrive/ui/widgets/setup.py:48 +#: selfdrive/ui/layouts/settings/device.py:24 +#, python-format +msgid "" +"Pair your device with comma connect (connect.comma.ai) and claim your comma " +"prime offer." +msgstr "" +"Підключіть свій пристрій до comma connect (connect.comma.ai) і отримайте " +"свою пропозицію comma prime." + +#: selfdrive/ui/widgets/setup.py:91 +#, python-format +msgid "Please connect to Wi-Fi to complete initial pairing" +msgstr "Будь ласка, підключіться до Wi-Fi, щоб завершити початкове сполучення." + +#: selfdrive/ui/layouts/settings/device.py:55 +#: selfdrive/ui/layouts/settings/device.py:187 +#, python-format +msgid "Power Off" +msgstr "Вимкнути" + +#: system/ui/widgets/network.py:144 +#, python-format +msgid "Prevent large data uploads when on a metered Wi-Fi connection" +msgstr "" +"Запобігайте завантаженню великих обсягів даних під час використання Wi-Fi-" +"з'єднання з обмеженим трафіком" + +#: system/ui/widgets/network.py:135 +#, python-format +msgid "Prevent large data uploads when on a metered cellular connection" +msgstr "" +"Запобігати великим завантаженням даних під час лімітного стільникового " +"з'єднання" + +#: selfdrive/ui/layouts/settings/device.py:25 +msgid "" +"Preview the driver facing camera to ensure that driver monitoring has good " +"visibility. (vehicle must be off)" +msgstr "" +"Попередньо перегляньте камеру, спрямовану на водія, щоб переконатися, що " +"система моніторингу водія має добру видимість. (автомобіль повинен бути " +"вимкнений)" + +#: selfdrive/ui/widgets/pairing_dialog.py:150 +#, python-format +msgid "QR Code Error" +msgstr "Помилка QR-коду" + +#: selfdrive/ui/widgets/ssh_key.py:31 +msgid "REMOVE" +msgstr "ВИДАЛИТИ" + +#: selfdrive/ui/layouts/settings/device.py:51 +#, python-format +msgid "RESET" +msgstr "Скинути" + +#: selfdrive/ui/layouts/settings/device.py:65 +#, python-format +msgid "REVIEW" +msgstr "ДИВИТИСЬ" + +#: selfdrive/ui/layouts/settings/device.py:55 +#: selfdrive/ui/layouts/settings/device.py:175 +#, python-format +msgid "Reboot" +msgstr "Перезавантажити" + +#: selfdrive/ui/onroad/alert_renderer.py:66 +#, python-format +msgid "Reboot Device" +msgstr "Перезавантажте пристрій" + +#: selfdrive/ui/widgets/offroad_alerts.py:112 +#, python-format +msgid "Reboot and Update" +msgstr "Перезавантажити та оновити" + +#: selfdrive/ui/layouts/settings/toggles.py:27 +msgid "" +"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)." +msgstr "" +"Отримувати попередження про необхідність повернутися в смугу, коли ваш " +"автомобіль перетинає виявлену лінію розмітки без увімкненого сигналу " +"повороту під час руху зі швидкістю понад 31 миль/год (50 км/год)." + +#: selfdrive/ui/layouts/settings/toggles.py:76 +#, python-format +msgid "Record and Upload Driver Camera" +msgstr "Писати та вантажити відео з камери водія" + +#: selfdrive/ui/layouts/settings/toggles.py:82 +#, python-format +msgid "Record and Upload Microphone Audio" +msgstr "Запис та завантаження аудіо з мікрофона" + +#: selfdrive/ui/layouts/settings/toggles.py:33 +msgid "" +"Record and store microphone audio while driving. The audio will be included " +"in the dashcam video in comma connect." +msgstr "" +"Записуйте та зберігайте аудіо з мікрофона під час руху. Аудіо буде включено " +"до відео з відеореєстратора в comma connect." + +#: selfdrive/ui/layouts/settings/device.py:67 +#, python-format +msgid "Regulatory" +msgstr "Нормативні документи" + +#: selfdrive/ui/layouts/settings/toggles.py:98 +#, python-format +msgid "Relaxed" +msgstr "Спокійний" + +#: selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "Remote access" +msgstr "Віддалений доступ" + +#: selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "Remote snapshots" +msgstr "Віддалені знімки" + +#: selfdrive/ui/widgets/ssh_key.py:123 +#, python-format +msgid "Request timed out" +msgstr "Час запиту вичерпано" + +#: selfdrive/ui/layouts/settings/device.py:119 +#, python-format +msgid "Reset" +msgstr "Скинути" + +#: selfdrive/ui/layouts/settings/device.py:51 +#, python-format +msgid "Reset Calibration" +msgstr "Скинути калібрування" + +#: selfdrive/ui/layouts/settings/device.py:65 +#, python-format +msgid "Review Training Guide" +msgstr "Переглянути посібник з навчання" + +#: selfdrive/ui/layouts/settings/device.py:27 +msgid "Review the rules, features, and limitations of openpilot" +msgstr "Перегляньте правила, функції та обмеження openpilot" + +#: selfdrive/ui/layouts/settings/software.py:61 +#, python-format +msgid "SELECT" +msgstr "ВИБРАТИ" + +#: selfdrive/ui/layouts/settings/developer.py:53 +#, python-format +msgid "SSH Keys" +msgstr "SSH ключі" + +#: system/ui/widgets/network.py:310 +#, python-format +msgid "Scanning Wi-Fi networks..." +msgstr "Пошук мереж..." + +#: system/ui/widgets/option_dialog.py:36 +#, python-format +msgid "Select" +msgstr "Вибрати" + +#: selfdrive/ui/layouts/settings/software.py:191 +#, python-format +msgid "Select a branch" +msgstr "Виберіть гілку" + +#: selfdrive/ui/layouts/settings/device.py:91 +#, python-format +msgid "Select a language" +msgstr "Виберіть мову" + +#: selfdrive/ui/layouts/settings/device.py:60 +#, python-format +msgid "Serial" +msgstr "Серійний номер" + +#: selfdrive/ui/widgets/offroad_alerts.py:106 +#, python-format +msgid "Snooze Update" +msgstr "Відкласти оновлення" + +#: selfdrive/ui/layouts/settings/settings.py:62 +msgid "Software" +msgstr "Програма" + +#: selfdrive/ui/layouts/settings/toggles.py:98 +#, python-format +msgid "Standard" +msgstr "Стандарт" + +#: selfdrive/ui/layouts/settings/toggles.py:22 +msgid "" +"Standard is recommended. In aggressive mode, openpilot will follow lead cars " +"closer and be more aggressive with the gas and brake. In relaxed mode " +"openpilot will stay further away from lead cars. On supported cars, you can " +"cycle through these personalities with your steering wheel distance button." +msgstr "" +"Рекомендується стандартний режим. В агресивному режимі openpilot буде " +"триматися ближче до автомобілів попереду і більш агресивно використовувати " +"газ і гальма. У спокійному режимі openpilot буде триматися на більшій " +"відстані від автомобілів попереду. На підтримуваних автомобілях ви можете " +"перемикатися між цими режимами за допомогою кнопки дистанції на кермі." + +#: selfdrive/ui/onroad/alert_renderer.py:59 +#: selfdrive/ui/onroad/alert_renderer.py:65 +#, python-format +msgid "System Unresponsive" +msgstr "Система не реагує" + +#: selfdrive/ui/onroad/alert_renderer.py:58 +#, python-format +msgid "TAKE CONTROL IMMEDIATELY" +msgstr "КЕРМУЙТЕ НЕГАЙНО" + +#: selfdrive/ui/layouts/sidebar.py:71 +#: selfdrive/ui/layouts/sidebar.py:125 +#: selfdrive/ui/layouts/sidebar.py:127 +#: selfdrive/ui/layouts/sidebar.py:129 +msgid "TEMP" +msgstr "ТЕМП" + +#: selfdrive/ui/layouts/settings/software.py:61 +#, python-format +msgid "Target Branch" +msgstr "Цільова гілка" + +#: system/ui/widgets/network.py:124 +#, python-format +msgid "Tethering Password" +msgstr "Пароль для точки доступу" + +#: selfdrive/ui/layouts/settings/settings.py:61 +msgid "Toggles" +msgstr "Перемикачі" + +#: selfdrive/ui/layouts/settings/software.py:72 +#, python-format +msgid "UNINSTALL" +msgstr "ВИДАЛИТИ" + +#: selfdrive/ui/layouts/home.py:155 +#, python-format +msgid "UPDATE" +msgstr "ОНОВИТИ" + +#: selfdrive/ui/layouts/settings/software.py:72 +#: selfdrive/ui/layouts/settings/software.py:171 +#, python-format +msgid "Uninstall" +msgstr "Видалити" + +#: selfdrive/ui/layouts/sidebar.py:117 +msgid "Unknown" +msgstr "Невідомо" + +#: selfdrive/ui/layouts/settings/software.py:48 +#, python-format +msgid "Updates are only downloaded while the car is off." +msgstr "Оновлення завантажуються лише тоді, коли автомобіль вимкнено." + +#: selfdrive/ui/widgets/prime.py:33 +#, python-format +msgid "Upgrade Now" +msgstr "Оновити зараз" + +#: selfdrive/ui/layouts/settings/toggles.py:31 +msgid "" +"Upload data from the driver facing camera and help improve the driver " +"monitoring algorithm." +msgstr "" +"Завантажуйте дані з камери, спрямованої на водія, та допоможіть покращити " +"алгоритм моніторингу водія." + +#: selfdrive/ui/layouts/settings/toggles.py:88 +#, python-format +msgid "Use Metric System" +msgstr "Використовувати метричну систему" + +#: selfdrive/ui/layouts/settings/toggles.py:17 +msgid "" +"Use the openpilot system for adaptive cruise control and lane keep driver " +"assistance. Your attention is required at all times to use this feature." +msgstr "" +"Використовуйте систему openpilot для адаптивного круїз-контролю та допомоги " +"в утриманні смуги руху. Ваша увага потрібна постійно при використанні цієї " +"функції. Зміна цього налаштування набуває чинності після вимкнення живлення " +"автомобіля." + +#: selfdrive/ui/layouts/sidebar.py:72 +#: selfdrive/ui/layouts/sidebar.py:144 +msgid "VEHICLE" +msgstr "АВТО" + +#: selfdrive/ui/layouts/settings/device.py:67 +#, python-format +msgid "VIEW" +msgstr "ДИВИСЬ" + +#: selfdrive/ui/onroad/alert_renderer.py:52 +#, python-format +msgid "Waiting to start" +msgstr "Очікування початку" + +#: selfdrive/ui/layouts/settings/developer.py:19 +msgid "" +"Warning: This grants SSH access to all public keys in your GitHub settings. " +"Never enter a GitHub username other than your own. A comma employee will " +"NEVER ask you to add their GitHub username." +msgstr "" +"Попередження: це надає доступ по SSH до всіх публічних ключів у ваших " +"налаштуваннях GitHub. Ніколи не вводьте ім'я користувача GitHub, окрім " +"вашого власного. Співробітник comma НІКОЛИ не попросить вас додати його ім'я " +"користувача GitHub." + +#: selfdrive/ui/layouts/onboarding.py:111 +#, python-format +msgid "Welcome to openpilot" +msgstr "Ласкаво просимо до openpilot" + +#: selfdrive/ui/layouts/settings/toggles.py:20 +msgid "When enabled, pressing the accelerator pedal will disengage openpilot." +msgstr "Якщо увімкнено, натискання на педаль акселератора вимкне openpilot." + +#: selfdrive/ui/layouts/sidebar.py:44 +msgid "Wi-Fi" +msgstr "Wi-Fi" + +#: system/ui/widgets/network.py:144 +#, python-format +msgid "Wi-Fi Network Metered" +msgstr "Трафік Wi-Fi" + +#: system/ui/widgets/network.py:314 +#, python-format +msgid "Wrong password" +msgstr "Невірний пароль" + +#: selfdrive/ui/layouts/onboarding.py:145 +#, python-format +msgid "You must accept the Terms and Conditions in order to use openpilot." +msgstr "Ви повинні прийняти Умови та положення, щоб користуватися openpilot." + +#: selfdrive/ui/layouts/onboarding.py:112 +#, python-format +msgid "" +"You must accept the Terms and Conditions to use openpilot. Read the latest " +"terms at https://comma.ai/terms before continuing." +msgstr "" +"Ви повинні прийняти Умови використання, щоб користуватися openpilot. Перед " +"тим, як продовжити, ознайомтеся з останніми умовами на сайті https://" +"comma.ai/terms." + +#: selfdrive/ui/onroad/driver_camera_dialog.py:34 +#, python-format +msgid "camera starting" +msgstr "запуск камери" + +#: selfdrive/ui/layouts/settings/software.py:105 +#, python-format +msgid "checking..." +msgstr "перевіряю..." + +#: selfdrive/ui/widgets/prime.py:63 +#, python-format +msgid "comma prime" +msgstr "comma prime" + +#: system/ui/widgets/network.py:142 +#, python-format +msgid "default" +msgstr "замовч." + +#: selfdrive/ui/layouts/settings/device.py:133 +#, python-format +msgid "down" +msgstr "вниз" + +#: selfdrive/ui/layouts/settings/software.py:106 +#, python-format +msgid "downloading..." +msgstr "завантажую..." + +#: selfdrive/ui/layouts/settings/software.py:114 +#, python-format +msgid "failed to check for update" +msgstr "не вдалося перевірити оновлення" + +#: selfdrive/ui/layouts/settings/software.py:107 +#, python-format +msgid "finalizing update..." +msgstr "завершую..." + +#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#, python-format +msgid "for \"{}\"" +msgstr "для \"{}\"" + +#: selfdrive/ui/onroad/hud_renderer.py:177 +#, python-format +msgid "km/h" +msgstr "км/год" + +#: system/ui/widgets/network.py:204 +#, python-format +msgid "leave blank for automatic configuration" +msgstr "залиште порожнім для автоматичного налаштування" + +#: selfdrive/ui/layouts/settings/device.py:134 +#, python-format +msgid "left" +msgstr "вліво" + +#: system/ui/widgets/network.py:142 +#, python-format +msgid "metered" +msgstr "обмеж." + +#: selfdrive/ui/onroad/hud_renderer.py:177 +#, python-format +msgid "mph" +msgstr "миль/год" + +#: selfdrive/ui/layouts/settings/software.py:20 +#, python-format +msgid "never" +msgstr "ніколи" + +#: selfdrive/ui/layouts/settings/software.py:31 +#, python-format +msgid "now" +msgstr "зараз" + +#: selfdrive/ui/layouts/settings/developer.py:71 +#, python-format +msgid "openpilot Longitudinal Control (Alpha)" +msgstr "Поздовжнє керування openpilot (Альфа)" + +#: selfdrive/ui/onroad/alert_renderer.py:51 +#, python-format +msgid "openpilot Unavailable" +msgstr "openpilot Недоступний" + +#: selfdrive/ui/layouts/settings/toggles.py:158 +#, python-format +msgid "" +"openpilot defaults to driving in chill mode. Experimental mode enables alpha-" +"level features that aren't ready for chill mode. Experimental features are " +"listed below:

End-to-End Longitudinal Control


Let the driving " +"model control the gas and brakes. openpilot will drive as it thinks a human " +"would, including stopping for red lights and stop signs. Since the driving " +"model decides the speed to drive, the set speed will only act as an upper " +"bound. This is an alpha quality feature; mistakes should be expected." +"

New Driving Visualization


The driving visualization will " +"transition to the road-facing wide-angle camera at low speeds to better show " +"some turns. The Experimental mode logo will also be shown in the top right " +"corner." +msgstr "" +"openpilot за замовчуванням працює в режимі спокій. Експериментальний режим " +"увімкне функції альфа-рівня, які ще не готові для режиму спокій. " +"Експериментальні функції перелічені нижче:

Кінцевий поздовжній " +"контроль


Дозвольте моделі водіння контролювати газ і гальма. " +"openpilot буде керувати автомобілем так, як це робив би людина, включаючи " +"зупинку на червоне світло і знаки зупинки. Оскільки модель водіння визначає " +"швидкість руху, задана швидкість буде діяти лише як верхня межа. Це функція " +"альфа-рівня; слід очікувати помилок.

Нова візуалізація водіння
Візуалізація водіння перейде на ширококутну камеру, спрямовану на " +"дорогу, при низьких швидкостях, щоб краще показувати деякі повороти. Логотип " +"експериментального режиму також буде показаний у верхньому правому куті." + +#: selfdrive/ui/layouts/settings/device.py:165 +#, python-format +msgid "" +"openpilot is continuously calibrating, resetting is rarely required. " +"Resetting calibration will restart openpilot if the car is powered on." +msgstr "" +"openpilot постійно калібрується, скидання рідко потрібне. Скидання " +"калібрування призведе до перезапуску openpilot, якщо автомобіль увімкнено." + +#: selfdrive/ui/layouts/settings/firehose.py:20 +msgid "" +"openpilot learns to drive by watching humans, like you, drive.\n" +"\n" +"Firehose Mode allows you to maximize your training data uploads to improve " +"openpilot's driving models. More data means bigger models, which means " +"better Experimental Mode." +msgstr "" +"openpilot вчиться керувати автомобілем, спостерігаючи за тим, як це роблять " +"люди, такі як ви.\n" +"\n" +"Режим зливи дозволяє максимально збільшити обсяг завантажуваних навчальних " +"даних, щоб поліпшити моделі керування автомобілем openpilot. Більше даних " +"означає більші моделі, а це означає кращий експериментальний режим." + +#: selfdrive/ui/layouts/settings/toggles.py:183 +#, python-format +msgid "openpilot longitudinal control may come in a future update." +msgstr "Поздовжнє керування openpilot може з'явитися в майбутньому оновленні." + +#: selfdrive/ui/layouts/settings/device.py:26 +msgid "" +"openpilot requires the device to be mounted within 4° left or right and " +"within 5° up or 9° down." +msgstr "" +"Для роботи openpilot потрібно, щоб пристрій був встановлений з нахилом не " +"більше 4° вліво або вправо та не більше 5° вгору або 9° вниз. openpilot " +"постійно калібрується, тому скидання калібрування потрібне рідко." + +#: selfdrive/ui/layouts/settings/device.py:134 +#, python-format +msgid "right" +msgstr "вправо" + +#: system/ui/widgets/network.py:142 +#, python-format +msgid "unmetered" +msgstr "необмеж." + +#: selfdrive/ui/layouts/settings/device.py:133 +#, python-format +msgid "up" +msgstr "вгору" + +#: selfdrive/ui/layouts/settings/software.py:125 +#, python-format +msgid "up to date, last checked never" +msgstr "оновлено, ніколи не перевірялось" + +#: selfdrive/ui/layouts/settings/software.py:123 +#, python-format +msgid "up to date, last checked {}" +msgstr "оновлено, перевірив {}" + +#: selfdrive/ui/layouts/settings/software.py:117 +#, python-format +msgid "update available" +msgstr "доступне оновлення" + +#: selfdrive/ui/layouts/home.py:169 +#, python-format +msgid "{} ALERT" +msgid_plural "{} ALERTS" +msgstr[0] "{} СПОВІЩЕННЯ" +msgstr[1] "{} СПОВІЩЕННЯ" +msgstr[2] "{} СПОВІЩЕНЬ" + +#: selfdrive/ui/layouts/settings/software.py:40 +#, python-format +msgid "{} day ago" +msgid_plural "{} days ago" +msgstr[0] "{} день тому" +msgstr[1] "{} дні тому" +msgstr[2] "{} днів тому" + +#: selfdrive/ui/layouts/settings/software.py:37 +#, python-format +msgid "{} hour ago" +msgid_plural "{} hours ago" +msgstr[0] "{} година тому" +msgstr[1] "{} години тому" +msgstr[2] "{} годин тому" + +#: selfdrive/ui/layouts/settings/software.py:34 +#, python-format +msgid "{} minute ago" +msgid_plural "{} minutes ago" +msgstr[0] "{} хвилина тому" +msgstr[1] "{} хвилини тому" +msgstr[2] "{} хвилин тому" + +#: selfdrive/ui/layouts/settings/firehose.py:111 +#, python-format +msgid "{} segment of your driving is in the training dataset so far." +msgid_plural "{} segments of your driving is in the training dataset so far." +msgstr[0] "" +"{} сегмент вашого водіння на даний момент містяться в тренувальному наборі " +"даних." +msgstr[1] "" +"{} сегменти вашого водіння на даний момент містяться в тренувальному наборі " +"даних." +msgstr[2] "" +"{} сегментів вашого водіння на даний момент містяться в тренувальному наборі " +"даних." + +#: selfdrive/ui/widgets/prime.py:62 +#, python-format +msgid "✓ SUBSCRIBED" +msgstr "✓ ПІДПИСАНО" + +#: selfdrive/ui/widgets/setup.py:22 +#, python-format +msgid "🔥 Firehose Mode 🔥" +msgstr "🌧️ Режим зливи 🌧️" diff --git a/selfdrive/ui/translations/languages.json b/selfdrive/ui/translations/languages.json index b0674dee8..47e673ce8 100644 --- a/selfdrive/ui/translations/languages.json +++ b/selfdrive/ui/translations/languages.json @@ -5,6 +5,7 @@ "Português": "pt-BR", "Español": "es", "Türkçe": "tr", + "Українська": "uk", "العربية": "ar", "ไทย": "th", "中文(繁體)": "zh-CHT", From 7119412d35e9b1a425db7eeb6cadc26d2caa9eb7 Mon Sep 17 00:00:00 2001 From: Matt Purnell <65473602+mpurnell1@users.noreply.github.com> Date: Mon, 8 Dec 2025 18:41:45 -0600 Subject: [PATCH 560/910] updated: fix skipped test case (#36786) Fix three failing tests --- system/updated/tests/test_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/updated/tests/test_base.py b/system/updated/tests/test_base.py index 699a0f0bd..c4894f271 100644 --- a/system/updated/tests/test_base.py +++ b/system/updated/tests/test_base.py @@ -133,7 +133,7 @@ class TestBaseUpdate: class ParamsBaseUpdateTest(TestBaseUpdate): def _test_finalized_update(self, branch, version, agnos_version, release_notes): assert self.params.get("UpdaterNewDescription").startswith(f"{version} / {branch}") - assert self.params.get("UpdaterNewReleaseNotes") == f"{release_notes}\n" + assert self.params.get("UpdaterNewReleaseNotes") == f"{release_notes}\n".encode() super()._test_finalized_update(branch, version, agnos_version, release_notes) def send_check_for_updates_signal(self, updated: ManagerProcess): From fb807cc007b32787b726d505109ba7b47eb1b45e Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 8 Dec 2025 18:39:47 -0800 Subject: [PATCH 561/910] ui: video diff tool (#36737) * video diff * format * duplicate * try * WINDOWED * ? * correct res * Revert "correct res" This reverts commit f90991192fce93a31d1b581a4f0ff93a7a972337. * save to report/ * add duplicate * work? * fix * more * more * and this * ffmpeg * branch * uncmt * test preview * Revert "uncmt" This reverts commit b02404dbbe515fd861717f831c7bb0243442ddbc. * create openpilot_master_ui_mici_raylib * ahh * push to master * copy and always run * test * does cmt break it? * who did this * fix? * fix that * hmm * hmm * ah this was moving it, and then the job below didn't run on master * google ai overview lied to me * use markdown to start * need to add to one branch * ???? * oof * no * this work? * test * try this * clean up master branch name * more cleanup more cleanup * don't fail for no diff! don't fail for no diff! * back * add to cmt * test it * should work * fix that * back * clean up * clean up * save to report * pull_request_target * sort --------- Co-authored-by: Shane Smiskol --- .github/workflows/mici_raylib_ui_preview.yaml | 151 +++++++++++++ .github/workflows/tests.yaml | 26 +++ pyproject.toml | 1 + selfdrive/ui/tests/.gitignore | 5 + selfdrive/ui/tests/diff/diff.py | 201 ++++++++++++++++++ selfdrive/ui/tests/diff/replay.py | 97 +++++++++ uv.lock | 41 +++- 7 files changed, 520 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/mici_raylib_ui_preview.yaml create mode 100755 selfdrive/ui/tests/diff/diff.py create mode 100755 selfdrive/ui/tests/diff/replay.py diff --git a/.github/workflows/mici_raylib_ui_preview.yaml b/.github/workflows/mici_raylib_ui_preview.yaml new file mode 100644 index 000000000..707825b1a --- /dev/null +++ b/.github/workflows/mici_raylib_ui_preview.yaml @@ -0,0 +1,151 @@ +name: "mici raylib ui preview" +on: + push: + branches: + - master + pull_request_target: + types: [assigned, opened, synchronize, reopened, edited] + branches: + - 'master' + paths: + - 'selfdrive/assets/**' + - 'selfdrive/ui/**' + - 'system/ui/**' + workflow_dispatch: + +env: + UI_JOB_NAME: "Create mici raylib UI Report" + REPORT_NAME: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && 'master' || github.event.number }} + SHA: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && github.sha || github.event.pull_request.head.sha }} + BRANCH_NAME: "openpilot/pr-${{ github.event.number }}-mici-raylib-ui" + MASTER_BRANCH_NAME: "openpilot_master_ui_mici_raylib" + # All report files are pushed here + REPORT_FILES_BRANCH_NAME: "mici-raylib-ui-reports" + +jobs: + preview: + if: github.repository == 'commaai/openpilot' + name: preview + runs-on: ubuntu-latest + timeout-minutes: 20 + permissions: + contents: read + pull-requests: write + actions: read + steps: + - uses: actions/checkout@v4 + with: + submodules: true + + - name: Waiting for ui generation to end + uses: lewagon/wait-on-check-action@v1.3.4 + with: + ref: ${{ env.SHA }} + check-name: ${{ env.UI_JOB_NAME }} + repo-token: ${{ secrets.GITHUB_TOKEN }} + allowed-conclusions: success + wait-interval: 20 + + - name: Getting workflow run ID + id: get_run_id + run: | + echo "run_id=$(curl https://api.github.com/repos/${{ github.repository }}/commits/${{ env.SHA }}/check-runs | jq -r '.check_runs[] | select(.name == "${{ env.UI_JOB_NAME }}") | .html_url | capture("(?[0-9]+)") | .number')" >> $GITHUB_OUTPUT + + - name: Getting proposed ui # filename: pr_ui/mici_ui_replay.mp4 + id: download-artifact + uses: dawidd6/action-download-artifact@v6 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + run_id: ${{ steps.get_run_id.outputs.run_id }} + search_artifacts: true + name: mici-raylib-report-1-${{ env.REPORT_NAME }} + path: ${{ github.workspace }}/pr_ui + + - name: Getting master ui # filename: master_ui_raylib/mici_ui_replay.mp4 + uses: actions/checkout@v4 + with: + repository: commaai/ci-artifacts + ssh-key: ${{ secrets.CI_ARTIFACTS_DEPLOY_KEY }} + path: ${{ github.workspace }}/master_ui_raylib + ref: ${{ env.MASTER_BRANCH_NAME }} + + - name: Saving new master ui + if: github.ref == 'refs/heads/master' && github.event_name == 'push' + working-directory: ${{ github.workspace }}/master_ui_raylib + run: | + git checkout --orphan=new_master_ui_mici_raylib + git rm -rf * + git branch -D ${{ env.MASTER_BRANCH_NAME }} + git branch -m ${{ env.MASTER_BRANCH_NAME }} + git config user.name "GitHub Actions Bot" + git config user.email "<>" + mv ${{ github.workspace }}/pr_ui/* . + git add . + git commit -m "mici raylib video for commit ${{ env.SHA }}" + git push origin ${{ env.MASTER_BRANCH_NAME }} --force + + - name: Setup FFmpeg + uses: AnimMouse/setup-ffmpeg@ae28d57dabbb148eff63170b6bf7f2b60062cbae + + - name: Finding diff + if: github.event_name == 'pull_request_target' + id: find_diff + run: | + # Find the video file from PR + pr_video="${{ github.workspace }}/pr_ui/mici_ui_replay_proposed.mp4" + mv "${{ github.workspace }}/pr_ui/mici_ui_replay.mp4" "$pr_video" + + master_video="${{ github.workspace }}/pr_ui/mici_ui_replay_master.mp4" + mv "${{ github.workspace }}/master_ui_raylib/mici_ui_replay.mp4" "$master_video" + + # Run report + export PYTHONPATH=${{ github.workspace }} + baseurl="https://github.com/commaai/ci-artifacts/raw/refs/heads/${{ env.BRANCH_NAME }}" + diff_exit_code=0 + python3 ${{ github.workspace }}/selfdrive/ui/tests/diff/diff.py "${{ github.workspace }}/pr_ui/mici_ui_replay_master.mp4" "${{ github.workspace }}/pr_ui/mici_ui_replay_proposed.mp4" "diff.html" --basedir "$baseurl" --no-open || diff_exit_code=$? + + # Copy diff report files + cp ${{ github.workspace }}/selfdrive/ui/tests/diff/report/diff.html ${{ github.workspace }}/pr_ui/ + cp ${{ github.workspace }}/selfdrive/ui/tests/diff/report/diff.mp4 ${{ github.workspace }}/pr_ui/ + + REPORT_URL="https://commaai.github.io/ci-artifacts/diff_pr_${{ github.event.number }}.html" + if [ $diff_exit_code -eq 0 ]; then + DIFF="✅ Videos are identical! [View Diff Report]($REPORT_URL)" + else + DIFF="❌ Videos differ! [View Diff Report]($REPORT_URL)" + fi + echo "DIFF=$DIFF" >> "$GITHUB_OUTPUT" + + - name: Saving proposed ui + if: github.event_name == 'pull_request_target' + working-directory: ${{ github.workspace }}/master_ui_raylib + run: | + # Overwrite PR branch w/ proposed ui, and master ui at this point in time for future reference + git config user.name "GitHub Actions Bot" + git config user.email "<>" + git checkout --orphan=${{ env.BRANCH_NAME }} + git rm -rf * + mv ${{ github.workspace }}/pr_ui/* . + git add . + git commit -m "mici raylib video for PR #${{ github.event.number }}" + git push origin ${{ env.BRANCH_NAME }} --force + + # Append diff report to report files branch + git fetch origin ${{ env.REPORT_FILES_BRANCH_NAME }} + git checkout ${{ env.REPORT_FILES_BRANCH_NAME }} + cp ${{ github.workspace }}/selfdrive/ui/tests/diff/report/diff.html diff_pr_${{ github.event.number }}.html + git add diff_pr_${{ github.event.number }}.html + git commit -m "mici raylib ui diff report for PR #${{ github.event.number }}" || echo "No changes to commit" + git push origin ${{ env.REPORT_FILES_BRANCH_NAME }} + + - name: Comment Video on PR + if: github.event_name == 'pull_request_target' + uses: thollander/actions-comment-pull-request@v2 + with: + message: | + + ## mici raylib UI Preview + ${{ steps.find_diff.outputs.DIFF }} + comment_tag: run_id_video_mici_raylib + pr_number: ${{ github.event.number }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 5ca020424..c5802b5cb 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -265,3 +265,29 @@ jobs: with: name: raylib-report-${{ inputs.run_number || '1' }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && 'master' || github.event.number }} path: selfdrive/ui/tests/test_ui/raylib_report/screenshots + + create_mici_raylib_ui_report: + name: Create mici raylib UI Report + runs-on: ${{ + (github.repository == 'commaai/openpilot') && + ((github.event_name != 'pull_request') || + (github.event.pull_request.head.repo.full_name == 'commaai/openpilot')) + && fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]') + || fromJSON('["ubuntu-24.04"]') }} + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - uses: ./.github/workflows/setup-with-retry + - name: Build openpilot + run: ${{ env.RUN }} "scons -j$(nproc)" + - name: Create mici raylib UI Report + run: > + ${{ env.RUN }} "PYTHONWARNINGS=ignore && + source selfdrive/test/setup_xvfb.sh && + WINDOWED=1 python3 selfdrive/ui/tests/diff/replay.py" + - name: Upload Raylib UI Report + uses: actions/upload-artifact@v4 + with: + name: mici-raylib-report-${{ inputs.run_number || '1' }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && 'master' || github.event.number }} + path: selfdrive/ui/tests/diff/report diff --git a/pyproject.toml b/pyproject.toml index b2acf1c09..b45a808f1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -85,6 +85,7 @@ docs = [ ] testing = [ + "coverage", "hypothesis ==6.47.*", "mypy", "pytest", diff --git a/selfdrive/ui/tests/.gitignore b/selfdrive/ui/tests/.gitignore index d926a7ae8..98f2a5e8c 100644 --- a/selfdrive/ui/tests/.gitignore +++ b/selfdrive/ui/tests/.gitignore @@ -2,3 +2,8 @@ test test_translations test_ui/report_1 test_ui/raylib_report + +diff/*.mp4 +diff/*.html +diff/.coverage +diff/htmlcov/ diff --git a/selfdrive/ui/tests/diff/diff.py b/selfdrive/ui/tests/diff/diff.py new file mode 100755 index 000000000..be7af5438 --- /dev/null +++ b/selfdrive/ui/tests/diff/diff.py @@ -0,0 +1,201 @@ +#!/usr/bin/env python3 +import os +import sys +import subprocess +import tempfile +import base64 +import webbrowser +import argparse +from pathlib import Path +from openpilot.common.basedir import BASEDIR + +DIFF_OUT_DIR = Path(BASEDIR) / "selfdrive" / "ui" / "tests" / "diff" / "report" + + +def extract_frames(video_path, output_dir): + output_pattern = str(output_dir / "frame_%04d.png") + cmd = ['ffmpeg', '-i', video_path, '-vsync', '0', output_pattern, '-y'] + subprocess.run(cmd, capture_output=True, check=True) + frames = sorted(output_dir.glob("frame_*.png")) + return frames + + +def compare_frames(frame1_path, frame2_path): + result = subprocess.run(['cmp', '-s', frame1_path, frame2_path]) + return result.returncode == 0 + + +def frame_to_data_url(frame_path): + with open(frame_path, 'rb') as f: + data = f.read() + return f"data:image/png;base64,{base64.b64encode(data).decode()}" + + +def create_diff_video(video1, video2, output_path): + """Create a diff video using ffmpeg blend filter with difference mode.""" + print("Creating diff video...") + cmd = ['ffmpeg', '-i', video1, '-i', video2, '-filter_complex', '[0:v]blend=all_mode=difference', '-vsync', '0', '-y', output_path] + subprocess.run(cmd, capture_output=True, check=True) + + +def find_differences(video1, video2): + with tempfile.TemporaryDirectory() as tmpdir: + tmpdir = Path(tmpdir) + + print(f"Extracting frames from {video1}...") + frames1_dir = tmpdir / "frames1" + frames1_dir.mkdir() + frames1 = extract_frames(video1, frames1_dir) + + print(f"Extracting frames from {video2}...") + frames2_dir = tmpdir / "frames2" + frames2_dir.mkdir() + frames2 = extract_frames(video2, frames2_dir) + + if len(frames1) != len(frames2): + print(f"WARNING: Frame count mismatch: {len(frames1)} vs {len(frames2)}") + min_frames = min(len(frames1), len(frames2)) + frames1 = frames1[:min_frames] + frames2 = frames2[:min_frames] + + print(f"Comparing {len(frames1)} frames...") + different_frames = [] + frame_data = [] + + for i, (f1, f2) in enumerate(zip(frames1, frames2, strict=False)): + is_different = not compare_frames(f1, f2) + if is_different: + different_frames.append(i) + + if i < 10 or i >= len(frames1) - 10 or is_different: + frame_data.append({'index': i, 'different': is_different, 'frame1_url': frame_to_data_url(f1), 'frame2_url': frame_to_data_url(f2)}) + + return different_frames, frame_data, len(frames1) + + +def generate_html_report(video1, video2, basedir, different_frames, frame_data, total_frames): + chunks = [] + if different_frames: + current_chunk = [different_frames[0]] + for i in range(1, len(different_frames)): + if different_frames[i] == different_frames[i - 1] + 1: + current_chunk.append(different_frames[i]) + else: + chunks.append(current_chunk) + current_chunk = [different_frames[i]] + chunks.append(current_chunk) + + result_text = ( + f"✅ Videos are identical! ({total_frames} frames)" + if len(different_frames) == 0 + else f"❌ Found {len(different_frames)} different frames out of {total_frames} total ({(len(different_frames) / total_frames * 100):.1f}%)" + ) + + html = f"""

UI Diff

+ + + + + + +
+

Video 1

+ +
+

Video 2

+ +
+

Pixel Diff

+ +
+ +
+

Results: {result_text}

+""" + return html + + +def main(): + parser = argparse.ArgumentParser(description='Compare two videos and generate HTML diff report') + parser.add_argument('video1', help='First video file') + parser.add_argument('video2', help='Second video file') + parser.add_argument('output', nargs='?', default='diff.html', help='Output HTML file (default: diff.html)') + parser.add_argument("--basedir", type=str, help="Base directory for output", default="") + parser.add_argument('--no-open', action='store_true', help='Do not open HTML report in browser') + + args = parser.parse_args() + + os.makedirs(DIFF_OUT_DIR, exist_ok=True) + + print("=" * 60) + print("VIDEO DIFF - HTML REPORT") + print("=" * 60) + print(f"Video 1: {args.video1}") + print(f"Video 2: {args.video2}") + print(f"Output: {args.output}") + print() + + # Create diff video + diff_video_path = os.path.join(os.path.dirname(args.output), DIFF_OUT_DIR / "diff.mp4") + create_diff_video(args.video1, args.video2, diff_video_path) + + different_frames, frame_data, total_frames = find_differences(args.video1, args.video2) + + if different_frames is None: + sys.exit(1) + + print() + print("Generating HTML report...") + html = generate_html_report(args.video1, args.video2, args.basedir, different_frames, frame_data, total_frames) + + with open(DIFF_OUT_DIR / args.output, 'w') as f: + f.write(html) + + # Open in browser by default + if not args.no_open: + print(f"Opening {args.output} in browser...") + webbrowser.open(f'file://{os.path.abspath(DIFF_OUT_DIR / args.output)}') + + return 0 if len(different_frames) == 0 else 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/selfdrive/ui/tests/diff/replay.py b/selfdrive/ui/tests/diff/replay.py new file mode 100755 index 000000000..44df75fa5 --- /dev/null +++ b/selfdrive/ui/tests/diff/replay.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 +import os +import time +import coverage +import pyray as rl +from openpilot.selfdrive.ui.tests.diff.diff import DIFF_OUT_DIR + +os.environ["RECORD"] = "1" +if "RECORD_OUTPUT" not in os.environ: + os.environ["RECORD_OUTPUT"] = "mici_ui_replay.mp4" + +os.environ["RECORD_OUTPUT"] = os.path.join(DIFF_OUT_DIR, os.environ["RECORD_OUTPUT"]) + +from openpilot.common.params import Params +from openpilot.system.version import terms_version, training_version +from openpilot.system.ui.lib.application import gui_app, MousePos, MouseEvent +from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.selfdrive.ui.mici.layouts.main import MiciMainLayout + +FPS = 60 +HEADLESS = os.getenv("WINDOWED", "0") == "1" + +SCRIPT = [ + (0, None), + (FPS * 1, (100, 100)), + (FPS * 2, None), +] + + +def setup_state(): + params = Params() + params.put("HasAcceptedTerms", terms_version) + params.put("CompletedTrainingVersion", training_version) + params.put("DongleId", "test123456789") + params.put("UpdaterCurrentDescription", "0.10.1 / test-branch / abc1234 / Nov 30") + return None + + +def inject_click(x, y): + press_event = MouseEvent(pos=MousePos(x, y), slot=0, left_pressed=True, left_released=False, left_down=True, t=time.monotonic()) + + release_event = MouseEvent(pos=MousePos(x, y), slot=0, left_pressed=False, left_released=True, left_down=False, t=time.monotonic()) + + with gui_app._mouse._lock: + gui_app._mouse._events.append(press_event) + gui_app._mouse._events.append(release_event) + + +def run_replay(): + setup_state() + os.makedirs(DIFF_OUT_DIR, exist_ok=True) + + if not HEADLESS: + rl.set_config_flags(rl.FLAG_WINDOW_HIDDEN) + gui_app.init_window("ui diff test", fps=FPS) + main_layout = MiciMainLayout() + main_layout.set_rect(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) + + frame = 0 + script_index = 0 + + for should_render in gui_app.render(): + while script_index < len(SCRIPT) and SCRIPT[script_index][0] == frame: + _, coords = SCRIPT[script_index] + if coords is not None: + inject_click(*coords) + script_index += 1 + + ui_state.update() + + if should_render: + main_layout.render() + + frame += 1 + + if script_index >= len(SCRIPT): + break + + gui_app.close() + + print(f"Total frames: {frame}") + print(f"Video saved to: {os.environ['RECORD_OUTPUT']}") + + +def main(): + cov = coverage.coverage(source=['openpilot.selfdrive.ui.mici']) + with cov.collect(): + run_replay() + cov.stop() + cov.save() + cov.report() + cov.html_report(directory=os.path.join(DIFF_OUT_DIR, 'htmlcov')) + print("HTML report: htmlcov/index.html") + + +if __name__ == "__main__": + main() diff --git a/uv.lock b/uv.lock index c34a6d9b7..650220c87 100644 --- a/uv.lock +++ b/uv.lock @@ -371,6 +371,41 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0c/58/bd257695f39d05594ca4ad60df5bcb7e32247f9951fd09a9b8edb82d1daa/contourpy-1.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:3d1a3799d62d45c18bafd41c5fa05120b96a28079f2393af559b843d1a966a77", size = 225315, upload-time = "2025-07-26T12:02:58.801Z" }, ] +[[package]] +name = "coverage" +version = "7.12.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/89/26/4a96807b193b011588099c3b5c89fbb05294e5b90e71018e065465f34eb6/coverage-7.12.0.tar.gz", hash = "sha256:fc11e0a4e372cb5f282f16ef90d4a585034050ccda536451901abfb19a57f40c", size = 819341, upload-time = "2025-11-18T13:34:20.766Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/0c/0dfe7f0487477d96432e4815537263363fb6dd7289743a796e8e51eabdf2/coverage-7.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aa124a3683d2af98bd9d9c2bfa7a5076ca7e5ab09fdb96b81fa7d89376ae928f", size = 217535, upload-time = "2025-11-18T13:32:08.812Z" }, + { url = "https://files.pythonhosted.org/packages/9b/f5/f9a4a053a5bbff023d3bec259faac8f11a1e5a6479c2ccf586f910d8dac7/coverage-7.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d93fbf446c31c0140208dcd07c5d882029832e8ed7891a39d6d44bd65f2316c3", size = 218044, upload-time = "2025-11-18T13:32:10.329Z" }, + { url = "https://files.pythonhosted.org/packages/95/c5/84fc3697c1fa10cd8571919bf9693f693b7373278daaf3b73e328d502bc8/coverage-7.12.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:52ca620260bd8cd6027317bdd8b8ba929be1d741764ee765b42c4d79a408601e", size = 248440, upload-time = "2025-11-18T13:32:12.536Z" }, + { url = "https://files.pythonhosted.org/packages/f4/36/2d93fbf6a04670f3874aed397d5a5371948a076e3249244a9e84fb0e02d6/coverage-7.12.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f3433ffd541380f3a0e423cff0f4926d55b0cc8c1d160fdc3be24a4c03aa65f7", size = 250361, upload-time = "2025-11-18T13:32:13.852Z" }, + { url = "https://files.pythonhosted.org/packages/5d/49/66dc65cc456a6bfc41ea3d0758c4afeaa4068a2b2931bf83be6894cf1058/coverage-7.12.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f7bbb321d4adc9f65e402c677cd1c8e4c2d0105d3ce285b51b4d87f1d5db5245", size = 252472, upload-time = "2025-11-18T13:32:15.068Z" }, + { url = "https://files.pythonhosted.org/packages/35/1f/ebb8a18dffd406db9fcd4b3ae42254aedcaf612470e8712f12041325930f/coverage-7.12.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:22a7aade354a72dff3b59c577bfd18d6945c61f97393bc5fb7bd293a4237024b", size = 248592, upload-time = "2025-11-18T13:32:16.328Z" }, + { url = "https://files.pythonhosted.org/packages/da/a8/67f213c06e5ea3b3d4980df7dc344d7fea88240b5fe878a5dcbdfe0e2315/coverage-7.12.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3ff651dcd36d2fea66877cd4a82de478004c59b849945446acb5baf9379a1b64", size = 250167, upload-time = "2025-11-18T13:32:17.687Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/e52aef68154164ea40cc8389c120c314c747fe63a04b013a5782e989b77f/coverage-7.12.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:31b8b2e38391a56e3cea39d22a23faaa7c3fc911751756ef6d2621d2a9daf742", size = 248238, upload-time = "2025-11-18T13:32:19.2Z" }, + { url = "https://files.pythonhosted.org/packages/1f/a4/4d88750bcf9d6d66f77865e5a05a20e14db44074c25fd22519777cb69025/coverage-7.12.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:297bc2da28440f5ae51c845a47c8175a4db0553a53827886e4fb25c66633000c", size = 247964, upload-time = "2025-11-18T13:32:21.027Z" }, + { url = "https://files.pythonhosted.org/packages/a7/6b/b74693158899d5b47b0bf6238d2c6722e20ba749f86b74454fac0696bb00/coverage-7.12.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6ff7651cc01a246908eac162a6a86fc0dbab6de1ad165dfb9a1e2ec660b44984", size = 248862, upload-time = "2025-11-18T13:32:22.304Z" }, + { url = "https://files.pythonhosted.org/packages/18/de/6af6730227ce0e8ade307b1cc4a08e7f51b419a78d02083a86c04ccceb29/coverage-7.12.0-cp311-cp311-win32.whl", hash = "sha256:313672140638b6ddb2c6455ddeda41c6a0b208298034544cfca138978c6baed6", size = 220033, upload-time = "2025-11-18T13:32:23.714Z" }, + { url = "https://files.pythonhosted.org/packages/e2/a1/e7f63021a7c4fe20994359fcdeae43cbef4a4d0ca36a5a1639feeea5d9e1/coverage-7.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:a1783ed5bd0d5938d4435014626568dc7f93e3cb99bc59188cc18857c47aa3c4", size = 220966, upload-time = "2025-11-18T13:32:25.599Z" }, + { url = "https://files.pythonhosted.org/packages/77/e8/deae26453f37c20c3aa0c4433a1e32cdc169bf415cce223a693117aa3ddd/coverage-7.12.0-cp311-cp311-win_arm64.whl", hash = "sha256:4648158fd8dd9381b5847622df1c90ff314efbfc1df4550092ab6013c238a5fc", size = 219637, upload-time = "2025-11-18T13:32:27.265Z" }, + { url = "https://files.pythonhosted.org/packages/02/bf/638c0427c0f0d47638242e2438127f3c8ee3cfc06c7fdeb16778ed47f836/coverage-7.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:29644c928772c78512b48e14156b81255000dcfd4817574ff69def189bcb3647", size = 217704, upload-time = "2025-11-18T13:32:28.906Z" }, + { url = "https://files.pythonhosted.org/packages/08/e1/706fae6692a66c2d6b871a608bbde0da6281903fa0e9f53a39ed441da36a/coverage-7.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8638cbb002eaa5d7c8d04da667813ce1067080b9a91099801a0053086e52b736", size = 218064, upload-time = "2025-11-18T13:32:30.161Z" }, + { url = "https://files.pythonhosted.org/packages/a9/8b/eb0231d0540f8af3ffda39720ff43cb91926489d01524e68f60e961366e4/coverage-7.12.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:083631eeff5eb9992c923e14b810a179798bb598e6a0dd60586819fc23be6e60", size = 249560, upload-time = "2025-11-18T13:32:31.835Z" }, + { url = "https://files.pythonhosted.org/packages/e9/a1/67fb52af642e974d159b5b379e4d4c59d0ebe1288677fbd04bbffe665a82/coverage-7.12.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:99d5415c73ca12d558e07776bd957c4222c687b9f1d26fa0e1b57e3598bdcde8", size = 252318, upload-time = "2025-11-18T13:32:33.178Z" }, + { url = "https://files.pythonhosted.org/packages/41/e5/38228f31b2c7665ebf9bdfdddd7a184d56450755c7e43ac721c11a4b8dab/coverage-7.12.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e949ebf60c717c3df63adb4a1a366c096c8d7fd8472608cd09359e1bd48ef59f", size = 253403, upload-time = "2025-11-18T13:32:34.45Z" }, + { url = "https://files.pythonhosted.org/packages/ec/4b/df78e4c8188f9960684267c5a4897836f3f0f20a20c51606ee778a1d9749/coverage-7.12.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6d907ddccbca819afa2cd014bc69983b146cca2735a0b1e6259b2a6c10be1e70", size = 249984, upload-time = "2025-11-18T13:32:35.747Z" }, + { url = "https://files.pythonhosted.org/packages/ba/51/bb163933d195a345c6f63eab9e55743413d064c291b6220df754075c2769/coverage-7.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b1518ecbad4e6173f4c6e6c4a46e49555ea5679bf3feda5edb1b935c7c44e8a0", size = 251339, upload-time = "2025-11-18T13:32:37.352Z" }, + { url = "https://files.pythonhosted.org/packages/15/40/c9b29cdb8412c837cdcbc2cfa054547dd83affe6cbbd4ce4fdb92b6ba7d1/coverage-7.12.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:51777647a749abdf6f6fd8c7cffab12de68ab93aab15efc72fbbb83036c2a068", size = 249489, upload-time = "2025-11-18T13:32:39.212Z" }, + { url = "https://files.pythonhosted.org/packages/c8/da/b3131e20ba07a0de4437a50ef3b47840dfabf9293675b0cd5c2c7f66dd61/coverage-7.12.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:42435d46d6461a3b305cdfcad7cdd3248787771f53fe18305548cba474e6523b", size = 249070, upload-time = "2025-11-18T13:32:40.598Z" }, + { url = "https://files.pythonhosted.org/packages/70/81/b653329b5f6302c08d683ceff6785bc60a34be9ae92a5c7b63ee7ee7acec/coverage-7.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5bcead88c8423e1855e64b8057d0544e33e4080b95b240c2a355334bb7ced937", size = 250929, upload-time = "2025-11-18T13:32:42.915Z" }, + { url = "https://files.pythonhosted.org/packages/a3/00/250ac3bca9f252a5fb1338b5ad01331ebb7b40223f72bef5b1b2cb03aa64/coverage-7.12.0-cp312-cp312-win32.whl", hash = "sha256:dcbb630ab034e86d2a0f79aefd2be07e583202f41e037602d438c80044957baa", size = 220241, upload-time = "2025-11-18T13:32:44.665Z" }, + { url = "https://files.pythonhosted.org/packages/64/1c/77e79e76d37ce83302f6c21980b45e09f8aa4551965213a10e62d71ce0ab/coverage-7.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:2fd8354ed5d69775ac42986a691fbf68b4084278710cee9d7c3eaa0c28fa982a", size = 221051, upload-time = "2025-11-18T13:32:46.008Z" }, + { url = "https://files.pythonhosted.org/packages/31/f5/641b8a25baae564f9e52cac0e2667b123de961985709a004e287ee7663cc/coverage-7.12.0-cp312-cp312-win_arm64.whl", hash = "sha256:737c3814903be30695b2de20d22bcc5428fdae305c61ba44cdc8b3252984c49c", size = 219692, upload-time = "2025-11-18T13:32:47.372Z" }, + { url = "https://files.pythonhosted.org/packages/ce/a3/43b749004e3c09452e39bb56347a008f0a0668aad37324a99b5c8ca91d9e/coverage-7.12.0-py3-none-any.whl", hash = "sha256:159d50c0b12e060b15ed3d39f87ed43d4f7f7ad40b8a534f4dd331adbb51104a", size = 209503, upload-time = "2025-11-18T13:34:18.892Z" }, +] + [[package]] name = "crcmod" version = "1.7" @@ -1349,6 +1384,7 @@ docs = [ ] testing = [ { name = "codespell" }, + { name = "coverage" }, { name = "hypothesis" }, { name = "mypy" }, { name = "pre-commit-hooks" }, @@ -1364,7 +1400,7 @@ testing = [ { name = "ruff" }, ] tools = [ - { name = "dearpygui" }, + { name = "dearpygui", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, { name = "metadrive-simulator", marker = "platform_machine != 'aarch64'" }, ] @@ -1378,10 +1414,11 @@ requires-dist = [ { name = "casadi", specifier = ">=3.6.6" }, { name = "cffi" }, { name = "codespell", marker = "extra == 'testing'" }, + { name = "coverage", marker = "extra == 'testing'" }, { name = "crcmod" }, { name = "cython" }, { name = "dbus-next", marker = "extra == 'dev'" }, - { name = "dearpygui", marker = "extra == 'tools'", specifier = ">=2.1.0" }, + { name = "dearpygui", marker = "(platform_machine != 'aarch64' and extra == 'tools') or (sys_platform != 'linux' and extra == 'tools')", specifier = ">=2.1.0" }, { name = "dictdiffer", marker = "extra == 'dev'" }, { name = "future-fstrings" }, { name = "hypothesis", marker = "extra == 'testing'", specifier = "==6.47.*" }, From 48a42a9c53b894da327f832c527be4f4c80a5702 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=80=8C=20crwusiz=20=E3=80=8D?= <43285072+crwusiz@users.noreply.github.com> Date: Tue, 9 Dec 2025 11:43:31 +0900 Subject: [PATCH 562/910] UI: Color Constants Uppercase (#36796) --- selfdrive/ui/mici/onroad/hud_renderer.py | 8 ++-- selfdrive/ui/onroad/hud_renderer.py | 52 ++++++++++++------------ 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/selfdrive/ui/mici/onroad/hud_renderer.py b/selfdrive/ui/mici/onroad/hud_renderer.py index 76b443842..7f489ccf9 100644 --- a/selfdrive/ui/mici/onroad/hud_renderer.py +++ b/selfdrive/ui/mici/onroad/hud_renderer.py @@ -30,8 +30,8 @@ class FontSizes: @dataclass(frozen=True) class Colors: - white: rl.Color = rl.WHITE - white_translucent: rl.Color = rl.Color(255, 255, 255, 200) + WHITE = rl.WHITE + WHITE_TRANSLUCENT = rl.Color(255, 255, 255, 200) FONT_SIZES = FontSizes() @@ -269,9 +269,9 @@ class HudRenderer(Widget): speed_text = str(round(self.speed)) speed_text_size = measure_text_cached(self._font_bold, speed_text, FONT_SIZES.current_speed) speed_pos = rl.Vector2(rect.x + rect.width / 2 - speed_text_size.x / 2, 180 - speed_text_size.y / 2) - rl.draw_text_ex(self._font_bold, speed_text, speed_pos, FONT_SIZES.current_speed, 0, COLORS.white) + rl.draw_text_ex(self._font_bold, speed_text, speed_pos, FONT_SIZES.current_speed, 0, COLORS.WHITE) unit_text = tr("km/h") if ui_state.is_metric else tr("mph") unit_text_size = measure_text_cached(self._font_medium, unit_text, FONT_SIZES.speed_unit) unit_pos = rl.Vector2(rect.x + rect.width / 2 - unit_text_size.x / 2, 290 - unit_text_size.y / 2) - rl.draw_text_ex(self._font_medium, unit_text, unit_pos, FONT_SIZES.speed_unit, 0, COLORS.white_translucent) + rl.draw_text_ex(self._font_medium, unit_text, unit_pos, FONT_SIZES.speed_unit, 0, COLORS.WHITE_TRANSLUCENT) diff --git a/selfdrive/ui/onroad/hud_renderer.py b/selfdrive/ui/onroad/hud_renderer.py index a2459c27e..79f150dee 100644 --- a/selfdrive/ui/onroad/hud_renderer.py +++ b/selfdrive/ui/onroad/hud_renderer.py @@ -35,20 +35,20 @@ class FontSizes: @dataclass(frozen=True) class Colors: - white: rl.Color = rl.WHITE - disengaged: rl.Color = rl.Color(145, 155, 149, 255) - override: rl.Color = rl.Color(145, 155, 149, 255) # Added - engaged: rl.Color = rl.Color(128, 216, 166, 255) - disengaged_bg: rl.Color = rl.Color(0, 0, 0, 153) - override_bg: rl.Color = rl.Color(145, 155, 149, 204) - engaged_bg: rl.Color = rl.Color(128, 216, 166, 204) - grey: rl.Color = rl.Color(166, 166, 166, 255) - dark_grey: rl.Color = rl.Color(114, 114, 114, 255) - black_translucent: rl.Color = rl.Color(0, 0, 0, 166) - white_translucent: rl.Color = rl.Color(255, 255, 255, 200) - border_translucent: rl.Color = rl.Color(255, 255, 255, 75) - header_gradient_start: rl.Color = rl.Color(0, 0, 0, 114) - header_gradient_end: rl.Color = rl.BLANK + WHITE = rl.WHITE + DISENGAGED = rl.Color(145, 155, 149, 255) + OVERRIDE = rl.Color(145, 155, 149, 255) # Added + ENGAGED = rl.Color(128, 216, 166, 255) + DISENGAGED_BG = rl.Color(0, 0, 0, 153) + OVERRIDE_BG = rl.Color(145, 155, 149, 204) + ENGAGED_BG = rl.Color(128, 216, 166, 204) + GREY = rl.Color(166, 166, 166, 255) + DARK_GREY = rl.Color(114, 114, 114, 255) + BLACK_TRANSLUCENT = rl.Color(0, 0, 0, 166) + WHITE_TRANSLUCENT = rl.Color(255, 255, 255, 200) + BORDER_TRANSLUCENT = rl.Color(255, 255, 255, 75) + HEADER_GRADIENT_START = rl.Color(0, 0, 0, 114) + HEADER_GRADIENT_END = rl.BLANK UI_CONFIG = UIConfig() @@ -108,8 +108,8 @@ class HudRenderer(Widget): int(rect.y), int(rect.width), UI_CONFIG.header_height, - COLORS.header_gradient_start, - COLORS.header_gradient_end, + COLORS.HEADER_GRADIENT_START, + COLORS.HEADER_GRADIENT_END, ) if self.is_cruise_available: @@ -131,19 +131,19 @@ class HudRenderer(Widget): y = rect.y + 45 set_speed_rect = rl.Rectangle(x, y, set_speed_width, UI_CONFIG.set_speed_height) - rl.draw_rectangle_rounded(set_speed_rect, 0.35, 10, COLORS.black_translucent) - rl.draw_rectangle_rounded_lines_ex(set_speed_rect, 0.35, 10, 6, COLORS.border_translucent) + rl.draw_rectangle_rounded(set_speed_rect, 0.35, 10, COLORS.BLACK_TRANSLUCENT) + rl.draw_rectangle_rounded_lines_ex(set_speed_rect, 0.35, 10, 6, COLORS.BORDER_TRANSLUCENT) - max_color = COLORS.grey - set_speed_color = COLORS.dark_grey + max_color = COLORS.GREY + set_speed_color = COLORS.DARK_GREY if self.is_cruise_set: - set_speed_color = COLORS.white + set_speed_color = COLORS.WHITE if ui_state.status == UIStatus.ENGAGED: - max_color = COLORS.engaged + max_color = COLORS.ENGAGED elif ui_state.status == UIStatus.DISENGAGED: - max_color = COLORS.disengaged + max_color = COLORS.DISENGAGED elif ui_state.status == UIStatus.OVERRIDE: - max_color = COLORS.override + max_color = COLORS.OVERRIDE max_text = tr("MAX") max_text_width = measure_text_cached(self._font_semi_bold, max_text, FONT_SIZES.max_speed).x @@ -172,9 +172,9 @@ class HudRenderer(Widget): speed_text = str(round(self.speed)) speed_text_size = measure_text_cached(self._font_bold, speed_text, FONT_SIZES.current_speed) speed_pos = rl.Vector2(rect.x + rect.width / 2 - speed_text_size.x / 2, 180 - speed_text_size.y / 2) - rl.draw_text_ex(self._font_bold, speed_text, speed_pos, FONT_SIZES.current_speed, 0, COLORS.white) + rl.draw_text_ex(self._font_bold, speed_text, speed_pos, FONT_SIZES.current_speed, 0, COLORS.WHITE) unit_text = tr("km/h") if ui_state.is_metric else tr("mph") unit_text_size = measure_text_cached(self._font_medium, unit_text, FONT_SIZES.speed_unit) unit_pos = rl.Vector2(rect.x + rect.width / 2 - unit_text_size.x / 2, 290 - unit_text_size.y / 2) - rl.draw_text_ex(self._font_medium, unit_text, unit_pos, FONT_SIZES.speed_unit, 0, COLORS.white_translucent) + rl.draw_text_ex(self._font_medium, unit_text, unit_pos, FONT_SIZES.speed_unit, 0, COLORS.WHITE_TRANSLUCENT) From d5f694650295e6a02efdb86e80194c2c6e751341 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 8 Dec 2025 18:48:58 -0800 Subject: [PATCH 563/910] camerad: probe os first --- system/camerad/cameras/spectra.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system/camerad/cameras/spectra.cc b/system/camerad/cameras/spectra.cc index caf787157..0d93b7046 100644 --- a/system/camerad/cameras/spectra.cc +++ b/system/camerad/cameras/spectra.cc @@ -1004,8 +1004,8 @@ bool SpectraCamera::openSensor() { }; // Figure out which sensor we have - if (!init_sensor_lambda(new OX03C10) && - !init_sensor_lambda(new OS04C10)) { + if (!init_sensor_lambda(new OS04C10) && + !init_sensor_lambda(new OX03C10)) { LOGE("** sensor %d FAILED bringup, disabling", cc.camera_num); enabled = false; return false; From 8d9e203130fa2c3c75af24955a628c278a694b83 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 8 Dec 2025 19:25:09 -0800 Subject: [PATCH 564/910] raylib ui diff: swipe around (#36807) * swipe support * swipe around * same --- selfdrive/ui/tests/diff/replay.py | 62 +++++++++++++++++++++++++------ 1 file changed, 50 insertions(+), 12 deletions(-) diff --git a/selfdrive/ui/tests/diff/replay.py b/selfdrive/ui/tests/diff/replay.py index 44df75fa5..cbb2d606e 100755 --- a/selfdrive/ui/tests/diff/replay.py +++ b/selfdrive/ui/tests/diff/replay.py @@ -3,6 +3,7 @@ import os import time import coverage import pyray as rl +from dataclasses import dataclass from openpilot.selfdrive.ui.tests.diff.diff import DIFF_OUT_DIR os.environ["RECORD"] = "1" @@ -20,10 +21,28 @@ from openpilot.selfdrive.ui.mici.layouts.main import MiciMainLayout FPS = 60 HEADLESS = os.getenv("WINDOWED", "0") == "1" + +@dataclass +class DummyEvent: + click: bool = False + # TODO: add some kind of intensity + swipe_left: bool = False + swipe_right: bool = False + swipe_down: bool = False + + SCRIPT = [ - (0, None), - (FPS * 1, (100, 100)), - (FPS * 2, None), + (0, DummyEvent()), + (FPS * 1, DummyEvent(swipe_right=True)), + (FPS * 2, DummyEvent(swipe_left=True)), + (FPS * 3, DummyEvent(swipe_left=True)), + (FPS * 4, DummyEvent(click=True)), + (FPS * 5, DummyEvent(click=True)), + (FPS * 6, DummyEvent(swipe_left=True)), + (FPS * 7, DummyEvent(swipe_left=True)), + (FPS * 8, DummyEvent(swipe_right=True)), + (FPS * 9, DummyEvent(swipe_down=True)), + (FPS * 10, DummyEvent()), ] @@ -36,14 +55,34 @@ def setup_state(): return None -def inject_click(x, y): - press_event = MouseEvent(pos=MousePos(x, y), slot=0, left_pressed=True, left_released=False, left_down=True, t=time.monotonic()) - - release_event = MouseEvent(pos=MousePos(x, y), slot=0, left_pressed=False, left_released=True, left_down=False, t=time.monotonic()) +def inject_click(coords): + events = [] + x, y = coords[0] + events.append(MouseEvent(pos=MousePos(x, y), slot=0, left_pressed=True, left_released=False, left_down=False, t=time.monotonic())) + for x, y in coords[1:]: + events.append(MouseEvent(pos=MousePos(x, y), slot=0, left_pressed=False, left_released=False, left_down=True, t=time.monotonic())) + x, y = coords[-1] + events.append(MouseEvent(pos=MousePos(x, y), slot=0, left_pressed=False, left_released=True, left_down=False, t=time.monotonic())) with gui_app._mouse._lock: - gui_app._mouse._events.append(press_event) - gui_app._mouse._events.append(release_event) + gui_app._mouse._events.extend(events) + + +def handle_event(event: DummyEvent): + if event.click: + inject_click([(gui_app.width // 2, gui_app.height // 2)]) + if event.swipe_left: + inject_click([(gui_app.width * 3 // 4, gui_app.height // 2), + (gui_app.width // 4, gui_app.height // 2), + (0, gui_app.height // 2)]) + if event.swipe_right: + inject_click([(gui_app.width // 4, gui_app.height // 2), + (gui_app.width * 3 // 4, gui_app.height // 2), + (gui_app.width, gui_app.height // 2)]) + if event.swipe_down: + inject_click([(gui_app.width // 2, gui_app.height // 4), + (gui_app.width // 2, gui_app.height * 3 // 4), + (gui_app.width // 2, gui_app.height)]) def run_replay(): @@ -61,9 +100,8 @@ def run_replay(): for should_render in gui_app.render(): while script_index < len(SCRIPT) and SCRIPT[script_index][0] == frame: - _, coords = SCRIPT[script_index] - if coords is not None: - inject_click(*coords) + _, event = SCRIPT[script_index] + handle_event(event) script_index += 1 ui_state.update() From c85db43705d829672abf8b60683eeeccafafe841 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 8 Dec 2025 20:14:19 -0800 Subject: [PATCH 565/910] camerad: misc labeling/cleanup (#36809) * what's 2c * include * no idea what this means * register comments --- system/camerad/sensors/os04c10.cc | 3 ++- system/camerad/sensors/os04c10_registers.h | 26 +++++++++++----------- system/camerad/sensors/ox03c10.cc | 5 +++-- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/system/camerad/sensors/os04c10.cc b/system/camerad/sensors/os04c10.cc index 38be4ecca..62c26ca80 100644 --- a/system/camerad/sensors/os04c10.cc +++ b/system/camerad/sensors/os04c10.cc @@ -1,6 +1,7 @@ #include #include "system/camerad/sensors/sensor.h" +#include "third_party/linux/include/msm_camsensor_sdk.h" namespace { @@ -51,7 +52,7 @@ OS04C10::OS04C10() { probe_expected_data = 0x5304; bits_per_pixel = 12; mipi_format = CAM_FORMAT_MIPI_RAW_12; - frame_data_type = 0x2c; + frame_data_type = CSI_RAW12; mclk_frequency = 24000000; // Hz // TODO: this was set from logs. actually calculate it out diff --git a/system/camerad/sensors/os04c10_registers.h b/system/camerad/sensors/os04c10_registers.h index 7cd9e97be..28d6b3310 100644 --- a/system/camerad/sensors/os04c10_registers.h +++ b/system/camerad/sensors/os04c10_registers.h @@ -4,10 +4,10 @@ const struct i2c_random_wr_payload start_reg_array_os04c10[] = {{0x100, 1}}; const struct i2c_random_wr_payload stop_reg_array_os04c10[] = {{0x100, 0}}; const struct i2c_random_wr_payload init_array_os04c10[] = { - // DP_2688X1520_NEWSTG_MIPI0776Mbps_30FPS_10BIT_FOURLANE - {0x0103, 0x01}, + // baseed on DP_2688X1520_NEWSTG_MIPI0776Mbps_30FPS_10BIT_FOURLANE + {0x0103, 0x01}, // software reset - // PLL + // PLL + clocks {0x0301, 0xe4}, {0x0303, 0x01}, {0x0305, 0xb6}, @@ -24,7 +24,7 @@ const struct i2c_random_wr_payload init_array_os04c10[] = { {0x3106, 0x21}, {0x3107, 0xa1}, - // ? + // Analog/timing fine-tuning block {0x3624, 0x00}, {0x3625, 0x4c}, {0x3660, 0x04}, @@ -101,7 +101,7 @@ const struct i2c_random_wr_payload init_array_os04c10[] = { {0x3f00, 0x0b}, {0x3f06, 0x04}, - // BLC + // BLC - black level correction {0x400a, 0x01}, {0x400b, 0x50}, {0x400e, 0x08}, @@ -157,7 +157,7 @@ const struct i2c_random_wr_payload init_array_os04c10[] = { {0x5180, 0x70}, {0x5181, 0x10}, - // DPC + // DPC - defective pixel correction {0x520a, 0x03}, {0x520b, 0x06}, {0x520c, 0x0c}, @@ -248,7 +248,7 @@ const struct i2c_random_wr_payload init_array_os04c10[] = { {0x4008, 0x01}, {0x4009, 0x06}, - // FSIN + // FSIN - frame sync {0x3002, 0x22}, {0x3663, 0x22}, {0x368a, 0x04}, @@ -276,8 +276,8 @@ const struct i2c_random_wr_payload init_array_os04c10[] = { {0x3816, 0x03}, {0x3817, 0x01}, - {0x380c, 0x0b}, {0x380d, 0xac}, // HTS - {0x380e, 0x06}, {0x380f, 0x9c}, // VTS + {0x380c, 0x0b}, {0x380d, 0xac}, // HTS (line length) + {0x380e, 0x06}, {0x380f, 0x9c}, // VTS (frame length) {0x3820, 0xb3}, {0x3821, 0x01}, @@ -309,17 +309,17 @@ const struct i2c_random_wr_payload init_array_os04c10[] = { // initialize exposure {0x3503, 0x88}, - // long + // long exposure {0x3500, 0x00}, {0x3501, 0x00}, {0x3502, 0x10}, {0x3508, 0x00}, {0x3509, 0x80}, {0x350a, 0x04}, {0x350b, 0x00}, - // short + // short exposure {0x3510, 0x00}, {0x3511, 0x00}, {0x3512, 0x40}, {0x350c, 0x00}, {0x350d, 0x80}, {0x350e, 0x04}, {0x350f, 0x00}, - // wb + // white balance // b {0x5100, 0x06}, {0x5101, 0x7e}, {0x5140, 0x06}, {0x5141, 0x7e}, @@ -332,7 +332,7 @@ const struct i2c_random_wr_payload init_array_os04c10[] = { }; const struct i2c_random_wr_payload ife_downscale_override_array_os04c10[] = { - // OS04C10_AA_00_02_17_wAO_2688x1524_MIPI728Mbps_Linear12bit_20FPS_4Lane_MCLK24MHz + // based on OS04C10_AA_00_02_17_wAO_2688x1524_MIPI728Mbps_Linear12bit_20FPS_4Lane_MCLK24MHz {0x3c8c, 0x40}, {0x3714, 0x24}, {0x37c2, 0x04}, diff --git a/system/camerad/sensors/ox03c10.cc b/system/camerad/sensors/ox03c10.cc index 6f7e658f4..05d58f03c 100644 --- a/system/camerad/sensors/ox03c10.cc +++ b/system/camerad/sensors/ox03c10.cc @@ -1,6 +1,7 @@ #include #include "system/camerad/sensors/sensor.h" +#include "third_party/linux/include/msm_camsensor_sdk.h" namespace { @@ -40,8 +41,8 @@ OX03C10::OX03C10() { probe_expected_data = 0x5803; bits_per_pixel = 12; mipi_format = CAM_FORMAT_MIPI_RAW_12; - frame_data_type = 0x2c; // one is 0x2a, two are 0x2b - mclk_frequency = 24000000; //Hz + frame_data_type = CSI_RAW12; + mclk_frequency = 24000000; // Hz readout_time_ns = 14697000; From 2d34c9896e51ac57e2d2f82d7e4d741db71efa2d Mon Sep 17 00:00:00 2001 From: infiniteCable2 Date: Tue, 9 Dec 2025 17:36:29 +0100 Subject: [PATCH 566/910] Update ictoggles.py --- selfdrive/ui/layouts/settings/ictoggles.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/selfdrive/ui/layouts/settings/ictoggles.py b/selfdrive/ui/layouts/settings/ictoggles.py index 11d9f5ea5..b1795a1ae 100644 --- a/selfdrive/ui/layouts/settings/ictoggles.py +++ b/selfdrive/ui/layouts/settings/ictoggles.py @@ -103,6 +103,12 @@ class ICTogglesLayout(Widget): "chffr_wheel.png", False, ), + "EnableSmoothSteer": ( + lambda: tr("Steer Smoothing"), + DESCRIPTIONS["EnableSmoothSteer"], + "chffr_wheel.png", + False, + ), "DarkMode": ( lambda: tr("Dark Mode"), DESCRIPTIONS["DarkMode"], From a7b54f2ae4c90b80254e39b38a225b80f360b2d9 Mon Sep 17 00:00:00 2001 From: infiniteCable2 Date: Tue, 9 Dec 2025 17:38:22 +0100 Subject: [PATCH 567/910] Update ictoggles.py --- selfdrive/ui/mici/layouts/settings/ictoggles.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/selfdrive/ui/mici/layouts/settings/ictoggles.py b/selfdrive/ui/mici/layouts/settings/ictoggles.py index e3bde0cc3..a0d7c461d 100644 --- a/selfdrive/ui/mici/layouts/settings/ictoggles.py +++ b/selfdrive/ui/mici/layouts/settings/ictoggles.py @@ -22,6 +22,7 @@ class ICTogglesLayoutMici(NavWidget): enable_sl_pred_sl = BigParamControl("VW: Predicative - Reaction to Speed Limits", "EnableSLPredReactToSL") enable_sl_pred_curve = BigParamControl("VW: Predicative - Reaction to Curves", "EnableSLPredReactToCurves") force_rhd_bsm = BigParamControl("VW: Force RHD for BSM", "ForceRHDForBSM") + enable_smooth_steer = BigParamControl("Steer Smothing", "EnableSmoothSteer") enable_dark_mode = BigParamControl("Dark Mode", "DarkMode") enable_onroad_screen_timer = BigParamControl("Onroad Screen Timeout", "DisableScreenTimer") @@ -34,6 +35,7 @@ class ICTogglesLayoutMici(NavWidget): enable_sl_pred_sl, enable_sl_pred_curve, force_rhd_bsm, + enable_smooth_steer, enable_dark_mode, enable_onroad_screen_timer, ], snap_items=False) @@ -47,6 +49,7 @@ class ICTogglesLayoutMici(NavWidget): ("EnableSLPredReactToSL", enable_sl_pred_sl), ("EnableSLPredReactToCurves", enable_sl_pred_curve), ("ForceRHDForBSM", force_rhd_bsm), + ("EnableSmoothSteer", enable_smooth_steer), ("DarkMode", enable_dark_mode), ("DisableScreenTimer", enable_onroad_screen_timer), ) From dfa55af86fcf6e9a5c62e69656078e1773883c16 Mon Sep 17 00:00:00 2001 From: infiniteCable2 Date: Tue, 9 Dec 2025 17:38:53 +0100 Subject: [PATCH 568/910] Update ictoggles.py --- selfdrive/ui/mici/layouts/settings/ictoggles.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/mici/layouts/settings/ictoggles.py b/selfdrive/ui/mici/layouts/settings/ictoggles.py index a0d7c461d..ff4bb3e41 100644 --- a/selfdrive/ui/mici/layouts/settings/ictoggles.py +++ b/selfdrive/ui/mici/layouts/settings/ictoggles.py @@ -22,7 +22,7 @@ class ICTogglesLayoutMici(NavWidget): enable_sl_pred_sl = BigParamControl("VW: Predicative - Reaction to Speed Limits", "EnableSLPredReactToSL") enable_sl_pred_curve = BigParamControl("VW: Predicative - Reaction to Curves", "EnableSLPredReactToCurves") force_rhd_bsm = BigParamControl("VW: Force RHD for BSM", "ForceRHDForBSM") - enable_smooth_steer = BigParamControl("Steer Smothing", "EnableSmoothSteer") + enable_smooth_steer = BigParamControl("Steer Smoothing", "EnableSmoothSteer") enable_dark_mode = BigParamControl("Dark Mode", "DarkMode") enable_onroad_screen_timer = BigParamControl("Onroad Screen Timeout", "DisableScreenTimer") From 34fed9f9082db34b95922204975f189cf9d09cfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Tue, 9 Dec 2025 10:19:10 -0800 Subject: [PATCH 569/910] URLFILE: Need to catch max retry (#36815) Need to catch max retry --- tools/lib/url_file.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tools/lib/url_file.py b/tools/lib/url_file.py index 01c6c5dc4..c791444f7 100644 --- a/tools/lib/url_file.py +++ b/tools/lib/url_file.py @@ -9,6 +9,7 @@ from urllib3.util import Timeout from openpilot.common.utils import atomic_write from openpilot.system.hardware.hw import Paths +from urllib3.exceptions import MaxRetryError # Cache chunk size K = 1000 @@ -60,7 +61,10 @@ class URLFile: pass def _request(self, method: str, url: str, headers: dict[str, str] | None = None) -> BaseHTTPResponse: - return URLFile.pool_manager().request(method, url, timeout=self._timeout, headers=headers) + try: + return URLFile.pool_manager().request(method, url, timeout=self._timeout, headers=headers) + except MaxRetryError as e: + raise URLFileException(f"Failed to {method} {url}: {e}") from e def get_length_online(self) -> int: response = self._request('HEAD', self._url) From 6bbc3f4d1c0ec47b8da736ed1102e663c43273c2 Mon Sep 17 00:00:00 2001 From: clintonsteiner <47841949+clintonsteiner@users.noreply.github.com> Date: Tue, 9 Dec 2025 13:04:03 -0600 Subject: [PATCH 570/910] pyproject: remove pytools pinning (#36812) * pyproject: remove pytools pinning * issue requiring pin is fixed * https://github.com/inducer/pyopencl/issues/827 * uv lock --------- Co-authored-by: Adeeb Shihadeh --- pyproject.toml | 2 +- uv.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b45a808f1..d5dc95e1b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -116,7 +116,7 @@ dev = [ "pyautogui", "pygame", "pyopencl; platform_machine != 'aarch64'", # broken on arm64 - "pytools < 2024.1.11; platform_machine != 'aarch64'", # pyopencl use a broken version + "pytools>=2025.1.6; platform_machine != 'aarch64'", "pywinctl", "pyprof2calltree", "tabulate", diff --git a/uv.lock b/uv.lock index 650220c87..b179517e0 100644 --- a/uv.lock +++ b/uv.lock @@ -1459,7 +1459,7 @@ requires-dist = [ { name = "pytest-subtests", marker = "extra == 'testing'" }, { name = "pytest-timeout", marker = "extra == 'testing'" }, { name = "pytest-xdist", marker = "extra == 'testing'", git = "https://github.com/sshane/pytest-xdist?rev=2b4372bd62699fb412c4fe2f95bf9f01bd2018da" }, - { name = "pytools", marker = "platform_machine != 'aarch64' and extra == 'dev'", specifier = "<2024.1.11" }, + { name = "pytools", marker = "platform_machine != 'aarch64' and extra == 'dev'", specifier = ">=2025.1.6" }, { name = "pywinctl", marker = "extra == 'dev'" }, { name = "pyzmq" }, { name = "qrcode" }, @@ -4488,16 +4488,16 @@ sdist = { url = "https://files.pythonhosted.org/packages/ef/c6/2c5999de3bb153352 [[package]] name = "pytools" -version = "2024.1.10" +version = "2025.2.5" 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'" }, ] -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" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/7b/f885a57e61ded45b5b10ca60f0b7575c9fb9a282e7513d0e23a33ee647e1/pytools-2025.2.5.tar.gz", hash = "sha256:a7f5350644d46d98ee9c7e67b4b41693308aa0f5e9b188d8f0694b27dc94e3a2", size = 85594, upload-time = "2025-10-07T15:53:30.49Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/66/cf/0a6aaa44b1f9e02b8c0648b5665a82246a93bcc75224c167b4fafa25c093/pytools-2024.1.10-py3-none-any.whl", hash = "sha256:9cabb71038048291400e244e2da441a051d86053339bc484e64e58d8ea263f44", size = 88108, upload-time = "2024-07-17T18:47:36.173Z" }, + { url = "https://files.pythonhosted.org/packages/f6/84/c42c29ca4bff35baa286df70b0097e0b1c88fd57e8e6bdb09cb161a6f3c1/pytools-2025.2.5-py3-none-any.whl", hash = "sha256:42e93751ec425781e103bbcd769ba35ecbacd43339c2905401608f2fdc30cf19", size = 98811, upload-time = "2025-10-07T15:53:29.089Z" }, ] [[package]] From dfd56a46d2cc263ed6054229f202357a111fed9e Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 9 Dec 2025 15:37:13 -0800 Subject: [PATCH 571/910] mici cameraview: log timings (#36816) missing from mici --- selfdrive/ui/mici/onroad/augmented_road_view.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/selfdrive/ui/mici/onroad/augmented_road_view.py b/selfdrive/ui/mici/onroad/augmented_road_view.py index ab55f392f..71ca03ccc 100644 --- a/selfdrive/ui/mici/onroad/augmented_road_view.py +++ b/selfdrive/ui/mici/onroad/augmented_road_view.py @@ -1,6 +1,7 @@ +import time import numpy as np import pyray as rl -from cereal import car, log +from cereal import messaging, car, log from msgq.visionipc import VisionStreamType from openpilot.selfdrive.ui.ui_state import ui_state, UIStatus from openpilot.selfdrive.ui.mici.onroad import SIDE_PANEL_WIDTH @@ -160,6 +161,9 @@ class AugmentedRoadView(CameraView): self._fade_texture = gui_app.texture("icons_mici/onroad/onroad_fade.png") + # debug + self._pm = messaging.PubMaster(['uiDebug']) + def is_swiping_left(self) -> bool: """Check if currently swiping left (for scroller to disable).""" return self._bookmark_icon.is_swiping_left() @@ -179,6 +183,7 @@ class AugmentedRoadView(CameraView): super()._handle_mouse_release(mouse_pos) def _render(self, _): + start_draw = time.monotonic() self._switch_stream_if_needed(ui_state.sm) # Update calibration before rendering @@ -244,6 +249,11 @@ class AugmentedRoadView(CameraView): rl.draw_rectangle(int(self.rect.x), int(self.rect.y), int(self.rect.width), int(self.rect.height), rl.Color(0, 0, 0, 175)) self._offroad_label.render(self._content_rect) + # publish uiDebug + msg = messaging.new_message('uiDebug') + msg.uiDebug.drawTimeMillis = (time.monotonic() - start_draw) * 1000 + self._pm.send('uiDebug', msg) + def _switch_stream_if_needed(self, sm): if sm['selfdriveState'].experimentalMode and WIDE_CAM in self.available_streams: v_ego = sm['carState'].vEgo From f78bacf96bf0da41828463141994bd38661cd6fa Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 9 Dec 2025 15:38:43 -0800 Subject: [PATCH 572/910] mici ui replay: temp remove swipes (#36818) hmm it IS nondeterm --- selfdrive/ui/tests/diff/replay.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/selfdrive/ui/tests/diff/replay.py b/selfdrive/ui/tests/diff/replay.py index cbb2d606e..9da157660 100755 --- a/selfdrive/ui/tests/diff/replay.py +++ b/selfdrive/ui/tests/diff/replay.py @@ -33,16 +33,9 @@ class DummyEvent: SCRIPT = [ (0, DummyEvent()), - (FPS * 1, DummyEvent(swipe_right=True)), - (FPS * 2, DummyEvent(swipe_left=True)), - (FPS * 3, DummyEvent(swipe_left=True)), - (FPS * 4, DummyEvent(click=True)), - (FPS * 5, DummyEvent(click=True)), - (FPS * 6, DummyEvent(swipe_left=True)), - (FPS * 7, DummyEvent(swipe_left=True)), - (FPS * 8, DummyEvent(swipe_right=True)), - (FPS * 9, DummyEvent(swipe_down=True)), - (FPS * 10, DummyEvent()), + (FPS * 1, DummyEvent(click=True)), + (FPS * 2, DummyEvent(click=True)), + (FPS * 3, DummyEvent()), ] From 53b7adedc26d1040aa4063648961038fb25c160a Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 10 Dec 2025 00:29:03 -0800 Subject: [PATCH 573/910] Fix UI timing test (#36823) * why did no one tell me about this?! * not necessary --- selfdrive/test/test_onroad.py | 5 +++-- selfdrive/ui/mici/onroad/cameraview.py | 1 - selfdrive/ui/onroad/cameraview.py | 1 - 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/selfdrive/test/test_onroad.py b/selfdrive/test/test_onroad.py index 27cc17624..b9506d080 100644 --- a/selfdrive/test/test_onroad.py +++ b/selfdrive/test/test_onroad.py @@ -206,8 +206,9 @@ class TestOnroad: result += "-------------- UI Draw Timing ------------------\n" result += "------------------------------------------------\n" - # skip first few frames -- connecting to vipc - ts = self.ts['uiDebug']['drawTimeMillis'][15:] + # other processes preempt ui while starting up + offset = int(20 * LOG_OFFSET) + ts = self.ts['uiDebug']['drawTimeMillis'][offset:] result += f"min {min(ts):.2f}ms\n" result += f"max {max(ts):.2f}ms\n" result += f"std {np.std(ts):.2f}ms\n" diff --git a/selfdrive/ui/mici/onroad/cameraview.py b/selfdrive/ui/mici/onroad/cameraview.py index f3e0ef409..89a4926ce 100644 --- a/selfdrive/ui/mici/onroad/cameraview.py +++ b/selfdrive/ui/mici/onroad/cameraview.py @@ -107,7 +107,6 @@ else: class CameraView(Widget): def __init__(self, name: str, stream_type: VisionStreamType): super().__init__() - # TODO: implement a receiver and connect thread self._name = name # Primary stream self.client = VisionIpcClient(name, stream_type, conflate=True) diff --git a/selfdrive/ui/onroad/cameraview.py b/selfdrive/ui/onroad/cameraview.py index 881a916df..544394846 100644 --- a/selfdrive/ui/onroad/cameraview.py +++ b/selfdrive/ui/onroad/cameraview.py @@ -68,7 +68,6 @@ else: class CameraView(Widget): def __init__(self, name: str, stream_type: VisionStreamType): super().__init__() - # TODO: implement a receiver and connect thread self._name = name # Primary stream self.client = VisionIpcClient(name, stream_type, conflate=True) From ff5b75d16479291a9060405e471102408358e232 Mon Sep 17 00:00:00 2001 From: rj-lynch Date: Thu, 11 Dec 2025 00:35:35 +0000 Subject: [PATCH 574/910] Refactor CarSpecificEvents Class extracting BRAND_EXTRA_GEARS (#36805) * Brand Extra Gears Dict added. Gear data removed from CarSpecificEvents Update method, data now held in global variable. * Added elif for Ford and Nissan events creation. BRAND_EXTRA_GEARS now extracted from CarSpecificEvents * Amended Chrysler and Toyota create_common_events calls. * format * can do this! * consis * whoops * type --------- Co-authored-by: RJ Co-authored-by: Shane Smiskol --- selfdrive/car/car_specific.py | 40 +++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/selfdrive/car/car_specific.py b/selfdrive/car/car_specific.py index 5319d286b..9e166a44d 100644 --- a/selfdrive/car/car_specific.py +++ b/selfdrive/car/car_specific.py @@ -26,6 +26,18 @@ class MockCarState: return CS +BRAND_EXTRA_GEARS = { + 'ford': [GearShifter.low, GearShifter.manumatic], + 'nissan': [GearShifter.brake], + 'chrysler': [GearShifter.low], + 'honda': [GearShifter.sport], + 'toyota': [GearShifter.sport], + 'gm': [GearShifter.sport, GearShifter.low, GearShifter.eco, GearShifter.manumatic], + 'volkswagen': [GearShifter.eco, GearShifter.sport, GearShifter.manumatic], + 'hyundai': [GearShifter.sport, GearShifter.manumatic] +} + + class CarSpecificEvents: def __init__(self, CP: structs.CarParams): self.CP = CP @@ -36,17 +48,13 @@ class CarSpecificEvents: self.silent_steer_warning = True def update(self, CS: car.CarState, CS_prev: car.CarState, CC: car.CarControl): + extra_gears = BRAND_EXTRA_GEARS.get(self.CP.brand, None) + 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.low, GearShifter.manumatic]) - - elif self.CP.brand == 'nissan': - events = self.create_common_events(CS, CS_prev, extra_gears=[GearShifter.brake]) - elif self.CP.brand == 'chrysler': - events = self.create_common_events(CS, CS_prev, extra_gears=[GearShifter.low]) + events = self.create_common_events(CS, CS_prev, extra_gears=extra_gears) # Low speed steer alert hysteresis logic if self.CP.minSteerSpeed > 0. and CS.vEgo < (self.CP.minSteerSpeed + 0.5): @@ -57,7 +65,7 @@ class CarSpecificEvents: events.add(EventName.belowSteerSpeed) elif self.CP.brand == 'honda': - events = self.create_common_events(CS, CS_prev, extra_gears=[GearShifter.sport], pcm_enable=False) + events = self.create_common_events(CS, CS_prev, extra_gears=extra_gears, pcm_enable=False) if self.CP.pcmCruise and CS.vEgo < self.CP.minEnableSpeed: events.add(EventName.belowEngageSpeed) @@ -79,7 +87,7 @@ class CarSpecificEvents: elif self.CP.brand == 'toyota': # TODO: when we check for unexpected disengagement, check gear not S1, S2, S3 - events = self.create_common_events(CS, CS_prev, extra_gears=[GearShifter.sport]) + events = self.create_common_events(CS, CS_prev, extra_gears=extra_gears) if self.CP.openpilotLongitudinalControl: if CS.cruiseState.standstill and not CS.brakePressed: @@ -94,9 +102,7 @@ class CarSpecificEvents: events.add(EventName.manualRestart) elif self.CP.brand == 'gm': - events = self.create_common_events(CS, CS_prev, extra_gears=[GearShifter.sport, GearShifter.low, - GearShifter.eco, GearShifter.manumatic], - pcm_enable=self.CP.pcmCruise) + events = self.create_common_events(CS, CS_prev, extra_gears=extra_gears, pcm_enable=self.CP.pcmCruise) # Enabling at a standstill with brake is allowed # TODO: verify 17 Volt can enable for the first time at a stop and allow for all GMs @@ -107,8 +113,7 @@ class CarSpecificEvents: events.add(EventName.resumeRequired) 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) + events = self.create_common_events(CS, CS_prev, extra_gears=extra_gears, pcm_enable=self.CP.pcmCruise) if self.CP.openpilotLongitudinalControl: if CS.vEgo < self.CP.minEnableSpeed + 0.5: @@ -121,15 +126,14 @@ class CarSpecificEvents: # events.add(EventName.steerTimeLimit) elif self.CP.brand == 'hyundai': - events = self.create_common_events(CS, CS_prev, extra_gears=(GearShifter.sport, GearShifter.manumatic), - pcm_enable=self.CP.pcmCruise, allow_button_cancel=False) + events = self.create_common_events(CS, CS_prev, extra_gears=extra_gears, pcm_enable=self.CP.pcmCruise, allow_button_cancel=False) else: - events = self.create_common_events(CS, CS_prev) + events = self.create_common_events(CS, CS_prev, extra_gears=extra_gears) return events - def create_common_events(self, CS: structs.CarState, CS_prev: car.CarState, extra_gears=None, pcm_enable=True, + def create_common_events(self, CS: structs.CarState, CS_prev: car.CarState, extra_gears: list | None = None, pcm_enable=True, allow_button_cancel=True): events = Events() From a49273d9d41c948bf7c6e9946b21a62a870cc307 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Fri, 12 Dec 2025 01:22:30 +0800 Subject: [PATCH 575/910] remove unused #include "common/params.h" from hardware.h (#36827) remove include --- system/hardware/tici/hardware.h | 1 - 1 file changed, 1 deletion(-) diff --git a/system/hardware/tici/hardware.h b/system/hardware/tici/hardware.h index 8a0c06694..d59b45efc 100644 --- a/system/hardware/tici/hardware.h +++ b/system/hardware/tici/hardware.h @@ -7,7 +7,6 @@ #include #include // for std::clamp -#include "common/params.h" #include "common/util.h" #include "system/hardware/base.h" From d8125f50d27c8877f74887f4456b1a81bef42cb9 Mon Sep 17 00:00:00 2001 From: YassineYousfi Date: Thu, 11 Dec 2025 09:38:53 -0800 Subject: [PATCH 576/910] dm: speedup stat filters convergence (#36756) * dm: speedup stat filters convergence * lint --- selfdrive/monitoring/helpers.py | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/selfdrive/monitoring/helpers.py b/selfdrive/monitoring/helpers.py index 1ed04e705..3377ce6c6 100644 --- a/selfdrive/monitoring/helpers.py +++ b/selfdrive/monitoring/helpers.py @@ -40,6 +40,9 @@ class DRIVER_MONITOR_SETTINGS: self._PHONE_THRESH2 = 15.0 self._PHONE_MAX_OFFSET = 0.06 self._PHONE_MIN_OFFSET = 0.025 + self._PHONE_DATA_AVG = 0.05 + self._PHONE_DATA_VAR = 3*0.005 + self._PHONE_MAX_COUNT = int(360 / self._DT_DMON) self._POSE_PITCH_THRESHOLD = 0.3133 self._POSE_PITCH_THRESHOLD_SLACK = 0.3237 @@ -50,6 +53,8 @@ class DRIVER_MONITOR_SETTINGS: self._PITCH_NATURAL_OFFSET = 0.011 # initial value before offset is learned self._PITCH_NATURAL_THRESHOLD = 0.449 self._YAW_NATURAL_OFFSET = 0.075 # initial value before offset is learned + self._PITCH_NATURAL_VAR = 3*0.01 + self._YAW_NATURAL_VAR = 3*0.05 self._PITCH_MAX_OFFSET = 0.124 self._PITCH_MIN_OFFSET = -0.0881 self._YAW_MAX_OFFSET = 0.289 @@ -70,6 +75,9 @@ class DRIVER_MONITOR_SETTINGS: self._WHEELPOS_CALIB_MIN_SPEED = 11 self._WHEELPOS_THRESHOLD = 0.5 self._WHEELPOS_FILTER_MIN_COUNT = int(15 / self._DT_DMON) # allow 15 seconds to converge wheel side + self._WHEELPOS_DATA_AVG = 0.03 + self._WHEELPOS_DATA_VAR = 3*5.5e-5 + self._WHEELPOS_MAX_COUNT = -1 self._RECOVERY_FACTOR_MAX = 5. # relative to minus step change self._RECOVERY_FACTOR_MIN = 1.25 # relative to minus step change @@ -78,30 +86,33 @@ class DRIVER_MONITOR_SETTINGS: self._MAX_TERMINAL_DURATION = int(30 / self._DT_DMON) # not allowed to engage after 30s of terminal alerts class DistractedType: + NOT_DISTRACTED = 0 DISTRACTED_POSE = 1 << 0 DISTRACTED_BLINK = 1 << 1 DISTRACTED_PHONE = 1 << 2 class DriverPose: - def __init__(self, max_trackable): + def __init__(self, settings): + pitch_filter_raw_priors = (settings._PITCH_NATURAL_OFFSET, settings._PITCH_NATURAL_VAR, 2) + yaw_filter_raw_priors = (settings._YAW_NATURAL_OFFSET, settings._YAW_NATURAL_VAR, 2) self.yaw = 0. self.pitch = 0. self.roll = 0. self.yaw_std = 0. self.pitch_std = 0. self.roll_std = 0. - self.pitch_offseter = RunningStatFilter(max_trackable=max_trackable) - self.yaw_offseter = RunningStatFilter(max_trackable=max_trackable) + self.pitch_offseter = RunningStatFilter(raw_priors=pitch_filter_raw_priors, max_trackable=settings._POSE_OFFSET_MAX_COUNT) + self.yaw_offseter = RunningStatFilter(raw_priors=yaw_filter_raw_priors, max_trackable=settings._POSE_OFFSET_MAX_COUNT) self.calibrated = False self.low_std = True self.cfactor_pitch = 1. self.cfactor_yaw = 1. class DriverProb: - def __init__(self, max_trackable): + def __init__(self, raw_priors, max_trackable): self.prob = 0. - self.prob_offseter = RunningStatFilter(max_trackable=max_trackable) + self.prob_offseter = RunningStatFilter(raw_priors=raw_priors, max_trackable=max_trackable) self.prob_calibrated = False class DriverBlink: @@ -140,9 +151,11 @@ class DriverMonitoring: self.settings = settings if settings is not None else DRIVER_MONITOR_SETTINGS(device_type=HARDWARE.get_device_type()) # init driver status - self.wheelpos = DriverProb(-1) - self.pose = DriverPose(self.settings._POSE_OFFSET_MAX_COUNT) - self.phone = DriverProb(self.settings._POSE_OFFSET_MAX_COUNT) + wheelpos_filter_raw_priors = (self.settings._WHEELPOS_DATA_AVG, self.settings._WHEELPOS_DATA_VAR, 2) + phone_filter_raw_priors = (self.settings._PHONE_DATA_AVG, self.settings._PHONE_DATA_VAR, 2) + self.wheelpos = DriverProb(raw_priors=wheelpos_filter_raw_priors, max_trackable=self.settings._WHEELPOS_MAX_COUNT) + self.phone = DriverProb(raw_priors=phone_filter_raw_priors, max_trackable=self.settings._PHONE_MAX_COUNT) + self.pose = DriverPose(settings=self.settings) self.blink = DriverBlink() self.always_on = always_on From 1391434f54d446857f009d51f1919c2e7b58b652 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 11 Dec 2025 11:22:08 -0800 Subject: [PATCH 577/910] setup: fix uv install fail (#36839) * pipefail * curl retry --- tools/install_python_dependencies.sh | 4 ++-- tools/setup.sh | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/tools/install_python_dependencies.sh b/tools/install_python_dependencies.sh index cdbaca32c..c2db249cf 100755 --- a/tools/install_python_dependencies.sh +++ b/tools/install_python_dependencies.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -set -e +set -euo pipefail # Increase the pip timeout to handle TimeoutError export PIP_DEFAULT_TIMEOUT=200 @@ -10,7 +10,7 @@ cd "$ROOT" if ! command -v "uv" > /dev/null 2>&1; then echo "installing uv..." - curl -LsSf https://astral.sh/uv/install.sh | sh + curl -LsSf --retry 5 --retry-delay 5 --retry-all-errors https://astral.sh/uv/install.sh | sh UV_BIN="$HOME/.local/bin" PATH="$UV_BIN:$PATH" fi diff --git a/tools/setup.sh b/tools/setup.sh index e0a9a4f6a..fd7efcee9 100755 --- a/tools/setup.sh +++ b/tools/setup.sh @@ -1,6 +1,5 @@ #!/usr/bin/env bash - -set -e +set -euo pipefail RED='\033[0;31m' GREEN='\033[0;32m' From c61ed10015d10241f33df1e0ecb0ff1e8ac661de Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 11 Dec 2025 13:04:59 -0800 Subject: [PATCH 578/910] USB GPU benchmarking (#36840) * test boot time * lil nicer * cleanup * revert that --------- Co-authored-by: Comma Device --- scripts/usbgpu/benchmark.sh | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100755 scripts/usbgpu/benchmark.sh diff --git a/scripts/usbgpu/benchmark.sh b/scripts/usbgpu/benchmark.sh new file mode 100755 index 000000000..04a76d054 --- /dev/null +++ b/scripts/usbgpu/benchmark.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +set -e + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" +cd $DIR/../../tinygrad_repo + +GREEN='\033[0;32m' +NC='\033[0m' + + +#export DEBUG=2 +export PYTHONPATH=. +export AM_RESET=1 +export AMD=1 +export AMD_IFACE=USB +export AMD_LLVM=1 + +python3 -m unittest -q --buffer test.test_tiny.TestTiny.test_plus \ + > /tmp/test_tiny.log 2>&1 || (cat /tmp/test_tiny.log; exit 1) +printf "${GREEN}Booted in ${SECONDS}s${NC}\n" +printf "${GREEN}=============${NC}\n" + +printf "\n\n" +printf "${GREEN}Transfer speeds:${NC}\n" +printf "${GREEN}================${NC}\n" +python3 test/external/external_test_usb_asm24.py TestDevCopySpeeds From edede31c320163cdf771352918e66f85b4b663eb Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 11 Dec 2025 18:00:43 -0800 Subject: [PATCH 579/910] athenad: get ES256 key (#36845) * fix * why not format * fix typing * cast --- common/api.py | 10 ++++++---- system/athena/athenad.py | 9 +++------ system/athena/registration.py | 4 +++- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/common/api.py b/common/api.py index 656c05ed5..7ea278038 100644 --- a/common/api.py +++ b/common/api.py @@ -7,9 +7,10 @@ from openpilot.system.version import get_version API_HOST = os.getenv('API_HOST', 'https://api.commadotai.com') - # name : jwt signature algorithm -KEYS = {"id_rsa" : "RS256", - "id_ecdsa" : "ES256"} +# name: jwt signature algorithm +KEYS = {"id_rsa": "RS256", + "id_ecdsa": "ES256"} + class Api: def __init__(self, dongle_id): @@ -50,7 +51,8 @@ def api_get(endpoint, method='GET', timeout=None, access_token=None, **params): return requests.request(method, API_HOST + "/" + endpoint, timeout=timeout, headers=headers, params=params) -def get_key_pair(): + +def get_key_pair() -> tuple[str, str, str] | tuple[None, None, None]: for key in KEYS: if os.path.isfile(Paths.persist_root() + f'/comma/{key}') and os.path.isfile(Paths.persist_root() + f'/comma/{key}.pub'): with open(Paths.persist_root() + f'/comma/{key}') as private, open(Paths.persist_root() + f'/comma/{key}.pub') as public: diff --git a/system/athena/athenad.py b/system/athena/athenad.py index 50c4f5408..3b71a9c31 100755 --- a/system/athena/athenad.py +++ b/system/athena/athenad.py @@ -30,7 +30,7 @@ from websocket import (ABNF, WebSocket, WebSocketException, WebSocketTimeoutExce import cereal.messaging as messaging from cereal import log from cereal.services import SERVICE_LIST -from openpilot.common.api import Api +from openpilot.common.api import Api, get_key_pair from openpilot.common.utils import CallbackReader, get_upload_stream from openpilot.common.params import Params from openpilot.common.realtime import set_core_affinity @@ -523,11 +523,8 @@ def startLocalProxy(global_end_event: threading.Event, remote_ws_uri: str, local @dispatcher.add_method def getPublicKey() -> str | None: - if not os.path.isfile(Paths.persist_root() + '/comma/id_rsa.pub'): - return None - - with open(Paths.persist_root() + '/comma/id_rsa.pub') as f: - return f.read() + _, _, public_key = get_key_pair() + return public_key @dispatcher.add_method diff --git a/system/athena/registration.py b/system/athena/registration.py index 81aee1ebf..405b2423f 100755 --- a/system/athena/registration.py +++ b/system/athena/registration.py @@ -2,6 +2,7 @@ import time import json import jwt +from typing import cast from pathlib import Path from datetime import datetime, timedelta, UTC @@ -69,7 +70,8 @@ def register(show_spinner=False) -> str | None: start_time = time.monotonic() while True: try: - register_token = jwt.encode({'register': True, 'exp': datetime.now(UTC).replace(tzinfo=None) + timedelta(hours=1)}, private_key, algorithm=jwt_algo) + register_token = jwt.encode({'register': True, 'exp': datetime.now(UTC).replace(tzinfo=None) + timedelta(hours=1)}, + cast(str, private_key), algorithm=jwt_algo) cloudlog.info("getting pilotauth") resp = api_get("v2/pilotauth/", method='POST', timeout=15, imei=imei1, imei2=imei2, serial=serial, public_key=public_key, register_token=register_token) From 13693e3a0ae1e25b1d90c959bdb138848ac9f700 Mon Sep 17 00:00:00 2001 From: Matt Purnell <65473602+mpurnell1@users.noreply.github.com> Date: Thu, 11 Dec 2025 20:19:59 -0600 Subject: [PATCH 580/910] loggerd: Fix test that fails on non-TICI devices (#36846) Only check for TICI files on TICI --- system/loggerd/tests/test_loggerd.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/system/loggerd/tests/test_loggerd.py b/system/loggerd/tests/test_loggerd.py index c6a4b12e6..1cac16adc 100644 --- a/system/loggerd/tests/test_loggerd.py +++ b/system/loggerd/tests/test_loggerd.py @@ -16,6 +16,7 @@ from openpilot.common.basedir import BASEDIR from openpilot.common.params import Params from openpilot.common.timeout import Timeout from openpilot.system.hardware.hw import Paths +from openpilot.system.hardware import TICI from openpilot.system.loggerd.xattr_cache import getxattr from openpilot.system.loggerd.deleter import PRESERVE_ATTR_NAME, PRESERVE_ATTR_VALUE from openpilot.system.manager.process_config import managed_processes @@ -221,13 +222,16 @@ class TestLoggerd: assert abs(boot.wallTimeNanos - time.time_ns()) < 5*1e9 # within 5s assert boot.launchLog == launch_log - for fn in ["console-ramoops", "pmsg-ramoops-0"]: - path = Path(os.path.join("/sys/fs/pstore/", fn)) - if path.is_file(): - with open(path, "rb") as f: - expected_val = f.read() - bootlog_val = [e.value for e in boot.pstore.entries if e.key == fn][0] - assert expected_val == bootlog_val + if TICI: + for fn in ["console-ramoops", "pmsg-ramoops-0"]: + path = Path(os.path.join("/sys/fs/pstore/", fn)) + if path.is_file(): + with open(path, "rb") as f: + expected_val = f.read() + bootlog_val = [e.value for e in boot.pstore.entries if e.key == fn][0] + assert expected_val == bootlog_val + else: + assert len(boot.pstore.entries) == 0 # next one should increment by one bl1 = re.match(RE.LOG_ID_V2, bootlog_path.name) From 2d91aa5abc44db65202f752e93d134683346241d Mon Sep 17 00:00:00 2001 From: Suyog Shinde <64534620+SuyogShinde942@users.noreply.github.com> Date: Thu, 11 Dec 2025 21:24:32 -0600 Subject: [PATCH 581/910] locationd: fix velocity calibration using wrong pose field (#36844) --- selfdrive/locationd/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/locationd/helpers.py b/selfdrive/locationd/helpers.py index bf4588a40..2a3ac8b86 100644 --- a/selfdrive/locationd/helpers.py +++ b/selfdrive/locationd/helpers.py @@ -172,7 +172,7 @@ class PoseCalibrator: ned_from_calib_euler = self._ned_from_calib(pose.orientation) angular_velocity_calib = self._transform_calib_from_device(pose.angular_velocity) acceleration_calib = self._transform_calib_from_device(pose.acceleration) - velocity_calib = self._transform_calib_from_device(pose.angular_velocity) + velocity_calib = self._transform_calib_from_device(pose.velocity) return Pose(ned_from_calib_euler, velocity_calib, acceleration_calib, angular_velocity_calib) From 0871a35c10d6fdb465b1ebba205628c2cb2a0ed7 Mon Sep 17 00:00:00 2001 From: Bruce Wayne Date: Thu, 11 Dec 2025 19:43:53 -0800 Subject: [PATCH 582/910] Revert "Dark Souls Model (#36764)" This reverts commit 83dad85cdd800d79d77ff964da72103218c12ef9. --- selfdrive/modeld/models/driving_policy.onnx | 2 +- selfdrive/modeld/models/driving_vision.onnx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/modeld/models/driving_policy.onnx b/selfdrive/modeld/models/driving_policy.onnx index ec451ba73..1e764af9b 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:e2929b07deb9fb1e492c7fa2832c51ac9e472bfe0b80730fdbbe263735866580 +oid sha256:c5a1f0655ddf266ed42ad1980389d96f47cc5e756da1fa3ca1477a920bb9b157 size 13926324 diff --git a/selfdrive/modeld/models/driving_vision.onnx b/selfdrive/modeld/models/driving_vision.onnx index e5ef27810..441c4a16a 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:2194eaee8a8c40f79a6f783d198991b1bf70a54b5885053e63789eab040a5228 +oid sha256:8f16d548ea4eb5d01518a9e90d4527cd97c31a84bcaf6f695dead8f0015fecc4 size 46271942 From b2e7dffa590f10901a98152ba5789a412bdb88a3 Mon Sep 17 00:00:00 2001 From: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Thu, 11 Dec 2025 20:24:45 -0800 Subject: [PATCH 583/910] modeld_v2: support planplus outputs (#1532) * conditional concatenation * v13 * modifucatuins --- sunnypilot/modeld_v2/fill_model_msg.py | 5 ++--- sunnypilot/modeld_v2/modeld.py | 5 ++++- sunnypilot/modeld_v2/parse_model_outputs_split.py | 2 ++ sunnypilot/models/helpers.py | 2 +- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/sunnypilot/modeld_v2/fill_model_msg.py b/sunnypilot/modeld_v2/fill_model_msg.py index ee0eb4868..57e968d02 100644 --- a/sunnypilot/modeld_v2/fill_model_msg.py +++ b/sunnypilot/modeld_v2/fill_model_msg.py @@ -11,13 +11,12 @@ SEND_RAW_PRED = os.getenv('SEND_RAW_PRED') ConfidenceClass = log.ModelDataV2.ConfidenceClass -def get_curvature_from_output(output, vego, lat_action_t, mlsim): +def get_curvature_from_output(output, plan, vego, lat_action_t, mlsim): if not mlsim: if desired_curv := output.get('desired_curvature'): # If the model outputs the desired curvature, use that directly return float(desired_curv[0, 0]) - plan_output = output['plan'][0] - return float(get_curvature_from_plan(plan_output[:, Plan.T_FROM_CURRENT_EULER][:, 2], plan_output[:, Plan.ORIENTATION_RATE][:, 2], + return float(get_curvature_from_plan(plan[:, Plan.T_FROM_CURRENT_EULER][:, 2], plan[:, Plan.ORIENTATION_RATE][:, 2], ModelConstants.T_IDXS, vego, lat_action_t)) diff --git a/sunnypilot/modeld_v2/modeld.py b/sunnypilot/modeld_v2/modeld.py index 0fd45940d..82eb099e7 100755 --- a/sunnypilot/modeld_v2/modeld.py +++ b/sunnypilot/modeld_v2/modeld.py @@ -28,6 +28,7 @@ from openpilot.sunnypilot.models.helpers import get_active_bundle from openpilot.sunnypilot.models.runners.helpers import get_model_runner PROCESS_NAME = "selfdrive.modeld.modeld_tinygrad" +RECOVERY_POWER = 1.0 # The higher this number the more aggressively the model will recover to lanecenter, too high and it will ping-pong class FrameMeta: @@ -156,11 +157,13 @@ class ModelState(ModelStateBase): def get_action_from_model(self, model_output: dict[str, np.ndarray], prev_action: log.ModelDataV2.Action, lat_action_t: float, long_action_t: float, v_ego: float) -> log.ModelDataV2.Action: plan = model_output['plan'][0] + if 'planplus' in model_output: + plan = plan + RECOVERY_POWER*model_output['planplus'][0] desired_accel, should_stop = get_accel_from_plan(plan[:, Plan.VELOCITY][:, 0], plan[:, Plan.ACCELERATION][:, 0], self.constants.T_IDXS, action_t=long_action_t) desired_accel = smooth_value(desired_accel, prev_action.desiredAcceleration, self.LONG_SMOOTH_SECONDS) - desired_curvature = get_curvature_from_output(model_output, v_ego, lat_action_t, self.mlsim) + desired_curvature = get_curvature_from_output(model_output, plan, v_ego, lat_action_t, self.mlsim) if self.generation is not None and self.generation >= 10: # smooth curvature for post FOF models if v_ego > self.MIN_LAT_CONTROL_SPEED: desired_curvature = smooth_value(desired_curvature, prev_action.desiredCurvature, self.LAT_SMOOTH_SECONDS) diff --git a/sunnypilot/modeld_v2/parse_model_outputs_split.py b/sunnypilot/modeld_v2/parse_model_outputs_split.py index 9cf321a1b..a099facd1 100644 --- a/sunnypilot/modeld_v2/parse_model_outputs_split.py +++ b/sunnypilot/modeld_v2/parse_model_outputs_split.py @@ -108,6 +108,8 @@ class Parser: plan_in_N, plan_out_N = (SplitModelConstants.PLAN_MHP_N, SplitModelConstants.PLAN_MHP_SELECTION) if plan_mhp else (0, 0) self.parse_mdn('plan', outs, in_N=plan_in_N, out_N=plan_out_N, out_shape=(SplitModelConstants.IDX_N, SplitModelConstants.PLAN_WIDTH)) + if 'planplus' in outs: + self.parse_mdn('planplus', outs, in_N=plan_in_N, out_N=plan_out_N, out_shape=(SplitModelConstants.IDX_N, SplitModelConstants.PLAN_WIDTH)) def split_outputs(self, outs: dict[str, np.ndarray]) -> None: if 'desired_curvature' in outs: diff --git a/sunnypilot/models/helpers.py b/sunnypilot/models/helpers.py index a58118537..ce6625a1f 100644 --- a/sunnypilot/models/helpers.py +++ b/sunnypilot/models/helpers.py @@ -19,7 +19,7 @@ from openpilot.system.hardware.hw import Paths from pathlib import Path # see the README.md for more details on the model selector versioning -CURRENT_SELECTOR_VERSION = 12 +CURRENT_SELECTOR_VERSION = 13 REQUIRED_MIN_SELECTOR_VERSION = 12 USE_ONNX = os.getenv('USE_ONNX', PC) From 9947206ccdb71f698119f6782300af377f4e735e Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 11 Dec 2025 21:52:24 -0800 Subject: [PATCH 584/910] comma four: fix wrapping steer right (#36848) rm extra space --- selfdrive/ui/mici/onroad/alert_renderer.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/selfdrive/ui/mici/onroad/alert_renderer.py b/selfdrive/ui/mici/onroad/alert_renderer.py index bdf85acc3..7ee83ff88 100644 --- a/selfdrive/ui/mici/onroad/alert_renderer.py +++ b/selfdrive/ui/mici/onroad/alert_renderer.py @@ -200,11 +200,11 @@ class AlertRenderer(Widget): text_x = self._rect.x + ALERT_MARGIN text_width = self._rect.width - ALERT_MARGIN if icon_side == 'left': - text_x = self._rect.x + self._txt_turn_signal_right.width + 20 * 2 - text_width = self._rect.width - ALERT_MARGIN - self._txt_turn_signal_right.width - 20 * 2 + text_x = self._rect.x + self._txt_turn_signal_right.width + text_width = self._rect.width - ALERT_MARGIN - self._txt_turn_signal_right.width elif icon_side == 'right': text_x = self._rect.x + ALERT_MARGIN - text_width = self._rect.width - ALERT_MARGIN - self._txt_turn_signal_right.width - 20 * 2 + text_width = self._rect.width - ALERT_MARGIN - self._txt_turn_signal_right.width text_rect = rl.Rectangle( text_x, From 928672999bd1a1f5d28a0110b363ac6a5f9c86b5 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 12 Dec 2025 02:31:06 -0500 Subject: [PATCH 585/910] ui: consolidate `NoElideButtonAction` (#1569) --- selfdrive/ui/sunnypilot/layouts/settings/models.py | 12 ++++-------- system/ui/sunnypilot/lib/utils.py | 12 ++++++++++++ 2 files changed, 16 insertions(+), 8 deletions(-) create mode 100644 system/ui/sunnypilot/lib/utils.py diff --git a/selfdrive/ui/sunnypilot/layouts/settings/models.py b/selfdrive/ui/sunnypilot/layouts/settings/models.py index 43f0d25b4..16d406ea9 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/models.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/models.py @@ -21,7 +21,8 @@ from openpilot.system.ui.widgets.toggle import ON_COLOR from openpilot.sunnypilot.models.runners.constants import CUSTOM_MODEL_PATH from openpilot.system.ui.sunnypilot.lib.styles import style -from openpilot.system.ui.sunnypilot.widgets.list_view import ButtonActionSP, ListItemSP, toggle_item_sp, option_item_sp +from openpilot.system.ui.sunnypilot.lib.utils import NoElideButtonAction +from openpilot.system.ui.sunnypilot.widgets.list_view import ListItemSP, toggle_item_sp, option_item_sp from openpilot.system.ui.sunnypilot.widgets.progress_bar import progress_item from openpilot.system.ui.sunnypilot.widgets.tree_dialog import TreeOptionDialog, TreeNode, TreeFolder @@ -29,11 +30,6 @@ if gui_app.sunnypilot_ui(): from openpilot.system.ui.sunnypilot.widgets.list_view import button_item_sp as button_item -class ModelAction(ButtonActionSP): - def get_width_hint(self): - return super().get_width_hint() + 1 - - class ModelsLayout(Widget): def __init__(self): super().__init__() @@ -55,7 +51,7 @@ class ModelsLayout(Widget): self.current_model_item = ListItemSP( title=tr("Current Model"), description="", - action_item=ModelAction(tr("SELECT")), + action_item=NoElideButtonAction(tr("SELECT")), callback=self._handle_current_model_clicked ) @@ -70,7 +66,7 @@ class ModelsLayout(Widget): self.clear_cache_item = ListItemSP( title=tr("Clear Model Cache"), description="", - action_item=ModelAction(tr("CLEAR")), + action_item=NoElideButtonAction(tr("CLEAR")), callback=self._clear_cache ) diff --git a/system/ui/sunnypilot/lib/utils.py b/system/ui/sunnypilot/lib/utils.py new file mode 100644 index 000000000..09230da14 --- /dev/null +++ b/system/ui/sunnypilot/lib/utils.py @@ -0,0 +1,12 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +from openpilot.system.ui.sunnypilot.widgets.list_view import ButtonActionSP + + +class NoElideButtonAction(ButtonActionSP): + def get_width_hint(self): + return super().get_width_hint() + 1 From 436ff5aa427f3d1e289188f1cf00271a44d2329c Mon Sep 17 00:00:00 2001 From: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Thu, 11 Dec 2025 23:40:33 -0800 Subject: [PATCH 586/910] ui: OSM panel (#1515) * param to control stock vs sp ui * init styles * SP Toggles * Lint * optimizations * sp raylib preview * fix callback * fix ui preview * dialog txt * compare vs what used to be done before InputDialog * merge origin raylib toggles * tree dialog * less trees for the planet * the heck * save the trees we got icons * Update process.py * Remove 'sunnypilot_ui' Removed 'sunnypilot_ui' parameter from params_keys.h * Update raylib_screenshots.py Removed the parameter setting for 'sunnypilot_ui' in the test. * ui: fuzzy search helper * better tree. fully dynamic and stuff * rm * more indent * Squashed commit of the following: commit 6b5b686fa5f1b6c8ba05cb98606bcb5e22fdcfd9 Author: discountchubbs Date: Mon Nov 24 17:16:17 2025 -0800 more indent commit 76bc538ac74898e026ee67502ddcb2a2a78eb429 Merge: 53eb821dc4 c53e2134e2 Author: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Mon Nov 24 17:15:48 2025 -0800 Merge branch 'master' into rl-tree-dialog commit 53eb821dc41a09f0bbe5ddbc6957ddfb6023c979 Merge: 82e1ebe97e 844f4cbc74 Author: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Mon Nov 24 11:54:55 2025 -0800 Merge branch 'master' into rl-tree-dialog commit 82e1ebe97ecae94f14adadcf77ac7a68d55cc0fe Author: discountchubbs Date: Mon Nov 24 10:23:35 2025 -0800 rm commit da3ff45bb6efe5f0bbd06eb6e4f61a7a3dd844b0 Merge: 41da513fca a829a1b972 Author: discountchubbs Date: Mon Nov 24 10:19:08 2025 -0800 Merge remote-tracking branch 'origin/rl-tree-dialog' into rl-tree-dialog commit 41da513fcaf5a5ff302427734813b005b6385f0b Author: discountchubbs Date: Mon Nov 24 10:18:43 2025 -0800 better tree. fully dynamic and stuff commit b2950149fbafda7d58bb5ed2b316f59ccc8a47eb Merge: 4fb8e4beed 924e5a3211 Author: discountchubbs Date: Mon Nov 24 10:17:51 2025 -0800 Merge remote-tracking branch 'origin/input-dialog' into rl-tree-dialog commit a829a1b97269de24dc707188808ad5b623968dfa Merge: 848290d07e 9edc36ca66 Author: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Mon Nov 24 10:16:28 2025 -0800 Merge branch 'master' into rl-tree-dialog commit 4fb8e4beede0e6771c25aaac7c8b8f90b2f21c03 Merge: 848290d07e af4f0f8372 Author: discountchubbs Date: Mon Nov 24 10:16:20 2025 -0800 Merge remote-tracking branch 'origin/fuzzy-dialog' into rl-tree-dialog commit af4f0f8372a63fdaeb94fe3b64703fbcca057b9c Merge: 1d5f0ab282 3cd55260d9 Author: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Mon Nov 24 06:39:29 2025 -0800 Merge branch 'master' into fuzzy-dialog commit 1d5f0ab282a01419d03e856b4a5a205f3bfb02aa Author: discountchubbs Date: Sun Nov 23 11:28:59 2025 -0800 ui: fuzzy search helper commit 848290d07eaab2ed5ba948bc8dd63338dc070ac1 Author: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Fri Nov 21 21:08:07 2025 -0800 Update raylib_screenshots.py Removed the parameter setting for 'sunnypilot_ui' in the test. commit 6694928a467641d500bf7b81ad82308ff1652eb7 Author: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Fri Nov 21 21:06:57 2025 -0800 Remove 'sunnypilot_ui' Removed 'sunnypilot_ui' parameter from params_keys.h commit b3c90ef7b2bdd0328442cdbeff7b13bcb260744e Merge: 0d3bc959c8 457b6634fd Author: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Fri Nov 21 21:06:04 2025 -0800 Merge branch 'master' into rl-tree-dialog commit 924e5a3211b3db71d785cd578ee2bdc31de5f495 Merge: a4ee4ba76d d92d2cb683 Author: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Fri Nov 21 19:33:18 2025 -0800 Merge branch 'master' into input-dialog commit a4ee4ba76d238fbb8da5261056da649e1dfbaab9 Merge: e911de5968 4f13a0f775 Author: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Fri Nov 21 16:24:31 2025 -0800 Merge branch 'master' into input-dialog commit e911de59684c44e02784ca98937cbdeb76401e05 Merge: cea6e00819 0ba5cbea91 Author: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Fri Nov 21 13:50:33 2025 -0800 Merge branch 'master' into input-dialog commit cea6e00819af66f3b46b9d72282a4e550c325798 Merge: d7b8ce86ed 8184cd8a6a Author: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Fri Nov 21 12:01:45 2025 -0800 Merge branch 'master' into input-dialog commit 0d3bc959c8547d97b14503cec73a67f5760ffb99 Author: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Wed Nov 19 20:29:30 2025 -0800 Update process.py commit 4f3c19ffb54db603528280e9aa941f719c764185 Author: James Vecellio Date: Wed Nov 19 20:28:59 2025 -0800 save the trees we got icons commit ae5c44355db6a625f9e257db2f62b2a5691a827f Author: discountchubbs Date: Wed Nov 19 13:38:04 2025 -0800 the heck commit 066438ad107ce9a3e9b4b2a1c0de062c523bc747 Merge: 9532675814 e74460f3a8 Author: discountchubbs Date: Wed Nov 19 12:18:17 2025 -0800 Merge remote-tracking branch 'origin/rl-tree-dialog' into rl-tree-dialog commit 95326758146bfe690b826d319c40f3bc7140392c Author: discountchubbs Date: Wed Nov 19 12:17:52 2025 -0800 less trees for the planet commit e74460f3a82d1338edb667bdf3423560270cbeab Merge: c347db376a 423a7d2ed0 Author: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Wed Nov 19 09:37:44 2025 -0800 Merge branch 'rl-sp-toggles' into rl-tree-dialog commit c347db376a08d3f3ee6d5cd66176bc12ec842582 Author: discountchubbs Date: Wed Nov 19 09:36:33 2025 -0800 tree dialog commit c9bd67b26121fce0637aeec924b777ef73a66575 Author: discountchubbs Date: Wed Nov 19 09:34:08 2025 -0800 merge origin raylib toggles commit d7b8ce86edaccb89452db51f4f69cf67643a0824 Author: discountchubbs Date: Mon Nov 17 20:21:33 2025 -0800 compare vs what used to be done before InputDialog commit 2d3d104658d660e8f9f7c12e312544c419a5d406 Merge: ded02895f4 f1025f6ee9 Author: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Mon Nov 17 19:24:20 2025 -0800 Merge branch 'master' into input-dialog commit ded02895f44a241afd3c9857760a18ec5e1406c4 Author: discountchubbs Date: Mon Nov 17 19:22:01 2025 -0800 dialog txt commit 9778a925b09438c7eb9618dfb0fc38eb13d53a39 Merge: cb03d08397 08e85808c5 Author: Jason Wen Date: Sun Nov 16 03:16:58 2025 -0500 input dialog commit 423a7d2ed0249e12743c2b36e1053d5a446f8d33 Author: nayan Date: Sun Nov 16 11:15:28 2025 -0500 fix ui preview commit e4e10d4b87d0bdf4fe2e766122935e5ee3706a5b Author: nayan Date: Sun Nov 16 11:15:22 2025 -0500 fix callback commit 362e9ce04becc1ef1d16d2f23daa01cb2d5303c4 Author: nayan Date: Sun Nov 16 09:53:28 2025 -0500 sp raylib preview commit 3946e643f68fdb5784a2b42839162903a5f4a592 Author: nayan Date: Sat Nov 15 20:24:20 2025 -0500 optimizations commit 0c37a385967c201d7db6e23ca3506bc21a22d698 Author: nayan Date: Sat Nov 15 09:42:12 2025 -0500 Lint commit 9c5acf61c047c81fd17bd02e9354225aabf5f7e8 Author: nayan Date: Sat Nov 15 09:29:07 2025 -0500 SP Toggles commit 121b304fe0dcd8bfad02a05aaba6d3e0c9d3029e Author: nayan Date: Sat Nov 15 09:28:58 2025 -0500 init styles commit 47d848293b54a6adf83c964120ed74dfbdf58359 Author: nayan Date: Sat Nov 15 09:28:43 2025 -0500 param to control stock vs sp ui * Squashed commit of the following: commit 70ad001add04810c6d30fd27e44299245b2b5c86 Merge: 142663c490 844f4cbc74 Author: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Mon Nov 24 11:54:58 2025 -0800 Merge branch 'master' into rl-progress-bar commit 142663c490b8ec88770fc0d8bc16cfcfff3f7a62 Author: discountchubbs Date: Sat Nov 22 20:12:44 2025 -0800 smoother updating commit 4476e418dd5cbd84e7ef1c65bc85c21a7806c6c7 Author: discountchubbs Date: Sat Nov 22 09:55:17 2025 -0800 easier to see commit ad66c22e88569c86f64095fab56a3c79310b63a3 Merge: 0c46ef5948 457b6634fd Author: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Sat Nov 22 07:45:05 2025 -0800 Merge branch 'master' into rl-progress-bar commit 0c46ef59482f678ec944ce772ac4202d257d29fd Author: discountchubbs Date: Sat Nov 22 07:42:59 2025 -0800 freaking test dir commit 11c19aad246b1e0bba02f49347517cf9af41a9d8 Merge: 7785238d54 8184cd8a6a Author: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Fri Nov 21 12:01:36 2025 -0800 Merge branch 'master' into rl-progress-bar commit 7785238d54e60f537418aea319e449ebccccbeac Author: discountchubbs Date: Wed Nov 19 07:47:10 2025 -0800 raylib: progress bar * OSM panel * fetching * only show if fav_param is used in the call * flattened and custom search query * conditional for mypy * sunny's new x,y makes this even easier! * download all * add back the rough estimate * not sure i like the 'Download' * simplify the path * actual size as of today * format * more simple * only show on download or delete * loathing loathing, unadulterated loathing, i loathe it all * loathing loathing, unadulterated loathing, i loathe it all * # Conflicts: # system/ui/sunnypilot/lib/styles.py # system/ui/sunnypilot/widgets/tree_dialog.py * search * st * Update osm.py * one second updates: its heavy process, which isnt really noticeable during downloads ayways. the once a second ensures responsiveness on the ui, while also maintaining 20fps on device for country/state downloads. * efficient? i hope * big boi texts * big boi texts * use our own classes * need to clear all params when delete all * more * collateral lol * do not behave as selected if canceled during US->States dialog * more * instead of timestamp, let's just show formatted time * disable button when downloading dbs * should be the buttons being disabled * well gotta re-enable them too * empty country * might be bigger now * fixes for mapd manager * should stay as a json * sanitize it a bit * revert * only nuke if the cancel button is called * always try to update the labels * Revert "always try to update the labels" This reverts commit ba0988fc06734234598f8235fdfe10270e1a3025. * re-enable button after download is complete * disable all while downloading (till we could cancel and re-download) * fix progress bar not filling up as intended for smaller total counts * revert * use new --------- Co-authored-by: nayan Co-authored-by: Jason Wen --- .../ui/sunnypilot/layouts/settings/osm.py | 222 +++++++++++++++++- sunnypilot/mapd/mapd_manager.py | 9 +- 2 files changed, 213 insertions(+), 18 deletions(-) diff --git a/selfdrive/ui/sunnypilot/layouts/settings/osm.py b/selfdrive/ui/sunnypilot/layouts/settings/osm.py index d57a0de9d..3726a820f 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/osm.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/osm.py @@ -4,27 +4,229 @@ Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. This file is part of sunnypilot and is licensed under the MIT License. See the LICENSE.md file in the root directory for more details. """ +import datetime +import os +import platform +import requests +import shutil +import threading +from pathlib import Path +from time import monotonic + from openpilot.common.params import Params +from openpilot.selfdrive.ui.ui_state import device, ui_state +from openpilot.selfdrive.ui.layouts.settings.software import time_ago +from openpilot.system.hardware.hw import Paths +from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.lib.multilang import tr +from openpilot.system.ui.widgets import DialogResult, Widget +from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog +from openpilot.system.ui.widgets.list_view import text_item from openpilot.system.ui.widgets.scroller_tici import Scroller -from openpilot.system.ui.widgets import Widget + +from openpilot.system.ui.sunnypilot.lib.utils import NoElideButtonAction +from openpilot.system.ui.sunnypilot.widgets.list_view import ListItemSP +from openpilot.system.ui.sunnypilot.widgets.tree_dialog import TreeFolder, TreeNode, TreeOptionDialog +from openpilot.system.ui.sunnypilot.widgets.progress_bar import progress_item + +MAP_PATH = Path(Paths.mapd_root()) / "offline" class OSMLayout(Widget): def __init__(self): super().__init__() - - self._params = Params() - items = self._initialize_items() - self._scroller = Scroller(items, line_separator=True, spacing=0) + self._current_percent = 0 + self._last_map_size_update = 0 + self._mem_params = Params("/dev/shm/params") if platform.system() != "Darwin" else ui_state.params + self._initialize_items() + self._update_map_size() + self._progress.set_visible(False) + self._state_btn.set_visible(False) + self._mapd_version.action_item.set_text(ui_state.params.get("MapdVersion") or "Loading...") + self._scroller = Scroller(self.items, line_separator=True, spacing=0) def _initialize_items(self): - items = [ + self._mapd_version = text_item(tr("Mapd Version"), lambda: ui_state.params.get("MapdVersion") or "Loading...") + self._delete_maps_btn = ListItemSP(tr("Downloaded Maps"), action_item=NoElideButtonAction(tr("DELETE"), enabled=True), callback=self._delete_maps) + self._progress = progress_item(tr("Downloading Map")) + self._update_btn = ListItemSP(tr("Database Update"), action_item=NoElideButtonAction(tr("CHECK"), enabled=True), callback=self._update_db) + self._country_btn = ListItemSP(tr("Country"), action_item=NoElideButtonAction(tr("SELECT"), enabled=True), callback=lambda: self._select_region("Country")) + self._state_btn = ListItemSP(tr("State"), action_item=NoElideButtonAction(tr("SELECT"), enabled=True), callback=lambda: self._select_region("State")) - ] - return items + self.items = [self._mapd_version, self._delete_maps_btn, self._progress, self._update_btn, self._country_btn, self._state_btn] - def _render(self, rect): - self._scroller.render(rect) + def _show_confirm(self, msg, confirm_text, func): + gui_app.set_modal_overlay(ConfirmDialog(msg, confirm_text), lambda res: func() if res == DialogResult.CONFIRM else None) + + def calculate_size(self): + total_size = 0 + directories_to_scan = [MAP_PATH] if MAP_PATH.exists() else [] + while directories_to_scan: + try: + for entry in os.scandir(directories_to_scan.pop()): + if entry.is_file(): + total_size += entry.stat().st_size + elif entry.is_dir(): + directories_to_scan.append(entry.path) + except OSError: + pass + self._delete_maps_btn.action_item.set_value(f"{total_size / 1024 ** 2:.2f} MB" if total_size < 1024 ** 3 else f"{total_size / 1024 ** 3:.2f} GB") + + def _update_map_size(self): + threading.Thread(target=self.calculate_size, daemon=True).start() + + def _do_delete_maps(self): + if MAP_PATH.exists(): + shutil.rmtree(MAP_PATH) + + for param in ("OsmDownloadedDate", "OsmLocal", "OsmLocationName", "OsmLocationTitle", "OsmStateName", "OsmStateTitle"): + ui_state.params.remove(param) + + self._delete_maps_btn.action_item.set_enabled(True) + self._delete_maps_btn.action_item.set_text(tr("DELETE")) + self._update_map_size() + + def _on_confirm_delete_maps(self): + self._delete_maps_btn.action_item.set_enabled(False) + self._delete_maps_btn.action_item.set_text("DELETING...") + threading.Thread(target=self._do_delete_maps).start() + + def _delete_maps(self): + self._show_confirm(tr("This will delete ALL downloaded maps\n\nAre you sure you want to delete all maps?"), + tr("Yes, delete all maps"), self._on_confirm_delete_maps) + + def _update_db(self): + self._show_confirm(tr("This will start the download process and it might take a while to complete."), tr("Start Download"), + lambda: ui_state.params.put_bool("OsmDbUpdatesCheck", True)) + + def _select_region(self, region_type): + is_country = region_type == "Country" + btn = self._country_btn if is_country else self._state_btn + btn.action_item.set_enabled(False) + btn.action_item.set_text(tr("FETCHING...")) + threading.Thread(target=self._do_select_region, args=(region_type, btn)).start() + + def _handle_region_selection(self, region_type, locations, key, res, ref): + if res != DialogResult.CONFIRM or not ref: + if region_type == "State" and res == DialogResult.CANCEL: + if ui_state.params.get("OsmLocationName") == "US" and not ui_state.params.get("OsmStateName"): + ui_state.params.remove("OsmLocationName") + ui_state.params.remove("OsmLocationTitle") + ui_state.params.remove("OsmLocal") + self._update_labels() + return + + if region_type == "Country": + ui_state.params.put_bool("OsmLocal", True) + ui_state.params.remove("OsmStateName") + ui_state.params.remove("OsmStateTitle") + + ui_state.params.put(f"{key}Name", ref) + name = next((n.data['display_name'] for n in locations if n.ref == ref), ref) + ui_state.params.put(f"{key}Title", name) + + if ref == "US" and region_type == "Country": + self._select_region("State") + else: + self._update_db() + + def _do_select_region(self, region_type, btn): + base_url = "https://raw.githubusercontent.com/pfeiferj/openpilot-mapd/main/" + url = base_url + ("nation_bounding_boxes.json" if region_type == "Country" else "us_states_bounding_boxes.json") + try: + data = requests.get(url, timeout=10).json() + locations = sorted([TreeNode(ref=k, data={'display_name': v['full_name']}) for k, v in data.items()], key=lambda n: n.data['display_name']) + except Exception: + locations = [] + + if region_type == "State": + locations.insert(0, TreeNode(ref="All", data={'display_name': tr("All states (~6.0 GB)")})) + + btn.action_item.set_enabled(True) + btn.action_item.set_text(tr("SELECT")) + + key = "OsmLocation" if region_type == "Country" else "OsmState" + current = ui_state.params.get(f"{key}Name") or "" + + dialog = TreeOptionDialog(tr(f"Select {region_type}"), [TreeFolder(folder="", nodes=locations)], current_ref=current, search_prompt="Perform a search") + dialog.on_exit = lambda res: self._handle_region_selection(region_type, locations, key, res, dialog.selection_ref) + gui_app.set_modal_overlay(dialog, callback=lambda res: self._handle_region_selection(region_type, locations, key, res, dialog.selection_ref)) + + def _update_labels(self): + downloading = bool(self._mem_params.get("OSMDownloadLocations")) + self._country_btn.set_enabled(not downloading) + self._state_btn.set_enabled(not downloading) + self._state_btn.set_visible(ui_state.params.get("OsmLocationName") == "US") + self._update_btn.set_visible(bool(ui_state.params.get("OsmLocationName"))) + + self._country_btn.action_item.set_value(ui_state.params.get("OsmLocationTitle") or "") + self._state_btn.action_item.set_value(ui_state.params.get("OsmStateTitle") or "") + + pending = ui_state.params.get_bool("OsmDbUpdatesCheck") + if downloading or pending: + if downloading: + device.reset_interactive_timeout() + self._update_map_size() + self._progress.set_visible(True) + progress = ui_state.params.get("OSMDownloadProgress") + total = progress.get('total_files', 0) if progress else 0 + done = progress.get('downloaded_files', 0) if progress else 0 + failed = total > 0 and not downloading and done < total + + if total > 0: + progress_perc = max(0.0, min(100.0, (done / total) * 100.0)) + else: + progress_perc = 0.0 + + if failed: + text = "0% - Downloading Maps" + btn_text = tr("Error: Invalid download. Retry.") + self._current_percent = 0.0 + elif total > 0 and downloading: + self._current_percent = progress_perc + perc_int = int(progress_perc) + text = f"{perc_int}% - Downloading Maps" + btn_text = f"{done}/{total} ({perc_int}%)" + else: + self._current_percent = 0.0 + text = "0% - Downloading Maps" + btn_text = tr("Downloading Maps...") + + self._progress.action_item.update(self._current_percent, text, show_progress=total > 0 and downloading and not failed) + self._update_btn.action_item.set_enabled(not downloading) # TODO-SP: introduce CANCEL database download with mapd + self._update_btn.action_item.set_value(btn_text) + self._country_btn.action_item.set_enabled(not downloading) + self._state_btn.action_item.set_enabled(not downloading) + self._delete_maps_btn.action_item.set_enabled(not downloading) + else: + self._progress.set_visible(False) + self._update_btn.action_item.set_enabled(True) + self._country_btn.action_item.set_enabled(True) + self._state_btn.action_item.set_enabled(True) + self._delete_maps_btn.action_item.set_enabled(True) + + ts = ui_state.params.get("OsmDownloadedDate") + dt: datetime.datetime | None = None + + if ts: + try: + ts_f = float(ts) + if ts_f > 0: + dt = datetime.datetime.fromtimestamp(ts_f, tz=datetime.UTC) + except (ValueError, TypeError): + dt = None + + formatted = time_ago(dt) + self._update_btn.action_item.set_value(tr("Last checked {}").format(formatted)) def show_event(self): self._scroller.show_event() + + def _update_state(self): + now = monotonic() + if now - self._last_map_size_update >= 1.0: + self._last_map_size_update = now + self._update_labels() + + def _render(self, rect): + self._scroller.render(rect) diff --git a/sunnypilot/mapd/mapd_manager.py b/sunnypilot/mapd/mapd_manager.py index 1211c1ecc..9304f8f0b 100755 --- a/sunnypilot/mapd/mapd_manager.py +++ b/sunnypilot/mapd/mapd_manager.py @@ -64,12 +64,7 @@ def request_refresh_osm_location_data(nations: list[str], states: list[str] = No "states": states or [] } - osm_download_locations_dump = json.dumps({ - "nations": nations, - "states": states or [] - }) - - print(f"Downloading maps for {osm_download_locations_dump}") + print(f"Downloading maps for {json.dumps(osm_download_locations)}") mem_params.put("OSMDownloadLocations", osm_download_locations) @@ -103,8 +98,6 @@ def filter_nations_and_states(nations: list[str], states: list[str] = None) -> t def update_osm_db() -> None: - # last_downloaded_date = params.get("OsmDownloadedDate", return_default=True) - # if params.get_bool("OsmDbUpdatesCheck") or time.monotonic() - last_downloaded_date >= 604800: # 7 days * 24 hours/day * 60 if params.get_bool("OsmDbUpdatesCheck"): cleanup_old_osm_data(get_files_for_cleanup()) country = params.get("OsmLocationName", return_default=True) From 46257aca02777737b04e2ce371e3a6e11c4f6a02 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 12 Dec 2025 03:16:57 -0500 Subject: [PATCH 587/910] ui: `HtmlModalSP` (#1570) * ui: `HtmlModalSP` * less --- system/ui/sunnypilot/widgets/html_render.py | 27 +++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 system/ui/sunnypilot/widgets/html_render.py diff --git a/system/ui/sunnypilot/widgets/html_render.py b/system/ui/sunnypilot/widgets/html_render.py new file mode 100644 index 000000000..259067723 --- /dev/null +++ b/system/ui/sunnypilot/widgets/html_render.py @@ -0,0 +1,27 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.widgets import DialogResult +from openpilot.system.ui.widgets.html_render import HtmlModal + + +class HtmlModalSP(HtmlModal): + def __init__(self, file_path=None, text=None, callback=None): + super().__init__(file_path=file_path, text=text) + self._callback = callback + self._dialog_result = DialogResult.NO_ACTION + self._ok_button._click_callback = self._on_ok_clicked + + def _on_ok_clicked(self): + self._dialog_result = DialogResult.CONFIRM + gui_app.set_modal_overlay(None) + + if self._callback: + self._callback(self._dialog_result) + + def reset(self): + self._dialog_result = DialogResult.NO_ACTION From 9421e1cbfebdb2970afd777cf4642bc5af5795cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Fri, 12 Dec 2025 18:04:16 -0800 Subject: [PATCH 588/910] Dark Souls 2 (#36849) 4b78e2e6-660f-4155-9105-81d4d8c658cd/400 --- selfdrive/modeld/models/driving_policy.onnx | 2 +- selfdrive/modeld/models/driving_vision.onnx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/modeld/models/driving_policy.onnx b/selfdrive/modeld/models/driving_policy.onnx index 1e764af9b..e0eb91812 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:c5a1f0655ddf266ed42ad1980389d96f47cc5e756da1fa3ca1477a920bb9b157 +oid sha256:f8fe9a71b0fd428a045a82ed50790179f77aa664391198f078e11e7b2cb2c2d7 size 13926324 diff --git a/selfdrive/modeld/models/driving_vision.onnx b/selfdrive/modeld/models/driving_vision.onnx index 441c4a16a..76c96670a 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:8f16d548ea4eb5d01518a9e90d4527cd97c31a84bcaf6f695dead8f0015fecc4 +oid sha256:1dc66bc06f250b577653ccbeaa2c6521b3d46749f601d0a1a366419e929ca438 size 46271942 From cc119b2a37c2503f0e1c54c3f7508113cb29685d Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 12 Dec 2025 21:37:26 -0800 Subject: [PATCH 589/910] comma four: adjust Wifi scroller sizes (#36854) * adjust sizes * back --- .../ui/mici/layouts/settings/network/wifi_ui.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index eec16d884..18c4dd5d6 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -83,11 +83,13 @@ class WifiIcon(Widget): class WifiItem(BigDialogOptionButton): LEFT_MARGIN = 20 + HEIGHT = 54 + SELECTED_HEIGHT = 74 def __init__(self, network: Network): super().__init__(network.ssid) - self.set_rect(rl.Rectangle(0, 0, gui_app.width, 64)) + self.set_rect(rl.Rectangle(0, 0, gui_app.width, self.HEIGHT)) self._selected_txt = gui_app.texture("icons_mici/settings/network/new/wifi_selected.png", 48, 96) @@ -95,6 +97,10 @@ class WifiItem(BigDialogOptionButton): self._wifi_icon = WifiIcon() self._wifi_icon.set_current_network(network) + def set_selected(self, selected: bool): + super().set_selected(selected) + self._rect.height = self.SELECTED_HEIGHT if selected else self.HEIGHT + def set_current_network(self, network: Network): self._network = network self._wifi_icon.set_current_network(network) @@ -109,7 +115,7 @@ class WifiItem(BigDialogOptionButton): self._wifi_icon.render(rl.Rectangle( self._rect.x + self.LEFT_MARGIN, self._rect.y, - self._rect.height, + self.SELECTED_HEIGHT, self._rect.height )) @@ -118,7 +124,7 @@ class WifiItem(BigDialogOptionButton): self._label.set_color(rl.Color(255, 255, 255, int(255 * 0.9))) self._label.set_font_weight(FontWeight.DISPLAY) else: - self._label.set_font_size(70) + self._label.set_font_size(54) self._label.set_color(rl.Color(255, 255, 255, int(255 * 0.58))) self._label.set_font_weight(FontWeight.DISPLAY_REGULAR) From cab2a28e1092a01cfe69f30ec95b59ebd6f3d4c3 Mon Sep 17 00:00:00 2001 From: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Fri, 12 Dec 2025 21:50:57 -0800 Subject: [PATCH 590/910] ui: Developer panel extension (#1521) * ui: developer panel * comment out * double translate * quickboot and more efficient file checking * use HtmlModalSP! * ui: `HtmlModalSP` * less * lint * less * just use existing dir on PC * grammar * match official * rename * biiig --------- Co-authored-by: Jason Wen --- .../sunnypilot/layouts/settings/developer.py | 106 ++++++++++++++++++ .../sunnypilot/layouts/settings/settings.py | 4 +- sunnypilot/sunnylink/params_metadata.json | 4 +- 3 files changed, 110 insertions(+), 4 deletions(-) create mode 100644 selfdrive/ui/sunnypilot/layouts/settings/developer.py diff --git a/selfdrive/ui/sunnypilot/layouts/settings/developer.py b/selfdrive/ui/sunnypilot/layouts/settings/developer.py new file mode 100644 index 000000000..f3a243337 --- /dev/null +++ b/selfdrive/ui/sunnypilot/layouts/settings/developer.py @@ -0,0 +1,106 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +import datetime +import os +from pathlib import Path + +from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.selfdrive.ui.layouts.settings.developer import DeveloperLayout +from openpilot.system.hardware import PC +from openpilot.system.hardware.hw import Paths +from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.lib.multilang import tr +from openpilot.system.ui.widgets import DialogResult +from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog +from openpilot.system.ui.widgets.list_view import button_item + +from openpilot.system.ui.sunnypilot.widgets.html_render import HtmlModalSP +from openpilot.system.ui.sunnypilot.widgets.list_view import toggle_item_sp + +PREBUILT_PATH = os.path.join(Paths.comma_home(), "prebuilt") if PC else "/data/openpilot/prebuilt" + + +class DeveloperLayoutSP(DeveloperLayout): + def __init__(self): + super().__init__() + self.error_log_path = os.path.join(Paths.crash_log_root(), "error.log") + self._is_release_branch: bool = self._is_release or ui_state.params.get_bool("IsReleaseSpBranch") + self._is_development_branch: bool = ui_state.params.get_bool("IsTestedBranch") or ui_state.params.get_bool("IsDevelopmentBranch") + self._initialize_items() + + for item in self.items: + self._scroller.add_widget(item) + + def _initialize_items(self): + self.show_advanced_controls = toggle_item_sp(tr("Show Advanced Controls"), + tr("Toggle visibility of advanced sunnypilot controls.
This only changes the visibility of the toggles; " + + "it does not change the actual enabled/disabled state."), param="ShowAdvancedControls") + + self.enable_github_runner_toggle = toggle_item_sp(tr("GitHub Runner Service"), tr("Enables or disables the GitHub runner service."), + param="EnableGithubRunner") + + self.enable_copyparty_toggle = toggle_item_sp(tr("copyparty Service"), + tr("copyparty is a very capable file server, you can use it to download your routes, view your logs " + + "and even make some edits on some files from your browser. " + + "Requires you to connect to your comma locally via its IP address."), param="EnableCopyparty") + + self.prebuilt_toggle = toggle_item_sp(tr("Quickboot Mode"), "", param="QuickBootToggle", callback=self._on_prebuilt_toggled) + + self.error_log_btn = button_item(tr("Error Log"), tr("VIEW"), tr("View the error log for sunnypilot crashes."), callback=self._on_error_log_clicked) + + self.items: list = [self.show_advanced_controls, self.enable_github_runner_toggle, self.enable_copyparty_toggle, self.prebuilt_toggle, self.error_log_btn,] + + @staticmethod + def _on_prebuilt_toggled(state): + if state: + Path(PREBUILT_PATH).touch(exist_ok=True) + else: + os.remove(PREBUILT_PATH) + ui_state.params.put_bool("QuickBootToggle", state) + + def _on_delete_confirm(self, result): + if result == DialogResult.CONFIRM: + if os.path.exists(self.error_log_path): + os.remove(self.error_log_path) + + def _on_error_log_closed(self, result, log_exists): + if result == DialogResult.CONFIRM and log_exists: + dialog2 = ConfirmDialog(tr("Would you like to delete this log?"), tr("Yes"), tr("No"), rich=False) + gui_app.set_modal_overlay(dialog2, callback=self._on_delete_confirm) + + def _on_error_log_clicked(self): + text = "" + if os.path.exists(self.error_log_path): + text = f"{datetime.datetime.fromtimestamp(os.path.getmtime(self.error_log_path)).strftime('%d-%b-%Y %H:%M:%S').upper()}

" + try: + with open(self.error_log_path) as file: + text += file.read() + except Exception: + pass + dialog = HtmlModalSP(text=text, callback=lambda result: self._on_error_log_closed(result, os.path.exists(self.error_log_path))) + gui_app.set_modal_overlay(dialog) + + def _update_state(self): + disable_updates = ui_state.params.get_bool("DisableUpdates") + show_advanced = ui_state.params.get_bool("ShowAdvancedControls") + + if (prebuilt_file := os.path.exists(PREBUILT_PATH)) != ui_state.params.get_bool("QuickBootToggle"): + ui_state.params.put_bool("QuickBootToggle", prebuilt_file) + self.prebuilt_toggle.action_item.set_state(prebuilt_file) + + self.prebuilt_toggle.set_visible(show_advanced and not (self._is_release_branch or self._is_development_branch)) + self.prebuilt_toggle.action_item.set_enabled(disable_updates) + + if disable_updates: + self.prebuilt_toggle.set_description(tr("When toggled on, this creates a prebuilt file to allow accelerated boot times. When toggled off, it " + + "removes the prebuilt file so compilation of locally edited cpp files can be made.")) + else: + self.prebuilt_toggle.set_description(tr("Quickboot mode requires updates to be disabled.
Enable 'Disable Updates' in the Software panel first.")) + + self.enable_copyparty_toggle.set_visible(show_advanced) + self.enable_github_runner_toggle.set_visible(show_advanced and not self._is_release_branch) + self.error_log_btn.set_visible(not self._is_release_branch) diff --git a/selfdrive/ui/sunnypilot/layouts/settings/settings.py b/selfdrive/ui/sunnypilot/layouts/settings/settings.py index 4de2cacaf..103dd99ef 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/settings.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/settings.py @@ -9,7 +9,6 @@ from enum import IntEnum import pyray as rl from openpilot.selfdrive.ui.layouts.settings import settings as OP -from openpilot.selfdrive.ui.layouts.settings.developer import DeveloperLayout from openpilot.selfdrive.ui.sunnypilot.layouts.settings.device import DeviceLayoutSP from openpilot.selfdrive.ui.layouts.settings.firehose import FirehoseLayout from openpilot.selfdrive.ui.sunnypilot.layouts.settings.software import SoftwareLayoutSP @@ -31,6 +30,7 @@ from openpilot.selfdrive.ui.sunnypilot.layouts.settings.steering import Steering from openpilot.selfdrive.ui.sunnypilot.layouts.settings.cruise import CruiseLayout from openpilot.selfdrive.ui.sunnypilot.layouts.settings.visuals import VisualsLayout from openpilot.selfdrive.ui.sunnypilot.layouts.settings.display import DisplayLayout +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.developer import DeveloperLayoutSP # from openpilot.selfdrive.ui.sunnypilot.layouts.settings.navigation import NavigationLayout @@ -125,7 +125,7 @@ class SettingsLayoutSP(OP.SettingsLayout): OP.PanelType.TRIPS: PanelInfo(tr_noop("Trips"), TripsLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_trips.png"), OP.PanelType.VEHICLE: PanelInfo(tr_noop("Vehicle"), VehicleLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_vehicle.png"), OP.PanelType.FIREHOSE: PanelInfo(tr_noop("Firehose"), FirehoseLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_firehose.png"), - OP.PanelType.DEVELOPER: PanelInfo(tr_noop("Developer"), DeveloperLayout(), icon="icons/shell.png"), + OP.PanelType.DEVELOPER: PanelInfo(tr_noop("Developer"), DeveloperLayoutSP(), icon="icons/shell.png"), } def _draw_sidebar(self, rect: rl.Rectangle): diff --git a/sunnypilot/sunnylink/params_metadata.json b/sunnypilot/sunnylink/params_metadata.json index 83ec3c7af..4cc6b1808 100644 --- a/sunnypilot/sunnylink/params_metadata.json +++ b/sunnypilot/sunnylink/params_metadata.json @@ -258,11 +258,11 @@ "description": "" }, "EnableCopyparty": { - "title": "Enable Copyparty", + "title": "copyparty Service", "description": "" }, "EnableGithubRunner": { - "title": "Enable GitHub Runner", + "title": "GitHub Runner Service", "description": "" }, "EnableSunnylinkUploader": { From e5cc1cb354cacad00ed0e7d4c7f955910bd8a007 Mon Sep 17 00:00:00 2001 From: infiniteCable2 Date: Sat, 13 Dec 2025 09:56:51 +0100 Subject: [PATCH 591/910] Update log.capnp add radar disable events --- cereal/log.capnp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cereal/log.capnp b/cereal/log.capnp index b6dc27dc2..ea880fffb 100644 --- a/cereal/log.capnp +++ b/cereal/log.capnp @@ -130,6 +130,8 @@ struct OnroadEvent @0xc4fa6047f024e718 { userBookmark @95; excessiveActuation @96; audioFeedback @97; + dashcamModeRadDisEngOn @98; + radarDisableFailed @99; soundsUnavailableDEPRECATED @47; } From 85918e88871e8fac9c6265977887ca112bbcdc54 Mon Sep 17 00:00:00 2001 From: infiniteCable2 Date: Sat, 13 Dec 2025 09:57:51 +0100 Subject: [PATCH 592/910] Update card.py add new method for failure detection in CI to put the comma into dashcam mode --- selfdrive/car/card.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/selfdrive/car/card.py b/selfdrive/car/card.py index 0c484c2d9..d28c2914b 100644 --- a/selfdrive/car/card.py +++ b/selfdrive/car/card.py @@ -123,6 +123,10 @@ class Car: self.CI, self.CP, self.CP_SP = CI, CI.CP, CI.CP_SP self.RI = RI + # supply a pre init method to set CP by checking data on the can bus + # e.g. car is in a state where the radar can not be disabled -> set dashcam mode + self.CI.pre_init(self.CP, self.CP_SP, *self.can_callbacks) + self.CP.alternativeExperience = 0 # mads set_alternative_experience(self.CP, self.CP_SP, self.params) From a32f88434c265db20eb0ca5aa0f8cc6247cb98ed Mon Sep 17 00:00:00 2001 From: infiniteCable2 Date: Sat, 13 Dec 2025 09:59:12 +0100 Subject: [PATCH 593/910] Update events.py add radar disable fail events --- selfdrive/selfdrived/events.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/selfdrive/selfdrived/events.py b/selfdrive/selfdrived/events.py index 202e5843e..3ba11c0b7 100755 --- a/selfdrive/selfdrived/events.py +++ b/selfdrive/selfdrived/events.py @@ -271,6 +271,12 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = { priority=Priority.HIGH), }, + EventName.dashcamModeRadDisEngOn: { + ET.PERMANENT: NormalPermanentAlert("Dashcam Mode - Radar Deactivation Failed", + "Engine is running. Retry during ignition.", + priority=Priority.LOWEST), + }, + EventName.dashcamMode: { ET.PERMANENT: NormalPermanentAlert("Dashcam Mode", priority=Priority.LOWEST), @@ -706,6 +712,16 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = { ET.NO_ENTRY: NoEntryAlert("Radar Temporarily Unavailable"), }, + EventName.radarDisableFailed: { + ET.NO_ENTRY: NoEntryAlert("Radar Deactivation Failed"), + ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("Radar Deactivation Failed"), + ET.PERMANENT: Alert( + "Radar Deactivation Failed", + "Retry during ignition", + AlertStatus.userPrompt, AlertSize.mid, + Priority.HIGH, VisualAlert.none, AudibleAlert.warningImmediate, .1), + }, + # Every frame from the camera should be processed by the model. If modeld # is not processing frames fast enough they have to be dropped. This alert is # thrown when over 20% of frames are dropped. From bf3f7ee3669fdc78663cecac4b4d626d31fc1ff6 Mon Sep 17 00:00:00 2001 From: infiniteCable2 Date: Sat, 13 Dec 2025 10:00:32 +0100 Subject: [PATCH 594/910] Update selfdrived.py radar disable failure calls --- selfdrive/selfdrived/selfdrived.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/selfdrive/selfdrived/selfdrived.py b/selfdrive/selfdrived/selfdrived.py index 7b546f378..380399d98 100755 --- a/selfdrive/selfdrived/selfdrived.py +++ b/selfdrive/selfdrived/selfdrived.py @@ -45,6 +45,7 @@ LaneChangeDirection = log.LaneChangeDirection EventName = log.OnroadEvent.EventName ButtonType = car.CarState.ButtonEvent.Type SafetyModel = car.CarParams.SafetyModel +DashcamOnlyReason = car.CarParams.DashcamOnlyReason TurnDirection = custom.ModelDataV2SP.TurnDirection IGNORED_SAFETY_MODES = (SafetyModel.silent, SafetyModel.noOutput) @@ -161,7 +162,10 @@ class SelfdriveD(CruiseHelper): self.events.add(EventName.carUnrecognized, static=True) set_offroad_alert("Offroad_CarUnrecognized", True) elif self.CP.passive: - self.events.add(EventName.dashcamMode, static=True) + if self.CP.dashcamOnlyReason == DashcamOnlyReason.radarDisableEngineOn: + self.events.add(EventName.dashcamModeRadDisEngOn, static=True) + else: + self.events.add(EventName.dashcamMode, static=True) self.events_sp = EventsSP() self.events_sp_prev = [] @@ -237,6 +241,9 @@ class SelfdriveD(CruiseHelper): (CS.regenBraking and (not self.CS_prev.regenBraking or not CS.standstill)): self.events.add(EventName.pedalPressed) + if CS.radarDisableFailed: + self.events.add(EventName.radarDisableFailed) + # Create events for temperature, disk space, and memory if self.sm['deviceState'].thermalStatus >= ThermalStatus.red: self.events.add(EventName.overheat) From b9712fda56d17ed6727df3aa93ca48145617f104 Mon Sep 17 00:00:00 2001 From: infiniteCable2 Date: Sat, 13 Dec 2025 10:06:40 +0100 Subject: [PATCH 595/910] OP Long Support with Camera Harness --- opendbc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc_repo b/opendbc_repo index 01595bfd2..8c56885fb 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 01595bfd20204585c80f5b1c60d04a4c152f1780 +Subproject commit 8c56885fbc7b634643f1273765c4b06c111d584e From 0dd59d04047b8bf52b9e07e25b93bedcee6b42e9 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 13 Dec 2025 01:32:06 -0800 Subject: [PATCH 596/910] comma four: fix missing WiFi show_event (#36858) * can't do this * can do this --- selfdrive/ui/mici/layouts/settings/network/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/selfdrive/ui/mici/layouts/settings/network/__init__.py b/selfdrive/ui/mici/layouts/settings/network/__init__.py index d085fdf55..1faf49311 100644 --- a/selfdrive/ui/mici/layouts/settings/network/__init__.py +++ b/selfdrive/ui/mici/layouts/settings/network/__init__.py @@ -171,6 +171,8 @@ class NetworkLayoutMici(NavWidget): }.get(self._wifi_manager.current_network_metered, 'default')) def _switch_to_panel(self, panel_type: NetworkPanelType): + if panel_type == NetworkPanelType.WIFI: + self._wifi_ui.show_event() self._current_panel = panel_type def _render(self, rect: rl.Rectangle): From 3206784dd8467b1d384293710d26bb0629c3aefe Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 13 Dec 2025 01:35:10 -0800 Subject: [PATCH 597/910] comma four: rm duplicate wifi show_event --- selfdrive/ui/mici/layouts/settings/network/wifi_ui.py | 1 - 1 file changed, 1 deletion(-) diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index 18c4dd5d6..99e41431e 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -350,7 +350,6 @@ class WifiUIMici(BigMultiOptionDialog): # Call super to prepare scroller; selection scroll is handled dynamically super().show_event() self._wifi_manager.set_active(True) - self._scroller.show_event() def hide_event(self): super().hide_event() From 350dc6a50fcc37b3967dbb736ee0417fd31b2946 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 13 Dec 2025 01:39:45 -0800 Subject: [PATCH 598/910] comma four: fix WiFi panel not starting at the top (#36859) * fix * fix --- system/ui/widgets/scroller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/ui/widgets/scroller.py b/system/ui/widgets/scroller.py index 72f76d90c..4858569d2 100644 --- a/system/ui/widgets/scroller.py +++ b/system/ui/widgets/scroller.py @@ -246,7 +246,7 @@ class Scroller(Widget): def show_event(self): super().show_event() if self._reset_scroll_at_show: - self.scroll_to(self.scroll_panel.get_offset()) + self.scroll_panel.set_offset(0.0) for item in self._items: item.show_event() From e9255d1e9c043dbc75169c5efc48bf9e1ac00a56 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 13 Dec 2025 01:41:01 -0800 Subject: [PATCH 599/910] NavWidget: disable nav bar for vertical scrollers (#36857) * disable nav bar vert scroller * cmt --- system/ui/widgets/__init__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/system/ui/widgets/__init__.py b/system/ui/widgets/__init__.py index 546c682f3..097ac74c7 100644 --- a/system/ui/widgets/__init__.py +++ b/system/ui/widgets/__init__.py @@ -270,13 +270,17 @@ class NavWidget(Widget, abc.ABC): in_dismiss_area = mouse_event.pos.y < self._rect.height * self.BACK_TOUCH_AREA_PERCENTAGE scroller_at_top = False + vertical_scroller = False # TODO: -20? snapping in WiFi dialog can make offset not be positive at the top if hasattr(self, '_scroller'): scroller_at_top = self._scroller.scroll_panel.get_offset() >= -20 and not self._scroller._horizontal + vertical_scroller = not self._scroller._horizontal elif hasattr(self, '_scroll_panel'): scroller_at_top = self._scroll_panel.get_offset() >= -20 and not self._scroll_panel._horizontal + vertical_scroller = not self._scroll_panel._horizontal - if in_dismiss_area or scroller_at_top: + # Vertical scrollers need to be at the top to swipe away to prevent erroneous swipes + if (not vertical_scroller and in_dismiss_area) or scroller_at_top: self._can_swipe_away = True self._back_button_start_pos = mouse_event.pos From f4dea7977b8a72d4eb0a4a7afc86e1b1f07d5529 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 13 Dec 2025 01:44:05 -0800 Subject: [PATCH 600/910] ui: improve network sort (#36855) * better sort * clean up --- system/ui/lib/wifi_manager.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index 28bd58f22..7e5f04ef6 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -630,7 +630,8 @@ class WifiManager: known_connections = self._get_connections() networks = [Network.from_dbus(ssid, ap_list, ssid in known_connections) for ssid, ap_list in aps.items()] - networks.sort(key=lambda n: (-n.is_connected, n.ssid.lower())) + # sort with quantized strength to reduce jumping + networks.sort(key=lambda n: (-n.is_connected, -round(n.strength / 100 * 4), n.ssid.lower())) self._networks = networks self._update_ipv4_address() From 7a324fc377819472ce9df9074a5d79338dd93bca Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 13 Dec 2025 01:50:55 -0800 Subject: [PATCH 601/910] comma four: reset WiFi SSID scroll on show (#36861) reset scroll --- selfdrive/ui/mici/widgets/dialog.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index b11056f99..e445c0c9c 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -285,6 +285,10 @@ class BigDialogOptionButton(Widget): font_weight=FontWeight.DISPLAY_REGULAR, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE, scroll=True) + def show_event(self): + super().show_event() + self._label.reset_scroll() + def set_selected(self, selected: bool): self._selected = selected From 6c5be6ddab1748bda122c0ac55c2b1ae5d0dcc47 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 13 Dec 2025 04:08:01 -0800 Subject: [PATCH 602/910] WifiUi: fix infinite wraps (#36863) * fix infinite wrap * fix selection * Revert "fix selection" This reverts commit 555c57922409312bf5d9efedf571994f157b9e44. * revert * revert * revert * revert * cleaner * cleaner * mypy!! --- .../ui/mici/layouts/settings/network/wifi_ui.py | 14 ++++++++------ selfdrive/ui/mici/widgets/dialog.py | 10 +++------- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index 99e41431e..5a79b8a63 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -389,12 +389,6 @@ class WifiUIMici(BigMultiOptionDialog): else: network_button = WifiItem(network) - def show_network_info_page(_network): - self._network_info_page.set_current_network(_network) - self._should_open_network_info_page = True - - network_button.set_click_callback(lambda _net=network, _button=network_button: _button._selected and show_network_info_page(_net)) - self.add_button(network_button) # remove networks no longer present @@ -406,6 +400,14 @@ class WifiUIMici(BigMultiOptionDialog): self._wifi_manager.connect_to_network(ssid, password) self._update_buttons() + def _on_option_selected(self, option: str): + super()._on_option_selected(option) + + # only open if button is already selected + if option in self._networks and option == self._selected_option: + self._network_info_page.set_current_network(self._networks[option]) + self._should_open_network_info_page = True + def _connect_to_network(self, ssid: str): network = self._networks.get(ssid) if network is None: diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index e445c0c9c..2118b62ed 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -331,14 +331,10 @@ class BigMultiOptionDialog(BigDialogBase): self.add_button(BigDialogOptionButton(option)) def add_button(self, button: BigDialogOptionButton): - og_callback = button._click_callback + def click_callback(_btn=button): + self._on_option_selected(_btn.option) - def wrapped_callback(btn=button): - self._on_option_selected(btn.option) - if og_callback: - og_callback() - - button.set_click_callback(wrapped_callback) + button.set_click_callback(click_callback) self._scroller.add_widget(button) def show_event(self): From 2e8586fab5f328e34f902aa349f57699b3f4d8ed Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 13 Dec 2025 04:11:36 -0800 Subject: [PATCH 603/910] WifiUi: remove delayed network panel open (#36865) not used --- selfdrive/ui/mici/layouts/settings/network/wifi_ui.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index 5a79b8a63..3129cd791 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -330,7 +330,6 @@ class WifiUIMici(BigMultiOptionDialog): self._network_info_page = NetworkInfoPage(wifi_manager, self._connect_to_network, self._forget_network, self._open_network_manage_page) self._network_info_page.set_connecting(lambda: self._connecting) - self._should_open_network_info_page = False # wait for scroll_to animation self._loading_animation = LoadingAnimation() @@ -355,12 +354,6 @@ class WifiUIMici(BigMultiOptionDialog): super().hide_event() self._wifi_manager.set_active(False) - def _update_state(self): - super()._update_state() - if self._should_open_network_info_page: - self._should_open_network_info_page = False - self._open_network_manage_page() - def _open_network_manage_page(self, result=None): self._network_info_page.update_networks(self._networks) gui_app.set_modal_overlay(self._network_info_page) @@ -406,7 +399,7 @@ class WifiUIMici(BigMultiOptionDialog): # only open if button is already selected if option in self._networks and option == self._selected_option: self._network_info_page.set_current_network(self._networks[option]) - self._should_open_network_info_page = True + self._open_network_manage_page() def _connect_to_network(self, ssid: str): network = self._networks.get(ssid) From 65008d281fef2e279ab5491219df8bace87894ed Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 13 Dec 2025 04:33:27 -0800 Subject: [PATCH 604/910] comma four: fix WiFi scroll to (#36864) * fix selection * stash * Revert "stash" This reverts commit d04ed66b090641072c86b8ed7ed86dbdbf67fbd9. * clean up * clean up * move * fix --- .../mici/layouts/settings/network/wifi_ui.py | 6 ----- selfdrive/ui/mici/widgets/dialog.py | 26 ++++++++++++++----- system/ui/widgets/scroller.py | 2 +- 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index 3129cd791..ba9da7aaa 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -83,8 +83,6 @@ class WifiIcon(Widget): class WifiItem(BigDialogOptionButton): LEFT_MARGIN = 20 - HEIGHT = 54 - SELECTED_HEIGHT = 74 def __init__(self, network: Network): super().__init__(network.ssid) @@ -97,10 +95,6 @@ class WifiItem(BigDialogOptionButton): self._wifi_icon = WifiIcon() self._wifi_icon.set_current_network(network) - def set_selected(self, selected: bool): - super().set_selected(selected) - self._rect.height = self.SELECTED_HEIGHT if selected else self.HEIGHT - def set_current_network(self, network: Network): self._network = network self._wifi_icon.set_current_network(network) diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index 2118b62ed..b5374791e 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -274,10 +274,13 @@ class BigInputDialog(BigDialogBase): class BigDialogOptionButton(Widget): + HEIGHT = 54 + SELECTED_HEIGHT = 74 + def __init__(self, option: str): super().__init__() self.option = option - self.set_rect(rl.Rectangle(0, 0, int(gui_app.width / 2 + 220), 64)) + self.set_rect(rl.Rectangle(0, 0, int(gui_app.width / 2 + 220), self.HEIGHT)) self._selected = False @@ -291,6 +294,7 @@ class BigDialogOptionButton(Widget): def set_selected(self, selected: bool): self._selected = selected + self._rect.height = self.SELECTED_HEIGHT if selected else self.HEIGHT def _render(self, _): if DEBUG: @@ -302,7 +306,7 @@ class BigDialogOptionButton(Widget): self._label.set_color(rl.Color(255, 255, 255, int(255 * 0.9))) self._label.set_font_weight(FontWeight.DISPLAY) else: - self._label.set_font_size(70) + self._label.set_font_size(54) self._label.set_color(rl.Color(255, 255, 255, int(255 * 0.58))) self._label.set_font_weight(FontWeight.DISPLAY_REGULAR) @@ -323,7 +327,7 @@ class BigMultiOptionDialog(BigDialogBase): self._selected_option: str = self._default_option self._last_selected_option: str = self._selected_option - self._scroller = Scroller([], horizontal=False, pad_start=100, pad_end=100, spacing=0) + self._scroller = Scroller([], horizontal=False, pad_start=100, pad_end=100, spacing=0, snap_items=True) if self._right_btn is not None: self._scroller.set_enabled(lambda: not cast(Widget, self._right_btn).is_pressed) @@ -348,10 +352,20 @@ class BigMultiOptionDialog(BigDialogBase): def _on_option_selected(self, option: str): y_pos = 0.0 for btn in self._scroller._items: - if cast(BigDialogOptionButton, btn).option == option: - y_pos = btn.rect.y + btn = cast(BigDialogOptionButton, btn) + if btn.option == option: + rect_center_y = self._rect.y + self._rect.height / 2 + if btn._selected: + height = btn.rect.height + else: + # when selecting an option under current, account for changing heights + btn_center_y = btn.rect.y + btn.rect.height / 2 # not accurate, just to determine direction + height_offset = BigDialogOptionButton.SELECTED_HEIGHT - BigDialogOptionButton.HEIGHT + height = (BigDialogOptionButton.HEIGHT - height_offset) if rect_center_y < btn_center_y else BigDialogOptionButton.SELECTED_HEIGHT + y_pos = rect_center_y - (btn.rect.y + height / 2) + break - self._scroller.scroll_to(y_pos, smooth=True) + self._scroller.scroll_to(-y_pos, smooth=True) def _selected_option_changed(self): pass diff --git a/system/ui/widgets/scroller.py b/system/ui/widgets/scroller.py index 4858569d2..2074de00b 100644 --- a/system/ui/widgets/scroller.py +++ b/system/ui/widgets/scroller.py @@ -74,7 +74,7 @@ class Scroller(Widget): return # FIXME: the padding correction doesn't seem correct - scroll_offset = self.scroll_panel.get_offset() - pos + self._pad_end + scroll_offset = self.scroll_panel.get_offset() - pos if smooth: self._scrolling_to = scroll_offset else: From 1504e103808ae9a594b4f4bd1b9b3319fbf02838 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 13 Dec 2025 05:14:21 -0800 Subject: [PATCH 605/910] WifiUi: pause updates while user is scrolling (#36866) * pause updates while user is scrolling * clean up --- selfdrive/ui/mici/layouts/settings/network/wifi_ui.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index ba9da7aaa..9f0c13440 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -9,6 +9,7 @@ from openpilot.selfdrive.ui.mici.widgets.dialog import BigMultiOptionDialog, Big from openpilot.system.ui.lib.application import gui_app, MousePos, FontWeight from openpilot.system.ui.widgets import Widget, NavWidget from openpilot.system.ui.lib.wifi_manager import WifiManager, Network, SecurityType +from openpilot.system.ui.lib.scroll_panel2 import ScrollState def normalize_ssid(ssid: str) -> str: @@ -366,6 +367,11 @@ class WifiUIMici(BigMultiOptionDialog): self._network_info_page.update_networks(self._networks) def _update_buttons(self): + # Don't update buttons while user is actively scrolling + scroll_state = self._scroller.scroll_panel.state + if scroll_state != ScrollState.STEADY: + return + for network in self._networks.values(): # pop and re-insert to eliminate stuttering on update (prevents position lost for a frame) network_button_idx = next((i for i, btn in enumerate(self._scroller._items) if btn.option == network.ssid), None) From 1c135f7ff2c2691cd49749f7c7af82c2baa70423 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 13 Dec 2025 05:28:53 -0800 Subject: [PATCH 606/910] WifiUi: pause updates while user is interacting (#36868) int not scroll --- .../ui/mici/layouts/settings/network/wifi_ui.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index 9f0c13440..713716978 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -9,7 +9,6 @@ from openpilot.selfdrive.ui.mici.widgets.dialog import BigMultiOptionDialog, Big from openpilot.system.ui.lib.application import gui_app, MousePos, FontWeight from openpilot.system.ui.widgets import Widget, NavWidget from openpilot.system.ui.lib.wifi_manager import WifiManager, Network, SecurityType -from openpilot.system.ui.lib.scroll_panel2 import ScrollState def normalize_ssid(ssid: str) -> str: @@ -317,6 +316,9 @@ class NetworkInfoPage(NavWidget): class WifiUIMici(BigMultiOptionDialog): + # Wait this long after user interacts with widget to update network list + INACTIVITY_TIMEOUT = 1 + def __init__(self, wifi_manager: WifiManager, back_callback: Callable): super().__init__([], None, None, right_btn_callback=None) @@ -332,6 +334,8 @@ class WifiUIMici(BigMultiOptionDialog): self._connecting: str | None = None self._networks: dict[str, Network] = {} + self._last_interaction_time = rl.get_time() + self._wifi_manager.add_callbacks( need_auth=self._on_need_auth, activated=self._on_activated, @@ -367,9 +371,8 @@ class WifiUIMici(BigMultiOptionDialog): self._network_info_page.update_networks(self._networks) def _update_buttons(self): - # Don't update buttons while user is actively scrolling - scroll_state = self._scroller.scroll_panel.state - if scroll_state != ScrollState.STEADY: + # Don't update buttons while user is actively interacting + if rl.get_time() - self._last_interaction_time < self.INACTIVITY_TIMEOUT: return for network in self._networks.values(): @@ -434,6 +437,11 @@ class WifiUIMici(BigMultiOptionDialog): def _on_disconnected(self): self._connecting = None + def _update_state(self): + super()._update_state() + if self.is_pressed: + self._last_interaction_time = rl.get_time() + def _render(self, _): super()._render(_) From 716ad288bb2d5e40d0fa9fd6ec8c81bf911ec433 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 13 Dec 2025 05:45:42 -0800 Subject: [PATCH 607/910] Widget: implement layout function (#36869) * we can implement layout to fix flashing * reorder * fix faster than normal snap and reduce duplicate calculations * yes --- system/ui/widgets/__init__.py | 10 +++++-- system/ui/widgets/scroller.py | 55 ++++++++++++++++++++--------------- 2 files changed, 38 insertions(+), 27 deletions(-) diff --git a/system/ui/widgets/__init__.py b/system/ui/widgets/__init__.py index 097ac74c7..a3fed6d96 100644 --- a/system/ui/widgets/__init__.py +++ b/system/ui/widgets/__init__.py @@ -100,6 +100,7 @@ class Widget(abc.ABC): if not self.is_visible: return None + self._layout() ret = self._render(self._rect) # Keep track of whether mouse down started within the widget's rectangle @@ -151,13 +152,16 @@ class Widget(abc.ABC): self.__is_pressed[mouse_event.slot] = False self._handle_mouse_event(mouse_event) - @abc.abstractmethod - def _render(self, rect: rl.Rectangle) -> bool | int | None: - """Render the widget within the given rectangle.""" + def _layout(self) -> None: + """Optionally lay out child widgets separately. This is called before rendering.""" def _update_state(self): """Optionally update the widget's non-layout state. This is called before rendering.""" + @abc.abstractmethod + def _render(self, rect: rl.Rectangle) -> bool | int | None: + """Render the widget within the given rectangle.""" + def _update_layout_rects(self) -> None: """Optionally update any layout rects on Widget rect change.""" diff --git a/system/ui/widgets/scroller.py b/system/ui/widgets/scroller.py index 2074de00b..f33ba941b 100644 --- a/system/ui/widgets/scroller.py +++ b/system/ui/widgets/scroller.py @@ -52,6 +52,11 @@ class Scroller(Widget): self._zoom_filter = FirstOrderFilter(1.0, 0.2, 1 / gui_app.target_fps) self._zoom_out_t: float = 0.0 + # layout state + self._visible_items: list[Widget] = [] + self._content_size: float = 0.0 + self._scroll_offset: float = 0.0 + self._item_pos_filter = BounceFilter(0.0, 0.05, 1 / gui_app.target_fps) # when not pressed, snap to closest item to be center @@ -160,28 +165,28 @@ class Scroller(Widget): return self.scroll_panel.get_offset() - def _render(self, _): - visible_items = [item for item in self._items if item.is_visible] + def _layout(self): + self._visible_items = [item for item in self._items if item.is_visible] # Add line separator between items if self._line_separator is not None: - l = len(visible_items) - for i in range(1, len(visible_items)): - visible_items.insert(l - i, self._line_separator) + l = len(self._visible_items) + for i in range(1, len(self._visible_items)): + self._visible_items.insert(l - i, self._line_separator) - content_size = sum(item.rect.width if self._horizontal else item.rect.height for item in visible_items) - content_size += self._spacing * (len(visible_items) - 1) - content_size += self._pad_start + self._pad_end + self._content_size = sum(item.rect.width if self._horizontal else item.rect.height for item in self._visible_items) + self._content_size += self._spacing * (len(self._visible_items) - 1) + self._content_size += self._pad_start + self._pad_end - scroll_offset = self._get_scroll(visible_items, content_size) + self._scroll_offset = self._get_scroll(self._visible_items, self._content_size) rl.begin_scissor_mode(int(self._rect.x), int(self._rect.y), int(self._rect.width), int(self._rect.height)) - self._item_pos_filter.update(scroll_offset) + self._item_pos_filter.update(self._scroll_offset) cur_pos = 0 - for idx, item in enumerate(visible_items): + for idx, item in enumerate(self._visible_items): spacing = self._spacing if (idx > 0) else self._pad_start # Nicely lay out items horizontally/vertically if self._horizontal: @@ -195,29 +200,31 @@ class Scroller(Widget): # Consider scroll if self._horizontal: - x += scroll_offset + x += self._scroll_offset else: - y += scroll_offset + y += self._scroll_offset # Add some jello effect when scrolling if DO_JELLO: if self._horizontal: cx = self._rect.x + self._rect.width / 2 - jello_offset = scroll_offset - np.interp(x + item.rect.width / 2, - [self._rect.x, cx, self._rect.x + self._rect.width], - [self._item_pos_filter.x, scroll_offset, self._item_pos_filter.x]) + jello_offset = self._scroll_offset - np.interp(x + item.rect.width / 2, + [self._rect.x, cx, self._rect.x + self._rect.width], + [self._item_pos_filter.x, self._scroll_offset, self._item_pos_filter.x]) x -= np.clip(jello_offset, -20, 20) else: cy = self._rect.y + self._rect.height / 2 - jello_offset = scroll_offset - np.interp(y + item.rect.height / 2, - [self._rect.y, cy, self._rect.y + self._rect.height], - [self._item_pos_filter.x, scroll_offset, self._item_pos_filter.x]) + jello_offset = self._scroll_offset - np.interp(y + item.rect.height / 2, + [self._rect.y, cy, self._rect.y + self._rect.height], + [self._item_pos_filter.x, self._scroll_offset, self._item_pos_filter.x]) y -= np.clip(jello_offset, -20, 20) # Update item state item.set_position(round(x), round(y)) # round to prevent jumping when settling item.set_parent_rect(self._rect) + def _render(self, _): + for item in self._visible_items: # Skip rendering if not in viewport if not rl.check_collision_recs(item.rect, self._rect): continue @@ -227,17 +234,17 @@ class Scroller(Widget): if scale != 1.0: rl.rl_push_matrix() rl.rl_scalef(scale, scale, 1.0) - rl.rl_translatef((1 - scale) * (x + item.rect.width / 2) / scale, - (1 - scale) * (y + item.rect.height / 2) / scale, 0) + rl.rl_translatef((1 - scale) * (item.rect.x + item.rect.width / 2) / scale, + (1 - scale) * (item.rect.y + item.rect.height / 2) / scale, 0) item.render() rl.rl_pop_matrix() else: item.render() # Draw scroll indicator - if SCROLL_BAR and not self._horizontal and len(visible_items) > 0: - _real_content_size = content_size - self._rect.height + self._txt_scroll_indicator.height - scroll_bar_y = -scroll_offset / _real_content_size * self._rect.height + if SCROLL_BAR and not self._horizontal and len(self._visible_items) > 0: + _real_content_size = self._content_size - self._rect.height + self._txt_scroll_indicator.height + scroll_bar_y = -self._scroll_offset / _real_content_size * self._rect.height scroll_bar_y = min(max(scroll_bar_y, self._rect.y), self._rect.y + self._rect.height - self._txt_scroll_indicator.height) rl.draw_texture_ex(self._txt_scroll_indicator, rl.Vector2(self._rect.x, scroll_bar_y), 0, 1.0, rl.WHITE) From 7cabab69a176b01e0bdd8898d8ea94083b273c94 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 13 Dec 2025 06:15:31 -0800 Subject: [PATCH 608/910] comma four: follow current network (#36862) * stay * whoops * whoops * fix * fix div by z * we can implement layout to fix flashing * Revert "we can implement layout to fix flashing" This reverts commit 7278a1e2a6117aec775ef4fabee2fd68b3d064f3. * random * clean up * wtf * rev * smooth * we can implement layout to fix flashing * snap looks so much better * fix * rev * better name * cmt * less random * even less random * simpler * cmt * clean up * clean up * clean up --- .../ui/mici/layouts/settings/network/wifi_ui.py | 16 ++++++++++++++-- selfdrive/ui/mici/widgets/dialog.py | 4 ++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index 713716978..ed5454d8e 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -334,7 +334,9 @@ class WifiUIMici(BigMultiOptionDialog): self._connecting: str | None = None self._networks: dict[str, Network] = {} + # widget state self._last_interaction_time = rl.get_time() + self._restore_selection = False self._wifi_manager.add_callbacks( need_auth=self._on_need_auth, @@ -390,14 +392,17 @@ class WifiUIMici(BigMultiOptionDialog): # remove networks no longer present self._scroller._items[:] = [btn for btn in self._scroller._items if btn.option in self._networks] + # try to restore previous selection to prevent jumping from adding/removing/reordering buttons + self._restore_selection = True + def _connect_with_password(self, ssid: str, password: str): if password: self._connecting = ssid self._wifi_manager.connect_to_network(ssid, password) self._update_buttons() - def _on_option_selected(self, option: str): - super()._on_option_selected(option) + def _on_option_selected(self, option: str, smooth_scroll: bool = True): + super()._on_option_selected(option, smooth_scroll) # only open if button is already selected if option in self._networks and option == self._selected_option: @@ -443,6 +448,13 @@ class WifiUIMici(BigMultiOptionDialog): self._last_interaction_time = rl.get_time() def _render(self, _): + # Update Scroller layout and restore current selection whenever buttons are updated, before first render + current_selection = self.get_selected_option() + if self._restore_selection and current_selection in self._networks: + self._scroller._layout() + BigMultiOptionDialog._on_option_selected(self, current_selection, smooth_scroll=False) + self._restore_selection = None + super()._render(_) if not self._networks: diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index b5374791e..4021a11c2 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -349,7 +349,7 @@ class BigMultiOptionDialog(BigDialogBase): def get_selected_option(self) -> str: return self._selected_option - def _on_option_selected(self, option: str): + def _on_option_selected(self, option: str, smooth_scroll: bool = True): y_pos = 0.0 for btn in self._scroller._items: btn = cast(BigDialogOptionButton, btn) @@ -365,7 +365,7 @@ class BigMultiOptionDialog(BigDialogBase): y_pos = rect_center_y - (btn.rect.y + height / 2) break - self._scroller.scroll_to(-y_pos, smooth=True) + self._scroller.scroll_to(-y_pos, smooth=smooth_scroll) def _selected_option_changed(self): pass From f287d487e596d85deb1c528e4dc2d96e7e339525 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 13 Dec 2025 06:16:31 -0800 Subject: [PATCH 609/910] GuiScrollPanel2: fix possible crash (#36870) fix crash --- system/ui/lib/scroll_panel2.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/system/ui/lib/scroll_panel2.py b/system/ui/lib/scroll_panel2.py index 00ef95cc8..0859071da 100644 --- a/system/ui/lib/scroll_panel2.py +++ b/system/ui/lib/scroll_panel2.py @@ -175,7 +175,8 @@ class GuiScrollPanel2: # Do not update velocity on the same frame the mouse was released previous_mouse_pos = self._get_mouse_pos(cast(MouseEvent, self._previous_mouse_event)) delta_x = mouse_pos - previous_mouse_pos - self._velocity = delta_x / (mouse_event.t - cast(MouseEvent, self._previous_mouse_event).t) + delta_t = max((mouse_event.t - cast(MouseEvent, self._previous_mouse_event).t), 1e-6) + self._velocity = delta_x / delta_t self._velocity = max(-MAX_SPEED, min(MAX_SPEED, self._velocity)) self._velocity_buffer.append(self._velocity) From a3c638697fc6e7ec4cb9e2dc83af73d5c534b8ef Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 13 Dec 2025 06:26:27 -0800 Subject: [PATCH 610/910] WifiUi: tweak unselected button size (#36871) looks too spaces --- selfdrive/ui/mici/layouts/settings/network/wifi_ui.py | 4 ++-- selfdrive/ui/mici/widgets/dialog.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index ed5454d8e..793bdcf4a 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -114,11 +114,11 @@ class WifiItem(BigDialogOptionButton): )) if self._selected: - self._label.set_font_size(74) + self._label.set_font_size(self.SELECTED_HEIGHT) self._label.set_color(rl.Color(255, 255, 255, int(255 * 0.9))) self._label.set_font_weight(FontWeight.DISPLAY) else: - self._label.set_font_size(54) + self._label.set_font_size(self.HEIGHT) self._label.set_color(rl.Color(255, 255, 255, int(255 * 0.58))) self._label.set_font_weight(FontWeight.DISPLAY_REGULAR) diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index 4021a11c2..3d9aa3f9e 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -274,7 +274,7 @@ class BigInputDialog(BigDialogBase): class BigDialogOptionButton(Widget): - HEIGHT = 54 + HEIGHT = 64 SELECTED_HEIGHT = 74 def __init__(self, option: str): @@ -302,11 +302,11 @@ class BigDialogOptionButton(Widget): # FIXME: offset x by -45 because scroller centers horizontally if self._selected: - self._label.set_font_size(74) + self._label.set_font_size(self.SELECTED_HEIGHT) self._label.set_color(rl.Color(255, 255, 255, int(255 * 0.9))) self._label.set_font_weight(FontWeight.DISPLAY) else: - self._label.set_font_size(54) + self._label.set_font_size(self.HEIGHT) self._label.set_color(rl.Color(255, 255, 255, int(255 * 0.58))) self._label.set_font_weight(FontWeight.DISPLAY_REGULAR) From d9bbc8f5bbbaf14dd5b26e64e573e521a20c1d3f Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 13 Dec 2025 15:17:20 -0500 Subject: [PATCH 611/910] ci: update prebuilt exclusions (#1572) --- .../workflows/sunnypilot-build-prebuilt.yaml | 21 ++++++------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/.github/workflows/sunnypilot-build-prebuilt.yaml b/.github/workflows/sunnypilot-build-prebuilt.yaml index 50456f93d..dacbacbe2 100644 --- a/.github/workflows/sunnypilot-build-prebuilt.yaml +++ b/.github/workflows/sunnypilot-build-prebuilt.yaml @@ -200,37 +200,28 @@ jobs: sudo rm -rf ${OUTPUT_DIR} mkdir -p ${OUTPUT_DIR} rsync -am${RUNNER_DEBUG:+v} \ - --include='**/panda/board/' \ - --include='**/panda/board/obj' \ - --include='**/panda/board/obj/panda.bin.signed' \ - --include='**/panda/board/obj/panda_h7.bin.signed' \ - --include='**/panda/board/obj/bootstub.panda.bin' \ - --include='**/panda/board/obj/bootstub.panda_h7.bin' \ --exclude='.sconsign.dblite' \ --exclude='*.a' \ --exclude='*.o' \ --exclude='*.os' \ --exclude='*.pyc' \ --exclude='moc_*' \ - --exclude='*.cc' \ + --exclude='__pycache__' \ --exclude='Jenkinsfile' \ - --exclude='supercombo.onnx' \ - --exclude='**/panda/board/*' \ - --exclude='**/panda/board/obj/**' \ - --exclude='**/panda/certs/' \ - --exclude='**/panda/crypto/' \ --exclude='**/release/' \ --exclude='**/.github/' \ --exclude='**/selfdrive/ui/replay/' \ --exclude='**/__pycache__/' \ - --exclude='**/selfdrive/ui/*.h' \ - --exclude='**/selfdrive/ui/**/*.h' \ - --exclude='**/selfdrive/ui/qt/offroad/sunnypilot/' \ --exclude='${{env.SCONS_CACHE_DIR}}' \ --exclude='**/.git/' \ --exclude='**/SConstruct' \ --exclude='**/SConscript' \ --exclude='**/.venv/' \ + --exclude='selfdrive/modeld/models/driving_vision.onnx' \ + --exclude='selfdrive/modeld/models/driving_policy.onnx' \ + --exclude='sunnypilot/modeld*/models/supercombo.onnx' \ + --exclude='third_party/*x86*' \ + --exclude='third_party/*Darwin*' \ --delete-excluded \ --chown=comma:comma \ ${BUILD_DIR}/ ${OUTPUT_DIR}/ From e2fd6f34e95c948049b623d27919dcf61f0096bd Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 13 Dec 2025 12:56:32 -0800 Subject: [PATCH 612/910] rm dead unlog_ci_segment.py --- tools/replay/unlog_ci_segment.py | 108 ------------------------------- 1 file changed, 108 deletions(-) delete mode 100755 tools/replay/unlog_ci_segment.py diff --git a/tools/replay/unlog_ci_segment.py b/tools/replay/unlog_ci_segment.py deleted file mode 100755 index e5a7a3ffd..000000000 --- a/tools/replay/unlog_ci_segment.py +++ /dev/null @@ -1,108 +0,0 @@ -#!/usr/bin/env python3 - -import argparse -import bisect -import select -import sys -import termios -import time -import tty -from collections import defaultdict - -import cereal.messaging as messaging -from openpilot.tools.lib.framereader import FrameReader -from openpilot.tools.lib.logreader import LogReader -from openpilot.tools.lib.openpilotci import get_url - -IGNORE = ['initData', 'sentinel'] - - -def input_ready(): - return select.select([sys.stdin], [], [], 0) == ([sys.stdin], [], []) - - -def replay(route, segment, loop): - route = route.replace('|', '/') - - lr = LogReader(get_url(route, segment, "rlog.bz2")) - fr = FrameReader(get_url(route, segment, "fcamera.hevc"), readahead=True) - - # Build mapping from frameId to segmentId from roadEncodeIdx, type == fullHEVC - msgs = [m for m in lr if m.which() not in IGNORE] - msgs = sorted(msgs, key=lambda m: m.logMonoTime) - times = [m.logMonoTime for m in msgs] - frame_idx = {m.roadEncodeIdx.frameId: m.roadEncodeIdx.segmentId for m in msgs if m.which() == 'roadEncodeIdx' and m.roadEncodeIdx.type == 'fullHEVC'} - - socks = {} - lag = 0.0 - i = 0 - max_i = len(msgs) - 2 - - while True: - msg = msgs[i].as_builder() - next_msg = msgs[i + 1] - - start_time = time.monotonic() - w = msg.which() - - if w == 'roadCameraState': - try: - img = fr.get(frame_idx[msg.roadCameraState.frameId]) - img = img[:, ::-1] # Convert RGB to BGR, which is what the camera outputs - msg.roadCameraState.image = img.flatten().tobytes() - except (KeyError, ValueError): - pass - - if w not in socks: - socks[w] = messaging.pub_sock(w) - - try: - if socks[w]: - socks[w].send(msg.to_bytes()) - except messaging.messaging_pyx.MultiplePublishersError: - socks[w] = None - - lag += (next_msg.logMonoTime - msg.logMonoTime) / 1e9 - lag -= time.monotonic() - start_time - - dt = max(lag, 0.0) - lag -= dt - time.sleep(dt) - - if lag < -1.0 and i % 1000 == 0: - print(f"{-lag:.2f} s behind") - - if input_ready(): - key = sys.stdin.read(1) - - # Handle pause - if key == " ": - while True: - if input_ready() and sys.stdin.read(1) == " ": - break - time.sleep(0.01) - - # Handle seek - dt = defaultdict(int, s=10, S=-10)[key] - new_time = msgs[i].logMonoTime + dt * 1e9 - i = bisect.bisect_left(times, new_time) - - i = (i + 1) % max_i if loop else min(i + 1, max_i) - - -if __name__ == "__main__": - parser = argparse.ArgumentParser() - parser.add_argument("--loop", action='store_true') - parser.add_argument("route") - parser.add_argument("segment") - args = parser.parse_args() - - orig_settings = termios.tcgetattr(sys.stdin) - tty.setcbreak(sys.stdin) - - try: - replay(args.route, args.segment, args.loop) - termios.tcsetattr(sys.stdin, termios.TCSADRAIN, orig_settings) - except Exception: - termios.tcsetattr(sys.stdin, termios.TCSADRAIN, orig_settings) - raise From eaed247d49580fdbf3784a887463ffae1f7400cf Mon Sep 17 00:00:00 2001 From: infiniteCable2 Date: Sun, 14 Dec 2025 17:28:46 +0100 Subject: [PATCH 613/910] cleanup --- opendbc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc_repo b/opendbc_repo index 0b64e59a6..6952dfd66 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 0b64e59a69fb3d6391c61da0539480aec1d730ae +Subproject commit 6952dfd667dd3fd2b829d4f8244727187f243760 From 9c2fd8d2be4eff9ffcf31f05c5967d8f9573623e Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 14 Dec 2025 22:44:51 -0500 Subject: [PATCH 614/910] ui: `ButtonSP` (#1575) * ui: `ButtonSP` * final * revert for now * revert --- .../ui/sunnypilot/layouts/settings/models.py | 4 ++-- system/ui/sunnypilot/lib/styles.py | 8 +++++++- system/ui/sunnypilot/widgets/list_view.py | 18 ++++++++++++++++-- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/selfdrive/ui/sunnypilot/layouts/settings/models.py b/selfdrive/ui/sunnypilot/layouts/settings/models.py index 16d406ea9..211aad6a2 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/models.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/models.py @@ -74,7 +74,7 @@ class ModelsLayout(Widget): self.lane_turn_value_control = option_item_sp(tr("Adjust Lane Turn Speed"), "LaneTurnValue", 500, 2000, tr("Set the maximum speed for lane turn desires. Default is 19 mph."), - int(round(100 / CV.MPH_TO_KPH)), None, True, "", style.BUTTON_WIDTH, None, True, + int(round(100 / CV.MPH_TO_KPH)), None, True, "", style.BUTTON_ACTION_WIDTH, None, True, lambda v: f"{int(round(v / 100 * (CV.MPH_TO_KPH if ui_state.is_metric else 1)))}" + f" {'km/h' if ui_state.is_metric else 'mph'}") @@ -86,7 +86,7 @@ class ModelsLayout(Widget): self.delay_control = option_item_sp(tr("Adjust Software Delay"), "LagdToggleDelay", 5, 50, tr("Adjust the software delay when Live Learning Steer Delay is toggled off. The default software delay value is 0.2"), - 1, None, True, "", style.BUTTON_WIDTH, None, True, lambda v: f"{v / 100:.2f}s") + 1, None, True, "", style.BUTTON_ACTION_WIDTH, None, True, lambda v: f"{v / 100:.2f}s") self.lagd_toggle = toggle_item_sp(tr("Live Learning Steer Delay"), "", param="LagdToggle") diff --git a/system/ui/sunnypilot/lib/styles.py b/system/ui/sunnypilot/lib/styles.py index 3e1d2dc40..84fe4efb0 100644 --- a/system/ui/sunnypilot/lib/styles.py +++ b/system/ui/sunnypilot/lib/styles.py @@ -28,7 +28,7 @@ class Base: TOGGLE_BG_HEIGHT = TOGGLE_HEIGHT - 20 # Button Control - BUTTON_WIDTH = 300 + BUTTON_ACTION_WIDTH = 300 BUTTON_HEIGHT = 120 @@ -81,5 +81,11 @@ class DefaultStyleSP(Base): BLUE = rl.Color(0, 134, 233, 255) YELLOW = rl.Color(255, 213, 0, 255) + # Button Colors + BUTTON_ENABLED_OFF = rl.Color(0x39, 0x39, 0x39, 0xFF) + BUTTON_OFF_PRESSED = rl.Color(0x4A, 0x4A, 0x4A, 0xFF) + BUTTON_DISABLED = rl.Color(0x12, 0x12, 0x12, 0xFF) + BUTTON_TEXT_DISABLED = rl.Color(0x5C, 0x5C, 0x5C, 0xFF) + style = DefaultStyleSP diff --git a/system/ui/sunnypilot/widgets/list_view.py b/system/ui/sunnypilot/widgets/list_view.py index ac646df5b..b79ed9271 100644 --- a/system/ui/sunnypilot/widgets/list_view.py +++ b/system/ui/sunnypilot/widgets/list_view.py @@ -11,6 +11,7 @@ from openpilot.common.params import Params from openpilot.system.ui.lib.application import gui_app, MousePos, FontWeight from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.sunnypilot.widgets.toggle import ToggleSP +from openpilot.system.ui.widgets.button import Button from openpilot.system.ui.widgets.label import gui_label from openpilot.system.ui.widgets.list_view import ListItem, ToggleAction, ItemAction, MultipleButtonAction, ButtonAction, \ _resolve_value, BUTTON_WIDTH, BUTTON_HEIGHT, TEXT_PADDING @@ -25,8 +26,21 @@ class ToggleActionSP(ToggleAction): self.toggle = ToggleSP(initial_state=initial_state, callback=callback, param=param) +class ButtonSP(Button): + def _update_state(self): + super()._update_state() + if self.enabled: + if self.is_pressed: + self._background_color = style.BUTTON_OFF_PRESSED + else: + self._background_color = style.BUTTON_ENABLED_OFF + else: + self._background_color = style.BUTTON_DISABLED + self._label.set_text_color(style.BUTTON_TEXT_DISABLED) + + class ButtonActionSP(ButtonAction): - def __init__(self, text: str | Callable[[], str], width: int = style.BUTTON_WIDTH, enabled: bool | Callable[[], bool] = True): + def __init__(self, text: str | Callable[[], str], width: int = style.BUTTON_ACTION_WIDTH, enabled: bool | Callable[[], bool] = True): super().__init__(text=text, width=width, enabled=enabled) self._value_color: rl.Color = style.ITEM_TEXT_VALUE_COLOR @@ -252,7 +266,7 @@ def toggle_item_sp(title: str | Callable[[], str], description: str | Callable[[ def multiple_button_item_sp(title: str | Callable[[], str], description: str | Callable[[], str], buttons: list[str | Callable[[], str]], - selected_index: int = 0, button_width: int = style.BUTTON_WIDTH, callback: Callable = None, + selected_index: int = 0, button_width: int = style.BUTTON_ACTION_WIDTH, callback: Callable = None, icon: str = "", param: str | None = None, inline: bool = False) -> ListItemSP: action = MultipleButtonActionSP(buttons, button_width, selected_index, callback=callback, param=param) return ListItemSP(title=title, description=description, icon=icon, action_item=action, inline=inline) From e7554170b8a6d5196ec063cbc1f5e8e48d49c52d Mon Sep 17 00:00:00 2001 From: Nayan Date: Sun, 14 Dec 2025 23:18:49 -0500 Subject: [PATCH 615/910] ui: `SimpleButtonActionSP` (#1502) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * commaai/openpilot:d05cb31e2e916fba41ba8167030945f427fd811b * bump opendbc * bump opendbc * bump opendbc * bump opendbc * bump opendbc * sunnypilot: remove Qt * cabana: revert to stock Qt * commaai/openpilot:5198b1b079c37742c1050f02ce0aa6dd42b038b9 * commaai/openpilot:954b567b9ba0f3d1ae57d6aa7797fa86dd92ec6e * commaai/openpilot:7534b2a160faa683412c04c1254440e338931c5e * sum more * bump opendbc * not yet * should've been symlink'ed * raylib says wut * quiet mode back * more fixes * no more * too extra red diff on the side * need to bring this back * too extra * let's update docs here * Revert "let's update docs here" This reverts commit 51fe03cd5121e6fdf14657b2c33852c34922b851. * param to control stock vs sp ui * init styles * SP Toggles * Lint * optimizations * multi-button * Lint * param to control stock vs sp ui * init styles * SP Toggles * Lint * optimizations * sp raylib preview * fix callback * fix ui preview * better padding * this * support for next line multi-button * uhh * disabled colors * listitem -> listitemsp * listitem -> listitemsp * add show_description method * remove padding from line separator. like, WHY? 😩😩 * ui: `GuiApplicationExt` * simple button * simple button * add to readme * use gui_app.sunnypilot_ui() * i've got something to confessa * sync * revert * Fix SimpleButtonActionSP not respecting enabled state * some more * ui: `ButtonSP` * slight cleanup * fixes * fix * unused * try this --------- Co-authored-by: Jason Wen Co-authored-by: DevTekVE Co-authored-by: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Co-authored-by: discountchubbs --- system/ui/sunnypilot/lib/styles.py | 4 +++ system/ui/sunnypilot/widgets/list_view.py | 35 +++++++++++++++++++---- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/system/ui/sunnypilot/lib/styles.py b/system/ui/sunnypilot/lib/styles.py index 84fe4efb0..7a29b5bb1 100644 --- a/system/ui/sunnypilot/lib/styles.py +++ b/system/ui/sunnypilot/lib/styles.py @@ -31,6 +31,10 @@ class Base: BUTTON_ACTION_WIDTH = 300 BUTTON_HEIGHT = 120 + # Simple Button Control + SIMPLE_BUTTON_WIDTH = 800 + SIMPLE_BUTTON_HEIGHT = 150 + @dataclass class DefaultStyleSP(Base): diff --git a/system/ui/sunnypilot/widgets/list_view.py b/system/ui/sunnypilot/widgets/list_view.py index b79ed9271..2d7239ae6 100644 --- a/system/ui/sunnypilot/widgets/list_view.py +++ b/system/ui/sunnypilot/widgets/list_view.py @@ -11,7 +11,7 @@ from openpilot.common.params import Params from openpilot.system.ui.lib.application import gui_app, MousePos, FontWeight from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.sunnypilot.widgets.toggle import ToggleSP -from openpilot.system.ui.widgets.button import Button +from openpilot.system.ui.widgets.button import Button, ButtonStyle from openpilot.system.ui.widgets.label import gui_label from openpilot.system.ui.widgets.list_view import ListItem, ToggleAction, ItemAction, MultipleButtonAction, ButtonAction, \ _resolve_value, BUTTON_WIDTH, BUTTON_HEIGHT, TEXT_PADDING @@ -39,6 +39,22 @@ class ButtonSP(Button): self._label.set_text_color(style.BUTTON_TEXT_DISABLED) +class SimpleButtonActionSP(ItemAction): + def __init__(self, button_text: str | Callable[[], str], callback: Callable = None, + enabled: bool | Callable[[], bool] = True, button_width: int = style.SIMPLE_BUTTON_WIDTH): + super().__init__(width=button_width, enabled=enabled) + self.button_action = ButtonSP(button_text, click_callback=callback, button_style=ButtonStyle.NORMAL, + border_radius=20) + + def set_touch_valid_callback(self, touch_callback: Callable[[], bool]) -> None: + super().set_touch_valid_callback(touch_callback) + self.button_action.set_touch_valid_callback(touch_callback) + + def _render(self, rect: rl.Rectangle) -> bool | int | None: + self.button_action.set_enabled(self.enabled) + return self.button_action.render(rect) + + class ButtonActionSP(ButtonAction): def __init__(self, text: str | Callable[[], str], width: int = style.BUTTON_ACTION_WIDTH, enabled: bool | Callable[[], bool] = True): super().__init__(text=text, width=width, enabled=enabled) @@ -179,7 +195,7 @@ class ListItemSP(ListItem): content_width = item_rect.width - (style.ITEM_PADDING * 2) title_width = measure_text_cached(self._font, self.title, style.ITEM_TEXT_FONT_SIZE).x right_width = min(content_width - title_width, right_width) - if isinstance(self.action_item, ToggleAction): + if isinstance(self.action_item, ToggleAction) or isinstance(self.action_item, SimpleButtonActionSP): action_x = item_rect.x else: action_x = item_rect.x + item_rect.width - right_width @@ -196,14 +212,15 @@ class ListItemSP(ListItem): content_x = self._rect.x + style.ITEM_PADDING text_x = content_x - left_action_item = isinstance(self.action_item, ToggleAction) + left_action_item = isinstance(self.action_item, ToggleAction) or isinstance(self.action_item, SimpleButtonActionSP) if left_action_item: + item_height = style.SIMPLE_BUTTON_HEIGHT if isinstance(self.action_item, SimpleButtonActionSP) else style.TOGGLE_HEIGHT left_rect = rl.Rectangle( content_x, - self._rect.y + (style.ITEM_BASE_HEIGHT - style.TOGGLE_HEIGHT) // 2, - style.TOGGLE_WIDTH, - style.TOGGLE_HEIGHT + self._rect.y + (style.ITEM_BASE_HEIGHT - item_height) // 2, + self.action_item.rect.width, + item_height ) text_x = left_rect.x + left_rect.width + style.ITEM_PADDING * 1.5 @@ -259,6 +276,12 @@ class ListItemSP(ListItem): self._html_renderer.render(description_rect) +def simple_button_item_sp(button_text: str | Callable[[], str], callback: Callable | None = None, + enabled: bool | Callable[[], bool] = True, button_width: int = style.SIMPLE_BUTTON_WIDTH) -> ListItemSP: + action = SimpleButtonActionSP(button_text=button_text, enabled=enabled, callback=callback, button_width=button_width) + return ListItemSP(title="", callback=callback, description="", action_item=action) + + def toggle_item_sp(title: str | Callable[[], str], description: str | Callable[[], str] | None = None, initial_state: bool = False, callback: Callable | None = None, icon: str = "", enabled: bool | Callable[[], bool] = True, param: str | None = None) -> ListItemSP: action = ToggleActionSP(initial_state=initial_state, enabled=enabled, callback=callback, param=param) From a1d0f6aa55521fa00202d1d4003a2207ad8cda96 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 14 Dec 2025 23:50:44 -0500 Subject: [PATCH 616/910] ci: use Brewfile for macOS setup and update Homebrew cache keys (#1576) * ci: use Brewfile for macOS setup and update Homebrew cache keys * Brewfile --- .github/workflows/tests.yaml | 9 ++++----- tools/Brewfile | 15 +++++++++++++++ tools/mac_setup.sh | 18 +----------------- 3 files changed, 20 insertions(+), 22 deletions(-) create mode 100644 tools/Brewfile diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 38bb7435e..7ae393bb5 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -115,14 +115,13 @@ jobs: - run: echo "CACHE_COMMIT_DATE=$(git log -1 --pretty='format:%cd' --date=format:'%Y-%m-%d-%H:%M')" >> $GITHUB_ENV - name: Homebrew cache uses: ./.github/workflows/auto-cache - if: false # disabling the cache for now because it is breaking macos builds... with: save: false # No need save here if we manually save it later conditionally path: ~/Library/Caches/Homebrew - key: brew-macos-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }} + key: brew-macos-${{ hashFiles('tools/Brewfile') }}-${{ github.sha }} restore-keys: | - brew-macos-${{ env.CACHE_COMMIT_DATE }} - brew-macos + brew-macos-${{ hashFiles('tools/Brewfile') }} + brew-macos- - name: Install dependencies run: ./tools/mac_setup.sh env: @@ -133,7 +132,7 @@ jobs: if: github.ref == 'refs/heads/master' with: path: ~/Library/Caches/Homebrew - key: brew-macos-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }} + key: brew-macos-${{ hashFiles('tools/Brewfile') }}-${{ github.sha }} - run: git lfs pull - name: Getting scons cache uses: ./.github/workflows/auto-cache diff --git a/tools/Brewfile b/tools/Brewfile new file mode 100644 index 000000000..af610be75 --- /dev/null +++ b/tools/Brewfile @@ -0,0 +1,15 @@ +brew "git-lfs" +brew "capnp" +brew "coreutils" +brew "eigen" +brew "ffmpeg" +brew "glfw" +brew "libusb" +brew "libtool" +brew "llvm" +brew "openssl@3.0" +brew "qt@5" +brew "zeromq" +cask "gcc-arm-embedded" +brew "portaudio" +brew "gcc@13" diff --git a/tools/mac_setup.sh b/tools/mac_setup.sh index 0ae0b3535..ae8a1974a 100755 --- a/tools/mac_setup.sh +++ b/tools/mac_setup.sh @@ -32,23 +32,7 @@ else brew up fi -brew bundle --file=- <<-EOS -brew "git-lfs" -brew "capnp" -brew "coreutils" -brew "eigen" -brew "ffmpeg" -brew "glfw" -brew "libusb" -brew "libtool" -brew "llvm" -brew "openssl@3.0" -brew "qt@5" -brew "zeromq" -cask "gcc-arm-embedded" -brew "portaudio" -brew "gcc@13" -EOS +brew bundle --file=$DIR/Brewfile echo "[ ] finished brew install t=$SECONDS" From 8273b4df5abfbde2f58b7e58a475a5373f14ae4b Mon Sep 17 00:00:00 2001 From: infiniteCable2 Date: Mon, 15 Dec 2025 19:24:32 +0100 Subject: [PATCH 617/910] no gas pressed signal offset difference for mqbevo because of fluctuation in that range when accel not pressed --- opendbc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc_repo b/opendbc_repo index 6952dfd66..836dd2973 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 6952dfd667dd3fd2b829d4f8244727187f243760 +Subproject commit 836dd29732bd2f3d57785ff2686a92912a8ded58 From 9d9e5aa02db229c56538301bcac69bc8fc988de8 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 15 Dec 2025 15:36:28 -0800 Subject: [PATCH 618/910] joystickd: add cruise control resume (#36876) * Add cruise control resume logic based on conditions * simple --- tools/joystick/joystickd.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/joystick/joystickd.py b/tools/joystick/joystickd.py index 673a5bc1d..789dad562 100755 --- a/tools/joystick/joystickd.py +++ b/tools/joystick/joystickd.py @@ -48,6 +48,7 @@ def joystickd_thread(): if CC.longActive: actuators.accel = 4.0 * float(np.clip(joystick_axes[0], -1, 1)) actuators.longControlState = LongCtrlState.pid if sm['carState'].vEgo > CP.vEgoStopping else LongCtrlState.stopping + CC.cruiseControl.resume = actuators.accel > 0.0 if CC.latActive: max_curvature = MAX_LAT_ACCEL / max(sm['carState'].vEgo ** 2, 5) From 9e4c2bcacf1a77200c349ddeaa116ece1941d2ac Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 15 Dec 2025 16:41:16 -0800 Subject: [PATCH 619/910] bump opendbc (#36878) * bump * update docs * bump * gotta go fast --- docs/CARS.md | 237 +++++++++--------- opendbc_repo | 2 +- .../test/process_replay/test_processes.py | 3 +- 3 files changed, 121 insertions(+), 121 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index fcb44154b..e0d61cd41 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -14,12 +14,12 @@ A supported vehicle is one that just works when you install a comma device. All |Acura|RDX 2016-18|AcuraWatch Plus or Advance Package|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Acura|RDX 2019-21|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Acura|TLX 2021|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Audi|A3 2014-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Audi|A3 Sportback e-tron 2017-18|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Audi|Q2 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Audi|Q3 2019-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Audi|RS3 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Audi|S3 2015-17|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Audi|A3 2014-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Audi|A3 Sportback e-tron 2017-18|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Audi|Q2 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Audi|Q3 2019-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Audi|RS3 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Audi|S3 2015-17|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Chevrolet|Bolt EUV 2022-23|Premier or Premier Redline Trim, without Super Cruise Package|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 harness box
- 1 mount
Buy Here
||| |Chevrolet|Bolt EV 2022-23|2LT Trim with Adaptive Cruise Control Package|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 harness box
- 1 mount
Buy Here
||| |Chevrolet|Equinox 2019-22|Adaptive Cruise Control (ACC)|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 harness box
- 1 mount
Buy Here
||| @@ -31,7 +31,7 @@ A supported vehicle is one that just works when you install a comma device. All |Chrysler|Pacifica Hybrid 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Chrysler|Pacifica Hybrid 2019-25|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |comma|body|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|None||| -|CUPRA|Ateca 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|CUPRA|Ateca 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Dodge|Durango 2020-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Ford|Bronco Sport 2021-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Ford|Escape 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| @@ -45,8 +45,8 @@ A supported vehicle is one that just works when you install a comma device. All |Ford|Explorer Hybrid 2020-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Ford|F-150 2021-23|Co-Pilot360 Assist 2.0|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Ford|F-150 Hybrid 2021-23|Co-Pilot360 Assist 2.0|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Ford|Focus 2018[3](#footnotes)|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Ford|Focus Hybrid 2018[3](#footnotes)|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Ford|Focus 2018[2](#footnotes)|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Ford|Focus Hybrid 2018[2](#footnotes)|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Ford|Kuga 2020-23|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Ford|Kuga Hybrid 2020-23|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Ford|Kuga Hybrid 2024|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| @@ -79,7 +79,7 @@ A supported vehicle is one that just works when you install a comma device. All |Honda|Accord Hybrid 2023-25|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Honda|City (Brazil only) 2023|All|openpilot available[1](#footnotes)|0 mph|14 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Honda|Civic 2016-18|Honda Sensing|openpilot|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Honda|Civic 2019-21|All|openpilot available[1](#footnotes)|0 mph|2 mph[5](#footnotes)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Civic 2019-21|All|openpilot available[1](#footnotes)|0 mph|2 mph[4](#footnotes)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Honda|Civic 2022-24|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Honda|Civic Hatchback 2017-18|Honda Sensing|openpilot available[1](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Honda|Civic Hatchback 2019-21|All|openpilot available[1](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| @@ -192,165 +192,164 @@ A supported vehicle is one that just works when you install a comma device. All |Kia|Stinger 2018-20|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Kia|Stinger 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Kia|Telluride 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Lexus|CT Hybrid 2017-18|Lexus Safety System+|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Lexus|ES 2017-18|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lexus|CT Hybrid 2017-18|Lexus Safety System+|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lexus|ES 2017-18|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Lexus|ES 2019-25|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Lexus|ES Hybrid 2017-18|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lexus|ES Hybrid 2017-18|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Lexus|ES Hybrid 2019-25|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Lexus|GS F 2016|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Lexus|IS 2017-19|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Lexus|IS 2022-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Lexus|LC 2024-25|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Lexus|NX 2018-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lexus|NX 2018-19|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Lexus|NX 2020-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Lexus|NX Hybrid 2018-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lexus|NX Hybrid 2018-19|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Lexus|NX Hybrid 2020-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Lexus|RC 2018-20|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Lexus|RC 2023|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Lexus|RX 2016|Lexus Safety System+|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Lexus|RX 2017-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lexus|RX 2016|Lexus Safety System+|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lexus|RX 2017-19|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Lexus|RX 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Lexus|RX Hybrid 2016|Lexus Safety System+|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Lexus|RX Hybrid 2017-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lexus|RX Hybrid 2016|Lexus Safety System+|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lexus|RX Hybrid 2017-19|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Lexus|RX Hybrid 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Lexus|UX Hybrid 2019-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Lincoln|Aviator 2020-24|Co-Pilot360 Plus|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Lincoln|Aviator Plug-in Hybrid 2020-24|Co-Pilot360 Plus|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|MAN|eTGE 2020-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|MAN|TGE 2017-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|MAN|eTGE 2020-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|MAN|TGE 2017-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Mazda|CX-5 2022-25|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Mazda connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Mazda|CX-9 2021-23|All|Stock|0 mph|28 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Mazda connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Nissan[6](#footnotes)|Altima 2019-20, 2024|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan B connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Nissan[6](#footnotes)|Leaf 2018-23|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Nissan[6](#footnotes)|Rogue 2018-20|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Nissan[6](#footnotes)|X-Trail 2017|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Nissan[5](#footnotes)|Altima 2019-20, 2024|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan B connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Nissan[5](#footnotes)|Leaf 2018-23|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Nissan[5](#footnotes)|Rogue 2018-20|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Nissan[5](#footnotes)|X-Trail 2017|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Ram|1500 2019-24|Adaptive Cruise Control (ACC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Ram connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Rivian|R1S 2022-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Rivian A connector
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Rivian|R1T 2022-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Rivian A connector
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|SEAT|Ateca 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|SEAT|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Subaru|Ascent 2019-21|All[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| -|Subaru|Crosstrek 2018-19|EyeSight Driver Assistance[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| -|Subaru|Crosstrek 2020-23|EyeSight Driver Assistance[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| -|Subaru|Forester 2019-21|All[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| -|Subaru|Impreza 2017-19|EyeSight Driver Assistance[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| -|Subaru|Impreza 2020-22|EyeSight Driver Assistance[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| -|Subaru|Legacy 2020-22|All[7](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru B connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| -|Subaru|Outback 2020-22|All[7](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru B connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| -|Subaru|XV 2018-19|EyeSight Driver Assistance[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| -|Subaru|XV 2020-21|EyeSight Driver Assistance[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| -|Škoda|Fabia 2022-23[14](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[16](#footnotes)||| -|Škoda|Kamiq 2021-23[12,14](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[16](#footnotes)||| -|Škoda|Karoq 2019-23[14](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Škoda|Kodiaq 2017-23[14](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Škoda|Octavia 2015-19[14](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Škoda|Octavia RS 2016[14](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Škoda|Octavia Scout 2017-19[14](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Škoda|Scala 2020-23[14](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[16](#footnotes)||| -|Škoda|Superb 2015-22[14](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Tesla[10](#footnotes)|Model 3 (with HW3) 2019-23[9](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Tesla A connector
- 1 USB-C coupler
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Tesla[10](#footnotes)|Model 3 (with HW4) 2024-25[9](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Tesla B connector
- 1 USB-C coupler
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Tesla[10](#footnotes)|Model Y (with HW3) 2020-23[9](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Tesla A connector
- 1 USB-C coupler
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Tesla[10](#footnotes)|Model Y (with HW4) 2024-25[9](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Tesla B connector
- 1 USB-C coupler
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|SEAT|Ateca 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|SEAT|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Subaru|Ascent 2019-21|All[6](#footnotes)|openpilot available[1,7](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|Crosstrek 2018-19|EyeSight Driver Assistance[6](#footnotes)|openpilot available[1,7](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|Crosstrek 2020-23|EyeSight Driver Assistance[6](#footnotes)|openpilot available[1,7](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|Forester 2019-21|All[6](#footnotes)|openpilot available[1,7](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|Impreza 2017-19|EyeSight Driver Assistance[6](#footnotes)|openpilot available[1,7](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|Impreza 2020-22|EyeSight Driver Assistance[6](#footnotes)|openpilot available[1,7](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|Legacy 2020-22|All[6](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru B connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|Outback 2020-22|All[6](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru B connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|XV 2018-19|EyeSight Driver Assistance[6](#footnotes)|openpilot available[1,7](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|XV 2020-21|EyeSight Driver Assistance[6](#footnotes)|openpilot available[1,7](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Škoda|Fabia 2022-23[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[15](#footnotes)||| +|Škoda|Kamiq 2021-23[11,13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[15](#footnotes)||| +|Škoda|Karoq 2019-23[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Škoda|Kodiaq 2017-23[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Škoda|Octavia 2015-19[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Škoda|Octavia RS 2016[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Škoda|Octavia Scout 2017-19[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Škoda|Scala 2020-23[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[15](#footnotes)||| +|Škoda|Superb 2015-22[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Tesla[9](#footnotes)|Model 3 (with HW3) 2019-23[8](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Tesla A connector
- 1 USB-C coupler
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Tesla[9](#footnotes)|Model 3 (with HW4) 2024-25[8](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Tesla B connector
- 1 USB-C coupler
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Tesla[9](#footnotes)|Model Y (with HW3) 2020-23[8](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Tesla A connector
- 1 USB-C coupler
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Tesla[9](#footnotes)|Model Y (with HW4) 2024-25[8](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Tesla B connector
- 1 USB-C coupler
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Toyota|Alphard 2019-20|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Toyota|Alphard Hybrid 2021|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Toyota|Avalon 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Toyota|Avalon 2017-18|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Toyota|Avalon 2019-21|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Avalon 2016|Toyota Safety Sense P|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Avalon 2017-18|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Avalon 2019-21|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Toyota|Avalon 2022|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Toyota|Avalon Hybrid 2019-21|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Avalon Hybrid 2019-21|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Toyota|Avalon Hybrid 2022|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Toyota|C-HR 2017-20|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Toyota|C-HR 2021|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Toyota|C-HR Hybrid 2017-20|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Toyota|C-HR Hybrid 2021-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Toyota|Camry 2018-20|All|Stock|0 mph[11](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Toyota|Camry 2021-24|All|openpilot|0 mph[11](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Camry 2018-20|All|Stock|0 mph[10](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Camry 2021-24|All|openpilot|0 mph[10](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Toyota|Camry Hybrid 2018-20|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Toyota|Camry Hybrid 2021-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Toyota|Corolla 2017-19|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Corolla 2017-19|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Toyota|Corolla 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Toyota|Corolla Cross (Non-US only) 2020-23|All|openpilot|17 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Toyota|Corolla Cross Hybrid (Non-US only) 2020-22|All|openpilot|17 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Toyota|Corolla Hatchback 2019-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Toyota|Corolla Hybrid 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Toyota|Corolla Hybrid (South America only) 2020-23|All|openpilot|17 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Toyota|Highlander 2017-19|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Highlander 2017-19|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Toyota|Highlander 2020-23|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Toyota|Highlander Hybrid 2017-19|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Highlander Hybrid 2017-19|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Toyota|Highlander Hybrid 2020-23|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Toyota|Mirai 2021|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Toyota|Prius 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Toyota|Prius 2017-20|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Prius 2016|Toyota Safety Sense P|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Prius 2017-20|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Toyota|Prius 2021-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Toyota|Prius Prime 2017-20|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Prius Prime 2017-20|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Toyota|Prius Prime 2021-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Toyota|Prius v 2017|Toyota Safety Sense P|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Toyota|RAV4 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Toyota|RAV4 2017-18|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Prius v 2017|Toyota Safety Sense P|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|RAV4 2016|Toyota Safety Sense P|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|RAV4 2017-18|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Toyota|RAV4 2019-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Toyota|RAV4 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Toyota|RAV4 2023-25|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Toyota|RAV4 Hybrid 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Toyota|RAV4 Hybrid 2017-18|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|RAV4 Hybrid 2016|Toyota Safety Sense P|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|RAV4 Hybrid 2017-18|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Toyota|RAV4 Hybrid 2019-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Toyota|RAV4 Hybrid 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Toyota|RAV4 Hybrid 2023-25|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Toyota|Sienna 2018-20|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Volkswagen|Arteon 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Arteon eHybrid 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Arteon R 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Arteon Shooting Brake 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Atlas 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Atlas Cross Sport 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|California 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Caravelle 2020|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|CC 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Crafter 2017-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|e-Crafter 2018-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|e-Golf 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Golf 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Golf Alltrack 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Golf GTD 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Golf GTE 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Golf GTI 2015-21|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Golf R 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Golf SportsVan 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Grand California 2019-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Jetta 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Jetta GLI 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Passat 2015-22[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Passat Alltrack 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Passat GTE 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Polo 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[16](#footnotes)||| -|Volkswagen|Polo GTI 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[16](#footnotes)||| -|Volkswagen|T-Cross 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[16](#footnotes)||| -|Volkswagen|T-Roc 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Taos 2022-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Teramont 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Teramont Cross Sport 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Teramont X 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Tiguan 2018-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Tiguan eHybrid 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Touran 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Toyota|Sienna 2018-20|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Volkswagen|Arteon 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Arteon eHybrid 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Arteon R 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Arteon Shooting Brake 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Atlas 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Atlas Cross Sport 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|California 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Caravelle 2020|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|CC 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Crafter 2017-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|e-Crafter 2018-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|e-Golf 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Golf 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Golf Alltrack 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Golf GTD 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Golf GTE 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Golf GTI 2015-21|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Golf R 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Golf SportsVan 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Grand California 2019-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Jetta 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Jetta GLI 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Passat 2015-22[12](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Passat Alltrack 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Passat GTE 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Polo 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[15](#footnotes)||| +|Volkswagen|Polo GTI 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[15](#footnotes)||| +|Volkswagen|T-Cross 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[15](#footnotes)||| +|Volkswagen|T-Roc 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Taos 2022-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Teramont 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Teramont Cross Sport 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Teramont X 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Tiguan 2018-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Tiguan eHybrid 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Touran 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
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`.
-2By default, this car will use the stock Adaptive Cruise Control (ACC) for longitudinal control. If the Driver Support Unit (DSU) is disconnected, openpilot ACC will replace stock ACC. NOTE: disconnecting the DSU disables Automatic Emergency Braking (AEB).
-3Refers only to the Focus Mk4 (C519) available in Europe/China/Taiwan/Australasia, not the Focus Mk3 (C346) in North and South America/Southeast Asia.
-4See more setup details for GM.
-52019 Honda Civic 1.6L Diesel Sedan does not have ALC below 12mph.
-6See more setup details for Nissan.
-7In the non-US market, openpilot requires the car to come equipped with EyeSight with Lane Keep Assistance.
-8Enabling longitudinal control (alpha) will disable all EyeSight functionality, including AEB, LDW, and RAB.
-9Some 2023 model years have HW4. To check which hardware type your vehicle has, look for Autopilot computer under Software -> Additional Vehicle Information on your vehicle's touchscreen. See this page for more information.
-10See more setup details for Tesla.
-11openpilot operates above 28mph for Camry 4CYL L, 4CYL LE and 4CYL SE which don't have Full-Speed Range Dynamic Radar Cruise Control.
-12Not including the China market Kamiq, which is based on the (currently) unsupported PQ34 platform.
-13Refers only to the MQB-based European B8 Passat, not the NMS Passat in the USA/China/Mideast markets.
-14Some Škoda vehicles are equipped with heated windshields, which are known to block GPS signal needed for some comma four functionality.
-15Only available for vehicles using a gateway (J533) harness. At this time, vehicles using a camera harness are limited to using stock ACC.
-16Model-years 2022 and beyond may have a combined CAN gateway and BCM, which is supported by openpilot in software, but doesn't yet have a harness available from the comma store.
+2Refers only to the Focus Mk4 (C519) available in Europe/China/Taiwan/Australasia, not the Focus Mk3 (C346) in North and South America/Southeast Asia.
+3See more setup details for GM.
+42019 Honda Civic 1.6L Diesel Sedan does not have ALC below 12mph.
+5See more setup details for Nissan.
+6In the non-US market, openpilot requires the car to come equipped with EyeSight with Lane Keep Assistance.
+7Enabling longitudinal control (alpha) will disable all EyeSight functionality, including AEB, LDW, and RAB.
+8Some 2023 model years have HW4. To check which hardware type your vehicle has, look for Autopilot computer under Software -> Additional Vehicle Information on your vehicle's touchscreen. See this page for more information.
+9See more setup details for Tesla.
+10openpilot operates above 28mph for Camry 4CYL L, 4CYL LE and 4CYL SE which don't have Full-Speed Range Dynamic Radar Cruise Control.
+11Not including the China market Kamiq, which is based on the (currently) unsupported PQ34 platform.
+12Refers only to the MQB-based European B8 Passat, not the NMS Passat in the USA/China/Mideast markets.
+13Some Škoda vehicles are equipped with heated windshields, which are known to block GPS signal needed for some comma four functionality.
+14Only available for vehicles using a gateway (J533) harness. At this time, vehicles using a camera harness are limited to using stock ACC.
+15Model-years 2022 and beyond may have a combined CAN gateway and BCM, which is supported by openpilot in software, but doesn't yet have a harness available from the comma store.
## Community Maintained Cars Although they're not upstream, the community has openpilot running on other makes and models. See the 'Community Supported Models' section of each make [on our wiki](https://wiki.comma.ai/). diff --git a/opendbc_repo b/opendbc_repo index 6171d1a97..4bd6ffea2 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 6171d1a976b632c4804e90e74a78370532a2f297 +Subproject commit 4bd6ffea2174f3c0fa01f728d612c0c9498b0b05 diff --git a/selfdrive/test/process_replay/test_processes.py b/selfdrive/test/process_replay/test_processes.py index 5868ca4c1..59e1ae054 100755 --- a/selfdrive/test/process_replay/test_processes.py +++ b/selfdrive/test/process_replay/test_processes.py @@ -44,7 +44,8 @@ segments = [ ("HYUNDAI", "regenAA0FC4ED71E|2025-04-08--22-57-50--0"), ("HYUNDAI2", "regenAFB9780D823|2025-04-08--23-00-34--0"), ("TOYOTA", "regen218A4DCFAA1|2025-04-08--22-57-51--0"), - ("TOYOTA2", "regen107352E20EB|2025-04-08--22-57-46--0"), + # TODO: get new RAV4 route without enableDsu + # ("TOYOTA2", "regen107352E20EB|2025-04-08--22-57-46--0"), ("TOYOTA3", "regen1455E3B4BDF|2025-04-09--03-26-06--0"), ("HONDA", "regenB328FF8BA0A|2025-04-08--22-57-45--0"), ("HONDA2", "regen6170C8C9A35|2025-04-08--22-57-46--0"), From 752ef8696af9687b7ea3d0309ab8f73990d6b61d Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 15 Dec 2025 19:04:11 -0800 Subject: [PATCH 620/910] sensord: remove last of dual IMU support (#36881) --- cereal/log.capnp | 6 +++--- cereal/services.py | 3 --- system/sensord/tests/test_sensord.py | 3 +-- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/cereal/log.capnp b/cereal/log.capnp index 686771e28..3a6432c84 100644 --- a/cereal/log.capnp +++ b/cereal/log.capnp @@ -2524,13 +2524,10 @@ struct Event { controlsState @7 :ControlsState; selfdriveState @130 :SelfdriveState; gyroscope @99 :SensorEventData; - gyroscope2 @100 :SensorEventData; accelerometer @98 :SensorEventData; - accelerometer2 @101 :SensorEventData; magnetometer @95 :SensorEventData; lightSensor @96 :SensorEventData; temperatureSensor @97 :SensorEventData; - temperatureSensor2 @123 :SensorEventData; pandaStates @81 :List(PandaState); peripheralState @80 :PeripheralState; radarState @13 :RadarState; @@ -2693,5 +2690,8 @@ struct Event { liveLocationKalmanDEPRECATED @72 :LiveLocationKalman; liveTracksDEPRECATED @16 :List(LiveTracksDEPRECATED); onroadEventsDEPRECATED @68: List(Car.OnroadEventDEPRECATED); + gyroscope2DEPRECATED @100 :SensorEventData; + accelerometer2DEPRECATED @101 :SensorEventData; + temperatureSensor2DEPRECATED @123 :SensorEventData; } } diff --git a/cereal/services.py b/cereal/services.py index edeca412c..f7269b79c 100755 --- a/cereal/services.py +++ b/cereal/services.py @@ -13,13 +13,10 @@ _services: dict[str, tuple] = { # service: (should_log, frequency, qlog decimation (optional)) # note: the "EncodeIdx" packets will still be in the log "gyroscope": (True, 104., 104), - "gyroscope2": (True, 100., 100), "accelerometer": (True, 104., 104), - "accelerometer2": (True, 100., 100), "magnetometer": (True, 25.), "lightSensor": (True, 100., 100), "temperatureSensor": (True, 2., 200), - "temperatureSensor2": (True, 2., 200), "gpsNMEA": (True, 9.), "deviceState": (True, 2., 1), "touch": (True, 20., 1), diff --git a/system/sensord/tests/test_sensord.py b/system/sensord/tests/test_sensord.py index 1dab65238..5e98e1224 100644 --- a/system/sensord/tests/test_sensord.py +++ b/system/sensord/tests/test_sensord.py @@ -56,8 +56,7 @@ def get_irq_count(irq: int): return sum(per_cpu) def read_sensor_events(duration_sec): - sensor_types = ['accelerometer', 'gyroscope', 'magnetometer', 'accelerometer2', - 'gyroscope2', 'temperatureSensor', 'temperatureSensor2'] + sensor_types = ['accelerometer', 'gyroscope', 'magnetometer', 'temperatureSensor',] socks = {} poller = messaging.Poller() events = defaultdict(list) From 507f4209273246b3c7bef7538a4dbdb72958d634 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 15 Dec 2025 19:19:41 -0800 Subject: [PATCH 621/910] Toyota: prevent roll in ICE after pressing resume while wanting to stay stopped (#36877) * bump * only show alert when user can leave standstill * cmt * stash * bump * bump to master --- opendbc_repo | 2 +- selfdrive/car/car_specific.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/opendbc_repo b/opendbc_repo index 4bd6ffea2..39773a987 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 4bd6ffea2174f3c0fa01f728d612c0c9498b0b05 +Subproject commit 39773a987eaefd680c6befa390d7898945daf2e7 diff --git a/selfdrive/car/car_specific.py b/selfdrive/car/car_specific.py index 9e166a44d..6210983d9 100644 --- a/selfdrive/car/car_specific.py +++ b/selfdrive/car/car_specific.py @@ -90,7 +90,8 @@ class CarSpecificEvents: events = self.create_common_events(CS, CS_prev, extra_gears=extra_gears) if self.CP.openpilotLongitudinalControl: - if CS.cruiseState.standstill and not CS.brakePressed: + # Only can leave standstill when planner wants to move + if CS.cruiseState.standstill and not CS.brakePressed and CC.cruiseControl.resume: events.add(EventName.resumeRequired) if CS.vEgo < self.CP.minEnableSpeed: events.add(EventName.belowEngageSpeed) From 545f7c6f2a37fce738a1081cde89434a067d89a8 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 15 Dec 2025 22:00:39 -0800 Subject: [PATCH 622/910] test_onroad: absolute memory usage test (#36885) * test_onroad: absolute memory usage test * show msgq size * reduce a little * bump msgq * Revert "bump msgq" This reverts commit 683d0ae9fc754f7b72e2bc4b256e9a3b0a60a127. --- msgq_repo | 2 +- selfdrive/test/test_onroad.py | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/msgq_repo b/msgq_repo index a16cf1f60..92999f6bc 160000 --- a/msgq_repo +++ b/msgq_repo @@ -1 +1 @@ -Subproject commit a16cf1f608538d14f66bd6142230d8728f2d0abc +Subproject commit 92999f6bc19c16170ff984473b43c799162faca1 diff --git a/selfdrive/test/test_onroad.py b/selfdrive/test/test_onroad.py index b9506d080..972f09be3 100644 --- a/selfdrive/test/test_onroad.py +++ b/selfdrive/test/test_onroad.py @@ -121,6 +121,7 @@ class TestOnroad: params.put_bool("RecordFront", True) set_params_enabled() os.environ['REPLAY'] = '1' + os.environ['MSGQ_PREALLOC'] = '1' os.environ['TESTING_CLOSET'] = '1' if os.path.exists(Paths.log_root()): shutil.rmtree(Paths.log_root()) @@ -283,11 +284,12 @@ class TestOnroad: print("------------------------------------------------") offset = int(SERVICE_LIST['deviceState'].frequency * LOG_OFFSET) mems = [m.deviceState.memoryUsagePercent for m in self.msgs['deviceState'][offset:]] - print("Memory usage: ", mems) + print("Overall memory usage: ", mems) + print("MSGQ (/dev/shm/) usage: ", subprocess.check_output(["du", "-hs", "/dev/shm"]).split()[0].decode()) # check for big leaks. note that memory usage is # expected to go up while the MSGQ buffers fill up - assert np.average(mems) <= 85, "Average memory usage above 85%" + assert np.average(mems) <= 82, "Average memory usage above 85%" assert np.max(np.diff(mems)) <= 4, "Max memory increase too high" assert np.average(np.diff(mems)) <= 1, "Average memory increase too high" From bcdeec3133643b4a6e2f16071cdcb130f2637c70 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 16 Dec 2025 13:27:14 -0800 Subject: [PATCH 623/910] Reduce pub-sub memory usage by 10x (#36884) less mem --- cereal/messaging/__init__.py | 16 +++++- cereal/messaging/socketmaster.cc | 5 +- cereal/services.py | 42 ++++++++------ msgq_repo | 2 +- selfdrive/debug/analyze-msg-size.py | 82 ++++++++++++++++++++++++++++ selfdrive/pandad/pandad.cc | 3 +- selfdrive/test/test_onroad.py | 2 +- system/loggerd/loggerd.cc | 2 +- system/loggerd/tests/test_loggerd.py | 15 +++-- tools/cabana/streams/devicestream.cc | 4 +- 10 files changed, 144 insertions(+), 29 deletions(-) create mode 100755 selfdrive/debug/analyze-msg-size.py diff --git a/cereal/messaging/__init__.py b/cereal/messaging/__init__.py index b03285f80..0ad846f0f 100644 --- a/cereal/messaging/__init__.py +++ b/cereal/messaging/__init__.py @@ -2,7 +2,7 @@ from msgq.ipc_pyx import Context, Poller, SubSocket, PubSocket, SocketEventHandle, toggle_fake_events, \ set_fake_prefix, get_fake_prefix, delete_fake_prefix, wait_for_one_event from msgq.ipc_pyx import MultiplePublishersError, IpcError -from msgq import fake_event_handle, pub_sock, sub_sock, drain_sock_raw +from msgq import fake_event_handle, drain_sock_raw import msgq import os @@ -18,6 +18,20 @@ from openpilot.common.util import MovingAverage NO_TRAVERSAL_LIMIT = 2**64-1 +def pub_sock(endpoint: str) -> PubSocket: + service = SERVICE_LIST.get(endpoint) + segment_size = service.queue_size if service else 0 + return msgq.pub_sock(endpoint, segment_size) + + +def sub_sock(endpoint: str, poller: Optional[Poller] = None, addr: str = "127.0.0.1", + conflate: bool = False, timeout: Optional[int] = None) -> SubSocket: + service = SERVICE_LIST.get(endpoint) + segment_size = service.queue_size if service else 0 + return msgq.sub_sock(endpoint, poller=poller, addr=addr, conflate=conflate, + timeout=timeout, segment_size=segment_size) + + def reset_context(): msgq.context = Context() diff --git a/cereal/messaging/socketmaster.cc b/cereal/messaging/socketmaster.cc index 7f7e2795c..dfeeb807e 100644 --- a/cereal/messaging/socketmaster.cc +++ b/cereal/messaging/socketmaster.cc @@ -50,7 +50,7 @@ SubMaster::SubMaster(const std::vector &service_list, const std::v assert(services.count(std::string(name)) > 0); service serv = services.at(std::string(name)); - SubSocket *socket = SubSocket::create(message_context.context(), name, address ? address : "127.0.0.1", true); + SubSocket *socket = SubSocket::create(message_context.context(), name, address ? address : "127.0.0.1", true, true, serv.queue_size); assert(socket != 0); bool is_polled = inList(poll, name) || poll.empty(); if (is_polled) poller_->registerSocket(socket); @@ -187,7 +187,8 @@ SubMaster::~SubMaster() { PubMaster::PubMaster(const std::vector &service_list) { for (auto name : service_list) { assert(services.count(name) > 0); - PubSocket *socket = PubSocket::create(message_context.context(), name); + service serv = services.at(std::string(name)); + PubSocket *socket = PubSocket::create(message_context.context(), name, true, serv.queue_size); assert(socket); sockets_[name] = socket; } diff --git a/cereal/services.py b/cereal/services.py index f7269b79c..e7350acea 100755 --- a/cereal/services.py +++ b/cereal/services.py @@ -1,12 +1,22 @@ #!/usr/bin/env python3 +from enum import IntEnum from typing import Optional +# TODO: this should be automatically determined using the capnp schema +class QueueSize(IntEnum): + BIG = 10 * 1024 * 1024 # 10MB - video frames, large AI outputs + MEDIUM = 2 * 1024 * 1024 # 2MB - high freq (CAN), livestream + SMALL = 250 * 1024 # 250KB - most services + + class Service: - def __init__(self, should_log: bool, frequency: float, decimation: Optional[int] = None): + def __init__(self, should_log: bool, frequency: float, decimation: Optional[int] = None, + queue_size: QueueSize = QueueSize.SMALL): self.should_log = should_log self.frequency = frequency self.decimation = decimation + self.queue_size = queue_size _services: dict[str, tuple] = { @@ -20,15 +30,15 @@ _services: dict[str, tuple] = { "gpsNMEA": (True, 9.), "deviceState": (True, 2., 1), "touch": (True, 20., 1), - "can": (True, 100., 2053), # decimation gives ~3 msgs in a full segment - "controlsState": (True, 100., 10), + "can": (True, 100., 2053, QueueSize.BIG), # decimation gives ~3 msgs in a full segment + "controlsState": (True, 100., 10, QueueSize.MEDIUM), "selfdriveState": (True, 100., 10), "pandaStates": (True, 10., 1), "peripheralState": (True, 2., 1), "radarState": (True, 20., 5), "roadEncodeIdx": (False, 20., 1), "liveTracks": (True, 20.), - "sendcan": (True, 100., 139), + "sendcan": (True, 100., 139, QueueSize.MEDIUM), "logMessage": (True, 0.), "errorLogMessage": (True, 0., 1), "liveCalibration": (True, 4., 4), @@ -40,7 +50,7 @@ _services: dict[str, tuple] = { "carOutput": (True, 100., 10), "longitudinalPlan": (True, 20., 10), "driverAssistance": (True, 20., 20), - "procLog": (True, 0.5, 15), + "procLog": (True, 0.5, 15, QueueSize.BIG), "gpsLocationExternal": (True, 10., 10), "gpsLocation": (True, 1., 1), "ubloxGnss": (True, 10.), @@ -62,7 +72,7 @@ _services: dict[str, tuple] = { "wideRoadEncodeIdx": (False, 20., 1), "wideRoadCameraState": (True, 20., 20), "drivingModelData": (True, 20., 10), - "modelV2": (True, 20.), + "modelV2": (True, 20., None, QueueSize.BIG), "managerState": (True, 2., 1), "uploaderState": (True, 0., 1), "navInstruction": (True, 1., 10), @@ -74,21 +84,21 @@ _services: dict[str, tuple] = { "rawAudioData": (False, 20.), "bookmarkButton": (True, 0., 1), "audioFeedback": (True, 0., 1), + "roadEncodeData": (False, 20., None, QueueSize.BIG), + "driverEncodeData": (False, 20., None, QueueSize.BIG), + "wideRoadEncodeData": (False, 20., None, QueueSize.BIG), + "qRoadEncodeData": (False, 20., None, QueueSize.BIG), # debug "uiDebug": (True, 0., 1), "testJoystick": (True, 0.), "alertDebug": (True, 20., 5), - "roadEncodeData": (False, 20.), - "driverEncodeData": (False, 20.), - "wideRoadEncodeData": (False, 20.), - "qRoadEncodeData": (False, 20.), "livestreamWideRoadEncodeIdx": (False, 20.), "livestreamRoadEncodeIdx": (False, 20.), "livestreamDriverEncodeIdx": (False, 20.), - "livestreamWideRoadEncodeData": (False, 20.), - "livestreamRoadEncodeData": (False, 20.), - "livestreamDriverEncodeData": (False, 20.), + "livestreamWideRoadEncodeData": (False, 20., None, QueueSize.MEDIUM), + "livestreamRoadEncodeData": (False, 20., None, QueueSize.MEDIUM), + "livestreamDriverEncodeData": (False, 20., None, QueueSize.MEDIUM), "customReservedRawData0": (True, 0.), "customReservedRawData1": (True, 0.), "customReservedRawData2": (True, 0.), @@ -106,13 +116,13 @@ def build_header(): h += "#include \n" h += "#include \n" - h += "struct service { std::string name; bool should_log; float frequency; int decimation; };\n" + h += "struct service { std::string name; bool should_log; float frequency; int decimation; size_t queue_size; };\n" h += "static std::map services = {\n" for k, v in SERVICE_LIST.items(): should_log = "true" if v.should_log else "false" decimation = -1 if v.decimation is None else v.decimation - h += ' { "%s", {"%s", %s, %f, %d}},\n' % \ - (k, k, should_log, v.frequency, decimation) + h += ' { "%s", {"%s", %s, %f, %d, %d}},\n' % \ + (k, k, should_log, v.frequency, decimation, v.queue_size) h += "};\n" h += "#endif\n" diff --git a/msgq_repo b/msgq_repo index 92999f6bc..345878d91 160000 --- a/msgq_repo +++ b/msgq_repo @@ -1 +1 @@ -Subproject commit 92999f6bc19c16170ff984473b43c799162faca1 +Subproject commit 345878d9141d0470d1a96f8fb5e59efb61dd5cf9 diff --git a/selfdrive/debug/analyze-msg-size.py b/selfdrive/debug/analyze-msg-size.py new file mode 100755 index 000000000..69015a6be --- /dev/null +++ b/selfdrive/debug/analyze-msg-size.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +import argparse +from tqdm import tqdm + +from cereal.services import SERVICE_LIST, QueueSize +from openpilot.tools.lib.logreader import LogReader + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Analyze message sizes from a log route") + parser.add_argument("route", nargs="?", default="98395b7c5b27882e/000000a8--f87e7cd255", + help="Log route to analyze (default: 98395b7c5b27882e/000000a8--f87e7cd255)") + args = parser.parse_args() + + lr = LogReader(args.route) + + szs = {} + for msg in tqdm(lr): + sz = len(msg.as_builder().to_bytes()) + msg_type = msg.which() + if msg_type not in szs: + szs[msg_type] = {'min': sz, 'max': sz, 'sum': sz, 'count': 1} + else: + szs[msg_type]['min'] = min(szs[msg_type]['min'], sz) + szs[msg_type]['max'] = max(szs[msg_type]['max'], sz) + szs[msg_type]['sum'] += sz + szs[msg_type]['count'] += 1 + + print() + print(f"{'Service':<36} {'Min (KB)':>12} {'Max (KB)':>12} {'Avg (KB)':>12} {'KB/min':>12} {'KB/sec':>12} {'Minutes in 10MB':>18} {'Seconds in Queue':>18}") + print("-" * 132) + def sort_key(x): + k, v = x + avg = v['sum'] / v['count'] + freq = SERVICE_LIST.get(k, None) + freq_val = freq.frequency if freq else 0.0 + kb_per_min = (avg * freq_val * 60) / 1024 if freq_val > 0 else 0.0 + return kb_per_min + total_kb_per_min = 0.0 + RINGBUFFER_SIZE_KB = 10 * 1024 # 10MB old default + for k, v in sorted(szs.items(), key=sort_key, reverse=True): + avg = v['sum'] / v['count'] + service = SERVICE_LIST.get(k, None) + freq_val = service.frequency if service else 0.0 + queue_size_kb = (service.queue_size / 1024) if service else 250 # default to SMALL + kb_per_min = (avg * freq_val * 60) / 1024 if freq_val > 0 else 0.0 + kb_per_sec = kb_per_min / 60 + minutes_in_buffer = RINGBUFFER_SIZE_KB / kb_per_min if kb_per_min > 0 else float('inf') + seconds_in_queue = (queue_size_kb / kb_per_sec) if kb_per_sec > 0 else float('inf') + total_kb_per_min += kb_per_min + min_str = f"{minutes_in_buffer:.2f}" if minutes_in_buffer != float('inf') else "inf" + sec_queue_str = f"{seconds_in_queue:.2f}" if seconds_in_queue != float('inf') else "inf" + print(f"{k:<36} {v['min']/1024:>12.2f} {v['max']/1024:>12.2f} {avg/1024:>12.2f} {kb_per_min:>12.2f} {kb_per_sec:>12.2f} {min_str:>18} {sec_queue_str:>18}") + + # Summary section + print() + print(f"Total usage: {total_kb_per_min / 1024:.2f} MB/min") + + # Calculate memory usage: old (10MB for all) vs new (from services.py) + OLD_SIZE = 10 * 1024 * 1024 # 10MB was the old default + old_total = len(SERVICE_LIST) * OLD_SIZE + + new_total = sum(s.queue_size for s in SERVICE_LIST.values()) + + # Count by queue size + size_counts = {QueueSize.BIG: 0, QueueSize.MEDIUM: 0, QueueSize.SMALL: 0} + for s in SERVICE_LIST.values(): + size_counts[s.queue_size] += 1 + + savings_pct = (1 - new_total / old_total) * 100 + + print() + print(f"{'Queue Size Comparison':<40}") + print("-" * 60) + print(f"{'Old (10MB default):':<30} {old_total / 1024 / 1024:>10.2f} MB") + print(f"{'New (from services.py):':<30} {new_total / 1024 / 1024:>10.2f} MB") + print(f"{'Savings:':<30} {savings_pct:>10.1f}%") + print() + print(f"{'Breakdown:':<30}") + print(f" BIG (10MB): {size_counts[QueueSize.BIG]:>3} services") + print(f" MEDIUM (2MB): {size_counts[QueueSize.MEDIUM]:>3} services") + print(f" SMALL (250KB): {size_counts[QueueSize.SMALL]:>3} services") diff --git a/selfdrive/pandad/pandad.cc b/selfdrive/pandad/pandad.cc index a76cbc46e..2fd4a4def 100644 --- a/selfdrive/pandad/pandad.cc +++ b/selfdrive/pandad/pandad.cc @@ -11,6 +11,7 @@ #include "cereal/gen/cpp/car.capnp.h" #include "cereal/messaging/messaging.h" +#include "cereal/services.h" #include "common/ratekeeper.h" #include "common/swaglog.h" #include "common/timing.h" @@ -82,7 +83,7 @@ void can_send_thread(std::vector pandas, bool fake_send) { AlignedBuffer aligned_buf; std::unique_ptr context(Context::create()); - std::unique_ptr subscriber(SubSocket::create(context.get(), "sendcan")); + std::unique_ptr subscriber(SubSocket::create(context.get(), "sendcan", "127.0.0.1", false, true, services.at("sendcan").queue_size)); assert(subscriber != NULL); subscriber->setTimeout(100); diff --git a/selfdrive/test/test_onroad.py b/selfdrive/test/test_onroad.py index 972f09be3..ed3fca5fb 100644 --- a/selfdrive/test/test_onroad.py +++ b/selfdrive/test/test_onroad.py @@ -289,7 +289,7 @@ class TestOnroad: # check for big leaks. note that memory usage is # expected to go up while the MSGQ buffers fill up - assert np.average(mems) <= 82, "Average memory usage above 85%" + assert np.average(mems) <= 65, "Average memory usage too high" assert np.max(np.diff(mems)) <= 4, "Max memory increase too high" assert np.average(np.diff(mems)) <= 1, "Average memory increase too high" diff --git a/system/loggerd/loggerd.cc b/system/loggerd/loggerd.cc index 21de1ff33..47da32102 100644 --- a/system/loggerd/loggerd.cc +++ b/system/loggerd/loggerd.cc @@ -238,7 +238,7 @@ void loggerd_thread() { if (it.should_log || (encoder && !livestream_encoder) || record_audio) { LOGD("logging %s", it.name.c_str()); - SubSocket * sock = SubSocket::create(ctx.get(), it.name); + SubSocket * sock = SubSocket::create(ctx.get(), it.name, "127.0.0.1", false, true, it.queue_size); assert(sock != NULL); poller->registerSocket(sock); service_state[sock] = { diff --git a/system/loggerd/tests/test_loggerd.py b/system/loggerd/tests/test_loggerd.py index 1cac16adc..9703ac2f5 100644 --- a/system/loggerd/tests/test_loggerd.py +++ b/system/loggerd/tests/test_loggerd.py @@ -24,7 +24,6 @@ from openpilot.system.version import get_version from openpilot.tools.lib.helpers import RE from openpilot.tools.lib.logreader import LogReader from msgq.visionipc import VisionIpcServer, VisionStreamType -from openpilot.common.transformations.camera import DEVICE_CAMERAS SentinelType = log.Sentinel.SentinelType @@ -99,13 +98,17 @@ class TestLoggerd: return sent_msgs def _publish_camera_and_audio_messages(self, num_segs=1, segment_length=5): - d = DEVICE_CAMERAS[("tici", "ar0231")] + # Use small frame sizes for testing (width, height, size, stride, uv_offset) + # NV12 format: size = stride * height * 1.5, uv_offset = stride * height + w, h = 320, 240 + frame_spec = (w, h, w * h * 3 // 2, w, w * h) streams = [ - (VisionStreamType.VISION_STREAM_ROAD, (d.fcam.width, d.fcam.height, 2048 * 2346, 2048, 2048 * 1216), "roadCameraState"), - (VisionStreamType.VISION_STREAM_DRIVER, (d.dcam.width, d.dcam.height, 2048 * 2346, 2048, 2048 * 1216), "driverCameraState"), - (VisionStreamType.VISION_STREAM_WIDE_ROAD, (d.ecam.width, d.ecam.height, 2048 * 2346, 2048, 2048 * 1216), "wideRoadCameraState"), + (VisionStreamType.VISION_STREAM_ROAD, frame_spec, "roadCameraState"), + (VisionStreamType.VISION_STREAM_DRIVER, frame_spec, "driverCameraState"), + (VisionStreamType.VISION_STREAM_WIDE_ROAD, frame_spec, "wideRoadCameraState"), ] + sm = messaging.SubMaster(["roadEncodeData"]) pm = messaging.PubMaster([s for _, _, s in streams] + ["rawAudioData"]) vipc_server = VisionIpcServer("camerad") for stream_type, frame_spec, _ in streams: @@ -139,6 +142,8 @@ class TestLoggerd: for _, _, state in streams: assert pm.wait_for_readers_to_update(state, timeout=5, dt=0.001) + sm.update(100) # wait for encode data publish + managed_processes["loggerd"].stop() managed_processes["encoderd"].stop() diff --git a/tools/cabana/streams/devicestream.cc b/tools/cabana/streams/devicestream.cc index 6de63dfbb..462dd7a36 100644 --- a/tools/cabana/streams/devicestream.cc +++ b/tools/cabana/streams/devicestream.cc @@ -3,6 +3,8 @@ #include #include +#include "cereal/services.h" + #include #include #include @@ -20,7 +22,7 @@ void DeviceStream::streamThread() { std::unique_ptr context(Context::create()); std::string address = zmq_address.isEmpty() ? "127.0.0.1" : zmq_address.toStdString(); - std::unique_ptr sock(SubSocket::create(context.get(), "can", address)); + std::unique_ptr sock(SubSocket::create(context.get(), "can", address, false, true, services.at("can").queue_size)); assert(sock != NULL); // run as fast as messages come in while (!QThread::currentThread()->isInterruptionRequested()) { From 4624d8f9367cc50bd8c594aef0860f052e1839da Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 16 Dec 2025 15:42:59 -0800 Subject: [PATCH 624/910] four: hide untoggleable toggles (#36890) * hide toggles * enabled is redundant --- selfdrive/ui/mici/layouts/settings/toggles.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/toggles.py b/selfdrive/ui/mici/layouts/settings/toggles.py index 8efb516a4..c16504fac 100644 --- a/selfdrive/ui/mici/layouts/settings/toggles.py +++ b/selfdrive/ui/mici/layouts/settings/toggles.py @@ -78,13 +78,13 @@ class TogglesLayoutMici(NavWidget): # CP gating for experimental mode if ui_state.CP is not None: if ui_state.has_longitudinal_control: - self._experimental_btn.set_enabled(True) - self._personality_toggle.set_enabled(True) + self._experimental_btn.set_visible(True) + self._personality_toggle.set_visible(True) else: # no long for now - self._experimental_btn.set_enabled(False) + self._experimental_btn.set_visible(False) self._experimental_btn.set_checked(False) - self._personality_toggle.set_enabled(False) + self._personality_toggle.set_visible(False) ui_state.params.remove("ExperimentalMode") # Refresh toggles from params to mirror external changes From 4fa4237e3f352dda99b215242d2dacbd44172704 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 16 Dec 2025 15:51:35 -0800 Subject: [PATCH 625/910] bump msgq (#36891) * bump msgq * update prefix --- common/prefix.py | 2 +- msgq_repo | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/common/prefix.py b/common/prefix.py index 207f8477d..b19ce1472 100644 --- a/common/prefix.py +++ b/common/prefix.py @@ -11,7 +11,7 @@ from openpilot.system.hardware.hw import DEFAULT_DOWNLOAD_CACHE_ROOT class OpenpilotPrefix: def __init__(self, prefix: str = None, create_dirs_on_enter: bool = True, clean_dirs_on_exit: bool = True, shared_download_cache: bool = False): self.prefix = prefix if prefix else str(uuid.uuid4().hex[0:15]) - self.msgq_path = os.path.join(Paths.shm_path(), self.prefix) + self.msgq_path = os.path.join(Paths.shm_path(), "msgq_" + self.prefix) self.create_dirs_on_enter = create_dirs_on_enter self.clean_dirs_on_exit = clean_dirs_on_exit self.shared_download_cache = shared_download_cache diff --git a/msgq_repo b/msgq_repo index 345878d91..6abe47bc9 160000 --- a/msgq_repo +++ b/msgq_repo @@ -1 +1 @@ -Subproject commit 345878d9141d0470d1a96f8fb5e59efb61dd5cf9 +Subproject commit 6abe47bc98b83338b6ea04a87a6b2b5c65d09630 From 9768109ec11416648076163af230d82563666628 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 16 Dec 2025 16:19:53 -0800 Subject: [PATCH 626/910] ui: generic hold gesture (#36893) * generic * fix * use in home * clean up * rm * clean up --- selfdrive/ui/mici/layouts/home.py | 33 ++++++++----------------------- system/ui/widgets/__init__.py | 30 ++++++++++++++++++++++++++-- 2 files changed, 36 insertions(+), 27 deletions(-) diff --git a/selfdrive/ui/mici/layouts/home.py b/selfdrive/ui/mici/layouts/home.py index 9152bdc7f..af97de92f 100644 --- a/selfdrive/ui/mici/layouts/home.py +++ b/selfdrive/ui/mici/layouts/home.py @@ -1,5 +1,3 @@ -import time - from cereal import log import pyray as rl from collections.abc import Callable @@ -83,9 +81,6 @@ class MiciHomeLayout(Widget): self._on_settings_click: Callable | None = None self._last_refresh = 0 - self._mouse_down_t: None | float = None - self._did_long_press = False - self._is_pressed_prev = False self._version_text = None self._experimental_mode = False @@ -124,23 +119,13 @@ class MiciHomeLayout(Widget): def _update_params(self): self._experimental_mode = ui_state.params.get_bool("ExperimentalMode") + def _handle_long_press(self, _): + # long gating for experimental mode - only allow toggle if longitudinal control is available + if ui_state.has_longitudinal_control: + self._experimental_mode = not self._experimental_mode + ui_state.params.put("ExperimentalMode", self._experimental_mode) + def _update_state(self): - if self.is_pressed and not self._is_pressed_prev: - self._mouse_down_t = time.monotonic() - elif not self.is_pressed and self._is_pressed_prev: - self._mouse_down_t = None - self._did_long_press = False - self._is_pressed_prev = self.is_pressed - - if self._mouse_down_t is not None: - if time.monotonic() - self._mouse_down_t > 0.5: - # long gating for experimental mode - only allow toggle if longitudinal control is available - if ui_state.has_longitudinal_control: - self._experimental_mode = not self._experimental_mode - ui_state.params.put("ExperimentalMode", self._experimental_mode) - self._mouse_down_t = None - self._did_long_press = True - if rl.get_time() - self._last_refresh > 5.0: device_state = ui_state.sm['deviceState'] self._update_network_status(device_state) @@ -159,10 +144,8 @@ class MiciHomeLayout(Widget): self._on_settings_click = on_settings def _handle_mouse_release(self, mouse_pos: MousePos): - if not self._did_long_press: - if self._on_settings_click: - self._on_settings_click() - self._did_long_press = False + if self._on_settings_click: + self._on_settings_click() def _get_version_text(self) -> tuple[str, str, str, str] | None: description = ui_state.params.get("UpdaterCurrentDescription") diff --git a/system/ui/widgets/__init__.py b/system/ui/widgets/__init__.py index a3fed6d96..5b3b4a691 100644 --- a/system/ui/widgets/__init__.py +++ b/system/ui/widgets/__init__.py @@ -20,6 +20,8 @@ class DialogResult(IntEnum): class Widget(abc.ABC): + LONG_PRESS_TIME = 0.5 + def __init__(self): self._rect: rl.Rectangle = rl.Rectangle(0, 0, 0, 0) self._parent_rect: rl.Rectangle | None = None @@ -33,6 +35,10 @@ class Widget(abc.ABC): self._multi_touch = False self.__was_awake = True + # Long press state (single touch only, slot 0) + self._long_press_start_t: float | None = None + self._long_press_fired: bool = False + @property def rect(self) -> rl.Rectangle: return self._rect @@ -127,19 +133,28 @@ class Widget(abc.ABC): self._handle_mouse_press(mouse_event.pos) self.__is_pressed[mouse_event.slot] = True self.__tracking_is_pressed[mouse_event.slot] = True + if mouse_event.slot == 0: + self._long_press_start_t = rl.get_time() + self._long_press_fired = False self._handle_mouse_event(mouse_event) # Callback such as scroll panel signifies user is scrolling elif not touch_valid: self.__is_pressed[mouse_event.slot] = False self.__tracking_is_pressed[mouse_event.slot] = False + if mouse_event.slot == 0: + self._long_press_start_t = None + self._long_press_fired = False elif mouse_event.left_released: self._handle_mouse_event(mouse_event) - if self.__is_pressed[mouse_event.slot] and mouse_in_rect: + if self.__is_pressed[mouse_event.slot] and mouse_in_rect and not (mouse_event.slot == 0 and self._long_press_fired): self._handle_mouse_release(mouse_event.pos) self.__is_pressed[mouse_event.slot] = False self.__tracking_is_pressed[mouse_event.slot] = False + if mouse_event.slot == 0: + self._long_press_start_t = None + self._long_press_fired = False # Mouse/touch is still within our rect elif mouse_in_rect: @@ -150,8 +165,17 @@ class Widget(abc.ABC): # Mouse/touch left our rect but may come back into focus later elif not mouse_in_rect: self.__is_pressed[mouse_event.slot] = False + if mouse_event.slot == 0: + self._long_press_start_t = None + self._long_press_fired = False self._handle_mouse_event(mouse_event) + # Long press detection + if self._long_press_start_t is not None and not self._long_press_fired: + if (rl.get_time() - self._long_press_start_t) >= self.LONG_PRESS_TIME: + self._long_press_fired = True + self._handle_long_press(gui_app.last_mouse_event.pos) + def _layout(self) -> None: """Optionally lay out child widgets separately. This is called before rendering.""" @@ -175,9 +199,11 @@ class Widget(abc.ABC): self._click_callback() return False + def _handle_long_press(self, mouse_pos: MousePos) -> None: + """Optionally handle a long-press gesture.""" + def _handle_mouse_event(self, mouse_event: MouseEvent) -> None: """Optionally handle mouse events. This is called before rendering.""" - # Default implementation does nothing, can be overridden by subclasses def show_event(self): """Optionally handle show event. Parent must manually call this""" From 95350ad854de32e2753fdd1bd15314f84663561f Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 16 Dec 2025 16:56:19 -0800 Subject: [PATCH 627/910] four: simpler steer saturated alert (#36894) * looks good * fix * cleanup --- selfdrive/selfdrived/events.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/selfdrive/selfdrived/events.py b/selfdrive/selfdrived/events.py index a9b1683c5..c6dbcccbe 100755 --- a/selfdrive/selfdrived/events.py +++ b/selfdrive/selfdrived/events.py @@ -254,15 +254,6 @@ def below_steer_speed_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.S Priority.LOW, VisualAlert.none, AudibleAlert.prompt, 0.4) -def steer_saturated_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert: - steer_text2 = "Steer Left" if sm['carControl'].actuators.torque > 0 else "Steer Right" - return Alert( - "Take Control", - steer_text2, - AlertStatus.userPrompt, AlertSize.mid, - Priority.LOW, VisualAlert.steerRequired, AudibleAlert.promptRepeat, 2.) - - def calibration_incomplete_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert: first_word = 'Recalibrating' if sm['liveCalibration'].calStatus == log.LiveCalibrationData.Status.recalibrating else 'Calibrating' return Alert( @@ -1075,7 +1066,11 @@ if HARDWARE.get_device_type() == 'mici': Priority.LOW, VisualAlert.none, AudibleAlert.prompt, .1), }, EventName.steerSaturated: { - ET.WARNING: steer_saturated_alert, + ET.WARNING: Alert( + "take control", + "turn exceeds limit", + AlertStatus.userPrompt, AlertSize.mid, + Priority.LOW, VisualAlert.steerRequired, AudibleAlert.promptRepeat, 2.), }, EventName.calibrationIncomplete: { ET.PERMANENT: calibration_incomplete_alert, From 90ed6d739cf38c1ca6a363fa3a82d95c6f733f75 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 16 Dec 2025 18:14:47 -0800 Subject: [PATCH 628/910] test_onroad: relax memory threshold (#36895) --- selfdrive/test/test_onroad.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/test/test_onroad.py b/selfdrive/test/test_onroad.py index ed3fca5fb..f57751c06 100644 --- a/selfdrive/test/test_onroad.py +++ b/selfdrive/test/test_onroad.py @@ -289,7 +289,7 @@ class TestOnroad: # check for big leaks. note that memory usage is # expected to go up while the MSGQ buffers fill up - assert np.average(mems) <= 65, "Average memory usage too high" + assert np.average(mems) <= 80, "Average memory usage too high" assert np.max(np.diff(mems)) <= 4, "Max memory increase too high" assert np.average(np.diff(mems)) <= 1, "Average memory increase too high" From 6069c87b07c1a615a9688201939e419f40866f3d Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 16 Dec 2025 19:21:00 -0800 Subject: [PATCH 629/910] Update RELEASES.md for version 0.10.3 --- RELEASES.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/RELEASES.md b/RELEASES.md index fabe635c7..5f2a3459a 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,5 +1,7 @@ -Version 0.10.3 (2025-12-10) +Version 0.10.3 (2025-12-17) ======================== +* New driving model +* New driver monitoring model Version 0.10.2 (2025-11-19) ======================== From c69c076acbc4136b56689024016542de8aec753e Mon Sep 17 00:00:00 2001 From: YassineYousfi Date: Tue, 16 Dec 2025 19:28:21 -0800 Subject: [PATCH 630/910] Update RELEASES.md --- RELEASES.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 5f2a3459a..618b28dc5 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,7 +1,9 @@ Version 0.10.3 (2025-12-17) ======================== -* New driving model -* New driver monitoring model +* New driving model #36249 + * minor changes in the temporal policy architecture and on-policy training physics noise model +* New driver monitoring model #36409 + * trained with a new driver monitoring dataset including comma four data Version 0.10.2 (2025-11-19) ======================== From a112e6e882f37db113cdc7e05784cbf8a8bbb3c9 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 16 Dec 2025 19:31:49 -0800 Subject: [PATCH 631/910] ui: override default interactive timeout (#36898) * impl * fix one place * don't need in setup * fix onboarding * need here too --- selfdrive/ui/mici/layouts/onboarding.py | 18 +++++++++++-- .../ui/mici/onroad/driver_camera_dialog.py | 4 +-- selfdrive/ui/ui_state.py | 25 +++++++++++++------ system/ui/mici_setup.py | 5 ---- 4 files changed, 36 insertions(+), 16 deletions(-) diff --git a/selfdrive/ui/mici/layouts/onboarding.py b/selfdrive/ui/mici/layouts/onboarding.py index abf772ce5..29bc55c9c 100644 --- a/selfdrive/ui/mici/layouts/onboarding.py +++ b/selfdrive/ui/mici/layouts/onboarding.py @@ -96,7 +96,6 @@ class TrainingGuideDMTutorial(Widget): # Wrap the continue callback to restore settings def wrapped_continue_callback(): device.set_offroad_brightness(None) - device.reset_interactive_timeout() continue_callback() self._dialog = DriverCameraSetupDialog(wrapped_continue_callback) @@ -112,7 +111,6 @@ class TrainingGuideDMTutorial(Widget): self._dialog.show_event() device.set_offroad_brightness(100) - device.reset_interactive_timeout(300) # 5 minutes def _update_state(self): super()._update_state() @@ -223,6 +221,14 @@ class TrainingGuide(Widget): TrainingGuideRecordFront(continue_callback=on_continue), ] + def show_event(self): + super().show_event() + device.set_override_interactive_timeout(300) + + def hide_event(self): + super().hide_event() + device.set_override_interactive_timeout(None) + def _advance_step(self): if self._step < len(self._steps) - 1: self._step += 1 @@ -319,6 +325,14 @@ class OnboardingWindow(Widget): self._training_guide = TrainingGuide(completed_callback=self._on_completed_training) self._decline_page = DeclinePage(back_callback=self._on_decline_back) + def show_event(self): + super().show_event() + device.set_override_interactive_timeout(300) + + def hide_event(self): + super().hide_event() + device.set_override_interactive_timeout(None) + @property def completed(self) -> bool: return self._accepted_terms and self._training_done diff --git a/selfdrive/ui/mici/onroad/driver_camera_dialog.py b/selfdrive/ui/mici/onroad/driver_camera_dialog.py index 9adb660d8..af7fc33a4 100644 --- a/selfdrive/ui/mici/onroad/driver_camera_dialog.py +++ b/selfdrive/ui/mici/onroad/driver_camera_dialog.py @@ -51,14 +51,14 @@ class DriverCameraDialog(NavWidget): super().show_event() ui_state.params.put_bool("IsDriverViewEnabled", True) self._publish_alert_sound(None) - device.reset_interactive_timeout(300) + device.set_override_interactive_timeout(300) ui_state.params.remove("DriverTooDistracted") self._pm = messaging.PubMaster(['selfdriveState']) def hide_event(self): super().hide_event() ui_state.params.put_bool("IsDriverViewEnabled", False) - device.reset_interactive_timeout() + device.set_override_interactive_timeout(None) def _handle_mouse_release(self, _): ui_state.params.remove("DriverTooDistracted") diff --git a/selfdrive/ui/ui_state.py b/selfdrive/ui/ui_state.py index ef0696a22..30a656509 100644 --- a/selfdrive/ui/ui_state.py +++ b/selfdrive/ui/ui_state.py @@ -187,6 +187,7 @@ class Device: def __init__(self): self._ignition = False self._interaction_time: float = -1 + self._override_interactive_timeout: int | None = None self._interactive_timeout_callbacks: list[Callable] = [] self._prev_timed_out = False self._awake: bool = True @@ -200,11 +201,21 @@ class Device: def awake(self) -> bool: return self._awake - def reset_interactive_timeout(self, timeout: int = -1) -> None: - if timeout == -1: - ignition_timeout = 10 if gui_app.big_ui() else 5 - timeout = ignition_timeout if ui_state.ignition else 30 - self._interaction_time = time.monotonic() + timeout + def set_override_interactive_timeout(self, timeout: int | None) -> None: + # Override the interactive timeout duration temporarily + self._override_interactive_timeout = timeout + self._reset_interactive_timeout() + + @property + def interactive_timeout(self) -> int: + if self._override_interactive_timeout is not None: + return self._override_interactive_timeout + + ignition_timeout = 10 if gui_app.big_ui() else 5 + return ignition_timeout if ui_state.ignition else 30 + + def _reset_interactive_timeout(self) -> None: + self._interaction_time = time.monotonic() + self.interactive_timeout def add_interactive_timeout_callback(self, callback: Callable): self._interactive_timeout_callbacks.append(callback) @@ -212,7 +223,7 @@ class Device: def update(self): # do initial reset if self._interaction_time <= 0: - self.reset_interactive_timeout() + self._reset_interactive_timeout() self._update_brightness() self._update_wakefulness() @@ -252,7 +263,7 @@ class Device: self._ignition = ui_state.ignition if ignition_just_turned_off or any(ev.left_down for ev in gui_app.mouse_events): - self.reset_interactive_timeout() + self._reset_interactive_timeout() interaction_timeout = time.monotonic() > self._interaction_time if interaction_timeout and not self._prev_timed_out: diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 9792c51e7..84be5bc3d 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -18,7 +18,6 @@ from openpilot.common.utils import run_cmd from openpilot.system.hardware import HARDWARE from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.lib.wifi_manager import WifiManager -from openpilot.selfdrive.ui.ui_state import device from openpilot.system.ui.lib.scroll_panel2 import GuiScrollPanel2 from openpilot.system.ui.widgets import Widget, DialogResult from openpilot.system.ui.widgets.button import (IconButton, SmallButton, WideRoundedButton, SmallerRoundedButton, @@ -226,10 +225,6 @@ class TermsPage(Widget): self._back_button.set_opacity(0.0) self._scroll_down_indicator.set_opacity(1.0) - def show_event(self): - super().show_event() - device.reset_interactive_timeout(300) - @property @abstractmethod def _content_height(self): From cecce82015b01e3e1dd22f2d605fda6eb82364bb Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 16 Dec 2025 20:34:51 -0800 Subject: [PATCH 632/910] ui: default text color 90% white (#36899) default 90% --- system/ui/lib/application.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 26e446612..719d38284 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -79,7 +79,7 @@ void main() { """ DEFAULT_TEXT_SIZE = 60 -DEFAULT_TEXT_COLOR = rl.WHITE +DEFAULT_TEXT_COLOR = rl.Color(255, 255, 255, int(255 * 0.9)) # Qt draws fonts accounting for ascent/descent differently, so compensate to match old styles # The real scales for the fonts below range from 1.212 to 1.266 From 31e46f929d2d4b4876027920d71759cb71b08ac8 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 16 Dec 2025 22:42:13 -0800 Subject: [PATCH 633/910] onboarding: fixup DM RHD detection (#36900) * helper * fix * use it * prop * bigger box * huh --- selfdrive/ui/mici/layouts/onboarding.py | 5 +++-- .../ui/mici/onroad/driver_camera_dialog.py | 18 ++++++++++-------- selfdrive/ui/mici/onroad/driver_state.py | 12 ++++++++++-- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/selfdrive/ui/mici/layouts/onboarding.py b/selfdrive/ui/mici/layouts/onboarding.py index 29bc55c9c..81109fb25 100644 --- a/selfdrive/ui/mici/layouts/onboarding.py +++ b/selfdrive/ui/mici/layouts/onboarding.py @@ -41,8 +41,7 @@ class DriverCameraSetupDialog(DriverCameraDialog): return -1 # Position dmoji on opposite side from driver - # TODO: we don't have design for RHD yet - is_rhd = False + is_rhd = self.driver_state_renderer.is_rhd driver_state_rect = ( rect.x if is_rhd else rect.x + rect.width - self.driver_state_renderer.rect.width, rect.y + (rect.height - self.driver_state_renderer.rect.height) / 2, @@ -50,6 +49,8 @@ class DriverCameraSetupDialog(DriverCameraDialog): self.driver_state_renderer.set_position(*driver_state_rect) self.driver_state_renderer.render() + self._draw_face_detection(rect) + rl.end_scissor_mode() return -1 diff --git a/selfdrive/ui/mici/onroad/driver_camera_dialog.py b/selfdrive/ui/mici/onroad/driver_camera_dialog.py index af7fc33a4..150e43656 100644 --- a/selfdrive/ui/mici/onroad/driver_camera_dialog.py +++ b/selfdrive/ui/mici/onroad/driver_camera_dialog.py @@ -89,7 +89,9 @@ class DriverCameraDialog(NavWidget): self._publish_alert_sound(None) return -1 - self._draw_face_detection(rect) + driver_data = self._draw_face_detection(rect) + if driver_data is not None: + self._draw_eyes(rect, driver_data) # Position dmoji on opposite side from driver dm_state = ui_state.sm["driverMonitoringState"] @@ -159,12 +161,10 @@ class DriverCameraDialog(NavWidget): if self._glasses_texture is None: self._glasses_texture = gui_app.texture("icons_mici/onroad/glasses.png", self._glasses_size, self._glasses_size) - def _draw_face_detection(self, rect: rl.Rectangle) -> None: - driver_state = ui_state.sm["driverStateV2"] - is_rhd = driver_state.wheelOnRightProb > 0.5 - driver_data = driver_state.rightDriverData if is_rhd else driver_state.leftDriverData - face_detect = driver_data.faceProb > 0.7 - if not face_detect: + def _draw_face_detection(self, rect: rl.Rectangle): + dm_state = ui_state.sm["driverMonitoringState"] + driver_data = self.driver_state_renderer.get_driver_data() + if not dm_state.faceDetected: return # Get face position and orientation @@ -188,7 +188,7 @@ class DriverCameraDialog(NavWidget): scale_y = rect.height / 1080.0 fbox_x = rect.x + rect.width / 2 + offset_x * scale_x fbox_y = rect.y + rect.height / 2 + offset_y * scale_y - box_size = 50 + box_size = 75 line_thickness = 3 line_color = rl.Color(255, 255, 255, int(alpha * 255)) @@ -199,7 +199,9 @@ class DriverCameraDialog(NavWidget): line_thickness, line_color, ) + return driver_data + def _draw_eyes(self, rect: rl.Rectangle, driver_data): # Draw eye indicators based on eye probabilities eye_offset_x = 10 eye_offset_y = 10 diff --git a/selfdrive/ui/mici/onroad/driver_state.py b/selfdrive/ui/mici/onroad/driver_state.py index 080083c3e..3862c299a 100644 --- a/selfdrive/ui/mici/onroad/driver_state.py +++ b/selfdrive/ui/mici/onroad/driver_state.py @@ -78,6 +78,10 @@ class DriverStateRenderer(Widget): """Returns True if dmoji should appear active (either actually active or forced)""" return bool(self._force_active or self._is_active) + @property + def is_rhd(self) -> bool: + return self._is_rhd + def _render(self, _): if DEBUG: rl.draw_rectangle_lines_ex(self._rect, 1, rl.RED) @@ -171,10 +175,9 @@ class DriverStateRenderer(Widget): if f.x > 0.01: rl.draw_line_ex((start_x, start_y), (end_x, end_y), 12, color) - def _update_state(self): + def get_driver_data(self): sm = ui_state.sm - # Get monitoring state dm_state = sm["driverMonitoringState"] self._is_active = dm_state.isActiveMode self._is_rhd = dm_state.isRHD @@ -182,6 +185,11 @@ class DriverStateRenderer(Widget): driverstate = sm["driverStateV2"] driver_data = driverstate.rightDriverData if self._is_rhd else driverstate.leftDriverData + return driver_data + + def _update_state(self): + # Get monitoring state + driver_data = self.get_driver_data() driver_orient = driver_data.faceOrientation if len(driver_orient) != 3: From 99983d39c3a6f88ecdae5cbd9485a693ef77a6f6 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 16 Dec 2025 23:16:45 -0800 Subject: [PATCH 634/910] comma four: simpler DM onboarding (#36896) * rm confirm mode * kinda works * how * disabled * do this * do this * wait * here * something * fade in * 4s * clean up * copy * help * 30deg center * stuff * reset_interactive_timeout * rm * simple * simple * copy * 1.5x * smooth opacity * power off slider * fix * new icons and gradient and rounded * final check * fix * how the hell did this work * clean up * clean up * flip * cmt * uh yeah * remove this * revert this * lint * 45 * clean up * fix * no show time * question * rm * no use * () * lint * call --- .../driver_monitoring/dm_background.png | 4 +- .../onroad/driver_monitoring/dm_person.png | 4 +- .../setup/driver_monitoring/dm_check.png | 3 + .../setup/driver_monitoring/dm_question.png | 3 + .../assets/icons_mici/setup/orange_dm.png | 3 + .../setup/small_button_disabled.png | 3 + selfdrive/ui/mici/layouts/onboarding.py | 161 +++++++++++++++--- .../ui/mici/onroad/driver_camera_dialog.py | 5 +- selfdrive/ui/mici/onroad/driver_state.py | 63 +++---- system/ui/mici_setup.py | 24 ++- system/ui/widgets/button.py | 11 +- system/ui/widgets/slider.py | 15 +- 12 files changed, 220 insertions(+), 79 deletions(-) create mode 100644 selfdrive/assets/icons_mici/setup/driver_monitoring/dm_check.png create mode 100644 selfdrive/assets/icons_mici/setup/driver_monitoring/dm_question.png create mode 100644 selfdrive/assets/icons_mici/setup/orange_dm.png create mode 100644 selfdrive/assets/icons_mici/setup/small_button_disabled.png diff --git a/selfdrive/assets/icons_mici/onroad/driver_monitoring/dm_background.png b/selfdrive/assets/icons_mici/onroad/driver_monitoring/dm_background.png index 4d83ed5cd..04ffc2435 100644 --- a/selfdrive/assets/icons_mici/onroad/driver_monitoring/dm_background.png +++ b/selfdrive/assets/icons_mici/onroad/driver_monitoring/dm_background.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f27352a18194a1c819e9eaea89cfc11d2964402df0a28efa3ba60ae2d972fe67 -size 13108 +oid sha256:b7eb870d01e5bf6c421e204026a4ea08e177731f2d6b5b17c4ad43c90c1c3e78 +size 23549 diff --git a/selfdrive/assets/icons_mici/onroad/driver_monitoring/dm_person.png b/selfdrive/assets/icons_mici/onroad/driver_monitoring/dm_person.png index 7aa7f0542..540b2029a 100644 --- a/selfdrive/assets/icons_mici/onroad/driver_monitoring/dm_person.png +++ b/selfdrive/assets/icons_mici/onroad/driver_monitoring/dm_person.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:25d66e42a28a3367eb40724d28652889089aa762438b475645269e0319c46009 -size 1431 +oid sha256:f7b3bb76ee2359076339285ea6bced5b680e5b919a1b7dee163f36cd819c9ea1 +size 1746 diff --git a/selfdrive/assets/icons_mici/setup/driver_monitoring/dm_check.png b/selfdrive/assets/icons_mici/setup/driver_monitoring/dm_check.png new file mode 100644 index 000000000..92993e3e0 --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/driver_monitoring/dm_check.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5b7dce550c008ff7a65ed19ccf308ecf92cd0118bb544978b7dd7393c5c27ae5 +size 809 diff --git a/selfdrive/assets/icons_mici/setup/driver_monitoring/dm_question.png b/selfdrive/assets/icons_mici/setup/driver_monitoring/dm_question.png new file mode 100644 index 000000000..53a837afb --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/driver_monitoring/dm_question.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e102b8b2e71a25d9f818b37d6f75ed958430cb765a07ae50713995779fb6a886 +size 1388 diff --git a/selfdrive/assets/icons_mici/setup/orange_dm.png b/selfdrive/assets/icons_mici/setup/orange_dm.png new file mode 100644 index 000000000..74cce9d97 --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/orange_dm.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:38a108f96f85a154b698693b07f2e4214124b8f2545b7c4490cea0aa998d75fd +size 11855 diff --git a/selfdrive/assets/icons_mici/setup/small_button_disabled.png b/selfdrive/assets/icons_mici/setup/small_button_disabled.png new file mode 100644 index 000000000..da8bb3eef --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/small_button_disabled.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6ed258d8e0531c19705953ded065c6d5e14929728a2909d8d4e335898fa5d080 +size 4056 diff --git a/selfdrive/ui/mici/layouts/onboarding.py b/selfdrive/ui/mici/layouts/onboarding.py index 81109fb25..d35881341 100644 --- a/selfdrive/ui/mici/layouts/onboarding.py +++ b/selfdrive/ui/mici/layouts/onboarding.py @@ -1,11 +1,14 @@ from enum import IntEnum -from collections.abc import Callable import weakref +import math +import numpy as np import pyray as rl +from openpilot.common.filter_simple import FirstOrderFilter +from openpilot.system.hardware import HARDWARE from openpilot.system.ui.lib.application import FontWeight, gui_app from openpilot.system.ui.widgets import Widget -from openpilot.system.ui.widgets.button import SmallButton +from openpilot.system.ui.widgets.button import SmallButton, SmallCircleIconButton from openpilot.system.ui.widgets.label import UnifiedLabel from openpilot.system.ui.widgets.slider import SmallSlider from openpilot.system.ui.mici_setup import TermsHeader, TermsPage as SetupTermsPage @@ -24,11 +27,12 @@ class OnboardingState(IntEnum): class DriverCameraSetupDialog(DriverCameraDialog): - def __init__(self, confirm_callback: Callable): + def __init__(self): super().__init__(no_escape=True) - self.driver_state_renderer = DriverStateRenderer(confirm_mode=True, confirm_callback=confirm_callback) - self.driver_state_renderer.set_rect(rl.Rectangle(0, 0, 200, 200)) + self.driver_state_renderer = DriverStateRenderer(inset=True) + self.driver_state_renderer.set_rect(rl.Rectangle(0, 0, 120, 120)) self.driver_state_renderer.load_icons() + self.driver_state_renderer.set_force_active(True) def _render(self, rect): rl.begin_scissor_mode(int(rect.x), int(rect.y), int(rect.width), int(rect.height)) @@ -42,11 +46,10 @@ class DriverCameraSetupDialog(DriverCameraDialog): # Position dmoji on opposite side from driver is_rhd = self.driver_state_renderer.is_rhd - driver_state_rect = ( - rect.x if is_rhd else rect.x + rect.width - self.driver_state_renderer.rect.width, - rect.y + (rect.height - self.driver_state_renderer.rect.height) / 2, + self.driver_state_renderer.set_position( + rect.x + 8 if is_rhd else rect.x + rect.width - self.driver_state_renderer.rect.width - 8, + rect.y + 8, ) - self.driver_state_renderer.set_position(*driver_state_rect) self.driver_state_renderer.render() self._draw_face_detection(rect) @@ -89,17 +92,54 @@ class TrainingGuidePreDMTutorial(SetupTermsPage): )) +class DMBadFaceDetected(SetupTermsPage): + def __init__(self, continue_callback, back_callback): + super().__init__(continue_callback, back_callback, continue_text="power off") + self._title_header = TermsHeader("make sure comma four can see your face", gui_app.texture("icons_mici/setup/orange_dm.png", 60, 60)) + self._dm_label = UnifiedLabel("Re-mount if your face is occluded or driver monitoring has difficulty tracking your face.", 42, FontWeight.ROMAN) + + @property + def _content_height(self): + return self._dm_label.rect.y + self._dm_label.rect.height - self._scroll_panel.get_offset() + + def _render_content(self, scroll_offset): + self._title_header.render(rl.Rectangle( + self._rect.x + 16, + self._rect.y + 16 + scroll_offset, + self._title_header.rect.width, + self._title_header.rect.height, + )) + + self._dm_label.render(rl.Rectangle( + self._rect.x + 16, + self._title_header.rect.y + self._title_header.rect.height + 16, + self._rect.width - 32, + self._dm_label.get_content_height(int(self._rect.width - 32)), + )) + + class TrainingGuideDMTutorial(Widget): + PROGRESS_DURATION = 4 + LOOKING_THRESHOLD_DEG = 30.0 + def __init__(self, continue_callback): super().__init__() - self._title_header = TermsHeader("fill the circle to continue", gui_app.texture("icons_mici/setup/green_dm.png", 60, 60)) + self._back_button = SmallCircleIconButton(gui_app.texture("icons_mici/setup/driver_monitoring/dm_question.png", 48, 48)) + self._back_button.set_click_callback(self._show_bad_face_page) + self._good_button = SmallCircleIconButton(gui_app.texture("icons_mici/setup/driver_monitoring/dm_check.png", 48, 35)) # Wrap the continue callback to restore settings def wrapped_continue_callback(): device.set_offroad_brightness(None) continue_callback() - self._dialog = DriverCameraSetupDialog(wrapped_continue_callback) + self._good_button.set_click_callback(wrapped_continue_callback) + self._good_button.set_enabled(False) + + self._progress = FirstOrderFilter(0.0, 0.5, 1 / gui_app.target_fps) + self._dialog = DriverCameraSetupDialog() + self._bad_face_page = DMBadFaceDetected(HARDWARE.shutdown, self._hide_bad_face_page) + self._should_show_bad_face_page = False # Disable driver monitoring model when device times out for inactivity def inactivity_callback(): @@ -107,9 +147,20 @@ class TrainingGuideDMTutorial(Widget): device.add_interactive_timeout_callback(inactivity_callback) + def _show_bad_face_page(self): + self._bad_face_page.show_event() + self.hide_event() + self._should_show_bad_face_page = True + + def _hide_bad_face_page(self): + self._bad_face_page.hide_event() + self.show_event() + self._should_show_bad_face_page = False + def show_event(self): super().show_event() self._dialog.show_event() + self._progress.x = 0.0 device.set_offroad_brightness(100) @@ -118,19 +169,91 @@ class TrainingGuideDMTutorial(Widget): if device.awake: ui_state.params.put_bool("IsDriverViewEnabled", True) + sm = ui_state.sm + if sm.recv_frame.get("driverMonitoringState", 0) == 0: + return + + dm_state = sm["driverMonitoringState"] + driver_data = self._dialog.driver_state_renderer.get_driver_data() + + if len(driver_data.faceOrientation) == 3: + pitch, yaw, _ = driver_data.faceOrientation + looking_center = abs(math.degrees(pitch)) < self.LOOKING_THRESHOLD_DEG and abs(math.degrees(yaw)) < self.LOOKING_THRESHOLD_DEG + else: + looking_center = False + + # stay at 100% once reached + if (dm_state.faceDetected and looking_center) or self._progress.x > 0.99: + slow = self._progress.x < 0.25 + duration = self.PROGRESS_DURATION * 2 if slow else self.PROGRESS_DURATION + self._progress.x += 1.0 / (duration * gui_app.target_fps) + self._progress.x = min(1.0, self._progress.x) + else: + self._progress.update(0.0) + + self._good_button.set_enabled(self._progress.x >= 0.999) + def _render(self, _): + if self._should_show_bad_face_page: + return self._bad_face_page.render(self._rect) + self._dialog.render(self._rect) - rl.draw_rectangle_gradient_v(int(self._rect.x), int(self._rect.y + self._rect.height - self._title_header.rect.height * 1.5 - 32), - int(self._rect.width), int(self._title_header.rect.height * 1.5 + 32), - rl.BLANK, rl.Color(0, 0, 0, 150)) - self._title_header.render(rl.Rectangle( - self._rect.x + 16, - self._rect.y + self._rect.height - self._title_header.rect.height - 16, - self._title_header.rect.width, - self._title_header.rect.height, + rl.draw_rectangle_gradient_v(int(self._rect.x), int(self._rect.y + self._rect.height - 80), + int(self._rect.width), 80, rl.BLANK, rl.BLACK) + + # draw white ring around dm icon to indicate progress + ring_thickness = 8 + + # DM icon is 120x120, positioned on opposite side from driver + dm_size = 120 + is_rhd = self._dialog.driver_state_renderer._is_rhd + dm_center_x = (self._rect.x + dm_size / 2 + 8) if is_rhd else (self._rect.x + self._rect.width - dm_size / 2 - 8) + dm_center_y = self._rect.y + dm_size / 2 + 8 + icon_edge_radius = dm_size / 2 + outer_radius = icon_edge_radius + 1 # 2px outward from icon edge + inner_radius = outer_radius - ring_thickness # Inset by ring_thickness + start_angle = 90.0 # Start from bottom + end_angle = start_angle + self._progress.x * 360.0 # Clockwise + + # Fade in alpha + current_angle = end_angle - start_angle + alpha = int(np.interp(current_angle, [0.0, 45.0], [0, 255])) + + # White to green + color_t = np.clip(np.interp(current_angle, [45.0, 360.0], [0.0, 1.0]), 0.0, 1.0) + r = int(np.interp(color_t, [0.0, 1.0], [255, 0])) + g = int(np.interp(color_t, [0.0, 1.0], [255, 255])) + b = int(np.interp(color_t, [0.0, 1.0], [255, 64])) + ring_color = rl.Color(r, g, b, alpha) + + rl.draw_ring( + rl.Vector2(dm_center_x, dm_center_y), + inner_radius, + outer_radius, + start_angle, + end_angle, + 36, + ring_color, + ) + + self._back_button.render(rl.Rectangle( + self._rect.x + 8, + self._rect.y + self._rect.height - self._back_button.rect.height, + self._back_button.rect.width, + self._back_button.rect.height, )) + self._good_button.render(rl.Rectangle( + self._rect.x + self._rect.width - self._good_button.rect.width - 8, + self._rect.y + self._rect.height - self._good_button.rect.height, + self._good_button.rect.width, + self._good_button.rect.height, + )) + + # rounded border + rl.draw_rectangle_rounded_lines_ex(self._rect, 0.2 * 1.02, 10, 50, rl.BLACK) + class TrainingGuideRecordFront(SetupTermsPage): def __init__(self, continue_callback): diff --git a/selfdrive/ui/mici/onroad/driver_camera_dialog.py b/selfdrive/ui/mici/onroad/driver_camera_dialog.py index 150e43656..bab3d6e6f 100644 --- a/selfdrive/ui/mici/onroad/driver_camera_dialog.py +++ b/selfdrive/ui/mici/onroad/driver_camera_dialog.py @@ -94,9 +94,8 @@ class DriverCameraDialog(NavWidget): self._draw_eyes(rect, driver_data) # Position dmoji on opposite side from driver - dm_state = ui_state.sm["driverMonitoringState"] driver_state_rect = ( - rect.x if dm_state.isRHD else rect.x + rect.width - self.driver_state_renderer.rect.width, + rect.x if self.driver_state_renderer.is_rhd else rect.x + rect.width - self.driver_state_renderer.rect.width, rect.y + (rect.height - self.driver_state_renderer.rect.height) / 2, ) self.driver_state_renderer.set_position(*driver_state_rect) @@ -140,7 +139,7 @@ class DriverCameraDialog(NavWidget): # Show first event (only one should be active at a time) event_name_str = str(dm_state.events[0].name).split('.')[-1] - alignment = rl.GuiTextAlignment.TEXT_ALIGN_RIGHT if dm_state.isRHD else rl.GuiTextAlignment.TEXT_ALIGN_LEFT + alignment = rl.GuiTextAlignment.TEXT_ALIGN_RIGHT if self.driver_state_renderer.is_rhd else rl.GuiTextAlignment.TEXT_ALIGN_LEFT shadow_rect = rl.Rectangle(rect.x + 2, rect.y + 2, rect.width, rect.height) gui_label(shadow_rect, event_name_str, font_size=40, font_weight=FontWeight.BOLD, diff --git a/selfdrive/ui/mici/onroad/driver_state.py b/selfdrive/ui/mici/onroad/driver_state.py index 3862c299a..356d7ac83 100644 --- a/selfdrive/ui/mici/onroad/driver_state.py +++ b/selfdrive/ui/mici/onroad/driver_state.py @@ -1,9 +1,7 @@ import pyray as rl -from collections.abc import Callable import numpy as np import math from cereal import log -from openpilot.system.hardware import PC from openpilot.common.filter_simple import FirstOrderFilter from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.widgets import Widget @@ -22,15 +20,11 @@ class DriverStateRenderer(Widget): LINES_ANGLE_INCREMENT = 5 LINES_STALE_ANGLES = 3.0 # seconds - def __init__(self, lines: bool = False, confirm_mode: bool = False, confirm_callback: Callable | None = None): + def __init__(self, lines: bool = False, inset: bool = False): super().__init__() self.set_rect(rl.Rectangle(0, 0, self.BASE_SIZE, self.BASE_SIZE)) - self._lines = lines or confirm_mode - - # In confirm mode, user must fill out the circle to confirm some action in the UI - self._confirm_mode = confirm_mode - self._confirm_callback = confirm_callback - self._confirm_angles: dict[int, float] = {} # angle: timestamp + self._lines = lines + self._inset = inset # In line mode, track smoothed angles assert 360 % self.LINES_ANGLE_INCREMENT == 0 @@ -54,12 +48,20 @@ class DriverStateRenderer(Widget): def load_icons(self): """Load or reload the driver face icon texture""" - self._dm_person = gui_app.texture("icons_mici/onroad/driver_monitoring/dm_person.png", self._rect.width, self._rect.height) - self._dm_cone = gui_app.texture("icons_mici/onroad/driver_monitoring/dm_cone.png", self._rect.width, self._rect.height) + cone_and_person_size = round(52 / self.BASE_SIZE * self._rect.width) + + # If inset is enabled, push cone and person smaller by 2x the current inset space + if self._inset: + # Current inset space = (rect.width - cone_and_person_size) / 2 + current_inset = (self._rect.width - cone_and_person_size) / 2 + # Reduce size by 2x the current inset (1x on each side) + cone_and_person_size = round(cone_and_person_size - current_inset * 2) + + self._dm_person = gui_app.texture("icons_mici/onroad/driver_monitoring/dm_person.png", cone_and_person_size, cone_and_person_size) + self._dm_cone = gui_app.texture("icons_mici/onroad/driver_monitoring/dm_cone.png", cone_and_person_size, cone_and_person_size) center_size = round(36 / self.BASE_SIZE * self._rect.width) self._dm_center = gui_app.texture("icons_mici/onroad/driver_monitoring/dm_center.png", center_size, center_size) - background_size = round(52 / self.BASE_SIZE * self._rect.width) - self._dm_background = gui_app.texture("icons_mici/onroad/driver_monitoring/dm_background.png", background_size, background_size) + self._dm_background = gui_app.texture("icons_mici/onroad/driver_monitoring/dm_background.png", self._rect.width, self._rect.height) def set_should_draw(self, should_draw: bool): self._should_draw = should_draw @@ -87,11 +89,13 @@ class DriverStateRenderer(Widget): rl.draw_rectangle_lines_ex(self._rect, 1, rl.RED) rl.draw_texture(self._dm_background, - int(self._rect.x + (self._rect.width - self._dm_background.width) / 2), - int(self._rect.y + (self._rect.height - self._dm_background.height) / 2), + int(self._rect.x), + int(self._rect.y), rl.Color(255, 255, 255, int(255 * self._fade_filter.x))) - rl.draw_texture(self._dm_person, int(self._rect.x), int(self._rect.y), + rl.draw_texture(self._dm_person, + int(self._rect.x + (self._rect.width - self._dm_person.width) / 2), + int(self._rect.y + (self._rect.height - self._dm_person.height) / 2), rl.Color(255, 255, 255, int(255 * 0.9 * self._fade_filter.x))) if self.effective_active: @@ -124,38 +128,18 @@ class DriverStateRenderer(Widget): else: # remove old angles - now = rl.get_time() - self._confirm_angles = {angle: t for angle, t in self._confirm_angles.items() if now - t < self.LINES_STALE_ANGLES} - - looking_center = self._looking_center_filter.x > 0.2 for angle, f in self._head_angles.items(): dst_from_current = ((angle - self._rotation_filter.x) % 360) - 180 target = 1.0 if abs(dst_from_current) <= self.LINES_ANGLE_INCREMENT * 5 else 0.0 if not self._face_detected: target = 0.0 - if self._confirm_mode: - # Extra careful to not add angles when looking near center - if target > 0 and not looking_center: - self._confirm_angles[angle] = now - - # User is looking at area already confirmed, reduce target to indicate where they are - if angle in self._confirm_angles and target == 0: - target = 0.65 - # Reduce all line lengths when looking center if self._looking_center: target = np.interp(self._looking_center_filter.x, [0.0, 1.0], [target, 0.45]) f.update(target) - self._draw_line(angle, f, self._looking_center and angle not in self._confirm_angles) - - # if all lines placed, reset for next time and call callback - if self._confirm_mode: - if len(self._confirm_angles) >= 360 // self.LINES_ANGLE_INCREMENT: - self._confirm_angles = {} - if self._confirm_callback is not None: - self._confirm_callback() + self._draw_line(angle, f, self._looking_center) def _draw_line(self, angle: int, f: FirstOrderFilter, grey: bool): line_length = self._rect.width / 6 @@ -226,10 +210,7 @@ class DriverStateRenderer(Widget): rotation = math.degrees(math.atan2(pitch, yaw)) angle_diff = rotation - self._rotation_filter.x angle_diff = ((angle_diff + 180) % 360) - 180 - if PC and self._confirm_mode: - self._rotation_filter.x += 2 - else: - self._rotation_filter.update(self._rotation_filter.x + angle_diff) + self._rotation_filter.update(self._rotation_filter.x + angle_diff) if not self.should_draw: self._fade_filter.update(0.0) diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 84be5bc3d..fda35be6e 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -24,7 +24,7 @@ from openpilot.system.ui.widgets.button import (IconButton, SmallButton, WideRou SmallCircleIconButton, WidishRoundedButton, SmallRedPillButton, FullRoundedButton) from openpilot.system.ui.widgets.label import UnifiedLabel -from openpilot.system.ui.widgets.slider import LargerSlider +from openpilot.system.ui.widgets.slider import LargerSlider, SmallSlider from openpilot.selfdrive.ui.mici.layouts.settings.network import WifiUIMici from openpilot.selfdrive.ui.mici.widgets.dialog import BigInputDialog @@ -198,15 +198,20 @@ class TermsPage(Widget): self._scroll_panel = GuiScrollPanel2(horizontal=False) self._continue_text = continue_text - self._continue_button: WideRoundedButton | FullRoundedButton - if back_callback is not None: + self._continue_slider: bool = continue_text in ("reboot", "power off") + self._continue_button: WideRoundedButton | FullRoundedButton | SmallSlider + if self._continue_slider: + self._continue_button = SmallSlider(continue_text, confirm_callback=continue_callback) + self._scroll_panel.set_enabled(lambda: not self._continue_button.is_pressed) + elif back_callback is not None: self._continue_button = WideRoundedButton(continue_text) else: self._continue_button = FullRoundedButton(continue_text) self._continue_button.set_enabled(False) self._continue_button.set_opacity(0.0) self._continue_button.set_touch_valid_callback(self._scroll_panel.is_touch_valid) - self._continue_button.set_click_callback(continue_callback) + if not self._continue_slider: + self._continue_button.set_click_callback(continue_callback) self._enable_back = back_callback is not None self._back_button = SmallButton(back_text) @@ -225,6 +230,10 @@ class TermsPage(Widget): self._back_button.set_opacity(0.0) self._scroll_down_indicator.set_opacity(1.0) + def show_event(self): + super().show_event() + self.reset() + @property @abstractmethod def _content_height(self): @@ -265,6 +274,11 @@ class TermsPage(Widget): rl.draw_rectangle_gradient_v(int(self._rect.x), int(self._rect.y + self._rect.height - 20), int(self._rect.width), 20, rl.BLANK, rl.BLACK) + # fade out back button as slider is moved + if self._continue_slider and scroll_offset <= self._scrolled_down_offset: + self._back_button.set_opacity(1.0 - self._continue_button.slider_percentage) + self._back_button.set_visible(self._continue_button.slider_percentage < 0.99) + self._back_button.render(rl.Rectangle( self._rect.x + 8, self._rect.y + self._rect.height - self._back_button.rect.height, @@ -275,6 +289,8 @@ class TermsPage(Widget): continue_x = self._rect.x + 8 if self._enable_back: continue_x = self._rect.x + self._rect.width - self._continue_button.rect.width - 8 + if self._continue_slider: + continue_x += 8 self._continue_button.render(rl.Rectangle( continue_x, self._rect.y + self._rect.height - self._continue_button.rect.height, diff --git a/system/ui/widgets/button.py b/system/ui/widgets/button.py index 34b2a51a4..9c0ea75b4 100644 --- a/system/ui/widgets/button.py +++ b/system/ui/widgets/button.py @@ -201,6 +201,7 @@ class SmallCircleIconButton(Widget): self._opacity_filter = FirstOrderFilter(1.0, 0.1, 1 / gui_app.target_fps) self._icon_bg_txt = gui_app.texture("icons_mici/setup/small_button.png", 100, 100) self._icon_bg_pressed_txt = gui_app.texture("icons_mici/setup/small_button_pressed.png", 100, 100) + self._icon_bg_disabled_txt = gui_app.texture("icons_mici/setup/small_button_disabled.png", 100, 100) self._icon_txt = icon_txt def set_opacity(self, opacity: float, smooth: bool = False): @@ -210,12 +211,18 @@ class SmallCircleIconButton(Widget): self._opacity_filter.x = opacity def _render(self, _): - bg_txt = self._icon_bg_pressed_txt if self.is_pressed else self._icon_bg_txt white = rl.Color(255, 255, 255, int(255 * self._opacity_filter.x)) + if not self.enabled: + bg_txt = self._icon_bg_disabled_txt + icon_white = rl.Color(255, 255, 255, int(white.a * 0.35)) + else: + bg_txt = self._icon_bg_pressed_txt if self.is_pressed else self._icon_bg_txt + icon_white = white + rl.draw_texture(bg_txt, int(self.rect.x), int(self.rect.y), white) icon_x = self.rect.x + (self.rect.width - self._icon_txt.width) / 2 icon_y = self.rect.y + (self.rect.height - self._icon_txt.height) / 2 - rl.draw_texture(self._icon_txt, int(icon_x), int(icon_y), white) + rl.draw_texture(self._icon_txt, int(icon_x), int(icon_y), icon_white) class SmallButton(Widget): diff --git a/system/ui/widgets/slider.py b/system/ui/widgets/slider.py index b17d8f3b7..455cdeef7 100644 --- a/system/ui/widgets/slider.py +++ b/system/ui/widgets/slider.py @@ -24,7 +24,7 @@ class SmallSlider(Widget): self._drag_threshold = -self._rect.width // 2 # State - self._opacity = 1.0 + self._opacity_filter = FirstOrderFilter(1.0, 0.1, 1 / gui_app.target_fps) self._confirmed_time = 0.0 self._confirm_callback_called = False # we keep dialog open by default, only call once self._start_x_circle = 0.0 @@ -54,8 +54,11 @@ class SmallSlider(Widget): self._confirmed_time = 0.0 self._confirm_callback_called = False - def set_opacity(self, opacity: float): - self._opacity = opacity + def set_opacity(self, opacity: float, smooth: bool = False): + if smooth: + self._opacity_filter.update(opacity) + else: + self._opacity_filter.x = opacity @property def slider_percentage(self): @@ -117,7 +120,7 @@ class SmallSlider(Widget): def _render(self, _): # TODO: iOS text shimmering animation - white = rl.Color(255, 255, 255, int(255 * self._opacity)) + white = rl.Color(255, 255, 255, int(255 * self._opacity_filter.x)) bg_txt_x = self._rect.x + (self._rect.width - self._bg_txt.width) / 2 bg_txt_y = self._rect.y + (self._rect.height - self._bg_txt.height) / 2 @@ -127,11 +130,11 @@ class SmallSlider(Widget): btn_y = self._rect.y + (self._rect.height - self._circle_bg_txt.height) / 2 if self._confirmed_time == 0.0 or self._scroll_x_circle > 0: - self._label.set_text_color(rl.Color(255, 255, 255, int(255 * 0.65 * (1.0 - self.slider_percentage) * self._opacity))) + self._label.set_text_color(rl.Color(255, 255, 255, int(255 * 0.65 * (1.0 - self.slider_percentage) * self._opacity_filter.x))) label_rect = rl.Rectangle( self._rect.x + 20, self._rect.y, - self._rect.width - self._circle_bg_txt.width - 20 * 3, + self._rect.width - self._circle_bg_txt.width - 20 * 2.5, self._rect.height, ) self._label.render(label_rect) From b9c3b1219a1687c9bf05b0bfc407ee227aace508 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 16 Dec 2025 23:45:34 -0800 Subject: [PATCH 635/910] ui: fix not showing networks if viewing right after startup --- selfdrive/ui/mici/layouts/settings/network/wifi_ui.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index 793bdcf4a..374539c4c 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -335,7 +335,7 @@ class WifiUIMici(BigMultiOptionDialog): self._networks: dict[str, Network] = {} # widget state - self._last_interaction_time = rl.get_time() + self._last_interaction_time = -float('inf') self._restore_selection = False self._wifi_manager.add_callbacks( @@ -350,6 +350,7 @@ class WifiUIMici(BigMultiOptionDialog): # Call super to prepare scroller; selection scroll is handled dynamically super().show_event() self._wifi_manager.set_active(True) + self._last_interaction_time = -float('inf') def hide_event(self): super().hide_event() From d2125aafd4c931817ff9a83a8a6c1c8def105175 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 17 Dec 2025 01:23:49 -0800 Subject: [PATCH 636/910] Fix tici DM dialog memory leak (#36790) * not finished * no * debug * clean up * clean up --- selfdrive/ui/onroad/driver_camera_dialog.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/selfdrive/ui/onroad/driver_camera_dialog.py b/selfdrive/ui/onroad/driver_camera_dialog.py index 543ea35e8..f69ad8c49 100644 --- a/selfdrive/ui/onroad/driver_camera_dialog.py +++ b/selfdrive/ui/onroad/driver_camera_dialog.py @@ -14,16 +14,20 @@ class DriverCameraDialog(CameraView): super().__init__("camerad", VisionStreamType.VISION_STREAM_DRIVER) self.driver_state_renderer = DriverStateRenderer() # TODO: this can grow unbounded, should be given some thought - device.add_interactive_timeout_callback(self.stop_dmonitoringmodeld) + device.add_interactive_timeout_callback(lambda: gui_app.set_modal_overlay(None)) ui_state.params.put_bool("IsDriverViewEnabled", True) - def stop_dmonitoringmodeld(self): + def hide_event(self): + super().hide_event() ui_state.params.put_bool("IsDriverViewEnabled", False) - gui_app.set_modal_overlay(None) + self.close() def _handle_mouse_release(self, _): super()._handle_mouse_release(_) - self.stop_dmonitoringmodeld() + gui_app.set_modal_overlay(None) + + def __del__(self): + self.close() def _render(self, rect): super()._render(rect) From 3fbd928b98ea8e63556d59cd3b14074f7d146afc Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 17 Dec 2025 01:53:17 -0800 Subject: [PATCH 637/910] Revert "ui: generic hold gesture (#36893)" This reverts commit 9768109ec11416648076163af230d82563666628. --- selfdrive/ui/mici/layouts/home.py | 33 +++++++++++++++++++++++-------- system/ui/widgets/__init__.py | 30 ++-------------------------- 2 files changed, 27 insertions(+), 36 deletions(-) diff --git a/selfdrive/ui/mici/layouts/home.py b/selfdrive/ui/mici/layouts/home.py index af97de92f..9152bdc7f 100644 --- a/selfdrive/ui/mici/layouts/home.py +++ b/selfdrive/ui/mici/layouts/home.py @@ -1,3 +1,5 @@ +import time + from cereal import log import pyray as rl from collections.abc import Callable @@ -81,6 +83,9 @@ class MiciHomeLayout(Widget): self._on_settings_click: Callable | None = None self._last_refresh = 0 + self._mouse_down_t: None | float = None + self._did_long_press = False + self._is_pressed_prev = False self._version_text = None self._experimental_mode = False @@ -119,13 +124,23 @@ class MiciHomeLayout(Widget): def _update_params(self): self._experimental_mode = ui_state.params.get_bool("ExperimentalMode") - def _handle_long_press(self, _): - # long gating for experimental mode - only allow toggle if longitudinal control is available - if ui_state.has_longitudinal_control: - self._experimental_mode = not self._experimental_mode - ui_state.params.put("ExperimentalMode", self._experimental_mode) - def _update_state(self): + if self.is_pressed and not self._is_pressed_prev: + self._mouse_down_t = time.monotonic() + elif not self.is_pressed and self._is_pressed_prev: + self._mouse_down_t = None + self._did_long_press = False + self._is_pressed_prev = self.is_pressed + + if self._mouse_down_t is not None: + if time.monotonic() - self._mouse_down_t > 0.5: + # long gating for experimental mode - only allow toggle if longitudinal control is available + if ui_state.has_longitudinal_control: + self._experimental_mode = not self._experimental_mode + ui_state.params.put("ExperimentalMode", self._experimental_mode) + self._mouse_down_t = None + self._did_long_press = True + if rl.get_time() - self._last_refresh > 5.0: device_state = ui_state.sm['deviceState'] self._update_network_status(device_state) @@ -144,8 +159,10 @@ class MiciHomeLayout(Widget): self._on_settings_click = on_settings def _handle_mouse_release(self, mouse_pos: MousePos): - if self._on_settings_click: - self._on_settings_click() + if not self._did_long_press: + if self._on_settings_click: + self._on_settings_click() + self._did_long_press = False def _get_version_text(self) -> tuple[str, str, str, str] | None: description = ui_state.params.get("UpdaterCurrentDescription") diff --git a/system/ui/widgets/__init__.py b/system/ui/widgets/__init__.py index 5b3b4a691..a3fed6d96 100644 --- a/system/ui/widgets/__init__.py +++ b/system/ui/widgets/__init__.py @@ -20,8 +20,6 @@ class DialogResult(IntEnum): class Widget(abc.ABC): - LONG_PRESS_TIME = 0.5 - def __init__(self): self._rect: rl.Rectangle = rl.Rectangle(0, 0, 0, 0) self._parent_rect: rl.Rectangle | None = None @@ -35,10 +33,6 @@ class Widget(abc.ABC): self._multi_touch = False self.__was_awake = True - # Long press state (single touch only, slot 0) - self._long_press_start_t: float | None = None - self._long_press_fired: bool = False - @property def rect(self) -> rl.Rectangle: return self._rect @@ -133,28 +127,19 @@ class Widget(abc.ABC): self._handle_mouse_press(mouse_event.pos) self.__is_pressed[mouse_event.slot] = True self.__tracking_is_pressed[mouse_event.slot] = True - if mouse_event.slot == 0: - self._long_press_start_t = rl.get_time() - self._long_press_fired = False self._handle_mouse_event(mouse_event) # Callback such as scroll panel signifies user is scrolling elif not touch_valid: self.__is_pressed[mouse_event.slot] = False self.__tracking_is_pressed[mouse_event.slot] = False - if mouse_event.slot == 0: - self._long_press_start_t = None - self._long_press_fired = False elif mouse_event.left_released: self._handle_mouse_event(mouse_event) - if self.__is_pressed[mouse_event.slot] and mouse_in_rect and not (mouse_event.slot == 0 and self._long_press_fired): + if self.__is_pressed[mouse_event.slot] and mouse_in_rect: self._handle_mouse_release(mouse_event.pos) self.__is_pressed[mouse_event.slot] = False self.__tracking_is_pressed[mouse_event.slot] = False - if mouse_event.slot == 0: - self._long_press_start_t = None - self._long_press_fired = False # Mouse/touch is still within our rect elif mouse_in_rect: @@ -165,17 +150,8 @@ class Widget(abc.ABC): # Mouse/touch left our rect but may come back into focus later elif not mouse_in_rect: self.__is_pressed[mouse_event.slot] = False - if mouse_event.slot == 0: - self._long_press_start_t = None - self._long_press_fired = False self._handle_mouse_event(mouse_event) - # Long press detection - if self._long_press_start_t is not None and not self._long_press_fired: - if (rl.get_time() - self._long_press_start_t) >= self.LONG_PRESS_TIME: - self._long_press_fired = True - self._handle_long_press(gui_app.last_mouse_event.pos) - def _layout(self) -> None: """Optionally lay out child widgets separately. This is called before rendering.""" @@ -199,11 +175,9 @@ class Widget(abc.ABC): self._click_callback() return False - def _handle_long_press(self, mouse_pos: MousePos) -> None: - """Optionally handle a long-press gesture.""" - def _handle_mouse_event(self, mouse_event: MouseEvent) -> None: """Optionally handle mouse events. This is called before rendering.""" + # Default implementation does nothing, can be overridden by subclasses def show_event(self): """Optionally handle show event. Parent must manually call this""" From 1646fd94b83ea98dc1120a53c4e88a37454e0c4c Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 17 Dec 2025 01:55:17 -0800 Subject: [PATCH 638/910] setup: go back to main page once connected (#36902) * call * break * print * fix * rm * debug * fix * yeah ideally wifiui has no clue about this * clean up * clean up * clean up * only need this * cu * rm * fix --- system/ui/lib/application.py | 7 +++++++ system/ui/mici_setup.py | 19 ++++++++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 719d38284..501a7ff37 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -218,6 +218,7 @@ class GuiApplication: self._trace_log_callback = None self._modal_overlay = ModalOverlay() self._modal_overlay_shown = False + self._modal_overlay_tick: Callable[[], None] | None = None self._mouse = MouseState(self._scale) self._mouse_events: list[MouseEvent] = [] @@ -347,6 +348,9 @@ class GuiApplication: self._modal_overlay = ModalOverlay(overlay=overlay, callback=callback) + def set_modal_overlay_tick(self, tick_function: Callable | None): + self._modal_overlay_tick = tick_function + def set_should_render(self, should_render: bool): self._should_render = should_render @@ -485,6 +489,9 @@ class GuiApplication: # Handle modal overlay rendering and input processing if self._handle_modal_overlay(): + # Allow a Widget to still run a function while overlay is shown + if self._modal_overlay_tick is not None: + self._modal_overlay_tick() yield False else: yield True diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index fda35be6e..2c6090b4a 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -454,9 +454,12 @@ class NetworkSetupPage(Widget): self._continue_button.set_click_callback(continue_callback) self._state = NetworkSetupState.MAIN + self._prev_has_internet = False def set_state(self, state: NetworkSetupState): self._state = state + if state == NetworkSetupState.WIFI_PANEL: + self._wifi_ui.show_event() def set_has_internet(self, has_internet: bool): if has_internet: @@ -468,6 +471,10 @@ class NetworkSetupPage(Widget): self._network_header.set_icon(self._no_wifi_txt) self._continue_button.set_enabled(False) + if has_internet and not self._prev_has_internet: + self.set_state(NetworkSetupState.MAIN) + self._prev_has_internet = has_internet + def show_event(self): super().show_event() self._state = NetworkSetupState.MAIN @@ -524,6 +531,8 @@ class Setup(Widget): self._network_monitor = NetworkConnectivityMonitor( lambda: self.state in (SetupState.NETWORK_SETUP, SetupState.NETWORK_SETUP_CUSTOM_SOFTWARE) ) + self._prev_has_internet = False + gui_app.set_modal_overlay_tick(self._modal_overlay_tick) self._start_page = StartPage() self._start_page.set_click_callback(self._getting_started_button_callback) @@ -541,6 +550,12 @@ class Setup(Widget): self._downloading_page = DownloadingPage() + def _modal_overlay_tick(self): + has_internet = self._network_monitor.network_connected.is_set() + if has_internet and not self._prev_has_internet: + gui_app.set_modal_overlay(None) + self._prev_has_internet = has_internet + def _update_state(self): self._wifi_manager.process_callbacks() @@ -614,7 +629,9 @@ class Setup(Widget): def render_network_setup(self, rect: rl.Rectangle): self._network_setup_page.render(rect) - self._network_setup_page.set_has_internet(self._network_monitor.network_connected.is_set()) + has_internet = self._network_monitor.network_connected.is_set() + self._prev_has_internet = has_internet + self._network_setup_page.set_has_internet(has_internet) def render_downloading(self, rect: rl.Rectangle): self._downloading_page.set_progress(self.download_progress) From be2818a131997214b8a284722e8060988422e100 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 17 Dec 2025 09:37:30 -0800 Subject: [PATCH 639/910] CI: tmp disable macOS due to brew bug (#36906) * need update? * try this * x * just disable it --- .github/workflows/tests.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index c5802b5cb..f0028841a 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -91,6 +91,7 @@ jobs: build_mac: name: build macOS + if: false # tmp disable due to brew install not working runs-on: ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-macos-8x14' || 'macos-latest' }} steps: - uses: actions/checkout@v4 From 4bfc28dec067e43074faed9cb6f9e59191519980 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 17 Dec 2025 10:19:05 -0800 Subject: [PATCH 640/910] lil more release notes --- RELEASES.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 618b28dc5..302835140 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,9 +1,11 @@ Version 0.10.3 (2025-12-17) ======================== * New driving model #36249 - * minor changes in the temporal policy architecture and on-policy training physics noise model + * New temporal policy architecture + * New on-policy training physics noise model * New driver monitoring model #36409 - * trained with a new driver monitoring dataset including comma four data + * Trained on a new dataset, including comma four data +* Improved inter-process communication memory efficiency Version 0.10.2 (2025-11-19) ======================== From 88a4f2baf1de736b6f95f5e9c493d8fdd4d829a3 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 17 Dec 2025 16:27:43 -0800 Subject: [PATCH 641/910] Onboarding: no buttons if no frame (#36909) no buttons if no frame --- selfdrive/ui/mici/layouts/onboarding.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/selfdrive/ui/mici/layouts/onboarding.py b/selfdrive/ui/mici/layouts/onboarding.py index d35881341..5d4412221 100644 --- a/selfdrive/ui/mici/layouts/onboarding.py +++ b/selfdrive/ui/mici/layouts/onboarding.py @@ -237,19 +237,20 @@ class TrainingGuideDMTutorial(Widget): ring_color, ) - self._back_button.render(rl.Rectangle( - self._rect.x + 8, - self._rect.y + self._rect.height - self._back_button.rect.height, - self._back_button.rect.width, - self._back_button.rect.height, - )) + if self._dialog._camera_view.frame: + self._back_button.render(rl.Rectangle( + self._rect.x + 8, + self._rect.y + self._rect.height - self._back_button.rect.height, + self._back_button.rect.width, + self._back_button.rect.height, + )) - self._good_button.render(rl.Rectangle( - self._rect.x + self._rect.width - self._good_button.rect.width - 8, - self._rect.y + self._rect.height - self._good_button.rect.height, - self._good_button.rect.width, - self._good_button.rect.height, - )) + self._good_button.render(rl.Rectangle( + self._rect.x + self._rect.width - self._good_button.rect.width - 8, + self._rect.y + self._rect.height - self._good_button.rect.height, + self._good_button.rect.width, + self._good_button.rect.height, + )) # rounded border rl.draw_rectangle_rounded_lines_ex(self._rect, 0.2 * 1.02, 10, 50, rl.BLACK) From 4e855683701949c5da290ac646de837e5301b14e Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 17 Dec 2025 17:02:19 -0800 Subject: [PATCH 642/910] MultiOptionDialog: support no default (#36912) fix --- selfdrive/ui/mici/widgets/dialog.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index 3d9aa3f9e..3cd9f8b2c 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -323,8 +323,8 @@ class BigMultiOptionDialog(BigDialogBase): if default is not None: assert default in options - self._default_option: str = default or (options[0] if len(options) > 0 else "") - self._selected_option: str = self._default_option + self._default_option: str | None = default + self._selected_option: str = self._default_option or (options[0] if len(options) > 0 else "") self._last_selected_option: str = self._selected_option self._scroller = Scroller([], horizontal=False, pad_start=100, pad_end=100, spacing=0, snap_items=True) @@ -344,7 +344,8 @@ class BigMultiOptionDialog(BigDialogBase): def show_event(self): super().show_event() self._scroller.show_event() - self._on_option_selected(self._default_option) + if self._default_option is not None: + self._on_option_selected(self._default_option) def get_selected_option(self) -> str: return self._selected_option From 2213f8f8a4002745ae67557ee518a8d374a9d701 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 17 Dec 2025 17:10:12 -0800 Subject: [PATCH 643/910] MultiOptionDialog: tap activates current option (#36911) * works * clean up --- .../mici/layouts/settings/network/wifi_ui.py | 11 ++++----- selfdrive/ui/mici/widgets/dialog.py | 24 +++++++++++-------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index 374539c4c..06347b565 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -388,7 +388,7 @@ class WifiUIMici(BigMultiOptionDialog): else: network_button = WifiItem(network) - self.add_button(network_button) + self._scroller.add_widget(network_button) # remove networks no longer present self._scroller._items[:] = [btn for btn in self._scroller._items if btn.option in self._networks] @@ -402,11 +402,10 @@ class WifiUIMici(BigMultiOptionDialog): self._wifi_manager.connect_to_network(ssid, password) self._update_buttons() - def _on_option_selected(self, option: str, smooth_scroll: bool = True): - super()._on_option_selected(option, smooth_scroll) + def _on_option_selected(self, option: str): + super()._on_option_selected(option) - # only open if button is already selected - if option in self._networks and option == self._selected_option: + if option in self._networks: self._network_info_page.set_current_network(self._networks[option]) self._open_network_manage_page() @@ -453,7 +452,7 @@ class WifiUIMici(BigMultiOptionDialog): current_selection = self.get_selected_option() if self._restore_selection and current_selection in self._networks: self._scroller._layout() - BigMultiOptionDialog._on_option_selected(self, current_selection, smooth_scroll=False) + BigMultiOptionDialog._on_option_selected(self, current_selection) self._restore_selection = None super()._render(_) diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index 3cd9f8b2c..eb36494ad 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -328,18 +328,12 @@ class BigMultiOptionDialog(BigDialogBase): self._last_selected_option: str = self._selected_option self._scroller = Scroller([], horizontal=False, pad_start=100, pad_end=100, spacing=0, snap_items=True) + self.set_touch_valid_callback(self._scroller.scroll_panel.is_touch_valid) if self._right_btn is not None: self._scroller.set_enabled(lambda: not cast(Widget, self._right_btn).is_pressed) for option in options: - self.add_button(BigDialogOptionButton(option)) - - def add_button(self, button: BigDialogOptionButton): - def click_callback(_btn=button): - self._on_option_selected(_btn.option) - - button.set_click_callback(click_callback) - self._scroller.add_widget(button) + self._scroller.add_widget(BigDialogOptionButton(option)) def show_event(self): super().show_event() @@ -350,7 +344,7 @@ class BigMultiOptionDialog(BigDialogBase): def get_selected_option(self) -> str: return self._selected_option - def _on_option_selected(self, option: str, smooth_scroll: bool = True): + def _on_option_selected(self, option: str): y_pos = 0.0 for btn in self._scroller._items: btn = cast(BigDialogOptionButton, btn) @@ -366,11 +360,21 @@ class BigMultiOptionDialog(BigDialogBase): y_pos = rect_center_y - (btn.rect.y + height / 2) break - self._scroller.scroll_to(-y_pos, smooth=smooth_scroll) + self._scroller.scroll_to(-y_pos) def _selected_option_changed(self): pass + def _handle_mouse_release(self, mouse_pos: MousePos): + super()._handle_mouse_release(mouse_pos) + + # select current option + for btn in self._scroller._items: + btn = cast(BigDialogOptionButton, btn) + if btn.option == self._selected_option: + self._on_option_selected(btn.option) + break + def _update_state(self): super()._update_state() From 7560497f155a29d7ee0c35f8e9f0bf679ae250ac Mon Sep 17 00:00:00 2001 From: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Wed, 17 Dec 2025 18:32:18 -0800 Subject: [PATCH 644/910] Revert "latcontrol_torque: lower kp and lower friction threshold (commaai/openpilot#36619)" (#1581) * Revert " latcontrol_torque: delay independent jerk and lower kp and lower friction threshold (#36619)" This reverts commit f01391a7d9b5d0b7a56a346d85c7de42302656b7 * revert opendbc_repo * bump --------- Co-authored-by: Jason Wen --- opendbc_repo | 2 +- selfdrive/controls/lib/latcontrol_torque.py | 35 ++++++++++++--------- selfdrive/test/process_replay/ref_commit | 2 +- 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/opendbc_repo b/opendbc_repo index 1890dbf29..a76d28a23 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 1890dbf2996049a2d58074adca0f4156a1591e0d +Subproject commit a76d28a231dd8a3de11ed47db2d185d3852c6925 diff --git a/selfdrive/controls/lib/latcontrol_torque.py b/selfdrive/controls/lib/latcontrol_torque.py index 48d174bfa..47d105f99 100644 --- a/selfdrive/controls/lib/latcontrol_torque.py +++ b/selfdrive/controls/lib/latcontrol_torque.py @@ -22,17 +22,15 @@ from openpilot.sunnypilot.selfdrive.controls.lib.latcontrol_torque_ext import La # Additionally, there is friction in the steering wheel that needs # to be overcome to move it at all, this is compensated for too. -KP = 0.8 -KI = 0.15 - +KP = 1.0 +KI = 0.3 +KD = 0.0 INTERP_SPEEDS = [1, 1.5, 2.0, 3.0, 5, 7.5, 10, 15, 30] KP_INTERP = [250, 120, 65, 30, 11.5, 5.5, 3.5, 2.0, KP] LP_FILTER_CUTOFF_HZ = 1.2 -JERK_LOOKAHEAD_SECONDS = 0.19 -JERK_GAIN = 0.3 LAT_ACCEL_REQUEST_BUFFER_SECONDS = 1.0 -VERSION = 1 +VERSION = 0 class LatControlTorque(LatControl): def __init__(self, CP, CP_SP, CI, dt): @@ -40,13 +38,13 @@ class LatControlTorque(LatControl): self.torque_params = CP.lateralTuning.torque.as_builder() self.torque_from_lateral_accel = CI.torque_from_lateral_accel() self.lateral_accel_from_torque = CI.lateral_accel_from_torque() - self.pid = PIDController([INTERP_SPEEDS, KP_INTERP], KI, rate=1/self.dt) + self.pid = PIDController([INTERP_SPEEDS, KP_INTERP], KI, KD, rate=1/self.dt) self.update_limits() self.steering_angle_deadzone_deg = self.torque_params.steeringAngleDeadzoneDeg self.lat_accel_request_buffer_len = int(LAT_ACCEL_REQUEST_BUFFER_SECONDS / self.dt) self.lat_accel_request_buffer = deque([0.] * self.lat_accel_request_buffer_len , maxlen=self.lat_accel_request_buffer_len) - self.lookahead_frames = int(JERK_LOOKAHEAD_SECONDS / self.dt) - self.jerk_filter = FirstOrderFilter(0.0, 1 / (2 * np.pi * LP_FILTER_CUTOFF_HZ), self.dt) + self.previous_measurement = 0.0 + self.measurement_rate_filter = FirstOrderFilter(0.0, 1 / (2 * np.pi * LP_FILTER_CUTOFF_HZ), self.dt) self.extension = LatControlTorqueExt(self, CP, CP_SP, CI) @@ -78,15 +76,17 @@ class LatControlTorque(LatControl): delay_frames = int(np.clip(lat_delay / self.dt, 1, self.lat_accel_request_buffer_len)) expected_lateral_accel = self.lat_accel_request_buffer[-delay_frames] - lookahead_idx = int(np.clip(-delay_frames + self.lookahead_frames, -self.lat_accel_request_buffer_len+1, -2)) - raw_lateral_jerk = (self.lat_accel_request_buffer[lookahead_idx+1] - self.lat_accel_request_buffer[lookahead_idx-1]) / (2 * self.dt) - desired_lateral_jerk = self.jerk_filter.update(raw_lateral_jerk) + # TODO factor out lateral jerk from error to later replace it with delay independent alternative future_desired_lateral_accel = desired_curvature * CS.vEgo ** 2 self.lat_accel_request_buffer.append(future_desired_lateral_accel) gravity_adjusted_future_lateral_accel = future_desired_lateral_accel - roll_compensation - setpoint = expected_lateral_accel + desired_lateral_jerk = (future_desired_lateral_accel - expected_lateral_accel) / lat_delay measurement = measured_curvature * CS.vEgo ** 2 + measurement_rate = self.measurement_rate_filter.update((measurement - self.previous_measurement) / self.dt) + self.previous_measurement = measurement + + setpoint = lat_delay * desired_lateral_jerk + expected_lateral_accel error = setpoint - measurement # do error correction in lateral acceleration space, convert at end to handle non-linear torque responses correctly @@ -94,10 +94,15 @@ class LatControlTorque(LatControl): ff = gravity_adjusted_future_lateral_accel # latAccelOffset corrects roll compensation bias from device roll misalignment relative to car roll ff -= self.torque_params.latAccelOffset - ff += get_friction(error + JERK_GAIN * desired_lateral_jerk, lateral_accel_deadzone, FRICTION_THRESHOLD, self.torque_params) + # TODO jerk is weighted by lat_delay for legacy reasons, but should be made independent of it + ff += get_friction(error, lateral_accel_deadzone, FRICTION_THRESHOLD, self.torque_params) freeze_integrator = steer_limited_by_safety or CS.steeringPressed or CS.vEgo < 5 - output_lataccel = self.pid.update(pid_log.error, speed=CS.vEgo, feedforward=ff, freeze_integrator=freeze_integrator) + output_lataccel = self.pid.update(pid_log.error, + -measurement_rate, + feedforward=ff, + speed=CS.vEgo, + freeze_integrator=freeze_integrator) output_torque = self.torque_from_lateral_accel(output_lataccel, self.torque_params) # Lateral acceleration torque controller extension updates diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 4a58e321f..cdd4301fc 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -e0ad86508edb61b3eaa1b84662c515d2c3368295 \ No newline at end of file +b508f43fb0481bce0859c9b6ab4f45ee690b8dab \ No newline at end of file From 6249211745d6cc68fd7446c78e2c9b9a64ca1393 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 17 Dec 2025 19:57:57 -0800 Subject: [PATCH 645/910] Update canError alert texts (#36918) * hmm * hmm it's small * can * ? * variant? * unknown --- selfdrive/selfdrived/events.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/selfdrive/selfdrived/events.py b/selfdrive/selfdrived/events.py index c6dbcccbe..1acfa2964 100755 --- a/selfdrive/selfdrived/events.py +++ b/selfdrive/selfdrived/events.py @@ -932,13 +932,13 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = { # - CAN data is received, but some message are not received at the right frequency # If you're not writing a new car port, this is usually cause by faulty wiring EventName.canError: { - ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("CAN Error"), + ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("Unknown Vehicle Variant"), ET.PERMANENT: Alert( - "CAN Error: Check Connections", + "Unknown Vehicle Variant", "", AlertStatus.normal, AlertSize.small, Priority.LOW, VisualAlert.none, AudibleAlert.none, 1., creation_delay=1.), - ET.NO_ENTRY: NoEntryAlert("CAN Error: Check Connections"), + ET.NO_ENTRY: NoEntryAlert("Unknown Vehicle Variant"), }, EventName.canBusMissing: { From 28fa7d5ed924e4b88b48a5a6338ff3d47de7a250 Mon Sep 17 00:00:00 2001 From: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Wed, 17 Dec 2025 20:54:39 -0800 Subject: [PATCH 646/910] sync: update message queues and upstream merge conflicts (#1585) * bug: fix sp message queues and protected method access * more --------- Co-authored-by: Jason Wen --- cereal/services.py | 6 +++--- selfdrive/ui/sunnypilot/layouts/settings/models.py | 2 +- selfdrive/ui/sunnypilot/layouts/settings/osm.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cereal/services.py b/cereal/services.py index 666bd0d27..a2fd5f7dc 100755 --- a/cereal/services.py +++ b/cereal/services.py @@ -90,8 +90,8 @@ _services: dict[str, tuple] = { "qRoadEncodeData": (False, 20., None, QueueSize.BIG), # sunnypilot - "modelManagerSP": (False, 1., 1), - "backupManagerSP": (False, 1., 1), + "modelManagerSP": (False, 1., 1, QueueSize.BIG), + "backupManagerSP": (False, 1., 1, QueueSize.BIG), "selfdriveStateSP": (True, 100., 10), "longitudinalPlanSP": (True, 20., 10), "onroadEventsSP": (True, 1., 1), @@ -99,7 +99,7 @@ _services: dict[str, tuple] = { "carControlSP": (True, 100., 10), "carStateSP": (True, 100., 10), "liveMapDataSP": (True, 1., 1), - "modelDataV2SP": (True, 20.), + "modelDataV2SP": (True, 20., None, QueueSize.BIG), "liveLocationKalman": (True, 20.), # debug diff --git a/selfdrive/ui/sunnypilot/layouts/settings/models.py b/selfdrive/ui/sunnypilot/layouts/settings/models.py index 211aad6a2..5820b34cc 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/models.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/models.py @@ -153,7 +153,7 @@ class ModelsLayout(Widget): self.clear_cache_item.action_item.set_value(f"{self._calculate_cache_size():.2f} MB") if self.download_status == custom.ModelManagerSP.DownloadStatus.downloading: - device.reset_interactive_timeout() + device._reset_interactive_timeout() for model in bundle.models: if label := labels.get(getattr(model.type, 'raw', model.type)): diff --git a/selfdrive/ui/sunnypilot/layouts/settings/osm.py b/selfdrive/ui/sunnypilot/layouts/settings/osm.py index 3726a820f..f8a3a8504 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/osm.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/osm.py @@ -165,7 +165,7 @@ class OSMLayout(Widget): pending = ui_state.params.get_bool("OsmDbUpdatesCheck") if downloading or pending: if downloading: - device.reset_interactive_timeout() + device._reset_interactive_timeout() self._update_map_size() self._progress.set_visible(True) progress = ui_state.params.get("OSMDownloadProgress") From f5b3d87e25424fda5e3e46a3c40910a3d9c255d1 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 18 Dec 2025 00:03:48 -0500 Subject: [PATCH 647/910] ci: add GitHub app token for authenticated pushes (#1586) --- .github/workflows/sunnypilot-master-dev-prep.yaml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sunnypilot-master-dev-prep.yaml b/.github/workflows/sunnypilot-master-dev-prep.yaml index e7a966374..10794bf0f 100644 --- a/.github/workflows/sunnypilot-master-dev-prep.yaml +++ b/.github/workflows/sunnypilot-master-dev-prep.yaml @@ -49,6 +49,7 @@ jobs: with: fetch-depth: 0 # Fetch all history for all branches token: ${{ secrets.GITHUB_TOKEN }} + persist-credentials: false - name: Wait for Tests uses: ./.github/workflows/wait-for-action # Path to where you place the action @@ -173,11 +174,20 @@ jobs: echo ' pushurl = ${{ env.LFS_PUSH_URL }}' >> .lfsconfig echo ' locksverify = false' >> .lfsconfig + - uses: actions/create-github-app-token@v2 + id: ci-token + with: + app-id: ${{ secrets.CI_GITHUB_ACTIONS_TOKEN_APP_ID }} + private-key: ${{ secrets.CI_GITHUB_ACTIONS_TOKEN_APP_PRIVATE_KEY }} + - name: Push changes if there are diffs - id: push-changes # Add an id so we can reference this step + id: push-changes run: | TARGET_BRANCH="${{ inputs.target_branch || env.DEFAULT_TARGET_BRANCH }}" + # Use the App Token to set the remote URL with authentication + git remote set-url origin "https://x-access-token:${{ steps.ci-token.outputs.token }}@github.com/${{ github.repository }}.git" + # Fetch the latest from remote git fetch origin $TARGET_BRANCH @@ -188,7 +198,7 @@ jobs: exit 0 fi - # If we get here, there are diffs, so push + # Push with the authenticated origin if ! git push origin $TARGET_BRANCH --force; then echo "Failed to push changes to $TARGET_BRANCH" exit 1 From 2b51adff11eb1f5ee785f89150c05ccdb9544947 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 18 Dec 2025 00:20:42 -0500 Subject: [PATCH 648/910] sunnylink: Vehicle Selector support (#1587) * sunnylink: Vehicle Selector support * shebang --- common/params_keys.h | 1 + .../selfdrive/car/sync_car_list_param.py | 31 +++++++++++++++++++ sunnypilot/sunnylink/athena/sunnylinkd.py | 3 ++ sunnypilot/sunnylink/params_metadata.json | 4 +++ 4 files changed, 39 insertions(+) create mode 100755 sunnypilot/selfdrive/car/sync_car_list_param.py diff --git a/common/params_keys.h b/common/params_keys.h index a5f0ae29f..1b06711a9 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -139,6 +139,7 @@ inline static std::unordered_map keys = { {"BlinkerMinLateralControlSpeed", {PERSISTENT | BACKUP, INT, "20"}}, // MPH or km/h {"BlinkerPauseLateralControl", {PERSISTENT | BACKUP, INT, "0"}}, {"Brightness", {PERSISTENT | BACKUP, INT, "0"}}, + {"CarList", {PERSISTENT, JSON}}, {"CarParamsSP", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BYTES}}, {"CarParamsSPCache", {CLEAR_ON_MANAGER_START, BYTES}}, {"CarParamsSPPersistent", {PERSISTENT, BYTES}}, diff --git a/sunnypilot/selfdrive/car/sync_car_list_param.py b/sunnypilot/selfdrive/car/sync_car_list_param.py new file mode 100755 index 000000000..5e25f6da9 --- /dev/null +++ b/sunnypilot/selfdrive/car/sync_car_list_param.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +import json +import os + +from openpilot.common.basedir import BASEDIR +from openpilot.common.params import Params +from openpilot.common.swaglog import cloudlog + +CAR_LIST_JSON_OUT = os.path.join(BASEDIR, "sunnypilot", "selfdrive", "car", "car_list.json") + + +def update_car_list_param(): + with open(CAR_LIST_JSON_OUT) as f: + current_car_list = json.load(f) + + params = Params() + if params.get("CarList") != current_car_list: + params.put("CarList", current_car_list) + cloudlog.warning("Updated CarList param with latest platform list") + else: + cloudlog.warning("CarList param is up to date, no need to update") + + +if __name__ == "__main__": + update_car_list_param() diff --git a/sunnypilot/sunnylink/athena/sunnylinkd.py b/sunnypilot/sunnylink/athena/sunnylinkd.py index a57b6ef55..3aeacf6a3 100755 --- a/sunnypilot/sunnylink/athena/sunnylinkd.py +++ b/sunnypilot/sunnylink/athena/sunnylinkd.py @@ -28,6 +28,7 @@ from websocket import (ABNF, WebSocket, WebSocketException, WebSocketTimeoutExce create_connection, WebSocketConnectionClosedException) import cereal.messaging as messaging +from openpilot.sunnypilot.selfdrive.car.sync_car_list_param import update_car_list_param from openpilot.sunnypilot.sunnylink.api import SunnylinkApi from openpilot.sunnypilot.sunnylink.utils import sunnylink_need_register, sunnylink_ready, get_param_as_byte, save_param_from_base64_encoded_string @@ -279,6 +280,8 @@ def main(exit_event: threading.Event = None): sunnylink_api = SunnylinkApi(sunnylink_dongle_id) UploadQueueCache.initialize(upload_queue) + update_car_list_param() + ws_uri = f"{SUNNYLINK_ATHENA_HOST}" conn_start = None conn_retries = 0 diff --git a/sunnypilot/sunnylink/params_metadata.json b/sunnypilot/sunnylink/params_metadata.json index 3228fd0d6..707e0620f 100644 --- a/sunnypilot/sunnylink/params_metadata.json +++ b/sunnypilot/sunnylink/params_metadata.json @@ -125,6 +125,10 @@ "title": "Car Battery Capacity", "description": "" }, + "CarList": { + "title": "Supported Car List", + "description": "All supported platform in sunnypilot" + }, "CarParams": { "title": "Car Params", "description": "" From 631d6d9ef4f8c375843c64c6b3552e32adf7b363 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 17 Dec 2025 21:26:52 -0800 Subject: [PATCH 649/910] Widget: mouse handlers don't use bool (#36921) * not used * lint --- selfdrive/ui/layouts/settings/settings.py | 8 +++----- system/ui/widgets/__init__.py | 6 ++---- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/selfdrive/ui/layouts/settings/settings.py b/selfdrive/ui/layouts/settings/settings.py index 72d3a4baf..68f45df77 100644 --- a/selfdrive/ui/layouts/settings/settings.py +++ b/selfdrive/ui/layouts/settings/settings.py @@ -145,20 +145,18 @@ class SettingsLayout(Widget): if panel.instance: panel.instance.render(content_rect) - def _handle_mouse_release(self, mouse_pos: MousePos) -> bool: + def _handle_mouse_release(self, mouse_pos: MousePos) -> None: # Check close button if rl.check_collision_point_rec(mouse_pos, self._close_btn_rect): if self._close_callback: self._close_callback() - return True + return # Check navigation buttons for panel_type, panel_info in self._panels.items(): if rl.check_collision_point_rec(mouse_pos, panel_info.button_rect): self.set_current_panel(panel_type) - return True - - return False + return def set_current_panel(self, panel_type: PanelType): if panel_type != self._current_panel: diff --git a/system/ui/widgets/__init__.py b/system/ui/widgets/__init__.py index a3fed6d96..8a46a3e43 100644 --- a/system/ui/widgets/__init__.py +++ b/system/ui/widgets/__init__.py @@ -165,15 +165,13 @@ class Widget(abc.ABC): def _update_layout_rects(self) -> None: """Optionally update any layout rects on Widget rect change.""" - def _handle_mouse_press(self, mouse_pos: MousePos) -> bool: + def _handle_mouse_press(self, mouse_pos: MousePos) -> None: """Optionally handle mouse press events.""" - return False - def _handle_mouse_release(self, mouse_pos: MousePos) -> bool: + def _handle_mouse_release(self, mouse_pos: MousePos) -> None: """Optionally handle mouse release events.""" if self._click_callback: self._click_callback() - return False def _handle_mouse_event(self, mouse_event: MouseEvent) -> None: """Optionally handle mouse events. This is called before rendering.""" From f3598ce3ed902a2ad3a2bcc894f2609edca39fef Mon Sep 17 00:00:00 2001 From: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Wed, 17 Dec 2025 21:29:37 -0800 Subject: [PATCH 650/910] ci: fix sunnypilot unit tests (#1584) This PR aims to fix sunnypilot pytest that was broken on MacOS due to calling capnp to_dict, which isn't supported on pycapnp library for Mac. Co-authored-by: Jason Wen --- sunnypilot/selfdrive/car/interfaces.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sunnypilot/selfdrive/car/interfaces.py b/sunnypilot/selfdrive/car/interfaces.py index b534b7e37..83114ac55 100644 --- a/sunnypilot/selfdrive/car/interfaces.py +++ b/sunnypilot/selfdrive/car/interfaces.py @@ -102,7 +102,10 @@ def setup_interfaces(CI: CarInterfaceBase, params: Params = None) -> None: _initialize_torque_lateral_control(CI, CP, enforce_torque, nnlc_enabled) _cleanup_unsupported_params(CP, CP_SP) - STATSLOGSP.raw('sunnypilot.car_params', CP.to_dict()) + try: + STATSLOGSP.raw('sunnypilot.car_params', CP.to_dict()) + except RuntimeError: + pass # to_dict fails on macOS due to library issues. # STATSLOGSP.raw('sunnypilot_params.car_params_sp', CP_SP.to_dict()) # https://github.com/sunnypilot/opendbc/pull/361 From 792a9b715c1c577f0adaf41c6fcc925884b2dc79 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 17 Dec 2025 21:30:40 -0800 Subject: [PATCH 651/910] Widget: track mouse events (#36922) * track * fix * rm --- system/ui/widgets/__init__.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/system/ui/widgets/__init__.py b/system/ui/widgets/__init__.py index 8a46a3e43..376836348 100644 --- a/system/ui/widgets/__init__.py +++ b/system/ui/widgets/__init__.py @@ -26,6 +26,7 @@ class Widget(abc.ABC): self.__is_pressed = [False] * MAX_TOUCH_SLOTS # if current mouse/touch down started within the widget's rectangle self.__tracking_is_pressed = [False] * MAX_TOUCH_SLOTS + self.__mouse_events: list[MouseEvent | None] = [None] * MAX_TOUCH_SLOTS self._enabled: bool | Callable[[], bool] = True self._is_visible: bool | Callable[[], bool] = True self._touch_valid_callback: Callable[[], bool] | None = None @@ -125,12 +126,14 @@ class Widget(abc.ABC): if mouse_event.left_pressed and touch_valid: if mouse_in_rect: self._handle_mouse_press(mouse_event.pos) + self.__mouse_events[mouse_event.slot] = mouse_event self.__is_pressed[mouse_event.slot] = True self.__tracking_is_pressed[mouse_event.slot] = True self._handle_mouse_event(mouse_event) # Callback such as scroll panel signifies user is scrolling elif not touch_valid: + self.__mouse_events[mouse_event.slot] = None self.__is_pressed[mouse_event.slot] = False self.__tracking_is_pressed[mouse_event.slot] = False @@ -138,17 +141,20 @@ class Widget(abc.ABC): self._handle_mouse_event(mouse_event) if self.__is_pressed[mouse_event.slot] and mouse_in_rect: self._handle_mouse_release(mouse_event.pos) + self.__mouse_events[mouse_event.slot] = None self.__is_pressed[mouse_event.slot] = False self.__tracking_is_pressed[mouse_event.slot] = False # Mouse/touch is still within our rect elif mouse_in_rect: if self.__tracking_is_pressed[mouse_event.slot]: + self.__mouse_events[mouse_event.slot] = mouse_event self.__is_pressed[mouse_event.slot] = True self._handle_mouse_event(mouse_event) # Mouse/touch left our rect but may come back into focus later elif not mouse_in_rect: + self.__mouse_events[mouse_event.slot] = None self.__is_pressed[mouse_event.slot] = False self._handle_mouse_event(mouse_event) From b3c2daf9e58c5fe174d9d402aca80215e9763806 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 17 Dec 2025 21:32:06 -0800 Subject: [PATCH 652/910] Revert "Widget: track mouse events (#36922)" https://github.com/commaai/openpilot/pull/36920#issuecomment-3668453692 This reverts commit 792a9b715c1c577f0adaf41c6fcc925884b2dc79. --- system/ui/widgets/__init__.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/system/ui/widgets/__init__.py b/system/ui/widgets/__init__.py index 376836348..8a46a3e43 100644 --- a/system/ui/widgets/__init__.py +++ b/system/ui/widgets/__init__.py @@ -26,7 +26,6 @@ class Widget(abc.ABC): self.__is_pressed = [False] * MAX_TOUCH_SLOTS # if current mouse/touch down started within the widget's rectangle self.__tracking_is_pressed = [False] * MAX_TOUCH_SLOTS - self.__mouse_events: list[MouseEvent | None] = [None] * MAX_TOUCH_SLOTS self._enabled: bool | Callable[[], bool] = True self._is_visible: bool | Callable[[], bool] = True self._touch_valid_callback: Callable[[], bool] | None = None @@ -126,14 +125,12 @@ class Widget(abc.ABC): if mouse_event.left_pressed and touch_valid: if mouse_in_rect: self._handle_mouse_press(mouse_event.pos) - self.__mouse_events[mouse_event.slot] = mouse_event self.__is_pressed[mouse_event.slot] = True self.__tracking_is_pressed[mouse_event.slot] = True self._handle_mouse_event(mouse_event) # Callback such as scroll panel signifies user is scrolling elif not touch_valid: - self.__mouse_events[mouse_event.slot] = None self.__is_pressed[mouse_event.slot] = False self.__tracking_is_pressed[mouse_event.slot] = False @@ -141,20 +138,17 @@ class Widget(abc.ABC): self._handle_mouse_event(mouse_event) if self.__is_pressed[mouse_event.slot] and mouse_in_rect: self._handle_mouse_release(mouse_event.pos) - self.__mouse_events[mouse_event.slot] = None self.__is_pressed[mouse_event.slot] = False self.__tracking_is_pressed[mouse_event.slot] = False # Mouse/touch is still within our rect elif mouse_in_rect: if self.__tracking_is_pressed[mouse_event.slot]: - self.__mouse_events[mouse_event.slot] = mouse_event self.__is_pressed[mouse_event.slot] = True self._handle_mouse_event(mouse_event) # Mouse/touch left our rect but may come back into focus later elif not mouse_in_rect: - self.__mouse_events[mouse_event.slot] = None self.__is_pressed[mouse_event.slot] = False self._handle_mouse_event(mouse_event) From a478b64ff3c0ac8356dbac31f907e6cf4090ca8e Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 17 Dec 2025 21:55:31 -0800 Subject: [PATCH 653/910] WifiUi: fix clicking (#36919) * fix * rm * rename * need this in case user swipes back to starting pos * better * better * clean up * cmt --- selfdrive/ui/mici/widgets/dialog.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index eb36494ad..34760925a 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -4,17 +4,17 @@ import pyray as rl from typing import Union from collections.abc import Callable from typing import cast -from openpilot.selfdrive.ui.mici.widgets.side_button import SideButton from openpilot.system.ui.widgets import Widget, NavWidget, DialogResult from openpilot.system.ui.widgets.label import UnifiedLabel, gui_label from openpilot.system.ui.widgets.mici_keyboard import MiciKeyboard from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.lib.wrap_text import wrap_text -from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos +from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos, MouseEvent from openpilot.system.ui.widgets.scroller import Scroller from openpilot.system.ui.widgets.slider import RedBigSlider, BigSlider from openpilot.common.filter_simple import FirstOrderFilter from openpilot.selfdrive.ui.mici.widgets.button import BigButton +from openpilot.selfdrive.ui.mici.widgets.side_button import SideButton DEBUG = False @@ -327,8 +327,10 @@ class BigMultiOptionDialog(BigDialogBase): self._selected_option: str = self._default_option or (options[0] if len(options) > 0 else "") self._last_selected_option: str = self._selected_option + # Widget doesn't differentiate between click and drag + self._can_click = True + self._scroller = Scroller([], horizontal=False, pad_start=100, pad_end=100, spacing=0, snap_items=True) - self.set_touch_valid_callback(self._scroller.scroll_panel.is_touch_valid) if self._right_btn is not None: self._scroller.set_enabled(lambda: not cast(Widget, self._right_btn).is_pressed) @@ -365,9 +367,23 @@ class BigMultiOptionDialog(BigDialogBase): def _selected_option_changed(self): pass + def _handle_mouse_press(self, mouse_pos: MousePos): + super()._handle_mouse_press(mouse_pos) + self._can_click = True + + def _handle_mouse_event(self, mouse_event: MouseEvent) -> None: + super()._handle_mouse_event(mouse_event) + + # # TODO: add generic _handle_mouse_click handler to Widget + if not self._scroller.scroll_panel.is_touch_valid(): + self._can_click = False + def _handle_mouse_release(self, mouse_pos: MousePos): super()._handle_mouse_release(mouse_pos) + if not self._can_click: + return + # select current option for btn in self._scroller._items: btn = cast(BigDialogOptionButton, btn) From b52d0df6e3bf0a155656add339c22584e9ab33a5 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 18 Dec 2025 02:08:20 -0500 Subject: [PATCH 654/910] ci: enable mici Raylib UI Preview in sunnypilot (#1588) --- .github/workflows/mici_raylib_ui_preview.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/mici_raylib_ui_preview.yaml b/.github/workflows/mici_raylib_ui_preview.yaml index 707825b1a..555258652 100644 --- a/.github/workflows/mici_raylib_ui_preview.yaml +++ b/.github/workflows/mici_raylib_ui_preview.yaml @@ -24,7 +24,7 @@ env: jobs: preview: - if: github.repository == 'commaai/openpilot' + if: github.repository == 'sunnypilot/sunnypilot' name: preview runs-on: ubuntu-latest timeout-minutes: 20 @@ -64,7 +64,7 @@ jobs: - name: Getting master ui # filename: master_ui_raylib/mici_ui_replay.mp4 uses: actions/checkout@v4 with: - repository: commaai/ci-artifacts + repository: sunnypilot/ci-artifacts ssh-key: ${{ secrets.CI_ARTIFACTS_DEPLOY_KEY }} path: ${{ github.workspace }}/master_ui_raylib ref: ${{ env.MASTER_BRANCH_NAME }} @@ -100,7 +100,7 @@ jobs: # Run report export PYTHONPATH=${{ github.workspace }} - baseurl="https://github.com/commaai/ci-artifacts/raw/refs/heads/${{ env.BRANCH_NAME }}" + baseurl="https://github.com/sunnypilot/ci-artifacts/raw/refs/heads/${{ env.BRANCH_NAME }}" diff_exit_code=0 python3 ${{ github.workspace }}/selfdrive/ui/tests/diff/diff.py "${{ github.workspace }}/pr_ui/mici_ui_replay_master.mp4" "${{ github.workspace }}/pr_ui/mici_ui_replay_proposed.mp4" "diff.html" --basedir "$baseurl" --no-open || diff_exit_code=$? @@ -108,7 +108,7 @@ jobs: cp ${{ github.workspace }}/selfdrive/ui/tests/diff/report/diff.html ${{ github.workspace }}/pr_ui/ cp ${{ github.workspace }}/selfdrive/ui/tests/diff/report/diff.mp4 ${{ github.workspace }}/pr_ui/ - REPORT_URL="https://commaai.github.io/ci-artifacts/diff_pr_${{ github.event.number }}.html" + REPORT_URL="https://sunnypilot.github.io/ci-artifacts/diff_pr_${{ github.event.number }}.html" if [ $diff_exit_code -eq 0 ]; then DIFF="✅ Videos are identical! [View Diff Report]($REPORT_URL)" else From 89d9fdca82aab17bb85ee1c63db4c06f2dd262a9 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 17 Dec 2025 23:15:22 -0800 Subject: [PATCH 655/910] WifiUi: tune wifi strengths (#36923) * tune wifi strengths * android nor ios show none or slash. it's always 1, 2, or 3 bars * match here * clean up --- .../ui/mici/layouts/settings/network/wifi_ui.py | 14 ++++---------- system/ui/lib/wifi_manager.py | 2 +- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index 06347b565..565fef5af 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -36,8 +36,6 @@ class WifiIcon(Widget): super().__init__() self.set_rect(rl.Rectangle(0, 0, 89, 64)) - self._wifi_slash_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_slash.png", 89, 64) - self._wifi_none_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_none.png", 89, 64) self._wifi_low_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_low.png", 89, 64) self._wifi_medium_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_medium.png", 89, 64) self._wifi_full_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_full.png", 89, 64) @@ -57,17 +55,13 @@ class WifiIcon(Widget): return # Determine which wifi strength icon to use - strength = round(self._network.strength / 100 * 4) - if strength == 4: + strength = round(self._network.strength / 100 * 2) + if strength == 2: strength_icon = self._wifi_full_txt - elif strength == 3: + elif strength == 1: strength_icon = self._wifi_medium_txt - elif strength == 2: - strength_icon = self._wifi_low_txt - elif self._network.strength < 0: - strength_icon = self._wifi_slash_txt else: - strength_icon = self._wifi_none_txt + strength_icon = self._wifi_low_txt icon_x = int(self._rect.x + (self._rect.width - strength_icon.width * self._scale) // 2) icon_y = int(self._rect.y + (self._rect.height - strength_icon.height * self._scale) // 2) diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index 7e5f04ef6..bd66b8e03 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -631,7 +631,7 @@ class WifiManager: known_connections = self._get_connections() networks = [Network.from_dbus(ssid, ap_list, ssid in known_connections) for ssid, ap_list in aps.items()] # sort with quantized strength to reduce jumping - networks.sort(key=lambda n: (-n.is_connected, -round(n.strength / 100 * 4), n.ssid.lower())) + networks.sort(key=lambda n: (-n.is_connected, -round(n.strength / 100 * 2), n.ssid.lower())) self._networks = networks self._update_ipv4_address() From 93f98a8a362319c9894a566cc8414f9e142bd086 Mon Sep 17 00:00:00 2001 From: Kumar <36933347+rav4kumar@users.noreply.github.com> Date: Thu, 18 Dec 2025 03:01:16 -0700 Subject: [PATCH 656/910] [TIZI/TICI] ui: Developer Metrics (#1523) * devui * clean up * clean up * optimize text measurement for better rendering performance * sp dir * decouple from stock HudRenderer * rename * fetch mode in _update_state * wrong type * start decoupling elements * decouple elements * un-ew this pls * fully decouple developer UI elements * rename * more decouple * full send * final --------- Co-authored-by: Jason Wen --- selfdrive/ui/onroad/augmented_road_view.py | 3 + selfdrive/ui/sunnypilot/onroad/__init__.py | 0 .../onroad/developer_ui/__init__.py | 164 ++++++++++ .../onroad/developer_ui/elements.py | 303 ++++++++++++++++++ .../ui/sunnypilot/onroad/hud_renderer.py | 20 ++ selfdrive/ui/sunnypilot/ui_state.py | 1 + 6 files changed, 491 insertions(+) create mode 100644 selfdrive/ui/sunnypilot/onroad/__init__.py create mode 100644 selfdrive/ui/sunnypilot/onroad/developer_ui/__init__.py create mode 100644 selfdrive/ui/sunnypilot/onroad/developer_ui/elements.py create mode 100644 selfdrive/ui/sunnypilot/onroad/hud_renderer.py diff --git a/selfdrive/ui/onroad/augmented_road_view.py b/selfdrive/ui/onroad/augmented_road_view.py index 1f202141c..a47c04053 100644 --- a/selfdrive/ui/onroad/augmented_road_view.py +++ b/selfdrive/ui/onroad/augmented_road_view.py @@ -14,6 +14,9 @@ from openpilot.system.ui.lib.application import gui_app from openpilot.common.transformations.camera import DEVICE_CAMERAS, DeviceCameraConfig, view_frame_from_device_frame from openpilot.common.transformations.orientation import rot_from_euler +if gui_app.sunnypilot_ui(): + from openpilot.selfdrive.ui.sunnypilot.onroad.hud_renderer import HudRendererSP as HudRenderer + OpState = log.SelfdriveState.OpenpilotState CALIBRATED = log.LiveCalibrationData.Status.calibrated ROAD_CAM = VisionStreamType.VISION_STREAM_ROAD diff --git a/selfdrive/ui/sunnypilot/onroad/__init__.py b/selfdrive/ui/sunnypilot/onroad/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/selfdrive/ui/sunnypilot/onroad/developer_ui/__init__.py b/selfdrive/ui/sunnypilot/onroad/developer_ui/__init__.py new file mode 100644 index 000000000..14a224ae7 --- /dev/null +++ b/selfdrive/ui/sunnypilot/onroad/developer_ui/__init__.py @@ -0,0 +1,164 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +import pyray as rl +from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.selfdrive.ui.sunnypilot.onroad.developer_ui.elements import ( + UiElement, RelDistElement, RelSpeedElement, SteeringAngleElement, + DesiredLateralAccelElement, ActualLateralAccelElement, DesiredSteeringAngleElement, + AEgoElement, LeadSpeedElement, FrictionCoefficientElement, LatAccelFactorElement, + SteeringTorqueEpsElement, BearingDegElement, AltitudeElement +) +from openpilot.system.ui.lib.application import gui_app, FontWeight +from openpilot.system.ui.lib.text_measure import measure_text_cached +from openpilot.system.ui.widgets import Widget + + +class DeveloperUiRenderer(Widget): + DEV_UI_OFF = 0 + DEV_UI_RIGHT = 1 + DEV_UI_BOTTOM = 2 + DEV_UI_BOTH = 3 + + def __init__(self): + super().__init__() + self._font_bold: rl.Font = gui_app.font(FontWeight.BOLD) + self._font_semi_bold: rl.Font = gui_app.font(FontWeight.SEMI_BOLD) + self.dev_ui_mode = self.DEV_UI_OFF + + self.rel_dist_elem = RelDistElement() + self.rel_speed_elem = RelSpeedElement() + self.steering_angle_elem = SteeringAngleElement() + self.desired_lat_accel_elem = DesiredLateralAccelElement() + self.actual_lat_accel_elem = ActualLateralAccelElement() + self.desired_steer_elem = DesiredSteeringAngleElement() + self.a_ego_elem = AEgoElement() + self.lead_speed_elem = LeadSpeedElement() + self.friction_elem = FrictionCoefficientElement() + self.lat_accel_factor_elem = LatAccelFactorElement() + self.steering_torque_elem = SteeringTorqueEpsElement() + self.bearing_elem = BearingDegElement() + self.altitude_elem = AltitudeElement() + + def _update_state(self) -> None: + self.dev_ui_mode = ui_state.developer_ui + + def _render(self, rect: rl.Rectangle) -> None: + if self.dev_ui_mode == self.DEV_UI_OFF: + return + + sm = ui_state.sm + if sm.recv_frame["carState"] < ui_state.started_frame: + return + + if self.dev_ui_mode == self.DEV_UI_RIGHT: + self._draw_right_dev_ui(rect) + elif self.dev_ui_mode == self.DEV_UI_BOTTOM: + self._draw_bottom_dev_ui(rect) + elif self.dev_ui_mode == self.DEV_UI_BOTH: + self._draw_right_dev_ui(rect) + self._draw_bottom_dev_ui(rect) + + def _draw_right_dev_ui(self, rect: rl.Rectangle) -> None: + sm = ui_state.sm + controls_state = sm['controlsState'] + + UI_BORDER_SIZE = 20 + container_width = 184 + x = int(rect.x + rect.width - container_width - UI_BORDER_SIZE * 2) + y = int(rect.y + UI_BORDER_SIZE * 1.5) + + elements = [ + self.rel_dist_elem.update(sm, ui_state.is_metric), + self.rel_speed_elem.update(sm, ui_state.is_metric), + self.steering_angle_elem.update(sm, ui_state.is_metric), + ] + if controls_state.lateralControlState.which() == 'torqueState': + elements.append(self.desired_lat_accel_elem.update(sm, ui_state.is_metric)) + elements.append(self.actual_lat_accel_elem.update(sm, ui_state.is_metric)) + else: + elements.append(self.desired_steer_elem.update(sm, ui_state.is_metric)) + + current_y = y + for element in elements: + current_y += self._draw_right_dev_ui_element(x, current_y, element) + + def _draw_right_dev_ui_element(self, x: int, y: int, element: UiElement) -> int: + x += 0 + y += 230 + container_width = 184 + label_size = 28 + value_size = 60 + unit_size = 28 + label_width = measure_text_cached(self._font_bold, element.label, label_size, 0).x + centered_label_x = x + (container_width - label_width) / 2 + rl.draw_text_ex(self._font_bold, element.label, rl.Vector2(centered_label_x, y), label_size, 0, rl.WHITE) + + y += 45 + value_width = measure_text_cached(self._font_bold, element.value, value_size, 0).x + centered_value_x = x + (container_width - value_width) / 2 + rl.draw_text_ex(self._font_bold, element.value, rl.Vector2(centered_value_x, y), value_size, 0, element.color) + + if element.unit: + units_height = measure_text_cached(self._font_bold, element.unit, unit_size, 0).x + + units_x = x + container_width - 10 + units_y = y + (value_size / 2) + (units_height / 2) + + rl.draw_text_pro(self._font_bold, element.unit, rl.Vector2(units_x, units_y), rl.Vector2(0, 0), -90.0, unit_size, 0, rl.WHITE) + + return 130 + + def _draw_bottom_dev_ui(self, rect: rl.Rectangle) -> None: + sm = ui_state.sm + bar_height = 61 + y = int(rect.y + rect.height - bar_height) + + rl.draw_rectangle(int(rect.x), y, int(rect.width), bar_height, + rl.Color(0, 0, 0, 100)) + + elements = [ + self.a_ego_elem.update(sm, ui_state.is_metric), + self.lead_speed_elem.update(sm, ui_state.is_metric), + ] + + # Add torque-specific elements if using torque control + if sm['controlsState'].lateralControlState.which() == 'torqueState': + if sm.valid['liveTorqueParameters']: + elements.extend([ + self.friction_elem.update(sm, ui_state.is_metric), + self.lat_accel_factor_elem.update(sm, ui_state.is_metric), + ]) + else: + # Non-torque: show steering torque and GPS data + elements.append(self.steering_torque_elem.update(sm, ui_state.is_metric)) + + if sm.valid['gpsLocationExternal'] or sm.valid['gpsLocation']: + elements.append(self.bearing_elem.update(sm, ui_state.is_metric)) + + # Add altitude if GPS available + if sm.valid['gpsLocationExternal'] or sm.valid['gpsLocation']: + elements.append(self.altitude_elem.update(sm, ui_state.is_metric)) + + current_x = int(rect.x + 90) + center_y = y + bar_height // 2 + for element in elements: + current_x += self._draw_bottom_dev_ui_element(current_x, center_y, element) + + def _draw_bottom_dev_ui_element(self, x: int, y: int, element: UiElement) -> int: + font_size = 38 + + label_text = f"{element.label} " + label_width = measure_text_cached(self._font_bold, label_text, font_size, 0).x + rl.draw_text_ex(self._font_bold, label_text, rl.Vector2(x, y - font_size // 2), font_size, 0, rl.WHITE) + + value_width = measure_text_cached(self._font_bold, element.value, font_size, 0).x + rl.draw_text_ex(self._font_bold, element.value, rl.Vector2(x + label_width + 10, y - font_size // 2), font_size, 0, element.color) + + if element.unit: + rl.draw_text_ex(self._font_bold, element.unit, rl.Vector2(x + label_width + value_width + 20, y - font_size // 2), font_size, 0, rl.WHITE) + + return 400 diff --git a/selfdrive/ui/sunnypilot/onroad/developer_ui/elements.py b/selfdrive/ui/sunnypilot/onroad/developer_ui/elements.py new file mode 100644 index 000000000..e8daca886 --- /dev/null +++ b/selfdrive/ui/sunnypilot/onroad/developer_ui/elements.py @@ -0,0 +1,303 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +import pyray as rl +from dataclasses import dataclass + +from openpilot.common.constants import CV + + +@dataclass +class UiElement: + value: str + label: str + unit: str + color: rl.Color + + +class LeadInfoElement: + @staticmethod + def get_lead_status(sm): + lead_one = sm['radarState'].leadOne + return lead_one.status, lead_one.dRel, lead_one.vRel + + @staticmethod + def get_lead_color(lead_d_rel: float, lead_v_rel: float = 0.0, use_v_rel: bool = False) -> rl.Color: + if use_v_rel: + if lead_v_rel < -4.4704: + return rl.RED + elif lead_v_rel < 0: + return rl.Color(255, 188, 0, 255) # Orange + else: + if lead_d_rel < 5: + return rl.RED + elif lead_d_rel < 15: + return rl.Color(255, 188, 0, 255) # Orange + return rl.WHITE + + +class LateralControlElement: + @staticmethod + def get_lat_color(lat_active: bool, steer_override: bool, angle_steers: float = 0.0, + check_angle: bool = False) -> rl.Color: + color = rl.WHITE + if lat_active: + color = rl.Color(145, 155, 149, 255) if steer_override else rl.Color(0, 255, 0, 255) + + if check_angle and lat_active: + if abs(angle_steers) > 180: + color = rl.RED + elif abs(angle_steers) > 90: + color = rl.Color(255, 188, 0, 255) + else: + # Keep green/grey from above + pass + elif check_angle and not lat_active: + if abs(angle_steers) > 180: + color = rl.RED + elif abs(angle_steers) > 90: + color = rl.Color(255, 188, 0, 255) + + return color + + +class RelDistElement(LeadInfoElement): + def __init__(self): + self.unit = "m" + + def update(self, sm, is_metric: bool) -> UiElement: + lead_status, lead_d_rel, _ = self.get_lead_status(sm) + value = f"{lead_d_rel:.0f}" if lead_status else "-" + color = self.get_lead_color(lead_d_rel) if lead_status else rl.WHITE + return UiElement(value, "REL DIST", self.unit, color) + + +class RelSpeedElement(LeadInfoElement): + def __init__(self): + self.unit = "km/h" + + def update(self, sm, is_metric: bool) -> UiElement: + lead_status, _, lead_v_rel = self.get_lead_status(sm) + + self.unit = "km/h" if is_metric else "mph" + + conversion = CV.MS_TO_KPH if is_metric else CV.MS_TO_MPH + value = f"{lead_v_rel * conversion:.0f}" if lead_status else "-" + color = self.get_lead_color(0, lead_v_rel, use_v_rel=True) if lead_status else rl.WHITE + + return UiElement(value, "REL SPEED", self.unit, color) + + +class SteeringAngleElement(LateralControlElement): + def __init__(self): + self.unit = "" + + def update(self, sm, is_metric: bool) -> UiElement: + car_state = sm['carState'] + angle_steers = car_state.steeringAngleDeg + lat_active = sm['carControl'].latActive + steer_override = car_state.steeringPressed + + value = f"{angle_steers:.1f}°" + color = self.get_lat_color(lat_active, steer_override, angle_steers, check_angle=True) + + return UiElement(value, "REAL STEER", self.unit, color) + + +class DesiredSteeringAngleElement(LateralControlElement): + def __init__(self): + self.unit = "" + + def update(self, sm, is_metric: bool) -> UiElement: + car_state = sm['carState'] + controls_state = sm['controlsState'] + lat_active = sm['carControl'].latActive + angle_steers = car_state.steeringAngleDeg + steer_angle_desired = controls_state.lateralControlState.angleState.steeringAngleDeg + + value = f"{steer_angle_desired:.1f}°" if lat_active else "-" + + color = rl.WHITE + if lat_active: + if abs(angle_steers) > 180: + color = rl.RED + elif abs(angle_steers) > 90: + color = rl.Color(255, 188, 0, 255) + else: + color = rl.Color(0, 255, 0, 255) + + return UiElement(value, "DESIRED STEER", self.unit, color) + + +class ActualLateralAccelElement(LateralControlElement): + def __init__(self): + self.unit = "m/s^2" + + def update(self, sm, is_metric: bool) -> UiElement: + controls_state = sm['controlsState'] + curvature = controls_state.curvature + v_ego = sm['carState'].vEgo + roll = sm['liveParameters'].roll if sm.valid['liveParameters'] else 0.0 + lat_active = sm['carControl'].latActive + steer_override = sm['carState'].steeringPressed + + actual_lat_accel = (curvature * v_ego ** 2) - (roll * 9.81) + value = f"{actual_lat_accel:.2f}" + color = self.get_lat_color(lat_active, steer_override) + + return UiElement(value, "ACTUAL L.A.", self.unit, color) + + +class DesiredLateralAccelElement(LateralControlElement): + def __init__(self): + self.unit = "m/s^2" + + def update(self, sm, is_metric: bool) -> UiElement: + controls_state = sm['controlsState'] + desired_curvature = controls_state.desiredCurvature + v_ego = sm['carState'].vEgo + roll = sm['liveParameters'].roll if sm.valid['liveParameters'] else 0.0 + lat_active = sm['carControl'].latActive + steer_override = sm['carState'].steeringPressed + + desired_lat_accel = (desired_curvature * v_ego ** 2) - (roll * 9.81) + value = f"{desired_lat_accel:.2f}" if lat_active else "-" + color = self.get_lat_color(lat_active, steer_override) + + return UiElement(value, "DESIRED L.A.", self.unit, color) + + +class AEgoElement: + def __init__(self): + self.unit = "m/s^2" + + def update(self, sm, is_metric: bool) -> UiElement: + a_ego = sm['carState'].aEgo + value = f"{a_ego:.1f}" + return UiElement(value, "ACC.", self.unit, rl.WHITE) + + +class LeadSpeedElement(LeadInfoElement): + def __init__(self): + self.unit = "km/h" + + def update(self, sm, is_metric: bool) -> UiElement: + lead_status, _, lead_v_rel = self.get_lead_status(sm) + v_ego = sm['carState'].vEgo + + self.unit = "km/h" if is_metric else "mph" + + conversion = CV.MS_TO_KPH if is_metric else CV.MS_TO_MPH + value = f"{(lead_v_rel + v_ego) * conversion:.0f}" if lead_status else "-" + color = self.get_lead_color(0, lead_v_rel, use_v_rel=True) if lead_status else rl.WHITE + + return UiElement(value, "L.S.", self.unit, color) + + +class FrictionCoefficientElement: + def __init__(self): + self.unit = "" + + def update(self, sm, is_metric: bool) -> UiElement: + ltp = sm['liveTorqueParameters'] + friction_coef = ltp.frictionCoefficientFiltered + live_valid = ltp.liveValid + + value = f"{friction_coef:.3f}" + color = rl.Color(0, 255, 0, 255) if live_valid else rl.WHITE + return UiElement(value, "FRIC.", self.unit, color) + + +class LatAccelFactorElement: + def __init__(self): + self.unit = "" + + def update(self, sm, is_metric: bool) -> UiElement: + ltp = sm['liveTorqueParameters'] + lat_accel_factor = ltp.latAccelFactorFiltered + live_valid = ltp.liveValid + + value = f"{lat_accel_factor:.3f}" + color = rl.Color(0, 255, 0, 255) if live_valid else rl.WHITE + return UiElement(value, "L.A.F.", self.unit, color) + + +class SteeringTorqueEpsElement: + def __init__(self): + self.unit = "N·dm" + + def update(self, sm, is_metric: bool) -> UiElement: + steering_torque_eps = sm['carState'].steeringTorqueEps + value = f"{abs(steering_torque_eps):.1f}" + return UiElement(value, "E.T.", self.unit, rl.WHITE) + + +class GpsInfoElement: + @staticmethod + def get_gps_data(sm): + if sm.valid['gpsLocationExternal']: + return sm['gpsLocationExternal'], True + elif sm.valid['gpsLocation']: + return sm['gpsLocation'], True + return None, False + + +class BearingDegElement(GpsInfoElement): + def __init__(self): + self.unit = "" + + def update(self, sm, is_metric: bool) -> UiElement: + gps_data, valid = self.get_gps_data(sm) + if not valid: + return UiElement("OFF | -", "B.D.", self.unit, rl.WHITE) + + bearing_accuracy_deg = gps_data.bearingAccuracyDeg + bearing_deg = gps_data.bearingDeg + + if bearing_accuracy_deg != 180.0: + value = f"{bearing_deg:.0f}°" + if (337.5 <= bearing_deg <= 360) or (0 <= bearing_deg <= 22.5): + dir_value = "N" + elif 22.5 < bearing_deg < 67.5: + dir_value = "NE" + elif 67.5 <= bearing_deg <= 112.5: + dir_value = "E" + elif 112.5 < bearing_deg < 157.5: + dir_value = "SE" + elif 157.5 <= bearing_deg <= 202.5: + dir_value = "S" + elif 202.5 < bearing_deg < 247.5: + dir_value = "SW" + elif 247.5 <= bearing_deg <= 292.5: + dir_value = "W" + else: # 292.5 < bearing_deg < 337.5 + dir_value = "NW" + else: + value = "-" + dir_value = "OFF" + + return UiElement(f"{dir_value} | {value}", "B.D.", self.unit, rl.WHITE) + + +class AltitudeElement(GpsInfoElement): + def __init__(self): + self.unit = "m" + + def update(self, sm, is_metric: bool) -> UiElement: + gps_data, valid = self.get_gps_data(sm) + + gps_accuracy = 0.0 + altitude = 0.0 + + if valid: + altitude = gps_data.altitude + if sm.valid['gpsLocationExternal']: + gps_accuracy = gps_data.horizontalAccuracy + else: + gps_accuracy = 1.0 # Simulate valid for legacy check + + value = f"{altitude:.1f}" if gps_accuracy != 0.0 else "-" + return UiElement(value, "ALT.", self.unit, rl.WHITE) diff --git a/selfdrive/ui/sunnypilot/onroad/hud_renderer.py b/selfdrive/ui/sunnypilot/onroad/hud_renderer.py new file mode 100644 index 000000000..33582df19 --- /dev/null +++ b/selfdrive/ui/sunnypilot/onroad/hud_renderer.py @@ -0,0 +1,20 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +import pyray as rl + +from openpilot.selfdrive.ui.onroad.hud_renderer import HudRenderer +from openpilot.selfdrive.ui.sunnypilot.onroad.developer_ui import DeveloperUiRenderer + + +class HudRendererSP(HudRenderer): + def __init__(self): + super().__init__() + self.developer_ui = DeveloperUiRenderer() + + def _render(self, rect: rl.Rectangle) -> None: + super()._render(rect) + self.developer_ui.render(rect) diff --git a/selfdrive/ui/sunnypilot/ui_state.py b/selfdrive/ui/sunnypilot/ui_state.py index af625ee61..8a0bc24ad 100644 --- a/selfdrive/ui/sunnypilot/ui_state.py +++ b/selfdrive/ui/sunnypilot/ui_state.py @@ -27,3 +27,4 @@ class UIStateSP: if CP_SP_bytes is not None: self.CP_SP = messaging.log_from_bytes(CP_SP_bytes, custom.CarParamsSP) self.sunnylink_enabled = self.params.get_bool("SunnylinkEnabled") + self.developer_ui = self.params.get("DevUIInfo") From 13b8a67ae27210f0a8b7544afca219ea1c0a641c Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Thu, 18 Dec 2025 16:03:45 -0800 Subject: [PATCH 657/910] reset: fix button overlap (#36929) fix --- system/ui/mici_reset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/ui/mici_reset.py b/system/ui/mici_reset.py index d9bb45d99..925afd7d1 100755 --- a/system/ui/mici_reset.py +++ b/system/ui/mici_reset.py @@ -132,7 +132,7 @@ class Reset(Widget): if self._reset_state == ResetState.FAILED: return "Reset failed. Reboot to try again." if self._mode == ResetMode.RECOVER: - return "Unable to mount data partition. Partition may be corrupted." + return "Unable to mount data partition. It may be corrupted." return "All content and settings will be erased." From e4359e9acb76586e99434592cdf3ee3ccf9aaecd Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 18 Dec 2025 17:15:47 -0800 Subject: [PATCH 658/910] api_get: use session (#36930) * use session * prob better for now * todo * same for firehose * no more stutters! * cmt --- common/api.py | 6 ++++-- selfdrive/ui/lib/prime_state.py | 4 +++- selfdrive/ui/mici/layouts/settings/firehose.py | 4 +++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/common/api.py b/common/api.py index 7ea278038..ebf0290d1 100644 --- a/common/api.py +++ b/common/api.py @@ -42,14 +42,16 @@ class Api: return token -def api_get(endpoint, method='GET', timeout=None, access_token=None, **params): +def api_get(endpoint, method='GET', timeout=None, access_token=None, session=None, **params): headers = {} if access_token is not None: headers['Authorization'] = "JWT " + access_token headers['User-Agent'] = "openpilot-" + get_version() - return requests.request(method, API_HOST + "/" + endpoint, timeout=timeout, headers=headers, params=params) + # TODO: add session to Api + req = requests if session is None else session + return req.request(method, API_HOST + "/" + endpoint, timeout=timeout, headers=headers, params=params) def get_key_pair() -> tuple[str, str, str] | tuple[None, None, None]: diff --git a/selfdrive/ui/lib/prime_state.py b/selfdrive/ui/lib/prime_state.py index e1ef387bf..1aed949be 100644 --- a/selfdrive/ui/lib/prime_state.py +++ b/selfdrive/ui/lib/prime_state.py @@ -1,5 +1,6 @@ from enum import IntEnum import os +import requests import threading import time @@ -29,6 +30,7 @@ class PrimeState: def __init__(self): self._params = Params() self._lock = threading.Lock() + self._session = requests.Session() # reuse session to reduce SSL handshake overhead self.prime_type: PrimeType = self._load_initial_state() self._running = False @@ -50,7 +52,7 @@ class PrimeState: try: identity_token = get_token(dongle_id) - response = api_get(f"v1.1/devices/{dongle_id}", timeout=self.API_TIMEOUT, access_token=identity_token) + response = api_get(f"v1.1/devices/{dongle_id}", timeout=self.API_TIMEOUT, access_token=identity_token, session=self._session) if response.status_code == 200: data = response.json() is_paired = data.get("is_paired", False) diff --git a/selfdrive/ui/mici/layouts/settings/firehose.py b/selfdrive/ui/mici/layouts/settings/firehose.py index 10e52bb3b..d305906e1 100644 --- a/selfdrive/ui/mici/layouts/settings/firehose.py +++ b/selfdrive/ui/mici/layouts/settings/firehose.py @@ -1,3 +1,4 @@ +import requests import threading import time import pyray as rl @@ -44,6 +45,7 @@ class FirehoseLayoutBase(Widget): def __init__(self): super().__init__() self._params = Params() + self._session = requests.Session() # reuse session to reduce SSL handshake overhead self._segment_count = self._get_segment_count() self._scroll_panel = GuiScrollPanel2(horizontal=False) @@ -203,7 +205,7 @@ class FirehoseLayoutBase(Widget): if not dongle_id or dongle_id == UNREGISTERED_DONGLE_ID: return identity_token = get_token(dongle_id) - response = api_get(f"v1/devices/{dongle_id}/firehose_stats", access_token=identity_token) + response = api_get(f"v1/devices/{dongle_id}/firehose_stats", access_token=identity_token, session=self._session) if response.status_code == 200: data = response.json() self._segment_count = data.get("firehose", 0) From 57e7c0b2c1cee42b7e56ee0420714d048fc8c6df Mon Sep 17 00:00:00 2001 From: Nayan Date: Thu, 18 Dec 2025 23:15:52 -0500 Subject: [PATCH 659/910] [comma 4] ui: sunnylink panel (#1544) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * param to control stock vs sp ui * init styles * SP Toggles * Lint * optimizations * sp raylib preview * fix callback * fix ui preview * sunnylink state * introducing ui_state_sp for py * poll from ui_state_sp * cloudlog & ruff * param to control stock vs sp ui * better * better padding * this * listitem -> listitemsp * add show_description method * remove padding from line separator. like, WHY? 😩😩 * ui: `GuiApplicationExt` * add to readme * use gui_app.sunnypilot_ui() * use gui_app.sunnypilot_ui() * fetch only when connected to network * init sunnylink panels * cleanup * lint * flippity floppity * fix backup/restore status * show contributor tier * sunnylink-mici * icons * fix * add uploader * final --------- Co-authored-by: Jason Wen --- selfdrive/ui/mici/layouts/main.py | 3 + .../ui/sunnypilot/mici/layouts/__init__.py | 0 .../ui/sunnypilot/mici/layouts/settings.py | 39 ++++ .../ui/sunnypilot/mici/layouts/sunnylink.py | 192 ++++++++++++++++++ .../ui/sunnypilot/mici/widgets/__init__.py | 0 .../mici/widgets/sunnylink_pairing_dialog.py | 57 ++++++ 6 files changed, 291 insertions(+) create mode 100644 selfdrive/ui/sunnypilot/mici/layouts/__init__.py create mode 100644 selfdrive/ui/sunnypilot/mici/layouts/settings.py create mode 100644 selfdrive/ui/sunnypilot/mici/layouts/sunnylink.py create mode 100644 selfdrive/ui/sunnypilot/mici/widgets/__init__.py create mode 100644 selfdrive/ui/sunnypilot/mici/widgets/sunnylink_pairing_dialog.py diff --git a/selfdrive/ui/mici/layouts/main.py b/selfdrive/ui/mici/layouts/main.py index b52f9ed39..a83ebd196 100644 --- a/selfdrive/ui/mici/layouts/main.py +++ b/selfdrive/ui/mici/layouts/main.py @@ -11,6 +11,9 @@ from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.scroller import Scroller from openpilot.system.ui.lib.application import gui_app +if gui_app.sunnypilot_ui(): + from openpilot.selfdrive.ui.sunnypilot.mici.layouts.settings import SettingsLayoutSP as SettingsLayout + ONROAD_DELAY = 2.5 # seconds diff --git a/selfdrive/ui/sunnypilot/mici/layouts/__init__.py b/selfdrive/ui/sunnypilot/mici/layouts/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/selfdrive/ui/sunnypilot/mici/layouts/settings.py b/selfdrive/ui/sunnypilot/mici/layouts/settings.py new file mode 100644 index 000000000..c6a2d5825 --- /dev/null +++ b/selfdrive/ui/sunnypilot/mici/layouts/settings.py @@ -0,0 +1,39 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +from enum import IntEnum + +from openpilot.selfdrive.ui.mici.layouts.settings import settings as OP +from openpilot.selfdrive.ui.mici.widgets.button import BigButton +from openpilot.selfdrive.ui.sunnypilot.mici.layouts.sunnylink import SunnylinkLayoutMici + +ICON_SIZE = 70 + +OP.PanelType = IntEnum( # type: ignore + "PanelType", + [es.name for es in OP.PanelType] + [ + "SUNNYLINK", + ], + start=0, +) + + +class SettingsLayoutSP(OP.SettingsLayout): + def __init__(self): + OP.SettingsLayout.__init__(self) + + sunnylink_btn = BigButton("sunnylink", "", "icons_mici/settings/developer/ssh.png") + sunnylink_btn.set_click_callback(lambda: self._set_current_panel(OP.PanelType.SUNNYLINK)) + self._panels.update({ + OP.PanelType.SUNNYLINK: OP.PanelInfo("sunnylink", SunnylinkLayoutMici(back_callback=lambda: self._set_current_panel(None))), + }) + + items = self._scroller._items.copy() + + items.insert(1, sunnylink_btn) + self._scroller._items.clear() + for item in items: + self._scroller.add_widget(item) diff --git a/selfdrive/ui/sunnypilot/mici/layouts/sunnylink.py b/selfdrive/ui/sunnypilot/mici/layouts/sunnylink.py new file mode 100644 index 000000000..2ab035c1c --- /dev/null +++ b/selfdrive/ui/sunnypilot/mici/layouts/sunnylink.py @@ -0,0 +1,192 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +from collections.abc import Callable + +import pyray as rl +from cereal import custom +from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigConfirmationDialogV2 +from openpilot.selfdrive.ui.sunnypilot.mici.widgets.sunnylink_pairing_dialog import SunnylinkPairingDialog +from openpilot.sunnypilot.sunnylink.api import UNREGISTERED_SUNNYLINK_DONGLE_ID +from openpilot.system.ui.lib.multilang import tr + +from openpilot.system.ui.widgets.scroller import Scroller +from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigToggle +from openpilot.system.ui.lib.application import gui_app, MousePos +from openpilot.system.ui.widgets import NavWidget +from openpilot.selfdrive.ui.ui_state import ui_state + + +class SunnylinkLayoutMici(NavWidget): + def __init__(self, back_callback: Callable): + super().__init__() + self.set_back_callback(back_callback) + self._restore_in_progress = False + self._backup_in_progress = False + self._sunnylink_enabled = ui_state.params.get("SunnylinkEnabled") + + self._sunnylink_toggle = BigToggle(text="", + initial_state=self._sunnylink_enabled, + toggle_callback=SunnylinkLayoutMici._sunnylink_toggle_callback) + self._sunnylink_sponsor_button = SunnylinkPairBigButton(sponsor_pairing=False) + self._sunnylink_pair_button = SunnylinkPairBigButton(sponsor_pairing=True) + self._backup_btn = BigButton(tr("backup settings"), "", "") + self._backup_btn.set_click_callback(lambda: self._handle_backup_restore_btn(restore=False)) + self._restore_btn = BigButton(tr("restore settings"), "", "") + self._restore_btn.set_click_callback(lambda: self._handle_backup_restore_btn(restore=True)) + self._sunnylink_uploader_toggle = BigToggle(text=tr("sunnylink uploader"), initial_state=False, + toggle_callback=SunnylinkLayoutMici._sunnylink_uploader_callback) + + self._scroller = Scroller([ + self._sunnylink_toggle, + self._sunnylink_sponsor_button, + self._sunnylink_pair_button, + self._backup_btn, + self._restore_btn, + self._sunnylink_uploader_toggle + ], snap_items=False) + + def _update_state(self): + super()._update_state() + self._sunnylink_enabled = ui_state.sunnylink_enabled + self._sunnylink_toggle.set_text(tr("enable sunnylink")) + self._sunnylink_pair_button.set_visible(self._sunnylink_enabled) + self._sunnylink_sponsor_button.set_visible(self._sunnylink_enabled) + self._backup_btn.set_visible(self._sunnylink_enabled) + self._restore_btn.set_visible(self._sunnylink_enabled) + self._sunnylink_uploader_toggle.set_visible(self._sunnylink_enabled) + self.handle_backup_restore_progress() + + if ui_state.sunnylink_state.is_sponsor(): + self._sunnylink_sponsor_button.set_text(tr("thanks")) + self._sunnylink_sponsor_button.set_value(ui_state.sunnylink_state.get_sponsor_tier().name.lower()) + self._sunnylink_sponsor_button.set_enabled(False) + else: + self._sunnylink_sponsor_button.set_text(tr("sponsor")) + self._sunnylink_sponsor_button.set_value("") + + if ui_state.sunnylink_state.is_paired(): + self._sunnylink_pair_button.set_text(tr("paired")) + else: + self._sunnylink_pair_button.set_text(tr("pair")) + + def show_event(self): + super().show_event() + self._scroller.show_event() + ui_state.update_params() + + def _render(self, rect: rl.Rectangle): + self._scroller.render(rect) + + @staticmethod + def _sunnylink_toggle_callback(state: bool): + ui_state.params.put_bool("SunnylinkEnabled", state) + ui_state.update_params() + + @staticmethod + def _sunnylink_uploader_callback(state: bool): + ui_state.params.put_bool("EnableSunnylinkUploader", state) + + def _handle_backup_restore_btn(self, restore: bool = False): + lbl = tr("slide to restore") if restore else tr("slide to backup") + icon = "icons_mici/settings/device/update.png" + dlg = BigConfirmationDialogV2(lbl, icon, confirm_callback=self._restore_handler if restore else self._backup_handler) + gui_app.set_modal_overlay(dlg) + + def _backup_handler(self): + self._backup_in_progress = True + self._backup_btn.set_enabled(False) + ui_state.params.put_bool("BackupManager_CreateBackup", True) + + def _restore_handler(self): + self._restore_in_progress = True + self._restore_btn.set_enabled(False) + ui_state.params.put("BackupManager_RestoreVersion", "latest") + + def handle_backup_restore_progress(self): + sunnylink_backup_manager = ui_state.sm["backupManagerSP"] + + backup_status = sunnylink_backup_manager.backupStatus + restore_status = sunnylink_backup_manager.restoreStatus + backup_progress = sunnylink_backup_manager.backupProgress + restore_progress = sunnylink_backup_manager.restoreProgress + + if self._backup_in_progress: + self._restore_btn.set_enabled(False) + self._backup_btn.set_enabled(False) + + if backup_status == custom.BackupManagerSP.Status.inProgress: + self._backup_in_progress = True + self._backup_btn.set_text(tr("backing up")) + text = tr(f"{backup_progress}%") + self._backup_btn.set_value(text) + + elif backup_status == custom.BackupManagerSP.Status.failed: + self._backup_in_progress = False + self._backup_btn.set_enabled(not ui_state.is_onroad()) + self._backup_btn.set_text(tr("backup")) + self._backup_btn.set_value(tr("failed")) + + elif (backup_status == custom.BackupManagerSP.Status.completed or + (backup_status == custom.BackupManagerSP.Status.idle and backup_progress == 100.0)): + self._backup_in_progress = False + gui_app.set_modal_overlay(BigDialog(title=tr("settings backed up"), description="")) + self._backup_btn.set_enabled(not ui_state.is_onroad()) + + elif self._restore_in_progress: + self._restore_btn.set_enabled(False) + self._backup_btn.set_enabled(False) + + if restore_status == custom.BackupManagerSP.Status.inProgress: + self._restore_in_progress = True + self._restore_btn.set_text(tr("restoring")) + text = tr(f"{restore_progress}%") + self._restore_btn.set_value(text) + + elif restore_status == custom.BackupManagerSP.Status.failed: + self._restore_in_progress = False + self._restore_btn.set_enabled(not ui_state.is_onroad()) + self._restore_btn.set_text(tr("restore")) + self._restore_btn.set_value(tr("failed")) + gui_app.set_modal_overlay(BigDialog(title=tr("unable to restore"), description="try again later.")) + + elif (restore_status == custom.BackupManagerSP.Status.completed or + (restore_status == custom.BackupManagerSP.Status.idle and restore_progress == 100.0)): + self._restore_in_progress = False + gui_app.set_modal_overlay(BigConfirmationDialogV2( + title="slide to restart", icon="icons_mici/settings/device/reboot.png", + confirm_callback=lambda: gui_app.request_close())) + + else: + can_enable = self._sunnylink_enabled and not ui_state.is_onroad() + self._backup_btn.set_enabled(can_enable) + self._backup_btn.set_text(tr("backup settings")) + self._backup_btn.set_value("") + self._restore_btn.set_enabled(can_enable) + self._restore_btn.set_text(tr("restore settings")) + self._restore_btn.set_value("") + + +class SunnylinkPairBigButton(BigButton): + def __init__(self, sponsor_pairing: bool = False): + self.sponsor_pairing = sponsor_pairing + super().__init__("", "", "") + + def _update_state(self): + super()._update_state() + + def _handle_mouse_release(self, mouse_pos: MousePos): + super()._handle_mouse_release(mouse_pos) + + dlg: BigDialog | SunnylinkPairingDialog | None = None + if UNREGISTERED_SUNNYLINK_DONGLE_ID == (ui_state.params.get("SunnylinkDongleId") or UNREGISTERED_SUNNYLINK_DONGLE_ID): + dlg = BigDialog(tr("sunnylink Dongle ID not found. Please reboot & try again."), "") + elif self.sponsor_pairing: + dlg = SunnylinkPairingDialog(sponsor_pairing=True) + elif not self.sponsor_pairing: + dlg = SunnylinkPairingDialog(sponsor_pairing=False) + if dlg: + gui_app.set_modal_overlay(dlg) diff --git a/selfdrive/ui/sunnypilot/mici/widgets/__init__.py b/selfdrive/ui/sunnypilot/mici/widgets/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/selfdrive/ui/sunnypilot/mici/widgets/sunnylink_pairing_dialog.py b/selfdrive/ui/sunnypilot/mici/widgets/sunnylink_pairing_dialog.py new file mode 100644 index 000000000..e2cef2fa0 --- /dev/null +++ b/selfdrive/ui/sunnypilot/mici/widgets/sunnylink_pairing_dialog.py @@ -0,0 +1,57 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +import base64 + +import pyray as rl +from openpilot.common.swaglog import cloudlog +from openpilot.selfdrive.ui.mici.widgets.pairing_dialog import PairingDialog +from openpilot.sunnypilot.sunnylink.api import SunnylinkApi, UNREGISTERED_SUNNYLINK_DONGLE_ID, API_HOST +from openpilot.system.ui.lib.application import FontWeight, gui_app +from openpilot.system.ui.lib.multilang import tr +from openpilot.system.ui.widgets import NavWidget +from openpilot.system.ui.widgets.label import MiciLabel + + +class SunnylinkPairingDialog(PairingDialog): + """Dialog for device pairing with QR code.""" + + def __init__(self, sponsor_pairing: bool = False): + PairingDialog.__init__(self) + self._sponsor_pairing = sponsor_pairing + label_text = tr("pair with sunnylink") if sponsor_pairing else tr("become a sunnypilot sponsor") + self._pair_label = MiciLabel(label_text, 48, font_weight=FontWeight.BOLD, + color=rl.Color(255, 255, 255, int(255 * 0.9)), line_height=40, wrap_text=True) + + def _get_pairing_url(self) -> str: + qr_string = "https://github.com/sponsors/sunnyhaibin" + + if self._sponsor_pairing: + try: + sl_dongle_id = self._params.get("SunnylinkDongleId") or UNREGISTERED_SUNNYLINK_DONGLE_ID + token = SunnylinkApi(sl_dongle_id).get_token() + inner_string = f"1|{sl_dongle_id}|{token}" + payload_bytes = base64.b64encode(inner_string.encode('utf-8')).decode('utf-8') + qr_string = f"{API_HOST}/sso?state={payload_bytes}" + except Exception: + cloudlog.exception("Failed to get pairing token") + + return qr_string + + def _update_state(self): + NavWidget._update_state(self) + + +if __name__ == "__main__": + gui_app.init_window("pairing device") + pairing = SunnylinkPairingDialog(sponsor_pairing=True) + try: + for _ in gui_app.render(): + result = pairing.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) + if result != -1: + break + finally: + del pairing From bb8a5bd476a98d61788c78959903b89d3e81440a Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 18 Dec 2025 20:50:34 -0800 Subject: [PATCH 660/910] Fix slow DM onboarding (#36932) * slow * interesting * check * clean up --- selfdrive/ui/mici/layouts/onboarding.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/mici/layouts/onboarding.py b/selfdrive/ui/mici/layouts/onboarding.py index 5d4412221..16e96d6f7 100644 --- a/selfdrive/ui/mici/layouts/onboarding.py +++ b/selfdrive/ui/mici/layouts/onboarding.py @@ -166,8 +166,8 @@ class TrainingGuideDMTutorial(Widget): def _update_state(self): super()._update_state() - if device.awake: - ui_state.params.put_bool("IsDriverViewEnabled", True) + if device.awake and not ui_state.params.get_bool("IsDriverViewEnabled"): + ui_state.params.put_bool_nonblocking("IsDriverViewEnabled", True) sm = ui_state.sm if sm.recv_frame.get("driverMonitoringState", 0) == 0: From dfd7a8c8d74b822a15738a2afedc8159990cd042 Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Thu, 18 Dec 2025 22:02:46 -0800 Subject: [PATCH 661/910] AGNOS 16 (#36915) * stage * prod * bump reset --- launch_env.sh | 2 +- system/hardware/tici/agnos.json | 20 +++++------ system/hardware/tici/all-partitions.json | 44 ++++++++++++------------ 3 files changed, 33 insertions(+), 33 deletions(-) diff --git a/launch_env.sh b/launch_env.sh index fcbee2ff8..314366f42 100755 --- a/launch_env.sh +++ b/launch_env.sh @@ -16,7 +16,7 @@ export VECLIB_MAXIMUM_THREADS=1 export QCOM_PRIORITY=12 if [ -z "$AGNOS_VERSION" ]; then - export AGNOS_VERSION="15.1" + export AGNOS_VERSION="16" fi export STAGING_ROOT="/data/safe_staging" diff --git a/system/hardware/tici/agnos.json b/system/hardware/tici/agnos.json index 5a2a092aa..e33a26bb2 100644 --- a/system/hardware/tici/agnos.json +++ b/system/hardware/tici/agnos.json @@ -56,28 +56,28 @@ }, { "name": "boot", - "url": "https://commadist.azureedge.net/agnosupdate/boot-90bd687e9e407834d4ee1b07f3d05527dfae0ff09c0cacd64cfd6097f6b10e2c.img.xz", - "hash": "90bd687e9e407834d4ee1b07f3d05527dfae0ff09c0cacd64cfd6097f6b10e2c", - "hash_raw": "90bd687e9e407834d4ee1b07f3d05527dfae0ff09c0cacd64cfd6097f6b10e2c", + "url": "https://commadist.azureedge.net/agnosupdate/boot-a0185fa5ffc860de2179e4d0fec703fef6d560eacd730f79f60891ca79c72756.img.xz", + "hash": "a0185fa5ffc860de2179e4d0fec703fef6d560eacd730f79f60891ca79c72756", + "hash_raw": "a0185fa5ffc860de2179e4d0fec703fef6d560eacd730f79f60891ca79c72756", "size": 17496064, "sparse": false, "full_check": true, "has_ab": true, - "ondevice_hash": "35014c39b55010ac955c10f808b088e74259147c7a8cbf989b3dff7d95a1e8ae" + "ondevice_hash": "0ee1ab104bb46d0f72e7d0b7d3e94629a7644a368896c6d4c558554fb955a08a" }, { "name": "system", - "url": "https://commadist.azureedge.net/agnosupdate/system-d9d476b466186014e7ae4b8232bc6fc5e79b122421bdc12ff4eb02d1c3f37818.img.xz", - "hash": "a068d4d692ec770884f0a15e1a6d7aba52385ecae138f6d43fb0a9b1643ed5cd", - "hash_raw": "d9d476b466186014e7ae4b8232bc6fc5e79b122421bdc12ff4eb02d1c3f37818", + "url": "https://commadist.azureedge.net/agnosupdate/system-0cf8cb01e40d05d6d325afe68b934a6c0dda3a56703b2ef3e3de637d754ae5dd.img.xz", + "hash": "7c58308be461126677ba02e9c9739556520ee02958934733867d86ecfe2e58e9", + "hash_raw": "0cf8cb01e40d05d6d325afe68b934a6c0dda3a56703b2ef3e3de637d754ae5dd", "size": 4718592000, "sparse": true, "full_check": false, "has_ab": true, - "ondevice_hash": "6ffa02f7113badc122742f33efebc5d17f1cd61dd6358f3e130c162707dbfaf4", + "ondevice_hash": "826790516410c325aa30265846946d06a556f0a7b23c957f65fd11c055a663da", "alt": { - "hash": "d9d476b466186014e7ae4b8232bc6fc5e79b122421bdc12ff4eb02d1c3f37818", - "url": "https://commadist.azureedge.net/agnosupdate/system-d9d476b466186014e7ae4b8232bc6fc5e79b122421bdc12ff4eb02d1c3f37818.img", + "hash": "0cf8cb01e40d05d6d325afe68b934a6c0dda3a56703b2ef3e3de637d754ae5dd", + "url": "https://commadist.azureedge.net/agnosupdate/system-0cf8cb01e40d05d6d325afe68b934a6c0dda3a56703b2ef3e3de637d754ae5dd.img", "size": 4718592000 } } diff --git a/system/hardware/tici/all-partitions.json b/system/hardware/tici/all-partitions.json index 3abf66cdd..b6718fe97 100644 --- a/system/hardware/tici/all-partitions.json +++ b/system/hardware/tici/all-partitions.json @@ -339,62 +339,62 @@ }, { "name": "boot", - "url": "https://commadist.azureedge.net/agnosupdate/boot-90bd687e9e407834d4ee1b07f3d05527dfae0ff09c0cacd64cfd6097f6b10e2c.img.xz", - "hash": "90bd687e9e407834d4ee1b07f3d05527dfae0ff09c0cacd64cfd6097f6b10e2c", - "hash_raw": "90bd687e9e407834d4ee1b07f3d05527dfae0ff09c0cacd64cfd6097f6b10e2c", + "url": "https://commadist.azureedge.net/agnosupdate/boot-a0185fa5ffc860de2179e4d0fec703fef6d560eacd730f79f60891ca79c72756.img.xz", + "hash": "a0185fa5ffc860de2179e4d0fec703fef6d560eacd730f79f60891ca79c72756", + "hash_raw": "a0185fa5ffc860de2179e4d0fec703fef6d560eacd730f79f60891ca79c72756", "size": 17496064, "sparse": false, "full_check": true, "has_ab": true, - "ondevice_hash": "35014c39b55010ac955c10f808b088e74259147c7a8cbf989b3dff7d95a1e8ae" + "ondevice_hash": "0ee1ab104bb46d0f72e7d0b7d3e94629a7644a368896c6d4c558554fb955a08a" }, { "name": "system", - "url": "https://commadist.azureedge.net/agnosupdate/system-d9d476b466186014e7ae4b8232bc6fc5e79b122421bdc12ff4eb02d1c3f37818.img.xz", - "hash": "a068d4d692ec770884f0a15e1a6d7aba52385ecae138f6d43fb0a9b1643ed5cd", - "hash_raw": "d9d476b466186014e7ae4b8232bc6fc5e79b122421bdc12ff4eb02d1c3f37818", + "url": "https://commadist.azureedge.net/agnosupdate/system-0cf8cb01e40d05d6d325afe68b934a6c0dda3a56703b2ef3e3de637d754ae5dd.img.xz", + "hash": "7c58308be461126677ba02e9c9739556520ee02958934733867d86ecfe2e58e9", + "hash_raw": "0cf8cb01e40d05d6d325afe68b934a6c0dda3a56703b2ef3e3de637d754ae5dd", "size": 4718592000, "sparse": true, "full_check": false, "has_ab": true, - "ondevice_hash": "6ffa02f7113badc122742f33efebc5d17f1cd61dd6358f3e130c162707dbfaf4", + "ondevice_hash": "826790516410c325aa30265846946d06a556f0a7b23c957f65fd11c055a663da", "alt": { - "hash": "d9d476b466186014e7ae4b8232bc6fc5e79b122421bdc12ff4eb02d1c3f37818", - "url": "https://commadist.azureedge.net/agnosupdate/system-d9d476b466186014e7ae4b8232bc6fc5e79b122421bdc12ff4eb02d1c3f37818.img", + "hash": "0cf8cb01e40d05d6d325afe68b934a6c0dda3a56703b2ef3e3de637d754ae5dd", + "url": "https://commadist.azureedge.net/agnosupdate/system-0cf8cb01e40d05d6d325afe68b934a6c0dda3a56703b2ef3e3de637d754ae5dd.img", "size": 4718592000 } }, { "name": "userdata_90", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_90-f9ea618ac97a86da49733ce66cd5e3aa19aa917666ee90de301cd746664e4d22.img.xz", - "hash": "dfc6812e76bd1583ed77a86eedf48cafdc306037d2a85c5d0aa7cdb23033b736", - "hash_raw": "f9ea618ac97a86da49733ce66cd5e3aa19aa917666ee90de301cd746664e4d22", + "url": "https://commadist.azureedge.net/agnosupdate/userdata_90-ec31b8116125a95755adb32853c401c462a14a74f538535532bf2c34d72c60eb.img.xz", + "hash": "aa0f0fe32187493e6135aee9e984d3f9705fc58560d537b34687bb6b51a38428", + "hash_raw": "ec31b8116125a95755adb32853c401c462a14a74f538535532bf2c34d72c60eb", "size": 96636764160, "sparse": true, "full_check": true, "has_ab": false, - "ondevice_hash": "ff95f994e9ed6504632f4b7c6daecef582f0a4e5261b8240d4474f16059faef4" + "ondevice_hash": "9c916b7d05543d4608b0401bc867639f44ce9671639a1a6da83b6d58b4eaa1b4" }, { "name": "userdata_89", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_89-393956e255c277b895bdb98bf65cfa3907e4b57822740ff82f857ac4e1a2f11e.img.xz", - "hash": "b5e2f05d31fc18fff18e82dcebfc2bf04de624baeca0511b93e50b3198b8a9ab", - "hash_raw": "393956e255c277b895bdb98bf65cfa3907e4b57822740ff82f857ac4e1a2f11e", + "url": "https://commadist.azureedge.net/agnosupdate/userdata_89-7f092cc841124c10300e43574e90e3367e983bfbe4faa0969024e79e5ce90b11.img.xz", + "hash": "fa83d4b7096857136820b0b0a8785c90677256b054c5c14039cd7b9b1065a90b", + "hash_raw": "7f092cc841124c10300e43574e90e3367e983bfbe4faa0969024e79e5ce90b11", "size": 95563022336, "sparse": true, "full_check": true, "has_ab": false, - "ondevice_hash": "db64c6abc72bfcddc1682c73cc73c7230ed2f6e835d292fd38d054a9d242b8fc" + "ondevice_hash": "1699e38de769eb32c21dfa6a5ac21eb3ad620a362c7b8abf1a2c0afe0f717530" }, { "name": "userdata_30", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_30-a4b3e2a2fc3612a37322b7b1a4c5737765841dc3b8d6d3bb58b1e5a271023068.img.xz", - "hash": "ecec713cf7d8f1f616f122a16b138931f818290447e36a5925da6a4fc0fc7bf3", - "hash_raw": "a4b3e2a2fc3612a37322b7b1a4c5737765841dc3b8d6d3bb58b1e5a271023068", + "url": "https://commadist.azureedge.net/agnosupdate/userdata_30-3df2dcd5e1f426c90b090fdbcd1a95b035d96a4bdaf88d5517245db5ee84f5ed.img.xz", + "hash": "890910f20b1ad88a728ee822a47b1234eb3d70cab28ca8a935679c8c2d33cbe9", + "hash_raw": "3df2dcd5e1f426c90b090fdbcd1a95b035d96a4bdaf88d5517245db5ee84f5ed", "size": 32212254720, "sparse": true, "full_check": true, "has_ab": false, - "ondevice_hash": "48fefa5a1880a4fd3dd50e1f9ddee297122053556816baca310d495129bc8893" + "ondevice_hash": "8e7cb392dd6e49c7d59fa850be7d1f44901314c86ba9c88be5bb27a0cd1123c9" } ] \ No newline at end of file From 654338f9c7c096377ad39afc39122fdd207ce3d1 Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Thu, 18 Dec 2025 22:52:14 -0800 Subject: [PATCH 662/910] update release instructions --- release/README.md | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/release/README.md b/release/README.md index fb651fa05..7aeea9fe4 100644 --- a/release/README.md +++ b/release/README.md @@ -4,18 +4,17 @@ ## release checklist ### Go to staging -- [ ] make a GitHub issue to track release +- [ ] make a GitHub issue to track release with this checklist - [ ] create release master branch -- [ ] update RELEASES.md + - [ ] create a branch from upstream master named `zerotentwo` for release `v0.10.2` + - [ ] revert risky commits (double check with autonomy team) + - [ ] push the new branch +- [ ] push to staging: + - [ ] make sure you are on the newly created release master branch (`zerotentwo`) + - [ ] run `BRANCH=devel-staging release/build_stripped.sh`. Jenkins will then automatically build staging on device, run `test_onroad` and update the staging branch - [ ] bump version on master: `common/version.h` and `RELEASES.md` -- [ ] build new userdata partition from `release3-staging` - [ ] post on Discord, tag `@release crew` -Updating staging: -1. either rebase on master or cherry-pick changes -2. run this to update: `BRANCH=devel-staging release/build_devel.sh` -3. build new userdata partition from `release3-staging` - ### Go to release - [ ] before going to release, test the following: - [ ] update from previous release -> new release @@ -26,7 +25,7 @@ Updating staging: - [ ] check sentry, MTBF, etc. - [ ] stress test passes in production - [ ] publish the blog post -- [ ] `git reset --hard origin/release3-staging` +- [ ] `git reset --hard origin/release-mici-staging` - [ ] tag the release: `git tag v0.X.X && git push origin v0.X.X` - [ ] create GitHub release - [ ] final test install on `openpilot.comma.ai` From 1c2f9e6190210a870191ffb39c719f73b069b24c Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Thu, 18 Dec 2025 23:26:06 -0800 Subject: [PATCH 663/910] bump version to 0.10.4 --- RELEASES.md | 3 +++ common/version.h | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/RELEASES.md b/RELEASES.md index 302835140..186f1bc04 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,3 +1,6 @@ +Version 0.10.4 (2026-02-17) +======================== + Version 0.10.3 (2025-12-17) ======================== * New driving model #36249 diff --git a/common/version.h b/common/version.h index c489ecc57..7e78d64b2 100644 --- a/common/version.h +++ b/common/version.h @@ -1 +1 @@ -#define COMMA_VERSION "0.10.3" +#define COMMA_VERSION "0.10.4" From c3143f3833a1ca6f1b949e70df54d9753e8195a9 Mon Sep 17 00:00:00 2001 From: eFini Date: Sat, 20 Dec 2025 01:38:23 +0800 Subject: [PATCH 664/910] Multilang: update zh translation (#36933) --- selfdrive/ui/translations/app_zh-CHS.po | 6 +++--- selfdrive/ui/translations/app_zh-CHT.po | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/selfdrive/ui/translations/app_zh-CHS.po b/selfdrive/ui/translations/app_zh-CHS.po index 16e436947..2400b6f44 100644 --- a/selfdrive/ui/translations/app_zh-CHS.po +++ b/selfdrive/ui/translations/app_zh-CHS.po @@ -200,7 +200,7 @@ msgstr "CONNECT" #: system/ui/widgets/network.py:369 #, python-format msgid "CONNECTING..." -msgstr "CONNECTING..." +msgstr "连接中..." #: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35 #: system/ui/widgets/keyboard.py:81 system/ui/widgets/network.py:318 @@ -1001,7 +1001,7 @@ msgstr "用于“{}”" #: selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "km/h" -msgstr "公里/时" +msgstr "km/h" #: system/ui/widgets/network.py:204 #, python-format @@ -1021,7 +1021,7 @@ msgstr "计量" #: selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "mph" -msgstr "英里/时" +msgstr "mph" #: selfdrive/ui/layouts/settings/software.py:20 #, python-format diff --git a/selfdrive/ui/translations/app_zh-CHT.po b/selfdrive/ui/translations/app_zh-CHT.po index 85cfb7740..f4d5e0a4e 100644 --- a/selfdrive/ui/translations/app_zh-CHT.po +++ b/selfdrive/ui/translations/app_zh-CHT.po @@ -200,7 +200,7 @@ msgstr "CONNECT" #: system/ui/widgets/network.py:369 #, python-format msgid "CONNECTING..." -msgstr "CONNECTING..." +msgstr "連線中..." #: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35 #: system/ui/widgets/keyboard.py:81 system/ui/widgets/network.py:318 @@ -1000,7 +1000,7 @@ msgstr "適用於「{}」" #: selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "km/h" -msgstr "公里/時" +msgstr "km/h" #: system/ui/widgets/network.py:204 #, python-format @@ -1020,7 +1020,7 @@ msgstr "計量" #: selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "mph" -msgstr "英里/時" +msgstr "mph" #: selfdrive/ui/layouts/settings/software.py:20 #, python-format From 5578b7e7540e194b36e78a8033aa7964875d3af7 Mon Sep 17 00:00:00 2001 From: royjr Date: Fri, 19 Dec 2025 14:36:10 -0500 Subject: [PATCH 665/910] ui: lateral-only and longitudinal-only UI statuses support (#1539) * init * add only colors * fix LAT_ONLY on mici * better ball * hide wheel on LONG_ONLY * hide torquebar on LONG_ONLY * simpler * dont block demo * path only on long * lanelines only on lat * hide on override * better * same LANE_LINE_COLORS for mads * use mads colors * Revert "use mads colors" This reverts commit 556321e5debe44e33d4ad98f440f0ed9f961fdf5. * slight decouple confidence ball * slight decouple model renderer * slight decouple augmented road view * decouple status update * decouple and override with our own, no overriding with steering if long only * fix * fix it --------- Co-authored-by: Jason Wen --- selfdrive/ui/mici/onroad/confidence_ball.py | 12 +++++- selfdrive/ui/mici/onroad/model_renderer.py | 3 ++ selfdrive/ui/mici/onroad/torque_bar.py | 6 +-- selfdrive/ui/onroad/augmented_road_view.py | 3 ++ .../ui/sunnypilot/mici/onroad/__init__.py | 0 .../sunnypilot/mici/onroad/confidence_ball.py | 26 ++++++++++++ .../sunnypilot/mici/onroad/model_renderer.py | 13 ++++++ .../sunnypilot/onroad/augmented_road_view.py | 13 ++++++ selfdrive/ui/sunnypilot/ui_state.py | 42 ++++++++++++++++++- selfdrive/ui/ui_state.py | 4 ++ 10 files changed, 116 insertions(+), 6 deletions(-) create mode 100644 selfdrive/ui/sunnypilot/mici/onroad/__init__.py create mode 100644 selfdrive/ui/sunnypilot/mici/onroad/confidence_ball.py create mode 100644 selfdrive/ui/sunnypilot/mici/onroad/model_renderer.py create mode 100644 selfdrive/ui/sunnypilot/onroad/augmented_road_view.py diff --git a/selfdrive/ui/mici/onroad/confidence_ball.py b/selfdrive/ui/mici/onroad/confidence_ball.py index a5c95470f..54699eab5 100644 --- a/selfdrive/ui/mici/onroad/confidence_ball.py +++ b/selfdrive/ui/mici/onroad/confidence_ball.py @@ -6,6 +6,8 @@ from openpilot.system.ui.widgets import Widget from openpilot.system.ui.lib.application import gui_app from openpilot.common.filter_simple import FirstOrderFilter +from openpilot.selfdrive.ui.sunnypilot.mici.onroad.confidence_ball import ConfidenceBallSP + def draw_circle_gradient(center_x: float, center_y: float, radius: int, top: rl.Color, bottom: rl.Color) -> None: @@ -21,9 +23,10 @@ def draw_circle_gradient(center_x: float, center_y: float, radius: int, 20, rl.BLACK) -class ConfidenceBall(Widget): +class ConfidenceBall(Widget, ConfidenceBallSP): def __init__(self, demo: bool = False): - super().__init__() + Widget.__init__(self) + ConfidenceBallSP.__init__(self) self._demo = demo self._confidence_filter = FirstOrderFilter(-0.5, 0.5, 1 / gui_app.target_fps) @@ -37,6 +40,8 @@ class ConfidenceBall(Widget): # animate status dot in from bottom if ui_state.status == UIStatus.DISENGAGED: self._confidence_filter.update(-0.5) + elif ui_state.status in (UIStatus.LAT_ONLY, UIStatus.LONG_ONLY): + self._confidence_filter.update(1 - max(self.get_animate_status_probs() or [1])) else: self._confidence_filter.update((1 - max(ui_state.sm['modelV2'].meta.disengagePredictions.brakeDisengageProbs or [1])) * (1 - max(ui_state.sm['modelV2'].meta.disengagePredictions.steerOverrideProbs or [1]))) @@ -65,6 +70,9 @@ class ConfidenceBall(Widget): top_dot_color = rl.Color(255, 0, 21, 255) bottom_dot_color = rl.Color(255, 0, 89, 255) + elif ui_state.status in (UIStatus.LAT_ONLY, UIStatus.LONG_ONLY): + top_dot_color = bottom_dot_color = self.get_lat_long_dot_color() + elif ui_state.status == UIStatus.OVERRIDE: top_dot_color = rl.Color(255, 255, 255, 255) bottom_dot_color = rl.Color(82, 82, 82, 255) diff --git a/selfdrive/ui/mici/onroad/model_renderer.py b/selfdrive/ui/mici/onroad/model_renderer.py index 3f1badfe8..db316aa63 100644 --- a/selfdrive/ui/mici/onroad/model_renderer.py +++ b/selfdrive/ui/mici/onroad/model_renderer.py @@ -12,6 +12,8 @@ from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.lib.shader_polygon import draw_polygon, Gradient from openpilot.system.ui.widgets import Widget +from openpilot.selfdrive.ui.sunnypilot.mici.onroad.model_renderer import LANE_LINE_COLORS_SP + CLIP_MARGIN = 500 MIN_DRAW_DISTANCE = 10.0 MAX_DRAW_DISTANCE = 100.0 @@ -32,6 +34,7 @@ LANE_LINE_COLORS = { UIStatus.DISENGAGED: rl.Color(200, 200, 200, 255), UIStatus.OVERRIDE: rl.Color(255, 255, 255, 255), UIStatus.ENGAGED: rl.Color(0, 255, 64, 255), + **LANE_LINE_COLORS_SP, } diff --git a/selfdrive/ui/mici/onroad/torque_bar.py b/selfdrive/ui/mici/onroad/torque_bar.py index d7c9f27a9..c8485a310 100644 --- a/selfdrive/ui/mici/onroad/torque_bar.py +++ b/selfdrive/ui/mici/onroad/torque_bar.py @@ -185,13 +185,13 @@ class TorqueBar(Widget): # animate alpha and angle span if not self._demo: - self._torque_line_alpha_filter.update(ui_state.status != UIStatus.DISENGAGED) + self._torque_line_alpha_filter.update(ui_state.status not in (UIStatus.DISENGAGED, UIStatus.LONG_ONLY)) else: self._torque_line_alpha_filter.update(1.0) torque_line_bg_alpha = np.interp(abs(self._torque_filter.x), [0.5, 1.0], [0.25, 0.5]) torque_line_bg_color = rl.Color(255, 255, 255, int(255 * torque_line_bg_alpha * self._torque_line_alpha_filter.x)) - if ui_state.status != UIStatus.ENGAGED and not self._demo: + if ui_state.status not in (UIStatus.ENGAGED, UIStatus.LAT_ONLY) and not self._demo: torque_line_bg_color = rl.Color(255, 255, 255, int(255 * 0.15 * self._torque_line_alpha_filter.x)) # draw curved line polygon torque bar @@ -234,7 +234,7 @@ class TorqueBar(Widget): max(0, abs(self._torque_filter.x) - 0.75) * 4, ) - if ui_state.status != UIStatus.ENGAGED and not self._demo: + if ui_state.status not in (UIStatus.ENGAGED, UIStatus.LAT_ONLY) and not self._demo: start_color = end_color = rl.Color(255, 255, 255, int(255 * 0.35 * self._torque_line_alpha_filter.x)) gradient = Gradient( diff --git a/selfdrive/ui/onroad/augmented_road_view.py b/selfdrive/ui/onroad/augmented_road_view.py index a47c04053..1d66379f9 100644 --- a/selfdrive/ui/onroad/augmented_road_view.py +++ b/selfdrive/ui/onroad/augmented_road_view.py @@ -17,6 +17,8 @@ from openpilot.common.transformations.orientation import rot_from_euler if gui_app.sunnypilot_ui(): from openpilot.selfdrive.ui.sunnypilot.onroad.hud_renderer import HudRendererSP as HudRenderer +from openpilot.selfdrive.ui.sunnypilot.onroad.augmented_road_view import BORDER_COLORS_SP + OpState = log.SelfdriveState.OpenpilotState CALIBRATED = log.LiveCalibrationData.Status.calibrated ROAD_CAM = VisionStreamType.VISION_STREAM_ROAD @@ -27,6 +29,7 @@ BORDER_COLORS = { UIStatus.DISENGAGED: rl.Color(0x12, 0x28, 0x39, 0xFF), # Blue for disengaged state UIStatus.OVERRIDE: rl.Color(0x89, 0x92, 0x8D, 0xFF), # Gray for override state UIStatus.ENGAGED: rl.Color(0x16, 0x7F, 0x40, 0xFF), # Green for engaged state + **BORDER_COLORS_SP, } WIDE_CAM_MAX_SPEED = 10.0 # m/s (22 mph) diff --git a/selfdrive/ui/sunnypilot/mici/onroad/__init__.py b/selfdrive/ui/sunnypilot/mici/onroad/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/selfdrive/ui/sunnypilot/mici/onroad/confidence_ball.py b/selfdrive/ui/sunnypilot/mici/onroad/confidence_ball.py new file mode 100644 index 000000000..4a1aa9224 --- /dev/null +++ b/selfdrive/ui/sunnypilot/mici/onroad/confidence_ball.py @@ -0,0 +1,26 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +from openpilot.selfdrive.ui.onroad.augmented_road_view import BORDER_COLORS +from openpilot.selfdrive.ui.ui_state import ui_state, UIStatus + + +class ConfidenceBallSP: + @staticmethod + def get_animate_status_probs(): + if ui_state.status == UIStatus.LAT_ONLY: + return ui_state.sm['modelV2'].meta.disengagePredictions.steerOverrideProbs + + # UIStatus.LONG_ONLY + return ui_state.sm['modelV2'].meta.disengagePredictions.brakeDisengageProbs + + @staticmethod + def get_lat_long_dot_color(): + if ui_state.status == UIStatus.LAT_ONLY: + return BORDER_COLORS[UIStatus.LAT_ONLY] + + # UIStatus.LONG_ONLY + return BORDER_COLORS[UIStatus.LONG_ONLY] diff --git a/selfdrive/ui/sunnypilot/mici/onroad/model_renderer.py b/selfdrive/ui/sunnypilot/mici/onroad/model_renderer.py new file mode 100644 index 000000000..5a718947c --- /dev/null +++ b/selfdrive/ui/sunnypilot/mici/onroad/model_renderer.py @@ -0,0 +1,13 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +import pyray as rl +from openpilot.selfdrive.ui.ui_state import UIStatus + +LANE_LINE_COLORS_SP = { + UIStatus.LAT_ONLY: rl.Color(0, 255, 64, 255), + UIStatus.LONG_ONLY: rl.Color(0, 255, 64, 255), +} diff --git a/selfdrive/ui/sunnypilot/onroad/augmented_road_view.py b/selfdrive/ui/sunnypilot/onroad/augmented_road_view.py new file mode 100644 index 000000000..0a5739cc0 --- /dev/null +++ b/selfdrive/ui/sunnypilot/onroad/augmented_road_view.py @@ -0,0 +1,13 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +import pyray as rl +from openpilot.selfdrive.ui.ui_state import UIStatus + +BORDER_COLORS_SP = { + UIStatus.LAT_ONLY: rl.Color(0x00, 0xC8, 0xC8, 0xFF), # Cyan for lateral-only state + UIStatus.LONG_ONLY: rl.Color(0x96, 0x1C, 0xA8, 0xFF), # Purple for longitudinal-only state +} diff --git a/selfdrive/ui/sunnypilot/ui_state.py b/selfdrive/ui/sunnypilot/ui_state.py index 8a0bc24ad..aca0644c1 100644 --- a/selfdrive/ui/sunnypilot/ui_state.py +++ b/selfdrive/ui/sunnypilot/ui_state.py @@ -4,10 +4,13 @@ Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. This file is part of sunnypilot and is licensed under the MIT License. See the LICENSE.md file in the root directory for more details. """ -from cereal import messaging, custom +from cereal import messaging, log, custom from openpilot.common.params import Params from openpilot.sunnypilot.sunnylink.sunnylink_state import SunnylinkState +OpenpilotState = log.SelfdriveState.OpenpilotState +MADSState = custom.ModularAssistiveDrivingSystem.ModularAssistiveDrivingSystemState + class UIStateSP: def __init__(self): @@ -22,6 +25,43 @@ class UIStateSP: def update(self) -> None: self.sunnylink_state.start() + @staticmethod + def update_status(ss, ss_sp, onroad_evt) -> str: + state = ss.state + mads = ss_sp.mads + mads_state = mads.state + + if state == OpenpilotState.preEnabled: + return "override" + + if state == OpenpilotState.overriding: + if not mads.available: + return "override" + + if any(e.overrideLongitudinal for e in onroad_evt): + return "override" + + if mads_state in (MADSState.paused, MADSState.overriding): + return "override" + + # MADS specific statuses + if not mads.available: + return "engaged" if ss.enabled else "disengaged" + + if not mads.enabled and not ss.enabled: + return "disengaged" + + if mads.enabled and ss.enabled: + return "engaged" + + if mads.enabled: + return "lat_only" + + if ss.enabled: + return "long_only" + + return "disengaged" + def update_params(self) -> None: CP_SP_bytes = self.params.get("CarParamsSPPersistent") if CP_SP_bytes is not None: diff --git a/selfdrive/ui/ui_state.py b/selfdrive/ui/ui_state.py index 378731390..f44dc152b 100644 --- a/selfdrive/ui/ui_state.py +++ b/selfdrive/ui/ui_state.py @@ -21,6 +21,8 @@ class UIStatus(Enum): DISENGAGED = "disengaged" ENGAGED = "engaged" OVERRIDE = "override" + LAT_ONLY = "lat_only" + LONG_ONLY = "long_only" class UIState(UIStateSP): @@ -156,6 +158,8 @@ class UIState(UIStateSP): else: self.status = UIStatus.ENGAGED if ss.enabled else UIStatus.DISENGAGED + self.status = UIStatus(UIStateSP.update_status(ss, self.sm["selfdriveStateSP"], self.sm["onroadEvents"])) + # Check for engagement state changes if self.engaged != self._engaged_prev: for callback in self._engaged_transition_callbacks: From 2e576178cbe26979060bb378390d3e87804cb6c2 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 19 Dec 2025 15:31:29 -0500 Subject: [PATCH 666/910] ci: fix duplicate `if` syntax error (#1590) --- .github/workflows/tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 68dd5617a..14fa6e1bd 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -107,8 +107,8 @@ jobs: build_mac: name: build macOS - if: false # tmp disable due to brew install not working runs-on: ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-macos-8x14' || 'macos-latest' }} + if: false # There'll be one day that this works. That day is not today. steps: - uses: actions/checkout@v4 with: From f8487cae23416baf563b468b0565286b519569e7 Mon Sep 17 00:00:00 2001 From: Nayan Date: Fri, 19 Dec 2025 15:49:24 -0500 Subject: [PATCH 667/910] sunnylink: elliptic curve keys support and improve key path handling (#1566) * support ecdsa for mici * lint * ugh * ugh ughain * more * symmetrical AES key derivation and some missing key handling * cleanup --------- Co-authored-by: Jason Wen --- sunnypilot/sunnylink/backups/manager.py | 4 +- sunnypilot/sunnylink/backups/utils.py | 84 +++++++++++++++---------- 2 files changed, 53 insertions(+), 35 deletions(-) diff --git a/sunnypilot/sunnylink/backups/manager.py b/sunnypilot/sunnylink/backups/manager.py index 1b3c623fc..cc3847604 100644 --- a/sunnypilot/sunnylink/backups/manager.py +++ b/sunnypilot/sunnylink/backups/manager.py @@ -19,7 +19,7 @@ from openpilot.system.version import get_version from cereal import messaging, custom from openpilot.sunnypilot.sunnylink.api import SunnylinkApi -from openpilot.sunnypilot.sunnylink.backups.utils import decrypt_compressed_data, encrypt_compress_data, SnakeCaseEncoder +from openpilot.sunnypilot.sunnylink.backups.utils import decrypt_compressed_data, encrypt_compressed_data, SnakeCaseEncoder from openpilot.sunnypilot.sunnylink.utils import get_param_as_byte, save_param_from_base64_encoded_string @@ -95,7 +95,7 @@ class BackupManagerSP: # Serialize and encrypt config data config_json = json.dumps(config_data) - encrypted_config = encrypt_compress_data(config_json, use_aes_256=True) + encrypted_config = encrypt_compressed_data(config_json, use_aes_256=True) self._update_progress(50.0, OperationType.BACKUP) backup_info = custom.BackupManagerSP.BackupInfo() diff --git a/sunnypilot/sunnylink/backups/utils.py b/sunnypilot/sunnylink/backups/utils.py index 1734a7efc..a81a13b2c 100644 --- a/sunnypilot/sunnylink/backups/utils.py +++ b/sunnypilot/sunnylink/backups/utils.py @@ -4,9 +4,9 @@ Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. This file is part of sunnypilot and is licensed under the MIT License. See the LICENSE.md file in the root directory for more details. """ - import base64 import hashlib +import os import zlib import re import json @@ -14,8 +14,9 @@ from pathlib import Path from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import serialization -from cryptography.hazmat.primitives.asymmetric import rsa +from cryptography.hazmat.primitives.asymmetric import rsa, ec +from openpilot.common.api.base import KEYS from openpilot.sunnypilot.sunnylink.backups.AESCipher import AESCipher from openpilot.system.hardware.hw import Paths @@ -27,37 +28,43 @@ class KeyDerivation: return f.read() @staticmethod - def derive_aes_key_iv_from_rsa(key_path: str, use_aes_256: bool) -> tuple[bytes, bytes]: - rsa_key_pem: bytes = KeyDerivation._load_key(key_path) - key_plain = rsa_key_pem.decode(errors="ignore") + def derive_aes_key_iv(key_path: str, use_aes_256: bool) -> tuple[bytes, bytes]: + key_pem: bytes = KeyDerivation._load_key(key_path) + key_plain = key_pem.decode(errors="ignore") if "private" in key_plain.lower(): - private_key = serialization.load_pem_private_key(rsa_key_pem, password=None, backend=default_backend()) - if not isinstance(private_key, rsa.RSAPrivateKey): - raise ValueError("Invalid RSA key format: Unable to determine if key is public or private.") - - der_data = private_key.private_bytes( - encoding=serialization.Encoding.DER, - format=serialization.PrivateFormat.TraditionalOpenSSL, - encryption_algorithm=serialization.NoEncryption() - ) + private_key = serialization.load_pem_private_key(key_pem, password=None, backend=default_backend()) + if isinstance(private_key, (rsa.RSAPrivateKey, ec.EllipticCurvePrivateKey)): + public_key = private_key.public_key() + else: + raise ValueError("Invalid key format: Unable to determine if key is public or private.") elif "public" in key_plain.lower(): - public_key = serialization.load_pem_public_key(rsa_key_pem, backend=default_backend()) - if not isinstance(public_key, rsa.RSAPublicKey): - raise ValueError("Invalid RSA key format: Unable to determine if key is public or private.") - - der_data = public_key.public_bytes(encoding=serialization.Encoding.DER, format=serialization.PublicFormat.PKCS1) + public_key = serialization.load_pem_public_key(key_pem, backend=default_backend()) # type: ignore[assignment] + if not isinstance(public_key, (rsa.RSAPublicKey, ec.EllipticCurvePublicKey)): + raise ValueError("Invalid key format: Unable to determine if key is public or private.") else: - raise ValueError("Unknown key format: Unable to determine if key is public or private.") + raise ValueError("Invalid key format: Unable to determine if key is public or private.") - sha256_hash = hashlib.sha256(der_data).digest() - aes_key = sha256_hash[:32] if use_aes_256 else sha256_hash[:16] - aes_iv = sha256_hash[16:32] + if isinstance(public_key, rsa.RSAPublicKey): + der_data = public_key.public_bytes(encoding=serialization.Encoding.DER, format=serialization.PublicFormat.PKCS1) + elif isinstance(public_key, ec.EllipticCurvePublicKey): + der_data = public_key.public_bytes(encoding=serialization.Encoding.DER, format=serialization.PublicFormat.SubjectPublicKeyInfo) + else: + raise ValueError("Unsupported key type.") - return aes_key, aes_iv + if use_aes_256: + # AES-256-CBC + key = hashlib.sha256(der_data).digest() + iv = hashlib.md5(der_data).digest() + else: + # AES-128-CBC + key = hashlib.md5(der_data).digest() + iv = hashlib.md5(der_data).digest() # Insecure IV reuse, kept for compatibility + + return key, iv -def qUncompress(data): +def uncompress_dat(data): """ Decompress data using zlib. @@ -71,7 +78,7 @@ def qUncompress(data): return zlib.decompress(data_stripped_4) -def qCompress(data): +def compress_dat(data): """ Compress data using zlib. @@ -85,6 +92,19 @@ def qCompress(data): return b"ZLIB" + compressed_data +def get_key_path(use_aes_256=False) -> str: + key_path = "" + for key in KEYS: + if os.path.isfile(Paths.persist_root() + f'/comma/{key}') and os.path.isfile(Paths.persist_root() + f'/comma/{key}.pub'): + key_path = str(Path(Paths.persist_root() + f'/comma/{key}') if use_aes_256 else Path(Paths.persist_root() + f'/comma/{key}.pub')) + break + + if not key_path: + raise FileNotFoundError("No valid key pair found in persist storage.") + + return key_path + + def decrypt_compressed_data(encrypted_base64, use_aes_256=False): """ Decrypt and decompress data from base64 string. @@ -96,18 +116,17 @@ def decrypt_compressed_data(encrypted_base64, use_aes_256=False): Returns: str: Decrypted and decompressed string """ - key_path = Path(f"{Paths.persist_root()}/comma/id_rsa") if use_aes_256 else Path(f"{Paths.persist_root()}/comma/id_rsa.pub") try: # Decode base64 encrypted_data = base64.b64decode(encrypted_base64) # Decrypt - key, iv = KeyDerivation.derive_aes_key_iv_from_rsa(str(key_path), use_aes_256) + key, iv = KeyDerivation.derive_aes_key_iv(get_key_path(use_aes_256), use_aes_256) cipher = AESCipher(key, iv) decrypted_data = cipher.decrypt(encrypted_data) # Decompress - decompressed_data = qUncompress(decrypted_data) + decompressed_data = uncompress_dat(decrypted_data) # Decode UTF-8 result = decompressed_data.decode('utf-8') @@ -117,7 +136,7 @@ def decrypt_compressed_data(encrypted_base64, use_aes_256=False): return "" -def encrypt_compress_data(text, use_aes_256=True): +def encrypt_compressed_data(text, use_aes_256=True): """ Compress and encrypt string data to base64. @@ -128,16 +147,15 @@ def encrypt_compress_data(text, use_aes_256=True): Returns: str: Base64 encoded encrypted data """ - key_path = Path(f"{Paths.persist_root()}/comma/id_rsa") if use_aes_256 else Path(f"{Paths.persist_root()}/comma/id_rsa.pub") try: # Encode to UTF-8 text_bytes = text.encode('utf-8') # Compress - compressed_data = qCompress(text_bytes) + compressed_data = compress_dat(text_bytes) # Encrypt - key, iv = KeyDerivation.derive_aes_key_iv_from_rsa(str(key_path), use_aes_256) + key, iv = KeyDerivation.derive_aes_key_iv(get_key_path(use_aes_256), use_aes_256) cipher = AESCipher(key, iv) encrypted_data = cipher.encrypt(compressed_data) From 40f838260b00fc282a341858064c7314330a31ed Mon Sep 17 00:00:00 2001 From: zikeji Date: Fri, 19 Dec 2025 16:31:01 -0500 Subject: [PATCH 668/910] sunnylink: block remote modification of SSH key parameters (#1591) * feat: add blocked parameter names * add unit test to validate * test: use cached method * move it out --------- Co-authored-by: Jason Wen --- sunnypilot/sunnylink/athena/sunnylinkd.py | 11 ++++ .../sunnylink/athena/tests/test_sunnylinkd.py | 59 +++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 sunnypilot/sunnylink/athena/tests/test_sunnylinkd.py diff --git a/sunnypilot/sunnylink/athena/sunnylinkd.py b/sunnypilot/sunnylink/athena/sunnylinkd.py index 3aeacf6a3..9f70aead5 100755 --- a/sunnypilot/sunnylink/athena/sunnylinkd.py +++ b/sunnypilot/sunnylink/athena/sunnylinkd.py @@ -42,6 +42,12 @@ METADATA_PATH = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__f params = Params() +# Parameters that should never be remotely modified for security reasons +BLOCKED_PARAMS = { + "GithubUsername", # Could grant SSH access + "GithubSshKeys", # Direct SSH key injection +} + def handle_long_poll(ws: WebSocket, exit_event: threading.Event | None) -> None: cloudlog.info("sunnylinkd.handle_long_poll started") @@ -248,6 +254,11 @@ def getParams(params_keys: list[str], compression: bool = False) -> str | dict[s @dispatcher.add_method def saveParams(params_to_update: dict[str, str], compression: bool = False) -> None: for key, value in params_to_update.items(): + # disallow modifications to blocked parameters + if key in BLOCKED_PARAMS: + cloudlog.warning(f"sunnylinkd.saveParams.blocked: Attempted to modify blocked parameter '{key}'") + continue + try: save_param_from_base64_encoded_string(key, value, compression) except Exception as e: diff --git a/sunnypilot/sunnylink/athena/tests/test_sunnylinkd.py b/sunnypilot/sunnylink/athena/tests/test_sunnylinkd.py new file mode 100644 index 000000000..616bff037 --- /dev/null +++ b/sunnypilot/sunnylink/athena/tests/test_sunnylinkd.py @@ -0,0 +1,59 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +from openpilot.sunnypilot.sunnylink.athena import sunnylinkd + + +class TestSunnylinkdMethods: + def setup_method(self): + self.saved_params = [] + + self.original_save = sunnylinkd.save_param_from_base64_encoded_string + + def mock_save_param(key, value, compression=False): + self.saved_params.append((key, value, compression)) + + sunnylinkd.save_param_from_base64_encoded_string = mock_save_param + + def teardown_method(self): + sunnylinkd.save_param_from_base64_encoded_string = self.original_save + + def test_saveParams_blocked(self): + blocked_params = { + "GithubUsername": "attacker", + "GithubSshKeys": "ssh-rsa attacker_key", + } + + sunnylinkd.saveParams(blocked_params) + + assert len(self.saved_params) == 0 + + def test_saveParams_allowed(self): + allowed_params = { + "SpeedLimitOffset": "5", + "MyCustomParam": "123" + } + + sunnylinkd.saveParams(allowed_params) + + # verify content + assert len(self.saved_params) == 2 + keys_saved = [p[0] for p in self.saved_params] + assert "SpeedLimitOffset" in keys_saved + assert "MyCustomParam" in keys_saved + + def test_saveParams_mixed(self): + mixed_params = { + "GithubUsername": "attacker", + "SpeedLimitOffset": "10" + } + + sunnylinkd.saveParams(mixed_params) + + # should save allowed one + assert len(self.saved_params) == 1 + assert self.saved_params[0][0] == "SpeedLimitOffset" + assert self.saved_params[0][1] == "10" From f42dbf0c3489ea040963fef7a57b5b83891175ea Mon Sep 17 00:00:00 2001 From: Kumar <36933347+rav4kumar@users.noreply.github.com> Date: Fri, 19 Dec 2025 15:36:43 -0700 Subject: [PATCH 669/910] [TIZI/TICI] ui: rainbow path (#1486) * rainbow * use monotonic * sp dir * lint * decouple from stock model renderer * call in ui state directly * it's a boolean * too long * nope --------- Co-authored-by: Jason Wen --- selfdrive/ui/onroad/model_renderer.py | 11 ++- .../ui/sunnypilot/onroad/model_renderer.py | 12 +++ .../ui/sunnypilot/onroad/rainbow_path.py | 78 +++++++++++++++++++ selfdrive/ui/sunnypilot/ui_state.py | 1 + 4 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 selfdrive/ui/sunnypilot/onroad/model_renderer.py create mode 100644 selfdrive/ui/sunnypilot/onroad/rainbow_path.py diff --git a/selfdrive/ui/onroad/model_renderer.py b/selfdrive/ui/onroad/model_renderer.py index b9f601f8f..bf38f8e55 100644 --- a/selfdrive/ui/onroad/model_renderer.py +++ b/selfdrive/ui/onroad/model_renderer.py @@ -11,6 +11,8 @@ from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.lib.shader_polygon import draw_polygon, Gradient from openpilot.system.ui.widgets import Widget +from openpilot.selfdrive.ui.sunnypilot.onroad.model_renderer import ModelRendererSP + CLIP_MARGIN = 500 MIN_DRAW_DISTANCE = 10.0 MAX_DRAW_DISTANCE = 100.0 @@ -41,9 +43,10 @@ class LeadVehicle: fill_alpha: int = 0 -class ModelRenderer(Widget): +class ModelRenderer(Widget, ModelRendererSP): def __init__(self): - super().__init__() + Widget.__init__(self) + ModelRendererSP.__init__(self) self._longitudinal_control = False self._experimental_mode = False self._blend_filter = FirstOrderFilter(1.0, 0.25, 1 / gui_app.target_fps) @@ -281,6 +284,10 @@ class ModelRenderer(Widget): allow_throttle = sm['longitudinalPlan'].allowThrottle or not self._longitudinal_control self._blend_filter.update(int(allow_throttle)) + if ui_state.rainbow_path: + self.rainbow_path.draw_rainbow_path(self._rect, self._path) + return + if self._experimental_mode: # Draw with acceleration coloring if len(self._exp_gradient.colors) > 1: diff --git a/selfdrive/ui/sunnypilot/onroad/model_renderer.py b/selfdrive/ui/sunnypilot/onroad/model_renderer.py new file mode 100644 index 000000000..214a855fb --- /dev/null +++ b/selfdrive/ui/sunnypilot/onroad/model_renderer.py @@ -0,0 +1,12 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +from openpilot.selfdrive.ui.sunnypilot.onroad.rainbow_path import RainbowPath + + +class ModelRendererSP: + def __init__(self): + self.rainbow_path = RainbowPath() diff --git a/selfdrive/ui/sunnypilot/onroad/rainbow_path.py b/selfdrive/ui/sunnypilot/onroad/rainbow_path.py new file mode 100644 index 000000000..cd76261f8 --- /dev/null +++ b/selfdrive/ui/sunnypilot/onroad/rainbow_path.py @@ -0,0 +1,78 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +import time +import colorsys +import pyray as rl +from openpilot.system.ui.lib.shader_polygon import draw_polygon, Gradient + + +class RainbowPath: + DEFAULT_NUM_SEGMENTS = 8 + DEFAULT_SPEED = 50.0 # degrees per second + DEFAULT_SATURATION = 0.9 + DEFAULT_LIGHTNESS = 0.6 + BASE_ALPHA = 0.8 + ALPHA_FADE = 0.3 # Alpha reduction from bottom to top + + def __init__(self, num_segments: int = None, speed: float = None, saturation: float = None, lightness: float = None): + self.num_segments = num_segments if num_segments is not None else self.DEFAULT_NUM_SEGMENTS + self.speed = speed if speed is not None else self.DEFAULT_SPEED + self.saturation = saturation if saturation is not None else self.DEFAULT_SATURATION + self.lightness = lightness if lightness is not None else self.DEFAULT_LIGHTNESS + + def set_speed(self, speed: float): + self.speed = speed + + def set_num_segments(self, num_segments: int): + self.num_segments = num_segments + + def set_saturation(self, saturation: float): + self.saturation = max(0.0, min(1.0, saturation)) + + def set_lightness(self, lightness: float): + self.lightness = max(0.0, min(1.0, lightness)) + + def get_gradient(self) -> Gradient: + time_offset = time.monotonic() + hue_offset = (time_offset * self.speed) % 360.0 + + segment_colors = [] + gradient_stops = [] + + for i in range(self.num_segments): + position = i / (self.num_segments - 1) + hue = (hue_offset + position * 360.0) % 360.0 + alpha = self.BASE_ALPHA * (1.0 - position * self.ALPHA_FADE) + color = self._hsla_to_color( + hue / 360.0, + self.saturation, + self.lightness, + alpha + ) + gradient_stops.append(position) + segment_colors.append(color) + + return Gradient( + start=(0.0, 1.0), # Bottom of path + end=(0.0, 0.0), # Top of path + colors=segment_colors, + stops=gradient_stops, + ) + + @staticmethod + def _hsla_to_color(h: float, s: float, l: float, a: float) -> rl.Color: + rgb = colorsys.hls_to_rgb(h, l, s) + return rl.Color( + int(rgb[0] * 255), + int(rgb[1] * 255), + int(rgb[2] * 255), + int(a * 255) + ) + + def draw_rainbow_path(self, rect, path): + gradient = self.get_gradient() + draw_polygon(rect, path.projected_points, gradient=gradient) diff --git a/selfdrive/ui/sunnypilot/ui_state.py b/selfdrive/ui/sunnypilot/ui_state.py index aca0644c1..2e817090b 100644 --- a/selfdrive/ui/sunnypilot/ui_state.py +++ b/selfdrive/ui/sunnypilot/ui_state.py @@ -68,3 +68,4 @@ class UIStateSP: self.CP_SP = messaging.log_from_bytes(CP_SP_bytes, custom.CarParamsSP) self.sunnylink_enabled = self.params.get_bool("SunnylinkEnabled") self.developer_ui = self.params.get("DevUIInfo") + self.rainbow_path = self.params.get_bool("RainbowMode") From 5bf2ac1657086cd21e6428b0994540d493dc96b6 Mon Sep 17 00:00:00 2001 From: Kumar <36933347+rav4kumar@users.noreply.github.com> Date: Fri, 19 Dec 2025 20:06:27 -0700 Subject: [PATCH 670/910] [TIZI/TICI] ui: chevron metrics (#1487) * chevron info * sp dir * rename * decouple from stock model renderer * pain * RED DIFF: get from ui state directly * built in * banned * no magic * space --------- Co-authored-by: Jason Wen --- selfdrive/ui/onroad/model_renderer.py | 6 +- .../ui/sunnypilot/onroad/chevron_metrics.py | 147 ++++++++++++++++++ .../ui/sunnypilot/onroad/model_renderer.py | 2 + selfdrive/ui/sunnypilot/ui_state.py | 1 + 4 files changed, 154 insertions(+), 2 deletions(-) create mode 100644 selfdrive/ui/sunnypilot/onroad/chevron_metrics.py diff --git a/selfdrive/ui/onroad/model_renderer.py b/selfdrive/ui/onroad/model_renderer.py index bf38f8e55..cae976534 100644 --- a/selfdrive/ui/onroad/model_renderer.py +++ b/selfdrive/ui/onroad/model_renderer.py @@ -11,7 +11,7 @@ from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.lib.shader_polygon import draw_polygon, Gradient from openpilot.system.ui.widgets import Widget -from openpilot.selfdrive.ui.sunnypilot.onroad.model_renderer import ModelRendererSP +from openpilot.selfdrive.ui.sunnypilot.onroad.model_renderer import ChevronMetrics, ModelRendererSP CLIP_MARGIN = 500 MIN_DRAW_DISTANCE = 10.0 @@ -43,9 +43,10 @@ class LeadVehicle: fill_alpha: int = 0 -class ModelRenderer(Widget, ModelRendererSP): +class ModelRenderer(Widget, ChevronMetrics, ModelRendererSP): def __init__(self): Widget.__init__(self) + ChevronMetrics.__init__(self) ModelRendererSP.__init__(self) self._longitudinal_control = False self._experimental_mode = False @@ -131,6 +132,7 @@ class ModelRenderer(Widget, ModelRendererSP): if render_lead_indicator and radar_state: self._draw_lead_indicator() + self.chevron_metrics.draw_lead_status(sm, radar_state, self._rect, self._lead_vehicles) def _update_raw_points(self, model): """Update raw 3D points from model data""" diff --git a/selfdrive/ui/sunnypilot/onroad/chevron_metrics.py b/selfdrive/ui/sunnypilot/onroad/chevron_metrics.py new file mode 100644 index 000000000..a8a342c12 --- /dev/null +++ b/selfdrive/ui/sunnypilot/onroad/chevron_metrics.py @@ -0,0 +1,147 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +import numpy as np + +import pyray as rl +from openpilot.common.constants import CV +from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.system.ui.lib.application import gui_app, FontWeight +from openpilot.system.ui.lib.text_measure import measure_text_cached + + +class ChevronOptions: + OFF = 0 + DISTANCE_ONLY = 1 + SPEED_ONLY = 2 + TTC_ONLY = 3 + ALL = 4 + + +class ChevronMetrics: + def __init__(self): + self._lead_status_alpha: float = 0.0 + self._font = gui_app.font(FontWeight.SEMI_BOLD) + + def update_alpha(self, has_lead: bool): + """Update the alpha value for fade in/out animation""" + if not has_lead: + self._lead_status_alpha = max(0.0, self._lead_status_alpha - 0.05) + else: + self._lead_status_alpha = min(1.0, self._lead_status_alpha + 0.1) + + def should_render(self) -> bool: + """Check if dev UI should be rendered""" + return ui_state.chevron_metrics != ChevronOptions.OFF and self._lead_status_alpha > 0.0 + + def _draw_lead(self, lead_data, lead_vehicle, v_ego: float, rect: rl.Rectangle): + """Draw lead vehicle status information (distance, speed, TTC)""" + if not self.should_render(): + return + + d_rel = lead_data.dRel + v_rel = lead_data.vRel + + if not lead_vehicle.chevron or len(lead_vehicle.chevron) < 2: + return + + chevron_x = lead_vehicle.chevron[1][0] + chevron_y = lead_vehicle.chevron[1][1] + sz = np.clip((25 * 30) / (d_rel / 3 + 30), 15.0, 30.0) * 2.35 + + text_lines = self._build_text_lines(d_rel, v_rel, v_ego) + if not text_lines: + return + + self._render_text_lines(text_lines, chevron_x, chevron_y, sz, rect) + + @staticmethod + def _build_text_lines(d_rel: float, v_rel: float, v_ego: float) -> list[str]: + """Build text lines based on chevron info setting""" + text_lines = [] + + # Distance + if ui_state.chevron_metrics == ChevronOptions.DISTANCE_ONLY or ui_state.chevron_metrics == ChevronOptions.ALL: + val = max(0.0, d_rel) + unit = "m" if ui_state.is_metric else "ft" + if not ui_state.is_metric: + val *= 3.28084 + text_lines.append(f"{val:.0f} {unit}") + + # Speed + if ui_state.chevron_metrics == ChevronOptions.SPEED_ONLY or ui_state.chevron_metrics == ChevronOptions.ALL: + multiplier = CV.MS_TO_KPH if ui_state.is_metric else CV.MS_TO_MPH + val = max(0.0, (v_rel + v_ego) * multiplier) + unit = "km/h" if ui_state.is_metric else "mph" + text_lines.append(f"{val:.0f} {unit}") + + # Time to collision + if ui_state.chevron_metrics == ChevronOptions.TTC_ONLY or ui_state.chevron_metrics == ChevronOptions.ALL: + val = (d_rel / v_ego) if (d_rel > 0 and v_ego > 0) else 0.0 + ttc_text = f"{val:.1f} s" if (0 < val < 200) else "---" + text_lines.append(ttc_text) + + return text_lines + + def _render_text_lines(self, text_lines: list[str], chevron_x: float, chevron_y: float, + sz: float, rect: rl.Rectangle): + """Render text lines with proper centering and positioning""" + font_size = 40 + line_height = 50 + margin = 20 + + text_y = chevron_y + sz + 15 + total_height = len(text_lines) * line_height + + # Adjust Y position if text would go off screen + if text_y + total_height > rect.height - margin: + y_max = min(chevron_y, rect.height - margin) + text_y = y_max - 15 - total_height + text_y = max(margin, text_y) + + alpha = int(255 * self._lead_status_alpha) + text_color = rl.Color(255, 255, 255, alpha) + shadow_color = rl.Color(0, 0, 0, int(200 * self._lead_status_alpha)) + + for i, line in enumerate(text_lines): + y = int(text_y + (i * line_height)) + if y + line_height > rect.height - margin: + break + + # Measure actual text width for proper centering + text_size = measure_text_cached(self._font, line, font_size, 0) + text_width = text_size.x + + # Center the text horizontally on the chevron + x = int(chevron_x - text_width / 2) + x = int(np.clip(x, margin, rect.width - text_width - margin)) + + # Draw shadow + rl.draw_text_ex(self._font, line, rl.Vector2(x + 2, y + 2), font_size, 0, shadow_color) + # Draw text + rl.draw_text_ex(self._font, line, rl.Vector2(x, y), font_size, 0, text_color) + + def draw_lead_status(self, sm, radar_state, rect, lead_vehicles): + lead_one = radar_state.leadOne + lead_two = radar_state.leadTwo + + has_lead_one = lead_one.status if lead_one else False + has_lead_two = lead_two.status if lead_two else False + + self.update_alpha(has_lead_one or has_lead_two) + + if not self.should_render(): + return + + v_ego = sm['carState'].vEgo + + if has_lead_one and lead_vehicles[0].chevron: + self._draw_lead(lead_one, lead_vehicles[0], v_ego, rect) + + if has_lead_two and lead_vehicles[1].chevron: + d_rel_diff = abs(lead_one.dRel - lead_two.dRel) if has_lead_one else float('inf') + if d_rel_diff > 3.0: + self._draw_lead(lead_two, lead_vehicles[1], v_ego, rect) diff --git a/selfdrive/ui/sunnypilot/onroad/model_renderer.py b/selfdrive/ui/sunnypilot/onroad/model_renderer.py index 214a855fb..5d7899766 100644 --- a/selfdrive/ui/sunnypilot/onroad/model_renderer.py +++ b/selfdrive/ui/sunnypilot/onroad/model_renderer.py @@ -4,9 +4,11 @@ Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. This file is part of sunnypilot and is licensed under the MIT License. See the LICENSE.md file in the root directory for more details. """ +from openpilot.selfdrive.ui.sunnypilot.onroad.chevron_metrics import ChevronMetrics from openpilot.selfdrive.ui.sunnypilot.onroad.rainbow_path import RainbowPath class ModelRendererSP: def __init__(self): self.rainbow_path = RainbowPath() + self.chevron_metrics = ChevronMetrics() diff --git a/selfdrive/ui/sunnypilot/ui_state.py b/selfdrive/ui/sunnypilot/ui_state.py index 2e817090b..fe90e3bdf 100644 --- a/selfdrive/ui/sunnypilot/ui_state.py +++ b/selfdrive/ui/sunnypilot/ui_state.py @@ -69,3 +69,4 @@ class UIStateSP: self.sunnylink_enabled = self.params.get_bool("SunnylinkEnabled") self.developer_ui = self.params.get("DevUIInfo") self.rainbow_path = self.params.get_bool("RainbowMode") + self.chevron_metrics = self.params.get("ChevronInfo") From 452aa675816b2f82e8baba80d9196993ac4218e6 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 20 Dec 2025 15:20:24 -0500 Subject: [PATCH 671/910] DM: fix upstream merge overwrite with `latActive` check (#1594) * Update enabled condition to include latActive * Todo-sp --- selfdrive/monitoring/helpers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/selfdrive/monitoring/helpers.py b/selfdrive/monitoring/helpers.py index 3377ce6c6..002e01ae3 100644 --- a/selfdrive/monitoring/helpers.py +++ b/selfdrive/monitoring/helpers.py @@ -449,7 +449,8 @@ class DriverMonitoring: rpyCalib = [0., 0., 0.] else: highway_speed = sm['carState'].vEgo - enabled = sm['selfdriveState'].enabled + # TODO-SP: unit test to assert both control checks are always present + enabled = sm['selfdriveState'].enabled or sm['carControl'].latActive wrong_gear = sm['carState'].gearShifter not in (car.CarState.GearShifter.drive, car.CarState.GearShifter.low) standstill = sm['carState'].standstill driver_engaged = sm['carState'].steeringPressed or sm['carState'].gasPressed From de975d5af9413a36fcb8c3b28be03b85a4296278 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 20 Dec 2025 15:04:13 -0800 Subject: [PATCH 672/910] card: remove MockCarstate (#36941) --- selfdrive/car/car_specific.py | 16 ---------------- selfdrive/car/card.py | 4 ---- 2 files changed, 20 deletions(-) diff --git a/selfdrive/car/car_specific.py b/selfdrive/car/car_specific.py index 6210983d9..307132e78 100644 --- a/selfdrive/car/car_specific.py +++ b/selfdrive/car/car_specific.py @@ -1,5 +1,4 @@ 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 @@ -11,21 +10,6 @@ EventName = log.OnroadEvent.EventName NetworkLocation = structs.CarParams.NetworkLocation -# TODO: the goal is to abstract this file into the CarState struct and make events generic -class MockCarState: - def __init__(self): - self.sm = messaging.SubMaster(['gpsLocation', 'gpsLocationExternal']) - - def update(self, CS: car.CarState): - self.sm.update(0) - gps_sock = 'gpsLocationExternal' if self.sm.recv_frame['gpsLocationExternal'] > 1 else 'gpsLocation' - - CS.vEgo = self.sm[gps_sock].speed - CS.vEgoRaw = self.sm[gps_sock].speed - - return CS - - BRAND_EXTRA_GEARS = { 'ford': [GearShifter.low, GearShifter.manumatic], 'nissan': [GearShifter.brake], diff --git a/selfdrive/car/card.py b/selfdrive/car/card.py index 27b04ae65..aa0d12e96 100755 --- a/selfdrive/car/card.py +++ b/selfdrive/car/card.py @@ -19,7 +19,6 @@ from opendbc.car.car_helpers import get_car, interfaces from opendbc.car.interfaces import CarInterfaceBase, RadarInterfaceBase 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 REPLAY = "REPLAY" in os.environ @@ -150,7 +149,6 @@ class Car: self.params.put_nonblocking("CarParamsCache", cp_bytes) self.params.put_nonblocking("CarParamsPersistent", cp_bytes) - self.mock_carstate = MockCarState() self.v_cruise_helper = VCruiseHelper(self.CP) self.is_metric = self.params.get_bool("IsMetric") @@ -167,8 +165,6 @@ class Car: # Update carState from CAN CS = self.CI.update(can_list) - if self.CP.brand == 'mock': - CS = self.mock_carstate.update(CS) # Update radar tracks from CAN RD: structs.RadarDataT | None = self.RI.update(can_list) From 67742699ccd76fe864e46758c3aa1a67334fc464 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 20 Dec 2025 15:22:30 -0800 Subject: [PATCH 673/910] card: move drivable gears to opendbc (#36942) * card: move drivable gears to opendbc * it's not none * bump opendbc * mypy --- opendbc_repo | 2 +- selfdrive/car/car_specific.py | 20 ++++---------------- 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/opendbc_repo b/opendbc_repo index 39773a987..4aa7ca972 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 39773a987eaefd680c6befa390d7898945daf2e7 +Subproject commit 4aa7ca972290af6c6bbd314d1975eab46a888b6f diff --git a/selfdrive/car/car_specific.py b/selfdrive/car/car_specific.py index 307132e78..90e4a6986 100644 --- a/selfdrive/car/car_specific.py +++ b/selfdrive/car/car_specific.py @@ -1,5 +1,6 @@ from cereal import car, log from opendbc.car import DT_CTRL, structs +from opendbc.car.car_helpers import interfaces from opendbc.car.interfaces import MAX_CTRL_SPEED from openpilot.selfdrive.selfdrived.events import Events @@ -10,18 +11,6 @@ EventName = log.OnroadEvent.EventName NetworkLocation = structs.CarParams.NetworkLocation -BRAND_EXTRA_GEARS = { - 'ford': [GearShifter.low, GearShifter.manumatic], - 'nissan': [GearShifter.brake], - 'chrysler': [GearShifter.low], - 'honda': [GearShifter.sport], - 'toyota': [GearShifter.sport], - 'gm': [GearShifter.sport, GearShifter.low, GearShifter.eco, GearShifter.manumatic], - 'volkswagen': [GearShifter.eco, GearShifter.sport, GearShifter.manumatic], - 'hyundai': [GearShifter.sport, GearShifter.manumatic] -} - - class CarSpecificEvents: def __init__(self, CP: structs.CarParams): self.CP = CP @@ -32,7 +21,7 @@ class CarSpecificEvents: self.silent_steer_warning = True def update(self, CS: car.CarState, CS_prev: car.CarState, CC: car.CarControl): - extra_gears = BRAND_EXTRA_GEARS.get(self.CP.brand, None) + extra_gears = interfaces[self.CP.carFingerprint].DRIVABLE_GEARS if self.CP.brand in ('body', 'mock'): events = Events() @@ -118,7 +107,7 @@ class CarSpecificEvents: return events - def create_common_events(self, CS: structs.CarState, CS_prev: car.CarState, extra_gears: list | None = None, pcm_enable=True, + def create_common_events(self, CS: structs.CarState, CS_prev: car.CarState, extra_gears: tuple = (), pcm_enable=True, allow_button_cancel=True): events = Events() @@ -126,8 +115,7 @@ class CarSpecificEvents: events.add(EventName.doorOpen) if CS.seatbeltUnlatched: events.add(EventName.seatbeltNotLatched) - if CS.gearShifter != GearShifter.drive and (extra_gears is None or - CS.gearShifter not in extra_gears): + if CS.gearShifter != GearShifter.drive and CS.gearShifter not in extra_gears: events.add(EventName.wrongGear) if CS.gearShifter == GearShifter.reverse: events.add(EventName.reverseGear) From e9a37d99c3d715e1b44d85da5c48c49136a5eb90 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Sun, 21 Dec 2025 08:33:52 +0800 Subject: [PATCH 674/910] Fix incorrect msgq path in prefix.h (#36943) --- common/prefix.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/prefix.h b/common/prefix.h index 2612c05d4..7d0265c25 100644 --- a/common/prefix.h +++ b/common/prefix.h @@ -13,7 +13,7 @@ public: if (prefix.empty()) { prefix = util::random_string(15); } - msgq_path = Path::shm_path() + "/" + prefix; + msgq_path = Path::shm_path() + "/msgq_" + prefix; bool ret = util::create_directories(msgq_path, 0777); assert(ret); setenv("OPENPILOT_PREFIX", prefix.c_str(), 1); From 1a1178140f614cb7fa6538a6d6cfeddba8b35599 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 20 Dec 2025 21:35:51 -0500 Subject: [PATCH 675/910] ui: include MADS enabled state to `engaged` check (#1595) --- selfdrive/ui/ui_state.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/ui_state.py b/selfdrive/ui/ui_state.py index f44dc152b..0e1710c24 100644 --- a/selfdrive/ui/ui_state.py +++ b/selfdrive/ui/ui_state.py @@ -100,7 +100,7 @@ class UIState(UIStateSP): @property def engaged(self) -> bool: - return self.started and self.sm["selfdriveState"].enabled + return self.started and (self.sm["selfdriveState"].enabled or self.sm["selfdriveStateSP"].mads.enabled) def is_onroad(self) -> bool: return self.started From 09c4b933a82aadc2ab1577ad9db394d91fc80fd0 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 20 Dec 2025 22:14:19 -0500 Subject: [PATCH 676/910] ui: capitalize button texts in Vehicle panel (#1597) --- selfdrive/ui/sunnypilot/layouts/settings/vehicle/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/sunnypilot/layouts/settings/vehicle/__init__.py b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/__init__.py index 86d9e6169..0dd12d76f 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/vehicle/__init__.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/__init__.py @@ -23,7 +23,7 @@ class VehicleLayout(Widget): self._current_brand = None self._platform_selector = PlatformSelector(self._update_brand_settings) - self._vehicle_item = ListItemSP(title=self._platform_selector.text, action_item=ButtonAction(text=tr("Select")), + self._vehicle_item = ListItemSP(title=self._platform_selector.text, action_item=ButtonAction(text=tr("SELECT")), callback=self._platform_selector._on_clicked) self._vehicle_item.title_color = self._platform_selector.color self._legend_widget = LegendWidget(self._platform_selector) @@ -42,7 +42,7 @@ class VehicleLayout(Widget): def _update_brand_settings(self): self._vehicle_item._title = self._platform_selector.text self._vehicle_item.title_color = self._platform_selector.color - vehicle_text = tr("Remove") if ui_state.params.get("CarPlatformBundle") else tr("Select") + vehicle_text = tr("REMOVE") if ui_state.params.get("CarPlatformBundle") else tr("SELECT") self._vehicle_item.action_item.set_text(vehicle_text) brand = self.get_brand() From 8904300565b74e775df5ec00b95ca73a71488046 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 20 Dec 2025 22:40:40 -0500 Subject: [PATCH 677/910] Toyota: Enforce Factory Longitudinal Control (#1596) * Toyota: enforce factory longitudinal control support * sunnylink! * bump * bruh --- common/params_keys.h | 1 + opendbc_repo | 2 +- .../layouts/settings/vehicle/brands/toyota.py | 44 +++++++++++++++++++ sunnypilot/selfdrive/car/interfaces.py | 7 ++- sunnypilot/sunnylink/params_metadata.json | 4 ++ 5 files changed, 56 insertions(+), 2 deletions(-) diff --git a/common/params_keys.h b/common/params_keys.h index 1b06711a9..7cfdf7540 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -213,6 +213,7 @@ inline static std::unordered_map keys = { {"SubaruStopAndGo", {PERSISTENT | BACKUP, BOOL, "0"}}, {"SubaruStopAndGoManualParkingBrake", {PERSISTENT | BACKUP, BOOL, "0"}}, {"TeslaCoopSteering", {PERSISTENT | BACKUP, BOOL, "0"}}, + {"ToyotaEnforceStockLongitudinal", {PERSISTENT | BACKUP, BOOL, "0"}}, {"DynamicExperimentalControl", {PERSISTENT | BACKUP, BOOL, "0"}}, {"BlindSpot", {PERSISTENT | BACKUP, BOOL, "0"}}, diff --git a/opendbc_repo b/opendbc_repo index a76d28a23..74ac67850 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit a76d28a231dd8a3de11ed47db2d185d3852c6925 +Subproject commit 74ac6785011b2861b822651f51d0cd2f01ce79d2 diff --git a/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/toyota.py b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/toyota.py index e061a8a22..ac3d04f36 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/toyota.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/toyota.py @@ -5,11 +5,55 @@ This file is part of sunnypilot and is licensed under the MIT License. See the LICENSE.md file in the root directory for more details. """ from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.base import BrandSettings +from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.lib.multilang import tr, tr_noop +from openpilot.system.ui.widgets import DialogResult +from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog +from openpilot.system.ui.sunnypilot.widgets.list_view import toggle_item_sp + + +DESCRIPTIONS = { + 'enforce_stock_longitudinal': tr_noop( + 'sunnypilot will not take over control of gas and brakes. Factory Toyota longitudinal control will be used.' + ), +} class ToyotaSettings(BrandSettings): def __init__(self): super().__init__() + self.enforce_stock_longitudinal = toggle_item_sp( + lambda: tr("Enforce Factory Longitudinal Control"), + description=lambda: tr(DESCRIPTIONS["enforce_stock_longitudinal"]), + initial_state=ui_state.params.get_bool("ToyotaEnforceStockLongitudinal"), + callback=self._on_enable_enforce_stock_longitudinal, + enabled=lambda: not ui_state.engaged, + ) + + self.items = [self.enforce_stock_longitudinal, ] + + def _on_enable_enforce_stock_longitudinal(self, state: bool): + if state: + def confirm_callback(result: int): + if result == DialogResult.CONFIRM: + ui_state.params.put_bool("ToyotaEnforceStockLongitudinal", True) + if ui_state.params.get_bool("AlphaLongitudinalEnabled"): + ui_state.params.put_bool("AlphaLongitudinalEnabled", False) + ui_state.params.put_bool("OnroadCycleRequested", True) + else: + self.enforce_stock_longitudinal.action_item.set_state(False) + + content = (f"

{self.enforce_stock_longitudinal.title}


" + + f"

{self.enforce_stock_longitudinal.description}

") + + dlg = ConfirmDialog(content, tr("Enable"), rich=True) + gui_app.set_modal_overlay(dlg, callback=confirm_callback) + + else: + ui_state.params.put_bool("ToyotaEnforceStockLongitudinal", False) + ui_state.params.put_bool("OnroadCycleRequested", True) + def update_settings(self): pass diff --git a/sunnypilot/selfdrive/car/interfaces.py b/sunnypilot/selfdrive/car/interfaces.py index 83114ac55..a93f5724b 100644 --- a/sunnypilot/selfdrive/car/interfaces.py +++ b/sunnypilot/selfdrive/car/interfaces.py @@ -114,7 +114,7 @@ def initialize_params(params) -> list[dict[str, Any]]: # hyundai keys.extend([ - "HyundaiLongitudinalTuning" + "HyundaiLongitudinalTuning", ]) # subaru @@ -128,4 +128,9 @@ def initialize_params(params) -> list[dict[str, Any]]: "TeslaCoopSteering", ]) + # toyota + keys.extend([ + "ToyotaEnforceStockLongitudinal", + ]) + return [{k: params.get(k, return_default=True)} for k in keys] diff --git a/sunnypilot/sunnylink/params_metadata.json b/sunnypilot/sunnylink/params_metadata.json index 707e0620f..bbefc3d0c 100644 --- a/sunnypilot/sunnylink/params_metadata.json +++ b/sunnypilot/sunnylink/params_metadata.json @@ -1037,6 +1037,10 @@ "max": 5.0, "step": 0.1 }, + "ToyotaEnforceStockLongitudinal": { + "title": "Toyota: Enforce Factory Longitudinal Control", + "description": "When enabled, sunnypilot will not take over control of gas and brakes. Factory Toyota longitudinal control will be used." + }, "TrainingVersion": { "title": "Training Version", "description": "" From 6c6be573c7460a6ece20a81ff7043a1831565a9f Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 20 Dec 2025 22:50:58 -0500 Subject: [PATCH 678/910] ui: `LineSeparatorSP` (#1598) --- system/ui/sunnypilot/widgets/list_view.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/system/ui/sunnypilot/widgets/list_view.py b/system/ui/sunnypilot/widgets/list_view.py index 2d7239ae6..cf69e9292 100644 --- a/system/ui/sunnypilot/widgets/list_view.py +++ b/system/ui/sunnypilot/widgets/list_view.py @@ -15,6 +15,7 @@ from openpilot.system.ui.widgets.button import Button, ButtonStyle from openpilot.system.ui.widgets.label import gui_label from openpilot.system.ui.widgets.list_view import ListItem, ToggleAction, ItemAction, MultipleButtonAction, ButtonAction, \ _resolve_value, BUTTON_WIDTH, BUTTON_HEIGHT, TEXT_PADDING +from openpilot.system.ui.widgets.scroller_tici import LineSeparator, LINE_COLOR, LINE_PADDING from openpilot.system.ui.sunnypilot.lib.styles import style from openpilot.system.ui.sunnypilot.widgets.option_control import OptionControlSP, LABEL_WIDTH @@ -312,3 +313,15 @@ def button_item_sp(title: str | Callable[[], str], button_text: str | Callable[[ callback: Callable | None = None, enabled: bool | Callable[[], bool] = True) -> ListItemSP: action = ButtonActionSP(text=button_text, enabled=enabled) return ListItemSP(title=title, description=description, action_item=action, callback=callback) + + +class LineSeparatorSP(LineSeparator): + def __init__(self, height: int = 1): + super().__init__() + self._rect = rl.Rectangle(0, 0, 0, height) + + def _render(self, _): + line_y = int(self._rect.y + self._rect.height // 2) + rl.draw_line(int(self._rect.x) + LINE_PADDING, line_y, + int(self._rect.x + self._rect.width) - LINE_PADDING, line_y, + LINE_COLOR) From 9c7c84bd038de56cbd25f9630bfcaa72b695ba8e Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 20 Dec 2025 23:30:30 -0500 Subject: [PATCH 679/910] ui: simplify non-inline action button positioning in `ListViewSP` (#1599) ui: update non-inline action button positioning in `ListViewSP` --- system/ui/sunnypilot/widgets/list_view.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/system/ui/sunnypilot/widgets/list_view.py b/system/ui/sunnypilot/widgets/list_view.py index cf69e9292..6ba41f143 100644 --- a/system/ui/sunnypilot/widgets/list_view.py +++ b/system/ui/sunnypilot/widgets/list_view.py @@ -180,13 +180,8 @@ class ListItemSP(ListItem): return rl.Rectangle(0, 0, 0, 0) if not self.inline: - has_description = bool(self.description) and self.description_visible - - if has_description: - action_y = item_rect.y + self._text_size.y + style.ITEM_PADDING * 3 - else: - action_y = item_rect.y + item_rect.height - style.BUTTON_HEIGHT - style.ITEM_PADDING * 1.5 - + text_size = measure_text_cached(self._font, self.title, style.ITEM_TEXT_FONT_SIZE) + action_y = item_rect.y + text_size.y + style.ITEM_PADDING * 3 return rl.Rectangle(item_rect.x + style.ITEM_PADDING, action_y, item_rect.width - (style.ITEM_PADDING * 2), style.BUTTON_HEIGHT) right_width = self.action_item.get_width_hint() From 8728c7dde38be4a10f3044312e12195ebe4d57f4 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 21 Dec 2025 15:42:53 -0800 Subject: [PATCH 680/910] killing car_specific.py, part 3 (#36946) * mv that * lil more * todo * cleanup --- selfdrive/car/car_specific.py | 34 ++++++++++++---------------------- 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/selfdrive/car/car_specific.py b/selfdrive/car/car_specific.py index 90e4a6986..cdeeebdcd 100644 --- a/selfdrive/car/car_specific.py +++ b/selfdrive/car/car_specific.py @@ -21,14 +21,12 @@ class CarSpecificEvents: self.silent_steer_warning = True def update(self, CS: car.CarState, CS_prev: car.CarState, CC: car.CarControl): - extra_gears = interfaces[self.CP.carFingerprint].DRIVABLE_GEARS - if self.CP.brand in ('body', 'mock'): - events = Events() + return Events() - elif self.CP.brand == 'chrysler': - events = self.create_common_events(CS, CS_prev, extra_gears=extra_gears) + events = self.create_common_events(CS, CS_prev) + if self.CP.brand == 'chrysler': # Low speed steer alert hysteresis logic if self.CP.minSteerSpeed > 0. and CS.vEgo < (self.CP.minSteerSpeed + 0.5): self.low_speed_alert = True @@ -38,8 +36,6 @@ class CarSpecificEvents: events.add(EventName.belowSteerSpeed) elif self.CP.brand == 'honda': - events = self.create_common_events(CS, CS_prev, extra_gears=extra_gears, pcm_enable=False) - if self.CP.pcmCruise and CS.vEgo < self.CP.minEnableSpeed: events.add(EventName.belowEngageSpeed) @@ -60,8 +56,6 @@ class CarSpecificEvents: elif self.CP.brand == 'toyota': # TODO: when we check for unexpected disengagement, check gear not S1, S2, S3 - events = self.create_common_events(CS, CS_prev, extra_gears=extra_gears) - if self.CP.openpilotLongitudinalControl: # Only can leave standstill when planner wants to move if CS.cruiseState.standstill and not CS.brakePressed and CC.cruiseControl.resume: @@ -76,8 +70,6 @@ class CarSpecificEvents: events.add(EventName.manualRestart) elif self.CP.brand == 'gm': - events = self.create_common_events(CS, CS_prev, extra_gears=extra_gears, pcm_enable=self.CP.pcmCruise) - # Enabling at a standstill with brake is allowed # TODO: verify 17 Volt can enable for the first time at a stop and allow for all GMs if CS.vEgo < self.CP.minEnableSpeed and not (CS.standstill and CS.brake >= 20 and @@ -87,8 +79,6 @@ class CarSpecificEvents: events.add(EventName.resumeRequired) elif self.CP.brand == 'volkswagen': - events = self.create_common_events(CS, CS_prev, extra_gears=extra_gears, pcm_enable=self.CP.pcmCruise) - if self.CP.openpilotLongitudinalControl: if CS.vEgo < self.CP.minEnableSpeed + 0.5: events.add(EventName.belowEngageSpeed) @@ -99,23 +89,23 @@ class CarSpecificEvents: # if CC.eps_timer_soft_disable_alert: # type: ignore[attr-defined] # events.add(EventName.steerTimeLimit) - elif self.CP.brand == 'hyundai': - events = self.create_common_events(CS, CS_prev, extra_gears=extra_gears, pcm_enable=self.CP.pcmCruise, allow_button_cancel=False) - - else: - events = self.create_common_events(CS, CS_prev, extra_gears=extra_gears) - return events - def create_common_events(self, CS: structs.CarState, CS_prev: car.CarState, extra_gears: tuple = (), pcm_enable=True, - allow_button_cancel=True): + def create_common_events(self, CS: structs.CarState, CS_prev: car.CarState): events = Events() + CI = interfaces[self.CP.carFingerprint] + # TODO: cleanup the honda-specific logic + pcm_enable = self.CP.pcmCruise and self.CP.brand != 'honda' + # TODO: on some hyundai cars, the cancel button is also the pause/resume button, + # so only use it for cancel when running openpilot longitudinal + allow_button_cancel = self.CP.brand != 'hyundai' + if CS.doorOpen: events.add(EventName.doorOpen) if CS.seatbeltUnlatched: events.add(EventName.seatbeltNotLatched) - if CS.gearShifter != GearShifter.drive and CS.gearShifter not in extra_gears: + if CS.gearShifter != GearShifter.drive and CS.gearShifter not in CI.DRIVABLE_GEARS: events.add(EventName.wrongGear) if CS.gearShifter == GearShifter.reverse: events.add(EventName.reverseGear) From 31ec0096e46ad2ac310d40a5f99ecf22243511ad Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 21 Dec 2025 16:21:35 -0800 Subject: [PATCH 681/910] ui: fix raylib init spam (#36947) * lil more * not none * don't need that either * too spammy now? --- Jenkinsfile | 2 +- system/ui/lib/application.py | 9 +++++---- system/ui/lib/multilang.py | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 73fa74c1c..c095eda8a 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -22,7 +22,7 @@ shopt -s huponexit # kill all child processes when the shell exits export CI=1 export PYTHONWARNINGS=error -export LOGPRINT=debug +#export LOGPRINT=debug # this has gotten too spammy... export TEST_DIR=${env.TEST_DIR} export SOURCE_DIR=${env.SOURCE_DIR} export GIT_BRANCH=${env.GIT_BRANCH} diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 501a7ff37..04cd37af3 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -192,6 +192,8 @@ class MouseState: class GuiApplication: def __init__(self, width: int | None = None, height: int | None = None): + self._set_log_callback() + self._fonts: dict[FontWeight, rl.Font] = {} self._width = width if width is not None else GuiApplication._default_width() self._height = height if height is not None else GuiApplication._default_height() @@ -215,7 +217,6 @@ class GuiApplication: self._last_fps_log_time: float = time.monotonic() self._frame = 0 self._window_close_requested = False - self._trace_log_callback = None self._modal_overlay = ModalOverlay() self._modal_overlay_shown = False self._modal_overlay_tick: Callable[[], None] | None = None @@ -260,9 +261,6 @@ class GuiApplication: signal.signal(signal.SIGINT, _close) atexit.register(self.close) - self._set_log_callback() - rl.set_trace_log_level(rl.TraceLogLevel.LOG_WARNING) - flags = rl.ConfigFlags.FLAG_MSAA_4X_HINT if ENABLE_VSYNC: flags |= rl.ConfigFlags.FLAG_VSYNC_HINT @@ -640,6 +638,9 @@ class GuiApplication: else: cloudlog.error(f"raylib: Unknown level {log_level}: {text_str}") + # ensure we get all the logs forwarded to us + rl.set_trace_log_level(rl.TraceLogLevel.LOG_DEBUG) + # Store callback reference self._trace_log_callback = trace_log_callback rl.set_trace_log_callback(self._trace_log_callback) diff --git a/system/ui/lib/multilang.py b/system/ui/lib/multilang.py index 9b10a8bdc..70de1e3d5 100644 --- a/system/ui/lib/multilang.py +++ b/system/ui/lib/multilang.py @@ -48,7 +48,7 @@ class Multilang: translation = gettext.GNUTranslations(fh) translation.install() self._translation = translation - cloudlog.warning(f"Loaded translations for language: {self._language}") + cloudlog.debug(f"Loaded translations for language: {self._language}") except FileNotFoundError: cloudlog.error(f"No translation file found for language: {self._language}, using default.") gettext.install('app') From 29a2f576f5929b2efe6761e3996a926e3b9e62d5 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 21 Dec 2025 16:49:15 -0800 Subject: [PATCH 682/910] unpin raylib-python-cffi (#36948) --- pyproject.toml | 4 ++-- uv.lock | 38 ++++++++++++++++++++++---------------- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d5dc95e1b..fc80488c6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ dependencies = [ # logging "pyzmq", "sentry-sdk", - "xattr", # used in place of 'os.getxattr' for macos compatibility + "xattr", # used in place of 'os.getxattr' for macOS compatibility # athena "PyJWT", @@ -72,7 +72,7 @@ dependencies = [ "zstandard", # ui - "raylib < 5.5.0.3", # TODO: unpin when they fix https://github.com/electronstudio/raylib-python-cffi/issues/186 + "raylib > 5.5.0.3", "qrcode", "mapbox-earcut", ] diff --git a/uv.lock b/uv.lock index b179517e0..53cb108c6 100644 --- a/uv.lock +++ b/uv.lock @@ -1463,7 +1463,7 @@ requires-dist = [ { name = "pywinctl", marker = "extra == 'dev'" }, { name = "pyzmq" }, { name = "qrcode" }, - { name = "raylib", specifier = "<5.5.0.3" }, + { name = "raylib", specifier = ">5.5.0.3" }, { name = "requests" }, { name = "ruff", marker = "extra == 'testing'" }, { name = "scons" }, @@ -4639,26 +4639,32 @@ wheels = [ [[package]] name = "raylib" -version = "5.5.0.2" +version = "5.5.0.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8c/35/9bf3a2af73c55fd4310dcaec4f997c739888e0db9b4dfac71b7680810852/raylib-5.5.0.2.tar.gz", hash = "sha256:83c108ae3b4af40b53c93d1de2afbe309e986dd5efeb280ebe2e61c79956edb0", size = 181172, upload-time = "2024-11-26T11:12:02.791Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7c/4b/858958762c075c54058ee3b0771838fd505ca908871e6a0397b01086e526/raylib-5.5.0.4.tar.gz", hash = "sha256:996506e8a533cd7a6a3ef6c44ec11f9d6936698f2c394a991af8022be33079a0", size = 184413, upload-time = "2025-12-11T15:32:12.465Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/c4/ce21721b474eb8f65379f7315b382ccfe1d5df728eea4dcf287b874e7461/raylib-5.5.0.2-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:37eb0ec97fc6b08f989489a50e09b5dde519e1bb8eb17e4033ac82227b0e5eda", size = 1703742, upload-time = "2024-11-26T11:09:31.115Z" }, - { url = "https://files.pythonhosted.org/packages/23/61/138e305c82549869bb8cd41abe75571559eafbeab6aed1ce7d8fbe3ffd58/raylib-5.5.0.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:bb9e506ecd3dbec6dba868eb036269837a8bde68220690842c3238239ee887ef", size = 1247449, upload-time = "2024-11-26T11:09:34.182Z" }, - { url = "https://files.pythonhosted.org/packages/85/e0/dc638c42d1a505f0992263d48e1434d82c21afdf376b06f549d2e281dfd4/raylib-5.5.0.2-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:70aa8bed67875a8cf25191f35263ef92d646bdfcb1f507915c81562a321f4931", size = 2184315, upload-time = "2024-11-26T11:09:36.715Z" }, - { url = "https://files.pythonhosted.org/packages/c9/1a/49db57283a28fdc1ff0e4604911b7fff085128c2ac8bdd9efa8c5c47439d/raylib-5.5.0.2-cp311-cp311-manylinux2014_x86_64.whl", hash = "sha256:0365e8c578f72f598795d9377fc70342f0d62aa193c2f304ca048b3e28866752", size = 2278139, upload-time = "2024-11-26T11:09:39.475Z" }, - { url = "https://files.pythonhosted.org/packages/f0/8a/e1a690ab6889d4cb67346a2d32bad8b8e8b0f85ec826b00f76b0ad7e6ad6/raylib-5.5.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:5219be70e7fca03e9c4fddebf7e60e885d77137125c7a13f3800a947f8562a13", size = 1693944, upload-time = "2024-11-26T11:09:41.596Z" }, - { url = "https://files.pythonhosted.org/packages/69/2b/49bfa6833ad74ddf318d54ecafe73d535f583531469ecbd5b009d79667d1/raylib-5.5.0.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5233c529d9a0cfd469d88239c2182e55c5215a7755d83cc3d611148d3b9c9e67", size = 1706157, upload-time = "2024-11-26T11:09:43.6Z" }, - { url = "https://files.pythonhosted.org/packages/58/9c/8a3f4de0c81ad1228bf26410cfe3ecdc73011c59f18e542685ffc92c0120/raylib-5.5.0.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:1f76204ffbc492722b571b12dbdc0dca89b10da76ddf48c12a3968d2db061dff", size = 1248027, upload-time = "2025-01-04T20:21:46.269Z" }, - { url = "https://files.pythonhosted.org/packages/7f/16/63baf1aae94832b9f5d15cafcee67bb6dd07a20cf64d40bac09903b79274/raylib-5.5.0.2-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:f8cc2e39f1d6b29211a97ec0ac818a5b04c43a40e747e4b4622101d48c711f9e", size = 2195374, upload-time = "2024-11-26T11:09:46.114Z" }, - { url = "https://files.pythonhosted.org/packages/70/bd/61a006b4e3ce4a6ca974cb0ceeb19f3816815ebabac650e9bf82767e65f6/raylib-5.5.0.2-cp312-cp312-manylinux2014_x86_64.whl", hash = "sha256:f12da578a28da7f48481f46323e5aab8dd25461982b0e80d045782d6e69649f5", size = 2299593, upload-time = "2024-11-26T11:09:48.963Z" }, - { url = "https://files.pythonhosted.org/packages/f4/4f/59d554cc495bea8235b17cebfc76ed57aaa602c613b870159e31282fd4c1/raylib-5.5.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:b40234bbad9523fd6a2049640c76a98b4d6f0b8f4bd19bd33eaee55faf5e050d", size = 1696780, upload-time = "2024-11-26T11:09:50.787Z" }, - { url = "https://files.pythonhosted.org/packages/4a/22/2e02e3738ad041f5ec2830aecdfab411fc2960bfc3400e03b477284bfaf7/raylib-5.5.0.2-pp311-pypy311_pp73-macosx_10_13_x86_64.whl", hash = "sha256:bc45fe1c0aac50aa319a9a66d44bb2bd0dcd038a44d95978191ae7bfeb4a06d8", size = 1216231, upload-time = "2025-02-12T04:21:59.38Z" }, - { url = "https://files.pythonhosted.org/packages/fe/7d/b29afedc4a706b12143f74f322cb32ad5a6f43e56aaca2a9fb89b0d94eee/raylib-5.5.0.2-pp311-pypy311_pp73-manylinux2014_x86_64.whl", hash = "sha256:2242fd6079da5137e9863a447224f800adef6386ca8f59013a5d62cc5cadab2b", size = 1394928, upload-time = "2025-02-12T04:22:03.021Z" }, - { url = "https://files.pythonhosted.org/packages/b6/fa/2daf36d78078c6871b241168a36156169cfc8ea089faba5abe8edad304be/raylib-5.5.0.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:e475a40764c9f83f9e66406bd86d85587eb923329a61ade463c3c59e1e880b16", size = 1564224, upload-time = "2025-02-12T04:22:05.911Z" }, + { url = "https://files.pythonhosted.org/packages/c2/14/98a78b819d7374dab309525ce45cd591d0d62db7f6ed2d5ed32b8f55d62b/raylib-5.5.0.4-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:09717ed32c9ec1c574370e2e2d30e9bc13876f7e2f2dd6e04dc366dae23e0994", size = 1632797, upload-time = "2025-12-11T15:27:15.429Z" }, + { url = "https://files.pythonhosted.org/packages/1e/f4/ec949f45274cf266875b30b67f8cb7243ecced05080cec54bf65ec73a8b2/raylib-5.5.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cef7b0e238eafc80a3be7e3c656a3ddc94cc523790758b7130df1957ba4ad4ad", size = 1550301, upload-time = "2025-12-11T15:27:17.429Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5a/8f60d367147019acef342746f20121b2341ec6596acd5c7941cb36bda02e/raylib-5.5.0.4-cp311-cp311-manylinux2010_i686.manylinux_2_12_i686.whl", hash = "sha256:bdaa119b767f380caf6dd4f9d42ab3bf8596d8fb98737d2951b36924a5a83ac0", size = 2036797, upload-time = "2025-12-11T15:27:20.044Z" }, + { url = "https://files.pythonhosted.org/packages/dd/ad/97dd93c389263c61a3057065f0f70db5fdc3c5768fa383a9b3e989ddb6a7/raylib-5.5.0.4-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:6a5cdeeb803d081342961eb1f7c4161af27e951d9ecf2b56d469d5730fcc6213", size = 2188009, upload-time = "2025-12-11T18:50:05.612Z" }, + { url = "https://files.pythonhosted.org/packages/42/6a/55be04012f3459842389689326910204f985cffcb8989a92475221f5660a/raylib-5.5.0.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4067fa8a6ed3eb78a1162fc2d40ce8c26c26c5ee544019d1902accf21ec22add", size = 2187633, upload-time = "2025-12-11T15:27:22.345Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b7/702ea311fcb1b82064a1c50f32fe86fce1f21caa39c54ca1d598a9862444/raylib-5.5.0.4-cp311-cp311-manylinux_2_35_aarch64.whl", hash = "sha256:faa403252fd0a692dd2f37d9dd4e79fa293ec05deae8a3b086b063cd725a48f9", size = 2247484, upload-time = "2025-12-11T15:27:24.718Z" }, + { url = "https://files.pythonhosted.org/packages/6b/18/b69d9ad9f4064785ad29c73672d40b36c59c3b3efd1dee264cdff4b48bf6/raylib-5.5.0.4-cp311-cp311-win32.whl", hash = "sha256:f01a769bb0797ab4f6e1efc950d5d8aca53548e97da7f527190a1ca5f671c389", size = 1456775, upload-time = "2025-12-11T15:27:26.776Z" }, + { url = "https://files.pythonhosted.org/packages/1a/7a/4025d9ceeee8e3ae4748b0f6c356c5ce97628bd5da8a056b6782c87f7e65/raylib-5.5.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:34771dea34a30fa4657f35b344d5ebf9eb11d9b62b23d9349742db5c5f3992bd", size = 1705555, upload-time = "2025-12-11T15:27:28.888Z" }, + { url = "https://files.pythonhosted.org/packages/95/21/9117d7013997a65f6d51c6f56145b2c583eeba8f7c1af71a60776eaae9b9/raylib-5.5.0.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:31f64f71e42fed10e8f3629028c9f5700906e0e573b915cfc2244d7a3f3b2ed9", size = 1635486, upload-time = "2025-12-11T15:27:31.05Z" }, + { url = "https://files.pythonhosted.org/packages/1c/a3/e55039c8f49856c5a194f2b81f27ca6ba2d5900024f09435587e177bfaf2/raylib-5.5.0.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:80bfa053e765d47a9f58d59e321a999184b5a5190e369dd015c12fcfd08d6217", size = 1554132, upload-time = "2025-12-11T15:27:33.291Z" }, + { url = "https://files.pythonhosted.org/packages/58/1c/86bee75ecaa577214da16b374f8de70b45885452703f622c63e06baa0b8e/raylib-5.5.0.4-cp312-cp312-manylinux2010_i686.manylinux_2_12_i686.whl", hash = "sha256:033240c61c1a1fc06fecff747a183671431a4ce63a0c8aafec59217845f86888", size = 2039888, upload-time = "2025-12-11T15:27:36.059Z" }, + { url = "https://files.pythonhosted.org/packages/fb/f9/00763899bb8a178a927b5dda90aca692c80ff6cec5f51e6fee88db3f45c2/raylib-5.5.0.4-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:ba87ca50c5748cab75de37a991b7f3f836ce500efbb2d737a923a5f464169088", size = 2198926, upload-time = "2025-12-11T18:50:08.813Z" }, + { url = "https://files.pythonhosted.org/packages/6b/e9/0123385e369904335985ebd59157f7a10c89c3a706dffcf6dace863a1fa2/raylib-5.5.0.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:788830bc371ce067c4930ff46a1b6eca0c9cf27bac88f81b035e4b73cc6bf197", size = 2205629, upload-time = "2025-12-11T15:27:39.491Z" }, + { url = "https://files.pythonhosted.org/packages/ae/f1/a9bde00b01956bbccc82c9112c8a6a64d50d44d7e4752c04dc59e59bde7e/raylib-5.5.0.4-cp312-cp312-manylinux_2_35_aarch64.whl", hash = "sha256:fb80c638f74a3a59af6a702078da3152a0013b959a225fc76cc6cc8fd0d91e36", size = 2259080, upload-time = "2025-12-11T15:27:41.862Z" }, + { url = "https://files.pythonhosted.org/packages/5c/fa/c25087b39d2db2d833a52b4056ae62db74e64b4be677f816e0b368e65453/raylib-5.5.0.4-cp312-cp312-win32.whl", hash = "sha256:e09f395035484337776c90e6c9955c5876b988db7e13168dcadb6ed11974f8ee", size = 1457266, upload-time = "2025-12-11T15:27:43.798Z" }, + { url = "https://files.pythonhosted.org/packages/2c/66/a307e61c953ace906ba68ba1174ed8f1e90e68d5fc3e3af9fb7dc46d68d1/raylib-5.5.0.4-cp312-cp312-win_amd64.whl", hash = "sha256:553043a050a31f2ef072f26d3a70373f838a04733f7c5b26a4e9ee3f8caf06ec", size = 1708354, upload-time = "2025-12-11T15:27:45.979Z" }, + { url = "https://files.pythonhosted.org/packages/e8/ba/fee7e6ae0be850f6581d4084ea97825b7895c8866fa8b2df347d408c8293/raylib-5.5.0.4-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c318357ce721c62a6848b6d84b26574cd77267e5758cfa2dbc01d4deb2a2b0b8", size = 1211520, upload-time = "2025-12-11T15:28:30.266Z" }, + { url = "https://files.pythonhosted.org/packages/80/a0/847066c6d824f535068112ed362d41c499f9a4aca52b82b74d9dfb1bdfc7/raylib-5.5.0.4-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:82a0ea2859d04f3b5b441910881ec48789484463856168fa8f35c7165e11d44c", size = 1433828, upload-time = "2025-12-11T15:28:32.204Z" }, + { url = "https://files.pythonhosted.org/packages/40/c6/a2cfb01d63246602ce49111f08d8716e1c7c2994efe4e14d87450176393c/raylib-5.5.0.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:871e77547cd3f78d98a47bef491821cd25c879b3b3b79f1973d8fb3f8841cdfb", size = 1572456, upload-time = "2025-12-11T15:28:34.333Z" }, ] [[package]] From c8eed435383147fc18b0627d87a5b9c56ae06ba3 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 21 Dec 2025 17:02:39 -0800 Subject: [PATCH 683/910] misc system/hardware/ cleanup (#36949) * misc system/hardware/ cleanup * lil more * pc is kinda base * lil more * lil more * lil more * int * lil more? --- system/hardware/.gitignore | 1 - system/hardware/base.py | 66 ++++++----------- system/hardware/fan_controller.py | 29 ++------ system/hardware/hardwared.py | 13 +--- system/hardware/pc/hardware.h | 1 - system/hardware/pc/hardware.py | 70 +----------------- system/hardware/tests/test_fan_controller.py | 6 +- system/hardware/tici/amplifier.py | 78 +++++++------------- system/hardware/tici/hardware.py | 6 +- system/hardware/tici/tests/test_amplifier.py | 5 +- 10 files changed, 71 insertions(+), 204 deletions(-) delete mode 100644 system/hardware/.gitignore diff --git a/system/hardware/.gitignore b/system/hardware/.gitignore deleted file mode 100644 index 980f09abf..000000000 --- a/system/hardware/.gitignore +++ /dev/null @@ -1 +0,0 @@ -eon/rat diff --git a/system/hardware/base.py b/system/hardware/base.py index 17d0ec161..05e8eb015 100644 --- a/system/hardware/base.py +++ b/system/hardware/base.py @@ -5,6 +5,7 @@ from dataclasses import dataclass, fields from cereal import log NetworkType = log.DeviceState.NetworkType +NetworkStrength = log.DeviceState.NetworkStrength class LPAError(RuntimeError): pass @@ -114,68 +115,57 @@ class HardwareBase(ABC): def booted(self) -> bool: return True - @abstractmethod def reboot(self, reason=None): - pass + print("REBOOT!") - @abstractmethod def uninstall(self): - pass + print("uninstall") - @abstractmethod def get_os_version(self): - pass + return None @abstractmethod def get_device_type(self): pass - @abstractmethod def get_imei(self, slot) -> str: - pass + return "" - @abstractmethod def get_serial(self): - pass + return "" - @abstractmethod def get_network_info(self): - pass + return None - @abstractmethod def get_network_type(self): - pass + return NetworkType.none - @abstractmethod def get_sim_info(self): - pass + return { + 'sim_id': '', + 'mcc_mnc': None, + 'network_type': ["Unknown"], + 'sim_state': ["ABSENT"], + 'data_connected': False + } - @abstractmethod def get_sim_lpa(self) -> LPABase: - pass + raise NotImplementedError("SIM LPA not available") - @abstractmethod def get_network_strength(self, network_type): - pass + return NetworkStrength.unknown def get_network_metered(self, network_type) -> bool: return network_type not in (NetworkType.none, NetworkType.wifi, NetworkType.ethernet) - @staticmethod - def set_bandwidth_limit(upload_speed_kbps: int, download_speed_kbps: int) -> None: - pass - - @abstractmethod def get_current_power_draw(self): - pass + return 0 - @abstractmethod def get_som_power_draw(self): - pass + return 0 - @abstractmethod def shutdown(self): - pass + print("SHUTDOWN!") def get_thermal_config(self): return ThermalConfig() @@ -183,31 +173,24 @@ class HardwareBase(ABC): def set_display_power(self, on: bool): pass - @abstractmethod def set_screen_brightness(self, percentage): pass - @abstractmethod def get_screen_brightness(self): - pass + return 0 - @abstractmethod def set_power_save(self, powersave_enabled): pass - @abstractmethod def get_gpu_usage_percent(self): - pass + return 0 def get_modem_version(self): return None - @abstractmethod def get_modem_temperatures(self): - pass + return [] - - @abstractmethod def initialize_hardware(self): pass @@ -217,9 +200,8 @@ class HardwareBase(ABC): def reboot_modem(self): pass - @abstractmethod def get_networks(self): - pass + return None def has_internal_panda(self) -> bool: return False diff --git a/system/hardware/fan_controller.py b/system/hardware/fan_controller.py index 365688429..b2140d33d 100755 --- a/system/hardware/fan_controller.py +++ b/system/hardware/fan_controller.py @@ -1,24 +1,13 @@ #!/usr/bin/env python3 import numpy as np -from abc import ABC, abstractmethod -from openpilot.common.realtime import DT_HW -from openpilot.common.swaglog import cloudlog from openpilot.common.pid import PIDController -class BaseFanController(ABC): - @abstractmethod - def update(self, cur_temp: float, ignition: bool) -> int: - pass - - -class TiciFanController(BaseFanController): - def __init__(self) -> None: - super().__init__() - cloudlog.info("Setting up TICI fan handler") +class FanController: + def __init__(self, rate: int) -> None: self.last_ignition = False - self.controller = PIDController(k_p=0, k_i=4e-3, rate=(1 / DT_HW)) + self.controller = PIDController(k_p=0, k_i=4e-3, rate=rate) def update(self, cur_temp: float, ignition: bool) -> int: self.controller.pos_limit = 100 if ignition else 30 @@ -26,13 +15,9 @@ class TiciFanController(BaseFanController): if ignition != self.last_ignition: self.controller.reset() - - error = cur_temp - 75 - fan_pwr_out = int(self.controller.update( - error=error, - feedforward=np.interp(cur_temp, [60.0, 100.0], [0, 100]) - )) - self.last_ignition = ignition - return fan_pwr_out + return int(self.controller.update( + error=(cur_temp - 75), # temperature setpoint in C + feedforward=np.interp(cur_temp, [60.0, 100.0], [0, 100]) + )) diff --git a/system/hardware/hardwared.py b/system/hardware/hardwared.py index 8ddc4da2f..aad30f77b 100755 --- a/system/hardware/hardwared.py +++ b/system/hardware/hardwared.py @@ -22,7 +22,7 @@ from openpilot.system.loggerd.config import get_available_percent from openpilot.system.statsd import statlog from openpilot.common.swaglog import cloudlog from openpilot.system.hardware.power_monitoring import PowerMonitoring -from openpilot.system.hardware.fan_controller import TiciFanController +from openpilot.system.hardware.fan_controller import FanController from openpilot.system.version import terms_version, training_version from openpilot.system.athena.registration import UNREGISTERED_DONGLE_ID @@ -210,14 +210,13 @@ def hardware_thread(end_event, hw_queue) -> None: HARDWARE.initialize_hardware() thermal_config = HARDWARE.get_thermal_config() - fan_controller = None + fan_controller = FanController(int(1./DT_HW)) while not end_event.is_set(): sm.update(PANDA_STATES_TIMEOUT) pandaStates = sm['pandaStates'] peripheralState = sm['peripheralState'] - peripheral_panda_present = peripheralState.pandaType != log.PandaState.PandaType.unknown # handle requests to cycle system started state if params.get_bool("OnroadCycleRequested"): @@ -234,11 +233,6 @@ def hardware_thread(end_event, hw_queue) -> None: in_car = pandaState.harnessStatus != log.PandaState.HarnessStatus.notConnected - # Setup fan handler on first connect to panda - if fan_controller is None and peripheral_panda_present: - if TICI: - fan_controller = TiciFanController() - elif (time.monotonic() - sm.recv_time['pandaStates']) > DISCONNECT_TIMEOUT: if onroad_conditions["ignition"]: onroad_conditions["ignition"] = False @@ -289,8 +283,7 @@ def hardware_thread(end_event, hw_queue) -> None: all_comp_temp = all_temp_filter.update(max(temp_sources)) msg.deviceState.maxTempC = all_comp_temp - if fan_controller is not None: - msg.deviceState.fanSpeedPercentDesired = fan_controller.update(all_comp_temp, onroad_conditions["ignition"]) + msg.deviceState.fanSpeedPercentDesired = fan_controller.update(all_comp_temp, onroad_conditions["ignition"]) is_offroad_for_5_min = (started_ts is None) and ((not started_seen) or (off_ts is None) or (time.monotonic() - off_ts > 60 * 5)) if is_offroad_for_5_min and offroad_comp_temp > OFFROAD_DANGER_TEMP: diff --git a/system/hardware/pc/hardware.h b/system/hardware/pc/hardware.h index 978dd771c..71f58b188 100644 --- a/system/hardware/pc/hardware.h +++ b/system/hardware/pc/hardware.h @@ -6,7 +6,6 @@ class HardwarePC : public HardwareNone { public: - static std::string get_os_version() { return "openpilot for PC"; } static std::string get_name() { return "pc"; } static cereal::InitData::DeviceType get_device_type() { return cereal::InitData::DeviceType::PC; } static bool PC() { return true; } diff --git a/system/hardware/pc/hardware.py b/system/hardware/pc/hardware.py index 6c1189639..f3d527429 100644 --- a/system/hardware/pc/hardware.py +++ b/system/hardware/pc/hardware.py @@ -1,78 +1,12 @@ -import random - from cereal import log -from openpilot.system.hardware.base import HardwareBase, LPABase +from openpilot.system.hardware.base import HardwareBase NetworkType = log.DeviceState.NetworkType -NetworkStrength = log.DeviceState.NetworkStrength + class Pc(HardwareBase): - def get_os_version(self): - return None - def get_device_type(self): return "pc" - def reboot(self, reason=None): - print("REBOOT!") - - def uninstall(self): - print("uninstall") - - def get_imei(self, slot): - return f"{random.randint(0, 1 << 32):015d}" - - def get_serial(self): - return "cccccccc" - - def get_network_info(self): - return None - def get_network_type(self): return NetworkType.wifi - - def get_sim_info(self): - return { - 'sim_id': '', - 'mcc_mnc': None, - 'network_type': ["Unknown"], - 'sim_state': ["ABSENT"], - '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 - - def get_current_power_draw(self): - return 0 - - def get_som_power_draw(self): - return 0 - - def shutdown(self): - print("SHUTDOWN!") - - def set_screen_brightness(self, percentage): - pass - - def get_screen_brightness(self): - return 0 - - def set_power_save(self, powersave_enabled): - pass - - def get_gpu_usage_percent(self): - return 0 - - def get_modem_temperatures(self): - return [] - - - def initialize_hardware(self): - pass - - def get_networks(self): - return None diff --git a/system/hardware/tests/test_fan_controller.py b/system/hardware/tests/test_fan_controller.py index 002c1edfd..ee39a24f8 100644 --- a/system/hardware/tests/test_fan_controller.py +++ b/system/hardware/tests/test_fan_controller.py @@ -1,12 +1,12 @@ import pytest -from openpilot.system.hardware.fan_controller import TiciFanController +from openpilot.system.hardware.fan_controller import FanController -ALL_CONTROLLERS = [TiciFanController] +ALL_CONTROLLERS = [FanController] def patched_controller(mocker, controller_class): mocker.patch("os.system", new=mocker.Mock()) - return controller_class() + return controller_class(2) class TestFanController: def wind_up(self, controller, ignition=True): diff --git a/system/hardware/tici/amplifier.py b/system/hardware/tici/amplifier.py index bfdcc6dda..d714837bb 100755 --- a/system/hardware/tici/amplifier.py +++ b/system/hardware/tici/amplifier.py @@ -6,23 +6,8 @@ from collections import namedtuple # https://datasheets.maximintegrated.com/en/ds/MAX98089.pdf AmpConfig = namedtuple('AmpConfig', ['name', 'value', 'register', 'offset', 'mask']) -EQParams = namedtuple('EQParams', ['K', 'k1', 'k2', 'c1', 'c2']) -def configs_from_eq_params(base, eq_params): - return [ - AmpConfig("K (high)", (eq_params.K >> 8), base, 0, 0xFF), - AmpConfig("K (low)", (eq_params.K & 0xFF), base + 1, 0, 0xFF), - AmpConfig("k1 (high)", (eq_params.k1 >> 8), base + 2, 0, 0xFF), - AmpConfig("k1 (low)", (eq_params.k1 & 0xFF), base + 3, 0, 0xFF), - AmpConfig("k2 (high)", (eq_params.k2 >> 8), base + 4, 0, 0xFF), - AmpConfig("k2 (low)", (eq_params.k2 & 0xFF), base + 5, 0, 0xFF), - AmpConfig("c1 (high)", (eq_params.c1 >> 8), base + 6, 0, 0xFF), - AmpConfig("c1 (low)", (eq_params.c1 & 0xFF), base + 7, 0, 0xFF), - AmpConfig("c2 (high)", (eq_params.c2 >> 8), base + 8, 0, 0xFF), - AmpConfig("c2 (low)", (eq_params.c2 & 0xFF), base + 9, 0, 0xFF), - ] - -BASE_CONFIG = [ +CONFIG = [ AmpConfig("MCLK prescaler", 0b01, 0x10, 4, 0b00110000), AmpConfig("PM: enable speakers", 0b11, 0x4D, 4, 0b00110000), AmpConfig("PM: enable DACs", 0b11, 0x4D, 0, 0b00000011), @@ -58,35 +43,31 @@ BASE_CONFIG = [ AmpConfig("Enhanced volume smoothing disabled", 0b0, 0x49, 7, 0b10000000), AmpConfig("Volume adjustment smoothing disabled", 0b0, 0x49, 6, 0b01000000), AmpConfig("Zero-crossing detection disabled", 0b0, 0x49, 5, 0b00100000), + + AmpConfig("Left speaker output from left DAC", 0b1, 0x2B, 0, 0b11111111), + AmpConfig("Right speaker output from right DAC", 0b1, 0x2C, 0, 0b11111111), + AmpConfig("Left Speaker Mixer Gain", 0b00, 0x2D, 0, 0b00000011), + AmpConfig("Right Speaker Mixer Gain", 0b00, 0x2D, 2, 0b00001100), + AmpConfig("Left speaker output volume", 0x17, 0x3D, 0, 0b00011111), + AmpConfig("Right speaker output volume", 0x17, 0x3E, 0, 0b00011111), + + AmpConfig("DAI2 EQ enable", 0b0, 0x49, 1, 0b00000010), + AmpConfig("DAI2: DC blocking", 0b0, 0x20, 0, 0b00000001), + AmpConfig("ALC enable", 0b0, 0x43, 7, 0b10000000), + AmpConfig("DAI2 EQ attenuation", 0x2, 0x32, 0, 0b00001111), + AmpConfig("Excursion limiter upper corner freq", 0b001, 0x41, 4, 0b01110000), + AmpConfig("Excursion limiter threshold", 0b100, 0x42, 0, 0b00001111), + AmpConfig("Distortion limit (THDCLP)", 0x0, 0x46, 4, 0b11110000), + AmpConfig("Distortion limiter release time constant", 0b1, 0x46, 0, 0b00000001), + AmpConfig("Left DAC input mixer: DAI1 left", 0b0, 0x22, 7, 0b10000000), + AmpConfig("Left DAC input mixer: DAI1 right", 0b0, 0x22, 6, 0b01000000), + AmpConfig("Left DAC input mixer: DAI2 left", 0b1, 0x22, 5, 0b00100000), + AmpConfig("Left DAC input mixer: DAI2 right", 0b0, 0x22, 4, 0b00010000), + AmpConfig("Right DAC input mixer: DAI2 left", 0b0, 0x22, 1, 0b00000010), + AmpConfig("Right DAC input mixer: DAI2 right", 0b1, 0x22, 0, 0b00000001), + AmpConfig("Volume adjustment smoothing disabled", 0b1, 0x49, 6, 0b01000000), ] -CONFIGS = { - "tizi": [ - AmpConfig("Left speaker output from left DAC", 0b1, 0x2B, 0, 0b11111111), - AmpConfig("Right speaker output from right DAC", 0b1, 0x2C, 0, 0b11111111), - AmpConfig("Left Speaker Mixer Gain", 0b00, 0x2D, 0, 0b00000011), - AmpConfig("Right Speaker Mixer Gain", 0b00, 0x2D, 2, 0b00001100), - AmpConfig("Left speaker output volume", 0x17, 0x3D, 0, 0b00011111), - AmpConfig("Right speaker output volume", 0x17, 0x3E, 0, 0b00011111), - - AmpConfig("DAI2 EQ enable", 0b0, 0x49, 1, 0b00000010), - AmpConfig("DAI2: DC blocking", 0b0, 0x20, 0, 0b00000001), - AmpConfig("ALC enable", 0b0, 0x43, 7, 0b10000000), - AmpConfig("DAI2 EQ attenuation", 0x2, 0x32, 0, 0b00001111), - AmpConfig("Excursion limiter upper corner freq", 0b001, 0x41, 4, 0b01110000), - AmpConfig("Excursion limiter threshold", 0b100, 0x42, 0, 0b00001111), - AmpConfig("Distortion limit (THDCLP)", 0x0, 0x46, 4, 0b11110000), - AmpConfig("Distortion limiter release time constant", 0b1, 0x46, 0, 0b00000001), - AmpConfig("Left DAC input mixer: DAI1 left", 0b0, 0x22, 7, 0b10000000), - AmpConfig("Left DAC input mixer: DAI1 right", 0b0, 0x22, 6, 0b01000000), - AmpConfig("Left DAC input mixer: DAI2 left", 0b1, 0x22, 5, 0b00100000), - AmpConfig("Left DAC input mixer: DAI2 right", 0b0, 0x22, 4, 0b00010000), - AmpConfig("Right DAC input mixer: DAI2 left", 0b0, 0x22, 1, 0b00000010), - AmpConfig("Right DAC input mixer: DAI2 right", 0b1, 0x22, 0, 0b00000001), - AmpConfig("Volume adjustment smoothing disabled", 0b1, 0x49, 6, 0b01000000), - ], -} - class Amplifier: AMP_I2C_BUS = 0 AMP_ADDRESS = 0x10 @@ -127,20 +108,15 @@ class Amplifier: def set_global_shutdown(self, amp_disabled: bool) -> bool: return self.set_configs([self._get_shutdown_config(amp_disabled), ]) - def initialize_configuration(self, model: str) -> bool: + def initialize_configuration(self) -> bool: cfgs = [ self._get_shutdown_config(True), - *BASE_CONFIG, - *CONFIGS[model], + *CONFIG, self._get_shutdown_config(False), ] return self.set_configs(cfgs) if __name__ == "__main__": - with open("/sys/firmware/devicetree/base/model") as f: - model = f.read().strip('\x00') - model = model.split('comma ')[-1] - amp = Amplifier() - amp.initialize_configuration(model) + amp.initialize_configuration() diff --git a/system/hardware/tici/hardware.py b/system/hardware/tici/hardware.py index 9791f15f3..3cdb33608 100644 --- a/system/hardware/tici/hardware.py +++ b/system/hardware/tici/hardware.py @@ -125,7 +125,7 @@ class Tici(HardwareBase): return int(f.read()) def set_ir_power(self, percent: int): - if self.get_device_type() in ("tici", "tizi"): + if self.get_device_type() == "tizi": return value = int((percent / 100) * 300) @@ -369,7 +369,7 @@ class Tici(HardwareBase): if self.amplifier is not None: self.amplifier.set_global_shutdown(amp_disabled=powersave_enabled) if not powersave_enabled: - self.amplifier.initialize_configuration(self.get_device_type()) + self.amplifier.initialize_configuration() # *** CPU config *** @@ -404,7 +404,7 @@ class Tici(HardwareBase): def initialize_hardware(self): if self.amplifier is not None: - self.amplifier.initialize_configuration(self.get_device_type()) + self.amplifier.initialize_configuration() # Allow hardwared to write engagement status to kmsg os.system("sudo chmod a+w /dev/kmsg") diff --git a/system/hardware/tici/tests/test_amplifier.py b/system/hardware/tici/tests/test_amplifier.py index 3f75436db..9ce00c3ff 100644 --- a/system/hardware/tici/tests/test_amplifier.py +++ b/system/hardware/tici/tests/test_amplifier.py @@ -5,7 +5,6 @@ import subprocess from panda import Panda from openpilot.system.hardware import TICI, HARDWARE -from openpilot.system.hardware.tici.hardware import Tici from openpilot.system.hardware.tici.amplifier import Amplifier @@ -39,7 +38,7 @@ class TestAmplifier: def test_init(self): amp = Amplifier(debug=True) - r = amp.initialize_configuration(Tici().get_device_type()) + r = amp.initialize_configuration() assert r assert self._check_for_i2c_errors(False) @@ -61,7 +60,7 @@ class TestAmplifier: time.sleep(random.randint(0, 5)) amp = Amplifier(debug=True) - r = amp.initialize_configuration(Tici().get_device_type()) + r = amp.initialize_configuration() assert r if self._check_for_i2c_errors(True): From a8cfa2e2feaf63fde0f34d6e036c0ad5498d02eb Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 21 Dec 2025 17:22:57 -0800 Subject: [PATCH 684/910] bump msgq (#36950) --- msgq_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/msgq_repo b/msgq_repo index 6abe47bc9..f3f440102 160000 --- a/msgq_repo +++ b/msgq_repo @@ -1 +1 @@ -Subproject commit 6abe47bc98b83338b6ea04a87a6b2b5c65d09630 +Subproject commit f3f440102ef0a22e0ab1c7400b8c8a98eb6275c5 From 3093bb0b66d20438197890d97bc5226518337972 Mon Sep 17 00:00:00 2001 From: Nayan Date: Sun, 21 Dec 2025 22:26:11 -0500 Subject: [PATCH 685/910] ui: fix sunnylink paired/sponsorship state (#1603) * fix * no point polling when disabled --- selfdrive/ui/sunnypilot/ui_state.py | 5 ++++- sunnypilot/sunnylink/sunnylink_state.py | 9 +++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/selfdrive/ui/sunnypilot/ui_state.py b/selfdrive/ui/sunnypilot/ui_state.py index fe90e3bdf..ac08538df 100644 --- a/selfdrive/ui/sunnypilot/ui_state.py +++ b/selfdrive/ui/sunnypilot/ui_state.py @@ -23,7 +23,10 @@ class UIStateSP: self.sunnylink_state = SunnylinkState() def update(self) -> None: - self.sunnylink_state.start() + if self.sunnylink_enabled: + self.sunnylink_state.start() + else: + self.sunnylink_state.stop() @staticmethod def update_status(ss, ss_sp, onroad_evt) -> str: diff --git a/sunnypilot/sunnylink/sunnylink_state.py b/sunnypilot/sunnylink/sunnylink_state.py index 13b5ad81e..efdfa7071 100644 --- a/sunnypilot/sunnylink/sunnylink_state.py +++ b/sunnypilot/sunnylink/sunnylink_state.py @@ -136,10 +136,11 @@ class SunnylinkState: token = self._api.get_token() response = self._api.api_get(f"device/{self.sunnylink_dongle_id}/roles", method='GET', access_token=token) if response.status_code == 200: - self._roles = _parse_roles(response.text) - self._params.put("SunnylinkCache_Roles", response.text) - sponsor_tier = self._get_highest_tier() + roles = response.text + self._params.put("SunnylinkCache_Roles", roles) with self._lock: + self._roles = _parse_roles(roles) + sponsor_tier = self._get_highest_tier() if sponsor_tier != self.sponsor_tier: self.sponsor_tier = sponsor_tier cloudlog.info(f"Sunnylink sponsor tier updated to {sponsor_tier.name}") @@ -157,7 +158,7 @@ class SunnylinkState: users = response.text self._params.put("SunnylinkCache_Users", users) with self._lock: - _parse_users(users) + self._users = _parse_users(users) except Exception as e: cloudlog.exception(f"Failed to fetch sunnylink users: {e} for dongle id {self.sunnylink_dongle_id}") From e54ddf30b89166b4e291d54a7f034a092097676d Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 21 Dec 2025 22:48:45 -0500 Subject: [PATCH 686/910] ci: restore workflows from source branch for reset and squash (#1607) --- .../workflows/sunnypilot-master-dev-prep.yaml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.github/workflows/sunnypilot-master-dev-prep.yaml b/.github/workflows/sunnypilot-master-dev-prep.yaml index 10794bf0f..8e8e3c3d9 100644 --- a/.github/workflows/sunnypilot-master-dev-prep.yaml +++ b/.github/workflows/sunnypilot-master-dev-prep.yaml @@ -174,6 +174,24 @@ jobs: echo ' pushurl = ${{ env.LFS_PUSH_URL }}' >> .lfsconfig echo ' locksverify = false' >> .lfsconfig + - name: Restore workflows from source + run: | + TARGET_BRANCH="${{ inputs.target_branch || env.DEFAULT_TARGET_BRANCH }}" + SOURCE_BRANCH="${{ inputs.source_branch || env.DEFAULT_SOURCE_BRANCH }}" + + # Ensure we are on the target branch + git checkout $TARGET_BRANCH + + echo "Restoring .github/workflows from $SOURCE_BRANCH" + git checkout origin/$SOURCE_BRANCH -- .github/workflows + + if ! git diff --cached --quiet; then + echo "Workflows differ. Committing restoration." + git commit -m "chore: restore .github/workflows from $SOURCE_BRANCH" + else + echo "Workflows match $SOURCE_BRANCH." + fi + - uses: actions/create-github-app-token@v2 id: ci-token with: From 6d7910ed74b46939ebddf2086d24ae4b714acee0 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 21 Dec 2025 22:56:51 -0500 Subject: [PATCH 687/910] ui: add `inline` to `option_item_sp` (#1600) --- system/ui/sunnypilot/widgets/list_view.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system/ui/sunnypilot/widgets/list_view.py b/system/ui/sunnypilot/widgets/list_view.py index 6ba41f143..4a9c5fead 100644 --- a/system/ui/sunnypilot/widgets/list_view.py +++ b/system/ui/sunnypilot/widgets/list_view.py @@ -296,12 +296,12 @@ def option_item_sp(title: str | Callable[[], str], param: str, value_change_step: int = 1, on_value_changed: Callable[[int], None] | None = None, enabled: bool | Callable[[], bool] = True, icon: str = "", label_width: int = LABEL_WIDTH, value_map: dict[int, int] | None = None, - use_float_scaling: bool = False, label_callback: Callable[[int], str] | None = None) -> ListItemSP: + use_float_scaling: bool = False, label_callback: Callable[[int], str] | None = None, inline: bool = False) -> ListItemSP: action = OptionControlSP( param, min_value, max_value, value_change_step, enabled, on_value_changed, value_map, label_width, use_float_scaling, label_callback ) - return ListItemSP(title=title, description=description, action_item=action, icon=icon) + return ListItemSP(title=title, description=description, action_item=action, icon=icon, inline=inline) def button_item_sp(title: str | Callable[[], str], button_text: str | Callable[[], str], description: str | Callable[[], str] | None = None, From e96b0da9d764d934d9e75b17b7a7a0bfd7ba6da0 Mon Sep 17 00:00:00 2001 From: Matt Purnell <65473602+mpurnell1@users.noreply.github.com> Date: Sun, 21 Dec 2025 22:07:01 -0600 Subject: [PATCH 688/910] ci: Add unit test to prevent MADS DM regressions (#1602) * monitoring/tests: Add unit test to prevent MADS regressions * dm: remove TODO --------- Co-authored-by: Jason Wen --- selfdrive/monitoring/helpers.py | 1 - selfdrive/monitoring/test_monitoring.py | 66 ++++++++++++++++++++++++- 2 files changed, 65 insertions(+), 2 deletions(-) diff --git a/selfdrive/monitoring/helpers.py b/selfdrive/monitoring/helpers.py index 002e01ae3..4f068c4f5 100644 --- a/selfdrive/monitoring/helpers.py +++ b/selfdrive/monitoring/helpers.py @@ -449,7 +449,6 @@ class DriverMonitoring: rpyCalib = [0., 0., 0.] else: highway_speed = sm['carState'].vEgo - # TODO-SP: unit test to assert both control checks are always present enabled = sm['selfdriveState'].enabled or sm['carControl'].latActive wrong_gear = sm['carState'].gearShifter not in (car.CarState.GearShifter.drive, car.CarState.GearShifter.low) standstill = sm['carState'].standstill diff --git a/selfdrive/monitoring/test_monitoring.py b/selfdrive/monitoring/test_monitoring.py index 6ea9b8028..75adb6a2c 100644 --- a/selfdrive/monitoring/test_monitoring.py +++ b/selfdrive/monitoring/test_monitoring.py @@ -1,6 +1,7 @@ import numpy as np +import pytest -from cereal import log +from cereal import log, car from openpilot.common.realtime import DT_DMON from openpilot.selfdrive.monitoring.helpers import DriverMonitoring, DRIVER_MONITOR_SETTINGS from openpilot.system.hardware import HARDWARE @@ -204,3 +205,66 @@ class TestMonitoring: assert EventName.driverUnresponsive in \ events[int((INVISIBLE_SECONDS_TO_RED-1+DT_DMON*d_status.settings._HI_STD_FALLBACK_TIME+0.1)/DT_DMON)].names + +@pytest.mark.parametrize("enabled_state, lat_active_state, expected", [ + (False, False, False), # Both Disabled + (True, False, True), # OP Enabled, Lat Inactive + (False, True, True), # OP Disabled, Lat Active (e.g. MADS) + (True, True, True) # Both Active +]) +def test_enabled_states(enabled_state, lat_active_state, expected): + """ + Test DriverMonitoring.run_step with all 4 combinations of: + - selfdriveState.enabled (True/False) + - carControl.latActive (True/False) + """ + cs = car.CarState.new_message() + cs.vEgo = 30.0 + cs.gearShifter = car.CarState.GearShifter.drive + cs.standstill = False + cs.steeringPressed = False + cs.gasPressed = False + + ss = log.SelfdriveState.new_message() + ss.enabled = enabled_state + + cc = car.CarControl.new_message() + cc.latActive = lat_active_state + + mv2 = log.ModelDataV2.new_message() + mv2.meta.disengagePredictions.brakeDisengageProbs = [0.0] + + lc = log.LiveCalibrationData.new_message() + lc.rpyCalib = [0.0, 0.0, 0.0] + + ds = make_msg(False) + + sm = { + 'carState': cs, + 'selfdriveState': ss, + 'carControl': cc, + 'modelV2': mv2, + 'liveCalibration': lc, + 'driverStateV2': ds + } + + driver_monitoring = DriverMonitoring() + + # run_test doesn't assign enabled to a variable, so we need to spy on _update_events to see its value + captured_args = [] + original_update_events = driver_monitoring._update_events + + def spy_update_events(driver_engaged, op_engaged, standstill, wrong_gear, car_speed): + captured_args.append(op_engaged) + return original_update_events(driver_engaged, op_engaged, standstill, wrong_gear, car_speed) + + driver_monitoring._update_events = spy_update_events + + driver_monitoring.run_step(sm, demo=False) + + # Assertion + assert len(captured_args) == 1, "Expected _update_events to be called exactly once" + actual_enabled = captured_args[0] + + assert actual_enabled == expected, f"Expected op_engaged={expected}, but got {actual_enabled}" + From 34ce7468695dac0469f8f9ece4bd9e03d2ce8b9b Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 22 Dec 2025 09:29:01 -0500 Subject: [PATCH 689/910] ui: `DualButtonActionSP` and `dual_button_item_sp` helpers (#1608) --- system/ui/sunnypilot/widgets/list_view.py | 50 +++++++++++++++++++---- 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/system/ui/sunnypilot/widgets/list_view.py b/system/ui/sunnypilot/widgets/list_view.py index 4a9c5fead..c71d6e226 100644 --- a/system/ui/sunnypilot/widgets/list_view.py +++ b/system/ui/sunnypilot/widgets/list_view.py @@ -14,7 +14,7 @@ from openpilot.system.ui.sunnypilot.widgets.toggle import ToggleSP from openpilot.system.ui.widgets.button import Button, ButtonStyle from openpilot.system.ui.widgets.label import gui_label from openpilot.system.ui.widgets.list_view import ListItem, ToggleAction, ItemAction, MultipleButtonAction, ButtonAction, \ - _resolve_value, BUTTON_WIDTH, BUTTON_HEIGHT, TEXT_PADDING + _resolve_value, BUTTON_WIDTH, BUTTON_HEIGHT, TEXT_PADDING, DualButtonAction from openpilot.system.ui.widgets.scroller_tici import LineSeparator, LINE_COLOR, LINE_PADDING from openpilot.system.ui.sunnypilot.lib.styles import style from openpilot.system.ui.sunnypilot.widgets.option_control import OptionControlSP, LABEL_WIDTH @@ -84,6 +84,33 @@ class ButtonActionSP(ButtonAction): return pressed +class DualButtonActionSP(DualButtonAction): + def __init__(self, left_text: str | Callable[[], str], right_text: str | Callable[[], str], left_callback: Callable = None, + right_callback: Callable = None, enabled: bool | Callable[[], bool] = True, border_radius: int = 15): + DualButtonAction.__init__(self, left_text, right_text, left_callback, right_callback, enabled) + self.left_button._border_radius = self.right_button._border_radius = border_radius + + def _render(self, rect: rl.Rectangle): + button_spacing = 20 + button_height = 150 + button_width = (rect.width - button_spacing) / 2 + button_y = rect.y + (rect.height - button_height) / 2 + + left_rect = rl.Rectangle(rect.x, button_y, button_width, button_height) + right_rect = rl.Rectangle(rect.x + button_width + button_spacing, button_y, button_width, button_height) + + # expand one to full width if other is not visible + if not self.left_button.is_visible: + right_rect.x = rect.x + right_rect.width = rect.width + elif not self.right_button.is_visible: + left_rect.width = rect.width + + # Render buttons + self.left_button.render(left_rect) + self.right_button.render(right_rect) + + class MultipleButtonActionSP(MultipleButtonAction): def __init__(self, buttons: list[str | Callable[[], str]], button_width: int, selected_index: int = 0, callback: Callable = None, param: str | None = None): @@ -251,13 +278,13 @@ class ListItemSP(ListItem): item_y = self._rect.y + (style.ITEM_BASE_HEIGHT - self._text_size.y) // 2 if self.inline else self._rect.y + style.ITEM_PADDING * 1.5 rl.draw_text_ex(self._font, self.title, rl.Vector2(text_x, item_y), style.ITEM_TEXT_FONT_SIZE, 0, self.title_color) - # Draw right item if present - if self.action_item: - right_rect = self.get_right_item_rect(self._rect) - if self.action_item.render(right_rect) and self.action_item.enabled: - # Right item was clicked/activated - if self.callback: - self.callback() + # Draw right item if present + if self.action_item: + right_rect = self.get_right_item_rect(self._rect) + if self.action_item.render(right_rect) and self.action_item.enabled: + # Right item was clicked/activated + if self.callback: + self.callback() # Draw description if visible if self.description_visible: @@ -310,6 +337,13 @@ def button_item_sp(title: str | Callable[[], str], button_text: str | Callable[[ return ListItemSP(title=title, description=description, action_item=action, callback=callback) +def dual_button_item_sp(left_text: str | Callable[[], str], right_text: str | Callable[[], str], left_callback: Callable = None, + right_callback: Callable = None, description: str | Callable[[], str] | None = None, + enabled: bool | Callable[[], bool] = True, border_radius: int = 15) -> ListItemSP: + action = DualButtonActionSP(left_text, right_text, left_callback, right_callback, enabled, border_radius) + return ListItemSP(title="", description=description, action_item=action) + + class LineSeparatorSP(LineSeparator): def __init__(self, height: int = 1): super().__init__() From 373894a81f573d76e667ce7fcdc24ec1ad2d29c4 Mon Sep 17 00:00:00 2001 From: Matt Purnell <65473602+mpurnell1@users.noreply.github.com> Date: Mon, 22 Dec 2025 23:42:24 -0600 Subject: [PATCH 690/910] docs: Update branch installation instructions in README (#1610) * Update branch installation instructions * Add another link * Add limited support link * Incorporate suggested link * Tweak wording --- README.md | 60 ++----------------------------------------------------- 1 file changed, 2 insertions(+), 58 deletions(-) diff --git a/README.md b/README.md index 598e1273a..71fbb00e4 100644 --- a/README.md +++ b/README.md @@ -11,66 +11,10 @@ Join the official sunnypilot community forum to stay up to date with all the lat https://docs.sunnypilot.ai/ is your one stop shop for everything from features to installation to FAQ about the sunnypilot ## 🚘 Running on a dedicated device in a car -* A supported device to run this software - * a [comma three](https://comma.ai/shop/products/three) or a [C3X](https://comma.ai/shop/comma-3x) -* This software -* One of [the 325+ supported cars](https://github.com/sunnypilot/sunnypilot/blob/master/docs/CARS.md). We support Honda, Toyota, Hyundai, Nissan, Kia, Chrysler, Lexus, Acura, Audi, VW, Ford, and more. If your car is not supported but has adaptive cruise control and lane-keeping assist, it's likely able to run sunnypilot. -* A [car harness](https://comma.ai/shop/products/car-harness) to connect to your car - -Detailed instructions for [how to mount the device in a car](https://comma.ai/setup). +First, check out this list of items you'll need to [get started](https://community.sunnypilot.ai/t/getting-started-using-sunnypilot-in-your-supported-car/251). ## Installation -Please refer to [Recommended Branches](#recommended-branches) to find your preferred/supported branch. This guide will assume you want to install the latest `staging` branch. - -### If you want to use our newest branches (our rewrite) -> [!TIP] ->You can see the rewrite state on our [rewrite project board](https://github.com/orgs/sunnypilot/projects/2), and to install the new branches, you can use the following links - -* sunnypilot not installed or you installed a version before 0.8.17? - 1. [Factory reset/uninstall](https://github.com/commaai/openpilot/wiki/FAQ#how-can-i-reset-the-device) the previous software if you have another software/fork installed. - 2. After factory reset/uninstall and upon reboot, select `Custom Software` when given the option. - 3. Input the installation URL per [Recommended Branches](#recommended-branches). Example: ```https://staging.sunnypilot.ai```. - 4. Complete the rest of the installation following the onscreen instructions. - -* sunnypilot already installed and you installed a version after 0.8.17? - 1. On the comma three/3X, go to `Settings` ▶️ `Software`. - 2. At the `Download` option, press `CHECK`. This will fetch the list of latest branches from sunnypilot. - 3. At the `Target Branch` option, press `SELECT` to open the Target Branch selector. - 4. Scroll to select the desired branch per Recommended Branches (see below). Example: `staging` - -### Recommended Branches -| Branch | Installation URL | -|:---------------:|:---------------------------------------------:| -| `release` | `https://release.sunnypilot.ai` | -| `staging` | `https://staging.sunnypilot.ai` | -| `dev` | `https://dev.sunnypilot.ai` | -| `custom-branch` | `https://install.sunnypilot.ai/{branch_name}` | - -> [!TIP] -> You can use sunnypilot/targetbranch as an install URL. Example: 'sunnypilot/staging'. - -> [!NOTE] -> Do you require further assistance with software installation? Join the [sunnypilot community forum](https://community.sunnypilot.ai/new-topic?category=general/qa) and create a topic in the General/Q&A Category channel. - - -
- -Older legacy branches - -### If you want to use our older legacy branches (*not recommended*) - -> [**IMPORTANT**] -> It is recommended to [re-flash AGNOS](https://flash.comma.ai/) if you intend to downgrade from the new branches. -> You can still restore the latest sunnylink backup made on the old branches. - -| Branch | Installation URL | -|:------------:|:--------------------------------:| -| `release-c3` | https://release-c3.sunnypilot.ai | -| `staging-c3` | https://staging-c3.sunnypilot.ai | -| `dev-c3` | https://dev-c3.sunnypilot.ai | - -
- +Next, refer to the sunnypilot community forum for [installation instructions](https://community.sunnypilot.ai/t/read-before-installing-sunnypilot/254), as well as a complete list of [Recommended Branch Installations](https://community.sunnypilot.ai/t/recommended-branch-installations/235). ## 🎆 Pull Requests We welcome both pull requests and issues on GitHub. Bug fixes are encouraged. From a04a5b4284066f3f05feede209f7ec544a79d6f7 Mon Sep 17 00:00:00 2001 From: Nayan Date: Tue, 23 Dec 2025 00:51:35 -0500 Subject: [PATCH 691/910] ui: expand `DeviceLayoutSP` (#1560) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * commaai/openpilot:d05cb31e2e916fba41ba8167030945f427fd811b * bump opendbc * bump opendbc * bump opendbc * bump opendbc * bump opendbc * sunnypilot: remove Qt * cabana: revert to stock Qt * commaai/openpilot:5198b1b079c37742c1050f02ce0aa6dd42b038b9 * commaai/openpilot:954b567b9ba0f3d1ae57d6aa7797fa86dd92ec6e * commaai/openpilot:7534b2a160faa683412c04c1254440e338931c5e * sum more * bump opendbc * not yet * should've been symlink'ed * raylib says wut * quiet mode back * more fixes * no more * too extra red diff on the side * need to bring this back * too extra * let's update docs here * Revert "let's update docs here" This reverts commit 51fe03cd5121e6fdf14657b2c33852c34922b851. * param to control stock vs sp ui * init styles * SP Toggles * Lint * optimizations * multi-button * Lint * param to control stock vs sp ui * init styles * SP Toggles * Lint * optimizations * sp raylib preview * fix callback * fix ui preview * better padding * this * support for next line multi-button * uhh * disabled colors * listitem -> listitemsp * listitem -> listitemsp * add show_description method * remove padding from line separator. like, WHY? 😩😩 * ui: `GuiApplicationExt` * simple button * simple button * add to readme * use gui_app.sunnypilot_ui() * i've got something to confessa * init * more init * power buttons always visible * uh, nope * add reset to offroad only * support wake up offroad * flippity floppity * dual button item sp * use dual button item sp * lint * keep @devtekve from going blind * more round * some * revert * slight diff * should've been inline * cleanup power btns and offroad transitions * bruh * 1st row red diff * 2nd row red diff * 3rd row red diff * slight diff * move around * more diff * only when onroad we move to the top, not the toggle * nah * sort --------- Co-authored-by: Jason Wen Co-authored-by: DevTekVE Co-authored-by: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Co-authored-by: discountchubbs --- .../ui/sunnypilot/layouts/settings/device.py | 208 ++++++++++++++++++ .../sunnypilot/layouts/settings/settings.py | 28 +-- selfdrive/ui/sunnypilot/ui_state.py | 9 + selfdrive/ui/ui_state.py | 6 +- system/ui/sunnypilot/widgets/list_view.py | 14 ++ 5 files changed, 249 insertions(+), 16 deletions(-) diff --git a/selfdrive/ui/sunnypilot/layouts/settings/device.py b/selfdrive/ui/sunnypilot/layouts/settings/device.py index 081969cf1..36c5fdb34 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/device.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/device.py @@ -5,8 +5,216 @@ This file is part of sunnypilot and is licensed under the MIT License. See the LICENSE.md file in the root directory for more details. """ from openpilot.selfdrive.ui.layouts.settings.device import DeviceLayout +from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.system.hardware import HARDWARE +from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.lib.multilang import tr +from openpilot.system.ui.sunnypilot.widgets.list_view import option_item_sp, multiple_button_item_sp, button_item_sp, \ + dual_button_item_sp, Spacer +from openpilot.system.ui.widgets import DialogResult +from openpilot.system.ui.widgets.button import ButtonStyle +from openpilot.system.ui.widgets.confirm_dialog import alert_dialog, ConfirmDialog +from openpilot.system.ui.widgets.list_view import text_item +from openpilot.system.ui.widgets.scroller_tici import LineSeparator + +offroad_time_options = { + 0: 0, + 1: 5, + 2: 10, + 3: 15, + 4: 30, + 5: 60, + 6: 120, + 7: 180, + 8: 300, + 9: 600, + 10: 1440, + 11: 1800, +} class DeviceLayoutSP(DeviceLayout): def __init__(self): DeviceLayout.__init__(self) + self._scroller._line_separator = None + + def _initialize_items(self): + DeviceLayout._initialize_items(self) + + # Using dual button with no right button for better alignment + self._always_offroad_btn = dual_button_item_sp( + left_text=lambda: tr("Enable Always Offroad"), + left_callback=self._handle_always_offroad, + right_text="", + right_callback=None, + ) + self._always_offroad_btn.action_item.right_button.set_visible(False) + + self._max_time_offroad = option_item_sp( + title=lambda: tr("Max Time Offroad"), + description=lambda: tr("Device will automatically shutdown after set time once the engine is turned off.\n(30h is the default)"), + param="MaxTimeOffroad", + min_value=0, + max_value=11, + value_change_step=1, + on_value_changed=None, + enabled=True, + icon="", + value_map=offroad_time_options, + label_width=360, + use_float_scaling=False, + inline=True, + label_callback=self._update_max_time_offroad_label + ) + + self._device_wake_mode = multiple_button_item_sp( + title=lambda: tr("Wake Up Behavior"), + description=self.wake_mode_description, + param="DeviceBootMode", + buttons=[lambda: tr("Default"), lambda: tr("Offroad")], + button_width=364, + callback=None, + inline=True, + ) + + self._quiet_mode_and_dcam = dual_button_item_sp( + left_text=lambda: tr("Quiet Mode"), + right_text=lambda: tr("Driver Camera Preview"), + left_callback=lambda: ui_state.params.put_bool("QuietMode", not ui_state.params.get_bool("QuietMode")), + right_callback=self._show_driver_camera + ) + self._quiet_mode_and_dcam.action_item.right_button.set_button_style(ButtonStyle.NORMAL) + + self._reg_and_training = dual_button_item_sp( + left_text=lambda: tr("Regulatory"), + left_callback=self._on_regulatory, + right_text=lambda: tr("Training Guide"), + right_callback=self._on_review_training_guide + ) + self._reg_and_training.action_item.right_button.set_button_style(ButtonStyle.NORMAL) + + self._onroad_uploads_and_reset_settings = dual_button_item_sp( + left_text=lambda: tr("Onroad Uploads"), + left_callback=lambda: ui_state.params.put_bool("OnroadUploads", not ui_state.params.get_bool("OnroadUploads")), + right_text=lambda: tr("Reset Settings"), + right_callback=self._reset_settings + ) + + self._power_buttons = dual_button_item_sp( + left_text=lambda: tr("Reboot"), + right_text=lambda: tr("Power Off"), + left_callback=self._reboot_prompt, + right_callback=self._power_off_prompt + ) + + items = [ + text_item(lambda: tr("Dongle ID"), self._params.get("DongleId") or (lambda: tr("N/A"))), + LineSeparator(), + text_item(lambda: tr("Serial"), self._params.get("HardwareSerial") or (lambda: tr("N/A"))), + LineSeparator(), + self._pair_device_btn, + LineSeparator(), + self._reset_calib_btn, + LineSeparator(), + button_item_sp(lambda: tr("Change Language"), lambda: tr("CHANGE"), callback=self._show_language_dialog), + LineSeparator(), + self._device_wake_mode, + LineSeparator(), + self._max_time_offroad, + LineSeparator(height=10), + self._quiet_mode_and_dcam, + self._reg_and_training, + self._onroad_uploads_and_reset_settings, + Spacer(10), + LineSeparator(height=10), + self._power_buttons, + ] + + return items + + def _offroad_transition(self): + self._power_buttons.action_item.right_button.set_visible(ui_state.is_offroad()) + + @staticmethod + def wake_mode_description() -> str: + def_str = tr("Default: Device will boot/wake-up normally & will be ready to engage.") + offrd_str = tr("Offroad: Device will be in Always Offroad mode after boot/wake-up.") + header = tr("Controls state of the device after boot/sleep.") + + return f"{header}\n\n{def_str}\n{offrd_str}" + + @staticmethod + def _reset_settings(): + def _do_reset(result: int): + if result == DialogResult.CONFIRM: + for _key in ui_state.params.all_keys(): + ui_state.params.remove(_key) + HARDWARE.reboot() + + def _second_confirm(result: int): + if result == DialogResult.CONFIRM: + gui_app.set_modal_overlay(ConfirmDialog( + text=tr("The reset cannot be undone. You have been warned."), + confirm_text=tr("Confirm") + ), callback=_do_reset) + + gui_app.set_modal_overlay(ConfirmDialog( + text=tr("Are you sure you want to reset all sunnypilot settings to default? Once the settings are reset, there is no going back."), + confirm_text=tr("Reset") + ), callback=_second_confirm) + + @staticmethod + def _handle_always_offroad(): + if ui_state.engaged: + gui_app.set_modal_overlay(alert_dialog(tr("Disengage to Enter Always Offroad Mode"))) + return + + _offroad_mode_state = ui_state.params.get_bool("OffroadMode") + _offroad_mode_str = tr("Are you sure you want to exit Always Offroad mode?") if _offroad_mode_state else \ + tr("Are you sure you want to enter Always Offroad mode?") + + def _set_always_offroad(result: int): + if result == DialogResult.CONFIRM and not ui_state.engaged: + ui_state.params.put_bool("OffroadMode", not _offroad_mode_state) + + gui_app.set_modal_overlay(ConfirmDialog(_offroad_mode_str, tr("Confirm")), callback=lambda result: _set_always_offroad(result)) + + @staticmethod + def _update_max_time_offroad_label(value: int) -> str: + label = tr("Always On") if value == 0 else f"{value}" + tr("m") if value < 60 else f"{value // 60}" + tr("h") + label += tr(" (Default)") if value == 1800 else "" + return label + + def _update_state(self): + super()._update_state() + + # Handle Always Offroad button + always_offroad = ui_state.params.get_bool("OffroadMode") + + # Text & Color + offroad_mode_btn_text = tr("Exit Always Offroad") if always_offroad else tr("Enable Always Offroad") + offroad_mode_btn_style = ButtonStyle.NORMAL if always_offroad else ButtonStyle.DANGER + self._always_offroad_btn.action_item.left_button.set_text(offroad_mode_btn_text) + self._always_offroad_btn.action_item.left_button.set_button_style(offroad_mode_btn_style) + + # Position + if self._scroller._items.__contains__(self._always_offroad_btn): + self._scroller._items.remove(self._always_offroad_btn) + if ui_state.is_offroad() and not always_offroad: + self._scroller._items.insert(len(self._scroller._items) - 1, self._always_offroad_btn) + elif not ui_state.is_offroad(): + self._scroller._items.insert(0, self._always_offroad_btn) + + # Quiet Mode button + self._quiet_mode_and_dcam.action_item.left_button.set_button_style(ButtonStyle.PRIMARY if ui_state.params.get_bool("QuietMode") else ButtonStyle.NORMAL) + + # Onroad Uploads + self._onroad_uploads_and_reset_settings.action_item.left_button.set_button_style( + ButtonStyle.PRIMARY if ui_state.params.get_bool("OnroadUploads") else ButtonStyle.NORMAL + ) + + # Offroad only buttons + self._quiet_mode_and_dcam.action_item.right_button.set_enabled(ui_state.is_offroad()) + self._reg_and_training.action_item.left_button.set_enabled(ui_state.is_offroad()) + self._reg_and_training.action_item.right_button.set_enabled(ui_state.is_offroad()) + self._onroad_uploads_and_reset_settings.action_item.right_button.set_enabled(ui_state.is_offroad()) diff --git a/selfdrive/ui/sunnypilot/layouts/settings/settings.py b/selfdrive/ui/sunnypilot/layouts/settings/settings.py index 103dd99ef..bc83c82f8 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/settings.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/settings.py @@ -9,28 +9,28 @@ from enum import IntEnum import pyray as rl from openpilot.selfdrive.ui.layouts.settings import settings as OP -from openpilot.selfdrive.ui.sunnypilot.layouts.settings.device import DeviceLayoutSP from openpilot.selfdrive.ui.layouts.settings.firehose import FirehoseLayout -from openpilot.selfdrive.ui.sunnypilot.layouts.settings.software import SoftwareLayoutSP from openpilot.selfdrive.ui.layouts.settings.toggles import TogglesLayout -from openpilot.system.ui.lib.application import gui_app, MousePos -from openpilot.system.ui.lib.multilang import tr_noop -from openpilot.system.ui.sunnypilot.lib.styles import style -from openpilot.system.ui.widgets.scroller_tici import Scroller -from openpilot.system.ui.lib.text_measure import measure_text_cached -from openpilot.system.ui.lib.wifi_manager import WifiManager -from openpilot.system.ui.widgets import Widget +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.cruise import CruiseLayout +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.developer import DeveloperLayoutSP +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.device import DeviceLayoutSP +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.display import DisplayLayout from openpilot.selfdrive.ui.sunnypilot.layouts.settings.models import ModelsLayout from openpilot.selfdrive.ui.sunnypilot.layouts.settings.network import NetworkUISP -from openpilot.selfdrive.ui.sunnypilot.layouts.settings.sunnylink import SunnylinkLayout from openpilot.selfdrive.ui.sunnypilot.layouts.settings.osm import OSMLayout +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.software import SoftwareLayoutSP +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.steering import SteeringLayout +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.sunnylink import SunnylinkLayout from openpilot.selfdrive.ui.sunnypilot.layouts.settings.trips import TripsLayout from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle import VehicleLayout -from openpilot.selfdrive.ui.sunnypilot.layouts.settings.steering import SteeringLayout -from openpilot.selfdrive.ui.sunnypilot.layouts.settings.cruise import CruiseLayout from openpilot.selfdrive.ui.sunnypilot.layouts.settings.visuals import VisualsLayout -from openpilot.selfdrive.ui.sunnypilot.layouts.settings.display import DisplayLayout -from openpilot.selfdrive.ui.sunnypilot.layouts.settings.developer import DeveloperLayoutSP +from openpilot.system.ui.lib.application import gui_app, MousePos +from openpilot.system.ui.lib.multilang import tr_noop +from openpilot.system.ui.lib.text_measure import measure_text_cached +from openpilot.system.ui.lib.wifi_manager import WifiManager +from openpilot.system.ui.sunnypilot.lib.styles import style +from openpilot.system.ui.widgets import Widget +from openpilot.system.ui.widgets.scroller_tici import Scroller # from openpilot.selfdrive.ui.sunnypilot.layouts.settings.navigation import NavigationLayout diff --git a/selfdrive/ui/sunnypilot/ui_state.py b/selfdrive/ui/sunnypilot/ui_state.py index ac08538df..ca8125512 100644 --- a/selfdrive/ui/sunnypilot/ui_state.py +++ b/selfdrive/ui/sunnypilot/ui_state.py @@ -73,3 +73,12 @@ class UIStateSP: self.developer_ui = self.params.get("DevUIInfo") self.rainbow_path = self.params.get_bool("RainbowMode") self.chevron_metrics = self.params.get("ChevronInfo") + + +class DeviceSP: + def __init__(self): + self._params = Params() + + def _set_awake(self, on: bool): + if on and self._params.get("DeviceBootMode", return_default=True) == 1: + self._params.put_bool("OffroadMode", True) diff --git a/selfdrive/ui/ui_state.py b/selfdrive/ui/ui_state.py index 0e1710c24..a86c84ada 100644 --- a/selfdrive/ui/ui_state.py +++ b/selfdrive/ui/ui_state.py @@ -12,7 +12,7 @@ from openpilot.selfdrive.ui.lib.prime_state import PrimeState from openpilot.system.ui.lib.application import gui_app from openpilot.system.hardware import HARDWARE, PC -from openpilot.selfdrive.ui.sunnypilot.ui_state import UIStateSP +from openpilot.selfdrive.ui.sunnypilot.ui_state import UIStateSP, DeviceSP BACKLIGHT_OFFROAD = 65 if HARDWARE.get_device_type() == "mici" else 50 @@ -192,8 +192,9 @@ class UIState(UIStateSP): self._param_update_time = time.monotonic() -class Device: +class Device(DeviceSP): def __init__(self): + DeviceSP.__init__(self) self._ignition = False self._interaction_time: float = -1 self._override_interactive_timeout: int | None = None @@ -284,6 +285,7 @@ class Device: def _set_awake(self, on: bool): if on != self._awake: + DeviceSP._set_awake(self, on) self._awake = on cloudlog.debug(f"setting display power {int(on)}") HARDWARE.set_display_power(on) diff --git a/system/ui/sunnypilot/widgets/list_view.py b/system/ui/sunnypilot/widgets/list_view.py index c71d6e226..bf78147a5 100644 --- a/system/ui/sunnypilot/widgets/list_view.py +++ b/system/ui/sunnypilot/widgets/list_view.py @@ -11,6 +11,7 @@ from openpilot.common.params import Params from openpilot.system.ui.lib.application import gui_app, MousePos, FontWeight from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.sunnypilot.widgets.toggle import ToggleSP +from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.button import Button, ButtonStyle from openpilot.system.ui.widgets.label import gui_label from openpilot.system.ui.widgets.list_view import ListItem, ToggleAction, ItemAction, MultipleButtonAction, ButtonAction, \ @@ -20,6 +21,19 @@ from openpilot.system.ui.sunnypilot.lib.styles import style from openpilot.system.ui.sunnypilot.widgets.option_control import OptionControlSP, LABEL_WIDTH +class Spacer(Widget): + def __init__(self, height: int = 1): + super().__init__() + self._rect = rl.Rectangle(0, 0, 0, height) + + def set_parent_rect(self, parent_rect: rl.Rectangle) -> None: + super().set_parent_rect(parent_rect) + self._rect.width = parent_rect.width + + def _render(self, _): + rl.draw_rectangle(int(self._rect.x), int(self._rect.y), int(self._rect.x + self._rect.width), int(self._rect.y), rl.Color(0,0,0,0)) + + class ToggleActionSP(ToggleAction): def __init__(self, initial_state: bool = False, width: int = style.TOGGLE_WIDTH, enabled: bool | Callable[[], bool] = True, callback: Callable[[bool], None] | None = None, param: str | None = None): From e82dc2d85076748eb1dc021dd84c3b43ed7477c1 Mon Sep 17 00:00:00 2001 From: infiniteCable2 Date: Tue, 23 Dec 2025 11:51:33 +0100 Subject: [PATCH 692/910] Update opendbc_repo --- opendbc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc_repo b/opendbc_repo index da51ac8b7..78d36b9ff 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit da51ac8b7b1f29eb35e795389b738e73a0e53311 +Subproject commit 78d36b9ff30acd2d15e84e708abd626d74b0e9a9 From c876a83a31c1021f00818d31ca66d66910d54a87 Mon Sep 17 00:00:00 2001 From: dzid26 Date: Tue, 23 Dec 2025 14:34:56 +0000 Subject: [PATCH 693/910] ui: fix malformed dongle ID display on the PC if dongleID is not set (#1612) --- selfdrive/ui/sunnypilot/layouts/settings/sunnylink.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/sunnypilot/layouts/settings/sunnylink.py b/selfdrive/ui/sunnypilot/layouts/settings/sunnylink.py index 316d4d224..2b5497fb5 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/sunnylink.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/sunnylink.py @@ -209,8 +209,8 @@ class SunnylinkLayout(Widget): return items @staticmethod - def _get_sunnylink_dongle_id() -> str | None: - return str(ui_state.params.get("SunnylinkDongleId") or (lambda: tr("N/A"))) + def _get_sunnylink_dongle_id() -> str: + return ui_state.params.get("SunnylinkDongleId") or tr("N/A") def _handle_pair_btn(self, sponsor_pairing: bool = False): sunnylink_dongle_id = self._get_sunnylink_dongle_id() From c6c644a3a6dc97ffe7a151f64c2b903fd74956b8 Mon Sep 17 00:00:00 2001 From: Nayan Date: Tue, 23 Dec 2025 16:34:00 -0500 Subject: [PATCH 694/910] [mici] ui: sunnypilot font on home menu (#1561) use sp font on home Co-authored-by: Jason Wen --- selfdrive/ui/mici/layouts/home.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/mici/layouts/home.py b/selfdrive/ui/mici/layouts/home.py index 750a30b73..bdffea4cf 100644 --- a/selfdrive/ui/mici/layouts/home.py +++ b/selfdrive/ui/mici/layouts/home.py @@ -109,7 +109,7 @@ class MiciHomeLayout(Widget): self._cell_high_txt = gui_app.texture("icons_mici/settings/network/cell_strength_high.png", 55, 35) self._cell_full_txt = gui_app.texture("icons_mici/settings/network/cell_strength_full.png", 55, 35) - self._openpilot_label = MiciLabel("sunnypilot", font_size=96, color=rl.Color(255, 255, 255, int(255 * 0.9)), font_weight=FontWeight.DISPLAY) + self._openpilot_label = MiciLabel("sunnypilot", font_size=90, color=rl.Color(255, 255, 255, int(255 * 0.9)), font_weight=FontWeight.AUDIOWIDE) self._version_label = MiciLabel("", font_size=36, font_weight=FontWeight.ROMAN) self._large_version_label = MiciLabel("", font_size=64, color=rl.GRAY, font_weight=FontWeight.ROMAN) self._date_label = MiciLabel("", font_size=36, color=rl.GRAY, font_weight=FontWeight.ROMAN) From 3f1f7ad89c785070ffac84a3e19cd6d07f16859a Mon Sep 17 00:00:00 2001 From: Trey Moen <50057480+greatgitsby@users.noreply.github.com> Date: Wed, 24 Dec 2025 09:04:34 -0800 Subject: [PATCH 695/910] feat(esim): remove bootstrap (#36954) feat: remove bootstrap --- system/hardware/base.py | 4 ---- system/hardware/esim.py | 27 +-------------------------- system/hardware/tici/esim.py | 29 ----------------------------- 3 files changed, 1 insertion(+), 59 deletions(-) diff --git a/system/hardware/base.py b/system/hardware/base.py index 05e8eb015..c12c0758f 100644 --- a/system/hardware/base.py +++ b/system/hardware/base.py @@ -66,10 +66,6 @@ class ThermalConfig: return ret class LPABase(ABC): - @abstractmethod - def bootstrap(self) -> None: - pass - @abstractmethod def list_profiles(self) -> list[Profile]: pass diff --git a/system/hardware/esim.py b/system/hardware/esim.py index 909ad41e0..58ead6593 100755 --- a/system/hardware/esim.py +++ b/system/hardware/esim.py @@ -3,32 +3,10 @@ import argparse import time from openpilot.system.hardware import HARDWARE -from openpilot.system.hardware.base import LPABase - - -def bootstrap(lpa: LPABase) -> None: - print('┌──────────────────────────────────────────────────────────────────────────────┐') - print('│ WARNING, PLEASE READ BEFORE PROCEEDING │') - print('│ │') - print('│ this is an irreversible operation that will remove the comma-provisioned │') - print('│ profile. │') - print('│ │') - print('│ after this operation, you must purchase a new eSIM from comma in order to │') - print('│ use the comma prime subscription again. │') - print('└──────────────────────────────────────────────────────────────────────────────┘') - print() - for severity in ('sure', '100% sure'): - print(f'are you {severity} you want to proceed? (y/N) ', end='') - confirm = input() - if confirm != 'y': - print('aborting') - exit(0) - lpa.bootstrap() if __name__ == '__main__': parser = argparse.ArgumentParser(prog='esim.py', description='manage eSIM profiles on your comma device', epilog='comma.ai') - parser.add_argument('--bootstrap', action='store_true', help='bootstrap the eUICC (required before downloading profiles)') 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)') @@ -38,10 +16,7 @@ if __name__ == '__main__': mutated = False lpa = HARDWARE.get_sim_lpa() - if args.bootstrap: - bootstrap(lpa) - mutated = True - elif args.switch: + if args.switch: lpa.switch_profile(args.switch) mutated = True elif args.delete: diff --git a/system/hardware/tici/esim.py b/system/hardware/tici/esim.py index 391ba4553..b489286f5 100644 --- a/system/hardware/tici/esim.py +++ b/system/hardware/tici/esim.py @@ -40,7 +40,6 @@ class TiciLPA(LPABase): self._process_notifications() def download_profile(self, qr: str, nickname: str | None = None) -> None: - self._check_bootstrapped() 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) @@ -55,7 +54,6 @@ class TiciLPA(LPABase): self._validate_successful(self._invoke('profile', 'nickname', iccid, nickname)) def switch_profile(self, iccid: str) -> None: - self._check_bootstrapped() self._validate_profile_exists(iccid) latest = self.get_active_profile() if latest and latest.iccid == iccid: @@ -63,33 +61,6 @@ class TiciLPA(LPABase): self._validate_successful(self._invoke('profile', 'enable', iccid)) self._process_notifications() - def bootstrap(self) -> None: - """ - find all comma-provisioned profiles and delete them. they conflict with user-provisioned profiles - and must be deleted. - - **note**: this is a **very** destructive operation. you **must** purchase a new comma SIM in order - to use comma prime again. - """ - if self._is_bootstrapped(): - return - - for p in self.list_profiles(): - if self.is_comma_profile(p.iccid): - self._disable_profile(p.iccid) - self.delete_profile(p.iccid) - - def _disable_profile(self, iccid: str) -> None: - self._validate_successful(self._invoke('profile', 'disable', iccid)) - self._process_notifications() - - def _check_bootstrapped(self) -> None: - assert self._is_bootstrapped(), 'eUICC is not bootstrapped, please bootstrap before performing this operation' - - def _is_bootstrapped(self) -> bool: - """ check if any comma provisioned profiles are on the eUICC """ - return not any(self.is_comma_profile(iccid) for iccid in (p.iccid for p in self.list_profiles())) - def _invoke(self, *cmd: str): proc = subprocess.Popen(['sudo', '-E', 'lpac'] + list(cmd), stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=self.env) try: From df67acaa2cae7b77448efaec1a8fdfc4e9693b01 Mon Sep 17 00:00:00 2001 From: infiniteCable2 Date: Sat, 27 Dec 2025 15:12:21 +0100 Subject: [PATCH 696/910] Reliable Gas Pressed Detection --- opendbc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc_repo b/opendbc_repo index 78d36b9ff..c09ce28d7 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 78d36b9ff30acd2d15e84e708abd626d74b0e9a9 +Subproject commit c09ce28d75107d02ca1f66b91573921da90c03ce From 763049f06886f59f9fd5975b0e7576551ecb26c0 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sat, 27 Dec 2025 17:49:26 +0100 Subject: [PATCH 697/910] SL: Re enable and validate ingestion of swaglogs (#1580) * Re enable and validate ingestion --- sunnypilot/sunnylink/athena/sunnylinkd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sunnypilot/sunnylink/athena/sunnylinkd.py b/sunnypilot/sunnylink/athena/sunnylinkd.py index 9f70aead5..d1a03778c 100755 --- a/sunnypilot/sunnylink/athena/sunnylinkd.py +++ b/sunnypilot/sunnylink/athena/sunnylinkd.py @@ -62,7 +62,7 @@ def handle_long_poll(ws: WebSocket, exit_event: threading.Event | None) -> None: threading.Thread(target=ws_ping, args=(ws, end_event), name='ws_ping'), threading.Thread(target=ws_queue, args=(end_event,), name='ws_queue'), threading.Thread(target=upload_handler, args=(end_event,), name='upload_handler'), - # threading.Thread(target=sunny_log_handler, args=(end_event, comma_prime_cellular_end_event), name='log_handler'), + threading.Thread(target=sunny_log_handler, args=(end_event, comma_prime_cellular_end_event), name='log_handler'), threading.Thread(target=stat_handler, args=(end_event, Paths.stats_sp_root(), True), name='stat_handler'), ] + [ threading.Thread(target=jsonrpc_handler, args=(end_event, partial(startLocalProxy, end_event),), name=f'worker_{x}') From 368947c88cb7556089312008c1322f7c12e4e04e Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 27 Dec 2025 16:49:30 -0800 Subject: [PATCH 698/910] msgq on macOS (#36958) * msgq on macOS * clean that up --- common/prefix.h | 6 +++++- common/prefix.py | 4 +++- msgq_repo | 2 +- tools/install_python_dependencies.sh | 2 -- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/common/prefix.h b/common/prefix.h index 7d0265c25..30ee18f63 100644 --- a/common/prefix.h +++ b/common/prefix.h @@ -13,7 +13,11 @@ public: if (prefix.empty()) { prefix = util::random_string(15); } - msgq_path = Path::shm_path() + "/msgq_" + prefix; +#ifdef __APPLE__ + msgq_path = "/tmp/msgq_" + prefix; +#else + msgq_path = "/dev/shm/msgq_" + prefix; +#endif bool ret = util::create_directories(msgq_path, 0777); assert(ret); setenv("OPENPILOT_PREFIX", prefix.c_str(), 1); diff --git a/common/prefix.py b/common/prefix.py index b19ce1472..86f69671d 100644 --- a/common/prefix.py +++ b/common/prefix.py @@ -1,4 +1,5 @@ import os +import platform import shutil import uuid @@ -11,7 +12,8 @@ from openpilot.system.hardware.hw import DEFAULT_DOWNLOAD_CACHE_ROOT class OpenpilotPrefix: def __init__(self, prefix: str = None, create_dirs_on_enter: bool = True, clean_dirs_on_exit: bool = True, shared_download_cache: bool = False): self.prefix = prefix if prefix else str(uuid.uuid4().hex[0:15]) - self.msgq_path = os.path.join(Paths.shm_path(), "msgq_" + self.prefix) + shm_path = "/tmp" if platform.system() == "Darwin" else "/dev/shm" + self.msgq_path = os.path.join(shm_path, "msgq_" + self.prefix) self.create_dirs_on_enter = create_dirs_on_enter self.clean_dirs_on_exit = clean_dirs_on_exit self.shared_download_cache = shared_download_cache diff --git a/msgq_repo b/msgq_repo index f3f440102..349bf449b 160000 --- a/msgq_repo +++ b/msgq_repo @@ -1 +1 @@ -Subproject commit f3f440102ef0a22e0ab1c7400b8c8a98eb6275c5 +Subproject commit 349bf449b7329527162598de357bfecf232abc3f diff --git a/tools/install_python_dependencies.sh b/tools/install_python_dependencies.sh index c2db249cf..4454845fc 100755 --- a/tools/install_python_dependencies.sh +++ b/tools/install_python_dependencies.sh @@ -25,7 +25,5 @@ source .venv/bin/activate if [[ "$(uname)" == 'Darwin' ]]; then touch "$ROOT"/.env - echo "# msgq doesn't work on mac" >> "$ROOT"/.env - echo "export ZMQ=1" >> "$ROOT"/.env echo "export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES" >> "$ROOT"/.env fi From 85cdb2ed9abc5153aa0dd377c83a84126cf80334 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 27 Dec 2025 23:15:18 -0800 Subject: [PATCH 699/910] fix(url_file): ensure seek position is always an integer (#36960) --- tools/lib/url_file.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/lib/url_file.py b/tools/lib/url_file.py index c791444f7..c7ccab1ae 100644 --- a/tools/lib/url_file.py +++ b/tools/lib/url_file.py @@ -164,7 +164,7 @@ class URLFile: return parts def seek(self, pos: int) -> None: - self._pos = pos + self._pos = int(pos) @property def name(self) -> str: From ea01a53711a01d4d88c1e4e8ae9cb7e93b7a070e Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 28 Dec 2025 10:42:49 -0800 Subject: [PATCH 700/910] switch from mypy to ty (#36961) --- common/git.py | 12 +-- common/prefix.py | 2 +- pyproject.toml | 78 ++++++++++--------- scripts/lint/lint.sh | 12 +-- selfdrive/debug/cycle_alerts.py | 6 +- selfdrive/debug/fingerprint_from_route.py | 11 +-- selfdrive/locationd/calibrationd.py | 2 +- selfdrive/locationd/helpers.py | 2 +- selfdrive/selfdrived/alertmanager.py | 2 +- selfdrive/test/fuzzy_generation.py | 2 +- selfdrive/test/process_replay/migration.py | 3 +- .../test/process_replay/process_replay.py | 6 +- selfdrive/test/process_replay/regen.py | 2 +- selfdrive/test/update_ci_routes.py | 2 +- selfdrive/ui/layouts/home.py | 2 +- selfdrive/ui/mici/widgets/button.py | 16 ++-- selfdrive/ui/mici/widgets/dialog.py | 4 +- selfdrive/ui/translations/auto_translate.py | 2 +- system/athena/athenad.py | 4 +- system/athena/tests/test_athenad.py | 2 +- system/loggerd/tests/loggerd_tests_common.py | 4 +- system/loggerd/tests/test_uploader.py | 2 +- system/loggerd/uploader.py | 2 +- system/manager/process.py | 2 +- system/ui/mici_updater.py | 17 ++-- system/ui/tici_updater.py | 17 ++-- system/ui/widgets/__init__.py | 6 +- system/ui/widgets/label.py | 4 +- system/ui/widgets/list_view.py | 11 +-- system/updated/casync/casync.py | 4 +- system/updated/updated.py | 2 +- system/webrtc/device/audio.py | 4 +- tools/jotpluggler/data.py | 2 +- tools/jotpluggler/views.py | 2 +- tools/lib/logreader.py | 5 +- tools/lib/vidindex.py | 2 +- uv.lock | 64 +++++++-------- 37 files changed, 160 insertions(+), 162 deletions(-) diff --git a/common/git.py b/common/git.py index 2296fa708..6b662e571 100644 --- a/common/git.py +++ b/common/git.py @@ -4,27 +4,27 @@ from openpilot.common.utils import run_cmd, run_cmd_default @cache -def get_commit(cwd: str = None, branch: str = "HEAD") -> str: +def get_commit(cwd: str | None = None, branch: str = "HEAD") -> str: return run_cmd_default(["git", "rev-parse", branch], cwd=cwd) @cache -def get_commit_date(cwd: str = None, commit: str = "HEAD") -> str: +def get_commit_date(cwd: str | None = None, commit: str = "HEAD") -> str: return run_cmd_default(["git", "show", "--no-patch", "--format='%ct %ci'", commit], cwd=cwd) @cache -def get_short_branch(cwd: str = None) -> str: +def get_short_branch(cwd: str | None = None) -> str: return run_cmd_default(["git", "rev-parse", "--abbrev-ref", "HEAD"], cwd=cwd) @cache -def get_branch(cwd: str = None) -> str: +def get_branch(cwd: str | None = None) -> str: return run_cmd_default(["git", "rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"], cwd=cwd) @cache -def get_origin(cwd: str = None) -> str: +def get_origin(cwd: str | None = None) -> str: try: local_branch = run_cmd(["git", "name-rev", "--name-only", "HEAD"], cwd=cwd) tracking_remote = run_cmd(["git", "config", "branch." + local_branch + ".remote"], cwd=cwd) @@ -34,7 +34,7 @@ def get_origin(cwd: str = None) -> str: @cache -def get_normalized_origin(cwd: str = None) -> str: +def get_normalized_origin(cwd: str | None = None) -> str: return get_origin(cwd) \ .replace("git@", "", 1) \ .replace(".git", "", 1) \ diff --git a/common/prefix.py b/common/prefix.py index 86f69671d..d0a5f9262 100644 --- a/common/prefix.py +++ b/common/prefix.py @@ -10,7 +10,7 @@ from openpilot.system.hardware.hw import Paths from openpilot.system.hardware.hw import DEFAULT_DOWNLOAD_CACHE_ROOT class OpenpilotPrefix: - def __init__(self, prefix: str = None, create_dirs_on_enter: bool = True, clean_dirs_on_exit: bool = True, shared_download_cache: bool = False): + def __init__(self, prefix: str | None = None, create_dirs_on_enter: bool = True, clean_dirs_on_exit: bool = True, shared_download_cache: bool = False): self.prefix = prefix if prefix else str(uuid.uuid4().hex[0:15]) shm_path = "/tmp" if platform.system() == "Darwin" else "/dev/shm" self.msgq_path = os.path.join(shm_path, "msgq_" + self.prefix) diff --git a/pyproject.toml b/pyproject.toml index fc80488c6..bb4ac0e8d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -87,7 +87,7 @@ docs = [ testing = [ "coverage", "hypothesis ==6.47.*", - "mypy", + "ty", "pytest", "pytest-cpp", "pytest-subtests", @@ -180,42 +180,6 @@ ignore-words-list = "bu,ro,te,ue,alo,hda,ois,nam,nams,ned,som,parm,setts,inout,w 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/*, *.po, uv.lock, *.onnx, ./cereal/gen/*, */c_generated_code/*, docs/assets/*, tools/plotjuggler/layouts/*, selfdrive/assets/offroad/mici_fcc.html" -[tool.mypy] -python_version = "3.11" -exclude = [ - "cereal/", - "msgq/", - "msgq_repo/", - "opendbc/", - "opendbc_repo/", - "panda/", - "rednose/", - "rednose_repo/", - "tinygrad/", - "tinygrad_repo/", - "teleoprtc/", - "teleoprtc_repo/", - "third_party/", -] - -# third-party packages -ignore_missing_imports=true - -# helpful warnings -warn_redundant_casts=true -warn_unreachable=true -warn_unused_ignores=true - -# restrict dynamic typing -warn_return_any=true - -# allow implicit optionals for default args -implicit_optional = true - -local_partial_types=true -explicit_package_bases=true -disable_error_code = "annotation-unchecked" - # https://beta.ruff.rs/docs/configuration/#using-pyprojecttoml [tool.ruff] indent-width = 2 @@ -274,3 +238,43 @@ lint.flake8-implicit-str-concat.allow-multiline = false [tool.ruff.format] quote-style = "preserve" + +[tool.ty.src] +exclude = [ + "cereal/", + "msgq/", + "msgq_repo/", + "opendbc/", + "opendbc_repo/", + "panda/", + "rednose/", + "rednose_repo/", + "tinygrad/", + "tinygrad_repo/", + "teleoprtc/", + "teleoprtc_repo/", + "third_party/", +] + +[tool.ty.rules] +# Ignore unresolved imports for Cython-compiled modules (.pyx) +unresolved-import = "ignore" +# Ignore unresolved attributes - many from capnp and Cython modules +unresolved-attribute = "ignore" +# Ignore invalid method overrides - signature variance issues +invalid-method-override = "ignore" +# Ignore possibly-missing-attribute - too many false positives +possibly-missing-attribute = "ignore" +# Ignore invalid assignment - often intentional monkey-patching +invalid-assignment = "ignore" +# Ignore no-matching-overload - numpy/ctypes overload matching issues +no-matching-overload = "ignore" +# Ignore invalid-argument-type - many false positives from raylib, ctypes, numpy +invalid-argument-type = "ignore" +# Ignore call-non-callable - false positives from dynamic types +call-non-callable = "ignore" +# Ignore unsupported-operator - false positives from dynamic types +unsupported-operator = "ignore" +# Ignore non-subscriptable - false positives from dynamic types +non-subscriptable = "ignore" +# not-iterable errors are now fixed diff --git a/scripts/lint/lint.sh b/scripts/lint/lint.sh index 578c63cd1..5581171e8 100755 --- a/scripts/lint/lint.sh +++ b/scripts/lint/lint.sh @@ -55,7 +55,7 @@ function run_tests() { run "check_nomerge_comments" $DIR/check_nomerge_comments.sh $ALL_FILES if [[ -z "$FAST" ]]; then - run "mypy" mypy $PYTHON_FILES + run "ty" ty check run "codespell" codespell $ALL_FILES fi @@ -69,7 +69,7 @@ function help() { echo "" echo -e "${BOLD}${UNDERLINE}Tests:${NC}" echo -e " ${BOLD}ruff${NC}" - echo -e " ${BOLD}mypy${NC}" + echo -e " ${BOLD}ty${NC}" echo -e " ${BOLD}codespell${NC}" echo -e " ${BOLD}check_added_large_files${NC}" echo -e " ${BOLD}check_shebang_scripts_are_executable${NC}" @@ -81,11 +81,11 @@ function help() { echo " Specify tests to skip separated by spaces" echo "" echo -e "${BOLD}${UNDERLINE}Examples:${NC}" - echo " op lint mypy ruff" - echo " Only run the mypy and ruff tests" + echo " op lint ty ruff" + echo " Only run the ty and ruff tests" echo "" - echo " op lint --skip mypy ruff" - echo " Skip the mypy and ruff tests" + echo " op lint --skip ty ruff" + echo " Skip the ty and ruff tests" echo "" echo " op lint" echo " Run all the tests" diff --git a/selfdrive/debug/cycle_alerts.py b/selfdrive/debug/cycle_alerts.py index 11e75a7a8..00fa33ac6 100755 --- a/selfdrive/debug/cycle_alerts.py +++ b/selfdrive/debug/cycle_alerts.py @@ -99,8 +99,7 @@ def cycle_alerts(duration=200, is_metric=False): alert = AM.process_alerts(frame, []) print(alert) for _ in range(duration): - dat = messaging.new_message() - dat.init('selfdriveState') + dat = messaging.new_message('selfdriveState') dat.selfdriveState.enabled = False if alert: @@ -112,8 +111,7 @@ def cycle_alerts(duration=200, is_metric=False): dat.selfdriveState.alertSound = alert.audible_alert pm.send('selfdriveState', dat) - dat = messaging.new_message() - dat.init('deviceState') + dat = messaging.new_message('deviceState') dat.deviceState.started = True pm.send('deviceState', dat) diff --git a/selfdrive/debug/fingerprint_from_route.py b/selfdrive/debug/fingerprint_from_route.py index 179ff4c83..5fd46f5b7 100755 --- a/selfdrive/debug/fingerprint_from_route.py +++ b/selfdrive/debug/fingerprint_from_route.py @@ -28,11 +28,12 @@ def get_fingerprint(lr): # TODO: also print the fw fingerprint merged with the existing ones # show FW fingerprint - print("\nFW fingerprint:\n") - for f in fw: - print(f" (Ecu.{f.ecu}, {hex(f.address)}, {None if f.subAddress == 0 else f.subAddress}): [") - print(f" {f.fwVersion},") - print(" ],") + if fw: + print("\nFW fingerprint:\n") + for f in fw: + print(f" (Ecu.{f.ecu}, {hex(f.address)}, {None if f.subAddress == 0 else f.subAddress}): [") + print(f" {f.fwVersion},") + print(" ],") print() print(f"VIN: {vin}") diff --git a/selfdrive/locationd/calibrationd.py b/selfdrive/locationd/calibrationd.py index 03c044982..3843cf37f 100755 --- a/selfdrive/locationd/calibrationd.py +++ b/selfdrive/locationd/calibrationd.py @@ -92,7 +92,7 @@ class Calibrator: valid_blocks: int = 0, wide_from_device_euler_init: np.ndarray = WIDE_FROM_DEVICE_EULER_INIT, height_init: np.ndarray = HEIGHT_INIT, - smooth_from: np.ndarray = None) -> None: + smooth_from: np.ndarray | None = None) -> None: if not np.isfinite(rpy_init).all(): self.rpy = RPY_INIT.copy() else: diff --git a/selfdrive/locationd/helpers.py b/selfdrive/locationd/helpers.py index 2a3ac8b86..73c4d8bf3 100644 --- a/selfdrive/locationd/helpers.py +++ b/selfdrive/locationd/helpers.py @@ -94,7 +94,7 @@ class PointBuckets: def add_point(self, x: float, y: float) -> None: raise NotImplementedError - def get_points(self, num_points: int = None) -> Any: + def get_points(self, num_points: int | None = None) -> Any: points = np.vstack([x.arr for x in self.buckets.values()]) if num_points is None: return points diff --git a/selfdrive/selfdrived/alertmanager.py b/selfdrive/selfdrived/alertmanager.py index 251d32ba9..385c276a9 100644 --- a/selfdrive/selfdrived/alertmanager.py +++ b/selfdrive/selfdrived/alertmanager.py @@ -13,7 +13,7 @@ with open(os.path.join(BASEDIR, "selfdrive/selfdrived/alerts_offroad.json")) as OFFROAD_ALERTS = json.load(f) -def set_offroad_alert(alert: str, show_alert: bool, extra_text: str = None) -> None: +def set_offroad_alert(alert: str, show_alert: bool, extra_text: str | None = None) -> None: if show_alert: a = copy.copy(OFFROAD_ALERTS[alert]) a['extra'] = extra_text or '' diff --git a/selfdrive/test/fuzzy_generation.py b/selfdrive/test/fuzzy_generation.py index 94eb0dfaa..131dab47b 100644 --- a/selfdrive/test/fuzzy_generation.py +++ b/selfdrive/test/fuzzy_generation.py @@ -44,7 +44,7 @@ class FuzzyGenerator: except capnp.lib.capnp.KjException: return self.generate_struct(field.schema) - def generate_struct(self, schema: capnp.lib.capnp._StructSchema, event: str = None) -> st.SearchStrategy[dict[str, Any]]: + def generate_struct(self, schema: capnp.lib.capnp._StructSchema, event: str | None = None) -> st.SearchStrategy[dict[str, Any]]: single_fill: tuple[str, ...] = (event,) if event else (self.draw(st.sampled_from(schema.union_fields)),) if schema.union_fields else () fields_to_generate = schema.non_union_fields + single_fill return st.fixed_dictionaries({field: self.generate_field(schema.fields[field]) for field in fields_to_generate if not field.endswith('DEPRECATED')}) diff --git a/selfdrive/test/process_replay/migration.py b/selfdrive/test/process_replay/migration.py index 33b363cfd..232722d1b 100644 --- a/selfdrive/test/process_replay/migration.py +++ b/selfdrive/test/process_replay/migration.py @@ -1,5 +1,6 @@ from collections import defaultdict from collections.abc import Callable +from typing import cast import capnp import functools import traceback @@ -67,7 +68,7 @@ def migrate(lr: LogIterable, migration_funcs: list[MigrationFunc]): if migration.product in grouped: # skip if product already exists continue - sorted_indices = sorted(ii for i in migration.inputs for ii in grouped[i]) + sorted_indices = sorted(ii for i in cast(list[str], migration.inputs) for ii in grouped.get(i, [])) msg_gen = [(i, lr[i]) for i in sorted_indices] r_ops, a_ops, d_ops = migration(msg_gen) replace_ops.extend(r_ops) diff --git a/selfdrive/test/process_replay/process_replay.py b/selfdrive/test/process_replay/process_replay.py index 1144b7955..8af72e5f4 100755 --- a/selfdrive/test/process_replay/process_replay.py +++ b/selfdrive/test/process_replay/process_replay.py @@ -610,9 +610,9 @@ def replay_process_with_name(name: str | Iterable[str], lr: LogIterable, *args, def replay_process( - cfg: ProcessConfig | Iterable[ProcessConfig], lr: LogIterable, frs: dict[str, FrameReader] = None, - fingerprint: str = None, return_all_logs: bool = False, custom_params: dict[str, Any] = None, - captured_output_store: dict[str, dict[str, str]] = None, disable_progress: bool = False + cfg: ProcessConfig | Iterable[ProcessConfig], lr: LogIterable, frs: dict[str, FrameReader] | None = None, + fingerprint: str | None = None, return_all_logs: bool = False, custom_params: dict[str, Any] | None = None, + captured_output_store: dict[str, dict[str, str]] | None = None, disable_progress: bool = False ) -> list[capnp._DynamicStructReader]: if isinstance(cfg, Iterable): cfgs = list(cfg) diff --git a/selfdrive/test/process_replay/regen.py b/selfdrive/test/process_replay/regen.py index ec35a5c3a..c501a4b25 100755 --- a/selfdrive/test/process_replay/regen.py +++ b/selfdrive/test/process_replay/regen.py @@ -16,7 +16,7 @@ from openpilot.tools.lib.openpilotci import get_url def regen_segment( - lr: LogIterable, frs: dict[str, Any] = None, + lr: LogIterable, frs: dict[str, Any] | None = None, processes: Iterable[ProcessConfig] = CONFIGS, disable_tqdm: bool = False ) -> list[capnp._DynamicStructReader]: all_msgs = sorted(lr, key=lambda m: m.logMonoTime) diff --git a/selfdrive/test/update_ci_routes.py b/selfdrive/test/update_ci_routes.py index 2bf06b486..54e1c8871 100755 --- a/selfdrive/test/update_ci_routes.py +++ b/selfdrive/test/update_ci_routes.py @@ -19,7 +19,7 @@ SOURCES: list[AzureContainer] = [ DEST = OpenpilotCIContainer -def upload_route(path: str, exclude_patterns: Iterable[str] = None) -> None: +def upload_route(path: str, exclude_patterns: Iterable[str] | None = None) -> None: if exclude_patterns is None: exclude_patterns = [r'dcamera\.hevc'] diff --git a/selfdrive/ui/layouts/home.py b/selfdrive/ui/layouts/home.py index cd6ae600e..a8404a20c 100644 --- a/selfdrive/ui/layouts/home.py +++ b/selfdrive/ui/layouts/home.py @@ -39,7 +39,7 @@ class HomeLayout(Widget): self.current_state = HomeLayoutState.HOME self.last_refresh = 0 - self.settings_callback: callable | None = None + self.settings_callback: Callable[[], None] | None = None self.update_available = False self.alert_count = 0 diff --git a/selfdrive/ui/mici/widgets/button.py b/selfdrive/ui/mici/widgets/button.py index be08e0fee..82310577b 100644 --- a/selfdrive/ui/mici/widgets/button.py +++ b/selfdrive/ui/mici/widgets/button.py @@ -71,7 +71,7 @@ class BigCircleButton(Widget): class BigCircleToggle(BigCircleButton): - def __init__(self, icon: str, toggle_callback: Callable = None): + def __init__(self, icon: str, toggle_callback: Callable | None = None): super().__init__(icon, False) self._toggle_callback = toggle_callback @@ -251,7 +251,7 @@ class BigButton(Widget): class BigToggle(BigButton): - def __init__(self, text: str, value: str = "", initial_state: bool = False, toggle_callback: Callable = None): + def __init__(self, text: str, value: str = "", initial_state: bool = False, toggle_callback: Callable | None = None): super().__init__(text, value, "") self._checked = initial_state self._toggle_callback = toggle_callback @@ -288,8 +288,8 @@ class BigToggle(BigButton): class BigMultiToggle(BigToggle): - def __init__(self, text: str, options: list[str], toggle_callback: Callable = None, - select_callback: Callable = None): + def __init__(self, text: str, options: list[str], toggle_callback: Callable | None = None, + select_callback: Callable | None = None): super().__init__(text, "", toggle_callback=toggle_callback) assert len(options) > 0 self._options = options @@ -327,8 +327,8 @@ class BigMultiToggle(BigToggle): class BigMultiParamToggle(BigMultiToggle): - def __init__(self, text: str, param: str, options: list[str], toggle_callback: Callable = None, - select_callback: Callable = None): + def __init__(self, text: str, param: str, options: list[str], toggle_callback: Callable | None = None, + select_callback: Callable | None = None): super().__init__(text, options, toggle_callback, select_callback) self._param = param @@ -345,7 +345,7 @@ class BigMultiParamToggle(BigMultiToggle): class BigParamControl(BigToggle): - def __init__(self, text: str, param: str, toggle_callback: Callable = None): + def __init__(self, text: str, param: str, toggle_callback: Callable | None = None): super().__init__(text, "", toggle_callback=toggle_callback) self.param = param self.params = Params() @@ -361,7 +361,7 @@ class BigParamControl(BigToggle): # TODO: param control base class class BigCircleParamControl(BigCircleToggle): - def __init__(self, icon: str, param: str, toggle_callback: Callable = None): + def __init__(self, icon: str, param: str, toggle_callback: Callable | None = None): super().__init__(icon, toggle_callback) self._param = param self.params = Params() diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index 34760925a..abd558aa8 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -137,7 +137,7 @@ class BigInputDialog(BigDialogBase): hint: str, default_text: str = "", minimum_length: int = 1, - confirm_callback: Callable[[str], None] = None): + confirm_callback: Callable[[str], None] | None = None): super().__init__(None, None) self._hint_label = UnifiedLabel(hint, font_size=35, text_color=rl.Color(255, 255, 255, int(255 * 0.35)), font_weight=FontWeight.MEDIUM) @@ -317,7 +317,7 @@ class BigMultiOptionDialog(BigDialogBase): BACK_TOUCH_AREA_PERCENTAGE = 0.1 def __init__(self, options: list[str], default: str | None, - right_btn: str | None = 'check', right_btn_callback: Callable[[], None] = None): + right_btn: str | None = 'check', right_btn_callback: Callable[[], None] | None = None): super().__init__(right_btn, right_btn_callback=right_btn_callback) self._options = options if default is not None: diff --git a/selfdrive/ui/translations/auto_translate.py b/selfdrive/ui/translations/auto_translate.py index 6251e0339..9354790f9 100755 --- a/selfdrive/ui/translations/auto_translate.py +++ b/selfdrive/ui/translations/auto_translate.py @@ -18,7 +18,7 @@ OPENAI_PROMPT = "You are a professional translator from English to {language} (I "The following sentence or word is in the GUI of a software called openpilot, translate it accordingly." -def get_language_files(languages: list[str] = None) -> dict[str, pathlib.Path]: +def get_language_files(languages: list[str] | None = None) -> dict[str, pathlib.Path]: files = {} with open(TRANSLATIONS_LANGUAGES) as fp: diff --git a/system/athena/athenad.py b/system/athena/athenad.py index 3b71a9c31..b52ef21ba 100755 --- a/system/athena/athenad.py +++ b/system/athena/athenad.py @@ -314,7 +314,7 @@ def upload_handler(end_event: threading.Event) -> None: cloudlog.exception("athena.upload_handler.exception") -def _do_upload(upload_item: UploadItem, callback: Callable = None) -> requests.Response: +def _do_upload(upload_item: UploadItem, callback: Callable | None = None) -> requests.Response: path = upload_item.path compress = False @@ -805,7 +805,7 @@ def backoff(retries: int) -> int: return random.randrange(0, min(128, int(2 ** retries))) -def main(exit_event: threading.Event = None): +def main(exit_event: threading.Event | None = None): try: set_core_affinity([0, 1, 2, 3]) except Exception: diff --git a/system/athena/tests/test_athenad.py b/system/athena/tests/test_athenad.py index 99ac3b1c6..5b3e0b41f 100644 --- a/system/athena/tests/test_athenad.py +++ b/system/athena/tests/test_athenad.py @@ -97,7 +97,7 @@ class TestAthenadMethods: break @staticmethod - def _create_file(file: str, parent: str = None, data: bytes = b'') -> str: + def _create_file(file: str, parent: str | None = None, data: bytes = b'') -> str: fn = os.path.join(Paths.log_root() if parent is None else parent, file) os.makedirs(os.path.dirname(fn), exist_ok=True) with open(fn, 'wb') as f: diff --git a/system/loggerd/tests/loggerd_tests_common.py b/system/loggerd/tests/loggerd_tests_common.py index 87c3da65c..8bf609ae8 100644 --- a/system/loggerd/tests/loggerd_tests_common.py +++ b/system/loggerd/tests/loggerd_tests_common.py @@ -10,7 +10,7 @@ from openpilot.system.hardware.hw import Paths from openpilot.system.loggerd.xattr_cache import setxattr -def create_random_file(file_path: Path, size_mb: float, lock: bool = False, upload_xattr: bytes = None) -> None: +def create_random_file(file_path: Path, size_mb: float, lock: bool = False, upload_xattr: bytes | None = None) -> None: file_path.parent.mkdir(parents=True, exist_ok=True) if lock: @@ -80,7 +80,7 @@ class UploaderTestCase: self.params.put("DongleId", "0000000000000000") def make_file_with_data(self, f_dir: str, fn: str, size_mb: float = .1, lock: bool = False, - upload_xattr: bytes = None, preserve_xattr: bytes = None) -> Path: + upload_xattr: bytes | None = None, preserve_xattr: bytes | None = None) -> Path: file_path = Path(Paths.log_root()) / f_dir / fn create_random_file(file_path, size_mb, lock, upload_xattr) diff --git a/system/loggerd/tests/test_uploader.py b/system/loggerd/tests/test_uploader.py index 961a8aa36..562bc068e 100644 --- a/system/loggerd/tests/test_uploader.py +++ b/system/loggerd/tests/test_uploader.py @@ -50,7 +50,7 @@ class TestUploader(UploaderTestCase): self.end_event.set() self.up_thread.join() - def gen_files(self, lock=False, xattr: bytes = None, boot=True) -> list[Path]: + def gen_files(self, lock=False, xattr: bytes | None = None, boot=True) -> list[Path]: f_paths = [] for t in ["qlog", "rlog", "dcamera.hevc", "fcamera.hevc"]: f_paths.append(self.make_file_with_data(self.seg_dir, t, 1, lock=lock, upload_xattr=xattr)) diff --git a/system/loggerd/uploader.py b/system/loggerd/uploader.py index 5b6234e1d..8ac38b6df 100755 --- a/system/loggerd/uploader.py +++ b/system/loggerd/uploader.py @@ -226,7 +226,7 @@ class Uploader: return self.upload(name, key, fn, network_type, metered) -def main(exit_event: threading.Event = None) -> None: +def main(exit_event: threading.Event | None = None) -> None: if exit_event is None: exit_event = threading.Event() diff --git a/system/manager/process.py b/system/manager/process.py index 1e2419826..36e1ba77b 100644 --- a/system/manager/process.py +++ b/system/manager/process.py @@ -81,7 +81,7 @@ class ManagerProcess(ABC): self.stop(sig=signal.SIGKILL) self.start() - def stop(self, retry: bool = True, block: bool = True, sig: signal.Signals = None) -> int | None: + def stop(self, retry: bool = True, block: bool = True, sig: signal.Signals | None = None) -> int | None: if self.proc is None: return None diff --git a/system/ui/mici_updater.py b/system/ui/mici_updater.py index 2ae2f7cc1..7ebb4262f 100755 --- a/system/ui/mici_updater.py +++ b/system/ui/mici_updater.py @@ -102,14 +102,15 @@ class Updater(Widget): self.process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, bufsize=1, universal_newlines=True) - for line in self.process.stdout: - parts = line.strip().split(":") - if len(parts) == 2: - self.progress_text = parts[0].lower() - try: - self.progress_value = int(float(parts[1])) - except ValueError: - pass + if self.process.stdout is not None: + for line in self.process.stdout: + parts = line.strip().split(":") + if len(parts) == 2: + self.progress_text = parts[0].lower() + try: + self.progress_value = int(float(parts[1])) + except ValueError: + pass exit_code = self.process.wait() if exit_code == 0: diff --git a/system/ui/tici_updater.py b/system/ui/tici_updater.py index 2e1a8687e..ebf4b3bec 100755 --- a/system/ui/tici_updater.py +++ b/system/ui/tici_updater.py @@ -71,14 +71,15 @@ class Updater(Widget): self.process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, bufsize=1, universal_newlines=True) - for line in self.process.stdout: - parts = line.strip().split(":") - if len(parts) == 2: - self.progress_text = parts[0] - try: - self.progress_value = int(float(parts[1])) - except ValueError: - pass + if self.process.stdout is not None: + for line in self.process.stdout: + parts = line.strip().split(":") + if len(parts) == 2: + self.progress_text = parts[0] + try: + self.progress_value = int(float(parts[1])) + except ValueError: + pass exit_code = self.process.wait() if exit_code == 0: diff --git a/system/ui/widgets/__init__.py b/system/ui/widgets/__init__.py index 8a46a3e43..4f3c977d2 100644 --- a/system/ui/widgets/__init__.py +++ b/system/ui/widgets/__init__.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import abc import pyray as rl from enum import IntEnum @@ -91,7 +93,7 @@ class Widget(abc.ABC): return self._rect return rl.get_collision_rec(self._rect, self._parent_rect) - def render(self, rect: rl.Rectangle = None) -> bool | int | None: + def render(self, rect: rl.Rectangle | None = None) -> bool | int | None: if rect is not None: self.set_rect(rect) @@ -359,7 +361,7 @@ class NavWidget(Widget, abc.ABC): self.set_position(self._rect.x, new_y) - def render(self, rect: rl.Rectangle = None) -> bool | int | None: + def render(self, rect: rl.Rectangle | None = None) -> bool | int | None: ret = super().render(rect) if self.back_enabled: diff --git a/system/ui/widgets/label.py b/system/ui/widgets/label.py index 97b293083..cb0cf66b1 100644 --- a/system/ui/widgets/label.py +++ b/system/ui/widgets/label.py @@ -31,13 +31,13 @@ class MiciLabel(Widget): def __init__(self, text: str, font_size: int = DEFAULT_TEXT_SIZE, - width: int = None, + width: int | None = None, 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_TOP, spacing: int = 0, - line_height: int = None, + line_height: int | None = None, elide_right: bool = True, wrap_text: bool = False, scroll: bool = False): diff --git a/system/ui/widgets/list_view.py b/system/ui/widgets/list_view.py index cfb8ab58a..32bf01cfc 100644 --- a/system/ui/widgets/list_view.py +++ b/system/ui/widgets/list_view.py @@ -174,8 +174,8 @@ class TextAction(ItemAction): class DualButtonAction(ItemAction): - def __init__(self, left_text: str | Callable[[], str], right_text: str | Callable[[], str], left_callback: Callable = None, - right_callback: Callable = None, enabled: bool | Callable[[], bool] = True): + def __init__(self, left_text: str | Callable[[], str], right_text: str | Callable[[], str], left_callback: Callable | None = None, + right_callback: Callable | None = None, enabled: bool | Callable[[], bool] = True): super().__init__(width=0, enabled=enabled) # Width 0 means use full width self.left_button = Button(left_text, click_callback=left_callback, button_style=ButtonStyle.NORMAL, text_padding=0) self.right_button = Button(right_text, click_callback=right_callback, button_style=ButtonStyle.DANGER, text_padding=0) @@ -207,7 +207,7 @@ class DualButtonAction(ItemAction): class MultipleButtonAction(ItemAction): - def __init__(self, buttons: list[str | Callable[[], str]], button_width: int, selected_index: int = 0, callback: Callable = None): + def __init__(self, buttons: list[str | Callable[[], str]], button_width: int, selected_index: int = 0, callback: Callable | None = None): super().__init__(width=len(buttons) * button_width + (len(buttons) - 1) * RIGHT_ITEM_PADDING, enabled=True) self.buttons = buttons self.button_width = button_width @@ -454,13 +454,14 @@ def text_item(title: str | Callable[[], str], value: str | Callable[[], str], de return ListItem(title=title, description=description, action_item=action, callback=callback) -def dual_button_item(left_text: str | Callable[[], str], right_text: str | Callable[[], str], left_callback: Callable = None, right_callback: Callable = None, +def dual_button_item(left_text: str | Callable[[], str], right_text: str | Callable[[], str], + left_callback: Callable | None = None, right_callback: Callable | None = None, description: str | Callable[[], str] | None = None, enabled: bool | Callable[[], bool] = True) -> ListItem: action = DualButtonAction(left_text, right_text, left_callback, right_callback, enabled) return ListItem(title="", description=description, action_item=action) def multiple_button_item(title: str | Callable[[], str], description: str | Callable[[], str], buttons: list[str | Callable[[], str]], selected_index: int, - button_width: int = BUTTON_WIDTH, callback: Callable = None, icon: str = ""): + button_width: int = BUTTON_WIDTH, callback: Callable | None = None, icon: str = ""): action = MultipleButtonAction(buttons, button_width, selected_index, callback=callback) return ListItem(title=title, description=description, icon=icon, action_item=action) diff --git a/system/updated/casync/casync.py b/system/updated/casync/casync.py index 79ac26f1c..2bd46a1ff 100755 --- a/system/updated/casync/casync.py +++ b/system/updated/casync/casync.py @@ -168,7 +168,7 @@ def build_chunk_dict(chunks: list[Chunk]) -> ChunkDict: def extract(target: list[Chunk], sources: list[tuple[str, ChunkReader, ChunkDict]], out_path: str, - progress: Callable[[int], None] = None): + progress: Callable[[int], None] | None = None): stats: dict[str, int] = defaultdict(int) mode = 'rb+' if os.path.exists(out_path) else 'wb' @@ -208,7 +208,7 @@ def extract_directory(target: list[Chunk], sources: list[tuple[str, ChunkReader, ChunkDict]], out_path: str, tmp_file: str, - progress: Callable[[int], None] = None): + progress: Callable[[int], None] | None = None): """extract a directory stored as a casync tar archive""" stats = extract(target, sources, tmp_file, progress) diff --git a/system/updated/updated.py b/system/updated/updated.py index a4a1f8f34..ffd10e038 100755 --- a/system/updated/updated.py +++ b/system/updated/updated.py @@ -68,7 +68,7 @@ def write_time_to_param(params, param) -> None: t = datetime.datetime.now(datetime.UTC).replace(tzinfo=None) params.put(param, t) -def run(cmd: list[str], cwd: str = None) -> str: +def run(cmd: list[str], cwd: str | None = None) -> str: return subprocess.check_output(cmd, cwd=cwd, stderr=subprocess.STDOUT, encoding='utf8') diff --git a/system/webrtc/device/audio.py b/system/webrtc/device/audio.py index 4b22033e0..b1859518a 100644 --- a/system/webrtc/device/audio.py +++ b/system/webrtc/device/audio.py @@ -16,7 +16,7 @@ class AudioInputStreamTrack(aiortc.mediastreams.AudioStreamTrack): pyaudio.paFloat32: 'flt', } - def __init__(self, audio_format: int = pyaudio.paInt16, rate: int = 16000, channels: int = 1, packet_time: float = 0.020, device_index: int = None): + def __init__(self, audio_format: int = pyaudio.paInt16, rate: int = 16000, channels: int = 1, packet_time: float = 0.020, device_index: int | None = None): super().__init__() self.p = pyaudio.PyAudio() @@ -48,7 +48,7 @@ class AudioInputStreamTrack(aiortc.mediastreams.AudioStreamTrack): class AudioOutputSpeaker: - def __init__(self, audio_format: int = pyaudio.paInt16, rate: int = 48000, channels: int = 2, packet_time: float = 0.2, device_index: int = None): + def __init__(self, audio_format: int = pyaudio.paInt16, rate: int = 48000, channels: int = 2, packet_time: float = 0.2, device_index: int | None = None): chunk_size = int(packet_time * rate) self.p = pyaudio.PyAudio() diff --git a/tools/jotpluggler/data.py b/tools/jotpluggler/data.py index cf27857d1..756b87bd2 100644 --- a/tools/jotpluggler/data.py +++ b/tools/jotpluggler/data.py @@ -9,7 +9,7 @@ from openpilot.selfdrive.test.process_replay.migration import migrate_all from openpilot.tools.lib.logreader import _LogFileReader, LogReader -def flatten_dict(d: dict, sep: str = "/", prefix: str = None) -> dict: +def flatten_dict(d: dict, sep: str = "/", prefix: str | None = None) -> dict: result = {} stack: list[tuple] = [(d, prefix)] diff --git a/tools/jotpluggler/views.py b/tools/jotpluggler/views.py index 1c4d9a8f3..cd723d161 100644 --- a/tools/jotpluggler/views.py +++ b/tools/jotpluggler/views.py @@ -9,7 +9,7 @@ from abc import ABC, abstractmethod class ViewPanel(ABC): """Abstract base class for all view panels that can be displayed in a plot container""" - def __init__(self, panel_id: str = None): + def __init__(self, panel_id: str | None = None): self.panel_id = panel_id or str(uuid.uuid4()) self.title = "Untitled Panel" diff --git a/tools/lib/logreader.py b/tools/lib/logreader.py index f9a90490b..9696c8524 100755 --- a/tools/lib/logreader.py +++ b/tools/lib/logreader.py @@ -13,7 +13,6 @@ import warnings import zstandard as zstd from collections.abc import Iterable, Iterator -from typing import cast from urllib.parse import parse_qs, urlparse from cereal import log as capnp_log @@ -180,7 +179,7 @@ def auto_source(identifier: str, sources: list[Source], default_mode: ReadMode) # We've found all files, return them if len(needed_seg_idxs) == 0: - return cast(list[str], list(valid_files.values())) + return list(valid_files.values()) else: raise FileNotFoundError(f"Did not find {fn} for seg idxs {needed_seg_idxs} of {sr.route_name}") @@ -245,7 +244,7 @@ class LogReader: return identifiers def __init__(self, identifier: str | list[str], default_mode: ReadMode = ReadMode.RLOG, - sources: list[Source] = None, sort_by_time=False, only_union_types=False): + sources: list[Source] | None = None, sort_by_time=False, only_union_types=False): if sources is None: sources = [internal_source, comma_api_source, openpilotci_source, comma_car_segments_source] diff --git a/tools/lib/vidindex.py b/tools/lib/vidindex.py index f2e4e9ca4..4aef6fb4d 100755 --- a/tools/lib/vidindex.py +++ b/tools/lib/vidindex.py @@ -140,7 +140,7 @@ def get_ue(dat: bytes, start_idx: int, skip_bits: int) -> tuple[int, int]: j -= 1 if prefix_val == 1 and prefix_len - 1 == suffix_len: - val = 2**(prefix_len-1) - 1 + suffix_val + val = int(2**(prefix_len-1) - 1 + suffix_val) size = prefix_len + suffix_len return val, size i += 1 diff --git a/uv.lock b/uv.lock index 53cb108c6..d8e3d8269 100644 --- a/uv.lock +++ b/uv.lock @@ -1188,41 +1188,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fd/69/b547032297c7e63ba2af494edba695d781af8a0c6e89e4d06cf848b21d80/multidict-6.6.4-py3-none-any.whl", hash = "sha256:27d8f8e125c07cb954e54d75d04905a9bba8a439c1d84aca94949d4d03d8601c", size = 12313, upload-time = "2025-08-11T12:08:46.891Z" }, ] -[[package]] -name = "mypy" -version = "1.18.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mypy-extensions" }, - { name = "pathspec" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c0/77/8f0d0001ffad290cef2f7f216f96c814866248a0b92a722365ed54648e7e/mypy-1.18.2.tar.gz", hash = "sha256:06a398102a5f203d7477b2923dda3634c36727fa5c237d8f859ef90c42a9924b", size = 3448846, upload-time = "2025-09-19T00:11:10.519Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/88/87/cafd3ae563f88f94eec33f35ff722d043e09832ea8530ef149ec1efbaf08/mypy-1.18.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:807d9315ab9d464125aa9fcf6d84fde6e1dc67da0b6f80e7405506b8ac72bc7f", size = 12731198, upload-time = "2025-09-19T00:09:44.857Z" }, - { url = "https://files.pythonhosted.org/packages/0f/e0/1e96c3d4266a06d4b0197ace5356d67d937d8358e2ee3ffac71faa843724/mypy-1.18.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:776bb00de1778caf4db739c6e83919c1d85a448f71979b6a0edd774ea8399341", size = 11817879, upload-time = "2025-09-19T00:09:47.131Z" }, - { url = "https://files.pythonhosted.org/packages/72/ef/0c9ba89eb03453e76bdac5a78b08260a848c7bfc5d6603634774d9cd9525/mypy-1.18.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1379451880512ffce14505493bd9fe469e0697543717298242574882cf8cdb8d", size = 12427292, upload-time = "2025-09-19T00:10:22.472Z" }, - { url = "https://files.pythonhosted.org/packages/1a/52/ec4a061dd599eb8179d5411d99775bec2a20542505988f40fc2fee781068/mypy-1.18.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1331eb7fd110d60c24999893320967594ff84c38ac6d19e0a76c5fd809a84c86", size = 13163750, upload-time = "2025-09-19T00:09:51.472Z" }, - { url = "https://files.pythonhosted.org/packages/c4/5f/2cf2ceb3b36372d51568f2208c021870fe7834cf3186b653ac6446511839/mypy-1.18.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ca30b50a51e7ba93b00422e486cbb124f1c56a535e20eff7b2d6ab72b3b2e37", size = 13351827, upload-time = "2025-09-19T00:09:58.311Z" }, - { url = "https://files.pythonhosted.org/packages/c8/7d/2697b930179e7277529eaaec1513f8de622818696857f689e4a5432e5e27/mypy-1.18.2-cp311-cp311-win_amd64.whl", hash = "sha256:664dc726e67fa54e14536f6e1224bcfce1d9e5ac02426d2326e2bb4e081d1ce8", size = 9757983, upload-time = "2025-09-19T00:10:09.071Z" }, - { url = "https://files.pythonhosted.org/packages/07/06/dfdd2bc60c66611dd8335f463818514733bc763e4760dee289dcc33df709/mypy-1.18.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:33eca32dd124b29400c31d7cf784e795b050ace0e1f91b8dc035672725617e34", size = 12908273, upload-time = "2025-09-19T00:10:58.321Z" }, - { url = "https://files.pythonhosted.org/packages/81/14/6a9de6d13a122d5608e1a04130724caf9170333ac5a924e10f670687d3eb/mypy-1.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3c47adf30d65e89b2dcd2fa32f3aeb5e94ca970d2c15fcb25e297871c8e4764", size = 11920910, upload-time = "2025-09-19T00:10:20.043Z" }, - { url = "https://files.pythonhosted.org/packages/5f/a9/b29de53e42f18e8cc547e38daa9dfa132ffdc64f7250e353f5c8cdd44bee/mypy-1.18.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d6c838e831a062f5f29d11c9057c6009f60cb294fea33a98422688181fe2893", size = 12465585, upload-time = "2025-09-19T00:10:33.005Z" }, - { url = "https://files.pythonhosted.org/packages/77/ae/6c3d2c7c61ff21f2bee938c917616c92ebf852f015fb55917fd6e2811db2/mypy-1.18.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01199871b6110a2ce984bde85acd481232d17413868c9807e95c1b0739a58914", size = 13348562, upload-time = "2025-09-19T00:10:11.51Z" }, - { url = "https://files.pythonhosted.org/packages/4d/31/aec68ab3b4aebdf8f36d191b0685d99faa899ab990753ca0fee60fb99511/mypy-1.18.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a2afc0fa0b0e91b4599ddfe0f91e2c26c2b5a5ab263737e998d6817874c5f7c8", size = 13533296, upload-time = "2025-09-19T00:10:06.568Z" }, - { url = "https://files.pythonhosted.org/packages/9f/83/abcb3ad9478fca3ebeb6a5358bb0b22c95ea42b43b7789c7fb1297ca44f4/mypy-1.18.2-cp312-cp312-win_amd64.whl", hash = "sha256:d8068d0afe682c7c4897c0f7ce84ea77f6de953262b12d07038f4d296d547074", size = 9828828, upload-time = "2025-09-19T00:10:28.203Z" }, - { url = "https://files.pythonhosted.org/packages/87/e3/be76d87158ebafa0309946c4a73831974d4d6ab4f4ef40c3b53a385a66fd/mypy-1.18.2-py3-none-any.whl", hash = "sha256:22a1748707dd62b58d2ae53562ffc4d7f8bcc727e8ac7cbc69c053ddc874d47e", size = 2352367, upload-time = "2025-09-19T00:10:15.489Z" }, -] - -[[package]] -name = "mypy-extensions" -version = "1.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, -] - [[package]] name = "natsort" version = "8.4.0" @@ -1386,7 +1351,6 @@ testing = [ { name = "codespell" }, { name = "coverage" }, { name = "hypothesis" }, - { name = "mypy" }, { name = "pre-commit-hooks" }, { name = "pytest" }, { name = "pytest-asyncio" }, @@ -1398,6 +1362,7 @@ testing = [ { name = "pytest-timeout" }, { name = "pytest-xdist" }, { name = "ruff" }, + { name = "ty" }, ] tools = [ { name = "dearpygui", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, @@ -1432,7 +1397,6 @@ requires-dist = [ { name = "matplotlib", marker = "extra == 'dev'" }, { name = "metadrive-simulator", marker = "platform_machine != 'aarch64' and extra == 'tools'", url = "https://github.com/commaai/metadrive/releases/download/MetaDrive-minimal-0.4.2.4/metadrive_simulator-0.4.2.4-py3-none-any.whl" }, { name = "mkdocs", marker = "extra == 'docs'" }, - { name = "mypy", marker = "extra == 'testing'" }, { name = "natsort", marker = "extra == 'docs'" }, { name = "numpy", specifier = ">=2.0" }, { name = "onnx", specifier = ">=1.14.0" }, @@ -1476,6 +1440,7 @@ requires-dist = [ { name = "sympy" }, { name = "tabulate", marker = "extra == 'dev'" }, { name = "tqdm" }, + { name = "ty", marker = "extra == 'testing'" }, { name = "types-requests", marker = "extra == 'dev'" }, { name = "types-tabulate", marker = "extra == 'dev'" }, { name = "websocket-client" }, @@ -4945,6 +4910,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" }, ] +[[package]] +name = "ty" +version = "0.0.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/43/8be3ec2e2ce6119cff9ee3a207fae0cb4f2b4f8ed6534175130a32be24a7/ty-0.0.7.tar.gz", hash = "sha256:90e53b20b86c418ee41a8385f17da44cc7f916f96f9eee87593423ce8292ca72", size = 4826677, upload-time = "2025-12-24T21:28:49.136Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/56/fafa123acf955089306372add312f16e97aba61f7c4daf74e2bb9c350d23/ty-0.0.7-py3-none-linux_armv6l.whl", hash = "sha256:b30105bd9a0b064497111c50c206d5b6a032f29bcf39f09a12085c3009d72784", size = 9862360, upload-time = "2025-12-24T21:28:36.762Z" }, + { url = "https://files.pythonhosted.org/packages/71/f4/9c30ff498d9a60e24f16d26c0cf93cd03a119913ffa720a77149f02df06e/ty-0.0.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b4df20889115f3d5611a9d9cdedc222e3fd82b5fe87bb0a9f7246e53a23becc7", size = 9712866, upload-time = "2025-12-24T21:28:25.926Z" }, + { url = "https://files.pythonhosted.org/packages/43/84/e06a4a6e4011890027ffee41efbf261b1335103d09009d625ace7f1a60eb/ty-0.0.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f699589d8511e1e17c5a7edfc5f4a4e80f2a6d4a3932a0e9e3422fd32d731472", size = 9221692, upload-time = "2025-12-24T21:28:29.649Z" }, + { url = "https://files.pythonhosted.org/packages/7a/e9/ebb4192d3627730125d40ee403a17dc91bab59d69c3eff286453b3218d01/ty-0.0.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3eaec2d8aa153ee4bcc43b17a384d0f9e66177c8c8127be3358b6b8348b9e3b", size = 9710340, upload-time = "2025-12-24T21:28:55.148Z" }, + { url = "https://files.pythonhosted.org/packages/8f/4a/ec144458a9cfb324d5cb471483094e62e74d73179343dff262a5cca1a1e1/ty-0.0.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:177d160295e6a56bdf0b61f6120bc4502fff301d4d10855ba711c109aa7f37fb", size = 9670317, upload-time = "2025-12-24T21:28:43.096Z" }, + { url = "https://files.pythonhosted.org/packages/b6/94/fe7106fd5e2ac06b81fba7b785a6216774618edc3fda9e17f58efe3cede6/ty-0.0.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30518b95ab5cc83615794cca765a5fb86df39a0d9c3dadc0ab2d787ab7830008", size = 10096517, upload-time = "2025-12-24T21:28:23.667Z" }, + { url = "https://files.pythonhosted.org/packages/45/d9/db96ccfd663c96bdd4bb63db72899198c01445012f939477a5318a563f14/ty-0.0.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:7867b3f75c2d9602cc6fb3b6d462580b707c2d112d4b27037142b0d01f8bfd03", size = 10996406, upload-time = "2025-12-24T21:28:39.134Z" }, + { url = "https://files.pythonhosted.org/packages/94/da/103915c08c3e6a14f95959614646fcdc9a240cd9a039fadbdcd086c819ee/ty-0.0.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:878d45858e209b7904753fbc5155f4cb75dadc20a26bbb77614bfef31580f9ae", size = 10712829, upload-time = "2025-12-24T21:28:27.745Z" }, + { url = "https://files.pythonhosted.org/packages/47/c0/d9be417bc8e459e13e9698978579eec9868f91f4c5d6ef663249967fec8b/ty-0.0.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:651820b193901825afce40ae68f6a51cd64dbfa4b81a45db90061401261f25e4", size = 10486541, upload-time = "2025-12-24T21:28:45.17Z" }, + { url = "https://files.pythonhosted.org/packages/ad/09/d1858c66620d8ae566e021ad0d7168914b1568841f8fe9e439116ce6b440/ty-0.0.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f56a5a0c1c045863b1b70c358a392b3f73b8528c5c571d409f19dd465525e116", size = 10255312, upload-time = "2025-12-24T21:28:53.17Z" }, + { url = "https://files.pythonhosted.org/packages/b6/0a/78f75089db491fd5fcc13d2845a0b2771b7f7d377450c64c6616e9c227bc/ty-0.0.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:748218fbc1f7b7f1b9d14e77d4f3d7fec72af794417e26b0185bdb94153afe1c", size = 9696201, upload-time = "2025-12-24T21:28:57.345Z" }, + { url = "https://files.pythonhosted.org/packages/01/9e/b26e94832fd563fef6f77a4487affc77a027b0e53106422c66aafb37fa01/ty-0.0.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1ff80f3985a52a7358b9069b4a8d223e92cf312544a934a062d6d3a4fb6876b3", size = 9688907, upload-time = "2025-12-24T21:28:59.485Z" }, + { url = "https://files.pythonhosted.org/packages/5a/8f/cc48601fb92c964cf6c34277e0d947076146b7de47aa11b5dbae45e01ce7/ty-0.0.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a808910ce672ba4446699f4c021283208f58f988bcfc3bdbdfc6e005819d9ee0", size = 9829982, upload-time = "2025-12-24T21:28:34.429Z" }, + { url = "https://files.pythonhosted.org/packages/b5/af/7fa9c2bfa25865968bded637f7e71f1a712f4fbede88f487b6a9101ab936/ty-0.0.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:2718fea5f314eda01703fb406ec89b1fc8710b3fc6a09bbd6f7a4f3502ddc889", size = 10361037, upload-time = "2025-12-24T21:28:47.027Z" }, + { url = "https://files.pythonhosted.org/packages/1c/5b/1a6ff1495975cd1c02aa8d03bc5c9d8006eaeb8bf354446f88d70f0518fd/ty-0.0.7-py3-none-win32.whl", hash = "sha256:ae89bb8dc50deb66f34eab3113aa61ac5d7f85ecf16279e5918548085a89021c", size = 9295092, upload-time = "2025-12-24T21:28:51.041Z" }, + { url = "https://files.pythonhosted.org/packages/ff/f6/47e9364635d048002354f84d2d0d6dfc9eb166dc67850739f88e1fec4fc5/ty-0.0.7-py3-none-win_amd64.whl", hash = "sha256:25bd20e3d4d0f07b422f9b42711ba24d28116031273bd23dbda66cec14df1c06", size = 10162816, upload-time = "2025-12-24T21:28:41.006Z" }, + { url = "https://files.pythonhosted.org/packages/7f/f4/c4fc28410c4493982b7481fb23f62bacb02fd2912ebec3b9bc7de18bebb8/ty-0.0.7-py3-none-win_arm64.whl", hash = "sha256:c87d27484dba9fca0053b6a9eee47eecc760aab2bbb8e6eab3d7f81531d1ad0c", size = 9653112, upload-time = "2025-12-24T21:28:31.562Z" }, +] + [[package]] name = "types-requests" version = "2.32.4.20250913" From ce1491df9c6bfcc45113de3a974973d6057933fc Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 28 Dec 2025 11:45:19 -0800 Subject: [PATCH 701/910] tools: add LRU eviction for log cache (#36959) * tools: add LRU for log cache * lil more * cleanup: * less syscall * manifest * cleanup * cleanup * lil more * cleanup * lil more * simpler * lil more --- tools/lib/tests/test_caching.py | 36 ++++++++++++++++++++++++++++- tools/lib/url_file.py | 41 ++++++++++++++++++++++++++++----- 2 files changed, 70 insertions(+), 7 deletions(-) diff --git a/tools/lib/tests/test_caching.py b/tools/lib/tests/test_caching.py index 2bb63b4dc..6e70ef90b 100644 --- a/tools/lib/tests/test_caching.py +++ b/tools/lib/tests/test_caching.py @@ -2,11 +2,13 @@ import http.server import os import shutil import socket +import tempfile import pytest from openpilot.selfdrive.test.helpers import http_server_context from openpilot.system.hardware.hw import Paths -from openpilot.tools.lib.url_file import URLFile +from openpilot.tools.lib.url_file import URLFile, prune_cache +import openpilot.tools.lib.url_file as url_file_module class CachingTestRequestHandler(http.server.BaseHTTPRequestHandler): @@ -128,3 +130,35 @@ class TestFileDownload: CachingTestRequestHandler.FILE_EXISTS = True length = URLFile(file_url).get_length() assert length == 4 + + +class TestCache: + def test_prune_cache(self, monkeypatch): + with tempfile.TemporaryDirectory() as tmpdir: + monkeypatch.setattr(Paths, 'download_cache_root', staticmethod(lambda: tmpdir + "/")) + + # setup test files and manifest + manifest_lines = [] + for i in range(3): + fname = f"hash_{i}" + with open(tmpdir + "/" + fname, "wb") as f: + f.truncate(1000) + manifest_lines.append(f"{fname} {1000 + i}") + with open(tmpdir + "/manifest.txt", "w") as f: + f.write('\n'.join(manifest_lines)) + + # under limit, shouldn't prune + assert len(os.listdir(tmpdir)) == 4 + prune_cache() + assert len(os.listdir(tmpdir)) == 4 + + # set a tiny cache limit to force eviction (1.5 chunks worth) + monkeypatch.setattr(url_file_module, 'CACHE_SIZE', url_file_module.CHUNK_SIZE + url_file_module.CHUNK_SIZE // 2) + + # prune_cache should evict oldest files to get under limit + prune_cache() + remaining = os.listdir(tmpdir) + # should have evicted at least one file + manifest + assert len(remaining) < 4 + # newest file should remain + assert manifest_lines[2].split()[0] in remaining diff --git a/tools/lib/url_file.py b/tools/lib/url_file.py index c7ccab1ae..790fa7e8f 100644 --- a/tools/lib/url_file.py +++ b/tools/lib/url_file.py @@ -1,8 +1,9 @@ -import re import logging import os +import re import socket -from hashlib import sha256 +import time +from hashlib import md5 from urllib3 import PoolManager, Retry from urllib3.response import BaseHTTPResponse from urllib3.util import Timeout @@ -14,14 +15,41 @@ from urllib3.exceptions import MaxRetryError # Cache chunk size K = 1000 CHUNK_SIZE = 1000 * K +CACHE_SIZE = 10 * 1024 * 1024 * 1024 # total cache size in GB logging.getLogger("urllib3").setLevel(logging.WARNING) -def hash_256(link: str) -> str: - return sha256((link.split("?")[0]).encode('utf-8')).hexdigest() +def hash_url(link: str) -> str: + return md5((link.split("?")[0]).encode('utf-8')).hexdigest() +def prune_cache(new_entry: str | None = None) -> None: + """Evicts oldest cache files (LRU) until cache is under the size limit.""" + # we use a manifest to avoid tons of os.stat syscalls (slow) + manifest = {} + manifest_path = Paths.download_cache_root() + "manifest.txt" + if os.path.exists(manifest_path): + with open(manifest_path) as f: + manifest = {parts[0]: int(parts[1]) for line in f if (parts := line.strip().split()) and len(parts) == 2} + + if new_entry: + manifest[new_entry] = int(time.time()) # noqa: TID251 + + # evict the least recently used files until under limit + sorted_items = sorted(manifest.items(), key=lambda x: x[1]) + while len(manifest) * CHUNK_SIZE > CACHE_SIZE and sorted_items: + key, _ = sorted_items.pop(0) + try: + os.remove(Paths.download_cache_root() + key) + except OSError: + pass + manifest.pop(key, None) + + # write out manifest + with atomic_write(manifest_path, mode="w", overwrite=True) as f: + f.write('\n'.join(f"{k} {v}" for k, v in manifest.items())) + class URLFileException(Exception): pass @@ -77,7 +105,7 @@ class URLFile: if self._length is not None: return self._length - file_length_path = os.path.join(Paths.download_cache_root(), hash_256(self._url) + "_length") + file_length_path = os.path.join(Paths.download_cache_root(), hash_url(self._url) + "_length") if not self._force_download and os.path.exists(file_length_path): with open(file_length_path) as file_length: content = file_length.read() @@ -103,7 +131,7 @@ class URLFile: while True: self._pos = position chunk_number = self._pos / CHUNK_SIZE - file_name = hash_256(self._url) + "_" + str(chunk_number) + file_name = hash_url(self._url) + "_" + str(chunk_number) full_path = os.path.join(Paths.download_cache_root(), str(file_name)) data = None # If we don't have a file, download it @@ -111,6 +139,7 @@ class URLFile: data = self.read_aux(ll=CHUNK_SIZE) with atomic_write(full_path, mode="wb", overwrite=True) as new_cached_file: new_cached_file.write(data) + prune_cache(file_name) else: with open(full_path, "rb") as cached_file: data = cached_file.read() From b58fddb83eab27504e0ba351d567a91e0d62e7e6 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 28 Dec 2025 12:20:45 -0800 Subject: [PATCH 702/910] replay: add --benchmark mode (#36957) --- tools/replay/main.cc | 27 +++++++++++++++ tools/replay/replay.cc | 76 ++++++++++++++++++++++++++++++++++++++--- tools/replay/replay.h | 17 ++++++++- tools/replay/seg_mgr.cc | 6 ++++ tools/replay/seg_mgr.h | 2 ++ 5 files changed, 123 insertions(+), 5 deletions(-) diff --git a/tools/replay/main.cc b/tools/replay/main.cc index 460901219..30f85b170 100644 --- a/tools/replay/main.cc +++ b/tools/replay/main.cc @@ -1,11 +1,13 @@ #include +#include #include #include #include #include #include "common/prefix.h" +#include "common/timing.h" #include "tools/replay/consoleui.h" #include "tools/replay/replay.h" #include "tools/replay/util.h" @@ -31,6 +33,7 @@ Options: --no-hw-decoder Disable HW video decoding --no-vipc Do not output video --all Output all messages including bookmarkButton, uiDebug, userBookmark + --benchmark Run in benchmark mode (process all events then exit with stats) -h, --help Show this help message )"; @@ -66,6 +69,7 @@ bool parseArgs(int argc, char *argv[], ReplayConfig &config) { {"no-hw-decoder", no_argument, nullptr, 0}, {"no-vipc", no_argument, nullptr, 0}, {"all", no_argument, nullptr, 0}, + {"benchmark", no_argument, nullptr, 0}, {"help", no_argument, nullptr, 'h'}, {nullptr, 0, nullptr, 0}, // Terminating entry }; @@ -79,6 +83,7 @@ bool parseArgs(int argc, char *argv[], ReplayConfig &config) { {"no-hw-decoder", REPLAY_FLAG_NO_HW_DECODER}, {"no-vipc", REPLAY_FLAG_NO_VIPC}, {"all", REPLAY_FLAG_ALL_SERVICES}, + {"benchmark", REPLAY_FLAG_BENCHMARK}, }; if (argc == 1) { @@ -149,6 +154,28 @@ int main(int argc, char *argv[]) { return 1; } + if (config.flags & REPLAY_FLAG_BENCHMARK) { + replay.start(config.start_seconds); + replay.waitForFinished(); + + const auto &stats = replay.getBenchmarkStats(); + uint64_t process_start = stats.process_start_ts; + + std::cout << "\n===== REPLAY BENCHMARK RESULTS =====\n"; + std::cout << "Route: " << replay.route().name() << "\n\n"; + + std::cout << "TIMELINE:\n"; + std::cout << " t=0 ms process start\n"; + for (const auto &[ts, event] : stats.timeline) { + double ms = (ts - process_start) / 1e6; + std::cout << " t=" << std::fixed << std::setprecision(0) << ms << " ms" + << std::string(std::max(1, 8 - static_cast(std::to_string(static_cast(ms)).length())), ' ') + << event << "\n"; + } + + return 0; + } + ConsoleUI console_ui(&replay); replay.start(config.start_seconds); return console_ui.exec(); diff --git a/tools/replay/replay.cc b/tools/replay/replay.cc index cc105dd10..d8d59e41a 100644 --- a/tools/replay/replay.cc +++ b/tools/replay/replay.cc @@ -2,6 +2,8 @@ #include #include +#include +#include #include "cereal/services.h" #include "common/params.h" #include "tools/replay/util.h" @@ -19,6 +21,14 @@ Replay::Replay(const std::string &route, std::vector allow, std::ve : sm_(sm), flags_(flags), seg_mgr_(std::make_unique(route, flags, data_dir, auto_source)) { std::signal(SIGUSR1, interrupt_sleep_handler); + if (flags_ & REPLAY_FLAG_BENCHMARK) { + benchmark_stats_.process_start_ts = nanos_since_boot(); + seg_mgr_->setBenchmarkCallback([this](int seg_num, const std::string& event) { + benchmark_stats_.timeline.emplace_back(nanos_since_boot(), + "segment " + std::to_string(seg_num) + " " + event); + }); + } + if (!(flags_ & REPLAY_FLAG_ALL_SERVICES)) { block.insert(block.end(), {"bookmarkButton", "uiDebug", "userBookmark"}); } @@ -78,8 +88,13 @@ Replay::~Replay() { bool Replay::load() { rInfo("loading route %s", seg_mgr_->route_.name().c_str()); + if (!seg_mgr_->load()) return false; + if (hasFlag(REPLAY_FLAG_BENCHMARK)) { + benchmark_stats_.timeline.emplace_back(nanos_since_boot(), "route metadata loaded"); + } + min_seconds_ = seg_mgr_->route_.segments().begin()->first * 60; max_seconds_ = (seg_mgr_->route_.segments().rbegin()->first + 1) * 60; return true; @@ -257,8 +272,13 @@ void Replay::streamThread() { stream_thread_id = pthread_self(); std::unique_lock lk(stream_lock_); + int last_processed_segment = -1; + uint64_t segment_start_time = 0; + bool streaming_started = false; + while (true) { stream_cv_.wait(lk, [this]() { return exit_ || (events_ready_ && !interrupt_requested_); }); + if (exit_) break; event_data_ = seg_mgr_->getEventData(); @@ -270,14 +290,19 @@ void Replay::streamThread() { continue; } - auto it = publishEvents(first, events.cend()); + if (!streaming_started && hasFlag(REPLAY_FLAG_BENCHMARK)) { + benchmark_stats_.timeline.emplace_back(nanos_since_boot(), "streaming started"); + streaming_started = true; + } + + auto it = publishEvents(first, events.cend(), last_processed_segment, segment_start_time); // Ensure frames are sent before unlocking to prevent race conditions if (camera_server_) { camera_server_->waitForSent(); } - if (it == events.cend() && !hasFlag(REPLAY_FLAG_NO_LOOP)) { + if (it == events.cend() && !hasFlag(REPLAY_FLAG_NO_LOOP) && !hasFlag(REPLAY_FLAG_BENCHMARK)) { int last_segment = seg_mgr_->route_.segments().rbegin()->first; if (event_data_->isSegmentLoaded(last_segment)) { rInfo("reaches the end of route, restart from beginning"); @@ -285,12 +310,28 @@ void Replay::streamThread() { seekTo(minSeconds(), false); stream_lock_.lock(); } + } else if (it == events.cend() && hasFlag(REPLAY_FLAG_BENCHMARK)) { + // Exit benchmark mode after first segment completes + exit_ = true; + break; } } + + if (hasFlag(REPLAY_FLAG_BENCHMARK)) { + benchmark_stats_.timeline.emplace_back(nanos_since_boot(), "benchmark done"); + + { + std::unique_lock lock(benchmark_lock_); + benchmark_done_ = true; + } + benchmark_cv_.notify_one(); + } } std::vector::const_iterator Replay::publishEvents(std::vector::const_iterator first, - std::vector::const_iterator last) { + std::vector::const_iterator last, + int &last_processed_segment, + uint64_t &segment_start_time) { uint64_t evt_start_ts = cur_mono_time_; uint64_t loop_start_ts = nanos_since_boot(); double prev_replay_speed = speed_; @@ -304,6 +345,23 @@ std::vector::const_iterator Replay::publishEvents(std::vector::con seg_mgr_->setCurrentSegment(segment); } + // Track segment completion for benchmark timeline + if (hasFlag(REPLAY_FLAG_BENCHMARK) && segment != last_processed_segment) { + if (last_processed_segment >= 0 && segment_start_time > 0) { + uint64_t processing_time_ns = nanos_since_boot() - segment_start_time; + double processing_time_ms = processing_time_ns / 1e6; + double realtime_factor = 60.0 / (processing_time_ns / 1e9); // 60s per segment + + std::ostringstream oss; + oss << "segment " << last_processed_segment << " done publishing (" + << std::fixed << std::setprecision(0) << processing_time_ms << " ms, " + << std::fixed << std::setprecision(0) << realtime_factor << "x realtime)"; + benchmark_stats_.timeline.emplace_back(nanos_since_boot(), oss.str()); + } + segment_start_time = nanos_since_boot(); + last_processed_segment = segment; + } + cur_mono_time_ = evt.mono_time; cur_which_ = evt.which; @@ -320,7 +378,8 @@ std::vector::const_iterator Replay::publishEvents(std::vector::con evt_start_ts = evt.mono_time; loop_start_ts = current_nanos; prev_replay_speed = speed_; - } else if (time_diff > 0) { + } else if (time_diff > 0 && !hasFlag(REPLAY_FLAG_BENCHMARK)) { + // Skip sleep in benchmark mode for maximum throughput precise_nano_sleep(time_diff, interrupt_requested_); } @@ -338,3 +397,12 @@ std::vector::const_iterator Replay::publishEvents(std::vector::con return first; } + +void Replay::waitForFinished() { + if (!hasFlag(REPLAY_FLAG_BENCHMARK)) { + return; + } + + std::unique_lock lock(benchmark_lock_); + benchmark_cv_.wait(lock, [this]() { return benchmark_done_; }); +} diff --git a/tools/replay/replay.h b/tools/replay/replay.h index 5e868d242..58c1b71b8 100644 --- a/tools/replay/replay.h +++ b/tools/replay/replay.h @@ -24,6 +24,12 @@ enum REPLAY_FLAGS { REPLAY_FLAG_NO_HW_DECODER = 0x0100, REPLAY_FLAG_NO_VIPC = 0x0400, REPLAY_FLAG_ALL_SERVICES = 0x0800, + REPLAY_FLAG_BENCHMARK = 0x1000, +}; + +struct BenchmarkStats { + uint64_t process_start_ts = 0; + std::vector> timeline; }; class Replay { @@ -57,6 +63,8 @@ public: inline const std::optional findAlertAtTime(double sec) const { return timeline_.findAlertAtTime(sec); } const std::shared_ptr getEventData() const { return seg_mgr_->getEventData(); } void installEventFilter(std::function filter) { event_filter_ = filter; } + void waitForFinished(); + const BenchmarkStats &getBenchmarkStats() const { return benchmark_stats_; } // Event callback functions std::function onSegmentsMerged = nullptr; @@ -72,7 +80,9 @@ private: void handleSegmentMerge(); void interruptStream(const std::function& update_fn); std::vector::const_iterator publishEvents(std::vector::const_iterator first, - std::vector::const_iterator last); + std::vector::const_iterator last, + int &last_processed_segment, + uint64_t &segment_start_time); void publishMessage(const Event *e); void publishFrame(const Event *e); void checkSeekProgress(); @@ -107,4 +117,9 @@ private: std::function event_filter_ = nullptr; std::shared_ptr event_data_ = std::make_shared(); + + BenchmarkStats benchmark_stats_; + std::condition_variable benchmark_cv_; + std::mutex benchmark_lock_; + bool benchmark_done_ = false; }; diff --git a/tools/replay/seg_mgr.cc b/tools/replay/seg_mgr.cc index f4e865d47..0778cacbc 100644 --- a/tools/replay/seg_mgr.cc +++ b/tools/replay/seg_mgr.cc @@ -118,9 +118,15 @@ void SegmentManager::loadSegmentsInRange(SegmentMap::iterator begin, SegmentMap: for (auto it = first; it != last; ++it) { auto &segment_ptr = it->second; if (!segment_ptr) { + if (onBenchmarkEvent_) { + onBenchmarkEvent_(it->first, "loading"); + } segment_ptr = std::make_shared( it->first, route_.at(it->first), flags_, filters_, [this](int seg_num, bool success) { + if (onBenchmarkEvent_) { + onBenchmarkEvent_(seg_num, success ? "loaded" : "load failed"); + } std::unique_lock lock(mutex_); needs_update_ = true; cv_.notify_one(); diff --git a/tools/replay/seg_mgr.h b/tools/replay/seg_mgr.h index 640169749..54e156fb6 100644 --- a/tools/replay/seg_mgr.h +++ b/tools/replay/seg_mgr.h @@ -27,6 +27,7 @@ public: bool load(); void setCurrentSegment(int seg_num); void setCallback(const std::function &callback) { onSegmentMergedCallback_ = callback; } + void setBenchmarkCallback(const std::function &callback) { onBenchmarkEvent_ = callback; } void setFilters(const std::vector &filters) { filters_ = filters; } const std::shared_ptr getEventData() const { return std::atomic_load(&event_data_); } bool hasSegment(int n) const { return segments_.find(n) != segments_.end(); } @@ -52,5 +53,6 @@ private: SegmentMap segments_; std::shared_ptr event_data_; std::function onSegmentMergedCallback_ = nullptr; + std::function onBenchmarkEvent_ = nullptr; std::set merged_segments_; }; From 883d1232d31bad9318d31d08ff5302f303192296 Mon Sep 17 00:00:00 2001 From: commaci-public <60409688+commaci-public@users.noreply.github.com> Date: Sun, 28 Dec 2025 13:00:35 -0800 Subject: [PATCH 703/910] [bot] Update Python packages (#36606) update Co-authored-by: Adeeb Shihadeh --- msgq_repo | 2 +- opendbc_repo | 2 +- panda | 2 +- tinygrad_repo | 2 +- uv.lock | 2615 +++++++++++++++++++++++++------------------------ 5 files changed, 1338 insertions(+), 1285 deletions(-) diff --git a/msgq_repo b/msgq_repo index 349bf449b..20f249385 160000 --- a/msgq_repo +++ b/msgq_repo @@ -1 +1 @@ -Subproject commit 349bf449b7329527162598de357bfecf232abc3f +Subproject commit 20f2493855ef32339b80f0ad76b3cb82210dc474 diff --git a/opendbc_repo b/opendbc_repo index 4aa7ca972..edf19be8e 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 4aa7ca972290af6c6bbd314d1975eab46a888b6f +Subproject commit edf19be8efe06a5387265adc4650725bb56b5c46 diff --git a/panda b/panda index 1ffad74f8..e42367df9 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 1ffad74f88e5683d9cd7c472e823928e28037e9e +Subproject commit e42367df97f1a5ea4ddb152566022c3ae4672e58 diff --git a/tinygrad_repo b/tinygrad_repo index 547304c47..f5090192c 160000 --- a/tinygrad_repo +++ b/tinygrad_repo @@ -1 +1 @@ -Subproject commit 547304c471b26ada0b34f400ccba67f3e1eb5965 +Subproject commit f5090192c84760be1227f7e3c4f99ad0603117ae diff --git a/uv.lock b/uv.lock index d8e3d8269..a5b529e52 100644 --- a/uv.lock +++ b/uv.lock @@ -21,7 +21,7 @@ wheels = [ [[package]] name = "aiohttp" -version = "3.12.15" +version = "3.13.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohappyeyeballs" }, @@ -32,55 +32,55 @@ dependencies = [ { name = "propcache" }, { name = "yarl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9b/e7/d92a237d8802ca88483906c388f7c201bbe96cd80a165ffd0ac2f6a8d59f/aiohttp-3.12.15.tar.gz", hash = "sha256:4fc61385e9c98d72fcdf47e6dd81833f47b2f77c114c29cd64a361be57a763a2", size = 7823716, upload-time = "2025-07-29T05:52:32.215Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/ce/3b83ebba6b3207a7135e5fcaba49706f8a4b6008153b4e30540c982fae26/aiohttp-3.13.2.tar.gz", hash = "sha256:40176a52c186aefef6eb3cad2cdd30cd06e3afbe88fe8ab2af9c0b90f228daca", size = 7837994, upload-time = "2025-10-28T20:59:39.937Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/20/19/9e86722ec8e835959bd97ce8c1efa78cf361fa4531fca372551abcc9cdd6/aiohttp-3.12.15-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d3ce17ce0220383a0f9ea07175eeaa6aa13ae5a41f30bc61d84df17f0e9b1117", size = 711246, upload-time = "2025-07-29T05:50:15.937Z" }, - { url = "https://files.pythonhosted.org/packages/71/f9/0a31fcb1a7d4629ac9d8f01f1cb9242e2f9943f47f5d03215af91c3c1a26/aiohttp-3.12.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:010cc9bbd06db80fe234d9003f67e97a10fe003bfbedb40da7d71c1008eda0fe", size = 483515, upload-time = "2025-07-29T05:50:17.442Z" }, - { url = "https://files.pythonhosted.org/packages/62/6c/94846f576f1d11df0c2e41d3001000527c0fdf63fce7e69b3927a731325d/aiohttp-3.12.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3f9d7c55b41ed687b9d7165b17672340187f87a773c98236c987f08c858145a9", size = 471776, upload-time = "2025-07-29T05:50:19.568Z" }, - { url = "https://files.pythonhosted.org/packages/f8/6c/f766d0aaafcee0447fad0328da780d344489c042e25cd58fde566bf40aed/aiohttp-3.12.15-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc4fbc61bb3548d3b482f9ac7ddd0f18c67e4225aaa4e8552b9f1ac7e6bda9e5", size = 1741977, upload-time = "2025-07-29T05:50:21.665Z" }, - { url = "https://files.pythonhosted.org/packages/17/e5/fb779a05ba6ff44d7bc1e9d24c644e876bfff5abe5454f7b854cace1b9cc/aiohttp-3.12.15-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7fbc8a7c410bb3ad5d595bb7118147dfbb6449d862cc1125cf8867cb337e8728", size = 1690645, upload-time = "2025-07-29T05:50:23.333Z" }, - { url = "https://files.pythonhosted.org/packages/37/4e/a22e799c2035f5d6a4ad2cf8e7c1d1bd0923192871dd6e367dafb158b14c/aiohttp-3.12.15-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:74dad41b3458dbb0511e760fb355bb0b6689e0630de8a22b1b62a98777136e16", size = 1789437, upload-time = "2025-07-29T05:50:25.007Z" }, - { url = "https://files.pythonhosted.org/packages/28/e5/55a33b991f6433569babb56018b2fb8fb9146424f8b3a0c8ecca80556762/aiohttp-3.12.15-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b6f0af863cf17e6222b1735a756d664159e58855da99cfe965134a3ff63b0b0", size = 1828482, upload-time = "2025-07-29T05:50:26.693Z" }, - { url = "https://files.pythonhosted.org/packages/c6/82/1ddf0ea4f2f3afe79dffed5e8a246737cff6cbe781887a6a170299e33204/aiohttp-3.12.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5b7fe4972d48a4da367043b8e023fb70a04d1490aa7d68800e465d1b97e493b", size = 1730944, upload-time = "2025-07-29T05:50:28.382Z" }, - { url = "https://files.pythonhosted.org/packages/1b/96/784c785674117b4cb3877522a177ba1b5e4db9ce0fd519430b5de76eec90/aiohttp-3.12.15-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6443cca89553b7a5485331bc9bedb2342b08d073fa10b8c7d1c60579c4a7b9bd", size = 1668020, upload-time = "2025-07-29T05:50:30.032Z" }, - { url = "https://files.pythonhosted.org/packages/12/8a/8b75f203ea7e5c21c0920d84dd24a5c0e971fe1e9b9ebbf29ae7e8e39790/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6c5f40ec615e5264f44b4282ee27628cea221fcad52f27405b80abb346d9f3f8", size = 1716292, upload-time = "2025-07-29T05:50:31.983Z" }, - { url = "https://files.pythonhosted.org/packages/47/0b/a1451543475bb6b86a5cfc27861e52b14085ae232896a2654ff1231c0992/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:2abbb216a1d3a2fe86dbd2edce20cdc5e9ad0be6378455b05ec7f77361b3ab50", size = 1711451, upload-time = "2025-07-29T05:50:33.989Z" }, - { url = "https://files.pythonhosted.org/packages/55/fd/793a23a197cc2f0d29188805cfc93aa613407f07e5f9da5cd1366afd9d7c/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:db71ce547012a5420a39c1b744d485cfb823564d01d5d20805977f5ea1345676", size = 1691634, upload-time = "2025-07-29T05:50:35.846Z" }, - { url = "https://files.pythonhosted.org/packages/ca/bf/23a335a6670b5f5dfc6d268328e55a22651b440fca341a64fccf1eada0c6/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ced339d7c9b5030abad5854aa5413a77565e5b6e6248ff927d3e174baf3badf7", size = 1785238, upload-time = "2025-07-29T05:50:37.597Z" }, - { url = "https://files.pythonhosted.org/packages/57/4f/ed60a591839a9d85d40694aba5cef86dde9ee51ce6cca0bb30d6eb1581e7/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:7c7dd29c7b5bda137464dc9bfc738d7ceea46ff70309859ffde8c022e9b08ba7", size = 1805701, upload-time = "2025-07-29T05:50:39.591Z" }, - { url = "https://files.pythonhosted.org/packages/85/e0/444747a9455c5de188c0f4a0173ee701e2e325d4b2550e9af84abb20cdba/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:421da6fd326460517873274875c6c5a18ff225b40da2616083c5a34a7570b685", size = 1718758, upload-time = "2025-07-29T05:50:41.292Z" }, - { url = "https://files.pythonhosted.org/packages/36/ab/1006278d1ffd13a698e5dd4bfa01e5878f6bddefc296c8b62649753ff249/aiohttp-3.12.15-cp311-cp311-win32.whl", hash = "sha256:4420cf9d179ec8dfe4be10e7d0fe47d6d606485512ea2265b0d8c5113372771b", size = 428868, upload-time = "2025-07-29T05:50:43.063Z" }, - { url = "https://files.pythonhosted.org/packages/10/97/ad2b18700708452400278039272032170246a1bf8ec5d832772372c71f1a/aiohttp-3.12.15-cp311-cp311-win_amd64.whl", hash = "sha256:edd533a07da85baa4b423ee8839e3e91681c7bfa19b04260a469ee94b778bf6d", size = 453273, upload-time = "2025-07-29T05:50:44.613Z" }, - { url = "https://files.pythonhosted.org/packages/63/97/77cb2450d9b35f517d6cf506256bf4f5bda3f93a66b4ad64ba7fc917899c/aiohttp-3.12.15-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:802d3868f5776e28f7bf69d349c26fc0efadb81676d0afa88ed00d98a26340b7", size = 702333, upload-time = "2025-07-29T05:50:46.507Z" }, - { url = "https://files.pythonhosted.org/packages/83/6d/0544e6b08b748682c30b9f65640d006e51f90763b41d7c546693bc22900d/aiohttp-3.12.15-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2800614cd560287be05e33a679638e586a2d7401f4ddf99e304d98878c29444", size = 476948, upload-time = "2025-07-29T05:50:48.067Z" }, - { url = "https://files.pythonhosted.org/packages/3a/1d/c8c40e611e5094330284b1aea8a4b02ca0858f8458614fa35754cab42b9c/aiohttp-3.12.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8466151554b593909d30a0a125d638b4e5f3836e5aecde85b66b80ded1cb5b0d", size = 469787, upload-time = "2025-07-29T05:50:49.669Z" }, - { url = "https://files.pythonhosted.org/packages/38/7d/b76438e70319796bfff717f325d97ce2e9310f752a267bfdf5192ac6082b/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e5a495cb1be69dae4b08f35a6c4579c539e9b5706f606632102c0f855bcba7c", size = 1716590, upload-time = "2025-07-29T05:50:51.368Z" }, - { url = "https://files.pythonhosted.org/packages/79/b1/60370d70cdf8b269ee1444b390cbd72ce514f0d1cd1a715821c784d272c9/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6404dfc8cdde35c69aaa489bb3542fb86ef215fc70277c892be8af540e5e21c0", size = 1699241, upload-time = "2025-07-29T05:50:53.628Z" }, - { url = "https://files.pythonhosted.org/packages/a3/2b/4968a7b8792437ebc12186db31523f541943e99bda8f30335c482bea6879/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3ead1c00f8521a5c9070fcb88f02967b1d8a0544e6d85c253f6968b785e1a2ab", size = 1754335, upload-time = "2025-07-29T05:50:55.394Z" }, - { url = "https://files.pythonhosted.org/packages/fb/c1/49524ed553f9a0bec1a11fac09e790f49ff669bcd14164f9fab608831c4d/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6990ef617f14450bc6b34941dba4f12d5613cbf4e33805932f853fbd1cf18bfb", size = 1800491, upload-time = "2025-07-29T05:50:57.202Z" }, - { url = "https://files.pythonhosted.org/packages/de/5e/3bf5acea47a96a28c121b167f5ef659cf71208b19e52a88cdfa5c37f1fcc/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd736ed420f4db2b8148b52b46b88ed038d0354255f9a73196b7bbce3ea97545", size = 1719929, upload-time = "2025-07-29T05:50:59.192Z" }, - { url = "https://files.pythonhosted.org/packages/39/94/8ae30b806835bcd1cba799ba35347dee6961a11bd507db634516210e91d8/aiohttp-3.12.15-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c5092ce14361a73086b90c6efb3948ffa5be2f5b6fbcf52e8d8c8b8848bb97c", size = 1635733, upload-time = "2025-07-29T05:51:01.394Z" }, - { url = "https://files.pythonhosted.org/packages/7a/46/06cdef71dd03acd9da7f51ab3a9107318aee12ad38d273f654e4f981583a/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:aaa2234bb60c4dbf82893e934d8ee8dea30446f0647e024074237a56a08c01bd", size = 1696790, upload-time = "2025-07-29T05:51:03.657Z" }, - { url = "https://files.pythonhosted.org/packages/02/90/6b4cfaaf92ed98d0ec4d173e78b99b4b1a7551250be8937d9d67ecb356b4/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6d86a2fbdd14192e2f234a92d3b494dd4457e683ba07e5905a0b3ee25389ac9f", size = 1718245, upload-time = "2025-07-29T05:51:05.911Z" }, - { url = "https://files.pythonhosted.org/packages/2e/e6/2593751670fa06f080a846f37f112cbe6f873ba510d070136a6ed46117c6/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a041e7e2612041a6ddf1c6a33b883be6a421247c7afd47e885969ee4cc58bd8d", size = 1658899, upload-time = "2025-07-29T05:51:07.753Z" }, - { url = "https://files.pythonhosted.org/packages/8f/28/c15bacbdb8b8eb5bf39b10680d129ea7410b859e379b03190f02fa104ffd/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5015082477abeafad7203757ae44299a610e89ee82a1503e3d4184e6bafdd519", size = 1738459, upload-time = "2025-07-29T05:51:09.56Z" }, - { url = "https://files.pythonhosted.org/packages/00/de/c269cbc4faa01fb10f143b1670633a8ddd5b2e1ffd0548f7aa49cb5c70e2/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:56822ff5ddfd1b745534e658faba944012346184fbfe732e0d6134b744516eea", size = 1766434, upload-time = "2025-07-29T05:51:11.423Z" }, - { url = "https://files.pythonhosted.org/packages/52/b0/4ff3abd81aa7d929b27d2e1403722a65fc87b763e3a97b3a2a494bfc63bc/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b2acbbfff69019d9014508c4ba0401822e8bae5a5fdc3b6814285b71231b60f3", size = 1726045, upload-time = "2025-07-29T05:51:13.689Z" }, - { url = "https://files.pythonhosted.org/packages/71/16/949225a6a2dd6efcbd855fbd90cf476052e648fb011aa538e3b15b89a57a/aiohttp-3.12.15-cp312-cp312-win32.whl", hash = "sha256:d849b0901b50f2185874b9a232f38e26b9b3d4810095a7572eacea939132d4e1", size = 423591, upload-time = "2025-07-29T05:51:15.452Z" }, - { url = "https://files.pythonhosted.org/packages/2b/d8/fa65d2a349fe938b76d309db1a56a75c4fb8cc7b17a398b698488a939903/aiohttp-3.12.15-cp312-cp312-win_amd64.whl", hash = "sha256:b390ef5f62bb508a9d67cb3bba9b8356e23b3996da7062f1a57ce1a79d2b3d34", size = 450266, upload-time = "2025-07-29T05:51:17.239Z" }, + { url = "https://files.pythonhosted.org/packages/35/74/b321e7d7ca762638cdf8cdeceb39755d9c745aff7a64c8789be96ddf6e96/aiohttp-3.13.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4647d02df098f6434bafd7f32ad14942f05a9caa06c7016fdcc816f343997dd0", size = 743409, upload-time = "2025-10-28T20:56:00.354Z" }, + { url = "https://files.pythonhosted.org/packages/99/3d/91524b905ec473beaf35158d17f82ef5a38033e5809fe8742e3657cdbb97/aiohttp-3.13.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e3403f24bcb9c3b29113611c3c16a2a447c3953ecf86b79775e7be06f7ae7ccb", size = 497006, upload-time = "2025-10-28T20:56:01.85Z" }, + { url = "https://files.pythonhosted.org/packages/eb/d3/7f68bc02a67716fe80f063e19adbd80a642e30682ce74071269e17d2dba1/aiohttp-3.13.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:43dff14e35aba17e3d6d5ba628858fb8cb51e30f44724a2d2f0c75be492c55e9", size = 493195, upload-time = "2025-10-28T20:56:03.314Z" }, + { url = "https://files.pythonhosted.org/packages/98/31/913f774a4708775433b7375c4f867d58ba58ead833af96c8af3621a0d243/aiohttp-3.13.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e2a9ea08e8c58bb17655630198833109227dea914cd20be660f52215f6de5613", size = 1747759, upload-time = "2025-10-28T20:56:04.904Z" }, + { url = "https://files.pythonhosted.org/packages/e8/63/04efe156f4326f31c7c4a97144f82132c3bb21859b7bb84748d452ccc17c/aiohttp-3.13.2-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53b07472f235eb80e826ad038c9d106c2f653584753f3ddab907c83f49eedead", size = 1704456, upload-time = "2025-10-28T20:56:06.986Z" }, + { url = "https://files.pythonhosted.org/packages/8e/02/4e16154d8e0a9cf4ae76f692941fd52543bbb148f02f098ca73cab9b1c1b/aiohttp-3.13.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e736c93e9c274fce6419af4aac199984d866e55f8a4cec9114671d0ea9688780", size = 1807572, upload-time = "2025-10-28T20:56:08.558Z" }, + { url = "https://files.pythonhosted.org/packages/34/58/b0583defb38689e7f06798f0285b1ffb3a6fb371f38363ce5fd772112724/aiohttp-3.13.2-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ff5e771f5dcbc81c64898c597a434f7682f2259e0cd666932a913d53d1341d1a", size = 1895954, upload-time = "2025-10-28T20:56:10.545Z" }, + { url = "https://files.pythonhosted.org/packages/6b/f3/083907ee3437425b4e376aa58b2c915eb1a33703ec0dc30040f7ae3368c6/aiohttp-3.13.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3b6fb0c207cc661fa0bf8c66d8d9b657331ccc814f4719468af61034b478592", size = 1747092, upload-time = "2025-10-28T20:56:12.118Z" }, + { url = "https://files.pythonhosted.org/packages/ac/61/98a47319b4e425cc134e05e5f3fc512bf9a04bf65aafd9fdcda5d57ec693/aiohttp-3.13.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:97a0895a8e840ab3520e2288db7cace3a1981300d48babeb50e7425609e2e0ab", size = 1606815, upload-time = "2025-10-28T20:56:14.191Z" }, + { url = "https://files.pythonhosted.org/packages/97/4b/e78b854d82f66bb974189135d31fce265dee0f5344f64dd0d345158a5973/aiohttp-3.13.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9e8f8afb552297aca127c90cb840e9a1d4bfd6a10d7d8f2d9176e1acc69bad30", size = 1723789, upload-time = "2025-10-28T20:56:16.101Z" }, + { url = "https://files.pythonhosted.org/packages/ed/fc/9d2ccc794fc9b9acd1379d625c3a8c64a45508b5091c546dea273a41929e/aiohttp-3.13.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:ed2f9c7216e53c3df02264f25d824b079cc5914f9e2deba94155190ef648ee40", size = 1718104, upload-time = "2025-10-28T20:56:17.655Z" }, + { url = "https://files.pythonhosted.org/packages/66/65/34564b8765ea5c7d79d23c9113135d1dd3609173da13084830f1507d56cf/aiohttp-3.13.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:99c5280a329d5fa18ef30fd10c793a190d996567667908bef8a7f81f8202b948", size = 1785584, upload-time = "2025-10-28T20:56:19.238Z" }, + { url = "https://files.pythonhosted.org/packages/30/be/f6a7a426e02fc82781afd62016417b3948e2207426d90a0e478790d1c8a4/aiohttp-3.13.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:2ca6ffef405fc9c09a746cb5d019c1672cd7f402542e379afc66b370833170cf", size = 1595126, upload-time = "2025-10-28T20:56:20.836Z" }, + { url = "https://files.pythonhosted.org/packages/e5/c7/8e22d5d28f94f67d2af496f14a83b3c155d915d1fe53d94b66d425ec5b42/aiohttp-3.13.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:47f438b1a28e926c37632bff3c44df7d27c9b57aaf4e34b1def3c07111fdb782", size = 1800665, upload-time = "2025-10-28T20:56:22.922Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/91133c8b68b1da9fc16555706aa7276fdf781ae2bb0876c838dd86b8116e/aiohttp-3.13.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9acda8604a57bb60544e4646a4615c1866ee6c04a8edef9b8ee6fd1d8fa2ddc8", size = 1739532, upload-time = "2025-10-28T20:56:25.924Z" }, + { url = "https://files.pythonhosted.org/packages/17/6b/3747644d26a998774b21a616016620293ddefa4d63af6286f389aedac844/aiohttp-3.13.2-cp311-cp311-win32.whl", hash = "sha256:868e195e39b24aaa930b063c08bb0c17924899c16c672a28a65afded9c46c6ec", size = 431876, upload-time = "2025-10-28T20:56:27.524Z" }, + { url = "https://files.pythonhosted.org/packages/c3/63/688462108c1a00eb9f05765331c107f95ae86f6b197b865d29e930b7e462/aiohttp-3.13.2-cp311-cp311-win_amd64.whl", hash = "sha256:7fd19df530c292542636c2a9a85854fab93474396a52f1695e799186bbd7f24c", size = 456205, upload-time = "2025-10-28T20:56:29.062Z" }, + { url = "https://files.pythonhosted.org/packages/29/9b/01f00e9856d0a73260e86dd8ed0c2234a466c5c1712ce1c281548df39777/aiohttp-3.13.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b1e56bab2e12b2b9ed300218c351ee2a3d8c8fdab5b1ec6193e11a817767e47b", size = 737623, upload-time = "2025-10-28T20:56:30.797Z" }, + { url = "https://files.pythonhosted.org/packages/5a/1b/4be39c445e2b2bd0aab4ba736deb649fabf14f6757f405f0c9685019b9e9/aiohttp-3.13.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:364e25edaabd3d37b1db1f0cbcee8c73c9a3727bfa262b83e5e4cf3489a2a9dc", size = 492664, upload-time = "2025-10-28T20:56:32.708Z" }, + { url = "https://files.pythonhosted.org/packages/28/66/d35dcfea8050e131cdd731dff36434390479b4045a8d0b9d7111b0a968f1/aiohttp-3.13.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c5c94825f744694c4b8db20b71dba9a257cd2ba8e010a803042123f3a25d50d7", size = 491808, upload-time = "2025-10-28T20:56:34.57Z" }, + { url = "https://files.pythonhosted.org/packages/00/29/8e4609b93e10a853b65f8291e64985de66d4f5848c5637cddc70e98f01f8/aiohttp-3.13.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba2715d842ffa787be87cbfce150d5e88c87a98e0b62e0f5aa489169a393dbbb", size = 1738863, upload-time = "2025-10-28T20:56:36.377Z" }, + { url = "https://files.pythonhosted.org/packages/9d/fa/4ebdf4adcc0def75ced1a0d2d227577cd7b1b85beb7edad85fcc87693c75/aiohttp-3.13.2-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:585542825c4bc662221fb257889e011a5aa00f1ae4d75d1d246a5225289183e3", size = 1700586, upload-time = "2025-10-28T20:56:38.034Z" }, + { url = "https://files.pythonhosted.org/packages/da/04/73f5f02ff348a3558763ff6abe99c223381b0bace05cd4530a0258e52597/aiohttp-3.13.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:39d02cb6025fe1aabca329c5632f48c9532a3dabccd859e7e2f110668972331f", size = 1768625, upload-time = "2025-10-28T20:56:39.75Z" }, + { url = "https://files.pythonhosted.org/packages/f8/49/a825b79ffec124317265ca7d2344a86bcffeb960743487cb11988ffb3494/aiohttp-3.13.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e67446b19e014d37342f7195f592a2a948141d15a312fe0e700c2fd2f03124f6", size = 1867281, upload-time = "2025-10-28T20:56:41.471Z" }, + { url = "https://files.pythonhosted.org/packages/b9/48/adf56e05f81eac31edcfae45c90928f4ad50ef2e3ea72cb8376162a368f8/aiohttp-3.13.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4356474ad6333e41ccefd39eae869ba15a6c5299c9c01dfdcfdd5c107be4363e", size = 1752431, upload-time = "2025-10-28T20:56:43.162Z" }, + { url = "https://files.pythonhosted.org/packages/30/ab/593855356eead019a74e862f21523db09c27f12fd24af72dbc3555b9bfd9/aiohttp-3.13.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:eeacf451c99b4525f700f078becff32c32ec327b10dcf31306a8a52d78166de7", size = 1562846, upload-time = "2025-10-28T20:56:44.85Z" }, + { url = "https://files.pythonhosted.org/packages/39/0f/9f3d32271aa8dc35036e9668e31870a9d3b9542dd6b3e2c8a30931cb27ae/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d8a9b889aeabd7a4e9af0b7f4ab5ad94d42e7ff679aaec6d0db21e3b639ad58d", size = 1699606, upload-time = "2025-10-28T20:56:46.519Z" }, + { url = "https://files.pythonhosted.org/packages/2c/3c/52d2658c5699b6ef7692a3f7128b2d2d4d9775f2a68093f74bca06cf01e1/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:fa89cb11bc71a63b69568d5b8a25c3ca25b6d54c15f907ca1c130d72f320b76b", size = 1720663, upload-time = "2025-10-28T20:56:48.528Z" }, + { url = "https://files.pythonhosted.org/packages/9b/d4/8f8f3ff1fb7fb9e3f04fcad4e89d8a1cd8fc7d05de67e3de5b15b33008ff/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8aa7c807df234f693fed0ecd507192fc97692e61fee5702cdc11155d2e5cadc8", size = 1737939, upload-time = "2025-10-28T20:56:50.77Z" }, + { url = "https://files.pythonhosted.org/packages/03/d3/ddd348f8a27a634daae39a1b8e291ff19c77867af438af844bf8b7e3231b/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:9eb3e33fdbe43f88c3c75fa608c25e7c47bbd80f48d012763cb67c47f39a7e16", size = 1555132, upload-time = "2025-10-28T20:56:52.568Z" }, + { url = "https://files.pythonhosted.org/packages/39/b8/46790692dc46218406f94374903ba47552f2f9f90dad554eed61bfb7b64c/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9434bc0d80076138ea986833156c5a48c9c7a8abb0c96039ddbb4afc93184169", size = 1764802, upload-time = "2025-10-28T20:56:54.292Z" }, + { url = "https://files.pythonhosted.org/packages/ba/e4/19ce547b58ab2a385e5f0b8aa3db38674785085abcf79b6e0edd1632b12f/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ff15c147b2ad66da1f2cbb0622313f2242d8e6e8f9b79b5206c84523a4473248", size = 1719512, upload-time = "2025-10-28T20:56:56.428Z" }, + { url = "https://files.pythonhosted.org/packages/70/30/6355a737fed29dcb6dfdd48682d5790cb5eab050f7b4e01f49b121d3acad/aiohttp-3.13.2-cp312-cp312-win32.whl", hash = "sha256:27e569eb9d9e95dbd55c0fc3ec3a9335defbf1d8bc1d20171a49f3c4c607b93e", size = 426690, upload-time = "2025-10-28T20:56:58.736Z" }, + { url = "https://files.pythonhosted.org/packages/0a/0d/b10ac09069973d112de6ef980c1f6bb31cb7dcd0bc363acbdad58f927873/aiohttp-3.13.2-cp312-cp312-win_amd64.whl", hash = "sha256:8709a0f05d59a71f33fd05c17fc11fcb8c30140506e13c2f5e8ee1b8964e1b45", size = 453465, upload-time = "2025-10-28T20:57:00.795Z" }, ] [[package]] name = "aioice" -version = "0.10.1" +version = "0.10.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "dnspython" }, { name = "ifaddr" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/95/a2/45dfab1d5a7f96c48595a5770379acf406cdf02a2cd1ac1729b599322b08/aioice-0.10.1.tar.gz", hash = "sha256:5c8e1422103448d171925c678fb39795e5fe13d79108bebb00aa75a899c2094a", size = 44304, upload-time = "2025-04-13T08:15:25.629Z" } +sdist = { url = "https://files.pythonhosted.org/packages/67/04/df7286233f468e19e9bedff023b6b246182f0b2ccb04ceeb69b2994021c6/aioice-0.10.2.tar.gz", hash = "sha256:bf236c6829ee33c8e540535d31cd5a066b531cb56de2be94c46be76d68b1a806", size = 44307, upload-time = "2025-11-28T15:56:48.836Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/58/af07dda649c22a1ae954ffb7aaaf4d4a57f1bf00ebdf62307affc0b8552f/aioice-0.10.1-py3-none-any.whl", hash = "sha256:f31ae2abc8608b1283ed5f21aebd7b6bd472b152ff9551e9b559b2d8efed79e9", size = 24872, upload-time = "2025-04-13T08:15:24.044Z" }, + { url = "https://files.pythonhosted.org/packages/c7/e3/0d23b1f930c17d371ce1ec36ee529f22fd19ebc2a07fe3418e3d1d884ce2/aioice-0.10.2-py3-none-any.whl", hash = "sha256:14911c15ab12d096dd14d372ebb4aecbb7420b52c9b76fdfcf54375dec17fcbf", size = 24875, upload-time = "2025-11-28T15:56:47.847Z" }, ] [[package]] @@ -123,11 +123,11 @@ wheels = [ [[package]] name = "attrs" -version = "25.3.0" +version = "25.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032, upload-time = "2025-03-13T11:10:22.779Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" }, + { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, ] [[package]] @@ -152,21 +152,20 @@ wheels = [ [[package]] name = "azure-core" -version = "1.35.1" +version = "1.37.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "requests" }, - { name = "six" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/15/6b/2653adc0f33adba8f11b1903701e6b1c10d34ce5d8e25dfa13a422f832b0/azure_core-1.35.1.tar.gz", hash = "sha256:435d05d6df0fff2f73fb3c15493bb4721ede14203f1ff1382aa6b6b2bdd7e562", size = 345290, upload-time = "2025-09-11T22:58:04.481Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ef/83/41c9371c8298999c67b007e308a0a3c4d6a59c6908fa9c62101f031f886f/azure_core-1.37.0.tar.gz", hash = "sha256:7064f2c11e4b97f340e8e8c6d923b822978be3016e46b7bc4aa4b337cfb48aee", size = 357620, upload-time = "2025-12-11T20:05:13.518Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/27/52/805980aa1ba18282077c484dba634ef0ede1e84eec8be9c92b2e162d0ed6/azure_core-1.35.1-py3-none-any.whl", hash = "sha256:12da0c9e08e48e198f9158b56ddbe33b421477e1dc98c2e1c8f9e254d92c468b", size = 211800, upload-time = "2025-09-11T22:58:06.281Z" }, + { url = "https://files.pythonhosted.org/packages/ee/34/a9914e676971a13d6cc671b1ed172f9804b50a3a80a143ff196e52f4c7ee/azure_core-1.37.0-py3-none-any.whl", hash = "sha256:b3abe2c59e7d6bb18b38c275a5029ff80f98990e7c90a5e646249a56630fcc19", size = 214006, upload-time = "2025-12-11T20:05:14.96Z" }, ] [[package]] name = "azure-identity" -version = "1.25.0" +version = "1.25.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "azure-core" }, @@ -175,14 +174,14 @@ dependencies = [ { name = "msal-extensions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4e/9e/4c9682a286c3c89e437579bd9f64f311020e5125c1321fd3a653166b5716/azure_identity-1.25.0.tar.gz", hash = "sha256:4177df34d684cddc026e6cf684e1abb57767aa9d84e7f2129b080ec45eee7733", size = 278507, upload-time = "2025-09-12T01:30:04.418Z" } +sdist = { url = "https://files.pythonhosted.org/packages/06/8d/1a6c41c28a37eab26dc85ab6c86992c700cd3f4a597d9ed174b0e9c69489/azure_identity-1.25.1.tar.gz", hash = "sha256:87ca8328883de6036443e1c37b40e8dc8fb74898240f61071e09d2e369361456", size = 279826, upload-time = "2025-10-06T20:30:02.194Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/75/54/81683b6756676a22e037b209695b08008258e603f7e47c56834029c5922a/azure_identity-1.25.0-py3-none-any.whl", hash = "sha256:becaec086bbdf8d1a6aa4fb080c2772a0f824a97d50c29637ec8cc4933f1e82d", size = 190861, upload-time = "2025-09-12T01:30:06.474Z" }, + { url = "https://files.pythonhosted.org/packages/83/7b/5652771e24fff12da9dde4c20ecf4682e606b104f26419d139758cc935a6/azure_identity-1.25.1-py3-none-any.whl", hash = "sha256:e9edd720af03dff020223cd269fa3a61e8f345ea75443858273bcb44844ab651", size = 191317, upload-time = "2025-10-06T20:30:04.251Z" }, ] [[package]] name = "azure-storage-blob" -version = "12.26.0" +version = "12.27.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "azure-core" }, @@ -190,9 +189,9 @@ dependencies = [ { name = "isodate" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/96/95/3e3414491ce45025a1cde107b6ae72bf72049e6021597c201cd6a3029b9a/azure_storage_blob-12.26.0.tar.gz", hash = "sha256:5dd7d7824224f7de00bfeb032753601c982655173061e242f13be6e26d78d71f", size = 583332, upload-time = "2025-07-16T21:34:07.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/36/7c/2fd872e11a88163f208b9c92de273bf64bb22d0eef9048cc6284d128a77a/azure_storage_blob-12.27.1.tar.gz", hash = "sha256:a1596cc4daf5dac9be115fcb5db67245eae894cf40e4248243754261f7b674a6", size = 597579, upload-time = "2025-10-29T12:27:16.185Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5b/64/63dbfdd83b31200ac58820a7951ddfdeed1fbee9285b0f3eae12d1357155/azure_storage_blob-12.26.0-py3-none-any.whl", hash = "sha256:8c5631b8b22b4f53ec5fff2f3bededf34cfef111e2af613ad42c9e6de00a77fe", size = 412907, upload-time = "2025-07-16T21:34:09.367Z" }, + { url = "https://files.pythonhosted.org/packages/3d/9e/1c90a122ea6180e8c72eb7294adc92531b0e08eb3d2324c2ba70d37f4802/azure_storage_blob-12.27.1-py3-none-any.whl", hash = "sha256:65d1e25a4628b7b6acd20ff7902d8da5b4fde8e46e19c8f6d213a3abc3ece272", size = 428954, upload-time = "2025-10-29T12:27:18.072Z" }, ] [[package]] @@ -220,11 +219,11 @@ wheels = [ [[package]] name = "certifi" -version = "2025.8.3" +version = "2025.11.12" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", size = 162386, upload-time = "2025-08-03T03:07:47.08Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/8c/58f469717fa48465e4a50c014a0400602d3c437d7c0c468e17ada824da3a/certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316", size = 160538, upload-time = "2025-11-12T02:54:51.517Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216, upload-time = "2025-08-03T03:07:45.777Z" }, + { url = "https://files.pythonhosted.org/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", size = 159438, upload-time = "2025-11-12T02:54:49.735Z" }, ] [[package]] @@ -265,54 +264,64 @@ wheels = [ [[package]] name = "charset-normalizer" -version = "3.4.3" +version = "3.4.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/83/2d/5fd176ceb9b2fc619e63405525573493ca23441330fcdaee6bef9460e924/charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14", size = 122371, upload-time = "2025-08-09T07:57:28.46Z" } +sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/b5/991245018615474a60965a7c9cd2b4efbaabd16d582a5547c47ee1c7730b/charset_normalizer-3.4.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b", size = 204483, upload-time = "2025-08-09T07:55:53.12Z" }, - { url = "https://files.pythonhosted.org/packages/c7/2a/ae245c41c06299ec18262825c1569c5d3298fc920e4ddf56ab011b417efd/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64", size = 145520, upload-time = "2025-08-09T07:55:54.712Z" }, - { url = "https://files.pythonhosted.org/packages/3a/a4/b3b6c76e7a635748c4421d2b92c7b8f90a432f98bda5082049af37ffc8e3/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91", size = 158876, upload-time = "2025-08-09T07:55:56.024Z" }, - { url = "https://files.pythonhosted.org/packages/e2/e6/63bb0e10f90a8243c5def74b5b105b3bbbfb3e7bb753915fe333fb0c11ea/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f", size = 156083, upload-time = "2025-08-09T07:55:57.582Z" }, - { url = "https://files.pythonhosted.org/packages/87/df/b7737ff046c974b183ea9aa111b74185ac8c3a326c6262d413bd5a1b8c69/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07", size = 150295, upload-time = "2025-08-09T07:55:59.147Z" }, - { url = "https://files.pythonhosted.org/packages/61/f1/190d9977e0084d3f1dc169acd060d479bbbc71b90bf3e7bf7b9927dec3eb/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30", size = 148379, upload-time = "2025-08-09T07:56:00.364Z" }, - { url = "https://files.pythonhosted.org/packages/4c/92/27dbe365d34c68cfe0ca76f1edd70e8705d82b378cb54ebbaeabc2e3029d/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14", size = 160018, upload-time = "2025-08-09T07:56:01.678Z" }, - { url = "https://files.pythonhosted.org/packages/99/04/baae2a1ea1893a01635d475b9261c889a18fd48393634b6270827869fa34/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c", size = 157430, upload-time = "2025-08-09T07:56:02.87Z" }, - { url = "https://files.pythonhosted.org/packages/2f/36/77da9c6a328c54d17b960c89eccacfab8271fdaaa228305330915b88afa9/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae", size = 151600, upload-time = "2025-08-09T07:56:04.089Z" }, - { url = "https://files.pythonhosted.org/packages/64/d4/9eb4ff2c167edbbf08cdd28e19078bf195762e9bd63371689cab5ecd3d0d/charset_normalizer-3.4.3-cp311-cp311-win32.whl", hash = "sha256:6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849", size = 99616, upload-time = "2025-08-09T07:56:05.658Z" }, - { url = "https://files.pythonhosted.org/packages/f4/9c/996a4a028222e7761a96634d1820de8a744ff4327a00ada9c8942033089b/charset_normalizer-3.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c", size = 107108, upload-time = "2025-08-09T07:56:07.176Z" }, - { url = "https://files.pythonhosted.org/packages/e9/5e/14c94999e418d9b87682734589404a25854d5f5d0408df68bc15b6ff54bb/charset_normalizer-3.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1", size = 205655, upload-time = "2025-08-09T07:56:08.475Z" }, - { url = "https://files.pythonhosted.org/packages/7d/a8/c6ec5d389672521f644505a257f50544c074cf5fc292d5390331cd6fc9c3/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884", size = 146223, upload-time = "2025-08-09T07:56:09.708Z" }, - { url = "https://files.pythonhosted.org/packages/fc/eb/a2ffb08547f4e1e5415fb69eb7db25932c52a52bed371429648db4d84fb1/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018", size = 159366, upload-time = "2025-08-09T07:56:11.326Z" }, - { url = "https://files.pythonhosted.org/packages/82/10/0fd19f20c624b278dddaf83b8464dcddc2456cb4b02bb902a6da126b87a1/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392", size = 157104, upload-time = "2025-08-09T07:56:13.014Z" }, - { url = "https://files.pythonhosted.org/packages/16/ab/0233c3231af734f5dfcf0844aa9582d5a1466c985bbed6cedab85af9bfe3/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f", size = 151830, upload-time = "2025-08-09T07:56:14.428Z" }, - { url = "https://files.pythonhosted.org/packages/ae/02/e29e22b4e02839a0e4a06557b1999d0a47db3567e82989b5bb21f3fbbd9f/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154", size = 148854, upload-time = "2025-08-09T07:56:16.051Z" }, - { url = "https://files.pythonhosted.org/packages/05/6b/e2539a0a4be302b481e8cafb5af8792da8093b486885a1ae4d15d452bcec/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491", size = 160670, upload-time = "2025-08-09T07:56:17.314Z" }, - { url = "https://files.pythonhosted.org/packages/31/e7/883ee5676a2ef217a40ce0bffcc3d0dfbf9e64cbcfbdf822c52981c3304b/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93", size = 158501, upload-time = "2025-08-09T07:56:18.641Z" }, - { url = "https://files.pythonhosted.org/packages/c1/35/6525b21aa0db614cf8b5792d232021dca3df7f90a1944db934efa5d20bb1/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f", size = 153173, upload-time = "2025-08-09T07:56:20.289Z" }, - { url = "https://files.pythonhosted.org/packages/50/ee/f4704bad8201de513fdc8aac1cabc87e38c5818c93857140e06e772b5892/charset_normalizer-3.4.3-cp312-cp312-win32.whl", hash = "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37", size = 99822, upload-time = "2025-08-09T07:56:21.551Z" }, - { url = "https://files.pythonhosted.org/packages/39/f5/3b3836ca6064d0992c58c7561c6b6eee1b3892e9665d650c803bd5614522/charset_normalizer-3.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc", size = 107543, upload-time = "2025-08-09T07:56:23.115Z" }, - { url = "https://files.pythonhosted.org/packages/8a/1f/f041989e93b001bc4e44bb1669ccdcf54d3f00e628229a85b08d330615c5/charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", size = 53175, upload-time = "2025-08-09T07:57:26.864Z" }, + { url = "https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", size = 206988, upload-time = "2025-10-14T04:40:33.79Z" }, + { url = "https://files.pythonhosted.org/packages/94/59/2e87300fe67ab820b5428580a53cad894272dbb97f38a7a814a2a1ac1011/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", size = 147324, upload-time = "2025-10-14T04:40:34.961Z" }, + { url = "https://files.pythonhosted.org/packages/07/fb/0cf61dc84b2b088391830f6274cb57c82e4da8bbc2efeac8c025edb88772/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", size = 142742, upload-time = "2025-10-14T04:40:36.105Z" }, + { url = "https://files.pythonhosted.org/packages/62/8b/171935adf2312cd745d290ed93cf16cf0dfe320863ab7cbeeae1dcd6535f/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", size = 160863, upload-time = "2025-10-14T04:40:37.188Z" }, + { url = "https://files.pythonhosted.org/packages/09/73/ad875b192bda14f2173bfc1bc9a55e009808484a4b256748d931b6948442/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", size = 157837, upload-time = "2025-10-14T04:40:38.435Z" }, + { url = "https://files.pythonhosted.org/packages/6d/fc/de9cce525b2c5b94b47c70a4b4fb19f871b24995c728e957ee68ab1671ea/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", size = 151550, upload-time = "2025-10-14T04:40:40.053Z" }, + { url = "https://files.pythonhosted.org/packages/55/c2/43edd615fdfba8c6f2dfbd459b25a6b3b551f24ea21981e23fb768503ce1/charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", size = 149162, upload-time = "2025-10-14T04:40:41.163Z" }, + { url = "https://files.pythonhosted.org/packages/03/86/bde4ad8b4d0e9429a4e82c1e8f5c659993a9a863ad62c7df05cf7b678d75/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", size = 150019, upload-time = "2025-10-14T04:40:42.276Z" }, + { url = "https://files.pythonhosted.org/packages/1f/86/a151eb2af293a7e7bac3a739b81072585ce36ccfb4493039f49f1d3cae8c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", size = 143310, upload-time = "2025-10-14T04:40:43.439Z" }, + { url = "https://files.pythonhosted.org/packages/b5/fe/43dae6144a7e07b87478fdfc4dbe9efd5defb0e7ec29f5f58a55aeef7bf7/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", size = 162022, upload-time = "2025-10-14T04:40:44.547Z" }, + { url = "https://files.pythonhosted.org/packages/80/e6/7aab83774f5d2bca81f42ac58d04caf44f0cc2b65fc6db2b3b2e8a05f3b3/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", size = 149383, upload-time = "2025-10-14T04:40:46.018Z" }, + { url = "https://files.pythonhosted.org/packages/4f/e8/b289173b4edae05c0dde07f69f8db476a0b511eac556dfe0d6bda3c43384/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", size = 159098, upload-time = "2025-10-14T04:40:47.081Z" }, + { url = "https://files.pythonhosted.org/packages/d8/df/fe699727754cae3f8478493c7f45f777b17c3ef0600e28abfec8619eb49c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", size = 152991, upload-time = "2025-10-14T04:40:48.246Z" }, + { url = "https://files.pythonhosted.org/packages/1a/86/584869fe4ddb6ffa3bd9f491b87a01568797fb9bd8933f557dba9771beaf/charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", size = 99456, upload-time = "2025-10-14T04:40:49.376Z" }, + { url = "https://files.pythonhosted.org/packages/65/f6/62fdd5feb60530f50f7e38b4f6a1d5203f4d16ff4f9f0952962c044e919a/charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", size = 106978, upload-time = "2025-10-14T04:40:50.844Z" }, + { url = "https://files.pythonhosted.org/packages/7a/9d/0710916e6c82948b3be62d9d398cb4fcf4e97b56d6a6aeccd66c4b2f2bd5/charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", size = 99969, upload-time = "2025-10-14T04:40:52.272Z" }, + { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" }, + { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" }, + { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" }, + { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" }, + { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" }, + { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" }, + { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" }, + { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" }, + { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" }, + { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" }, + { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" }, + { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" }, + { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" }, + { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, ] [[package]] name = "click" -version = "8.3.0" +version = "8.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/46/61/de6cd827efad202d7057d93e0fed9294b96952e188f7384832791c7b2254/click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4", size = 276943, upload-time = "2025-09-18T17:32:23.696Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc", size = 107295, upload-time = "2025-09-18T17:32:22.42Z" }, + { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, ] [[package]] name = "cloudpickle" -version = "3.1.1" +version = "3.1.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/52/39/069100b84d7418bc358d81669d5748efb14b9cceacd2f9c75f550424132f/cloudpickle-3.1.1.tar.gz", hash = "sha256:b216fa8ae4019d5482a8ac3c95d8f6346115d8835911fd4aefd1a445e4242c64", size = 22113, upload-time = "2025-01-14T17:02:05.085Z" } +sdist = { url = "https://files.pythonhosted.org/packages/27/fb/576f067976d320f5f0114a8d9fa1215425441bb35627b1993e5afd8111e5/cloudpickle-3.1.2.tar.gz", hash = "sha256:7fda9eb655c9c230dab534f1983763de5835249750e85fbcef43aaa30a9a2414", size = 22330, upload-time = "2025-11-03T09:25:26.604Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/e8/64c37fadfc2816a7701fa8a6ed8d87327c7d54eacfbfb6edab14a2f2be75/cloudpickle-3.1.1-py3-none-any.whl", hash = "sha256:c8c5a44295039331ee9dad40ba100a9c7297b6f988e50e87ccdf3765a668350e", size = 20992, upload-time = "2025-01-14T17:02:02.417Z" }, + { url = "https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl", hash = "sha256:9acb47f6afd73f60dc1df93bb801b472f05ff42fa6c84167d25cb206be1fbf4a", size = 22228, upload-time = "2025-11-03T09:25:25.534Z" }, ] [[package]] @@ -373,37 +382,37 @@ wheels = [ [[package]] name = "coverage" -version = "7.12.0" +version = "7.13.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/89/26/4a96807b193b011588099c3b5c89fbb05294e5b90e71018e065465f34eb6/coverage-7.12.0.tar.gz", hash = "sha256:fc11e0a4e372cb5f282f16ef90d4a585034050ccda536451901abfb19a57f40c", size = 819341, upload-time = "2025-11-18T13:34:20.766Z" } +sdist = { url = "https://files.pythonhosted.org/packages/23/f9/e92df5e07f3fc8d4c7f9a0f146ef75446bf870351cd37b788cf5897f8079/coverage-7.13.1.tar.gz", hash = "sha256:b7593fe7eb5feaa3fbb461ac79aac9f9fc0387a5ca8080b0c6fe2ca27b091afd", size = 825862, upload-time = "2025-12-28T15:42:56.969Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/0c/0dfe7f0487477d96432e4815537263363fb6dd7289743a796e8e51eabdf2/coverage-7.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aa124a3683d2af98bd9d9c2bfa7a5076ca7e5ab09fdb96b81fa7d89376ae928f", size = 217535, upload-time = "2025-11-18T13:32:08.812Z" }, - { url = "https://files.pythonhosted.org/packages/9b/f5/f9a4a053a5bbff023d3bec259faac8f11a1e5a6479c2ccf586f910d8dac7/coverage-7.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d93fbf446c31c0140208dcd07c5d882029832e8ed7891a39d6d44bd65f2316c3", size = 218044, upload-time = "2025-11-18T13:32:10.329Z" }, - { url = "https://files.pythonhosted.org/packages/95/c5/84fc3697c1fa10cd8571919bf9693f693b7373278daaf3b73e328d502bc8/coverage-7.12.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:52ca620260bd8cd6027317bdd8b8ba929be1d741764ee765b42c4d79a408601e", size = 248440, upload-time = "2025-11-18T13:32:12.536Z" }, - { url = "https://files.pythonhosted.org/packages/f4/36/2d93fbf6a04670f3874aed397d5a5371948a076e3249244a9e84fb0e02d6/coverage-7.12.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f3433ffd541380f3a0e423cff0f4926d55b0cc8c1d160fdc3be24a4c03aa65f7", size = 250361, upload-time = "2025-11-18T13:32:13.852Z" }, - { url = "https://files.pythonhosted.org/packages/5d/49/66dc65cc456a6bfc41ea3d0758c4afeaa4068a2b2931bf83be6894cf1058/coverage-7.12.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f7bbb321d4adc9f65e402c677cd1c8e4c2d0105d3ce285b51b4d87f1d5db5245", size = 252472, upload-time = "2025-11-18T13:32:15.068Z" }, - { url = "https://files.pythonhosted.org/packages/35/1f/ebb8a18dffd406db9fcd4b3ae42254aedcaf612470e8712f12041325930f/coverage-7.12.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:22a7aade354a72dff3b59c577bfd18d6945c61f97393bc5fb7bd293a4237024b", size = 248592, upload-time = "2025-11-18T13:32:16.328Z" }, - { url = "https://files.pythonhosted.org/packages/da/a8/67f213c06e5ea3b3d4980df7dc344d7fea88240b5fe878a5dcbdfe0e2315/coverage-7.12.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3ff651dcd36d2fea66877cd4a82de478004c59b849945446acb5baf9379a1b64", size = 250167, upload-time = "2025-11-18T13:32:17.687Z" }, - { url = "https://files.pythonhosted.org/packages/f0/00/e52aef68154164ea40cc8389c120c314c747fe63a04b013a5782e989b77f/coverage-7.12.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:31b8b2e38391a56e3cea39d22a23faaa7c3fc911751756ef6d2621d2a9daf742", size = 248238, upload-time = "2025-11-18T13:32:19.2Z" }, - { url = "https://files.pythonhosted.org/packages/1f/a4/4d88750bcf9d6d66f77865e5a05a20e14db44074c25fd22519777cb69025/coverage-7.12.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:297bc2da28440f5ae51c845a47c8175a4db0553a53827886e4fb25c66633000c", size = 247964, upload-time = "2025-11-18T13:32:21.027Z" }, - { url = "https://files.pythonhosted.org/packages/a7/6b/b74693158899d5b47b0bf6238d2c6722e20ba749f86b74454fac0696bb00/coverage-7.12.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6ff7651cc01a246908eac162a6a86fc0dbab6de1ad165dfb9a1e2ec660b44984", size = 248862, upload-time = "2025-11-18T13:32:22.304Z" }, - { url = "https://files.pythonhosted.org/packages/18/de/6af6730227ce0e8ade307b1cc4a08e7f51b419a78d02083a86c04ccceb29/coverage-7.12.0-cp311-cp311-win32.whl", hash = "sha256:313672140638b6ddb2c6455ddeda41c6a0b208298034544cfca138978c6baed6", size = 220033, upload-time = "2025-11-18T13:32:23.714Z" }, - { url = "https://files.pythonhosted.org/packages/e2/a1/e7f63021a7c4fe20994359fcdeae43cbef4a4d0ca36a5a1639feeea5d9e1/coverage-7.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:a1783ed5bd0d5938d4435014626568dc7f93e3cb99bc59188cc18857c47aa3c4", size = 220966, upload-time = "2025-11-18T13:32:25.599Z" }, - { url = "https://files.pythonhosted.org/packages/77/e8/deae26453f37c20c3aa0c4433a1e32cdc169bf415cce223a693117aa3ddd/coverage-7.12.0-cp311-cp311-win_arm64.whl", hash = "sha256:4648158fd8dd9381b5847622df1c90ff314efbfc1df4550092ab6013c238a5fc", size = 219637, upload-time = "2025-11-18T13:32:27.265Z" }, - { url = "https://files.pythonhosted.org/packages/02/bf/638c0427c0f0d47638242e2438127f3c8ee3cfc06c7fdeb16778ed47f836/coverage-7.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:29644c928772c78512b48e14156b81255000dcfd4817574ff69def189bcb3647", size = 217704, upload-time = "2025-11-18T13:32:28.906Z" }, - { url = "https://files.pythonhosted.org/packages/08/e1/706fae6692a66c2d6b871a608bbde0da6281903fa0e9f53a39ed441da36a/coverage-7.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8638cbb002eaa5d7c8d04da667813ce1067080b9a91099801a0053086e52b736", size = 218064, upload-time = "2025-11-18T13:32:30.161Z" }, - { url = "https://files.pythonhosted.org/packages/a9/8b/eb0231d0540f8af3ffda39720ff43cb91926489d01524e68f60e961366e4/coverage-7.12.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:083631eeff5eb9992c923e14b810a179798bb598e6a0dd60586819fc23be6e60", size = 249560, upload-time = "2025-11-18T13:32:31.835Z" }, - { url = "https://files.pythonhosted.org/packages/e9/a1/67fb52af642e974d159b5b379e4d4c59d0ebe1288677fbd04bbffe665a82/coverage-7.12.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:99d5415c73ca12d558e07776bd957c4222c687b9f1d26fa0e1b57e3598bdcde8", size = 252318, upload-time = "2025-11-18T13:32:33.178Z" }, - { url = "https://files.pythonhosted.org/packages/41/e5/38228f31b2c7665ebf9bdfdddd7a184d56450755c7e43ac721c11a4b8dab/coverage-7.12.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e949ebf60c717c3df63adb4a1a366c096c8d7fd8472608cd09359e1bd48ef59f", size = 253403, upload-time = "2025-11-18T13:32:34.45Z" }, - { url = "https://files.pythonhosted.org/packages/ec/4b/df78e4c8188f9960684267c5a4897836f3f0f20a20c51606ee778a1d9749/coverage-7.12.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6d907ddccbca819afa2cd014bc69983b146cca2735a0b1e6259b2a6c10be1e70", size = 249984, upload-time = "2025-11-18T13:32:35.747Z" }, - { url = "https://files.pythonhosted.org/packages/ba/51/bb163933d195a345c6f63eab9e55743413d064c291b6220df754075c2769/coverage-7.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b1518ecbad4e6173f4c6e6c4a46e49555ea5679bf3feda5edb1b935c7c44e8a0", size = 251339, upload-time = "2025-11-18T13:32:37.352Z" }, - { url = "https://files.pythonhosted.org/packages/15/40/c9b29cdb8412c837cdcbc2cfa054547dd83affe6cbbd4ce4fdb92b6ba7d1/coverage-7.12.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:51777647a749abdf6f6fd8c7cffab12de68ab93aab15efc72fbbb83036c2a068", size = 249489, upload-time = "2025-11-18T13:32:39.212Z" }, - { url = "https://files.pythonhosted.org/packages/c8/da/b3131e20ba07a0de4437a50ef3b47840dfabf9293675b0cd5c2c7f66dd61/coverage-7.12.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:42435d46d6461a3b305cdfcad7cdd3248787771f53fe18305548cba474e6523b", size = 249070, upload-time = "2025-11-18T13:32:40.598Z" }, - { url = "https://files.pythonhosted.org/packages/70/81/b653329b5f6302c08d683ceff6785bc60a34be9ae92a5c7b63ee7ee7acec/coverage-7.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5bcead88c8423e1855e64b8057d0544e33e4080b95b240c2a355334bb7ced937", size = 250929, upload-time = "2025-11-18T13:32:42.915Z" }, - { url = "https://files.pythonhosted.org/packages/a3/00/250ac3bca9f252a5fb1338b5ad01331ebb7b40223f72bef5b1b2cb03aa64/coverage-7.12.0-cp312-cp312-win32.whl", hash = "sha256:dcbb630ab034e86d2a0f79aefd2be07e583202f41e037602d438c80044957baa", size = 220241, upload-time = "2025-11-18T13:32:44.665Z" }, - { url = "https://files.pythonhosted.org/packages/64/1c/77e79e76d37ce83302f6c21980b45e09f8aa4551965213a10e62d71ce0ab/coverage-7.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:2fd8354ed5d69775ac42986a691fbf68b4084278710cee9d7c3eaa0c28fa982a", size = 221051, upload-time = "2025-11-18T13:32:46.008Z" }, - { url = "https://files.pythonhosted.org/packages/31/f5/641b8a25baae564f9e52cac0e2667b123de961985709a004e287ee7663cc/coverage-7.12.0-cp312-cp312-win_arm64.whl", hash = "sha256:737c3814903be30695b2de20d22bcc5428fdae305c61ba44cdc8b3252984c49c", size = 219692, upload-time = "2025-11-18T13:32:47.372Z" }, - { url = "https://files.pythonhosted.org/packages/ce/a3/43b749004e3c09452e39bb56347a008f0a0668aad37324a99b5c8ca91d9e/coverage-7.12.0-py3-none-any.whl", hash = "sha256:159d50c0b12e060b15ed3d39f87ed43d4f7f7ad40b8a534f4dd331adbb51104a", size = 209503, upload-time = "2025-11-18T13:34:18.892Z" }, + { url = "https://files.pythonhosted.org/packages/b4/9b/77baf488516e9ced25fc215a6f75d803493fc3f6a1a1227ac35697910c2a/coverage-7.13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a55d509a1dc5a5b708b5dad3b5334e07a16ad4c2185e27b40e4dba796ab7f88", size = 218755, upload-time = "2025-12-28T15:40:30.812Z" }, + { url = "https://files.pythonhosted.org/packages/d7/cd/7ab01154e6eb79ee2fab76bf4d89e94c6648116557307ee4ebbb85e5c1bf/coverage-7.13.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4d010d080c4888371033baab27e47c9df7d6fb28d0b7b7adf85a4a49be9298b3", size = 219257, upload-time = "2025-12-28T15:40:32.333Z" }, + { url = "https://files.pythonhosted.org/packages/01/d5/b11ef7863ffbbdb509da0023fad1e9eda1c0eaea61a6d2ea5b17d4ac706e/coverage-7.13.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d938b4a840fb1523b9dfbbb454f652967f18e197569c32266d4d13f37244c3d9", size = 249657, upload-time = "2025-12-28T15:40:34.1Z" }, + { url = "https://files.pythonhosted.org/packages/f7/7c/347280982982383621d29b8c544cf497ae07ac41e44b1ca4903024131f55/coverage-7.13.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bf100a3288f9bb7f919b87eb84f87101e197535b9bd0e2c2b5b3179633324fee", size = 251581, upload-time = "2025-12-28T15:40:36.131Z" }, + { url = "https://files.pythonhosted.org/packages/82/f6/ebcfed11036ade4c0d75fa4453a6282bdd225bc073862766eec184a4c643/coverage-7.13.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef6688db9bf91ba111ae734ba6ef1a063304a881749726e0d3575f5c10a9facf", size = 253691, upload-time = "2025-12-28T15:40:37.626Z" }, + { url = "https://files.pythonhosted.org/packages/02/92/af8f5582787f5d1a8b130b2dcba785fa5e9a7a8e121a0bb2220a6fdbdb8a/coverage-7.13.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0b609fc9cdbd1f02e51f67f51e5aee60a841ef58a68d00d5ee2c0faf357481a3", size = 249799, upload-time = "2025-12-28T15:40:39.47Z" }, + { url = "https://files.pythonhosted.org/packages/24/aa/0e39a2a3b16eebf7f193863323edbff38b6daba711abaaf807d4290cf61a/coverage-7.13.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c43257717611ff5e9a1d79dce8e47566235ebda63328718d9b65dd640bc832ef", size = 251389, upload-time = "2025-12-28T15:40:40.954Z" }, + { url = "https://files.pythonhosted.org/packages/73/46/7f0c13111154dc5b978900c0ccee2e2ca239b910890e674a77f1363d483e/coverage-7.13.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e09fbecc007f7b6afdfb3b07ce5bd9f8494b6856dd4f577d26c66c391b829851", size = 249450, upload-time = "2025-12-28T15:40:42.489Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ca/e80da6769e8b669ec3695598c58eef7ad98b0e26e66333996aee6316db23/coverage-7.13.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:a03a4f3a19a189919c7055098790285cc5c5b0b3976f8d227aea39dbf9f8bfdb", size = 249170, upload-time = "2025-12-28T15:40:44.279Z" }, + { url = "https://files.pythonhosted.org/packages/af/18/9e29baabdec1a8644157f572541079b4658199cfd372a578f84228e860de/coverage-7.13.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3820778ea1387c2b6a818caec01c63adc5b3750211af6447e8dcfb9b6f08dbba", size = 250081, upload-time = "2025-12-28T15:40:45.748Z" }, + { url = "https://files.pythonhosted.org/packages/00/f8/c3021625a71c3b2f516464d322e41636aea381018319050a8114105872ee/coverage-7.13.1-cp311-cp311-win32.whl", hash = "sha256:ff10896fa55167371960c5908150b434b71c876dfab97b69478f22c8b445ea19", size = 221281, upload-time = "2025-12-28T15:40:47.232Z" }, + { url = "https://files.pythonhosted.org/packages/27/56/c216625f453df6e0559ed666d246fcbaaa93f3aa99eaa5080cea1229aa3d/coverage-7.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:a998cc0aeeea4c6d5622a3754da5a493055d2d95186bad877b0a34ea6e6dbe0a", size = 222215, upload-time = "2025-12-28T15:40:49.19Z" }, + { url = "https://files.pythonhosted.org/packages/5c/9a/be342e76f6e531cae6406dc46af0d350586f24d9b67fdfa6daee02df71af/coverage-7.13.1-cp311-cp311-win_arm64.whl", hash = "sha256:fea07c1a39a22614acb762e3fbbb4011f65eedafcb2948feeef641ac78b4ee5c", size = 220886, upload-time = "2025-12-28T15:40:51.067Z" }, + { url = "https://files.pythonhosted.org/packages/ce/8a/87af46cccdfa78f53db747b09f5f9a21d5fc38d796834adac09b30a8ce74/coverage-7.13.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6f34591000f06e62085b1865c9bc5f7858df748834662a51edadfd2c3bfe0dd3", size = 218927, upload-time = "2025-12-28T15:40:52.814Z" }, + { url = "https://files.pythonhosted.org/packages/82/a8/6e22fdc67242a4a5a153f9438d05944553121c8f4ba70cb072af4c41362e/coverage-7.13.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b67e47c5595b9224599016e333f5ec25392597a89d5744658f837d204e16c63e", size = 219288, upload-time = "2025-12-28T15:40:54.262Z" }, + { url = "https://files.pythonhosted.org/packages/d0/0a/853a76e03b0f7c4375e2ca025df45c918beb367f3e20a0a8e91967f6e96c/coverage-7.13.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3e7b8bd70c48ffb28461ebe092c2345536fb18bbbf19d287c8913699735f505c", size = 250786, upload-time = "2025-12-28T15:40:56.059Z" }, + { url = "https://files.pythonhosted.org/packages/ea/b4/694159c15c52b9f7ec7adf49d50e5f8ee71d3e9ef38adb4445d13dd56c20/coverage-7.13.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c223d078112e90dc0e5c4e35b98b9584164bea9fbbd221c0b21c5241f6d51b62", size = 253543, upload-time = "2025-12-28T15:40:57.585Z" }, + { url = "https://files.pythonhosted.org/packages/96/b2/7f1f0437a5c855f87e17cf5d0dc35920b6440ff2b58b1ba9788c059c26c8/coverage-7.13.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:794f7c05af0763b1bbd1b9e6eff0e52ad068be3b12cd96c87de037b01390c968", size = 254635, upload-time = "2025-12-28T15:40:59.443Z" }, + { url = "https://files.pythonhosted.org/packages/e9/d1/73c3fdb8d7d3bddd9473c9c6a2e0682f09fc3dfbcb9c3f36412a7368bcab/coverage-7.13.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0642eae483cc8c2902e4af7298bf886d605e80f26382124cddc3967c2a3df09e", size = 251202, upload-time = "2025-12-28T15:41:01.328Z" }, + { url = "https://files.pythonhosted.org/packages/66/3c/f0edf75dcc152f145d5598329e864bbbe04ab78660fe3e8e395f9fff010f/coverage-7.13.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9f5e772ed5fef25b3de9f2008fe67b92d46831bd2bc5bdc5dd6bfd06b83b316f", size = 252566, upload-time = "2025-12-28T15:41:03.319Z" }, + { url = "https://files.pythonhosted.org/packages/17/b3/e64206d3c5f7dcbceafd14941345a754d3dbc78a823a6ed526e23b9cdaab/coverage-7.13.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:45980ea19277dc0a579e432aef6a504fe098ef3a9032ead15e446eb0f1191aee", size = 250711, upload-time = "2025-12-28T15:41:06.411Z" }, + { url = "https://files.pythonhosted.org/packages/dc/ad/28a3eb970a8ef5b479ee7f0c484a19c34e277479a5b70269dc652b730733/coverage-7.13.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:e4f18eca6028ffa62adbd185a8f1e1dd242f2e68164dba5c2b74a5204850b4cf", size = 250278, upload-time = "2025-12-28T15:41:08.285Z" }, + { url = "https://files.pythonhosted.org/packages/54/e3/c8f0f1a93133e3e1291ca76cbb63565bd4b5c5df63b141f539d747fff348/coverage-7.13.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f8dca5590fec7a89ed6826fce625595279e586ead52e9e958d3237821fbc750c", size = 252154, upload-time = "2025-12-28T15:41:09.969Z" }, + { url = "https://files.pythonhosted.org/packages/d0/bf/9939c5d6859c380e405b19e736321f1c7d402728792f4c752ad1adcce005/coverage-7.13.1-cp312-cp312-win32.whl", hash = "sha256:ff86d4e85188bba72cfb876df3e11fa243439882c55957184af44a35bd5880b7", size = 221487, upload-time = "2025-12-28T15:41:11.468Z" }, + { url = "https://files.pythonhosted.org/packages/fa/dc/7282856a407c621c2aad74021680a01b23010bb8ebf427cf5eacda2e876f/coverage-7.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:16cc1da46c04fb0fb128b4dc430b78fa2aba8a6c0c9f8eb391fd5103409a6ac6", size = 222299, upload-time = "2025-12-28T15:41:13.386Z" }, + { url = "https://files.pythonhosted.org/packages/10/79/176a11203412c350b3e9578620013af35bcdb79b651eb976f4a4b32044fa/coverage-7.13.1-cp312-cp312-win_arm64.whl", hash = "sha256:8d9bc218650022a768f3775dd7fdac1886437325d8d295d923ebcfef4892ad5c", size = 220941, upload-time = "2025-12-28T15:41:14.975Z" }, + { url = "https://files.pythonhosted.org/packages/cc/48/d9f421cb8da5afaa1a64570d9989e00fb7955e6acddc5a12979f7666ef60/coverage-7.13.1-py3-none-any.whl", hash = "sha256:2016745cb3ba554469d02819d78958b571792bb68e31302610e898f80dd3a573", size = 210722, upload-time = "2025-12-28T15:42:54.901Z" }, ] [[package]] @@ -452,31 +461,28 @@ wheels = [ [[package]] name = "cython" -version = "3.1.4" +version = "3.2.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a7/f6/d762df1f436a0618455d37f4e4c4872a7cd0dcfc8dec3022ee99e4389c69/cython-3.1.4.tar.gz", hash = "sha256:9aefefe831331e2d66ab31799814eae4d0f8a2d246cbaaaa14d1be29ef777683", size = 3190778, upload-time = "2025-09-16T07:20:33.531Z" } +sdist = { url = "https://files.pythonhosted.org/packages/39/e1/c0d92b1258722e1bc62a12e630c33f1f842fdab53fd8cd5de2f75c6449a9/cython-3.2.3.tar.gz", hash = "sha256:f13832412d633376ffc08d751cc18ed0d7d00a398a4065e2871db505258748a6", size = 3276650, upload-time = "2025-12-14T07:50:34.691Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b5/ab/0a568bac7c4c052db4ae27edf01e16f3093cdfef04a2dfd313ef1b3c478a/cython-3.1.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d1d7013dba5fb0506794d4ef8947ff5ed021370614950a8d8d04e57c8c84499e", size = 3026389, upload-time = "2025-09-16T07:22:02.212Z" }, - { url = "https://files.pythonhosted.org/packages/cb/b7/51f5566e1309215a7fef744975b2fabb56d3fdc5fa1922fd7e306c14f523/cython-3.1.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:eed989f5c139d6550ef2665b783d86fab99372590c97f10a3c26c4523c5fce9e", size = 2955954, upload-time = "2025-09-16T07:22:03.782Z" }, - { url = "https://files.pythonhosted.org/packages/28/fd/ad8314520000fe96292fb8208c640fa862baa3053d2f3453a2acb50cafb8/cython-3.1.4-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3df3beb8b024dfd73cfddb7f2f7456751cebf6e31655eed3189c209b634bc2f2", size = 3412005, upload-time = "2025-09-16T07:22:05.483Z" }, - { url = "https://files.pythonhosted.org/packages/0c/3b/e570f8bcb392e7943fc9a25d1b2d1646ef0148ff017d3681511acf6bbfdc/cython-3.1.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f8354703f1168e1aaa01348940f719734c1f11298be333bdb5b94101d49677c0", size = 3191100, upload-time = "2025-09-16T07:22:07.144Z" }, - { url = "https://files.pythonhosted.org/packages/78/81/f1ea09f563ebab732542cb11bf363710e53f3842458159ea2c160788bc8e/cython-3.1.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a928bd7d446247855f54f359057ab4a32c465219c8c1e299906a483393a59a9e", size = 3313786, upload-time = "2025-09-16T07:22:09.15Z" }, - { url = "https://files.pythonhosted.org/packages/ca/17/06575eb6175a926523bada7dac1cd05cc74add96cebbf2e8b492a2494291/cython-3.1.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c233bfff4cc7b9d629eecb7345f9b733437f76dc4441951ec393b0a6e29919fc", size = 3205775, upload-time = "2025-09-16T07:22:10.745Z" }, - { url = "https://files.pythonhosted.org/packages/10/ba/61a8cf56a76ab21ddf6476b70884feff2a2e56b6d9010e1e1b1e06c46f70/cython-3.1.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e9691a2cbc2faf0cd819108bceccf9bfc56c15a06d172eafe74157388c44a601", size = 3428423, upload-time = "2025-09-16T07:22:12.404Z" }, - { url = "https://files.pythonhosted.org/packages/c4/c2/42cf9239088d6b4b62c1c017c36e0e839f64c8d68674ce4172d0e0168d3b/cython-3.1.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ada319207432ea7c6691c70b5c112d261637d79d21ba086ae3726fedde79bfbf", size = 3330489, upload-time = "2025-09-16T07:22:14.576Z" }, - { url = "https://files.pythonhosted.org/packages/b5/08/36a619d6b1fc671a11744998e5cdd31790589e3cb4542927c97f3f351043/cython-3.1.4-cp311-cp311-win32.whl", hash = "sha256:dae81313c28222bf7be695f85ae1d16625aac35a0973a3af1e001f63379440c5", size = 2482410, upload-time = "2025-09-16T07:22:17.373Z" }, - { url = "https://files.pythonhosted.org/packages/6d/58/7d9ae7944bcd32e6f02d1a8d5d0c3875125227d050e235584127f2c64ffd/cython-3.1.4-cp311-cp311-win_amd64.whl", hash = "sha256:60d2f192059ac34c5c26527f2beac823d34aaa766ef06792a3b7f290c18ac5e2", size = 2713755, upload-time = "2025-09-16T07:22:18.949Z" }, - { url = "https://files.pythonhosted.org/packages/f0/51/2939c739cfdc67ab94935a2c4fcc75638afd15e1954552655503a4112e92/cython-3.1.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0d26af46505d0e54fe0f05e7ad089fd0eed8fa04f385f3ab88796f554467bcb9", size = 3062976, upload-time = "2025-09-16T07:22:20.517Z" }, - { url = "https://files.pythonhosted.org/packages/eb/bd/a84de57fd01017bf5dba84a49aeee826db21112282bf8d76ab97567ee15d/cython-3.1.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:66ac8bb5068156c92359e3f0eefa138c177d59d1a2e8a89467881fa7d06aba3b", size = 2970701, upload-time = "2025-09-16T07:22:22.644Z" }, - { url = "https://files.pythonhosted.org/packages/71/79/a09004c8e42f5be188c7636b1be479cdb244a6d8837e1878d062e4e20139/cython-3.1.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cb2e42714faec723d2305607a04bafb49a48a8d8f25dd39368d884c058dbcfbc", size = 3387730, upload-time = "2025-09-16T07:22:24.271Z" }, - { url = "https://files.pythonhosted.org/packages/fb/bd/979f8c59e247f562642f3eb98a1b453530e1f7954ef071835c08ed2bf6ba/cython-3.1.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0fd655b27997a209a574873304ded9629de588f021154009e8f923475e2c677", size = 3167289, upload-time = "2025-09-16T07:22:26.35Z" }, - { url = "https://files.pythonhosted.org/packages/34/f8/0b98537f0b4e8c01f76d2a6cf75389987538e4d4ac9faf25836fd18c9689/cython-3.1.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9def7c41f4dc339003b1e6875f84edf059989b9c7f5e9a245d3ce12c190742d9", size = 3321099, upload-time = "2025-09-16T07:22:27.957Z" }, - { url = "https://files.pythonhosted.org/packages/f3/39/437968a2e7c7f57eb6e1144f6aca968aa15fbbf169b2d4da5d1ff6c21442/cython-3.1.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:196555584a8716bf7e017e23ca53e9f632ed493f9faa327d0718e7551588f55d", size = 3179897, upload-time = "2025-09-16T07:22:30.014Z" }, - { url = "https://files.pythonhosted.org/packages/2c/04/b3f42915f034d133f1a34e74a2270bc2def02786f9b40dc9028fbb968814/cython-3.1.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:7fff0e739e07a20726484b8898b8628a7b87acb960d0fc5486013c6b77b7bb97", size = 3400936, upload-time = "2025-09-16T07:22:31.705Z" }, - { url = "https://files.pythonhosted.org/packages/21/eb/2ad9fa0896ab6cf29875a09a9f4aaea37c28b79b869a013bf9b58e4e652e/cython-3.1.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c2754034fa10f95052949cd6b07eb2f61d654c1b9cfa0b17ea53a269389422e8", size = 3332131, upload-time = "2025-09-16T07:22:33.32Z" }, - { url = "https://files.pythonhosted.org/packages/3c/bf/f19283f8405e7e564c3353302a8665ea2c589be63a8e1be1b503043366a9/cython-3.1.4-cp312-cp312-win32.whl", hash = "sha256:2e0808ff3614a1dbfd1adfcbff9b2b8119292f1824b3535b4a173205109509f8", size = 2487672, upload-time = "2025-09-16T07:22:35.227Z" }, - { url = "https://files.pythonhosted.org/packages/30/bf/32150a2e6c7b50b81c5dc9e942d41969400223a9c49d04e2ed955709894c/cython-3.1.4-cp312-cp312-win_amd64.whl", hash = "sha256:f262b32327b6bce340cce5d45bbfe3972cb62543a4930460d8564a489f3aea12", size = 2705348, upload-time = "2025-09-16T07:22:37.922Z" }, - { url = "https://files.pythonhosted.org/packages/7c/24/f7351052cf9db771fe4f32fca47fd66e6d9b53d8613b17faf7d130a9d553/cython-3.1.4-py3-none-any.whl", hash = "sha256:d194d95e4fa029a3f6c7d46bdd16d973808c7ea4797586911fdb67cb98b1a2c6", size = 1227541, upload-time = "2025-09-16T07:20:29.595Z" }, + { url = "https://files.pythonhosted.org/packages/c3/85/77315c92d29d782bee1b36e30b8d76ad1e731cb7ea0af17e285885f3bb68/cython-3.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c041f7e338cca2422e0924716b04fabeda57636214324fc1941396acce99e7c7", size = 2951618, upload-time = "2025-12-14T07:50:53.883Z" }, + { url = "https://files.pythonhosted.org/packages/cb/dd/a8209e0d424a0207ddb4a3097a97b667027af3cfada762d85f3bed08ccf8/cython-3.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:283262b8f902323ceb6ed3b643f275a2a963e7ab059f0714a467933383cbc56d", size = 3243636, upload-time = "2025-12-14T07:50:56.346Z" }, + { url = "https://files.pythonhosted.org/packages/1f/2d/bc1927fd7174f7928b86cc9b83589d39592b9273c8b1d2295ca0c0071984/cython-3.2.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22a624290c2883387b2c2cfb5224c15bff21432c6a2cf0c23ac8df3dcbd45e96", size = 3378528, upload-time = "2025-12-14T07:50:57.988Z" }, + { url = "https://files.pythonhosted.org/packages/ad/10/5add6a6e1721f9c36b5d5b4f3b75fa7af43196e4f2a474921a7277e31b7a/cython-3.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:26404441f733fd1cfb0dd9c45477f501437e7d51fad05bb402bd2feb4e127aa3", size = 2769341, upload-time = "2025-12-14T07:50:59.581Z" }, + { url = "https://files.pythonhosted.org/packages/b4/14/d16282d17c9eb2f78ca9ccd5801fed22f6c3360f5a55dbcce3c93cc70352/cython-3.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cf210228c15b5c625824d8e31d43b6fea25f9e13c81dac632f2f7d838e0229a5", size = 2968471, upload-time = "2025-12-14T07:51:01.207Z" }, + { url = "https://files.pythonhosted.org/packages/d0/3c/46304a942dac5a636701c55f5b05ec00ad151e6722cd068fe3d0993349bb/cython-3.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f5bf0cebeb4147e172a114437d3fce5a507595d8fdd821be792b1bb25c691514", size = 3223581, upload-time = "2025-12-14T07:51:04.336Z" }, + { url = "https://files.pythonhosted.org/packages/29/ad/15da606d71f40bcf2c405f84ca3d4195cb252f4eaa2f551fe6b2e630ee7c/cython-3.2.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d1f8700ba89c977438744f083890d87187f15709507a5489e0f6d682053b7fa0", size = 3391391, upload-time = "2025-12-14T07:51:05.998Z" }, + { url = "https://files.pythonhosted.org/packages/51/9e/045b35eb678682edc3e2d57112cf5ac3581a9ef274eb220b638279195678/cython-3.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:25732f3981a93407826297f4423206e5e22c3cfccfc74e37bf444453bbdc076f", size = 2756814, upload-time = "2025-12-14T07:51:07.759Z" }, + { url = "https://files.pythonhosted.org/packages/43/49/afe1e3df87a770861cf17ba39f4a91f6d22a2571010fc1890b3708360630/cython-3.2.3-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:74f482da8b605c61b4df6ff716d013f20131949cb2fa59b03e63abd36ef5bac0", size = 2874467, upload-time = "2025-12-14T07:51:31.568Z" }, + { url = "https://files.pythonhosted.org/packages/c7/da/044f725a083e28fb4de5bd33d13ec13f0753734b6ae52d4bc07434610cc8/cython-3.2.3-cp39-abi3-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0a75a04688875b275a6c875565e672325bae04327dd6ec2fc25aeb5c6cf82fce", size = 3211272, upload-time = "2025-12-14T07:51:33.673Z" }, + { url = "https://files.pythonhosted.org/packages/95/14/af02ba6e2e03279f2ca2956e3024a44faed4c8496bda8170b663dc3ba6e8/cython-3.2.3-cp39-abi3-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6b01b36c9eb1b68c25bddbeef7379f7bfc37f7c9afc044e71840ffab761a2dd0", size = 2856058, upload-time = "2025-12-14T07:51:36.015Z" }, + { url = "https://files.pythonhosted.org/packages/69/16/d254359396c2f099ab154f89b2b35f5b8b0dd21a8102c2c96a7e00291434/cython-3.2.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3829f99d611412288f44ff543e9d2b5c0c83274998b2a6680bbe5cca3539c1fd", size = 2993276, upload-time = "2025-12-14T07:51:37.863Z" }, + { url = "https://files.pythonhosted.org/packages/51/0e/1a071381923e896f751f8fbff2a01c5dc8860a8b9a90066f6ec8df561dc4/cython-3.2.3-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:c2365a0c79ab9c0fa86d30a4a6ba7e37fc1be9537c48b79b9d63ee7e08bf2fef", size = 2890843, upload-time = "2025-12-14T07:51:40.409Z" }, + { url = "https://files.pythonhosted.org/packages/f4/46/1e93e10766db988e6bb8e5c6f7e2e90b9e62f1ac8dee4c1a6cf1fc170773/cython-3.2.3-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:3141734fb15f8b5e9402b9240f8da8336edecae91742b41c85678c31ab68f66d", size = 3225339, upload-time = "2025-12-14T07:51:42.09Z" }, + { url = "https://files.pythonhosted.org/packages/d4/ae/c284b06ae6a9c95d5883bf8744d10466cf0df64cef041a4c80ccf9fd07bd/cython-3.2.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9a24cc653fad3adbd9cbaa638d80df3aa08a1fe27f62eb35850971c70be680df", size = 3114751, upload-time = "2025-12-14T07:51:44.088Z" }, + { url = "https://files.pythonhosted.org/packages/c6/d6/7795a4775c70256217134195f06b07233cf17b00f8905d5b3d782208af64/cython-3.2.3-cp39-abi3-win32.whl", hash = "sha256:b39dff92db70cbd95528f3b81d70e06bd6d3fc9c1dd91321e4d3b999ece3bceb", size = 2435616, upload-time = "2025-12-14T07:51:46.063Z" }, + { url = "https://files.pythonhosted.org/packages/18/9e/2a3edcb858ad74e6274448dccf32150c532bc6e423f112a71f65ff3b5680/cython-3.2.3-cp39-abi3-win_arm64.whl", hash = "sha256:18edc858e6a52de47fe03ffa97ea14dadf450e20069de0a8aef531006c4bbd93", size = 2440952, upload-time = "2025-12-14T07:51:47.943Z" }, + { url = "https://files.pythonhosted.org/packages/e5/41/54fd429ff8147475fc24ca43246f85d78fb4e747c27f227e68f1594648f1/cython-3.2.3-py3-none-any.whl", hash = "sha256:06a1317097f540d3bb6c7b81ed58a0d8b9dbfa97abf39dfd4c22ee87a6c7241e", size = 1255561, upload-time = "2025-12-14T07:50:31.217Z" }, ] [[package]] @@ -490,17 +496,17 @@ wheels = [ [[package]] name = "dearpygui" -version = "2.1.0" +version = "2.1.1" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/92/fe/66293fc40254a29f060efd3398f2b1001ed79263ae1837db9ec42caa8f1d/dearpygui-2.1.0-cp311-cp311-macosx_10_6_x86_64.whl", hash = "sha256:03e5dc0b3dd2f7965e50bbe41f3316a814408064b582586de994d93afedb125c", size = 2100924, upload-time = "2025-07-07T14:20:00.602Z" }, - { url = "https://files.pythonhosted.org/packages/c4/4d/9fa1c3156ba7bbf4dc89e2e322998752fccfdc3575923a98dd6a4da48911/dearpygui-2.1.0-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:b5b37710c3fa135c48e2347f39ecd1f415146e86db5d404707a0bf72d16bd304", size = 1874441, upload-time = "2025-07-07T14:20:09.165Z" }, - { url = "https://files.pythonhosted.org/packages/5a/3c/af5673b50699e1734296a0b5bcef39bb6989175b001ad1f9b0e7888ad90d/dearpygui-2.1.0-cp311-cp311-manylinux1_x86_64.whl", hash = "sha256:b0cfd7ac7eaa090fc22d6aa60fc4b527fc631cee10c348e4d8df92bb39af03d2", size = 2636574, upload-time = "2025-07-07T14:20:14.951Z" }, - { url = "https://files.pythonhosted.org/packages/7f/db/ed4db0bb3d88e7a8c405472641419086bef9632c4b8b0489dc0c43519c0d/dearpygui-2.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:a9af54f96d3ef30c5db9d12cdf3266f005507396fb0da2e12e6b22b662161070", size = 1810266, upload-time = "2025-07-07T14:19:51.565Z" }, - { url = "https://files.pythonhosted.org/packages/55/9d/20a55786cc9d9266395544463d5db3be3528f7d5244bc52ba760de5dcc2d/dearpygui-2.1.0-cp312-cp312-macosx_10_6_x86_64.whl", hash = "sha256:1270ceb9cdb8ecc047c42477ccaa075b7864b314a5d09191f9280a24c8aa90a0", size = 2101499, upload-time = "2025-07-07T14:20:01.701Z" }, - { url = "https://files.pythonhosted.org/packages/a7/b2/39d820796b7ac4d0ebf93306c1f031bf3516b159408286f1fb495c6babeb/dearpygui-2.1.0-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:ce9969eb62057b9d4c88a8baaed13b5fbe4058caa9faf5b19fec89da75aece3d", size = 1874385, upload-time = "2025-07-07T14:20:11.226Z" }, - { url = "https://files.pythonhosted.org/packages/fc/26/c29998ffeb5eb8d638f307851e51a81c8bd4aeaf89ad660fc67ea4d1ac1a/dearpygui-2.1.0-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:a3ca8cf788db63ef7e2e8d6f277631b607d548b37606f080ca1b42b1f0a9b183", size = 2635863, upload-time = "2025-07-07T14:20:17.186Z" }, - { url = "https://files.pythonhosted.org/packages/28/9c/3ab33927f1d8c839c5b7033a33d44fc9f0aeb00c264fc9772cb7555a03c4/dearpygui-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:43f0e4db9402f44fc3683a1f5c703564819de18cc15a042de7f1ed1c8cb5d148", size = 1810460, upload-time = "2025-07-07T14:19:53.13Z" }, + { url = "https://files.pythonhosted.org/packages/57/b5/d5bae262633d05f82aeeb3f84ae6240fe3f2955ab6fb4509ebf8048a0b9e/dearpygui-2.1.1-cp311-cp311-macosx_10_6_x86_64.whl", hash = "sha256:8a7c8608b365f4b380b7326679023595fecd04b78c514f2cfd349b0a1108bd0e", size = 2100934, upload-time = "2025-11-14T14:47:38.172Z" }, + { url = "https://files.pythonhosted.org/packages/93/86/75fa9a0f0a7b4b62810cc6f1e8ebaea3df0a825c0adf27d2024aaac2d178/dearpygui-2.1.1-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:ee87153fdd494ccead8c2345553acd7a9ee61c031e3223f4160aa560709248a3", size = 1895473, upload-time = "2025-11-14T14:47:47.064Z" }, + { url = "https://files.pythonhosted.org/packages/ab/7a/e109e06f8f4379d41a4e672c49aba42e7fcf0eec88056fa06185f4e52c98/dearpygui-2.1.1-cp311-cp311-manylinux1_x86_64.whl", hash = "sha256:964fbb3735017b919efa58104b2d7e9b84a168ff5c1031ae0652d5bc0a48bf5b", size = 2640408, upload-time = "2025-11-14T14:47:53.124Z" }, + { url = "https://files.pythonhosted.org/packages/f3/b5/2ec29d9b47c30ecee96c6f6a0cf229f2898ce3e133a1a0e5b0cd5db82e6b/dearpygui-2.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:6141184ff59fa4b8df1b81b077cb8cc2b2ef9c0ff92e69c6063062b6d251f426", size = 1808736, upload-time = "2025-11-14T14:47:26.46Z" }, + { url = "https://files.pythonhosted.org/packages/79/41/2146e8d03d28b5a66d5282beb26ffd9ab68a729a29d31e2fe91809271bf5/dearpygui-2.1.1-cp312-cp312-macosx_10_6_x86_64.whl", hash = "sha256:238aea7b4be7376f564dae8edd563b280ec1483a03786022969938507691e017", size = 2101529, upload-time = "2025-11-14T14:47:39.646Z" }, + { url = "https://files.pythonhosted.org/packages/b0/c5/fcc37ef834fe225241aa4f18d77aaa2903134f283077978d65a901c624c6/dearpygui-2.1.1-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:c27ca6ecd4913555b717f3bb341c0b6a27d6c9fdc9932f0b3c31ae2ef893ae35", size = 1895555, upload-time = "2025-11-14T14:47:48.149Z" }, + { url = "https://files.pythonhosted.org/packages/74/66/19f454ba02d5f03a847cc1dfee4a849cd2307d97add5ba26fecdca318adb/dearpygui-2.1.1-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:8c071e9c165d89217bdcdaf769c6069252fcaee50bf369489add524107932273", size = 2641509, upload-time = "2025-11-14T14:47:54.581Z" }, + { url = "https://files.pythonhosted.org/packages/5e/58/d01538556103d544a5a5b4cbcb00646ff92d8a97f0a6283a56bede4307c8/dearpygui-2.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:9f2291313d2035f8a4108e13f60d8c1a0e7c19af7554a7739a3fd15b3d5af8f7", size = 1808971, upload-time = "2025-11-14T14:47:28.15Z" }, ] [[package]] @@ -535,11 +541,11 @@ wheels = [ [[package]] name = "execnet" -version = "2.1.1" +version = "2.1.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bb/ff/b4c0dc78fbe20c3e59c0c7334de0c27eb4001a2b2017999af398bf730817/execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3", size = 166524, upload-time = "2024-04-08T09:04:19.245Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bf/89/780e11f9588d9e7128a3f87788354c7946a9cbb1401ad38a48c4db9a4f07/execnet-2.1.2.tar.gz", hash = "sha256:63d83bfdd9a23e35b9c6a3261412324f964c2ec8dcd8d3c6916ee9373e0befcd", size = 166622, upload-time = "2025-11-12T09:56:37.75Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/43/09/2aea36ff60d16dd8879bdb2f5b3ee0ba8d08cbbdcdfe870e695ce3784385/execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc", size = 40612, upload-time = "2024-04-08T09:04:17.414Z" }, + { url = "https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec", size = 40708, upload-time = "2025-11-12T09:56:36.333Z" }, ] [[package]] @@ -553,79 +559,77 @@ wheels = [ [[package]] name = "filelock" -version = "3.19.1" +version = "3.20.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/40/bb/0ab3e58d22305b6f5440629d20683af28959bf793d98d11950e305c1c326/filelock-3.19.1.tar.gz", hash = "sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58", size = 17687, upload-time = "2025-08-14T16:56:03.016Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/23/ce7a1126827cedeb958fc043d61745754464eb56c5937c35bbf2b8e26f34/filelock-3.20.1.tar.gz", hash = "sha256:b8360948b351b80f420878d8516519a2204b07aefcdcfd24912a5d33127f188c", size = 19476, upload-time = "2025-12-15T23:54:28.027Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/42/14/42b2651a2f46b022ccd948bca9f2d5af0fd8929c4eec235b8d6d844fbe67/filelock-3.19.1-py3-none-any.whl", hash = "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d", size = 15988, upload-time = "2025-08-14T16:56:01.633Z" }, + { url = "https://files.pythonhosted.org/packages/e3/7f/a1a97644e39e7316d850784c642093c99df1290a460df4ede27659056834/filelock-3.20.1-py3-none-any.whl", hash = "sha256:15d9e9a67306188a44baa72f569d2bfd803076269365fdea0934385da4dc361a", size = 16666, upload-time = "2025-12-15T23:54:26.874Z" }, ] [[package]] name = "fonttools" -version = "4.60.0" +version = "4.61.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/27/d9/4eabd956fe123651a1f0efe29d9758b3837b5ae9a98934bdb571117033bb/fonttools-4.60.0.tar.gz", hash = "sha256:8f5927f049091a0ca74d35cce7f78e8f7775c83a6901a8fbe899babcc297146a", size = 3553671, upload-time = "2025-09-17T11:34:01.504Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/ca/cf17b88a8df95691275a3d77dc0a5ad9907f328ae53acbe6795da1b2f5ed/fonttools-4.61.1.tar.gz", hash = "sha256:6675329885c44657f826ef01d9e4fb33b9158e9d93c537d84ad8399539bc6f69", size = 3565756, upload-time = "2025-12-12T17:31:24.246Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/da/3d/c57731fbbf204ef1045caca28d5176430161ead73cd9feac3e9d9ef77ee6/fonttools-4.60.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a9106c202d68ff5f9b4a0094c4d7ad2eaa7e9280f06427b09643215e706eb016", size = 2830883, upload-time = "2025-09-17T11:32:10.552Z" }, - { url = "https://files.pythonhosted.org/packages/cc/2d/b7a6ebaed464ce441c755252cc222af11edc651d17c8f26482f429cc2c0e/fonttools-4.60.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9da3a4a3f2485b156bb429b4f8faa972480fc01f553f7c8c80d05d48f17eec89", size = 2356005, upload-time = "2025-09-17T11:32:13.248Z" }, - { url = "https://files.pythonhosted.org/packages/ee/c2/ea834e921324e2051403e125c1fe0bfbdde4951a7c1784e4ae6bdbd286cc/fonttools-4.60.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f84de764c6057b2ffd4feb50ddef481d92e348f0c70f2c849b723118d352bf3", size = 5041201, upload-time = "2025-09-17T11:32:15.373Z" }, - { url = "https://files.pythonhosted.org/packages/93/3c/1c64a338e9aa410d2d0728827d5bb1301463078cb225b94589f27558b427/fonttools-4.60.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:800b3fa0d5c12ddff02179d45b035a23989a6c597a71c8035c010fff3b2ef1bb", size = 4977696, upload-time = "2025-09-17T11:32:17.674Z" }, - { url = "https://files.pythonhosted.org/packages/07/cc/c8c411a0d9732bb886b870e052f20658fec9cf91118314f253950d2c1d65/fonttools-4.60.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd68f60b030277f292a582d31c374edfadc60bb33d51ec7b6cd4304531819ba", size = 5020386, upload-time = "2025-09-17T11:32:20.089Z" }, - { url = "https://files.pythonhosted.org/packages/13/01/1d3bc07cf92e7f4fc27f06d4494bf6078dc595b2e01b959157a4fd23df12/fonttools-4.60.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:53328e3ca9e5c8660ef6de07c35f8f312c189b757535e12141be7a8ec942de6e", size = 5131575, upload-time = "2025-09-17T11:32:22.582Z" }, - { url = "https://files.pythonhosted.org/packages/5a/16/08db3917ee19e89d2eb0ee637d37cd4136c849dc421ff63f406b9165c1a1/fonttools-4.60.0-cp311-cp311-win32.whl", hash = "sha256:d493c175ddd0b88a5376e61163e3e6fde3be8b8987db9b092e0a84650709c9e7", size = 2229297, upload-time = "2025-09-17T11:32:24.834Z" }, - { url = "https://files.pythonhosted.org/packages/d2/0b/76764da82c0dfcea144861f568d9e83f4b921e84f2be617b451257bb25a7/fonttools-4.60.0-cp311-cp311-win_amd64.whl", hash = "sha256:cc2770c9dc49c2d0366e9683f4d03beb46c98042d7ccc8ddbadf3459ecb051a7", size = 2277193, upload-time = "2025-09-17T11:32:27.094Z" }, - { url = "https://files.pythonhosted.org/packages/2a/9b/706ebf84b55ab03439c1f3a94d6915123c0d96099f4238b254fdacffe03a/fonttools-4.60.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8c68928a438d60dfde90e2f09aa7f848ed201176ca6652341744ceec4215859f", size = 2831953, upload-time = "2025-09-17T11:32:29.39Z" }, - { url = "https://files.pythonhosted.org/packages/76/40/782f485be450846e4f3aecff1f10e42af414fc6e19d235c70020f64278e1/fonttools-4.60.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b7133821249097cffabf0624eafd37f5a3358d5ce814febe9db688e3673e724e", size = 2351716, upload-time = "2025-09-17T11:32:31.46Z" }, - { url = "https://files.pythonhosted.org/packages/39/77/ad8d2a6ecc19716eb488c8cf118de10f7802e14bdf61d136d7b52358d6b1/fonttools-4.60.0-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d3638905d3d77ac8791127ce181f7cb434f37e4204d8b2e31b8f1e154320b41f", size = 4922729, upload-time = "2025-09-17T11:32:33.659Z" }, - { url = "https://files.pythonhosted.org/packages/6b/48/aa543037c6e7788e1bc36b3f858ac70a59d32d0f45915263d0b330a35140/fonttools-4.60.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7968a26ef010ae89aabbb2f8e9dec1e2709a2541bb8620790451ee8aeb4f6fbf", size = 4967188, upload-time = "2025-09-17T11:32:35.74Z" }, - { url = "https://files.pythonhosted.org/packages/ac/58/e407d2028adc6387947eff8f2940b31f4ed40b9a83c2c7bbc8b9255126e2/fonttools-4.60.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1ef01ca7847c356b0fe026b7b92304bc31dc60a4218689ee0acc66652c1a36b2", size = 4910043, upload-time = "2025-09-17T11:32:38.054Z" }, - { url = "https://files.pythonhosted.org/packages/16/ef/e78519b3c296ef757a21b792fc6a785aa2ef9a2efb098083d8ed5f6ee2ba/fonttools-4.60.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f3482d7ed7867edfcf785f77c1dffc876c4b2ddac19539c075712ff2a0703cf5", size = 5061980, upload-time = "2025-09-17T11:32:40.457Z" }, - { url = "https://files.pythonhosted.org/packages/00/4c/ad72444d1e3ef704ee90af8d5abf198016a39908d322bf41235562fb01a0/fonttools-4.60.0-cp312-cp312-win32.whl", hash = "sha256:8c937c4fe8addff575a984c9519433391180bf52cf35895524a07b520f376067", size = 2217750, upload-time = "2025-09-17T11:32:42.586Z" }, - { url = "https://files.pythonhosted.org/packages/46/55/3e8ac21963e130242f5a9ea2ebc57f5726d704bf4dcca89088b5b637b2d3/fonttools-4.60.0-cp312-cp312-win_amd64.whl", hash = "sha256:99b06d5d6f29f32e312adaed0367112f5ff2d300ea24363d377ec917daf9e8c5", size = 2266025, upload-time = "2025-09-17T11:32:44.8Z" }, - { url = "https://files.pythonhosted.org/packages/f9/a4/247d3e54eb5ed59e94e09866cfc4f9567e274fbf310ba390711851f63b3b/fonttools-4.60.0-py3-none-any.whl", hash = "sha256:496d26e4d14dcccdd6ada2e937e4d174d3138e3d73f5c9b6ec6eb2fd1dab4f66", size = 1142186, upload-time = "2025-09-17T11:33:59.287Z" }, + { url = "https://files.pythonhosted.org/packages/69/12/bf9f4eaa2fad039356cc627587e30ed008c03f1cebd3034376b5ee8d1d44/fonttools-4.61.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c6604b735bb12fef8e0efd5578c9fb5d3d8532d5001ea13a19cddf295673ee09", size = 2852213, upload-time = "2025-12-12T17:29:46.675Z" }, + { url = "https://files.pythonhosted.org/packages/ac/49/4138d1acb6261499bedde1c07f8c2605d1d8f9d77a151e5507fd3ef084b6/fonttools-4.61.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5ce02f38a754f207f2f06557523cd39a06438ba3aafc0639c477ac409fc64e37", size = 2401689, upload-time = "2025-12-12T17:29:48.769Z" }, + { url = "https://files.pythonhosted.org/packages/e5/fe/e6ce0fe20a40e03aef906af60aa87668696f9e4802fa283627d0b5ed777f/fonttools-4.61.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77efb033d8d7ff233385f30c62c7c79271c8885d5c9657d967ede124671bbdfb", size = 5058809, upload-time = "2025-12-12T17:29:51.701Z" }, + { url = "https://files.pythonhosted.org/packages/79/61/1ca198af22f7dd22c17ab86e9024ed3c06299cfdb08170640e9996d501a0/fonttools-4.61.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:75c1a6dfac6abd407634420c93864a1e274ebc1c7531346d9254c0d8f6ca00f9", size = 5036039, upload-time = "2025-12-12T17:29:53.659Z" }, + { url = "https://files.pythonhosted.org/packages/99/cc/fa1801e408586b5fce4da9f5455af8d770f4fc57391cd5da7256bb364d38/fonttools-4.61.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0de30bfe7745c0d1ffa2b0b7048fb7123ad0d71107e10ee090fa0b16b9452e87", size = 5034714, upload-time = "2025-12-12T17:29:55.592Z" }, + { url = "https://files.pythonhosted.org/packages/bf/aa/b7aeafe65adb1b0a925f8f25725e09f078c635bc22754f3fecb7456955b0/fonttools-4.61.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:58b0ee0ab5b1fc9921eccfe11d1435added19d6494dde14e323f25ad2bc30c56", size = 5158648, upload-time = "2025-12-12T17:29:57.861Z" }, + { url = "https://files.pythonhosted.org/packages/99/f9/08ea7a38663328881384c6e7777bbefc46fd7d282adfd87a7d2b84ec9d50/fonttools-4.61.1-cp311-cp311-win32.whl", hash = "sha256:f79b168428351d11e10c5aeb61a74e1851ec221081299f4cf56036a95431c43a", size = 2280681, upload-time = "2025-12-12T17:29:59.943Z" }, + { url = "https://files.pythonhosted.org/packages/07/ad/37dd1ae5fa6e01612a1fbb954f0927681f282925a86e86198ccd7b15d515/fonttools-4.61.1-cp311-cp311-win_amd64.whl", hash = "sha256:fe2efccb324948a11dd09d22136fe2ac8a97d6c1347cf0b58a911dcd529f66b7", size = 2331951, upload-time = "2025-12-12T17:30:02.254Z" }, + { url = "https://files.pythonhosted.org/packages/6f/16/7decaa24a1bd3a70c607b2e29f0adc6159f36a7e40eaba59846414765fd4/fonttools-4.61.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f3cb4a569029b9f291f88aafc927dd53683757e640081ca8c412781ea144565e", size = 2851593, upload-time = "2025-12-12T17:30:04.225Z" }, + { url = "https://files.pythonhosted.org/packages/94/98/3c4cb97c64713a8cf499b3245c3bf9a2b8fd16a3e375feff2aed78f96259/fonttools-4.61.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41a7170d042e8c0024703ed13b71893519a1a6d6e18e933e3ec7507a2c26a4b2", size = 2400231, upload-time = "2025-12-12T17:30:06.47Z" }, + { url = "https://files.pythonhosted.org/packages/b7/37/82dbef0f6342eb01f54bca073ac1498433d6ce71e50c3c3282b655733b31/fonttools-4.61.1-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10d88e55330e092940584774ee5e8a6971b01fc2f4d3466a1d6c158230880796", size = 4954103, upload-time = "2025-12-12T17:30:08.432Z" }, + { url = "https://files.pythonhosted.org/packages/6c/44/f3aeac0fa98e7ad527f479e161aca6c3a1e47bb6996b053d45226fe37bf2/fonttools-4.61.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:15acc09befd16a0fb8a8f62bc147e1a82817542d72184acca9ce6e0aeda9fa6d", size = 5004295, upload-time = "2025-12-12T17:30:10.56Z" }, + { url = "https://files.pythonhosted.org/packages/14/e8/7424ced75473983b964d09f6747fa09f054a6d656f60e9ac9324cf40c743/fonttools-4.61.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e6bcdf33aec38d16508ce61fd81838f24c83c90a1d1b8c68982857038673d6b8", size = 4944109, upload-time = "2025-12-12T17:30:12.874Z" }, + { url = "https://files.pythonhosted.org/packages/c8/8b/6391b257fa3d0b553d73e778f953a2f0154292a7a7a085e2374b111e5410/fonttools-4.61.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5fade934607a523614726119164ff621e8c30e8fa1ffffbbd358662056ba69f0", size = 5093598, upload-time = "2025-12-12T17:30:15.79Z" }, + { url = "https://files.pythonhosted.org/packages/d9/71/fd2ea96cdc512d92da5678a1c98c267ddd4d8c5130b76d0f7a80f9a9fde8/fonttools-4.61.1-cp312-cp312-win32.whl", hash = "sha256:75da8f28eff26defba42c52986de97b22106cb8f26515b7c22443ebc9c2d3261", size = 2269060, upload-time = "2025-12-12T17:30:18.058Z" }, + { url = "https://files.pythonhosted.org/packages/80/3b/a3e81b71aed5a688e89dfe0e2694b26b78c7d7f39a5ffd8a7d75f54a12a8/fonttools-4.61.1-cp312-cp312-win_amd64.whl", hash = "sha256:497c31ce314219888c0e2fce5ad9178ca83fe5230b01a5006726cdf3ac9f24d9", size = 2319078, upload-time = "2025-12-12T17:30:22.862Z" }, + { url = "https://files.pythonhosted.org/packages/c7/4e/ce75a57ff3aebf6fc1f4e9d508b8e5810618a33d900ad6c19eb30b290b97/fonttools-4.61.1-py3-none-any.whl", hash = "sha256:17d2bf5d541add43822bcf0c43d7d847b160c9bb01d15d5007d84e2217aaa371", size = 1148996, upload-time = "2025-12-12T17:31:21.03Z" }, ] [[package]] name = "frozenlist" -version = "1.7.0" +version = "1.8.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/79/b1/b64018016eeb087db503b038296fd782586432b9c077fc5c7839e9cb6ef6/frozenlist-1.7.0.tar.gz", hash = "sha256:2e310d81923c2437ea8670467121cc3e9b0f76d3043cc1d2331d56c7fb7a3a8f", size = 45078, upload-time = "2025-06-09T23:02:35.538Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875, upload-time = "2025-10-06T05:38:17.865Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/34/7e/803dde33760128acd393a27eb002f2020ddb8d99d30a44bfbaab31c5f08a/frozenlist-1.7.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:aa51e147a66b2d74de1e6e2cf5921890de6b0f4820b257465101d7f37b49fb5a", size = 82251, upload-time = "2025-06-09T23:00:16.279Z" }, - { url = "https://files.pythonhosted.org/packages/75/a9/9c2c5760b6ba45eae11334db454c189d43d34a4c0b489feb2175e5e64277/frozenlist-1.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9b35db7ce1cd71d36ba24f80f0c9e7cff73a28d7a74e91fe83e23d27c7828750", size = 48183, upload-time = "2025-06-09T23:00:17.698Z" }, - { url = "https://files.pythonhosted.org/packages/47/be/4038e2d869f8a2da165f35a6befb9158c259819be22eeaf9c9a8f6a87771/frozenlist-1.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:34a69a85e34ff37791e94542065c8416c1afbf820b68f720452f636d5fb990cd", size = 47107, upload-time = "2025-06-09T23:00:18.952Z" }, - { url = "https://files.pythonhosted.org/packages/79/26/85314b8a83187c76a37183ceed886381a5f992975786f883472fcb6dc5f2/frozenlist-1.7.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a646531fa8d82c87fe4bb2e596f23173caec9185bfbca5d583b4ccfb95183e2", size = 237333, upload-time = "2025-06-09T23:00:20.275Z" }, - { url = "https://files.pythonhosted.org/packages/1f/fd/e5b64f7d2c92a41639ffb2ad44a6a82f347787abc0c7df5f49057cf11770/frozenlist-1.7.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:79b2ffbba483f4ed36a0f236ccb85fbb16e670c9238313709638167670ba235f", size = 231724, upload-time = "2025-06-09T23:00:21.705Z" }, - { url = "https://files.pythonhosted.org/packages/20/fb/03395c0a43a5976af4bf7534759d214405fbbb4c114683f434dfdd3128ef/frozenlist-1.7.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a26f205c9ca5829cbf82bb2a84b5c36f7184c4316617d7ef1b271a56720d6b30", size = 245842, upload-time = "2025-06-09T23:00:23.148Z" }, - { url = "https://files.pythonhosted.org/packages/d0/15/c01c8e1dffdac5d9803507d824f27aed2ba76b6ed0026fab4d9866e82f1f/frozenlist-1.7.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bcacfad3185a623fa11ea0e0634aac7b691aa925d50a440f39b458e41c561d98", size = 239767, upload-time = "2025-06-09T23:00:25.103Z" }, - { url = "https://files.pythonhosted.org/packages/14/99/3f4c6fe882c1f5514b6848aa0a69b20cb5e5d8e8f51a339d48c0e9305ed0/frozenlist-1.7.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72c1b0fe8fe451b34f12dce46445ddf14bd2a5bcad7e324987194dc8e3a74c86", size = 224130, upload-time = "2025-06-09T23:00:27.061Z" }, - { url = "https://files.pythonhosted.org/packages/4d/83/220a374bd7b2aeba9d0725130665afe11de347d95c3620b9b82cc2fcab97/frozenlist-1.7.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61d1a5baeaac6c0798ff6edfaeaa00e0e412d49946c53fae8d4b8e8b3566c4ae", size = 235301, upload-time = "2025-06-09T23:00:29.02Z" }, - { url = "https://files.pythonhosted.org/packages/03/3c/3e3390d75334a063181625343e8daab61b77e1b8214802cc4e8a1bb678fc/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7edf5c043c062462f09b6820de9854bf28cc6cc5b6714b383149745e287181a8", size = 234606, upload-time = "2025-06-09T23:00:30.514Z" }, - { url = "https://files.pythonhosted.org/packages/23/1e/58232c19608b7a549d72d9903005e2d82488f12554a32de2d5fb59b9b1ba/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:d50ac7627b3a1bd2dcef6f9da89a772694ec04d9a61b66cf87f7d9446b4a0c31", size = 248372, upload-time = "2025-06-09T23:00:31.966Z" }, - { url = "https://files.pythonhosted.org/packages/c0/a4/e4a567e01702a88a74ce8a324691e62a629bf47d4f8607f24bf1c7216e7f/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ce48b2fece5aeb45265bb7a58259f45027db0abff478e3077e12b05b17fb9da7", size = 229860, upload-time = "2025-06-09T23:00:33.375Z" }, - { url = "https://files.pythonhosted.org/packages/73/a6/63b3374f7d22268b41a9db73d68a8233afa30ed164c46107b33c4d18ecdd/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:fe2365ae915a1fafd982c146754e1de6ab3478def8a59c86e1f7242d794f97d5", size = 245893, upload-time = "2025-06-09T23:00:35.002Z" }, - { url = "https://files.pythonhosted.org/packages/6d/eb/d18b3f6e64799a79673c4ba0b45e4cfbe49c240edfd03a68be20002eaeaa/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:45a6f2fdbd10e074e8814eb98b05292f27bad7d1883afbe009d96abdcf3bc898", size = 246323, upload-time = "2025-06-09T23:00:36.468Z" }, - { url = "https://files.pythonhosted.org/packages/5a/f5/720f3812e3d06cd89a1d5db9ff6450088b8f5c449dae8ffb2971a44da506/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:21884e23cffabb157a9dd7e353779077bf5b8f9a58e9b262c6caad2ef5f80a56", size = 233149, upload-time = "2025-06-09T23:00:37.963Z" }, - { url = "https://files.pythonhosted.org/packages/69/68/03efbf545e217d5db8446acfd4c447c15b7c8cf4dbd4a58403111df9322d/frozenlist-1.7.0-cp311-cp311-win32.whl", hash = "sha256:284d233a8953d7b24f9159b8a3496fc1ddc00f4db99c324bd5fb5f22d8698ea7", size = 39565, upload-time = "2025-06-09T23:00:39.753Z" }, - { url = "https://files.pythonhosted.org/packages/58/17/fe61124c5c333ae87f09bb67186d65038834a47d974fc10a5fadb4cc5ae1/frozenlist-1.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:387cbfdcde2f2353f19c2f66bbb52406d06ed77519ac7ee21be0232147c2592d", size = 44019, upload-time = "2025-06-09T23:00:40.988Z" }, - { url = "https://files.pythonhosted.org/packages/ef/a2/c8131383f1e66adad5f6ecfcce383d584ca94055a34d683bbb24ac5f2f1c/frozenlist-1.7.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3dbf9952c4bb0e90e98aec1bd992b3318685005702656bc6f67c1a32b76787f2", size = 81424, upload-time = "2025-06-09T23:00:42.24Z" }, - { url = "https://files.pythonhosted.org/packages/4c/9d/02754159955088cb52567337d1113f945b9e444c4960771ea90eb73de8db/frozenlist-1.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1f5906d3359300b8a9bb194239491122e6cf1444c2efb88865426f170c262cdb", size = 47952, upload-time = "2025-06-09T23:00:43.481Z" }, - { url = "https://files.pythonhosted.org/packages/01/7a/0046ef1bd6699b40acd2067ed6d6670b4db2f425c56980fa21c982c2a9db/frozenlist-1.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3dabd5a8f84573c8d10d8859a50ea2dec01eea372031929871368c09fa103478", size = 46688, upload-time = "2025-06-09T23:00:44.793Z" }, - { url = "https://files.pythonhosted.org/packages/d6/a2/a910bafe29c86997363fb4c02069df4ff0b5bc39d33c5198b4e9dd42d8f8/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa57daa5917f1738064f302bf2626281a1cb01920c32f711fbc7bc36111058a8", size = 243084, upload-time = "2025-06-09T23:00:46.125Z" }, - { url = "https://files.pythonhosted.org/packages/64/3e/5036af9d5031374c64c387469bfcc3af537fc0f5b1187d83a1cf6fab1639/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c193dda2b6d49f4c4398962810fa7d7c78f032bf45572b3e04dd5249dff27e08", size = 233524, upload-time = "2025-06-09T23:00:47.73Z" }, - { url = "https://files.pythonhosted.org/packages/06/39/6a17b7c107a2887e781a48ecf20ad20f1c39d94b2a548c83615b5b879f28/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfe2b675cf0aaa6d61bf8fbffd3c274b3c9b7b1623beb3809df8a81399a4a9c4", size = 248493, upload-time = "2025-06-09T23:00:49.742Z" }, - { url = "https://files.pythonhosted.org/packages/be/00/711d1337c7327d88c44d91dd0f556a1c47fb99afc060ae0ef66b4d24793d/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8fc5d5cda37f62b262405cf9652cf0856839c4be8ee41be0afe8858f17f4c94b", size = 244116, upload-time = "2025-06-09T23:00:51.352Z" }, - { url = "https://files.pythonhosted.org/packages/24/fe/74e6ec0639c115df13d5850e75722750adabdc7de24e37e05a40527ca539/frozenlist-1.7.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0d5ce521d1dd7d620198829b87ea002956e4319002ef0bc8d3e6d045cb4646e", size = 224557, upload-time = "2025-06-09T23:00:52.855Z" }, - { url = "https://files.pythonhosted.org/packages/8d/db/48421f62a6f77c553575201e89048e97198046b793f4a089c79a6e3268bd/frozenlist-1.7.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:488d0a7d6a0008ca0db273c542098a0fa9e7dfaa7e57f70acef43f32b3f69dca", size = 241820, upload-time = "2025-06-09T23:00:54.43Z" }, - { url = "https://files.pythonhosted.org/packages/1d/fa/cb4a76bea23047c8462976ea7b7a2bf53997a0ca171302deae9d6dd12096/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:15a7eaba63983d22c54d255b854e8108e7e5f3e89f647fc854bd77a237e767df", size = 236542, upload-time = "2025-06-09T23:00:56.409Z" }, - { url = "https://files.pythonhosted.org/packages/5d/32/476a4b5cfaa0ec94d3f808f193301debff2ea42288a099afe60757ef6282/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1eaa7e9c6d15df825bf255649e05bd8a74b04a4d2baa1ae46d9c2d00b2ca2cb5", size = 249350, upload-time = "2025-06-09T23:00:58.468Z" }, - { url = "https://files.pythonhosted.org/packages/8d/ba/9a28042f84a6bf8ea5dbc81cfff8eaef18d78b2a1ad9d51c7bc5b029ad16/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e4389e06714cfa9d47ab87f784a7c5be91d3934cd6e9a7b85beef808297cc025", size = 225093, upload-time = "2025-06-09T23:01:00.015Z" }, - { url = "https://files.pythonhosted.org/packages/bc/29/3a32959e68f9cf000b04e79ba574527c17e8842e38c91d68214a37455786/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:73bd45e1488c40b63fe5a7df892baf9e2a4d4bb6409a2b3b78ac1c6236178e01", size = 245482, upload-time = "2025-06-09T23:01:01.474Z" }, - { url = "https://files.pythonhosted.org/packages/80/e8/edf2f9e00da553f07f5fa165325cfc302dead715cab6ac8336a5f3d0adc2/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:99886d98e1643269760e5fe0df31e5ae7050788dd288947f7f007209b8c33f08", size = 249590, upload-time = "2025-06-09T23:01:02.961Z" }, - { url = "https://files.pythonhosted.org/packages/1c/80/9a0eb48b944050f94cc51ee1c413eb14a39543cc4f760ed12657a5a3c45a/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:290a172aae5a4c278c6da8a96222e6337744cd9c77313efe33d5670b9f65fc43", size = 237785, upload-time = "2025-06-09T23:01:05.095Z" }, - { url = "https://files.pythonhosted.org/packages/f3/74/87601e0fb0369b7a2baf404ea921769c53b7ae00dee7dcfe5162c8c6dbf0/frozenlist-1.7.0-cp312-cp312-win32.whl", hash = "sha256:426c7bc70e07cfebc178bc4c2bf2d861d720c4fff172181eeb4a4c41d4ca2ad3", size = 39487, upload-time = "2025-06-09T23:01:06.54Z" }, - { url = "https://files.pythonhosted.org/packages/0b/15/c026e9a9fc17585a9d461f65d8593d281fedf55fbf7eb53f16c6df2392f9/frozenlist-1.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:563b72efe5da92e02eb68c59cb37205457c977aa7a449ed1b37e6939e5c47c6a", size = 43874, upload-time = "2025-06-09T23:01:07.752Z" }, - { url = "https://files.pythonhosted.org/packages/ee/45/b82e3c16be2182bff01179db177fe144d58b5dc787a7d4492c6ed8b9317f/frozenlist-1.7.0-py3-none-any.whl", hash = "sha256:9a5af342e34f7e97caf8c995864c7a396418ae2859cc6fdf1b1073020d516a7e", size = 13106, upload-time = "2025-06-09T23:02:34.204Z" }, + { url = "https://files.pythonhosted.org/packages/bc/03/077f869d540370db12165c0aa51640a873fb661d8b315d1d4d67b284d7ac/frozenlist-1.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:09474e9831bc2b2199fad6da3c14c7b0fbdd377cce9d3d77131be28906cb7d84", size = 86912, upload-time = "2025-10-06T05:35:45.98Z" }, + { url = "https://files.pythonhosted.org/packages/df/b5/7610b6bd13e4ae77b96ba85abea1c8cb249683217ef09ac9e0ae93f25a91/frozenlist-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:17c883ab0ab67200b5f964d2b9ed6b00971917d5d8a92df149dc2c9779208ee9", size = 50046, upload-time = "2025-10-06T05:35:47.009Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ef/0e8f1fe32f8a53dd26bdd1f9347efe0778b0fddf62789ea683f4cc7d787d/frozenlist-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa47e444b8ba08fffd1c18e8cdb9a75db1b6a27f17507522834ad13ed5922b93", size = 50119, upload-time = "2025-10-06T05:35:48.38Z" }, + { url = "https://files.pythonhosted.org/packages/11/b1/71a477adc7c36e5fb628245dfbdea2166feae310757dea848d02bd0689fd/frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2552f44204b744fba866e573be4c1f9048d6a324dfe14475103fd51613eb1d1f", size = 231067, upload-time = "2025-10-06T05:35:49.97Z" }, + { url = "https://files.pythonhosted.org/packages/45/7e/afe40eca3a2dc19b9904c0f5d7edfe82b5304cb831391edec0ac04af94c2/frozenlist-1.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:957e7c38f250991e48a9a73e6423db1bb9dd14e722a10f6b8bb8e16a0f55f695", size = 233160, upload-time = "2025-10-06T05:35:51.729Z" }, + { url = "https://files.pythonhosted.org/packages/a6/aa/7416eac95603ce428679d273255ffc7c998d4132cfae200103f164b108aa/frozenlist-1.8.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8585e3bb2cdea02fc88ffa245069c36555557ad3609e83be0ec71f54fd4abb52", size = 228544, upload-time = "2025-10-06T05:35:53.246Z" }, + { url = "https://files.pythonhosted.org/packages/8b/3d/2a2d1f683d55ac7e3875e4263d28410063e738384d3adc294f5ff3d7105e/frozenlist-1.8.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:edee74874ce20a373d62dc28b0b18b93f645633c2943fd90ee9d898550770581", size = 243797, upload-time = "2025-10-06T05:35:54.497Z" }, + { url = "https://files.pythonhosted.org/packages/78/1e/2d5565b589e580c296d3bb54da08d206e797d941a83a6fdea42af23be79c/frozenlist-1.8.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c9a63152fe95756b85f31186bddf42e4c02c6321207fd6601a1c89ebac4fe567", size = 247923, upload-time = "2025-10-06T05:35:55.861Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c3/65872fcf1d326a7f101ad4d86285c403c87be7d832b7470b77f6d2ed5ddc/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b6db2185db9be0a04fecf2f241c70b63b1a242e2805be291855078f2b404dd6b", size = 230886, upload-time = "2025-10-06T05:35:57.399Z" }, + { url = "https://files.pythonhosted.org/packages/a0/76/ac9ced601d62f6956f03cc794f9e04c81719509f85255abf96e2510f4265/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f4be2e3d8bc8aabd566f8d5b8ba7ecc09249d74ba3c9ed52e54dc23a293f0b92", size = 245731, upload-time = "2025-10-06T05:35:58.563Z" }, + { url = "https://files.pythonhosted.org/packages/b9/49/ecccb5f2598daf0b4a1415497eba4c33c1e8ce07495eb07d2860c731b8d5/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c8d1634419f39ea6f5c427ea2f90ca85126b54b50837f31497f3bf38266e853d", size = 241544, upload-time = "2025-10-06T05:35:59.719Z" }, + { url = "https://files.pythonhosted.org/packages/53/4b/ddf24113323c0bbcc54cb38c8b8916f1da7165e07b8e24a717b4a12cbf10/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:1a7fa382a4a223773ed64242dbe1c9c326ec09457e6b8428efb4118c685c3dfd", size = 241806, upload-time = "2025-10-06T05:36:00.959Z" }, + { url = "https://files.pythonhosted.org/packages/a7/fb/9b9a084d73c67175484ba2789a59f8eebebd0827d186a8102005ce41e1ba/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:11847b53d722050808926e785df837353bd4d75f1d494377e59b23594d834967", size = 229382, upload-time = "2025-10-06T05:36:02.22Z" }, + { url = "https://files.pythonhosted.org/packages/95/a3/c8fb25aac55bf5e12dae5c5aa6a98f85d436c1dc658f21c3ac73f9fa95e5/frozenlist-1.8.0-cp311-cp311-win32.whl", hash = "sha256:27c6e8077956cf73eadd514be8fb04d77fc946a7fe9f7fe167648b0b9085cc25", size = 39647, upload-time = "2025-10-06T05:36:03.409Z" }, + { url = "https://files.pythonhosted.org/packages/0a/f5/603d0d6a02cfd4c8f2a095a54672b3cf967ad688a60fb9faf04fc4887f65/frozenlist-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:ac913f8403b36a2c8610bbfd25b8013488533e71e62b4b4adce9c86c8cea905b", size = 44064, upload-time = "2025-10-06T05:36:04.368Z" }, + { url = "https://files.pythonhosted.org/packages/5d/16/c2c9ab44e181f043a86f9a8f84d5124b62dbcb3a02c0977ec72b9ac1d3e0/frozenlist-1.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:d4d3214a0f8394edfa3e303136d0575eece0745ff2b47bd2cb2e66dd92d4351a", size = 39937, upload-time = "2025-10-06T05:36:05.669Z" }, + { url = "https://files.pythonhosted.org/packages/69/29/948b9aa87e75820a38650af445d2ef2b6b8a6fab1a23b6bb9e4ef0be2d59/frozenlist-1.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1", size = 87782, upload-time = "2025-10-06T05:36:06.649Z" }, + { url = "https://files.pythonhosted.org/packages/64/80/4f6e318ee2a7c0750ed724fa33a4bdf1eacdc5a39a7a24e818a773cd91af/frozenlist-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:229bf37d2e4acdaf808fd3f06e854a4a7a3661e871b10dc1f8f1896a3b05f18b", size = 50594, upload-time = "2025-10-06T05:36:07.69Z" }, + { url = "https://files.pythonhosted.org/packages/2b/94/5c8a2b50a496b11dd519f4a24cb5496cf125681dd99e94c604ccdea9419a/frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f833670942247a14eafbb675458b4e61c82e002a148f49e68257b79296e865c4", size = 50448, upload-time = "2025-10-06T05:36:08.78Z" }, + { url = "https://files.pythonhosted.org/packages/6a/bd/d91c5e39f490a49df14320f4e8c80161cfcce09f1e2cde1edd16a551abb3/frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:494a5952b1c597ba44e0e78113a7266e656b9794eec897b19ead706bd7074383", size = 242411, upload-time = "2025-10-06T05:36:09.801Z" }, + { url = "https://files.pythonhosted.org/packages/8f/83/f61505a05109ef3293dfb1ff594d13d64a2324ac3482be2cedc2be818256/frozenlist-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f423a119f4777a4a056b66ce11527366a8bb92f54e541ade21f2374433f6d4", size = 243014, upload-time = "2025-10-06T05:36:11.394Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cb/cb6c7b0f7d4023ddda30cf56b8b17494eb3a79e3fda666bf735f63118b35/frozenlist-1.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3462dd9475af2025c31cc61be6652dfa25cbfb56cbbf52f4ccfe029f38decaf8", size = 234909, upload-time = "2025-10-06T05:36:12.598Z" }, + { url = "https://files.pythonhosted.org/packages/31/c5/cd7a1f3b8b34af009fb17d4123c5a778b44ae2804e3ad6b86204255f9ec5/frozenlist-1.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4c800524c9cd9bac5166cd6f55285957fcfc907db323e193f2afcd4d9abd69b", size = 250049, upload-time = "2025-10-06T05:36:14.065Z" }, + { url = "https://files.pythonhosted.org/packages/c0/01/2f95d3b416c584a1e7f0e1d6d31998c4a795f7544069ee2e0962a4b60740/frozenlist-1.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d6a5df73acd3399d893dafc71663ad22534b5aa4f94e8a2fabfe856c3c1b6a52", size = 256485, upload-time = "2025-10-06T05:36:15.39Z" }, + { url = "https://files.pythonhosted.org/packages/ce/03/024bf7720b3abaebcff6d0793d73c154237b85bdf67b7ed55e5e9596dc9a/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:405e8fe955c2280ce66428b3ca55e12b3c4e9c336fb2103a4937e891c69a4a29", size = 237619, upload-time = "2025-10-06T05:36:16.558Z" }, + { url = "https://files.pythonhosted.org/packages/69/fa/f8abdfe7d76b731f5d8bd217827cf6764d4f1d9763407e42717b4bed50a0/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:908bd3f6439f2fef9e85031b59fd4f1297af54415fb60e4254a95f75b3cab3f3", size = 250320, upload-time = "2025-10-06T05:36:17.821Z" }, + { url = "https://files.pythonhosted.org/packages/f5/3c/b051329f718b463b22613e269ad72138cc256c540f78a6de89452803a47d/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:294e487f9ec720bd8ffcebc99d575f7eff3568a08a253d1ee1a0378754b74143", size = 246820, upload-time = "2025-10-06T05:36:19.046Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ae/58282e8f98e444b3f4dd42448ff36fa38bef29e40d40f330b22e7108f565/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:74c51543498289c0c43656701be6b077f4b265868fa7f8a8859c197006efb608", size = 250518, upload-time = "2025-10-06T05:36:20.763Z" }, + { url = "https://files.pythonhosted.org/packages/8f/96/007e5944694d66123183845a106547a15944fbbb7154788cbf7272789536/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:776f352e8329135506a1d6bf16ac3f87bc25b28e765949282dcc627af36123aa", size = 239096, upload-time = "2025-10-06T05:36:22.129Z" }, + { url = "https://files.pythonhosted.org/packages/66/bb/852b9d6db2fa40be96f29c0d1205c306288f0684df8fd26ca1951d461a56/frozenlist-1.8.0-cp312-cp312-win32.whl", hash = "sha256:433403ae80709741ce34038da08511d4a77062aa924baf411ef73d1146e74faf", size = 39985, upload-time = "2025-10-06T05:36:23.661Z" }, + { url = "https://files.pythonhosted.org/packages/b8/af/38e51a553dd66eb064cdf193841f16f077585d4d28394c2fa6235cb41765/frozenlist-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:34187385b08f866104f0c0617404c8eb08165ab1272e884abc89c112e9c00746", size = 44591, upload-time = "2025-10-06T05:36:24.958Z" }, + { url = "https://files.pythonhosted.org/packages/a7/06/1dc65480ab147339fecc70797e9c2f69d9cea9cf38934ce08df070fdb9cb/frozenlist-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:fe3c58d2f5db5fbd18c2987cba06d51b0529f52bc3a6cdc33d3f4eab725104bd", size = 40102, upload-time = "2025-10-06T05:36:26.333Z" }, + { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" }, ] [[package]] @@ -651,37 +655,37 @@ wheels = [ [[package]] name = "google-crc32c" -version = "1.7.1" +version = "1.8.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/19/ae/87802e6d9f9d69adfaedfcfd599266bf386a54d0be058b532d04c794f76d/google_crc32c-1.7.1.tar.gz", hash = "sha256:2bff2305f98846f3e825dbeec9ee406f89da7962accdb29356e4eadc251bd472", size = 14495, upload-time = "2025-03-26T14:29:13.32Z" } +sdist = { url = "https://files.pythonhosted.org/packages/03/41/4b9c02f99e4c5fb477122cd5437403b552873f014616ac1d19ac8221a58d/google_crc32c-1.8.0.tar.gz", hash = "sha256:a428e25fb7691024de47fecfbff7ff957214da51eddded0da0ae0e0f03a2cf79", size = 14192, upload-time = "2025-12-16T00:35:25.142Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f7/94/220139ea87822b6fdfdab4fb9ba81b3fff7ea2c82e2af34adc726085bffc/google_crc32c-1.7.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:6fbab4b935989e2c3610371963ba1b86afb09537fd0c633049be82afe153ac06", size = 30468, upload-time = "2025-03-26T14:32:52.215Z" }, - { url = "https://files.pythonhosted.org/packages/94/97/789b23bdeeb9d15dc2904660463ad539d0318286d7633fe2760c10ed0c1c/google_crc32c-1.7.1-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:ed66cbe1ed9cbaaad9392b5259b3eba4a9e565420d734e6238813c428c3336c9", size = 30313, upload-time = "2025-03-26T14:57:38.758Z" }, - { url = "https://files.pythonhosted.org/packages/81/b8/976a2b843610c211e7ccb3e248996a61e87dbb2c09b1499847e295080aec/google_crc32c-1.7.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee6547b657621b6cbed3562ea7826c3e11cab01cd33b74e1f677690652883e77", size = 33048, upload-time = "2025-03-26T14:41:30.679Z" }, - { url = "https://files.pythonhosted.org/packages/c9/16/a3842c2cf591093b111d4a5e2bfb478ac6692d02f1b386d2a33283a19dc9/google_crc32c-1.7.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d68e17bad8f7dd9a49181a1f5a8f4b251c6dbc8cc96fb79f1d321dfd57d66f53", size = 32669, upload-time = "2025-03-26T14:41:31.432Z" }, - { url = "https://files.pythonhosted.org/packages/04/17/ed9aba495916fcf5fe4ecb2267ceb851fc5f273c4e4625ae453350cfd564/google_crc32c-1.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:6335de12921f06e1f774d0dd1fbea6bf610abe0887a1638f64d694013138be5d", size = 33476, upload-time = "2025-03-26T14:29:10.211Z" }, - { url = "https://files.pythonhosted.org/packages/dd/b7/787e2453cf8639c94b3d06c9d61f512234a82e1d12d13d18584bd3049904/google_crc32c-1.7.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:2d73a68a653c57281401871dd4aeebbb6af3191dcac751a76ce430df4d403194", size = 30470, upload-time = "2025-03-26T14:34:31.655Z" }, - { url = "https://files.pythonhosted.org/packages/ed/b4/6042c2b0cbac3ec3a69bb4c49b28d2f517b7a0f4a0232603c42c58e22b44/google_crc32c-1.7.1-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:22beacf83baaf59f9d3ab2bbb4db0fb018da8e5aebdce07ef9f09fce8220285e", size = 30315, upload-time = "2025-03-26T15:01:54.634Z" }, - { url = "https://files.pythonhosted.org/packages/29/ad/01e7a61a5d059bc57b702d9ff6a18b2585ad97f720bd0a0dbe215df1ab0e/google_crc32c-1.7.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19eafa0e4af11b0a4eb3974483d55d2d77ad1911e6cf6f832e1574f6781fd337", size = 33180, upload-time = "2025-03-26T14:41:32.168Z" }, - { url = "https://files.pythonhosted.org/packages/3b/a5/7279055cf004561894ed3a7bfdf5bf90a53f28fadd01af7cd166e88ddf16/google_crc32c-1.7.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6d86616faaea68101195c6bdc40c494e4d76f41e07a37ffdef270879c15fb65", size = 32794, upload-time = "2025-03-26T14:41:33.264Z" }, - { url = "https://files.pythonhosted.org/packages/0f/d6/77060dbd140c624e42ae3ece3df53b9d811000729a5c821b9fd671ceaac6/google_crc32c-1.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:b7491bdc0c7564fcf48c0179d2048ab2f7c7ba36b84ccd3a3e1c3f7a72d3bba6", size = 33477, upload-time = "2025-03-26T14:29:10.94Z" }, - { url = "https://files.pythonhosted.org/packages/16/1b/1693372bf423ada422f80fd88260dbfd140754adb15cbc4d7e9a68b1cb8e/google_crc32c-1.7.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85fef7fae11494e747c9fd1359a527e5970fc9603c90764843caabd3a16a0a48", size = 28241, upload-time = "2025-03-26T14:41:45.898Z" }, - { url = "https://files.pythonhosted.org/packages/fd/3c/2a19a60a473de48717b4efb19398c3f914795b64a96cf3fbe82588044f78/google_crc32c-1.7.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6efb97eb4369d52593ad6f75e7e10d053cf00c48983f7a973105bc70b0ac4d82", size = 28048, upload-time = "2025-03-26T14:41:46.696Z" }, + { url = "https://files.pythonhosted.org/packages/5d/ef/21ccfaab3d5078d41efe8612e0ed0bfc9ce22475de074162a91a25f7980d/google_crc32c-1.8.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:014a7e68d623e9a4222d663931febc3033c5c7c9730785727de2a81f87d5bab8", size = 31298, upload-time = "2025-12-16T00:20:32.241Z" }, + { url = "https://files.pythonhosted.org/packages/c5/b8/f8413d3f4b676136e965e764ceedec904fe38ae8de0cdc52a12d8eb1096e/google_crc32c-1.8.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:86cfc00fe45a0ac7359e5214a1704e51a99e757d0272554874f419f79838c5f7", size = 30872, upload-time = "2025-12-16T00:33:58.785Z" }, + { url = "https://files.pythonhosted.org/packages/f6/fd/33aa4ec62b290477181c55bb1c9302c9698c58c0ce9a6ab4874abc8b0d60/google_crc32c-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:19b40d637a54cb71e0829179f6cb41835f0fbd9e8eb60552152a8b52c36cbe15", size = 33243, upload-time = "2025-12-16T00:40:21.46Z" }, + { url = "https://files.pythonhosted.org/packages/71/03/4820b3bd99c9653d1a5210cb32f9ba4da9681619b4d35b6a052432df4773/google_crc32c-1.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:17446feb05abddc187e5441a45971b8394ea4c1b6efd88ab0af393fd9e0a156a", size = 33608, upload-time = "2025-12-16T00:40:22.204Z" }, + { url = "https://files.pythonhosted.org/packages/7c/43/acf61476a11437bf9733fb2f70599b1ced11ec7ed9ea760fdd9a77d0c619/google_crc32c-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:71734788a88f551fbd6a97be9668a0020698e07b2bf5b3aa26a36c10cdfb27b2", size = 34439, upload-time = "2025-12-16T00:35:20.458Z" }, + { url = "https://files.pythonhosted.org/packages/e9/5f/7307325b1198b59324c0fa9807cafb551afb65e831699f2ce211ad5c8240/google_crc32c-1.8.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:4b8286b659c1335172e39563ab0a768b8015e88e08329fa5321f774275fc3113", size = 31300, upload-time = "2025-12-16T00:21:56.723Z" }, + { url = "https://files.pythonhosted.org/packages/21/8e/58c0d5d86e2220e6a37befe7e6a94dd2f6006044b1a33edf1ff6d9f7e319/google_crc32c-1.8.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:2a3dc3318507de089c5384cc74d54318401410f82aa65b2d9cdde9d297aca7cb", size = 30867, upload-time = "2025-12-16T00:38:31.302Z" }, + { url = "https://files.pythonhosted.org/packages/ce/a9/a780cc66f86335a6019f557a8aaca8fbb970728f0efd2430d15ff1beae0e/google_crc32c-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:14f87e04d613dfa218d6135e81b78272c3b904e2a7053b841481b38a7d901411", size = 33364, upload-time = "2025-12-16T00:40:22.96Z" }, + { url = "https://files.pythonhosted.org/packages/21/3f/3457ea803db0198c9aaca2dd373750972ce28a26f00544b6b85088811939/google_crc32c-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb5c869c2923d56cb0c8e6bcdd73c009c36ae39b652dbe46a05eb4ef0ad01454", size = 33740, upload-time = "2025-12-16T00:40:23.96Z" }, + { url = "https://files.pythonhosted.org/packages/df/c0/87c2073e0c72515bb8733d4eef7b21548e8d189f094b5dad20b0ecaf64f6/google_crc32c-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:3cc0c8912038065eafa603b238abf252e204accab2a704c63b9e14837a854962", size = 34437, upload-time = "2025-12-16T00:35:21.395Z" }, + { url = "https://files.pythonhosted.org/packages/52/c5/c171e4d8c44fec1422d801a6d2e5d7ddabd733eeda505c79730ee9607f07/google_crc32c-1.8.0-pp311-pypy311_pp73-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:87fa445064e7db928226b2e6f0d5304ab4cd0339e664a4e9a25029f384d9bb93", size = 28615, upload-time = "2025-12-16T00:40:29.298Z" }, + { url = "https://files.pythonhosted.org/packages/9c/97/7d75fe37a7a6ed171a2cf17117177e7aab7e6e0d115858741b41e9dd4254/google_crc32c-1.8.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f639065ea2042d5c034bf258a9f085eaa7af0cd250667c0635a3118e8f92c69c", size = 28800, upload-time = "2025-12-16T00:40:30.322Z" }, ] [[package]] name = "gymnasium" -version = "1.2.0" +version = "1.2.3" 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/fd/17/c2a0e15c2cd5a8e788389b280996db927b923410de676ec5c7b2695e9261/gymnasium-1.2.0.tar.gz", hash = "sha256:344e87561012558f603880baf264ebc97f8a5c997a957b0c9f910281145534b0", size = 821142, upload-time = "2025-06-27T08:21:20.262Z" } +sdist = { url = "https://files.pythonhosted.org/packages/76/59/653a9417d98ed3e29ef9734ba52c3495f6c6823b8d5c0c75369f25111708/gymnasium-1.2.3.tar.gz", hash = "sha256:2b2cb5b5fbbbdf3afb9f38ca952cc48aa6aa3e26561400d940747fda3ad42509", size = 829230, upload-time = "2025-12-18T16:51:10.234Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/e2/a111dbb8625af467ea4760a1373d6ef27aac3137931219902406ccc05423/gymnasium-1.2.0-py3-none-any.whl", hash = "sha256:fc4a1e4121a9464c29b4d7dc6ade3fbeaa36dea448682f5f71a6d2c17489ea76", size = 944301, upload-time = "2025-06-27T08:21:18.83Z" }, + { url = "https://files.pythonhosted.org/packages/56/d3/ea5f088e3638dbab12e5c20d6559d5b3bdaeaa1f2af74e526e6815836285/gymnasium-1.2.3-py3-none-any.whl", hash = "sha256:e6314bba8f549c7fdcc8677f7cd786b64908af6e79b57ddaa5ce1825bffb5373", size = 952113, upload-time = "2025-12-18T16:51:08.445Z" }, ] [[package]] @@ -699,11 +703,11 @@ wheels = [ [[package]] name = "idna" -version = "3.10" +version = "3.11" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, ] [[package]] @@ -717,11 +721,11 @@ wheels = [ [[package]] name = "iniconfig" -version = "2.1.0" +version = "2.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, ] [[package]] @@ -881,67 +885,75 @@ wheels = [ [[package]] name = "mapbox-earcut" -version = "1.0.3" +version = "2.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8d/70/0a322197c1178f47941e5e6e13b0a4adeaaa7c465c18e3b4ead3eba49860/mapbox_earcut-1.0.3.tar.gz", hash = "sha256:b6bac5d519d9947a6321a699c15d58e0b5740da61b9210ed229e05ad207c1c04", size = 24029, upload-time = "2024-12-25T12:49:09.119Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bc/7b/bbf6b00488662be5d2eb7a188222c264b6f713bac10dc4a77bf37a4cb4b6/mapbox_earcut-2.0.0.tar.gz", hash = "sha256:81eab6b86cf99551deb698b98e3f7502c57900e5c479df15e1bdaf1a57f0f9d6", size = 39934, upload-time = "2025-11-16T18:41:27.251Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/61/d7/b37a45c248100e7285a40de87a8b1808ca4ca10228e265f2d0c320702d96/mapbox_earcut-1.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bbf24029e7447eb0351000f4fd3185327a00dac5ed756b07330b0bdaed6932db", size = 71057, upload-time = "2024-12-25T12:48:09.131Z" }, - { url = "https://files.pythonhosted.org/packages/1b/df/2b63eb0d3a24e14f67adc816de18c2e09f3eb0997c512ace84dd59c3ed96/mapbox_earcut-1.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:998e2f1e3769538f7656a34296d08a37cb71ce57aa8cf4387572bc00029b52ce", size = 65300, upload-time = "2024-12-25T12:48:11.677Z" }, - { url = "https://files.pythonhosted.org/packages/87/37/9dd9575f5c00e35d480e7150e5bb315a35d9cf5642bfb75ca628a31e1341/mapbox_earcut-1.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df2382d84d6d168f73479673d297753e37440772f233cc03ebb54d150e37b174", size = 96965, upload-time = "2024-12-25T12:48:12.968Z" }, - { url = "https://files.pythonhosted.org/packages/3b/91/5708233941b5bf73149ba35f7aa32c6ee2cf4a33cd33069e7dba69d4129f/mapbox_earcut-1.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ccddb4bb04f11beab62943eb5a1bcd52c5a71d236bfce0ecc03e45e97fdb24b", size = 1070953, upload-time = "2024-12-25T12:48:15.495Z" }, - { url = "https://files.pythonhosted.org/packages/2a/fe/b35b999ba786aa17ddc47bc04231de076665eb511e1cd58cf6fef3581172/mapbox_earcut-1.0.3-cp311-cp311-win32.whl", hash = "sha256:f19b2bcf6475bc591f48437d3214691a6730f39b1f6dfd7505b69c4345485b0c", size = 65245, upload-time = "2024-12-25T12:48:17.826Z" }, - { url = "https://files.pythonhosted.org/packages/11/81/18ac08b0bb0c22dd9028c7ecb31ae4086d31128b13fb3903e717331072ac/mapbox_earcut-1.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:811a64ad5e6ecf09b96af533e5c169299ba173e53eb4ff0209de1adcfae314be", size = 72356, upload-time = "2024-12-25T12:48:20.164Z" }, - { url = "https://files.pythonhosted.org/packages/96/7c/707a4ce96e078f7d382cc32b4a6c2326eca68d77ead5e990f5f940d16140/mapbox_earcut-1.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5be71b7ec2180a27ce1178d53933430a3292b6ac3f94f2144513ee51d9034007", size = 70333, upload-time = "2024-12-25T12:48:22.565Z" }, - { url = "https://files.pythonhosted.org/packages/fb/47/ba2a14732f6e197b0ed879a1992b4d85054294b23627ad681b4fb1251d16/mapbox_earcut-1.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:eb874f7562a49ae0fb7bd47bcc9b4854cc53e3e4f7f26674f02f3cadb006ce16", size = 64697, upload-time = "2024-12-25T12:48:25.025Z" }, - { url = "https://files.pythonhosted.org/packages/e7/68/59a514811da76c3c801207bd6d7094ea5ba75648c2e7f15d4cb98b08216f/mapbox_earcut-1.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73b9f06f2f8a795d835342aa80e021cfceda78fdca7bc07dc1a0b4aca90239f3", size = 96182, upload-time = "2024-12-25T12:48:26.316Z" }, - { url = "https://files.pythonhosted.org/packages/3f/79/97bf509ade0f9aeb5b5f94b1aff86393c2f584379a80e392fdfcbea434ae/mapbox_earcut-1.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fdc55574ef7b613004874a459d2d59c07e1ef45cebb83f86c4958f7d3e2d6069", size = 1070584, upload-time = "2024-12-25T12:48:29.065Z" }, - { url = "https://files.pythonhosted.org/packages/de/7a/5a6e205bab9ff49d1dae392f6179a444f820880d8985f26080816fa6c7ba/mapbox_earcut-1.0.3-cp312-cp312-win32.whl", hash = "sha256:790f52c67a0bd81032eaf61ebc181b1825b8b6daf01cb69e9eaa38521dd07aeb", size = 65375, upload-time = "2024-12-25T12:48:30.618Z" }, - { url = "https://files.pythonhosted.org/packages/7a/59/674a67f92772563d5a943ce2c4ed834ed341e3a0fd77b8eb4b79057f5193/mapbox_earcut-1.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:cc1bbf35be0d9853dd448374330684ddbd0112497dee7d21b7417b0ab6236ac7", size = 72575, upload-time = "2024-12-25T12:48:33.544Z" }, + { url = "https://files.pythonhosted.org/packages/07/9f/fbd15d9e348e75e986d6912c4eab99888106b7e5fb0a01e765422f7cd464/mapbox_earcut-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:9b5040e79e3783295e99c90277f31c1cbaddd3335297275331995ba5680e3649", size = 55773, upload-time = "2025-11-16T18:40:20.045Z" }, + { url = "https://files.pythonhosted.org/packages/72/40/be761298704fbbaa81c5618bb306f1510fb068e482f6a1c8b3b6c1b31479/mapbox_earcut-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1cf43baafec3ef1e967319d9b5da96bc6ddf3dbb204b6f3535275eda4b519a72", size = 52444, upload-time = "2025-11-16T18:40:21.501Z" }, + { url = "https://files.pythonhosted.org/packages/5a/0b/0c0c08db9663238ffb82c48259582dc0047a3255d98c0ac83c48026b7544/mapbox_earcut-2.0.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a283531847f603dd9d69afb75b21bd009d385ca9485fcd3e5a7fa5db1ccd913", size = 56803, upload-time = "2025-11-16T18:40:22.891Z" }, + { url = "https://files.pythonhosted.org/packages/f0/4a/86796859383d7d11fa5d4bcf1983f94c6cbb9eeb60fb3bab527fec4b32fa/mapbox_earcut-2.0.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ab697676f4cec4572d4e941b7a3429a6687bf2ac6e8db3f3781024e3239ae3a0", size = 59403, upload-time = "2025-11-16T18:40:24.021Z" }, + { url = "https://files.pythonhosted.org/packages/6c/db/adaf981ab3bcfcf993ef317636b1f27210d6834bb1e8d63db6ad7c08214a/mapbox_earcut-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f1bdac76e048f4299accf4eaf797079ddfc330442e7231c15535ed198100d6c5", size = 152876, upload-time = "2025-11-16T18:40:25.588Z" }, + { url = "https://files.pythonhosted.org/packages/d2/83/86417974039e7554c9e1e55c852a7e9c2a1390d64675eb85d70e5fa7eb37/mapbox_earcut-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4a6945b23f859bef11ce3194303d17bd371c86b637e7029f81b1feaff3db3758", size = 157548, upload-time = "2025-11-16T18:40:27.202Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4c/c82a292bb21e5c651d81334123db2d654c5c9d19b2197080d3429dc1e49a/mapbox_earcut-2.0.0-cp311-cp311-win32.whl", hash = "sha256:8e119524c29406afb5eaa15e933f297d35679293a3ca62ced22f97a14c484cb5", size = 51424, upload-time = "2025-11-16T18:40:28.415Z" }, + { url = "https://files.pythonhosted.org/packages/30/57/6c39d7db81f72a3e4814ef152c8fb8dfe275dc4b03c9bfa073d251e3755f/mapbox_earcut-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:378bbbb3304e446023752db8f44ecd6e7ef965bcbda36541d2ae64442ba94254", size = 56662, upload-time = "2025-11-16T18:40:29.863Z" }, + { url = "https://files.pythonhosted.org/packages/f4/d6/a1ef6e196b3d6968bf6546d4f7e54c559f9cff8991fdb880df0ba1618f52/mapbox_earcut-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:6d249a431abd6bbff36f1fd0493247a86de962244cc4081b4d5050b02ed48fb1", size = 50505, upload-time = "2025-11-16T18:40:30.992Z" }, + { url = "https://files.pythonhosted.org/packages/8d/93/846804029d955c3c841d8efff77c2b0e8d9aab057d3a077dc8e3f88b5ea4/mapbox_earcut-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:db55ce18e698bc9d90914ee7d4f8c3e4d23827456ece7c5d7a1ec91e90c7122b", size = 55623, upload-time = "2025-11-16T18:40:32.113Z" }, + { url = "https://files.pythonhosted.org/packages/d3/f6/cc9ece104bc3876b350dba6fef7f34fb7b20ecc028d2cdbdbecb436b1ed1/mapbox_earcut-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:01dd6099d16123baf582a11b2bd1d59ce848498cf0cdca3812fd1f8b20ff33b7", size = 52028, upload-time = "2025-11-16T18:40:33.516Z" }, + { url = "https://files.pythonhosted.org/packages/88/6e/230da4aabcc56c99e9bddb4c43ce7d4ba3609c0caf2d316fb26535d7c60c/mapbox_earcut-2.0.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2d5a098aae26a52282bc981a38e7bf6b889d2ea7442f2cd1903d2ba842f4ff07", size = 56351, upload-time = "2025-11-16T18:40:35.217Z" }, + { url = "https://files.pythonhosted.org/packages/1a/f7/5cdd3752526e91d91336c7263af7767b291d21e63c89d7190a60051f0f87/mapbox_earcut-2.0.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de35f241d0b9110ad9260f295acedd9d7cc0d7acfe30d36b1b3ee8419c2caba1", size = 59209, upload-time = "2025-11-16T18:40:36.634Z" }, + { url = "https://files.pythonhosted.org/packages/7b/a2/b7781416cb93b37b95d0444e03f87184de8815e57ff202ce4105fa921325/mapbox_earcut-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6cb63ab85e2e430c350f93e75c13f8b91cb8c8a045f3cd714c390b69a720368a", size = 152316, upload-time = "2025-11-16T18:40:38.147Z" }, + { url = "https://files.pythonhosted.org/packages/c1/74/396338e3d345e4e36fb23a0380921098b6a95ce7fb19c4777f4185a5974e/mapbox_earcut-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fb3c9f069fc3795306db87f8139f70c4f047532f897a3de05f54dc1faebc97f6", size = 157268, upload-time = "2025-11-16T18:40:39.753Z" }, + { url = "https://files.pythonhosted.org/packages/56/2c/66fd137ea86c508f6cd7247f7f6e2d1dabffc9f0e9ccf14c71406b197af1/mapbox_earcut-2.0.0-cp312-cp312-win32.whl", hash = "sha256:eb290e6676217707ed238dd55e07b0a8ca3ab928f6a27c4afefb2ff3af08d7cb", size = 51226, upload-time = "2025-11-16T18:40:41.018Z" }, + { url = "https://files.pythonhosted.org/packages/b8/84/7b78e37b0c2109243c0dad7d9ba9774b02fcee228bf61cf727a5aa1702e2/mapbox_earcut-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:5ef5b3319a43375272ad2cad9333ed16e569b5102e32a4241451358897e6f6ee", size = 56417, upload-time = "2025-11-16T18:40:42.173Z" }, + { url = "https://files.pythonhosted.org/packages/75/7f/cd7195aa27c1c8f2b9d38025a5a8663f32cd01c07b648a54b1308ab26c15/mapbox_earcut-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:a4a3706feb5cc8c782d8f68bb0110c8d551304043f680a87a54b0651a2c208c3", size = 50111, upload-time = "2025-11-16T18:40:43.334Z" }, ] [[package]] name = "markdown" -version = "3.9" +version = "3.10" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8d/37/02347f6d6d8279247a5837082ebc26fc0d5aaeaf75aa013fcbb433c777ab/markdown-3.9.tar.gz", hash = "sha256:d2900fe1782bd33bdbbd56859defef70c2e78fc46668f8eb9df3128138f2cb6a", size = 364585, upload-time = "2025-09-04T20:25:22.885Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/ab/7dd27d9d863b3376fcf23a5a13cb5d024aed1db46f963f1b5735ae43b3be/markdown-3.10.tar.gz", hash = "sha256:37062d4f2aa4b2b6b32aefb80faa300f82cc790cb949a35b8caede34f2b68c0e", size = 364931, upload-time = "2025-11-03T19:51:15.007Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/70/ae/44c4a6a4cbb496d93c6257954260fe3a6e91b7bed2240e5dad2a717f5111/markdown-3.9-py3-none-any.whl", hash = "sha256:9f4d91ed810864ea88a6f32c07ba8bee1346c0cc1f6b1f9f6c822f2a9667d280", size = 107441, upload-time = "2025-09-04T20:25:21.784Z" }, + { url = "https://files.pythonhosted.org/packages/70/81/54e3ce63502cd085a0c556652a4e1b919c45a446bd1e5300e10c44c8c521/markdown-3.10-py3-none-any.whl", hash = "sha256:b5b99d6951e2e4948d939255596523444c0e677c669700b1d17aa4a8a464cb7c", size = 107678, upload-time = "2025-11-03T19:51:13.887Z" }, ] [[package]] name = "markupsafe" -version = "3.0.2" +version = "3.0.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353, upload-time = "2024-10-18T15:21:02.187Z" }, - { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392, upload-time = "2024-10-18T15:21:02.941Z" }, - { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984, upload-time = "2024-10-18T15:21:03.953Z" }, - { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120, upload-time = "2024-10-18T15:21:06.495Z" }, - { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032, upload-time = "2024-10-18T15:21:07.295Z" }, - { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057, upload-time = "2024-10-18T15:21:08.073Z" }, - { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359, upload-time = "2024-10-18T15:21:09.318Z" }, - { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306, upload-time = "2024-10-18T15:21:10.185Z" }, - { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094, upload-time = "2024-10-18T15:21:11.005Z" }, - { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521, upload-time = "2024-10-18T15:21:12.911Z" }, - { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274, upload-time = "2024-10-18T15:21:13.777Z" }, - { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348, upload-time = "2024-10-18T15:21:14.822Z" }, - { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149, upload-time = "2024-10-18T15:21:15.642Z" }, - { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118, upload-time = "2024-10-18T15:21:17.133Z" }, - { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993, upload-time = "2024-10-18T15:21:18.064Z" }, - { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178, upload-time = "2024-10-18T15:21:18.859Z" }, - { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319, upload-time = "2024-10-18T15:21:19.671Z" }, - { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352, upload-time = "2024-10-18T15:21:20.971Z" }, - { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097, upload-time = "2024-10-18T15:21:22.646Z" }, - { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601, upload-time = "2024-10-18T15:21:23.499Z" }, + { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, + { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, + { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" }, + { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" }, + { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" }, + { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" }, + { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" }, + { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" }, + { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" }, + { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" }, + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, ] [[package]] name = "matplotlib" -version = "3.10.6" +version = "3.10.8" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "contourpy" }, @@ -954,25 +966,25 @@ dependencies = [ { name = "pyparsing" }, { name = "python-dateutil" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a0/59/c3e6453a9676ffba145309a73c462bb407f4400de7de3f2b41af70720a3c/matplotlib-3.10.6.tar.gz", hash = "sha256:ec01b645840dd1996df21ee37f208cd8ba57644779fa20464010638013d3203c", size = 34804264, upload-time = "2025-08-30T00:14:25.137Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/76/d3c6e3a13fe484ebe7718d14e269c9569c4eb0020a968a327acb3b9a8fe6/matplotlib-3.10.8.tar.gz", hash = "sha256:2299372c19d56bcd35cf05a2738308758d32b9eaed2371898d8f5bd33f084aa3", size = 34806269, upload-time = "2025-12-10T22:56:51.155Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/80/d6/5d3665aa44c49005aaacaa68ddea6fcb27345961cd538a98bb0177934ede/matplotlib-3.10.6-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:905b60d1cb0ee604ce65b297b61cf8be9f4e6cfecf95a3fe1c388b5266bc8f4f", size = 8257527, upload-time = "2025-08-30T00:12:45.31Z" }, - { url = "https://files.pythonhosted.org/packages/8c/af/30ddefe19ca67eebd70047dabf50f899eaff6f3c5e6a1a7edaecaf63f794/matplotlib-3.10.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7bac38d816637343e53d7185d0c66677ff30ffb131044a81898b5792c956ba76", size = 8119583, upload-time = "2025-08-30T00:12:47.236Z" }, - { url = "https://files.pythonhosted.org/packages/d3/29/4a8650a3dcae97fa4f375d46efcb25920d67b512186f8a6788b896062a81/matplotlib-3.10.6-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:942a8de2b5bfff1de31d95722f702e2966b8a7e31f4e68f7cd963c7cd8861cf6", size = 8692682, upload-time = "2025-08-30T00:12:48.781Z" }, - { url = "https://files.pythonhosted.org/packages/aa/d3/b793b9cb061cfd5d42ff0f69d1822f8d5dbc94e004618e48a97a8373179a/matplotlib-3.10.6-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a3276c85370bc0dfca051ec65c5817d1e0f8f5ce1b7787528ec8ed2d524bbc2f", size = 9521065, upload-time = "2025-08-30T00:12:50.602Z" }, - { url = "https://files.pythonhosted.org/packages/f7/c5/53de5629f223c1c66668d46ac2621961970d21916a4bc3862b174eb2a88f/matplotlib-3.10.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9df5851b219225731f564e4b9e7f2ac1e13c9e6481f941b5631a0f8e2d9387ce", size = 9576888, upload-time = "2025-08-30T00:12:52.92Z" }, - { url = "https://files.pythonhosted.org/packages/fc/8e/0a18d6d7d2d0a2e66585032a760d13662e5250c784d53ad50434e9560991/matplotlib-3.10.6-cp311-cp311-win_amd64.whl", hash = "sha256:abb5d9478625dd9c9eb51a06d39aae71eda749ae9b3138afb23eb38824026c7e", size = 8115158, upload-time = "2025-08-30T00:12:54.863Z" }, - { url = "https://files.pythonhosted.org/packages/07/b3/1a5107bb66c261e23b9338070702597a2d374e5aa7004b7adfc754fbed02/matplotlib-3.10.6-cp311-cp311-win_arm64.whl", hash = "sha256:886f989ccfae63659183173bb3fced7fd65e9eb793c3cc21c273add368536951", size = 7992444, upload-time = "2025-08-30T00:12:57.067Z" }, - { url = "https://files.pythonhosted.org/packages/ea/1a/7042f7430055d567cc3257ac409fcf608599ab27459457f13772c2d9778b/matplotlib-3.10.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:31ca662df6a80bd426f871105fdd69db7543e28e73a9f2afe80de7e531eb2347", size = 8272404, upload-time = "2025-08-30T00:12:59.112Z" }, - { url = "https://files.pythonhosted.org/packages/a9/5d/1d5f33f5b43f4f9e69e6a5fe1fb9090936ae7bc8e2ff6158e7a76542633b/matplotlib-3.10.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1678bb61d897bb4ac4757b5ecfb02bfb3fddf7f808000fb81e09c510712fda75", size = 8128262, upload-time = "2025-08-30T00:13:01.141Z" }, - { url = "https://files.pythonhosted.org/packages/67/c3/135fdbbbf84e0979712df58e5e22b4f257b3f5e52a3c4aacf1b8abec0d09/matplotlib-3.10.6-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:56cd2d20842f58c03d2d6e6c1f1cf5548ad6f66b91e1e48f814e4fb5abd1cb95", size = 8697008, upload-time = "2025-08-30T00:13:03.24Z" }, - { url = "https://files.pythonhosted.org/packages/9c/be/c443ea428fb2488a3ea7608714b1bd85a82738c45da21b447dc49e2f8e5d/matplotlib-3.10.6-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:662df55604a2f9a45435566d6e2660e41efe83cd94f4288dfbf1e6d1eae4b0bb", size = 9530166, upload-time = "2025-08-30T00:13:05.951Z" }, - { url = "https://files.pythonhosted.org/packages/a9/35/48441422b044d74034aea2a3e0d1a49023f12150ebc58f16600132b9bbaf/matplotlib-3.10.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:08f141d55148cd1fc870c3387d70ca4df16dee10e909b3b038782bd4bda6ea07", size = 9593105, upload-time = "2025-08-30T00:13:08.356Z" }, - { url = "https://files.pythonhosted.org/packages/45/c3/994ef20eb4154ab84cc08d033834555319e4af970165e6c8894050af0b3c/matplotlib-3.10.6-cp312-cp312-win_amd64.whl", hash = "sha256:590f5925c2d650b5c9d813c5b3b5fc53f2929c3f8ef463e4ecfa7e052044fb2b", size = 8122784, upload-time = "2025-08-30T00:13:10.367Z" }, - { url = "https://files.pythonhosted.org/packages/57/b8/5c85d9ae0e40f04e71bedb053aada5d6bab1f9b5399a0937afb5d6b02d98/matplotlib-3.10.6-cp312-cp312-win_arm64.whl", hash = "sha256:f44c8d264a71609c79a78d50349e724f5d5fc3684ead7c2a473665ee63d868aa", size = 7992823, upload-time = "2025-08-30T00:13:12.24Z" }, - { url = "https://files.pythonhosted.org/packages/12/bb/02c35a51484aae5f49bd29f091286e7af5f3f677a9736c58a92b3c78baeb/matplotlib-3.10.6-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:f2d684c3204fa62421bbf770ddfebc6b50130f9cad65531eeba19236d73bb488", size = 8252296, upload-time = "2025-08-30T00:14:19.49Z" }, - { url = "https://files.pythonhosted.org/packages/7d/85/41701e3092005aee9a2445f5ee3904d9dbd4a7df7a45905ffef29b7ef098/matplotlib-3.10.6-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:6f4a69196e663a41d12a728fab8751177215357906436804217d6d9cf0d4d6cf", size = 8116749, upload-time = "2025-08-30T00:14:21.344Z" }, - { url = "https://files.pythonhosted.org/packages/16/53/8d8fa0ea32a8c8239e04d022f6c059ee5e1b77517769feccd50f1df43d6d/matplotlib-3.10.6-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d6ca6ef03dfd269f4ead566ec6f3fb9becf8dab146fb999022ed85ee9f6b3eb", size = 8693933, upload-time = "2025-08-30T00:14:22.942Z" }, + { url = "https://files.pythonhosted.org/packages/f8/86/de7e3a1cdcfc941483af70609edc06b83e7c8a0e0dc9ac325200a3f4d220/matplotlib-3.10.8-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6be43b667360fef5c754dda5d25a32e6307a03c204f3c0fc5468b78fa87b4160", size = 8251215, upload-time = "2025-12-10T22:55:16.175Z" }, + { url = "https://files.pythonhosted.org/packages/fd/14/baad3222f424b19ce6ad243c71de1ad9ec6b2e4eb1e458a48fdc6d120401/matplotlib-3.10.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2b336e2d91a3d7006864e0990c83b216fcdca64b5a6484912902cef87313d78", size = 8139625, upload-time = "2025-12-10T22:55:17.712Z" }, + { url = "https://files.pythonhosted.org/packages/8f/a0/7024215e95d456de5883e6732e708d8187d9753a21d32f8ddb3befc0c445/matplotlib-3.10.8-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:efb30e3baaea72ce5928e32bab719ab4770099079d66726a62b11b1ef7273be4", size = 8712614, upload-time = "2025-12-10T22:55:20.8Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f4/b8347351da9a5b3f41e26cf547252d861f685c6867d179a7c9d60ad50189/matplotlib-3.10.8-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d56a1efd5bfd61486c8bc968fa18734464556f0fb8e51690f4ac25d85cbbbbc2", size = 9540997, upload-time = "2025-12-10T22:55:23.258Z" }, + { url = "https://files.pythonhosted.org/packages/9e/c0/c7b914e297efe0bc36917bf216b2acb91044b91e930e878ae12981e461e5/matplotlib-3.10.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:238b7ce5717600615c895050239ec955d91f321c209dd110db988500558e70d6", size = 9596825, upload-time = "2025-12-10T22:55:25.217Z" }, + { url = "https://files.pythonhosted.org/packages/6f/d3/a4bbc01c237ab710a1f22b4da72f4ff6d77eb4c7735ea9811a94ae239067/matplotlib-3.10.8-cp311-cp311-win_amd64.whl", hash = "sha256:18821ace09c763ec93aef5eeff087ee493a24051936d7b9ebcad9662f66501f9", size = 8135090, upload-time = "2025-12-10T22:55:27.162Z" }, + { url = "https://files.pythonhosted.org/packages/89/dd/a0b6588f102beab33ca6f5218b31725216577b2a24172f327eaf6417d5c9/matplotlib-3.10.8-cp311-cp311-win_arm64.whl", hash = "sha256:bab485bcf8b1c7d2060b4fcb6fc368a9e6f4cd754c9c2fea281f4be21df394a2", size = 8012377, upload-time = "2025-12-10T22:55:29.185Z" }, + { url = "https://files.pythonhosted.org/packages/9e/67/f997cdcbb514012eb0d10cd2b4b332667997fb5ebe26b8d41d04962fa0e6/matplotlib-3.10.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:64fcc24778ca0404ce0cb7b6b77ae1f4c7231cdd60e6778f999ee05cbd581b9a", size = 8260453, upload-time = "2025-12-10T22:55:30.709Z" }, + { url = "https://files.pythonhosted.org/packages/7e/65/07d5f5c7f7c994f12c768708bd2e17a4f01a2b0f44a1c9eccad872433e2e/matplotlib-3.10.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b9a5ca4ac220a0cdd1ba6bcba3608547117d30468fefce49bb26f55c1a3d5c58", size = 8148321, upload-time = "2025-12-10T22:55:33.265Z" }, + { url = "https://files.pythonhosted.org/packages/3e/f3/c5195b1ae57ef85339fd7285dfb603b22c8b4e79114bae5f4f0fcf688677/matplotlib-3.10.8-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3ab4aabc72de4ff77b3ec33a6d78a68227bf1123465887f9905ba79184a1cc04", size = 8716944, upload-time = "2025-12-10T22:55:34.922Z" }, + { url = "https://files.pythonhosted.org/packages/00/f9/7638f5cc82ec8a7aa005de48622eecc3ed7c9854b96ba15bd76b7fd27574/matplotlib-3.10.8-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24d50994d8c5816ddc35411e50a86ab05f575e2530c02752e02538122613371f", size = 9550099, upload-time = "2025-12-10T22:55:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/57/61/78cd5920d35b29fd2a0fe894de8adf672ff52939d2e9b43cb83cd5ce1bc7/matplotlib-3.10.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:99eefd13c0dc3b3c1b4d561c1169e65fe47aab7b8158754d7c084088e2329466", size = 9613040, upload-time = "2025-12-10T22:55:38.715Z" }, + { url = "https://files.pythonhosted.org/packages/30/4e/c10f171b6e2f44d9e3a2b96efa38b1677439d79c99357600a62cc1e9594e/matplotlib-3.10.8-cp312-cp312-win_amd64.whl", hash = "sha256:dd80ecb295460a5d9d260df63c43f4afbdd832d725a531f008dad1664f458adf", size = 8142717, upload-time = "2025-12-10T22:55:41.103Z" }, + { url = "https://files.pythonhosted.org/packages/f1/76/934db220026b5fef85f45d51a738b91dea7d70207581063cd9bd8fafcf74/matplotlib-3.10.8-cp312-cp312-win_arm64.whl", hash = "sha256:3c624e43ed56313651bc18a47f838b60d7b8032ed348911c54906b130b20071b", size = 8012751, upload-time = "2025-12-10T22:55:42.684Z" }, + { url = "https://files.pythonhosted.org/packages/04/30/3afaa31c757f34b7725ab9d2ba8b48b5e89c2019c003e7d0ead143aabc5a/matplotlib-3.10.8-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6da7c2ce169267d0d066adcf63758f0604aa6c3eebf67458930f9d9b79ad1db1", size = 8249198, upload-time = "2025-12-10T22:56:45.584Z" }, + { url = "https://files.pythonhosted.org/packages/48/2f/6334aec331f57485a642a7c8be03cb286f29111ae71c46c38b363230063c/matplotlib-3.10.8-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9153c3292705be9f9c64498a8872118540c3f4123d1a1c840172edf262c8be4a", size = 8136817, upload-time = "2025-12-10T22:56:47.339Z" }, + { url = "https://files.pythonhosted.org/packages/73/e4/6d6f14b2a759c622f191b2d67e9075a3f56aaccb3be4bb9bb6890030d0a0/matplotlib-3.10.8-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ae029229a57cd1e8fe542485f27e7ca7b23aa9e8944ddb4985d0bc444f1eca2", size = 8713867, upload-time = "2025-12-10T22:56:48.954Z" }, ] [[package]] @@ -989,22 +1001,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:d0afaf3b005e35e14b929d5491d2d5b64562d0c1cd5093ba969fb63908670dd4" }, @@ -1078,23 +1090,23 @@ wheels = [ [[package]] name = "ml-dtypes" -version = "0.5.3" +version = "0.5.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/78/a7/aad060393123cfb383956dca68402aff3db1e1caffd5764887ed5153f41b/ml_dtypes-0.5.3.tar.gz", hash = "sha256:95ce33057ba4d05df50b1f3cfefab22e351868a843b3b15a46c65836283670c9", size = 692316, upload-time = "2025-07-29T18:39:19.454Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0e/4a/c27b42ed9b1c7d13d9ba8b6905dece787d6259152f2309338aed29b2447b/ml_dtypes-0.5.4.tar.gz", hash = "sha256:8ab06a50fb9bf9666dd0fe5dfb4676fa2b0ac0f31ecff72a6c3af8e22c063453", size = 692314, upload-time = "2025-11-17T22:32:31.031Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/af/f1/720cb1409b5d0c05cff9040c0e9fba73fa4c67897d33babf905d5d46a070/ml_dtypes-0.5.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4a177b882667c69422402df6ed5c3428ce07ac2c1f844d8a1314944651439458", size = 667412, upload-time = "2025-07-29T18:38:25.275Z" }, - { url = "https://files.pythonhosted.org/packages/6a/d5/05861ede5d299f6599f86e6bc1291714e2116d96df003cfe23cc54bcc568/ml_dtypes-0.5.3-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9849ce7267444c0a717c80c6900997de4f36e2815ce34ac560a3edb2d9a64cd2", size = 4964606, upload-time = "2025-07-29T18:38:27.045Z" }, - { url = "https://files.pythonhosted.org/packages/db/dc/72992b68de367741bfab8df3b3fe7c29f982b7279d341aa5bf3e7ef737ea/ml_dtypes-0.5.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c3f5ae0309d9f888fd825c2e9d0241102fadaca81d888f26f845bc8c13c1e4ee", size = 4938435, upload-time = "2025-07-29T18:38:29.193Z" }, - { url = "https://files.pythonhosted.org/packages/81/1c/d27a930bca31fb07d975a2d7eaf3404f9388114463b9f15032813c98f893/ml_dtypes-0.5.3-cp311-cp311-win_amd64.whl", hash = "sha256:58e39349d820b5702bb6f94ea0cb2dc8ec62ee81c0267d9622067d8333596a46", size = 206334, upload-time = "2025-07-29T18:38:30.687Z" }, - { url = "https://files.pythonhosted.org/packages/1a/d8/6922499effa616012cb8dc445280f66d100a7ff39b35c864cfca019b3f89/ml_dtypes-0.5.3-cp311-cp311-win_arm64.whl", hash = "sha256:66c2756ae6cfd7f5224e355c893cfd617fa2f747b8bbd8996152cbdebad9a184", size = 157584, upload-time = "2025-07-29T18:38:32.187Z" }, - { url = "https://files.pythonhosted.org/packages/0d/eb/bc07c88a6ab002b4635e44585d80fa0b350603f11a2097c9d1bfacc03357/ml_dtypes-0.5.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:156418abeeda48ea4797db6776db3c5bdab9ac7be197c1233771e0880c304057", size = 663864, upload-time = "2025-07-29T18:38:33.777Z" }, - { url = "https://files.pythonhosted.org/packages/cf/89/11af9b0f21b99e6386b6581ab40fb38d03225f9de5f55cf52097047e2826/ml_dtypes-0.5.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1db60c154989af253f6c4a34e8a540c2c9dce4d770784d426945e09908fbb177", size = 4951313, upload-time = "2025-07-29T18:38:36.45Z" }, - { url = "https://files.pythonhosted.org/packages/d8/a9/b98b86426c24900b0c754aad006dce2863df7ce0bb2bcc2c02f9cc7e8489/ml_dtypes-0.5.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1b255acada256d1fa8c35ed07b5f6d18bc21d1556f842fbc2d5718aea2cd9e55", size = 4928805, upload-time = "2025-07-29T18:38:38.29Z" }, - { url = "https://files.pythonhosted.org/packages/50/c1/85e6be4fc09c6175f36fb05a45917837f30af9a5146a5151cb3a3f0f9e09/ml_dtypes-0.5.3-cp312-cp312-win_amd64.whl", hash = "sha256:da65e5fd3eea434ccb8984c3624bc234ddcc0d9f4c81864af611aaebcc08a50e", size = 208182, upload-time = "2025-07-29T18:38:39.72Z" }, - { url = "https://files.pythonhosted.org/packages/9e/17/cf5326d6867be057f232d0610de1458f70a8ce7b6290e4b4a277ea62b4cd/ml_dtypes-0.5.3-cp312-cp312-win_arm64.whl", hash = "sha256:8bb9cd1ce63096567f5f42851f5843b5a0ea11511e50039a7649619abfb4ba6d", size = 161560, upload-time = "2025-07-29T18:38:41.072Z" }, + { url = "https://files.pythonhosted.org/packages/c6/5e/712092cfe7e5eb667b8ad9ca7c54442f21ed7ca8979745f1000e24cf8737/ml_dtypes-0.5.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6c7ecb74c4bd71db68a6bea1edf8da8c34f3d9fe218f038814fd1d310ac76c90", size = 679734, upload-time = "2025-11-17T22:31:39.223Z" }, + { url = "https://files.pythonhosted.org/packages/4f/cf/912146dfd4b5c0eea956836c01dcd2fce6c9c844b2691f5152aca196ce4f/ml_dtypes-0.5.4-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bc11d7e8c44a65115d05e2ab9989d1e045125d7be8e05a071a48bc76eb6d6040", size = 5056165, upload-time = "2025-11-17T22:31:41.071Z" }, + { url = "https://files.pythonhosted.org/packages/a9/80/19189ea605017473660e43762dc853d2797984b3c7bf30ce656099add30c/ml_dtypes-0.5.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:19b9a53598f21e453ea2fbda8aa783c20faff8e1eeb0d7ab899309a0053f1483", size = 5034975, upload-time = "2025-11-17T22:31:42.758Z" }, + { url = "https://files.pythonhosted.org/packages/b4/24/70bd59276883fdd91600ca20040b41efd4902a923283c4d6edcb1de128d2/ml_dtypes-0.5.4-cp311-cp311-win_amd64.whl", hash = "sha256:7c23c54a00ae43edf48d44066a7ec31e05fdc2eee0be2b8b50dd1903a1db94bb", size = 210742, upload-time = "2025-11-17T22:31:44.068Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c9/64230ef14e40aa3f1cb254ef623bf812735e6bec7772848d19131111ac0d/ml_dtypes-0.5.4-cp311-cp311-win_arm64.whl", hash = "sha256:557a31a390b7e9439056644cb80ed0735a6e3e3bb09d67fd5687e4b04238d1de", size = 160709, upload-time = "2025-11-17T22:31:46.557Z" }, + { url = "https://files.pythonhosted.org/packages/a8/b8/3c70881695e056f8a32f8b941126cf78775d9a4d7feba8abcb52cb7b04f2/ml_dtypes-0.5.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a174837a64f5b16cab6f368171a1a03a27936b31699d167684073ff1c4237dac", size = 676927, upload-time = "2025-11-17T22:31:48.182Z" }, + { url = "https://files.pythonhosted.org/packages/54/0f/428ef6881782e5ebb7eca459689448c0394fa0a80bea3aa9262cba5445ea/ml_dtypes-0.5.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a7f7c643e8b1320fd958bf098aa7ecf70623a42ec5154e3be3be673f4c34d900", size = 5028464, upload-time = "2025-11-17T22:31:50.135Z" }, + { url = "https://files.pythonhosted.org/packages/3a/cb/28ce52eb94390dda42599c98ea0204d74799e4d8047a0eb559b6fd648056/ml_dtypes-0.5.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ad459e99793fa6e13bd5b7e6792c8f9190b4e5a1b45c63aba14a4d0a7f1d5ff", size = 5009002, upload-time = "2025-11-17T22:31:52.001Z" }, + { url = "https://files.pythonhosted.org/packages/f5/f0/0cfadd537c5470378b1b32bd859cf2824972174b51b873c9d95cfd7475a5/ml_dtypes-0.5.4-cp312-cp312-win_amd64.whl", hash = "sha256:c1a953995cccb9e25a4ae19e34316671e4e2edaebe4cf538229b1fc7109087b7", size = 212222, upload-time = "2025-11-17T22:31:53.742Z" }, + { url = "https://files.pythonhosted.org/packages/16/2e/9acc86985bfad8f2c2d30291b27cd2bb4c74cea08695bd540906ed744249/ml_dtypes-0.5.4-cp312-cp312-win_arm64.whl", hash = "sha256:9bad06436568442575beb2d03389aa7456c690a5b05892c471215bfd8cf39460", size = 160793, upload-time = "2025-11-17T22:31:55.358Z" }, ] [[package]] @@ -1119,16 +1131,16 @@ wheels = [ [[package]] name = "msal" -version = "1.33.0" +version = "1.34.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cryptography" }, { name = "pyjwt", extra = ["crypto"] }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d5/da/81acbe0c1fd7e9e4ec35f55dadeba9833a847b9a6ba2e2d1e4432da901dd/msal-1.33.0.tar.gz", hash = "sha256:836ad80faa3e25a7d71015c990ce61f704a87328b1e73bcbb0623a18cbf17510", size = 153801, upload-time = "2025-07-22T19:36:33.693Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cf/0e/c857c46d653e104019a84f22d4494f2119b4fe9f896c92b4b864b3b045cc/msal-1.34.0.tar.gz", hash = "sha256:76ba83b716ea5a6d75b0279c0ac353a0e05b820ca1f6682c0eb7f45190c43c2f", size = 153961, upload-time = "2025-09-22T23:05:48.989Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/86/5b/fbc73e91f7727ae1e79b21ed833308e99dc11cc1cd3d4717f579775de5e9/msal-1.33.0-py3-none-any.whl", hash = "sha256:c0cd41cecf8eaed733ee7e3be9e040291eba53b0f262d3ae9c58f38b04244273", size = 116853, upload-time = "2025-07-22T19:36:32.403Z" }, + { url = "https://files.pythonhosted.org/packages/c2/dc/18d48843499e278538890dc709e9ee3dea8375f8be8e82682851df1b48b5/msal-1.34.0-py3-none-any.whl", hash = "sha256:f669b1644e4950115da7a176441b0e13ec2975c29528d8b9e81316023676d6e1", size = 116987, upload-time = "2025-09-22T23:05:47.294Z" }, ] [[package]] @@ -1145,47 +1157,47 @@ wheels = [ [[package]] name = "multidict" -version = "6.6.4" +version = "6.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/69/7f/0652e6ed47ab288e3756ea9c0df8b14950781184d4bd7883f4d87dd41245/multidict-6.6.4.tar.gz", hash = "sha256:d2d4e4787672911b48350df02ed3fa3fffdc2f2e8ca06dd6afdf34189b76a9dd", size = 101843, upload-time = "2025-08-11T12:08:48.217Z" } +sdist = { url = "https://files.pythonhosted.org/packages/80/1e/5492c365f222f907de1039b91f922b93fa4f764c713ee858d235495d8f50/multidict-6.7.0.tar.gz", hash = "sha256:c6e99d9a65ca282e578dfea819cfa9c0a62b2499d8677392e09feaf305e9e6f5", size = 101834, upload-time = "2025-10-06T14:52:30.657Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/7f/90a7f01e2d005d6653c689039977f6856718c75c5579445effb7e60923d1/multidict-6.6.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c7a0e9b561e6460484318a7612e725df1145d46b0ef57c6b9866441bf6e27e0c", size = 76472, upload-time = "2025-08-11T12:06:29.006Z" }, - { url = "https://files.pythonhosted.org/packages/54/a3/bed07bc9e2bb302ce752f1dabc69e884cd6a676da44fb0e501b246031fdd/multidict-6.6.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6bf2f10f70acc7a2446965ffbc726e5fc0b272c97a90b485857e5c70022213eb", size = 44634, upload-time = "2025-08-11T12:06:30.374Z" }, - { url = "https://files.pythonhosted.org/packages/a7/4b/ceeb4f8f33cf81277da464307afeaf164fb0297947642585884f5cad4f28/multidict-6.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:66247d72ed62d5dd29752ffc1d3b88f135c6a8de8b5f63b7c14e973ef5bda19e", size = 44282, upload-time = "2025-08-11T12:06:31.958Z" }, - { url = "https://files.pythonhosted.org/packages/03/35/436a5da8702b06866189b69f655ffdb8f70796252a8772a77815f1812679/multidict-6.6.4-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:105245cc6b76f51e408451a844a54e6823bbd5a490ebfe5bdfc79798511ceded", size = 229696, upload-time = "2025-08-11T12:06:33.087Z" }, - { url = "https://files.pythonhosted.org/packages/b6/0e/915160be8fecf1fca35f790c08fb74ca684d752fcba62c11daaf3d92c216/multidict-6.6.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cbbc54e58b34c3bae389ef00046be0961f30fef7cb0dd9c7756aee376a4f7683", size = 246665, upload-time = "2025-08-11T12:06:34.448Z" }, - { url = "https://files.pythonhosted.org/packages/08/ee/2f464330acd83f77dcc346f0b1a0eaae10230291450887f96b204b8ac4d3/multidict-6.6.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:56c6b3652f945c9bc3ac6c8178cd93132b8d82dd581fcbc3a00676c51302bc1a", size = 225485, upload-time = "2025-08-11T12:06:35.672Z" }, - { url = "https://files.pythonhosted.org/packages/71/cc/9a117f828b4d7fbaec6adeed2204f211e9caf0a012692a1ee32169f846ae/multidict-6.6.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b95494daf857602eccf4c18ca33337dd2be705bccdb6dddbfc9d513e6addb9d9", size = 257318, upload-time = "2025-08-11T12:06:36.98Z" }, - { url = "https://files.pythonhosted.org/packages/25/77/62752d3dbd70e27fdd68e86626c1ae6bccfebe2bb1f84ae226363e112f5a/multidict-6.6.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e5b1413361cef15340ab9dc61523e653d25723e82d488ef7d60a12878227ed50", size = 254689, upload-time = "2025-08-11T12:06:38.233Z" }, - { url = "https://files.pythonhosted.org/packages/00/6e/fac58b1072a6fc59af5e7acb245e8754d3e1f97f4f808a6559951f72a0d4/multidict-6.6.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e167bf899c3d724f9662ef00b4f7fef87a19c22b2fead198a6f68b263618df52", size = 246709, upload-time = "2025-08-11T12:06:39.517Z" }, - { url = "https://files.pythonhosted.org/packages/01/ef/4698d6842ef5e797c6db7744b0081e36fb5de3d00002cc4c58071097fac3/multidict-6.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:aaea28ba20a9026dfa77f4b80369e51cb767c61e33a2d4043399c67bd95fb7c6", size = 243185, upload-time = "2025-08-11T12:06:40.796Z" }, - { url = "https://files.pythonhosted.org/packages/aa/c9/d82e95ae1d6e4ef396934e9b0e942dfc428775f9554acf04393cce66b157/multidict-6.6.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:8c91cdb30809a96d9ecf442ec9bc45e8cfaa0f7f8bdf534e082c2443a196727e", size = 237838, upload-time = "2025-08-11T12:06:42.595Z" }, - { url = "https://files.pythonhosted.org/packages/57/cf/f94af5c36baaa75d44fab9f02e2a6bcfa0cd90acb44d4976a80960759dbc/multidict-6.6.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1a0ccbfe93ca114c5d65a2471d52d8829e56d467c97b0e341cf5ee45410033b3", size = 246368, upload-time = "2025-08-11T12:06:44.304Z" }, - { url = "https://files.pythonhosted.org/packages/4a/fe/29f23460c3d995f6a4b678cb2e9730e7277231b981f0b234702f0177818a/multidict-6.6.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:55624b3f321d84c403cb7d8e6e982f41ae233d85f85db54ba6286f7295dc8a9c", size = 253339, upload-time = "2025-08-11T12:06:45.597Z" }, - { url = "https://files.pythonhosted.org/packages/29/b6/fd59449204426187b82bf8a75f629310f68c6adc9559dc922d5abe34797b/multidict-6.6.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:4a1fb393a2c9d202cb766c76208bd7945bc194eba8ac920ce98c6e458f0b524b", size = 246933, upload-time = "2025-08-11T12:06:46.841Z" }, - { url = "https://files.pythonhosted.org/packages/19/52/d5d6b344f176a5ac3606f7a61fb44dc746e04550e1a13834dff722b8d7d6/multidict-6.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:43868297a5759a845fa3a483fb4392973a95fb1de891605a3728130c52b8f40f", size = 242225, upload-time = "2025-08-11T12:06:48.588Z" }, - { url = "https://files.pythonhosted.org/packages/ec/d3/5b2281ed89ff4d5318d82478a2a2450fcdfc3300da48ff15c1778280ad26/multidict-6.6.4-cp311-cp311-win32.whl", hash = "sha256:ed3b94c5e362a8a84d69642dbeac615452e8af9b8eb825b7bc9f31a53a1051e2", size = 41306, upload-time = "2025-08-11T12:06:49.95Z" }, - { url = "https://files.pythonhosted.org/packages/74/7d/36b045c23a1ab98507aefd44fd8b264ee1dd5e5010543c6fccf82141ccef/multidict-6.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:d8c112f7a90d8ca5d20213aa41eac690bb50a76da153e3afb3886418e61cb22e", size = 46029, upload-time = "2025-08-11T12:06:51.082Z" }, - { url = "https://files.pythonhosted.org/packages/0f/5e/553d67d24432c5cd52b49047f2d248821843743ee6d29a704594f656d182/multidict-6.6.4-cp311-cp311-win_arm64.whl", hash = "sha256:3bb0eae408fa1996d87247ca0d6a57b7fc1dcf83e8a5c47ab82c558c250d4adf", size = 43017, upload-time = "2025-08-11T12:06:52.243Z" }, - { url = "https://files.pythonhosted.org/packages/05/f6/512ffd8fd8b37fb2680e5ac35d788f1d71bbaf37789d21a820bdc441e565/multidict-6.6.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0ffb87be160942d56d7b87b0fdf098e81ed565add09eaa1294268c7f3caac4c8", size = 76516, upload-time = "2025-08-11T12:06:53.393Z" }, - { url = "https://files.pythonhosted.org/packages/99/58/45c3e75deb8855c36bd66cc1658007589662ba584dbf423d01df478dd1c5/multidict-6.6.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d191de6cbab2aff5de6c5723101705fd044b3e4c7cfd587a1929b5028b9714b3", size = 45394, upload-time = "2025-08-11T12:06:54.555Z" }, - { url = "https://files.pythonhosted.org/packages/fd/ca/e8c4472a93a26e4507c0b8e1f0762c0d8a32de1328ef72fd704ef9cc5447/multidict-6.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:38a0956dd92d918ad5feff3db8fcb4a5eb7dba114da917e1a88475619781b57b", size = 43591, upload-time = "2025-08-11T12:06:55.672Z" }, - { url = "https://files.pythonhosted.org/packages/05/51/edf414f4df058574a7265034d04c935aa84a89e79ce90fcf4df211f47b16/multidict-6.6.4-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:6865f6d3b7900ae020b495d599fcf3765653bc927951c1abb959017f81ae8287", size = 237215, upload-time = "2025-08-11T12:06:57.213Z" }, - { url = "https://files.pythonhosted.org/packages/c8/45/8b3d6dbad8cf3252553cc41abea09ad527b33ce47a5e199072620b296902/multidict-6.6.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a2088c126b6f72db6c9212ad827d0ba088c01d951cee25e758c450da732c138", size = 258299, upload-time = "2025-08-11T12:06:58.946Z" }, - { url = "https://files.pythonhosted.org/packages/3c/e8/8ca2e9a9f5a435fc6db40438a55730a4bf4956b554e487fa1b9ae920f825/multidict-6.6.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0f37bed7319b848097085d7d48116f545985db988e2256b2e6f00563a3416ee6", size = 242357, upload-time = "2025-08-11T12:07:00.301Z" }, - { url = "https://files.pythonhosted.org/packages/0f/84/80c77c99df05a75c28490b2af8f7cba2a12621186e0a8b0865d8e745c104/multidict-6.6.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:01368e3c94032ba6ca0b78e7ccb099643466cf24f8dc8eefcfdc0571d56e58f9", size = 268369, upload-time = "2025-08-11T12:07:01.638Z" }, - { url = "https://files.pythonhosted.org/packages/0d/e9/920bfa46c27b05fb3e1ad85121fd49f441492dca2449c5bcfe42e4565d8a/multidict-6.6.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8fe323540c255db0bffee79ad7f048c909f2ab0edb87a597e1c17da6a54e493c", size = 269341, upload-time = "2025-08-11T12:07:02.943Z" }, - { url = "https://files.pythonhosted.org/packages/af/65/753a2d8b05daf496f4a9c367fe844e90a1b2cac78e2be2c844200d10cc4c/multidict-6.6.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8eb3025f17b0a4c3cd08cda49acf312a19ad6e8a4edd9dbd591e6506d999402", size = 256100, upload-time = "2025-08-11T12:07:04.564Z" }, - { url = "https://files.pythonhosted.org/packages/09/54/655be13ae324212bf0bc15d665a4e34844f34c206f78801be42f7a0a8aaa/multidict-6.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bbc14f0365534d35a06970d6a83478b249752e922d662dc24d489af1aa0d1be7", size = 253584, upload-time = "2025-08-11T12:07:05.914Z" }, - { url = "https://files.pythonhosted.org/packages/5c/74/ab2039ecc05264b5cec73eb018ce417af3ebb384ae9c0e9ed42cb33f8151/multidict-6.6.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:75aa52fba2d96bf972e85451b99d8e19cc37ce26fd016f6d4aa60da9ab2b005f", size = 251018, upload-time = "2025-08-11T12:07:08.301Z" }, - { url = "https://files.pythonhosted.org/packages/af/0a/ccbb244ac848e56c6427f2392741c06302bbfba49c0042f1eb3c5b606497/multidict-6.6.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4fefd4a815e362d4f011919d97d7b4a1e566f1dde83dc4ad8cfb5b41de1df68d", size = 251477, upload-time = "2025-08-11T12:07:10.248Z" }, - { url = "https://files.pythonhosted.org/packages/0e/b0/0ed49bba775b135937f52fe13922bc64a7eaf0a3ead84a36e8e4e446e096/multidict-6.6.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:db9801fe021f59a5b375ab778973127ca0ac52429a26e2fd86aa9508f4d26eb7", size = 263575, upload-time = "2025-08-11T12:07:11.928Z" }, - { url = "https://files.pythonhosted.org/packages/3e/d9/7fb85a85e14de2e44dfb6a24f03c41e2af8697a6df83daddb0e9b7569f73/multidict-6.6.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a650629970fa21ac1fb06ba25dabfc5b8a2054fcbf6ae97c758aa956b8dba802", size = 259649, upload-time = "2025-08-11T12:07:13.244Z" }, - { url = "https://files.pythonhosted.org/packages/03/9e/b3a459bcf9b6e74fa461a5222a10ff9b544cb1cd52fd482fb1b75ecda2a2/multidict-6.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:452ff5da78d4720d7516a3a2abd804957532dd69296cb77319c193e3ffb87e24", size = 251505, upload-time = "2025-08-11T12:07:14.57Z" }, - { url = "https://files.pythonhosted.org/packages/86/a2/8022f78f041dfe6d71e364001a5cf987c30edfc83c8a5fb7a3f0974cff39/multidict-6.6.4-cp312-cp312-win32.whl", hash = "sha256:8c2fcb12136530ed19572bbba61b407f655e3953ba669b96a35036a11a485793", size = 41888, upload-time = "2025-08-11T12:07:15.904Z" }, - { url = "https://files.pythonhosted.org/packages/c7/eb/d88b1780d43a56db2cba24289fa744a9d216c1a8546a0dc3956563fd53ea/multidict-6.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:047d9425860a8c9544fed1b9584f0c8bcd31bcde9568b047c5e567a1025ecd6e", size = 46072, upload-time = "2025-08-11T12:07:17.045Z" }, - { url = "https://files.pythonhosted.org/packages/9f/16/b929320bf5750e2d9d4931835a4c638a19d2494a5b519caaaa7492ebe105/multidict-6.6.4-cp312-cp312-win_arm64.whl", hash = "sha256:14754eb72feaa1e8ae528468f24250dd997b8e2188c3d2f593f9eba259e4b364", size = 43222, upload-time = "2025-08-11T12:07:18.328Z" }, - { url = "https://files.pythonhosted.org/packages/fd/69/b547032297c7e63ba2af494edba695d781af8a0c6e89e4d06cf848b21d80/multidict-6.6.4-py3-none-any.whl", hash = "sha256:27d8f8e125c07cb954e54d75d04905a9bba8a439c1d84aca94949d4d03d8601c", size = 12313, upload-time = "2025-08-11T12:08:46.891Z" }, + { url = "https://files.pythonhosted.org/packages/34/9e/5c727587644d67b2ed479041e4b1c58e30afc011e3d45d25bbe35781217c/multidict-6.7.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4d409aa42a94c0b3fa617708ef5276dfe81012ba6753a0370fcc9d0195d0a1fc", size = 76604, upload-time = "2025-10-06T14:48:54.277Z" }, + { url = "https://files.pythonhosted.org/packages/17/e4/67b5c27bd17c085a5ea8f1ec05b8a3e5cba0ca734bfcad5560fb129e70ca/multidict-6.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:14c9e076eede3b54c636f8ce1c9c252b5f057c62131211f0ceeec273810c9721", size = 44715, upload-time = "2025-10-06T14:48:55.445Z" }, + { url = "https://files.pythonhosted.org/packages/4d/e1/866a5d77be6ea435711bef2a4291eed11032679b6b28b56b4776ab06ba3e/multidict-6.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4c09703000a9d0fa3c3404b27041e574cc7f4df4c6563873246d0e11812a94b6", size = 44332, upload-time = "2025-10-06T14:48:56.706Z" }, + { url = "https://files.pythonhosted.org/packages/31/61/0c2d50241ada71ff61a79518db85ada85fdabfcf395d5968dae1cbda04e5/multidict-6.7.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a265acbb7bb33a3a2d626afbe756371dce0279e7b17f4f4eda406459c2b5ff1c", size = 245212, upload-time = "2025-10-06T14:48:58.042Z" }, + { url = "https://files.pythonhosted.org/packages/ac/e0/919666a4e4b57fff1b57f279be1c9316e6cdc5de8a8b525d76f6598fefc7/multidict-6.7.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51cb455de290ae462593e5b1cb1118c5c22ea7f0d3620d9940bf695cea5a4bd7", size = 246671, upload-time = "2025-10-06T14:49:00.004Z" }, + { url = "https://files.pythonhosted.org/packages/a1/cc/d027d9c5a520f3321b65adea289b965e7bcbd2c34402663f482648c716ce/multidict-6.7.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:db99677b4457c7a5c5a949353e125ba72d62b35f74e26da141530fbb012218a7", size = 225491, upload-time = "2025-10-06T14:49:01.393Z" }, + { url = "https://files.pythonhosted.org/packages/75/c4/bbd633980ce6155a28ff04e6a6492dd3335858394d7bb752d8b108708558/multidict-6.7.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f470f68adc395e0183b92a2f4689264d1ea4b40504a24d9882c27375e6662bb9", size = 257322, upload-time = "2025-10-06T14:49:02.745Z" }, + { url = "https://files.pythonhosted.org/packages/4c/6d/d622322d344f1f053eae47e033b0b3f965af01212de21b10bcf91be991fb/multidict-6.7.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0db4956f82723cc1c270de9c6e799b4c341d327762ec78ef82bb962f79cc07d8", size = 254694, upload-time = "2025-10-06T14:49:04.15Z" }, + { url = "https://files.pythonhosted.org/packages/a8/9f/78f8761c2705d4c6d7516faed63c0ebdac569f6db1bef95e0d5218fdc146/multidict-6.7.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3e56d780c238f9e1ae66a22d2adf8d16f485381878250db8d496623cd38b22bd", size = 246715, upload-time = "2025-10-06T14:49:05.967Z" }, + { url = "https://files.pythonhosted.org/packages/78/59/950818e04f91b9c2b95aab3d923d9eabd01689d0dcd889563988e9ea0fd8/multidict-6.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9d14baca2ee12c1a64740d4531356ba50b82543017f3ad6de0deb943c5979abb", size = 243189, upload-time = "2025-10-06T14:49:07.37Z" }, + { url = "https://files.pythonhosted.org/packages/7a/3d/77c79e1934cad2ee74991840f8a0110966d9599b3af95964c0cd79bb905b/multidict-6.7.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:295a92a76188917c7f99cda95858c822f9e4aae5824246bba9b6b44004ddd0a6", size = 237845, upload-time = "2025-10-06T14:49:08.759Z" }, + { url = "https://files.pythonhosted.org/packages/63/1b/834ce32a0a97a3b70f86437f685f880136677ac00d8bce0027e9fd9c2db7/multidict-6.7.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:39f1719f57adbb767ef592a50ae5ebb794220d1188f9ca93de471336401c34d2", size = 246374, upload-time = "2025-10-06T14:49:10.574Z" }, + { url = "https://files.pythonhosted.org/packages/23/ef/43d1c3ba205b5dec93dc97f3fba179dfa47910fc73aaaea4f7ceb41cec2a/multidict-6.7.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:0a13fb8e748dfc94749f622de065dd5c1def7e0d2216dba72b1d8069a389c6ff", size = 253345, upload-time = "2025-10-06T14:49:12.331Z" }, + { url = "https://files.pythonhosted.org/packages/6b/03/eaf95bcc2d19ead522001f6a650ef32811aa9e3624ff0ad37c445c7a588c/multidict-6.7.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e3aa16de190d29a0ea1b48253c57d99a68492c8dd8948638073ab9e74dc9410b", size = 246940, upload-time = "2025-10-06T14:49:13.821Z" }, + { url = "https://files.pythonhosted.org/packages/e8/df/ec8a5fd66ea6cd6f525b1fcbb23511b033c3e9bc42b81384834ffa484a62/multidict-6.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a048ce45dcdaaf1defb76b2e684f997fb5abf74437b6cb7b22ddad934a964e34", size = 242229, upload-time = "2025-10-06T14:49:15.603Z" }, + { url = "https://files.pythonhosted.org/packages/8a/a2/59b405d59fd39ec86d1142630e9049243015a5f5291ba49cadf3c090c541/multidict-6.7.0-cp311-cp311-win32.whl", hash = "sha256:a90af66facec4cebe4181b9e62a68be65e45ac9b52b67de9eec118701856e7ff", size = 41308, upload-time = "2025-10-06T14:49:16.871Z" }, + { url = "https://files.pythonhosted.org/packages/32/0f/13228f26f8b882c34da36efa776c3b7348455ec383bab4a66390e42963ae/multidict-6.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:95b5ffa4349df2887518bb839409bcf22caa72d82beec453216802f475b23c81", size = 46037, upload-time = "2025-10-06T14:49:18.457Z" }, + { url = "https://files.pythonhosted.org/packages/84/1f/68588e31b000535a3207fd3c909ebeec4fb36b52c442107499c18a896a2a/multidict-6.7.0-cp311-cp311-win_arm64.whl", hash = "sha256:329aa225b085b6f004a4955271a7ba9f1087e39dcb7e65f6284a988264a63912", size = 43023, upload-time = "2025-10-06T14:49:19.648Z" }, + { url = "https://files.pythonhosted.org/packages/c2/9e/9f61ac18d9c8b475889f32ccfa91c9f59363480613fc807b6e3023d6f60b/multidict-6.7.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8a3862568a36d26e650a19bb5cbbba14b71789032aebc0423f8cc5f150730184", size = 76877, upload-time = "2025-10-06T14:49:20.884Z" }, + { url = "https://files.pythonhosted.org/packages/38/6f/614f09a04e6184f8824268fce4bc925e9849edfa654ddd59f0b64508c595/multidict-6.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:960c60b5849b9b4f9dcc9bea6e3626143c252c74113df2c1540aebce70209b45", size = 45467, upload-time = "2025-10-06T14:49:22.054Z" }, + { url = "https://files.pythonhosted.org/packages/b3/93/c4f67a436dd026f2e780c433277fff72be79152894d9fc36f44569cab1a6/multidict-6.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2049be98fb57a31b4ccf870bf377af2504d4ae35646a19037ec271e4c07998aa", size = 43834, upload-time = "2025-10-06T14:49:23.566Z" }, + { url = "https://files.pythonhosted.org/packages/7f/f5/013798161ca665e4a422afbc5e2d9e4070142a9ff8905e482139cd09e4d0/multidict-6.7.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0934f3843a1860dd465d38895c17fce1f1cb37295149ab05cd1b9a03afacb2a7", size = 250545, upload-time = "2025-10-06T14:49:24.882Z" }, + { url = "https://files.pythonhosted.org/packages/71/2f/91dbac13e0ba94669ea5119ba267c9a832f0cb65419aca75549fcf09a3dc/multidict-6.7.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b3e34f3a1b8131ba06f1a73adab24f30934d148afcd5f5de9a73565a4404384e", size = 258305, upload-time = "2025-10-06T14:49:26.778Z" }, + { url = "https://files.pythonhosted.org/packages/ef/b0/754038b26f6e04488b48ac621f779c341338d78503fb45403755af2df477/multidict-6.7.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:efbb54e98446892590dc2458c19c10344ee9a883a79b5cec4bc34d6656e8d546", size = 242363, upload-time = "2025-10-06T14:49:28.562Z" }, + { url = "https://files.pythonhosted.org/packages/87/15/9da40b9336a7c9fa606c4cf2ed80a649dffeb42b905d4f63a1d7eb17d746/multidict-6.7.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a35c5fc61d4f51eb045061e7967cfe3123d622cd500e8868e7c0c592a09fedc4", size = 268375, upload-time = "2025-10-06T14:49:29.96Z" }, + { url = "https://files.pythonhosted.org/packages/82/72/c53fcade0cc94dfaad583105fd92b3a783af2091eddcb41a6d5a52474000/multidict-6.7.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29fe6740ebccba4175af1b9b87bf553e9c15cd5868ee967e010efcf94e4fd0f1", size = 269346, upload-time = "2025-10-06T14:49:31.404Z" }, + { url = "https://files.pythonhosted.org/packages/0d/e2/9baffdae21a76f77ef8447f1a05a96ec4bc0a24dae08767abc0a2fe680b8/multidict-6.7.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:123e2a72e20537add2f33a79e605f6191fba2afda4cbb876e35c1a7074298a7d", size = 256107, upload-time = "2025-10-06T14:49:32.974Z" }, + { url = "https://files.pythonhosted.org/packages/3c/06/3f06f611087dc60d65ef775f1fb5aca7c6d61c6db4990e7cda0cef9b1651/multidict-6.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b284e319754366c1aee2267a2036248b24eeb17ecd5dc16022095e747f2f4304", size = 253592, upload-time = "2025-10-06T14:49:34.52Z" }, + { url = "https://files.pythonhosted.org/packages/20/24/54e804ec7945b6023b340c412ce9c3f81e91b3bf5fa5ce65558740141bee/multidict-6.7.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:803d685de7be4303b5a657b76e2f6d1240e7e0a8aa2968ad5811fa2285553a12", size = 251024, upload-time = "2025-10-06T14:49:35.956Z" }, + { url = "https://files.pythonhosted.org/packages/14/48/011cba467ea0b17ceb938315d219391d3e421dfd35928e5dbdc3f4ae76ef/multidict-6.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c04a328260dfd5db8c39538f999f02779012268f54614902d0afc775d44e0a62", size = 251484, upload-time = "2025-10-06T14:49:37.631Z" }, + { url = "https://files.pythonhosted.org/packages/0d/2f/919258b43bb35b99fa127435cfb2d91798eb3a943396631ef43e3720dcf4/multidict-6.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8a19cdb57cd3df4cd865849d93ee14920fb97224300c88501f16ecfa2604b4e0", size = 263579, upload-time = "2025-10-06T14:49:39.502Z" }, + { url = "https://files.pythonhosted.org/packages/31/22/a0e884d86b5242b5a74cf08e876bdf299e413016b66e55511f7a804a366e/multidict-6.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b2fd74c52accced7e75de26023b7dccee62511a600e62311b918ec5c168fc2a", size = 259654, upload-time = "2025-10-06T14:49:41.32Z" }, + { url = "https://files.pythonhosted.org/packages/b2/e5/17e10e1b5c5f5a40f2fcbb45953c9b215f8a4098003915e46a93f5fcaa8f/multidict-6.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3e8bfdd0e487acf992407a140d2589fe598238eaeffa3da8448d63a63cd363f8", size = 251511, upload-time = "2025-10-06T14:49:46.021Z" }, + { url = "https://files.pythonhosted.org/packages/e3/9a/201bb1e17e7af53139597069c375e7b0dcbd47594604f65c2d5359508566/multidict-6.7.0-cp312-cp312-win32.whl", hash = "sha256:dd32a49400a2c3d52088e120ee00c1e3576cbff7e10b98467962c74fdb762ed4", size = 41895, upload-time = "2025-10-06T14:49:48.718Z" }, + { url = "https://files.pythonhosted.org/packages/46/e2/348cd32faad84eaf1d20cce80e2bb0ef8d312c55bca1f7fa9865e7770aaf/multidict-6.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:92abb658ef2d7ef22ac9f8bb88e8b6c3e571671534e029359b6d9e845923eb1b", size = 46073, upload-time = "2025-10-06T14:49:50.28Z" }, + { url = "https://files.pythonhosted.org/packages/25/ec/aad2613c1910dce907480e0c3aa306905830f25df2e54ccc9dea450cb5aa/multidict-6.7.0-cp312-cp312-win_arm64.whl", hash = "sha256:490dab541a6a642ce1a9d61a4781656b346a55c13038f0b1244653828e3a83ec", size = 43226, upload-time = "2025-10-06T14:49:52.304Z" }, + { url = "https://files.pythonhosted.org/packages/b7/da/7d22601b625e241d4f23ef1ebff8acfc60da633c9e7e7922e24d10f592b3/multidict-6.7.0-py3-none-any.whl", hash = "sha256:394fc5c42a333c9ffc3e421a4c85e08580d990e08b99f6bf35b4132114c5dcb3", size = 12317, upload-time = "2025-10-06T14:52:29.272Z" }, ] [[package]] @@ -1199,44 +1211,44 @@ wheels = [ [[package]] name = "numpy" -version = "2.3.3" +version = "2.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d0/19/95b3d357407220ed24c139018d2518fab0a61a948e68286a25f1a4d049ff/numpy-2.3.3.tar.gz", hash = "sha256:ddc7c39727ba62b80dfdbedf400d1c10ddfa8eefbd7ec8dcb118be8b56d31029", size = 20576648, upload-time = "2025-09-09T16:54:12.543Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a4/7a/6a3d14e205d292b738db449d0de649b373a59edb0d0b4493821d0a3e8718/numpy-2.4.0.tar.gz", hash = "sha256:6e504f7b16118198f138ef31ba24d985b124c2c469fe8467007cf30fd992f934", size = 20685720, upload-time = "2025-12-20T16:18:19.023Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7a/45/e80d203ef6b267aa29b22714fb558930b27960a0c5ce3c19c999232bb3eb/numpy-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0ffc4f5caba7dfcbe944ed674b7eef683c7e94874046454bb79ed7ee0236f59d", size = 21259253, upload-time = "2025-09-09T15:56:02.094Z" }, - { url = "https://files.pythonhosted.org/packages/52/18/cf2c648fccf339e59302e00e5f2bc87725a3ce1992f30f3f78c9044d7c43/numpy-2.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e7e946c7170858a0295f79a60214424caac2ffdb0063d4d79cb681f9aa0aa569", size = 14450980, upload-time = "2025-09-09T15:56:05.926Z" }, - { url = "https://files.pythonhosted.org/packages/93/fb/9af1082bec870188c42a1c239839915b74a5099c392389ff04215dcee812/numpy-2.3.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:cd4260f64bc794c3390a63bf0728220dd1a68170c169088a1e0dfa2fde1be12f", size = 5379709, upload-time = "2025-09-09T15:56:07.95Z" }, - { url = "https://files.pythonhosted.org/packages/75/0f/bfd7abca52bcbf9a4a65abc83fe18ef01ccdeb37bfb28bbd6ad613447c79/numpy-2.3.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:f0ddb4b96a87b6728df9362135e764eac3cfa674499943ebc44ce96c478ab125", size = 6913923, upload-time = "2025-09-09T15:56:09.443Z" }, - { url = "https://files.pythonhosted.org/packages/79/55/d69adad255e87ab7afda1caf93ca997859092afeb697703e2f010f7c2e55/numpy-2.3.3-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:afd07d377f478344ec6ca2b8d4ca08ae8bd44706763d1efb56397de606393f48", size = 14589591, upload-time = "2025-09-09T15:56:11.234Z" }, - { url = "https://files.pythonhosted.org/packages/10/a2/010b0e27ddeacab7839957d7a8f00e91206e0c2c47abbb5f35a2630e5387/numpy-2.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bc92a5dedcc53857249ca51ef29f5e5f2f8c513e22cfb90faeb20343b8c6f7a6", size = 16938714, upload-time = "2025-09-09T15:56:14.637Z" }, - { url = "https://files.pythonhosted.org/packages/1c/6b/12ce8ede632c7126eb2762b9e15e18e204b81725b81f35176eac14dc5b82/numpy-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7af05ed4dc19f308e1d9fc759f36f21921eb7bbfc82843eeec6b2a2863a0aefa", size = 16370592, upload-time = "2025-09-09T15:56:17.285Z" }, - { url = "https://files.pythonhosted.org/packages/b4/35/aba8568b2593067bb6a8fe4c52babb23b4c3b9c80e1b49dff03a09925e4a/numpy-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:433bf137e338677cebdd5beac0199ac84712ad9d630b74eceeb759eaa45ddf30", size = 18884474, upload-time = "2025-09-09T15:56:20.943Z" }, - { url = "https://files.pythonhosted.org/packages/45/fa/7f43ba10c77575e8be7b0138d107e4f44ca4a1ef322cd16980ea3e8b8222/numpy-2.3.3-cp311-cp311-win32.whl", hash = "sha256:eb63d443d7b4ffd1e873f8155260d7f58e7e4b095961b01c91062935c2491e57", size = 6599794, upload-time = "2025-09-09T15:56:23.258Z" }, - { url = "https://files.pythonhosted.org/packages/0a/a2/a4f78cb2241fe5664a22a10332f2be886dcdea8784c9f6a01c272da9b426/numpy-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:ec9d249840f6a565f58d8f913bccac2444235025bbb13e9a4681783572ee3caa", size = 13088104, upload-time = "2025-09-09T15:56:25.476Z" }, - { url = "https://files.pythonhosted.org/packages/79/64/e424e975adbd38282ebcd4891661965b78783de893b381cbc4832fb9beb2/numpy-2.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:74c2a948d02f88c11a3c075d9733f1ae67d97c6bdb97f2bb542f980458b257e7", size = 10460772, upload-time = "2025-09-09T15:56:27.679Z" }, - { url = "https://files.pythonhosted.org/packages/51/5d/bb7fc075b762c96329147799e1bcc9176ab07ca6375ea976c475482ad5b3/numpy-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cfdd09f9c84a1a934cde1eec2267f0a43a7cd44b2cca4ff95b7c0d14d144b0bf", size = 20957014, upload-time = "2025-09-09T15:56:29.966Z" }, - { url = "https://files.pythonhosted.org/packages/6b/0e/c6211bb92af26517acd52125a237a92afe9c3124c6a68d3b9f81b62a0568/numpy-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb32e3cf0f762aee47ad1ddc6672988f7f27045b0783c887190545baba73aa25", size = 14185220, upload-time = "2025-09-09T15:56:32.175Z" }, - { url = "https://files.pythonhosted.org/packages/22/f2/07bb754eb2ede9073f4054f7c0286b0d9d2e23982e090a80d478b26d35ca/numpy-2.3.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:396b254daeb0a57b1fe0ecb5e3cff6fa79a380fa97c8f7781a6d08cd429418fe", size = 5113918, upload-time = "2025-09-09T15:56:34.175Z" }, - { url = "https://files.pythonhosted.org/packages/81/0a/afa51697e9fb74642f231ea36aca80fa17c8fb89f7a82abd5174023c3960/numpy-2.3.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:067e3d7159a5d8f8a0b46ee11148fc35ca9b21f61e3c49fbd0a027450e65a33b", size = 6647922, upload-time = "2025-09-09T15:56:36.149Z" }, - { url = "https://files.pythonhosted.org/packages/5d/f5/122d9cdb3f51c520d150fef6e87df9279e33d19a9611a87c0d2cf78a89f4/numpy-2.3.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c02d0629d25d426585fb2e45a66154081b9fa677bc92a881ff1d216bc9919a8", size = 14281991, upload-time = "2025-09-09T15:56:40.548Z" }, - { url = "https://files.pythonhosted.org/packages/51/64/7de3c91e821a2debf77c92962ea3fe6ac2bc45d0778c1cbe15d4fce2fd94/numpy-2.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9192da52b9745f7f0766531dcfa978b7763916f158bb63bdb8a1eca0068ab20", size = 16641643, upload-time = "2025-09-09T15:56:43.343Z" }, - { url = "https://files.pythonhosted.org/packages/30/e4/961a5fa681502cd0d68907818b69f67542695b74e3ceaa513918103b7e80/numpy-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cd7de500a5b66319db419dc3c345244404a164beae0d0937283b907d8152e6ea", size = 16056787, upload-time = "2025-09-09T15:56:46.141Z" }, - { url = "https://files.pythonhosted.org/packages/99/26/92c912b966e47fbbdf2ad556cb17e3a3088e2e1292b9833be1dfa5361a1a/numpy-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:93d4962d8f82af58f0b2eb85daaf1b3ca23fe0a85d0be8f1f2b7bb46034e56d7", size = 18579598, upload-time = "2025-09-09T15:56:49.844Z" }, - { url = "https://files.pythonhosted.org/packages/17/b6/fc8f82cb3520768718834f310c37d96380d9dc61bfdaf05fe5c0b7653e01/numpy-2.3.3-cp312-cp312-win32.whl", hash = "sha256:5534ed6b92f9b7dca6c0a19d6df12d41c68b991cef051d108f6dbff3babc4ebf", size = 6320800, upload-time = "2025-09-09T15:56:52.499Z" }, - { url = "https://files.pythonhosted.org/packages/32/ee/de999f2625b80d043d6d2d628c07d0d5555a677a3cf78fdf868d409b8766/numpy-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:497d7cad08e7092dba36e3d296fe4c97708c93daf26643a1ae4b03f6294d30eb", size = 12786615, upload-time = "2025-09-09T15:56:54.422Z" }, - { url = "https://files.pythonhosted.org/packages/49/6e/b479032f8a43559c383acb20816644f5f91c88f633d9271ee84f3b3a996c/numpy-2.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:ca0309a18d4dfea6fc6262a66d06c26cfe4640c3926ceec90e57791a82b6eee5", size = 10195936, upload-time = "2025-09-09T15:56:56.541Z" }, - { url = "https://files.pythonhosted.org/packages/b8/f2/7e0a37cfced2644c9563c529f29fa28acbd0960dde32ece683aafa6f4949/numpy-2.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1e02c7159791cd481e1e6d5ddd766b62a4d5acf8df4d4d1afe35ee9c5c33a41e", size = 21131019, upload-time = "2025-09-09T15:58:42.838Z" }, - { url = "https://files.pythonhosted.org/packages/1a/7e/3291f505297ed63831135a6cc0f474da0c868a1f31b0dd9a9f03a7a0d2ed/numpy-2.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:dca2d0fc80b3893ae72197b39f69d55a3cd8b17ea1b50aa4c62de82419936150", size = 14376288, upload-time = "2025-09-09T15:58:45.425Z" }, - { url = "https://files.pythonhosted.org/packages/bf/4b/ae02e985bdeee73d7b5abdefeb98aef1207e96d4c0621ee0cf228ddfac3c/numpy-2.3.3-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:99683cbe0658f8271b333a1b1b4bb3173750ad59c0c61f5bbdc5b318918fffe3", size = 5305425, upload-time = "2025-09-09T15:58:48.6Z" }, - { url = "https://files.pythonhosted.org/packages/8b/eb/9df215d6d7250db32007941500dc51c48190be25f2401d5b2b564e467247/numpy-2.3.3-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:d9d537a39cc9de668e5cd0e25affb17aec17b577c6b3ae8a3d866b479fbe88d0", size = 6819053, upload-time = "2025-09-09T15:58:50.401Z" }, - { url = "https://files.pythonhosted.org/packages/57/62/208293d7d6b2a8998a4a1f23ac758648c3c32182d4ce4346062018362e29/numpy-2.3.3-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8596ba2f8af5f93b01d97563832686d20206d303024777f6dfc2e7c7c3f1850e", size = 14420354, upload-time = "2025-09-09T15:58:52.704Z" }, - { url = "https://files.pythonhosted.org/packages/ed/0c/8e86e0ff7072e14a71b4c6af63175e40d1e7e933ce9b9e9f765a95b4e0c3/numpy-2.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1ec5615b05369925bd1125f27df33f3b6c8bc10d788d5999ecd8769a1fa04db", size = 16760413, upload-time = "2025-09-09T15:58:55.027Z" }, - { url = "https://files.pythonhosted.org/packages/af/11/0cc63f9f321ccf63886ac203336777140011fb669e739da36d8db3c53b98/numpy-2.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:2e267c7da5bf7309670523896df97f93f6e469fb931161f483cd6882b3b1a5dc", size = 12971844, upload-time = "2025-09-09T15:58:57.359Z" }, + { url = "https://files.pythonhosted.org/packages/26/7e/7bae7cbcc2f8132271967aa03e03954fc1e48aa1f3bf32b29ca95fbef352/numpy-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:316b2f2584682318539f0bcaca5a496ce9ca78c88066579ebd11fd06f8e4741e", size = 16940166, upload-time = "2025-12-20T16:15:43.434Z" }, + { url = "https://files.pythonhosted.org/packages/0f/27/6c13f5b46776d6246ec884ac5817452672156a506d08a1f2abb39961930a/numpy-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2718c1de8504121714234b6f8241d0019450353276c88b9453c9c3d92e101db", size = 12641781, upload-time = "2025-12-20T16:15:45.701Z" }, + { url = "https://files.pythonhosted.org/packages/14/1c/83b4998d4860d15283241d9e5215f28b40ac31f497c04b12fa7f428ff370/numpy-2.4.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:21555da4ec4a0c942520ead42c3b0dc9477441e085c42b0fbdd6a084869a6f6b", size = 5470247, upload-time = "2025-12-20T16:15:47.943Z" }, + { url = "https://files.pythonhosted.org/packages/54/08/cbce72c835d937795571b0464b52069f869c9e78b0c076d416c5269d2718/numpy-2.4.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:413aa561266a4be2d06cd2b9665e89d9f54c543f418773076a76adcf2af08bc7", size = 6799807, upload-time = "2025-12-20T16:15:49.795Z" }, + { url = "https://files.pythonhosted.org/packages/ff/be/2e647961cd8c980591d75cdcd9e8f647d69fbe05e2a25613dc0a2ea5fb1a/numpy-2.4.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0feafc9e03128074689183031181fac0897ff169692d8492066e949041096548", size = 14701992, upload-time = "2025-12-20T16:15:51.615Z" }, + { url = "https://files.pythonhosted.org/packages/a2/fb/e1652fb8b6fd91ce6ed429143fe2e01ce714711e03e5b762615e7b36172c/numpy-2.4.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8fdfed3deaf1928fb7667d96e0567cdf58c2b370ea2ee7e586aa383ec2cb346", size = 16646871, upload-time = "2025-12-20T16:15:54.129Z" }, + { url = "https://files.pythonhosted.org/packages/62/23/d841207e63c4322842f7cd042ae981cffe715c73376dcad8235fb31debf1/numpy-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e06a922a469cae9a57100864caf4f8a97a1026513793969f8ba5b63137a35d25", size = 16487190, upload-time = "2025-12-20T16:15:56.147Z" }, + { url = "https://files.pythonhosted.org/packages/bc/a0/6a842c8421ebfdec0a230e65f61e0dabda6edbef443d999d79b87c273965/numpy-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:927ccf5cd17c48f801f4ed43a7e5673a2724bd2171460be3e3894e6e332ef83a", size = 18580762, upload-time = "2025-12-20T16:15:58.524Z" }, + { url = "https://files.pythonhosted.org/packages/0a/d1/c79e0046641186f2134dde05e6181825b911f8bdcef31b19ddd16e232847/numpy-2.4.0-cp311-cp311-win32.whl", hash = "sha256:882567b7ae57c1b1a0250208cc21a7976d8cbcc49d5a322e607e6f09c9e0bd53", size = 6233359, upload-time = "2025-12-20T16:16:00.938Z" }, + { url = "https://files.pythonhosted.org/packages/fc/f0/74965001d231f28184d6305b8cdc1b6fcd4bf23033f6cb039cfe76c9fca7/numpy-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:8b986403023c8f3bf8f487c2e6186afda156174d31c175f747d8934dfddf3479", size = 12601132, upload-time = "2025-12-20T16:16:02.484Z" }, + { url = "https://files.pythonhosted.org/packages/65/32/55408d0f46dfebce38017f5bd931affa7256ad6beac1a92a012e1fbc67a7/numpy-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:3f3096405acc48887458bbf9f6814d43785ac7ba2a57ea6442b581dedbc60ce6", size = 10573977, upload-time = "2025-12-20T16:16:04.77Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ff/f6400ffec95de41c74b8e73df32e3fff1830633193a7b1e409be7fb1bb8c/numpy-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2a8b6bb8369abefb8bd1801b054ad50e02b3275c8614dc6e5b0373c305291037", size = 16653117, upload-time = "2025-12-20T16:16:06.709Z" }, + { url = "https://files.pythonhosted.org/packages/fd/28/6c23e97450035072e8d830a3c411bf1abd1f42c611ff9d29e3d8f55c6252/numpy-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2e284ca13d5a8367e43734148622caf0b261b275673823593e3e3634a6490f83", size = 12369711, upload-time = "2025-12-20T16:16:08.758Z" }, + { url = "https://files.pythonhosted.org/packages/bc/af/acbef97b630ab1bb45e6a7d01d1452e4251aa88ce680ac36e56c272120ec/numpy-2.4.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:49ff32b09f5aa0cd30a20c2b39db3e669c845589f2b7fc910365210887e39344", size = 5198355, upload-time = "2025-12-20T16:16:10.902Z" }, + { url = "https://files.pythonhosted.org/packages/c1/c8/4e0d436b66b826f2e53330adaa6311f5cac9871a5b5c31ad773b27f25a74/numpy-2.4.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:36cbfb13c152b1c7c184ddac43765db8ad672567e7bafff2cc755a09917ed2e6", size = 6545298, upload-time = "2025-12-20T16:16:12.607Z" }, + { url = "https://files.pythonhosted.org/packages/ef/27/e1f5d144ab54eac34875e79037011d511ac57b21b220063310cb96c80fbc/numpy-2.4.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:35ddc8f4914466e6fc954c76527aa91aa763682a4f6d73249ef20b418fe6effb", size = 14398387, upload-time = "2025-12-20T16:16:14.257Z" }, + { url = "https://files.pythonhosted.org/packages/67/64/4cb909dd5ab09a9a5d086eff9586e69e827b88a5585517386879474f4cf7/numpy-2.4.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc578891de1db95b2a35001b695451767b580bb45753717498213c5ff3c41d63", size = 16363091, upload-time = "2025-12-20T16:16:17.32Z" }, + { url = "https://files.pythonhosted.org/packages/9d/9c/8efe24577523ec6809261859737cf117b0eb6fdb655abdfdc81b2e468ce4/numpy-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:98e81648e0b36e325ab67e46b5400a7a6d4a22b8a7c8e8bbfe20e7db7906bf95", size = 16176394, upload-time = "2025-12-20T16:16:19.524Z" }, + { url = "https://files.pythonhosted.org/packages/61/f0/1687441ece7b47a62e45a1f82015352c240765c707928edd8aef875d5951/numpy-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d57b5046c120561ba8fa8e4030fbb8b822f3063910fa901ffadf16e2b7128ad6", size = 18287378, upload-time = "2025-12-20T16:16:22.866Z" }, + { url = "https://files.pythonhosted.org/packages/d3/6f/f868765d44e6fc466467ed810ba9d8d6db1add7d4a748abfa2a4c99a3194/numpy-2.4.0-cp312-cp312-win32.whl", hash = "sha256:92190db305a6f48734d3982f2c60fa30d6b5ee9bff10f2887b930d7b40119f4c", size = 5955432, upload-time = "2025-12-20T16:16:25.06Z" }, + { url = "https://files.pythonhosted.org/packages/d4/b5/94c1e79fcbab38d1ca15e13777477b2914dd2d559b410f96949d6637b085/numpy-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:680060061adb2d74ce352628cb798cfdec399068aa7f07ba9fb818b2b3305f98", size = 12306201, upload-time = "2025-12-20T16:16:26.979Z" }, + { url = "https://files.pythonhosted.org/packages/70/09/c39dadf0b13bb0768cd29d6a3aaff1fb7c6905ac40e9aaeca26b1c086e06/numpy-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:39699233bc72dd482da1415dcb06076e32f60eddc796a796c5fb6c5efce94667", size = 10308234, upload-time = "2025-12-20T16:16:29.417Z" }, + { url = "https://files.pythonhosted.org/packages/4b/ef/088e7c7342f300aaf3ee5f2c821c4b9996a1bef2aaf6a49cc8ab4883758e/numpy-2.4.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b54c83f1c0c0f1d748dca0af516062b8829d53d1f0c402be24b4257a9c48ada6", size = 16819003, upload-time = "2025-12-20T16:18:03.41Z" }, + { url = "https://files.pythonhosted.org/packages/ff/ce/a53017b5443b4b84517182d463fc7bcc2adb4faa8b20813f8e5f5aeb5faa/numpy-2.4.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:aabb081ca0ec5d39591fc33018cd4b3f96e1a2dd6756282029986d00a785fba4", size = 12567105, upload-time = "2025-12-20T16:18:05.594Z" }, + { url = "https://files.pythonhosted.org/packages/77/58/5ff91b161f2ec650c88a626c3905d938c89aaadabd0431e6d9c1330c83e2/numpy-2.4.0-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:8eafe7c36c8430b7794edeab3087dec7bf31d634d92f2af9949434b9d1964cba", size = 5395590, upload-time = "2025-12-20T16:18:08.031Z" }, + { url = "https://files.pythonhosted.org/packages/1d/4e/f1a084106df8c2df8132fc437e56987308e0524836aa7733721c8429d4fe/numpy-2.4.0-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:2f585f52b2baf07ff3356158d9268ea095e221371f1074fadea2f42544d58b4d", size = 6709947, upload-time = "2025-12-20T16:18:09.836Z" }, + { url = "https://files.pythonhosted.org/packages/63/09/3d8aeb809c0332c3f642da812ac2e3d74fc9252b3021f8c30c82e99e3f3d/numpy-2.4.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:32ed06d0fe9cae27d8fb5f400c63ccee72370599c75e683a6358dd3a4fb50aaf", size = 14535119, upload-time = "2025-12-20T16:18:12.105Z" }, + { url = "https://files.pythonhosted.org/packages/fd/7f/68f0fc43a2cbdc6bb239160c754d87c922f60fbaa0fa3cd3d312b8a7f5ee/numpy-2.4.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:57c540ed8fb1f05cb997c6761cd56db72395b0d6985e90571ff660452ade4f98", size = 16475815, upload-time = "2025-12-20T16:18:14.433Z" }, + { url = "https://files.pythonhosted.org/packages/11/73/edeacba3167b1ca66d51b1a5a14697c2c40098b5ffa01811c67b1785a5ab/numpy-2.4.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:a39fb973a726e63223287adc6dafe444ce75af952d711e400f3bf2b36ef55a7b", size = 12489376, upload-time = "2025-12-20T16:18:16.524Z" }, ] [[package]] name = "onnx" -version = "1.19.0" +version = "1.20.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "ml-dtypes" }, @@ -1244,20 +1256,20 @@ dependencies = [ { name = "protobuf" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5b/bf/b0a63ee9f3759dcd177b28c6f2cb22f2aecc6d9b3efecaabc298883caa5f/onnx-1.19.0.tar.gz", hash = "sha256:aa3f70b60f54a29015e41639298ace06adf1dd6b023b9b30f1bca91bb0db9473", size = 11949859, upload-time = "2025-08-27T02:34:27.107Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/bf/824b13b7ea14c2d374b48a296cfa412442e5559326fbab5441a4fcb68924/onnx-1.20.0.tar.gz", hash = "sha256:1a93ec69996b4556062d552ed1aa0671978cfd3c17a40bf4c89a1ae169c6a4ad", size = 12049527, upload-time = "2025-12-01T18:14:34.679Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/db/5c/b959b17608cfb6ccf6359b39fe56a5b0b7d965b3d6e6a3c0add90812c36e/onnx-1.19.0-cp311-cp311-macosx_12_0_universal2.whl", hash = "sha256:206f00c47b85b5c7af79671e3307147407991a17994c26974565aadc9e96e4e4", size = 18312580, upload-time = "2025-08-27T02:33:03.081Z" }, - { url = "https://files.pythonhosted.org/packages/2c/ee/ac052bbbc832abe0debb784c2c57f9582444fb5f51d63c2967fd04432444/onnx-1.19.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4d7bee94abaac28988b50da675ae99ef8dd3ce16210d591fbd0b214a5930beb3", size = 18029165, upload-time = "2025-08-27T02:33:05.771Z" }, - { url = "https://files.pythonhosted.org/packages/5c/c9/8687ba0948d46fd61b04e3952af9237883bbf8f16d716e7ed27e688d73b8/onnx-1.19.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7730b96b68c0c354bbc7857961bb4909b9aaa171360a8e3708d0a4c749aaadeb", size = 18202125, upload-time = "2025-08-27T02:33:09.325Z" }, - { url = "https://files.pythonhosted.org/packages/e2/16/6249c013e81bd689f46f96c7236d7677f1af5dd9ef22746716b48f10e506/onnx-1.19.0-cp311-cp311-win32.whl", hash = "sha256:7cb7a3ad8059d1a0dfdc5e0a98f71837d82002e441f112825403b137227c2c97", size = 16332738, upload-time = "2025-08-27T02:33:12.448Z" }, - { url = "https://files.pythonhosted.org/packages/6a/28/34a1e2166e418c6a78e5c82e66f409d9da9317832f11c647f7d4e23846a6/onnx-1.19.0-cp311-cp311-win_amd64.whl", hash = "sha256:d75452a9be868bd30c3ef6aa5991df89bbfe53d0d90b2325c5e730fbd91fff85", size = 16452303, upload-time = "2025-08-27T02:33:15.176Z" }, - { url = "https://files.pythonhosted.org/packages/e6/b7/639664626e5ba8027860c4d2a639ee02b37e9c322215c921e9222513c3aa/onnx-1.19.0-cp311-cp311-win_arm64.whl", hash = "sha256:23c7959370d7b3236f821e609b0af7763cff7672a758e6c1fc877bac099e786b", size = 16425340, upload-time = "2025-08-27T02:33:17.78Z" }, - { url = "https://files.pythonhosted.org/packages/0d/94/f56f6ca5e2f921b28c0f0476705eab56486b279f04e1d568ed64c14e7764/onnx-1.19.0-cp312-cp312-macosx_12_0_universal2.whl", hash = "sha256:61d94e6498ca636756f8f4ee2135708434601b2892b7c09536befb19bc8ca007", size = 18322331, upload-time = "2025-08-27T02:33:20.373Z" }, - { url = "https://files.pythonhosted.org/packages/c8/00/8cc3f3c40b54b28f96923380f57c9176872e475face726f7d7a78bd74098/onnx-1.19.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:224473354462f005bae985c72028aaa5c85ab11de1b71d55b06fdadd64a667dd", size = 18027513, upload-time = "2025-08-27T02:33:23.44Z" }, - { url = "https://files.pythonhosted.org/packages/61/90/17c4d2566fd0117a5e412688c9525f8950d467f477fbd574e6b32bc9cb8d/onnx-1.19.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ae475c85c89bc4d1f16571006fd21a3e7c0e258dd2c091f6e8aafb083d1ed9b", size = 18202278, upload-time = "2025-08-27T02:33:26.103Z" }, - { url = "https://files.pythonhosted.org/packages/bc/6e/a9383d9cf6db4ac761a129b081e9fa5d0cd89aad43cf1e3fc6285b915c7d/onnx-1.19.0-cp312-cp312-win32.whl", hash = "sha256:323f6a96383a9cdb3960396cffea0a922593d221f3929b17312781e9f9b7fb9f", size = 16333080, upload-time = "2025-08-27T02:33:28.559Z" }, - { url = "https://files.pythonhosted.org/packages/a7/2e/3ff480a8c1fa7939662bdc973e41914add2d4a1f2b8572a3c39c2e4982e5/onnx-1.19.0-cp312-cp312-win_amd64.whl", hash = "sha256:50220f3499a499b1a15e19451a678a58e22ad21b34edf2c844c6ef1d9febddc2", size = 16453927, upload-time = "2025-08-27T02:33:31.177Z" }, - { url = "https://files.pythonhosted.org/packages/57/37/ad500945b1b5c154fe9d7b826b30816ebd629d10211ea82071b5bcc30aa4/onnx-1.19.0-cp312-cp312-win_arm64.whl", hash = "sha256:efb768299580b786e21abe504e1652ae6189f0beed02ab087cd841cb4bb37e43", size = 16426022, upload-time = "2025-08-27T02:33:33.515Z" }, + { url = "https://files.pythonhosted.org/packages/9e/9a/125ad5ed919d1782b26b0b4404e51adc44afd029be30d5a81b446dccd9c5/onnx-1.20.0-cp311-cp311-macosx_12_0_universal2.whl", hash = "sha256:00dc8ae2c7b283f79623961f450b5515bd2c4b47a7027e7a1374ba49cef27768", size = 18341929, upload-time = "2025-12-01T18:13:43.79Z" }, + { url = "https://files.pythonhosted.org/packages/4d/3c/85280dd05396493f3e1b4feb7a3426715e344b36083229437f31d9788a01/onnx-1.20.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f62978ecfb8f320faba6704abd20253a5a79aacc4e5d39a9c061dd63d3b7574f", size = 17899362, upload-time = "2025-12-01T18:13:46.496Z" }, + { url = "https://files.pythonhosted.org/packages/26/db/e11cf9aaa6ccbcd27ea94d321020fef3207cba388bff96111e6431f97d1a/onnx-1.20.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:71177f8fd5c0dd90697bc281f5035f73707bdac83257a5c54d74403a1100ace9", size = 18119129, upload-time = "2025-12-01T18:13:49.662Z" }, + { url = "https://files.pythonhosted.org/packages/ef/0b/1b99e7ba5ccfa8ecb3509ec579c8520098d09b903ccd520026d60faa7c75/onnx-1.20.0-cp311-cp311-win32.whl", hash = "sha256:1d3d0308e2c194f4b782f51e78461b567fac8ce6871c0cf5452ede261683cc8f", size = 16364604, upload-time = "2025-12-01T18:13:52.691Z" }, + { url = "https://files.pythonhosted.org/packages/51/ab/7399817821d0d18ff67292ac183383e41f4f4ddff2047902f1b7b51d2d40/onnx-1.20.0-cp311-cp311-win_amd64.whl", hash = "sha256:3a6de7dda77926c323b0e5a830dc9c2866ce350c1901229e193be1003a076c25", size = 16488019, upload-time = "2025-12-01T18:13:55.776Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e0/23059c11d9c0fb1951acec504a5cc86e1dd03d2eef3a98cf1941839f5322/onnx-1.20.0-cp311-cp311-win_arm64.whl", hash = "sha256:afc4cf83ce5d547ebfbb276dae8eb0ec836254a8698d462b4ba5f51e717fd1ae", size = 16446841, upload-time = "2025-12-01T18:13:58.091Z" }, + { url = "https://files.pythonhosted.org/packages/5e/19/2caa972a31014a8cb4525f715f2a75d93caef9d4b9da2809cc05d0489e43/onnx-1.20.0-cp312-abi3-macosx_12_0_universal2.whl", hash = "sha256:31efe37d7d1d659091f34ddd6a31780334acf7c624176832db9a0a8ececa8fb5", size = 18340913, upload-time = "2025-12-01T18:14:00.477Z" }, + { url = "https://files.pythonhosted.org/packages/78/bb/b98732309f2f6beb4cdcf7b955d7bbfd75a191185370ee21233373db381e/onnx-1.20.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d75da05e743eb9a11ff155a775cae5745e71f1cd0ca26402881b8f20e8d6e449", size = 17896118, upload-time = "2025-12-01T18:14:03.239Z" }, + { url = "https://files.pythonhosted.org/packages/84/a7/38aa564871d062c11538d65c575af9c7e057be880c09ecbd899dd1abfa83/onnx-1.20.0-cp312-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02e0d72ab09a983fce46686b155a5049898558d9f3bc6e8515120d6c40666318", size = 18115415, upload-time = "2025-12-01T18:14:06.261Z" }, + { url = "https://files.pythonhosted.org/packages/3b/17/a600b62cf4ad72976c66f83ce9e324205af434706ad5ec0e35129e125aef/onnx-1.20.0-cp312-abi3-win32.whl", hash = "sha256:392ca68b34b97e172d33b507e1e7bfdf2eea96603e6e7ff109895b82ff009dc7", size = 16363019, upload-time = "2025-12-01T18:14:09.16Z" }, + { url = "https://files.pythonhosted.org/packages/9c/3b/5146ba0a89f73c026bb468c49612bab8d005aa28155ebf06cf5f2eb8d36c/onnx-1.20.0-cp312-abi3-win_amd64.whl", hash = "sha256:259b05758d41645f5545c09f887187662b350d40db8d707c33c94a4f398e1733", size = 16485934, upload-time = "2025-12-01T18:14:13.046Z" }, + { url = "https://files.pythonhosted.org/packages/f3/bc/d251b97395e721b3034e9578d4d4d9fb33aac4197ae16ce8c7ed79a26dce/onnx-1.20.0-cp312-abi3-win_arm64.whl", hash = "sha256:2d25a9e1fde44bc69988e50e2211f62d6afcd01b0fd6dfd23429fd978a35d32f", size = 16444946, upload-time = "2025-12-01T18:14:15.801Z" }, ] [[package]] @@ -1481,8 +1493,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 = [ @@ -1494,8 +1506,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 = [ @@ -1522,48 +1534,48 @@ wheels = [ [[package]] name = "pillow" -version = "11.3.0" +version = "12.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/d0d6dea55cd152ce3d6767bb38a8fc10e33796ba4ba210cbab9354b6d238/pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523", size = 47113069, upload-time = "2025-07-01T09:16:30.666Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/cace85a1b0c9775a9f8f5d5423c8261c858760e2466c79b2dd184638b056/pillow-12.0.0.tar.gz", hash = "sha256:87d4f8125c9988bfbed67af47dd7a953e2fc7b0cc1e7800ec6d2080d490bb353", size = 47008828, upload-time = "2025-10-15T18:24:14.008Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/db/26/77f8ed17ca4ffd60e1dcd220a6ec6d71210ba398cfa33a13a1cd614c5613/pillow-11.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1cd110edf822773368b396281a2293aeb91c90a2db00d78ea43e7e861631b722", size = 5316531, upload-time = "2025-07-01T09:13:59.203Z" }, - { url = "https://files.pythonhosted.org/packages/cb/39/ee475903197ce709322a17a866892efb560f57900d9af2e55f86db51b0a5/pillow-11.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c412fddd1b77a75aa904615ebaa6001f169b26fd467b4be93aded278266b288", size = 4686560, upload-time = "2025-07-01T09:14:01.101Z" }, - { url = "https://files.pythonhosted.org/packages/d5/90/442068a160fd179938ba55ec8c97050a612426fae5ec0a764e345839f76d/pillow-11.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1aa4de119a0ecac0a34a9c8bde33f34022e2e8f99104e47a3ca392fd60e37d", size = 5870978, upload-time = "2025-07-03T13:09:55.638Z" }, - { url = "https://files.pythonhosted.org/packages/13/92/dcdd147ab02daf405387f0218dcf792dc6dd5b14d2573d40b4caeef01059/pillow-11.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:91da1d88226663594e3f6b4b8c3c8d85bd504117d043740a8e0ec449087cc494", size = 7641168, upload-time = "2025-07-03T13:10:00.37Z" }, - { url = "https://files.pythonhosted.org/packages/6e/db/839d6ba7fd38b51af641aa904e2960e7a5644d60ec754c046b7d2aee00e5/pillow-11.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:643f189248837533073c405ec2f0bb250ba54598cf80e8c1e043381a60632f58", size = 5973053, upload-time = "2025-07-01T09:14:04.491Z" }, - { url = "https://files.pythonhosted.org/packages/f2/2f/d7675ecae6c43e9f12aa8d58b6012683b20b6edfbdac7abcb4e6af7a3784/pillow-11.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:106064daa23a745510dabce1d84f29137a37224831d88eb4ce94bb187b1d7e5f", size = 6640273, upload-time = "2025-07-01T09:14:06.235Z" }, - { url = "https://files.pythonhosted.org/packages/45/ad/931694675ede172e15b2ff03c8144a0ddaea1d87adb72bb07655eaffb654/pillow-11.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd8ff254faf15591e724dc7c4ddb6bf4793efcbe13802a4ae3e863cd300b493e", size = 6082043, upload-time = "2025-07-01T09:14:07.978Z" }, - { url = "https://files.pythonhosted.org/packages/3a/04/ba8f2b11fc80d2dd462d7abec16351b45ec99cbbaea4387648a44190351a/pillow-11.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:932c754c2d51ad2b2271fd01c3d121daaa35e27efae2a616f77bf164bc0b3e94", size = 6715516, upload-time = "2025-07-01T09:14:10.233Z" }, - { url = "https://files.pythonhosted.org/packages/48/59/8cd06d7f3944cc7d892e8533c56b0acb68399f640786313275faec1e3b6f/pillow-11.3.0-cp311-cp311-win32.whl", hash = "sha256:b4b8f3efc8d530a1544e5962bd6b403d5f7fe8b9e08227c6b255f98ad82b4ba0", size = 6274768, upload-time = "2025-07-01T09:14:11.921Z" }, - { url = "https://files.pythonhosted.org/packages/f1/cc/29c0f5d64ab8eae20f3232da8f8571660aa0ab4b8f1331da5c2f5f9a938e/pillow-11.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:1a992e86b0dd7aeb1f053cd506508c0999d710a8f07b4c791c63843fc6a807ac", size = 6986055, upload-time = "2025-07-01T09:14:13.623Z" }, - { url = "https://files.pythonhosted.org/packages/c6/df/90bd886fabd544c25addd63e5ca6932c86f2b701d5da6c7839387a076b4a/pillow-11.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:30807c931ff7c095620fe04448e2c2fc673fcbb1ffe2a7da3fb39613489b1ddd", size = 2423079, upload-time = "2025-07-01T09:14:15.268Z" }, - { url = "https://files.pythonhosted.org/packages/40/fe/1bc9b3ee13f68487a99ac9529968035cca2f0a51ec36892060edcc51d06a/pillow-11.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdae223722da47b024b867c1ea0be64e0df702c5e0a60e27daad39bf960dd1e4", size = 5278800, upload-time = "2025-07-01T09:14:17.648Z" }, - { url = "https://files.pythonhosted.org/packages/2c/32/7e2ac19b5713657384cec55f89065fb306b06af008cfd87e572035b27119/pillow-11.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:921bd305b10e82b4d1f5e802b6850677f965d8394203d182f078873851dada69", size = 4686296, upload-time = "2025-07-01T09:14:19.828Z" }, - { url = "https://files.pythonhosted.org/packages/8e/1e/b9e12bbe6e4c2220effebc09ea0923a07a6da1e1f1bfbc8d7d29a01ce32b/pillow-11.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb76541cba2f958032d79d143b98a3a6b3ea87f0959bbe256c0b5e416599fd5d", size = 5871726, upload-time = "2025-07-03T13:10:04.448Z" }, - { url = "https://files.pythonhosted.org/packages/8d/33/e9200d2bd7ba00dc3ddb78df1198a6e80d7669cce6c2bdbeb2530a74ec58/pillow-11.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:67172f2944ebba3d4a7b54f2e95c786a3a50c21b88456329314caaa28cda70f6", size = 7644652, upload-time = "2025-07-03T13:10:10.391Z" }, - { url = "https://files.pythonhosted.org/packages/41/f1/6f2427a26fc683e00d985bc391bdd76d8dd4e92fac33d841127eb8fb2313/pillow-11.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f07ed9f56a3b9b5f49d3661dc9607484e85c67e27f3e8be2c7d28ca032fec7", size = 5977787, upload-time = "2025-07-01T09:14:21.63Z" }, - { url = "https://files.pythonhosted.org/packages/e4/c9/06dd4a38974e24f932ff5f98ea3c546ce3f8c995d3f0985f8e5ba48bba19/pillow-11.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:676b2815362456b5b3216b4fd5bd89d362100dc6f4945154ff172e206a22c024", size = 6645236, upload-time = "2025-07-01T09:14:23.321Z" }, - { url = "https://files.pythonhosted.org/packages/40/e7/848f69fb79843b3d91241bad658e9c14f39a32f71a301bcd1d139416d1be/pillow-11.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3e184b2f26ff146363dd07bde8b711833d7b0202e27d13540bfe2e35a323a809", size = 6086950, upload-time = "2025-07-01T09:14:25.237Z" }, - { url = "https://files.pythonhosted.org/packages/0b/1a/7cff92e695a2a29ac1958c2a0fe4c0b2393b60aac13b04a4fe2735cad52d/pillow-11.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6be31e3fc9a621e071bc17bb7de63b85cbe0bfae91bb0363c893cbe67247780d", size = 6723358, upload-time = "2025-07-01T09:14:27.053Z" }, - { url = "https://files.pythonhosted.org/packages/26/7d/73699ad77895f69edff76b0f332acc3d497f22f5d75e5360f78cbcaff248/pillow-11.3.0-cp312-cp312-win32.whl", hash = "sha256:7b161756381f0918e05e7cb8a371fff367e807770f8fe92ecb20d905d0e1c149", size = 6275079, upload-time = "2025-07-01T09:14:30.104Z" }, - { url = "https://files.pythonhosted.org/packages/8c/ce/e7dfc873bdd9828f3b6e5c2bbb74e47a98ec23cc5c74fc4e54462f0d9204/pillow-11.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a6444696fce635783440b7f7a9fc24b3ad10a9ea3f0ab66c5905be1c19ccf17d", size = 6986324, upload-time = "2025-07-01T09:14:31.899Z" }, - { url = "https://files.pythonhosted.org/packages/16/8f/b13447d1bf0b1f7467ce7d86f6e6edf66c0ad7cf44cf5c87a37f9bed9936/pillow-11.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:2aceea54f957dd4448264f9bf40875da0415c83eb85f55069d89c0ed436e3542", size = 2423067, upload-time = "2025-07-01T09:14:33.709Z" }, - { url = "https://files.pythonhosted.org/packages/9e/e3/6fa84033758276fb31da12e5fb66ad747ae83b93c67af17f8c6ff4cc8f34/pillow-11.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7c8ec7a017ad1bd562f93dbd8505763e688d388cde6e4a010ae1486916e713e6", size = 5270566, upload-time = "2025-07-01T09:16:19.801Z" }, - { url = "https://files.pythonhosted.org/packages/5b/ee/e8d2e1ab4892970b561e1ba96cbd59c0d28cf66737fc44abb2aec3795a4e/pillow-11.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9ab6ae226de48019caa8074894544af5b53a117ccb9d3b3dcb2871464c829438", size = 4654618, upload-time = "2025-07-01T09:16:21.818Z" }, - { url = "https://files.pythonhosted.org/packages/f2/6d/17f80f4e1f0761f02160fc433abd4109fa1548dcfdca46cfdadaf9efa565/pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fe27fb049cdcca11f11a7bfda64043c37b30e6b91f10cb5bab275806c32f6ab3", size = 4874248, upload-time = "2025-07-03T13:11:20.738Z" }, - { url = "https://files.pythonhosted.org/packages/de/5f/c22340acd61cef960130585bbe2120e2fd8434c214802f07e8c03596b17e/pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:465b9e8844e3c3519a983d58b80be3f668e2a7a5db97f2784e7079fbc9f9822c", size = 6583963, upload-time = "2025-07-03T13:11:26.283Z" }, - { url = "https://files.pythonhosted.org/packages/31/5e/03966aedfbfcbb4d5f8aa042452d3361f325b963ebbadddac05b122e47dd/pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5418b53c0d59b3824d05e029669efa023bbef0f3e92e75ec8428f3799487f361", size = 4957170, upload-time = "2025-07-01T09:16:23.762Z" }, - { url = "https://files.pythonhosted.org/packages/cc/2d/e082982aacc927fc2cab48e1e731bdb1643a1406acace8bed0900a61464e/pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:504b6f59505f08ae014f724b6207ff6222662aab5cc9542577fb084ed0676ac7", size = 5581505, upload-time = "2025-07-01T09:16:25.593Z" }, - { url = "https://files.pythonhosted.org/packages/34/e7/ae39f538fd6844e982063c3a5e4598b8ced43b9633baa3a85ef33af8c05c/pillow-11.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c84d689db21a1c397d001aa08241044aa2069e7587b398c8cc63020390b1c1b8", size = 6984598, upload-time = "2025-07-01T09:16:27.732Z" }, + { url = "https://files.pythonhosted.org/packages/0e/5a/a2f6773b64edb921a756eb0729068acad9fc5208a53f4a349396e9436721/pillow-12.0.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0fd00cac9c03256c8b2ff58f162ebcd2587ad3e1f2e397eab718c47e24d231cc", size = 5289798, upload-time = "2025-10-15T18:21:47.763Z" }, + { url = "https://files.pythonhosted.org/packages/2e/05/069b1f8a2e4b5a37493da6c5868531c3f77b85e716ad7a590ef87d58730d/pillow-12.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3475b96f5908b3b16c47533daaa87380c491357d197564e0ba34ae75c0f3257", size = 4650589, upload-time = "2025-10-15T18:21:49.515Z" }, + { url = "https://files.pythonhosted.org/packages/61/e3/2c820d6e9a36432503ead175ae294f96861b07600a7156154a086ba7111a/pillow-12.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:110486b79f2d112cf6add83b28b627e369219388f64ef2f960fef9ebaf54c642", size = 6230472, upload-time = "2025-10-15T18:21:51.052Z" }, + { url = "https://files.pythonhosted.org/packages/4f/89/63427f51c64209c5e23d4d52071c8d0f21024d3a8a487737caaf614a5795/pillow-12.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5269cc1caeedb67e6f7269a42014f381f45e2e7cd42d834ede3c703a1d915fe3", size = 8033887, upload-time = "2025-10-15T18:21:52.604Z" }, + { url = "https://files.pythonhosted.org/packages/f6/1b/c9711318d4901093c15840f268ad649459cd81984c9ec9887756cca049a5/pillow-12.0.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa5129de4e174daccbc59d0a3b6d20eaf24417d59851c07ebb37aeb02947987c", size = 6343964, upload-time = "2025-10-15T18:21:54.619Z" }, + { url = "https://files.pythonhosted.org/packages/41/1e/db9470f2d030b4995083044cd8738cdd1bf773106819f6d8ba12597d5352/pillow-12.0.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bee2a6db3a7242ea309aa7ee8e2780726fed67ff4e5b40169f2c940e7eb09227", size = 7034756, upload-time = "2025-10-15T18:21:56.151Z" }, + { url = "https://files.pythonhosted.org/packages/cc/b0/6177a8bdd5ee4ed87cba2de5a3cc1db55ffbbec6176784ce5bb75aa96798/pillow-12.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:90387104ee8400a7b4598253b4c406f8958f59fcf983a6cea2b50d59f7d63d0b", size = 6458075, upload-time = "2025-10-15T18:21:57.759Z" }, + { url = "https://files.pythonhosted.org/packages/bc/5e/61537aa6fa977922c6a03253a0e727e6e4a72381a80d63ad8eec350684f2/pillow-12.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bc91a56697869546d1b8f0a3ff35224557ae7f881050e99f615e0119bf934b4e", size = 7125955, upload-time = "2025-10-15T18:21:59.372Z" }, + { url = "https://files.pythonhosted.org/packages/1f/3d/d5033539344ee3cbd9a4d69e12e63ca3a44a739eb2d4c8da350a3d38edd7/pillow-12.0.0-cp311-cp311-win32.whl", hash = "sha256:27f95b12453d165099c84f8a8bfdfd46b9e4bda9e0e4b65f0635430027f55739", size = 6298440, upload-time = "2025-10-15T18:22:00.982Z" }, + { url = "https://files.pythonhosted.org/packages/4d/42/aaca386de5cc8bd8a0254516957c1f265e3521c91515b16e286c662854c4/pillow-12.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:b583dc9070312190192631373c6c8ed277254aa6e6084b74bdd0a6d3b221608e", size = 6999256, upload-time = "2025-10-15T18:22:02.617Z" }, + { url = "https://files.pythonhosted.org/packages/ba/f1/9197c9c2d5708b785f631a6dfbfa8eb3fb9672837cb92ae9af812c13b4ed/pillow-12.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:759de84a33be3b178a64c8ba28ad5c135900359e85fb662bc6e403ad4407791d", size = 2436025, upload-time = "2025-10-15T18:22:04.598Z" }, + { url = "https://files.pythonhosted.org/packages/2c/90/4fcce2c22caf044e660a198d740e7fbc14395619e3cb1abad12192c0826c/pillow-12.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:53561a4ddc36facb432fae7a9d8afbfaf94795414f5cdc5fc52f28c1dca90371", size = 5249377, upload-time = "2025-10-15T18:22:05.993Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e0/ed960067543d080691d47d6938ebccbf3976a931c9567ab2fbfab983a5dd/pillow-12.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:71db6b4c1653045dacc1585c1b0d184004f0d7e694c7b34ac165ca70c0838082", size = 4650343, upload-time = "2025-10-15T18:22:07.718Z" }, + { url = "https://files.pythonhosted.org/packages/e7/a1/f81fdeddcb99c044bf7d6faa47e12850f13cee0849537a7d27eeab5534d4/pillow-12.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2fa5f0b6716fc88f11380b88b31fe591a06c6315e955c096c35715788b339e3f", size = 6232981, upload-time = "2025-10-15T18:22:09.287Z" }, + { url = "https://files.pythonhosted.org/packages/88/e1/9098d3ce341a8750b55b0e00c03f1630d6178f38ac191c81c97a3b047b44/pillow-12.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:82240051c6ca513c616f7f9da06e871f61bfd7805f566275841af15015b8f98d", size = 8041399, upload-time = "2025-10-15T18:22:10.872Z" }, + { url = "https://files.pythonhosted.org/packages/a7/62/a22e8d3b602ae8cc01446d0c57a54e982737f44b6f2e1e019a925143771d/pillow-12.0.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55f818bd74fe2f11d4d7cbc65880a843c4075e0ac7226bc1a23261dbea531953", size = 6347740, upload-time = "2025-10-15T18:22:12.769Z" }, + { url = "https://files.pythonhosted.org/packages/4f/87/424511bdcd02c8d7acf9f65caa09f291a519b16bd83c3fb3374b3d4ae951/pillow-12.0.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b87843e225e74576437fd5b6a4c2205d422754f84a06942cfaf1dc32243e45a8", size = 7040201, upload-time = "2025-10-15T18:22:14.813Z" }, + { url = "https://files.pythonhosted.org/packages/dc/4d/435c8ac688c54d11755aedfdd9f29c9eeddf68d150fe42d1d3dbd2365149/pillow-12.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c607c90ba67533e1b2355b821fef6764d1dd2cbe26b8c1005ae84f7aea25ff79", size = 6462334, upload-time = "2025-10-15T18:22:16.375Z" }, + { url = "https://files.pythonhosted.org/packages/2b/f2/ad34167a8059a59b8ad10bc5c72d4d9b35acc6b7c0877af8ac885b5f2044/pillow-12.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:21f241bdd5080a15bc86d3466a9f6074a9c2c2b314100dd896ac81ee6db2f1ba", size = 7134162, upload-time = "2025-10-15T18:22:17.996Z" }, + { url = "https://files.pythonhosted.org/packages/0c/b1/a7391df6adacf0a5c2cf6ac1cf1fcc1369e7d439d28f637a847f8803beb3/pillow-12.0.0-cp312-cp312-win32.whl", hash = "sha256:dd333073e0cacdc3089525c7df7d39b211bcdf31fc2824e49d01c6b6187b07d0", size = 6298769, upload-time = "2025-10-15T18:22:19.923Z" }, + { url = "https://files.pythonhosted.org/packages/a2/0b/d87733741526541c909bbf159e338dcace4f982daac6e5a8d6be225ca32d/pillow-12.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:9fe611163f6303d1619bbcb653540a4d60f9e55e622d60a3108be0d5b441017a", size = 7001107, upload-time = "2025-10-15T18:22:21.644Z" }, + { url = "https://files.pythonhosted.org/packages/bc/96/aaa61ce33cc98421fb6088af2a03be4157b1e7e0e87087c888e2370a7f45/pillow-12.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:7dfb439562f234f7d57b1ac6bc8fe7f838a4bd49c79230e0f6a1da93e82f1fad", size = 2436012, upload-time = "2025-10-15T18:22:23.621Z" }, + { url = "https://files.pythonhosted.org/packages/1d/b3/582327e6c9f86d037b63beebe981425d6811104cb443e8193824ef1a2f27/pillow-12.0.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b22bd8c974942477156be55a768f7aa37c46904c175be4e158b6a86e3a6b7ca8", size = 5215068, upload-time = "2025-10-15T18:23:59.594Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d6/67748211d119f3b6540baf90f92fae73ae51d5217b171b0e8b5f7e5d558f/pillow-12.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:805ebf596939e48dbb2e4922a1d3852cfc25c38160751ce02da93058b48d252a", size = 4614994, upload-time = "2025-10-15T18:24:01.669Z" }, + { url = "https://files.pythonhosted.org/packages/2d/e1/f8281e5d844c41872b273b9f2c34a4bf64ca08905668c8ae730eedc7c9fa/pillow-12.0.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cae81479f77420d217def5f54b5b9d279804d17e982e0f2fa19b1d1e14ab5197", size = 5246639, upload-time = "2025-10-15T18:24:03.403Z" }, + { url = "https://files.pythonhosted.org/packages/94/5a/0d8ab8ffe8a102ff5df60d0de5af309015163bf710c7bb3e8311dd3b3ad0/pillow-12.0.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aeaefa96c768fc66818730b952a862235d68825c178f1b3ffd4efd7ad2edcb7c", size = 6986839, upload-time = "2025-10-15T18:24:05.344Z" }, + { url = "https://files.pythonhosted.org/packages/20/2e/3434380e8110b76cd9eb00a363c484b050f949b4bbe84ba770bb8508a02c/pillow-12.0.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09f2d0abef9e4e2f349305a4f8cc784a8a6c2f58a8c4892eea13b10a943bd26e", size = 5313505, upload-time = "2025-10-15T18:24:07.137Z" }, + { url = "https://files.pythonhosted.org/packages/57/ca/5a9d38900d9d74785141d6580950fe705de68af735ff6e727cb911b64740/pillow-12.0.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bdee52571a343d721fb2eb3b090a82d959ff37fc631e3f70422e0c2e029f3e76", size = 5963654, upload-time = "2025-10-15T18:24:09.579Z" }, + { url = "https://files.pythonhosted.org/packages/95/7e/f896623c3c635a90537ac093c6a618ebe1a90d87206e42309cb5d98a1b9e/pillow-12.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:b290fd8aa38422444d4b50d579de197557f182ef1068b75f5aa8558638b8d0a5", size = 6997850, upload-time = "2025-10-15T18:24:11.495Z" }, ] [[package]] name = "platformdirs" -version = "4.4.0" +version = "4.5.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/23/e8/21db9c9987b0e728855bd57bff6984f67952bea55d6f75e055c46b5383e8/platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf", size = 21634, upload-time = "2025-08-26T14:32:04.268Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cf/86/0248f086a84f01b37aaec0fa567b397df1a119f73c16f6c7a9aac73ea309/platformdirs-4.5.1.tar.gz", hash = "sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda", size = 21715, upload-time = "2025-12-05T13:52:58.638Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/40/4b/2028861e724d3bd36227adfa20d3fd24c3fc6d52032f4a93c133be5d17ce/platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85", size = 18654, upload-time = "2025-08-26T14:32:02.735Z" }, + { url = "https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31", size = 18731, upload-time = "2025-12-05T13:52:56.823Z" }, ] [[package]] @@ -1595,73 +1607,72 @@ sdist = { url = "https://files.pythonhosted.org/packages/a3/a6/b8e451f6cff1c99b4 [[package]] name = "propcache" -version = "0.3.2" +version = "0.4.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a6/16/43264e4a779dd8588c21a70f0709665ee8f611211bdd2c87d952cfa7c776/propcache-0.3.2.tar.gz", hash = "sha256:20d7d62e4e7ef05f221e0db2856b979540686342e7dd9973b815599c7057e168", size = 44139, upload-time = "2025-06-09T22:56:06.081Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9e/da/e9fc233cf63743258bff22b3dfa7ea5baef7b5bc324af47a0ad89b8ffc6f/propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d", size = 46442, upload-time = "2025-10-08T19:49:02.291Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/80/8d/e8b436717ab9c2cfc23b116d2c297305aa4cd8339172a456d61ebf5669b8/propcache-0.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0b8d2f607bd8f80ddc04088bc2a037fdd17884a6fcadc47a96e334d72f3717be", size = 74207, upload-time = "2025-06-09T22:54:05.399Z" }, - { url = "https://files.pythonhosted.org/packages/d6/29/1e34000e9766d112171764b9fa3226fa0153ab565d0c242c70e9945318a7/propcache-0.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06766d8f34733416e2e34f46fea488ad5d60726bb9481d3cddf89a6fa2d9603f", size = 43648, upload-time = "2025-06-09T22:54:08.023Z" }, - { url = "https://files.pythonhosted.org/packages/46/92/1ad5af0df781e76988897da39b5f086c2bf0f028b7f9bd1f409bb05b6874/propcache-0.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2dc1f4a1df4fecf4e6f68013575ff4af84ef6f478fe5344317a65d38a8e6dc9", size = 43496, upload-time = "2025-06-09T22:54:09.228Z" }, - { url = "https://files.pythonhosted.org/packages/b3/ce/e96392460f9fb68461fabab3e095cb00c8ddf901205be4eae5ce246e5b7e/propcache-0.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be29c4f4810c5789cf10ddf6af80b041c724e629fa51e308a7a0fb19ed1ef7bf", size = 217288, upload-time = "2025-06-09T22:54:10.466Z" }, - { url = "https://files.pythonhosted.org/packages/c5/2a/866726ea345299f7ceefc861a5e782b045545ae6940851930a6adaf1fca6/propcache-0.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59d61f6970ecbd8ff2e9360304d5c8876a6abd4530cb752c06586849ac8a9dc9", size = 227456, upload-time = "2025-06-09T22:54:11.828Z" }, - { url = "https://files.pythonhosted.org/packages/de/03/07d992ccb6d930398689187e1b3c718339a1c06b8b145a8d9650e4726166/propcache-0.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:62180e0b8dbb6b004baec00a7983e4cc52f5ada9cd11f48c3528d8cfa7b96a66", size = 225429, upload-time = "2025-06-09T22:54:13.823Z" }, - { url = "https://files.pythonhosted.org/packages/5d/e6/116ba39448753b1330f48ab8ba927dcd6cf0baea8a0ccbc512dfb49ba670/propcache-0.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c144ca294a204c470f18cf4c9d78887810d04a3e2fbb30eea903575a779159df", size = 213472, upload-time = "2025-06-09T22:54:15.232Z" }, - { url = "https://files.pythonhosted.org/packages/a6/85/f01f5d97e54e428885a5497ccf7f54404cbb4f906688a1690cd51bf597dc/propcache-0.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5c2a784234c28854878d68978265617aa6dc0780e53d44b4d67f3651a17a9a2", size = 204480, upload-time = "2025-06-09T22:54:17.104Z" }, - { url = "https://files.pythonhosted.org/packages/e3/79/7bf5ab9033b8b8194cc3f7cf1aaa0e9c3256320726f64a3e1f113a812dce/propcache-0.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5745bc7acdafa978ca1642891b82c19238eadc78ba2aaa293c6863b304e552d7", size = 214530, upload-time = "2025-06-09T22:54:18.512Z" }, - { url = "https://files.pythonhosted.org/packages/31/0b/bd3e0c00509b609317df4a18e6b05a450ef2d9a963e1d8bc9c9415d86f30/propcache-0.3.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:c0075bf773d66fa8c9d41f66cc132ecc75e5bb9dd7cce3cfd14adc5ca184cb95", size = 205230, upload-time = "2025-06-09T22:54:19.947Z" }, - { url = "https://files.pythonhosted.org/packages/7a/23/fae0ff9b54b0de4e819bbe559508da132d5683c32d84d0dc2ccce3563ed4/propcache-0.3.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5f57aa0847730daceff0497f417c9de353c575d8da3579162cc74ac294c5369e", size = 206754, upload-time = "2025-06-09T22:54:21.716Z" }, - { url = "https://files.pythonhosted.org/packages/b7/7f/ad6a3c22630aaa5f618b4dc3c3598974a72abb4c18e45a50b3cdd091eb2f/propcache-0.3.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:eef914c014bf72d18efb55619447e0aecd5fb7c2e3fa7441e2e5d6099bddff7e", size = 218430, upload-time = "2025-06-09T22:54:23.17Z" }, - { url = "https://files.pythonhosted.org/packages/5b/2c/ba4f1c0e8a4b4c75910742f0d333759d441f65a1c7f34683b4a74c0ee015/propcache-0.3.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2a4092e8549031e82facf3decdbc0883755d5bbcc62d3aea9d9e185549936dcf", size = 223884, upload-time = "2025-06-09T22:54:25.539Z" }, - { url = "https://files.pythonhosted.org/packages/88/e4/ebe30fc399e98572019eee82ad0caf512401661985cbd3da5e3140ffa1b0/propcache-0.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:85871b050f174bc0bfb437efbdb68aaf860611953ed12418e4361bc9c392749e", size = 211480, upload-time = "2025-06-09T22:54:26.892Z" }, - { url = "https://files.pythonhosted.org/packages/96/0a/7d5260b914e01d1d0906f7f38af101f8d8ed0dc47426219eeaf05e8ea7c2/propcache-0.3.2-cp311-cp311-win32.whl", hash = "sha256:36c8d9b673ec57900c3554264e630d45980fd302458e4ac801802a7fd2ef7897", size = 37757, upload-time = "2025-06-09T22:54:28.241Z" }, - { url = "https://files.pythonhosted.org/packages/e1/2d/89fe4489a884bc0da0c3278c552bd4ffe06a1ace559db5ef02ef24ab446b/propcache-0.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53af8cb6a781b02d2ea079b5b853ba9430fcbe18a8e3ce647d5982a3ff69f39", size = 41500, upload-time = "2025-06-09T22:54:29.4Z" }, - { url = "https://files.pythonhosted.org/packages/a8/42/9ca01b0a6f48e81615dca4765a8f1dd2c057e0540f6116a27dc5ee01dfb6/propcache-0.3.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8de106b6c84506b31c27168582cd3cb3000a6412c16df14a8628e5871ff83c10", size = 73674, upload-time = "2025-06-09T22:54:30.551Z" }, - { url = "https://files.pythonhosted.org/packages/af/6e/21293133beb550f9c901bbece755d582bfaf2176bee4774000bd4dd41884/propcache-0.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:28710b0d3975117239c76600ea351934ac7b5ff56e60953474342608dbbb6154", size = 43570, upload-time = "2025-06-09T22:54:32.296Z" }, - { url = "https://files.pythonhosted.org/packages/0c/c8/0393a0a3a2b8760eb3bde3c147f62b20044f0ddac81e9d6ed7318ec0d852/propcache-0.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce26862344bdf836650ed2487c3d724b00fbfec4233a1013f597b78c1cb73615", size = 43094, upload-time = "2025-06-09T22:54:33.929Z" }, - { url = "https://files.pythonhosted.org/packages/37/2c/489afe311a690399d04a3e03b069225670c1d489eb7b044a566511c1c498/propcache-0.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bca54bd347a253af2cf4544bbec232ab982f4868de0dd684246b67a51bc6b1db", size = 226958, upload-time = "2025-06-09T22:54:35.186Z" }, - { url = "https://files.pythonhosted.org/packages/9d/ca/63b520d2f3d418c968bf596839ae26cf7f87bead026b6192d4da6a08c467/propcache-0.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55780d5e9a2ddc59711d727226bb1ba83a22dd32f64ee15594b9392b1f544eb1", size = 234894, upload-time = "2025-06-09T22:54:36.708Z" }, - { url = "https://files.pythonhosted.org/packages/11/60/1d0ed6fff455a028d678df30cc28dcee7af77fa2b0e6962ce1df95c9a2a9/propcache-0.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:035e631be25d6975ed87ab23153db6a73426a48db688070d925aa27e996fe93c", size = 233672, upload-time = "2025-06-09T22:54:38.062Z" }, - { url = "https://files.pythonhosted.org/packages/37/7c/54fd5301ef38505ab235d98827207176a5c9b2aa61939b10a460ca53e123/propcache-0.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee6f22b6eaa39297c751d0e80c0d3a454f112f5c6481214fcf4c092074cecd67", size = 224395, upload-time = "2025-06-09T22:54:39.634Z" }, - { url = "https://files.pythonhosted.org/packages/ee/1a/89a40e0846f5de05fdc6779883bf46ba980e6df4d2ff8fb02643de126592/propcache-0.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ca3aee1aa955438c4dba34fc20a9f390e4c79967257d830f137bd5a8a32ed3b", size = 212510, upload-time = "2025-06-09T22:54:41.565Z" }, - { url = "https://files.pythonhosted.org/packages/5e/33/ca98368586c9566a6b8d5ef66e30484f8da84c0aac3f2d9aec6d31a11bd5/propcache-0.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7a4f30862869fa2b68380d677cc1c5fcf1e0f2b9ea0cf665812895c75d0ca3b8", size = 222949, upload-time = "2025-06-09T22:54:43.038Z" }, - { url = "https://files.pythonhosted.org/packages/ba/11/ace870d0aafe443b33b2f0b7efdb872b7c3abd505bfb4890716ad7865e9d/propcache-0.3.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b77ec3c257d7816d9f3700013639db7491a434644c906a2578a11daf13176251", size = 217258, upload-time = "2025-06-09T22:54:44.376Z" }, - { url = "https://files.pythonhosted.org/packages/5b/d2/86fd6f7adffcfc74b42c10a6b7db721d1d9ca1055c45d39a1a8f2a740a21/propcache-0.3.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cab90ac9d3f14b2d5050928483d3d3b8fb6b4018893fc75710e6aa361ecb2474", size = 213036, upload-time = "2025-06-09T22:54:46.243Z" }, - { url = "https://files.pythonhosted.org/packages/07/94/2d7d1e328f45ff34a0a284cf5a2847013701e24c2a53117e7c280a4316b3/propcache-0.3.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0b504d29f3c47cf6b9e936c1852246c83d450e8e063d50562115a6be6d3a2535", size = 227684, upload-time = "2025-06-09T22:54:47.63Z" }, - { url = "https://files.pythonhosted.org/packages/b7/05/37ae63a0087677e90b1d14710e532ff104d44bc1efa3b3970fff99b891dc/propcache-0.3.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:ce2ac2675a6aa41ddb2a0c9cbff53780a617ac3d43e620f8fd77ba1c84dcfc06", size = 234562, upload-time = "2025-06-09T22:54:48.982Z" }, - { url = "https://files.pythonhosted.org/packages/a4/7c/3f539fcae630408d0bd8bf3208b9a647ccad10976eda62402a80adf8fc34/propcache-0.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:62b4239611205294cc433845b914131b2a1f03500ff3c1ed093ed216b82621e1", size = 222142, upload-time = "2025-06-09T22:54:50.424Z" }, - { url = "https://files.pythonhosted.org/packages/7c/d2/34b9eac8c35f79f8a962546b3e97e9d4b990c420ee66ac8255d5d9611648/propcache-0.3.2-cp312-cp312-win32.whl", hash = "sha256:df4a81b9b53449ebc90cc4deefb052c1dd934ba85012aa912c7ea7b7e38b60c1", size = 37711, upload-time = "2025-06-09T22:54:52.072Z" }, - { url = "https://files.pythonhosted.org/packages/19/61/d582be5d226cf79071681d1b46b848d6cb03d7b70af7063e33a2787eaa03/propcache-0.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7046e79b989d7fe457bb755844019e10f693752d169076138abf17f31380800c", size = 41479, upload-time = "2025-06-09T22:54:53.234Z" }, - { url = "https://files.pythonhosted.org/packages/cc/35/cc0aaecf278bb4575b8555f2b137de5ab821595ddae9da9d3cd1da4072c7/propcache-0.3.2-py3-none-any.whl", hash = "sha256:98f1ec44fb675f5052cccc8e609c46ed23a35a1cfd18545ad4e29002d858a43f", size = 12663, upload-time = "2025-06-09T22:56:04.484Z" }, + { url = "https://files.pythonhosted.org/packages/8c/d4/4e2c9aaf7ac2242b9358f98dccd8f90f2605402f5afeff6c578682c2c491/propcache-0.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:60a8fda9644b7dfd5dece8c61d8a85e271cb958075bfc4e01083c148b61a7caf", size = 80208, upload-time = "2025-10-08T19:46:24.597Z" }, + { url = "https://files.pythonhosted.org/packages/c2/21/d7b68e911f9c8e18e4ae43bdbc1e1e9bbd971f8866eb81608947b6f585ff/propcache-0.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c30b53e7e6bda1d547cabb47c825f3843a0a1a42b0496087bb58d8fedf9f41b5", size = 45777, upload-time = "2025-10-08T19:46:25.733Z" }, + { url = "https://files.pythonhosted.org/packages/d3/1d/11605e99ac8ea9435651ee71ab4cb4bf03f0949586246476a25aadfec54a/propcache-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6918ecbd897443087a3b7cd978d56546a812517dcaaca51b49526720571fa93e", size = 47647, upload-time = "2025-10-08T19:46:27.304Z" }, + { url = "https://files.pythonhosted.org/packages/58/1a/3c62c127a8466c9c843bccb503d40a273e5cc69838805f322e2826509e0d/propcache-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3d902a36df4e5989763425a8ab9e98cd8ad5c52c823b34ee7ef307fd50582566", size = 214929, upload-time = "2025-10-08T19:46:28.62Z" }, + { url = "https://files.pythonhosted.org/packages/56/b9/8fa98f850960b367c4b8fe0592e7fc341daa7a9462e925228f10a60cf74f/propcache-0.4.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a9695397f85973bb40427dedddf70d8dc4a44b22f1650dd4af9eedf443d45165", size = 221778, upload-time = "2025-10-08T19:46:30.358Z" }, + { url = "https://files.pythonhosted.org/packages/46/a6/0ab4f660eb59649d14b3d3d65c439421cf2f87fe5dd68591cbe3c1e78a89/propcache-0.4.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2bb07ffd7eaad486576430c89f9b215f9e4be68c4866a96e97db9e97fead85dc", size = 228144, upload-time = "2025-10-08T19:46:32.607Z" }, + { url = "https://files.pythonhosted.org/packages/52/6a/57f43e054fb3d3a56ac9fc532bc684fc6169a26c75c353e65425b3e56eef/propcache-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd6f30fdcf9ae2a70abd34da54f18da086160e4d7d9251f81f3da0ff84fc5a48", size = 210030, upload-time = "2025-10-08T19:46:33.969Z" }, + { url = "https://files.pythonhosted.org/packages/40/e2/27e6feebb5f6b8408fa29f5efbb765cd54c153ac77314d27e457a3e993b7/propcache-0.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fc38cba02d1acba4e2869eef1a57a43dfbd3d49a59bf90dda7444ec2be6a5570", size = 208252, upload-time = "2025-10-08T19:46:35.309Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f8/91c27b22ccda1dbc7967f921c42825564fa5336a01ecd72eb78a9f4f53c2/propcache-0.4.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:67fad6162281e80e882fb3ec355398cf72864a54069d060321f6cd0ade95fe85", size = 202064, upload-time = "2025-10-08T19:46:36.993Z" }, + { url = "https://files.pythonhosted.org/packages/f2/26/7f00bd6bd1adba5aafe5f4a66390f243acab58eab24ff1a08bebb2ef9d40/propcache-0.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f10207adf04d08bec185bae14d9606a1444715bc99180f9331c9c02093e1959e", size = 212429, upload-time = "2025-10-08T19:46:38.398Z" }, + { url = "https://files.pythonhosted.org/packages/84/89/fd108ba7815c1117ddca79c228f3f8a15fc82a73bca8b142eb5de13b2785/propcache-0.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e9b0d8d0845bbc4cfcdcbcdbf5086886bc8157aa963c31c777ceff7846c77757", size = 216727, upload-time = "2025-10-08T19:46:39.732Z" }, + { url = "https://files.pythonhosted.org/packages/79/37/3ec3f7e3173e73f1d600495d8b545b53802cbf35506e5732dd8578db3724/propcache-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:981333cb2f4c1896a12f4ab92a9cc8f09ea664e9b7dbdc4eff74627af3a11c0f", size = 205097, upload-time = "2025-10-08T19:46:41.025Z" }, + { url = "https://files.pythonhosted.org/packages/61/b0/b2631c19793f869d35f47d5a3a56fb19e9160d3c119f15ac7344fc3ccae7/propcache-0.4.1-cp311-cp311-win32.whl", hash = "sha256:f1d2f90aeec838a52f1c1a32fe9a619fefd5e411721a9117fbf82aea638fe8a1", size = 38084, upload-time = "2025-10-08T19:46:42.693Z" }, + { url = "https://files.pythonhosted.org/packages/f4/78/6cce448e2098e9f3bfc91bb877f06aa24b6ccace872e39c53b2f707c4648/propcache-0.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:364426a62660f3f699949ac8c621aad6977be7126c5807ce48c0aeb8e7333ea6", size = 41637, upload-time = "2025-10-08T19:46:43.778Z" }, + { url = "https://files.pythonhosted.org/packages/9c/e9/754f180cccd7f51a39913782c74717c581b9cc8177ad0e949f4d51812383/propcache-0.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:e53f3a38d3510c11953f3e6a33f205c6d1b001129f972805ca9b42fc308bc239", size = 38064, upload-time = "2025-10-08T19:46:44.872Z" }, + { url = "https://files.pythonhosted.org/packages/a2/0f/f17b1b2b221d5ca28b4b876e8bb046ac40466513960646bda8e1853cdfa2/propcache-0.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e153e9cd40cc8945138822807139367f256f89c6810c2634a4f6902b52d3b4e2", size = 80061, upload-time = "2025-10-08T19:46:46.075Z" }, + { url = "https://files.pythonhosted.org/packages/76/47/8ccf75935f51448ba9a16a71b783eb7ef6b9ee60f5d14c7f8a8a79fbeed7/propcache-0.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cd547953428f7abb73c5ad82cbb32109566204260d98e41e5dfdc682eb7f8403", size = 46037, upload-time = "2025-10-08T19:46:47.23Z" }, + { url = "https://files.pythonhosted.org/packages/0a/b6/5c9a0e42df4d00bfb4a3cbbe5cf9f54260300c88a0e9af1f47ca5ce17ac0/propcache-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f048da1b4f243fc44f205dfd320933a951b8d89e0afd4c7cacc762a8b9165207", size = 47324, upload-time = "2025-10-08T19:46:48.384Z" }, + { url = "https://files.pythonhosted.org/packages/9e/d3/6c7ee328b39a81ee877c962469f1e795f9db87f925251efeb0545e0020d0/propcache-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec17c65562a827bba85e3872ead335f95405ea1674860d96483a02f5c698fa72", size = 225505, upload-time = "2025-10-08T19:46:50.055Z" }, + { url = "https://files.pythonhosted.org/packages/01/5d/1c53f4563490b1d06a684742cc6076ef944bc6457df6051b7d1a877c057b/propcache-0.4.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:405aac25c6394ef275dee4c709be43745d36674b223ba4eb7144bf4d691b7367", size = 230242, upload-time = "2025-10-08T19:46:51.815Z" }, + { url = "https://files.pythonhosted.org/packages/20/e1/ce4620633b0e2422207c3cb774a0ee61cac13abc6217763a7b9e2e3f4a12/propcache-0.4.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0013cb6f8dde4b2a2f66903b8ba740bdfe378c943c4377a200551ceb27f379e4", size = 238474, upload-time = "2025-10-08T19:46:53.208Z" }, + { url = "https://files.pythonhosted.org/packages/46/4b/3aae6835b8e5f44ea6a68348ad90f78134047b503765087be2f9912140ea/propcache-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15932ab57837c3368b024473a525e25d316d8353016e7cc0e5ba9eb343fbb1cf", size = 221575, upload-time = "2025-10-08T19:46:54.511Z" }, + { url = "https://files.pythonhosted.org/packages/6e/a5/8a5e8678bcc9d3a1a15b9a29165640d64762d424a16af543f00629c87338/propcache-0.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:031dce78b9dc099f4c29785d9cf5577a3faf9ebf74ecbd3c856a7b92768c3df3", size = 216736, upload-time = "2025-10-08T19:46:56.212Z" }, + { url = "https://files.pythonhosted.org/packages/f1/63/b7b215eddeac83ca1c6b934f89d09a625aa9ee4ba158338854c87210cc36/propcache-0.4.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ab08df6c9a035bee56e31af99be621526bd237bea9f32def431c656b29e41778", size = 213019, upload-time = "2025-10-08T19:46:57.595Z" }, + { url = "https://files.pythonhosted.org/packages/57/74/f580099a58c8af587cac7ba19ee7cb418506342fbbe2d4a4401661cca886/propcache-0.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4d7af63f9f93fe593afbf104c21b3b15868efb2c21d07d8732c0c4287e66b6a6", size = 220376, upload-time = "2025-10-08T19:46:59.067Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ee/542f1313aff7eaf19c2bb758c5d0560d2683dac001a1c96d0774af799843/propcache-0.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cfc27c945f422e8b5071b6e93169679e4eb5bf73bbcbf1ba3ae3a83d2f78ebd9", size = 226988, upload-time = "2025-10-08T19:47:00.544Z" }, + { url = "https://files.pythonhosted.org/packages/8f/18/9c6b015dd9c6930f6ce2229e1f02fb35298b847f2087ea2b436a5bfa7287/propcache-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:35c3277624a080cc6ec6f847cbbbb5b49affa3598c4535a0a4682a697aaa5c75", size = 215615, upload-time = "2025-10-08T19:47:01.968Z" }, + { url = "https://files.pythonhosted.org/packages/80/9e/e7b85720b98c45a45e1fca6a177024934dc9bc5f4d5dd04207f216fc33ed/propcache-0.4.1-cp312-cp312-win32.whl", hash = "sha256:671538c2262dadb5ba6395e26c1731e1d52534bfe9ae56d0b5573ce539266aa8", size = 38066, upload-time = "2025-10-08T19:47:03.503Z" }, + { url = "https://files.pythonhosted.org/packages/54/09/d19cff2a5aaac632ec8fc03737b223597b1e347416934c1b3a7df079784c/propcache-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:cb2d222e72399fcf5890d1d5cc1060857b9b236adff2792ff48ca2dfd46c81db", size = 41655, upload-time = "2025-10-08T19:47:04.973Z" }, + { url = "https://files.pythonhosted.org/packages/68/ab/6b5c191bb5de08036a8c697b265d4ca76148efb10fa162f14af14fb5f076/propcache-0.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:204483131fb222bdaaeeea9f9e6c6ed0cac32731f75dfc1d4a567fc1926477c1", size = 37789, upload-time = "2025-10-08T19:47:06.077Z" }, + { url = "https://files.pythonhosted.org/packages/5b/5a/bc7b4a4ef808fa59a816c17b20c4bef6884daebbdf627ff2a161da67da19/propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237", size = 13305, upload-time = "2025-10-08T19:49:00.792Z" }, ] [[package]] name = "protobuf" -version = "6.32.1" +version = "6.33.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fa/a4/cc17347aa2897568beece2e674674359f911d6fe21b0b8d6268cd42727ac/protobuf-6.32.1.tar.gz", hash = "sha256:ee2469e4a021474ab9baafea6cd070e5bf27c7d29433504ddea1a4ee5850f68d", size = 440635, upload-time = "2025-09-11T21:38:42.935Z" } +sdist = { url = "https://files.pythonhosted.org/packages/34/44/e49ecff446afeec9d1a66d6bbf9adc21e3c7cea7803a920ca3773379d4f6/protobuf-6.33.2.tar.gz", hash = "sha256:56dc370c91fbb8ac85bc13582c9e373569668a290aa2e66a590c2a0d35ddb9e4", size = 444296, upload-time = "2025-12-06T00:17:53.311Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c0/98/645183ea03ab3995d29086b8bf4f7562ebd3d10c9a4b14ee3f20d47cfe50/protobuf-6.32.1-cp310-abi3-win32.whl", hash = "sha256:a8a32a84bc9f2aad712041b8b366190f71dde248926da517bde9e832e4412085", size = 424411, upload-time = "2025-09-11T21:38:27.427Z" }, - { url = "https://files.pythonhosted.org/packages/8c/f3/6f58f841f6ebafe076cebeae33fc336e900619d34b1c93e4b5c97a81fdfa/protobuf-6.32.1-cp310-abi3-win_amd64.whl", hash = "sha256:b00a7d8c25fa471f16bc8153d0e53d6c9e827f0953f3c09aaa4331c718cae5e1", size = 435738, upload-time = "2025-09-11T21:38:30.959Z" }, - { url = "https://files.pythonhosted.org/packages/10/56/a8a3f4e7190837139e68c7002ec749190a163af3e330f65d90309145a210/protobuf-6.32.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d8c7e6eb619ffdf105ee4ab76af5a68b60a9d0f66da3ea12d1640e6d8dab7281", size = 426454, upload-time = "2025-09-11T21:38:34.076Z" }, - { url = "https://files.pythonhosted.org/packages/3f/be/8dd0a927c559b37d7a6c8ab79034fd167dcc1f851595f2e641ad62be8643/protobuf-6.32.1-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:2f5b80a49e1eb7b86d85fcd23fe92df154b9730a725c3b38c4e43b9d77018bf4", size = 322874, upload-time = "2025-09-11T21:38:35.509Z" }, - { url = "https://files.pythonhosted.org/packages/5c/f6/88d77011b605ef979aace37b7703e4eefad066f7e84d935e5a696515c2dd/protobuf-6.32.1-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:b1864818300c297265c83a4982fd3169f97122c299f56a56e2445c3698d34710", size = 322013, upload-time = "2025-09-11T21:38:37.017Z" }, - { url = "https://files.pythonhosted.org/packages/97/b7/15cc7d93443d6c6a84626ae3258a91f4c6ac8c0edd5df35ea7658f71b79c/protobuf-6.32.1-py3-none-any.whl", hash = "sha256:2601b779fc7d32a866c6b4404f9d42a3f67c5b9f3f15b4db3cccabe06b95c346", size = 169289, upload-time = "2025-09-11T21:38:41.234Z" }, + { url = "https://files.pythonhosted.org/packages/bc/91/1e3a34881a88697a7354ffd177e8746e97a722e5e8db101544b47e84afb1/protobuf-6.33.2-cp310-abi3-win32.whl", hash = "sha256:87eb388bd2d0f78febd8f4c8779c79247b26a5befad525008e49a6955787ff3d", size = 425603, upload-time = "2025-12-06T00:17:41.114Z" }, + { url = "https://files.pythonhosted.org/packages/64/20/4d50191997e917ae13ad0a235c8b42d8c1ab9c3e6fd455ca16d416944355/protobuf-6.33.2-cp310-abi3-win_amd64.whl", hash = "sha256:fc2a0e8b05b180e5fc0dd1559fe8ebdae21a27e81ac77728fb6c42b12c7419b4", size = 436930, upload-time = "2025-12-06T00:17:43.278Z" }, + { url = "https://files.pythonhosted.org/packages/b2/ca/7e485da88ba45c920fb3f50ae78de29ab925d9e54ef0de678306abfbb497/protobuf-6.33.2-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d9b19771ca75935b3a4422957bc518b0cecb978b31d1dd12037b088f6bcc0e43", size = 427621, upload-time = "2025-12-06T00:17:44.445Z" }, + { url = "https://files.pythonhosted.org/packages/7d/4f/f743761e41d3b2b2566748eb76bbff2b43e14d5fcab694f494a16458b05f/protobuf-6.33.2-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:b5d3b5625192214066d99b2b605f5783483575656784de223f00a8d00754fc0e", size = 324460, upload-time = "2025-12-06T00:17:45.678Z" }, + { url = "https://files.pythonhosted.org/packages/b1/fa/26468d00a92824020f6f2090d827078c09c9c587e34cbfd2d0c7911221f8/protobuf-6.33.2-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:8cd7640aee0b7828b6d03ae518b5b4806fdfc1afe8de82f79c3454f8aef29872", size = 339168, upload-time = "2025-12-06T00:17:46.813Z" }, + { url = "https://files.pythonhosted.org/packages/56/13/333b8f421738f149d4fe5e49553bc2a2ab75235486259f689b4b91f96cec/protobuf-6.33.2-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:1f8017c48c07ec5859106533b682260ba3d7c5567b1ca1f24297ce03384d1b4f", size = 323270, upload-time = "2025-12-06T00:17:48.253Z" }, + { url = "https://files.pythonhosted.org/packages/0e/15/4f02896cc3df04fc465010a4c6a0cd89810f54617a32a70ef531ed75d61c/protobuf-6.33.2-py3-none-any.whl", hash = "sha256:7636aad9bb01768870266de5dc009de2d1b936771b38a793f73cbbf279c91c5c", size = 170501, upload-time = "2025-12-06T00:17:52.211Z" }, ] [[package]] name = "psutil" -version = "7.1.0" +version = "7.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b3/31/4723d756b59344b643542936e37a31d1d3204bcdc42a7daa8ee9eb06fb50/psutil-7.1.0.tar.gz", hash = "sha256:655708b3c069387c8b77b072fc429a57d0e214221d01c0a772df7dfedcb3bcd2", size = 497660, upload-time = "2025-09-17T20:14:52.902Z" } +sdist = { url = "https://files.pythonhosted.org/packages/be/7c/31d1c3ceb1260301f87565f50689dc6da3db427ece1e1e012af22abca54e/psutil-7.2.0.tar.gz", hash = "sha256:2e4f8e1552f77d14dc96fb0f6240c5b34a37081c0889f0853b3b29a496e5ef64", size = 489863, upload-time = "2025-12-23T20:26:24.616Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/46/62/ce4051019ee20ce0ed74432dd73a5bb087a6704284a470bb8adff69a0932/psutil-7.1.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:76168cef4397494250e9f4e73eb3752b146de1dd950040b29186d0cce1d5ca13", size = 245242, upload-time = "2025-09-17T20:14:56.126Z" }, - { url = "https://files.pythonhosted.org/packages/38/61/f76959fba841bf5b61123fbf4b650886dc4094c6858008b5bf73d9057216/psutil-7.1.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:5d007560c8c372efdff9e4579c2846d71de737e4605f611437255e81efcca2c5", size = 246682, upload-time = "2025-09-17T20:14:58.25Z" }, - { url = "https://files.pythonhosted.org/packages/88/7a/37c99d2e77ec30d63398ffa6a660450b8a62517cabe44b3e9bae97696e8d/psutil-7.1.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22e4454970b32472ce7deaa45d045b34d3648ce478e26a04c7e858a0a6e75ff3", size = 287994, upload-time = "2025-09-17T20:14:59.901Z" }, - { url = "https://files.pythonhosted.org/packages/9d/de/04c8c61232f7244aa0a4b9a9fbd63a89d5aeaf94b2fc9d1d16e2faa5cbb0/psutil-7.1.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c70e113920d51e89f212dd7be06219a9b88014e63a4cec69b684c327bc474e3", size = 291163, upload-time = "2025-09-17T20:15:01.481Z" }, - { url = "https://files.pythonhosted.org/packages/f4/58/c4f976234bf6d4737bc8c02a81192f045c307b72cf39c9e5c5a2d78927f6/psutil-7.1.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7d4a113425c037300de3ac8b331637293da9be9713855c4fc9d2d97436d7259d", size = 293625, upload-time = "2025-09-17T20:15:04.492Z" }, - { url = "https://files.pythonhosted.org/packages/79/87/157c8e7959ec39ced1b11cc93c730c4fb7f9d408569a6c59dbd92ceb35db/psutil-7.1.0-cp37-abi3-win32.whl", hash = "sha256:09ad740870c8d219ed8daae0ad3b726d3bf9a028a198e7f3080f6a1888b99bca", size = 244812, upload-time = "2025-09-17T20:15:07.462Z" }, - { url = "https://files.pythonhosted.org/packages/bf/e9/b44c4f697276a7a95b8e94d0e320a7bf7f3318521b23de69035540b39838/psutil-7.1.0-cp37-abi3-win_amd64.whl", hash = "sha256:57f5e987c36d3146c0dd2528cd42151cf96cd359b9d67cfff836995cc5df9a3d", size = 247965, upload-time = "2025-09-17T20:15:09.673Z" }, - { url = "https://files.pythonhosted.org/packages/26/65/1070a6e3c036f39142c2820c4b52e9243246fcfc3f96239ac84472ba361e/psutil-7.1.0-cp37-abi3-win_arm64.whl", hash = "sha256:6937cb68133e7c97b6cc9649a570c9a18ba0efebed46d8c5dae4c07fa1b67a07", size = 244971, upload-time = "2025-09-17T20:15:12.262Z" }, + { url = "https://files.pythonhosted.org/packages/40/c5/a49160bf3e165b7b93a60579a353cf5d939d7f878fe5fd369110f1d18043/psutil-7.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:977a2fcd132d15cb05b32b2d85b98d087cad039b0ce435731670ba74da9e6133", size = 128116, upload-time = "2025-12-23T20:26:53.516Z" }, + { url = "https://files.pythonhosted.org/packages/10/a1/c75feb480f60cd768fb6ed00ac362a16a33e5076ec8475a22d8162fb2659/psutil-7.2.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:24151011c21fadd94214d7139d7c6c54569290d7e553989bdf0eab73b13beb8c", size = 128925, upload-time = "2025-12-23T20:26:55.573Z" }, + { url = "https://files.pythonhosted.org/packages/12/ff/e93136587c00a543f4bc768b157fac2c47cd77b180d4f4e5c6efb6ea53a2/psutil-7.2.0-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:91f211ba9279e7c61d9d8f84b713cfc38fa161cb0597d5cb3f1ca742f6848254", size = 154666, upload-time = "2025-12-23T20:26:57.312Z" }, + { url = "https://files.pythonhosted.org/packages/b8/dd/4c2de9c3827c892599d277a69d2224136800870a8a88a80981de905de28d/psutil-7.2.0-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f37415188b7ea98faf90fed51131181646c59098b077550246e2e092e127418b", size = 156109, upload-time = "2025-12-23T20:26:58.851Z" }, + { url = "https://files.pythonhosted.org/packages/81/3f/090943c682d3629968dd0b04826ddcbc760ee1379021dbe316e2ddfcd01b/psutil-7.2.0-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0d12c7ce6ed1128cd81fd54606afa054ac7dbb9773469ebb58cf2f171c49f2ac", size = 148081, upload-time = "2025-12-23T20:27:01.318Z" }, + { url = "https://files.pythonhosted.org/packages/c4/88/c39648ebb8ec182d0364af53cdefe6eddb5f3872ba718b5855a8ff65d6d4/psutil-7.2.0-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ca0faef7976530940dcd39bc5382d0d0d5eb023b186a4901ca341bd8d8684151", size = 147376, upload-time = "2025-12-23T20:27:03.347Z" }, + { url = "https://files.pythonhosted.org/packages/01/a2/5b39e08bd9b27476bc7cce7e21c71a481ad60b81ffac49baf02687a50d7f/psutil-7.2.0-cp37-abi3-win_amd64.whl", hash = "sha256:abdb74137ca232d20250e9ad471f58d500e7743bc8253ba0bfbf26e570c0e437", size = 136910, upload-time = "2025-12-23T20:27:05.289Z" }, + { url = "https://files.pythonhosted.org/packages/59/54/53839db1258c1eaeb4ded57ff202144ebc75b23facc05a74fd98d338b0c6/psutil-7.2.0-cp37-abi3-win_arm64.whl", hash = "sha256:284e71038b3139e7ab3834b63b3eb5aa5565fcd61a681ec746ef9a0a8c457fd2", size = 133807, upload-time = "2025-12-23T20:27:06.825Z" }, ] [[package]] @@ -1820,23 +1831,24 @@ crypto = [ [[package]] name = "pylibsrtp" -version = "0.12.0" +version = "1.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/54/c8/a59e61f5dd655f5f21033bd643dd31fe980a537ed6f373cdfb49d3a3bd32/pylibsrtp-0.12.0.tar.gz", hash = "sha256:f5c3c0fb6954e7bb74dc7e6398352740ca67327e6759a199fe852dbc7b84b8ac", size = 10878, upload-time = "2025-04-06T12:35:51.804Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/a6/6e532bec974aaecbf9fe4e12538489fb1c28456e65088a50f305aeab9f89/pylibsrtp-1.0.0.tar.gz", hash = "sha256:b39dff075b263a8ded5377f2490c60d2af452c9f06c4d061c7a2b640612b34d4", size = 10858, upload-time = "2025-10-13T16:12:31.552Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/65/f0/b818395c4cae2d5cc5a0c78fc47d694eae78e6a0d678baeb52a381a26327/pylibsrtp-0.12.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:5adde3cf9a5feef561d0eb7ed99dedb30b9bf1ce9a0c1770b2bf19fd0b98bc9a", size = 1727918, upload-time = "2025-04-06T12:35:36.456Z" }, - { url = "https://files.pythonhosted.org/packages/05/1a/ee553abe4431b7bd9bab18f078c0ad2298b94ea55e664da6ecb8700b1052/pylibsrtp-0.12.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:d2c81d152606721331ece87c80ed17159ba6da55c7c61a6b750cff67ab7f63a5", size = 2057900, upload-time = "2025-04-06T12:35:38.253Z" }, - { url = "https://files.pythonhosted.org/packages/7f/a2/2dd0188be58d3cba48c5eb4b3c787e5743c111cd0c9289de4b6f2798382a/pylibsrtp-0.12.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:242fa3d44219846bf1734d5df595563a2c8fbb0fb00ccc79ab0f569fc0af2c1b", size = 2567047, upload-time = "2025-04-06T12:35:39.797Z" }, - { url = "https://files.pythonhosted.org/packages/6c/3a/4bdab9fc1d78f2efa02c8a8f3e9c187bfa278e89481b5123f07c8dd69310/pylibsrtp-0.12.0-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b74aaf8fac1b119a3c762f54751c3d20e77227b84c26d85aae57c2c43129b49c", size = 2168775, upload-time = "2025-04-06T12:35:41.422Z" }, - { url = "https://files.pythonhosted.org/packages/d0/fc/0b1e1bfed420d79427d50aff84c370dcd78d81af9500c1e86fbcc5bf95e1/pylibsrtp-0.12.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33e3e223102989b71f07e1deeb804170ed53fb4e1b283762eb031bd45bb425d4", size = 2225033, upload-time = "2025-04-06T12:35:43.03Z" }, - { url = "https://files.pythonhosted.org/packages/39/7b/e1021d27900315c2c077ec7d45f50274cedbdde067ff679d44df06f01a8a/pylibsrtp-0.12.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:36d07de64dbc82dbbb99fd77f36c8e23d6730bdbcccf09701945690a9a9a422a", size = 2606093, upload-time = "2025-04-06T12:35:44.587Z" }, - { url = "https://files.pythonhosted.org/packages/eb/c2/0fae6687a06fcde210a778148ec808af49e431c36fe9908503a695c35479/pylibsrtp-0.12.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:ef03b4578577690f716fd023daed8914eee6de9a764fa128eda19a0e645cc032", size = 2193213, upload-time = "2025-04-06T12:35:46.167Z" }, - { url = "https://files.pythonhosted.org/packages/67/c2/2ed7a4a5c38b999fd34298f76b93d29f5ba8c06f85cfad3efd9468343715/pylibsrtp-0.12.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:0a8421e9fe4d20ce48d439430e55149f12b1bca1b0436741972c362c49948c0a", size = 2256774, upload-time = "2025-04-06T12:35:47.704Z" }, - { url = "https://files.pythonhosted.org/packages/48/d7/f13fedce3b21d24f6f154d1dee7287464a34728dcb3b0c50f687dbad5765/pylibsrtp-0.12.0-cp39-abi3-win32.whl", hash = "sha256:cbc9bfbfb2597e993a1aa16b832ba16a9dd4647f70815421bb78484f8b50b924", size = 1156186, upload-time = "2025-04-06T12:35:48.78Z" }, - { url = "https://files.pythonhosted.org/packages/9b/26/3a20b638a3a3995368f856eeb10701dd6c0e9ace9fb6665eeb1b95ccce19/pylibsrtp-0.12.0-cp39-abi3-win_amd64.whl", hash = "sha256:061ef1dbb5f08079ac6d7515b7e67ca48a3163e16e5b820beea6b01cb31d7e54", size = 1485072, upload-time = "2025-04-06T12:35:50.312Z" }, + { url = "https://files.pythonhosted.org/packages/aa/af/89e61a62fa3567f1b7883feb4d19e19564066c2fcd41c37e08d317b51881/pylibsrtp-1.0.0-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:822c30ea9e759b333dc1f56ceac778707c51546e97eb874de98d7d378c000122", size = 1865017, upload-time = "2025-10-13T16:12:15.62Z" }, + { url = "https://files.pythonhosted.org/packages/8d/0e/8d215484a9877adcf2459a8b28165fc89668b034565277fd55d666edd247/pylibsrtp-1.0.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:aaad74e5c8cbc1c32056c3767fea494c1e62b3aea2c908eda2a1051389fdad76", size = 2182739, upload-time = "2025-10-13T16:12:17.121Z" }, + { url = "https://files.pythonhosted.org/packages/57/3f/76a841978877ae13eac0d4af412c13bbd5d83b3df2c1f5f2175f2e0f68e5/pylibsrtp-1.0.0-cp310-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9209b86e662ebbd17c8a9e8549ba57eca92a3e87fb5ba8c0e27b8c43cd08a767", size = 2732922, upload-time = "2025-10-13T16:12:18.348Z" }, + { url = "https://files.pythonhosted.org/packages/0e/14/cf5d2a98a66fdfe258f6b036cda570f704a644fa861d7883a34bc359501e/pylibsrtp-1.0.0-cp310-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:293c9f2ac21a2bd689c477603a1aa235d85cf252160e6715f0101e42a43cbedc", size = 2434534, upload-time = "2025-10-13T16:12:20.074Z" }, + { url = "https://files.pythonhosted.org/packages/bd/08/a3f6e86c04562f7dce6717cd2206a0f84ca85c5e38121d998e0e330194c3/pylibsrtp-1.0.0-cp310-abi3-manylinux_2_28_i686.whl", hash = "sha256:81fb8879c2e522021a7cbd3f4bda1b37c192e1af939dfda3ff95b4723b329663", size = 2345818, upload-time = "2025-10-13T16:12:21.439Z" }, + { url = "https://files.pythonhosted.org/packages/8e/d5/130c2b5b4b51df5631684069c6f0a6761c59d096a33d21503ac207cf0e47/pylibsrtp-1.0.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:4ddb562e443cf2e557ea2dfaeef0d7e6b90e96dd38eb079b4ab2c8e34a79f50b", size = 2774490, upload-time = "2025-10-13T16:12:22.659Z" }, + { url = "https://files.pythonhosted.org/packages/91/e3/715a453bfee3bea92a243888ad359094a7727cc6d393f21281320fe7798c/pylibsrtp-1.0.0-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:f02e616c9dfab2b03b32d8cc7b748f9d91814c0211086f987629a60f05f6e2cc", size = 2372603, upload-time = "2025-10-13T16:12:24.036Z" }, + { url = "https://files.pythonhosted.org/packages/e3/56/52fa74294254e1f53a4ff170ee2006e57886cf4bb3db46a02b4f09e1d99f/pylibsrtp-1.0.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:c134fa09e7b80a5b7fed626230c5bc257fd771bd6978e754343e7a61d96bc7e6", size = 2451269, upload-time = "2025-10-13T16:12:25.475Z" }, + { url = "https://files.pythonhosted.org/packages/1e/51/2e9b34f484cbdd3bac999bf1f48b696d7389433e900639089e8fc4e0da0d/pylibsrtp-1.0.0-cp310-abi3-win32.whl", hash = "sha256:bae377c3b402b17b9bbfbfe2534c2edba17aa13bea4c64ce440caacbe0858b55", size = 1247503, upload-time = "2025-10-13T16:12:27.39Z" }, + { url = "https://files.pythonhosted.org/packages/c3/70/43db21af194580aba2d9a6d4c7bd8c1a6e887fa52cd810b88f89096ecad2/pylibsrtp-1.0.0-cp310-abi3-win_amd64.whl", hash = "sha256:8d6527c4a78a39a8d397f8862a8b7cdad4701ee866faf9de4ab8c70be61fd34d", size = 1601659, upload-time = "2025-10-13T16:12:29.037Z" }, + { url = "https://files.pythonhosted.org/packages/8e/ec/6e02b2561d056ea5b33046e3cad21238e6a9097b97d6ccc0fbe52b50c858/pylibsrtp-1.0.0-cp310-abi3-win_arm64.whl", hash = "sha256:2696bdb2180d53ac55d0eb7b58048a2aa30cd4836dd2ca683669889137a94d2a", size = 1159246, upload-time = "2025-10-13T16:12:30.285Z" }, ] [[package]] @@ -1865,7 +1877,7 @@ wheels = [ [[package]] name = "pyobjc" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, @@ -1878,6 +1890,7 @@ dependencies = [ { 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-arkit", marker = "platform_release >= '25.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'" }, @@ -1898,6 +1911,7 @@ dependencies = [ { 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-compositorservices", marker = "platform_release >= '25.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'" }, @@ -1938,6 +1952,7 @@ dependencies = [ { 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-gamesave", marker = "platform_release >= '25.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'" }, @@ -2027,118 +2042,118 @@ dependencies = [ { name = "pyobjc-framework-vision", marker = "platform_release >= '17.0' and sys_platform == 'darwin'" }, { name = "pyobjc-framework-webkit", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/db/5e/16bc372806790d295c76b5c7851767cc9ee3787b3e581f5d7cc44158e4e0/pyobjc-11.1.tar.gz", hash = "sha256:a71b14389657811d658526ba4d5faba4ef7eadbddcf9fe8bf4fb3a6261effba3", size = 11161, upload-time = "2025-06-14T20:56:32.819Z" } +sdist = { url = "https://files.pythonhosted.org/packages/17/06/d77639ba166cc09aed2d32ae204811b47bc5d40e035cdc9bff7fff72ec5f/pyobjc-12.1.tar.gz", hash = "sha256:686d6db3eb3182fac9846b8ce3eedf4c7d2680b21b8b8d6e6df054a17e92a12d", size = 11345, upload-time = "2025-11-14T10:07:28.155Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a9/32/ad08b45fc0ad9850054ffe66fb0cb2ff7af3d2007c192dda14cf9a3ea893/pyobjc-11.1-py3-none-any.whl", hash = "sha256:903f822cba40be53d408b8eaf834514937ec0b4e6af1c5ecc24fcb652812dd85", size = 4164, upload-time = "2025-06-14T20:44:42.659Z" }, + { url = "https://files.pythonhosted.org/packages/ef/00/1085de7b73abf37ec27ad59f7a1d7a406e6e6da45720bced2e198fdf1ddf/pyobjc-12.1-py3-none-any.whl", hash = "sha256:6f8c36cf87b1159d2ca1aa387ffc3efcd51cc3da13ef47c65f45e6d9fbccc729", size = 4226, upload-time = "2025-11-14T09:30:25.185Z" }, ] [[package]] name = "pyobjc-core" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e8/e9/0b85c81e2b441267bca707b5d89f56c2f02578ef8f3eafddf0e0c0b8848c/pyobjc_core-11.1.tar.gz", hash = "sha256:b63d4d90c5df7e762f34739b39cc55bc63dbcf9fb2fb3f2671e528488c7a87fe", size = 974602, upload-time = "2025-06-14T20:56:34.189Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b8/b6/d5612eb40be4fd5ef88c259339e6313f46ba67577a95d86c3470b951fce0/pyobjc_core-12.1.tar.gz", hash = "sha256:2bb3903f5387f72422145e1466b3ac3f7f0ef2e9960afa9bcd8961c5cbf8bd21", size = 1000532, upload-time = "2025-11-14T10:08:28.292Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/a7/55afc166d89e3fcd87966f48f8bca3305a3a2d7c62100715b9ffa7153a90/pyobjc_core-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ec36680b5c14e2f73d432b03ba7c1457dc6ca70fa59fd7daea1073f2b4157d33", size = 671075, upload-time = "2025-06-14T20:44:46.594Z" }, - { url = "https://files.pythonhosted.org/packages/c0/09/e83228e878e73bf756749939f906a872da54488f18d75658afa7f1abbab1/pyobjc_core-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:765b97dea6b87ec4612b3212258024d8496ea23517c95a1c5f0735f96b7fd529", size = 677985, upload-time = "2025-06-14T20:44:48.375Z" }, + { url = "https://files.pythonhosted.org/packages/95/df/d2b290708e9da86d6e7a9a2a2022b91915cf2e712a5a82e306cb6ee99792/pyobjc_core-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c918ebca280925e7fcb14c5c43ce12dcb9574a33cccb889be7c8c17f3bcce8b6", size = 671263, upload-time = "2025-11-14T09:31:35.231Z" }, + { url = "https://files.pythonhosted.org/packages/64/5a/6b15e499de73050f4a2c88fff664ae154307d25dc04da8fb38998a428358/pyobjc_core-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:818bcc6723561f207e5b5453efe9703f34bc8781d11ce9b8be286bb415eb4962", size = 678335, upload-time = "2025-11-14T09:32:20.107Z" }, ] [[package]] name = "pyobjc-framework-accessibility" -version = "11.1" +version = "12.1" 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'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/78/b4/10c16e9d48568a68da2f61866b19468d4ac7129c377d4b1333ee936ae5d0/pyobjc_framework_accessibility-11.1.tar.gz", hash = "sha256:c0fa5f1e00906ec002f582c7d3d80463a46d19f672bf5ec51144f819eeb40656", size = 45098, upload-time = "2025-06-14T20:56:35.287Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/87/8ca40428d05a668fecc638f2f47dba86054dbdc35351d247f039749de955/pyobjc_framework_accessibility-12.1.tar.gz", hash = "sha256:5ff362c3425edc242d49deec11f5f3e26e565cefb6a2872eda59ab7362149772", size = 29800, upload-time = "2025-11-14T10:08:31.949Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ff/c5/8803e4f9c3f2d3f5672097438e305be9ccfb87ad092c68cbf02b172bf1d2/pyobjc_framework_accessibility-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:332263153d829b946b311ddc8b9a4402b52d40a572b44c69c3242451ced1b008", size = 11135, upload-time = "2025-06-14T20:44:58.339Z" }, - { url = "https://files.pythonhosted.org/packages/5d/bd/087d511e0ea356434399609a38e8819978943cbeaca3ca7cc5f35c93d0b2/pyobjc_framework_accessibility-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a049b63b32514da68aaaeef0d6c00a125e0618e4042aa6dbe3867b74fb2a8b2b", size = 11158, upload-time = "2025-06-14T20:44:59.032Z" }, + { url = "https://files.pythonhosted.org/packages/76/00/182c57584ad8e5946a82dacdc83c9791567e10bffdea1fe92272b3fdec14/pyobjc_framework_accessibility-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5e29dac0ce8327cd5a8b9a5a8bd8aa83e4070018b93699e97ac0c3af09b42a9a", size = 11301, upload-time = "2025-11-14T09:35:28.678Z" }, + { url = "https://files.pythonhosted.org/packages/cc/95/9ea0d1c16316b4b5babf4b0515e9a133ac64269d3ec031f15ee9c7c2a8c1/pyobjc_framework_accessibility-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:537691a0b28fedb8385cd093df069a6e5d7e027629671fc47b50210404eca20b", size = 11335, upload-time = "2025-11-14T09:35:30.81Z" }, ] [[package]] name = "pyobjc-framework-accounts" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/12/45/ca21003f68ad0f13b5a9ac1761862ad2ddd83224b4314a2f7d03ca437c8d/pyobjc_framework_accounts-11.1.tar.gz", hash = "sha256:384fec156e13ff75253bb094339013f4013464f6dfd47e2f7de3e2ae7441c030", size = 17086, upload-time = "2025-06-14T20:56:36.035Z" } +sdist = { url = "https://files.pythonhosted.org/packages/65/10/f6fe336c7624d6753c1f6edac102310ce4434d49b548c479e8e6420d4024/pyobjc_framework_accounts-12.1.tar.gz", hash = "sha256:76d62c5e7b831eb8f4c9ca6abaf79d9ed961dfffe24d89a041fb1de97fe56a3e", size = 15202, upload-time = "2025-11-14T10:08:33.995Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/db/fa1c4a964fb9f390af8fce1d82c053f9d4467ffe6acdaab464bb3220e673/pyobjc_framework_accounts-11.1-py2.py3-none-any.whl", hash = "sha256:9c3fe342be7b8e73cba735e5a38affbe349cf8bc19091aa4fd788eabf2074b72", size = 5117, upload-time = "2025-06-14T20:45:04.696Z" }, + { url = "https://files.pythonhosted.org/packages/ac/70/5f9214250f92fbe2e07f35778875d2771d612f313af2a0e4bacba80af28e/pyobjc_framework_accounts-12.1-py2.py3-none-any.whl", hash = "sha256:e1544ad11a2f889a7aaed649188d0e76d58595a27eec07ca663847a7adb21ae5", size = 5104, upload-time = "2025-11-14T09:35:40.246Z" }, ] [[package]] name = "pyobjc-framework-addressbook" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/eb/d3/f5bb5c72be5c6e52224f43e23e5a44e86d2c35ee9af36939e5514c6c7a0f/pyobjc_framework_addressbook-11.1.tar.gz", hash = "sha256:ce2db3be4a3128bf79d5c41319a6d16b73754785ce75ac694d0d658c690922fc", size = 97609, upload-time = "2025-06-14T20:56:37.324Z" } +sdist = { url = "https://files.pythonhosted.org/packages/18/28/0404af2a1c6fa8fd266df26fb6196a8f3fb500d6fe3dab94701949247bea/pyobjc_framework_addressbook-12.1.tar.gz", hash = "sha256:c48b740cf981103cef1743d0804a226d86481fcb839bd84b80e9a586187e8000", size = 44359, upload-time = "2025-11-14T10:08:37.687Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8f/46/27ade210b0bcf2903540c37e96f5e88ec5303e98dc12b255148f12ef9c04/pyobjc_framework_addressbook-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d1d69330b5a87a29d26feea95dcf40681fd00ba3b40ac89579072ce536b6b647", size = 13156, upload-time = "2025-06-14T20:45:06.788Z" }, - { url = "https://files.pythonhosted.org/packages/c2/de/e1ba5f113c05b543a097040add795fa4b85fdd5ad850b56d83cd6ce8afff/pyobjc_framework_addressbook-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fb3d0a710f8342a0c63a8e4caf64a044b4d7e42d6d242c8e1b54470238b938cb", size = 13173, upload-time = "2025-06-14T20:45:07.755Z" }, + { url = "https://files.pythonhosted.org/packages/9f/5a/2ecaa94e5f56c6631f0820ec4209f8075c1b7561fe37495e2d024de1c8df/pyobjc_framework_addressbook-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:681755ada6c95bd4a096bc2b9f9c24661ffe6bff19a96963ee3fad34f3d61d2b", size = 12879, upload-time = "2025-11-14T09:35:45.21Z" }, + { url = "https://files.pythonhosted.org/packages/b6/33/da709c69cbb60df9522cd614d5c23c15b649b72e5d62fed1048e75c70e7b/pyobjc_framework_addressbook-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7893dd784322f4674299fb3ca40cb03385e5eddb78defd38f08c0b730813b56c", size = 12894, upload-time = "2025-11-14T09:35:47.498Z" }, ] [[package]] name = "pyobjc-framework-adservices" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2a/3f/af76eab6eee0a405a4fdee172e7181773040158476966ecd757b0a98bfc5/pyobjc_framework_adservices-11.1.tar.gz", hash = "sha256:44c72f8163705c9aa41baca938fdb17dde257639e5797e6a5c3a2b2d8afdade9", size = 12473, upload-time = "2025-06-14T20:56:38.147Z" } +sdist = { url = "https://files.pythonhosted.org/packages/19/04/1c3d3e0a1ac981664f30b33407dcdf8956046ecde6abc88832cf2aa535f4/pyobjc_framework_adservices-12.1.tar.gz", hash = "sha256:7a31fc8d5c6fd58f012db87c89ba581361fc905114bfb912e0a3a87475c02183", size = 11793, upload-time = "2025-11-14T10:08:39.56Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8e/11/a63a171ce86c25a6ae85ebff6a9ab92b0d0cb1fd66ddc7d7b0d803f36191/pyobjc_framework_adservices-11.1-py2.py3-none-any.whl", hash = "sha256:1744f59a75b2375e139c39f3e85658e62cd10cc0f12b158a80421f18734e9ffc", size = 3474, upload-time = "2025-06-14T20:45:13.263Z" }, + { url = "https://files.pythonhosted.org/packages/ad/13/f7796469b25f50750299c4b0e95dc2f75c7c7fc4c93ef2c644f947f10529/pyobjc_framework_adservices-12.1-py2.py3-none-any.whl", hash = "sha256:9ca3c55e35b2abb3149a0bce5de9a1f7e8ee4f8642036910ca8586ab2e161538", size = 3492, upload-time = "2025-11-14T09:35:57.344Z" }, ] [[package]] name = "pyobjc-framework-adsupport" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7f/03/9c51edd964796a97def4e1433d76a128dd7059b685fb4366081bf4e292ba/pyobjc_framework_adsupport-11.1.tar.gz", hash = "sha256:78b9667c275785df96219d205bd4309731869c3298d0931e32aed83bede29096", size = 12556, upload-time = "2025-06-14T20:56:38.741Z" } +sdist = { url = "https://files.pythonhosted.org/packages/43/77/f26a2e9994d4df32e9b3680c8014e350b0f1c78d7673b3eba9de2e04816f/pyobjc_framework_adsupport-12.1.tar.gz", hash = "sha256:9a68480e76de567c339dca29a8c739d6d7b5cad30e1cd585ff6e49ec2fc283dd", size = 11645, upload-time = "2025-11-14T10:08:41.439Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7d/b8/ad895efb24311cab2b9d6f7f7f6a833b7f354f80fec606e6c7893da9349b/pyobjc_framework_adsupport-11.1-py2.py3-none-any.whl", hash = "sha256:c3e009612778948910d3a7135b9d77b9b7c06aab29d40957770834c083acf825", size = 3387, upload-time = "2025-06-14T20:45:14.394Z" }, + { url = "https://files.pythonhosted.org/packages/cd/1a/3e90d5a09953bde7b60946cd09cca1411aed05dea855cb88cb9e944c7006/pyobjc_framework_adsupport-12.1-py2.py3-none-any.whl", hash = "sha256:97dcd8799dd61f047bb2eb788bbde81f86e95241b5e5173a3a61cfc05b5598b1", size = 3401, upload-time = "2025-11-14T09:35:59.039Z" }, ] [[package]] name = "pyobjc-framework-applescriptkit" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/bc/63/1bcfcdca53bf5bba3a7b4d73d24232ae1721a378a32fd4ebc34a35549df2/pyobjc_framework_applescriptkit-11.1.tar.gz", hash = "sha256:477707352eaa6cc4a5f8c593759dc3227a19d5958481b1482f0d59394a4601c3", size = 12392, upload-time = "2025-06-14T20:56:39.331Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/f1/e0c07b2a9eb98f1a2050f153d287a52a92f873eeddb41b74c52c144d8767/pyobjc_framework_applescriptkit-12.1.tar.gz", hash = "sha256:cb09f88cf0ad9753dedc02720065818f854b50e33eb4194f0ea34de6d7a3eb33", size = 11451, upload-time = "2025-11-14T10:08:43.328Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c6/0e/68ac4ce71e613697a087c262aefacc9ed54eaf0cf1d9ffcd89134bfdab9b/pyobjc_framework_applescriptkit-11.1-py2.py3-none-any.whl", hash = "sha256:e22cbc9d1a25a4a713f21aa94dd017c311186b02062fc7ffbde3009495fb0067", size = 4334, upload-time = "2025-06-14T20:45:15.205Z" }, + { url = "https://files.pythonhosted.org/packages/3b/70/6c399c6ebc37a4e48acf63967e0a916878aedfe420531f6d739215184c0c/pyobjc_framework_applescriptkit-12.1-py2.py3-none-any.whl", hash = "sha256:b955fc017b524027f635d92a8a45a5fd9fbae898f3e03de16ecd94aa4c4db987", size = 4352, upload-time = "2025-11-14T09:36:00.705Z" }, ] [[package]] name = "pyobjc-framework-applescriptobjc" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a3/27/687b55b575367df045879b786f358355e40e41f847968e557d0718a6c4a4/pyobjc_framework_applescriptobjc-11.1.tar.gz", hash = "sha256:c8a0ec975b64411a4f16a1280c5ea8dbe949fd361e723edd343102f0f95aba6e", size = 12445, upload-time = "2025-06-14T20:56:39.976Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c0/4b/e4d1592207cbe17355e01828bdd11dd58f31356108f6a49f5e0484a5df50/pyobjc_framework_applescriptobjc-12.1.tar.gz", hash = "sha256:dce080ed07409b0dda2fee75d559bd312ea1ef0243a4338606440f282a6a0f5f", size = 11588, upload-time = "2025-11-14T10:08:45.037Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2d/33/ceb6a512b41fbf3458b9a281997ebb3056cc354981215261f0a2bf7d15d6/pyobjc_framework_applescriptobjc-11.1-py2.py3-none-any.whl", hash = "sha256:ac22526fd1f0a3b07ac1d77f90046b77f10ec9549182114f2428ee1e96d3de2b", size = 4433, upload-time = "2025-06-14T20:45:16.061Z" }, + { url = "https://files.pythonhosted.org/packages/3e/5f/9ce6706399706930eb29c5308037109c30cfb36f943a6df66fdf38cc842a/pyobjc_framework_applescriptobjc-12.1-py2.py3-none-any.whl", hash = "sha256:79068f982cc22471712ce808c0a8fd5deea11258fc8d8c61968a84b1962a3d10", size = 4454, upload-time = "2025-11-14T09:36:02.276Z" }, ] [[package]] name = "pyobjc-framework-applicationservices" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, @@ -2146,84 +2161,97 @@ dependencies = [ { name = "pyobjc-framework-coretext", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/be/3f/b33ce0cecc3a42f6c289dcbf9ff698b0d9e85f5796db2e9cb5dadccffbb9/pyobjc_framework_applicationservices-11.1.tar.gz", hash = "sha256:03fcd8c0c600db98fa8b85eb7b3bc31491701720c795e3f762b54e865138bbaf", size = 224842, upload-time = "2025-06-14T20:56:40.648Z" } +sdist = { url = "https://files.pythonhosted.org/packages/be/6a/d4e613c8e926a5744fc47a9e9fea08384a510dc4f27d844f7ad7a2d793bd/pyobjc_framework_applicationservices-12.1.tar.gz", hash = "sha256:c06abb74f119bc27aeb41bf1aef8102c0ae1288aec1ac8665ea186a067a8945b", size = 103247, upload-time = "2025-11-14T10:08:52.18Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/39/2d/9fde6de0b2a95fbb3d77ba11b3cc4f289dd208f38cb3a28389add87c0f44/pyobjc_framework_applicationservices-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cf45d15eddae36dec2330a9992fc852476b61c8f529874b9ec2805c768a75482", size = 30991, upload-time = "2025-06-14T20:45:18.169Z" }, - { url = "https://files.pythonhosted.org/packages/38/ec/46a5c710e2d7edf55105223c34fed5a7b7cc7aba7d00a3a7b0405d6a2d1a/pyobjc_framework_applicationservices-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f4a85ccd78bab84f7f05ac65ff9be117839dfc09d48c39edd65c617ed73eb01c", size = 31056, upload-time = "2025-06-14T20:45:18.925Z" }, + { url = "https://files.pythonhosted.org/packages/17/86/d07eff705ff909a0ffa96d14fc14026e9fc9dd716233648c53dfd5056b8e/pyobjc_framework_applicationservices-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bdddd492eeac6d14ff2f5bd342aba29e30dffa72a2d358c08444da22129890e2", size = 32784, upload-time = "2025-11-14T09:36:08.755Z" }, + { url = "https://files.pythonhosted.org/packages/37/a7/55fa88def5c02732c4b747606ff1cbce6e1f890734bbd00f5596b21eaa02/pyobjc_framework_applicationservices-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c8f6e2fb3b3e9214ab4864ef04eee18f592b46a986c86ea0113448b310520532", size = 32835, upload-time = "2025-11-14T09:36:11.855Z" }, ] [[package]] name = "pyobjc-framework-apptrackingtransparency" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/49/68/7aa3afffd038dd6e5af764336bca734eb910121013ca71030457b61e5b99/pyobjc_framework_apptrackingtransparency-11.1.tar.gz", hash = "sha256:796cc5f83346c10973806cfb535d4200b894a5d2626ff2eeb1972d594d14fed4", size = 13135, upload-time = "2025-06-14T20:56:41.494Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/de/f24348982ecab0cb13067c348fc5fbc882c60d704ca290bada9a2b3e594b/pyobjc_framework_apptrackingtransparency-12.1.tar.gz", hash = "sha256:e25bf4e4dfa2d929993ee8e852b28fdf332fa6cde0a33328fdc3b2f502fa50ec", size = 12407, upload-time = "2025-11-14T10:08:54.118Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/21/37/22cc0293c911a98a49c5fc007b968d82797101dd06e89c4c3266564ff443/pyobjc_framework_apptrackingtransparency-11.1-py2.py3-none-any.whl", hash = "sha256:e25c3eae25d24ee8b523b7ecc4d2b07af37c7733444b80c4964071dea7b0cb19", size = 3862, upload-time = "2025-06-14T20:45:23.851Z" }, + { url = "https://files.pythonhosted.org/packages/19/b2/90120b93ecfb099b6af21696c26356ad0f2182bdef72b6cba28aa6472ca6/pyobjc_framework_apptrackingtransparency-12.1-py2.py3-none-any.whl", hash = "sha256:23a98ade55495f2f992ecf62c3cbd8f648cbd68ba5539c3f795bf66de82e37ca", size = 3879, upload-time = "2025-11-14T09:36:26.425Z" }, +] + +[[package]] +name = "pyobjc-framework-arkit" +version = "12.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/8b/843fe08e696bca8e7fc129344965ab6280f8336f64f01ba0a8862d219c3f/pyobjc_framework_arkit-12.1.tar.gz", hash = "sha256:0c5c6b702926179700b68ba29b8247464c3b609fd002a07a3308e72cfa953adf", size = 35814, upload-time = "2025-11-14T10:08:57.55Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/21/1e/64c55b409243b3eb9abc7a99e7b27ad4e16b9e74bc4b507fb7e7b81fd41a/pyobjc_framework_arkit-12.1-py2.py3-none-any.whl", hash = "sha256:f6d39e28d858ee03f052d6780a552247e682204382dbc090f1d3192fa1b21493", size = 8302, upload-time = "2025-11-14T09:36:28.127Z" }, ] [[package]] name = "pyobjc-framework-audiovideobridging" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c3/25/6c5a7b1443d30139cc722029880284ea9dfa575f0436471b9364fcd499f5/pyobjc_framework_audiovideobridging-11.1.tar.gz", hash = "sha256:12756b3aa35083b8ad5c9139b6a0e2f4792e217096b5bf6b702d499038203991", size = 72913, upload-time = "2025-06-14T20:56:42.128Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9f/51/f81581e7a3c5cb6c9254c6f1e1ee1d614930493761dec491b5b0d49544b9/pyobjc_framework_audiovideobridging-12.1.tar.gz", hash = "sha256:6230ace6bec1f38e8a727c35d054a7be54e039b3053f98e6dd8d08d6baee2625", size = 38457, upload-time = "2025-11-14T10:09:01.122Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3f/d0/952ccd59944f98f10f39c061ef7c3dceecbcd2654910e763c0ad2fd1c910/pyobjc_framework_audiovideobridging-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:db570433910d1df49cc45d25f7a966227033c794fb41133d59212689b86b1ac6", size = 11021, upload-time = "2025-06-14T20:45:25.498Z" }, - { url = "https://files.pythonhosted.org/packages/1d/69/3e8e3da4db835168d18155a2c90fcca441047fc9c2e021d2ea01b4c6eb8c/pyobjc_framework_audiovideobridging-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:591e80ff6973ea51a12f7c1a2e3fd59496633a51d5a1bf73f4fb989a43e23681", size = 11032, upload-time = "2025-06-14T20:45:26.196Z" }, + { url = "https://files.pythonhosted.org/packages/0a/f8/c614630fa382720bbd42a0ff567378630c36d10f114476d6c70b73f73b49/pyobjc_framework_audiovideobridging-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6bc24a7063b08c7d9f1749a4641430d363b6dba642c04d09b58abcee7a5260cb", size = 11037, upload-time = "2025-11-14T09:36:32.583Z" }, + { url = "https://files.pythonhosted.org/packages/f3/8e/a28badfcc6c731696e3d3a8a83927bd844d992f9152f903c2fee355702ca/pyobjc_framework_audiovideobridging-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:010021502649e2cca4e999a7c09358d48c6b0ed83530bbc0b85bba6834340e4b", size = 11052, upload-time = "2025-11-14T09:36:34.475Z" }, ] [[package]] name = "pyobjc-framework-authenticationservices" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8f/b7/3e9ad0ed3625dc02e495615ea5dbf55ca95cbd25b3e31f25092f5caad640/pyobjc_framework_authenticationservices-11.1.tar.gz", hash = "sha256:8fd801cdb53d426b4e678b0a8529c005d0c44f5a17ccd7052a7c3a1a87caed6a", size = 115266, upload-time = "2025-06-14T20:56:42.889Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/18/86218de3bf67fc1d810065f353d9df70c740de567ebee8550d476cb23862/pyobjc_framework_authenticationservices-12.1.tar.gz", hash = "sha256:cef71faeae2559f5c0ff9a81c9ceea1c81108e2f4ec7de52a98c269feff7a4b6", size = 58683, upload-time = "2025-11-14T10:09:06.003Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/31/99/0a9d2b9c1aa3b9713d322ddb90a59537013afdae5661af233409e7a24dc9/pyobjc_framework_authenticationservices-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3987b7fc9493c2ba77b773df99f6631bff1ee9b957d99e34afa6b4e1c9d48bfb", size = 20280, upload-time = "2025-06-14T20:45:32.617Z" }, - { url = "https://files.pythonhosted.org/packages/7e/2d/cbb5e88c3713fb68cda7d76d37737076c1653bf1ac95418c30d4b614f4be/pyobjc_framework_authenticationservices-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:6655dd53d9135ef85265a4297da5e7459ed7836973f2796027fdfbfd7f08e433", size = 20385, upload-time = "2025-06-14T20:45:33.359Z" }, + { url = "https://files.pythonhosted.org/packages/c2/16/2f19d8a95f0cf8e940f7b7fb506ced805d5522b4118336c8e640c34517ae/pyobjc_framework_authenticationservices-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c15bb81282356f3f062ac79ff4166c93097448edc44b17dcf686e1dac78cc832", size = 20636, upload-time = "2025-11-14T09:36:48.35Z" }, + { url = "https://files.pythonhosted.org/packages/f1/1d/e9f296fe1ee9a074ff6c45ce9eb109fc3b45696de000f373265c8e42fd47/pyobjc_framework_authenticationservices-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:6fd5ce10fe5359cbbfe03eb12cab3e01992b32ab65653c579b00ac93cf674985", size = 20738, upload-time = "2025-11-14T09:36:51.094Z" }, ] [[package]] name = "pyobjc-framework-automaticassessmentconfiguration" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3d/39/d4c94e0245d290b83919854c4f205851cc0b2603f843448fdfb8e74aad71/pyobjc_framework_automaticassessmentconfiguration-11.1.tar.gz", hash = "sha256:70eadbf8600101901a56fcd7014d8941604e14f3b3728bc4fb0178a9a9420032", size = 24933, upload-time = "2025-06-14T20:56:43.984Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/24/080afe8189c47c4bb3daa191ccfd962400ca31a67c14b0f7c2d002c2e249/pyobjc_framework_automaticassessmentconfiguration-12.1.tar.gz", hash = "sha256:2b732c02d9097682ca16e48f5d3b10056b740bc091e217ee4d5715194c8970b1", size = 21895, upload-time = "2025-11-14T10:09:08.779Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/ca/f4ee1c9c274e0a41f8885f842fc78e520a367437edf9ca86eca46709e62d/pyobjc_framework_automaticassessmentconfiguration-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:50cc5466bec1f58f79921d49544b525b56897cb985dfcfabf825ee231c27bcfc", size = 9167, upload-time = "2025-06-14T20:45:39.52Z" }, - { url = "https://files.pythonhosted.org/packages/5e/e0/5a67f8ee0393447ca8251cbd06788cb7f3a1f4b9b052afd2e1b2cdfcb504/pyobjc_framework_automaticassessmentconfiguration-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:55d1684dd676730fb1afbc7c67e0669e3a7159f18c126fea7453fe6182c098f9", size = 9193, upload-time = "2025-06-14T20:45:40.52Z" }, + { url = "https://files.pythonhosted.org/packages/fc/c9/4d2785565cc470daa222f93f3d332af97de600aef6bd23507ec07501999d/pyobjc_framework_automaticassessmentconfiguration-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d94a4a3beb77b3b2ab7b610c4b41e28593d15571724a9e6ab196b82acc98dc13", size = 9316, upload-time = "2025-11-14T09:37:05.052Z" }, + { url = "https://files.pythonhosted.org/packages/f2/b2/fbec3d649bf275d7a9604e5f56015be02ef8dcf002f4ae4d760436b8e222/pyobjc_framework_automaticassessmentconfiguration-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c2e22ea67d7e6d6a84d968169f83d92b59857a49ab12132de07345adbfea8a62", size = 9332, upload-time = "2025-11-14T09:37:07.083Z" }, ] [[package]] name = "pyobjc-framework-automator" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/63/9f/097ed9f4de9e9491a1b08bb7d85d35a95d726c9e9f5f5bf203b359a436b6/pyobjc_framework_automator-11.1.tar.gz", hash = "sha256:9b46c55a4f9ae2b3c39ff560f42ced66bdd18c093188f0b5fc4060ad911838e4", size = 201439, upload-time = "2025-06-14T20:56:44.767Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/08/362bf6ac2bba393c46cf56078d4578b692b56857c385e47690637a72f0dd/pyobjc_framework_automator-12.1.tar.gz", hash = "sha256:7491a99347bb30da3a3f744052a03434ee29bee3e2ae520576f7e796740e4ba7", size = 186068, upload-time = "2025-11-14T10:09:20.82Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f0/c0/ebcc5a041440625ca984cde4ff96bc3e2cac4e5a37ca5bf4506ef4a98c54/pyobjc_framework_automator-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bf675a19edd97de9c19dcfd0fea9af9ebbd3409786c162670d1d71cb2738e341", size = 10004, upload-time = "2025-06-14T20:45:46.111Z" }, - { url = "https://files.pythonhosted.org/packages/0e/1e/3ed1df2168e596151da2329258951dae334e194d7de3b117c7e29a768ffc/pyobjc_framework_automator-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:af5941f8d90167244209b352512b7779e5590d17dc1e703e087a6cfe79ee3d64", size = 10029, upload-time = "2025-06-14T20:45:46.823Z" }, + { url = "https://files.pythonhosted.org/packages/e7/99/480e07eef053a2ad2a5cf1e15f71982f21d7f4119daafac338fa0352309c/pyobjc_framework_automator-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4f3d96da10d28c5c197193a9d805a13157b1cb694b6c535983f8572f5f8746ea", size = 10016, upload-time = "2025-11-14T09:37:18.621Z" }, + { url = "https://files.pythonhosted.org/packages/e3/36/2e8c36ddf20d501f9d344ed694e39021190faffc44b596f3a430bf437174/pyobjc_framework_automator-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4df9aec77f0fbca66cd3534d1b8398fe6f3e3c2748c0fc12fec2546c7f2e3ffd", size = 10034, upload-time = "2025-11-14T09:37:20.293Z" }, ] [[package]] name = "pyobjc-framework-avfoundation" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, @@ -2232,58 +2260,58 @@ dependencies = [ { name = "pyobjc-framework-coremedia", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3c/1f/90cdbce1d3b4861cbb17c12adf57daeec32477eb1df8d3f9ab8551bdadfb/pyobjc_framework_avfoundation-11.1.tar.gz", hash = "sha256:6663056cc6ca49af8de6d36a7fff498f51e1a9a7f1bde7afba718a8ceaaa7377", size = 832178, upload-time = "2025-06-14T20:56:46.329Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/42/c026ab308edc2ed5582d8b4b93da6b15d1b6557c0086914a4aabedd1f032/pyobjc_framework_avfoundation-12.1.tar.gz", hash = "sha256:eda0bb60be380f9ba2344600c4231dd58a3efafa99fdc65d3673ecfbb83f6fcb", size = 310047, upload-time = "2025-11-14T10:09:40.069Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0f/48/31286b2b09a619d8047256d7180e0d511be71ab598e5f54f034977b59bbf/pyobjc_framework_avfoundation-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8a0ccbdba46b69dec1d12eea52eef56fcd63c492f73e41011bb72508b2aa2d0e", size = 70711, upload-time = "2025-06-14T20:45:52.461Z" }, - { url = "https://files.pythonhosted.org/packages/43/30/d5d03dd4a508bdaa2156ff379e9e109020de23cbb6316c5865d341aa6db1/pyobjc_framework_avfoundation-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:94f065db4e87b1baebb5cf9f464cf9d82c5f903fff192001ebc974d9e3132c7e", size = 70746, upload-time = "2025-06-14T20:45:53.253Z" }, + { url = "https://files.pythonhosted.org/packages/9a/5a/4ef36b309138840ff8cd85364f66c29e27023f291004c335a99f6e87e599/pyobjc_framework_avfoundation-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:82cc2c2d9ab6cc04feeb4700ff251d00f1fcafff573c63d4e87168ff80adb926", size = 83328, upload-time = "2025-11-14T09:37:40.808Z" }, + { url = "https://files.pythonhosted.org/packages/a6/00/ca471e5dd33f040f69320832e45415d00440260bf7f8221a9df4c4662659/pyobjc_framework_avfoundation-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bf634f89265b4d93126153200d885b6de4859ed6b3bc65e69ff75540bc398406", size = 83375, upload-time = "2025-11-14T09:37:47.262Z" }, ] [[package]] name = "pyobjc-framework-avkit" -version = "11.1" +version = "12.1" 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'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/61/ff/9f41f2b8de786871184b48c4e5052cb7c9fcc204e7fee06687fa32b08bed/pyobjc_framework_avkit-11.1.tar.gz", hash = "sha256:d948204a7b94e0e878b19a909f9b33342e19d9ea519571d66a21fce8f72e3263", size = 46825, upload-time = "2025-06-14T20:56:47.494Z" } +sdist = { url = "https://files.pythonhosted.org/packages/34/a9/e44db1a1f26e2882c140f1d502d508b1f240af9048909dcf1e1a687375b4/pyobjc_framework_avkit-12.1.tar.gz", hash = "sha256:a5c0ddb0cb700f9b09c8afeca2c58952d554139e9bb078236d2355b1fddfb588", size = 28473, upload-time = "2025-11-14T10:09:43.105Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/6c/ee7504367f4a9337d3e78cd34beb9fcb58ad30e274c2a9f1d8058b9837f2/pyobjc_framework_avkit-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:88f70e2a399e43ce7bc3124b3b35d65537daddb358ea542fbb0146fa6406be8a", size = 11517, upload-time = "2025-06-14T20:45:59.676Z" }, - { url = "https://files.pythonhosted.org/packages/b2/2f/6ec6a4ec7eb9ca329f36bbd2a51750fe5064d44dd437d8615abb7121ec93/pyobjc_framework_avkit-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ef9cd9fe37c6199bfde7ee5cd6e76ede23a6797932882785c53ef3070e209afb", size = 11539, upload-time = "2025-06-14T20:46:00.375Z" }, + { url = "https://files.pythonhosted.org/packages/8c/68/409ee30f3418b76573c70aa05fa4c38e9b8b1d4864093edcc781d66019c2/pyobjc_framework_avkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:78bd31a8aed48644e5407b444dec8b1e15ff77af765607b52edf88b8f1213ac7", size = 11583, upload-time = "2025-11-14T09:38:17.569Z" }, + { url = "https://files.pythonhosted.org/packages/75/34/e77b18f7ed0bd707afd388702e910bdf2d0acee39d1139e8619c916d3eb4/pyobjc_framework_avkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eef2c0a51465de025a4509db05ef18ca2b678bb00ee0a8fbad7fd470edfd58f9", size = 11613, upload-time = "2025-11-14T09:38:19.78Z" }, ] [[package]] name = "pyobjc-framework-avrouting" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cf/42/94bc18b968a4ee8b6427257f907ffbfc97f8ba6a6202953da149b649d638/pyobjc_framework_avrouting-11.1.tar.gz", hash = "sha256:7db1291d9f53cc58d34b2a826feb721a85f50ceb5e71952e8762baacd3db3fc0", size = 21069, upload-time = "2025-06-14T20:56:48.57Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6e/83/15bf6c28ec100dae7f92d37c9e117b3b4ee6b4873db062833e16f1cfd6c4/pyobjc_framework_avrouting-12.1.tar.gz", hash = "sha256:6a6c5e583d14f6501df530a9d0559a32269a821fc8140e3646015f097155cd1c", size = 20031, upload-time = "2025-11-14T10:09:45.701Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/54/d4/0d17fd5a761d8a3d7dab0e096315de694b47dd48d2bb9655534e44399385/pyobjc_framework_avrouting-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:45cbabbf69764b2467d78adb8f3b7f209d1a8ee690e19f9a32d05c62a9c3a131", size = 8192, upload-time = "2025-06-14T20:46:05.479Z" }, - { url = "https://files.pythonhosted.org/packages/01/17/ce199bc7fb3ba1f7b0474554bd71d1bdd3d5a141e1d9722ff9f46c104e1d/pyobjc_framework_avrouting-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:dc309e175abf3961f933f8b341c0504b17f4717931242ebb121a83256b8b5c13", size = 8212, upload-time = "2025-06-14T20:46:06.17Z" }, + { url = "https://files.pythonhosted.org/packages/69/a7/5c5725db9c91b492ffbd4ae3e40025deeb9e60fcc7c8fbd5279b52280b95/pyobjc_framework_avrouting-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9a79f05fb66e337cabc19a9d949c8b29a5145c879f42e29ba02b601b7700d1bb", size = 8431, upload-time = "2025-11-14T09:38:33.018Z" }, + { url = "https://files.pythonhosted.org/packages/68/54/fa24f666525c1332a11b2de959c9877b0fe08f00f29ecf96964b24246c13/pyobjc_framework_avrouting-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4c0fb0d3d260527320377a70c87688ca5e4a208b09fddcae2b4257d7fe9b1e18", size = 8450, upload-time = "2025-11-14T09:38:34.941Z" }, ] [[package]] name = "pyobjc-framework-backgroundassets" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/08/76/21e1632a212f997d7a5f26d53eb997951978916858039b79f43ebe3d10b2/pyobjc_framework_backgroundassets-11.1.tar.gz", hash = "sha256:2e14b50539d96d5fca70c49f21b69fdbad81a22549e3630f5e4f20d5c0204fc2", size = 24803, upload-time = "2025-06-14T20:56:49.566Z" } +sdist = { url = "https://files.pythonhosted.org/packages/34/d1/e917fba82790495152fd3508c5053827658881cf7e9887ba60def5e3f221/pyobjc_framework_backgroundassets-12.1.tar.gz", hash = "sha256:8da34df9ae4519c360c429415477fdaf3fbba5addbc647b3340b8783454eb419", size = 26210, upload-time = "2025-11-14T10:09:48.792Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/74/ac/b1cb5c0ec2691ea225d53c2b9411d5ea1896f8f72eb5ca92978664443bb0/pyobjc_framework_backgroundassets-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bd371ce08d1b79f540d5994139898097b83b1d4e4471c264892433d448b24de0", size = 9691, upload-time = "2025-06-14T20:46:12.197Z" }, - { url = "https://files.pythonhosted.org/packages/ad/77/a6ad2df35fd71b3c26f52698d25174899ba1be134766022f5bf804ebf12d/pyobjc_framework_backgroundassets-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:13bf451c59b409b6ce1ac0e717a970a1b03bca7a944a7f19219da0d46ab7c561", size = 9707, upload-time = "2025-06-14T20:46:12.88Z" }, + { url = "https://files.pythonhosted.org/packages/c1/49/33c1c3eaf26a7d89dd414e14939d4f02063d66252d0f51c02082350223e0/pyobjc_framework_backgroundassets-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:17de7990b5ea8047d447339f9e9e6f54b954ffc06647c830932a1688c4743fea", size = 10763, upload-time = "2025-11-14T09:38:46.671Z" }, + { url = "https://files.pythonhosted.org/packages/de/34/bbba61f0e8ecb0fe0da7aa2c9ea15f7cb0dca2fb2914fcdcd77b782b5c11/pyobjc_framework_backgroundassets-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2c11cb98650c1a4bc68eeb4b040541ba96613434c5957e98e9bb363413b23c91", size = 10786, upload-time = "2025-11-14T09:38:48.341Z" }, ] [[package]] name = "pyobjc-framework-browserenginekit" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, @@ -2292,82 +2320,82 @@ dependencies = [ { name = "pyobjc-framework-coremedia", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/30/75/087270d9f81e913b57c7db58eaff8691fa0574b11faf9302340b3b8320f1/pyobjc_framework_browserenginekit-11.1.tar.gz", hash = "sha256:918440cefb10480024f645169de3733e30ede65e41267fa12c7b90c264a0a479", size = 31944, upload-time = "2025-06-14T20:56:50.195Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5d/b9/39f9de1730e6f8e73be0e4f0c6087cd9439cbe11645b8052d22e1fb8e69b/pyobjc_framework_browserenginekit-12.1.tar.gz", hash = "sha256:6a1a34a155778ab55ab5f463e885f2a3b4680231264e1fe078e62ddeccce49ed", size = 29120, upload-time = "2025-11-14T10:09:51.582Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ea/29/ec0a0cc6fb15911769cb8e5ad8ada85e3f5cf4889fafbb90d936c6b7053b/pyobjc_framework_browserenginekit-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:29b5f5949170af0235485e79aa465a7af2b2e0913d0c2c9ab1ac033224a90edb", size = 11088, upload-time = "2025-06-14T20:46:18.696Z" }, - { url = "https://files.pythonhosted.org/packages/89/90/a50bb66a5e041ace99b6c8b1df43b38d5f2e1bf771f57409e4aebf1dfae5/pyobjc_framework_browserenginekit-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9b815b167533015d62832b956e9cfb962bd2026f5a4ccd66718cf3bb2e15ab27", size = 11115, upload-time = "2025-06-14T20:46:19.401Z" }, + { url = "https://files.pythonhosted.org/packages/f2/a4/2d576d71b2e4b3e1a9aa9fd62eb73167d90cdc2e07b425bbaba8edd32ff5/pyobjc_framework_browserenginekit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:41229c766fb3e5bba2de5e580776388297303b4d63d3065fef3f67b77ec46c3f", size = 11526, upload-time = "2025-11-14T09:38:58.861Z" }, + { url = "https://files.pythonhosted.org/packages/46/e0/8d2cebbfcfd6aacb805ae0ae7ba931f6a39140540b2e1e96719e7be28359/pyobjc_framework_browserenginekit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d15766bb841b081447015c9626e2a766febfe651f487893d29c5d72bef976b94", size = 11545, upload-time = "2025-11-14T09:39:00.988Z" }, ] [[package]] name = "pyobjc-framework-businesschat" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/85/be/9d9d9d9383c411a58323ea510d768443287ca21610af652b815b3205ea80/pyobjc_framework_businesschat-11.1.tar.gz", hash = "sha256:69589d2f0cb4e7892e5ecc6aed79b1abd1ec55c099a7faacae6a326bc921259d", size = 12698, upload-time = "2025-06-14T20:56:51.173Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4d/da/bc09b6ed19e9ea38ecca9387c291ca11fa680a8132d82b27030f82551c23/pyobjc_framework_businesschat-12.1.tar.gz", hash = "sha256:f6fa3a8369a1a51363e1757530128741d9d09ed90692a1d6777a4c0fbad25868", size = 12055, upload-time = "2025-11-14T10:09:53.436Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/87/a4/5b8bb268b263678c0908cdaa8bed2534a6caac5862d05236f6c361d130ba/pyobjc_framework_businesschat-11.1-py2.py3-none-any.whl", hash = "sha256:7fdc1219b988ce3ae896bffd01f547c06cec3b4e4b2d0aa04d251444d7f1c2db", size = 3458, upload-time = "2025-06-14T20:46:24.651Z" }, + { url = "https://files.pythonhosted.org/packages/53/88/4c727424b05efa33ed7f6c45e40333e5a8a8dc5bb238e34695addd68463b/pyobjc_framework_businesschat-12.1-py2.py3-none-any.whl", hash = "sha256:f66ce741507b324de3c301d72ba0cfa6aaf7093d7235972332807645c118cc29", size = 3474, upload-time = "2025-11-14T09:39:10.771Z" }, ] [[package]] name = "pyobjc-framework-calendarstore" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/41/df/7ca8ee65b16d5fc862d7e8664289472eed918cf4d76921de6bdaa1461c65/pyobjc_framework_calendarstore-11.1.tar.gz", hash = "sha256:858ee00e6a380d9c086c2d7db82c116a6c406234038e0ec8fc2ad02e385dc437", size = 68215, upload-time = "2025-06-14T20:56:51.799Z" } +sdist = { url = "https://files.pythonhosted.org/packages/88/41/ae955d1c44dcc18b5b9df45c679e9a08311a0f853b9d981bca760cf1eef2/pyobjc_framework_calendarstore-12.1.tar.gz", hash = "sha256:f9a798d560a3c99ad4c0d2af68767bc5695d8b1aabef04d8377861cd1d6d1670", size = 52272, upload-time = "2025-11-14T10:09:58.48Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/94/69cb863bd88349df0f6cf491fd3ca4d674816c4d66270f9e2620cc6e16ed/pyobjc_framework_calendarstore-11.1-py2.py3-none-any.whl", hash = "sha256:bf066e17392c978becf17a61863eb81727bf593a2bfdab261177126072557e24", size = 5265, upload-time = "2025-06-14T20:46:25.457Z" }, + { url = "https://files.pythonhosted.org/packages/fa/70/f68aebdb7d3fa2dec2e9da9e9cdaa76d370de326a495917dbcde7bb7711e/pyobjc_framework_calendarstore-12.1-py2.py3-none-any.whl", hash = "sha256:18533e0fcbcdd29ee5884dfbd30606710f65df9b688bf47daee1438ee22e50cc", size = 5285, upload-time = "2025-11-14T09:39:12.473Z" }, ] [[package]] name = "pyobjc-framework-callkit" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/51/d5/4f0b62ab35be619e8c8d96538a03cf56fde6fd53540e1837e0fa588b3f6c/pyobjc_framework_callkit-11.1.tar.gz", hash = "sha256:b84d5ea38dff0cbe0754f5f9f6f33c742e216f12e7166179a8ec2cf4b0bfca94", size = 46648, upload-time = "2025-06-14T20:56:52.579Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1a/c0/1859d4532d39254df085309aff55b85323576f00a883626325af40da4653/pyobjc_framework_callkit-12.1.tar.gz", hash = "sha256:fd6dc9688b785aab360139d683be56f0844bf68bf5e45d0eb770cb68221083cc", size = 29171, upload-time = "2025-11-14T10:10:01.336Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/f8/6e368225634cad9e457c4f8f0580ed318cb2f2c8110f2e56935fc12502f3/pyobjc_framework_callkit-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1db8b74abd6489d73c8619972730bea87a7d1f55d47649150fc1a30fdc6840fb", size = 11211, upload-time = "2025-06-14T20:46:27.146Z" }, - { url = "https://files.pythonhosted.org/packages/18/2a/209572a6dba6768a57667e1f87a83ce8cadf18de5d6b1a91b95ce548d0f8/pyobjc_framework_callkit-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:554e09ca3dab44d93a89927d9e300f004d2ef0db020b10425a4622b432e7b684", size = 11269, upload-time = "2025-06-14T20:46:28.164Z" }, + { url = "https://files.pythonhosted.org/packages/2b/f6/aafd14b31e00d59d830f9a8e8e46c4f41a249f0370499d5b017599362cf1/pyobjc_framework_callkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e73beae08e6a32bcced8d5bdb45b52d6a0866dd1485eaaddba6063f17d41fcb0", size = 11273, upload-time = "2025-11-14T09:39:16.837Z" }, + { url = "https://files.pythonhosted.org/packages/2e/b7/b3a498b14751b4be6af5272c9be9ded718aa850ebf769b052c7d610a142a/pyobjc_framework_callkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:12adc0ace464a057f8908187698e1d417c6c53619797a69d096f4329bffb1089", size = 11334, upload-time = "2025-11-14T09:39:18.622Z" }, ] [[package]] name = "pyobjc-framework-carbon" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/39/a4/d751851865d9a78405cfec0c8b2931b1e96b9914e9788cd441fa4e8290d0/pyobjc_framework_carbon-11.1.tar.gz", hash = "sha256:047f098535479efa3ab89da1ebdf3cf9ec0b439a33a4f32806193886e9fcea71", size = 37291, upload-time = "2025-06-14T20:56:53.642Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0c/0f/9ab8e518a4e5ac4a1e2fdde38a054c32aef82787ff7f30927345c18b7765/pyobjc_framework_carbon-12.1.tar.gz", hash = "sha256:57a72807db252d5746caccc46da4bd20ff8ea9e82109af9f72735579645ff4f0", size = 37293, upload-time = "2025-11-14T10:10:04.464Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/84/44/f1a20b5aa3833af4d461074c479263a410ef90d17dbec11f78ad9c34dbab/pyobjc_framework_carbon-11.1-py2.py3-none-any.whl", hash = "sha256:1bf66853e939315ad7ee968170b16dd12cb838c42b80dfcd5354687760998825", size = 4753, upload-time = "2025-06-14T20:46:33.141Z" }, + { url = "https://files.pythonhosted.org/packages/a4/9e/91853c8f98b9d5bccf464113908620c94cc12c2a3e4625f3ce172e3ea4bc/pyobjc_framework_carbon-12.1-py2.py3-none-any.whl", hash = "sha256:f8b719b3c7c5cf1d61ac7c45a8a70b5e5e5a83fa02f5194c2a48a7e81a3d1b7f", size = 4625, upload-time = "2025-11-14T09:39:27.937Z" }, ] [[package]] name = "pyobjc-framework-cfnetwork" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6f/49/7b24172e3d6eb0ddffc33a7498a2bea264aa2958c3fecaeb463bef88f0b8/pyobjc_framework_cfnetwork-11.1.tar.gz", hash = "sha256:ad600163eeadb7bf71abc51a9b6f2b5462a018d3f9bb1510c5ce3fdf2f22959d", size = 79069, upload-time = "2025-06-14T20:56:54.615Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d2/6a/f5f0f191956e187db85312cbffcc41bf863670d121b9190b4a35f0d36403/pyobjc_framework_cfnetwork-12.1.tar.gz", hash = "sha256:2d16e820f2d43522c793f55833fda89888139d7a84ca5758548ba1f3a325a88d", size = 44383, upload-time = "2025-11-14T10:10:08.428Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/61/74b0d0430807615b7f91a688a871ffd94a61d4764a101e2a53e0c95dd05e/pyobjc_framework_cfnetwork-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d7a24746d0754b3a0042def2cd64aa205e5614f12ea0de9461c8e26d97633c72", size = 18953, upload-time = "2025-06-14T20:46:35.409Z" }, - { url = "https://files.pythonhosted.org/packages/c2/31/05b4fb79e7f738f7f7d7a58734de2fab47d9a1fb219c2180e8c07efe2550/pyobjc_framework_cfnetwork-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:70beb8095df76e0e8eb7ab218be1e69ae180e01a4d77f7cad73c97b4eb7a296a", size = 19141, upload-time = "2025-06-14T20:46:36.134Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7e/82aca783499b690163dd19d5ccbba580398970874a3431bfd7c14ceddbb3/pyobjc_framework_cfnetwork-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3bf93c0f3d262f629e72f8dd43384d0930ed8e610b3fc5ff555c0c1a1e05334a", size = 18949, upload-time = "2025-11-14T09:39:32.924Z" }, + { url = "https://files.pythonhosted.org/packages/f9/0b/28034e63f3a25b30ede814469c3f57d44268cbced19664c84a8664200f9d/pyobjc_framework_cfnetwork-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:92760da248c757085fc39bce4388a0f6f0b67540e51edf60a92ad60ca907d071", size = 19135, upload-time = "2025-11-14T09:39:36.382Z" }, ] [[package]] name = "pyobjc-framework-cinematic" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, @@ -2376,28 +2404,28 @@ dependencies = [ { name = "pyobjc-framework-coremedia", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-metal", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/57/6f/c2d0b49e01e654496a1781bafb9da72a6fbd00f5abb39dc4a3a0045167c7/pyobjc_framework_cinematic-11.1.tar.gz", hash = "sha256:efde39a6a2379e1738dbc5434b2470cd187cf3114ffb81390b3b1abda470b382", size = 25522, upload-time = "2025-06-14T20:56:55.379Z" } +sdist = { url = "https://files.pythonhosted.org/packages/67/4e/f4cc7f9f7f66df0290c90fe445f1ff5aa514c6634f5203fe049161053716/pyobjc_framework_cinematic-12.1.tar.gz", hash = "sha256:795068c30447548c0e8614e9c432d4b288b13d5614622ef2f9e3246132329b06", size = 21215, upload-time = "2025-11-14T10:10:10.795Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/05/bd/a9b51c770bd96546a101c9e9994f851b87336f168a77048241517ca4db8c/pyobjc_framework_cinematic-11.1-py2.py3-none-any.whl", hash = "sha256:b62c024c1a9c7890481bc2fdfaf0cd3c251a4a08357d57dc1795d98920fcdbd1", size = 4562, upload-time = "2025-06-14T20:46:40.989Z" }, + { url = "https://files.pythonhosted.org/packages/c9/a0/cd85c827ce5535c08d936e5723c16ee49f7ff633f2e9881f4f58bf83e4ce/pyobjc_framework_cinematic-12.1-py2.py3-none-any.whl", hash = "sha256:c003543bb6908379680a93dfd77a44228686b86c118cf3bc930f60241d0cd141", size = 5031, upload-time = "2025-11-14T09:39:49.003Z" }, ] [[package]] name = "pyobjc-framework-classkit" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7a/8b/5150b4faddd15d5dd795bc62b2256c4f7dafc983cfa694fcf88121ea0016/pyobjc_framework_classkit-11.1.tar.gz", hash = "sha256:ee1e26395eb00b3ed5442e3234cdbfe925d2413185af38eca0477d7166651df4", size = 39831, upload-time = "2025-06-14T20:56:56.036Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/ef/67815278023b344a79c7e95f748f647245d6f5305136fc80615254ad447c/pyobjc_framework_classkit-12.1.tar.gz", hash = "sha256:8d1e9dd75c3d14938ff533d88b72bca2d34918e4461f418ea323bfb2498473b4", size = 26298, upload-time = "2025-11-14T10:10:13.406Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/89/86/5b9ef1d5aa3f4835d164c9be46afae634911db56c6ad7795e212ef9bb50b/pyobjc_framework_classkit-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:018da363d06f3615c07a8623cbdb024a31b1f8b96a933ff2656c0e903063842c", size = 8895, upload-time = "2025-06-14T20:46:42.689Z" }, - { url = "https://files.pythonhosted.org/packages/75/79/2552fd5e1da73dffb35589469b3cd8c0928e3100462761350d19ea922e59/pyobjc_framework_classkit-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:161dcb9b718649e6331a5eab5a76c2b43a9b322b15b37b3f8f9c5faad12ee6d1", size = 8911, upload-time = "2025-06-14T20:46:43.714Z" }, + { url = "https://files.pythonhosted.org/packages/14/e2/67bd062fbc9761c34b9911ed099ee50ccddc3032779ce420ca40083ee15c/pyobjc_framework_classkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bd90aacc68eff3412204a9040fa81eb18348cbd88ed56d33558349f3e51bff52", size = 8857, upload-time = "2025-11-14T09:39:53.283Z" }, + { url = "https://files.pythonhosted.org/packages/87/5e/cf43c647af872499fc8e80cc6ac6e9ad77d9c77861dc2e62bdd9b01473ce/pyobjc_framework_classkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c027a3cd9be5fee3f605589118b8b278297c384a271f224c1a98b224e0c087e6", size = 8877, upload-time = "2025-11-14T09:39:54.979Z" }, ] [[package]] name = "pyobjc-framework-cloudkit" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, @@ -2406,880 +2434,907 @@ dependencies = [ { name = "pyobjc-framework-coredata", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-corelocation", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/58/a6/bfe5be55ed95704efca0e86b218155a9c801735107cedba3af8ea4580a05/pyobjc_framework_cloudkit-11.1.tar.gz", hash = "sha256:40d2dc4bf28c5be9b836b01e4d267a15d847d756c2a65530e1fcd79b2825e86d", size = 122778, upload-time = "2025-06-14T20:56:56.73Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/09/762ee4f3ae8568b8e0e5392c705bc4aa1929aa454646c124ca470f1bf9fc/pyobjc_framework_cloudkit-12.1.tar.gz", hash = "sha256:1dddd38e60863f88adb3d1d37d3b4ccb9cbff48c4ef02ab50e36fa40c2379d2f", size = 53730, upload-time = "2025-11-14T10:10:17.831Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/25/d9/5570a217cef8130708e860b86f4f22bb5827247c97121523a9dfd4784148/pyobjc_framework_cloudkit-11.1-py2.py3-none-any.whl", hash = "sha256:c583e40c710cf85ebe34173d1d2995e832a20127edc8899b2f35b13f98498af1", size = 10870, upload-time = "2025-06-14T20:46:48.781Z" }, + { url = "https://files.pythonhosted.org/packages/35/71/cbef7179bf1a594558ea27f1e5ad18f5c17ef71a8a24192aae16127bc849/pyobjc_framework_cloudkit-12.1-py2.py3-none-any.whl", hash = "sha256:875e37bf1a2ce3d05c2492692650104f2d908b56b71a0aedf6620bc517c6c9ca", size = 11090, upload-time = "2025-11-14T09:40:04.207Z" }, ] [[package]] name = "pyobjc-framework-cocoa" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4b/c5/7a866d24bc026f79239b74d05e2cf3088b03263da66d53d1b4cf5207f5ae/pyobjc_framework_cocoa-11.1.tar.gz", hash = "sha256:87df76b9b73e7ca699a828ff112564b59251bb9bbe72e610e670a4dc9940d038", size = 5565335, upload-time = "2025-06-14T20:56:59.683Z" } +sdist = { url = "https://files.pythonhosted.org/packages/02/a3/16ca9a15e77c061a9250afbae2eae26f2e1579eb8ca9462ae2d2c71e1169/pyobjc_framework_cocoa-12.1.tar.gz", hash = "sha256:5556c87db95711b985d5efdaaf01c917ddd41d148b1e52a0c66b1a2e2c5c1640", size = 2772191, upload-time = "2025-11-14T10:13:02.069Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/90/43/6841046aa4e257b6276cd23e53cacedfb842ecaf3386bb360fa9cc319aa1/pyobjc_framework_cocoa-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7b9a9b8ba07f5bf84866399e3de2aa311ed1c34d5d2788a995bdbe82cc36cfa0", size = 388177, upload-time = "2025-06-14T20:46:51.454Z" }, - { url = "https://files.pythonhosted.org/packages/68/da/41c0f7edc92ead461cced7e67813e27fa17da3c5da428afdb4086c69d7ba/pyobjc_framework_cocoa-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:806de56f06dfba8f301a244cce289d54877c36b4b19818e3b53150eb7c2424d0", size = 388983, upload-time = "2025-06-14T20:46:52.591Z" }, + { url = "https://files.pythonhosted.org/packages/3f/07/5760735c0fffc65107e648eaf7e0991f46da442ac4493501be5380e6d9d4/pyobjc_framework_cocoa-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f52228bcf38da64b77328787967d464e28b981492b33a7675585141e1b0a01e6", size = 383812, upload-time = "2025-11-14T09:40:53.169Z" }, + { url = "https://files.pythonhosted.org/packages/95/bf/ee4f27ec3920d5c6fc63c63e797c5b2cc4e20fe439217085d01ea5b63856/pyobjc_framework_cocoa-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:547c182837214b7ec4796dac5aee3aa25abc665757b75d7f44f83c994bcb0858", size = 384590, upload-time = "2025-11-14T09:41:17.336Z" }, ] [[package]] name = "pyobjc-framework-collaboration" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/66/49/9dbe8407d5dd663747267c1234d1b914bab66e1878d22f57926261a3063b/pyobjc_framework_collaboration-11.1.tar.gz", hash = "sha256:4564e3931bfc51773623d4f57f2431b58a39b75cb964ae5c48d27ee4dde2f4ea", size = 16839, upload-time = "2025-06-14T20:57:01.101Z" } +sdist = { url = "https://files.pythonhosted.org/packages/64/21/77fe64b39eae98412de1a0d33e9c735aa9949d53fff6b2d81403572b410b/pyobjc_framework_collaboration-12.1.tar.gz", hash = "sha256:2afa264d3233fc0a03a56789c6fefe655ffd81a2da4ba1dc79ea0c45931ad47b", size = 14299, upload-time = "2025-11-14T10:13:04.631Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/62/24/4c9deedcc62d223a45d4b4fa16162729923d2b3e2231467de6ecd079f3f8/pyobjc_framework_collaboration-11.1-py2.py3-none-any.whl", hash = "sha256:3629ea5b56c513fb330d43952afabb2df2a2ac2f9048b8ec6e8ab4486191390a", size = 4891, upload-time = "2025-06-14T20:46:59.734Z" }, + { url = "https://files.pythonhosted.org/packages/2a/66/1507de01f1e2b309f8e11553a52769e4e2e9939ed770b5b560ef5bc27bc1/pyobjc_framework_collaboration-12.1-py2.py3-none-any.whl", hash = "sha256:182d6e6080833b97f9bef61738ae7bacb509714538f0d7281e5f0814c804b315", size = 4907, upload-time = "2025-11-14T09:42:55.781Z" }, ] [[package]] name = "pyobjc-framework-colorsync" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b5/97/7613b6041f62c52f972e42dd5d79476b56b84d017a8b5e4add4d9cfaca36/pyobjc_framework_colorsync-11.1.tar.gz", hash = "sha256:7a346f71f34b2ccd1b020a34c219b85bf8b6f6e05283d503185aeb7767a269dd", size = 38999, upload-time = "2025-06-14T20:57:01.761Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c0/b4/706e4cc9db25b400201fc90f3edfaa1ab2d51b400b19437b043a68532078/pyobjc_framework_colorsync-12.1.tar.gz", hash = "sha256:d69dab7df01245a8c1bd536b9231c97993a5d1a2765d77692ce40ebbe6c1b8e9", size = 25269, upload-time = "2025-11-14T10:13:07.522Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/30/d5/c8fc7c47cbb9865058094dc9cf3f57879156ff55fb261cf199e7081d1db7/pyobjc_framework_colorsync-11.1-py2.py3-none-any.whl", hash = "sha256:d19d6da2c7175a3896a63c9b40a8ab98ade0779a5b40062789681501c33efd5c", size = 5971, upload-time = "2025-06-14T20:47:00.547Z" }, + { url = "https://files.pythonhosted.org/packages/e8/e1/82e45c712f43905ee1e6d585180764e8fa6b6f1377feb872f9f03c8c1fb8/pyobjc_framework_colorsync-12.1-py2.py3-none-any.whl", hash = "sha256:41e08d5b9a7af4b380c9adab24c7ff59dfd607b3073ae466693a3e791d8ffdc9", size = 6020, upload-time = "2025-11-14T09:42:57.504Z" }, +] + +[[package]] +name = "pyobjc-framework-compositorservices" +version = "12.1" +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'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/54/c5/0ba31d7af7e464b7f7ece8c2bd09112bdb0b7260848402e79ba6aacc622c/pyobjc_framework_compositorservices-12.1.tar.gz", hash = "sha256:028e357bbee7fbd3723339a321bbe14e6da5a772708a661a13eea5f17c89e4ab", size = 23292, upload-time = "2025-11-14T10:13:10.392Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/34/5a2de8d531dbb88023898e0b5d2ce8edee14751af6c70e6103f6aa31a669/pyobjc_framework_compositorservices-12.1-py2.py3-none-any.whl", hash = "sha256:9ef22d4eacd492e13099b9b8936db892cdbbef1e3d23c3484e0ed749f83c4984", size = 5910, upload-time = "2025-11-14T09:42:59.154Z" }, ] [[package]] name = "pyobjc-framework-contacts" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a6/85/34868b6447d552adf8674bac226b55c2baacacee0d67ee031e33805d6faa/pyobjc_framework_contacts-11.1.tar.gz", hash = "sha256:752036e7d8952a4122296d7772f274170a5f35a53ee6454a27f3e1d9603222cc", size = 84814, upload-time = "2025-06-14T20:57:02.582Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/a0/ce0542d211d4ea02f5cbcf72ee0a16b66b0d477a4ba5c32e00117703f2f0/pyobjc_framework_contacts-12.1.tar.gz", hash = "sha256:89bca3c5cf31404b714abaa1673577e1aaad6f2ef49d4141c6dbcc0643a789ad", size = 42378, upload-time = "2025-11-14T10:13:14.203Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/68/e1/27715ef476441cb05d4442b93fe6380a57a946cda008f70399cadb4ff1fd/pyobjc_framework_contacts-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:68148653f27c1eaeff2ad4831b5e68393071a382aab773629cd047ce55556726", size = 12067, upload-time = "2025-06-14T20:47:02.178Z" }, - { url = "https://files.pythonhosted.org/packages/30/c8/0d47af11112bf382e059cfe2dd03be98914f0621ddff8858bb9af864f8c5/pyobjc_framework_contacts-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:576ee4aec05d755444bff10b45833f73083b5b3d1b2740e133b92111f7765e54", size = 12141, upload-time = "2025-06-14T20:47:02.884Z" }, + { url = "https://files.pythonhosted.org/packages/94/f5/5d2c03cf5219f2e35f3f908afa11868e9096aff33b29b41d63f2de3595f2/pyobjc_framework_contacts-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8ab86070895a005239256d207e18209b1a79d35335b6604db160e8375a7165e6", size = 12086, upload-time = "2025-11-14T09:43:03.225Z" }, + { url = "https://files.pythonhosted.org/packages/32/c8/2c4638c0d06447886a34070eebb9ba57407d4dd5f0fcb7ab642568272b88/pyobjc_framework_contacts-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2e5ce33b686eb9c0a39351938a756442ea8dea88f6ae2f16bff5494a8569c687", size = 12165, upload-time = "2025-11-14T09:43:05.119Z" }, ] [[package]] name = "pyobjc-framework-contactsui" -version = "11.1" +version = "12.1" 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'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3f/57/8765b54a30edaa2a56df62e11e7c32e41b6ea300513256adffa191689368/pyobjc_framework_contactsui-11.1.tar.gz", hash = "sha256:5bc29ea2b10a342018e1b96be6b140c10ebe3cfb6417278770feef5e88026a1f", size = 20031, upload-time = "2025-06-14T20:57:03.603Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/0c/7bb7f898456a81d88d06a1084a42e374519d2e40a668a872b69b11f8c1f9/pyobjc_framework_contactsui-12.1.tar.gz", hash = "sha256:aaeca7c9e0c9c4e224d73636f9a558f9368c2c7422155a41fd4d7a13613a77c1", size = 18769, upload-time = "2025-11-14T10:13:16.301Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/38/02/f65f2eb6e2ad91c95e5a6b532fe8dd5cd0c190fbaff71e4a85346e16c0f6/pyobjc_framework_contactsui-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1c0f03c71e63daf5dbf760bf0e45620618a6f1ea62f8c17e288463c1fd4d2685", size = 7858, upload-time = "2025-06-14T20:47:08.346Z" }, - { url = "https://files.pythonhosted.org/packages/46/b6/50ec09f1bb18c422b8c079e02328689f32e977b43ab7651c05e8274854dc/pyobjc_framework_contactsui-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c34a6f27ef5aa4742cc44fd5b4d16fe1e1745ff839578b4c059faf2c58eee3ca", size = 7875, upload-time = "2025-06-14T20:47:09.041Z" }, + { url = "https://files.pythonhosted.org/packages/04/e3/8d330640bf0337289834334c54c599fec2dad38a8a3b736d40bcb5d8db6e/pyobjc_framework_contactsui-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:10e7ce3b105795919605be89ebeecffd656e82dbf1bafa5db6d51d6def2265ee", size = 7871, upload-time = "2025-11-14T09:43:16.973Z" }, + { url = "https://files.pythonhosted.org/packages/f3/ab/319aa52dfe6f836f4dc542282c2c13996222d4f5c9ea7ff8f391b12dac83/pyobjc_framework_contactsui-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:057f40d2f6eb1b169a300675ec75cc7a747cddcbcee8ece133e652a7086c5ab5", size = 7888, upload-time = "2025-11-14T09:43:18.502Z" }, ] [[package]] name = "pyobjc-framework-coreaudio" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/39/c0/4ab6005cf97e534725b0c14b110d4864b367c282b1c5b0d8f42aad74a83f/pyobjc_framework_coreaudio-11.1.tar.gz", hash = "sha256:b7b89540ae7efc6c1e3208ac838ef2acfc4d2c506dd629d91f6b3b3120e55c1b", size = 141032, upload-time = "2025-06-14T20:57:04.348Z" } +sdist = { url = "https://files.pythonhosted.org/packages/84/d1/0b884c5564ab952ff5daa949128c64815300556019c1bba0cf2ca752a1a0/pyobjc_framework_coreaudio-12.1.tar.gz", hash = "sha256:a9e72925fcc1795430496ce0bffd4ddaa92c22460a10308a7283ade830089fe1", size = 75077, upload-time = "2025-11-14T10:13:22.345Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/54/1d/81339c1087519a9f125396c717b85a05b49c2c54137bdf4ca01c1ccb6239/pyobjc_framework_coreaudio-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:73a46f0db2fa8ca2e8c47c3ddcc2751e67a0f8600246a6718553b15ee0dbbdb6", size = 35383, upload-time = "2025-06-14T20:47:14.234Z" }, - { url = "https://files.pythonhosted.org/packages/3d/fe/c43521642db98a4ec29fa535781c1316342bb52d5fc709696cbb1e8ca6cd/pyobjc_framework_coreaudio-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2538d1242dab4e27efb346eafbad50594e7e95597fa7220f0bab2099c825da55", size = 36765, upload-time = "2025-06-14T20:47:15.344Z" }, + { url = "https://files.pythonhosted.org/packages/9e/25/491ff549fd9a40be4416793d335bff1911d3d1d1e1635e3b0defbd2cf585/pyobjc_framework_coreaudio-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a452de6b509fa4a20160c0410b72330ac871696cd80237883955a5b3a4de8f2a", size = 35327, upload-time = "2025-11-14T09:43:32.523Z" }, + { url = "https://files.pythonhosted.org/packages/a9/48/05b5192122e23140cf583eac99ccc5bf615591d6ff76483ba986c38ee750/pyobjc_framework_coreaudio-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a5ad6309779663f846ab36fe6c49647e470b7e08473c3e48b4f004017bdb68a4", size = 36908, upload-time = "2025-11-14T09:43:36.108Z" }, ] [[package]] name = "pyobjc-framework-coreaudiokit" -version = "11.1" +version = "12.1" 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'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f1/4e/c49b26c60047c511727efe994b412276c487dfe90f1ee0fced0bddbdf8a3/pyobjc_framework_coreaudiokit-11.1.tar.gz", hash = "sha256:0b461c3d6123fda4da6b6aaa022efc918c1de2e126a5cf07d2189d63fa54ba40", size = 21955, upload-time = "2025-06-14T20:57:05.218Z" } +sdist = { url = "https://files.pythonhosted.org/packages/41/1c/5c7e39b9361d4eec99b9115b593edd9825388acd594cb3b4519f8f1ac12c/pyobjc_framework_coreaudiokit-12.1.tar.gz", hash = "sha256:b83624f8de3068ab2ca279f786be0804da5cf904ff9979d96007b69ef4869e1e", size = 20137, upload-time = "2025-11-14T10:13:24.611Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/07/44/0de5d26e383d0b00f2f44394db74e0953bc1e35b74072a67fde916e8e31e/pyobjc_framework_coreaudiokit-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4743fbd210159cffffb0a7b8e06bf8b8527ba4bf01e76806fae2696fd6990e77", size = 7234, upload-time = "2025-06-14T20:47:21.271Z" }, - { url = "https://files.pythonhosted.org/packages/18/27/d8ff6293851a7d9665724fa5c324d28200776ec10a04b850ba21ad1f9be1/pyobjc_framework_coreaudiokit-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:20440a2926b1d91da8efc8bc060e77c7a195cb0443dbf3770eaca9e597276748", size = 7266, upload-time = "2025-06-14T20:47:22.136Z" }, + { url = "https://files.pythonhosted.org/packages/c2/53/e4233fbe5b94b124f5612e1edc130a9280c4674a1d1bf42079ea14b816e1/pyobjc_framework_coreaudiokit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e1144c272f8d6429a34a6757700048f4631eb067c4b08d4768ddc28c371a7014", size = 7250, upload-time = "2025-11-14T09:43:53.208Z" }, + { url = "https://files.pythonhosted.org/packages/19/d7/f171c04c6496afeaad2ab658b0c810682c8407127edc94d4b3f3b90c2bb1/pyobjc_framework_coreaudiokit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:97d5dd857e73d5b597cfc980972b021314b760e2f5bdde7bbba0334fbf404722", size = 7273, upload-time = "2025-11-14T09:43:55.411Z" }, ] [[package]] name = "pyobjc-framework-corebluetooth" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3d/fe/2081dfd9413b7b4d719935c33762fbed9cce9dc06430f322d1e2c9dbcd91/pyobjc_framework_corebluetooth-11.1.tar.gz", hash = "sha256:1deba46e3fcaf5e1c314f4bbafb77d9fe49ec248c493ad00d8aff2df212d6190", size = 60337, upload-time = "2025-06-14T20:57:05.919Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/25/d21d6cb3fd249c2c2aa96ee54279f40876a0c93e7161b3304bf21cbd0bfe/pyobjc_framework_corebluetooth-12.1.tar.gz", hash = "sha256:8060c1466d90bbb9100741a1091bb79975d9ba43911c9841599879fc45c2bbe0", size = 33157, upload-time = "2025-11-14T10:13:28.064Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8c/75/3318e85b7328c99c752e40592a907fc5c755cddc6d73beacbb432f6aa2d0/pyobjc_framework_corebluetooth-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:433b8593eb1ea8b6262b243ec903e1de4434b768ce103ebe15aac249b890cc2a", size = 13143, upload-time = "2025-06-14T20:47:28.889Z" }, - { url = "https://files.pythonhosted.org/packages/8a/bc/083ea1ae57a31645df7fad59921528f6690995f7b7c84a203399ded7e7fe/pyobjc_framework_corebluetooth-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:36bef95a822c68b72f505cf909913affd61a15b56eeaeafea7302d35a82f4f05", size = 13163, upload-time = "2025-06-14T20:47:29.624Z" }, + { url = "https://files.pythonhosted.org/packages/57/7a/26ae106beb97e9c4745065edb3ce3c2bdd91d81f5b52b8224f82ce9d5fb9/pyobjc_framework_corebluetooth-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:37e6456c8a076bd5a2bdd781d0324edd5e7397ef9ac9234a97433b522efb13cf", size = 13189, upload-time = "2025-11-14T09:44:06.229Z" }, + { url = "https://files.pythonhosted.org/packages/2a/56/01fef62a479cdd6ff9ee40b6e062a205408ff386ce5ba56d7e14a71fcf73/pyobjc_framework_corebluetooth-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fe72c9732ee6c5c793b9543f08c1f5bdd98cd95dfc9d96efd5708ec9d6eeb213", size = 13209, upload-time = "2025-11-14T09:44:08.203Z" }, ] [[package]] name = "pyobjc-framework-coredata" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/00/e3/af497da7a7c895b6ff529d709d855a783f34afcc4b87ab57a1a2afb3f876/pyobjc_framework_coredata-11.1.tar.gz", hash = "sha256:fe9fd985f8e06c70c0fb1e6bbea5b731461f9e76f8f8d8e89c7c72667cdc6adf", size = 260628, upload-time = "2025-06-14T20:57:06.729Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3e/c5/8cd46cd4f1b7cf88bdeed3848f830ea9cdcc4e55cd0287a968a2838033fb/pyobjc_framework_coredata-12.1.tar.gz", hash = "sha256:1e47d3c5e51fdc87a90da62b97cae1bc49931a2bb064db1305827028e1fc0ffa", size = 124348, upload-time = "2025-11-14T10:13:36.435Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/29/d9/7f12bdba0503d0ef7b1ac26e05429aecc33b4aaf190155a6bec8b576850d/pyobjc_framework_coredata-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c66ae04cc658eafdfb987f9705e21f9782edee6773a8adb6bfa190500e4e7e29", size = 16428, upload-time = "2025-06-14T20:47:34.981Z" }, - { url = "https://files.pythonhosted.org/packages/5b/ac/77935aa9891bd6be952b1e6780df2bae748971dd0fe0b5155894004840bd/pyobjc_framework_coredata-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c9b2374784e67694a18fc8c120a12f11b355a20b643c01f23ae2ce87330a75e0", size = 16443, upload-time = "2025-06-14T20:47:35.711Z" }, + { url = "https://files.pythonhosted.org/packages/6b/a8/4c694c85365071baef36013a7460850dcf6ebfea0ba239e52d7293cdcb93/pyobjc_framework_coredata-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c861dc42b786243cbd96d9ea07d74023787d03637ef69a2f75a1191a2f16d9d6", size = 16395, upload-time = "2025-11-14T09:44:21.105Z" }, + { url = "https://files.pythonhosted.org/packages/a3/29/fe24dc81e0f154805534923a56fe572c3b296092f086cf5a239fccc2d46a/pyobjc_framework_coredata-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a3ee3581ca23ead0b152257e98622fe0bf7e7948f30a62a25a17cafe28fe015e", size = 16409, upload-time = "2025-11-14T09:44:23.582Z" }, ] [[package]] name = "pyobjc-framework-corehaptics" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5f/83/cc997ec4687a68214dd3ad1bdf64353305f5c7e827fad211adac4c28b39f/pyobjc_framework_corehaptics-11.1.tar.gz", hash = "sha256:e5da3a97ed6aca9b7268c8c5196c0a339773a50baa72d1502d3435dc1a2a80f1", size = 42722, upload-time = "2025-06-14T20:57:08.019Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3a/2f/74a3da79d9188b05dd4be4428a819ea6992d4dfaedf7d629027cf1f57bfc/pyobjc_framework_corehaptics-12.1.tar.gz", hash = "sha256:521dd2986c8a4266d583dd9ed9ae42053b11ae7d3aa89bf53fbee88307d8db10", size = 22164, upload-time = "2025-11-14T10:13:38.941Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/21/d0/0fb20c0f19beae53c905653ffdcbf32e3b4119420c737ff4733f7ebb3b29/pyobjc_framework_corehaptics-11.1-py2.py3-none-any.whl", hash = "sha256:8f8c47ccca5052d07f95d2f35e6e399c5ac1f2072ba9d9eaae902edf4e3a7af4", size = 5363, upload-time = "2025-06-14T20:47:40.582Z" }, + { url = "https://files.pythonhosted.org/packages/25/f4/f469d6a9cac7c195f3d08fa65f94c32dd1dcf97a54b481be648fb3a7a5f3/pyobjc_framework_corehaptics-12.1-py2.py3-none-any.whl", hash = "sha256:a3b07d36ddf5c86a9cdaa411ab53d09553d26ea04fc7d4f82d21a84f0fc05fc0", size = 5382, upload-time = "2025-11-14T09:44:34.725Z" }, ] [[package]] name = "pyobjc-framework-corelocation" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/95/ef/fbd2e01ec137208af7bfefe222773748d27f16f845b0efa950d65e2bd719/pyobjc_framework_corelocation-11.1.tar.gz", hash = "sha256:46a67b99925ee3d53914331759c6ee110b31bb790b74b05915acfca41074c206", size = 104508, upload-time = "2025-06-14T20:57:08.731Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/79/b75885e0d75397dc2fe1ed9ca80be2b64c18b817f5fb924277cb1bf7b163/pyobjc_framework_corelocation-12.1.tar.gz", hash = "sha256:3674e9353f949d91dde6230ad68f6d5748a7f0424751e08a2c09d06050d66231", size = 53511, upload-time = "2025-11-14T10:13:43.384Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/f9/8137e8bf86f8e6350298217dcc635fd6577d64b484f9d250ddb85a84efa0/pyobjc_framework_corelocation-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ea261e7d87c6f62f1b03c252c273ea7fd6f314e3e73c69c6fb3fe807bf183462", size = 12741, upload-time = "2025-06-14T20:47:42.583Z" }, - { url = "https://files.pythonhosted.org/packages/95/cb/282d59421cdb89a5e5fcce72fc37d6eeace98a2a86d71f3be3cd47801298/pyobjc_framework_corelocation-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:562e31124f80207becfd8df01868f73fa5aa70169cc4460e1209fb16916e4fb4", size = 12752, upload-time = "2025-06-14T20:47:43.273Z" }, + { url = "https://files.pythonhosted.org/packages/34/ac/44b6cb414ce647da8328d0ed39f0a8b6eb54e72189ce9049678ce2cb04c3/pyobjc_framework_corelocation-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ffc96b9ba504b35fe3e0fcfb0153e68fdfca6fe71663d240829ceab2d7122588", size = 12700, upload-time = "2025-11-14T09:44:38.717Z" }, + { url = "https://files.pythonhosted.org/packages/71/57/1b670890fbf650f1a00afe5ee897ea3856a4a1417c2304c633ee2e978ed0/pyobjc_framework_corelocation-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8c35ad29a062fea7d417fd8997a9309660ba7963f2847c004e670efbe6bb5b00", size = 12721, upload-time = "2025-11-14T09:44:41.185Z" }, ] [[package]] name = "pyobjc-framework-coremedia" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/95/5d/81513acd219df77a89176f1574d936b81ad6f6002225cabb64d55efb7e8d/pyobjc_framework_coremedia-11.1.tar.gz", hash = "sha256:82cdc087f61e21b761e677ea618a575d4c0dbe00e98230bf9cea540cff931db3", size = 216389, upload-time = "2025-06-14T20:57:09.546Z" } +sdist = { url = "https://files.pythonhosted.org/packages/da/7d/5ad600ff7aedfef8ba8f51b11d9aaacdf247b870bd14045d6e6f232e3df9/pyobjc_framework_coremedia-12.1.tar.gz", hash = "sha256:166c66a9c01e7a70103f3ca44c571431d124b9070612ef63a1511a4e6d9d84a7", size = 89566, upload-time = "2025-11-14T10:13:49.788Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/32/48/811ccea77d2c0d8156a489e2900298502eb6648d9c041c7f0c514c8f8a29/pyobjc_framework_coremedia-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:aacf47006e1c6bf6124fb2b5016a8d5fd5cf504b6b488f9eba4e389ab0f0a051", size = 29118, upload-time = "2025-06-14T20:47:48.895Z" }, - { url = "https://files.pythonhosted.org/packages/2c/d1/b3d004d6a2d2188d196779d92fe8cfa2533f5722cd216fbc4f0cffc63b24/pyobjc_framework_coremedia-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ea5055298af91e463ffa7977d573530f9bada57b8f2968dcc76a75e339b9a598", size = 29015, upload-time = "2025-06-14T20:47:49.655Z" }, + { url = "https://files.pythonhosted.org/packages/c8/bc/e66de468b3777d8fece69279cf6d2af51d2263e9a1ccad21b90c35c74b1b/pyobjc_framework_coremedia-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ee7b822c9bb674b5b0a70bfb133410acae354e9241b6983f075395f3562f3c46", size = 29503, upload-time = "2025-11-14T09:44:54.716Z" }, + { url = "https://files.pythonhosted.org/packages/c8/ae/f773cdc33c34a3f9ce6db829dbf72661b65c28ea9efaec8940364185b977/pyobjc_framework_coremedia-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:161a627f5c8cd30a5ebb935189f740e21e6cd94871a9afd463efdb5d51e255fa", size = 29396, upload-time = "2025-11-14T09:44:57.563Z" }, ] [[package]] name = "pyobjc-framework-coremediaio" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/64/68/9cef2aefba8e69916049ff43120e8794df8051bdf1f690a55994bbe4eb57/pyobjc_framework_coremediaio-11.1.tar.gz", hash = "sha256:bccd69712578b177144ded398f4695d71a765ef61204da51a21f0c90b4ad4c64", size = 108326, upload-time = "2025-06-14T20:57:10.435Z" } +sdist = { url = "https://files.pythonhosted.org/packages/08/8e/23baee53ccd6c011c965cff62eb55638b4088c3df27d2bf05004105d6190/pyobjc_framework_coremediaio-12.1.tar.gz", hash = "sha256:880b313b28f00b27775d630174d09e0b53d1cdbadb74216618c9dd5b3eb6806a", size = 51100, upload-time = "2025-11-14T10:13:54.277Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/aa/38/6bcddd7d57fa19173621aa29b46342756ed1a081103d24e4bdac1cf882fe/pyobjc_framework_coremediaio-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4438713ee4611d5310f4f2e71e557b6138bc79c0363e3d45ecb8c09227dfa58e", size = 17203, upload-time = "2025-06-14T20:47:55.781Z" }, - { url = "https://files.pythonhosted.org/packages/4b/b5/5dd941c1d7020a78b563a213fb8be7c6c3c1073c488914e158cd5417f4f7/pyobjc_framework_coremediaio-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:39ad2518de9943c474e5ca0037e78f92423c3352deeee6c513a489016dac1266", size = 17250, upload-time = "2025-06-14T20:47:56.505Z" }, + { url = "https://files.pythonhosted.org/packages/46/6c/88514f8938719f74aa13abb9fd5492499f1834391133809b4e125c3e7150/pyobjc_framework_coremediaio-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3da79c5b9785c5ccc1f5982de61d4d0f1ba29717909eb6720734076ccdc0633c", size = 17218, upload-time = "2025-11-14T09:45:15.294Z" }, + { url = "https://files.pythonhosted.org/packages/d4/0c/9425c53c9a8c26e468e065ba12ef076bab20197ff7c82052a6dddd46d42b/pyobjc_framework_coremediaio-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1108f8a278928fbca465f95123ea4a56456bd6571c1dc8b91793e6c61d624517", size = 17277, upload-time = "2025-11-14T09:45:17.457Z" }, ] [[package]] name = "pyobjc-framework-coremidi" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/06/ca/2ae5149966ccd78290444f88fa62022e2b96ed2fddd47e71d9fd249a9f82/pyobjc_framework_coremidi-11.1.tar.gz", hash = "sha256:095030c59d50c23aa53608777102bc88744ff8b10dfb57afe24b428dcd12e376", size = 107817, upload-time = "2025-06-14T20:57:11.245Z" } +sdist = { url = "https://files.pythonhosted.org/packages/75/96/2d583060a71a73c8a7e6d92f2a02675621b63c1f489f2639e020fae34792/pyobjc_framework_coremidi-12.1.tar.gz", hash = "sha256:3c6f1fd03997c3b0f20ab8545126b1ce5f0cddcc1587dffacad876c161da8c54", size = 55587, upload-time = "2025-11-14T10:13:58.903Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/37/fc/db75c55e492e5e34be637da2eeeaadbb579655b6d17159de419237bc9bdf/pyobjc_framework_coremidi-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5f8c2fdc9d1b7967e2a5ec0d5281eaddc00477bed9753aa14d5b881dc3a9ad8f", size = 24256, upload-time = "2025-06-14T20:48:02.448Z" }, - { url = "https://files.pythonhosted.org/packages/c2/2d/57c279dd74a9073d1416b10b05ebb9598f4868cad010d87f46ef4b517324/pyobjc_framework_coremidi-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:deb9120478a831a898f22f68737fc683bb9b937e77556e78b75986aebd349c55", size = 24277, upload-time = "2025-06-14T20:48:03.184Z" }, + { url = "https://files.pythonhosted.org/packages/76/d5/49b8720ec86f64e3dc3c804bd7e16fabb2a234a9a8b1b6753332ed343b4e/pyobjc_framework_coremidi-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:af3cdf195e8d5e30d1203889cc4107bebc6eb901aaa81bf3faf15e9ffaca0735", size = 24282, upload-time = "2025-11-14T09:45:32.288Z" }, + { url = "https://files.pythonhosted.org/packages/e3/2d/99520f6f1685e4cad816e55cbf6d85f8ce6ea908107950e2d37dc17219d8/pyobjc_framework_coremidi-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e84ffc1de59691c04201b0872e184fe55b5589f3a14876bd14460f3b5f3cd109", size = 24317, upload-time = "2025-11-14T09:45:34.92Z" }, ] [[package]] name = "pyobjc-framework-coreml" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0d/5d/4309f220981d769b1a2f0dcb2c5c104490d31389a8ebea67e5595ce1cb74/pyobjc_framework_coreml-11.1.tar.gz", hash = "sha256:775923eefb9eac2e389c0821b10564372de8057cea89f1ea1cdaf04996c970a7", size = 82005, upload-time = "2025-06-14T20:57:12.004Z" } +sdist = { url = "https://files.pythonhosted.org/packages/30/2d/baa9ea02cbb1c200683cb7273b69b4bee5070e86f2060b77e6a27c2a9d7e/pyobjc_framework_coreml-12.1.tar.gz", hash = "sha256:0d1a4216891a18775c9e0170d908714c18e4f53f9dc79fb0f5263b2aa81609ba", size = 40465, upload-time = "2025-11-14T10:14:02.265Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/76/9c/2218a8f457f56075a8a3f2490bd9d01c8e69c807139eaa0a6ac570531ca5/pyobjc_framework_coreml-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b5be7889ad99da1aca040238fd99af9ee87ea8a6628f24d33e2e4890b88dd139", size = 11414, upload-time = "2025-06-14T20:48:09.267Z" }, - { url = "https://files.pythonhosted.org/packages/3e/9e/a1b6d30b4f91c7cc4780e745e1e73a322bd3524a773f66f5eac0b1600d85/pyobjc_framework_coreml-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c768b03d72488b964d753392e9c587684961d8237b69cca848b3a5a00aea79c9", size = 11436, upload-time = "2025-06-14T20:48:10.048Z" }, + { url = "https://files.pythonhosted.org/packages/34/0f/f55369da4a33cfe1db38a3512aac4487602783d3a1d572d2c8c4ccce6abc/pyobjc_framework_coreml-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:16dafcfb123f022e62f47a590a7eccf7d0cb5957a77fd5f062b5ee751cb5a423", size = 11331, upload-time = "2025-11-14T09:45:50.445Z" }, + { url = "https://files.pythonhosted.org/packages/bb/39/4defef0deb25c5d7e3b7826d301e71ac5b54ef901b7dac4db1adc00f172d/pyobjc_framework_coreml-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:10dc8e8db53d7631ebc712cad146e3a9a9a443f4e1a037e844149a24c3c42669", size = 11356, upload-time = "2025-11-14T09:45:52.271Z" }, ] [[package]] name = "pyobjc-framework-coremotion" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a5/95/e469dc7100ea6b9c29a074a4f713d78b32a78d7ec5498c25c83a56744fc2/pyobjc_framework_coremotion-11.1.tar.gz", hash = "sha256:5884a568521c0836fac39d46683a4dea3d259a23837920897042ffb922d9ac3e", size = 67050, upload-time = "2025-06-14T20:57:12.705Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/eb/abef7d405670cf9c844befc2330a46ee59f6ff7bac6f199bf249561a2ca6/pyobjc_framework_coremotion-12.1.tar.gz", hash = "sha256:8e1b094d34084cc8cf07bedc0630b4ee7f32b0215011f79c9e3cd09d205a27c7", size = 33851, upload-time = "2025-11-14T10:14:05.619Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1d/3f/137c983dbccbdbc4a07fb2453e494af938078969bcde9252fbbad0ba939d/pyobjc_framework_coremotion-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:501248a726816e05552d1c1f7e2be2c7305cda792c46905d9aee7079dfad2eea", size = 10353, upload-time = "2025-06-14T20:48:15.365Z" }, - { url = "https://files.pythonhosted.org/packages/e9/17/ffa3cf9fde9df31f3d6ecb38507c61c6d8d81276d0a9116979cafd5a0ab7/pyobjc_framework_coremotion-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8c3b33228a170bf8495508a8923451ec600435c7bff93d7614f19c913baeafd1", size = 10368, upload-time = "2025-06-14T20:48:16.066Z" }, + { url = "https://files.pythonhosted.org/packages/77/fd/0d24796779e4d8187abbce5d06cfd7614496d57a68081c5ff1e978b398f9/pyobjc_framework_coremotion-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ed8cb67927985d97b1dd23ab6a4a1b716fc7c409c35349816108781efdcbb5b6", size = 10382, upload-time = "2025-11-14T09:46:03.438Z" }, + { url = "https://files.pythonhosted.org/packages/bc/75/89fa4aab818aeca21ac0a60b7ceb89a9e685df0ddd3828d36a6f84a0cff0/pyobjc_framework_coremotion-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a77908ab83c422030f913a2a761d196359ab47f6d1e7c76f21de2c6c05ea2f5f", size = 10406, upload-time = "2025-11-14T09:46:05.076Z" }, ] [[package]] name = "pyobjc-framework-coreservices" -version = "11.1" +version = "12.1" 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'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a8/a9/141d18019a25776f507992f9e7ffc051ca5a734848d8ea8d848f7c938efc/pyobjc_framework_coreservices-11.1.tar.gz", hash = "sha256:cf8eb5e272c60a96d025313eca26ff2487dcd02c47034cc9db39f6852d077873", size = 1245086, upload-time = "2025-06-14T20:57:13.914Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/b3/52338a3ff41713f7d7bccaf63bef4ba4a8f2ce0c7eaff39a3629d022a79a/pyobjc_framework_coreservices-12.1.tar.gz", hash = "sha256:fc6a9f18fc6da64c166fe95f2defeb7ac8a9836b3b03bb6a891d36035260dbaa", size = 366150, upload-time = "2025-11-14T10:14:28.133Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/35/a984b9aace173e92b3509f82afe5e0f8ecddf5cf43bf0c01c803f60a19ce/pyobjc_framework_coreservices-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f7260e09a0550d57756ad655f3d3815f21fc3f0386aed014be4b46194c346941", size = 30243, upload-time = "2025-06-14T20:48:21.563Z" }, - { url = "https://files.pythonhosted.org/packages/fa/0f/52827197a1fa1dabefd77803920eaf340f25e0c81944844ab329d511cade/pyobjc_framework_coreservices-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:6bd313ec326efd715b4b10c3ebcc9f054e3ee3178be407b97ea225cd871351d2", size = 30252, upload-time = "2025-06-14T20:48:22.657Z" }, + { url = "https://files.pythonhosted.org/packages/55/56/c905deb5ab6f7f758faac3f2cbc6f62fde89f8364837b626801bba0975c3/pyobjc_framework_coreservices-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b6ef07bcf99e941395491f1efcf46e99e5fb83eb6bfa12ae5371135d83f731e1", size = 30196, upload-time = "2025-11-14T09:46:19.356Z" }, + { url = "https://files.pythonhosted.org/packages/61/6c/33984caaf497fc5a6f86350d7ca4fac8abeb2bc33203edc96955a21e8c05/pyobjc_framework_coreservices-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8751dc2edcb7cfa248bf8a274c4d6493e8d53ef28a843827a4fc9a0a8b04b8be", size = 30206, upload-time = "2025-11-14T09:46:22.732Z" }, ] [[package]] name = "pyobjc-framework-corespotlight" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/31/c7/b67ebfb63b7ccbfda780d583056d1fd4b610ba3839c8ebe3435b86122c61/pyobjc_framework_corespotlight-11.1.tar.gz", hash = "sha256:4dd363c8d3ff7619659b63dd31400f135b03e32435b5d151459ecdacea14e0f2", size = 87161, upload-time = "2025-06-14T20:57:14.934Z" } +sdist = { url = "https://files.pythonhosted.org/packages/99/d0/88ca73b0cf23847af463334989dd8f98e44f801b811e7e1d8a5627ec20b4/pyobjc_framework_corespotlight-12.1.tar.gz", hash = "sha256:57add47380cd0bbb9793f50a4a4b435a90d4ebd2a33698e058cb353ddfb0d068", size = 38002, upload-time = "2025-11-14T10:14:31.948Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/46/d4/87a87384bbb2e27864d527eb00973a056bae72603e6c581711231f2479fc/pyobjc_framework_corespotlight-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d3c571289ce9107f1ade92ad036633f81355f22f70e8ba82d7335f1757381b89", size = 9954, upload-time = "2025-06-14T20:48:28.065Z" }, - { url = "https://files.pythonhosted.org/packages/b9/f8/06b7edfeabe5b3874485b6e5bbe4a39d9f2e1f44348faa7cb320fbc6f21a/pyobjc_framework_corespotlight-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7cedd3792fe1fe2a8dc65a8ff1f70baf12415a5dc9dc4d88f987059567d7e694", size = 9977, upload-time = "2025-06-14T20:48:28.757Z" }, + { url = "https://files.pythonhosted.org/packages/d4/37/1e7bacb9307a8df52234923e054b7303783e7a48a4637d44ce390b015921/pyobjc_framework_corespotlight-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:404a1e362fe19f0dff477edc1665d8ad90aada928246802da777399f7c06b22e", size = 9976, upload-time = "2025-11-14T09:46:45.221Z" }, + { url = "https://files.pythonhosted.org/packages/f6/3b/d3031eddff8029859de6d92b1f741625b1c233748889141a6a5a89b96f0e/pyobjc_framework_corespotlight-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bfcea64ab3250e2886d202b8731be3817b5ac0c8c9f43e77d0d5a0b6602e71a7", size = 9996, upload-time = "2025-11-14T09:46:47.157Z" }, ] [[package]] name = "pyobjc-framework-coretext" -version = "11.1" +version = "12.1" 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'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/65/e9/d3231c4f87d07b8525401fd6ad3c56607c9e512c5490f0a7a6abb13acab6/pyobjc_framework_coretext-11.1.tar.gz", hash = "sha256:a29bbd5d85c77f46a8ee81d381b847244c88a3a5a96ac22f509027ceceaffaf6", size = 274702, upload-time = "2025-06-14T20:57:16.059Z" } +sdist = { url = "https://files.pythonhosted.org/packages/29/da/682c9c92a39f713bd3c56e7375fa8f1b10ad558ecb075258ab6f1cdd4a6d/pyobjc_framework_coretext-12.1.tar.gz", hash = "sha256:e0adb717738fae395dc645c9e8a10bb5f6a4277e73cba8fa2a57f3b518e71da5", size = 90124, upload-time = "2025-11-14T10:14:38.596Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4c/59/d6cc5470157cfd328b2d1ee2c1b6f846a5205307fce17291b57236d9f46e/pyobjc_framework_coretext-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b4f4d2d2a6331fa64465247358d7aafce98e4fb654b99301a490627a073d021e", size = 30072, upload-time = "2025-06-14T20:48:34.248Z" }, - { url = "https://files.pythonhosted.org/packages/32/67/9cc5189c366e67dc3e5b5976fac73cc6405841095f795d3fa0d5fc43d76a/pyobjc_framework_coretext-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1597bf7234270ee1b9963bf112e9061050d5fb8e1384b3f50c11bde2fe2b1570", size = 30175, upload-time = "2025-06-14T20:48:35.023Z" }, + { url = "https://files.pythonhosted.org/packages/f0/81/7b8efc41e743adfa2d74b92dec263c91bcebfb188d2a8f5eea1886a195ff/pyobjc_framework_coretext-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4f6742ba5b0bb7629c345e99eff928fbfd9e9d3d667421ac1a2a43bdb7ba9833", size = 29990, upload-time = "2025-11-14T09:47:01.206Z" }, + { url = "https://files.pythonhosted.org/packages/cd/0f/ddf45bf0e3ba4fbdc7772de4728fd97ffc34a0b5a15e1ab1115b202fe4ae/pyobjc_framework_coretext-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d246fa654bdbf43bae3969887d58f0b336c29b795ad55a54eb76397d0e62b93c", size = 30108, upload-time = "2025-11-14T09:47:04.228Z" }, ] [[package]] name = "pyobjc-framework-corewlan" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c6/d8/03aff3c75485fc999e260946ef1e9adf17640a6e08d7bf603d31cfcf73fc/pyobjc_framework_corewlan-11.1.tar.gz", hash = "sha256:4a8afea75393cc0a6fe696e136233aa0ed54266f35a47b55a3583f4cb078e6ce", size = 65792, upload-time = "2025-06-14T20:57:16.931Z" } +sdist = { url = "https://files.pythonhosted.org/packages/88/71/739a5d023566b506b3fd3d2412983faa95a8c16226c0dcd0f67a9294a342/pyobjc_framework_corewlan-12.1.tar.gz", hash = "sha256:a9d82ec71ef61f37e1d611caf51a4203f3dbd8caf827e98128a1afaa0fd2feb5", size = 32417, upload-time = "2025-11-14T10:14:41.921Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/03/ba/e73152fc1beee1bf75489d4a6f89ebd9783340e50ca1948cde029d7b0411/pyobjc_framework_corewlan-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e12f127b37a7ab8f349167332633392f2d6d29b87c9b98137a289d0fc1e07b5b", size = 9993, upload-time = "2025-06-14T20:48:41.081Z" }, - { url = "https://files.pythonhosted.org/packages/09/8a/74feabaad1225eb2c44d043924ed8caea31683e6760cd9b918b8d965efea/pyobjc_framework_corewlan-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7bd0775d2466ad500aad4747d8a889993db3a14240239f30ef53c087745e9c8e", size = 10016, upload-time = "2025-06-14T20:48:41.792Z" }, + { url = "https://files.pythonhosted.org/packages/f5/74/4d8a52b930a276f6f9b4f3b1e07cd518cb6d923cb512e39c935e3adb0b86/pyobjc_framework_corewlan-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3e3f2614eb37dfd6860d6a0683877c2f3b909758ef78b68e5f6b7ea9c858cc51", size = 9931, upload-time = "2025-11-14T09:47:20.849Z" }, + { url = "https://files.pythonhosted.org/packages/4e/31/3e9cf2c0ac3c979062958eae7a275b602515c9c76fd30680e1ee0fea82ae/pyobjc_framework_corewlan-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5cba04c0550fc777767cd3a5471e4ed837406ab182d7d5c273bc5ce6ea237bfe", size = 9958, upload-time = "2025-11-14T09:47:22.474Z" }, ] [[package]] name = "pyobjc-framework-cryptotokenkit" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/eb/92/7fab6fcc6bb659d6946cfb2f670058180bcc4ca1626878b0f7c95107abf0/pyobjc_framework_cryptotokenkit-11.1.tar.gz", hash = "sha256:5f82f44d9ab466c715a7c8ad4d5ec47c68aacd78bd67b5466a7b8215a2265328", size = 59223, upload-time = "2025-06-14T20:57:17.658Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/7c/d03ff4f74054578577296f33bc669fce16c7827eb1a553bb372b5aab30ca/pyobjc_framework_cryptotokenkit-12.1.tar.gz", hash = "sha256:c95116b4b7a41bf5b54aff823a4ef6f4d9da4d0441996d6d2c115026a42d82f5", size = 32716, upload-time = "2025-11-14T10:14:45.024Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/eb/b6/783495dc440277a330930bac7b560cf54d5e1838fc30fdc3162722db8a62/pyobjc_framework_cryptotokenkit-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2b76fb928bc398091141dc52b26e02511065afd0b6de5533fa0e71ab13c51589", size = 12515, upload-time = "2025-06-14T20:48:47.346Z" }, - { url = "https://files.pythonhosted.org/packages/76/f1/4cb9c90a55ec13301d60ac1c4d774c37b4ebc6db6331d3853021c933fcc8/pyobjc_framework_cryptotokenkit-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:6384cb1d86fc586e2da934a5a37900825bd789e3a5df97517691de9af354af0c", size = 12543, upload-time = "2025-06-14T20:48:48.079Z" }, + { url = "https://files.pythonhosted.org/packages/2c/90/1623b60d6189db08f642777374fd32287b06932c51dfeb1e9ed5bbf67f35/pyobjc_framework_cryptotokenkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d84b75569054fa0886e3e341c00d7179d5fe287e6d1509630dd698ee60ec5af1", size = 12598, upload-time = "2025-11-14T09:47:33.798Z" }, + { url = "https://files.pythonhosted.org/packages/0f/c7/aecba253cf21303b2c9f3ce03fc0e987523609d7839ea8e0a688ae816c96/pyobjc_framework_cryptotokenkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ef51a86c1d0125fabdfad0b3efa51098fb03660d8dad2787d82e8b71c9f189de", size = 12633, upload-time = "2025-11-14T09:47:35.707Z" }, ] [[package]] name = "pyobjc-framework-datadetection" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7d/4d/65c61d8878b44689e28d5729be9edbb73e20b1b0500d1095172cfd24aea6/pyobjc_framework_datadetection-11.1.tar.gz", hash = "sha256:cbe0080b51e09b2f91eaf2a9babec3dcf2883d7966bc0abd8393ef7abfcfc5db", size = 13485, upload-time = "2025-06-14T20:57:18.829Z" } +sdist = { url = "https://files.pythonhosted.org/packages/db/97/9b03832695ec4d3008e6150ddfdc581b0fda559d9709a98b62815581259a/pyobjc_framework_datadetection-12.1.tar.gz", hash = "sha256:95539e46d3bc970ce890aa4a97515db10b2690597c5dd362996794572e5d5de0", size = 12323, upload-time = "2025-11-14T10:14:46.769Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/08/c4/ef2136e4e0cc69b02479295822aa33c8e26995b265c8a1184867b65a0a06/pyobjc_framework_datadetection-11.1-py2.py3-none-any.whl", hash = "sha256:5afd3dde7bba3324befb7a3133c9aeaa5088efd72dccc0804267a74799f4a12f", size = 3482, upload-time = "2025-06-14T20:48:54.301Z" }, + { url = "https://files.pythonhosted.org/packages/70/1c/5d2f941501e84da8fef8ef3fd378b5c083f063f083f97dd3e8a07f0404b3/pyobjc_framework_datadetection-12.1-py2.py3-none-any.whl", hash = "sha256:4dc8e1d386d655b44b2681a4a2341fb2fc9addbf3dda14cb1553cd22be6a5387", size = 3497, upload-time = "2025-11-14T09:47:45.826Z" }, ] [[package]] name = "pyobjc-framework-devicecheck" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f3/f2/b1d263f8231f815a9eeff15809f4b7428dacdc0a6aa267db5ed907445066/pyobjc_framework_devicecheck-11.1.tar.gz", hash = "sha256:8b05973eb2673571144d81346336e749a21cec90bd7fcaade76ffd3b147a0741", size = 13954, upload-time = "2025-06-14T20:57:19.782Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/af/c676107c40d51f55d0a42043865d7246db821d01241b518ea1d3b3ef1394/pyobjc_framework_devicecheck-12.1.tar.gz", hash = "sha256:567e85fc1f567b3fe64ac1cdc323d989509331f64ee54fbcbde2001aec5adbdb", size = 12885, upload-time = "2025-11-14T10:14:48.804Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/39/72/17698a0d68b1067b20b32b4afd74bcafb53a7c73ae8fc608addc7b9e7a37/pyobjc_framework_devicecheck-11.1-py2.py3-none-any.whl", hash = "sha256:8edb36329cdd5d55e2c2c57c379cb5ba1f500f74a08fe8d2612b1a69b7a26435", size = 3668, upload-time = "2025-06-14T20:48:55.098Z" }, + { url = "https://files.pythonhosted.org/packages/c5/d8/1f1b13fa4775b6474c9ad0f4b823953eaeb6c11bd6f03fa8479429b36577/pyobjc_framework_devicecheck-12.1-py2.py3-none-any.whl", hash = "sha256:ffd58148bdef4a1ee8548b243861b7d97a686e73808ca0efac5bef3c430e4a15", size = 3684, upload-time = "2025-11-14T09:47:47.25Z" }, ] [[package]] name = "pyobjc-framework-devicediscoveryextension" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9a/b8/102863bfa2f1e414c88bb9f51151a9a58b99c268a841b59d46e0dcc5fe6d/pyobjc_framework_devicediscoveryextension-11.1.tar.gz", hash = "sha256:ae160ea40f25d3ee5e7ce80ac9c1b315f94d0a4c7ccb86920396f71c6bf799a0", size = 14298, upload-time = "2025-06-14T20:57:20.738Z" } +sdist = { url = "https://files.pythonhosted.org/packages/91/b0/e6e2ed6a7f4b689746818000a003ff7ab9c10945df66398ae8d323ae9579/pyobjc_framework_devicediscoveryextension-12.1.tar.gz", hash = "sha256:60e12445fad97ff1f83472255c943685a8f3a9d95b3126d887cfe769b7261044", size = 14718, upload-time = "2025-11-14T10:14:50.723Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/67/89/fce0c0c89746f399d13e08b40fc12e29a2495f4dcebd30893336d047af18/pyobjc_framework_devicediscoveryextension-11.1-py2.py3-none-any.whl", hash = "sha256:96e5b13c718bd0e6c80fbd4e14b8073cffc88b3ab9bb1bbb4dab7893a62e4f11", size = 4249, upload-time = "2025-06-14T20:48:55.895Z" }, + { url = "https://files.pythonhosted.org/packages/7e/0c/005fe8db1e19135f493a3de8c8d38031e1ad2d626de4ef89f282acf4aff7/pyobjc_framework_devicediscoveryextension-12.1-py2.py3-none-any.whl", hash = "sha256:d6d6b606d27d4d88efc0bed4727c375e749149b360290c3ad2afc52337739a1b", size = 4321, upload-time = "2025-11-14T09:47:48.78Z" }, ] [[package]] name = "pyobjc-framework-dictionaryservices" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-coreservices", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d6/13/c46f6db61133fee15e3471f33a679da2af10d63fa2b4369e0cd476988721/pyobjc_framework_dictionaryservices-11.1.tar.gz", hash = "sha256:39c24452d0ddd037afeb73a1742614c94535f15b1c024a8a6cc7ff081e1d22e7", size = 10578, upload-time = "2025-06-14T20:57:21.392Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7a/c0/daf03cdaf6d4e04e0cf164db358378c07facd21e4e3f8622505d72573e2c/pyobjc_framework_dictionaryservices-12.1.tar.gz", hash = "sha256:354158f3c55d66681fa903c7b3cb05a435b717fa78d0cef44d258d61156454a7", size = 10573, upload-time = "2025-11-14T10:14:53.961Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6c/86/4e757b4064a0feb8d60456672560adad0bb5df530ba6621fe65d175dbd90/pyobjc_framework_dictionaryservices-11.1-py2.py3-none-any.whl", hash = "sha256:92f4871066653f18e2394ac93b0a2ab50588d60020f6b3bd93e97b67cd511326", size = 3913, upload-time = "2025-06-14T20:48:56.806Z" }, + { url = "https://files.pythonhosted.org/packages/e7/13/ab308e934146cfd54691ddad87e572cd1edb6659d795903c4c75904e2d7d/pyobjc_framework_dictionaryservices-12.1-py2.py3-none-any.whl", hash = "sha256:578854eec17fa473ac17ab30050a7bbb2ab69f17c5c49b673695254c3e88ad4b", size = 3930, upload-time = "2025-11-14T09:47:50.782Z" }, ] [[package]] name = "pyobjc-framework-discrecording" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a5/b2/d8d1a28643c2ab681b517647bacb68496c98886336ffbd274f0b2ad28cdc/pyobjc_framework_discrecording-11.1.tar.gz", hash = "sha256:37585458e363b20bb28acdb5cc265dfca934d8a07b7baed2584953c11c927a87", size = 123004, upload-time = "2025-06-14T20:57:22.01Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c4/87/8bd4544793bfcdf507174abddd02b1f077b48fab0004b3db9a63142ce7e9/pyobjc_framework_discrecording-12.1.tar.gz", hash = "sha256:6defc8ea97fb33b4d43870c673710c04c3dc48be30cdf78ba28191a922094990", size = 55607, upload-time = "2025-11-14T10:14:58.276Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/8c/0ff85cc34218e54236eb866e71c35e3308a661f50aea090d400e9121d9c4/pyobjc_framework_discrecording-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:dc8a7820fc193c2bfcd843c31de945dc45e77e5413089eabbc72be16a4f52e53", size = 14558, upload-time = "2025-06-14T20:48:58.495Z" }, - { url = "https://files.pythonhosted.org/packages/5e/17/032fa44bb66b6a20c432f3311072f88478b42dcf39b21ebb6c3bbdf2954f/pyobjc_framework_discrecording-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e29bc8c3741ae52fae092f892de856dbab2363e71537a8ae6fd026ecb88e2252", size = 14581, upload-time = "2025-06-14T20:48:59.228Z" }, + { url = "https://files.pythonhosted.org/packages/0e/ce/89df4d53a0a5e3a590d6e735eca4f0ba4d1ccc0e0acfbc14164026a3c502/pyobjc_framework_discrecording-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f7d815f28f781e20de0bf278aaa10b0de7e5ea1189aa17676c0bf5b99e9e0d52", size = 14540, upload-time = "2025-11-14T09:47:55.442Z" }, + { url = "https://files.pythonhosted.org/packages/c8/70/14a5aa348a5eba16e8773bb56698575cf114aa55aa303037b7000fc53959/pyobjc_framework_discrecording-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:865f1551e58459da6073360afc8f2cc452472c676ba83dcaa9b0c44e7775e4b5", size = 14566, upload-time = "2025-11-14T09:47:57.503Z" }, ] [[package]] name = "pyobjc-framework-discrecordingui" -version = "11.1" +version = "12.1" 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'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/25/53/d71717f00332b8fc3d8a5c7234fdc270adadfeb5ca9318a55986f5c29c44/pyobjc_framework_discrecordingui-11.1.tar.gz", hash = "sha256:a9f10e2e7ee19582c77f0755ae11a64e3d61c652cbd8a5bf52756f599be24797", size = 19370, upload-time = "2025-06-14T20:57:22.791Z" } +sdist = { url = "https://files.pythonhosted.org/packages/30/63/8667f5bb1ecb556add04e86b278cb358dc1f2f03862705cae6f09097464c/pyobjc_framework_discrecordingui-12.1.tar.gz", hash = "sha256:6793d4a1a7f3219d063f39d87f1d4ebbbb3347e35d09194a193cfe16cba718a8", size = 16450, upload-time = "2025-11-14T10:15:00.254Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4a/a6/505af43f7a17e0ca3d45e099900764e8758e0ca65341e894b74ade513556/pyobjc_framework_discrecordingui-11.1-py2.py3-none-any.whl", hash = "sha256:33233b87d7b85ce277a51d27acca0f5b38485cf1d1dc8e28a065910047766ee2", size = 4721, upload-time = "2025-06-14T20:49:03.737Z" }, + { url = "https://files.pythonhosted.org/packages/f2/4e/76016130c27b98943c5758a05beab3ba1bc9349ee881e1dfc509ea954233/pyobjc_framework_discrecordingui-12.1-py2.py3-none-any.whl", hash = "sha256:6544ef99cad3dee95716c83cb207088768b6ecd3de178f7e1b17df5997689dfd", size = 4702, upload-time = "2025-11-14T09:48:08.01Z" }, ] [[package]] name = "pyobjc-framework-diskarbitration" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/da/2a/68fa0c99e04ec1ec24b0b7d6f5b7ec735d5e8a73277c5c0671438a69a403/pyobjc_framework_diskarbitration-11.1.tar.gz", hash = "sha256:a933efc6624779a393fafe0313e43378bcae2b85d6d15cff95ac30048c1ef490", size = 19866, upload-time = "2025-06-14T20:57:23.435Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3a/42/f75fcabec1a0033e4c5235cc8225773f610321d565b63bf982c10c6bbee4/pyobjc_framework_diskarbitration-12.1.tar.gz", hash = "sha256:6703bc5a09b38a720c9ffca356b58f7e99fa76fc988c9ec4d87112344e63dfc2", size = 17121, upload-time = "2025-11-14T10:15:02.223Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1f/72/9534ca88effbf2897e07b722920b3f10890dbc780c6fff1ab4893ec1af10/pyobjc_framework_diskarbitration-11.1-py2.py3-none-any.whl", hash = "sha256:6a8e551e54df481a9081abba6fd680f6633babe5c7735f649731b22896bb6f08", size = 4849, upload-time = "2025-06-14T20:49:04.513Z" }, + { url = "https://files.pythonhosted.org/packages/48/65/c1f54c47af17cb6b923eab85e95f22396c52f90ee8f5b387acffad9a99ea/pyobjc_framework_diskarbitration-12.1-py2.py3-none-any.whl", hash = "sha256:54caf3079fe4ae5ac14466a9b68923ee260a1a88a8290686b4a2015ba14c2db6", size = 4877, upload-time = "2025-11-14T09:48:09.945Z" }, ] [[package]] name = "pyobjc-framework-dvdplayback" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b8/76/77046325b1957f0cbcdf4f96667496d042ed4758f3413f1d21df5b085939/pyobjc_framework_dvdplayback-11.1.tar.gz", hash = "sha256:b44c36a62c8479e649133216e22941859407cca5796b5f778815ef9340a838f4", size = 64558, upload-time = "2025-06-14T20:57:24.118Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cf/dd/7859a58e8dd336c77f83feb76d502e9623c394ea09322e29a03f5bc04d32/pyobjc_framework_dvdplayback-12.1.tar.gz", hash = "sha256:279345d4b5fb2c47dd8e5c2fd289e644b6648b74f5c25079805eeb61bfc4a9cd", size = 32332, upload-time = "2025-11-14T10:15:05.257Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/59/0c/f0fefa171b6938010d87194e26e63eea5c990c33d2d7828de66802f57c36/pyobjc_framework_dvdplayback-11.1-py2.py3-none-any.whl", hash = "sha256:6094e4651ea29540ac817294b27e1596b9d1883d30e78fb5f9619daf94ed30cb", size = 8221, upload-time = "2025-06-14T20:49:05.297Z" }, + { url = "https://files.pythonhosted.org/packages/29/7d/22c07c28fab1f15f0d364806e39a6ca63c737c645fe7e98e157878b5998c/pyobjc_framework_dvdplayback-12.1-py2.py3-none-any.whl", hash = "sha256:af911cc222272a55b46a1a02a46a355279aecfd8132231d8d1b279e252b8ad4c", size = 8243, upload-time = "2025-11-14T09:48:11.824Z" }, ] [[package]] name = "pyobjc-framework-eventkit" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b4/c4/cbba8f2dce13b9be37ecfd423ba2b92aa3f209dbb58ede6c4ce3b242feee/pyobjc_framework_eventkit-11.1.tar.gz", hash = "sha256:5643150f584243681099c5e9435efa833a913e93fe9ca81f62007e287349b561", size = 75177, upload-time = "2025-06-14T20:57:24.81Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b6/42/4ec97e641fdcf30896fe76476181622954cb017117b1429f634d24816711/pyobjc_framework_eventkit-12.1.tar.gz", hash = "sha256:7c1882be2f444b1d0f71e9a0cd1e9c04ad98e0261292ab741fc9de0b8bbbbae9", size = 28538, upload-time = "2025-11-14T10:15:07.878Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/05/0a/384b9ff4c6380cac310cb7b92c145896c20a690192dbfc07b38909787ded/pyobjc_framework_eventkit-11.1-py2.py3-none-any.whl", hash = "sha256:c303207610d9c742f4090799f60103cede466002f3c89cf66011c8bf1987750b", size = 6805, upload-time = "2025-06-14T20:49:06.147Z" }, + { url = "https://files.pythonhosted.org/packages/f4/35/142f43227627d6324993869d354b9e57eb1e88c4e229e2271592254daf25/pyobjc_framework_eventkit-12.1-py2.py3-none-any.whl", hash = "sha256:3d2d36d5bd9e0a13887a6ac7cdd36675985ebe2a9cb3cdf8cec0725670c92c60", size = 6820, upload-time = "2025-11-14T09:48:14.035Z" }, ] [[package]] name = "pyobjc-framework-exceptionhandling" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/19/0d/c72a885b40d28a99b586447f9ea6f400589f13d554fcd6f13a2c841bb6d2/pyobjc_framework_exceptionhandling-11.1.tar.gz", hash = "sha256:e010f56bf60ab4e9e3225954ebb53e9d7135d37097043ac6dd2a3f35770d4efa", size = 17890, upload-time = "2025-06-14T20:57:25.521Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/17/5c9d4164f7ccf6b9100be0ad597a7857395dd58ea492cba4f0e9c0b77049/pyobjc_framework_exceptionhandling-12.1.tar.gz", hash = "sha256:7f0719eeea6695197fce0e7042342daa462683dc466eb6a442aad897032ab00d", size = 16694, upload-time = "2025-11-14T10:15:10.173Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/81/dde9c73bf307b62c2d605fc818d3e49f857f39e0841766093dbc9ea47b08/pyobjc_framework_exceptionhandling-11.1-py2.py3-none-any.whl", hash = "sha256:31e6538160dfd7526ac0549bc0fce5d039932aea84c36abbe7b49c79ffc62437", size = 7078, upload-time = "2025-06-14T20:49:07.713Z" }, + { url = "https://files.pythonhosted.org/packages/0b/ad/8e05acf3635f20ea7d878be30d58a484c8b901a8552c501feb7893472f86/pyobjc_framework_exceptionhandling-12.1-py2.py3-none-any.whl", hash = "sha256:2f1eae14cf0162e53a0888d9ffe63f047501fe583a23cdc9c966e89f48cf4713", size = 7113, upload-time = "2025-11-14T09:48:15.685Z" }, ] [[package]] name = "pyobjc-framework-executionpolicy" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0b/cf/54431846508c5d5bb114a415ebb96187da5847105918169e42f4ca3b00e6/pyobjc_framework_executionpolicy-11.1.tar.gz", hash = "sha256:3280ad2f4c5eaf45901f310cee0c52db940c0c63e959ad082efb8df41055d986", size = 13496, upload-time = "2025-06-14T20:57:26.173Z" } +sdist = { url = "https://files.pythonhosted.org/packages/95/11/db765e76e7b00e1521d7bb3a61ae49b59e7573ac108da174720e5d96b61b/pyobjc_framework_executionpolicy-12.1.tar.gz", hash = "sha256:682866589365cd01d3a724d8a2781794b5cba1e152411a58825ea52d7b972941", size = 12594, upload-time = "2025-11-14T10:15:12.077Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a6/d2/cb192d55786d0f881f2fb60d45b61862a1fcade945f6a7a549ed62f47e61/pyobjc_framework_executionpolicy-11.1-py2.py3-none-any.whl", hash = "sha256:7d4141e572cb916e73bb34bb74f6f976a8aa0a396a0bffd1cf66e5505f7c76c8", size = 3719, upload-time = "2025-06-14T20:49:08.521Z" }, + { url = "https://files.pythonhosted.org/packages/51/2c/f10352398f10f244401ab8f53cabd127dc3f5dbbfc8de83464661d716671/pyobjc_framework_executionpolicy-12.1-py2.py3-none-any.whl", hash = "sha256:c3a9eca3bd143cf202787dd5e3f40d954c198f18a5e0b8b3e2fcdd317bf33a52", size = 3739, upload-time = "2025-11-14T09:48:17.35Z" }, ] [[package]] name = "pyobjc-framework-extensionkit" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ce/7d/89adf16c7de4246477714dce8fcffae4242778aecd0c5f0ad9904725f42c/pyobjc_framework_extensionkit-11.1.tar.gz", hash = "sha256:c114a96f13f586dbbab8b6219a92fa4829896a645c8cd15652a6215bc8ff5409", size = 19766, upload-time = "2025-06-14T20:57:27.106Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/d4/e9b1f74d29ad9dea3d60468d59b80e14ed3a19f9f7a25afcbc10d29c8a1e/pyobjc_framework_extensionkit-12.1.tar.gz", hash = "sha256:773987353e8aba04223dbba3149253db944abfb090c35318b3a770195b75da6d", size = 18694, upload-time = "2025-11-14T10:15:14.104Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0f/90/e6607b779756e039c0a4725a37cf70dc5b13c54a8cedbcf01ec1608866b1/pyobjc_framework_extensionkit-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:61fd9f9758f95bcff2bf26fe475f679dfff9457d7130f114089e88fd5009675a", size = 7894, upload-time = "2025-06-14T20:49:10.593Z" }, - { url = "https://files.pythonhosted.org/packages/90/2a/93105b5452d2ff680a47e38a3ec6f2a37164babd95e0ab976c07984366de/pyobjc_framework_extensionkit-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d505a64617c9db4373eb386664d62a82ba9ffc909bffad42cb4da8ca8e244c66", size = 7914, upload-time = "2025-06-14T20:49:11.842Z" }, + { url = "https://files.pythonhosted.org/packages/4f/02/3d1df48f838dc9d64f03bedd29f0fdac6c31945251c9818c3e34083eb731/pyobjc_framework_extensionkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9139c064e1c7f21455411848eb39f092af6085a26cad322aa26309260e7929d9", size = 7919, upload-time = "2025-11-14T09:48:22.14Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/8064dad6114a489e5439cc20d9fb0dd64cfc406d875b4a3c87015b3f6266/pyobjc_framework_extensionkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7e01d705c7ac6d080ae34a81db6d9b81875eabefa63fd6eafbfa30f676dd780b", size = 7932, upload-time = "2025-11-14T09:48:23.653Z" }, ] [[package]] name = "pyobjc-framework-externalaccessory" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d9/a3/519242e6822e1ddc9e64e21f717529079dbc28a353474420da8315d0a8b1/pyobjc_framework_externalaccessory-11.1.tar.gz", hash = "sha256:50887e948b78a1d94646422c243ac2a9e40761675e38b9184487870a31e83371", size = 23123, upload-time = "2025-06-14T20:57:27.845Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/35/86c097ae2fdf912c61c1276e80f3e090a3fc898c75effdf51d86afec456b/pyobjc_framework_externalaccessory-12.1.tar.gz", hash = "sha256:079f770a115d517a6ab87db1b8a62ca6cdf6c35ae65f45eecc21b491e78776c0", size = 20958, upload-time = "2025-11-14T10:15:16.419Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/63/54/d532badd43eba2db3fed2501b8e47a57cab233de2090ee97f4cff723e706/pyobjc_framework_externalaccessory-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a2b22f72b83721d841e5a3128df29fc41d785597357c6bbce84555a2b51a1e9d", size = 8887, upload-time = "2025-06-14T20:49:17.703Z" }, - { url = "https://files.pythonhosted.org/packages/7d/1b/e2def12aca9162b0fe0bbf0790d35595d46b2ef12603749c42af9234ffca/pyobjc_framework_externalaccessory-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:00caf75b959db5d14118d78c04085e2148255498839cdee735a0b9f6ef86b6a2", size = 8903, upload-time = "2025-06-14T20:49:18.393Z" }, + { url = "https://files.pythonhosted.org/packages/18/01/2a83b63e82ce58722277a00521c3aeec58ac5abb3086704554e47f8becf3/pyobjc_framework_externalaccessory-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:32208e05c9448c8f41b3efdd35dbea4a8b119af190f7a2db0d580be8a5cf962e", size = 8911, upload-time = "2025-11-14T09:48:35.349Z" }, + { url = "https://files.pythonhosted.org/packages/ec/52/984034396089766b6e5ff3be0f93470e721c420fa9d1076398557532234f/pyobjc_framework_externalaccessory-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:dedbf7a09375ac19668156c1417bd7829565b164a246b714e225b9cbb6a351ad", size = 8932, upload-time = "2025-11-14T09:48:37.393Z" }, ] [[package]] name = "pyobjc-framework-fileprovider" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1b/80/3ebba2c1e5e3aeae989fe038c259a93e7e7e18fd56666ece514d000d38ea/pyobjc_framework_fileprovider-11.1.tar.gz", hash = "sha256:748ca1c75f84afdf5419346a24bf8eec44dca071986f31f00071dc191b3e9ca8", size = 91696, upload-time = "2025-06-14T20:57:28.546Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/9a/724b1fae5709f8860f06a6a2a46de568f9bb8bdb2e2aae45b4e010368f51/pyobjc_framework_fileprovider-12.1.tar.gz", hash = "sha256:45034e0d00ae153c991aa01cb1fd41874650a30093e77ba73401dcce5534c8ad", size = 43071, upload-time = "2025-11-14T10:15:19.989Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/e4/c7b985d1199e3697ab5c3247027fe488b9d81b1fb597c34350942dc5838c/pyobjc_framework_fileprovider-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:888d6fb3fd625889ce0e409320c3379330473a386095cb4eda2b4caf0198ff66", size = 19546, upload-time = "2025-06-14T20:49:23.436Z" }, - { url = "https://files.pythonhosted.org/packages/49/b2/859d733b0110e56511478ba837fd8a7ba43aa8f8c7e5231b9e3f0258bfbf/pyobjc_framework_fileprovider-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ce6092dfe74c78c0b2abc03bfc18a0f5d8ddc624fc6a1d8dfef26d7796653072", size = 19622, upload-time = "2025-06-14T20:49:24.162Z" }, + { url = "https://files.pythonhosted.org/packages/1d/37/2f56167e9f43d3b25a5ed073305ca0cfbfc66bedec7aae9e1f2c9c337265/pyobjc_framework_fileprovider-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9d527c417f06d27c4908e51d4e6ccce0adcd80c054f19e709626e55c511dc963", size = 20970, upload-time = "2025-11-14T09:48:50.557Z" }, + { url = "https://files.pythonhosted.org/packages/2c/f5/56f0751a2988b2caca89d6800c8f29246828d1a7498bb676ef1ab28000b7/pyobjc_framework_fileprovider-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:89b140ea8369512ddf4164b007cbe35b4d97d1dcb8affa12a7264c0ab8d56e45", size = 21003, upload-time = "2025-11-14T09:48:53.128Z" }, ] [[package]] name = "pyobjc-framework-fileproviderui" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-fileprovider", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/75/ed/0f5af06869661822c4a70aacd674da5d1e6b6661240e2883bbc7142aa525/pyobjc_framework_fileproviderui-11.1.tar.gz", hash = "sha256:162a23e67f59e1bb247e84dda88d513d7944d815144901a46be6fe051b6c7970", size = 13163, upload-time = "2025-06-14T20:57:29.568Z" } +sdist = { url = "https://files.pythonhosted.org/packages/50/00/234f9b93f75255845df81d9d5ea20cb83ecb5c0a4e59147168b622dd0b9d/pyobjc_framework_fileproviderui-12.1.tar.gz", hash = "sha256:15296429d9db0955abc3242b2920b7a810509a85118dbc185f3ac8234e5a6165", size = 12437, upload-time = "2025-11-14T10:15:22.044Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/62/01/667e139a0610494e181fccdce519f644166f3d8955b330674deba5876f0d/pyobjc_framework_fileproviderui-11.1-py2.py3-none-any.whl", hash = "sha256:f2765f114c2f4356aa41fb45c621fa8f0a4fae0b6d3c6b1a274366f5fe7fe829", size = 3696, upload-time = "2025-06-14T20:49:29.404Z" }, + { url = "https://files.pythonhosted.org/packages/e8/65/cc4397511bd0af91993d6302a2aed205296a9ad626146eefdfc8a9624219/pyobjc_framework_fileproviderui-12.1-py2.py3-none-any.whl", hash = "sha256:521a914055089e28631018bd78df4c4f7416e98b4150f861d4a5bc97d5b1ffe4", size = 3715, upload-time = "2025-11-14T09:49:04.213Z" }, ] [[package]] name = "pyobjc-framework-findersync" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2a/82/c6b670494ac0c4cf14cf2db0dfbe0df71925d20595404939383ddbcc56d3/pyobjc_framework_findersync-11.1.tar.gz", hash = "sha256:692364937f418f0e4e4abd395a09a7d4a0cdd55fd4e0184de85ee59642defb6e", size = 15045, upload-time = "2025-06-14T20:57:30.173Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/63/c8da472e0910238a905bc48620e005a1b8ae7921701408ca13e5fb0bfb4b/pyobjc_framework_findersync-12.1.tar.gz", hash = "sha256:c513104cef0013c233bf8655b527df665ce6f840c8bc0b3781e996933d4dcfa6", size = 13507, upload-time = "2025-11-14T10:15:24.161Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/61/10/748ff914c5b7fbae5fa2436cd44b11caeabb8d2f6f6f1b9ab581f70f32af/pyobjc_framework_findersync-11.1-py2.py3-none-any.whl", hash = "sha256:c72b0fd8b746b99cfa498da36c5bb333121b2080ad73fa8cbea05cd47db1fa82", size = 4873, upload-time = "2025-06-14T20:49:30.194Z" }, + { url = "https://files.pythonhosted.org/packages/6a/9f/ec7f393e3e2fd11cbdf930d884a0ba81078bdb61920b3cba4f264de8b446/pyobjc_framework_findersync-12.1-py2.py3-none-any.whl", hash = "sha256:e07abeca52c486cf14927f617afc27afa7a3828b99fab3ad02355105fb29203e", size = 4889, upload-time = "2025-11-14T09:49:05.763Z" }, ] [[package]] name = "pyobjc-framework-fsevents" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8e/83/ec0b9ba355dbc34f27ed748df9df4eb6dbfdd9bbd614b0f193752f36f419/pyobjc_framework_fsevents-11.1.tar.gz", hash = "sha256:d29157d04124503c4dfa9dcbbdc8c34d3bab134d3db3a48d96d93f26bd94c14d", size = 29587, upload-time = "2025-06-14T20:57:30.796Z" } +sdist = { url = "https://files.pythonhosted.org/packages/43/17/21f45d2bca2efc72b975f2dfeae7a163dbeabb1236c1f188578403fd4f09/pyobjc_framework_fsevents-12.1.tar.gz", hash = "sha256:a22350e2aa789dec59b62da869c1b494a429f8c618854b1383d6473f4c065a02", size = 26487, upload-time = "2025-11-14T10:15:26.796Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/14/6a/25118832a128db99a53be4c45f473192f72923d9b9690785539cee1a9858/pyobjc_framework_fsevents-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:95cc5d839d298b8e95175fb72df8a8e1b08773fd2e0d031efe91eee23e0c8830", size = 13076, upload-time = "2025-06-14T20:49:32.269Z" }, - { url = "https://files.pythonhosted.org/packages/13/c7/378d78e0fd956370f2b120b209117384b5b98925c6d8210a33fd73db4a15/pyobjc_framework_fsevents-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8b51d120b8f12a1ca94e28cf74113bf2bfd4c5aee7035b452e895518f4df7630", size = 13147, upload-time = "2025-06-14T20:49:33.022Z" }, + { url = "https://files.pythonhosted.org/packages/a4/3f/a7fe5656b205ee3a9fd828e342157b91e643ee3e5c0d50b12bd4c737f683/pyobjc_framework_fsevents-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:459cc0aac9850c489d238ba778379d09f073bbc3626248855e78c4bc4d97fe46", size = 13059, upload-time = "2025-11-14T09:49:09.814Z" }, + { url = "https://files.pythonhosted.org/packages/2d/e3/2c5eeea390c0b053e2d73b223af3ec87a3e99a8106e8d3ee79942edb0822/pyobjc_framework_fsevents-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a2949358513fd7bc622fb362b5c4af4fc24fc6307320070ca410885e5e13d975", size = 13141, upload-time = "2025-11-14T09:49:11.947Z" }, ] [[package]] name = "pyobjc-framework-fskit" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/46/47/d1f04c6115fa78936399a389cc5e0e443f8341c9a6c1c0df7f6fdbe51286/pyobjc_framework_fskit-11.1.tar.gz", hash = "sha256:9ded1eab19b4183cb04381e554bbbe679c1213fd58599d6fc6e135e93b51136f", size = 42091, upload-time = "2025-06-14T20:57:31.504Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/55/d00246d6e6d9756e129e1d94bc131c99eece2daa84b2696f6442b8a22177/pyobjc_framework_fskit-12.1.tar.gz", hash = "sha256:ec54e941cdb0b7d800616c06ca76a93685bd7119b8aa6eb4e7a3ee27658fc7ba", size = 42372, upload-time = "2025-11-14T10:15:30.411Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/16/76/1152bd8121ef2c9a0ccdf10624d647095ce944d34f654f001b458edef668/pyobjc_framework_fskit-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:59a939ac8442d648f73a3da75923aa3637ac4693850d995f1914260c8f4f7947", size = 19922, upload-time = "2025-06-14T20:49:38.424Z" }, - { url = "https://files.pythonhosted.org/packages/59/8f/db8f03688db77bfa4b78e89af1d89e910c5e877e94d58bdb3e93cc302e5d/pyobjc_framework_fskit-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1e50b8f949f1386fada73b408463c87eb81ef7fd0b3482bacf0c206a73723013", size = 19948, upload-time = "2025-06-14T20:49:39.18Z" }, + { url = "https://files.pythonhosted.org/packages/e7/1a/5a0b6b8dc18b9dbcb7d1ef7bebdd93f12560097dafa6d7c4b3c15649afba/pyobjc_framework_fskit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:95b9135eea81eeed319dcca32c9db04b38688301586180b86c4585fef6b0e9cd", size = 20228, upload-time = "2025-11-14T09:49:25.324Z" }, + { url = "https://files.pythonhosted.org/packages/6d/a9/0c47469fe80fa14bc698bb0a5b772b44283cc3aca0f67e7f70ab45e09b24/pyobjc_framework_fskit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:50972897adea86508cfee33ec4c23aa91dede97e9da1640ea2fe74702b065be1", size = 20250, upload-time = "2025-11-14T09:49:28.065Z" }, ] [[package]] name = "pyobjc-framework-gamecenter" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1b/8e/b594fd1dc32a59462fc68ad502be2bd87c70e6359b4e879a99bcc4beaf5b/pyobjc_framework_gamecenter-11.1.tar.gz", hash = "sha256:a1c4ed54e11a6e4efba6f2a21ace92bcf186e3fe5c74a385b31f6b1a515ec20c", size = 31981, upload-time = "2025-06-14T20:57:32.192Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d2/f8/b5fd86f6b722d4259228922e125b50e0a6975120a1c4d957e990fb84e42c/pyobjc_framework_gamecenter-12.1.tar.gz", hash = "sha256:de4118f14c9cf93eb0316d49da410faded3609ce9cd63425e9ef878cebb7ea72", size = 31473, upload-time = "2025-11-14T10:15:33.38Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/21/a8/8d9c2d0ff9f42a0951063a9eaff1e39c46c15e89ce4e5e274114340ca976/pyobjc_framework_gamecenter-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:81abe136292ea157acb6c54871915fe6d386146a9386179ded0b974ac435045c", size = 18601, upload-time = "2025-06-14T20:49:44.946Z" }, - { url = "https://files.pythonhosted.org/packages/99/52/0e56f21a6660a4f43882ec641b9e19b7ea92dc7474cec48cda1c9bed9c49/pyobjc_framework_gamecenter-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:779cdf8f52348be7f64d16e3ea37fd621d5ee933c032db3a22a8ccad46d69c59", size = 18634, upload-time = "2025-06-14T20:49:45.737Z" }, + { url = "https://files.pythonhosted.org/packages/ca/17/6491f9e96664e05ec00af7942a6c2f69217771522c9d1180524273cac7cb/pyobjc_framework_gamecenter-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:30943512f2aa8cb129f8e1abf951bf06922ca20b868e918b26c19202f4ee5cc4", size = 18824, upload-time = "2025-11-14T09:49:42.543Z" }, + { url = "https://files.pythonhosted.org/packages/16/ee/b496cc4248c5b901e159d6d9a437da9b86a3105fc3999a66744ba2b2c884/pyobjc_framework_gamecenter-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e8d6d10b868be7c00c2d5a0944cc79315945735dcf17eaa3fec1a7986d26be9b", size = 18868, upload-time = "2025-11-14T09:49:44.767Z" }, ] [[package]] name = "pyobjc-framework-gamecontroller" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/70/4c/1dd62103092a182f2ab8904c8a8e3922d2b0a80a7adab0c20e5fd0207d75/pyobjc_framework_gamecontroller-11.1.tar.gz", hash = "sha256:4d5346faf90e1ebe5602c0c480afbf528a35a7a1ad05f9b49991fdd2a97f105b", size = 115783, upload-time = "2025-06-14T20:57:32.879Z" } +sdist = { url = "https://files.pythonhosted.org/packages/21/14/353bb1fe448cd833839fd199ab26426c0248088753e63c22fe19dc07530f/pyobjc_framework_gamecontroller-12.1.tar.gz", hash = "sha256:64ed3cc4844b67f1faeb540c7cc8d512c84f70b3a4bafdb33d4663a2b2a2b1d8", size = 54554, upload-time = "2025-11-14T10:15:37.591Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/8e/09e73e03e9f57e77df58cf77f6069d3455a3c388a890ff815e86d036ae39/pyobjc_framework_gamecontroller-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:782779f080508acf869187c0cbd3a48c55ee059d3a14fe89ccd6349537923214", size = 20825, upload-time = "2025-06-14T20:49:51.565Z" }, - { url = "https://files.pythonhosted.org/packages/40/e3/e35bccb0284046ef716db4897b70d061b8b16c91fb2c434b1e782322ef56/pyobjc_framework_gamecontroller-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d2cbc0c6c7d9c63e6b5b0b124d0c2bad01bb4b136f3cbc305f27d31f8aab6083", size = 20850, upload-time = "2025-06-14T20:49:52.401Z" }, + { url = "https://files.pythonhosted.org/packages/e4/dc/1d8bd4845a46cb5a5c1f860d85394e64729b2447bbe149bb33301bc99056/pyobjc_framework_gamecontroller-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2633c2703fb30ce068b2f5ce145edbd10fd574d2670b5cdee77a9a126f154fec", size = 20913, upload-time = "2025-11-14T09:49:58.863Z" }, + { url = "https://files.pythonhosted.org/packages/06/28/9f03d0ef7c78340441f78b19fb2d2c952af04a240da5ed30c7cf2d0d0f4e/pyobjc_framework_gamecontroller-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:878aa6590c1510e91bfc8710d6c880e7a8f3656a7b7b6f4f3af487a6f677ccd5", size = 20949, upload-time = "2025-11-14T09:50:01.608Z" }, ] [[package]] name = "pyobjc-framework-gamekit" -version = "11.1" +version = "12.1" 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'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5b/7b/ba141ec0f85ca816f493d1f6fe68c72d01092e5562e53c470a0111d9c34b/pyobjc_framework_gamekit-11.1.tar.gz", hash = "sha256:9b8db075da8866c4ef039a165af227bc29393dc11a617a40671bf6b3975ae269", size = 165397, upload-time = "2025-06-14T20:57:33.711Z" } +sdist = { url = "https://files.pythonhosted.org/packages/52/7b/d625c0937557f7e2e64200fdbeb867d2f6f86b2f148b8d6bfe085e32d872/pyobjc_framework_gamekit-12.1.tar.gz", hash = "sha256:014d032c3484093f1409f8f631ba8a0fd2ff7a3ae23fd9d14235340889854c16", size = 63833, upload-time = "2025-11-14T10:15:42.842Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b4/2a/f206682b9ff76983bae14a479a9c8a9098e58efc3db31f88211d6ad4fd42/pyobjc_framework_gamekit-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5e07c25eab051905c6bd46f368d8b341ef8603dce588ff6dbd82d609dd4fbf71", size = 21932, upload-time = "2025-06-14T20:49:58.154Z" }, - { url = "https://files.pythonhosted.org/packages/1f/23/094e4fe38f2de029365604f0b7dffde7b0edfc57c3d388294c20ed663de2/pyobjc_framework_gamekit-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f945c7cfe53c4a349a03a1272f2736cc5cf88fe9e7a7a407abb03899635d860c", size = 21952, upload-time = "2025-06-14T20:49:58.933Z" }, + { url = "https://files.pythonhosted.org/packages/06/47/d3b78cf57bc2d62dc1408aaad226b776d167832063bbaa0c7cc7a9a6fa12/pyobjc_framework_gamekit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:eb263e90a6af3d7294bc1b1ea5907f8e33bb77d62fb806696f8df7e14806ccad", size = 22463, upload-time = "2025-11-14T09:50:16.444Z" }, + { url = "https://files.pythonhosted.org/packages/c4/05/1c49e1030dc9f2812fa8049442158be76c32f271075f4571f94e4389ea86/pyobjc_framework_gamekit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2eee796d5781157f2c5684f7ef4c2a7ace9d9b408a26a9e7e92e8adf5a3f63d7", size = 22493, upload-time = "2025-11-14T09:50:19.129Z" }, ] [[package]] name = "pyobjc-framework-gameplaykit" -version = "11.1" +version = "12.1" 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'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e0/07/f38b1d83eac10ea4f75c605ffc4850585740db89b90842d311e586ee36cd/pyobjc_framework_gameplaykit-11.1.tar.gz", hash = "sha256:9ae2bee69b0cc1afa0e210b4663c7cdbb3cc94be1374808df06f98f992e83639", size = 73399, upload-time = "2025-06-14T20:57:34.538Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/11/c310bbc2526f95cce662cc1f1359bb11e2458eab0689737b4850d0f6acb7/pyobjc_framework_gameplaykit-12.1.tar.gz", hash = "sha256:935ebd806d802888969357946245d35a304c530c86f1ffe584e2cf21f0a608a8", size = 41511, upload-time = "2025-11-14T10:15:46.529Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0f/29/df66f53f887990878b2b00b1336e451a15e360a384be74559acf47854bc3/pyobjc_framework_gameplaykit-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ac9f50941988c30175149af481a49b2026c56a9a497c6dbf2974ffb50ffe0af8", size = 13065, upload-time = "2025-06-14T20:50:05.243Z" }, - { url = "https://files.pythonhosted.org/packages/e7/f5/65bdbefb9de7cbc2edf0b1f76286736536e31c216cfac1a5f84ea15f0fc1/pyobjc_framework_gameplaykit-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0e4f34db8177b8b4d89fd22a2a882a6c9f6e50cb438ea2fbbf96845481bcd80d", size = 13091, upload-time = "2025-06-14T20:50:05.962Z" }, + { url = "https://files.pythonhosted.org/packages/3b/84/7a4a2c358770f5ffdb6bdabb74dcefdfa248b17c250a7c0f9d16d3b8d987/pyobjc_framework_gameplaykit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b2fb27f9f48c3279937e938a0456a5231b5c89e53e3199b9d54009a0bbd1228a", size = 13125, upload-time = "2025-11-14T09:50:34.384Z" }, + { url = "https://files.pythonhosted.org/packages/35/1f/e5fe404f92ec0f9c8c37b00d6cb3ba96ee396c7f91b0a41a39b64bfc2743/pyobjc_framework_gameplaykit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:309b0d7479f702830c9be92dbe5855ac2557a9d23f05f063caf9d9fdb85ff5f0", size = 13150, upload-time = "2025-11-14T09:50:36.884Z" }, +] + +[[package]] +name = "pyobjc-framework-gamesave" +version = "12.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1b/1f/8d05585c844535e75dbc242dd6bdfecfc613d074dcb700362d1c908fb403/pyobjc_framework_gamesave-12.1.tar.gz", hash = "sha256:eb731c97aa644e78a87838ed56d0e5bdbaae125bdc8854a7772394877312cc2e", size = 12654, upload-time = "2025-11-14T10:15:48.344Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/59/ec/93d48cb048a1b35cea559cc9261b07f0d410078b3af029121302faa410d0/pyobjc_framework_gamesave-12.1-py2.py3-none-any.whl", hash = "sha256:432e69f8404be9290d42c89caba241a3156ed52013947978ac54f0f032a14ffd", size = 3689, upload-time = "2025-11-14T09:50:47.263Z" }, ] [[package]] name = "pyobjc-framework-healthkit" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/af/66/fa76f7c8e36e4c10677d42d91a8e220c135c610a06b759571db1abe26a32/pyobjc_framework_healthkit-11.1.tar.gz", hash = "sha256:20f59bd9e1ffafe5893b4eff5867fdfd20bd46c3d03bc4009219d82fc6815f76", size = 202009, upload-time = "2025-06-14T20:57:35.285Z" } +sdist = { url = "https://files.pythonhosted.org/packages/af/67/436630d00ba1028ea33cc9df2fc28e081481433e5075600f2ea1ff00f45e/pyobjc_framework_healthkit-12.1.tar.gz", hash = "sha256:29c5e5de54b41080b7a4b0207698ac6f600dcb9149becc9c6b3a69957e200e5c", size = 91802, upload-time = "2025-11-14T10:15:54.661Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/70/aa/c337d27dd98ffcbba2b1200126fcf624d1ccbeb7a4ed9205d48bfe2c1ca8/pyobjc_framework_healthkit-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:34bce3d144c461af7e577fcf6bbb7739d0537bf42f081960122923a7ef2e06c0", size = 20301, upload-time = "2025-06-14T20:50:12.158Z" }, - { url = "https://files.pythonhosted.org/packages/c7/08/12fca070ad2dc0b9c311df209b9b6d275ee192cb5ccbc94616d9ddd80d88/pyobjc_framework_healthkit-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ab4350f9fe65909107dd7992b367a6c8aac7dc31ed3d5b52eeb2310367d0eb0b", size = 20311, upload-time = "2025-06-14T20:50:13.271Z" }, + { url = "https://files.pythonhosted.org/packages/1e/37/b23d3c04ee37cbb94ff92caedc3669cd259be0344fcf6bdf1ff75ff0a078/pyobjc_framework_healthkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e67bce41f8f63c11000394c6ce1dc694655d9ff0458771340d2c782f9eafcc6e", size = 20785, upload-time = "2025-11-14T09:50:52.152Z" }, + { url = "https://files.pythonhosted.org/packages/65/87/bb1c438de51c4fa733a99ce4d3301e585f14d7efd94031a97707c0be2b46/pyobjc_framework_healthkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:15b6fc958ff5de42888b18dffdec839cb36d2dd8b82076ed2f21a51db5271109", size = 20799, upload-time = "2025-11-14T09:50:54.531Z" }, ] [[package]] name = "pyobjc-framework-imagecapturecore" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7b/3b/f4edbc58a8c7394393f8d00d0e764f655545e743ee4e33917f27b8c68e7b/pyobjc_framework_imagecapturecore-11.1.tar.gz", hash = "sha256:a610ceb6726e385b132a1481a68ce85ccf56f94667b6d6e1c45a2cfab806a624", size = 100398, upload-time = "2025-06-14T20:57:36.503Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/a1/39347381fc7d3cd5ab942d86af347b25c73f0ddf6f5227d8b4d8f5328016/pyobjc_framework_imagecapturecore-12.1.tar.gz", hash = "sha256:c4776c59f4db57727389d17e1ffd9c567b854b8db52198b3ccc11281711074e5", size = 46397, upload-time = "2025-11-14T10:15:58.541Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/50/72/465741d33757ef2162a1c9e12d6c8a41b5490949a92431c42a139c132303/pyobjc_framework_imagecapturecore-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ede4c15da909a4d819c732a5554b8282a7b56a1b73d82aef908124147921945a", size = 15999, upload-time = "2025-06-14T20:50:18.742Z" }, - { url = "https://files.pythonhosted.org/packages/61/62/54ed61e7cd3213549c8e98ca87a6b21afbb428d2c41948ae48ea019bf973/pyobjc_framework_imagecapturecore-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ed296c23d3d8d1d9af96a6486d09fb8d294cc318e4a2152e6f134151c76065f8", size = 16021, upload-time = "2025-06-14T20:50:19.836Z" }, + { url = "https://files.pythonhosted.org/packages/b4/6b/b34d5c9041e90b8a82d87025a1854b60a8ec2d88d9ef9e715f3a40109ed5/pyobjc_framework_imagecapturecore-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:64d1eb677fe5b658a6b6ed734b7120998ea738ca038ec18c4f9c776e90bd9402", size = 15983, upload-time = "2025-11-14T09:51:09.978Z" }, + { url = "https://files.pythonhosted.org/packages/50/13/632957b284dec3743d73fb30dbdf03793b3cf1b4c62e61e6484d870f3879/pyobjc_framework_imagecapturecore-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a2777e17ff71fb5a327a897e48c5c7b5a561723a80f990d26e6ed5a1b8748816", size = 16012, upload-time = "2025-11-14T09:51:12.058Z" }, ] [[package]] name = "pyobjc-framework-inputmethodkit" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/02/32/6a90bba682a31960ba1fc2d3b263e9be26043c4fb7aed273c13647c8b7d9/pyobjc_framework_inputmethodkit-11.1.tar.gz", hash = "sha256:7037579524041dcee71a649293c2660f9359800455a15e6a2f74a17b46d78496", size = 27203, upload-time = "2025-06-14T20:57:37.246Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5d/b8/d33dd8b7306029bbbd80525bf833fc547e6a223c494bf69a534487283a28/pyobjc_framework_inputmethodkit-12.1.tar.gz", hash = "sha256:f63b6fe2fa7f1412eae63baea1e120e7865e3b68ccfb7d8b0a4aadb309f2b278", size = 23054, upload-time = "2025-11-14T10:16:01.464Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/23/a4226040eec8ed930c81073776064f30d627db03e9db5b24720aad8fd14d/pyobjc_framework_inputmethodkit-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9b0e47c3bc7f1e628c906436c1735041ed2e9aa7cba3f70084b6311c63c508be", size = 9480, upload-time = "2025-06-14T20:50:25.184Z" }, - { url = "https://files.pythonhosted.org/packages/a8/0d/8a570072096fe339702e4ae9d98e59ee7c6c14124d4437c9a8c4482dda6d/pyobjc_framework_inputmethodkit-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:dd0c591a9d26967018a781fa4638470147ef2a9af3ab4a28612f147573eeefba", size = 9489, upload-time = "2025-06-14T20:50:25.875Z" }, + { url = "https://files.pythonhosted.org/packages/a7/04/1315f84dba5704a4976ea0185f877f0f33f28781473a817010cee209a8f0/pyobjc_framework_inputmethodkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c4e02f49816799a31d558866492048d69e8086178770b76f4c511295610e02ab", size = 9502, upload-time = "2025-11-14T09:51:24.708Z" }, + { url = "https://files.pythonhosted.org/packages/01/c2/59bea66405784b25f5d4e821467ba534a0b92dfc98e07257c971e2a8ed73/pyobjc_framework_inputmethodkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0b7d813d46a060572fc0c14ef832e4fe538ebf64e5cab80ee955191792ce0110", size = 9506, upload-time = "2025-11-14T09:51:26.924Z" }, ] [[package]] name = "pyobjc-framework-installerplugins" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4d/89/9a881e466476ca21f3ff3e8e87ccfba1aaad9b88f7eea4be6d3f05b07107/pyobjc_framework_installerplugins-11.1.tar.gz", hash = "sha256:363e59c7e05553d881f0facd41884f17b489ff443d7856e33dd0312064c746d9", size = 27451, upload-time = "2025-06-14T20:57:37.915Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/60/ca4ab04eafa388a97a521db7d60a812e2f81a3c21c2372587872e6b074f9/pyobjc_framework_installerplugins-12.1.tar.gz", hash = "sha256:1329a193bd2e92a2320a981a9a421a9b99749bade3e5914358923e94fe995795", size = 25277, upload-time = "2025-11-14T10:16:04.379Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3d/01/45c3d159d671c5f488a40f70aa6791b8483a3ed32b461800990bb5ab4bb3/pyobjc_framework_installerplugins-11.1-py2.py3-none-any.whl", hash = "sha256:f92b06c9595f3c800b7aabf1c1a235bfb4b2de3f5406d5f604d8e2ddd0aecb4e", size = 4798, upload-time = "2025-06-14T20:50:30.799Z" }, + { url = "https://files.pythonhosted.org/packages/99/1f/31dca45db3342882a628aa1b27707a283d4dc7ef558fddd2533175a0661a/pyobjc_framework_installerplugins-12.1-py2.py3-none-any.whl", hash = "sha256:d2201c81b05bdbe0abf0af25db58dc230802573463bea322f8b2863e37b511d5", size = 4813, upload-time = "2025-11-14T09:51:37.836Z" }, ] [[package]] name = "pyobjc-framework-instantmessage" -version = "11.1" +version = "12.1" 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'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9f/b9/5cec4dd0053b5f63c01211a60a286c47464d9f3e0c81bd682e6542dbff00/pyobjc_framework_instantmessage-11.1.tar.gz", hash = "sha256:c222aa61eb009704b333f6e63df01a0e690136e7e495907e5396882779bf9525", size = 33774, upload-time = "2025-06-14T20:57:38.553Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d4/67/66754e0d26320ba24a33608ca94d3f38e60ee6b2d2e094cb6269b346fdd4/pyobjc_framework_instantmessage-12.1.tar.gz", hash = "sha256:f453118d5693dc3c94554791bd2aaafe32a8b03b0e3d8ec3934b44b7fdd1f7e7", size = 31217, upload-time = "2025-11-14T10:16:07.693Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/91/34/acd618e90036822aaf01080d64558ba93e33e15ed91beb7d1d2aab290138/pyobjc_framework_instantmessage-11.1-py2.py3-none-any.whl", hash = "sha256:a70b716e279135eec5666af031f536c0f32dec57cfeae55cc9ff8457f10d4f3d", size = 5419, upload-time = "2025-06-14T20:50:31.993Z" }, + { url = "https://files.pythonhosted.org/packages/c1/38/6ae95b5c87d887c075bd5f4f7cca3d21dafd0a77cfdde870e87ca17579eb/pyobjc_framework_instantmessage-12.1-py2.py3-none-any.whl", hash = "sha256:cd91d38e8f356afd726b6ea8c235699316ea90edfd3472965c251efbf4150bc9", size = 5436, upload-time = "2025-11-14T09:51:39.557Z" }, ] [[package]] name = "pyobjc-framework-intents" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4c/af/d7f260d06b79acca8028e373c2fe30bf0be014388ba612f538f40597d929/pyobjc_framework_intents-11.1.tar.gz", hash = "sha256:13185f206493f45d6bd2d4903c2136b1c4f8b9aa37628309ace6ff4a906b4695", size = 448459, upload-time = "2025-06-14T20:57:39.589Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/a1/3bab6139e94b97eca098e1562f5d6840e3ff10ea1f7fd704a17111a97d5b/pyobjc_framework_intents-12.1.tar.gz", hash = "sha256:bd688c3ab34a18412f56e459e9dae29e1f4152d3c2048fcacdef5fc49dfb9765", size = 132262, upload-time = "2025-11-14T10:16:16.428Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c5/1d/10fdbf3b8dd6451465ae147143ba3159397a50ff81aed1eb86c153e987b5/pyobjc_framework_intents-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:da2f11ee64c75cfbebb1c2be52a20b3618f32b6c47863809ff64c61e8a1dffb9", size = 32227, upload-time = "2025-06-14T20:50:34.303Z" }, - { url = "https://files.pythonhosted.org/packages/8a/37/e6fa5737da42fb1265041bd3bd4f2be96f09294018fabf07139dd9dbc7b9/pyobjc_framework_intents-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a663e2de1b7ae7b547de013f89773963f8180023e36f2cebfe8060395dc34c33", size = 32253, upload-time = "2025-06-14T20:50:35.028Z" }, + { url = "https://files.pythonhosted.org/packages/d0/25/648db47b9c3879fa50c65ab7cc5fbe0dd400cc97141ac2658ef2e196c0b6/pyobjc_framework_intents-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:dc68dc49f1f8d9f8d2ffbc0f57ad25caac35312ddc444899707461e596024fec", size = 32134, upload-time = "2025-11-14T09:51:46.369Z" }, + { url = "https://files.pythonhosted.org/packages/7a/90/e9489492ae90b4c1ffd02c1221c0432b8768d475787e7887f79032c2487a/pyobjc_framework_intents-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0ea9f3e79bf4baf6c7b0fd2d2797184ed51a372bf7f32974b4424f9bd067ef50", size = 32156, upload-time = "2025-11-14T09:51:49.438Z" }, ] [[package]] name = "pyobjc-framework-intentsui" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-intents", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/86/46/20aae4a71efb514b096f36273a6129b48b01535bf501e5719d4a97fcb3a5/pyobjc_framework_intentsui-11.1.tar.gz", hash = "sha256:c8182155af4dce369c18d6e6ed9c25bbd8110c161ed5f1b4fb77cf5cdb99d135", size = 21305, upload-time = "2025-06-14T20:57:40.477Z" } +sdist = { url = "https://files.pythonhosted.org/packages/19/cf/f0e385b9cfbf153d68efe8d19e5ae672b59acbbfc1f9b58faaefc5ec8c9e/pyobjc_framework_intentsui-12.1.tar.gz", hash = "sha256:16bdf4b7b91c0d1ec9d5513a1182861f1b5b7af95d4f4218ff7cf03032d57f99", size = 19784, upload-time = "2025-11-14T10:16:18.716Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/31/e3/db74fc161bb85bc442dfddf50321924613b67cf49288e2a8b335bf6d546a/pyobjc_framework_intentsui-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:252f7833fabb036cd56d59b445922b25cda1561b54c0989702618a5561d8e748", size = 8936, upload-time = "2025-06-14T20:50:40.522Z" }, - { url = "https://files.pythonhosted.org/packages/43/7c/77fbd2a6f85eb905fbf27ba7540eaf2a026771ed5100fb1c01143cf47e9b/pyobjc_framework_intentsui-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:99a3ae40eb2a6ef1125955dd513c8acc88ce7d8d90130a8cdeaec8336e6fbec5", size = 8965, upload-time = "2025-06-14T20:50:41.281Z" }, + { url = "https://files.pythonhosted.org/packages/84/cc/7678f901cbf5bca8ccace568ae85ee7baddcd93d78754ac43a3bb5e5a7ac/pyobjc_framework_intentsui-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a877555e313d74ac3b10f7b4e738647eea9f744c00a227d1238935ac3f9d7968", size = 8961, upload-time = "2025-11-14T09:52:05.595Z" }, + { url = "https://files.pythonhosted.org/packages/f1/17/06812542a9028f5b2dcce56f52f25633c08b638faacd43bad862aad1b41d/pyobjc_framework_intentsui-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:cb894fcc4c9ea613a424dcf6fb48142d51174559b82cfdafac8cb47555c842cf", size = 8983, upload-time = "2025-11-14T09:52:07.667Z" }, ] [[package]] name = "pyobjc-framework-iobluetooth" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/93/e0/74b7b10c567b66c5f38b45ab240336325a4c889f43072d90f2b90aaeb7c0/pyobjc_framework_iobluetooth-11.1.tar.gz", hash = "sha256:094fd4be60cd1371b17cb4b33a3894e0d88a11b36683912be0540a7d51de76f1", size = 300992, upload-time = "2025-06-14T20:57:41.256Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/aa/ca3944bbdfead4201b4ae6b51510942c5a7d8e5e2dc3139a071c74061fdf/pyobjc_framework_iobluetooth-12.1.tar.gz", hash = "sha256:8a434118812f4c01dfc64339d41fe8229516864a59d2803e9094ee4cbe2b7edd", size = 155241, upload-time = "2025-11-14T10:16:28.896Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0a/13/31a514e48bd54880aadb1aac3a042fca5f499780628c18f4f54f06d4ece2/pyobjc_framework_iobluetooth-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7d8858cf2e4b2ef5e8bf29b76c06d4f2e6a2264c325146d07dfab94c46633329", size = 40378, upload-time = "2025-06-14T20:50:46.298Z" }, - { url = "https://files.pythonhosted.org/packages/da/94/eef57045762e955795a4e3312674045c52f8c506133acf9efe1b3370b93f/pyobjc_framework_iobluetooth-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:883781e7223cb0c63fab029d640721ded747f2e2b067645bc8b695ef02a4a4dd", size = 40406, upload-time = "2025-06-14T20:50:47.101Z" }, + { url = "https://files.pythonhosted.org/packages/f6/ab/ad6b36f574c3d52b5e935b1d57ab0f14f4e4cd328cc922d2b6ba6428c12d/pyobjc_framework_iobluetooth-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:77959f2ecf379aa41eb0848fdb25da7c322f9f4a82429965c87c4bc147137953", size = 40415, upload-time = "2025-11-14T09:52:22.069Z" }, + { url = "https://files.pythonhosted.org/packages/0b/b6/933b56afb5e84c3c35c074c9e30d7b701c6038989d4867867bdaa7ab618b/pyobjc_framework_iobluetooth-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:111a6e54be9e9dcf77fa2bf84fdac09fae339aa33087d8647ea7ffbd34765d4c", size = 40439, upload-time = "2025-11-14T09:52:26.071Z" }, ] [[package]] name = "pyobjc-framework-iobluetoothui" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-iobluetooth", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/dd/32/872272faeab6fe471eac6962c75db72ce65c3556e00b4edebdb41aaab7cb/pyobjc_framework_iobluetoothui-11.1.tar.gz", hash = "sha256:060c721f1cd8af4452493e8153b72b572edcd2a7e3b635d79d844f885afee860", size = 22835, upload-time = "2025-06-14T20:57:42.119Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8f/39/31d9a4e8565a4b1ec0a9ad81480dc0879f3df28799eae3bc22d1dd53705d/pyobjc_framework_iobluetoothui-12.1.tar.gz", hash = "sha256:81f8158bdfb2966a574b6988eb346114d6a4c277300c8c0a978c272018184e6f", size = 16495, upload-time = "2025-11-14T10:16:31.212Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d6/ed/35efed52ed3fa698480624e49ee5f3d859827aad5ff1c7334150c695e188/pyobjc_framework_iobluetoothui-11.1-py2.py3-none-any.whl", hash = "sha256:3c5a382d81f319a1ab9ab11b7ead04e53b758fdfeb604755d39c3039485eaac6", size = 4026, upload-time = "2025-06-14T20:50:52.018Z" }, + { url = "https://files.pythonhosted.org/packages/e3/c9/69aeda0cdb5d25d30dc4596a1c5b464fc81b5c0c4e28efc54b7e11bde51c/pyobjc_framework_iobluetoothui-12.1-py2.py3-none-any.whl", hash = "sha256:a6d8ab98efa3029130577a57ee96b183c35c39b0f1c53a7534f8838260fab993", size = 4045, upload-time = "2025-11-14T09:52:42.201Z" }, ] [[package]] name = "pyobjc-framework-iosurface" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c5/ce/38ec17d860d0ee040bb737aad8ca7c7ff46bef6c9cffa47382d67682bb2d/pyobjc_framework_iosurface-11.1.tar.gz", hash = "sha256:a468b3a31e8cd70a2675a3ddc7176ab13aa521c035f11188b7a3af8fff8b148b", size = 20275, upload-time = "2025-06-14T20:57:42.742Z" } +sdist = { url = "https://files.pythonhosted.org/packages/07/61/0f12ad67a72d434e1c84b229ec760b5be71f53671ee9018593961c8bfeb7/pyobjc_framework_iosurface-12.1.tar.gz", hash = "sha256:4b9d0c66431aa296f3ca7c4f84c00dc5fc961194830ad7682fdbbc358fa0db55", size = 17690, upload-time = "2025-11-14T10:16:33.282Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1d/26/fa912d397b577ee318b20110a3c959e898514a1dce19b4f13f238a31a677/pyobjc_framework_iosurface-11.1-py2.py3-none-any.whl", hash = "sha256:0c36ad56f8ec675dd07616418a2bc29126412b54627655abd21de31bcafe2a79", size = 4948, upload-time = "2025-06-14T20:50:52.801Z" }, + { url = "https://files.pythonhosted.org/packages/88/ad/793d98a7ed9b775dc8cce54144cdab0df1808a1960ee017e46189291a8f3/pyobjc_framework_iosurface-12.1-py2.py3-none-any.whl", hash = "sha256:e784e248397cfebef4655d2c0025766d3eaa4a70474e363d084fc5ce2a4f2a3f", size = 4902, upload-time = "2025-11-14T09:52:43.899Z" }, ] [[package]] name = "pyobjc-framework-ituneslibrary" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ee/43/aebefed774b434965752f9001685af0b19c02353aa7a12d2918af0948181/pyobjc_framework_ituneslibrary-11.1.tar.gz", hash = "sha256:e2212a9340e4328056ade3c2f9d4305c71f3f6af050204a135f9fa9aa3ba9c5e", size = 47388, upload-time = "2025-06-14T20:57:43.383Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/46/d9bcec88675bf4ee887b9707bd245e2a793e7cb916cf310f286741d54b1f/pyobjc_framework_ituneslibrary-12.1.tar.gz", hash = "sha256:7f3aa76c4d05f6fa6015056b88986cacbda107c3f29520dd35ef0936c7367a6e", size = 23730, upload-time = "2025-11-14T10:16:36.127Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/57/a29150f734b45b7408cc06efb9e2156328ae74624e5c4a7fe95118e13e94/pyobjc_framework_ituneslibrary-11.1-py2.py3-none-any.whl", hash = "sha256:4e87d41f82acb6d98cf70ac3c932a568ceb3c2035383cbf177f54e63de6b815f", size = 5191, upload-time = "2025-06-14T20:50:53.637Z" }, + { url = "https://files.pythonhosted.org/packages/de/92/b598694a1713ee46f45c4bfb1a0425082253cbd2b1caf9f8fd50f292b017/pyobjc_framework_ituneslibrary-12.1-py2.py3-none-any.whl", hash = "sha256:fb678d7c3ff14c81672e09c015e25880dac278aa819971f4d5f75d46465932ef", size = 5205, upload-time = "2025-11-14T09:52:45.733Z" }, ] [[package]] name = "pyobjc-framework-kernelmanagement" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1a/b6/708f10ac16425834cb5f8b71efdbe39b42c3b1009ac0c1796a42fc98cd36/pyobjc_framework_kernelmanagement-11.1.tar.gz", hash = "sha256:e934d1638cd89e38d6c6c5d4d9901b4295acee2d39cbfe0bd91aae9832961b44", size = 12543, upload-time = "2025-06-14T20:57:44.046Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/7e/ecbac119866e8ac2cce700d7a48a4297946412ac7cbc243a7084a6582fb1/pyobjc_framework_kernelmanagement-12.1.tar.gz", hash = "sha256:488062893ac2074e0c8178667bf864a21f7909c11111de2f6a10d9bc579df59d", size = 11773, upload-time = "2025-11-14T10:16:38.216Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/cf/17ff988ad1a0e55a4be5336c64220aa620ad19bb2f487a1122e9a864b29e/pyobjc_framework_kernelmanagement-11.1-py2.py3-none-any.whl", hash = "sha256:ec74690bd3383a7945c4a038cc4e1553ec5c1d2408b60e2b0003a3564bff7c47", size = 3656, upload-time = "2025-06-14T20:50:54.484Z" }, + { url = "https://files.pythonhosted.org/packages/94/32/04325a20f39d88d6d712437e536961a9e6a4ec19f204f241de6ed54d1d84/pyobjc_framework_kernelmanagement-12.1-py2.py3-none-any.whl", hash = "sha256:926381bfbfbc985c3e6dfcb7004af21bb16ff66ecbc08912b925989a705944ff", size = 3704, upload-time = "2025-11-14T09:52:47.268Z" }, ] [[package]] name = "pyobjc-framework-latentsemanticmapping" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/db/8a/4e54ee2bc77d59d770b287daf73b629e2715a2b3b31264d164398131cbad/pyobjc_framework_latentsemanticmapping-11.1.tar.gz", hash = "sha256:c6c3142301e4d375c24a47dfaeebc2f3d0fc33128a1c0a755794865b9a371145", size = 17444, upload-time = "2025-06-14T20:57:44.643Z" } +sdist = { url = "https://files.pythonhosted.org/packages/88/3c/b621dac54ae8e77ac25ee75dd93e310e2d6e0faaf15b8da13513258d6657/pyobjc_framework_latentsemanticmapping-12.1.tar.gz", hash = "sha256:f0b1fa823313eefecbf1539b4ed4b32461534b7a35826c2cd9f6024411dc9284", size = 15526, upload-time = "2025-11-14T10:16:40.149Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/50/d62815b02968236eb46c33f0fb0f7293a32ef68d2ec50c397140846d4e42/pyobjc_framework_latentsemanticmapping-11.1-py2.py3-none-any.whl", hash = "sha256:57f3b183021759a100d2847a4d8aa314f4033be3d2845038b62e5e823d96e871", size = 5454, upload-time = "2025-06-14T20:50:55.658Z" }, + { url = "https://files.pythonhosted.org/packages/29/8e/74a7eb29b545f294485cd3cf70557b4a35616555fe63021edbb3e0ea4c20/pyobjc_framework_latentsemanticmapping-12.1-py2.py3-none-any.whl", hash = "sha256:7d760213b42bc8b1bc1472e1873c0f78ee80f987225978837b1fecdceddbdbf4", size = 5471, upload-time = "2025-11-14T09:52:48.939Z" }, ] [[package]] name = "pyobjc-framework-launchservices" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-coreservices", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2b/0a/a76b13109b8ab563fdb2d7182ca79515f132f82ac6e1c52351a6b02896a8/pyobjc_framework_launchservices-11.1.tar.gz", hash = "sha256:80b55368b1e208d6c2c58395cc7bc12a630a2a402e00e4930493e9bace22b7bb", size = 20446, upload-time = "2025-06-14T20:57:45.258Z" } +sdist = { url = "https://files.pythonhosted.org/packages/37/d0/24673625922b0ad21546be5cf49e5ec1afaa4553ae92f222adacdc915907/pyobjc_framework_launchservices-12.1.tar.gz", hash = "sha256:4d2d34c9bd6fb7f77566155b539a2c70283d1f0326e1695da234a93ef48352dc", size = 20470, upload-time = "2025-11-14T10:16:42.499Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/12/30/a4de9021fdef7db0b224cdc1eae75811d889dc1debdfafdabf8be7bd0fb9/pyobjc_framework_launchservices-11.1-py2.py3-none-any.whl", hash = "sha256:8b58f1156651058b2905c87ce48468f4799db86a7edf760e1897fedd057a3908", size = 3889, upload-time = "2025-06-14T20:50:56.484Z" }, + { url = "https://files.pythonhosted.org/packages/08/af/9a0aebaab4c15632dc8fcb3669c68fa541a3278d99541d9c5f966fbc0909/pyobjc_framework_launchservices-12.1-py2.py3-none-any.whl", hash = "sha256:e63e78fceeed4d4dc807f9dabd5cf90407e4f552fab6a0d75a8d0af63094ad3c", size = 3905, upload-time = "2025-11-14T09:52:50.71Z" }, ] [[package]] name = "pyobjc-framework-libdispatch" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/be/89/7830c293ba71feb086cb1551455757f26a7e2abd12f360d375aae32a4d7d/pyobjc_framework_libdispatch-11.1.tar.gz", hash = "sha256:11a704e50a0b7dbfb01552b7d686473ffa63b5254100fdb271a1fe368dd08e87", size = 53942, upload-time = "2025-06-14T20:57:45.903Z" } +sdist = { url = "https://files.pythonhosted.org/packages/26/e8/75b6b9b3c88b37723c237e5a7600384ea2d84874548671139db02e76652b/pyobjc_framework_libdispatch-12.1.tar.gz", hash = "sha256:4035535b4fae1b5e976f3e0e38b6e3442ffea1b8aa178d0ca89faa9b8ecdea41", size = 38277, upload-time = "2025-11-14T10:16:46.235Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/cd/1010dee9f932a9686c27ce2e45e91d5b6875f5f18d2daafadea70090e111/pyobjc_framework_libdispatch-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2ddca472c2cbc6bb192e05b8b501d528ce49333abe7ef0eef28df3133a8e18b7", size = 20441, upload-time = "2025-06-14T20:50:58.3Z" }, - { url = "https://files.pythonhosted.org/packages/ac/92/ff9ceb14e1604193dcdb50643f2578e1010c68556711cd1a00eb25489c2b/pyobjc_framework_libdispatch-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:dc9a7b8c2e8a63789b7cf69563bb7247bde15353208ef1353fff0af61b281684", size = 15627, upload-time = "2025-06-14T20:50:59.055Z" }, + { url = "https://files.pythonhosted.org/packages/1f/75/c4aeab6ce7268373d4ceabbc5c406c4bbf557038649784384910932985f8/pyobjc_framework_libdispatch-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:954cc2d817b71383bd267cc5cd27d83536c5f879539122353ca59f1c945ac706", size = 20463, upload-time = "2025-11-14T09:52:55.703Z" }, + { url = "https://files.pythonhosted.org/packages/83/6f/96e15c7b2f7b51fc53252216cd0bed0c3541bc0f0aeb32756fefd31bed7d/pyobjc_framework_libdispatch-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0e9570d7a9a3136f54b0b834683bf3f206acd5df0e421c30f8fd4f8b9b556789", size = 15650, upload-time = "2025-11-14T09:52:59.284Z" }, ] [[package]] name = "pyobjc-framework-libxpc" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6a/c9/7e15e38ac23f5bfb4e82bdf3b7ef88e2f56a8b4ad884009bc2d5267d2e1f/pyobjc_framework_libxpc-11.1.tar.gz", hash = "sha256:8fd7468aa520ff19915f6d793070b84be1498cb87224bee2bad1f01d8375273a", size = 49135, upload-time = "2025-06-14T20:57:46.59Z" } +sdist = { url = "https://files.pythonhosted.org/packages/16/e4/364db7dc26f235e3d7eaab2f92057f460b39800bffdec3128f113388ac9f/pyobjc_framework_libxpc-12.1.tar.gz", hash = "sha256:e46363a735f3ecc9a2f91637750623f90ee74f9938a4e7c833e01233174af44d", size = 35186, upload-time = "2025-11-14T10:16:49.503Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/39/01/f5fbc7627f838aea5960f3287b75cbda9233f76fc3ba82f088630d5d16cc/pyobjc_framework_libxpc-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4ec8a7df24d85a561fc21d0eb0db89e8cddefeedec71c69bccf17f99804068ed", size = 19466, upload-time = "2025-06-14T20:51:05.138Z" }, - { url = "https://files.pythonhosted.org/packages/be/8f/dfd8e1e1e461f857a1e50138e69b17c0e62a8dcaf7dea791cc158d2bf854/pyobjc_framework_libxpc-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c29b2df8d74ff6f489afa7c39f7c848c5f3d0531a6bbe704571782ee6c820084", size = 19573, upload-time = "2025-06-14T20:51:05.902Z" }, + { url = "https://files.pythonhosted.org/packages/7c/c9/701630d025407497b7af50a795ddb6202c184da7f12b46aa683dae3d3552/pyobjc_framework_libxpc-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8d7201db995e5dcd38775fd103641d8fb69b8577d8e6a405c5562e6c0bb72fd1", size = 19620, upload-time = "2025-11-14T09:53:12.529Z" }, + { url = "https://files.pythonhosted.org/packages/82/7f/fdec72430f90921b154517a6f9bbeefa7bacfb16b91320742eb16a5955c5/pyobjc_framework_libxpc-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ba93e91e9ca79603dd265382e9f80e9bd32309cd09c8ac3e6489fc5b233676c8", size = 19730, upload-time = "2025-11-14T09:53:17.113Z" }, ] [[package]] name = "pyobjc-framework-linkpresentation" -version = "11.1" +version = "12.1" 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'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b9/76/22873be73f12a3a11ae57af13167a1d2379e4e7eef584de137156a00f5ef/pyobjc_framework_linkpresentation-11.1.tar.gz", hash = "sha256:a785f393b01fdaada6d7d6d8de46b7173babba205b13b44f1dc884b3695c2fc9", size = 14987, upload-time = "2025-06-14T20:57:47.277Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/58/c0c5919d883485ccdb6dccd8ecfe50271d2f6e6ab7c9b624789235ccec5a/pyobjc_framework_linkpresentation-12.1.tar.gz", hash = "sha256:84df6779591bb93217aa8bd82c10e16643441678547d2d73ba895475a02ade94", size = 13330, upload-time = "2025-11-14T10:16:52.169Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3d/59/23249e76e06e3c1a4f88acac7144999fae5a5a8ce4b90272d08cc0ac38ae/pyobjc_framework_linkpresentation-11.1-py2.py3-none-any.whl", hash = "sha256:018093469d780a45d98f4e159f1ea90771caec456b1599abcc6f3bf3c6873094", size = 3847, upload-time = "2025-06-14T20:51:10.817Z" }, + { url = "https://files.pythonhosted.org/packages/ad/51/226eb45f196f3bf93374713571aae6c8a4760389e1d9435c4a4cc3f38ea4/pyobjc_framework_linkpresentation-12.1-py2.py3-none-any.whl", hash = "sha256:853a84c7b525b77b114a7a8d798aef83f528ed3a6803bda12184fe5af4e79a47", size = 3865, upload-time = "2025-11-14T09:53:28.386Z" }, ] [[package]] name = "pyobjc-framework-localauthentication" -version = "11.1" +version = "12.1" 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'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e5/27/9e3195f3561574140e9b9071a36f7e0ebd18f50ade9261d23b5b9df8fccd/pyobjc_framework_localauthentication-11.1.tar.gz", hash = "sha256:3cd48907c794bd414ac68b8ac595d83c7e1453b63fc2cfc2d2035b690d31eaa1", size = 40700, upload-time = "2025-06-14T20:57:47.931Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8d/0e/7e5d9a58bb3d5b79a75d925557ef68084171526191b1c0929a887a553d4f/pyobjc_framework_localauthentication-12.1.tar.gz", hash = "sha256:2284f587d8e1206166e4495b33f420c1de486c36c28c4921d09eec858a699d05", size = 29947, upload-time = "2025-11-14T10:16:54.923Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4e/9a/acc10d45041445db99a121950b0d4f4ff977dbe5e95ec154fe2e1740ff08/pyobjc_framework_localauthentication-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1b6d52d07abd2240f7bc02b01ea1c630c280ed3fbc3fabe1e43b7444cfd41788", size = 10707, upload-time = "2025-06-14T20:51:12.436Z" }, - { url = "https://files.pythonhosted.org/packages/91/db/59f118cc2658814c6b501b7360ca4fe6a82fd289ced5897b99787130ceef/pyobjc_framework_localauthentication-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:aa3815f936612d78e51b53beed9115c57ae2fd49500bb52c4030a35856e6569e", size = 10730, upload-time = "2025-06-14T20:51:13.487Z" }, + { url = "https://files.pythonhosted.org/packages/6e/cb/cf9d13943e13dc868a68844448a7714c16f4ee6ecac384d21aaa5ac43796/pyobjc_framework_localauthentication-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2d7e1b3f987dc387361517525c2c38550dc44dfb3ba42dec3a9fbf35015831a6", size = 10762, upload-time = "2025-11-14T09:53:32.035Z" }, + { url = "https://files.pythonhosted.org/packages/05/93/91761ad4e5fa1c3ec25819865d1ccfbee033987147087bff4fcce67a4dc4/pyobjc_framework_localauthentication-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3af1acd287d830cc7f912f46cde0dab054952bde0adaf66c8e8524311a68d279", size = 10773, upload-time = "2025-11-14T09:53:34.074Z" }, ] [[package]] name = "pyobjc-framework-localauthenticationembeddedui" -version = "11.1" +version = "12.1" 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'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/29/7b/08c1e52487b07e9aee4c24a78f7c82a46695fa883113e3eece40f8e32d40/pyobjc_framework_localauthenticationembeddedui-11.1.tar.gz", hash = "sha256:22baf3aae606e5204e194f02bb205f244e27841ea7b4a4431303955475b4fa56", size = 14076, upload-time = "2025-06-14T20:57:48.557Z" } +sdist = { url = "https://files.pythonhosted.org/packages/31/20/83ab4180e29b9a4a44d735c7f88909296c6adbe6250e8e00a156aff753e1/pyobjc_framework_localauthenticationembeddedui-12.1.tar.gz", hash = "sha256:a15ec44bf2769c872e86c6b550b6dd4f58d4eda40ad9ff00272a67d279d1d4e9", size = 13611, upload-time = "2025-11-14T10:16:57.145Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/51/3d/2aaa3a4f0e82f0ac95cc432a6079f6dc20aa18a66c9a87ac6128c70df9ef/pyobjc_framework_localauthenticationembeddedui-11.1-py2.py3-none-any.whl", hash = "sha256:3539a947b102b41ea6e40e7c145f27280d2f36a2a9a1211de32fa675d91585eb", size = 3973, upload-time = "2025-06-14T20:51:18.2Z" }, + { url = "https://files.pythonhosted.org/packages/30/7d/0d46639c7a26b6af928ab4c822cd28b733791e02ac28cc84c3014bcf7dc7/pyobjc_framework_localauthenticationembeddedui-12.1-py2.py3-none-any.whl", hash = "sha256:a7ce7b56346597b9f4768be61938cbc8fc5b1292137225b6c7f631b9cde97cd7", size = 3991, upload-time = "2025-11-14T09:53:42.958Z" }, ] [[package]] name = "pyobjc-framework-mailkit" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7e/7e/f22d733897e7618bd70a658b0353f5f897c583df04e7c5a2d68b99d43fbb/pyobjc_framework_mailkit-11.1.tar.gz", hash = "sha256:bf97dc44cb09b9eb9d591660dc0a41f077699976144b954caa4b9f0479211fd7", size = 32012, upload-time = "2025-06-14T20:57:49.173Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2a/98/3d9028620c1cd32ff4fb031155aba3b5511e980cdd114dd51383be9cb51b/pyobjc_framework_mailkit-12.1.tar.gz", hash = "sha256:d5574b7259baec17096410efcaacf5d45c7bb5f893d4c25cbb7072369799b652", size = 20996, upload-time = "2025-11-14T10:16:59.449Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bf/23/1897fc071e8e71bc0bef53bcb0d600eb1ed3bd6c4609f7257ddfe151d37a/pyobjc_framework_mailkit-11.1-py2.py3-none-any.whl", hash = "sha256:8e6026462567baba194468e710e83787f29d9e8c98ea0583f7b401ea9515966e", size = 4854, upload-time = "2025-06-14T20:51:18.978Z" }, + { url = "https://files.pythonhosted.org/packages/70/8d/3c968b736a3a8bd9d8e870b39b1c772a013eea1b81b89fc4efad9021a6cb/pyobjc_framework_mailkit-12.1-py2.py3-none-any.whl", hash = "sha256:536ac0c4ea3560364cd159a6512c3c18a744a12e4e0883c07df0f8a2ff21e3fe", size = 4871, upload-time = "2025-11-14T09:53:44.697Z" }, ] [[package]] name = "pyobjc-framework-mapkit" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, @@ -3287,28 +3342,28 @@ dependencies = [ { name = "pyobjc-framework-corelocation", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/57/f0/505e074f49c783f2e65ca82174fd2d4348568f3f7281c1b81af816cf83bb/pyobjc_framework_mapkit-11.1.tar.gz", hash = "sha256:f3a5016f266091be313a118a42c0ea4f951c399b5259d93639eb643dacc626f1", size = 165614, upload-time = "2025-06-14T20:57:50.362Z" } +sdist = { url = "https://files.pythonhosted.org/packages/36/bb/2a668203c20e509a648c35e803d79d0c7f7816dacba74eb5ad8acb186790/pyobjc_framework_mapkit-12.1.tar.gz", hash = "sha256:dbc32dc48e821aaa9b4294402c240adbc1c6834e658a07677b7c19b7990533c5", size = 63520, upload-time = "2025-11-14T10:17:04.221Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/dc/a7e03a9066e6eed9d1707ae45453a5332057950e16de6665402c804ae7af/pyobjc_framework_mapkit-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:daee6bedc3acc23e62d1e7c3ab97e10425ca57e0c3cc47d2b212254705cc5c44", size = 22481, upload-time = "2025-06-14T20:51:20.694Z" }, - { url = "https://files.pythonhosted.org/packages/30/0a/50aa2fba57499ff657cacb9ef1730006442e4f42d9a822dae46239603ecc/pyobjc_framework_mapkit-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:91976c6dbc8cbb020e059a0ccdeab8933184712f77164dbad5a5526c1a49599d", size = 22515, upload-time = "2025-06-14T20:51:21.439Z" }, + { url = "https://files.pythonhosted.org/packages/d0/8f/411067e5c5cd23b9fe4c5edfb02ed94417b94eefe56562d36e244edc70ff/pyobjc_framework_mapkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e8aa82d4aae81765c05dcd53fd362af615aa04159fc7a1df1d0eac9c252cb7d5", size = 22493, upload-time = "2025-11-14T09:53:50.112Z" }, + { url = "https://files.pythonhosted.org/packages/11/00/a3de41cdf3e6cd7a144e38999fe1ea9777ad19e19d863f2da862e7affe7b/pyobjc_framework_mapkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:84ad7766271c114bdc423e4e2ff5433e5fc6771a3338b5f8e4b54d0340775800", size = 22518, upload-time = "2025-11-14T09:53:52.727Z" }, ] [[package]] name = "pyobjc-framework-mediaaccessibility" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8d/81/60412b423c121de0fa0aa3ef679825e1e2fe8b00fceddec7d72333ef564b/pyobjc_framework_mediaaccessibility-11.1.tar.gz", hash = "sha256:52479a998fec3d079d2d4590a945fc78c41fe7ac8c76f1964c9d8156880565a4", size = 18440, upload-time = "2025-06-14T20:57:51.126Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/10/dc1007e56944ed2e981e69e7b2fed2b2202c79b0d5b742b29b1081d1cbdd/pyobjc_framework_mediaaccessibility-12.1.tar.gz", hash = "sha256:cc4e3b1d45e84133d240318d53424eff55968f5c6873c2c53267598853445a3f", size = 16325, upload-time = "2025-11-14T10:17:07.454Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/99/a1/f4cbdf8478ad01859e2c8eef08e28b8a53b9aa4fe5d238a86bad29b73555/pyobjc_framework_mediaaccessibility-11.1-py2.py3-none-any.whl", hash = "sha256:cd07e7fc375ff1e8d225e0aa2bd9c2c1497a4d3aa5a80bfb13b08800fcd7f034", size = 4691, upload-time = "2025-06-14T20:51:26.596Z" }, + { url = "https://files.pythonhosted.org/packages/a2/0c/7fb5462561f59d739192c6d02ba0fd36ad7841efac5a8398a85a030ef7fc/pyobjc_framework_mediaaccessibility-12.1-py2.py3-none-any.whl", hash = "sha256:2ff8845c97dd52b0e5cf53990291e6d77c8a73a7aac0e9235d62d9a4256916d1", size = 4800, upload-time = "2025-11-14T09:54:05.04Z" }, ] [[package]] name = "pyobjc-framework-mediaextension" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, @@ -3316,276 +3371,276 @@ dependencies = [ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-coremedia", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e1/09/fd214dc0cf3f3bc3f528815af4799c0cb7b4bf4032703b19ea63486a132b/pyobjc_framework_mediaextension-11.1.tar.gz", hash = "sha256:85a1c8a94e9175fb364c453066ef99b95752343fd113f08a3805cad56e2fa709", size = 58489, upload-time = "2025-06-14T20:57:51.796Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/aa/1e8015711df1cdb5e4a0aa0ed4721409d39971ae6e1e71915e3ab72423a3/pyobjc_framework_mediaextension-12.1.tar.gz", hash = "sha256:44409d63cc7d74e5724a68e3f9252cb62fd0fd3ccf0ca94c6a33e5c990149953", size = 39425, upload-time = "2025-11-14T10:17:11.486Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/25/95315f730e9b73ef9e8936ed3ded636d3ac71b4d5653d4caf1d20a2314a8/pyobjc_framework_mediaextension-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:915c0cbb04913beb1f1ac8939dc0e615da8ddfba3927863a476af49f193415c5", size = 38858, upload-time = "2025-06-14T20:51:28.296Z" }, - { url = "https://files.pythonhosted.org/packages/56/78/2c2d8265851f6060dbf4434c21bd67bf569b8c3071ba1f257e43aae563a8/pyobjc_framework_mediaextension-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:06cb19004413a4b08dd75cf1e5dadea7f2df8d15feeeb7adb529d0cf947fa789", size = 38859, upload-time = "2025-06-14T20:51:29.102Z" }, + { url = "https://files.pythonhosted.org/packages/8e/6f/60b63edf5d27acf450e4937b7193c1a2bd6195fee18e15df6a5734dedb71/pyobjc_framework_mediaextension-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9555f937f2508bd2b6264cba088e2c2e516b2f94a6c804aee40e33fd89c2fb78", size = 38957, upload-time = "2025-11-14T09:54:13.22Z" }, + { url = "https://files.pythonhosted.org/packages/2b/ed/99038bcf72ec68e452709af10a087c1377c2d595ba4e66d7a2b0775145d2/pyobjc_framework_mediaextension-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:442bc3a759efb5c154cb75d643a5e182297093533fcdd1c24be6f64f68b93371", size = 38973, upload-time = "2025-11-14T09:54:16.701Z" }, ] [[package]] name = "pyobjc-framework-medialibrary" -version = "11.1" +version = "12.1" 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'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2b/06/11ff622fb5fbdd557998a45cedd2b0a1c7ea5cc6c5cb015dd6e42ebd1c41/pyobjc_framework_medialibrary-11.1.tar.gz", hash = "sha256:102f4326f789734b7b2dfe689abd3840ca75a76fb8058bd3e4f85398ae2ce29d", size = 18706, upload-time = "2025-06-14T20:57:52.474Z" } +sdist = { url = "https://files.pythonhosted.org/packages/34/e9/848ebd02456f8fdb41b42298ec585bfed5899dbd30306ea5b0a7e4c4b341/pyobjc_framework_medialibrary-12.1.tar.gz", hash = "sha256:690dcca09b62511df18f58e8566cb33d9652aae09fe63a83f594bd018b5edfcd", size = 15995, upload-time = "2025-11-14T10:17:15.45Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/62/2b/a4200080d97f88fdd406119bb8f00ccb7f32794f84735485510c14e87e76/pyobjc_framework_medialibrary-11.1-py2.py3-none-any.whl", hash = "sha256:779be84bd280f63837ce02028ca46b41b090902aa4205887ffd5777f49377669", size = 4340, upload-time = "2025-06-14T20:51:34.339Z" }, + { url = "https://files.pythonhosted.org/packages/c2/cd/eeaf8585a343fda5b8cf3b8f144c872d1057c845202098b9441a39b76cb0/pyobjc_framework_medialibrary-12.1-py2.py3-none-any.whl", hash = "sha256:1f03ad6802a5c6e19ee3208b065689d3ec79defe1052cb80e00f54e1eff5f2a0", size = 4361, upload-time = "2025-11-14T09:54:32.259Z" }, ] [[package]] name = "pyobjc-framework-mediaplayer" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-avfoundation", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/80/d5/daba26eb8c70af1f3823acfd7925356acc4dd75eeac4fc86dc95d94d0e15/pyobjc_framework_mediaplayer-11.1.tar.gz", hash = "sha256:d07a634b98e1b9eedd82d76f35e616525da096bd341051ea74f0971e0f2f2ddd", size = 93749, upload-time = "2025-06-14T20:57:53.165Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e9/f0/851f6f47e11acbd62d5f5dcb8274afc969135e30018591f75bf3cbf6417f/pyobjc_framework_mediaplayer-12.1.tar.gz", hash = "sha256:5ef3f669bdf837d87cdb5a486ec34831542360d14bcba099c7c2e0383380794c", size = 35402, upload-time = "2025-11-14T10:17:18.97Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/aa/b37aac80d821bd2fa347ddad1f6c7c75b23155e500edf1cb3b3740c27036/pyobjc_framework_mediaplayer-11.1-py2.py3-none-any.whl", hash = "sha256:b655cf537ea52d73209eb12935a047301c30239b318a366600f0f44335d51c9a", size = 6960, upload-time = "2025-06-14T20:51:35.171Z" }, + { url = "https://files.pythonhosted.org/packages/58/c0/038ee3efd286c0fbc89c1e0cb688f4670ed0e5803aa36e739e79ffc91331/pyobjc_framework_mediaplayer-12.1-py2.py3-none-any.whl", hash = "sha256:85d9baec131807bfdf0f4c24d4b943e83cce806ab31c95c7e19c78e3fb7eefc8", size = 7120, upload-time = "2025-11-14T09:54:33.901Z" }, ] [[package]] name = "pyobjc-framework-mediatoolbox" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e1/68/cc230d2dfdeb974fdcfa828de655a43ce2bf4962023fd55bbb7ab0970100/pyobjc_framework_mediatoolbox-11.1.tar.gz", hash = "sha256:97834addc5179b3165c0d8cd74cc97ad43ed4c89547724216426348aca3b822a", size = 23568, upload-time = "2025-06-14T20:57:53.913Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/71/be5879380a161f98212a336b432256f307d1dcbaaaeb8ec988aea2ada2cd/pyobjc_framework_mediatoolbox-12.1.tar.gz", hash = "sha256:385b48746a5f08756ee87afc14037e552954c427ed5745d7ece31a21a7bad5ab", size = 22305, upload-time = "2025-11-14T10:17:22.501Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/99/bc/6b69ca3c2bf1573b907be460c6a413ff2dfd1c037da53f46aec3bcdb3c73/pyobjc_framework_mediatoolbox-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:da60c0409b18dfb9fa60a60589881e1382c007700b99722926270feadcf3bfc1", size = 12630, upload-time = "2025-06-14T20:51:36.873Z" }, - { url = "https://files.pythonhosted.org/packages/b5/23/6b5d999e1e71c42d5d116d992515955ac1bbc5cf4890072bb26f38eb9802/pyobjc_framework_mediatoolbox-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2867c91645a335ee29b47e9c0e9fd3ea8c9daad0c0719c50b8bf244d76998056", size = 12785, upload-time = "2025-06-14T20:51:37.593Z" }, + { url = "https://files.pythonhosted.org/packages/8f/7a/f20ebd3c590b2cc85cde3e608e49309bfccf9312e4aca7b7ea60908d36d7/pyobjc_framework_mediatoolbox-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:74de0cb2d5aaa77e81f8b97eab0f39cd2fab5bf6fa7c6fb5546740cbfb1f8c1f", size = 12656, upload-time = "2025-11-14T09:54:39.215Z" }, + { url = "https://files.pythonhosted.org/packages/9c/94/d5ee221f2afbc64b2a7074efe25387cd8700e8116518904b28091ea6ad74/pyobjc_framework_mediatoolbox-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d7bcfeeff3fbf7e9e556ecafd8eaed2411df15c52baf134efa7480494e6faf6d", size = 12818, upload-time = "2025-11-14T09:54:41.251Z" }, ] [[package]] name = "pyobjc-framework-metal" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/af/cf/29fea96fd49bf72946c5dac4c43ef50f26c15e9f76edd6f15580d556aa23/pyobjc_framework_metal-11.1.tar.gz", hash = "sha256:f9fd3b7574a824632ee9b7602973da30f172d2b575dd0c0f5ef76b44cfe9f6f9", size = 446549, upload-time = "2025-06-14T20:57:54.731Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/06/a84f7eb8561d5631954b9458cfca04b690b80b5b85ce70642bc89335f52a/pyobjc_framework_metal-12.1.tar.gz", hash = "sha256:bb554877d5ee2bf3f340ad88e8fe1b85baab7b5ec4bd6ae0f4f7604147e3eae7", size = 181847, upload-time = "2025-11-14T10:17:34.157Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/e8/cd0621e246dc0dc06f55c50af3002573ad19208e30f6806ec997ac587886/pyobjc_framework_metal-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:157a0052be459ffb35a3687f77a96ea87b42caf4cdd0b9f7245242b100edb4f0", size = 58066, upload-time = "2025-06-14T20:51:44.243Z" }, - { url = "https://files.pythonhosted.org/packages/4c/94/3d5a8bed000dec4a13e72dde175898b488192716b7256a05cc253c77020d/pyobjc_framework_metal-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1f3aae0f9a4192a7f4f158dbee126ab5ef63a81bf9165ec63bc50c353c8d0e6f", size = 57969, upload-time = "2025-06-14T20:51:45.051Z" }, + { url = "https://files.pythonhosted.org/packages/1d/cf/edbb8b6dd084df3d235b74dbeb1fc5daf4d063ee79d13fa3bc1cb1779177/pyobjc_framework_metal-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:59e10f9b36d2e409f80f42b6175457a07b18a21ca57ff268f4bc519cd30db202", size = 75920, upload-time = "2025-11-14T09:55:01.048Z" }, + { url = "https://files.pythonhosted.org/packages/d0/48/9286d06e1b14c11b65d3fea1555edc0061d9ebe11898dff8a14089e3a4c9/pyobjc_framework_metal-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:38ab566b5a2979a43e13593d3eb12000a45e574576fe76996a5e1eb75ad7ac78", size = 75841, upload-time = "2025-11-14T09:55:06.801Z" }, ] [[package]] name = "pyobjc-framework-metalfx" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-metal", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/10/20/4c839a356b534c161fb97e06589f418fc78cc5a0808362bdecf4f9a61a8d/pyobjc_framework_metalfx-11.1.tar.gz", hash = "sha256:555c1b895d4ba31be43930f45e219a5d7bb0e531d148a78b6b75b677cc588fd8", size = 27002, upload-time = "2025-06-14T20:57:55.949Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/09/ce5c74565677fde66de3b9d35389066b19e5d1bfef9d9a4ad80f0c858c0c/pyobjc_framework_metalfx-12.1.tar.gz", hash = "sha256:1551b686fb80083a97879ce0331bdb1d4c9b94557570b7ecc35ebf40ff65c90b", size = 29470, upload-time = "2025-11-14T10:17:37.16Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/f5/df29eeaaf053cd931fb74204a5f8827f88875a81c456b1e0fa24ea0bbcee/pyobjc_framework_metalfx-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cbfca74f437fcde89de85d14de33c2e617d3084f5fc2b4d614a700e516324f55", size = 10091, upload-time = "2025-06-14T20:51:51.084Z" }, - { url = "https://files.pythonhosted.org/packages/36/73/a8df8fa445a09fbc917a495a30b13fbcf224b5576c1e464d5ece9824a493/pyobjc_framework_metalfx-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:60e1dcdf133d2504d810c3a9ba5a02781c9d54c2112a9238de8e3ca4e8debf31", size = 10107, upload-time = "2025-06-14T20:51:51.783Z" }, + { url = "https://files.pythonhosted.org/packages/8b/e5/5494639c927085bbba1a310e73662e0bda44b90cdff67fa03a4e1c24d4c4/pyobjc_framework_metalfx-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1ec3f7ab036eae45e067fbf209676f98075892aa307d73bb9394304960746cd2", size = 15026, upload-time = "2025-11-14T09:55:35.239Z" }, + { url = "https://files.pythonhosted.org/packages/2a/0b/508e3af499694f4eec74cc3ab0530e38db76e43a27db9ecb98c50c68f5f9/pyobjc_framework_metalfx-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a4418ae5c2eb77ec00695fa720a547638dc252dfd77ecb6feb88f713f5a948fd", size = 15062, upload-time = "2025-11-14T09:55:37.352Z" }, ] [[package]] name = "pyobjc-framework-metalkit" -version = "11.1" +version = "12.1" 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'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/45/cb/7e01bc61625c7a6fea9c9888c9ed35aa6bbc47cda2fcd02b6525757bc2b8/pyobjc_framework_metalkit-11.1.tar.gz", hash = "sha256:8811cd81ee9583b9330df4f2499a73dcc53f3359cb92767b409acaec9e4faa1e", size = 45135, upload-time = "2025-06-14T20:57:56.601Z" } +sdist = { url = "https://files.pythonhosted.org/packages/14/15/5091147aae12d4011a788b93971c3376aaaf9bf32aa935a2c9a06a71e18b/pyobjc_framework_metalkit-12.1.tar.gz", hash = "sha256:14cc5c256f0e3471b412a5b3582cb2a0d36d3d57401a8aa09e433252d1c34824", size = 25473, upload-time = "2025-11-14T10:17:39.721Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/eb/fd5640015fc91b16e23cafe3a84508775344cd13f621e62b9c32d1750a83/pyobjc_framework_metalkit-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:95abb993d17be7a9d1174701594cc040e557983d0a0e9f49b1dfa9868ef20ed6", size = 8711, upload-time = "2025-06-14T20:51:56.765Z" }, - { url = "https://files.pythonhosted.org/packages/87/0c/516b6d7a67a170b7d2316701d5288797a19dd283fcc2f73b7b78973e1392/pyobjc_framework_metalkit-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4854cf74fccf6ce516b49bf7cf8fc7c22da9a3743914a2f4b00f336206ad47ec", size = 8730, upload-time = "2025-06-14T20:51:57.824Z" }, + { url = "https://files.pythonhosted.org/packages/10/c5/f72cbc3a5e83211cbfa33b60611abcebbe893854d0f2b28ff6f444f97549/pyobjc_framework_metalkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:28636454f222d9b20cb61f6e8dc1ebd48237903feb4d0dbdf9d7904c542475e5", size = 8735, upload-time = "2025-11-14T09:55:50.053Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c0/c8b5b060895cd51493afe3f09909b7e34893b1161cf4d93bc8e3cd306129/pyobjc_framework_metalkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1c4869076571d94788fe539fabfdd568a5c8e340936c7726d2551196640bd152", size = 8755, upload-time = "2025-11-14T09:55:51.683Z" }, ] [[package]] name = "pyobjc-framework-metalperformanceshaders" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-metal", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d0/11/5df398a158a6efe2c87ac5cae121ef2788242afe5d4302d703147b9fcd91/pyobjc_framework_metalperformanceshaders-11.1.tar.gz", hash = "sha256:8a312d090a0f51651e63d9001e6cc7c1aa04ceccf23b494cbf84b7fd3d122071", size = 302113, upload-time = "2025-06-14T20:57:57.407Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c5/68/58da38e54aa0d8c19f0d3084d8c84e92d54cc8c9254041f07119d86aa073/pyobjc_framework_metalperformanceshaders-12.1.tar.gz", hash = "sha256:b198e755b95a1de1525e63c3b14327ae93ef1d88359e6be1ce554a3493755b50", size = 137301, upload-time = "2025-11-14T10:17:49.554Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/64/ce/bbcf26f8aa94fb6edcf1a71ef23cd8df2afd4b5c2be451432211827c2ab0/pyobjc_framework_metalperformanceshaders-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:81ec1f85c55d11529008e6a0fb1329d5184620f04d89751c11bf14d7dd9798ee", size = 32650, upload-time = "2025-06-14T20:52:04.451Z" }, - { url = "https://files.pythonhosted.org/packages/89/df/f844516a54ef0fa1d047fe5fd94b63bc8b1218c09f7d4309b2a67a79708d/pyobjc_framework_metalperformanceshaders-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:06b2a4e446fe859e30f7efc7ccfbaefd443225a6ec53d949a113a6a4acc16c4c", size = 32888, upload-time = "2025-06-14T20:52:05.225Z" }, + { url = "https://files.pythonhosted.org/packages/00/0f/6dc06a08599a3bc211852a5e6dcb4ed65dfbf1066958feb367ba7702798a/pyobjc_framework_metalperformanceshaders-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e0159a6f731dc0fd126481a26490683586864e9d47c678900049a8ffe0135f56", size = 32988, upload-time = "2025-11-14T09:56:05.323Z" }, + { url = "https://files.pythonhosted.org/packages/62/84/d505496fca9341e0cb11258ace7640cd986fe3e831f8b4749035e9f82109/pyobjc_framework_metalperformanceshaders-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c00e786c352b3ff5d86cf0cf3a830dc9f6fc32a03ae1a7539d20d11324adb2e8", size = 33242, upload-time = "2025-11-14T09:56:09.354Z" }, ] [[package]] name = "pyobjc-framework-metalperformanceshadersgraph" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-metalperformanceshaders", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/32/c3/8d98661f7eecd1f1b0d80a80961069081b88efd3a82fbbed2d7e6050c0ad/pyobjc_framework_metalperformanceshadersgraph-11.1.tar.gz", hash = "sha256:d25225aab4edc6f786b29fe3d9badc4f3e2d0caeab1054cd4f224258c1b6dbe2", size = 105098, upload-time = "2025-06-14T20:57:58.273Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/56/7ad0cd085532f7bdea9a8d4e9a2dfde376d26dd21e5eabdf1a366040eff8/pyobjc_framework_metalperformanceshadersgraph-12.1.tar.gz", hash = "sha256:b8fd017b47698037d7b172d41bed7a4835f4c4f2a288235819d200005f89ee35", size = 42992, upload-time = "2025-11-14T10:17:53.502Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0d/a1/2033cf8b0d9f059e3495a1d9a691751b242379c36dd5bcb96c8edb121c9e/pyobjc_framework_metalperformanceshadersgraph-11.1-py2.py3-none-any.whl", hash = "sha256:9b8b014e8301c2ae608a25f73bbf23c8f3f73a6f5fdbafddad509a21b84df681", size = 6461, upload-time = "2025-06-14T20:52:10.522Z" }, + { url = "https://files.pythonhosted.org/packages/e2/c9/5e7fd0d4bc9bdf7b442f36e020677c721ba9b4c1dc1fa3180085f22a4ef9/pyobjc_framework_metalperformanceshadersgraph-12.1-py2.py3-none-any.whl", hash = "sha256:85a1c7a6114ada05c7924b3235a1a98c45359410d148097488f15aee5ebb6ab9", size = 6481, upload-time = "2025-11-14T09:56:23.66Z" }, ] [[package]] name = "pyobjc-framework-metrickit" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/bd/48/8ae969a51a91864000e39c1de74627b12ff587b1dbad9406f7a30dfe71f8/pyobjc_framework_metrickit-11.1.tar.gz", hash = "sha256:a79d37575489916c35840e6a07edd958be578d3be7a3d621684d028d721f0b85", size = 40952, upload-time = "2025-06-14T20:57:58.996Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/13/5576ddfbc0b174810a49171e2dbe610bdafd3b701011c6ecd9b3a461de8a/pyobjc_framework_metrickit-12.1.tar.gz", hash = "sha256:77841daf6b36ba0c19df88545fd910c0516acf279e6b7b4fa0a712a046eaa9f1", size = 27627, upload-time = "2025-11-14T10:17:56.353Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/cd/e459511c194d25c4acd31cbdb5c118215795785840861d55dbc8bd55cf35/pyobjc_framework_metrickit-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a5d2b394f7acadd17d8947d188106424f59393b45dd4a842ac3cc50935170e3e", size = 8063, upload-time = "2025-06-14T20:52:12.696Z" }, - { url = "https://files.pythonhosted.org/packages/55/d1/aea4655e7eaa9ab19da8fe78ab363270443059c8a542b8f8a071b4988b57/pyobjc_framework_metrickit-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a034e6b982e915da881edef87d71b063e596511d52aef7a32c683571f364156e", size = 8081, upload-time = "2025-06-14T20:52:13.72Z" }, + { url = "https://files.pythonhosted.org/packages/5e/b0/e57c60af3e9214e05309dca201abb82e10e8cf91952d90d572b641d62027/pyobjc_framework_metrickit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:da6650afd9523cf7a9cae177f4bbd1ad45cc122d97784785fa1482847485142c", size = 8102, upload-time = "2025-11-14T09:56:27.194Z" }, + { url = "https://files.pythonhosted.org/packages/b7/04/8da5126e47306438c99750f1dfed430d7cc388f6b7f420ae748f3060ab96/pyobjc_framework_metrickit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3ec96e9ec7dc37fbce57dae277f0d89c66ffe1c3fa2feaca1b7125f8b2b29d87", size = 8120, upload-time = "2025-11-14T09:56:28.73Z" }, ] [[package]] name = "pyobjc-framework-mlcompute" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8b/e6/f064dec650fb1209f41aba0c3074416cb9b975a7cf4d05d93036e3d917f0/pyobjc_framework_mlcompute-11.1.tar.gz", hash = "sha256:f6c4c3ea6a62e4e3927abf9783c40495aa8bb9a8c89def744b0822da58c2354b", size = 89021, upload-time = "2025-06-14T20:57:59.997Z" } +sdist = { url = "https://files.pythonhosted.org/packages/00/69/15f8ce96c14383aa783c8e4bc1e6d936a489343bb197b8e71abb3ddc1cb8/pyobjc_framework_mlcompute-12.1.tar.gz", hash = "sha256:3281db120273dcc56e97becffd5cedf9c62042788289f7be6ea067a863164f1e", size = 40698, upload-time = "2025-11-14T10:17:59.792Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/cc/f47a4ac2d1a792b82206fdab58cc61b3aae15e694803ea2c81f3dfc16d9d/pyobjc_framework_mlcompute-11.1-py2.py3-none-any.whl", hash = "sha256:975150725e919f8d3d33f830898f3cd2fd19a440999faab320609487f4eae19d", size = 6778, upload-time = "2025-06-14T20:52:19.844Z" }, + { url = "https://files.pythonhosted.org/packages/ac/f7/4614b9ccd0151795e328b9ed881fbcbb13e577a8ec4ae3507edb1a462731/pyobjc_framework_mlcompute-12.1-py2.py3-none-any.whl", hash = "sha256:4f0fc19551d710a03dfc4c7129299897544ff8ea76db6c7539ecc2f9b2571bde", size = 6744, upload-time = "2025-11-14T09:56:36.973Z" }, ] [[package]] name = "pyobjc-framework-modelio" -version = "11.1" +version = "12.1" 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'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a0/27/140bf75706332729de252cc4141e8c8afe16a0e9e5818b5a23155aa3473c/pyobjc_framework_modelio-11.1.tar.gz", hash = "sha256:fad0fa2c09d468ac7e49848e144f7bbce6826f2178b3120add8960a83e5bfcb7", size = 123203, upload-time = "2025-06-14T20:58:01.035Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b4/11/32c358111b623b4a0af9e90470b198fffc068b45acac74e1ba711aee7199/pyobjc_framework_modelio-12.1.tar.gz", hash = "sha256:d041d7bca7c2a4526344d3e593347225b7a2e51a499b3aa548895ba516d1bdbb", size = 66482, upload-time = "2025-11-14T10:18:04.92Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6c/66/8109e52c7d97a108d4852a2032c9d7a7ecd27c6085bd7b2920b2ab575df4/pyobjc_framework_modelio-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4365fb96eb42b71c12efdfa2ff9d44755d5c292b8d1c78b947833d84271e359f", size = 20142, upload-time = "2025-06-14T20:52:21.582Z" }, - { url = "https://files.pythonhosted.org/packages/18/84/5f223b82894777388ef1aa09579d9c044044877a72075213741c97adc901/pyobjc_framework_modelio-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5d5e11389bde0852490b2a37896aaf9eb674b2a3586f2c572f9101cecb7bc576", size = 20172, upload-time = "2025-06-14T20:52:22.327Z" }, + { url = "https://files.pythonhosted.org/packages/35/c0/c67b806f3f2bb6264a4f7778a2aa82c7b0f50dfac40f6a60366ffc5afaf5/pyobjc_framework_modelio-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1c2c99d47a7e4956a75ce19bddbe2d8ada7d7ce9e2f56ff53fc2898367187749", size = 20180, upload-time = "2025-11-14T09:56:41.924Z" }, + { url = "https://files.pythonhosted.org/packages/f6/0e/b8331100f0d658ecb3e87e75c108e2ae8ac7c78b521fd5ad0205b60a2584/pyobjc_framework_modelio-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:68d971917c289fdddf69094c74915d2ccb746b42b150e0bdc16d8161e6164022", size = 20193, upload-time = "2025-11-14T09:56:44.296Z" }, ] [[package]] name = "pyobjc-framework-multipeerconnectivity" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/73/99/75bf6170e282d9e546b353b65af7859de8b1b27ddc431fc4afbf15423d01/pyobjc_framework_multipeerconnectivity-11.1.tar.gz", hash = "sha256:a3dacca5e6e2f1960dd2d1107d98399ff81ecf54a9852baa8ec8767dbfdbf54b", size = 26149, upload-time = "2025-06-14T20:58:01.793Z" } +sdist = { url = "https://files.pythonhosted.org/packages/87/35/0d0bb6881004cb238cfd7bf74f4b2e42601a1accdf27b2189ec61cf3a2dc/pyobjc_framework_multipeerconnectivity-12.1.tar.gz", hash = "sha256:7123f734b7174cacbe92a51a62b4645cc9033f6b462ff945b504b62e1b9e6c1c", size = 22816, upload-time = "2025-11-14T10:18:07.363Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8d/fc/a3fc2514879a39673202f7ea5e835135255c5e510d30c58a43239ec1d9e0/pyobjc_framework_multipeerconnectivity-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b3c9d4d36e0c142b4ce91033740ed5bca19fe7ec96870d90610d2942ecd3cd39", size = 11955, upload-time = "2025-06-14T20:52:28.392Z" }, - { url = "https://files.pythonhosted.org/packages/b4/fe/5c29c227f6ed81147ec6ec3e681fc680a7ffe0360f96901371435ea68570/pyobjc_framework_multipeerconnectivity-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:970031deb3dbf8da1fcb04e785d4bd2eeedae8f6677db92881df6d92b05c31d6", size = 11981, upload-time = "2025-06-14T20:52:29.406Z" }, + { url = "https://files.pythonhosted.org/packages/12/eb/e3e4ba158167696498f6491f91a8ac7e24f1ebbab5042cd34318e5d2035c/pyobjc_framework_multipeerconnectivity-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7372e505ed050286aeb83d7e158fda65ad379eae12e1526f32da0a260a8b7d06", size = 11981, upload-time = "2025-11-14T09:56:58.858Z" }, + { url = "https://files.pythonhosted.org/packages/33/8d/0646ff7db36942829f0e84be18ba44bc5cd96d6a81651f8e7dc0974821c1/pyobjc_framework_multipeerconnectivity-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1c3bd254a16debed321debf4858f9c9b7d41572ddf1058a4bacf6a5bcfedeeff", size = 12001, upload-time = "2025-11-14T09:57:01.027Z" }, ] [[package]] name = "pyobjc-framework-naturallanguage" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a2/e9/5352fbf09c5d5360405dea49fb77e53ed55acd572a94ce9a0d05f64d2b70/pyobjc_framework_naturallanguage-11.1.tar.gz", hash = "sha256:ab1fc711713aa29c32719774fc623bf2d32168aed21883970d4896e901ff4b41", size = 46120, upload-time = "2025-06-14T20:58:02.808Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/d1/c81c0cdbb198d498edc9bc5fbb17e79b796450c17bb7541adbf502f9ad65/pyobjc_framework_naturallanguage-12.1.tar.gz", hash = "sha256:cb27a1e1e5b2913d308c49fcd2fd04ab5ea87cb60cac4a576a91ebf6a50e52f6", size = 23524, upload-time = "2025-11-14T10:18:09.883Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4b/f2/de86665d48737c74756b016c0f3bf93c99ca4151b48b14e2fbe7233283f8/pyobjc_framework_naturallanguage-11.1-py2.py3-none-any.whl", hash = "sha256:65a780273d2cdd12a3fa304e9c9ad822cb71facd9281f1b35a71640c53826f7c", size = 5306, upload-time = "2025-06-14T20:52:34.024Z" }, + { url = "https://files.pythonhosted.org/packages/ec/d8/715a11111f76c80769cb267a19ecf2a4ac76152a6410debb5a4790422256/pyobjc_framework_naturallanguage-12.1-py2.py3-none-any.whl", hash = "sha256:a02ef383ec88948ca28f03ab8995523726b3bc75c49f593b5c89c218bcbce7ce", size = 5320, upload-time = "2025-11-14T09:57:10.294Z" }, ] [[package]] name = "pyobjc-framework-netfs" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/68/5d/d68cc59a1c1ea61f227ed58e7b185a444d560655320b53ced155076f5b78/pyobjc_framework_netfs-11.1.tar.gz", hash = "sha256:9c49f050c8171dc37e54d05dd12a63979c8b6b565c10f05092923a2250446f50", size = 15910, upload-time = "2025-06-14T20:58:03.811Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/68/4bf0e5b8cc0780cf7acf0aec54def58c8bcf8d733db0bd38f5a264d1af06/pyobjc_framework_netfs-12.1.tar.gz", hash = "sha256:e8d0c25f41d7d9ced1aa2483238d0a80536df21f4b588640a72e1bdb87e75c1e", size = 14799, upload-time = "2025-11-14T10:18:11.85Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/77/cc/199b06f214f8a2db26eb47e3ab7015a306597a1bca25dcb4d14ddc65bd4a/pyobjc_framework_netfs-11.1-py2.py3-none-any.whl", hash = "sha256:f202e8e0c2e73516d3eac7a43b1c66f9911cdbb37ea32750ed197d82162c994a", size = 4143, upload-time = "2025-06-14T20:52:35.428Z" }, + { url = "https://files.pythonhosted.org/packages/7e/6b/8c2f223879edd3e3f030d0a9c9ba812775519c6d0c257e3e7255785ca6e7/pyobjc_framework_netfs-12.1-py2.py3-none-any.whl", hash = "sha256:0021f8b141e693d3821524c170e9c645090eb320e80c2935ddb978a6e8b8da81", size = 4163, upload-time = "2025-11-14T09:57:11.845Z" }, ] [[package]] name = "pyobjc-framework-network" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0a/ee/5ea93e48eca341b274027e1532bd8629fd55d609cd9c39c2c3acf26158c3/pyobjc_framework_network-11.1.tar.gz", hash = "sha256:f6df7a58a1279bbc976fd7e2efe813afbbb18427df40463e6e2ee28fba07d2df", size = 124670, upload-time = "2025-06-14T20:58:05.491Z" } +sdist = { url = "https://files.pythonhosted.org/packages/38/13/a71270a1b0a9ec979e68b8ec84b0f960e908b17b51cb3cac246a74d52b6b/pyobjc_framework_network-12.1.tar.gz", hash = "sha256:dbf736ff84d1caa41224e86ff84d34b4e9eb6918ae4e373a44d3cb597648a16a", size = 56990, upload-time = "2025-11-14T10:18:16.714Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/17/e9/a54f32daa0365bf000b739fc386d4783432273a9075337aa57a3808af65d/pyobjc_framework_network-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e56691507584c09cdb50f1cd69b5f57b42fd55c396e8c34fab8c5b81b44d36ed", size = 19499, upload-time = "2025-06-14T20:52:37.158Z" }, - { url = "https://files.pythonhosted.org/packages/15/c2/3c6626fdb3616fde2c173d313d15caea22d141abcc2fbf3b615f8555abe3/pyobjc_framework_network-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8cdc9be8ec3b0ae95e5c649e4bbcdf502cffd357dacc566223be707bdd5ac271", size = 19513, upload-time = "2025-06-14T20:52:38.423Z" }, + { url = "https://files.pythonhosted.org/packages/e3/7c/4f9fc6b94be3e949b7579128cbb9171943e27d1d7841db12d66b76aeadc3/pyobjc_framework_network-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d1ad948b9b977f432bf05363381d7d85a7021246ebf9d50771b35bf8d4548d2b", size = 19593, upload-time = "2025-11-14T09:57:17.027Z" }, + { url = "https://files.pythonhosted.org/packages/9d/ef/a53f04f43e93932817f2ea71689dcc8afe3b908d631c21d11ec30c7b2e87/pyobjc_framework_network-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5e53aad64eae2933fe12d49185d66aca62fb817abf8a46f86b01e436ce1b79e4", size = 19613, upload-time = "2025-11-14T09:57:19.571Z" }, ] [[package]] name = "pyobjc-framework-networkextension" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/71/30/d1eee738d702bbca78effdaa346a2b05359ab8a96d961b7cb44838e236ca/pyobjc_framework_networkextension-11.1.tar.gz", hash = "sha256:2b74b430ca651293e5aa90a1e7571b200d0acbf42803af87306ac8a1c70b0d4b", size = 217252, upload-time = "2025-06-14T20:58:06.311Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bf/3e/ac51dbb2efa16903e6af01f3c1f5a854c558661a7a5375c3e8767ac668e8/pyobjc_framework_networkextension-12.1.tar.gz", hash = "sha256:36abc339a7f214ab6a05cb2384a9df912f247163710741e118662bd049acfa2e", size = 62796, upload-time = "2025-11-14T10:18:21.769Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/76/d7/b10aa191d37900ade78f1b7806d17ff29fa95f40ce7aeecce6f15ec94ac9/pyobjc_framework_networkextension-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:55e5ca70c81a864896b603cfcabf4c065783f64395460d16fe16db2bf0866d60", size = 14101, upload-time = "2025-06-14T20:52:44.527Z" }, - { url = "https://files.pythonhosted.org/packages/b6/26/526cd9f63e390e9c2153c41dc0982231b0b1ca88865deb538b77e1c3513d/pyobjc_framework_networkextension-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:853458aae8b43634461f6c44759750e2dc784c9aba561f9468ab14529b5a7fbe", size = 14114, upload-time = "2025-06-14T20:52:45.274Z" }, + { url = "https://files.pythonhosted.org/packages/6e/4e/aa34fc983f001cdb1afbbb4d08b42fd019fc9816caca0bf0b166db1688c1/pyobjc_framework_networkextension-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c3082c29f94ca3a05cd1f3219999ca3af9b6dece1302ccf789f347e612bb9303", size = 14368, upload-time = "2025-11-14T09:57:33.748Z" }, + { url = "https://files.pythonhosted.org/packages/f6/14/4934b10ade5ad0518001bfc25260d926816b9c7d08d85ef45e8a61fdef1b/pyobjc_framework_networkextension-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:adc9baacfc532944d67018e381c7645f66a9fa0064939a5a841476d81422cdcc", size = 14376, upload-time = "2025-11-14T09:57:36.132Z" }, ] [[package]] name = "pyobjc-framework-notificationcenter" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a8/4a/d3529b9bd7aae2c89d258ebc234673c5435e217a5136abd8c0aba37b916b/pyobjc_framework_notificationcenter-11.1.tar.gz", hash = "sha256:0b938053f2d6b1cea9db79313639d7eb9ddd5b2a5436a346be0887e75101e717", size = 23389, upload-time = "2025-06-14T20:58:07.136Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c6/12/ae0fe82fb1e02365c9fe9531c9de46322f7af09e3659882212c6bf24d75e/pyobjc_framework_notificationcenter-12.1.tar.gz", hash = "sha256:2d09f5ab9dc39770bae4fa0c7cfe961e6c440c8fc465191d403633dccc941094", size = 21282, upload-time = "2025-11-14T10:18:24.51Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ea/ed/3beb825e2b80de45b90e7cd510ad52890ac4a5a4de88cd9a5291235519fb/pyobjc_framework_notificationcenter-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3d44413818e7fa3662f784cdcf0730c86676dd7333b7d24a7da13d4ffcde491b", size = 9859, upload-time = "2025-06-14T20:52:51.744Z" }, - { url = "https://files.pythonhosted.org/packages/6d/92/cd00fe5e54a191fb77611fe728a8c8a0a6edb229857d32f27806582406ca/pyobjc_framework_notificationcenter-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:65fc67374a471890245c7a1d60cf67dcf160075a9c048a5d89608a8290f33b03", size = 9880, upload-time = "2025-06-14T20:52:52.406Z" }, + { url = "https://files.pythonhosted.org/packages/47/aa/03526fc0cc285c0f8cf31c74ce3a7a464011cc8fa82a35a1637d9878c788/pyobjc_framework_notificationcenter-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:84e254f2a56ff5372793dea938a2b2683dd0bc40c5107fede76f9c2c1f6641a2", size = 9871, upload-time = "2025-11-14T09:57:49.208Z" }, + { url = "https://files.pythonhosted.org/packages/d8/05/3168637dd425257df5693c2ceafecf92d2e6833c0aaa6594d894a528d797/pyobjc_framework_notificationcenter-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:82a735bd63f315f0a56abd206373917b7d09a0ae35fd99f1639a0fac4c525c0a", size = 9895, upload-time = "2025-11-14T09:57:51.151Z" }, ] [[package]] name = "pyobjc-framework-opendirectory" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9d/02/ac56c56fdfbc24cdf87f4a624f81bbe2e371d0983529b211a18c6170e932/pyobjc_framework_opendirectory-11.1.tar.gz", hash = "sha256:319ac3424ed0350be458b78148914468a8fc13a069d62e7869e3079108e4f118", size = 188880, upload-time = "2025-06-14T20:58:08.003Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5b/11/bc2f71d3077b3bd078dccad5c0c5c57ec807fefe3d90c97b97dd0ed3d04b/pyobjc_framework_opendirectory-12.1.tar.gz", hash = "sha256:2c63ce5dd179828ef2d8f9e3961da3bfa971a57db07a6c34eedc296548a928bb", size = 61049, upload-time = "2025-11-14T10:18:29.336Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/06/56/f0f5b7222d5030192c44010ab7260681e349efea2f1b1b9f116ba1951d6d/pyobjc_framework_opendirectory-11.1-py2.py3-none-any.whl", hash = "sha256:bb4219b0d98dff4a952c50a79b1855ce74e1defd0d241f3013def5b09256fd7b", size = 11829, upload-time = "2025-06-14T20:52:56.715Z" }, + { url = "https://files.pythonhosted.org/packages/d6/e7/3c2dece9c5b28af28a44d72a27b35ea5ffac31fed7cbd8d696ea75dc4a81/pyobjc_framework_opendirectory-12.1-py2.py3-none-any.whl", hash = "sha256:b5b5a5cf3cc2fb25147b16b79f046b90e3982bf3ded1b210a993d8cfdba737c4", size = 11845, upload-time = "2025-11-14T09:58:00.175Z" }, ] [[package]] name = "pyobjc-framework-osakit" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/56/22/f9cdfb5de255b335f99e61a3284be7cb1552a43ed1dfe7c22cc868c23819/pyobjc_framework_osakit-11.1.tar.gz", hash = "sha256:920987da78b67578367c315d208f87e8fab01dd35825d72242909f29fb43c820", size = 22290, upload-time = "2025-06-14T20:58:09.103Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cb/b9/bf52c555c75a83aa45782122432fa06066bb76469047f13d06fb31e585c4/pyobjc_framework_osakit-12.1.tar.gz", hash = "sha256:36ea6acf03483dc1e4344a0cce7250a9656f44277d12bc265fa86d4cbde01f23", size = 17102, upload-time = "2025-11-14T10:18:31.354Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/14/65/c6531ce0792d5035d87f054b0ccf22e453328fda2e68e11a7f70486da23a/pyobjc_framework_osakit-11.1-py2.py3-none-any.whl", hash = "sha256:1b0c0cc537ffb8a8365ef9a8b46f717a7cc2906414b6a3983777a6c0e4d53d5a", size = 4143, upload-time = "2025-06-14T20:52:57.555Z" }, + { url = "https://files.pythonhosted.org/packages/99/10/30a15d7b23e6fcfa63d41ca4c7356c39ff81300249de89c3ff28216a9790/pyobjc_framework_osakit-12.1-py2.py3-none-any.whl", hash = "sha256:c49165336856fd75113d2e264a98c6deb235f1bd033eae48f661d4d832d85e6b", size = 4162, upload-time = "2025-11-14T09:58:01.953Z" }, ] [[package]] name = "pyobjc-framework-oslog" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, @@ -3593,583 +3648,583 @@ dependencies = [ { name = "pyobjc-framework-coremedia", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/79/93/3feb7f6150b50165524750a424f5434448392123420cb4673db766c3f54a/pyobjc_framework_oslog-11.1.tar.gz", hash = "sha256:b2af409617e6b68fa1f1467c5a5679ebf59afd0cdc4b4528e1616059959a7979", size = 24689, upload-time = "2025-06-14T20:58:09.739Z" } +sdist = { url = "https://files.pythonhosted.org/packages/12/42/805c9b4ac6ad25deb4215989d8fc41533d01e07ffd23f31b65620bade546/pyobjc_framework_oslog-12.1.tar.gz", hash = "sha256:d0ec6f4e3d1689d5e4341bc1130c6f24cb4ad619939f6c14d11a7e80c0ac4553", size = 21193, upload-time = "2025-11-14T10:18:33.645Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/66/7a/2db26fc24e16c84312a0de432bab16ca586223fd6c5ba08e49c192ae95f6/pyobjc_framework_oslog-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5dab25ef1cde4237cd2957c1f61c2888968e924304f7b9d9699eceeb330e9817", size = 7793, upload-time = "2025-06-14T20:52:59.132Z" }, - { url = "https://files.pythonhosted.org/packages/40/da/fd3bd62899cd679743056aa2c28bc821c2688682a17ddde1a08d6d9d67fc/pyobjc_framework_oslog-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7ae29c31ce51c476d3a37ca303465dd8bdfa98df2f6f951cf14c497e984a1ba9", size = 7799, upload-time = "2025-06-14T20:52:59.935Z" }, + { url = "https://files.pythonhosted.org/packages/d9/d5/8d37c2e733bd8a9a16546ceca07809d14401a059f8433cdc13579cd6a41a/pyobjc_framework_oslog-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8dd03386331fbb6b39df8941d99071da0bfeda7d10f6434d1daa1c69f0e7bb14", size = 7802, upload-time = "2025-11-14T09:58:05.619Z" }, + { url = "https://files.pythonhosted.org/packages/ee/60/0b742347d484068e9d6867cd95dedd1810c790b6aca45f6ef1d0f089f1f5/pyobjc_framework_oslog-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:072a41d36fcf780a070f13ac2569f8bafbb5ae4792fab4136b1a4d602dd9f5b4", size = 7813, upload-time = "2025-11-14T09:58:07.768Z" }, ] [[package]] name = "pyobjc-framework-passkit" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5c/05/063db500e7df70e39cbb5518a5a03c2acc06a1ca90b057061daea00129f3/pyobjc_framework_passkit-11.1.tar.gz", hash = "sha256:d2408b58960fca66607b483353c1ffbd751ef0bef394a1853ec414a34029566f", size = 144859, upload-time = "2025-06-14T20:58:10.761Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/d4/2afb59fb0f99eb2f03888850887e536f1ef64b303fd756283679471a5189/pyobjc_framework_passkit-12.1.tar.gz", hash = "sha256:d8c27c352e86a3549bf696504e6b25af5f2134b173d9dd60d66c6d3da53bb078", size = 53835, upload-time = "2025-11-14T10:18:37.906Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/80/18/343eb846e62704fbd64e178e0cbf75b121955c1973bf51ddd0871a42910a/pyobjc_framework_passkit-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:67b7b1ee9454919c073c2cba7bdba444a766a4e1dd15a5e906f4fa0c61525347", size = 13949, upload-time = "2025-06-14T20:53:04.98Z" }, - { url = "https://files.pythonhosted.org/packages/9d/ba/9e52213e0c0100079e4ef397cf4fd5ba8939fa4de19339755d1a373407a8/pyobjc_framework_passkit-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:779eaea4e1931cfda4c8701e1111307b14bf9067b359a319fc992b6848a86932", size = 13959, upload-time = "2025-06-14T20:53:05.694Z" }, + { url = "https://files.pythonhosted.org/packages/25/e6/dabd6b99bdadc50aa0306495d8d0afe4b9b3475c2bafdad182721401a724/pyobjc_framework_passkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cb5c8f0fdc46db6b91c51ee1f41a2b81e9a482c96a0c91c096dcb78a012b740a", size = 14087, upload-time = "2025-11-14T09:58:18.991Z" }, + { url = "https://files.pythonhosted.org/packages/d8/dc/9cb27e8b7b00649af5e802815ffa8928bd8a619f2984a1bea7dabd28f741/pyobjc_framework_passkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7e95a484ec529dbf1d44f5f7f1406502a77bda733511e117856e3dca9fa29c5c", size = 14102, upload-time = "2025-11-14T09:58:20.903Z" }, ] [[package]] name = "pyobjc-framework-pencilkit" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/75/d0/bbbe9dadcfc37e33a63d43b381a8d9a64eca27559df38efb74d524fa6260/pyobjc_framework_pencilkit-11.1.tar.gz", hash = "sha256:9c173e0fe70179feadc3558de113a8baad61b584fe70789b263af202bfa4c6be", size = 22570, upload-time = "2025-06-14T20:58:11.538Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/43/859068016bcbe7d80597d5c579de0b84b0da62c5c55cdf9cc940e9f9c0f8/pyobjc_framework_pencilkit-12.1.tar.gz", hash = "sha256:d404982d1f7a474369f3e7fea3fbd6290326143fa4138d64b6753005a6263dc4", size = 17664, upload-time = "2025-11-14T10:18:40.045Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a3/f6/59ffc3f26ea9cfda4d40409f9afc2a38e5c0c6a68a3a8c9202e8b98b03b1/pyobjc_framework_pencilkit-11.1-py2.py3-none-any.whl", hash = "sha256:b7824907bbcf28812f588dda730e78f662313baf40befd485c6f2fcb49018019", size = 4026, upload-time = "2025-06-14T20:53:10.449Z" }, + { url = "https://files.pythonhosted.org/packages/e8/26/daf47dcfced8f7326218dced5c68ed2f3b522ec113329218ce1305809535/pyobjc_framework_pencilkit-12.1-py2.py3-none-any.whl", hash = "sha256:33b88e5ed15724a12fd8bf27a68614b654ff739d227e81161298bc0d03acca4f", size = 4206, upload-time = "2025-11-14T09:58:30.814Z" }, ] [[package]] name = "pyobjc-framework-phase" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-avfoundation", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c6/d2/e9384b5b3fbcc79e8176cb39fcdd48b77f60cd1cb64f9ee4353762b037dc/pyobjc_framework_phase-11.1.tar.gz", hash = "sha256:a940d81ac5c393ae3da94144cf40af33932e0a9731244e2cfd5c9c8eb851e3fc", size = 58986, upload-time = "2025-06-14T20:58:12.196Z" } +sdist = { url = "https://files.pythonhosted.org/packages/96/51/3b25eaf7ca85f38ceef892fdf066b7faa0fec716f35ea928c6ffec6ae311/pyobjc_framework_phase-12.1.tar.gz", hash = "sha256:3a69005c572f6fd777276a835115eb8359a33673d4a87e754209f99583534475", size = 32730, upload-time = "2025-11-14T10:18:43.102Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f5/9e/55782f02b3bfb58f030b062176e8b0dba5f8fbd6e50d27a687f559c4179d/pyobjc_framework_phase-11.1-py2.py3-none-any.whl", hash = "sha256:cfa61f9c6c004161913946501538258aed48c448b886adbf9ed035957d93fa15", size = 6822, upload-time = "2025-06-14T20:53:11.618Z" }, + { url = "https://files.pythonhosted.org/packages/ae/9f/1ae45db731e8d6dd3e0b408c3accd0cf3236849e671f95c7c8cf95687240/pyobjc_framework_phase-12.1-py2.py3-none-any.whl", hash = "sha256:99a1c1efc6644f5312cce3693117d4e4482538f65ad08fe59e41e2579b67ab17", size = 6902, upload-time = "2025-11-14T09:58:32.436Z" }, ] [[package]] name = "pyobjc-framework-photos" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/78/b0/576652ecd05c26026ab4e75e0d81466edd570d060ce7df3d6bd812eb90d0/pyobjc_framework_photos-11.1.tar.gz", hash = "sha256:c8c3b25b14a2305047f72c7c081ff3655b3d051f7ed531476c03246798f8156d", size = 92569, upload-time = "2025-06-14T20:58:12.939Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b8/53/f8a3dc7f711034d2283e289cd966fb7486028ea132a24260290ff32d3525/pyobjc_framework_photos-12.1.tar.gz", hash = "sha256:adb68aaa29e186832d3c36a0b60b0592a834e24c5263e9d78c956b2b77dce563", size = 47034, upload-time = "2025-11-14T10:18:47.27Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/df/25/ec3b0234d20948816791399e580f6dd83c0d50a24219c954708f755742c4/pyobjc_framework_photos-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:959dfc82f20513366b85cd37d8541bb0a6ab4f3bfa2f8094e9758a5245032d67", size = 12198, upload-time = "2025-06-14T20:53:13.563Z" }, - { url = "https://files.pythonhosted.org/packages/fa/24/2400e6b738d3ed622c61a7cc6604eec769f398071a1eb6a16dfdf3a9ceea/pyobjc_framework_photos-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8dbfffd29cfa63a8396ede0030785c15a5bc36065d3dd98fc6176a59e7abb3d3", size = 12224, upload-time = "2025-06-14T20:53:14.793Z" }, + { url = "https://files.pythonhosted.org/packages/e4/e0/8824f7cb167934a8aa1c088b7e6f1b5a9342b14694e76eda95fc736282b2/pyobjc_framework_photos-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f28db92602daac9d760067449fc9bf940594536e65ad542aec47d52b56f51959", size = 12319, upload-time = "2025-11-14T09:58:36.324Z" }, + { url = "https://files.pythonhosted.org/packages/13/38/e6f25aec46a1a9d0a310795606cc43f9823d41c3e152114b814b597835a8/pyobjc_framework_photos-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eda8a584a851506a1ebbb2ee8de2cb1ed9e3431e6a642ef6a9543e32117d17b9", size = 12358, upload-time = "2025-11-14T09:58:38.131Z" }, ] [[package]] name = "pyobjc-framework-photosui" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/20/bb/e6de720efde2e9718677c95c6ae3f97047be437cda7a0f050cd1d6d2a434/pyobjc_framework_photosui-11.1.tar.gz", hash = "sha256:1c7ffab4860ce3e2b50feeed4f1d84488a9e38546db0bec09484d8d141c650df", size = 48443, upload-time = "2025-06-14T20:58:13.626Z" } +sdist = { url = "https://files.pythonhosted.org/packages/40/a5/14c538828ed1a420e047388aedc4a2d7d9292030d81bf6b1ced2ec27b6e9/pyobjc_framework_photosui-12.1.tar.gz", hash = "sha256:9141234bb9d17687f1e8b66303158eccdd45132341fbe5e892174910035f029a", size = 29886, upload-time = "2025-11-14T10:18:50.238Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/af/c1/3d67c2af53fe91feb6f64dbc501bbcfd5d325b7f0f0ffffd5d033334cb03/pyobjc_framework_photosui-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d93722aeb8c134569035fd7e6632d0247e1bcb18c3cc4e0a288664218f241b85", size = 11667, upload-time = "2025-06-14T20:53:20.464Z" }, - { url = "https://files.pythonhosted.org/packages/f8/c1/a5c84c1695e7a066743d63d10b219d94f3c07d706871682e42f7db389f5c/pyobjc_framework_photosui-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b2f278f569dfd596a32468351411518a651d12cb91e60620094e852c525a5f10", size = 11682, upload-time = "2025-06-14T20:53:21.162Z" }, + { url = "https://files.pythonhosted.org/packages/64/6c/d678767bbeafa932b91c88bc8bb3a586a1b404b5564b0dc791702eb376c3/pyobjc_framework_photosui-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:02ca941187b2a2dcbbd4964d7b2a05de869653ed8484dc059a51cc70f520cd07", size = 11688, upload-time = "2025-11-14T09:58:51.84Z" }, + { url = "https://files.pythonhosted.org/packages/16/a2/b5afca8039b1a659a2a979bb1bdbdddfdf9b1d2724a2cc4633dca2573d5f/pyobjc_framework_photosui-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:713e3bb25feb5ea891e67260c2c0769cab44a7f11b252023bfcf9f8c29dd1206", size = 11714, upload-time = "2025-11-14T09:58:53.674Z" }, ] [[package]] name = "pyobjc-framework-preferencepanes" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/34/ac/9324602daf9916308ebf1935b8a4b91c93b9ae993dcd0da731c0619c2836/pyobjc_framework_preferencepanes-11.1.tar.gz", hash = "sha256:6e4a55195ec9fc921e0eaad6b3038d0ab91f0bb2f39206aa6fccd24b14a0f1d8", size = 26212, upload-time = "2025-06-14T20:58:14.361Z" } +sdist = { url = "https://files.pythonhosted.org/packages/90/bc/e87df041d4f7f6b7721bf7996fa02aa0255939fb0fac0ecb294229765f92/pyobjc_framework_preferencepanes-12.1.tar.gz", hash = "sha256:b2a02f9049f136bdeca7642b3307637b190850e5853b74b5c372bc7d88ef9744", size = 24543, upload-time = "2025-11-14T10:18:53.259Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a1/51/75c7e32272241f706ce8168e04a32be02c4b0c244358330f730fc85695c3/pyobjc_framework_preferencepanes-11.1-py2.py3-none-any.whl", hash = "sha256:6ee5f5a7eb294e03ea3bac522ac4b69e6dc83ceceff627a0a2d289afe1e01ad9", size = 4786, upload-time = "2025-06-14T20:53:25.603Z" }, + { url = "https://files.pythonhosted.org/packages/36/7b/8ceec1ab0446224d685e243e2770c5a5c92285bcab0b9324dbe7a893ae5a/pyobjc_framework_preferencepanes-12.1-py2.py3-none-any.whl", hash = "sha256:1b3af9db9e0cfed8db28c260b2cf9a22c15fda5f0ff4c26157b17f99a0e29bbf", size = 4797, upload-time = "2025-11-14T09:59:03.998Z" }, ] [[package]] name = "pyobjc-framework-pushkit" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9f/f0/92d0eb26bf8af8ebf6b5b88df77e70b807de11f01af0162e0a429fcfb892/pyobjc_framework_pushkit-11.1.tar.gz", hash = "sha256:540769a4aadc3c9f08beca8496fe305372501eb28fdbca078db904a07b8e10f4", size = 21362, upload-time = "2025-06-14T20:58:15.642Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/45/de756b62709add6d0615f86e48291ee2bee40223e7dde7bbe68a952593f0/pyobjc_framework_pushkit-12.1.tar.gz", hash = "sha256:829a2fc8f4780e75fc2a41217290ee0ff92d4ade43c42def4d7e5af436d8ae82", size = 19465, upload-time = "2025-11-14T10:18:57.727Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d9/dc/415d6d7e3ed04d8b2f8dc6d458e7c6db3f503737b092d71b4856bf1607f7/pyobjc_framework_pushkit-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5e2f08b667035df6b11a0a26f038610df1eebbedf9f3f111c241b5afaaf7c5fd", size = 8149, upload-time = "2025-06-14T20:53:28.096Z" }, - { url = "https://files.pythonhosted.org/packages/31/65/260014c5d13c54bd359221b0a890cbffdb99eecff3703f253cf648e45036/pyobjc_framework_pushkit-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:21993b7e9127b05575a954faa68e85301c6a4c04e34e38aff9050f67a05c562a", size = 8174, upload-time = "2025-06-14T20:53:28.805Z" }, + { url = "https://files.pythonhosted.org/packages/a1/b2/d92045e0d4399feda83ee56a9fd685b5c5c175f7ac8423e2cd9b3d52a9da/pyobjc_framework_pushkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:03f41be8b27d06302ea487a6b250aaf811917a0e7d648cd4043fac759d027210", size = 8158, upload-time = "2025-11-14T09:59:09.593Z" }, + { url = "https://files.pythonhosted.org/packages/b9/01/74cf1dd0764c590de05dc1e87d168031e424f834721940b7bb02c67fe821/pyobjc_framework_pushkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7bdf472a55ac65154e03f54ae0bcad64c4cf45e9b1acba62f15107f2bc994d69", size = 8177, upload-time = "2025-11-14T09:59:11.155Z" }, ] [[package]] name = "pyobjc-framework-quartz" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c7/ac/6308fec6c9ffeda9942fef72724f4094c6df4933560f512e63eac37ebd30/pyobjc_framework_quartz-11.1.tar.gz", hash = "sha256:a57f35ccfc22ad48c87c5932818e583777ff7276605fef6afad0ac0741169f75", size = 3953275, upload-time = "2025-06-14T20:58:17.924Z" } +sdist = { url = "https://files.pythonhosted.org/packages/94/18/cc59f3d4355c9456fc945eae7fe8797003c4da99212dd531ad1b0de8a0c6/pyobjc_framework_quartz-12.1.tar.gz", hash = "sha256:27f782f3513ac88ec9b6c82d9767eef95a5cf4175ce88a1e5a65875fee799608", size = 3159099, upload-time = "2025-11-14T10:21:24.31Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/77/cb/38172fdb350b3f47e18d87c5760e50f4efbb4da6308182b5e1310ff0cde4/pyobjc_framework_quartz-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2d501fe95ef15d8acf587cb7dc4ab4be3c5a84e2252017da8dbb7df1bbe7a72a", size = 215565, upload-time = "2025-06-14T20:53:35.262Z" }, - { url = "https://files.pythonhosted.org/packages/9b/37/ee6e0bdd31b3b277fec00e5ee84d30eb1b5b8b0e025095e24ddc561697d0/pyobjc_framework_quartz-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9ac806067541917d6119b98d90390a6944e7d9bd737f5c0a79884202327c9204", size = 216410, upload-time = "2025-06-14T20:53:36.346Z" }, + { url = "https://files.pythonhosted.org/packages/b7/ef/dcd22b743e38b3c430fce4788176c2c5afa8bfb01085b8143b02d1e75201/pyobjc_framework_quartz-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:19f99ac49a0b15dd892e155644fe80242d741411a9ed9c119b18b7466048625a", size = 217795, upload-time = "2025-11-14T09:59:46.922Z" }, + { url = "https://files.pythonhosted.org/packages/e9/9b/780f057e5962f690f23fdff1083a4cfda5a96d5b4d3bb49505cac4f624f2/pyobjc_framework_quartz-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7730cdce46c7e985535b5a42c31381af4aa6556e5642dc55b5e6597595e57a16", size = 218798, upload-time = "2025-11-14T10:00:01.236Z" }, ] [[package]] name = "pyobjc-framework-quicklookthumbnailing" -version = "11.1" +version = "12.1" 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'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/aa/98/6e87f360c2dfc870ae7870b8a25fdea8ddf1d62092c755686cebe7ec1a07/pyobjc_framework_quicklookthumbnailing-11.1.tar.gz", hash = "sha256:1614dc108c1d45bbf899ea84b8691288a5b1d25f2d6f0c57dfffa962b7a478c3", size = 16527, upload-time = "2025-06-14T20:58:20.811Z" } +sdist = { url = "https://files.pythonhosted.org/packages/97/1a/b90539500e9a27c2049c388d85a824fc0704009b11e33b05009f52a6dc67/pyobjc_framework_quicklookthumbnailing-12.1.tar.gz", hash = "sha256:4f7e09e873e9bda236dce6e2f238cab571baeb75eca2e0bc0961d5fcd85f3c8f", size = 14790, upload-time = "2025-11-14T10:21:26.442Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/65/4a/ddc35bdcd44278f22df2154a52025915dba6c80d94e458d92e9e7430d1e4/pyobjc_framework_quicklookthumbnailing-11.1-py2.py3-none-any.whl", hash = "sha256:4d1863c6c83c2a199c1dbe704b4f8b71287168f4090ed218d37dc59277f0d9c9", size = 4219, upload-time = "2025-06-14T20:53:43.198Z" }, + { url = "https://files.pythonhosted.org/packages/1e/22/7bd07b5b44bf8540514a9f24bc46da68812c1fd6c63bb2d3496e5ea44bf0/pyobjc_framework_quicklookthumbnailing-12.1-py2.py3-none-any.whl", hash = "sha256:5efe50b0318188b3a4147681788b47fce64709f6fe0e1b5d020e408ef40ab08e", size = 4234, upload-time = "2025-11-14T10:01:02.209Z" }, ] [[package]] name = "pyobjc-framework-replaykit" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c8/4f/014e95f0fd6842d7fcc3d443feb6ee65ac69d06c66ffa9327fc33ceb7c27/pyobjc_framework_replaykit-11.1.tar.gz", hash = "sha256:6919baa123a6d8aad769769fcff87369e13ee7bae11b955a8185a406a651061b", size = 26132, upload-time = "2025-06-14T20:58:21.853Z" } +sdist = { url = "https://files.pythonhosted.org/packages/35/f8/b92af879734d91c1726227e7a03b9e68ab8d9d2bb1716d1a5c29254087f2/pyobjc_framework_replaykit-12.1.tar.gz", hash = "sha256:95801fd35c329d7302b2541f2754e6574bf36547ab869fbbf41e408dfa07268a", size = 23312, upload-time = "2025-11-14T10:21:29.18Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/72/97/2b4fbd52c6727977c0fdbde2b4a15226a9beb836248c289781e4129394e4/pyobjc_framework_replaykit-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4d88c3867349865d8a3a06ea064f15aed7e5be20d22882ac8a647d9b6959594e", size = 10066, upload-time = "2025-06-14T20:53:45.555Z" }, - { url = "https://files.pythonhosted.org/packages/b9/73/846cebb36fc279df18f10dc3a27cba8fe2e47e95350a3651147e4d454719/pyobjc_framework_replaykit-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:22c6d09be9a6e758426d723a6c3658ad6bbb66f97ba9a1909bfcf29a91d99921", size = 10087, upload-time = "2025-06-14T20:53:46.242Z" }, + { url = "https://files.pythonhosted.org/packages/10/b1/fab264c6a82a78cd050a773c61dec397c5df7e7969eba3c57e17c8964ea3/pyobjc_framework_replaykit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3a2f9da6939d7695fa40de9c560c20948d31b0cc2f892fdd611fc566a6b83606", size = 10090, upload-time = "2025-11-14T10:01:06.321Z" }, + { url = "https://files.pythonhosted.org/packages/6b/fc/c68d2111b2655148d88574959d3d8b21d3a003573013301d4d2a7254c1af/pyobjc_framework_replaykit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b0528c2a6188440fdc2017f0924c0a0f15d0a2f6aa295f1d1c2d6b3894c22f1d", size = 10120, upload-time = "2025-11-14T10:01:08.397Z" }, ] [[package]] name = "pyobjc-framework-safariservices" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1a/fc/c47d2abf3c1de6db21d685cace76a0931d594aa369e3d090260295273f6e/pyobjc_framework_safariservices-11.1.tar.gz", hash = "sha256:39a17df1a8e1c339457f3acbff0dc0eae4681d158f9d783a11995cf484aa9cd0", size = 34905, upload-time = "2025-06-14T20:58:22.492Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3e/4b/8f896bafbdbfa180a5ba1e21a6f5dc63150c09cba69d85f68708e02866ae/pyobjc_framework_safariservices-12.1.tar.gz", hash = "sha256:6a56f71c1e692bca1f48fe7c40e4c5a41e148b4e3c6cfb185fd80a4d4a951897", size = 25165, upload-time = "2025-11-14T10:21:32.041Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c9/aa/0c9f3456a57dbee711210a0ac3fe58aff9bf881ab7c65727b885193eb8af/pyobjc_framework_safariservices-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a441a2e99f7d6475bea00c3d53de924143b8f90052be226aee16f1f6d9cfdc8c", size = 7262, upload-time = "2025-06-14T20:53:52.057Z" }, - { url = "https://files.pythonhosted.org/packages/d7/13/9636e9d3dc362daaaa025b2aa4e28606a1e197dfc6506d3a246be8315f8a/pyobjc_framework_safariservices-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c92eb9e35f98368ea1bfaa8cdd41138ca8b004ea5a85833390a44e5626ca5061", size = 7275, upload-time = "2025-06-14T20:53:53.075Z" }, + { url = "https://files.pythonhosted.org/packages/f1/bb/da1059bfad021c417e090058c0a155419b735b4891a7eedc03177b376012/pyobjc_framework_safariservices-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ae709cf7a72ac7b95d2f131349f852d5d7a1729a8d760ea3308883f8269a4c37", size = 7281, upload-time = "2025-11-14T10:01:19.294Z" }, + { url = "https://files.pythonhosted.org/packages/67/3a/8c525562fd782c88bc44e8c07fc2c073919f98dead08fffd50f280ef1afa/pyobjc_framework_safariservices-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b475abc82504fc1c0801096a639562d6a6d37370193e8e4a406de9199a7cea13", size = 7281, upload-time = "2025-11-14T10:01:21.238Z" }, ] [[package]] name = "pyobjc-framework-safetykit" -version = "11.1" +version = "12.1" 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'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/28/cc/f6aa5d6f45179bd084416511be4e5b0dd0752cb76daa93869e6edb806096/pyobjc_framework_safetykit-11.1.tar.gz", hash = "sha256:c6b44e0cf69e27584ac3ef3d8b771d19a7c2ccd9c6de4138d091358e036322d4", size = 21240, upload-time = "2025-06-14T20:58:23.132Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f4/bf/ad6bf60ceb61614c9c9f5758190971e9b90c45b1c7a244e45db64138b6c2/pyobjc_framework_safetykit-12.1.tar.gz", hash = "sha256:0cd4850659fb9b5632fd8ad21f2de6863e8303ff0d51c5cc9c0034aac5db08d8", size = 20086, upload-time = "2025-11-14T10:21:34.212Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a3/ad/1e9c661510cc4cd96f2beffc7ba39af36064c742e265303c689e85aaa0ad/pyobjc_framework_safetykit-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3333e8e53a1e8c8133936684813a2254e5d1b4fe313333a3d0273e31b9158cf7", size = 8513, upload-time = "2025-06-14T20:53:58.413Z" }, - { url = "https://files.pythonhosted.org/packages/9c/8f/6f4c833e31526a81faef9bf19695b332ba8d2fa53d92640abd6fb3ac1d78/pyobjc_framework_safetykit-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b76fccdb970d3d751a540c47712e9110afac9abea952cb9b7bc0d5867db896e3", size = 8523, upload-time = "2025-06-14T20:53:59.443Z" }, + { url = "https://files.pythonhosted.org/packages/94/68/77f17fba082de7c65176e0d74aacbce5c9c9066d6d6edcde5a537c8c140a/pyobjc_framework_safetykit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6c63bcd5d571bba149e28c49c8db06073e54e073b08589e94b850b39a43e52b0", size = 8539, upload-time = "2025-11-14T10:01:31.201Z" }, + { url = "https://files.pythonhosted.org/packages/b7/0c/08a20fb7516405186c0fe7299530edd4aa22c24f73290198312447f26c8c/pyobjc_framework_safetykit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e4977f7069a23252053d1a42b1a053aefc19b85c960a5214b05daf3c037a6f16", size = 8550, upload-time = "2025-11-14T10:01:32.885Z" }, ] [[package]] name = "pyobjc-framework-scenekit" -version = "11.1" +version = "12.1" 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'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/64/cf/2d89777120d2812e7ee53c703bf6fc8968606c29ddc1351bc63f0a2a5692/pyobjc_framework_scenekit-11.1.tar.gz", hash = "sha256:82941f1e5040114d6e2c9fd35507244e102ef561c637686091b71a7ad0f31306", size = 214118, upload-time = "2025-06-14T20:58:24.003Z" } +sdist = { url = "https://files.pythonhosted.org/packages/94/8c/1f4005cf0cb68f84dd98b93bbc0974ee7851bb33d976791c85e042dc2278/pyobjc_framework_scenekit-12.1.tar.gz", hash = "sha256:1bd5b866f31fd829f26feac52e807ed942254fd248115c7c742cfad41d949426", size = 101212, upload-time = "2025-11-14T10:21:41.265Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/51/46/d011b5a88e45d78265f5df144759ff57e50d361d44c9adb68c2fb58b276d/pyobjc_framework_scenekit-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3e777dacb563946ad0c2351e6cfe3f16b8587a65772ec0654e2be9f75764d234", size = 33490, upload-time = "2025-06-14T20:54:04.845Z" }, - { url = "https://files.pythonhosted.org/packages/e0/f9/bdcd8a4bc6c387ef07f3e2190cea6a03d4f7ed761784f492b01323e8d900/pyobjc_framework_scenekit-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c803d95b30c4ce49f46ff7174806f5eb84e4c3a152f8f580c5da0313c5c67041", size = 33558, upload-time = "2025-06-14T20:54:05.59Z" }, + { url = "https://files.pythonhosted.org/packages/a0/7f/eda261013dc41cc70f3157d1a750712dc29b64fc05be84232006b5cd57e5/pyobjc_framework_scenekit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:01bf1336a7a8bdc96fabde8f3506aa7a7d1905e20a5c46030a57daf0ce2cbd16", size = 33542, upload-time = "2025-11-14T10:01:47.613Z" }, + { url = "https://files.pythonhosted.org/packages/d2/f1/4986bd96e0ba0f60bff482a6b135b9d6db65d56578d535751f18f88190f0/pyobjc_framework_scenekit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:40aea10098893f0b06191f1e79d7b25e12e36a9265549d324238bdb25c7e6df0", size = 33597, upload-time = "2025-11-14T10:01:51.297Z" }, ] [[package]] name = "pyobjc-framework-screencapturekit" -version = "11.1" +version = "12.1" 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'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/32/a5/9bd1f1ad1773a1304ccde934ff39e0f0a0b0034441bf89166aea649606de/pyobjc_framework_screencapturekit-11.1.tar.gz", hash = "sha256:11443781a30ed446f2d892c9e6642ca4897eb45f1a1411136ca584997fa739e0", size = 53548, upload-time = "2025-06-14T20:58:24.837Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/7f/73458db1361d2cb408f43821a1e3819318a0f81885f833d78d93bdc698e0/pyobjc_framework_screencapturekit-12.1.tar.gz", hash = "sha256:50992c6128b35ab45d9e336f0993ddd112f58b8c8c8f0892a9cb42d61bd1f4c9", size = 32573, upload-time = "2025-11-14T10:21:44.497Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/e0/fd1957e962c4a1624171dbbda4e425615848a7bcc9b45a524018dc449874/pyobjc_framework_screencapturekit-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7203108d28d7373501c455cd4a8bbcd2eb7849906dbc7859ac17a350b141553c", size = 11280, upload-time = "2025-06-14T20:54:11.699Z" }, - { url = "https://files.pythonhosted.org/packages/98/37/840f306dcf01dd2bd092ae8dcf371a3bad3a0f88f0780d0840f899a8c047/pyobjc_framework_screencapturekit-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:641fa7834f54558859209e174c83551d5fa239ca6943ace52665f7d45e562ff2", size = 11308, upload-time = "2025-06-14T20:54:12.382Z" }, + { url = "https://files.pythonhosted.org/packages/79/92/fe66408f4bd74f6b6da75977d534a7091efe988301d13da4f009bf54ab71/pyobjc_framework_screencapturekit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ae412d397eedf189e763defe3497fcb8dffa5e0b54f62390cb33bf9b1cfb864a", size = 11473, upload-time = "2025-11-14T10:02:09.177Z" }, + { url = "https://files.pythonhosted.org/packages/05/a8/533acdbf26e0a908ff640d3a445481f3c948682ca887be6711b5fcf82682/pyobjc_framework_screencapturekit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:27df138ce2dfa9d4aae5106d4877e9ed694b5a174643c058f1c48678ffc7001a", size = 11504, upload-time = "2025-11-14T10:02:11.36Z" }, ] [[package]] name = "pyobjc-framework-screensaver" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7c/f6/f2d48583b29fc67b64aa1f415fd51faf003d045cdb1f3acab039b9a3f59f/pyobjc_framework_screensaver-11.1.tar.gz", hash = "sha256:d5fbc9dc076cc574ead183d521840b56be0c160415e43cb8e01cfddd6d6372c2", size = 24302, upload-time = "2025-06-14T20:58:25.52Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/99/7cfbce880cea61253a44eed594dce66c2b2fbf29e37eaedcd40cffa949e9/pyobjc_framework_screensaver-12.1.tar.gz", hash = "sha256:c4ca111317c5a3883b7eace0a9e7dd72bc6ffaa2ca954bdec918c3ab7c65c96f", size = 22229, upload-time = "2025-11-14T10:21:47.299Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f0/8c/2236e5796f329a92ce7664036da91e91d63d86217972dc2939261ce88dde/pyobjc_framework_screensaver-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8b959761fddf06d9fb3fed6cd0cea6009d60473317e11490f66dcf0444011d5f", size = 8466, upload-time = "2025-06-14T20:54:18.329Z" }, - { url = "https://files.pythonhosted.org/packages/76/f9/4ae982c7a1387b64954130b72187e140329b73c647acb4d6b6eb3c033d8d/pyobjc_framework_screensaver-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f2d22293cf9d715e4692267a1678096afd6793c0519d9417cf77c8a6c706a543", size = 8402, upload-time = "2025-06-14T20:54:19.044Z" }, + { url = "https://files.pythonhosted.org/packages/2d/8d/87ca0fa0a9eda3097a0f4f2eef1544bf1d984697939fbef7cda7495fddb9/pyobjc_framework_screensaver-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5bd10809005fbe0d68fe651f32a393ce059e90da38e74b6b3cd055ed5b23eaa9", size = 8480, upload-time = "2025-11-14T10:02:22.798Z" }, + { url = "https://files.pythonhosted.org/packages/5a/a4/2481711f2e9557b90bac74fa8bf821162cf7b65835732ae560fd52e9037e/pyobjc_framework_screensaver-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a3c90c2299eac6d01add81427ae2f90d7724f15d676261e838d7a7750f812322", size = 8422, upload-time = "2025-11-14T10:02:24.49Z" }, ] [[package]] name = "pyobjc-framework-screentime" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/82/33/ebed70a1de134de936bb9a12d5c76f24e1e335ff4964f9bb0af9b09607f1/pyobjc_framework_screentime-11.1.tar.gz", hash = "sha256:9bb8269456bbb674e1421182efe49f9168ceefd4e7c497047c7bf63e2f510a34", size = 14875, upload-time = "2025-06-14T20:58:26.179Z" } +sdist = { url = "https://files.pythonhosted.org/packages/10/11/ba18f905321895715dac3cae2071c2789745ae13605b283b8114b41e0459/pyobjc_framework_screentime-12.1.tar.gz", hash = "sha256:583de46b365543bbbcf27cd70eedd375d397441d64a2cf43c65286fd9c91af55", size = 13413, upload-time = "2025-11-14T10:21:49.17Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ea/20/783eccea7206ceeda42a09a4614e3da92889e4c54abe9dec2e5e53576e1a/pyobjc_framework_screentime-11.1-py2.py3-none-any.whl", hash = "sha256:50a4e4ab33d6643a52616e990aa1c697d5e3e8f9f9bdab8d631e6d42d8287b4f", size = 3949, upload-time = "2025-06-14T20:54:26.916Z" }, + { url = "https://files.pythonhosted.org/packages/27/06/904174de6170e11b53673cc5844e5f13394eeeed486e0bcdf5288c1b0853/pyobjc_framework_screentime-12.1-py2.py3-none-any.whl", hash = "sha256:d34a068ec8ba2704987fcd05c37c9a9392de61d92933e6e71c8e4eaa4dfce029", size = 3963, upload-time = "2025-11-14T10:02:32.577Z" }, ] [[package]] name = "pyobjc-framework-scriptingbridge" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8e/c1/5b1dd01ff173df4c6676f97405113458918819cb2064c1735b61948e8800/pyobjc_framework_scriptingbridge-11.1.tar.gz", hash = "sha256:604445c759210a35d86d3e0dfcde0aac8e5e3e9d9e35759e0723952138843699", size = 23155, upload-time = "2025-06-14T20:58:26.812Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0c/cb/adc0a09e8c4755c2281bd12803a87f36e0832a8fc853a2d663433dbb72ce/pyobjc_framework_scriptingbridge-12.1.tar.gz", hash = "sha256:0e90f866a7e6a8aeaf723d04c826657dd528c8c1b91e7a605f8bb947c74ad082", size = 20339, upload-time = "2025-11-14T10:21:51.769Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/76/e173ca0b121693bdc6ac5797b30fd5771f31a682d15fd46402dc6f9ca3d1/pyobjc_framework_scriptingbridge-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d6020c69c14872105852ff99aab7cd2b2671e61ded3faefb071dc40a8916c527", size = 8301, upload-time = "2025-06-14T20:54:29.082Z" }, - { url = "https://files.pythonhosted.org/packages/c1/64/31849063e3e81b4c312ce838dc98f0409c09eb33bc79dbb5261cb994a4c4/pyobjc_framework_scriptingbridge-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:226ba12d9cbd504411b702323b0507dd1690e81b4ce657c5f0d8b998c46cf374", size = 8323, upload-time = "2025-06-14T20:54:30.105Z" }, + { url = "https://files.pythonhosted.org/packages/42/de/0943ee8d7f1a7d8467df6e2ea017a6d5041caff2fb0283f37fea4c4ce370/pyobjc_framework_scriptingbridge-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e6e37e69760d6ac9d813decf135d107760d33e1cdf7335016522235607f6f31b", size = 8335, upload-time = "2025-11-14T10:02:36.654Z" }, + { url = "https://files.pythonhosted.org/packages/51/46/e0b07d2b3ff9effb8b1179a6cc681a953d3dfbf0eb8b1d6a0e54cef2e922/pyobjc_framework_scriptingbridge-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8083cd68c559c55a3787b2e74fc983c8665e5078571475aaeabf4f34add36b62", size = 8356, upload-time = "2025-11-14T10:02:38.559Z" }, ] [[package]] name = "pyobjc-framework-searchkit" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-coreservices", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6e/20/61b73fddae0d1a94f5defb0cd4b4f391ec03bfcce7ebe830cb827d5e208a/pyobjc_framework_searchkit-11.1.tar.gz", hash = "sha256:13a194eefcf1359ce9972cd92f2aadddf103f3efb1b18fd578ba5367dff3c10c", size = 30918, upload-time = "2025-06-14T20:58:27.447Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6e/60/a38523198430e14fdef21ebe62a93c43aedd08f1f3a07ea3d96d9997db5d/pyobjc_framework_searchkit-12.1.tar.gz", hash = "sha256:ddd94131dabbbc2d7c3f17db3da87c1a712c431310eef16f07187771e7e85226", size = 30942, upload-time = "2025-11-14T10:21:55.483Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/ed/a118d275a9132c8f5adcd353e4d9e844777068e33d51b195f46671161a7f/pyobjc_framework_searchkit-11.1-py2.py3-none-any.whl", hash = "sha256:9c9d6ca71cef637ccc3627225fb924a460b3d0618ed79bb0b3c12fcbe9270323", size = 3714, upload-time = "2025-06-14T20:54:34.329Z" }, + { url = "https://files.pythonhosted.org/packages/72/46/4f9cd3011f47b43b21b2924ab3770303c3f0a4d16f05550d38c5fcb42e78/pyobjc_framework_searchkit-12.1-py2.py3-none-any.whl", hash = "sha256:844ce62b7296b19da8db7dedd539d07f7b3fb3bb8b029c261f7bcf0e01a97758", size = 3733, upload-time = "2025-11-14T10:02:47.026Z" }, ] [[package]] name = "pyobjc-framework-security" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ee/6f/ba50ed2d9c1192c67590a7cfefa44fc5f85c776d1e25beb224dec32081f6/pyobjc_framework_security-11.1.tar.gz", hash = "sha256:dabcee6987c6bae575e2d1ef0fcbe437678c4f49f1c25a4b131a5e960f31a2da", size = 302291, upload-time = "2025-06-14T20:58:28.506Z" } +sdist = { url = "https://files.pythonhosted.org/packages/80/aa/796e09a3e3d5cee32ebeebb7dcf421b48ea86e28c387924608a05e3f668b/pyobjc_framework_security-12.1.tar.gz", hash = "sha256:7fecb982bd2f7c4354513faf90ba4c53c190b7e88167984c2d0da99741de6da9", size = 168044, upload-time = "2025-11-14T10:22:06.334Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ac/ae/1679770d9a1cf5f2fe532a3567a51f0c5ee09054ae2c4003ae8f3e11eea4/pyobjc_framework_security-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d361231697486e97cfdafadf56709190696ab26a6a086dbba5f170e042e13daa", size = 41202, upload-time = "2025-06-14T20:54:36.255Z" }, - { url = "https://files.pythonhosted.org/packages/35/16/7fc52ab1364ada5885bf9b4c9ea9da3ad892b847c9b86aa59e086b16fc11/pyobjc_framework_security-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2eb4ba6d8b221b9ad5d010e026247e8aa26ee43dcaf327e848340ed227d22d7e", size = 41222, upload-time = "2025-06-14T20:54:37.032Z" }, + { url = "https://files.pythonhosted.org/packages/5e/3d/8d3a39cd292d7c76ab76233498189bc7170fc80f573b415308464f68c7ee/pyobjc_framework_security-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1b2d8819f0fb7b619ec7627a0d8c1cac1a57c5143579ce8ac21548165680684b", size = 41287, upload-time = "2025-11-14T10:02:54.491Z" }, + { url = "https://files.pythonhosted.org/packages/76/66/5160c0f938fc0515fe8d9af146aac1b093f7ef285ce797fedae161b6c0e8/pyobjc_framework_security-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ab42e55f5b782332be5442750fcd9637ee33247d57c7b1d5801bc0e24ee13278", size = 41280, upload-time = "2025-11-14T10:02:58.097Z" }, ] [[package]] name = "pyobjc-framework-securityfoundation" -version = "11.1" +version = "12.1" 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'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5c/d4/19591dd0938a45b6d8711ef9ae5375b87c37a55b45d79c52d6f83a8d991f/pyobjc_framework_securityfoundation-11.1.tar.gz", hash = "sha256:b3c4cf70735a93e9df40f3a14478143959c415778f27be8c0dc9ae0c5b696b92", size = 13270, upload-time = "2025-06-14T20:58:29.304Z" } +sdist = { url = "https://files.pythonhosted.org/packages/57/d5/c2b77e83c1585ba43e5f00c917273ba4bf7ed548c1b691f6766eb0418d52/pyobjc_framework_securityfoundation-12.1.tar.gz", hash = "sha256:1f39f4b3db6e3bd3a420aaf4923228b88e48c90692cf3612b0f6f1573302a75d", size = 12669, upload-time = "2025-11-14T10:22:09.256Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6c/ab/23db6b1c09810d6bcc4eab96e62487fb4284b57e447eabe6c001cb41e36d/pyobjc_framework_securityfoundation-11.1-py2.py3-none-any.whl", hash = "sha256:25f2cf10f80c122f462e9d4d43efe9fd697299c194e0c357e76650e234e6d286", size = 3772, upload-time = "2025-06-14T20:54:41.732Z" }, + { url = "https://files.pythonhosted.org/packages/93/1e/349fb71a413b37b1b41e712c7ca180df82144478f8a9a59497d66d0f2ea2/pyobjc_framework_securityfoundation-12.1-py2.py3-none-any.whl", hash = "sha256:579cf23e63434226f78ffe0afb8426e971009588e4ad812c478d47dfd558201c", size = 3792, upload-time = "2025-11-14T10:03:14.459Z" }, ] [[package]] name = "pyobjc-framework-securityinterface" -version = "11.1" +version = "12.1" 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'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a1/be/c846651c3e7f38a637c40ae1bcda9f14237c2395637c3a188df4f733c727/pyobjc_framework_securityinterface-11.1.tar.gz", hash = "sha256:e7aa6373e525f3ae05d71276e821a6348c53fec9f812b90eec1dbadfcb507bc9", size = 37648, upload-time = "2025-06-14T20:58:29.932Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/64/bf5b5d82655112a2314422ee649f1e1e73d4381afa87e1651ce7e8444694/pyobjc_framework_securityinterface-12.1.tar.gz", hash = "sha256:deef11ad03be8d9ff77db6e7ac40f6b641ee2d72eaafcf91040537942472e88b", size = 25552, upload-time = "2025-11-14T10:22:12.098Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1c/ec/8073f37f56870efb039970f1cc4536f279c5d476abab2e8654129789277f/pyobjc_framework_securityinterface-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3e884620b22918d462764f0665f6ac0cbb8142bb160fcd27c4f4357f81da73b7", size = 10769, upload-time = "2025-06-14T20:54:43.344Z" }, - { url = "https://files.pythonhosted.org/packages/6f/ab/48b8027a24f3f8924f5be5f97217961b4ed23e6be49b3bd94ee8a0d56a1e/pyobjc_framework_securityinterface-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:26056441b325029da06a7c7b8dd1a0c9a4ad7d980596c1b04d132a502b4cacc0", size = 10837, upload-time = "2025-06-14T20:54:44.052Z" }, + { url = "https://files.pythonhosted.org/packages/37/1c/a01fd56765792d1614eb5e8dc0a7d5467564be6a2056b417c9ec7efc648f/pyobjc_framework_securityinterface-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ed599be750122376392e95c2407d57bd94644e8320ddef1d67660e16e96b0d06", size = 10719, upload-time = "2025-11-14T10:03:18.353Z" }, + { url = "https://files.pythonhosted.org/packages/59/3e/17889a6de03dc813606bb97887dc2c4c2d4e7c8f266bc439548bae756e90/pyobjc_framework_securityinterface-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5cb5e79a73ea17663ebd29e350401162d93e42343da7d96c77efb38ae64ff01f", size = 10783, upload-time = "2025-11-14T10:03:20.202Z" }, ] [[package]] name = "pyobjc-framework-securityui" -version = "11.1" +version = "12.1" 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'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/07/5b/3b5585d56e0bcaba82e0661224bbc7aaf29fba6b10498971dbe08b2b490a/pyobjc_framework_securityui-11.1.tar.gz", hash = "sha256:e80c93e8a56bf89e4c0333047b9f8219752dd6de290681e9e2e2b2e26d69e92d", size = 12179, upload-time = "2025-06-14T20:58:30.928Z" } +sdist = { url = "https://files.pythonhosted.org/packages/83/3f/d870305f5dec58cd02966ca06ac29b69fb045d8b46dfb64e2da31f295345/pyobjc_framework_securityui-12.1.tar.gz", hash = "sha256:f1435fed85edc57533c334a4efc8032170424b759da184cb7a7a950ceea0e0b6", size = 12184, upload-time = "2025-11-14T10:22:14.323Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/a4/c9fcc42065b6aed73b14b9650c1dc0a4af26a30d418cbc1bab33621b461c/pyobjc_framework_securityui-11.1-py2.py3-none-any.whl", hash = "sha256:3cdb101b03459fcf8e4064b90021d06761003f669181e02f43ff585e6ba2403d", size = 3581, upload-time = "2025-06-14T20:54:49.474Z" }, + { url = "https://files.pythonhosted.org/packages/36/7f/eff9ffdd34511cc95a60e5bd62f1cfbcbcec1a5012ef1168161506628c87/pyobjc_framework_securityui-12.1-py2.py3-none-any.whl", hash = "sha256:3e988b83c9a2bb0393207eaa030fc023a8708a975ac5b8ea0508cdafc2b60705", size = 3594, upload-time = "2025-11-14T10:03:29.628Z" }, ] [[package]] name = "pyobjc-framework-sensitivecontentanalysis" -version = "11.1" +version = "12.1" 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'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/56/7b/e28f6b30d99e9d464427a07ada82b33cd3292f310bf478a1824051d066b9/pyobjc_framework_sensitivecontentanalysis-11.1.tar.gz", hash = "sha256:5b310515c7386f7afaf13e4632d7d9590688182bb7b563f8026c304bdf317308", size = 12796, upload-time = "2025-06-14T20:58:31.488Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/ce/17bf31753e14cb4d64fffaaba2377453c4977c2c5d3cf2ff0a3db30026c7/pyobjc_framework_sensitivecontentanalysis-12.1.tar.gz", hash = "sha256:2c615ac10e93eb547b32b214cd45092056bee0e79696426fd09978dc3e670f25", size = 13745, upload-time = "2025-11-14T10:22:16.447Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3c/63/76a939ecac74ca079702165330c692ad2c05ff9b2b446a72ddc8cdc63bb9/pyobjc_framework_sensitivecontentanalysis-11.1-py2.py3-none-any.whl", hash = "sha256:dbb78f5917f986a63878bb91263bceba28bd86fc381bad9461cf391646db369f", size = 3852, upload-time = "2025-06-14T20:54:50.75Z" }, + { url = "https://files.pythonhosted.org/packages/95/23/c99568a0d4e38bd8337d52e4ae25a0b0bd540577f2e06f3430c951d73209/pyobjc_framework_sensitivecontentanalysis-12.1-py2.py3-none-any.whl", hash = "sha256:faf19d32d4599ac2b18fb1ccdc3e33b2b242bdf34c02e69978bd62d3643ad068", size = 4230, upload-time = "2025-11-14T10:03:31.26Z" }, ] [[package]] name = "pyobjc-framework-servicemanagement" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/20/c6/32e11599d9d232311607b79eb2d1d21c52eaaf001599ea85f8771a933fa2/pyobjc_framework_servicemanagement-11.1.tar.gz", hash = "sha256:90a07164da49338480e0e135b445acc6ae7c08549a2037d1e512d2605fedd80a", size = 16645, upload-time = "2025-06-14T20:58:32.062Z" } +sdist = { url = "https://files.pythonhosted.org/packages/31/d0/b26c83ae96ab55013df5fedf89337d4d62311b56ce3f520fc7597d223d82/pyobjc_framework_servicemanagement-12.1.tar.gz", hash = "sha256:08120981749a698033a1d7a6ab99dbbe412c5c0d40f2b4154014b52113511c1d", size = 14585, upload-time = "2025-11-14T10:22:18.735Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/f1/222462f5afcb6cb3c1fc9e6092dfcffcc7eb9db8bd2cef8c1743a22fbe95/pyobjc_framework_servicemanagement-11.1-py2.py3-none-any.whl", hash = "sha256:104f56557342a05ad68cd0c9daf63b7f4678957fe1f919f03a872f1607a50710", size = 5338, upload-time = "2025-06-14T20:54:51.614Z" }, + { url = "https://files.pythonhosted.org/packages/ee/5d/1009c32189f9cb26da0124b4a60640ed26dd8ad453810594f0cbfab0ff70/pyobjc_framework_servicemanagement-12.1-py2.py3-none-any.whl", hash = "sha256:9a2941f16eeb71e55e1cd94f50197f91520778c7f48ad896761f5e78725cc08f", size = 5357, upload-time = "2025-11-14T10:03:32.928Z" }, ] [[package]] name = "pyobjc-framework-sharedwithyou" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-sharedwithyoucore", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fe/a5/e299fbd0c13d4fac9356459f21372f6eef4279d0fbc99ba316d88dfbbfb4/pyobjc_framework_sharedwithyou-11.1.tar.gz", hash = "sha256:ece3a28a3083d0bcad0ac95b01f0eb699b9d2d0c02c61305bfd402678753ff6e", size = 34216, upload-time = "2025-06-14T20:58:32.75Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/8b/8ab209a143c11575a857e2111acc5427fb4986b84708b21324cbcbf5591b/pyobjc_framework_sharedwithyou-12.1.tar.gz", hash = "sha256:167d84794a48f408ee51f885210c616fda1ec4bff3dd8617a4b5547f61b05caf", size = 24791, upload-time = "2025-11-14T10:22:21.248Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/23/7caefaddc58702da830d1cc4eb3c45ae82dcd605ea362126ab47ebd54f7d/pyobjc_framework_sharedwithyou-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ce1c37d5f8cf5b0fe8a261e4e7256da677162fd5aa7b724e83532cdfe58d8f94", size = 8725, upload-time = "2025-06-14T20:54:53.179Z" }, - { url = "https://files.pythonhosted.org/packages/57/44/211e1f18676e85d3656671fc0c954ced2cd007e55f1b0b6b2e4d0a0852eb/pyobjc_framework_sharedwithyou-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:99e1749187ae370be7b9c55dd076d1b8143f0d8db3e83f52540586f32e7abb33", size = 8740, upload-time = "2025-06-14T20:54:53.879Z" }, + { url = "https://files.pythonhosted.org/packages/19/69/3ad9b344808c5619adc253b665f8677829dfb978888227e07233d120cfab/pyobjc_framework_sharedwithyou-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:359c03096a6988371ea89921806bb81483ea509c9aa7114f9cd20efd511b3576", size = 8739, upload-time = "2025-11-14T10:03:36.48Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ee/e5113ce985a480d13a0fa3d41a242c8068dc09b3c13210557cf5cc6a544a/pyobjc_framework_sharedwithyou-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a99a6ebc6b6de7bc8663b1f07332fab9560b984a57ce344dc5703f25258f258d", size = 8763, upload-time = "2025-11-14T10:03:38.467Z" }, ] [[package]] name = "pyobjc-framework-sharedwithyoucore" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/79/a3/1ca6ff1b785772c7c5a38a7c017c6f971b1eda638d6a0aab3bbde18ac086/pyobjc_framework_sharedwithyoucore-11.1.tar.gz", hash = "sha256:790050d25f47bda662a9f008b17ca640ac2460f2559a56b17995e53f2f44ed73", size = 29459, upload-time = "2025-06-14T20:58:33.422Z" } +sdist = { url = "https://files.pythonhosted.org/packages/55/ef/84059c5774fd5435551ab7ab40b51271cfb9997b0d21f491c6b429fe57a8/pyobjc_framework_sharedwithyoucore-12.1.tar.gz", hash = "sha256:0813149eeb755d718b146ec9365eb4ca3262b6af9ff9ba7db2f7b6f4fd104518", size = 22350, upload-time = "2025-11-14T10:22:23.611Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7a/df/08cfa01dcdb4655514b7a10eb7c40da2bdb7866078c761d6ed26c9f464f7/pyobjc_framework_sharedwithyoucore-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9a7fe5ffcc65093ef7cd25903769ad557c3d3c5a59155a31f3f934cf555101e6", size = 8489, upload-time = "2025-06-14T20:54:59.631Z" }, - { url = "https://files.pythonhosted.org/packages/b9/70/3b2e13fcf393aa434b1cf5c29c6aaf65ee5b8361254df3a920ed436bb5e4/pyobjc_framework_sharedwithyoucore-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:dd18c588b29de322c25821934d6aa6d2bbbdbb89b6a4efacdb248b4115fc488d", size = 8512, upload-time = "2025-06-14T20:55:00.411Z" }, + { url = "https://files.pythonhosted.org/packages/ce/a1/83e58eca8827a1a9975a9c5de7f8c0bdc73b5f53ee79768d1fdbec6747de/pyobjc_framework_sharedwithyoucore-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f4f9f7fed0768ebbbc2d24248365da2cf5f014b8822b2a1fbbce5fa920f410f1", size = 8512, upload-time = "2025-11-14T10:03:49.176Z" }, + { url = "https://files.pythonhosted.org/packages/dd/0e/0c2b0591ebc72d437dccca7a1e7164c5f11dde2189d4f4c707a132bab740/pyobjc_framework_sharedwithyoucore-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ed928266ae9d577ff73de72a03bebc66a751918eb59ca660a9eca157392f17be", size = 8530, upload-time = "2025-11-14T10:03:50.839Z" }, ] [[package]] name = "pyobjc-framework-shazamkit" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/de/08/ba739b97f1e441653bae8da5dd1e441bbbfa43940018d21edb60da7dd163/pyobjc_framework_shazamkit-11.1.tar.gz", hash = "sha256:c6e3c9ab8744d9319a89b78ae6f185bb5704efb68509e66d77bcd1f84a9446d6", size = 25797, upload-time = "2025-06-14T20:58:34.086Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ed/2c/8d82c5066cc376de68ad8c1454b7c722c7a62215e5c2f9dac5b33a6c3d42/pyobjc_framework_shazamkit-12.1.tar.gz", hash = "sha256:71db2addd016874639a224ed32b2000b858802b0370c595a283cce27f76883fe", size = 22518, upload-time = "2025-11-14T10:22:25.996Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/b6/c03bc9aad7f15979b5d7f144baf5161c3c40e0bca194cce82e1bce0804a9/pyobjc_framework_shazamkit-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2fe6990d0ec1b40d4efd0d0e49c2deb65198f49b963e6215c608c140b3149151", size = 8540, upload-time = "2025-06-14T20:55:05.978Z" }, - { url = "https://files.pythonhosted.org/packages/89/b7/594b8bdc406603a7a07cdb33f2be483fed16aebc35aeb087385fc9eca844/pyobjc_framework_shazamkit-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b323f5409b01711aa2b6e2113306084fab2cc83fa57a0c3d55bd5876358b68d8", size = 8560, upload-time = "2025-06-14T20:55:07.564Z" }, + { url = "https://files.pythonhosted.org/packages/92/12/09d83a8ac51dc11a574449dea48ffa99b3a7c9baf74afeedb487394d110d/pyobjc_framework_shazamkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0c10ba22de524fbedf06270a71bb0a3dbd4a3853b7002ddf54394589c3be6939", size = 8555, upload-time = "2025-11-14T10:04:02.552Z" }, + { url = "https://files.pythonhosted.org/packages/04/5e/7d60d8e7b036b20d0e94cd7c4563e7414653344482e85fbc7facffabc95f/pyobjc_framework_shazamkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e184dd0f61a604b1cfcf44418eb95b943e7b8f536058a29e4b81acadd27a9420", size = 8577, upload-time = "2025-11-14T10:04:04.182Z" }, ] [[package]] name = "pyobjc-framework-social" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/07/2e/cc7707b7a40df392c579087947049f3e1f0e00597e7151ec411f654d8bef/pyobjc_framework_social-11.1.tar.gz", hash = "sha256:fbc09d7b00dad45b547f9b2329f4dcee3f5a50e2348de1870de0bd7be853a5b7", size = 14540, upload-time = "2025-06-14T20:58:35.116Z" } +sdist = { url = "https://files.pythonhosted.org/packages/31/21/afc6f37dfdd2cafcba0227e15240b5b0f1f4ad57621aeefda2985ac9560e/pyobjc_framework_social-12.1.tar.gz", hash = "sha256:1963db6939e92ae40dd9d68852e8f88111cbfd37a83a9fdbc9a0c08993ca7e60", size = 13184, upload-time = "2025-11-14T10:22:28.048Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/86/1d/e1026c082a66075dbb7e57983c0aaaed3ee09f06c346743e8af24d1dc21a/pyobjc_framework_social-11.1-py2.py3-none-any.whl", hash = "sha256:ab5878c47d7a0639704c191cee43eeb259e09688808f0905c42551b9f79e1d57", size = 4444, upload-time = "2025-06-14T20:55:12.536Z" }, + { url = "https://files.pythonhosted.org/packages/f6/fb/090867e332d49a1e492e4b8972ac6034d1c7d17cf39f546077f35be58c46/pyobjc_framework_social-12.1-py2.py3-none-any.whl", hash = "sha256:2f3b36ba5769503b1bc945f85fd7b255d42d7f6e417d78567507816502ff2b44", size = 4462, upload-time = "2025-11-14T10:04:14.578Z" }, ] [[package]] name = "pyobjc-framework-soundanalysis" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e0/d4/b9497dbb57afdf0d22f61bb6e776a6f46cf9294c890448acde5b46dd61f3/pyobjc_framework_soundanalysis-11.1.tar.gz", hash = "sha256:42cd25b7e0f343d8b59367f72b5dae96cf65696bdb8eeead8d7424ed37aa1434", size = 16539, upload-time = "2025-06-14T20:58:35.813Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/d6/5039b61edc310083425f87ce2363304d3a87617e941c1d07968c63b5638d/pyobjc_framework_soundanalysis-12.1.tar.gz", hash = "sha256:e2deead8b9a1c4513dbdcf703b21650dcb234b60a32d08afcec4895582b040b1", size = 14804, upload-time = "2025-11-14T10:22:29.998Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/13/b4/7e8cf3a02e615239568fdf12497233bbd5b58082615cd28a0c7cd4636309/pyobjc_framework_soundanalysis-11.1-py2.py3-none-any.whl", hash = "sha256:6cf983c24fb2ad2aa5e7499ab2d30ff134d887fe91fd2641acf7472e546ab4e5", size = 4161, upload-time = "2025-06-14T20:55:13.342Z" }, + { url = "https://files.pythonhosted.org/packages/53/d3/8df5183d52d20d459225d3f5d24f55e01b8cd9fe587ed972e3f20dd18709/pyobjc_framework_soundanalysis-12.1-py2.py3-none-any.whl", hash = "sha256:8b2029ab48c1a9772f247f0aea995e8c3ff4706909002a9c1551722769343a52", size = 4188, upload-time = "2025-11-14T10:04:16.12Z" }, ] [[package]] name = "pyobjc-framework-speech" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/67/76/2a1fd7637b2c662349ede09806e159306afeebfba18fb062ad053b41d811/pyobjc_framework_speech-11.1.tar.gz", hash = "sha256:d382977208c3710eacea89e05eae4578f1638bb5a7b667c06971e3d34e96845c", size = 41179, upload-time = "2025-06-14T20:58:36.43Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8d/3d/194cf19fe7a56c2be5dfc28f42b3b597a62ebb1e1f52a7dd9c55b917ac6c/pyobjc_framework_speech-12.1.tar.gz", hash = "sha256:2a2a546ba6c52d5dd35ddcfee3fd9226a428043d1719597e8701851a6566afdd", size = 25218, upload-time = "2025-11-14T10:22:32.505Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b5/d3/c3b1d542c5ddc816924f02edf2ececcda226f35c91e95ed80f2632fbd91c/pyobjc_framework_speech-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d3e0276a66d2fa4357959a6f6fb5def03f8e0fd3aa43711d6a81ab2573b9415f", size = 9171, upload-time = "2025-06-14T20:55:15.316Z" }, - { url = "https://files.pythonhosted.org/packages/78/59/267f4699055beb39723ccbff70909ec3851e4adf17386f6ad85e5d983780/pyobjc_framework_speech-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7726eff52cfa9cc7178ddcd1285cbc23b5f89ee55b4b850b0d2e90bb4f8e044b", size = 9180, upload-time = "2025-06-14T20:55:16.556Z" }, + { url = "https://files.pythonhosted.org/packages/03/54/77e12e4c23a98fc49d874f9703c9f8fd0257d64bb0c6ae329b91fc7a99e3/pyobjc_framework_speech-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0301bfae5d0d09b6e69bd4dbabc5631209e291cc40bda223c69ed0c618f8f2dc", size = 9248, upload-time = "2025-11-14T10:04:19.73Z" }, + { url = "https://files.pythonhosted.org/packages/f9/1b/224cb98c9c32a6d5e68072f89d26444095be54c6f461efe4fefe9d1330a5/pyobjc_framework_speech-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:cae4b88ef9563157a6c9e66b37778fc4022ee44dd1a2a53081c2adbb69698945", size = 9254, upload-time = "2025-11-14T10:04:21.361Z" }, ] [[package]] name = "pyobjc-framework-spritekit" -version = "11.1" +version = "12.1" 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'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/16/02/2e253ba4f7fad6efe05fd5fcf44aede093f6c438d608d67c6c6623a1846d/pyobjc_framework_spritekit-11.1.tar.gz", hash = "sha256:914da6e846573cac8db5e403dec9a3e6f6edf5211f9b7e429734924d00f65108", size = 130297, upload-time = "2025-06-14T20:58:37.113Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b6/78/d683ebe0afb49f46d2d21d38c870646e7cb3c2e83251f264e79d357b1b74/pyobjc_framework_spritekit-12.1.tar.gz", hash = "sha256:a851f4ef5aa65cc9e08008644a528e83cb31021a1c0f17ebfce4de343764d403", size = 64470, upload-time = "2025-11-14T10:22:37.569Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8f/83/1c874cffba691cf8c103e0fdf55b53d9749577794efb9fc30e4394ffef41/pyobjc_framework_spritekit-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1c8c94d37c054b6e3c22c237f6458c12649776e5ac921d066ab99dee2e580909", size = 17718, upload-time = "2025-06-14T20:55:22.543Z" }, - { url = "https://files.pythonhosted.org/packages/f1/fe/39d92bf40ec7a6116f89fd95053321f7c00c50c10d82b9adfa0f9ebdb10c/pyobjc_framework_spritekit-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8b470a890db69e70ef428dfff88da499500fca9b2d44da7120dc588d13a2dbdb", size = 17776, upload-time = "2025-06-14T20:55:23.639Z" }, + { url = "https://files.pythonhosted.org/packages/60/6a/e8e44fc690d898394093f3a1c5fe90110d1fbcc6e3f486764437c022b0f8/pyobjc_framework_spritekit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:26fd12944684713ae1e3cdd229348609c1142e60802624161ca0c3540eec3ffa", size = 17736, upload-time = "2025-11-14T10:04:33.202Z" }, + { url = "https://files.pythonhosted.org/packages/3b/38/97c3b6c3437e3e9267fb4e1cd86e0da4eff07e0abe7cd6923644d2dfc878/pyobjc_framework_spritekit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1649e57c25145795d04bb6a1ec44c20ef7cf0af7c60a9f6f5bc7998dd269db1e", size = 17802, upload-time = "2025-11-14T10:04:35.346Z" }, ] [[package]] name = "pyobjc-framework-storekit" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/44/a0/58cab9ebc9ac9282e1d4734b1987d1c3cd652b415ec3e678fcc5e735d279/pyobjc_framework_storekit-11.1.tar.gz", hash = "sha256:85acc30c0bfa120b37c3c5ac693fe9ad2c2e351ee7a1f9ea6f976b0c311ff164", size = 76421, upload-time = "2025-06-14T20:58:37.86Z" } +sdist = { url = "https://files.pythonhosted.org/packages/00/87/8a66a145feb026819775d44975c71c1c64df4e5e9ea20338f01456a61208/pyobjc_framework_storekit-12.1.tar.gz", hash = "sha256:818452e67e937a10b5c8451758274faa44ad5d4329df0fa85735115fb0608da9", size = 34574, upload-time = "2025-11-14T10:22:40.73Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/30/7549a7bd2b068cd460792e09a66d88465aab2ac6fb2ddcf77b7bf5712eee/pyobjc_framework_storekit-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:624105bd26a9ce5a097b3f96653e2700d33bb095828ed65ee0f4679b34d9f1e1", size = 11841, upload-time = "2025-06-14T20:55:29.735Z" }, - { url = "https://files.pythonhosted.org/packages/ac/61/6404aac6857ea43798882333bcc26bfd3c9c3a1efc7a575cbf3e53538e2a/pyobjc_framework_storekit-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5ca3373272b6989917c88571ca170ce6d771180fe1a2b44c7643fe084569b93e", size = 11868, upload-time = "2025-06-14T20:55:30.454Z" }, + { url = "https://files.pythonhosted.org/packages/d9/41/af2afc4d27bde026cfd3b725ee1b082b2838dcaa9880ab719226957bc7cd/pyobjc_framework_storekit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a29f45bcba9dee4cf73dae05ab0f94d06a32fb052e31414d0c23791c1ec7931c", size = 12810, upload-time = "2025-11-14T10:04:48.693Z" }, + { url = "https://files.pythonhosted.org/packages/8a/9f/938985e506de0cc3a543e44e1f9990e9e2fb8980b8f3bcfc8f7921d09061/pyobjc_framework_storekit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9fe2d65a2b644bb6b4fdd3002292cba153560917de3dd6cf969431fa32d21dd0", size = 12819, upload-time = "2025-11-14T10:04:50.945Z" }, ] [[package]] name = "pyobjc-framework-symbols" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cd/af/7191276204bd3e7db1d0a3e490a869956606f77f7a303a04d92a5d0c3f7b/pyobjc_framework_symbols-11.1.tar.gz", hash = "sha256:0e09b7813ef2ebdca7567d3179807444dd60f3f393202b35b755d4e1baf99982", size = 13377, upload-time = "2025-06-14T20:58:38.542Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/ce/a48819eb8524fa2dc11fb3dd40bb9c4dcad0596fe538f5004923396c2c6c/pyobjc_framework_symbols-12.1.tar.gz", hash = "sha256:7d8e999b8a59c97d38d1d343b6253b1b7d04bf50b665700957d89c8ac43b9110", size = 12782, upload-time = "2025-11-14T10:22:42.609Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/6a/c91f64ef9b8cd20245b88e392c66cb2279c511724f4ea2983d92584d6f3e/pyobjc_framework_symbols-11.1-py2.py3-none-any.whl", hash = "sha256:1de6fc3af15fc8d5fd4869663a3250311844ec33e99ec8a1991a352ab61d641d", size = 3312, upload-time = "2025-06-14T20:55:35.456Z" }, + { url = "https://files.pythonhosted.org/packages/f0/ea/6e9af9c750d68109ac54fbffb5463e33a7b54ffe8b9901a5b6b603b7884b/pyobjc_framework_symbols-12.1-py2.py3-none-any.whl", hash = "sha256:c72eecbc25f6bfcd39c733067276270057c5aca684be20fdc56def645f2b6446", size = 3331, upload-time = "2025-11-14T10:05:01.333Z" }, ] [[package]] name = "pyobjc-framework-syncservices" -version = "11.1" +version = "12.1" 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'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/69/45/cd9fa83ed1d75be7130fb8e41c375f05b5d6621737ec37e9d8da78676613/pyobjc_framework_syncservices-11.1.tar.gz", hash = "sha256:0f141d717256b98c17ec2eddbc983c4bd39dfa00dc0c31b4174742e73a8447fe", size = 57996, upload-time = "2025-06-14T20:58:39.146Z" } +sdist = { url = "https://files.pythonhosted.org/packages/21/91/6d03a988831ddb0fb001b13573560e9a5bcccde575b99350f98fe56a2dd4/pyobjc_framework_syncservices-12.1.tar.gz", hash = "sha256:6a213e93d9ce15128810987e4c5de8c73cfab1564ac8d273e6b437a49965e976", size = 31032, upload-time = "2025-11-14T10:22:45.902Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f5/7e/60e184beafca85571cfa68d46a8f453a54edbc7d2eceb18163cfec438438/pyobjc_framework_syncservices-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bc6159bda4597149c6999b052a35ffd9fc4817988293da6e54a1e073fa571653", size = 13464, upload-time = "2025-06-14T20:55:37.117Z" }, - { url = "https://files.pythonhosted.org/packages/01/2b/6d7d65c08a9c51eed12eb7f83eaa48deaed621036f77221b3b0346c3f6c2/pyobjc_framework_syncservices-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:03124c8c7c7ce837f51e1c9bdcf84c6f1d5201f92c8a1c172ec34908d5e57415", size = 13496, upload-time = "2025-06-14T20:55:37.83Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9b/25c117f8ffe15aa6cc447da7f5c179627ebafb2b5ec30dfb5e70fede2549/pyobjc_framework_syncservices-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e81a38c2eb7617cb0ecfc4406c1ae2a97c60e95af42e863b2b0f1f6facd9b0da", size = 13380, upload-time = "2025-11-14T10:05:05.814Z" }, + { url = "https://files.pythonhosted.org/packages/54/ac/a83cdd120e279ee905e9085afda90992159ed30c6a728b2c56fa2d36b6ea/pyobjc_framework_syncservices-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0cd629bea95692aad2d26196657cde2fbadedae252c7846964228661a600b900", size = 13411, upload-time = "2025-11-14T10:05:07.741Z" }, ] [[package]] name = "pyobjc-framework-systemconfiguration" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e2/3d/41590c0afc72e93d911348fbde0c9c1071ff53c6f86df42df64b21174bb9/pyobjc_framework_systemconfiguration-11.1.tar.gz", hash = "sha256:f30ed0e9a8233fecb06522e67795918ab230ddcc4a18e15494eff7532f4c3ae1", size = 143410, upload-time = "2025-06-14T20:58:39.917Z" } +sdist = { url = "https://files.pythonhosted.org/packages/90/7d/50848df8e1c6b5e13967dee9fb91d3391fe1f2399d2d0797d2fc5edb32ba/pyobjc_framework_systemconfiguration-12.1.tar.gz", hash = "sha256:90fe04aa059876a21626931c71eaff742a27c79798a46347fd053d7008ec496e", size = 59158, upload-time = "2025-11-14T10:22:53.056Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/64/9b/8fe26a9ac85898fa58f6206f357745ec44cd95b63786503ce05c382344ce/pyobjc_framework_systemconfiguration-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d12d5078611c905162bc951dffbb2a989b0dfd156952ba1884736c8dcbe38f7f", size = 21732, upload-time = "2025-06-14T20:55:43.951Z" }, - { url = "https://files.pythonhosted.org/packages/b9/61/0e9841bf1c7597f380a6dcefcc9335b6a909f20d9bdf07910cddc8552b42/pyobjc_framework_systemconfiguration-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:6881929b828a566bf1349f09db4943e96a2b33f42556e1f7f6f28b192420f6fc", size = 21639, upload-time = "2025-06-14T20:55:44.678Z" }, + { url = "https://files.pythonhosted.org/packages/1d/7b/9126a7af1b798998837027390a20b981e0298e51c4c55eed6435967145cb/pyobjc_framework_systemconfiguration-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:796390a80500cc7fde86adc71b11cdc41d09507dd69103d3443fbb60e94fb438", size = 21663, upload-time = "2025-11-14T10:05:21.259Z" }, + { url = "https://files.pythonhosted.org/packages/d3/d3/bb935c3d4bae9e6ce4a52638e30eea7039c480dd96bc4f0777c9fabda21b/pyobjc_framework_systemconfiguration-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0e5bb9103d39483964431db7125195c59001b7bff2961869cfe157b4c861e52d", size = 21578, upload-time = "2025-11-14T10:05:25.572Z" }, ] [[package]] name = "pyobjc-framework-systemextensions" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b4/57/4609fd9183383616b1e643c2489ad774335f679523a974b9ce346a6d4d5b/pyobjc_framework_systemextensions-11.1.tar.gz", hash = "sha256:8ff9f0aad14dcdd07dd47545c1dd20df7a286306967b0a0232c81fcc382babe6", size = 23062, upload-time = "2025-06-14T20:58:40.686Z" } +sdist = { url = "https://files.pythonhosted.org/packages/12/01/8a706cd3f7dfcb9a5017831f2e6f9e5538298e90052db3bb8163230cbc4f/pyobjc_framework_systemextensions-12.1.tar.gz", hash = "sha256:243e043e2daee4b5c46cd90af5fff46b34596aac25011bab8ba8a37099685eeb", size = 20701, upload-time = "2025-11-14T10:22:58.257Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/10/53/0fb6a200383fa98001ffa66b4f6344c68ccd092506699a353b30f18d7094/pyobjc_framework_systemextensions-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7e742ae51cdd86c0e609fe47189ea446de98d13b235b0a138a3f2e37e98cd359", size = 9125, upload-time = "2025-06-14T20:55:50.431Z" }, - { url = "https://files.pythonhosted.org/packages/76/40/d9be444b39ec12d68b5e4f712b71d6c00d654936ff5744ea380c1bfabf06/pyobjc_framework_systemextensions-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3a2b1e84e4a118bfe13efb9f2888b065dc937e2a7e60afd4d0a82b51b8301a10", size = 9130, upload-time = "2025-06-14T20:55:51.127Z" }, + { url = "https://files.pythonhosted.org/packages/ac/a1/f8df6d59e06bc4b5989a76724e8551935e5b99aff6a21d3592e5ced91f1c/pyobjc_framework_systemextensions-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2a4e82160e43c0b1aa17e6d4435e840a655737fbe534e00e37fc1961fbf3bebd", size = 9156, upload-time = "2025-11-14T10:05:39.744Z" }, + { url = "https://files.pythonhosted.org/packages/0a/cc/a42883d6ad0ae257a7fa62660b4dd13be15f8fa657922f9a5b6697f26e28/pyobjc_framework_systemextensions-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:01fac4f8d88c0956d9fc714d24811cd070e67200ba811904317d91e849e38233", size = 9166, upload-time = "2025-11-14T10:05:41.479Z" }, ] [[package]] name = "pyobjc-framework-threadnetwork" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e7/a4/5400a222ced0e4f077a8f4dd0188e08e2af4762e72ed0ed39f9d27feefc9/pyobjc_framework_threadnetwork-11.1.tar.gz", hash = "sha256:73a32782f44b61ca0f8a4a9811c36b1ca1cdcf96c8a3ba4de35d8e8e58a86ad5", size = 13572, upload-time = "2025-06-14T20:58:41.311Z" } +sdist = { url = "https://files.pythonhosted.org/packages/62/7e/f1816c3461e4121186f2f7750c58af083d1826bbd73f72728da3edcf4915/pyobjc_framework_threadnetwork-12.1.tar.gz", hash = "sha256:e071eedb41bfc1b205111deb54783ec5a035ccd6929e6e0076336107fdd046ee", size = 12788, upload-time = "2025-11-14T10:23:00.329Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/f0/b7a577d00bdb561efef82b046a75f627a60de53566ab2d9e9ddd5bd11b66/pyobjc_framework_threadnetwork-11.1-py2.py3-none-any.whl", hash = "sha256:55021455215a0d3ad4e40152f94154e29062e73655558c5f6e71ab097d90083e", size = 3751, upload-time = "2025-06-14T20:55:55.643Z" }, + { url = "https://files.pythonhosted.org/packages/4f/b8/94b37dd353302c051a76f1a698cf55b5ad50ca061db7f0f332aa9e195766/pyobjc_framework_threadnetwork-12.1-py2.py3-none-any.whl", hash = "sha256:07d937748fc54199f5ec04d5a408e8691a870481c11b641785c2adc279dd8e4b", size = 3771, upload-time = "2025-11-14T10:05:49.899Z" }, ] [[package]] name = "pyobjc-framework-uniformtypeidentifiers" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c5/4f/066ed1c69352ccc29165f45afb302f8c9c2b5c6f33ee3abfa41b873c07e5/pyobjc_framework_uniformtypeidentifiers-11.1.tar.gz", hash = "sha256:86c499bec8953aeb0c95af39b63f2592832384f09f12523405650b5d5f1ed5e9", size = 20599, upload-time = "2025-06-14T20:58:41.945Z" } +sdist = { url = "https://files.pythonhosted.org/packages/65/b8/dd9d2a94509a6c16d965a7b0155e78edf520056313a80f0cd352413f0d0b/pyobjc_framework_uniformtypeidentifiers-12.1.tar.gz", hash = "sha256:64510a6df78336579e9c39b873cfcd03371c4b4be2cec8af75a8a3d07dff607d", size = 17030, upload-time = "2025-11-14T10:23:02.222Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/de/3b/b63b8137dd9f455d5abece6702c06c6b613fac6fda1319aaa2f79d00c380/pyobjc_framework_uniformtypeidentifiers-11.1-py2.py3-none-any.whl", hash = "sha256:6e2e8ea89eb8ca03bc2bc8e506fff901e71d916276475c8d81fbf0280059cb4c", size = 4891, upload-time = "2025-06-14T20:55:56.432Z" }, + { url = "https://files.pythonhosted.org/packages/4e/5f/1f10f5275b06d213c9897850f1fca9c881c741c1f9190cea6db982b71824/pyobjc_framework_uniformtypeidentifiers-12.1-py2.py3-none-any.whl", hash = "sha256:ec5411e39152304d2a7e0e426c3058fa37a00860af64e164794e0bcffee813f2", size = 4901, upload-time = "2025-11-14T10:05:51.532Z" }, ] [[package]] name = "pyobjc-framework-usernotifications" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b4/4c/e7e180fcd06c246c37f218bcb01c40ea0213fde5ace3c09d359e60dcaafd/pyobjc_framework_usernotifications-11.1.tar.gz", hash = "sha256:38fc763afa7854b41ddfca8803f679a7305d278af8a7ad02044adc1265699996", size = 55428, upload-time = "2025-06-14T20:58:42.572Z" } +sdist = { url = "https://files.pythonhosted.org/packages/90/cd/e0253072f221fa89a42fe53f1a2650cc9bf415eb94ae455235bd010ee12e/pyobjc_framework_usernotifications-12.1.tar.gz", hash = "sha256:019ccdf2d400f9a428769df7dba4ea97c02453372bc5f8b75ce7ae54dfe130f9", size = 29749, upload-time = "2025-11-14T10:23:05.364Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/bb/ae9c9301a86b7c0c26583c59ac761374cb6928c3d34cae514939e93e44b1/pyobjc_framework_usernotifications-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7140d337dd9dc3635add2177086429fdd6ef24970935b22fffdc5ec7f02ebf60", size = 9599, upload-time = "2025-06-14T20:55:58.051Z" }, - { url = "https://files.pythonhosted.org/packages/03/af/a54e343a7226dc65a65f7a561c060f8c96cb9f92f41ce2242d20d82ae594/pyobjc_framework_usernotifications-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ce6006989fd4a59ec355f6797ccdc9946014ea5241ff7875854799934dbba901", size = 9606, upload-time = "2025-06-14T20:55:59.088Z" }, + { url = "https://files.pythonhosted.org/packages/d1/96/aa25bb0727e661a352d1c52e7288e25c12fe77047f988bb45557c17cf2d7/pyobjc_framework_usernotifications-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c62e8d7153d72c4379071e34258aa8b7263fa59212cfffd2f137013667e50381", size = 9632, upload-time = "2025-11-14T10:05:55.166Z" }, + { url = "https://files.pythonhosted.org/packages/61/ad/c95053a475246464cba686e16269b0973821601910d1947d088b855a8dac/pyobjc_framework_usernotifications-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:412afb2bf5fe0049f9c4e732e81a8a35d5ebf97c30a5a6abd276259d020c82ac", size = 9644, upload-time = "2025-11-14T10:05:56.801Z" }, ] [[package]] name = "pyobjc-framework-usernotificationsui" -version = "11.1" +version = "12.1" 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'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d2/c4/03d97bd3adcee9b857533cb42967df0d019f6a034adcdbcfca2569d415b2/pyobjc_framework_usernotificationsui-11.1.tar.gz", hash = "sha256:18e0182bddd10381884530d6a28634ebb3280912592f8f2ad5bac2a9308c6a65", size = 14123, upload-time = "2025-06-14T20:58:43.267Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0e/03/73e29fd5e5973cb3800c9d56107c1062547ef7524cbcc757c3cbbd5465c6/pyobjc_framework_usernotificationsui-12.1.tar.gz", hash = "sha256:51381c97c7344099377870e49ed0871fea85ba50efe50ab05ccffc06b43ec02e", size = 13125, upload-time = "2025-11-14T10:23:07.259Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9d/2c/0bb489b5ac4daf83b113018701ce30a0cb4bf47c615c92c5844a16e0a012/pyobjc_framework_usernotificationsui-11.1-py2.py3-none-any.whl", hash = "sha256:b84d73d90ab319acf8fad5c59b7a5e2b6023fbb2efd68c58b532e3b3b52f647a", size = 3914, upload-time = "2025-06-14T20:56:03.978Z" }, + { url = "https://files.pythonhosted.org/packages/23/c8/52ac8a879079c1fbf25de8335ff506f7db87ff61e64838b20426f817f5d5/pyobjc_framework_usernotificationsui-12.1-py2.py3-none-any.whl", hash = "sha256:11af59dc5abfcb72c08769ab4d7ca32a628527a8ba341786431a0d2dacf31605", size = 3933, upload-time = "2025-11-14T10:06:05.478Z" }, ] [[package]] name = "pyobjc-framework-videosubscriberaccount" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/aa/00/cd9d93d06204bbb7fe68fb97022b0dd4ecdf8af3adb6d70a41e22c860d55/pyobjc_framework_videosubscriberaccount-11.1.tar.gz", hash = "sha256:2dd78586260fcee51044e129197e8bf2e157176e02babeec2f873afa4235d8c6", size = 28856, upload-time = "2025-06-14T20:58:43.903Z" } +sdist = { url = "https://files.pythonhosted.org/packages/10/f8/27927a9c125c622656ee5aada4596ccb8e5679da0260742360f193df6dcf/pyobjc_framework_videosubscriberaccount-12.1.tar.gz", hash = "sha256:750459fa88220ab83416f769f2d5d210a1f77b8938fa4d119aad0002fc32846b", size = 18793, upload-time = "2025-11-14T10:23:09.33Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4b/dc/b409dee6dd58a5db2e9a681bde8894c9715468689f18e040f7d252794c3d/pyobjc_framework_videosubscriberaccount-11.1-py2.py3-none-any.whl", hash = "sha256:d5a95ae9f2a6f0180a5bbb10e76c064f0fd327aae00a2fe90aa7b65ed4dad7ef", size = 4695, upload-time = "2025-06-14T20:56:06.027Z" }, + { url = "https://files.pythonhosted.org/packages/41/ca/e2f982916267508c1594f1e50d27bf223a24f55a5e175ab7d7822a00997c/pyobjc_framework_videosubscriberaccount-12.1-py2.py3-none-any.whl", hash = "sha256:381a5e8a3016676e52b88e38b706559fa09391d33474d8a8a52f20a883104a7b", size = 4825, upload-time = "2025-11-14T10:06:07.027Z" }, ] [[package]] name = "pyobjc-framework-videotoolbox" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, @@ -4177,29 +4232,29 @@ dependencies = [ { name = "pyobjc-framework-coremedia", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e5/e3/df9096f54ae1f27cab8f922ee70cbda5d80f8c1d12734c38580829858133/pyobjc_framework_videotoolbox-11.1.tar.gz", hash = "sha256:a27985656e1b639cdb102fcc727ebc39f71bb1a44cdb751c8c80cc9fe938f3a9", size = 88551, upload-time = "2025-06-14T20:58:44.566Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/5f/6995ee40dc0d1a3460ee183f696e5254c0ad14a25b5bc5fd9bd7266c077b/pyobjc_framework_videotoolbox-12.1.tar.gz", hash = "sha256:7adc8670f3b94b086aed6e86c3199b388892edab4f02933c2e2d9b1657561bef", size = 57825, upload-time = "2025-11-14T10:23:13.825Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/41/fda951f1c734a68d7bf46ecc03bfff376a690ad771029c4289ba0423a52e/pyobjc_framework_videotoolbox-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:94c17bffe0f4692db2e7641390dfdcd0f73ddbb0afa6c81ef504219be0777930", size = 17325, upload-time = "2025-06-14T20:56:07.719Z" }, - { url = "https://files.pythonhosted.org/packages/1f/cf/569babadbf1f9598f62c400ee02da19d4ab5f36276978c81080999399df9/pyobjc_framework_videotoolbox-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c55285c3c78183fd2a092d582e30b562777a82985cccca9e7e99a0aff2601591", size = 17432, upload-time = "2025-06-14T20:56:08.457Z" }, + { url = "https://files.pythonhosted.org/packages/1e/42/53d57b09fd4879988084ec0d9b74c645c9fdd322be594c9601f6cf265dd0/pyobjc_framework_videotoolbox-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a1eb1eb41c0ffdd8dcc6a9b68ab2b5bc50824a85820c8a7802a94a22dfbb4f91", size = 18781, upload-time = "2025-11-14T10:06:11.89Z" }, + { url = "https://files.pythonhosted.org/packages/94/a5/91c6c95416f41c412c2079950527cb746c0712ec319c51a6c728c8d6b231/pyobjc_framework_videotoolbox-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eb6ce6837344ee319122066c16ada4beb913e7bfd62188a8d14b1ecbb5a89234", size = 18908, upload-time = "2025-11-14T10:06:14.087Z" }, ] [[package]] name = "pyobjc-framework-virtualization" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f1/ff/57214e8f42755eeaad516a7e673dae4341b8742005d368ecc22c7a790b0b/pyobjc_framework_virtualization-11.1.tar.gz", hash = "sha256:4221ee5eb669e43a2ff46e04178bec149af2d65205deb5d4db5fa62ea060e022", size = 78633, upload-time = "2025-06-14T20:58:45.358Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/6a/9d110b5521d9b898fad10928818c9f55d66a4af9ac097426c65a9878b095/pyobjc_framework_virtualization-12.1.tar.gz", hash = "sha256:e96afd8e801e92c6863da0921e40a3b68f724804f888bce43791330658abdb0f", size = 40682, upload-time = "2025-11-14T10:23:17.456Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/64/8b/5eeabfd08d5e6801010496969c1b67517bbda348ff0578ca5f075aa58926/pyobjc_framework_virtualization-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c2a812da4c995e1f8076678130d0b0a63042aa48219f8fb43b70e13eabcbdbc2", size = 13054, upload-time = "2025-06-14T20:56:13.866Z" }, - { url = "https://files.pythonhosted.org/packages/c8/4f/fe1930f4ce2c7d2f4c34bb53adf43f412bc91364e8e4cb450a7c8a6b8b59/pyobjc_framework_virtualization-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:59df6702b3e63200752be7d9c0dc590cb4c3b699c886f9a8634dd224c74b3c3c", size = 13084, upload-time = "2025-06-14T20:56:14.617Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ee/e18d0d9014c42758d7169144acb2d37eb5ff19bf959db74b20eac706bd8c/pyobjc_framework_virtualization-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a88a307dc96885afc227ceda4067f1af787f024063f4ccf453d59e7afd47cda8", size = 13099, upload-time = "2025-11-14T10:06:27.403Z" }, + { url = "https://files.pythonhosted.org/packages/c6/f2/0da47e91f3f8eeda9a8b4bb0d3a0c54a18925009e99b66a8226b9e06ce1e/pyobjc_framework_virtualization-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7d5724b38e64b39ab5ec3b45993afa29fc88b307d99ee2c7a1c0fd770e9b4b21", size = 13131, upload-time = "2025-11-14T10:06:29.337Z" }, ] [[package]] name = "pyobjc-framework-vision" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, @@ -4207,47 +4262,48 @@ dependencies = [ { name = "pyobjc-framework-coreml", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/40/a8/7128da4d0a0103cabe58910a7233e2f98d18c590b1d36d4b3efaaedba6b9/pyobjc_framework_vision-11.1.tar.gz", hash = "sha256:26590512ee7758da3056499062a344b8a351b178be66d4b719327884dde4216b", size = 133721, upload-time = "2025-06-14T20:58:46.095Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c2/5a/08bb3e278f870443d226c141af14205ff41c0274da1e053b72b11dfc9fb2/pyobjc_framework_vision-12.1.tar.gz", hash = "sha256:a30959100e85dcede3a786c544e621ad6eb65ff6abf85721f805822b8c5fe9b0", size = 59538, upload-time = "2025-11-14T10:23:21.979Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/10/69/a745a5491d7af6034ac9e0d627e7b41b42978df0a469b86cdf372ba8917f/pyobjc_framework_vision-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bfbde43c9d4296e1d26548b6d30ae413e2029425968cd8bce96d3c5a735e8f2c", size = 21657, upload-time = "2025-06-14T20:56:20.265Z" }, - { url = "https://files.pythonhosted.org/packages/a2/b5/54c0227a695557ea3065bc035b20a5c256f6f3b861e095eee1ec4b4d8cee/pyobjc_framework_vision-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:df076c3e3e672887182953efc934c1f9683304737e792ec09a29bfee90d2e26a", size = 16829, upload-time = "2025-06-14T20:56:21.355Z" }, + { url = "https://files.pythonhosted.org/packages/bd/37/e30cf4eef2b4c7e20ccadc1249117c77305fbc38b2e5904eb42e3753f63c/pyobjc_framework_vision-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1edbf2fc18ce3b31108f845901a88f2236783ae6bf0bc68438d7ece572dc2a29", size = 21432, upload-time = "2025-11-14T10:06:42.373Z" }, + { url = "https://files.pythonhosted.org/packages/3a/5a/23502935b3fc877d7573e743fc3e6c28748f33a45c43851d503bde52cde7/pyobjc_framework_vision-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:6b3211d84f3a12aad0cde752cfd43a80d0218960ac9e6b46b141c730e7d655bd", size = 16625, upload-time = "2025-11-14T10:06:44.422Z" }, ] [[package]] name = "pyobjc-framework-webkit" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/92/04/fb3d0b68994f7e657ef00c1ac5fc1c04ae2fc7ea581d647f5ae1f6739b14/pyobjc_framework_webkit-11.1.tar.gz", hash = "sha256:27e701c7aaf4f24fc7e601a128e2ef14f2773f4ab071b9db7438dc5afb5053ae", size = 717102, upload-time = "2025-06-14T20:58:47.461Z" } +sdist = { url = "https://files.pythonhosted.org/packages/14/10/110a50e8e6670765d25190ca7f7bfeecc47ec4a8c018cb928f4f82c56e04/pyobjc_framework_webkit-12.1.tar.gz", hash = "sha256:97a54dd05ab5266bd4f614e41add517ae62cdd5a30328eabb06792474b37d82a", size = 284531, upload-time = "2025-11-14T10:23:40.287Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8d/b6/d62c01a83c22619edf2379a6941c9f6b7aee01c565b9c1170696f85cba95/pyobjc_framework_webkit-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:10ec89d727af8f216ba5911ff5553f84a5b660f5ddf75b07788e3a439c281165", size = 51406, upload-time = "2025-06-14T20:56:26.845Z" }, - { url = "https://files.pythonhosted.org/packages/8a/7e/fa2c18c0c0f9321e5036e54b9da7a196956b531e50fe1a76e7dfdbe8fac2/pyobjc_framework_webkit-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1a6e6f64ca53c4953f17e808ecac11da288d9a6ade738156ba161732a5e0c96a", size = 51464, upload-time = "2025-06-14T20:56:27.653Z" }, + { url = "https://files.pythonhosted.org/packages/e5/37/5082a0bbe12e48d4ffa53b0c0f09c77a4a6ffcfa119e26fa8dd77c08dc1c/pyobjc_framework_webkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3db734877025614eaef4504fadc0fbbe1279f68686a6f106f2e614e89e0d1a9d", size = 49970, upload-time = "2025-11-14T10:07:01.413Z" }, + { url = "https://files.pythonhosted.org/packages/db/67/64920c8d201a7fc27962f467c636c4e763b43845baba2e091a50a97a5d52/pyobjc_framework_webkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:af2c7197447638b92aafbe4847c063b6dd5e1ed83b44d3ce7e71e4c9b042ab5a", size = 50084, upload-time = "2025-11-14T10:07:05.868Z" }, ] [[package]] name = "pyopencl" -version = "2025.1" +version = "2025.2.7" 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" }, + { name = "typing-extensions" }, ] -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" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/cb/8927052160bc0d3bd1123a645aaf57f696da364216b57b49f92ba0777bcc/pyopencl-2025.2.7.tar.gz", hash = "sha256:a68d92eb2970418b1a7ca45aff71984c02d2e4261e01402b273f257b5d6d7511", size = 444787, upload-time = "2025-10-28T14:23:15.497Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/99/ce/c40c6248b29195397a6e176615c24a8047cdd3afe847932a1f27603c1b14/pyopencl-2025.1-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:a302e4ee1bb19ff244f5ae2b5a83a98977daa13f31929a85f23723020a4bec01", size = 424117, upload-time = "2025-01-22T00:16:08.957Z" }, - { url = "https://files.pythonhosted.org/packages/71/dd/8dd4e18396c705567be7eda65234932f8eb7e975cc15ae167265ed9c7d20/pyopencl-2025.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:48527fc5a250b9e89f2eaaa1c9423e1bc15ff181bd41ffa1e29e31890dc1d3b7", size = 408327, upload-time = "2025-01-22T00:16:10.42Z" }, - { url = "https://files.pythonhosted.org/packages/47/7b/270c4e6765b675eaa97af8ff0c964e21dc74ac2a2f04e2c24431c91ce382/pyopencl-2025.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95490199c490e47b245b88e64e06f95272502d13810da172ac3b0f0340cf8715", size = 724636, upload-time = "2025-01-22T00:16:12.881Z" }, - { url = "https://files.pythonhosted.org/packages/0d/55/996d7877793acfc340678a71dc98151a8c39dbb289cf24ecae08c0af68eb/pyopencl-2025.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cd2cb3c031beeec93cf660c8809d6ad58a2cde7dcd95ae12b4b2da6ac8e2da41", size = 1173429, upload-time = "2025-01-22T00:16:14.466Z" }, - { url = "https://files.pythonhosted.org/packages/3d/7c/d2a89b1c24c318375856e8b7611bc03ddf687134f68ddbb387496453eda8/pyopencl-2025.1-cp311-cp311-win_amd64.whl", hash = "sha256:d8d3cdb02dc8750a9cc11c5b37f219da1f61b4216af4a830eb52b451a0e9f9a7", size = 457877, upload-time = "2025-01-22T00:16:17.21Z" }, - { url = "https://files.pythonhosted.org/packages/02/c0/d9536211ecfddd3bdf7eaa1658d085fcd92120061ac6f4e662a5062660ff/pyopencl-2025.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:88c564d94a5067ab6b9429a7d92b655254da8b2b5a33c7e30e10c5a0cf3154e1", size = 425706, upload-time = "2025-01-22T00:16:18.771Z" }, - { url = "https://files.pythonhosted.org/packages/63/b9/3e6dd574cc9ffb2271be135ecdb36cd6aea70a1f74e80539b048072a256a/pyopencl-2025.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f204cd6974ca19e7ffe14f21afac9967b06a6718f3aecbbc4a4df313e8e7ebc2", size = 408163, upload-time = "2025-01-22T00:16:20.282Z" }, - { url = "https://files.pythonhosted.org/packages/a4/4d/7f6f2e24b12585b81fd49f689d21ba62187656d061e3cb43840f12097dad/pyopencl-2025.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dce8d1b3fd2046e89377b754117fdb3505f45edacfda6ad2b3a29670c0526ad1", size = 719348, upload-time = "2025-01-22T00:16:22.15Z" }, - { url = "https://files.pythonhosted.org/packages/f0/45/3c93510819859e047d494dd8f4ed80c26378bb964a8e5e850cc079cc1f6e/pyopencl-2025.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fbb0b7b775424f0c4c929a00f09eb351075ea9e4d2b1c80afe37d09bec218ee9", size = 1170733, upload-time = "2025-01-22T00:16:24.575Z" }, - { url = "https://files.pythonhosted.org/packages/91/ba/b745715085ef893fa7d1c7e04c95e3e554b6b27b2fb0800d6bbca563b43c/pyopencl-2025.1-cp312-cp312-win_amd64.whl", hash = "sha256:78f2a58d2e177793fb5a7b9c8a574e3a5f1d178c4df713969d1b08341c817d60", size = 457762, upload-time = "2025-01-22T00:16:27.334Z" }, + { url = "https://files.pythonhosted.org/packages/48/ec/5e511d47fa0d5ad576cd259ceffe4662d6e617dc716044f1ead0d1ba29da/pyopencl-2025.2.7-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:359b484989e9caf532d73206d83422bc0cb8a0e4708c7feeb1f9115752ecda1c", size = 445658, upload-time = "2025-10-28T14:22:41.047Z" }, + { url = "https://files.pythonhosted.org/packages/bd/04/bba1d1ba36ba57eed24ee5407cd2172414792c0a6e89fbf9cc0591e0ba9d/pyopencl-2025.2.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3749a54e124ab72f2cb656aa4049befd300ac31f2125d03c8a1ec42c10f6c22e", size = 427841, upload-time = "2025-10-28T14:22:42.378Z" }, + { url = "https://files.pythonhosted.org/packages/93/cd/ae3ee576c3f296be713e63b2088fdf217ea3c90d7957bbe5bf2ee3165d6a/pyopencl-2025.2.7-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5244cd402f4411832599f3891b557a4be4c3ce98d0165111ab8f8f3638898b94", size = 734543, upload-time = "2025-10-28T14:22:43.614Z" }, + { url = "https://files.pythonhosted.org/packages/d7/30/321d4d174af5e58122e381afac9ce851e9f84f9b7d6502708913a6a1ef8b/pyopencl-2025.2.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3c25a874f94cd2126bc526e22e94a2e4def7c82ebd095fed7e3b5708332d2391", size = 1227048, upload-time = "2025-10-28T14:22:44.947Z" }, + { url = "https://files.pythonhosted.org/packages/20/28/7363bc9b124ad5b4fa6c4bcb0b4fc2a8e213e85e3c192af8c31c320796f2/pyopencl-2025.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:0c9989080124f27f44df86f048e55ecd3e376e20933e470cc9c14506c82ee5dd", size = 472884, upload-time = "2025-10-28T14:22:47.232Z" }, + { url = "https://files.pythonhosted.org/packages/f9/04/e3a17fa79266140246c6193934c0526185f31ffbe45f4ecd6a41ba58c42d/pyopencl-2025.2.7-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:3525c7e35369d505b8a6141f4dfcdcc78a9714c0c30f37e82834906a678c992a", size = 447013, upload-time = "2025-10-28T14:22:48.442Z" }, + { url = "https://files.pythonhosted.org/packages/c5/3b/b2d3c4e69063b53ea6728f09dee7220c780e040b159b21e5d5a369c877fe/pyopencl-2025.2.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:91ab33d7844c2712c65f1881dbf10755e76010db01b1b9d041eb3b947a29f994", size = 427543, upload-time = "2025-10-28T14:22:49.956Z" }, + { url = "https://files.pythonhosted.org/packages/24/fd/8f1042308f3369c653443e9b461156cb28f805da41889a34788dbcaf6470/pyopencl-2025.2.7-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:386c1cdad727df58b34ad6026cf3fe1a2254474977a8afdf8d40eb6daf8806bc", size = 732584, upload-time = "2025-10-28T14:22:51.613Z" }, + { url = "https://files.pythonhosted.org/packages/24/2f/7413a39eb56e3f9130232a2964e50ef72b5d6a351af7624225d185c1ed11/pyopencl-2025.2.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5ce3643e6367d2c7362a33df4f1d676068ae45db3721bf8aee7d7c60c73065e7", size = 1223000, upload-time = "2025-10-28T14:22:52.976Z" }, + { url = "https://files.pythonhosted.org/packages/08/70/66b371eb8fa3867d139df5e6b7107b611eb1cc8231b7755101f589dda66f/pyopencl-2025.2.7-cp312-cp312-win_amd64.whl", hash = "sha256:0791a7303236f53bcc99ae87515452b2e9cb0303949212cd5e115c4889a44f06", size = 472938, upload-time = "2025-10-28T14:22:54.689Z" }, ] [[package]] @@ -4264,20 +4320,20 @@ wheels = [ [[package]] name = "pyparsing" -version = "3.2.5" +version = "3.3.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f2/a5/181488fc2b9d093e3972d2a472855aae8a03f000592dbfce716a512b3359/pyparsing-3.2.5.tar.gz", hash = "sha256:2df8d5b7b2802ef88e8d016a2eb9c7aeaa923529cd251ed0fe4608275d4105b6", size = 1099274, upload-time = "2025-09-21T04:11:06.277Z" } +sdist = { url = "https://files.pythonhosted.org/packages/33/c1/1d9de9aeaa1b89b0186e5fe23294ff6517fce1bc69149185577cd31016b2/pyparsing-3.3.1.tar.gz", hash = "sha256:47fad0f17ac1e2cad3de3b458570fbc9b03560aa029ed5e16ee5554da9a2251c", size = 1550512, upload-time = "2025-12-23T03:14:04.391Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl", hash = "sha256:e38a4f02064cf41fe6593d328d0512495ad1f3d8a91c4f73fc401b3079a59a5e", size = 113890, upload-time = "2025-09-21T04:11:04.117Z" }, + { url = "https://files.pythonhosted.org/packages/8b/40/2614036cdd416452f5bf98ec037f38a1afb17f327cb8e6b652d4729e0af8/pyparsing-3.3.1-py3-none-any.whl", hash = "sha256:023b5e7e5520ad96642e2c6db4cb683d3970bd640cdf7115049a6e9c3682df82", size = 121793, upload-time = "2025-12-23T03:14:02.103Z" }, ] [[package]] name = "pyperclip" -version = "1.10.0" +version = "1.11.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/15/99/25f4898cf420efb6f45f519de018f4faea5391114a8618b16736ef3029f1/pyperclip-1.10.0.tar.gz", hash = "sha256:180c8346b1186921c75dfd14d9048a6b5d46bfc499778811952c6dd6eb1ca6be", size = 12193, upload-time = "2025-09-18T00:54:00.384Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/52/d87eba7cb129b81563019d1679026e7a112ef76855d6159d24754dbd2a51/pyperclip-1.11.0.tar.gz", hash = "sha256:244035963e4428530d9e3a6101a1ef97209c6825edab1567beac148ccc1db1b6", size = 12185, upload-time = "2025-09-26T14:40:37.245Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/bc/22540e73c5f5ae18f02924cd3954a6c9a4aa6b713c841a94c98335d333a1/pyperclip-1.10.0-py3-none-any.whl", hash = "sha256:596fbe55dc59263bff26e61d2afbe10223e2fccb5210c9c96a28d6887cfcc7ec", size = 11062, upload-time = "2025-09-18T00:53:59.252Z" }, + { url = "https://files.pythonhosted.org/packages/df/80/fc9d01d5ed37ba4c42ca2b55b4339ae6e200b456be3a1aaddf4a9fa99b8c/pyperclip-1.11.0-py3-none-any.whl", hash = "sha256:299403e9ff44581cb9ba2ffeed69c7aa96a008622ad0c46cb575ca75b5b84273", size = 11063, upload-time = "2025-09-26T14:40:36.069Z" }, ] [[package]] @@ -4312,7 +4368,7 @@ wheels = [ [[package]] name = "pytest" -version = "8.4.2" +version = "9.0.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -4321,22 +4377,22 @@ dependencies = [ { name = "pluggy" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, ] [[package]] name = "pytest-asyncio" -version = "1.2.0" +version = "1.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pytest" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/42/86/9e3c5f48f7b7b638b216e4b9e645f54d199d7abbbab7a64a13b4e12ba10f/pytest_asyncio-1.2.0.tar.gz", hash = "sha256:c609a64a2a8768462d0c99811ddb8bd2583c33fd33cf7f21af1c142e824ffb57", size = 50119, upload-time = "2025-09-12T07:33:53.816Z" } +sdist = { url = "https://files.pythonhosted.org/packages/90/2c/8af215c0f776415f3590cac4f9086ccefd6fd463befeae41cd4d3f193e5a/pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5", size = 50087, upload-time = "2025-11-10T16:07:47.256Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/04/93/2fa34714b7a4ae72f2f8dad66ba17dd9a2c793220719e736dda28b7aec27/pytest_asyncio-1.2.0-py3-none-any.whl", hash = "sha256:8e17ae5e46d8e7efe51ab6494dd2010f4ca8dae51652aa3c8d55acf50bfb2e99", size = 15095, upload-time = "2025-09-12T07:33:52.639Z" }, + { url = "https://files.pythonhosted.org/packages/e5/35/f8b19922b6a25bc0880171a2f1a003eaeb93657475193ab516fd87cac9da/pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5", size = 15075, upload-time = "2025-11-10T16:07:45.537Z" }, ] [[package]] @@ -4389,15 +4445,15 @@ wheels = [ [[package]] name = "pytest-subtests" -version = "0.14.2" +version = "0.15.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "attrs" }, { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/59/30/6ec8dfc678ddfd1c294212bbd7088c52d3f7fbf3f05e6d8a440c13b9741a/pytest_subtests-0.14.2.tar.gz", hash = "sha256:7154a8665fd528ee70a76d00216a44d139dc3c9c83521a0f779f7b0ad4f800de", size = 18083, upload-time = "2025-06-13T10:50:01.636Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bb/d9/20097971a8d315e011e055d512fa120fd6be3bdb8f4b3aa3e3c6bf77bebc/pytest_subtests-0.15.0.tar.gz", hash = "sha256:cb495bde05551b784b8f0b8adfaa27edb4131469a27c339b80fd8d6ba33f887c", size = 18525, upload-time = "2025-10-20T16:26:18.358Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/47/d4/9bf12e59fb882b0cf4f993871e1adbee094802224c429b00861acee1a169/pytest_subtests-0.14.2-py3-none-any.whl", hash = "sha256:8da0787c994ab372a13a0ad7d390533ad2e4385cac167b3ac501258c885d0b66", size = 9115, upload-time = "2025-06-13T10:50:00.543Z" }, + { url = "https://files.pythonhosted.org/packages/23/64/bba465299b37448b4c1b84c7a04178399ac22d47b3dc5db1874fe55a2bd3/pytest_subtests-0.15.0-py3-none-any.whl", hash = "sha256:da2d0ce348e1f8d831d5a40d81e3aeac439fec50bd5251cbb7791402696a9493", size = 9185, upload-time = "2025-10-20T16:26:17.239Z" }, ] [[package]] @@ -4456,9 +4512,9 @@ name = "pytools" version = "2025.2.5" 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/c3/7b/f885a57e61ded45b5b10ca60f0b7575c9fb9a282e7513d0e23a33ee647e1/pytools-2025.2.5.tar.gz", hash = "sha256:a7f5350644d46d98ee9c7e67b4b41693308aa0f5e9b188d8f0694b27dc94e3a2", size = 85594, upload-time = "2025-10-07T15:53:30.49Z" } wheels = [ @@ -4518,28 +4574,29 @@ wheels = [ [[package]] name = "pyyaml" -version = "6.0.2" +version = "6.0.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612, upload-time = "2024-08-06T20:32:03.408Z" }, - { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040, upload-time = "2024-08-06T20:32:04.926Z" }, - { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829, upload-time = "2024-08-06T20:32:06.459Z" }, - { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167, upload-time = "2024-08-06T20:32:08.338Z" }, - { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952, upload-time = "2024-08-06T20:32:14.124Z" }, - { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301, upload-time = "2024-08-06T20:32:16.17Z" }, - { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638, upload-time = "2024-08-06T20:32:18.555Z" }, - { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850, upload-time = "2024-08-06T20:32:19.889Z" }, - { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980, upload-time = "2024-08-06T20:32:21.273Z" }, - { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873, upload-time = "2024-08-06T20:32:25.131Z" }, - { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302, upload-time = "2024-08-06T20:32:26.511Z" }, - { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154, upload-time = "2024-08-06T20:32:28.363Z" }, - { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223, upload-time = "2024-08-06T20:32:30.058Z" }, - { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542, upload-time = "2024-08-06T20:32:31.881Z" }, - { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164, upload-time = "2024-08-06T20:32:37.083Z" }, - { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611, upload-time = "2024-08-06T20:32:38.898Z" }, - { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591, upload-time = "2024-08-06T20:32:40.241Z" }, - { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338, upload-time = "2024-08-06T20:32:41.93Z" }, + { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, + { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, + { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, + { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, + { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, + { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, ] [[package]] @@ -4649,97 +4706,99 @@ wheels = [ [[package]] name = "ruamel-yaml" -version = "0.18.15" +version = "0.18.17" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "ruamel-yaml-clib", marker = "platform_python_implementation == 'CPython'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3e/db/f3950f5e5031b618aae9f423a39bf81a55c148aecd15a34527898e752cf4/ruamel.yaml-0.18.15.tar.gz", hash = "sha256:dbfca74b018c4c3fba0b9cc9ee33e53c371194a9000e694995e620490fd40700", size = 146865, upload-time = "2025-08-19T11:15:10.694Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3a/2b/7a1f1ebcd6b3f14febdc003e658778d81e76b40df2267904ee6b13f0c5c6/ruamel_yaml-0.18.17.tar.gz", hash = "sha256:9091cd6e2d93a3a4b157ddb8fabf348c3de7f1fb1381346d985b6b247dcd8d3c", size = 149602, upload-time = "2025-12-17T20:02:55.757Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/e5/f2a0621f1781b76a38194acae72f01e37b1941470407345b6e8653ad7640/ruamel.yaml-0.18.15-py3-none-any.whl", hash = "sha256:148f6488d698b7a5eded5ea793a025308b25eca97208181b6a026037f391f701", size = 119702, upload-time = "2025-08-19T11:15:07.696Z" }, + { url = "https://files.pythonhosted.org/packages/af/fe/b6045c782f1fd1ae317d2a6ca1884857ce5c20f59befe6ab25a8603c43a7/ruamel_yaml-0.18.17-py3-none-any.whl", hash = "sha256:9c8ba9eb3e793efdf924b60d521820869d5bf0cb9c6f1b82d82de8295e290b9d", size = 121594, upload-time = "2025-12-17T20:02:07.657Z" }, ] [[package]] name = "ruamel-yaml-clib" -version = "0.2.13" +version = "0.2.15" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3c/fd/32c91c4d301ebe7931a3c44f7593650613afe14854e3cc9d2764321b7a46/ruamel.yaml.clib-0.2.13.tar.gz", hash = "sha256:8d4f8d7853053a5a19171a0f515f1e14f503bd3e772c4da6bafe7d0893d4f299", size = 201264, upload-time = "2025-09-22T13:33:47.268Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ea/97/60fda20e2fb54b83a61ae14648b0817c8f5d84a3821e40bfbdae1437026a/ruamel_yaml_clib-0.2.15.tar.gz", hash = "sha256:46e4cc8c43ef6a94885f72512094e482114a8a706d3c555a34ed4b0d20200600", size = 225794, upload-time = "2025-11-16T16:12:59.761Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bb/86/fa6be53f11d1c663a261d137d616bbb326cb937b361a55a00cacf9ba76aa/ruamel.yaml.clib-0.2.13-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:825a7483da63b9448fdad9778269a5d02e5f64040aa8326fec071e830c2fc49c", size = 136894, upload-time = "2025-09-22T13:32:56.684Z" }, - { url = "https://files.pythonhosted.org/packages/70/f1/d26dbf3c53befd01d8b341cb29c555c1e910ef29478b4ebd50d1d67fbc64/ruamel.yaml.clib-0.2.13-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:54707e2200b9e1c34a62af09edd3ea3469a722951365311398458abe36ff45d6", size = 640033, upload-time = "2025-09-22T13:33:00.01Z" }, - { url = "https://files.pythonhosted.org/packages/a8/80/119ce9e40690b7e0dfc6bec41369333593f8738fa4f58dbbffdb9b80339b/ruamel.yaml.clib-0.2.13-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:958b1fc6334cd745db7099a69da4f60503cfbbc05ce1412b1d06c5922cd8314b", size = 738065, upload-time = "2025-09-22T13:32:57.712Z" }, - { url = "https://files.pythonhosted.org/packages/0b/85/09625df6b3ccf0ae0ddc93c0ab4d89f785debc0ae1e7efa541696b788b6a/ruamel.yaml.clib-0.2.13-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d50c415df5ba918c483a2486a90dd5a3e6df634fca688f71d266e15a618d643f", size = 700721, upload-time = "2025-09-22T13:32:58.881Z" }, - { url = "https://files.pythonhosted.org/packages/e0/2e/c05050760b20fe10cb17b3fe7efd5d5653baefa862121f5b0825418f3d8b/ruamel.yaml.clib-0.2.13-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bbdb57e57a5bb3510bfb43496bab44857546c56c7f04bb7bfd0b248388d41c2e", size = 641589, upload-time = "2025-09-22T13:33:01.482Z" }, - { url = "https://files.pythonhosted.org/packages/d7/e0/6cfd5c61070f0e5556a724c753919b8758b86fe89f5b53eb9f53d0506b27/ruamel.yaml.clib-0.2.13-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:df8ef03eb60fc2e1c0570612363091898b97a7b3181422e1b34c3ee22d7e0a9e", size = 743806, upload-time = "2025-09-22T13:33:02.682Z" }, - { url = "https://files.pythonhosted.org/packages/85/01/14964bd94bbe809497d1affc0625e428803cc9fd21e145b1b2edcbbc3c92/ruamel.yaml.clib-0.2.13-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:89168de27694f7903a1317381525efdf7ea2c377b5b1eeeb927940870a7e8945", size = 769530, upload-time = "2025-09-22T13:33:04.044Z" }, - { url = "https://files.pythonhosted.org/packages/1d/c0/a39cc7de38b5acc81241dda9888ecabc1ee90fb3ebf9189ddfc65c08555d/ruamel.yaml.clib-0.2.13-cp311-cp311-win32.whl", hash = "sha256:8cdb4c720137bb7834cf2c09753d5f3468023eebb7f6ad2604ddf920328753dd", size = 100254, upload-time = "2025-09-22T13:33:06.243Z" }, - { url = "https://files.pythonhosted.org/packages/1f/7f/d5c1e0279df8dd9ca92138bec8387a07112d97598f668ccb3190d0bc06aa/ruamel.yaml.clib-0.2.13-cp311-cp311-win_amd64.whl", hash = "sha256:670d8a9c5b22af152863236b7a7fec471aafefc5e89b637b8f98d280cb92a0ce", size = 118235, upload-time = "2025-09-22T13:33:05.202Z" }, - { url = "https://files.pythonhosted.org/packages/50/ae/b6b3ed185206af04c779eec77a4a57587278b713b72034957d127307bbef/ruamel.yaml.clib-0.2.13-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:810500056a0e294131042eca6687a786e82aad817e8e0bc5ce059d2f95b67fef", size = 137955, upload-time = "2025-09-22T13:33:07.585Z" }, - { url = "https://files.pythonhosted.org/packages/6f/2b/b0307fc587cebabd8faaaeb1476c825fe4dbb5ad2b5c152dfbcc31b258c4/ruamel.yaml.clib-0.2.13-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:f8e44b1b583f93a8f4d00e5285d9ea8ccd9524cdd4c54788db2880a11f21287d", size = 645916, upload-time = "2025-09-22T13:33:11.619Z" }, - { url = "https://files.pythonhosted.org/packages/74/a9/2ddad6eb03195206aca765b4ff7bd7098c46fbc65957d5ac53f96ef8d6f4/ruamel.yaml.clib-0.2.13-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d1ab027be86f7d5ccc9b777a22194fd3850012df413f216bf1608768b4daa2f", size = 753115, upload-time = "2025-09-22T13:33:08.793Z" }, - { url = "https://files.pythonhosted.org/packages/42/a0/ded38d60fc34f69afef6f3e379f896a80d34532bb6b4828e95f7003be610/ruamel.yaml.clib-0.2.13-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:55f01d82d7d96213f0963609876ae841c81e217985c668100c5fb95cc9a564b3", size = 703451, upload-time = "2025-09-22T13:33:10.111Z" }, - { url = "https://files.pythonhosted.org/packages/3e/49/121067d5621a77913dc0fd473bdf2ec4a515c564806b107d3b1257cd5b14/ruamel.yaml.clib-0.2.13-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:031c7cdd5751cda19ddf90e42756ec2ee72a30791c2f65f5f800f9ee929862f0", size = 647403, upload-time = "2025-09-22T13:33:12.778Z" }, - { url = "https://files.pythonhosted.org/packages/15/fc/0845929db1840eec6aa6133b8c89941e251c3bc67180aee7d71a30bc1c80/ruamel.yaml.clib-0.2.13-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ba745411caabc994963729663a7c2d09398795c36dc4b5672ca4fdc445ab6544", size = 745860, upload-time = "2025-09-22T13:33:13.99Z" }, - { url = "https://files.pythonhosted.org/packages/d6/0a/dd0007d41a321edf64c4bda0aab70936281ee213406e6913105499f7d62a/ruamel.yaml.clib-0.2.13-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fb90267cc3748858236b9cedb580191be1d0d218f6cde64e954fa759d90fb08d", size = 770202, upload-time = "2025-09-22T13:33:15.181Z" }, - { url = "https://files.pythonhosted.org/packages/93/0d/eeed0a9824b14fa089a82247fca96de6b202af84e4093ba2a15afc0b4cb9/ruamel.yaml.clib-0.2.13-cp312-cp312-win32.whl", hash = "sha256:5188c3212c10fcd14a6de8ce787c0713a49fc5972054703f790cfd9f1d27a90b", size = 98831, upload-time = "2025-09-22T13:33:17.362Z" }, - { url = "https://files.pythonhosted.org/packages/25/45/c882a32e4b5c0ed6a5b4e0933af427fef48f3aad7259b87f38e33ee20536/ruamel.yaml.clib-0.2.13-cp312-cp312-win_amd64.whl", hash = "sha256:518b13f9fe601a559a759f3d21cdb2590cb85611934a7c0f09c09dc1a869ec83", size = 115567, upload-time = "2025-09-22T13:33:16.358Z" }, + { url = "https://files.pythonhosted.org/packages/2c/80/8ce7b9af532aa94dd83360f01ce4716264db73de6bc8efd22c32341f6658/ruamel_yaml_clib-0.2.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c583229f336682b7212a43d2fa32c30e643d3076178fb9f7a6a14dde85a2d8bd", size = 147998, upload-time = "2025-11-16T16:13:13.241Z" }, + { url = "https://files.pythonhosted.org/packages/53/09/de9d3f6b6701ced5f276d082ad0f980edf08ca67114523d1b9264cd5e2e0/ruamel_yaml_clib-0.2.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:56ea19c157ed8c74b6be51b5fa1c3aff6e289a041575f0556f66e5fb848bb137", size = 132743, upload-time = "2025-11-16T16:13:14.265Z" }, + { url = "https://files.pythonhosted.org/packages/0e/f7/73a9b517571e214fe5c246698ff3ed232f1ef863c8ae1667486625ec688a/ruamel_yaml_clib-0.2.15-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5fea0932358e18293407feb921d4f4457db837b67ec1837f87074667449f9401", size = 731459, upload-time = "2025-11-16T20:22:44.338Z" }, + { url = "https://files.pythonhosted.org/packages/9b/a2/0dc0013169800f1c331a6f55b1282c1f4492a6d32660a0cf7b89e6684919/ruamel_yaml_clib-0.2.15-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef71831bd61fbdb7aa0399d5c4da06bea37107ab5c79ff884cc07f2450910262", size = 749289, upload-time = "2025-11-16T16:13:15.633Z" }, + { url = "https://files.pythonhosted.org/packages/aa/ed/3fb20a1a96b8dc645d88c4072df481fe06e0289e4d528ebbdcc044ebc8b3/ruamel_yaml_clib-0.2.15-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:617d35dc765715fa86f8c3ccdae1e4229055832c452d4ec20856136acc75053f", size = 777630, upload-time = "2025-11-16T16:13:16.898Z" }, + { url = "https://files.pythonhosted.org/packages/60/50/6842f4628bc98b7aa4733ab2378346e1441e150935ad3b9f3c3c429d9408/ruamel_yaml_clib-0.2.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b45498cc81a4724a2d42273d6cfc243c0547ad7c6b87b4f774cb7bcc131c98d", size = 744368, upload-time = "2025-11-16T16:13:18.117Z" }, + { url = "https://files.pythonhosted.org/packages/d3/b0/128ae8e19a7d794c2e36130a72b3bb650ce1dd13fb7def6cf10656437dcf/ruamel_yaml_clib-0.2.15-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:def5663361f6771b18646620fca12968aae730132e104688766cf8a3b1d65922", size = 745233, upload-time = "2025-11-16T20:22:45.833Z" }, + { url = "https://files.pythonhosted.org/packages/75/05/91130633602d6ba7ce3e07f8fc865b40d2a09efd4751c740df89eed5caf9/ruamel_yaml_clib-0.2.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:014181cdec565c8745b7cbc4de3bf2cc8ced05183d986e6d1200168e5bb59490", size = 770963, upload-time = "2025-11-16T16:13:19.344Z" }, + { url = "https://files.pythonhosted.org/packages/fd/4b/fd4542e7f33d7d1bc64cc9ac9ba574ce8cf145569d21f5f20133336cdc8c/ruamel_yaml_clib-0.2.15-cp311-cp311-win32.whl", hash = "sha256:d290eda8f6ada19e1771b54e5706b8f9807e6bb08e873900d5ba114ced13e02c", size = 102640, upload-time = "2025-11-16T16:13:20.498Z" }, + { url = "https://files.pythonhosted.org/packages/bb/eb/00ff6032c19c7537371e3119287999570867a0eafb0154fccc80e74bf57a/ruamel_yaml_clib-0.2.15-cp311-cp311-win_amd64.whl", hash = "sha256:bdc06ad71173b915167702f55d0f3f027fc61abd975bd308a0968c02db4a4c3e", size = 121996, upload-time = "2025-11-16T16:13:21.855Z" }, + { url = "https://files.pythonhosted.org/packages/72/4b/5fde11a0722d676e469d3d6f78c6a17591b9c7e0072ca359801c4bd17eee/ruamel_yaml_clib-0.2.15-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cb15a2e2a90c8475df45c0949793af1ff413acfb0a716b8b94e488ea95ce7cff", size = 149088, upload-time = "2025-11-16T16:13:22.836Z" }, + { url = "https://files.pythonhosted.org/packages/85/82/4d08ac65ecf0ef3b046421985e66301a242804eb9a62c93ca3437dc94ee0/ruamel_yaml_clib-0.2.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:64da03cbe93c1e91af133f5bec37fd24d0d4ba2418eaf970d7166b0a26a148a2", size = 134553, upload-time = "2025-11-16T16:13:24.151Z" }, + { url = "https://files.pythonhosted.org/packages/b9/cb/22366d68b280e281a932403b76da7a988108287adff2bfa5ce881200107a/ruamel_yaml_clib-0.2.15-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f6d3655e95a80325b84c4e14c080b2470fe4f33b6846f288379ce36154993fb1", size = 737468, upload-time = "2025-11-16T20:22:47.335Z" }, + { url = "https://files.pythonhosted.org/packages/71/73/81230babf8c9e33770d43ed9056f603f6f5f9665aea4177a2c30ae48e3f3/ruamel_yaml_clib-0.2.15-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:71845d377c7a47afc6592aacfea738cc8a7e876d586dfba814501d8c53c1ba60", size = 753349, upload-time = "2025-11-16T16:13:26.269Z" }, + { url = "https://files.pythonhosted.org/packages/61/62/150c841f24cda9e30f588ef396ed83f64cfdc13b92d2f925bb96df337ba9/ruamel_yaml_clib-0.2.15-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11e5499db1ccbc7f4b41f0565e4f799d863ea720e01d3e99fa0b7b5fcd7802c9", size = 788211, upload-time = "2025-11-16T16:13:27.441Z" }, + { url = "https://files.pythonhosted.org/packages/30/93/e79bd9cbecc3267499d9ead919bd61f7ddf55d793fb5ef2b1d7d92444f35/ruamel_yaml_clib-0.2.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4b293a37dc97e2b1e8a1aec62792d1e52027087c8eea4fc7b5abd2bdafdd6642", size = 743203, upload-time = "2025-11-16T16:13:28.671Z" }, + { url = "https://files.pythonhosted.org/packages/8d/06/1eb640065c3a27ce92d76157f8efddb184bd484ed2639b712396a20d6dce/ruamel_yaml_clib-0.2.15-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:512571ad41bba04eac7268fe33f7f4742210ca26a81fe0c75357fa682636c690", size = 747292, upload-time = "2025-11-16T20:22:48.584Z" }, + { url = "https://files.pythonhosted.org/packages/a5/21/ee353e882350beab65fcc47a91b6bdc512cace4358ee327af2962892ff16/ruamel_yaml_clib-0.2.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e5e9f630c73a490b758bf14d859a39f375e6999aea5ddd2e2e9da89b9953486a", size = 771624, upload-time = "2025-11-16T16:13:29.853Z" }, + { url = "https://files.pythonhosted.org/packages/57/34/cc1b94057aa867c963ecf9ea92ac59198ec2ee3a8d22a126af0b4d4be712/ruamel_yaml_clib-0.2.15-cp312-cp312-win32.whl", hash = "sha256:f4421ab780c37210a07d138e56dd4b51f8642187cdfb433eb687fe8c11de0144", size = 100342, upload-time = "2025-11-16T16:13:31.067Z" }, + { url = "https://files.pythonhosted.org/packages/b3/e5/8925a4208f131b218f9a7e459c0d6fcac8324ae35da269cb437894576366/ruamel_yaml_clib-0.2.15-cp312-cp312-win_amd64.whl", hash = "sha256:2b216904750889133d9222b7b873c199d48ecbb12912aca78970f84a5aa1a4bc", size = 119013, upload-time = "2025-11-16T16:13:32.164Z" }, ] [[package]] name = "rubicon-objc" -version = "0.5.2" +version = "0.5.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a7/2f/612049b8601e810080f63f4b85acbf2ed0784349088d3c944eb288e1d487/rubicon_objc-0.5.2.tar.gz", hash = "sha256:1180593935f6a8a39c23b5f4b7baa24aedf9f7285e80804a1d9d6b50a50572f5", size = 175710, upload-time = "2025-08-07T06:12:25.194Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4f/d2/d39ecd205661a5c14c90dbd92a722a203848a3621785c9783716341de427/rubicon_objc-0.5.3.tar.gz", hash = "sha256:74c25920c5951a05db9d3a1aac31d23816ec7dacc841a5b124d911b99ea71b9a", size = 171512, upload-time = "2025-12-03T03:51:10.264Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9f/a4/11ad29100060af56408ed084dca76a16d2ce8eb31b75081bfc0eec45d755/rubicon_objc-0.5.2-py3-none-any.whl", hash = "sha256:829b253c579e51fc34f4bb6587c34806e78960dcc1eb24e62b38141a1fe02b39", size = 63512, upload-time = "2025-08-07T06:12:23.766Z" }, + { url = "https://files.pythonhosted.org/packages/93/ab/e834c01138c272fb2e37d2f3c7cba708bc694dbc7b3f03b743f29ceb92d5/rubicon_objc-0.5.3-py3-none-any.whl", hash = "sha256:31dedcda9be38435f5ec067906e1eea5d0ddb790330e98a22e94ff424758b415", size = 64414, upload-time = "2025-12-03T03:51:09.082Z" }, ] [[package]] name = "ruff" -version = "0.13.1" +version = "0.14.10" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ab/33/c8e89216845615d14d2d42ba2bee404e7206a8db782f33400754f3799f05/ruff-0.13.1.tar.gz", hash = "sha256:88074c3849087f153d4bb22e92243ad4c1b366d7055f98726bc19aa08dc12d51", size = 5397987, upload-time = "2025-09-18T19:52:44.33Z" } +sdist = { url = "https://files.pythonhosted.org/packages/57/08/52232a877978dd8f9cf2aeddce3e611b40a63287dfca29b6b8da791f5e8d/ruff-0.14.10.tar.gz", hash = "sha256:9a2e830f075d1a42cd28420d7809ace390832a490ed0966fe373ba288e77aaf4", size = 5859763, upload-time = "2025-12-18T19:28:57.98Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/41/ca37e340938f45cfb8557a97a5c347e718ef34702546b174e5300dbb1f28/ruff-0.13.1-py3-none-linux_armv6l.whl", hash = "sha256:b2abff595cc3cbfa55e509d89439b5a09a6ee3c252d92020bd2de240836cf45b", size = 12304308, upload-time = "2025-09-18T19:51:56.253Z" }, - { url = "https://files.pythonhosted.org/packages/ff/84/ba378ef4129415066c3e1c80d84e539a0d52feb250685091f874804f28af/ruff-0.13.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:4ee9f4249bf7f8bb3984c41bfaf6a658162cdb1b22e3103eabc7dd1dc5579334", size = 12937258, upload-time = "2025-09-18T19:52:00.184Z" }, - { url = "https://files.pythonhosted.org/packages/8d/b6/ec5e4559ae0ad955515c176910d6d7c93edcbc0ed1a3195a41179c58431d/ruff-0.13.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5c5da4af5f6418c07d75e6f3224e08147441f5d1eac2e6ce10dcce5e616a3bae", size = 12214554, upload-time = "2025-09-18T19:52:02.753Z" }, - { url = "https://files.pythonhosted.org/packages/70/d6/cb3e3b4f03b9b0c4d4d8f06126d34b3394f6b4d764912fe80a1300696ef6/ruff-0.13.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80524f84a01355a59a93cef98d804e2137639823bcee2931f5028e71134a954e", size = 12448181, upload-time = "2025-09-18T19:52:05.279Z" }, - { url = "https://files.pythonhosted.org/packages/d2/ea/bf60cb46d7ade706a246cd3fb99e4cfe854efa3dfbe530d049c684da24ff/ruff-0.13.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff7f5ce8d7988767dd46a148192a14d0f48d1baea733f055d9064875c7d50389", size = 12104599, upload-time = "2025-09-18T19:52:07.497Z" }, - { url = "https://files.pythonhosted.org/packages/2d/3e/05f72f4c3d3a69e65d55a13e1dd1ade76c106d8546e7e54501d31f1dc54a/ruff-0.13.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c55d84715061f8b05469cdc9a446aa6c7294cd4bd55e86a89e572dba14374f8c", size = 13791178, upload-time = "2025-09-18T19:52:10.189Z" }, - { url = "https://files.pythonhosted.org/packages/81/e7/01b1fc403dd45d6cfe600725270ecc6a8f8a48a55bc6521ad820ed3ceaf8/ruff-0.13.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:ac57fed932d90fa1624c946dc67a0a3388d65a7edc7d2d8e4ca7bddaa789b3b0", size = 14814474, upload-time = "2025-09-18T19:52:12.866Z" }, - { url = "https://files.pythonhosted.org/packages/fa/92/d9e183d4ed6185a8df2ce9faa3f22e80e95b5f88d9cc3d86a6d94331da3f/ruff-0.13.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c366a71d5b4f41f86a008694f7a0d75fe409ec298685ff72dc882f882d532e36", size = 14217531, upload-time = "2025-09-18T19:52:15.245Z" }, - { url = "https://files.pythonhosted.org/packages/3b/4a/6ddb1b11d60888be224d721e01bdd2d81faaf1720592858ab8bac3600466/ruff-0.13.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4ea9d1b5ad3e7a83ee8ebb1229c33e5fe771e833d6d3dcfca7b77d95b060d38", size = 13265267, upload-time = "2025-09-18T19:52:17.649Z" }, - { url = "https://files.pythonhosted.org/packages/81/98/3f1d18a8d9ea33ef2ad508f0417fcb182c99b23258ec5e53d15db8289809/ruff-0.13.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0f70202996055b555d3d74b626406476cc692f37b13bac8828acff058c9966a", size = 13243120, upload-time = "2025-09-18T19:52:20.332Z" }, - { url = "https://files.pythonhosted.org/packages/8d/86/b6ce62ce9c12765fa6c65078d1938d2490b2b1d9273d0de384952b43c490/ruff-0.13.1-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:f8cff7a105dad631085d9505b491db33848007d6b487c3c1979dd8d9b2963783", size = 13443084, upload-time = "2025-09-18T19:52:23.032Z" }, - { url = "https://files.pythonhosted.org/packages/a1/6e/af7943466a41338d04503fb5a81b2fd07251bd272f546622e5b1599a7976/ruff-0.13.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:9761e84255443316a258dd7dfbd9bfb59c756e52237ed42494917b2577697c6a", size = 12295105, upload-time = "2025-09-18T19:52:25.263Z" }, - { url = "https://files.pythonhosted.org/packages/3f/97/0249b9a24f0f3ebd12f007e81c87cec6d311de566885e9309fcbac5b24cc/ruff-0.13.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:3d376a88c3102ef228b102211ef4a6d13df330cb0f5ca56fdac04ccec2a99700", size = 12072284, upload-time = "2025-09-18T19:52:27.478Z" }, - { url = "https://files.pythonhosted.org/packages/f6/85/0b64693b2c99d62ae65236ef74508ba39c3febd01466ef7f354885e5050c/ruff-0.13.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:cbefd60082b517a82c6ec8836989775ac05f8991715d228b3c1d86ccc7df7dae", size = 12970314, upload-time = "2025-09-18T19:52:30.212Z" }, - { url = "https://files.pythonhosted.org/packages/96/fc/342e9f28179915d28b3747b7654f932ca472afbf7090fc0c4011e802f494/ruff-0.13.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:dd16b9a5a499fe73f3c2ef09a7885cb1d97058614d601809d37c422ed1525317", size = 13422360, upload-time = "2025-09-18T19:52:32.676Z" }, - { url = "https://files.pythonhosted.org/packages/37/54/6177a0dc10bce6f43e392a2192e6018755473283d0cf43cc7e6afc182aea/ruff-0.13.1-py3-none-win32.whl", hash = "sha256:55e9efa692d7cb18580279f1fbb525146adc401f40735edf0aaeabd93099f9a0", size = 12178448, upload-time = "2025-09-18T19:52:35.545Z" }, - { url = "https://files.pythonhosted.org/packages/64/51/c6a3a33d9938007b8bdc8ca852ecc8d810a407fb513ab08e34af12dc7c24/ruff-0.13.1-py3-none-win_amd64.whl", hash = "sha256:3a3fb595287ee556de947183489f636b9f76a72f0fa9c028bdcabf5bab2cc5e5", size = 13286458, upload-time = "2025-09-18T19:52:38.198Z" }, - { url = "https://files.pythonhosted.org/packages/fd/04/afc078a12cf68592345b1e2d6ecdff837d286bac023d7a22c54c7a698c5b/ruff-0.13.1-py3-none-win_arm64.whl", hash = "sha256:c0bae9ffd92d54e03c2bf266f466da0a65e145f298ee5b5846ed435f6a00518a", size = 12437893, upload-time = "2025-09-18T19:52:41.283Z" }, + { url = "https://files.pythonhosted.org/packages/60/01/933704d69f3f05ee16ef11406b78881733c186fe14b6a46b05cfcaf6d3b2/ruff-0.14.10-py3-none-linux_armv6l.whl", hash = "sha256:7a3ce585f2ade3e1f29ec1b92df13e3da262178df8c8bdf876f48fa0e8316c49", size = 13527080, upload-time = "2025-12-18T19:29:25.642Z" }, + { url = "https://files.pythonhosted.org/packages/df/58/a0349197a7dfa603ffb7f5b0470391efa79ddc327c1e29c4851e85b09cc5/ruff-0.14.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:674f9be9372907f7257c51f1d4fc902cb7cf014b9980152b802794317941f08f", size = 13797320, upload-time = "2025-12-18T19:29:02.571Z" }, + { url = "https://files.pythonhosted.org/packages/7b/82/36be59f00a6082e38c23536df4e71cdbc6af8d7c707eade97fcad5c98235/ruff-0.14.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d85713d522348837ef9df8efca33ccb8bd6fcfc86a2cde3ccb4bc9d28a18003d", size = 12918434, upload-time = "2025-12-18T19:28:51.202Z" }, + { url = "https://files.pythonhosted.org/packages/a6/00/45c62a7f7e34da92a25804f813ebe05c88aa9e0c25e5cb5a7d23dd7450e3/ruff-0.14.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6987ebe0501ae4f4308d7d24e2d0fe3d7a98430f5adfd0f1fead050a740a3a77", size = 13371961, upload-time = "2025-12-18T19:29:04.991Z" }, + { url = "https://files.pythonhosted.org/packages/40/31/a5906d60f0405f7e57045a70f2d57084a93ca7425f22e1d66904769d1628/ruff-0.14.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:16a01dfb7b9e4eee556fbfd5392806b1b8550c9b4a9f6acd3dbe6812b193c70a", size = 13275629, upload-time = "2025-12-18T19:29:21.381Z" }, + { url = "https://files.pythonhosted.org/packages/3e/60/61c0087df21894cf9d928dc04bcd4fb10e8b2e8dca7b1a276ba2155b2002/ruff-0.14.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7165d31a925b7a294465fa81be8c12a0e9b60fb02bf177e79067c867e71f8b1f", size = 14029234, upload-time = "2025-12-18T19:29:00.132Z" }, + { url = "https://files.pythonhosted.org/packages/44/84/77d911bee3b92348b6e5dab5a0c898d87084ea03ac5dc708f46d88407def/ruff-0.14.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c561695675b972effb0c0a45db233f2c816ff3da8dcfbe7dfc7eed625f218935", size = 15449890, upload-time = "2025-12-18T19:28:53.573Z" }, + { url = "https://files.pythonhosted.org/packages/e9/36/480206eaefa24a7ec321582dda580443a8f0671fdbf6b1c80e9c3e93a16a/ruff-0.14.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4bb98fcbbc61725968893682fd4df8966a34611239c9fd07a1f6a07e7103d08e", size = 15123172, upload-time = "2025-12-18T19:29:23.453Z" }, + { url = "https://files.pythonhosted.org/packages/5c/38/68e414156015ba80cef5473d57919d27dfb62ec804b96180bafdeaf0e090/ruff-0.14.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f24b47993a9d8cb858429e97bdf8544c78029f09b520af615c1d261bf827001d", size = 14460260, upload-time = "2025-12-18T19:29:27.808Z" }, + { url = "https://files.pythonhosted.org/packages/b3/19/9e050c0dca8aba824d67cc0db69fb459c28d8cd3f6855b1405b3f29cc91d/ruff-0.14.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59aabd2e2c4fd614d2862e7939c34a532c04f1084476d6833dddef4afab87e9f", size = 14229978, upload-time = "2025-12-18T19:29:11.32Z" }, + { url = "https://files.pythonhosted.org/packages/51/eb/e8dd1dd6e05b9e695aa9dd420f4577debdd0f87a5ff2fedda33c09e9be8c/ruff-0.14.10-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:213db2b2e44be8625002dbea33bb9c60c66ea2c07c084a00d55732689d697a7f", size = 14338036, upload-time = "2025-12-18T19:29:09.184Z" }, + { url = "https://files.pythonhosted.org/packages/6a/12/f3e3a505db7c19303b70af370d137795fcfec136d670d5de5391e295c134/ruff-0.14.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b914c40ab64865a17a9a5b67911d14df72346a634527240039eb3bd650e5979d", size = 13264051, upload-time = "2025-12-18T19:29:13.431Z" }, + { url = "https://files.pythonhosted.org/packages/08/64/8c3a47eaccfef8ac20e0484e68e0772013eb85802f8a9f7603ca751eb166/ruff-0.14.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1484983559f026788e3a5c07c81ef7d1e97c1c78ed03041a18f75df104c45405", size = 13283998, upload-time = "2025-12-18T19:29:06.994Z" }, + { url = "https://files.pythonhosted.org/packages/12/84/534a5506f4074e5cc0529e5cd96cfc01bb480e460c7edf5af70d2bcae55e/ruff-0.14.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c70427132db492d25f982fffc8d6c7535cc2fd2c83fc8888f05caaa248521e60", size = 13601891, upload-time = "2025-12-18T19:28:55.811Z" }, + { url = "https://files.pythonhosted.org/packages/0d/1e/14c916087d8598917dbad9b2921d340f7884824ad6e9c55de948a93b106d/ruff-0.14.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5bcf45b681e9f1ee6445d317ce1fa9d6cba9a6049542d1c3d5b5958986be8830", size = 14336660, upload-time = "2025-12-18T19:29:16.531Z" }, + { url = "https://files.pythonhosted.org/packages/f2/1c/d7b67ab43f30013b47c12b42d1acd354c195351a3f7a1d67f59e54227ede/ruff-0.14.10-py3-none-win32.whl", hash = "sha256:104c49fc7ab73f3f3a758039adea978869a918f31b73280db175b43a2d9b51d6", size = 13196187, upload-time = "2025-12-18T19:29:19.006Z" }, + { url = "https://files.pythonhosted.org/packages/fb/9c/896c862e13886fae2af961bef3e6312db9ebc6adc2b156fe95e615dee8c1/ruff-0.14.10-py3-none-win_amd64.whl", hash = "sha256:466297bd73638c6bdf06485683e812db1c00c7ac96d4ddd0294a338c62fdc154", size = 14661283, upload-time = "2025-12-18T19:29:30.16Z" }, + { url = "https://files.pythonhosted.org/packages/74/31/b0e29d572670dca3674eeee78e418f20bdf97fa8aa9ea71380885e175ca0/ruff-0.14.10-py3-none-win_arm64.whl", hash = "sha256:e51d046cf6dda98a4633b8a8a771451107413b0f07183b2bef03f075599e44e6", size = 13729839, upload-time = "2025-12-18T19:28:48.636Z" }, ] [[package]] name = "scons" -version = "4.9.1" +version = "4.10.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c8/c1/30176c76c1ef723fab62e5cdb15d3c972427a146cb6f868748613d7b25af/scons-4.9.1.tar.gz", hash = "sha256:bacac880ba2e86d6a156c116e2f8f2bfa82b257046f3ac2666c85c53c615c338", size = 3252106, upload-time = "2025-03-27T20:42:19.765Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/c9/2f430bb39e4eccba32ce8008df4a3206df651276422204e177a09e12b30b/scons-4.10.1.tar.gz", hash = "sha256:99c0e94a42a2c1182fa6859b0be697953db07ba936ecc9817ae0d218ced20b15", size = 3258403, upload-time = "2025-11-16T22:43:39.258Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/45/92/50b739021983b131dcacd57aa8b04d31c5acc2e7e0eb4ed4a362f438c6b7/scons-4.9.1-py3-none-any.whl", hash = "sha256:d2db1a22eb6039e97cbbb0106f18f435af033d0aad190299c9688378e2810a5e", size = 4131331, upload-time = "2025-03-27T20:42:16.665Z" }, + { url = "https://files.pythonhosted.org/packages/ce/bf/931fb9fbb87234c32b8b1b1c15fba23472a10777c12043336675633809a7/scons-4.10.1-py3-none-any.whl", hash = "sha256:bd9d1c52f908d874eba92a8c0c0a8dcf2ed9f3b88ab956d0fce1da479c4e7126", size = 4136069, upload-time = "2025-11-16T22:43:35.933Z" }, ] [[package]] name = "sentry-sdk" -version = "2.38.0" +version = "2.48.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b2/22/60fd703b34d94d216b2387e048ac82de3e86b63bc28869fb076f8bb0204a/sentry_sdk-2.38.0.tar.gz", hash = "sha256:792d2af45e167e2f8a3347143f525b9b6bac6f058fb2014720b40b84ccbeb985", size = 348116, upload-time = "2025-09-15T15:00:37.846Z" } +sdist = { url = "https://files.pythonhosted.org/packages/40/f0/0e9dc590513d5e742d7799e2038df3a05167cba084c6ca4f3cdd75b55164/sentry_sdk-2.48.0.tar.gz", hash = "sha256:5213190977ff7fdff8a58b722fb807f8d5524a80488626ebeda1b5676c0c1473", size = 384828, upload-time = "2025-12-16T14:55:41.722Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7a/84/bde4c4bbb269b71bc09316af8eb00da91f67814d40337cc12ef9c8742541/sentry_sdk-2.38.0-py2.py3-none-any.whl", hash = "sha256:2324aea8573a3fa1576df7fb4d65c4eb8d9929c8fa5939647397a07179eef8d0", size = 370346, upload-time = "2025-09-15T15:00:35.821Z" }, + { url = "https://files.pythonhosted.org/packages/4d/19/8d77f9992e5cbfcaa9133c3bf63b4fbbb051248802e1e803fed5c552fbb2/sentry_sdk-2.48.0-py2.py3-none-any.whl", hash = "sha256:6b12ac256769d41825d9b7518444e57fa35b5642df4c7c5e322af4d2c8721172", size = 414555, upload-time = "2025-12-16T14:55:40.152Z" }, ] [[package]] @@ -4784,29 +4843,29 @@ wheels = [ [[package]] name = "shapely" -version = "2.1.1" +version = "2.1.2" 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/ca/3c/2da625233f4e605155926566c0e7ea8dda361877f48e8b1655e53456f252/shapely-2.1.1.tar.gz", hash = "sha256:500621967f2ffe9642454808009044c21e5b35db89ce69f8a2042c2ffd0e2772", size = 315422, upload-time = "2025-05-19T11:04:41.265Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4d/bc/0989043118a27cccb4e906a46b7565ce36ca7b57f5a18b78f4f1b0f72d9d/shapely-2.1.2.tar.gz", hash = "sha256:2ed4ecb28320a433db18a5bf029986aa8afcfd740745e78847e330d5d94922a9", size = 315489, upload-time = "2025-09-24T13:51:41.432Z" } wheels = [ - { 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" }, + { url = "https://files.pythonhosted.org/packages/8f/8d/1ff672dea9ec6a7b5d422eb6d095ed886e2e523733329f75fdcb14ee1149/shapely-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:91121757b0a36c9aac3427a651a7e6567110a4a67c97edf04f8d55d4765f6618", size = 1820038, upload-time = "2025-09-24T13:50:15.628Z" }, + { url = "https://files.pythonhosted.org/packages/4f/ce/28fab8c772ce5db23a0d86bf0adaee0c4c79d5ad1db766055fa3dab442e2/shapely-2.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:16a9c722ba774cf50b5d4541242b4cce05aafd44a015290c82ba8a16931ff63d", size = 1626039, upload-time = "2025-09-24T13:50:16.881Z" }, + { url = "https://files.pythonhosted.org/packages/70/8b/868b7e3f4982f5006e9395c1e12343c66a8155c0374fdc07c0e6a1ab547d/shapely-2.1.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cc4f7397459b12c0b196c9efe1f9d7e92463cbba142632b4cc6d8bbbbd3e2b09", size = 3001519, upload-time = "2025-09-24T13:50:18.606Z" }, + { url = "https://files.pythonhosted.org/packages/13/02/58b0b8d9c17c93ab6340edd8b7308c0c5a5b81f94ce65705819b7416dba5/shapely-2.1.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:136ab87b17e733e22f0961504d05e77e7be8c9b5a8184f685b4a91a84efe3c26", size = 3110842, upload-time = "2025-09-24T13:50:21.77Z" }, + { url = "https://files.pythonhosted.org/packages/af/61/8e389c97994d5f331dcffb25e2fa761aeedfb52b3ad9bcdd7b8671f4810a/shapely-2.1.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:16c5d0fc45d3aa0a69074979f4f1928ca2734fb2e0dde8af9611e134e46774e7", size = 4021316, upload-time = "2025-09-24T13:50:23.626Z" }, + { url = "https://files.pythonhosted.org/packages/d3/d4/9b2a9fe6039f9e42ccf2cb3e84f219fd8364b0c3b8e7bbc857b5fbe9c14c/shapely-2.1.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6ddc759f72b5b2b0f54a7e7cde44acef680a55019eb52ac63a7af2cf17cb9cd2", size = 4178586, upload-time = "2025-09-24T13:50:25.443Z" }, + { url = "https://files.pythonhosted.org/packages/16/f6/9840f6963ed4decf76b08fd6d7fed14f8779fb7a62cb45c5617fa8ac6eab/shapely-2.1.2-cp311-cp311-win32.whl", hash = "sha256:2fa78b49485391224755a856ed3b3bd91c8455f6121fee0db0e71cefb07d0ef6", size = 1543961, upload-time = "2025-09-24T13:50:26.968Z" }, + { url = "https://files.pythonhosted.org/packages/38/1e/3f8ea46353c2a33c1669eb7327f9665103aa3a8dfe7f2e4ef714c210b2c2/shapely-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:c64d5c97b2f47e3cd9b712eaced3b061f2b71234b3fc263e0fcf7d889c6559dc", size = 1722856, upload-time = "2025-09-24T13:50:28.497Z" }, + { url = "https://files.pythonhosted.org/packages/24/c0/f3b6453cf2dfa99adc0ba6675f9aaff9e526d2224cbd7ff9c1a879238693/shapely-2.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fe2533caae6a91a543dec62e8360fe86ffcdc42a7c55f9dfd0128a977a896b94", size = 1833550, upload-time = "2025-09-24T13:50:30.019Z" }, + { url = "https://files.pythonhosted.org/packages/86/07/59dee0bc4b913b7ab59ab1086225baca5b8f19865e6101db9ebb7243e132/shapely-2.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ba4d1333cc0bc94381d6d4308d2e4e008e0bd128bdcff5573199742ee3634359", size = 1643556, upload-time = "2025-09-24T13:50:32.291Z" }, + { url = "https://files.pythonhosted.org/packages/26/29/a5397e75b435b9895cd53e165083faed5d12fd9626eadec15a83a2411f0f/shapely-2.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0bd308103340030feef6c111d3eb98d50dc13feea33affc8a6f9fa549e9458a3", size = 2988308, upload-time = "2025-09-24T13:50:33.862Z" }, + { url = "https://files.pythonhosted.org/packages/b9/37/e781683abac55dde9771e086b790e554811a71ed0b2b8a1e789b7430dd44/shapely-2.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1e7d4d7ad262a48bb44277ca12c7c78cb1b0f56b32c10734ec9a1d30c0b0c54b", size = 3099844, upload-time = "2025-09-24T13:50:35.459Z" }, + { url = "https://files.pythonhosted.org/packages/d8/f3/9876b64d4a5a321b9dc482c92bb6f061f2fa42131cba643c699f39317cb9/shapely-2.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e9eddfe513096a71896441a7c37db72da0687b34752c4e193577a145c71736fc", size = 3988842, upload-time = "2025-09-24T13:50:37.478Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a0/704c7292f7014c7e74ec84eddb7b109e1fbae74a16deae9c1504b1d15565/shapely-2.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:980c777c612514c0cf99bc8a9de6d286f5e186dcaf9091252fcd444e5638193d", size = 4152714, upload-time = "2025-09-24T13:50:39.9Z" }, + { url = "https://files.pythonhosted.org/packages/53/46/319c9dc788884ad0785242543cdffac0e6530e4d0deb6c4862bc4143dcf3/shapely-2.1.2-cp312-cp312-win32.whl", hash = "sha256:9111274b88e4d7b54a95218e243282709b330ef52b7b86bc6aaf4f805306f454", size = 1542745, upload-time = "2025-09-24T13:50:41.414Z" }, + { url = "https://files.pythonhosted.org/packages/ec/bf/cb6c1c505cb31e818e900b9312d514f381fbfa5c4363edfce0fcc4f8c1a4/shapely-2.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:743044b4cfb34f9a67205cee9279feaf60ba7d02e69febc2afc609047cb49179", size = 1722861, upload-time = "2025-09-24T13:50:43.35Z" }, ] [[package]] @@ -4840,11 +4899,11 @@ wheels = [ [[package]] name = "smbus2" -version = "0.5.0" +version = "0.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/10/c9/6d85aa809e107adf85303010a59b340be109c8f815cbedc5c08c73bcffef/smbus2-0.5.0.tar.gz", hash = "sha256:4a5946fd82277870c2878befdb1a29bb28d15cda14ea4d8d2d54cf3d4bdcb035", size = 16950, upload-time = "2024-10-19T09:20:56.746Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/36/afafd43770caae69f04e21402552a8f94a072def46a002fab9357f4852ce/smbus2-0.6.0.tar.gz", hash = "sha256:9b5ff1e998e114730f9dfe0c4babbef06c92468cfb61eaa684e30f225661b95b", size = 17403, upload-time = "2025-12-20T09:02:52.017Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/85/9f/2235ba9001e3c29fc342eeb222104420bcb7bac51555f0c034376a744075/smbus2-0.5.0-py2.py3-none-any.whl", hash = "sha256:1a15c3b9fa69357beb038cc0b5d37939702f8bfde1ddc89ca9f17d8461dbe949", size = 11527, upload-time = "2024-10-19T09:20:55.202Z" }, + { url = "https://files.pythonhosted.org/packages/a5/cf/2e1d6805da6f9c9b3a4358076ff2e072d828ba7fed124edc1b729e210c55/smbus2-0.6.0-py2.py3-none-any.whl", hash = "sha256:03d83d2a9a4afc5ddca0698ccabf101cb3de52bc5aefd7b76778ffb27ff654e0", size = 11849, upload-time = "2025-12-20T09:02:51.219Z" }, ] [[package]] @@ -4858,17 +4917,17 @@ wheels = [ [[package]] name = "sounddevice" -version = "0.5.2" +version = "0.5.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi" }, ] -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" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/4f/28e734898b870db15b6474453f19813d3c81b91c806d9e6f867bd6e4dd03/sounddevice-0.5.3.tar.gz", hash = "sha256:cbac2b60198fbab84533697e7c4904cc895ec69d5fb3973556c9eb74a4629b2c", size = 53465, upload-time = "2025-10-19T13:23:57.922Z" } wheels = [ - { 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" }, + { url = "https://files.pythonhosted.org/packages/73/e7/9020e9f0f3df00432728f4c4044387468a743e3d9a4f91123d77be10010e/sounddevice-0.5.3-py3-none-any.whl", hash = "sha256:ea7738baa0a9f9fef7390f649e41c9f2c8ada776180e56c2ffd217133c92a806", size = 32670, upload-time = "2025-10-19T13:23:51.779Z" }, + { url = "https://files.pythonhosted.org/packages/2f/39/714118f8413e0e353436914f2b976665161f1be2b6483ac15a8f61484c14/sounddevice-0.5.3-py3-none-macosx_10_6_x86_64.macosx_10_6_universal2.whl", hash = "sha256:278dc4451fff70934a176df048b77d80d7ce1623a6ec9db8b34b806f3112f9c2", size = 108306, upload-time = "2025-10-19T13:23:53.277Z" }, + { url = "https://files.pythonhosted.org/packages/f5/74/52186e3e5c833d00273f7949a9383adff93692c6e02406bf359cb4d3e921/sounddevice-0.5.3-py3-none-win32.whl", hash = "sha256:845d6927bcf14e84be5292a61ab3359cf8e6b9145819ec6f3ac2619ff089a69c", size = 312882, upload-time = "2025-10-19T13:23:54.829Z" }, + { url = "https://files.pythonhosted.org/packages/66/c7/16123d054aef6d445176c9122bfbe73c11087589b2413cab22aff5a7839a/sounddevice-0.5.3-py3-none-win_amd64.whl", hash = "sha256:f55ad20082efc2bdec06928e974fbcae07bc6c405409ae1334cefe7d377eb687", size = 364025, upload-time = "2025-10-19T13:23:56.362Z" }, ] [[package]] @@ -4967,11 +5026,11 @@ wheels = [ [[package]] name = "urllib3" -version = "2.5.0" +version = "2.6.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1e/24/a2a2ed9addd907787d7aa0355ba36a6cadf1768b934c652ea78acbd59dcd/urllib3-2.6.2.tar.gz", hash = "sha256:016f9c98bb7e98085cb2b4b17b87d2c702975664e4f060c6532e64d1c1a5e797", size = 432930, upload-time = "2025-12-11T15:56:40.252Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, + { url = "https://files.pythonhosted.org/packages/6d/b9/4095b668ea3678bf6a0af005527f39de12fb026516fb3df17495a733b7f8/urllib3-2.6.2-py3-none-any.whl", hash = "sha256:ec21cddfe7724fc7cb4ba4bea7aa8e2ef36f607a4bab81aa6ce42a13dc3f03dd", size = 131182, upload-time = "2025-12-11T15:56:38.584Z" }, ] [[package]] @@ -5000,40 +5059,36 @@ wheels = [ [[package]] name = "websocket-client" -version = "1.8.0" +version = "1.9.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e6/30/fba0d96b4b5fbf5948ed3f4681f7da2f9f64512e1d303f94b4cc174c24a5/websocket_client-1.8.0.tar.gz", hash = "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da", size = 54648, upload-time = "2024-04-23T22:16:16.976Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/41/aa4bf9664e4cda14c3b39865b12251e8e7d239f4cd0e3cc1b6c2ccde25c1/websocket_client-1.9.0.tar.gz", hash = "sha256:9e813624b6eb619999a97dc7958469217c3176312b3a16a4bd1bc7e08a46ec98", size = 70576, upload-time = "2025-10-07T21:16:36.495Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/84/44687a29792a70e111c5c477230a72c4b957d88d16141199bf9acb7537a3/websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526", size = 58826, upload-time = "2024-04-23T22:16:14.422Z" }, + { url = "https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl", hash = "sha256:af248a825037ef591efbf6ed20cc5faa03d3b47b9e5a2230a529eeee1c1fc3ef", size = 82616, upload-time = "2025-10-07T21:16:34.951Z" }, ] [[package]] name = "xattr" -version = "1.2.0" +version = "1.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/50/65/14438ae55acf7f8fc396ee8340d740a3e1d6ef382bf25bf24156cfb83563/xattr-1.2.0.tar.gz", hash = "sha256:a64c8e21eff1be143accf80fd3b8fde3e28a478c37da298742af647ac3e5e0a7", size = 17293, upload-time = "2025-07-14T03:15:44.884Z" } +sdist = { url = "https://files.pythonhosted.org/packages/08/d5/25f7b19af3a2cb4000cac4f9e5525a40bec79f4f5d0ac9b517c0544586a0/xattr-1.3.0.tar.gz", hash = "sha256:30439fabd7de0787b27e9a6e1d569c5959854cb322f64ce7380fedbfa5035036", size = 17148, upload-time = "2025-10-13T22:16:47.353Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/e2/bf74df197a415f25e07378bfa301788e3bf2ac029c3a6c7bd56a900934ff/xattr-1.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:00c26c14c90058338993bb2d3e1cebf562e94ec516cafba64a8f34f74b9d18b4", size = 24246, upload-time = "2025-07-14T03:14:34.873Z" }, - { url = "https://files.pythonhosted.org/packages/a5/51/922df424556ff35b20ca043da5e4dcf0f99cbcb674f59046d08ceff3ebc7/xattr-1.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b4f43dc644db87d5eb9484a9518c34a864cb2e588db34cffc42139bf55302a1c", size = 19212, upload-time = "2025-07-14T03:14:35.905Z" }, - { url = "https://files.pythonhosted.org/packages/7c/72/1ed37812e8285c8002b8834395c53cc89a2d83aa088db642b217be439017/xattr-1.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c7602583fc643ca76576498e2319c7cef0b72aef1936701678589da6371b731b", size = 19546, upload-time = "2025-07-14T03:14:37.242Z" }, - { url = "https://files.pythonhosted.org/packages/d4/b8/ec75db23d81beec68e3be20ea176c11f125697d3bbb5e118b9de9ea7a9ab/xattr-1.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90c3ad4a9205cceb64ec54616aa90aa42d140c8ae3b9710a0aaa2843a6f1aca7", size = 39426, upload-time = "2025-07-14T03:14:38.264Z" }, - { url = "https://files.pythonhosted.org/packages/d4/9f/c24950641b138072eda7f34d86966dd15cfe3af9a111b5e77b85ee55f99c/xattr-1.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83d87cfe19cd606fc0709d45a4d6efc276900797deced99e239566926a5afedf", size = 37311, upload-time = "2025-07-14T03:14:39.347Z" }, - { url = "https://files.pythonhosted.org/packages/d0/d5/3b7e0dab706d09c6cdb2f05384610e6c5693c72e3794d54a4cad8c838373/xattr-1.2.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c67dabd9ddc04ead63fbc85aed459c9afcc24abfc5bb3217fff7ec9a466faacb", size = 39222, upload-time = "2025-07-14T03:14:40.768Z" }, - { url = "https://files.pythonhosted.org/packages/0e/16/80cf8ec7d92d20b2860c96a1eca18d25e27fa4770f32c9e8250ff32e7386/xattr-1.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9a18ee82d8ba2c17f1e8414bfeb421fa763e0fb4acbc1e124988ca1584ad32d5", size = 38694, upload-time = "2025-07-14T03:14:41.93Z" }, - { url = "https://files.pythonhosted.org/packages/38/c0/b154b254e6e4596aed3210dd48b2e82d958b16d9a7f65346b9154968d2d0/xattr-1.2.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:38de598c47b85185e745986a061094d2e706e9c2d9022210d2c738066990fe91", size = 37055, upload-time = "2025-07-14T03:14:43.435Z" }, - { url = "https://files.pythonhosted.org/packages/dc/1d/3a615660849ef9bdf46d04f9c6d40ee082f7427678013ff85452ed9497db/xattr-1.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:15e754e854bdaac366ad3f1c8fbf77f6668e8858266b4246e8c5f487eeaf1179", size = 38275, upload-time = "2025-07-14T03:14:45.18Z" }, - { url = "https://files.pythonhosted.org/packages/37/e5/b048a5f6c5a489915026b70b9133242a2a368383ddab24e4e3a5bdba7ebd/xattr-1.2.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:daff0c1f5c5e4eaf758c56259c4f72631fa9619875e7a25554b6077dc73da964", size = 24240, upload-time = "2025-07-14T03:14:46.173Z" }, - { url = "https://files.pythonhosted.org/packages/cc/f5/d795774f719a0be6137041d4833ca00b178f816e538948548dff79530f34/xattr-1.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:109b11fb3f73a0d4e199962f11230ab5f462e85a8021874f96c1732aa61148d5", size = 19218, upload-time = "2025-07-14T03:14:47.412Z" }, - { url = "https://files.pythonhosted.org/packages/cb/8b/65f3bed09ca9ced27bbba8d4a3326f14a58b98ac102163d85b545f81d9c2/xattr-1.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7c7c12968ce0bf798d8ba90194cef65de768bee9f51a684e022c74cab4218305", size = 19539, upload-time = "2025-07-14T03:14:48.413Z" }, - { url = "https://files.pythonhosted.org/packages/96/2d/01ecfdf41ce70f7e29c8a21e730de3c157fb1cb84391923581af81a44c45/xattr-1.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d37989dabf25ff18773e4aaeebcb65604b9528f8645f43e02bebaa363e3ae958", size = 39631, upload-time = "2025-07-14T03:14:49.665Z" }, - { url = "https://files.pythonhosted.org/packages/c9/e9/15cbf9c59cf1117e3c45dd429c52f9dab25d95e65ac245c5ad9532986bec/xattr-1.2.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:165de92b0f2adafb336f936931d044619b9840e35ba01079f4dd288747b73714", size = 37552, upload-time = "2025-07-14T03:14:50.718Z" }, - { url = "https://files.pythonhosted.org/packages/9d/f5/cb4dad87843fe79d605cf5d10caad80e2c338a06f0363f1443449185f489/xattr-1.2.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82191c006ae4c609b22b9aea5f38f68fff022dc6884c4c0e1dba329effd4b288", size = 39472, upload-time = "2025-07-14T03:14:51.74Z" }, - { url = "https://files.pythonhosted.org/packages/5a/d9/012df7b814cc4a0ae41afb59ac31d0469227397b29f58c1377e8db0f34ba/xattr-1.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2b2e9c87dc643b09d86befad218e921f6e65b59a4668d6262b85308de5dbd1dd", size = 38802, upload-time = "2025-07-14T03:14:52.801Z" }, - { url = "https://files.pythonhosted.org/packages/d8/08/e107a5d294a816586f274c33aea480fe740fd446276efc84c067e6c82de2/xattr-1.2.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:14edd5d47d0bb92b23222c0bb6379abbddab01fb776b2170758e666035ecf3aa", size = 37125, upload-time = "2025-07-14T03:14:54.313Z" }, - { url = "https://files.pythonhosted.org/packages/3e/6c/a6f9152e10543af67ea277caae7c5a6400a581e407c42156ffce71dd8242/xattr-1.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:12183d5eb104d4da787638c7dadf63b718472d92fec6dbe12994ea5d094d7863", size = 38456, upload-time = "2025-07-14T03:14:55.383Z" }, + { url = "https://files.pythonhosted.org/packages/8a/64/292426ad5653e72c6e1325bbff22868a20077290d967cebb9c0624ad08b6/xattr-1.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:331a51bf8f20c27822f44054b0d760588462d3ed472d5e52ba135cf0bea510e8", size = 23448, upload-time = "2025-10-13T22:15:59.229Z" }, + { url = "https://files.pythonhosted.org/packages/63/84/6539fbe620da8e5927406e76b9c8abad8953025d5f578d792747c38a8c0e/xattr-1.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:196360f068b74fa0132a8c6001ce1333f095364b8f43b6fd8cdaf2f18741ef89", size = 18553, upload-time = "2025-10-13T22:16:00.151Z" }, + { url = "https://files.pythonhosted.org/packages/cc/bb/c1c2e24a49f8d13ff878fb85aabc42ea1b2f98ce08d8205b9661d517a9cc/xattr-1.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:405d2e4911d37f2b9400fa501acd920fe0c97fe2b2ec252cb23df4b59c000811", size = 18848, upload-time = "2025-10-13T22:16:01.046Z" }, + { url = "https://files.pythonhosted.org/packages/02/c2/a60aad150322b217dfe33695d8d9f32bc01e8f300641b6ba4b73f4b3c03f/xattr-1.3.0-cp311-cp311-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4ae3a66ae1effd40994f64defeeaa97da369406485e60bfb421f2d781be3b75d", size = 38547, upload-time = "2025-10-13T22:16:01.973Z" }, + { url = "https://files.pythonhosted.org/packages/c6/58/2eca142bad4ea0a2be6b58d3122d0acce310c4e53fa7defd168202772178/xattr-1.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:69cd3bfe779f7ba87abe6473fdfa428460cf9e78aeb7e390cfd737b784edf1b5", size = 38753, upload-time = "2025-10-13T22:16:03.244Z" }, + { url = "https://files.pythonhosted.org/packages/2b/50/d032e5254c2c27d36bdb02abdf2735db6768a441f0e3d0f139e0f9f56638/xattr-1.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c5742ca61761a99ae0c522f90a39d5fb8139280f27b254e3128482296d1df2db", size = 38054, upload-time = "2025-10-13T22:16:04.656Z" }, + { url = "https://files.pythonhosted.org/packages/04/24/458a306439aabe0083ca0a7b14c3e6a800ab9782b5ec0bdcec4ec9f3dc6c/xattr-1.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4a04ada131e9bdfd32db3ab1efa9f852646f4f7c9d6fde0596c3825c67161be3", size = 37562, upload-time = "2025-10-13T22:16:05.97Z" }, + { url = "https://files.pythonhosted.org/packages/bf/78/00bdc9290066173e53e1e734d8d8e1a84a6faa9c66aee9df81e4d9aeec1c/xattr-1.3.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:dd4e63614722d183e81842cb237fd1cc978d43384166f9fe22368bfcb187ebe5", size = 23476, upload-time = "2025-10-13T22:16:06.942Z" }, + { url = "https://files.pythonhosted.org/packages/53/16/5243722294eb982514fa7b6b87a29dfb7b29b8e5e1486500c5babaf6e4b3/xattr-1.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:995843ef374af73e3370b0c107319611f3cdcdb6d151d629449efecad36be4c4", size = 18556, upload-time = "2025-10-13T22:16:08.209Z" }, + { url = "https://files.pythonhosted.org/packages/d6/5c/d7ab0e547bea885b55f097206459bd612cefb652c5fc1f747130cbc0d42c/xattr-1.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fa23a25220e29d956cedf75746e3df6cc824cc1553326d6516479967c540e386", size = 18869, upload-time = "2025-10-13T22:16:10.319Z" }, + { url = "https://files.pythonhosted.org/packages/98/25/25cc7d64f07de644b7e9057842227adf61017e5bcfe59a79df79f768874c/xattr-1.3.0-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b4345387087fffcd28f709eb45aae113d911e1a1f4f0f70d46b43ba81e69ccdd", size = 38797, upload-time = "2025-10-13T22:16:11.624Z" }, + { url = "https://files.pythonhosted.org/packages/a9/24/cc350bcdbed006dfcc6ade0ac817693b8b3d4b2787f20e427fd0697042e4/xattr-1.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fe92bb05eb849ab468fe13e942be0f8d7123f15d074f3aba5223fad0c4b484de", size = 38956, upload-time = "2025-10-13T22:16:13.121Z" }, + { url = "https://files.pythonhosted.org/packages/9b/b2/9416317ac89e2ed759a861857cda0d5e284c3691e6f460d36cc2bd5ce4d1/xattr-1.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6c42ef5bdac3febbe28d3db14d3a8a159d84ba5daca2b13deae6f9f1fc0d4092", size = 38214, upload-time = "2025-10-13T22:16:14.389Z" }, + { url = "https://files.pythonhosted.org/packages/38/63/188f7cb41ab35d795558325d5cc8ab552171d5498cfb178fd14409651e18/xattr-1.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2aaa5d66af6523332189108f34e966ca120ff816dfa077ca34b31e6263f8a236", size = 37754, upload-time = "2025-10-13T22:16:15.306Z" }, ] [[package]] @@ -5041,7 +5096,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 = [ @@ -5050,50 +5105,48 @@ wheels = [ [[package]] name = "yarl" -version = "1.20.1" +version = "1.22.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "idna" }, { name = "multidict" }, { name = "propcache" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3c/fb/efaa23fa4e45537b827620f04cf8f3cd658b76642205162e072703a5b963/yarl-1.20.1.tar.gz", hash = "sha256:d017a4997ee50c91fd5466cef416231bb82177b93b029906cefc542ce14c35ac", size = 186428, upload-time = "2025-06-10T00:46:09.923Z" } +sdist = { url = "https://files.pythonhosted.org/packages/57/63/0c6ebca57330cd313f6102b16dd57ffaf3ec4c83403dcb45dbd15c6f3ea1/yarl-1.22.0.tar.gz", hash = "sha256:bebf8557577d4401ba8bd9ff33906f1376c877aa78d1fe216ad01b4d6745af71", size = 187169, upload-time = "2025-10-06T14:12:55.963Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b1/18/893b50efc2350e47a874c5c2d67e55a0ea5df91186b2a6f5ac52eff887cd/yarl-1.20.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:47ee6188fea634bdfaeb2cc420f5b3b17332e6225ce88149a17c413c77ff269e", size = 133833, upload-time = "2025-06-10T00:43:07.393Z" }, - { url = "https://files.pythonhosted.org/packages/89/ed/b8773448030e6fc47fa797f099ab9eab151a43a25717f9ac043844ad5ea3/yarl-1.20.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d0f6500f69e8402d513e5eedb77a4e1818691e8f45e6b687147963514d84b44b", size = 91070, upload-time = "2025-06-10T00:43:09.538Z" }, - { url = "https://files.pythonhosted.org/packages/e3/e3/409bd17b1e42619bf69f60e4f031ce1ccb29bd7380117a55529e76933464/yarl-1.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a8900a42fcdaad568de58887c7b2f602962356908eedb7628eaf6021a6e435b", size = 89818, upload-time = "2025-06-10T00:43:11.575Z" }, - { url = "https://files.pythonhosted.org/packages/f8/77/64d8431a4d77c856eb2d82aa3de2ad6741365245a29b3a9543cd598ed8c5/yarl-1.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bad6d131fda8ef508b36be3ece16d0902e80b88ea7200f030a0f6c11d9e508d4", size = 347003, upload-time = "2025-06-10T00:43:14.088Z" }, - { url = "https://files.pythonhosted.org/packages/8d/d2/0c7e4def093dcef0bd9fa22d4d24b023788b0a33b8d0088b51aa51e21e99/yarl-1.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:df018d92fe22aaebb679a7f89fe0c0f368ec497e3dda6cb81a567610f04501f1", size = 336537, upload-time = "2025-06-10T00:43:16.431Z" }, - { url = "https://files.pythonhosted.org/packages/f0/f3/fc514f4b2cf02cb59d10cbfe228691d25929ce8f72a38db07d3febc3f706/yarl-1.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f969afbb0a9b63c18d0feecf0db09d164b7a44a053e78a7d05f5df163e43833", size = 362358, upload-time = "2025-06-10T00:43:18.704Z" }, - { url = "https://files.pythonhosted.org/packages/ea/6d/a313ac8d8391381ff9006ac05f1d4331cee3b1efaa833a53d12253733255/yarl-1.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:812303eb4aa98e302886ccda58d6b099e3576b1b9276161469c25803a8db277d", size = 357362, upload-time = "2025-06-10T00:43:20.888Z" }, - { url = "https://files.pythonhosted.org/packages/00/70/8f78a95d6935a70263d46caa3dd18e1f223cf2f2ff2037baa01a22bc5b22/yarl-1.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98c4a7d166635147924aa0bf9bfe8d8abad6fffa6102de9c99ea04a1376f91e8", size = 348979, upload-time = "2025-06-10T00:43:23.169Z" }, - { url = "https://files.pythonhosted.org/packages/cb/05/42773027968968f4f15143553970ee36ead27038d627f457cc44bbbeecf3/yarl-1.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12e768f966538e81e6e7550f9086a6236b16e26cd964cf4df35349970f3551cf", size = 337274, upload-time = "2025-06-10T00:43:27.111Z" }, - { url = "https://files.pythonhosted.org/packages/05/be/665634aa196954156741ea591d2f946f1b78ceee8bb8f28488bf28c0dd62/yarl-1.20.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fe41919b9d899661c5c28a8b4b0acf704510b88f27f0934ac7a7bebdd8938d5e", size = 363294, upload-time = "2025-06-10T00:43:28.96Z" }, - { url = "https://files.pythonhosted.org/packages/eb/90/73448401d36fa4e210ece5579895731f190d5119c4b66b43b52182e88cd5/yarl-1.20.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:8601bc010d1d7780592f3fc1bdc6c72e2b6466ea34569778422943e1a1f3c389", size = 358169, upload-time = "2025-06-10T00:43:30.701Z" }, - { url = "https://files.pythonhosted.org/packages/c3/b0/fce922d46dc1eb43c811f1889f7daa6001b27a4005587e94878570300881/yarl-1.20.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:daadbdc1f2a9033a2399c42646fbd46da7992e868a5fe9513860122d7fe7a73f", size = 362776, upload-time = "2025-06-10T00:43:32.51Z" }, - { url = "https://files.pythonhosted.org/packages/f1/0d/b172628fce039dae8977fd22caeff3eeebffd52e86060413f5673767c427/yarl-1.20.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:03aa1e041727cb438ca762628109ef1333498b122e4c76dd858d186a37cec845", size = 381341, upload-time = "2025-06-10T00:43:34.543Z" }, - { url = "https://files.pythonhosted.org/packages/6b/9b/5b886d7671f4580209e855974fe1cecec409aa4a89ea58b8f0560dc529b1/yarl-1.20.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:642980ef5e0fa1de5fa96d905c7e00cb2c47cb468bfcac5a18c58e27dbf8d8d1", size = 379988, upload-time = "2025-06-10T00:43:36.489Z" }, - { url = "https://files.pythonhosted.org/packages/73/be/75ef5fd0fcd8f083a5d13f78fd3f009528132a1f2a1d7c925c39fa20aa79/yarl-1.20.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:86971e2795584fe8c002356d3b97ef6c61862720eeff03db2a7c86b678d85b3e", size = 371113, upload-time = "2025-06-10T00:43:38.592Z" }, - { url = "https://files.pythonhosted.org/packages/50/4f/62faab3b479dfdcb741fe9e3f0323e2a7d5cd1ab2edc73221d57ad4834b2/yarl-1.20.1-cp311-cp311-win32.whl", hash = "sha256:597f40615b8d25812f14562699e287f0dcc035d25eb74da72cae043bb884d773", size = 81485, upload-time = "2025-06-10T00:43:41.038Z" }, - { url = "https://files.pythonhosted.org/packages/f0/09/d9c7942f8f05c32ec72cd5c8e041c8b29b5807328b68b4801ff2511d4d5e/yarl-1.20.1-cp311-cp311-win_amd64.whl", hash = "sha256:26ef53a9e726e61e9cd1cda6b478f17e350fb5800b4bd1cd9fe81c4d91cfeb2e", size = 86686, upload-time = "2025-06-10T00:43:42.692Z" }, - { url = "https://files.pythonhosted.org/packages/5f/9a/cb7fad7d73c69f296eda6815e4a2c7ed53fc70c2f136479a91c8e5fbdb6d/yarl-1.20.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdcc4cd244e58593a4379fe60fdee5ac0331f8eb70320a24d591a3be197b94a9", size = 133667, upload-time = "2025-06-10T00:43:44.369Z" }, - { url = "https://files.pythonhosted.org/packages/67/38/688577a1cb1e656e3971fb66a3492501c5a5df56d99722e57c98249e5b8a/yarl-1.20.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b29a2c385a5f5b9c7d9347e5812b6f7ab267193c62d282a540b4fc528c8a9d2a", size = 91025, upload-time = "2025-06-10T00:43:46.295Z" }, - { url = "https://files.pythonhosted.org/packages/50/ec/72991ae51febeb11a42813fc259f0d4c8e0507f2b74b5514618d8b640365/yarl-1.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1112ae8154186dfe2de4732197f59c05a83dc814849a5ced892b708033f40dc2", size = 89709, upload-time = "2025-06-10T00:43:48.22Z" }, - { url = "https://files.pythonhosted.org/packages/99/da/4d798025490e89426e9f976702e5f9482005c548c579bdae792a4c37769e/yarl-1.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90bbd29c4fe234233f7fa2b9b121fb63c321830e5d05b45153a2ca68f7d310ee", size = 352287, upload-time = "2025-06-10T00:43:49.924Z" }, - { url = "https://files.pythonhosted.org/packages/1a/26/54a15c6a567aac1c61b18aa0f4b8aa2e285a52d547d1be8bf48abe2b3991/yarl-1.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:680e19c7ce3710ac4cd964e90dad99bf9b5029372ba0c7cbfcd55e54d90ea819", size = 345429, upload-time = "2025-06-10T00:43:51.7Z" }, - { url = "https://files.pythonhosted.org/packages/d6/95/9dcf2386cb875b234353b93ec43e40219e14900e046bf6ac118f94b1e353/yarl-1.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a979218c1fdb4246a05efc2cc23859d47c89af463a90b99b7c56094daf25a16", size = 365429, upload-time = "2025-06-10T00:43:53.494Z" }, - { url = "https://files.pythonhosted.org/packages/91/b2/33a8750f6a4bc224242a635f5f2cff6d6ad5ba651f6edcccf721992c21a0/yarl-1.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255b468adf57b4a7b65d8aad5b5138dce6a0752c139965711bdcb81bc370e1b6", size = 363862, upload-time = "2025-06-10T00:43:55.766Z" }, - { url = "https://files.pythonhosted.org/packages/98/28/3ab7acc5b51f4434b181b0cee8f1f4b77a65919700a355fb3617f9488874/yarl-1.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a97d67108e79cfe22e2b430d80d7571ae57d19f17cda8bb967057ca8a7bf5bfd", size = 355616, upload-time = "2025-06-10T00:43:58.056Z" }, - { url = "https://files.pythonhosted.org/packages/36/a3/f666894aa947a371724ec7cd2e5daa78ee8a777b21509b4252dd7bd15e29/yarl-1.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8570d998db4ddbfb9a590b185a0a33dbf8aafb831d07a5257b4ec9948df9cb0a", size = 339954, upload-time = "2025-06-10T00:43:59.773Z" }, - { url = "https://files.pythonhosted.org/packages/f1/81/5f466427e09773c04219d3450d7a1256138a010b6c9f0af2d48565e9ad13/yarl-1.20.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:97c75596019baae7c71ccf1d8cc4738bc08134060d0adfcbe5642f778d1dca38", size = 365575, upload-time = "2025-06-10T00:44:02.051Z" }, - { url = "https://files.pythonhosted.org/packages/2e/e3/e4b0ad8403e97e6c9972dd587388940a032f030ebec196ab81a3b8e94d31/yarl-1.20.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1c48912653e63aef91ff988c5432832692ac5a1d8f0fb8a33091520b5bbe19ef", size = 365061, upload-time = "2025-06-10T00:44:04.196Z" }, - { url = "https://files.pythonhosted.org/packages/ac/99/b8a142e79eb86c926f9f06452eb13ecb1bb5713bd01dc0038faf5452e544/yarl-1.20.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4c3ae28f3ae1563c50f3d37f064ddb1511ecc1d5584e88c6b7c63cf7702a6d5f", size = 364142, upload-time = "2025-06-10T00:44:06.527Z" }, - { url = "https://files.pythonhosted.org/packages/34/f2/08ed34a4a506d82a1a3e5bab99ccd930a040f9b6449e9fd050320e45845c/yarl-1.20.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c5e9642f27036283550f5f57dc6156c51084b458570b9d0d96100c8bebb186a8", size = 381894, upload-time = "2025-06-10T00:44:08.379Z" }, - { url = "https://files.pythonhosted.org/packages/92/f8/9a3fbf0968eac704f681726eff595dce9b49c8a25cd92bf83df209668285/yarl-1.20.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2c26b0c49220d5799f7b22c6838409ee9bc58ee5c95361a4d7831f03cc225b5a", size = 383378, upload-time = "2025-06-10T00:44:10.51Z" }, - { url = "https://files.pythonhosted.org/packages/af/85/9363f77bdfa1e4d690957cd39d192c4cacd1c58965df0470a4905253b54f/yarl-1.20.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:564ab3d517e3d01c408c67f2e5247aad4019dcf1969982aba3974b4093279004", size = 374069, upload-time = "2025-06-10T00:44:12.834Z" }, - { url = "https://files.pythonhosted.org/packages/35/99/9918c8739ba271dcd935400cff8b32e3cd319eaf02fcd023d5dcd487a7c8/yarl-1.20.1-cp312-cp312-win32.whl", hash = "sha256:daea0d313868da1cf2fac6b2d3a25c6e3a9e879483244be38c8e6a41f1d876a5", size = 81249, upload-time = "2025-06-10T00:44:14.731Z" }, - { url = "https://files.pythonhosted.org/packages/eb/83/5d9092950565481b413b31a23e75dd3418ff0a277d6e0abf3729d4d1ce25/yarl-1.20.1-cp312-cp312-win_amd64.whl", hash = "sha256:48ea7d7f9be0487339828a4de0360d7ce0efc06524a48e1810f945c45b813698", size = 86710, upload-time = "2025-06-10T00:44:16.716Z" }, - { url = "https://files.pythonhosted.org/packages/b4/2d/2345fce04cfd4bee161bf1e7d9cdc702e3e16109021035dbb24db654a622/yarl-1.20.1-py3-none-any.whl", hash = "sha256:83b8eb083fe4683c6115795d9fc1cfaf2cbbefb19b3a1cb68f6527460f483a77", size = 46542, upload-time = "2025-06-10T00:46:07.521Z" }, + { url = "https://files.pythonhosted.org/packages/4d/27/5ab13fc84c76a0250afd3d26d5936349a35be56ce5785447d6c423b26d92/yarl-1.22.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1ab72135b1f2db3fed3997d7e7dc1b80573c67138023852b6efb336a5eae6511", size = 141607, upload-time = "2025-10-06T14:09:16.298Z" }, + { url = "https://files.pythonhosted.org/packages/6a/a1/d065d51d02dc02ce81501d476b9ed2229d9a990818332242a882d5d60340/yarl-1.22.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:669930400e375570189492dc8d8341301578e8493aec04aebc20d4717f899dd6", size = 94027, upload-time = "2025-10-06T14:09:17.786Z" }, + { url = "https://files.pythonhosted.org/packages/c1/da/8da9f6a53f67b5106ffe902c6fa0164e10398d4e150d85838b82f424072a/yarl-1.22.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:792a2af6d58177ef7c19cbf0097aba92ca1b9cb3ffdd9c7470e156c8f9b5e028", size = 94963, upload-time = "2025-10-06T14:09:19.662Z" }, + { url = "https://files.pythonhosted.org/packages/68/fe/2c1f674960c376e29cb0bec1249b117d11738db92a6ccc4a530b972648db/yarl-1.22.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ea66b1c11c9150f1372f69afb6b8116f2dd7286f38e14ea71a44eee9ec51b9d", size = 368406, upload-time = "2025-10-06T14:09:21.402Z" }, + { url = "https://files.pythonhosted.org/packages/95/26/812a540e1c3c6418fec60e9bbd38e871eaba9545e94fa5eff8f4a8e28e1e/yarl-1.22.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3e2daa88dc91870215961e96a039ec73e4937da13cf77ce17f9cad0c18df3503", size = 336581, upload-time = "2025-10-06T14:09:22.98Z" }, + { url = "https://files.pythonhosted.org/packages/0b/f5/5777b19e26fdf98563985e481f8be3d8a39f8734147a6ebf459d0dab5a6b/yarl-1.22.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba440ae430c00eee41509353628600212112cd5018d5def7e9b05ea7ac34eb65", size = 388924, upload-time = "2025-10-06T14:09:24.655Z" }, + { url = "https://files.pythonhosted.org/packages/86/08/24bd2477bd59c0bbd994fe1d93b126e0472e4e3df5a96a277b0a55309e89/yarl-1.22.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e6438cc8f23a9c1478633d216b16104a586b9761db62bfacb6425bac0a36679e", size = 392890, upload-time = "2025-10-06T14:09:26.617Z" }, + { url = "https://files.pythonhosted.org/packages/46/00/71b90ed48e895667ecfb1eaab27c1523ee2fa217433ed77a73b13205ca4b/yarl-1.22.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c52a6e78aef5cf47a98ef8e934755abf53953379b7d53e68b15ff4420e6683d", size = 365819, upload-time = "2025-10-06T14:09:28.544Z" }, + { url = "https://files.pythonhosted.org/packages/30/2d/f715501cae832651d3282387c6a9236cd26bd00d0ff1e404b3dc52447884/yarl-1.22.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3b06bcadaac49c70f4c88af4ffcfbe3dc155aab3163e75777818092478bcbbe7", size = 363601, upload-time = "2025-10-06T14:09:30.568Z" }, + { url = "https://files.pythonhosted.org/packages/f8/f9/a678c992d78e394e7126ee0b0e4e71bd2775e4334d00a9278c06a6cce96a/yarl-1.22.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:6944b2dc72c4d7f7052683487e3677456050ff77fcf5e6204e98caf785ad1967", size = 358072, upload-time = "2025-10-06T14:09:32.528Z" }, + { url = "https://files.pythonhosted.org/packages/2c/d1/b49454411a60edb6fefdcad4f8e6dbba7d8019e3a508a1c5836cba6d0781/yarl-1.22.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d5372ca1df0f91a86b047d1277c2aaf1edb32d78bbcefffc81b40ffd18f027ed", size = 385311, upload-time = "2025-10-06T14:09:34.634Z" }, + { url = "https://files.pythonhosted.org/packages/87/e5/40d7a94debb8448c7771a916d1861d6609dddf7958dc381117e7ba36d9e8/yarl-1.22.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:51af598701f5299012b8416486b40fceef8c26fc87dc6d7d1f6fc30609ea0aa6", size = 381094, upload-time = "2025-10-06T14:09:36.268Z" }, + { url = "https://files.pythonhosted.org/packages/35/d8/611cc282502381ad855448643e1ad0538957fc82ae83dfe7762c14069e14/yarl-1.22.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b266bd01fedeffeeac01a79ae181719ff848a5a13ce10075adbefc8f1daee70e", size = 370944, upload-time = "2025-10-06T14:09:37.872Z" }, + { url = "https://files.pythonhosted.org/packages/2d/df/fadd00fb1c90e1a5a8bd731fa3d3de2e165e5a3666a095b04e31b04d9cb6/yarl-1.22.0-cp311-cp311-win32.whl", hash = "sha256:a9b1ba5610a4e20f655258d5a1fdc7ebe3d837bb0e45b581398b99eb98b1f5ca", size = 81804, upload-time = "2025-10-06T14:09:39.359Z" }, + { url = "https://files.pythonhosted.org/packages/b5/f7/149bb6f45f267cb5c074ac40c01c6b3ea6d8a620d34b337f6321928a1b4d/yarl-1.22.0-cp311-cp311-win_amd64.whl", hash = "sha256:078278b9b0b11568937d9509b589ee83ef98ed6d561dfe2020e24a9fd08eaa2b", size = 86858, upload-time = "2025-10-06T14:09:41.068Z" }, + { url = "https://files.pythonhosted.org/packages/2b/13/88b78b93ad3f2f0b78e13bfaaa24d11cbc746e93fe76d8c06bf139615646/yarl-1.22.0-cp311-cp311-win_arm64.whl", hash = "sha256:b6a6f620cfe13ccec221fa312139135166e47ae169f8253f72a0abc0dae94376", size = 81637, upload-time = "2025-10-06T14:09:42.712Z" }, + { url = "https://files.pythonhosted.org/packages/75/ff/46736024fee3429b80a165a732e38e5d5a238721e634ab41b040d49f8738/yarl-1.22.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e340382d1afa5d32b892b3ff062436d592ec3d692aeea3bef3a5cfe11bbf8c6f", size = 142000, upload-time = "2025-10-06T14:09:44.631Z" }, + { url = "https://files.pythonhosted.org/packages/5a/9a/b312ed670df903145598914770eb12de1bac44599549b3360acc96878df8/yarl-1.22.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f1e09112a2c31ffe8d80be1b0988fa6a18c5d5cad92a9ffbb1c04c91bfe52ad2", size = 94338, upload-time = "2025-10-06T14:09:46.372Z" }, + { url = "https://files.pythonhosted.org/packages/ba/f5/0601483296f09c3c65e303d60c070a5c19fcdbc72daa061e96170785bc7d/yarl-1.22.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:939fe60db294c786f6b7c2d2e121576628468f65453d86b0fe36cb52f987bd74", size = 94909, upload-time = "2025-10-06T14:09:48.648Z" }, + { url = "https://files.pythonhosted.org/packages/60/41/9a1fe0b73dbcefce72e46cf149b0e0a67612d60bfc90fb59c2b2efdfbd86/yarl-1.22.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e1651bf8e0398574646744c1885a41198eba53dc8a9312b954073f845c90a8df", size = 372940, upload-time = "2025-10-06T14:09:50.089Z" }, + { url = "https://files.pythonhosted.org/packages/17/7a/795cb6dfee561961c30b800f0ed616b923a2ec6258b5def2a00bf8231334/yarl-1.22.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b8a0588521a26bf92a57a1705b77b8b59044cdceccac7151bd8d229e66b8dedb", size = 345825, upload-time = "2025-10-06T14:09:52.142Z" }, + { url = "https://files.pythonhosted.org/packages/d7/93/a58f4d596d2be2ae7bab1a5846c4d270b894958845753b2c606d666744d3/yarl-1.22.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:42188e6a615c1a75bcaa6e150c3fe8f3e8680471a6b10150c5f7e83f47cc34d2", size = 386705, upload-time = "2025-10-06T14:09:54.128Z" }, + { url = "https://files.pythonhosted.org/packages/61/92/682279d0e099d0e14d7fd2e176bd04f48de1484f56546a3e1313cd6c8e7c/yarl-1.22.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f6d2cb59377d99718913ad9a151030d6f83ef420a2b8f521d94609ecc106ee82", size = 396518, upload-time = "2025-10-06T14:09:55.762Z" }, + { url = "https://files.pythonhosted.org/packages/db/0f/0d52c98b8a885aeda831224b78f3be7ec2e1aa4a62091f9f9188c3c65b56/yarl-1.22.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50678a3b71c751d58d7908edc96d332af328839eea883bb554a43f539101277a", size = 377267, upload-time = "2025-10-06T14:09:57.958Z" }, + { url = "https://files.pythonhosted.org/packages/22/42/d2685e35908cbeaa6532c1fc73e89e7f2efb5d8a7df3959ea8e37177c5a3/yarl-1.22.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e8fbaa7cec507aa24ea27a01456e8dd4b6fab829059b69844bd348f2d467124", size = 365797, upload-time = "2025-10-06T14:09:59.527Z" }, + { url = "https://files.pythonhosted.org/packages/a2/83/cf8c7bcc6355631762f7d8bdab920ad09b82efa6b722999dfb05afa6cfac/yarl-1.22.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:433885ab5431bc3d3d4f2f9bd15bfa1614c522b0f1405d62c4f926ccd69d04fa", size = 365535, upload-time = "2025-10-06T14:10:01.139Z" }, + { url = "https://files.pythonhosted.org/packages/25/e1/5302ff9b28f0c59cac913b91fe3f16c59a033887e57ce9ca5d41a3a94737/yarl-1.22.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b790b39c7e9a4192dc2e201a282109ed2985a1ddbd5ac08dc56d0e121400a8f7", size = 382324, upload-time = "2025-10-06T14:10:02.756Z" }, + { url = "https://files.pythonhosted.org/packages/bf/cd/4617eb60f032f19ae3a688dc990d8f0d89ee0ea378b61cac81ede3e52fae/yarl-1.22.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:31f0b53913220599446872d757257be5898019c85e7971599065bc55065dc99d", size = 383803, upload-time = "2025-10-06T14:10:04.552Z" }, + { url = "https://files.pythonhosted.org/packages/59/65/afc6e62bb506a319ea67b694551dab4a7e6fb7bf604e9bd9f3e11d575fec/yarl-1.22.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a49370e8f711daec68d09b821a34e1167792ee2d24d405cbc2387be4f158b520", size = 374220, upload-time = "2025-10-06T14:10:06.489Z" }, + { url = "https://files.pythonhosted.org/packages/e7/3d/68bf18d50dc674b942daec86a9ba922d3113d8399b0e52b9897530442da2/yarl-1.22.0-cp312-cp312-win32.whl", hash = "sha256:70dfd4f241c04bd9239d53b17f11e6ab672b9f1420364af63e8531198e3f5fe8", size = 81589, upload-time = "2025-10-06T14:10:09.254Z" }, + { url = "https://files.pythonhosted.org/packages/c8/9a/6ad1a9b37c2f72874f93e691b2e7ecb6137fb2b899983125db4204e47575/yarl-1.22.0-cp312-cp312-win_amd64.whl", hash = "sha256:8884d8b332a5e9b88e23f60bb166890009429391864c685e17bd73a9eda9105c", size = 87213, upload-time = "2025-10-06T14:10:11.369Z" }, + { url = "https://files.pythonhosted.org/packages/44/c5/c21b562d1680a77634d748e30c653c3ca918beb35555cff24986fff54598/yarl-1.22.0-cp312-cp312-win_arm64.whl", hash = "sha256:ea70f61a47f3cc93bdf8b2f368ed359ef02a01ca6393916bc8ff877427181e74", size = 81330, upload-time = "2025-10-06T14:10:13.112Z" }, + { url = "https://files.pythonhosted.org/packages/73/ae/b48f95715333080afb75a4504487cbe142cae1268afc482d06692d605ae6/yarl-1.22.0-py3-none-any.whl", hash = "sha256:1380560bdba02b6b6c90de54133c81c9f2a453dee9912fe58c1dcced1edb7cff", size = 46814, upload-time = "2025-10-06T14:12:53.872Z" }, ] [[package]] From adf9ec5360154b9238fc7a91b8722005e17a32a7 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 28 Dec 2025 15:39:20 -0800 Subject: [PATCH 704/910] tools: speed up Route() (#36963) * tools: speed up Route() * cleanup --- tools/lib/route.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/tools/lib/route.py b/tools/lib/route.py index 1fc26fb99..98334a06c 100644 --- a/tools/lib/route.py +++ b/tools/lib/route.py @@ -23,7 +23,6 @@ class FileName: class Route: def __init__(self, name, data_dir=None): - self._metadata = None self._name = RouteName(name) self.files = None if data_dir is not None: @@ -32,13 +31,6 @@ class Route: self._segments = self._get_segments_remote() self.max_seg_number = self._segments[-1].name.segment_num - @property - def metadata(self): - if not self._metadata: - api = CommaApi(get_token()) - self._metadata = api.get('v1/route/' + self.name.canonical_name) - return self._metadata - @property def name(self): return self._name @@ -90,7 +82,6 @@ class Route: url if fn in FileName.DCAMERA else segments[segment_name].dcamera_path, url if fn in FileName.ECAMERA else segments[segment_name].ecamera_path, url if fn in FileName.QCAMERA else segments[segment_name].qcamera_path, - self.metadata['url'], ) else: segments[segment_name] = Segment( @@ -101,7 +92,6 @@ class Route: url if fn in FileName.DCAMERA else None, url if fn in FileName.ECAMERA else None, url if fn in FileName.QCAMERA else None, - self.metadata['url'], ) return sorted(segments.values(), key=lambda seg: seg.name.segment_num) @@ -167,7 +157,7 @@ class Route: except StopIteration: qcamera_path = None - segments.append(Segment(segment, log_path, qlog_path, camera_path, dcamera_path, ecamera_path, qcamera_path, self.metadata['url'])) + segments.append(Segment(segment, log_path, qlog_path, camera_path, dcamera_path, ecamera_path, qcamera_path)) if len(segments) == 0: raise ValueError(f'Could not find segments for route {self.name.canonical_name} in data directory {data_dir}') @@ -175,10 +165,9 @@ class Route: class Segment: - def __init__(self, name, log_path, qlog_path, camera_path, dcamera_path, ecamera_path, qcamera_path, url): + def __init__(self, name, log_path, qlog_path, camera_path, dcamera_path, ecamera_path, qcamera_path): 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 @@ -190,6 +179,18 @@ class Segment: def name(self): return self._name + @staticmethod + @cache + def _get_route_metadata(route_name: str): + api = CommaApi(get_token()) + return api.get(f'v1/route/{route_name}') + + @property + def url(self): + route_name = self._name.route_name.canonical_name + metadata = self._get_route_metadata(route_name) + return f'{metadata["url"]}/{self._name.segment_num}' + @property def events(self): if not self._events: From 63c9a85c6a15785d9c5182c54b770b4822cfc435 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 28 Dec 2025 21:38:54 -0800 Subject: [PATCH 705/910] FrameReader: use HW accel if available (#36964) * FrameReader: add macOS hw accel * sys * more platforms * logging --- tools/lib/framereader.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tools/lib/framereader.py b/tools/lib/framereader.py index 30ce1f80f..64e2f9f43 100644 --- a/tools/lib/framereader.py +++ b/tools/lib/framereader.py @@ -1,6 +1,8 @@ import os import subprocess import json +import logging +from functools import cache from collections.abc import Iterator from collections import OrderedDict @@ -9,11 +11,24 @@ from openpilot.tools.lib.filereader import FileReader, resolve_name from openpilot.tools.lib.exceptions import DataUnreadableError from openpilot.tools.lib.vidindex import hevc_index +logger = logging.getLogger("tools") HEVC_SLICE_B = 0 HEVC_SLICE_P = 1 HEVC_SLICE_I = 2 +@cache +def get_hw_accel() -> list[str]: + """Detect and return the best available ffmpeg hardware acceleration.""" + priority = ("videotoolbox", "cuda", "vaapi", "d3d11va") + result = subprocess.run(["ffmpeg", "-hwaccels"], capture_output=True, text=True, timeout=5) + for accel in priority: + if accel in result.stdout.lower(): + logger.info(f"HW accelerated video decode found, using ffmpeg's {accel}") + return ["-hwaccel", accel] + logger.warning("no HW accelerated video found with `ffmpeg -hwaccels`. falling back to ffmpeg CPU decode") + return [] + class LRUCache: def __init__(self, capacity: int): @@ -46,6 +61,7 @@ def decompress_video_data(rawdat, w, h, pix_fmt="rgb24", vid_fmt='hevc') -> np.n threads = os.getenv("FFMPEG_THREADS", "0") args = ["ffmpeg", "-v", "quiet", "-threads", threads, + *get_hw_accel(), "-c:v", "hevc", "-vsync", "0", "-f", vid_fmt, @@ -171,3 +187,7 @@ class FrameReader: self.fidx, frame = next(self.it) self._cache[self.fidx] = frame return self._cache[fidx] + + +if __name__ == "__main__": + get_hw_accel() From 9442bc9aeccf979a90fcc7e1e1b291ea630559df Mon Sep 17 00:00:00 2001 From: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Mon, 29 Dec 2025 14:55:39 -0700 Subject: [PATCH 706/910] modeld_v2: planplus model tuning (#1620) * modeld: planplus model tuning * little more * final --------- Co-authored-by: Jason Wen --- common/params_keys.h | 1 + sunnypilot/modeld_v2/modeld.py | 6 +- .../modeld_v2/tests/test_recovery_power.py | 61 +++++++++++++++++++ sunnypilot/sunnylink/params_metadata.json | 7 +++ 4 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 sunnypilot/modeld_v2/tests/test_recovery_power.py diff --git a/common/params_keys.h b/common/params_keys.h index 7cfdf7540..89f70471c 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -224,6 +224,7 @@ inline static std::unordered_map keys = { {"LagdValueCache", {PERSISTENT, FLOAT, "0.2"}}, {"LaneTurnDesire", {PERSISTENT | BACKUP, BOOL, "0"}}, {"LaneTurnValue", {PERSISTENT | BACKUP, FLOAT, "19.0"}}, + {"PlanplusControl", {PERSISTENT | BACKUP, FLOAT, "1.0"}}, // mapd {"MapAdvisorySpeedLimit", {CLEAR_ON_ONROAD_TRANSITION, FLOAT}}, diff --git a/sunnypilot/modeld_v2/modeld.py b/sunnypilot/modeld_v2/modeld.py index 82eb099e7..b3b1d35fd 100755 --- a/sunnypilot/modeld_v2/modeld.py +++ b/sunnypilot/modeld_v2/modeld.py @@ -28,7 +28,6 @@ from openpilot.sunnypilot.models.helpers import get_active_bundle from openpilot.sunnypilot.models.runners.helpers import get_model_runner PROCESS_NAME = "selfdrive.modeld.modeld_tinygrad" -RECOVERY_POWER = 1.0 # The higher this number the more aggressively the model will recover to lanecenter, too high and it will ping-pong class FrameMeta: @@ -63,6 +62,7 @@ class ModelState(ModelStateBase): self.LAT_SMOOTH_SECONDS = float(overrides.get('lat', ".0")) self.LONG_SMOOTH_SECONDS = float(overrides.get('long', ".0")) self.MIN_LAT_CONTROL_SPEED = 0.3 + self.PLANPLUS_CONTROL: float = 1.0 buffer_length = 5 if self.model_runner.is_20hz else 2 self.frames = {name: DrivingModelFrame(context, buffer_length) for name in self.model_runner.vision_input_names} @@ -158,7 +158,8 @@ class ModelState(ModelStateBase): lat_action_t: float, long_action_t: float, v_ego: float) -> log.ModelDataV2.Action: plan = model_output['plan'][0] if 'planplus' in model_output: - plan = plan + RECOVERY_POWER*model_output['planplus'][0] + recovery_power = self.PLANPLUS_CONTROL * (0.75 if v_ego > 20.0 else 1.0) + plan = plan + recovery_power * model_output['planplus'][0] desired_accel, should_stop = get_accel_from_plan(plan[:, Plan.VELOCITY][:, 0], plan[:, Plan.ACCELERATION][:, 0], self.constants.T_IDXS, action_t=long_action_t) desired_accel = smooth_value(desired_accel, prev_action.desiredAcceleration, self.LONG_SMOOTH_SECONDS) @@ -283,6 +284,7 @@ def main(demo=False): v_ego = max(sm["carState"].vEgo, 0.) if sm.frame % 60 == 0: model.lat_delay = get_lat_delay(params, sm["liveDelay"].lateralDelay) + model.PLANPLUS_CONTROL = params.get("PlanplusControl", return_default=True) lat_delay = model.lat_delay + model.LAT_SMOOTH_SECONDS if sm.updated["liveCalibration"] and sm.seen['roadCameraState'] and sm.seen['deviceState']: device_from_calib_euler = np.array(sm["liveCalibration"].rpyCalib, dtype=np.float32) diff --git a/sunnypilot/modeld_v2/tests/test_recovery_power.py b/sunnypilot/modeld_v2/tests/test_recovery_power.py new file mode 100644 index 000000000..e38b2c86d --- /dev/null +++ b/sunnypilot/modeld_v2/tests/test_recovery_power.py @@ -0,0 +1,61 @@ +import numpy as np + +from cereal import log + +from openpilot.sunnypilot.modeld_v2.constants import Plan +from openpilot.sunnypilot.modeld_v2.modeld import ModelState +import openpilot.sunnypilot.modeld_v2.modeld as modeld + + +class MockStruct: + def __init__(self, **kwargs): + for k, v in kwargs.items(): + setattr(self, k, v) + + +def test_recovery_power_scaling(): + state = MockStruct( + PLANPLUS_CONTROL=1.0, + LONG_SMOOTH_SECONDS=0.3, + LAT_SMOOTH_SECONDS=0.1, + MIN_LAT_CONTROL_SPEED=0.3, + mlsim=True, + generation=12, + constants=MockStruct(T_IDXS=np.arange(100), DESIRE_LEN=8) + ) + prev_action = log.ModelDataV2.Action() + recorded_vel: list = [] + + def mock_accel(plan_vel, plan_accel, t_idxs, action_t=0.0): + recorded_vel.append(plan_vel.copy()) + return 0.0, False + + modeld.get_accel_from_plan = mock_accel + modeld.get_curvature_from_output = lambda *args: 0.0 + plan = np.random.rand(1, 100, 15).astype(np.float32) + planplus = np.random.rand(1, 100, 15).astype(np.float32) + + model_output: dict = { + 'plan': plan.copy(), + 'planplus': planplus.copy() + } + + test_cases: list = [ + # (control, v_ego, expected_factor) + (0.55, 20.0, 1.0), + (1.0, 25.0, .75), + (1.5, 25.1, 0.75), + (2.0, 20.0, 1.0), + (0.75, 19.0, 1.0), + (0.8, 25.1, 0.75), + ] + + for control, v_ego, factor in test_cases: + state.PLANPLUS_CONTROL = control + recorded_vel.clear() + ModelState.get_action_from_model(state, model_output, prev_action, 0.0, 0.0, v_ego) + + expected_recovery_power = control * factor + expected_plan_vel = plan[0, :, Plan.VELOCITY][:, 0] + expected_recovery_power * planplus[0, :, Plan.VELOCITY][:, 0] + + np.testing.assert_allclose(recorded_vel[0], expected_plan_vel, rtol=1e-5, atol=1e-6) diff --git a/sunnypilot/sunnylink/params_metadata.json b/sunnypilot/sunnylink/params_metadata.json index bbefc3d0c..f335457fb 100644 --- a/sunnypilot/sunnylink/params_metadata.json +++ b/sunnypilot/sunnylink/params_metadata.json @@ -826,6 +826,13 @@ "title": "Panda Som Reset Triggered", "description": "" }, + "PlanplusControl": { + "title": "Plan Plus Controls", + "description": "Adjust planplus model recentering strength. The higher this number the more aggressively the model will recover to lanecenter, too high and it will ping-pong", + "min": 0.0, + "max": 2.0, + "step": 0.1 + }, "PrimeType": { "title": "Prime Type", "description": "" From 6df313b974ef2d7a779c23b1540b30c5eaed283c Mon Sep 17 00:00:00 2001 From: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Mon, 29 Dec 2025 15:01:58 -0700 Subject: [PATCH 707/910] modeld_v2: remove dead test (#1621) Co-authored-by: Jason Wen --- sunnypilot/modeld_v2/tests/test_modeld.py | 102 ---------------------- 1 file changed, 102 deletions(-) delete mode 100644 sunnypilot/modeld_v2/tests/test_modeld.py diff --git a/sunnypilot/modeld_v2/tests/test_modeld.py b/sunnypilot/modeld_v2/tests/test_modeld.py deleted file mode 100644 index 6927c9e47..000000000 --- a/sunnypilot/modeld_v2/tests/test_modeld.py +++ /dev/null @@ -1,102 +0,0 @@ -import numpy as np -import random - -import cereal.messaging as messaging -from msgq.visionipc import VisionIpcServer, VisionStreamType -from opendbc.car.car_helpers import get_demo_car_params -from openpilot.common.params import Params -from openpilot.common.transformations.camera import DEVICE_CAMERAS -from openpilot.common.realtime import DT_MDL -from openpilot.system.manager.process_config import managed_processes -from openpilot.selfdrive.test.process_replay.vision_meta import meta_from_camera_state - -CAM = DEVICE_CAMERAS[("tici", "ar0231")].fcam -IMG = np.zeros(int(CAM.width*CAM.height*(3/2)), dtype=np.uint8) -IMG_BYTES = IMG.flatten().tobytes() - - -class TestModeld: - - def setup_method(self): - self.vipc_server = VisionIpcServer("camerad") - self.vipc_server.create_buffers(VisionStreamType.VISION_STREAM_ROAD, 40, CAM.width, CAM.height) - self.vipc_server.create_buffers(VisionStreamType.VISION_STREAM_DRIVER, 40, CAM.width, CAM.height) - self.vipc_server.create_buffers(VisionStreamType.VISION_STREAM_WIDE_ROAD, 40, CAM.width, CAM.height) - self.vipc_server.start_listener() - Params().put("CarParams", get_demo_car_params().to_bytes()) - - self.sm = messaging.SubMaster(['modelV2', 'cameraOdometry']) - self.pm = messaging.PubMaster(['roadCameraState', 'wideRoadCameraState', 'liveCalibration']) - - managed_processes['modeld'].start() - self.pm.wait_for_readers_to_update("roadCameraState", 10) - - def teardown_method(self): - managed_processes['modeld'].stop() - del self.vipc_server - - def _send_frames(self, frame_id, cams=None): - if cams is None: - cams = ('roadCameraState', 'wideRoadCameraState') - - cs = None - for cam in cams: - msg = messaging.new_message(cam) - cs = getattr(msg, cam) - cs.frameId = frame_id - cs.timestampSof = int((frame_id * DT_MDL) * 1e9) - cs.timestampEof = int(cs.timestampSof + (DT_MDL * 1e9)) - cam_meta = meta_from_camera_state(cam) - - self.pm.send(msg.which(), msg) - self.vipc_server.send(cam_meta.stream, IMG_BYTES, cs.frameId, - cs.timestampSof, cs.timestampEof) - return cs - - def _wait(self): - self.sm.update(5000) - if self.sm['modelV2'].frameId != self.sm['cameraOdometry'].frameId: - self.sm.update(1000) - - def test_modeld(self): - for n in range(1, 500): - cs = self._send_frames(n) - self._wait() - - mdl = self.sm['modelV2'] - assert mdl.frameId == n - assert mdl.frameIdExtra == n - assert mdl.timestampEof == cs.timestampEof - assert mdl.frameAge == 0 - assert mdl.frameDropPerc == 0 - - odo = self.sm['cameraOdometry'] - assert odo.frameId == n - assert odo.timestampEof == cs.timestampEof - - def test_dropped_frames(self): - """ - modeld should only run on consecutive road frames - """ - frame_id = -1 - road_frames = list() - for n in range(1, 50): - if (random.random() < 0.1) and n > 3: - cams = random.choice([(), ('wideRoadCameraState', )]) - self._send_frames(n, cams) - else: - self._send_frames(n) - road_frames.append(n) - self._wait() - - if len(road_frames) < 3 or road_frames[-1] - road_frames[-2] == 1: - frame_id = road_frames[-1] - - mdl = self.sm['modelV2'] - odo = self.sm['cameraOdometry'] - assert mdl.frameId == frame_id - assert mdl.frameIdExtra == frame_id - assert odo.frameId == frame_id - if n != frame_id: - assert not self.sm.updated['modelV2'] - assert not self.sm.updated['cameraOdometry'] From a5348b8679c45fd3e903bae6fe093bcc9fcbd971 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 29 Dec 2025 16:41:52 -0800 Subject: [PATCH 708/910] crcmod -> crcmod-plus (#36968) --- pyproject.toml | 2 +- uv.lock | 90 +++++++++++++++++++++++++++++--------------------- 2 files changed, 53 insertions(+), 39 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index bb4ac0e8d..e04889449 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,7 @@ dependencies = [ "pyserial", # pigeond + qcomgpsd "requests", # many one-off uses "sympy", # rednose + friends - "crcmod", # cars + qcomgpsd + "crcmod-plus", # cars + qcomgpsd "tqdm", # cars (fw_versions.py) on start + many one-off uses # hardwared diff --git a/uv.lock b/uv.lock index a5b529e52..a40167bb9 100644 --- a/uv.lock +++ b/uv.lock @@ -416,10 +416,24 @@ wheels = [ ] [[package]] -name = "crcmod" -version = "1.7" +name = "crcmod-plus" +version = "2.3.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6b/b0/e595ce2a2527e169c3bcd6c33d2473c1918e0b7f6826a043ca1245dd4e5b/crcmod-1.7.tar.gz", hash = "sha256:dc7051a0db5f2bd48665a990d3ec1cc305a466a77358ca4492826f41f283601e", size = 89670, upload-time = "2010-06-27T14:35:29.538Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/0c/71733bbaf38e9f1eaecfdf7f8e350993f3dcac208a5297c41503ae66e513/crcmod_plus-2.3.1.tar.gz", hash = "sha256:732ffe3c3ce3ef9b272e1827d8fb894590c4d6ff553f2a2b41ae30f4f94b0f5d", size = 22319, upload-time = "2025-10-10T22:14:21.691Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/e0/2dad2e6f0cd4914b4144496d9785780ec820e200816c080df785cfa34da6/crcmod_plus-2.3.1-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:b7e35e0f7d93d7571c2c9c3d6760e456999ea4c1eae5ead6acac247b5a79e469", size = 23279, upload-time = "2025-10-10T22:13:47.281Z" }, + { url = "https://files.pythonhosted.org/packages/66/76/53c0b65b9679b903f98fc54efa32b0e5a19634712a45200c7a80674aa6f5/crcmod_plus-2.3.1-cp311-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6853243120db84677b94b625112116f0ef69cd581741d20de58dce4c34242654", size = 20185, upload-time = "2025-10-10T22:13:48.06Z" }, + { url = "https://files.pythonhosted.org/packages/98/79/2b4dc9bb26394873d7699737124408b5106264ae33053fdec600e9a9fa65/crcmod_plus-2.3.1-cp311-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:17735bc4e944d552ea18c8609fc6d08a5e64ee9b29cc216ba4d623754029cc3a", size = 26999, upload-time = "2025-10-10T22:13:48.854Z" }, + { url = "https://files.pythonhosted.org/packages/bb/e8/f5d66778b5a1bff915807016561a02b5cebf6b3840fb8a2be40bbb0c8575/crcmod_plus-2.3.1-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8ac755040a2a35f43ab331978c48a9acb4ff64b425f282a296be467a410f00c3", size = 27536, upload-time = "2025-10-10T22:13:49.956Z" }, + { url = "https://files.pythonhosted.org/packages/f3/2c/0113ad30cadad40c22eef08c0f2618f2446dd282f02268fecbcfc9fda3c1/crcmod_plus-2.3.1-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7bdcfb838ca093ca673a3bbb37f62d1e5ec7182e00cc5ee2d00759f9f9f8ab11", size = 27385, upload-time = "2025-10-10T22:13:50.765Z" }, + { url = "https://files.pythonhosted.org/packages/8e/ba/501ef1b02119402cf1a31c01eb2cb8399660bca863c2f4dd3dc060220284/crcmod_plus-2.3.1-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9166bc3c9b5e7b07b4e6854cac392b4a451b31d58d3950e48c140ab7b5d05394", size = 27135, upload-time = "2025-10-10T22:13:51.889Z" }, + { url = "https://files.pythonhosted.org/packages/49/90/d4556c9db69c83e726c5b88da3d656fdaac7d60c4d27b43cb939bed80069/crcmod_plus-2.3.1-cp311-abi3-win32.whl", hash = "sha256:cb99b694cce5c862560cf332a8b5e793620e28f0de3726995608bbd6f9b6e09a", size = 22384, upload-time = "2025-10-10T22:13:53.016Z" }, + { url = "https://files.pythonhosted.org/packages/4d/7e/57bb97a8c7b4e19900744f58b67dc83bc9c83aaac670deeede9fb3bfab6a/crcmod_plus-2.3.1-cp311-abi3-win_amd64.whl", hash = "sha256:82b0f7e968c430c5a80fe0fc59e75cb54f2e84df2ed0cee5a3ff9cadfbf8a220", size = 22912, upload-time = "2025-10-10T22:13:53.849Z" }, + { url = "https://files.pythonhosted.org/packages/76/66/419ae3991bb68943cb752e2f4d317c555e3f02a298dd498f26113874ee59/crcmod_plus-2.3.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:9397324da1be2729f894744d9031a21ed97584c17fb0289e69e0c3c60916fc5f", size = 19880, upload-time = "2025-10-10T22:14:17.269Z" }, + { url = "https://files.pythonhosted.org/packages/18/f0/d10c9b859927b2cdc38eafc33c8b66e4ede02eaa174df4575681dab5a0f1/crcmod_plus-2.3.1-pp311-pypy311_pp73-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:073c7a3b832652e66c41c8b8705eaecda704d1cbe850b9fa05fdee36cd50745a", size = 21120, upload-time = "2025-10-10T22:14:18.117Z" }, + { url = "https://files.pythonhosted.org/packages/6c/68/cbd8f1707b37b80f9a0bf643e04747b0196f69cf065b52ed56639afbecef/crcmod_plus-2.3.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e5f4c62553f772ea7ae12d9484801b752622c9c288e49ee7ea34a20b94e4920", size = 21698, upload-time = "2025-10-10T22:14:20.044Z" }, + { url = "https://files.pythonhosted.org/packages/41/1b/4ab1681ecbfc48d7e4641fb178c97374eb475ae4109255bdd832110cbbe2/crcmod_plus-2.3.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:5e80a9860f66f339956f540d86a768f4fe8c8bfcb139811f14be864425c48d64", size = 23289, upload-time = "2025-10-10T22:14:20.875Z" }, +] [[package]] name = "cryptography" @@ -678,10 +692,10 @@ name = "gymnasium" version = "1.2.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cloudpickle" }, - { name = "farama-notifications" }, - { name = "numpy" }, - { name = "typing-extensions" }, + { 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'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/76/59/653a9417d98ed3e29ef9734ba52c3495f6c6823b8d5c0c75369f25111708/gymnasium-1.2.3.tar.gz", hash = "sha256:2b2cb5b5fbbbdf3afb9f38ca952cc48aa6aa3e26561400d940747fda3ad42509", size = 829230, upload-time = "2025-12-18T16:51:10.234Z" } wheels = [ @@ -1001,22 +1015,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" }, - { 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" }, + { 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'" }, ] 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:d0afaf3b005e35e14b929d5491d2d5b64562d0c1cd5093ba969fb63908670dd4" }, @@ -1298,7 +1312,7 @@ dependencies = [ { name = "aiortc" }, { name = "casadi" }, { name = "cffi" }, - { name = "crcmod" }, + { name = "crcmod-plus" }, { name = "cython" }, { name = "future-fstrings" }, { name = "inputs" }, @@ -1392,7 +1406,7 @@ requires-dist = [ { name = "cffi" }, { name = "codespell", marker = "extra == 'testing'" }, { name = "coverage", marker = "extra == 'testing'" }, - { name = "crcmod" }, + { name = "crcmod-plus" }, { name = "cython" }, { name = "dbus-next", marker = "extra == 'dev'" }, { name = "dearpygui", marker = "(platform_machine != 'aarch64' and extra == 'tools') or (sys_platform != 'linux' and extra == 'tools')", specifier = ">=2.1.0" }, @@ -1493,8 +1507,8 @@ name = "panda3d-gltf" version = "0.13" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "panda3d" }, - { name = "panda3d-simplepbr" }, + { name = "panda3d", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "panda3d-simplepbr", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, ] 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 = [ @@ -1506,8 +1520,8 @@ name = "panda3d-simplepbr" version = "0.13.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "panda3d" }, - { name = "typing-extensions" }, + { name = "panda3d", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "typing-extensions", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, ] 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 = [ @@ -4287,10 +4301,10 @@ name = "pyopencl" version = "2025.2.7" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy" }, - { name = "platformdirs" }, - { name = "pytools" }, - { name = "typing-extensions" }, + { 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 = "typing-extensions", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f8/cb/8927052160bc0d3bd1123a645aaf57f696da364216b57b49f92ba0777bcc/pyopencl-2025.2.7.tar.gz", hash = "sha256:a68d92eb2970418b1a7ca45aff71984c02d2e4261e01402b273f257b5d6d7511", size = 444787, upload-time = "2025-10-28T14:23:15.497Z" } wheels = [ @@ -4512,9 +4526,9 @@ name = "pytools" version = "2025.2.5" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "platformdirs" }, - { name = "siphash24" }, - { name = "typing-extensions" }, + { 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'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c3/7b/f885a57e61ded45b5b10ca60f0b7575c9fb9a282e7513d0e23a33ee647e1/pytools-2025.2.5.tar.gz", hash = "sha256:a7f5350644d46d98ee9c7e67b4b41693308aa0f5e9b188d8f0694b27dc94e3a2", size = 85594, upload-time = "2025-10-07T15:53:30.49Z" } wheels = [ @@ -4846,7 +4860,7 @@ name = "shapely" version = "2.1.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy" }, + { name = "numpy", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/4d/bc/0989043118a27cccb4e906a46b7565ce36ca7b57f5a18b78f4f1b0f72d9d/shapely-2.1.2.tar.gz", hash = "sha256:2ed4ecb28320a433db18a5bf029986aa8afcfd740745e78847e330d5d94922a9", size = 315489, upload-time = "2025-09-24T13:51:41.432Z" } wheels = [ @@ -5096,7 +5110,7 @@ name = "yapf" version = "0.43.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "platformdirs" }, + { name = "platformdirs", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/23/97/b6f296d1e9cc1ec25c7604178b48532fa5901f721bcf1b8d8148b13e5588/yapf-0.43.0.tar.gz", hash = "sha256:00d3aa24bfedff9420b2e0d5d9f5ab6d9d4268e72afbf59bb3fa542781d5218e", size = 254907, upload-time = "2024-11-14T00:11:41.584Z" } wheels = [ From edeede5e82282369e0e5f8e302de0863159efb3d Mon Sep 17 00:00:00 2001 From: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Mon, 29 Dec 2025 20:03:08 -0700 Subject: [PATCH 709/910] modeld_v2: conditional model compilation for metadrive testing (#1623) * modeld_v2: conditional model compilation for PC * full send * shebang --------- Co-authored-by: Jason Wen --- sunnypilot/modeld_v2/SConscript | 36 +++++++++ sunnypilot/modeld_v2/install_models_pc.py | 89 +++++++++++++++++++++++ sunnypilot/models/runners/helpers.py | 10 +-- 3 files changed, 126 insertions(+), 9 deletions(-) create mode 100755 sunnypilot/modeld_v2/install_models_pc.py diff --git a/sunnypilot/modeld_v2/SConscript b/sunnypilot/modeld_v2/SConscript index 4c04b7382..750a242ad 100644 --- a/sunnypilot/modeld_v2/SConscript +++ b/sunnypilot/modeld_v2/SConscript @@ -1,3 +1,4 @@ +import os import glob Import('env', 'envCython', 'arch', 'cereal', 'messaging', 'common', 'visionipc', 'transformations') @@ -28,3 +29,38 @@ for pathdef, fn in {'TRANSFORM': 'transforms/transform.cl', 'LOADYUV': 'transfor cython_libs = envCython["LIBS"] + libs commonmodel_lib = lenv.Library('commonmodel', common_src) lenvCython.Program('models/commonmodel_pyx.so', 'models/commonmodel_pyx.pyx', LIBS=[commonmodel_lib, *cython_libs], FRAMEWORKS=frameworks) +tinygrad_files = ["#"+x for x in glob.glob(env.Dir("#tinygrad_repo").relpath + "/**", recursive=True, root_dir=env.Dir("#").abspath) if 'pycache' not in x] + +# Get model metadata +PC = not os.path.isfile('/TICI') +if PC: + inputs = tinygrad_files + [File(Dir("#sunnypilot/modeld_v2").File("install_models_pc.py").abspath)] + outputs = [] + model_dir = Dir("models").abspath + cmd = f'python3 {Dir("#sunnypilot/modeld_v2").abspath}/install_models_pc.py {model_dir}' + + for model_name in ['supercombo', 'driving_vision', 'driving_policy']: + if File(f"models/{model_name}.onnx").exists(): + inputs.append(File(f"models/{model_name}.onnx")) + inputs.append(File(f"models/{model_name}_tinygrad.pkl")) + outputs.append(File(f"models/{model_name}_metadata.pkl")) + if outputs: + lenv.Command(outputs, inputs, cmd) + +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 ['supercombo', 'driving_vision', 'driving_policy']: + if File(f"models/{model_name}.onnx").exists(): + flags = { + 'larch64': 'DEV=QCOM', + 'Darwin': f'DEV=CPU HOME={os.path.expanduser("~")} IMAGE=0', # tinygrad calls brew which needs a $HOME in the env + }.get(arch, 'DEV=CPU CPU_LLVM=1 IMAGE=0') + tg_compile(flags, model_name) diff --git a/sunnypilot/modeld_v2/install_models_pc.py b/sunnypilot/modeld_v2/install_models_pc.py new file mode 100755 index 000000000..3f964dc28 --- /dev/null +++ b/sunnypilot/modeld_v2/install_models_pc.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python3 +import sys +import shutil +import pickle +import codecs +import onnx +from pathlib import Path + +from openpilot.system.hardware.hw import Paths + + +def get_name_and_shape(value_info): + shape = tuple([int(dim.dim_value) for dim in value_info.type.tensor_type.shape.dim]) + return value_info.name, shape + + +def get_metadata_value_by_name(model, name): + for prop in model.metadata_props: + if prop.key == name: + return prop.value + return None + + +def generate_metadata_pkl(model_path, output_path): + try: + model = onnx.load(str(model_path)) + output_slices = get_metadata_value_by_name(model, 'output_slices') + + if output_slices: + metadata = { + 'model_checkpoint': get_metadata_value_by_name(model, 'model_checkpoint'), + 'output_slices': pickle.loads(codecs.decode(output_slices.encode(), "base64")), + 'input_shapes': dict([get_name_and_shape(x) for x in model.graph.input]), + 'output_shapes': dict([get_name_and_shape(x) for x in model.graph.output]) + } + with open(output_path, 'wb') as f: + pickle.dump(metadata, f) + return True + else: + return False + except Exception: + return False + + +def install_models(model_dir): + model_dir = Path(model_dir) + models = ["driving_policy", "driving_vision"] + found_models = [] + + for model in models: + if (model_dir / f"{model}.onnx").exists(): + found_models.append(model) + + if not found_models: + return + + try: + custom_name = input(f"Found models ({', '.join(found_models)}). Enter model short name (e.g. wmiv4): ").strip() + except EOFError: + return + + if not custom_name: + print("No name provided, skipping installation.") + return + + dest_dir = Path(Paths.model_root()) + dest_dir.mkdir(parents=True, exist_ok=True) + + for model in found_models: + onnx_path = model_dir / f"{model}.onnx" + tinygrad_pkl = model_dir / f"{model}_tinygrad.pkl" + metadata_pkl = model_dir / f"{model}_metadata.pkl" + + if not metadata_pkl.exists(): + generate_metadata_pkl(onnx_path, metadata_pkl) + + dest_tinygrad = dest_dir / f"{model}_{custom_name}_tinygrad.pkl" + dest_metadata = dest_dir / f"{model}_{custom_name}_metadata.pkl" + + if tinygrad_pkl.exists(): + shutil.move(str(tinygrad_pkl), str(dest_tinygrad)) + if metadata_pkl.exists(): + shutil.move(str(metadata_pkl), str(dest_metadata)) + +if __name__ == "__main__": + if len(sys.argv) < 2: + print("Usage: install_models_pc.py ") + sys.exit(1) + install_models(sys.argv[1]) diff --git a/sunnypilot/models/runners/helpers.py b/sunnypilot/models/runners/helpers.py index 6a128b340..8f9d8fc2f 100644 --- a/sunnypilot/models/runners/helpers.py +++ b/sunnypilot/models/runners/helpers.py @@ -2,25 +2,17 @@ from openpilot.sunnypilot.models.helpers import get_active_bundle from openpilot.sunnypilot.models.runners.model_runner import ModelRunner from openpilot.sunnypilot.models.runners.tinygrad.tinygrad_runner import TinygradRunner, TinygradSplitRunner from openpilot.sunnypilot.models.runners.constants import ModelType -from openpilot.system.hardware import TICI -if not TICI: - from openpilot.sunnypilot.models.runners.onnx.onnx_runner import ONNXRunner def get_model_runner() -> ModelRunner: """ Factory function to create and return the appropriate ModelRunner instance. - Selects between ONNXRunner (for non-TICI platforms) and TinygradRunner - (for TICI platforms), choosing TinygradSplitRunner if separate vision/policy + Selects TinygradRunner, choosing TinygradSplitRunner if separate vision/policy models are detected in the active bundle. :return: An instance of a ModelRunner subclass (ONNXRunner, TinygradRunner, or TinygradSplitRunner). """ - if not TICI: - return ONNXRunner() - - # On TICI platforms, use Tinygrad runners bundle = get_active_bundle() if bundle and bundle.models: model_types = {m.type.raw for m in bundle.models} From 70386c6b00a903deff9a641104911bc6c7ec51a5 Mon Sep 17 00:00:00 2001 From: Nayan Date: Tue, 30 Dec 2025 23:20:20 -0500 Subject: [PATCH 710/910] ui: fix Always Offroad button visibility (#1632) always offroad button fix --- selfdrive/ui/sunnypilot/layouts/settings/device.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/sunnypilot/layouts/settings/device.py b/selfdrive/ui/sunnypilot/layouts/settings/device.py index 36c5fdb34..448a4faab 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/device.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/device.py @@ -202,7 +202,7 @@ class DeviceLayoutSP(DeviceLayout): self._scroller._items.remove(self._always_offroad_btn) if ui_state.is_offroad() and not always_offroad: self._scroller._items.insert(len(self._scroller._items) - 1, self._always_offroad_btn) - elif not ui_state.is_offroad(): + else: self._scroller._items.insert(0, self._always_offroad_btn) # Quiet Mode button From fb8f46cba9dc90af481719945c21eeecef8d6996 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 31 Dec 2025 00:08:36 -0500 Subject: [PATCH 711/910] Reimplement sunnypilot Terms of Service & sunnylink Consent Screens (#1633) * tos reimpl * nah * simpler * check consent on sunnylink panel - mici * slight cleanup * rename * keep it off * decouple * more rename * more decouple * a bit more * fix state * decouple more * a bit more * wrong type * rearrange * don't do that * final * lint * include * more --------- Co-authored-by: nayan --- common/params_keys.h | 2 + selfdrive/ui/layouts/onboarding.py | 47 +++++-- selfdrive/ui/mici/layouts/onboarding.py | 45 +++++-- selfdrive/ui/sunnypilot/layouts/onboarding.py | 116 ++++++++++++++++++ .../sunnypilot/layouts/settings/sunnylink.py | 34 +++-- selfdrive/ui/sunnypilot/mici/__init__.py | 0 .../ui/sunnypilot/mici/layouts/onboarding.py | 97 +++++++++++++++ .../ui/sunnypilot/mici/layouts/sunnylink.py | 47 ++++--- selfdrive/ui/tests/diff/replay.py | 4 +- .../ui/tests/test_ui/raylib_screenshots.py | 4 +- sunnypilot/selfdrive/assets/logo.png | 3 + sunnypilot/sunnylink/athena/sunnylinkd.py | 6 +- sunnypilot/sunnylink/params_metadata.json | 8 ++ system/hardware/hardwared.py | 3 +- system/version.py | 3 + 15 files changed, 376 insertions(+), 43 deletions(-) create mode 100644 selfdrive/ui/sunnypilot/layouts/onboarding.py create mode 100644 selfdrive/ui/sunnypilot/mici/__init__.py create mode 100644 selfdrive/ui/sunnypilot/mici/layouts/onboarding.py create mode 100644 sunnypilot/selfdrive/assets/logo.png diff --git a/common/params_keys.h b/common/params_keys.h index 89f70471c..054ce97e6 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -145,6 +145,7 @@ inline static std::unordered_map keys = { {"CarParamsSPPersistent", {PERSISTENT, BYTES}}, {"CarPlatformBundle", {PERSISTENT | BACKUP, JSON}}, {"ChevronInfo", {PERSISTENT | BACKUP, INT, "4"}}, + {"CompletedSunnylinkConsentVersion", {PERSISTENT, STRING, "0"}}, {"CustomAccIncrementsEnabled", {PERSISTENT | BACKUP, BOOL, "0"}}, {"CustomAccLongPressIncrement", {PERSISTENT | BACKUP, INT, "5"}}, {"CustomAccShortPressIncrement", {PERSISTENT | BACKUP, INT, "1"}}, @@ -154,6 +155,7 @@ inline static std::unordered_map keys = { {"EnableGithubRunner", {PERSISTENT | BACKUP, BOOL}}, {"GreenLightAlert", {PERSISTENT | BACKUP, BOOL, "0"}}, {"GithubRunnerSufficientVoltage", {CLEAR_ON_MANAGER_START , BOOL}}, + {"HasAcceptedTermsSP", {PERSISTENT, STRING, "0"}}, {"HideVEgoUI", {PERSISTENT | BACKUP, BOOL, "0"}}, {"IntelligentCruiseButtonManagement", {PERSISTENT | BACKUP , BOOL}}, {"InteractivityTimeout", {PERSISTENT | BACKUP, INT, "0"}}, diff --git a/selfdrive/ui/layouts/onboarding.py b/selfdrive/ui/layouts/onboarding.py index b19cebb26..c480a3ed9 100644 --- a/selfdrive/ui/layouts/onboarding.py +++ b/selfdrive/ui/layouts/onboarding.py @@ -11,7 +11,9 @@ from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.button import Button, ButtonStyle from openpilot.system.ui.widgets.label import Label from openpilot.selfdrive.ui.ui_state import ui_state -from openpilot.system.version import terms_version, training_version +from openpilot.system.version import terms_version, training_version, terms_version_sp + +from openpilot.selfdrive.ui.sunnypilot.layouts.onboarding import SunnylinkOnboarding DEBUG = False @@ -33,6 +35,7 @@ class OnboardingState(IntEnum): TERMS = 0 ONBOARDING = 1 DECLINE = 2 + SUNNYLINK_CONSENT = 3 class TrainingGuide(Widget): @@ -110,14 +113,14 @@ class TermsPage(Widget): self._on_decline = on_decline self._title = Label(tr("Welcome to sunnypilot"), font_size=90, font_weight=FontWeight.BOLD, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT) - self._desc = Label(tr("You must accept the Terms and Conditions to use sunnypilot. Read the latest terms at https://comma.ai/terms before continuing."), + self._desc = Label(tr("You must accept the Terms of Service to use sunnypilot. Read the latest terms at https://sunnypilot.ai/terms before continuing."), font_size=90, font_weight=FontWeight.MEDIUM, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT) self._decline_btn = Button(tr("Decline"), click_callback=on_decline) self._accept_btn = Button(tr("Agree"), button_style=ButtonStyle.PRIMARY, click_callback=on_accept) def _render(self, _): - welcome_x = self._rect.x + 165 + welcome_x = self._rect.x + 95 welcome_y = self._rect.y + 165 welcome_rect = rl.Rectangle(welcome_x, welcome_y, self._rect.width - welcome_x, 90) self._title.render(welcome_rect) @@ -143,7 +146,7 @@ class TermsPage(Widget): class DeclinePage(Widget): def __init__(self, back_callback=None): super().__init__() - self._text = Label(tr("You must accept the Terms and Conditions in order to use sunnypilot."), + self._text = Label(tr("You must accept the Terms of Service in order to use sunnypilot."), font_size=90, font_weight=FontWeight.MEDIUM, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT) self._back_btn = Button(tr("Back"), click_callback=back_callback) self._uninstall_btn = Button(tr("Decline, uninstall sunnypilot"), button_style=ButtonStyle.DANGER, @@ -180,9 +183,21 @@ class OnboardingWindow(Widget): self._training_guide: TrainingGuide | None = None self._decline_page = DeclinePage(back_callback=self._on_decline_back) + # sunnylink consent pages + self._accepted_terms = self._accepted_terms and ui_state.params.get("HasAcceptedTermsSP") == terms_version_sp + self._sunnylink = SunnylinkOnboarding() + if not self._accepted_terms: + self._state = OnboardingState.TERMS + elif not self._sunnylink.completed: + self._state = OnboardingState.SUNNYLINK_CONSENT + elif not self._training_done: + self._state = OnboardingState.ONBOARDING + else: + self._state = OnboardingState.ONBOARDING + @property def completed(self) -> bool: - return self._accepted_terms and self._training_done + return self._accepted_terms and self._sunnylink.completed and self._training_done def _on_terms_declined(self): self._state = OnboardingState.DECLINE @@ -192,8 +207,12 @@ class OnboardingWindow(Widget): def _on_terms_accepted(self): ui_state.params.put("HasAcceptedTerms", terms_version) - self._state = OnboardingState.ONBOARDING - if self._training_done: + ui_state.params.put("HasAcceptedTermsSP", terms_version_sp) + if not self._sunnylink.completed: + self._state = OnboardingState.SUNNYLINK_CONSENT + elif not self._training_done: + self._state = OnboardingState.ONBOARDING + else: gui_app.set_modal_overlay(None) def _on_completed_training(self): @@ -206,8 +225,18 @@ class OnboardingWindow(Widget): if self._state == OnboardingState.TERMS: self._terms.render(self._rect) - if self._state == OnboardingState.ONBOARDING: - self._training_guide.render(self._rect) + elif self._state == OnboardingState.SUNNYLINK_CONSENT: + self._sunnylink.render(self._rect) + if self._sunnylink.completed: + if not self._training_done: + self._state = OnboardingState.ONBOARDING + else: + gui_app.set_modal_overlay(None) + elif self._state == OnboardingState.ONBOARDING: + if not self._training_done: + self._training_guide.render(self._rect) + else: + gui_app.set_modal_overlay(None) elif self._state == OnboardingState.DECLINE: self._decline_page.render(self._rect) return -1 diff --git a/selfdrive/ui/mici/layouts/onboarding.py b/selfdrive/ui/mici/layouts/onboarding.py index b175a3fd2..c7fcd7853 100644 --- a/selfdrive/ui/mici/layouts/onboarding.py +++ b/selfdrive/ui/mici/layouts/onboarding.py @@ -17,13 +17,16 @@ from openpilot.selfdrive.ui.mici.onroad.driver_state import DriverStateRenderer from openpilot.selfdrive.ui.mici.onroad.driver_camera_dialog import DriverCameraDialog from openpilot.system.ui.widgets.label import gui_label from openpilot.system.ui.lib.multilang import tr -from openpilot.system.version import terms_version, training_version +from openpilot.system.version import terms_version, training_version, terms_version_sp + +from openpilot.selfdrive.ui.sunnypilot.mici.layouts.onboarding import SunnylinkOnboarding class OnboardingState(IntEnum): TERMS = 0 ONBOARDING = 1 DECLINE = 2 + SUNNYLINK_CONSENT = 3 class DriverCameraSetupDialog(DriverCameraDialog): @@ -412,10 +415,10 @@ class TermsPage(SetupTermsPage): super().__init__(on_accept, on_decline, "decline") info_txt = gui_app.texture("icons_mici/setup/green_info.png", 60, 60) - self._title_header = TermsHeader("terms & conditions", info_txt) + self._title_header = TermsHeader("terms of service", info_txt) - self._terms_label = UnifiedLabel("You must accept the Terms and Conditions to use sunnypilot. " + - "Read the latest terms at https://comma.ai/terms before continuing.", 36, + self._terms_label = UnifiedLabel("You must accept the Terms of Service to use sunnypilot. " + + "Read the latest terms at https://sunnypilot.ai/terms before continuing.", 36, FontWeight.ROMAN) @property @@ -449,6 +452,18 @@ class OnboardingWindow(Widget): self._training_guide = TrainingGuide(completed_callback=self._on_completed_training) self._decline_page = DeclinePage(back_callback=self._on_decline_back) + # sunnylink consent pages + self._accepted_terms = self._accepted_terms and ui_state.params.get("HasAcceptedTermsSP") == terms_version_sp + self._sunnylink = SunnylinkOnboarding() + if not self._accepted_terms: + self._state = OnboardingState.TERMS + elif not self._sunnylink.completed: + self._state = OnboardingState.SUNNYLINK_CONSENT + elif not self._training_done: + self._state = OnboardingState.ONBOARDING + else: + self._state = OnboardingState.ONBOARDING + def show_event(self): super().show_event() device.set_override_interactive_timeout(300) @@ -459,7 +474,7 @@ class OnboardingWindow(Widget): @property def completed(self) -> bool: - return self._accepted_terms and self._training_done + return self._accepted_terms and self._sunnylink.completed and self._training_done def _on_terms_declined(self): self._state = OnboardingState.DECLINE @@ -473,7 +488,13 @@ class OnboardingWindow(Widget): def _on_terms_accepted(self): ui_state.params.put("HasAcceptedTerms", terms_version) - self._state = OnboardingState.ONBOARDING + ui_state.params.put("HasAcceptedTermsSP", terms_version_sp) + if not self._sunnylink.completed: + self._state = OnboardingState.SUNNYLINK_CONSENT + elif not self._training_done: + self._state = OnboardingState.ONBOARDING + else: + self.close() def _on_completed_training(self): ui_state.params.put("CompletedTrainingVersion", training_version) @@ -482,8 +503,18 @@ class OnboardingWindow(Widget): def _render(self, _): if self._state == OnboardingState.TERMS: self._terms.render(self._rect) + elif self._state == OnboardingState.SUNNYLINK_CONSENT: + self._sunnylink.render(self._rect) + if self._sunnylink.completed: + if not self._training_done: + self._state = OnboardingState.ONBOARDING + else: + self.close() elif self._state == OnboardingState.ONBOARDING: - self._training_guide.render(self._rect) + if not self._training_done: + self._training_guide.render(self._rect) + else: + self.close() elif self._state == OnboardingState.DECLINE: self._decline_page.render(self._rect) return -1 diff --git a/selfdrive/ui/sunnypilot/layouts/onboarding.py b/selfdrive/ui/sunnypilot/layouts/onboarding.py new file mode 100644 index 000000000..9ba9f0749 --- /dev/null +++ b/selfdrive/ui/sunnypilot/layouts/onboarding.py @@ -0,0 +1,116 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +import pyray as rl +from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.system.ui.lib.application import FontWeight +from openpilot.system.ui.lib.multilang import tr +from openpilot.system.ui.widgets import Widget +from openpilot.system.ui.widgets.button import Button, ButtonStyle +from openpilot.system.ui.widgets.label import Label +from openpilot.system.version import sunnylink_consent_version, sunnylink_consent_declined + + +class SunnylinkConsentPage(Widget): + def __init__(self, done_callback=None): + super().__init__() + self._done_callback = done_callback + self._step = 0 + + self._title = Label(tr("sunnylink"), font_size=90, font_weight=FontWeight.BOLD, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT) + + self._content = [ + { + "text": tr("sunnylink enables secured remote access to your comma device from anywhere, " + + "including settings management, remote monitoring, real-time dashboard, etc."), + "primary_btn": tr("Enable"), + "secondary_btn": tr("Disable"), + "highlight_primary": True + }, + { + "text": tr("sunnylink is designed to be enabled as part of sunnypilot's core functionality. " + + "If sunnylink is disabled, features such as settings management, remote monitoring, " + + "real-time dashboards will be unavailable."), + "secondary_btn": tr("Back"), + "danger_btn": tr("Disable"), + "highlight_primary": True + } + ] + + self._primary_btn = Button("", button_style=ButtonStyle.PRIMARY, click_callback=lambda: self._handle_choice("enable")) + self._secondary_btn = Button("", button_style=ButtonStyle.NORMAL, click_callback=lambda: self._handle_choice("secondary")) + self._danger_btn = Button("", button_style=ButtonStyle.DANGER, click_callback=lambda: self._handle_choice("disable")) + + def _handle_choice(self, choice): + if choice == "enable": + ui_state.params.put_bool("SunnylinkEnabled", True) + ui_state.params.put("CompletedSunnylinkConsentVersion", sunnylink_consent_version) + if self._done_callback: + self._done_callback() + elif choice == "secondary": + if self._step == 0: + self._step = 1 + elif self._step == 1: + self._step = 0 + elif choice == "disable": + ui_state.params.put_bool("SunnylinkEnabled", False) + ui_state.params.put("CompletedSunnylinkConsentVersion", sunnylink_consent_declined) + if self._done_callback: + self._done_callback() + + def _render(self, _): + step_data = self._content[self._step] + + welcome_x = self._rect.x + 95 + welcome_y = self._rect.y + 165 + welcome_rect = rl.Rectangle(welcome_x, welcome_y, self._rect.width - welcome_x, 90) + self._title.render(welcome_rect) + + desc_x = welcome_x + desc_y = welcome_y + 120 + desc_rect = rl.Rectangle(desc_x, desc_y, self._rect.width - desc_x, self._rect.height - desc_y - 250) + + desc_label = Label(step_data["text"], font_size=90, font_weight=FontWeight.MEDIUM, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT) + desc_label.render(desc_rect) + + btn_y = self._rect.y + self._rect.height - 160 - 45 + + if "danger_btn" in step_data: + btn_width = (self._rect.width - 45 * 3) / 2 + + self._secondary_btn.set_text(step_data["secondary_btn"]) + self._secondary_btn.render(rl.Rectangle(self._rect.x + 45, btn_y, btn_width, 160)) + + self._danger_btn.set_text(step_data["danger_btn"]) + self._danger_btn.render(rl.Rectangle(self._rect.x + 45 * 2 + btn_width, btn_y, btn_width, 160)) + + else: + btn_width = (self._rect.width - 45 * 3) / 2 + + self._secondary_btn.set_text(step_data["secondary_btn"]) + self._secondary_btn.render(rl.Rectangle(self._rect.x + 45, btn_y, btn_width, 160)) + + self._primary_btn.set_text(step_data["primary_btn"]) + self._primary_btn.render(rl.Rectangle(self._rect.x + 45 * 2 + btn_width, btn_y, btn_width, 160)) + + return -1 + + +class SunnylinkOnboarding: + def __init__(self): + self.consent_page = SunnylinkConsentPage(done_callback=self._on_done) + self.consent_done: bool = ui_state.params.get("CompletedSunnylinkConsentVersion") in {sunnylink_consent_version, sunnylink_consent_declined} + + @property + def completed(self) -> bool: + return self.consent_done + + def _on_done(self): + self.consent_done = True + + def render(self, rect): + if not self.consent_done: + self.consent_page.render(rect) diff --git a/selfdrive/ui/sunnypilot/layouts/settings/sunnylink.py b/selfdrive/ui/sunnypilot/layouts/settings/sunnylink.py index 2b5497fb5..00baf0ccc 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/sunnylink.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/sunnylink.py @@ -4,23 +4,23 @@ Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. This file is part of sunnypilot and is licensed under the MIT License. See the LICENSE.md file in the root directory for more details. """ +import pyray as rl from cereal import custom +from openpilot.selfdrive.ui.sunnypilot.layouts.onboarding import SunnylinkConsentPage from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.sunnypilot.sunnylink.api import UNREGISTERED_SUNNYLINK_DONGLE_ID from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.lib.multilang import tr +from openpilot.system.ui.sunnypilot.widgets.list_view import button_item_sp +from openpilot.system.ui.sunnypilot.widgets.list_view import toggle_item_sp from openpilot.system.ui.sunnypilot.widgets.sunnylink_pairing_dialog import SunnylinkPairingDialog +from openpilot.system.ui.widgets import Widget, DialogResult from openpilot.system.ui.widgets.button import ButtonStyle, Button from openpilot.system.ui.widgets.confirm_dialog import alert_dialog, ConfirmDialog from openpilot.system.ui.widgets.label import UnifiedLabel -from openpilot.system.ui.widgets.list_view import button_item, dual_button_item +from openpilot.system.ui.widgets.list_view import dual_button_item from openpilot.system.ui.widgets.scroller_tici import Scroller, LineSeparator -from openpilot.system.ui.widgets import Widget, DialogResult -from openpilot.system.ui.sunnypilot.widgets.list_view import toggle_item_sp -import pyray as rl - -if gui_app.sunnypilot_ui(): - from openpilot.system.ui.sunnypilot.widgets.list_view import button_item_sp as button_item +from openpilot.system.version import sunnylink_consent_version class SunnylinkHeader(Widget): @@ -160,14 +160,14 @@ class SunnylinkLayout(Widget): self._sunnylink_description = SunnylinkDescriptionItem() self._sunnylink_description.set_visible(False) - self._sponsor_btn = button_item( + self._sponsor_btn = button_item_sp( title=tr("Sponsor Status"), button_text=tr("SPONSOR"), description=tr( "Become a sponsor of sunnypilot to get early access to sunnylink features when they become available."), callback=lambda: self._handle_pair_btn(False) ) - self._pair_btn = button_item( + self._pair_btn = button_item_sp( title=tr("Pair GitHub Account"), button_text=tr("Not Paired"), description=tr( @@ -302,6 +302,22 @@ class SunnylinkLayout(Widget): self._restore_btn.set_text(tr("Restore Settings")) def _sunnylink_toggle_callback(self, state: bool): + sl_consent: bool = ui_state.params.get("CompletedSunnylinkConsentVersion") == sunnylink_consent_version + sl_enabled: bool = ui_state.params.get_bool("SunnylinkEnabled") + + if state and not sl_consent and not sl_enabled: + def on_consent_done(): + enabled = ui_state.params.get_bool("SunnylinkEnabled") + self._update_description(enabled) + gui_app.set_modal_overlay(None) + + sl_terms_dlg = SunnylinkConsentPage(done_callback=on_consent_done) + gui_app.set_modal_overlay(sl_terms_dlg) + else: + ui_state.params.put_bool("SunnylinkEnabled", state) + self._update_description(state) + + def _update_description(self, state: bool): if state: description = tr( "Welcome back!! We're excited to see you've enabled sunnylink again!") diff --git a/selfdrive/ui/sunnypilot/mici/__init__.py b/selfdrive/ui/sunnypilot/mici/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/selfdrive/ui/sunnypilot/mici/layouts/onboarding.py b/selfdrive/ui/sunnypilot/mici/layouts/onboarding.py new file mode 100644 index 000000000..930853e55 --- /dev/null +++ b/selfdrive/ui/sunnypilot/mici/layouts/onboarding.py @@ -0,0 +1,97 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +import pyray as rl +from openpilot.system.ui.lib.application import FontWeight, gui_app +from openpilot.system.ui.widgets.label import UnifiedLabel +from openpilot.system.ui.widgets.slider import SmallSlider +from openpilot.system.ui.mici_setup import TermsHeader, TermsPage as SetupTermsPage +from openpilot.system.version import sunnylink_consent_version, sunnylink_consent_declined +from openpilot.selfdrive.ui.ui_state import ui_state + + +class SunnylinkConsentPage(SetupTermsPage): + def __init__(self, on_accept=None, on_decline=None, left_text: str = "disable", right_text: str = "enable"): + super().__init__(on_accept, on_decline, left_text, continue_text=right_text) + + self._title_header = TermsHeader("sunnylink", + gui_app.texture("../../sunnypilot/selfdrive/assets/logo.png", 66, 60)) + + self._terms_label = UnifiedLabel("sunnylink enables secured remote access to your comma device from anywhere, " + + "including settings management, remote monitoring, real-time dashboard, etc.", + 36, FontWeight.ROMAN) + + @property + def _content_height(self): + return self._terms_label.rect.y + self._terms_label.rect.height - self._scroll_panel.get_offset() + + def _render(self, _): + super()._render(_) + return -1 + + def _render_content(self, scroll_offset): + self._title_header.set_position(self._rect.x + 16, self._rect.y + 12 + scroll_offset) + self._title_header.render() + + self._terms_label.render(rl.Rectangle( + self._rect.x + 16, + self._title_header.rect.y + self._title_header.rect.height + self.ITEM_SPACING, + self._rect.width - 100, + self._terms_label.get_content_height(int(self._rect.width - 100)), + )) + + +class SunnylinkConsentDisableConfirmPage(SunnylinkConsentPage): + def __init__(self, on_accept=None, on_decline=None): + super().__init__(on_accept=on_decline, on_decline=on_accept, left_text="enable", right_text="disable") + + # we flip the continue & disable buttons to use slider for disable + self._continue_slider = True + self._continue_button = SmallSlider("disable", confirm_callback=on_decline) + self._scroll_panel.set_enabled(lambda: not self._continue_button.is_pressed) + + self._title_header = TermsHeader("disable sunnylink?", + gui_app.texture("icons_mici/setup/red_warning.png", 66, 60)) + + self._terms_label = UnifiedLabel("sunnylink is designed to be enabled as part of sunnypilot's core functionality. " + + "If sunnylink is disabled, features such as settings management, " + + "remote monitoring, real-time dashboards will be unavailable.", + 36, FontWeight.ROMAN) + + +class SunnylinkOnboarding: + def __init__(self): + self.consent_done: bool = ui_state.params.get("CompletedSunnylinkConsentVersion") in {sunnylink_consent_version, sunnylink_consent_declined} + self.disable_confirm = False + + self.consent_page = SunnylinkConsentPage(on_decline=self._on_decline, on_accept=self._on_accept) + self.confirm_page = SunnylinkConsentDisableConfirmPage(on_decline=self._on_confirm_decline, on_accept=self._on_accept) + + @property + def completed(self) -> bool: + return self.consent_done + + def _on_accept(self): + ui_state.params.put("CompletedSunnylinkConsentVersion", sunnylink_consent_version) + ui_state.params.put_bool("SunnylinkEnabled", True) + self.consent_done = True + + def _on_decline(self): + self.disable_confirm = True + + def _on_confirm_decline(self): + ui_state.params.put_bool("SunnylinkEnabled", False) + ui_state.params.put("CompletedSunnylinkConsentVersion", sunnylink_consent_declined) + self.consent_done = True + + def render(self, rect): + if self.consent_done: + return + + if self.disable_confirm: + self.confirm_page.render(rect) + else: + self.consent_page.render(rect) diff --git a/selfdrive/ui/sunnypilot/mici/layouts/sunnylink.py b/selfdrive/ui/sunnypilot/mici/layouts/sunnylink.py index 2ab035c1c..172ef7d2f 100644 --- a/selfdrive/ui/sunnypilot/mici/layouts/sunnylink.py +++ b/selfdrive/ui/sunnypilot/mici/layouts/sunnylink.py @@ -8,16 +8,17 @@ from collections.abc import Callable import pyray as rl from cereal import custom -from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigConfirmationDialogV2 -from openpilot.selfdrive.ui.sunnypilot.mici.widgets.sunnylink_pairing_dialog import SunnylinkPairingDialog -from openpilot.sunnypilot.sunnylink.api import UNREGISTERED_SUNNYLINK_DONGLE_ID -from openpilot.system.ui.lib.multilang import tr - -from openpilot.system.ui.widgets.scroller import Scroller from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigToggle -from openpilot.system.ui.lib.application import gui_app, MousePos -from openpilot.system.ui.widgets import NavWidget +from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigConfirmationDialogV2 +from openpilot.selfdrive.ui.sunnypilot.mici.layouts.onboarding import SunnylinkConsentPage +from openpilot.selfdrive.ui.sunnypilot.mici.widgets.sunnylink_pairing_dialog import SunnylinkPairingDialog from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.sunnypilot.sunnylink.api import UNREGISTERED_SUNNYLINK_DONGLE_ID +from openpilot.system.ui.lib.application import gui_app, MousePos +from openpilot.system.ui.lib.multilang import tr +from openpilot.system.ui.widgets import NavWidget +from openpilot.system.ui.widgets.scroller import Scroller +from openpilot.system.version import sunnylink_consent_version, sunnylink_consent_declined class SunnylinkLayoutMici(NavWidget): @@ -28,9 +29,9 @@ class SunnylinkLayoutMici(NavWidget): self._backup_in_progress = False self._sunnylink_enabled = ui_state.params.get("SunnylinkEnabled") - self._sunnylink_toggle = BigToggle(text="", + self._sunnylink_toggle = BigToggle(text=tr("enable sunnylink"), initial_state=self._sunnylink_enabled, - toggle_callback=SunnylinkLayoutMici._sunnylink_toggle_callback) + toggle_callback=self._sunnylink_toggle_callback) self._sunnylink_sponsor_button = SunnylinkPairBigButton(sponsor_pairing=False) self._sunnylink_pair_button = SunnylinkPairBigButton(sponsor_pairing=True) self._backup_btn = BigButton(tr("backup settings"), "", "") @@ -38,7 +39,7 @@ class SunnylinkLayoutMici(NavWidget): self._restore_btn = BigButton(tr("restore settings"), "", "") self._restore_btn.set_click_callback(lambda: self._handle_backup_restore_btn(restore=True)) self._sunnylink_uploader_toggle = BigToggle(text=tr("sunnylink uploader"), initial_state=False, - toggle_callback=SunnylinkLayoutMici._sunnylink_uploader_callback) + toggle_callback=self._sunnylink_uploader_callback) self._scroller = Scroller([ self._sunnylink_toggle, @@ -51,8 +52,8 @@ class SunnylinkLayoutMici(NavWidget): def _update_state(self): super()._update_state() - self._sunnylink_enabled = ui_state.sunnylink_enabled - self._sunnylink_toggle.set_text(tr("enable sunnylink")) + self._sunnylink_enabled = ui_state.params.get("SunnylinkEnabled") + self._sunnylink_toggle.set_checked(self._sunnylink_enabled) self._sunnylink_pair_button.set_visible(self._sunnylink_enabled) self._sunnylink_sponsor_button.set_visible(self._sunnylink_enabled) self._backup_btn.set_visible(self._sunnylink_enabled) @@ -83,7 +84,25 @@ class SunnylinkLayoutMici(NavWidget): @staticmethod def _sunnylink_toggle_callback(state: bool): - ui_state.params.put_bool("SunnylinkEnabled", state) + sl_consent: bool = ui_state.params.get("CompletedSunnylinkConsentVersion") == sunnylink_consent_version + sl_enabled: bool = ui_state.params.get("SunnylinkEnabled") + + def sl_terms_accepted(): + ui_state.params.put("CompletedSunnylinkConsentVersion", sunnylink_consent_version) + ui_state.params.put_bool("SunnylinkEnabled", True) + gui_app.set_modal_overlay(None) + + def sl_terms_declined(): + ui_state.params.put("CompletedSunnylinkConsentVersion", sunnylink_consent_declined) + ui_state.params.put_bool("SunnylinkEnabled", False) + gui_app.set_modal_overlay(None) + + if state and not sl_consent and not sl_enabled: + sl_terms_dlg = SunnylinkConsentPage(on_accept=sl_terms_accepted, on_decline=sl_terms_declined) + gui_app.set_modal_overlay(sl_terms_dlg) + else: + ui_state.params.put_bool("SunnylinkEnabled", state) + ui_state.update_params() @staticmethod diff --git a/selfdrive/ui/tests/diff/replay.py b/selfdrive/ui/tests/diff/replay.py index 9da157660..ce24bf919 100755 --- a/selfdrive/ui/tests/diff/replay.py +++ b/selfdrive/ui/tests/diff/replay.py @@ -13,7 +13,7 @@ if "RECORD_OUTPUT" not in os.environ: os.environ["RECORD_OUTPUT"] = os.path.join(DIFF_OUT_DIR, os.environ["RECORD_OUTPUT"]) from openpilot.common.params import Params -from openpilot.system.version import terms_version, training_version +from openpilot.system.version import terms_version, training_version, terms_version_sp, sunnylink_consent_version from openpilot.system.ui.lib.application import gui_app, MousePos, MouseEvent from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.selfdrive.ui.mici.layouts.main import MiciMainLayout @@ -45,6 +45,8 @@ def setup_state(): params.put("CompletedTrainingVersion", training_version) params.put("DongleId", "test123456789") params.put("UpdaterCurrentDescription", "0.10.1 / test-branch / abc1234 / Nov 30") + params.put("HasAcceptedTermsSP", terms_version_sp) + params.put("CompletedSunnylinkConsentVersion", sunnylink_consent_version) return None diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py index e209ab806..cd27b1c12 100755 --- a/selfdrive/ui/tests/test_ui/raylib_screenshots.py +++ b/selfdrive/ui/tests/test_ui/raylib_screenshots.py @@ -18,7 +18,7 @@ from openpilot.common.prefix import OpenpilotPrefix from openpilot.selfdrive.test.helpers import with_processes from openpilot.selfdrive.selfdrived.alertmanager import set_offroad_alert from openpilot.system.updated.updated import parse_release_notes -from openpilot.system.version import terms_version, training_version +from openpilot.system.version import terms_version, training_version, terms_version_sp, sunnylink_consent_version AlertSize = log.SelfdriveState.AlertSize AlertStatus = log.SelfdriveState.AlertStatus @@ -378,6 +378,8 @@ def create_screenshots(): # Set terms and training version (to skip onboarding) params.put("HasAcceptedTerms", terms_version) params.put("CompletedTrainingVersion", training_version) + params.put("HasAcceptedTermsSP", terms_version_sp) + params.put("CompletedSunnylinkConsentVersion", sunnylink_consent_version) if name == "homescreen_paired": params.put("PrimeType", 0) # NONE diff --git a/sunnypilot/selfdrive/assets/logo.png b/sunnypilot/selfdrive/assets/logo.png new file mode 100644 index 000000000..690cf7fb7 --- /dev/null +++ b/sunnypilot/selfdrive/assets/logo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:66b3aefa108dd0c7f64205a11e424430c318e6fd06de31b5550d0b9d05616e6a +size 19035 diff --git a/sunnypilot/sunnylink/athena/sunnylinkd.py b/sunnypilot/sunnylink/athena/sunnylinkd.py index d1a03778c..73fa42e71 100755 --- a/sunnypilot/sunnylink/athena/sunnylinkd.py +++ b/sunnypilot/sunnylink/athena/sunnylinkd.py @@ -42,10 +42,14 @@ METADATA_PATH = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__f params = Params() -# Parameters that should never be remotely modified for security reasons +# Parameters that should never be remotely modified BLOCKED_PARAMS = { + "CompletedSunnylinkConsentVersion", + "CompletedTrainingVersion", "GithubUsername", # Could grant SSH access "GithubSshKeys", # Direct SSH key injection + "HasAcceptedTerms", + "HasAcceptedTermsSP", } diff --git a/sunnypilot/sunnylink/params_metadata.json b/sunnypilot/sunnylink/params_metadata.json index f335457fb..95a143ea6 100644 --- a/sunnypilot/sunnylink/params_metadata.json +++ b/sunnypilot/sunnylink/params_metadata.json @@ -165,6 +165,10 @@ "title": "Chevron Info", "description": "" }, + "CompletedSunnylinkConsentVersion": { + "title": "Completed sunnylink Consent Version", + "description": "" + }, "CompletedTrainingVersion": { "title": "Completed Training Version", "description": "" @@ -349,6 +353,10 @@ "title": "Has Accepted Terms", "description": "" }, + "HasAcceptedTermsSP": { + "title": "Has Accepted sunnypilot Terms", + "description": "" + }, "HideVEgoUI": { "title": "Hide vEgo UI", "description": "" diff --git a/system/hardware/hardwared.py b/system/hardware/hardwared.py index 1d1893a8c..1179914d0 100755 --- a/system/hardware/hardwared.py +++ b/system/hardware/hardwared.py @@ -23,7 +23,7 @@ from openpilot.system.statsd import statlog from openpilot.common.swaglog import cloudlog from openpilot.system.hardware.power_monitoring import PowerMonitoring from openpilot.system.hardware.fan_controller import TiciFanController -from openpilot.system.version import terms_version, training_version, get_build_metadata +from openpilot.system.version import terms_version, training_version, get_build_metadata, terms_version_sp ThermalStatus = log.DeviceState.ThermalStatus NetworkType = log.DeviceState.NetworkType @@ -310,6 +310,7 @@ def hardware_thread(end_event, hw_queue) -> None: startup_conditions["no_excessive_actuation"] = params.get("Offroad_ExcessiveActuation") is None startup_conditions["not_uninstalling"] = not params.get_bool("DoUninstall") startup_conditions["accepted_terms"] = params.get("HasAcceptedTerms") == terms_version + startup_conditions["accepted_terms_sp"] = params.get("HasAcceptedTermsSP") == terms_version_sp # with 2% left, we killall, otherwise the phone will take a long time to boot startup_conditions["free_space"] = msg.deviceState.freeSpacePercent > 2 diff --git a/system/version.py b/system/version.py index 8a0e2da3e..ae6ac1b13 100755 --- a/system/version.py +++ b/system/version.py @@ -30,6 +30,9 @@ BUILD_METADATA_FILENAME = "build.json" training_version: str = "0.2.0" terms_version: str = "2" +terms_version_sp: str = "1.0" +sunnylink_consent_version: str = "1.0" +sunnylink_consent_declined: str = "-1" def get_version(path: str = BASEDIR) -> str: From 19a7d1d5d71a857d942835e27ee195c09cc54414 Mon Sep 17 00:00:00 2001 From: Kumar <36933347+rav4kumar@users.noreply.github.com> Date: Tue, 30 Dec 2025 22:27:53 -0700 Subject: [PATCH 712/910] [TIZI/TICI] ui: update dmoji position and Developer UI adjustments (#1601) * ui: improve layout and centering of bottom developer UI elements * int * less is more, y'all * always show actual lat for all cars * lint * perfect * cleanup * too long * inherit * remove unused * inir * need to fix * final --------- Co-authored-by: Jason Wen --- selfdrive/ui/onroad/augmented_road_view.py | 1 + .../onroad/developer_ui/__init__.py | 51 +++++++++++++------ .../onroad/developer_ui/elements.py | 21 ++++++++ .../ui/sunnypilot/onroad/driver_state.py | 49 ++++++++++++++++++ 4 files changed, 107 insertions(+), 15 deletions(-) create mode 100644 selfdrive/ui/sunnypilot/onroad/driver_state.py diff --git a/selfdrive/ui/onroad/augmented_road_view.py b/selfdrive/ui/onroad/augmented_road_view.py index 1d66379f9..a1d10193a 100644 --- a/selfdrive/ui/onroad/augmented_road_view.py +++ b/selfdrive/ui/onroad/augmented_road_view.py @@ -16,6 +16,7 @@ from openpilot.common.transformations.orientation import rot_from_euler if gui_app.sunnypilot_ui(): from openpilot.selfdrive.ui.sunnypilot.onroad.hud_renderer import HudRendererSP as HudRenderer + from openpilot.selfdrive.ui.sunnypilot.onroad.driver_state import DriverStateRendererSP as DriverStateRenderer from openpilot.selfdrive.ui.sunnypilot.onroad.augmented_road_view import BORDER_COLORS_SP diff --git a/selfdrive/ui/sunnypilot/onroad/developer_ui/__init__.py b/selfdrive/ui/sunnypilot/onroad/developer_ui/__init__.py index 14a224ae7..441a169d2 100644 --- a/selfdrive/ui/sunnypilot/onroad/developer_ui/__init__.py +++ b/selfdrive/ui/sunnypilot/onroad/developer_ui/__init__.py @@ -22,6 +22,7 @@ class DeveloperUiRenderer(Widget): DEV_UI_RIGHT = 1 DEV_UI_BOTTOM = 2 DEV_UI_BOTH = 3 + BOTTOM_BAR_HEIGHT = 61 def __init__(self): super().__init__() @@ -43,6 +44,12 @@ class DeveloperUiRenderer(Widget): self.bearing_elem = BearingDegElement() self.altitude_elem = AltitudeElement() + @staticmethod + def get_bottom_dev_ui_offset(): + if ui_state.developer_ui in (DeveloperUiRenderer.DEV_UI_BOTTOM, DeveloperUiRenderer.DEV_UI_BOTH): + return DeveloperUiRenderer.BOTTOM_BAR_HEIGHT + return 0 + def _update_state(self) -> None: self.dev_ui_mode = ui_state.developer_ui @@ -78,10 +85,11 @@ class DeveloperUiRenderer(Widget): ] if controls_state.lateralControlState.which() == 'torqueState': elements.append(self.desired_lat_accel_elem.update(sm, ui_state.is_metric)) - elements.append(self.actual_lat_accel_elem.update(sm, ui_state.is_metric)) else: elements.append(self.desired_steer_elem.update(sm, ui_state.is_metric)) + elements.append(self.actual_lat_accel_elem.update(sm, ui_state.is_metric)) + current_y = y for element in elements: current_y += self._draw_right_dev_ui_element(x, current_y, element) @@ -105,7 +113,7 @@ class DeveloperUiRenderer(Widget): if element.unit: units_height = measure_text_cached(self._font_bold, element.unit, unit_size, 0).x - units_x = x + container_width - 10 + units_x = x + container_width units_y = y + (value_size / 2) + (units_height / 2) rl.draw_text_pro(self._font_bold, element.unit, rl.Vector2(units_x, units_y), rl.Vector2(0, 0), -90.0, unit_size, 0, rl.WHITE) @@ -143,22 +151,35 @@ class DeveloperUiRenderer(Widget): if sm.valid['gpsLocationExternal'] or sm.valid['gpsLocation']: elements.append(self.altitude_elem.update(sm, ui_state.is_metric)) - current_x = int(rect.x + 90) - center_y = y + bar_height // 2 - for element in elements: - current_x += self._draw_bottom_dev_ui_element(current_x, center_y, element) + if not elements: + return - def _draw_bottom_dev_ui_element(self, x: int, y: int, element: UiElement) -> int: font_size = 38 + element_widths = [] + for element in elements: + element.measure(self._font_bold, font_size) + element_widths.append(element.total_width) - label_text = f"{element.label} " - label_width = measure_text_cached(self._font_bold, label_text, font_size, 0).x - rl.draw_text_ex(self._font_bold, label_text, rl.Vector2(x, y - font_size // 2), font_size, 0, rl.WHITE) + total_element_width = sum(element_widths) + num_gaps = len(elements) + 1 + available_width = rect.width + gap_width = (available_width - total_element_width) / num_gaps - value_width = measure_text_cached(self._font_bold, element.value, font_size, 0).x - rl.draw_text_ex(self._font_bold, element.value, rl.Vector2(x + label_width + 10, y - font_size // 2), font_size, 0, element.color) + center_y = y + bar_height // 2 + current_x = rect.x + gap_width + + for i, element in enumerate(elements): + element_center_x = int(current_x + element_widths[i] / 2) + self._draw_bottom_dev_ui_element(element_center_x, center_y, element) + current_x += element_widths[i] + gap_width + + def _draw_bottom_dev_ui_element(self, center_x: int, y: int, element: UiElement) -> None: + font_size = 38 + start_x = center_x - element.total_width / 2 + + rl.draw_text_ex(self._font_bold, element.label_text, rl.Vector2(start_x, y - font_size // 2), font_size, 0, rl.WHITE) + rl.draw_text_ex(self._font_bold, element.val_text, rl.Vector2(start_x + element.label_width, y - font_size // 2), font_size, 0, element.color) if element.unit: - rl.draw_text_ex(self._font_bold, element.unit, rl.Vector2(x + label_width + value_width + 20, y - font_size // 2), font_size, 0, rl.WHITE) - - return 400 + rl.draw_text_ex(self._font_bold, element.unit_text, rl.Vector2(start_x + element.label_width + element.val_width, y - font_size // 2), + font_size, 0, rl.WHITE) diff --git a/selfdrive/ui/sunnypilot/onroad/developer_ui/elements.py b/selfdrive/ui/sunnypilot/onroad/developer_ui/elements.py index e8daca886..652469bdd 100644 --- a/selfdrive/ui/sunnypilot/onroad/developer_ui/elements.py +++ b/selfdrive/ui/sunnypilot/onroad/developer_ui/elements.py @@ -10,12 +10,33 @@ from dataclasses import dataclass from openpilot.common.constants import CV +from openpilot.system.ui.lib.text_measure import measure_text_cached + + @dataclass class UiElement: value: str label: str unit: str color: rl.Color + val_text: str = "" + label_text: str = "" + unit_text: str = "" + val_width: float = 0.0 + label_width: float = 0.0 + unit_width: float = 0.0 + total_width: float = 0.0 + + def measure(self, font, font_size: int): + self.label_text = f"{self.label} " + self.val_text = self.value + self.unit_text = f" {self.unit}" if self.unit else "" + + self.label_width = measure_text_cached(font, self.label_text, font_size, 0).x + self.val_width = measure_text_cached(font, self.val_text, font_size, 0).x + self.unit_width = measure_text_cached(font, self.unit_text, font_size, 0).x if self.unit else 0 + + self.total_width = self.label_width + self.val_width + self.unit_width class LeadInfoElement: diff --git a/selfdrive/ui/sunnypilot/onroad/driver_state.py b/selfdrive/ui/sunnypilot/onroad/driver_state.py new file mode 100644 index 000000000..4f2d264ee --- /dev/null +++ b/selfdrive/ui/sunnypilot/onroad/driver_state.py @@ -0,0 +1,49 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +import numpy as np + +from openpilot.selfdrive.ui import UI_BORDER_SIZE +from openpilot.selfdrive.ui.onroad.driver_state import DriverStateRenderer, BTN_SIZE, ARC_LENGTH +from openpilot.selfdrive.ui.sunnypilot.onroad.developer_ui import DeveloperUiRenderer + + +class DriverStateRendererSP(DriverStateRenderer): + def __init__(self): + super().__init__() + self.dev_ui_offset = DeveloperUiRenderer.get_bottom_dev_ui_offset() + + def _pre_calculate_drawing_elements(self): + """Pre-calculate all drawing elements based on the current rectangle""" + # Calculate icon position (bottom-left or bottom-right) + width, height = self._rect.width, self._rect.height + offset = UI_BORDER_SIZE + BTN_SIZE // 2 + self.position_x = self._rect.x + (width - offset if self.is_rhd else offset) + self.position_y = self._rect.y + height - offset - self.dev_ui_offset + + # Pre-calculate the face lines positions + positioned_keypoints = self.face_keypoints_transformed + np.array([self.position_x, self.position_y]) + for i in range(len(positioned_keypoints)): + self.face_lines[i].x = positioned_keypoints[i][0] + self.face_lines[i].y = positioned_keypoints[i][1] + + # Calculate arc dimensions based on head rotation + delta_x = -self.driver_pose_sins[1] * ARC_LENGTH / 2.0 # Horizontal movement + delta_y = -self.driver_pose_sins[0] * ARC_LENGTH / 2.0 # Vertical movement + + # Horizontal arc + h_width = abs(delta_x) + self.h_arc_data = self._calculate_arc_data( + delta_x, h_width, self.position_x, self.position_y - ARC_LENGTH / 2, + self.driver_pose_sins[1], self.driver_pose_diff[1], is_horizontal=True + ) + + # Vertical arc + v_height = abs(delta_y) + self.v_arc_data = self._calculate_arc_data( + delta_y, v_height, self.position_x - ARC_LENGTH / 2, self.position_y, + self.driver_pose_sins[0], self.driver_pose_diff[0], is_horizontal=False + ) From 84b1f363e42293104b4d56ed5b07f549fce381cf Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 31 Dec 2025 12:43:36 -0800 Subject: [PATCH 713/910] Alert for stock LKAS (#36969) * alert stock lkas * high * i didn't do this --- cereal/log.capnp | 1 + selfdrive/car/car_specific.py | 2 ++ selfdrive/selfdrived/events.py | 9 +++++++++ 3 files changed, 12 insertions(+) diff --git a/cereal/log.capnp b/cereal/log.capnp index 3a6432c84..2f300881b 100644 --- a/cereal/log.capnp +++ b/cereal/log.capnp @@ -87,6 +87,7 @@ struct OnroadEvent @0xc4fa6047f024e718 { laneChange @50; lowMemory @51; stockAeb @52; + stockLkas @98; ldw @53; carUnrecognized @54; invalidLkasSetting @55; diff --git a/selfdrive/car/car_specific.py b/selfdrive/car/car_specific.py index cdeeebdcd..bc903ec08 100644 --- a/selfdrive/car/car_specific.py +++ b/selfdrive/car/car_specific.py @@ -119,6 +119,8 @@ class CarSpecificEvents: events.add(EventName.stockFcw) if CS.stockAeb: events.add(EventName.stockAeb) + if CS.stockLkas: + events.add(EventName.stockLkas) if CS.vEgo > MAX_CTRL_SPEED: events.add(EventName.speedTooHigh) if CS.cruiseState.nonAdaptive: diff --git a/selfdrive/selfdrived/events.py b/selfdrive/selfdrived/events.py index 1acfa2964..35d4bda42 100755 --- a/selfdrive/selfdrived/events.py +++ b/selfdrive/selfdrived/events.py @@ -477,6 +477,15 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = { ET.NO_ENTRY: NoEntryAlert("Stock AEB: Risk of Collision"), }, + EventName.stockLkas: { + ET.PERMANENT: Alert( + "TAKE CONTROL", + "Stock LKAS: Lane Departure Detected", + AlertStatus.critical, AlertSize.full, + Priority.HIGH, VisualAlert.fcw, AudibleAlert.none, 2.), + ET.NO_ENTRY: NoEntryAlert("Stock LKAS: Lane Departure Detected"), + }, + EventName.fcw: { ET.PERMANENT: Alert( "BRAKE!", From a30fc9bcd2d044e3f11b102fc443da5d15a7d314 Mon Sep 17 00:00:00 2001 From: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Thu, 1 Jan 2026 12:23:01 -0700 Subject: [PATCH 714/910] modeld: configurable camera offset (#1614) * modeld: configurable camera offset Negative Values: Shears the image to the left, moving the models center to the Right. Positive Value: Shears the image to the right, moving the models center to the Left. * modeld: camera offset class * verify zero offset I @ A = A * slithered and slunked * Update params_metadata.json * wait * Update model_renderer.py * Update model_renderer.py * requested changes * stricter * Update model_renderer.py * more * return default * Update params_metadata.json * final --------- Co-authored-by: Jason Wen --- common/params_keys.h | 1 + selfdrive/ui/mici/onroad/model_renderer.py | 15 +++- selfdrive/ui/onroad/model_renderer.py | 15 ++-- selfdrive/ui/sunnypilot/ui_state.py | 1 + sunnypilot/modeld/modeld.py | 4 + sunnypilot/modeld_v2/camera_offset_helper.py | 39 +++++++++ sunnypilot/modeld_v2/modeld.py | 7 +- .../tests/test_camera_offset_helper.py | 84 +++++++++++++++++++ sunnypilot/sunnylink/params_metadata.json | 7 ++ 9 files changed, 162 insertions(+), 11 deletions(-) create mode 100644 sunnypilot/modeld_v2/camera_offset_helper.py create mode 100644 sunnypilot/modeld_v2/tests/test_camera_offset_helper.py diff --git a/common/params_keys.h b/common/params_keys.h index 054ce97e6..a559d411e 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -221,6 +221,7 @@ inline static std::unordered_map keys = { {"BlindSpot", {PERSISTENT | BACKUP, BOOL, "0"}}, // sunnypilot model params + {"CameraOffset", {PERSISTENT | BACKUP, FLOAT, "0.0"}}, {"LagdToggle", {PERSISTENT | BACKUP, BOOL, "1"}}, {"LagdToggleDelay", {PERSISTENT | BACKUP, FLOAT, "0.2"}}, {"LagdValueCache", {PERSISTENT, FLOAT, "0.2"}}, diff --git a/selfdrive/ui/mici/onroad/model_renderer.py b/selfdrive/ui/mici/onroad/model_renderer.py index db316aa63..0908de0bf 100644 --- a/selfdrive/ui/mici/onroad/model_renderer.py +++ b/selfdrive/ui/mici/onroad/model_renderer.py @@ -80,6 +80,9 @@ class ModelRenderer(Widget): self._transform_dirty = True self._clip_region = None + self._counter = -1 + self._camera_offset = ui_state.params.get("CameraOffset", return_default=True) if ui_state.active_bundle else 0.0 + self._exp_gradient = Gradient( start=(0.0, 1.0), # Bottom of path end=(0.0, 0.0), # Top of path @@ -99,6 +102,10 @@ class ModelRenderer(Widget): def _render(self, rect: rl.Rectangle): sm = ui_state.sm + if self._counter % 180 == 0: # This runs at 60fps, so we query every 3 seconds + self._camera_offset = ui_state.params.get("CameraOffset", return_default=True) if ui_state.active_bundle else 0.0 + self._counter += 1 + self._torque_filter.update(-ui_state.sm['carOutput'].actuatorsOutput.torque) # Check if data is up-to-date @@ -150,13 +157,13 @@ class ModelRenderer(Widget): def _update_raw_points(self, model): """Update raw 3D points from model data""" - self._path.raw_points = np.array([model.position.x, model.position.y, model.position.z], dtype=np.float32).T + self._path.raw_points = np.array([model.position.x, np.array(model.position.y) + self._camera_offset, model.position.z], dtype=np.float32).T for i, lane_line in enumerate(model.laneLines): - self._lane_lines[i].raw_points = np.array([lane_line.x, lane_line.y, lane_line.z], dtype=np.float32).T + self._lane_lines[i].raw_points = np.array([lane_line.x, np.array(lane_line.y) + self._camera_offset, lane_line.z], dtype=np.float32).T for i, road_edge in enumerate(model.roadEdges): - self._road_edges[i].raw_points = np.array([road_edge.x, road_edge.y, road_edge.z], dtype=np.float32).T + self._road_edges[i].raw_points = np.array([road_edge.x, np.array(road_edge.y) + self._camera_offset, road_edge.z], dtype=np.float32).T self._lane_line_probs = np.array(model.laneLineProbs, dtype=np.float32) self._road_edge_stds = np.array(model.roadEdgeStds, dtype=np.float32) @@ -174,7 +181,7 @@ class ModelRenderer(Widget): # Get z-coordinate from path at the lead vehicle position z = self._path.raw_points[idx, 2] if idx < len(self._path.raw_points) else 0.0 - point = self._map_to_screen(d_rel, -y_rel, z + self._path_offset_z) + point = self._map_to_screen(d_rel, -y_rel + self._camera_offset, z + self._path_offset_z) if point: self._lead_vehicles[i] = self._update_lead_vehicle(d_rel, v_rel, point, self._rect) diff --git a/selfdrive/ui/onroad/model_renderer.py b/selfdrive/ui/onroad/model_renderer.py index cae976534..353cc5aa4 100644 --- a/selfdrive/ui/onroad/model_renderer.py +++ b/selfdrive/ui/onroad/model_renderer.py @@ -56,7 +56,8 @@ class ModelRenderer(Widget, ChevronMetrics, ModelRendererSP): self._road_edge_stds = np.zeros(2, dtype=np.float32) self._lead_vehicles = [LeadVehicle(), LeadVehicle()] self._path_offset_z = HEIGHT_INIT[0] - + self._counter = -1 + self._camera_offset = ui_state.params.get("CameraOffset", return_default=True) if ui_state.active_bundle else 0.0 # Initialize ModelPoints objects self._path = ModelPoints() self._lane_lines = [ModelPoints() for _ in range(4)] @@ -103,6 +104,10 @@ class ModelRenderer(Widget, ChevronMetrics, ModelRendererSP): live_calib = sm['liveCalibration'] self._path_offset_z = live_calib.height[0] if live_calib.height else HEIGHT_INIT[0] + if self._counter % 60 == 0: + self._camera_offset = ui_state.params.get("CameraOffset", return_default=True) if ui_state.active_bundle else 0.0 + self._counter += 1 + if sm.updated['carParams']: self._longitudinal_control = sm['carParams'].openpilotLongitudinalControl @@ -136,13 +141,13 @@ class ModelRenderer(Widget, ChevronMetrics, ModelRendererSP): def _update_raw_points(self, model): """Update raw 3D points from model data""" - self._path.raw_points = np.array([model.position.x, model.position.y, model.position.z], dtype=np.float32).T + self._path.raw_points = np.array([model.position.x, np.array(model.position.y) + self._camera_offset, model.position.z], dtype=np.float32).T for i, lane_line in enumerate(model.laneLines): - self._lane_lines[i].raw_points = np.array([lane_line.x, lane_line.y, lane_line.z], dtype=np.float32).T + self._lane_lines[i].raw_points = np.array([lane_line.x, np.array(lane_line.y) + self._camera_offset, lane_line.z], dtype=np.float32).T for i, road_edge in enumerate(model.roadEdges): - self._road_edges[i].raw_points = np.array([road_edge.x, road_edge.y, road_edge.z], dtype=np.float32).T + self._road_edges[i].raw_points = np.array([road_edge.x, np.array(road_edge.y) + self._camera_offset, road_edge.z], dtype=np.float32).T self._lane_line_probs = np.array(model.laneLineProbs, dtype=np.float32) self._road_edge_stds = np.array(model.roadEdgeStds, dtype=np.float32) @@ -160,7 +165,7 @@ class ModelRenderer(Widget, ChevronMetrics, ModelRendererSP): # Get z-coordinate from path at the lead vehicle position z = self._path.raw_points[idx, 2] if idx < len(self._path.raw_points) else 0.0 - point = self._map_to_screen(d_rel, -y_rel, z + self._path_offset_z) + point = self._map_to_screen(d_rel, -y_rel + self._camera_offset, z + self._path_offset_z) if point: self._lead_vehicles[i] = self._update_lead_vehicle(d_rel, v_rel, point, self._rect) diff --git a/selfdrive/ui/sunnypilot/ui_state.py b/selfdrive/ui/sunnypilot/ui_state.py index ca8125512..21ed78b09 100644 --- a/selfdrive/ui/sunnypilot/ui_state.py +++ b/selfdrive/ui/sunnypilot/ui_state.py @@ -73,6 +73,7 @@ class UIStateSP: self.developer_ui = self.params.get("DevUIInfo") self.rainbow_path = self.params.get_bool("RainbowMode") self.chevron_metrics = self.params.get("ChevronInfo") + self.active_bundle = self.params.get("ModelManager_ActiveBundle") class DeviceSP: diff --git a/sunnypilot/modeld/modeld.py b/sunnypilot/modeld/modeld.py index 3d11ed23f..b36aea405 100755 --- a/sunnypilot/modeld/modeld.py +++ b/sunnypilot/modeld/modeld.py @@ -24,6 +24,7 @@ from openpilot.sunnypilot.livedelay.helpers import get_lat_delay from openpilot.sunnypilot.modeld.runners import ModelRunner, Runtime from openpilot.sunnypilot.modeld.parse_model_outputs import Parser from openpilot.sunnypilot.modeld.fill_model_msg import fill_model_msg, fill_pose_msg, PublishState +from openpilot.sunnypilot.modeld_v2.camera_offset_helper import CameraOffsetHelper from openpilot.sunnypilot.modeld.constants import ModelConstants, Plan from openpilot.sunnypilot.models.helpers import get_active_bundle, get_model_path, load_metadata, prepare_inputs, load_meta_constants from openpilot.sunnypilot.modeld.models.commonmodel_pyx import ModelFrame, CLContext @@ -195,6 +196,7 @@ def main(demo=False): buf_main, buf_extra = None, None meta_main = FrameMeta() meta_extra = FrameMeta() + camera_offset_helper = CameraOffsetHelper() if demo: @@ -250,12 +252,14 @@ def main(demo=False): frame_id = sm["roadCameraState"].frameId if sm.frame % 60 == 0: model.lat_delay = get_lat_delay(params, sm["liveDelay"].lateralDelay) + camera_offset_helper.set_offset(params.get("CameraOffset", return_default=True)) lat_delay = model.lat_delay + model.LAT_SMOOTH_SECONDS if sm.updated["liveCalibration"] and sm.seen['roadCameraState'] and sm.seen['deviceState']: device_from_calib_euler = np.array(sm["liveCalibration"].rpyCalib, dtype=np.float32) dc = DEVICE_CAMERAS[(str(sm['deviceState'].deviceType), str(sm['roadCameraState'].sensor))] model_transform_main = get_warp_matrix(device_from_calib_euler, dc.ecam.intrinsics if main_wide_camera else dc.fcam.intrinsics, False).astype(np.float32) model_transform_extra = get_warp_matrix(device_from_calib_euler, dc.ecam.intrinsics, True).astype(np.float32) + model_transform_main, model_transform_extra = camera_offset_helper.update(model_transform_main, model_transform_extra, sm, main_wide_camera) live_calib_seen = True traffic_convention = np.zeros(2) diff --git a/sunnypilot/modeld_v2/camera_offset_helper.py b/sunnypilot/modeld_v2/camera_offset_helper.py new file mode 100644 index 000000000..7502c3eeb --- /dev/null +++ b/sunnypilot/modeld_v2/camera_offset_helper.py @@ -0,0 +1,39 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +import numpy as np + +from openpilot.common.transformations.camera import DEVICE_CAMERAS + + +class CameraOffsetHelper: + def __init__(self): + self.camera_offset = 0.0 + self.actual_camera_offset = 0.0 + + @staticmethod + def apply_camera_offset(model_transform, intrinsics, height, offset_param): + cy = intrinsics[1, 2] + shear = np.eye(3, dtype=np.float32) + shear[0, 1] = offset_param / height + shear[0, 2] = -offset_param / height * cy + model_transform = (shear @ model_transform).astype(np.float32) + return model_transform + + def set_offset(self, offset): + self.camera_offset = offset + + def update(self, model_transform_main, model_transform_extra, sm, main_wide_camera): + self.actual_camera_offset = (0.9 * self.actual_camera_offset) + (0.1 * self.camera_offset) + dc = DEVICE_CAMERAS[(str(sm['deviceState'].deviceType), str(sm['roadCameraState'].sensor))] + height = sm["liveCalibration"].height[0] if sm['liveCalibration'].height else 1.22 + + intrinsics_main = dc.ecam.intrinsics if main_wide_camera else dc.fcam.intrinsics + model_transform_main = self.apply_camera_offset(model_transform_main, intrinsics_main, height, self.actual_camera_offset) + + intrinsics_extra = dc.ecam.intrinsics + model_transform_extra = self.apply_camera_offset(model_transform_extra, intrinsics_extra, height, self.actual_camera_offset) + return model_transform_main, model_transform_extra diff --git a/sunnypilot/modeld_v2/modeld.py b/sunnypilot/modeld_v2/modeld.py index b3b1d35fd..be0724db0 100755 --- a/sunnypilot/modeld_v2/modeld.py +++ b/sunnypilot/modeld_v2/modeld.py @@ -21,6 +21,7 @@ from openpilot.sunnypilot.modeld_v2.fill_model_msg import fill_model_msg, fill_p from openpilot.sunnypilot.modeld_v2.constants import Plan from openpilot.sunnypilot.modeld_v2.models.commonmodel_pyx import DrivingModelFrame, CLContext from openpilot.sunnypilot.modeld_v2.meta_helper import load_meta_constants +from openpilot.sunnypilot.modeld_v2.camera_offset_helper import CameraOffsetHelper from openpilot.sunnypilot.livedelay.helpers import get_lat_delay from openpilot.sunnypilot.modeld.modeld_base import ModelStateBase @@ -230,6 +231,7 @@ def main(demo=False): buf_main, buf_extra = None, None meta_main = FrameMeta() meta_extra = FrameMeta() + camera_offset_helper = CameraOffsetHelper() if demo: @@ -285,13 +287,14 @@ def main(demo=False): if sm.frame % 60 == 0: model.lat_delay = get_lat_delay(params, sm["liveDelay"].lateralDelay) model.PLANPLUS_CONTROL = params.get("PlanplusControl", return_default=True) + camera_offset_helper.set_offset(params.get("CameraOffset", return_default=True)) lat_delay = model.lat_delay + model.LAT_SMOOTH_SECONDS if sm.updated["liveCalibration"] and sm.seen['roadCameraState'] and sm.seen['deviceState']: device_from_calib_euler = np.array(sm["liveCalibration"].rpyCalib, dtype=np.float32) dc = DEVICE_CAMERAS[(str(sm['deviceState'].deviceType), str(sm['roadCameraState'].sensor))] - model_transform_main = get_warp_matrix(device_from_calib_euler, dc.ecam.intrinsics if main_wide_camera else dc.fcam.intrinsics, - False).astype(np.float32) + model_transform_main = get_warp_matrix(device_from_calib_euler, dc.ecam.intrinsics if main_wide_camera else dc.fcam.intrinsics, False).astype(np.float32) model_transform_extra = get_warp_matrix(device_from_calib_euler, dc.ecam.intrinsics, True).astype(np.float32) + model_transform_main, model_transform_extra = camera_offset_helper.update(model_transform_main, model_transform_extra, sm, main_wide_camera) live_calib_seen = True traffic_convention = np.zeros(2) diff --git a/sunnypilot/modeld_v2/tests/test_camera_offset_helper.py b/sunnypilot/modeld_v2/tests/test_camera_offset_helper.py new file mode 100644 index 000000000..f25bcd0a3 --- /dev/null +++ b/sunnypilot/modeld_v2/tests/test_camera_offset_helper.py @@ -0,0 +1,84 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +import numpy as np + +from openpilot.common.transformations.camera import DEVICE_CAMERAS +from openpilot.common.transformations.model import get_warp_matrix +from openpilot.sunnypilot.modeld_v2.camera_offset_helper import CameraOffsetHelper + + +class MockStruct: + def __init__(self, **kwargs): + for k, v in kwargs.items(): + setattr(self, k, v) + + def __getitem__(self, item): + return getattr(self, item) + + +class TestCameraOffset: + def setup_method(self): + self.camera_offset = CameraOffsetHelper() + self.dc = DEVICE_CAMERAS[('mici', 'os04c10')] + + def test_smoothing(self): + self.camera_offset.set_offset(0.2) + + sm = MockStruct( + deviceState=MockStruct(deviceType='mici'), + roadCameraState=MockStruct(sensor='os04c10'), + liveCalibration=MockStruct(rpyCalib=[0.0, 0.0, 0.0], height=[1.22]) + ) + + intrinsics_main = self.dc.fcam.intrinsics + intrinsics_extra = self.dc.ecam.intrinsics + device_from_calib_euler = np.array([0.0, 0.0, 0.0], dtype=np.float32) + main_transform = get_warp_matrix(device_from_calib_euler, intrinsics_main, False).astype(np.float32) + extra_transform = get_warp_matrix(device_from_calib_euler, intrinsics_extra, True).astype(np.float32) + + self.camera_offset.update(main_transform, extra_transform, sm, False) + np.testing.assert_almost_equal(self.camera_offset.actual_camera_offset, 0.02) + self.camera_offset.update(main_transform, extra_transform, sm, False) + np.testing.assert_almost_equal(self.camera_offset.actual_camera_offset, 0.038) + + def test_camera_offset_(self): + intrinsics = self.dc.fcam.intrinsics + transform = np.eye(3, dtype=np.float32) + height = 1.22 + offset = 0.1 + + cy = intrinsics[1, 2] + expected_shear = np.eye(3, dtype=np.float32) + expected_shear[0, 1] = offset / height + expected_shear[0, 2] = -offset / height * cy + + result = CameraOffsetHelper.apply_camera_offset(transform, intrinsics, height, offset) + np.testing.assert_array_almost_equal(result, expected_shear) + + def test_update(self): + sm = MockStruct( + deviceState=MockStruct(deviceType='mici'), + roadCameraState=MockStruct(sensor='os04c10'), + liveCalibration=MockStruct(rpyCalib=[0.0, 0.0, 0.0], height=[1.22]) + ) + intrinsics_main = self.dc.fcam.intrinsics + intrinsics_extra = self.dc.ecam.intrinsics + device_from_calib_euler = np.array([0.0, 0.0, 0.0], dtype=np.float32) + main_transform = get_warp_matrix(device_from_calib_euler, intrinsics_main, False).astype(np.float32) + extra_transform = get_warp_matrix(device_from_calib_euler, intrinsics_extra, True).astype(np.float32) + + self.camera_offset.set_offset(0.0) # test default offset doesn't change transformation + main_out, extra_out = self.camera_offset.update(main_transform, extra_transform, sm, False) + np.testing.assert_array_equal(main_out, main_transform) + np.testing.assert_array_equal(extra_out, extra_transform) + + self.camera_offset.set_offset(0.2) # test valid offset changes transformation + main_out, extra_out = self.camera_offset.update(main_transform, extra_transform, sm, False) + assert not np.array_equal(main_out, main_transform) + assert not np.array_equal(extra_out, extra_transform) + assert main_out[0, 1] != 0.0 + assert main_out[0, 2] != 0.0 diff --git a/sunnypilot/sunnylink/params_metadata.json b/sunnypilot/sunnylink/params_metadata.json index 95a143ea6..cccf98d79 100644 --- a/sunnypilot/sunnylink/params_metadata.json +++ b/sunnypilot/sunnylink/params_metadata.json @@ -121,6 +121,13 @@ "title": "Camera Debug Exp Time", "description": "" }, + "CameraOffset": { + "title": "Adjust Camera Offset on non Default Model", + "description": "Adjust this to center the car in its lane by virtually shifting the camera's perspective. Negative Values: Shears the image to the left, moving the model's center to the Right; Positive Values: Shears the image to the right, moving the model's center to the Left. Note: these values are in meters.", + "min": -0.35, + "max": 0.35, + "step": 0.01 + }, "CarBatteryCapacity": { "title": "Car Battery Capacity", "description": "" From bb40d161e8d24f3e4c4835603610ea669ab00154 Mon Sep 17 00:00:00 2001 From: Alexandre Nobuharu Sato <66435071+AlexandreSato@users.noreply.github.com> Date: Thu, 1 Jan 2026 20:13:18 -0300 Subject: [PATCH 715/910] Add back badges for keep multilanguage support (#36967) --- selfdrive/ui/translations/README.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 selfdrive/ui/translations/README.md diff --git a/selfdrive/ui/translations/README.md b/selfdrive/ui/translations/README.md new file mode 100644 index 000000000..433eb7d64 --- /dev/null +++ b/selfdrive/ui/translations/README.md @@ -0,0 +1,3 @@ +# Multilanguage + +[![languages](https://raw.githubusercontent.com/commaai/openpilot/badges/translation_badge.svg)](#) From f62177a8274ac75b82011d1a7ae57f8458c85091 Mon Sep 17 00:00:00 2001 From: YassineYousfi Date: Thu, 1 Jan 2026 19:06:31 -0800 Subject: [PATCH 716/910] FrameReader: use hwaccel auto (#36973) * FrameReader: use hwaccel auto * rm main block --- tools/lib/framereader.py | 46 ++++++++++++---------------------------- 1 file changed, 13 insertions(+), 33 deletions(-) diff --git a/tools/lib/framereader.py b/tools/lib/framereader.py index 64e2f9f43..7f5571877 100644 --- a/tools/lib/framereader.py +++ b/tools/lib/framereader.py @@ -2,7 +2,6 @@ import os import subprocess import json import logging -from functools import cache from collections.abc import Iterator from collections import OrderedDict @@ -17,19 +16,6 @@ HEVC_SLICE_B = 0 HEVC_SLICE_P = 1 HEVC_SLICE_I = 2 -@cache -def get_hw_accel() -> list[str]: - """Detect and return the best available ffmpeg hardware acceleration.""" - priority = ("videotoolbox", "cuda", "vaapi", "d3d11va") - result = subprocess.run(["ffmpeg", "-hwaccels"], capture_output=True, text=True, timeout=5) - for accel in priority: - if accel in result.stdout.lower(): - logger.info(f"HW accelerated video decode found, using ffmpeg's {accel}") - return ["-hwaccel", accel] - logger.warning("no HW accelerated video found with `ffmpeg -hwaccels`. falling back to ffmpeg CPU decode") - return [] - - class LRUCache: def __init__(self, capacity: int): self._cache: OrderedDict = OrderedDict() @@ -47,7 +33,6 @@ class LRUCache: def __contains__(self, key): return key in self._cache - def assert_hvec(fn: str) -> None: with FileReader(fn) as f: header = f.read(4) @@ -57,11 +42,11 @@ def assert_hvec(fn: str) -> None: if 'hevc' not in fn: raise NotImplementedError(fn) -def decompress_video_data(rawdat, w, h, pix_fmt="rgb24", vid_fmt='hevc') -> np.ndarray: +def decompress_video_data(rawdat, w, h, pix_fmt="rgb24", vid_fmt='hevc', loglevel="info") -> np.ndarray: threads = os.getenv("FFMPEG_THREADS", "0") - args = ["ffmpeg", "-v", "quiet", + args = ["ffmpeg", "-v", loglevel, "-threads", threads, - *get_hw_accel(), + "-hwaccel", "auto", "-c:v", "hevc", "-vsync", "0", "-f", vid_fmt, @@ -114,15 +99,15 @@ def get_video_index(fn): 'probe': probe } - class FfmpegDecoder: def __init__(self, fn: str, index_data: dict|None = None, - pix_fmt: str = "rgb24"): + pix_fmt: str = "rgb24", loglevel="quiet"): self.fn = fn self.index, self.prefix, self.w, self.h = get_index_data(fn, index_data) self.frame_count = len(self.index) - 1 # sentinel row at the end self.iframes = np.where(self.index[:, 0] == HEVC_SLICE_I)[0] self.pix_fmt = pix_fmt + self.loglevel = loglevel def _gop_bounds(self, frame_idx: int): f_b = frame_idx @@ -134,7 +119,7 @@ class FfmpegDecoder: return f_b, f_e, self.index[f_b, 1], self.index[f_e, 1] def _decode_gop(self, raw: bytes) -> Iterator[np.ndarray]: - yield from decompress_video_data(raw, self.w, self.h, self.pix_fmt) + yield from decompress_video_data(raw, self.w, self.h, self.pix_fmt, loglevel=self.loglevel) def get_gop_start(self, frame_idx: int): return self.iframes[np.searchsorted(self.iframes, frame_idx, side="right") - 1] @@ -149,7 +134,7 @@ class FfmpegDecoder: f.seek(off_b) raw = self.prefix + f.read(off_e - off_b) # number of frames to discard inside this GOP before the wanted one - for i, frm in enumerate(decompress_video_data(raw, self.w, self.h, self.pix_fmt)): + for i, frm in enumerate(decompress_video_data(raw, self.w, self.h, self.pix_fmt, loglevel=self.loglevel)): fidx = f_b + i if fidx >= end_fidx: return @@ -157,17 +142,16 @@ class FfmpegDecoder: yield fidx, frm fidx += 1 -def FrameIterator(fn: str, index_data: dict|None=None, - pix_fmt: str = "rgb24", - start_fidx:int=0, end_fidx=None, frame_skip:int=1) -> Iterator[np.ndarray]: - dec = FfmpegDecoder(fn, pix_fmt=pix_fmt, index_data=index_data) +def FrameIterator(fn: str, index_data: dict|None=None, pix_fmt: str = "rgb24", + start_fidx:int=0, end_fidx=None, frame_skip:int=1, loglevel="quiet") -> Iterator[np.ndarray]: + dec = FfmpegDecoder(fn, pix_fmt=pix_fmt, index_data=index_data, loglevel=loglevel) for _, frame in dec.get_iterator(start_fidx=start_fidx, end_fidx=end_fidx, frame_skip=frame_skip): yield frame class FrameReader: - def __init__(self, fn: str, index_data: dict|None = None, - cache_size: int = 30, pix_fmt: str = "rgb24"): - self.decoder = FfmpegDecoder(fn, index_data, pix_fmt) + def __init__(self, fn: str, index_data: dict|None = None, cache_size: int = 30, + pix_fmt: str = "rgb24", loglevel="quiet"): + self.decoder = FfmpegDecoder(fn, index_data, pix_fmt, loglevel=loglevel) self.iframes = self.decoder.iframes self._cache: LRUCache = LRUCache(cache_size) self.w, self.h, self.frame_count, = self.decoder.w, self.decoder.h, self.decoder.frame_count @@ -187,7 +171,3 @@ class FrameReader: self.fidx, frame = next(self.it) self._cache[self.fidx] = frame return self._cache[fidx] - - -if __name__ == "__main__": - get_hw_accel() From adbf68f77120b4568de68c37b8bed4ed433e01fe Mon Sep 17 00:00:00 2001 From: YassineYousfi Date: Fri, 2 Jan 2026 11:29:45 -0800 Subject: [PATCH 717/910] FrameReader: add hwaccel arg and clear frames_cache (#36974) --- tools/lib/framereader.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tools/lib/framereader.py b/tools/lib/framereader.py index 7f5571877..c13f7fd31 100644 --- a/tools/lib/framereader.py +++ b/tools/lib/framereader.py @@ -42,11 +42,11 @@ def assert_hvec(fn: str) -> None: if 'hevc' not in fn: raise NotImplementedError(fn) -def decompress_video_data(rawdat, w, h, pix_fmt="rgb24", vid_fmt='hevc', loglevel="info") -> np.ndarray: +def decompress_video_data(rawdat, w, h, pix_fmt="rgb24", vid_fmt='hevc', hwaccel="auto", loglevel="info") -> np.ndarray: threads = os.getenv("FFMPEG_THREADS", "0") args = ["ffmpeg", "-v", loglevel, "-threads", threads, - "-hwaccel", "auto", + "-hwaccel", hwaccel, "-c:v", "hevc", "-vsync", "0", "-f", vid_fmt, @@ -101,13 +101,13 @@ def get_video_index(fn): class FfmpegDecoder: def __init__(self, fn: str, index_data: dict|None = None, - pix_fmt: str = "rgb24", loglevel="quiet"): + pix_fmt: str = "rgb24", hwaccel="auto", loglevel="quiet"): self.fn = fn self.index, self.prefix, self.w, self.h = get_index_data(fn, index_data) self.frame_count = len(self.index) - 1 # sentinel row at the end self.iframes = np.where(self.index[:, 0] == HEVC_SLICE_I)[0] self.pix_fmt = pix_fmt - self.loglevel = loglevel + self.loglevel, self.hwaccel = loglevel, hwaccel def _gop_bounds(self, frame_idx: int): f_b = frame_idx @@ -119,7 +119,7 @@ class FfmpegDecoder: return f_b, f_e, self.index[f_b, 1], self.index[f_e, 1] def _decode_gop(self, raw: bytes) -> Iterator[np.ndarray]: - yield from decompress_video_data(raw, self.w, self.h, self.pix_fmt, loglevel=self.loglevel) + yield from decompress_video_data(raw, self.w, self.h, pix_fmt=self.pix_fmt, hwaccel=self.hwaccel, loglevel=self.loglevel) def get_gop_start(self, frame_idx: int): return self.iframes[np.searchsorted(self.iframes, frame_idx, side="right") - 1] @@ -134,7 +134,7 @@ class FfmpegDecoder: f.seek(off_b) raw = self.prefix + f.read(off_e - off_b) # number of frames to discard inside this GOP before the wanted one - for i, frm in enumerate(decompress_video_data(raw, self.w, self.h, self.pix_fmt, loglevel=self.loglevel)): + for i, frm in enumerate(decompress_video_data(raw, self.w, self.h, self.pix_fmt, hwaccel=self.hwaccel, loglevel=self.loglevel)): fidx = f_b + i if fidx >= end_fidx: return @@ -143,15 +143,15 @@ class FfmpegDecoder: fidx += 1 def FrameIterator(fn: str, index_data: dict|None=None, pix_fmt: str = "rgb24", - start_fidx:int=0, end_fidx=None, frame_skip:int=1, loglevel="quiet") -> Iterator[np.ndarray]: - dec = FfmpegDecoder(fn, pix_fmt=pix_fmt, index_data=index_data, loglevel=loglevel) + start_fidx:int=0, end_fidx=None, frame_skip:int=1, hwaccel="auto", loglevel="quiet") -> Iterator[np.ndarray]: + dec = FfmpegDecoder(fn, pix_fmt=pix_fmt, index_data=index_data, hwaccel=hwaccel, loglevel=loglevel) for _, frame in dec.get_iterator(start_fidx=start_fidx, end_fidx=end_fidx, frame_skip=frame_skip): yield frame class FrameReader: def __init__(self, fn: str, index_data: dict|None = None, cache_size: int = 30, - pix_fmt: str = "rgb24", loglevel="quiet"): - self.decoder = FfmpegDecoder(fn, index_data, pix_fmt, loglevel=loglevel) + pix_fmt: str = "rgb24", hwaccel="auto", loglevel="quiet"): + self.decoder = FfmpegDecoder(fn, index_data=index_data, pix_fmt=pix_fmt, hwaccel=hwaccel, loglevel=loglevel) self.iframes = self.decoder.iframes self._cache: LRUCache = LRUCache(cache_size) self.w, self.h, self.frame_count, = self.decoder.w, self.decoder.h, self.decoder.frame_count From 50b8ae9e09b2e5a428ff2b45d8c062a700602d26 Mon Sep 17 00:00:00 2001 From: Nayan Date: Sat, 3 Jan 2026 08:35:02 -0500 Subject: [PATCH 718/910] sunnylink: update params metadata (#1636) * sunnylink model controls * cleanup more controls * update verbiage Co-authored-by: DevTekVE --------- Co-authored-by: DevTekVE --- sunnypilot/sunnylink/params_metadata.json | 67 ++++++++++++----------- 1 file changed, 35 insertions(+), 32 deletions(-) diff --git a/sunnypilot/sunnylink/params_metadata.json b/sunnypilot/sunnylink/params_metadata.json index cccf98d79..f0ddbc954 100644 --- a/sunnypilot/sunnylink/params_metadata.json +++ b/sunnypilot/sunnylink/params_metadata.json @@ -122,8 +122,8 @@ "description": "" }, "CameraOffset": { - "title": "Adjust Camera Offset on non Default Model", - "description": "Adjust this to center the car in its lane by virtually shifting the camera's perspective. Negative Values: Shears the image to the left, moving the model's center to the Right; Positive Values: Shears the image to the right, moving the model's center to the Left. Note: these values are in meters.", + "title": "Adjust Camera Offset", + "description": "Virtually shift camera's perspective to move model's center to Left(+ values) or Right (- values)", "min": -0.35, "max": 0.35, "step": 0.01 @@ -193,7 +193,7 @@ "description": "" }, "CustomAccIncrementsEnabled": { - "title": "Custom ACC Increments Enabled", + "title": "Custom ACC Increments", "description": "" }, "CustomAccLongPressIncrement": { @@ -211,8 +211,8 @@ "step": 1 }, "CustomTorqueParams": { - "title": "Custom Torque Params", - "description": "" + "title": "Enable Custom Torque Tuning", + "description": "Enables custom tuning for Torque lateral control" }, "DevUIInfo": { "title": "Developer UI Info", @@ -286,7 +286,7 @@ }, "EnforceTorqueControl": { "title": "Enforce Torque Control", - "description": "" + "description": "Enable this to enforce sunnypilot to steer with Torque lateral control." }, "ExperimentalMode": { "title": "Experimental Mode", @@ -451,12 +451,15 @@ "description": "" }, "LagdToggle": { - "title": "LaGD Toggle", - "description": "" + "title": "Live Learning Steer Delay", + "description": "Allow device to learn and adapt car's steering response time" }, "LagdToggleDelay": { - "title": "LaGD Toggle Delay", - "description": "" + "title": "Manual Software Delay", + "description": "Software delay to use when Live Learning Steer Delay is toggled off", + "min": 0.05, + "max": 0.5, + "step": 0.01 }, "LagdValueCache": { "title": "LaGD Value Cache", @@ -464,11 +467,11 @@ }, "LaneTurnDesire": { "title": "Lane Turn Desire", - "description": "" + "description": "Force model to plan an intent to turn based on blinker" }, "LaneTurnValue": { - "title": "Lane Turn Value", - "description": "", + "title": "Lane Turn Speed", + "description": "Maximum speed for lane turn desire", "min": 0, "max": 20, "step": 1 @@ -546,12 +549,12 @@ "description": "" }, "LiveTorqueParamsRelaxedToggle": { - "title": "Live Torque Params Relaxed Toggle", - "description": "" + "title": "Less Restrict Settings for Self-Tune (Beta)", + "description": "Less strict settings when using Self-Tune. This allows torqued to be more forgiving when learning values." }, "LiveTorqueParamsToggle": { - "title": "Live Torque Params Toggle", - "description": "" + "title": "Self-Tune", + "description": "Enables self-tune for Torque lateral control" }, "LocationFilterInitialState": { "title": "Location Filter Initial State", @@ -704,7 +707,7 @@ "description": "" }, "OffroadMode": { - "title": "Offroad Mode", + "title": "Force Offroad Mode", "description": "" }, "Offroad_CarUnrecognized": { @@ -768,18 +771,18 @@ "description": "" }, "OnroadScreenOffBrightness": { - "title": "Onroad Screen Off Brightness", + "title": "Onroad Brightness", "description": "", "min": 0, "max": 100, "step": 5 }, "OnroadScreenOffControl": { - "title": "Onroad Screen Off Control", - "description": "" + "title": "Onroad Screen: Reduced Brightness", + "description": "Turn off device screen or reduce brightness after driving starts" }, "OnroadScreenOffTimer": { - "title": "Onroad Screen Off Timer", + "title": "Onroad Brightness Delay", "description": "", "min": 0, "max": 60, @@ -885,7 +888,7 @@ "description": "" }, "RoadNameToggle": { - "title": "Road Name Toggle", + "title": "Display Road Name", "description": "" }, "RouteCount": { @@ -898,7 +901,7 @@ }, "ShowAdvancedControls": { "title": "Show Advanced Controls", - "description": "" + "description": "Enable to show advanced controls on device" }, "ShowDebugInfo": { "title": "UI Debug Mode", @@ -909,11 +912,11 @@ "description": "" }, "SmartCruiseControlMap": { - "title": "Smart Cruise Control Map", + "title": "Smart Cruise Control - Map", "description": "" }, "SmartCruiseControlVision": { - "title": "Smart Cruise Control Vision", + "title": "Smart Cruise Control - Vision", "description": "" }, "SnoozeUpdate": { @@ -921,7 +924,7 @@ "description": "" }, "SpeedLimitMode": { - "title": "Speed Limit Mode", + "title": "Speed Limit Assist Mode", "description": "", "options": [ { @@ -961,7 +964,7 @@ ] }, "SpeedLimitPolicy": { - "title": "Speed Limit Policy", + "title": "Speed Limit Source", "description": "", "options": [ { @@ -987,7 +990,7 @@ ] }, "SpeedLimitValueOffset": { - "title": "Speed Limit Value Offset", + "title": "Speed Limit Offset Value", "description": "", "min": -30, "max": 30, @@ -1042,18 +1045,18 @@ "description": "" }, "TorqueParamsOverrideEnabled": { - "title": "Torque Params Override Enabled", + "title": "Manual Real-Time Tuning", "description": "" }, "TorqueParamsOverrideFriction": { - "title": "Torque Params Override Friction", + "title": "Manual Tune - Friction", "description": "", "min": 0.0, "max": 1.0, "step": 0.01 }, "TorqueParamsOverrideLatAccelFactor": { - "title": "Torque Params Override Lat Accel Factor", + "title": "Manual Tune - Lateral Acceleration Factor", "description": "", "min": 0.1, "max": 5.0, From 9a04a5eaae0e5fea4cb7b5221d11aa68e2d0587d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 3 Jan 2026 15:54:55 -0500 Subject: [PATCH 719/910] [bot] Update Python packages (#1565) * Update Python packages * no --------- Co-authored-by: github-actions[bot] Co-authored-by: Jason Wen --- opendbc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc_repo b/opendbc_repo index 74ac67850..e03fbf9be 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 74ac6785011b2861b822651f51d0cd2f01ce79d2 +Subproject commit e03fbf9be8d063ad8aee260a67338e1dadea8037 From 987f53e69a145a7c02567bdcefc09674f5042494 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sat, 3 Jan 2026 21:01:21 -0500 Subject: [PATCH 720/910] [TIZI/TICI] ui: sunnylink status on sidebar (#1638) * Initial plan * feat: add sunnylink status metric Co-authored-by: devtekve <7696966+devtekve@users.noreply.github.com> * chore: extract sidebar constants Co-authored-by: devtekve <7696966+devtekve@users.noreply.github.com> * refactor: guard metric spacing Co-authored-by: devtekve <7696966+devtekve@users.noreply.github.com> * chore: clarify sunnylink helpers Co-authored-by: devtekve <7696966+devtekve@users.noreply.github.com> * refactor: guard metric spacing edge cases Co-authored-by: devtekve <7696966+devtekve@users.noreply.github.com> * chore: simplify spacing guards Co-authored-by: devtekve <7696966+devtekve@users.noreply.github.com> * chore: normalize sunnylink params Co-authored-by: devtekve <7696966+devtekve@users.noreply.github.com> * chore: harden sunnylink param parsing Co-authored-by: devtekve <7696966+devtekve@users.noreply.github.com> * chore: add param decode helper Co-authored-by: devtekve <7696966+devtekve@users.noreply.github.com> * chore: simplify sidebar metric spacing Co-authored-by: devtekve <7696966+devtekve@users.noreply.github.com> * chore: update sunnylink status color logic for improved clarity * sunnylink: update status handling to reflect offline state and improve fault indication sunnylink: enhance status handling with temporary fault indication * sunnylink: enhance status update logic for improved accuracy and clarity * make it int * Ugly with zero value, but done. Now we only need to remember to check the new sidebar if the old sidebar ever changes * Revert "Ugly with zero value, but done. Now we only need to remember to check the new sidebar if the old sidebar ever changes" This reverts commit 2d3b740e382206997885d47c60585b929baa773f. * decouple * no bad bot --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: devtekve <7696966+devtekve@users.noreply.github.com> Co-authored-by: DevTekVE Co-authored-by: Jason Wen --- selfdrive/ui/layouts/sidebar.py | 15 +++- selfdrive/ui/sunnypilot/layouts/sidebar.py | 87 ++++++++++++++++++++++ 2 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 selfdrive/ui/sunnypilot/layouts/sidebar.py diff --git a/selfdrive/ui/layouts/sidebar.py b/selfdrive/ui/layouts/sidebar.py index 050cd795b..bfa60c88e 100644 --- a/selfdrive/ui/layouts/sidebar.py +++ b/selfdrive/ui/layouts/sidebar.py @@ -9,6 +9,8 @@ from openpilot.system.ui.lib.multilang import tr, tr_noop from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.widgets import Widget +from openpilot.selfdrive.ui.sunnypilot.layouts.sidebar import SidebarSP + SIDEBAR_WIDTH = 300 METRIC_HEIGHT = 126 METRIC_WIDTH = 240 @@ -62,9 +64,10 @@ class MetricData: self.color = color -class Sidebar(Widget): +class Sidebar(Widget, SidebarSP): def __init__(self): - super().__init__() + Widget.__init__(self) + SidebarSP.__init__(self) self._net_type = NETWORK_TYPES.get(NetworkType.none) self._net_strength = 0 @@ -112,6 +115,7 @@ class Sidebar(Widget): self._update_temperature_status(device_state) self._update_connection_status(device_state) self._update_panda_status() + SidebarSP._update_sunnylink_status(self) def _update_network_status(self, device_state): self._net_type = NETWORK_TYPES.get(device_state.networkType.raw, tr_noop("Unknown")) @@ -200,6 +204,13 @@ class Sidebar(Widget): rl.draw_text_ex(self._font_regular, tr(self._net_type), text_pos, FONT_SIZE, 0, Colors.WHITE) def _draw_metrics(self, rect: rl.Rectangle): + if gui_app.sunnypilot_ui(): + metrics, start_y, spacing = SidebarSP._draw_metrics_w_sunnylink(self, rect, self._temp_status, self._panda_status, self._connect_status) + for idx, metric in enumerate(metrics): + self._draw_metric(rect, metric, start_y + idx * spacing) + + return + metrics = [(self._temp_status, 338), (self._panda_status, 496), (self._connect_status, 654)] for metric, y_offset in metrics: diff --git a/selfdrive/ui/sunnypilot/layouts/sidebar.py b/selfdrive/ui/sunnypilot/layouts/sidebar.py new file mode 100644 index 000000000..79bb15dbb --- /dev/null +++ b/selfdrive/ui/sunnypilot/layouts/sidebar.py @@ -0,0 +1,87 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +import pyray as rl +import time +from dataclasses import dataclass +from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.sunnypilot.sunnylink.api import UNREGISTERED_SUNNYLINK_DONGLE_ID +from openpilot.system.ui.lib.multilang import tr_noop + + +PING_TIMEOUT_NS = 80_000_000_000 # 80 seconds in nanoseconds +METRIC_HEIGHT = 126 +METRIC_MARGIN = 30 +METRIC_START_Y = 300 +HOME_BTN = rl.Rectangle(60, 860, 180, 180) + + +# Color scheme +class Colors: + WHITE = rl.WHITE + WHITE_DIM = rl.Color(255, 255, 255, 85) + GRAY = rl.Color(84, 84, 84, 255) + + # Status colors + GOOD = rl.WHITE + WARNING = rl.Color(218, 202, 37, 255) + DANGER = rl.Color(201, 34, 49, 255) + PROGRESS = rl.Color(0, 134, 233, 255) + DISABLED = rl.Color(128, 128, 128, 255) + + # UI elements + METRIC_BORDER = rl.Color(255, 255, 255, 85) + BUTTON_NORMAL = rl.WHITE + BUTTON_PRESSED = rl.Color(255, 255, 255, 166) + + +@dataclass(slots=True) +class MetricData: + label: str + value: str + color: rl.Color + + def update(self, label: str, value: str, color: rl.Color): + self.label = label + self.value = value + self.color = color + + +class SidebarSP: + def __init__(self): + self._sunnylink_status = MetricData(tr_noop("SUNNYLINK"), tr_noop("OFFLINE"), Colors.WARNING) + + def _update_sunnylink_status(self): + if not ui_state.params.get_bool("SunnylinkEnabled"): + self._sunnylink_status.update(tr_noop("SUNNYLINK"), tr_noop("DISABLED"), Colors.DISABLED) + return + + last_ping = ui_state.params.get("LastSunnylinkPingTime") or 0 + dongle_id = ui_state.params.get("SunnylinkDongleId") + + is_online = last_ping and (time.monotonic_ns() - last_ping) < PING_TIMEOUT_NS + is_temp_fault = ui_state.params.get_bool("SunnylinkTempFault") + is_registering = not is_temp_fault and dongle_id in (None, "", UNREGISTERED_SUNNYLINK_DONGLE_ID) + + # Determine status/color pair based on priority + if last_ping: + status, color = (tr_noop("ONLINE"), Colors.GOOD) if is_online else (tr_noop("ERROR"), Colors.DANGER) + elif is_temp_fault: + status, color = (tr_noop("FAULT"), Colors.WARNING) + elif is_registering: + status, color = (tr_noop("REGIST..."), Colors.PROGRESS) + else: + status, color = (tr_noop("OFFLINE"), Colors.DANGER) + + self._sunnylink_status.update(tr_noop("SUNNYLINK"), status, color) + + def _draw_metrics_w_sunnylink(self, rect: rl.Rectangle, _temp, _panda, _connect): + metrics = [_temp, _panda, _connect, self._sunnylink_status] + start_y = int(rect.y) + METRIC_START_Y + available_height = max(0, int(HOME_BTN.y) - METRIC_MARGIN - METRIC_HEIGHT - start_y) + spacing = available_height / max(1, len(metrics) - 1) + + return metrics, start_y, spacing From 1eb82fcc852798e6ef7dd0af7216442bdf82bd9c Mon Sep 17 00:00:00 2001 From: Nayan Date: Sun, 4 Jan 2026 00:27:22 -0500 Subject: [PATCH 721/910] ui: Global Brightness Override (#1579) * global brightness * initialize * keep stock * lint --------- Co-authored-by: Jason Wen --- selfdrive/ui/sunnypilot/ui_state.py | 3 +++ selfdrive/ui/ui_state.py | 11 ++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/selfdrive/ui/sunnypilot/ui_state.py b/selfdrive/ui/sunnypilot/ui_state.py index 21ed78b09..79ca410e1 100644 --- a/selfdrive/ui/sunnypilot/ui_state.py +++ b/selfdrive/ui/sunnypilot/ui_state.py @@ -22,6 +22,8 @@ class UIStateSP: self.sunnylink_state = SunnylinkState() + self.global_brightness_override: int = self.params.get("Brightness", return_default=True) + def update(self) -> None: if self.sunnylink_enabled: self.sunnylink_state.start() @@ -74,6 +76,7 @@ class UIStateSP: self.rainbow_path = self.params.get_bool("RainbowMode") self.chevron_metrics = self.params.get("ChevronInfo") self.active_bundle = self.params.get("ModelManager_ActiveBundle") + self.global_brightness_override = self.params.get("Brightness", return_default=True) class DeviceSP: diff --git a/selfdrive/ui/ui_state.py b/selfdrive/ui/ui_state.py index a86c84ada..7aad769bb 100644 --- a/selfdrive/ui/ui_state.py +++ b/selfdrive/ui/ui_state.py @@ -255,9 +255,18 @@ class Device(DeviceSP): else: clipped_brightness = ((clipped_brightness + 16.0) / 116.0) ** 3.0 - clipped_brightness = float(np.interp(clipped_brightness, [0, 1], [30, 100])) + if gui_app.sunnypilot_ui(): + if ui_state.global_brightness_override <= 0: + min_global_brightness = 1 if ui_state.global_brightness_override < 0 else 30 + clipped_brightness = float(np.interp(clipped_brightness, [0, 1], [min_global_brightness, 100])) + else: + clipped_brightness = float(np.interp(clipped_brightness, [0, 1], [30, 100])) brightness = round(self._brightness_filter.update(clipped_brightness)) + + if gui_app.sunnypilot_ui() and ui_state.global_brightness_override > 0: + brightness = ui_state.global_brightness_override + if not self._awake: brightness = 0 From e5e56614c97d147c8e05c69b60bd61b7f434dcb0 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 4 Jan 2026 00:33:32 -0500 Subject: [PATCH 722/910] ui: Customizable Interactive Timeout (#1640) * ui: Custom Interactive Timeout * rename * lint --- selfdrive/ui/sunnypilot/ui_state.py | 2 ++ selfdrive/ui/ui_state.py | 3 +++ 2 files changed, 5 insertions(+) diff --git a/selfdrive/ui/sunnypilot/ui_state.py b/selfdrive/ui/sunnypilot/ui_state.py index 79ca410e1..35d85eca5 100644 --- a/selfdrive/ui/sunnypilot/ui_state.py +++ b/selfdrive/ui/sunnypilot/ui_state.py @@ -22,6 +22,7 @@ class UIStateSP: self.sunnylink_state = SunnylinkState() + self.custom_interactive_timeout: int = self.params.get("InteractivityTimeout", return_default=True) self.global_brightness_override: int = self.params.get("Brightness", return_default=True) def update(self) -> None: @@ -76,6 +77,7 @@ class UIStateSP: self.rainbow_path = self.params.get_bool("RainbowMode") self.chevron_metrics = self.params.get("ChevronInfo") self.active_bundle = self.params.get("ModelManager_ActiveBundle") + self.custom_interactive_timeout = self.params.get("InteractivityTimeout", return_default=True) self.global_brightness_override = self.params.get("Brightness", return_default=True) diff --git a/selfdrive/ui/ui_state.py b/selfdrive/ui/ui_state.py index 7aad769bb..c7f9a7dda 100644 --- a/selfdrive/ui/ui_state.py +++ b/selfdrive/ui/ui_state.py @@ -221,6 +221,9 @@ class Device(DeviceSP): if self._override_interactive_timeout is not None: return self._override_interactive_timeout + if gui_app.sunnypilot_ui() and ui_state.custom_interactive_timeout != 0: + return ui_state.custom_interactive_timeout + ignition_timeout = 10 if gui_app.big_ui() else 5 return ignition_timeout if ui_state.ignition else 30 From be854df32d438cbd0e7977f61f9d104de179d2bc Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 4 Jan 2026 13:46:30 -0800 Subject: [PATCH 723/910] remove unused dbus-next package (#36979) --- pyproject.toml | 1 - uv.lock | 11 ----------- 2 files changed, 12 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e04889449..d6360a58a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -107,7 +107,6 @@ dev = [ "av", "azure-identity", "azure-storage-blob", - "dbus-next", # TODO: remove once we moved everything to jeepney "dictdiffer", "jeepney", "matplotlib", diff --git a/uv.lock b/uv.lock index a40167bb9..f0f3cb35b 100644 --- a/uv.lock +++ b/uv.lock @@ -499,15 +499,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e5/41/54fd429ff8147475fc24ca43246f85d78fb4e747c27f227e68f1594648f1/cython-3.2.3-py3-none-any.whl", hash = "sha256:06a1317097f540d3bb6c7b81ed58a0d8b9dbfa97abf39dfd4c22ee87a6c7241e", size = 1255561, upload-time = "2025-12-14T07:50:31.217Z" }, ] -[[package]] -name = "dbus-next" -version = "0.2.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ce/45/6a40fbe886d60a8c26f480e7d12535502b5ba123814b3b9a0b002ebca198/dbus_next-0.2.3.tar.gz", hash = "sha256:f4eae26909332ada528c0a3549dda8d4f088f9b365153952a408e28023a626a5", size = 71112, upload-time = "2021-07-25T22:11:28.398Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/fc/c0a3f4c4eaa5a22fbef91713474666e13d0ea2a69c84532579490a9f2cc8/dbus_next-0.2.3-py3-none-any.whl", hash = "sha256:58948f9aff9db08316734c0be2a120f6dc502124d9642f55e90ac82ffb16a18b", size = 57885, upload-time = "2021-07-25T22:11:25.466Z" }, -] - [[package]] name = "dearpygui" version = "2.1.1" @@ -1352,7 +1343,6 @@ dev = [ { name = "av" }, { name = "azure-identity" }, { name = "azure-storage-blob" }, - { name = "dbus-next" }, { name = "dictdiffer" }, { name = "jeepney" }, { name = "matplotlib" }, @@ -1408,7 +1398,6 @@ requires-dist = [ { name = "coverage", marker = "extra == 'testing'" }, { name = "crcmod-plus" }, { name = "cython" }, - { name = "dbus-next", marker = "extra == 'dev'" }, { name = "dearpygui", marker = "(platform_machine != 'aarch64' and extra == 'tools') or (sys_platform != 'linux' and extra == 'tools')", specifier = ">=2.1.0" }, { name = "dictdiffer", marker = "extra == 'dev'" }, { name = "future-fstrings" }, From 3c5974930aac69724a72aa895e2014807a9a0b52 Mon Sep 17 00:00:00 2001 From: Jason Young <46612682+jyoung8607@users.noreply.github.com> Date: Sun, 4 Jan 2026 17:09:26 -0500 Subject: [PATCH 724/910] cleanup SecOC release gating (#36980) --- selfdrive/car/card.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/car/card.py b/selfdrive/car/card.py index aa0d12e96..12b831347 100755 --- a/selfdrive/car/card.py +++ b/selfdrive/car/card.py @@ -117,7 +117,7 @@ class Car: safety_config.safetyModel = structs.CarParams.SafetyModel.noOutput self.CP.safetyConfigs = [safety_config] - if self.CP.secOcRequired and not is_release: + if self.CP.secOcRequired: # Copy user key if available try: with open("/cache/params/SecOCKey") as f: From 84bce8ae020b0819cf41d31e832dd565a53e642d Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 4 Jan 2026 17:52:10 -0800 Subject: [PATCH 725/910] rm pygame (#36982) * rm pygame * lil more * cleanup * lil more --- pyproject.toml | 1 - tools/replay/lib/ui_helpers.py | 91 ++++++------ tools/replay/ui.py | 244 +++++++++++++++++++-------------- 3 files changed, 182 insertions(+), 154 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d6360a58a..903995cf6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -113,7 +113,6 @@ dev = [ "opencv-python-headless", "parameterized >=0.8, <0.9", "pyautogui", - "pygame", "pyopencl; platform_machine != 'aarch64'", # broken on arm64 "pytools>=2025.1.6; platform_machine != 'aarch64'", "pywinctl", diff --git a/tools/replay/lib/ui_helpers.py b/tools/replay/lib/ui_helpers.py index b90cbd93b..7c95e9cad 100644 --- a/tools/replay/lib/ui_helpers.py +++ b/tools/replay/lib/ui_helpers.py @@ -1,9 +1,8 @@ import itertools -from typing import Any import matplotlib.pyplot as plt import numpy as np -import pygame +import pyray as rl from matplotlib.backends.backend_agg import FigureCanvasAgg @@ -18,21 +17,25 @@ YELLOW = (255, 255, 0) BLACK = (0, 0, 0) WHITE = (255, 255, 255) + class UIParams: lidar_x, lidar_y, lidar_zoom = 384, 960, 6 - lidar_car_x, lidar_car_y = lidar_x / 2., lidar_y / 1.1 + lidar_car_x, lidar_car_y = lidar_x / 2.0, lidar_y / 1.1 car_hwidth = 1.7272 / 2 * lidar_zoom car_front = 2.6924 * lidar_zoom car_back = 1.8796 * lidar_zoom car_color = 110 + + UP = UIParams METER_WIDTH = 20 + class Calibration: def __init__(self, num_px, rpy, intrinsic, calib_scale): self.intrinsic = intrinsic - self.extrinsics_matrix = get_view_frame_from_calib_frame(rpy[0], rpy[1], rpy[2], 0.0)[:,:3] + self.extrinsics_matrix = get_view_frame_from_calib_frame(rpy[0], rpy[1], rpy[2], 0.0)[:, :3] self.zoom = calib_scale def car_space_to_ff(self, x, y, z): @@ -47,19 +50,18 @@ class Calibration: return pts / self.zoom -_COLOR_CACHE : dict[tuple[int, int, int], Any] = {} +_COLOR_CACHE: dict[tuple[int, int, int], int] = { + (255, 0, 0): 1, # RED + (0, 255, 0): 2, # GREEN + (0, 0, 255): 3, # BLUE + (255, 255, 0): 4, # YELLOW + (0, 0, 0): 0, # BLACK + (255, 255, 255): 255, # WHITE +} + + def find_color(lidar_surface, color): - if color in _COLOR_CACHE: - return _COLOR_CACHE[color] - tcolor = 0 - ret = 255 - for x in lidar_surface.get_palette(): - if x[0:3] == color: - ret = tcolor - break - tcolor += 1 - _COLOR_CACHE[color] = ret - return ret + return _COLOR_CACHE.get(color, 255) def to_topdown_pt(y, x): @@ -91,13 +93,7 @@ def draw_path(path, color, img, calibration, top_down, lid_color=None, z_off=0): def init_plots(arr, name_to_arr_idx, plot_xlims, plot_ylims, plot_names, plot_colors, plot_styles): - color_palette = { "r": (1, 0, 0), - "g": (0, 1, 0), - "b": (0, 0, 1), - "k": (0, 0, 0), - "y": (1, 1, 0), - "p": (0, 1, 1), - "m": (1, 0, 1)} + color_palette = {"r": (1, 0, 0), "g": (0, 1, 0), "b": (0, 0, 1), "k": (0, 0, 0), "y": (1, 1, 0), "p": (0, 1, 1), "m": (1, 0, 1)} dpi = 90 fig = plt.figure(figsize=(575 / dpi, 600 / dpi), dpi=dpi) @@ -107,7 +103,7 @@ def init_plots(arr, name_to_arr_idx, plot_xlims, plot_ylims, plot_names, plot_co axs = [] for pn in range(len(plot_ylims)): - ax = fig.add_subplot(len(plot_ylims), 1, len(axs)+1) + ax = fig.add_subplot(len(plot_ylims), 1, len(axs) + 1) ax.set_xlim(plot_xlims[pn][0], plot_xlims[pn][1]) ax.set_ylim(plot_ylims[pn][0], plot_ylims[pn][1]) ax.patch.set_facecolor((0.4, 0.4, 0.4)) @@ -116,15 +112,11 @@ def init_plots(arr, name_to_arr_idx, plot_xlims, plot_ylims, plot_names, plot_co plots, idxs, plot_select = [], [], [] for i, pl_list in enumerate(plot_names): for j, item in enumerate(pl_list): - plot, = axs[i].plot(arr[:, name_to_arr_idx[item]], - label=item, - color=color_palette[plot_colors[i][j]], - linestyle=plot_styles[i][j]) + (plot,) = axs[i].plot(arr[:, name_to_arr_idx[item]], label=item, color=color_palette[plot_colors[i][j]], linestyle=plot_styles[i][j]) plots.append(plot) idxs.append(name_to_arr_idx[item]) plot_select.append(i) - axs[i].set_title(", ".join(f"{nm} ({cl})" - for (nm, cl) in zip(pl_list, plot_colors[i], strict=False)), fontsize=10) + axs[i].set_title(", ".join(f"{nm} ({cl})" for (nm, cl) in zip(pl_list, plot_colors[i], strict=False)), fontsize=10) axs[i].tick_params(axis="x", colors="white") axs[i].tick_params(axis="y", colors="white") axs[i].title.set_color("white") @@ -134,6 +126,12 @@ def init_plots(arr, name_to_arr_idx, plot_xlims, plot_ylims, plot_names, plot_co canvas.draw() + # Pre-create texture for plots (reuse each frame to avoid log spam) + w, h = canvas.get_width_height() + plot_image = rl.gen_image_color(w, h, rl.BLACK) + plot_texture = rl.load_texture_from_image(plot_image) + rl.unload_image(plot_image) + def draw_plots(arr): for ax in axs: ax.draw_artist(ax.patch) @@ -141,17 +139,13 @@ def init_plots(arr, name_to_arr_idx, plot_xlims, plot_ylims, plot_names, plot_co plots[i].set_ydata(arr[:, idxs[i]]) axs[plot_select[i]].draw_artist(plots[i]) - raw_data = canvas.buffer_rgba() - plot_surface = pygame.image.frombuffer(raw_data, canvas.get_width_height(), "RGBA").convert() - return plot_surface + raw_data = np.ascontiguousarray(canvas.buffer_rgba(), dtype=np.uint8) + rl.update_texture(plot_texture, rl.ffi.cast("void *", raw_data.ctypes.data)) + return plot_texture return draw_plots -def pygame_modules_have_loaded(): - return pygame.display.get_init() and pygame.font.get_init() - - def plot_model(m, img, calibration, top_down): if calibration is None or top_down is None: return @@ -166,7 +160,7 @@ def plot_model(m, img, calibration, top_down): _, py_top = to_topdown_pt(x + x_std, y) px, py_bottom = to_topdown_pt(x - x_std, y) - top_down[1][int(round(px - 4)):int(round(px + 4)), py_top:py_bottom] = find_color(top_down[0], YELLOW) + top_down[1][int(round(px - 4)) : int(round(px + 4)), py_top:py_bottom] = find_color(top_down[0], YELLOW) for path, prob, _ in zip(m.laneLines, m.laneLineProbs, m.laneLineStds, strict=True): color = (0, int(255 * prob), 0) @@ -202,22 +196,15 @@ def maybe_update_radar_points(lt, lid_overlay): # negative here since radar is left positive px, py = to_topdown_pt(pt[0], -pt[1]) if px != -1: - lid_overlay[px - 4:px + 4, py - 4:py + 4] = 0 - lid_overlay[px - 2:px + 2, py - 2:py + 2] = 255 + lid_overlay[px - 4 : px + 4, py - 4 : py + 4] = 0 + lid_overlay[px - 2 : px + 2, py - 2 : py + 2] = 255 + def get_blank_lid_overlay(UP): lid_overlay = np.zeros((UP.lidar_x, UP.lidar_y), 'uint8') # Draw the car. - lid_overlay[int(round(UP.lidar_car_x - UP.car_hwidth)):int( - round(UP.lidar_car_x + UP.car_hwidth)), int(round(UP.lidar_car_y - - UP.car_front))] = UP.car_color - lid_overlay[int(round(UP.lidar_car_x - UP.car_hwidth)):int( - round(UP.lidar_car_x + UP.car_hwidth)), int(round(UP.lidar_car_y + - UP.car_back))] = UP.car_color - lid_overlay[int(round(UP.lidar_car_x - UP.car_hwidth)), int( - round(UP.lidar_car_y - UP.car_front)):int(round( - UP.lidar_car_y + UP.car_back))] = UP.car_color - lid_overlay[int(round(UP.lidar_car_x + UP.car_hwidth)), int( - round(UP.lidar_car_y - UP.car_front)):int(round( - UP.lidar_car_y + UP.car_back))] = UP.car_color + lid_overlay[int(round(UP.lidar_car_x - UP.car_hwidth)) : int(round(UP.lidar_car_x + UP.car_hwidth)), int(round(UP.lidar_car_y - UP.car_front))] = UP.car_color + lid_overlay[int(round(UP.lidar_car_x - UP.car_hwidth)) : int(round(UP.lidar_car_x + UP.car_hwidth)), int(round(UP.lidar_car_y + UP.car_back))] = UP.car_color + lid_overlay[int(round(UP.lidar_car_x - UP.car_hwidth)), int(round(UP.lidar_car_y - UP.car_front)) : int(round(UP.lidar_car_y + UP.car_back))] = UP.car_color + lid_overlay[int(round(UP.lidar_car_x + UP.car_hwidth)), int(round(UP.lidar_car_y - UP.car_front)) : int(round(UP.lidar_car_y + UP.car_back))] = UP.car_color return lid_overlay diff --git a/tools/replay/ui.py b/tools/replay/ui.py index d71d63d1c..54667ebfe 100755 --- a/tools/replay/ui.py +++ b/tools/replay/ui.py @@ -5,57 +5,88 @@ import sys import cv2 import numpy as np -import pygame +import pyray as rl import cereal.messaging as messaging from openpilot.common.basedir import BASEDIR from openpilot.common.transformations.camera import DEVICE_CAMERAS -from openpilot.tools.replay.lib.ui_helpers import (UP, - BLACK, GREEN, - YELLOW, Calibration, - get_blank_lid_overlay, init_plots, - maybe_update_radar_points, plot_lead, - plot_model, - pygame_modules_have_loaded) +from openpilot.tools.replay.lib.ui_helpers import ( + UP, + BLACK, + GREEN, + YELLOW, + Calibration, + get_blank_lid_overlay, + init_plots, + maybe_update_radar_points, + plot_lead, + plot_model, +) from msgq.visionipc import VisionIpcClient, VisionStreamType os.environ['BASEDIR'] = BASEDIR ANGLE_SCALE = 5.0 + def ui_thread(addr): cv2.setNumThreads(1) - pygame.init() - pygame.font.init() - assert pygame_modules_have_loaded() - disp_info = pygame.display.Info() - max_height = disp_info.current_h + # Get monitor info before creating window + rl.set_config_flags(rl.ConfigFlags.FLAG_MSAA_4X_HINT) + rl.init_window(1, 1, "") + max_height = rl.get_monitor_height(0) + rl.close_window() hor_mode = os.getenv("HORIZONTAL") is not None - hor_mode = True if max_height < 960+300 else hor_mode + hor_mode = True if max_height < 960 + 300 else hor_mode if hor_mode: - size = (640+384+640, 960) + size = (640 + 384 + 640, 960) write_x = 5 write_y = 680 else: - size = (640+384, 960+300) + size = (640 + 384, 960 + 300) write_x = 645 write_y = 970 - pygame.display.set_caption("openpilot debug UI") - screen = pygame.display.set_mode(size, pygame.DOUBLEBUF) + rl.set_trace_log_level(rl.TraceLogLevel.LOG_ERROR) + rl.set_config_flags(rl.ConfigFlags.FLAG_MSAA_4X_HINT) + rl.init_window(size[0], size[1], "openpilot debug UI") + rl.set_target_fps(60) - alert1_font = pygame.font.SysFont("arial", 30) - alert2_font = pygame.font.SysFont("arial", 20) - info_font = pygame.font.SysFont("arial", 15) + # Load font + font_path = os.path.join(BASEDIR, "selfdrive/assets/fonts/JetBrainsMono-Medium.ttf") + font = rl.load_font_ex(font_path, 32, None, 0) - camera_surface = pygame.surface.Surface((640, 480), 0, 24).convert() - top_down_surface = pygame.surface.Surface((UP.lidar_x, UP.lidar_y), 0, 8) + # Create textures for camera and top-down view + camera_image = rl.gen_image_color(640, 480, rl.BLACK) + camera_texture = rl.load_texture_from_image(camera_image) + rl.unload_image(camera_image) - sm = messaging.SubMaster(['carState', 'longitudinalPlan', 'carControl', 'radarState', 'liveCalibration', 'controlsState', - 'selfdriveState', 'liveTracks', 'modelV2', 'liveParameters', 'roadCameraState'], addr=addr) + # lid_overlay array is (lidar_x, lidar_y) = (384, 960) + # pygame treats first axis as width, so texture is 384 wide x 960 tall + # For raylib, we need to transpose to get (height, width) = (960, 384) for the RGBA array + top_down_image = rl.gen_image_color(UP.lidar_x, UP.lidar_y, rl.BLACK) + top_down_texture = rl.load_texture_from_image(top_down_image) + rl.unload_image(top_down_image) + + sm = messaging.SubMaster( + [ + 'carState', + 'longitudinalPlan', + 'carControl', + 'radarState', + 'liveCalibration', + 'controlsState', + 'selfdriveState', + 'liveTracks', + 'modelV2', + 'liveParameters', + 'roadCameraState', + ], + addr=addr, + ) img = np.zeros((480, 640, 3), dtype='uint8') imgff = None @@ -65,74 +96,78 @@ def ui_thread(addr): lid_overlay_blank = get_blank_lid_overlay(UP) # plots - name_to_arr_idx = { "gas": 0, - "computer_gas": 1, - "user_brake": 2, - "computer_brake": 3, - "v_ego": 4, - "v_pid": 5, - "angle_steers_des": 6, - "angle_steers": 7, - "angle_steers_k": 8, - "steer_torque": 9, - "v_override": 10, - "v_cruise": 11, - "a_ego": 12, - "a_target": 13} + name_to_arr_idx = { + "gas": 0, + "computer_gas": 1, + "user_brake": 2, + "computer_brake": 3, + "v_ego": 4, + "v_pid": 5, + "angle_steers_des": 6, + "angle_steers": 7, + "angle_steers_k": 8, + "steer_torque": 9, + "v_override": 10, + "v_cruise": 11, + "a_ego": 12, + "a_target": 13, + } plot_arr = np.zeros((100, len(name_to_arr_idx.values()))) plot_xlims = [(0, plot_arr.shape[0]), (0, plot_arr.shape[0]), (0, plot_arr.shape[0]), (0, plot_arr.shape[0])] - plot_ylims = [(-0.1, 1.1), (-ANGLE_SCALE, ANGLE_SCALE), (0., 75.), (-3.0, 2.0)] - plot_names = [["gas", "computer_gas", "user_brake", "computer_brake"], - ["angle_steers", "angle_steers_des", "angle_steers_k", "steer_torque"], - ["v_ego", "v_override", "v_pid", "v_cruise"], - ["a_ego", "a_target"]] - plot_colors = [["b", "b", "g", "r", "y"], - ["b", "g", "y", "r"], - ["b", "g", "r", "y"], - ["b", "r"]] - plot_styles = [["-", "-", "-", "-", "-"], - ["-", "-", "-", "-"], - ["-", "-", "-", "-"], - ["-", "-"]] + plot_ylims = [(-0.1, 1.1), (-ANGLE_SCALE, ANGLE_SCALE), (0.0, 75.0), (-3.0, 2.0)] + plot_names = [ + ["gas", "computer_gas", "user_brake", "computer_brake"], + ["angle_steers", "angle_steers_des", "angle_steers_k", "steer_torque"], + ["v_ego", "v_override", "v_pid", "v_cruise"], + ["a_ego", "a_target"], + ] + plot_colors = [["b", "b", "g", "r", "y"], ["b", "g", "y", "r"], ["b", "g", "r", "y"], ["b", "r"]] + plot_styles = [["-", "-", "-", "-", "-"], ["-", "-", "-", "-"], ["-", "-", "-", "-"], ["-", "-"]] draw_plots = init_plots(plot_arr, name_to_arr_idx, plot_xlims, plot_ylims, plot_names, plot_colors, plot_styles) + # Palette for converting lid_overlay grayscale indices to RGBA colors + palette = np.zeros((256, 4), dtype=np.uint8) + palette[:, 3] = 255 # alpha + palette[1] = [255, 0, 0, 255] # RED + palette[2] = [0, 255, 0, 255] # GREEN + palette[3] = [0, 0, 255, 255] # BLUE + palette[4] = [255, 255, 0, 255] # YELLOW + palette[110] = [110, 110, 110, 255] # car_color (gray) + palette[255] = [255, 255, 255, 255] # WHITE + vipc_client = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_ROAD, True) - while True: - for event in pygame.event.get(): - if event.type == pygame.QUIT: - pygame.quit() - sys.exit() - - screen.fill((64, 64, 64)) - lid_overlay = lid_overlay_blank.copy() - top_down = top_down_surface, lid_overlay - + while not rl.window_should_close(): # ***** frame ***** if not vipc_client.is_connected(): - vipc_client.connect(True) + vipc_client.connect(False) + + rl.begin_drawing() + rl.clear_background(rl.Color(64, 64, 64, 255)) yuv_img_raw = vipc_client.recv() if yuv_img_raw is None or not yuv_img_raw.data.any(): + rl.draw_text_ex(font, "waiting for frames", rl.Vector2(200, 200), 30, 0, rl.WHITE) + rl.end_drawing() continue + lid_overlay = lid_overlay_blank.copy() + top_down = top_down_texture, lid_overlay + sm.update(0) camera = DEVICE_CAMERAS[("tici", str(sm['roadCameraState'].sensor))] imgff = np.frombuffer(yuv_img_raw.data, dtype=np.uint8).reshape((len(yuv_img_raw.data) // vipc_client.stride, vipc_client.stride)) num_px = vipc_client.width * vipc_client.height - rgb = cv2.cvtColor(imgff[:vipc_client.height * 3 // 2, :vipc_client.width], cv2.COLOR_YUV2RGB_NV12) + rgb = cv2.cvtColor(imgff[: vipc_client.height * 3 // 2, : vipc_client.width], cv2.COLOR_YUV2RGB_NV12) qcam = "QCAM" in os.environ - bb_scale = (528 if qcam else camera.fcam.width) / 640. - calib_scale = camera.fcam.width / 640. - zoom_matrix = np.asarray([ - [bb_scale, 0., 0.], - [0., bb_scale, 0.], - [0., 0., 1.]]) + bb_scale = (528 if qcam else camera.fcam.width) / 640.0 + calib_scale = camera.fcam.width / 640.0 + zoom_matrix = np.asarray([[bb_scale, 0.0, 0.0], [0.0, bb_scale, 0.0], [0.0, 0.0, 1.0]]) cv2.warpAffine(rgb, zoom_matrix[:2], (img.shape[1], img.shape[0]), dst=img, flags=cv2.WARP_INVERSE_MAP) intrinsic_matrix = camera.fcam.intrinsics @@ -151,11 +186,11 @@ def ui_thread(addr): plot_arr[-1, name_to_arr_idx['angle_steers_k']] = angle_steers_k plot_arr[-1, name_to_arr_idx['gas']] = sm['carState'].gasDEPRECATED # TODO gas is deprecated - plot_arr[-1, name_to_arr_idx['computer_gas']] = np.clip(sm['carControl'].actuators.accel/4.0, 0.0, 1.0) + plot_arr[-1, name_to_arr_idx['computer_gas']] = np.clip(sm['carControl'].actuators.accel / 4.0, 0.0, 1.0) plot_arr[-1, name_to_arr_idx['user_brake']] = sm['carState'].brake plot_arr[-1, name_to_arr_idx['steer_torque']] = sm['carControl'].actuators.torque * ANGLE_SCALE # TODO brake is deprecated - plot_arr[-1, name_to_arr_idx['computer_brake']] = np.clip(-sm['carControl'].actuators.accel/4.0, 0.0, 1.0) + plot_arr[-1, name_to_arr_idx['computer_brake']] = np.clip(-sm['carControl'].actuators.accel / 4.0, 0.0, 1.0) plot_arr[-1, name_to_arr_idx['v_ego']] = sm['carState'].vEgo plot_arr[-1, name_to_arr_idx['v_cruise']] = sm['carState'].cruiseState.speed plot_arr[-1, name_to_arr_idx['a_ego']] = sm['carState'].aEgo @@ -177,56 +212,63 @@ def ui_thread(addr): calibration = Calibration(num_px, rpyCalib, intrinsic_matrix, calib_scale) # *** blits *** - pygame.surfarray.blit_array(camera_surface, img.swapaxes(0, 1)) - screen.blit(camera_surface, (0, 0)) + # Update camera texture from numpy array + img_rgba = cv2.cvtColor(img, cv2.COLOR_RGB2RGBA) + rl.update_texture(camera_texture, rl.ffi.cast("void *", img_rgba.ctypes.data)) + rl.draw_texture(camera_texture, 0, 0, rl.WHITE) # display alerts - alert_line1 = alert1_font.render(sm['selfdriveState'].alertText1, True, (255, 0, 0)) - alert_line2 = alert2_font.render(sm['selfdriveState'].alertText2, True, (255, 0, 0)) - screen.blit(alert_line1, (180, 150)) - screen.blit(alert_line2, (180, 190)) + rl.draw_text_ex(font, sm['selfdriveState'].alertText1, rl.Vector2(180, 150), 30, 0, rl.RED) + rl.draw_text_ex(font, sm['selfdriveState'].alertText2, rl.Vector2(180, 190), 20, 0, rl.RED) + # draw plots (texture is reused internally) + plot_texture = draw_plots(plot_arr) if hor_mode: - screen.blit(draw_plots(plot_arr), (640+384, 0)) + rl.draw_texture(plot_texture, 640 + 384, 0, rl.WHITE) else: - screen.blit(draw_plots(plot_arr), (0, 600)) + rl.draw_texture(plot_texture, 0, 600, rl.WHITE) - pygame.surfarray.blit_array(*top_down) - screen.blit(top_down[0], (640, 0)) + # Convert lid_overlay to RGBA and update top_down texture + # lid_overlay is (384, 960), need to transpose to (960, 384) for row-major RGBA buffer + lid_rgba = palette[lid_overlay.T] + rl.update_texture(top_down_texture, rl.ffi.cast("void *", np.ascontiguousarray(lid_rgba).ctypes.data)) + rl.draw_texture(top_down_texture, 640, 0, rl.WHITE) SPACING = 25 - lines = [ - info_font.render("ENABLED", True, GREEN if sm['selfdriveState'].enabled else BLACK), - info_font.render("SPEED: " + str(round(sm['carState'].vEgo, 1)) + " m/s", True, YELLOW), - info_font.render("LONG CONTROL STATE: " + str(sm['controlsState'].longControlState), True, YELLOW), - info_font.render("LONG MPC SOURCE: " + str(sm['longitudinalPlan'].longitudinalPlanSource), True, YELLOW), + ("ENABLED", GREEN if sm['selfdriveState'].enabled else BLACK), + ("SPEED: " + str(round(sm['carState'].vEgo, 1)) + " m/s", YELLOW), + ("LONG CONTROL STATE: " + str(sm['controlsState'].longControlState), YELLOW), + ("LONG MPC SOURCE: " + str(sm['longitudinalPlan'].longitudinalPlanSource), YELLOW), None, - info_font.render("ANGLE OFFSET (AVG): " + str(round(sm['liveParameters'].angleOffsetAverageDeg, 2)) + " deg", True, YELLOW), - info_font.render("ANGLE OFFSET (INSTANT): " + str(round(sm['liveParameters'].angleOffsetDeg, 2)) + " deg", True, YELLOW), - info_font.render("STIFFNESS: " + str(round(sm['liveParameters'].stiffnessFactor * 100., 2)) + " %", True, YELLOW), - info_font.render("STEER RATIO: " + str(round(sm['liveParameters'].steerRatio, 2)), True, YELLOW) + ("ANGLE OFFSET (AVG): " + str(round(sm['liveParameters'].angleOffsetAverageDeg, 2)) + " deg", YELLOW), + ("ANGLE OFFSET (INSTANT): " + str(round(sm['liveParameters'].angleOffsetDeg, 2)) + " deg", YELLOW), + ("STIFFNESS: " + str(round(sm['liveParameters'].stiffnessFactor * 100.0, 2)) + " %", YELLOW), + ("STEER RATIO: " + str(round(sm['liveParameters'].steerRatio, 2)), YELLOW), ] for i, line in enumerate(lines): if line is not None: - screen.blit(line, (write_x, write_y + i * SPACING)) + color = rl.Color(line[1][0], line[1][1], line[1][2], 255) + rl.draw_text_ex(font, line[0], rl.Vector2(write_x, write_y + i * SPACING), 20, 0, color) + + rl.end_drawing() + + rl.unload_texture(camera_texture) + rl.unload_texture(top_down_texture) + rl.unload_font(font) + rl.close_window() - # this takes time...vsync or something - pygame.display.flip() def get_arg_parser(): - parser = argparse.ArgumentParser( - description="Show replay data in a UI.", - formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser = argparse.ArgumentParser(description="Show replay data in a UI.", formatter_class=argparse.ArgumentDefaultsHelpFormatter) - parser.add_argument("ip_address", nargs="?", default="127.0.0.1", - help="The ip address on which to receive zmq messages.") + parser.add_argument("ip_address", nargs="?", default="127.0.0.1", help="The ip address on which to receive zmq messages.") - parser.add_argument("--frame-address", default=None, - help="The frame address (fully qualified ZMQ endpoint for frames) on which to receive zmq messages.") + parser.add_argument("--frame-address", default=None, help="The frame address (fully qualified ZMQ endpoint for frames) on which to receive zmq messages.") return parser + if __name__ == "__main__": args = get_arg_parser().parse_args(sys.argv[1:]) From ea64c4c0ae71e22b51852dc82065f76d96973235 Mon Sep 17 00:00:00 2001 From: Jason Young <46612682+jyoung8607@users.noreply.github.com> Date: Sun, 4 Jan 2026 21:35:13 -0500 Subject: [PATCH 726/910] VW: Enable torqued (#36983) --- selfdrive/locationd/torqued.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/locationd/torqued.py b/selfdrive/locationd/torqued.py index 3f9b846e8..f4dc5a147 100755 --- a/selfdrive/locationd/torqued.py +++ b/selfdrive/locationd/torqued.py @@ -33,7 +33,7 @@ MIN_BUCKET_POINTS = np.array([100, 300, 500, 500, 500, 500, 300, 100]) MIN_ENGAGE_BUFFER = 2 # secs VERSION = 1 # bump this to invalidate old parameter caches -ALLOWED_CARS = ['toyota', 'hyundai', 'rivian', 'honda'] +ALLOWED_CARS = ['toyota', 'hyundai', 'rivian', 'honda', 'volkswagen'] def slope2rot(slope): From 5d3ab260e1ca25f3e196a42e2e2d7dc08f5dc1e3 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 5 Jan 2026 10:15:20 -0800 Subject: [PATCH 727/910] welcome lexus ls --- RELEASES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASES.md b/RELEASES.md index 186f1bc04..6191c6ba3 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,5 +1,6 @@ Version 0.10.4 (2026-02-17) ======================== +* Lexus LS 2018 support thanks to Hacheoy! Version 0.10.3 (2025-12-17) ======================== From c693bc1247e5c5f066262a1790cca4229c1e98a1 Mon Sep 17 00:00:00 2001 From: YassineYousfi Date: Mon, 5 Jan 2026 16:14:05 -0800 Subject: [PATCH 728/910] =?UTF-8?q?MacroStiff=20Model=20=F0=9F=9F=A5?= =?UTF-8?q?=F0=9F=9F=A9=F0=9F=9F=A6=F0=9F=9F=A8=20(#36972)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 8c06e95e-d7c0-4fd9-ba02-9f0b6848785e/400 * test * test * test now --- selfdrive/modeld/models/driving_policy.onnx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/modeld/models/driving_policy.onnx b/selfdrive/modeld/models/driving_policy.onnx index e0eb91812..4f7aaf086 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:f8fe9a71b0fd428a045a82ed50790179f77aa664391198f078e11e7b2cb2c2d7 +oid sha256:4cf2023d6742c20aac7cecbf520afe370a64bcf5efdcf3531b1b128f9dfa3cc8 size 13926324 From c66d8d17a45e083f74d77482575f775a22238ec3 Mon Sep 17 00:00:00 2001 From: infiniteCable2 Date: Thu, 8 Jan 2026 18:50:04 +0100 Subject: [PATCH 729/910] fix max steering power handling --- opendbc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc_repo b/opendbc_repo index c09ce28d7..54b7220dc 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit c09ce28d75107d02ca1f66b91573921da90c03ce +Subproject commit 54b7220dc2d597abb3da3508b57b6035d7f9b415 From 9233452d75febc0fd744a13c4f5558ea9d0a0636 Mon Sep 17 00:00:00 2001 From: infiniteCable2 Date: Thu, 8 Jan 2026 19:05:25 +0100 Subject: [PATCH 730/910] Update opendbc_repo --- opendbc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc_repo b/opendbc_repo index 54b7220dc..89ac00965 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 54b7220dc2d597abb3da3508b57b6035d7f9b415 +Subproject commit 89ac009652290652967e64e3968b1cb8d2d2b038 From 3edb3243f6ecfcf4a8db6ffa8b2e86ab179a5730 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Fri, 9 Jan 2026 09:16:57 -0800 Subject: [PATCH 731/910] SC driving (#36986) f1d30a23-4122-400a-80a6-557502284c36/200 --- selfdrive/modeld/models/driving_policy.onnx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/modeld/models/driving_policy.onnx b/selfdrive/modeld/models/driving_policy.onnx index 4f7aaf086..27e4c8f7b 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:4cf2023d6742c20aac7cecbf520afe370a64bcf5efdcf3531b1b128f9dfa3cc8 +oid sha256:66f406ee179d984a4d8c93e38da479dcd1893127308dd3a7c322a7481a6b51b2 size 13926324 From f51c2aecede836a9fa9246cd2436a47d844a2e7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Fri, 9 Jan 2026 15:04:33 -0800 Subject: [PATCH 732/910] Modeld: less lat smoothing (#36987) * lat is plenty smooth! * fix --- selfdrive/modeld/modeld.py | 2 +- selfdrive/test/process_replay/ref_commit | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index 006eeef6f..1f347dc32 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -40,7 +40,7 @@ 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 +LAT_SMOOTH_SECONDS = 0.0 LONG_SMOOTH_SECONDS = 0.3 MIN_LAT_CONTROL_SPEED = 0.3 diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 4a58e321f..c109bf49f 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -e0ad86508edb61b3eaa1b84662c515d2c3368295 \ No newline at end of file +b259f6f8f099a9d82e4c65dd5deae2e4e293007b \ No newline at end of file From a58db66a98b3afe723deaf5b723a412a8233e47c Mon Sep 17 00:00:00 2001 From: Nayan Date: Fri, 9 Jan 2026 18:40:09 -0500 Subject: [PATCH 733/910] sunnylink: add units to param metadata (#1643) add units --- sunnypilot/sunnylink/params_metadata.json | 68 ++++++++++++++++++++--- 1 file changed, 60 insertions(+), 8 deletions(-) diff --git a/sunnypilot/sunnylink/params_metadata.json b/sunnypilot/sunnylink/params_metadata.json index f0ddbc954..e484787b6 100644 --- a/sunnypilot/sunnylink/params_metadata.json +++ b/sunnypilot/sunnylink/params_metadata.json @@ -126,11 +126,13 @@ "description": "Virtually shift camera's perspective to move model's center to Left(+ values) or Right (- values)", "min": -0.35, "max": 0.35, - "step": 0.01 + "step": 0.01, + "unit": "meters" }, "CarBatteryCapacity": { "title": "Car Battery Capacity", - "description": "" + "description": "Battery Size", + "unit": "kWh" }, "CarList": { "title": "Supported Car List", @@ -396,7 +398,8 @@ }, "InteractivityTimeout": { "title": "Interactivity Timeout", - "description": "" + "description": "", + "unit": "seconds" }, "IsDevelopmentBranch": { "title": "Is Development Branch", @@ -630,7 +633,8 @@ }, "MaxTimeOffroad": { "title": "Max Time Offroad", - "description": "" + "description": "", + "unit": "seconds" }, "ModelManager_ActiveBundle": { "title": "Model Manager Active Bundle", @@ -784,9 +788,56 @@ "OnroadScreenOffTimer": { "title": "Onroad Brightness Delay", "description": "", - "min": 0, - "max": 60, - "step": 1 + "options": [ + { + "value": 15, + "label": "15s" + }, + { + "value": 30, + "label": "30s" + }, + { + "value": 60, + "label": "1m" + }, + { + "value": 120, + "label": "2m" + }, + { + "value": 180, + "label": "3m" + }, + { + "value": 240, + "label": "4m" + }, + { + "value": 300, + "label": "5m" + }, + { + "value": 360, + "label": "6m" + }, + { + "value": 420, + "label": "7m" + }, + { + "value": 480, + "label": "8m" + }, + { + "value": 540, + "label": "9m" + }, + { + "value": 600, + "label": "10m" + } + ] }, "OnroadUploads": { "title": "Onroad Uploads", @@ -1060,7 +1111,8 @@ "description": "", "min": 0.1, "max": 5.0, - "step": 0.1 + "step": 0.1, + "unit": "m/s²" }, "ToyotaEnforceStockLongitudinal": { "title": "Toyota: Enforce Factory Longitudinal Control", From 15d3a166f725725697e670cae3b5087624a42bf6 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 9 Jan 2026 20:49:14 -0800 Subject: [PATCH 734/910] enable pyopencl on arm64 (#36990) --- pyproject.toml | 2 +- uv.lock | 42 +++++++++--------------------------------- 2 files changed, 10 insertions(+), 34 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 903995cf6..c21ba734d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -113,7 +113,7 @@ dev = [ "opencv-python-headless", "parameterized >=0.8, <0.9", "pyautogui", - "pyopencl; platform_machine != 'aarch64'", # broken on arm64 + "pyopencl", "pytools>=2025.1.6; platform_machine != 'aarch64'", "pywinctl", "pyprof2calltree", diff --git a/uv.lock b/uv.lock index f0f3cb35b..73c8fe7c9 100644 --- a/uv.lock +++ b/uv.lock @@ -1349,8 +1349,7 @@ dev = [ { name = "opencv-python-headless" }, { name = "parameterized" }, { name = "pyautogui" }, - { name = "pygame" }, - { name = "pyopencl", marker = "platform_machine != 'aarch64'" }, + { name = "pyopencl" }, { name = "pyprof2calltree" }, { name = "pytools", marker = "platform_machine != 'aarch64'" }, { name = "pywinctl" }, @@ -1423,9 +1422,8 @@ requires-dist = [ { name = "pyautogui", marker = "extra == 'dev'" }, { name = "pycapnp", specifier = "==2.1.0" }, { name = "pycryptodome" }, - { name = "pygame", marker = "extra == 'dev'" }, { name = "pyjwt" }, - { name = "pyopencl", marker = "platform_machine != 'aarch64' and extra == 'dev'" }, + { name = "pyopencl", marker = "extra == 'dev'" }, { name = "pyopenssl", specifier = "<24.3.0" }, { name = "pyprof2calltree", marker = "extra == 'dev'" }, { name = "pyserial" }, @@ -1778,28 +1776,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9b/4d/b9add7c84060d4c1906abe9a7e5359f2a60f7a9a4f67268b2766673427d8/pyee-13.0.0-py3-none-any.whl", hash = "sha256:48195a3cddb3b1515ce0695ed76036b5ccc2ef3a9f963ff9f77aec0139845498", size = 15730, upload-time = "2025-03-17T18:53:14.532Z" }, ] -[[package]] -name = "pygame" -version = "2.6.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/49/cc/08bba60f00541f62aaa252ce0cfbd60aebd04616c0b9574f755b583e45ae/pygame-2.6.1.tar.gz", hash = "sha256:56fb02ead529cee00d415c3e007f75e0780c655909aaa8e8bf616ee09c9feb1f", size = 14808125, upload-time = "2024-09-29T13:41:34.698Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c4/ca/8f367cb9fe734c4f6f6400e045593beea2635cd736158f9fabf58ee14e3c/pygame-2.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:20349195326a5e82a16e351ed93465a7845a7e2a9af55b7bc1b2110ea3e344e1", size = 13113753, upload-time = "2024-09-29T14:26:13.751Z" }, - { url = "https://files.pythonhosted.org/packages/83/47/6edf2f890139616b3219be9cfcc8f0cb8f42eb15efd59597927e390538cb/pygame-2.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f3935459109da4bb0b3901da9904f0a3e52028a3332a355d298b1673a334cf21", size = 12378146, upload-time = "2024-09-29T14:26:22.456Z" }, - { url = "https://files.pythonhosted.org/packages/00/9e/0d8aa8cf93db2d2ee38ebaf1c7b61d0df36ded27eb726221719c150c673d/pygame-2.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c31dbdb5d0217f32764797d21c2752e258e5fb7e895326538d82b5f75a0cd856", size = 13611760, upload-time = "2024-09-29T11:10:47.317Z" }, - { url = "https://files.pythonhosted.org/packages/d7/9e/d06adaa5cc65876bcd7a24f59f67e07f7e4194e6298130024ed3fb22c456/pygame-2.6.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:173badf82fa198e6888017bea40f511cb28e69ecdd5a72b214e81e4dcd66c3b1", size = 14298054, upload-time = "2024-09-29T11:39:53.891Z" }, - { url = "https://files.pythonhosted.org/packages/7a/a1/9ae2852ebd3a7cc7d9ae7ff7919ab983e4a5c1b7a14e840732f23b2b48f6/pygame-2.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce8cc108b92de9b149b344ad2e25eedbe773af0dc41dfb24d1f07f679b558c60", size = 13977107, upload-time = "2024-09-29T11:39:56.831Z" }, - { url = "https://files.pythonhosted.org/packages/31/df/6788fd2e9a864d0496a77670e44a7c012184b7a5382866ab0e60c55c0f28/pygame-2.6.1-cp311-cp311-win32.whl", hash = "sha256:811e7b925146d8149d79193652cbb83e0eca0aae66476b1cb310f0f4226b8b5c", size = 10250863, upload-time = "2024-09-29T11:44:48.199Z" }, - { url = "https://files.pythonhosted.org/packages/d2/55/ca3eb851aeef4f6f2e98a360c201f0d00bd1ba2eb98e2c7850d80aabc526/pygame-2.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:91476902426facd4bb0dad4dc3b2573bc82c95c71b135e0daaea072ed528d299", size = 10622016, upload-time = "2024-09-29T12:17:01.545Z" }, - { url = "https://files.pythonhosted.org/packages/92/16/2c602c332f45ff9526d61f6bd764db5096ff9035433e2172e2d2cadae8db/pygame-2.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4ee7f2771f588c966fa2fa8b829be26698c9b4836f82ede5e4edc1a68594942e", size = 13118279, upload-time = "2024-09-29T14:26:30.427Z" }, - { url = "https://files.pythonhosted.org/packages/cd/53/77ccbc384b251c6e34bfd2e734c638233922449a7844e3c7a11ef91cee39/pygame-2.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c8040ea2ab18c6b255af706ec01355c8a6b08dc48d77fd4ee783f8fc46a843bf", size = 12384524, upload-time = "2024-09-29T14:26:49.996Z" }, - { url = "https://files.pythonhosted.org/packages/06/be/3ed337583f010696c3b3435e89a74fb29d0c74d0931e8f33c0a4246307a9/pygame-2.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c47a6938de93fa610accd4969e638c2aebcb29b2fca518a84c3a39d91ab47116", size = 13587123, upload-time = "2024-09-29T11:10:50.072Z" }, - { url = "https://files.pythonhosted.org/packages/fd/ca/b015586a450db59313535662991b34d24c1f0c0dc149cc5f496573900f4e/pygame-2.6.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33006f784e1c7d7e466fcb61d5489da59cc5f7eb098712f792a225df1d4e229d", size = 14275532, upload-time = "2024-09-29T11:39:59.356Z" }, - { url = "https://files.pythonhosted.org/packages/b9/f2/d31e6ad42d657af07be2ffd779190353f759a07b51232b9e1d724f2cda46/pygame-2.6.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1206125f14cae22c44565c9d333607f1d9f59487b1f1432945dfc809aeaa3e88", size = 13952653, upload-time = "2024-09-29T11:40:01.781Z" }, - { url = "https://files.pythonhosted.org/packages/f3/42/8ea2a6979e6fa971702fece1747e862e2256d4a8558fe0da6364dd946c53/pygame-2.6.1-cp312-cp312-win32.whl", hash = "sha256:84fc4054e25262140d09d39e094f6880d730199710829902f0d8ceae0213379e", size = 10252421, upload-time = "2024-09-29T11:14:26.877Z" }, - { url = "https://files.pythonhosted.org/packages/5f/90/7d766d54bb95939725e9a9361f9c06b0cfbe3fe100aa35400f0a461a278a/pygame-2.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:3a9e7396be0d9633831c3f8d5d82dd63ba373ad65599628294b7a4f8a5a01a65", size = 10624591, upload-time = "2024-09-29T11:52:54.489Z" }, -] - [[package]] name = "pygetwindow" version = "0.0.9" @@ -4290,10 +4266,10 @@ name = "pyopencl" version = "2025.2.7" 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 = "typing-extensions", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "numpy" }, + { name = "platformdirs" }, + { name = "pytools" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f8/cb/8927052160bc0d3bd1123a645aaf57f696da364216b57b49f92ba0777bcc/pyopencl-2025.2.7.tar.gz", hash = "sha256:a68d92eb2970418b1a7ca45aff71984c02d2e4261e01402b273f257b5d6d7511", size = 444787, upload-time = "2025-10-28T14:23:15.497Z" } wheels = [ @@ -4515,9 +4491,9 @@ name = "pytools" version = "2025.2.5" 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/c3/7b/f885a57e61ded45b5b10ca60f0b7575c9fb9a282e7513d0e23a33ee647e1/pytools-2025.2.5.tar.gz", hash = "sha256:a7f5350644d46d98ee9c7e67b4b41693308aa0f5e9b188d8f0694b27dc94e3a2", size = 85594, upload-time = "2025-10-07T15:53:30.49Z" } wheels = [ From 7e18bfebc97bda00763f5609fd4d6d3bbe76dda4 Mon Sep 17 00:00:00 2001 From: infiniteCable2 Date: Sat, 10 Jan 2026 14:33:21 +0100 Subject: [PATCH 735/910] reduce power by v correctly limitted by allowed steps --- opendbc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc_repo b/opendbc_repo index 89ac00965..fc6d29753 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 89ac009652290652967e64e3968b1cb8d2d2b038 +Subproject commit fc6d29753414f0ee332e42c30368f90114b04b1c From a0c10be1ffd9d3d73409aa826b111713be1a85bf Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sat, 10 Jan 2026 21:24:44 +0100 Subject: [PATCH 736/910] Revert "ui: Global Brightness Override (#1579)" This reverts commit 1eb82fcc --- selfdrive/ui/sunnypilot/ui_state.py | 2 -- selfdrive/ui/ui_state.py | 11 +---------- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/selfdrive/ui/sunnypilot/ui_state.py b/selfdrive/ui/sunnypilot/ui_state.py index 35d85eca5..fe7124e5c 100644 --- a/selfdrive/ui/sunnypilot/ui_state.py +++ b/selfdrive/ui/sunnypilot/ui_state.py @@ -23,7 +23,6 @@ class UIStateSP: self.sunnylink_state = SunnylinkState() self.custom_interactive_timeout: int = self.params.get("InteractivityTimeout", return_default=True) - self.global_brightness_override: int = self.params.get("Brightness", return_default=True) def update(self) -> None: if self.sunnylink_enabled: @@ -78,7 +77,6 @@ class UIStateSP: self.chevron_metrics = self.params.get("ChevronInfo") self.active_bundle = self.params.get("ModelManager_ActiveBundle") self.custom_interactive_timeout = self.params.get("InteractivityTimeout", return_default=True) - self.global_brightness_override = self.params.get("Brightness", return_default=True) class DeviceSP: diff --git a/selfdrive/ui/ui_state.py b/selfdrive/ui/ui_state.py index c7f9a7dda..11e0cce74 100644 --- a/selfdrive/ui/ui_state.py +++ b/selfdrive/ui/ui_state.py @@ -258,18 +258,9 @@ class Device(DeviceSP): else: clipped_brightness = ((clipped_brightness + 16.0) / 116.0) ** 3.0 - if gui_app.sunnypilot_ui(): - if ui_state.global_brightness_override <= 0: - min_global_brightness = 1 if ui_state.global_brightness_override < 0 else 30 - clipped_brightness = float(np.interp(clipped_brightness, [0, 1], [min_global_brightness, 100])) - else: - clipped_brightness = float(np.interp(clipped_brightness, [0, 1], [30, 100])) + clipped_brightness = float(np.interp(clipped_brightness, [0, 1], [30, 100])) brightness = round(self._brightness_filter.update(clipped_brightness)) - - if gui_app.sunnypilot_ui() and ui_state.global_brightness_override > 0: - brightness = ui_state.global_brightness_override - if not self._awake: brightness = 0 From 2a3a68e1f8c0a3be8163de8404e4819d7345ae0b Mon Sep 17 00:00:00 2001 From: infiniteCable2 Date: Sun, 11 Jan 2026 09:07:21 +0100 Subject: [PATCH 737/910] AWV signal low frequency handling --- opendbc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc_repo b/opendbc_repo index fc6d29753..4731f34cc 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit fc6d29753414f0ee332e42c30368f90114b04b1c +Subproject commit 4731f34cce21dc41e0648179a498487b95d253af From 7e8592b559ca06247a58d369abf84acbb64eb704 Mon Sep 17 00:00:00 2001 From: infiniteCable2 Date: Sun, 11 Jan 2026 17:13:23 +0100 Subject: [PATCH 738/910] MADS: support driver assistance button --- opendbc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc_repo b/opendbc_repo index 4731f34cc..466762a95 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 4731f34cce21dc41e0648179a498487b95d253af +Subproject commit 466762a95dd5055374b1166121c69364da2952f7 From 1252188b4b8e4228af29ed12639d4183186ef9c7 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 13 Jan 2026 14:33:13 -0800 Subject: [PATCH 739/910] sensord: tighten temperature threshold (#36994) * sensord: tighten temperature threshold * lil more --- system/sensord/tests/test_sensord.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/system/sensord/tests/test_sensord.py b/system/sensord/tests/test_sensord.py index 5e98e1224..20214e12f 100644 --- a/system/sensord/tests/test_sensord.py +++ b/system/sensord/tests/test_sensord.py @@ -32,23 +32,17 @@ SENSOR_CONFIGURATIONS: list[set] = { Sensor = log.SensorEventData.SensorSource SensorConfig = namedtuple('SensorConfig', ['type', 'sanity_min', 'sanity_max']) ALL_SENSORS = { - Sensor.lsm6ds3: { - SensorConfig("acceleration", 5, 15), - SensorConfig("gyroUncalibrated", 0, .2), - SensorConfig("temperature", 0, 60), - }, - Sensor.lsm6ds3trc: { SensorConfig("acceleration", 5, 15), SensorConfig("gyroUncalibrated", 0, .2), - SensorConfig("temperature", 0, 60), + SensorConfig("temperature", 10, 40), # set for max range of our office }, Sensor.mmc5603nj: { SensorConfig("magneticUncalibrated", 0, 300), } } - +ALL_SENSORS[Sensor.lsm6ds3] = ALL_SENSORS[Sensor.lsm6ds3trc] def get_irq_count(irq: int): with open(f"/sys/kernel/irq/{irq}/per_cpu_count") as f: From 5e4b88201e78a2c4d84f29b919c333a2ea1997ce Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 15 Jan 2026 14:38:43 -0800 Subject: [PATCH 740/910] Toyota: whitelist hybrids for standstill resume behaviour (#36996) tioyota --- selfdrive/car/car_specific.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/selfdrive/car/car_specific.py b/selfdrive/car/car_specific.py index bc903ec08..c93d7ac4a 100644 --- a/selfdrive/car/car_specific.py +++ b/selfdrive/car/car_specific.py @@ -2,6 +2,7 @@ from cereal import car, log from opendbc.car import DT_CTRL, structs from opendbc.car.car_helpers import interfaces from opendbc.car.interfaces import MAX_CTRL_SPEED +from opendbc.car.toyota.values import ToyotaFlags from openpilot.selfdrive.selfdrived.events import Events @@ -58,7 +59,7 @@ class CarSpecificEvents: # TODO: when we check for unexpected disengagement, check gear not S1, S2, S3 if self.CP.openpilotLongitudinalControl: # Only can leave standstill when planner wants to move - if CS.cruiseState.standstill and not CS.brakePressed and CC.cruiseControl.resume: + if CS.cruiseState.standstill and not CS.brakePressed and (CC.cruiseControl.resume or self.CP.flags & ToyotaFlags.HYBRID.value): events.add(EventName.resumeRequired) if CS.vEgo < self.CP.minEnableSpeed: events.add(EventName.belowEngageSpeed) From 7f8dbf24e72a37cc1b587d7390c6b6274ef581e7 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 15 Jan 2026 17:18:40 -0800 Subject: [PATCH 741/910] Cabana: fix for internal source (#36998) * fix for internal sources from cursor * clean up * more * not needed * clean up * sure --- tools/replay/filereader.cc | 2 +- tools/replay/framereader.cc | 2 +- tools/replay/util.cc | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tools/replay/filereader.cc b/tools/replay/filereader.cc index d74aaebab..cb13775a3 100644 --- a/tools/replay/filereader.cc +++ b/tools/replay/filereader.cc @@ -17,7 +17,7 @@ std::string cacheFilePath(const std::string &url) { } std::string FileReader::read(const std::string &file, std::atomic *abort) { - const bool is_remote = file.find("https://") == 0; + const bool is_remote = (file.find("https://") == 0) || (file.find("http://") == 0); const std::string local_file = is_remote ? cacheFilePath(file) : file; std::string result; diff --git a/tools/replay/framereader.cc b/tools/replay/framereader.cc index a5f0f748c..96ec5915b 100644 --- a/tools/replay/framereader.cc +++ b/tools/replay/framereader.cc @@ -72,7 +72,7 @@ FrameReader::~FrameReader() { } bool FrameReader::load(CameraType type, const std::string &url, bool no_hw_decoder, std::atomic *abort, bool local_cache, int chunk_size, int retries) { - auto local_file_path = url.find("https://") == 0 ? cacheFilePath(url) : url; + auto local_file_path = (url.find("https://") == 0 || url.find("http://") == 0) ? cacheFilePath(url) : url; if (!util::file_exists(local_file_path)) { FileReader f(local_cache, chunk_size, retries); if (f.read(url, abort).empty()) { diff --git a/tools/replay/util.cc b/tools/replay/util.cc index 94cea961f..481564322 100644 --- a/tools/replay/util.cc +++ b/tools/replay/util.cc @@ -154,6 +154,8 @@ size_t getRemoteFileSize(const std::string &url, std::atomic *abort) { curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, dumy_write_cb); curl_easy_setopt(curl, CURLOPT_HEADER, 1); curl_easy_setopt(curl, CURLOPT_NOBODY, 1); + curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); CURLM *cm = curl_multi_init(); curl_multi_add_handle(cm, curl); From 1f9efd93115db3dba28459bec790cfb35ee58e2f Mon Sep 17 00:00:00 2001 From: Matt Purnell <65473602+mpurnell1@users.noreply.github.com> Date: Sat, 17 Jan 2026 00:31:26 -0600 Subject: [PATCH 742/910] transformations: move Cython to pure Python (#36830) * Remove cython for transformations * Add new test * Switch back to program to fix mac builds * Convert to Python instead * Fix failing builds * lint * Implement conversion in pure python/numpy * Add more tests * Fix bugs in tests --- common/SConscript | 7 +- common/transformations/SConscript | 5 - common/transformations/coordinates.cc | 100 ----- common/transformations/coordinates.hpp | 43 --- common/transformations/orientation.cc | 144 -------- common/transformations/orientation.hpp | 17 - .../transformations/tests/test_coordinates.py | 33 ++ .../transformations/tests/test_orientation.py | 30 ++ common/transformations/transformations.pxd | 72 ---- common/transformations/transformations.py | 342 ++++++++++++++++++ common/transformations/transformations.pyx | 173 --------- selfdrive/modeld/SConscript | 2 +- 12 files changed, 407 insertions(+), 561 deletions(-) delete mode 100644 common/transformations/SConscript delete mode 100644 common/transformations/coordinates.cc delete mode 100644 common/transformations/coordinates.hpp delete mode 100644 common/transformations/orientation.cc delete mode 100644 common/transformations/orientation.hpp delete mode 100644 common/transformations/transformations.pxd create mode 100644 common/transformations/transformations.py delete mode 100644 common/transformations/transformations.pyx diff --git a/common/SConscript b/common/SConscript index c771ee78b..1c68cf05c 100644 --- a/common/SConscript +++ b/common/SConscript @@ -19,11 +19,6 @@ if GetOption('extras'): # Cython bindings params_python = envCython.Program('params_pyx.so', 'params_pyx.pyx', LIBS=envCython['LIBS'] + [_common, 'zmq', 'json11']) -SConscript([ - 'transformations/SConscript', -]) - -Import('transformations_python') -common_python = [params_python, transformations_python] +common_python = [params_python] Export('common_python') diff --git a/common/transformations/SConscript b/common/transformations/SConscript deleted file mode 100644 index 4ac73a165..000000000 --- a/common/transformations/SConscript +++ /dev/null @@ -1,5 +0,0 @@ -Import('env', 'envCython') - -transformations = env.Library('transformations', ['orientation.cc', 'coordinates.cc']) -transformations_python = envCython.Program('transformations.so', 'transformations.pyx') -Export('transformations', 'transformations_python') diff --git a/common/transformations/coordinates.cc b/common/transformations/coordinates.cc deleted file mode 100644 index f3f10e547..000000000 --- a/common/transformations/coordinates.cc +++ /dev/null @@ -1,100 +0,0 @@ -#define _USE_MATH_DEFINES - -#include "common/transformations/coordinates.hpp" - -#include -#include -#include - -double a = 6378137; // lgtm [cpp/short-global-name] -double b = 6356752.3142; // lgtm [cpp/short-global-name] -double esq = 6.69437999014 * 0.001; // lgtm [cpp/short-global-name] -double e1sq = 6.73949674228 * 0.001; - - -static Geodetic to_degrees(Geodetic geodetic){ - geodetic.lat = RAD2DEG(geodetic.lat); - geodetic.lon = RAD2DEG(geodetic.lon); - return geodetic; -} - -static Geodetic to_radians(Geodetic geodetic){ - geodetic.lat = DEG2RAD(geodetic.lat); - geodetic.lon = DEG2RAD(geodetic.lon); - return geodetic; -} - - -ECEF geodetic2ecef(const Geodetic &geodetic) { - auto g = to_radians(geodetic); - double xi = sqrt(1.0 - esq * pow(sin(g.lat), 2)); - double x = (a / xi + g.alt) * cos(g.lat) * cos(g.lon); - double y = (a / xi + g.alt) * cos(g.lat) * sin(g.lon); - double z = (a / xi * (1.0 - esq) + g.alt) * sin(g.lat); - return {x, y, z}; -} - -Geodetic ecef2geodetic(const ECEF &e) { - // Convert from ECEF to geodetic using Ferrari's methods - // https://en.wikipedia.org/wiki/Geographic_coordinate_conversion#Ferrari.27s_solution - double x = e.x; - double y = e.y; - double z = e.z; - - double r = sqrt(x * x + y * y); - double Esq = a * a - b * b; - double F = 54 * b * b * z * z; - double G = r * r + (1 - esq) * z * z - esq * Esq; - double C = (esq * esq * F * r * r) / (pow(G, 3)); - double S = cbrt(1 + C + sqrt(C * C + 2 * C)); - double P = F / (3 * pow((S + 1 / S + 1), 2) * G * G); - double Q = sqrt(1 + 2 * esq * esq * P); - double r_0 = -(P * esq * r) / (1 + Q) + sqrt(0.5 * a * a*(1 + 1.0 / Q) - P * (1 - esq) * z * z / (Q * (1 + Q)) - 0.5 * P * r * r); - double U = sqrt(pow((r - esq * r_0), 2) + z * z); - double V = sqrt(pow((r - esq * r_0), 2) + (1 - esq) * z * z); - double Z_0 = b * b * z / (a * V); - double h = U * (1 - b * b / (a * V)); - - double lat = atan((z + e1sq * Z_0) / r); - double lon = atan2(y, x); - - return to_degrees({lat, lon, h}); -} - -LocalCoord::LocalCoord(const Geodetic &geodetic, const ECEF &e) { - init_ecef << e.x, e.y, e.z; - - auto g = to_radians(geodetic); - - ned2ecef_matrix << - -sin(g.lat)*cos(g.lon), -sin(g.lon), -cos(g.lat)*cos(g.lon), - -sin(g.lat)*sin(g.lon), cos(g.lon), -cos(g.lat)*sin(g.lon), - cos(g.lat), 0, -sin(g.lat); - ecef2ned_matrix = ned2ecef_matrix.transpose(); -} - -NED LocalCoord::ecef2ned(const ECEF &e) { - Eigen::Vector3d ecef; - ecef << e.x, e.y, e.z; - - Eigen::Vector3d ned = (ecef2ned_matrix * (ecef - init_ecef)); - return {ned[0], ned[1], ned[2]}; -} - -ECEF LocalCoord::ned2ecef(const NED &n) { - Eigen::Vector3d ned; - ned << n.n, n.e, n.d; - - Eigen::Vector3d ecef = (ned2ecef_matrix * ned) + init_ecef; - return {ecef[0], ecef[1], ecef[2]}; -} - -NED LocalCoord::geodetic2ned(const Geodetic &g) { - ECEF e = ::geodetic2ecef(g); - return ecef2ned(e); -} - -Geodetic LocalCoord::ned2geodetic(const NED &n) { - ECEF e = ned2ecef(n); - return ::ecef2geodetic(e); -} diff --git a/common/transformations/coordinates.hpp b/common/transformations/coordinates.hpp deleted file mode 100644 index dc8ff7a4b..000000000 --- a/common/transformations/coordinates.hpp +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - -#include - -#define DEG2RAD(x) ((x) * M_PI / 180.0) -#define RAD2DEG(x) ((x) * 180.0 / M_PI) - -struct ECEF { - double x, y, z; - Eigen::Vector3d to_vector() const { - return Eigen::Vector3d(x, y, z); - } -}; - -struct NED { - double n, e, d; - Eigen::Vector3d to_vector() const { - return Eigen::Vector3d(n, e, d); - } -}; - -struct Geodetic { - double lat, lon, alt; - bool radians=false; -}; - -ECEF geodetic2ecef(const Geodetic &g); -Geodetic ecef2geodetic(const ECEF &e); - -class LocalCoord { -public: - Eigen::Matrix3d ned2ecef_matrix; - Eigen::Matrix3d ecef2ned_matrix; - Eigen::Vector3d init_ecef; - LocalCoord(const Geodetic &g, const ECEF &e); - LocalCoord(const Geodetic &g) : LocalCoord(g, ::geodetic2ecef(g)) {} - LocalCoord(const ECEF &e) : LocalCoord(::ecef2geodetic(e), e) {} - - NED ecef2ned(const ECEF &e); - ECEF ned2ecef(const NED &n); - NED geodetic2ned(const Geodetic &g); - Geodetic ned2geodetic(const NED &n); -}; diff --git a/common/transformations/orientation.cc b/common/transformations/orientation.cc deleted file mode 100644 index fb5e47a86..000000000 --- a/common/transformations/orientation.cc +++ /dev/null @@ -1,144 +0,0 @@ -#define _USE_MATH_DEFINES - -#include -#include -#include - -#include "common/transformations/orientation.hpp" -#include "common/transformations/coordinates.hpp" - -Eigen::Quaterniond ensure_unique(const Eigen::Quaterniond &quat) { - if (quat.w() > 0){ - return quat; - } else { - return Eigen::Quaterniond(-quat.w(), -quat.x(), -quat.y(), -quat.z()); - } -} - -Eigen::Quaterniond euler2quat(const Eigen::Vector3d &euler) { - Eigen::Quaterniond q; - - q = Eigen::AngleAxisd(euler(2), Eigen::Vector3d::UnitZ()) - * Eigen::AngleAxisd(euler(1), Eigen::Vector3d::UnitY()) - * Eigen::AngleAxisd(euler(0), Eigen::Vector3d::UnitX()); - return ensure_unique(q); -} - - -Eigen::Vector3d quat2euler(const Eigen::Quaterniond &quat) { - // TODO: switch to eigen implementation if the range of the Euler angles doesn't matter anymore - // Eigen::Vector3d euler = quat.toRotationMatrix().eulerAngles(2, 1, 0); - // return {euler(2), euler(1), euler(0)}; - double gamma = atan2(2 * (quat.w() * quat.x() + quat.y() * quat.z()), 1 - 2 * (quat.x()*quat.x() + quat.y()*quat.y())); - double asin_arg_clipped = std::clamp(2 * (quat.w() * quat.y() - quat.z() * quat.x()), -1.0, 1.0); - double theta = asin(asin_arg_clipped); - double psi = atan2(2 * (quat.w() * quat.z() + quat.x() * quat.y()), 1 - 2 * (quat.y()*quat.y() + quat.z()*quat.z())); - return {gamma, theta, psi}; -} - -Eigen::Matrix3d quat2rot(const Eigen::Quaterniond &quat) { - return quat.toRotationMatrix(); -} - -Eigen::Quaterniond rot2quat(const Eigen::Matrix3d &rot) { - return ensure_unique(Eigen::Quaterniond(rot)); -} - -Eigen::Matrix3d euler2rot(const Eigen::Vector3d &euler) { - return quat2rot(euler2quat(euler)); -} - -Eigen::Vector3d rot2euler(const Eigen::Matrix3d &rot) { - return quat2euler(rot2quat(rot)); -} - -Eigen::Matrix3d rot_matrix(double roll, double pitch, double yaw) { - return euler2rot({roll, pitch, yaw}); -} - -Eigen::Matrix3d rot(const Eigen::Vector3d &axis, double angle) { - Eigen::Quaterniond q; - q = Eigen::AngleAxisd(angle, axis); - return q.toRotationMatrix(); -} - - -Eigen::Vector3d ecef_euler_from_ned(const ECEF &ecef_init, const Eigen::Vector3d &ned_pose) { - /* - Using Rotations to Build Aerospace Coordinate Systems - Don Koks - https://apps.dtic.mil/dtic/tr/fulltext/u2/a484864.pdf - */ - LocalCoord converter = LocalCoord(ecef_init); - Eigen::Vector3d zero = ecef_init.to_vector(); - - Eigen::Vector3d x0 = converter.ned2ecef({1, 0, 0}).to_vector() - zero; - Eigen::Vector3d y0 = converter.ned2ecef({0, 1, 0}).to_vector() - zero; - Eigen::Vector3d z0 = converter.ned2ecef({0, 0, 1}).to_vector() - zero; - - Eigen::Vector3d x1 = rot(z0, ned_pose(2)) * x0; - Eigen::Vector3d y1 = rot(z0, ned_pose(2)) * y0; - Eigen::Vector3d z1 = rot(z0, ned_pose(2)) * z0; - - Eigen::Vector3d x2 = rot(y1, ned_pose(1)) * x1; - Eigen::Vector3d y2 = rot(y1, ned_pose(1)) * y1; - Eigen::Vector3d z2 = rot(y1, ned_pose(1)) * z1; - - Eigen::Vector3d x3 = rot(x2, ned_pose(0)) * x2; - Eigen::Vector3d y3 = rot(x2, ned_pose(0)) * y2; - - - x0 = Eigen::Vector3d(1, 0, 0); - y0 = Eigen::Vector3d(0, 1, 0); - z0 = Eigen::Vector3d(0, 0, 1); - - double psi = atan2(x3.dot(y0), x3.dot(x0)); - double theta = atan2(-x3.dot(z0), sqrt(pow(x3.dot(x0), 2) + pow(x3.dot(y0), 2))); - - y2 = rot(z0, psi) * y0; - z2 = rot(y2, theta) * z0; - - double phi = atan2(y3.dot(z2), y3.dot(y2)); - - return {phi, theta, psi}; -} - -Eigen::Vector3d ned_euler_from_ecef(const ECEF &ecef_init, const Eigen::Vector3d &ecef_pose) { - /* - Using Rotations to Build Aerospace Coordinate Systems - Don Koks - https://apps.dtic.mil/dtic/tr/fulltext/u2/a484864.pdf - */ - LocalCoord converter = LocalCoord(ecef_init); - - Eigen::Vector3d x0 = Eigen::Vector3d(1, 0, 0); - Eigen::Vector3d y0 = Eigen::Vector3d(0, 1, 0); - Eigen::Vector3d z0 = Eigen::Vector3d(0, 0, 1); - - Eigen::Vector3d x1 = rot(z0, ecef_pose(2)) * x0; - Eigen::Vector3d y1 = rot(z0, ecef_pose(2)) * y0; - Eigen::Vector3d z1 = rot(z0, ecef_pose(2)) * z0; - - Eigen::Vector3d x2 = rot(y1, ecef_pose(1)) * x1; - Eigen::Vector3d y2 = rot(y1, ecef_pose(1)) * y1; - Eigen::Vector3d z2 = rot(y1, ecef_pose(1)) * z1; - - Eigen::Vector3d x3 = rot(x2, ecef_pose(0)) * x2; - Eigen::Vector3d y3 = rot(x2, ecef_pose(0)) * y2; - - Eigen::Vector3d zero = ecef_init.to_vector(); - x0 = converter.ned2ecef({1, 0, 0}).to_vector() - zero; - y0 = converter.ned2ecef({0, 1, 0}).to_vector() - zero; - z0 = converter.ned2ecef({0, 0, 1}).to_vector() - zero; - - double psi = atan2(x3.dot(y0), x3.dot(x0)); - double theta = atan2(-x3.dot(z0), sqrt(pow(x3.dot(x0), 2) + pow(x3.dot(y0), 2))); - - y2 = rot(z0, psi) * y0; - z2 = rot(y2, theta) * z0; - - double phi = atan2(y3.dot(z2), y3.dot(y2)); - - return {phi, theta, psi}; -} - diff --git a/common/transformations/orientation.hpp b/common/transformations/orientation.hpp deleted file mode 100644 index 0874a0a81..000000000 --- a/common/transformations/orientation.hpp +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once -#include -#include "common/transformations/coordinates.hpp" - - -Eigen::Quaterniond ensure_unique(const Eigen::Quaterniond &quat); - -Eigen::Quaterniond euler2quat(const Eigen::Vector3d &euler); -Eigen::Vector3d quat2euler(const Eigen::Quaterniond &quat); -Eigen::Matrix3d quat2rot(const Eigen::Quaterniond &quat); -Eigen::Quaterniond rot2quat(const Eigen::Matrix3d &rot); -Eigen::Matrix3d euler2rot(const Eigen::Vector3d &euler); -Eigen::Vector3d rot2euler(const Eigen::Matrix3d &rot); -Eigen::Matrix3d rot_matrix(double roll, double pitch, double yaw); -Eigen::Matrix3d rot(const Eigen::Vector3d &axis, double angle); -Eigen::Vector3d ecef_euler_from_ned(const ECEF &ecef_init, const Eigen::Vector3d &ned_pose); -Eigen::Vector3d ned_euler_from_ecef(const ECEF &ecef_init, const Eigen::Vector3d &ecef_pose); diff --git a/common/transformations/tests/test_coordinates.py b/common/transformations/tests/test_coordinates.py index 11a6bf70e..0b5d1c36d 100644 --- a/common/transformations/tests/test_coordinates.py +++ b/common/transformations/tests/test_coordinates.py @@ -102,3 +102,36 @@ class TestNED: np.testing.assert_allclose(converter.ned2ecef(ned_offsets_batch), ecef_positions_offset_batch, rtol=1e-9, atol=1e-7) + + def test_errors(self): + # Test wrong shape/type for geodetic2ecef + # numpy_wrap raises IndexError for scalar input + with np.testing.assert_raises(IndexError): + coord.geodetic2ecef(1.0) + + with np.testing.assert_raises_regex(ValueError, "Geodetic must be size 3"): + coord.geodetic2ecef([0, 0]) + + with np.testing.assert_raises_regex(ValueError, "Geodetic must be size 3"): + coord.geodetic2ecef([0, 0, 0, 0]) + + with np.testing.assert_raises(TypeError): + coord.geodetic2ecef(['a', 'b', 'c']) + + # Test LocalCoord constructor errors + with np.testing.assert_raises(ValueError): + coord.LocalCoord.from_geodetic([0, 0]) + + with np.testing.assert_raises(ValueError): + coord.LocalCoord.from_geodetic(1) + + with np.testing.assert_raises(TypeError): + coord.LocalCoord.from_geodetic(['a', 'b', 'c']) + + # Test wrong shape/type for ecef2geodetic + with np.testing.assert_raises(ValueError): + coord.ecef2geodetic([1, 2]) + with np.testing.assert_raises(ValueError): + coord.ecef2geodetic([1, 2, 3, 4]) + with np.testing.assert_raises(IndexError): + coord.ecef2geodetic(1.0) diff --git a/common/transformations/tests/test_orientation.py b/common/transformations/tests/test_orientation.py index 55fbc6581..1bf94115c 100644 --- a/common/transformations/tests/test_orientation.py +++ b/common/transformations/tests/test_orientation.py @@ -1,4 +1,5 @@ import numpy as np +import pytest from openpilot.common.transformations.orientation import euler2quat, quat2euler, euler2rot, rot2euler, \ rot2quat, quat2rot, \ @@ -59,3 +60,32 @@ class TestOrientation: np.testing.assert_allclose(ned_eulers[i], ned_euler_from_ecef(ecef_positions[i], eulers[i]), rtol=1e-7) #np.testing.assert_allclose(eulers[i], ecef_euler_from_ned(ecef_positions[i], ned_eulers[i]), rtol=1e-7) # np.testing.assert_allclose(ned_eulers, ned_euler_from_ecef(ecef_positions, eulers), rtol=1e-7) + + def test_inputs(self): + with pytest.raises(ValueError): + euler2quat([1, 2]) + + with pytest.raises(ValueError): + quat2rot([1, 2, 3]) + + with pytest.raises(IndexError): + rot2quat(np.zeros((2, 2))) + + def test_euler_rot_consistency(self): + rpy = [0.1, 0.2, 0.3] + R = euler2rot(rpy) + + # R -> q -> R + q = rot2quat(R) + R_new = quat2rot(q) + np.testing.assert_allclose(R, R_new, atol=1e-15) + + # q -> R -> Euler (quat2euler) -> R + rpy_new = quat2euler(q) + R_new2 = euler2rot(rpy_new) + np.testing.assert_allclose(R, R_new2, atol=1e-15) + + # R -> Euler (rot2euler) -> R + rpy_from_rot = rot2euler(R) + R_new3 = euler2rot(rpy_from_rot) + np.testing.assert_allclose(R, R_new3, atol=1e-15) diff --git a/common/transformations/transformations.pxd b/common/transformations/transformations.pxd deleted file mode 100644 index fe32e18de..000000000 --- a/common/transformations/transformations.pxd +++ /dev/null @@ -1,72 +0,0 @@ -# cython: language_level=3 -from libcpp cimport bool - -cdef extern from "orientation.cc": - pass - -cdef extern from "orientation.hpp": - cdef cppclass Quaternion "Eigen::Quaterniond": - Quaternion() - Quaternion(double, double, double, double) - double w() - double x() - double y() - double z() - - cdef cppclass Vector3 "Eigen::Vector3d": - Vector3() - Vector3(double, double, double) - double operator()(int) - - cdef cppclass Matrix3 "Eigen::Matrix3d": - Matrix3() - Matrix3(double*) - - double operator()(int, int) - - Quaternion euler2quat(const Vector3 &) - Vector3 quat2euler(const Quaternion &) - Matrix3 quat2rot(const Quaternion &) - Quaternion rot2quat(const Matrix3 &) - Vector3 rot2euler(const Matrix3 &) - Matrix3 euler2rot(const Vector3 &) - Matrix3 rot_matrix(double, double, double) - Vector3 ecef_euler_from_ned(const ECEF &, const Vector3 &) - Vector3 ned_euler_from_ecef(const ECEF &, const Vector3 &) - - -cdef extern from "coordinates.cc": - cdef struct ECEF: - double x - double y - double z - - cdef struct NED: - double n - double e - double d - - cdef struct Geodetic: - double lat - double lon - double alt - bool radians - - ECEF geodetic2ecef(const Geodetic &) - Geodetic ecef2geodetic(const ECEF &) - - cdef cppclass LocalCoord_c "LocalCoord": - Matrix3 ned2ecef_matrix - Matrix3 ecef2ned_matrix - - LocalCoord_c(const Geodetic &, const ECEF &) - LocalCoord_c(const Geodetic &) - LocalCoord_c(const ECEF &) - - NED ecef2ned(const ECEF &) - ECEF ned2ecef(const NED &) - NED geodetic2ned(const Geodetic &) - Geodetic ned2geodetic(const NED &) - -cdef extern from "coordinates.hpp": - pass diff --git a/common/transformations/transformations.py b/common/transformations/transformations.py new file mode 100644 index 000000000..5cb6220f9 --- /dev/null +++ b/common/transformations/transformations.py @@ -0,0 +1,342 @@ +import numpy as np + + +# Constants +a = 6378137.0 +b = 6356752.3142 +esq = 6.69437999014e-3 +e1sq = 6.73949674228e-3 + + +def geodetic2ecef_single(g): + """ + Convert geodetic coordinates (latitude, longitude, altitude) to ECEF. + """ + try: + if len(g) != 3: + raise ValueError("Geodetic must be size 3") + except TypeError: + raise ValueError("Geodetic must be a sequence of length 3") from None + + lat, lon, alt = g + lat = np.radians(lat) + lon = np.radians(lon) + xi = np.sqrt(1.0 - esq * np.sin(lat)**2) + x = (a / xi + alt) * np.cos(lat) * np.cos(lon) + y = (a / xi + alt) * np.cos(lat) * np.sin(lon) + z = (a / xi * (1.0 - esq) + alt) * np.sin(lat) + return np.array([x, y, z]) + + +def ecef2geodetic_single(e): + """ + Convert ECEF to geodetic coordinates using Ferrari's solution. + """ + x, y, z = e + r = np.sqrt(x**2 + y**2) + Esq = a**2 - b**2 + F = 54 * b**2 * z**2 + G = r**2 + (1 - esq) * z**2 - esq * Esq + C = (esq**2 * F * r**2) / (G**3) + S = np.cbrt(1 + C + np.sqrt(C**2 + 2 * C)) + P = F / (3 * (S + 1 / S + 1)**2 * G**2) + Q = np.sqrt(1 + 2 * esq**2 * P) + r_0 = -(P * esq * r) / (1 + Q) + np.sqrt(0.5 * a**2 * (1 + 1.0 / Q) - P * (1 - esq) * z**2 / (Q * (1 + Q)) - 0.5 * P * r**2) + U = np.sqrt((r - esq * r_0)**2 + z**2) + V = np.sqrt((r - esq * r_0)**2 + (1 - esq) * z**2) + Z_0 = b**2 * z / (a * V) + h = U * (1 - b**2 / (a * V)) + lat = np.arctan((z + e1sq * Z_0) / r) + lon = np.arctan2(y, x) + return np.array([np.degrees(lat), np.degrees(lon), h]) + + +def euler2quat_single(euler): + """ + Convert Euler angles (roll, pitch, yaw) to a quaternion. + Rotation order: Z-Y-X (yaw, pitch, roll). + """ + phi, theta, psi = euler + + c_phi, s_phi = np.cos(phi / 2), np.sin(phi / 2) + c_theta, s_theta = np.cos(theta / 2), np.sin(theta / 2) + c_psi, s_psi = np.cos(psi / 2), np.sin(psi / 2) + + w = c_phi * c_theta * c_psi + s_phi * s_theta * s_psi + x = s_phi * c_theta * c_psi - c_phi * s_theta * s_psi + y = c_phi * s_theta * c_psi + s_phi * c_theta * s_psi + z = c_phi * c_theta * s_psi - s_phi * s_theta * c_psi + + if w < 0: + return np.array([-w, -x, -y, -z]) + return np.array([w, x, y, z]) + + +def quat2euler_single(q): + """ + Convert a quaternion to Euler angles (roll, pitch, yaw). + """ + w, x, y, z = q + gamma = np.arctan2(2 * (w * x + y * z), 1 - 2 * (x**2 + y**2)) + sin_arg = 2 * (w * y - z * x) + sin_arg = np.clip(sin_arg, -1.0, 1.0) + theta = np.arcsin(sin_arg) + psi = np.arctan2(2 * (w * z + x * y), 1 - 2 * (y**2 + z**2)) + return np.array([gamma, theta, psi]) + + +def quat2rot_single(q): + """ + Convert a quaternion to a 3x3 rotation matrix. + """ + w, x, y, z = q + xx, yy, zz = x * x, y * y, z * z + xy, xz, yz = x * y, x * z, y * z + wx, wy, wz = w * x, w * y, w * z + + mat = np.array([ + [1 - 2 * (yy + zz), 2 * (xy - wz), 2 * (xz + wy)], + [2 * (xy + wz), 1 - 2 * (xx + zz), 2 * (yz - wx)], + [2 * (xz - wy), 2 * (yz + wx), 1 - 2 * (xx + yy)] + ]) + return mat + + +def rot2quat_single(rot): + """ + Convert a 3x3 rotation matrix to a quaternion. + """ + trace = np.trace(rot) + if trace > 0: + s = 0.5 / np.sqrt(trace + 1.0) + w = 0.25 / s + x = (rot[2, 1] - rot[1, 2]) * s + y = (rot[0, 2] - rot[2, 0]) * s + z = (rot[1, 0] - rot[0, 1]) * s + else: + if rot[0, 0] > rot[1, 1] and rot[0, 0] > rot[2, 2]: + s = 2.0 * np.sqrt(1.0 + rot[0, 0] - rot[1, 1] - rot[2, 2]) + w = (rot[2, 1] - rot[1, 2]) / s + x = 0.25 * s + y = (rot[0, 1] + rot[1, 0]) / s + z = (rot[0, 2] + rot[2, 0]) / s + elif rot[1, 1] > rot[2, 2]: + s = 2.0 * np.sqrt(1.0 + rot[1, 1] - rot[0, 0] - rot[2, 2]) + w = (rot[0, 2] - rot[2, 0]) / s + x = (rot[0, 1] + rot[1, 0]) / s + y = 0.25 * s + z = (rot[1, 2] + rot[2, 1]) / s + else: + s = 2.0 * np.sqrt(1.0 + rot[2, 2] - rot[0, 0] - rot[1, 1]) + w = (rot[1, 0] - rot[0, 1]) / s + x = (rot[0, 2] + rot[2, 0]) / s + y = (rot[1, 2] + rot[2, 1]) / s + z = 0.25 * s + + if w < 0: + return np.array([-w, -x, -y, -z]) + return np.array([w, x, y, z]) + + +def euler2rot_single(euler): + """ + Convert Euler angles (roll, pitch, yaw) to a 3x3 rotation matrix. + Rotation order: Z-Y-X (yaw, pitch, roll). + """ + phi, theta, psi = euler + + cx, sx = np.cos(phi), np.sin(phi) + cy, sy = np.cos(theta), np.sin(theta) + cz, sz = np.cos(psi), np.sin(psi) + + Rx = np.array([[1, 0, 0], [0, cx, -sx], [0, sx, cx]]) + Ry = np.array([[cy, 0, sy], [0, 1, 0], [-sy, 0, cy]]) + Rz = np.array([[cz, -sz, 0], [sz, cz, 0], [0, 0, 1]]) + + return Rz @ Ry @ Rx + + +def rot2euler_single(rot): + """ + Convert a 3x3 rotation matrix to Euler angles (roll, pitch, yaw). + """ + return quat2euler_single(rot2quat_single(rot)) + + +def rot_matrix(roll, pitch, yaw): + """ + Create a 3x3 rotation matrix from roll, pitch, and yaw angles. + """ + return euler2rot_single([roll, pitch, yaw]) + + +def axis_angle_to_rot(axis, angle): + """ + Convert an axis-angle representation to a 3x3 rotation matrix. + """ + c = np.cos(angle / 2) + s = np.sin(angle / 2) + q = np.array([c, s*axis[0], s*axis[1], s*axis[2]]) + return quat2rot_single(q) + + +class LocalCoord: + """ + A class to handle conversions between ECEF and local NED coordinates. + """ + def __init__(self, geodetic=None, ecef=None): + """ + Initialize LocalCoord with either geodetic or ECEF coordinates. + """ + if geodetic is not None: + self.init_ecef = geodetic2ecef_single(geodetic) + lat, lon, _ = geodetic + elif ecef is not None: + self.init_ecef = np.array(ecef) + lat, lon, _ = ecef2geodetic_single(ecef) + else: + raise ValueError("Must provide geodetic or ecef") + + lat = np.radians(lat) + lon = np.radians(lon) + + self.ned2ecef_matrix = np.array([ + [-np.sin(lat) * np.cos(lon), -np.sin(lon), -np.cos(lat) * np.cos(lon)], + [-np.sin(lat) * np.sin(lon), np.cos(lon), -np.cos(lat) * np.sin(lon)], + [np.cos(lat), 0, -np.sin(lat)] + ]) + self.ecef2ned_matrix = self.ned2ecef_matrix.T + + @classmethod + def from_geodetic(cls, geodetic): + """ + Create a LocalCoord instance from geodetic coordinates. + """ + return cls(geodetic=geodetic) + + @classmethod + def from_ecef(cls, ecef): + """ + Create a LocalCoord instance from ECEF coordinates. + """ + return cls(ecef=ecef) + + def ecef2ned_single(self, ecef): + """ + Convert a single ECEF point to NED coordinates relative to the origin. + """ + return self.ecef2ned_matrix @ (ecef - self.init_ecef) + + def ned2ecef_single(self, ned): + """ + Convert a single NED point to ECEF coordinates. + """ + return self.ned2ecef_matrix @ ned + self.init_ecef + + def geodetic2ned_single(self, geodetic): + """ + Convert a single geodetic point to NED coordinates. + """ + ecef = geodetic2ecef_single(geodetic) + return self.ecef2ned_single(ecef) + + def ned2geodetic_single(self, ned): + """ + Convert a single NED point to geodetic coordinates. + """ + ecef = self.ned2ecef_single(ned) + return ecef2geodetic_single(ecef) + + @property + def ned_from_ecef_matrix(self): + """ + Returns the rotation matrix from ECEF to NED coordinates. + """ + return self.ecef2ned_matrix + + @property + def ecef_from_ned_matrix(self): + """ + Returns the rotation matrix from NED to ECEF coordinates. + """ + return self.ned2ecef_matrix + + +def ecef_euler_from_ned_single(ecef_init, ned_pose): + """ + Convert NED Euler angles (roll, pitch, yaw) at a given ECEF origin + to equivalent ECEF Euler angles. + """ + converter = LocalCoord(ecef=ecef_init) + zero = np.array(ecef_init) + + x0 = converter.ned2ecef_single([1, 0, 0]) - zero + y0 = converter.ned2ecef_single([0, 1, 0]) - zero + z0 = converter.ned2ecef_single([0, 0, 1]) - zero + + phi, theta, psi = ned_pose + + x1 = axis_angle_to_rot(z0, psi) @ x0 + y1 = axis_angle_to_rot(z0, psi) @ y0 + z1 = axis_angle_to_rot(z0, psi) @ z0 + + x2 = axis_angle_to_rot(y1, theta) @ x1 + y2 = axis_angle_to_rot(y1, theta) @ y1 + z2 = axis_angle_to_rot(y1, theta) @ z1 + + x3 = axis_angle_to_rot(x2, phi) @ x2 + y3 = axis_angle_to_rot(x2, phi) @ y2 + + x0 = np.array([1.0, 0, 0]) + y0 = np.array([0, 1.0, 0]) + z0 = np.array([0, 0, 1.0]) + + psi_out = np.arctan2(np.dot(x3, y0), np.dot(x3, x0)) + theta_out = np.arctan2(-np.dot(x3, z0), np.sqrt(np.dot(x3, x0)**2 + np.dot(x3, y0)**2)) + + y2 = axis_angle_to_rot(z0, psi_out) @ y0 + z2 = axis_angle_to_rot(y2, theta_out) @ z0 + + phi_out = np.arctan2(np.dot(y3, z2), np.dot(y3, y2)) + + return np.array([phi_out, theta_out, psi_out]) + + +def ned_euler_from_ecef_single(ecef_init, ecef_pose): + """ + Convert ECEF Euler angles (roll, pitch, yaw) at a given ECEF origin + to equivalent NED Euler angles. + """ + converter = LocalCoord(ecef=ecef_init) + + x0 = np.array([1.0, 0, 0]) + y0 = np.array([0, 1.0, 0]) + z0 = np.array([0, 0, 1.0]) + + phi, theta, psi = ecef_pose + + x1 = axis_angle_to_rot(z0, psi) @ x0 + y1 = axis_angle_to_rot(z0, psi) @ y0 + z1 = axis_angle_to_rot(z0, psi) @ z0 + + x2 = axis_angle_to_rot(y1, theta) @ x1 + y2 = axis_angle_to_rot(y1, theta) @ y1 + z2 = axis_angle_to_rot(y1, theta) @ z1 + + x3 = axis_angle_to_rot(x2, phi) @ x2 + y3 = axis_angle_to_rot(x2, phi) @ y2 + + zero = np.array(ecef_init) + x0 = converter.ned2ecef_single([1, 0, 0]) - zero + y0 = converter.ned2ecef_single([0, 1, 0]) - zero + z0 = converter.ned2ecef_single([0, 0, 1]) - zero + + psi_out = np.arctan2(np.dot(x3, y0), np.dot(x3, x0)) + theta_out = np.arctan2(-np.dot(x3, z0), np.sqrt(np.dot(x3, x0)**2 + np.dot(x3, y0)**2)) + + y2 = axis_angle_to_rot(z0, psi_out) @ y0 + z2 = axis_angle_to_rot(y2, theta_out) @ z0 + + phi_out = np.arctan2(np.dot(y3, z2), np.dot(y3, y2)) + + return np.array([phi_out, theta_out, psi_out]) diff --git a/common/transformations/transformations.pyx b/common/transformations/transformations.pyx deleted file mode 100644 index ae045c369..000000000 --- a/common/transformations/transformations.pyx +++ /dev/null @@ -1,173 +0,0 @@ -# distutils: language = c++ -# cython: language_level = 3 -from openpilot.common.transformations.transformations cimport Matrix3, Vector3, Quaternion -from openpilot.common.transformations.transformations cimport ECEF, NED, Geodetic - -from openpilot.common.transformations.transformations cimport euler2quat as euler2quat_c -from openpilot.common.transformations.transformations cimport quat2euler as quat2euler_c -from openpilot.common.transformations.transformations cimport quat2rot as quat2rot_c -from openpilot.common.transformations.transformations cimport rot2quat as rot2quat_c -from openpilot.common.transformations.transformations cimport euler2rot as euler2rot_c -from openpilot.common.transformations.transformations cimport rot2euler as rot2euler_c -from openpilot.common.transformations.transformations cimport rot_matrix as rot_matrix_c -from openpilot.common.transformations.transformations cimport ecef_euler_from_ned as ecef_euler_from_ned_c -from openpilot.common.transformations.transformations cimport ned_euler_from_ecef as ned_euler_from_ecef_c -from openpilot.common.transformations.transformations cimport geodetic2ecef as geodetic2ecef_c -from openpilot.common.transformations.transformations cimport ecef2geodetic as ecef2geodetic_c -from openpilot.common.transformations.transformations cimport LocalCoord_c - - -import numpy as np -cimport numpy as np - -cdef np.ndarray[double, ndim=2] matrix2numpy(Matrix3 m): - return np.array([ - [m(0, 0), m(0, 1), m(0, 2)], - [m(1, 0), m(1, 1), m(1, 2)], - [m(2, 0), m(2, 1), m(2, 2)], - ]) - -cdef Matrix3 numpy2matrix(np.ndarray[double, ndim=2, mode="fortran"] m): - assert m.shape[0] == 3 - assert m.shape[1] == 3 - return Matrix3(m.data) - -cdef ECEF list2ecef(ecef): - cdef ECEF e - e.x = ecef[0] - e.y = ecef[1] - e.z = ecef[2] - return e - -cdef NED list2ned(ned): - cdef NED n - n.n = ned[0] - n.e = ned[1] - n.d = ned[2] - return n - -cdef Geodetic list2geodetic(geodetic): - cdef Geodetic g - g.lat = geodetic[0] - g.lon = geodetic[1] - g.alt = geodetic[2] - return g - -def euler2quat_single(euler): - cdef Vector3 e = Vector3(euler[0], euler[1], euler[2]) - cdef Quaternion q = euler2quat_c(e) - return [q.w(), q.x(), q.y(), q.z()] - -def quat2euler_single(quat): - cdef Quaternion q = Quaternion(quat[0], quat[1], quat[2], quat[3]) - cdef Vector3 e = quat2euler_c(q) - return [e(0), e(1), e(2)] - -def quat2rot_single(quat): - cdef Quaternion q = Quaternion(quat[0], quat[1], quat[2], quat[3]) - cdef Matrix3 r = quat2rot_c(q) - return matrix2numpy(r) - -def rot2quat_single(rot): - cdef Matrix3 r = numpy2matrix(np.asfortranarray(rot, dtype=np.double)) - cdef Quaternion q = rot2quat_c(r) - return [q.w(), q.x(), q.y(), q.z()] - -def euler2rot_single(euler): - cdef Vector3 e = Vector3(euler[0], euler[1], euler[2]) - cdef Matrix3 r = euler2rot_c(e) - return matrix2numpy(r) - -def rot2euler_single(rot): - cdef Matrix3 r = numpy2matrix(np.asfortranarray(rot, dtype=np.double)) - cdef Vector3 e = rot2euler_c(r) - return [e(0), e(1), e(2)] - -def rot_matrix(roll, pitch, yaw): - return matrix2numpy(rot_matrix_c(roll, pitch, yaw)) - -def ecef_euler_from_ned_single(ecef_init, ned_pose): - cdef ECEF init = list2ecef(ecef_init) - cdef Vector3 pose = Vector3(ned_pose[0], ned_pose[1], ned_pose[2]) - - cdef Vector3 e = ecef_euler_from_ned_c(init, pose) - return [e(0), e(1), e(2)] - -def ned_euler_from_ecef_single(ecef_init, ecef_pose): - cdef ECEF init = list2ecef(ecef_init) - cdef Vector3 pose = Vector3(ecef_pose[0], ecef_pose[1], ecef_pose[2]) - - cdef Vector3 e = ned_euler_from_ecef_c(init, pose) - return [e(0), e(1), e(2)] - -def geodetic2ecef_single(geodetic): - cdef Geodetic g = list2geodetic(geodetic) - cdef ECEF e = geodetic2ecef_c(g) - return [e.x, e.y, e.z] - -def ecef2geodetic_single(ecef): - cdef ECEF e = list2ecef(ecef) - cdef Geodetic g = ecef2geodetic_c(e) - return [g.lat, g.lon, g.alt] - - -cdef class LocalCoord: - cdef LocalCoord_c * lc - - def __init__(self, geodetic=None, ecef=None): - assert (geodetic is not None) or (ecef is not None) - if geodetic is not None: - self.lc = new LocalCoord_c(list2geodetic(geodetic)) - elif ecef is not None: - self.lc = new LocalCoord_c(list2ecef(ecef)) - - @property - def ned2ecef_matrix(self): - return matrix2numpy(self.lc.ned2ecef_matrix) - - @property - def ecef2ned_matrix(self): - return matrix2numpy(self.lc.ecef2ned_matrix) - - @property - def ned_from_ecef_matrix(self): - return self.ecef2ned_matrix - - @property - def ecef_from_ned_matrix(self): - return self.ned2ecef_matrix - - @classmethod - def from_geodetic(cls, geodetic): - return cls(geodetic=geodetic) - - @classmethod - def from_ecef(cls, ecef): - return cls(ecef=ecef) - - def ecef2ned_single(self, ecef): - assert self.lc - cdef ECEF e = list2ecef(ecef) - cdef NED n = self.lc.ecef2ned(e) - return [n.n, n.e, n.d] - - def ned2ecef_single(self, ned): - assert self.lc - cdef NED n = list2ned(ned) - cdef ECEF e = self.lc.ned2ecef(n) - return [e.x, e.y, e.z] - - def geodetic2ned_single(self, geodetic): - assert self.lc - cdef Geodetic g = list2geodetic(geodetic) - cdef NED n = self.lc.geodetic2ned(g) - return [n.n, n.e, n.d] - - def ned2geodetic_single(self, ned): - assert self.lc - cdef NED n = list2ned(ned) - cdef Geodetic g = self.lc.ned2geodetic(n) - return [g.lat, g.lon, g.alt] - - def __dealloc__(self): - del self.lc diff --git a/selfdrive/modeld/SConscript b/selfdrive/modeld/SConscript index 8b33a457f..a184b6a23 100644 --- a/selfdrive/modeld/SConscript +++ b/selfdrive/modeld/SConscript @@ -1,7 +1,7 @@ import os import glob -Import('env', 'envCython', 'arch', 'cereal', 'messaging', 'common', 'visionipc', 'transformations') +Import('env', 'envCython', 'arch', 'cereal', 'messaging', 'common', 'visionipc') lenv = env.Clone() lenvCython = envCython.Clone() From 49b6ef7f48c660b4913cc5dddd6fa7c38a56226a Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sat, 17 Jan 2026 14:36:19 -0500 Subject: [PATCH 743/910] SL: Fix MaxTimeOffroad metadata unit from seconds to minutes (#1650) * Initial plan * Fix MaxTimeOffroad metadata unit from seconds to minutes Co-authored-by: devtekve <7696966+devtekve@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: devtekve <7696966+devtekve@users.noreply.github.com> --- sunnypilot/sunnylink/params_metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sunnypilot/sunnylink/params_metadata.json b/sunnypilot/sunnylink/params_metadata.json index e484787b6..1fc3f991b 100644 --- a/sunnypilot/sunnylink/params_metadata.json +++ b/sunnypilot/sunnylink/params_metadata.json @@ -634,7 +634,7 @@ "MaxTimeOffroad": { "title": "Max Time Offroad", "description": "", - "unit": "seconds" + "unit": "minutes" }, "ModelManager_ActiveBundle": { "title": "Model Manager Active Bundle", From 3662a8e96250b669d0334f41deb2b23d057f0804 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 19 Jan 2026 01:26:16 -0500 Subject: [PATCH 744/910] ui: Customizable Onroad Brightness (#1641) * ui: Customizable Onroad Brightness * fixes * lint * reset on show/hide * reset on show/hide for mici * only set if true * wrong var * try this out * use clear * starts cleanup * wake for all visual alerts and handle timeouts * fixup: wake for all visual alerts and handle timeouts * handle always wake if there's an event properly * some * slightly more * need this back * Reapply "ui: Global Brightness Override (#1579)" This reverts commit a0c10be1ffd9d3d73409aa826b111713be1a85bf. * do not touch light sensor logic * override properly and clip to 30% minimum * wrap * lint * update immediately * read * max global brightness * rename * gotta do it for mici too lol * revert * Revert "revert" This reverts commit 121a082de11960ffa40b9f1414fe46fa54023507. * no more * ui * more * intenum * simplify ONROAD_BRIGHTNESS_TIMER_VALUES * no more toggle * 15 seconds countdown for auto dark regardless * auto dark refinement * only consume if expired * immediately set * rename * update sl metadata * no more --------- Co-authored-by: nayan Co-authored-by: DevTekVE --- common/params_keys.h | 1 - selfdrive/ui/mici/onroad/alert_renderer.py | 3 + .../ui/mici/onroad/augmented_road_view.py | 11 +++ selfdrive/ui/onroad/alert_renderer.py | 4 + selfdrive/ui/onroad/augmented_road_view.py | 14 ++- .../ui/sunnypilot/layouts/settings/display.py | 60 +++++++++++- selfdrive/ui/sunnypilot/ui_state.py | 88 +++++++++++++++++ selfdrive/ui/ui_state.py | 13 ++- sunnypilot/sunnylink/params_metadata.json | 97 ++++++++++++++++++- 9 files changed, 279 insertions(+), 12 deletions(-) diff --git a/common/params_keys.h b/common/params_keys.h index a559d411e..e04211392 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -168,7 +168,6 @@ inline static std::unordered_map keys = { {"OffroadMode", {CLEAR_ON_MANAGER_START, BOOL}}, {"Offroad_TiciSupport", {CLEAR_ON_MANAGER_START, JSON}}, {"OnroadScreenOffBrightness", {PERSISTENT | BACKUP, INT, "0"}}, - {"OnroadScreenOffControl", {PERSISTENT | BACKUP, BOOL}}, {"OnroadScreenOffTimer", {PERSISTENT | BACKUP, INT, "15"}}, {"OnroadUploads", {PERSISTENT | BACKUP, BOOL, "1"}}, {"QuickBootToggle", {PERSISTENT | BACKUP, BOOL, "0"}}, diff --git a/selfdrive/ui/mici/onroad/alert_renderer.py b/selfdrive/ui/mici/onroad/alert_renderer.py index b5f796bb0..5c93e5e42 100644 --- a/selfdrive/ui/mici/onroad/alert_renderer.py +++ b/selfdrive/ui/mici/onroad/alert_renderer.py @@ -222,6 +222,9 @@ class AlertRenderer(Widget): self._alert_y_filter.update(self._rect.y - 50 if alert is None else self._rect.y) self._alpha_filter.update(0 if alert is None else 1) + if gui_app.sunnypilot_ui(): + ui_state.onroad_brightness_handle_alerts(ui_state.started, alert) + if alert is None: # If still animating out, keep the previous alert if self._alpha_filter.x > 0.01 and self._prev_alert is not None: diff --git a/selfdrive/ui/mici/onroad/augmented_road_view.py b/selfdrive/ui/mici/onroad/augmented_road_view.py index 5fe33a7e4..0546920e5 100644 --- a/selfdrive/ui/mici/onroad/augmented_road_view.py +++ b/selfdrive/ui/mici/onroad/augmented_road_view.py @@ -19,6 +19,9 @@ from openpilot.common.transformations.camera import DEVICE_CAMERAS, DeviceCamera from openpilot.common.transformations.orientation import rot_from_euler from enum import IntEnum +if gui_app.sunnypilot_ui(): + from openpilot.selfdrive.ui.sunnypilot.ui_state import OnroadTimerStatus + OpState = log.SelfdriveState.OpenpilotState CALIBRATED = log.LiveCalibrationData.Status.calibrated ROAD_CAM = VisionStreamType.VISION_STREAM_ROAD @@ -351,6 +354,14 @@ class AugmentedRoadView(CameraView): return self._cached_matrix + def show_event(self): + if gui_app.sunnypilot_ui(): + ui_state.reset_onroad_sleep_timer(OnroadTimerStatus.RESUME) + + def hide_event(self): + if gui_app.sunnypilot_ui(): + ui_state.reset_onroad_sleep_timer(OnroadTimerStatus.PAUSE) + if __name__ == "__main__": gui_app.init_window("OnRoad Camera View") diff --git a/selfdrive/ui/onroad/alert_renderer.py b/selfdrive/ui/onroad/alert_renderer.py index 1e5f99cc6..2c21b4006 100644 --- a/selfdrive/ui/onroad/alert_renderer.py +++ b/selfdrive/ui/onroad/alert_renderer.py @@ -116,6 +116,10 @@ class AlertRenderer(Widget): def _render(self, rect: rl.Rectangle): alert = self.get_alert(ui_state.sm) + + if gui_app.sunnypilot_ui(): + ui_state.onroad_brightness_handle_alerts(ui_state.started, alert) + if not alert: return diff --git a/selfdrive/ui/onroad/augmented_road_view.py b/selfdrive/ui/onroad/augmented_road_view.py index a1d10193a..bcbcb2dcf 100644 --- a/selfdrive/ui/onroad/augmented_road_view.py +++ b/selfdrive/ui/onroad/augmented_road_view.py @@ -15,10 +15,10 @@ from openpilot.common.transformations.camera import DEVICE_CAMERAS, DeviceCamera from openpilot.common.transformations.orientation import rot_from_euler if gui_app.sunnypilot_ui(): - from openpilot.selfdrive.ui.sunnypilot.onroad.hud_renderer import HudRendererSP as HudRenderer + from openpilot.selfdrive.ui.sunnypilot.onroad.augmented_road_view import BORDER_COLORS_SP from openpilot.selfdrive.ui.sunnypilot.onroad.driver_state import DriverStateRendererSP as DriverStateRenderer - -from openpilot.selfdrive.ui.sunnypilot.onroad.augmented_road_view import BORDER_COLORS_SP + from openpilot.selfdrive.ui.sunnypilot.onroad.hud_renderer import HudRendererSP as HudRenderer + from openpilot.selfdrive.ui.sunnypilot.ui_state import OnroadTimerStatus OpState = log.SelfdriveState.OpenpilotState CALIBRATED = log.LiveCalibrationData.Status.calibrated @@ -224,6 +224,14 @@ class AugmentedRoadView(CameraView): return self._cached_matrix + def show_event(self): + if gui_app.sunnypilot_ui(): + ui_state.reset_onroad_sleep_timer(OnroadTimerStatus.RESUME) + + def hide_event(self): + if gui_app.sunnypilot_ui(): + ui_state.reset_onroad_sleep_timer(OnroadTimerStatus.PAUSE) + if __name__ == "__main__": gui_app.init_window("OnRoad Camera View") diff --git a/selfdrive/ui/sunnypilot/layouts/settings/display.py b/selfdrive/ui/sunnypilot/layouts/settings/display.py index d1d0a661b..abbfbe7c9 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/display.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/display.py @@ -4,9 +4,21 @@ Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. This file is part of sunnypilot and is licensed under the MIT License. See the LICENSE.md file in the root directory for more details. """ +from enum import IntEnum + from openpilot.common.params import Params -from openpilot.system.ui.widgets.scroller_tici import Scroller +from openpilot.system.ui.sunnypilot.widgets.option_control import OptionControlSP from openpilot.system.ui.widgets import Widget +from openpilot.system.ui.lib.multilang import tr +from openpilot.system.ui.widgets.scroller_tici import Scroller +from openpilot.system.ui.sunnypilot.widgets.list_view import option_item_sp, ToggleActionSP + +ONROAD_BRIGHTNESS_TIMER_VALUES = {0: 15, 1: 30, **{i: (i - 1) * 60 for i in range(2, 12)}} + + +class OnroadBrightness(IntEnum): + AUTO = 0 + AUTO_DARK = 1 class DisplayLayout(Widget): @@ -18,11 +30,55 @@ class DisplayLayout(Widget): self._scroller = Scroller(items, line_separator=True, spacing=0) def _initialize_items(self): + self._onroad_brightness = option_item_sp( + param="OnroadScreenOffBrightness", + title=lambda: tr("Onroad Brightness"), + description="", + min_value=0, + max_value=21, + value_change_step=1, + label_callback=lambda value: self.update_onroad_brightness(value), + inline=True + ) + self._onroad_brightness_timer = option_item_sp( + param="OnroadScreenOffTimer", + title=lambda: tr("Onroad Brightness Delay"), + description="", + min_value=0, + max_value=11, + value_change_step=1, + value_map=ONROAD_BRIGHTNESS_TIMER_VALUES, + label_callback=lambda value: f"{value} s" if value < 60 else f"{int(value/60)} m", + inline=True + ) items = [ - + self._onroad_brightness, + self._onroad_brightness_timer, ] return items + @staticmethod + def update_onroad_brightness(val): + if val == OnroadBrightness.AUTO: + return tr("Auto (Default)") + + if val == OnroadBrightness.AUTO_DARK: + return tr("Auto (Dark)") + + return f"{(val - 1) * 5} %" + + def _update_state(self): + super()._update_state() + + for _item in self._scroller._items: + if isinstance(_item.action_item, ToggleActionSP) and _item.action_item.toggle.param_key is not None: + _item.action_item.set_state(self._params.get_bool(_item.action_item.toggle.param_key)) + elif isinstance(_item.action_item, OptionControlSP) and _item.action_item.param_key is not None: + _item.action_item.set_value(self._params.get(_item.action_item.param_key, return_default=True)) + + brightness_val = self._params.get("OnroadScreenOffBrightness", return_default=True) + self._onroad_brightness_timer.action_item.set_enabled(brightness_val not in (OnroadBrightness.AUTO, OnroadBrightness.AUTO_DARK)) + def _render(self, rect): self._scroller.render(rect) diff --git a/selfdrive/ui/sunnypilot/ui_state.py b/selfdrive/ui/sunnypilot/ui_state.py index fe7124e5c..2644c157a 100644 --- a/selfdrive/ui/sunnypilot/ui_state.py +++ b/selfdrive/ui/sunnypilot/ui_state.py @@ -4,13 +4,25 @@ Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. This file is part of sunnypilot and is licensed under the MIT License. See the LICENSE.md file in the root directory for more details. """ +from enum import Enum + from cereal import messaging, log, custom from openpilot.common.params import Params +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.display import OnroadBrightness from openpilot.sunnypilot.sunnylink.sunnylink_state import SunnylinkState +from openpilot.system.ui.lib.application import gui_app OpenpilotState = log.SelfdriveState.OpenpilotState MADSState = custom.ModularAssistiveDrivingSystem.ModularAssistiveDrivingSystemState +ONROAD_BRIGHTNESS_TIMER_PAUSED = -1 + + +class OnroadTimerStatus(Enum): + NONE = 0 + PAUSE = 1 + RESUME = 2 + class UIStateSP: def __init__(self): @@ -21,8 +33,11 @@ class UIStateSP: ] self.sunnylink_state = SunnylinkState() + self.update_params() + self.onroad_brightness_timer: int = 0 self.custom_interactive_timeout: int = self.params.get("InteractivityTimeout", return_default=True) + self.reset_onroad_sleep_timer() def update(self) -> None: if self.sunnylink_enabled: @@ -30,6 +45,40 @@ class UIStateSP: else: self.sunnylink_state.stop() + def onroad_brightness_handle_alerts(self, started: bool, alert): + has_alert = started and self.onroad_brightness != OnroadBrightness.AUTO and alert is not None + + self.update_onroad_brightness(has_alert) + if has_alert: + self.reset_onroad_sleep_timer() + + def update_onroad_brightness(self, has_alert: bool) -> None: + if has_alert: + return + + if self.onroad_brightness_timer > 0: + self.onroad_brightness_timer -= 1 + + def reset_onroad_sleep_timer(self, timer_status: OnroadTimerStatus = OnroadTimerStatus.NONE) -> None: + # Toggling from active state to inactive + if timer_status == OnroadTimerStatus.PAUSE and self.onroad_brightness_timer != ONROAD_BRIGHTNESS_TIMER_PAUSED: + self.onroad_brightness_timer = ONROAD_BRIGHTNESS_TIMER_PAUSED + # Toggling from a previously inactive state or resetting an active timer + elif (self.onroad_brightness_timer_param >= 0 and self.onroad_brightness != OnroadBrightness.AUTO and + self.onroad_brightness_timer != ONROAD_BRIGHTNESS_TIMER_PAUSED) or timer_status == OnroadTimerStatus.RESUME: + if self.onroad_brightness == OnroadBrightness.AUTO_DARK: + self.onroad_brightness_timer = 15 * gui_app.target_fps + else: + self.onroad_brightness_timer = self.onroad_brightness_timer_param * gui_app.target_fps + + @property + def onroad_brightness_timer_expired(self) -> bool: + return self.onroad_brightness != OnroadBrightness.AUTO and self.onroad_brightness_timer == 0 + + @property + def auto_onroad_brightness(self) -> bool: + return self.onroad_brightness in (OnroadBrightness.AUTO, OnroadBrightness.AUTO_DARK) + @staticmethod def update_status(ss, ss_sp, onroad_evt) -> str: state = ss.state @@ -78,6 +127,10 @@ class UIStateSP: self.active_bundle = self.params.get("ModelManager_ActiveBundle") self.custom_interactive_timeout = self.params.get("InteractivityTimeout", return_default=True) + # Onroad Screen Brightness + self.onroad_brightness = int(float(self.params.get("OnroadScreenOffBrightness", return_default=True))) + self.onroad_brightness_timer_param = self.params.get("OnroadScreenOffTimer", return_default=True) + class DeviceSP: def __init__(self): @@ -86,3 +139,38 @@ class DeviceSP: def _set_awake(self, on: bool): if on and self._params.get("DeviceBootMode", return_default=True) == 1: self._params.put_bool("OffroadMode", True) + + @staticmethod + def set_onroad_brightness(_ui_state, awake: bool, cur_brightness: float) -> float: + if not awake or not _ui_state.started: + return cur_brightness + + if _ui_state.onroad_brightness_timer != 0: + if _ui_state.onroad_brightness == OnroadBrightness.AUTO_DARK: + return max(30.0, cur_brightness) + # For AUTO (Default) and Manual modes (while timer running), use standard brightness + return cur_brightness + + # 0: Auto (Default), 1: Auto (Dark) + if _ui_state.onroad_brightness == OnroadBrightness.AUTO: + return cur_brightness + elif _ui_state.onroad_brightness == OnroadBrightness.AUTO_DARK: + return cur_brightness + + # 2-21: 5% - 100% + return float((_ui_state.onroad_brightness - 1) * 5) + + @staticmethod + def set_min_onroad_brightness(_ui_state, min_brightness: int) -> int: + if _ui_state.onroad_brightness == OnroadBrightness.AUTO_DARK: + min_brightness = 10 + + return min_brightness + + @staticmethod + def wake_from_dimmed_onroad_brightness(_ui_state, evs) -> None: + if _ui_state.started and (_ui_state.onroad_brightness_timer_expired or _ui_state.onroad_brightness == OnroadBrightness.AUTO_DARK): + if any(ev.left_down for ev in evs): + if _ui_state.onroad_brightness_timer_expired: + gui_app.mouse_events.clear() + _ui_state.reset_onroad_sleep_timer() diff --git a/selfdrive/ui/ui_state.py b/selfdrive/ui/ui_state.py index 11e0cce74..a9127ac3c 100644 --- a/selfdrive/ui/ui_state.py +++ b/selfdrive/ui/ui_state.py @@ -258,9 +258,17 @@ class Device(DeviceSP): else: clipped_brightness = ((clipped_brightness + 16.0) / 116.0) ** 3.0 - clipped_brightness = float(np.interp(clipped_brightness, [0, 1], [30, 100])) + min_brightness = 30 + if gui_app.sunnypilot_ui(): + min_brightness = DeviceSP.set_min_onroad_brightness(ui_state, min_brightness) + + clipped_brightness = float(np.interp(clipped_brightness, [0, 1], [min_brightness, 100])) brightness = round(self._brightness_filter.update(clipped_brightness)) + + if gui_app.sunnypilot_ui(): + brightness = DeviceSP.set_onroad_brightness(ui_state, self._awake, brightness) + if not self._awake: brightness = 0 @@ -276,6 +284,9 @@ class Device(DeviceSP): self._ignition = ui_state.ignition if ignition_just_turned_off or any(ev.left_down for ev in gui_app.mouse_events): + if gui_app.sunnypilot_ui(): + DeviceSP.wake_from_dimmed_onroad_brightness(ui_state, gui_app.mouse_events) + self._reset_interactive_timeout() interaction_timeout = time.monotonic() > self._interaction_time diff --git a/sunnypilot/sunnylink/params_metadata.json b/sunnypilot/sunnylink/params_metadata.json index 1fc3f991b..0c1c9016f 100644 --- a/sunnypilot/sunnylink/params_metadata.json +++ b/sunnypilot/sunnylink/params_metadata.json @@ -777,13 +777,100 @@ "OnroadScreenOffBrightness": { "title": "Onroad Brightness", "description": "", - "min": 0, - "max": 100, - "step": 5 + "options": [ + { + "value": 0, + "label": "Auto (Default)" + }, + { + "value": 1, + "label": "Auto (Dark)" + }, + { + "value": 2, + "label": "5 %" + }, + { + "value": 3, + "label": "10 %" + }, + { + "value": 4, + "label": "15 %" + }, + { + "value": 5, + "label": "20 %" + }, + { + "value": 6, + "label": "25 %" + }, + { + "value": 7, + "label": "30 %" + }, + { + "value": 8, + "label": "35 %" + }, + { + "value": 9, + "label": "40 %" + }, + { + "value": 10, + "label": "45 %" + }, + { + "value": 11, + "label": "50 %" + }, + { + "value": 12, + "label": "55 %" + }, + { + "value": 13, + "label": "60 %" + }, + { + "value": 14, + "label": "65 %" + }, + { + "value": 15, + "label": "70 %" + }, + { + "value": 16, + "label": "75 %" + }, + { + "value": 17, + "label": "80 %" + }, + { + "value": 18, + "label": "85 %" + }, + { + "value": 19, + "label": "90 %" + }, + { + "value": 20, + "label": "95 %" + }, + { + "value": 21, + "label": "100 %" + } + ] }, "OnroadScreenOffControl": { - "title": "Onroad Screen: Reduced Brightness", - "description": "Turn off device screen or reduce brightness after driving starts" + "title": "Onroad Brightness", + "description": "Adjusts the screen brightness while it's in onroad state." }, "OnroadScreenOffTimer": { "title": "Onroad Brightness Delay", From e7b6e62b823c5c8563050b925734c5c1f5eb1f77 Mon Sep 17 00:00:00 2001 From: Nayan Date: Mon, 19 Jan 2026 01:42:24 -0500 Subject: [PATCH 745/910] [TIZI/TICI] ui: expose Interactivity Timeout option (#1497) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * param to control stock vs sp ui * init styles * SP Toggles * Lint * optimizations * Panels. With Icons. And Scroller. * patience, grasshopper * more patience, grasshopper * sp raylib preview * fix callback * fix ui preview * add ui previews * Option Control * Need this * introducing ui_state_sp for py * param to control stock vs sp ui * better * add ui_update callback * better padding * this * listitem -> listitemsp * Revert "add ui_update callback" This reverts commit 4da32cc0097434aab0aa6a3c35465eabb23c8958. * add show_description method * remove padding from line separator. like, WHY? 😩😩 * simplify * I. SAID. SIMPLIFY. * AAARGGGGGG..... * init * option control value fix * add all controls * hide all controls * lint * scroller -> scroller_tici * scroller -> scroller_tici * ui: `GuiApplicationExt` * add to readme * use gui_app.sunnypilot_ui() * use gui_app.sunnypilot_ui() * use gui_app.sunnypilot_ui() * optimizations * Removed hide for now * refresh controls * ugh * global brightness * initialize * inline everything again * change name * Onroad Brightness reimpl * Custom Interactive Timeout reimpl * Global Brightness Override reimpl * keep stock * ui: Custom Interactive Timeout * rename * ui: Customizable Onroad Brightness * lint * lint * Revert "Global Brightness Override reimpl" This reverts commit 53522da4f869fef410b9abd117d4fd367f67a12c. * Revert "Custom Interactive Timeout reimpl" This reverts commit 459863a9bb54cbc0829a2071caaca1e1fd45cbcf. * Revert "Onroad Brightness reimpl" This reverts commit 4092d23e57273d013f691cd8ed51ad302e69a6bd. * fixes * lint * reset on show/hide * reset on show/hide for mici * only set if true * wrong var * try this out * use clear * starts cleanup * wake for all visual alerts and handle timeouts * fixup: wake for all visual alerts and handle timeouts * handle always wake if there's an event properly * some * slightly more * need this back * Reapply "ui: Global Brightness Override (#1579)" This reverts commit a0c10be1ffd9d3d73409aa826b111713be1a85bf. * do not touch light sensor logic * override properly and clip to 30% minimum * wrap * lint * update immediately * read * max global brightness * rename * gotta do it for mici too lol * update metadata * desc --------- Co-authored-by: Jason Wen Co-authored-by: DevTekVE --- .../ui/sunnypilot/layouts/settings/display.py | 14 +++++ sunnypilot/sunnylink/params_metadata.json | 57 ++++++++++++++++++- 2 files changed, 69 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/sunnypilot/layouts/settings/display.py b/selfdrive/ui/sunnypilot/layouts/settings/display.py index abbfbe7c9..e5f1eba18 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/display.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/display.py @@ -51,9 +51,23 @@ class DisplayLayout(Widget): label_callback=lambda value: f"{value} s" if value < 60 else f"{int(value/60)} m", inline=True ) + self._interactivity_timeout = option_item_sp( + param="InteractivityTimeout", + title=lambda: tr("Interactivity Timeout"), + description=lambda: tr("Apply a custom timeout for settings UI." + + "
This is the time after which settings UI closes automatically " + + "if user is not interacting with the screen."), + min_value=0, + max_value=120, + value_change_step=10, + label_callback=lambda value: (tr("Default") if not value or value == 0 else + f"{value} s" if value < 60 else f"{int(value/60)} m"), + inline=True + ) items = [ self._onroad_brightness, self._onroad_brightness_timer, + self._interactivity_timeout, ] return items diff --git a/sunnypilot/sunnylink/params_metadata.json b/sunnypilot/sunnylink/params_metadata.json index 0c1c9016f..e4390586b 100644 --- a/sunnypilot/sunnylink/params_metadata.json +++ b/sunnypilot/sunnylink/params_metadata.json @@ -398,8 +398,61 @@ }, "InteractivityTimeout": { "title": "Interactivity Timeout", - "description": "", - "unit": "seconds" + "description": "Apply a custom timeout for settings UI. This is the time after which settings UI closes automatically if user is not interacting with the screen.", + "options": [ + { + "value": 0, + "label": "Default" + }, + { + "value": 10, + "label": "10 s" + }, + { + "value": 20, + "label": "20 s" + }, + { + "value": 30, + "label": "30 s" + }, + { + "value": 40, + "label": "40 s" + }, + { + "value": 50, + "label": "50 s" + }, + { + "value": 60, + "label": "1 m" + }, + { + "value": 70, + "label": "1 m" + }, + { + "value": 80, + "label": "1 m" + }, + { + "value": 90, + "label": "1 m" + }, + { + "value": 100, + "label": "1 m" + }, + { + "value": 110, + "label": "1 m" + }, + { + "value": 120, + "label": "2 m" + } + ] }, "IsDevelopmentBranch": { "title": "Is Development Branch", From a46ff01cab6d79ec35b1fd91b5180da534804d0b Mon Sep 17 00:00:00 2001 From: commaci-public <60409688+commaci-public@users.noreply.github.com> Date: Mon, 19 Jan 2026 11:39:21 -0800 Subject: [PATCH 746/910] [bot] Update Python packages (#36966) * Update Python packages * ty fixes --------- Co-authored-by: Vehicle Researcher Co-authored-by: Adeeb Shihadeh --- common/pid.py | 12 +- docs/CARS.md | 159 +- panda | 2 +- pyproject.toml | 4 +- selfdrive/car/car_specific.py | 2 +- .../debug/car/hyundai_enable_radar_points.py | 6 +- selfdrive/debug/car/vw_mqb_config.py | 10 +- selfdrive/debug/cpu_usage_stat.py | 1 - selfdrive/debug/fuzz_fw_fingerprint.py | 1 - selfdrive/debug/measure_torque_time_to_max.py | 1 - selfdrive/debug/test_fw_query_on_routes.py | 1 - selfdrive/locationd/calibrationd.py | 2 +- selfdrive/locationd/paramsd.py | 4 +- system/qcomgpsd/nmeaport.py | 4 +- system/ui/lib/application.py | 3 +- system/ui/widgets/__init__.py | 4 +- system/ui/widgets/network.py | 4 +- tinygrad_repo | 2 +- tools/jotpluggler/pluggle.py | 2 +- tools/tuning/measure_steering_accuracy.py | 1 - uv.lock | 1640 ++++++++--------- 21 files changed, 912 insertions(+), 953 deletions(-) diff --git a/common/pid.py b/common/pid.py index e3fa8afdf..b3d64d6fc 100644 --- a/common/pid.py +++ b/common/pid.py @@ -3,15 +3,9 @@ from numbers import Number class PIDController: def __init__(self, k_p, k_i, k_d=0., pos_limit=1e308, neg_limit=-1e308, rate=100): - self._k_p = k_p - self._k_i = k_i - self._k_d = k_d - if isinstance(self._k_p, Number): - self._k_p = [[0], [self._k_p]] - if isinstance(self._k_i, Number): - self._k_i = [[0], [self._k_i]] - if isinstance(self._k_d, Number): - self._k_d = [[0], [self._k_d]] + self._k_p: list[list[float]] = [[0], [k_p]] if isinstance(k_p, Number) else k_p + self._k_i: list[list[float]] = [[0], [k_i]] if isinstance(k_i, Number) else k_i + self._k_d: list[list[float]] = [[0], [k_d]] if isinstance(k_d, Number) else k_d self.set_limits(pos_limit, neg_limit) diff --git a/docs/CARS.md b/docs/CARS.md index e0d61cd41..08c06b230 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -4,22 +4,22 @@ 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. -# 325 Supported Cars +# 326 Supported Cars |Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Hardware Needed
 |Video|Setup Video| |---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| |Acura|ILX 2016-18|Technology Plus Package or AcuraWatch Plus|openpilot|26 mph|25 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Acura|ILX 2019|All|openpilot|26 mph|25 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Acura|MDX 2025|All except Type S|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Acura|MDX 2025-26|All except Type S|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Acura|RDX 2016-18|AcuraWatch Plus or Advance Package|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Acura|RDX 2019-21|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Acura|TLX 2021|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Audi|A3 2014-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Audi|A3 Sportback e-tron 2017-18|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Audi|Q2 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Audi|Q3 2019-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Audi|RS3 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Audi|S3 2015-17|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Audi|A3 2014-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Audi|A3 Sportback e-tron 2017-18|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Audi|Q2 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Audi|Q3 2019-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Audi|RS3 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Audi|S3 2015-17|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Chevrolet|Bolt EUV 2022-23|Premier or Premier Redline Trim, without Super Cruise Package|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 harness box
- 1 mount
Buy Here
||| |Chevrolet|Bolt EV 2022-23|2LT Trim with Adaptive Cruise Control Package|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 harness box
- 1 mount
Buy Here
||| |Chevrolet|Equinox 2019-22|Adaptive Cruise Control (ACC)|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 harness box
- 1 mount
Buy Here
||| @@ -31,33 +31,33 @@ A supported vehicle is one that just works when you install a comma device. All |Chrysler|Pacifica Hybrid 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Chrysler|Pacifica Hybrid 2019-25|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |comma|body|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|None||| -|CUPRA|Ateca 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|CUPRA|Ateca 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Dodge|Durango 2020-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Ford|Bronco Sport 2021-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Ford|Escape 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Ford|Escape 2023-24|Co-Pilot360 Assist+|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Ford|Escape 2023-24|Co-Pilot360 Assist+|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Ford|Escape Hybrid 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Ford|Escape Hybrid 2023-24|Co-Pilot360 Assist+|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Ford|Escape Hybrid 2023-24|Co-Pilot360 Assist+|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Ford|Escape Plug-in Hybrid 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Ford|Escape Plug-in Hybrid 2023-24|Co-Pilot360 Assist+|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Ford|Expedition 2022-24|Co-Pilot360 Assist 2.0|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Ford|Escape Plug-in Hybrid 2023-24|Co-Pilot360 Assist+|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Ford|Expedition 2022-24|Co-Pilot360 Assist 2.0|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Ford|Explorer 2020-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Ford|Explorer Hybrid 2020-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Ford|F-150 2021-23|Co-Pilot360 Assist 2.0|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Ford|F-150 Hybrid 2021-23|Co-Pilot360 Assist 2.0|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Ford|F-150 2021-23|Co-Pilot360 Assist 2.0|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Ford|F-150 Hybrid 2021-23|Co-Pilot360 Assist 2.0|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Ford|Focus 2018[2](#footnotes)|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Ford|Focus Hybrid 2018[2](#footnotes)|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Ford|Kuga 2020-23|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Ford|Kuga Hybrid 2020-23|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Ford|Kuga Hybrid 2024|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Ford|Kuga Hybrid 2024|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Ford|Kuga Plug-in Hybrid 2020-23|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Ford|Kuga Plug-in Hybrid 2024|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Ford|Kuga Plug-in Hybrid 2024|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Ford|Maverick 2022|LARIAT Luxury|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Ford|Maverick 2023-24|Co-Pilot360 Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Ford|Maverick Hybrid 2022|LARIAT Luxury|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Ford|Maverick Hybrid 2023-24|Co-Pilot360 Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Ford|Mustang Mach-E 2021-24|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Ford|Ranger 2024|Adaptive Cruise Control with Lane Centering|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Ford|Mustang Mach-E 2021-24|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Ford|Ranger 2024|Adaptive Cruise Control with Lane Centering|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Genesis|G70 2018|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Genesis|G70 2019-21|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Genesis|G70 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| @@ -91,7 +91,7 @@ A supported vehicle is one that just works when you install a comma device. All |Honda|CR-V 2017-22|Honda Sensing|openpilot available[1](#footnotes)|0 mph|15 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Honda|CR-V 2023-26|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Honda|CR-V Hybrid 2017-22|Honda Sensing|openpilot available[1](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Honda|CR-V Hybrid 2023-25|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|CR-V Hybrid 2023-26|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Honda|e 2020|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Honda|Fit 2018-20|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Honda|Freed 2020|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| @@ -201,6 +201,7 @@ A supported vehicle is one that just works when you install a comma device. All |Lexus|IS 2017-19|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Lexus|IS 2022-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Lexus|LC 2024-25|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lexus|LS 2018|All except Lexus Safety System+ A|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Lexus|NX 2018-19|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Lexus|NX 2020-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Lexus|NX Hybrid 2018-19|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| @@ -216,19 +217,19 @@ A supported vehicle is one that just works when you install a comma device. All |Lexus|UX Hybrid 2019-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Lincoln|Aviator 2020-24|Co-Pilot360 Plus|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Lincoln|Aviator Plug-in Hybrid 2020-24|Co-Pilot360 Plus|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|MAN|eTGE 2020-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|MAN|TGE 2017-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|MAN|eTGE 2020-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|MAN|TGE 2017-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Mazda|CX-5 2022-25|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Mazda connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Mazda|CX-9 2021-23|All|Stock|0 mph|28 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Mazda connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Nissan[5](#footnotes)|Altima 2019-20, 2024|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan B connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Nissan[5](#footnotes)|Leaf 2018-23|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Nissan[5](#footnotes)|Rogue 2018-20|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Nissan[5](#footnotes)|X-Trail 2017|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Nissan[5](#footnotes)|Altima 2019-20, 2024|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan B connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Nissan[5](#footnotes)|Leaf 2018-23|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Nissan[5](#footnotes)|Rogue 2018-20|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Nissan[5](#footnotes)|X-Trail 2017|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Ram|1500 2019-24|Adaptive Cruise Control (ACC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Ram connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Rivian|R1S 2022-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Rivian A connector
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Rivian|R1T 2022-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Rivian A connector
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|SEAT|Ateca 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|SEAT|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Rivian|R1S 2022-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Rivian A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Rivian|R1T 2022-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Rivian A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|SEAT|Ateca 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|SEAT|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Subaru|Ascent 2019-21|All[6](#footnotes)|openpilot available[1,7](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| |Subaru|Crosstrek 2018-19|EyeSight Driver Assistance[6](#footnotes)|openpilot available[1,7](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| |Subaru|Crosstrek 2020-23|EyeSight Driver Assistance[6](#footnotes)|openpilot available[1,7](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| @@ -239,19 +240,19 @@ A supported vehicle is one that just works when you install a comma device. All |Subaru|Outback 2020-22|All[6](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru B connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| |Subaru|XV 2018-19|EyeSight Driver Assistance[6](#footnotes)|openpilot available[1,7](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| |Subaru|XV 2020-21|EyeSight Driver Assistance[6](#footnotes)|openpilot available[1,7](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| -|Škoda|Fabia 2022-23[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[15](#footnotes)||| -|Škoda|Kamiq 2021-23[11,13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[15](#footnotes)||| -|Škoda|Karoq 2019-23[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Škoda|Kodiaq 2017-23[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Škoda|Octavia 2015-19[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Škoda|Octavia RS 2016[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Škoda|Octavia Scout 2017-19[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Škoda|Scala 2020-23[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[15](#footnotes)||| -|Škoda|Superb 2015-22[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Tesla[9](#footnotes)|Model 3 (with HW3) 2019-23[8](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Tesla A connector
- 1 USB-C coupler
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Tesla[9](#footnotes)|Model 3 (with HW4) 2024-25[8](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Tesla B connector
- 1 USB-C coupler
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Tesla[9](#footnotes)|Model Y (with HW3) 2020-23[8](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Tesla A connector
- 1 USB-C coupler
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Tesla[9](#footnotes)|Model Y (with HW4) 2024-25[8](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Tesla B connector
- 1 USB-C coupler
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Škoda|Fabia 2022-23[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[15](#footnotes)||| +|Škoda|Kamiq 2021-23[11,13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[15](#footnotes)||| +|Škoda|Karoq 2019-23[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Škoda|Kodiaq 2017-23[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Škoda|Octavia 2015-19[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Škoda|Octavia RS 2016[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Škoda|Octavia Scout 2017-19[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Škoda|Scala 2020-23[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[15](#footnotes)||| +|Škoda|Superb 2015-22[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Tesla[9](#footnotes)|Model 3 (with HW3) 2019-23[8](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Tesla A connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Tesla[9](#footnotes)|Model 3 (with HW4) 2024-25[8](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Tesla B connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Tesla[9](#footnotes)|Model Y (with HW3) 2020-23[8](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Tesla A connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Tesla[9](#footnotes)|Model Y (with HW4) 2024-25[8](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Tesla B connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Toyota|Alphard 2019-20|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Toyota|Alphard Hybrid 2021|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Toyota|Avalon 2016|Toyota Safety Sense P|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| @@ -297,42 +298,42 @@ A supported vehicle is one that just works when you install a comma device. All |Toyota|RAV4 Hybrid 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Toyota|RAV4 Hybrid 2023-25|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Toyota|Sienna 2018-20|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Volkswagen|Arteon 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Arteon eHybrid 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Arteon R 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Arteon Shooting Brake 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Atlas 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Atlas Cross Sport 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|California 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Caravelle 2020|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|CC 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Crafter 2017-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|e-Crafter 2018-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|e-Golf 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Golf 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Golf Alltrack 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Golf GTD 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Golf GTE 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Golf GTI 2015-21|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Golf R 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Golf SportsVan 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Grand California 2019-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Jetta 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Jetta GLI 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Passat 2015-22[12](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Passat Alltrack 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Passat GTE 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Polo 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[15](#footnotes)||| -|Volkswagen|Polo GTI 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[15](#footnotes)||| -|Volkswagen|T-Cross 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[15](#footnotes)||| -|Volkswagen|T-Roc 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Taos 2022-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Teramont 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Teramont Cross Sport 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Teramont X 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Tiguan 2018-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Tiguan eHybrid 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Touran 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Arteon 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Arteon eHybrid 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Arteon R 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Arteon Shooting Brake 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Atlas 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Atlas Cross Sport 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|California 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Caravelle 2020|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|CC 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Crafter 2017-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|e-Crafter 2018-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|e-Golf 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Golf 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Golf Alltrack 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Golf GTD 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Golf GTE 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Golf GTI 2015-21|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Golf R 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Golf SportsVan 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Grand California 2019-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Jetta 2019-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Jetta GLI 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Passat 2015-22[12](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Passat Alltrack 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Passat GTE 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Polo 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[15](#footnotes)||| +|Volkswagen|Polo GTI 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[15](#footnotes)||| +|Volkswagen|T-Cross 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[15](#footnotes)||| +|Volkswagen|T-Roc 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Taos 2022-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Teramont 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Teramont Cross Sport 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Teramont X 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Tiguan 2018-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Tiguan eHybrid 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Touran 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
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/panda b/panda index e42367df9..3dd38b76b 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit e42367df97f1a5ea4ddb152566022c3ae4672e58 +Subproject commit 3dd38b76b48903efb4705f55752e9719ba2f5564 diff --git a/pyproject.toml b/pyproject.toml index c21ba734d..9a70f69d2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -273,6 +273,6 @@ invalid-argument-type = "ignore" call-non-callable = "ignore" # Ignore unsupported-operator - false positives from dynamic types unsupported-operator = "ignore" -# Ignore non-subscriptable - false positives from dynamic types -non-subscriptable = "ignore" +# Ignore not-subscriptable - false positives from dynamic types +not-subscriptable = "ignore" # not-iterable errors are now fixed diff --git a/selfdrive/car/car_specific.py b/selfdrive/car/car_specific.py index c93d7ac4a..86494afc7 100644 --- a/selfdrive/car/car_specific.py +++ b/selfdrive/car/car_specific.py @@ -87,7 +87,7 @@ class CarSpecificEvents: events.add(EventName.speedTooLow) # TODO: this needs to be implemented generically in carState struct - # if CC.eps_timer_soft_disable_alert: # type: ignore[attr-defined] + # if CC.eps_timer_soft_disable_alert: # events.add(EventName.steerTimeLimit) return events diff --git a/selfdrive/debug/car/hyundai_enable_radar_points.py b/selfdrive/debug/car/hyundai_enable_radar_points.py index 93f5949ea..df150a522 100755 --- a/selfdrive/debug/car/hyundai_enable_radar_points.py +++ b/selfdrive/debug/car/hyundai_enable_radar_points.py @@ -101,11 +101,11 @@ if __name__ == "__main__": uds_client = UdsClient(panda, 0x7D0, bus=args.bus) print("\n[START DIAGNOSTIC SESSION]") - session_type : SESSION_TYPE = 0x07 # type: ignore + session_type : SESSION_TYPE = 0x07 uds_client.diagnostic_session_control(session_type) print("[HARDWARE/SOFTWARE VERSION]") - fw_version_data_id : DATA_IDENTIFIER_TYPE = 0xf100 # type: ignore + fw_version_data_id : DATA_IDENTIFIER_TYPE = 0xf100 fw_version = uds_client.read_data_by_identifier(fw_version_data_id) print(fw_version) if fw_version not in SUPPORTED_FW_VERSIONS.keys(): @@ -113,7 +113,7 @@ if __name__ == "__main__": sys.exit(1) print("[GET CONFIGURATION]") - config_data_id : DATA_IDENTIFIER_TYPE = 0x0142 # type: ignore + config_data_id : DATA_IDENTIFIER_TYPE = 0x0142 current_config = uds_client.read_data_by_identifier(config_data_id) config_values = SUPPORTED_FW_VERSIONS[fw_version] new_config = config_values.default_config if args.default else config_values.tracks_enabled diff --git a/selfdrive/debug/car/vw_mqb_config.py b/selfdrive/debug/car/vw_mqb_config.py index 3c55642e4..13ee7786d 100755 --- a/selfdrive/debug/car/vw_mqb_config.py +++ b/selfdrive/debug/car/vw_mqb_config.py @@ -55,7 +55,7 @@ if __name__ == "__main__": sw_ver = uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.VEHICLE_MANUFACTURER_ECU_SOFTWARE_VERSION_NUMBER).decode("utf-8") component = uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.SYSTEM_NAME_OR_ENGINE_TYPE).decode("utf-8") odx_file = uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.ODX_FILE).decode("utf-8").rstrip('\x00') - current_coding = uds_client.read_data_by_identifier(VOLKSWAGEN_DATA_IDENTIFIER_TYPE.CODING) # type: ignore + current_coding = uds_client.read_data_by_identifier(VOLKSWAGEN_DATA_IDENTIFIER_TYPE.CODING) coding_text = current_coding.hex() print("\nEPS diagnostic data\n") @@ -126,9 +126,9 @@ if __name__ == "__main__": new_coding = current_coding[0:coding_byte] + new_byte.to_bytes(1, "little") + current_coding[coding_byte+1:] try: - seed = uds_client.security_access(ACCESS_TYPE_LEVEL_1.REQUEST_SEED) # type: ignore + seed = uds_client.security_access(ACCESS_TYPE_LEVEL_1.REQUEST_SEED) key = struct.unpack("!I", seed)[0] + 28183 # yeah, it's like that - uds_client.security_access(ACCESS_TYPE_LEVEL_1.SEND_KEY, struct.pack("!I", key)) # type: ignore + uds_client.security_access(ACCESS_TYPE_LEVEL_1.SEND_KEY, struct.pack("!I", key)) except (NegativeResponseError, MessageTimeoutError): print("Security access failed!") print("Open the hood and retry (disables the \"diagnostic firewall\" on newer vehicles)") @@ -148,7 +148,7 @@ if __name__ == "__main__": uds_client.write_data_by_identifier(DATA_IDENTIFIER_TYPE.PROGRAMMING_DATE, prog_date) tester_num = uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.CALIBRATION_REPAIR_SHOP_CODE_OR_CALIBRATION_EQUIPMENT_SERIAL_NUMBER) uds_client.write_data_by_identifier(DATA_IDENTIFIER_TYPE.REPAIR_SHOP_CODE_OR_TESTER_SERIAL_NUMBER, tester_num) - uds_client.write_data_by_identifier(VOLKSWAGEN_DATA_IDENTIFIER_TYPE.CODING, new_coding) # type: ignore + uds_client.write_data_by_identifier(VOLKSWAGEN_DATA_IDENTIFIER_TYPE.CODING, new_coding) except (NegativeResponseError, MessageTimeoutError): print("Writing new configuration failed!") print("Make sure the comma processes are stopped: tmux kill-session -t comma") @@ -156,7 +156,7 @@ if __name__ == "__main__": try: # Read back result just to make 100% sure everything worked - current_coding_text = uds_client.read_data_by_identifier(VOLKSWAGEN_DATA_IDENTIFIER_TYPE.CODING).hex() # type: ignore + current_coding_text = uds_client.read_data_by_identifier(VOLKSWAGEN_DATA_IDENTIFIER_TYPE.CODING).hex() print(f" New coding: {current_coding_text}") except (NegativeResponseError, MessageTimeoutError): print("Reading back updated coding failed!") diff --git a/selfdrive/debug/cpu_usage_stat.py b/selfdrive/debug/cpu_usage_stat.py index 397e9f35f..089685103 100755 --- a/selfdrive/debug/cpu_usage_stat.py +++ b/selfdrive/debug/cpu_usage_stat.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# type: ignore ''' System tools like top/htop can only show current cpu usage values, so I write this script to do statistics jobs. Features: diff --git a/selfdrive/debug/fuzz_fw_fingerprint.py b/selfdrive/debug/fuzz_fw_fingerprint.py index fa99e6bfb..b4276c0c1 100755 --- a/selfdrive/debug/fuzz_fw_fingerprint.py +++ b/selfdrive/debug/fuzz_fw_fingerprint.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# type: ignore import random from collections import defaultdict diff --git a/selfdrive/debug/measure_torque_time_to_max.py b/selfdrive/debug/measure_torque_time_to_max.py index 7052dccf7..e99aeae46 100755 --- a/selfdrive/debug/measure_torque_time_to_max.py +++ b/selfdrive/debug/measure_torque_time_to_max.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# type: ignore import os import argparse diff --git a/selfdrive/debug/test_fw_query_on_routes.py b/selfdrive/debug/test_fw_query_on_routes.py index 1216b7299..ab539e4fe 100755 --- a/selfdrive/debug/test_fw_query_on_routes.py +++ b/selfdrive/debug/test_fw_query_on_routes.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# type: ignore from collections import defaultdict import argparse diff --git a/selfdrive/locationd/calibrationd.py b/selfdrive/locationd/calibrationd.py index 3843cf37f..036f5822d 100755 --- a/selfdrive/locationd/calibrationd.py +++ b/selfdrive/locationd/calibrationd.py @@ -47,7 +47,7 @@ DEBUG = os.getenv("DEBUG") is not None def is_calibration_valid(rpy: np.ndarray) -> bool: - return (PITCH_LIMITS[0] < rpy[1] < PITCH_LIMITS[1]) and (YAW_LIMITS[0] < rpy[2] < YAW_LIMITS[1]) # type: ignore + return (PITCH_LIMITS[0] < rpy[1] < PITCH_LIMITS[1]) and (YAW_LIMITS[0] < rpy[2] < YAW_LIMITS[1]) def sanity_clip(rpy: np.ndarray) -> np.ndarray: diff --git a/selfdrive/locationd/paramsd.py b/selfdrive/locationd/paramsd.py index b4084fe5b..fd03d3d09 100755 --- a/selfdrive/locationd/paramsd.py +++ b/selfdrive/locationd/paramsd.py @@ -127,8 +127,8 @@ class VehicleParamsLearner: if not self.active: # Reset time when stopped so uncertainty doesn't grow - self.kf.filter.set_filter_time(t) # type: ignore - self.kf.filter.reset_rewind() # type: ignore + self.kf.filter.set_filter_time(t) + self.kf.filter.reset_rewind() def get_msg(self, valid: bool, debug: bool = False) -> capnp._DynamicStructBuilder: x = self.kf.x diff --git a/system/qcomgpsd/nmeaport.py b/system/qcomgpsd/nmeaport.py index 8b9ab5108..10b8516ed 100644 --- a/system/qcomgpsd/nmeaport.py +++ b/system/qcomgpsd/nmeaport.py @@ -107,11 +107,11 @@ def process_nmea_port_messages(device:str="/dev/ttyUSB1") -> NoReturn: match fields[0]: case "$GNCLK": # fields at end are reserved (not used) - gnss_clock = GnssClockNmeaPort(*fields[1:10]) # type: ignore[arg-type] + gnss_clock = GnssClockNmeaPort(*fields[1:10]) print(gnss_clock) case "$GNMEAS": # fields at end are reserved (not used) - gnss_meas = GnssMeasNmeaPort(*fields[1:14]) # type: ignore[arg-type] + gnss_meas = GnssMeasNmeaPort(*fields[1:14]) print(gnss_meas) except Exception as e: print(e) diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 04cd37af3..151f22ac1 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -184,7 +184,8 @@ class MouseState: time.monotonic(), ) # Only add changes - if self._prev_mouse_event[slot] is None or ev[:-1] != self._prev_mouse_event[slot][:-1]: + prev = self._prev_mouse_event[slot] + if prev is None or ev[:-1] != prev[:-1]: with self._lock: self._events.append(ev) self._prev_mouse_event[slot] = ev diff --git a/system/ui/widgets/__init__.py b/system/ui/widgets/__init__.py index 4f3c977d2..42d9a910a 100644 --- a/system/ui/widgets/__init__.py +++ b/system/ui/widgets/__init__.py @@ -12,7 +12,7 @@ try: except ImportError: class Device: awake = True - device = Device() # type: ignore + device = Device() class DialogResult(IntEnum): @@ -299,7 +299,7 @@ class NavWidget(Widget, abc.ABC): # block horizontal swiping if now swiping away if self._can_swipe_away: - if mouse_event.pos.y - self._back_button_start_pos.y > START_DISMISSING_THRESHOLD: # type: ignore + if mouse_event.pos.y - self._back_button_start_pos.y > START_DISMISSING_THRESHOLD: self._swiping_away = True elif mouse_event.left_released: diff --git a/system/ui/widgets/network.py b/system/ui/widgets/network.py index f41a04c24..8f5168958 100644 --- a/system/ui/widgets/network.py +++ b/system/ui/widgets/network.py @@ -22,8 +22,8 @@ try: from openpilot.selfdrive.ui.lib.prime_state import PrimeType except Exception: Params = None - ui_state = None # type: ignore - PrimeType = None # type: ignore + ui_state = None + PrimeType = None NM_DEVICE_STATE_NEED_AUTH = 60 MIN_PASSWORD_LENGTH = 8 diff --git a/tinygrad_repo b/tinygrad_repo index f5090192c..7cb7abeeb 160000 --- a/tinygrad_repo +++ b/tinygrad_repo @@ -1 +1 @@ -Subproject commit f5090192c84760be1227f7e3c4f99ad0603117ae +Subproject commit 7cb7abeeb02c681a463f252179354db4bb5e3809 diff --git a/tools/jotpluggler/pluggle.py b/tools/jotpluggler/pluggle.py index 879b67751..6fb8d5049 100755 --- a/tools/jotpluggler/pluggle.py +++ b/tools/jotpluggler/pluggle.py @@ -7,7 +7,7 @@ import dearpygui.dearpygui as dpg import multiprocessing import uuid import signal -import yaml # type: ignore +import yaml from openpilot.common.swaglog import cloudlog from openpilot.common.basedir import BASEDIR from openpilot.tools.jotpluggler.data import DataManager diff --git a/tools/tuning/measure_steering_accuracy.py b/tools/tuning/measure_steering_accuracy.py index 7e4e97574..e4aef0ba1 100755 --- a/tools/tuning/measure_steering_accuracy.py +++ b/tools/tuning/measure_steering_accuracy.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# type: ignore import os import time diff --git a/uv.lock b/uv.lock index 73c8fe7c9..674b18b7e 100644 --- a/uv.lock +++ b/uv.lock @@ -2,12 +2,8 @@ version = 1 revision = 3 requires-python = ">=3.11, <3.13" resolution-markers = [ - "python_full_version >= '3.12' and sys_platform == 'darwin'", - "python_full_version >= '3.12' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "(python_full_version >= '3.12' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.12' and sys_platform != 'darwin' and sys_platform != 'linux')", - "python_full_version < '3.12' and sys_platform == 'darwin'", - "python_full_version < '3.12' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "(python_full_version < '3.12' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.12' and sys_platform != 'darwin' and sys_platform != 'linux')", + "python_full_version >= '3.12'", + "python_full_version < '3.12'", ] [[package]] @@ -21,7 +17,7 @@ wheels = [ [[package]] name = "aiohttp" -version = "3.13.2" +version = "3.13.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohappyeyeballs" }, @@ -32,42 +28,42 @@ dependencies = [ { name = "propcache" }, { name = "yarl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1c/ce/3b83ebba6b3207a7135e5fcaba49706f8a4b6008153b4e30540c982fae26/aiohttp-3.13.2.tar.gz", hash = "sha256:40176a52c186aefef6eb3cad2cdd30cd06e3afbe88fe8ab2af9c0b90f228daca", size = 7837994, upload-time = "2025-10-28T20:59:39.937Z" } +sdist = { url = "https://files.pythonhosted.org/packages/50/42/32cf8e7704ceb4481406eb87161349abb46a57fee3f008ba9cb610968646/aiohttp-3.13.3.tar.gz", hash = "sha256:a949eee43d3782f2daae4f4a2819b2cb9b0c5d3b7f7a927067cc84dafdbb9f88", size = 7844556, upload-time = "2026-01-03T17:33:05.204Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/35/74/b321e7d7ca762638cdf8cdeceb39755d9c745aff7a64c8789be96ddf6e96/aiohttp-3.13.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4647d02df098f6434bafd7f32ad14942f05a9caa06c7016fdcc816f343997dd0", size = 743409, upload-time = "2025-10-28T20:56:00.354Z" }, - { url = "https://files.pythonhosted.org/packages/99/3d/91524b905ec473beaf35158d17f82ef5a38033e5809fe8742e3657cdbb97/aiohttp-3.13.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e3403f24bcb9c3b29113611c3c16a2a447c3953ecf86b79775e7be06f7ae7ccb", size = 497006, upload-time = "2025-10-28T20:56:01.85Z" }, - { url = "https://files.pythonhosted.org/packages/eb/d3/7f68bc02a67716fe80f063e19adbd80a642e30682ce74071269e17d2dba1/aiohttp-3.13.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:43dff14e35aba17e3d6d5ba628858fb8cb51e30f44724a2d2f0c75be492c55e9", size = 493195, upload-time = "2025-10-28T20:56:03.314Z" }, - { url = "https://files.pythonhosted.org/packages/98/31/913f774a4708775433b7375c4f867d58ba58ead833af96c8af3621a0d243/aiohttp-3.13.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e2a9ea08e8c58bb17655630198833109227dea914cd20be660f52215f6de5613", size = 1747759, upload-time = "2025-10-28T20:56:04.904Z" }, - { url = "https://files.pythonhosted.org/packages/e8/63/04efe156f4326f31c7c4a97144f82132c3bb21859b7bb84748d452ccc17c/aiohttp-3.13.2-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53b07472f235eb80e826ad038c9d106c2f653584753f3ddab907c83f49eedead", size = 1704456, upload-time = "2025-10-28T20:56:06.986Z" }, - { url = "https://files.pythonhosted.org/packages/8e/02/4e16154d8e0a9cf4ae76f692941fd52543bbb148f02f098ca73cab9b1c1b/aiohttp-3.13.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e736c93e9c274fce6419af4aac199984d866e55f8a4cec9114671d0ea9688780", size = 1807572, upload-time = "2025-10-28T20:56:08.558Z" }, - { url = "https://files.pythonhosted.org/packages/34/58/b0583defb38689e7f06798f0285b1ffb3a6fb371f38363ce5fd772112724/aiohttp-3.13.2-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ff5e771f5dcbc81c64898c597a434f7682f2259e0cd666932a913d53d1341d1a", size = 1895954, upload-time = "2025-10-28T20:56:10.545Z" }, - { url = "https://files.pythonhosted.org/packages/6b/f3/083907ee3437425b4e376aa58b2c915eb1a33703ec0dc30040f7ae3368c6/aiohttp-3.13.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3b6fb0c207cc661fa0bf8c66d8d9b657331ccc814f4719468af61034b478592", size = 1747092, upload-time = "2025-10-28T20:56:12.118Z" }, - { url = "https://files.pythonhosted.org/packages/ac/61/98a47319b4e425cc134e05e5f3fc512bf9a04bf65aafd9fdcda5d57ec693/aiohttp-3.13.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:97a0895a8e840ab3520e2288db7cace3a1981300d48babeb50e7425609e2e0ab", size = 1606815, upload-time = "2025-10-28T20:56:14.191Z" }, - { url = "https://files.pythonhosted.org/packages/97/4b/e78b854d82f66bb974189135d31fce265dee0f5344f64dd0d345158a5973/aiohttp-3.13.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9e8f8afb552297aca127c90cb840e9a1d4bfd6a10d7d8f2d9176e1acc69bad30", size = 1723789, upload-time = "2025-10-28T20:56:16.101Z" }, - { url = "https://files.pythonhosted.org/packages/ed/fc/9d2ccc794fc9b9acd1379d625c3a8c64a45508b5091c546dea273a41929e/aiohttp-3.13.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:ed2f9c7216e53c3df02264f25d824b079cc5914f9e2deba94155190ef648ee40", size = 1718104, upload-time = "2025-10-28T20:56:17.655Z" }, - { url = "https://files.pythonhosted.org/packages/66/65/34564b8765ea5c7d79d23c9113135d1dd3609173da13084830f1507d56cf/aiohttp-3.13.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:99c5280a329d5fa18ef30fd10c793a190d996567667908bef8a7f81f8202b948", size = 1785584, upload-time = "2025-10-28T20:56:19.238Z" }, - { url = "https://files.pythonhosted.org/packages/30/be/f6a7a426e02fc82781afd62016417b3948e2207426d90a0e478790d1c8a4/aiohttp-3.13.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:2ca6ffef405fc9c09a746cb5d019c1672cd7f402542e379afc66b370833170cf", size = 1595126, upload-time = "2025-10-28T20:56:20.836Z" }, - { url = "https://files.pythonhosted.org/packages/e5/c7/8e22d5d28f94f67d2af496f14a83b3c155d915d1fe53d94b66d425ec5b42/aiohttp-3.13.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:47f438b1a28e926c37632bff3c44df7d27c9b57aaf4e34b1def3c07111fdb782", size = 1800665, upload-time = "2025-10-28T20:56:22.922Z" }, - { url = "https://files.pythonhosted.org/packages/d1/11/91133c8b68b1da9fc16555706aa7276fdf781ae2bb0876c838dd86b8116e/aiohttp-3.13.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9acda8604a57bb60544e4646a4615c1866ee6c04a8edef9b8ee6fd1d8fa2ddc8", size = 1739532, upload-time = "2025-10-28T20:56:25.924Z" }, - { url = "https://files.pythonhosted.org/packages/17/6b/3747644d26a998774b21a616016620293ddefa4d63af6286f389aedac844/aiohttp-3.13.2-cp311-cp311-win32.whl", hash = "sha256:868e195e39b24aaa930b063c08bb0c17924899c16c672a28a65afded9c46c6ec", size = 431876, upload-time = "2025-10-28T20:56:27.524Z" }, - { url = "https://files.pythonhosted.org/packages/c3/63/688462108c1a00eb9f05765331c107f95ae86f6b197b865d29e930b7e462/aiohttp-3.13.2-cp311-cp311-win_amd64.whl", hash = "sha256:7fd19df530c292542636c2a9a85854fab93474396a52f1695e799186bbd7f24c", size = 456205, upload-time = "2025-10-28T20:56:29.062Z" }, - { url = "https://files.pythonhosted.org/packages/29/9b/01f00e9856d0a73260e86dd8ed0c2234a466c5c1712ce1c281548df39777/aiohttp-3.13.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b1e56bab2e12b2b9ed300218c351ee2a3d8c8fdab5b1ec6193e11a817767e47b", size = 737623, upload-time = "2025-10-28T20:56:30.797Z" }, - { url = "https://files.pythonhosted.org/packages/5a/1b/4be39c445e2b2bd0aab4ba736deb649fabf14f6757f405f0c9685019b9e9/aiohttp-3.13.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:364e25edaabd3d37b1db1f0cbcee8c73c9a3727bfa262b83e5e4cf3489a2a9dc", size = 492664, upload-time = "2025-10-28T20:56:32.708Z" }, - { url = "https://files.pythonhosted.org/packages/28/66/d35dcfea8050e131cdd731dff36434390479b4045a8d0b9d7111b0a968f1/aiohttp-3.13.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c5c94825f744694c4b8db20b71dba9a257cd2ba8e010a803042123f3a25d50d7", size = 491808, upload-time = "2025-10-28T20:56:34.57Z" }, - { url = "https://files.pythonhosted.org/packages/00/29/8e4609b93e10a853b65f8291e64985de66d4f5848c5637cddc70e98f01f8/aiohttp-3.13.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba2715d842ffa787be87cbfce150d5e88c87a98e0b62e0f5aa489169a393dbbb", size = 1738863, upload-time = "2025-10-28T20:56:36.377Z" }, - { url = "https://files.pythonhosted.org/packages/9d/fa/4ebdf4adcc0def75ced1a0d2d227577cd7b1b85beb7edad85fcc87693c75/aiohttp-3.13.2-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:585542825c4bc662221fb257889e011a5aa00f1ae4d75d1d246a5225289183e3", size = 1700586, upload-time = "2025-10-28T20:56:38.034Z" }, - { url = "https://files.pythonhosted.org/packages/da/04/73f5f02ff348a3558763ff6abe99c223381b0bace05cd4530a0258e52597/aiohttp-3.13.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:39d02cb6025fe1aabca329c5632f48c9532a3dabccd859e7e2f110668972331f", size = 1768625, upload-time = "2025-10-28T20:56:39.75Z" }, - { url = "https://files.pythonhosted.org/packages/f8/49/a825b79ffec124317265ca7d2344a86bcffeb960743487cb11988ffb3494/aiohttp-3.13.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e67446b19e014d37342f7195f592a2a948141d15a312fe0e700c2fd2f03124f6", size = 1867281, upload-time = "2025-10-28T20:56:41.471Z" }, - { url = "https://files.pythonhosted.org/packages/b9/48/adf56e05f81eac31edcfae45c90928f4ad50ef2e3ea72cb8376162a368f8/aiohttp-3.13.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4356474ad6333e41ccefd39eae869ba15a6c5299c9c01dfdcfdd5c107be4363e", size = 1752431, upload-time = "2025-10-28T20:56:43.162Z" }, - { url = "https://files.pythonhosted.org/packages/30/ab/593855356eead019a74e862f21523db09c27f12fd24af72dbc3555b9bfd9/aiohttp-3.13.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:eeacf451c99b4525f700f078becff32c32ec327b10dcf31306a8a52d78166de7", size = 1562846, upload-time = "2025-10-28T20:56:44.85Z" }, - { url = "https://files.pythonhosted.org/packages/39/0f/9f3d32271aa8dc35036e9668e31870a9d3b9542dd6b3e2c8a30931cb27ae/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d8a9b889aeabd7a4e9af0b7f4ab5ad94d42e7ff679aaec6d0db21e3b639ad58d", size = 1699606, upload-time = "2025-10-28T20:56:46.519Z" }, - { url = "https://files.pythonhosted.org/packages/2c/3c/52d2658c5699b6ef7692a3f7128b2d2d4d9775f2a68093f74bca06cf01e1/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:fa89cb11bc71a63b69568d5b8a25c3ca25b6d54c15f907ca1c130d72f320b76b", size = 1720663, upload-time = "2025-10-28T20:56:48.528Z" }, - { url = "https://files.pythonhosted.org/packages/9b/d4/8f8f3ff1fb7fb9e3f04fcad4e89d8a1cd8fc7d05de67e3de5b15b33008ff/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8aa7c807df234f693fed0ecd507192fc97692e61fee5702cdc11155d2e5cadc8", size = 1737939, upload-time = "2025-10-28T20:56:50.77Z" }, - { url = "https://files.pythonhosted.org/packages/03/d3/ddd348f8a27a634daae39a1b8e291ff19c77867af438af844bf8b7e3231b/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:9eb3e33fdbe43f88c3c75fa608c25e7c47bbd80f48d012763cb67c47f39a7e16", size = 1555132, upload-time = "2025-10-28T20:56:52.568Z" }, - { url = "https://files.pythonhosted.org/packages/39/b8/46790692dc46218406f94374903ba47552f2f9f90dad554eed61bfb7b64c/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9434bc0d80076138ea986833156c5a48c9c7a8abb0c96039ddbb4afc93184169", size = 1764802, upload-time = "2025-10-28T20:56:54.292Z" }, - { url = "https://files.pythonhosted.org/packages/ba/e4/19ce547b58ab2a385e5f0b8aa3db38674785085abcf79b6e0edd1632b12f/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ff15c147b2ad66da1f2cbb0622313f2242d8e6e8f9b79b5206c84523a4473248", size = 1719512, upload-time = "2025-10-28T20:56:56.428Z" }, - { url = "https://files.pythonhosted.org/packages/70/30/6355a737fed29dcb6dfdd48682d5790cb5eab050f7b4e01f49b121d3acad/aiohttp-3.13.2-cp312-cp312-win32.whl", hash = "sha256:27e569eb9d9e95dbd55c0fc3ec3a9335defbf1d8bc1d20171a49f3c4c607b93e", size = 426690, upload-time = "2025-10-28T20:56:58.736Z" }, - { url = "https://files.pythonhosted.org/packages/0a/0d/b10ac09069973d112de6ef980c1f6bb31cb7dcd0bc363acbdad58f927873/aiohttp-3.13.2-cp312-cp312-win_amd64.whl", hash = "sha256:8709a0f05d59a71f33fd05c17fc11fcb8c30140506e13c2f5e8ee1b8964e1b45", size = 453465, upload-time = "2025-10-28T20:57:00.795Z" }, + { url = "https://files.pythonhosted.org/packages/f1/4c/a164164834f03924d9a29dc3acd9e7ee58f95857e0b467f6d04298594ebb/aiohttp-3.13.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5b6073099fb654e0a068ae678b10feff95c5cae95bbfcbfa7af669d361a8aa6b", size = 746051, upload-time = "2026-01-03T17:29:43.287Z" }, + { url = "https://files.pythonhosted.org/packages/82/71/d5c31390d18d4f58115037c432b7e0348c60f6f53b727cad33172144a112/aiohttp-3.13.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cb93e166e6c28716c8c6aeb5f99dfb6d5ccf482d29fe9bf9a794110e6d0ab64", size = 499234, upload-time = "2026-01-03T17:29:44.822Z" }, + { url = "https://files.pythonhosted.org/packages/0e/c9/741f8ac91e14b1d2e7100690425a5b2b919a87a5075406582991fb7de920/aiohttp-3.13.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:28e027cf2f6b641693a09f631759b4d9ce9165099d2b5d92af9bd4e197690eea", size = 494979, upload-time = "2026-01-03T17:29:46.405Z" }, + { url = "https://files.pythonhosted.org/packages/75/b5/31d4d2e802dfd59f74ed47eba48869c1c21552c586d5e81a9d0d5c2ad640/aiohttp-3.13.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3b61b7169ababd7802f9568ed96142616a9118dd2be0d1866e920e77ec8fa92a", size = 1748297, upload-time = "2026-01-03T17:29:48.083Z" }, + { url = "https://files.pythonhosted.org/packages/1a/3e/eefad0ad42959f226bb79664826883f2687d602a9ae2941a18e0484a74d3/aiohttp-3.13.3-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:80dd4c21b0f6237676449c6baaa1039abae86b91636b6c91a7f8e61c87f89540", size = 1707172, upload-time = "2026-01-03T17:29:49.648Z" }, + { url = "https://files.pythonhosted.org/packages/c5/3a/54a64299fac2891c346cdcf2aa6803f994a2e4beeaf2e5a09dcc54acc842/aiohttp-3.13.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:65d2ccb7eabee90ce0503c17716fc77226be026dcc3e65cce859a30db715025b", size = 1805405, upload-time = "2026-01-03T17:29:51.244Z" }, + { url = "https://files.pythonhosted.org/packages/6c/70/ddc1b7169cf64075e864f64595a14b147a895a868394a48f6a8031979038/aiohttp-3.13.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5b179331a481cb5529fca8b432d8d3c7001cb217513c94cd72d668d1248688a3", size = 1899449, upload-time = "2026-01-03T17:29:53.938Z" }, + { url = "https://files.pythonhosted.org/packages/a1/7e/6815aab7d3a56610891c76ef79095677b8b5be6646aaf00f69b221765021/aiohttp-3.13.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d4c940f02f49483b18b079d1c27ab948721852b281f8b015c058100e9421dd1", size = 1748444, upload-time = "2026-01-03T17:29:55.484Z" }, + { url = "https://files.pythonhosted.org/packages/6b/f2/073b145c4100da5511f457dc0f7558e99b2987cf72600d42b559db856fbc/aiohttp-3.13.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f9444f105664c4ce47a2a7171a2418bce5b7bae45fb610f4e2c36045d85911d3", size = 1606038, upload-time = "2026-01-03T17:29:57.179Z" }, + { url = "https://files.pythonhosted.org/packages/0a/c1/778d011920cae03ae01424ec202c513dc69243cf2db303965615b81deeea/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:694976222c711d1d00ba131904beb60534f93966562f64440d0c9d41b8cdb440", size = 1724156, upload-time = "2026-01-03T17:29:58.914Z" }, + { url = "https://files.pythonhosted.org/packages/0e/cb/3419eabf4ec1e9ec6f242c32b689248365a1cf621891f6f0386632525494/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f33ed1a2bf1997a36661874b017f5c4b760f41266341af36febaf271d179f6d7", size = 1722340, upload-time = "2026-01-03T17:30:01.962Z" }, + { url = "https://files.pythonhosted.org/packages/7a/e5/76cf77bdbc435bf233c1f114edad39ed4177ccbfab7c329482b179cff4f4/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e636b3c5f61da31a92bf0d91da83e58fdfa96f178ba682f11d24f31944cdd28c", size = 1783041, upload-time = "2026-01-03T17:30:03.609Z" }, + { url = "https://files.pythonhosted.org/packages/9d/d4/dd1ca234c794fd29c057ce8c0566b8ef7fd6a51069de5f06fa84b9a1971c/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:5d2d94f1f5fcbe40838ac51a6ab5704a6f9ea42e72ceda48de5e6b898521da51", size = 1596024, upload-time = "2026-01-03T17:30:05.132Z" }, + { url = "https://files.pythonhosted.org/packages/55/58/4345b5f26661a6180afa686c473620c30a66afdf120ed3dd545bbc809e85/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2be0e9ccf23e8a94f6f0650ce06042cefc6ac703d0d7ab6c7a917289f2539ad4", size = 1804590, upload-time = "2026-01-03T17:30:07.135Z" }, + { url = "https://files.pythonhosted.org/packages/7b/06/05950619af6c2df7e0a431d889ba2813c9f0129cec76f663e547a5ad56f2/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9af5e68ee47d6534d36791bbe9b646d2a7c7deb6fc24d7943628edfbb3581f29", size = 1740355, upload-time = "2026-01-03T17:30:09.083Z" }, + { url = "https://files.pythonhosted.org/packages/3e/80/958f16de79ba0422d7c1e284b2abd0c84bc03394fbe631d0a39ffa10e1eb/aiohttp-3.13.3-cp311-cp311-win32.whl", hash = "sha256:a2212ad43c0833a873d0fb3c63fa1bacedd4cf6af2fee62bf4b739ceec3ab239", size = 433701, upload-time = "2026-01-03T17:30:10.869Z" }, + { url = "https://files.pythonhosted.org/packages/dc/f2/27cdf04c9851712d6c1b99df6821a6623c3c9e55956d4b1e318c337b5a48/aiohttp-3.13.3-cp311-cp311-win_amd64.whl", hash = "sha256:642f752c3eb117b105acbd87e2c143de710987e09860d674e068c4c2c441034f", size = 457678, upload-time = "2026-01-03T17:30:12.719Z" }, + { url = "https://files.pythonhosted.org/packages/a0/be/4fc11f202955a69e0db803a12a062b8379c970c7c84f4882b6da17337cc1/aiohttp-3.13.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b903a4dfee7d347e2d87697d0713be59e0b87925be030c9178c5faa58ea58d5c", size = 739732, upload-time = "2026-01-03T17:30:14.23Z" }, + { url = "https://files.pythonhosted.org/packages/97/2c/621d5b851f94fa0bb7430d6089b3aa970a9d9b75196bc93bb624b0db237a/aiohttp-3.13.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a45530014d7a1e09f4a55f4f43097ba0fd155089372e105e4bff4ca76cb1b168", size = 494293, upload-time = "2026-01-03T17:30:15.96Z" }, + { url = "https://files.pythonhosted.org/packages/5d/43/4be01406b78e1be8320bb8316dc9c42dbab553d281c40364e0f862d5661c/aiohttp-3.13.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:27234ef6d85c914f9efeb77ff616dbf4ad2380be0cda40b4db086ffc7ddd1b7d", size = 493533, upload-time = "2026-01-03T17:30:17.431Z" }, + { url = "https://files.pythonhosted.org/packages/8d/a8/5a35dc56a06a2c90d4742cbf35294396907027f80eea696637945a106f25/aiohttp-3.13.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d32764c6c9aafb7fb55366a224756387cd50bfa720f32b88e0e6fa45b27dcf29", size = 1737839, upload-time = "2026-01-03T17:30:19.422Z" }, + { url = "https://files.pythonhosted.org/packages/bf/62/4b9eeb331da56530bf2e198a297e5303e1c1ebdceeb00fe9b568a65c5a0c/aiohttp-3.13.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b1a6102b4d3ebc07dad44fbf07b45bb600300f15b552ddf1851b5390202ea2e3", size = 1703932, upload-time = "2026-01-03T17:30:21.756Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f6/af16887b5d419e6a367095994c0b1332d154f647e7dc2bd50e61876e8e3d/aiohttp-3.13.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c014c7ea7fb775dd015b2d3137378b7be0249a448a1612268b5a90c2d81de04d", size = 1771906, upload-time = "2026-01-03T17:30:23.932Z" }, + { url = "https://files.pythonhosted.org/packages/ce/83/397c634b1bcc24292fa1e0c7822800f9f6569e32934bdeef09dae7992dfb/aiohttp-3.13.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2b8d8ddba8f95ba17582226f80e2de99c7a7948e66490ef8d947e272a93e9463", size = 1871020, upload-time = "2026-01-03T17:30:26Z" }, + { url = "https://files.pythonhosted.org/packages/86/f6/a62cbbf13f0ac80a70f71b1672feba90fdb21fd7abd8dbf25c0105fb6fa3/aiohttp-3.13.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ae8dd55c8e6c4257eae3a20fd2c8f41edaea5992ed67156642493b8daf3cecc", size = 1755181, upload-time = "2026-01-03T17:30:27.554Z" }, + { url = "https://files.pythonhosted.org/packages/0a/87/20a35ad487efdd3fba93d5843efdfaa62d2f1479eaafa7453398a44faf13/aiohttp-3.13.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:01ad2529d4b5035578f5081606a465f3b814c542882804e2e8cda61adf5c71bf", size = 1561794, upload-time = "2026-01-03T17:30:29.254Z" }, + { url = "https://files.pythonhosted.org/packages/de/95/8fd69a66682012f6716e1bc09ef8a1a2a91922c5725cb904689f112309c4/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bb4f7475e359992b580559e008c598091c45b5088f28614e855e42d39c2f1033", size = 1697900, upload-time = "2026-01-03T17:30:31.033Z" }, + { url = "https://files.pythonhosted.org/packages/e5/66/7b94b3b5ba70e955ff597672dad1691333080e37f50280178967aff68657/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c19b90316ad3b24c69cd78d5c9b4f3aa4497643685901185b65166293d36a00f", size = 1728239, upload-time = "2026-01-03T17:30:32.703Z" }, + { url = "https://files.pythonhosted.org/packages/47/71/6f72f77f9f7d74719692ab65a2a0252584bf8d5f301e2ecb4c0da734530a/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:96d604498a7c782cb15a51c406acaea70d8c027ee6b90c569baa6e7b93073679", size = 1740527, upload-time = "2026-01-03T17:30:34.695Z" }, + { url = "https://files.pythonhosted.org/packages/fa/b4/75ec16cbbd5c01bdaf4a05b19e103e78d7ce1ef7c80867eb0ace42ff4488/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:084911a532763e9d3dd95adf78a78f4096cd5f58cdc18e6fdbc1b58417a45423", size = 1554489, upload-time = "2026-01-03T17:30:36.864Z" }, + { url = "https://files.pythonhosted.org/packages/52/8f/bc518c0eea29f8406dcf7ed1f96c9b48e3bc3995a96159b3fc11f9e08321/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7a4a94eb787e606d0a09404b9c38c113d3b099d508021faa615d70a0131907ce", size = 1767852, upload-time = "2026-01-03T17:30:39.433Z" }, + { url = "https://files.pythonhosted.org/packages/9d/f2/a07a75173124f31f11ea6f863dc44e6f09afe2bca45dd4e64979490deab1/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:87797e645d9d8e222e04160ee32aa06bc5c163e8499f24db719e7852ec23093a", size = 1722379, upload-time = "2026-01-03T17:30:41.081Z" }, + { url = "https://files.pythonhosted.org/packages/3c/4a/1a3fee7c21350cac78e5c5cef711bac1b94feca07399f3d406972e2d8fcd/aiohttp-3.13.3-cp312-cp312-win32.whl", hash = "sha256:b04be762396457bef43f3597c991e192ee7da460a4953d7e647ee4b1c28e7046", size = 428253, upload-time = "2026-01-03T17:30:42.644Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b7/76175c7cb4eb73d91ad63c34e29fc4f77c9386bba4a65b53ba8e05ee3c39/aiohttp-3.13.3-cp312-cp312-win_amd64.whl", hash = "sha256:e3531d63d3bdfa7e3ac5e9b27b2dd7ec9df3206a98e0b3445fa906f233264c57", size = 455407, upload-time = "2026-01-03T17:30:44.195Z" }, ] [[package]] @@ -152,15 +148,15 @@ wheels = [ [[package]] name = "azure-core" -version = "1.37.0" +version = "1.38.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ef/83/41c9371c8298999c67b007e308a0a3c4d6a59c6908fa9c62101f031f886f/azure_core-1.37.0.tar.gz", hash = "sha256:7064f2c11e4b97f340e8e8c6d923b822978be3016e46b7bc4aa4b337cfb48aee", size = 357620, upload-time = "2025-12-11T20:05:13.518Z" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/1b/e503e08e755ea94e7d3419c9242315f888fc664211c90d032e40479022bf/azure_core-1.38.0.tar.gz", hash = "sha256:8194d2682245a3e4e3151a667c686464c3786fed7918b394d035bdcd61bb5993", size = 363033, upload-time = "2026-01-12T17:03:05.535Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ee/34/a9914e676971a13d6cc671b1ed172f9804b50a3a80a143ff196e52f4c7ee/azure_core-1.37.0-py3-none-any.whl", hash = "sha256:b3abe2c59e7d6bb18b38c275a5029ff80f98990e7c90a5e646249a56630fcc19", size = 214006, upload-time = "2025-12-11T20:05:14.96Z" }, + { url = "https://files.pythonhosted.org/packages/fc/d8/b8fcba9464f02b121f39de2db2bf57f0b216fe11d014513d666e8634380d/azure_core-1.38.0-py3-none-any.whl", hash = "sha256:ab0c9b2cd71fecb1842d52c965c95285d3cfb38902f6766e4a471f1cd8905335", size = 217825, upload-time = "2026-01-12T17:03:07.291Z" }, ] [[package]] @@ -181,7 +177,7 @@ wheels = [ [[package]] name = "azure-storage-blob" -version = "12.27.1" +version = "12.28.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "azure-core" }, @@ -189,9 +185,9 @@ dependencies = [ { name = "isodate" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/36/7c/2fd872e11a88163f208b9c92de273bf64bb22d0eef9048cc6284d128a77a/azure_storage_blob-12.27.1.tar.gz", hash = "sha256:a1596cc4daf5dac9be115fcb5db67245eae894cf40e4248243754261f7b674a6", size = 597579, upload-time = "2025-10-29T12:27:16.185Z" } +sdist = { url = "https://files.pythonhosted.org/packages/71/24/072ba8e27b0e2d8fec401e9969b429d4f5fc4c8d4f0f05f4661e11f7234a/azure_storage_blob-12.28.0.tar.gz", hash = "sha256:e7d98ea108258d29aa0efbfd591b2e2075fa1722a2fae8699f0b3c9de11eff41", size = 604225, upload-time = "2026-01-06T23:48:57.282Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3d/9e/1c90a122ea6180e8c72eb7294adc92531b0e08eb3d2324c2ba70d37f4802/azure_storage_blob-12.27.1-py3-none-any.whl", hash = "sha256:65d1e25a4628b7b6acd20ff7902d8da5b4fde8e46e19c8f6d213a3abc3ece272", size = 428954, upload-time = "2025-10-29T12:27:18.072Z" }, + { url = "https://files.pythonhosted.org/packages/d8/3a/6ef2047a072e54e1142718d433d50e9514c999a58f51abfff7902f3a72f8/azure_storage_blob-12.28.0-py3-none-any.whl", hash = "sha256:00fb1db28bf6a7b7ecaa48e3b1d5c83bfadacc5a678b77826081304bd87d6461", size = 431499, upload-time = "2026-01-06T23:48:58.995Z" }, ] [[package]] @@ -219,11 +215,11 @@ wheels = [ [[package]] name = "certifi" -version = "2025.11.12" +version = "2026.1.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/8c/58f469717fa48465e4a50c014a0400602d3c437d7c0c468e17ada824da3a/certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316", size = 160538, upload-time = "2025-11-12T02:54:51.517Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/2d/a891ca51311197f6ad14a7ef42e2399f36cf2f9bd44752b3dc4eab60fdc5/certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120", size = 154268, upload-time = "2026-01-04T02:42:41.825Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", size = 159438, upload-time = "2025-11-12T02:54:49.735Z" }, + { url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900, upload-time = "2026-01-04T02:42:40.15Z" }, ] [[package]] @@ -475,28 +471,28 @@ wheels = [ [[package]] name = "cython" -version = "3.2.3" +version = "3.2.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/39/e1/c0d92b1258722e1bc62a12e630c33f1f842fdab53fd8cd5de2f75c6449a9/cython-3.2.3.tar.gz", hash = "sha256:f13832412d633376ffc08d751cc18ed0d7d00a398a4065e2871db505258748a6", size = 3276650, upload-time = "2025-12-14T07:50:34.691Z" } +sdist = { url = "https://files.pythonhosted.org/packages/91/85/7574c9cd44b69a27210444b6650f6477f56c75fee1b70d7672d3e4166167/cython-3.2.4.tar.gz", hash = "sha256:84226ecd313b233da27dc2eb3601b4f222b8209c3a7216d8733b031da1dc64e6", size = 3280291, upload-time = "2026-01-04T14:14:14.473Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c3/85/77315c92d29d782bee1b36e30b8d76ad1e731cb7ea0af17e285885f3bb68/cython-3.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c041f7e338cca2422e0924716b04fabeda57636214324fc1941396acce99e7c7", size = 2951618, upload-time = "2025-12-14T07:50:53.883Z" }, - { url = "https://files.pythonhosted.org/packages/cb/dd/a8209e0d424a0207ddb4a3097a97b667027af3cfada762d85f3bed08ccf8/cython-3.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:283262b8f902323ceb6ed3b643f275a2a963e7ab059f0714a467933383cbc56d", size = 3243636, upload-time = "2025-12-14T07:50:56.346Z" }, - { url = "https://files.pythonhosted.org/packages/1f/2d/bc1927fd7174f7928b86cc9b83589d39592b9273c8b1d2295ca0c0071984/cython-3.2.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22a624290c2883387b2c2cfb5224c15bff21432c6a2cf0c23ac8df3dcbd45e96", size = 3378528, upload-time = "2025-12-14T07:50:57.988Z" }, - { url = "https://files.pythonhosted.org/packages/ad/10/5add6a6e1721f9c36b5d5b4f3b75fa7af43196e4f2a474921a7277e31b7a/cython-3.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:26404441f733fd1cfb0dd9c45477f501437e7d51fad05bb402bd2feb4e127aa3", size = 2769341, upload-time = "2025-12-14T07:50:59.581Z" }, - { url = "https://files.pythonhosted.org/packages/b4/14/d16282d17c9eb2f78ca9ccd5801fed22f6c3360f5a55dbcce3c93cc70352/cython-3.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cf210228c15b5c625824d8e31d43b6fea25f9e13c81dac632f2f7d838e0229a5", size = 2968471, upload-time = "2025-12-14T07:51:01.207Z" }, - { url = "https://files.pythonhosted.org/packages/d0/3c/46304a942dac5a636701c55f5b05ec00ad151e6722cd068fe3d0993349bb/cython-3.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f5bf0cebeb4147e172a114437d3fce5a507595d8fdd821be792b1bb25c691514", size = 3223581, upload-time = "2025-12-14T07:51:04.336Z" }, - { url = "https://files.pythonhosted.org/packages/29/ad/15da606d71f40bcf2c405f84ca3d4195cb252f4eaa2f551fe6b2e630ee7c/cython-3.2.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d1f8700ba89c977438744f083890d87187f15709507a5489e0f6d682053b7fa0", size = 3391391, upload-time = "2025-12-14T07:51:05.998Z" }, - { url = "https://files.pythonhosted.org/packages/51/9e/045b35eb678682edc3e2d57112cf5ac3581a9ef274eb220b638279195678/cython-3.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:25732f3981a93407826297f4423206e5e22c3cfccfc74e37bf444453bbdc076f", size = 2756814, upload-time = "2025-12-14T07:51:07.759Z" }, - { url = "https://files.pythonhosted.org/packages/43/49/afe1e3df87a770861cf17ba39f4a91f6d22a2571010fc1890b3708360630/cython-3.2.3-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:74f482da8b605c61b4df6ff716d013f20131949cb2fa59b03e63abd36ef5bac0", size = 2874467, upload-time = "2025-12-14T07:51:31.568Z" }, - { url = "https://files.pythonhosted.org/packages/c7/da/044f725a083e28fb4de5bd33d13ec13f0753734b6ae52d4bc07434610cc8/cython-3.2.3-cp39-abi3-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0a75a04688875b275a6c875565e672325bae04327dd6ec2fc25aeb5c6cf82fce", size = 3211272, upload-time = "2025-12-14T07:51:33.673Z" }, - { url = "https://files.pythonhosted.org/packages/95/14/af02ba6e2e03279f2ca2956e3024a44faed4c8496bda8170b663dc3ba6e8/cython-3.2.3-cp39-abi3-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6b01b36c9eb1b68c25bddbeef7379f7bfc37f7c9afc044e71840ffab761a2dd0", size = 2856058, upload-time = "2025-12-14T07:51:36.015Z" }, - { url = "https://files.pythonhosted.org/packages/69/16/d254359396c2f099ab154f89b2b35f5b8b0dd21a8102c2c96a7e00291434/cython-3.2.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3829f99d611412288f44ff543e9d2b5c0c83274998b2a6680bbe5cca3539c1fd", size = 2993276, upload-time = "2025-12-14T07:51:37.863Z" }, - { url = "https://files.pythonhosted.org/packages/51/0e/1a071381923e896f751f8fbff2a01c5dc8860a8b9a90066f6ec8df561dc4/cython-3.2.3-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:c2365a0c79ab9c0fa86d30a4a6ba7e37fc1be9537c48b79b9d63ee7e08bf2fef", size = 2890843, upload-time = "2025-12-14T07:51:40.409Z" }, - { url = "https://files.pythonhosted.org/packages/f4/46/1e93e10766db988e6bb8e5c6f7e2e90b9e62f1ac8dee4c1a6cf1fc170773/cython-3.2.3-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:3141734fb15f8b5e9402b9240f8da8336edecae91742b41c85678c31ab68f66d", size = 3225339, upload-time = "2025-12-14T07:51:42.09Z" }, - { url = "https://files.pythonhosted.org/packages/d4/ae/c284b06ae6a9c95d5883bf8744d10466cf0df64cef041a4c80ccf9fd07bd/cython-3.2.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9a24cc653fad3adbd9cbaa638d80df3aa08a1fe27f62eb35850971c70be680df", size = 3114751, upload-time = "2025-12-14T07:51:44.088Z" }, - { url = "https://files.pythonhosted.org/packages/c6/d6/7795a4775c70256217134195f06b07233cf17b00f8905d5b3d782208af64/cython-3.2.3-cp39-abi3-win32.whl", hash = "sha256:b39dff92db70cbd95528f3b81d70e06bd6d3fc9c1dd91321e4d3b999ece3bceb", size = 2435616, upload-time = "2025-12-14T07:51:46.063Z" }, - { url = "https://files.pythonhosted.org/packages/18/9e/2a3edcb858ad74e6274448dccf32150c532bc6e423f112a71f65ff3b5680/cython-3.2.3-cp39-abi3-win_arm64.whl", hash = "sha256:18edc858e6a52de47fe03ffa97ea14dadf450e20069de0a8aef531006c4bbd93", size = 2440952, upload-time = "2025-12-14T07:51:47.943Z" }, - { url = "https://files.pythonhosted.org/packages/e5/41/54fd429ff8147475fc24ca43246f85d78fb4e747c27f227e68f1594648f1/cython-3.2.3-py3-none-any.whl", hash = "sha256:06a1317097f540d3bb6c7b81ed58a0d8b9dbfa97abf39dfd4c22ee87a6c7241e", size = 1255561, upload-time = "2025-12-14T07:50:31.217Z" }, + { url = "https://files.pythonhosted.org/packages/85/cc/8f06145ec3efa121c8b1b67f06a640386ddacd77ee3e574da582a21b14ee/cython-3.2.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ff9af2134c05e3734064808db95b4dd7341a39af06e8945d05ea358e1741aaed", size = 2953769, upload-time = "2026-01-04T14:15:00.361Z" }, + { url = "https://files.pythonhosted.org/packages/55/b0/706cf830eddd831666208af1b3058c2e0758ae157590909c1f634b53bed9/cython-3.2.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:67922c9de058a0bfb72d2e75222c52d09395614108c68a76d9800f150296ddb3", size = 3243841, upload-time = "2026-01-04T14:15:02.066Z" }, + { url = "https://files.pythonhosted.org/packages/ac/25/58893afd4ef45f79e3d4db82742fa4ff874b936d67a83c92939053920ccd/cython-3.2.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b362819d155fff1482575e804e43e3a8825332d32baa15245f4642022664a3f4", size = 3378083, upload-time = "2026-01-04T14:15:04.248Z" }, + { url = "https://files.pythonhosted.org/packages/32/e4/424a004d7c0d8a4050c81846ebbd22272ececfa9a498cb340aa44fccbec2/cython-3.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:1a64a112a34ec719b47c01395647e54fb4cf088a511613f9a3a5196694e8e382", size = 2769990, upload-time = "2026-01-04T14:15:06.53Z" }, + { url = "https://files.pythonhosted.org/packages/91/4d/1eb0c7c196a136b1926f4d7f0492a96c6fabd604d77e6cd43b56a3a16d83/cython-3.2.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:64d7f71be3dd6d6d4a4c575bb3a4674ea06d1e1e5e4cd1b9882a2bc40ed3c4c9", size = 2970064, upload-time = "2026-01-04T14:15:08.567Z" }, + { url = "https://files.pythonhosted.org/packages/03/1c/46e34b08bea19a1cdd1e938a4c123e6299241074642db9d81983cef95e9f/cython-3.2.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:869487ea41d004f8b92171f42271fbfadb1ec03bede3158705d16cd570d6b891", size = 3226757, upload-time = "2026-01-04T14:15:10.812Z" }, + { url = "https://files.pythonhosted.org/packages/12/33/3298a44d201c45bcf0d769659725ae70e9c6c42adf8032f6d89c8241098d/cython-3.2.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:55b6c44cd30821f0b25220ceba6fe636ede48981d2a41b9bbfe3c7902ce44ea7", size = 3388969, upload-time = "2026-01-04T14:15:12.45Z" }, + { url = "https://files.pythonhosted.org/packages/bb/f3/4275cd3ea0a4cf4606f9b92e7f8766478192010b95a7f516d1b7cf22cb10/cython-3.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:767b143704bdd08a563153448955935844e53b852e54afdc552b43902ed1e235", size = 2756457, upload-time = "2026-01-04T14:15:14.67Z" }, + { url = "https://files.pythonhosted.org/packages/0a/8b/fd393f0923c82be4ec0db712fffb2ff0a7a131707b842c99bf24b549274d/cython-3.2.4-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:36bf3f5eb56d5281aafabecbaa6ed288bc11db87547bba4e1e52943ae6961ccf", size = 2875622, upload-time = "2026-01-04T14:15:39.749Z" }, + { url = "https://files.pythonhosted.org/packages/73/48/48530d9b9d64ec11dbe0dd3178a5fe1e0b27977c1054ecffb82be81e9b6a/cython-3.2.4-cp39-abi3-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6d5267f22b6451eb1e2e1b88f6f78a2c9c8733a6ddefd4520d3968d26b824581", size = 3210669, upload-time = "2026-01-04T14:15:41.911Z" }, + { url = "https://files.pythonhosted.org/packages/5e/91/4865fbfef1f6bb4f21d79c46104a53d1a3fa4348286237e15eafb26e0828/cython-3.2.4-cp39-abi3-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3b6e58f73a69230218d5381817850ce6d0da5bb7e87eb7d528c7027cbba40b06", size = 2856835, upload-time = "2026-01-04T14:15:43.815Z" }, + { url = "https://files.pythonhosted.org/packages/fa/39/60317957dbef179572398253f29d28f75f94ab82d6d39ea3237fb6c89268/cython-3.2.4-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e71efb20048358a6b8ec604a0532961c50c067b5e63e345e2e359fff72feaee8", size = 2994408, upload-time = "2026-01-04T14:15:45.422Z" }, + { url = "https://files.pythonhosted.org/packages/8d/30/7c24d9292650db4abebce98abc9b49c820d40fa7c87921c0a84c32f4efe7/cython-3.2.4-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:28b1e363b024c4b8dcf52ff68125e635cb9cb4b0ba997d628f25e32543a71103", size = 2891478, upload-time = "2026-01-04T14:15:47.394Z" }, + { url = "https://files.pythonhosted.org/packages/86/70/03dc3c962cde9da37a93cca8360e576f904d5f9beecfc9d70b1f820d2e5f/cython-3.2.4-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:31a90b4a2c47bb6d56baeb926948348ec968e932c1ae2c53239164e3e8880ccf", size = 3225663, upload-time = "2026-01-04T14:15:49.446Z" }, + { url = "https://files.pythonhosted.org/packages/b1/97/10b50c38313c37b1300325e2e53f48ea9a2c078a85c0c9572057135e31d5/cython-3.2.4-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e65e4773021f8dc8532010b4fbebe782c77f9a0817e93886e518c93bd6a44e9d", size = 3115628, upload-time = "2026-01-04T14:15:51.323Z" }, + { url = "https://files.pythonhosted.org/packages/8f/b1/d6a353c9b147848122a0db370863601fdf56de2d983b5c4a6a11e6ee3cd7/cython-3.2.4-cp39-abi3-win32.whl", hash = "sha256:2b1f12c0e4798293d2754e73cd6f35fa5bbdf072bdc14bc6fc442c059ef2d290", size = 2437463, upload-time = "2026-01-04T14:15:53.787Z" }, + { url = "https://files.pythonhosted.org/packages/2d/d8/319a1263b9c33b71343adfd407e5daffd453daef47ebc7b642820a8b68ed/cython-3.2.4-cp39-abi3-win_arm64.whl", hash = "sha256:3b8e62049afef9da931d55de82d8f46c9a147313b69d5ff6af6e9121d545ce7a", size = 2442754, upload-time = "2026-01-04T14:15:55.382Z" }, + { url = "https://files.pythonhosted.org/packages/ff/fa/d3c15189f7c52aaefbaea76fb012119b04b9013f4bf446cb4eb4c26c4e6b/cython-3.2.4-py3-none-any.whl", hash = "sha256:732fc93bc33ae4b14f6afaca663b916c2fdd5dcbfad7114e17fb2434eeaea45c", size = 1257078, upload-time = "2026-01-04T14:14:12.373Z" }, ] [[package]] @@ -538,7 +534,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" }, @@ -564,11 +560,11 @@ wheels = [ [[package]] name = "filelock" -version = "3.20.1" +version = "3.20.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a7/23/ce7a1126827cedeb958fc043d61745754464eb56c5937c35bbf2b8e26f34/filelock-3.20.1.tar.gz", hash = "sha256:b8360948b351b80f420878d8516519a2204b07aefcdcfd24912a5d33127f188c", size = 19476, upload-time = "2025-12-15T23:54:28.027Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/65/ce7f1b70157833bf3cb851b556a37d4547ceafc158aa9b34b36782f23696/filelock-3.20.3.tar.gz", hash = "sha256:18c57ee915c7ec61cff0ecf7f0f869936c7c30191bb0cf406f1341778d0834e1", size = 19485, upload-time = "2026-01-09T17:55:05.421Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e3/7f/a1a97644e39e7316d850784c642093c99df1290a460df4ede27659056834/filelock-3.20.1-py3-none-any.whl", hash = "sha256:15d9e9a67306188a44baa72f569d2bfd803076269365fdea0934385da4dc361a", size = 16666, upload-time = "2025-12-15T23:54:26.874Z" }, + { url = "https://files.pythonhosted.org/packages/b5/36/7fb70f04bf00bc646cd5bb45aa9eddb15e19437a28b8fb2b4a5249fac770/filelock-3.20.3-py3-none-any.whl", hash = "sha256:4b0dda527ee31078689fc205ec4f1c1bf7d56cf88b6dc9426c4f230e46c2dce1", size = 16701, upload-time = "2026-01-09T17:55:04.334Z" }, ] [[package]] @@ -683,10 +679,10 @@ name = "gymnasium" version = "1.2.3" 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/76/59/653a9417d98ed3e29ef9734ba52c3495f6c6823b8d5c0c75369f25111708/gymnasium-1.2.3.tar.gz", hash = "sha256:2b2cb5b5fbbbdf3afb9f38ca952cc48aa6aa3e26561400d940747fda3ad42509", size = 829230, upload-time = "2025-12-18T16:51:10.234Z" } wheels = [ @@ -1006,22 +1002,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:d0afaf3b005e35e14b929d5491d2d5b64562d0c1cd5093ba969fb63908670dd4" }, @@ -1216,44 +1212,44 @@ wheels = [ [[package]] name = "numpy" -version = "2.4.0" +version = "2.4.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a4/7a/6a3d14e205d292b738db449d0de649b373a59edb0d0b4493821d0a3e8718/numpy-2.4.0.tar.gz", hash = "sha256:6e504f7b16118198f138ef31ba24d985b124c2c469fe8467007cf30fd992f934", size = 20685720, upload-time = "2025-12-20T16:18:19.023Z" } +sdist = { url = "https://files.pythonhosted.org/packages/24/62/ae72ff66c0f1fd959925b4c11f8c2dea61f47f6acaea75a08512cdfe3fed/numpy-2.4.1.tar.gz", hash = "sha256:a1ceafc5042451a858231588a104093474c6a5c57dcc724841f5c888d237d690", size = 20721320, upload-time = "2026-01-10T06:44:59.619Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/26/7e/7bae7cbcc2f8132271967aa03e03954fc1e48aa1f3bf32b29ca95fbef352/numpy-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:316b2f2584682318539f0bcaca5a496ce9ca78c88066579ebd11fd06f8e4741e", size = 16940166, upload-time = "2025-12-20T16:15:43.434Z" }, - { url = "https://files.pythonhosted.org/packages/0f/27/6c13f5b46776d6246ec884ac5817452672156a506d08a1f2abb39961930a/numpy-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2718c1de8504121714234b6f8241d0019450353276c88b9453c9c3d92e101db", size = 12641781, upload-time = "2025-12-20T16:15:45.701Z" }, - { url = "https://files.pythonhosted.org/packages/14/1c/83b4998d4860d15283241d9e5215f28b40ac31f497c04b12fa7f428ff370/numpy-2.4.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:21555da4ec4a0c942520ead42c3b0dc9477441e085c42b0fbdd6a084869a6f6b", size = 5470247, upload-time = "2025-12-20T16:15:47.943Z" }, - { url = "https://files.pythonhosted.org/packages/54/08/cbce72c835d937795571b0464b52069f869c9e78b0c076d416c5269d2718/numpy-2.4.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:413aa561266a4be2d06cd2b9665e89d9f54c543f418773076a76adcf2af08bc7", size = 6799807, upload-time = "2025-12-20T16:15:49.795Z" }, - { url = "https://files.pythonhosted.org/packages/ff/be/2e647961cd8c980591d75cdcd9e8f647d69fbe05e2a25613dc0a2ea5fb1a/numpy-2.4.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0feafc9e03128074689183031181fac0897ff169692d8492066e949041096548", size = 14701992, upload-time = "2025-12-20T16:15:51.615Z" }, - { url = "https://files.pythonhosted.org/packages/a2/fb/e1652fb8b6fd91ce6ed429143fe2e01ce714711e03e5b762615e7b36172c/numpy-2.4.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8fdfed3deaf1928fb7667d96e0567cdf58c2b370ea2ee7e586aa383ec2cb346", size = 16646871, upload-time = "2025-12-20T16:15:54.129Z" }, - { url = "https://files.pythonhosted.org/packages/62/23/d841207e63c4322842f7cd042ae981cffe715c73376dcad8235fb31debf1/numpy-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e06a922a469cae9a57100864caf4f8a97a1026513793969f8ba5b63137a35d25", size = 16487190, upload-time = "2025-12-20T16:15:56.147Z" }, - { url = "https://files.pythonhosted.org/packages/bc/a0/6a842c8421ebfdec0a230e65f61e0dabda6edbef443d999d79b87c273965/numpy-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:927ccf5cd17c48f801f4ed43a7e5673a2724bd2171460be3e3894e6e332ef83a", size = 18580762, upload-time = "2025-12-20T16:15:58.524Z" }, - { url = "https://files.pythonhosted.org/packages/0a/d1/c79e0046641186f2134dde05e6181825b911f8bdcef31b19ddd16e232847/numpy-2.4.0-cp311-cp311-win32.whl", hash = "sha256:882567b7ae57c1b1a0250208cc21a7976d8cbcc49d5a322e607e6f09c9e0bd53", size = 6233359, upload-time = "2025-12-20T16:16:00.938Z" }, - { url = "https://files.pythonhosted.org/packages/fc/f0/74965001d231f28184d6305b8cdc1b6fcd4bf23033f6cb039cfe76c9fca7/numpy-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:8b986403023c8f3bf8f487c2e6186afda156174d31c175f747d8934dfddf3479", size = 12601132, upload-time = "2025-12-20T16:16:02.484Z" }, - { url = "https://files.pythonhosted.org/packages/65/32/55408d0f46dfebce38017f5bd931affa7256ad6beac1a92a012e1fbc67a7/numpy-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:3f3096405acc48887458bbf9f6814d43785ac7ba2a57ea6442b581dedbc60ce6", size = 10573977, upload-time = "2025-12-20T16:16:04.77Z" }, - { url = "https://files.pythonhosted.org/packages/8b/ff/f6400ffec95de41c74b8e73df32e3fff1830633193a7b1e409be7fb1bb8c/numpy-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2a8b6bb8369abefb8bd1801b054ad50e02b3275c8614dc6e5b0373c305291037", size = 16653117, upload-time = "2025-12-20T16:16:06.709Z" }, - { url = "https://files.pythonhosted.org/packages/fd/28/6c23e97450035072e8d830a3c411bf1abd1f42c611ff9d29e3d8f55c6252/numpy-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2e284ca13d5a8367e43734148622caf0b261b275673823593e3e3634a6490f83", size = 12369711, upload-time = "2025-12-20T16:16:08.758Z" }, - { url = "https://files.pythonhosted.org/packages/bc/af/acbef97b630ab1bb45e6a7d01d1452e4251aa88ce680ac36e56c272120ec/numpy-2.4.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:49ff32b09f5aa0cd30a20c2b39db3e669c845589f2b7fc910365210887e39344", size = 5198355, upload-time = "2025-12-20T16:16:10.902Z" }, - { url = "https://files.pythonhosted.org/packages/c1/c8/4e0d436b66b826f2e53330adaa6311f5cac9871a5b5c31ad773b27f25a74/numpy-2.4.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:36cbfb13c152b1c7c184ddac43765db8ad672567e7bafff2cc755a09917ed2e6", size = 6545298, upload-time = "2025-12-20T16:16:12.607Z" }, - { url = "https://files.pythonhosted.org/packages/ef/27/e1f5d144ab54eac34875e79037011d511ac57b21b220063310cb96c80fbc/numpy-2.4.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:35ddc8f4914466e6fc954c76527aa91aa763682a4f6d73249ef20b418fe6effb", size = 14398387, upload-time = "2025-12-20T16:16:14.257Z" }, - { url = "https://files.pythonhosted.org/packages/67/64/4cb909dd5ab09a9a5d086eff9586e69e827b88a5585517386879474f4cf7/numpy-2.4.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc578891de1db95b2a35001b695451767b580bb45753717498213c5ff3c41d63", size = 16363091, upload-time = "2025-12-20T16:16:17.32Z" }, - { url = "https://files.pythonhosted.org/packages/9d/9c/8efe24577523ec6809261859737cf117b0eb6fdb655abdfdc81b2e468ce4/numpy-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:98e81648e0b36e325ab67e46b5400a7a6d4a22b8a7c8e8bbfe20e7db7906bf95", size = 16176394, upload-time = "2025-12-20T16:16:19.524Z" }, - { url = "https://files.pythonhosted.org/packages/61/f0/1687441ece7b47a62e45a1f82015352c240765c707928edd8aef875d5951/numpy-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d57b5046c120561ba8fa8e4030fbb8b822f3063910fa901ffadf16e2b7128ad6", size = 18287378, upload-time = "2025-12-20T16:16:22.866Z" }, - { url = "https://files.pythonhosted.org/packages/d3/6f/f868765d44e6fc466467ed810ba9d8d6db1add7d4a748abfa2a4c99a3194/numpy-2.4.0-cp312-cp312-win32.whl", hash = "sha256:92190db305a6f48734d3982f2c60fa30d6b5ee9bff10f2887b930d7b40119f4c", size = 5955432, upload-time = "2025-12-20T16:16:25.06Z" }, - { url = "https://files.pythonhosted.org/packages/d4/b5/94c1e79fcbab38d1ca15e13777477b2914dd2d559b410f96949d6637b085/numpy-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:680060061adb2d74ce352628cb798cfdec399068aa7f07ba9fb818b2b3305f98", size = 12306201, upload-time = "2025-12-20T16:16:26.979Z" }, - { url = "https://files.pythonhosted.org/packages/70/09/c39dadf0b13bb0768cd29d6a3aaff1fb7c6905ac40e9aaeca26b1c086e06/numpy-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:39699233bc72dd482da1415dcb06076e32f60eddc796a796c5fb6c5efce94667", size = 10308234, upload-time = "2025-12-20T16:16:29.417Z" }, - { url = "https://files.pythonhosted.org/packages/4b/ef/088e7c7342f300aaf3ee5f2c821c4b9996a1bef2aaf6a49cc8ab4883758e/numpy-2.4.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b54c83f1c0c0f1d748dca0af516062b8829d53d1f0c402be24b4257a9c48ada6", size = 16819003, upload-time = "2025-12-20T16:18:03.41Z" }, - { url = "https://files.pythonhosted.org/packages/ff/ce/a53017b5443b4b84517182d463fc7bcc2adb4faa8b20813f8e5f5aeb5faa/numpy-2.4.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:aabb081ca0ec5d39591fc33018cd4b3f96e1a2dd6756282029986d00a785fba4", size = 12567105, upload-time = "2025-12-20T16:18:05.594Z" }, - { url = "https://files.pythonhosted.org/packages/77/58/5ff91b161f2ec650c88a626c3905d938c89aaadabd0431e6d9c1330c83e2/numpy-2.4.0-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:8eafe7c36c8430b7794edeab3087dec7bf31d634d92f2af9949434b9d1964cba", size = 5395590, upload-time = "2025-12-20T16:18:08.031Z" }, - { url = "https://files.pythonhosted.org/packages/1d/4e/f1a084106df8c2df8132fc437e56987308e0524836aa7733721c8429d4fe/numpy-2.4.0-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:2f585f52b2baf07ff3356158d9268ea095e221371f1074fadea2f42544d58b4d", size = 6709947, upload-time = "2025-12-20T16:18:09.836Z" }, - { url = "https://files.pythonhosted.org/packages/63/09/3d8aeb809c0332c3f642da812ac2e3d74fc9252b3021f8c30c82e99e3f3d/numpy-2.4.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:32ed06d0fe9cae27d8fb5f400c63ccee72370599c75e683a6358dd3a4fb50aaf", size = 14535119, upload-time = "2025-12-20T16:18:12.105Z" }, - { url = "https://files.pythonhosted.org/packages/fd/7f/68f0fc43a2cbdc6bb239160c754d87c922f60fbaa0fa3cd3d312b8a7f5ee/numpy-2.4.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:57c540ed8fb1f05cb997c6761cd56db72395b0d6985e90571ff660452ade4f98", size = 16475815, upload-time = "2025-12-20T16:18:14.433Z" }, - { url = "https://files.pythonhosted.org/packages/11/73/edeacba3167b1ca66d51b1a5a14697c2c40098b5ffa01811c67b1785a5ab/numpy-2.4.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:a39fb973a726e63223287adc6dafe444ce75af952d711e400f3bf2b36ef55a7b", size = 12489376, upload-time = "2025-12-20T16:18:16.524Z" }, + { url = "https://files.pythonhosted.org/packages/a5/34/2b1bc18424f3ad9af577f6ce23600319968a70575bd7db31ce66731bbef9/numpy-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0cce2a669e3c8ba02ee563c7835f92c153cf02edff1ae05e1823f1dde21b16a5", size = 16944563, upload-time = "2026-01-10T06:42:14.615Z" }, + { url = "https://files.pythonhosted.org/packages/2c/57/26e5f97d075aef3794045a6ca9eada6a4ed70eb9a40e7a4a93f9ac80d704/numpy-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:899d2c18024984814ac7e83f8f49d8e8180e2fbe1b2e252f2e7f1d06bea92425", size = 12645658, upload-time = "2026-01-10T06:42:17.298Z" }, + { url = "https://files.pythonhosted.org/packages/8e/ba/80fc0b1e3cb2fd5c6143f00f42eb67762aa043eaa05ca924ecc3222a7849/numpy-2.4.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:09aa8a87e45b55a1c2c205d42e2808849ece5c484b2aab11fecabec3841cafba", size = 5474132, upload-time = "2026-01-10T06:42:19.637Z" }, + { url = "https://files.pythonhosted.org/packages/40/ae/0a5b9a397f0e865ec171187c78d9b57e5588afc439a04ba9cab1ebb2c945/numpy-2.4.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:edee228f76ee2dab4579fad6f51f6a305de09d444280109e0f75df247ff21501", size = 6804159, upload-time = "2026-01-10T06:42:21.44Z" }, + { url = "https://files.pythonhosted.org/packages/86/9c/841c15e691c7085caa6fd162f063eff494099c8327aeccd509d1ab1e36ab/numpy-2.4.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a92f227dbcdc9e4c3e193add1a189a9909947d4f8504c576f4a732fd0b54240a", size = 14708058, upload-time = "2026-01-10T06:42:23.546Z" }, + { url = "https://files.pythonhosted.org/packages/5d/9d/7862db06743f489e6a502a3b93136d73aea27d97b2cf91504f70a27501d6/numpy-2.4.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:538bf4ec353709c765ff75ae616c34d3c3dca1a68312727e8f2676ea644f8509", size = 16651501, upload-time = "2026-01-10T06:42:25.909Z" }, + { url = "https://files.pythonhosted.org/packages/a6/9c/6fc34ebcbd4015c6e5f0c0ce38264010ce8a546cb6beacb457b84a75dfc8/numpy-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ac08c63cb7779b85e9d5318e6c3518b424bc1f364ac4cb2c6136f12e5ff2dccc", size = 16492627, upload-time = "2026-01-10T06:42:28.938Z" }, + { url = "https://files.pythonhosted.org/packages/aa/63/2494a8597502dacda439f61b3c0db4da59928150e62be0e99395c3ad23c5/numpy-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4f9c360ecef085e5841c539a9a12b883dff005fbd7ce46722f5e9cef52634d82", size = 18585052, upload-time = "2026-01-10T06:42:31.312Z" }, + { url = "https://files.pythonhosted.org/packages/6a/93/098e1162ae7522fc9b618d6272b77404c4656c72432ecee3abc029aa3de0/numpy-2.4.1-cp311-cp311-win32.whl", hash = "sha256:0f118ce6b972080ba0758c6087c3617b5ba243d806268623dc34216d69099ba0", size = 6236575, upload-time = "2026-01-10T06:42:33.872Z" }, + { url = "https://files.pythonhosted.org/packages/8c/de/f5e79650d23d9e12f38a7bc6b03ea0835b9575494f8ec94c11c6e773b1b1/numpy-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:18e14c4d09d55eef39a6ab5b08406e84bc6869c1e34eef45564804f90b7e0574", size = 12604479, upload-time = "2026-01-10T06:42:35.778Z" }, + { url = "https://files.pythonhosted.org/packages/dd/65/e1097a7047cff12ce3369bd003811516b20ba1078dbdec135e1cd7c16c56/numpy-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:6461de5113088b399d655d45c3897fa188766415d0f568f175ab071c8873bd73", size = 10578325, upload-time = "2026-01-10T06:42:38.518Z" }, + { url = "https://files.pythonhosted.org/packages/78/7f/ec53e32bf10c813604edf07a3682616bd931d026fcde7b6d13195dfb684a/numpy-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d3703409aac693fa82c0aee023a1ae06a6e9d065dba10f5e8e80f642f1e9d0a2", size = 16656888, upload-time = "2026-01-10T06:42:40.913Z" }, + { url = "https://files.pythonhosted.org/packages/b8/e0/1f9585d7dae8f14864e948fd7fa86c6cb72dee2676ca2748e63b1c5acfe0/numpy-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7211b95ca365519d3596a1d8688a95874cc94219d417504d9ecb2df99fa7bfa8", size = 12373956, upload-time = "2026-01-10T06:42:43.091Z" }, + { url = "https://files.pythonhosted.org/packages/8e/43/9762e88909ff2326f5e7536fa8cb3c49fb03a7d92705f23e6e7f553d9cb3/numpy-2.4.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:5adf01965456a664fc727ed69cc71848f28d063217c63e1a0e200a118d5eec9a", size = 5202567, upload-time = "2026-01-10T06:42:45.107Z" }, + { url = "https://files.pythonhosted.org/packages/4b/ee/34b7930eb61e79feb4478800a4b95b46566969d837546aa7c034c742ef98/numpy-2.4.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:26f0bcd9c79a00e339565b303badc74d3ea2bd6d52191eeca5f95936cad107d0", size = 6549459, upload-time = "2026-01-10T06:42:48.152Z" }, + { url = "https://files.pythonhosted.org/packages/79/e3/5f115fae982565771be994867c89bcd8d7208dbfe9469185497d70de5ddf/numpy-2.4.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0093e85df2960d7e4049664b26afc58b03236e967fb942354deef3208857a04c", size = 14404859, upload-time = "2026-01-10T06:42:49.947Z" }, + { url = "https://files.pythonhosted.org/packages/d9/7d/9c8a781c88933725445a859cac5d01b5871588a15969ee6aeb618ba99eee/numpy-2.4.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7ad270f438cbdd402c364980317fb6b117d9ec5e226fff5b4148dd9aa9fc6e02", size = 16371419, upload-time = "2026-01-10T06:42:52.409Z" }, + { url = "https://files.pythonhosted.org/packages/a6/d2/8aa084818554543f17cf4162c42f162acbd3bb42688aefdba6628a859f77/numpy-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:297c72b1b98100c2e8f873d5d35fb551fce7040ade83d67dd51d38c8d42a2162", size = 16182131, upload-time = "2026-01-10T06:42:54.694Z" }, + { url = "https://files.pythonhosted.org/packages/60/db/0425216684297c58a8df35f3284ef56ec4a043e6d283f8a59c53562caf1b/numpy-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:cf6470d91d34bf669f61d515499859fa7a4c2f7c36434afb70e82df7217933f9", size = 18295342, upload-time = "2026-01-10T06:42:56.991Z" }, + { url = "https://files.pythonhosted.org/packages/31/4c/14cb9d86240bd8c386c881bafbe43f001284b7cce3bc01623ac9475da163/numpy-2.4.1-cp312-cp312-win32.whl", hash = "sha256:b6bcf39112e956594b3331316d90c90c90fb961e39696bda97b89462f5f3943f", size = 5959015, upload-time = "2026-01-10T06:42:59.631Z" }, + { url = "https://files.pythonhosted.org/packages/51/cf/52a703dbeb0c65807540d29699fef5fda073434ff61846a564d5c296420f/numpy-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:e1a27bb1b2dee45a2a53f5ca6ff2d1a7f135287883a1689e930d44d1ff296c87", size = 12310730, upload-time = "2026-01-10T06:43:01.627Z" }, + { url = "https://files.pythonhosted.org/packages/69/80/a828b2d0ade5e74a9fe0f4e0a17c30fdc26232ad2bc8c9f8b3197cf7cf18/numpy-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:0e6e8f9d9ecf95399982019c01223dc130542960a12edfa8edd1122dfa66a8a8", size = 10312166, upload-time = "2026-01-10T06:43:03.673Z" }, + { url = "https://files.pythonhosted.org/packages/1e/48/d86f97919e79314a1cdee4c832178763e6e98e623e123d0bada19e92c15a/numpy-2.4.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:8ad35f20be147a204e28b6a0575fbf3540c5e5f802634d4258d55b1ff5facce1", size = 16822202, upload-time = "2026-01-10T06:44:43.738Z" }, + { url = "https://files.pythonhosted.org/packages/51/e9/1e62a7f77e0f37dcfb0ad6a9744e65df00242b6ea37dfafb55debcbf5b55/numpy-2.4.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:8097529164c0f3e32bb89412a0905d9100bf434d9692d9fc275e18dcf53c9344", size = 12569985, upload-time = "2026-01-10T06:44:45.945Z" }, + { url = "https://files.pythonhosted.org/packages/c7/7e/914d54f0c801342306fdcdce3e994a56476f1b818c46c47fc21ae968088c/numpy-2.4.1-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:ea66d2b41ca4a1630aae5507ee0a71647d3124d1741980138aa8f28f44dac36e", size = 5398484, upload-time = "2026-01-10T06:44:48.012Z" }, + { url = "https://files.pythonhosted.org/packages/1c/d8/9570b68584e293a33474e7b5a77ca404f1dcc655e40050a600dee81d27fb/numpy-2.4.1-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:d3f8f0df9f4b8be57b3bf74a1d087fec68f927a2fab68231fdb442bf2c12e426", size = 6713216, upload-time = "2026-01-10T06:44:49.725Z" }, + { url = "https://files.pythonhosted.org/packages/33/9b/9dd6e2db8d49eb24f86acaaa5258e5f4c8ed38209a4ee9de2d1a0ca25045/numpy-2.4.1-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2023ef86243690c2791fd6353e5b4848eedaa88ca8a2d129f462049f6d484696", size = 14538937, upload-time = "2026-01-10T06:44:51.498Z" }, + { url = "https://files.pythonhosted.org/packages/53/87/d5bd995b0f798a37105b876350d346eea5838bd8f77ea3d7a48392f3812b/numpy-2.4.1-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8361ea4220d763e54cff2fbe7d8c93526b744f7cd9ddab47afeff7e14e8503be", size = 16479830, upload-time = "2026-01-10T06:44:53.931Z" }, + { url = "https://files.pythonhosted.org/packages/5b/c7/b801bf98514b6ae6475e941ac05c58e6411dd863ea92916bfd6d510b08c1/numpy-2.4.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:4f1b68ff47680c2925f8063402a693ede215f0257f02596b1318ecdfb1d79e33", size = 12492579, upload-time = "2026-01-10T06:44:57.094Z" }, ] [[package]] name = "onnx" -version = "1.20.0" +version = "1.20.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "ml-dtypes" }, @@ -1261,37 +1257,38 @@ dependencies = [ { name = "protobuf" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/bd/bf/824b13b7ea14c2d374b48a296cfa412442e5559326fbab5441a4fcb68924/onnx-1.20.0.tar.gz", hash = "sha256:1a93ec69996b4556062d552ed1aa0671978cfd3c17a40bf4c89a1ae169c6a4ad", size = 12049527, upload-time = "2025-12-01T18:14:34.679Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/8a/335c03a8683a88a32f9a6bb98899ea6df241a41df64b37b9696772414794/onnx-1.20.1.tar.gz", hash = "sha256:ded16de1df563d51fbc1ad885f2a426f814039d8b5f4feb77febe09c0295ad67", size = 12048980, upload-time = "2026-01-10T01:40:03.043Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/9a/125ad5ed919d1782b26b0b4404e51adc44afd029be30d5a81b446dccd9c5/onnx-1.20.0-cp311-cp311-macosx_12_0_universal2.whl", hash = "sha256:00dc8ae2c7b283f79623961f450b5515bd2c4b47a7027e7a1374ba49cef27768", size = 18341929, upload-time = "2025-12-01T18:13:43.79Z" }, - { url = "https://files.pythonhosted.org/packages/4d/3c/85280dd05396493f3e1b4feb7a3426715e344b36083229437f31d9788a01/onnx-1.20.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f62978ecfb8f320faba6704abd20253a5a79aacc4e5d39a9c061dd63d3b7574f", size = 17899362, upload-time = "2025-12-01T18:13:46.496Z" }, - { url = "https://files.pythonhosted.org/packages/26/db/e11cf9aaa6ccbcd27ea94d321020fef3207cba388bff96111e6431f97d1a/onnx-1.20.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:71177f8fd5c0dd90697bc281f5035f73707bdac83257a5c54d74403a1100ace9", size = 18119129, upload-time = "2025-12-01T18:13:49.662Z" }, - { url = "https://files.pythonhosted.org/packages/ef/0b/1b99e7ba5ccfa8ecb3509ec579c8520098d09b903ccd520026d60faa7c75/onnx-1.20.0-cp311-cp311-win32.whl", hash = "sha256:1d3d0308e2c194f4b782f51e78461b567fac8ce6871c0cf5452ede261683cc8f", size = 16364604, upload-time = "2025-12-01T18:13:52.691Z" }, - { url = "https://files.pythonhosted.org/packages/51/ab/7399817821d0d18ff67292ac183383e41f4f4ddff2047902f1b7b51d2d40/onnx-1.20.0-cp311-cp311-win_amd64.whl", hash = "sha256:3a6de7dda77926c323b0e5a830dc9c2866ce350c1901229e193be1003a076c25", size = 16488019, upload-time = "2025-12-01T18:13:55.776Z" }, - { url = "https://files.pythonhosted.org/packages/fd/e0/23059c11d9c0fb1951acec504a5cc86e1dd03d2eef3a98cf1941839f5322/onnx-1.20.0-cp311-cp311-win_arm64.whl", hash = "sha256:afc4cf83ce5d547ebfbb276dae8eb0ec836254a8698d462b4ba5f51e717fd1ae", size = 16446841, upload-time = "2025-12-01T18:13:58.091Z" }, - { url = "https://files.pythonhosted.org/packages/5e/19/2caa972a31014a8cb4525f715f2a75d93caef9d4b9da2809cc05d0489e43/onnx-1.20.0-cp312-abi3-macosx_12_0_universal2.whl", hash = "sha256:31efe37d7d1d659091f34ddd6a31780334acf7c624176832db9a0a8ececa8fb5", size = 18340913, upload-time = "2025-12-01T18:14:00.477Z" }, - { url = "https://files.pythonhosted.org/packages/78/bb/b98732309f2f6beb4cdcf7b955d7bbfd75a191185370ee21233373db381e/onnx-1.20.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d75da05e743eb9a11ff155a775cae5745e71f1cd0ca26402881b8f20e8d6e449", size = 17896118, upload-time = "2025-12-01T18:14:03.239Z" }, - { url = "https://files.pythonhosted.org/packages/84/a7/38aa564871d062c11538d65c575af9c7e057be880c09ecbd899dd1abfa83/onnx-1.20.0-cp312-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02e0d72ab09a983fce46686b155a5049898558d9f3bc6e8515120d6c40666318", size = 18115415, upload-time = "2025-12-01T18:14:06.261Z" }, - { url = "https://files.pythonhosted.org/packages/3b/17/a600b62cf4ad72976c66f83ce9e324205af434706ad5ec0e35129e125aef/onnx-1.20.0-cp312-abi3-win32.whl", hash = "sha256:392ca68b34b97e172d33b507e1e7bfdf2eea96603e6e7ff109895b82ff009dc7", size = 16363019, upload-time = "2025-12-01T18:14:09.16Z" }, - { url = "https://files.pythonhosted.org/packages/9c/3b/5146ba0a89f73c026bb468c49612bab8d005aa28155ebf06cf5f2eb8d36c/onnx-1.20.0-cp312-abi3-win_amd64.whl", hash = "sha256:259b05758d41645f5545c09f887187662b350d40db8d707c33c94a4f398e1733", size = 16485934, upload-time = "2025-12-01T18:14:13.046Z" }, - { url = "https://files.pythonhosted.org/packages/f3/bc/d251b97395e721b3034e9578d4d4d9fb33aac4197ae16ce8c7ed79a26dce/onnx-1.20.0-cp312-abi3-win_arm64.whl", hash = "sha256:2d25a9e1fde44bc69988e50e2211f62d6afcd01b0fd6dfd23429fd978a35d32f", size = 16444946, upload-time = "2025-12-01T18:14:15.801Z" }, + { url = "https://files.pythonhosted.org/packages/0c/38/1a0e74d586c08833404100f5c052f92732fb5be417c0b2d7cb0838443bfe/onnx-1.20.1-cp311-cp311-macosx_12_0_universal2.whl", hash = "sha256:53426e1b458641e7a537e9f176330012ff59d90206cac1c1a9d03cdd73ed3095", size = 17904965, upload-time = "2026-01-10T01:39:13.532Z" }, + { url = "https://files.pythonhosted.org/packages/96/25/64b076e9684d17335f80b15b3bf502f7a8e1a89f08a6b208d4f2861b3011/onnx-1.20.1-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ca7281f8c576adf396c338cf43fff26faee8d4d2e2577b8e73738f37ceccf945", size = 17415179, upload-time = "2026-01-10T01:39:16.516Z" }, + { url = "https://files.pythonhosted.org/packages/ac/d5/6743b409421ced20ad5af1b3a7b4c4e568689ffaca86db431692fca409a6/onnx-1.20.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2297f428c51c7fc6d8fad0cf34384284dfeff3f86799f8e83ef905451348ade0", size = 17513672, upload-time = "2026-01-10T01:39:19.35Z" }, + { url = "https://files.pythonhosted.org/packages/9a/6b/dae82e6fdb2043302f29adca37522312ea2be55b75907b59be06fbdffe87/onnx-1.20.1-cp311-cp311-win32.whl", hash = "sha256:63d9cbcab8c96841eadeb7c930e07bfab4dde8081eb76fb68e0dfb222706b81e", size = 16239336, upload-time = "2026-01-10T01:39:22.506Z" }, + { url = "https://files.pythonhosted.org/packages/8e/17/a0d7863390c1f2067d7c02dcc1477034965c32aaa1407bfcf775305ffee4/onnx-1.20.1-cp311-cp311-win_amd64.whl", hash = "sha256:d78cde72d7ca8356a2d99c5dc0dbf67264254828cae2c5780184486c0cd7b3bf", size = 16392120, upload-time = "2026-01-10T01:39:25.106Z" }, + { url = "https://files.pythonhosted.org/packages/aa/72/9b879a46eb7a3322223791f36bf9c25d95da9ed93779eabb75a560f22e5b/onnx-1.20.1-cp311-cp311-win_arm64.whl", hash = "sha256:0104bb2d4394c179bcea3df7599a45a2932b80f4633840896fcf0d7d8daecea2", size = 16346923, upload-time = "2026-01-10T01:39:27.782Z" }, + { url = "https://files.pythonhosted.org/packages/7c/4c/4b17e82f91ab9aa07ff595771e935ca73547b035030dc5f5a76e63fbfea9/onnx-1.20.1-cp312-abi3-macosx_12_0_universal2.whl", hash = "sha256:1d923bb4f0ce1b24c6859222a7e6b2f123e7bfe7623683662805f2e7b9e95af2", size = 17903547, upload-time = "2026-01-10T01:39:31.015Z" }, + { url = "https://files.pythonhosted.org/packages/64/5e/1bfa100a9cb3f2d3d5f2f05f52f7e60323b0e20bb0abace1ae64dbc88f25/onnx-1.20.1-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ddc0b7d8b5a94627dc86c533d5e415af94cbfd103019a582669dad1f56d30281", size = 17412021, upload-time = "2026-01-10T01:39:33.885Z" }, + { url = "https://files.pythonhosted.org/packages/fb/71/d3fec0dcf9a7a99e7368112d9c765154e81da70fcba1e3121131a45c245b/onnx-1.20.1-cp312-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9336b6b8e6efcf5c490a845f6afd7e041c89a56199aeda384ed7d58fb953b080", size = 17510450, upload-time = "2026-01-10T01:39:36.589Z" }, + { url = "https://files.pythonhosted.org/packages/74/a7/edce1403e05a46e59b502fae8e3350ceeac5841f8e8f1561e98562ed9b09/onnx-1.20.1-cp312-abi3-win32.whl", hash = "sha256:564c35a94811979808ab5800d9eb4f3f32c12daedba7e33ed0845f7c61ef2431", size = 16238216, upload-time = "2026-01-10T01:39:39.46Z" }, + { url = "https://files.pythonhosted.org/packages/8b/c7/8690c81200ae652ac550c1df52f89d7795e6cc941f3cb38c9ef821419e80/onnx-1.20.1-cp312-abi3-win_amd64.whl", hash = "sha256:9fe7f9a633979d50984b94bda8ceb7807403f59a341d09d19342dc544d0ca1d5", size = 16389207, upload-time = "2026-01-10T01:39:41.955Z" }, + { url = "https://files.pythonhosted.org/packages/01/a0/4fb0e6d36eaf079af366b2c1f68bafe92df6db963e2295da84388af64abc/onnx-1.20.1-cp312-abi3-win_arm64.whl", hash = "sha256:21d747348b1c8207406fa2f3e12b82f53e0d5bb3958bcd0288bd27d3cb6ebb00", size = 16344155, upload-time = "2026-01-10T01:39:45.536Z" }, ] [[package]] name = "opencv-python-headless" -version = "4.11.0.86" +version = "4.13.0.90" source = { registry = "https://pypi.org/simple" } dependencies = [ { 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 = [ - { url = "https://files.pythonhosted.org/packages/dc/53/2c50afa0b1e05ecdb4603818e85f7d174e683d874ef63a6abe3ac92220c8/opencv_python_headless-4.11.0.86-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:48128188ade4a7e517237c8e1e11a9cdf5c282761473383e77beb875bb1e61ca", size = 37326460, upload-time = "2025-01-16T13:52:57.015Z" }, - { url = "https://files.pythonhosted.org/packages/3b/43/68555327df94bb9b59a1fd645f63fafb0762515344d2046698762fc19d58/opencv_python_headless-4.11.0.86-cp37-abi3-macosx_13_0_x86_64.whl", hash = "sha256:a66c1b286a9de872c343ee7c3553b084244299714ebb50fbdcd76f07ebbe6c81", size = 56723330, upload-time = "2025-01-16T13:55:45.731Z" }, - { url = "https://files.pythonhosted.org/packages/45/be/1438ce43ebe65317344a87e4b150865c5585f4c0db880a34cdae5ac46881/opencv_python_headless-4.11.0.86-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6efabcaa9df731f29e5ea9051776715b1bdd1845d7c9530065c7951d2a2899eb", size = 29487060, upload-time = "2025-01-16T13:51:59.625Z" }, - { url = "https://files.pythonhosted.org/packages/dd/5c/c139a7876099916879609372bfa513b7f1257f7f1a908b0bdc1c2328241b/opencv_python_headless-4.11.0.86-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e0a27c19dd1f40ddff94976cfe43066fbbe9dfbb2ec1907d66c19caef42a57b", size = 49969856, upload-time = "2025-01-16T13:53:29.654Z" }, - { url = "https://files.pythonhosted.org/packages/95/dd/ed1191c9dc91abcc9f752b499b7928aacabf10567bb2c2535944d848af18/opencv_python_headless-4.11.0.86-cp37-abi3-win32.whl", hash = "sha256:f447d8acbb0b6f2808da71fddd29c1cdd448d2bc98f72d9bb78a7a898fc9621b", size = 29324425, upload-time = "2025-01-16T13:52:49.048Z" }, - { url = "https://files.pythonhosted.org/packages/86/8a/69176a64335aed183529207ba8bc3d329c2999d852b4f3818027203f50e6/opencv_python_headless-4.11.0.86-cp37-abi3-win_amd64.whl", hash = "sha256:6c304df9caa7a6a5710b91709dd4786bf20a74d57672b3c31f7033cc638174ca", size = 39402386, upload-time = "2025-01-16T13:52:56.418Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/38c4cbb5ccfce7aaf36fd9be9fc74a15c85a48ef90bfaca2049b486e10c5/opencv_python_headless-4.13.0.90-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:12a28674f215542c9bf93338de1b5bffd76996d32da9acb9e739fdb9c8bbd738", size = 46020414, upload-time = "2026-01-18T09:07:10.801Z" }, + { url = "https://files.pythonhosted.org/packages/93/c5/4b40daa5003b45aa8397f160324a091ed323733e2446dc0bdf3655e77b84/opencv_python_headless-4.13.0.90-cp37-abi3-macosx_14_0_x86_64.whl", hash = "sha256:32255203040dc98803be96362e13f9e4bce20146898222d2e5c242f80de50da5", size = 32568519, upload-time = "2026-01-18T09:07:52.368Z" }, + { url = "https://files.pythonhosted.org/packages/da/65/920e64a7f03cf5917cd2c6a3046293843c1a16ad89f0ed0f1c683979c9de/opencv_python_headless-4.13.0.90-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e13790342591557050157713af17a7435ac1b50c65282715093c9297fa045d8f", size = 35191272, upload-time = "2026-01-18T09:08:49.235Z" }, + { url = "https://files.pythonhosted.org/packages/fc/13/af150685be342dc09bfb0824e2a280020ccf1c7fc64e15a31d9209016aa9/opencv_python_headless-4.13.0.90-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dbc1f4625e5af3a80ebdbd84380227c0f445228588f2521b11af47710caca1ba", size = 57683677, upload-time = "2026-01-18T09:10:23.588Z" }, + { url = "https://files.pythonhosted.org/packages/cd/47/baab2a3b6d8da8c52e73d00207d1ed3155601c2c332ea855455b3fbc8ff4/opencv_python_headless-4.13.0.90-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:eba38bc255d0b7d1969c5bcc90a060ca2b61a3403b613872c750bfa5dfe9e03b", size = 36590019, upload-time = "2026-01-18T09:10:49.053Z" }, + { url = "https://files.pythonhosted.org/packages/81/a1/facfe2801a861b424c4221d66e1281cf19735c00e07f063a337a208c11b5/opencv_python_headless-4.13.0.90-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f46b17ea0aa7e4124ca6ad71143f89233ae9557f61d2326bcdb34329a1ddf9bd", size = 62535926, upload-time = "2026-01-18T09:12:47.229Z" }, + { url = "https://files.pythonhosted.org/packages/06/d2/5e9ee7512306c1caa518be929d1f44bb1c189f342f538f73bea6fb94919f/opencv_python_headless-4.13.0.90-cp37-abi3-win32.whl", hash = "sha256:96060fc57a1abb1144b0b8129e2ff3bfcdd0ccd8e8bd05bd85256ff4ed587d3b", size = 30811665, upload-time = "2026-01-18T09:13:44.517Z" }, + { url = "https://files.pythonhosted.org/packages/a0/09/0a4d832448dccd03b2b1bdee70b9fc2e02c147cc7e06975e9cd729569d90/opencv_python_headless-4.13.0.90-cp37-abi3-win_amd64.whl", hash = "sha256:0e0c8c9f620802fddc4fa7f471a1d263c7b0dca16cd9e7e2f996bb8bd2128c0c", size = 40070035, upload-time = "2026-01-18T09:15:14.652Z" }, ] [[package]] @@ -1494,8 +1491,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 = [ @@ -1507,8 +1504,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 = [ @@ -1526,48 +1523,48 @@ wheels = [ [[package]] name = "pathspec" -version = "0.12.1" +version = "1.0.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/b2/bb8e495d5262bfec41ab5cb18f522f1012933347fb5d9e62452d446baca2/pathspec-1.0.3.tar.gz", hash = "sha256:bac5cf97ae2c2876e2d25ebb15078eb04d76e4b98921ee31c6f85ade8b59444d", size = 130841, upload-time = "2026-01-09T15:46:46.009Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, + { url = "https://files.pythonhosted.org/packages/32/2b/121e912bd60eebd623f873fd090de0e84f322972ab25a7f9044c056804ed/pathspec-1.0.3-py3-none-any.whl", hash = "sha256:e80767021c1cc524aa3fb14bedda9c34406591343cc42797b386ce7b9354fb6c", size = 55021, upload-time = "2026-01-09T15:46:44.652Z" }, ] [[package]] name = "pillow" -version = "12.0.0" +version = "12.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/cace85a1b0c9775a9f8f5d5423c8261c858760e2466c79b2dd184638b056/pillow-12.0.0.tar.gz", hash = "sha256:87d4f8125c9988bfbed67af47dd7a953e2fc7b0cc1e7800ec6d2080d490bb353", size = 47008828, upload-time = "2025-10-15T18:24:14.008Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/02/d52c733a2452ef1ffcc123b68e6606d07276b0e358db70eabad7e40042b7/pillow-12.1.0.tar.gz", hash = "sha256:5c5ae0a06e9ea030ab786b0251b32c7e4ce10e58d983c0d5c56029455180b5b9", size = 46977283, upload-time = "2026-01-02T09:13:29.892Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/5a/a2f6773b64edb921a756eb0729068acad9fc5208a53f4a349396e9436721/pillow-12.0.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0fd00cac9c03256c8b2ff58f162ebcd2587ad3e1f2e397eab718c47e24d231cc", size = 5289798, upload-time = "2025-10-15T18:21:47.763Z" }, - { url = "https://files.pythonhosted.org/packages/2e/05/069b1f8a2e4b5a37493da6c5868531c3f77b85e716ad7a590ef87d58730d/pillow-12.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3475b96f5908b3b16c47533daaa87380c491357d197564e0ba34ae75c0f3257", size = 4650589, upload-time = "2025-10-15T18:21:49.515Z" }, - { url = "https://files.pythonhosted.org/packages/61/e3/2c820d6e9a36432503ead175ae294f96861b07600a7156154a086ba7111a/pillow-12.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:110486b79f2d112cf6add83b28b627e369219388f64ef2f960fef9ebaf54c642", size = 6230472, upload-time = "2025-10-15T18:21:51.052Z" }, - { url = "https://files.pythonhosted.org/packages/4f/89/63427f51c64209c5e23d4d52071c8d0f21024d3a8a487737caaf614a5795/pillow-12.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5269cc1caeedb67e6f7269a42014f381f45e2e7cd42d834ede3c703a1d915fe3", size = 8033887, upload-time = "2025-10-15T18:21:52.604Z" }, - { url = "https://files.pythonhosted.org/packages/f6/1b/c9711318d4901093c15840f268ad649459cd81984c9ec9887756cca049a5/pillow-12.0.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa5129de4e174daccbc59d0a3b6d20eaf24417d59851c07ebb37aeb02947987c", size = 6343964, upload-time = "2025-10-15T18:21:54.619Z" }, - { url = "https://files.pythonhosted.org/packages/41/1e/db9470f2d030b4995083044cd8738cdd1bf773106819f6d8ba12597d5352/pillow-12.0.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bee2a6db3a7242ea309aa7ee8e2780726fed67ff4e5b40169f2c940e7eb09227", size = 7034756, upload-time = "2025-10-15T18:21:56.151Z" }, - { url = "https://files.pythonhosted.org/packages/cc/b0/6177a8bdd5ee4ed87cba2de5a3cc1db55ffbbec6176784ce5bb75aa96798/pillow-12.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:90387104ee8400a7b4598253b4c406f8958f59fcf983a6cea2b50d59f7d63d0b", size = 6458075, upload-time = "2025-10-15T18:21:57.759Z" }, - { url = "https://files.pythonhosted.org/packages/bc/5e/61537aa6fa977922c6a03253a0e727e6e4a72381a80d63ad8eec350684f2/pillow-12.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bc91a56697869546d1b8f0a3ff35224557ae7f881050e99f615e0119bf934b4e", size = 7125955, upload-time = "2025-10-15T18:21:59.372Z" }, - { url = "https://files.pythonhosted.org/packages/1f/3d/d5033539344ee3cbd9a4d69e12e63ca3a44a739eb2d4c8da350a3d38edd7/pillow-12.0.0-cp311-cp311-win32.whl", hash = "sha256:27f95b12453d165099c84f8a8bfdfd46b9e4bda9e0e4b65f0635430027f55739", size = 6298440, upload-time = "2025-10-15T18:22:00.982Z" }, - { url = "https://files.pythonhosted.org/packages/4d/42/aaca386de5cc8bd8a0254516957c1f265e3521c91515b16e286c662854c4/pillow-12.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:b583dc9070312190192631373c6c8ed277254aa6e6084b74bdd0a6d3b221608e", size = 6999256, upload-time = "2025-10-15T18:22:02.617Z" }, - { url = "https://files.pythonhosted.org/packages/ba/f1/9197c9c2d5708b785f631a6dfbfa8eb3fb9672837cb92ae9af812c13b4ed/pillow-12.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:759de84a33be3b178a64c8ba28ad5c135900359e85fb662bc6e403ad4407791d", size = 2436025, upload-time = "2025-10-15T18:22:04.598Z" }, - { url = "https://files.pythonhosted.org/packages/2c/90/4fcce2c22caf044e660a198d740e7fbc14395619e3cb1abad12192c0826c/pillow-12.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:53561a4ddc36facb432fae7a9d8afbfaf94795414f5cdc5fc52f28c1dca90371", size = 5249377, upload-time = "2025-10-15T18:22:05.993Z" }, - { url = "https://files.pythonhosted.org/packages/fd/e0/ed960067543d080691d47d6938ebccbf3976a931c9567ab2fbfab983a5dd/pillow-12.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:71db6b4c1653045dacc1585c1b0d184004f0d7e694c7b34ac165ca70c0838082", size = 4650343, upload-time = "2025-10-15T18:22:07.718Z" }, - { url = "https://files.pythonhosted.org/packages/e7/a1/f81fdeddcb99c044bf7d6faa47e12850f13cee0849537a7d27eeab5534d4/pillow-12.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2fa5f0b6716fc88f11380b88b31fe591a06c6315e955c096c35715788b339e3f", size = 6232981, upload-time = "2025-10-15T18:22:09.287Z" }, - { url = "https://files.pythonhosted.org/packages/88/e1/9098d3ce341a8750b55b0e00c03f1630d6178f38ac191c81c97a3b047b44/pillow-12.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:82240051c6ca513c616f7f9da06e871f61bfd7805f566275841af15015b8f98d", size = 8041399, upload-time = "2025-10-15T18:22:10.872Z" }, - { url = "https://files.pythonhosted.org/packages/a7/62/a22e8d3b602ae8cc01446d0c57a54e982737f44b6f2e1e019a925143771d/pillow-12.0.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55f818bd74fe2f11d4d7cbc65880a843c4075e0ac7226bc1a23261dbea531953", size = 6347740, upload-time = "2025-10-15T18:22:12.769Z" }, - { url = "https://files.pythonhosted.org/packages/4f/87/424511bdcd02c8d7acf9f65caa09f291a519b16bd83c3fb3374b3d4ae951/pillow-12.0.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b87843e225e74576437fd5b6a4c2205d422754f84a06942cfaf1dc32243e45a8", size = 7040201, upload-time = "2025-10-15T18:22:14.813Z" }, - { url = "https://files.pythonhosted.org/packages/dc/4d/435c8ac688c54d11755aedfdd9f29c9eeddf68d150fe42d1d3dbd2365149/pillow-12.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c607c90ba67533e1b2355b821fef6764d1dd2cbe26b8c1005ae84f7aea25ff79", size = 6462334, upload-time = "2025-10-15T18:22:16.375Z" }, - { url = "https://files.pythonhosted.org/packages/2b/f2/ad34167a8059a59b8ad10bc5c72d4d9b35acc6b7c0877af8ac885b5f2044/pillow-12.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:21f241bdd5080a15bc86d3466a9f6074a9c2c2b314100dd896ac81ee6db2f1ba", size = 7134162, upload-time = "2025-10-15T18:22:17.996Z" }, - { url = "https://files.pythonhosted.org/packages/0c/b1/a7391df6adacf0a5c2cf6ac1cf1fcc1369e7d439d28f637a847f8803beb3/pillow-12.0.0-cp312-cp312-win32.whl", hash = "sha256:dd333073e0cacdc3089525c7df7d39b211bcdf31fc2824e49d01c6b6187b07d0", size = 6298769, upload-time = "2025-10-15T18:22:19.923Z" }, - { url = "https://files.pythonhosted.org/packages/a2/0b/d87733741526541c909bbf159e338dcace4f982daac6e5a8d6be225ca32d/pillow-12.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:9fe611163f6303d1619bbcb653540a4d60f9e55e622d60a3108be0d5b441017a", size = 7001107, upload-time = "2025-10-15T18:22:21.644Z" }, - { url = "https://files.pythonhosted.org/packages/bc/96/aaa61ce33cc98421fb6088af2a03be4157b1e7e0e87087c888e2370a7f45/pillow-12.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:7dfb439562f234f7d57b1ac6bc8fe7f838a4bd49c79230e0f6a1da93e82f1fad", size = 2436012, upload-time = "2025-10-15T18:22:23.621Z" }, - { url = "https://files.pythonhosted.org/packages/1d/b3/582327e6c9f86d037b63beebe981425d6811104cb443e8193824ef1a2f27/pillow-12.0.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b22bd8c974942477156be55a768f7aa37c46904c175be4e158b6a86e3a6b7ca8", size = 5215068, upload-time = "2025-10-15T18:23:59.594Z" }, - { url = "https://files.pythonhosted.org/packages/fd/d6/67748211d119f3b6540baf90f92fae73ae51d5217b171b0e8b5f7e5d558f/pillow-12.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:805ebf596939e48dbb2e4922a1d3852cfc25c38160751ce02da93058b48d252a", size = 4614994, upload-time = "2025-10-15T18:24:01.669Z" }, - { url = "https://files.pythonhosted.org/packages/2d/e1/f8281e5d844c41872b273b9f2c34a4bf64ca08905668c8ae730eedc7c9fa/pillow-12.0.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cae81479f77420d217def5f54b5b9d279804d17e982e0f2fa19b1d1e14ab5197", size = 5246639, upload-time = "2025-10-15T18:24:03.403Z" }, - { url = "https://files.pythonhosted.org/packages/94/5a/0d8ab8ffe8a102ff5df60d0de5af309015163bf710c7bb3e8311dd3b3ad0/pillow-12.0.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aeaefa96c768fc66818730b952a862235d68825c178f1b3ffd4efd7ad2edcb7c", size = 6986839, upload-time = "2025-10-15T18:24:05.344Z" }, - { url = "https://files.pythonhosted.org/packages/20/2e/3434380e8110b76cd9eb00a363c484b050f949b4bbe84ba770bb8508a02c/pillow-12.0.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09f2d0abef9e4e2f349305a4f8cc784a8a6c2f58a8c4892eea13b10a943bd26e", size = 5313505, upload-time = "2025-10-15T18:24:07.137Z" }, - { url = "https://files.pythonhosted.org/packages/57/ca/5a9d38900d9d74785141d6580950fe705de68af735ff6e727cb911b64740/pillow-12.0.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bdee52571a343d721fb2eb3b090a82d959ff37fc631e3f70422e0c2e029f3e76", size = 5963654, upload-time = "2025-10-15T18:24:09.579Z" }, - { url = "https://files.pythonhosted.org/packages/95/7e/f896623c3c635a90537ac093c6a618ebe1a90d87206e42309cb5d98a1b9e/pillow-12.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:b290fd8aa38422444d4b50d579de197557f182ef1068b75f5aa8558638b8d0a5", size = 6997850, upload-time = "2025-10-15T18:24:11.495Z" }, + { url = "https://files.pythonhosted.org/packages/43/c4/bf8328039de6cc22182c3ef007a2abfbbdab153661c0a9aa78af8d706391/pillow-12.1.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:a83e0850cb8f5ac975291ebfc4170ba481f41a28065277f7f735c202cd8e0af3", size = 5304057, upload-time = "2026-01-02T09:10:46.627Z" }, + { url = "https://files.pythonhosted.org/packages/43/06/7264c0597e676104cc22ca73ee48f752767cd4b1fe084662620b17e10120/pillow-12.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b6e53e82ec2db0717eabb276aa56cf4e500c9a7cec2c2e189b55c24f65a3e8c0", size = 4657811, upload-time = "2026-01-02T09:10:49.548Z" }, + { url = "https://files.pythonhosted.org/packages/72/64/f9189e44474610daf83da31145fa56710b627b5c4c0b9c235e34058f6b31/pillow-12.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:40a8e3b9e8773876d6e30daed22f016509e3987bab61b3b7fe309d7019a87451", size = 6232243, upload-time = "2026-01-02T09:10:51.62Z" }, + { url = "https://files.pythonhosted.org/packages/ef/30/0df458009be6a4caca4ca2c52975e6275c387d4e5c95544e34138b41dc86/pillow-12.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:800429ac32c9b72909c671aaf17ecd13110f823ddb7db4dfef412a5587c2c24e", size = 8037872, upload-time = "2026-01-02T09:10:53.446Z" }, + { url = "https://files.pythonhosted.org/packages/e4/86/95845d4eda4f4f9557e25381d70876aa213560243ac1a6d619c46caaedd9/pillow-12.1.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b022eaaf709541b391ee069f0022ee5b36c709df71986e3f7be312e46f42c84", size = 6345398, upload-time = "2026-01-02T09:10:55.426Z" }, + { url = "https://files.pythonhosted.org/packages/5c/1f/8e66ab9be3aaf1435bc03edd1ebdf58ffcd17f7349c1d970cafe87af27d9/pillow-12.1.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f345e7bc9d7f368887c712aa5054558bad44d2a301ddf9248599f4161abc7c0", size = 7034667, upload-time = "2026-01-02T09:10:57.11Z" }, + { url = "https://files.pythonhosted.org/packages/f9/f6/683b83cb9b1db1fb52b87951b1c0b99bdcfceaa75febf11406c19f82cb5e/pillow-12.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d70347c8a5b7ccd803ec0c85c8709f036e6348f1e6a5bf048ecd9c64d3550b8b", size = 6458743, upload-time = "2026-01-02T09:10:59.331Z" }, + { url = "https://files.pythonhosted.org/packages/9a/7d/de833d63622538c1d58ce5395e7c6cb7e7dce80decdd8bde4a484e095d9f/pillow-12.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1fcc52d86ce7a34fd17cb04e87cfdb164648a3662a6f20565910a99653d66c18", size = 7159342, upload-time = "2026-01-02T09:11:01.82Z" }, + { url = "https://files.pythonhosted.org/packages/8c/40/50d86571c9e5868c42b81fe7da0c76ca26373f3b95a8dd675425f4a92ec1/pillow-12.1.0-cp311-cp311-win32.whl", hash = "sha256:3ffaa2f0659e2f740473bcf03c702c39a8d4b2b7ffc629052028764324842c64", size = 6328655, upload-time = "2026-01-02T09:11:04.556Z" }, + { url = "https://files.pythonhosted.org/packages/6c/af/b1d7e301c4cd26cd45d4af884d9ee9b6fab893b0ad2450d4746d74a6968c/pillow-12.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:806f3987ffe10e867bab0ddad45df1148a2b98221798457fa097ad85d6e8bc75", size = 7031469, upload-time = "2026-01-02T09:11:06.538Z" }, + { url = "https://files.pythonhosted.org/packages/48/36/d5716586d887fb2a810a4a61518a327a1e21c8b7134c89283af272efe84b/pillow-12.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:9f5fefaca968e700ad1a4a9de98bf0869a94e397fe3524c4c9450c1445252304", size = 2452515, upload-time = "2026-01-02T09:11:08.226Z" }, + { url = "https://files.pythonhosted.org/packages/20/31/dc53fe21a2f2996e1b7d92bf671cdb157079385183ef7c1ae08b485db510/pillow-12.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a332ac4ccb84b6dde65dbace8431f3af08874bf9770719d32a635c4ef411b18b", size = 5262642, upload-time = "2026-01-02T09:11:10.138Z" }, + { url = "https://files.pythonhosted.org/packages/ab/c1/10e45ac9cc79419cedf5121b42dcca5a50ad2b601fa080f58c22fb27626e/pillow-12.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:907bfa8a9cb790748a9aa4513e37c88c59660da3bcfffbd24a7d9e6abf224551", size = 4657464, upload-time = "2026-01-02T09:11:12.319Z" }, + { url = "https://files.pythonhosted.org/packages/ad/26/7b82c0ab7ef40ebede7a97c72d473bda5950f609f8e0c77b04af574a0ddb/pillow-12.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:efdc140e7b63b8f739d09a99033aa430accce485ff78e6d311973a67b6bf3208", size = 6234878, upload-time = "2026-01-02T09:11:14.096Z" }, + { url = "https://files.pythonhosted.org/packages/76/25/27abc9792615b5e886ca9411ba6637b675f1b77af3104710ac7353fe5605/pillow-12.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bef9768cab184e7ae6e559c032e95ba8d07b3023c289f79a2bd36e8bf85605a5", size = 8044868, upload-time = "2026-01-02T09:11:15.903Z" }, + { url = "https://files.pythonhosted.org/packages/0a/ea/f200a4c36d836100e7bc738fc48cd963d3ba6372ebc8298a889e0cfc3359/pillow-12.1.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:742aea052cf5ab5034a53c3846165bc3ce88d7c38e954120db0ab867ca242661", size = 6349468, upload-time = "2026-01-02T09:11:17.631Z" }, + { url = "https://files.pythonhosted.org/packages/11/8f/48d0b77ab2200374c66d344459b8958c86693be99526450e7aee714e03e4/pillow-12.1.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a6dfc2af5b082b635af6e08e0d1f9f1c4e04d17d4e2ca0ef96131e85eda6eb17", size = 7041518, upload-time = "2026-01-02T09:11:19.389Z" }, + { url = "https://files.pythonhosted.org/packages/1d/23/c281182eb986b5d31f0a76d2a2c8cd41722d6fb8ed07521e802f9bba52de/pillow-12.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:609e89d9f90b581c8d16358c9087df76024cf058fa693dd3e1e1620823f39670", size = 6462829, upload-time = "2026-01-02T09:11:21.28Z" }, + { url = "https://files.pythonhosted.org/packages/25/ef/7018273e0faac099d7b00982abdcc39142ae6f3bd9ceb06de09779c4a9d6/pillow-12.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:43b4899cfd091a9693a1278c4982f3e50f7fb7cff5153b05174b4afc9593b616", size = 7166756, upload-time = "2026-01-02T09:11:23.559Z" }, + { url = "https://files.pythonhosted.org/packages/8f/c8/993d4b7ab2e341fe02ceef9576afcf5830cdec640be2ac5bee1820d693d4/pillow-12.1.0-cp312-cp312-win32.whl", hash = "sha256:aa0c9cc0b82b14766a99fbe6084409972266e82f459821cd26997a488a7261a7", size = 6328770, upload-time = "2026-01-02T09:11:25.661Z" }, + { url = "https://files.pythonhosted.org/packages/a7/87/90b358775a3f02765d87655237229ba64a997b87efa8ccaca7dd3e36e7a7/pillow-12.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:d70534cea9e7966169ad29a903b99fc507e932069a881d0965a1a84bb57f6c6d", size = 7033406, upload-time = "2026-01-02T09:11:27.474Z" }, + { url = "https://files.pythonhosted.org/packages/5d/cf/881b457eccacac9e5b2ddd97d5071fb6d668307c57cbf4e3b5278e06e536/pillow-12.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:65b80c1ee7e14a87d6a068dd3b0aea268ffcabfe0498d38661b00c5b4b22e74c", size = 2452612, upload-time = "2026-01-02T09:11:29.309Z" }, + { url = "https://files.pythonhosted.org/packages/8b/bc/224b1d98cffd7164b14707c91aac83c07b047fbd8f58eba4066a3e53746a/pillow-12.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ca94b6aac0d7af2a10ba08c0f888b3d5114439b6b3ef39968378723622fed377", size = 5228605, upload-time = "2026-01-02T09:13:14.084Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ca/49ca7769c4550107de049ed85208240ba0f330b3f2e316f24534795702ce/pillow-12.1.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:351889afef0f485b84078ea40fe33727a0492b9af3904661b0abbafee0355b72", size = 4622245, upload-time = "2026-01-02T09:13:15.964Z" }, + { url = "https://files.pythonhosted.org/packages/73/48/fac807ce82e5955bcc2718642b94b1bd22a82a6d452aea31cbb678cddf12/pillow-12.1.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb0984b30e973f7e2884362b7d23d0a348c7143ee559f38ef3eaab640144204c", size = 5247593, upload-time = "2026-01-02T09:13:17.913Z" }, + { url = "https://files.pythonhosted.org/packages/d2/95/3e0742fe358c4664aed4fd05d5f5373dcdad0b27af52aa0972568541e3f4/pillow-12.1.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:84cabc7095dd535ca934d57e9ce2a72ffd216e435a84acb06b2277b1de2689bd", size = 6989008, upload-time = "2026-01-02T09:13:20.083Z" }, + { url = "https://files.pythonhosted.org/packages/5a/74/fe2ac378e4e202e56d50540d92e1ef4ff34ed687f3c60f6a121bcf99437e/pillow-12.1.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53d8b764726d3af1a138dd353116f774e3862ec7e3794e0c8781e30db0f35dfc", size = 5313824, upload-time = "2026-01-02T09:13:22.405Z" }, + { url = "https://files.pythonhosted.org/packages/f3/77/2a60dee1adee4e2655ac328dd05c02a955c1cd683b9f1b82ec3feb44727c/pillow-12.1.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5da841d81b1a05ef940a8567da92decaa15bc4d7dedb540a8c219ad83d91808a", size = 5963278, upload-time = "2026-01-02T09:13:24.706Z" }, + { url = "https://files.pythonhosted.org/packages/2d/71/64e9b1c7f04ae0027f788a248e6297d7fcc29571371fe7d45495a78172c0/pillow-12.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:75af0b4c229ac519b155028fa1be632d812a519abba9b46b20e50c6caa184f19", size = 7029809, upload-time = "2026-01-02T09:13:26.541Z" }, ] [[package]] @@ -1647,33 +1644,33 @@ wheels = [ [[package]] name = "protobuf" -version = "6.33.2" +version = "6.33.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/34/44/e49ecff446afeec9d1a66d6bbf9adc21e3c7cea7803a920ca3773379d4f6/protobuf-6.33.2.tar.gz", hash = "sha256:56dc370c91fbb8ac85bc13582c9e373569668a290aa2e66a590c2a0d35ddb9e4", size = 444296, upload-time = "2025-12-06T00:17:53.311Z" } +sdist = { url = "https://files.pythonhosted.org/packages/53/b8/cda15d9d46d03d4aa3a67cb6bffe05173440ccf86a9541afaf7ac59a1b6b/protobuf-6.33.4.tar.gz", hash = "sha256:dc2e61bca3b10470c1912d166fe0af67bfc20eb55971dcef8dfa48ce14f0ed91", size = 444346, upload-time = "2026-01-12T18:33:40.109Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bc/91/1e3a34881a88697a7354ffd177e8746e97a722e5e8db101544b47e84afb1/protobuf-6.33.2-cp310-abi3-win32.whl", hash = "sha256:87eb388bd2d0f78febd8f4c8779c79247b26a5befad525008e49a6955787ff3d", size = 425603, upload-time = "2025-12-06T00:17:41.114Z" }, - { url = "https://files.pythonhosted.org/packages/64/20/4d50191997e917ae13ad0a235c8b42d8c1ab9c3e6fd455ca16d416944355/protobuf-6.33.2-cp310-abi3-win_amd64.whl", hash = "sha256:fc2a0e8b05b180e5fc0dd1559fe8ebdae21a27e81ac77728fb6c42b12c7419b4", size = 436930, upload-time = "2025-12-06T00:17:43.278Z" }, - { url = "https://files.pythonhosted.org/packages/b2/ca/7e485da88ba45c920fb3f50ae78de29ab925d9e54ef0de678306abfbb497/protobuf-6.33.2-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d9b19771ca75935b3a4422957bc518b0cecb978b31d1dd12037b088f6bcc0e43", size = 427621, upload-time = "2025-12-06T00:17:44.445Z" }, - { url = "https://files.pythonhosted.org/packages/7d/4f/f743761e41d3b2b2566748eb76bbff2b43e14d5fcab694f494a16458b05f/protobuf-6.33.2-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:b5d3b5625192214066d99b2b605f5783483575656784de223f00a8d00754fc0e", size = 324460, upload-time = "2025-12-06T00:17:45.678Z" }, - { url = "https://files.pythonhosted.org/packages/b1/fa/26468d00a92824020f6f2090d827078c09c9c587e34cbfd2d0c7911221f8/protobuf-6.33.2-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:8cd7640aee0b7828b6d03ae518b5b4806fdfc1afe8de82f79c3454f8aef29872", size = 339168, upload-time = "2025-12-06T00:17:46.813Z" }, - { url = "https://files.pythonhosted.org/packages/56/13/333b8f421738f149d4fe5e49553bc2a2ab75235486259f689b4b91f96cec/protobuf-6.33.2-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:1f8017c48c07ec5859106533b682260ba3d7c5567b1ca1f24297ce03384d1b4f", size = 323270, upload-time = "2025-12-06T00:17:48.253Z" }, - { url = "https://files.pythonhosted.org/packages/0e/15/4f02896cc3df04fc465010a4c6a0cd89810f54617a32a70ef531ed75d61c/protobuf-6.33.2-py3-none-any.whl", hash = "sha256:7636aad9bb01768870266de5dc009de2d1b936771b38a793f73cbbf279c91c5c", size = 170501, upload-time = "2025-12-06T00:17:52.211Z" }, + { url = "https://files.pythonhosted.org/packages/e0/be/24ef9f3095bacdf95b458543334d0c4908ccdaee5130420bf064492c325f/protobuf-6.33.4-cp310-abi3-win32.whl", hash = "sha256:918966612c8232fc6c24c78e1cd89784307f5814ad7506c308ee3cf86662850d", size = 425612, upload-time = "2026-01-12T18:33:29.656Z" }, + { url = "https://files.pythonhosted.org/packages/31/ad/e5693e1974a28869e7cd244302911955c1cebc0161eb32dfa2b25b6e96f0/protobuf-6.33.4-cp310-abi3-win_amd64.whl", hash = "sha256:8f11ffae31ec67fc2554c2ef891dcb561dae9a2a3ed941f9e134c2db06657dbc", size = 436962, upload-time = "2026-01-12T18:33:31.345Z" }, + { url = "https://files.pythonhosted.org/packages/66/15/6ee23553b6bfd82670207ead921f4d8ef14c107e5e11443b04caeb5ab5ec/protobuf-6.33.4-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:2fe67f6c014c84f655ee06f6f66213f9254b3a8b6bda6cda0ccd4232c73c06f0", size = 427612, upload-time = "2026-01-12T18:33:32.646Z" }, + { url = "https://files.pythonhosted.org/packages/2b/48/d301907ce6d0db75f959ca74f44b475a9caa8fcba102d098d3c3dd0f2d3f/protobuf-6.33.4-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:757c978f82e74d75cba88eddec479df9b99a42b31193313b75e492c06a51764e", size = 324484, upload-time = "2026-01-12T18:33:33.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/1c/e53078d3f7fe710572ab2dcffd993e1e3b438ae71cfc031b71bae44fcb2d/protobuf-6.33.4-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:c7c64f259c618f0bef7bee042075e390debbf9682334be2b67408ec7c1c09ee6", size = 339256, upload-time = "2026-01-12T18:33:35.231Z" }, + { url = "https://files.pythonhosted.org/packages/e8/8e/971c0edd084914f7ee7c23aa70ba89e8903918adca179319ee94403701d5/protobuf-6.33.4-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:3df850c2f8db9934de4cf8f9152f8dc2558f49f298f37f90c517e8e5c84c30e9", size = 323311, upload-time = "2026-01-12T18:33:36.305Z" }, + { url = "https://files.pythonhosted.org/packages/75/b1/1dc83c2c661b4c62d56cc081706ee33a4fc2835bd90f965baa2663ef7676/protobuf-6.33.4-py3-none-any.whl", hash = "sha256:1fe3730068fcf2e595816a6c34fe66eeedd37d51d0400b72fabc848811fdc1bc", size = 170532, upload-time = "2026-01-12T18:33:39.199Z" }, ] [[package]] name = "psutil" -version = "7.2.0" +version = "7.2.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/be/7c/31d1c3ceb1260301f87565f50689dc6da3db427ece1e1e012af22abca54e/psutil-7.2.0.tar.gz", hash = "sha256:2e4f8e1552f77d14dc96fb0f6240c5b34a37081c0889f0853b3b29a496e5ef64", size = 489863, upload-time = "2025-12-23T20:26:24.616Z" } +sdist = { url = "https://files.pythonhosted.org/packages/73/cb/09e5184fb5fc0358d110fc3ca7f6b1d033800734d34cac10f4136cfac10e/psutil-7.2.1.tar.gz", hash = "sha256:f7583aec590485b43ca601dd9cea0dcd65bd7bb21d30ef4ddbf4ea6b5ed1bdd3", size = 490253, upload-time = "2025-12-29T08:26:00.169Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/40/c5/a49160bf3e165b7b93a60579a353cf5d939d7f878fe5fd369110f1d18043/psutil-7.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:977a2fcd132d15cb05b32b2d85b98d087cad039b0ce435731670ba74da9e6133", size = 128116, upload-time = "2025-12-23T20:26:53.516Z" }, - { url = "https://files.pythonhosted.org/packages/10/a1/c75feb480f60cd768fb6ed00ac362a16a33e5076ec8475a22d8162fb2659/psutil-7.2.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:24151011c21fadd94214d7139d7c6c54569290d7e553989bdf0eab73b13beb8c", size = 128925, upload-time = "2025-12-23T20:26:55.573Z" }, - { url = "https://files.pythonhosted.org/packages/12/ff/e93136587c00a543f4bc768b157fac2c47cd77b180d4f4e5c6efb6ea53a2/psutil-7.2.0-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:91f211ba9279e7c61d9d8f84b713cfc38fa161cb0597d5cb3f1ca742f6848254", size = 154666, upload-time = "2025-12-23T20:26:57.312Z" }, - { url = "https://files.pythonhosted.org/packages/b8/dd/4c2de9c3827c892599d277a69d2224136800870a8a88a80981de905de28d/psutil-7.2.0-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f37415188b7ea98faf90fed51131181646c59098b077550246e2e092e127418b", size = 156109, upload-time = "2025-12-23T20:26:58.851Z" }, - { url = "https://files.pythonhosted.org/packages/81/3f/090943c682d3629968dd0b04826ddcbc760ee1379021dbe316e2ddfcd01b/psutil-7.2.0-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0d12c7ce6ed1128cd81fd54606afa054ac7dbb9773469ebb58cf2f171c49f2ac", size = 148081, upload-time = "2025-12-23T20:27:01.318Z" }, - { url = "https://files.pythonhosted.org/packages/c4/88/c39648ebb8ec182d0364af53cdefe6eddb5f3872ba718b5855a8ff65d6d4/psutil-7.2.0-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ca0faef7976530940dcd39bc5382d0d0d5eb023b186a4901ca341bd8d8684151", size = 147376, upload-time = "2025-12-23T20:27:03.347Z" }, - { url = "https://files.pythonhosted.org/packages/01/a2/5b39e08bd9b27476bc7cce7e21c71a481ad60b81ffac49baf02687a50d7f/psutil-7.2.0-cp37-abi3-win_amd64.whl", hash = "sha256:abdb74137ca232d20250e9ad471f58d500e7743bc8253ba0bfbf26e570c0e437", size = 136910, upload-time = "2025-12-23T20:27:05.289Z" }, - { url = "https://files.pythonhosted.org/packages/59/54/53839db1258c1eaeb4ded57ff202144ebc75b23facc05a74fd98d338b0c6/psutil-7.2.0-cp37-abi3-win_arm64.whl", hash = "sha256:284e71038b3139e7ab3834b63b3eb5aa5565fcd61a681ec746ef9a0a8c457fd2", size = 133807, upload-time = "2025-12-23T20:27:06.825Z" }, + { url = "https://files.pythonhosted.org/packages/c5/cf/5180eb8c8bdf6a503c6919f1da28328bd1e6b3b1b5b9d5b01ae64f019616/psutil-7.2.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b2e953fcfaedcfbc952b44744f22d16575d3aa78eb4f51ae74165b4e96e55f42", size = 128137, upload-time = "2025-12-29T08:26:27.759Z" }, + { url = "https://files.pythonhosted.org/packages/c5/2c/78e4a789306a92ade5000da4f5de3255202c534acdadc3aac7b5458fadef/psutil-7.2.1-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:05cc68dbb8c174828624062e73078e7e35406f4ca2d0866c272c2410d8ef06d1", size = 128947, upload-time = "2025-12-29T08:26:29.548Z" }, + { url = "https://files.pythonhosted.org/packages/29/f8/40e01c350ad9a2b3cb4e6adbcc8a83b17ee50dd5792102b6142385937db5/psutil-7.2.1-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e38404ca2bb30ed7267a46c02f06ff842e92da3bb8c5bfdadbd35a5722314d8", size = 154694, upload-time = "2025-12-29T08:26:32.147Z" }, + { url = "https://files.pythonhosted.org/packages/06/e4/b751cdf839c011a9714a783f120e6a86b7494eb70044d7d81a25a5cd295f/psutil-7.2.1-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab2b98c9fc19f13f59628d94df5cc4cc4844bc572467d113a8b517d634e362c6", size = 156136, upload-time = "2025-12-29T08:26:34.079Z" }, + { url = "https://files.pythonhosted.org/packages/44/ad/bbf6595a8134ee1e94a4487af3f132cef7fce43aef4a93b49912a48c3af7/psutil-7.2.1-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:f78baafb38436d5a128f837fab2d92c276dfb48af01a240b861ae02b2413ada8", size = 148108, upload-time = "2025-12-29T08:26:36.225Z" }, + { url = "https://files.pythonhosted.org/packages/1c/15/dd6fd869753ce82ff64dcbc18356093471a5a5adf4f77ed1f805d473d859/psutil-7.2.1-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:99a4cd17a5fdd1f3d014396502daa70b5ec21bf4ffe38393e152f8e449757d67", size = 147402, upload-time = "2025-12-29T08:26:39.21Z" }, + { url = "https://files.pythonhosted.org/packages/34/68/d9317542e3f2b180c4306e3f45d3c922d7e86d8ce39f941bb9e2e9d8599e/psutil-7.2.1-cp37-abi3-win_amd64.whl", hash = "sha256:b1b0671619343aa71c20ff9767eced0483e4fc9e1f489d50923738caf6a03c17", size = 136938, upload-time = "2025-12-29T08:26:41.036Z" }, + { url = "https://files.pythonhosted.org/packages/3e/73/2ce007f4198c80fcf2cb24c169884f833fe93fbc03d55d302627b094ee91/psutil-7.2.1-cp37-abi3-win_arm64.whl", hash = "sha256:0d67c1822c355aa6f7314d92018fb4268a76668a536f133599b91edd48759442", size = 133836, upload-time = "2025-12-29T08:26:43.086Z" }, ] [[package]] @@ -1859,167 +1856,167 @@ name = "pyobjc" version = "12.1" 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-arkit", marker = "platform_release >= '25.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-compositorservices", marker = "platform_release >= '25.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-fskit", marker = "platform_release >= '24.4' 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-gamesave", marker = "platform_release >= '25.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-securityui", marker = "platform_release >= '24.4' and 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-arkit", marker = "platform_release >= '25.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-compositorservices", marker = "platform_release >= '25.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-fskit", marker = "platform_release >= '24.4'" }, + { 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-gamesave", marker = "platform_release >= '25.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-securityui", marker = "platform_release >= '24.4'" }, + { 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/17/06/d77639ba166cc09aed2d32ae204811b47bc5d40e035cdc9bff7fff72ec5f/pyobjc-12.1.tar.gz", hash = "sha256:686d6db3eb3182fac9846b8ce3eedf4c7d2680b21b8b8d6e6df054a17e92a12d", size = 11345, upload-time = "2025-11-14T10:07:28.155Z" } wheels = [ @@ -2041,9 +2038,9 @@ name = "pyobjc-framework-accessibility" version = "12.1" 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/2d/87/8ca40428d05a668fecc638f2f47dba86054dbdc35351d247f039749de955/pyobjc_framework_accessibility-12.1.tar.gz", hash = "sha256:5ff362c3425edc242d49deec11f5f3e26e565cefb6a2872eda59ab7362149772", size = 29800, upload-time = "2025-11-14T10:08:31.949Z" } wheels = [ @@ -2056,8 +2053,8 @@ name = "pyobjc-framework-accounts" version = "12.1" 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/10/f6fe336c7624d6753c1f6edac102310ce4434d49b548c479e8e6420d4024/pyobjc_framework_accounts-12.1.tar.gz", hash = "sha256:76d62c5e7b831eb8f4c9ca6abaf79d9ed961dfffe24d89a041fb1de97fe56a3e", size = 15202, upload-time = "2025-11-14T10:08:33.995Z" } wheels = [ @@ -2069,8 +2066,8 @@ name = "pyobjc-framework-addressbook" version = "12.1" 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/18/28/0404af2a1c6fa8fd266df26fb6196a8f3fb500d6fe3dab94701949247bea/pyobjc_framework_addressbook-12.1.tar.gz", hash = "sha256:c48b740cf981103cef1743d0804a226d86481fcb839bd84b80e9a586187e8000", size = 44359, upload-time = "2025-11-14T10:08:37.687Z" } wheels = [ @@ -2083,8 +2080,8 @@ name = "pyobjc-framework-adservices" version = "12.1" 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/19/04/1c3d3e0a1ac981664f30b33407dcdf8956046ecde6abc88832cf2aa535f4/pyobjc_framework_adservices-12.1.tar.gz", hash = "sha256:7a31fc8d5c6fd58f012db87c89ba581361fc905114bfb912e0a3a87475c02183", size = 11793, upload-time = "2025-11-14T10:08:39.56Z" } wheels = [ @@ -2096,8 +2093,8 @@ name = "pyobjc-framework-adsupport" version = "12.1" 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/77/f26a2e9994d4df32e9b3680c8014e350b0f1c78d7673b3eba9de2e04816f/pyobjc_framework_adsupport-12.1.tar.gz", hash = "sha256:9a68480e76de567c339dca29a8c739d6d7b5cad30e1cd585ff6e49ec2fc283dd", size = 11645, upload-time = "2025-11-14T10:08:41.439Z" } wheels = [ @@ -2109,8 +2106,8 @@ name = "pyobjc-framework-applescriptkit" version = "12.1" 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/cd/f1/e0c07b2a9eb98f1a2050f153d287a52a92f873eeddb41b74c52c144d8767/pyobjc_framework_applescriptkit-12.1.tar.gz", hash = "sha256:cb09f88cf0ad9753dedc02720065818f854b50e33eb4194f0ea34de6d7a3eb33", size = 11451, upload-time = "2025-11-14T10:08:43.328Z" } wheels = [ @@ -2122,8 +2119,8 @@ name = "pyobjc-framework-applescriptobjc" version = "12.1" 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/4b/e4d1592207cbe17355e01828bdd11dd58f31356108f6a49f5e0484a5df50/pyobjc_framework_applescriptobjc-12.1.tar.gz", hash = "sha256:dce080ed07409b0dda2fee75d559bd312ea1ef0243a4338606440f282a6a0f5f", size = 11588, upload-time = "2025-11-14T10:08:45.037Z" } wheels = [ @@ -2135,10 +2132,10 @@ name = "pyobjc-framework-applicationservices" version = "12.1" 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/be/6a/d4e613c8e926a5744fc47a9e9fea08384a510dc4f27d844f7ad7a2d793bd/pyobjc_framework_applicationservices-12.1.tar.gz", hash = "sha256:c06abb74f119bc27aeb41bf1aef8102c0ae1288aec1ac8665ea186a067a8945b", size = 103247, upload-time = "2025-11-14T10:08:52.18Z" } wheels = [ @@ -2151,8 +2148,8 @@ name = "pyobjc-framework-apptrackingtransparency" version = "12.1" 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/0d/de/f24348982ecab0cb13067c348fc5fbc882c60d704ca290bada9a2b3e594b/pyobjc_framework_apptrackingtransparency-12.1.tar.gz", hash = "sha256:e25bf4e4dfa2d929993ee8e852b28fdf332fa6cde0a33328fdc3b2f502fa50ec", size = 12407, upload-time = "2025-11-14T10:08:54.118Z" } wheels = [ @@ -2164,8 +2161,8 @@ name = "pyobjc-framework-arkit" version = "12.1" 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/c9/8b/843fe08e696bca8e7fc129344965ab6280f8336f64f01ba0a8862d219c3f/pyobjc_framework_arkit-12.1.tar.gz", hash = "sha256:0c5c6b702926179700b68ba29b8247464c3b609fd002a07a3308e72cfa953adf", size = 35814, upload-time = "2025-11-14T10:08:57.55Z" } wheels = [ @@ -2177,8 +2174,8 @@ name = "pyobjc-framework-audiovideobridging" version = "12.1" 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/51/f81581e7a3c5cb6c9254c6f1e1ee1d614930493761dec491b5b0d49544b9/pyobjc_framework_audiovideobridging-12.1.tar.gz", hash = "sha256:6230ace6bec1f38e8a727c35d054a7be54e039b3053f98e6dd8d08d6baee2625", size = 38457, upload-time = "2025-11-14T10:09:01.122Z" } wheels = [ @@ -2191,8 +2188,8 @@ name = "pyobjc-framework-authenticationservices" version = "12.1" 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/6c/18/86218de3bf67fc1d810065f353d9df70c740de567ebee8550d476cb23862/pyobjc_framework_authenticationservices-12.1.tar.gz", hash = "sha256:cef71faeae2559f5c0ff9a81c9ceea1c81108e2f4ec7de52a98c269feff7a4b6", size = 58683, upload-time = "2025-11-14T10:09:06.003Z" } wheels = [ @@ -2205,8 +2202,8 @@ name = "pyobjc-framework-automaticassessmentconfiguration" version = "12.1" 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/24/080afe8189c47c4bb3daa191ccfd962400ca31a67c14b0f7c2d002c2e249/pyobjc_framework_automaticassessmentconfiguration-12.1.tar.gz", hash = "sha256:2b732c02d9097682ca16e48f5d3b10056b740bc091e217ee4d5715194c8970b1", size = 21895, upload-time = "2025-11-14T10:09:08.779Z" } wheels = [ @@ -2219,8 +2216,8 @@ name = "pyobjc-framework-automator" version = "12.1" 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/08/362bf6ac2bba393c46cf56078d4578b692b56857c385e47690637a72f0dd/pyobjc_framework_automator-12.1.tar.gz", hash = "sha256:7491a99347bb30da3a3f744052a03434ee29bee3e2ae520576f7e796740e4ba7", size = 186068, upload-time = "2025-11-14T10:09:20.82Z" } wheels = [ @@ -2233,11 +2230,11 @@ name = "pyobjc-framework-avfoundation" version = "12.1" 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/cd/42/c026ab308edc2ed5582d8b4b93da6b15d1b6557c0086914a4aabedd1f032/pyobjc_framework_avfoundation-12.1.tar.gz", hash = "sha256:eda0bb60be380f9ba2344600c4231dd58a3efafa99fdc65d3673ecfbb83f6fcb", size = 310047, upload-time = "2025-11-14T10:09:40.069Z" } wheels = [ @@ -2250,9 +2247,9 @@ name = "pyobjc-framework-avkit" version = "12.1" 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/34/a9/e44db1a1f26e2882c140f1d502d508b1f240af9048909dcf1e1a687375b4/pyobjc_framework_avkit-12.1.tar.gz", hash = "sha256:a5c0ddb0cb700f9b09c8afeca2c58952d554139e9bb078236d2355b1fddfb588", size = 28473, upload-time = "2025-11-14T10:09:43.105Z" } wheels = [ @@ -2265,8 +2262,8 @@ name = "pyobjc-framework-avrouting" version = "12.1" 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/6e/83/15bf6c28ec100dae7f92d37c9e117b3b4ee6b4873db062833e16f1cfd6c4/pyobjc_framework_avrouting-12.1.tar.gz", hash = "sha256:6a6c5e583d14f6501df530a9d0559a32269a821fc8140e3646015f097155cd1c", size = 20031, upload-time = "2025-11-14T10:09:45.701Z" } wheels = [ @@ -2279,8 +2276,8 @@ name = "pyobjc-framework-backgroundassets" version = "12.1" 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/34/d1/e917fba82790495152fd3508c5053827658881cf7e9887ba60def5e3f221/pyobjc_framework_backgroundassets-12.1.tar.gz", hash = "sha256:8da34df9ae4519c360c429415477fdaf3fbba5addbc647b3340b8783454eb419", size = 26210, upload-time = "2025-11-14T10:09:48.792Z" } wheels = [ @@ -2293,11 +2290,11 @@ name = "pyobjc-framework-browserenginekit" version = "12.1" 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/5d/b9/39f9de1730e6f8e73be0e4f0c6087cd9439cbe11645b8052d22e1fb8e69b/pyobjc_framework_browserenginekit-12.1.tar.gz", hash = "sha256:6a1a34a155778ab55ab5f463e885f2a3b4680231264e1fe078e62ddeccce49ed", size = 29120, upload-time = "2025-11-14T10:09:51.582Z" } wheels = [ @@ -2310,8 +2307,8 @@ name = "pyobjc-framework-businesschat" version = "12.1" 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/da/bc09b6ed19e9ea38ecca9387c291ca11fa680a8132d82b27030f82551c23/pyobjc_framework_businesschat-12.1.tar.gz", hash = "sha256:f6fa3a8369a1a51363e1757530128741d9d09ed90692a1d6777a4c0fbad25868", size = 12055, upload-time = "2025-11-14T10:09:53.436Z" } wheels = [ @@ -2323,8 +2320,8 @@ name = "pyobjc-framework-calendarstore" version = "12.1" 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/88/41/ae955d1c44dcc18b5b9df45c679e9a08311a0f853b9d981bca760cf1eef2/pyobjc_framework_calendarstore-12.1.tar.gz", hash = "sha256:f9a798d560a3c99ad4c0d2af68767bc5695d8b1aabef04d8377861cd1d6d1670", size = 52272, upload-time = "2025-11-14T10:09:58.48Z" } wheels = [ @@ -2336,8 +2333,8 @@ name = "pyobjc-framework-callkit" version = "12.1" 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/1a/c0/1859d4532d39254df085309aff55b85323576f00a883626325af40da4653/pyobjc_framework_callkit-12.1.tar.gz", hash = "sha256:fd6dc9688b785aab360139d683be56f0844bf68bf5e45d0eb770cb68221083cc", size = 29171, upload-time = "2025-11-14T10:10:01.336Z" } wheels = [ @@ -2350,8 +2347,8 @@ name = "pyobjc-framework-carbon" version = "12.1" 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/0f/9ab8e518a4e5ac4a1e2fdde38a054c32aef82787ff7f30927345c18b7765/pyobjc_framework_carbon-12.1.tar.gz", hash = "sha256:57a72807db252d5746caccc46da4bd20ff8ea9e82109af9f72735579645ff4f0", size = 37293, upload-time = "2025-11-14T10:10:04.464Z" } wheels = [ @@ -2363,8 +2360,8 @@ name = "pyobjc-framework-cfnetwork" version = "12.1" 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/d2/6a/f5f0f191956e187db85312cbffcc41bf863670d121b9190b4a35f0d36403/pyobjc_framework_cfnetwork-12.1.tar.gz", hash = "sha256:2d16e820f2d43522c793f55833fda89888139d7a84ca5758548ba1f3a325a88d", size = 44383, upload-time = "2025-11-14T10:10:08.428Z" } wheels = [ @@ -2377,11 +2374,11 @@ name = "pyobjc-framework-cinematic" version = "12.1" 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/67/4e/f4cc7f9f7f66df0290c90fe445f1ff5aa514c6634f5203fe049161053716/pyobjc_framework_cinematic-12.1.tar.gz", hash = "sha256:795068c30447548c0e8614e9c432d4b288b13d5614622ef2f9e3246132329b06", size = 21215, upload-time = "2025-11-14T10:10:10.795Z" } wheels = [ @@ -2393,8 +2390,8 @@ name = "pyobjc-framework-classkit" version = "12.1" 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/ac/ef/67815278023b344a79c7e95f748f647245d6f5305136fc80615254ad447c/pyobjc_framework_classkit-12.1.tar.gz", hash = "sha256:8d1e9dd75c3d14938ff533d88b72bca2d34918e4461f418ea323bfb2498473b4", size = 26298, upload-time = "2025-11-14T10:10:13.406Z" } wheels = [ @@ -2407,11 +2404,11 @@ name = "pyobjc-framework-cloudkit" version = "12.1" 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/2d/09/762ee4f3ae8568b8e0e5392c705bc4aa1929aa454646c124ca470f1bf9fc/pyobjc_framework_cloudkit-12.1.tar.gz", hash = "sha256:1dddd38e60863f88adb3d1d37d3b4ccb9cbff48c4ef02ab50e36fa40c2379d2f", size = 53730, upload-time = "2025-11-14T10:10:17.831Z" } wheels = [ @@ -2423,7 +2420,7 @@ name = "pyobjc-framework-cocoa" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, ] sdist = { url = "https://files.pythonhosted.org/packages/02/a3/16ca9a15e77c061a9250afbae2eae26f2e1579eb8ca9462ae2d2c71e1169/pyobjc_framework_cocoa-12.1.tar.gz", hash = "sha256:5556c87db95711b985d5efdaaf01c917ddd41d148b1e52a0c66b1a2e2c5c1640", size = 2772191, upload-time = "2025-11-14T10:13:02.069Z" } wheels = [ @@ -2436,8 +2433,8 @@ name = "pyobjc-framework-collaboration" version = "12.1" 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/64/21/77fe64b39eae98412de1a0d33e9c735aa9949d53fff6b2d81403572b410b/pyobjc_framework_collaboration-12.1.tar.gz", hash = "sha256:2afa264d3233fc0a03a56789c6fefe655ffd81a2da4ba1dc79ea0c45931ad47b", size = 14299, upload-time = "2025-11-14T10:13:04.631Z" } wheels = [ @@ -2449,8 +2446,8 @@ name = "pyobjc-framework-colorsync" version = "12.1" 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/b4/706e4cc9db25b400201fc90f3edfaa1ab2d51b400b19437b043a68532078/pyobjc_framework_colorsync-12.1.tar.gz", hash = "sha256:d69dab7df01245a8c1bd536b9231c97993a5d1a2765d77692ce40ebbe6c1b8e9", size = 25269, upload-time = "2025-11-14T10:13:07.522Z" } wheels = [ @@ -2462,9 +2459,9 @@ name = "pyobjc-framework-compositorservices" version = "12.1" 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/54/c5/0ba31d7af7e464b7f7ece8c2bd09112bdb0b7260848402e79ba6aacc622c/pyobjc_framework_compositorservices-12.1.tar.gz", hash = "sha256:028e357bbee7fbd3723339a321bbe14e6da5a772708a661a13eea5f17c89e4ab", size = 23292, upload-time = "2025-11-14T10:13:10.392Z" } wheels = [ @@ -2476,8 +2473,8 @@ name = "pyobjc-framework-contacts" version = "12.1" 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/4b/a0/ce0542d211d4ea02f5cbcf72ee0a16b66b0d477a4ba5c32e00117703f2f0/pyobjc_framework_contacts-12.1.tar.gz", hash = "sha256:89bca3c5cf31404b714abaa1673577e1aaad6f2ef49d4141c6dbcc0643a789ad", size = 42378, upload-time = "2025-11-14T10:13:14.203Z" } wheels = [ @@ -2490,9 +2487,9 @@ name = "pyobjc-framework-contactsui" version = "12.1" 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/cd/0c/7bb7f898456a81d88d06a1084a42e374519d2e40a668a872b69b11f8c1f9/pyobjc_framework_contactsui-12.1.tar.gz", hash = "sha256:aaeca7c9e0c9c4e224d73636f9a558f9368c2c7422155a41fd4d7a13613a77c1", size = 18769, upload-time = "2025-11-14T10:13:16.301Z" } wheels = [ @@ -2505,8 +2502,8 @@ name = "pyobjc-framework-coreaudio" version = "12.1" 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/d1/0b884c5564ab952ff5daa949128c64815300556019c1bba0cf2ca752a1a0/pyobjc_framework_coreaudio-12.1.tar.gz", hash = "sha256:a9e72925fcc1795430496ce0bffd4ddaa92c22460a10308a7283ade830089fe1", size = 75077, upload-time = "2025-11-14T10:13:22.345Z" } wheels = [ @@ -2519,9 +2516,9 @@ name = "pyobjc-framework-coreaudiokit" version = "12.1" 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/41/1c/5c7e39b9361d4eec99b9115b593edd9825388acd594cb3b4519f8f1ac12c/pyobjc_framework_coreaudiokit-12.1.tar.gz", hash = "sha256:b83624f8de3068ab2ca279f786be0804da5cf904ff9979d96007b69ef4869e1e", size = 20137, upload-time = "2025-11-14T10:13:24.611Z" } wheels = [ @@ -2534,8 +2531,8 @@ name = "pyobjc-framework-corebluetooth" version = "12.1" 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/4b/25/d21d6cb3fd249c2c2aa96ee54279f40876a0c93e7161b3304bf21cbd0bfe/pyobjc_framework_corebluetooth-12.1.tar.gz", hash = "sha256:8060c1466d90bbb9100741a1091bb79975d9ba43911c9841599879fc45c2bbe0", size = 33157, upload-time = "2025-11-14T10:13:28.064Z" } wheels = [ @@ -2548,8 +2545,8 @@ name = "pyobjc-framework-coredata" version = "12.1" 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/3e/c5/8cd46cd4f1b7cf88bdeed3848f830ea9cdcc4e55cd0287a968a2838033fb/pyobjc_framework_coredata-12.1.tar.gz", hash = "sha256:1e47d3c5e51fdc87a90da62b97cae1bc49931a2bb064db1305827028e1fc0ffa", size = 124348, upload-time = "2025-11-14T10:13:36.435Z" } wheels = [ @@ -2562,8 +2559,8 @@ name = "pyobjc-framework-corehaptics" version = "12.1" 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/3a/2f/74a3da79d9188b05dd4be4428a819ea6992d4dfaedf7d629027cf1f57bfc/pyobjc_framework_corehaptics-12.1.tar.gz", hash = "sha256:521dd2986c8a4266d583dd9ed9ae42053b11ae7d3aa89bf53fbee88307d8db10", size = 22164, upload-time = "2025-11-14T10:13:38.941Z" } wheels = [ @@ -2575,8 +2572,8 @@ name = "pyobjc-framework-corelocation" version = "12.1" 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/79/b75885e0d75397dc2fe1ed9ca80be2b64c18b817f5fb924277cb1bf7b163/pyobjc_framework_corelocation-12.1.tar.gz", hash = "sha256:3674e9353f949d91dde6230ad68f6d5748a7f0424751e08a2c09d06050d66231", size = 53511, upload-time = "2025-11-14T10:13:43.384Z" } wheels = [ @@ -2589,8 +2586,8 @@ name = "pyobjc-framework-coremedia" version = "12.1" 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/7d/5ad600ff7aedfef8ba8f51b11d9aaacdf247b870bd14045d6e6f232e3df9/pyobjc_framework_coremedia-12.1.tar.gz", hash = "sha256:166c66a9c01e7a70103f3ca44c571431d124b9070612ef63a1511a4e6d9d84a7", size = 89566, upload-time = "2025-11-14T10:13:49.788Z" } wheels = [ @@ -2603,8 +2600,8 @@ name = "pyobjc-framework-coremediaio" version = "12.1" 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/08/8e/23baee53ccd6c011c965cff62eb55638b4088c3df27d2bf05004105d6190/pyobjc_framework_coremediaio-12.1.tar.gz", hash = "sha256:880b313b28f00b27775d630174d09e0b53d1cdbadb74216618c9dd5b3eb6806a", size = 51100, upload-time = "2025-11-14T10:13:54.277Z" } wheels = [ @@ -2617,8 +2614,8 @@ name = "pyobjc-framework-coremidi" version = "12.1" 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/75/96/2d583060a71a73c8a7e6d92f2a02675621b63c1f489f2639e020fae34792/pyobjc_framework_coremidi-12.1.tar.gz", hash = "sha256:3c6f1fd03997c3b0f20ab8545126b1ce5f0cddcc1587dffacad876c161da8c54", size = 55587, upload-time = "2025-11-14T10:13:58.903Z" } wheels = [ @@ -2631,8 +2628,8 @@ name = "pyobjc-framework-coreml" version = "12.1" 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/30/2d/baa9ea02cbb1c200683cb7273b69b4bee5070e86f2060b77e6a27c2a9d7e/pyobjc_framework_coreml-12.1.tar.gz", hash = "sha256:0d1a4216891a18775c9e0170d908714c18e4f53f9dc79fb0f5263b2aa81609ba", size = 40465, upload-time = "2025-11-14T10:14:02.265Z" } wheels = [ @@ -2645,8 +2642,8 @@ name = "pyobjc-framework-coremotion" version = "12.1" 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/2c/eb/abef7d405670cf9c844befc2330a46ee59f6ff7bac6f199bf249561a2ca6/pyobjc_framework_coremotion-12.1.tar.gz", hash = "sha256:8e1b094d34084cc8cf07bedc0630b4ee7f32b0215011f79c9e3cd09d205a27c7", size = 33851, upload-time = "2025-11-14T10:14:05.619Z" } wheels = [ @@ -2659,9 +2656,9 @@ name = "pyobjc-framework-coreservices" version = "12.1" 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/4c/b3/52338a3ff41713f7d7bccaf63bef4ba4a8f2ce0c7eaff39a3629d022a79a/pyobjc_framework_coreservices-12.1.tar.gz", hash = "sha256:fc6a9f18fc6da64c166fe95f2defeb7ac8a9836b3b03bb6a891d36035260dbaa", size = 366150, upload-time = "2025-11-14T10:14:28.133Z" } wheels = [ @@ -2674,8 +2671,8 @@ name = "pyobjc-framework-corespotlight" version = "12.1" 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/99/d0/88ca73b0cf23847af463334989dd8f98e44f801b811e7e1d8a5627ec20b4/pyobjc_framework_corespotlight-12.1.tar.gz", hash = "sha256:57add47380cd0bbb9793f50a4a4b435a90d4ebd2a33698e058cb353ddfb0d068", size = 38002, upload-time = "2025-11-14T10:14:31.948Z" } wheels = [ @@ -2688,9 +2685,9 @@ name = "pyobjc-framework-coretext" version = "12.1" 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/29/da/682c9c92a39f713bd3c56e7375fa8f1b10ad558ecb075258ab6f1cdd4a6d/pyobjc_framework_coretext-12.1.tar.gz", hash = "sha256:e0adb717738fae395dc645c9e8a10bb5f6a4277e73cba8fa2a57f3b518e71da5", size = 90124, upload-time = "2025-11-14T10:14:38.596Z" } wheels = [ @@ -2703,8 +2700,8 @@ name = "pyobjc-framework-corewlan" version = "12.1" 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/88/71/739a5d023566b506b3fd3d2412983faa95a8c16226c0dcd0f67a9294a342/pyobjc_framework_corewlan-12.1.tar.gz", hash = "sha256:a9d82ec71ef61f37e1d611caf51a4203f3dbd8caf827e98128a1afaa0fd2feb5", size = 32417, upload-time = "2025-11-14T10:14:41.921Z" } wheels = [ @@ -2717,8 +2714,8 @@ name = "pyobjc-framework-cryptotokenkit" version = "12.1" 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/7c/d03ff4f74054578577296f33bc669fce16c7827eb1a553bb372b5aab30ca/pyobjc_framework_cryptotokenkit-12.1.tar.gz", hash = "sha256:c95116b4b7a41bf5b54aff823a4ef6f4d9da4d0441996d6d2c115026a42d82f5", size = 32716, upload-time = "2025-11-14T10:14:45.024Z" } wheels = [ @@ -2731,8 +2728,8 @@ name = "pyobjc-framework-datadetection" version = "12.1" 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/db/97/9b03832695ec4d3008e6150ddfdc581b0fda559d9709a98b62815581259a/pyobjc_framework_datadetection-12.1.tar.gz", hash = "sha256:95539e46d3bc970ce890aa4a97515db10b2690597c5dd362996794572e5d5de0", size = 12323, upload-time = "2025-11-14T10:14:46.769Z" } wheels = [ @@ -2744,8 +2741,8 @@ name = "pyobjc-framework-devicecheck" version = "12.1" 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/cd/af/c676107c40d51f55d0a42043865d7246db821d01241b518ea1d3b3ef1394/pyobjc_framework_devicecheck-12.1.tar.gz", hash = "sha256:567e85fc1f567b3fe64ac1cdc323d989509331f64ee54fbcbde2001aec5adbdb", size = 12885, upload-time = "2025-11-14T10:14:48.804Z" } wheels = [ @@ -2757,8 +2754,8 @@ name = "pyobjc-framework-devicediscoveryextension" version = "12.1" 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/91/b0/e6e2ed6a7f4b689746818000a003ff7ab9c10945df66398ae8d323ae9579/pyobjc_framework_devicediscoveryextension-12.1.tar.gz", hash = "sha256:60e12445fad97ff1f83472255c943685a8f3a9d95b3126d887cfe769b7261044", size = 14718, upload-time = "2025-11-14T10:14:50.723Z" } wheels = [ @@ -2770,8 +2767,8 @@ name = "pyobjc-framework-dictionaryservices" version = "12.1" 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/7a/c0/daf03cdaf6d4e04e0cf164db358378c07facd21e4e3f8622505d72573e2c/pyobjc_framework_dictionaryservices-12.1.tar.gz", hash = "sha256:354158f3c55d66681fa903c7b3cb05a435b717fa78d0cef44d258d61156454a7", size = 10573, upload-time = "2025-11-14T10:14:53.961Z" } wheels = [ @@ -2783,8 +2780,8 @@ name = "pyobjc-framework-discrecording" version = "12.1" 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/87/8bd4544793bfcdf507174abddd02b1f077b48fab0004b3db9a63142ce7e9/pyobjc_framework_discrecording-12.1.tar.gz", hash = "sha256:6defc8ea97fb33b4d43870c673710c04c3dc48be30cdf78ba28191a922094990", size = 55607, upload-time = "2025-11-14T10:14:58.276Z" } wheels = [ @@ -2797,9 +2794,9 @@ name = "pyobjc-framework-discrecordingui" version = "12.1" 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/30/63/8667f5bb1ecb556add04e86b278cb358dc1f2f03862705cae6f09097464c/pyobjc_framework_discrecordingui-12.1.tar.gz", hash = "sha256:6793d4a1a7f3219d063f39d87f1d4ebbbb3347e35d09194a193cfe16cba718a8", size = 16450, upload-time = "2025-11-14T10:15:00.254Z" } wheels = [ @@ -2811,8 +2808,8 @@ name = "pyobjc-framework-diskarbitration" version = "12.1" 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/3a/42/f75fcabec1a0033e4c5235cc8225773f610321d565b63bf982c10c6bbee4/pyobjc_framework_diskarbitration-12.1.tar.gz", hash = "sha256:6703bc5a09b38a720c9ffca356b58f7e99fa76fc988c9ec4d87112344e63dfc2", size = 17121, upload-time = "2025-11-14T10:15:02.223Z" } wheels = [ @@ -2824,8 +2821,8 @@ name = "pyobjc-framework-dvdplayback" version = "12.1" 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/cf/dd/7859a58e8dd336c77f83feb76d502e9623c394ea09322e29a03f5bc04d32/pyobjc_framework_dvdplayback-12.1.tar.gz", hash = "sha256:279345d4b5fb2c47dd8e5c2fd289e644b6648b74f5c25079805eeb61bfc4a9cd", size = 32332, upload-time = "2025-11-14T10:15:05.257Z" } wheels = [ @@ -2837,8 +2834,8 @@ name = "pyobjc-framework-eventkit" version = "12.1" 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/b6/42/4ec97e641fdcf30896fe76476181622954cb017117b1429f634d24816711/pyobjc_framework_eventkit-12.1.tar.gz", hash = "sha256:7c1882be2f444b1d0f71e9a0cd1e9c04ad98e0261292ab741fc9de0b8bbbbae9", size = 28538, upload-time = "2025-11-14T10:15:07.878Z" } wheels = [ @@ -2850,8 +2847,8 @@ name = "pyobjc-framework-exceptionhandling" version = "12.1" 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/f1/17/5c9d4164f7ccf6b9100be0ad597a7857395dd58ea492cba4f0e9c0b77049/pyobjc_framework_exceptionhandling-12.1.tar.gz", hash = "sha256:7f0719eeea6695197fce0e7042342daa462683dc466eb6a442aad897032ab00d", size = 16694, upload-time = "2025-11-14T10:15:10.173Z" } wheels = [ @@ -2863,8 +2860,8 @@ name = "pyobjc-framework-executionpolicy" version = "12.1" 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/95/11/db765e76e7b00e1521d7bb3a61ae49b59e7573ac108da174720e5d96b61b/pyobjc_framework_executionpolicy-12.1.tar.gz", hash = "sha256:682866589365cd01d3a724d8a2781794b5cba1e152411a58825ea52d7b972941", size = 12594, upload-time = "2025-11-14T10:15:12.077Z" } wheels = [ @@ -2876,8 +2873,8 @@ name = "pyobjc-framework-extensionkit" version = "12.1" 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/d4/e9b1f74d29ad9dea3d60468d59b80e14ed3a19f9f7a25afcbc10d29c8a1e/pyobjc_framework_extensionkit-12.1.tar.gz", hash = "sha256:773987353e8aba04223dbba3149253db944abfb090c35318b3a770195b75da6d", size = 18694, upload-time = "2025-11-14T10:15:14.104Z" } wheels = [ @@ -2890,8 +2887,8 @@ name = "pyobjc-framework-externalaccessory" version = "12.1" 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/8e/35/86c097ae2fdf912c61c1276e80f3e090a3fc898c75effdf51d86afec456b/pyobjc_framework_externalaccessory-12.1.tar.gz", hash = "sha256:079f770a115d517a6ab87db1b8a62ca6cdf6c35ae65f45eecc21b491e78776c0", size = 20958, upload-time = "2025-11-14T10:15:16.419Z" } wheels = [ @@ -2904,8 +2901,8 @@ name = "pyobjc-framework-fileprovider" version = "12.1" 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/9a/724b1fae5709f8860f06a6a2a46de568f9bb8bdb2e2aae45b4e010368f51/pyobjc_framework_fileprovider-12.1.tar.gz", hash = "sha256:45034e0d00ae153c991aa01cb1fd41874650a30093e77ba73401dcce5534c8ad", size = 43071, upload-time = "2025-11-14T10:15:19.989Z" } wheels = [ @@ -2918,8 +2915,8 @@ name = "pyobjc-framework-fileproviderui" version = "12.1" 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/50/00/234f9b93f75255845df81d9d5ea20cb83ecb5c0a4e59147168b622dd0b9d/pyobjc_framework_fileproviderui-12.1.tar.gz", hash = "sha256:15296429d9db0955abc3242b2920b7a810509a85118dbc185f3ac8234e5a6165", size = 12437, upload-time = "2025-11-14T10:15:22.044Z" } wheels = [ @@ -2931,8 +2928,8 @@ name = "pyobjc-framework-findersync" version = "12.1" 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/63/c8da472e0910238a905bc48620e005a1b8ae7921701408ca13e5fb0bfb4b/pyobjc_framework_findersync-12.1.tar.gz", hash = "sha256:c513104cef0013c233bf8655b527df665ce6f840c8bc0b3781e996933d4dcfa6", size = 13507, upload-time = "2025-11-14T10:15:24.161Z" } wheels = [ @@ -2944,8 +2941,8 @@ name = "pyobjc-framework-fsevents" version = "12.1" 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/17/21f45d2bca2efc72b975f2dfeae7a163dbeabb1236c1f188578403fd4f09/pyobjc_framework_fsevents-12.1.tar.gz", hash = "sha256:a22350e2aa789dec59b62da869c1b494a429f8c618854b1383d6473f4c065a02", size = 26487, upload-time = "2025-11-14T10:15:26.796Z" } wheels = [ @@ -2958,8 +2955,8 @@ name = "pyobjc-framework-fskit" version = "12.1" 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/55/d00246d6e6d9756e129e1d94bc131c99eece2daa84b2696f6442b8a22177/pyobjc_framework_fskit-12.1.tar.gz", hash = "sha256:ec54e941cdb0b7d800616c06ca76a93685bd7119b8aa6eb4e7a3ee27658fc7ba", size = 42372, upload-time = "2025-11-14T10:15:30.411Z" } wheels = [ @@ -2972,8 +2969,8 @@ name = "pyobjc-framework-gamecenter" version = "12.1" 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/d2/f8/b5fd86f6b722d4259228922e125b50e0a6975120a1c4d957e990fb84e42c/pyobjc_framework_gamecenter-12.1.tar.gz", hash = "sha256:de4118f14c9cf93eb0316d49da410faded3609ce9cd63425e9ef878cebb7ea72", size = 31473, upload-time = "2025-11-14T10:15:33.38Z" } wheels = [ @@ -2986,8 +2983,8 @@ name = "pyobjc-framework-gamecontroller" version = "12.1" 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/21/14/353bb1fe448cd833839fd199ab26426c0248088753e63c22fe19dc07530f/pyobjc_framework_gamecontroller-12.1.tar.gz", hash = "sha256:64ed3cc4844b67f1faeb540c7cc8d512c84f70b3a4bafdb33d4663a2b2a2b1d8", size = 54554, upload-time = "2025-11-14T10:15:37.591Z" } wheels = [ @@ -3000,9 +2997,9 @@ name = "pyobjc-framework-gamekit" version = "12.1" 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/52/7b/d625c0937557f7e2e64200fdbeb867d2f6f86b2f148b8d6bfe085e32d872/pyobjc_framework_gamekit-12.1.tar.gz", hash = "sha256:014d032c3484093f1409f8f631ba8a0fd2ff7a3ae23fd9d14235340889854c16", size = 63833, upload-time = "2025-11-14T10:15:42.842Z" } wheels = [ @@ -3015,9 +3012,9 @@ name = "pyobjc-framework-gameplaykit" version = "12.1" 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/e2/11/c310bbc2526f95cce662cc1f1359bb11e2458eab0689737b4850d0f6acb7/pyobjc_framework_gameplaykit-12.1.tar.gz", hash = "sha256:935ebd806d802888969357946245d35a304c530c86f1ffe584e2cf21f0a608a8", size = 41511, upload-time = "2025-11-14T10:15:46.529Z" } wheels = [ @@ -3030,8 +3027,8 @@ name = "pyobjc-framework-gamesave" version = "12.1" 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/1f/8d05585c844535e75dbc242dd6bdfecfc613d074dcb700362d1c908fb403/pyobjc_framework_gamesave-12.1.tar.gz", hash = "sha256:eb731c97aa644e78a87838ed56d0e5bdbaae125bdc8854a7772394877312cc2e", size = 12654, upload-time = "2025-11-14T10:15:48.344Z" } wheels = [ @@ -3043,8 +3040,8 @@ name = "pyobjc-framework-healthkit" version = "12.1" 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/af/67/436630d00ba1028ea33cc9df2fc28e081481433e5075600f2ea1ff00f45e/pyobjc_framework_healthkit-12.1.tar.gz", hash = "sha256:29c5e5de54b41080b7a4b0207698ac6f600dcb9149becc9c6b3a69957e200e5c", size = 91802, upload-time = "2025-11-14T10:15:54.661Z" } wheels = [ @@ -3057,8 +3054,8 @@ name = "pyobjc-framework-imagecapturecore" version = "12.1" 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/6d/a1/39347381fc7d3cd5ab942d86af347b25c73f0ddf6f5227d8b4d8f5328016/pyobjc_framework_imagecapturecore-12.1.tar.gz", hash = "sha256:c4776c59f4db57727389d17e1ffd9c567b854b8db52198b3ccc11281711074e5", size = 46397, upload-time = "2025-11-14T10:15:58.541Z" } wheels = [ @@ -3071,8 +3068,8 @@ name = "pyobjc-framework-inputmethodkit" version = "12.1" 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/5d/b8/d33dd8b7306029bbbd80525bf833fc547e6a223c494bf69a534487283a28/pyobjc_framework_inputmethodkit-12.1.tar.gz", hash = "sha256:f63b6fe2fa7f1412eae63baea1e120e7865e3b68ccfb7d8b0a4aadb309f2b278", size = 23054, upload-time = "2025-11-14T10:16:01.464Z" } wheels = [ @@ -3085,8 +3082,8 @@ name = "pyobjc-framework-installerplugins" version = "12.1" 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/60/ca4ab04eafa388a97a521db7d60a812e2f81a3c21c2372587872e6b074f9/pyobjc_framework_installerplugins-12.1.tar.gz", hash = "sha256:1329a193bd2e92a2320a981a9a421a9b99749bade3e5914358923e94fe995795", size = 25277, upload-time = "2025-11-14T10:16:04.379Z" } wheels = [ @@ -3098,9 +3095,9 @@ name = "pyobjc-framework-instantmessage" version = "12.1" 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/d4/67/66754e0d26320ba24a33608ca94d3f38e60ee6b2d2e094cb6269b346fdd4/pyobjc_framework_instantmessage-12.1.tar.gz", hash = "sha256:f453118d5693dc3c94554791bd2aaafe32a8b03b0e3d8ec3934b44b7fdd1f7e7", size = 31217, upload-time = "2025-11-14T10:16:07.693Z" } wheels = [ @@ -3112,8 +3109,8 @@ name = "pyobjc-framework-intents" version = "12.1" 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/f1/a1/3bab6139e94b97eca098e1562f5d6840e3ff10ea1f7fd704a17111a97d5b/pyobjc_framework_intents-12.1.tar.gz", hash = "sha256:bd688c3ab34a18412f56e459e9dae29e1f4152d3c2048fcacdef5fc49dfb9765", size = 132262, upload-time = "2025-11-14T10:16:16.428Z" } wheels = [ @@ -3126,8 +3123,8 @@ name = "pyobjc-framework-intentsui" version = "12.1" 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/19/cf/f0e385b9cfbf153d68efe8d19e5ae672b59acbbfc1f9b58faaefc5ec8c9e/pyobjc_framework_intentsui-12.1.tar.gz", hash = "sha256:16bdf4b7b91c0d1ec9d5513a1182861f1b5b7af95d4f4218ff7cf03032d57f99", size = 19784, upload-time = "2025-11-14T10:16:18.716Z" } wheels = [ @@ -3140,8 +3137,8 @@ name = "pyobjc-framework-iobluetooth" version = "12.1" 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/aa/ca3944bbdfead4201b4ae6b51510942c5a7d8e5e2dc3139a071c74061fdf/pyobjc_framework_iobluetooth-12.1.tar.gz", hash = "sha256:8a434118812f4c01dfc64339d41fe8229516864a59d2803e9094ee4cbe2b7edd", size = 155241, upload-time = "2025-11-14T10:16:28.896Z" } wheels = [ @@ -3154,8 +3151,8 @@ name = "pyobjc-framework-iobluetoothui" version = "12.1" 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/8f/39/31d9a4e8565a4b1ec0a9ad81480dc0879f3df28799eae3bc22d1dd53705d/pyobjc_framework_iobluetoothui-12.1.tar.gz", hash = "sha256:81f8158bdfb2966a574b6988eb346114d6a4c277300c8c0a978c272018184e6f", size = 16495, upload-time = "2025-11-14T10:16:31.212Z" } wheels = [ @@ -3167,8 +3164,8 @@ name = "pyobjc-framework-iosurface" version = "12.1" 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/07/61/0f12ad67a72d434e1c84b229ec760b5be71f53671ee9018593961c8bfeb7/pyobjc_framework_iosurface-12.1.tar.gz", hash = "sha256:4b9d0c66431aa296f3ca7c4f84c00dc5fc961194830ad7682fdbbc358fa0db55", size = 17690, upload-time = "2025-11-14T10:16:33.282Z" } wheels = [ @@ -3180,8 +3177,8 @@ name = "pyobjc-framework-ituneslibrary" version = "12.1" 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/46/d9bcec88675bf4ee887b9707bd245e2a793e7cb916cf310f286741d54b1f/pyobjc_framework_ituneslibrary-12.1.tar.gz", hash = "sha256:7f3aa76c4d05f6fa6015056b88986cacbda107c3f29520dd35ef0936c7367a6e", size = 23730, upload-time = "2025-11-14T10:16:36.127Z" } wheels = [ @@ -3193,8 +3190,8 @@ name = "pyobjc-framework-kernelmanagement" version = "12.1" 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/7e/ecbac119866e8ac2cce700d7a48a4297946412ac7cbc243a7084a6582fb1/pyobjc_framework_kernelmanagement-12.1.tar.gz", hash = "sha256:488062893ac2074e0c8178667bf864a21f7909c11111de2f6a10d9bc579df59d", size = 11773, upload-time = "2025-11-14T10:16:38.216Z" } wheels = [ @@ -3206,8 +3203,8 @@ name = "pyobjc-framework-latentsemanticmapping" version = "12.1" 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/88/3c/b621dac54ae8e77ac25ee75dd93e310e2d6e0faaf15b8da13513258d6657/pyobjc_framework_latentsemanticmapping-12.1.tar.gz", hash = "sha256:f0b1fa823313eefecbf1539b4ed4b32461534b7a35826c2cd9f6024411dc9284", size = 15526, upload-time = "2025-11-14T10:16:40.149Z" } wheels = [ @@ -3219,8 +3216,8 @@ name = "pyobjc-framework-launchservices" version = "12.1" 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/37/d0/24673625922b0ad21546be5cf49e5ec1afaa4553ae92f222adacdc915907/pyobjc_framework_launchservices-12.1.tar.gz", hash = "sha256:4d2d34c9bd6fb7f77566155b539a2c70283d1f0326e1695da234a93ef48352dc", size = 20470, upload-time = "2025-11-14T10:16:42.499Z" } wheels = [ @@ -3232,8 +3229,8 @@ name = "pyobjc-framework-libdispatch" version = "12.1" 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/26/e8/75b6b9b3c88b37723c237e5a7600384ea2d84874548671139db02e76652b/pyobjc_framework_libdispatch-12.1.tar.gz", hash = "sha256:4035535b4fae1b5e976f3e0e38b6e3442ffea1b8aa178d0ca89faa9b8ecdea41", size = 38277, upload-time = "2025-11-14T10:16:46.235Z" } wheels = [ @@ -3246,8 +3243,8 @@ name = "pyobjc-framework-libxpc" version = "12.1" 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/16/e4/364db7dc26f235e3d7eaab2f92057f460b39800bffdec3128f113388ac9f/pyobjc_framework_libxpc-12.1.tar.gz", hash = "sha256:e46363a735f3ecc9a2f91637750623f90ee74f9938a4e7c833e01233174af44d", size = 35186, upload-time = "2025-11-14T10:16:49.503Z" } wheels = [ @@ -3260,9 +3257,9 @@ name = "pyobjc-framework-linkpresentation" version = "12.1" 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/e3/58/c0c5919d883485ccdb6dccd8ecfe50271d2f6e6ab7c9b624789235ccec5a/pyobjc_framework_linkpresentation-12.1.tar.gz", hash = "sha256:84df6779591bb93217aa8bd82c10e16643441678547d2d73ba895475a02ade94", size = 13330, upload-time = "2025-11-14T10:16:52.169Z" } wheels = [ @@ -3274,9 +3271,9 @@ name = "pyobjc-framework-localauthentication" version = "12.1" 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/8d/0e/7e5d9a58bb3d5b79a75d925557ef68084171526191b1c0929a887a553d4f/pyobjc_framework_localauthentication-12.1.tar.gz", hash = "sha256:2284f587d8e1206166e4495b33f420c1de486c36c28c4921d09eec858a699d05", size = 29947, upload-time = "2025-11-14T10:16:54.923Z" } wheels = [ @@ -3289,9 +3286,9 @@ name = "pyobjc-framework-localauthenticationembeddedui" version = "12.1" 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/31/20/83ab4180e29b9a4a44d735c7f88909296c6adbe6250e8e00a156aff753e1/pyobjc_framework_localauthenticationembeddedui-12.1.tar.gz", hash = "sha256:a15ec44bf2769c872e86c6b550b6dd4f58d4eda40ad9ff00272a67d279d1d4e9", size = 13611, upload-time = "2025-11-14T10:16:57.145Z" } wheels = [ @@ -3303,8 +3300,8 @@ name = "pyobjc-framework-mailkit" version = "12.1" 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/98/3d9028620c1cd32ff4fb031155aba3b5511e980cdd114dd51383be9cb51b/pyobjc_framework_mailkit-12.1.tar.gz", hash = "sha256:d5574b7259baec17096410efcaacf5d45c7bb5f893d4c25cbb7072369799b652", size = 20996, upload-time = "2025-11-14T10:16:59.449Z" } wheels = [ @@ -3316,10 +3313,10 @@ name = "pyobjc-framework-mapkit" version = "12.1" 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/36/bb/2a668203c20e509a648c35e803d79d0c7f7816dacba74eb5ad8acb186790/pyobjc_framework_mapkit-12.1.tar.gz", hash = "sha256:dbc32dc48e821aaa9b4294402c240adbc1c6834e658a07677b7c19b7990533c5", size = 63520, upload-time = "2025-11-14T10:17:04.221Z" } wheels = [ @@ -3332,8 +3329,8 @@ name = "pyobjc-framework-mediaaccessibility" version = "12.1" 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/e0/10/dc1007e56944ed2e981e69e7b2fed2b2202c79b0d5b742b29b1081d1cbdd/pyobjc_framework_mediaaccessibility-12.1.tar.gz", hash = "sha256:cc4e3b1d45e84133d240318d53424eff55968f5c6873c2c53267598853445a3f", size = 16325, upload-time = "2025-11-14T10:17:07.454Z" } wheels = [ @@ -3345,10 +3342,10 @@ name = "pyobjc-framework-mediaextension" version = "12.1" 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/d6/aa/1e8015711df1cdb5e4a0aa0ed4721409d39971ae6e1e71915e3ab72423a3/pyobjc_framework_mediaextension-12.1.tar.gz", hash = "sha256:44409d63cc7d74e5724a68e3f9252cb62fd0fd3ccf0ca94c6a33e5c990149953", size = 39425, upload-time = "2025-11-14T10:17:11.486Z" } wheels = [ @@ -3361,9 +3358,9 @@ name = "pyobjc-framework-medialibrary" version = "12.1" 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/34/e9/848ebd02456f8fdb41b42298ec585bfed5899dbd30306ea5b0a7e4c4b341/pyobjc_framework_medialibrary-12.1.tar.gz", hash = "sha256:690dcca09b62511df18f58e8566cb33d9652aae09fe63a83f594bd018b5edfcd", size = 15995, upload-time = "2025-11-14T10:17:15.45Z" } wheels = [ @@ -3375,8 +3372,8 @@ name = "pyobjc-framework-mediaplayer" version = "12.1" 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/e9/f0/851f6f47e11acbd62d5f5dcb8274afc969135e30018591f75bf3cbf6417f/pyobjc_framework_mediaplayer-12.1.tar.gz", hash = "sha256:5ef3f669bdf837d87cdb5a486ec34831542360d14bcba099c7c2e0383380794c", size = 35402, upload-time = "2025-11-14T10:17:18.97Z" } wheels = [ @@ -3388,8 +3385,8 @@ name = "pyobjc-framework-mediatoolbox" version = "12.1" 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/71/be5879380a161f98212a336b432256f307d1dcbaaaeb8ec988aea2ada2cd/pyobjc_framework_mediatoolbox-12.1.tar.gz", hash = "sha256:385b48746a5f08756ee87afc14037e552954c427ed5745d7ece31a21a7bad5ab", size = 22305, upload-time = "2025-11-14T10:17:22.501Z" } wheels = [ @@ -3402,8 +3399,8 @@ name = "pyobjc-framework-metal" version = "12.1" 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/06/a84f7eb8561d5631954b9458cfca04b690b80b5b85ce70642bc89335f52a/pyobjc_framework_metal-12.1.tar.gz", hash = "sha256:bb554877d5ee2bf3f340ad88e8fe1b85baab7b5ec4bd6ae0f4f7604147e3eae7", size = 181847, upload-time = "2025-11-14T10:17:34.157Z" } wheels = [ @@ -3416,8 +3413,8 @@ name = "pyobjc-framework-metalfx" version = "12.1" 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/f1/09/ce5c74565677fde66de3b9d35389066b19e5d1bfef9d9a4ad80f0c858c0c/pyobjc_framework_metalfx-12.1.tar.gz", hash = "sha256:1551b686fb80083a97879ce0331bdb1d4c9b94557570b7ecc35ebf40ff65c90b", size = 29470, upload-time = "2025-11-14T10:17:37.16Z" } wheels = [ @@ -3430,9 +3427,9 @@ name = "pyobjc-framework-metalkit" version = "12.1" 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/14/15/5091147aae12d4011a788b93971c3376aaaf9bf32aa935a2c9a06a71e18b/pyobjc_framework_metalkit-12.1.tar.gz", hash = "sha256:14cc5c256f0e3471b412a5b3582cb2a0d36d3d57401a8aa09e433252d1c34824", size = 25473, upload-time = "2025-11-14T10:17:39.721Z" } wheels = [ @@ -3445,8 +3442,8 @@ name = "pyobjc-framework-metalperformanceshaders" version = "12.1" 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/c5/68/58da38e54aa0d8c19f0d3084d8c84e92d54cc8c9254041f07119d86aa073/pyobjc_framework_metalperformanceshaders-12.1.tar.gz", hash = "sha256:b198e755b95a1de1525e63c3b14327ae93ef1d88359e6be1ce554a3493755b50", size = 137301, upload-time = "2025-11-14T10:17:49.554Z" } wheels = [ @@ -3459,8 +3456,8 @@ name = "pyobjc-framework-metalperformanceshadersgraph" version = "12.1" 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/a7/56/7ad0cd085532f7bdea9a8d4e9a2dfde376d26dd21e5eabdf1a366040eff8/pyobjc_framework_metalperformanceshadersgraph-12.1.tar.gz", hash = "sha256:b8fd017b47698037d7b172d41bed7a4835f4c4f2a288235819d200005f89ee35", size = 42992, upload-time = "2025-11-14T10:17:53.502Z" } wheels = [ @@ -3472,8 +3469,8 @@ name = "pyobjc-framework-metrickit" version = "12.1" 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/ba/13/5576ddfbc0b174810a49171e2dbe610bdafd3b701011c6ecd9b3a461de8a/pyobjc_framework_metrickit-12.1.tar.gz", hash = "sha256:77841daf6b36ba0c19df88545fd910c0516acf279e6b7b4fa0a712a046eaa9f1", size = 27627, upload-time = "2025-11-14T10:17:56.353Z" } wheels = [ @@ -3486,8 +3483,8 @@ name = "pyobjc-framework-mlcompute" version = "12.1" 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/00/69/15f8ce96c14383aa783c8e4bc1e6d936a489343bb197b8e71abb3ddc1cb8/pyobjc_framework_mlcompute-12.1.tar.gz", hash = "sha256:3281db120273dcc56e97becffd5cedf9c62042788289f7be6ea067a863164f1e", size = 40698, upload-time = "2025-11-14T10:17:59.792Z" } wheels = [ @@ -3499,9 +3496,9 @@ name = "pyobjc-framework-modelio" version = "12.1" 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/b4/11/32c358111b623b4a0af9e90470b198fffc068b45acac74e1ba711aee7199/pyobjc_framework_modelio-12.1.tar.gz", hash = "sha256:d041d7bca7c2a4526344d3e593347225b7a2e51a499b3aa548895ba516d1bdbb", size = 66482, upload-time = "2025-11-14T10:18:04.92Z" } wheels = [ @@ -3514,8 +3511,8 @@ name = "pyobjc-framework-multipeerconnectivity" version = "12.1" 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/87/35/0d0bb6881004cb238cfd7bf74f4b2e42601a1accdf27b2189ec61cf3a2dc/pyobjc_framework_multipeerconnectivity-12.1.tar.gz", hash = "sha256:7123f734b7174cacbe92a51a62b4645cc9033f6b462ff945b504b62e1b9e6c1c", size = 22816, upload-time = "2025-11-14T10:18:07.363Z" } wheels = [ @@ -3528,8 +3525,8 @@ name = "pyobjc-framework-naturallanguage" version = "12.1" 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/8a/d1/c81c0cdbb198d498edc9bc5fbb17e79b796450c17bb7541adbf502f9ad65/pyobjc_framework_naturallanguage-12.1.tar.gz", hash = "sha256:cb27a1e1e5b2913d308c49fcd2fd04ab5ea87cb60cac4a576a91ebf6a50e52f6", size = 23524, upload-time = "2025-11-14T10:18:09.883Z" } wheels = [ @@ -3541,8 +3538,8 @@ name = "pyobjc-framework-netfs" version = "12.1" 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/46/68/4bf0e5b8cc0780cf7acf0aec54def58c8bcf8d733db0bd38f5a264d1af06/pyobjc_framework_netfs-12.1.tar.gz", hash = "sha256:e8d0c25f41d7d9ced1aa2483238d0a80536df21f4b588640a72e1bdb87e75c1e", size = 14799, upload-time = "2025-11-14T10:18:11.85Z" } wheels = [ @@ -3554,8 +3551,8 @@ name = "pyobjc-framework-network" version = "12.1" 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/13/a71270a1b0a9ec979e68b8ec84b0f960e908b17b51cb3cac246a74d52b6b/pyobjc_framework_network-12.1.tar.gz", hash = "sha256:dbf736ff84d1caa41224e86ff84d34b4e9eb6918ae4e373a44d3cb597648a16a", size = 56990, upload-time = "2025-11-14T10:18:16.714Z" } wheels = [ @@ -3568,8 +3565,8 @@ name = "pyobjc-framework-networkextension" version = "12.1" 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/bf/3e/ac51dbb2efa16903e6af01f3c1f5a854c558661a7a5375c3e8767ac668e8/pyobjc_framework_networkextension-12.1.tar.gz", hash = "sha256:36abc339a7f214ab6a05cb2384a9df912f247163710741e118662bd049acfa2e", size = 62796, upload-time = "2025-11-14T10:18:21.769Z" } wheels = [ @@ -3582,8 +3579,8 @@ name = "pyobjc-framework-notificationcenter" version = "12.1" 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/c6/12/ae0fe82fb1e02365c9fe9531c9de46322f7af09e3659882212c6bf24d75e/pyobjc_framework_notificationcenter-12.1.tar.gz", hash = "sha256:2d09f5ab9dc39770bae4fa0c7cfe961e6c440c8fc465191d403633dccc941094", size = 21282, upload-time = "2025-11-14T10:18:24.51Z" } wheels = [ @@ -3596,8 +3593,8 @@ name = "pyobjc-framework-opendirectory" version = "12.1" 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/5b/11/bc2f71d3077b3bd078dccad5c0c5c57ec807fefe3d90c97b97dd0ed3d04b/pyobjc_framework_opendirectory-12.1.tar.gz", hash = "sha256:2c63ce5dd179828ef2d8f9e3961da3bfa971a57db07a6c34eedc296548a928bb", size = 61049, upload-time = "2025-11-14T10:18:29.336Z" } wheels = [ @@ -3609,8 +3606,8 @@ name = "pyobjc-framework-osakit" version = "12.1" 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/b9/bf52c555c75a83aa45782122432fa06066bb76469047f13d06fb31e585c4/pyobjc_framework_osakit-12.1.tar.gz", hash = "sha256:36ea6acf03483dc1e4344a0cce7250a9656f44277d12bc265fa86d4cbde01f23", size = 17102, upload-time = "2025-11-14T10:18:31.354Z" } wheels = [ @@ -3622,10 +3619,10 @@ name = "pyobjc-framework-oslog" version = "12.1" 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/12/42/805c9b4ac6ad25deb4215989d8fc41533d01e07ffd23f31b65620bade546/pyobjc_framework_oslog-12.1.tar.gz", hash = "sha256:d0ec6f4e3d1689d5e4341bc1130c6f24cb4ad619939f6c14d11a7e80c0ac4553", size = 21193, upload-time = "2025-11-14T10:18:33.645Z" } wheels = [ @@ -3638,8 +3635,8 @@ name = "pyobjc-framework-passkit" version = "12.1" 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/6c/d4/2afb59fb0f99eb2f03888850887e536f1ef64b303fd756283679471a5189/pyobjc_framework_passkit-12.1.tar.gz", hash = "sha256:d8c27c352e86a3549bf696504e6b25af5f2134b173d9dd60d66c6d3da53bb078", size = 53835, upload-time = "2025-11-14T10:18:37.906Z" } wheels = [ @@ -3652,8 +3649,8 @@ name = "pyobjc-framework-pencilkit" version = "12.1" 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/43/859068016bcbe7d80597d5c579de0b84b0da62c5c55cdf9cc940e9f9c0f8/pyobjc_framework_pencilkit-12.1.tar.gz", hash = "sha256:d404982d1f7a474369f3e7fea3fbd6290326143fa4138d64b6753005a6263dc4", size = 17664, upload-time = "2025-11-14T10:18:40.045Z" } wheels = [ @@ -3665,8 +3662,8 @@ name = "pyobjc-framework-phase" version = "12.1" 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/96/51/3b25eaf7ca85f38ceef892fdf066b7faa0fec716f35ea928c6ffec6ae311/pyobjc_framework_phase-12.1.tar.gz", hash = "sha256:3a69005c572f6fd777276a835115eb8359a33673d4a87e754209f99583534475", size = 32730, upload-time = "2025-11-14T10:18:43.102Z" } wheels = [ @@ -3678,8 +3675,8 @@ name = "pyobjc-framework-photos" version = "12.1" 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/53/f8a3dc7f711034d2283e289cd966fb7486028ea132a24260290ff32d3525/pyobjc_framework_photos-12.1.tar.gz", hash = "sha256:adb68aaa29e186832d3c36a0b60b0592a834e24c5263e9d78c956b2b77dce563", size = 47034, upload-time = "2025-11-14T10:18:47.27Z" } wheels = [ @@ -3692,8 +3689,8 @@ name = "pyobjc-framework-photosui" version = "12.1" 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/a5/14c538828ed1a420e047388aedc4a2d7d9292030d81bf6b1ced2ec27b6e9/pyobjc_framework_photosui-12.1.tar.gz", hash = "sha256:9141234bb9d17687f1e8b66303158eccdd45132341fbe5e892174910035f029a", size = 29886, upload-time = "2025-11-14T10:18:50.238Z" } wheels = [ @@ -3706,8 +3703,8 @@ name = "pyobjc-framework-preferencepanes" version = "12.1" 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/90/bc/e87df041d4f7f6b7721bf7996fa02aa0255939fb0fac0ecb294229765f92/pyobjc_framework_preferencepanes-12.1.tar.gz", hash = "sha256:b2a02f9049f136bdeca7642b3307637b190850e5853b74b5c372bc7d88ef9744", size = 24543, upload-time = "2025-11-14T10:18:53.259Z" } wheels = [ @@ -3719,8 +3716,8 @@ name = "pyobjc-framework-pushkit" version = "12.1" 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/a9/45/de756b62709add6d0615f86e48291ee2bee40223e7dde7bbe68a952593f0/pyobjc_framework_pushkit-12.1.tar.gz", hash = "sha256:829a2fc8f4780e75fc2a41217290ee0ff92d4ade43c42def4d7e5af436d8ae82", size = 19465, upload-time = "2025-11-14T10:18:57.727Z" } wheels = [ @@ -3733,8 +3730,8 @@ name = "pyobjc-framework-quartz" version = "12.1" 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/94/18/cc59f3d4355c9456fc945eae7fe8797003c4da99212dd531ad1b0de8a0c6/pyobjc_framework_quartz-12.1.tar.gz", hash = "sha256:27f782f3513ac88ec9b6c82d9767eef95a5cf4175ce88a1e5a65875fee799608", size = 3159099, upload-time = "2025-11-14T10:21:24.31Z" } wheels = [ @@ -3747,9 +3744,9 @@ name = "pyobjc-framework-quicklookthumbnailing" version = "12.1" 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/97/1a/b90539500e9a27c2049c388d85a824fc0704009b11e33b05009f52a6dc67/pyobjc_framework_quicklookthumbnailing-12.1.tar.gz", hash = "sha256:4f7e09e873e9bda236dce6e2f238cab571baeb75eca2e0bc0961d5fcd85f3c8f", size = 14790, upload-time = "2025-11-14T10:21:26.442Z" } wheels = [ @@ -3761,8 +3758,8 @@ name = "pyobjc-framework-replaykit" version = "12.1" 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/f8/b92af879734d91c1726227e7a03b9e68ab8d9d2bb1716d1a5c29254087f2/pyobjc_framework_replaykit-12.1.tar.gz", hash = "sha256:95801fd35c329d7302b2541f2754e6574bf36547ab869fbbf41e408dfa07268a", size = 23312, upload-time = "2025-11-14T10:21:29.18Z" } wheels = [ @@ -3775,8 +3772,8 @@ name = "pyobjc-framework-safariservices" version = "12.1" 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/3e/4b/8f896bafbdbfa180a5ba1e21a6f5dc63150c09cba69d85f68708e02866ae/pyobjc_framework_safariservices-12.1.tar.gz", hash = "sha256:6a56f71c1e692bca1f48fe7c40e4c5a41e148b4e3c6cfb185fd80a4d4a951897", size = 25165, upload-time = "2025-11-14T10:21:32.041Z" } wheels = [ @@ -3789,9 +3786,9 @@ name = "pyobjc-framework-safetykit" version = "12.1" 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/f4/bf/ad6bf60ceb61614c9c9f5758190971e9b90c45b1c7a244e45db64138b6c2/pyobjc_framework_safetykit-12.1.tar.gz", hash = "sha256:0cd4850659fb9b5632fd8ad21f2de6863e8303ff0d51c5cc9c0034aac5db08d8", size = 20086, upload-time = "2025-11-14T10:21:34.212Z" } wheels = [ @@ -3804,9 +3801,9 @@ name = "pyobjc-framework-scenekit" version = "12.1" 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/94/8c/1f4005cf0cb68f84dd98b93bbc0974ee7851bb33d976791c85e042dc2278/pyobjc_framework_scenekit-12.1.tar.gz", hash = "sha256:1bd5b866f31fd829f26feac52e807ed942254fd248115c7c742cfad41d949426", size = 101212, upload-time = "2025-11-14T10:21:41.265Z" } wheels = [ @@ -3819,9 +3816,9 @@ name = "pyobjc-framework-screencapturekit" version = "12.1" 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/2d/7f/73458db1361d2cb408f43821a1e3819318a0f81885f833d78d93bdc698e0/pyobjc_framework_screencapturekit-12.1.tar.gz", hash = "sha256:50992c6128b35ab45d9e336f0993ddd112f58b8c8c8f0892a9cb42d61bd1f4c9", size = 32573, upload-time = "2025-11-14T10:21:44.497Z" } wheels = [ @@ -3834,8 +3831,8 @@ name = "pyobjc-framework-screensaver" version = "12.1" 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/7d/99/7cfbce880cea61253a44eed594dce66c2b2fbf29e37eaedcd40cffa949e9/pyobjc_framework_screensaver-12.1.tar.gz", hash = "sha256:c4ca111317c5a3883b7eace0a9e7dd72bc6ffaa2ca954bdec918c3ab7c65c96f", size = 22229, upload-time = "2025-11-14T10:21:47.299Z" } wheels = [ @@ -3848,8 +3845,8 @@ name = "pyobjc-framework-screentime" version = "12.1" 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/10/11/ba18f905321895715dac3cae2071c2789745ae13605b283b8114b41e0459/pyobjc_framework_screentime-12.1.tar.gz", hash = "sha256:583de46b365543bbbcf27cd70eedd375d397441d64a2cf43c65286fd9c91af55", size = 13413, upload-time = "2025-11-14T10:21:49.17Z" } wheels = [ @@ -3861,8 +3858,8 @@ name = "pyobjc-framework-scriptingbridge" version = "12.1" 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/cb/adc0a09e8c4755c2281bd12803a87f36e0832a8fc853a2d663433dbb72ce/pyobjc_framework_scriptingbridge-12.1.tar.gz", hash = "sha256:0e90f866a7e6a8aeaf723d04c826657dd528c8c1b91e7a605f8bb947c74ad082", size = 20339, upload-time = "2025-11-14T10:21:51.769Z" } wheels = [ @@ -3875,8 +3872,8 @@ name = "pyobjc-framework-searchkit" version = "12.1" 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/6e/60/a38523198430e14fdef21ebe62a93c43aedd08f1f3a07ea3d96d9997db5d/pyobjc_framework_searchkit-12.1.tar.gz", hash = "sha256:ddd94131dabbbc2d7c3f17db3da87c1a712c431310eef16f07187771e7e85226", size = 30942, upload-time = "2025-11-14T10:21:55.483Z" } wheels = [ @@ -3888,8 +3885,8 @@ name = "pyobjc-framework-security" version = "12.1" 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/80/aa/796e09a3e3d5cee32ebeebb7dcf421b48ea86e28c387924608a05e3f668b/pyobjc_framework_security-12.1.tar.gz", hash = "sha256:7fecb982bd2f7c4354513faf90ba4c53c190b7e88167984c2d0da99741de6da9", size = 168044, upload-time = "2025-11-14T10:22:06.334Z" } wheels = [ @@ -3902,9 +3899,9 @@ name = "pyobjc-framework-securityfoundation" version = "12.1" 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/57/d5/c2b77e83c1585ba43e5f00c917273ba4bf7ed548c1b691f6766eb0418d52/pyobjc_framework_securityfoundation-12.1.tar.gz", hash = "sha256:1f39f4b3db6e3bd3a420aaf4923228b88e48c90692cf3612b0f6f1573302a75d", size = 12669, upload-time = "2025-11-14T10:22:09.256Z" } wheels = [ @@ -3916,9 +3913,9 @@ name = "pyobjc-framework-securityinterface" version = "12.1" 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/f9/64/bf5b5d82655112a2314422ee649f1e1e73d4381afa87e1651ce7e8444694/pyobjc_framework_securityinterface-12.1.tar.gz", hash = "sha256:deef11ad03be8d9ff77db6e7ac40f6b641ee2d72eaafcf91040537942472e88b", size = 25552, upload-time = "2025-11-14T10:22:12.098Z" } wheels = [ @@ -3931,9 +3928,9 @@ name = "pyobjc-framework-securityui" version = "12.1" 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/83/3f/d870305f5dec58cd02966ca06ac29b69fb045d8b46dfb64e2da31f295345/pyobjc_framework_securityui-12.1.tar.gz", hash = "sha256:f1435fed85edc57533c334a4efc8032170424b759da184cb7a7a950ceea0e0b6", size = 12184, upload-time = "2025-11-14T10:22:14.323Z" } wheels = [ @@ -3945,9 +3942,9 @@ name = "pyobjc-framework-sensitivecontentanalysis" version = "12.1" 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/e7/ce/17bf31753e14cb4d64fffaaba2377453c4977c2c5d3cf2ff0a3db30026c7/pyobjc_framework_sensitivecontentanalysis-12.1.tar.gz", hash = "sha256:2c615ac10e93eb547b32b214cd45092056bee0e79696426fd09978dc3e670f25", size = 13745, upload-time = "2025-11-14T10:22:16.447Z" } wheels = [ @@ -3959,8 +3956,8 @@ name = "pyobjc-framework-servicemanagement" version = "12.1" 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/d0/b26c83ae96ab55013df5fedf89337d4d62311b56ce3f520fc7597d223d82/pyobjc_framework_servicemanagement-12.1.tar.gz", hash = "sha256:08120981749a698033a1d7a6ab99dbbe412c5c0d40f2b4154014b52113511c1d", size = 14585, upload-time = "2025-11-14T10:22:18.735Z" } wheels = [ @@ -3972,8 +3969,8 @@ name = "pyobjc-framework-sharedwithyou" version = "12.1" 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/0f/8b/8ab209a143c11575a857e2111acc5427fb4986b84708b21324cbcbf5591b/pyobjc_framework_sharedwithyou-12.1.tar.gz", hash = "sha256:167d84794a48f408ee51f885210c616fda1ec4bff3dd8617a4b5547f61b05caf", size = 24791, upload-time = "2025-11-14T10:22:21.248Z" } wheels = [ @@ -3986,8 +3983,8 @@ name = "pyobjc-framework-sharedwithyoucore" version = "12.1" 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/ef/84059c5774fd5435551ab7ab40b51271cfb9997b0d21f491c6b429fe57a8/pyobjc_framework_sharedwithyoucore-12.1.tar.gz", hash = "sha256:0813149eeb755d718b146ec9365eb4ca3262b6af9ff9ba7db2f7b6f4fd104518", size = 22350, upload-time = "2025-11-14T10:22:23.611Z" } wheels = [ @@ -4000,8 +3997,8 @@ name = "pyobjc-framework-shazamkit" version = "12.1" 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/ed/2c/8d82c5066cc376de68ad8c1454b7c722c7a62215e5c2f9dac5b33a6c3d42/pyobjc_framework_shazamkit-12.1.tar.gz", hash = "sha256:71db2addd016874639a224ed32b2000b858802b0370c595a283cce27f76883fe", size = 22518, upload-time = "2025-11-14T10:22:25.996Z" } wheels = [ @@ -4014,8 +4011,8 @@ name = "pyobjc-framework-social" version = "12.1" 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/21/afc6f37dfdd2cafcba0227e15240b5b0f1f4ad57621aeefda2985ac9560e/pyobjc_framework_social-12.1.tar.gz", hash = "sha256:1963db6939e92ae40dd9d68852e8f88111cbfd37a83a9fdbc9a0c08993ca7e60", size = 13184, upload-time = "2025-11-14T10:22:28.048Z" } wheels = [ @@ -4027,8 +4024,8 @@ name = "pyobjc-framework-soundanalysis" version = "12.1" 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/d6/5039b61edc310083425f87ce2363304d3a87617e941c1d07968c63b5638d/pyobjc_framework_soundanalysis-12.1.tar.gz", hash = "sha256:e2deead8b9a1c4513dbdcf703b21650dcb234b60a32d08afcec4895582b040b1", size = 14804, upload-time = "2025-11-14T10:22:29.998Z" } wheels = [ @@ -4040,8 +4037,8 @@ name = "pyobjc-framework-speech" version = "12.1" 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/8d/3d/194cf19fe7a56c2be5dfc28f42b3b597a62ebb1e1f52a7dd9c55b917ac6c/pyobjc_framework_speech-12.1.tar.gz", hash = "sha256:2a2a546ba6c52d5dd35ddcfee3fd9226a428043d1719597e8701851a6566afdd", size = 25218, upload-time = "2025-11-14T10:22:32.505Z" } wheels = [ @@ -4054,9 +4051,9 @@ name = "pyobjc-framework-spritekit" version = "12.1" 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/b6/78/d683ebe0afb49f46d2d21d38c870646e7cb3c2e83251f264e79d357b1b74/pyobjc_framework_spritekit-12.1.tar.gz", hash = "sha256:a851f4ef5aa65cc9e08008644a528e83cb31021a1c0f17ebfce4de343764d403", size = 64470, upload-time = "2025-11-14T10:22:37.569Z" } wheels = [ @@ -4069,8 +4066,8 @@ name = "pyobjc-framework-storekit" version = "12.1" 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/00/87/8a66a145feb026819775d44975c71c1c64df4e5e9ea20338f01456a61208/pyobjc_framework_storekit-12.1.tar.gz", hash = "sha256:818452e67e937a10b5c8451758274faa44ad5d4329df0fa85735115fb0608da9", size = 34574, upload-time = "2025-11-14T10:22:40.73Z" } wheels = [ @@ -4083,8 +4080,8 @@ name = "pyobjc-framework-symbols" version = "12.1" 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/ce/a48819eb8524fa2dc11fb3dd40bb9c4dcad0596fe538f5004923396c2c6c/pyobjc_framework_symbols-12.1.tar.gz", hash = "sha256:7d8e999b8a59c97d38d1d343b6253b1b7d04bf50b665700957d89c8ac43b9110", size = 12782, upload-time = "2025-11-14T10:22:42.609Z" } wheels = [ @@ -4096,9 +4093,9 @@ name = "pyobjc-framework-syncservices" version = "12.1" 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/21/91/6d03a988831ddb0fb001b13573560e9a5bcccde575b99350f98fe56a2dd4/pyobjc_framework_syncservices-12.1.tar.gz", hash = "sha256:6a213e93d9ce15128810987e4c5de8c73cfab1564ac8d273e6b437a49965e976", size = 31032, upload-time = "2025-11-14T10:22:45.902Z" } wheels = [ @@ -4111,8 +4108,8 @@ name = "pyobjc-framework-systemconfiguration" version = "12.1" 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/90/7d/50848df8e1c6b5e13967dee9fb91d3391fe1f2399d2d0797d2fc5edb32ba/pyobjc_framework_systemconfiguration-12.1.tar.gz", hash = "sha256:90fe04aa059876a21626931c71eaff742a27c79798a46347fd053d7008ec496e", size = 59158, upload-time = "2025-11-14T10:22:53.056Z" } wheels = [ @@ -4125,8 +4122,8 @@ name = "pyobjc-framework-systemextensions" version = "12.1" 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/12/01/8a706cd3f7dfcb9a5017831f2e6f9e5538298e90052db3bb8163230cbc4f/pyobjc_framework_systemextensions-12.1.tar.gz", hash = "sha256:243e043e2daee4b5c46cd90af5fff46b34596aac25011bab8ba8a37099685eeb", size = 20701, upload-time = "2025-11-14T10:22:58.257Z" } wheels = [ @@ -4139,8 +4136,8 @@ name = "pyobjc-framework-threadnetwork" version = "12.1" 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/7e/f1816c3461e4121186f2f7750c58af083d1826bbd73f72728da3edcf4915/pyobjc_framework_threadnetwork-12.1.tar.gz", hash = "sha256:e071eedb41bfc1b205111deb54783ec5a035ccd6929e6e0076336107fdd046ee", size = 12788, upload-time = "2025-11-14T10:23:00.329Z" } wheels = [ @@ -4152,8 +4149,8 @@ name = "pyobjc-framework-uniformtypeidentifiers" version = "12.1" 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/b8/dd9d2a94509a6c16d965a7b0155e78edf520056313a80f0cd352413f0d0b/pyobjc_framework_uniformtypeidentifiers-12.1.tar.gz", hash = "sha256:64510a6df78336579e9c39b873cfcd03371c4b4be2cec8af75a8a3d07dff607d", size = 17030, upload-time = "2025-11-14T10:23:02.222Z" } wheels = [ @@ -4165,8 +4162,8 @@ name = "pyobjc-framework-usernotifications" version = "12.1" 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/90/cd/e0253072f221fa89a42fe53f1a2650cc9bf415eb94ae455235bd010ee12e/pyobjc_framework_usernotifications-12.1.tar.gz", hash = "sha256:019ccdf2d400f9a428769df7dba4ea97c02453372bc5f8b75ce7ae54dfe130f9", size = 29749, upload-time = "2025-11-14T10:23:05.364Z" } wheels = [ @@ -4179,9 +4176,9 @@ name = "pyobjc-framework-usernotificationsui" version = "12.1" 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/0e/03/73e29fd5e5973cb3800c9d56107c1062547ef7524cbcc757c3cbbd5465c6/pyobjc_framework_usernotificationsui-12.1.tar.gz", hash = "sha256:51381c97c7344099377870e49ed0871fea85ba50efe50ab05ccffc06b43ec02e", size = 13125, upload-time = "2025-11-14T10:23:07.259Z" } wheels = [ @@ -4193,8 +4190,8 @@ name = "pyobjc-framework-videosubscriberaccount" version = "12.1" 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/10/f8/27927a9c125c622656ee5aada4596ccb8e5679da0260742360f193df6dcf/pyobjc_framework_videosubscriberaccount-12.1.tar.gz", hash = "sha256:750459fa88220ab83416f769f2d5d210a1f77b8938fa4d119aad0002fc32846b", size = 18793, upload-time = "2025-11-14T10:23:09.33Z" } wheels = [ @@ -4206,10 +4203,10 @@ name = "pyobjc-framework-videotoolbox" version = "12.1" 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/b3/5f/6995ee40dc0d1a3460ee183f696e5254c0ad14a25b5bc5fd9bd7266c077b/pyobjc_framework_videotoolbox-12.1.tar.gz", hash = "sha256:7adc8670f3b94b086aed6e86c3199b388892edab4f02933c2e2d9b1657561bef", size = 57825, upload-time = "2025-11-14T10:23:13.825Z" } wheels = [ @@ -4222,8 +4219,8 @@ name = "pyobjc-framework-virtualization" version = "12.1" 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/3b/6a/9d110b5521d9b898fad10928818c9f55d66a4af9ac097426c65a9878b095/pyobjc_framework_virtualization-12.1.tar.gz", hash = "sha256:e96afd8e801e92c6863da0921e40a3b68f724804f888bce43791330658abdb0f", size = 40682, upload-time = "2025-11-14T10:23:17.456Z" } wheels = [ @@ -4236,10 +4233,10 @@ name = "pyobjc-framework-vision" version = "12.1" 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/c2/5a/08bb3e278f870443d226c141af14205ff41c0274da1e053b72b11dfc9fb2/pyobjc_framework_vision-12.1.tar.gz", hash = "sha256:a30959100e85dcede3a786c544e621ad6eb65ff6abf85721f805822b8c5fe9b0", size = 59538, upload-time = "2025-11-14T10:23:21.979Z" } wheels = [ @@ -4252,8 +4249,8 @@ name = "pyobjc-framework-webkit" version = "12.1" 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/10/110a50e8e6670765d25190ca7f7bfeecc47ec4a8c018cb928f4f82c56e04/pyobjc_framework_webkit-12.1.tar.gz", hash = "sha256:97a54dd05ab5266bd4f614e41add517ae62cdd5a30328eabb06792474b37d82a", size = 284531, upload-time = "2025-11-14T10:23:40.287Z" } wheels = [ @@ -4263,7 +4260,7 @@ wheels = [ [[package]] name = "pyopencl" -version = "2025.2.7" +version = "2026.1.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, @@ -4271,18 +4268,22 @@ dependencies = [ { name = "pytools" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f8/cb/8927052160bc0d3bd1123a645aaf57f696da364216b57b49f92ba0777bcc/pyopencl-2025.2.7.tar.gz", hash = "sha256:a68d92eb2970418b1a7ca45aff71984c02d2e4261e01402b273f257b5d6d7511", size = 444787, upload-time = "2025-10-28T14:23:15.497Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/81/fd8a2a695916a82e861bcf17b5b8fd9f81e12c9e5931f9ba536678d7b43a/pyopencl-2026.1.2.tar.gz", hash = "sha256:4397dd0b4cbb8b55f3e09bf87114a2465574506b363890b805b860c348b61970", size = 445132, upload-time = "2026-01-16T22:52:24.765Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/48/ec/5e511d47fa0d5ad576cd259ceffe4662d6e617dc716044f1ead0d1ba29da/pyopencl-2025.2.7-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:359b484989e9caf532d73206d83422bc0cb8a0e4708c7feeb1f9115752ecda1c", size = 445658, upload-time = "2025-10-28T14:22:41.047Z" }, - { url = "https://files.pythonhosted.org/packages/bd/04/bba1d1ba36ba57eed24ee5407cd2172414792c0a6e89fbf9cc0591e0ba9d/pyopencl-2025.2.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3749a54e124ab72f2cb656aa4049befd300ac31f2125d03c8a1ec42c10f6c22e", size = 427841, upload-time = "2025-10-28T14:22:42.378Z" }, - { url = "https://files.pythonhosted.org/packages/93/cd/ae3ee576c3f296be713e63b2088fdf217ea3c90d7957bbe5bf2ee3165d6a/pyopencl-2025.2.7-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5244cd402f4411832599f3891b557a4be4c3ce98d0165111ab8f8f3638898b94", size = 734543, upload-time = "2025-10-28T14:22:43.614Z" }, - { url = "https://files.pythonhosted.org/packages/d7/30/321d4d174af5e58122e381afac9ce851e9f84f9b7d6502708913a6a1ef8b/pyopencl-2025.2.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3c25a874f94cd2126bc526e22e94a2e4def7c82ebd095fed7e3b5708332d2391", size = 1227048, upload-time = "2025-10-28T14:22:44.947Z" }, - { url = "https://files.pythonhosted.org/packages/20/28/7363bc9b124ad5b4fa6c4bcb0b4fc2a8e213e85e3c192af8c31c320796f2/pyopencl-2025.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:0c9989080124f27f44df86f048e55ecd3e376e20933e470cc9c14506c82ee5dd", size = 472884, upload-time = "2025-10-28T14:22:47.232Z" }, - { url = "https://files.pythonhosted.org/packages/f9/04/e3a17fa79266140246c6193934c0526185f31ffbe45f4ecd6a41ba58c42d/pyopencl-2025.2.7-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:3525c7e35369d505b8a6141f4dfcdcc78a9714c0c30f37e82834906a678c992a", size = 447013, upload-time = "2025-10-28T14:22:48.442Z" }, - { url = "https://files.pythonhosted.org/packages/c5/3b/b2d3c4e69063b53ea6728f09dee7220c780e040b159b21e5d5a369c877fe/pyopencl-2025.2.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:91ab33d7844c2712c65f1881dbf10755e76010db01b1b9d041eb3b947a29f994", size = 427543, upload-time = "2025-10-28T14:22:49.956Z" }, - { url = "https://files.pythonhosted.org/packages/24/fd/8f1042308f3369c653443e9b461156cb28f805da41889a34788dbcaf6470/pyopencl-2025.2.7-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:386c1cdad727df58b34ad6026cf3fe1a2254474977a8afdf8d40eb6daf8806bc", size = 732584, upload-time = "2025-10-28T14:22:51.613Z" }, - { url = "https://files.pythonhosted.org/packages/24/2f/7413a39eb56e3f9130232a2964e50ef72b5d6a351af7624225d185c1ed11/pyopencl-2025.2.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5ce3643e6367d2c7362a33df4f1d676068ae45db3721bf8aee7d7c60c73065e7", size = 1223000, upload-time = "2025-10-28T14:22:52.976Z" }, - { url = "https://files.pythonhosted.org/packages/08/70/66b371eb8fa3867d139df5e6b7107b611eb1cc8231b7755101f589dda66f/pyopencl-2025.2.7-cp312-cp312-win_amd64.whl", hash = "sha256:0791a7303236f53bcc99ae87515452b2e9cb0303949212cd5e115c4889a44f06", size = 472938, upload-time = "2025-10-28T14:22:54.689Z" }, + { url = "https://files.pythonhosted.org/packages/e3/88/abf34e31d572c59203774a66cd81c1e3b3d60b911241483675151149c6f1/pyopencl-2026.1.2-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:8052e8b402b3ed33ee0807d87d4734f66f67dbafbfb3f5a8b81e478e4d417372", size = 437029, upload-time = "2026-01-16T22:51:30.953Z" }, + { url = "https://files.pythonhosted.org/packages/5c/3d/2dd2d8bbf05a190681582b40fc1ee55b210d00ccebcbb416c62b9f9c81a1/pyopencl-2026.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d5e03681c3fe22d5185b16a727d96783e3787e0b65e7a29e4afe01ae0cb4e802", size = 429031, upload-time = "2026-01-16T22:51:32.674Z" }, + { url = "https://files.pythonhosted.org/packages/41/16/e554b3bd20be2e858cfb6683ee6549aeebbe5f769e5b95f561f79340ab20/pyopencl-2026.1.2-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c8c209d517d1421b17d20b80589a2c39e09ea33350f0367314e1caeed3bc741", size = 689596, upload-time = "2026-01-16T22:51:33.913Z" }, + { url = "https://files.pythonhosted.org/packages/22/a8/1df41cf6c7b25b3bfda14aa0183c6a90eaf849528ba27753eaa25fb26e20/pyopencl-2026.1.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e64e2e34bcfad426bd24b71fdb6b02aa5cb02475147742fe07ef93e81866fc7e", size = 736427, upload-time = "2026-01-16T22:51:36.595Z" }, + { url = "https://files.pythonhosted.org/packages/fc/3d/177b6a675691f7b6f708faef33f981e72fbc4bfed2b1dfa94dc70d0e8a25/pyopencl-2026.1.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:65b151c56b936481d6b6050c2b9bc520840e1402be78c282ba5c01921c25477d", size = 1163888, upload-time = "2026-01-16T22:51:37.973Z" }, + { url = "https://files.pythonhosted.org/packages/e9/fa/5905571d9fa48827c0427a3e664c0213dd045940d581b3b739d83df9c0f6/pyopencl-2026.1.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cc40003446037f391ca0970694efb0627e2870fabb20ee21be75bc445a39d8f4", size = 1228235, upload-time = "2026-01-16T22:51:39.786Z" }, + { url = "https://files.pythonhosted.org/packages/1e/3d/538c675d078b91680d8d82962110d0c9fd42e1584763d515d6e2e82d8c57/pyopencl-2026.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:b6a8e109ade7db60e8b1beb48df8f080941d0cd77fb2c225ad509c80cdef603e", size = 474753, upload-time = "2026-01-16T22:51:41.771Z" }, + { url = "https://files.pythonhosted.org/packages/cd/34/1497070e44d1689ddbd01d24a2265910e84ebc53457a489b9d2b6e1ac675/pyopencl-2026.1.2-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:7d88e59901bfe1f9296fd89acd9968f008dc7cfee7995f8cd09c3f1a77119aa6", size = 438145, upload-time = "2026-01-16T22:51:43.658Z" }, + { url = "https://files.pythonhosted.org/packages/5b/a3/71d6af8741b52d3bef443518c1ccfda003adcfa9cc1d0df83dac7005d08c/pyopencl-2026.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3f96a3bff8a09d2fa924e7c33dafac6ea3ef7ec70e746d6d8e17ce2d959a6836", size = 428820, upload-time = "2026-01-16T22:51:45.326Z" }, + { url = "https://files.pythonhosted.org/packages/db/ea/c8dbabeceac9cad3dbb368e08e0aa208cc6c6251c5134cc25eb15da03639/pyopencl-2026.1.2-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4d4e8e8215ec4fdee4b235b61977cdb1c4f041b487bdcf357be799f45b423d61", size = 685478, upload-time = "2026-01-16T22:51:46.545Z" }, + { url = "https://files.pythonhosted.org/packages/64/c7/5854ef7471dfee195bcef6348a107525ca4d1b73c15240e6444d490f9920/pyopencl-2026.1.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0052a8ccbd282d8ab196705e31f4c3ab344113ea5d5c3ddaeede00cdcab068b", size = 734017, upload-time = "2026-01-16T22:51:48.277Z" }, + { url = "https://files.pythonhosted.org/packages/3d/79/42d4eec282ed299b38d8136d05545113ec8771a1bd6b10bb4ba83ae1236c/pyopencl-2026.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e43da12a376e9283407c2820b24cceeaa129b042ac710947cf8e07b13e294689", size = 1159871, upload-time = "2026-01-16T22:51:49.569Z" }, + { url = "https://files.pythonhosted.org/packages/a0/9a/fdc5d3bed0440d6206109e051008aa0a54ca131d64314bbd42177b8f0763/pyopencl-2026.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1b14b2cf11dec9e0b75cbd14223d1b3c93950fc3e2f7a306b54fa1b17a2cae0f", size = 1225288, upload-time = "2026-01-16T22:51:51.125Z" }, + { url = "https://files.pythonhosted.org/packages/2d/e3/358c19180e0dab5c7dd1fcacc569e6a7ab02a7fddcb9c954f393ceddb2fa/pyopencl-2026.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:d02d7ecabc8d34590dccffe12346689adc5a1ceb07df5acc4ea6c4db8aa28277", size = 474876, upload-time = "2026-01-16T22:51:52.912Z" }, ] [[package]] @@ -4473,7 +4474,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 = [ @@ -4652,7 +4653,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c4/5a/8f60d367147019acef342746f20121b2341ec6596acd5c7941cb36bda02e/raylib-5.5.0.4-cp311-cp311-manylinux2010_i686.manylinux_2_12_i686.whl", hash = "sha256:bdaa119b767f380caf6dd4f9d42ab3bf8596d8fb98737d2951b36924a5a83ac0", size = 2036797, upload-time = "2025-12-11T15:27:20.044Z" }, { url = "https://files.pythonhosted.org/packages/dd/ad/97dd93c389263c61a3057065f0f70db5fdc3c5768fa383a9b3e989ddb6a7/raylib-5.5.0.4-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:6a5cdeeb803d081342961eb1f7c4161af27e951d9ecf2b56d469d5730fcc6213", size = 2188009, upload-time = "2025-12-11T18:50:05.612Z" }, { url = "https://files.pythonhosted.org/packages/42/6a/55be04012f3459842389689326910204f985cffcb8989a92475221f5660a/raylib-5.5.0.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4067fa8a6ed3eb78a1162fc2d40ce8c26c26c5ee544019d1902accf21ec22add", size = 2187633, upload-time = "2025-12-11T15:27:22.345Z" }, - { url = "https://files.pythonhosted.org/packages/2b/b7/702ea311fcb1b82064a1c50f32fe86fce1f21caa39c54ca1d598a9862444/raylib-5.5.0.4-cp311-cp311-manylinux_2_35_aarch64.whl", hash = "sha256:faa403252fd0a692dd2f37d9dd4e79fa293ec05deae8a3b086b063cd725a48f9", size = 2247484, upload-time = "2025-12-11T15:27:24.718Z" }, { url = "https://files.pythonhosted.org/packages/6b/18/b69d9ad9f4064785ad29c73672d40b36c59c3b3efd1dee264cdff4b48bf6/raylib-5.5.0.4-cp311-cp311-win32.whl", hash = "sha256:f01a769bb0797ab4f6e1efc950d5d8aca53548e97da7f527190a1ca5f671c389", size = 1456775, upload-time = "2025-12-11T15:27:26.776Z" }, { url = "https://files.pythonhosted.org/packages/1a/7a/4025d9ceeee8e3ae4748b0f6c356c5ce97628bd5da8a056b6782c87f7e65/raylib-5.5.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:34771dea34a30fa4657f35b344d5ebf9eb11d9b62b23d9349742db5c5f3992bd", size = 1705555, upload-time = "2025-12-11T15:27:28.888Z" }, { url = "https://files.pythonhosted.org/packages/95/21/9117d7013997a65f6d51c6f56145b2c583eeba8f7c1af71a60776eaae9b9/raylib-5.5.0.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:31f64f71e42fed10e8f3629028c9f5700906e0e573b915cfc2244d7a3f3b2ed9", size = 1635486, upload-time = "2025-12-11T15:27:31.05Z" }, @@ -4660,7 +4660,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/58/1c/86bee75ecaa577214da16b374f8de70b45885452703f622c63e06baa0b8e/raylib-5.5.0.4-cp312-cp312-manylinux2010_i686.manylinux_2_12_i686.whl", hash = "sha256:033240c61c1a1fc06fecff747a183671431a4ce63a0c8aafec59217845f86888", size = 2039888, upload-time = "2025-12-11T15:27:36.059Z" }, { url = "https://files.pythonhosted.org/packages/fb/f9/00763899bb8a178a927b5dda90aca692c80ff6cec5f51e6fee88db3f45c2/raylib-5.5.0.4-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:ba87ca50c5748cab75de37a991b7f3f836ce500efbb2d737a923a5f464169088", size = 2198926, upload-time = "2025-12-11T18:50:08.813Z" }, { url = "https://files.pythonhosted.org/packages/6b/e9/0123385e369904335985ebd59157f7a10c89c3a706dffcf6dace863a1fa2/raylib-5.5.0.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:788830bc371ce067c4930ff46a1b6eca0c9cf27bac88f81b035e4b73cc6bf197", size = 2205629, upload-time = "2025-12-11T15:27:39.491Z" }, - { url = "https://files.pythonhosted.org/packages/ae/f1/a9bde00b01956bbccc82c9112c8a6a64d50d44d7e4752c04dc59e59bde7e/raylib-5.5.0.4-cp312-cp312-manylinux_2_35_aarch64.whl", hash = "sha256:fb80c638f74a3a59af6a702078da3152a0013b959a225fc76cc6cc8fd0d91e36", size = 2259080, upload-time = "2025-12-11T15:27:41.862Z" }, { url = "https://files.pythonhosted.org/packages/5c/fa/c25087b39d2db2d833a52b4056ae62db74e64b4be677f816e0b368e65453/raylib-5.5.0.4-cp312-cp312-win32.whl", hash = "sha256:e09f395035484337776c90e6c9955c5876b988db7e13168dcadb6ed11974f8ee", size = 1457266, upload-time = "2025-12-11T15:27:43.798Z" }, { url = "https://files.pythonhosted.org/packages/2c/66/a307e61c953ace906ba68ba1174ed8f1e90e68d5fc3e3af9fb7dc46d68d1/raylib-5.5.0.4-cp312-cp312-win_amd64.whl", hash = "sha256:553043a050a31f2ef072f26d3a70373f838a04733f7c5b26a4e9ee3f8caf06ec", size = 1708354, upload-time = "2025-12-11T15:27:45.979Z" }, { url = "https://files.pythonhosted.org/packages/e8/ba/fee7e6ae0be850f6581d4084ea97825b7895c8866fa8b2df347d408c8293/raylib-5.5.0.4-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c318357ce721c62a6848b6d84b26574cd77267e5758cfa2dbc01d4deb2a2b0b8", size = 1211520, upload-time = "2025-12-11T15:28:30.266Z" }, @@ -4685,42 +4684,11 @@ wheels = [ [[package]] name = "ruamel-yaml" -version = "0.18.17" +version = "0.19.1" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "ruamel-yaml-clib", marker = "platform_python_implementation == 'CPython'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3a/2b/7a1f1ebcd6b3f14febdc003e658778d81e76b40df2267904ee6b13f0c5c6/ruamel_yaml-0.18.17.tar.gz", hash = "sha256:9091cd6e2d93a3a4b157ddb8fabf348c3de7f1fb1381346d985b6b247dcd8d3c", size = 149602, upload-time = "2025-12-17T20:02:55.757Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/3b/ebda527b56beb90cb7652cb1c7e4f91f48649fbcd8d2eb2fb6e77cd3329b/ruamel_yaml-0.19.1.tar.gz", hash = "sha256:53eb66cd27849eff968ebf8f0bf61f46cdac2da1d1f3576dd4ccee9b25c31993", size = 142709, upload-time = "2026-01-02T16:50:31.84Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/af/fe/b6045c782f1fd1ae317d2a6ca1884857ce5c20f59befe6ab25a8603c43a7/ruamel_yaml-0.18.17-py3-none-any.whl", hash = "sha256:9c8ba9eb3e793efdf924b60d521820869d5bf0cb9c6f1b82d82de8295e290b9d", size = 121594, upload-time = "2025-12-17T20:02:07.657Z" }, -] - -[[package]] -name = "ruamel-yaml-clib" -version = "0.2.15" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ea/97/60fda20e2fb54b83a61ae14648b0817c8f5d84a3821e40bfbdae1437026a/ruamel_yaml_clib-0.2.15.tar.gz", hash = "sha256:46e4cc8c43ef6a94885f72512094e482114a8a706d3c555a34ed4b0d20200600", size = 225794, upload-time = "2025-11-16T16:12:59.761Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/80/8ce7b9af532aa94dd83360f01ce4716264db73de6bc8efd22c32341f6658/ruamel_yaml_clib-0.2.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c583229f336682b7212a43d2fa32c30e643d3076178fb9f7a6a14dde85a2d8bd", size = 147998, upload-time = "2025-11-16T16:13:13.241Z" }, - { url = "https://files.pythonhosted.org/packages/53/09/de9d3f6b6701ced5f276d082ad0f980edf08ca67114523d1b9264cd5e2e0/ruamel_yaml_clib-0.2.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:56ea19c157ed8c74b6be51b5fa1c3aff6e289a041575f0556f66e5fb848bb137", size = 132743, upload-time = "2025-11-16T16:13:14.265Z" }, - { url = "https://files.pythonhosted.org/packages/0e/f7/73a9b517571e214fe5c246698ff3ed232f1ef863c8ae1667486625ec688a/ruamel_yaml_clib-0.2.15-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5fea0932358e18293407feb921d4f4457db837b67ec1837f87074667449f9401", size = 731459, upload-time = "2025-11-16T20:22:44.338Z" }, - { url = "https://files.pythonhosted.org/packages/9b/a2/0dc0013169800f1c331a6f55b1282c1f4492a6d32660a0cf7b89e6684919/ruamel_yaml_clib-0.2.15-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef71831bd61fbdb7aa0399d5c4da06bea37107ab5c79ff884cc07f2450910262", size = 749289, upload-time = "2025-11-16T16:13:15.633Z" }, - { url = "https://files.pythonhosted.org/packages/aa/ed/3fb20a1a96b8dc645d88c4072df481fe06e0289e4d528ebbdcc044ebc8b3/ruamel_yaml_clib-0.2.15-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:617d35dc765715fa86f8c3ccdae1e4229055832c452d4ec20856136acc75053f", size = 777630, upload-time = "2025-11-16T16:13:16.898Z" }, - { url = "https://files.pythonhosted.org/packages/60/50/6842f4628bc98b7aa4733ab2378346e1441e150935ad3b9f3c3c429d9408/ruamel_yaml_clib-0.2.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b45498cc81a4724a2d42273d6cfc243c0547ad7c6b87b4f774cb7bcc131c98d", size = 744368, upload-time = "2025-11-16T16:13:18.117Z" }, - { url = "https://files.pythonhosted.org/packages/d3/b0/128ae8e19a7d794c2e36130a72b3bb650ce1dd13fb7def6cf10656437dcf/ruamel_yaml_clib-0.2.15-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:def5663361f6771b18646620fca12968aae730132e104688766cf8a3b1d65922", size = 745233, upload-time = "2025-11-16T20:22:45.833Z" }, - { url = "https://files.pythonhosted.org/packages/75/05/91130633602d6ba7ce3e07f8fc865b40d2a09efd4751c740df89eed5caf9/ruamel_yaml_clib-0.2.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:014181cdec565c8745b7cbc4de3bf2cc8ced05183d986e6d1200168e5bb59490", size = 770963, upload-time = "2025-11-16T16:13:19.344Z" }, - { url = "https://files.pythonhosted.org/packages/fd/4b/fd4542e7f33d7d1bc64cc9ac9ba574ce8cf145569d21f5f20133336cdc8c/ruamel_yaml_clib-0.2.15-cp311-cp311-win32.whl", hash = "sha256:d290eda8f6ada19e1771b54e5706b8f9807e6bb08e873900d5ba114ced13e02c", size = 102640, upload-time = "2025-11-16T16:13:20.498Z" }, - { url = "https://files.pythonhosted.org/packages/bb/eb/00ff6032c19c7537371e3119287999570867a0eafb0154fccc80e74bf57a/ruamel_yaml_clib-0.2.15-cp311-cp311-win_amd64.whl", hash = "sha256:bdc06ad71173b915167702f55d0f3f027fc61abd975bd308a0968c02db4a4c3e", size = 121996, upload-time = "2025-11-16T16:13:21.855Z" }, - { url = "https://files.pythonhosted.org/packages/72/4b/5fde11a0722d676e469d3d6f78c6a17591b9c7e0072ca359801c4bd17eee/ruamel_yaml_clib-0.2.15-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cb15a2e2a90c8475df45c0949793af1ff413acfb0a716b8b94e488ea95ce7cff", size = 149088, upload-time = "2025-11-16T16:13:22.836Z" }, - { url = "https://files.pythonhosted.org/packages/85/82/4d08ac65ecf0ef3b046421985e66301a242804eb9a62c93ca3437dc94ee0/ruamel_yaml_clib-0.2.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:64da03cbe93c1e91af133f5bec37fd24d0d4ba2418eaf970d7166b0a26a148a2", size = 134553, upload-time = "2025-11-16T16:13:24.151Z" }, - { url = "https://files.pythonhosted.org/packages/b9/cb/22366d68b280e281a932403b76da7a988108287adff2bfa5ce881200107a/ruamel_yaml_clib-0.2.15-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f6d3655e95a80325b84c4e14c080b2470fe4f33b6846f288379ce36154993fb1", size = 737468, upload-time = "2025-11-16T20:22:47.335Z" }, - { url = "https://files.pythonhosted.org/packages/71/73/81230babf8c9e33770d43ed9056f603f6f5f9665aea4177a2c30ae48e3f3/ruamel_yaml_clib-0.2.15-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:71845d377c7a47afc6592aacfea738cc8a7e876d586dfba814501d8c53c1ba60", size = 753349, upload-time = "2025-11-16T16:13:26.269Z" }, - { url = "https://files.pythonhosted.org/packages/61/62/150c841f24cda9e30f588ef396ed83f64cfdc13b92d2f925bb96df337ba9/ruamel_yaml_clib-0.2.15-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11e5499db1ccbc7f4b41f0565e4f799d863ea720e01d3e99fa0b7b5fcd7802c9", size = 788211, upload-time = "2025-11-16T16:13:27.441Z" }, - { url = "https://files.pythonhosted.org/packages/30/93/e79bd9cbecc3267499d9ead919bd61f7ddf55d793fb5ef2b1d7d92444f35/ruamel_yaml_clib-0.2.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4b293a37dc97e2b1e8a1aec62792d1e52027087c8eea4fc7b5abd2bdafdd6642", size = 743203, upload-time = "2025-11-16T16:13:28.671Z" }, - { url = "https://files.pythonhosted.org/packages/8d/06/1eb640065c3a27ce92d76157f8efddb184bd484ed2639b712396a20d6dce/ruamel_yaml_clib-0.2.15-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:512571ad41bba04eac7268fe33f7f4742210ca26a81fe0c75357fa682636c690", size = 747292, upload-time = "2025-11-16T20:22:48.584Z" }, - { url = "https://files.pythonhosted.org/packages/a5/21/ee353e882350beab65fcc47a91b6bdc512cace4358ee327af2962892ff16/ruamel_yaml_clib-0.2.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e5e9f630c73a490b758bf14d859a39f375e6999aea5ddd2e2e9da89b9953486a", size = 771624, upload-time = "2025-11-16T16:13:29.853Z" }, - { url = "https://files.pythonhosted.org/packages/57/34/cc1b94057aa867c963ecf9ea92ac59198ec2ee3a8d22a126af0b4d4be712/ruamel_yaml_clib-0.2.15-cp312-cp312-win32.whl", hash = "sha256:f4421ab780c37210a07d138e56dd4b51f8642187cdfb433eb687fe8c11de0144", size = 100342, upload-time = "2025-11-16T16:13:31.067Z" }, - { url = "https://files.pythonhosted.org/packages/b3/e5/8925a4208f131b218f9a7e459c0d6fcac8324ae35da269cb437894576366/ruamel_yaml_clib-0.2.15-cp312-cp312-win_amd64.whl", hash = "sha256:2b216904750889133d9222b7b873c199d48ecbb12912aca78970f84a5aa1a4bc", size = 119013, upload-time = "2025-11-16T16:13:32.164Z" }, + { url = "https://files.pythonhosted.org/packages/b8/0c/51f6841f1d84f404f92463fc2b1ba0da357ca1e3db6b7fbda26956c3b82a/ruamel_yaml-0.19.1-py3-none-any.whl", hash = "sha256:27592957fedf6e0b62f281e96effd28043345e0e66001f97683aa9a40c667c93", size = 118102, upload-time = "2026-01-02T16:50:29.201Z" }, ] [[package]] @@ -4734,28 +4702,28 @@ wheels = [ [[package]] name = "ruff" -version = "0.14.10" +version = "0.14.13" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/57/08/52232a877978dd8f9cf2aeddce3e611b40a63287dfca29b6b8da791f5e8d/ruff-0.14.10.tar.gz", hash = "sha256:9a2e830f075d1a42cd28420d7809ace390832a490ed0966fe373ba288e77aaf4", size = 5859763, upload-time = "2025-12-18T19:28:57.98Z" } +sdist = { url = "https://files.pythonhosted.org/packages/50/0a/1914efb7903174b381ee2ffeebb4253e729de57f114e63595114c8ca451f/ruff-0.14.13.tar.gz", hash = "sha256:83cd6c0763190784b99650a20fec7633c59f6ebe41c5cc9d45ee42749563ad47", size = 6059504, upload-time = "2026-01-15T20:15:16.918Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/60/01/933704d69f3f05ee16ef11406b78881733c186fe14b6a46b05cfcaf6d3b2/ruff-0.14.10-py3-none-linux_armv6l.whl", hash = "sha256:7a3ce585f2ade3e1f29ec1b92df13e3da262178df8c8bdf876f48fa0e8316c49", size = 13527080, upload-time = "2025-12-18T19:29:25.642Z" }, - { url = "https://files.pythonhosted.org/packages/df/58/a0349197a7dfa603ffb7f5b0470391efa79ddc327c1e29c4851e85b09cc5/ruff-0.14.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:674f9be9372907f7257c51f1d4fc902cb7cf014b9980152b802794317941f08f", size = 13797320, upload-time = "2025-12-18T19:29:02.571Z" }, - { url = "https://files.pythonhosted.org/packages/7b/82/36be59f00a6082e38c23536df4e71cdbc6af8d7c707eade97fcad5c98235/ruff-0.14.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d85713d522348837ef9df8efca33ccb8bd6fcfc86a2cde3ccb4bc9d28a18003d", size = 12918434, upload-time = "2025-12-18T19:28:51.202Z" }, - { url = "https://files.pythonhosted.org/packages/a6/00/45c62a7f7e34da92a25804f813ebe05c88aa9e0c25e5cb5a7d23dd7450e3/ruff-0.14.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6987ebe0501ae4f4308d7d24e2d0fe3d7a98430f5adfd0f1fead050a740a3a77", size = 13371961, upload-time = "2025-12-18T19:29:04.991Z" }, - { url = "https://files.pythonhosted.org/packages/40/31/a5906d60f0405f7e57045a70f2d57084a93ca7425f22e1d66904769d1628/ruff-0.14.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:16a01dfb7b9e4eee556fbfd5392806b1b8550c9b4a9f6acd3dbe6812b193c70a", size = 13275629, upload-time = "2025-12-18T19:29:21.381Z" }, - { url = "https://files.pythonhosted.org/packages/3e/60/61c0087df21894cf9d928dc04bcd4fb10e8b2e8dca7b1a276ba2155b2002/ruff-0.14.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7165d31a925b7a294465fa81be8c12a0e9b60fb02bf177e79067c867e71f8b1f", size = 14029234, upload-time = "2025-12-18T19:29:00.132Z" }, - { url = "https://files.pythonhosted.org/packages/44/84/77d911bee3b92348b6e5dab5a0c898d87084ea03ac5dc708f46d88407def/ruff-0.14.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c561695675b972effb0c0a45db233f2c816ff3da8dcfbe7dfc7eed625f218935", size = 15449890, upload-time = "2025-12-18T19:28:53.573Z" }, - { url = "https://files.pythonhosted.org/packages/e9/36/480206eaefa24a7ec321582dda580443a8f0671fdbf6b1c80e9c3e93a16a/ruff-0.14.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4bb98fcbbc61725968893682fd4df8966a34611239c9fd07a1f6a07e7103d08e", size = 15123172, upload-time = "2025-12-18T19:29:23.453Z" }, - { url = "https://files.pythonhosted.org/packages/5c/38/68e414156015ba80cef5473d57919d27dfb62ec804b96180bafdeaf0e090/ruff-0.14.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f24b47993a9d8cb858429e97bdf8544c78029f09b520af615c1d261bf827001d", size = 14460260, upload-time = "2025-12-18T19:29:27.808Z" }, - { url = "https://files.pythonhosted.org/packages/b3/19/9e050c0dca8aba824d67cc0db69fb459c28d8cd3f6855b1405b3f29cc91d/ruff-0.14.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59aabd2e2c4fd614d2862e7939c34a532c04f1084476d6833dddef4afab87e9f", size = 14229978, upload-time = "2025-12-18T19:29:11.32Z" }, - { url = "https://files.pythonhosted.org/packages/51/eb/e8dd1dd6e05b9e695aa9dd420f4577debdd0f87a5ff2fedda33c09e9be8c/ruff-0.14.10-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:213db2b2e44be8625002dbea33bb9c60c66ea2c07c084a00d55732689d697a7f", size = 14338036, upload-time = "2025-12-18T19:29:09.184Z" }, - { url = "https://files.pythonhosted.org/packages/6a/12/f3e3a505db7c19303b70af370d137795fcfec136d670d5de5391e295c134/ruff-0.14.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b914c40ab64865a17a9a5b67911d14df72346a634527240039eb3bd650e5979d", size = 13264051, upload-time = "2025-12-18T19:29:13.431Z" }, - { url = "https://files.pythonhosted.org/packages/08/64/8c3a47eaccfef8ac20e0484e68e0772013eb85802f8a9f7603ca751eb166/ruff-0.14.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1484983559f026788e3a5c07c81ef7d1e97c1c78ed03041a18f75df104c45405", size = 13283998, upload-time = "2025-12-18T19:29:06.994Z" }, - { url = "https://files.pythonhosted.org/packages/12/84/534a5506f4074e5cc0529e5cd96cfc01bb480e460c7edf5af70d2bcae55e/ruff-0.14.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c70427132db492d25f982fffc8d6c7535cc2fd2c83fc8888f05caaa248521e60", size = 13601891, upload-time = "2025-12-18T19:28:55.811Z" }, - { url = "https://files.pythonhosted.org/packages/0d/1e/14c916087d8598917dbad9b2921d340f7884824ad6e9c55de948a93b106d/ruff-0.14.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5bcf45b681e9f1ee6445d317ce1fa9d6cba9a6049542d1c3d5b5958986be8830", size = 14336660, upload-time = "2025-12-18T19:29:16.531Z" }, - { url = "https://files.pythonhosted.org/packages/f2/1c/d7b67ab43f30013b47c12b42d1acd354c195351a3f7a1d67f59e54227ede/ruff-0.14.10-py3-none-win32.whl", hash = "sha256:104c49fc7ab73f3f3a758039adea978869a918f31b73280db175b43a2d9b51d6", size = 13196187, upload-time = "2025-12-18T19:29:19.006Z" }, - { url = "https://files.pythonhosted.org/packages/fb/9c/896c862e13886fae2af961bef3e6312db9ebc6adc2b156fe95e615dee8c1/ruff-0.14.10-py3-none-win_amd64.whl", hash = "sha256:466297bd73638c6bdf06485683e812db1c00c7ac96d4ddd0294a338c62fdc154", size = 14661283, upload-time = "2025-12-18T19:29:30.16Z" }, - { url = "https://files.pythonhosted.org/packages/74/31/b0e29d572670dca3674eeee78e418f20bdf97fa8aa9ea71380885e175ca0/ruff-0.14.10-py3-none-win_arm64.whl", hash = "sha256:e51d046cf6dda98a4633b8a8a771451107413b0f07183b2bef03f075599e44e6", size = 13729839, upload-time = "2025-12-18T19:28:48.636Z" }, + { url = "https://files.pythonhosted.org/packages/c3/ae/0deefbc65ca74b0ab1fd3917f94dc3b398233346a74b8bbb0a916a1a6bf6/ruff-0.14.13-py3-none-linux_armv6l.whl", hash = "sha256:76f62c62cd37c276cb03a275b198c7c15bd1d60c989f944db08a8c1c2dbec18b", size = 13062418, upload-time = "2026-01-15T20:14:50.779Z" }, + { url = "https://files.pythonhosted.org/packages/47/df/5916604faa530a97a3c154c62a81cb6b735c0cb05d1e26d5ad0f0c8ac48a/ruff-0.14.13-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:914a8023ece0528d5cc33f5a684f5f38199bbb566a04815c2c211d8f40b5d0ed", size = 13442344, upload-time = "2026-01-15T20:15:07.94Z" }, + { url = "https://files.pythonhosted.org/packages/4c/f3/e0e694dd69163c3a1671e102aa574a50357536f18a33375050334d5cd517/ruff-0.14.13-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d24899478c35ebfa730597a4a775d430ad0d5631b8647a3ab368c29b7e7bd063", size = 12354720, upload-time = "2026-01-15T20:15:09.854Z" }, + { url = "https://files.pythonhosted.org/packages/c3/e8/67f5fcbbaee25e8fc3b56cc33e9892eca7ffe09f773c8e5907757a7e3bdb/ruff-0.14.13-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9aaf3870f14d925bbaf18b8a2347ee0ae7d95a2e490e4d4aea6813ed15ebc80e", size = 12774493, upload-time = "2026-01-15T20:15:20.908Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ce/d2e9cb510870b52a9565d885c0d7668cc050e30fa2c8ac3fb1fda15c083d/ruff-0.14.13-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac5b7f63dd3b27cc811850f5ffd8fff845b00ad70e60b043aabf8d6ecc304e09", size = 12815174, upload-time = "2026-01-15T20:15:05.74Z" }, + { url = "https://files.pythonhosted.org/packages/88/00/c38e5da58beebcf4fa32d0ddd993b63dfacefd02ab7922614231330845bf/ruff-0.14.13-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78d2b1097750d90ba82ce4ba676e85230a0ed694178ca5e61aa9b459970b3eb9", size = 13680909, upload-time = "2026-01-15T20:15:14.537Z" }, + { url = "https://files.pythonhosted.org/packages/61/61/cd37c9dd5bd0a3099ba79b2a5899ad417d8f3b04038810b0501a80814fd7/ruff-0.14.13-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:7d0bf87705acbbcb8d4c24b2d77fbb73d40210a95c3903b443cd9e30824a5032", size = 15144215, upload-time = "2026-01-15T20:15:22.886Z" }, + { url = "https://files.pythonhosted.org/packages/56/8a/85502d7edbf98c2df7b8876f316c0157359165e16cdf98507c65c8d07d3d/ruff-0.14.13-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3eb5da8e2c9e9f13431032fdcbe7681de9ceda5835efee3269417c13f1fed5c", size = 14706067, upload-time = "2026-01-15T20:14:48.271Z" }, + { url = "https://files.pythonhosted.org/packages/7e/2f/de0df127feb2ee8c1e54354dc1179b4a23798f0866019528c938ba439aca/ruff-0.14.13-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:642442b42957093811cd8d2140dfadd19c7417030a7a68cf8d51fcdd5f217427", size = 14133916, upload-time = "2026-01-15T20:14:57.357Z" }, + { url = "https://files.pythonhosted.org/packages/0d/77/9b99686bb9fe07a757c82f6f95e555c7a47801a9305576a9c67e0a31d280/ruff-0.14.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4acdf009f32b46f6e8864af19cbf6841eaaed8638e65c8dac845aea0d703c841", size = 13859207, upload-time = "2026-01-15T20:14:55.111Z" }, + { url = "https://files.pythonhosted.org/packages/7d/46/2bdcb34a87a179a4d23022d818c1c236cb40e477faf0d7c9afb6813e5876/ruff-0.14.13-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:591a7f68860ea4e003917d19b5c4f5ac39ff558f162dc753a2c5de897fd5502c", size = 14043686, upload-time = "2026-01-15T20:14:52.841Z" }, + { url = "https://files.pythonhosted.org/packages/1a/a9/5c6a4f56a0512c691cf143371bcf60505ed0f0860f24a85da8bd123b2bf1/ruff-0.14.13-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:774c77e841cc6e046fc3e91623ce0903d1cd07e3a36b1a9fe79b81dab3de506b", size = 12663837, upload-time = "2026-01-15T20:15:18.921Z" }, + { url = "https://files.pythonhosted.org/packages/fe/bb/b920016ece7651fa7fcd335d9d199306665486694d4361547ccb19394c44/ruff-0.14.13-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:61f4e40077a1248436772bb6512db5fc4457fe4c49e7a94ea7c5088655dd21ae", size = 12805867, upload-time = "2026-01-15T20:14:59.272Z" }, + { url = "https://files.pythonhosted.org/packages/7d/b3/0bd909851e5696cd21e32a8fc25727e5f58f1934b3596975503e6e85415c/ruff-0.14.13-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6d02f1428357fae9e98ac7aa94b7e966fd24151088510d32cf6f902d6c09235e", size = 13208528, upload-time = "2026-01-15T20:15:03.732Z" }, + { url = "https://files.pythonhosted.org/packages/3b/3b/e2d94cb613f6bbd5155a75cbe072813756363eba46a3f2177a1fcd0cd670/ruff-0.14.13-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e399341472ce15237be0c0ae5fbceca4b04cd9bebab1a2b2c979e015455d8f0c", size = 13929242, upload-time = "2026-01-15T20:15:11.918Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c5/abd840d4132fd51a12f594934af5eba1d5d27298a6f5b5d6c3be45301caf/ruff-0.14.13-py3-none-win32.whl", hash = "sha256:ef720f529aec113968b45dfdb838ac8934e519711da53a0456038a0efecbd680", size = 12919024, upload-time = "2026-01-15T20:14:43.647Z" }, + { url = "https://files.pythonhosted.org/packages/c2/55/6384b0b8ce731b6e2ade2b5449bf07c0e4c31e8a2e68ea65b3bafadcecc5/ruff-0.14.13-py3-none-win_amd64.whl", hash = "sha256:6070bd026e409734b9257e03e3ef18c6e1a216f0435c6751d7a8ec69cb59abef", size = 14097887, upload-time = "2026-01-15T20:15:01.48Z" }, + { url = "https://files.pythonhosted.org/packages/4d/e1/7348090988095e4e39560cfc2f7555b1b2a7357deba19167b600fdf5215d/ruff-0.14.13-py3-none-win_arm64.whl", hash = "sha256:7ab819e14f1ad9fe39f246cfcc435880ef7a9390d81a2b6ac7e01039083dd247", size = 13080224, upload-time = "2026-01-15T20:14:45.853Z" }, ] [[package]] @@ -4769,15 +4737,15 @@ wheels = [ [[package]] name = "sentry-sdk" -version = "2.48.0" +version = "2.49.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/40/f0/0e9dc590513d5e742d7799e2038df3a05167cba084c6ca4f3cdd75b55164/sentry_sdk-2.48.0.tar.gz", hash = "sha256:5213190977ff7fdff8a58b722fb807f8d5524a80488626ebeda1b5676c0c1473", size = 384828, upload-time = "2025-12-16T14:55:41.722Z" } +sdist = { url = "https://files.pythonhosted.org/packages/02/94/23ac26616a883f492428d9ee9ad6eee391612125326b784dbfc30e1e7bab/sentry_sdk-2.49.0.tar.gz", hash = "sha256:c1878599cde410d481c04ef50ee3aedd4f600e4d0d253f4763041e468b332c30", size = 387228, upload-time = "2026-01-08T09:56:25.642Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/19/8d77f9992e5cbfcaa9133c3bf63b4fbbb051248802e1e803fed5c552fbb2/sentry_sdk-2.48.0-py2.py3-none-any.whl", hash = "sha256:6b12ac256769d41825d9b7518444e57fa35b5642df4c7c5e322af4d2c8721172", size = 414555, upload-time = "2025-12-16T14:55:40.152Z" }, + { url = "https://files.pythonhosted.org/packages/88/43/1c586f9f413765201234541857cb82fda076f4b0f7bad4a0ec248da39cf3/sentry_sdk-2.49.0-py2.py3-none-any.whl", hash = "sha256:6ea78499133874445a20fe9c826c9e960070abeb7ae0cdf930314ab16bb97aa0", size = 415693, upload-time = "2026-01-08T09:56:21.872Z" }, ] [[package]] @@ -4825,7 +4793,7 @@ name = "shapely" version = "2.1.2" 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/4d/bc/0989043118a27cccb4e906a46b7565ce36ca7b57f5a18b78f4f1b0f72d9d/shapely-2.1.2.tar.gz", hash = "sha256:2ed4ecb28320a433db18a5bf029986aa8afcfd740745e78847e330d5d94922a9", size = 315489, upload-time = "2025-09-24T13:51:41.432Z" } wheels = [ @@ -4950,39 +4918,39 @@ wheels = [ [[package]] name = "ty" -version = "0.0.7" +version = "0.0.12" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b3/43/8be3ec2e2ce6119cff9ee3a207fae0cb4f2b4f8ed6534175130a32be24a7/ty-0.0.7.tar.gz", hash = "sha256:90e53b20b86c418ee41a8385f17da44cc7f916f96f9eee87593423ce8292ca72", size = 4826677, upload-time = "2025-12-24T21:28:49.136Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/78/ba1a4ad403c748fbba8be63b7e774a90e80b67192f6443d624c64fe4aaab/ty-0.0.12.tar.gz", hash = "sha256:cd01810e106c3b652a01b8f784dd21741de9fdc47bd595d02c122a7d5cefeee7", size = 4981303, upload-time = "2026-01-14T22:30:48.537Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6e/56/fafa123acf955089306372add312f16e97aba61f7c4daf74e2bb9c350d23/ty-0.0.7-py3-none-linux_armv6l.whl", hash = "sha256:b30105bd9a0b064497111c50c206d5b6a032f29bcf39f09a12085c3009d72784", size = 9862360, upload-time = "2025-12-24T21:28:36.762Z" }, - { url = "https://files.pythonhosted.org/packages/71/f4/9c30ff498d9a60e24f16d26c0cf93cd03a119913ffa720a77149f02df06e/ty-0.0.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b4df20889115f3d5611a9d9cdedc222e3fd82b5fe87bb0a9f7246e53a23becc7", size = 9712866, upload-time = "2025-12-24T21:28:25.926Z" }, - { url = "https://files.pythonhosted.org/packages/43/84/e06a4a6e4011890027ffee41efbf261b1335103d09009d625ace7f1a60eb/ty-0.0.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f699589d8511e1e17c5a7edfc5f4a4e80f2a6d4a3932a0e9e3422fd32d731472", size = 9221692, upload-time = "2025-12-24T21:28:29.649Z" }, - { url = "https://files.pythonhosted.org/packages/7a/e9/ebb4192d3627730125d40ee403a17dc91bab59d69c3eff286453b3218d01/ty-0.0.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3eaec2d8aa153ee4bcc43b17a384d0f9e66177c8c8127be3358b6b8348b9e3b", size = 9710340, upload-time = "2025-12-24T21:28:55.148Z" }, - { url = "https://files.pythonhosted.org/packages/8f/4a/ec144458a9cfb324d5cb471483094e62e74d73179343dff262a5cca1a1e1/ty-0.0.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:177d160295e6a56bdf0b61f6120bc4502fff301d4d10855ba711c109aa7f37fb", size = 9670317, upload-time = "2025-12-24T21:28:43.096Z" }, - { url = "https://files.pythonhosted.org/packages/b6/94/fe7106fd5e2ac06b81fba7b785a6216774618edc3fda9e17f58efe3cede6/ty-0.0.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30518b95ab5cc83615794cca765a5fb86df39a0d9c3dadc0ab2d787ab7830008", size = 10096517, upload-time = "2025-12-24T21:28:23.667Z" }, - { url = "https://files.pythonhosted.org/packages/45/d9/db96ccfd663c96bdd4bb63db72899198c01445012f939477a5318a563f14/ty-0.0.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:7867b3f75c2d9602cc6fb3b6d462580b707c2d112d4b27037142b0d01f8bfd03", size = 10996406, upload-time = "2025-12-24T21:28:39.134Z" }, - { url = "https://files.pythonhosted.org/packages/94/da/103915c08c3e6a14f95959614646fcdc9a240cd9a039fadbdcd086c819ee/ty-0.0.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:878d45858e209b7904753fbc5155f4cb75dadc20a26bbb77614bfef31580f9ae", size = 10712829, upload-time = "2025-12-24T21:28:27.745Z" }, - { url = "https://files.pythonhosted.org/packages/47/c0/d9be417bc8e459e13e9698978579eec9868f91f4c5d6ef663249967fec8b/ty-0.0.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:651820b193901825afce40ae68f6a51cd64dbfa4b81a45db90061401261f25e4", size = 10486541, upload-time = "2025-12-24T21:28:45.17Z" }, - { url = "https://files.pythonhosted.org/packages/ad/09/d1858c66620d8ae566e021ad0d7168914b1568841f8fe9e439116ce6b440/ty-0.0.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f56a5a0c1c045863b1b70c358a392b3f73b8528c5c571d409f19dd465525e116", size = 10255312, upload-time = "2025-12-24T21:28:53.17Z" }, - { url = "https://files.pythonhosted.org/packages/b6/0a/78f75089db491fd5fcc13d2845a0b2771b7f7d377450c64c6616e9c227bc/ty-0.0.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:748218fbc1f7b7f1b9d14e77d4f3d7fec72af794417e26b0185bdb94153afe1c", size = 9696201, upload-time = "2025-12-24T21:28:57.345Z" }, - { url = "https://files.pythonhosted.org/packages/01/9e/b26e94832fd563fef6f77a4487affc77a027b0e53106422c66aafb37fa01/ty-0.0.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1ff80f3985a52a7358b9069b4a8d223e92cf312544a934a062d6d3a4fb6876b3", size = 9688907, upload-time = "2025-12-24T21:28:59.485Z" }, - { url = "https://files.pythonhosted.org/packages/5a/8f/cc48601fb92c964cf6c34277e0d947076146b7de47aa11b5dbae45e01ce7/ty-0.0.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a808910ce672ba4446699f4c021283208f58f988bcfc3bdbdfc6e005819d9ee0", size = 9829982, upload-time = "2025-12-24T21:28:34.429Z" }, - { url = "https://files.pythonhosted.org/packages/b5/af/7fa9c2bfa25865968bded637f7e71f1a712f4fbede88f487b6a9101ab936/ty-0.0.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:2718fea5f314eda01703fb406ec89b1fc8710b3fc6a09bbd6f7a4f3502ddc889", size = 10361037, upload-time = "2025-12-24T21:28:47.027Z" }, - { url = "https://files.pythonhosted.org/packages/1c/5b/1a6ff1495975cd1c02aa8d03bc5c9d8006eaeb8bf354446f88d70f0518fd/ty-0.0.7-py3-none-win32.whl", hash = "sha256:ae89bb8dc50deb66f34eab3113aa61ac5d7f85ecf16279e5918548085a89021c", size = 9295092, upload-time = "2025-12-24T21:28:51.041Z" }, - { url = "https://files.pythonhosted.org/packages/ff/f6/47e9364635d048002354f84d2d0d6dfc9eb166dc67850739f88e1fec4fc5/ty-0.0.7-py3-none-win_amd64.whl", hash = "sha256:25bd20e3d4d0f07b422f9b42711ba24d28116031273bd23dbda66cec14df1c06", size = 10162816, upload-time = "2025-12-24T21:28:41.006Z" }, - { url = "https://files.pythonhosted.org/packages/7f/f4/c4fc28410c4493982b7481fb23f62bacb02fd2912ebec3b9bc7de18bebb8/ty-0.0.7-py3-none-win_arm64.whl", hash = "sha256:c87d27484dba9fca0053b6a9eee47eecc760aab2bbb8e6eab3d7f81531d1ad0c", size = 9653112, upload-time = "2025-12-24T21:28:31.562Z" }, + { url = "https://files.pythonhosted.org/packages/7d/8f/c21314d074dda5fb13d3300fa6733fd0d8ff23ea83a721818740665b6314/ty-0.0.12-py3-none-linux_armv6l.whl", hash = "sha256:eb9da1e2c68bd754e090eab39ed65edf95168d36cbeb43ff2bd9f86b4edd56d1", size = 9614164, upload-time = "2026-01-14T22:30:44.016Z" }, + { url = "https://files.pythonhosted.org/packages/09/28/f8a4d944d13519d70c486e8f96d6fa95647ac2aa94432e97d5cfec1f42f6/ty-0.0.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:c181f42aa19b0ed7f1b0c2d559980b1f1d77cc09419f51c8321c7ddf67758853", size = 9542337, upload-time = "2026-01-14T22:30:05.687Z" }, + { url = "https://files.pythonhosted.org/packages/e1/9c/f576e360441de7a8201daa6dc4ebc362853bc5305e059cceeb02ebdd9a48/ty-0.0.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1f829e1eecd39c3e1b032149db7ae6a3284f72fc36b42436e65243a9ed1173db", size = 8909582, upload-time = "2026-01-14T22:30:46.089Z" }, + { url = "https://files.pythonhosted.org/packages/d6/13/0898e494032a5d8af3060733d12929e3e7716db6c75eac63fa125730a3e7/ty-0.0.12-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f45162e7826e1789cf3374627883cdeb0d56b82473a0771923e4572928e90be3", size = 9384932, upload-time = "2026-01-14T22:30:13.769Z" }, + { url = "https://files.pythonhosted.org/packages/e4/1a/b35b6c697008a11d4cedfd34d9672db2f0a0621ec80ece109e13fca4dfef/ty-0.0.12-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d11fec40b269bec01e751b2337d1c7ffa959a2c2090a950d7e21c2792442cccd", size = 9453140, upload-time = "2026-01-14T22:30:11.131Z" }, + { url = "https://files.pythonhosted.org/packages/dd/1e/71c9edbc79a3c88a0711324458f29c7dbf6c23452c6e760dc25725483064/ty-0.0.12-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09d99e37e761a4d2651ad9d5a610d11235fbcbf35dc6d4bc04abf54e7cf894f1", size = 9960680, upload-time = "2026-01-14T22:30:33.621Z" }, + { url = "https://files.pythonhosted.org/packages/0e/75/39375129f62dd22f6ad5a99cd2a42fd27d8b91b235ce2db86875cdad397d/ty-0.0.12-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:d9ca0cdb17bd37397da7b16a7cd23423fc65c3f9691e453ad46c723d121225a1", size = 10904518, upload-time = "2026-01-14T22:30:08.464Z" }, + { url = "https://files.pythonhosted.org/packages/32/5e/26c6d88fafa11a9d31ca9f4d12989f57782ec61e7291d4802d685b5be118/ty-0.0.12-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcf2757b905e7eddb7e456140066335b18eb68b634a9f72d6f54a427ab042c64", size = 10525001, upload-time = "2026-01-14T22:30:16.454Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a5/2f0b91894af13187110f9ad7ee926d86e4e6efa755c9c88a820ed7f84c85/ty-0.0.12-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:00cf34c1ebe1147efeda3021a1064baa222c18cdac114b7b050bbe42deb4ca80", size = 10307103, upload-time = "2026-01-14T22:30:41.221Z" }, + { url = "https://files.pythonhosted.org/packages/4b/77/13d0410827e4bc713ebb7fdaf6b3590b37dcb1b82e0a81717b65548f2442/ty-0.0.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb3a655bd869352e9a22938d707631ac9fbca1016242b1f6d132d78f347c851", size = 10072737, upload-time = "2026-01-14T22:30:51.783Z" }, + { url = "https://files.pythonhosted.org/packages/e1/dd/fc36d8bac806c74cf04b4ca735bca14d19967ca84d88f31e121767880df1/ty-0.0.12-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:4658e282c7cb82be304052f8f64f9925f23c3c4f90eeeb32663c74c4b095d7ba", size = 9368726, upload-time = "2026-01-14T22:30:18.683Z" }, + { url = "https://files.pythonhosted.org/packages/54/70/9e8e461647550f83e2fe54bc632ccbdc17a4909644783cdbdd17f7296059/ty-0.0.12-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:c167d838eaaa06e03bb66a517f75296b643d950fbd93c1d1686a187e5a8dbd1f", size = 9454704, upload-time = "2026-01-14T22:30:22.759Z" }, + { url = "https://files.pythonhosted.org/packages/04/9b/6292cf7c14a0efeca0539cf7d78f453beff0475cb039fbea0eb5d07d343d/ty-0.0.12-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2956e0c9ab7023533b461d8a0e6b2ea7b78e01a8dde0688e8234d0fce10c4c1c", size = 9649829, upload-time = "2026-01-14T22:30:31.234Z" }, + { url = "https://files.pythonhosted.org/packages/49/bd/472a5d2013371e4870886cff791c94abdf0b92d43d305dd0f8e06b6ff719/ty-0.0.12-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5c6a3fd7479580009f21002f3828320621d8a82d53b7ba36993234e3ccad58c8", size = 10162814, upload-time = "2026-01-14T22:30:36.174Z" }, + { url = "https://files.pythonhosted.org/packages/31/e9/2ecbe56826759845a7c21d80aa28187865ea62bc9757b056f6cbc06f78ed/ty-0.0.12-py3-none-win32.whl", hash = "sha256:a91c24fd75c0f1796d8ede9083e2c0ec96f106dbda73a09fe3135e075d31f742", size = 9140115, upload-time = "2026-01-14T22:30:38.903Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6d/d9531eff35a5c0ec9dbc10231fac21f9dd6504814048e81d6ce1c84dc566/ty-0.0.12-py3-none-win_amd64.whl", hash = "sha256:df151894be55c22d47068b0f3b484aff9e638761e2267e115d515fcc9c5b4a4b", size = 9884532, upload-time = "2026-01-14T22:30:25.112Z" }, + { url = "https://files.pythonhosted.org/packages/e9/f3/20b49e75967023b123a221134548ad7000f9429f13fdcdda115b4c26305f/ty-0.0.12-py3-none-win_arm64.whl", hash = "sha256:cea99d334b05629de937ce52f43278acf155d3a316ad6a35356635f886be20ea", size = 9313974, upload-time = "2026-01-14T22:30:27.44Z" }, ] [[package]] name = "types-requests" -version = "2.32.4.20250913" +version = "2.32.4.20260107" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/36/27/489922f4505975b11de2b5ad07b4fe1dca0bca9be81a703f26c5f3acfce5/types_requests-2.32.4.20250913.tar.gz", hash = "sha256:abd6d4f9ce3a9383f269775a9835a4c24e5cd6b9f647d64f88aa4613c33def5d", size = 23113, upload-time = "2025-09-13T02:40:02.309Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/f3/a0663907082280664d745929205a89d41dffb29e89a50f753af7d57d0a96/types_requests-2.32.4.20260107.tar.gz", hash = "sha256:018a11ac158f801bfa84857ddec1650750e393df8a004a8a9ae2a9bec6fcb24f", size = 23165, upload-time = "2026-01-07T03:20:54.091Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/20/9a227ea57c1285986c4cf78400d0a91615d25b24e257fd9e2969606bdfae/types_requests-2.32.4.20250913-py3-none-any.whl", hash = "sha256:78c9c1fffebbe0fa487a418e0fa5252017e9c60d1a2da394077f1780f655d7e1", size = 20658, upload-time = "2025-09-13T02:40:01.115Z" }, + { url = "https://files.pythonhosted.org/packages/1c/12/709ea261f2bf91ef0a26a9eed20f2623227a8ed85610c1e54c5805692ecb/types_requests-2.32.4.20260107-py3-none-any.whl", hash = "sha256:b703fe72f8ce5b31ef031264fe9395cac8f46a04661a79f7ed31a80fb308730d", size = 20676, upload-time = "2026-01-07T03:20:52.929Z" }, ] [[package]] @@ -5005,11 +4973,11 @@ wheels = [ [[package]] name = "urllib3" -version = "2.6.2" +version = "2.6.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1e/24/a2a2ed9addd907787d7aa0355ba36a6cadf1768b934c652ea78acbd59dcd/urllib3-2.6.2.tar.gz", hash = "sha256:016f9c98bb7e98085cb2b4b17b87d2c702975664e4f060c6532e64d1c1a5e797", size = 432930, upload-time = "2025-12-11T15:56:40.252Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/b9/4095b668ea3678bf6a0af005527f39de12fb026516fb3df17495a733b7f8/urllib3-2.6.2-py3-none-any.whl", hash = "sha256:ec21cddfe7724fc7cb4ba4bea7aa8e2ef36f607a4bab81aa6ce42a13dc3f03dd", size = 131182, upload-time = "2025-12-11T15:56:38.584Z" }, + { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, ] [[package]] @@ -5075,7 +5043,7 @@ name = "yapf" version = "0.43.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "platformdirs", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "platformdirs" }, ] sdist = { url = "https://files.pythonhosted.org/packages/23/97/b6f296d1e9cc1ec25c7604178b48532fa5901f721bcf1b8d8148b13e5588/yapf-0.43.0.tar.gz", hash = "sha256:00d3aa24bfedff9420b2e0d5d9f5ab6d9d4268e72afbf59bb3fa542781d5218e", size = 254907, upload-time = "2024-11-14T00:11:41.584Z" } wheels = [ From 0b41b42f7b23637e3bbb1f7495d318d954f971e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Mon, 19 Jan 2026 11:48:06 -0800 Subject: [PATCH 747/910] =?UTF-8?q?WMI=20model=20=F0=9F=8D=89=20(#36798)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 1791ea0f-8667-4e0b-be73-084d912f6c4c/100 * eab53871-1f8c-45be-9a98-f6b3dd6a0adc/100 * dd075c9d-0c49-402e-b4f2-9adbe5301c84/100 * e8b5b1b0-2d37-4b62-bd39-21ff0d08ee68/100 * 1aff00c7-06c5-46a6-8a79-7e56f77d81bf/100 * 3547a2cc-1699-4e7d-a2ab-4eb87d0b8684/100 * 849aa9fb-dae6-4604-923e-050883def218/100 * 0e0f6dd2-96dc-4f34-a7cd-63bccc2f5616/100 * 887f923b-7e79-43c6-8f1f-053e1490f859/100 * 1fa82260-1171-4db5-9968-d34ce2e14694/100 * Revert "1fa82260-1171-4db5-9968-d34ce2e14694/100" This reverts commit 855f5e4ddefd69a20cc4e9da004eb53f3e00d950. * a27b3122-733e-4a65-938b-acfebebbe5e8/100 --------- Co-authored-by: Yassine Yousfi --- selfdrive/modeld/models/driving_policy.onnx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/modeld/models/driving_policy.onnx b/selfdrive/modeld/models/driving_policy.onnx index 27e4c8f7b..92c81954d 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:66f406ee179d984a4d8c93e38da479dcd1893127308dd3a7c322a7481a6b51b2 +oid sha256:1edea5bb56f876db4cec97c150799513f6a59373f3ad152d55e4dcaab1b809e3 size 13926324 From 039b85f355d6a6638575cd0a311d354ded99b5b3 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 19 Jan 2026 15:33:23 -0800 Subject: [PATCH 748/910] bump opendbc (#37003) * bump opendbc * bump * bump * bump * bump bump bump --- opendbc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc_repo b/opendbc_repo index edf19be8e..796ece26a 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit edf19be8efe06a5387265adc4650725bb56b5c46 +Subproject commit 796ece26acd8b9255810ca71941ed72626589ee7 From 10db1edc7f6eaba28e93479b11d483340bd3826b Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 19 Jan 2026 15:50:00 -0800 Subject: [PATCH 749/910] merge common.util and common.utils (#36951) * common: merge common.util and common.utils * cleanup * cleanup --- cereal/messaging/__init__.py | 2 +- common/realtime.py | 2 +- common/util.py | 46 --------------------------- common/utils.py | 53 ++++++++++++++++++++++++++++++-- system/hardware/tici/hardware.py | 2 +- system/sensord/sensord.py | 2 +- 6 files changed, 54 insertions(+), 53 deletions(-) delete mode 100644 common/util.py diff --git a/cereal/messaging/__init__.py b/cereal/messaging/__init__.py index 0ad846f0f..d5033cd63 100644 --- a/cereal/messaging/__init__.py +++ b/cereal/messaging/__init__.py @@ -13,7 +13,7 @@ from typing import Optional, List, Union, Dict from cereal import log from cereal.services import SERVICE_LIST -from openpilot.common.util import MovingAverage +from openpilot.common.utils import MovingAverage NO_TRAVERSAL_LIMIT = 2**64-1 diff --git a/common/realtime.py b/common/realtime.py index 57926b4c4..0b1468102 100644 --- a/common/realtime.py +++ b/common/realtime.py @@ -6,7 +6,7 @@ import time from setproctitle import getproctitle -from openpilot.common.util import MovingAverage +from openpilot.common.utils import MovingAverage from openpilot.system.hardware import PC diff --git a/common/util.py b/common/util.py deleted file mode 100644 index e6ddb46e7..000000000 --- a/common/util.py +++ /dev/null @@ -1,46 +0,0 @@ -import os -import subprocess - -def sudo_write(val: str, path: str) -> None: - try: - with open(path, 'w') as f: - f.write(str(val)) - except PermissionError: - os.system(f"sudo chmod a+w {path}") - try: - with open(path, 'w') as f: - f.write(str(val)) - except PermissionError: - # fallback for debugfs files - os.system(f"sudo su -c 'echo {val} > {path}'") - -def sudo_read(path: str) -> str: - try: - return subprocess.check_output(f"sudo cat {path}", shell=True, encoding='utf8').strip() - except Exception: - return "" - -class MovingAverage: - def __init__(self, window_size: int): - self.window_size: int = window_size - self.buffer: list[float] = [0.0] * window_size - self.index: int = 0 - self.count: int = 0 - self.sum: float = 0.0 - - def add_value(self, new_value: float): - # Update the sum: subtract the value being replaced and add the new value - self.sum -= self.buffer[self.index] - self.buffer[self.index] = new_value - self.sum += new_value - - # Update the index in a circular manner - self.index = (self.index + 1) % self.window_size - - # Track the number of added values (for partial windows) - self.count = min(self.count + 1, self.window_size) - - def get_average(self) -> float: - if self.count == 0: - return float('nan') - return self.sum / self.count diff --git a/common/utils.py b/common/utils.py index 71b29a0c4..caa9a5795 100644 --- a/common/utils.py +++ b/common/utils.py @@ -7,14 +7,61 @@ import time import functools from subprocess import Popen, PIPE, TimeoutExpired import zstandard as zstd -from openpilot.common.swaglog import cloudlog LOG_COMPRESSION_LEVEL = 10 # little benefit up to level 15. level ~17 is a small step change +def sudo_write(val: str, path: str) -> None: + try: + with open(path, 'w') as f: + f.write(str(val)) + except PermissionError: + os.system(f"sudo chmod a+w {path}") + try: + with open(path, 'w') as f: + f.write(str(val)) + except PermissionError: + # fallback for debugfs files + os.system(f"sudo su -c 'echo {val} > {path}'") + + +def sudo_read(path: str) -> str: + try: + return subprocess.check_output(f"sudo cat {path}", shell=True, encoding='utf8').strip() + except Exception: + return "" + + +class MovingAverage: + def __init__(self, window_size: int): + self.window_size: int = window_size + self.buffer: list[float] = [0.0] * window_size + self.index: int = 0 + self.count: int = 0 + self.sum: float = 0.0 + + def add_value(self, new_value: float): + # Update the sum: subtract the value being replaced and add the new value + self.sum -= self.buffer[self.index] + self.buffer[self.index] = new_value + self.sum += new_value + + # Update the index in a circular manner + self.index = (self.index + 1) % self.window_size + + # Track the number of added values (for partial windows) + self.count = min(self.count + 1, self.window_size) + + def get_average(self) -> float: + if self.count == 0: + return float('nan') + return self.sum / self.count + + class CallbackReader: """Wraps a file, but overrides the read method to also call a callback function with the number of bytes read so far.""" + def __init__(self, f, callback, *args): self.f = f self.callback = callback @@ -107,11 +154,11 @@ def retry(attempts=3, delay=1.0, ignore_failure=False): try: return func(*args, **kwargs) except Exception: - cloudlog.exception(f"{func.__name__} failed, trying again") + print(f"{func.__name__} failed, trying again") time.sleep(delay) if ignore_failure: - cloudlog.error(f"{func.__name__} failed after retry") + print(f"{func.__name__} failed after retry") else: raise Exception(f"{func.__name__} failed after retry") return wrapper diff --git a/system/hardware/tici/hardware.py b/system/hardware/tici/hardware.py index 3cdb33608..5a84afce0 100644 --- a/system/hardware/tici/hardware.py +++ b/system/hardware/tici/hardware.py @@ -8,7 +8,7 @@ from functools import cached_property, lru_cache from pathlib import Path from cereal import log -from openpilot.common.util import sudo_read, sudo_write +from openpilot.common.utils import sudo_read, sudo_write from openpilot.common.gpio import gpio_set, gpio_init, get_irqs_for_action from openpilot.system.hardware.base import HardwareBase, LPABase, ThermalConfig, ThermalZone from openpilot.system.hardware.tici import iwlist diff --git a/system/sensord/sensord.py b/system/sensord/sensord.py index cc0366881..62908c6f1 100755 --- a/system/sensord/sensord.py +++ b/system/sensord/sensord.py @@ -7,7 +7,7 @@ import threading import cereal.messaging as messaging from cereal.services import SERVICE_LIST -from openpilot.common.util import sudo_write +from openpilot.common.utils import sudo_write from openpilot.common.realtime import config_realtime_process, Ratekeeper from openpilot.common.swaglog import cloudlog from openpilot.common.gpio import gpiochip_get_ro_value_fd, gpioevent_data From 13efc421c42f6e9c06430d80be02ba2a8ea50643 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Mon, 19 Jan 2026 16:27:41 -0800 Subject: [PATCH 750/910] NV12 buffer shape helpers (#36683) * Give this a try * can codex debug? * simpler * Revert "simpler" This reverts commit 572335008c1c719aa985d14bd740253ff94b94a9. * better * cleanup * try again * tie * try this * try this * do tests fail without this? * doesn't seem needed * unused * don't need duplicate * passes CI? * try this * try this * try this * I don't understand this, so back to before * keep that alignment * set uv_height --------- Co-authored-by: Adeeb Shihadeh --- system/camerad/cameras/camera_common.cc | 6 +----- system/camerad/cameras/nv12_info.h | 19 +++++++++++++++++++ system/camerad/cameras/spectra.cc | 14 +++----------- system/camerad/cameras/spectra.h | 1 + tools/replay/camera.cc | 11 ++--------- tools/replay/camera.h | 2 -- 6 files changed, 26 insertions(+), 27 deletions(-) create mode 100644 system/camerad/cameras/nv12_info.h diff --git a/system/camerad/cameras/camera_common.cc b/system/camerad/cameras/camera_common.cc index 1f6ad9b4b..88bca7f77 100644 --- a/system/camerad/cameras/camera_common.cc +++ b/system/camerad/cameras/camera_common.cc @@ -26,11 +26,7 @@ void CameraBuf::init(cl_device_id device_id, cl_context context, SpectraCamera * LOGD("allocated %d CL buffers", frame_buf_count); } - // the encoder HW tells us the size it wants after setting it up. - // TODO: VENUS_BUFFER_SIZE should give the size, but it's too small. dependent on encoder settings? - size_t nv12_size = (out_img_width <= 1344 ? 2900 : 2346)*cam->stride; - - vipc_server->create_buffers_with_sizes(stream_type, VIPC_BUFFER_COUNT, out_img_width, out_img_height, nv12_size, cam->stride, cam->uv_offset); + vipc_server->create_buffers_with_sizes(stream_type, VIPC_BUFFER_COUNT, out_img_width, out_img_height, cam->yuv_size, cam->stride, cam->uv_offset); LOGD("created %d YUV vipc buffers with size %dx%d", VIPC_BUFFER_COUNT, cam->stride, cam->y_height); } diff --git a/system/camerad/cameras/nv12_info.h b/system/camerad/cameras/nv12_info.h new file mode 100644 index 000000000..0f4aee81a --- /dev/null +++ b/system/camerad/cameras/nv12_info.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#include +#include + +#include "third_party/linux/include/msm_media_info.h" + +// Returns NV12 aligned width, height, and buffer size for the given frame. +inline std::tuple get_nv12_info(int width, int height) { + // the encoder HW tells us the size it wants after setting it up. + // TODO: VENUS_BUFFER_SIZE should give the size, but it's too small. dependent on encoder settings? + const uint32_t nv12_width = VENUS_Y_STRIDE(COLOR_FMT_NV12, width); + const uint32_t nv12_height = VENUS_Y_SCANLINES(COLOR_FMT_NV12, height); + assert(nv12_width == VENUS_UV_STRIDE(COLOR_FMT_NV12, width)); + assert(nv12_height / 2 == VENUS_UV_SCANLINES(COLOR_FMT_NV12, height)); + const uint32_t nv12_buffer_size = (width <= 1344 ? 2900 : 2346)*nv12_width; + return {nv12_width, nv12_height, nv12_buffer_size}; +} diff --git a/system/camerad/cameras/spectra.cc b/system/camerad/cameras/spectra.cc index 0d93b7046..20f48bb55 100644 --- a/system/camerad/cameras/spectra.cc +++ b/system/camerad/cameras/spectra.cc @@ -283,20 +283,12 @@ void SpectraCamera::camera_open(VisionIpcServer *v, cl_device_id device_id, cl_c buf.out_img_width = sensor->frame_width / sensor->out_scale; buf.out_img_height = (sensor->hdr_offset > 0 ? (sensor->frame_height - sensor->hdr_offset) / 2 : sensor->frame_height) / sensor->out_scale; - - // size is driven by all the HW that handles frames, - // the video encoder has certain alignment requirements in this case - stride = VENUS_Y_STRIDE(COLOR_FMT_NV12, buf.out_img_width); - y_height = VENUS_Y_SCANLINES(COLOR_FMT_NV12, buf.out_img_height); - uv_height = VENUS_UV_SCANLINES(COLOR_FMT_NV12, buf.out_img_height); - uv_offset = stride*y_height; - yuv_size = uv_offset + stride*uv_height; + std::tie(stride, y_height, yuv_size) = get_nv12_info(buf.out_img_width, buf.out_img_height); + uv_height = y_height / 2; + uv_offset = stride * y_height; if (cc.output_type != ISP_RAW_OUTPUT) { uv_offset = ALIGNED_SIZE(uv_offset, 0x1000); - yuv_size = uv_offset + ALIGNED_SIZE(stride*uv_height, 0x1000); } - assert(stride == VENUS_UV_STRIDE(COLOR_FMT_NV12, buf.out_img_width)); - assert(y_height/2 == uv_height); open = true; configISP(); diff --git a/system/camerad/cameras/spectra.h b/system/camerad/cameras/spectra.h index 13cb13f98..983873c7f 100644 --- a/system/camerad/cameras/spectra.h +++ b/system/camerad/cameras/spectra.h @@ -12,6 +12,7 @@ #include "common/util.h" #include "common/swaglog.h" #include "system/camerad/cameras/hw.h" +#include "system/camerad/cameras/nv12_info.h" #include "system/camerad/cameras/camera_common.h" #include "system/camerad/sensors/sensor.h" diff --git a/tools/replay/camera.cc b/tools/replay/camera.cc index 73243ed20..2962195af 100644 --- a/tools/replay/camera.cc +++ b/tools/replay/camera.cc @@ -7,18 +7,11 @@ #include "third_party/linux/include/msm_media_info.h" #include "tools/replay/util.h" +#include "system/camerad/cameras/nv12_info.h" + const int BUFFER_COUNT = 40; -std::tuple get_nv12_info(int width, int height) { - int nv12_width = VENUS_Y_STRIDE(COLOR_FMT_NV12, width); - int nv12_height = VENUS_Y_SCANLINES(COLOR_FMT_NV12, height); - assert(nv12_width == VENUS_UV_STRIDE(COLOR_FMT_NV12, width)); - assert(nv12_height / 2 == VENUS_UV_SCANLINES(COLOR_FMT_NV12, height)); - size_t nv12_buffer_size = 2346 * nv12_width; // comes from v4l2_format.fmt.pix_mp.plane_fmt[0].sizeimage - return {nv12_width, nv12_height, nv12_buffer_size}; -} - CameraServer::CameraServer(std::pair camera_size[MAX_CAMERAS]) { for (int i = 0; i < MAX_CAMERAS; ++i) { std::tie(cameras_[i].width, cameras_[i].height) = camera_size[i]; diff --git a/tools/replay/camera.h b/tools/replay/camera.h index 21c3d98dc..943301884 100644 --- a/tools/replay/camera.h +++ b/tools/replay/camera.h @@ -10,8 +10,6 @@ #include "tools/replay/framereader.h" #include "tools/replay/logreader.h" -std::tuple get_nv12_info(int width, int height); - class CameraServer { public: CameraServer(std::pair camera_size[MAX_CAMERAS] = nullptr); From c179a3ccb7a5e3a6ff550feb04e97255f40256f2 Mon Sep 17 00:00:00 2001 From: Mauricio Alvarez Leon <65101411+BBBmau@users.noreply.github.com> Date: Mon, 19 Jan 2026 16:45:45 -0800 Subject: [PATCH 751/910] `CI`: enable `macos` tests (#37005) enable macos tests --- .github/workflows/tests.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index f0028841a..c5802b5cb 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -91,7 +91,6 @@ jobs: build_mac: name: build macOS - if: false # tmp disable due to brew install not working runs-on: ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-macos-8x14' || 'macos-latest' }} steps: - uses: actions/checkout@v4 From 6c7f3751e7fb30680810fe6d7141121099a21aa0 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 19 Jan 2026 17:18:22 -0800 Subject: [PATCH 752/910] camerad: calculate buffer sizes with VENUS helpers (#37006) * Revert "NV12 buffer shape helpers (#36683)" This reverts commit 13efc421c42f6e9c06430d80be02ba2a8ea50643. * camerad: calculate buffer sizes with VENUS helpers * copy header: * assert aligned * python nv12 info * debug * handle padding * use the helper --- system/camerad/cameras/nv12_info.h | 23 +- system/camerad/cameras/nv12_info.py | 21 + system/camerad/cameras/spectra.cc | 11 +- system/camerad/cameras/spectra.h | 1 - system/camerad/snapshot.py | 10 +- third_party/linux/include/msm_media_info.h | 587 ++++++++++++++++++++- tools/replay/camera.cc | 10 +- 7 files changed, 631 insertions(+), 32 deletions(-) create mode 100644 system/camerad/cameras/nv12_info.py diff --git a/system/camerad/cameras/nv12_info.h b/system/camerad/cameras/nv12_info.h index 0f4aee81a..e8eb11740 100644 --- a/system/camerad/cameras/nv12_info.h +++ b/system/camerad/cameras/nv12_info.h @@ -6,14 +6,17 @@ #include "third_party/linux/include/msm_media_info.h" -// Returns NV12 aligned width, height, and buffer size for the given frame. -inline std::tuple get_nv12_info(int width, int height) { - // the encoder HW tells us the size it wants after setting it up. - // TODO: VENUS_BUFFER_SIZE should give the size, but it's too small. dependent on encoder settings? - const uint32_t nv12_width = VENUS_Y_STRIDE(COLOR_FMT_NV12, width); - const uint32_t nv12_height = VENUS_Y_SCANLINES(COLOR_FMT_NV12, height); - assert(nv12_width == VENUS_UV_STRIDE(COLOR_FMT_NV12, width)); - assert(nv12_height / 2 == VENUS_UV_SCANLINES(COLOR_FMT_NV12, height)); - const uint32_t nv12_buffer_size = (width <= 1344 ? 2900 : 2346)*nv12_width; - return {nv12_width, nv12_height, nv12_buffer_size}; +// Returns NV12 aligned (stride, y_height, uv_height, buffer_size) for the given frame dimensions. +inline std::tuple get_nv12_info(int width, int height) { + const uint32_t stride = VENUS_Y_STRIDE(COLOR_FMT_NV12, width); + const uint32_t y_height = VENUS_Y_SCANLINES(COLOR_FMT_NV12, height); + const uint32_t uv_height = VENUS_UV_SCANLINES(COLOR_FMT_NV12, height); + const uint32_t size = VENUS_BUFFER_SIZE(COLOR_FMT_NV12, width, height); + + // Sanity checks for NV12 format assumptions + assert(stride == VENUS_UV_STRIDE(COLOR_FMT_NV12, width)); + assert(y_height / 2 == uv_height); + assert((stride * y_height) % 0x1000 == 0); // uv_offset must be page-aligned + + return {stride, y_height, uv_height, size}; } diff --git a/system/camerad/cameras/nv12_info.py b/system/camerad/cameras/nv12_info.py new file mode 100644 index 000000000..bcb6312d2 --- /dev/null +++ b/system/camerad/cameras/nv12_info.py @@ -0,0 +1,21 @@ +# Python version of system/camerad/cameras/nv12_info.h +# Calculations from third_party/linux/include/msm_media_info.h (VENUS_BUFFER_SIZE) + +def align(val: int, alignment: int) -> int: + return ((val + alignment - 1) // alignment) * alignment + +def get_nv12_info(width: int, height: int) -> tuple[int, int, int, int]: + """Returns (stride, y_height, uv_height, buffer_size) for NV12 frame dimensions.""" + stride = align(width, 128) + y_height = align(height, 32) + uv_height = align(height // 2, 16) + + # VENUS_BUFFER_SIZE for NV12 + y_plane = stride * y_height + uv_plane = stride * uv_height + 4096 + size = y_plane + uv_plane + max(16 * 1024, 8 * stride) + size = align(size, 4096) + size += align(width, 512) * 512 # kernel padding for non-aligned frames + size = align(size, 4096) + + return stride, y_height, uv_height, size diff --git a/system/camerad/cameras/spectra.cc b/system/camerad/cameras/spectra.cc index 20f48bb55..5c3e7a9d2 100644 --- a/system/camerad/cameras/spectra.cc +++ b/system/camerad/cameras/spectra.cc @@ -12,11 +12,11 @@ #include "media/cam_isp_ife.h" #include "media/cam_sensor_cmn_header.h" #include "media/cam_sync.h" -#include "third_party/linux/include/msm_media_info.h" #include "common/util.h" #include "common/swaglog.h" #include "system/camerad/cameras/ife.h" +#include "system/camerad/cameras/nv12_info.h" #include "system/camerad/cameras/spectra.h" #include "system/camerad/cameras/bps_blobs.h" @@ -283,12 +283,11 @@ void SpectraCamera::camera_open(VisionIpcServer *v, cl_device_id device_id, cl_c buf.out_img_width = sensor->frame_width / sensor->out_scale; buf.out_img_height = (sensor->hdr_offset > 0 ? (sensor->frame_height - sensor->hdr_offset) / 2 : sensor->frame_height) / sensor->out_scale; - std::tie(stride, y_height, yuv_size) = get_nv12_info(buf.out_img_width, buf.out_img_height); - uv_height = y_height / 2; + + // size is driven by all the HW that handles frames, + // the video encoder has certain alignment requirements in this case + std::tie(stride, y_height, uv_height, yuv_size) = get_nv12_info(buf.out_img_width, buf.out_img_height); uv_offset = stride * y_height; - if (cc.output_type != ISP_RAW_OUTPUT) { - uv_offset = ALIGNED_SIZE(uv_offset, 0x1000); - } open = true; configISP(); diff --git a/system/camerad/cameras/spectra.h b/system/camerad/cameras/spectra.h index 983873c7f..13cb13f98 100644 --- a/system/camerad/cameras/spectra.h +++ b/system/camerad/cameras/spectra.h @@ -12,7 +12,6 @@ #include "common/util.h" #include "common/swaglog.h" #include "system/camerad/cameras/hw.h" -#include "system/camerad/cameras/nv12_info.h" #include "system/camerad/cameras/camera_common.h" #include "system/camerad/sensors/sensor.h" diff --git a/system/camerad/snapshot.py b/system/camerad/snapshot.py index b3369891d..035a4acdc 100755 --- a/system/camerad/snapshot.py +++ b/system/camerad/snapshot.py @@ -43,9 +43,15 @@ def yuv_to_rgb(y, u, v): def extract_image(buf): + # NV12 format: Y plane followed by interleaved UV plane + # UV plane size is stride * uv_height, where uv_height = align(height/2, 16) + uv_height = ((buf.height // 2) + 15) // 16 * 16 + uv_plane_size = buf.stride * uv_height + y = np.array(buf.data[:buf.uv_offset], dtype=np.uint8).reshape((-1, buf.stride))[:buf.height, :buf.width] - u = np.array(buf.data[buf.uv_offset::2], dtype=np.uint8).reshape((-1, buf.stride//2))[:buf.height//2, :buf.width//2] - v = np.array(buf.data[buf.uv_offset+1::2], dtype=np.uint8).reshape((-1, buf.stride//2))[:buf.height//2, :buf.width//2] + uv_data = buf.data[buf.uv_offset:buf.uv_offset + uv_plane_size] + u = np.array(uv_data[::2], dtype=np.uint8).reshape((-1, buf.stride//2))[:buf.height//2, :buf.width//2] + v = np.array(uv_data[1::2], dtype=np.uint8).reshape((-1, buf.stride//2))[:buf.height//2, :buf.width//2] return yuv_to_rgb(y, u, v) diff --git a/third_party/linux/include/msm_media_info.h b/third_party/linux/include/msm_media_info.h index 39dceb2c4..3fd0c8849 100644 --- a/third_party/linux/include/msm_media_info.h +++ b/third_party/linux/include/msm_media_info.h @@ -2,7 +2,9 @@ #define __MEDIA_INFO_H__ #ifndef MSM_MEDIA_ALIGN -#define MSM_MEDIA_ALIGN(__sz, __align) (((__sz) + (__align-1)) & (~(__align-1))) +#define MSM_MEDIA_ALIGN(__sz, __align) (((__align) & ((__align) - 1)) ?\ + ((((__sz) + (__align) - 1) / (__align)) * (__align)) :\ + (((__sz) + (__align) - 1) & (~((__align) - 1)))) #endif #ifndef MSM_MEDIA_ROUNDUP @@ -148,7 +150,12 @@ enum color_fmts { * + 2*(UV_Stride * UV_Scanlines) + Extradata), 4096) */ COLOR_FMT_NV12_MVTB, - /* Venus NV12 UBWC: + /* + * The buffer can be of 2 types: + * (1) Venus NV12 UBWC Progressive + * (2) Venus NV12 UBWC Interlaced + * + * (1) Venus NV12 UBWC Progressive Buffer Format: * Compressed Macro-tile format for NV12. * Contains 4 planes in the following order - * (A) Y_Meta_Plane @@ -234,6 +241,186 @@ enum color_fmts { * Total size = align( Y_UBWC_Plane_size + UV_UBWC_Plane_size + * Y_Meta_Plane_size + UV_Meta_Plane_size * + max(Extradata, Y_Stride * 48), 4096) + * + * + * (2) Venus NV12 UBWC Interlaced Buffer Format: + * Compressed Macro-tile format for NV12 interlaced. + * Contains 8 planes in the following order - + * (A) Y_Meta_Top_Field_Plane + * (B) Y_UBWC_Top_Field_Plane + * (C) UV_Meta_Top_Field_Plane + * (D) UV_UBWC_Top_Field_Plane + * (E) Y_Meta_Bottom_Field_Plane + * (F) Y_UBWC_Bottom_Field_Plane + * (G) UV_Meta_Bottom_Field_Plane + * (H) UV_UBWC_Bottom_Field_Plane + * Y_Meta_Top_Field_Plane consists of meta information to decode + * compressed tile data for Y_UBWC_Top_Field_Plane. + * Y_UBWC_Top_Field_Plane consists of Y data in compressed macro-tile + * format for top field of an interlaced frame. + * UBWC decoder block will use the Y_Meta_Top_Field_Plane data together + * with Y_UBWC_Top_Field_Plane data to produce loss-less uncompressed + * 8 bit Y samples for top field of an interlaced frame. + * + * UV_Meta_Top_Field_Plane consists of meta information to decode + * compressed tile data in UV_UBWC_Top_Field_Plane. + * UV_UBWC_Top_Field_Plane consists of UV data in compressed macro-tile + * format for top field of an interlaced frame. + * UBWC decoder block will use UV_Meta_Top_Field_Plane data together + * with UV_UBWC_Top_Field_Plane data to produce loss-less uncompressed + * 8 bit subsampled color difference samples for top field of an + * interlaced frame. + * + * Each tile in Y_UBWC_Top_Field_Plane/UV_UBWC_Top_Field_Plane is + * independently decodable and randomly accessible. There is no + * dependency between tiles. + * + * Y_Meta_Bottom_Field_Plane consists of meta information to decode + * compressed tile data for Y_UBWC_Bottom_Field_Plane. + * Y_UBWC_Bottom_Field_Plane consists of Y data in compressed macro-tile + * format for bottom field of an interlaced frame. + * UBWC decoder block will use the Y_Meta_Bottom_Field_Plane data + * together with Y_UBWC_Bottom_Field_Plane data to produce loss-less + * uncompressed 8 bit Y samples for bottom field of an interlaced frame. + * + * UV_Meta_Bottom_Field_Plane consists of meta information to decode + * compressed tile data in UV_UBWC_Bottom_Field_Plane. + * UV_UBWC_Bottom_Field_Plane consists of UV data in compressed + * macro-tile format for bottom field of an interlaced frame. + * UBWC decoder block will use UV_Meta_Bottom_Field_Plane data together + * with UV_UBWC_Bottom_Field_Plane data to produce loss-less + * uncompressed 8 bit subsampled color difference samples for bottom + * field of an interlaced frame. + * + * Each tile in Y_UBWC_Bottom_Field_Plane/UV_UBWC_Bottom_Field_Plane is + * independently decodable and randomly accessible. There is no + * dependency between tiles. + * + * <-----Y_TF_Meta_Stride----> + * <-------- Width ------> + * M M M M M M M M M M M M . . ^ ^ + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . Half_height | + * M M M M M M M M M M M M . . | Meta_Y_TF_Scanlines + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . V | + * . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . -------> Buffer size aligned to 4k + * . . . . . . . . . . . . . . V + * <-Compressed tile Y_TF Stride-> + * <------- Width -------> + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . ^ ^ + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . | | + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . Half_height | + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . | Macro_tile_Y_TF_Scanlines + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . | | + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . | | + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . | | + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . V | + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . -------> Buffer size aligned to 4k + * . . . . . . . . . . . . . . . . V + * <----UV_TF_Meta_Stride----> + * M M M M M M M M M M M M . . ^ + * M M M M M M M M M M M M . . | + * M M M M M M M M M M M M . . | + * M M M M M M M M M M M M . . M_UV_TF_Scanlines + * . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . V + * . . . . . . . . . . . . . . -------> Buffer size aligned to 4k + * <-Compressed tile UV_TF Stride-> + * U* V* U* V* U* V* U* V* . . . . ^ + * U* V* U* V* U* V* U* V* . . . . | + * U* V* U* V* U* V* U* V* . . . . | + * U* V* U* V* U* V* U* V* . . . . UV_TF_Scanlines + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . V + * . . . . . . . . . . . . . . . . -------> Buffer size aligned to 4k + * <-----Y_BF_Meta_Stride----> + * <-------- Width ------> + * M M M M M M M M M M M M . . ^ ^ + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . Half_height | + * M M M M M M M M M M M M . . | Meta_Y_BF_Scanlines + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . V | + * . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . -------> Buffer size aligned to 4k + * . . . . . . . . . . . . . . V + * <-Compressed tile Y_BF Stride-> + * <------- Width -------> + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . ^ ^ + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . | | + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . Half_height | + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . | Macro_tile_Y_BF_Scanlines + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . | | + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . | | + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . | | + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . V | + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . -------> Buffer size aligned to 4k + * . . . . . . . . . . . . . . . . V + * <----UV_BF_Meta_Stride----> + * M M M M M M M M M M M M . . ^ + * M M M M M M M M M M M M . . | + * M M M M M M M M M M M M . . | + * M M M M M M M M M M M M . . M_UV_BF_Scanlines + * . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . V + * . . . . . . . . . . . . . . -------> Buffer size aligned to 4k + * <-Compressed tile UV_BF Stride-> + * U* V* U* V* U* V* U* V* . . . . ^ + * U* V* U* V* U* V* U* V* . . . . | + * U* V* U* V* U* V* U* V* . . . . | + * U* V* U* V* U* V* U* V* . . . . UV_BF_Scanlines + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . V + * . . . . . . . . . . . . . . . . -------> Buffer size aligned to 4k + * + * Half_height = (Height+1)>>1 + * Y_TF_Stride = align(Width, 128) + * UV_TF_Stride = align(Width, 128) + * Y_TF_Scanlines = align(Half_height, 32) + * UV_TF_Scanlines = align((Half_height+1)/2, 32) + * Y_UBWC_TF_Plane_size = align(Y_TF_Stride * Y_TF_Scanlines, 4096) + * UV_UBWC_TF_Plane_size = align(UV_TF_Stride * UV_TF_Scanlines, 4096) + * Y_TF_Meta_Stride = align(roundup(Width, Y_TileWidth), 64) + * Y_TF_Meta_Scanlines = align(roundup(Half_height, Y_TileHeight), 16) + * Y_TF_Meta_Plane_size = + * align(Y_TF_Meta_Stride * Y_TF_Meta_Scanlines, 4096) + * UV_TF_Meta_Stride = align(roundup(Width, UV_TileWidth), 64) + * UV_TF_Meta_Scanlines = align(roundup(Half_height, UV_TileHeight), 16) + * UV_TF_Meta_Plane_size = + * align(UV_TF_Meta_Stride * UV_TF_Meta_Scanlines, 4096) + * Y_BF_Stride = align(Width, 128) + * UV_BF_Stride = align(Width, 128) + * Y_BF_Scanlines = align(Half_height, 32) + * UV_BF_Scanlines = align((Half_height+1)/2, 32) + * Y_UBWC_BF_Plane_size = align(Y_BF_Stride * Y_BF_Scanlines, 4096) + * UV_UBWC_BF_Plane_size = align(UV_BF_Stride * UV_BF_Scanlines, 4096) + * Y_BF_Meta_Stride = align(roundup(Width, Y_TileWidth), 64) + * Y_BF_Meta_Scanlines = align(roundup(Half_height, Y_TileHeight), 16) + * Y_BF_Meta_Plane_size = + * align(Y_BF_Meta_Stride * Y_BF_Meta_Scanlines, 4096) + * UV_BF_Meta_Stride = align(roundup(Width, UV_TileWidth), 64) + * UV_BF_Meta_Scanlines = align(roundup(Half_height, UV_TileHeight), 16) + * UV_BF_Meta_Plane_size = + * align(UV_BF_Meta_Stride * UV_BF_Meta_Scanlines, 4096) + * Extradata = 8k + * + * Total size = align( Y_UBWC_TF_Plane_size + UV_UBWC_TF_Plane_size + + * Y_TF_Meta_Plane_size + UV_TF_Meta_Plane_size + + * Y_UBWC_BF_Plane_size + UV_UBWC_BF_Plane_size + + * Y_BF_Meta_Plane_size + UV_BF_Meta_Plane_size + + * + max(Extradata, Y_TF_Stride * 48), 4096) */ COLOR_FMT_NV12_UBWC, /* Venus NV12 10-bit UBWC: @@ -399,8 +586,233 @@ enum color_fmts { * Extradata, 4096) */ COLOR_FMT_RGBA8888_UBWC, + /* Venus RGBA1010102 UBWC format: + * Contains 2 planes in the following order - + * (A) Meta plane + * (B) RGBA plane + * + * <--- RGB_Meta_Stride ----> + * <-------- Width ------> + * M M M M M M M M M M M M . . ^ ^ + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . Height | + * M M M M M M M M M M M M . . | Meta_RGB_Scanlines + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . V | + * . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . -------> Buffer size aligned to 4k + * . . . . . . . . . . . . . . V + * <-------- RGB_Stride --------> + * <------- Width -------> + * R R R R R R R R R R R R . . . . ^ ^ + * R R R R R R R R R R R R . . . . | | + * R R R R R R R R R R R R . . . . Height | + * R R R R R R R R R R R R . . . . | RGB_Scanlines + * R R R R R R R R R R R R . . . . | | + * R R R R R R R R R R R R . . . . | | + * R R R R R R R R R R R R . . . . | | + * R R R R R R R R R R R R . . . . V | + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . -------> Buffer size aligned to 4k + * . . . . . . . . . . . . . . . . V + * + * RGB_Stride = align(Width * 4, 256) + * RGB_Scanlines = align(Height, 16) + * RGB_Plane_size = align(RGB_Stride * RGB_Scanlines, 4096) + * RGB_Meta_Stride = align(roundup(Width, RGB_TileWidth), 64) + * RGB_Meta_Scanline = align(roundup(Height, RGB_TileHeight), 16) + * RGB_Meta_Plane_size = align(RGB_Meta_Stride * + * RGB_Meta_Scanlines, 4096) + * Extradata = 8k + * + * Total size = align(RGB_Meta_Plane_size + RGB_Plane_size + + * Extradata, 4096) + */ + COLOR_FMT_RGBA1010102_UBWC, + /* Venus RGB565 UBWC format: + * Contains 2 planes in the following order - + * (A) Meta plane + * (B) RGB plane + * + * <--- RGB_Meta_Stride ----> + * <-------- Width ------> + * M M M M M M M M M M M M . . ^ ^ + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . Height | + * M M M M M M M M M M M M . . | Meta_RGB_Scanlines + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . V | + * . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . -------> Buffer size aligned to 4k + * . . . . . . . . . . . . . . V + * <-------- RGB_Stride --------> + * <------- Width -------> + * R R R R R R R R R R R R . . . . ^ ^ + * R R R R R R R R R R R R . . . . | | + * R R R R R R R R R R R R . . . . Height | + * R R R R R R R R R R R R . . . . | RGB_Scanlines + * R R R R R R R R R R R R . . . . | | + * R R R R R R R R R R R R . . . . | | + * R R R R R R R R R R R R . . . . | | + * R R R R R R R R R R R R . . . . V | + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . -------> Buffer size aligned to 4k + * . . . . . . . . . . . . . . . . V + * + * RGB_Stride = align(Width * 2, 128) + * RGB_Scanlines = align(Height, 16) + * RGB_Plane_size = align(RGB_Stride * RGB_Scanlines, 4096) + * RGB_Meta_Stride = align(roundup(Width, RGB_TileWidth), 64) + * RGB_Meta_Scanline = align(roundup(Height, RGB_TileHeight), 16) + * RGB_Meta_Plane_size = align(RGB_Meta_Stride * + * RGB_Meta_Scanlines, 4096) + * Extradata = 8k + * + * Total size = align(RGB_Meta_Plane_size + RGB_Plane_size + + * Extradata, 4096) + */ + COLOR_FMT_RGB565_UBWC, + /* P010 UBWC: + * Compressed Macro-tile format for NV12. + * Contains 4 planes in the following order - + * (A) Y_Meta_Plane + * (B) Y_UBWC_Plane + * (C) UV_Meta_Plane + * (D) UV_UBWC_Plane + * + * Y_Meta_Plane consists of meta information to decode compressed + * tile data in Y_UBWC_Plane. + * Y_UBWC_Plane consists of Y data in compressed macro-tile format. + * UBWC decoder block will use the Y_Meta_Plane data together with + * Y_UBWC_Plane data to produce loss-less uncompressed 10 bit Y samples. + * + * UV_Meta_Plane consists of meta information to decode compressed + * tile data in UV_UBWC_Plane. + * UV_UBWC_Plane consists of UV data in compressed macro-tile format. + * UBWC decoder block will use UV_Meta_Plane data together with + * UV_UBWC_Plane data to produce loss-less uncompressed 10 bit 2x2 + * subsampled color difference samples. + * + * Each tile in Y_UBWC_Plane/UV_UBWC_Plane is independently decodable + * and randomly accessible. There is no dependency between tiles. + * + * <----- Y_Meta_Stride -----> + * <-------- Width ------> + * M M M M M M M M M M M M . . ^ ^ + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . Height | + * M M M M M M M M M M M M . . | Meta_Y_Scanlines + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . V | + * . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . -------> Buffer size aligned to 4k + * . . . . . . . . . . . . . . V + * <--Compressed tile Y Stride---> + * <------- Width -------> + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . ^ ^ + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . | | + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . Height | + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . | Macro_tile_Y_Scanlines + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . | | + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . | | + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . | | + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . V | + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . -------> Buffer size aligned to 4k + * . . . . . . . . . . . . . . . . V + * <----- UV_Meta_Stride ----> + * M M M M M M M M M M M M . . ^ + * M M M M M M M M M M M M . . | + * M M M M M M M M M M M M . . | + * M M M M M M M M M M M M . . M_UV_Scanlines + * . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . V + * . . . . . . . . . . . . . . -------> Buffer size aligned to 4k + * <--Compressed tile UV Stride---> + * U* V* U* V* U* V* U* V* . . . . ^ + * U* V* U* V* U* V* U* V* . . . . | + * U* V* U* V* U* V* U* V* . . . . | + * U* V* U* V* U* V* U* V* . . . . UV_Scanlines + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . V + * . . . . . . . . . . . . . . . . -------> Buffer size aligned to 4k + * + * + * Y_Stride = align(Width * 2, 256) + * UV_Stride = align(Width * 2, 256) + * Y_Scanlines = align(Height, 16) + * UV_Scanlines = align(Height/2, 16) + * Y_UBWC_Plane_Size = align(Y_Stride * Y_Scanlines, 4096) + * UV_UBWC_Plane_Size = align(UV_Stride * UV_Scanlines, 4096) + * Y_Meta_Stride = align(roundup(Width, Y_TileWidth), 64) + * Y_Meta_Scanlines = align(roundup(Height, Y_TileHeight), 16) + * Y_Meta_Plane_size = align(Y_Meta_Stride * Y_Meta_Scanlines, 4096) + * UV_Meta_Stride = align(roundup(Width, UV_TileWidth), 64) + * UV_Meta_Scanlines = align(roundup(Height, UV_TileHeight), 16) + * UV_Meta_Plane_size = align(UV_Meta_Stride * UV_Meta_Scanlines, 4096) + * Extradata = 8k + * + * Total size = align(Y_UBWC_Plane_size + UV_UBWC_Plane_size + + * Y_Meta_Plane_size + UV_Meta_Plane_size + * + max(Extradata, Y_Stride * 48), 4096) + */ + COLOR_FMT_P010_UBWC, + /* Venus P010: + * YUV 4:2:0 image with a plane of 10 bit Y samples followed + * by an interleaved U/V plane containing 10 bit 2x2 subsampled + * colour difference samples. + * + * <-------- Y/UV_Stride --------> + * <------- Width -------> + * Y Y Y Y Y Y Y Y Y Y Y Y . . . . ^ ^ + * Y Y Y Y Y Y Y Y Y Y Y Y . . . . | | + * Y Y Y Y Y Y Y Y Y Y Y Y . . . . Height | + * Y Y Y Y Y Y Y Y Y Y Y Y . . . . | Y_Scanlines + * Y Y Y Y Y Y Y Y Y Y Y Y . . . . | | + * Y Y Y Y Y Y Y Y Y Y Y Y . . . . | | + * Y Y Y Y Y Y Y Y Y Y Y Y . . . . | | + * Y Y Y Y Y Y Y Y Y Y Y Y . . . . V | + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . V + * U V U V U V U V U V U V . . . . ^ + * U V U V U V U V U V U V . . . . | + * U V U V U V U V U V U V . . . . | + * U V U V U V U V U V U V . . . . UV_Scanlines + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . V + * . . . . . . . . . . . . . . . . --> Buffer size alignment + * + * Y_Stride : Width * 2 aligned to 128 + * UV_Stride : Width * 2 aligned to 128 + * Y_Scanlines: Height aligned to 32 + * UV_Scanlines: Height/2 aligned to 16 + * Extradata: Arbitrary (software-imposed) padding + * Total size = align((Y_Stride * Y_Scanlines + * + UV_Stride * UV_Scanlines + * + max(Extradata, Y_Stride * 8), 4096) + */ + COLOR_FMT_P010, }; +#define COLOR_FMT_RGBA1010102_UBWC COLOR_FMT_RGBA1010102_UBWC +#define COLOR_FMT_RGB565_UBWC COLOR_FMT_RGB565_UBWC +#define COLOR_FMT_P010_UBWC COLOR_FMT_P010_UBWC +#define COLOR_FMT_P010 COLOR_FMT_P010 + static inline unsigned int VENUS_EXTRADATA_SIZE(int width, int height) { (void)height; @@ -413,9 +825,17 @@ static inline unsigned int VENUS_EXTRADATA_SIZE(int width, int height) return 16 * 1024; } +/* + * Function arguments: + * @color_fmt + * @width + * Progressive: width + * Interlaced: width + */ static inline unsigned int VENUS_Y_STRIDE(int color_fmt, int width) { unsigned int alignment, stride = 0; + if (!width) goto invalid_input; @@ -432,6 +852,14 @@ static inline unsigned int VENUS_Y_STRIDE(int color_fmt, int width) stride = MSM_MEDIA_ALIGN(width, 192); stride = MSM_MEDIA_ALIGN(stride * 4/3, alignment); break; + case COLOR_FMT_P010_UBWC: + alignment = 256; + stride = MSM_MEDIA_ALIGN(width * 2, alignment); + break; + case COLOR_FMT_P010: + alignment = 128; + stride = MSM_MEDIA_ALIGN(width*2, alignment); + break; default: break; } @@ -439,9 +867,17 @@ invalid_input: return stride; } +/* + * Function arguments: + * @color_fmt + * @width + * Progressive: width + * Interlaced: width + */ static inline unsigned int VENUS_UV_STRIDE(int color_fmt, int width) { unsigned int alignment, stride = 0; + if (!width) goto invalid_input; @@ -458,6 +894,14 @@ static inline unsigned int VENUS_UV_STRIDE(int color_fmt, int width) stride = MSM_MEDIA_ALIGN(width, 192); stride = MSM_MEDIA_ALIGN(stride * 4/3, alignment); break; + case COLOR_FMT_P010_UBWC: + alignment = 256; + stride = MSM_MEDIA_ALIGN(width * 2, alignment); + break; + case COLOR_FMT_P010: + alignment = 128; + stride = MSM_MEDIA_ALIGN(width*2, alignment); + break; default: break; } @@ -465,9 +909,17 @@ invalid_input: return stride; } +/* + * Function arguments: + * @color_fmt + * @height + * Progressive: height + * Interlaced: (height+1)>>1 + */ static inline unsigned int VENUS_Y_SCANLINES(int color_fmt, int height) { unsigned int alignment, sclines = 0; + if (!height) goto invalid_input; @@ -476,9 +928,11 @@ static inline unsigned int VENUS_Y_SCANLINES(int color_fmt, int height) case COLOR_FMT_NV12: case COLOR_FMT_NV12_MVTB: case COLOR_FMT_NV12_UBWC: + case COLOR_FMT_P010: alignment = 32; break; case COLOR_FMT_NV12_BPP10_UBWC: + case COLOR_FMT_P010_UBWC: alignment = 16; break; default: @@ -489,9 +943,17 @@ invalid_input: return sclines; } +/* + * Function arguments: + * @color_fmt + * @height + * Progressive: height + * Interlaced: (height+1)>>1 + */ static inline unsigned int VENUS_UV_SCANLINES(int color_fmt, int height) { unsigned int alignment, sclines = 0; + if (!height) goto invalid_input; @@ -500,6 +962,8 @@ static inline unsigned int VENUS_UV_SCANLINES(int color_fmt, int height) case COLOR_FMT_NV12: case COLOR_FMT_NV12_MVTB: case COLOR_FMT_NV12_BPP10_UBWC: + case COLOR_FMT_P010_UBWC: + case COLOR_FMT_P010: alignment = 16; break; case COLOR_FMT_NV12_UBWC: @@ -509,12 +973,19 @@ static inline unsigned int VENUS_UV_SCANLINES(int color_fmt, int height) goto invalid_input; } - sclines = MSM_MEDIA_ALIGN(height / 2, alignment); + sclines = MSM_MEDIA_ALIGN((height+1)>>1, alignment); invalid_input: return sclines; } +/* + * Function arguments: + * @color_fmt + * @width + * Progressive: width + * Interlaced: width + */ static inline unsigned int VENUS_Y_META_STRIDE(int color_fmt, int width) { int y_tile_width = 0, y_meta_stride = 0; @@ -524,6 +995,7 @@ static inline unsigned int VENUS_Y_META_STRIDE(int color_fmt, int width) switch (color_fmt) { case COLOR_FMT_NV12_UBWC: + case COLOR_FMT_P010_UBWC: y_tile_width = 32; break; case COLOR_FMT_NV12_BPP10_UBWC: @@ -540,6 +1012,13 @@ invalid_input: return y_meta_stride; } +/* + * Function arguments: + * @color_fmt + * @height + * Progressive: height + * Interlaced: (height+1)>>1 + */ static inline unsigned int VENUS_Y_META_SCANLINES(int color_fmt, int height) { int y_tile_height = 0, y_meta_scanlines = 0; @@ -552,6 +1031,7 @@ static inline unsigned int VENUS_Y_META_SCANLINES(int color_fmt, int height) y_tile_height = 8; break; case COLOR_FMT_NV12_BPP10_UBWC: + case COLOR_FMT_P010_UBWC: y_tile_height = 4; break; default: @@ -565,6 +1045,13 @@ invalid_input: return y_meta_scanlines; } +/* + * Function arguments: + * @color_fmt + * @width + * Progressive: width + * Interlaced: width + */ static inline unsigned int VENUS_UV_META_STRIDE(int color_fmt, int width) { int uv_tile_width = 0, uv_meta_stride = 0; @@ -574,6 +1061,7 @@ static inline unsigned int VENUS_UV_META_STRIDE(int color_fmt, int width) switch (color_fmt) { case COLOR_FMT_NV12_UBWC: + case COLOR_FMT_P010_UBWC: uv_tile_width = 16; break; case COLOR_FMT_NV12_BPP10_UBWC: @@ -583,13 +1071,20 @@ static inline unsigned int VENUS_UV_META_STRIDE(int color_fmt, int width) goto invalid_input; } - uv_meta_stride = MSM_MEDIA_ROUNDUP(width / 2, uv_tile_width); + uv_meta_stride = MSM_MEDIA_ROUNDUP((width+1)>>1, uv_tile_width); uv_meta_stride = MSM_MEDIA_ALIGN(uv_meta_stride, 64); invalid_input: return uv_meta_stride; } +/* + * Function arguments: + * @color_fmt + * @height + * Progressive: height + * Interlaced: (height+1)>>1 + */ static inline unsigned int VENUS_UV_META_SCANLINES(int color_fmt, int height) { int uv_tile_height = 0, uv_meta_scanlines = 0; @@ -602,13 +1097,14 @@ static inline unsigned int VENUS_UV_META_SCANLINES(int color_fmt, int height) uv_tile_height = 8; break; case COLOR_FMT_NV12_BPP10_UBWC: + case COLOR_FMT_P010_UBWC: uv_tile_height = 4; break; default: goto invalid_input; } - uv_meta_scanlines = MSM_MEDIA_ROUNDUP(height / 2, uv_tile_height); + uv_meta_scanlines = MSM_MEDIA_ROUNDUP((height+1)>>1, uv_tile_height); uv_meta_scanlines = MSM_MEDIA_ALIGN(uv_meta_scanlines, 16); invalid_input: @@ -617,7 +1113,8 @@ invalid_input: static inline unsigned int VENUS_RGB_STRIDE(int color_fmt, int width) { - unsigned int alignment = 0, stride = 0; + unsigned int alignment = 0, stride = 0, bpp = 4; + if (!width) goto invalid_input; @@ -625,14 +1122,19 @@ static inline unsigned int VENUS_RGB_STRIDE(int color_fmt, int width) case COLOR_FMT_RGBA8888: alignment = 128; break; + case COLOR_FMT_RGB565_UBWC: + alignment = 256; + bpp = 2; + break; case COLOR_FMT_RGBA8888_UBWC: + case COLOR_FMT_RGBA1010102_UBWC: alignment = 256; break; default: goto invalid_input; } - stride = MSM_MEDIA_ALIGN(width * 4, alignment); + stride = MSM_MEDIA_ALIGN(width * bpp, alignment); invalid_input: return stride; @@ -650,6 +1152,8 @@ static inline unsigned int VENUS_RGB_SCANLINES(int color_fmt, int height) alignment = 32; break; case COLOR_FMT_RGBA8888_UBWC: + case COLOR_FMT_RGBA1010102_UBWC: + case COLOR_FMT_RGB565_UBWC: alignment = 16; break; default: @@ -671,6 +1175,8 @@ static inline unsigned int VENUS_RGB_META_STRIDE(int color_fmt, int width) switch (color_fmt) { case COLOR_FMT_RGBA8888_UBWC: + case COLOR_FMT_RGBA1010102_UBWC: + case COLOR_FMT_RGB565_UBWC: rgb_tile_width = 16; break; default: @@ -693,6 +1199,8 @@ static inline unsigned int VENUS_RGB_META_SCANLINES(int color_fmt, int height) switch (color_fmt) { case COLOR_FMT_RGBA8888_UBWC: + case COLOR_FMT_RGBA1010102_UBWC: + case COLOR_FMT_RGB565_UBWC: rgb_tile_height = 4; break; default: @@ -706,11 +1214,22 @@ invalid_input: return rgb_meta_scanlines; } +/* + * Function arguments: + * @color_fmt + * @width + * Progressive: width + * Interlaced: width + * @height + * Progressive: height + * Interlaced: height + */ static inline unsigned int VENUS_BUFFER_SIZE( int color_fmt, int width, int height) { const unsigned int extra_size = VENUS_EXTRADATA_SIZE(width, height); unsigned int uv_alignment = 0, size = 0; + unsigned int w_alignment = 512; unsigned int y_plane, uv_plane, y_stride, uv_stride, y_sclines, uv_sclines; unsigned int y_ubwc_plane = 0, uv_ubwc_plane = 0; @@ -740,6 +1259,18 @@ static inline unsigned int VENUS_BUFFER_SIZE( size = y_plane + uv_plane + MSM_MEDIA_MAX(extra_size, 8 * y_stride); size = MSM_MEDIA_ALIGN(size, 4096); + + /* Additional size to cover last row of non-aligned frame */ + size += MSM_MEDIA_ALIGN(width, w_alignment) * w_alignment; + size = MSM_MEDIA_ALIGN(size, 4096); + break; + case COLOR_FMT_P010: + uv_alignment = 4096; + y_plane = y_stride * y_sclines; + uv_plane = uv_stride * uv_sclines + uv_alignment; + size = y_plane + uv_plane + + MSM_MEDIA_MAX(extra_size, 8 * y_stride); + size = MSM_MEDIA_ALIGN(size, 4096); break; case COLOR_FMT_NV12_MVTB: uv_alignment = 4096; @@ -750,6 +1281,30 @@ static inline unsigned int VENUS_BUFFER_SIZE( size = MSM_MEDIA_ALIGN(size, 4096); break; case COLOR_FMT_NV12_UBWC: + y_sclines = VENUS_Y_SCANLINES(color_fmt, (height+1)>>1); + y_ubwc_plane = MSM_MEDIA_ALIGN(y_stride * y_sclines, 4096); + uv_sclines = VENUS_UV_SCANLINES(color_fmt, (height+1)>>1); + uv_ubwc_plane = MSM_MEDIA_ALIGN(uv_stride * uv_sclines, 4096); + y_meta_stride = VENUS_Y_META_STRIDE(color_fmt, width); + y_meta_scanlines = + VENUS_Y_META_SCANLINES(color_fmt, (height+1)>>1); + y_meta_plane = MSM_MEDIA_ALIGN( + y_meta_stride * y_meta_scanlines, 4096); + uv_meta_stride = VENUS_UV_META_STRIDE(color_fmt, width); + uv_meta_scanlines = + VENUS_UV_META_SCANLINES(color_fmt, (height+1)>>1); + uv_meta_plane = MSM_MEDIA_ALIGN(uv_meta_stride * + uv_meta_scanlines, 4096); + + size = (y_ubwc_plane + uv_ubwc_plane + y_meta_plane + + uv_meta_plane)*2 + + MSM_MEDIA_MAX(extra_size + 8192, 48 * y_stride); + size = MSM_MEDIA_ALIGN(size, 4096); + + /* Additional size to cover last row of non-aligned frame */ + size += MSM_MEDIA_ALIGN(width, w_alignment) * w_alignment; + size = MSM_MEDIA_ALIGN(size, 4096); + break; case COLOR_FMT_NV12_BPP10_UBWC: y_ubwc_plane = MSM_MEDIA_ALIGN(y_stride * y_sclines, 4096); uv_ubwc_plane = MSM_MEDIA_ALIGN(uv_stride * uv_sclines, 4096); @@ -767,12 +1322,30 @@ static inline unsigned int VENUS_BUFFER_SIZE( MSM_MEDIA_MAX(extra_size + 8192, 48 * y_stride); size = MSM_MEDIA_ALIGN(size, 4096); break; + case COLOR_FMT_P010_UBWC: + y_ubwc_plane = MSM_MEDIA_ALIGN(y_stride * y_sclines, 4096); + uv_ubwc_plane = MSM_MEDIA_ALIGN(uv_stride * uv_sclines, 4096); + y_meta_stride = VENUS_Y_META_STRIDE(color_fmt, width); + y_meta_scanlines = VENUS_Y_META_SCANLINES(color_fmt, height); + y_meta_plane = MSM_MEDIA_ALIGN( + y_meta_stride * y_meta_scanlines, 4096); + uv_meta_stride = VENUS_UV_META_STRIDE(color_fmt, width); + uv_meta_scanlines = VENUS_UV_META_SCANLINES(color_fmt, height); + uv_meta_plane = MSM_MEDIA_ALIGN(uv_meta_stride * + uv_meta_scanlines, 4096); + + size = y_ubwc_plane + uv_ubwc_plane + y_meta_plane + + uv_meta_plane; + size = MSM_MEDIA_ALIGN(size, 4096); + break; case COLOR_FMT_RGBA8888: rgb_plane = MSM_MEDIA_ALIGN(rgb_stride * rgb_scanlines, 4096); size = rgb_plane; size = MSM_MEDIA_ALIGN(size, 4096); break; case COLOR_FMT_RGBA8888_UBWC: + case COLOR_FMT_RGBA1010102_UBWC: + case COLOR_FMT_RGB565_UBWC: rgb_ubwc_plane = MSM_MEDIA_ALIGN(rgb_stride * rgb_scanlines, 4096); rgb_meta_stride = VENUS_RGB_META_STRIDE(color_fmt, width); diff --git a/tools/replay/camera.cc b/tools/replay/camera.cc index 2962195af..671c0320b 100644 --- a/tools/replay/camera.cc +++ b/tools/replay/camera.cc @@ -1,14 +1,11 @@ #include "tools/replay/camera.h" -#include #include #include -#include "third_party/linux/include/msm_media_info.h" -#include "tools/replay/util.h" #include "system/camerad/cameras/nv12_info.h" - +#include "tools/replay/util.h" const int BUFFER_COUNT = 40; @@ -43,9 +40,10 @@ void CameraServer::startVipcServer() { if (cam.width > 0 && cam.height > 0) { rInfo("camera[%d] frame size %dx%d", cam.type, cam.width, cam.height); - auto [nv12_width, nv12_height, nv12_buffer_size] = get_nv12_info(cam.width, cam.height); + auto [stride, y_height, uv_height_, buffer_size] = get_nv12_info(cam.width, cam.height); + (void)uv_height_; // unused in replay vipc_server_->create_buffers_with_sizes(cam.stream_type, BUFFER_COUNT, cam.width, cam.height, - nv12_buffer_size, nv12_width, nv12_width * nv12_height); + buffer_size, stride, stride * y_height); if (!cam.thread.joinable()) { cam.thread = std::thread(&CameraServer::cameraThread, this, std::ref(cam)); } From a7dfd36c00c910b694dfd3e2b3f6e0a8cc892802 Mon Sep 17 00:00:00 2001 From: Lukas <61192133+lukasloetkolben@users.noreply.github.com> Date: Wed, 21 Jan 2026 00:15:12 +0100 Subject: [PATCH 753/910] docs: comma 3X to comma four (#37009) * comma 3X -> comma four * add comma four ports image --- README.md | 8 ++++---- docs/CONTRIBUTING.md | 8 ++++---- docs/assets/four-ports.svg | 3 +++ docs/concepts/glossary.md | 2 +- docs/getting-started/what-is-openpilot.md | 2 +- docs/how-to/connect-to-comma.md | 17 +++++++++-------- docs/how-to/replay-a-drive.md | 2 +- mkdocs.yml | 2 +- selfdrive/debug/README.md | 2 +- tools/camerastream/README.md | 2 +- tools/joystick/joystick_control.py | 4 ++-- tools/replay/README.md | 2 +- 12 files changed, 29 insertions(+), 25 deletions(-) create mode 100644 docs/assets/four-ports.svg diff --git a/README.md b/README.md index a77a80935..158bb08c4 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ · Community · - Try it on a comma 3X + Try it on a comma four

Quick start: `bash <(curl -fsSL openpilot.comma.ai)` @@ -42,10 +42,10 @@ Using openpilot in a car ------ To use openpilot in a car, you need four things: -1. **Supported Device:** a comma 3X, available at [comma.ai/shop](https://comma.ai/shop/comma-3x). -2. **Software:** The setup procedure for the comma 3X allows users to enter a URL for custom software. Use the URL `openpilot.comma.ai` to install the release version. +1. **Supported Device:** a comma four, available at [comma.ai/shop](https://comma.ai/shop/comma-3x). +2. **Software:** The setup procedure for the comma four allows users to enter a URL for custom software. Use the URL `openpilot.comma.ai` to install the release version. 3. **Supported Car:** Ensure that you have one of [the 275+ supported cars](docs/CARS.md). -4. **Car Harness:** You will also need a [car harness](https://comma.ai/shop/car-harness) to connect your comma 3X to your car. +4. **Car Harness:** You will also need a [car harness](https://comma.ai/shop/car-harness) to connect your comma four to your car. We have detailed instructions for [how to install the harness and device in a car](https://comma.ai/setup). Note that it's possible to run openpilot on [other hardware](https://blog.comma.ai/self-driving-car-for-free/), although it's not plug-and-play. diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 7583095ea..d189324ff 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -13,13 +13,13 @@ Development is coordinated through [Discord](https://discord.comma.ai) and GitHu ## What contributions are we looking for? **openpilot's priorities are [safety](SAFETY.md), stability, quality, and features, in that order.** -openpilot is part of comma's mission to *solve self-driving cars while delivering shippable intermediaries*, and all development is towards that goal. +openpilot is part of comma's mission to *solve self-driving cars while delivering shippable intermediaries*, and all development is towards that goal. ### What gets merged? The probability of a pull request being merged is a function of its value to the project and the effort it will take us to get it merged. If a PR offers *some* value but will take lots of time to get merged, it will be closed. -Simple, well-tested bug fixes are the easiest to merge, and new features are the hardest to get merged. +Simple, well-tested bug fixes are the easiest to merge, and new features are the hardest to get merged. All of these are examples of good PRs: * typo fix: https://github.com/commaai/openpilot/pull/30678 @@ -29,7 +29,7 @@ All of these are examples of good PRs: ### What doesn't get merged? -* **style changes**: code is art, and it's up to the author to make it beautiful +* **style changes**: code is art, and it's up to the author to make it beautiful * **500+ line PRs**: clean it up, break it up into smaller PRs, or both * **PRs without a clear goal**: every PR must have a singular and clear goal * **UI design**: we do not have a good review process for this yet @@ -39,7 +39,7 @@ All of these are examples of good PRs: ### First contribution [Projects / openpilot bounties](https://github.com/orgs/commaai/projects/26/views/1?pane=info) is the best place to get started and goes in-depth on what's expected when working on a bounty. -There's lot of bounties that don't require a comma 3X or a car. +There's lot of bounties that don't require a comma four or a car. ## Pull Requests diff --git a/docs/assets/four-ports.svg b/docs/assets/four-ports.svg new file mode 100644 index 000000000..7bab31abf --- /dev/null +++ b/docs/assets/four-ports.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:494bd79c4d81d8bf766845cb451fa14e5b9ad931ff8aa90daea0ba67e164abe3 +size 105562 diff --git a/docs/concepts/glossary.md b/docs/concepts/glossary.md index a09b0f078..df9e9aa08 100644 --- a/docs/concepts/glossary.md +++ b/docs/concepts/glossary.md @@ -6,4 +6,4 @@ * **segment**: routes are split into one minute chunks called segments. * **comma connect**: the web viewer for all your routes; check it out at [connect.comma.ai](https://connect.comma.ai). * **panda**: this is the secondary processor on the device that implements the functional safety and directly talks to the car over CAN. See the [panda repo](https://github.com/commaai/panda). -* **comma 3X**: the latest hardware by comma.ai for running openpilot. more info at [comma.ai/shop](https://comma.ai/shop). +* **comma four**: the latest hardware by comma.ai for running openpilot. more info at [comma.ai/shop](https://comma.ai/shop). diff --git a/docs/getting-started/what-is-openpilot.md b/docs/getting-started/what-is-openpilot.md index b3c56c841..6fab2b979 100644 --- a/docs/getting-started/what-is-openpilot.md +++ b/docs/getting-started/what-is-openpilot.md @@ -5,7 +5,7 @@ ## How do I use it? -openpilot is designed to be used on the comma 3X. +openpilot is designed to be used on the comma four. ## How does it work? diff --git a/docs/how-to/connect-to-comma.md b/docs/how-to/connect-to-comma.md index 5f02e1159..f0e45b19a 100644 --- a/docs/how-to/connect-to-comma.md +++ b/docs/how-to/connect-to-comma.md @@ -1,19 +1,20 @@ -# connect to a comma 3X +# Connect to comma 3X or comma four -A comma 3X is a normal [Linux](https://github.com/commaai/agnos-builder) computer that exposes [SSH](https://wiki.archlinux.org/title/Secure_Shell) and a [serial console](https://wiki.archlinux.org/title/Working_with_the_serial_console). +A comma four is a normal [Linux](https://github.com/commaai/agnos-builder) computer that exposes [SSH](https://wiki.archlinux.org/title/Secure_Shell) and a [serial console](https://wiki.archlinux.org/title/Working_with_the_serial_console). ## Serial Console -On both the comma three and 3X, the serial console is accessible from the main OBD-C port. +On the comma 3X, the serial console is accessible from the main OBD-C port. Connect the comma 3X to your computer with a normal USB C cable, or use a [comma serial](https://comma.ai/shop/comma-serial) for steady 12V power. -On the comma three, the serial console is exposed through a UART-to-USB chip, and `tools/scripts/serial.sh` can be used to connect. - -On the comma 3X, the serial console is accessible through the [panda](https://github.com/commaai/panda) using the `panda/tests/som_debug.sh` script. +The serial console is accessible through the [panda](https://github.com/commaai/panda) using the `panda/tests/som_debug.sh` script. * Username: `comma` * Password: `comma` +> [!NOTE] +> Serial console access through the OBD-C port is not available on the comma four. On comma four devices, serial access requires opening the device to access the internal debug connector. + ## SSH In order to SSH into your device, you'll need a GitHub account with SSH keys. See this [GitHub article](https://docs.github.com/en/github/authenticating-to-github/connecting-to-github-with-ssh) for getting your account setup with SSH keys. @@ -34,7 +35,7 @@ For doing development work on device, it's recommended to use [SSH agent forward In order to use ADB on your device, you'll need to perform the following steps using the image below for reference: -![comma 3/3x back](../assets/three-back.svg) +![comma four ports](../assets/four-ports.svg) * Plug your device into constant power using port 2, letting the device boot up * Enable ADB in your device's settings @@ -45,7 +46,7 @@ In order to use ADB on your device, you'll need to perform the following steps u * Here's an example command for connecting to your device using its tethered connection: `adb connect 192.168.43.1:5555` > [!NOTE] -> The default port for ADB is 5555 on the comma 3X. +> The default port for ADB is 5555 on the comma four. For more info on ADB, see the [Android Debug Bridge (ADB) documentation](https://developer.android.com/tools/adb). diff --git a/docs/how-to/replay-a-drive.md b/docs/how-to/replay-a-drive.md index b0db36a46..a11b29dcc 100644 --- a/docs/how-to/replay-a-drive.md +++ b/docs/how-to/replay-a-drive.md @@ -8,7 +8,7 @@ Replaying is a critical tool for openpilot development and debugging. Just run `tools/replay/replay --demo`. ## Replaying CAN data -*Hardware required: jungle and comma 3X* +*Hardware required: jungle and comma four* 1. Connect your PC to a jungle. 2. diff --git a/mkdocs.yml b/mkdocs.yml index 550f807ac..f54c6e39b 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -21,7 +21,7 @@ nav: - What is openpilot?: getting-started/what-is-openpilot.md - How-to: - Turn the speed blue: how-to/turn-the-speed-blue.md - - Connect to a comma 3X: how-to/connect-to-comma.md + - Connect to a comma 3X or comma four: how-to/connect-to-comma.md # - Make your first pull request: how-to/make-first-pr.md #- Replay a drive: how-to/replay-a-drive.md - Concepts: diff --git a/selfdrive/debug/README.md b/selfdrive/debug/README.md index 83b8a994d..172cf700e 100644 --- a/selfdrive/debug/README.md +++ b/selfdrive/debug/README.md @@ -52,7 +52,7 @@ optional arguments: -h, --help show this help message and exit --debug enable ISO-TP/UDS stack debugging output -This tool is meant to run directly on a vehicle-installed comma three, with +This tool is meant to run directly on a vehicle-installed comma four, with the openpilot/tmux processes stopped. It should also work on a separate PC with a USB- attached comma panda. Vehicle ignition must be on. Recommend engine not be running when making changes. Must turn ignition off and on again for any changes to take effect. diff --git a/tools/camerastream/README.md b/tools/camerastream/README.md index 2f7498a07..f75ebbf0f 100644 --- a/tools/camerastream/README.md +++ b/tools/camerastream/README.md @@ -49,7 +49,7 @@ usage: compressed_vipc.py [-h] [--nvidia] [--cams CAMS] [--silent] addr Decode video streams and broadcast on VisionIPC positional arguments: - addr Address of comma three + addr Address of comma four options: -h, --help show this help message and exit diff --git a/tools/joystick/joystick_control.py b/tools/joystick/joystick_control.py index 11d17e587..8fe28ec0f 100755 --- a/tools/joystick/joystick_control.py +++ b/tools/joystick/joystick_control.py @@ -42,7 +42,7 @@ class Keyboard: class Joystick: def __init__(self): - # This class supports a PlayStation 5 DualSense controller on the comma 3X + # This class supports a PlayStation 5 DualSense controller on the comma four # TODO: find a way to get this from API or detect gamepad/PC, perhaps "inputs" doesn't support it self.cancel_button = 'BTN_NORTH' # BTN_NORTH=X/triangle if HARDWARE.get_device_type() == 'pc': @@ -123,7 +123,7 @@ def main(): if __name__ == '__main__': parser = argparse.ArgumentParser(description='Publishes events from your joystick to control your car.\n' + 'openpilot must be offroad before starting joystick_control. This tool supports ' + - 'a PlayStation 5 DualSense controller on the comma 3X.', + 'a PlayStation 5 DualSense controller on the comma four.', formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser.add_argument('--keyboard', action='store_true', help='Use your keyboard instead of a joystick') args = parser.parse_args() diff --git a/tools/replay/README.md b/tools/replay/README.md index 794c08f6a..97ee91d98 100644 --- a/tools/replay/README.md +++ b/tools/replay/README.md @@ -101,7 +101,7 @@ tools/plotjuggler/juggle.py --stream ## watch3 -watch all three cameras simultaneously from your comma three routes with watch3 +watch all three cameras simultaneously from your comma four routes with watch3 simply replay a route using the `--dcam` and `--ecam` flags: From adf6f28ebf31fc3dcc502a5d761bf113d0d46546 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Tue, 20 Jan 2026 15:34:57 -0800 Subject: [PATCH 754/910] LatcontrolTorque: always fill buffer (#36991) --- selfdrive/controls/lib/latcontrol_torque.py | 17 +++++++++-------- selfdrive/test/process_replay/ref_commit | 2 +- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/selfdrive/controls/lib/latcontrol_torque.py b/selfdrive/controls/lib/latcontrol_torque.py index 0ba38736d..1f7fb4dfa 100644 --- a/selfdrive/controls/lib/latcontrol_torque.py +++ b/selfdrive/controls/lib/latcontrol_torque.py @@ -59,27 +59,28 @@ class LatControlTorque(LatControl): def update(self, active, CS, VM, params, steer_limited_by_safety, desired_curvature, curvature_limited, lat_delay): pid_log = log.ControlsState.LateralTorqueState.new_message() pid_log.version = VERSION + measured_curvature = -VM.calc_curvature(math.radians(CS.steeringAngleDeg - params.angleOffsetDeg), CS.vEgo, params.roll) + measurement = measured_curvature * CS.vEgo ** 2 + future_desired_lateral_accel = desired_curvature * CS.vEgo ** 2 + self.lat_accel_request_buffer.append(future_desired_lateral_accel) + if not active: output_torque = 0.0 pid_log.active = False else: - measured_curvature = -VM.calc_curvature(math.radians(CS.steeringAngleDeg - params.angleOffsetDeg), CS.vEgo, params.roll) roll_compensation = params.roll * ACCELERATION_DUE_TO_GRAVITY curvature_deadzone = abs(VM.calc_curvature(math.radians(self.steering_angle_deadzone_deg), CS.vEgo, 0.0)) lateral_accel_deadzone = curvature_deadzone * CS.vEgo ** 2 - delay_frames = int(np.clip(lat_delay / self.dt, 1, self.lat_accel_request_buffer_len)) + delay_frames = int(np.clip(lat_delay / self.dt + 1, 1, self.lat_accel_request_buffer_len)) expected_lateral_accel = self.lat_accel_request_buffer[-delay_frames] + setpoint = expected_lateral_accel + error = setpoint - measurement + lookahead_idx = int(np.clip(-delay_frames + self.lookahead_frames, -self.lat_accel_request_buffer_len+1, -2)) raw_lateral_jerk = (self.lat_accel_request_buffer[lookahead_idx+1] - self.lat_accel_request_buffer[lookahead_idx-1]) / (2 * self.dt) desired_lateral_jerk = self.jerk_filter.update(raw_lateral_jerk) - future_desired_lateral_accel = desired_curvature * CS.vEgo ** 2 - self.lat_accel_request_buffer.append(future_desired_lateral_accel) gravity_adjusted_future_lateral_accel = future_desired_lateral_accel - roll_compensation - setpoint = expected_lateral_accel - - measurement = measured_curvature * CS.vEgo ** 2 - error = setpoint - measurement # do error correction in lateral acceleration space, convert at end to handle non-linear torque responses correctly pid_log.error = float(error) diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index c109bf49f..274bec8b4 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -b259f6f8f099a9d82e4c65dd5deae2e4e293007b \ No newline at end of file +cdd8ecaf03b0581d6a4df7659b916f3d22167a23 \ No newline at end of file From 79472cdf83c65345e666f40069ac34082bb4be84 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 20 Jan 2026 16:04:05 -0800 Subject: [PATCH 755/910] Revert "docs: comma 3X to comma four (#37009)" This reverts commit a7dfd36c00c910b694dfd3e2b3f6e0a8cc892802. --- README.md | 8 ++++---- docs/CONTRIBUTING.md | 8 ++++---- docs/assets/four-ports.svg | 3 --- docs/concepts/glossary.md | 2 +- docs/getting-started/what-is-openpilot.md | 2 +- docs/how-to/connect-to-comma.md | 17 ++++++++--------- docs/how-to/replay-a-drive.md | 2 +- mkdocs.yml | 2 +- selfdrive/debug/README.md | 2 +- tools/camerastream/README.md | 2 +- tools/joystick/joystick_control.py | 4 ++-- tools/replay/README.md | 2 +- 12 files changed, 25 insertions(+), 29 deletions(-) delete mode 100644 docs/assets/four-ports.svg diff --git a/README.md b/README.md index 158bb08c4..a77a80935 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ · Community · - Try it on a comma four + Try it on a comma 3X

Quick start: `bash <(curl -fsSL openpilot.comma.ai)` @@ -42,10 +42,10 @@ Using openpilot in a car ------ To use openpilot in a car, you need four things: -1. **Supported Device:** a comma four, available at [comma.ai/shop](https://comma.ai/shop/comma-3x). -2. **Software:** The setup procedure for the comma four allows users to enter a URL for custom software. Use the URL `openpilot.comma.ai` to install the release version. +1. **Supported Device:** a comma 3X, available at [comma.ai/shop](https://comma.ai/shop/comma-3x). +2. **Software:** The setup procedure for the comma 3X allows users to enter a URL for custom software. Use the URL `openpilot.comma.ai` to install the release version. 3. **Supported Car:** Ensure that you have one of [the 275+ supported cars](docs/CARS.md). -4. **Car Harness:** You will also need a [car harness](https://comma.ai/shop/car-harness) to connect your comma four to your car. +4. **Car Harness:** You will also need a [car harness](https://comma.ai/shop/car-harness) to connect your comma 3X to your car. We have detailed instructions for [how to install the harness and device in a car](https://comma.ai/setup). Note that it's possible to run openpilot on [other hardware](https://blog.comma.ai/self-driving-car-for-free/), although it's not plug-and-play. diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index d189324ff..7583095ea 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -13,13 +13,13 @@ Development is coordinated through [Discord](https://discord.comma.ai) and GitHu ## What contributions are we looking for? **openpilot's priorities are [safety](SAFETY.md), stability, quality, and features, in that order.** -openpilot is part of comma's mission to *solve self-driving cars while delivering shippable intermediaries*, and all development is towards that goal. +openpilot is part of comma's mission to *solve self-driving cars while delivering shippable intermediaries*, and all development is towards that goal. ### What gets merged? The probability of a pull request being merged is a function of its value to the project and the effort it will take us to get it merged. If a PR offers *some* value but will take lots of time to get merged, it will be closed. -Simple, well-tested bug fixes are the easiest to merge, and new features are the hardest to get merged. +Simple, well-tested bug fixes are the easiest to merge, and new features are the hardest to get merged. All of these are examples of good PRs: * typo fix: https://github.com/commaai/openpilot/pull/30678 @@ -29,7 +29,7 @@ All of these are examples of good PRs: ### What doesn't get merged? -* **style changes**: code is art, and it's up to the author to make it beautiful +* **style changes**: code is art, and it's up to the author to make it beautiful * **500+ line PRs**: clean it up, break it up into smaller PRs, or both * **PRs without a clear goal**: every PR must have a singular and clear goal * **UI design**: we do not have a good review process for this yet @@ -39,7 +39,7 @@ All of these are examples of good PRs: ### First contribution [Projects / openpilot bounties](https://github.com/orgs/commaai/projects/26/views/1?pane=info) is the best place to get started and goes in-depth on what's expected when working on a bounty. -There's lot of bounties that don't require a comma four or a car. +There's lot of bounties that don't require a comma 3X or a car. ## Pull Requests diff --git a/docs/assets/four-ports.svg b/docs/assets/four-ports.svg deleted file mode 100644 index 7bab31abf..000000000 --- a/docs/assets/four-ports.svg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:494bd79c4d81d8bf766845cb451fa14e5b9ad931ff8aa90daea0ba67e164abe3 -size 105562 diff --git a/docs/concepts/glossary.md b/docs/concepts/glossary.md index df9e9aa08..a09b0f078 100644 --- a/docs/concepts/glossary.md +++ b/docs/concepts/glossary.md @@ -6,4 +6,4 @@ * **segment**: routes are split into one minute chunks called segments. * **comma connect**: the web viewer for all your routes; check it out at [connect.comma.ai](https://connect.comma.ai). * **panda**: this is the secondary processor on the device that implements the functional safety and directly talks to the car over CAN. See the [panda repo](https://github.com/commaai/panda). -* **comma four**: the latest hardware by comma.ai for running openpilot. more info at [comma.ai/shop](https://comma.ai/shop). +* **comma 3X**: the latest hardware by comma.ai for running openpilot. more info at [comma.ai/shop](https://comma.ai/shop). diff --git a/docs/getting-started/what-is-openpilot.md b/docs/getting-started/what-is-openpilot.md index 6fab2b979..b3c56c841 100644 --- a/docs/getting-started/what-is-openpilot.md +++ b/docs/getting-started/what-is-openpilot.md @@ -5,7 +5,7 @@ ## How do I use it? -openpilot is designed to be used on the comma four. +openpilot is designed to be used on the comma 3X. ## How does it work? diff --git a/docs/how-to/connect-to-comma.md b/docs/how-to/connect-to-comma.md index f0e45b19a..5f02e1159 100644 --- a/docs/how-to/connect-to-comma.md +++ b/docs/how-to/connect-to-comma.md @@ -1,20 +1,19 @@ -# Connect to comma 3X or comma four +# connect to a comma 3X -A comma four is a normal [Linux](https://github.com/commaai/agnos-builder) computer that exposes [SSH](https://wiki.archlinux.org/title/Secure_Shell) and a [serial console](https://wiki.archlinux.org/title/Working_with_the_serial_console). +A comma 3X is a normal [Linux](https://github.com/commaai/agnos-builder) computer that exposes [SSH](https://wiki.archlinux.org/title/Secure_Shell) and a [serial console](https://wiki.archlinux.org/title/Working_with_the_serial_console). ## Serial Console -On the comma 3X, the serial console is accessible from the main OBD-C port. +On both the comma three and 3X, the serial console is accessible from the main OBD-C port. Connect the comma 3X to your computer with a normal USB C cable, or use a [comma serial](https://comma.ai/shop/comma-serial) for steady 12V power. -The serial console is accessible through the [panda](https://github.com/commaai/panda) using the `panda/tests/som_debug.sh` script. +On the comma three, the serial console is exposed through a UART-to-USB chip, and `tools/scripts/serial.sh` can be used to connect. + +On the comma 3X, the serial console is accessible through the [panda](https://github.com/commaai/panda) using the `panda/tests/som_debug.sh` script. * Username: `comma` * Password: `comma` -> [!NOTE] -> Serial console access through the OBD-C port is not available on the comma four. On comma four devices, serial access requires opening the device to access the internal debug connector. - ## SSH In order to SSH into your device, you'll need a GitHub account with SSH keys. See this [GitHub article](https://docs.github.com/en/github/authenticating-to-github/connecting-to-github-with-ssh) for getting your account setup with SSH keys. @@ -35,7 +34,7 @@ For doing development work on device, it's recommended to use [SSH agent forward In order to use ADB on your device, you'll need to perform the following steps using the image below for reference: -![comma four ports](../assets/four-ports.svg) +![comma 3/3x back](../assets/three-back.svg) * Plug your device into constant power using port 2, letting the device boot up * Enable ADB in your device's settings @@ -46,7 +45,7 @@ In order to use ADB on your device, you'll need to perform the following steps u * Here's an example command for connecting to your device using its tethered connection: `adb connect 192.168.43.1:5555` > [!NOTE] -> The default port for ADB is 5555 on the comma four. +> The default port for ADB is 5555 on the comma 3X. For more info on ADB, see the [Android Debug Bridge (ADB) documentation](https://developer.android.com/tools/adb). diff --git a/docs/how-to/replay-a-drive.md b/docs/how-to/replay-a-drive.md index a11b29dcc..b0db36a46 100644 --- a/docs/how-to/replay-a-drive.md +++ b/docs/how-to/replay-a-drive.md @@ -8,7 +8,7 @@ Replaying is a critical tool for openpilot development and debugging. Just run `tools/replay/replay --demo`. ## Replaying CAN data -*Hardware required: jungle and comma four* +*Hardware required: jungle and comma 3X* 1. Connect your PC to a jungle. 2. diff --git a/mkdocs.yml b/mkdocs.yml index f54c6e39b..550f807ac 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -21,7 +21,7 @@ nav: - What is openpilot?: getting-started/what-is-openpilot.md - How-to: - Turn the speed blue: how-to/turn-the-speed-blue.md - - Connect to a comma 3X or comma four: how-to/connect-to-comma.md + - Connect to a comma 3X: how-to/connect-to-comma.md # - Make your first pull request: how-to/make-first-pr.md #- Replay a drive: how-to/replay-a-drive.md - Concepts: diff --git a/selfdrive/debug/README.md b/selfdrive/debug/README.md index 172cf700e..83b8a994d 100644 --- a/selfdrive/debug/README.md +++ b/selfdrive/debug/README.md @@ -52,7 +52,7 @@ optional arguments: -h, --help show this help message and exit --debug enable ISO-TP/UDS stack debugging output -This tool is meant to run directly on a vehicle-installed comma four, with +This tool is meant to run directly on a vehicle-installed comma three, with the openpilot/tmux processes stopped. It should also work on a separate PC with a USB- attached comma panda. Vehicle ignition must be on. Recommend engine not be running when making changes. Must turn ignition off and on again for any changes to take effect. diff --git a/tools/camerastream/README.md b/tools/camerastream/README.md index f75ebbf0f..2f7498a07 100644 --- a/tools/camerastream/README.md +++ b/tools/camerastream/README.md @@ -49,7 +49,7 @@ usage: compressed_vipc.py [-h] [--nvidia] [--cams CAMS] [--silent] addr Decode video streams and broadcast on VisionIPC positional arguments: - addr Address of comma four + addr Address of comma three options: -h, --help show this help message and exit diff --git a/tools/joystick/joystick_control.py b/tools/joystick/joystick_control.py index 8fe28ec0f..11d17e587 100755 --- a/tools/joystick/joystick_control.py +++ b/tools/joystick/joystick_control.py @@ -42,7 +42,7 @@ class Keyboard: class Joystick: def __init__(self): - # This class supports a PlayStation 5 DualSense controller on the comma four + # This class supports a PlayStation 5 DualSense controller on the comma 3X # TODO: find a way to get this from API or detect gamepad/PC, perhaps "inputs" doesn't support it self.cancel_button = 'BTN_NORTH' # BTN_NORTH=X/triangle if HARDWARE.get_device_type() == 'pc': @@ -123,7 +123,7 @@ def main(): if __name__ == '__main__': parser = argparse.ArgumentParser(description='Publishes events from your joystick to control your car.\n' + 'openpilot must be offroad before starting joystick_control. This tool supports ' + - 'a PlayStation 5 DualSense controller on the comma four.', + 'a PlayStation 5 DualSense controller on the comma 3X.', formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser.add_argument('--keyboard', action='store_true', help='Use your keyboard instead of a joystick') args = parser.parse_args() diff --git a/tools/replay/README.md b/tools/replay/README.md index 97ee91d98..794c08f6a 100644 --- a/tools/replay/README.md +++ b/tools/replay/README.md @@ -101,7 +101,7 @@ tools/plotjuggler/juggle.py --stream ## watch3 -watch all three cameras simultaneously from your comma four routes with watch3 +watch all three cameras simultaneously from your comma three routes with watch3 simply replay a route using the `--dcam` and `--ecam` flags: From bc979ea6aa858a811b8ecb75485577ecf10f251d Mon Sep 17 00:00:00 2001 From: felsager <76905857+felsager@users.noreply.github.com> Date: Tue, 20 Jan 2026 16:16:38 -0800 Subject: [PATCH 756/910] Latcontrol torque test: ensure desired lateral accel buffer is consistent (#37004) --- .../tests/test_latcontrol_torque_buffer.py | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 selfdrive/controls/tests/test_latcontrol_torque_buffer.py diff --git a/selfdrive/controls/tests/test_latcontrol_torque_buffer.py b/selfdrive/controls/tests/test_latcontrol_torque_buffer.py new file mode 100644 index 000000000..76d0c2842 --- /dev/null +++ b/selfdrive/controls/tests/test_latcontrol_torque_buffer.py @@ -0,0 +1,36 @@ +from parameterized import parameterized + +from cereal import car, log +from opendbc.car.car_helpers import interfaces +from opendbc.car.toyota.values import CAR as TOYOTA +from opendbc.car.vehicle_model import VehicleModel +from openpilot.common.realtime import DT_CTRL +from openpilot.selfdrive.controls.lib.latcontrol_torque import LatControlTorque, LAT_ACCEL_REQUEST_BUFFER_SECONDS + +def get_controller(car_name): + CarInterface = interfaces[car_name] + CP = CarInterface.get_non_essential_params(car_name) + CI = CarInterface(CP) + VM = VehicleModel(CP) + controller = LatControlTorque(CP.as_reader(), CI, DT_CTRL) + return controller, VM + +class TestLatControlTorqueBuffer: + + @parameterized.expand([(TOYOTA.TOYOTA_COROLLA_TSS2,)]) + def test_request_buffer_consistency(self, car_name): + buffer_steps = int(LAT_ACCEL_REQUEST_BUFFER_SECONDS / DT_CTRL) + controller, VM = get_controller(car_name) + + CS = car.CarState.new_message() + CS.vEgo = 30 + CS.steeringPressed = False + params = log.LiveParametersData.new_message() + + for _ in range(buffer_steps): + controller.update(True, CS, VM, params, False, 0.001, False, 0.2) + assert all(val != 0 for val in controller.lat_accel_request_buffer) + + for _ in range(buffer_steps): + controller.update(False, CS, VM, params, False, 0.0, False, 0.2) + assert all(val == 0 for val in controller.lat_accel_request_buffer) From c9cfe2c7273074a350a39fd4d78d16933cb6a81f Mon Sep 17 00:00:00 2001 From: felsager <76905857+felsager@users.noreply.github.com> Date: Wed, 21 Jan 2026 12:32:56 -0800 Subject: [PATCH 757/910] LatcontrolTorque: move jerk calculation and filtering outside if else (#37011) --- selfdrive/controls/lib/latcontrol_torque.py | 36 ++++++++++----------- selfdrive/test/process_replay/ref_commit | 2 +- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/selfdrive/controls/lib/latcontrol_torque.py b/selfdrive/controls/lib/latcontrol_torque.py index 1f7fb4dfa..903700d4b 100644 --- a/selfdrive/controls/lib/latcontrol_torque.py +++ b/selfdrive/controls/lib/latcontrol_torque.py @@ -64,30 +64,30 @@ class LatControlTorque(LatControl): future_desired_lateral_accel = desired_curvature * CS.vEgo ** 2 self.lat_accel_request_buffer.append(future_desired_lateral_accel) + roll_compensation = params.roll * ACCELERATION_DUE_TO_GRAVITY + curvature_deadzone = abs(VM.calc_curvature(math.radians(self.steering_angle_deadzone_deg), CS.vEgo, 0.0)) + lateral_accel_deadzone = curvature_deadzone * CS.vEgo ** 2 + + delay_frames = int(np.clip(lat_delay / self.dt + 1, 1, self.lat_accel_request_buffer_len)) + expected_lateral_accel = self.lat_accel_request_buffer[-delay_frames] + setpoint = expected_lateral_accel + error = setpoint - measurement + + lookahead_idx = int(np.clip(-delay_frames + self.lookahead_frames, -self.lat_accel_request_buffer_len+1, -2)) + raw_lateral_jerk = (self.lat_accel_request_buffer[lookahead_idx+1] - self.lat_accel_request_buffer[lookahead_idx-1]) / (2 * self.dt) + desired_lateral_jerk = self.jerk_filter.update(raw_lateral_jerk) + gravity_adjusted_future_lateral_accel = future_desired_lateral_accel - roll_compensation + ff = gravity_adjusted_future_lateral_accel + # latAccelOffset corrects roll compensation bias from device roll misalignment relative to car roll + ff -= self.torque_params.latAccelOffset + ff += get_friction(error + JERK_GAIN * desired_lateral_jerk, lateral_accel_deadzone, FRICTION_THRESHOLD, self.torque_params) + if not active: output_torque = 0.0 pid_log.active = False else: - roll_compensation = params.roll * ACCELERATION_DUE_TO_GRAVITY - curvature_deadzone = abs(VM.calc_curvature(math.radians(self.steering_angle_deadzone_deg), CS.vEgo, 0.0)) - lateral_accel_deadzone = curvature_deadzone * CS.vEgo ** 2 - - delay_frames = int(np.clip(lat_delay / self.dt + 1, 1, self.lat_accel_request_buffer_len)) - expected_lateral_accel = self.lat_accel_request_buffer[-delay_frames] - setpoint = expected_lateral_accel - error = setpoint - measurement - - lookahead_idx = int(np.clip(-delay_frames + self.lookahead_frames, -self.lat_accel_request_buffer_len+1, -2)) - raw_lateral_jerk = (self.lat_accel_request_buffer[lookahead_idx+1] - self.lat_accel_request_buffer[lookahead_idx-1]) / (2 * self.dt) - desired_lateral_jerk = self.jerk_filter.update(raw_lateral_jerk) - gravity_adjusted_future_lateral_accel = future_desired_lateral_accel - roll_compensation - # do error correction in lateral acceleration space, convert at end to handle non-linear torque responses correctly pid_log.error = float(error) - ff = gravity_adjusted_future_lateral_accel - # latAccelOffset corrects roll compensation bias from device roll misalignment relative to car roll - ff -= self.torque_params.latAccelOffset - ff += get_friction(error + JERK_GAIN * desired_lateral_jerk, lateral_accel_deadzone, FRICTION_THRESHOLD, self.torque_params) freeze_integrator = steer_limited_by_safety or CS.steeringPressed or CS.vEgo < 5 output_lataccel = self.pid.update(pid_log.error, speed=CS.vEgo, feedforward=ff, freeze_integrator=freeze_integrator) diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 274bec8b4..7b9039180 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -cdd8ecaf03b0581d6a4df7659b916f3d22167a23 \ No newline at end of file +77951c4ccd0916b87c8dfda9faa33cd2d5d2cc11 \ No newline at end of file From 1459d3519da2fdb2d981baf7811c2eaa2127eb80 Mon Sep 17 00:00:00 2001 From: ZwX1616 Date: Thu, 22 Jan 2026 18:41:08 -0800 Subject: [PATCH 758/910] DM: Ford GT model (#37013) * b483cec4-7816-4570-a774-be3a2c100098/50 * shipfest * da4b8724-8998-45da-aa36-d8fb390492b9 * revert * typo * deprecates --- cereal/log.capnp | 4 +-- .../modeld/models/dmonitoring_model.onnx | 4 +-- selfdrive/monitoring/helpers.py | 25 +++---------------- 3 files changed, 8 insertions(+), 25 deletions(-) diff --git a/cereal/log.capnp b/cereal/log.capnp index 2f300881b..12bef17b9 100644 --- a/cereal/log.capnp +++ b/cereal/log.capnp @@ -2227,9 +2227,9 @@ struct DriverMonitoringState @0xb83cda094a1da284 { isActiveMode @16 :Bool; isRHD @4 :Bool; uncertainCount @19 :UInt32; - phoneProbOffset @20 :Float32; - phoneProbValidCount @21 :UInt32; + phoneProbOffsetDEPRECATED @20 :Float32; + phoneProbValidCountDEPRECATED @21 :UInt32; isPreviewDEPRECATED @15 :Bool; rhdCheckedDEPRECATED @5 :Bool; eventsDEPRECATED @0 :List(Car.OnroadEventDEPRECATED); diff --git a/selfdrive/modeld/models/dmonitoring_model.onnx b/selfdrive/modeld/models/dmonitoring_model.onnx index 9b1c4a183..4052a1548 100644 --- a/selfdrive/modeld/models/dmonitoring_model.onnx +++ b/selfdrive/modeld/models/dmonitoring_model.onnx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3446bf8b22e50e47669a25bf32460ae8baf8547037f346753e19ecbfcf6d4e59 -size 6954368 +oid sha256:35e4a5d4c4d481f915e42358af4665b2c92b8f5c1efd1c0731f21b876ad1d856 +size 6954249 diff --git a/selfdrive/monitoring/helpers.py b/selfdrive/monitoring/helpers.py index 3377ce6c6..0b54504b6 100644 --- a/selfdrive/monitoring/helpers.py +++ b/selfdrive/monitoring/helpers.py @@ -35,14 +35,7 @@ class DRIVER_MONITOR_SETTINGS: self._EYE_THRESHOLD = 0.65 self._SG_THRESHOLD = 0.9 self._BLINK_THRESHOLD = 0.865 - - self._PHONE_THRESH = 0.75 if device_type == 'mici' else 0.4 - self._PHONE_THRESH2 = 15.0 - self._PHONE_MAX_OFFSET = 0.06 - self._PHONE_MIN_OFFSET = 0.025 - self._PHONE_DATA_AVG = 0.05 - self._PHONE_DATA_VAR = 3*0.005 - self._PHONE_MAX_COUNT = int(360 / self._DT_DMON) + self._PHONE_THRESH = 0.5 self._POSE_PITCH_THRESHOLD = 0.3133 self._POSE_PITCH_THRESHOLD_SLACK = 0.3237 @@ -152,11 +145,10 @@ class DriverMonitoring: # init driver status wheelpos_filter_raw_priors = (self.settings._WHEELPOS_DATA_AVG, self.settings._WHEELPOS_DATA_VAR, 2) - phone_filter_raw_priors = (self.settings._PHONE_DATA_AVG, self.settings._PHONE_DATA_VAR, 2) self.wheelpos = DriverProb(raw_priors=wheelpos_filter_raw_priors, max_trackable=self.settings._WHEELPOS_MAX_COUNT) - self.phone = DriverProb(raw_priors=phone_filter_raw_priors, max_trackable=self.settings._PHONE_MAX_COUNT) self.pose = DriverPose(settings=self.settings) self.blink = DriverBlink() + self.phone_prob = 0. self.always_on = always_on self.distracted_types = [] @@ -257,12 +249,7 @@ class DriverMonitoring: if (self.blink.left + self.blink.right)*0.5 > self.settings._BLINK_THRESHOLD: distracted_types.append(DistractedType.DISTRACTED_BLINK) - if self.phone.prob_calibrated: - using_phone = self.phone.prob > max(min(self.phone.prob_offseter.filtered_stat.M, self.settings._PHONE_MAX_OFFSET), self.settings._PHONE_MIN_OFFSET) \ - * self.settings._PHONE_THRESH2 - else: - using_phone = self.phone.prob > self.settings._PHONE_THRESH - if using_phone: + if self.phone_prob > self.settings._PHONE_THRESH: distracted_types.append(DistractedType.DISTRACTED_PHONE) return distracted_types @@ -301,7 +288,7 @@ class DriverMonitoring: * (driver_data.sunglassesProb < self.settings._SG_THRESHOLD) self.blink.right = driver_data.rightBlinkProb * (driver_data.rightEyeProb > self.settings._EYE_THRESHOLD) \ * (driver_data.sunglassesProb < self.settings._SG_THRESHOLD) - self.phone.prob = driver_data.phoneProb + self.phone_prob = driver_data.phoneProb self.distracted_types = self._get_distracted_types() self.driver_distracted = (DistractedType.DISTRACTED_PHONE in self.distracted_types @@ -315,11 +302,9 @@ class DriverMonitoring: if self.face_detected and car_speed > self.settings._POSE_CALIB_MIN_SPEED and self.pose.low_std and (not op_engaged or not self.driver_distracted): self.pose.pitch_offseter.push_and_update(self.pose.pitch) self.pose.yaw_offseter.push_and_update(self.pose.yaw) - self.phone.prob_offseter.push_and_update(self.phone.prob) self.pose.calibrated = self.pose.pitch_offseter.filtered_stat.n > self.settings._POSE_OFFSET_MIN_COUNT and \ self.pose.yaw_offseter.filtered_stat.n > self.settings._POSE_OFFSET_MIN_COUNT - self.phone.prob_calibrated = self.phone.prob_offseter.filtered_stat.n > self.settings._POSE_OFFSET_MIN_COUNT if self.face_detected and not self.driver_distracted: if model_std_max > self.settings._DCAM_UNCERTAIN_ALERT_THRESHOLD: @@ -425,8 +410,6 @@ class DriverMonitoring: "posePitchValidCount": self.pose.pitch_offseter.filtered_stat.n, "poseYawOffset": self.pose.yaw_offseter.filtered_stat.mean(), "poseYawValidCount": self.pose.yaw_offseter.filtered_stat.n, - "phoneProbOffset": self.phone.prob_offseter.filtered_stat.mean(), - "phoneProbValidCount": self.phone.prob_offseter.filtered_stat.n, "stepChange": self.step_change, "awarenessActive": self.awareness_active, "awarenessPassive": self.awareness_passive, From ba6e5f125dfa2f1e3fa3cd2a57be77de989fe3d6 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 23 Jan 2026 00:24:15 -0800 Subject: [PATCH 759/910] Fix bridge w/ ZMQ (#37018) * fix * can also do this * 1 less +lines but more diff - Revert "can also do this" This reverts commit 8e18218099af6d3bc852d8ef0069b80d9322d6ca. --- cereal/messaging/bridge.cc | 3 ++- cereal/messaging/msgq_to_zmq.cc | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/cereal/messaging/bridge.cc b/cereal/messaging/bridge.cc index 69ecd188e..fb92c575c 100644 --- a/cereal/messaging/bridge.cc +++ b/cereal/messaging/bridge.cc @@ -33,7 +33,8 @@ void zmq_to_msgq(const std::vector &endpoints, const std::string &i for (auto endpoint : endpoints) { auto pub_sock = new MSGQPubSocket(); auto sub_sock = new ZMQSubSocket(); - pub_sock->connect(pub_context.get(), endpoint); + size_t queue_size = services.at(endpoint).queue_size; + pub_sock->connect(pub_context.get(), endpoint, true, queue_size); sub_sock->connect(sub_context.get(), endpoint, ip, false); poller->registerSocket(sub_sock); diff --git a/cereal/messaging/msgq_to_zmq.cc b/cereal/messaging/msgq_to_zmq.cc index ce626f2aa..7f8c738d4 100644 --- a/cereal/messaging/msgq_to_zmq.cc +++ b/cereal/messaging/msgq_to_zmq.cc @@ -2,6 +2,7 @@ #include +#include "cereal/services.h" #include "common/util.h" extern ExitHandler do_exit; @@ -108,7 +109,8 @@ void MsgqToZmq::zmqMonitorThread() { if (++pair.connected_clients == 1) { // Create new MSGQ subscriber socket and map to ZMQ publisher pair.sub_sock = std::make_unique(); - pair.sub_sock->connect(msgq_context.get(), pair.endpoint, "127.0.0.1"); + size_t queue_size = services.at(pair.endpoint).queue_size; + pair.sub_sock->connect(msgq_context.get(), pair.endpoint, "127.0.0.1", false, true, queue_size); sub2pub[pair.sub_sock.get()] = pair.pub_sock.get(); registerSockets(); } From 3715fe85aa76506e3be33505ef5dab64979d24e8 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 23 Jan 2026 00:55:12 -0800 Subject: [PATCH 760/910] bump opendbc (#37019) --- opendbc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc_repo b/opendbc_repo index 796ece26a..1908668b0 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 796ece26acd8b9255810ca71941ed72626589ee7 +Subproject commit 1908668b05691564ea5fc80bc11b784a9dee0714 From 12220ec82dc50cd12e9df57e6c350ab0d774443e Mon Sep 17 00:00:00 2001 From: Matt Purnell <65473602+mpurnell1@users.noreply.github.com> Date: Fri, 23 Jan 2026 19:11:23 -0600 Subject: [PATCH 761/910] cereal: update msgq imports (#36833) Update outdated reference Co-authored-by: Adeeb Shihadeh --- cereal/messaging/__init__.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/cereal/messaging/__init__.py b/cereal/messaging/__init__.py index d5033cd63..2c925b4cc 100644 --- a/cereal/messaging/__init__.py +++ b/cereal/messaging/__init__.py @@ -1,10 +1,8 @@ # must be built with scons -from msgq.ipc_pyx import Context, Poller, SubSocket, PubSocket, SocketEventHandle, toggle_fake_events, \ - set_fake_prefix, get_fake_prefix, delete_fake_prefix, wait_for_one_event -from msgq.ipc_pyx import MultiplePublishersError, IpcError -from msgq import fake_event_handle, drain_sock_raw +from msgq import fake_event_handle, drain_sock_raw, MultiplePublishersError, IpcError, \ + Context, Poller, SubSocket, PubSocket, SocketEventHandle, toggle_fake_events, \ + set_fake_prefix, get_fake_prefix, delete_fake_prefix, wait_for_one_event import msgq - import os import capnp import time From a0a5c9b9ca19833e7fac3b73c3c25899aa11e49b Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 23 Jan 2026 22:49:10 -0500 Subject: [PATCH 762/910] ui: add `set_title` and improve state updates in `ListViewSP` (#1656) --- system/ui/sunnypilot/widgets/list_view.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/system/ui/sunnypilot/widgets/list_view.py b/system/ui/sunnypilot/widgets/list_view.py index 1241f8eda..39e88169d 100644 --- a/system/ui/sunnypilot/widgets/list_view.py +++ b/system/ui/sunnypilot/widgets/list_view.py @@ -192,6 +192,9 @@ class ListItemSP(ListItem): self._right_value_font = gui_app.font(FontWeight.NORMAL) self._right_value_color: rl.Color = style.ITEM_TEXT_VALUE_COLOR + def set_title(self, title: str | Callable[[], str] = ""): + self._title = title + def set_right_value(self, value: str | Callable[[], str], color: rl.Color = style.ITEM_TEXT_VALUE_COLOR): self._right_value_source = value self._right_value_color = color @@ -202,6 +205,13 @@ class ListItemSP(ListItem): return "" return str(_resolve_value(self._right_value_source, "")) + def _update_state(self): + prev_desc = self._prev_description + super()._update_state() + if self.description_visible and self._prev_description != prev_desc: + content_width = int(self._rect.width - style.ITEM_PADDING * 2) + self._rect.height = self.get_item_height(self._font, content_width) + def get_item_height(self, font: rl.Font, max_width: int) -> float: height = super().get_item_height(font, max_width) From 2e788ae54dc2758215ffd725fe553316bab7d67a Mon Sep 17 00:00:00 2001 From: Nayan Date: Fri, 23 Jan 2026 22:55:50 -0500 Subject: [PATCH 763/910] [TIZI/TICI] ui: Steering panel (#1540) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * commaai/openpilot:d05cb31e2e916fba41ba8167030945f427fd811b * bump opendbc * bump opendbc * bump opendbc * bump opendbc * bump opendbc * sunnypilot: remove Qt * cabana: revert to stock Qt * commaai/openpilot:5198b1b079c37742c1050f02ce0aa6dd42b038b9 * commaai/openpilot:954b567b9ba0f3d1ae57d6aa7797fa86dd92ec6e * commaai/openpilot:7534b2a160faa683412c04c1254440e338931c5e * sum more * bump opendbc * not yet * should've been symlink'ed * raylib says wut * quiet mode back * more fixes * no more * too extra red diff on the side * need to bring this back * too extra * let's update docs here * Revert "let's update docs here" This reverts commit 51fe03cd5121e6fdf14657b2c33852c34922b851. * param to control stock vs sp ui * init styles * SP Toggles * Lint * optimizations * multi-button * Lint * param to control stock vs sp ui * init styles * SP Toggles * Lint * optimizations * Panels. With Icons. And Scroller. * patience, grasshopper * more patience, grasshopper * sp raylib preview * fix callback * fix ui preview * add ui previews * Option Control * Need this * sunnylink state * introducing ui_state_sp for py * poll from ui_state_sp * cloudlog & ruff * param to control stock vs sp ui * better * sponsor & pairing qr * init panel elements * backup & restore * fruit loops * update * enable, disable, enable, disable * handle layout updates * not needed * add ui_update callback * change it up * better padding * this * support for next line multi-button * uhh * disabled colors * better * listitem -> listitemsp * listitem -> listitemsp * Revert "add ui_update callback" This reverts commit 4da32cc0097434aab0aa6a3c35465eabb23c8958. * add show_description method * remove padding from line separator. like, WHY? 😩😩 * simplify * I. SAID. SIMPLIFY. * AAARGGGGGG..... * option control value fix * left button * more init * simple_button, yay * simple_button, yay * more more init * scroller -> scroller_tici * scroller -> scroller_tici * ui: `GuiApplicationExt` * simple button * simple button * add to readme * use gui_app.sunnypilot_ui() * use gui_app.sunnypilot_ui() * use gui_app.sunnypilot_ui() * optimizations * change order * subpanels * lane change timer * update toggles * update toggles * add cp_sp to ui_state * mads * add cp_sp to ui_state_sp * fix ui crash * update params * ui_state changes * descriptions * Update scroller.py * wrong pr * listen nessa, yes nessa * i've got something to confessa * some bs * # Conflicts: # selfdrive/ui/sunnypilot/layouts/settings/steering.py # selfdrive/ui/sunnypilot/layouts/settings/vehicle.py # system/ui/sunnypilot/widgets/list_view.py * sine * more * Delete selfdrive/ui/sunnypilot/layouts/vehicle_settings/platform_selector.py * Update styles.py * allow sunnylink * nah * more * sync * lint * revert * button is always shown, just disabled if off * revert * Fix SimpleButtonActionSP not respecting enabled state * some changes * new pr * some more * ui: `ButtonSP` * slight cleanup * fixes * no * fix * much better * ui: `LineSeparatorSP` * ui: add `inline` to `option_item_sp` * small cleanup mads * lane change * allow changing title * torque settings init * lont * more logic * import directly * more --------- Co-authored-by: Jason Wen Co-authored-by: DevTekVE Co-authored-by: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Co-authored-by: discountchubbs --- .../sunnypilot/layouts/settings/steering.py | 130 ++++++++++++++++- .../lane_change_settings.py | 81 +++++++++++ .../steering_sub_layouts/mads_settings.py | 135 ++++++++++++++++++ .../steering_sub_layouts/torque_settings.py | 115 +++++++++++++++ 4 files changed, 456 insertions(+), 5 deletions(-) create mode 100644 selfdrive/ui/sunnypilot/layouts/settings/steering_sub_layouts/lane_change_settings.py create mode 100644 selfdrive/ui/sunnypilot/layouts/settings/steering_sub_layouts/mads_settings.py create mode 100644 selfdrive/ui/sunnypilot/layouts/settings/steering_sub_layouts/torque_settings.py diff --git a/selfdrive/ui/sunnypilot/layouts/settings/steering.py b/selfdrive/ui/sunnypilot/layouts/settings/steering.py index ac67eb916..f82c4097c 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/steering.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/steering.py @@ -4,27 +4,147 @@ Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. This file is part of sunnypilot and is licensed under the MIT License. See the LICENSE.md file in the root directory for more details. """ -from openpilot.common.params import Params +from cereal import car +from enum import IntEnum + +from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.system.ui.lib.multilang import tr +from openpilot.system.ui.sunnypilot.widgets.list_view import toggle_item_sp, simple_button_item_sp, option_item_sp, LineSeparatorSP from openpilot.system.ui.widgets.scroller_tici import Scroller from openpilot.system.ui.widgets import Widget +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.steering_sub_layouts.lane_change_settings import LaneChangeSettingsLayout +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.steering_sub_layouts.mads_settings import MadsSettingsLayout +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.steering_sub_layouts.torque_settings import TorqueSettingsLayout + + +class PanelType(IntEnum): + STEERING = 0 + MADS = 1 + LANE_CHANGE = 2 + TORQUE_CONTROL = 3 class SteeringLayout(Widget): def __init__(self): super().__init__() - self._params = Params() + self._current_panel = PanelType.STEERING + self._lane_change_settings_layout = LaneChangeSettingsLayout(lambda: self._set_current_panel(PanelType.STEERING)) + self._mads_settings_layout = MadsSettingsLayout(lambda: self._set_current_panel(PanelType.STEERING)) + self._torque_control_layout = TorqueSettingsLayout(lambda: self._set_current_panel(PanelType.STEERING)) + items = self._initialize_items() - self._scroller = Scroller(items, line_separator=True, spacing=0) + self._scroller = Scroller(items, line_separator=False, spacing=0) def _initialize_items(self): - items = [ + self._mads_base_desc = tr("Enable the beloved MADS feature. " + + "Disable toggle to revert back to stock sunnypilot engagement/disengagement.") + self._mads_limited_desc = tr("This platform supports limited MADS settings.") + self._mads_full_desc = tr("This platform supports all MADS settings.") + self._mads_check_compat_desc = tr("Start the vehicle to check vehicle compatibility.") + self._mads_toggle = toggle_item_sp( + param="Mads", + title=lambda: tr("Modular Assistive Driving System (MADS)"), + description=self._mads_base_desc, + ) + self._mads_settings_button = simple_button_item_sp( + button_text=lambda: tr("Customize MADS"), + button_width=800, + callback=lambda: self._set_current_panel(PanelType.MADS) + ) + self._lane_change_settings_button = simple_button_item_sp( + button_text=lambda: tr("Customize Lane Change"), + button_width=800, + callback=lambda: self._set_current_panel(PanelType.LANE_CHANGE) + ) + self._blinker_control_toggle = toggle_item_sp( + param="BlinkerPauseLateralControl", + description=lambda: tr("Pause lateral control with blinker when traveling below the desired speed selected."), + title=lambda: tr("Pause Lateral Control with Blinker"), + ) + self._blinker_control_options = option_item_sp( + param="BlinkerMinLateralControlSpeed", + title=lambda: tr("Minimum Speed to Pause Lateral Control"), + min_value=0, + max_value=255, + value_change_step=5, + description="", + label_callback=lambda speed: f'{speed} {"km/h" if ui_state.is_metric else "mph"}', + ) + self._torque_control_toggle = toggle_item_sp( + param="EnforceTorqueControl", + title=lambda: tr("Enforce Torque Lateral Control"), + description=lambda: tr("Enable this to enforce sunnypilot to steer with Torque lateral control."), + ) + self._torque_customization_button = simple_button_item_sp( + button_text=lambda: tr("Customize Torque Params"), + button_width=850, + callback=lambda: self._set_current_panel(PanelType.TORQUE_CONTROL) + ) + self._nnlc_toggle = toggle_item_sp( + param="NeuralNetworkLateralControl", + title=lambda: tr("Neural Network Lateral Control (NNLC)"), + description="" + ) + + items = [ + self._mads_toggle, + self._mads_settings_button, + LineSeparatorSP(40), + self._lane_change_settings_button, + LineSeparatorSP(40), + self._blinker_control_toggle, + self._blinker_control_options, + LineSeparatorSP(40), + self._torque_control_toggle, + self._torque_customization_button, + LineSeparatorSP(40), + self._nnlc_toggle, ] return items + def _set_current_panel(self, panel: PanelType): + self._current_panel = panel + + def _update_state(self): + super()._update_state() + + torque_allowed = True + if ui_state.CP is not None: + mads_main_desc = self._mads_limited_desc if self._mads_settings_layout._mads_limited_settings() else self._mads_full_desc + self._mads_toggle.set_description(f"{mads_main_desc}

{self._mads_base_desc}") + + if ui_state.CP.steerControlType == car.CarParams.SteerControlType.angle: + ui_state.params.remove("EnforceTorqueControl") + ui_state.params.remove("NeuralNetworkLateralControl") + torque_allowed = False + else: + self._mads_toggle.set_description(f"{self._mads_check_compat_desc}

{self._mads_base_desc}") + ui_state.params.remove("EnforceTorqueControl") + ui_state.params.remove("NeuralNetworkLateralControl") + torque_allowed = False + + self._mads_toggle.action_item.set_enabled(ui_state.is_offroad()) + self._mads_settings_button.action_item.set_enabled(ui_state.is_offroad() and self._mads_toggle.action_item.get_state()) + self._blinker_control_options.set_visible(self._blinker_control_toggle.action_item.get_state()) + + enforce_torque_enabled = self._torque_control_toggle.action_item.get_state() + nnlc_enabled = self._nnlc_toggle.action_item.get_state() + self._nnlc_toggle.action_item.set_enabled(ui_state.is_offroad() and torque_allowed and not enforce_torque_enabled) + self._torque_control_toggle.action_item.set_enabled(ui_state.is_offroad() and torque_allowed and not nnlc_enabled) + self._torque_customization_button.action_item.set_enabled(self._torque_control_toggle.action_item.get_state()) + def _render(self, rect): - self._scroller.render(rect) + if self._current_panel == PanelType.LANE_CHANGE: + self._lane_change_settings_layout.render(rect) + elif self._current_panel == PanelType.MADS: + self._mads_settings_layout.render(rect) + elif self._current_panel == PanelType.TORQUE_CONTROL: + self._torque_control_layout.render(rect) + else: + self._scroller.render(rect) def show_event(self): + self._set_current_panel(PanelType.STEERING) self._scroller.show_event() diff --git a/selfdrive/ui/sunnypilot/layouts/settings/steering_sub_layouts/lane_change_settings.py b/selfdrive/ui/sunnypilot/layouts/settings/steering_sub_layouts/lane_change_settings.py new file mode 100644 index 000000000..9a5d5202a --- /dev/null +++ b/selfdrive/ui/sunnypilot/layouts/settings/steering_sub_layouts/lane_change_settings.py @@ -0,0 +1,81 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +from collections.abc import Callable +import pyray as rl + +from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.system.ui.lib.multilang import tr +from openpilot.system.ui.sunnypilot.widgets.list_view import toggle_item_sp, option_item_sp, LineSeparatorSP +from openpilot.system.ui.widgets.network import NavButton +from openpilot.system.ui.widgets.scroller_tici import Scroller +from openpilot.system.ui.widgets import Widget + +from openpilot.sunnypilot.selfdrive.controls.lib.auto_lane_change import AutoLaneChangeMode + + +class LaneChangeSettingsLayout(Widget): + def __init__(self, back_btn_callback: Callable): + super().__init__() + self._back_button = NavButton(tr("Back")) + self._back_button.set_click_callback(back_btn_callback) + + items = self._initialize_items() + self._scroller = Scroller(items, line_separator=False, spacing=0) + + def _initialize_items(self): + self._lane_change_timer = option_item_sp( + title=lambda: tr("Auto Lane Change by Blinker"), + param="AutoLaneChangeTimer", + description=lambda: tr("Set a timer to delay the auto lane change operation when the blinker is used. " + + "No nudge on the steering wheel is required to auto lane change if a timer is set. Default is Nudge.
" + + "Please use caution when using this feature. Only use the blinker when traffic and road conditions permit."), + min_value=-1, + max_value=5, + value_change_step=1, + label_callback=(lambda x: + tr("Off") if x == -1 else + tr("Nudge") if x == 0 else + tr("Nudgeless") if x == 1 else + f"0.5 {tr('s')}" if x == 2 else + f"1 {tr('s')}" if x == 3 else + f"2 {tr('s')}" if x == 4 else + f"3 {tr('s')}") + ) + self._bsm_delay = toggle_item_sp( + param="AutoLaneChangeBsmDelay", + title=lambda: tr("Auto Lane Change: Delay with Blind Spot"), + description=lambda: tr("Toggle to enable a delay timer for seamless lane changes when blind spot monitoring " + + "(BSM) detects a obstructing vehicle, ensuring safe maneuvering."), + ) + + items = [ + self._lane_change_timer, + LineSeparatorSP(40), + self._bsm_delay, + ] + + return items + + def _update_state(self): + super()._update_state() + self._update_toggles() + + def _render(self, rect): + self._back_button.set_position(self._rect.x, self._rect.y + 20) + self._back_button.render() + # subtract button + content_rect = rl.Rectangle(rect.x, rect.y + self._back_button.rect.height + 40, rect.width, rect.height - self._back_button.rect.height - 40) + self._scroller.render(content_rect) + + def show_event(self): + self._scroller.show_event() + + def _update_toggles(self): + enable_bsm = ui_state.CP and ui_state.CP.enableBsm + if not enable_bsm and ui_state.params.get_bool("AutoLaneChangeBsmDelay"): + ui_state.params.remove("AutoLaneChangeBsmDelay") + self._bsm_delay.action_item.set_enabled(enable_bsm and ui_state.params.get("AutoLaneChangeTimer", return_default=True) > AutoLaneChangeMode.NUDGE) diff --git a/selfdrive/ui/sunnypilot/layouts/settings/steering_sub_layouts/mads_settings.py b/selfdrive/ui/sunnypilot/layouts/settings/steering_sub_layouts/mads_settings.py new file mode 100644 index 000000000..d3f38e04d --- /dev/null +++ b/selfdrive/ui/sunnypilot/layouts/settings/steering_sub_layouts/mads_settings.py @@ -0,0 +1,135 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +from collections.abc import Callable +import pyray as rl + +from opendbc.sunnypilot.car.tesla.values import TeslaFlagsSP +from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.system.ui.lib.multilang import tr, tr_noop +from openpilot.system.ui.widgets import Widget +from openpilot.system.ui.widgets.network import NavButton +from openpilot.system.ui.widgets.scroller_tici import Scroller +from openpilot.system.ui.sunnypilot.widgets.list_view import multiple_button_item_sp, toggle_item_sp + +MADS_STEERING_MODE_OPTIONS = [ + (tr("Remain Active"), tr_noop("Remain Active: ALC will remain active when the brake pedal is pressed.")), + (tr("Pause"), tr_noop("Pause: ALC will pause when the brake pedal is pressed.")), + (tr("Disengage"), tr_noop("Disengage: ALC will disengage when the brake pedal is pressed.")), +] + +MADS_MAIN_CRUISE_BASE_DESC = tr("Note: For vehicles without LFA/LKAS button, disabling this will prevent lateral control engagement.") +MADS_UNIFIED_ENGAGEMENT_MODE_BASE_DESC = "{engage}

{note}

".format( + engage=tr("Engage lateral and longitudinal control with cruise control engagement."), + note=tr("Note: Once lateral control is engaged via UEM, it will remain engaged until it is manually disabled via the MADS button or car shut off."), +) + +STATUS_CHECK_COMPATIBILITY = tr("Start the vehicle to check vehicle compatibility.") +DEFAULT_TO_OFF = tr("This feature defaults to OFF, and does not allow selection due to vehicle limitations.") +DEFAULT_TO_ON = tr("This feature defaults to ON, and does not allow selection due to vehicle limitations.") +STATUS_DISENGAGE_ONLY = tr("This platform only supports Disengage mode due to vehicle limitations.") + + +class MadsSettingsLayout(Widget): + def __init__(self, back_btn_callback: Callable): + super().__init__() + self._back_button = NavButton(tr("Back")) + self._back_button.set_click_callback(back_btn_callback) + self._initialize_items() + self._scroller = Scroller(self.items, line_separator=True, spacing=0) + + def _initialize_items(self): + self._main_cruise_toggle = toggle_item_sp( + title=lambda: tr("Toggle with Main Cruise"), + description=MADS_MAIN_CRUISE_BASE_DESC, + param="MadsMainCruiseAllowed", + ) + self._unified_engagement_toggle = toggle_item_sp( + title=lambda: tr("Unified Engagement Mode (UEM)"), + description=MADS_UNIFIED_ENGAGEMENT_MODE_BASE_DESC, + param="MadsUnifiedEngagementMode" + ) + self._steering_mode = multiple_button_item_sp( + param="MadsSteeringMode", + title=lambda: tr("Steering Mode on Brake Pedal"), + description="", + buttons=[opt[0] for opt in MADS_STEERING_MODE_OPTIONS], + inline=False, + button_width=350, + callback=self._update_steering_mode_description, + ) + + self.items = [ + self._main_cruise_toggle, + self._unified_engagement_toggle, + self._steering_mode, + ] + + def _update_state(self): + super()._update_state() + self._update_toggles() + + def _render(self, rect): + self._back_button.set_position(self._rect.x, self._rect.y + 20) + self._back_button.render() + # subtract button + content_rect = rl.Rectangle(rect.x, rect.y + self._back_button.rect.height + 40, rect.width, rect.height - self._back_button.rect.height - 40) + self._scroller.render(content_rect) + + def show_event(self): + self._scroller.show_event() + + @staticmethod + def _mads_limited_settings() -> bool: + brand = "" + if ui_state.is_offroad(): + bundle = ui_state.params.get("CarPlatformBundle") + if bundle: + brand = bundle.get("brand", "") + if not brand: + brand = ui_state.CP.brand if ui_state.CP else "" + + if brand == "rivian": + return True + elif brand == "tesla": + return not (ui_state.CP_SP and ui_state.CP_SP.flags & TeslaFlagsSP.HAS_VEHICLE_BUS) + return False + + def _update_steering_mode_description(self, button_index: int): + base_desc = tr("Choose how Automatic Lane Centering (ALC) behaves after the brake pedal is manually pressed in sunnypilot.") + result = base_desc + "

" + for opt in MADS_STEERING_MODE_OPTIONS: + desc = "" + opt[1] + "" if button_index == MADS_STEERING_MODE_OPTIONS.index(opt) else opt[1] + result += desc + "
" + self._steering_mode.set_description(result) + self._steering_mode.show_description(True) + + def _update_toggles(self): + self._update_steering_mode_description(self._steering_mode.action_item.get_selected_button()) + if self._mads_limited_settings(): + ui_state.params.remove("MadsMainCruiseAllowed") + ui_state.params.put_bool("MadsUnifiedEngagementMode", True) + ui_state.params.put("MadsSteeringMode", 2) + + self._main_cruise_toggle.action_item.set_enabled(False) + self._main_cruise_toggle.action_item.set_state(False) + self._main_cruise_toggle.set_description("" + DEFAULT_TO_OFF + "
" + MADS_MAIN_CRUISE_BASE_DESC) + + self._unified_engagement_toggle.action_item.set_enabled(False) + self._unified_engagement_toggle.action_item.set_state(True) + self._unified_engagement_toggle.set_description("" + DEFAULT_TO_ON + "
" + MADS_UNIFIED_ENGAGEMENT_MODE_BASE_DESC) + + self._steering_mode.action_item.set_enabled(False) + self._steering_mode.set_description(STATUS_DISENGAGE_ONLY) + self._steering_mode.action_item.set_selected_button(2) + else: + self._main_cruise_toggle.action_item.set_enabled(True) + self._main_cruise_toggle.set_description(MADS_MAIN_CRUISE_BASE_DESC) + + self._unified_engagement_toggle.action_item.set_enabled(True) + self._unified_engagement_toggle.set_description(MADS_UNIFIED_ENGAGEMENT_MODE_BASE_DESC) + + self._steering_mode.action_item.set_enabled(True) diff --git a/selfdrive/ui/sunnypilot/layouts/settings/steering_sub_layouts/torque_settings.py b/selfdrive/ui/sunnypilot/layouts/settings/steering_sub_layouts/torque_settings.py new file mode 100644 index 000000000..dceee8b51 --- /dev/null +++ b/selfdrive/ui/sunnypilot/layouts/settings/steering_sub_layouts/torque_settings.py @@ -0,0 +1,115 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +from collections.abc import Callable +import pyray as rl + +from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.system.ui.lib.multilang import tr +from openpilot.system.ui.sunnypilot.widgets.list_view import toggle_item_sp, option_item_sp +from openpilot.system.ui.widgets.network import NavButton +from openpilot.system.ui.widgets.scroller_tici import Scroller +from openpilot.system.ui.widgets import Widget + + +class TorqueSettingsLayout(Widget): + def __init__(self, back_btn_callback: Callable): + super().__init__() + self._back_button = NavButton(tr("Back")) + self._back_button.set_click_callback(back_btn_callback) + items = self._initialize_items() + self._scroller = Scroller(items, line_separator=True, spacing=0) + + def _initialize_items(self): + self._self_tune_toggle = toggle_item_sp( + param="LiveTorqueParamsToggle", + title=lambda: tr("Self-Tune"), + description=lambda: tr("Enables self-tune for Torque lateral control for platforms that do not use " + + "Torque lateral control by default."), + ) + self._relaxed_tune_toggle = toggle_item_sp( + param="LiveTorqueParamsRelaxedToggle", + title=lambda: tr("Less Restrict Settings for Self-Tune (Beta)"), + description=lambda: tr("Less strict settings when using Self-Tune. This allows torqued to be more " + + "forgiving when learning values."), + ) + self._custom_tune_toggle = toggle_item_sp( + param="CustomTorqueParams", + title=lambda: tr("Enable Custom Tuning"), + description=lambda: tr("Enables custom tuning for Torque lateral control. " + + "Modifying Lateral Acceleration Factor and Friction below will override the offline values " + + "indicated in the YAML files within \"opendbc/car/torque_data\". " + + "The values will also be used live when \"Manual Real-Time Tuning\" toggle is enabled."), + ) + self._torque_prams_override_toggle = toggle_item_sp( + param="TorqueParamsOverrideEnabled", + title=lambda: tr("Manual Real-Time Tuning"), + description=lambda: tr("Enforces the torque lateral controller to use the fixed values instead of the learned " + + "values from Self-Tune. Enabling this toggle overrides Self-Tune values."), + ) + self._torque_lat_accel_factor = option_item_sp( + title=lambda: tr("Lateral Acceleration Factor"), + param="TorqueParamsOverrideLatAccelFactor", + description="", + min_value=1, + max_value=500, + value_change_step=1, + label_callback=(lambda x: f"{x/100} m/s^2"), + use_float_scaling=True + ) + + self._torque_friction = option_item_sp( + title=lambda: tr("Friction"), + param="TorqueParamsOverrideFriction", + description="", + min_value=1, + max_value=100, + value_change_step=1, + label_callback=(lambda x: f"{x/100}"), + use_float_scaling=True + ) + + items = [ + self._self_tune_toggle, + self._relaxed_tune_toggle, + self._custom_tune_toggle, + self._torque_prams_override_toggle, + self._torque_lat_accel_factor, + self._torque_friction, + ] + return items + + def _update_state(self): + super()._update_state() + if not ui_state.params.get_bool("LiveTorqueParamsToggle"): + ui_state.params.remove("LiveTorqueParamsRelaxedToggle") + self._relaxed_tune_toggle.action_item.set_state(False) + self._self_tune_toggle.action_item.set_enabled(ui_state.is_offroad()) + self._relaxed_tune_toggle.action_item.set_enabled(ui_state.is_offroad() and self._self_tune_toggle.action_item.get_state()) + self._custom_tune_toggle.action_item.set_enabled(ui_state.is_offroad()) + custom_tune_enabled = self._custom_tune_toggle.action_item.get_state() + self._torque_prams_override_toggle.set_visible(custom_tune_enabled) + self._torque_lat_accel_factor.set_visible(custom_tune_enabled) + self._torque_friction.set_visible(custom_tune_enabled) + + self._torque_prams_override_toggle.action_item.set_enabled(ui_state.is_offroad()) + sliders_enabled = self._torque_prams_override_toggle.action_item.get_state() or ui_state.is_offroad() + self._torque_lat_accel_factor.action_item.set_enabled(sliders_enabled) + self._torque_friction.action_item.set_enabled(sliders_enabled) + + title_text = tr("Real-Time & Offline") if ui_state.params.get("TorqueParamsOverrideEnabled") else tr("Offline Only") + self._torque_lat_accel_factor.set_title(lambda: tr("Lateral Acceleration Factor") + " (" + title_text + ")") + self._torque_friction.set_title(lambda: tr("Friction") + " (" + title_text + ")") + + def _render(self, rect): + self._back_button.set_position(self._rect.x, self._rect.y + 20) + self._back_button.render() + # subtract button + content_rect = rl.Rectangle(rect.x, rect.y + self._back_button.rect.height + 40, rect.width, rect.height - self._back_button.rect.height - 40) + self._scroller.render(content_rect) + + def show_event(self): + self._scroller.show_event() From 560ed80123caa15ee9a7f81ecd57f05815ba857e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20R=C4=85czy?= Date: Sat, 24 Jan 2026 04:04:54 +0000 Subject: [PATCH 764/910] tools: seekable URLFile (#37022) * Make URLFile seekable * Return value in seek --- tools/lib/url_file.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/tools/lib/url_file.py b/tools/lib/url_file.py index 790fa7e8f..8e2f0a922 100644 --- a/tools/lib/url_file.py +++ b/tools/lib/url_file.py @@ -192,8 +192,25 @@ class URLFile: raise URLFileException(f"Expected {len(ranges)} parts, got {len(parts)} ({self._url})") return parts - def seek(self, pos: int) -> None: - self._pos = int(pos) + def seekable(self) -> bool: + return True + + def seek(self, pos: int, whence: int = 0) -> int: + pos = int(pos) + if whence == os.SEEK_SET: + self._pos = pos + elif whence == os.SEEK_CUR: + self._pos += pos + elif whence == os.SEEK_END: + length = self.get_length() + assert length != -1, "Cannot seek from end on unknown length file" + self._pos = length + pos + else: + raise URLFileException("Invalid whence value") + return self._pos + + def tell(self) -> int: + return self._pos @property def name(self) -> str: From 8c36739ebd346f3a52e89978f4dcf657841b61da Mon Sep 17 00:00:00 2001 From: Kumar <36933347+rav4kumar@users.noreply.github.com> Date: Fri, 23 Jan 2026 21:27:12 -0700 Subject: [PATCH 765/910] [TIZI/TICI] ui: Rocket Fuel (#1337) * rocket * raylib * extra --------- Co-authored-by: Jason Wen --- common/params_keys.h | 1 + .../ui/sunnypilot/onroad/hud_renderer.py | 5 +++ selfdrive/ui/sunnypilot/onroad/rocket_fuel.py | 45 +++++++++++++++++++ selfdrive/ui/sunnypilot/ui_state.py | 1 + sunnypilot/sunnylink/params_metadata.json | 4 ++ 5 files changed, 56 insertions(+) create mode 100644 selfdrive/ui/sunnypilot/onroad/rocket_fuel.py diff --git a/common/params_keys.h b/common/params_keys.h index e04211392..ecc656cc7 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -173,6 +173,7 @@ inline static std::unordered_map keys = { {"QuickBootToggle", {PERSISTENT | BACKUP, BOOL, "0"}}, {"QuietMode", {PERSISTENT | BACKUP, BOOL, "0"}}, {"RainbowMode", {PERSISTENT | BACKUP, BOOL, "0"}}, + {"RocketFuel", {PERSISTENT | BACKUP, BOOL, "0"}}, {"ShowAdvancedControls", {PERSISTENT | BACKUP, BOOL, "0"}}, {"ShowTurnSignals", {PERSISTENT | BACKUP, BOOL, "0"}}, {"StandstillTimer", {PERSISTENT | BACKUP, BOOL, "0"}}, diff --git a/selfdrive/ui/sunnypilot/onroad/hud_renderer.py b/selfdrive/ui/sunnypilot/onroad/hud_renderer.py index 33582df19..9a29a97b9 100644 --- a/selfdrive/ui/sunnypilot/onroad/hud_renderer.py +++ b/selfdrive/ui/sunnypilot/onroad/hud_renderer.py @@ -6,15 +6,20 @@ See the LICENSE.md file in the root directory for more details. """ import pyray as rl +from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.selfdrive.ui.onroad.hud_renderer import HudRenderer from openpilot.selfdrive.ui.sunnypilot.onroad.developer_ui import DeveloperUiRenderer +from openpilot.selfdrive.ui.sunnypilot.onroad.rocket_fuel import RocketFuel class HudRendererSP(HudRenderer): def __init__(self): super().__init__() self.developer_ui = DeveloperUiRenderer() + self.rocket_fuel = RocketFuel() def _render(self, rect: rl.Rectangle) -> None: super()._render(rect) self.developer_ui.render(rect) + if ui_state.rocket_fuel: + self.rocket_fuel.render(rect, ui_state.sm) diff --git a/selfdrive/ui/sunnypilot/onroad/rocket_fuel.py b/selfdrive/ui/sunnypilot/onroad/rocket_fuel.py new file mode 100644 index 000000000..af25711a9 --- /dev/null +++ b/selfdrive/ui/sunnypilot/onroad/rocket_fuel.py @@ -0,0 +1,45 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +import pyray as rl + + +class RocketFuel: + def __init__(self): + self.vc_accel = 0.0 + + def render(self, rect: rl.Rectangle, sm) -> None: + vc_accel0 = sm['carState'].aEgo + + # Smooth the acceleration + self.vc_accel = self.vc_accel + (vc_accel0 - self.vc_accel) / 5.0 + + hha = 0.0 + color = rl.Color(0, 0, 0, 0) # Transparent by default + + if self.vc_accel > 0: + hha = 0.85 - 0.1 / self.vc_accel # only extend up to 85% + color = rl.Color(0, 245, 0, 200) + elif self.vc_accel < 0: + hha = 0.85 + 0.1 / self.vc_accel # only extend up to 85% + color = rl.Color(245, 0, 0, 200) + + if hha < 0: + hha = 0.0 + + hha = hha * rect.height + wp = 28.0 + + # Draw + rect_h = rect.height + + if self.vc_accel > 0: + ra_y = rect_h / 2.0 - hha / 2.0 + else: + ra_y = rect_h / 2.0 + + if hha > 0: + rl.draw_rectangle(int(rect.x), int(rect.y + ra_y), int(wp), int(hha / 2.0), color) diff --git a/selfdrive/ui/sunnypilot/ui_state.py b/selfdrive/ui/sunnypilot/ui_state.py index 2644c157a..9ef4e1d7c 100644 --- a/selfdrive/ui/sunnypilot/ui_state.py +++ b/selfdrive/ui/sunnypilot/ui_state.py @@ -122,6 +122,7 @@ class UIStateSP: self.CP_SP = messaging.log_from_bytes(CP_SP_bytes, custom.CarParamsSP) self.sunnylink_enabled = self.params.get_bool("SunnylinkEnabled") self.developer_ui = self.params.get("DevUIInfo") + self.rocket_fuel = self.params.get_bool("RocketFuel") self.rainbow_path = self.params.get_bool("RainbowMode") self.chevron_metrics = self.params.get("ChevronInfo") self.active_bundle = self.params.get("ModelManager_ActiveBundle") diff --git a/sunnypilot/sunnylink/params_metadata.json b/sunnypilot/sunnylink/params_metadata.json index e4390586b..7f597a962 100644 --- a/sunnypilot/sunnylink/params_metadata.json +++ b/sunnypilot/sunnylink/params_metadata.json @@ -1082,6 +1082,10 @@ "title": "Display Road Name", "description": "" }, + "RocketFuel": { + "title": "Display Rocket Fuel Bar", + "description": "Show an indicator on the left side of the screen to display real-time vehicle acceleration and deceleration." + }, "RouteCount": { "title": "Route Count", "description": "" From 76d50df4665ec83e1dc6d9fc6502fbf90d5d8d94 Mon Sep 17 00:00:00 2001 From: Kumar <36933347+rav4kumar@users.noreply.github.com> Date: Fri, 23 Jan 2026 22:46:00 -0700 Subject: [PATCH 766/910] [TIZI/TICI] ui: MICI style turn signals (#1504) * mici turn signal for c3x * sp dir * decouple * more * ty * refactor and slim down * bigger --------- Co-authored-by: Jason Wen --- .../ui/sunnypilot/onroad/hud_renderer.py | 9 ++ selfdrive/ui/sunnypilot/onroad/turn_signal.py | 125 ++++++++++++++++++ 2 files changed, 134 insertions(+) create mode 100644 selfdrive/ui/sunnypilot/onroad/turn_signal.py diff --git a/selfdrive/ui/sunnypilot/onroad/hud_renderer.py b/selfdrive/ui/sunnypilot/onroad/hud_renderer.py index 9a29a97b9..0155e38a6 100644 --- a/selfdrive/ui/sunnypilot/onroad/hud_renderer.py +++ b/selfdrive/ui/sunnypilot/onroad/hud_renderer.py @@ -10,6 +10,7 @@ from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.selfdrive.ui.onroad.hud_renderer import HudRenderer from openpilot.selfdrive.ui.sunnypilot.onroad.developer_ui import DeveloperUiRenderer from openpilot.selfdrive.ui.sunnypilot.onroad.rocket_fuel import RocketFuel +from openpilot.selfdrive.ui.sunnypilot.onroad.turn_signal import TurnSignalController class HudRendererSP(HudRenderer): @@ -17,9 +18,17 @@ class HudRendererSP(HudRenderer): super().__init__() self.developer_ui = DeveloperUiRenderer() self.rocket_fuel = RocketFuel() + self.turn_signal_controller = TurnSignalController() + + def _update_state(self) -> None: + super()._update_state() + self.turn_signal_controller.update() def _render(self, rect: rl.Rectangle) -> None: super()._render(rect) self.developer_ui.render(rect) + + self.turn_signal_controller.render() + if ui_state.rocket_fuel: self.rocket_fuel.render(rect, ui_state.sm) diff --git a/selfdrive/ui/sunnypilot/onroad/turn_signal.py b/selfdrive/ui/sunnypilot/onroad/turn_signal.py new file mode 100644 index 000000000..ad14e72f5 --- /dev/null +++ b/selfdrive/ui/sunnypilot/onroad/turn_signal.py @@ -0,0 +1,125 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +import pyray as rl +import time +from dataclasses import dataclass + +from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.selfdrive.ui.mici.onroad.alert_renderer import IconSide, TURN_SIGNAL_BLINK_PERIOD +from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.widgets import Widget +from openpilot.common.filter_simple import FirstOrderFilter + + +@dataclass(frozen=True) +class TurnSignalConfig: + left_x: int = 870 + left_y: int = 220 + right_x: int = 1140 + right_y: int = 220 + size: int = 150 + + +class TurnSignalWidget(Widget): + def __init__(self, direction: IconSide): + super().__init__() + self._direction = direction + self._active = False + + self._turn_signal_timer = 0.0 + self._turn_signal_alpha_filter = FirstOrderFilter(0.0, 0.3, 1 / gui_app.target_fps) + + texture_path = f'icons_mici/onroad/turn_signal_{direction}.png' + self._texture = gui_app.texture(texture_path, 120, 109) + + def _render(self, _): + if not self._active: + return + + if time.monotonic() - self._turn_signal_timer > TURN_SIGNAL_BLINK_PERIOD: + self._turn_signal_timer = time.monotonic() + self._turn_signal_alpha_filter.x = 255 * 2 + else: + self._turn_signal_alpha_filter.update(255 * 0.2) + + icon_alpha = int(min(self._turn_signal_alpha_filter.x, 255)) + + if self._texture: + pos_x = int(self._rect.x + (self._rect.width - self._texture.width) / 2) + pos_y = int(self._rect.y + (self._rect.height - self._texture.height) / 2) + color = rl.Color(255, 255, 255, icon_alpha) + rl.draw_texture(self._texture, pos_x, pos_y, color) + + def activate(self): + if not self._active: + self._turn_signal_timer = 0.0 + self._active = True + + def deactivate(self): + self._active = False + self._turn_signal_timer = 0.0 + + +class TurnSignalController: + def __init__(self, config: TurnSignalConfig | None = None): + self._config = config or TurnSignalConfig() + self._left_signal = TurnSignalWidget(direction=IconSide.left) + self._right_signal = TurnSignalWidget(direction=IconSide.right) + self._last_icon_side = None + + def update(self): + sm = ui_state.sm + ss = sm['selfdriveState'] + + event_name = ss.alertType.split('/')[0] if ss.alertType else '' + + if event_name == 'preLaneChangeLeft': + self._last_icon_side = IconSide.left + self._left_signal.activate() + self._right_signal.deactivate() + + elif event_name == 'preLaneChangeRight': + self._last_icon_side = IconSide.right + self._right_signal.activate() + self._left_signal.deactivate() + + elif event_name == 'laneChange': + if self._last_icon_side == IconSide.left: + self._left_signal.activate() + self._right_signal.deactivate() + elif self._last_icon_side == IconSide.right: + self._right_signal.activate() + self._left_signal.deactivate() + + else: + self._last_icon_side = None + self._left_signal.deactivate() + self._right_signal.deactivate() + + def render(self): + if self._last_icon_side == IconSide.left: + self._left_signal.render(rl.Rectangle( + self._config.left_x, + self._config.left_y, + self._config.size, + self._config.size + )) + elif self._last_icon_side == IconSide.right: + self._right_signal.render(rl.Rectangle( + self._config.right_x, + self._config.right_y, + self._config.size, + self._config.size + )) + + @property + def config(self) -> TurnSignalConfig: + return self._config + + @config.setter + def config(self, new_config: TurnSignalConfig): + self._config = new_config From 1bd3255f14f9e32bca542778652e979412538972 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 24 Jan 2026 01:04:25 -0500 Subject: [PATCH 767/910] [TIZI/TICI] ui: MICI style blindspot indicators (#1657) * introduce blinker * add blind spot * bigger * nah * lint --- selfdrive/ui/sunnypilot/onroad/turn_signal.py | 71 ++++++++++++++----- 1 file changed, 54 insertions(+), 17 deletions(-) diff --git a/selfdrive/ui/sunnypilot/onroad/turn_signal.py b/selfdrive/ui/sunnypilot/onroad/turn_signal.py index ad14e72f5..4e5f1183d 100644 --- a/selfdrive/ui/sunnypilot/onroad/turn_signal.py +++ b/selfdrive/ui/sunnypilot/onroad/turn_signal.py @@ -29,24 +29,30 @@ class TurnSignalWidget(Widget): super().__init__() self._direction = direction self._active = False + self._type = 'signal' self._turn_signal_timer = 0.0 self._turn_signal_alpha_filter = FirstOrderFilter(0.0, 0.3, 1 / gui_app.target_fps) - texture_path = f'icons_mici/onroad/turn_signal_{direction}.png' - self._texture = gui_app.texture(texture_path, 120, 109) + self._signal_texture = gui_app.texture(f'icons_mici/onroad/turn_signal_{direction}.png', 120, 109) + self._blind_spot_texture = gui_app.texture(f'icons_mici/onroad/blind_spot_{direction}.png', 120, 109) + self._texture = self._signal_texture def _render(self, _): if not self._active: return - if time.monotonic() - self._turn_signal_timer > TURN_SIGNAL_BLINK_PERIOD: - self._turn_signal_timer = time.monotonic() - self._turn_signal_alpha_filter.x = 255 * 2 + if self._type == 'signal': + if time.monotonic() - self._turn_signal_timer > TURN_SIGNAL_BLINK_PERIOD: + self._turn_signal_timer = time.monotonic() + self._turn_signal_alpha_filter.x = 255 * 2 + else: + self._turn_signal_alpha_filter.update(255 * 0.2) + icon_alpha = int(min(self._turn_signal_alpha_filter.x, 255)) else: - self._turn_signal_alpha_filter.update(255 * 0.2) + icon_alpha = 255 - icon_alpha = int(min(self._turn_signal_alpha_filter.x, 255)) + self._texture = self._blind_spot_texture if self._type == 'blind_spot' else self._signal_texture if self._texture: pos_x = int(self._rect.x + (self._rect.width - self._texture.width) / 2) @@ -54,10 +60,11 @@ class TurnSignalWidget(Widget): color = rl.Color(255, 255, 255, icon_alpha) rl.draw_texture(self._texture, pos_x, pos_y, color) - def activate(self): - if not self._active: + def activate(self, _type: str = 'signal'): + if not self._active or self._type != _type: self._turn_signal_timer = 0.0 self._active = True + self._type = _type def deactivate(self): self._active = False @@ -79,36 +86,66 @@ class TurnSignalController: if event_name == 'preLaneChangeLeft': self._last_icon_side = IconSide.left - self._left_signal.activate() + self._left_signal.activate('signal') self._right_signal.deactivate() elif event_name == 'preLaneChangeRight': self._last_icon_side = IconSide.right - self._right_signal.activate() + self._right_signal.activate('signal') self._left_signal.deactivate() elif event_name == 'laneChange': if self._last_icon_side == IconSide.left: - self._left_signal.activate() + self._left_signal.activate('signal') self._right_signal.deactivate() elif self._last_icon_side == IconSide.right: - self._right_signal.activate() + self._right_signal.activate('signal') + self._left_signal.deactivate() + + elif event_name == 'laneChangeBlocked': + CS = sm['carState'] + if CS.leftBlinker: + icon_side = IconSide.left + elif CS.rightBlinker: + icon_side = IconSide.right + else: + icon_side = self._last_icon_side + + if icon_side == IconSide.left: + self._left_signal.activate('blind_spot') + self._right_signal.deactivate() + elif icon_side == IconSide.right: + self._right_signal.activate('blind_spot') self._left_signal.deactivate() else: self._last_icon_side = None - self._left_signal.deactivate() - self._right_signal.deactivate() + CS = sm['carState'] + + if CS.leftBlindspot: + self._left_signal.activate('blind_spot') + elif CS.leftBlinker: + self._left_signal.activate('signal') + else: + self._left_signal.deactivate() + + if CS.rightBlindspot: + self._right_signal.activate('blind_spot') + elif CS.rightBlinker: + self._right_signal.activate('signal') + else: + self._right_signal.deactivate() def render(self): - if self._last_icon_side == IconSide.left: + if self._left_signal._active: self._left_signal.render(rl.Rectangle( self._config.left_x, self._config.left_y, self._config.size, self._config.size )) - elif self._last_icon_side == IconSide.right: + + if self._right_signal._active: self._right_signal.render(rl.Rectangle( self._config.right_x, self._config.right_y, From d7770ad55ca67fc15f766e75b66e7e3946d8595e Mon Sep 17 00:00:00 2001 From: Kumar <36933347+rav4kumar@users.noreply.github.com> Date: Fri, 23 Jan 2026 23:36:47 -0700 Subject: [PATCH 768/910] [MICI] ui: display blindspot indicators when available (#1525) * always bsm * c4 bsm for c3x * position * sperate * sp dir * revert * decouple * final --------- Co-authored-by: Jason Wen --- .../ui/mici/onroad/augmented_road_view.py | 1 + selfdrive/ui/mici/onroad/hud_renderer.py | 4 +- .../ui/sunnypilot/mici/onroad/hud_renderer.py | 27 ++++++++++ .../onroad/blind_spot_indicators.py | 49 +++++++++++++++++++ 4 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 selfdrive/ui/sunnypilot/mici/onroad/hud_renderer.py create mode 100644 selfdrive/ui/sunnypilot/onroad/blind_spot_indicators.py diff --git a/selfdrive/ui/mici/onroad/augmented_road_view.py b/selfdrive/ui/mici/onroad/augmented_road_view.py index 0546920e5..316052fdc 100644 --- a/selfdrive/ui/mici/onroad/augmented_road_view.py +++ b/selfdrive/ui/mici/onroad/augmented_road_view.py @@ -20,6 +20,7 @@ from openpilot.common.transformations.orientation import rot_from_euler from enum import IntEnum if gui_app.sunnypilot_ui(): + from openpilot.selfdrive.ui.sunnypilot.mici.onroad.hud_renderer import HudRendererSP as HudRenderer from openpilot.selfdrive.ui.sunnypilot.ui_state import OnroadTimerStatus OpState = log.SelfdriveState.OpenpilotState diff --git a/selfdrive/ui/mici/onroad/hud_renderer.py b/selfdrive/ui/mici/onroad/hud_renderer.py index 7f489ccf9..44ce12437 100644 --- a/selfdrive/ui/mici/onroad/hud_renderer.py +++ b/selfdrive/ui/mici/onroad/hud_renderer.py @@ -183,11 +183,13 @@ class HudRenderer(Widget): def _draw_steering_wheel(self, rect: rl.Rectangle) -> None: wheel_txt = self._txt_wheel_critical if self._show_wheel_critical else self._txt_wheel + bsm_detected = self._has_blind_spot_detected() if gui_app.sunnypilot_ui() else False + if self._show_wheel_critical: self._wheel_alpha_filter.update(255) self._wheel_y_filter.update(0) else: - if ui_state.status == UIStatus.DISENGAGED: + if ui_state.status == UIStatus.DISENGAGED or bsm_detected: self._wheel_alpha_filter.update(0) self._wheel_y_filter.update(wheel_txt.height / 2) else: diff --git a/selfdrive/ui/sunnypilot/mici/onroad/hud_renderer.py b/selfdrive/ui/sunnypilot/mici/onroad/hud_renderer.py new file mode 100644 index 000000000..de52bb462 --- /dev/null +++ b/selfdrive/ui/sunnypilot/mici/onroad/hud_renderer.py @@ -0,0 +1,27 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +import pyray as rl + +from openpilot.selfdrive.ui.mici.onroad.hud_renderer import HudRenderer +from openpilot.selfdrive.ui.sunnypilot.onroad.blind_spot_indicators import BlindSpotIndicators + + +class HudRendererSP(HudRenderer): + def __init__(self): + super().__init__() + self.blind_spot_indicators = BlindSpotIndicators() + + def _update_state(self) -> None: + super()._update_state() + self.blind_spot_indicators.update() + + def _render(self, rect: rl.Rectangle) -> None: + super()._render(rect) + self.blind_spot_indicators.render(rect) + + def _has_blind_spot_detected(self) -> bool: + return self.blind_spot_indicators.detected diff --git a/selfdrive/ui/sunnypilot/onroad/blind_spot_indicators.py b/selfdrive/ui/sunnypilot/onroad/blind_spot_indicators.py new file mode 100644 index 000000000..1087579fe --- /dev/null +++ b/selfdrive/ui/sunnypilot/onroad/blind_spot_indicators.py @@ -0,0 +1,49 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +import pyray as rl + +from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.system.ui.lib.application import gui_app +from openpilot.common.filter_simple import FirstOrderFilter + + +class BlindSpotIndicators: + def __init__(self): + self._txt_blind_spot_left: rl.Texture = gui_app.texture('icons_mici/onroad/blind_spot_left.png', 108, 128) + self._txt_blind_spot_right: rl.Texture = gui_app.texture('icons_mici/onroad/blind_spot_right.png', 108, 128) + + self._blind_spot_left_alpha_filter = FirstOrderFilter(0, 0.15, 1 / gui_app.target_fps) + self._blind_spot_right_alpha_filter = FirstOrderFilter(0, 0.15, 1 / gui_app.target_fps) + + def update(self) -> None: + sm = ui_state.sm + CS = sm['carState'] + + self._blind_spot_left_alpha_filter.update(1.0 if CS.leftBlindspot else 0.0) + self._blind_spot_right_alpha_filter.update(1.0 if CS.rightBlindspot else 0.0) + + @property + def detected(self) -> bool: + return self._blind_spot_left_alpha_filter.x > 0.01 or self._blind_spot_right_alpha_filter.x > 0.01 + + def render(self, rect: rl.Rectangle) -> None: + BLIND_SPOT_MARGIN_X = 20 # Distance from edge of screen + BLIND_SPOT_Y_OFFSET = 100 # Distance from top of screen + + if self._blind_spot_left_alpha_filter.x > 0.01: + pos_x = int(rect.x + BLIND_SPOT_MARGIN_X) + pos_y = int(rect.y + BLIND_SPOT_Y_OFFSET) + alpha = int(255 * self._blind_spot_left_alpha_filter.x) + color = rl.Color(255, 255, 255, alpha) + rl.draw_texture(self._txt_blind_spot_left, pos_x, pos_y, color) + + if self._blind_spot_right_alpha_filter.x > 0.01: + pos_x = int(rect.x + rect.width - BLIND_SPOT_MARGIN_X - self._txt_blind_spot_right.width) + pos_y = int(rect.y + BLIND_SPOT_Y_OFFSET) + alpha = int(255 * self._blind_spot_right_alpha_filter.x) + color = rl.Color(255, 255, 255, alpha) + rl.draw_texture(self._txt_blind_spot_right, pos_x, pos_y, color) From 5c01365125c1b1fff9239a8cde5f9482e3265022 Mon Sep 17 00:00:00 2001 From: Kumar <36933347+rav4kumar@users.noreply.github.com> Date: Sat, 24 Jan 2026 00:04:42 -0700 Subject: [PATCH 769/910] [TIZI/TICI] ui: Road Name (#1654) * road name * decouple * rename --------- Co-authored-by: Jason Wen --- .../ui/sunnypilot/onroad/hud_renderer.py | 4 ++ selfdrive/ui/sunnypilot/onroad/road_name.py | 56 +++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 selfdrive/ui/sunnypilot/onroad/road_name.py diff --git a/selfdrive/ui/sunnypilot/onroad/hud_renderer.py b/selfdrive/ui/sunnypilot/onroad/hud_renderer.py index 0155e38a6..933b573bf 100644 --- a/selfdrive/ui/sunnypilot/onroad/hud_renderer.py +++ b/selfdrive/ui/sunnypilot/onroad/hud_renderer.py @@ -9,6 +9,7 @@ import pyray as rl from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.selfdrive.ui.onroad.hud_renderer import HudRenderer from openpilot.selfdrive.ui.sunnypilot.onroad.developer_ui import DeveloperUiRenderer +from openpilot.selfdrive.ui.sunnypilot.onroad.road_name import RoadNameRenderer from openpilot.selfdrive.ui.sunnypilot.onroad.rocket_fuel import RocketFuel from openpilot.selfdrive.ui.sunnypilot.onroad.turn_signal import TurnSignalController @@ -17,17 +18,20 @@ class HudRendererSP(HudRenderer): def __init__(self): super().__init__() self.developer_ui = DeveloperUiRenderer() + self.road_name_renderer = RoadNameRenderer() self.rocket_fuel = RocketFuel() self.turn_signal_controller = TurnSignalController() def _update_state(self) -> None: super()._update_state() + self.road_name_renderer.update() self.turn_signal_controller.update() def _render(self, rect: rl.Rectangle) -> None: super()._render(rect) self.developer_ui.render(rect) + self.road_name_renderer.render(rect) self.turn_signal_controller.render() if ui_state.rocket_fuel: diff --git a/selfdrive/ui/sunnypilot/onroad/road_name.py b/selfdrive/ui/sunnypilot/onroad/road_name.py new file mode 100644 index 000000000..652e620ad --- /dev/null +++ b/selfdrive/ui/sunnypilot/onroad/road_name.py @@ -0,0 +1,56 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +import pyray as rl + +from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.system.ui.lib.application import gui_app, FontWeight +from openpilot.system.ui.lib.text_measure import measure_text_cached +from openpilot.system.ui.widgets import Widget + + +class RoadNameRenderer(Widget): + def __init__(self): + super().__init__() + self.road_name = "" + self.is_metric = False + self.font_demi = gui_app.font(FontWeight.SEMI_BOLD) + + def update(self): + sm = ui_state.sm + if sm.recv_frame["carState"] < ui_state.started_frame: + return + + self.is_metric = ui_state.is_metric + + if sm.updated["liveMapDataSP"]: + lmd = sm["liveMapDataSP"] + self.road_name = lmd.roadName + + def _render(self, rect: rl.Rectangle): + if not self.road_name: + return + + text = self.road_name + text_size = measure_text_cached(self.font_demi, text, 46) + + padding = 40 + rect_width = max(200, min(text_size.x + padding, rect.width - 40)) + + road_rect = rl.Rectangle(rect.x + rect.width / 2 - rect_width / 2, rect.y - 4, rect_width, 60) + + rl.draw_rectangle_rounded(road_rect, 0.2, 10, rl.Color(0, 0, 0, 120)) + + max_text_width = road_rect.width - 20 + if text_size.x > max_text_width: + while text_size.x > max_text_width and len(text) > 3: + text = text[:-1] + text_size = measure_text_cached(self.font_demi, text + "...", 46) + text = text + "..." + + sz = measure_text_cached(self.font_demi, text, 46) + origin = rl.Vector2(road_rect.x + road_rect.width / 2 - sz.x / 2, road_rect.y + road_rect.height / 2 - sz.y / 2) + rl.draw_text_ex(self.font_demi, text, origin, 46, 0, rl.Color(255, 255, 255, 200)) From fc4a0fb944de619e64e1ee9b312bdd6c03509a02 Mon Sep 17 00:00:00 2001 From: dzid26 Date: Sat, 24 Jan 2026 07:11:38 +0000 Subject: [PATCH 770/910] [TIZI/TICI] ui: Blue "Exit Always Offroad" button (#1655) Blue "Exit Always Offroad" button Co-authored-by: Jason Wen --- selfdrive/ui/sunnypilot/layouts/settings/device.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/sunnypilot/layouts/settings/device.py b/selfdrive/ui/sunnypilot/layouts/settings/device.py index 448a4faab..8f9d0ae22 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/device.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/device.py @@ -193,7 +193,7 @@ class DeviceLayoutSP(DeviceLayout): # Text & Color offroad_mode_btn_text = tr("Exit Always Offroad") if always_offroad else tr("Enable Always Offroad") - offroad_mode_btn_style = ButtonStyle.NORMAL if always_offroad else ButtonStyle.DANGER + offroad_mode_btn_style = ButtonStyle.PRIMARY if always_offroad else ButtonStyle.DANGER self._always_offroad_btn.action_item.left_button.set_text(offroad_mode_btn_text) self._always_offroad_btn.action_item.left_button.set_button_style(offroad_mode_btn_style) From 7c90c0669a8257fb9e086d9ae392950cc511ce82 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 24 Jan 2026 10:51:41 -0800 Subject: [PATCH 771/910] script for CI results (#37024) --- scripts/ci_results.py | 209 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 209 insertions(+) create mode 100755 scripts/ci_results.py diff --git a/scripts/ci_results.py b/scripts/ci_results.py new file mode 100755 index 000000000..c3d53f222 --- /dev/null +++ b/scripts/ci_results.py @@ -0,0 +1,209 @@ +#!/usr/bin/env python3 +"""Fetch CI results from GitHub Actions and Jenkins.""" + +import argparse +import json +import subprocess +import time +import urllib.error +import urllib.request +from datetime import datetime + +JENKINS_URL = "https://jenkins.comma.life" +DEFAULT_TIMEOUT = 1800 # 30 minutes +POLL_INTERVAL = 30 # seconds +LOG_TAIL_LINES = 10 # lines of log to include for failed jobs + + +def get_git_info(): + branch = subprocess.check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"], text=True).strip() + commit = subprocess.check_output(["git", "rev-parse", "HEAD"], text=True).strip() + return branch, commit + + +def get_github_actions_status(commit_sha): + result = subprocess.run( + ["gh", "run", "list", "--commit", commit_sha, "--workflow", "tests.yaml", "--json", "databaseId,status,conclusion"], + capture_output=True, text=True, check=True + ) + runs = json.loads(result.stdout) + if not runs: + return None, None + + run_id = runs[0]["databaseId"] + result = subprocess.run( + ["gh", "run", "view", str(run_id), "--json", "jobs"], + capture_output=True, text=True, check=True + ) + data = json.loads(result.stdout) + jobs = {job["name"]: {"status": job["status"], "conclusion": job["conclusion"], + "duration": format_duration(job) if job["conclusion"] not in ("skipped", None) and job.get("startedAt") else "", + "id": job["databaseId"]} + for job in data.get("jobs", [])} + return jobs, run_id + + +def get_github_job_log(run_id, job_id): + result = subprocess.run( + ["gh", "run", "view", str(run_id), "--job", str(job_id), "--log-failed"], + capture_output=True, text=True + ) + lines = result.stdout.strip().split('\n') + return '\n'.join(lines[-LOG_TAIL_LINES:]) if len(lines) > LOG_TAIL_LINES else result.stdout.strip() + + +def format_duration(job): + start = datetime.fromisoformat(job["startedAt"].replace("Z", "+00:00")) + end = datetime.fromisoformat(job["completedAt"].replace("Z", "+00:00")) + secs = int((end - start).total_seconds()) + return f"{secs // 60}m {secs % 60}s" + + +def get_jenkins_status(branch, commit_sha): + base_url = f"{JENKINS_URL}/job/openpilot/job/{branch}" + try: + # Get list of recent builds + with urllib.request.urlopen(f"{base_url}/api/json?tree=builds[number,url]", timeout=10) as resp: + builds = json.loads(resp.read().decode()).get("builds", []) + + # Find build matching commit + for build in builds[:20]: # check last 20 builds + with urllib.request.urlopen(f"{build['url']}api/json", timeout=10) as resp: + data = json.loads(resp.read().decode()) + for action in data.get("actions", []): + if action.get("_class") == "hudson.plugins.git.util.BuildData": + build_sha = action.get("lastBuiltRevision", {}).get("SHA1", "") + if build_sha.startswith(commit_sha) or commit_sha.startswith(build_sha): + # Get stages info + stages = [] + try: + with urllib.request.urlopen(f"{build['url']}wfapi/describe", timeout=10) as resp2: + wf_data = json.loads(resp2.read().decode()) + stages = [{"name": s["name"], "status": s["status"]} for s in wf_data.get("stages", [])] + except urllib.error.HTTPError: + pass + return { + "number": data["number"], + "in_progress": data.get("inProgress", False), + "result": data.get("result"), + "url": data.get("url", ""), + "stages": stages, + } + return None # no build found for this commit + except urllib.error.HTTPError: + return None # branch doesn't exist on Jenkins + + +def get_jenkins_log(build_url): + url = f"{build_url}consoleText" + with urllib.request.urlopen(url, timeout=30) as resp: + text = resp.read().decode(errors='replace') + lines = text.strip().split('\n') + return '\n'.join(lines[-LOG_TAIL_LINES:]) if len(lines) > LOG_TAIL_LINES else text.strip() + + +def is_complete(gh_status, jenkins_status): + gh_done = gh_status is None or all(j["status"] == "completed" for j in gh_status.values()) + jenkins_done = jenkins_status is None or not jenkins_status.get("in_progress", True) + return gh_done and jenkins_done + + +def status_icon(status, conclusion=None): + if status == "completed": + return ":white_check_mark:" if conclusion == "success" else ":x:" + return ":hourglass:" if status == "in_progress" else ":grey_question:" + + +def format_markdown(gh_status, gh_run_id, jenkins_status, commit_sha, branch): + lines = ["# CI Results", "", + f"**Branch**: {branch}", + f"**Commit**: {commit_sha[:7]}", + f"**Generated**: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}", ""] + + lines.extend(["## GitHub Actions", "", "| Job | Status | Duration |", "|-----|--------|----------|"]) + failed_gh_jobs = [] + if gh_status: + for job_name, job in gh_status.items(): + icon = status_icon(job["status"], job.get("conclusion")) + conclusion = job.get("conclusion") or job["status"] + lines.append(f"| {job_name} | {icon} {conclusion} | {job.get('duration', '')} |") + if job.get("conclusion") == "failure": + failed_gh_jobs.append((job_name, job.get("id"))) + else: + lines.append("| - | No workflow runs found | |") + + lines.extend(["", "## Jenkins", "", "| Stage | Status |", "|-------|--------|"]) + failed_jenkins_stages = [] + if jenkins_status: + stages = jenkins_status.get("stages", []) + if stages: + for stage in stages: + icon = ":white_check_mark:" if stage["status"] == "SUCCESS" else ( + ":x:" if stage["status"] == "FAILED" else ":hourglass:") + lines.append(f"| {stage['name']} | {icon} {stage['status'].lower()} |") + if stage["status"] == "FAILED": + failed_jenkins_stages.append(stage["name"]) + else: + icon = ":hourglass:" if jenkins_status["in_progress"] else ( + ":white_check_mark:" if jenkins_status["result"] == "SUCCESS" else ":x:") + status = "in progress" if jenkins_status["in_progress"] else (jenkins_status["result"] or "unknown") + lines.append(f"| #{jenkins_status['number']} | {icon} {status.lower()} |") + if jenkins_status.get("url"): + lines.append(f"\n[View build]({jenkins_status['url']})") + else: + lines.append("| - | No builds found for branch |") + + if failed_gh_jobs or failed_jenkins_stages: + lines.extend(["", "## Failure Logs", ""]) + + for job_name, job_id in failed_gh_jobs: + lines.append(f"### GitHub Actions: {job_name}") + log = get_github_job_log(gh_run_id, job_id) + lines.extend(["", "```", log, "```", ""]) + + for stage_name in failed_jenkins_stages: + lines.append(f"### Jenkins: {stage_name}") + log = get_jenkins_log(jenkins_status["url"]) + lines.extend(["", "```", log, "```", ""]) + + return "\n".join(lines) + "\n" + + +def main(): + parser = argparse.ArgumentParser(description="Fetch CI results from GitHub Actions and Jenkins") + parser.add_argument("--wait", action="store_true", help="Wait for CI to complete") + parser.add_argument("--timeout", type=int, default=DEFAULT_TIMEOUT, help="Timeout in seconds (default: 1800)") + parser.add_argument("-o", "--output", default="ci_results.md", help="Output file (default: ci_results.md)") + parser.add_argument("--branch", help="Branch to check (default: current branch)") + parser.add_argument("--commit", help="Commit SHA to check (default: HEAD)") + args = parser.parse_args() + + branch, commit = get_git_info() + branch = args.branch or branch + commit = args.commit or commit + print(f"Fetching CI results for {branch} @ {commit[:7]}") + + start_time = time.monotonic() + while True: + gh_status, gh_run_id = get_github_actions_status(commit) + jenkins_status = get_jenkins_status(branch, commit) if branch != "HEAD" else None + + if not args.wait or is_complete(gh_status, jenkins_status): + break + + elapsed = time.monotonic() - start_time + if elapsed >= args.timeout: + print(f"Timeout after {int(elapsed)}s") + break + + print(f"CI still running, waiting {POLL_INTERVAL}s... ({int(elapsed)}s elapsed)") + time.sleep(POLL_INTERVAL) + + content = format_markdown(gh_status, gh_run_id, jenkins_status, commit, branch) + with open(args.output, "w") as f: + f.write(content) + print(f"Results written to {args.output}") + + +if __name__ == "__main__": + main() From de024fd4a7f7d92a825dbbf614078d123c2fc25c Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 24 Jan 2026 12:02:33 -0800 Subject: [PATCH 772/910] pandad: pure Python capnp helpers (#37025) * pandad: pure Python capnp helpers * cleanup --- .../lib/longitudinal_mpc_lib/SConscript | 4 +- selfdrive/pandad/SConscript | 6 +- selfdrive/pandad/__init__.py | 1 - selfdrive/pandad/can_list_to_can_capnp.cc | 50 ----------- selfdrive/pandad/can_types.h | 15 ---- selfdrive/pandad/pandad_api_impl.py | 88 +++++++++++++++++++ selfdrive/pandad/pandad_api_impl.pyx | 56 ------------ 7 files changed, 91 insertions(+), 129 deletions(-) delete mode 100644 selfdrive/pandad/can_list_to_can_capnp.cc delete mode 100644 selfdrive/pandad/can_types.h create mode 100644 selfdrive/pandad/pandad_api_impl.py delete mode 100644 selfdrive/pandad/pandad_api_impl.pyx diff --git a/selfdrive/controls/lib/longitudinal_mpc_lib/SConscript b/selfdrive/controls/lib/longitudinal_mpc_lib/SConscript index 164b96514..7a6c02a53 100644 --- a/selfdrive/controls/lib/longitudinal_mpc_lib/SConscript +++ b/selfdrive/controls/lib/longitudinal_mpc_lib/SConscript @@ -1,4 +1,4 @@ -Import('env', 'envCython', 'arch', 'msgq_python', 'common_python', 'pandad_python', 'np_version') +Import('env', 'envCython', 'arch', 'msgq_python', 'common_python', 'np_version') gen = "c_generated_code" @@ -67,7 +67,7 @@ lenv.Clean(generated_files, Dir(gen)) generated_long = lenv.Command(generated_files, source_list, f"cd {Dir('.').abspath} && python3 long_mpc.py") -lenv.Depends(generated_long, [msgq_python, common_python, pandad_python]) +lenv.Depends(generated_long, [msgq_python, common_python]) lenv["CFLAGS"].append("-DACADOS_WITH_QPOASES") lenv["CXXFLAGS"].append("-DACADOS_WITH_QPOASES") diff --git a/selfdrive/pandad/SConscript b/selfdrive/pandad/SConscript index 58777cafe..5e0b782c1 100644 --- a/selfdrive/pandad/SConscript +++ b/selfdrive/pandad/SConscript @@ -1,13 +1,9 @@ -Import('env', 'envCython', 'common', 'messaging') +Import('env', 'common', 'messaging') libs = ['usb-1.0', common, messaging, 'pthread'] panda = env.Library('panda', ['panda.cc', 'panda_comms.cc', 'spi.cc']) env.Program('pandad', ['main.cc', 'pandad.cc', 'panda_safety.cc'], LIBS=[panda] + libs) -env.Library('libcan_list_to_can_capnp', ['can_list_to_can_capnp.cc']) - -pandad_python = envCython.Program('pandad_api_impl.so', 'pandad_api_impl.pyx', LIBS=["can_list_to_can_capnp", 'capnp', 'kj'] + envCython["LIBS"]) -Export('pandad_python') if GetOption('extras'): env.Program('tests/test_pandad_usbprotocol', ['tests/test_pandad_usbprotocol.cc'], LIBS=[panda] + libs) diff --git a/selfdrive/pandad/__init__.py b/selfdrive/pandad/__init__.py index cc680e167..0c17e886a 100644 --- a/selfdrive/pandad/__init__.py +++ b/selfdrive/pandad/__init__.py @@ -1,4 +1,3 @@ -# Cython, now uses scons to build from openpilot.selfdrive.pandad.pandad_api_impl import can_list_to_can_capnp, can_capnp_to_list assert can_list_to_can_capnp assert can_capnp_to_list diff --git a/selfdrive/pandad/can_list_to_can_capnp.cc b/selfdrive/pandad/can_list_to_can_capnp.cc deleted file mode 100644 index f2cf15345..000000000 --- a/selfdrive/pandad/can_list_to_can_capnp.cc +++ /dev/null @@ -1,50 +0,0 @@ -#include "cereal/messaging/messaging.h" -#include "selfdrive/pandad/can_types.h" - -void can_list_to_can_capnp_cpp(const std::vector &can_list, std::string &out, bool sendcan, bool valid) { - MessageBuilder msg; - auto event = msg.initEvent(valid); - - auto canData = sendcan ? event.initSendcan(can_list.size()) : event.initCan(can_list.size()); - int j = 0; - for (auto it = can_list.begin(); it != can_list.end(); it++, j++) { - auto c = canData[j]; - c.setAddress(it->address); - c.setDat(kj::arrayPtr((uint8_t*)it->dat.data(), it->dat.size())); - c.setSrc(it->src); - } - const uint64_t msg_size = capnp::computeSerializedSizeInWords(msg) * sizeof(capnp::word); - out.resize(msg_size); - kj::ArrayOutputStream output_stream(kj::ArrayPtr((unsigned char *)out.data(), msg_size)); - capnp::writeMessage(output_stream, msg); -} - -// Converts a vector of Cap'n Proto serialized can strings into a vector of CanData structures. -void can_capnp_to_can_list_cpp(const std::vector &strings, std::vector &can_list, bool sendcan) { - AlignedBuffer aligned_buf; - can_list.reserve(strings.size()); - - for (const auto &str : strings) { - // extract the messages - capnp::FlatArrayMessageReader reader(aligned_buf.align(str.data(), str.size())); - cereal::Event::Reader event = reader.getRoot(); - - auto frames = sendcan ? event.getSendcan() : event.getCan(); - - // Add new CanData entry - CanData &can_data = can_list.emplace_back(); - can_data.nanos = event.getLogMonoTime(); - can_data.frames.reserve(frames.size()); - - // Populate CAN frames - for (const auto &frame : frames) { - CanFrame &can_frame = can_data.frames.emplace_back(); - can_frame.src = frame.getSrc(); - can_frame.address = frame.getAddress(); - - // Copy CAN data - auto dat = frame.getDat(); - can_frame.dat.assign(dat.begin(), dat.end()); - } - } -} diff --git a/selfdrive/pandad/can_types.h b/selfdrive/pandad/can_types.h deleted file mode 100644 index 5fae581cf..000000000 --- a/selfdrive/pandad/can_types.h +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once - -#include -#include - -struct CanFrame { - long src; - uint32_t address; - std::vector dat; -}; - -struct CanData { - uint64_t nanos; - std::vector frames; -}; \ No newline at end of file diff --git a/selfdrive/pandad/pandad_api_impl.py b/selfdrive/pandad/pandad_api_impl.py new file mode 100644 index 000000000..75a7ba484 --- /dev/null +++ b/selfdrive/pandad/pandad_api_impl.py @@ -0,0 +1,88 @@ +import time +from cereal import log + +NO_TRAVERSAL_LIMIT = 2**64 - 1 + +# Cache schema fields for faster access (avoids string lookup on each field access) +_cached_reader_fields = None # (address_field, dat_field, src_field) for reading +_cached_writer_fields = None # (address_field, dat_field, src_field) for writing + + +def _get_reader_fields(schema): + """Get cached schema field objects for reading.""" + global _cached_reader_fields + if _cached_reader_fields is None: + fields = schema.fields + _cached_reader_fields = (fields['address'], fields['dat'], fields['src']) + return _cached_reader_fields + + +def _get_writer_fields(schema): + """Get cached schema field objects for writing.""" + global _cached_writer_fields + if _cached_writer_fields is None: + fields = schema.fields + _cached_writer_fields = (fields['address'], fields['dat'], fields['src']) + return _cached_writer_fields + + +def can_list_to_can_capnp(can_msgs, msgtype='can', valid=True): + """Convert list of CAN messages to Cap'n Proto serialized bytes. + + Args: + can_msgs: List of tuples [(address, data_bytes, src), ...] + msgtype: 'can' or 'sendcan' + valid: Whether the event is valid + + Returns: + Cap'n Proto serialized bytes + """ + global _cached_writer_fields + + dat = log.Event.new_message(valid=valid, logMonoTime=int(time.monotonic() * 1e9)) + can_data = dat.init(msgtype, len(can_msgs)) + + # Cache schema fields on first call + if _cached_writer_fields is None and len(can_msgs) > 0: + _cached_writer_fields = _get_writer_fields(can_data[0].schema) + + if _cached_writer_fields is not None: + addr_f, dat_f, src_f = _cached_writer_fields + for i, msg in enumerate(can_msgs): + f = can_data[i] + f._set_by_field(addr_f, msg[0]) + f._set_by_field(dat_f, msg[1]) + f._set_by_field(src_f, msg[2]) + + return dat.to_bytes() + + +def can_capnp_to_list(strings, msgtype='can'): + """Convert Cap'n Proto serialized bytes to list of CAN messages. + + Args: + strings: Tuple/list of serialized Cap'n Proto bytes + msgtype: 'can' or 'sendcan' + + Returns: + List of tuples [(nanos, [(address, data, src), ...]), ...] + """ + global _cached_reader_fields + result = [] + + for s in strings: + with log.Event.from_bytes(s, traversal_limit_in_words=NO_TRAVERSAL_LIMIT) as event: + frames = getattr(event, msgtype) + + # Cache schema fields on first frame for faster access + if _cached_reader_fields is None and len(frames) > 0: + _cached_reader_fields = _get_reader_fields(frames[0].schema) + + if _cached_reader_fields is not None: + addr_f, dat_f, src_f = _cached_reader_fields + frame_list = [(f._get_by_field(addr_f), f._get_by_field(dat_f), f._get_by_field(src_f)) for f in frames] + else: + frame_list = [] + + result.append((event.logMonoTime, frame_list)) + return result diff --git a/selfdrive/pandad/pandad_api_impl.pyx b/selfdrive/pandad/pandad_api_impl.pyx deleted file mode 100644 index aaecb8a59..000000000 --- a/selfdrive/pandad/pandad_api_impl.pyx +++ /dev/null @@ -1,56 +0,0 @@ -# distutils: language = c++ -# cython: language_level=3 -from cython.operator cimport dereference as deref, preincrement as preinc -from libcpp.vector cimport vector -from libcpp.string cimport string -from libcpp cimport bool -from libc.stdint cimport uint8_t, uint32_t, uint64_t - -cdef extern from "selfdrive/pandad/can_types.h": - cdef struct CanFrame: - long src - uint32_t address - vector[uint8_t] dat - - cdef struct CanData: - uint64_t nanos - vector[CanFrame] frames - -cdef extern from "can_list_to_can_capnp.cc": - void can_list_to_can_capnp_cpp(const vector[CanFrame] &can_list, string &out, bool sendcan, bool valid) nogil - void can_capnp_to_can_list_cpp(const vector[string] &strings, vector[CanData] &can_data, bool sendcan) - -def can_list_to_can_capnp(can_msgs, msgtype='can', valid=True): - cdef CanFrame *f - cdef vector[CanFrame] can_list - cdef uint32_t cpp_can_msgs_len = len(can_msgs) - - with nogil: - can_list.reserve(cpp_can_msgs_len) - - for can_msg in can_msgs: - f = &(can_list.emplace_back()) - f.address = can_msg[0] - f.dat = can_msg[1] - f.src = can_msg[2] - - cdef string out - cdef bool is_sendcan = (msgtype == 'sendcan') - cdef bool is_valid = valid - with nogil: - can_list_to_can_capnp_cpp(can_list, out, is_sendcan, is_valid) - return out - -def can_capnp_to_list(strings, msgtype='can'): - cdef vector[CanData] data - can_capnp_to_can_list_cpp(strings, data, msgtype == 'sendcan') - - result = [] - cdef CanData *d - cdef vector[CanData].iterator it = data.begin() - while it != data.end(): - d = &deref(it) - frames = [(f.address, (&f.dat[0])[:f.dat.size()], f.src) for f in d.frames] - result.append((d.nanos, frames)) - preinc(it) - return result From 5dea009113b5946e8f13aa62471f461930c13d50 Mon Sep 17 00:00:00 2001 From: Candy0707 <93701039+Candy0707@users.noreply.github.com> Date: Sun, 25 Jan 2026 11:50:39 +0800 Subject: [PATCH 773/910] [TIZI/TICI] ui: Fix misaligned turn signals and blindspot indicators with sidebar (#1659) * Fix Bug Turn and blind * use separate y coord for left and right signals --------- Co-authored-by: Jason Wen --- .../ui/sunnypilot/onroad/hud_renderer.py | 2 +- selfdrive/ui/sunnypilot/onroad/turn_signal.py | 32 +++++++++---------- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/selfdrive/ui/sunnypilot/onroad/hud_renderer.py b/selfdrive/ui/sunnypilot/onroad/hud_renderer.py index 933b573bf..0ea008f4e 100644 --- a/selfdrive/ui/sunnypilot/onroad/hud_renderer.py +++ b/selfdrive/ui/sunnypilot/onroad/hud_renderer.py @@ -32,7 +32,7 @@ class HudRendererSP(HudRenderer): self.developer_ui.render(rect) self.road_name_renderer.render(rect) - self.turn_signal_controller.render() + self.turn_signal_controller.render(rect) if ui_state.rocket_fuel: self.rocket_fuel.render(rect, ui_state.sm) diff --git a/selfdrive/ui/sunnypilot/onroad/turn_signal.py b/selfdrive/ui/sunnypilot/onroad/turn_signal.py index 4e5f1183d..3a66ffeb0 100644 --- a/selfdrive/ui/sunnypilot/onroad/turn_signal.py +++ b/selfdrive/ui/sunnypilot/onroad/turn_signal.py @@ -17,10 +17,10 @@ from openpilot.common.filter_simple import FirstOrderFilter @dataclass(frozen=True) class TurnSignalConfig: - left_x: int = 870 - left_y: int = 220 - right_x: int = 1140 - right_y: int = 220 + left_x: int = 80 + left_y: int = 190 + right_x: int = 80 + right_y: int = 190 size: int = 150 @@ -136,22 +136,20 @@ class TurnSignalController: else: self._right_signal.deactivate() - def render(self): + def render(self, rect: rl.Rectangle): + x = rect.x + rect.width / 2 + + left_x = x - self._config.left_x - self._config.size + left_y = rect.y + self._config.left_y + + right_x = x + self._config.right_x + right_y = rect.y + self._config.right_y + if self._left_signal._active: - self._left_signal.render(rl.Rectangle( - self._config.left_x, - self._config.left_y, - self._config.size, - self._config.size - )) + self._left_signal.render(rl.Rectangle(left_x, left_y, self._config.size, self._config.size)) if self._right_signal._active: - self._right_signal.render(rl.Rectangle( - self._config.right_x, - self._config.right_y, - self._config.size, - self._config.size - )) + self._right_signal.render(rl.Rectangle(right_x, right_y, self._config.size, self._config.size)) @property def config(self) -> TurnSignalConfig: From 9482f4659b077fdc40ef1cfa3cb46ae948531432 Mon Sep 17 00:00:00 2001 From: infiniteCable2 Date: Sun, 25 Jan 2026 11:57:22 +0100 Subject: [PATCH 774/910] bump --- opendbc_repo | 2 +- panda | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/opendbc_repo b/opendbc_repo index 466762a95..b8c700137 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 466762a95dd5055374b1166121c69364da2952f7 +Subproject commit b8c700137a9cf96e0f9b5281b4ee817341e75e4e diff --git a/panda b/panda index 3214b53a7..e07abd4ea 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 3214b53a75e064d3fc6fe8b36f4cb33d034bb0f2 +Subproject commit e07abd4ea8008957e5d31b70cf65ecdf788da56c From 2b005a8de803937878d86481737d650b7cd57fab Mon Sep 17 00:00:00 2001 From: infiniteCable2 Date: Sun, 25 Jan 2026 12:19:08 +0100 Subject: [PATCH 775/910] fix events --- cereal/log.capnp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cereal/log.capnp b/cereal/log.capnp index 95103d406..93f0a7e84 100644 --- a/cereal/log.capnp +++ b/cereal/log.capnp @@ -131,8 +131,8 @@ struct OnroadEvent @0xc4fa6047f024e718 { userBookmark @95; excessiveActuation @96; audioFeedback @97; - dashcamModeRadDisEngOn @98; - radarDisableFailed @99; + dashcamModeRadDisEngOn @99; + radarDisableFailed @100; soundsUnavailableDEPRECATED @47; } From c25af32d9108dc0bcdc3df554911467b4f85957b Mon Sep 17 00:00:00 2001 From: infiniteCable <75014343+infiniteCable@users.noreply.github.com> Date: Sun, 25 Jan 2026 14:59:37 +0100 Subject: [PATCH 776/910] Update README.md hint comma four compatbility --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 480b6cce0..23781b427 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -## ✍ To install this fork use installer.comma.ai/infiniteCable2/master +## ✍ To install this fork use installer.comma.ai/infiniteCable2/sync (Comma Four compatible) ![](https://user-images.githubusercontent.com/47793918/233812617-beab2e71-57b9-479e-8bff-c3931347ca40.png) From fb58e8f1f7470d4699359988939a4a84a23a9673 Mon Sep 17 00:00:00 2001 From: Kumar <36933347+rav4kumar@users.noreply.github.com> Date: Sun, 25 Jan 2026 23:54:42 -0700 Subject: [PATCH 777/910] [TIZI/TICI] ui: Speed Limit (#1653) * sla ui remove dub debug ahead postition maybe posttion think * sunny will be mad Reapply "test" This reverts commit 7a35fd3053425b06c1e4cebf68b6c9676eb6e279. Revert "test" This reverts commit 1a79155d3cfc8e6e09a6fc38d13747883745ef6a. test * road name * Revert "road name" This reverts commit 02e69b008603c357f14b4793e8fc893e00b3d3a0. * decouple * dial it in * match cur * no magic numbers * cleanup * more cleanup * match * always draw ahead * Revert "always draw ahead" This reverts commit f695e000789aab316af8234d8ea3e282e0b72ec3. * new * type * Revert "type" This reverts commit 2dafa024076ff0b9e027c88e5fd532f8bc4ec0c7. * default --------- Co-authored-by: Jason Wen --- .../ui/sunnypilot/onroad/hud_renderer.py | 5 +- selfdrive/ui/sunnypilot/onroad/speed_limit.py | 281 ++++++++++++++++++ selfdrive/ui/sunnypilot/ui_state.py | 1 + 3 files changed, 286 insertions(+), 1 deletion(-) create mode 100644 selfdrive/ui/sunnypilot/onroad/speed_limit.py diff --git a/selfdrive/ui/sunnypilot/onroad/hud_renderer.py b/selfdrive/ui/sunnypilot/onroad/hud_renderer.py index 0ea008f4e..8ca726980 100644 --- a/selfdrive/ui/sunnypilot/onroad/hud_renderer.py +++ b/selfdrive/ui/sunnypilot/onroad/hud_renderer.py @@ -11,6 +11,7 @@ from openpilot.selfdrive.ui.onroad.hud_renderer import HudRenderer from openpilot.selfdrive.ui.sunnypilot.onroad.developer_ui import DeveloperUiRenderer from openpilot.selfdrive.ui.sunnypilot.onroad.road_name import RoadNameRenderer from openpilot.selfdrive.ui.sunnypilot.onroad.rocket_fuel import RocketFuel +from openpilot.selfdrive.ui.sunnypilot.onroad.speed_limit import SpeedLimitRenderer from openpilot.selfdrive.ui.sunnypilot.onroad.turn_signal import TurnSignalController @@ -20,18 +21,20 @@ class HudRendererSP(HudRenderer): self.developer_ui = DeveloperUiRenderer() self.road_name_renderer = RoadNameRenderer() self.rocket_fuel = RocketFuel() + self.speed_limit_renderer = SpeedLimitRenderer() self.turn_signal_controller = TurnSignalController() def _update_state(self) -> None: super()._update_state() self.road_name_renderer.update() + self.speed_limit_renderer.update() self.turn_signal_controller.update() def _render(self, rect: rl.Rectangle) -> None: super()._render(rect) self.developer_ui.render(rect) - self.road_name_renderer.render(rect) + self.speed_limit_renderer.render(rect) self.turn_signal_controller.render(rect) if ui_state.rocket_fuel: diff --git a/selfdrive/ui/sunnypilot/onroad/speed_limit.py b/selfdrive/ui/sunnypilot/onroad/speed_limit.py new file mode 100644 index 000000000..a0b5ea393 --- /dev/null +++ b/selfdrive/ui/sunnypilot/onroad/speed_limit.py @@ -0,0 +1,281 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +from dataclasses import dataclass +import math +import pyray as rl + +from cereal import custom +from openpilot.common.constants import CV +from openpilot.common.filter_simple import FirstOrderFilter +from openpilot.selfdrive.ui.onroad.hud_renderer import UI_CONFIG +from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit.common import Mode as SpeedLimitMode +from openpilot.system.ui.lib.application import gui_app, FontWeight +from openpilot.system.ui.lib.multilang import tr +from openpilot.system.ui.lib.text_measure import measure_text_cached +from openpilot.system.ui.widgets import Widget + +METER_TO_FOOT = 3.28084 +METER_TO_MILE = 0.000621371 +AHEAD_THRESHOLD = 5 + +AssistState = custom.LongitudinalPlanSP.SpeedLimit.AssistState +SpeedLimitSource = custom.LongitudinalPlanSP.SpeedLimit.Source + + +@dataclass(frozen=True) +class Colors: + WHITE = rl.WHITE + BLACK = rl.BLACK + RED = rl.RED + GREY = rl.Color(145, 155, 149, 255) + DARK_GREY = rl.Color(77, 77, 77, 255) + SUB_BG = rl.Color(0, 0, 0, 180) + MUTCD_LINES = rl.Color(255, 255, 255, 100) + + +class SpeedLimitRenderer(Widget): + def __init__(self): + super().__init__() + + self.speed_limit = 0.0 + self.speed_limit_last = 0.0 + self.speed_limit_offset = 0.0 + self.speed_limit_valid = False + self.speed_limit_last_valid = False + self.speed_limit_final_last = 0.0 + self.speed_limit_source = SpeedLimitSource.none + self.speed_limit_assist_state = AssistState.disabled + + self.speed_limit_ahead = 0.0 + self.speed_limit_ahead_dist = 0.0 + self.speed_limit_ahead_dist_prev = 0.0 + self.speed_limit_ahead_valid = False + self.speed_limit_ahead_frame = 0 + + self.assist_frame = 0 + self.speed = 0.0 + self.set_speed = 0.0 + + self.font_bold = gui_app.font(FontWeight.BOLD) + self.font_demi = gui_app.font(FontWeight.SEMI_BOLD) + self.font_norm = gui_app.font(FontWeight.NORMAL) + self._sign_alpha_filter = FirstOrderFilter(1.0, 0.5, 1 / gui_app.target_fps) + + arrow_size = 90 + self._arrow_up = gui_app.texture("../../sunnypilot/selfdrive/assets/img_plus_arrow_up.png", arrow_size, arrow_size) + self._arrow_down = gui_app.texture("../../sunnypilot/selfdrive/assets/img_minus_arrow_down.png", arrow_size, arrow_size) + + @property + def speed_conv(self): + return CV.MS_TO_KPH if ui_state.is_metric else CV.MS_TO_MPH + + def update(self): + sm = ui_state.sm + if sm.recv_frame["carState"] < ui_state.started_frame: + return + + if sm.updated["longitudinalPlanSP"]: + lp_sp = sm["longitudinalPlanSP"] + resolver = lp_sp.speedLimit.resolver + assist = lp_sp.speedLimit.assist + + self.speed_limit = resolver.speedLimit * self.speed_conv + self.speed_limit_last = resolver.speedLimitLast * self.speed_conv + self.speed_limit_offset = resolver.speedLimitOffset * self.speed_conv + self.speed_limit_valid = resolver.speedLimitValid + self.speed_limit_last_valid = resolver.speedLimitLastValid + self.speed_limit_final_last = resolver.speedLimitFinalLast * self.speed_conv + self.speed_limit_source = resolver.source + self.speed_limit_assist_state = assist.state + + if sm.updated["liveMapDataSP"]: + lmd = sm["liveMapDataSP"] + self.speed_limit_ahead_valid = lmd.speedLimitAheadValid + self.speed_limit_ahead = lmd.speedLimitAhead * self.speed_conv + self.speed_limit_ahead_dist = lmd.speedLimitAheadDistance + + if self.speed_limit_ahead_dist < self.speed_limit_ahead_dist_prev and self.speed_limit_ahead_frame < AHEAD_THRESHOLD: + self.speed_limit_ahead_frame += 1 + elif self.speed_limit_ahead_dist > self.speed_limit_ahead_dist_prev and self.speed_limit_ahead_frame > 0: + self.speed_limit_ahead_frame -= 1 + + self.speed_limit_ahead_dist_prev = self.speed_limit_ahead_dist + + cs = sm["carState"] + self.set_speed = cs.cruiseState.speed * self.speed_conv + v_ego = cs.vEgoCluster if cs.vEgoCluster != 0.0 else cs.vEgo + self.speed = max(0.0, v_ego * self.speed_conv) + + @staticmethod + def _draw_text_centered(font, text, size, pos_center, color): + sz = measure_text_cached(font, text, size) + rl.draw_text_ex(font, text, rl.Vector2(pos_center.x - sz.x / 2, pos_center.y - sz.y / 2), size, 0, color) + + def _render(self, rect: rl.Rectangle): + width = UI_CONFIG.set_speed_width_metric if ui_state.is_metric else UI_CONFIG.set_speed_width_imperial + x = rect.x + 60 + width + 30 - 6 + y = rect.y + 45 - 6 + + sign_rect = rl.Rectangle(x, y, width, UI_CONFIG.set_speed_height + 6 * 2) + + if self.speed_limit_assist_state == AssistState.preActive: + self.assist_frame += 1 + pulse_value = 0.65 + 0.35 * math.sin(self.assist_frame * math.pi / gui_app.target_fps) + alpha = self._sign_alpha_filter.update(pulse_value) + else: + self.assist_frame = 0 + alpha = self._sign_alpha_filter.update(1.0) + + if ui_state.speed_limit_mode != SpeedLimitMode.off: + self._draw_sign_main(sign_rect, alpha) + if self.speed_limit_assist_state == AssistState.preActive: + self._draw_pre_active_arrow(sign_rect) + else: + self._draw_ahead_info(sign_rect) + + def _draw_sign_main(self, rect, alpha=1.0): + speed_limit_warning_enabled = ui_state.speed_limit_mode >= SpeedLimitMode.warning + has_limit = self.speed_limit_valid or self.speed_limit_last_valid + is_overspeed = has_limit and round(self.speed_limit_final_last) < round(self.speed) + + limit_str = str(round(self.speed_limit_last)) if has_limit else "---" + sub_text = "" + if self.speed_limit_offset != 0: + sign = "" if self.speed_limit_offset > 0 else "-" + sub_text = f"{sign}{round(abs(self.speed_limit_offset))}" + + txt_color = Colors.BLACK + if speed_limit_warning_enabled and is_overspeed: + txt_color = Colors.RED + elif not self.speed_limit_valid: + txt_color = Colors.GREY + + if ui_state.is_metric: + self._render_vienna(rect, limit_str, sub_text, txt_color, has_limit, alpha) + else: + self._render_mutcd(rect, limit_str, sub_text, txt_color, has_limit, alpha) + + def _draw_pre_active_arrow(self, sign_rect): + set_speed_rounded = round(self.set_speed) + limit_rounded = round(self.speed_limit_final_last) + + bounce_frequency = 2.0 * math.pi / (gui_app.target_fps * 2.5) + bounce_offset = int(20 * math.sin(self.assist_frame * bounce_frequency)) + + sign_margin = 12 + arrow_spacing = int(sign_margin * 1.4) + arrow_x = sign_rect.x + sign_rect.width + arrow_spacing + + if set_speed_rounded < limit_rounded: + arrow_y = sign_rect.y + (sign_rect.height - self._arrow_up.height) / 2 + bounce_offset + rl.draw_texture(self._arrow_up, int(arrow_x), int(arrow_y), rl.WHITE) + elif set_speed_rounded > limit_rounded: + arrow_y = sign_rect.y + (sign_rect.height - self._arrow_down.height) / 2 - bounce_offset + rl.draw_texture(self._arrow_down, int(arrow_x), int(arrow_y), rl.WHITE) + + def _render_vienna(self, rect, val, sub, color, has_limit, alpha=1.0): + center = rl.Vector2(rect.x + rect.width / 2, rect.y + rect.height / 2) + radius = (rect.width + 18) / 2 + + white = rl.Color(255, 255, 255, int(255 * alpha)) + red = rl.Color(255, 0, 0, int(255 * alpha)) + + if hasattr(color, 'r'): + text_color = rl.Color(color.r, color.g, color.b, int(255 * alpha)) + else: + text_color = rl.Color(color[0], color[1], color[2], int(255 * alpha)) + + black = rl.Color(0, 0, 0, int(255 * alpha)) + dark_grey = rl.Color(77, 77, 77, int(255 * alpha)) + + rl.draw_circle_v(center, radius, white) + rl.draw_ring(center, radius * 0.80, radius, 0, 360, 36, red) + + f_size = 70 if len(val) >= 3 else 85 + self._draw_text_centered(self.font_bold, val, f_size, center, text_color) + + if sub and has_limit: + s_radius = radius * 0.4 + s_center = rl.Vector2(rect.x + rect.width - s_radius / 2, rect.y + s_radius / 2) + + rl.draw_circle_v(s_center, s_radius, black) + rl.draw_ring(s_center, s_radius - 3, s_radius, 0, 360, 36, dark_grey) + + f_scale = 0.5 if len(sub) < 3 else 0.45 + self._draw_text_centered(self.font_bold, sub, int(s_radius * 2 * f_scale), s_center, white) + + def _render_mutcd(self, rect, val, sub, color, has_limit, alpha=1.0): + white = rl.Color(255, 255, 255, int(255 * alpha)) + black = rl.Color(0, 0, 0, int(255 * alpha)) + dark_grey = rl.Color(77, 77, 77, int(255 * alpha)) + + if hasattr(color, 'r'): + text_color = rl.Color(color.r, color.g, color.b, int(255 * alpha)) + else: + text_color = rl.Color(color[0], color[1], color[2], int(255 * alpha)) + + rl.draw_rectangle_rounded(rect, 0.35, 10, white) + inner = rl.Rectangle(rect.x + 10, rect.y + 10, rect.width - 20, rect.height - 20) + rl.draw_rectangle_rounded_lines_ex(inner, 0.35, 10, 4, black) + + self._draw_text_centered(self.font_demi, "SPEED", 40, rl.Vector2(rect.x + rect.width / 2, rect.y + 40), black) + self._draw_text_centered(self.font_demi, "LIMIT", 40, rl.Vector2(rect.x + rect.width / 2, rect.y + 80), black) + self._draw_text_centered(self.font_bold, val, 90, rl.Vector2(rect.x + rect.width / 2, rect.y + 150), text_color) + + if sub and has_limit: + box_sz = rect.width * 0.3 + overlap = box_sz * 0.2 + s_rect = rl.Rectangle(rect.x + rect.width - box_sz / 1.5 + overlap, rect.y - box_sz / 1.25 + overlap, box_sz, box_sz) + + rl.draw_rectangle_rounded(s_rect, 0.35, 10, black) + rl.draw_rectangle_rounded_lines_ex(s_rect, 0.35, 10, 6, dark_grey) + + f_scale = 0.6 if len(sub) < 3 else 0.475 + self._draw_text_centered(self.font_bold, sub, int(box_sz * f_scale), rl.Vector2(s_rect.x + box_sz / 2, s_rect.y + box_sz / 2), white) + + def _draw_ahead_info(self, sign_rect): + source_is_map = self.speed_limit_source == SpeedLimitSource.map + valid = self.speed_limit_ahead_valid and self.speed_limit_ahead > 0 and self.speed_limit_ahead != self.speed_limit + + if not (valid and source_is_map): + return + + rect = rl.Rectangle(sign_rect.x + (sign_rect.width - 170) / 2, sign_rect.y + sign_rect.height + 10, 170, 160) + rl.draw_rectangle_rounded(rect, 0.35, 10, Colors.SUB_BG) + rl.draw_rectangle_rounded_lines_ex(rect, 0.35, 10, 3, Colors.MUTCD_LINES) + + mid_x = rect.x + rect.width / 2 + self._draw_text_centered(self.font_demi, "AHEAD", 40, rl.Vector2(mid_x, rect.y + 28), Colors.GREY) + self._draw_text_centered(self.font_bold, str(round(self.speed_limit_ahead)), 70, rl.Vector2(mid_x, rect.y + 82), Colors.WHITE) + self._draw_text_centered(self.font_norm, self._format_dist(self.speed_limit_ahead_dist), 36, rl.Vector2(mid_x, rect.y + 134), Colors.GREY) + + @staticmethod + def _format_dist(d): + # metric + if ui_state.is_metric: + if d < 50: + return tr("Near") + + if d >= 1000: + return f"{d / 1000:.1f} km" + + d_rounded = round(d, -1) if d < 200 else round(d, -2) + return f"{int(d_rounded)} m" + + # imperial + d_ft = d * METER_TO_FOOT + if d_ft < 100: + return tr("Near") + + if d_ft >= 900: + return f"{d * METER_TO_MILE:.1f} mi" + + if d_ft < 500: + return f"{int(round(d_ft / 50) * 50)} ft" + + return f"{int(round(d_ft / 100) * 100)} ft" diff --git a/selfdrive/ui/sunnypilot/ui_state.py b/selfdrive/ui/sunnypilot/ui_state.py index 9ef4e1d7c..f38280d49 100644 --- a/selfdrive/ui/sunnypilot/ui_state.py +++ b/selfdrive/ui/sunnypilot/ui_state.py @@ -127,6 +127,7 @@ class UIStateSP: self.chevron_metrics = self.params.get("ChevronInfo") self.active_bundle = self.params.get("ModelManager_ActiveBundle") self.custom_interactive_timeout = self.params.get("InteractivityTimeout", return_default=True) + self.speed_limit_mode = self.params.get("SpeedLimitMode", return_default=True) # Onroad Screen Brightness self.onroad_brightness = int(float(self.params.get("OnroadScreenOffBrightness", return_default=True))) From 71a418d166d00d86226c02453a70c57776f46009 Mon Sep 17 00:00:00 2001 From: commaci-public <60409688+commaci-public@users.noreply.github.com> Date: Mon, 26 Jan 2026 09:14:57 -0800 Subject: [PATCH 778/910] [bot] Update Python packages (#37028) Update Python packages Co-authored-by: Vehicle Researcher --- docs/CARS.md | 3 +- opendbc_repo | 2 +- panda | 2 +- tinygrad_repo | 2 +- uv.lock | 262 +++++++++++++++++++++++++------------------------- 5 files changed, 136 insertions(+), 135 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index 08c06b230..b34967939 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -4,7 +4,7 @@ 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. -# 326 Supported Cars +# 327 Supported Cars |Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Hardware Needed
 |Video|Setup Video| |---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| @@ -14,6 +14,7 @@ A supported vehicle is one that just works when you install a comma device. All |Acura|RDX 2016-18|AcuraWatch Plus or Advance Package|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Acura|RDX 2019-21|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Acura|TLX 2021|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Acura|TLX 2025|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Audi|A3 2014-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Audi|A3 Sportback e-tron 2017-18|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Audi|Q2 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| diff --git a/opendbc_repo b/opendbc_repo index 1908668b0..d424d1f24 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 1908668b05691564ea5fc80bc11b784a9dee0714 +Subproject commit d424d1f247384b68923b8093875e1a370ef8221d diff --git a/panda b/panda index 3dd38b76b..81615ad9d 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 3dd38b76b48903efb4705f55752e9719ba2f5564 +Subproject commit 81615ad9d53aef5583e064f340e9cdeb23d4119c diff --git a/tinygrad_repo b/tinygrad_repo index 7cb7abeeb..774a454bb 160000 --- a/tinygrad_repo +++ b/tinygrad_repo @@ -1 +1 @@ -Subproject commit 7cb7abeeb02c681a463f252179354db4bb5e3809 +Subproject commit 774a454bb5e6d0fe3756a8add9302c0a3d592bd9 diff --git a/uv.lock b/uv.lock index 674b18b7e..b221995b8 100644 --- a/uv.lock +++ b/uv.lock @@ -378,37 +378,37 @@ wheels = [ [[package]] name = "coverage" -version = "7.13.1" +version = "7.13.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/23/f9/e92df5e07f3fc8d4c7f9a0f146ef75446bf870351cd37b788cf5897f8079/coverage-7.13.1.tar.gz", hash = "sha256:b7593fe7eb5feaa3fbb461ac79aac9f9fc0387a5ca8080b0c6fe2ca27b091afd", size = 825862, upload-time = "2025-12-28T15:42:56.969Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ad/49/349848445b0e53660e258acbcc9b0d014895b6739237920886672240f84b/coverage-7.13.2.tar.gz", hash = "sha256:044c6951ec37146b72a50cc81ef02217d27d4c3640efd2640311393cbbf143d3", size = 826523, upload-time = "2026-01-25T13:00:04.889Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b4/9b/77baf488516e9ced25fc215a6f75d803493fc3f6a1a1227ac35697910c2a/coverage-7.13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a55d509a1dc5a5b708b5dad3b5334e07a16ad4c2185e27b40e4dba796ab7f88", size = 218755, upload-time = "2025-12-28T15:40:30.812Z" }, - { url = "https://files.pythonhosted.org/packages/d7/cd/7ab01154e6eb79ee2fab76bf4d89e94c6648116557307ee4ebbb85e5c1bf/coverage-7.13.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4d010d080c4888371033baab27e47c9df7d6fb28d0b7b7adf85a4a49be9298b3", size = 219257, upload-time = "2025-12-28T15:40:32.333Z" }, - { url = "https://files.pythonhosted.org/packages/01/d5/b11ef7863ffbbdb509da0023fad1e9eda1c0eaea61a6d2ea5b17d4ac706e/coverage-7.13.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d938b4a840fb1523b9dfbbb454f652967f18e197569c32266d4d13f37244c3d9", size = 249657, upload-time = "2025-12-28T15:40:34.1Z" }, - { url = "https://files.pythonhosted.org/packages/f7/7c/347280982982383621d29b8c544cf497ae07ac41e44b1ca4903024131f55/coverage-7.13.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bf100a3288f9bb7f919b87eb84f87101e197535b9bd0e2c2b5b3179633324fee", size = 251581, upload-time = "2025-12-28T15:40:36.131Z" }, - { url = "https://files.pythonhosted.org/packages/82/f6/ebcfed11036ade4c0d75fa4453a6282bdd225bc073862766eec184a4c643/coverage-7.13.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef6688db9bf91ba111ae734ba6ef1a063304a881749726e0d3575f5c10a9facf", size = 253691, upload-time = "2025-12-28T15:40:37.626Z" }, - { url = "https://files.pythonhosted.org/packages/02/92/af8f5582787f5d1a8b130b2dcba785fa5e9a7a8e121a0bb2220a6fdbdb8a/coverage-7.13.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0b609fc9cdbd1f02e51f67f51e5aee60a841ef58a68d00d5ee2c0faf357481a3", size = 249799, upload-time = "2025-12-28T15:40:39.47Z" }, - { url = "https://files.pythonhosted.org/packages/24/aa/0e39a2a3b16eebf7f193863323edbff38b6daba711abaaf807d4290cf61a/coverage-7.13.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c43257717611ff5e9a1d79dce8e47566235ebda63328718d9b65dd640bc832ef", size = 251389, upload-time = "2025-12-28T15:40:40.954Z" }, - { url = "https://files.pythonhosted.org/packages/73/46/7f0c13111154dc5b978900c0ccee2e2ca239b910890e674a77f1363d483e/coverage-7.13.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e09fbecc007f7b6afdfb3b07ce5bd9f8494b6856dd4f577d26c66c391b829851", size = 249450, upload-time = "2025-12-28T15:40:42.489Z" }, - { url = "https://files.pythonhosted.org/packages/ac/ca/e80da6769e8b669ec3695598c58eef7ad98b0e26e66333996aee6316db23/coverage-7.13.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:a03a4f3a19a189919c7055098790285cc5c5b0b3976f8d227aea39dbf9f8bfdb", size = 249170, upload-time = "2025-12-28T15:40:44.279Z" }, - { url = "https://files.pythonhosted.org/packages/af/18/9e29baabdec1a8644157f572541079b4658199cfd372a578f84228e860de/coverage-7.13.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3820778ea1387c2b6a818caec01c63adc5b3750211af6447e8dcfb9b6f08dbba", size = 250081, upload-time = "2025-12-28T15:40:45.748Z" }, - { url = "https://files.pythonhosted.org/packages/00/f8/c3021625a71c3b2f516464d322e41636aea381018319050a8114105872ee/coverage-7.13.1-cp311-cp311-win32.whl", hash = "sha256:ff10896fa55167371960c5908150b434b71c876dfab97b69478f22c8b445ea19", size = 221281, upload-time = "2025-12-28T15:40:47.232Z" }, - { url = "https://files.pythonhosted.org/packages/27/56/c216625f453df6e0559ed666d246fcbaaa93f3aa99eaa5080cea1229aa3d/coverage-7.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:a998cc0aeeea4c6d5622a3754da5a493055d2d95186bad877b0a34ea6e6dbe0a", size = 222215, upload-time = "2025-12-28T15:40:49.19Z" }, - { url = "https://files.pythonhosted.org/packages/5c/9a/be342e76f6e531cae6406dc46af0d350586f24d9b67fdfa6daee02df71af/coverage-7.13.1-cp311-cp311-win_arm64.whl", hash = "sha256:fea07c1a39a22614acb762e3fbbb4011f65eedafcb2948feeef641ac78b4ee5c", size = 220886, upload-time = "2025-12-28T15:40:51.067Z" }, - { url = "https://files.pythonhosted.org/packages/ce/8a/87af46cccdfa78f53db747b09f5f9a21d5fc38d796834adac09b30a8ce74/coverage-7.13.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6f34591000f06e62085b1865c9bc5f7858df748834662a51edadfd2c3bfe0dd3", size = 218927, upload-time = "2025-12-28T15:40:52.814Z" }, - { url = "https://files.pythonhosted.org/packages/82/a8/6e22fdc67242a4a5a153f9438d05944553121c8f4ba70cb072af4c41362e/coverage-7.13.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b67e47c5595b9224599016e333f5ec25392597a89d5744658f837d204e16c63e", size = 219288, upload-time = "2025-12-28T15:40:54.262Z" }, - { url = "https://files.pythonhosted.org/packages/d0/0a/853a76e03b0f7c4375e2ca025df45c918beb367f3e20a0a8e91967f6e96c/coverage-7.13.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3e7b8bd70c48ffb28461ebe092c2345536fb18bbbf19d287c8913699735f505c", size = 250786, upload-time = "2025-12-28T15:40:56.059Z" }, - { url = "https://files.pythonhosted.org/packages/ea/b4/694159c15c52b9f7ec7adf49d50e5f8ee71d3e9ef38adb4445d13dd56c20/coverage-7.13.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c223d078112e90dc0e5c4e35b98b9584164bea9fbbd221c0b21c5241f6d51b62", size = 253543, upload-time = "2025-12-28T15:40:57.585Z" }, - { url = "https://files.pythonhosted.org/packages/96/b2/7f1f0437a5c855f87e17cf5d0dc35920b6440ff2b58b1ba9788c059c26c8/coverage-7.13.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:794f7c05af0763b1bbd1b9e6eff0e52ad068be3b12cd96c87de037b01390c968", size = 254635, upload-time = "2025-12-28T15:40:59.443Z" }, - { url = "https://files.pythonhosted.org/packages/e9/d1/73c3fdb8d7d3bddd9473c9c6a2e0682f09fc3dfbcb9c3f36412a7368bcab/coverage-7.13.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0642eae483cc8c2902e4af7298bf886d605e80f26382124cddc3967c2a3df09e", size = 251202, upload-time = "2025-12-28T15:41:01.328Z" }, - { url = "https://files.pythonhosted.org/packages/66/3c/f0edf75dcc152f145d5598329e864bbbe04ab78660fe3e8e395f9fff010f/coverage-7.13.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9f5e772ed5fef25b3de9f2008fe67b92d46831bd2bc5bdc5dd6bfd06b83b316f", size = 252566, upload-time = "2025-12-28T15:41:03.319Z" }, - { url = "https://files.pythonhosted.org/packages/17/b3/e64206d3c5f7dcbceafd14941345a754d3dbc78a823a6ed526e23b9cdaab/coverage-7.13.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:45980ea19277dc0a579e432aef6a504fe098ef3a9032ead15e446eb0f1191aee", size = 250711, upload-time = "2025-12-28T15:41:06.411Z" }, - { url = "https://files.pythonhosted.org/packages/dc/ad/28a3eb970a8ef5b479ee7f0c484a19c34e277479a5b70269dc652b730733/coverage-7.13.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:e4f18eca6028ffa62adbd185a8f1e1dd242f2e68164dba5c2b74a5204850b4cf", size = 250278, upload-time = "2025-12-28T15:41:08.285Z" }, - { url = "https://files.pythonhosted.org/packages/54/e3/c8f0f1a93133e3e1291ca76cbb63565bd4b5c5df63b141f539d747fff348/coverage-7.13.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f8dca5590fec7a89ed6826fce625595279e586ead52e9e958d3237821fbc750c", size = 252154, upload-time = "2025-12-28T15:41:09.969Z" }, - { url = "https://files.pythonhosted.org/packages/d0/bf/9939c5d6859c380e405b19e736321f1c7d402728792f4c752ad1adcce005/coverage-7.13.1-cp312-cp312-win32.whl", hash = "sha256:ff86d4e85188bba72cfb876df3e11fa243439882c55957184af44a35bd5880b7", size = 221487, upload-time = "2025-12-28T15:41:11.468Z" }, - { url = "https://files.pythonhosted.org/packages/fa/dc/7282856a407c621c2aad74021680a01b23010bb8ebf427cf5eacda2e876f/coverage-7.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:16cc1da46c04fb0fb128b4dc430b78fa2aba8a6c0c9f8eb391fd5103409a6ac6", size = 222299, upload-time = "2025-12-28T15:41:13.386Z" }, - { url = "https://files.pythonhosted.org/packages/10/79/176a11203412c350b3e9578620013af35bcdb79b651eb976f4a4b32044fa/coverage-7.13.1-cp312-cp312-win_arm64.whl", hash = "sha256:8d9bc218650022a768f3775dd7fdac1886437325d8d295d923ebcfef4892ad5c", size = 220941, upload-time = "2025-12-28T15:41:14.975Z" }, - { url = "https://files.pythonhosted.org/packages/cc/48/d9f421cb8da5afaa1a64570d9989e00fb7955e6acddc5a12979f7666ef60/coverage-7.13.1-py3-none-any.whl", hash = "sha256:2016745cb3ba554469d02819d78958b571792bb68e31302610e898f80dd3a573", size = 210722, upload-time = "2025-12-28T15:42:54.901Z" }, + { url = "https://files.pythonhosted.org/packages/6c/01/abca50583a8975bb6e1c59eff67ed8e48bb127c07dad5c28d9e96ccc09ec/coverage-7.13.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:060ebf6f2c51aff5ba38e1f43a2095e087389b1c69d559fde6049a4b0001320e", size = 218971, upload-time = "2026-01-25T12:57:36.953Z" }, + { url = "https://files.pythonhosted.org/packages/eb/0e/b6489f344d99cd1e5b4d5e1be52dfd3f8a3dc5112aa6c33948da8cabad4e/coverage-7.13.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c1ea8ca9db5e7469cd364552985e15911548ea5b69c48a17291f0cac70484b2e", size = 219473, upload-time = "2026-01-25T12:57:38.934Z" }, + { url = "https://files.pythonhosted.org/packages/17/11/db2f414915a8e4ec53f60b17956c27f21fb68fcf20f8a455ce7c2ccec638/coverage-7.13.2-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b780090d15fd58f07cf2011943e25a5f0c1c894384b13a216b6c86c8a8a7c508", size = 249896, upload-time = "2026-01-25T12:57:40.365Z" }, + { url = "https://files.pythonhosted.org/packages/80/06/0823fe93913663c017e508e8810c998c8ebd3ec2a5a85d2c3754297bdede/coverage-7.13.2-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:88a800258d83acb803c38175b4495d293656d5fac48659c953c18e5f539a274b", size = 251810, upload-time = "2026-01-25T12:57:42.045Z" }, + { url = "https://files.pythonhosted.org/packages/61/dc/b151c3cc41b28cdf7f0166c5fa1271cbc305a8ec0124cce4b04f74791a18/coverage-7.13.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6326e18e9a553e674d948536a04a80d850a5eeefe2aae2e6d7cf05d54046c01b", size = 253920, upload-time = "2026-01-25T12:57:44.026Z" }, + { url = "https://files.pythonhosted.org/packages/2d/35/e83de0556e54a4729a2b94ea816f74ce08732e81945024adee46851c2264/coverage-7.13.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:59562de3f797979e1ff07c587e2ac36ba60ca59d16c211eceaa579c266c5022f", size = 250025, upload-time = "2026-01-25T12:57:45.624Z" }, + { url = "https://files.pythonhosted.org/packages/39/67/af2eb9c3926ce3ea0d58a0d2516fcbdacf7a9fc9559fe63076beaf3f2596/coverage-7.13.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:27ba1ed6f66b0e2d61bfa78874dffd4f8c3a12f8e2b5410e515ab345ba7bc9c3", size = 251612, upload-time = "2026-01-25T12:57:47.713Z" }, + { url = "https://files.pythonhosted.org/packages/26/62/5be2e25f3d6c711d23b71296f8b44c978d4c8b4e5b26871abfc164297502/coverage-7.13.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8be48da4d47cc68754ce643ea50b3234557cbefe47c2f120495e7bd0a2756f2b", size = 249670, upload-time = "2026-01-25T12:57:49.378Z" }, + { url = "https://files.pythonhosted.org/packages/b3/51/400d1b09a8344199f9b6a6fc1868005d766b7ea95e7882e494fa862ca69c/coverage-7.13.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:2a47a4223d3361b91176aedd9d4e05844ca67d7188456227b6bf5e436630c9a1", size = 249395, upload-time = "2026-01-25T12:57:50.86Z" }, + { url = "https://files.pythonhosted.org/packages/e0/36/f02234bc6e5230e2f0a63fd125d0a2093c73ef20fdf681c7af62a140e4e7/coverage-7.13.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c6f141b468740197d6bd38f2b26ade124363228cc3f9858bd9924ab059e00059", size = 250298, upload-time = "2026-01-25T12:57:52.287Z" }, + { url = "https://files.pythonhosted.org/packages/b0/06/713110d3dd3151b93611c9cbfc65c15b4156b44f927fced49ac0b20b32a4/coverage-7.13.2-cp311-cp311-win32.whl", hash = "sha256:89567798404af067604246e01a49ef907d112edf2b75ef814b1364d5ce267031", size = 221485, upload-time = "2026-01-25T12:57:53.876Z" }, + { url = "https://files.pythonhosted.org/packages/16/0c/3ae6255fa1ebcb7dec19c9a59e85ef5f34566d1265c70af5b2fc981da834/coverage-7.13.2-cp311-cp311-win_amd64.whl", hash = "sha256:21dd57941804ae2ac7e921771a5e21bbf9aabec317a041d164853ad0a96ce31e", size = 222421, upload-time = "2026-01-25T12:57:55.433Z" }, + { url = "https://files.pythonhosted.org/packages/b5/37/fabc3179af4d61d89ea47bd04333fec735cd5e8b59baad44fed9fc4170d7/coverage-7.13.2-cp311-cp311-win_arm64.whl", hash = "sha256:10758e0586c134a0bafa28f2d37dd2cdb5e4a90de25c0fc0c77dabbad46eca28", size = 221088, upload-time = "2026-01-25T12:57:57.41Z" }, + { url = "https://files.pythonhosted.org/packages/46/39/e92a35f7800222d3f7b2cbb7bbc3b65672ae8d501cb31801b2d2bd7acdf1/coverage-7.13.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f106b2af193f965d0d3234f3f83fc35278c7fb935dfbde56ae2da3dd2c03b84d", size = 219142, upload-time = "2026-01-25T12:58:00.448Z" }, + { url = "https://files.pythonhosted.org/packages/45/7a/8bf9e9309c4c996e65c52a7c5a112707ecdd9fbaf49e10b5a705a402bbb4/coverage-7.13.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78f45d21dc4d5d6bd29323f0320089ef7eae16e4bef712dff79d184fa7330af3", size = 219503, upload-time = "2026-01-25T12:58:02.451Z" }, + { url = "https://files.pythonhosted.org/packages/87/93/17661e06b7b37580923f3f12406ac91d78aeed293fb6da0b69cc7957582f/coverage-7.13.2-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:fae91dfecd816444c74531a9c3d6ded17a504767e97aa674d44f638107265b99", size = 251006, upload-time = "2026-01-25T12:58:04.059Z" }, + { url = "https://files.pythonhosted.org/packages/12/f0/f9e59fb8c310171497f379e25db060abef9fa605e09d63157eebec102676/coverage-7.13.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:264657171406c114787b441484de620e03d8f7202f113d62fcd3d9688baa3e6f", size = 253750, upload-time = "2026-01-25T12:58:05.574Z" }, + { url = "https://files.pythonhosted.org/packages/e5/b1/1935e31add2232663cf7edd8269548b122a7d100047ff93475dbaaae673e/coverage-7.13.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae47d8dcd3ded0155afbb59c62bd8ab07ea0fd4902e1c40567439e6db9dcaf2f", size = 254862, upload-time = "2026-01-25T12:58:07.647Z" }, + { url = "https://files.pythonhosted.org/packages/af/59/b5e97071ec13df5f45da2b3391b6cdbec78ba20757bc92580a5b3d5fa53c/coverage-7.13.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8a0b33e9fd838220b007ce8f299114d406c1e8edb21336af4c97a26ecfd185aa", size = 251420, upload-time = "2026-01-25T12:58:09.309Z" }, + { url = "https://files.pythonhosted.org/packages/3f/75/9495932f87469d013dc515fb0ce1aac5fa97766f38f6b1a1deb1ee7b7f3a/coverage-7.13.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b3becbea7f3ce9a2d4d430f223ec15888e4deb31395840a79e916368d6004cce", size = 252786, upload-time = "2026-01-25T12:58:10.909Z" }, + { url = "https://files.pythonhosted.org/packages/6a/59/af550721f0eb62f46f7b8cb7e6f1860592189267b1c411a4e3a057caacee/coverage-7.13.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f819c727a6e6eeb8711e4ce63d78c620f69630a2e9d53bc95ca5379f57b6ba94", size = 250928, upload-time = "2026-01-25T12:58:12.449Z" }, + { url = "https://files.pythonhosted.org/packages/9b/b1/21b4445709aae500be4ab43bbcfb4e53dc0811c3396dcb11bf9f23fd0226/coverage-7.13.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:4f7b71757a3ab19f7ba286e04c181004c1d61be921795ee8ba6970fd0ec91da5", size = 250496, upload-time = "2026-01-25T12:58:14.047Z" }, + { url = "https://files.pythonhosted.org/packages/ba/b1/0f5d89dfe0392990e4f3980adbde3eb34885bc1effb2dc369e0bf385e389/coverage-7.13.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b7fc50d2afd2e6b4f6f2f403b70103d280a8e0cb35320cbbe6debcda02a1030b", size = 252373, upload-time = "2026-01-25T12:58:15.976Z" }, + { url = "https://files.pythonhosted.org/packages/01/c9/0cf1a6a57a9968cc049a6b896693faa523c638a5314b1fc374eb2b2ac904/coverage-7.13.2-cp312-cp312-win32.whl", hash = "sha256:292250282cf9bcf206b543d7608bda17ca6fc151f4cbae949fc7e115112fbd41", size = 221696, upload-time = "2026-01-25T12:58:17.517Z" }, + { url = "https://files.pythonhosted.org/packages/4d/05/d7540bf983f09d32803911afed135524570f8c47bb394bf6206c1dc3a786/coverage-7.13.2-cp312-cp312-win_amd64.whl", hash = "sha256:eeea10169fac01549a7921d27a3e517194ae254b542102267bef7a93ed38c40e", size = 222504, upload-time = "2026-01-25T12:58:19.115Z" }, + { url = "https://files.pythonhosted.org/packages/15/8b/1a9f037a736ced0a12aacf6330cdaad5008081142a7070bc58b0f7930cbc/coverage-7.13.2-cp312-cp312-win_arm64.whl", hash = "sha256:2a5b567f0b635b592c917f96b9a9cb3dbd4c320d03f4bf94e9084e494f2e8894", size = 221120, upload-time = "2026-01-25T12:58:21.334Z" }, + { url = "https://files.pythonhosted.org/packages/d2/db/d291e30fdf7ea617a335531e72294e0c723356d7fdde8fba00610a76bda9/coverage-7.13.2-py3-none-any.whl", hash = "sha256:40ce1ea1e25125556d8e76bd0b61500839a07944cc287ac21d5626f3e620cad5", size = 210943, upload-time = "2026-01-25T13:00:02.388Z" }, ] [[package]] @@ -915,11 +915,11 @@ wheels = [ [[package]] name = "markdown" -version = "3.10" +version = "3.10.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7d/ab/7dd27d9d863b3376fcf23a5a13cb5d024aed1db46f963f1b5735ae43b3be/markdown-3.10.tar.gz", hash = "sha256:37062d4f2aa4b2b6b32aefb80faa300f82cc790cb949a35b8caede34f2b68c0e", size = 364931, upload-time = "2025-11-03T19:51:15.007Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b7/b1/af95bcae8549f1f3fd70faacb29075826a0d689a27f232e8cee315efa053/markdown-3.10.1.tar.gz", hash = "sha256:1c19c10bd5c14ac948c53d0d762a04e2fa35a6d58a6b7b1e6bfcbe6fefc0001a", size = 365402, upload-time = "2026-01-21T18:09:28.206Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/70/81/54e3ce63502cd085a0c556652a4e1b919c45a446bd1e5300e10c44c8c521/markdown-3.10-py3-none-any.whl", hash = "sha256:b5b99d6951e2e4948d939255596523444c0e677c669700b1d17aa4a8a464cb7c", size = 107678, upload-time = "2025-11-03T19:51:13.887Z" }, + { url = "https://files.pythonhosted.org/packages/59/1b/6ef961f543593969d25b2afe57a3564200280528caa9bd1082eecdd7b3bc/markdown-3.10.1-py3-none-any.whl", hash = "sha256:867d788939fe33e4b736426f5b9f651ad0c0ae0ecf89df0ca5d1176c70812fe3", size = 107684, upload-time = "2026-01-21T18:09:27.203Z" }, ] [[package]] @@ -1158,47 +1158,47 @@ wheels = [ [[package]] name = "multidict" -version = "6.7.0" +version = "6.7.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/80/1e/5492c365f222f907de1039b91f922b93fa4f764c713ee858d235495d8f50/multidict-6.7.0.tar.gz", hash = "sha256:c6e99d9a65ca282e578dfea819cfa9c0a62b2499d8677392e09feaf305e9e6f5", size = 101834, upload-time = "2025-10-06T14:52:30.657Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1a/c2/c2d94cbe6ac1753f3fc980da97b3d930efe1da3af3c9f5125354436c073d/multidict-6.7.1.tar.gz", hash = "sha256:ec6652a1bee61c53a3e5776b6049172c53b6aaba34f18c9ad04f82712bac623d", size = 102010, upload-time = "2026-01-26T02:46:45.979Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/34/9e/5c727587644d67b2ed479041e4b1c58e30afc011e3d45d25bbe35781217c/multidict-6.7.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4d409aa42a94c0b3fa617708ef5276dfe81012ba6753a0370fcc9d0195d0a1fc", size = 76604, upload-time = "2025-10-06T14:48:54.277Z" }, - { url = "https://files.pythonhosted.org/packages/17/e4/67b5c27bd17c085a5ea8f1ec05b8a3e5cba0ca734bfcad5560fb129e70ca/multidict-6.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:14c9e076eede3b54c636f8ce1c9c252b5f057c62131211f0ceeec273810c9721", size = 44715, upload-time = "2025-10-06T14:48:55.445Z" }, - { url = "https://files.pythonhosted.org/packages/4d/e1/866a5d77be6ea435711bef2a4291eed11032679b6b28b56b4776ab06ba3e/multidict-6.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4c09703000a9d0fa3c3404b27041e574cc7f4df4c6563873246d0e11812a94b6", size = 44332, upload-time = "2025-10-06T14:48:56.706Z" }, - { url = "https://files.pythonhosted.org/packages/31/61/0c2d50241ada71ff61a79518db85ada85fdabfcf395d5968dae1cbda04e5/multidict-6.7.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a265acbb7bb33a3a2d626afbe756371dce0279e7b17f4f4eda406459c2b5ff1c", size = 245212, upload-time = "2025-10-06T14:48:58.042Z" }, - { url = "https://files.pythonhosted.org/packages/ac/e0/919666a4e4b57fff1b57f279be1c9316e6cdc5de8a8b525d76f6598fefc7/multidict-6.7.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51cb455de290ae462593e5b1cb1118c5c22ea7f0d3620d9940bf695cea5a4bd7", size = 246671, upload-time = "2025-10-06T14:49:00.004Z" }, - { url = "https://files.pythonhosted.org/packages/a1/cc/d027d9c5a520f3321b65adea289b965e7bcbd2c34402663f482648c716ce/multidict-6.7.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:db99677b4457c7a5c5a949353e125ba72d62b35f74e26da141530fbb012218a7", size = 225491, upload-time = "2025-10-06T14:49:01.393Z" }, - { url = "https://files.pythonhosted.org/packages/75/c4/bbd633980ce6155a28ff04e6a6492dd3335858394d7bb752d8b108708558/multidict-6.7.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f470f68adc395e0183b92a2f4689264d1ea4b40504a24d9882c27375e6662bb9", size = 257322, upload-time = "2025-10-06T14:49:02.745Z" }, - { url = "https://files.pythonhosted.org/packages/4c/6d/d622322d344f1f053eae47e033b0b3f965af01212de21b10bcf91be991fb/multidict-6.7.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0db4956f82723cc1c270de9c6e799b4c341d327762ec78ef82bb962f79cc07d8", size = 254694, upload-time = "2025-10-06T14:49:04.15Z" }, - { url = "https://files.pythonhosted.org/packages/a8/9f/78f8761c2705d4c6d7516faed63c0ebdac569f6db1bef95e0d5218fdc146/multidict-6.7.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3e56d780c238f9e1ae66a22d2adf8d16f485381878250db8d496623cd38b22bd", size = 246715, upload-time = "2025-10-06T14:49:05.967Z" }, - { url = "https://files.pythonhosted.org/packages/78/59/950818e04f91b9c2b95aab3d923d9eabd01689d0dcd889563988e9ea0fd8/multidict-6.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9d14baca2ee12c1a64740d4531356ba50b82543017f3ad6de0deb943c5979abb", size = 243189, upload-time = "2025-10-06T14:49:07.37Z" }, - { url = "https://files.pythonhosted.org/packages/7a/3d/77c79e1934cad2ee74991840f8a0110966d9599b3af95964c0cd79bb905b/multidict-6.7.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:295a92a76188917c7f99cda95858c822f9e4aae5824246bba9b6b44004ddd0a6", size = 237845, upload-time = "2025-10-06T14:49:08.759Z" }, - { url = "https://files.pythonhosted.org/packages/63/1b/834ce32a0a97a3b70f86437f685f880136677ac00d8bce0027e9fd9c2db7/multidict-6.7.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:39f1719f57adbb767ef592a50ae5ebb794220d1188f9ca93de471336401c34d2", size = 246374, upload-time = "2025-10-06T14:49:10.574Z" }, - { url = "https://files.pythonhosted.org/packages/23/ef/43d1c3ba205b5dec93dc97f3fba179dfa47910fc73aaaea4f7ceb41cec2a/multidict-6.7.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:0a13fb8e748dfc94749f622de065dd5c1def7e0d2216dba72b1d8069a389c6ff", size = 253345, upload-time = "2025-10-06T14:49:12.331Z" }, - { url = "https://files.pythonhosted.org/packages/6b/03/eaf95bcc2d19ead522001f6a650ef32811aa9e3624ff0ad37c445c7a588c/multidict-6.7.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e3aa16de190d29a0ea1b48253c57d99a68492c8dd8948638073ab9e74dc9410b", size = 246940, upload-time = "2025-10-06T14:49:13.821Z" }, - { url = "https://files.pythonhosted.org/packages/e8/df/ec8a5fd66ea6cd6f525b1fcbb23511b033c3e9bc42b81384834ffa484a62/multidict-6.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a048ce45dcdaaf1defb76b2e684f997fb5abf74437b6cb7b22ddad934a964e34", size = 242229, upload-time = "2025-10-06T14:49:15.603Z" }, - { url = "https://files.pythonhosted.org/packages/8a/a2/59b405d59fd39ec86d1142630e9049243015a5f5291ba49cadf3c090c541/multidict-6.7.0-cp311-cp311-win32.whl", hash = "sha256:a90af66facec4cebe4181b9e62a68be65e45ac9b52b67de9eec118701856e7ff", size = 41308, upload-time = "2025-10-06T14:49:16.871Z" }, - { url = "https://files.pythonhosted.org/packages/32/0f/13228f26f8b882c34da36efa776c3b7348455ec383bab4a66390e42963ae/multidict-6.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:95b5ffa4349df2887518bb839409bcf22caa72d82beec453216802f475b23c81", size = 46037, upload-time = "2025-10-06T14:49:18.457Z" }, - { url = "https://files.pythonhosted.org/packages/84/1f/68588e31b000535a3207fd3c909ebeec4fb36b52c442107499c18a896a2a/multidict-6.7.0-cp311-cp311-win_arm64.whl", hash = "sha256:329aa225b085b6f004a4955271a7ba9f1087e39dcb7e65f6284a988264a63912", size = 43023, upload-time = "2025-10-06T14:49:19.648Z" }, - { url = "https://files.pythonhosted.org/packages/c2/9e/9f61ac18d9c8b475889f32ccfa91c9f59363480613fc807b6e3023d6f60b/multidict-6.7.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8a3862568a36d26e650a19bb5cbbba14b71789032aebc0423f8cc5f150730184", size = 76877, upload-time = "2025-10-06T14:49:20.884Z" }, - { url = "https://files.pythonhosted.org/packages/38/6f/614f09a04e6184f8824268fce4bc925e9849edfa654ddd59f0b64508c595/multidict-6.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:960c60b5849b9b4f9dcc9bea6e3626143c252c74113df2c1540aebce70209b45", size = 45467, upload-time = "2025-10-06T14:49:22.054Z" }, - { url = "https://files.pythonhosted.org/packages/b3/93/c4f67a436dd026f2e780c433277fff72be79152894d9fc36f44569cab1a6/multidict-6.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2049be98fb57a31b4ccf870bf377af2504d4ae35646a19037ec271e4c07998aa", size = 43834, upload-time = "2025-10-06T14:49:23.566Z" }, - { url = "https://files.pythonhosted.org/packages/7f/f5/013798161ca665e4a422afbc5e2d9e4070142a9ff8905e482139cd09e4d0/multidict-6.7.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0934f3843a1860dd465d38895c17fce1f1cb37295149ab05cd1b9a03afacb2a7", size = 250545, upload-time = "2025-10-06T14:49:24.882Z" }, - { url = "https://files.pythonhosted.org/packages/71/2f/91dbac13e0ba94669ea5119ba267c9a832f0cb65419aca75549fcf09a3dc/multidict-6.7.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b3e34f3a1b8131ba06f1a73adab24f30934d148afcd5f5de9a73565a4404384e", size = 258305, upload-time = "2025-10-06T14:49:26.778Z" }, - { url = "https://files.pythonhosted.org/packages/ef/b0/754038b26f6e04488b48ac621f779c341338d78503fb45403755af2df477/multidict-6.7.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:efbb54e98446892590dc2458c19c10344ee9a883a79b5cec4bc34d6656e8d546", size = 242363, upload-time = "2025-10-06T14:49:28.562Z" }, - { url = "https://files.pythonhosted.org/packages/87/15/9da40b9336a7c9fa606c4cf2ed80a649dffeb42b905d4f63a1d7eb17d746/multidict-6.7.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a35c5fc61d4f51eb045061e7967cfe3123d622cd500e8868e7c0c592a09fedc4", size = 268375, upload-time = "2025-10-06T14:49:29.96Z" }, - { url = "https://files.pythonhosted.org/packages/82/72/c53fcade0cc94dfaad583105fd92b3a783af2091eddcb41a6d5a52474000/multidict-6.7.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29fe6740ebccba4175af1b9b87bf553e9c15cd5868ee967e010efcf94e4fd0f1", size = 269346, upload-time = "2025-10-06T14:49:31.404Z" }, - { url = "https://files.pythonhosted.org/packages/0d/e2/9baffdae21a76f77ef8447f1a05a96ec4bc0a24dae08767abc0a2fe680b8/multidict-6.7.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:123e2a72e20537add2f33a79e605f6191fba2afda4cbb876e35c1a7074298a7d", size = 256107, upload-time = "2025-10-06T14:49:32.974Z" }, - { url = "https://files.pythonhosted.org/packages/3c/06/3f06f611087dc60d65ef775f1fb5aca7c6d61c6db4990e7cda0cef9b1651/multidict-6.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b284e319754366c1aee2267a2036248b24eeb17ecd5dc16022095e747f2f4304", size = 253592, upload-time = "2025-10-06T14:49:34.52Z" }, - { url = "https://files.pythonhosted.org/packages/20/24/54e804ec7945b6023b340c412ce9c3f81e91b3bf5fa5ce65558740141bee/multidict-6.7.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:803d685de7be4303b5a657b76e2f6d1240e7e0a8aa2968ad5811fa2285553a12", size = 251024, upload-time = "2025-10-06T14:49:35.956Z" }, - { url = "https://files.pythonhosted.org/packages/14/48/011cba467ea0b17ceb938315d219391d3e421dfd35928e5dbdc3f4ae76ef/multidict-6.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c04a328260dfd5db8c39538f999f02779012268f54614902d0afc775d44e0a62", size = 251484, upload-time = "2025-10-06T14:49:37.631Z" }, - { url = "https://files.pythonhosted.org/packages/0d/2f/919258b43bb35b99fa127435cfb2d91798eb3a943396631ef43e3720dcf4/multidict-6.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8a19cdb57cd3df4cd865849d93ee14920fb97224300c88501f16ecfa2604b4e0", size = 263579, upload-time = "2025-10-06T14:49:39.502Z" }, - { url = "https://files.pythonhosted.org/packages/31/22/a0e884d86b5242b5a74cf08e876bdf299e413016b66e55511f7a804a366e/multidict-6.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b2fd74c52accced7e75de26023b7dccee62511a600e62311b918ec5c168fc2a", size = 259654, upload-time = "2025-10-06T14:49:41.32Z" }, - { url = "https://files.pythonhosted.org/packages/b2/e5/17e10e1b5c5f5a40f2fcbb45953c9b215f8a4098003915e46a93f5fcaa8f/multidict-6.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3e8bfdd0e487acf992407a140d2589fe598238eaeffa3da8448d63a63cd363f8", size = 251511, upload-time = "2025-10-06T14:49:46.021Z" }, - { url = "https://files.pythonhosted.org/packages/e3/9a/201bb1e17e7af53139597069c375e7b0dcbd47594604f65c2d5359508566/multidict-6.7.0-cp312-cp312-win32.whl", hash = "sha256:dd32a49400a2c3d52088e120ee00c1e3576cbff7e10b98467962c74fdb762ed4", size = 41895, upload-time = "2025-10-06T14:49:48.718Z" }, - { url = "https://files.pythonhosted.org/packages/46/e2/348cd32faad84eaf1d20cce80e2bb0ef8d312c55bca1f7fa9865e7770aaf/multidict-6.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:92abb658ef2d7ef22ac9f8bb88e8b6c3e571671534e029359b6d9e845923eb1b", size = 46073, upload-time = "2025-10-06T14:49:50.28Z" }, - { url = "https://files.pythonhosted.org/packages/25/ec/aad2613c1910dce907480e0c3aa306905830f25df2e54ccc9dea450cb5aa/multidict-6.7.0-cp312-cp312-win_arm64.whl", hash = "sha256:490dab541a6a642ce1a9d61a4781656b346a55c13038f0b1244653828e3a83ec", size = 43226, upload-time = "2025-10-06T14:49:52.304Z" }, - { url = "https://files.pythonhosted.org/packages/b7/da/7d22601b625e241d4f23ef1ebff8acfc60da633c9e7e7922e24d10f592b3/multidict-6.7.0-py3-none-any.whl", hash = "sha256:394fc5c42a333c9ffc3e421a4c85e08580d990e08b99f6bf35b4132114c5dcb3", size = 12317, upload-time = "2025-10-06T14:52:29.272Z" }, + { url = "https://files.pythonhosted.org/packages/ce/f1/a90635c4f88fb913fbf4ce660b83b7445b7a02615bda034b2f8eb38fd597/multidict-6.7.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7ff981b266af91d7b4b3793ca3382e53229088d193a85dfad6f5f4c27fc73e5d", size = 76626, upload-time = "2026-01-26T02:43:26.485Z" }, + { url = "https://files.pythonhosted.org/packages/a6/9b/267e64eaf6fc637a15b35f5de31a566634a2740f97d8d094a69d34f524a4/multidict-6.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:844c5bca0b5444adb44a623fb0a1310c2f4cd41f402126bb269cd44c9b3f3e1e", size = 44706, upload-time = "2026-01-26T02:43:27.607Z" }, + { url = "https://files.pythonhosted.org/packages/dd/a4/d45caf2b97b035c57267791ecfaafbd59c68212004b3842830954bb4b02e/multidict-6.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f2a0a924d4c2e9afcd7ec64f9de35fcd96915149b2216e1cb2c10a56df483855", size = 44356, upload-time = "2026-01-26T02:43:28.661Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d2/0a36c8473f0cbaeadd5db6c8b72d15bbceeec275807772bfcd059bef487d/multidict-6.7.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8be1802715a8e892c784c0197c2ace276ea52702a0ede98b6310c8f255a5afb3", size = 244355, upload-time = "2026-01-26T02:43:31.165Z" }, + { url = "https://files.pythonhosted.org/packages/5d/16/8c65be997fd7dd311b7d39c7b6e71a0cb449bad093761481eccbbe4b42a2/multidict-6.7.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e2d2ed645ea29f31c4c7ea1552fcfd7cb7ba656e1eafd4134a6620c9f5fdd9e", size = 246433, upload-time = "2026-01-26T02:43:32.581Z" }, + { url = "https://files.pythonhosted.org/packages/01/fb/4dbd7e848d2799c6a026ec88ad39cf2b8416aa167fcc903baa55ecaa045c/multidict-6.7.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:95922cee9a778659e91db6497596435777bd25ed116701a4c034f8e46544955a", size = 225376, upload-time = "2026-01-26T02:43:34.417Z" }, + { url = "https://files.pythonhosted.org/packages/b6/8a/4a3a6341eac3830f6053062f8fbc9a9e54407c80755b3f05bc427295c2d0/multidict-6.7.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6b83cabdc375ffaaa15edd97eb7c0c672ad788e2687004990074d7d6c9b140c8", size = 257365, upload-time = "2026-01-26T02:43:35.741Z" }, + { url = "https://files.pythonhosted.org/packages/f7/a2/dd575a69c1aa206e12d27d0770cdf9b92434b48a9ef0cd0d1afdecaa93c4/multidict-6.7.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:38fb49540705369bab8484db0689d86c0a33a0a9f2c1b197f506b71b4b6c19b0", size = 254747, upload-time = "2026-01-26T02:43:36.976Z" }, + { url = "https://files.pythonhosted.org/packages/5a/56/21b27c560c13822ed93133f08aa6372c53a8e067f11fbed37b4adcdac922/multidict-6.7.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:439cbebd499f92e9aa6793016a8acaa161dfa749ae86d20960189f5398a19144", size = 246293, upload-time = "2026-01-26T02:43:38.258Z" }, + { url = "https://files.pythonhosted.org/packages/5a/a4/23466059dc3854763423d0ad6c0f3683a379d97673b1b89ec33826e46728/multidict-6.7.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6d3bc717b6fe763b8be3f2bee2701d3c8eb1b2a8ae9f60910f1b2860c82b6c49", size = 242962, upload-time = "2026-01-26T02:43:40.034Z" }, + { url = "https://files.pythonhosted.org/packages/1f/67/51dd754a3524d685958001e8fa20a0f5f90a6a856e0a9dcabff69be3dbb7/multidict-6.7.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:619e5a1ac57986dbfec9f0b301d865dddf763696435e2962f6d9cf2fdff2bb71", size = 237360, upload-time = "2026-01-26T02:43:41.752Z" }, + { url = "https://files.pythonhosted.org/packages/64/3f/036dfc8c174934d4b55d86ff4f978e558b0e585cef70cfc1ad01adc6bf18/multidict-6.7.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0b38ebffd9be37c1170d33bc0f36f4f262e0a09bc1aac1c34c7aa51a7293f0b3", size = 245940, upload-time = "2026-01-26T02:43:43.042Z" }, + { url = "https://files.pythonhosted.org/packages/3d/20/6214d3c105928ebc353a1c644a6ef1408bc5794fcb4f170bb524a3c16311/multidict-6.7.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:10ae39c9cfe6adedcdb764f5e8411d4a92b055e35573a2eaa88d3323289ef93c", size = 253502, upload-time = "2026-01-26T02:43:44.371Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e2/c653bc4ae1be70a0f836b82172d643fcf1dade042ba2676ab08ec08bff0f/multidict-6.7.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:25167cc263257660290fba06b9318d2026e3c910be240a146e1f66dd114af2b0", size = 247065, upload-time = "2026-01-26T02:43:45.745Z" }, + { url = "https://files.pythonhosted.org/packages/c8/11/a854b4154cd3bd8b1fd375e8a8ca9d73be37610c361543d56f764109509b/multidict-6.7.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:128441d052254f42989ef98b7b6a6ecb1e6f708aa962c7984235316db59f50fa", size = 241870, upload-time = "2026-01-26T02:43:47.054Z" }, + { url = "https://files.pythonhosted.org/packages/13/bf/9676c0392309b5fdae322333d22a829715b570edb9baa8016a517b55b558/multidict-6.7.1-cp311-cp311-win32.whl", hash = "sha256:d62b7f64ffde3b99d06b707a280db04fb3855b55f5a06df387236051d0668f4a", size = 41302, upload-time = "2026-01-26T02:43:48.753Z" }, + { url = "https://files.pythonhosted.org/packages/c9/68/f16a3a8ba6f7b6dc92a1f19669c0810bd2c43fc5a02da13b1cbf8e253845/multidict-6.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:bdbf9f3b332abd0cdb306e7c2113818ab1e922dc84b8f8fd06ec89ed2a19ab8b", size = 45981, upload-time = "2026-01-26T02:43:49.921Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ad/9dd5305253fa00cd3c7555dbef69d5bf4133debc53b87ab8d6a44d411665/multidict-6.7.1-cp311-cp311-win_arm64.whl", hash = "sha256:b8c990b037d2fff2f4e33d3f21b9b531c5745b33a49a7d6dbe7a177266af44f6", size = 43159, upload-time = "2026-01-26T02:43:51.635Z" }, + { url = "https://files.pythonhosted.org/packages/8d/9c/f20e0e2cf80e4b2e4b1c365bf5fe104ee633c751a724246262db8f1a0b13/multidict-6.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a90f75c956e32891a4eda3639ce6dd86e87105271f43d43442a3aedf3cddf172", size = 76893, upload-time = "2026-01-26T02:43:52.754Z" }, + { url = "https://files.pythonhosted.org/packages/fe/cf/18ef143a81610136d3da8193da9d80bfe1cb548a1e2d1c775f26b23d024a/multidict-6.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fccb473e87eaa1382689053e4a4618e7ba7b9b9b8d6adf2027ee474597128cd", size = 45456, upload-time = "2026-01-26T02:43:53.893Z" }, + { url = "https://files.pythonhosted.org/packages/a9/65/1caac9d4cd32e8433908683446eebc953e82d22b03d10d41a5f0fefe991b/multidict-6.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0fa96985700739c4c7853a43c0b3e169360d6855780021bfc6d0f1ce7c123e7", size = 43872, upload-time = "2026-01-26T02:43:55.041Z" }, + { url = "https://files.pythonhosted.org/packages/cf/3b/d6bd75dc4f3ff7c73766e04e705b00ed6dbbaccf670d9e05a12b006f5a21/multidict-6.7.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cb2a55f408c3043e42b40cc8eecd575afa27b7e0b956dfb190de0f8499a57a53", size = 251018, upload-time = "2026-01-26T02:43:56.198Z" }, + { url = "https://files.pythonhosted.org/packages/fd/80/c959c5933adedb9ac15152e4067c702a808ea183a8b64cf8f31af8ad3155/multidict-6.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb0ce7b2a32d09892b3dd6cc44877a0d02a33241fafca5f25c8b6b62374f8b75", size = 258883, upload-time = "2026-01-26T02:43:57.499Z" }, + { url = "https://files.pythonhosted.org/packages/86/85/7ed40adafea3d4f1c8b916e3b5cc3a8e07dfcdcb9cd72800f4ed3ca1b387/multidict-6.7.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c3a32d23520ee37bf327d1e1a656fec76a2edd5c038bf43eddfa0572ec49c60b", size = 242413, upload-time = "2026-01-26T02:43:58.755Z" }, + { url = "https://files.pythonhosted.org/packages/d2/57/b8565ff533e48595503c785f8361ff9a4fde4d67de25c207cd0ba3befd03/multidict-6.7.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9c90fed18bffc0189ba814749fdcc102b536e83a9f738a9003e569acd540a733", size = 268404, upload-time = "2026-01-26T02:44:00.216Z" }, + { url = "https://files.pythonhosted.org/packages/e0/50/9810c5c29350f7258180dfdcb2e52783a0632862eb334c4896ac717cebcb/multidict-6.7.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:da62917e6076f512daccfbbde27f46fed1c98fee202f0559adec8ee0de67f71a", size = 269456, upload-time = "2026-01-26T02:44:02.202Z" }, + { url = "https://files.pythonhosted.org/packages/f3/8d/5e5be3ced1d12966fefb5c4ea3b2a5b480afcea36406559442c6e31d4a48/multidict-6.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bfde23ef6ed9db7eaee6c37dcec08524cb43903c60b285b172b6c094711b3961", size = 256322, upload-time = "2026-01-26T02:44:03.56Z" }, + { url = "https://files.pythonhosted.org/packages/31/6e/d8a26d81ac166a5592782d208dd90dfdc0a7a218adaa52b45a672b46c122/multidict-6.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3758692429e4e32f1ba0df23219cd0b4fc0a52f476726fff9337d1a57676a582", size = 253955, upload-time = "2026-01-26T02:44:04.845Z" }, + { url = "https://files.pythonhosted.org/packages/59/4c/7c672c8aad41534ba619bcd4ade7a0dc87ed6b8b5c06149b85d3dd03f0cd/multidict-6.7.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:398c1478926eca669f2fd6a5856b6de9c0acf23a2cb59a14c0ba5844fa38077e", size = 251254, upload-time = "2026-01-26T02:44:06.133Z" }, + { url = "https://files.pythonhosted.org/packages/7b/bd/84c24de512cbafbdbc39439f74e967f19570ce7924e3007174a29c348916/multidict-6.7.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c102791b1c4f3ab36ce4101154549105a53dc828f016356b3e3bcae2e3a039d3", size = 252059, upload-time = "2026-01-26T02:44:07.518Z" }, + { url = "https://files.pythonhosted.org/packages/fa/ba/f5449385510825b73d01c2d4087bf6d2fccc20a2d42ac34df93191d3dd03/multidict-6.7.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a088b62bd733e2ad12c50dad01b7d0166c30287c166e137433d3b410add807a6", size = 263588, upload-time = "2026-01-26T02:44:09.382Z" }, + { url = "https://files.pythonhosted.org/packages/d7/11/afc7c677f68f75c84a69fe37184f0f82fce13ce4b92f49f3db280b7e92b3/multidict-6.7.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3d51ff4785d58d3f6c91bdbffcb5e1f7ddfda557727043aa20d20ec4f65e324a", size = 259642, upload-time = "2026-01-26T02:44:10.73Z" }, + { url = "https://files.pythonhosted.org/packages/2b/17/ebb9644da78c4ab36403739e0e6e0e30ebb135b9caf3440825001a0bddcb/multidict-6.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc5907494fccf3e7d3f94f95c91d6336b092b5fc83811720fae5e2765890dfba", size = 251377, upload-time = "2026-01-26T02:44:12.042Z" }, + { url = "https://files.pythonhosted.org/packages/ca/a4/840f5b97339e27846c46307f2530a2805d9d537d8b8bd416af031cad7fa0/multidict-6.7.1-cp312-cp312-win32.whl", hash = "sha256:28ca5ce2fd9716631133d0e9a9b9a745ad7f60bac2bccafb56aa380fc0b6c511", size = 41887, upload-time = "2026-01-26T02:44:14.245Z" }, + { url = "https://files.pythonhosted.org/packages/80/31/0b2517913687895f5904325c2069d6a3b78f66cc641a86a2baf75a05dcbb/multidict-6.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcee94dfbd638784645b066074b338bc9cc155d4b4bffa4adce1615c5a426c19", size = 46053, upload-time = "2026-01-26T02:44:15.371Z" }, + { url = "https://files.pythonhosted.org/packages/0c/5b/aba28e4ee4006ae4c7df8d327d31025d760ffa992ea23812a601d226e682/multidict-6.7.1-cp312-cp312-win_arm64.whl", hash = "sha256:ba0a9fb644d0c1a2194cf7ffb043bd852cea63a57f66fbd33959f7dae18517bf", size = 43307, upload-time = "2026-01-26T02:44:16.852Z" }, + { url = "https://files.pythonhosted.org/packages/81/08/7036c080d7117f28a4af526d794aab6a84463126db031b007717c1a6676e/multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56", size = 12319, upload-time = "2026-01-26T02:46:44.004Z" }, ] [[package]] @@ -1461,11 +1461,11 @@ provides-extras = ["docs", "testing", "dev", "tools"] [[package]] name = "packaging" -version = "25.0" +version = "26.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, + { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, ] [[package]] @@ -1735,11 +1735,11 @@ wheels = [ [[package]] name = "pycparser" -version = "2.23" +version = "3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, ] [[package]] @@ -4300,11 +4300,11 @@ wheels = [ [[package]] name = "pyparsing" -version = "3.3.1" +version = "3.3.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/33/c1/1d9de9aeaa1b89b0186e5fe23294ff6517fce1bc69149185577cd31016b2/pyparsing-3.3.1.tar.gz", hash = "sha256:47fad0f17ac1e2cad3de3b458570fbc9b03560aa029ed5e16ee5554da9a2251c", size = 1550512, upload-time = "2025-12-23T03:14:04.391Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/91/9c6ee907786a473bf81c5f53cf703ba0957b23ab84c264080fb5a450416f/pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc", size = 6851574, upload-time = "2026-01-21T03:57:59.36Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/40/2614036cdd416452f5bf98ec037f38a1afb17f327cb8e6b652d4729e0af8/pyparsing-3.3.1-py3-none-any.whl", hash = "sha256:023b5e7e5520ad96642e2c6db4cb683d3970bd640cdf7115049a6e9c3682df82", size = 121793, upload-time = "2025-12-23T03:14:02.103Z" }, + { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" }, ] [[package]] @@ -4702,28 +4702,28 @@ wheels = [ [[package]] name = "ruff" -version = "0.14.13" +version = "0.14.14" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/50/0a/1914efb7903174b381ee2ffeebb4253e729de57f114e63595114c8ca451f/ruff-0.14.13.tar.gz", hash = "sha256:83cd6c0763190784b99650a20fec7633c59f6ebe41c5cc9d45ee42749563ad47", size = 6059504, upload-time = "2026-01-15T20:15:16.918Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/06/f71e3a86b2df0dfa2d2f72195941cd09b44f87711cb7fa5193732cb9a5fc/ruff-0.14.14.tar.gz", hash = "sha256:2d0f819c9a90205f3a867dbbd0be083bee9912e170fd7d9704cc8ae45824896b", size = 4515732, upload-time = "2026-01-22T22:30:17.527Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c3/ae/0deefbc65ca74b0ab1fd3917f94dc3b398233346a74b8bbb0a916a1a6bf6/ruff-0.14.13-py3-none-linux_armv6l.whl", hash = "sha256:76f62c62cd37c276cb03a275b198c7c15bd1d60c989f944db08a8c1c2dbec18b", size = 13062418, upload-time = "2026-01-15T20:14:50.779Z" }, - { url = "https://files.pythonhosted.org/packages/47/df/5916604faa530a97a3c154c62a81cb6b735c0cb05d1e26d5ad0f0c8ac48a/ruff-0.14.13-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:914a8023ece0528d5cc33f5a684f5f38199bbb566a04815c2c211d8f40b5d0ed", size = 13442344, upload-time = "2026-01-15T20:15:07.94Z" }, - { url = "https://files.pythonhosted.org/packages/4c/f3/e0e694dd69163c3a1671e102aa574a50357536f18a33375050334d5cd517/ruff-0.14.13-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d24899478c35ebfa730597a4a775d430ad0d5631b8647a3ab368c29b7e7bd063", size = 12354720, upload-time = "2026-01-15T20:15:09.854Z" }, - { url = "https://files.pythonhosted.org/packages/c3/e8/67f5fcbbaee25e8fc3b56cc33e9892eca7ffe09f773c8e5907757a7e3bdb/ruff-0.14.13-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9aaf3870f14d925bbaf18b8a2347ee0ae7d95a2e490e4d4aea6813ed15ebc80e", size = 12774493, upload-time = "2026-01-15T20:15:20.908Z" }, - { url = "https://files.pythonhosted.org/packages/6b/ce/d2e9cb510870b52a9565d885c0d7668cc050e30fa2c8ac3fb1fda15c083d/ruff-0.14.13-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac5b7f63dd3b27cc811850f5ffd8fff845b00ad70e60b043aabf8d6ecc304e09", size = 12815174, upload-time = "2026-01-15T20:15:05.74Z" }, - { url = "https://files.pythonhosted.org/packages/88/00/c38e5da58beebcf4fa32d0ddd993b63dfacefd02ab7922614231330845bf/ruff-0.14.13-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78d2b1097750d90ba82ce4ba676e85230a0ed694178ca5e61aa9b459970b3eb9", size = 13680909, upload-time = "2026-01-15T20:15:14.537Z" }, - { url = "https://files.pythonhosted.org/packages/61/61/cd37c9dd5bd0a3099ba79b2a5899ad417d8f3b04038810b0501a80814fd7/ruff-0.14.13-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:7d0bf87705acbbcb8d4c24b2d77fbb73d40210a95c3903b443cd9e30824a5032", size = 15144215, upload-time = "2026-01-15T20:15:22.886Z" }, - { url = "https://files.pythonhosted.org/packages/56/8a/85502d7edbf98c2df7b8876f316c0157359165e16cdf98507c65c8d07d3d/ruff-0.14.13-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3eb5da8e2c9e9f13431032fdcbe7681de9ceda5835efee3269417c13f1fed5c", size = 14706067, upload-time = "2026-01-15T20:14:48.271Z" }, - { url = "https://files.pythonhosted.org/packages/7e/2f/de0df127feb2ee8c1e54354dc1179b4a23798f0866019528c938ba439aca/ruff-0.14.13-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:642442b42957093811cd8d2140dfadd19c7417030a7a68cf8d51fcdd5f217427", size = 14133916, upload-time = "2026-01-15T20:14:57.357Z" }, - { url = "https://files.pythonhosted.org/packages/0d/77/9b99686bb9fe07a757c82f6f95e555c7a47801a9305576a9c67e0a31d280/ruff-0.14.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4acdf009f32b46f6e8864af19cbf6841eaaed8638e65c8dac845aea0d703c841", size = 13859207, upload-time = "2026-01-15T20:14:55.111Z" }, - { url = "https://files.pythonhosted.org/packages/7d/46/2bdcb34a87a179a4d23022d818c1c236cb40e477faf0d7c9afb6813e5876/ruff-0.14.13-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:591a7f68860ea4e003917d19b5c4f5ac39ff558f162dc753a2c5de897fd5502c", size = 14043686, upload-time = "2026-01-15T20:14:52.841Z" }, - { url = "https://files.pythonhosted.org/packages/1a/a9/5c6a4f56a0512c691cf143371bcf60505ed0f0860f24a85da8bd123b2bf1/ruff-0.14.13-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:774c77e841cc6e046fc3e91623ce0903d1cd07e3a36b1a9fe79b81dab3de506b", size = 12663837, upload-time = "2026-01-15T20:15:18.921Z" }, - { url = "https://files.pythonhosted.org/packages/fe/bb/b920016ece7651fa7fcd335d9d199306665486694d4361547ccb19394c44/ruff-0.14.13-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:61f4e40077a1248436772bb6512db5fc4457fe4c49e7a94ea7c5088655dd21ae", size = 12805867, upload-time = "2026-01-15T20:14:59.272Z" }, - { url = "https://files.pythonhosted.org/packages/7d/b3/0bd909851e5696cd21e32a8fc25727e5f58f1934b3596975503e6e85415c/ruff-0.14.13-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6d02f1428357fae9e98ac7aa94b7e966fd24151088510d32cf6f902d6c09235e", size = 13208528, upload-time = "2026-01-15T20:15:03.732Z" }, - { url = "https://files.pythonhosted.org/packages/3b/3b/e2d94cb613f6bbd5155a75cbe072813756363eba46a3f2177a1fcd0cd670/ruff-0.14.13-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e399341472ce15237be0c0ae5fbceca4b04cd9bebab1a2b2c979e015455d8f0c", size = 13929242, upload-time = "2026-01-15T20:15:11.918Z" }, - { url = "https://files.pythonhosted.org/packages/6a/c5/abd840d4132fd51a12f594934af5eba1d5d27298a6f5b5d6c3be45301caf/ruff-0.14.13-py3-none-win32.whl", hash = "sha256:ef720f529aec113968b45dfdb838ac8934e519711da53a0456038a0efecbd680", size = 12919024, upload-time = "2026-01-15T20:14:43.647Z" }, - { url = "https://files.pythonhosted.org/packages/c2/55/6384b0b8ce731b6e2ade2b5449bf07c0e4c31e8a2e68ea65b3bafadcecc5/ruff-0.14.13-py3-none-win_amd64.whl", hash = "sha256:6070bd026e409734b9257e03e3ef18c6e1a216f0435c6751d7a8ec69cb59abef", size = 14097887, upload-time = "2026-01-15T20:15:01.48Z" }, - { url = "https://files.pythonhosted.org/packages/4d/e1/7348090988095e4e39560cfc2f7555b1b2a7357deba19167b600fdf5215d/ruff-0.14.13-py3-none-win_arm64.whl", hash = "sha256:7ab819e14f1ad9fe39f246cfcc435880ef7a9390d81a2b6ac7e01039083dd247", size = 13080224, upload-time = "2026-01-15T20:14:45.853Z" }, + { url = "https://files.pythonhosted.org/packages/d2/89/20a12e97bc6b9f9f68343952da08a8099c57237aef953a56b82711d55edd/ruff-0.14.14-py3-none-linux_armv6l.whl", hash = "sha256:7cfe36b56e8489dee8fbc777c61959f60ec0f1f11817e8f2415f429552846aed", size = 10467650, upload-time = "2026-01-22T22:30:08.578Z" }, + { url = "https://files.pythonhosted.org/packages/a3/b1/c5de3fd2d5a831fcae21beda5e3589c0ba67eec8202e992388e4b17a6040/ruff-0.14.14-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6006a0082336e7920b9573ef8a7f52eec837add1265cc74e04ea8a4368cd704c", size = 10883245, upload-time = "2026-01-22T22:30:04.155Z" }, + { url = "https://files.pythonhosted.org/packages/b8/7c/3c1db59a10e7490f8f6f8559d1db8636cbb13dccebf18686f4e3c9d7c772/ruff-0.14.14-py3-none-macosx_11_0_arm64.whl", hash = "sha256:026c1d25996818f0bf498636686199d9bd0d9d6341c9c2c3b62e2a0198b758de", size = 10231273, upload-time = "2026-01-22T22:30:34.642Z" }, + { url = "https://files.pythonhosted.org/packages/a1/6e/5e0e0d9674be0f8581d1f5e0f0a04761203affce3232c1a1189d0e3b4dad/ruff-0.14.14-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f666445819d31210b71e0a6d1c01e24447a20b85458eea25a25fe8142210ae0e", size = 10585753, upload-time = "2026-01-22T22:30:31.781Z" }, + { url = "https://files.pythonhosted.org/packages/23/09/754ab09f46ff1884d422dc26d59ba18b4e5d355be147721bb2518aa2a014/ruff-0.14.14-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3c0f18b922c6d2ff9a5e6c3ee16259adc513ca775bcf82c67ebab7cbd9da5bc8", size = 10286052, upload-time = "2026-01-22T22:30:24.827Z" }, + { url = "https://files.pythonhosted.org/packages/c8/cc/e71f88dd2a12afb5f50733851729d6b571a7c3a35bfdb16c3035132675a0/ruff-0.14.14-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1629e67489c2dea43e8658c3dba659edbfd87361624b4040d1df04c9740ae906", size = 11043637, upload-time = "2026-01-22T22:30:13.239Z" }, + { url = "https://files.pythonhosted.org/packages/67/b2/397245026352494497dac935d7f00f1468c03a23a0c5db6ad8fc49ca3fb2/ruff-0.14.14-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:27493a2131ea0f899057d49d303e4292b2cae2bb57253c1ed1f256fbcd1da480", size = 12194761, upload-time = "2026-01-22T22:30:22.542Z" }, + { url = "https://files.pythonhosted.org/packages/5b/06/06ef271459f778323112c51b7587ce85230785cd64e91772034ddb88f200/ruff-0.14.14-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:01ff589aab3f5b539e35db38425da31a57521efd1e4ad1ae08fc34dbe30bd7df", size = 12005701, upload-time = "2026-01-22T22:30:20.499Z" }, + { url = "https://files.pythonhosted.org/packages/41/d6/99364514541cf811ccc5ac44362f88df66373e9fec1b9d1c4cc830593fe7/ruff-0.14.14-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1cc12d74eef0f29f51775f5b755913eb523546b88e2d733e1d701fe65144e89b", size = 11282455, upload-time = "2026-01-22T22:29:59.679Z" }, + { url = "https://files.pythonhosted.org/packages/ca/71/37daa46f89475f8582b7762ecd2722492df26421714a33e72ccc9a84d7a5/ruff-0.14.14-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb8481604b7a9e75eff53772496201690ce2687067e038b3cc31aaf16aa0b974", size = 11215882, upload-time = "2026-01-22T22:29:57.032Z" }, + { url = "https://files.pythonhosted.org/packages/2c/10/a31f86169ec91c0705e618443ee74ede0bdd94da0a57b28e72db68b2dbac/ruff-0.14.14-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:14649acb1cf7b5d2d283ebd2f58d56b75836ed8c6f329664fa91cdea19e76e66", size = 11180549, upload-time = "2026-01-22T22:30:27.175Z" }, + { url = "https://files.pythonhosted.org/packages/fd/1e/c723f20536b5163adf79bdd10c5f093414293cdf567eed9bdb7b83940f3f/ruff-0.14.14-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e8058d2145566510790eab4e2fad186002e288dec5e0d343a92fe7b0bc1b3e13", size = 10543416, upload-time = "2026-01-22T22:30:01.964Z" }, + { url = "https://files.pythonhosted.org/packages/3e/34/8a84cea7e42c2d94ba5bde1d7a4fae164d6318f13f933d92da6d7c2041ff/ruff-0.14.14-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e651e977a79e4c758eb807f0481d673a67ffe53cfa92209781dfa3a996cf8412", size = 10285491, upload-time = "2026-01-22T22:30:29.51Z" }, + { url = "https://files.pythonhosted.org/packages/55/ef/b7c5ea0be82518906c978e365e56a77f8de7678c8bb6651ccfbdc178c29f/ruff-0.14.14-py3-none-musllinux_1_2_i686.whl", hash = "sha256:cc8b22da8d9d6fdd844a68ae937e2a0adf9b16514e9a97cc60355e2d4b219fc3", size = 10733525, upload-time = "2026-01-22T22:30:06.499Z" }, + { url = "https://files.pythonhosted.org/packages/6a/5b/aaf1dfbcc53a2811f6cc0a1759de24e4b03e02ba8762daabd9b6bd8c59e3/ruff-0.14.14-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:16bc890fb4cc9781bb05beb5ab4cd51be9e7cb376bf1dd3580512b24eb3fda2b", size = 11315626, upload-time = "2026-01-22T22:30:36.848Z" }, + { url = "https://files.pythonhosted.org/packages/2c/aa/9f89c719c467dfaf8ad799b9bae0df494513fb21d31a6059cb5870e57e74/ruff-0.14.14-py3-none-win32.whl", hash = "sha256:b530c191970b143375b6a68e6f743800b2b786bbcf03a7965b06c4bf04568167", size = 10502442, upload-time = "2026-01-22T22:30:38.93Z" }, + { url = "https://files.pythonhosted.org/packages/87/44/90fa543014c45560cae1fffc63ea059fb3575ee6e1cb654562197e5d16fb/ruff-0.14.14-py3-none-win_amd64.whl", hash = "sha256:3dde1435e6b6fe5b66506c1dff67a421d0b7f6488d466f651c07f4cab3bf20fd", size = 11630486, upload-time = "2026-01-22T22:30:10.852Z" }, + { url = "https://files.pythonhosted.org/packages/9e/6a/40fee331a52339926a92e17ae748827270b288a35ef4a15c9c8f2ec54715/ruff-0.14.14-py3-none-win_arm64.whl", hash = "sha256:56e6981a98b13a32236a72a8da421d7839221fa308b223b9283312312e5ac76c", size = 10920448, upload-time = "2026-01-22T22:30:15.417Z" }, ] [[package]] @@ -4737,15 +4737,15 @@ wheels = [ [[package]] name = "sentry-sdk" -version = "2.49.0" +version = "2.50.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/02/94/23ac26616a883f492428d9ee9ad6eee391612125326b784dbfc30e1e7bab/sentry_sdk-2.49.0.tar.gz", hash = "sha256:c1878599cde410d481c04ef50ee3aedd4f600e4d0d253f4763041e468b332c30", size = 387228, upload-time = "2026-01-08T09:56:25.642Z" } +sdist = { url = "https://files.pythonhosted.org/packages/15/8a/3c4f53d32c21012e9870913544e56bfa9e931aede080779a0f177513f534/sentry_sdk-2.50.0.tar.gz", hash = "sha256:873437a989ee1b8b25579847bae8384515bf18cfed231b06c591b735c1781fe3", size = 401233, upload-time = "2026-01-20T12:53:16.244Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/43/1c586f9f413765201234541857cb82fda076f4b0f7bad4a0ec248da39cf3/sentry_sdk-2.49.0-py2.py3-none-any.whl", hash = "sha256:6ea78499133874445a20fe9c826c9e960070abeb7ae0cdf930314ab16bb97aa0", size = 415693, upload-time = "2026-01-08T09:56:21.872Z" }, + { url = "https://files.pythonhosted.org/packages/4e/5b/cbc2bb9569f03c8e15d928357e7e6179e5cfab45544a3bbac8aec4caf9be/sentry_sdk-2.50.0-py2.py3-none-any.whl", hash = "sha256:0ef0ed7168657ceb5a0be081f4102d92042a125462d1d1a29277992e344e749e", size = 424961, upload-time = "2026-01-20T12:53:14.826Z" }, ] [[package]] @@ -4781,11 +4781,11 @@ wheels = [ [[package]] name = "setuptools" -version = "80.9.0" +version = "80.10.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" } +sdist = { url = "https://files.pythonhosted.org/packages/76/95/faf61eb8363f26aa7e1d762267a8d602a1b26d4f3a1e758e92cb3cb8b054/setuptools-80.10.2.tar.gz", hash = "sha256:8b0e9d10c784bf7d262c4e5ec5d4ec94127ce206e8738f29a437945fbc219b70", size = 1200343, upload-time = "2026-01-25T22:38:17.252Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" }, + { url = "https://files.pythonhosted.org/packages/94/b8/f1f62a5e3c0ad2ff1d189590bfa4c46b4f3b6e49cef6f26c6ee4e575394d/setuptools-80.10.2-py3-none-any.whl", hash = "sha256:95b30ddfb717250edb492926c92b5221f7ef3fbcc2b07579bcd4a27da21d0173", size = 1064234, upload-time = "2026-01-25T22:38:15.216Z" }, ] [[package]] @@ -4864,17 +4864,18 @@ wheels = [ [[package]] name = "sounddevice" -version = "0.5.3" +version = "0.5.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4e/4f/28e734898b870db15b6474453f19813d3c81b91c806d9e6f867bd6e4dd03/sounddevice-0.5.3.tar.gz", hash = "sha256:cbac2b60198fbab84533697e7c4904cc895ec69d5fb3973556c9eb74a4629b2c", size = 53465, upload-time = "2025-10-19T13:23:57.922Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2a/f9/2592608737553638fca98e21e54bfec40bf577bb98a61b2770c912aab25e/sounddevice-0.5.5.tar.gz", hash = "sha256:22487b65198cb5bf2208755105b524f78ad173e5ab6b445bdab1c989f6698df3", size = 143191, upload-time = "2026-01-23T18:36:43.529Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/73/e7/9020e9f0f3df00432728f4c4044387468a743e3d9a4f91123d77be10010e/sounddevice-0.5.3-py3-none-any.whl", hash = "sha256:ea7738baa0a9f9fef7390f649e41c9f2c8ada776180e56c2ffd217133c92a806", size = 32670, upload-time = "2025-10-19T13:23:51.779Z" }, - { url = "https://files.pythonhosted.org/packages/2f/39/714118f8413e0e353436914f2b976665161f1be2b6483ac15a8f61484c14/sounddevice-0.5.3-py3-none-macosx_10_6_x86_64.macosx_10_6_universal2.whl", hash = "sha256:278dc4451fff70934a176df048b77d80d7ce1623a6ec9db8b34b806f3112f9c2", size = 108306, upload-time = "2025-10-19T13:23:53.277Z" }, - { url = "https://files.pythonhosted.org/packages/f5/74/52186e3e5c833d00273f7949a9383adff93692c6e02406bf359cb4d3e921/sounddevice-0.5.3-py3-none-win32.whl", hash = "sha256:845d6927bcf14e84be5292a61ab3359cf8e6b9145819ec6f3ac2619ff089a69c", size = 312882, upload-time = "2025-10-19T13:23:54.829Z" }, - { url = "https://files.pythonhosted.org/packages/66/c7/16123d054aef6d445176c9122bfbe73c11087589b2413cab22aff5a7839a/sounddevice-0.5.3-py3-none-win_amd64.whl", hash = "sha256:f55ad20082efc2bdec06928e974fbcae07bc6c405409ae1334cefe7d377eb687", size = 364025, upload-time = "2025-10-19T13:23:56.362Z" }, + { url = "https://files.pythonhosted.org/packages/1e/0a/478e441fd049002cf308520c0d62dd8333e7c6cc8d997f0dda07b9fbcc46/sounddevice-0.5.5-py3-none-any.whl", hash = "sha256:30ff99f6c107f49d25ad16a45cacd8d91c25a1bcdd3e81a206b921a3a6405b1f", size = 32807, upload-time = "2026-01-23T18:36:35.649Z" }, + { url = "https://files.pythonhosted.org/packages/56/f9/c037c35f6d0b6bc3bc7bfb314f1d6f1f9a341328ef47cd63fc4f850a7b27/sounddevice-0.5.5-py3-none-macosx_10_6_x86_64.macosx_10_6_universal2.whl", hash = "sha256:05eb9fd6c54c38d67741441c19164c0dae8ce80453af2d8c4ad2e7823d15b722", size = 108557, upload-time = "2026-01-23T18:36:37.41Z" }, + { url = "https://files.pythonhosted.org/packages/88/a1/d19dd9889cd4bce2e233c4fac007cd8daaf5b9fe6e6a5d432cf17be0b807/sounddevice-0.5.5-py3-none-win32.whl", hash = "sha256:1234cc9b4c9df97b6cbe748146ae0ec64dd7d6e44739e8e42eaa5b595313a103", size = 317765, upload-time = "2026-01-23T18:36:39.047Z" }, + { url = "https://files.pythonhosted.org/packages/c3/0e/002ed7c4c1c2ab69031f78989d3b789fee3a7fba9e586eb2b81688bf4961/sounddevice-0.5.5-py3-none-win_amd64.whl", hash = "sha256:cfc6b2c49fb7f555591c78cb8ecf48d6a637fd5b6e1db5fec6ed9365d64b3519", size = 365324, upload-time = "2026-01-23T18:36:40.496Z" }, + { url = "https://files.pythonhosted.org/packages/4e/39/a61d4b83a7746b70d23d9173be688c0c6bfc7173772344b7442c2c155497/sounddevice-0.5.5-py3-none-win_arm64.whl", hash = "sha256:3861901ddd8230d2e0e8ae62ac320cdd4c688d81df89da036dcb812f757bb3e6", size = 317115, upload-time = "2026-01-23T18:36:42.235Z" }, ] [[package]] @@ -4918,27 +4919,26 @@ wheels = [ [[package]] name = "ty" -version = "0.0.12" +version = "0.0.13" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b5/78/ba1a4ad403c748fbba8be63b7e774a90e80b67192f6443d624c64fe4aaab/ty-0.0.12.tar.gz", hash = "sha256:cd01810e106c3b652a01b8f784dd21741de9fdc47bd595d02c122a7d5cefeee7", size = 4981303, upload-time = "2026-01-14T22:30:48.537Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/dc/b607f00916f5a7c52860b84a66dc17bc6988e8445e96b1d6e175a3837397/ty-0.0.13.tar.gz", hash = "sha256:7a1d135a400ca076407ea30012d1f75419634160ed3b9cad96607bf2956b23b3", size = 4999183, upload-time = "2026-01-21T13:21:16.133Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7d/8f/c21314d074dda5fb13d3300fa6733fd0d8ff23ea83a721818740665b6314/ty-0.0.12-py3-none-linux_armv6l.whl", hash = "sha256:eb9da1e2c68bd754e090eab39ed65edf95168d36cbeb43ff2bd9f86b4edd56d1", size = 9614164, upload-time = "2026-01-14T22:30:44.016Z" }, - { url = "https://files.pythonhosted.org/packages/09/28/f8a4d944d13519d70c486e8f96d6fa95647ac2aa94432e97d5cfec1f42f6/ty-0.0.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:c181f42aa19b0ed7f1b0c2d559980b1f1d77cc09419f51c8321c7ddf67758853", size = 9542337, upload-time = "2026-01-14T22:30:05.687Z" }, - { url = "https://files.pythonhosted.org/packages/e1/9c/f576e360441de7a8201daa6dc4ebc362853bc5305e059cceeb02ebdd9a48/ty-0.0.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1f829e1eecd39c3e1b032149db7ae6a3284f72fc36b42436e65243a9ed1173db", size = 8909582, upload-time = "2026-01-14T22:30:46.089Z" }, - { url = "https://files.pythonhosted.org/packages/d6/13/0898e494032a5d8af3060733d12929e3e7716db6c75eac63fa125730a3e7/ty-0.0.12-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f45162e7826e1789cf3374627883cdeb0d56b82473a0771923e4572928e90be3", size = 9384932, upload-time = "2026-01-14T22:30:13.769Z" }, - { url = "https://files.pythonhosted.org/packages/e4/1a/b35b6c697008a11d4cedfd34d9672db2f0a0621ec80ece109e13fca4dfef/ty-0.0.12-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d11fec40b269bec01e751b2337d1c7ffa959a2c2090a950d7e21c2792442cccd", size = 9453140, upload-time = "2026-01-14T22:30:11.131Z" }, - { url = "https://files.pythonhosted.org/packages/dd/1e/71c9edbc79a3c88a0711324458f29c7dbf6c23452c6e760dc25725483064/ty-0.0.12-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09d99e37e761a4d2651ad9d5a610d11235fbcbf35dc6d4bc04abf54e7cf894f1", size = 9960680, upload-time = "2026-01-14T22:30:33.621Z" }, - { url = "https://files.pythonhosted.org/packages/0e/75/39375129f62dd22f6ad5a99cd2a42fd27d8b91b235ce2db86875cdad397d/ty-0.0.12-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:d9ca0cdb17bd37397da7b16a7cd23423fc65c3f9691e453ad46c723d121225a1", size = 10904518, upload-time = "2026-01-14T22:30:08.464Z" }, - { url = "https://files.pythonhosted.org/packages/32/5e/26c6d88fafa11a9d31ca9f4d12989f57782ec61e7291d4802d685b5be118/ty-0.0.12-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcf2757b905e7eddb7e456140066335b18eb68b634a9f72d6f54a427ab042c64", size = 10525001, upload-time = "2026-01-14T22:30:16.454Z" }, - { url = "https://files.pythonhosted.org/packages/c2/a5/2f0b91894af13187110f9ad7ee926d86e4e6efa755c9c88a820ed7f84c85/ty-0.0.12-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:00cf34c1ebe1147efeda3021a1064baa222c18cdac114b7b050bbe42deb4ca80", size = 10307103, upload-time = "2026-01-14T22:30:41.221Z" }, - { url = "https://files.pythonhosted.org/packages/4b/77/13d0410827e4bc713ebb7fdaf6b3590b37dcb1b82e0a81717b65548f2442/ty-0.0.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb3a655bd869352e9a22938d707631ac9fbca1016242b1f6d132d78f347c851", size = 10072737, upload-time = "2026-01-14T22:30:51.783Z" }, - { url = "https://files.pythonhosted.org/packages/e1/dd/fc36d8bac806c74cf04b4ca735bca14d19967ca84d88f31e121767880df1/ty-0.0.12-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:4658e282c7cb82be304052f8f64f9925f23c3c4f90eeeb32663c74c4b095d7ba", size = 9368726, upload-time = "2026-01-14T22:30:18.683Z" }, - { url = "https://files.pythonhosted.org/packages/54/70/9e8e461647550f83e2fe54bc632ccbdc17a4909644783cdbdd17f7296059/ty-0.0.12-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:c167d838eaaa06e03bb66a517f75296b643d950fbd93c1d1686a187e5a8dbd1f", size = 9454704, upload-time = "2026-01-14T22:30:22.759Z" }, - { url = "https://files.pythonhosted.org/packages/04/9b/6292cf7c14a0efeca0539cf7d78f453beff0475cb039fbea0eb5d07d343d/ty-0.0.12-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2956e0c9ab7023533b461d8a0e6b2ea7b78e01a8dde0688e8234d0fce10c4c1c", size = 9649829, upload-time = "2026-01-14T22:30:31.234Z" }, - { url = "https://files.pythonhosted.org/packages/49/bd/472a5d2013371e4870886cff791c94abdf0b92d43d305dd0f8e06b6ff719/ty-0.0.12-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5c6a3fd7479580009f21002f3828320621d8a82d53b7ba36993234e3ccad58c8", size = 10162814, upload-time = "2026-01-14T22:30:36.174Z" }, - { url = "https://files.pythonhosted.org/packages/31/e9/2ecbe56826759845a7c21d80aa28187865ea62bc9757b056f6cbc06f78ed/ty-0.0.12-py3-none-win32.whl", hash = "sha256:a91c24fd75c0f1796d8ede9083e2c0ec96f106dbda73a09fe3135e075d31f742", size = 9140115, upload-time = "2026-01-14T22:30:38.903Z" }, - { url = "https://files.pythonhosted.org/packages/5d/6d/d9531eff35a5c0ec9dbc10231fac21f9dd6504814048e81d6ce1c84dc566/ty-0.0.12-py3-none-win_amd64.whl", hash = "sha256:df151894be55c22d47068b0f3b484aff9e638761e2267e115d515fcc9c5b4a4b", size = 9884532, upload-time = "2026-01-14T22:30:25.112Z" }, - { url = "https://files.pythonhosted.org/packages/e9/f3/20b49e75967023b123a221134548ad7000f9429f13fdcdda115b4c26305f/ty-0.0.12-py3-none-win_arm64.whl", hash = "sha256:cea99d334b05629de937ce52f43278acf155d3a316ad6a35356635f886be20ea", size = 9313974, upload-time = "2026-01-14T22:30:27.44Z" }, + { url = "https://files.pythonhosted.org/packages/1a/df/3632f1918f4c0a33184f107efc5d436ab6da147fd3d3b94b3af6461efbf4/ty-0.0.13-py3-none-linux_armv6l.whl", hash = "sha256:1b2b8e02697c3a94c722957d712a0615bcc317c9b9497be116ef746615d892f2", size = 9993501, upload-time = "2026-01-21T13:21:26.628Z" }, + { url = "https://files.pythonhosted.org/packages/92/87/6a473ced5ac280c6ce5b1627c71a8a695c64481b99aabc798718376a441e/ty-0.0.13-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:f15cdb8e233e2b5adfce673bb21f4c5e8eaf3334842f7eea3c70ac6fda8c1de5", size = 9860986, upload-time = "2026-01-21T13:21:24.425Z" }, + { url = "https://files.pythonhosted.org/packages/5d/9b/d89ae375cf0a7cd9360e1164ce017f8c753759be63b6a11ed4c944abe8c6/ty-0.0.13-py3-none-macosx_11_0_arm64.whl", hash = "sha256:0819e89ac9f0d8af7a062837ce197f0461fee2fc14fd07e2c368780d3a397b73", size = 9350748, upload-time = "2026-01-21T13:21:28.502Z" }, + { url = "https://files.pythonhosted.org/packages/a8/a6/9ad58518056fab344b20c0bb2c1911936ebe195318e8acc3bc45ac1c6b6b/ty-0.0.13-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1de79f481084b7cc7a202ba0d7a75e10970d10ffa4f025b23f2e6b7324b74886", size = 9849884, upload-time = "2026-01-21T13:21:21.886Z" }, + { url = "https://files.pythonhosted.org/packages/b1/c3/8add69095fa179f523d9e9afcc15a00818af0a37f2b237a9b59bc0046c34/ty-0.0.13-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4fb2154cff7c6e95d46bfaba283c60642616f20d73e5f96d0c89c269f3e1bcec", size = 9822975, upload-time = "2026-01-21T13:21:14.292Z" }, + { url = "https://files.pythonhosted.org/packages/a4/05/4c0927c68a0a6d43fb02f3f0b6c19c64e3461dc8ed6c404dde0efb8058f7/ty-0.0.13-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00be58d89337c27968a20d58ca553458608c5b634170e2bec82824c2e4cf4d96", size = 10294045, upload-time = "2026-01-21T13:21:30.505Z" }, + { url = "https://files.pythonhosted.org/packages/b4/86/6dc190838aba967557fe0bfd494c595d00b5081315a98aaf60c0e632aaeb/ty-0.0.13-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:72435eade1fa58c6218abb4340f43a6c3ff856ae2dc5722a247d3a6dd32e9737", size = 10916460, upload-time = "2026-01-21T13:21:07.788Z" }, + { url = "https://files.pythonhosted.org/packages/04/40/9ead96b7c122e1109dfcd11671184c3506996bf6a649306ec427e81d9544/ty-0.0.13-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:77a548742ee8f621d718159e7027c3b555051d096a49bb580249a6c5fc86c271", size = 10597154, upload-time = "2026-01-21T13:21:18.064Z" }, + { url = "https://files.pythonhosted.org/packages/aa/7d/e832a2c081d2be845dc6972d0c7998914d168ccbc0b9c86794419ab7376e/ty-0.0.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da067c57c289b7cf914669704b552b6207c2cc7f50da4118c3e12388642e6b3f", size = 10410710, upload-time = "2026-01-21T13:21:12.388Z" }, + { url = "https://files.pythonhosted.org/packages/31/e3/898be3a96237a32f05c4c29b43594dc3b46e0eedfe8243058e46153b324f/ty-0.0.13-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:d1b50a01fffa140417fca5a24b658fbe0734074a095d5b6f0552484724474343", size = 9826299, upload-time = "2026-01-21T13:21:00.845Z" }, + { url = "https://files.pythonhosted.org/packages/bb/eb/db2d852ce0ed742505ff18ee10d7d252f3acfd6fc60eca7e9c7a0288a6d8/ty-0.0.13-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0f33c46f52e5e9378378eca0d8059f026f3c8073ace02f7f2e8d079ddfe5207e", size = 9831610, upload-time = "2026-01-21T13:21:05.842Z" }, + { url = "https://files.pythonhosted.org/packages/9e/61/149f59c8abaddcbcbb0bd13b89c7741ae1c637823c5cf92ed2c644fcadef/ty-0.0.13-py3-none-musllinux_1_2_i686.whl", hash = "sha256:168eda24d9a0b202cf3758c2962cc295878842042b7eca9ed2965259f59ce9f2", size = 9978885, upload-time = "2026-01-21T13:21:10.306Z" }, + { url = "https://files.pythonhosted.org/packages/a0/cd/026d4e4af60a80918a8d73d2c42b8262dd43ab2fa7b28d9743004cb88d57/ty-0.0.13-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:d4917678b95dc8cb399cc459fab568ba8d5f0f33b7a94bf840d9733043c43f29", size = 10506453, upload-time = "2026-01-21T13:20:56.633Z" }, + { url = "https://files.pythonhosted.org/packages/63/06/8932833a4eca2df49c997a29afb26721612de8078ae79074c8fe87e17516/ty-0.0.13-py3-none-win32.whl", hash = "sha256:c1f2ec40daa405508b053e5b8e440fbae5fdb85c69c9ab0ee078f8bc00eeec3d", size = 9433482, upload-time = "2026-01-21T13:20:58.717Z" }, + { url = "https://files.pythonhosted.org/packages/aa/fd/e8d972d1a69df25c2cecb20ea50e49ad5f27a06f55f1f5f399a563e71645/ty-0.0.13-py3-none-win_amd64.whl", hash = "sha256:8b7b1ab9f187affbceff89d51076038363b14113be29bda2ddfa17116de1d476", size = 10319156, upload-time = "2026-01-21T13:21:03.266Z" }, + { url = "https://files.pythonhosted.org/packages/2d/c2/05fdd64ac003a560d4fbd1faa7d9a31d75df8f901675e5bed1ee2ceeff87/ty-0.0.13-py3-none-win_arm64.whl", hash = "sha256:1c9630333497c77bb9bcabba42971b96ee1f36c601dd3dcac66b4134f9fa38f0", size = 9808316, upload-time = "2026-01-21T13:20:54.053Z" }, ] [[package]] From d76f756f42a4af121d64d46611ecb93936081760 Mon Sep 17 00:00:00 2001 From: felsager <76905857+felsager@users.noreply.github.com> Date: Mon, 26 Jan 2026 15:02:57 -0800 Subject: [PATCH 779/910] long_mpc: simplify longitudinal planner by removing "modes" (#37014) --- .../lib/longitudinal_mpc_lib/long_mpc.py | 125 +++++------------- .../controls/lib/longitudinal_planner.py | 32 ++--- .../controls/tests/test_following_distance.py | 7 +- 3 files changed, 47 insertions(+), 117 deletions(-) diff --git a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py index 3f9d8245b..9408132c5 100755 --- a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py +++ b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py @@ -35,7 +35,7 @@ X_EGO_OBSTACLE_COST = 3. X_EGO_COST = 0. V_EGO_COST = 0. A_EGO_COST = 0. -J_EGO_COST = 5.0 +J_EGO_COST = 5. A_CHANGE_COST = 200. DANGER_ZONE_COST = 100. CRASH_DISTANCE = .25 @@ -43,7 +43,6 @@ LEAD_DANGER_FACTOR = 0.75 LIMIT_COST = 1e6 ACADOS_SOLVER_TYPE = 'SQP_RTI' - # Fewer timestamps don't hurt performance and lead to # much better convergence of the MPC with low iterations N = 12 @@ -57,6 +56,7 @@ COMFORT_BRAKE = 2.5 STOP_DISTANCE = 6.0 CRUISE_MIN_ACCEL = -1.2 CRUISE_MAX_ACCEL = 1.6 +MIN_X_LEAD_FACTOR = 0.5 def get_jerk_factor(personality=log.LongitudinalPersonality.standard): if personality==log.LongitudinalPersonality.relaxed: @@ -85,20 +85,12 @@ def get_stopped_equivalence_factor(v_lead): def get_safe_obstacle_distance(v_ego, t_follow): return (v_ego**2) / (2 * COMFORT_BRAKE) + t_follow * v_ego + STOP_DISTANCE -def desired_follow_distance(v_ego, v_lead, t_follow=None): - if t_follow is None: - t_follow = get_T_FOLLOW() - return get_safe_obstacle_distance(v_ego, t_follow) - get_stopped_equivalence_factor(v_lead) - - def gen_long_model(): model = AcadosModel() model.name = MODEL_NAME - # set up states & controls - x_ego = SX.sym('x_ego') - v_ego = SX.sym('v_ego') - a_ego = SX.sym('a_ego') + # states + x_ego, v_ego, a_ego = SX.sym('x_ego'), SX.sym('v_ego'), SX.sym('a_ego') model.x = vertcat(x_ego, v_ego, a_ego) # controls @@ -126,7 +118,6 @@ def gen_long_model(): model.f_expl_expr = f_expl return model - def gen_long_ocp(): ocp = AcadosOcp() ocp.model = gen_long_model() @@ -222,30 +213,31 @@ def gen_long_ocp(): class LongitudinalMpc: - def __init__(self, mode='acc', dt=DT_MDL): - self.mode = mode + def __init__(self, dt=DT_MDL): self.dt = dt self.solver = AcadosOcpSolverCython(MODEL_NAME, ACADOS_SOLVER_TYPE, N) self.reset() self.source = SOURCES[2] def reset(self): - # self.solver = AcadosOcpSolverCython(MODEL_NAME, ACADOS_SOLVER_TYPE, N) self.solver.reset() - # self.solver.options_set('print_level', 2) + + self.x_sol = np.zeros((N+1, X_DIM)) + self.u_sol = np.zeros((N, 1)) self.v_solution = np.zeros(N+1) self.a_solution = np.zeros(N+1) - self.prev_a = np.array(self.a_solution) self.j_solution = np.zeros(N) + self.prev_a = np.array(self.a_solution) self.yref = np.zeros((N+1, COST_DIM)) + for i in range(N): self.solver.cost_set(i, "yref", self.yref[i]) self.solver.cost_set(N, "yref", self.yref[N][:COST_E_DIM]) - self.x_sol = np.zeros((N+1, X_DIM)) - self.u_sol = np.zeros((N,1)) + self.params = np.zeros((N+1, PARAM_DIM)) for i in range(N+1): self.solver.set(i, 'x', np.zeros(X_DIM)) + self.last_cloudlog_t = 0 self.status = False self.crash_cnt = 0.0 @@ -276,16 +268,9 @@ class LongitudinalMpc: def set_weights(self, prev_accel_constraint=True, personality=log.LongitudinalPersonality.standard): jerk_factor = get_jerk_factor(personality) - if self.mode == 'acc': - a_change_cost = A_CHANGE_COST if prev_accel_constraint else 0 - cost_weights = [X_EGO_OBSTACLE_COST, X_EGO_COST, V_EGO_COST, A_EGO_COST, jerk_factor * a_change_cost, jerk_factor * J_EGO_COST] - constraint_cost_weights = [LIMIT_COST, LIMIT_COST, LIMIT_COST, DANGER_ZONE_COST] - elif self.mode == 'blended': - a_change_cost = 40.0 if prev_accel_constraint else 0 - cost_weights = [0., 0.1, 0.2, 5.0, a_change_cost, 1.0] - constraint_cost_weights = [LIMIT_COST, LIMIT_COST, LIMIT_COST, DANGER_ZONE_COST] - else: - raise NotImplementedError(f'Planner mode {self.mode} not recognized in planner cost set') + a_change_cost = A_CHANGE_COST if prev_accel_constraint else 0 + cost_weights = [X_EGO_OBSTACLE_COST, X_EGO_COST, V_EGO_COST, A_EGO_COST, jerk_factor * a_change_cost, jerk_factor * J_EGO_COST] + constraint_cost_weights = [LIMIT_COST, LIMIT_COST, LIMIT_COST, DANGER_ZONE_COST] self.set_cost_weights(cost_weights, constraint_cost_weights) def set_cur_state(self, v, a): @@ -320,14 +305,14 @@ class LongitudinalMpc: # MPC will not converge if immediate crash is expected # Clip lead distance to what is still possible to brake for - min_x_lead = ((v_ego + v_lead)/2) * (v_ego - v_lead) / (-ACCEL_MIN * 2) + min_x_lead = MIN_X_LEAD_FACTOR * (v_ego + v_lead) * (v_ego - v_lead) / (-ACCEL_MIN * 2) x_lead = np.clip(x_lead, min_x_lead, 1e8) v_lead = np.clip(v_lead, 0.0, 1e8) a_lead = np.clip(a_lead, -10., 5.) lead_xv = self.extrapolate_lead(x_lead, v_lead, a_lead, a_lead_tau) return lead_xv - def update(self, radarstate, v_cruise, x, v, a, j, personality=log.LongitudinalPersonality.standard): + def update(self, radarstate, v_cruise, personality=log.LongitudinalPersonality.standard): t_follow = get_T_FOLLOW(personality) v_ego = self.x0[1] self.status = radarstate.leadOne.status or radarstate.leadTwo.status @@ -341,56 +326,28 @@ class LongitudinalMpc: lead_0_obstacle = lead_xv_0[:,0] + get_stopped_equivalence_factor(lead_xv_0[:,1]) lead_1_obstacle = lead_xv_1[:,0] + get_stopped_equivalence_factor(lead_xv_1[:,1]) - self.params[:,0] = ACCEL_MIN - self.params[:,1] = ACCEL_MAX + # Fake an obstacle for cruise, this ensures smooth acceleration to set speed + # when the leads are no factor. + v_lower = v_ego + (T_IDXS * CRUISE_MIN_ACCEL * 1.05) + # TODO does this make sense when max_a is negative? + v_upper = v_ego + (T_IDXS * CRUISE_MAX_ACCEL * 1.05) + v_cruise_clipped = np.clip(v_cruise * np.ones(N+1), v_lower, v_upper) + cruise_obstacle = np.cumsum(T_DIFFS * v_cruise_clipped) + get_safe_obstacle_distance(v_cruise_clipped, t_follow) - # Update in ACC mode or ACC/e2e blend - if self.mode == 'acc': - self.params[:,5] = LEAD_DANGER_FACTOR + x_obstacles = np.column_stack([lead_0_obstacle, lead_1_obstacle, cruise_obstacle]) + self.source = SOURCES[np.argmin(x_obstacles[0])] - # Fake an obstacle for cruise, this ensures smooth acceleration to set speed - # when the leads are no factor. - v_lower = v_ego + (T_IDXS * CRUISE_MIN_ACCEL * 1.05) - # TODO does this make sense when max_a is negative? - v_upper = v_ego + (T_IDXS * CRUISE_MAX_ACCEL * 1.05) - v_cruise_clipped = np.clip(v_cruise * np.ones(N+1), - v_lower, - v_upper) - cruise_obstacle = np.cumsum(T_DIFFS * v_cruise_clipped) + get_safe_obstacle_distance(v_cruise_clipped, t_follow) - x_obstacles = np.column_stack([lead_0_obstacle, lead_1_obstacle, cruise_obstacle]) - self.source = SOURCES[np.argmin(x_obstacles[0])] - - # These are not used in ACC mode - x[:], v[:], a[:], j[:] = 0.0, 0.0, 0.0, 0.0 - - elif self.mode == 'blended': - self.params[:,5] = 1.0 - - x_obstacles = np.column_stack([lead_0_obstacle, - lead_1_obstacle]) - cruise_target = T_IDXS * np.clip(v_cruise, v_ego - 2.0, 1e3) + x[0] - xforward = ((v[1:] + v[:-1]) / 2) * (T_IDXS[1:] - T_IDXS[:-1]) - x = np.cumsum(np.insert(xforward, 0, x[0])) - - x_and_cruise = np.column_stack([x, cruise_target]) - x = np.min(x_and_cruise, axis=1) - - self.source = 'e2e' if x_and_cruise[1,0] < x_and_cruise[1,1] else 'cruise' - - else: - raise NotImplementedError(f'Planner mode {self.mode} not recognized in planner update') - - self.yref[:,1] = x - self.yref[:,2] = v - self.yref[:,3] = a - self.yref[:,5] = j + self.yref[:,:] = 0.0 for i in range(N): self.solver.set(i, "yref", self.yref[i]) self.solver.set(N, "yref", self.yref[N][:COST_E_DIM]) + self.params[:,0] = ACCEL_MIN + self.params[:,1] = ACCEL_MAX self.params[:,2] = np.min(x_obstacles, axis=1) self.params[:,3] = np.copy(self.prev_a) self.params[:,4] = t_follow + self.params[:,5] = LEAD_DANGER_FACTOR self.run() if (np.any(lead_xv_0[FCW_IDXS,0] - self.x_sol[FCW_IDXS,0] < CRASH_DISTANCE) and @@ -399,18 +356,7 @@ class LongitudinalMpc: else: self.crash_cnt = 0 - # Check if it got within lead comfort range - # TODO This should be done cleaner - if self.mode == 'blended': - if any((lead_0_obstacle - get_safe_obstacle_distance(self.x_sol[:,1], t_follow))- self.x_sol[:,0] < 0.0): - self.source = 'lead0' - if any((lead_1_obstacle - get_safe_obstacle_distance(self.x_sol[:,1], t_follow))- self.x_sol[:,0] < 0.0) and \ - (lead_1_obstacle[0] - lead_0_obstacle[0]): - self.source = 'lead1' - def run(self): - # t0 = time.monotonic() - # reset = 0 for i in range(N+1): self.solver.set(i, 'p', self.params[i]) self.solver.constraints_set(0, "lbx", self.x0) @@ -422,13 +368,6 @@ class LongitudinalMpc: self.time_linearization = float(self.solver.get_stats('time_lin')[0]) self.time_integrator = float(self.solver.get_stats('time_sim')[0]) - # qp_iter = self.solver.get_stats('statistics')[-1][-1] # SQP_RTI specific - # print(f"long_mpc timings: tot {self.solve_time:.2e}, qp {self.time_qp_solution:.2e}, lin {self.time_linearization:.2e}, \ - # integrator {self.time_integrator:.2e}, qp_iter {qp_iter}") - # res = self.solver.get_residuals() - # print(f"long_mpc residuals: {res[0]:.2e}, {res[1]:.2e}, {res[2]:.2e}, {res[3]:.2e}") - # self.solver.print_statistics() - for i in range(N+1): self.x_sol[i] = self.solver.get(i, 'x') for i in range(N): @@ -446,12 +385,8 @@ class LongitudinalMpc: self.last_cloudlog_t = t cloudlog.warning(f"Long mpc reset, solution_status: {self.solution_status}") self.reset() - # reset = 1 - # print(f"long_mpc timings: total internal {self.solve_time:.2e}, external: {(time.monotonic() - t0):.2e} qp {self.time_qp_solution:.2e}, \ - # lin {self.time_linearization:.2e} qp_iter {qp_iter}, reset {reset}") if __name__ == "__main__": ocp = gen_long_ocp() AcadosOcpSolver.generate(ocp, json_file=JSON_FILE) - # AcadosOcpSolver.build(ocp.code_export_directory, with_cython=True) diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index 34fc85f8a..ad84ecf24 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -9,13 +9,12 @@ from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.realtime import DT_MDL from openpilot.selfdrive.modeld.constants import ModelConstants from openpilot.selfdrive.controls.lib.longcontrol import LongCtrlState -from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import LongitudinalMpc +from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import LongitudinalMpc, SOURCES from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import T_IDXS as T_IDXS_MPC from openpilot.selfdrive.controls.lib.drive_helpers import CONTROL_N, get_accel_from_plan from openpilot.selfdrive.car.cruise import V_CRUISE_MAX, V_CRUISE_UNSET from openpilot.common.swaglog import cloudlog -LON_MPC_STEP = 0.2 # first step is 0.2s A_CRUISE_MAX_VALS = [1.6, 1.2, 0.8, 0.6] A_CRUISE_MAX_BP = [0., 10.0, 25., 40.] CONTROL_N_T_IDX = ModelConstants.T_IDXS[:CONTROL_N] @@ -26,14 +25,12 @@ MIN_ALLOW_THROTTLE_SPEED = 2.5 _A_TOTAL_MAX_V = [1.7, 3.2] _A_TOTAL_MAX_BP = [20., 40.] - def get_max_accel(v_ego): return np.interp(v_ego, A_CRUISE_MAX_BP, A_CRUISE_MAX_VALS) def get_coast_accel(pitch): return np.sin(pitch) * -5.65 - 0.3 # fitted from data using xx/projects/allow_throttle/compute_coast_accel.py - def limit_accel_in_turns(v_ego, angle_steers, a_target, CP): """ This function returns a limited long acceleration allowed, depending on the existing lateral acceleration @@ -52,8 +49,6 @@ class LongitudinalPlanner: def __init__(self, CP, init_v=0.0, init_a=0.0, dt=DT_MDL): self.CP = CP self.mpc = LongitudinalMpc(dt=dt) - # TODO remove mpc modes when TR released - self.mpc.mode = 'acc' self.fcw = False self.dt = dt self.allow_throttle = True @@ -67,7 +62,6 @@ class LongitudinalPlanner: self.v_desired_trajectory = np.zeros(CONTROL_N) self.a_desired_trajectory = np.zeros(CONTROL_N) self.j_desired_trajectory = np.zeros(CONTROL_N) - self.solverExecutionTime = 0.0 @staticmethod def parse_model(model_msg): @@ -90,8 +84,6 @@ class LongitudinalPlanner: return x, v, a, j, throttle_prob def update(self, sm): - mode = 'blended' if sm['selfdriveState'].experimentalMode else 'acc' - if len(sm['carControl'].orientationNED) == 3: accel_coast = get_coast_accel(sm['carControl'].orientationNED[1]) else: @@ -113,12 +105,9 @@ class LongitudinalPlanner: # No change cost when user is controlling the speed, or when standstill prev_accel_constraint = not (reset_state or sm['carState'].standstill) - if mode == 'acc': - accel_clip = [ACCEL_MIN, get_max_accel(v_ego)] - steer_angle_without_offset = sm['carState'].steeringAngleDeg - sm['liveParameters'].angleOffsetDeg - accel_clip = limit_accel_in_turns(v_ego, steer_angle_without_offset, accel_clip, self.CP) - else: - accel_clip = [ACCEL_MIN, ACCEL_MAX] + accel_clip = [ACCEL_MIN, get_max_accel(v_ego)] + steer_angle_without_offset = sm['carState'].steeringAngleDeg - sm['liveParameters'].angleOffsetDeg + accel_clip = limit_accel_in_turns(v_ego, steer_angle_without_offset, accel_clip, self.CP) if reset_state: self.v_desired_filter.x = v_ego @@ -127,7 +116,7 @@ class LongitudinalPlanner: # Prevent divergence, smooth in current v_ego self.v_desired_filter.x = max(0.0, self.v_desired_filter.update(v_ego)) - x, v, a, j, throttle_prob = self.parse_model(sm['modelV2']) + _, _, _, _, throttle_prob = self.parse_model(sm['modelV2']) # Don't clip at low speeds since throttle_prob doesn't account for creep self.allow_throttle = throttle_prob > ALLOW_THROTTLE_THRESHOLD or v_ego <= MIN_ALLOW_THROTTLE_SPEED @@ -141,7 +130,7 @@ class LongitudinalPlanner: self.mpc.set_weights(prev_accel_constraint, personality=sm['selfdriveState'].personality) self.mpc.set_cur_state(self.v_desired_filter.x, self.a_desired) - self.mpc.update(sm['radarState'], v_cruise, x, v, a, j, personality=sm['selfdriveState'].personality) + self.mpc.update(sm['radarState'], v_cruise, personality=sm['selfdriveState'].personality) self.v_desired_trajectory = np.interp(CONTROL_N_T_IDX, T_IDXS_MPC, self.mpc.v_solution) self.a_desired_trajectory = np.interp(CONTROL_N_T_IDX, T_IDXS_MPC, self.mpc.a_solution) @@ -163,12 +152,13 @@ class LongitudinalPlanner: output_a_target_e2e = sm['modelV2'].action.desiredAcceleration output_should_stop_e2e = sm['modelV2'].action.shouldStop - if mode == 'acc': + if (output_a_target_e2e < output_a_target_mpc) and sm['selfdriveState'].experimentalMode: + output_a_target = output_a_target_e2e + self.output_should_stop = output_should_stop_e2e + self.mpc.source = SOURCES[3] + else: output_a_target = output_a_target_mpc self.output_should_stop = output_should_stop_mpc - else: - output_a_target = min(output_a_target_mpc, output_a_target_e2e) - self.output_should_stop = output_should_stop_e2e or output_should_stop_mpc for idx in range(2): accel_clip[idx] = np.clip(accel_clip[idx], self.prev_accel_clip[idx] - 0.05, self.prev_accel_clip[idx] + 0.05) diff --git a/selfdrive/controls/tests/test_following_distance.py b/selfdrive/controls/tests/test_following_distance.py index 0fd543dd6..8f66d89bf 100644 --- a/selfdrive/controls/tests/test_following_distance.py +++ b/selfdrive/controls/tests/test_following_distance.py @@ -4,10 +4,15 @@ from parameterized import parameterized_class from cereal import log -from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import desired_follow_distance, get_T_FOLLOW +from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import get_safe_obstacle_distance, get_stopped_equivalence_factor, get_T_FOLLOW from openpilot.selfdrive.test.longitudinal_maneuvers.maneuver import Maneuver +def desired_follow_distance(v_ego, v_lead, t_follow=None): + if t_follow is None: + t_follow = get_T_FOLLOW() + return get_safe_obstacle_distance(v_ego, t_follow) - get_stopped_equivalence_factor(v_lead) + def run_following_distance_simulation(v_lead, t_end=100.0, e2e=False, personality=0): man = Maneuver( '', From 97329e46ae11b92cf7f82fed485e5e141be6dfe2 Mon Sep 17 00:00:00 2001 From: felsager <76905857+felsager@users.noreply.github.com> Date: Mon, 26 Jan 2026 16:07:13 -0800 Subject: [PATCH 780/910] longitudinal maneuvers: add report for longitudinal mpc tuning (#37030) --- .../mpc_longitudinal_tuning_report.py | 276 ++++++++++++++++++ 1 file changed, 276 insertions(+) create mode 100644 tools/longitudinal_maneuvers/mpc_longitudinal_tuning_report.py diff --git a/tools/longitudinal_maneuvers/mpc_longitudinal_tuning_report.py b/tools/longitudinal_maneuvers/mpc_longitudinal_tuning_report.py new file mode 100644 index 000000000..583c6240e --- /dev/null +++ b/tools/longitudinal_maneuvers/mpc_longitudinal_tuning_report.py @@ -0,0 +1,276 @@ +import io +import sys +import markdown +import numpy as np +import matplotlib.pyplot as plt +from openpilot.selfdrive.test.longitudinal_maneuvers.maneuver import Maneuver +from openpilot.selfdrive.controls.tests.test_following_distance import desired_follow_distance + +TIME = 0 +EGO_V = 3 +EGO_A = 5 +LEAD_DISTANCE= 2 + +axis_labels = ['Time (s)', + 'Ego position (m)', + 'Lead distance (m)', + 'Ego Velocity (m/s)', + 'Lead Velocity (m/s)', + 'Ego acceleration (m/s^2)', + ] + + +def get_html_from_results(results, labels, AXIS): + fig, ax = plt.subplots(figsize=(16, 8)) + for idx, speed in enumerate(list(results.keys())): + ax.plot(results[speed][:, TIME], results[speed][:, AXIS], label=labels[idx]) + + ax.set_xlabel('Time (s)') + ax.set_ylabel(axis_labels[AXIS]) + ax.legend(bbox_to_anchor=(1.02, 1), loc='upper left', borderaxespad=0) + ax.grid(True, linestyle='--', alpha=0.7) + ax.text(-0.075, 0.5, '.', transform=ax.transAxes, color='none') + + fig_buffer = io.StringIO() + fig.savefig(fig_buffer, format='svg', bbox_inches='tight') + plt.close(fig) + return fig_buffer.getvalue() + '
' + + +htmls = [] + +results = {} +name = 'Resuming behind lead' +labels = [] +for lead_accel in np.linspace(1.0, 4.0, 4): + man = Maneuver( + '', + duration=11, + initial_speed=0.0, + lead_relevancy=True, + initial_distance_lead=desired_follow_distance(0.0, 0.0), + speed_lead_values=[0.0, 10 * lead_accel], + cruise_values=[100, 100], + prob_lead_values=[1.0, 1.0], + breakpoints=[1., 11], + ) + valid, results[lead_accel] = man.evaluate() + labels.append(f'{lead_accel} m/s^2 lead acceleration') + +htmls.append(markdown.markdown('# ' + name)) +htmls.append(get_html_from_results(results, labels, EGO_V)) +htmls.append(get_html_from_results(results, labels, EGO_A)) + + +results = {} +name = 'Approaching stopped car from 140m' +labels = [] +for speed in np.arange(0,45,5): + man = Maneuver( + name, + duration=30., + initial_speed=float(speed), + lead_relevancy=True, + initial_distance_lead=140., + speed_lead_values=[0.0, 0.], + breakpoints=[0., 30.], + ) + valid, results[speed] = man.evaluate() + results[speed][:,2] = results[speed][:,2] - results[speed][:,1] + labels.append(f'{speed} m/s approach speed') + +htmls.append(markdown.markdown('# ' + name)) +htmls.append(get_html_from_results(results, labels, EGO_A)) +htmls.append(get_html_from_results(results, labels, LEAD_DISTANCE)) + + +results = {} +name = 'Following 5s oscillating lead' +labels = [] +speed = np.int64(10) +for oscil in np.arange(0, 10, 1): + man = Maneuver( + '', + duration=30., + initial_speed=float(speed), + lead_relevancy=True, + initial_distance_lead=desired_follow_distance(speed, speed), + speed_lead_values=[speed, speed, speed - oscil, speed + oscil, speed - oscil, speed + oscil, speed - oscil], + breakpoints=[0.,2., 5, 8, 15, 18, 25.], + ) + valid, results[oscil] = man.evaluate() + labels.append(f'{oscil} m/s oscilliation size') + +htmls.append(markdown.markdown('# ' + name)) +htmls.append(get_html_from_results(results, labels, EGO_V)) +htmls.append(get_html_from_results(results, labels, EGO_A)) + + + +results = {} +name = 'Speed profile when converging to steady state lead at 30m/s' +labels = [] +for distance in np.arange(20, 140, 10): + man = Maneuver( + '', + duration=50, + initial_speed=30.0, + lead_relevancy=True, + initial_distance_lead=distance, + speed_lead_values=[30.0], + breakpoints=[0.], + ) + valid, results[distance] = man.evaluate() + results[distance][:,2] = results[distance][:,2] - results[distance][:,1] + labels.append(f'{distance} m initial distance') + +htmls.append(markdown.markdown('# ' + name)) +htmls.append(get_html_from_results(results, labels, EGO_V)) +htmls.append(get_html_from_results(results, labels, LEAD_DISTANCE)) + + +results = {} +name = 'Speed profile when converging to steady state lead at 20m/s' +labels = [] +for distance in np.arange(20, 140, 10): + man = Maneuver( + '', + duration=50, + initial_speed=20.0, + lead_relevancy=True, + initial_distance_lead=distance, + speed_lead_values=[20.0], + breakpoints=[0.], + ) + valid, results[distance] = man.evaluate() + results[distance][:,2] = results[distance][:,2] - results[distance][:,1] + labels.append(f'{distance} m initial distance') + +htmls.append(markdown.markdown('# ' + name)) +htmls.append(get_html_from_results(results, labels, EGO_V)) +htmls.append(get_html_from_results(results, labels, LEAD_DISTANCE)) + + +results = {} +name = 'Following car at 30m/s that comes to a stop' +labels = [] +for stop_time in np.arange(4, 14, 1): + man = Maneuver( + '', + duration=50, + initial_speed=30.0, + lead_relevancy=True, + initial_distance_lead=60.0, + speed_lead_values=[30.0, 30.0, 0.0, 0.0], + breakpoints=[0., 20., 20 + stop_time, 30 + stop_time], + ) + valid, results[stop_time] = man.evaluate() + results[stop_time][:,2] = results[stop_time][:,2] - results[stop_time][:,1] + labels.append(f'{stop_time} seconds stop time') + +htmls.append(markdown.markdown('# ' + name)) +htmls.append(get_html_from_results(results, labels, EGO_A)) +htmls.append(get_html_from_results(results, labels, LEAD_DISTANCE)) + + +results = {} +name = 'Response to cut-in at half follow distance' +labels = [] +for speed in np.arange(0, 40, 5): + man = Maneuver( + '', + duration=10, + initial_speed=float(speed), + lead_relevancy=True, + initial_distance_lead=desired_follow_distance(speed, speed)/2, + speed_lead_values=[speed, speed, speed], + cruise_values=[speed, speed, speed], + prob_lead_values=[0.0, 0.0, 1.0], + breakpoints=[0., 5.0, 5.01], + ) + valid, results[speed] = man.evaluate() + labels.append(f'{speed} m/s speed') + +htmls.append(markdown.markdown('# ' + name)) +htmls.append(get_html_from_results(results, labels, EGO_A)) +htmls.append(get_html_from_results(results, labels, LEAD_DISTANCE)) + + +results = {} +name = 'Follow a lead that accelerates at 2m/s^2 until steady state speed' +labels = [] +for speed in np.arange(0, 40, 5): + man = Maneuver( + '', + duration=50, + initial_speed=0.0, + lead_relevancy=True, + initial_distance_lead=desired_follow_distance(0.0, 0.0), + speed_lead_values=[0.0, 0.0, speed], + prob_lead_values=[1.0, 1.0, 1.0], + breakpoints=[0., 1.0, speed/2], + ) + valid, results[speed] = man.evaluate() + labels.append(f'{speed} m/s speed') + +htmls.append(markdown.markdown('# ' + name)) +htmls.append(get_html_from_results(results, labels, EGO_V)) +htmls.append(get_html_from_results(results, labels, EGO_A)) + + +results = {} +name = 'From stop to cruise' +labels = [] +for speed in np.arange(0, 40, 5): + man = Maneuver( + '', + duration=50, + initial_speed=0.0, + lead_relevancy=True, + initial_distance_lead=desired_follow_distance(0.0, 0.0), + speed_lead_values=[0.0, 0.0], + cruise_values=[0.0, speed], + prob_lead_values=[0.0, 0.0], + breakpoints=[1., 1.01], + ) + valid, results[speed] = man.evaluate() + labels.append(f'{speed} m/s speed') + +htmls.append(markdown.markdown('# ' + name)) +htmls.append(get_html_from_results(results, labels, EGO_V)) +htmls.append(get_html_from_results(results, labels, EGO_A)) + + +results = {} +name = 'From cruise to min' +labels = [] +for speed in np.arange(10, 40, 5): + man = Maneuver( + '', + duration=50, + initial_speed=float(speed), + lead_relevancy=True, + initial_distance_lead=desired_follow_distance(0.0, 0.0), + speed_lead_values=[0.0, 0.0], + cruise_values=[speed, 10.0], + prob_lead_values=[0.0, 0.0], + breakpoints=[1., 1.01], + ) + valid, results[speed] = man.evaluate() + labels.append(f'{speed} m/s speed') + +htmls.append(markdown.markdown('# ' + name)) +htmls.append(get_html_from_results(results, labels, EGO_V)) +htmls.append(get_html_from_results(results, labels, EGO_A)) + +if len(sys.argv) < 2: + file_name = 'long_mpc_tune_report.html' +else: + file_name = sys.argv[1] + +with open(file_name, 'w') as f: + f.write(markdown.markdown('# MPC longitudinal tuning report')) + +with open(file_name, 'a') as f: + for html in htmls: + f.write(html) From 93015c1c178e218adcfe9688ea13f58effc6d94e Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 27 Jan 2026 15:40:09 -0800 Subject: [PATCH 781/910] ui: fix button label color (#37031) label color --- selfdrive/ui/mici/widgets/button.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/mici/widgets/button.py b/selfdrive/ui/mici/widgets/button.py index 82310577b..9678827a9 100644 --- a/selfdrive/ui/mici/widgets/button.py +++ b/selfdrive/ui/mici/widgets/button.py @@ -16,7 +16,7 @@ except ImportError: SCROLLING_SPEED_PX_S = 50 COMPLICATION_SIZE = 36 -LABEL_COLOR = rl.WHITE +LABEL_COLOR = rl.Color(255, 255, 255, int(255 * 0.9)) LABEL_HORIZONTAL_PADDING = 40 COMPLICATION_GREY = rl.Color(0xAA, 0xAA, 0xAA, 255) PRESSED_SCALE = 1.15 if DO_ZOOM else 1.07 From bf8cae5e7cbbec97483f7e358568ea2e21ca8579 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 27 Jan 2026 16:20:32 -0800 Subject: [PATCH 782/910] mici ui: new icons (#37021) * new icons * add missing * fixed tethering big icon, size of pairing comma, buttons now use 90percent white * why o why * newline * fancy * already default * fixes * add firehose * ltl * fix caps lock icon --------- Co-authored-by: nickorie --- selfdrive/assets/icons_mici/adb_short.png | 3 +++ .../buttons/toggle_dot_disabled.png | 4 +-- .../assets/icons_mici/exclamation_point.png | 4 +-- .../assets/icons_mici/experimental_mode.png | 4 +-- selfdrive/assets/icons_mici/microphone.png | 4 +-- .../icons_mici/offroad_alerts/green_wheel.png | 4 +-- .../offroad_alerts/orange_warning.png | 4 +-- .../icons_mici/offroad_alerts/red_warning.png | 4 +-- .../icons_mici/onroad/blind_spot_left.png | 4 +-- .../icons_mici/onroad/blind_spot_right.png | 4 +-- .../assets/icons_mici/onroad/bookmark.png | 4 +-- .../icons_mici/onroad/bookmark_fill.png | 3 +++ .../driver_monitoring/dm_background.png | 4 +-- .../onroad/driver_monitoring/dm_person.png | 4 +-- .../assets/icons_mici/onroad/eye_fill.png | 4 +-- .../assets/icons_mici/onroad/eye_orange.png | 4 +-- .../assets/icons_mici/onroad/glasses.png | 4 +-- .../assets/icons_mici/onroad/onroad_fade.png | 4 +-- .../icons_mici/onroad/turn_signal_left.png | 4 +-- .../icons_mici/onroad/turn_signal_right.png | 4 +-- selfdrive/assets/icons_mici/settings.png | 4 +-- .../assets/icons_mici/settings/comma_icon.png | 4 +-- .../icons_mici/settings/developer/ssh.png | 4 +-- .../icons_mici/settings/developer_icon.png | 4 +-- .../icons_mici/settings/device/cameras.png | 4 +-- .../icons_mici/settings/device/info.png | 4 +-- .../icons_mici/settings/device/language.png | 4 +-- .../icons_mici/settings/device/lkas.png | 4 +-- .../icons_mici/settings/device/pair.png | 4 +-- .../icons_mici/settings/device/power.png | 4 +-- .../icons_mici/settings/device/reboot.png | 4 +-- .../icons_mici/settings/device/uninstall.png | 4 +-- .../icons_mici/settings/device/up_to_date.png | 4 +-- .../icons_mici/settings/device/update.png | 4 +-- .../icons_mici/settings/device_icon.png | 4 +-- .../assets/icons_mici/settings/firehose.png | 3 +++ .../settings/keyboard/backspace.png | 4 +-- .../settings/keyboard/caps_lock.png | 4 +-- .../settings/keyboard/caps_lower.png | 4 +-- .../settings/keyboard/caps_upper.png | 4 +-- .../icons_mici/settings/keyboard/confirm.png | 4 +-- .../icons_mici/settings/keyboard/space.png | 4 +-- .../icons_mici/settings/manual_icon.png | 3 --- .../settings/network/cell_strength_full.png | 4 +-- .../settings/network/cell_strength_high.png | 4 +-- .../settings/network/cell_strength_low.png | 4 +-- .../settings/network/cell_strength_medium.png | 4 +-- .../settings/network/cell_strength_none.png | 4 +-- .../icons_mici/settings/network/new/lock.png | 4 +-- .../icons_mici/settings/network/new/trash.png | 4 +-- .../icons_mici/settings/network/tethering.png | 4 +-- .../settings/network/wifi_strength_full.png | 4 +-- .../settings/network/wifi_strength_low.png | 4 +-- .../settings/network/wifi_strength_medium.png | 4 +-- .../settings/network/wifi_strength_none.png | 4 +-- .../settings/network/wifi_strength_slash.png | 4 +-- .../icons_mici/settings/toggles_icon.png | 3 --- .../assets/icons_mici/setup/back_new.png | 4 +-- .../setup/driver_monitoring/dm_check.png | 4 +-- .../setup/driver_monitoring/dm_question.png | 4 +-- .../assets/icons_mici/setup/green_car.png | 3 --- .../assets/icons_mici/setup/green_dm.png | 4 +-- .../assets/icons_mici/setup/green_info.png | 4 +-- .../assets/icons_mici/setup/green_pedal.png | 3 --- .../assets/icons_mici/setup/orange_dm.png | 4 +-- .../assets/icons_mici/setup/red_warning.png | 4 +-- selfdrive/assets/icons_mici/setup/restore.png | 4 +-- .../setup/scroll_down_indicator.png | 4 +-- .../setup/small_slider/slider_arrow.png | 4 +-- selfdrive/assets/icons_mici/setup/warning.png | 4 +-- selfdrive/assets/icons_mici/ssh_short.png | 3 +++ .../assets/icons_mici/tethering_short.png | 3 +++ .../assets/icons_mici/turn_intent_left.png | 4 +-- .../assets/icons_mici/turn_intent_right.png | 4 +-- selfdrive/assets/icons_mici/wheel.png | 4 +-- .../assets/icons_mici/wheel_critical.png | 4 +-- selfdrive/ui/mici/layouts/home.py | 20 +++++++-------- selfdrive/ui/mici/layouts/onboarding.py | 4 +-- .../ui/mici/layouts/settings/developer.py | 8 +++--- selfdrive/ui/mici/layouts/settings/device.py | 12 ++++----- .../mici/layouts/settings/network/__init__.py | 9 +++---- .../mici/layouts/settings/network/wifi_ui.py | 12 ++++----- .../ui/mici/layouts/settings/settings.py | 10 ++++---- selfdrive/ui/mici/onroad/alert_renderer.py | 8 +++--- selfdrive/ui/mici/onroad/hud_renderer.py | 4 +-- selfdrive/ui/mici/widgets/button.py | 25 +++++++++++-------- selfdrive/ui/mici/widgets/dialog.py | 4 +-- selfdrive/ui/mici/widgets/pairing_dialog.py | 2 +- system/ui/widgets/mici_keyboard.py | 20 ++++++++------- 89 files changed, 220 insertions(+), 213 deletions(-) create mode 100644 selfdrive/assets/icons_mici/adb_short.png create mode 100644 selfdrive/assets/icons_mici/onroad/bookmark_fill.png create mode 100644 selfdrive/assets/icons_mici/settings/firehose.png delete mode 100644 selfdrive/assets/icons_mici/settings/manual_icon.png delete mode 100644 selfdrive/assets/icons_mici/settings/toggles_icon.png delete mode 100644 selfdrive/assets/icons_mici/setup/green_car.png delete mode 100644 selfdrive/assets/icons_mici/setup/green_pedal.png create mode 100644 selfdrive/assets/icons_mici/ssh_short.png create mode 100644 selfdrive/assets/icons_mici/tethering_short.png diff --git a/selfdrive/assets/icons_mici/adb_short.png b/selfdrive/assets/icons_mici/adb_short.png new file mode 100644 index 000000000..c49226c85 --- /dev/null +++ b/selfdrive/assets/icons_mici/adb_short.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:263598da73c577c01cebd31ae78f45969ef8b335be1a5f55d54a696bb2982c0a +size 2062 diff --git a/selfdrive/assets/icons_mici/buttons/toggle_dot_disabled.png b/selfdrive/assets/icons_mici/buttons/toggle_dot_disabled.png index 0e21bc1b5..1ff4db45a 100644 --- a/selfdrive/assets/icons_mici/buttons/toggle_dot_disabled.png +++ b/selfdrive/assets/icons_mici/buttons/toggle_dot_disabled.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:613af9ed79bb26c60fbd19c094214f0881736c0e293f6d000b530cde0478a273 -size 2470 +oid sha256:89ac033d879beeb0a7fa1919838e0ec64b1a625a4aafc14f7b990c607a79b676 +size 2220 diff --git a/selfdrive/assets/icons_mici/exclamation_point.png b/selfdrive/assets/icons_mici/exclamation_point.png index 246fc015e..ede3b638b 100644 --- a/selfdrive/assets/icons_mici/exclamation_point.png +++ b/selfdrive/assets/icons_mici/exclamation_point.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b77579c099c688d1a27f356197fba9c2c8efcf4d391af580b4b29f0e70587919 -size 2086 +oid sha256:254b7f753b70c964847b686f0f71af751f2f49beea6ede4aeb333fe06062a257 +size 2289 diff --git a/selfdrive/assets/icons_mici/experimental_mode.png b/selfdrive/assets/icons_mici/experimental_mode.png index e0138bfd6..75850d08f 100644 --- a/selfdrive/assets/icons_mici/experimental_mode.png +++ b/selfdrive/assets/icons_mici/experimental_mode.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:eb42b8d6259238beb26f286dc28fb2dc8d91b00fec1f7a7655296b5769439a15 -size 15690 +oid sha256:01841b602632c66ab14a8e52b874a1623f09641dc2ef0620f4e2d00bb4a913f3 +size 16243 diff --git a/selfdrive/assets/icons_mici/microphone.png b/selfdrive/assets/icons_mici/microphone.png index 9718a6b13..9af8f2f45 100644 --- a/selfdrive/assets/icons_mici/microphone.png +++ b/selfdrive/assets/icons_mici/microphone.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:17b6fe530598cbad34bcf31d4f21f929b792aacedef51b3ffef1941c86017811 -size 7331 +oid sha256:744dbaa68ee74e300cd46439bad79449c860e1c5c027304b0f382bd5383fba77 +size 6817 diff --git a/selfdrive/assets/icons_mici/offroad_alerts/green_wheel.png b/selfdrive/assets/icons_mici/offroad_alerts/green_wheel.png index 6a8351f6e..08181ca35 100644 --- a/selfdrive/assets/icons_mici/offroad_alerts/green_wheel.png +++ b/selfdrive/assets/icons_mici/offroad_alerts/green_wheel.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:05f3626e790622a4ad90e982c4aacb612d0785a752339352a3187addf763e2e9 -size 13288 +oid sha256:3b11ee84d48972a2499cb29f01594d77a1a39692f6424a315a3f83262bc16087 +size 13481 diff --git a/selfdrive/assets/icons_mici/offroad_alerts/orange_warning.png b/selfdrive/assets/icons_mici/offroad_alerts/orange_warning.png index 13af475c6..52e6836d4 100644 --- a/selfdrive/assets/icons_mici/offroad_alerts/orange_warning.png +++ b/selfdrive/assets/icons_mici/offroad_alerts/orange_warning.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a877882a8dccb884bd35918f9f9b427a724a59e90a638e54f6fd5d0680ad173c -size 12137 +oid sha256:d548405a65ba4d4590c55866612dc6aa0e78d9278fc864ef60fe3e463edf4a68 +size 12169 diff --git a/selfdrive/assets/icons_mici/offroad_alerts/red_warning.png b/selfdrive/assets/icons_mici/offroad_alerts/red_warning.png index 83c3595b2..df608d351 100644 --- a/selfdrive/assets/icons_mici/offroad_alerts/red_warning.png +++ b/selfdrive/assets/icons_mici/offroad_alerts/red_warning.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ba944b208abed9b8b9752adb8017bd29cd2e98c89fb07ee5d0a595185c7564a5 -size 11898 +oid sha256:b6fc63326d34fbe72f6daf104d101ce19e547dbfe134427c067c957a7179df74 +size 12124 diff --git a/selfdrive/assets/icons_mici/onroad/blind_spot_left.png b/selfdrive/assets/icons_mici/onroad/blind_spot_left.png index 5d3b1e5d7..fdc189b85 100644 --- a/selfdrive/assets/icons_mici/onroad/blind_spot_left.png +++ b/selfdrive/assets/icons_mici/onroad/blind_spot_left.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a23743d21bc8160e013625210654a55634e4ed58e60057b70e08761bac1c3680 -size 40406 +oid sha256:77b20a8c478d982412d556afb3a035b80b4aa9fe7a86aea761af4a42147d9435 +size 45297 diff --git a/selfdrive/assets/icons_mici/onroad/blind_spot_right.png b/selfdrive/assets/icons_mici/onroad/blind_spot_right.png index 67216078d..b6cd7834e 100644 --- a/selfdrive/assets/icons_mici/onroad/blind_spot_right.png +++ b/selfdrive/assets/icons_mici/onroad/blind_spot_right.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:acbfa3e38f0b9f422f5c1335ce20013852df2892b813db176a51918adc83ad58 -size 40979 +oid sha256:584cea202afff6dd20d67ae1a9cd6d2b8cc07598bccb91a8d1bac0142567308e +size 45489 diff --git a/selfdrive/assets/icons_mici/onroad/bookmark.png b/selfdrive/assets/icons_mici/onroad/bookmark.png index 207182276..305561f50 100644 --- a/selfdrive/assets/icons_mici/onroad/bookmark.png +++ b/selfdrive/assets/icons_mici/onroad/bookmark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e0d00d743b01c49c2b739127e9916a229caf8c48346d6d168863b080ddcaa409 -size 11124 +oid sha256:fd91685bf656e828648acf035a4737acb2c4709e8514cf0aa0a10fa470a9bb60 +size 11580 diff --git a/selfdrive/assets/icons_mici/onroad/bookmark_fill.png b/selfdrive/assets/icons_mici/onroad/bookmark_fill.png new file mode 100644 index 000000000..531d5db1c --- /dev/null +++ b/selfdrive/assets/icons_mici/onroad/bookmark_fill.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f3f57346a1cf9a66f9fd746f87bcebb23b7a403e9d6e4fd7701b126abcdd47ea +size 18476 diff --git a/selfdrive/assets/icons_mici/onroad/driver_monitoring/dm_background.png b/selfdrive/assets/icons_mici/onroad/driver_monitoring/dm_background.png index 04ffc2435..4129b13d9 100644 --- a/selfdrive/assets/icons_mici/onroad/driver_monitoring/dm_background.png +++ b/selfdrive/assets/icons_mici/onroad/driver_monitoring/dm_background.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b7eb870d01e5bf6c421e204026a4ea08e177731f2d6b5b17c4ad43c90c1c3e78 -size 23549 +oid sha256:cb89d9f11cf44992f92142aa5ad84e1ac700a2601aff2abab373e2a822af149e +size 11678 diff --git a/selfdrive/assets/icons_mici/onroad/driver_monitoring/dm_person.png b/selfdrive/assets/icons_mici/onroad/driver_monitoring/dm_person.png index 540b2029a..5b917f3a4 100644 --- a/selfdrive/assets/icons_mici/onroad/driver_monitoring/dm_person.png +++ b/selfdrive/assets/icons_mici/onroad/driver_monitoring/dm_person.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f7b3bb76ee2359076339285ea6bced5b680e5b919a1b7dee163f36cd819c9ea1 -size 1746 +oid sha256:e2772c6a9fe9c57099d347ad49f0cb7c906593f1fdf0e6dde96d104baf0200b0 +size 1365 diff --git a/selfdrive/assets/icons_mici/onroad/eye_fill.png b/selfdrive/assets/icons_mici/onroad/eye_fill.png index 8f0e8ebfb..78758a980 100644 --- a/selfdrive/assets/icons_mici/onroad/eye_fill.png +++ b/selfdrive/assets/icons_mici/onroad/eye_fill.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:51af75afbaf30abeaae1c99c7ad3e25cf5d5c90a2d6c799aad353b3302384b0a -size 4829 +oid sha256:07310879d093108435c0011846ae1184966db86443bc6e7ca036a6fa6123700b +size 4983 diff --git a/selfdrive/assets/icons_mici/onroad/eye_orange.png b/selfdrive/assets/icons_mici/onroad/eye_orange.png index b61b9b063..932c71260 100644 --- a/selfdrive/assets/icons_mici/onroad/eye_orange.png +++ b/selfdrive/assets/icons_mici/onroad/eye_orange.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:88b2ecf3a9834d2b156bb632ec2090d7dc112e8ab61711ba645c03489d1c457f -size 29157 +oid sha256:7be447e56d649e0362ef650494b484e140a01ead31799ce43b266f5781c918d2 +size 36473 diff --git a/selfdrive/assets/icons_mici/onroad/glasses.png b/selfdrive/assets/icons_mici/onroad/glasses.png index 1ac4442f4..006972fd3 100644 --- a/selfdrive/assets/icons_mici/onroad/glasses.png +++ b/selfdrive/assets/icons_mici/onroad/glasses.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:28c95c8970648d40b35b94724936a9ab7a6f4cbca367a40f01b86f9abedc70e5 -size 1587 +oid sha256:56de402482b5987ed9a0ff3f793a1c89f857304b34fbb8a3deb5b5d4a332be1c +size 3688 diff --git a/selfdrive/assets/icons_mici/onroad/onroad_fade.png b/selfdrive/assets/icons_mici/onroad/onroad_fade.png index bc12e57e1..3f823061b 100644 --- a/selfdrive/assets/icons_mici/onroad/onroad_fade.png +++ b/selfdrive/assets/icons_mici/onroad/onroad_fade.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d2a2cb4db429467783d7f721ffbed7838551e4aabf32771e73759c87b4a67bca -size 28880 +oid sha256:2aa6d04ba038f15a92868de6e6c7b04f624b4fe89d03bc3e9c4cd44cb729b24e +size 38317 diff --git a/selfdrive/assets/icons_mici/onroad/turn_signal_left.png b/selfdrive/assets/icons_mici/onroad/turn_signal_left.png index 48f52ff9c..97b5cf144 100644 --- a/selfdrive/assets/icons_mici/onroad/turn_signal_left.png +++ b/selfdrive/assets/icons_mici/onroad/turn_signal_left.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0e845a211cf5d03f781efdd6eec4f8106e8dd85799ea59b51834a9099b479141 -size 30348 +oid sha256:f9f7d0554c0c79ab605c1119ffdef0a4f55196e53b75a65b6ac5218911e24a02 +size 45701 diff --git a/selfdrive/assets/icons_mici/onroad/turn_signal_right.png b/selfdrive/assets/icons_mici/onroad/turn_signal_right.png index 87ca979fb..6bcb68dac 100644 --- a/selfdrive/assets/icons_mici/onroad/turn_signal_right.png +++ b/selfdrive/assets/icons_mici/onroad/turn_signal_right.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:009005539f14acc29a4f5510b4e9531d2ba3667133644f6e0069c12b08ba0fd9 -size 35370 +oid sha256:7fae4872ab3c24d5e4c2be6150127a844f89bbdcadfccdff2dfed180e125d577 +size 45699 diff --git a/selfdrive/assets/icons_mici/settings.png b/selfdrive/assets/icons_mici/settings.png index e668ed1fe..4ba7df9fd 100644 --- a/selfdrive/assets/icons_mici/settings.png +++ b/selfdrive/assets/icons_mici/settings.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:38a52171bdc6feb3ddfd2d9f9e59db3dabd09fa0aafbc9f81137c59bd03b7c26 -size 2321 +oid sha256:14b457d2dc19d8658f525cc6989c9cfcf0edaf695b18767514242acbdbe2a6dd +size 2198 diff --git a/selfdrive/assets/icons_mici/settings/comma_icon.png b/selfdrive/assets/icons_mici/settings/comma_icon.png index 72a7c8c8f..dd38a8938 100644 --- a/selfdrive/assets/icons_mici/settings/comma_icon.png +++ b/selfdrive/assets/icons_mici/settings/comma_icon.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:10f469a6f5d25d9e2b0b1aae51b4fbd06d2c7b8417613bb321c2a30bb7298dab -size 1392 +oid sha256:7ad4ee47ec6470f788a026f95ed86bf344f64f9cf3186c9c78927233d2694a1d +size 1388 diff --git a/selfdrive/assets/icons_mici/settings/developer/ssh.png b/selfdrive/assets/icons_mici/settings/developer/ssh.png index cd86937ae..0f17d04ec 100644 --- a/selfdrive/assets/icons_mici/settings/developer/ssh.png +++ b/selfdrive/assets/icons_mici/settings/developer/ssh.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c655994336b7da4ca986c6f27494bcab66e77f016ec9db8df271de53ed93e517 -size 1328 +oid sha256:b26133bee089627202d5e89a4e939ad23aaceb5d8e26d7381b1aea3ef892f2ee +size 2620 diff --git a/selfdrive/assets/icons_mici/settings/developer_icon.png b/selfdrive/assets/icons_mici/settings/developer_icon.png index af16c0291..f9d553c7c 100644 --- a/selfdrive/assets/icons_mici/settings/developer_icon.png +++ b/selfdrive/assets/icons_mici/settings/developer_icon.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a1f058c5640bd763d2f6927432a1daff1587770ea0d06f2e351a28462e9d8335 -size 1743 +oid sha256:ebb4f7ad9fd2f9fb3c69a38fbc00cbe690809b0ff202ffd4768ae5b699acc035 +size 1759 diff --git a/selfdrive/assets/icons_mici/settings/device/cameras.png b/selfdrive/assets/icons_mici/settings/device/cameras.png index c44c51127..ae9a88c4d 100644 --- a/selfdrive/assets/icons_mici/settings/device/cameras.png +++ b/selfdrive/assets/icons_mici/settings/device/cameras.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:77a1281979f0b50f0e109ead56a88a33b81ef5901dd1a4537eb3fa048e0d90de -size 1345 +oid sha256:5f47e636025e044977f278a35546e0fc971f48fd53c2eeafd3508e95c35f378f +size 3117 diff --git a/selfdrive/assets/icons_mici/settings/device/info.png b/selfdrive/assets/icons_mici/settings/device/info.png index cb1632069..9a29c46d0 100644 --- a/selfdrive/assets/icons_mici/settings/device/info.png +++ b/selfdrive/assets/icons_mici/settings/device/info.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2649d36259700d32a0edef878647e76492b1bec2fe34ac8ea806d4e7e4c57855 -size 2668 +oid sha256:66858a5d3302333485fa391f7a9bb3a9b1ab4ae881e7fb47b04c3a4507011c94 +size 2613 diff --git a/selfdrive/assets/icons_mici/settings/device/language.png b/selfdrive/assets/icons_mici/settings/device/language.png index f6d57b313..d2ef27de3 100644 --- a/selfdrive/assets/icons_mici/settings/device/language.png +++ b/selfdrive/assets/icons_mici/settings/device/language.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4b982ac1b78b45487490d1dbbffed1f68735f6a35def502e882f706c30683aff -size 3664 +oid sha256:f646263b26de46f79cac836ef6865b0f25ddc91e386b99311723b68bd06693c9 +size 3304 diff --git a/selfdrive/assets/icons_mici/settings/device/lkas.png b/selfdrive/assets/icons_mici/settings/device/lkas.png index 186ea78fb..80d37d4d5 100644 --- a/selfdrive/assets/icons_mici/settings/device/lkas.png +++ b/selfdrive/assets/icons_mici/settings/device/lkas.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ab6aeb6cba94acf948a0ad64a485db00bf1f3de1360ae4c57212f3f083b2bd24 -size 2554 +oid sha256:a05a41e66c7a24d461a4bbcdab0979031e5900e1db270af52ca363f0bed521f5 +size 2028 diff --git a/selfdrive/assets/icons_mici/settings/device/pair.png b/selfdrive/assets/icons_mici/settings/device/pair.png index f072b2363..807d44335 100644 --- a/selfdrive/assets/icons_mici/settings/device/pair.png +++ b/selfdrive/assets/icons_mici/settings/device/pair.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ed671f4ad1523f0e66498af39e6075a0c19842ae05eddd00871a6e48ed3685d7 -size 1594 +oid sha256:678483230831d0a7d3dcad5f067a7b641e5d2ae0db477665dfc6c53a675eba18 +size 1779 diff --git a/selfdrive/assets/icons_mici/settings/device/power.png b/selfdrive/assets/icons_mici/settings/device/power.png index a2de14a4e..711f1a4ab 100644 --- a/selfdrive/assets/icons_mici/settings/device/power.png +++ b/selfdrive/assets/icons_mici/settings/device/power.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5b45645ad9ff27776fdb1caa27827c526cae57f8bd4e23bd1160cb0094121ff2 -size 2338 +oid sha256:a34885e79f42d19b7777dd07e7ab51df344880cb770c48e0baaddb177c2ae938 +size 2228 diff --git a/selfdrive/assets/icons_mici/settings/device/reboot.png b/selfdrive/assets/icons_mici/settings/device/reboot.png index 6c89cd9fc..298a85c50 100644 --- a/selfdrive/assets/icons_mici/settings/device/reboot.png +++ b/selfdrive/assets/icons_mici/settings/device/reboot.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f24039f82d7399d02a155022de65b6dc3b8edcf17059a73a9fd3a9209e3f5575 -size 2360 +oid sha256:1356fe3ddda14568e9be1dca4e16ca9048852e3a27a3f531cd58d7d368485a82 +size 2362 diff --git a/selfdrive/assets/icons_mici/settings/device/uninstall.png b/selfdrive/assets/icons_mici/settings/device/uninstall.png index f9173711e..53f8bc0e7 100644 --- a/selfdrive/assets/icons_mici/settings/device/uninstall.png +++ b/selfdrive/assets/icons_mici/settings/device/uninstall.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:558ea538fb258079f9eb05fe048b2806c7635b9f0452af874b00cb8d79b45f9b -size 2421 +oid sha256:50a8ce4fa8ff7f5b0f56ba0dc65b4802dc0be2dc0967b5cb3a15e3b79a4e513e +size 2424 diff --git a/selfdrive/assets/icons_mici/settings/device/up_to_date.png b/selfdrive/assets/icons_mici/settings/device/up_to_date.png index ee925458d..e09f7d330 100644 --- a/selfdrive/assets/icons_mici/settings/device/up_to_date.png +++ b/selfdrive/assets/icons_mici/settings/device/up_to_date.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4510e65775c6001758ebcf4dc13e9fa561cce5159d1fd54fbb506f22d3c7bdf3 -size 3149 +oid sha256:61bc44b6e0f99640434d6abcb64880c7bf575eda5cdcf7d74cba7d73307dd39a +size 2739 diff --git a/selfdrive/assets/icons_mici/settings/device/update.png b/selfdrive/assets/icons_mici/settings/device/update.png index cc05931b0..498c06619 100644 --- a/selfdrive/assets/icons_mici/settings/device/update.png +++ b/selfdrive/assets/icons_mici/settings/device/update.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c6137349218ea22adba44f46a096afe2efc35536b2251192ed0ea61be443a3c5 -size 2493 +oid sha256:f28cdeaba9146521335bc11ad60a8e0368eb0ed1381e88b35a12a6138ba22ed6 +size 2409 diff --git a/selfdrive/assets/icons_mici/settings/device_icon.png b/selfdrive/assets/icons_mici/settings/device_icon.png index 0caf0d07c..6a716e4df 100644 --- a/selfdrive/assets/icons_mici/settings/device_icon.png +++ b/selfdrive/assets/icons_mici/settings/device_icon.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:db20bea98259b204be634ce0d9a23fbfdcfc73a324fc0aac0f9ac54e1c51556d -size 2443 +oid sha256:2273629450aa870f0964dd285721c35d3d313fb8b4684122215a65844ae744d0 +size 1888 diff --git a/selfdrive/assets/icons_mici/settings/firehose.png b/selfdrive/assets/icons_mici/settings/firehose.png new file mode 100644 index 000000000..37451c048 --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/firehose.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:416656861380981acc114e5285b448d6e4dc42b98539d0ba16821cbc3db89208 +size 1364 diff --git a/selfdrive/assets/icons_mici/settings/keyboard/backspace.png b/selfdrive/assets/icons_mici/settings/keyboard/backspace.png index 342f8e28d..53ff00c2a 100644 --- a/selfdrive/assets/icons_mici/settings/keyboard/backspace.png +++ b/selfdrive/assets/icons_mici/settings/keyboard/backspace.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:116bbbd1509e6644f7b65b8dacd2402b0918785bd80207504a99ab7e13ab738f -size 2049 +oid sha256:69bb4a401429c3fdf473778f751288b2aafea27eb13f09b20e83d55212f084ba +size 1963 diff --git a/selfdrive/assets/icons_mici/settings/keyboard/caps_lock.png b/selfdrive/assets/icons_mici/settings/keyboard/caps_lock.png index d63cc56fb..2d173bfc9 100644 --- a/selfdrive/assets/icons_mici/settings/keyboard/caps_lock.png +++ b/selfdrive/assets/icons_mici/settings/keyboard/caps_lock.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3e8c7fec57640de6bfa8d0ede977e40920a8e651b68ed14e3d6c1850e702f3e3 -size 1399 +oid sha256:563c211fd98018e24418235602e596f3a481f04fddde0a14590e563474fcffd2 +size 1423 diff --git a/selfdrive/assets/icons_mici/settings/keyboard/caps_lower.png b/selfdrive/assets/icons_mici/settings/keyboard/caps_lower.png index eb3893430..a3ce71f04 100644 --- a/selfdrive/assets/icons_mici/settings/keyboard/caps_lower.png +++ b/selfdrive/assets/icons_mici/settings/keyboard/caps_lower.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b7dab3af28938e9c3ad7b6c3b60526bb76498b0103c7276d90c4bff3622f07d0 -size 1157 +oid sha256:6f81811ea9cdc409d5549035ca928c76e22396193e1cefb6cacab3747ee0c297 +size 1142 diff --git a/selfdrive/assets/icons_mici/settings/keyboard/caps_upper.png b/selfdrive/assets/icons_mici/settings/keyboard/caps_upper.png index 4a2cae6c8..7c147bc07 100644 --- a/selfdrive/assets/icons_mici/settings/keyboard/caps_upper.png +++ b/selfdrive/assets/icons_mici/settings/keyboard/caps_upper.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0c5a88a0e8e810115b6d497d3e230d866bd96a715ddac632f48c78b40e1df702 -size 1059 +oid sha256:60875e73dd9659122c9248d8e99d5cfd301d68dabeec2cb42cebce812c9baae9 +size 1102 diff --git a/selfdrive/assets/icons_mici/settings/keyboard/confirm.png b/selfdrive/assets/icons_mici/settings/keyboard/confirm.png index 09b180e97..98ca5c61d 100644 --- a/selfdrive/assets/icons_mici/settings/keyboard/confirm.png +++ b/selfdrive/assets/icons_mici/settings/keyboard/confirm.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:32ce109a9fe4814bb9bed88f67d85292791f4a6d7c162e07561920221ac38b2d -size 1411 +oid sha256:43b64365a42d7bf772d567b8867a6ced4ec0175bb88b6acaa3a5345f19ca696e +size 1268 diff --git a/selfdrive/assets/icons_mici/settings/keyboard/space.png b/selfdrive/assets/icons_mici/settings/keyboard/space.png index 778d1847d..3d6110972 100644 --- a/selfdrive/assets/icons_mici/settings/keyboard/space.png +++ b/selfdrive/assets/icons_mici/settings/keyboard/space.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9b04d17f3b0340a94210efa5c9547e0ac340dd6b6dd9ac1f81ba5eb3f89f405d -size 619 +oid sha256:f431e428772991323ee3ce662479e1ab29c3d80a72b93cf9c9673716ba245d5f +size 654 diff --git a/selfdrive/assets/icons_mici/settings/manual_icon.png b/selfdrive/assets/icons_mici/settings/manual_icon.png deleted file mode 100644 index 100b29da4..000000000 --- a/selfdrive/assets/icons_mici/settings/manual_icon.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:957330e9fbc8c03f05dbef8097178a40efc0fc52a6faf7a9917f97046d9a5e99 -size 1559 diff --git a/selfdrive/assets/icons_mici/settings/network/cell_strength_full.png b/selfdrive/assets/icons_mici/settings/network/cell_strength_full.png index 4bf0cd872..13f70386d 100644 --- a/selfdrive/assets/icons_mici/settings/network/cell_strength_full.png +++ b/selfdrive/assets/icons_mici/settings/network/cell_strength_full.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6a981d5c5558859b283cb6321c84eec947f82fc2dea8dbdd19b66781e4d3f61f -size 1060 +oid sha256:fb7af523411c5ed75c6e1418dfc2a379486f6dbd7f2f1c281d3ff54e1ea7810e +size 777 diff --git a/selfdrive/assets/icons_mici/settings/network/cell_strength_high.png b/selfdrive/assets/icons_mici/settings/network/cell_strength_high.png index df6d00933..1fea6d23b 100644 --- a/selfdrive/assets/icons_mici/settings/network/cell_strength_high.png +++ b/selfdrive/assets/icons_mici/settings/network/cell_strength_high.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:58da16ede432cf89096c11dc0f4ea098735863fb09a1d655cb06de8a112bd263 -size 1205 +oid sha256:db86e176e016458fcff00d40e37636a808977e0cc01bcc9c04b31a1001562de8 +size 936 diff --git a/selfdrive/assets/icons_mici/settings/network/cell_strength_low.png b/selfdrive/assets/icons_mici/settings/network/cell_strength_low.png index c3323a9fe..d763f86c7 100644 --- a/selfdrive/assets/icons_mici/settings/network/cell_strength_low.png +++ b/selfdrive/assets/icons_mici/settings/network/cell_strength_low.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:031bbd50c34d8fd5e71bdc292ba3e50b28a13c56a48dc84117723f1b35b42f51 -size 1224 +oid sha256:1cd0b3a00db36ee7eacf5887d07d40e5351fb441d98643a02df4c742cd1e935d +size 945 diff --git a/selfdrive/assets/icons_mici/settings/network/cell_strength_medium.png b/selfdrive/assets/icons_mici/settings/network/cell_strength_medium.png index 64ab947c5..148ee63e9 100644 --- a/selfdrive/assets/icons_mici/settings/network/cell_strength_medium.png +++ b/selfdrive/assets/icons_mici/settings/network/cell_strength_medium.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ccb5f2227c72dd28e40c9f19965abe007cbd7b47cdca924907dc9fad906f5c81 -size 1219 +oid sha256:25724acfe0c261070b103ef5933053d5dd8b726ece42d0e5f715f05c67be2294 +size 956 diff --git a/selfdrive/assets/icons_mici/settings/network/cell_strength_none.png b/selfdrive/assets/icons_mici/settings/network/cell_strength_none.png index 6cdef706b..c6d82ac31 100644 --- a/selfdrive/assets/icons_mici/settings/network/cell_strength_none.png +++ b/selfdrive/assets/icons_mici/settings/network/cell_strength_none.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:92c195721fe2b4ca42176077bf4ca3484cdfc314e961f1431b2296476bcae891 -size 1178 +oid sha256:cb0aeb6260bcd0642204f842112479f4b19b350db9addae5e14c9c5131bcf956 +size 781 diff --git a/selfdrive/assets/icons_mici/settings/network/new/lock.png b/selfdrive/assets/icons_mici/settings/network/new/lock.png index 0a0b18c7a..9fc152d3d 100644 --- a/selfdrive/assets/icons_mici/settings/network/new/lock.png +++ b/selfdrive/assets/icons_mici/settings/network/new/lock.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:40dbbb3000e1137ec11fe658fbfebae7cadfc91356953317335f9bb70fcb40d3 -size 1235 +oid sha256:782161f35b4925c7063c441b0c341331c814614cf241f21b4e70134280c630f0 +size 1182 diff --git a/selfdrive/assets/icons_mici/settings/network/new/trash.png b/selfdrive/assets/icons_mici/settings/network/new/trash.png index 99e1a2e24..81e5f13e4 100644 --- a/selfdrive/assets/icons_mici/settings/network/new/trash.png +++ b/selfdrive/assets/icons_mici/settings/network/new/trash.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:efabf98ed66fe4447c0f13c74aec681b084de780c551ce18258c79636d4123c5 -size 1524 +oid sha256:9074162bf0469fc5ab0b5711a121289a983c887161df269ac120edd8fd024499 +size 1533 diff --git a/selfdrive/assets/icons_mici/settings/network/tethering.png b/selfdrive/assets/icons_mici/settings/network/tethering.png index 9e7b90be4..4bb416b0b 100644 --- a/selfdrive/assets/icons_mici/settings/network/tethering.png +++ b/selfdrive/assets/icons_mici/settings/network/tethering.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2907ce46d1b6e676402f390c530955b65e76baf0b77fafc0616c50b988b3994c -size 1609 +oid sha256:b1e322ea6e57b05b3515fcd4e9100f890e6ff80607c11360b7927fa5a9765beb +size 2752 diff --git a/selfdrive/assets/icons_mici/settings/network/wifi_strength_full.png b/selfdrive/assets/icons_mici/settings/network/wifi_strength_full.png index 1a1655fdd..fe81ffa57 100644 --- a/selfdrive/assets/icons_mici/settings/network/wifi_strength_full.png +++ b/selfdrive/assets/icons_mici/settings/network/wifi_strength_full.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f2715ea698eccb3648ab96cbddf897ea1842acbc1eb9667bc6f34aba82d0896b -size 1976 +oid sha256:73c76e5240bdff64c1d1ed0ac2bb9c3fadb2fd61fbf8dc710b812757af8bcf6c +size 2026 diff --git a/selfdrive/assets/icons_mici/settings/network/wifi_strength_low.png b/selfdrive/assets/icons_mici/settings/network/wifi_strength_low.png index 4d64d8062..2649cc89d 100644 --- a/selfdrive/assets/icons_mici/settings/network/wifi_strength_low.png +++ b/selfdrive/assets/icons_mici/settings/network/wifi_strength_low.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:58d839402c6f002ba8d2217888190b338fc3ac13d372df0988fac7bf95b89302 -size 2111 +oid sha256:e66cc6174a54177793c42ef3525a9aa1592e05b0abb677442c7226269d1371a5 +size 2196 diff --git a/selfdrive/assets/icons_mici/settings/network/wifi_strength_medium.png b/selfdrive/assets/icons_mici/settings/network/wifi_strength_medium.png index 2d53a20ce..888183337 100644 --- a/selfdrive/assets/icons_mici/settings/network/wifi_strength_medium.png +++ b/selfdrive/assets/icons_mici/settings/network/wifi_strength_medium.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a9918724409dbfa1973a097a692c2f57e45cc2bc0ce71c498ef3e02aa82559d3 -size 2128 +oid sha256:7948a9234f2bc996aefb3a9e58a37c06ebbf54e8e4596e47800f78ef7e81961f +size 2231 diff --git a/selfdrive/assets/icons_mici/settings/network/wifi_strength_none.png b/selfdrive/assets/icons_mici/settings/network/wifi_strength_none.png index 482a0e104..848d7849a 100644 --- a/selfdrive/assets/icons_mici/settings/network/wifi_strength_none.png +++ b/selfdrive/assets/icons_mici/settings/network/wifi_strength_none.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3fcef95eb18e2db566b907ae99b8d8f450424b3b7823fdc24cdfe066ccf64378 -size 2141 +oid sha256:a57ea402448dacc2026631174e448b6254698fe92309221576400cbf28196936 +size 2195 diff --git a/selfdrive/assets/icons_mici/settings/network/wifi_strength_slash.png b/selfdrive/assets/icons_mici/settings/network/wifi_strength_slash.png index 38ddff84b..4457a3fcd 100644 --- a/selfdrive/assets/icons_mici/settings/network/wifi_strength_slash.png +++ b/selfdrive/assets/icons_mici/settings/network/wifi_strength_slash.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:73e4ae4741a039f41d79827c40be6da83f8c6eb79e9103db2dfec718ca96efb7 -size 2512 +oid sha256:7e6d166bdbbcdc106e7cd4a44ba85848888f18a6ef34e86daac8e12a3f519443 +size 2318 diff --git a/selfdrive/assets/icons_mici/settings/toggles_icon.png b/selfdrive/assets/icons_mici/settings/toggles_icon.png deleted file mode 100644 index ccb343e8e..000000000 --- a/selfdrive/assets/icons_mici/settings/toggles_icon.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0297535eb73bea71e87c363dc12385bb9163b81403797e50966b20259f725542 -size 2528 diff --git a/selfdrive/assets/icons_mici/setup/back_new.png b/selfdrive/assets/icons_mici/setup/back_new.png index c4834a564..20e7fe3b8 100644 --- a/selfdrive/assets/icons_mici/setup/back_new.png +++ b/selfdrive/assets/icons_mici/setup/back_new.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7198352d23952d0f2fbc128f20523ea6f2f2b7e378aa495da748a0e34f192806 -size 1641 +oid sha256:d29a9c295b33b3164c37a68ad77795595e6ac877a5b308d28112b0315ecd498f +size 1687 diff --git a/selfdrive/assets/icons_mici/setup/driver_monitoring/dm_check.png b/selfdrive/assets/icons_mici/setup/driver_monitoring/dm_check.png index 92993e3e0..dfb9799b0 100644 --- a/selfdrive/assets/icons_mici/setup/driver_monitoring/dm_check.png +++ b/selfdrive/assets/icons_mici/setup/driver_monitoring/dm_check.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5b7dce550c008ff7a65ed19ccf308ecf92cd0118bb544978b7dd7393c5c27ae5 -size 809 +oid sha256:2290105f9b055b3c3d482d883d148de3418cad07b653133b0f61137e1976c407 +size 1412 diff --git a/selfdrive/assets/icons_mici/setup/driver_monitoring/dm_question.png b/selfdrive/assets/icons_mici/setup/driver_monitoring/dm_question.png index 53a837afb..fa29be182 100644 --- a/selfdrive/assets/icons_mici/setup/driver_monitoring/dm_question.png +++ b/selfdrive/assets/icons_mici/setup/driver_monitoring/dm_question.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e102b8b2e71a25d9f818b37d6f75ed958430cb765a07ae50713995779fb6a886 -size 1388 +oid sha256:ec9691d2572e2e084f0b3c99a1dcd0daadf5040d16c02347ffec9dd5466c061a +size 1438 diff --git a/selfdrive/assets/icons_mici/setup/green_car.png b/selfdrive/assets/icons_mici/setup/green_car.png deleted file mode 100644 index 867cadbbd..000000000 --- a/selfdrive/assets/icons_mici/setup/green_car.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ce8a34777e0b185f457b98845aa17fe6b5192ca46101463aecd21a9e04c0f0f0 -size 13281 diff --git a/selfdrive/assets/icons_mici/setup/green_dm.png b/selfdrive/assets/icons_mici/setup/green_dm.png index d41edd4c2..87f4ffe78 100644 --- a/selfdrive/assets/icons_mici/setup/green_dm.png +++ b/selfdrive/assets/icons_mici/setup/green_dm.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:78795eaa5e0be5fa369e172c02f5bd4b06d20f44363ccb8cbd02cb181b13e529 -size 14289 +oid sha256:8b6d7747dd6bbf47d9782fc0d847c224b933f6616218ade1f9220018aa9d6acc +size 15052 diff --git a/selfdrive/assets/icons_mici/setup/green_info.png b/selfdrive/assets/icons_mici/setup/green_info.png index 309e56e6e..57e005abd 100644 --- a/selfdrive/assets/icons_mici/setup/green_info.png +++ b/selfdrive/assets/icons_mici/setup/green_info.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2b0b1777d5bed7149982af9f2abab3fab7b6c576e3d53cf2c459804c6ec9ca1e -size 3957 +oid sha256:5055bc385a1de674e6f3cbafdb611ee4b1088de2a3c357bce76f6a192226c952 +size 14154 diff --git a/selfdrive/assets/icons_mici/setup/green_pedal.png b/selfdrive/assets/icons_mici/setup/green_pedal.png deleted file mode 100644 index 2dd18f489..000000000 --- a/selfdrive/assets/icons_mici/setup/green_pedal.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6cadcda59bc861a1e710e0a8ac67024bdcc44b5f9261abbf098ff11cefb1da51 -size 12209 diff --git a/selfdrive/assets/icons_mici/setup/orange_dm.png b/selfdrive/assets/icons_mici/setup/orange_dm.png index 74cce9d97..97df767a9 100644 --- a/selfdrive/assets/icons_mici/setup/orange_dm.png +++ b/selfdrive/assets/icons_mici/setup/orange_dm.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:38a108f96f85a154b698693b07f2e4214124b8f2545b7c4490cea0aa998d75fd -size 11855 +oid sha256:9c45ab0b949c1c71651f9f48cf6ff10196d64eb85e042b063e92b1d7ca02dcb5 +size 13155 diff --git a/selfdrive/assets/icons_mici/setup/red_warning.png b/selfdrive/assets/icons_mici/setup/red_warning.png index ed0634079..387794cf1 100644 --- a/selfdrive/assets/icons_mici/setup/red_warning.png +++ b/selfdrive/assets/icons_mici/setup/red_warning.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:448d3e7214a77b02b32020ddb440ccd8fe72e110493a51cc10901c8242e72ca8 -size 3185 +oid sha256:e8e8bc3c15df7512a81b902e47fb069eff1370c833095d3b25f3866efb815fff +size 11123 diff --git a/selfdrive/assets/icons_mici/setup/restore.png b/selfdrive/assets/icons_mici/setup/restore.png index 6aa6c6b85..5eff92404 100644 --- a/selfdrive/assets/icons_mici/setup/restore.png +++ b/selfdrive/assets/icons_mici/setup/restore.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9d6b99696163cac1867d46998af9e53e212b82641b33c93b51276671f400a5ac -size 2962 +oid sha256:1f5ee67cd334d259ac33f932281db36533877009b5769c92d9cff3054fd5627c +size 2942 diff --git a/selfdrive/assets/icons_mici/setup/scroll_down_indicator.png b/selfdrive/assets/icons_mici/setup/scroll_down_indicator.png index 4d74d8607..3cd26e518 100644 --- a/selfdrive/assets/icons_mici/setup/scroll_down_indicator.png +++ b/selfdrive/assets/icons_mici/setup/scroll_down_indicator.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:52535e34e27b0341f7690a72dc16555eeb6e032bc2c2cde0786469852fdf5987 -size 1267 +oid sha256:a733c425113a7f6ff5ec3dc50ef94b5481c0f2d306e33d1485be8ee6b2798532 +size 1136 diff --git a/selfdrive/assets/icons_mici/setup/small_slider/slider_arrow.png b/selfdrive/assets/icons_mici/setup/small_slider/slider_arrow.png index bbf1d9625..acf5b1741 100644 --- a/selfdrive/assets/icons_mici/setup/small_slider/slider_arrow.png +++ b/selfdrive/assets/icons_mici/setup/small_slider/slider_arrow.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8425c56cb413ba757c94febe0332ce472dbf1472236b03cc4e627746fb86d701 -size 1149 +oid sha256:75a6557935075a646b17d083202832daafb263d4cfa38aea2af407afc04e2ef4 +size 1312 diff --git a/selfdrive/assets/icons_mici/setup/warning.png b/selfdrive/assets/icons_mici/setup/warning.png index 806eea28b..1b7839f47 100644 --- a/selfdrive/assets/icons_mici/setup/warning.png +++ b/selfdrive/assets/icons_mici/setup/warning.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3bc7a85a0672183d80817f337084060465e143362037955025c11bc8ac531076 -size 3247 +oid sha256:7584d32ac0231381e38646fdac2f71b4517905ef22024f01bd9e124d3918f33a +size 9194 diff --git a/selfdrive/assets/icons_mici/ssh_short.png b/selfdrive/assets/icons_mici/ssh_short.png new file mode 100644 index 000000000..699ddd72e --- /dev/null +++ b/selfdrive/assets/icons_mici/ssh_short.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ef1735e6effcb625ea618fa35a6b908b28ca483d5997e15241d48e2d3d29819e +size 1433 diff --git a/selfdrive/assets/icons_mici/tethering_short.png b/selfdrive/assets/icons_mici/tethering_short.png new file mode 100644 index 000000000..f97fed95d --- /dev/null +++ b/selfdrive/assets/icons_mici/tethering_short.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fce940a3cbd2e9530e8efdde90794013a272919b2f3ea482bc06535c795640e7 +size 2176 diff --git a/selfdrive/assets/icons_mici/turn_intent_left.png b/selfdrive/assets/icons_mici/turn_intent_left.png index 6c2c47e88..3934200c9 100644 --- a/selfdrive/assets/icons_mici/turn_intent_left.png +++ b/selfdrive/assets/icons_mici/turn_intent_left.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ead8287b7041c32456e13721c238a71933256ca3d2b7e649c8f8731585eb5de8 -size 906 +oid sha256:001cb8227eaaff5367055395d9b3ccd5822f9a47276091832d8ad28b074d77c9 +size 914 diff --git a/selfdrive/assets/icons_mici/turn_intent_right.png b/selfdrive/assets/icons_mici/turn_intent_right.png index 03a7245e7..e34277873 100644 --- a/selfdrive/assets/icons_mici/turn_intent_right.png +++ b/selfdrive/assets/icons_mici/turn_intent_right.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6fe0532f7040aae78baa85c4cca44f5c939adb6a6f15889e2ca036f4a493f848 -size 935 +oid sha256:7b7e0194a8b9009e493cdce35cd15711596a54227c740e9d6419a3891c6c4037 +size 912 diff --git a/selfdrive/assets/icons_mici/wheel.png b/selfdrive/assets/icons_mici/wheel.png index f122349b8..a43bcb3b9 100644 --- a/selfdrive/assets/icons_mici/wheel.png +++ b/selfdrive/assets/icons_mici/wheel.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cc3ef0c8c3038d75f99df2c565a361107bc903944d1afe91de0cbed9f6ca062a -size 2725 +oid sha256:8cf9c6361ed82551eb99e028e0a75ff56b72ca856ccf7c9a76afe6745434980a +size 2720 diff --git a/selfdrive/assets/icons_mici/wheel_critical.png b/selfdrive/assets/icons_mici/wheel_critical.png index c0e5e8619..676b0b4d7 100644 --- a/selfdrive/assets/icons_mici/wheel_critical.png +++ b/selfdrive/assets/icons_mici/wheel_critical.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:12783dc05ea6dae2647ac3a3a7c8391d520c3f0cf2f458333a357ee9633eb6c4 -size 10909 +oid sha256:4c3d9082b295f9e5ddef93f8d4e9cb961ea2374c7affd26394bbccb26e7137b2 +size 11023 diff --git a/selfdrive/ui/mici/layouts/home.py b/selfdrive/ui/mici/layouts/home.py index 9152bdc7f..f5dab7249 100644 --- a/selfdrive/ui/mici/layouts/home.py +++ b/selfdrive/ui/mici/layouts/home.py @@ -92,22 +92,22 @@ class MiciHomeLayout(Widget): self._settings_txt = gui_app.texture("icons_mici/settings.png", 48, 48) self._experimental_txt = gui_app.texture("icons_mici/experimental_mode.png", 48, 48) - self._mic_txt = gui_app.texture("icons_mici/microphone.png", 48, 48) + self._mic_txt = gui_app.texture("icons_mici/microphone.png", 32, 46) self._net_type = NETWORK_TYPES.get(NetworkType.none) self._net_strength = 0 self._wifi_slash_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_slash.png", 50, 44) - self._wifi_none_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_none.png", 50, 44) - self._wifi_low_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_low.png", 50, 44) - self._wifi_medium_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_medium.png", 50, 44) - self._wifi_full_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_full.png", 50, 44) + self._wifi_none_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_none.png", 50, 37) + self._wifi_low_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_low.png", 50, 37) + self._wifi_medium_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_medium.png", 50, 37) + self._wifi_full_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_full.png", 50, 37) - self._cell_none_txt = gui_app.texture("icons_mici/settings/network/cell_strength_none.png", 55, 35) - self._cell_low_txt = gui_app.texture("icons_mici/settings/network/cell_strength_low.png", 55, 35) - self._cell_medium_txt = gui_app.texture("icons_mici/settings/network/cell_strength_medium.png", 55, 35) - self._cell_high_txt = gui_app.texture("icons_mici/settings/network/cell_strength_high.png", 55, 35) - self._cell_full_txt = gui_app.texture("icons_mici/settings/network/cell_strength_full.png", 55, 35) + self._cell_none_txt = gui_app.texture("icons_mici/settings/network/cell_strength_none.png", 54, 36) + self._cell_low_txt = gui_app.texture("icons_mici/settings/network/cell_strength_low.png", 54, 36) + self._cell_medium_txt = gui_app.texture("icons_mici/settings/network/cell_strength_medium.png", 54, 36) + self._cell_high_txt = gui_app.texture("icons_mici/settings/network/cell_strength_high.png", 54, 36) + self._cell_full_txt = gui_app.texture("icons_mici/settings/network/cell_strength_full.png", 54, 36) self._openpilot_label = MiciLabel("openpilot", font_size=96, color=rl.Color(255, 255, 255, int(255 * 0.9)), font_weight=FontWeight.DISPLAY) self._version_label = MiciLabel("", font_size=36, font_weight=FontWeight.ROMAN) diff --git a/selfdrive/ui/mici/layouts/onboarding.py b/selfdrive/ui/mici/layouts/onboarding.py index 16e96d6f7..4248fef2e 100644 --- a/selfdrive/ui/mici/layouts/onboarding.py +++ b/selfdrive/ui/mici/layouts/onboarding.py @@ -124,9 +124,9 @@ class TrainingGuideDMTutorial(Widget): def __init__(self, continue_callback): super().__init__() - self._back_button = SmallCircleIconButton(gui_app.texture("icons_mici/setup/driver_monitoring/dm_question.png", 48, 48)) + self._back_button = SmallCircleIconButton(gui_app.texture("icons_mici/setup/driver_monitoring/dm_question.png", 28, 48)) self._back_button.set_click_callback(self._show_bad_face_page) - self._good_button = SmallCircleIconButton(gui_app.texture("icons_mici/setup/driver_monitoring/dm_check.png", 48, 35)) + self._good_button = SmallCircleIconButton(gui_app.texture("icons_mici/setup/driver_monitoring/dm_check.png", 42, 42)) # Wrap the continue callback to restore settings def wrapped_continue_callback(): diff --git a/selfdrive/ui/mici/layouts/settings/developer.py b/selfdrive/ui/mici/layouts/settings/developer.py index 8fc63e896..b6145e042 100644 --- a/selfdrive/ui/mici/layouts/settings/developer.py +++ b/selfdrive/ui/mici/layouts/settings/developer.py @@ -3,7 +3,7 @@ from collections.abc import Callable from openpilot.common.time_helpers import system_time_valid from openpilot.system.ui.widgets.scroller import Scroller -from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigToggle, BigParamControl +from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigToggle, BigParamControl, BigCircleParamControl from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigInputDialog from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.widgets import NavWidget @@ -36,15 +36,15 @@ class DeveloperLayoutMici(NavWidget): return gui_app.set_modal_overlay(dlg) - txt_ssh = gui_app.texture("icons_mici/settings/developer/ssh.png", 77, 44) + txt_ssh = gui_app.texture("icons_mici/settings/developer/ssh.png", 56, 64) github_username = ui_state.params.get("GithubUsername") or "" self._ssh_keys_btn = BigButton("SSH keys", "Not set" if not github_username else github_username, icon=txt_ssh) self._ssh_keys_btn.set_click_callback(ssh_keys_callback) # adb, ssh, ssh keys, debug mode, joystick debug mode, longitudinal maneuver mode, ip address # ******** Main Scroller ******** - self._adb_toggle = BigParamControl("enable ADB", "AdbEnabled") - self._ssh_toggle = BigParamControl("enable SSH", "SshEnabled") + self._adb_toggle = BigCircleParamControl("icons_mici/adb_short.png", "AdbEnabled", icon_size=(82, 82), icon_offset=(0, 12)) + self._ssh_toggle = BigCircleParamControl("icons_mici/ssh_short.png", "SshEnabled", icon_size=(82, 82), icon_offset=(0, 12)) self._joystick_toggle = BigToggle("joystick debug mode", initial_state=ui_state.params.get_bool("JoystickDebugMode"), toggle_callback=self._on_joystick_debug_mode) diff --git a/selfdrive/ui/mici/layouts/settings/device.py b/selfdrive/ui/mici/layouts/settings/device.py index 988c823a9..30ea90f3d 100644 --- a/selfdrive/ui/mici/layouts/settings/device.py +++ b/selfdrive/ui/mici/layouts/settings/device.py @@ -119,7 +119,7 @@ class UpdaterState(IntEnum): class PairBigButton(BigButton): def __init__(self): - super().__init__("pair", "connect.comma.ai", "icons_mici/settings/comma_icon.png") + super().__init__("pair", "connect.comma.ai", "icons_mici/settings/comma_icon.png", icon_size=(33, 60)) def _update_state(self): if ui_state.prime_state.is_paired(): @@ -153,8 +153,8 @@ UPDATER_TIMEOUT = 10.0 # seconds to wait for updater to respond class UpdateOpenpilotBigButton(BigButton): def __init__(self): - self._txt_update_icon = gui_app.texture("icons_mici/settings/device/update.png", 64, 64) - self._txt_reboot_icon = gui_app.texture("icons_mici/settings/device/reboot.png", 64, 64) + self._txt_update_icon = gui_app.texture("icons_mici/settings/device/update.png", 64, 75) + self._txt_reboot_icon = gui_app.texture("icons_mici/settings/device/reboot.png", 64, 70) self._txt_up_to_date_icon = gui_app.texture("icons_mici/settings/device/up_to_date.png", 64, 64) super().__init__("update openpilot", "", self._txt_update_icon) @@ -291,16 +291,16 @@ class DeviceLayoutMici(NavWidget): def uninstall_openpilot_callback(): ui_state.params.put_bool("DoUninstall", True) - reset_calibration_btn = BigButton("reset calibration", "", "icons_mici/settings/device/lkas.png") + reset_calibration_btn = BigButton("reset calibration", "", "icons_mici/settings/device/lkas.png", icon_size=(114, 60)) reset_calibration_btn.set_click_callback(lambda: _engaged_confirmation_callback(reset_calibration_callback, "reset")) uninstall_openpilot_btn = BigButton("uninstall openpilot", "", "icons_mici/settings/device/uninstall.png") uninstall_openpilot_btn.set_click_callback(lambda: _engaged_confirmation_callback(uninstall_openpilot_callback, "uninstall")) - reboot_btn = BigCircleButton("icons_mici/settings/device/reboot.png", red=False) + reboot_btn = BigCircleButton("icons_mici/settings/device/reboot.png", red=False, icon_size=(64, 70)) reboot_btn.set_click_callback(lambda: _engaged_confirmation_callback(reboot_callback, "reboot")) - self._power_off_btn = BigCircleButton("icons_mici/settings/device/power.png", red=True) + self._power_off_btn = BigCircleButton("icons_mici/settings/device/power.png", red=True, icon_size=(64, 66)) self._power_off_btn.set_click_callback(lambda: _engaged_confirmation_callback(power_off_callback, "power off")) self._load_languages() diff --git a/selfdrive/ui/mici/layouts/settings/network/__init__.py b/selfdrive/ui/mici/layouts/settings/network/__init__.py index 1faf49311..0d5e52783 100644 --- a/selfdrive/ui/mici/layouts/settings/network/__init__.py +++ b/selfdrive/ui/mici/layouts/settings/network/__init__.py @@ -4,7 +4,7 @@ from collections.abc import Callable from openpilot.system.ui.widgets.scroller import Scroller from openpilot.selfdrive.ui.mici.layouts.settings.network.wifi_ui import WifiUIMici -from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigMultiToggle, BigToggle, BigParamControl +from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigMultiToggle, BigParamControl, BigCircleToggle from openpilot.selfdrive.ui.mici.widgets.dialog import BigInputDialog from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.selfdrive.ui.lib.prime_state import PrimeType @@ -33,15 +33,14 @@ class NetworkLayoutMici(NavWidget): networks_updated=self._on_network_updated, ) - _tethering_icon = "icons_mici/settings/network/tethering.png" - # ******** Tethering ******** def tethering_toggle_callback(checked: bool): self._tethering_toggle_btn.set_enabled(False) self._network_metered_btn.set_enabled(False) self._wifi_manager.set_tethering_active(checked) - self._tethering_toggle_btn = BigToggle("enable tethering", "", toggle_callback=tethering_toggle_callback) + self._tethering_toggle_btn = BigCircleToggle("icons_mici/tethering_short.png", toggle_callback=tethering_toggle_callback, + icon_size=(82, 82), icon_offset=(0, 12)) def tethering_password_callback(password: str): if password: @@ -53,7 +52,7 @@ class NetworkLayoutMici(NavWidget): confirm_callback=tethering_password_callback) gui_app.set_modal_overlay(dlg) - txt_tethering = gui_app.texture(_tethering_icon, 64, 53) + txt_tethering = gui_app.texture("icons_mici/settings/network/tethering.png", 64, 54) self._tethering_password_btn = BigButton("tethering password", "", txt_tethering) self._tethering_password_btn.set_click_callback(tethering_password_clicked) diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index 565fef5af..23b89438d 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -34,12 +34,12 @@ class LoadingAnimation(Widget): class WifiIcon(Widget): def __init__(self): super().__init__() - self.set_rect(rl.Rectangle(0, 0, 89, 64)) + self.set_rect(rl.Rectangle(0, 0, 86, 64)) - self._wifi_low_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_low.png", 89, 64) - self._wifi_medium_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_medium.png", 89, 64) - self._wifi_full_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_full.png", 89, 64) - self._lock_txt = gui_app.texture("icons_mici/settings/network/new/lock.png", 23, 32) + self._wifi_low_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_low.png", 86, 64) + self._wifi_medium_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_medium.png", 86, 64) + self._wifi_full_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_full.png", 86, 64) + self._lock_txt = gui_app.texture("icons_mici/settings/network/new/lock.png", 22, 32) self._network: Network | None = None self._scale = 1.0 @@ -169,7 +169,7 @@ class ForgetButton(Widget): self._bg_txt = gui_app.texture("icons_mici/settings/network/new/forget_button.png", 100, 100) self._bg_pressed_txt = gui_app.texture("icons_mici/settings/network/new/forget_button_pressed.png", 100, 100) - self._trash_txt = gui_app.texture("icons_mici/settings/network/new/trash.png", 32, 36) + self._trash_txt = gui_app.texture("icons_mici/settings/network/new/trash.png", 35, 42) self.set_rect(rl.Rectangle(0, 0, 100 + self.HORIZONTAL_MARGIN * 2, 100)) def _handle_mouse_release(self, mouse_pos: MousePos): diff --git a/selfdrive/ui/mici/layouts/settings/settings.py b/selfdrive/ui/mici/layouts/settings/settings.py index a45277774..391789903 100644 --- a/selfdrive/ui/mici/layouts/settings/settings.py +++ b/selfdrive/ui/mici/layouts/settings/settings.py @@ -36,16 +36,16 @@ class SettingsLayout(NavWidget): self._params = Params() self._current_panel = None # PanelType.DEVICE - toggles_btn = BigButton("toggles", "", "icons_mici/settings/toggles_icon.png") + toggles_btn = BigButton("toggles", "", "icons_mici/settings.png") toggles_btn.set_click_callback(lambda: self._set_current_panel(PanelType.TOGGLES)) - network_btn = BigButton("network", "", "icons_mici/settings/network/wifi_strength_full.png") + network_btn = BigButton("network", "", "icons_mici/settings/network/wifi_strength_full.png", icon_size=(76, 56)) network_btn.set_click_callback(lambda: self._set_current_panel(PanelType.NETWORK)) - device_btn = BigButton("device", "", "icons_mici/settings/device_icon.png") + device_btn = BigButton("device", "", "icons_mici/settings/device_icon.png", icon_size=(74, 60)) device_btn.set_click_callback(lambda: self._set_current_panel(PanelType.DEVICE)) - developer_btn = BigButton("developer", "", "icons_mici/settings/developer_icon.png") + developer_btn = BigButton("developer", "", "icons_mici/settings/developer_icon.png", icon_size=(64, 60)) developer_btn.set_click_callback(lambda: self._set_current_panel(PanelType.DEVELOPER)) - firehose_btn = BigButton("firehose", "", "icons_mici/settings/comma_icon.png") + firehose_btn = BigButton("firehose", "", "icons_mici/settings/firehose.png", icon_size=(52, 62)) firehose_btn.set_click_callback(lambda: self._set_current_panel(PanelType.FIREHOSE)) self._scroller = Scroller([ diff --git a/selfdrive/ui/mici/onroad/alert_renderer.py b/selfdrive/ui/mici/onroad/alert_renderer.py index 7ee83ff88..64dd04c31 100644 --- a/selfdrive/ui/mici/onroad/alert_renderer.py +++ b/selfdrive/ui/mici/onroad/alert_renderer.py @@ -111,10 +111,10 @@ class AlertRenderer(Widget): self._load_icons() def _load_icons(self): - self._txt_turn_signal_left = gui_app.texture('icons_mici/onroad/turn_signal_left.png', 100, 91) - self._txt_turn_signal_right = gui_app.texture('icons_mici/onroad/turn_signal_right.png', 100, 91) - self._txt_blind_spot_left = gui_app.texture('icons_mici/onroad/blind_spot_left.png', 108, 128) - self._txt_blind_spot_right = gui_app.texture('icons_mici/onroad/blind_spot_right.png', 108, 128) + self._txt_turn_signal_left = gui_app.texture('icons_mici/onroad/turn_signal_left.png', 104, 96) + self._txt_turn_signal_right = gui_app.texture('icons_mici/onroad/turn_signal_right.png', 104, 96) + self._txt_blind_spot_left = gui_app.texture('icons_mici/onroad/blind_spot_left.png', 134, 150) + self._txt_blind_spot_right = gui_app.texture('icons_mici/onroad/blind_spot_right.png', 134, 150) def get_alert(self, sm: messaging.SubMaster) -> Alert | None: """Generate the current alert based on selfdrive state.""" diff --git a/selfdrive/ui/mici/onroad/hud_renderer.py b/selfdrive/ui/mici/onroad/hud_renderer.py index 7f489ccf9..a6fa1a62b 100644 --- a/selfdrive/ui/mici/onroad/hud_renderer.py +++ b/selfdrive/ui/mici/onroad/hud_renderer.py @@ -49,8 +49,8 @@ class TurnIntent(Widget): self._turn_intent_alpha_filter = FirstOrderFilter(0, 0.05, 1 / gui_app.target_fps) self._turn_intent_rotation_filter = FirstOrderFilter(0, 0.1, 1 / gui_app.target_fps) - self._txt_turn_intent_left: rl.Texture = gui_app.texture('icons_mici/turn_intent_left.png', 50, 19) - self._txt_turn_intent_right: rl.Texture = gui_app.texture('icons_mici/turn_intent_right.png', 50, 19) + self._txt_turn_intent_left: rl.Texture = gui_app.texture('icons_mici/turn_intent_left.png', 50, 20) + self._txt_turn_intent_right: rl.Texture = gui_app.texture('icons_mici/turn_intent_right.png', 50, 20) def _render(self, _): if self._turn_intent_alpha_filter.x > 1e-2: diff --git a/selfdrive/ui/mici/widgets/button.py b/selfdrive/ui/mici/widgets/button.py index 9678827a9..0b252c21a 100644 --- a/selfdrive/ui/mici/widgets/button.py +++ b/selfdrive/ui/mici/widgets/button.py @@ -29,9 +29,10 @@ class ScrollState(Enum): class BigCircleButton(Widget): - def __init__(self, icon: str, red: bool = False): + def __init__(self, icon: str, red: bool = False, icon_size: tuple[int, int] = (64, 53), icon_offset: tuple[int, int] = (0, 0)): super().__init__() self._red = red + self._icon_offset = icon_offset # State self.set_rect(rl.Rectangle(0, 0, 180, 180)) @@ -39,7 +40,7 @@ class BigCircleButton(Widget): self._scale_filter = BounceFilter(1.0, 0.1, 1 / gui_app.target_fps) # Icons - self._txt_icon = gui_app.texture(icon, 64, 53) + self._txt_icon = gui_app.texture(icon, *icon_size) self._txt_btn_disabled_bg = gui_app.texture("icons_mici/buttons/button_circle_disabled.png", 180, 180) self._txt_btn_bg = gui_app.texture("icons_mici/buttons/button_circle.png", 180, 180) @@ -66,13 +67,13 @@ class BigCircleButton(Widget): # draw icon icon_color = rl.WHITE if self.enabled else rl.Color(255, 255, 255, int(255 * 0.35)) - rl.draw_texture(self._txt_icon, int(self._rect.x + (self._rect.width - self._txt_icon.width) / 2), - int(self._rect.y + (self._rect.height - self._txt_icon.height) / 2), icon_color) + rl.draw_texture(self._txt_icon, int(self._rect.x + (self._rect.width - self._txt_icon.width) / 2 + self._icon_offset[0]), + int(self._rect.y + (self._rect.height - self._txt_icon.height) / 2 + self._icon_offset[1]), icon_color) class BigCircleToggle(BigCircleButton): - def __init__(self, icon: str, toggle_callback: Callable | None = None): - super().__init__(icon, False) + def __init__(self, icon: str, toggle_callback: Callable | None = None, icon_size: tuple[int, int] = (64, 53), icon_offset: tuple[int, int] = (0, 0)): + super().__init__(icon, False, icon_size=icon_size, icon_offset=icon_offset) self._toggle_callback = toggle_callback # State @@ -80,7 +81,7 @@ class BigCircleToggle(BigCircleButton): # Icons self._txt_toggle_enabled = gui_app.texture("icons_mici/buttons/toggle_dot_enabled.png", 66, 66) - self._txt_toggle_disabled = gui_app.texture("icons_mici/buttons/toggle_dot_disabled.png", 70, 70) # TODO: why discrepancy? + self._txt_toggle_disabled = gui_app.texture("icons_mici/buttons/toggle_dot_disabled.png", 66, 66) def set_checked(self, checked: bool): self._checked = checked @@ -104,11 +105,12 @@ class BigCircleToggle(BigCircleButton): class BigButton(Widget): """A lightweight stand-in for the Qt BigButton, drawn & updated each frame.""" - def __init__(self, text: str, value: str = "", icon: Union[str, rl.Texture] = ""): + def __init__(self, text: str, value: str = "", icon: Union[str, rl.Texture] = "", icon_size: tuple[int, int] = (64, 64)): super().__init__() self.set_rect(rl.Rectangle(0, 0, 402, 180)) self.text = text self.value = value + self._icon_size = icon_size self.set_icon(icon) self._scale_filter = BounceFilter(1.0, 0.1, 1 / gui_app.target_fps) @@ -134,7 +136,7 @@ class BigButton(Widget): self._scroll_state = ScrollState.PRE_SCROLL def set_icon(self, icon: Union[str, rl.Texture]): - self._txt_icon = gui_app.texture(icon, 64, 64) if isinstance(icon, str) and len(icon) else icon + self._txt_icon = gui_app.texture(icon, *self._icon_size) if isinstance(icon, str) and len(icon) else icon def set_rotate_icon(self, rotate: bool): if rotate and self._rotate_icon_t is not None: @@ -361,8 +363,9 @@ class BigParamControl(BigToggle): # TODO: param control base class class BigCircleParamControl(BigCircleToggle): - def __init__(self, icon: str, param: str, toggle_callback: Callable | None = None): - super().__init__(icon, toggle_callback) + def __init__(self, icon: str, param: str, toggle_callback: Callable | None = None, icon_size: tuple[int, int] = (64, 53), + icon_offset: tuple[int, int] = (0, 0)): + super().__init__(icon, toggle_callback, icon_size=icon_size, icon_offset=icon_offset) self._param = param self.params = Params() self.set_checked(self.params.get_bool(self._param, False)) diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index abd558aa8..67123d33a 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -147,10 +147,10 @@ class BigInputDialog(BigDialogBase): self._backspace_held_time: float | None = None - self._backspace_img = gui_app.texture("icons_mici/settings/keyboard/backspace.png", 44, 44) + self._backspace_img = gui_app.texture("icons_mici/settings/keyboard/backspace.png", 42, 36) self._backspace_img_alpha = FirstOrderFilter(0, 0.05, 1 / gui_app.target_fps) - self._enter_img = gui_app.texture("icons_mici/settings/keyboard/confirm.png", 44, 44) + self._enter_img = gui_app.texture("icons_mici/settings/keyboard/confirm.png", 42, 36) self._enter_img_alpha = FirstOrderFilter(0, 0.05, 1 / gui_app.target_fps) # rects for top buttons diff --git a/selfdrive/ui/mici/widgets/pairing_dialog.py b/selfdrive/ui/mici/widgets/pairing_dialog.py index e064205d5..88bab2d00 100644 --- a/selfdrive/ui/mici/widgets/pairing_dialog.py +++ b/selfdrive/ui/mici/widgets/pairing_dialog.py @@ -24,7 +24,7 @@ class PairingDialog(NavWidget): self._qr_texture: rl.Texture | None = None self._last_qr_generation = float("-inf") - self._txt_pair = gui_app.texture("icons_mici/settings/device/pair.png", 84, 64) + self._txt_pair = gui_app.texture("icons_mici/settings/device/pair.png", 33, 60) self._pair_label = MiciLabel("pair with comma connect", 48, font_weight=FontWeight.BOLD, color=rl.Color(255, 255, 255, int(255 * 0.9)), line_height=40, wrap_text=True) diff --git a/system/ui/widgets/mici_keyboard.py b/system/ui/widgets/mici_keyboard.py index 7459dc573..a81cf8530 100644 --- a/system/ui/widgets/mici_keyboard.py +++ b/system/ui/widgets/mici_keyboard.py @@ -105,13 +105,15 @@ class SmallKey(Key): class IconKey(Key): - def __init__(self, icon: str, vertical_align: str = "center", char: str = ""): + def __init__(self, icon: str, vertical_align: str = "center", char: str = "", icon_size: tuple[int, int] = (38, 38)): super().__init__(char) - self._icon = gui_app.texture(icon, 38, 38) + self._icon_size = icon_size + self._icon = gui_app.texture(icon, *icon_size) self._vertical_align = vertical_align - def set_icon(self, icon: str): - self._icon = gui_app.texture(icon, 38, 38) + def set_icon(self, icon: str, icon_size: tuple[int, int] | None = None): + size = icon_size if icon_size is not None else self._icon_size + self._icon = gui_app.texture(icon, *size) def _render(self, _): scale = np.interp(self._size_filter.x, [CHAR_FONT_SIZE, CHAR_NEAR_FONT_SIZE], [1, 1.5]) @@ -167,8 +169,8 @@ class MiciKeyboard(Widget): self._super_special_keys = [[Key(char) for char in row] for row in super_special_chars] # control keys - self._space_key = IconKey("icons_mici/settings/keyboard/space.png", char=" ", vertical_align="bottom") - self._caps_key = IconKey("icons_mici/settings/keyboard/caps_lower.png") + self._space_key = IconKey("icons_mici/settings/keyboard/space.png", char=" ", vertical_align="bottom", icon_size=(43, 14)) + self._caps_key = IconKey("icons_mici/settings/keyboard/caps_lower.png", icon_size=(38, 33)) # these two are in different places on some layouts self._123_key, self._123_key2 = SmallKey("123"), SmallKey("123") self._abc_key = SmallKey("abc") @@ -269,14 +271,14 @@ class MiciKeyboard(Widget): self._set_keys(self._upper_keys if cycle else self._lower_keys) if not cycle: self._caps_state = CapsState.LOWER - self._caps_key.set_icon("icons_mici/settings/keyboard/caps_lower.png") + self._caps_key.set_icon("icons_mici/settings/keyboard/caps_lower.png", icon_size=(38, 33)) else: if self._caps_state == CapsState.LOWER: self._caps_state = CapsState.UPPER - self._caps_key.set_icon("icons_mici/settings/keyboard/caps_upper.png") + self._caps_key.set_icon("icons_mici/settings/keyboard/caps_upper.png", icon_size=(38, 33)) elif self._caps_state == CapsState.UPPER: self._caps_state = CapsState.LOCK - self._caps_key.set_icon("icons_mici/settings/keyboard/caps_lock.png") + self._caps_key.set_icon("icons_mici/settings/keyboard/caps_lock.png", icon_size=(39, 38)) else: self._set_uppercase(False) From 2fc10e82998373a9bfe88f1de3d8179393f7567c Mon Sep 17 00:00:00 2001 From: felsager <76905857+felsager@users.noreply.github.com> Date: Tue, 27 Jan 2026 17:15:39 -0800 Subject: [PATCH 783/910] Maneuver: log drel and use it in tuning report (#37033) --- .../test/longitudinal_maneuvers/maneuver.py | 3 ++- .../mpc_longitudinal_tuning_report.py | 17 ++++++++++------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/selfdrive/test/longitudinal_maneuvers/maneuver.py b/selfdrive/test/longitudinal_maneuvers/maneuver.py index dfd5b3e10..ba0379f2d 100644 --- a/selfdrive/test/longitudinal_maneuvers/maneuver.py +++ b/selfdrive/test/longitudinal_maneuvers/maneuver.py @@ -60,7 +60,8 @@ class Maneuver: log['distance_lead'], log['speed'], speed_lead, - log['acceleration']])) + log['acceleration'], + log['d_rel']])) if d_rel < .4 and (self.only_radar or prob_lead > 0.5): print("Crashed!!!!") diff --git a/tools/longitudinal_maneuvers/mpc_longitudinal_tuning_report.py b/tools/longitudinal_maneuvers/mpc_longitudinal_tuning_report.py index 583c6240e..8c1a60f5b 100644 --- a/tools/longitudinal_maneuvers/mpc_longitudinal_tuning_report.py +++ b/tools/longitudinal_maneuvers/mpc_longitudinal_tuning_report.py @@ -7,16 +7,18 @@ from openpilot.selfdrive.test.longitudinal_maneuvers.maneuver import Maneuver from openpilot.selfdrive.controls.tests.test_following_distance import desired_follow_distance TIME = 0 +LEAD_DISTANCE= 2 EGO_V = 3 EGO_A = 5 -LEAD_DISTANCE= 2 +D_REL = 6 axis_labels = ['Time (s)', 'Ego position (m)', - 'Lead distance (m)', + 'Lead absolute position (m)', 'Ego Velocity (m/s)', 'Lead Velocity (m/s)', 'Ego acceleration (m/s^2)', + 'Lead distance (m)' ] @@ -81,7 +83,7 @@ for speed in np.arange(0,45,5): htmls.append(markdown.markdown('# ' + name)) htmls.append(get_html_from_results(results, labels, EGO_A)) -htmls.append(get_html_from_results(results, labels, LEAD_DISTANCE)) +htmls.append(get_html_from_results(results, labels, D_REL)) results = {} @@ -102,6 +104,7 @@ for oscil in np.arange(0, 10, 1): labels.append(f'{oscil} m/s oscilliation size') htmls.append(markdown.markdown('# ' + name)) +htmls.append(get_html_from_results(results, labels, D_REL)) htmls.append(get_html_from_results(results, labels, EGO_V)) htmls.append(get_html_from_results(results, labels, EGO_A)) @@ -126,7 +129,7 @@ for distance in np.arange(20, 140, 10): htmls.append(markdown.markdown('# ' + name)) htmls.append(get_html_from_results(results, labels, EGO_V)) -htmls.append(get_html_from_results(results, labels, LEAD_DISTANCE)) +htmls.append(get_html_from_results(results, labels, D_REL)) results = {} @@ -148,7 +151,7 @@ for distance in np.arange(20, 140, 10): htmls.append(markdown.markdown('# ' + name)) htmls.append(get_html_from_results(results, labels, EGO_V)) -htmls.append(get_html_from_results(results, labels, LEAD_DISTANCE)) +htmls.append(get_html_from_results(results, labels, D_REL)) results = {} @@ -170,7 +173,7 @@ for stop_time in np.arange(4, 14, 1): htmls.append(markdown.markdown('# ' + name)) htmls.append(get_html_from_results(results, labels, EGO_A)) -htmls.append(get_html_from_results(results, labels, LEAD_DISTANCE)) +htmls.append(get_html_from_results(results, labels, D_REL)) results = {} @@ -193,7 +196,7 @@ for speed in np.arange(0, 40, 5): htmls.append(markdown.markdown('# ' + name)) htmls.append(get_html_from_results(results, labels, EGO_A)) -htmls.append(get_html_from_results(results, labels, LEAD_DISTANCE)) +htmls.append(get_html_from_results(results, labels, D_REL)) results = {} From 0b958f7c9ae682e0ab95d0dc9f45f605be0dfce0 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 27 Jan 2026 19:59:25 -0800 Subject: [PATCH 784/910] onroad: fill bookmark icon when activated (#37034) * bookmark fill * and here's what i would have done * add --- selfdrive/ui/mici/onroad/augmented_road_view.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/mici/onroad/augmented_road_view.py b/selfdrive/ui/mici/onroad/augmented_road_view.py index 71ca03ccc..4e00a3aaf 100644 --- a/selfdrive/ui/mici/onroad/augmented_road_view.py +++ b/selfdrive/ui/mici/onroad/augmented_road_view.py @@ -46,6 +46,8 @@ class BookmarkIcon(Widget): super().__init__() self._bookmark_callback = bookmark_callback self._icon = gui_app.texture("icons_mici/onroad/bookmark.png", 180, 180) + self._icon_fill = gui_app.texture("icons_mici/onroad/bookmark_fill.png", 180, 180) + self._active_icon = self._icon self._offset_filter = BounceFilter(0.0, 0.1, 1 / gui_app.target_fps) # State @@ -84,6 +86,7 @@ class BookmarkIcon(Widget): if self._offset_filter.x < 1e-3: self._interacting = False + self._active_icon = self._icon def _handle_mouse_event(self, mouse_event: MouseEvent): if not ui_state.started: @@ -96,6 +99,7 @@ class BookmarkIcon(Widget): self._is_swiping = True self._is_swiping_left = False self._state = BookmarkState.DRAGGING + self._active_icon = self._icon elif mouse_event.left_down and self._is_swiping: self._swipe_current_x = mouse_event.pos.x @@ -112,6 +116,7 @@ class BookmarkIcon(Widget): if swipe_distance > self.PEEK_THRESHOLD: self._state = BookmarkState.TRIGGERED self._triggered_time = rl.get_time() + self._active_icon = self._icon_fill self._bookmark_callback() else: # Otherwise, transition back to hidden @@ -125,8 +130,8 @@ class BookmarkIcon(Widget): """Render the bookmark icon.""" if self._offset_filter.x > 0: icon_x = self.rect.x + self.rect.width - round(self._offset_filter.x) - icon_y = self.rect.y + (self.rect.height - self._icon.height) / 2 # Vertically centered - rl.draw_texture(self._icon, int(icon_x), int(icon_y), rl.WHITE) + icon_y = self.rect.y + (self.rect.height - self._active_icon.height) / 2 # Vertically centered + rl.draw_texture(self._active_icon, int(icon_x), int(icon_y), rl.WHITE) class AugmentedRoadView(CameraView): From d849d6f1d7a7c380038a9e7e72df70a125fb7b03 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 27 Jan 2026 21:28:50 -0800 Subject: [PATCH 785/910] mici keyboard: bold SmallKey (#37035) bold SmallKey --- system/ui/widgets/mici_keyboard.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/system/ui/widgets/mici_keyboard.py b/system/ui/widgets/mici_keyboard.py index a81cf8530..7fc384780 100644 --- a/system/ui/widgets/mici_keyboard.py +++ b/system/ui/widgets/mici_keyboard.py @@ -38,10 +38,10 @@ def fast_euclidean_distance(dx, dy): class Key(Widget): - def __init__(self, char: str): + def __init__(self, char: str, font_weight: FontWeight = FontWeight.SEMI_BOLD): super().__init__() self.char = char - self._font = gui_app.font(FontWeight.SEMI_BOLD) + self._font = gui_app.font(font_weight) self._x_filter = BounceFilter(0.0, 0.1 * ANIMATION_SCALE, 1 / gui_app.target_fps) self._y_filter = BounceFilter(0.0, 0.1 * ANIMATION_SCALE, 1 / gui_app.target_fps) self._size_filter = BounceFilter(CHAR_FONT_SIZE, 0.1 * ANIMATION_SCALE, 1 / gui_app.target_fps) @@ -97,7 +97,7 @@ class Key(Widget): class SmallKey(Key): def __init__(self, chars: str): - super().__init__(chars) + super().__init__(chars, FontWeight.BOLD) self._size_filter.x = NUMBER_LAYER_SWITCH_FONT_SIZE def set_font_size(self, size: float): From 5ba0793010616412d700cebb511eac3565439520 Mon Sep 17 00:00:00 2001 From: infiniteCable2 Date: Wed, 28 Jan 2026 17:37:25 +0100 Subject: [PATCH 786/910] Disable audible Car Steer Alerts toggle --- common/params_keys.h | 1 + opendbc_repo | 2 +- selfdrive/controls/controlsd.py | 3 +++ selfdrive/ui/layouts/settings/ictoggles.py | 9 +++++++++ selfdrive/ui/mici/layouts/settings/ictoggles.py | 3 +++ 5 files changed, 17 insertions(+), 1 deletion(-) diff --git a/common/params_keys.h b/common/params_keys.h index 88403ad34..2d4bb1a35 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -143,6 +143,7 @@ inline static std::unordered_map keys = { {"EnableSLPredReactToCurves", {PERSISTENT, BOOL}}, {"BatteryDetails", {PERSISTENT, BOOL}}, {"ForceRHDForBSM", {PERSISTENT, BOOL}}, + {"DisableCarSteerAlerts", {PERSISTENT, BOOL}}, // --- sunnypilot params --- // {"ApiCache_DriveStats", {PERSISTENT, JSON}}, diff --git a/opendbc_repo b/opendbc_repo index b8c700137..f2aefa716 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit b8c700137a9cf96e0f9b5281b4ee817341e75e4e +Subproject commit f2aefa716be4829499c5b3164ef9962469960475 diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index d5e9c132a..4e7969e17 100644 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -65,6 +65,7 @@ class Controls(ControlsExt): self.enable_long_comfort_mode = self.params.get_bool("EnableLongComfortMode") self.smooth_steer = PT2Filter(46.0, 1.0, DT_CTRL) self.force_rhd_for_bsm = self.params.get_bool("ForceRHDForBSM") + self.disable_car_steer_alerts = self.params.get_bool("DisableCarSteerAlerts") self.pose_calibrator = PoseCalibrator() self.calibrated_pose: Pose | None = None @@ -99,6 +100,7 @@ class Controls(ControlsExt): self.enable_pred_react_to_curves = self.params.get_bool("EnableSLPredReactToCurves") self.force_rhd_for_bsm = self.params.get_bool("ForceRHDForBSM") self.enable_long_comfort_mode = self.params.get_bool("EnableLongComfortMode") + self.disable_car_steer_alerts = self.params.get_bool("DisableCarSteerAlerts") def state_control(self): CS = self.sm['carState'] @@ -195,6 +197,7 @@ class Controls(ControlsExt): CC.steerLimited = self.steer_limited_by_safety CC.forceRHDForBSM = self.force_rhd_for_bsm CC.longComfortMode = self.enable_long_comfort_mode + CC.disableCarSteerAlerts = self.disable_car_steer_alerts # Orientation and angle rates can be useful for carcontroller # Only calibrated (car) frame is relevant for the carcontroller diff --git a/selfdrive/ui/layouts/settings/ictoggles.py b/selfdrive/ui/layouts/settings/ictoggles.py index b1795a1ae..f4c6898d3 100644 --- a/selfdrive/ui/layouts/settings/ictoggles.py +++ b/selfdrive/ui/layouts/settings/ictoggles.py @@ -45,6 +45,9 @@ DESCRIPTIONS = { "DisableScreenTimer": tr_noop( "The onroad screen is turned of after 10 seconds. It will be temporarily enabled on alerts" ), + "DisableCarSteerAlerts": tr_noop( + "Disables audible steering alerts from car" + ), } @@ -121,6 +124,12 @@ class ICTogglesLayout(Widget): "eye_closed.png", False, ), + "DisableCarSteerAlerts": ( + lambda: tr("VW: Disable Car Steer Alert Chime"), + DESCRIPTIONS["DisableCarSteerAlerts"], + "chffr_wheel.png", + False, + ), } self._toggles = {} diff --git a/selfdrive/ui/mici/layouts/settings/ictoggles.py b/selfdrive/ui/mici/layouts/settings/ictoggles.py index ff4bb3e41..89eea6984 100644 --- a/selfdrive/ui/mici/layouts/settings/ictoggles.py +++ b/selfdrive/ui/mici/layouts/settings/ictoggles.py @@ -22,6 +22,7 @@ class ICTogglesLayoutMici(NavWidget): enable_sl_pred_sl = BigParamControl("VW: Predicative - Reaction to Speed Limits", "EnableSLPredReactToSL") enable_sl_pred_curve = BigParamControl("VW: Predicative - Reaction to Curves", "EnableSLPredReactToCurves") force_rhd_bsm = BigParamControl("VW: Force RHD for BSM", "ForceRHDForBSM") + disable_car_steer_alerts = BigParamControl("VW: Disable Car Steer Alert Chime", "DisableCarSteerAlerts") enable_smooth_steer = BigParamControl("Steer Smoothing", "EnableSmoothSteer") enable_dark_mode = BigParamControl("Dark Mode", "DarkMode") enable_onroad_screen_timer = BigParamControl("Onroad Screen Timeout", "DisableScreenTimer") @@ -35,6 +36,7 @@ class ICTogglesLayoutMici(NavWidget): enable_sl_pred_sl, enable_sl_pred_curve, force_rhd_bsm, + disable_car_steer_alerts, enable_smooth_steer, enable_dark_mode, enable_onroad_screen_timer, @@ -49,6 +51,7 @@ class ICTogglesLayoutMici(NavWidget): ("EnableSLPredReactToSL", enable_sl_pred_sl), ("EnableSLPredReactToCurves", enable_sl_pred_curve), ("ForceRHDForBSM", force_rhd_bsm), + ("DisableCarSteerAlerts", disable_car_steer_alerts), ("EnableSmoothSteer", enable_smooth_steer), ("DarkMode", enable_dark_mode), ("DisableScreenTimer", enable_onroad_screen_timer), From 2f0e3038d43d569efe499f3efa585f53674f7adf Mon Sep 17 00:00:00 2001 From: infiniteCable2 Date: Wed, 28 Jan 2026 17:42:17 +0100 Subject: [PATCH 787/910] order --- selfdrive/ui/layouts/settings/ictoggles.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/selfdrive/ui/layouts/settings/ictoggles.py b/selfdrive/ui/layouts/settings/ictoggles.py index f4c6898d3..70aaaf8b1 100644 --- a/selfdrive/ui/layouts/settings/ictoggles.py +++ b/selfdrive/ui/layouts/settings/ictoggles.py @@ -106,6 +106,12 @@ class ICTogglesLayout(Widget): "chffr_wheel.png", False, ), + "DisableCarSteerAlerts": ( + lambda: tr("VW: Disable Car Steer Alert Chime"), + DESCRIPTIONS["DisableCarSteerAlerts"], + "chffr_wheel.png", + False, + ), "EnableSmoothSteer": ( lambda: tr("Steer Smoothing"), DESCRIPTIONS["EnableSmoothSteer"], @@ -124,12 +130,6 @@ class ICTogglesLayout(Widget): "eye_closed.png", False, ), - "DisableCarSteerAlerts": ( - lambda: tr("VW: Disable Car Steer Alert Chime"), - DESCRIPTIONS["DisableCarSteerAlerts"], - "chffr_wheel.png", - False, - ), } self._toggles = {} From e89e4407c57f0616bcdc3bd3d48bd19e95274eb9 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 28 Jan 2026 19:50:53 -0800 Subject: [PATCH 788/910] Tweak stockLkas alert (#37040) * stockLkas alert is orange, small, mid prio, ldw vis alert * copy exactly from existing ldw alert with prompt sound, black alert --- selfdrive/selfdrived/events.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/selfdrive/selfdrived/events.py b/selfdrive/selfdrived/events.py index 35d4bda42..0e37a959c 100755 --- a/selfdrive/selfdrived/events.py +++ b/selfdrive/selfdrived/events.py @@ -479,10 +479,10 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = { EventName.stockLkas: { ET.PERMANENT: Alert( - "TAKE CONTROL", "Stock LKAS: Lane Departure Detected", - AlertStatus.critical, AlertSize.full, - Priority.HIGH, VisualAlert.fcw, AudibleAlert.none, 2.), + "", + AlertStatus.userPrompt, AlertSize.small, + Priority.LOW, VisualAlert.ldw, AudibleAlert.prompt, 3.), ET.NO_ENTRY: NoEntryAlert("Stock LKAS: Lane Departure Detected"), }, From bddd20c4252303362f33241ac35ee7d51acec87b Mon Sep 17 00:00:00 2001 From: T3d Date: Thu, 29 Jan 2026 19:36:51 +0100 Subject: [PATCH 789/910] Complete french translations in app_fr.po (#37023) --- selfdrive/ui/translations/app_fr.po | 94 +++++++++++++++-------------- 1 file changed, 50 insertions(+), 44 deletions(-) diff --git a/selfdrive/ui/translations/app_fr.po b/selfdrive/ui/translations/app_fr.po index f883d4d48..409761588 100644 --- a/selfdrive/ui/translations/app_fr.po +++ b/selfdrive/ui/translations/app_fr.po @@ -5,10 +5,10 @@ # msgid "" msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" +"Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-10-23 00:50-0700\n" -"PO-Revision-Date: 2025-10-20 18:19-0700\n" +"PO-Revision-Date: 2026-01-24 12:37+0100\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" "Language: fr\n" @@ -16,21 +16,22 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" +"X-Generator: Poedit 3.8\n" #: selfdrive/ui/layouts/settings/device.py:160 #, python-format msgid " Steering torque response calibration is complete." -msgstr "" +msgstr " L'étalonnage de la réponse du couple de direction est terminé." #: selfdrive/ui/layouts/settings/device.py:158 #, python-format msgid " Steering torque response calibration is {}% complete." -msgstr "" +msgstr " L'étalonnage de la réponse du couple de direction est terminé à {}%." #: selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." -msgstr "" +msgstr " Votre appareil est orienté {:.1f}° {} et {:.1f}° {}." #: selfdrive/ui/layouts/sidebar.py:43 msgid "--" @@ -79,12 +80,13 @@ msgstr "" #: selfdrive/ui/layouts/settings/device.py:148 #, python-format msgid "

Steering lag calibration is complete." -msgstr "" +msgstr "

L'étalonnage du délai de réponse de la direction est terminé." #: selfdrive/ui/layouts/settings/device.py:146 #, python-format msgid "

Steering lag calibration is {}% complete." msgstr "" +"

L'étalonnage du délai de réponse de la direction est terminé à {}%." #: selfdrive/ui/layouts/settings/firehose.py:138 #, python-format @@ -107,7 +109,7 @@ msgstr "AJOUTER" #: system/ui/widgets/network.py:139 #, python-format msgid "APN Setting" -msgstr "" +msgstr "Paramètres APN" #: selfdrive/ui/widgets/offroad_alerts.py:109 #, python-format @@ -117,7 +119,7 @@ msgstr "Accuser réception d'actionnement excessif" #: system/ui/widgets/network.py:74 system/ui/widgets/network.py:95 #, python-format msgid "Advanced" -msgstr "" +msgstr "Avancé" #: selfdrive/ui/layouts/settings/toggles.py:98 #, python-format @@ -208,18 +210,18 @@ msgstr "CONNECTER" #: system/ui/widgets/network.py:369 #, python-format msgid "CONNECTING..." -msgstr "CONNECTER" +msgstr "CONNECTER..." #: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35 #: system/ui/widgets/keyboard.py:81 system/ui/widgets/network.py:318 #, python-format msgid "Cancel" -msgstr "" +msgstr "Annuler" #: system/ui/widgets/network.py:134 #, python-format msgid "Cellular Metered" -msgstr "" +msgstr "Données cellulaire limitées" #: selfdrive/ui/layouts/settings/device.py:68 #, python-format @@ -230,7 +232,7 @@ msgstr "Changer la langue" #, python-format msgid "Changing this setting will restart openpilot if the car is powered on." msgstr "" -" La modification de ce réglage redémarrera openpilot si la voiture est sous " +"La modification de ce réglage redémarrera openpilot si la voiture est sous " "tension." #: selfdrive/ui/widgets/pairing_dialog.py:129 @@ -318,7 +320,7 @@ msgstr "Personnalité de conduite" #: system/ui/widgets/network.py:123 system/ui/widgets/network.py:139 #, python-format msgid "EDIT" -msgstr "" +msgstr "EDITER" #: selfdrive/ui/layouts/sidebar.py:138 msgid "ERROR" @@ -387,22 +389,22 @@ msgstr "" #: system/ui/widgets/network.py:204 #, python-format msgid "Enter APN" -msgstr "" +msgstr "Saisir l'APN" #: system/ui/widgets/network.py:241 #, python-format msgid "Enter SSID" -msgstr "" +msgstr "Entrer le SSID" #: system/ui/widgets/network.py:254 #, python-format msgid "Enter new tethering password" -msgstr "" +msgstr "Saisir le mot de passe du partage de connexion" #: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 #, python-format msgid "Enter password" -msgstr "" +msgstr "Saisir le mot de passe" #: selfdrive/ui/widgets/ssh_key.py:89 #, python-format @@ -412,7 +414,7 @@ msgstr "Entrez votre nom d'utilisateur GitHub" #: system/ui/widgets/list_view.py:123 system/ui/widgets/list_view.py:160 #, python-format msgid "Error" -msgstr "" +msgstr "Erreur" #: selfdrive/ui/layouts/settings/toggles.py:52 #, python-format @@ -431,7 +433,7 @@ msgstr "" #: system/ui/widgets/network.py:373 #, python-format msgid "FORGETTING..." -msgstr "" +msgstr "OUBLIER..." #: selfdrive/ui/widgets/setup.py:44 #, python-format @@ -493,12 +495,12 @@ msgstr "" #: system/ui/widgets/network.py:318 system/ui/widgets/network.py:451 #, python-format msgid "Forget" -msgstr "" +msgstr "Oublier" #: system/ui/widgets/network.py:319 #, python-format msgid "Forget Wi-Fi Network \"{}\"?" -msgstr "" +msgstr "Oublier le réseau Wi-Fi \"{}\" ?" #: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 msgid "GOOD" @@ -532,7 +534,7 @@ msgstr "INSTALLER" #: system/ui/widgets/network.py:150 #, python-format msgid "IP Address" -msgstr "" +msgstr "Adresse IP" #: selfdrive/ui/layouts/settings/software.py:53 #, python-format @@ -574,7 +576,7 @@ msgstr "" #: selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "N/A" -msgstr "" +msgstr "NC" #: selfdrive/ui/layouts/sidebar.py:142 msgid "NO" @@ -592,7 +594,7 @@ msgstr "Aucune clé SSH trouvée" #: selfdrive/ui/widgets/ssh_key.py:126 #, python-format msgid "No SSH keys found for user '{}'" -msgstr "Aucune clé SSH trouvée pour l'utilisateur '{username}'" +msgstr "Aucune clé SSH trouvée pour l'utilisateur '{}'" #: selfdrive/ui/widgets/offroad_alerts.py:320 #, python-format @@ -677,11 +679,15 @@ msgstr "Éteindre" #, python-format msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "" +"Eviter les transferts de données volumineux lorsque vous êtes connecté à un " +"réseau Wi-Fi limité" #: system/ui/widgets/network.py:135 #, python-format msgid "Prevent large data uploads when on a metered cellular connection" msgstr "" +"Eviter les transferts de données volumineux lors d'une connexion à un réseau " +"cellulaire limité" #: selfdrive/ui/layouts/settings/device.py:25 msgid "" @@ -802,32 +808,32 @@ msgstr "Consultez les règles, fonctionnalités et limitations d'openpilot" #: selfdrive/ui/layouts/settings/software.py:61 #, python-format msgid "SELECT" -msgstr "" +msgstr "SELECTIONNER" #: selfdrive/ui/layouts/settings/developer.py:53 #, python-format msgid "SSH Keys" -msgstr "" +msgstr "Clefs SSH" #: system/ui/widgets/network.py:310 #, python-format msgid "Scanning Wi-Fi networks..." -msgstr "" +msgstr "Analyse des réseaux Wi-Fi..." #: system/ui/widgets/option_dialog.py:36 #, python-format msgid "Select" -msgstr "" +msgstr "Sélectionner" #: selfdrive/ui/layouts/settings/software.py:183 #, python-format msgid "Select a branch" -msgstr "" +msgstr "Sélectionner une branche" #: selfdrive/ui/layouts/settings/device.py:91 #, python-format msgid "Select a language" -msgstr "" +msgstr "Sélectionner un langage" #: selfdrive/ui/layouts/settings/device.py:60 #, python-format @@ -880,12 +886,12 @@ msgstr "TEMPÉRATURE" #: selfdrive/ui/layouts/settings/software.py:61 #, python-format msgid "Target Branch" -msgstr "" +msgstr "Branche cible" #: system/ui/widgets/network.py:124 #, python-format msgid "Tethering Password" -msgstr "" +msgstr "Mot de passe du partage de connexion" #: selfdrive/ui/layouts/settings/settings.py:64 msgid "Toggles" @@ -986,12 +992,12 @@ msgstr "Wi‑Fi" #: system/ui/widgets/network.py:144 #, python-format msgid "Wi-Fi Network Metered" -msgstr "" +msgstr "Réseau Wi-Fi limité" #: system/ui/widgets/network.py:314 #, python-format msgid "Wrong password" -msgstr "" +msgstr "Mauvais mot de passe" #: selfdrive/ui/layouts/onboarding.py:145 #, python-format @@ -1020,12 +1026,12 @@ msgstr "comma prime" #: system/ui/widgets/network.py:142 #, python-format msgid "default" -msgstr "" +msgstr "défaut" #: selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid "down" -msgstr "" +msgstr "bas" #: selfdrive/ui/layouts/settings/software.py:106 #, python-format @@ -1035,7 +1041,7 @@ msgstr "échec de la vérification de mise à jour" #: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 #, python-format msgid "for \"{}\"" -msgstr "" +msgstr "pour \"{}\"" #: selfdrive/ui/onroad/hud_renderer.py:177 #, python-format @@ -1045,17 +1051,17 @@ msgstr "km/h" #: system/ui/widgets/network.py:204 #, python-format msgid "leave blank for automatic configuration" -msgstr "" +msgstr "ne pas remplir pour une configuration automatique" #: selfdrive/ui/layouts/settings/device.py:134 #, python-format msgid "left" -msgstr "" +msgstr "gauche" #: system/ui/widgets/network.py:142 #, python-format msgid "metered" -msgstr "" +msgstr "limité" #: selfdrive/ui/onroad/hud_renderer.py:177 #, python-format @@ -1116,7 +1122,7 @@ msgid "" "openpilot is continuously calibrating, resetting is rarely required. " "Resetting calibration will restart openpilot if the car is powered on." msgstr "" -" La modification de ce réglage redémarrera openpilot si la voiture est sous " +"La modification de ce réglage redémarrera openpilot si la voiture est sous " "tension." #: selfdrive/ui/layouts/settings/firehose.py:20 @@ -1153,17 +1159,17 @@ msgstr "" #: selfdrive/ui/layouts/settings/device.py:134 #, python-format msgid "right" -msgstr "" +msgstr "droite" #: system/ui/widgets/network.py:142 #, python-format msgid "unmetered" -msgstr "" +msgstr "non limité" #: selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid "up" -msgstr "" +msgstr "haut" #: selfdrive/ui/layouts/settings/software.py:117 #, python-format From df7f426405de7ea4885cc54cd44c8ee9c9152f8c Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 30 Jan 2026 00:09:19 -0800 Subject: [PATCH 790/910] bump opendbc (#37043) * bump opendbc * update refs --- opendbc_repo | 2 +- selfdrive/test/process_replay/ref_commit | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/opendbc_repo b/opendbc_repo index d424d1f24..c8e92d046 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit d424d1f247384b68923b8093875e1a370ef8221d +Subproject commit c8e92d046324be54cfedccd2a27101060861e82b diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 7b9039180..85b79391c 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -77951c4ccd0916b87c8dfda9faa33cd2d5d2cc11 \ No newline at end of file +67f3daf309dc6cbb6844fcbaeb83e6596637e551 \ No newline at end of file From 569099eb70eef3c379bb61c84e4dd0504c63bd99 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 30 Jan 2026 00:09:44 -0800 Subject: [PATCH 791/910] update docs --- docs/CARS.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index b34967939..65f79cdba 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -4,7 +4,7 @@ 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. -# 327 Supported Cars +# 328 Supported Cars |Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Hardware Needed
 |Video|Setup Video| |---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| @@ -103,6 +103,7 @@ A supported vehicle is one that just works when you install a comma device. All |Honda|N-Box 2018|All|openpilot available[1](#footnotes)|0 mph|11 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Honda|Odyssey 2018-20|Honda Sensing|openpilot|26 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Honda|Odyssey 2021-26|All|openpilot available[1](#footnotes)|0 mph|43 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Odyssey (Taiwan) 2018-19|Honda Sensing|openpilot|19 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Honda|Passport 2019-25|All|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Honda|Passport 2026|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Honda|Pilot 2016-22|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| @@ -202,7 +203,7 @@ A supported vehicle is one that just works when you install a comma device. All |Lexus|IS 2017-19|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Lexus|IS 2022-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Lexus|LC 2024-25|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Lexus|LS 2018|All except Lexus Safety System+ A|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lexus|LS 2018|All except Lexus Safety System+ A|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Lexus|NX 2018-19|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Lexus|NX 2020-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Lexus|NX Hybrid 2018-19|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| From 32f0a2cbbc0f39b23105c906ff0d77bc4a746c7f Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 30 Jan 2026 00:30:11 -0800 Subject: [PATCH 792/910] bump opendbc (#37046) bump --- opendbc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc_repo b/opendbc_repo index c8e92d046..e76c2cf5b 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit c8e92d046324be54cfedccd2a27101060861e82b +Subproject commit e76c2cf5bb0042bc5822efa78fff0362feed7b54 From db3df61c34cc8280e81f3204f58454bf3393a743 Mon Sep 17 00:00:00 2001 From: King Art Date: Sat, 31 Jan 2026 01:16:56 +0000 Subject: [PATCH 793/910] fix non-determinism in modeld build (#37042) * fix non-determinism in selfservice model build also trim down model compile dependencies to the minimum required * Apply suggestions from code review --------- Co-authored-by: Shane Smiskol --- selfdrive/modeld/SConscript | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/modeld/SConscript b/selfdrive/modeld/SConscript index a184b6a23..91f359744 100644 --- a/selfdrive/modeld/SConscript +++ b/selfdrive/modeld/SConscript @@ -29,7 +29,7 @@ for pathdef, fn in {'TRANSFORM': 'transforms/transform.cl', 'LOADYUV': 'transfor cython_libs = envCython["LIBS"] + libs commonmodel_lib = lenv.Library('commonmodel', common_src) lenvCython.Program('models/commonmodel_pyx.so', 'models/commonmodel_pyx.pyx', LIBS=[commonmodel_lib, *cython_libs], FRAMEWORKS=frameworks) -tinygrad_files = ["#"+x for x in glob.glob(env.Dir("#tinygrad_repo").relpath + "/**", recursive=True, root_dir=env.Dir("#").abspath) if 'pycache' not in x] +tinygrad_files = sorted(["#"+x for x in glob.glob(env.Dir("#tinygrad_repo").relpath + "/**", recursive=True, root_dir=env.Dir("#").abspath) if 'pycache' not in x]) # Get model metadata for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']: From c35df583a5cb287304a88701b2a9040f74788ecc Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 31 Jan 2026 15:52:50 -0800 Subject: [PATCH 794/910] tools: enable log caching by default (#36962) --- .github/workflows/repo-maintenance.yaml | 2 +- .github/workflows/tests.yaml | 2 +- selfdrive/car/tests/big_cars_test.sh | 1 - selfdrive/test/process_replay/README.md | 2 +- tools/lib/tests/test_caching.py | 13 ++++++++----- tools/lib/tests/test_logreader.py | 10 ++++++++-- tools/lib/url_file.py | 4 ++-- tools/replay/can_replay.py | 2 -- tools/tuning/measure_steering_accuracy.py | 4 ---- 9 files changed, 21 insertions(+), 19 deletions(-) diff --git a/.github/workflows/repo-maintenance.yaml b/.github/workflows/repo-maintenance.yaml index 7bb91c0ca..b8b29e602 100644 --- a/.github/workflows/repo-maintenance.yaml +++ b/.github/workflows/repo-maintenance.yaml @@ -8,7 +8,7 @@ on: env: BASE_IMAGE: openpilot-base BUILD: selfdrive/test/docker_build.sh base - RUN: docker run --shm-size 2G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e CI=1 -e PYTHONWARNINGS=error -e FILEREADER_CACHE=1 -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/bash -c + RUN: docker run --shm-size 2G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e CI=1 -e PYTHONWARNINGS=error -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/bash -c jobs: update_translations: diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index c5802b5cb..c7da61945 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -25,7 +25,7 @@ env: DOCKER_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }} BUILD: selfdrive/test/docker_build.sh base - RUN: docker run --shm-size 2G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e CI=1 -e PYTHONWARNINGS=error -e FILEREADER_CACHE=1 -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/bash -c + RUN: docker run --shm-size 2G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e CI=1 -e PYTHONWARNINGS=error -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/bash -c PYTEST: pytest --continue-on-collection-errors --durations=0 -n logical diff --git a/selfdrive/car/tests/big_cars_test.sh b/selfdrive/car/tests/big_cars_test.sh index 863b8bead..bb6e82dd0 100755 --- a/selfdrive/car/tests/big_cars_test.sh +++ b/selfdrive/car/tests/big_cars_test.sh @@ -6,7 +6,6 @@ cd $BASEDIR export MAX_EXAMPLES=300 export INTERNAL_SEG_CNT=300 -export FILEREADER_CACHE=1 export INTERNAL_SEG_LIST=selfdrive/car/tests/test_models_segs.txt cd selfdrive/car/tests && pytest test_models.py test_car_interfaces.py diff --git a/selfdrive/test/process_replay/README.md b/selfdrive/test/process_replay/README.md index dc801e428..8e279c71c 100644 --- a/selfdrive/test/process_replay/README.md +++ b/selfdrive/test/process_replay/README.md @@ -5,7 +5,7 @@ Process replay is a regression test designed to identify any changes in the outp If the test fails, make sure that you didn't unintentionally change anything. If there are intentional changes, the reference logs will be updated. Use `test_processes.py` to run the test locally. -Use `FILEREADER_CACHE='1' test_processes.py` to cache log files. +Log files are cached by default. Use `DISABLE_FILEREADER_CACHE='1' test_processes.py` to disable caching. Currently the following processes are tested: diff --git a/tools/lib/tests/test_caching.py b/tools/lib/tests/test_caching.py index 6e70ef90b..cb14098e6 100644 --- a/tools/lib/tests/test_caching.py +++ b/tools/lib/tests/test_caching.py @@ -56,13 +56,13 @@ class TestFileDownload: for k, v in retry_defaults.items(): assert getattr(URLFile.pool_manager().connection_pool_kw["retries"], k) == v - # ensure caching off by default and cache dir doesn't get created - os.environ.pop("FILEREADER_CACHE", None) + # ensure caching on by default and cache dir gets created + os.environ.pop("DISABLE_FILEREADER_CACHE", None) if os.path.exists(Paths.download_cache_root()): shutil.rmtree(Paths.download_cache_root()) URLFile(f"{host}/test.txt").get_length() URLFile(f"{host}/test.txt").read() - assert not os.path.exists(Paths.download_cache_root()) + assert os.path.exists(Paths.download_cache_root()) def compare_loads(self, url, start=0, length=None): """Compares range between cached and non cached version""" @@ -90,7 +90,7 @@ class TestFileDownload: def test_small_file(self): # Make sure we don't force cache - os.environ["FILEREADER_CACHE"] = "0" + os.environ.pop("DISABLE_FILEREADER_CACHE", None) small_file_url = "https://raw.githubusercontent.com/commaai/openpilot/master/docs/SAFETY.md" # If you want large file to be larger than a chunk # large_file_url = "https://commadataci.blob.core.windows.net/openpilotci/0375fdf7b1ce594d/2019-06-13--08-32-25/3/fcamera.hevc" @@ -119,7 +119,10 @@ class TestFileDownload: @pytest.mark.parametrize("cache_enabled", [True, False]) def test_recover_from_missing_file(self, host, cache_enabled): - os.environ["FILEREADER_CACHE"] = "1" if cache_enabled else "0" + if cache_enabled: + os.environ.pop("DISABLE_FILEREADER_CACHE", None) + else: + os.environ["DISABLE_FILEREADER_CACHE"] = "1" file_url = f"{host}/test.png" diff --git a/tools/lib/tests/test_logreader.py b/tools/lib/tests/test_logreader.py index ee75a8b1c..0151940c4 100644 --- a/tools/lib/tests/test_logreader.py +++ b/tools/lib/tests/test_logreader.py @@ -93,7 +93,10 @@ class TestLogReader: @pytest.mark.parametrize("cache_enabled", [True, False]) def test_direct_parsing(self, mocker, cache_enabled): file_exists_mock = mocker.patch("openpilot.tools.lib.filereader.file_exists") - os.environ["FILEREADER_CACHE"] = "1" if cache_enabled else "0" + if cache_enabled: + os.environ.pop("DISABLE_FILEREADER_CACHE", None) + else: + os.environ["DISABLE_FILEREADER_CACHE"] = "1" qlog = tempfile.NamedTemporaryFile(mode='wb', delete=False) with requests.get(QLOG_FILE, stream=True) as r: @@ -181,7 +184,10 @@ class TestLogReader: @parameterized.expand([(True,), (False,)]) @pytest.mark.slow def test_run_across_segments(self, cache_enabled): - os.environ["FILEREADER_CACHE"] = "1" if cache_enabled else "0" + if cache_enabled: + os.environ.pop("DISABLE_FILEREADER_CACHE", None) + else: + os.environ["DISABLE_FILEREADER_CACHE"] = "1" lr = LogReader(f"{TEST_ROUTE}/0:4") assert len(lr.run_across_segments(4, noop)) == len(list(lr)) diff --git a/tools/lib/url_file.py b/tools/lib/url_file.py index 8e2f0a922..de1207046 100644 --- a/tools/lib/url_file.py +++ b/tools/lib/url_file.py @@ -74,8 +74,8 @@ class URLFile: self._timeout = Timeout(connect=timeout, read=timeout) self._pos = 0 self._length: int | None = None - # True by default, false if FILEREADER_CACHE is defined, but can be overwritten by the cache input - self._force_download = not int(os.environ.get("FILEREADER_CACHE", "0")) + # Caching enabled by default, can be disabled with DISABLE_FILEREADER_CACHE=1, or overwritten by the cache input + self._force_download = int(os.environ.get("DISABLE_FILEREADER_CACHE", "0")) == 1 if cache is not None: self._force_download = not cache diff --git a/tools/replay/can_replay.py b/tools/replay/can_replay.py index 13c30a62a..071732007 100755 --- a/tools/replay/can_replay.py +++ b/tools/replay/can_replay.py @@ -5,8 +5,6 @@ import time import usb1 import threading -os.environ['FILEREADER_CACHE'] = '1' - from openpilot.common.realtime import config_realtime_process, Ratekeeper, DT_CTRL from openpilot.selfdrive.pandad import can_capnp_to_list from openpilot.tools.lib.logreader import LogReader diff --git a/tools/tuning/measure_steering_accuracy.py b/tools/tuning/measure_steering_accuracy.py index e4aef0ba1..ae3344c2e 100755 --- a/tools/tuning/measure_steering_accuracy.py +++ b/tools/tuning/measure_steering_accuracy.py @@ -117,12 +117,8 @@ if __name__ == "__main__": parser.add_argument('--route', help="route name") parser.add_argument('--addr', default='127.0.0.1', help="IP address for optional ZMQ listener, default to msgq") parser.add_argument('--group', default='all', help="speed group to display, [crawl|slow|medium|fast|veryfast|germany|all], default to all") - parser.add_argument('--cache', default=False, action='store_true', help="use cached data, default to False") args = parser.parse_args() - if args.cache: - os.environ['FILEREADER_CACHE'] = '1' - tool = SteeringAccuracyTool(args) if args.route is not None: From 1dfef69a3c8b6a5e622cfa648075a67ed267bbdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A1draic=20Slattery?= Date: Sun, 1 Feb 2026 03:37:00 +0100 Subject: [PATCH 795/910] chore: Update outdated GitHub Actions versions (#37020) * chore: Update outdated GitHub Actions versions * just the github ones --------- Co-authored-by: Adeeb Shihadeh --- .github/workflows/auto_pr_review.yaml | 6 ++--- .github/workflows/badges.yaml | 2 +- .github/workflows/ci_weekly_report.yaml | 2 +- .github/workflows/docs.yaml | 4 +-- .github/workflows/jenkins-pr-trigger.yaml | 6 ++--- .github/workflows/mici_raylib_ui_preview.yaml | 4 +-- .github/workflows/model_review.yaml | 4 +-- .github/workflows/prebuilt.yaml | 2 +- .github/workflows/raylib_ui_preview.yaml | 2 +- .github/workflows/release.yaml | 2 +- .github/workflows/repo-maintenance.yaml | 4 +-- .github/workflows/stale.yaml | 4 +-- .github/workflows/tests.yaml | 26 +++++++++---------- 13 files changed, 34 insertions(+), 34 deletions(-) diff --git a/.github/workflows/auto_pr_review.yaml b/.github/workflows/auto_pr_review.yaml index c6a1cb982..725154d21 100644 --- a/.github/workflows/auto_pr_review.yaml +++ b/.github/workflows/auto_pr_review.yaml @@ -11,12 +11,12 @@ jobs: pull-requests: write runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: submodules: false # Label PRs - - uses: actions/labeler@v5.0.0 + - uses: actions/labeler@v6 with: dot: true configuration-path: .github/labeler.yaml @@ -36,7 +36,7 @@ jobs: # Welcome comment - name: "First timers PR" - uses: actions/first-interaction@v1 + uses: actions/first-interaction@v3 if: github.event.pull_request.head.repo.full_name != 'commaai/openpilot' with: repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/badges.yaml b/.github/workflows/badges.yaml index cd30e4f37..3f9c9c1c5 100644 --- a/.github/workflows/badges.yaml +++ b/.github/workflows/badges.yaml @@ -17,7 +17,7 @@ jobs: permissions: contents: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: submodules: true - uses: ./.github/workflows/setup-with-retry diff --git a/.github/workflows/ci_weekly_report.yaml b/.github/workflows/ci_weekly_report.yaml index 37a46b209..c7f5ec34f 100644 --- a/.github/workflows/ci_weekly_report.yaml +++ b/.github/workflows/ci_weekly_report.yaml @@ -41,7 +41,7 @@ jobs: if: always() && github.repository == 'commaai/openpilot' steps: - name: Get job results - uses: actions/github-script@v7 + uses: actions/github-script@v8 id: get-job-results with: script: | diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index 92c311829..23a89de1c 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -22,7 +22,7 @@ jobs: steps: - uses: commaai/timeout@v1 - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: submodules: true @@ -34,7 +34,7 @@ jobs: mkdocs build # Push to docs.comma.ai - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 if: github.ref == 'refs/heads/master' && github.repository == 'commaai/openpilot' with: path: openpilot-docs diff --git a/.github/workflows/jenkins-pr-trigger.yaml b/.github/workflows/jenkins-pr-trigger.yaml index 14e2fdf49..f8a53c5ae 100644 --- a/.github/workflows/jenkins-pr-trigger.yaml +++ b/.github/workflows/jenkins-pr-trigger.yaml @@ -15,7 +15,7 @@ jobs: steps: - name: Check for trigger phrase id: check_comment - uses: actions/github-script@v7 + uses: actions/github-script@v8 with: script: | const triggerPhrase = "trigger-jenkins"; @@ -35,7 +35,7 @@ jobs: - name: Checkout repository if: steps.check_comment.outputs.result == 'true' - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: ref: refs/pull/${{ github.event.issue.number }}/head @@ -49,7 +49,7 @@ jobs: - name: Delete trigger comment if: steps.check_comment.outputs.result == 'true' && always() - uses: actions/github-script@v7 + uses: actions/github-script@v8 with: script: | await github.rest.issues.deleteComment({ diff --git a/.github/workflows/mici_raylib_ui_preview.yaml b/.github/workflows/mici_raylib_ui_preview.yaml index 707825b1a..5025d407c 100644 --- a/.github/workflows/mici_raylib_ui_preview.yaml +++ b/.github/workflows/mici_raylib_ui_preview.yaml @@ -33,7 +33,7 @@ jobs: pull-requests: write actions: read steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: submodules: true @@ -62,7 +62,7 @@ jobs: path: ${{ github.workspace }}/pr_ui - name: Getting master ui # filename: master_ui_raylib/mici_ui_replay.mp4 - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: repository: commaai/ci-artifacts ssh-key: ${{ secrets.CI_ARTIFACTS_DEPLOY_KEY }} diff --git a/.github/workflows/model_review.yaml b/.github/workflows/model_review.yaml index 0e1825864..6b8ce143d 100644 --- a/.github/workflows/model_review.yaml +++ b/.github/workflows/model_review.yaml @@ -16,9 +16,9 @@ jobs: if: github.repository == 'commaai/openpilot' steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Checkout master - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: ref: master path: base diff --git a/.github/workflows/prebuilt.yaml b/.github/workflows/prebuilt.yaml index d8963ec89..921c27465 100644 --- a/.github/workflows/prebuilt.yaml +++ b/.github/workflows/prebuilt.yaml @@ -29,7 +29,7 @@ jobs: running-workflow-name: 'build prebuilt' repo-token: ${{ secrets.GITHUB_TOKEN }} check-regexp: ^((?!.*(build master-ci).*).)*$ - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: submodules: true - run: git lfs pull diff --git a/.github/workflows/raylib_ui_preview.yaml b/.github/workflows/raylib_ui_preview.yaml index 18880e8a1..9044a97f5 100644 --- a/.github/workflows/raylib_ui_preview.yaml +++ b/.github/workflows/raylib_ui_preview.yaml @@ -58,7 +58,7 @@ jobs: path: ${{ github.workspace }}/pr_ui - name: Getting master ui - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: repository: commaai/ci-artifacts ssh-key: ${{ secrets.CI_ARTIFACTS_DEPLOY_KEY }} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 0f4ce6cb3..0f34dbe43 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -30,7 +30,7 @@ jobs: running-workflow-name: 'build master-ci' repo-token: ${{ secrets.GITHUB_TOKEN }} check-regexp: ^((?!.*(build prebuilt).*).)*$ - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: submodules: true fetch-depth: 0 diff --git a/.github/workflows/repo-maintenance.yaml b/.github/workflows/repo-maintenance.yaml index b8b29e602..810b602d7 100644 --- a/.github/workflows/repo-maintenance.yaml +++ b/.github/workflows/repo-maintenance.yaml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest if: github.repository == 'commaai/openpilot' steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: ./.github/workflows/setup-with-retry - name: Update translations run: | @@ -39,7 +39,7 @@ jobs: image: ghcr.io/commaai/openpilot-base:latest if: github.repository == 'commaai/openpilot' steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: submodules: true - name: uv lock diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml index 1ecd114dc..cb7c0ac07 100644 --- a/.github/workflows/stale.yaml +++ b/.github/workflows/stale.yaml @@ -13,7 +13,7 @@ jobs: stale: runs-on: ubuntu-latest steps: - - uses: actions/stale@v9 + - uses: actions/stale@v10 with: exempt-all-milestones: true @@ -34,7 +34,7 @@ jobs: stale_drafts: runs-on: ubuntu-latest steps: - - uses: actions/stale@v9 + - uses: actions/stale@v10 with: exempt-all-milestones: true diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index c7da61945..4ade42b66 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -41,7 +41,7 @@ jobs: env: STRIPPED_DIR: /tmp/releasepilot steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: submodules: true - name: Getting LFS files @@ -77,7 +77,7 @@ jobs: && fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]') || fromJSON('["ubuntu-24.04"]') }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: submodules: true - name: Setup docker push @@ -93,7 +93,7 @@ jobs: name: build macOS runs-on: ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-macos-8x14' || 'macos-latest' }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: submodules: true - run: echo "CACHE_COMMIT_DATE=$(git log -1 --pretty='format:%cd' --date=format:'%Y-%m-%d-%H:%M')" >> $GITHUB_ENV @@ -133,7 +133,7 @@ jobs: env: PYTHONWARNINGS: default steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: submodules: true - uses: ./.github/workflows/setup-with-retry @@ -150,7 +150,7 @@ jobs: && fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]') || fromJSON('["ubuntu-24.04"]') }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: submodules: true - uses: ./.github/workflows/setup-with-retry @@ -175,14 +175,14 @@ jobs: && fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]') || fromJSON('["ubuntu-24.04"]') }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: submodules: true - uses: ./.github/workflows/setup-with-retry id: setup-step - name: Cache test routes id: dependency-cache - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: .ci_cache/comma_download_cache key: proc-replay-${{ hashFiles('selfdrive/test/process_replay/ref_commit', 'selfdrive/test/process_replay/test_processes.py') }} @@ -198,7 +198,7 @@ jobs: id: print-diff if: always() run: cat selfdrive/test/process_replay/diff.txt - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v6 if: always() continue-on-error: true with: @@ -225,7 +225,7 @@ jobs: || fromJSON('["ubuntu-24.04"]') }} if: false # FIXME: Started to timeout recently steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: submodules: true - uses: ./.github/workflows/setup-with-retry @@ -249,7 +249,7 @@ jobs: && fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]') || fromJSON('["ubuntu-24.04"]') }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: submodules: true - uses: ./.github/workflows/setup-with-retry @@ -261,7 +261,7 @@ jobs: source selfdrive/test/setup_xvfb.sh && python3 selfdrive/ui/tests/test_ui/raylib_screenshots.py" - name: Upload Raylib UI Report - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: raylib-report-${{ inputs.run_number || '1' }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && 'master' || github.event.number }} path: selfdrive/ui/tests/test_ui/raylib_report/screenshots @@ -275,7 +275,7 @@ jobs: && fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]') || fromJSON('["ubuntu-24.04"]') }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: submodules: true - uses: ./.github/workflows/setup-with-retry @@ -287,7 +287,7 @@ jobs: source selfdrive/test/setup_xvfb.sh && WINDOWED=1 python3 selfdrive/ui/tests/diff/replay.py" - name: Upload Raylib UI Report - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: mici-raylib-report-${{ inputs.run_number || '1' }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && 'master' || github.event.number }} path: selfdrive/ui/tests/diff/report From cd70e23dc3b990eecea2364c8e0b2d2ac48de7fd Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 31 Jan 2026 20:15:23 -0800 Subject: [PATCH 796/910] clips: direct rendering with raylib (#36935) * good clips * replace * fix * fix font * lil more --- common/utils.py | 21 ++ system/ui/lib/application.py | 54 +++- tools/clip/run.py | 566 ++++++++++++++++++----------------- 3 files changed, 365 insertions(+), 276 deletions(-) diff --git a/common/utils.py b/common/utils.py index caa9a5795..ccc6719f5 100644 --- a/common/utils.py +++ b/common/utils.py @@ -10,6 +10,27 @@ import zstandard as zstd LOG_COMPRESSION_LEVEL = 10 # little benefit up to level 15. level ~17 is a small step change +class Timer: + """Simple lap timer for profiling sequential operations.""" + + def __init__(self): + self._start = self._lap = time.monotonic() + self._sections = {} + + def lap(self, name): + now = time.monotonic() + self._sections[name] = now - self._lap + self._lap = now + + @property + def total(self): + return time.monotonic() - self._start + + def fmt(self, duration): + parts = ", ".join(f"{k}={v:.2f}s" + (f" ({duration/v:.0f}x)" if k == 'render' and v > 0 else "") for k, v in self._sections.items()) + total = self.total + realtime = f"{duration/total:.1f}x realtime" if total > 0 else "N/A" + return f"{duration}s in {total:.1f}s ({realtime}) | {parts}" def sudo_write(val: str, path: str) -> None: try: diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 151f22ac1..da314a394 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -1,6 +1,7 @@ import atexit import cffi import os +import queue import time import signal import sys @@ -40,6 +41,9 @@ PROFILE_RENDER = int(os.getenv("PROFILE_RENDER", "0")) PROFILE_STATS = int(os.getenv("PROFILE_STATS", "100")) # Number of functions to show in profile output RECORD = os.getenv("RECORD") == "1" RECORD_OUTPUT = str(Path(os.getenv("RECORD_OUTPUT", "output")).with_suffix(".mp4")) +RECORD_BITRATE = os.getenv("RECORD_BITRATE", "") # Target bitrate e.g. "2000k" +RECORD_SPEED = int(os.getenv("RECORD_SPEED", "1")) # Speed multiplier +OFFSCREEN = os.getenv("OFFSCREEN") == "1" # Disable FPS limiting for fast offline rendering GL_VERSION = """ #version 300 es @@ -213,6 +217,9 @@ class GuiApplication: self._render_texture: rl.RenderTexture | None = None self._burn_in_shader: rl.Shader | None = None self._ffmpeg_proc: subprocess.Popen | None = None + self._ffmpeg_queue: queue.Queue | None = None + self._ffmpeg_thread: threading.Thread | None = None + self._ffmpeg_stop_event: threading.Event | None = None self._textures: dict[str, rl.Texture] = {} self._target_fps: int = _DEFAULT_FPS self._last_fps_log_time: float = time.monotonic() @@ -277,25 +284,36 @@ class GuiApplication: rl.set_texture_filter(self._render_texture.texture, rl.TextureFilter.TEXTURE_FILTER_BILINEAR) if RECORD: + output_fps = fps * RECORD_SPEED ffmpeg_args = [ 'ffmpeg', '-v', 'warning', # Reduce ffmpeg log spam - '-stats', # Show encoding progress + '-nostats', # Suppress encoding progress '-f', 'rawvideo', # Input format '-pix_fmt', 'rgba', # Input pixel format '-s', f'{self._width}x{self._height}', # Input resolution '-r', str(fps), # Input frame rate '-i', 'pipe:0', # Input from stdin - '-vf', 'vflip,format=yuv420p', # Flip vertically and convert rgba to yuv420p - '-c:v', 'libx264', # Video codec - '-preset', 'ultrafast', # Encoding speed + '-vf', 'vflip,format=yuv420p', # Flip vertically and convert to yuv420p + '-r', str(output_fps), # Output frame rate (for speed multiplier) + '-c:v', 'libx264', + '-preset', 'ultrafast', + ] + if RECORD_BITRATE: + ffmpeg_args += ['-b:v', RECORD_BITRATE, '-maxrate', RECORD_BITRATE, '-bufsize', RECORD_BITRATE] + ffmpeg_args += [ '-y', # Overwrite existing file '-f', 'mp4', # Output format RECORD_OUTPUT, # Output file path ] self._ffmpeg_proc = subprocess.Popen(ffmpeg_args, stdin=subprocess.PIPE) + self._ffmpeg_queue = queue.Queue(maxsize=60) # Buffer up to 60 frames + self._ffmpeg_stop_event = threading.Event() + self._ffmpeg_thread = threading.Thread(target=self._ffmpeg_writer_thread, daemon=True) + self._ffmpeg_thread.start() - rl.set_target_fps(fps) + # OFFSCREEN disables FPS limiting for fast offline rendering (e.g. clips) + rl.set_target_fps(0 if OFFSCREEN else fps) self._target_fps = fps self._set_styles() @@ -337,6 +355,21 @@ class GuiApplication: print(f"{green}UI window ready in {elapsed_ms:.1f} ms{reset}") sys.exit(0) + def _ffmpeg_writer_thread(self): + """Background thread that writes frames to ffmpeg.""" + while True: + try: + data = self._ffmpeg_queue.get(timeout=1.0) + if data is None: # Sentinel to stop + break + self._ffmpeg_proc.stdin.write(data) + except queue.Empty: + if self._ffmpeg_stop_event.is_set(): + break + continue + except Exception: + break + def set_modal_overlay(self, overlay, callback: Callable | None = None): if self._modal_overlay.overlay is not None: if hasattr(self._modal_overlay.overlay, 'hide_event'): @@ -409,11 +442,17 @@ class GuiApplication: return texture def close_ffmpeg(self): + if self._ffmpeg_thread is not None: + # Signal thread to stop, send sentinel, then wait for it to drain + self._ffmpeg_stop_event.set() + self._ffmpeg_queue.put(None) + self._ffmpeg_thread.join(timeout=30) + if self._ffmpeg_proc is not None: self._ffmpeg_proc.stdin.flush() self._ffmpeg_proc.stdin.close() try: - self._ffmpeg_proc.wait(timeout=5) + self._ffmpeg_proc.wait(timeout=30) except subprocess.TimeoutExpired: self._ffmpeg_proc.terminate() self._ffmpeg_proc.wait() @@ -525,8 +564,7 @@ class GuiApplication: image = rl.load_image_from_texture(self._render_texture.texture) data_size = image.width * image.height * 4 data = bytes(rl.ffi.buffer(image.data, data_size)) - self._ffmpeg_proc.stdin.write(data) - self._ffmpeg_proc.stdin.flush() + self._ffmpeg_queue.put(data) # Async write via background thread rl.unload_image(image) self._monitor_fps() diff --git a/tools/clip/run.py b/tools/clip/run.py index 9045a4381..324ee6669 100755 --- a/tools/clip/run.py +++ b/tools/clip/run.py @@ -1,310 +1,340 @@ #!/usr/bin/env python3 - -import logging import os -import platform -import shutil import sys import time -from argparse import ArgumentParser, ArgumentTypeError -from collections.abc import Sequence +import logging +import subprocess +import threading +import queue +import multiprocessing +import itertools +import numpy as np +import tqdm +from argparse import ArgumentParser from pathlib import Path -from random import randint -from subprocess import Popen -from typing import Literal +from concurrent.futures import ThreadPoolExecutor, as_completed -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.common.utils import managed_proc from openpilot.tools.lib.route import Route from openpilot.tools.lib.logreader import LogReader +from openpilot.tools.lib.filereader import FileReader +from openpilot.tools.lib.framereader import FrameReader, ffprobe +from openpilot.selfdrive.test.process_replay.migration import migrate_all +from openpilot.common.prefix import OpenpilotPrefix +from openpilot.common.utils import Timer +from msgq.visionipc import VisionIpcServer, VisionStreamType -DEFAULT_OUTPUT = 'output.mp4' -DEMO_START = 90 -DEMO_END = 105 -DEMO_ROUTE = 'a2a0ccea32023010/2023-07-27--13-01-19' FRAMERATE = 20 -PIXEL_DEPTH = '24' -RESOLUTION = '2160x1080' -SECONDS_TO_WARM = 2 -PROC_WAIT_SECONDS = 30*10 +DEMO_ROUTE, DEMO_START, DEMO_END = 'a2a0ccea32023010/2023-07-27--13-01-19', 90, 105 -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()) - -logger = logging.getLogger('clip.py') +logger = logging.getLogger('clip') -def check_for_failure(procs: list[Popen]): - for proc in procs: - exit_code = proc.poll() - if exit_code is not None and exit_code != 0: - cmd = str(proc.args) - if isinstance(proc.args, str): - cmd = proc.args - elif isinstance(proc.args, Sequence): - cmd = str(proc.args[0]) - msg = f'{cmd} failed, exit code {exit_code}' - logger.error(msg) - stdout, stderr = proc.communicate() - if stdout: - logger.error(stdout.decode()) - if stderr: - logger.error(stderr.decode()) - raise ChildProcessError(msg) - - -def escape_ffmpeg_text(value: str): - special_chars = {',': '\\,', ':': '\\:', '=': '\\=', '[': '\\[', ']': '\\]'} - value = value.replace('\\', '\\\\\\\\\\\\\\\\') - for char, escaped in special_chars.items(): - value = value.replace(char, escaped) - return value - - -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{init_data.version}", - f"route: {route.name.canonical_name}", - f"car: {car_params.carFingerprint}", - f"origin: {origin}", - f"branch: {init_data.gitBranch}", - f"commit: {init_data.gitCommit[:7]}", - f"modified: {str(init_data.dirty).lower()}", - ]) - - -def parse_args(parser: ArgumentParser): +def parse_args(): + parser = ArgumentParser(description="Direct clip renderer") + parser.add_argument("route", nargs="?", help="Route ID (dongle/route or dongle/route/start/end)") + parser.add_argument("-s", "--start", type=int, help="Start time in seconds") + parser.add_argument("-e", "--end", type=int, help="End time in seconds") + parser.add_argument("-o", "--output", default="output.mp4", help="Output file path") + parser.add_argument("-d", "--data-dir", help="Local directory with route data") + parser.add_argument("-t", "--title", help="Title overlay text") + parser.add_argument("-f", "--file-size", type=float, default=9.0, help="Target file size in MB") + parser.add_argument("-x", "--speed", type=int, default=1, help="Speed multiplier") + parser.add_argument("--demo", action="store_true", help="Use demo route with default timing") + parser.add_argument("--big", action="store_true", default=True, help="Use big UI (2160x1080)") + parser.add_argument("--qcam", action="store_true", help="Use qcamera instead of fcamera") + parser.add_argument("--windowed", action="store_true", help="Show window") + parser.add_argument("--no-metadata", action="store_true", help="Disable metadata overlay") + parser.add_argument("--no-time-overlay", action="store_true", help="Disable time overlay") args = parser.parse_args() + if args.demo: - args.route = DEMO_ROUTE - if args.start is None or args.end is None: - args.start = DEMO_START - args.end = DEMO_END - elif args.route.count('/') == 1: - if args.start is None or args.end is None: - parser.error('must provide both start and end if timing is not in the route ID') - elif args.route.count('/') == 3: - if args.start is not None or args.end is not None: - parser.error('don\'t provide timing when including it in the route ID') + args.route, args.start, args.end = args.route or DEMO_ROUTE, args.start or DEMO_START, args.end or DEMO_END + elif not args.route: + parser.error("route is required (or use --demo)") + + if args.route and args.route.count('/') == 3: parts = args.route.split('/') - args.route = '/'.join(parts[:2]) - args.start = int(parts[2]) - args.end = int(parts[3]) + args.route, args.start, args.end = '/'.join(parts[:2]), args.start or int(parts[2]), args.end or int(parts[3]) + + if args.start is None or args.end is None: + parser.error("--start and --end are required") if args.end <= args.start: - parser.error(f'end ({args.end}) must be greater than start ({args.start})') - if args.start < SECONDS_TO_WARM: - parser.error(f'start must be greater than {SECONDS_TO_WARM}s to allow the UI time to warm up') - - try: - args.route = Route(args.route, data_dir=args.data_dir) - except Exception as e: - parser.error(f'failed to get route: {e}') - - # FIXME: length isn't exactly max segment seconds, simplify to replay exiting at end of data - length = round(args.route.max_seg_number * 60) - if args.start >= length: - parser.error(f'start ({args.start}s) cannot be after end of route ({length}s)') - if args.end > length: - parser.error(f'end ({args.end}s) cannot be after end of route ({length}s)') - + parser.error(f"end ({args.end}) must be greater than start ({args.start})") return args -def populate_car_params(lr: LogReader): - init_data = lr.first('initData') - assert init_data is not None +def setup_env(output_path: str, big: bool = False, speed: int = 1, target_mb: float = 0, duration: int = 0): + os.environ.update({"RECORD": "1", "OFFSCREEN": "1", "RECORD_OUTPUT": str(Path(output_path).with_suffix(".mp4"))}) + if speed > 1: + os.environ["RECORD_SPEED"] = str(speed) + if target_mb > 0 and duration > 0: + os.environ["RECORD_BITRATE"] = f"{int(target_mb * 8 * 1024 / (duration / speed))}k" + if big: + os.environ["BIG"] = "1" + + +def _download_segment(path: str) -> bytes: + with FileReader(path) as f: + return bytes(f.read()) + + +def _parse_and_chunk_segment(args: tuple) -> list[dict]: + raw_data, fps = args + from openpilot.tools.lib.logreader import _LogFileReader + messages = migrate_all(list(_LogFileReader("", dat=raw_data, sort_by_time=True))) + if not messages: + return [] + + dt_ns, chunks, current, next_time = 1e9 / fps, [], {}, messages[0].logMonoTime + 1e9 / fps # type: ignore[var-annotated] + for msg in messages: + if msg.logMonoTime >= next_time: + chunks.append(current) + current, next_time = {}, next_time + dt_ns * ((msg.logMonoTime - next_time) // dt_ns + 1) + current[msg.which()] = msg + return chunks + [current] if current else chunks + + +def load_logs_parallel(log_paths: list[str], fps: int = 20) -> list[dict]: + num_workers = min(16, len(log_paths), (multiprocessing.cpu_count() or 1)) + logger.info(f"Downloading {len(log_paths)} segments with {num_workers} workers...") + + with ThreadPoolExecutor(max_workers=num_workers) as pool: + futures = {pool.submit(_download_segment, path): idx for idx, path in enumerate(log_paths)} + raw_data = {futures[f]: f.result() for f in as_completed(futures)} + + logger.info("Parsing and chunking segments...") + with multiprocessing.Pool(num_workers) as pool: + return list(itertools.chain.from_iterable(pool.map(_parse_and_chunk_segment, [(raw_data[i], fps) for i in range(len(log_paths))]))) + + +def patch_submaster(message_chunks, ui_state): + # Reset started_frame so alerts render correctly (recv_frame must be >= started_frame) + ui_state.started_frame = 0 + ui_state.started_time = time.monotonic() + + def mock_update(timeout=None): + sm, t = ui_state.sm, time.monotonic() + sm.updated = dict.fromkeys(sm.services, False) + if sm.frame < len(message_chunks): + for svc, msg in message_chunks[sm.frame].items(): + if svc in sm.data: + sm.seen[svc] = sm.updated[svc] = sm.alive[svc] = sm.valid[svc] = True + sm.data[svc] = getattr(msg.as_builder(), svc) + sm.logMonoTime[svc], sm.recv_time[svc], sm.recv_frame[svc] = msg.logMonoTime, t, sm.frame + sm.frame += 1 + ui_state.sm.update = mock_update + + +def get_frame_dimensions(camera_path: str) -> tuple[int, int]: + """Get frame dimensions from a video file using ffprobe.""" + probe = ffprobe(camera_path) + stream = probe["streams"][0] + return stream["width"], stream["height"] + + +def iter_segment_frames(camera_paths, start_time, end_time, fps=20, use_qcam=False, frame_size: tuple[int, int] | None = None): + frames_per_seg = fps * 60 + start_frame, end_frame = int(start_time * fps), int(end_time * fps) + current_seg: int = -1 + seg_frames: FrameReader | np.ndarray | None = None + + for global_idx in range(start_frame, end_frame): + seg_idx, local_idx = global_idx // frames_per_seg, global_idx % frames_per_seg + + if seg_idx != current_seg: + current_seg = seg_idx + path = camera_paths[seg_idx] if seg_idx < len(camera_paths) else None + if not path: + raise RuntimeError(f"No camera file for segment {seg_idx}") + + if use_qcam: + w, h = frame_size or get_frame_dimensions(path) + with FileReader(path) as f: + result = subprocess.run(["ffmpeg", "-v", "quiet", "-i", "-", "-f", "rawvideo", "-pix_fmt", "nv12", "-"], + input=f.read(), capture_output=True) + if result.returncode != 0: + raise RuntimeError(f"ffmpeg failed: {result.stderr.decode()}") + seg_frames = np.frombuffer(result.stdout, dtype=np.uint8).reshape(-1, w * h * 3 // 2) + else: + seg_frames = FrameReader(path, pix_fmt="nv12") + + assert seg_frames is not None + frame = seg_frames[local_idx] if use_qcam else seg_frames.get(local_idx) # type: ignore[index, union-attr] + yield global_idx, frame + + +class FrameQueue: + def __init__(self, camera_paths, start_time, end_time, fps=20, prefetch_count=60, use_qcam=False): + # Probe first valid camera file for dimensions + first_path = next((p for p in camera_paths if p), None) + if not first_path: + raise RuntimeError("No valid camera paths") + self.frame_w, self.frame_h = get_frame_dimensions(first_path) + + self._queue, self._stop, self._error = queue.Queue(maxsize=prefetch_count), threading.Event(), None + self._thread = threading.Thread(target=self._worker, + args=(camera_paths, start_time, end_time, fps, use_qcam, (self.frame_w, self.frame_h)), daemon=True) + self._thread.start() + + def _worker(self, camera_paths, start_time, end_time, fps, use_qcam, frame_size): + try: + for idx, data in iter_segment_frames(camera_paths, start_time, end_time, fps, use_qcam, frame_size): + if self._stop.is_set(): + break + self._queue.put((idx, data.tobytes())) + except Exception as e: + logger.exception("Decode error") + self._error = e + finally: + self._queue.put(None) + + def get(self, timeout=60.0): + if self._error: + raise self._error + result = self._queue.get(timeout=timeout) + if result is None: + raise StopIteration("No more frames") + return result + + def stop(self): + self._stop.set() + while not self._queue.empty(): + try: + self._queue.get_nowait() + except queue.Empty: + break + self._thread.join(timeout=2.0) + + +def load_route_metadata(route): + from openpilot.common.params import Params, UnknownKeyName + lr = LogReader(route.log_paths()[0]) + init_data, car_params = lr.first('initData'), lr.first('carParams') params = Params() - entries = init_data.params.entries - for cp in entries: - key, value = cp.key, cp.value + for entry in init_data.params.entries: try: - params.put(key, params.cpp2python(key, value)) + params.put(entry.key, params.cpp2python(entry.key, entry.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') + pass + + origin = init_data.gitRemote.split('/')[3] if len(init_data.gitRemote.split('/')) > 3 else 'unknown' + return { + 'version': init_data.version, 'route': route.name.canonical_name, + 'car': car_params.carFingerprint if car_params else 'unknown', 'origin': origin, + 'branch': init_data.gitBranch, 'commit': init_data.gitCommit[:7], 'modified': str(init_data.dirty).lower(), + } -def validate_env(parser: ArgumentParser): - if platform.system() not in ['Linux']: - parser.exit(1, f'clip.py: error: {platform.system()} is not a supported operating system\n') - for proc in ['Xvfb', 'ffmpeg']: - if shutil.which(proc) is None: - parser.exit(1, f'clip.py: error: missing {proc} command, is it installed?\n') - for proc in [REPLAY, UI]: - if shutil.which(proc) is None: - parser.exit(1, f'clip.py: error: missing {proc} command, did you build openpilot yet?\n') +def draw_text_box(rl, text, x, y, size, gui_app, font, font_scale, color=None, center=False): + box_color, text_color = rl.Color(0, 0, 0, 85), color or rl.WHITE + # measure_text_ex is NOT auto-scaled, so multiply by font_scale + # draw_text_ex IS auto-scaled, so pass size directly + text_size = rl.measure_text_ex(font, text, size * font_scale, 0) + text_width, text_height = int(text_size.x), int(text_size.y) + if center: + x = (gui_app.width - text_width) // 2 + rl.draw_rectangle(x - 8, y - 4, text_width + 16, text_height + 8, box_color) + rl.draw_text_ex(font, text, rl.Vector2(x, y), size, 0, text_color) -def validate_output_file(output_file: str): - if not output_file.endswith('.mp4'): - raise ArgumentTypeError('output must be an mp4') - return output_file +def render_overlays(rl, gui_app, font, font_scale, metadata, title, start_time, frame_idx, show_metadata, show_time): + if show_metadata and metadata and frame_idx < FRAMERATE * 5: + m = metadata + text = ", ".join([f"openpilot v{m['version']}", f"route: {m['route']}", f"car: {m['car']}", f"origin: {m['origin']}", + f"branch: {m['branch']}", f"commit: {m['commit']}", f"modified: {m['modified']}"]) + # Truncate if too wide (leave 20px margin on each side) + max_width = gui_app.width - 40 + while rl.measure_text_ex(font, text, 15 * font_scale, 0).x > max_width and len(text) > 20: + text = text[:-4] + "..." + draw_text_box(rl, text, 0, 8, 15, gui_app, font, font_scale, center=True) - -def validate_route(route: str): - if route.count('/') not in (1, 3): - raise ArgumentTypeError(f'route must include or exclude timing, example: {DEMO_ROUTE}') - return route - - -def validate_title(title: str): - if len(title) > 80: - raise ArgumentTypeError('title must be no longer than 80 chars') - return title - - -def wait_for_frames(procs: list[Popen]): - sm = SubMaster(['uiDebug']) - no_frames_drawn = True - while no_frames_drawn: - sm.update() - no_frames_drawn = sm['uiDebug'].drawTimeMillis == 0. - check_for_failure(procs) - - -def clip( - data_dir: str | None, - quality: Literal['low', 'high'], - prefix: str, - route: Route, - out: str, - start: int, - end: int, - speed: 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 - bit_rate_kbps = int(round(target_mb * 8 * 1024 * 1024 / duration / 1000)) - - # TODO: evaluate creating fn that inspects /tmp/.X11-unix and creates unused display to avoid possibility of collision - display = f':{randint(99, 999)}' - - box_style = 'box=1:boxcolor=black@0.33:boxborderw=7' - meta_text = get_meta_text(lr, route) - overlays = [ - # metadata overlay - f"drawtext=text='{escape_ffmpeg_text(meta_text)}':fontfile={OPENPILOT_FONT}:fontcolor=white:fontsize=15:{box_style}:x=(w-text_w)/2:y=5.5:enable='between(t,1,5)'", - # 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={OPENPILOT_FONT}:fontcolor=white:fontsize=32:{box_style}:x=(w-text_w)/2:y=53") + draw_text_box(rl, title, 0, 60, 32, gui_app, font, font_scale, center=True) - if speed > 1: - overlays += [ - f"setpts=PTS/{speed}", - "fps=60", - ] + if show_time: + t = start_time + frame_idx / FRAMERATE + time_text = f"{int(t)//60:02d}:{int(t)%60:02d}" + time_width = int(rl.measure_text_ex(font, time_text, 24 * font_scale, 0).x) + draw_text_box(rl, time_text, gui_app.width - time_width - 45, 45, 24, gui_app, font, font_scale) - ffmpeg_cmd = [ - '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, '--ecam', '-c', '1', '-s', str(begin_at), '--prefix', prefix] - if data_dir: - replay_cmd.extend(['--data_dir', data_dir]) - if quality == 'low': - replay_cmd.append('--qcam') - replay_cmd.append(route.name.canonical_name) +def clip(route: Route, output: str, start: int, end: int, headless: bool = True, big: bool = False, + title: str | None = None, show_metadata: bool = True, show_time: bool = True, use_qcam: bool = False): + timer, duration = Timer(), end - start - ui_cmd = [UI, '-platform', 'xcb'] - xvfb_cmd = ['Xvfb', display, '-terminate', '-screen', '0', f'{RESOLUTION}x{PIXEL_DEPTH}'] + import pyray as rl + if big: + from openpilot.selfdrive.ui.layouts.main import MainLayout + else: + from openpilot.selfdrive.ui.mici.layouts.main import MiciMainLayout as MainLayout # type: ignore[assignment] + from openpilot.selfdrive.ui.ui_state import ui_state + from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE + timer.lap("import") - with OpenpilotPrefix(prefix, shared_download_cache=True): - populate_car_params(lr) - env = os.environ.copy() - env['DISPLAY'] = display + logger.info(f"Clipping {route.name.canonical_name}, {start}s-{end}s ({duration}s)") + seg_start, seg_end = start // 60, (end - 1) // 60 + 1 + all_chunks = load_logs_parallel(route.log_paths()[seg_start:seg_end], fps=FRAMERATE) + timer.lap("logs") - with managed_proc(xvfb_cmd, env) as xvfb_proc, managed_proc(ui_cmd, env) as ui_proc, managed_proc(replay_cmd, env) as replay_proc: - procs = [xvfb_proc, ui_proc, replay_proc] - logger.info('waiting for replay to begin (loading segments, may take a while)...') - wait_for_frames(procs) - logger.debug(f'letting UI warm up ({SECONDS_TO_WARM}s)...') - time.sleep(SECONDS_TO_WARM) - check_for_failure(procs) - with managed_proc(ffmpeg_cmd, env) as ffmpeg_proc: - procs.append(ffmpeg_proc) - logger.info(f'recording in progress ({duration}s)...') - ffmpeg_proc.wait(duration + PROC_WAIT_SECONDS) - check_for_failure(procs) - logger.info(f'recording complete: {Path(out).resolve()}') + frame_start = (start - seg_start * 60) * FRAMERATE + message_chunks = all_chunks[frame_start:frame_start + duration * FRAMERATE] + if not message_chunks: + logger.error("No messages to render") + sys.exit(1) + + metadata = load_route_metadata(route) if show_metadata else None + if headless: + rl.set_config_flags(rl.ConfigFlags.FLAG_WINDOW_HIDDEN) + + with OpenpilotPrefix(shared_download_cache=True): + camera_paths = route.qcamera_paths() if use_qcam else route.camera_paths() + frame_queue = FrameQueue(camera_paths, start, end, fps=FRAMERATE, use_qcam=use_qcam) + + vipc = VisionIpcServer("camerad") + vipc.create_buffers(VisionStreamType.VISION_STREAM_ROAD, 4, frame_queue.frame_w, frame_queue.frame_h) + vipc.start_listener() + + patch_submaster(message_chunks, ui_state) + gui_app.init_window("clip", fps=FRAMERATE) + + main_layout = MainLayout() + main_layout.set_rect(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) + font = gui_app.font(FontWeight.NORMAL) + timer.lap("setup") + + frame_idx = 0 + with tqdm.tqdm(total=len(message_chunks), desc="Rendering", unit="frame") as pbar: + for should_render in gui_app.render(): + if frame_idx >= len(message_chunks): + break + _, frame_bytes = frame_queue.get() + vipc.send(VisionStreamType.VISION_STREAM_ROAD, frame_bytes, frame_idx, int(frame_idx * 5e7), int(frame_idx * 5e7)) + ui_state.update() + if should_render: + main_layout.render() + render_overlays(rl, gui_app, font, FONT_SCALE, metadata, title, start, frame_idx, show_metadata, show_time) + frame_idx += 1 + pbar.update(1) + timer.lap("render") + + frame_queue.stop() + gui_app.close() + timer.lap("ffmpeg") + + logger.info(f"Clip saved to: {Path(output).resolve()}") + logger.info(f"Generated {timer.fmt(duration)}") def main(): - p = ArgumentParser(prog='clip.py', description='clip your openpilot route.', epilog='comma.ai') - validate_env(p) - route_group = p.add_mutually_exclusive_group(required=True) - route_group.add_argument('route', nargs='?', type=validate_route, help=f'The route (e.g. {DEMO_ROUTE} or {DEMO_ROUTE}/{DEMO_START}/{DEMO_END})') - route_group.add_argument('--demo', help='use the demo route', action='store_true') - p.add_argument('-d', '--data-dir', help='local directory where route data is stored') - p.add_argument('-e', '--end', help='stop clipping at seconds', type=int) - p.add_argument('-f', '--file-size', help='target file size (Discord/GitHub support max 10MB, default is 9MB)', type=float, default=9.) - p.add_argument('-o', '--output', help='output clip to (.mp4)', type=validate_output_file, default=DEFAULT_OUTPUT) - p.add_argument('-p', '--prefix', help='openpilot prefix', default=f'clip_{randint(100, 99999)}') - p.add_argument('-q', '--quality', help='quality of camera (low = qcam, high = hevc)', choices=['low', 'high'], default='high') - p.add_argument('-x', '--speed', help='record the clip at this speed multiple', type=int, default=1) - p.add_argument('-s', '--start', help='start clipping at seconds', type=int) - p.add_argument('-t', '--title', help='overlay this title on the video (e.g. "Chill driving across the Golden Gate Bridge")', type=validate_title) - args = parse_args(p) - exit_code = 1 - try: - clip( - data_dir=args.data_dir, - quality=args.quality, - prefix=args.prefix, - route=args.route, - out=args.output, - start=args.start, - end=args.end, - speed=args.speed, - 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) - sys.exit(exit_code) + logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s\t%(message)s") + args = parse_args() + assert args.big, "Clips doesn't support mici UI yet. TODO: make it work" + + setup_env(args.output, big=args.big, speed=args.speed, target_mb=args.file_size, duration=args.end - args.start) + clip(Route(args.route, data_dir=args.data_dir), args.output, args.start, args.end, not args.windowed, + args.big, args.title, not args.no_metadata, not args.no_time_overlay, args.qcam) -if __name__ == '__main__': - logging.basicConfig(level=logging.INFO, format='%(asctime)s %(name)s %(levelname)s\t%(message)s') +if __name__ == "__main__": main() From e76e1e500c07a02a1b534f85238ee1ab0a441d41 Mon Sep 17 00:00:00 2001 From: Trey Moen <50057480+greatgitsby@users.noreply.github.com> Date: Sun, 1 Feb 2026 14:21:00 -0700 Subject: [PATCH 797/910] clips: use AugmentedRoadView instead of MainLayout (#37053) Render only the road view in clips rather than the full main layout, matching the updated UI module structure. --- tools/clip/run.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tools/clip/run.py b/tools/clip/run.py index 324ee6669..5fb693f30 100755 --- a/tools/clip/run.py +++ b/tools/clip/run.py @@ -265,9 +265,9 @@ def clip(route: Route, output: str, start: int, end: int, headless: bool = True, import pyray as rl if big: - from openpilot.selfdrive.ui.layouts.main import MainLayout + from openpilot.selfdrive.ui.onroad.augmented_road_view import AugmentedRoadView else: - from openpilot.selfdrive.ui.mici.layouts.main import MiciMainLayout as MainLayout # type: ignore[assignment] + from openpilot.selfdrive.ui.mici.onroad.augmented_road_view import AugmentedRoadView # type: ignore[assignment] from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE timer.lap("import") @@ -298,8 +298,8 @@ def clip(route: Route, output: str, start: int, end: int, headless: bool = True, patch_submaster(message_chunks, ui_state) gui_app.init_window("clip", fps=FRAMERATE) - main_layout = MainLayout() - main_layout.set_rect(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) + road_view = AugmentedRoadView() + road_view.set_rect(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) font = gui_app.font(FontWeight.NORMAL) timer.lap("setup") @@ -312,7 +312,7 @@ def clip(route: Route, output: str, start: int, end: int, headless: bool = True, vipc.send(VisionStreamType.VISION_STREAM_ROAD, frame_bytes, frame_idx, int(frame_idx * 5e7), int(frame_idx * 5e7)) ui_state.update() if should_render: - main_layout.render() + road_view.render() render_overlays(rl, gui_app, font, FONT_SCALE, metadata, title, start, frame_idx, show_metadata, show_time) frame_idx += 1 pbar.update(1) From 0a84b004065edfa2f0efaedad329b3853409c316 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 1 Feb 2026 13:36:55 -0800 Subject: [PATCH 798/910] fix up status for in progress builds --- scripts/ci_results.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/ci_results.py b/scripts/ci_results.py index c3d53f222..a133541c6 100755 --- a/scripts/ci_results.py +++ b/scripts/ci_results.py @@ -143,6 +143,9 @@ def format_markdown(gh_status, gh_run_id, jenkins_status, commit_sha, branch): lines.append(f"| {stage['name']} | {icon} {stage['status'].lower()} |") if stage["status"] == "FAILED": failed_jenkins_stages.append(stage["name"]) + # Show overall build status if still in progress + if jenkins_status["in_progress"]: + lines.append("| (build in progress) | :hourglass: in_progress |") else: icon = ":hourglass:" if jenkins_status["in_progress"] else ( ":white_check_mark:" if jenkins_status["result"] == "SUCCESS" else ":x:") From 7a990b99f7e2a714ee00b7f0e7955dd588f3c88e Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 1 Feb 2026 15:07:22 -0800 Subject: [PATCH 799/910] rm future-fstrings package (#37056) --- pyproject.toml | 1 - uv.lock | 11 ----------- 2 files changed, 12 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9a70f69d2..57fd0b835 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,7 +58,6 @@ dependencies = [ # acados deps "casadi >=3.6.6", # 3.12 fixed in 3.6.6 - "future-fstrings", # joystickd "inputs", diff --git a/uv.lock b/uv.lock index b221995b8..e488d1d78 100644 --- a/uv.lock +++ b/uv.lock @@ -633,15 +633,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" }, ] -[[package]] -name = "future-fstrings" -version = "1.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5d/e2/3874574cce18a2e3608abfe5b4b5b3c9765653c464f5da18df8971cf501d/future_fstrings-1.2.0.tar.gz", hash = "sha256:6cf41cbe97c398ab5a81168ce0dbb8ad95862d3caf23c21e4430627b90844089", size = 5786, upload-time = "2019-06-16T03:04:42.651Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ab/6d/ea1d52e9038558dd37f5d30647eb9f07888c164960a5d4daa5f970c6da25/future_fstrings-1.2.0-py2.py3-none-any.whl", hash = "sha256:90e49598b553d8746c4dc7d9442e0359d038c3039d802c91c0a55505da318c63", size = 6138, upload-time = "2019-06-16T03:04:40.395Z" }, -] - [[package]] name = "ghp-import" version = "2.1.0" @@ -1302,7 +1293,6 @@ dependencies = [ { name = "cffi" }, { name = "crcmod-plus" }, { name = "cython" }, - { name = "future-fstrings" }, { name = "inputs" }, { name = "json-rpc" }, { name = "kaitaistruct" }, @@ -1396,7 +1386,6 @@ requires-dist = [ { name = "cython" }, { name = "dearpygui", marker = "(platform_machine != 'aarch64' and extra == 'tools') or (sys_platform != 'linux' and extra == 'tools')", specifier = ">=2.1.0" }, { name = "dictdiffer", marker = "extra == 'dev'" }, - { name = "future-fstrings" }, { name = "hypothesis", marker = "extra == 'testing'", specifier = "==6.47.*" }, { name = "inputs" }, { name = "jeepney", marker = "extra == 'dev'" }, From 422de598984a26225ba729546852a2e5eb000eeb Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 1 Feb 2026 15:24:42 -0800 Subject: [PATCH 800/910] acados: strip future-fstrings declaration (#37057) * Revert "rm future-fstrings package (#37056)" This reverts commit 7a990b99f7e2a714ee00b7f0e7955dd588f3c88e. * Reapply "rm future-fstrings package (#37056)" This reverts commit 8b93f6646eed6863ad67b9bab558d305ecb8b7b4. * strip it * cleanup --- third_party/acados/acados_template/acados_ocp.py | 1 - third_party/acados/acados_template/acados_ocp_solver.py | 1 - .../acados/acados_template/acados_ocp_solver_pyx.pyx | 1 - third_party/acados/acados_template/acados_sim.py | 1 - third_party/acados/acados_template/acados_sim_solver.py | 1 - .../acados/acados_template/acados_sim_solver_common.pxd | 1 - .../acados/acados_template/acados_sim_solver_pyx.pyx | 1 - third_party/acados/acados_template/acados_solver_common.pxd | 1 - third_party/acados/acados_template/builders.py | 1 - third_party/acados/acados_template/gnsf/__init__.py | 0 third_party/acados/acados_template/utils.py | 1 - third_party/acados/build.sh | 6 ++++++ 12 files changed, 6 insertions(+), 10 deletions(-) delete mode 100644 third_party/acados/acados_template/gnsf/__init__.py diff --git a/third_party/acados/acados_template/acados_ocp.py b/third_party/acados/acados_template/acados_ocp.py index ec02822ce..d6236e1f6 100644 --- a/third_party/acados/acados_template/acados_ocp.py +++ b/third_party/acados/acados_template/acados_ocp.py @@ -1,4 +1,3 @@ -# -*- coding: future_fstrings -*- # # Copyright (c) The acados authors. # diff --git a/third_party/acados/acados_template/acados_ocp_solver.py b/third_party/acados/acados_template/acados_ocp_solver.py index ffc9cf4b0..229bdf603 100644 --- a/third_party/acados/acados_template/acados_ocp_solver.py +++ b/third_party/acados/acados_template/acados_ocp_solver.py @@ -1,4 +1,3 @@ -# -*- coding: future_fstrings -*- # # Copyright (c) The acados authors. # diff --git a/third_party/acados/acados_template/acados_ocp_solver_pyx.pyx b/third_party/acados/acados_template/acados_ocp_solver_pyx.pyx index acd7f02d0..bc03ba086 100644 --- a/third_party/acados/acados_template/acados_ocp_solver_pyx.pyx +++ b/third_party/acados/acados_template/acados_ocp_solver_pyx.pyx @@ -1,4 +1,3 @@ -# -*- coding: future_fstrings -*- # # Copyright (c) The acados authors. # diff --git a/third_party/acados/acados_template/acados_sim.py b/third_party/acados/acados_template/acados_sim.py index c0d6937a4..7faa49fb1 100644 --- a/third_party/acados/acados_template/acados_sim.py +++ b/third_party/acados/acados_template/acados_sim.py @@ -1,4 +1,3 @@ -# -*- coding: future_fstrings -*- # # Copyright (c) The acados authors. # diff --git a/third_party/acados/acados_template/acados_sim_solver.py b/third_party/acados/acados_template/acados_sim_solver.py index 612f439ea..de5ee1070 100644 --- a/third_party/acados/acados_template/acados_sim_solver.py +++ b/third_party/acados/acados_template/acados_sim_solver.py @@ -1,4 +1,3 @@ -# -*- coding: future_fstrings -*- # # Copyright (c) The acados authors. # diff --git a/third_party/acados/acados_template/acados_sim_solver_common.pxd b/third_party/acados/acados_template/acados_sim_solver_common.pxd index cc6a58efd..7c20a6d24 100644 --- a/third_party/acados/acados_template/acados_sim_solver_common.pxd +++ b/third_party/acados/acados_template/acados_sim_solver_common.pxd @@ -1,4 +1,3 @@ -# -*- coding: future_fstrings -*- # # Copyright (c) The acados authors. # diff --git a/third_party/acados/acados_template/acados_sim_solver_pyx.pyx b/third_party/acados/acados_template/acados_sim_solver_pyx.pyx index be400addc..01964fd7a 100644 --- a/third_party/acados/acados_template/acados_sim_solver_pyx.pyx +++ b/third_party/acados/acados_template/acados_sim_solver_pyx.pyx @@ -1,4 +1,3 @@ -# -*- coding: future_fstrings -*- # # Copyright (c) The acados authors. # diff --git a/third_party/acados/acados_template/acados_solver_common.pxd b/third_party/acados/acados_template/acados_solver_common.pxd index c6d59d40a..75d021626 100644 --- a/third_party/acados/acados_template/acados_solver_common.pxd +++ b/third_party/acados/acados_template/acados_solver_common.pxd @@ -1,4 +1,3 @@ -# -*- coding: future_fstrings -*- # # Copyright (c) The acados authors. # diff --git a/third_party/acados/acados_template/builders.py b/third_party/acados/acados_template/builders.py index 6f21bfe8c..8acc05b52 100644 --- a/third_party/acados/acados_template/builders.py +++ b/third_party/acados/acados_template/builders.py @@ -1,4 +1,3 @@ -# -*- coding: future_fstrings -*- # # Copyright 2019 Gianluca Frison, Dimitris Kouzoupis, Robin Verschueren, # Andrea Zanelli, Niels van Duijkeren, Jonathan Frey, Tommaso Sartor, diff --git a/third_party/acados/acados_template/gnsf/__init__.py b/third_party/acados/acados_template/gnsf/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/third_party/acados/acados_template/utils.py b/third_party/acados/acados_template/utils.py index d6f6c02f8..f27617fa3 100644 --- a/third_party/acados/acados_template/utils.py +++ b/third_party/acados/acados_template/utils.py @@ -1,4 +1,3 @@ -# -*- coding: future_fstrings -*- # # Copyright (c) The acados authors. # diff --git a/third_party/acados/build.sh b/third_party/acados/build.sh index b45c167b1..2b803ef6b 100755 --- a/third_party/acados/build.sh +++ b/third_party/acados/build.sh @@ -44,6 +44,12 @@ cp -r $DIR/acados_repo/lib $INSTALL_DIR cp -r $DIR/acados_repo/interfaces/acados_template/acados_template $DIR/ #pip3 install -e $DIR/acados/interfaces/acados_template +# skip macOS - sed is different :/ +if [[ "$OSTYPE" != "darwin"* ]]; then + # strip future_fstrings to avoid having to install the compatibility package + find $DIR/acados_template/ -type f -exec sed -i '/future.fstrings/d' {} + +fi + # build tera cd $DIR/acados_repo/interfaces/acados_template/tera_renderer/ if [[ "$OSTYPE" == "darwin"* ]]; then From 948d42b3e59e073d23e3506dd254a58a0227f4a5 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 1 Feb 2026 15:42:42 -0800 Subject: [PATCH 801/910] rm pyopencl package (#37058) rm pyopencl --- .gitignore | 4 + pyproject.toml | 1 - tools/sim/lib/camerad.py | 54 +++++----- tools/sim/rgb_to_nv12.cl | 119 ----------------------- tools/sim/tests/test_metadrive_bridge.py | 1 - uv.lock | 30 ------ 6 files changed, 35 insertions(+), 174 deletions(-) delete mode 100644 tools/sim/rgb_to_nv12.cl diff --git a/.gitignore b/.gitignore index e4992a3d0..e2a30fb70 100644 --- a/.gitignore +++ b/.gitignore @@ -95,3 +95,7 @@ Pipfile # Ignore all local history of files .history .ionide + +.claude/ +PLAN.md +TASK.md diff --git a/pyproject.toml b/pyproject.toml index 57fd0b835..2239770ac 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -112,7 +112,6 @@ dev = [ "opencv-python-headless", "parameterized >=0.8, <0.9", "pyautogui", - "pyopencl", "pytools>=2025.1.6; platform_machine != 'aarch64'", "pywinctl", "pyprof2calltree", diff --git a/tools/sim/lib/camerad.py b/tools/sim/lib/camerad.py index be4e1a610..7634b8524 100644 --- a/tools/sim/lib/camerad.py +++ b/tools/sim/lib/camerad.py @@ -1,14 +1,39 @@ import numpy as np -import os -import pyopencl as cl -import pyopencl.array as cl_array from msgq.visionipc import VisionIpcServer, VisionStreamType from cereal import messaging -from openpilot.common.basedir import BASEDIR from openpilot.tools.sim.lib.common import W, H + +def rgb_to_nv12(rgb): + """Convert RGB image to NV12 (YUV420) format using BT.601 coefficients.""" + h, w = rgb.shape[:2] + r = rgb[:, :, 0].astype(np.int32) + g = rgb[:, :, 1].astype(np.int32) + b = rgb[:, :, 2].astype(np.int32) + + # Y plane - BT.601 coefficients (matches original OpenCL kernel) + y = (((b * 13 + g * 65 + r * 33) + 64) >> 7) + 16 + y = np.clip(y, 0, 255).astype(np.uint8) + + # Subsample RGB for UV (2x2 box filter) + r_sub = (r[0::2, 0::2] + r[0::2, 1::2] + r[1::2, 0::2] + r[1::2, 1::2] + 2) >> 2 + g_sub = (g[0::2, 0::2] + g[0::2, 1::2] + g[1::2, 0::2] + g[1::2, 1::2] + 2) >> 2 + b_sub = (b[0::2, 0::2] + b[0::2, 1::2] + b[1::2, 0::2] + b[1::2, 1::2] + 2) >> 2 + + # U and V planes + u = np.clip((b_sub * 56 - g_sub * 37 - r_sub * 19 + 0x8080) >> 8, 0, 255).astype(np.uint8) + v = np.clip((r_sub * 56 - g_sub * 47 - b_sub * 9 + 0x8080) >> 8, 0, 255).astype(np.uint8) + + # Interleave UV for NV12 format + uv = np.empty((h // 2, w), dtype=np.uint8) + uv[:, 0::2] = u + uv[:, 1::2] = v + + return np.concatenate([y.ravel(), uv.ravel()]).tobytes() + + class Camerad: """Simulates the camerad daemon""" def __init__(self, dual_camera): @@ -24,18 +49,6 @@ class Camerad: self.vipc_server.start_listener() - # set up for pyopencl rgb to yuv conversion - self.ctx = cl.create_some_context() - self.queue = cl.CommandQueue(self.ctx) - cl_arg = f" -DHEIGHT={H} -DWIDTH={W} -DRGB_STRIDE={W * 3} -DUV_WIDTH={W // 2} -DUV_HEIGHT={H // 2} -DRGB_SIZE={W * H} -DCL_DEBUG " - - kernel_fn = os.path.join(BASEDIR, "tools/sim/rgb_to_nv12.cl") - with open(kernel_fn) as f: - prg = cl.Program(self.ctx, f.read()).build(cl_arg) - self.krnl = prg.rgb_to_nv12 - self.Wdiv4 = W // 4 if (W % 4 == 0) else (W + (4 - W % 4)) // 4 - self.Hdiv4 = H // 4 if (H % 4 == 0) else (H + (4 - H % 4)) // 4 - def cam_send_yuv_road(self, yuv): self._send_yuv(yuv, self.frame_road_id, 'roadCameraState', VisionStreamType.VISION_STREAM_ROAD) self.frame_road_id += 1 @@ -44,16 +57,11 @@ class Camerad: self._send_yuv(yuv, self.frame_wide_id, 'wideRoadCameraState', VisionStreamType.VISION_STREAM_WIDE_ROAD) self.frame_wide_id += 1 - # Returns: yuv bytes def rgb_to_yuv(self, rgb): + """Convert RGB to NV12 YUV format.""" assert rgb.shape == (H, W, 3), f"{rgb.shape}" assert rgb.dtype == np.uint8 - - rgb_cl = cl_array.to_device(self.queue, rgb) - yuv_cl = cl_array.empty_like(rgb_cl) - self.krnl(self.queue, (self.Wdiv4, self.Hdiv4), None, rgb_cl.data, yuv_cl.data).wait() - yuv = np.resize(yuv_cl.get(), rgb.size // 2) - return yuv.data.tobytes() + return rgb_to_nv12(rgb) def _send_yuv(self, yuv, frame_id, pub_type, yuv_type): eof = int(frame_id * 0.05 * 1e9) diff --git a/tools/sim/rgb_to_nv12.cl b/tools/sim/rgb_to_nv12.cl deleted file mode 100644 index 54816d5d7..000000000 --- a/tools/sim/rgb_to_nv12.cl +++ /dev/null @@ -1,119 +0,0 @@ -#define RGB_TO_Y(r, g, b) ((((mul24(b, 13) + mul24(g, 65) + mul24(r, 33)) + 64) >> 7) + 16) -#define RGB_TO_U(r, g, b) ((mul24(b, 56) - mul24(g, 37) - mul24(r, 19) + 0x8080) >> 8) -#define RGB_TO_V(r, g, b) ((mul24(r, 56) - mul24(g, 47) - mul24(b, 9) + 0x8080) >> 8) -#define AVERAGE(x, y, z, w) ((convert_ushort(x) + convert_ushort(y) + convert_ushort(z) + convert_ushort(w) + 1) >> 1) - -inline void convert_2_ys(__global uchar * out_yuv, int yi, const uchar8 rgbs1) { - uchar2 yy = (uchar2)( - RGB_TO_Y(rgbs1.s2, rgbs1.s1, rgbs1.s0), - RGB_TO_Y(rgbs1.s5, rgbs1.s4, rgbs1.s3) - ); -#ifdef CL_DEBUG - if(yi >= RGB_SIZE) - printf("Y vector2 overflow, %d > %d\n", yi, RGB_SIZE); -#endif - vstore2(yy, 0, out_yuv + yi); -} - -inline void convert_4_ys(__global uchar * out_yuv, int yi, const uchar8 rgbs1, const uchar8 rgbs3) { - const uchar4 yy = (uchar4)( - RGB_TO_Y(rgbs1.s2, rgbs1.s1, rgbs1.s0), - RGB_TO_Y(rgbs1.s5, rgbs1.s4, rgbs1.s3), - RGB_TO_Y(rgbs3.s0, rgbs1.s7, rgbs1.s6), - RGB_TO_Y(rgbs3.s3, rgbs3.s2, rgbs3.s1) - ); -#ifdef CL_DEBUG - if(yi > RGB_SIZE - 4) - printf("Y vector4 overflow, %d > %d\n", yi, RGB_SIZE - 4); -#endif - vstore4(yy, 0, out_yuv + yi); -} - -inline void convert_uv(__global uchar * out_yuv, int uvi, - const uchar8 rgbs1, const uchar8 rgbs2) { - // U & V: average of 2x2 pixels square - const short ab = AVERAGE(rgbs1.s0, rgbs1.s3, rgbs2.s0, rgbs2.s3); - const short ag = AVERAGE(rgbs1.s1, rgbs1.s4, rgbs2.s1, rgbs2.s4); - const short ar = AVERAGE(rgbs1.s2, rgbs1.s5, rgbs2.s2, rgbs2.s5); -#ifdef CL_DEBUG - if(uvi >= RGB_SIZE + RGB_SIZE / 2) - printf("UV overflow, %d >= %d\n", uvi, RGB_SIZE + RGB_SIZE / 2); -#endif - out_yuv[uvi] = RGB_TO_U(ar, ag, ab); - out_yuv[uvi+1] = RGB_TO_V(ar, ag, ab); -} - -inline void convert_2_uvs(__global uchar * out_yuv, int uvi, - const uchar8 rgbs1, const uchar8 rgbs2, const uchar8 rgbs3, const uchar8 rgbs4) { - // U & V: average of 2x2 pixels square - const short ab1 = AVERAGE(rgbs1.s0, rgbs1.s3, rgbs2.s0, rgbs2.s3); - const short ag1 = AVERAGE(rgbs1.s1, rgbs1.s4, rgbs2.s1, rgbs2.s4); - const short ar1 = AVERAGE(rgbs1.s2, rgbs1.s5, rgbs2.s2, rgbs2.s5); - const short ab2 = AVERAGE(rgbs1.s6, rgbs3.s1, rgbs2.s6, rgbs4.s1); - const short ag2 = AVERAGE(rgbs1.s7, rgbs3.s2, rgbs2.s7, rgbs4.s2); - const short ar2 = AVERAGE(rgbs3.s0, rgbs3.s3, rgbs4.s0, rgbs4.s3); - uchar4 uv = (uchar4)( - RGB_TO_U(ar1, ag1, ab1), - RGB_TO_V(ar1, ag1, ab1), - RGB_TO_U(ar2, ag2, ab2), - RGB_TO_V(ar2, ag2, ab2) - ); -#ifdef CL_DEBUG1 - if(uvi > RGB_SIZE + RGB_SIZE / 2 - 4) - printf("UV2 overflow, %d >= %d\n", uvi, RGB_SIZE + RGB_SIZE / 2 - 2); -#endif - vstore4(uv, 0, out_yuv + uvi); -} - -__kernel void rgb_to_nv12(__global uchar const * const rgb, - __global uchar * out_yuv) -{ - const int dx = get_global_id(0); - const int dy = get_global_id(1); - const int col = mul24(dx, 4); // Current column in rgb image - const int row = mul24(dy, 4); // Current row in rgb image - const int bgri_start = mad24(row, RGB_STRIDE, mul24(col, 3)); // Start offset of rgb data being converted - const int yi_start = mad24(row, WIDTH, col); // Start offset in the target yuv buffer - int uvi = mad24(row / 2, WIDTH, RGB_SIZE + col); - int num_col = min(WIDTH - col, 4); - int num_row = min(HEIGHT - row, 4); - if(num_row == 4) { - const uchar8 rgbs0_0 = vload8(0, rgb + bgri_start); - const uchar8 rgbs0_1 = vload8(0, rgb + bgri_start + 8); - const uchar8 rgbs1_0 = vload8(0, rgb + bgri_start + RGB_STRIDE); - const uchar8 rgbs1_1 = vload8(0, rgb + bgri_start + RGB_STRIDE + 8); - const uchar8 rgbs2_0 = vload8(0, rgb + bgri_start + RGB_STRIDE * 2); - const uchar8 rgbs2_1 = vload8(0, rgb + bgri_start + RGB_STRIDE * 2 + 8); - const uchar8 rgbs3_0 = vload8(0, rgb + bgri_start + RGB_STRIDE * 3); - const uchar8 rgbs3_1 = vload8(0, rgb + bgri_start + RGB_STRIDE * 3 + 8); - if(num_col == 4) { - convert_4_ys(out_yuv, yi_start, rgbs0_0, rgbs0_1); - convert_4_ys(out_yuv, yi_start + WIDTH, rgbs1_0, rgbs1_1); - convert_4_ys(out_yuv, yi_start + WIDTH * 2, rgbs2_0, rgbs2_1); - convert_4_ys(out_yuv, yi_start + WIDTH * 3, rgbs3_0, rgbs3_1); - convert_2_uvs(out_yuv, uvi, rgbs0_0, rgbs1_0, rgbs0_1, rgbs1_1); - convert_2_uvs(out_yuv, uvi + WIDTH, rgbs2_0, rgbs3_0, rgbs2_1, rgbs3_1); - } else if(num_col == 2) { - convert_2_ys(out_yuv, yi_start, rgbs0_0); - convert_2_ys(out_yuv, yi_start + WIDTH, rgbs1_0); - convert_2_ys(out_yuv, yi_start + WIDTH * 2, rgbs2_0); - convert_2_ys(out_yuv, yi_start + WIDTH * 3, rgbs3_0); - convert_uv(out_yuv, uvi, rgbs0_0, rgbs1_0); - convert_uv(out_yuv, uvi + WIDTH, rgbs2_0, rgbs3_0); - } - } else { - const uchar8 rgbs0_0 = vload8(0, rgb + bgri_start); - const uchar8 rgbs0_1 = vload8(0, rgb + bgri_start + 8); - const uchar8 rgbs1_0 = vload8(0, rgb + bgri_start + RGB_STRIDE); - const uchar8 rgbs1_1 = vload8(0, rgb + bgri_start + RGB_STRIDE + 8); - if(num_col == 4) { - convert_4_ys(out_yuv, yi_start, rgbs0_0, rgbs0_1); - convert_4_ys(out_yuv, yi_start + WIDTH, rgbs1_0, rgbs1_1); - convert_2_uvs(out_yuv, uvi, rgbs0_0, rgbs1_0, rgbs0_1, rgbs1_1); - } else if(num_col == 2) { - convert_2_ys(out_yuv, yi_start, rgbs0_0); - convert_2_ys(out_yuv, yi_start + WIDTH, rgbs1_0); - convert_uv(out_yuv, uvi, rgbs0_0, rgbs1_0); - } - } -} diff --git a/tools/sim/tests/test_metadrive_bridge.py b/tools/sim/tests/test_metadrive_bridge.py index 04ce5d584..9be640d73 100644 --- a/tools/sim/tests/test_metadrive_bridge.py +++ b/tools/sim/tests/test_metadrive_bridge.py @@ -8,7 +8,6 @@ from openpilot.tools.sim.bridge.metadrive.metadrive_bridge import MetaDriveBridg from openpilot.tools.sim.tests.test_sim_bridge import TestSimBridgeBase @pytest.mark.slow -@pytest.mark.filterwarnings("ignore::pyopencl.CompilerWarning") # Unimportant warning of non-empty compile log class TestMetaDriveBridge(TestSimBridgeBase): @pytest.fixture(autouse=True) def setup_create_bridge(self, test_duration): diff --git a/uv.lock b/uv.lock index e488d1d78..da387a390 100644 --- a/uv.lock +++ b/uv.lock @@ -1336,7 +1336,6 @@ dev = [ { name = "opencv-python-headless" }, { name = "parameterized" }, { name = "pyautogui" }, - { name = "pyopencl" }, { name = "pyprof2calltree" }, { name = "pytools", marker = "platform_machine != 'aarch64'" }, { name = "pywinctl" }, @@ -1409,7 +1408,6 @@ requires-dist = [ { name = "pycapnp", specifier = "==2.1.0" }, { name = "pycryptodome" }, { name = "pyjwt" }, - { name = "pyopencl", marker = "extra == 'dev'" }, { name = "pyopenssl", specifier = "<24.3.0" }, { name = "pyprof2calltree", marker = "extra == 'dev'" }, { name = "pyserial" }, @@ -4247,34 +4245,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/db/67/64920c8d201a7fc27962f467c636c4e763b43845baba2e091a50a97a5d52/pyobjc_framework_webkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:af2c7197447638b92aafbe4847c063b6dd5e1ed83b44d3ce7e71e4c9b042ab5a", size = 50084, upload-time = "2025-11-14T10:07:05.868Z" }, ] -[[package]] -name = "pyopencl" -version = "2026.1.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, - { name = "platformdirs" }, - { name = "pytools" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d8/81/fd8a2a695916a82e861bcf17b5b8fd9f81e12c9e5931f9ba536678d7b43a/pyopencl-2026.1.2.tar.gz", hash = "sha256:4397dd0b4cbb8b55f3e09bf87114a2465574506b363890b805b860c348b61970", size = 445132, upload-time = "2026-01-16T22:52:24.765Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e3/88/abf34e31d572c59203774a66cd81c1e3b3d60b911241483675151149c6f1/pyopencl-2026.1.2-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:8052e8b402b3ed33ee0807d87d4734f66f67dbafbfb3f5a8b81e478e4d417372", size = 437029, upload-time = "2026-01-16T22:51:30.953Z" }, - { url = "https://files.pythonhosted.org/packages/5c/3d/2dd2d8bbf05a190681582b40fc1ee55b210d00ccebcbb416c62b9f9c81a1/pyopencl-2026.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d5e03681c3fe22d5185b16a727d96783e3787e0b65e7a29e4afe01ae0cb4e802", size = 429031, upload-time = "2026-01-16T22:51:32.674Z" }, - { url = "https://files.pythonhosted.org/packages/41/16/e554b3bd20be2e858cfb6683ee6549aeebbe5f769e5b95f561f79340ab20/pyopencl-2026.1.2-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c8c209d517d1421b17d20b80589a2c39e09ea33350f0367314e1caeed3bc741", size = 689596, upload-time = "2026-01-16T22:51:33.913Z" }, - { url = "https://files.pythonhosted.org/packages/22/a8/1df41cf6c7b25b3bfda14aa0183c6a90eaf849528ba27753eaa25fb26e20/pyopencl-2026.1.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e64e2e34bcfad426bd24b71fdb6b02aa5cb02475147742fe07ef93e81866fc7e", size = 736427, upload-time = "2026-01-16T22:51:36.595Z" }, - { url = "https://files.pythonhosted.org/packages/fc/3d/177b6a675691f7b6f708faef33f981e72fbc4bfed2b1dfa94dc70d0e8a25/pyopencl-2026.1.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:65b151c56b936481d6b6050c2b9bc520840e1402be78c282ba5c01921c25477d", size = 1163888, upload-time = "2026-01-16T22:51:37.973Z" }, - { url = "https://files.pythonhosted.org/packages/e9/fa/5905571d9fa48827c0427a3e664c0213dd045940d581b3b739d83df9c0f6/pyopencl-2026.1.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cc40003446037f391ca0970694efb0627e2870fabb20ee21be75bc445a39d8f4", size = 1228235, upload-time = "2026-01-16T22:51:39.786Z" }, - { url = "https://files.pythonhosted.org/packages/1e/3d/538c675d078b91680d8d82962110d0c9fd42e1584763d515d6e2e82d8c57/pyopencl-2026.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:b6a8e109ade7db60e8b1beb48df8f080941d0cd77fb2c225ad509c80cdef603e", size = 474753, upload-time = "2026-01-16T22:51:41.771Z" }, - { url = "https://files.pythonhosted.org/packages/cd/34/1497070e44d1689ddbd01d24a2265910e84ebc53457a489b9d2b6e1ac675/pyopencl-2026.1.2-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:7d88e59901bfe1f9296fd89acd9968f008dc7cfee7995f8cd09c3f1a77119aa6", size = 438145, upload-time = "2026-01-16T22:51:43.658Z" }, - { url = "https://files.pythonhosted.org/packages/5b/a3/71d6af8741b52d3bef443518c1ccfda003adcfa9cc1d0df83dac7005d08c/pyopencl-2026.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3f96a3bff8a09d2fa924e7c33dafac6ea3ef7ec70e746d6d8e17ce2d959a6836", size = 428820, upload-time = "2026-01-16T22:51:45.326Z" }, - { url = "https://files.pythonhosted.org/packages/db/ea/c8dbabeceac9cad3dbb368e08e0aa208cc6c6251c5134cc25eb15da03639/pyopencl-2026.1.2-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4d4e8e8215ec4fdee4b235b61977cdb1c4f041b487bdcf357be799f45b423d61", size = 685478, upload-time = "2026-01-16T22:51:46.545Z" }, - { url = "https://files.pythonhosted.org/packages/64/c7/5854ef7471dfee195bcef6348a107525ca4d1b73c15240e6444d490f9920/pyopencl-2026.1.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0052a8ccbd282d8ab196705e31f4c3ab344113ea5d5c3ddaeede00cdcab068b", size = 734017, upload-time = "2026-01-16T22:51:48.277Z" }, - { url = "https://files.pythonhosted.org/packages/3d/79/42d4eec282ed299b38d8136d05545113ec8771a1bd6b10bb4ba83ae1236c/pyopencl-2026.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e43da12a376e9283407c2820b24cceeaa129b042ac710947cf8e07b13e294689", size = 1159871, upload-time = "2026-01-16T22:51:49.569Z" }, - { url = "https://files.pythonhosted.org/packages/a0/9a/fdc5d3bed0440d6206109e051008aa0a54ca131d64314bbd42177b8f0763/pyopencl-2026.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1b14b2cf11dec9e0b75cbd14223d1b3c93950fc3e2f7a306b54fa1b17a2cae0f", size = 1225288, upload-time = "2026-01-16T22:51:51.125Z" }, - { url = "https://files.pythonhosted.org/packages/2d/e3/358c19180e0dab5c7dd1fcacc569e6a7ab02a7fddcb9c954f393ceddb2fa/pyopencl-2026.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:d02d7ecabc8d34590dccffe12346689adc5a1ceb07df5acc4ea6c4db8aa28277", size = 474876, upload-time = "2026-01-16T22:51:52.912Z" }, -] - [[package]] name = "pyopenssl" version = "24.2.1" From 5da6bf9e036aa69994bb462fa972648e8ac33255 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 1 Feb 2026 15:46:40 -0800 Subject: [PATCH 802/910] rm pytools package (#37059) --- pyproject.toml | 1 - uv.lock | 36 ------------------------------------ 2 files changed, 37 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 2239770ac..76b02d0c2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -112,7 +112,6 @@ dev = [ "opencv-python-headless", "parameterized >=0.8, <0.9", "pyautogui", - "pytools>=2025.1.6; platform_machine != 'aarch64'", "pywinctl", "pyprof2calltree", "tabulate", diff --git a/uv.lock b/uv.lock index da387a390..85f12bb05 100644 --- a/uv.lock +++ b/uv.lock @@ -1337,7 +1337,6 @@ dev = [ { name = "parameterized" }, { name = "pyautogui" }, { name = "pyprof2calltree" }, - { name = "pytools", marker = "platform_machine != 'aarch64'" }, { name = "pywinctl" }, { name = "tabulate" }, { name = "types-requests" }, @@ -1420,7 +1419,6 @@ requires-dist = [ { name = "pytest-subtests", marker = "extra == 'testing'" }, { name = "pytest-timeout", marker = "extra == 'testing'" }, { name = "pytest-xdist", marker = "extra == 'testing'", git = "https://github.com/sshane/pytest-xdist?rev=2b4372bd62699fb412c4fe2f95bf9f01bd2018da" }, - { name = "pytools", marker = "platform_machine != 'aarch64' and extra == 'dev'", specifier = ">=2025.1.6" }, { name = "pywinctl", marker = "extra == 'dev'" }, { name = "pyzmq" }, { name = "qrcode" }, @@ -4446,20 +4444,6 @@ version = "0.15" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/ef/c6/2c5999de3bb1533521f1101e8fe56fd9c266732f4d48011c7c69b29d12ae/python3-xlib-0.15.tar.gz", hash = "sha256:dc4245f3ae4aa5949c1d112ee4723901ade37a96721ba9645f2bfa56e5b383f8", size = 132828, upload-time = "2014-05-31T12:28:59.603Z" } -[[package]] -name = "pytools" -version = "2025.2.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "platformdirs" }, - { name = "siphash24" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c3/7b/f885a57e61ded45b5b10ca60f0b7575c9fb9a282e7513d0e23a33ee647e1/pytools-2025.2.5.tar.gz", hash = "sha256:a7f5350644d46d98ee9c7e67b4b41693308aa0f5e9b188d8f0694b27dc94e3a2", size = 85594, upload-time = "2025-10-07T15:53:30.49Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f6/84/c42c29ca4bff35baa286df70b0097e0b1c88fd57e8e6bdb09cb161a6f3c1/pytools-2025.2.5-py3-none-any.whl", hash = "sha256:42e93751ec425781e103bbcd769ba35ecbacd43339c2905401608f2fdc30cf19", size = 98811, upload-time = "2025-10-07T15:53:29.089Z" }, -] - [[package]] name = "pytweening" version = "1.2.0" @@ -4774,26 +4758,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/bf/cb6c1c505cb31e818e900b9312d514f381fbfa5c4363edfce0fcc4f8c1a4/shapely-2.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:743044b4cfb34f9a67205cee9279feaf60ba7d02e69febc2afc609047cb49179", size = 1722861, upload-time = "2025-09-24T13:50:43.35Z" }, ] -[[package]] -name = "siphash24" -version = "1.8" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/67/a2/e049b6fccf7a94bd1b2f68b3059a7d6a7aea86a808cac80cb9ae71ab6254/siphash24-1.8.tar.gz", hash = "sha256:aa932f0af4a7335caef772fdaf73a433a32580405c41eb17ff24077944b0aa97", size = 19946, upload-time = "2025-09-02T20:42:04.856Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/82/23/f53f5bd8866c6ea3abe434c9f208e76ea027210d8b75cd0e0dc849661c7a/siphash24-1.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4662ac616bce4d3c9d6003a0d398e56f8be408fc53a166b79fad08d4f34268e", size = 76930, upload-time = "2025-09-02T20:41:00.869Z" }, - { url = "https://files.pythonhosted.org/packages/0b/25/aebf246904424a06e7ffb7a40cfa9ea9e590ea0fac82e182e0f5d1f1d7ef/siphash24-1.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:53d6bed0951a99c6d2891fa6f8acfd5ca80c3e96c60bcee99f6fa01a04773b1c", size = 74315, upload-time = "2025-09-02T20:41:02.38Z" }, - { url = "https://files.pythonhosted.org/packages/59/3f/7010407c3416ef052d46550d54afb2581fb247018fc6500af8c66669eff2/siphash24-1.8-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d114c03648630e9e07dac2fe95442404e4607adca91640d274ece1a4fa71123e", size = 99756, upload-time = "2025-09-02T20:41:03.902Z" }, - { url = "https://files.pythonhosted.org/packages/d4/9f/09c734833e69badd7e3faed806b4372bd6564ae0946bd250d5239885914f/siphash24-1.8-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:88c1a55ff82b127c5d3b96927a430d8859e6a98846a5b979833ac790682dd91b", size = 104044, upload-time = "2025-09-02T20:41:05.505Z" }, - { url = "https://files.pythonhosted.org/packages/24/30/56a26d9141a34433da221f732599e2b23d2d70a966c249a9f00feb9a2915/siphash24-1.8-cp311-cp311-win32.whl", hash = "sha256:9430255e6a1313470f52c07c4a4643c451a5b2853f6d4008e4dda05cafb6ce7c", size = 62196, upload-time = "2025-09-02T20:41:07.299Z" }, - { url = "https://files.pythonhosted.org/packages/47/b2/11b0ae63fd374652544e1b12f72ba2cc3fe6c93c1483bd8ff6935b0a8a4b/siphash24-1.8-cp311-cp311-win_amd64.whl", hash = "sha256:1e4b37e4ef0b4496169adce2a58b6c3f230b5852dfa5f7ad0b2d664596409e47", size = 77162, upload-time = "2025-09-02T20:41:08.878Z" }, - { url = "https://files.pythonhosted.org/packages/7f/82/ce3545ce8052ac7ca104b183415a27ec3335e5ed51978fdd7b433f3cfe5b/siphash24-1.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:df5ed437c6e6cc96196b38728e57cd30b0427df45223475a90e173f5015ef5ba", size = 78136, upload-time = "2025-09-02T20:41:10.083Z" }, - { url = "https://files.pythonhosted.org/packages/15/88/896c3b91bc9deb78c415448b1db67343917f35971a9e23a5967a9d323b8a/siphash24-1.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f4ef78abdf811325c7089a35504df339c48c0007d4af428a044431d329721e56", size = 74588, upload-time = "2025-09-02T20:41:11.251Z" }, - { url = "https://files.pythonhosted.org/packages/12/fd/8dad3f5601db485ba862e1c1f91a5d77fb563650856a6708e9acb40ee53c/siphash24-1.8-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:065eff55c4fefb3a29fd26afb2c072abf7f668ffd53b91d41f92a1c485fcbe5c", size = 98655, upload-time = "2025-09-02T20:41:12.45Z" }, - { url = "https://files.pythonhosted.org/packages/e3/cc/e0c352624c1f2faad270aeb5cce6e173977ef66b9b5e918aa6f32af896bf/siphash24-1.8-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ac6fa84ebfd47677262aa0bcb0f5a70f796f5fc5704b287ee1b65a3bd4fb7a5d", size = 103217, upload-time = "2025-09-02T20:41:13.746Z" }, - { url = "https://files.pythonhosted.org/packages/5b/f6/0b1675bea4d40affcae642d9c7337702a4138b93c544230280712403e968/siphash24-1.8-cp312-cp312-win32.whl", hash = "sha256:6582f73615552ca055e51e03cb02a28e570a641a7f500222c86c2d811b5037eb", size = 63114, upload-time = "2025-09-02T20:41:14.972Z" }, - { url = "https://files.pythonhosted.org/packages/3d/39/afefef85d72ed8b5cf1aa9283f712e3cd43c9682fabbc809dec54baa8452/siphash24-1.8-cp312-cp312-win_amd64.whl", hash = "sha256:44ea6d794a7cbe184e1e1da2df81c5ebb672ab3867935c3e87c08bb0c2fa4879", size = 76232, upload-time = "2025-09-02T20:41:16.112Z" }, -] - [[package]] name = "six" version = "1.17.0" From 35241a5fb871623b69f793946188b41964d82196 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 1 Feb 2026 16:03:18 -0800 Subject: [PATCH 803/910] cleanup pyproject (#37060) * cleanup pyproject * lil more * fix warning --- pyproject.toml | 26 +++++-------------- tools/plotjuggler/juggle.py | 2 +- uv.lock | 51 ++----------------------------------- 3 files changed, 9 insertions(+), 70 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 76b02d0c2..bba80ee8d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ license = {text = "MIT License"} version = "0.1.0" description = "an open source driver assistance system" authors = [ - {name ="Vehicle Researcher", email="user@comma.ai"} + {name = "Vehicle Researcher", email="user@comma.ai"} ] dependencies = [ @@ -74,6 +74,7 @@ dependencies = [ "raylib > 5.5.0.3", "qrcode", "mapbox-earcut", + "jeepney", ] [project.optional-dependencies] @@ -93,7 +94,6 @@ testing = [ # https://github.com/pytest-dev/pytest-xdist/pull/1229 "pytest-xdist @ git+https://github.com/sshane/pytest-xdist@2b4372bd62699fb412c4fe2f95bf9f01bd2018da", "pytest-timeout", - "pytest-randomly", "pytest-asyncio", "pytest-mock", "pytest-repeat", @@ -107,16 +107,12 @@ dev = [ "azure-identity", "azure-storage-blob", "dictdiffer", - "jeepney", "matplotlib", "opencv-python-headless", "parameterized >=0.8, <0.9", "pyautogui", "pywinctl", - "pyprof2calltree", "tabulate", - "types-requests", - "types-tabulate", ] tools = [ @@ -153,19 +149,9 @@ markers = [ testpaths = [ "common", "selfdrive", - "system/manager", - "system/updated", - "system/athena", - "system/camerad", - "system/hardware", - "system/loggerd", - "system/tests", - "system/ubloxd", - "system/webrtc", - "tools/lib/tests", - "tools/replay", - "tools/cabana", - "cereal/messaging/tests", + "system", + "tools", + "cereal", ] [tool.codespell] @@ -175,7 +161,7 @@ ignore-words-list = "bu,ro,te,ue,alo,hda,ois,nam,nams,ned,som,parm,setts,inout,w 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/*, *.po, uv.lock, *.onnx, ./cereal/gen/*, */c_generated_code/*, docs/assets/*, tools/plotjuggler/layouts/*, selfdrive/assets/offroad/mici_fcc.html" -# https://beta.ruff.rs/docs/configuration/#using-pyprojecttoml +# https://docs.astral.sh/ruff/configuration/#using-pyprojecttoml [tool.ruff] indent-width = 2 lint.select = [ diff --git a/tools/plotjuggler/juggle.py b/tools/plotjuggler/juggle.py index 34f33d195..142e64050 100755 --- a/tools/plotjuggler/juggle.py +++ b/tools/plotjuggler/juggle.py @@ -47,7 +47,7 @@ def install(): tmpf.write(chunk) with tarfile.open(tmp.name) as tar: - tar.extractall(path=INSTALL_DIR) + tar.extractall(path=INSTALL_DIR, filter="data") def get_plotjuggler_version(): diff --git a/uv.lock b/uv.lock index 85f12bb05..3e3522eb3 100644 --- a/uv.lock +++ b/uv.lock @@ -1294,6 +1294,7 @@ dependencies = [ { name = "crcmod-plus" }, { name = "cython" }, { name = "inputs" }, + { name = "jeepney" }, { name = "json-rpc" }, { name = "kaitaistruct" }, { name = "libusb1" }, @@ -1331,16 +1332,12 @@ dev = [ { name = "azure-identity" }, { name = "azure-storage-blob" }, { name = "dictdiffer" }, - { name = "jeepney" }, { name = "matplotlib" }, { name = "opencv-python-headless" }, { name = "parameterized" }, { name = "pyautogui" }, - { name = "pyprof2calltree" }, { name = "pywinctl" }, { name = "tabulate" }, - { name = "types-requests" }, - { name = "types-tabulate" }, ] docs = [ { name = "jinja2" }, @@ -1356,7 +1353,6 @@ testing = [ { name = "pytest-asyncio" }, { name = "pytest-cpp" }, { name = "pytest-mock" }, - { name = "pytest-randomly" }, { name = "pytest-repeat" }, { name = "pytest-subtests" }, { name = "pytest-timeout" }, @@ -1386,7 +1382,7 @@ requires-dist = [ { name = "dictdiffer", marker = "extra == 'dev'" }, { name = "hypothesis", marker = "extra == 'testing'", specifier = "==6.47.*" }, { name = "inputs" }, - { name = "jeepney", marker = "extra == 'dev'" }, + { name = "jeepney" }, { name = "jinja2", marker = "extra == 'docs'" }, { name = "json-rpc" }, { name = "kaitaistruct" }, @@ -1408,13 +1404,11 @@ requires-dist = [ { name = "pycryptodome" }, { name = "pyjwt" }, { name = "pyopenssl", specifier = "<24.3.0" }, - { name = "pyprof2calltree", marker = "extra == 'dev'" }, { name = "pyserial" }, { name = "pytest", marker = "extra == 'testing'" }, { name = "pytest-asyncio", marker = "extra == 'testing'" }, { name = "pytest-cpp", marker = "extra == 'testing'" }, { name = "pytest-mock", marker = "extra == 'testing'" }, - { name = "pytest-randomly", marker = "extra == 'testing'" }, { name = "pytest-repeat", marker = "extra == 'testing'" }, { name = "pytest-subtests", marker = "extra == 'testing'" }, { name = "pytest-timeout", marker = "extra == 'testing'" }, @@ -1436,8 +1430,6 @@ requires-dist = [ { name = "tabulate", marker = "extra == 'dev'" }, { name = "tqdm" }, { name = "ty", marker = "extra == 'testing'" }, - { name = "types-requests", marker = "extra == 'dev'" }, - { name = "types-tabulate", marker = "extra == 'dev'" }, { name = "websocket-client" }, { name = "xattr" }, { name = "zstandard" }, @@ -4273,12 +4265,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/df/80/fc9d01d5ed37ba4c42ca2b55b4339ae6e200b456be3a1aaddf4a9fa99b8c/pyperclip-1.11.0-py3-none-any.whl", hash = "sha256:299403e9ff44581cb9ba2ffeed69c7aa96a008622ad0c46cb575ca75b5b84273", size = 11063, upload-time = "2025-09-26T14:40:36.069Z" }, ] -[[package]] -name = "pyprof2calltree" -version = "1.4.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ca/2a/e9a76261183b4b5e059a6625d7aae0bcb0a77622bc767d4497148ce2e218/pyprof2calltree-1.4.5.tar.gz", hash = "sha256:a635672ff31677486350b2be9a823ef92f740e6354a6aeda8fa4a8a3768e8f2f", size = 10080, upload-time = "2020-04-19T10:39:09.819Z" } - [[package]] name = "pyrect" version = "0.2.0" @@ -4356,18 +4342,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5a/cc/06253936f4a7fa2e0f48dfe6d851d9c56df896a9ab09ac019d70b760619c/pytest_mock-3.15.1-py3-none-any.whl", hash = "sha256:0a25e2eb88fe5168d535041d09a4529a188176ae608a6d249ee65abc0949630d", size = 10095, upload-time = "2025-09-16T16:37:25.734Z" }, ] -[[package]] -name = "pytest-randomly" -version = "4.0.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pytest" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c4/1d/258a4bf1109258c00c35043f40433be5c16647387b6e7cd5582d638c116b/pytest_randomly-4.0.1.tar.gz", hash = "sha256:174e57bb12ac2c26f3578188490bd333f0e80620c3f47340158a86eca0593cd8", size = 14130, upload-time = "2025-09-12T15:23:00.085Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/33/3e/a4a9227807b56869790aad3e24472a554b585974fe7e551ea350f50897ae/pytest_randomly-4.0.1-py3-none-any.whl", hash = "sha256:e0dfad2fd4f35e07beff1e47c17fbafcf98f9bf4531fd369d9260e2f858bfcb7", size = 8304, upload-time = "2025-09-12T15:22:58.946Z" }, -] - [[package]] name = "pytest-repeat" version = "0.9.4" @@ -4864,27 +4838,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2d/c2/05fdd64ac003a560d4fbd1faa7d9a31d75df8f901675e5bed1ee2ceeff87/ty-0.0.13-py3-none-win_arm64.whl", hash = "sha256:1c9630333497c77bb9bcabba42971b96ee1f36c601dd3dcac66b4134f9fa38f0", size = 9808316, upload-time = "2026-01-21T13:20:54.053Z" }, ] -[[package]] -name = "types-requests" -version = "2.32.4.20260107" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "urllib3" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/0f/f3/a0663907082280664d745929205a89d41dffb29e89a50f753af7d57d0a96/types_requests-2.32.4.20260107.tar.gz", hash = "sha256:018a11ac158f801bfa84857ddec1650750e393df8a004a8a9ae2a9bec6fcb24f", size = 23165, upload-time = "2026-01-07T03:20:54.091Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1c/12/709ea261f2bf91ef0a26a9eed20f2623227a8ed85610c1e54c5805692ecb/types_requests-2.32.4.20260107-py3-none-any.whl", hash = "sha256:b703fe72f8ce5b31ef031264fe9395cac8f46a04661a79f7ed31a80fb308730d", size = 20676, upload-time = "2026-01-07T03:20:52.929Z" }, -] - -[[package]] -name = "types-tabulate" -version = "0.9.0.20241207" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3f/43/16030404a327e4ff8c692f2273854019ed36718667b2993609dc37d14dd4/types_tabulate-0.9.0.20241207.tar.gz", hash = "sha256:ac1ac174750c0a385dfd248edc6279fa328aaf4ea317915ab879a2ec47833230", size = 8195, upload-time = "2024-12-07T02:54:42.554Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5e/86/a9ebfd509cbe74471106dffed320e208c72537f9aeb0a55eaa6b1b5e4d17/types_tabulate-0.9.0.20241207-py3-none-any.whl", hash = "sha256:b8dad1343c2a8ba5861c5441370c3e35908edd234ff036d4298708a1d4cf8a85", size = 8307, upload-time = "2024-12-07T02:54:41.031Z" }, -] - [[package]] name = "typing-extensions" version = "4.15.0" From b03e7821d46d2876c1ddd5995bad24b9f59ca76e Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 1 Feb 2026 17:26:58 -0800 Subject: [PATCH 804/910] replace smbus2 package with minimal implementation (#37061) * replace smbus2 package with minimal implementation * cleanup * fix up --- common/i2c.py | 81 ++++++++++++++++++++++++++++ pyproject.toml | 3 -- system/hardware/tici/amplifier.py | 3 +- system/sensord/sensors/i2c_sensor.py | 5 +- uv.lock | 11 ---- 5 files changed, 86 insertions(+), 17 deletions(-) create mode 100644 common/i2c.py diff --git a/common/i2c.py b/common/i2c.py new file mode 100644 index 000000000..1dfaa659a --- /dev/null +++ b/common/i2c.py @@ -0,0 +1,81 @@ +import os +import fcntl +import ctypes + +# I2C constants from /usr/include/linux/i2c-dev.h +I2C_SLAVE = 0x0703 +I2C_SLAVE_FORCE = 0x0706 +I2C_SMBUS = 0x0720 + +# SMBus transfer types +I2C_SMBUS_READ = 1 +I2C_SMBUS_WRITE = 0 +I2C_SMBUS_BYTE_DATA = 2 +I2C_SMBUS_I2C_BLOCK_DATA = 8 + +I2C_SMBUS_BLOCK_MAX = 32 + + +class _I2cSmbusData(ctypes.Union): + _fields_ = [ + ("byte", ctypes.c_uint8), + ("word", ctypes.c_uint16), + ("block", ctypes.c_uint8 * (I2C_SMBUS_BLOCK_MAX + 2)), + ] + + +class _I2cSmbusIoctlData(ctypes.Structure): + _fields_ = [ + ("read_write", ctypes.c_uint8), + ("command", ctypes.c_uint8), + ("size", ctypes.c_uint32), + ("data", ctypes.POINTER(_I2cSmbusData)), + ] + + +class SMBus: + def __init__(self, bus: int): + self._fd = os.open(f'/dev/i2c-{bus}', os.O_RDWR) + + def __enter__(self) -> 'SMBus': + return self + + def __exit__(self, *args) -> None: + self.close() + + def close(self) -> None: + if hasattr(self, '_fd') and self._fd >= 0: + os.close(self._fd) + self._fd = -1 + + def _set_address(self, addr: int, force: bool = False) -> None: + ioctl_arg = I2C_SLAVE_FORCE if force else I2C_SLAVE + fcntl.ioctl(self._fd, ioctl_arg, addr) + + def _smbus_access(self, read_write: int, command: int, size: int, data: _I2cSmbusData) -> None: + ioctl_data = _I2cSmbusIoctlData(read_write, command, size, ctypes.pointer(data)) + fcntl.ioctl(self._fd, I2C_SMBUS, ioctl_data) + + def read_byte_data(self, addr: int, register: int, force: bool = False) -> int: + self._set_address(addr, force) + data = _I2cSmbusData() + self._smbus_access(I2C_SMBUS_READ, register, I2C_SMBUS_BYTE_DATA, data) + return int(data.byte) + + def write_byte_data(self, addr: int, register: int, value: int, force: bool = False) -> None: + self._set_address(addr, force) + data = _I2cSmbusData() + data.byte = value & 0xFF + self._smbus_access(I2C_SMBUS_WRITE, register, I2C_SMBUS_BYTE_DATA, data) + + def read_i2c_block_data(self, addr: int, register: int, length: int, force: bool = False) -> list[int]: + self._set_address(addr, force) + if not (0 <= length <= I2C_SMBUS_BLOCK_MAX): + raise ValueError(f"length must be 0..{I2C_SMBUS_BLOCK_MAX}") + + data = _I2cSmbusData() + data.block[0] = length + self._smbus_access(I2C_SMBUS_READ, register, I2C_SMBUS_I2C_BLOCK_DATA, data) + read_len = int(data.block[0]) or length + read_len = min(read_len, length) + return [int(b) for b in data.block[1 : read_len + 1]] diff --git a/pyproject.toml b/pyproject.toml index bba80ee8d..19491ba53 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,9 +17,6 @@ dependencies = [ "crcmod-plus", # cars + qcomgpsd "tqdm", # cars (fw_versions.py) on start + many one-off uses - # hardwared - "smbus2", # configuring amp - # core "cffi", "scons", diff --git a/system/hardware/tici/amplifier.py b/system/hardware/tici/amplifier.py index d714837bb..09436e6ff 100755 --- a/system/hardware/tici/amplifier.py +++ b/system/hardware/tici/amplifier.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 import time -from smbus2 import SMBus from collections import namedtuple +from openpilot.common.i2c import SMBus + # https://datasheets.maximintegrated.com/en/ds/MAX98089.pdf AmpConfig = namedtuple('AmpConfig', ['name', 'value', 'register', 'offset', 'mask']) diff --git a/system/sensord/sensors/i2c_sensor.py b/system/sensord/sensors/i2c_sensor.py index 336ebb1fd..57edcc52d 100644 --- a/system/sensord/sensors/i2c_sensor.py +++ b/system/sensord/sensors/i2c_sensor.py @@ -1,9 +1,10 @@ import time -import smbus2 import ctypes from collections.abc import Iterable from cereal import log +from openpilot.common.i2c import SMBus + class Sensor: class SensorException(Exception): @@ -13,7 +14,7 @@ class Sensor: pass def __init__(self, bus: int) -> None: - self.bus = smbus2.SMBus(bus) + self.bus = SMBus(bus) self.source = log.SensorEventData.SensorSource.velodyne # unknown self.start_ts = 0. diff --git a/uv.lock b/uv.lock index 3e3522eb3..2c5f32ec7 100644 --- a/uv.lock +++ b/uv.lock @@ -1316,7 +1316,6 @@ dependencies = [ { name = "sentry-sdk" }, { name = "setproctitle" }, { name = "setuptools" }, - { name = "smbus2" }, { name = "sounddevice" }, { name = "spidev", marker = "sys_platform == 'linux'" }, { name = "sympy" }, @@ -1423,7 +1422,6 @@ requires-dist = [ { name = "sentry-sdk" }, { name = "setproctitle" }, { name = "setuptools" }, - { name = "smbus2" }, { name = "sounddevice" }, { name = "spidev", marker = "sys_platform == 'linux'" }, { name = "sympy" }, @@ -4741,15 +4739,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, ] -[[package]] -name = "smbus2" -version = "0.6.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4e/36/afafd43770caae69f04e21402552a8f94a072def46a002fab9357f4852ce/smbus2-0.6.0.tar.gz", hash = "sha256:9b5ff1e998e114730f9dfe0c4babbef06c92468cfb61eaa684e30f225661b95b", size = 17403, upload-time = "2025-12-20T09:02:52.017Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a5/cf/2e1d6805da6f9c9b3a4358076ff2e072d828ba7fed124edc1b729e210c55/smbus2-0.6.0-py2.py3-none-any.whl", hash = "sha256:03d83d2a9a4afc5ddca0698ccabf101cb3de52bc5aefd7b76778ffb27ff654e0", size = 11849, upload-time = "2025-12-20T09:02:51.219Z" }, -] - [[package]] name = "sortedcontainers" version = "2.4.0" From 5fc4c2b25cce04ce6229da76893a2b46d010ccde Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 1 Feb 2026 20:00:55 -0800 Subject: [PATCH 805/910] ubloxd: remove kaitai (#37055) * rm kaitai * lil less * bs * lil less * lil less --- SConstruct | 2 - pyproject.toml | 3 - system/ubloxd/SConscript | 11 -- system/ubloxd/binary_struct.py | 280 +++++++++++++++++++++++++++ system/ubloxd/generated/glonass.py | 247 ------------------------ system/ubloxd/generated/gps.py | 193 ------------------- system/ubloxd/generated/ubx.py | 273 --------------------------- system/ubloxd/glonass.ksy | 176 ----------------- system/ubloxd/glonass.py | 156 +++++++++++++++ system/ubloxd/gps.ksy | 189 ------------------- system/ubloxd/gps.py | 116 ++++++++++++ system/ubloxd/ubloxd.py | 33 +++- system/ubloxd/ubx.ksy | 293 ----------------------------- system/ubloxd/ubx.py | 180 ++++++++++++++++++ uv.lock | 11 -- 15 files changed, 756 insertions(+), 1407 deletions(-) delete mode 100644 system/ubloxd/SConscript create mode 100644 system/ubloxd/binary_struct.py delete mode 100644 system/ubloxd/generated/glonass.py delete mode 100644 system/ubloxd/generated/gps.py delete mode 100644 system/ubloxd/generated/ubx.py delete mode 100644 system/ubloxd/glonass.ksy create mode 100644 system/ubloxd/glonass.py delete mode 100644 system/ubloxd/gps.ksy create mode 100644 system/ubloxd/gps.py delete mode 100644 system/ubloxd/ubx.ksy create mode 100644 system/ubloxd/ubx.py diff --git a/SConstruct b/SConstruct index 094503cfa..ca5b7b6cb 100644 --- a/SConstruct +++ b/SConstruct @@ -14,7 +14,6 @@ Decider('MD5-timestamp') SetOption('num_jobs', max(1, int(os.cpu_count()/2))) -AddOption('--kaitai', action='store_true', help='Regenerate kaitai struct parsers') AddOption('--asan', action='store_true', help='turn on ASAN') AddOption('--ubsan', action='store_true', help='turn on UBSan') AddOption('--mutation', action='store_true', help='generate mutation-ready code') @@ -202,7 +201,6 @@ SConscript(['rednose/SConscript']) # Build system services SConscript([ - 'system/ubloxd/SConscript', 'system/loggerd/SConscript', ]) diff --git a/pyproject.toml b/pyproject.toml index 19491ba53..1be5c395f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,9 +33,6 @@ dependencies = [ "pyopenssl < 24.3.0", "pyaudio", - # ubloxd (TODO: just use struct) - "kaitaistruct", - # panda "libusb1", "spidev; platform_system == 'Linux'", diff --git a/system/ubloxd/SConscript b/system/ubloxd/SConscript deleted file mode 100644 index 9eb50760b..000000000 --- a/system/ubloxd/SConscript +++ /dev/null @@ -1,11 +0,0 @@ -Import('env') - -if GetOption('kaitai'): - current_dir = Dir('./generated/').srcnode().abspath - python_cmd = f"kaitai-struct-compiler --target python --outdir {current_dir} $SOURCES" - env.Command(File('./generated/ubx.py'), 'ubx.ksy', python_cmd) - env.Command(File('./generated/gps.py'), 'gps.ksy', python_cmd) - env.Command(File('./generated/glonass.py'), 'glonass.ksy', python_cmd) - # kaitai issue: https://github.com/kaitai-io/kaitai_struct/issues/910 - py_glonass_fix = env.Command(None, File('./generated/glonass.py'), "sed -i 's/self._io.align_to_byte()/# self._io.align_to_byte()/' $SOURCES") - env.Depends(py_glonass_fix, File('./generated/glonass.py')) diff --git a/system/ubloxd/binary_struct.py b/system/ubloxd/binary_struct.py new file mode 100644 index 000000000..7b229620a --- /dev/null +++ b/system/ubloxd/binary_struct.py @@ -0,0 +1,280 @@ +""" +Binary struct parsing DSL. + +Defines a declarative schema for binary messages using dataclasses +and type annotations. +""" + +import struct +from enum import Enum +from dataclasses import dataclass, is_dataclass +from typing import Annotated, Any, TypeVar, get_args, get_origin + + +class FieldType: + """Base class for field type descriptors.""" + + +@dataclass(frozen=True) +class IntType(FieldType): + bits: int + signed: bool + big_endian: bool = False + +@dataclass(frozen=True) +class FloatType(FieldType): + bits: int + +@dataclass(frozen=True) +class BitsType(FieldType): + bits: int + +@dataclass(frozen=True) +class BytesType(FieldType): + size: int + +@dataclass(frozen=True) +class ArrayType(FieldType): + element_type: Any + count_field: str + +@dataclass(frozen=True) +class SwitchType(FieldType): + selector: str + cases: dict[Any, Any] + default: Any = None + +@dataclass(frozen=True) +class EnumType(FieldType): + base_type: FieldType + enum_cls: type[Enum] + +@dataclass(frozen=True) +class ConstType(FieldType): + base_type: FieldType + expected: Any + +@dataclass(frozen=True) +class SubstreamType(FieldType): + length_field: str + element_type: Any + +# Common types - little endian +u8 = IntType(8, False) +u16 = IntType(16, False) +u32 = IntType(32, False) +s8 = IntType(8, True) +s16 = IntType(16, True) +s32 = IntType(32, True) +f32 = FloatType(32) +f64 = FloatType(64) +# Big endian variants +u16be = IntType(16, False, big_endian=True) +u32be = IntType(32, False, big_endian=True) +s16be = IntType(16, True, big_endian=True) +s32be = IntType(32, True, big_endian=True) + + +def bits(n: int) -> BitsType: + """Create a bit-level field type.""" + return BitsType(n) + +def bytes_field(size: int) -> BytesType: + """Create a fixed-size bytes field.""" + return BytesType(size) + +def array(element_type: Any, count_field: str) -> ArrayType: + """Create an array/repeated field.""" + return ArrayType(element_type, count_field) + +def switch(selector: str, cases: dict[Any, Any], default: Any = None) -> SwitchType: + """Create a switch-on field.""" + return SwitchType(selector, cases, default) + +def enum(base_type: Any, enum_cls: type[Enum]) -> EnumType: + """Create an enum-wrapped field.""" + field_type = _field_type_from_spec(base_type) + if field_type is None: + raise TypeError(f"Unsupported field type: {base_type!r}") + return EnumType(field_type, enum_cls) + +def const(base_type: Any, expected: Any) -> ConstType: + """Create a constant-value field.""" + field_type = _field_type_from_spec(base_type) + if field_type is None: + raise TypeError(f"Unsupported field type: {base_type!r}") + return ConstType(field_type, expected) + +def substream(length_field: str, element_type: Any) -> SubstreamType: + """Parse a fixed-length substream using an inner schema.""" + return SubstreamType(length_field, element_type) + + +class BinaryReader: + def __init__(self, data: bytes): + self.data = data + self.pos = 0 + self.bit_pos = 0 # 0-7, position within current byte + + def _require(self, n: int) -> None: + if self.pos + n > len(self.data): + raise EOFError("Unexpected end of data") + + def _read_struct(self, fmt: str): + self._align_to_byte() + size = struct.calcsize(fmt) + self._require(size) + value = struct.unpack_from(fmt, self.data, self.pos)[0] + self.pos += size + return value + + def read_bytes(self, n: int) -> bytes: + self._align_to_byte() + self._require(n) + result = self.data[self.pos : self.pos + n] + self.pos += n + return result + + def read_bits_int_be(self, n: int) -> int: + result = 0 + bits_remaining = n + while bits_remaining > 0: + if self.pos >= len(self.data): + raise EOFError("Unexpected end of data while reading bits") + bits_in_byte = 8 - self.bit_pos + bits_to_read = min(bits_remaining, bits_in_byte) + byte_val = self.data[self.pos] + shift = bits_in_byte - bits_to_read + mask = (1 << bits_to_read) - 1 + extracted = (byte_val >> shift) & mask + result = (result << bits_to_read) | extracted + self.bit_pos += bits_to_read + bits_remaining -= bits_to_read + if self.bit_pos >= 8: + self.bit_pos = 0 + self.pos += 1 + return result + + def _align_to_byte(self) -> None: + if self.bit_pos > 0: + self.bit_pos = 0 + self.pos += 1 + + +T = TypeVar('T', bound='BinaryStruct') + + +class BinaryStruct: + """Base class for binary struct definitions.""" + + def __init_subclass__(cls, **kwargs) -> None: + super().__init_subclass__(**kwargs) + if cls is BinaryStruct: + return + if not is_dataclass(cls): + dataclass(init=False)(cls) + fields = list(getattr(cls, '__annotations__', {}).items()) + cls.__binary_fields__ = fields # type: ignore[attr-defined] + + @classmethod + def _read(inner_cls, reader: BinaryReader): + obj = inner_cls.__new__(inner_cls) + for name, spec in inner_cls.__binary_fields__: + value = _parse_field(spec, reader, obj) + setattr(obj, name, value) + return obj + + cls._read = _read # type: ignore[attr-defined] + + @classmethod + def from_bytes(cls: type[T], data: bytes) -> T: + """Parse struct from bytes.""" + reader = BinaryReader(data) + return cls._read(reader) + + @classmethod + def _read(cls: type[T], reader: BinaryReader) -> T: + """Override in subclasses to implement parsing.""" + raise NotImplementedError + + +def _resolve_path(obj: Any, path: str) -> Any: + cur = obj + for part in path.split('.'): + cur = getattr(cur, part) + return cur + +def _unwrap_annotated(spec: Any) -> tuple[Any, ...]: + if get_origin(spec) is Annotated: + return get_args(spec)[1:] + return () + +def _field_type_from_spec(spec: Any) -> FieldType | None: + if isinstance(spec, FieldType): + return spec + for item in _unwrap_annotated(spec): + if isinstance(item, FieldType): + return item + return None + + +def _int_format(field_type: IntType) -> str: + if field_type.bits == 8: + return 'b' if field_type.signed else 'B' + endian = '>' if field_type.big_endian else '<' + if field_type.bits == 16: + code = 'h' if field_type.signed else 'H' + elif field_type.bits == 32: + code = 'i' if field_type.signed else 'I' + else: + raise ValueError(f"Unsupported integer size: {field_type.bits}") + return f"{endian}{code}" + +def _float_format(field_type: FloatType) -> str: + if field_type.bits == 32: + return ' Any: + field_type = _field_type_from_spec(spec) + if field_type is not None: + spec = field_type + if isinstance(spec, ConstType): + value = _parse_field(spec.base_type, reader, obj) + if value != spec.expected: + raise ValueError(f"Invalid constant: expected {spec.expected!r}, got {value!r}") + return value + if isinstance(spec, EnumType): + raw = _parse_field(spec.base_type, reader, obj) + try: + return spec.enum_cls(raw) + except ValueError: + return raw + if isinstance(spec, SwitchType): + key = _resolve_path(obj, spec.selector) + target = spec.cases.get(key, spec.default) + if target is None: + return None + return _parse_field(target, reader, obj) + if isinstance(spec, ArrayType): + count = _resolve_path(obj, spec.count_field) + return [_parse_field(spec.element_type, reader, obj) for _ in range(int(count))] + if isinstance(spec, SubstreamType): + length = _resolve_path(obj, spec.length_field) + data = reader.read_bytes(int(length)) + sub_reader = BinaryReader(data) + return _parse_field(spec.element_type, sub_reader, obj) + if isinstance(spec, IntType): + return reader._read_struct(_int_format(spec)) + if isinstance(spec, FloatType): + return reader._read_struct(_float_format(spec)) + if isinstance(spec, BitsType): + value = reader.read_bits_int_be(spec.bits) + return bool(value) if spec.bits == 1 else value + if isinstance(spec, BytesType): + return reader.read_bytes(spec.size) + if isinstance(spec, type) and issubclass(spec, BinaryStruct): + return spec._read(reader) + raise TypeError(f"Unsupported field spec: {spec!r}") diff --git a/system/ubloxd/generated/glonass.py b/system/ubloxd/generated/glonass.py deleted file mode 100644 index 40aa16bb6..000000000 --- a/system/ubloxd/generated/glonass.py +++ /dev/null @@ -1,247 +0,0 @@ -# This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild - -import kaitaistruct -from kaitaistruct import KaitaiStruct, KaitaiStream, BytesIO - - -if getattr(kaitaistruct, 'API_VERSION', (0, 9)) < (0, 9): - raise Exception("Incompatible Kaitai Struct Python API: 0.9 or later is required, but you have %s" % (kaitaistruct.__version__)) - -class Glonass(KaitaiStruct): - def __init__(self, _io, _parent=None, _root=None): - self._io = _io - self._parent = _parent - self._root = _root if _root else self - self._read() - - def _read(self): - self.idle_chip = self._io.read_bits_int_be(1) != 0 - self.string_number = self._io.read_bits_int_be(4) - # workaround for kaitai bit alignment issue (see glonass_fix.patch for C++) - # self._io.align_to_byte() - _on = self.string_number - if _on == 4: - self.data = Glonass.String4(self._io, self, self._root) - elif _on == 1: - self.data = Glonass.String1(self._io, self, self._root) - elif _on == 3: - self.data = Glonass.String3(self._io, self, self._root) - elif _on == 5: - self.data = Glonass.String5(self._io, self, self._root) - elif _on == 2: - self.data = Glonass.String2(self._io, self, self._root) - else: - self.data = Glonass.StringNonImmediate(self._io, self, self._root) - self.hamming_code = self._io.read_bits_int_be(8) - self.pad_1 = self._io.read_bits_int_be(11) - self.superframe_number = self._io.read_bits_int_be(16) - self.pad_2 = self._io.read_bits_int_be(8) - self.frame_number = self._io.read_bits_int_be(8) - - class String4(KaitaiStruct): - def __init__(self, _io, _parent=None, _root=None): - self._io = _io - self._parent = _parent - self._root = _root if _root else self - self._read() - - def _read(self): - self.tau_n_sign = self._io.read_bits_int_be(1) != 0 - self.tau_n_value = self._io.read_bits_int_be(21) - self.delta_tau_n_sign = self._io.read_bits_int_be(1) != 0 - self.delta_tau_n_value = self._io.read_bits_int_be(4) - self.e_n = self._io.read_bits_int_be(5) - self.not_used_1 = self._io.read_bits_int_be(14) - self.p4 = self._io.read_bits_int_be(1) != 0 - self.f_t = self._io.read_bits_int_be(4) - self.not_used_2 = self._io.read_bits_int_be(3) - self.n_t = self._io.read_bits_int_be(11) - self.n = self._io.read_bits_int_be(5) - self.m = self._io.read_bits_int_be(2) - - @property - def tau_n(self): - if hasattr(self, '_m_tau_n'): - return self._m_tau_n - - self._m_tau_n = ((self.tau_n_value * -1) if self.tau_n_sign else self.tau_n_value) - return getattr(self, '_m_tau_n', None) - - @property - def delta_tau_n(self): - if hasattr(self, '_m_delta_tau_n'): - return self._m_delta_tau_n - - self._m_delta_tau_n = ((self.delta_tau_n_value * -1) if self.delta_tau_n_sign else self.delta_tau_n_value) - return getattr(self, '_m_delta_tau_n', None) - - - class StringNonImmediate(KaitaiStruct): - def __init__(self, _io, _parent=None, _root=None): - self._io = _io - self._parent = _parent - self._root = _root if _root else self - self._read() - - def _read(self): - self.data_1 = self._io.read_bits_int_be(64) - self.data_2 = self._io.read_bits_int_be(8) - - - class String5(KaitaiStruct): - def __init__(self, _io, _parent=None, _root=None): - self._io = _io - self._parent = _parent - self._root = _root if _root else self - self._read() - - def _read(self): - self.n_a = self._io.read_bits_int_be(11) - self.tau_c = self._io.read_bits_int_be(32) - self.not_used = self._io.read_bits_int_be(1) != 0 - self.n_4 = self._io.read_bits_int_be(5) - self.tau_gps = self._io.read_bits_int_be(22) - self.l_n = self._io.read_bits_int_be(1) != 0 - - - class String1(KaitaiStruct): - def __init__(self, _io, _parent=None, _root=None): - self._io = _io - self._parent = _parent - self._root = _root if _root else self - self._read() - - def _read(self): - self.not_used = self._io.read_bits_int_be(2) - self.p1 = self._io.read_bits_int_be(2) - self.t_k = self._io.read_bits_int_be(12) - self.x_vel_sign = self._io.read_bits_int_be(1) != 0 - self.x_vel_value = self._io.read_bits_int_be(23) - self.x_accel_sign = self._io.read_bits_int_be(1) != 0 - self.x_accel_value = self._io.read_bits_int_be(4) - self.x_sign = self._io.read_bits_int_be(1) != 0 - self.x_value = self._io.read_bits_int_be(26) - - @property - def x_vel(self): - if hasattr(self, '_m_x_vel'): - return self._m_x_vel - - self._m_x_vel = ((self.x_vel_value * -1) if self.x_vel_sign else self.x_vel_value) - return getattr(self, '_m_x_vel', None) - - @property - def x_accel(self): - if hasattr(self, '_m_x_accel'): - return self._m_x_accel - - self._m_x_accel = ((self.x_accel_value * -1) if self.x_accel_sign else self.x_accel_value) - return getattr(self, '_m_x_accel', None) - - @property - def x(self): - if hasattr(self, '_m_x'): - return self._m_x - - self._m_x = ((self.x_value * -1) if self.x_sign else self.x_value) - return getattr(self, '_m_x', None) - - - class String2(KaitaiStruct): - def __init__(self, _io, _parent=None, _root=None): - self._io = _io - self._parent = _parent - self._root = _root if _root else self - self._read() - - def _read(self): - self.b_n = self._io.read_bits_int_be(3) - self.p2 = self._io.read_bits_int_be(1) != 0 - self.t_b = self._io.read_bits_int_be(7) - self.not_used = self._io.read_bits_int_be(5) - self.y_vel_sign = self._io.read_bits_int_be(1) != 0 - self.y_vel_value = self._io.read_bits_int_be(23) - self.y_accel_sign = self._io.read_bits_int_be(1) != 0 - self.y_accel_value = self._io.read_bits_int_be(4) - self.y_sign = self._io.read_bits_int_be(1) != 0 - self.y_value = self._io.read_bits_int_be(26) - - @property - def y_vel(self): - if hasattr(self, '_m_y_vel'): - return self._m_y_vel - - self._m_y_vel = ((self.y_vel_value * -1) if self.y_vel_sign else self.y_vel_value) - return getattr(self, '_m_y_vel', None) - - @property - def y_accel(self): - if hasattr(self, '_m_y_accel'): - return self._m_y_accel - - self._m_y_accel = ((self.y_accel_value * -1) if self.y_accel_sign else self.y_accel_value) - return getattr(self, '_m_y_accel', None) - - @property - def y(self): - if hasattr(self, '_m_y'): - return self._m_y - - self._m_y = ((self.y_value * -1) if self.y_sign else self.y_value) - return getattr(self, '_m_y', None) - - - class String3(KaitaiStruct): - def __init__(self, _io, _parent=None, _root=None): - self._io = _io - self._parent = _parent - self._root = _root if _root else self - self._read() - - def _read(self): - self.p3 = self._io.read_bits_int_be(1) != 0 - self.gamma_n_sign = self._io.read_bits_int_be(1) != 0 - self.gamma_n_value = self._io.read_bits_int_be(10) - self.not_used = self._io.read_bits_int_be(1) != 0 - self.p = self._io.read_bits_int_be(2) - self.l_n = self._io.read_bits_int_be(1) != 0 - self.z_vel_sign = self._io.read_bits_int_be(1) != 0 - self.z_vel_value = self._io.read_bits_int_be(23) - self.z_accel_sign = self._io.read_bits_int_be(1) != 0 - self.z_accel_value = self._io.read_bits_int_be(4) - self.z_sign = self._io.read_bits_int_be(1) != 0 - self.z_value = self._io.read_bits_int_be(26) - - @property - def gamma_n(self): - if hasattr(self, '_m_gamma_n'): - return self._m_gamma_n - - self._m_gamma_n = ((self.gamma_n_value * -1) if self.gamma_n_sign else self.gamma_n_value) - return getattr(self, '_m_gamma_n', None) - - @property - def z_vel(self): - if hasattr(self, '_m_z_vel'): - return self._m_z_vel - - self._m_z_vel = ((self.z_vel_value * -1) if self.z_vel_sign else self.z_vel_value) - return getattr(self, '_m_z_vel', None) - - @property - def z_accel(self): - if hasattr(self, '_m_z_accel'): - return self._m_z_accel - - self._m_z_accel = ((self.z_accel_value * -1) if self.z_accel_sign else self.z_accel_value) - return getattr(self, '_m_z_accel', None) - - @property - def z(self): - if hasattr(self, '_m_z'): - return self._m_z - - self._m_z = ((self.z_value * -1) if self.z_sign else self.z_value) - return getattr(self, '_m_z', None) - - diff --git a/system/ubloxd/generated/gps.py b/system/ubloxd/generated/gps.py deleted file mode 100644 index a999016f3..000000000 --- a/system/ubloxd/generated/gps.py +++ /dev/null @@ -1,193 +0,0 @@ -# This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild - -import kaitaistruct -from kaitaistruct import KaitaiStruct, KaitaiStream, BytesIO - - -if getattr(kaitaistruct, 'API_VERSION', (0, 9)) < (0, 9): - raise Exception("Incompatible Kaitai Struct Python API: 0.9 or later is required, but you have %s" % (kaitaistruct.__version__)) - -class Gps(KaitaiStruct): - def __init__(self, _io, _parent=None, _root=None): - self._io = _io - self._parent = _parent - self._root = _root if _root else self - self._read() - - def _read(self): - self.tlm = Gps.Tlm(self._io, self, self._root) - self.how = Gps.How(self._io, self, self._root) - _on = self.how.subframe_id - if _on == 1: - self.body = Gps.Subframe1(self._io, self, self._root) - elif _on == 2: - self.body = Gps.Subframe2(self._io, self, self._root) - elif _on == 3: - self.body = Gps.Subframe3(self._io, self, self._root) - elif _on == 4: - self.body = Gps.Subframe4(self._io, self, self._root) - - class Subframe1(KaitaiStruct): - def __init__(self, _io, _parent=None, _root=None): - self._io = _io - self._parent = _parent - self._root = _root if _root else self - self._read() - - def _read(self): - self.week_no = self._io.read_bits_int_be(10) - self.code = self._io.read_bits_int_be(2) - self.sv_accuracy = self._io.read_bits_int_be(4) - self.sv_health = self._io.read_bits_int_be(6) - self.iodc_msb = self._io.read_bits_int_be(2) - self.l2_p_data_flag = self._io.read_bits_int_be(1) != 0 - self.reserved1 = self._io.read_bits_int_be(23) - self.reserved2 = self._io.read_bits_int_be(24) - self.reserved3 = self._io.read_bits_int_be(24) - self.reserved4 = self._io.read_bits_int_be(16) - self._io.align_to_byte() - self.t_gd = self._io.read_s1() - self.iodc_lsb = self._io.read_u1() - self.t_oc = self._io.read_u2be() - self.af_2 = self._io.read_s1() - self.af_1 = self._io.read_s2be() - self.af_0_sign = self._io.read_bits_int_be(1) != 0 - self.af_0_value = self._io.read_bits_int_be(21) - self.reserved5 = self._io.read_bits_int_be(2) - - @property - def af_0(self): - if hasattr(self, '_m_af_0'): - return self._m_af_0 - - self._m_af_0 = ((self.af_0_value - (1 << 21)) if self.af_0_sign else self.af_0_value) - return getattr(self, '_m_af_0', None) - - - class Subframe3(KaitaiStruct): - def __init__(self, _io, _parent=None, _root=None): - self._io = _io - self._parent = _parent - self._root = _root if _root else self - self._read() - - def _read(self): - self.c_ic = self._io.read_s2be() - self.omega_0 = self._io.read_s4be() - self.c_is = self._io.read_s2be() - self.i_0 = self._io.read_s4be() - self.c_rc = self._io.read_s2be() - self.omega = self._io.read_s4be() - self.omega_dot_sign = self._io.read_bits_int_be(1) != 0 - self.omega_dot_value = self._io.read_bits_int_be(23) - self._io.align_to_byte() - self.iode = self._io.read_u1() - self.idot_sign = self._io.read_bits_int_be(1) != 0 - self.idot_value = self._io.read_bits_int_be(13) - self.reserved = self._io.read_bits_int_be(2) - - @property - def omega_dot(self): - if hasattr(self, '_m_omega_dot'): - return self._m_omega_dot - - self._m_omega_dot = ((self.omega_dot_value - (1 << 23)) if self.omega_dot_sign else self.omega_dot_value) - return getattr(self, '_m_omega_dot', None) - - @property - def idot(self): - if hasattr(self, '_m_idot'): - return self._m_idot - - self._m_idot = ((self.idot_value - (1 << 13)) if self.idot_sign else self.idot_value) - return getattr(self, '_m_idot', None) - - - class Subframe4(KaitaiStruct): - def __init__(self, _io, _parent=None, _root=None): - self._io = _io - self._parent = _parent - self._root = _root if _root else self - self._read() - - def _read(self): - self.data_id = self._io.read_bits_int_be(2) - self.page_id = self._io.read_bits_int_be(6) - self._io.align_to_byte() - _on = self.page_id - if _on == 56: - self.body = Gps.Subframe4.IonosphereData(self._io, self, self._root) - - class IonosphereData(KaitaiStruct): - def __init__(self, _io, _parent=None, _root=None): - self._io = _io - self._parent = _parent - self._root = _root if _root else self - self._read() - - def _read(self): - self.a0 = self._io.read_s1() - self.a1 = self._io.read_s1() - self.a2 = self._io.read_s1() - self.a3 = self._io.read_s1() - self.b0 = self._io.read_s1() - self.b1 = self._io.read_s1() - self.b2 = self._io.read_s1() - self.b3 = self._io.read_s1() - - - - class How(KaitaiStruct): - def __init__(self, _io, _parent=None, _root=None): - self._io = _io - self._parent = _parent - self._root = _root if _root else self - self._read() - - def _read(self): - self.tow_count = self._io.read_bits_int_be(17) - self.alert = self._io.read_bits_int_be(1) != 0 - self.anti_spoof = self._io.read_bits_int_be(1) != 0 - self.subframe_id = self._io.read_bits_int_be(3) - self.reserved = self._io.read_bits_int_be(2) - - - class Tlm(KaitaiStruct): - def __init__(self, _io, _parent=None, _root=None): - self._io = _io - self._parent = _parent - self._root = _root if _root else self - self._read() - - def _read(self): - self.preamble = self._io.read_bytes(1) - if not self.preamble == b"\x8B": - raise kaitaistruct.ValidationNotEqualError(b"\x8B", self.preamble, self._io, u"/types/tlm/seq/0") - self.tlm = self._io.read_bits_int_be(14) - self.integrity_status = self._io.read_bits_int_be(1) != 0 - self.reserved = self._io.read_bits_int_be(1) != 0 - - - class Subframe2(KaitaiStruct): - def __init__(self, _io, _parent=None, _root=None): - self._io = _io - self._parent = _parent - self._root = _root if _root else self - self._read() - - def _read(self): - self.iode = self._io.read_u1() - self.c_rs = self._io.read_s2be() - self.delta_n = self._io.read_s2be() - self.m_0 = self._io.read_s4be() - self.c_uc = self._io.read_s2be() - self.e = self._io.read_s4be() - self.c_us = self._io.read_s2be() - self.sqrt_a = self._io.read_u4be() - self.t_oe = self._io.read_u2be() - self.fit_interval_flag = self._io.read_bits_int_be(1) != 0 - self.aoda = self._io.read_bits_int_be(5) - self.reserved = self._io.read_bits_int_be(2) - - - diff --git a/system/ubloxd/generated/ubx.py b/system/ubloxd/generated/ubx.py deleted file mode 100644 index 994658438..000000000 --- a/system/ubloxd/generated/ubx.py +++ /dev/null @@ -1,273 +0,0 @@ -# This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild - -import kaitaistruct -from kaitaistruct import KaitaiStruct, KaitaiStream, BytesIO -from enum import Enum - - -if getattr(kaitaistruct, 'API_VERSION', (0, 9)) < (0, 9): - raise Exception("Incompatible Kaitai Struct Python API: 0.9 or later is required, but you have %s" % (kaitaistruct.__version__)) - -class Ubx(KaitaiStruct): - - class GnssType(Enum): - gps = 0 - sbas = 1 - galileo = 2 - beidou = 3 - imes = 4 - qzss = 5 - glonass = 6 - def __init__(self, _io, _parent=None, _root=None): - self._io = _io - self._parent = _parent - self._root = _root if _root else self - self._read() - - def _read(self): - self.magic = self._io.read_bytes(2) - if not self.magic == b"\xB5\x62": - raise kaitaistruct.ValidationNotEqualError(b"\xB5\x62", self.magic, self._io, u"/seq/0") - self.msg_type = self._io.read_u2be() - self.length = self._io.read_u2le() - _on = self.msg_type - if _on == 2569: - self.body = Ubx.MonHw(self._io, self, self._root) - elif _on == 533: - self.body = Ubx.RxmRawx(self._io, self, self._root) - elif _on == 531: - self.body = Ubx.RxmSfrbx(self._io, self, self._root) - elif _on == 309: - self.body = Ubx.NavSat(self._io, self, self._root) - elif _on == 2571: - self.body = Ubx.MonHw2(self._io, self, self._root) - elif _on == 263: - self.body = Ubx.NavPvt(self._io, self, self._root) - - class RxmRawx(KaitaiStruct): - def __init__(self, _io, _parent=None, _root=None): - self._io = _io - self._parent = _parent - self._root = _root if _root else self - self._read() - - def _read(self): - self.rcv_tow = self._io.read_f8le() - self.week = self._io.read_u2le() - self.leap_s = self._io.read_s1() - self.num_meas = self._io.read_u1() - self.rec_stat = self._io.read_u1() - self.reserved1 = self._io.read_bytes(3) - self._raw_meas = [] - self.meas = [] - for i in range(self.num_meas): - self._raw_meas.append(self._io.read_bytes(32)) - _io__raw_meas = KaitaiStream(BytesIO(self._raw_meas[i])) - self.meas.append(Ubx.RxmRawx.Measurement(_io__raw_meas, self, self._root)) - - - class Measurement(KaitaiStruct): - def __init__(self, _io, _parent=None, _root=None): - self._io = _io - self._parent = _parent - self._root = _root if _root else self - self._read() - - def _read(self): - self.pr_mes = self._io.read_f8le() - self.cp_mes = self._io.read_f8le() - self.do_mes = self._io.read_f4le() - self.gnss_id = KaitaiStream.resolve_enum(Ubx.GnssType, self._io.read_u1()) - self.sv_id = self._io.read_u1() - self.reserved2 = self._io.read_bytes(1) - self.freq_id = self._io.read_u1() - self.lock_time = self._io.read_u2le() - self.cno = self._io.read_u1() - self.pr_stdev = self._io.read_u1() - self.cp_stdev = self._io.read_u1() - self.do_stdev = self._io.read_u1() - self.trk_stat = self._io.read_u1() - self.reserved3 = self._io.read_bytes(1) - - - - class RxmSfrbx(KaitaiStruct): - def __init__(self, _io, _parent=None, _root=None): - self._io = _io - self._parent = _parent - self._root = _root if _root else self - self._read() - - def _read(self): - self.gnss_id = KaitaiStream.resolve_enum(Ubx.GnssType, self._io.read_u1()) - self.sv_id = self._io.read_u1() - self.reserved1 = self._io.read_bytes(1) - self.freq_id = self._io.read_u1() - self.num_words = self._io.read_u1() - self.reserved2 = self._io.read_bytes(1) - self.version = self._io.read_u1() - self.reserved3 = self._io.read_bytes(1) - self.body = [] - for i in range(self.num_words): - self.body.append(self._io.read_u4le()) - - - - class NavSat(KaitaiStruct): - def __init__(self, _io, _parent=None, _root=None): - self._io = _io - self._parent = _parent - self._root = _root if _root else self - self._read() - - def _read(self): - self.itow = self._io.read_u4le() - self.version = self._io.read_u1() - self.num_svs = self._io.read_u1() - self.reserved = self._io.read_bytes(2) - self._raw_svs = [] - self.svs = [] - for i in range(self.num_svs): - self._raw_svs.append(self._io.read_bytes(12)) - _io__raw_svs = KaitaiStream(BytesIO(self._raw_svs[i])) - self.svs.append(Ubx.NavSat.Nav(_io__raw_svs, self, self._root)) - - - class Nav(KaitaiStruct): - def __init__(self, _io, _parent=None, _root=None): - self._io = _io - self._parent = _parent - self._root = _root if _root else self - self._read() - - def _read(self): - self.gnss_id = KaitaiStream.resolve_enum(Ubx.GnssType, self._io.read_u1()) - self.sv_id = self._io.read_u1() - self.cno = self._io.read_u1() - self.elev = self._io.read_s1() - self.azim = self._io.read_s2le() - self.pr_res = self._io.read_s2le() - self.flags = self._io.read_u4le() - - - - class NavPvt(KaitaiStruct): - def __init__(self, _io, _parent=None, _root=None): - self._io = _io - self._parent = _parent - self._root = _root if _root else self - self._read() - - def _read(self): - self.i_tow = self._io.read_u4le() - self.year = self._io.read_u2le() - self.month = self._io.read_u1() - self.day = self._io.read_u1() - self.hour = self._io.read_u1() - self.min = self._io.read_u1() - self.sec = self._io.read_u1() - self.valid = self._io.read_u1() - self.t_acc = self._io.read_u4le() - self.nano = self._io.read_s4le() - self.fix_type = self._io.read_u1() - self.flags = self._io.read_u1() - self.flags2 = self._io.read_u1() - self.num_sv = self._io.read_u1() - self.lon = self._io.read_s4le() - self.lat = self._io.read_s4le() - self.height = self._io.read_s4le() - self.h_msl = self._io.read_s4le() - self.h_acc = self._io.read_u4le() - self.v_acc = self._io.read_u4le() - self.vel_n = self._io.read_s4le() - self.vel_e = self._io.read_s4le() - self.vel_d = self._io.read_s4le() - self.g_speed = self._io.read_s4le() - self.head_mot = self._io.read_s4le() - self.s_acc = self._io.read_s4le() - self.head_acc = self._io.read_u4le() - self.p_dop = self._io.read_u2le() - self.flags3 = self._io.read_u1() - self.reserved1 = self._io.read_bytes(5) - self.head_veh = self._io.read_s4le() - self.mag_dec = self._io.read_s2le() - self.mag_acc = self._io.read_u2le() - - - class MonHw2(KaitaiStruct): - - class ConfigSource(Enum): - flash = 102 - otp = 111 - config_pins = 112 - rom = 113 - def __init__(self, _io, _parent=None, _root=None): - self._io = _io - self._parent = _parent - self._root = _root if _root else self - self._read() - - def _read(self): - self.ofs_i = self._io.read_s1() - self.mag_i = self._io.read_u1() - self.ofs_q = self._io.read_s1() - self.mag_q = self._io.read_u1() - self.cfg_source = KaitaiStream.resolve_enum(Ubx.MonHw2.ConfigSource, self._io.read_u1()) - self.reserved1 = self._io.read_bytes(3) - self.low_lev_cfg = self._io.read_u4le() - self.reserved2 = self._io.read_bytes(8) - self.post_status = self._io.read_u4le() - self.reserved3 = self._io.read_bytes(4) - - - class MonHw(KaitaiStruct): - - class AntennaStatus(Enum): - init = 0 - dontknow = 1 - ok = 2 - short = 3 - open = 4 - - class AntennaPower(Enum): - false = 0 - true = 1 - dontknow = 2 - def __init__(self, _io, _parent=None, _root=None): - self._io = _io - self._parent = _parent - self._root = _root if _root else self - self._read() - - def _read(self): - self.pin_sel = self._io.read_u4le() - self.pin_bank = self._io.read_u4le() - self.pin_dir = self._io.read_u4le() - self.pin_val = self._io.read_u4le() - self.noise_per_ms = self._io.read_u2le() - self.agc_cnt = self._io.read_u2le() - self.a_status = KaitaiStream.resolve_enum(Ubx.MonHw.AntennaStatus, self._io.read_u1()) - self.a_power = KaitaiStream.resolve_enum(Ubx.MonHw.AntennaPower, self._io.read_u1()) - self.flags = self._io.read_u1() - self.reserved1 = self._io.read_bytes(1) - self.used_mask = self._io.read_u4le() - self.vp = self._io.read_bytes(17) - self.jam_ind = self._io.read_u1() - self.reserved2 = self._io.read_bytes(2) - self.pin_irq = self._io.read_u4le() - self.pull_h = self._io.read_u4le() - self.pull_l = self._io.read_u4le() - - - @property - def checksum(self): - if hasattr(self, '_m_checksum'): - return self._m_checksum - - _pos = self._io.pos() - self._io.seek((self.length + 6)) - self._m_checksum = self._io.read_u2le() - self._io.seek(_pos) - return getattr(self, '_m_checksum', None) - - diff --git a/system/ubloxd/glonass.ksy b/system/ubloxd/glonass.ksy deleted file mode 100644 index be99f6e49..000000000 --- a/system/ubloxd/glonass.ksy +++ /dev/null @@ -1,176 +0,0 @@ -# http://gauss.gge.unb.ca/GLONASS.ICD.pdf -# some variables are misprinted but good in the old doc -# https://www.unavco.org/help/glossary/docs/ICD_GLONASS_4.0_(1998)_en.pdf -meta: - id: glonass - endian: be - bit-endian: be -seq: - - id: idle_chip - type: b1 - - id: string_number - type: b4 - - id: data - type: - switch-on: string_number - cases: - 1: string_1 - 2: string_2 - 3: string_3 - 4: string_4 - 5: string_5 - _: string_non_immediate - - id: hamming_code - type: b8 - - id: pad_1 - type: b11 - - id: superframe_number - type: b16 - - id: pad_2 - type: b8 - - id: frame_number - type: b8 - -types: - string_1: - seq: - - id: not_used - type: b2 - - id: p1 - type: b2 - - id: t_k - type: b12 - - id: x_vel_sign - type: b1 - - id: x_vel_value - type: b23 - - id: x_accel_sign - type: b1 - - id: x_accel_value - type: b4 - - id: x_sign - type: b1 - - id: x_value - type: b26 - instances: - x_vel: - value: 'x_vel_sign ? (x_vel_value * (-1)) : x_vel_value' - x_accel: - value: 'x_accel_sign ? (x_accel_value * (-1)) : x_accel_value' - x: - value: 'x_sign ? (x_value * (-1)) : x_value' - string_2: - seq: - - id: b_n - type: b3 - - id: p2 - type: b1 - - id: t_b - type: b7 - - id: not_used - type: b5 - - id: y_vel_sign - type: b1 - - id: y_vel_value - type: b23 - - id: y_accel_sign - type: b1 - - id: y_accel_value - type: b4 - - id: y_sign - type: b1 - - id: y_value - type: b26 - instances: - y_vel: - value: 'y_vel_sign ? (y_vel_value * (-1)) : y_vel_value' - y_accel: - value: 'y_accel_sign ? (y_accel_value * (-1)) : y_accel_value' - y: - value: 'y_sign ? (y_value * (-1)) : y_value' - string_3: - seq: - - id: p3 - type: b1 - - id: gamma_n_sign - type: b1 - - id: gamma_n_value - type: b10 - - id: not_used - type: b1 - - id: p - type: b2 - - id: l_n - type: b1 - - id: z_vel_sign - type: b1 - - id: z_vel_value - type: b23 - - id: z_accel_sign - type: b1 - - id: z_accel_value - type: b4 - - id: z_sign - type: b1 - - id: z_value - type: b26 - instances: - gamma_n: - value: 'gamma_n_sign ? (gamma_n_value * (-1)) : gamma_n_value' - z_vel: - value: 'z_vel_sign ? (z_vel_value * (-1)) : z_vel_value' - z_accel: - value: 'z_accel_sign ? (z_accel_value * (-1)) : z_accel_value' - z: - value: 'z_sign ? (z_value * (-1)) : z_value' - string_4: - seq: - - id: tau_n_sign - type: b1 - - id: tau_n_value - type: b21 - - id: delta_tau_n_sign - type: b1 - - id: delta_tau_n_value - type: b4 - - id: e_n - type: b5 - - id: not_used_1 - type: b14 - - id: p4 - type: b1 - - id: f_t - type: b4 - - id: not_used_2 - type: b3 - - id: n_t - type: b11 - - id: n - type: b5 - - id: m - type: b2 - instances: - tau_n: - value: 'tau_n_sign ? (tau_n_value * (-1)) : tau_n_value' - delta_tau_n: - value: 'delta_tau_n_sign ? (delta_tau_n_value * (-1)) : delta_tau_n_value' - string_5: - seq: - - id: n_a - type: b11 - - id: tau_c - type: b32 - - id: not_used - type: b1 - - id: n_4 - type: b5 - - id: tau_gps - type: b22 - - id: l_n - type: b1 - string_non_immediate: - seq: - - id: data_1 - type: b64 - - id: data_2 - type: b8 diff --git a/system/ubloxd/glonass.py b/system/ubloxd/glonass.py new file mode 100644 index 000000000..144ccdde6 --- /dev/null +++ b/system/ubloxd/glonass.py @@ -0,0 +1,156 @@ +""" +Parses GLONASS navigation strings per GLONASS ICD specification. +http://gauss.gge.unb.ca/GLONASS.ICD.pdf +https://www.unavco.org/help/glossary/docs/ICD_GLONASS_4.0_(1998)_en.pdf +""" + +from typing import Annotated + +from openpilot.system.ubloxd import binary_struct as bs + + +class Glonass(bs.BinaryStruct): + class String1(bs.BinaryStruct): + not_used: Annotated[int, bs.bits(2)] + p1: Annotated[int, bs.bits(2)] + t_k: Annotated[int, bs.bits(12)] + x_vel_sign: Annotated[bool, bs.bits(1)] + x_vel_value: Annotated[int, bs.bits(23)] + x_accel_sign: Annotated[bool, bs.bits(1)] + x_accel_value: Annotated[int, bs.bits(4)] + x_sign: Annotated[bool, bs.bits(1)] + x_value: Annotated[int, bs.bits(26)] + + @property + def x_vel(self) -> int: + """Computed x_vel from sign-magnitude representation.""" + return (self.x_vel_value * -1) if self.x_vel_sign else self.x_vel_value + + @property + def x_accel(self) -> int: + """Computed x_accel from sign-magnitude representation.""" + return (self.x_accel_value * -1) if self.x_accel_sign else self.x_accel_value + + @property + def x(self) -> int: + """Computed x from sign-magnitude representation.""" + return (self.x_value * -1) if self.x_sign else self.x_value + + class String2(bs.BinaryStruct): + b_n: Annotated[int, bs.bits(3)] + p2: Annotated[bool, bs.bits(1)] + t_b: Annotated[int, bs.bits(7)] + not_used: Annotated[int, bs.bits(5)] + y_vel_sign: Annotated[bool, bs.bits(1)] + y_vel_value: Annotated[int, bs.bits(23)] + y_accel_sign: Annotated[bool, bs.bits(1)] + y_accel_value: Annotated[int, bs.bits(4)] + y_sign: Annotated[bool, bs.bits(1)] + y_value: Annotated[int, bs.bits(26)] + + @property + def y_vel(self) -> int: + """Computed y_vel from sign-magnitude representation.""" + return (self.y_vel_value * -1) if self.y_vel_sign else self.y_vel_value + + @property + def y_accel(self) -> int: + """Computed y_accel from sign-magnitude representation.""" + return (self.y_accel_value * -1) if self.y_accel_sign else self.y_accel_value + + @property + def y(self) -> int: + """Computed y from sign-magnitude representation.""" + return (self.y_value * -1) if self.y_sign else self.y_value + + class String3(bs.BinaryStruct): + p3: Annotated[bool, bs.bits(1)] + gamma_n_sign: Annotated[bool, bs.bits(1)] + gamma_n_value: Annotated[int, bs.bits(10)] + not_used: Annotated[bool, bs.bits(1)] + p: Annotated[int, bs.bits(2)] + l_n: Annotated[bool, bs.bits(1)] + z_vel_sign: Annotated[bool, bs.bits(1)] + z_vel_value: Annotated[int, bs.bits(23)] + z_accel_sign: Annotated[bool, bs.bits(1)] + z_accel_value: Annotated[int, bs.bits(4)] + z_sign: Annotated[bool, bs.bits(1)] + z_value: Annotated[int, bs.bits(26)] + + @property + def gamma_n(self) -> int: + """Computed gamma_n from sign-magnitude representation.""" + return (self.gamma_n_value * -1) if self.gamma_n_sign else self.gamma_n_value + + @property + def z_vel(self) -> int: + """Computed z_vel from sign-magnitude representation.""" + return (self.z_vel_value * -1) if self.z_vel_sign else self.z_vel_value + + @property + def z_accel(self) -> int: + """Computed z_accel from sign-magnitude representation.""" + return (self.z_accel_value * -1) if self.z_accel_sign else self.z_accel_value + + @property + def z(self) -> int: + """Computed z from sign-magnitude representation.""" + return (self.z_value * -1) if self.z_sign else self.z_value + + class String4(bs.BinaryStruct): + tau_n_sign: Annotated[bool, bs.bits(1)] + tau_n_value: Annotated[int, bs.bits(21)] + delta_tau_n_sign: Annotated[bool, bs.bits(1)] + delta_tau_n_value: Annotated[int, bs.bits(4)] + e_n: Annotated[int, bs.bits(5)] + not_used_1: Annotated[int, bs.bits(14)] + p4: Annotated[bool, bs.bits(1)] + f_t: Annotated[int, bs.bits(4)] + not_used_2: Annotated[int, bs.bits(3)] + n_t: Annotated[int, bs.bits(11)] + n: Annotated[int, bs.bits(5)] + m: Annotated[int, bs.bits(2)] + + @property + def tau_n(self) -> int: + """Computed tau_n from sign-magnitude representation.""" + return (self.tau_n_value * -1) if self.tau_n_sign else self.tau_n_value + + @property + def delta_tau_n(self) -> int: + """Computed delta_tau_n from sign-magnitude representation.""" + return (self.delta_tau_n_value * -1) if self.delta_tau_n_sign else self.delta_tau_n_value + + class String5(bs.BinaryStruct): + n_a: Annotated[int, bs.bits(11)] + tau_c: Annotated[int, bs.bits(32)] + not_used: Annotated[bool, bs.bits(1)] + n_4: Annotated[int, bs.bits(5)] + tau_gps: Annotated[int, bs.bits(22)] + l_n: Annotated[bool, bs.bits(1)] + + class StringNonImmediate(bs.BinaryStruct): + data_1: Annotated[int, bs.bits(64)] + data_2: Annotated[int, bs.bits(8)] + + idle_chip: Annotated[bool, bs.bits(1)] + string_number: Annotated[int, bs.bits(4)] + data: Annotated[ + object, + bs.switch( + 'string_number', + { + 1: String1, + 2: String2, + 3: String3, + 4: String4, + 5: String5, + }, + default=StringNonImmediate, + ), + ] + hamming_code: Annotated[int, bs.bits(8)] + pad_1: Annotated[int, bs.bits(11)] + superframe_number: Annotated[int, bs.bits(16)] + pad_2: Annotated[int, bs.bits(8)] + frame_number: Annotated[int, bs.bits(8)] diff --git a/system/ubloxd/gps.ksy b/system/ubloxd/gps.ksy deleted file mode 100644 index 893ad1b25..000000000 --- a/system/ubloxd/gps.ksy +++ /dev/null @@ -1,189 +0,0 @@ -# https://www.gps.gov/technical/icwg/IS-GPS-200E.pdf -meta: - id: gps - endian: be - bit-endian: be -seq: - - id: tlm - type: tlm - - id: how - type: how - - id: body - type: - switch-on: how.subframe_id - cases: - 1: subframe_1 - 2: subframe_2 - 3: subframe_3 - 4: subframe_4 -types: - tlm: - seq: - - id: preamble - contents: [0x8b] - - id: tlm - type: b14 - - id: integrity_status - type: b1 - - id: reserved - type: b1 - how: - seq: - - id: tow_count - type: b17 - - id: alert - type: b1 - - id: anti_spoof - type: b1 - - id: subframe_id - type: b3 - - id: reserved - type: b2 - subframe_1: - seq: - # Word 3 - - id: week_no - type: b10 - - id: code - type: b2 - - id: sv_accuracy - type: b4 - - id: sv_health - type: b6 - - id: iodc_msb - type: b2 - # Word 4 - - id: l2_p_data_flag - type: b1 - - id: reserved1 - type: b23 - # Word 5 - - id: reserved2 - type: b24 - # Word 6 - - id: reserved3 - type: b24 - # Word 7 - - id: reserved4 - type: b16 - - id: t_gd - type: s1 - # Word 8 - - id: iodc_lsb - type: u1 - - id: t_oc - type: u2 - # Word 9 - - id: af_2 - type: s1 - - id: af_1 - type: s2 - # Word 10 - - id: af_0_sign - type: b1 - - id: af_0_value - type: b21 - - id: reserved5 - type: b2 - instances: - af_0: - value: 'af_0_sign ? (af_0_value - (1 << 21)) : af_0_value' - subframe_2: - seq: - # Word 3 - - id: iode - type: u1 - - id: c_rs - type: s2 - # Word 4 & 5 - - id: delta_n - type: s2 - - id: m_0 - type: s4 - # Word 6 & 7 - - id: c_uc - type: s2 - - id: e - type: s4 - # Word 8 & 9 - - id: c_us - type: s2 - - id: sqrt_a - type: u4 - # Word 10 - - id: t_oe - type: u2 - - id: fit_interval_flag - type: b1 - - id: aoda - type: b5 - - id: reserved - type: b2 - subframe_3: - seq: - # Word 3 & 4 - - id: c_ic - type: s2 - - id: omega_0 - type: s4 - # Word 5 & 6 - - id: c_is - type: s2 - - id: i_0 - type: s4 - # Word 7 & 8 - - id: c_rc - type: s2 - - id: omega - type: s4 - # Word 9 - - id: omega_dot_sign - type: b1 - - id: omega_dot_value - type: b23 - # Word 10 - - id: iode - type: u1 - - id: idot_sign - type: b1 - - id: idot_value - type: b13 - - id: reserved - type: b2 - instances: - omega_dot: - value: 'omega_dot_sign ? (omega_dot_value - (1 << 23)) : omega_dot_value' - idot: - value: 'idot_sign ? (idot_value - (1 << 13)) : idot_value' - subframe_4: - seq: - # Word 3 - - id: data_id - type: b2 - - id: page_id - type: b6 - - id: body - type: - switch-on: page_id - cases: - 56: ionosphere_data - types: - ionosphere_data: - seq: - - id: a0 - type: s1 - - id: a1 - type: s1 - - id: a2 - type: s1 - - id: a3 - type: s1 - - id: b0 - type: s1 - - id: b1 - type: s1 - - id: b2 - type: s1 - - id: b3 - type: s1 - diff --git a/system/ubloxd/gps.py b/system/ubloxd/gps.py new file mode 100644 index 000000000..1c0833bd9 --- /dev/null +++ b/system/ubloxd/gps.py @@ -0,0 +1,116 @@ +""" +Parses GPS navigation subframes per IS-GPS-200E specification. +https://www.gps.gov/technical/icwg/IS-GPS-200E.pdf +""" + +from typing import Annotated + +from openpilot.system.ubloxd import binary_struct as bs + + +class Gps(bs.BinaryStruct): + class Tlm(bs.BinaryStruct): + preamble: Annotated[bytes, bs.const(bs.bytes_field(1), b"\x8b")] + tlm: Annotated[int, bs.bits(14)] + integrity_status: Annotated[bool, bs.bits(1)] + reserved: Annotated[bool, bs.bits(1)] + + class How(bs.BinaryStruct): + tow_count: Annotated[int, bs.bits(17)] + alert: Annotated[bool, bs.bits(1)] + anti_spoof: Annotated[bool, bs.bits(1)] + subframe_id: Annotated[int, bs.bits(3)] + reserved: Annotated[int, bs.bits(2)] + + class Subframe1(bs.BinaryStruct): + week_no: Annotated[int, bs.bits(10)] + code: Annotated[int, bs.bits(2)] + sv_accuracy: Annotated[int, bs.bits(4)] + sv_health: Annotated[int, bs.bits(6)] + iodc_msb: Annotated[int, bs.bits(2)] + l2_p_data_flag: Annotated[bool, bs.bits(1)] + reserved1: Annotated[int, bs.bits(23)] + reserved2: Annotated[int, bs.bits(24)] + reserved3: Annotated[int, bs.bits(24)] + reserved4: Annotated[int, bs.bits(16)] + t_gd: Annotated[int, bs.s8] + iodc_lsb: Annotated[int, bs.u8] + t_oc: Annotated[int, bs.u16be] + af_2: Annotated[int, bs.s8] + af_1: Annotated[int, bs.s16be] + af_0_sign: Annotated[bool, bs.bits(1)] + af_0_value: Annotated[int, bs.bits(21)] + reserved5: Annotated[int, bs.bits(2)] + + @property + def af_0(self) -> int: + """Computed af_0 from sign-magnitude representation.""" + return (self.af_0_value - (1 << 21)) if self.af_0_sign else self.af_0_value + + class Subframe2(bs.BinaryStruct): + iode: Annotated[int, bs.u8] + c_rs: Annotated[int, bs.s16be] + delta_n: Annotated[int, bs.s16be] + m_0: Annotated[int, bs.s32be] + c_uc: Annotated[int, bs.s16be] + e: Annotated[int, bs.s32be] + c_us: Annotated[int, bs.s16be] + sqrt_a: Annotated[int, bs.u32be] + t_oe: Annotated[int, bs.u16be] + fit_interval_flag: Annotated[bool, bs.bits(1)] + aoda: Annotated[int, bs.bits(5)] + reserved: Annotated[int, bs.bits(2)] + + class Subframe3(bs.BinaryStruct): + c_ic: Annotated[int, bs.s16be] + omega_0: Annotated[int, bs.s32be] + c_is: Annotated[int, bs.s16be] + i_0: Annotated[int, bs.s32be] + c_rc: Annotated[int, bs.s16be] + omega: Annotated[int, bs.s32be] + omega_dot_sign: Annotated[bool, bs.bits(1)] + omega_dot_value: Annotated[int, bs.bits(23)] + iode: Annotated[int, bs.u8] + idot_sign: Annotated[bool, bs.bits(1)] + idot_value: Annotated[int, bs.bits(13)] + reserved: Annotated[int, bs.bits(2)] + + @property + def omega_dot(self) -> int: + """Computed omega_dot from sign-magnitude representation.""" + return (self.omega_dot_value - (1 << 23)) if self.omega_dot_sign else self.omega_dot_value + + @property + def idot(self) -> int: + """Computed idot from sign-magnitude representation.""" + return (self.idot_value - (1 << 13)) if self.idot_sign else self.idot_value + + class Subframe4(bs.BinaryStruct): + class IonosphereData(bs.BinaryStruct): + a0: Annotated[int, bs.s8] + a1: Annotated[int, bs.s8] + a2: Annotated[int, bs.s8] + a3: Annotated[int, bs.s8] + b0: Annotated[int, bs.s8] + b1: Annotated[int, bs.s8] + b2: Annotated[int, bs.s8] + b3: Annotated[int, bs.s8] + + data_id: Annotated[int, bs.bits(2)] + page_id: Annotated[int, bs.bits(6)] + body: Annotated[object, bs.switch('page_id', {56: IonosphereData})] + + tlm: Tlm + how: How + body: Annotated[ + object, + bs.switch( + 'how.subframe_id', + { + 1: Subframe1, + 2: Subframe2, + 3: Subframe3, + 4: Subframe4, + }, + ), + ] diff --git a/system/ubloxd/ubloxd.py b/system/ubloxd/ubloxd.py index 6882ad095..e55cadcf7 100755 --- a/system/ubloxd/ubloxd.py +++ b/system/ubloxd/ubloxd.py @@ -8,9 +8,9 @@ from dataclasses import dataclass from cereal import log from cereal import messaging -from openpilot.system.ubloxd.generated.ubx import Ubx -from openpilot.system.ubloxd.generated.gps import Gps -from openpilot.system.ubloxd.generated.glonass import Glonass +from openpilot.system.ubloxd.ubx import Ubx +from openpilot.system.ubloxd.gps import Gps +from openpilot.system.ubloxd.glonass import Glonass SECS_IN_MIN = 60 @@ -52,7 +52,7 @@ class UbxFramer: # find preamble if len(self.buf) < 2: break - start = self.buf.find(b"\xB5\x62") + start = self.buf.find(b"\xb5\x62") if start < 0: # no preamble in buffer self.buf.clear() @@ -98,9 +98,22 @@ class UbloxMsgParser: # user range accuracy in meters glonass_URA_lookup: dict[int, float] = { - 0: 1, 1: 2, 2: 2.5, 3: 4, 4: 5, 5: 7, - 6: 10, 7: 12, 8: 14, 9: 16, 10: 32, - 11: 64, 12: 128, 13: 256, 14: 512, 15: 1024, + 0: 1, + 1: 2, + 2: 2.5, + 3: 4, + 4: 5, + 5: 7, + 6: 10, + 7: 12, + 8: 14, + 9: 16, + 10: 32, + 11: 64, + 12: 128, + 13: 256, + 14: 512, + 15: 1024, } def __init__(self) -> None: @@ -121,7 +134,7 @@ class UbloxMsgParser: body = Ubx.NavPvt.from_bytes(payload) return self._gen_nav_pvt(body) if msg_type == 0x0213: - # Manually parse RXM-SFRBX to avoid Kaitai EOF on some frames + # Manually parse RXM-SFRBX to avoid EOF on some frames if len(payload) < 8: return None gnss_id = payload[0] @@ -134,7 +147,7 @@ class UbloxMsgParser: words: list[int] = [] off = 8 for _ in range(num_words): - words.append(int.from_bytes(payload[off:off+4], 'little')) + words.append(int.from_bytes(payload[off : off + 4], 'little')) off += 4 class _SfrbxView: @@ -143,6 +156,7 @@ class UbloxMsgParser: self.sv_id = sid self.freq_id = fid self.body = body + view = _SfrbxView(gnss_id, sv_id, freq_id, words) return self._gen_rxm_sfrbx(view) if msg_type == 0x0215: @@ -515,5 +529,6 @@ def main(): service, dat = res pm.send(service, dat) + if __name__ == '__main__': main() diff --git a/system/ubloxd/ubx.ksy b/system/ubloxd/ubx.ksy deleted file mode 100644 index 02c757fe7..000000000 --- a/system/ubloxd/ubx.ksy +++ /dev/null @@ -1,293 +0,0 @@ -meta: - id: ubx - endian: le -seq: - - id: magic - contents: [0xb5, 0x62] - - id: msg_type - type: u2be - - id: length - type: u2 - - id: body - type: - switch-on: msg_type - cases: - 0x0107: nav_pvt - 0x0213: rxm_sfrbx - 0x0215: rxm_rawx - 0x0a09: mon_hw - 0x0a0b: mon_hw2 - 0x0135: nav_sat -instances: - checksum: - pos: length + 6 - type: u2 - -types: - mon_hw: - seq: - - id: pin_sel - type: u4 - - id: pin_bank - type: u4 - - id: pin_dir - type: u4 - - id: pin_val - type: u4 - - id: noise_per_ms - type: u2 - - id: agc_cnt - type: u2 - - id: a_status - type: u1 - enum: antenna_status - - id: a_power - type: u1 - enum: antenna_power - - id: flags - type: u1 - - id: reserved1 - size: 1 - - id: used_mask - type: u4 - - id: vp - size: 17 - - id: jam_ind - type: u1 - - id: reserved2 - size: 2 - - id: pin_irq - type: u4 - - id: pull_h - type: u4 - - id: pull_l - type: u4 - enums: - antenna_status: - 0: init - 1: dontknow - 2: ok - 3: short - 4: open - antenna_power: - 0: off - 1: on - 2: dontknow - - mon_hw2: - seq: - - id: ofs_i - type: s1 - - id: mag_i - type: u1 - - id: ofs_q - type: s1 - - id: mag_q - type: u1 - - id: cfg_source - type: u1 - enum: config_source - - id: reserved1 - size: 3 - - id: low_lev_cfg - type: u4 - - id: reserved2 - size: 8 - - id: post_status - type: u4 - - id: reserved3 - size: 4 - - enums: - config_source: - 113: rom - 111: otp - 112: config_pins - 102: flash - - rxm_sfrbx: - seq: - - id: gnss_id - type: u1 - enum: gnss_type - - id: sv_id - type: u1 - - id: reserved1 - size: 1 - - id: freq_id - type: u1 - - id: num_words - type: u1 - - id: reserved2 - size: 1 - - id: version - type: u1 - - id: reserved3 - size: 1 - - id: body - type: u4 - repeat: expr - repeat-expr: num_words - - rxm_rawx: - seq: - - id: rcv_tow - type: f8 - - id: week - type: u2 - - id: leap_s - type: s1 - - id: num_meas - type: u1 - - id: rec_stat - type: u1 - - id: reserved1 - size: 3 - - id: meas - type: measurement - size: 32 - repeat: expr - repeat-expr: num_meas - types: - measurement: - seq: - - id: pr_mes - type: f8 - - id: cp_mes - type: f8 - - id: do_mes - type: f4 - - id: gnss_id - type: u1 - enum: gnss_type - - id: sv_id - type: u1 - - id: reserved2 - size: 1 - - id: freq_id - type: u1 - - id: lock_time - type: u2 - - id: cno - type: u1 - - id: pr_stdev - type: u1 - - id: cp_stdev - type: u1 - - id: do_stdev - type: u1 - - id: trk_stat - type: u1 - - id: reserved3 - size: 1 - nav_sat: - seq: - - id: itow - type: u4 - - id: version - type: u1 - - id: num_svs - type: u1 - - id: reserved - size: 2 - - id: svs - type: nav - size: 12 - repeat: expr - repeat-expr: num_svs - types: - nav: - seq: - - id: gnss_id - type: u1 - enum: gnss_type - - id: sv_id - type: u1 - - id: cno - type: u1 - - id: elev - type: s1 - - id: azim - type: s2 - - id: pr_res - type: s2 - - id: flags - type: u4 - - nav_pvt: - seq: - - id: i_tow - type: u4 - - id: year - type: u2 - - id: month - type: u1 - - id: day - type: u1 - - id: hour - type: u1 - - id: min - type: u1 - - id: sec - type: u1 - - id: valid - type: u1 - - id: t_acc - type: u4 - - id: nano - type: s4 - - id: fix_type - type: u1 - - id: flags - type: u1 - - id: flags2 - type: u1 - - id: num_sv - type: u1 - - id: lon - type: s4 - - id: lat - type: s4 - - id: height - type: s4 - - id: h_msl - type: s4 - - id: h_acc - type: u4 - - id: v_acc - type: u4 - - id: vel_n - type: s4 - - id: vel_e - type: s4 - - id: vel_d - type: s4 - - id: g_speed - type: s4 - - id: head_mot - type: s4 - - id: s_acc - type: s4 - - id: head_acc - type: u4 - - id: p_dop - type: u2 - - id: flags3 - type: u1 - - id: reserved1 - size: 5 - - id: head_veh - type: s4 - - id: mag_dec - type: s2 - - id: mag_acc - type: u2 -enums: - gnss_type: - 0: gps - 1: sbas - 2: galileo - 3: beidou - 4: imes - 5: qzss - 6: glonass diff --git a/system/ubloxd/ubx.py b/system/ubloxd/ubx.py new file mode 100644 index 000000000..857498ebf --- /dev/null +++ b/system/ubloxd/ubx.py @@ -0,0 +1,180 @@ +""" +UBX protocol parser +""" + +from enum import IntEnum +from typing import Annotated + +from openpilot.system.ubloxd import binary_struct as bs + + +class GnssType(IntEnum): + gps = 0 + sbas = 1 + galileo = 2 + beidou = 3 + imes = 4 + qzss = 5 + glonass = 6 + + +class Ubx(bs.BinaryStruct): + GnssType = GnssType + + class RxmRawx(bs.BinaryStruct): + class Measurement(bs.BinaryStruct): + pr_mes: Annotated[float, bs.f64] + cp_mes: Annotated[float, bs.f64] + do_mes: Annotated[float, bs.f32] + gnss_id: Annotated[GnssType | int, bs.enum(bs.u8, GnssType)] + sv_id: Annotated[int, bs.u8] + reserved2: Annotated[bytes, bs.bytes_field(1)] + freq_id: Annotated[int, bs.u8] + lock_time: Annotated[int, bs.u16] + cno: Annotated[int, bs.u8] + pr_stdev: Annotated[int, bs.u8] + cp_stdev: Annotated[int, bs.u8] + do_stdev: Annotated[int, bs.u8] + trk_stat: Annotated[int, bs.u8] + reserved3: Annotated[bytes, bs.bytes_field(1)] + + rcv_tow: Annotated[float, bs.f64] + week: Annotated[int, bs.u16] + leap_s: Annotated[int, bs.s8] + num_meas: Annotated[int, bs.u8] + rec_stat: Annotated[int, bs.u8] + reserved1: Annotated[bytes, bs.bytes_field(3)] + meas: Annotated[list[Measurement], bs.array(Measurement, count_field='num_meas')] + + class RxmSfrbx(bs.BinaryStruct): + gnss_id: Annotated[GnssType | int, bs.enum(bs.u8, GnssType)] + sv_id: Annotated[int, bs.u8] + reserved1: Annotated[bytes, bs.bytes_field(1)] + freq_id: Annotated[int, bs.u8] + num_words: Annotated[int, bs.u8] + reserved2: Annotated[bytes, bs.bytes_field(1)] + version: Annotated[int, bs.u8] + reserved3: Annotated[bytes, bs.bytes_field(1)] + body: Annotated[list[int], bs.array(bs.u32, count_field='num_words')] + + class NavSat(bs.BinaryStruct): + class Nav(bs.BinaryStruct): + gnss_id: Annotated[GnssType | int, bs.enum(bs.u8, GnssType)] + sv_id: Annotated[int, bs.u8] + cno: Annotated[int, bs.u8] + elev: Annotated[int, bs.s8] + azim: Annotated[int, bs.s16] + pr_res: Annotated[int, bs.s16] + flags: Annotated[int, bs.u32] + + itow: Annotated[int, bs.u32] + version: Annotated[int, bs.u8] + num_svs: Annotated[int, bs.u8] + reserved: Annotated[bytes, bs.bytes_field(2)] + svs: Annotated[list[Nav], bs.array(Nav, count_field='num_svs')] + + class NavPvt(bs.BinaryStruct): + i_tow: Annotated[int, bs.u32] + year: Annotated[int, bs.u16] + month: Annotated[int, bs.u8] + day: Annotated[int, bs.u8] + hour: Annotated[int, bs.u8] + min: Annotated[int, bs.u8] + sec: Annotated[int, bs.u8] + valid: Annotated[int, bs.u8] + t_acc: Annotated[int, bs.u32] + nano: Annotated[int, bs.s32] + fix_type: Annotated[int, bs.u8] + flags: Annotated[int, bs.u8] + flags2: Annotated[int, bs.u8] + num_sv: Annotated[int, bs.u8] + lon: Annotated[int, bs.s32] + lat: Annotated[int, bs.s32] + height: Annotated[int, bs.s32] + h_msl: Annotated[int, bs.s32] + h_acc: Annotated[int, bs.u32] + v_acc: Annotated[int, bs.u32] + vel_n: Annotated[int, bs.s32] + vel_e: Annotated[int, bs.s32] + vel_d: Annotated[int, bs.s32] + g_speed: Annotated[int, bs.s32] + head_mot: Annotated[int, bs.s32] + s_acc: Annotated[int, bs.s32] + head_acc: Annotated[int, bs.u32] + p_dop: Annotated[int, bs.u16] + flags3: Annotated[int, bs.u8] + reserved1: Annotated[bytes, bs.bytes_field(5)] + head_veh: Annotated[int, bs.s32] + mag_dec: Annotated[int, bs.s16] + mag_acc: Annotated[int, bs.u16] + + class MonHw2(bs.BinaryStruct): + class ConfigSource(IntEnum): + flash = 102 + otp = 111 + config_pins = 112 + rom = 113 + + ofs_i: Annotated[int, bs.s8] + mag_i: Annotated[int, bs.u8] + ofs_q: Annotated[int, bs.s8] + mag_q: Annotated[int, bs.u8] + cfg_source: Annotated[ConfigSource | int, bs.enum(bs.u8, ConfigSource)] + reserved1: Annotated[bytes, bs.bytes_field(3)] + low_lev_cfg: Annotated[int, bs.u32] + reserved2: Annotated[bytes, bs.bytes_field(8)] + post_status: Annotated[int, bs.u32] + reserved3: Annotated[bytes, bs.bytes_field(4)] + + class MonHw(bs.BinaryStruct): + class AntennaStatus(IntEnum): + init = 0 + dontknow = 1 + ok = 2 + short = 3 + open = 4 + + class AntennaPower(IntEnum): + false = 0 + true = 1 + dontknow = 2 + + pin_sel: Annotated[int, bs.u32] + pin_bank: Annotated[int, bs.u32] + pin_dir: Annotated[int, bs.u32] + pin_val: Annotated[int, bs.u32] + noise_per_ms: Annotated[int, bs.u16] + agc_cnt: Annotated[int, bs.u16] + a_status: Annotated[AntennaStatus | int, bs.enum(bs.u8, AntennaStatus)] + a_power: Annotated[AntennaPower | int, bs.enum(bs.u8, AntennaPower)] + flags: Annotated[int, bs.u8] + reserved1: Annotated[bytes, bs.bytes_field(1)] + used_mask: Annotated[int, bs.u32] + vp: Annotated[bytes, bs.bytes_field(17)] + jam_ind: Annotated[int, bs.u8] + reserved2: Annotated[bytes, bs.bytes_field(2)] + pin_irq: Annotated[int, bs.u32] + pull_h: Annotated[int, bs.u32] + pull_l: Annotated[int, bs.u32] + + magic: Annotated[bytes, bs.const(bs.bytes_field(2), b"\xb5\x62")] + msg_type: Annotated[int, bs.u16be] + length: Annotated[int, bs.u16] + body: Annotated[ + object, + bs.substream( + 'length', + bs.switch( + 'msg_type', + { + 0x0107: NavPvt, + 0x0213: RxmSfrbx, + 0x0215: RxmRawx, + 0x0A09: MonHw, + 0x0A0B: MonHw2, + 0x0135: NavSat, + }, + ), + ), + ] + checksum: Annotated[int, bs.u16] diff --git a/uv.lock b/uv.lock index 2c5f32ec7..572305f08 100644 --- a/uv.lock +++ b/uv.lock @@ -768,15 +768,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/94/9e/820c4b086ad01ba7d77369fb8b11470a01fac9b4977f02e18659cf378b6b/json_rpc-1.15.0-py2.py3-none-any.whl", hash = "sha256:4a4668bbbe7116feb4abbd0f54e64a4adcf4b8f648f19ffa0848ad0f6606a9bf", size = 39450, upload-time = "2023-06-11T09:45:47.136Z" }, ] -[[package]] -name = "kaitaistruct" -version = "0.11" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/27/b8/ca7319556912f68832daa4b81425314857ec08dfccd8dbc8c0f65c992108/kaitaistruct-0.11.tar.gz", hash = "sha256:053ee764288e78b8e53acf748e9733268acbd579b8d82a427b1805453625d74b", size = 11519, upload-time = "2025-09-08T15:46:25.037Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4a/4a/cf14bf3b1f5ffb13c69cf5f0ea78031247790558ee88984a8bdd22fae60d/kaitaistruct-0.11-py2.py3-none-any.whl", hash = "sha256:5c6ce79177b4e193a577ecd359e26516d1d6d000a0bffd6e1010f2a46a62a561", size = 11372, upload-time = "2025-09-08T15:46:23.635Z" }, -] - [[package]] name = "kiwisolver" version = "1.4.9" @@ -1296,7 +1287,6 @@ dependencies = [ { name = "inputs" }, { name = "jeepney" }, { name = "json-rpc" }, - { name = "kaitaistruct" }, { name = "libusb1" }, { name = "mapbox-earcut" }, { name = "numpy" }, @@ -1384,7 +1374,6 @@ requires-dist = [ { name = "jeepney" }, { name = "jinja2", marker = "extra == 'docs'" }, { name = "json-rpc" }, - { name = "kaitaistruct" }, { name = "libusb1" }, { name = "mapbox-earcut" }, { name = "matplotlib", marker = "extra == 'dev'" }, From 831f2396d9a60eb8c73b7d0af4c4961353c73e51 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 2 Feb 2026 08:08:09 -0800 Subject: [PATCH 806/910] bump opendbc --- opendbc_repo | 2 +- pyproject.toml | 1 - uv.lock | 11 ----------- 3 files changed, 1 insertion(+), 13 deletions(-) diff --git a/opendbc_repo b/opendbc_repo index e76c2cf5b..7c78ee87b 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit e76c2cf5bb0042bc5822efa78fff0362feed7b54 +Subproject commit 7c78ee87b7b54bb2179d86d5e28c1f65bbf96669 diff --git a/pyproject.toml b/pyproject.toml index 1be5c395f..c80d23b8e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -74,7 +74,6 @@ dependencies = [ [project.optional-dependencies] docs = [ "Jinja2", - "natsort", "mkdocs", ] diff --git a/uv.lock b/uv.lock index 572305f08..054a67cc9 100644 --- a/uv.lock +++ b/uv.lock @@ -1183,15 +1183,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/81/08/7036c080d7117f28a4af526d794aab6a84463126db031b007717c1a6676e/multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56", size = 12319, upload-time = "2026-01-26T02:46:44.004Z" }, ] -[[package]] -name = "natsort" -version = "8.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e2/a9/a0c57aee75f77794adaf35322f8b6404cbd0f89ad45c87197a937764b7d0/natsort-8.4.0.tar.gz", hash = "sha256:45312c4a0e5507593da193dedd04abb1469253b601ecaf63445ad80f0a1ea581", size = 76575, upload-time = "2023-06-20T04:17:19.925Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/82/7a9d0550484a62c6da82858ee9419f3dd1ccc9aa1c26a1e43da3ecd20b0d/natsort-8.4.0-py3-none-any.whl", hash = "sha256:4732914fb471f56b5cce04d7bae6f164a592c7712e1c85f9ef585e197299521c", size = 38268, upload-time = "2023-06-20T04:17:17.522Z" }, -] - [[package]] name = "numpy" version = "2.4.1" @@ -1331,7 +1322,6 @@ dev = [ docs = [ { name = "jinja2" }, { name = "mkdocs" }, - { name = "natsort" }, ] testing = [ { name = "codespell" }, @@ -1379,7 +1369,6 @@ requires-dist = [ { name = "matplotlib", marker = "extra == 'dev'" }, { name = "metadrive-simulator", marker = "platform_machine != 'aarch64' and extra == 'tools'", url = "https://github.com/commaai/metadrive/releases/download/MetaDrive-minimal-0.4.2.4/metadrive_simulator-0.4.2.4-py3-none-any.whl" }, { name = "mkdocs", marker = "extra == 'docs'" }, - { name = "natsort", marker = "extra == 'docs'" }, { name = "numpy", specifier = ">=2.0" }, { name = "onnx", specifier = ">=1.14.0" }, { name = "opencv-python-headless", marker = "extra == 'dev'" }, From fd50941cff09b6710358aa9651c7dfeb94ffe79c Mon Sep 17 00:00:00 2001 From: Trey Moen <50057480+greatgitsby@users.noreply.github.com> Date: Mon, 2 Feb 2026 09:13:49 -0700 Subject: [PATCH 807/910] chore: bump minimum Python version to 3.12.3 (#37052) --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c80d23b8e..2400364c7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "openpilot" -requires-python = ">= 3.11, < 3.13" +requires-python = ">= 3.12.3, < 3.13" license = {text = "MIT License"} version = "0.1.0" description = "an open source driver assistance system" From a668bc9edad0223a79c797b8943fe8daaa95ade1 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 2 Feb 2026 16:58:45 -0800 Subject: [PATCH 808/910] comma four setup improvements (#37066) * always check, no flickering from has inter -> waiting -> has inter from the reset * 1s interval. i see read timeouts at 0.5s sometimes * clean up * cursor * Revert "cursor" This reverts commit 13ec6312aa7f71b58771f8789456e97c4481856a. * clean up --- system/ui/mici_setup.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 2c6090b4a..fac26f06e 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -49,7 +49,7 @@ exec ./launch_openpilot.sh class NetworkConnectivityMonitor: - def __init__(self, should_check: Callable[[], bool] | None = None, check_interval: float = 0.5): + def __init__(self, should_check: Callable[[], bool] | None = None, check_interval: float = 1.0): self.network_connected = threading.Event() self.wifi_connected = threading.Event() self._should_check = should_check or (lambda: True) @@ -78,7 +78,7 @@ class NetworkConnectivityMonitor: if self._should_check(): try: request = urllib.request.Request(OPENPILOT_URL, method="HEAD") - urllib.request.urlopen(request, timeout=0.5) + urllib.request.urlopen(request, timeout=1.0) self.network_connected.set() if HARDWARE.get_network_type() == NetworkType.wifi: self.wifi_connected.set() @@ -528,9 +528,8 @@ class Setup(Widget): self.download_thread = None self._wifi_manager = WifiManager() self._wifi_manager.set_active(True) - self._network_monitor = NetworkConnectivityMonitor( - lambda: self.state in (SetupState.NETWORK_SETUP, SetupState.NETWORK_SETUP_CUSTOM_SOFTWARE) - ) + self._network_monitor = NetworkConnectivityMonitor() + self._network_monitor.start() self._prev_has_internet = False gui_app.set_modal_overlay_tick(self._modal_overlay_tick) @@ -569,10 +568,8 @@ class Setup(Widget): if self.state in (SetupState.NETWORK_SETUP, SetupState.NETWORK_SETUP_CUSTOM_SOFTWARE): self._network_setup_page.show_event() self._network_monitor.reset() - self._network_monitor.start() else: self._network_setup_page.hide_event() - self._network_monitor.stop() def _render(self, rect: rl.Rectangle): if self.state == SetupState.GETTING_STARTED: @@ -618,7 +615,6 @@ class Setup(Widget): self._set_state(SetupState.SOFTWARE_SELECTION) def _network_setup_continue_button_callback(self): - self._network_monitor.stop() if self.state == SetupState.NETWORK_SETUP: self.download(OPENPILOT_URL) elif self.state == SetupState.NETWORK_SETUP_CUSTOM_SOFTWARE: @@ -628,10 +624,10 @@ class Setup(Widget): self._network_monitor.stop() def render_network_setup(self, rect: rl.Rectangle): - self._network_setup_page.render(rect) has_internet = self._network_monitor.network_connected.is_set() self._prev_has_internet = has_internet self._network_setup_page.set_has_internet(has_internet) + self._network_setup_page.render(rect) def render_downloading(self, rect: rl.Rectangle): self._downloading_page.set_progress(self.download_progress) From 8f970bcb99ac4e6e4e95be9a42fca2d4fa86de12 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 2 Feb 2026 22:13:37 -0500 Subject: [PATCH 809/910] Reapply "latcontrol_torque: lower kp and lower friction threshold (commaai/openpilot#36619)" (#1581) (#1669) * Reapply "latcontrol_torque: lower kp and lower friction threshold (commaai/openpilot#36619)" (#1581) This reverts commit 7560497f * bump --- opendbc_repo | 2 +- selfdrive/controls/lib/latcontrol_torque.py | 35 +++++++++------------ selfdrive/test/process_replay/ref_commit | 2 +- 3 files changed, 17 insertions(+), 22 deletions(-) diff --git a/opendbc_repo b/opendbc_repo index bbc6869d7..a65319968 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit bbc6869d70d99d70dffb73f737b36bce46f54cca +Subproject commit a653199681e95be113aea491bb7a6023c8b73eb1 diff --git a/selfdrive/controls/lib/latcontrol_torque.py b/selfdrive/controls/lib/latcontrol_torque.py index 47d105f99..48d174bfa 100644 --- a/selfdrive/controls/lib/latcontrol_torque.py +++ b/selfdrive/controls/lib/latcontrol_torque.py @@ -22,15 +22,17 @@ from openpilot.sunnypilot.selfdrive.controls.lib.latcontrol_torque_ext import La # Additionally, there is friction in the steering wheel that needs # to be overcome to move it at all, this is compensated for too. -KP = 1.0 -KI = 0.3 -KD = 0.0 +KP = 0.8 +KI = 0.15 + INTERP_SPEEDS = [1, 1.5, 2.0, 3.0, 5, 7.5, 10, 15, 30] KP_INTERP = [250, 120, 65, 30, 11.5, 5.5, 3.5, 2.0, KP] LP_FILTER_CUTOFF_HZ = 1.2 +JERK_LOOKAHEAD_SECONDS = 0.19 +JERK_GAIN = 0.3 LAT_ACCEL_REQUEST_BUFFER_SECONDS = 1.0 -VERSION = 0 +VERSION = 1 class LatControlTorque(LatControl): def __init__(self, CP, CP_SP, CI, dt): @@ -38,13 +40,13 @@ class LatControlTorque(LatControl): self.torque_params = CP.lateralTuning.torque.as_builder() self.torque_from_lateral_accel = CI.torque_from_lateral_accel() self.lateral_accel_from_torque = CI.lateral_accel_from_torque() - self.pid = PIDController([INTERP_SPEEDS, KP_INTERP], KI, KD, rate=1/self.dt) + self.pid = PIDController([INTERP_SPEEDS, KP_INTERP], KI, rate=1/self.dt) self.update_limits() self.steering_angle_deadzone_deg = self.torque_params.steeringAngleDeadzoneDeg self.lat_accel_request_buffer_len = int(LAT_ACCEL_REQUEST_BUFFER_SECONDS / self.dt) self.lat_accel_request_buffer = deque([0.] * self.lat_accel_request_buffer_len , maxlen=self.lat_accel_request_buffer_len) - self.previous_measurement = 0.0 - self.measurement_rate_filter = FirstOrderFilter(0.0, 1 / (2 * np.pi * LP_FILTER_CUTOFF_HZ), self.dt) + self.lookahead_frames = int(JERK_LOOKAHEAD_SECONDS / self.dt) + self.jerk_filter = FirstOrderFilter(0.0, 1 / (2 * np.pi * LP_FILTER_CUTOFF_HZ), self.dt) self.extension = LatControlTorqueExt(self, CP, CP_SP, CI) @@ -76,17 +78,15 @@ class LatControlTorque(LatControl): delay_frames = int(np.clip(lat_delay / self.dt, 1, self.lat_accel_request_buffer_len)) expected_lateral_accel = self.lat_accel_request_buffer[-delay_frames] - # TODO factor out lateral jerk from error to later replace it with delay independent alternative + lookahead_idx = int(np.clip(-delay_frames + self.lookahead_frames, -self.lat_accel_request_buffer_len+1, -2)) + raw_lateral_jerk = (self.lat_accel_request_buffer[lookahead_idx+1] - self.lat_accel_request_buffer[lookahead_idx-1]) / (2 * self.dt) + desired_lateral_jerk = self.jerk_filter.update(raw_lateral_jerk) future_desired_lateral_accel = desired_curvature * CS.vEgo ** 2 self.lat_accel_request_buffer.append(future_desired_lateral_accel) gravity_adjusted_future_lateral_accel = future_desired_lateral_accel - roll_compensation - desired_lateral_jerk = (future_desired_lateral_accel - expected_lateral_accel) / lat_delay + setpoint = expected_lateral_accel measurement = measured_curvature * CS.vEgo ** 2 - measurement_rate = self.measurement_rate_filter.update((measurement - self.previous_measurement) / self.dt) - self.previous_measurement = measurement - - setpoint = lat_delay * desired_lateral_jerk + expected_lateral_accel error = setpoint - measurement # do error correction in lateral acceleration space, convert at end to handle non-linear torque responses correctly @@ -94,15 +94,10 @@ class LatControlTorque(LatControl): ff = gravity_adjusted_future_lateral_accel # latAccelOffset corrects roll compensation bias from device roll misalignment relative to car roll ff -= self.torque_params.latAccelOffset - # TODO jerk is weighted by lat_delay for legacy reasons, but should be made independent of it - ff += get_friction(error, lateral_accel_deadzone, FRICTION_THRESHOLD, self.torque_params) + ff += get_friction(error + JERK_GAIN * desired_lateral_jerk, lateral_accel_deadzone, FRICTION_THRESHOLD, self.torque_params) freeze_integrator = steer_limited_by_safety or CS.steeringPressed or CS.vEgo < 5 - output_lataccel = self.pid.update(pid_log.error, - -measurement_rate, - feedforward=ff, - speed=CS.vEgo, - freeze_integrator=freeze_integrator) + output_lataccel = self.pid.update(pid_log.error, speed=CS.vEgo, feedforward=ff, freeze_integrator=freeze_integrator) output_torque = self.torque_from_lateral_accel(output_lataccel, self.torque_params) # Lateral acceleration torque controller extension updates diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index c109bf49f..4a58e321f 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -b259f6f8f099a9d82e4c65dd5deae2e4e293007b \ No newline at end of file +e0ad86508edb61b3eaa1b84662c515d2c3368295 \ No newline at end of file From 85b9f8962e8330283f3ec59a025ee97328dc29ae Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 2 Feb 2026 22:32:52 -0800 Subject: [PATCH 810/910] Clean up four keyboard text rects (#37068) * start clean up * rm * not really needed * more * clean up --- selfdrive/ui/mici/widgets/dialog.py | 43 ++++++++++++----------------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index 67123d33a..b23abe608 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -132,6 +132,7 @@ class BigConfirmationDialogV2(BigDialogBase): class BigInputDialog(BigDialogBase): BACK_TOUCH_AREA_PERCENTAGE = 0.2 BACKSPACE_RATE = 25 # hz + TEXT_INPUT_SIZE = 35 def __init__(self, hint: str, @@ -179,53 +180,44 @@ class BigInputDialog(BigDialogBase): self._backspace_held_time = None def _render(self, _): - text_input_size = 35 - # draw current text so far below everything. text floats left but always stays in view text = self._keyboard.text() candidate_char = self._keyboard.get_candidate_character() - text_size = measure_text_cached(gui_app.font(FontWeight.ROMAN), text + candidate_char or self._hint_label.text, text_input_size) - text_x = PADDING * 2 + self._enter_img.width + text_size = measure_text_cached(gui_app.font(FontWeight.ROMAN), text + candidate_char or self._hint_label.text, self.TEXT_INPUT_SIZE) - # text needs to move left if we're at the end where right button is - text_rect = rl.Rectangle(text_x, - int(self._rect.y + PADDING), - # clip width to right button when in view - int(self._rect.width - text_x - PADDING * 2 - self._enter_img.width + 5), # TODO: why 5? - int(text_size.y)) - - # draw rounded background for text input bg_block_margin = 5 - text_field_rect = rl.Rectangle(text_rect.x - bg_block_margin, text_rect.y - bg_block_margin, - text_rect.width + bg_block_margin * 2, text_input_size + bg_block_margin * 2) + text_x = PADDING * 2 + self._enter_img.width + bg_block_margin + text_field_rect = rl.Rectangle(text_x, int(self._rect.y + PADDING) - bg_block_margin, + int(self._rect.width - text_x - PADDING * 2 - self._enter_img.width) - bg_block_margin * 2, + int(text_size.y)) # draw text input # push text left with a gradient on left side if too long - if text_size.x > text_rect.width: - text_x -= text_size.x - text_rect.width + if text_size.x > text_field_rect.width: + text_x -= text_size.x - text_field_rect.width - rl.begin_scissor_mode(int(text_rect.x), int(text_rect.y), int(text_rect.width), int(text_rect.height)) - rl.draw_text_ex(gui_app.font(FontWeight.ROMAN), text, rl.Vector2(text_x, text_rect.y), text_input_size, 0, rl.WHITE) + rl.begin_scissor_mode(int(text_field_rect.x), int(text_field_rect.y), int(text_field_rect.width), int(text_field_rect.height)) + rl.draw_text_ex(gui_app.font(FontWeight.ROMAN), text, rl.Vector2(text_x, text_field_rect.y), self.TEXT_INPUT_SIZE, 0, rl.WHITE) # draw grayed out character user is hovering over if candidate_char: - candidate_char_size = measure_text_cached(gui_app.font(FontWeight.ROMAN), candidate_char, text_input_size) + candidate_char_size = measure_text_cached(gui_app.font(FontWeight.ROMAN), candidate_char, self.TEXT_INPUT_SIZE) rl.draw_text_ex(gui_app.font(FontWeight.ROMAN), candidate_char, - rl.Vector2(min(text_x + text_size.x, text_rect.x + text_rect.width) - candidate_char_size.x, text_rect.y), - text_input_size, 0, rl.Color(255, 255, 255, 128)) + rl.Vector2(min(text_x + text_size.x, text_field_rect.x + text_field_rect.width) - candidate_char_size.x, text_field_rect.y), + self.TEXT_INPUT_SIZE, 0, rl.Color(255, 255, 255, 128)) rl.end_scissor_mode() # draw gradient on left side to indicate more text - if text_size.x > text_rect.width: - rl.draw_rectangle_gradient_h(int(text_rect.x), int(text_rect.y), 80, int(text_rect.height), + if text_size.x > text_field_rect.width: + rl.draw_rectangle_gradient_h(int(text_field_rect.x), int(text_field_rect.y), 80, int(text_field_rect.height), rl.BLACK, rl.BLANK) # draw cursor if text: blink_alpha = (math.sin(rl.get_time() * 6) + 1) / 2 - cursor_x = min(text_x + text_size.x + 3, text_rect.x + text_rect.width) - rl.draw_rectangle_rounded(rl.Rectangle(int(cursor_x), int(text_rect.y), 4, int(text_size.y)), + cursor_x = min(text_x + text_size.x + 3, text_field_rect.x + text_field_rect.width) + rl.draw_rectangle_rounded(rl.Rectangle(int(cursor_x), int(text_field_rect.y), 4, int(text_size.y)), 1, 4, rl.Color(255, 255, 255, int(255 * blink_alpha))) # draw backspace icon with nice fade @@ -255,7 +247,6 @@ class BigInputDialog(BigDialogBase): # draw debugging rect bounds if DEBUG: rl.draw_rectangle_lines_ex(text_field_rect, 1, rl.Color(100, 100, 100, 255)) - rl.draw_rectangle_lines_ex(text_rect, 1, rl.Color(0, 255, 0, 255)) rl.draw_rectangle_lines_ex(self._top_right_button_rect, 1, rl.Color(0, 255, 0, 255)) rl.draw_rectangle_lines_ex(self._top_left_button_rect, 1, rl.Color(0, 255, 0, 255)) From aac90dd11b449996307b863e1ef0564cfdc771fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Tue, 3 Feb 2026 13:59:45 -0800 Subject: [PATCH 811/910] Bump tg (#37069) bump tg --- tinygrad_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tinygrad_repo b/tinygrad_repo index 774a454bb..2f55005ad 160000 --- a/tinygrad_repo +++ b/tinygrad_repo @@ -1 +1 @@ -Subproject commit 774a454bb5e6d0fe3756a8add9302c0a3d592bd9 +Subproject commit 2f55005ad93c777cca69b20dddc28c7f02f0eb01 From 54cf8d6a5ecb59714c4ac8991e53fa85269b31a1 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 3 Feb 2026 15:55:05 -0800 Subject: [PATCH 812/910] four keyboard: fix keys lagging behind parent widget (#37073) * fix keys lagging behind * use parent rect * use parent rect * cmt --- system/ui/widgets/mici_keyboard.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/system/ui/widgets/mici_keyboard.py b/system/ui/widgets/mici_keyboard.py index 7fc384780..6d2e08e05 100644 --- a/system/ui/widgets/mici_keyboard.py +++ b/system/ui/widgets/mici_keyboard.py @@ -53,20 +53,23 @@ class Key(Widget): self.original_position = rl.Vector2(0, 0) def set_position(self, x: float, y: float, smooth: bool = True): - # TODO: swipe up from NavWidget has the keys lag behind other elements a bit + # Smooth keys within parent rect + base_y = self._parent_rect.y if self._parent_rect else 0.0 + local_y = y - base_y + if not self._position_initialized: self._x_filter.x = x - self._y_filter.x = y + self._y_filter.x = local_y # keep track of original position so dragging around feels consistent. also move touch area down a bit self.original_position = rl.Vector2(x, y + KEY_TOUCH_AREA_OFFSET) self._position_initialized = True if not smooth: self._x_filter.x = x - self._y_filter.x = y + self._y_filter.x = local_y self._rect.x = self._x_filter.update(x) - self._rect.y = self._y_filter.update(y) + self._rect.y = base_y + self._y_filter.update(local_y) def set_alpha(self, alpha: float): self._alpha_filter.update(alpha) @@ -367,6 +370,7 @@ class MiciKeyboard(Widget): key.set_font_size(font_size) # TODO: I like the push amount, so we should clip the pos inside the keyboard rect + key.set_parent_rect(self._rect) key.set_position(key_x, key_y) def _render(self, _): From ee7601ae9db36708281fa42c35b5c9e31b74a888 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Tue, 3 Feb 2026 15:55:13 -0800 Subject: [PATCH 813/910] long planner: Min(stopping) is also important (#37074) Min(stopping) is also important --- selfdrive/controls/lib/longitudinal_planner.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index ad84ecf24..c5c03eba1 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -152,10 +152,11 @@ class LongitudinalPlanner: output_a_target_e2e = sm['modelV2'].action.desiredAcceleration output_should_stop_e2e = sm['modelV2'].action.shouldStop - if (output_a_target_e2e < output_a_target_mpc) and sm['selfdriveState'].experimentalMode: - output_a_target = output_a_target_e2e - self.output_should_stop = output_should_stop_e2e - self.mpc.source = SOURCES[3] + if sm['selfdriveState'].experimentalMode: + output_a_target = min(output_a_target_e2e, output_a_target_mpc) + self.output_should_stop = output_should_stop_e2e or output_should_stop_mpc + if output_a_target < output_a_target_mpc: + self.mpc.source = SOURCES[3] else: output_a_target = output_a_target_mpc self.output_should_stop = output_should_stop_mpc From 5b6436a90cf6902b8aaa71c2b6f3d7164d8ae391 Mon Sep 17 00:00:00 2001 From: YassineYousfi Date: Tue, 3 Feb 2026 19:14:02 -0800 Subject: [PATCH 814/910] CD210 model (#37050) a27b3122-733e-4a65-938b-acfebebbe5e8/100 --- selfdrive/modeld/models/driving_policy.onnx | 4 ++-- selfdrive/modeld/models/driving_vision.onnx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/selfdrive/modeld/models/driving_policy.onnx b/selfdrive/modeld/models/driving_policy.onnx index 92c81954d..611ae9fe8 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:1edea5bb56f876db4cec97c150799513f6a59373f3ad152d55e4dcaab1b809e3 -size 13926324 +oid sha256:78477124cbf3ffe30fa951ebada8410b43c4242c6054584d656f1d329b067e15 +size 14060847 diff --git a/selfdrive/modeld/models/driving_vision.onnx b/selfdrive/modeld/models/driving_vision.onnx index 76c96670a..6c9fc4c84 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:1dc66bc06f250b577653ccbeaa2c6521b3d46749f601d0a1a366419e929ca438 -size 46271942 +oid sha256:ee29ee5bce84d1ce23e9ff381280de9b4e4d96d2934cd751740354884e112c66 +size 46877473 From f309be90383da69768774162424a1a2dfbb4dfba Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 4 Feb 2026 00:15:08 -0500 Subject: [PATCH 815/910] DEC: refactor mode conditions with Experimental Mode --- selfdrive/controls/lib/longitudinal_planner.py | 2 +- .../selfdrive/controls/lib/longitudinal_planner.py | 12 ++++-------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index f5bdd3710..cabaa5529 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -160,7 +160,7 @@ class LongitudinalPlanner(LongitudinalPlannerSP): output_a_target_e2e = sm['modelV2'].action.desiredAcceleration output_should_stop_e2e = sm['modelV2'].action.shouldStop - if sm['selfdriveState'].experimentalMode: + if self.is_e2e(sm): output_a_target = min(output_a_target_e2e, output_a_target_mpc) self.output_should_stop = output_should_stop_e2e or output_should_stop_mpc if output_a_target < output_a_target_mpc: diff --git a/sunnypilot/selfdrive/controls/lib/longitudinal_planner.py b/sunnypilot/selfdrive/controls/lib/longitudinal_planner.py index 68b50c3e1..6efda4585 100644 --- a/sunnypilot/selfdrive/controls/lib/longitudinal_planner.py +++ b/sunnypilot/selfdrive/controls/lib/longitudinal_planner.py @@ -36,16 +36,12 @@ class LongitudinalPlannerSP: self.output_v_target = 0. self.output_a_target = 0. - @property - def mlsim(self) -> bool: - # If we don't have a generation set, we assume it's default model. Which as of today are mlsim. - return bool(self.generation is None or self.generation >= 11) - - def get_mpc_mode(self) -> str | None: + def is_e2e(self, sm: messaging.SubMaster) -> bool: + experimental_mode = sm['selfdriveState'].experimentalMode if not self.dec.active(): - return None + return experimental_mode - return self.dec.mode() + return experimental_mode and self.dec.mode() == "blended" def update_targets(self, sm: messaging.SubMaster, v_ego: float, a_ego: float, v_cruise: float) -> tuple[float, float]: CS = sm['carState'] From 54c177c4421d82fd1eaa51a3e364d9d4c1dffe9a Mon Sep 17 00:00:00 2001 From: infiniteCable2 Date: Wed, 4 Feb 2026 19:29:30 +0100 Subject: [PATCH 816/910] bump --- opendbc_repo | 2 +- panda | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/opendbc_repo b/opendbc_repo index f2aefa716..2c672df49 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit f2aefa716be4829499c5b3164ef9962469960475 +Subproject commit 2c672df49a6aa9a1d6744f4c32f0c22a470b62d6 diff --git a/panda b/panda index e07abd4ea..4d9ed8dcd 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit e07abd4ea8008957e5d31b70cf65ecdf788da56c +Subproject commit 4d9ed8dcd7c435a7ff8a4952fd34059c415e445c From e20e505ca251d212e75c5b76a573c7417661e573 Mon Sep 17 00:00:00 2001 From: infiniteCable2 Date: Wed, 4 Feb 2026 19:40:38 +0100 Subject: [PATCH 817/910] fix icon --- selfdrive/ui/mici/layouts/settings/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/mici/layouts/settings/settings.py b/selfdrive/ui/mici/layouts/settings/settings.py index b26af50b3..cbff51cd9 100644 --- a/selfdrive/ui/mici/layouts/settings/settings.py +++ b/selfdrive/ui/mici/layouts/settings/settings.py @@ -40,7 +40,7 @@ class SettingsLayout(NavWidget): toggles_btn = BigButton("toggles", "", "icons_mici/settings.png") toggles_btn.set_click_callback(lambda: self._set_current_panel(PanelType.TOGGLES)) - ictoggles_btn = BigButton("ictoggles", "", "icons_mici/settings/toggles_icon.png") + ictoggles_btn = BigButton("ictoggles", "", "icons_mici/settings.png") ictoggles_btn.set_click_callback(lambda: self._set_current_panel(PanelType.ICTOGGLES)) network_btn = BigButton("network", "", "icons_mici/settings/network/wifi_strength_full.png", icon_size=(76, 56)) network_btn.set_click_callback(lambda: self._set_current_panel(PanelType.NETWORK)) From 5e35feb3aba8b2f8c7f8d03c1a5949555bfde097 Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Wed, 4 Feb 2026 13:25:09 -0600 Subject: [PATCH 818/910] clips: allow mici UI (now default) (#37070) fix: make big false by default and remove assertion --- tools/clip/run.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tools/clip/run.py b/tools/clip/run.py index 5fb693f30..f2b871be6 100755 --- a/tools/clip/run.py +++ b/tools/clip/run.py @@ -40,7 +40,7 @@ def parse_args(): parser.add_argument("-f", "--file-size", type=float, default=9.0, help="Target file size in MB") parser.add_argument("-x", "--speed", type=int, default=1, help="Speed multiplier") parser.add_argument("--demo", action="store_true", help="Use demo route with default timing") - parser.add_argument("--big", action="store_true", default=True, help="Use big UI (2160x1080)") + parser.add_argument("--big", action="store_true", help="Use big UI (2160x1080)") parser.add_argument("--qcam", action="store_true", help="Use qcamera instead of fcamera") parser.add_argument("--windowed", action="store_true", help="Show window") parser.add_argument("--no-metadata", action="store_true", help="Disable metadata overlay") @@ -329,7 +329,6 @@ def clip(route: Route, output: str, start: int, end: int, headless: bool = True, def main(): logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s\t%(message)s") args = parse_args() - assert args.big, "Clips doesn't support mici UI yet. TODO: make it work" setup_env(args.output, big=args.big, speed=args.speed, target_mb=args.file_size, duration=args.end - args.start) clip(Route(args.route, data_dir=args.data_dir), args.output, args.start, args.end, not args.windowed, From 8879d481e55bc026cf2750aaee984866bd5a4f6c Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 4 Feb 2026 16:04:49 -0800 Subject: [PATCH 819/910] comma four: new keyboard enter button (#37072) * works * enter dis * clean up * clean up * no debug * useless * poadding --- .../icons_mici/settings/keyboard/confirm.png | 3 --- .../icons_mici/settings/keyboard/enter.png | 3 +++ .../settings/keyboard/enter_disabled.png | 3 +++ selfdrive/ui/mici/widgets/dialog.py | 19 +++++++++++-------- 4 files changed, 17 insertions(+), 11 deletions(-) delete mode 100644 selfdrive/assets/icons_mici/settings/keyboard/confirm.png create mode 100644 selfdrive/assets/icons_mici/settings/keyboard/enter.png create mode 100644 selfdrive/assets/icons_mici/settings/keyboard/enter_disabled.png diff --git a/selfdrive/assets/icons_mici/settings/keyboard/confirm.png b/selfdrive/assets/icons_mici/settings/keyboard/confirm.png deleted file mode 100644 index 98ca5c61d..000000000 --- a/selfdrive/assets/icons_mici/settings/keyboard/confirm.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:43b64365a42d7bf772d567b8867a6ced4ec0175bb88b6acaa3a5345f19ca696e -size 1268 diff --git a/selfdrive/assets/icons_mici/settings/keyboard/enter.png b/selfdrive/assets/icons_mici/settings/keyboard/enter.png new file mode 100644 index 000000000..0b7fc95c5 --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/keyboard/enter.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3dd956d5ccfce01a01bea74ef59c9e73dfca406a5ff9ac62417203afa6027fba +size 5620 diff --git a/selfdrive/assets/icons_mici/settings/keyboard/enter_disabled.png b/selfdrive/assets/icons_mici/settings/keyboard/enter_disabled.png new file mode 100644 index 000000000..251d5d8d1 --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/keyboard/enter_disabled.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1dd1c2308872729d58adab390030ae9c987dc7908f0c39391651ea2b6cb620c5 +size 2445 diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index b23abe608..49d73f7b0 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -151,7 +151,8 @@ class BigInputDialog(BigDialogBase): self._backspace_img = gui_app.texture("icons_mici/settings/keyboard/backspace.png", 42, 36) self._backspace_img_alpha = FirstOrderFilter(0, 0.05, 1 / gui_app.target_fps) - self._enter_img = gui_app.texture("icons_mici/settings/keyboard/confirm.png", 42, 36) + self._enter_img = gui_app.texture("icons_mici/settings/keyboard/enter.png", 76, 62) + self._enter_disabled_img = gui_app.texture("icons_mici/settings/keyboard/enter_disabled.png", 76, 62) self._enter_img_alpha = FirstOrderFilter(0, 0.05, 1 / gui_app.target_fps) # rects for top buttons @@ -186,9 +187,9 @@ class BigInputDialog(BigDialogBase): text_size = measure_text_cached(gui_app.font(FontWeight.ROMAN), text + candidate_char or self._hint_label.text, self.TEXT_INPUT_SIZE) bg_block_margin = 5 - text_x = PADDING * 2 + self._enter_img.width + bg_block_margin + text_x = PADDING / 2 + self._enter_img.width + PADDING text_field_rect = rl.Rectangle(text_x, int(self._rect.y + PADDING) - bg_block_margin, - int(self._rect.width - text_x - PADDING * 2 - self._enter_img.width) - bg_block_margin * 2, + int(self._rect.width - text_x * 2), int(text_size.y)) # draw text input @@ -224,7 +225,7 @@ class BigInputDialog(BigDialogBase): self._backspace_img_alpha.update(255 * bool(text)) if self._backspace_img_alpha.x > 1: color = rl.Color(255, 255, 255, int(self._backspace_img_alpha.x)) - rl.draw_texture(self._backspace_img, int(self._rect.width - self._enter_img.width - 15), int(text_field_rect.y), color) + rl.draw_texture(self._backspace_img, int(self._rect.width - self._backspace_img.width - 27), int(self._rect.y + 14), color) if not text and self._hint_label.text and not candidate_char: # draw description if no text entered yet and not drawing candidate char @@ -236,10 +237,12 @@ class BigInputDialog(BigDialogBase): self._top_right_button_rect = rl.Rectangle(text_field_rect.x + text_field_rect.width, self._rect.y, self._rect.width - (text_field_rect.x + text_field_rect.width), self._top_left_button_rect.height) - self._enter_img_alpha.update(255 if (len(text) >= self._minimum_length) else 255 * 0.35) - if self._enter_img_alpha.x > 1: - color = rl.Color(255, 255, 255, int(self._enter_img_alpha.x)) - rl.draw_texture(self._enter_img, int(self._rect.x + 15), int(text_field_rect.y), color) + # draw enter button + self._enter_img_alpha.update(255 if len(text) >= self._minimum_length else 0) + color = rl.Color(255, 255, 255, int(self._enter_img_alpha.x)) + rl.draw_texture(self._enter_img, int(self._rect.x + PADDING / 2), int(self._rect.y), color) + color = rl.Color(255, 255, 255, 255 - int(self._enter_img_alpha.x)) + rl.draw_texture(self._enter_disabled_img, int(self._rect.x + PADDING / 2), int(self._rect.y), color) # keyboard goes over everything self._keyboard.render(self._rect) From 3ea474dd5877300987841cf3f4df296a1a34f18a Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 4 Feb 2026 19:31:44 -0500 Subject: [PATCH 820/910] tools: fix Python version comparison using normalized semantic version format (#37075) --- tools/op.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/op.sh b/tools/op.sh index 8b5062ad9..8c41926e0 100755 --- a/tools/op.sh +++ b/tools/op.sh @@ -161,9 +161,9 @@ function op_check_python() { loge "ERROR_PYTHON_NOT_FOUND" return 1 else - LB=$(echo $REQUIRED_PYTHON_VERSION | tr -d -c '[0-9,]' | cut -d ',' -f1) - UB=$(echo $REQUIRED_PYTHON_VERSION | tr -d -c '[0-9,]' | cut -d ',' -f2) - VERSION=$(echo $INSTALLED_PYTHON_VERSION | grep -o '[0-9]\+\.[0-9]\+' | tr -d -c '[0-9]') + LB=$(echo $REQUIRED_PYTHON_VERSION | tr -d '",' | awk '{ split($4, v, "."); printf "%d%02d%02d", v[1], v[2], v[3] }') + UB=$(echo $REQUIRED_PYTHON_VERSION | tr -d '",' | awk '{ split($6, v, "."); printf "%d%02d%02d", v[1], v[2], v[3] }') + VERSION=$(echo $INSTALLED_PYTHON_VERSION | awk '{ split($2, v, "."); printf "%d%02d%02d", v[1], v[2], v[3] }') if [[ $VERSION -ge LB && $VERSION -lt UB ]]; then echo -e " ↳ [${GREEN}✔${NC}] $INSTALLED_PYTHON_VERSION detected." else From 2230933d4be2c3db83e7737208065aba79b961a9 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 4 Feb 2026 16:38:24 -0800 Subject: [PATCH 821/910] Back to tethering BigButton (#37082) Back to tethering big button --- selfdrive/assets/icons_mici/tethering_short.png | 3 --- selfdrive/ui/mici/layouts/settings/network/__init__.py | 5 ++--- 2 files changed, 2 insertions(+), 6 deletions(-) delete mode 100644 selfdrive/assets/icons_mici/tethering_short.png diff --git a/selfdrive/assets/icons_mici/tethering_short.png b/selfdrive/assets/icons_mici/tethering_short.png deleted file mode 100644 index f97fed95d..000000000 --- a/selfdrive/assets/icons_mici/tethering_short.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fce940a3cbd2e9530e8efdde90794013a272919b2f3ea482bc06535c795640e7 -size 2176 diff --git a/selfdrive/ui/mici/layouts/settings/network/__init__.py b/selfdrive/ui/mici/layouts/settings/network/__init__.py index 0d5e52783..d3ca11ce3 100644 --- a/selfdrive/ui/mici/layouts/settings/network/__init__.py +++ b/selfdrive/ui/mici/layouts/settings/network/__init__.py @@ -4,7 +4,7 @@ from collections.abc import Callable from openpilot.system.ui.widgets.scroller import Scroller from openpilot.selfdrive.ui.mici.layouts.settings.network.wifi_ui import WifiUIMici -from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigMultiToggle, BigParamControl, BigCircleToggle +from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigMultiToggle, BigParamControl, BigToggle from openpilot.selfdrive.ui.mici.widgets.dialog import BigInputDialog from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.selfdrive.ui.lib.prime_state import PrimeType @@ -39,8 +39,7 @@ class NetworkLayoutMici(NavWidget): self._network_metered_btn.set_enabled(False) self._wifi_manager.set_tethering_active(checked) - self._tethering_toggle_btn = BigCircleToggle("icons_mici/tethering_short.png", toggle_callback=tethering_toggle_callback, - icon_size=(82, 82), icon_offset=(0, 12)) + self._tethering_toggle_btn = BigToggle("enable tethering", "", toggle_callback=tethering_toggle_callback) def tethering_password_callback(password: str): if password: From 944c23f369ad660ee8f3d0ab260370ee6922f087 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 4 Feb 2026 17:06:11 -0800 Subject: [PATCH 822/910] Stock LKAS: remove permanent alert (#37083) rm perm --- selfdrive/selfdrived/events.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/selfdrive/selfdrived/events.py b/selfdrive/selfdrived/events.py index 0e37a959c..6fb96b6e5 100755 --- a/selfdrive/selfdrived/events.py +++ b/selfdrive/selfdrived/events.py @@ -478,11 +478,6 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = { }, EventName.stockLkas: { - ET.PERMANENT: Alert( - "Stock LKAS: Lane Departure Detected", - "", - AlertStatus.userPrompt, AlertSize.small, - Priority.LOW, VisualAlert.ldw, AudibleAlert.prompt, 3.), ET.NO_ENTRY: NoEntryAlert("Stock LKAS: Lane Departure Detected"), }, From 1979a6d8e848b2946a7db1b8a866f6eaa45f2463 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 4 Feb 2026 19:30:20 -0800 Subject: [PATCH 823/910] BigButton: remove unused scrolling (#37085) * BigButton: remove unused scrolling * clean up --- selfdrive/ui/mici/widgets/button.py | 39 ----------------------------- 1 file changed, 39 deletions(-) diff --git a/selfdrive/ui/mici/widgets/button.py b/selfdrive/ui/mici/widgets/button.py index 0b252c21a..172a156c3 100644 --- a/selfdrive/ui/mici/widgets/button.py +++ b/selfdrive/ui/mici/widgets/button.py @@ -5,7 +5,6 @@ from collections.abc import Callable from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.label import MiciLabel from openpilot.system.ui.widgets.scroller import DO_ZOOM -from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos from openpilot.common.filter_simple import BounceFilter @@ -129,12 +128,6 @@ class BigButton(Widget): self._load_images() - # internal state - self._scroll_offset = 0 # in pixels - self._needs_scroll = measure_text_cached(self._label_font, text, self._get_label_font_size()).x + 25 > self._rect.width - self._scroll_timer = 0 - self._scroll_state = ScrollState.PRE_SCROLL - def set_icon(self, icon: Union[str, rl.Texture]): self._txt_icon = gui_app.texture(icon, *self._icon_size) if isinstance(icon, str) and len(icon) else icon @@ -178,38 +171,6 @@ class BigButton(Widget): def get_text(self): return self.text - def _update_state(self): - # hold on text for a bit, scroll, hold again, reset - if self._needs_scroll: - """`dt` should be seconds since last frame (rl.get_frame_time()).""" - # TODO: this comment is generated by GPT, prob wrong and misused - dt = rl.get_frame_time() - - self._scroll_timer += dt - if self._scroll_state == ScrollState.PRE_SCROLL: - if self._scroll_timer < 0.5: - return - self._scroll_state = ScrollState.SCROLLING - self._scroll_timer = 0 - - elif self._scroll_state == ScrollState.SCROLLING: - self._scroll_offset -= SCROLLING_SPEED_PX_S * dt - # reset when text has completely left the button + 50 px gap - # TODO: use global constant for 30+30 px gap - # TODO: add std Widget padding option integrated into the self._rect - full_len = measure_text_cached(self._label_font, self.text, self._get_label_font_size()).x + 30 + 30 - if self._scroll_offset < (self._rect.width - full_len): - self._scroll_state = ScrollState.POST_SCROLL - self._scroll_timer = 0 - - elif self._scroll_state == ScrollState.POST_SCROLL: - # wait for a bit before starting to scroll again - if self._scroll_timer < 0.75: - return - self._scroll_state = ScrollState.PRE_SCROLL - self._scroll_timer = 0 - self._scroll_offset = 0 - def _render(self, _): # draw _txt_default_bg txt_bg = self._txt_default_bg From 177a1a3c8b022c85e92a1bc68e32a136ee551735 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 4 Feb 2026 22:25:28 -0800 Subject: [PATCH 824/910] BigButton: use UnifiedLabel (#37086) * BigButton: remove unused scrolling * start * debug * stash * actually removing the hardcoded size for multioption fixes it * back * cursor does sub label * clean up * more * more * try this for now * nick suggest * clean up * more * more --- selfdrive/ui/mici/layouts/settings/device.py | 4 +- selfdrive/ui/mici/widgets/button.py | 42 +++++++++----------- 2 files changed, 20 insertions(+), 26 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/device.py b/selfdrive/ui/mici/layouts/settings/device.py index 30ea90f3d..c12a92482 100644 --- a/selfdrive/ui/mici/layouts/settings/device.py +++ b/selfdrive/ui/mici/layouts/settings/device.py @@ -322,11 +322,11 @@ class DeviceLayoutMici(NavWidget): regulatory_btn = BigButton("regulatory info", "", "icons_mici/settings/device/info.png") regulatory_btn.set_click_callback(self._on_regulatory) - driver_cam_btn = BigButton("driver camera preview", "", "icons_mici/settings/device/cameras.png") + driver_cam_btn = BigButton("driver\ncamera preview", "", "icons_mici/settings/device/cameras.png") driver_cam_btn.set_click_callback(self._show_driver_camera) driver_cam_btn.set_enabled(lambda: ui_state.is_offroad()) - review_training_guide_btn = BigButton("review training guide", "", "icons_mici/settings/device/info.png") + review_training_guide_btn = BigButton("review\nntraining guide", "", "icons_mici/settings/device/info.png") review_training_guide_btn.set_click_callback(self._on_review_training_guide) review_training_guide_btn.set_enabled(lambda: ui_state.is_offroad()) diff --git a/selfdrive/ui/mici/widgets/button.py b/selfdrive/ui/mici/widgets/button.py index 172a156c3..30a66376e 100644 --- a/selfdrive/ui/mici/widgets/button.py +++ b/selfdrive/ui/mici/widgets/button.py @@ -3,7 +3,7 @@ from typing import Union from enum import Enum from collections.abc import Callable from openpilot.system.ui.widgets import Widget -from openpilot.system.ui.widgets.label import MiciLabel +from openpilot.system.ui.widgets.label import UnifiedLabel from openpilot.system.ui.widgets.scroller import DO_ZOOM from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos from openpilot.common.filter_simple import BounceFilter @@ -119,12 +119,10 @@ class BigButton(Widget): self._label_font = gui_app.font(FontWeight.DISPLAY) self._value_font = gui_app.font(FontWeight.ROMAN) - self._label = MiciLabel(text, font_size=self._get_label_font_size(), width=int(self._rect.width - LABEL_HORIZONTAL_PADDING * 2), - font_weight=FontWeight.DISPLAY, color=LABEL_COLOR, - alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM, wrap_text=True) - self._sub_label = MiciLabel(value, font_size=COMPLICATION_SIZE, width=int(self._rect.width - LABEL_HORIZONTAL_PADDING * 2), - font_weight=FontWeight.ROMAN, color=COMPLICATION_GREY, - alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM, wrap_text=True) + self._label = UnifiedLabel(text, font_size=self._get_label_font_size(), font_weight=FontWeight.DISPLAY, + text_color=LABEL_COLOR, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM, line_height=0.9) + self._sub_label = UnifiedLabel(value, font_size=COMPLICATION_SIZE, font_weight=FontWeight.ROMAN, + text_color=COMPLICATION_GREY, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM) self._load_images() @@ -142,15 +140,16 @@ class BigButton(Widget): self._txt_disabled_bg = gui_app.texture("icons_mici/buttons/button_rectangle_disabled.png", 402, 180) self._txt_hover_bg = gui_app.texture("icons_mici/buttons/button_rectangle_hover.png", 402, 180) + def _width_hint(self) -> int: + return int(self._rect.width - LABEL_HORIZONTAL_PADDING * 2) + def _get_label_font_size(self): if len(self.text) < 12: font_size = 64 elif len(self.text) < 17: font_size = 48 - elif len(self.text) < 20: - font_size = 42 else: - font_size = 36 + font_size = 42 if self.value: font_size -= 20 @@ -189,14 +188,16 @@ class BigButton(Widget): ly = btn_y + self._rect.height - 33 # - 40# - self._get_label_font_size() / 2 if self.value: - self._sub_label.set_position(lx, ly) - ly -= self._sub_label.font_size + 9 - self._sub_label.render() + sub_label_height = self._sub_label.get_content_height(self._width_hint()) + sub_label_rect = rl.Rectangle(lx, ly - sub_label_height, self._width_hint(), sub_label_height) + self._sub_label.render(sub_label_rect) + ly -= sub_label_height label_color = LABEL_COLOR if self.enabled else rl.Color(255, 255, 255, int(255 * 0.35)) self._label.set_color(label_color) - self._label.set_position(lx, ly) - self._label.render() + label_height = self._label.get_content_height(self._width_hint()) + label_rect = rl.Rectangle(lx, ly - label_height, self._width_hint(), label_height) + self._label.render(label_rect) # ICON ------------------------------------------------------------------- if self._txt_icon: @@ -219,8 +220,6 @@ class BigToggle(BigButton): self._checked = initial_state self._toggle_callback = toggle_callback - self._label.set_font_size(48) - def _load_images(self): super()._load_images() self._txt_enabled_toggle = gui_app.texture("icons_mici/buttons/toggle_pill_enabled.png", 84, 66) @@ -258,15 +257,10 @@ class BigMultiToggle(BigToggle): self._options = options self._select_callback = select_callback - self._label.set_width(int(self._rect.width - LABEL_HORIZONTAL_PADDING * 2 - self._txt_enabled_toggle.width)) - # TODO: why isn't this automatic? - self._label.set_font_size(self._get_label_font_size()) - self.set_value(self._options[0]) - def _get_label_font_size(self): - font_size = super()._get_label_font_size() - return font_size - 6 + def _width_hint(self) -> int: + return int(self._rect.width - LABEL_HORIZONTAL_PADDING * 2 - self._txt_enabled_toggle.width) def _handle_mouse_release(self, mouse_pos: MousePos): super()._handle_mouse_release(mouse_pos) From fc6afd5ab8bac4b19f0ca0564015884a9b90489b Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 4 Feb 2026 22:58:16 -0800 Subject: [PATCH 825/910] Network menu improvements (#37077) * try this * that's handy * todo, not sure what happens here * set_text * normalize * scroll wifi * clean up * more * better check --- .../mici/layouts/settings/network/__init__.py | 35 +++++++++++++------ .../mici/layouts/settings/network/wifi_ui.py | 6 +++- selfdrive/ui/mici/widgets/button.py | 11 ++++-- 3 files changed, 38 insertions(+), 14 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/network/__init__.py b/selfdrive/ui/mici/layouts/settings/network/__init__.py index d3ca11ce3..fb1d56a1f 100644 --- a/selfdrive/ui/mici/layouts/settings/network/__init__.py +++ b/selfdrive/ui/mici/layouts/settings/network/__init__.py @@ -3,7 +3,7 @@ from enum import IntEnum from collections.abc import Callable from openpilot.system.ui.widgets.scroller import Scroller -from openpilot.selfdrive.ui.mici.layouts.settings.network.wifi_ui import WifiUIMici +from openpilot.selfdrive.ui.mici.layouts.settings.network.wifi_ui import WifiUIMici, WifiIcon, normalize_ssid from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigMultiToggle, BigParamControl, BigToggle from openpilot.selfdrive.ui.mici.widgets.dialog import BigInputDialog from openpilot.selfdrive.ui.ui_state import ui_state @@ -55,9 +55,6 @@ class NetworkLayoutMici(NavWidget): self._tethering_password_btn = BigButton("tethering password", "", txt_tethering) self._tethering_password_btn.set_click_callback(tethering_password_clicked) - # ******** IP Address ******** - self._ip_address_btn = BigButton("IP Address", "Not connected") - # ******** Network Metered ******** def network_metered_callback(value: str): self._network_metered_btn.set_enabled(False) @@ -73,8 +70,13 @@ class NetworkLayoutMici(NavWidget): self._network_metered_btn = BigMultiToggle("network usage", ["default", "metered", "unmetered"], select_callback=network_metered_callback) self._network_metered_btn.set_enabled(False) - wifi_button = BigButton("wi-fi") - wifi_button.set_click_callback(lambda: self._switch_to_panel(NetworkPanelType.WIFI)) + self._wifi_slash_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_slash.png", 64, 56) + self._wifi_low_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_low.png", 64, 47) + self._wifi_medium_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_medium.png", 64, 47) + self._wifi_full_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_full.png", 64, 47) + + self._wifi_button = BigButton("wi-fi", "not connected", self._wifi_slash_txt, scroll=True) + self._wifi_button.set_click_callback(lambda: self._switch_to_panel(NetworkPanelType.WIFI)) # ******** Advanced settings ******** # ******** Roaming toggle ******** @@ -89,7 +91,7 @@ class NetworkLayoutMici(NavWidget): # Main scroller ---------------------------------- self._scroller = Scroller([ - wifi_button, + self._wifi_button, self._network_metered_btn, self._tethering_toggle_btn, self._tethering_password_btn, @@ -98,7 +100,6 @@ class NetworkLayoutMici(NavWidget): self._apn_btn, self._cellular_metered_btn, # */ - self._ip_address_btn, ], snap_items=False) # Set initial config @@ -157,8 +158,22 @@ class NetworkLayoutMici(NavWidget): self._network_metered_btn.set_enabled(lambda: not tethering_active and bool(self._wifi_manager.ipv4_address)) self._tethering_toggle_btn.set_checked(tethering_active) - # Update IP address - self._ip_address_btn.set_value(self._wifi_manager.ipv4_address or "Not connected") + # Update wi-fi button with ssid and ip address + # TODO: make sure we handle hidden ssids + connected_network = next((network for network in networks if network.is_connected), None) + self._wifi_button.set_text(normalize_ssid(connected_network.ssid) if connected_network is not None else "wi-fi") + self._wifi_button.set_value(self._wifi_manager.ipv4_address or "not connected") + if connected_network is not None: + strength = WifiIcon.get_strength_icon_idx(connected_network.strength) + if strength == 2: + strength_icon = self._wifi_full_txt + elif strength == 1: + strength_icon = self._wifi_medium_txt + else: + strength_icon = self._wifi_low_txt + self._wifi_button.set_icon(strength_icon) + else: + self._wifi_button.set_icon(self._wifi_slash_txt) # Update network metered self._network_metered_btn.set_value( diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index 23b89438d..7791f18cf 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -50,12 +50,16 @@ class WifiIcon(Widget): def set_scale(self, scale: float): self._scale = scale + @staticmethod + def get_strength_icon_idx(strength: int) -> int: + return round(strength / 100 * 2) + def _render(self, _): if self._network is None: return # Determine which wifi strength icon to use - strength = round(self._network.strength / 100 * 2) + strength = self.get_strength_icon_idx(self._network.strength) if strength == 2: strength_icon = self._wifi_full_txt elif strength == 1: diff --git a/selfdrive/ui/mici/widgets/button.py b/selfdrive/ui/mici/widgets/button.py index 30a66376e..a5f7ee686 100644 --- a/selfdrive/ui/mici/widgets/button.py +++ b/selfdrive/ui/mici/widgets/button.py @@ -104,12 +104,14 @@ class BigCircleToggle(BigCircleButton): class BigButton(Widget): """A lightweight stand-in for the Qt BigButton, drawn & updated each frame.""" - def __init__(self, text: str, value: str = "", icon: Union[str, rl.Texture] = "", icon_size: tuple[int, int] = (64, 64)): + def __init__(self, text: str, value: str = "", icon: Union[str, rl.Texture] = "", icon_size: tuple[int, int] = (64, 64), + scroll: bool = False): super().__init__() self.set_rect(rl.Rectangle(0, 0, 402, 180)) self.text = text self.value = value self._icon_size = icon_size + self._scroll = scroll self.set_icon(icon) self._scale_filter = BounceFilter(1.0, 0.1, 1 / gui_app.target_fps) @@ -120,7 +122,8 @@ class BigButton(Widget): self._value_font = gui_app.font(FontWeight.ROMAN) self._label = UnifiedLabel(text, font_size=self._get_label_font_size(), font_weight=FontWeight.DISPLAY, - text_color=LABEL_COLOR, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM, line_height=0.9) + text_color=LABEL_COLOR, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM, scroll=scroll, + line_height=0.9) self._sub_label = UnifiedLabel(value, font_size=COMPLICATION_SIZE, font_weight=FontWeight.ROMAN, text_color=COMPLICATION_GREY, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM) @@ -141,7 +144,9 @@ class BigButton(Widget): self._txt_hover_bg = gui_app.texture("icons_mici/buttons/button_rectangle_hover.png", 402, 180) def _width_hint(self) -> int: - return int(self._rect.width - LABEL_HORIZONTAL_PADDING * 2) + # Single line if scrolling, so hide behind icon if exists + icon_size = self._icon_size[0] if self._txt_icon and self._scroll and self.value else 0 + return int(self._rect.width - LABEL_HORIZONTAL_PADDING * 2 - icon_size) def _get_label_font_size(self): if len(self.text) < 12: From 39dcc883301f47c4061e3206cba6d5d50ab72778 Mon Sep 17 00:00:00 2001 From: ugtthis <142481257+ugtthis@users.noreply.github.com> Date: Thu, 5 Feb 2026 01:03:01 -0600 Subject: [PATCH 826/910] UI: only show `onroad_fade.png` when engaged (#37051) * only show when engaged * retrigger CI * fade animation 0.1 * nl --------- Co-authored-by: Shane Smiskol --- selfdrive/ui/mici/onroad/augmented_road_view.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/selfdrive/ui/mici/onroad/augmented_road_view.py b/selfdrive/ui/mici/onroad/augmented_road_view.py index 4e00a3aaf..69bcca401 100644 --- a/selfdrive/ui/mici/onroad/augmented_road_view.py +++ b/selfdrive/ui/mici/onroad/augmented_road_view.py @@ -14,7 +14,7 @@ from openpilot.selfdrive.ui.mici.onroad.cameraview import CameraView from openpilot.system.ui.lib.application import FontWeight, gui_app, MousePos, MouseEvent from openpilot.system.ui.widgets.label import UnifiedLabel from openpilot.system.ui.widgets import Widget -from openpilot.common.filter_simple import BounceFilter +from openpilot.common.filter_simple import BounceFilter, FirstOrderFilter from openpilot.common.transformations.camera import DEVICE_CAMERAS, DeviceCameraConfig, view_frame_from_device_frame from openpilot.common.transformations.orientation import rot_from_euler from enum import IntEnum @@ -165,6 +165,7 @@ class AugmentedRoadView(CameraView): alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE) self._fade_texture = gui_app.texture("icons_mici/onroad/onroad_fade.png") + self._fade_alpha_filter = FirstOrderFilter(0, 0.1, 1 / gui_app.target_fps) # debug self._pm = messaging.PubMaster(['uiDebug']) @@ -217,8 +218,11 @@ class AugmentedRoadView(CameraView): # Draw all UI overlays self._model_renderer.render(self._content_rect) - # Fade out bottom of overlays for looks - rl.draw_texture_ex(self._fade_texture, rl.Vector2(self._content_rect.x, self._content_rect.y), 0.0, 1.0, rl.WHITE) + # Fade out bottom of overlays for looks (only when engaged) + fade_alpha = self._fade_alpha_filter.update(ui_state.status != UIStatus.DISENGAGED) + if fade_alpha > 1e-2: + rl.draw_texture_ex(self._fade_texture, rl.Vector2(self._content_rect.x, self._content_rect.y), 0.0, 1.0, + rl.Color(255, 255, 255, int(255 * fade_alpha))) alert_to_render, not_animating_out = self._alert_renderer.will_render() From a5c973be896bb87469e70250ea18af3783e09434 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 4 Feb 2026 23:52:59 -0800 Subject: [PATCH 827/910] NavBar: fix no show animation (#37090) * 1.5 not enough time on small screen * ohhhh * clean up --- system/ui/widgets/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/system/ui/widgets/__init__.py b/system/ui/widgets/__init__.py index 42d9a910a..5d474e8ae 100644 --- a/system/ui/widgets/__init__.py +++ b/system/ui/widgets/__init__.py @@ -195,7 +195,7 @@ NAV_BAR_WIDTH = 205 NAV_BAR_HEIGHT = 8 DISMISS_PUSH_OFFSET = 50 + NAV_BAR_MARGIN + NAV_BAR_HEIGHT # px extra to push down when dismissing -DISMISS_TIME_SECONDS = 1.5 +DISMISS_TIME_SECONDS = 2.0 class NavBar(Widget): @@ -371,13 +371,13 @@ class NavWidget(Widget, abc.ABC): else: self._nav_bar_y_filter.update(NAV_BAR_MARGIN) - self._nav_bar.set_position(bar_x, round(self._nav_bar_y_filter.x)) - self._nav_bar.render() - # draw black above widget when dismissing if self._rect.y > 0: rl.draw_rectangle(int(self._rect.x), 0, int(self._rect.width), int(self._rect.y), rl.BLACK) + self._nav_bar.set_position(bar_x, round(self._nav_bar_y_filter.x)) + self._nav_bar.render() + return ret def show_event(self): From 4d65c52e6d16ac1604d5d21d6f8e691391046bd2 Mon Sep 17 00:00:00 2001 From: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Thu, 5 Feb 2026 05:35:57 -0800 Subject: [PATCH 828/910] modeld_v2: refactor abstract class to support off-policy models (#1672) * modeld_v2: refactor abstract class to support off-policy models. * whoops * bump --- cereal/custom.capnp | 1 + release/ci/model_generator.py | 4 ++- sunnypilot/modeld_v2/SConscript | 4 +-- sunnypilot/modeld_v2/install_models_pc.py | 2 +- sunnypilot/models/fetcher.py | 2 +- .../models/runners/tinygrad/model_types.py | 16 ++++++++++ .../runners/tinygrad/tinygrad_runner.py | 32 +++++++++++++++---- 7 files changed, 50 insertions(+), 11 deletions(-) diff --git a/cereal/custom.capnp b/cereal/custom.capnp index 5c0a004fa..53986262e 100644 --- a/cereal/custom.capnp +++ b/cereal/custom.capnp @@ -153,6 +153,7 @@ struct ModelManagerSP @0xaedffd8f31e7b55d { navigation @1; vision @2; policy @3; + offPolicy @4; } } diff --git a/release/ci/model_generator.py b/release/ci/model_generator.py index ee41343be..96352254b 100755 --- a/release/ci/model_generator.py +++ b/release/ci/model_generator.py @@ -68,8 +68,10 @@ def generate_metadata(model_path: Path, output_dir: Path, short_name: str): metadata_file = metadata_file.rename(output_path / f"{base}_{short_name.lower()}_metadata.pkl") # Build the metadata structure + model_type = "offPolicy" if "off_policy" in base else base.split("_")[-1] + model_metadata = { - "type": base.split("_")[-1] if "dmonitoring" not in base else "dmonitoring", + "type": model_type, "artifact": { "file_name": tinygrad_file.name, "download_uri": { diff --git a/sunnypilot/modeld_v2/SConscript b/sunnypilot/modeld_v2/SConscript index 48b9c75ef..94033846b 100644 --- a/sunnypilot/modeld_v2/SConscript +++ b/sunnypilot/modeld_v2/SConscript @@ -39,7 +39,7 @@ if PC: model_dir = Dir("models").abspath cmd = f'python3 {Dir("#sunnypilot/modeld_v2").abspath}/install_models_pc.py {model_dir}' - for model_name in ['supercombo', 'driving_vision', 'driving_policy']: + for model_name in ['supercombo', 'driving_vision', 'driving_off_policy', 'driving_policy']: if File(f"models/{model_name}.onnx").exists(): inputs.append(File(f"models/{model_name}.onnx")) inputs.append(File(f"models/{model_name}_tinygrad.pkl")) @@ -57,7 +57,7 @@ def tg_compile(flags, model_name): ) # Compile small models -for model_name in ['supercombo', 'driving_vision', 'driving_policy']: +for model_name in ['supercombo', 'driving_vision', 'driving_off_policy', 'driving_policy']: if File(f"models/{model_name}.onnx").exists(): flags = { 'larch64': 'DEV=QCOM', diff --git a/sunnypilot/modeld_v2/install_models_pc.py b/sunnypilot/modeld_v2/install_models_pc.py index 3f964dc28..a378d90b1 100755 --- a/sunnypilot/modeld_v2/install_models_pc.py +++ b/sunnypilot/modeld_v2/install_models_pc.py @@ -44,7 +44,7 @@ def generate_metadata_pkl(model_path, output_path): def install_models(model_dir): model_dir = Path(model_dir) - models = ["driving_policy", "driving_vision"] + models = ["driving_off_policy", "driving_policy", "driving_vision"] found_models = [] for model in models: diff --git a/sunnypilot/models/fetcher.py b/sunnypilot/models/fetcher.py index a917d6cbb..1d8da083a 100644 --- a/sunnypilot/models/fetcher.py +++ b/sunnypilot/models/fetcher.py @@ -116,7 +116,7 @@ class ModelCache: class ModelFetcher: """Handles fetching and caching of model data from remote source""" - MODEL_URL = "https://raw.githubusercontent.com/sunnypilot/sunnypilot-docs/refs/heads/gh-pages/docs/driving_models_v10.json" + MODEL_URL = "https://raw.githubusercontent.com/sunnypilot/sunnypilot-docs/refs/heads/gh-pages/docs/driving_models_v11.json" def __init__(self, params: Params): self.params = params diff --git a/sunnypilot/models/runners/tinygrad/model_types.py b/sunnypilot/models/runners/tinygrad/model_types.py index ba388aed9..11f096582 100644 --- a/sunnypilot/models/runners/tinygrad/model_types.py +++ b/sunnypilot/models/runners/tinygrad/model_types.py @@ -13,6 +13,22 @@ SEND_RAW_PRED = os.getenv('SEND_RAW_PRED') CUSTOM_MODEL_PATH = Paths.model_root() +class OffPolicyTinygrad(ModularRunner, ABC): + """ + A TinygradRunner specialized for off-policy models. + + Uses a SplitParser to handle outputs specific to the off-policy part of a split model setup. + """ + def __init__(self): + self._off_policy_parser = SplitParser() + self.parser_method_dict[ModelType.offPolicy] = self._parse_off_policy_outputs + + def _parse_off_policy_outputs(self, model_outputs: np.ndarray) -> NumpyDict: + """Parses off-policy model outputs using SplitParser.""" + result: NumpyDict = self._off_policy_parser.parse_policy_outputs(self._slice_outputs(model_outputs)) + return result + + class PolicyTinygrad(ModularRunner, ABC): """ A TinygradRunner specialized for policy-only models. diff --git a/sunnypilot/models/runners/tinygrad/tinygrad_runner.py b/sunnypilot/models/runners/tinygrad/tinygrad_runner.py index 2800179fb..7b48e3086 100644 --- a/sunnypilot/models/runners/tinygrad/tinygrad_runner.py +++ b/sunnypilot/models/runners/tinygrad/tinygrad_runner.py @@ -4,7 +4,7 @@ import numpy as np from openpilot.sunnypilot.modeld_v2.runners.tinygrad_helpers import qcom_tensor_from_opencl_address from openpilot.sunnypilot.models.runners.constants import CLMemDict, FrameDict, NumpyDict, ModelType, ShapeDict, CUSTOM_MODEL_PATH, SliceDict from openpilot.sunnypilot.models.runners.model_runner import ModelRunner -from openpilot.sunnypilot.models.runners.tinygrad.model_types import PolicyTinygrad, VisionTinygrad, SupercomboTinygrad +from openpilot.sunnypilot.models.runners.tinygrad.model_types import PolicyTinygrad, VisionTinygrad, SupercomboTinygrad, OffPolicyTinygrad from openpilot.system.hardware import TICI from openpilot.sunnypilot.models.split_model_constants import SplitModelConstants from openpilot.sunnypilot.modeld_v2.constants import ModelConstants @@ -12,7 +12,7 @@ from openpilot.sunnypilot.modeld_v2.constants import ModelConstants from tinygrad.tensor import Tensor -class TinygradRunner(ModelRunner, SupercomboTinygrad, PolicyTinygrad, VisionTinygrad): +class TinygradRunner(ModelRunner, SupercomboTinygrad, PolicyTinygrad, VisionTinygrad, OffPolicyTinygrad): """ A ModelRunner implementation for executing Tinygrad models. @@ -27,6 +27,7 @@ class TinygradRunner(ModelRunner, SupercomboTinygrad, PolicyTinygrad, VisionTiny SupercomboTinygrad.__init__(self) PolicyTinygrad.__init__(self) VisionTinygrad.__init__(self) + OffPolicyTinygrad.__init__(self) self._constants = ModelConstants self._model_data = self.models.get(model_type) if not self._model_data or not self._model_data.model: @@ -106,13 +107,20 @@ class TinygradSplitRunner(ModelRunner): self.is_20hz_3d = True self.vision_runner = TinygradRunner(ModelType.vision) self.policy_runner = TinygradRunner(ModelType.policy) + self.off_policy_runner = TinygradRunner(ModelType.offPolicy) if self.models.get(ModelType.offPolicy) else None self._constants = SplitModelConstants def _run_model(self) -> NumpyDict: """Runs both vision and policy models and merges their parsed outputs.""" policy_output = self.policy_runner.run_model() vision_output = self.vision_runner.run_model() - return {**policy_output, **vision_output} # Combine results + outputs = {**policy_output, **vision_output} + + if self.off_policy_runner: + off_policy_output = self.off_policy_runner.run_model() + outputs.update(off_policy_output) + + return outputs @property def vision_input_names(self) -> list[str]: @@ -122,12 +130,18 @@ class TinygradSplitRunner(ModelRunner): @property def input_shapes(self) -> ShapeDict: """Returns the combined input shapes from both vision and policy models.""" - return {**self.policy_runner.input_shapes, **self.vision_runner.input_shapes} + shapes = {**self.policy_runner.input_shapes, **self.vision_runner.input_shapes} + if self.off_policy_runner: + shapes.update(self.off_policy_runner.input_shapes) + return shapes @property def output_slices(self) -> SliceDict: """Returns the combined output slices from both vision and policy models.""" - return {**self.policy_runner.output_slices, **self.vision_runner.output_slices} + slices = {**self.policy_runner.output_slices, **self.vision_runner.output_slices} + if self.off_policy_runner: + slices.update(self.off_policy_runner.output_slices) + return slices def prepare_inputs(self, imgs_cl: CLMemDict, numpy_inputs: NumpyDict, frames: FrameDict) -> dict: """Prepares inputs for both vision and policy models.""" @@ -135,5 +149,11 @@ class TinygradSplitRunner(ModelRunner): self.policy_runner.prepare_policy_inputs(numpy_inputs) # Vision inputs depend on imgs_cl and frames self.vision_runner.prepare_vision_inputs(imgs_cl, frames) + inputs = {**self.policy_runner.inputs, **self.vision_runner.inputs} + + if self.off_policy_runner: + self.off_policy_runner.prepare_policy_inputs(numpy_inputs) + inputs.update(self.off_policy_runner.inputs) + # Return combined inputs (though they are stored within respective runners) - return {**self.policy_runner.inputs, **self.vision_runner.inputs} + return inputs From 8aed5a1a89a8d699cf96c35eacca6432bad95d20 Mon Sep 17 00:00:00 2001 From: royjr Date: Thu, 5 Feb 2026 12:28:01 -0500 Subject: [PATCH 829/910] translations: remove arabic (#37087) * remove arabic * more --- selfdrive/assets/fonts/process.py | 2 +- selfdrive/ui/translations/app_ar.po | 1218 ---------------------- selfdrive/ui/translations/languages.json | 1 - system/ui/lib/multilang.py | 1 - 4 files changed, 1 insertion(+), 1221 deletions(-) delete mode 100644 selfdrive/ui/translations/app_ar.po diff --git a/selfdrive/assets/fonts/process.py b/selfdrive/assets/fonts/process.py index ddc8b3a86..30ccf9ca5 100755 --- a/selfdrive/assets/fonts/process.py +++ b/selfdrive/assets/fonts/process.py @@ -11,7 +11,7 @@ LANGUAGES_FILE = TRANSLATIONS_DIR / "languages.json" GLYPH_PADDING = 6 EXTRA_CHARS = "–‑✓×°§•X⚙✕◀▶✔⌫⇧␣○●↳çêüñ–‑✓×°§•€£¥" -UNIFONT_LANGUAGES = {"ar", "th", "zh-CHT", "zh-CHS", "ko", "ja"} +UNIFONT_LANGUAGES = {"th", "zh-CHT", "zh-CHS", "ko", "ja"} def _languages(): diff --git a/selfdrive/ui/translations/app_ar.po b/selfdrive/ui/translations/app_ar.po deleted file mode 100644 index 608389fc0..000000000 --- a/selfdrive/ui/translations/app_ar.po +++ /dev/null @@ -1,1218 +0,0 @@ -# Arabic translations for PACKAGE package. -# Copyright (C) 2025 THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# Automatically generated, 2025. -# -msgid "" -msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-23 00:50-0700\n" -"PO-Revision-Date: 2025-10-22 16:32-0700\n" -"Last-Translator: Automatically generated\n" -"Language-Team: none\n" -"Language: ar\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=6; plural=n==0?0:n==1?1:n==2?2:(n%100>=3 && " -"n%100<=10)?3:(n%100>=11 && n%100<=99)?4:5;\n" - -#: selfdrive/ui/layouts/settings/device.py:160 -#, python-format -msgid " Steering torque response calibration is complete." -msgstr " اكتملت معايرة استجابة عزم التوجيه." - -#: selfdrive/ui/layouts/settings/device.py:158 -#, python-format -msgid " Steering torque response calibration is {}% complete." -msgstr " اكتملت معايرة استجابة عزم التوجيه بنسبة {}٪." - -#: selfdrive/ui/layouts/settings/device.py:133 -#, python-format -msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." -msgstr " جهازك موجه بمقدار {:.1f}° {} و {:.1f}° {}." - -#: selfdrive/ui/layouts/sidebar.py:43 -msgid "--" -msgstr "--" - -#: selfdrive/ui/widgets/prime.py:47 -#, python-format -msgid "1 year of drive storage" -msgstr "سنة واحدة من تخزين القيادة" - -#: selfdrive/ui/widgets/prime.py:47 -#, python-format -msgid "24/7 LTE connectivity" -msgstr "اتصال LTE على مدار الساعة" - -#: selfdrive/ui/layouts/sidebar.py:46 -msgid "2G" -msgstr "2G" - -#: selfdrive/ui/layouts/sidebar.py:47 -msgid "3G" -msgstr "3G" - -#: selfdrive/ui/layouts/sidebar.py:49 -msgid "5G" -msgstr "5G" - -#: selfdrive/ui/layouts/settings/developer.py:23 -msgid "" -"WARNING: openpilot longitudinal control is in alpha for this car and will " -"disable Automatic Emergency Braking (AEB).

On this car, openpilot " -"defaults to the car's built-in ACC instead of openpilot's longitudinal " -"control. Enable this to switch to openpilot longitudinal control. Enabling " -"Experimental mode is recommended when enabling openpilot longitudinal " -"control alpha. Changing this setting will restart openpilot if the car is " -"powered on." -msgstr "" -"تحذير: التحكم الطولي لـ openpilot في مرحلة ألفا لهذه السيارة وسيُعطّل نظام " -"الكبح التلقائي في حالات الطوارئ (AEB).

في هذه السيارة، يعتمد " -"openpilot افتراضياً على نظام ACC المدمج بدلاً من التحكم الطولي لـ openpilot. " -"فعّل هذا الخيار للتبديل إلى التحكم الطولي لـ openpilot. يُنصح بتمكين وضع " -"التجربة عند تفعيل نسخة ألفا من التحكم الطولي. تغيير هذا الإعداد سيعيد تشغيل " -"openpilot إذا كانت السيارة قيد التشغيل." - -#: selfdrive/ui/layouts/settings/device.py:148 -#, python-format -msgid "

Steering lag calibration is complete." -msgstr "

اكتملت معايرة تأخر التوجيه." - -#: selfdrive/ui/layouts/settings/device.py:146 -#, python-format -msgid "

Steering lag calibration is {}% complete." -msgstr "

اكتملت معايرة تأخر التوجيه بنسبة {}٪." - -#: selfdrive/ui/layouts/settings/firehose.py:138 -#, python-format -msgid "ACTIVE" -msgstr "نشط" - -#: selfdrive/ui/layouts/settings/developer.py:15 -msgid "" -"ADB (Android Debug Bridge) allows connecting to your device over USB or over " -"the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." -msgstr "" -"يتيح ADB (Android Debug Bridge) الاتصال بجهازك عبر USB أو عبر الشبكة. راجع " -"https://docs.comma.ai/how-to/connect-to-comma لمزيد من المعلومات." - -#: selfdrive/ui/widgets/ssh_key.py:30 -msgid "ADD" -msgstr "إضافة" - -#: system/ui/widgets/network.py:139 -#, python-format -msgid "APN Setting" -msgstr "إعداد APN" - -#: selfdrive/ui/widgets/offroad_alerts.py:109 -#, python-format -msgid "Acknowledge Excessive Actuation" -msgstr "تأكيد التشغيل المفرط" - -#: system/ui/widgets/network.py:74 system/ui/widgets/network.py:95 -#, python-format -msgid "Advanced" -msgstr "متقدم" - -#: selfdrive/ui/layouts/settings/toggles.py:98 -#, python-format -msgid "Aggressive" -msgstr "عدواني" - -#: selfdrive/ui/layouts/onboarding.py:116 -#, python-format -msgid "Agree" -msgstr "موافقة" - -#: selfdrive/ui/layouts/settings/toggles.py:70 -#, python-format -msgid "Always-On Driver Monitoring" -msgstr "مراقبة السائق دائماً" - -#: selfdrive/ui/layouts/settings/toggles.py:186 -#, python-format -msgid "" -"An alpha version of openpilot longitudinal control can be tested, along with " -"Experimental mode, on non-release branches." -msgstr "" -"يمكن اختبار نسخة ألفا من التحكم الطولي لـ openpilot، مع وضع التجربة، على " -"الفروع غير الإصدارية." - -#: selfdrive/ui/layouts/settings/device.py:187 -#, python-format -msgid "Are you sure you want to power off?" -msgstr "هل أنت متأكد أنك تريد إيقاف التشغيل؟" - -#: selfdrive/ui/layouts/settings/device.py:175 -#, python-format -msgid "Are you sure you want to reboot?" -msgstr "هل أنت متأكد أنك تريد إعادة التشغيل؟" - -#: selfdrive/ui/layouts/settings/device.py:119 -#, python-format -msgid "Are you sure you want to reset calibration?" -msgstr "هل أنت متأكد أنك تريد إعادة ضبط المعايرة؟" - -#: selfdrive/ui/layouts/settings/software.py:163 -#, python-format -msgid "Are you sure you want to uninstall?" -msgstr "هل أنت متأكد أنك تريد إلغاء التثبيت؟" - -#: system/ui/widgets/network.py:99 selfdrive/ui/layouts/onboarding.py:147 -#, python-format -msgid "Back" -msgstr "رجوع" - -#: selfdrive/ui/widgets/prime.py:38 -#, python-format -msgid "Become a comma prime member at connect.comma.ai" -msgstr "انضم إلى comma prime عبر connect.comma.ai" - -#: selfdrive/ui/widgets/pairing_dialog.py:130 -#, python-format -msgid "Bookmark connect.comma.ai to your home screen to use it like an app" -msgstr "ثبّت connect.comma.ai على شاشتك الرئيسية لاستخدامه كتطبيق" - -#: selfdrive/ui/layouts/settings/device.py:68 -#, python-format -msgid "CHANGE" -msgstr "تغيير" - -#: selfdrive/ui/layouts/settings/software.py:50 -#: selfdrive/ui/layouts/settings/software.py:107 -#: selfdrive/ui/layouts/settings/software.py:118 -#: selfdrive/ui/layouts/settings/software.py:147 -#, python-format -msgid "CHECK" -msgstr "تحقق" - -#: selfdrive/ui/widgets/exp_mode_button.py:50 -#, python-format -msgid "CHILL MODE ON" -msgstr "وضع الهدوء مُفعل" - -#: system/ui/widgets/network.py:155 selfdrive/ui/layouts/sidebar.py:73 -#: selfdrive/ui/layouts/sidebar.py:134 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:138 -#, python-format -msgid "CONNECT" -msgstr "CONNECT" - -#: system/ui/widgets/network.py:369 -#, python-format -msgid "CONNECTING..." -msgstr "CONNECTING..." - -#: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35 -#: system/ui/widgets/keyboard.py:81 system/ui/widgets/network.py:318 -#, python-format -msgid "Cancel" -msgstr "إلغاء" - -#: system/ui/widgets/network.py:134 -#, python-format -msgid "Cellular Metered" -msgstr "خلوي بتعرفة محدودة" - -#: selfdrive/ui/layouts/settings/device.py:68 -#, python-format -msgid "Change Language" -msgstr "تغيير اللغة" - -#: selfdrive/ui/layouts/settings/toggles.py:125 -#, python-format -msgid "Changing this setting will restart openpilot if the car is powered on." -msgstr "" -"سيؤدي تغيير هذا الإعداد إلى إعادة تشغيل openpilot إذا كانت السيارة قيد " -"التشغيل." - -#: selfdrive/ui/widgets/pairing_dialog.py:129 -#, python-format -msgid "Click \"add new device\" and scan the QR code on the right" -msgstr "اضغط \"إضافة جهاز جديد\" ثم امسح رمز QR على اليمين" - -#: selfdrive/ui/widgets/offroad_alerts.py:104 -#, python-format -msgid "Close" -msgstr "إغلاق" - -#: selfdrive/ui/layouts/settings/software.py:49 -#, python-format -msgid "Current Version" -msgstr "الإصدار الحالي" - -#: selfdrive/ui/layouts/settings/software.py:110 -#, python-format -msgid "DOWNLOAD" -msgstr "تنزيل" - -#: selfdrive/ui/layouts/onboarding.py:115 -#, python-format -msgid "Decline" -msgstr "رفض" - -#: selfdrive/ui/layouts/onboarding.py:148 -#, python-format -msgid "Decline, uninstall openpilot" -msgstr "رفض، وإلغاء تثبيت openpilot" - -#: selfdrive/ui/layouts/settings/settings.py:67 -msgid "Developer" -msgstr "المطور" - -#: selfdrive/ui/layouts/settings/settings.py:62 -msgid "Device" -msgstr "الجهاز" - -#: selfdrive/ui/layouts/settings/toggles.py:58 -#, python-format -msgid "Disengage on Accelerator Pedal" -msgstr "فصل عند الضغط على دواسة الوقود" - -#: selfdrive/ui/layouts/settings/device.py:184 -#, python-format -msgid "Disengage to Power Off" -msgstr "افصل لإيقاف التشغيل" - -#: selfdrive/ui/layouts/settings/device.py:172 -#, python-format -msgid "Disengage to Reboot" -msgstr "افصل لإعادة التشغيل" - -#: selfdrive/ui/layouts/settings/device.py:103 -#, python-format -msgid "Disengage to Reset Calibration" -msgstr "افصل لإعادة ضبط المعايرة" - -#: selfdrive/ui/layouts/settings/toggles.py:32 -msgid "Display speed in km/h instead of mph." -msgstr "عرض السرعة بالكيلومتر/ساعة بدلاً من الميل/ساعة." - -#: selfdrive/ui/layouts/settings/device.py:59 -#, python-format -msgid "Dongle ID" -msgstr "معرّف الدونجل" - -#: selfdrive/ui/layouts/settings/software.py:50 -#, python-format -msgid "Download" -msgstr "تنزيل" - -#: selfdrive/ui/layouts/settings/device.py:62 -#, python-format -msgid "Driver Camera" -msgstr "كاميرا السائق" - -#: selfdrive/ui/layouts/settings/toggles.py:96 -#, python-format -msgid "Driving Personality" -msgstr "شخصية القيادة" - -#: system/ui/widgets/network.py:123 system/ui/widgets/network.py:139 -#, python-format -msgid "EDIT" -msgstr "تعديل" - -#: selfdrive/ui/layouts/sidebar.py:138 -msgid "ERROR" -msgstr "خطأ" - -#: selfdrive/ui/layouts/sidebar.py:45 -msgid "ETH" -msgstr "ETH" - -#: selfdrive/ui/widgets/exp_mode_button.py:50 -#, python-format -msgid "EXPERIMENTAL MODE ON" -msgstr "وضع التجربة مُفعل" - -#: selfdrive/ui/layouts/settings/developer.py:166 -#: selfdrive/ui/layouts/settings/toggles.py:228 -#, python-format -msgid "Enable" -msgstr "تمكين" - -#: selfdrive/ui/layouts/settings/developer.py:39 -#, python-format -msgid "Enable ADB" -msgstr "تمكين ADB" - -#: selfdrive/ui/layouts/settings/toggles.py:64 -#, python-format -msgid "Enable Lane Departure Warnings" -msgstr "تمكين تحذيرات مغادرة المسار" - -#: system/ui/widgets/network.py:129 -#, python-format -msgid "Enable Roaming" -msgstr "تمكين التجوال" - -#: selfdrive/ui/layouts/settings/developer.py:48 -#, python-format -msgid "Enable SSH" -msgstr "تمكين SSH" - -#: system/ui/widgets/network.py:120 -#, python-format -msgid "Enable Tethering" -msgstr "تمكين الربط" - -#: selfdrive/ui/layouts/settings/toggles.py:30 -msgid "Enable driver monitoring even when openpilot is not engaged." -msgstr "تمكين مراقبة السائق حتى عندما لا يكون openpilot مُشغلاً." - -#: selfdrive/ui/layouts/settings/toggles.py:46 -#, python-format -msgid "Enable openpilot" -msgstr "تمكين openpilot" - -#: selfdrive/ui/layouts/settings/toggles.py:189 -#, python-format -msgid "" -"Enable the openpilot longitudinal control (alpha) toggle to allow " -"Experimental mode." -msgstr "فعّل تبديل التحكم الطولي (ألفا) لـ openpilot للسماح بوضع التجربة." - -#: system/ui/widgets/network.py:204 -#, python-format -msgid "Enter APN" -msgstr "أدخل APN" - -#: system/ui/widgets/network.py:241 -#, python-format -msgid "Enter SSID" -msgstr "أدخل SSID" - -#: system/ui/widgets/network.py:254 -#, python-format -msgid "Enter new tethering password" -msgstr "أدخل كلمة مرور الربط الجديدة" - -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 -#, python-format -msgid "Enter password" -msgstr "أدخل كلمة المرور" - -#: selfdrive/ui/widgets/ssh_key.py:89 -#, python-format -msgid "Enter your GitHub username" -msgstr "أدخل اسم مستخدم GitHub الخاص بك" - -#: system/ui/widgets/list_view.py:123 system/ui/widgets/list_view.py:160 -#, python-format -msgid "Error" -msgstr "خطأ" - -#: selfdrive/ui/layouts/settings/toggles.py:52 -#, python-format -msgid "Experimental Mode" -msgstr "وضع التجربة" - -#: selfdrive/ui/layouts/settings/toggles.py:181 -#, python-format -msgid "" -"Experimental mode is currently unavailable on this car since the car's stock " -"ACC is used for longitudinal control." -msgstr "" -"وضع التجربة غير متاح حالياً في هذه السيارة لأن نظام ACC الأصلي يُستخدم للتحكم " -"الطولي." - -#: system/ui/widgets/network.py:373 -#, python-format -msgid "FORGETTING..." -msgstr "جارٍ النسيان..." - -#: selfdrive/ui/widgets/setup.py:44 -#, python-format -msgid "Finish Setup" -msgstr "إنهاء الإعداد" - -#: selfdrive/ui/layouts/settings/settings.py:66 -msgid "Firehose" -msgstr "Firehose" - -#: selfdrive/ui/layouts/settings/firehose.py:18 -msgid "Firehose Mode" -msgstr "وضع Firehose" - -#: selfdrive/ui/layouts/settings/firehose.py:25 -msgid "" -"For maximum effectiveness, bring your device inside and connect to a good " -"USB-C adapter and Wi-Fi weekly.\n" -"\n" -"Firehose Mode can also work while you're driving if connected to a hotspot " -"or unlimited SIM card.\n" -"\n" -"\n" -"Frequently Asked Questions\n" -"\n" -"Does it matter how or where I drive? Nope, just drive as you normally " -"would.\n" -"\n" -"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a " -"subset of your segments.\n" -"\n" -"What's a good USB-C adapter? Any fast phone or laptop charger should be " -"fine.\n" -"\n" -"Does it matter which software I run? Yes, only upstream openpilot (and " -"particular forks) are able to be used for training." -msgstr "" -"لأقصى فعالية، أحضر جهازك إلى الداخل واتصل بمحوّل USB‑C جيد وبشبكة Wi‑Fi " -"أسبوعياً.\n" -"\n" -"يمكن أن يعمل وضع Firehose أيضاً أثناء القيادة إذا كنت متصلاً بنقطة اتصال أو " -"بشريحة غير محدودة.\n" -"\n" -"\n" -"الأسئلة الشائعة\n" -"\n" -"هل يهم كيف أو أين أقود؟ لا، قد بقدر المعتاد.\n" -"\n" -"هل يتم سحب كل مقاطعي في وضع Firehose؟ لا، نقوم بسحب مجموعة فرعية من " -"المقاطع.\n" -"\n" -"ما هو محول USB‑C الجيد؟ أي شاحن هاتف أو حاسب محمول سريع سيكون مناسباً.\n" -"\n" -"هل يهم أي برنامج أشغّل؟ نعم، فقط openpilot الأصلي (وبعض التفرعات المحددة) " -"يمكن استخدامه للتدريب." - -#: system/ui/widgets/network.py:318 system/ui/widgets/network.py:451 -#, python-format -msgid "Forget" -msgstr "نسيان" - -#: system/ui/widgets/network.py:319 -#, python-format -msgid "Forget Wi-Fi Network \"{}\"?" -msgstr "هل تريد نسيان شبكة Wi‑Fi \"{}\"؟" - -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 -msgid "GOOD" -msgstr "جيد" - -#: selfdrive/ui/widgets/pairing_dialog.py:128 -#, python-format -msgid "Go to https://connect.comma.ai on your phone" -msgstr "اذهب إلى https://connect.comma.ai على هاتفك" - -#: selfdrive/ui/layouts/sidebar.py:129 -msgid "HIGH" -msgstr "مرتفع" - -#: system/ui/widgets/network.py:155 -#, python-format -msgid "Hidden Network" -msgstr "شبكة مخفية" - -#: selfdrive/ui/layouts/settings/firehose.py:140 -#, python-format -msgid "INACTIVE: connect to an unmetered network" -msgstr "غير نشط: اتصل بشبكة غير محدودة التعرفة" - -#: selfdrive/ui/layouts/settings/software.py:53 -#: selfdrive/ui/layouts/settings/software.py:136 -#, python-format -msgid "INSTALL" -msgstr "تثبيت" - -#: system/ui/widgets/network.py:150 -#, python-format -msgid "IP Address" -msgstr "عنوان IP" - -#: selfdrive/ui/layouts/settings/software.py:53 -#, python-format -msgid "Install Update" -msgstr "تثبيت التحديث" - -#: selfdrive/ui/layouts/settings/developer.py:56 -#, python-format -msgid "Joystick Debug Mode" -msgstr "وضع تصحيح عصا التحكم" - -#: selfdrive/ui/widgets/ssh_key.py:29 -msgid "LOADING" -msgstr "جارٍ التحميل" - -#: selfdrive/ui/layouts/sidebar.py:48 -msgid "LTE" -msgstr "LTE" - -#: selfdrive/ui/layouts/settings/developer.py:64 -#, python-format -msgid "Longitudinal Maneuver Mode" -msgstr "وضع المناورة الطولية" - -#: selfdrive/ui/onroad/hud_renderer.py:148 -#, python-format -msgid "MAX" -msgstr "أقصى" - -#: selfdrive/ui/widgets/setup.py:75 -#, python-format -msgid "" -"Maximize your training data uploads to improve openpilot's driving models." -msgstr "زد من تحميل بيانات التدريب لتحسين نماذج قيادة openpilot." - -#: selfdrive/ui/layouts/settings/device.py:59 -#: selfdrive/ui/layouts/settings/device.py:60 -#, python-format -msgid "N/A" -msgstr "غير متوفر" - -#: selfdrive/ui/layouts/sidebar.py:142 -msgid "NO" -msgstr "لا" - -#: selfdrive/ui/layouts/settings/settings.py:63 -msgid "Network" -msgstr "الشبكة" - -#: selfdrive/ui/widgets/ssh_key.py:114 -#, python-format -msgid "No SSH keys found" -msgstr "لم يتم العثور على مفاتيح SSH" - -#: selfdrive/ui/widgets/ssh_key.py:126 -#, python-format -msgid "No SSH keys found for user '{}'" -msgstr "لم يتم العثور على مفاتيح SSH للمستخدم '{}'" - -#: selfdrive/ui/widgets/offroad_alerts.py:320 -#, python-format -msgid "No release notes available." -msgstr "لا توجد ملاحظات إصدار متاحة." - -#: selfdrive/ui/layouts/sidebar.py:73 selfdrive/ui/layouts/sidebar.py:134 -msgid "OFFLINE" -msgstr "غير متصل" - -#: system/ui/widgets/html_render.py:263 system/ui/widgets/confirm_dialog.py:93 -#: selfdrive/ui/layouts/sidebar.py:127 -#, python-format -msgid "OK" -msgstr "موافق" - -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:144 -msgid "ONLINE" -msgstr "متصل" - -#: selfdrive/ui/widgets/setup.py:20 -#, python-format -msgid "Open" -msgstr "فتح" - -#: selfdrive/ui/layouts/settings/device.py:48 -#, python-format -msgid "PAIR" -msgstr "إقران" - -#: selfdrive/ui/layouts/sidebar.py:142 -msgid "PANDA" -msgstr "PANDA" - -#: selfdrive/ui/layouts/settings/device.py:62 -#, python-format -msgid "PREVIEW" -msgstr "معاينة" - -#: selfdrive/ui/widgets/prime.py:44 -#, python-format -msgid "PRIME FEATURES:" -msgstr "ميزات PRIME:" - -#: selfdrive/ui/layouts/settings/device.py:48 -#, python-format -msgid "Pair Device" -msgstr "إقران الجهاز" - -#: selfdrive/ui/widgets/setup.py:19 -#, python-format -msgid "Pair device" -msgstr "إقران الجهاز" - -#: selfdrive/ui/widgets/pairing_dialog.py:103 -#, python-format -msgid "Pair your device to your comma account" -msgstr "قم بإقران جهازك بحساب comma" - -#: selfdrive/ui/widgets/setup.py:48 selfdrive/ui/layouts/settings/device.py:24 -#, python-format -msgid "" -"Pair your device with comma connect (connect.comma.ai) and claim your comma " -"prime offer." -msgstr "" -"أقرِن جهازك مع comma connect (connect.comma.ai) واحصل على عرض comma prime." - -#: selfdrive/ui/widgets/setup.py:91 -#, python-format -msgid "Please connect to Wi-Fi to complete initial pairing" -msgstr "يرجى الاتصال بشبكة Wi‑Fi لإكمال الاقتران الأولي" - -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:187 -#, python-format -msgid "Power Off" -msgstr "إيقاف التشغيل" - -#: system/ui/widgets/network.py:144 -#, python-format -msgid "Prevent large data uploads when on a metered Wi-Fi connection" -msgstr "منع رفع البيانات الكبيرة عند الاتصال بشبكة Wi‑Fi محدودة التعرفة" - -#: system/ui/widgets/network.py:135 -#, python-format -msgid "Prevent large data uploads when on a metered cellular connection" -msgstr "منع رفع البيانات الكبيرة عند الاتصال الخلوي محدود التعرفة" - -#: selfdrive/ui/layouts/settings/device.py:25 -msgid "" -"Preview the driver facing camera to ensure that driver monitoring has good " -"visibility. (vehicle must be off)" -msgstr "" -"عاين كاميرا مواجهة السائق للتأكد من أن مراقبة السائق تتم برؤية جيدة. (يجب أن " -"تكون المركبة متوقفة)" - -#: selfdrive/ui/widgets/pairing_dialog.py:161 -#, python-format -msgid "QR Code Error" -msgstr "خطأ في رمز QR" - -#: selfdrive/ui/widgets/ssh_key.py:31 -msgid "REMOVE" -msgstr "إزالة" - -#: selfdrive/ui/layouts/settings/device.py:51 -#, python-format -msgid "RESET" -msgstr "إعادة ضبط" - -#: selfdrive/ui/layouts/settings/device.py:65 -#, python-format -msgid "REVIEW" -msgstr "مراجعة" - -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:175 -#, python-format -msgid "Reboot" -msgstr "إعادة التشغيل" - -#: selfdrive/ui/onroad/alert_renderer.py:66 -#, python-format -msgid "Reboot Device" -msgstr "إعادة تشغيل الجهاز" - -#: selfdrive/ui/widgets/offroad_alerts.py:112 -#, python-format -msgid "Reboot and Update" -msgstr "إعادة التشغيل والتحديث" - -#: selfdrive/ui/layouts/settings/toggles.py:27 -msgid "" -"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)." -msgstr "" -"استقبال تنبيهات للتوجيه للعودة إلى المسار عند انحراف المركبة فوق خط المسار " -"المُكتشف بدون إشارة انعطاف مفعّلة أثناء القيادة فوق 31 ميل/س (50 كم/س)." - -#: selfdrive/ui/layouts/settings/toggles.py:76 -#, python-format -msgid "Record and Upload Driver Camera" -msgstr "تسجيل ورفع فيديو كاميرا السائق" - -#: selfdrive/ui/layouts/settings/toggles.py:82 -#, python-format -msgid "Record and Upload Microphone Audio" -msgstr "تسجيل ورفع صوت الميكروفون" - -#: selfdrive/ui/layouts/settings/toggles.py:33 -msgid "" -"Record and store microphone audio while driving. The audio will be included " -"in the dashcam video in comma connect." -msgstr "" -"تسجيل وتخزين صوت الميكروفون أثناء القيادة. سيُدرج الصوت في فيديو الكاميرا " -"الأمامية في comma connect." - -#: selfdrive/ui/layouts/settings/device.py:67 -#, python-format -msgid "Regulatory" -msgstr "لوائح" - -#: selfdrive/ui/layouts/settings/toggles.py:98 -#, python-format -msgid "Relaxed" -msgstr "مسترخٍ" - -#: selfdrive/ui/widgets/prime.py:47 -#, python-format -msgid "Remote access" -msgstr "وصول عن بُعد" - -#: selfdrive/ui/widgets/prime.py:47 -#, python-format -msgid "Remote snapshots" -msgstr "لقطات عن بُعد" - -#: selfdrive/ui/widgets/ssh_key.py:123 -#, python-format -msgid "Request timed out" -msgstr "انتهت مهلة الطلب" - -#: selfdrive/ui/layouts/settings/device.py:119 -#, python-format -msgid "Reset" -msgstr "إعادة ضبط" - -#: selfdrive/ui/layouts/settings/device.py:51 -#, python-format -msgid "Reset Calibration" -msgstr "إعادة ضبط المعايرة" - -#: selfdrive/ui/layouts/settings/device.py:65 -#, python-format -msgid "Review Training Guide" -msgstr "مراجعة دليل التدريب" - -#: selfdrive/ui/layouts/settings/device.py:27 -msgid "Review the rules, features, and limitations of openpilot" -msgstr "مراجعة قواعد وميزات وحدود openpilot" - -#: selfdrive/ui/layouts/settings/software.py:61 -#, python-format -msgid "SELECT" -msgstr "اختيار" - -#: selfdrive/ui/layouts/settings/developer.py:53 -#, python-format -msgid "SSH Keys" -msgstr "مفاتيح SSH" - -#: system/ui/widgets/network.py:310 -#, python-format -msgid "Scanning Wi-Fi networks..." -msgstr "جارٍ مسح شبكات Wi‑Fi..." - -#: system/ui/widgets/option_dialog.py:36 -#, python-format -msgid "Select" -msgstr "اختيار" - -#: selfdrive/ui/layouts/settings/software.py:183 -#, python-format -msgid "Select a branch" -msgstr "اختر فرعاً" - -#: selfdrive/ui/layouts/settings/device.py:91 -#, python-format -msgid "Select a language" -msgstr "اختر لغة" - -#: selfdrive/ui/layouts/settings/device.py:60 -#, python-format -msgid "Serial" -msgstr "الرقم التسلسلي" - -#: selfdrive/ui/widgets/offroad_alerts.py:106 -#, python-format -msgid "Snooze Update" -msgstr "تأجيل التحديث" - -#: selfdrive/ui/layouts/settings/settings.py:65 -msgid "Software" -msgstr "البرمجيات" - -#: selfdrive/ui/layouts/settings/toggles.py:98 -#, python-format -msgid "Standard" -msgstr "قياسي" - -#: selfdrive/ui/layouts/settings/toggles.py:22 -msgid "" -"Standard is recommended. In aggressive mode, openpilot will follow lead cars " -"closer and be more aggressive with the gas and brake. In relaxed mode " -"openpilot will stay further away from lead cars. On supported cars, you can " -"cycle through these personalities with your steering wheel distance button." -msgstr "" -"يوصى بالوضع القياسي. في الوضع العدواني، سيتبع openpilot السيارات الأمامية عن " -"قرب وسيكون أكثر شدة في الوقود والفرامل. في الوضع المسترخي سيبقى بعيداً أكثر " -"عن السيارات الأمامية. في السيارات المدعومة، يمكنك التنقل بين هذه الشخصيات " -"بزر مسافة المقود." - -#: selfdrive/ui/onroad/alert_renderer.py:59 -#: selfdrive/ui/onroad/alert_renderer.py:65 -#, python-format -msgid "System Unresponsive" -msgstr "النظام لا يستجيب" - -#: selfdrive/ui/onroad/alert_renderer.py:58 -#, python-format -msgid "TAKE CONTROL IMMEDIATELY" -msgstr "تولَّ السيطرة فوراً" - -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 -#: selfdrive/ui/layouts/sidebar.py:127 selfdrive/ui/layouts/sidebar.py:129 -msgid "TEMP" -msgstr "الحرارة" - -#: selfdrive/ui/layouts/settings/software.py:61 -#, python-format -msgid "Target Branch" -msgstr "الفرع المستهدف" - -#: system/ui/widgets/network.py:124 -#, python-format -msgid "Tethering Password" -msgstr "كلمة مرور الربط" - -#: selfdrive/ui/layouts/settings/settings.py:64 -msgid "Toggles" -msgstr "مفاتيح التبديل" - -#: selfdrive/ui/layouts/settings/software.py:72 -#, python-format -msgid "UNINSTALL" -msgstr "إلغاء التثبيت" - -#: selfdrive/ui/layouts/home.py:155 -#, python-format -msgid "UPDATE" -msgstr "تحديث" - -#: selfdrive/ui/layouts/settings/software.py:72 -#: selfdrive/ui/layouts/settings/software.py:163 -#, python-format -msgid "Uninstall" -msgstr "إلغاء التثبيت" - -#: selfdrive/ui/layouts/sidebar.py:117 -msgid "Unknown" -msgstr "غير معروف" - -#: selfdrive/ui/layouts/settings/software.py:48 -#, python-format -msgid "Updates are only downloaded while the car is off." -msgstr "يتم تنزيل التحديثات فقط عندما تكون السيارة متوقفة." - -#: selfdrive/ui/widgets/prime.py:33 -#, python-format -msgid "Upgrade Now" -msgstr "الترقية الآن" - -#: selfdrive/ui/layouts/settings/toggles.py:31 -msgid "" -"Upload data from the driver facing camera and help improve the driver " -"monitoring algorithm." -msgstr "" -"ارفع بيانات من كاميرا مواجهة السائق وساعد في تحسين خوارزمية مراقبة السائق." - -#: selfdrive/ui/layouts/settings/toggles.py:88 -#, python-format -msgid "Use Metric System" -msgstr "استخدام النظام المتري" - -#: selfdrive/ui/layouts/settings/toggles.py:17 -msgid "" -"Use the openpilot system for adaptive cruise control and lane keep driver " -"assistance. Your attention is required at all times to use this feature." -msgstr "" -"استخدم نظام openpilot للتحكم الذكي بالسرعة والمساعدة على البقاء داخل المسار. " -"يتطلب استخدام هذه الميزة انتباهك الكامل في جميع الأوقات." - -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:144 -msgid "VEHICLE" -msgstr "المركبة" - -#: selfdrive/ui/layouts/settings/device.py:67 -#, python-format -msgid "VIEW" -msgstr "عرض" - -#: selfdrive/ui/onroad/alert_renderer.py:52 -#, python-format -msgid "Waiting to start" -msgstr "بانتظار البدء" - -#: selfdrive/ui/layouts/settings/developer.py:19 -msgid "" -"Warning: This grants SSH access to all public keys in your GitHub settings. " -"Never enter a GitHub username other than your own. A comma employee will " -"NEVER ask you to add their GitHub username." -msgstr "" -"تحذير: يمنح هذا وصول SSH إلى جميع المفاتيح العامة في إعدادات GitHub الخاصة " -"بك. لا تُدخل مطلقاً اسم مستخدم GitHub غير اسمك. لن يطلب منك موظف في comma أبداً " -"إضافة اسم مستخدمهم." - -#: selfdrive/ui/layouts/onboarding.py:111 -#, python-format -msgid "Welcome to openpilot" -msgstr "مرحباً بك في openpilot" - -#: selfdrive/ui/layouts/settings/toggles.py:20 -msgid "When enabled, pressing the accelerator pedal will disengage openpilot." -msgstr "عند التمكين، سيؤدي الضغط على دواسة الوقود إلى فصل openpilot." - -#: selfdrive/ui/layouts/sidebar.py:44 -msgid "Wi-Fi" -msgstr "Wi‑Fi" - -#: system/ui/widgets/network.py:144 -#, python-format -msgid "Wi-Fi Network Metered" -msgstr "شبكة Wi‑Fi محدودة التعرفة" - -#: system/ui/widgets/network.py:314 -#, python-format -msgid "Wrong password" -msgstr "كلمة مرور خاطئة" - -#: selfdrive/ui/layouts/onboarding.py:145 -#, python-format -msgid "You must accept the Terms and Conditions in order to use openpilot." -msgstr "يجب عليك قبول الشروط والأحكام لاستخدام openpilot." - -#: selfdrive/ui/layouts/onboarding.py:112 -#, python-format -msgid "" -"You must accept the Terms and Conditions to use openpilot. Read the latest " -"terms at https://comma.ai/terms before continuing." -msgstr "" -"يجب عليك قبول الشروط والأحكام لاستخدام openpilot. اقرأ أحدث الشروط على " -"https://comma.ai/terms قبل المتابعة." - -#: selfdrive/ui/onroad/driver_camera_dialog.py:34 -#, python-format -msgid "camera starting" -msgstr "بدء تشغيل الكاميرا" - -#: selfdrive/ui/widgets/prime.py:63 -#, python-format -msgid "comma prime" -msgstr "comma prime" - -#: system/ui/widgets/network.py:142 -#, python-format -msgid "default" -msgstr "افتراضي" - -#: selfdrive/ui/layouts/settings/device.py:133 -#, python-format -msgid "down" -msgstr "أسفل" - -#: selfdrive/ui/layouts/settings/software.py:106 -#, python-format -msgid "failed to check for update" -msgstr "فشل التحقق من وجود تحديث" - -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 -#, python-format -msgid "for \"{}\"" -msgstr "لـ \"{}\"" - -#: selfdrive/ui/onroad/hud_renderer.py:177 -#, python-format -msgid "km/h" -msgstr "كم/س" - -#: system/ui/widgets/network.py:204 -#, python-format -msgid "leave blank for automatic configuration" -msgstr "اتركه فارغاً للإعداد التلقائي" - -#: selfdrive/ui/layouts/settings/device.py:134 -#, python-format -msgid "left" -msgstr "يسار" - -#: system/ui/widgets/network.py:142 -#, python-format -msgid "metered" -msgstr "محدود التعرفة" - -#: selfdrive/ui/onroad/hud_renderer.py:177 -#, python-format -msgid "mph" -msgstr "ميل/س" - -#: selfdrive/ui/layouts/settings/software.py:20 -#, python-format -msgid "never" -msgstr "أبداً" - -#: selfdrive/ui/layouts/settings/software.py:31 -#, python-format -msgid "now" -msgstr "الآن" - -#: selfdrive/ui/layouts/settings/developer.py:71 -#, python-format -msgid "openpilot Longitudinal Control (Alpha)" -msgstr "التحكم الطولي لـ openpilot (ألفا)" - -#: selfdrive/ui/onroad/alert_renderer.py:51 -#, python-format -msgid "openpilot Unavailable" -msgstr "openpilot غير متاح" - -#: selfdrive/ui/layouts/settings/toggles.py:158 -#, python-format -msgid "" -"openpilot defaults to driving in chill mode. Experimental mode enables alpha-" -"level features that aren't ready for chill mode. Experimental features are " -"listed below:

End-to-End Longitudinal Control


Let the driving " -"model control the gas and brakes. openpilot will drive as it thinks a human " -"would, including stopping for red lights and stop signs. Since the driving " -"model decides the speed to drive, the set speed will only act as an upper " -"bound. This is an alpha quality feature; mistakes should be expected." -"

New Driving Visualization


The driving visualization will " -"transition to the road-facing wide-angle camera at low speeds to better show " -"some turns. The Experimental mode logo will also be shown in the top right " -"corner." -msgstr "" -"يعمل openpilot افتراضياً في وضع الهدوء. يفعّل وضع التجربة ميزات بمستوى ألفا " -"غير الجاهزة لوضع الهدوء. الميزات التجريبية مدرجة أدناه:

التحكم الطولي " -"من طرف لطرف


دع نموذج القيادة يتحكم في الوقود والفرامل. سيقود " -"openpilot كما يظن أن الإنسان سيقود، بما في ذلك التوقف عند الإشارات الحمراء " -"وعلامات التوقف. بما أن نموذج القيادة يقرر السرعة، فإن السرعة المضبوطة تعمل " -"كحد أعلى فقط. هذه ميزة بجودة ألفا؛ يُتوقع حدوث أخطاء.

تصوير قيادة " -"جديد


سينتقل عرض القيادة إلى الكاميرا الواسعة المواجهة للطريق عند " -"السرعات المنخفضة لإظهار بعض المنعطفات بشكل أفضل. كما سيظهر شعار وضع التجربة " -"في الزاوية العلوية اليمنى." - -#: selfdrive/ui/layouts/settings/device.py:165 -#, python-format -msgid "" -"openpilot is continuously calibrating, resetting is rarely required. " -"Resetting calibration will restart openpilot if the car is powered on." -msgstr "" -"يقوم openpilot بالمعايرة بشكل مستمر، ونادراً ما تتطلب إعادة الضبط. ستؤدي " -"إعادة ضبط المعايرة إلى إعادة تشغيل openpilot إذا كانت السيارة قيد التشغيل." - -#: selfdrive/ui/layouts/settings/firehose.py:20 -msgid "" -"openpilot learns to drive by watching humans, like you, drive.\n" -"\n" -"Firehose Mode allows you to maximize your training data uploads to improve " -"openpilot's driving models. More data means bigger models, which means " -"better Experimental Mode." -msgstr "" -"يتعلم openpilot القيادة بمشاهدة البشر، مثلك، يقودون.\n" -"\n" -"يتيح وضع Firehose زيادة تحميل بيانات التدريب لتحسين نماذج قيادة openpilot. " -"المزيد من البيانات يعني نماذج أكبر، مما يعني وضع تجربة أفضل." - -#: selfdrive/ui/layouts/settings/toggles.py:183 -#, python-format -msgid "openpilot longitudinal control may come in a future update." -msgstr "قد يأتي التحكم الطولي لـ openpilot في تحديث مستقبلي." - -#: selfdrive/ui/layouts/settings/device.py:26 -msgid "" -"openpilot requires the device to be mounted within 4° left or right and " -"within 5° up or 9° down." -msgstr "" -"يتطلب openpilot تركيب الجهاز ضمن 4° يساراً أو يميناً وضمن 5° للأعلى أو 9° " -"للأسفل." - -#: selfdrive/ui/layouts/settings/device.py:134 -#, python-format -msgid "right" -msgstr "يمين" - -#: system/ui/widgets/network.py:142 -#, python-format -msgid "unmetered" -msgstr "غير محدود" - -#: selfdrive/ui/layouts/settings/device.py:133 -#, python-format -msgid "up" -msgstr "أعلى" - -#: selfdrive/ui/layouts/settings/software.py:117 -#, python-format -msgid "up to date, last checked never" -msgstr "محدّث، آخر تحقق: أبداً" - -#: selfdrive/ui/layouts/settings/software.py:115 -#, python-format -msgid "up to date, last checked {}" -msgstr "محدّث، آخر تحقق {}" - -#: selfdrive/ui/layouts/settings/software.py:109 -#, python-format -msgid "update available" -msgstr "تحديث متاح" - -#: selfdrive/ui/layouts/home.py:169 -#, python-format -msgid "{} ALERT" -msgid_plural "{} ALERTS" -msgstr[0] "{} تنبيه" -msgstr[1] "{} تنبيه" -msgstr[2] "{} تنبيهان" -msgstr[3] "{} تنبيهات" -msgstr[4] "{} تنبيهات" -msgstr[5] "{} تنبيه" - -#: selfdrive/ui/layouts/settings/software.py:40 -#, python-format -msgid "{} day ago" -msgid_plural "{} days ago" -msgstr[0] "قبل {} يوم" -msgstr[1] "قبل {} يوم" -msgstr[2] "قبل {} يومين" -msgstr[3] "قبل {} أيام" -msgstr[4] "قبل {} أيام" -msgstr[5] "قبل {} يوم" - -#: selfdrive/ui/layouts/settings/software.py:37 -#, python-format -msgid "{} hour ago" -msgid_plural "{} hours ago" -msgstr[0] "قبل {} ساعة" -msgstr[1] "قبل {} ساعة" -msgstr[2] "قبل {} ساعتين" -msgstr[3] "قبل {} ساعات" -msgstr[4] "قبل {} ساعات" -msgstr[5] "قبل {} ساعة" - -#: selfdrive/ui/layouts/settings/software.py:34 -#, python-format -msgid "{} minute ago" -msgid_plural "{} minutes ago" -msgstr[0] "قبل {} دقيقة" -msgstr[1] "قبل {} دقيقة" -msgstr[2] "قبل {} دقيقتين" -msgstr[3] "قبل {} دقائق" -msgstr[4] "قبل {} دقائق" -msgstr[5] "قبل {} دقيقة" - -#: selfdrive/ui/layouts/settings/firehose.py:111 -#, python-format -msgid "{} segment of your driving is in the training dataset so far." -msgid_plural "{} segments of your driving is in the training dataset so far." -msgstr[0] "{} مقطع من قيادتك ضمن مجموعة بيانات التدريب حتى الآن." -msgstr[1] "{} مقطع من قيادتك ضمن مجموعة بيانات التدريب حتى الآن." -msgstr[2] "{} مقطعان من قيادتك ضمن مجموعة بيانات التدريب حتى الآن." -msgstr[3] "{} مقاطع من قيادتك ضمن مجموعة بيانات التدريب حتى الآن." -msgstr[4] "{} مقاطع من قيادتك ضمن مجموعة بيانات التدريب حتى الآن." -msgstr[5] "{} مقطع من قيادتك ضمن مجموعة بيانات التدريب حتى الآن." - -#: selfdrive/ui/widgets/prime.py:62 -#, python-format -msgid "✓ SUBSCRIBED" -msgstr "✓ مشترك" - -#: selfdrive/ui/widgets/setup.py:22 -#, python-format -msgid "🔥 Firehose Mode 🔥" -msgstr "🔥 وضع Firehose 🔥" diff --git a/selfdrive/ui/translations/languages.json b/selfdrive/ui/translations/languages.json index 47e673ce8..99d3aafe3 100644 --- a/selfdrive/ui/translations/languages.json +++ b/selfdrive/ui/translations/languages.json @@ -6,7 +6,6 @@ "Español": "es", "Türkçe": "tr", "Українська": "uk", - "العربية": "ar", "ไทย": "th", "中文(繁體)": "zh-CHT", "中文(简体)": "zh-CHS", diff --git a/system/ui/lib/multilang.py b/system/ui/lib/multilang.py index 70de1e3d5..ad2a5f939 100644 --- a/system/ui/lib/multilang.py +++ b/system/ui/lib/multilang.py @@ -16,7 +16,6 @@ TRANSLATIONS_DIR = UI_DIR.joinpath("translations") LANGUAGES_FILE = TRANSLATIONS_DIR.joinpath("languages.json") UNIFONT_LANGUAGES = [ - "ar", "th", "zh-CHT", "zh-CHS", From 28795d3065a291d68b218739cb0508625beacc45 Mon Sep 17 00:00:00 2001 From: Jason Young <46612682+jyoung8607@users.noreply.github.com> Date: Thu, 5 Feb 2026 16:30:03 -0500 Subject: [PATCH 830/910] bump opendbc (#37091) --- opendbc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc_repo b/opendbc_repo index 7c78ee87b..39ee86119 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 7c78ee87b7b54bb2179d86d5e28c1f65bbf96669 +Subproject commit 39ee861197edca894fc1b2e28d6fd1a38aea9ca1 From 7e959c5a3e7611a19b24c46729461805a5bb9f84 Mon Sep 17 00:00:00 2001 From: felsager <76905857+felsager@users.noreply.github.com> Date: Thu, 5 Feb 2026 15:55:03 -0800 Subject: [PATCH 831/910] long_mpc: use log.capnp source enum instead of list (#37093) --- .../controls/lib/longitudinal_mpc_lib/long_mpc.py | 11 +++++++++-- selfdrive/controls/lib/longitudinal_planner.py | 5 +++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py index 9408132c5..f6d2eba91 100755 --- a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py +++ b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py @@ -217,7 +217,7 @@ class LongitudinalMpc: self.dt = dt self.solver = AcadosOcpSolverCython(MODEL_NAME, ACADOS_SOLVER_TYPE, N) self.reset() - self.source = SOURCES[2] + self.source = log.LongitudinalPlan.LongitudinalPlanSource.cruise def reset(self): self.solver.reset() @@ -335,7 +335,14 @@ class LongitudinalMpc: cruise_obstacle = np.cumsum(T_DIFFS * v_cruise_clipped) + get_safe_obstacle_distance(v_cruise_clipped, t_follow) x_obstacles = np.column_stack([lead_0_obstacle, lead_1_obstacle, cruise_obstacle]) - self.source = SOURCES[np.argmin(x_obstacles[0])] + lead_idx = np.argmin(x_obstacles[0]) + match lead_idx: + case 0: + self.source = log.LongitudinalPlan.LongitudinalPlanSource.lead0 + case 1: + self.source = log.LongitudinalPlan.LongitudinalPlanSource.lead1 + case 2: + self.source = log.LongitudinalPlan.LongitudinalPlanSource.cruise self.yref[:,:] = 0.0 for i in range(N): diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index c5c03eba1..47228ada4 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -3,13 +3,14 @@ import math import numpy as np import cereal.messaging as messaging +from cereal import log from opendbc.car.interfaces import ACCEL_MIN, ACCEL_MAX from openpilot.common.constants import CV from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.realtime import DT_MDL from openpilot.selfdrive.modeld.constants import ModelConstants from openpilot.selfdrive.controls.lib.longcontrol import LongCtrlState -from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import LongitudinalMpc, SOURCES +from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import LongitudinalMpc from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import T_IDXS as T_IDXS_MPC from openpilot.selfdrive.controls.lib.drive_helpers import CONTROL_N, get_accel_from_plan from openpilot.selfdrive.car.cruise import V_CRUISE_MAX, V_CRUISE_UNSET @@ -156,7 +157,7 @@ class LongitudinalPlanner: output_a_target = min(output_a_target_e2e, output_a_target_mpc) self.output_should_stop = output_should_stop_e2e or output_should_stop_mpc if output_a_target < output_a_target_mpc: - self.mpc.source = SOURCES[3] + self.mpc.source = log.LongitudinalPlan.LongitudinalPlanSource.e2e else: output_a_target = output_a_target_mpc self.output_should_stop = output_should_stop_mpc From 64f74dad2702a11b6ad2c38e3c3ffc228514adbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Thu, 5 Feb 2026 16:23:28 -0800 Subject: [PATCH 832/910] Revert "long_mpc: use log.capnp source enum instead of list" (#37095) Revert "long_mpc: use log.capnp source enum instead of list (#37093)" This reverts commit 7e959c5a3e7611a19b24c46729461805a5bb9f84. --- .../controls/lib/longitudinal_mpc_lib/long_mpc.py | 11 ++--------- selfdrive/controls/lib/longitudinal_planner.py | 5 ++--- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py index f6d2eba91..9408132c5 100755 --- a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py +++ b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py @@ -217,7 +217,7 @@ class LongitudinalMpc: self.dt = dt self.solver = AcadosOcpSolverCython(MODEL_NAME, ACADOS_SOLVER_TYPE, N) self.reset() - self.source = log.LongitudinalPlan.LongitudinalPlanSource.cruise + self.source = SOURCES[2] def reset(self): self.solver.reset() @@ -335,14 +335,7 @@ class LongitudinalMpc: cruise_obstacle = np.cumsum(T_DIFFS * v_cruise_clipped) + get_safe_obstacle_distance(v_cruise_clipped, t_follow) x_obstacles = np.column_stack([lead_0_obstacle, lead_1_obstacle, cruise_obstacle]) - lead_idx = np.argmin(x_obstacles[0]) - match lead_idx: - case 0: - self.source = log.LongitudinalPlan.LongitudinalPlanSource.lead0 - case 1: - self.source = log.LongitudinalPlan.LongitudinalPlanSource.lead1 - case 2: - self.source = log.LongitudinalPlan.LongitudinalPlanSource.cruise + self.source = SOURCES[np.argmin(x_obstacles[0])] self.yref[:,:] = 0.0 for i in range(N): diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index 47228ada4..c5c03eba1 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -3,14 +3,13 @@ import math import numpy as np import cereal.messaging as messaging -from cereal import log from opendbc.car.interfaces import ACCEL_MIN, ACCEL_MAX from openpilot.common.constants import CV from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.realtime import DT_MDL from openpilot.selfdrive.modeld.constants import ModelConstants from openpilot.selfdrive.controls.lib.longcontrol import LongCtrlState -from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import LongitudinalMpc +from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import LongitudinalMpc, SOURCES from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import T_IDXS as T_IDXS_MPC from openpilot.selfdrive.controls.lib.drive_helpers import CONTROL_N, get_accel_from_plan from openpilot.selfdrive.car.cruise import V_CRUISE_MAX, V_CRUISE_UNSET @@ -157,7 +156,7 @@ class LongitudinalPlanner: output_a_target = min(output_a_target_e2e, output_a_target_mpc) self.output_should_stop = output_should_stop_e2e or output_should_stop_mpc if output_a_target < output_a_target_mpc: - self.mpc.source = log.LongitudinalPlan.LongitudinalPlanSource.e2e + self.mpc.source = SOURCES[3] else: output_a_target = output_a_target_mpc self.output_should_stop = output_should_stop_mpc From 2b8e887e442df39df3bb5b00aab07a9700ce50dd Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 5 Feb 2026 23:39:22 -0800 Subject: [PATCH 833/910] mici setup: remove unused functions --- system/ui/mici_setup.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index fac26f06e..91e5f0fb9 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -590,15 +590,9 @@ class Setup(Widget): def _custom_software_warning_back_button_callback(self): self._set_state(SetupState.SOFTWARE_SELECTION) - def _custom_software_warning_continue_button_callback(self): - self._set_state(SetupState.CUSTOM_SOFTWARE) - def _getting_started_button_callback(self): self._set_state(SetupState.SOFTWARE_SELECTION) - def _software_selection_back_button_callback(self): - self._set_state(SetupState.GETTING_STARTED) - def _software_selection_continue_button_callback(self): self.use_openpilot() From f5b84e74f4ddd9814e80630ecb718fca5392b6c0 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 5 Feb 2026 23:42:16 -0800 Subject: [PATCH 834/910] fix mici setup int enum --- system/ui/mici_setup.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 91e5f0fb9..78b6b5cec 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -94,12 +94,12 @@ class NetworkConnectivityMonitor: class SetupState(IntEnum): GETTING_STARTED = 0 NETWORK_SETUP = 1 - NETWORK_SETUP_CUSTOM_SOFTWARE = 8 - SOFTWARE_SELECTION = 2 - CUSTOM_SOFTWARE = 3 - DOWNLOADING = 4 - DOWNLOAD_FAILED = 5 - CUSTOM_SOFTWARE_WARNING = 6 + NETWORK_SETUP_CUSTOM_SOFTWARE = 2 + SOFTWARE_SELECTION = 3 + CUSTOM_SOFTWARE = 4 + DOWNLOADING = 5 + DOWNLOAD_FAILED = 6 + CUSTOM_SOFTWARE_WARNING = 7 class StartPage(Widget): From cb670c2c3dd1564e9dd5d0fc9a1b1e005dc8183c Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Fri, 6 Feb 2026 11:58:30 -0600 Subject: [PATCH 835/910] clips: improve overlays for mici (#37088) * adjust overlay sizes and wrap metadata text for mici * comment * adjust overlay rendering to dynamically calculate line height for wrapped metadata text * render time first so we can use width in margin calculation * update comment (to retry failed CI actually) * increase metadata size on mici --- tools/clip/run.py | 63 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 49 insertions(+), 14 deletions(-) diff --git a/tools/clip/run.py b/tools/clip/run.py index f2b871be6..679bb241c 100755 --- a/tools/clip/run.py +++ b/tools/clip/run.py @@ -238,25 +238,60 @@ def draw_text_box(rl, text, x, y, size, gui_app, font, font_scale, color=None, c rl.draw_text_ex(font, text, rl.Vector2(x, y), size, 0, text_color) -def render_overlays(rl, gui_app, font, font_scale, metadata, title, start_time, frame_idx, show_metadata, show_time): +def _wrap_text_by_delimiter(text: str, rl, font, font_size: int, font_scale: float, max_width: int, delimiter: str = ", ") -> list[str]: + """Wrap text by splitting on delimiter when it exceeds max_width.""" + words = text.split(delimiter) + lines: list[str] = [] + current_line: list[str] = [] + # Build lines word by word + for word in words: + current_line.append(word) + check_line = delimiter.join(current_line) + # Check if line exceeds max width + if rl.measure_text_ex(font, check_line, font_size * font_scale, 0).x > max_width: + current_line.pop() # Line is too long, move word to next line + if current_line: + lines.append(delimiter.join(current_line)) + current_line = [word] + # Add leftover words as last line + if current_line: + lines.append(delimiter.join(current_line)) + return lines + + +def render_overlays(rl, gui_app, font, font_scale, big, metadata, title, start_time, frame_idx, show_metadata, show_time): + metadata_size = 16 if big else 12 + title_size = 32 if big else 24 + time_size = 24 if big else 16 + + # Time overlay + time_width = 0 + if show_time: + t = start_time + frame_idx / FRAMERATE + time_text = f"{int(t) // 60:02d}:{int(t) % 60:02d}" + time_width = int(rl.measure_text_ex(font, time_text, time_size * font_scale, 0).x) + draw_text_box(rl, time_text, gui_app.width - time_width - 5, 0, time_size, gui_app, font, font_scale) + + # Metadata overlay (first 5 seconds) if show_metadata and metadata and frame_idx < FRAMERATE * 5: m = metadata text = ", ".join([f"openpilot v{m['version']}", f"route: {m['route']}", f"car: {m['car']}", f"origin: {m['origin']}", f"branch: {m['branch']}", f"commit: {m['commit']}", f"modified: {m['modified']}"]) - # Truncate if too wide (leave 20px margin on each side) - max_width = gui_app.width - 40 - while rl.measure_text_ex(font, text, 15 * font_scale, 0).x > max_width and len(text) > 20: - text = text[:-4] + "..." - draw_text_box(rl, text, 0, 8, 15, gui_app, font, font_scale, center=True) + # Wrap text if too wide (leave margin on each side) + margin = 2 * (time_width + 10 if show_time else 20) # leave enough margin for time overlay + max_width = gui_app.width - margin + lines = _wrap_text_by_delimiter(text, rl, font, metadata_size, font_scale, max_width) + # Draw wrapped metadata text + y_offset = 6 + for line in lines: + draw_text_box(rl, line, 0, y_offset, metadata_size, gui_app, font, font_scale, center=True) + line_height = int(rl.measure_text_ex(font, line, metadata_size * font_scale, 0).y) + 4 + y_offset += line_height + + # Title overlay if title: - draw_text_box(rl, title, 0, 60, 32, gui_app, font, font_scale, center=True) - - if show_time: - t = start_time + frame_idx / FRAMERATE - time_text = f"{int(t)//60:02d}:{int(t)%60:02d}" - time_width = int(rl.measure_text_ex(font, time_text, 24 * font_scale, 0).x) - draw_text_box(rl, time_text, gui_app.width - time_width - 45, 45, 24, gui_app, font, font_scale) + draw_text_box(rl, title, 0, 60, title_size, gui_app, font, font_scale, center=True) def clip(route: Route, output: str, start: int, end: int, headless: bool = True, big: bool = False, @@ -313,7 +348,7 @@ def clip(route: Route, output: str, start: int, end: int, headless: bool = True, ui_state.update() if should_render: road_view.render() - render_overlays(rl, gui_app, font, FONT_SCALE, metadata, title, start, frame_idx, show_metadata, show_time) + render_overlays(rl, gui_app, font, FONT_SCALE, big, metadata, title, start, frame_idx, show_metadata, show_time) frame_idx += 1 pbar.update(1) timer.lap("render") From 7099bd18e3ca5936cdd3c8de8a8c679cc7edb053 Mon Sep 17 00:00:00 2001 From: felsager <76905857+felsager@users.noreply.github.com> Date: Fri, 6 Feb 2026 10:35:54 -0800 Subject: [PATCH 836/910] longitudinal mpc tuning: behind if main (#37099) --- .../mpc_longitudinal_tuning_report.py | 419 +++++++++--------- 1 file changed, 210 insertions(+), 209 deletions(-) diff --git a/tools/longitudinal_maneuvers/mpc_longitudinal_tuning_report.py b/tools/longitudinal_maneuvers/mpc_longitudinal_tuning_report.py index 8c1a60f5b..51e38112f 100644 --- a/tools/longitudinal_maneuvers/mpc_longitudinal_tuning_report.py +++ b/tools/longitudinal_maneuvers/mpc_longitudinal_tuning_report.py @@ -3,8 +3,8 @@ import sys import markdown import numpy as np import matplotlib.pyplot as plt -from openpilot.selfdrive.test.longitudinal_maneuvers.maneuver import Maneuver from openpilot.selfdrive.controls.tests.test_following_distance import desired_follow_distance +from openpilot.selfdrive.test.longitudinal_maneuvers.maneuver import Maneuver TIME = 0 LEAD_DISTANCE= 2 @@ -21,7 +21,6 @@ axis_labels = ['Time (s)', 'Lead distance (m)' ] - def get_html_from_results(results, labels, AXIS): fig, ax = plt.subplots(figsize=(16, 8)) for idx, speed in enumerate(list(results.keys())): @@ -38,242 +37,244 @@ def get_html_from_results(results, labels, AXIS): plt.close(fig) return fig_buffer.getvalue() + '
' +def generate_mpc_tuning_report(): + htmls = [] -htmls = [] + results = {} + name = 'Resuming behind lead' + labels = [] + for lead_accel in np.linspace(1.0, 4.0, 4): + man = Maneuver( + '', + duration=11, + initial_speed=0.0, + lead_relevancy=True, + initial_distance_lead=desired_follow_distance(0.0, 0.0), + speed_lead_values=[0.0, 10 * lead_accel], + cruise_values=[100, 100], + prob_lead_values=[1.0, 1.0], + breakpoints=[1., 11], + ) + valid, results[lead_accel] = man.evaluate() + labels.append(f'{lead_accel} m/s^2 lead acceleration') -results = {} -name = 'Resuming behind lead' -labels = [] -for lead_accel in np.linspace(1.0, 4.0, 4): - man = Maneuver( - '', - duration=11, - initial_speed=0.0, - lead_relevancy=True, - initial_distance_lead=desired_follow_distance(0.0, 0.0), - speed_lead_values=[0.0, 10 * lead_accel], - cruise_values=[100, 100], - prob_lead_values=[1.0, 1.0], - breakpoints=[1., 11], - ) - valid, results[lead_accel] = man.evaluate() - labels.append(f'{lead_accel} m/s^2 lead acceleration') - -htmls.append(markdown.markdown('# ' + name)) -htmls.append(get_html_from_results(results, labels, EGO_V)) -htmls.append(get_html_from_results(results, labels, EGO_A)) + htmls.append(markdown.markdown('# ' + name)) + htmls.append(get_html_from_results(results, labels, EGO_V)) + htmls.append(get_html_from_results(results, labels, EGO_A)) -results = {} -name = 'Approaching stopped car from 140m' -labels = [] -for speed in np.arange(0,45,5): - man = Maneuver( - name, - duration=30., - initial_speed=float(speed), - lead_relevancy=True, - initial_distance_lead=140., - speed_lead_values=[0.0, 0.], - breakpoints=[0., 30.], - ) - valid, results[speed] = man.evaluate() - results[speed][:,2] = results[speed][:,2] - results[speed][:,1] - labels.append(f'{speed} m/s approach speed') + results = {} + name = 'Approaching stopped car from 140m' + labels = [] + for speed in np.arange(0,45,5): + man = Maneuver( + name, + duration=30., + initial_speed=float(speed), + lead_relevancy=True, + initial_distance_lead=140., + speed_lead_values=[0.0, 0.], + breakpoints=[0., 30.], + ) + valid, results[speed] = man.evaluate() + results[speed][:,2] = results[speed][:,2] - results[speed][:,1] + labels.append(f'{speed} m/s approach speed') -htmls.append(markdown.markdown('# ' + name)) -htmls.append(get_html_from_results(results, labels, EGO_A)) -htmls.append(get_html_from_results(results, labels, D_REL)) + htmls.append(markdown.markdown('# ' + name)) + htmls.append(get_html_from_results(results, labels, EGO_A)) + htmls.append(get_html_from_results(results, labels, D_REL)) -results = {} -name = 'Following 5s oscillating lead' -labels = [] -speed = np.int64(10) -for oscil in np.arange(0, 10, 1): - man = Maneuver( - '', - duration=30., - initial_speed=float(speed), - lead_relevancy=True, - initial_distance_lead=desired_follow_distance(speed, speed), - speed_lead_values=[speed, speed, speed - oscil, speed + oscil, speed - oscil, speed + oscil, speed - oscil], - breakpoints=[0.,2., 5, 8, 15, 18, 25.], - ) - valid, results[oscil] = man.evaluate() - labels.append(f'{oscil} m/s oscilliation size') + results = {} + name = 'Following 5s (triangular) oscillating lead' + labels = [] + speed = np.int64(10) + for oscil in np.arange(0, 10, 1): + man = Maneuver( + '', + duration=30., + initial_speed=float(speed), + lead_relevancy=True, + initial_distance_lead=desired_follow_distance(speed, speed), + speed_lead_values=[speed, speed, speed - oscil, speed + oscil, speed - oscil, speed + oscil, speed - oscil], + breakpoints=[0.,2., 5, 8, 15, 18, 25.], + ) + valid, results[oscil] = man.evaluate() + labels.append(f'{oscil} m/s oscillation size') -htmls.append(markdown.markdown('# ' + name)) -htmls.append(get_html_from_results(results, labels, D_REL)) -htmls.append(get_html_from_results(results, labels, EGO_V)) -htmls.append(get_html_from_results(results, labels, EGO_A)) + htmls.append(markdown.markdown('# ' + name)) + htmls.append(get_html_from_results(results, labels, D_REL)) + htmls.append(get_html_from_results(results, labels, EGO_V)) + htmls.append(get_html_from_results(results, labels, EGO_A)) + results = {} + name = 'Speed profile when converging to steady state lead at 30m/s' + labels = [] + for distance in np.arange(20, 140, 10): + man = Maneuver( + '', + duration=50, + initial_speed=30.0, + lead_relevancy=True, + initial_distance_lead=distance, + speed_lead_values=[30.0], + breakpoints=[0.], + ) + valid, results[distance] = man.evaluate() + results[distance][:,2] = results[distance][:,2] - results[distance][:,1] + labels.append(f'{distance} m initial distance') -results = {} -name = 'Speed profile when converging to steady state lead at 30m/s' -labels = [] -for distance in np.arange(20, 140, 10): - man = Maneuver( - '', - duration=50, - initial_speed=30.0, - lead_relevancy=True, - initial_distance_lead=distance, - speed_lead_values=[30.0], - breakpoints=[0.], - ) - valid, results[distance] = man.evaluate() - results[distance][:,2] = results[distance][:,2] - results[distance][:,1] - labels.append(f'{distance} m initial distance') - -htmls.append(markdown.markdown('# ' + name)) -htmls.append(get_html_from_results(results, labels, EGO_V)) -htmls.append(get_html_from_results(results, labels, D_REL)) + htmls.append(markdown.markdown('# ' + name)) + htmls.append(get_html_from_results(results, labels, EGO_V)) + htmls.append(get_html_from_results(results, labels, D_REL)) -results = {} -name = 'Speed profile when converging to steady state lead at 20m/s' -labels = [] -for distance in np.arange(20, 140, 10): - man = Maneuver( - '', - duration=50, - initial_speed=20.0, - lead_relevancy=True, - initial_distance_lead=distance, - speed_lead_values=[20.0], - breakpoints=[0.], - ) - valid, results[distance] = man.evaluate() - results[distance][:,2] = results[distance][:,2] - results[distance][:,1] - labels.append(f'{distance} m initial distance') + results = {} + name = 'Speed profile when converging to steady state lead at 20m/s' + labels = [] + for distance in np.arange(20, 140, 10): + man = Maneuver( + '', + duration=50, + initial_speed=20.0, + lead_relevancy=True, + initial_distance_lead=distance, + speed_lead_values=[20.0], + breakpoints=[0.], + ) + valid, results[distance] = man.evaluate() + results[distance][:,2] = results[distance][:,2] - results[distance][:,1] + labels.append(f'{distance} m initial distance') -htmls.append(markdown.markdown('# ' + name)) -htmls.append(get_html_from_results(results, labels, EGO_V)) -htmls.append(get_html_from_results(results, labels, D_REL)) + htmls.append(markdown.markdown('# ' + name)) + htmls.append(get_html_from_results(results, labels, EGO_V)) + htmls.append(get_html_from_results(results, labels, D_REL)) -results = {} -name = 'Following car at 30m/s that comes to a stop' -labels = [] -for stop_time in np.arange(4, 14, 1): - man = Maneuver( - '', - duration=50, - initial_speed=30.0, - lead_relevancy=True, - initial_distance_lead=60.0, - speed_lead_values=[30.0, 30.0, 0.0, 0.0], - breakpoints=[0., 20., 20 + stop_time, 30 + stop_time], - ) - valid, results[stop_time] = man.evaluate() - results[stop_time][:,2] = results[stop_time][:,2] - results[stop_time][:,1] - labels.append(f'{stop_time} seconds stop time') + results = {} + name = 'Following car at 30m/s that comes to a stop' + labels = [] + for stop_time in np.arange(4, 14, 1): + man = Maneuver( + '', + duration=50, + initial_speed=30.0, + lead_relevancy=True, + initial_distance_lead=60.0, + speed_lead_values=[30.0, 30.0, 0.0, 0.0], + breakpoints=[0., 20., 20 + stop_time, 30 + stop_time], + ) + valid, results[stop_time] = man.evaluate() + results[stop_time][:,2] = results[stop_time][:,2] - results[stop_time][:,1] + labels.append(f'{stop_time} seconds stop time') -htmls.append(markdown.markdown('# ' + name)) -htmls.append(get_html_from_results(results, labels, EGO_A)) -htmls.append(get_html_from_results(results, labels, D_REL)) + htmls.append(markdown.markdown('# ' + name)) + htmls.append(get_html_from_results(results, labels, EGO_A)) + htmls.append(get_html_from_results(results, labels, D_REL)) -results = {} -name = 'Response to cut-in at half follow distance' -labels = [] -for speed in np.arange(0, 40, 5): - man = Maneuver( - '', - duration=10, - initial_speed=float(speed), - lead_relevancy=True, - initial_distance_lead=desired_follow_distance(speed, speed)/2, - speed_lead_values=[speed, speed, speed], - cruise_values=[speed, speed, speed], - prob_lead_values=[0.0, 0.0, 1.0], - breakpoints=[0., 5.0, 5.01], - ) - valid, results[speed] = man.evaluate() - labels.append(f'{speed} m/s speed') + results = {} + name = 'Response to cut-in at half follow distance' + labels = [] + for speed in np.arange(0, 40, 5): + man = Maneuver( + '', + duration=10, + initial_speed=float(speed), + lead_relevancy=True, + initial_distance_lead=desired_follow_distance(speed, speed)/2, + speed_lead_values=[speed, speed, speed], + cruise_values=[speed, speed, speed], + prob_lead_values=[0.0, 0.0, 1.0], + breakpoints=[0., 5.0, 5.01], + ) + valid, results[speed] = man.evaluate() + labels.append(f'{speed} m/s speed') -htmls.append(markdown.markdown('# ' + name)) -htmls.append(get_html_from_results(results, labels, EGO_A)) -htmls.append(get_html_from_results(results, labels, D_REL)) + htmls.append(markdown.markdown('# ' + name)) + htmls.append(get_html_from_results(results, labels, EGO_A)) + htmls.append(get_html_from_results(results, labels, D_REL)) -results = {} -name = 'Follow a lead that accelerates at 2m/s^2 until steady state speed' -labels = [] -for speed in np.arange(0, 40, 5): - man = Maneuver( - '', - duration=50, - initial_speed=0.0, - lead_relevancy=True, - initial_distance_lead=desired_follow_distance(0.0, 0.0), - speed_lead_values=[0.0, 0.0, speed], - prob_lead_values=[1.0, 1.0, 1.0], - breakpoints=[0., 1.0, speed/2], - ) - valid, results[speed] = man.evaluate() - labels.append(f'{speed} m/s speed') + results = {} + name = 'Follow a lead that accelerates at 2m/s^2 until steady state speed' + labels = [] + for speed in np.arange(0, 40, 5): + man = Maneuver( + '', + duration=50, + initial_speed=0.0, + lead_relevancy=True, + initial_distance_lead=desired_follow_distance(0.0, 0.0), + speed_lead_values=[0.0, 0.0, speed], + prob_lead_values=[1.0, 1.0, 1.0], + breakpoints=[0., 1.0, speed/2], + ) + valid, results[speed] = man.evaluate() + labels.append(f'{speed} m/s speed') -htmls.append(markdown.markdown('# ' + name)) -htmls.append(get_html_from_results(results, labels, EGO_V)) -htmls.append(get_html_from_results(results, labels, EGO_A)) + htmls.append(markdown.markdown('# ' + name)) + htmls.append(get_html_from_results(results, labels, EGO_V)) + htmls.append(get_html_from_results(results, labels, EGO_A)) -results = {} -name = 'From stop to cruise' -labels = [] -for speed in np.arange(0, 40, 5): - man = Maneuver( - '', - duration=50, - initial_speed=0.0, - lead_relevancy=True, - initial_distance_lead=desired_follow_distance(0.0, 0.0), - speed_lead_values=[0.0, 0.0], - cruise_values=[0.0, speed], - prob_lead_values=[0.0, 0.0], - breakpoints=[1., 1.01], - ) - valid, results[speed] = man.evaluate() - labels.append(f'{speed} m/s speed') + results = {} + name = 'From stop to cruise' + labels = [] + for speed in np.arange(0, 40, 5): + man = Maneuver( + '', + duration=50, + initial_speed=0.0, + lead_relevancy=True, + initial_distance_lead=desired_follow_distance(0.0, 0.0), + speed_lead_values=[0.0, 0.0], + cruise_values=[0.0, speed], + prob_lead_values=[0.0, 0.0], + breakpoints=[1., 1.01], + ) + valid, results[speed] = man.evaluate() + labels.append(f'{speed} m/s speed') -htmls.append(markdown.markdown('# ' + name)) -htmls.append(get_html_from_results(results, labels, EGO_V)) -htmls.append(get_html_from_results(results, labels, EGO_A)) + htmls.append(markdown.markdown('# ' + name)) + htmls.append(get_html_from_results(results, labels, EGO_V)) + htmls.append(get_html_from_results(results, labels, EGO_A)) -results = {} -name = 'From cruise to min' -labels = [] -for speed in np.arange(10, 40, 5): - man = Maneuver( - '', - duration=50, - initial_speed=float(speed), - lead_relevancy=True, - initial_distance_lead=desired_follow_distance(0.0, 0.0), - speed_lead_values=[0.0, 0.0], - cruise_values=[speed, 10.0], - prob_lead_values=[0.0, 0.0], - breakpoints=[1., 1.01], - ) - valid, results[speed] = man.evaluate() - labels.append(f'{speed} m/s speed') + results = {} + name = 'From cruise to min' + labels = [] + for speed in np.arange(10, 40, 5): + man = Maneuver( + '', + duration=50, + initial_speed=float(speed), + lead_relevancy=True, + initial_distance_lead=desired_follow_distance(0.0, 0.0), + speed_lead_values=[0.0, 0.0], + cruise_values=[speed, 10.0], + prob_lead_values=[0.0, 0.0], + breakpoints=[1., 1.01], + ) + valid, results[speed] = man.evaluate() + labels.append(f'{speed} m/s speed') -htmls.append(markdown.markdown('# ' + name)) -htmls.append(get_html_from_results(results, labels, EGO_V)) -htmls.append(get_html_from_results(results, labels, EGO_A)) + htmls.append(markdown.markdown('# ' + name)) + htmls.append(get_html_from_results(results, labels, EGO_V)) + htmls.append(get_html_from_results(results, labels, EGO_A)) -if len(sys.argv) < 2: - file_name = 'long_mpc_tune_report.html' -else: - file_name = sys.argv[1] + return htmls -with open(file_name, 'w') as f: - f.write(markdown.markdown('# MPC longitudinal tuning report')) +if __name__ == '__main__': + htmls = generate_mpc_tuning_report() -with open(file_name, 'a') as f: - for html in htmls: - f.write(html) + if len(sys.argv) < 2: + file_name = 'long_mpc_tune_report.html' + else: + file_name = sys.argv[1] + + with open(file_name, 'w') as f: + f.write(markdown.markdown('# MPC longitudinal tuning report')) + for html in htmls: + f.write(html) From 9755520b87fac634da3068561511b8df93626c9b Mon Sep 17 00:00:00 2001 From: felsager <76905857+felsager@users.noreply.github.com> Date: Fri, 6 Feb 2026 11:30:49 -0800 Subject: [PATCH 837/910] longitudinal mpc tuning report: add sinusoidal oscillation scenario (#37100) --- .../mpc_longitudinal_tuning_report.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tools/longitudinal_maneuvers/mpc_longitudinal_tuning_report.py b/tools/longitudinal_maneuvers/mpc_longitudinal_tuning_report.py index 51e38112f..547f45aa2 100644 --- a/tools/longitudinal_maneuvers/mpc_longitudinal_tuning_report.py +++ b/tools/longitudinal_maneuvers/mpc_longitudinal_tuning_report.py @@ -3,6 +3,7 @@ import sys import markdown import numpy as np import matplotlib.pyplot as plt +from openpilot.common.realtime import DT_MDL from openpilot.selfdrive.controls.tests.test_following_distance import desired_follow_distance from openpilot.selfdrive.test.longitudinal_maneuvers.maneuver import Maneuver @@ -108,6 +109,33 @@ def generate_mpc_tuning_report(): htmls.append(get_html_from_results(results, labels, EGO_A)) + results = {} + name = 'Following 5s (sinusoidal) oscillating lead' + labels = [] + speed = np.int64(10) + duration = float(30) + f_osc = 1. / 5 + for oscil in np.arange(0, 10, 1): + bps = DT_MDL * np.arange(int(duration / DT_MDL)) + lead_speeds = speed + oscil * np.sin(2 * np.pi * f_osc * bps) + man = Maneuver( + '', + duration=duration, + initial_speed=float(speed), + lead_relevancy=True, + initial_distance_lead=desired_follow_distance(speed, speed), + speed_lead_values=lead_speeds, + breakpoints=bps, + ) + valid, results[oscil] = man.evaluate() + labels.append(f'{oscil} m/s oscilliation size') + + htmls.append(markdown.markdown('# ' + name)) + htmls.append(get_html_from_results(results, labels, D_REL)) + htmls.append(get_html_from_results(results, labels, EGO_V)) + htmls.append(get_html_from_results(results, labels, EGO_A)) + + results = {} name = 'Speed profile when converging to steady state lead at 30m/s' labels = [] From 187d3a079c45b2221cb46744bff4defc224f1fc5 Mon Sep 17 00:00:00 2001 From: felsager <76905857+felsager@users.noreply.github.com> Date: Fri, 6 Feb 2026 13:36:51 -0800 Subject: [PATCH 838/910] long_mpc: use log.capnp source enum (#37096) --- selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py | 7 ++++--- selfdrive/controls/lib/longitudinal_planner.py | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py index 9408132c5..e75705cb6 100755 --- a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py +++ b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py @@ -22,7 +22,8 @@ LONG_MPC_DIR = os.path.dirname(os.path.abspath(__file__)) EXPORT_DIR = os.path.join(LONG_MPC_DIR, "c_generated_code") JSON_FILE = os.path.join(LONG_MPC_DIR, "acados_ocp_long.json") -SOURCES = ['lead0', 'lead1', 'cruise', 'e2e'] +LongitudinalPlanSource = log.LongitudinalPlan.LongitudinalPlanSource +MPC_SOURCES = (LongitudinalPlanSource.lead0, LongitudinalPlanSource.lead1, LongitudinalPlanSource.cruise) X_DIM = 3 U_DIM = 1 @@ -217,7 +218,7 @@ class LongitudinalMpc: self.dt = dt self.solver = AcadosOcpSolverCython(MODEL_NAME, ACADOS_SOLVER_TYPE, N) self.reset() - self.source = SOURCES[2] + self.source = LongitudinalPlanSource.cruise def reset(self): self.solver.reset() @@ -335,7 +336,7 @@ class LongitudinalMpc: cruise_obstacle = np.cumsum(T_DIFFS * v_cruise_clipped) + get_safe_obstacle_distance(v_cruise_clipped, t_follow) x_obstacles = np.column_stack([lead_0_obstacle, lead_1_obstacle, cruise_obstacle]) - self.source = SOURCES[np.argmin(x_obstacles[0])] + self.source = MPC_SOURCES[np.argmin(x_obstacles[0])] self.yref[:,:] = 0.0 for i in range(N): diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index c5c03eba1..64de1a8fd 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -9,7 +9,7 @@ from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.realtime import DT_MDL from openpilot.selfdrive.modeld.constants import ModelConstants from openpilot.selfdrive.controls.lib.longcontrol import LongCtrlState -from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import LongitudinalMpc, SOURCES +from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import LongitudinalMpc, LongitudinalPlanSource from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import T_IDXS as T_IDXS_MPC from openpilot.selfdrive.controls.lib.drive_helpers import CONTROL_N, get_accel_from_plan from openpilot.selfdrive.car.cruise import V_CRUISE_MAX, V_CRUISE_UNSET @@ -156,7 +156,7 @@ class LongitudinalPlanner: output_a_target = min(output_a_target_e2e, output_a_target_mpc) self.output_should_stop = output_should_stop_e2e or output_should_stop_mpc if output_a_target < output_a_target_mpc: - self.mpc.source = SOURCES[3] + self.mpc.source = LongitudinalPlanSource.e2e else: output_a_target = output_a_target_mpc self.output_should_stop = output_should_stop_mpc From 593c3a0c8e69686ee821b2a0b2cfa9901b67dc27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Fri, 6 Feb 2026 14:13:46 -0800 Subject: [PATCH 839/910] Calibrate in tg (#36621) * squash * bump tg * fix linmt * Ready to merge * cleaner * match modeld * more dead stuff --- selfdrive/modeld/SConscript | 49 ++--- selfdrive/modeld/compile_warp.py | 206 ++++++++++++++++++ selfdrive/modeld/dmonitoringmodeld.py | 27 ++- selfdrive/modeld/modeld.py | 47 ++-- selfdrive/modeld/models/commonmodel.cc | 64 ------ selfdrive/modeld/models/commonmodel.h | 97 --------- selfdrive/modeld/models/commonmodel.pxd | 27 --- selfdrive/modeld/models/commonmodel_pyx.pxd | 13 -- selfdrive/modeld/models/commonmodel_pyx.pyx | 74 ------- selfdrive/modeld/transforms/loadyuv.cc | 76 ------- selfdrive/modeld/transforms/loadyuv.cl | 47 ---- selfdrive/modeld/transforms/loadyuv.h | 20 -- selfdrive/modeld/transforms/transform.cc | 97 --------- selfdrive/modeld/transforms/transform.cl | 54 ----- selfdrive/modeld/transforms/transform.h | 25 --- selfdrive/test/process_replay/model_replay.py | 2 +- .../test/process_replay/process_replay.py | 15 +- 17 files changed, 280 insertions(+), 660 deletions(-) create mode 100755 selfdrive/modeld/compile_warp.py delete mode 100644 selfdrive/modeld/models/commonmodel.cc delete mode 100644 selfdrive/modeld/models/commonmodel.h delete mode 100644 selfdrive/modeld/models/commonmodel.pxd delete mode 100644 selfdrive/modeld/models/commonmodel_pyx.pxd delete mode 100644 selfdrive/modeld/models/commonmodel_pyx.pyx delete mode 100644 selfdrive/modeld/transforms/loadyuv.cc delete mode 100644 selfdrive/modeld/transforms/loadyuv.cl delete mode 100644 selfdrive/modeld/transforms/loadyuv.h delete mode 100644 selfdrive/modeld/transforms/transform.cc delete mode 100644 selfdrive/modeld/transforms/transform.cl delete mode 100644 selfdrive/modeld/transforms/transform.h diff --git a/selfdrive/modeld/SConscript b/selfdrive/modeld/SConscript index 91f359744..72d3f95c4 100644 --- a/selfdrive/modeld/SConscript +++ b/selfdrive/modeld/SConscript @@ -1,35 +1,10 @@ import os import glob -Import('env', 'envCython', 'arch', 'cereal', 'messaging', 'common', 'visionipc') +Import('env', 'arch') lenv = env.Clone() -lenvCython = envCython.Clone() -libs = [cereal, messaging, visionipc, common, 'capnp', 'kj', 'pthread'] -frameworks = [] - -common_src = [ - "models/commonmodel.cc", - "transforms/loadyuv.cc", - "transforms/transform.cc", -] - -# OpenCL is a framework on Mac -if arch == "Darwin": - frameworks += ['OpenCL'] -else: - libs += ['OpenCL'] - -# Set path definitions -for pathdef, fn in {'TRANSFORM': 'transforms/transform.cl', 'LOADYUV': 'transforms/loadyuv.cl'}.items(): - for xenv in (lenv, lenvCython): - xenv['CXXFLAGS'].append(f'-D{pathdef}_PATH=\\"{File(fn).abspath}\\"') - -# Compile cython -cython_libs = envCython["LIBS"] + libs -commonmodel_lib = lenv.Library('commonmodel', common_src) -lenvCython.Program('models/commonmodel_pyx.so', 'models/commonmodel_pyx.pyx', LIBS=[commonmodel_lib, *cython_libs], FRAMEWORKS=frameworks) -tinygrad_files = sorted(["#"+x for x in glob.glob(env.Dir("#tinygrad_repo").relpath + "/**", recursive=True, root_dir=env.Dir("#").abspath) if 'pycache' not in x]) +tinygrad_files = ["#"+x for x in glob.glob(env.Dir("#tinygrad_repo").relpath + "/**", recursive=True, root_dir=env.Dir("#").abspath) if 'pycache' not in x] # Get model metadata for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']: @@ -38,22 +13,30 @@ for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']: 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 warp +tg_flags = { + 'larch64': 'DEV=QCOM FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0', + 'Darwin': f'DEV=CPU HOME={os.path.expanduser("~")}', # tinygrad calls brew which needs a $HOME in the env +}.get(arch, 'DEV=CPU CPU_LLVM=1') +image_flag = { + 'larch64': 'IMAGE=2', +}.get(arch, 'IMAGE=0') +script_files = [File(Dir("#selfdrive/modeld").File("compile_warp.py").abspath)] +cmd = f'{tg_flags} python3 {Dir("#selfdrive/modeld").abspath}/compile_warp.py ' +lenv.Command(fn + "warp_tinygrad.pkl", tinygrad_files + script_files, cmd) + 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' + f'{pythonpath_string} {flags} {image_flag} 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': 'DEV=QCOM FLOAT16=1 NOLOCALS=1 IMAGE=2 JIT_BATCH_SIZE=0', - 'Darwin': f'DEV=CPU HOME={os.path.expanduser("~")}', # tinygrad calls brew which needs a $HOME in the env - }.get(arch, 'DEV=CPU CPU_LLVM=1') - tg_compile(flags, model_name) + tg_compile(tg_flags, model_name) # Compile BIG model if USB GPU is available if "USBGPU" in os.environ: diff --git a/selfdrive/modeld/compile_warp.py b/selfdrive/modeld/compile_warp.py new file mode 100755 index 000000000..5adb60e62 --- /dev/null +++ b/selfdrive/modeld/compile_warp.py @@ -0,0 +1,206 @@ +#!/usr/bin/env python3 +import time +import pickle +import numpy as np +from pathlib import Path +from tinygrad.tensor import Tensor +from tinygrad.helpers import Context +from tinygrad.device import Device +from tinygrad.engine.jit import TinyJit + +from openpilot.system.camerad.cameras.nv12_info import get_nv12_info +from openpilot.common.transformations.model import MEDMODEL_INPUT_SIZE, DM_INPUT_SIZE +from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye + +MODELS_DIR = Path(__file__).parent / 'models' + +CAMERA_CONFIGS = [ + (_ar_ox_fisheye.width, _ar_ox_fisheye.height), # tici: 1928x1208 + (_os_fisheye.width, _os_fisheye.height), # mici: 1344x760 +] + +UV_SCALE_MATRIX = np.array([[0.5, 0, 0], [0, 0.5, 0], [0, 0, 1]], dtype=np.float32) +UV_SCALE_MATRIX_INV = np.linalg.inv(UV_SCALE_MATRIX) + +IMG_BUFFER_SHAPE = (30, MEDMODEL_INPUT_SIZE[1] // 2, MEDMODEL_INPUT_SIZE[0] // 2) + + +def warp_pkl_path(w, h): + return MODELS_DIR / f'warp_{w}x{h}_tinygrad.pkl' + + +def dm_warp_pkl_path(w, h): + return MODELS_DIR / f'dm_warp_{w}x{h}_tinygrad.pkl' + + +def warp_perspective_tinygrad(src_flat, M_inv, dst_shape, src_shape, stride_pad, ratio): + w_dst, h_dst = dst_shape + h_src, w_src = src_shape + + x = Tensor.arange(w_dst).reshape(1, w_dst).expand(h_dst, w_dst) + y = Tensor.arange(h_dst).reshape(h_dst, 1).expand(h_dst, w_dst) + ones = Tensor.ones_like(x) + dst_coords = x.reshape(1, -1).cat(y.reshape(1, -1)).cat(ones.reshape(1, -1)) + + src_coords = M_inv @ dst_coords + src_coords = src_coords / src_coords[2:3, :] + + x_nn_clipped = Tensor.round(src_coords[0]).clip(0, w_src - 1).cast('int') + y_nn_clipped = Tensor.round(src_coords[1]).clip(0, h_src - 1).cast('int') + idx = y_nn_clipped * w_src + (y_nn_clipped * ratio).cast('int') * stride_pad + x_nn_clipped + + sampled = src_flat[idx] + return sampled + + +def frames_to_tensor(frames, model_w, model_h): + H = (frames.shape[0] * 2) // 3 + W = frames.shape[1] + in_img1 = Tensor.cat(frames[0:H:2, 0::2], + frames[1:H:2, 0::2], + frames[0:H:2, 1::2], + frames[1:H:2, 1::2], + frames[H:H+H//4].reshape((H//2, W//2)), + frames[H+H//4:H+H//2].reshape((H//2, W//2)), dim=0).reshape((6, H//2, W//2)) + return in_img1 + + +def make_frame_prepare(cam_w, cam_h, model_w, model_h): + stride, y_height, _, _ = get_nv12_info(cam_w, cam_h) + uv_offset = stride * y_height + stride_pad = stride - cam_w + + def frame_prepare_tinygrad(input_frame, M_inv): + tg_scale = Tensor(UV_SCALE_MATRIX) + M_inv_uv = tg_scale @ M_inv @ Tensor(UV_SCALE_MATRIX_INV) + with Context(SPLIT_REDUCEOP=0): + y = warp_perspective_tinygrad(input_frame[:cam_h*stride], + M_inv, (model_w, model_h), + (cam_h, cam_w), stride_pad, 1).realize() + u = warp_perspective_tinygrad(input_frame[uv_offset:uv_offset + (cam_h//4)*stride], + M_inv_uv, (model_w//2, model_h//2), + (cam_h//2, cam_w//2), stride_pad, 0.5).realize() + v = warp_perspective_tinygrad(input_frame[uv_offset + (cam_h//4)*stride:uv_offset + (cam_h//2)*stride], + M_inv_uv, (model_w//2, model_h//2), + (cam_h//2, cam_w//2), stride_pad, 0.5).realize() + yuv = y.cat(u).cat(v).reshape((model_h * 3 // 2, model_w)) + tensor = frames_to_tensor(yuv, model_w, model_h) + return tensor + return frame_prepare_tinygrad + + +def make_update_img_input(frame_prepare, model_w, model_h): + def update_img_input_tinygrad(tensor, frame, M_inv): + M_inv = M_inv.to(Device.DEFAULT) + new_img = frame_prepare(frame, M_inv) + full_buffer = tensor[6:].cat(new_img, dim=0).contiguous() + return full_buffer, Tensor.cat(full_buffer[:6], full_buffer[-6:], dim=0).contiguous().reshape(1, 12, model_h//2, model_w//2) + return update_img_input_tinygrad + + +def make_update_both_imgs(frame_prepare, model_w, model_h): + update_img = make_update_img_input(frame_prepare, model_w, model_h) + + def update_both_imgs_tinygrad(calib_img_buffer, new_img, M_inv, + calib_big_img_buffer, new_big_img, M_inv_big): + calib_img_buffer, calib_img_pair = update_img(calib_img_buffer, new_img, M_inv) + calib_big_img_buffer, calib_big_img_pair = update_img(calib_big_img_buffer, new_big_img, M_inv_big) + return calib_img_buffer, calib_img_pair, calib_big_img_buffer, calib_big_img_pair + return update_both_imgs_tinygrad + + +def make_warp_dm(cam_w, cam_h, dm_w, dm_h): + stride, y_height, _, _ = get_nv12_info(cam_w, cam_h) + stride_pad = stride - cam_w + + def warp_dm(input_frame, M_inv): + M_inv = M_inv.to(Device.DEFAULT) + with Context(SPLIT_REDUCEOP=0): + result = warp_perspective_tinygrad(input_frame[:cam_h*stride], M_inv, (dm_w, dm_h), (cam_h, cam_w), stride_pad, 1).reshape(-1, dm_h * dm_w) + return result + return warp_dm + + +def compile_modeld_warp(cam_w, cam_h): + model_w, model_h = MEDMODEL_INPUT_SIZE + _, _, _, yuv_size = get_nv12_info(cam_w, cam_h) + + print(f"Compiling modeld warp for {cam_w}x{cam_h}...") + + frame_prepare = make_frame_prepare(cam_w, cam_h, model_w, model_h) + update_both_imgs = make_update_both_imgs(frame_prepare, model_w, model_h) + update_img_jit = TinyJit(update_both_imgs, prune=True) + + full_buffer = Tensor.zeros(IMG_BUFFER_SHAPE, dtype='uint8').contiguous().realize() + big_full_buffer = Tensor.zeros(IMG_BUFFER_SHAPE, dtype='uint8').contiguous().realize() + full_buffer_np = np.zeros(IMG_BUFFER_SHAPE, dtype=np.uint8) + big_full_buffer_np = np.zeros(IMG_BUFFER_SHAPE, dtype=np.uint8) + + for i in range(10): + new_frame_np = (32 * np.random.randn(yuv_size).astype(np.float32) + 128).clip(0, 255).astype(np.uint8) + img_inputs = [full_buffer, + Tensor.from_blob(new_frame_np.ctypes.data, (yuv_size,), dtype='uint8').realize(), + Tensor(Tensor.randn(3, 3).mul(8).realize().numpy(), device='NPY')] + new_big_frame_np = (32 * np.random.randn(yuv_size).astype(np.float32) + 128).clip(0, 255).astype(np.uint8) + big_img_inputs = [big_full_buffer, + Tensor.from_blob(new_big_frame_np.ctypes.data, (yuv_size,), dtype='uint8').realize(), + Tensor(Tensor.randn(3, 3).mul(8).realize().numpy(), device='NPY')] + inputs = img_inputs + big_img_inputs + Device.default.synchronize() + + inputs_np = [x.numpy() for x in inputs] + inputs_np[0] = full_buffer_np + inputs_np[3] = big_full_buffer_np + + st = time.perf_counter() + out = update_img_jit(*inputs) + full_buffer = out[0].contiguous().realize().clone() + big_full_buffer = out[2].contiguous().realize().clone() + mt = time.perf_counter() + Device.default.synchronize() + et = time.perf_counter() + print(f" [{i+1}/10] enqueue {(mt-st)*1e3:6.2f} ms -- total {(et-st)*1e3:6.2f} ms") + + pkl_path = warp_pkl_path(cam_w, cam_h) + with open(pkl_path, "wb") as f: + pickle.dump(update_img_jit, f) + print(f" Saved to {pkl_path}") + + jit = pickle.load(open(pkl_path, "rb")) + jit(*inputs) + + +def compile_dm_warp(cam_w, cam_h): + dm_w, dm_h = DM_INPUT_SIZE + _, _, _, yuv_size = get_nv12_info(cam_w, cam_h) + + print(f"Compiling DM warp for {cam_w}x{cam_h}...") + + warp_dm = make_warp_dm(cam_w, cam_h, dm_w, dm_h) + warp_dm_jit = TinyJit(warp_dm, prune=True) + + for i in range(10): + inputs = [Tensor.from_blob((32 * Tensor.randn(yuv_size,) + 128).cast(dtype='uint8').realize().numpy().ctypes.data, (yuv_size,), dtype='uint8'), + Tensor(Tensor.randn(3, 3).mul(8).realize().numpy(), device='NPY')] + Device.default.synchronize() + st = time.perf_counter() + warp_dm_jit(*inputs) + mt = time.perf_counter() + Device.default.synchronize() + et = time.perf_counter() + print(f" [{i+1}/10] enqueue {(mt-st)*1e3:6.2f} ms -- total {(et-st)*1e3:6.2f} ms") + + pkl_path = dm_warp_pkl_path(cam_w, cam_h) + with open(pkl_path, "wb") as f: + pickle.dump(warp_dm_jit, f) + print(f" Saved to {pkl_path}") + + +def run_and_save_pickle(): + for cam_w, cam_h in CAMERA_CONFIGS: + compile_modeld_warp(cam_w, cam_h) + compile_dm_warp(cam_w, cam_h) + + +if __name__ == "__main__": + run_and_save_pickle() diff --git a/selfdrive/modeld/dmonitoringmodeld.py b/selfdrive/modeld/dmonitoringmodeld.py index fca762c69..f12c13081 100755 --- a/selfdrive/modeld/dmonitoringmodeld.py +++ b/selfdrive/modeld/dmonitoringmodeld.py @@ -3,7 +3,6 @@ import os from openpilot.system.hardware import TICI os.environ['DEV'] = 'QCOM' if TICI else 'CPU' from tinygrad.tensor import Tensor -from tinygrad.dtype import dtypes import time import pickle import numpy as np @@ -12,19 +11,19 @@ from pathlib import Path from cereal import messaging from cereal.messaging import PubMaster, SubMaster from msgq.visionipc import VisionIpcClient, VisionStreamType, VisionBuf +from msgq.visionipc.visionipc_pyx import CLContext from openpilot.common.swaglog import cloudlog from openpilot.common.realtime import config_realtime_process from openpilot.common.transformations.model import dmonitoringmodel_intrinsics from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye -from openpilot.selfdrive.modeld.models.commonmodel_pyx import CLContext, MonitoringModelFrame +from openpilot.system.camerad.cameras.nv12_info import get_nv12_info from openpilot.selfdrive.modeld.parse_model_outputs import sigmoid, safe_exp -from openpilot.selfdrive.modeld.runners.tinygrad_helpers import qcom_tensor_from_opencl_address PROCESS_NAME = "selfdrive.modeld.dmonitoringmodeld" SEND_RAW_PRED = os.getenv('SEND_RAW_PRED') MODEL_PKL_PATH = Path(__file__).parent / 'models/dmonitoring_model_tinygrad.pkl' METADATA_PATH = Path(__file__).parent / 'models/dmonitoring_model_metadata.pkl' - +MODELS_DIR = Path(__file__).parent / 'models' class ModelState: inputs: dict[str, np.ndarray] @@ -36,12 +35,15 @@ class ModelState: self.input_shapes = model_metadata['input_shapes'] self.output_slices = model_metadata['output_slices'] - self.frame = MonitoringModelFrame(cl_ctx) self.numpy_inputs = { 'calib': np.zeros(self.input_shapes['calib'], dtype=np.float32), } + self.warp_inputs_np = {'transform': np.zeros((3,3), dtype=np.float32)} + self.warp_inputs = {k: Tensor(v, device='NPY') for k,v in self.warp_inputs_np.items()} + self.frame_buf_params = None self.tensor_inputs = {k: Tensor(v, device='NPY').realize() for k,v in self.numpy_inputs.items()} + self.image_warp = None with open(MODEL_PKL_PATH, "rb") as f: self.model_run = pickle.load(f) @@ -50,14 +52,15 @@ class ModelState: t1 = time.perf_counter() - input_img_cl = self.frame.prepare(buf, transform.flatten()) - if TICI: - # The imgs tensors are backed by opencl memory, only need init once - if 'input_img' not in self.tensor_inputs: - self.tensor_inputs['input_img'] = qcom_tensor_from_opencl_address(input_img_cl.mem_address, self.input_shapes['input_img'], dtype=dtypes.uint8) - else: - self.tensor_inputs['input_img'] = Tensor(self.frame.buffer_from_cl(input_img_cl).reshape(self.input_shapes['input_img']), dtype=dtypes.uint8).realize() + if self.image_warp is None: + self.frame_buf_params = get_nv12_info(buf.width, buf.height) + warp_path = MODELS_DIR / f'dm_warp_{buf.width}x{buf.height}_tinygrad.pkl' + with open(warp_path, "rb") as f: + self.image_warp = pickle.load(f) + self.warp_inputs['frame'] = Tensor.from_blob(buf.data.ctypes.data, (self.frame_buf_params[3],), dtype='uint8').realize() + self.warp_inputs_np['transform'][:] = transform[:] + self.tensor_inputs['input_img'] = self.image_warp(self.warp_inputs['frame'], self.warp_inputs['transform']).realize() output = self.model_run(**self.tensor_inputs).contiguous().realize().uop.base.buffer.numpy() diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index 1f347dc32..d9445c8d3 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -7,7 +7,6 @@ if USBGPU: os.environ['DEV'] = 'AMD' os.environ['AMD_IFACE'] = 'USB' from tinygrad.tensor import Tensor -from tinygrad.dtype import dtypes import time import pickle import numpy as np @@ -16,20 +15,20 @@ from cereal import car, log from pathlib import Path from cereal.messaging import PubMaster, SubMaster from msgq.visionipc import VisionIpcClient, VisionStreamType, VisionBuf +from msgq.visionipc.visionipc_pyx import CLContext from opendbc.car.car_helpers import get_demo_car_params from openpilot.common.swaglog import cloudlog from openpilot.common.params import Params from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.realtime import config_realtime_process, DT_MDL from openpilot.common.transformations.camera import DEVICE_CAMERAS +from openpilot.system.camerad.cameras.nv12_info import get_nv12_info from openpilot.common.transformations.model import get_warp_matrix 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.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 -from openpilot.selfdrive.modeld.models.commonmodel_pyx import DrivingModelFrame, CLContext -from openpilot.selfdrive.modeld.runners.tinygrad_helpers import qcom_tensor_from_opencl_address PROCESS_NAME = "selfdrive.modeld.modeld" @@ -39,11 +38,15 @@ VISION_PKL_PATH = Path(__file__).parent / 'models/driving_vision_tinygrad.pkl' 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' +MODELS_DIR = Path(__file__).parent / 'models' LAT_SMOOTH_SECONDS = 0.0 LONG_SMOOTH_SECONDS = 0.3 MIN_LAT_CONTROL_SPEED = 0.3 +IMG_QUEUE_SHAPE = (6*(ModelConstants.MODEL_RUN_FREQ//ModelConstants.MODEL_CONTEXT_FREQ + 1), 128, 256) +assert IMG_QUEUE_SHAPE[0] == 30 + def get_action_from_model(model_output: dict[str, np.ndarray], prev_action: log.ModelDataV2.Action, lat_action_t: float, long_action_t: float, v_ego: float) -> log.ModelDataV2.Action: @@ -136,7 +139,6 @@ class InputQueues: return out class ModelState: - frames: dict[str, DrivingModelFrame] inputs: dict[str, np.ndarray] output: np.ndarray prev_desire: np.ndarray # for tracking the rising edge of the pulse @@ -155,7 +157,6 @@ class ModelState: self.policy_output_slices = policy_metadata['output_slices'] policy_output_size = policy_metadata['output_shapes']['outputs'][1] - self.frames = {name: DrivingModelFrame(context, ModelConstants.MODEL_RUN_FREQ//ModelConstants.MODEL_CONTEXT_FREQ) for name in self.vision_input_names} self.prev_desire = np.zeros(ModelConstants.DESIRE_LEN, dtype=np.float32) # policy inputs @@ -166,11 +167,17 @@ class ModelState: self.full_input_queues.reset() # img buffers are managed in openCL transform code - self.vision_inputs: dict[str, Tensor] = {} + self.img_queues = {'img': Tensor.zeros(IMG_QUEUE_SHAPE, dtype='uint8').contiguous().realize(), + 'big_img': Tensor.zeros(IMG_QUEUE_SHAPE, dtype='uint8').contiguous().realize(),} + self.full_frames : dict[str, Tensor] = {} + self.transforms_np = {k: np.zeros((3,3), dtype=np.float32) for k in self.img_queues} + self.transforms = {k: Tensor(v, device='NPY').realize() for k, v in self.transforms_np.items()} self.vision_output = np.zeros(vision_output_size, dtype=np.float32) self.policy_inputs = {k: Tensor(v, device='NPY').realize() for k,v in self.numpy_inputs.items()} self.policy_output = np.zeros(policy_output_size, dtype=np.float32) self.parser = Parser() + self.frame_buf_params : dict[str, tuple[int, int, int, int]] = {} + self.update_imgs = None with open(VISION_PKL_PATH, "rb") as f: self.vision_run = pickle.load(f) @@ -188,23 +195,28 @@ class ModelState: inputs['desire_pulse'][0] = 0 new_desire = np.where(inputs['desire_pulse'] - self.prev_desire > .99, inputs['desire_pulse'], 0) self.prev_desire[:] = inputs['desire_pulse'] + if self.update_imgs is None: + for key in bufs.keys(): + w, h = bufs[key].width, bufs[key].height + self.frame_buf_params[key] = get_nv12_info(w, h) + warp_path = MODELS_DIR / f'warp_{w}x{h}_tinygrad.pkl' + with open(warp_path, "rb") as f: + self.update_imgs = pickle.load(f) - imgs_cl = {name: self.frames[name].prepare(bufs[name], transforms[name].flatten()) for name in self.vision_input_names} - 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: - self.vision_inputs[key] = qcom_tensor_from_opencl_address(imgs_cl[key].mem_address, self.vision_input_shapes[key], dtype=dtypes.uint8) - else: - for key in imgs_cl: - frame_input = self.frames[key].buffer_from_cl(imgs_cl[key]).reshape(self.vision_input_shapes[key]) - self.vision_inputs[key] = Tensor(frame_input, dtype=dtypes.uint8).realize() + for key in bufs.keys(): + self.full_frames[key] = Tensor.from_blob(bufs[key].data.ctypes.data, (self.frame_buf_params[key][3],), dtype='uint8').realize() + self.transforms_np[key][:,:] = transforms[key][:,:] + + out = self.update_imgs(self.img_queues['img'], self.full_frames['img'], self.transforms['img'], + self.img_queues['big_img'], self.full_frames['big_img'], self.transforms['big_img']) + self.img_queues['img'], self.img_queues['big_img'], = out[0].realize(), out[2].realize() + vision_inputs = {'img': out[1], 'big_img': out[3]} if prepare_only: return None - self.vision_output = self.vision_run(**self.vision_inputs).contiguous().realize().uop.base.buffer.numpy() + self.vision_output = self.vision_run(**vision_inputs).contiguous().realize().uop.base.buffer.numpy() vision_outputs_dict = self.parser.parse_vision_outputs(self.slice_outputs(self.vision_output, self.vision_output_slices)) self.full_input_queues.enqueue({'features_buffer': vision_outputs_dict['hidden_state'], 'desire_pulse': new_desire}) @@ -214,7 +226,6 @@ class ModelState: self.policy_output = self.policy_run(**self.policy_inputs).contiguous().realize().uop.base.buffer.numpy() policy_outputs_dict = self.parser.parse_policy_outputs(self.slice_outputs(self.policy_output, self.policy_output_slices)) - combined_outputs_dict = {**vision_outputs_dict, **policy_outputs_dict} if SEND_RAW_PRED: combined_outputs_dict['raw_pred'] = np.concatenate([self.vision_output.copy(), self.policy_output.copy()]) diff --git a/selfdrive/modeld/models/commonmodel.cc b/selfdrive/modeld/models/commonmodel.cc deleted file mode 100644 index d3341e76e..000000000 --- a/selfdrive/modeld/models/commonmodel.cc +++ /dev/null @@ -1,64 +0,0 @@ -#include "selfdrive/modeld/models/commonmodel.h" - -#include -#include - -#include "common/clutil.h" - -DrivingModelFrame::DrivingModelFrame(cl_device_id device_id, cl_context context, int _temporal_skip) : ModelFrame(device_id, context) { - input_frames = std::make_unique(buf_size); - temporal_skip = _temporal_skip; - input_frames_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, buf_size, NULL, &err)); - img_buffer_20hz_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, (temporal_skip+1)*frame_size_bytes, NULL, &err)); - region.origin = temporal_skip * frame_size_bytes; - region.size = frame_size_bytes; - last_img_cl = CL_CHECK_ERR(clCreateSubBuffer(img_buffer_20hz_cl, CL_MEM_READ_WRITE, CL_BUFFER_CREATE_TYPE_REGION, ®ion, &err)); - - loadyuv_init(&loadyuv, context, device_id, MODEL_WIDTH, MODEL_HEIGHT); - init_transform(device_id, context, MODEL_WIDTH, MODEL_HEIGHT); -} - -cl_mem* DrivingModelFrame::prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection) { - run_transform(yuv_cl, MODEL_WIDTH, MODEL_HEIGHT, frame_width, frame_height, frame_stride, frame_uv_offset, projection); - - for (int i = 0; i < temporal_skip; i++) { - CL_CHECK(clEnqueueCopyBuffer(q, img_buffer_20hz_cl, img_buffer_20hz_cl, (i+1)*frame_size_bytes, i*frame_size_bytes, frame_size_bytes, 0, nullptr, nullptr)); - } - loadyuv_queue(&loadyuv, q, y_cl, u_cl, v_cl, last_img_cl); - - copy_queue(&loadyuv, q, img_buffer_20hz_cl, input_frames_cl, 0, 0, frame_size_bytes); - copy_queue(&loadyuv, q, last_img_cl, input_frames_cl, 0, frame_size_bytes, frame_size_bytes); - - // NOTE: Since thneed is using a different command queue, this clFinish is needed to ensure the image is ready. - clFinish(q); - return &input_frames_cl; -} - -DrivingModelFrame::~DrivingModelFrame() { - deinit_transform(); - loadyuv_destroy(&loadyuv); - CL_CHECK(clReleaseMemObject(input_frames_cl)); - CL_CHECK(clReleaseMemObject(img_buffer_20hz_cl)); - CL_CHECK(clReleaseMemObject(last_img_cl)); - CL_CHECK(clReleaseCommandQueue(q)); -} - - -MonitoringModelFrame::MonitoringModelFrame(cl_device_id device_id, cl_context context) : ModelFrame(device_id, context) { - input_frames = std::make_unique(buf_size); - input_frame_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, buf_size, NULL, &err)); - - init_transform(device_id, context, MODEL_WIDTH, MODEL_HEIGHT); -} - -cl_mem* MonitoringModelFrame::prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection) { - run_transform(yuv_cl, MODEL_WIDTH, MODEL_HEIGHT, frame_width, frame_height, frame_stride, frame_uv_offset, projection); - clFinish(q); - return &y_cl; -} - -MonitoringModelFrame::~MonitoringModelFrame() { - deinit_transform(); - CL_CHECK(clReleaseMemObject(input_frame_cl)); - CL_CHECK(clReleaseCommandQueue(q)); -} diff --git a/selfdrive/modeld/models/commonmodel.h b/selfdrive/modeld/models/commonmodel.h deleted file mode 100644 index 176d7eb6d..000000000 --- a/selfdrive/modeld/models/commonmodel.h +++ /dev/null @@ -1,97 +0,0 @@ -#pragma once - -#include -#include -#include - -#include - -#define CL_USE_DEPRECATED_OPENCL_1_2_APIS -#ifdef __APPLE__ -#include -#else -#include -#endif - -#include "common/mat.h" -#include "selfdrive/modeld/transforms/loadyuv.h" -#include "selfdrive/modeld/transforms/transform.h" - -class ModelFrame { -public: - ModelFrame(cl_device_id device_id, cl_context context) { - q = CL_CHECK_ERR(clCreateCommandQueue(context, device_id, 0, &err)); - } - virtual ~ModelFrame() {} - virtual cl_mem* prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection) { return NULL; } - uint8_t* buffer_from_cl(cl_mem *in_frames, int buffer_size) { - CL_CHECK(clEnqueueReadBuffer(q, *in_frames, CL_TRUE, 0, buffer_size, input_frames.get(), 0, nullptr, nullptr)); - clFinish(q); - return &input_frames[0]; - } - - int MODEL_WIDTH; - int MODEL_HEIGHT; - int MODEL_FRAME_SIZE; - int buf_size; - -protected: - cl_mem y_cl, u_cl, v_cl; - Transform transform; - cl_command_queue q; - std::unique_ptr input_frames; - - void init_transform(cl_device_id device_id, cl_context context, int model_width, int model_height) { - y_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, model_width * model_height, NULL, &err)); - u_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, (model_width / 2) * (model_height / 2), NULL, &err)); - v_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, (model_width / 2) * (model_height / 2), NULL, &err)); - transform_init(&transform, context, device_id); - } - - void deinit_transform() { - transform_destroy(&transform); - CL_CHECK(clReleaseMemObject(v_cl)); - CL_CHECK(clReleaseMemObject(u_cl)); - CL_CHECK(clReleaseMemObject(y_cl)); - } - - void run_transform(cl_mem yuv_cl, int model_width, int model_height, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection) { - transform_queue(&transform, q, - yuv_cl, frame_width, frame_height, frame_stride, frame_uv_offset, - y_cl, u_cl, v_cl, model_width, model_height, projection); - } -}; - -class DrivingModelFrame : public ModelFrame { -public: - DrivingModelFrame(cl_device_id device_id, cl_context context, int _temporal_skip); - ~DrivingModelFrame(); - cl_mem* prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection); - - const int MODEL_WIDTH = 512; - const int MODEL_HEIGHT = 256; - const int MODEL_FRAME_SIZE = MODEL_WIDTH * MODEL_HEIGHT * 3 / 2; - const int buf_size = MODEL_FRAME_SIZE * 2; // 2 frames are temporal_skip frames apart - const size_t frame_size_bytes = MODEL_FRAME_SIZE * sizeof(uint8_t); - -private: - LoadYUVState loadyuv; - cl_mem img_buffer_20hz_cl, last_img_cl, input_frames_cl; - cl_buffer_region region; - int temporal_skip; -}; - -class MonitoringModelFrame : public ModelFrame { -public: - MonitoringModelFrame(cl_device_id device_id, cl_context context); - ~MonitoringModelFrame(); - cl_mem* prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection); - - const int MODEL_WIDTH = 1440; - const int MODEL_HEIGHT = 960; - const int MODEL_FRAME_SIZE = MODEL_WIDTH * MODEL_HEIGHT; - const int buf_size = MODEL_FRAME_SIZE; - -private: - cl_mem input_frame_cl; -}; diff --git a/selfdrive/modeld/models/commonmodel.pxd b/selfdrive/modeld/models/commonmodel.pxd deleted file mode 100644 index 4ac64d917..000000000 --- a/selfdrive/modeld/models/commonmodel.pxd +++ /dev/null @@ -1,27 +0,0 @@ -# distutils: language = c++ - -from msgq.visionipc.visionipc cimport cl_device_id, cl_context, cl_mem - -cdef extern from "common/mat.h": - cdef struct mat3: - float v[9] - -cdef extern from "common/clutil.h": - cdef unsigned long CL_DEVICE_TYPE_DEFAULT - cl_device_id cl_get_device_id(unsigned long) - cl_context cl_create_context(cl_device_id) - void cl_release_context(cl_context) - -cdef extern from "selfdrive/modeld/models/commonmodel.h": - cppclass ModelFrame: - int buf_size - unsigned char * buffer_from_cl(cl_mem*, int); - cl_mem * prepare(cl_mem, int, int, int, int, mat3) - - cppclass DrivingModelFrame: - int buf_size - DrivingModelFrame(cl_device_id, cl_context, int) - - cppclass MonitoringModelFrame: - int buf_size - MonitoringModelFrame(cl_device_id, cl_context) diff --git a/selfdrive/modeld/models/commonmodel_pyx.pxd b/selfdrive/modeld/models/commonmodel_pyx.pxd deleted file mode 100644 index 0bb798625..000000000 --- a/selfdrive/modeld/models/commonmodel_pyx.pxd +++ /dev/null @@ -1,13 +0,0 @@ -# distutils: language = c++ - -from msgq.visionipc.visionipc cimport cl_mem -from msgq.visionipc.visionipc_pyx cimport CLContext as BaseCLContext - -cdef class CLContext(BaseCLContext): - pass - -cdef class CLMem: - cdef cl_mem * mem - - @staticmethod - cdef create(void*) diff --git a/selfdrive/modeld/models/commonmodel_pyx.pyx b/selfdrive/modeld/models/commonmodel_pyx.pyx deleted file mode 100644 index 5b7d11bc7..000000000 --- a/selfdrive/modeld/models/commonmodel_pyx.pyx +++ /dev/null @@ -1,74 +0,0 @@ -# distutils: language = c++ -# cython: c_string_encoding=ascii, language_level=3 - -import numpy as np -cimport numpy as cnp -from libc.string cimport memcpy -from libc.stdint cimport uintptr_t - -from msgq.visionipc.visionipc cimport cl_mem -from msgq.visionipc.visionipc_pyx cimport VisionBuf, CLContext as BaseCLContext -from .commonmodel cimport CL_DEVICE_TYPE_DEFAULT, cl_get_device_id, cl_create_context, cl_release_context -from .commonmodel cimport mat3, ModelFrame as cppModelFrame, DrivingModelFrame as cppDrivingModelFrame, MonitoringModelFrame as cppMonitoringModelFrame - - -cdef class CLContext(BaseCLContext): - def __cinit__(self): - self.device_id = cl_get_device_id(CL_DEVICE_TYPE_DEFAULT) - self.context = cl_create_context(self.device_id) - - def __dealloc__(self): - if self.context: - cl_release_context(self.context) - -cdef class CLMem: - @staticmethod - cdef create(void * cmem): - mem = CLMem() - mem.mem = cmem - return mem - - @property - def mem_address(self): - return (self.mem) - -def cl_from_visionbuf(VisionBuf buf): - return CLMem.create(&buf.buf.buf_cl) - - -cdef class ModelFrame: - cdef cppModelFrame * frame - cdef int buf_size - - def __dealloc__(self): - del self.frame - - def prepare(self, VisionBuf buf, float[:] projection): - cdef mat3 cprojection - memcpy(cprojection.v, &projection[0], 9*sizeof(float)) - cdef cl_mem * data - data = self.frame.prepare(buf.buf.buf_cl, buf.width, buf.height, buf.stride, buf.uv_offset, cprojection) - return CLMem.create(data) - - def buffer_from_cl(self, CLMem in_frames): - cdef unsigned char * data2 - data2 = self.frame.buffer_from_cl(in_frames.mem, self.buf_size) - return np.asarray( data2) - - -cdef class DrivingModelFrame(ModelFrame): - cdef cppDrivingModelFrame * _frame - - def __cinit__(self, CLContext context, int temporal_skip): - self._frame = new cppDrivingModelFrame(context.device_id, context.context, temporal_skip) - self.frame = (self._frame) - self.buf_size = self._frame.buf_size - -cdef class MonitoringModelFrame(ModelFrame): - cdef cppMonitoringModelFrame * _frame - - def __cinit__(self, CLContext context): - self._frame = new cppMonitoringModelFrame(context.device_id, context.context) - self.frame = (self._frame) - self.buf_size = self._frame.buf_size - diff --git a/selfdrive/modeld/transforms/loadyuv.cc b/selfdrive/modeld/transforms/loadyuv.cc deleted file mode 100644 index c93f5cd03..000000000 --- a/selfdrive/modeld/transforms/loadyuv.cc +++ /dev/null @@ -1,76 +0,0 @@ -#include "selfdrive/modeld/transforms/loadyuv.h" - -#include -#include -#include - -void loadyuv_init(LoadYUVState* s, cl_context ctx, cl_device_id device_id, int width, int height) { - memset(s, 0, sizeof(*s)); - - s->width = width; - s->height = height; - - char args[1024]; - snprintf(args, sizeof(args), - "-cl-fast-relaxed-math -cl-denorms-are-zero " - "-DTRANSFORMED_WIDTH=%d -DTRANSFORMED_HEIGHT=%d", - width, height); - cl_program prg = cl_program_from_file(ctx, device_id, LOADYUV_PATH, args); - - s->loadys_krnl = CL_CHECK_ERR(clCreateKernel(prg, "loadys", &err)); - s->loaduv_krnl = CL_CHECK_ERR(clCreateKernel(prg, "loaduv", &err)); - s->copy_krnl = CL_CHECK_ERR(clCreateKernel(prg, "copy", &err)); - - // done with this - CL_CHECK(clReleaseProgram(prg)); -} - -void loadyuv_destroy(LoadYUVState* s) { - CL_CHECK(clReleaseKernel(s->loadys_krnl)); - CL_CHECK(clReleaseKernel(s->loaduv_krnl)); - CL_CHECK(clReleaseKernel(s->copy_krnl)); -} - -void loadyuv_queue(LoadYUVState* s, cl_command_queue q, - cl_mem y_cl, cl_mem u_cl, cl_mem v_cl, - cl_mem out_cl) { - cl_int global_out_off = 0; - - CL_CHECK(clSetKernelArg(s->loadys_krnl, 0, sizeof(cl_mem), &y_cl)); - CL_CHECK(clSetKernelArg(s->loadys_krnl, 1, sizeof(cl_mem), &out_cl)); - CL_CHECK(clSetKernelArg(s->loadys_krnl, 2, sizeof(cl_int), &global_out_off)); - - const size_t loadys_work_size = (s->width*s->height)/8; - CL_CHECK(clEnqueueNDRangeKernel(q, s->loadys_krnl, 1, NULL, - &loadys_work_size, NULL, 0, 0, NULL)); - - const size_t loaduv_work_size = ((s->width/2)*(s->height/2))/8; - global_out_off += (s->width*s->height); - - CL_CHECK(clSetKernelArg(s->loaduv_krnl, 0, sizeof(cl_mem), &u_cl)); - CL_CHECK(clSetKernelArg(s->loaduv_krnl, 1, sizeof(cl_mem), &out_cl)); - CL_CHECK(clSetKernelArg(s->loaduv_krnl, 2, sizeof(cl_int), &global_out_off)); - - CL_CHECK(clEnqueueNDRangeKernel(q, s->loaduv_krnl, 1, NULL, - &loaduv_work_size, NULL, 0, 0, NULL)); - - global_out_off += (s->width/2)*(s->height/2); - - CL_CHECK(clSetKernelArg(s->loaduv_krnl, 0, sizeof(cl_mem), &v_cl)); - CL_CHECK(clSetKernelArg(s->loaduv_krnl, 1, sizeof(cl_mem), &out_cl)); - CL_CHECK(clSetKernelArg(s->loaduv_krnl, 2, sizeof(cl_int), &global_out_off)); - - CL_CHECK(clEnqueueNDRangeKernel(q, s->loaduv_krnl, 1, NULL, - &loaduv_work_size, NULL, 0, 0, NULL)); -} - -void copy_queue(LoadYUVState* s, cl_command_queue q, cl_mem src, cl_mem dst, - size_t src_offset, size_t dst_offset, size_t size) { - CL_CHECK(clSetKernelArg(s->copy_krnl, 0, sizeof(cl_mem), &src)); - CL_CHECK(clSetKernelArg(s->copy_krnl, 1, sizeof(cl_mem), &dst)); - CL_CHECK(clSetKernelArg(s->copy_krnl, 2, sizeof(cl_int), &src_offset)); - CL_CHECK(clSetKernelArg(s->copy_krnl, 3, sizeof(cl_int), &dst_offset)); - const size_t copy_work_size = size/8; - CL_CHECK(clEnqueueNDRangeKernel(q, s->copy_krnl, 1, NULL, - ©_work_size, NULL, 0, 0, NULL)); -} \ No newline at end of file diff --git a/selfdrive/modeld/transforms/loadyuv.cl b/selfdrive/modeld/transforms/loadyuv.cl deleted file mode 100644 index 970187a6d..000000000 --- a/selfdrive/modeld/transforms/loadyuv.cl +++ /dev/null @@ -1,47 +0,0 @@ -#define UV_SIZE ((TRANSFORMED_WIDTH/2)*(TRANSFORMED_HEIGHT/2)) - -__kernel void loadys(__global uchar8 const * const Y, - __global uchar * out, - int out_offset) -{ - const int gid = get_global_id(0); - const int ois = gid * 8; - const int oy = ois / TRANSFORMED_WIDTH; - const int ox = ois % TRANSFORMED_WIDTH; - - const uchar8 ys = Y[gid]; - - // 02 - // 13 - - __global uchar* outy0; - __global uchar* outy1; - if ((oy & 1) == 0) { - outy0 = out + out_offset; //y0 - outy1 = out + out_offset + UV_SIZE*2; //y2 - } else { - outy0 = out + out_offset + UV_SIZE; //y1 - outy1 = out + out_offset + UV_SIZE*3; //y3 - } - - vstore4(ys.s0246, 0, outy0 + (oy/2) * (TRANSFORMED_WIDTH/2) + ox/2); - vstore4(ys.s1357, 0, outy1 + (oy/2) * (TRANSFORMED_WIDTH/2) + ox/2); -} - -__kernel void loaduv(__global uchar8 const * const in, - __global uchar8 * out, - int out_offset) -{ - const int gid = get_global_id(0); - const uchar8 inv = in[gid]; - out[gid + out_offset / 8] = inv; -} - -__kernel void copy(__global uchar8 * in, - __global uchar8 * out, - int in_offset, - int out_offset) -{ - const int gid = get_global_id(0); - out[gid + out_offset / 8] = in[gid + in_offset / 8]; -} diff --git a/selfdrive/modeld/transforms/loadyuv.h b/selfdrive/modeld/transforms/loadyuv.h deleted file mode 100644 index 659059cd2..000000000 --- a/selfdrive/modeld/transforms/loadyuv.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include "common/clutil.h" - -typedef struct { - int width, height; - cl_kernel loadys_krnl, loaduv_krnl, copy_krnl; -} LoadYUVState; - -void loadyuv_init(LoadYUVState* s, cl_context ctx, cl_device_id device_id, int width, int height); - -void loadyuv_destroy(LoadYUVState* s); - -void loadyuv_queue(LoadYUVState* s, cl_command_queue q, - cl_mem y_cl, cl_mem u_cl, cl_mem v_cl, - cl_mem out_cl); - - -void copy_queue(LoadYUVState* s, cl_command_queue q, cl_mem src, cl_mem dst, - size_t src_offset, size_t dst_offset, size_t size); \ No newline at end of file diff --git a/selfdrive/modeld/transforms/transform.cc b/selfdrive/modeld/transforms/transform.cc deleted file mode 100644 index 305643cf4..000000000 --- a/selfdrive/modeld/transforms/transform.cc +++ /dev/null @@ -1,97 +0,0 @@ -#include "selfdrive/modeld/transforms/transform.h" - -#include -#include - -#include "common/clutil.h" - -void transform_init(Transform* s, cl_context ctx, cl_device_id device_id) { - memset(s, 0, sizeof(*s)); - - cl_program prg = cl_program_from_file(ctx, device_id, TRANSFORM_PATH, ""); - s->krnl = CL_CHECK_ERR(clCreateKernel(prg, "warpPerspective", &err)); - // done with this - CL_CHECK(clReleaseProgram(prg)); - - s->m_y_cl = CL_CHECK_ERR(clCreateBuffer(ctx, CL_MEM_READ_WRITE, 3*3*sizeof(float), NULL, &err)); - s->m_uv_cl = CL_CHECK_ERR(clCreateBuffer(ctx, CL_MEM_READ_WRITE, 3*3*sizeof(float), NULL, &err)); -} - -void transform_destroy(Transform* s) { - CL_CHECK(clReleaseMemObject(s->m_y_cl)); - CL_CHECK(clReleaseMemObject(s->m_uv_cl)); - CL_CHECK(clReleaseKernel(s->krnl)); -} - -void transform_queue(Transform* s, - cl_command_queue q, - cl_mem in_yuv, int in_width, int in_height, int in_stride, int in_uv_offset, - cl_mem out_y, cl_mem out_u, cl_mem out_v, - int out_width, int out_height, - const mat3& projection) { - const int zero = 0; - - // sampled using pixel center origin - // (because that's how fastcv and opencv does it) - - mat3 projection_y = projection; - - // in and out uv is half the size of y. - mat3 projection_uv = transform_scale_buffer(projection, 0.5); - - CL_CHECK(clEnqueueWriteBuffer(q, s->m_y_cl, CL_TRUE, 0, 3*3*sizeof(float), (void*)projection_y.v, 0, NULL, NULL)); - CL_CHECK(clEnqueueWriteBuffer(q, s->m_uv_cl, CL_TRUE, 0, 3*3*sizeof(float), (void*)projection_uv.v, 0, NULL, NULL)); - - const int in_y_width = in_width; - const int in_y_height = in_height; - const int in_y_px_stride = 1; - const int in_uv_width = in_width/2; - const int in_uv_height = in_height/2; - const int in_uv_px_stride = 2; - const int in_u_offset = in_uv_offset; - const int in_v_offset = in_uv_offset + 1; - - const int out_y_width = out_width; - const int out_y_height = out_height; - const int out_uv_width = out_width/2; - const int out_uv_height = out_height/2; - - CL_CHECK(clSetKernelArg(s->krnl, 0, sizeof(cl_mem), &in_yuv)); // src - CL_CHECK(clSetKernelArg(s->krnl, 1, sizeof(cl_int), &in_stride)); // src_row_stride - CL_CHECK(clSetKernelArg(s->krnl, 2, sizeof(cl_int), &in_y_px_stride)); // src_px_stride - CL_CHECK(clSetKernelArg(s->krnl, 3, sizeof(cl_int), &zero)); // src_offset - CL_CHECK(clSetKernelArg(s->krnl, 4, sizeof(cl_int), &in_y_height)); // src_rows - CL_CHECK(clSetKernelArg(s->krnl, 5, sizeof(cl_int), &in_y_width)); // src_cols - CL_CHECK(clSetKernelArg(s->krnl, 6, sizeof(cl_mem), &out_y)); // dst - CL_CHECK(clSetKernelArg(s->krnl, 7, sizeof(cl_int), &out_y_width)); // dst_row_stride - CL_CHECK(clSetKernelArg(s->krnl, 8, sizeof(cl_int), &zero)); // dst_offset - CL_CHECK(clSetKernelArg(s->krnl, 9, sizeof(cl_int), &out_y_height)); // dst_rows - CL_CHECK(clSetKernelArg(s->krnl, 10, sizeof(cl_int), &out_y_width)); // dst_cols - CL_CHECK(clSetKernelArg(s->krnl, 11, sizeof(cl_mem), &s->m_y_cl)); // M - - const size_t work_size_y[2] = {(size_t)out_y_width, (size_t)out_y_height}; - - CL_CHECK(clEnqueueNDRangeKernel(q, s->krnl, 2, NULL, - (const size_t*)&work_size_y, NULL, 0, 0, NULL)); - - const size_t work_size_uv[2] = {(size_t)out_uv_width, (size_t)out_uv_height}; - - CL_CHECK(clSetKernelArg(s->krnl, 2, sizeof(cl_int), &in_uv_px_stride)); // src_px_stride - CL_CHECK(clSetKernelArg(s->krnl, 3, sizeof(cl_int), &in_u_offset)); // src_offset - CL_CHECK(clSetKernelArg(s->krnl, 4, sizeof(cl_int), &in_uv_height)); // src_rows - CL_CHECK(clSetKernelArg(s->krnl, 5, sizeof(cl_int), &in_uv_width)); // src_cols - CL_CHECK(clSetKernelArg(s->krnl, 6, sizeof(cl_mem), &out_u)); // dst - CL_CHECK(clSetKernelArg(s->krnl, 7, sizeof(cl_int), &out_uv_width)); // dst_row_stride - CL_CHECK(clSetKernelArg(s->krnl, 8, sizeof(cl_int), &zero)); // dst_offset - CL_CHECK(clSetKernelArg(s->krnl, 9, sizeof(cl_int), &out_uv_height)); // dst_rows - CL_CHECK(clSetKernelArg(s->krnl, 10, sizeof(cl_int), &out_uv_width)); // dst_cols - CL_CHECK(clSetKernelArg(s->krnl, 11, sizeof(cl_mem), &s->m_uv_cl)); // M - - CL_CHECK(clEnqueueNDRangeKernel(q, s->krnl, 2, NULL, - (const size_t*)&work_size_uv, NULL, 0, 0, NULL)); - CL_CHECK(clSetKernelArg(s->krnl, 3, sizeof(cl_int), &in_v_offset)); // src_ofset - CL_CHECK(clSetKernelArg(s->krnl, 6, sizeof(cl_mem), &out_v)); // dst - - CL_CHECK(clEnqueueNDRangeKernel(q, s->krnl, 2, NULL, - (const size_t*)&work_size_uv, NULL, 0, 0, NULL)); -} diff --git a/selfdrive/modeld/transforms/transform.cl b/selfdrive/modeld/transforms/transform.cl deleted file mode 100644 index 2ca25920c..000000000 --- a/selfdrive/modeld/transforms/transform.cl +++ /dev/null @@ -1,54 +0,0 @@ -#define INTER_BITS 5 -#define INTER_TAB_SIZE (1 << INTER_BITS) -#define INTER_SCALE 1.f / INTER_TAB_SIZE - -#define INTER_REMAP_COEF_BITS 15 -#define INTER_REMAP_COEF_SCALE (1 << INTER_REMAP_COEF_BITS) - -__kernel void warpPerspective(__global const uchar * src, - int src_row_stride, int src_px_stride, int src_offset, int src_rows, int src_cols, - __global uchar * dst, - int dst_row_stride, int dst_offset, int dst_rows, int dst_cols, - __constant float * M) -{ - int dx = get_global_id(0); - int dy = get_global_id(1); - - if (dx < dst_cols && dy < dst_rows) - { - float X0 = M[0] * dx + M[1] * dy + M[2]; - float Y0 = M[3] * dx + M[4] * dy + M[5]; - float W = M[6] * dx + M[7] * dy + M[8]; - W = W != 0.0f ? INTER_TAB_SIZE / W : 0.0f; - int X = rint(X0 * W), Y = rint(Y0 * W); - - int sx = convert_short_sat(X >> INTER_BITS); - int sy = convert_short_sat(Y >> INTER_BITS); - - short sx_clamp = clamp(sx, 0, src_cols - 1); - short sx_p1_clamp = clamp(sx + 1, 0, src_cols - 1); - short sy_clamp = clamp(sy, 0, src_rows - 1); - short sy_p1_clamp = clamp(sy + 1, 0, src_rows - 1); - int v0 = convert_int(src[mad24(sy_clamp, src_row_stride, src_offset + sx_clamp*src_px_stride)]); - int v1 = convert_int(src[mad24(sy_clamp, src_row_stride, src_offset + sx_p1_clamp*src_px_stride)]); - int v2 = convert_int(src[mad24(sy_p1_clamp, src_row_stride, src_offset + sx_clamp*src_px_stride)]); - int v3 = convert_int(src[mad24(sy_p1_clamp, src_row_stride, src_offset + sx_p1_clamp*src_px_stride)]); - - short ay = (short)(Y & (INTER_TAB_SIZE - 1)); - short ax = (short)(X & (INTER_TAB_SIZE - 1)); - float taby = 1.f/INTER_TAB_SIZE*ay; - float tabx = 1.f/INTER_TAB_SIZE*ax; - - int dst_index = mad24(dy, dst_row_stride, dst_offset + dx); - - int itab0 = convert_short_sat_rte( (1.0f-taby)*(1.0f-tabx) * INTER_REMAP_COEF_SCALE ); - int itab1 = convert_short_sat_rte( (1.0f-taby)*tabx * INTER_REMAP_COEF_SCALE ); - int itab2 = convert_short_sat_rte( taby*(1.0f-tabx) * INTER_REMAP_COEF_SCALE ); - int itab3 = convert_short_sat_rte( taby*tabx * INTER_REMAP_COEF_SCALE ); - - int val = v0 * itab0 + v1 * itab1 + v2 * itab2 + v3 * itab3; - - uchar pix = convert_uchar_sat((val + (1 << (INTER_REMAP_COEF_BITS-1))) >> INTER_REMAP_COEF_BITS); - dst[dst_index] = pix; - } -} diff --git a/selfdrive/modeld/transforms/transform.h b/selfdrive/modeld/transforms/transform.h deleted file mode 100644 index 771a7054b..000000000 --- a/selfdrive/modeld/transforms/transform.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -#define CL_USE_DEPRECATED_OPENCL_1_2_APIS -#ifdef __APPLE__ -#include -#else -#include -#endif - -#include "common/mat.h" - -typedef struct { - cl_kernel krnl; - cl_mem m_y_cl, m_uv_cl; -} Transform; - -void transform_init(Transform* s, cl_context ctx, cl_device_id device_id); - -void transform_destroy(Transform* transform); - -void transform_queue(Transform* s, cl_command_queue q, - cl_mem yuv, int in_width, int in_height, int in_stride, int in_uv_offset, - cl_mem out_y, cl_mem out_u, cl_mem out_v, - int out_width, int out_height, - const mat3& projection); diff --git a/selfdrive/test/process_replay/model_replay.py b/selfdrive/test/process_replay/model_replay.py index 9ba599bac..c24232840 100755 --- a/selfdrive/test/process_replay/model_replay.py +++ b/selfdrive/test/process_replay/model_replay.py @@ -34,7 +34,7 @@ GITHUB = GithubUtils(API_TOKEN, DATA_TOKEN) EXEC_TIMINGS = [ # model, instant max, average max - ("modelV2", 0.035, 0.025), + ("modelV2", 0.035, 0.028), ("driverStateV2", 0.02, 0.015), ] diff --git a/selfdrive/test/process_replay/process_replay.py b/selfdrive/test/process_replay/process_replay.py index 8af72e5f4..5143334bc 100755 --- a/selfdrive/test/process_replay/process_replay.py +++ b/selfdrive/test/process_replay/process_replay.py @@ -4,6 +4,7 @@ import time import copy import heapq import signal +import numpy as np from collections import Counter from dataclasses import dataclass, field from itertools import islice @@ -23,6 +24,7 @@ from openpilot.common.params import Params from openpilot.common.prefix import OpenpilotPrefix from openpilot.common.timeout import Timeout from openpilot.common.realtime import DT_CTRL +from openpilot.system.camerad.cameras.nv12_info import get_nv12_info from openpilot.system.manager.process_config import managed_processes from openpilot.selfdrive.test.process_replay.vision_meta import meta_from_camera_state, available_streams from openpilot.selfdrive.test.process_replay.migration import migrate_all @@ -203,7 +205,8 @@ class ProcessContainer: if meta.camera_state in self.cfg.vision_pubs: assert frs[meta.camera_state].pix_fmt == 'nv12' frame_size = (frs[meta.camera_state].w, frs[meta.camera_state].h) - vipc_server.create_buffers(meta.stream, 2, *frame_size) + stride, y_height, _, yuv_size = get_nv12_info(frame_size[0], frame_size[1]) + vipc_server.create_buffers_with_sizes(meta.stream, 2, frame_size[0], frame_size[1], yuv_size, stride, stride * y_height) vipc_server.start_listener() self.vipc_server = vipc_server @@ -300,7 +303,15 @@ class ProcessContainer: camera_meta = meta_from_camera_state(m.which()) assert frs is not None img = frs[m.which()].get(camera_state.frameId) - self.vipc_server.send(camera_meta.stream, img.flatten().tobytes(), + + h, w = frs[m.which()].h, frs[m.which()].w + stride, y_height, _, yuv_size = get_nv12_info(w, h) + uv_offset = stride * y_height + padded_img = np.zeros((yuv_size), dtype=np.uint8).reshape((-1, stride)) + padded_img[:h, :w] = img[:h * w].reshape((-1, w)) + padded_img[uv_offset // stride:uv_offset // stride + h // 2, :w] = img[h * w:].reshape((-1, w)) + + self.vipc_server.send(camera_meta.stream, padded_img.flatten().tobytes(), camera_state.frameId, camera_state.timestampSof, camera_state.timestampEof) self.msg_queue = [] From 12597856dae9442357c45dd9857542ca6b4b8ada Mon Sep 17 00:00:00 2001 From: felsager <76905857+felsager@users.noreply.github.com> Date: Fri, 6 Feb 2026 14:26:20 -0800 Subject: [PATCH 840/910] long mpc: state name before subscript (#37101) --- .../controls/lib/longitudinal_mpc_lib/long_mpc.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py index e75705cb6..efdef9dd7 100755 --- a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py +++ b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py @@ -108,10 +108,10 @@ def gen_long_model(): a_min = SX.sym('a_min') a_max = SX.sym('a_max') x_obstacle = SX.sym('x_obstacle') - prev_a = SX.sym('prev_a') + a_prev = SX.sym('a_prev') lead_t_follow = SX.sym('lead_t_follow') lead_danger_factor = SX.sym('lead_danger_factor') - model.p = vertcat(a_min, a_max, x_obstacle, prev_a, lead_t_follow, lead_danger_factor) + model.p = vertcat(a_min, a_max, x_obstacle, a_prev, lead_t_follow, lead_danger_factor) # dynamics model f_expl = vertcat(v_ego, a_ego, j_ego) @@ -143,7 +143,7 @@ def gen_long_ocp(): a_min, a_max = ocp.model.p[0], ocp.model.p[1] x_obstacle = ocp.model.p[2] - prev_a = ocp.model.p[3] + a_prev = ocp.model.p[3] lead_t_follow = ocp.model.p[4] lead_danger_factor = ocp.model.p[5] @@ -160,7 +160,7 @@ def gen_long_ocp(): x_ego, v_ego, a_ego, - a_ego - prev_a, + a_ego - a_prev, j_ego] ocp.model.cost_y_expr = vertcat(*costs) ocp.model.cost_y_expr_e = vertcat(*costs[:-1]) @@ -228,7 +228,7 @@ class LongitudinalMpc: self.v_solution = np.zeros(N+1) self.a_solution = np.zeros(N+1) self.j_solution = np.zeros(N) - self.prev_a = np.array(self.a_solution) + self.a_prev = np.array(self.a_solution) self.yref = np.zeros((N+1, COST_DIM)) for i in range(N): @@ -346,7 +346,7 @@ class LongitudinalMpc: self.params[:,0] = ACCEL_MIN self.params[:,1] = ACCEL_MAX self.params[:,2] = np.min(x_obstacles, axis=1) - self.params[:,3] = np.copy(self.prev_a) + self.params[:,3] = np.copy(self.a_prev) self.params[:,4] = t_follow self.params[:,5] = LEAD_DANGER_FACTOR @@ -378,7 +378,7 @@ class LongitudinalMpc: self.a_solution = self.x_sol[:,2] self.j_solution = self.u_sol[:,0] - self.prev_a = np.interp(T_IDXS + self.dt, T_IDXS, self.a_solution) + self.a_prev = np.interp(T_IDXS + self.dt, T_IDXS, self.a_solution) t = time.monotonic() if self.solution_status != 0: From 96fded03992bd418099e0c19bb404d371b14966c Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 6 Feb 2026 15:13:08 -0800 Subject: [PATCH 841/910] clip: clean up imports (#37104) * wtf is this * don't break import timing * they are the same * clean up * good catch --- tools/clip/run.py | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/tools/clip/run.py b/tools/clip/run.py index 679bb241c..13f591eb4 100755 --- a/tools/clip/run.py +++ b/tools/clip/run.py @@ -226,11 +226,11 @@ def load_route_metadata(route): } -def draw_text_box(rl, text, x, y, size, gui_app, font, font_scale, color=None, center=False): +def draw_text_box(text, x, y, size, gui_app, font, color=None, center=False): + import pyray as rl + from openpilot.system.ui.lib.text_measure import measure_text_cached box_color, text_color = rl.Color(0, 0, 0, 85), color or rl.WHITE - # measure_text_ex is NOT auto-scaled, so multiply by font_scale - # draw_text_ex IS auto-scaled, so pass size directly - text_size = rl.measure_text_ex(font, text, size * font_scale, 0) + text_size = measure_text_cached(font, text, size) text_width, text_height = int(text_size.x), int(text_size.y) if center: x = (gui_app.width - text_width) // 2 @@ -238,7 +238,8 @@ def draw_text_box(rl, text, x, y, size, gui_app, font, font_scale, color=None, c rl.draw_text_ex(font, text, rl.Vector2(x, y), size, 0, text_color) -def _wrap_text_by_delimiter(text: str, rl, font, font_size: int, font_scale: float, max_width: int, delimiter: str = ", ") -> list[str]: +def _wrap_text_by_delimiter(text: str, font, font_size: int, max_width: int, delimiter: str = ", ") -> list[str]: + from openpilot.system.ui.lib.text_measure import measure_text_cached """Wrap text by splitting on delimiter when it exceeds max_width.""" words = text.split(delimiter) lines: list[str] = [] @@ -248,7 +249,7 @@ def _wrap_text_by_delimiter(text: str, rl, font, font_size: int, font_scale: flo current_line.append(word) check_line = delimiter.join(current_line) # Check if line exceeds max width - if rl.measure_text_ex(font, check_line, font_size * font_scale, 0).x > max_width: + if measure_text_cached(font, check_line, font_size).x > max_width: current_line.pop() # Line is too long, move word to next line if current_line: lines.append(delimiter.join(current_line)) @@ -259,7 +260,8 @@ def _wrap_text_by_delimiter(text: str, rl, font, font_size: int, font_scale: flo return lines -def render_overlays(rl, gui_app, font, font_scale, big, metadata, title, start_time, frame_idx, show_metadata, show_time): +def render_overlays(gui_app, font, big, metadata, title, start_time, frame_idx, show_metadata, show_time): + from openpilot.system.ui.lib.text_measure import measure_text_cached metadata_size = 16 if big else 12 title_size = 32 if big else 24 time_size = 24 if big else 16 @@ -269,8 +271,8 @@ def render_overlays(rl, gui_app, font, font_scale, big, metadata, title, start_t if show_time: t = start_time + frame_idx / FRAMERATE time_text = f"{int(t) // 60:02d}:{int(t) % 60:02d}" - time_width = int(rl.measure_text_ex(font, time_text, time_size * font_scale, 0).x) - draw_text_box(rl, time_text, gui_app.width - time_width - 5, 0, time_size, gui_app, font, font_scale) + time_width = int(measure_text_cached(font, time_text, time_size).x) + draw_text_box(time_text, gui_app.width - time_width - 5, 0, time_size, gui_app, font) # Metadata overlay (first 5 seconds) if show_metadata and metadata and frame_idx < FRAMERATE * 5: @@ -280,18 +282,18 @@ def render_overlays(rl, gui_app, font, font_scale, big, metadata, title, start_t # Wrap text if too wide (leave margin on each side) margin = 2 * (time_width + 10 if show_time else 20) # leave enough margin for time overlay max_width = gui_app.width - margin - lines = _wrap_text_by_delimiter(text, rl, font, metadata_size, font_scale, max_width) + lines = _wrap_text_by_delimiter(text, font, metadata_size, max_width) # Draw wrapped metadata text y_offset = 6 for line in lines: - draw_text_box(rl, line, 0, y_offset, metadata_size, gui_app, font, font_scale, center=True) - line_height = int(rl.measure_text_ex(font, line, metadata_size * font_scale, 0).y) + 4 + draw_text_box(line, 0, y_offset, metadata_size, gui_app, font, center=True) + line_height = int(measure_text_cached(font, line, metadata_size).y) + 4 y_offset += line_height # Title overlay if title: - draw_text_box(rl, title, 0, 60, title_size, gui_app, font, font_scale, center=True) + draw_text_box(title, 0, 60, title_size, gui_app, font, center=True) def clip(route: Route, output: str, start: int, end: int, headless: bool = True, big: bool = False, @@ -304,7 +306,7 @@ def clip(route: Route, output: str, start: int, end: int, headless: bool = True, else: from openpilot.selfdrive.ui.mici.onroad.augmented_road_view import AugmentedRoadView # type: ignore[assignment] from openpilot.selfdrive.ui.ui_state import ui_state - from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE + from openpilot.system.ui.lib.application import gui_app, FontWeight timer.lap("import") logger.info(f"Clipping {route.name.canonical_name}, {start}s-{end}s ({duration}s)") @@ -348,7 +350,7 @@ def clip(route: Route, output: str, start: int, end: int, headless: bool = True, ui_state.update() if should_render: road_view.render() - render_overlays(rl, gui_app, font, FONT_SCALE, big, metadata, title, start, frame_idx, show_metadata, show_time) + render_overlays(gui_app, font, big, metadata, title, start, frame_idx, show_metadata, show_time) frame_idx += 1 pbar.update(1) timer.lap("render") From 4ce701150a3f932aca210e3ff372ffcdb2a23e83 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 6 Feb 2026 16:06:16 -0800 Subject: [PATCH 842/910] rm common/mat.h --- common/mat.h | 85 ---------------------------------------------------- 1 file changed, 85 deletions(-) delete mode 100644 common/mat.h diff --git a/common/mat.h b/common/mat.h deleted file mode 100644 index 8e10d6197..000000000 --- a/common/mat.h +++ /dev/null @@ -1,85 +0,0 @@ -#pragma once - -typedef struct vec3 { - float v[3]; -} vec3; - -typedef struct vec4 { - float v[4]; -} vec4; - -typedef struct mat3 { - float v[3*3]; -} mat3; - -typedef struct mat4 { - float v[4*4]; -} mat4; - -static inline mat3 matmul3(const mat3 &a, const mat3 &b) { - mat3 ret = {{0.0}}; - for (int r=0; r<3; r++) { - for (int c=0; c<3; c++) { - float v = 0.0; - for (int k=0; k<3; k++) { - v += a.v[r*3+k] * b.v[k*3+c]; - } - ret.v[r*3+c] = v; - } - } - return ret; -} - -static inline vec3 matvecmul3(const mat3 &a, const vec3 &b) { - vec3 ret = {{0.0}}; - for (int r=0; r<3; r++) { - for (int c=0; c<3; c++) { - ret.v[r] += a.v[r*3+c] * b.v[c]; - } - } - return ret; -} - -static inline mat4 matmul(const mat4 &a, const mat4 &b) { - mat4 ret = {{0.0}}; - for (int r=0; r<4; r++) { - for (int c=0; c<4; c++) { - float v = 0.0; - for (int k=0; k<4; k++) { - v += a.v[r*4+k] * b.v[k*4+c]; - } - ret.v[r*4+c] = v; - } - } - return ret; -} - -static inline vec4 matvecmul(const mat4 &a, const vec4 &b) { - vec4 ret = {{0.0}}; - for (int r=0; r<4; r++) { - for (int c=0; c<4; c++) { - ret.v[r] += a.v[r*4+c] * b.v[c]; - } - } - return ret; -} - -// scales the input and output space of a transformation matrix -// that assumes pixel-center origin. -static inline mat3 transform_scale_buffer(const mat3 &in, float s) { - // in_pt = ( transform(out_pt/s + 0.5) - 0.5) * s - - mat3 transform_out = {{ - 1.0f/s, 0.0f, 0.5f, - 0.0f, 1.0f/s, 0.5f, - 0.0f, 0.0f, 1.0f, - }}; - - mat3 transform_in = {{ - s, 0.0f, -0.5f*s, - 0.0f, s, -0.5f*s, - 0.0f, 0.0f, 1.0f, - }}; - - return matmul3(transform_in, matmul3(in, transform_out)); -} From d5cbb89d84212bfd99d5fdd8b19f76d90b4f40e0 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 6 Feb 2026 16:36:47 -0800 Subject: [PATCH 843/910] Remove all the OpenCL (#37105) * Remove all the OpenCL * lil more * bump msgq --- Dockerfile.openpilot_base | 37 - SConstruct | 1 - common/SConscript | 1 - common/clutil.cc | 98 -- common/clutil.h | 28 - msgq_repo | 2 +- selfdrive/modeld/dmonitoringmodeld.py | 8 +- selfdrive/modeld/modeld.py | 14 +- selfdrive/modeld/runners/tinygrad_helpers.py | 8 - system/camerad/SConscript | 2 +- system/camerad/cameras/camera_common.cc | 5 +- system/camerad/cameras/camera_common.h | 2 +- system/camerad/cameras/camera_qcom2.cc | 22 +- system/camerad/cameras/spectra.cc | 4 +- system/camerad/cameras/spectra.h | 2 +- system/loggerd/SConscript | 5 +- third_party/opencl/include/CL/cl.h | 1452 ----------------- third_party/opencl/include/CL/cl_d3d10.h | 131 -- third_party/opencl/include/CL/cl_d3d11.h | 131 -- .../opencl/include/CL/cl_dx9_media_sharing.h | 132 -- third_party/opencl/include/CL/cl_egl.h | 136 -- third_party/opencl/include/CL/cl_ext.h | 391 ----- third_party/opencl/include/CL/cl_ext_qcom.h | 255 --- third_party/opencl/include/CL/cl_gl.h | 167 -- third_party/opencl/include/CL/cl_gl_ext.h | 74 - third_party/opencl/include/CL/cl_platform.h | 1333 --------------- third_party/opencl/include/CL/opencl.h | 59 - tools/cabana/SConscript | 2 - tools/install_ubuntu_dependencies.sh | 3 - tools/replay/SConscript | 5 - tools/webcam/README.md | 4 - 31 files changed, 22 insertions(+), 4492 deletions(-) delete mode 100644 common/clutil.cc delete mode 100644 common/clutil.h delete mode 100644 selfdrive/modeld/runners/tinygrad_helpers.py delete mode 100644 third_party/opencl/include/CL/cl.h delete mode 100644 third_party/opencl/include/CL/cl_d3d10.h delete mode 100644 third_party/opencl/include/CL/cl_d3d11.h delete mode 100644 third_party/opencl/include/CL/cl_dx9_media_sharing.h delete mode 100644 third_party/opencl/include/CL/cl_egl.h delete mode 100644 third_party/opencl/include/CL/cl_ext.h delete mode 100644 third_party/opencl/include/CL/cl_ext_qcom.h delete mode 100644 third_party/opencl/include/CL/cl_gl.h delete mode 100644 third_party/opencl/include/CL/cl_gl_ext.h delete mode 100644 third_party/opencl/include/CL/cl_platform.h delete mode 100644 third_party/opencl/include/CL/opencl.h diff --git a/Dockerfile.openpilot_base b/Dockerfile.openpilot_base index 44d8d95e9..8a6041299 100644 --- a/Dockerfile.openpilot_base +++ b/Dockerfile.openpilot_base @@ -18,43 +18,6 @@ RUN /tmp/tools/install_ubuntu_dependencies.sh && \ cd /usr/lib/gcc/arm-none-eabi/* && \ rm -rf arm/ thumb/nofp thumb/v6* thumb/v8* thumb/v7+fp thumb/v7-r+fp.sp -# Add OpenCL -RUN apt-get update && apt-get install -y --no-install-recommends \ - apt-utils \ - alien \ - unzip \ - tar \ - curl \ - xz-utils \ - dbus \ - gcc-arm-none-eabi \ - tmux \ - vim \ - libx11-6 \ - wget \ - && rm -rf /var/lib/apt/lists/* - -RUN mkdir -p /tmp/opencl-driver-intel && \ - cd /tmp/opencl-driver-intel && \ - wget https://github.com/intel/llvm/releases/download/2024-WW14/oclcpuexp-2024.17.3.0.09_rel.tar.gz && \ - wget https://github.com/oneapi-src/oneTBB/releases/download/v2021.12.0/oneapi-tbb-2021.12.0-lin.tgz && \ - mkdir -p /opt/intel/oclcpuexp_2024.17.3.0.09_rel && \ - cd /opt/intel/oclcpuexp_2024.17.3.0.09_rel && \ - tar -zxvf /tmp/opencl-driver-intel/oclcpuexp-2024.17.3.0.09_rel.tar.gz && \ - mkdir -p /etc/OpenCL/vendors && \ - echo /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64/libintelocl.so > /etc/OpenCL/vendors/intel_expcpu.icd && \ - cd /opt/intel && \ - tar -zxvf /tmp/opencl-driver-intel/oneapi-tbb-2021.12.0-lin.tgz && \ - ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbb.so /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \ - ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbbmalloc.so /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \ - ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbb.so.12 /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \ - ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbbmalloc.so.2 /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \ - mkdir -p /etc/ld.so.conf.d && \ - echo /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 > /etc/ld.so.conf.d/libintelopenclexp.conf && \ - ldconfig -f /etc/ld.so.conf.d/libintelopenclexp.conf && \ - cd / && \ - rm -rf /tmp/opencl-driver-intel - ENV NVIDIA_VISIBLE_DEVICES=all ENV NVIDIA_DRIVER_CAPABILITIES=graphics,utility,compute ENV QTWEBENGINE_DISABLE_SANDBOX=1 diff --git a/SConstruct b/SConstruct index ca5b7b6cb..4f04be624 100644 --- a/SConstruct +++ b/SConstruct @@ -94,7 +94,6 @@ env = Environment( # Arch-specific flags and paths if arch == "larch64": - env.Append(CPPPATH=["#third_party/opencl/include"]) env.Append(LIBPATH=[ "/usr/local/lib", "/system/vendor/lib64", diff --git a/common/SConscript b/common/SConscript index 1c68cf05c..15a0e5eff 100644 --- a/common/SConscript +++ b/common/SConscript @@ -5,7 +5,6 @@ common_libs = [ 'swaglog.cc', 'util.cc', 'ratekeeper.cc', - 'clutil.cc', ] _common = env.Library('common', common_libs, LIBS="json11") diff --git a/common/clutil.cc b/common/clutil.cc deleted file mode 100644 index f8381a7e0..000000000 --- a/common/clutil.cc +++ /dev/null @@ -1,98 +0,0 @@ -#include "common/clutil.h" - -#include -#include -#include - -#include "common/util.h" -#include "common/swaglog.h" - -namespace { // helper functions - -template -std::string get_info(Func get_info_func, Id id, Name param_name) { - size_t size = 0; - CL_CHECK(get_info_func(id, param_name, 0, NULL, &size)); - std::string info(size, '\0'); - CL_CHECK(get_info_func(id, param_name, size, info.data(), NULL)); - return info; -} -inline std::string get_platform_info(cl_platform_id id, cl_platform_info name) { return get_info(&clGetPlatformInfo, id, name); } -inline std::string get_device_info(cl_device_id id, cl_device_info name) { return get_info(&clGetDeviceInfo, id, name); } - -void cl_print_info(cl_platform_id platform, cl_device_id device) { - size_t work_group_size = 0; - cl_device_type device_type = 0; - clGetDeviceInfo(device, CL_DEVICE_MAX_WORK_GROUP_SIZE, sizeof(work_group_size), &work_group_size, NULL); - clGetDeviceInfo(device, CL_DEVICE_TYPE, sizeof(device_type), &device_type, NULL); - const char *type_str = "Other..."; - switch (device_type) { - case CL_DEVICE_TYPE_CPU: type_str ="CL_DEVICE_TYPE_CPU"; break; - case CL_DEVICE_TYPE_GPU: type_str = "CL_DEVICE_TYPE_GPU"; break; - case CL_DEVICE_TYPE_ACCELERATOR: type_str = "CL_DEVICE_TYPE_ACCELERATOR"; break; - } - - LOGD("vendor: %s", get_platform_info(platform, CL_PLATFORM_VENDOR).c_str()); - LOGD("platform version: %s", get_platform_info(platform, CL_PLATFORM_VERSION).c_str()); - LOGD("profile: %s", get_platform_info(platform, CL_PLATFORM_PROFILE).c_str()); - LOGD("extensions: %s", get_platform_info(platform, CL_PLATFORM_EXTENSIONS).c_str()); - LOGD("name: %s", get_device_info(device, CL_DEVICE_NAME).c_str()); - LOGD("device version: %s", get_device_info(device, CL_DEVICE_VERSION).c_str()); - LOGD("max work group size: %zu", work_group_size); - LOGD("type = %d, %s", (int)device_type, type_str); -} - -void cl_print_build_errors(cl_program program, cl_device_id device) { - cl_build_status status; - clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_STATUS, sizeof(status), &status, NULL); - size_t log_size; - clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, 0, NULL, &log_size); - std::string log(log_size, '\0'); - clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, log_size, &log[0], NULL); - - LOGE("build failed; status=%d, log: %s", status, log.c_str()); -} - -} // namespace - -cl_device_id cl_get_device_id(cl_device_type device_type) { - cl_uint num_platforms = 0; - CL_CHECK(clGetPlatformIDs(0, NULL, &num_platforms)); - std::unique_ptr platform_ids = std::make_unique(num_platforms); - CL_CHECK(clGetPlatformIDs(num_platforms, &platform_ids[0], NULL)); - - for (size_t i = 0; i < num_platforms; ++i) { - LOGD("platform[%zu] CL_PLATFORM_NAME: %s", i, get_platform_info(platform_ids[i], CL_PLATFORM_NAME).c_str()); - - // Get first device - if (cl_device_id device_id = NULL; clGetDeviceIDs(platform_ids[i], device_type, 1, &device_id, NULL) == 0 && device_id) { - cl_print_info(platform_ids[i], device_id); - return device_id; - } - } - LOGE("No valid openCL platform found"); - assert(0); - return nullptr; -} - -cl_context cl_create_context(cl_device_id device_id) { - return CL_CHECK_ERR(clCreateContext(NULL, 1, &device_id, NULL, NULL, &err)); -} - -void cl_release_context(cl_context context) { - clReleaseContext(context); -} - -cl_program cl_program_from_file(cl_context ctx, cl_device_id device_id, const char* path, const char* args) { - return cl_program_from_source(ctx, device_id, util::read_file(path), args); -} - -cl_program cl_program_from_source(cl_context ctx, cl_device_id device_id, const std::string& src, const char* args) { - const char *csrc = src.c_str(); - cl_program prg = CL_CHECK_ERR(clCreateProgramWithSource(ctx, 1, &csrc, NULL, &err)); - if (int err = clBuildProgram(prg, 1, &device_id, args, NULL, NULL); err != 0) { - cl_print_build_errors(prg, device_id); - assert(0); - } - return prg; -} diff --git a/common/clutil.h b/common/clutil.h deleted file mode 100644 index b364e79d4..000000000 --- a/common/clutil.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -#ifdef __APPLE__ -#include -#else -#include -#endif - -#include - -#define CL_CHECK(_expr) \ - do { \ - assert(CL_SUCCESS == (_expr)); \ - } while (0) - -#define CL_CHECK_ERR(_expr) \ - ({ \ - cl_int err = CL_INVALID_VALUE; \ - __typeof__(_expr) _ret = _expr; \ - assert(_ret&& err == CL_SUCCESS); \ - _ret; \ - }) - -cl_device_id cl_get_device_id(cl_device_type device_type); -cl_context cl_create_context(cl_device_id device_id); -void cl_release_context(cl_context context); -cl_program cl_program_from_source(cl_context ctx, cl_device_id device_id, const std::string& src, const char* args = nullptr); -cl_program cl_program_from_file(cl_context ctx, cl_device_id device_id, const char* path, const char* args); diff --git a/msgq_repo b/msgq_repo index 20f249385..f9ebdca88 160000 --- a/msgq_repo +++ b/msgq_repo @@ -1 +1 @@ -Subproject commit 20f2493855ef32339b80f0ad76b3cb82210dc474 +Subproject commit f9ebdca885cfe25295d09de9fd57023a10758de5 diff --git a/selfdrive/modeld/dmonitoringmodeld.py b/selfdrive/modeld/dmonitoringmodeld.py index f12c13081..7d4df713c 100755 --- a/selfdrive/modeld/dmonitoringmodeld.py +++ b/selfdrive/modeld/dmonitoringmodeld.py @@ -11,7 +11,6 @@ from pathlib import Path from cereal import messaging from cereal.messaging import PubMaster, SubMaster from msgq.visionipc import VisionIpcClient, VisionStreamType, VisionBuf -from msgq.visionipc.visionipc_pyx import CLContext from openpilot.common.swaglog import cloudlog from openpilot.common.realtime import config_realtime_process from openpilot.common.transformations.model import dmonitoringmodel_intrinsics @@ -29,7 +28,7 @@ class ModelState: inputs: dict[str, np.ndarray] output: np.ndarray - def __init__(self, cl_ctx): + def __init__(self): with open(METADATA_PATH, 'rb') as f: model_metadata = pickle.load(f) self.input_shapes = model_metadata['input_shapes'] @@ -110,12 +109,11 @@ def get_driverstate_packet(model_output, frame_id: int, location_ts: int, exec_t def main(): config_realtime_process(7, 5) - cl_context = CLContext() - model = ModelState(cl_context) + model = ModelState() cloudlog.warning("models loaded, dmonitoringmodeld starting") cloudlog.warning("connecting to driver stream") - vipc_client = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_DRIVER, True, cl_context) + vipc_client = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_DRIVER, True) while not vipc_client.connect(False): time.sleep(0.1) assert vipc_client.is_connected() diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index d9445c8d3..7fae36510 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -15,7 +15,6 @@ from cereal import car, log from pathlib import Path from cereal.messaging import PubMaster, SubMaster from msgq.visionipc import VisionIpcClient, VisionStreamType, VisionBuf -from msgq.visionipc.visionipc_pyx import CLContext from opendbc.car.car_helpers import get_demo_car_params from openpilot.common.swaglog import cloudlog from openpilot.common.params import Params @@ -143,7 +142,7 @@ class ModelState: output: np.ndarray prev_desire: np.ndarray # for tracking the rising edge of the pulse - def __init__(self, context: CLContext): + def __init__(self): with open(VISION_METADATA_PATH, 'rb') as f: vision_metadata = pickle.load(f) self.vision_input_shapes = vision_metadata['input_shapes'] @@ -166,7 +165,6 @@ class ModelState: self.full_input_queues.update_dtypes_and_shapes({k: self.numpy_inputs[k].dtype}, {k: self.numpy_inputs[k].shape}) self.full_input_queues.reset() - # img buffers are managed in openCL transform code self.img_queues = {'img': Tensor.zeros(IMG_QUEUE_SHAPE, dtype='uint8').contiguous().realize(), 'big_img': Tensor.zeros(IMG_QUEUE_SHAPE, dtype='uint8').contiguous().realize(),} self.full_frames : dict[str, Tensor] = {} @@ -242,10 +240,8 @@ def main(demo=False): 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("loading model") + model = ModelState() cloudlog.warning(f"models loaded in {time.monotonic() - st:.1f}s, modeld starting") # visionipc clients @@ -258,8 +254,8 @@ def main(demo=False): time.sleep(.1) vipc_client_main_stream = VisionStreamType.VISION_STREAM_WIDE_ROAD if main_wide_camera else VisionStreamType.VISION_STREAM_ROAD - vipc_client_main = VisionIpcClient("camerad", vipc_client_main_stream, True, cl_context) - vipc_client_extra = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_WIDE_ROAD, False, cl_context) + vipc_client_main = VisionIpcClient("camerad", vipc_client_main_stream, True) + vipc_client_extra = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_WIDE_ROAD, False) cloudlog.warning(f"vision stream set up, main_wide_camera: {main_wide_camera}, use_extra_client: {use_extra_client}") while not vipc_client_main.connect(False): diff --git a/selfdrive/modeld/runners/tinygrad_helpers.py b/selfdrive/modeld/runners/tinygrad_helpers.py deleted file mode 100644 index 776381341..000000000 --- a/selfdrive/modeld/runners/tinygrad_helpers.py +++ /dev/null @@ -1,8 +0,0 @@ - -from tinygrad.tensor import Tensor -from tinygrad.helpers import to_mv - -def qcom_tensor_from_opencl_address(opencl_address, shape, dtype): - cl_buf_desc_ptr = to_mv(opencl_address, 8).cast('Q')[0] - rawbuf_ptr = to_mv(cl_buf_desc_ptr, 0x100).cast('Q')[20] # offset 0xA0 is a raw gpu pointer. - return Tensor.from_blob(rawbuf_ptr, shape, dtype=dtype, device='QCOM') diff --git a/system/camerad/SConscript b/system/camerad/SConscript index e288c6d8b..c28330b32 100644 --- a/system/camerad/SConscript +++ b/system/camerad/SConscript @@ -1,6 +1,6 @@ Import('env', 'arch', 'messaging', 'common', 'visionipc') -libs = [common, 'OpenCL', messaging, visionipc] +libs = [common, messaging, visionipc] if arch != "Darwin": camera_obj = env.Object(['cameras/camera_qcom2.cc', 'cameras/camera_common.cc', 'cameras/spectra.cc', diff --git a/system/camerad/cameras/camera_common.cc b/system/camerad/cameras/camera_common.cc index 88bca7f77..329192b63 100644 --- a/system/camerad/cameras/camera_common.cc +++ b/system/camerad/cameras/camera_common.cc @@ -7,7 +7,7 @@ #include "system/camerad/cameras/spectra.h" -void CameraBuf::init(cl_device_id device_id, cl_context context, SpectraCamera *cam, VisionIpcServer * v, int frame_cnt, VisionStreamType type) { +void CameraBuf::init(SpectraCamera *cam, VisionIpcServer * v, int frame_cnt, VisionStreamType type) { vipc_server = v; stream_type = type; frame_buf_count = frame_cnt; @@ -21,9 +21,8 @@ void CameraBuf::init(cl_device_id device_id, cl_context context, SpectraCamera * const int raw_frame_size = (sensor->frame_height + sensor->extra_height) * sensor->frame_stride; for (int i = 0; i < frame_buf_count; i++) { camera_bufs_raw[i].allocate(raw_frame_size); - camera_bufs_raw[i].init_cl(device_id, context); } - LOGD("allocated %d CL buffers", frame_buf_count); + LOGD("allocated %d buffers", frame_buf_count); } vipc_server->create_buffers_with_sizes(stream_type, VIPC_BUFFER_COUNT, out_img_width, out_img_height, cam->yuv_size, cam->stride, cam->uv_offset); diff --git a/system/camerad/cameras/camera_common.h b/system/camerad/cameras/camera_common.h index c26859cbc..7f35e06a8 100644 --- a/system/camerad/cameras/camera_common.h +++ b/system/camerad/cameras/camera_common.h @@ -36,7 +36,7 @@ public: CameraBuf() = default; ~CameraBuf(); - void init(cl_device_id device_id, cl_context context, SpectraCamera *cam, VisionIpcServer * v, int frame_cnt, VisionStreamType type); + void init(SpectraCamera *cam, VisionIpcServer * v, int frame_cnt, VisionStreamType type); void sendFrameToVipc(); }; diff --git a/system/camerad/cameras/camera_qcom2.cc b/system/camerad/cameras/camera_qcom2.cc index d741e13cf..6a7f599ab 100644 --- a/system/camerad/cameras/camera_qcom2.cc +++ b/system/camerad/cameras/camera_qcom2.cc @@ -12,16 +12,8 @@ #include #include -#ifdef __TICI__ -#include "CL/cl_ext_qcom.h" -#else -#define CL_PRIORITY_HINT_HIGH_QCOM NULL -#define CL_CONTEXT_PRIORITY_HINT_QCOM NULL -#endif - #include "media/cam_sensor_cmn_header.h" -#include "common/clutil.h" #include "common/params.h" #include "common/swaglog.h" @@ -57,7 +49,7 @@ public: CameraState(SpectraMaster *master, const CameraConfig &config) : camera(master, config) {}; ~CameraState(); - void init(VisionIpcServer *v, cl_device_id device_id, cl_context ctx); + void init(VisionIpcServer *v); void update_exposure_score(float desired_ev, int exp_t, int exp_g_idx, float exp_gain); void set_camera_exposure(float grey_frac); void set_exposure_rect(); @@ -68,8 +60,8 @@ public: } }; -void CameraState::init(VisionIpcServer *v, cl_device_id device_id, cl_context ctx) { - camera.camera_open(v, device_id, ctx); +void CameraState::init(VisionIpcServer *v) { + camera.camera_open(v); if (!camera.enabled) return; @@ -257,11 +249,7 @@ void CameraState::sendState() { void camerad_thread() { // TODO: centralize enabled handling - cl_device_id device_id = cl_get_device_id(CL_DEVICE_TYPE_DEFAULT); - const cl_context_properties props[] = {CL_CONTEXT_PRIORITY_HINT_QCOM, CL_PRIORITY_HINT_HIGH_QCOM, 0}; - cl_context ctx = CL_CHECK_ERR(clCreateContext(props, 1, &device_id, NULL, NULL, &err)); - - VisionIpcServer v("camerad", device_id, ctx); + VisionIpcServer v("camerad"); // *** initial ISP init *** SpectraMaster m; @@ -271,7 +259,7 @@ void camerad_thread() { std::vector> cams; for (const auto &config : ALL_CAMERA_CONFIGS) { auto cam = std::make_unique(&m, config); - cam->init(&v, device_id, ctx); + cam->init(&v); cams.emplace_back(std::move(cam)); } diff --git a/system/camerad/cameras/spectra.cc b/system/camerad/cameras/spectra.cc index 5c3e7a9d2..73e0a78da 100644 --- a/system/camerad/cameras/spectra.cc +++ b/system/camerad/cameras/spectra.cc @@ -274,7 +274,7 @@ int SpectraCamera::clear_req_queue() { return ret; } -void SpectraCamera::camera_open(VisionIpcServer *v, cl_device_id device_id, cl_context ctx) { +void SpectraCamera::camera_open(VisionIpcServer *v) { if (!openSensor()) { return; } @@ -296,7 +296,7 @@ void SpectraCamera::camera_open(VisionIpcServer *v, cl_device_id device_id, cl_c linkDevices(); LOGD("camera init %d", cc.camera_num); - buf.init(device_id, ctx, this, v, ife_buf_depth, cc.stream_type); + buf.init(this, v, ife_buf_depth, cc.stream_type); camera_map_bufs(); clearAndRequeue(1); } diff --git a/system/camerad/cameras/spectra.h b/system/camerad/cameras/spectra.h index 13cb13f98..a02b8a6ca 100644 --- a/system/camerad/cameras/spectra.h +++ b/system/camerad/cameras/spectra.h @@ -113,7 +113,7 @@ public: SpectraCamera(SpectraMaster *master, const CameraConfig &config); ~SpectraCamera(); - void camera_open(VisionIpcServer *v, cl_device_id device_id, cl_context ctx); + void camera_open(VisionIpcServer *v); bool handle_camera_event(const cam_req_mgr_message *event_data); void camera_close(); void camera_map_bufs(); diff --git a/system/loggerd/SConscript b/system/loggerd/SConscript index cf169f4dc..cc8ef7c88 100644 --- a/system/loggerd/SConscript +++ b/system/loggerd/SConscript @@ -2,16 +2,13 @@ Import('env', 'arch', 'messaging', 'common', 'visionipc') libs = [common, messaging, visionipc, 'avformat', 'avcodec', 'avutil', - 'yuv', 'OpenCL', 'pthread', 'zstd'] + 'yuv', 'pthread', 'zstd'] src = ['logger.cc', 'zstd_writer.cc', 'video_writer.cc', 'encoder/encoder.cc', 'encoder/v4l_encoder.cc', 'encoder/jpeg_encoder.cc'] if arch != "larch64": src += ['encoder/ffmpeg_encoder.cc'] if arch == "Darwin": - # fix OpenCL - del libs[libs.index('OpenCL')] - env['FRAMEWORKS'] = ['OpenCL'] # exclude v4l del src[src.index('encoder/v4l_encoder.cc')] diff --git a/third_party/opencl/include/CL/cl.h b/third_party/opencl/include/CL/cl.h deleted file mode 100644 index 0086319f5..000000000 --- a/third_party/opencl/include/CL/cl.h +++ /dev/null @@ -1,1452 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - ******************************************************************************/ - -#ifndef __OPENCL_CL_H -#define __OPENCL_CL_H - -#ifdef __APPLE__ -#include -#else -#include -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -/******************************************************************************/ - -typedef struct _cl_platform_id * cl_platform_id; -typedef struct _cl_device_id * cl_device_id; -typedef struct _cl_context * cl_context; -typedef struct _cl_command_queue * cl_command_queue; -typedef struct _cl_mem * cl_mem; -typedef struct _cl_program * cl_program; -typedef struct _cl_kernel * cl_kernel; -typedef struct _cl_event * cl_event; -typedef struct _cl_sampler * cl_sampler; - -typedef cl_uint cl_bool; /* WARNING! Unlike cl_ types in cl_platform.h, cl_bool is not guaranteed to be the same size as the bool in kernels. */ -typedef cl_ulong cl_bitfield; -typedef cl_bitfield cl_device_type; -typedef cl_uint cl_platform_info; -typedef cl_uint cl_device_info; -typedef cl_bitfield cl_device_fp_config; -typedef cl_uint cl_device_mem_cache_type; -typedef cl_uint cl_device_local_mem_type; -typedef cl_bitfield cl_device_exec_capabilities; -typedef cl_bitfield cl_device_svm_capabilities; -typedef cl_bitfield cl_command_queue_properties; -typedef intptr_t cl_device_partition_property; -typedef cl_bitfield cl_device_affinity_domain; - -typedef intptr_t cl_context_properties; -typedef cl_uint cl_context_info; -typedef cl_bitfield cl_queue_properties; -typedef cl_uint cl_command_queue_info; -typedef cl_uint cl_channel_order; -typedef cl_uint cl_channel_type; -typedef cl_bitfield cl_mem_flags; -typedef cl_bitfield cl_svm_mem_flags; -typedef cl_uint cl_mem_object_type; -typedef cl_uint cl_mem_info; -typedef cl_bitfield cl_mem_migration_flags; -typedef cl_uint cl_image_info; -typedef cl_uint cl_buffer_create_type; -typedef cl_uint cl_addressing_mode; -typedef cl_uint cl_filter_mode; -typedef cl_uint cl_sampler_info; -typedef cl_bitfield cl_map_flags; -typedef intptr_t cl_pipe_properties; -typedef cl_uint cl_pipe_info; -typedef cl_uint cl_program_info; -typedef cl_uint cl_program_build_info; -typedef cl_uint cl_program_binary_type; -typedef cl_int cl_build_status; -typedef cl_uint cl_kernel_info; -typedef cl_uint cl_kernel_arg_info; -typedef cl_uint cl_kernel_arg_address_qualifier; -typedef cl_uint cl_kernel_arg_access_qualifier; -typedef cl_bitfield cl_kernel_arg_type_qualifier; -typedef cl_uint cl_kernel_work_group_info; -typedef cl_uint cl_kernel_sub_group_info; -typedef cl_uint cl_event_info; -typedef cl_uint cl_command_type; -typedef cl_uint cl_profiling_info; -typedef cl_bitfield cl_sampler_properties; -typedef cl_uint cl_kernel_exec_info; - -typedef struct _cl_image_format { - cl_channel_order image_channel_order; - cl_channel_type image_channel_data_type; -} cl_image_format; - -typedef struct _cl_image_desc { - cl_mem_object_type image_type; - size_t image_width; - size_t image_height; - size_t image_depth; - size_t image_array_size; - size_t image_row_pitch; - size_t image_slice_pitch; - cl_uint num_mip_levels; - cl_uint num_samples; -#ifdef __GNUC__ - __extension__ /* Prevents warnings about anonymous union in -pedantic builds */ -#endif - union { - cl_mem buffer; - cl_mem mem_object; - }; -} cl_image_desc; - -typedef struct _cl_buffer_region { - size_t origin; - size_t size; -} cl_buffer_region; - - -/******************************************************************************/ - -/* Error Codes */ -#define CL_SUCCESS 0 -#define CL_DEVICE_NOT_FOUND -1 -#define CL_DEVICE_NOT_AVAILABLE -2 -#define CL_COMPILER_NOT_AVAILABLE -3 -#define CL_MEM_OBJECT_ALLOCATION_FAILURE -4 -#define CL_OUT_OF_RESOURCES -5 -#define CL_OUT_OF_HOST_MEMORY -6 -#define CL_PROFILING_INFO_NOT_AVAILABLE -7 -#define CL_MEM_COPY_OVERLAP -8 -#define CL_IMAGE_FORMAT_MISMATCH -9 -#define CL_IMAGE_FORMAT_NOT_SUPPORTED -10 -#define CL_BUILD_PROGRAM_FAILURE -11 -#define CL_MAP_FAILURE -12 -#define CL_MISALIGNED_SUB_BUFFER_OFFSET -13 -#define CL_EXEC_STATUS_ERROR_FOR_EVENTS_IN_WAIT_LIST -14 -#define CL_COMPILE_PROGRAM_FAILURE -15 -#define CL_LINKER_NOT_AVAILABLE -16 -#define CL_LINK_PROGRAM_FAILURE -17 -#define CL_DEVICE_PARTITION_FAILED -18 -#define CL_KERNEL_ARG_INFO_NOT_AVAILABLE -19 - -#define CL_INVALID_VALUE -30 -#define CL_INVALID_DEVICE_TYPE -31 -#define CL_INVALID_PLATFORM -32 -#define CL_INVALID_DEVICE -33 -#define CL_INVALID_CONTEXT -34 -#define CL_INVALID_QUEUE_PROPERTIES -35 -#define CL_INVALID_COMMAND_QUEUE -36 -#define CL_INVALID_HOST_PTR -37 -#define CL_INVALID_MEM_OBJECT -38 -#define CL_INVALID_IMAGE_FORMAT_DESCRIPTOR -39 -#define CL_INVALID_IMAGE_SIZE -40 -#define CL_INVALID_SAMPLER -41 -#define CL_INVALID_BINARY -42 -#define CL_INVALID_BUILD_OPTIONS -43 -#define CL_INVALID_PROGRAM -44 -#define CL_INVALID_PROGRAM_EXECUTABLE -45 -#define CL_INVALID_KERNEL_NAME -46 -#define CL_INVALID_KERNEL_DEFINITION -47 -#define CL_INVALID_KERNEL -48 -#define CL_INVALID_ARG_INDEX -49 -#define CL_INVALID_ARG_VALUE -50 -#define CL_INVALID_ARG_SIZE -51 -#define CL_INVALID_KERNEL_ARGS -52 -#define CL_INVALID_WORK_DIMENSION -53 -#define CL_INVALID_WORK_GROUP_SIZE -54 -#define CL_INVALID_WORK_ITEM_SIZE -55 -#define CL_INVALID_GLOBAL_OFFSET -56 -#define CL_INVALID_EVENT_WAIT_LIST -57 -#define CL_INVALID_EVENT -58 -#define CL_INVALID_OPERATION -59 -#define CL_INVALID_GL_OBJECT -60 -#define CL_INVALID_BUFFER_SIZE -61 -#define CL_INVALID_MIP_LEVEL -62 -#define CL_INVALID_GLOBAL_WORK_SIZE -63 -#define CL_INVALID_PROPERTY -64 -#define CL_INVALID_IMAGE_DESCRIPTOR -65 -#define CL_INVALID_COMPILER_OPTIONS -66 -#define CL_INVALID_LINKER_OPTIONS -67 -#define CL_INVALID_DEVICE_PARTITION_COUNT -68 -#define CL_INVALID_PIPE_SIZE -69 -#define CL_INVALID_DEVICE_QUEUE -70 - -/* OpenCL Version */ -#define CL_VERSION_1_0 1 -#define CL_VERSION_1_1 1 -#define CL_VERSION_1_2 1 -#define CL_VERSION_2_0 1 -#define CL_VERSION_2_1 1 - -/* cl_bool */ -#define CL_FALSE 0 -#define CL_TRUE 1 -#define CL_BLOCKING CL_TRUE -#define CL_NON_BLOCKING CL_FALSE - -/* cl_platform_info */ -#define CL_PLATFORM_PROFILE 0x0900 -#define CL_PLATFORM_VERSION 0x0901 -#define CL_PLATFORM_NAME 0x0902 -#define CL_PLATFORM_VENDOR 0x0903 -#define CL_PLATFORM_EXTENSIONS 0x0904 -#define CL_PLATFORM_HOST_TIMER_RESOLUTION 0x0905 - -/* cl_device_type - bitfield */ -#define CL_DEVICE_TYPE_DEFAULT (1 << 0) -#define CL_DEVICE_TYPE_CPU (1 << 1) -#define CL_DEVICE_TYPE_GPU (1 << 2) -#define CL_DEVICE_TYPE_ACCELERATOR (1 << 3) -#define CL_DEVICE_TYPE_CUSTOM (1 << 4) -#define CL_DEVICE_TYPE_ALL 0xFFFFFFFF - -/* cl_device_info */ -#define CL_DEVICE_TYPE 0x1000 -#define CL_DEVICE_VENDOR_ID 0x1001 -#define CL_DEVICE_MAX_COMPUTE_UNITS 0x1002 -#define CL_DEVICE_MAX_WORK_ITEM_DIMENSIONS 0x1003 -#define CL_DEVICE_MAX_WORK_GROUP_SIZE 0x1004 -#define CL_DEVICE_MAX_WORK_ITEM_SIZES 0x1005 -#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_CHAR 0x1006 -#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_SHORT 0x1007 -#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_INT 0x1008 -#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_LONG 0x1009 -#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_FLOAT 0x100A -#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_DOUBLE 0x100B -#define CL_DEVICE_MAX_CLOCK_FREQUENCY 0x100C -#define CL_DEVICE_ADDRESS_BITS 0x100D -#define CL_DEVICE_MAX_READ_IMAGE_ARGS 0x100E -#define CL_DEVICE_MAX_WRITE_IMAGE_ARGS 0x100F -#define CL_DEVICE_MAX_MEM_ALLOC_SIZE 0x1010 -#define CL_DEVICE_IMAGE2D_MAX_WIDTH 0x1011 -#define CL_DEVICE_IMAGE2D_MAX_HEIGHT 0x1012 -#define CL_DEVICE_IMAGE3D_MAX_WIDTH 0x1013 -#define CL_DEVICE_IMAGE3D_MAX_HEIGHT 0x1014 -#define CL_DEVICE_IMAGE3D_MAX_DEPTH 0x1015 -#define CL_DEVICE_IMAGE_SUPPORT 0x1016 -#define CL_DEVICE_MAX_PARAMETER_SIZE 0x1017 -#define CL_DEVICE_MAX_SAMPLERS 0x1018 -#define CL_DEVICE_MEM_BASE_ADDR_ALIGN 0x1019 -#define CL_DEVICE_MIN_DATA_TYPE_ALIGN_SIZE 0x101A -#define CL_DEVICE_SINGLE_FP_CONFIG 0x101B -#define CL_DEVICE_GLOBAL_MEM_CACHE_TYPE 0x101C -#define CL_DEVICE_GLOBAL_MEM_CACHELINE_SIZE 0x101D -#define CL_DEVICE_GLOBAL_MEM_CACHE_SIZE 0x101E -#define CL_DEVICE_GLOBAL_MEM_SIZE 0x101F -#define CL_DEVICE_MAX_CONSTANT_BUFFER_SIZE 0x1020 -#define CL_DEVICE_MAX_CONSTANT_ARGS 0x1021 -#define CL_DEVICE_LOCAL_MEM_TYPE 0x1022 -#define CL_DEVICE_LOCAL_MEM_SIZE 0x1023 -#define CL_DEVICE_ERROR_CORRECTION_SUPPORT 0x1024 -#define CL_DEVICE_PROFILING_TIMER_RESOLUTION 0x1025 -#define CL_DEVICE_ENDIAN_LITTLE 0x1026 -#define CL_DEVICE_AVAILABLE 0x1027 -#define CL_DEVICE_COMPILER_AVAILABLE 0x1028 -#define CL_DEVICE_EXECUTION_CAPABILITIES 0x1029 -#define CL_DEVICE_QUEUE_PROPERTIES 0x102A /* deprecated */ -#define CL_DEVICE_QUEUE_ON_HOST_PROPERTIES 0x102A -#define CL_DEVICE_NAME 0x102B -#define CL_DEVICE_VENDOR 0x102C -#define CL_DRIVER_VERSION 0x102D -#define CL_DEVICE_PROFILE 0x102E -#define CL_DEVICE_VERSION 0x102F -#define CL_DEVICE_EXTENSIONS 0x1030 -#define CL_DEVICE_PLATFORM 0x1031 -#define CL_DEVICE_DOUBLE_FP_CONFIG 0x1032 -/* 0x1033 reserved for CL_DEVICE_HALF_FP_CONFIG */ -#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_HALF 0x1034 -#define CL_DEVICE_HOST_UNIFIED_MEMORY 0x1035 /* deprecated */ -#define CL_DEVICE_NATIVE_VECTOR_WIDTH_CHAR 0x1036 -#define CL_DEVICE_NATIVE_VECTOR_WIDTH_SHORT 0x1037 -#define CL_DEVICE_NATIVE_VECTOR_WIDTH_INT 0x1038 -#define CL_DEVICE_NATIVE_VECTOR_WIDTH_LONG 0x1039 -#define CL_DEVICE_NATIVE_VECTOR_WIDTH_FLOAT 0x103A -#define CL_DEVICE_NATIVE_VECTOR_WIDTH_DOUBLE 0x103B -#define CL_DEVICE_NATIVE_VECTOR_WIDTH_HALF 0x103C -#define CL_DEVICE_OPENCL_C_VERSION 0x103D -#define CL_DEVICE_LINKER_AVAILABLE 0x103E -#define CL_DEVICE_BUILT_IN_KERNELS 0x103F -#define CL_DEVICE_IMAGE_MAX_BUFFER_SIZE 0x1040 -#define CL_DEVICE_IMAGE_MAX_ARRAY_SIZE 0x1041 -#define CL_DEVICE_PARENT_DEVICE 0x1042 -#define CL_DEVICE_PARTITION_MAX_SUB_DEVICES 0x1043 -#define CL_DEVICE_PARTITION_PROPERTIES 0x1044 -#define CL_DEVICE_PARTITION_AFFINITY_DOMAIN 0x1045 -#define CL_DEVICE_PARTITION_TYPE 0x1046 -#define CL_DEVICE_REFERENCE_COUNT 0x1047 -#define CL_DEVICE_PREFERRED_INTEROP_USER_SYNC 0x1048 -#define CL_DEVICE_PRINTF_BUFFER_SIZE 0x1049 -#define CL_DEVICE_IMAGE_PITCH_ALIGNMENT 0x104A -#define CL_DEVICE_IMAGE_BASE_ADDRESS_ALIGNMENT 0x104B -#define CL_DEVICE_MAX_READ_WRITE_IMAGE_ARGS 0x104C -#define CL_DEVICE_MAX_GLOBAL_VARIABLE_SIZE 0x104D -#define CL_DEVICE_QUEUE_ON_DEVICE_PROPERTIES 0x104E -#define CL_DEVICE_QUEUE_ON_DEVICE_PREFERRED_SIZE 0x104F -#define CL_DEVICE_QUEUE_ON_DEVICE_MAX_SIZE 0x1050 -#define CL_DEVICE_MAX_ON_DEVICE_QUEUES 0x1051 -#define CL_DEVICE_MAX_ON_DEVICE_EVENTS 0x1052 -#define CL_DEVICE_SVM_CAPABILITIES 0x1053 -#define CL_DEVICE_GLOBAL_VARIABLE_PREFERRED_TOTAL_SIZE 0x1054 -#define CL_DEVICE_MAX_PIPE_ARGS 0x1055 -#define CL_DEVICE_PIPE_MAX_ACTIVE_RESERVATIONS 0x1056 -#define CL_DEVICE_PIPE_MAX_PACKET_SIZE 0x1057 -#define CL_DEVICE_PREFERRED_PLATFORM_ATOMIC_ALIGNMENT 0x1058 -#define CL_DEVICE_PREFERRED_GLOBAL_ATOMIC_ALIGNMENT 0x1059 -#define CL_DEVICE_PREFERRED_LOCAL_ATOMIC_ALIGNMENT 0x105A -#define CL_DEVICE_IL_VERSION 0x105B -#define CL_DEVICE_MAX_NUM_SUB_GROUPS 0x105C -#define CL_DEVICE_SUB_GROUP_INDEPENDENT_FORWARD_PROGRESS 0x105D - -/* cl_device_fp_config - bitfield */ -#define CL_FP_DENORM (1 << 0) -#define CL_FP_INF_NAN (1 << 1) -#define CL_FP_ROUND_TO_NEAREST (1 << 2) -#define CL_FP_ROUND_TO_ZERO (1 << 3) -#define CL_FP_ROUND_TO_INF (1 << 4) -#define CL_FP_FMA (1 << 5) -#define CL_FP_SOFT_FLOAT (1 << 6) -#define CL_FP_CORRECTLY_ROUNDED_DIVIDE_SQRT (1 << 7) - -/* cl_device_mem_cache_type */ -#define CL_NONE 0x0 -#define CL_READ_ONLY_CACHE 0x1 -#define CL_READ_WRITE_CACHE 0x2 - -/* cl_device_local_mem_type */ -#define CL_LOCAL 0x1 -#define CL_GLOBAL 0x2 - -/* cl_device_exec_capabilities - bitfield */ -#define CL_EXEC_KERNEL (1 << 0) -#define CL_EXEC_NATIVE_KERNEL (1 << 1) - -/* cl_command_queue_properties - bitfield */ -#define CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE (1 << 0) -#define CL_QUEUE_PROFILING_ENABLE (1 << 1) -#define CL_QUEUE_ON_DEVICE (1 << 2) -#define CL_QUEUE_ON_DEVICE_DEFAULT (1 << 3) - -/* cl_context_info */ -#define CL_CONTEXT_REFERENCE_COUNT 0x1080 -#define CL_CONTEXT_DEVICES 0x1081 -#define CL_CONTEXT_PROPERTIES 0x1082 -#define CL_CONTEXT_NUM_DEVICES 0x1083 - -/* cl_context_properties */ -#define CL_CONTEXT_PLATFORM 0x1084 -#define CL_CONTEXT_INTEROP_USER_SYNC 0x1085 - -/* cl_device_partition_property */ -#define CL_DEVICE_PARTITION_EQUALLY 0x1086 -#define CL_DEVICE_PARTITION_BY_COUNTS 0x1087 -#define CL_DEVICE_PARTITION_BY_COUNTS_LIST_END 0x0 -#define CL_DEVICE_PARTITION_BY_AFFINITY_DOMAIN 0x1088 - -/* cl_device_affinity_domain */ -#define CL_DEVICE_AFFINITY_DOMAIN_NUMA (1 << 0) -#define CL_DEVICE_AFFINITY_DOMAIN_L4_CACHE (1 << 1) -#define CL_DEVICE_AFFINITY_DOMAIN_L3_CACHE (1 << 2) -#define CL_DEVICE_AFFINITY_DOMAIN_L2_CACHE (1 << 3) -#define CL_DEVICE_AFFINITY_DOMAIN_L1_CACHE (1 << 4) -#define CL_DEVICE_AFFINITY_DOMAIN_NEXT_PARTITIONABLE (1 << 5) - -/* cl_device_svm_capabilities */ -#define CL_DEVICE_SVM_COARSE_GRAIN_BUFFER (1 << 0) -#define CL_DEVICE_SVM_FINE_GRAIN_BUFFER (1 << 1) -#define CL_DEVICE_SVM_FINE_GRAIN_SYSTEM (1 << 2) -#define CL_DEVICE_SVM_ATOMICS (1 << 3) - -/* cl_command_queue_info */ -#define CL_QUEUE_CONTEXT 0x1090 -#define CL_QUEUE_DEVICE 0x1091 -#define CL_QUEUE_REFERENCE_COUNT 0x1092 -#define CL_QUEUE_PROPERTIES 0x1093 -#define CL_QUEUE_SIZE 0x1094 -#define CL_QUEUE_DEVICE_DEFAULT 0x1095 - -/* cl_mem_flags and cl_svm_mem_flags - bitfield */ -#define CL_MEM_READ_WRITE (1 << 0) -#define CL_MEM_WRITE_ONLY (1 << 1) -#define CL_MEM_READ_ONLY (1 << 2) -#define CL_MEM_USE_HOST_PTR (1 << 3) -#define CL_MEM_ALLOC_HOST_PTR (1 << 4) -#define CL_MEM_COPY_HOST_PTR (1 << 5) -/* reserved (1 << 6) */ -#define CL_MEM_HOST_WRITE_ONLY (1 << 7) -#define CL_MEM_HOST_READ_ONLY (1 << 8) -#define CL_MEM_HOST_NO_ACCESS (1 << 9) -#define CL_MEM_SVM_FINE_GRAIN_BUFFER (1 << 10) /* used by cl_svm_mem_flags only */ -#define CL_MEM_SVM_ATOMICS (1 << 11) /* used by cl_svm_mem_flags only */ -#define CL_MEM_KERNEL_READ_AND_WRITE (1 << 12) - -/* cl_mem_migration_flags - bitfield */ -#define CL_MIGRATE_MEM_OBJECT_HOST (1 << 0) -#define CL_MIGRATE_MEM_OBJECT_CONTENT_UNDEFINED (1 << 1) - -/* cl_channel_order */ -#define CL_R 0x10B0 -#define CL_A 0x10B1 -#define CL_RG 0x10B2 -#define CL_RA 0x10B3 -#define CL_RGB 0x10B4 -#define CL_RGBA 0x10B5 -#define CL_BGRA 0x10B6 -#define CL_ARGB 0x10B7 -#define CL_INTENSITY 0x10B8 -#define CL_LUMINANCE 0x10B9 -#define CL_Rx 0x10BA -#define CL_RGx 0x10BB -#define CL_RGBx 0x10BC -#define CL_DEPTH 0x10BD -#define CL_DEPTH_STENCIL 0x10BE -#define CL_sRGB 0x10BF -#define CL_sRGBx 0x10C0 -#define CL_sRGBA 0x10C1 -#define CL_sBGRA 0x10C2 -#define CL_ABGR 0x10C3 - -/* cl_channel_type */ -#define CL_SNORM_INT8 0x10D0 -#define CL_SNORM_INT16 0x10D1 -#define CL_UNORM_INT8 0x10D2 -#define CL_UNORM_INT16 0x10D3 -#define CL_UNORM_SHORT_565 0x10D4 -#define CL_UNORM_SHORT_555 0x10D5 -#define CL_UNORM_INT_101010 0x10D6 -#define CL_SIGNED_INT8 0x10D7 -#define CL_SIGNED_INT16 0x10D8 -#define CL_SIGNED_INT32 0x10D9 -#define CL_UNSIGNED_INT8 0x10DA -#define CL_UNSIGNED_INT16 0x10DB -#define CL_UNSIGNED_INT32 0x10DC -#define CL_HALF_FLOAT 0x10DD -#define CL_FLOAT 0x10DE -#define CL_UNORM_INT24 0x10DF -#define CL_UNORM_INT_101010_2 0x10E0 - -/* cl_mem_object_type */ -#define CL_MEM_OBJECT_BUFFER 0x10F0 -#define CL_MEM_OBJECT_IMAGE2D 0x10F1 -#define CL_MEM_OBJECT_IMAGE3D 0x10F2 -#define CL_MEM_OBJECT_IMAGE2D_ARRAY 0x10F3 -#define CL_MEM_OBJECT_IMAGE1D 0x10F4 -#define CL_MEM_OBJECT_IMAGE1D_ARRAY 0x10F5 -#define CL_MEM_OBJECT_IMAGE1D_BUFFER 0x10F6 -#define CL_MEM_OBJECT_PIPE 0x10F7 - -/* cl_mem_info */ -#define CL_MEM_TYPE 0x1100 -#define CL_MEM_FLAGS 0x1101 -#define CL_MEM_SIZE 0x1102 -#define CL_MEM_HOST_PTR 0x1103 -#define CL_MEM_MAP_COUNT 0x1104 -#define CL_MEM_REFERENCE_COUNT 0x1105 -#define CL_MEM_CONTEXT 0x1106 -#define CL_MEM_ASSOCIATED_MEMOBJECT 0x1107 -#define CL_MEM_OFFSET 0x1108 -#define CL_MEM_USES_SVM_POINTER 0x1109 - -/* cl_image_info */ -#define CL_IMAGE_FORMAT 0x1110 -#define CL_IMAGE_ELEMENT_SIZE 0x1111 -#define CL_IMAGE_ROW_PITCH 0x1112 -#define CL_IMAGE_SLICE_PITCH 0x1113 -#define CL_IMAGE_WIDTH 0x1114 -#define CL_IMAGE_HEIGHT 0x1115 -#define CL_IMAGE_DEPTH 0x1116 -#define CL_IMAGE_ARRAY_SIZE 0x1117 -#define CL_IMAGE_BUFFER 0x1118 -#define CL_IMAGE_NUM_MIP_LEVELS 0x1119 -#define CL_IMAGE_NUM_SAMPLES 0x111A - -/* cl_pipe_info */ -#define CL_PIPE_PACKET_SIZE 0x1120 -#define CL_PIPE_MAX_PACKETS 0x1121 - -/* cl_addressing_mode */ -#define CL_ADDRESS_NONE 0x1130 -#define CL_ADDRESS_CLAMP_TO_EDGE 0x1131 -#define CL_ADDRESS_CLAMP 0x1132 -#define CL_ADDRESS_REPEAT 0x1133 -#define CL_ADDRESS_MIRRORED_REPEAT 0x1134 - -/* cl_filter_mode */ -#define CL_FILTER_NEAREST 0x1140 -#define CL_FILTER_LINEAR 0x1141 - -/* cl_sampler_info */ -#define CL_SAMPLER_REFERENCE_COUNT 0x1150 -#define CL_SAMPLER_CONTEXT 0x1151 -#define CL_SAMPLER_NORMALIZED_COORDS 0x1152 -#define CL_SAMPLER_ADDRESSING_MODE 0x1153 -#define CL_SAMPLER_FILTER_MODE 0x1154 -#define CL_SAMPLER_MIP_FILTER_MODE 0x1155 -#define CL_SAMPLER_LOD_MIN 0x1156 -#define CL_SAMPLER_LOD_MAX 0x1157 - -/* cl_map_flags - bitfield */ -#define CL_MAP_READ (1 << 0) -#define CL_MAP_WRITE (1 << 1) -#define CL_MAP_WRITE_INVALIDATE_REGION (1 << 2) - -/* cl_program_info */ -#define CL_PROGRAM_REFERENCE_COUNT 0x1160 -#define CL_PROGRAM_CONTEXT 0x1161 -#define CL_PROGRAM_NUM_DEVICES 0x1162 -#define CL_PROGRAM_DEVICES 0x1163 -#define CL_PROGRAM_SOURCE 0x1164 -#define CL_PROGRAM_BINARY_SIZES 0x1165 -#define CL_PROGRAM_BINARIES 0x1166 -#define CL_PROGRAM_NUM_KERNELS 0x1167 -#define CL_PROGRAM_KERNEL_NAMES 0x1168 -#define CL_PROGRAM_IL 0x1169 - -/* cl_program_build_info */ -#define CL_PROGRAM_BUILD_STATUS 0x1181 -#define CL_PROGRAM_BUILD_OPTIONS 0x1182 -#define CL_PROGRAM_BUILD_LOG 0x1183 -#define CL_PROGRAM_BINARY_TYPE 0x1184 -#define CL_PROGRAM_BUILD_GLOBAL_VARIABLE_TOTAL_SIZE 0x1185 - -/* cl_program_binary_type */ -#define CL_PROGRAM_BINARY_TYPE_NONE 0x0 -#define CL_PROGRAM_BINARY_TYPE_COMPILED_OBJECT 0x1 -#define CL_PROGRAM_BINARY_TYPE_LIBRARY 0x2 -#define CL_PROGRAM_BINARY_TYPE_EXECUTABLE 0x4 - -/* cl_build_status */ -#define CL_BUILD_SUCCESS 0 -#define CL_BUILD_NONE -1 -#define CL_BUILD_ERROR -2 -#define CL_BUILD_IN_PROGRESS -3 - -/* cl_kernel_info */ -#define CL_KERNEL_FUNCTION_NAME 0x1190 -#define CL_KERNEL_NUM_ARGS 0x1191 -#define CL_KERNEL_REFERENCE_COUNT 0x1192 -#define CL_KERNEL_CONTEXT 0x1193 -#define CL_KERNEL_PROGRAM 0x1194 -#define CL_KERNEL_ATTRIBUTES 0x1195 -#define CL_KERNEL_MAX_NUM_SUB_GROUPS 0x11B9 -#define CL_KERNEL_COMPILE_NUM_SUB_GROUPS 0x11BA - -/* cl_kernel_arg_info */ -#define CL_KERNEL_ARG_ADDRESS_QUALIFIER 0x1196 -#define CL_KERNEL_ARG_ACCESS_QUALIFIER 0x1197 -#define CL_KERNEL_ARG_TYPE_NAME 0x1198 -#define CL_KERNEL_ARG_TYPE_QUALIFIER 0x1199 -#define CL_KERNEL_ARG_NAME 0x119A - -/* cl_kernel_arg_address_qualifier */ -#define CL_KERNEL_ARG_ADDRESS_GLOBAL 0x119B -#define CL_KERNEL_ARG_ADDRESS_LOCAL 0x119C -#define CL_KERNEL_ARG_ADDRESS_CONSTANT 0x119D -#define CL_KERNEL_ARG_ADDRESS_PRIVATE 0x119E - -/* cl_kernel_arg_access_qualifier */ -#define CL_KERNEL_ARG_ACCESS_READ_ONLY 0x11A0 -#define CL_KERNEL_ARG_ACCESS_WRITE_ONLY 0x11A1 -#define CL_KERNEL_ARG_ACCESS_READ_WRITE 0x11A2 -#define CL_KERNEL_ARG_ACCESS_NONE 0x11A3 - -/* cl_kernel_arg_type_qualifer */ -#define CL_KERNEL_ARG_TYPE_NONE 0 -#define CL_KERNEL_ARG_TYPE_CONST (1 << 0) -#define CL_KERNEL_ARG_TYPE_RESTRICT (1 << 1) -#define CL_KERNEL_ARG_TYPE_VOLATILE (1 << 2) -#define CL_KERNEL_ARG_TYPE_PIPE (1 << 3) - -/* cl_kernel_work_group_info */ -#define CL_KERNEL_WORK_GROUP_SIZE 0x11B0 -#define CL_KERNEL_COMPILE_WORK_GROUP_SIZE 0x11B1 -#define CL_KERNEL_LOCAL_MEM_SIZE 0x11B2 -#define CL_KERNEL_PREFERRED_WORK_GROUP_SIZE_MULTIPLE 0x11B3 -#define CL_KERNEL_PRIVATE_MEM_SIZE 0x11B4 -#define CL_KERNEL_GLOBAL_WORK_SIZE 0x11B5 - -/* cl_kernel_sub_group_info */ -#define CL_KERNEL_MAX_SUB_GROUP_SIZE_FOR_NDRANGE 0x2033 -#define CL_KERNEL_SUB_GROUP_COUNT_FOR_NDRANGE 0x2034 -#define CL_KERNEL_LOCAL_SIZE_FOR_SUB_GROUP_COUNT 0x11B8 - -/* cl_kernel_exec_info */ -#define CL_KERNEL_EXEC_INFO_SVM_PTRS 0x11B6 -#define CL_KERNEL_EXEC_INFO_SVM_FINE_GRAIN_SYSTEM 0x11B7 - -/* cl_event_info */ -#define CL_EVENT_COMMAND_QUEUE 0x11D0 -#define CL_EVENT_COMMAND_TYPE 0x11D1 -#define CL_EVENT_REFERENCE_COUNT 0x11D2 -#define CL_EVENT_COMMAND_EXECUTION_STATUS 0x11D3 -#define CL_EVENT_CONTEXT 0x11D4 - -/* cl_command_type */ -#define CL_COMMAND_NDRANGE_KERNEL 0x11F0 -#define CL_COMMAND_TASK 0x11F1 -#define CL_COMMAND_NATIVE_KERNEL 0x11F2 -#define CL_COMMAND_READ_BUFFER 0x11F3 -#define CL_COMMAND_WRITE_BUFFER 0x11F4 -#define CL_COMMAND_COPY_BUFFER 0x11F5 -#define CL_COMMAND_READ_IMAGE 0x11F6 -#define CL_COMMAND_WRITE_IMAGE 0x11F7 -#define CL_COMMAND_COPY_IMAGE 0x11F8 -#define CL_COMMAND_COPY_IMAGE_TO_BUFFER 0x11F9 -#define CL_COMMAND_COPY_BUFFER_TO_IMAGE 0x11FA -#define CL_COMMAND_MAP_BUFFER 0x11FB -#define CL_COMMAND_MAP_IMAGE 0x11FC -#define CL_COMMAND_UNMAP_MEM_OBJECT 0x11FD -#define CL_COMMAND_MARKER 0x11FE -#define CL_COMMAND_ACQUIRE_GL_OBJECTS 0x11FF -#define CL_COMMAND_RELEASE_GL_OBJECTS 0x1200 -#define CL_COMMAND_READ_BUFFER_RECT 0x1201 -#define CL_COMMAND_WRITE_BUFFER_RECT 0x1202 -#define CL_COMMAND_COPY_BUFFER_RECT 0x1203 -#define CL_COMMAND_USER 0x1204 -#define CL_COMMAND_BARRIER 0x1205 -#define CL_COMMAND_MIGRATE_MEM_OBJECTS 0x1206 -#define CL_COMMAND_FILL_BUFFER 0x1207 -#define CL_COMMAND_FILL_IMAGE 0x1208 -#define CL_COMMAND_SVM_FREE 0x1209 -#define CL_COMMAND_SVM_MEMCPY 0x120A -#define CL_COMMAND_SVM_MEMFILL 0x120B -#define CL_COMMAND_SVM_MAP 0x120C -#define CL_COMMAND_SVM_UNMAP 0x120D - -/* command execution status */ -#define CL_COMPLETE 0x0 -#define CL_RUNNING 0x1 -#define CL_SUBMITTED 0x2 -#define CL_QUEUED 0x3 - -/* cl_buffer_create_type */ -#define CL_BUFFER_CREATE_TYPE_REGION 0x1220 - -/* cl_profiling_info */ -#define CL_PROFILING_COMMAND_QUEUED 0x1280 -#define CL_PROFILING_COMMAND_SUBMIT 0x1281 -#define CL_PROFILING_COMMAND_START 0x1282 -#define CL_PROFILING_COMMAND_END 0x1283 -#define CL_PROFILING_COMMAND_COMPLETE 0x1284 - -/********************************************************************************************************/ - -/* Platform API */ -extern CL_API_ENTRY cl_int CL_API_CALL -clGetPlatformIDs(cl_uint /* num_entries */, - cl_platform_id * /* platforms */, - cl_uint * /* num_platforms */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetPlatformInfo(cl_platform_id /* platform */, - cl_platform_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -/* Device APIs */ -extern CL_API_ENTRY cl_int CL_API_CALL -clGetDeviceIDs(cl_platform_id /* platform */, - cl_device_type /* device_type */, - cl_uint /* num_entries */, - cl_device_id * /* devices */, - cl_uint * /* num_devices */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetDeviceInfo(cl_device_id /* device */, - cl_device_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clCreateSubDevices(cl_device_id /* in_device */, - const cl_device_partition_property * /* properties */, - cl_uint /* num_devices */, - cl_device_id * /* out_devices */, - cl_uint * /* num_devices_ret */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clRetainDevice(cl_device_id /* device */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clReleaseDevice(cl_device_id /* device */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clSetDefaultDeviceCommandQueue(cl_context /* context */, - cl_device_id /* device */, - cl_command_queue /* command_queue */) CL_API_SUFFIX__VERSION_2_1; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetDeviceAndHostTimer(cl_device_id /* device */, - cl_ulong* /* device_timestamp */, - cl_ulong* /* host_timestamp */) CL_API_SUFFIX__VERSION_2_1; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetHostTimer(cl_device_id /* device */, - cl_ulong * /* host_timestamp */) CL_API_SUFFIX__VERSION_2_1; - - -/* Context APIs */ -extern CL_API_ENTRY cl_context CL_API_CALL -clCreateContext(const cl_context_properties * /* properties */, - cl_uint /* num_devices */, - const cl_device_id * /* devices */, - void (CL_CALLBACK * /* pfn_notify */)(const char *, const void *, size_t, void *), - void * /* user_data */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_context CL_API_CALL -clCreateContextFromType(const cl_context_properties * /* properties */, - cl_device_type /* device_type */, - void (CL_CALLBACK * /* pfn_notify*/ )(const char *, const void *, size_t, void *), - void * /* user_data */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clRetainContext(cl_context /* context */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clReleaseContext(cl_context /* context */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetContextInfo(cl_context /* context */, - cl_context_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -/* Command Queue APIs */ -extern CL_API_ENTRY cl_command_queue CL_API_CALL -clCreateCommandQueueWithProperties(cl_context /* context */, - cl_device_id /* device */, - const cl_queue_properties * /* properties */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clRetainCommandQueue(cl_command_queue /* command_queue */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clReleaseCommandQueue(cl_command_queue /* command_queue */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetCommandQueueInfo(cl_command_queue /* command_queue */, - cl_command_queue_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -/* Memory Object APIs */ -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreateBuffer(cl_context /* context */, - cl_mem_flags /* flags */, - size_t /* size */, - void * /* host_ptr */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreateSubBuffer(cl_mem /* buffer */, - cl_mem_flags /* flags */, - cl_buffer_create_type /* buffer_create_type */, - const void * /* buffer_create_info */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_1; - -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreateImage(cl_context /* context */, - cl_mem_flags /* flags */, - const cl_image_format * /* image_format */, - const cl_image_desc * /* image_desc */, - void * /* host_ptr */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreatePipe(cl_context /* context */, - cl_mem_flags /* flags */, - cl_uint /* pipe_packet_size */, - cl_uint /* pipe_max_packets */, - const cl_pipe_properties * /* properties */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clRetainMemObject(cl_mem /* memobj */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clReleaseMemObject(cl_mem /* memobj */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetSupportedImageFormats(cl_context /* context */, - cl_mem_flags /* flags */, - cl_mem_object_type /* image_type */, - cl_uint /* num_entries */, - cl_image_format * /* image_formats */, - cl_uint * /* num_image_formats */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetMemObjectInfo(cl_mem /* memobj */, - cl_mem_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetImageInfo(cl_mem /* image */, - cl_image_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetPipeInfo(cl_mem /* pipe */, - cl_pipe_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_2_0; - - -extern CL_API_ENTRY cl_int CL_API_CALL -clSetMemObjectDestructorCallback(cl_mem /* memobj */, - void (CL_CALLBACK * /*pfn_notify*/)( cl_mem /* memobj */, void* /*user_data*/), - void * /*user_data */ ) CL_API_SUFFIX__VERSION_1_1; - -/* SVM Allocation APIs */ -extern CL_API_ENTRY void * CL_API_CALL -clSVMAlloc(cl_context /* context */, - cl_svm_mem_flags /* flags */, - size_t /* size */, - cl_uint /* alignment */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY void CL_API_CALL -clSVMFree(cl_context /* context */, - void * /* svm_pointer */) CL_API_SUFFIX__VERSION_2_0; - -/* Sampler APIs */ -extern CL_API_ENTRY cl_sampler CL_API_CALL -clCreateSamplerWithProperties(cl_context /* context */, - const cl_sampler_properties * /* normalized_coords */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clRetainSampler(cl_sampler /* sampler */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clReleaseSampler(cl_sampler /* sampler */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetSamplerInfo(cl_sampler /* sampler */, - cl_sampler_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -/* Program Object APIs */ -extern CL_API_ENTRY cl_program CL_API_CALL -clCreateProgramWithSource(cl_context /* context */, - cl_uint /* count */, - const char ** /* strings */, - const size_t * /* lengths */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_program CL_API_CALL -clCreateProgramWithBinary(cl_context /* context */, - cl_uint /* num_devices */, - const cl_device_id * /* device_list */, - const size_t * /* lengths */, - const unsigned char ** /* binaries */, - cl_int * /* binary_status */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_program CL_API_CALL -clCreateProgramWithBuiltInKernels(cl_context /* context */, - cl_uint /* num_devices */, - const cl_device_id * /* device_list */, - const char * /* kernel_names */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_program CL_API_CALL -clCreateProgramWithIL(cl_context /* context */, - const void* /* il */, - size_t /* length */, - cl_int* /* errcode_ret */) CL_API_SUFFIX__VERSION_2_1; - - -extern CL_API_ENTRY cl_int CL_API_CALL -clRetainProgram(cl_program /* program */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clReleaseProgram(cl_program /* program */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clBuildProgram(cl_program /* program */, - cl_uint /* num_devices */, - const cl_device_id * /* device_list */, - const char * /* options */, - void (CL_CALLBACK * /* pfn_notify */)(cl_program /* program */, void * /* user_data */), - void * /* user_data */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clCompileProgram(cl_program /* program */, - cl_uint /* num_devices */, - const cl_device_id * /* device_list */, - const char * /* options */, - cl_uint /* num_input_headers */, - const cl_program * /* input_headers */, - const char ** /* header_include_names */, - void (CL_CALLBACK * /* pfn_notify */)(cl_program /* program */, void * /* user_data */), - void * /* user_data */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_program CL_API_CALL -clLinkProgram(cl_context /* context */, - cl_uint /* num_devices */, - const cl_device_id * /* device_list */, - const char * /* options */, - cl_uint /* num_input_programs */, - const cl_program * /* input_programs */, - void (CL_CALLBACK * /* pfn_notify */)(cl_program /* program */, void * /* user_data */), - void * /* user_data */, - cl_int * /* errcode_ret */ ) CL_API_SUFFIX__VERSION_1_2; - - -extern CL_API_ENTRY cl_int CL_API_CALL -clUnloadPlatformCompiler(cl_platform_id /* platform */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetProgramInfo(cl_program /* program */, - cl_program_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetProgramBuildInfo(cl_program /* program */, - cl_device_id /* device */, - cl_program_build_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -/* Kernel Object APIs */ -extern CL_API_ENTRY cl_kernel CL_API_CALL -clCreateKernel(cl_program /* program */, - const char * /* kernel_name */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clCreateKernelsInProgram(cl_program /* program */, - cl_uint /* num_kernels */, - cl_kernel * /* kernels */, - cl_uint * /* num_kernels_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_kernel CL_API_CALL -clCloneKernel(cl_kernel /* source_kernel */, - cl_int* /* errcode_ret */) CL_API_SUFFIX__VERSION_2_1; - -extern CL_API_ENTRY cl_int CL_API_CALL -clRetainKernel(cl_kernel /* kernel */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clReleaseKernel(cl_kernel /* kernel */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clSetKernelArg(cl_kernel /* kernel */, - cl_uint /* arg_index */, - size_t /* arg_size */, - const void * /* arg_value */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clSetKernelArgSVMPointer(cl_kernel /* kernel */, - cl_uint /* arg_index */, - const void * /* arg_value */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clSetKernelExecInfo(cl_kernel /* kernel */, - cl_kernel_exec_info /* param_name */, - size_t /* param_value_size */, - const void * /* param_value */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetKernelInfo(cl_kernel /* kernel */, - cl_kernel_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetKernelArgInfo(cl_kernel /* kernel */, - cl_uint /* arg_indx */, - cl_kernel_arg_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetKernelWorkGroupInfo(cl_kernel /* kernel */, - cl_device_id /* device */, - cl_kernel_work_group_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetKernelSubGroupInfo(cl_kernel /* kernel */, - cl_device_id /* device */, - cl_kernel_sub_group_info /* param_name */, - size_t /* input_value_size */, - const void* /*input_value */, - size_t /* param_value_size */, - void* /* param_value */, - size_t* /* param_value_size_ret */ ) CL_API_SUFFIX__VERSION_2_1; - - -/* Event Object APIs */ -extern CL_API_ENTRY cl_int CL_API_CALL -clWaitForEvents(cl_uint /* num_events */, - const cl_event * /* event_list */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetEventInfo(cl_event /* event */, - cl_event_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_event CL_API_CALL -clCreateUserEvent(cl_context /* context */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_1; - -extern CL_API_ENTRY cl_int CL_API_CALL -clRetainEvent(cl_event /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clReleaseEvent(cl_event /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clSetUserEventStatus(cl_event /* event */, - cl_int /* execution_status */) CL_API_SUFFIX__VERSION_1_1; - -extern CL_API_ENTRY cl_int CL_API_CALL -clSetEventCallback( cl_event /* event */, - cl_int /* command_exec_callback_type */, - void (CL_CALLBACK * /* pfn_notify */)(cl_event, cl_int, void *), - void * /* user_data */) CL_API_SUFFIX__VERSION_1_1; - -/* Profiling APIs */ -extern CL_API_ENTRY cl_int CL_API_CALL -clGetEventProfilingInfo(cl_event /* event */, - cl_profiling_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -/* Flush and Finish APIs */ -extern CL_API_ENTRY cl_int CL_API_CALL -clFlush(cl_command_queue /* command_queue */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clFinish(cl_command_queue /* command_queue */) CL_API_SUFFIX__VERSION_1_0; - -/* Enqueued Commands APIs */ -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueReadBuffer(cl_command_queue /* command_queue */, - cl_mem /* buffer */, - cl_bool /* blocking_read */, - size_t /* offset */, - size_t /* size */, - void * /* ptr */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueReadBufferRect(cl_command_queue /* command_queue */, - cl_mem /* buffer */, - cl_bool /* blocking_read */, - const size_t * /* buffer_offset */, - const size_t * /* host_offset */, - const size_t * /* region */, - size_t /* buffer_row_pitch */, - size_t /* buffer_slice_pitch */, - size_t /* host_row_pitch */, - size_t /* host_slice_pitch */, - void * /* ptr */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_1; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueWriteBuffer(cl_command_queue /* command_queue */, - cl_mem /* buffer */, - cl_bool /* blocking_write */, - size_t /* offset */, - size_t /* size */, - const void * /* ptr */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueWriteBufferRect(cl_command_queue /* command_queue */, - cl_mem /* buffer */, - cl_bool /* blocking_write */, - const size_t * /* buffer_offset */, - const size_t * /* host_offset */, - const size_t * /* region */, - size_t /* buffer_row_pitch */, - size_t /* buffer_slice_pitch */, - size_t /* host_row_pitch */, - size_t /* host_slice_pitch */, - const void * /* ptr */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_1; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueFillBuffer(cl_command_queue /* command_queue */, - cl_mem /* buffer */, - const void * /* pattern */, - size_t /* pattern_size */, - size_t /* offset */, - size_t /* size */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueCopyBuffer(cl_command_queue /* command_queue */, - cl_mem /* src_buffer */, - cl_mem /* dst_buffer */, - size_t /* src_offset */, - size_t /* dst_offset */, - size_t /* size */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueCopyBufferRect(cl_command_queue /* command_queue */, - cl_mem /* src_buffer */, - cl_mem /* dst_buffer */, - const size_t * /* src_origin */, - const size_t * /* dst_origin */, - const size_t * /* region */, - size_t /* src_row_pitch */, - size_t /* src_slice_pitch */, - size_t /* dst_row_pitch */, - size_t /* dst_slice_pitch */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_1; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueReadImage(cl_command_queue /* command_queue */, - cl_mem /* image */, - cl_bool /* blocking_read */, - const size_t * /* origin[3] */, - const size_t * /* region[3] */, - size_t /* row_pitch */, - size_t /* slice_pitch */, - void * /* ptr */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueWriteImage(cl_command_queue /* command_queue */, - cl_mem /* image */, - cl_bool /* blocking_write */, - const size_t * /* origin[3] */, - const size_t * /* region[3] */, - size_t /* input_row_pitch */, - size_t /* input_slice_pitch */, - const void * /* ptr */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueFillImage(cl_command_queue /* command_queue */, - cl_mem /* image */, - const void * /* fill_color */, - const size_t * /* origin[3] */, - const size_t * /* region[3] */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueCopyImage(cl_command_queue /* command_queue */, - cl_mem /* src_image */, - cl_mem /* dst_image */, - const size_t * /* src_origin[3] */, - const size_t * /* dst_origin[3] */, - const size_t * /* region[3] */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueCopyImageToBuffer(cl_command_queue /* command_queue */, - cl_mem /* src_image */, - cl_mem /* dst_buffer */, - const size_t * /* src_origin[3] */, - const size_t * /* region[3] */, - size_t /* dst_offset */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueCopyBufferToImage(cl_command_queue /* command_queue */, - cl_mem /* src_buffer */, - cl_mem /* dst_image */, - size_t /* src_offset */, - const size_t * /* dst_origin[3] */, - const size_t * /* region[3] */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY void * CL_API_CALL -clEnqueueMapBuffer(cl_command_queue /* command_queue */, - cl_mem /* buffer */, - cl_bool /* blocking_map */, - cl_map_flags /* map_flags */, - size_t /* offset */, - size_t /* size */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY void * CL_API_CALL -clEnqueueMapImage(cl_command_queue /* command_queue */, - cl_mem /* image */, - cl_bool /* blocking_map */, - cl_map_flags /* map_flags */, - const size_t * /* origin[3] */, - const size_t * /* region[3] */, - size_t * /* image_row_pitch */, - size_t * /* image_slice_pitch */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueUnmapMemObject(cl_command_queue /* command_queue */, - cl_mem /* memobj */, - void * /* mapped_ptr */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueMigrateMemObjects(cl_command_queue /* command_queue */, - cl_uint /* num_mem_objects */, - const cl_mem * /* mem_objects */, - cl_mem_migration_flags /* flags */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueNDRangeKernel(cl_command_queue /* command_queue */, - cl_kernel /* kernel */, - cl_uint /* work_dim */, - const size_t * /* global_work_offset */, - const size_t * /* global_work_size */, - const size_t * /* local_work_size */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueNativeKernel(cl_command_queue /* command_queue */, - void (CL_CALLBACK * /*user_func*/)(void *), - void * /* args */, - size_t /* cb_args */, - cl_uint /* num_mem_objects */, - const cl_mem * /* mem_list */, - const void ** /* args_mem_loc */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueMarkerWithWaitList(cl_command_queue /* command_queue */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueBarrierWithWaitList(cl_command_queue /* command_queue */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueSVMFree(cl_command_queue /* command_queue */, - cl_uint /* num_svm_pointers */, - void *[] /* svm_pointers[] */, - void (CL_CALLBACK * /*pfn_free_func*/)(cl_command_queue /* queue */, - cl_uint /* num_svm_pointers */, - void *[] /* svm_pointers[] */, - void * /* user_data */), - void * /* user_data */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueSVMMemcpy(cl_command_queue /* command_queue */, - cl_bool /* blocking_copy */, - void * /* dst_ptr */, - const void * /* src_ptr */, - size_t /* size */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueSVMMemFill(cl_command_queue /* command_queue */, - void * /* svm_ptr */, - const void * /* pattern */, - size_t /* pattern_size */, - size_t /* size */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueSVMMap(cl_command_queue /* command_queue */, - cl_bool /* blocking_map */, - cl_map_flags /* flags */, - void * /* svm_ptr */, - size_t /* size */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueSVMUnmap(cl_command_queue /* command_queue */, - void * /* svm_ptr */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueSVMMigrateMem(cl_command_queue /* command_queue */, - cl_uint /* num_svm_pointers */, - const void ** /* svm_pointers */, - const size_t * /* sizes */, - cl_mem_migration_flags /* flags */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_2_1; - - -/* Extension function access - * - * Returns the extension function address for the given function name, - * or NULL if a valid function can not be found. The client must - * check to make sure the address is not NULL, before using or - * calling the returned function address. - */ -extern CL_API_ENTRY void * CL_API_CALL -clGetExtensionFunctionAddressForPlatform(cl_platform_id /* platform */, - const char * /* func_name */) CL_API_SUFFIX__VERSION_1_2; - - -/* Deprecated OpenCL 1.1 APIs */ -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_mem CL_API_CALL -clCreateImage2D(cl_context /* context */, - cl_mem_flags /* flags */, - const cl_image_format * /* image_format */, - size_t /* image_width */, - size_t /* image_height */, - size_t /* image_row_pitch */, - void * /* host_ptr */, - cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; - -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_mem CL_API_CALL -clCreateImage3D(cl_context /* context */, - cl_mem_flags /* flags */, - const cl_image_format * /* image_format */, - size_t /* image_width */, - size_t /* image_height */, - size_t /* image_depth */, - size_t /* image_row_pitch */, - size_t /* image_slice_pitch */, - void * /* host_ptr */, - cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; - -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_int CL_API_CALL -clEnqueueMarker(cl_command_queue /* command_queue */, - cl_event * /* event */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; - -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_int CL_API_CALL -clEnqueueWaitForEvents(cl_command_queue /* command_queue */, - cl_uint /* num_events */, - const cl_event * /* event_list */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; - -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_int CL_API_CALL -clEnqueueBarrier(cl_command_queue /* command_queue */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; - -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_int CL_API_CALL -clUnloadCompiler(void) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; - -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED void * CL_API_CALL -clGetExtensionFunctionAddress(const char * /* func_name */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; - -/* Deprecated OpenCL 2.0 APIs */ -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_2_DEPRECATED cl_command_queue CL_API_CALL -clCreateCommandQueue(cl_context /* context */, - cl_device_id /* device */, - cl_command_queue_properties /* properties */, - cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED; - - -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_2_DEPRECATED cl_sampler CL_API_CALL -clCreateSampler(cl_context /* context */, - cl_bool /* normalized_coords */, - cl_addressing_mode /* addressing_mode */, - cl_filter_mode /* filter_mode */, - cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED; - -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_2_DEPRECATED cl_int CL_API_CALL -clEnqueueTask(cl_command_queue /* command_queue */, - cl_kernel /* kernel */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED; - -#ifdef __cplusplus -} -#endif - -#endif /* __OPENCL_CL_H */ - diff --git a/third_party/opencl/include/CL/cl_d3d10.h b/third_party/opencl/include/CL/cl_d3d10.h deleted file mode 100644 index d5960a43f..000000000 --- a/third_party/opencl/include/CL/cl_d3d10.h +++ /dev/null @@ -1,131 +0,0 @@ -/********************************************************************************** - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - **********************************************************************************/ - -/* $Revision: 11708 $ on $Date: 2010-06-13 23:36:24 -0700 (Sun, 13 Jun 2010) $ */ - -#ifndef __OPENCL_CL_D3D10_H -#define __OPENCL_CL_D3D10_H - -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/****************************************************************************** - * cl_khr_d3d10_sharing */ -#define cl_khr_d3d10_sharing 1 - -typedef cl_uint cl_d3d10_device_source_khr; -typedef cl_uint cl_d3d10_device_set_khr; - -/******************************************************************************/ - -/* Error Codes */ -#define CL_INVALID_D3D10_DEVICE_KHR -1002 -#define CL_INVALID_D3D10_RESOURCE_KHR -1003 -#define CL_D3D10_RESOURCE_ALREADY_ACQUIRED_KHR -1004 -#define CL_D3D10_RESOURCE_NOT_ACQUIRED_KHR -1005 - -/* cl_d3d10_device_source_nv */ -#define CL_D3D10_DEVICE_KHR 0x4010 -#define CL_D3D10_DXGI_ADAPTER_KHR 0x4011 - -/* cl_d3d10_device_set_nv */ -#define CL_PREFERRED_DEVICES_FOR_D3D10_KHR 0x4012 -#define CL_ALL_DEVICES_FOR_D3D10_KHR 0x4013 - -/* cl_context_info */ -#define CL_CONTEXT_D3D10_DEVICE_KHR 0x4014 -#define CL_CONTEXT_D3D10_PREFER_SHARED_RESOURCES_KHR 0x402C - -/* cl_mem_info */ -#define CL_MEM_D3D10_RESOURCE_KHR 0x4015 - -/* cl_image_info */ -#define CL_IMAGE_D3D10_SUBRESOURCE_KHR 0x4016 - -/* cl_command_type */ -#define CL_COMMAND_ACQUIRE_D3D10_OBJECTS_KHR 0x4017 -#define CL_COMMAND_RELEASE_D3D10_OBJECTS_KHR 0x4018 - -/******************************************************************************/ - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clGetDeviceIDsFromD3D10KHR_fn)( - cl_platform_id platform, - cl_d3d10_device_source_khr d3d_device_source, - void * d3d_object, - cl_d3d10_device_set_khr d3d_device_set, - cl_uint num_entries, - cl_device_id * devices, - cl_uint * num_devices) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D10BufferKHR_fn)( - cl_context context, - cl_mem_flags flags, - ID3D10Buffer * resource, - cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D10Texture2DKHR_fn)( - cl_context context, - cl_mem_flags flags, - ID3D10Texture2D * resource, - UINT subresource, - cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D10Texture3DKHR_fn)( - cl_context context, - cl_mem_flags flags, - ID3D10Texture3D * resource, - UINT subresource, - cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueAcquireD3D10ObjectsKHR_fn)( - cl_command_queue command_queue, - cl_uint num_objects, - const cl_mem * mem_objects, - cl_uint num_events_in_wait_list, - const cl_event * event_wait_list, - cl_event * event) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueReleaseD3D10ObjectsKHR_fn)( - cl_command_queue command_queue, - cl_uint num_objects, - const cl_mem * mem_objects, - cl_uint num_events_in_wait_list, - const cl_event * event_wait_list, - cl_event * event) CL_API_SUFFIX__VERSION_1_0; - -#ifdef __cplusplus -} -#endif - -#endif /* __OPENCL_CL_D3D10_H */ - diff --git a/third_party/opencl/include/CL/cl_d3d11.h b/third_party/opencl/include/CL/cl_d3d11.h deleted file mode 100644 index 39f907239..000000000 --- a/third_party/opencl/include/CL/cl_d3d11.h +++ /dev/null @@ -1,131 +0,0 @@ -/********************************************************************************** - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - **********************************************************************************/ - -/* $Revision: 11708 $ on $Date: 2010-06-13 23:36:24 -0700 (Sun, 13 Jun 2010) $ */ - -#ifndef __OPENCL_CL_D3D11_H -#define __OPENCL_CL_D3D11_H - -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/****************************************************************************** - * cl_khr_d3d11_sharing */ -#define cl_khr_d3d11_sharing 1 - -typedef cl_uint cl_d3d11_device_source_khr; -typedef cl_uint cl_d3d11_device_set_khr; - -/******************************************************************************/ - -/* Error Codes */ -#define CL_INVALID_D3D11_DEVICE_KHR -1006 -#define CL_INVALID_D3D11_RESOURCE_KHR -1007 -#define CL_D3D11_RESOURCE_ALREADY_ACQUIRED_KHR -1008 -#define CL_D3D11_RESOURCE_NOT_ACQUIRED_KHR -1009 - -/* cl_d3d11_device_source */ -#define CL_D3D11_DEVICE_KHR 0x4019 -#define CL_D3D11_DXGI_ADAPTER_KHR 0x401A - -/* cl_d3d11_device_set */ -#define CL_PREFERRED_DEVICES_FOR_D3D11_KHR 0x401B -#define CL_ALL_DEVICES_FOR_D3D11_KHR 0x401C - -/* cl_context_info */ -#define CL_CONTEXT_D3D11_DEVICE_KHR 0x401D -#define CL_CONTEXT_D3D11_PREFER_SHARED_RESOURCES_KHR 0x402D - -/* cl_mem_info */ -#define CL_MEM_D3D11_RESOURCE_KHR 0x401E - -/* cl_image_info */ -#define CL_IMAGE_D3D11_SUBRESOURCE_KHR 0x401F - -/* cl_command_type */ -#define CL_COMMAND_ACQUIRE_D3D11_OBJECTS_KHR 0x4020 -#define CL_COMMAND_RELEASE_D3D11_OBJECTS_KHR 0x4021 - -/******************************************************************************/ - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clGetDeviceIDsFromD3D11KHR_fn)( - cl_platform_id platform, - cl_d3d11_device_source_khr d3d_device_source, - void * d3d_object, - cl_d3d11_device_set_khr d3d_device_set, - cl_uint num_entries, - cl_device_id * devices, - cl_uint * num_devices) CL_API_SUFFIX__VERSION_1_2; - -typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D11BufferKHR_fn)( - cl_context context, - cl_mem_flags flags, - ID3D11Buffer * resource, - cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_2; - -typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D11Texture2DKHR_fn)( - cl_context context, - cl_mem_flags flags, - ID3D11Texture2D * resource, - UINT subresource, - cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_2; - -typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D11Texture3DKHR_fn)( - cl_context context, - cl_mem_flags flags, - ID3D11Texture3D * resource, - UINT subresource, - cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_2; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueAcquireD3D11ObjectsKHR_fn)( - cl_command_queue command_queue, - cl_uint num_objects, - const cl_mem * mem_objects, - cl_uint num_events_in_wait_list, - const cl_event * event_wait_list, - cl_event * event) CL_API_SUFFIX__VERSION_1_2; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueReleaseD3D11ObjectsKHR_fn)( - cl_command_queue command_queue, - cl_uint num_objects, - const cl_mem * mem_objects, - cl_uint num_events_in_wait_list, - const cl_event * event_wait_list, - cl_event * event) CL_API_SUFFIX__VERSION_1_2; - -#ifdef __cplusplus -} -#endif - -#endif /* __OPENCL_CL_D3D11_H */ - diff --git a/third_party/opencl/include/CL/cl_dx9_media_sharing.h b/third_party/opencl/include/CL/cl_dx9_media_sharing.h deleted file mode 100644 index 2729e8b9e..000000000 --- a/third_party/opencl/include/CL/cl_dx9_media_sharing.h +++ /dev/null @@ -1,132 +0,0 @@ -/********************************************************************************** - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - **********************************************************************************/ - -/* $Revision: 11708 $ on $Date: 2010-06-13 23:36:24 -0700 (Sun, 13 Jun 2010) $ */ - -#ifndef __OPENCL_CL_DX9_MEDIA_SHARING_H -#define __OPENCL_CL_DX9_MEDIA_SHARING_H - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/******************************************************************************/ -/* cl_khr_dx9_media_sharing */ -#define cl_khr_dx9_media_sharing 1 - -typedef cl_uint cl_dx9_media_adapter_type_khr; -typedef cl_uint cl_dx9_media_adapter_set_khr; - -#if defined(_WIN32) -#include -typedef struct _cl_dx9_surface_info_khr -{ - IDirect3DSurface9 *resource; - HANDLE shared_handle; -} cl_dx9_surface_info_khr; -#endif - - -/******************************************************************************/ - -/* Error Codes */ -#define CL_INVALID_DX9_MEDIA_ADAPTER_KHR -1010 -#define CL_INVALID_DX9_MEDIA_SURFACE_KHR -1011 -#define CL_DX9_MEDIA_SURFACE_ALREADY_ACQUIRED_KHR -1012 -#define CL_DX9_MEDIA_SURFACE_NOT_ACQUIRED_KHR -1013 - -/* cl_media_adapter_type_khr */ -#define CL_ADAPTER_D3D9_KHR 0x2020 -#define CL_ADAPTER_D3D9EX_KHR 0x2021 -#define CL_ADAPTER_DXVA_KHR 0x2022 - -/* cl_media_adapter_set_khr */ -#define CL_PREFERRED_DEVICES_FOR_DX9_MEDIA_ADAPTER_KHR 0x2023 -#define CL_ALL_DEVICES_FOR_DX9_MEDIA_ADAPTER_KHR 0x2024 - -/* cl_context_info */ -#define CL_CONTEXT_ADAPTER_D3D9_KHR 0x2025 -#define CL_CONTEXT_ADAPTER_D3D9EX_KHR 0x2026 -#define CL_CONTEXT_ADAPTER_DXVA_KHR 0x2027 - -/* cl_mem_info */ -#define CL_MEM_DX9_MEDIA_ADAPTER_TYPE_KHR 0x2028 -#define CL_MEM_DX9_MEDIA_SURFACE_INFO_KHR 0x2029 - -/* cl_image_info */ -#define CL_IMAGE_DX9_MEDIA_PLANE_KHR 0x202A - -/* cl_command_type */ -#define CL_COMMAND_ACQUIRE_DX9_MEDIA_SURFACES_KHR 0x202B -#define CL_COMMAND_RELEASE_DX9_MEDIA_SURFACES_KHR 0x202C - -/******************************************************************************/ - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clGetDeviceIDsFromDX9MediaAdapterKHR_fn)( - cl_platform_id platform, - cl_uint num_media_adapters, - cl_dx9_media_adapter_type_khr * media_adapter_type, - void * media_adapters, - cl_dx9_media_adapter_set_khr media_adapter_set, - cl_uint num_entries, - cl_device_id * devices, - cl_uint * num_devices) CL_API_SUFFIX__VERSION_1_2; - -typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromDX9MediaSurfaceKHR_fn)( - cl_context context, - cl_mem_flags flags, - cl_dx9_media_adapter_type_khr adapter_type, - void * surface_info, - cl_uint plane, - cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_2; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueAcquireDX9MediaSurfacesKHR_fn)( - cl_command_queue command_queue, - cl_uint num_objects, - const cl_mem * mem_objects, - cl_uint num_events_in_wait_list, - const cl_event * event_wait_list, - cl_event * event) CL_API_SUFFIX__VERSION_1_2; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueReleaseDX9MediaSurfacesKHR_fn)( - cl_command_queue command_queue, - cl_uint num_objects, - const cl_mem * mem_objects, - cl_uint num_events_in_wait_list, - const cl_event * event_wait_list, - cl_event * event) CL_API_SUFFIX__VERSION_1_2; - -#ifdef __cplusplus -} -#endif - -#endif /* __OPENCL_CL_DX9_MEDIA_SHARING_H */ - diff --git a/third_party/opencl/include/CL/cl_egl.h b/third_party/opencl/include/CL/cl_egl.h deleted file mode 100644 index a765bd526..000000000 --- a/third_party/opencl/include/CL/cl_egl.h +++ /dev/null @@ -1,136 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - ******************************************************************************/ - -#ifndef __OPENCL_CL_EGL_H -#define __OPENCL_CL_EGL_H - -#ifdef __APPLE__ - -#else -#include -#endif - -#ifdef __cplusplus -extern "C" { -#endif - - -/* Command type for events created with clEnqueueAcquireEGLObjectsKHR */ -#define CL_COMMAND_EGL_FENCE_SYNC_OBJECT_KHR 0x202F -#define CL_COMMAND_ACQUIRE_EGL_OBJECTS_KHR 0x202D -#define CL_COMMAND_RELEASE_EGL_OBJECTS_KHR 0x202E - -/* Error type for clCreateFromEGLImageKHR */ -#define CL_INVALID_EGL_OBJECT_KHR -1093 -#define CL_EGL_RESOURCE_NOT_ACQUIRED_KHR -1092 - -/* CLeglImageKHR is an opaque handle to an EGLImage */ -typedef void* CLeglImageKHR; - -/* CLeglDisplayKHR is an opaque handle to an EGLDisplay */ -typedef void* CLeglDisplayKHR; - -/* CLeglSyncKHR is an opaque handle to an EGLSync object */ -typedef void* CLeglSyncKHR; - -/* properties passed to clCreateFromEGLImageKHR */ -typedef intptr_t cl_egl_image_properties_khr; - - -#define cl_khr_egl_image 1 - -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreateFromEGLImageKHR(cl_context /* context */, - CLeglDisplayKHR /* egldisplay */, - CLeglImageKHR /* eglimage */, - cl_mem_flags /* flags */, - const cl_egl_image_properties_khr * /* properties */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromEGLImageKHR_fn)( - cl_context context, - CLeglDisplayKHR egldisplay, - CLeglImageKHR eglimage, - cl_mem_flags flags, - const cl_egl_image_properties_khr * properties, - cl_int * errcode_ret); - - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueAcquireEGLObjectsKHR(cl_command_queue /* command_queue */, - cl_uint /* num_objects */, - const cl_mem * /* mem_objects */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueAcquireEGLObjectsKHR_fn)( - cl_command_queue command_queue, - cl_uint num_objects, - const cl_mem * mem_objects, - cl_uint num_events_in_wait_list, - const cl_event * event_wait_list, - cl_event * event); - - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueReleaseEGLObjectsKHR(cl_command_queue /* command_queue */, - cl_uint /* num_objects */, - const cl_mem * /* mem_objects */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueReleaseEGLObjectsKHR_fn)( - cl_command_queue command_queue, - cl_uint num_objects, - const cl_mem * mem_objects, - cl_uint num_events_in_wait_list, - const cl_event * event_wait_list, - cl_event * event); - - -#define cl_khr_egl_event 1 - -extern CL_API_ENTRY cl_event CL_API_CALL -clCreateEventFromEGLSyncKHR(cl_context /* context */, - CLeglSyncKHR /* sync */, - CLeglDisplayKHR /* display */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_event (CL_API_CALL *clCreateEventFromEGLSyncKHR_fn)( - cl_context context, - CLeglSyncKHR sync, - CLeglDisplayKHR display, - cl_int * errcode_ret); - -#ifdef __cplusplus -} -#endif - -#endif /* __OPENCL_CL_EGL_H */ diff --git a/third_party/opencl/include/CL/cl_ext.h b/third_party/opencl/include/CL/cl_ext.h deleted file mode 100644 index 794158389..000000000 --- a/third_party/opencl/include/CL/cl_ext.h +++ /dev/null @@ -1,391 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - ******************************************************************************/ - -/* $Revision: 11928 $ on $Date: 2010-07-13 09:04:56 -0700 (Tue, 13 Jul 2010) $ */ - -/* cl_ext.h contains OpenCL extensions which don't have external */ -/* (OpenGL, D3D) dependencies. */ - -#ifndef __CL_EXT_H -#define __CL_EXT_H - -#ifdef __cplusplus -extern "C" { -#endif - -#ifdef __APPLE__ - #include - #include -#else - #include -#endif - -/* cl_khr_fp16 extension - no extension #define since it has no functions */ -#define CL_DEVICE_HALF_FP_CONFIG 0x1033 - -/* Memory object destruction - * - * Apple extension for use to manage externally allocated buffers used with cl_mem objects with CL_MEM_USE_HOST_PTR - * - * Registers a user callback function that will be called when the memory object is deleted and its resources - * freed. Each call to clSetMemObjectCallbackFn registers the specified user callback function on a callback - * stack associated with memobj. The registered user callback functions are called in the reverse order in - * which they were registered. The user callback functions are called and then the memory object is deleted - * and its resources freed. This provides a mechanism for the application (and libraries) using memobj to be - * notified when the memory referenced by host_ptr, specified when the memory object is created and used as - * the storage bits for the memory object, can be reused or freed. - * - * The application may not call CL api's with the cl_mem object passed to the pfn_notify. - * - * Please check for the "cl_APPLE_SetMemObjectDestructor" extension using clGetDeviceInfo(CL_DEVICE_EXTENSIONS) - * before using. - */ -#define cl_APPLE_SetMemObjectDestructor 1 -cl_int CL_API_ENTRY clSetMemObjectDestructorAPPLE( cl_mem /* memobj */, - void (* /*pfn_notify*/)( cl_mem /* memobj */, void* /*user_data*/), - void * /*user_data */ ) CL_EXT_SUFFIX__VERSION_1_0; - - -/* Context Logging Functions - * - * The next three convenience functions are intended to be used as the pfn_notify parameter to clCreateContext(). - * Please check for the "cl_APPLE_ContextLoggingFunctions" extension using clGetDeviceInfo(CL_DEVICE_EXTENSIONS) - * before using. - * - * clLogMessagesToSystemLog fowards on all log messages to the Apple System Logger - */ -#define cl_APPLE_ContextLoggingFunctions 1 -extern void CL_API_ENTRY clLogMessagesToSystemLogAPPLE( const char * /* errstr */, - const void * /* private_info */, - size_t /* cb */, - void * /* user_data */ ) CL_EXT_SUFFIX__VERSION_1_0; - -/* clLogMessagesToStdout sends all log messages to the file descriptor stdout */ -extern void CL_API_ENTRY clLogMessagesToStdoutAPPLE( const char * /* errstr */, - const void * /* private_info */, - size_t /* cb */, - void * /* user_data */ ) CL_EXT_SUFFIX__VERSION_1_0; - -/* clLogMessagesToStderr sends all log messages to the file descriptor stderr */ -extern void CL_API_ENTRY clLogMessagesToStderrAPPLE( const char * /* errstr */, - const void * /* private_info */, - size_t /* cb */, - void * /* user_data */ ) CL_EXT_SUFFIX__VERSION_1_0; - - -/************************ -* cl_khr_icd extension * -************************/ -#define cl_khr_icd 1 - -/* cl_platform_info */ -#define CL_PLATFORM_ICD_SUFFIX_KHR 0x0920 - -/* Additional Error Codes */ -#define CL_PLATFORM_NOT_FOUND_KHR -1001 - -extern CL_API_ENTRY cl_int CL_API_CALL -clIcdGetPlatformIDsKHR(cl_uint /* num_entries */, - cl_platform_id * /* platforms */, - cl_uint * /* num_platforms */); - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clIcdGetPlatformIDsKHR_fn)( - cl_uint /* num_entries */, - cl_platform_id * /* platforms */, - cl_uint * /* num_platforms */); - - -/* Extension: cl_khr_image2D_buffer - * - * This extension allows a 2D image to be created from a cl_mem buffer without a copy. - * The type associated with a 2D image created from a buffer in an OpenCL program is image2d_t. - * Both the sampler and sampler-less read_image built-in functions are supported for 2D images - * and 2D images created from a buffer. Similarly, the write_image built-ins are also supported - * for 2D images created from a buffer. - * - * When the 2D image from buffer is created, the client must specify the width, - * height, image format (i.e. channel order and channel data type) and optionally the row pitch - * - * The pitch specified must be a multiple of CL_DEVICE_IMAGE_PITCH_ALIGNMENT pixels. - * The base address of the buffer must be aligned to CL_DEVICE_IMAGE_BASE_ADDRESS_ALIGNMENT pixels. - */ - -/************************************* - * cl_khr_initalize_memory extension * - *************************************/ - -#define CL_CONTEXT_MEMORY_INITIALIZE_KHR 0x2030 - - -/************************************** - * cl_khr_terminate_context extension * - **************************************/ - -#define CL_DEVICE_TERMINATE_CAPABILITY_KHR 0x2031 -#define CL_CONTEXT_TERMINATE_KHR 0x2032 - -#define cl_khr_terminate_context 1 -extern CL_API_ENTRY cl_int CL_API_CALL clTerminateContextKHR(cl_context /* context */) CL_EXT_SUFFIX__VERSION_1_2; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clTerminateContextKHR_fn)(cl_context /* context */) CL_EXT_SUFFIX__VERSION_1_2; - - -/* - * Extension: cl_khr_spir - * - * This extension adds support to create an OpenCL program object from a - * Standard Portable Intermediate Representation (SPIR) instance - */ - -#define CL_DEVICE_SPIR_VERSIONS 0x40E0 -#define CL_PROGRAM_BINARY_TYPE_INTERMEDIATE 0x40E1 - - -/****************************************** -* cl_nv_device_attribute_query extension * -******************************************/ -/* cl_nv_device_attribute_query extension - no extension #define since it has no functions */ -#define CL_DEVICE_COMPUTE_CAPABILITY_MAJOR_NV 0x4000 -#define CL_DEVICE_COMPUTE_CAPABILITY_MINOR_NV 0x4001 -#define CL_DEVICE_REGISTERS_PER_BLOCK_NV 0x4002 -#define CL_DEVICE_WARP_SIZE_NV 0x4003 -#define CL_DEVICE_GPU_OVERLAP_NV 0x4004 -#define CL_DEVICE_KERNEL_EXEC_TIMEOUT_NV 0x4005 -#define CL_DEVICE_INTEGRATED_MEMORY_NV 0x4006 - -/********************************* -* cl_amd_device_attribute_query * -*********************************/ -#define CL_DEVICE_PROFILING_TIMER_OFFSET_AMD 0x4036 - -/********************************* -* cl_arm_printf extension -*********************************/ -#define CL_PRINTF_CALLBACK_ARM 0x40B0 -#define CL_PRINTF_BUFFERSIZE_ARM 0x40B1 - -#ifdef CL_VERSION_1_1 - /*********************************** - * cl_ext_device_fission extension * - ***********************************/ - #define cl_ext_device_fission 1 - - extern CL_API_ENTRY cl_int CL_API_CALL - clReleaseDeviceEXT( cl_device_id /*device*/ ) CL_EXT_SUFFIX__VERSION_1_1; - - typedef CL_API_ENTRY cl_int - (CL_API_CALL *clReleaseDeviceEXT_fn)( cl_device_id /*device*/ ) CL_EXT_SUFFIX__VERSION_1_1; - - extern CL_API_ENTRY cl_int CL_API_CALL - clRetainDeviceEXT( cl_device_id /*device*/ ) CL_EXT_SUFFIX__VERSION_1_1; - - typedef CL_API_ENTRY cl_int - (CL_API_CALL *clRetainDeviceEXT_fn)( cl_device_id /*device*/ ) CL_EXT_SUFFIX__VERSION_1_1; - - typedef cl_ulong cl_device_partition_property_ext; - extern CL_API_ENTRY cl_int CL_API_CALL - clCreateSubDevicesEXT( cl_device_id /*in_device*/, - const cl_device_partition_property_ext * /* properties */, - cl_uint /*num_entries*/, - cl_device_id * /*out_devices*/, - cl_uint * /*num_devices*/ ) CL_EXT_SUFFIX__VERSION_1_1; - - typedef CL_API_ENTRY cl_int - ( CL_API_CALL * clCreateSubDevicesEXT_fn)( cl_device_id /*in_device*/, - const cl_device_partition_property_ext * /* properties */, - cl_uint /*num_entries*/, - cl_device_id * /*out_devices*/, - cl_uint * /*num_devices*/ ) CL_EXT_SUFFIX__VERSION_1_1; - - /* cl_device_partition_property_ext */ - #define CL_DEVICE_PARTITION_EQUALLY_EXT 0x4050 - #define CL_DEVICE_PARTITION_BY_COUNTS_EXT 0x4051 - #define CL_DEVICE_PARTITION_BY_NAMES_EXT 0x4052 - #define CL_DEVICE_PARTITION_BY_AFFINITY_DOMAIN_EXT 0x4053 - - /* clDeviceGetInfo selectors */ - #define CL_DEVICE_PARENT_DEVICE_EXT 0x4054 - #define CL_DEVICE_PARTITION_TYPES_EXT 0x4055 - #define CL_DEVICE_AFFINITY_DOMAINS_EXT 0x4056 - #define CL_DEVICE_REFERENCE_COUNT_EXT 0x4057 - #define CL_DEVICE_PARTITION_STYLE_EXT 0x4058 - - /* error codes */ - #define CL_DEVICE_PARTITION_FAILED_EXT -1057 - #define CL_INVALID_PARTITION_COUNT_EXT -1058 - #define CL_INVALID_PARTITION_NAME_EXT -1059 - - /* CL_AFFINITY_DOMAINs */ - #define CL_AFFINITY_DOMAIN_L1_CACHE_EXT 0x1 - #define CL_AFFINITY_DOMAIN_L2_CACHE_EXT 0x2 - #define CL_AFFINITY_DOMAIN_L3_CACHE_EXT 0x3 - #define CL_AFFINITY_DOMAIN_L4_CACHE_EXT 0x4 - #define CL_AFFINITY_DOMAIN_NUMA_EXT 0x10 - #define CL_AFFINITY_DOMAIN_NEXT_FISSIONABLE_EXT 0x100 - - /* cl_device_partition_property_ext list terminators */ - #define CL_PROPERTIES_LIST_END_EXT ((cl_device_partition_property_ext) 0) - #define CL_PARTITION_BY_COUNTS_LIST_END_EXT ((cl_device_partition_property_ext) 0) - #define CL_PARTITION_BY_NAMES_LIST_END_EXT ((cl_device_partition_property_ext) 0 - 1) - -/********************************* -* cl_qcom_ext_host_ptr extension -*********************************/ - -#define CL_MEM_EXT_HOST_PTR_QCOM (1 << 29) - -#define CL_DEVICE_EXT_MEM_PADDING_IN_BYTES_QCOM 0x40A0 -#define CL_DEVICE_PAGE_SIZE_QCOM 0x40A1 -#define CL_IMAGE_ROW_ALIGNMENT_QCOM 0x40A2 -#define CL_IMAGE_SLICE_ALIGNMENT_QCOM 0x40A3 -#define CL_MEM_HOST_UNCACHED_QCOM 0x40A4 -#define CL_MEM_HOST_WRITEBACK_QCOM 0x40A5 -#define CL_MEM_HOST_WRITETHROUGH_QCOM 0x40A6 -#define CL_MEM_HOST_WRITE_COMBINING_QCOM 0x40A7 - -typedef cl_uint cl_image_pitch_info_qcom; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetDeviceImageInfoQCOM(cl_device_id device, - size_t image_width, - size_t image_height, - const cl_image_format *image_format, - cl_image_pitch_info_qcom param_name, - size_t param_value_size, - void *param_value, - size_t *param_value_size_ret); - -typedef struct _cl_mem_ext_host_ptr -{ - /* Type of external memory allocation. */ - /* Legal values will be defined in layered extensions. */ - cl_uint allocation_type; - - /* Host cache policy for this external memory allocation. */ - cl_uint host_cache_policy; - -} cl_mem_ext_host_ptr; - -/********************************* -* cl_qcom_ion_host_ptr extension -*********************************/ - -#define CL_MEM_ION_HOST_PTR_QCOM 0x40A8 - -typedef struct _cl_mem_ion_host_ptr -{ - /* Type of external memory allocation. */ - /* Must be CL_MEM_ION_HOST_PTR_QCOM for ION allocations. */ - cl_mem_ext_host_ptr ext_host_ptr; - - /* ION file descriptor */ - int ion_filedesc; - - /* Host pointer to the ION allocated memory */ - void* ion_hostptr; - -} cl_mem_ion_host_ptr; - -#endif /* CL_VERSION_1_1 */ - - -#ifdef CL_VERSION_2_0 -/********************************* -* cl_khr_sub_groups extension -*********************************/ -#define cl_khr_sub_groups 1 - -typedef cl_uint cl_kernel_sub_group_info_khr; - -/* cl_khr_sub_group_info */ -#define CL_KERNEL_MAX_SUB_GROUP_SIZE_FOR_NDRANGE_KHR 0x2033 -#define CL_KERNEL_SUB_GROUP_COUNT_FOR_NDRANGE_KHR 0x2034 - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetKernelSubGroupInfoKHR(cl_kernel /* in_kernel */, - cl_device_id /*in_device*/, - cl_kernel_sub_group_info_khr /* param_name */, - size_t /*input_value_size*/, - const void * /*input_value*/, - size_t /*param_value_size*/, - void* /*param_value*/, - size_t* /*param_value_size_ret*/ ) CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED; - -typedef CL_API_ENTRY cl_int - ( CL_API_CALL * clGetKernelSubGroupInfoKHR_fn)(cl_kernel /* in_kernel */, - cl_device_id /*in_device*/, - cl_kernel_sub_group_info_khr /* param_name */, - size_t /*input_value_size*/, - const void * /*input_value*/, - size_t /*param_value_size*/, - void* /*param_value*/, - size_t* /*param_value_size_ret*/ ) CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED; -#endif /* CL_VERSION_2_0 */ - -#ifdef CL_VERSION_2_1 -/********************************* -* cl_khr_priority_hints extension -*********************************/ -#define cl_khr_priority_hints 1 - -typedef cl_uint cl_queue_priority_khr; - -/* cl_command_queue_properties */ -#define CL_QUEUE_PRIORITY_KHR 0x1096 - -/* cl_queue_priority_khr */ -#define CL_QUEUE_PRIORITY_HIGH_KHR (1<<0) -#define CL_QUEUE_PRIORITY_MED_KHR (1<<1) -#define CL_QUEUE_PRIORITY_LOW_KHR (1<<2) - -#endif /* CL_VERSION_2_1 */ - -#ifdef CL_VERSION_2_1 -/********************************* -* cl_khr_throttle_hints extension -*********************************/ -#define cl_khr_throttle_hints 1 - -typedef cl_uint cl_queue_throttle_khr; - -/* cl_command_queue_properties */ -#define CL_QUEUE_THROTTLE_KHR 0x1097 - -/* cl_queue_throttle_khr */ -#define CL_QUEUE_THROTTLE_HIGH_KHR (1<<0) -#define CL_QUEUE_THROTTLE_MED_KHR (1<<1) -#define CL_QUEUE_THROTTLE_LOW_KHR (1<<2) - -#endif /* CL_VERSION_2_1 */ - -#ifdef __cplusplus -} -#endif - - -#endif /* __CL_EXT_H */ diff --git a/third_party/opencl/include/CL/cl_ext_qcom.h b/third_party/opencl/include/CL/cl_ext_qcom.h deleted file mode 100644 index 6328a1cd9..000000000 --- a/third_party/opencl/include/CL/cl_ext_qcom.h +++ /dev/null @@ -1,255 +0,0 @@ -/* Copyright (c) 2009-2017 Qualcomm Technologies, Inc. All Rights Reserved. - * Qualcomm Technologies Proprietary and Confidential. - */ - -#ifndef __OPENCL_CL_EXT_QCOM_H -#define __OPENCL_CL_EXT_QCOM_H - -// Needed by cl_khr_egl_event extension -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - - -/************************************ - * cl_qcom_create_buffer_from_image * - ************************************/ - -#define CL_BUFFER_FROM_IMAGE_ROW_PITCH_QCOM 0x40C0 -#define CL_BUFFER_FROM_IMAGE_SLICE_PITCH_QCOM 0x40C1 - -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreateBufferFromImageQCOM(cl_mem image, - cl_mem_flags flags, - cl_int *errcode_ret); - - -/************************************ - * cl_qcom_limited_printf extension * - ************************************/ - -/* Builtin printf function buffer size in bytes. */ -#define CL_DEVICE_PRINTF_BUFFER_SIZE_QCOM 0x1049 - - -/************************************* - * cl_qcom_extended_images extension * - *************************************/ - -#define CL_CONTEXT_ENABLE_EXTENDED_IMAGES_QCOM 0x40AA -#define CL_DEVICE_EXTENDED_IMAGE2D_MAX_WIDTH_QCOM 0x40AB -#define CL_DEVICE_EXTENDED_IMAGE2D_MAX_HEIGHT_QCOM 0x40AC -#define CL_DEVICE_EXTENDED_IMAGE3D_MAX_WIDTH_QCOM 0x40AD -#define CL_DEVICE_EXTENDED_IMAGE3D_MAX_HEIGHT_QCOM 0x40AE -#define CL_DEVICE_EXTENDED_IMAGE3D_MAX_DEPTH_QCOM 0x40AF - -/************************************* - * cl_qcom_perf_hint extension * - *************************************/ - -typedef cl_uint cl_perf_hint; - -#define CL_CONTEXT_PERF_HINT_QCOM 0x40C2 - -/*cl_perf_hint*/ -#define CL_PERF_HINT_HIGH_QCOM 0x40C3 -#define CL_PERF_HINT_NORMAL_QCOM 0x40C4 -#define CL_PERF_HINT_LOW_QCOM 0x40C5 - -extern CL_API_ENTRY cl_int CL_API_CALL -clSetPerfHintQCOM(cl_context context, - cl_perf_hint perf_hint); - -// This extension is published at Khronos, so its definitions are made in cl_ext.h. -// This duplication is for backward compatibility. - -#ifndef CL_MEM_ANDROID_NATIVE_BUFFER_HOST_PTR_QCOM - -/********************************* -* cl_qcom_android_native_buffer_host_ptr extension -*********************************/ - -#define CL_MEM_ANDROID_NATIVE_BUFFER_HOST_PTR_QCOM 0x40C6 - - -typedef struct _cl_mem_android_native_buffer_host_ptr -{ - // Type of external memory allocation. - // Must be CL_MEM_ANDROID_NATIVE_BUFFER_HOST_PTR_QCOM for Android native buffers. - cl_mem_ext_host_ptr ext_host_ptr; - - // Virtual pointer to the android native buffer - void* anb_ptr; - -} cl_mem_android_native_buffer_host_ptr; - -#endif //#ifndef CL_MEM_ANDROID_NATIVE_BUFFER_HOST_PTR_QCOM - -/*********************************** -* cl_img_egl_image extension * -************************************/ -typedef void* CLeglImageIMG; -typedef void* CLeglDisplayIMG; - -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreateFromEGLImageIMG(cl_context context, - cl_mem_flags flags, - CLeglImageIMG image, - CLeglDisplayIMG display, - cl_int *errcode_ret); - - -/********************************* -* cl_qcom_other_image extension -*********************************/ - -// Extended flag for creating/querying QCOM non-standard images -#define CL_MEM_OTHER_IMAGE_QCOM (1<<25) - -// cl_channel_type -#define CL_QCOM_UNORM_MIPI10 0x4159 -#define CL_QCOM_UNORM_MIPI12 0x415A -#define CL_QCOM_UNSIGNED_MIPI10 0x415B -#define CL_QCOM_UNSIGNED_MIPI12 0x415C -#define CL_QCOM_UNORM_INT10 0x415D -#define CL_QCOM_UNORM_INT12 0x415E -#define CL_QCOM_UNSIGNED_INT16 0x415F - -// cl_channel_order -// Dedicate 0x4130-0x415F range for QCOM extended image formats -// 0x4130 - 0x4132 range is assigned to pixel-oriented compressed format -#define CL_QCOM_BAYER 0x414E - -#define CL_QCOM_NV12 0x4133 -#define CL_QCOM_NV12_Y 0x4134 -#define CL_QCOM_NV12_UV 0x4135 - -#define CL_QCOM_TILED_NV12 0x4136 -#define CL_QCOM_TILED_NV12_Y 0x4137 -#define CL_QCOM_TILED_NV12_UV 0x4138 - -#define CL_QCOM_P010 0x413C -#define CL_QCOM_P010_Y 0x413D -#define CL_QCOM_P010_UV 0x413E - -#define CL_QCOM_TILED_P010 0x413F -#define CL_QCOM_TILED_P010_Y 0x4140 -#define CL_QCOM_TILED_P010_UV 0x4141 - - -#define CL_QCOM_TP10 0x4145 -#define CL_QCOM_TP10_Y 0x4146 -#define CL_QCOM_TP10_UV 0x4147 - -#define CL_QCOM_TILED_TP10 0x4148 -#define CL_QCOM_TILED_TP10_Y 0x4149 -#define CL_QCOM_TILED_TP10_UV 0x414A - -/********************************* -* cl_qcom_compressed_image extension -*********************************/ - -// Extended flag for creating/querying QCOM non-planar compressed images -#define CL_MEM_COMPRESSED_IMAGE_QCOM (1<<27) - -// Extended image format -// cl_channel_order -#define CL_QCOM_COMPRESSED_RGBA 0x4130 -#define CL_QCOM_COMPRESSED_RGBx 0x4131 - -#define CL_QCOM_COMPRESSED_NV12_Y 0x413A -#define CL_QCOM_COMPRESSED_NV12_UV 0x413B - -#define CL_QCOM_COMPRESSED_P010 0x4142 -#define CL_QCOM_COMPRESSED_P010_Y 0x4143 -#define CL_QCOM_COMPRESSED_P010_UV 0x4144 - -#define CL_QCOM_COMPRESSED_TP10 0x414B -#define CL_QCOM_COMPRESSED_TP10_Y 0x414C -#define CL_QCOM_COMPRESSED_TP10_UV 0x414D - -#define CL_QCOM_COMPRESSED_NV12_4R 0x414F -#define CL_QCOM_COMPRESSED_NV12_4R_Y 0x4150 -#define CL_QCOM_COMPRESSED_NV12_4R_UV 0x4151 -/********************************* -* cl_qcom_compressed_yuv_image_read extension -*********************************/ - -// Extended flag for creating/querying QCOM compressed images -#define CL_MEM_COMPRESSED_YUV_IMAGE_QCOM (1<<28) - -// Extended image format -#define CL_QCOM_COMPRESSED_NV12 0x10C4 - -// Extended flag for setting ION buffer allocation type -#define CL_MEM_ION_HOST_PTR_COMPRESSED_YUV_QCOM 0x40CD -#define CL_MEM_ION_HOST_PTR_PROTECTED_COMPRESSED_YUV_QCOM 0x40CE - -/********************************* -* cl_qcom_accelerated_image_ops -*********************************/ -#define CL_MEM_OBJECT_WEIGHT_IMAGE_QCOM 0x4110 -#define CL_DEVICE_HOF_MAX_NUM_PHASES_QCOM 0x4111 -#define CL_DEVICE_HOF_MAX_FILTER_SIZE_X_QCOM 0x4112 -#define CL_DEVICE_HOF_MAX_FILTER_SIZE_Y_QCOM 0x4113 -#define CL_DEVICE_BLOCK_MATCHING_MAX_REGION_SIZE_X_QCOM 0x4114 -#define CL_DEVICE_BLOCK_MATCHING_MAX_REGION_SIZE_Y_QCOM 0x4115 - -//Extended flag for specifying weight image type -#define CL_WEIGHT_IMAGE_SEPARABLE_QCOM (1<<0) - -// Box Filter -typedef struct _cl_box_filter_size_qcom -{ - // Width of box filter on X direction. - float box_filter_width; - - // Height of box filter on Y direction. - float box_filter_height; -} cl_box_filter_size_qcom; - -// HOF Weight Image Desc -typedef struct _cl_weight_desc_qcom -{ - /** Coordinate of the "center" point of the weight image, - based on the weight image's top-left corner as the origin. */ - size_t center_coord_x; - size_t center_coord_y; - cl_bitfield flags; -} cl_weight_desc_qcom; - -typedef struct _cl_weight_image_desc_qcom -{ - cl_image_desc image_desc; - cl_weight_desc_qcom weight_desc; -} cl_weight_image_desc_qcom; - -/************************************* - * cl_qcom_protected_context extension * - *************************************/ - -#define CL_CONTEXT_PROTECTED_QCOM 0x40C7 -#define CL_MEM_ION_HOST_PTR_PROTECTED_QCOM 0x40C8 - -/************************************* - * cl_qcom_priority_hint extension * - *************************************/ -#define CL_PRIORITY_HINT_NONE_QCOM 0 -typedef cl_uint cl_priority_hint; - -#define CL_CONTEXT_PRIORITY_HINT_QCOM 0x40C9 - -/*cl_priority_hint*/ -#define CL_PRIORITY_HINT_HIGH_QCOM 0x40CA -#define CL_PRIORITY_HINT_NORMAL_QCOM 0x40CB -#define CL_PRIORITY_HINT_LOW_QCOM 0x40CC - -#ifdef __cplusplus -} -#endif - -#endif /* __OPENCL_CL_EXT_QCOM_H */ diff --git a/third_party/opencl/include/CL/cl_gl.h b/third_party/opencl/include/CL/cl_gl.h deleted file mode 100644 index 945daa83d..000000000 --- a/third_party/opencl/include/CL/cl_gl.h +++ /dev/null @@ -1,167 +0,0 @@ -/********************************************************************************** - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - **********************************************************************************/ - -#ifndef __OPENCL_CL_GL_H -#define __OPENCL_CL_GL_H - -#ifdef __APPLE__ -#include -#else -#include -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -typedef cl_uint cl_gl_object_type; -typedef cl_uint cl_gl_texture_info; -typedef cl_uint cl_gl_platform_info; -typedef struct __GLsync *cl_GLsync; - -/* cl_gl_object_type = 0x2000 - 0x200F enum values are currently taken */ -#define CL_GL_OBJECT_BUFFER 0x2000 -#define CL_GL_OBJECT_TEXTURE2D 0x2001 -#define CL_GL_OBJECT_TEXTURE3D 0x2002 -#define CL_GL_OBJECT_RENDERBUFFER 0x2003 -#define CL_GL_OBJECT_TEXTURE2D_ARRAY 0x200E -#define CL_GL_OBJECT_TEXTURE1D 0x200F -#define CL_GL_OBJECT_TEXTURE1D_ARRAY 0x2010 -#define CL_GL_OBJECT_TEXTURE_BUFFER 0x2011 - -/* cl_gl_texture_info */ -#define CL_GL_TEXTURE_TARGET 0x2004 -#define CL_GL_MIPMAP_LEVEL 0x2005 -#define CL_GL_NUM_SAMPLES 0x2012 - - -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreateFromGLBuffer(cl_context /* context */, - cl_mem_flags /* flags */, - cl_GLuint /* bufobj */, - int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreateFromGLTexture(cl_context /* context */, - cl_mem_flags /* flags */, - cl_GLenum /* target */, - cl_GLint /* miplevel */, - cl_GLuint /* texture */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreateFromGLRenderbuffer(cl_context /* context */, - cl_mem_flags /* flags */, - cl_GLuint /* renderbuffer */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetGLObjectInfo(cl_mem /* memobj */, - cl_gl_object_type * /* gl_object_type */, - cl_GLuint * /* gl_object_name */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetGLTextureInfo(cl_mem /* memobj */, - cl_gl_texture_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueAcquireGLObjects(cl_command_queue /* command_queue */, - cl_uint /* num_objects */, - const cl_mem * /* mem_objects */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueReleaseGLObjects(cl_command_queue /* command_queue */, - cl_uint /* num_objects */, - const cl_mem * /* mem_objects */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - - -/* Deprecated OpenCL 1.1 APIs */ -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_mem CL_API_CALL -clCreateFromGLTexture2D(cl_context /* context */, - cl_mem_flags /* flags */, - cl_GLenum /* target */, - cl_GLint /* miplevel */, - cl_GLuint /* texture */, - cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; - -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_mem CL_API_CALL -clCreateFromGLTexture3D(cl_context /* context */, - cl_mem_flags /* flags */, - cl_GLenum /* target */, - cl_GLint /* miplevel */, - cl_GLuint /* texture */, - cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; - -/* cl_khr_gl_sharing extension */ - -#define cl_khr_gl_sharing 1 - -typedef cl_uint cl_gl_context_info; - -/* Additional Error Codes */ -#define CL_INVALID_GL_SHAREGROUP_REFERENCE_KHR -1000 - -/* cl_gl_context_info */ -#define CL_CURRENT_DEVICE_FOR_GL_CONTEXT_KHR 0x2006 -#define CL_DEVICES_FOR_GL_CONTEXT_KHR 0x2007 - -/* Additional cl_context_properties */ -#define CL_GL_CONTEXT_KHR 0x2008 -#define CL_EGL_DISPLAY_KHR 0x2009 -#define CL_GLX_DISPLAY_KHR 0x200A -#define CL_WGL_HDC_KHR 0x200B -#define CL_CGL_SHAREGROUP_KHR 0x200C - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetGLContextInfoKHR(const cl_context_properties * /* properties */, - cl_gl_context_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clGetGLContextInfoKHR_fn)( - const cl_context_properties * properties, - cl_gl_context_info param_name, - size_t param_value_size, - void * param_value, - size_t * param_value_size_ret); - -#ifdef __cplusplus -} -#endif - -#endif /* __OPENCL_CL_GL_H */ diff --git a/third_party/opencl/include/CL/cl_gl_ext.h b/third_party/opencl/include/CL/cl_gl_ext.h deleted file mode 100644 index e3c14c640..000000000 --- a/third_party/opencl/include/CL/cl_gl_ext.h +++ /dev/null @@ -1,74 +0,0 @@ -/********************************************************************************** - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - **********************************************************************************/ - -/* $Revision: 11708 $ on $Date: 2010-06-13 23:36:24 -0700 (Sun, 13 Jun 2010) $ */ - -/* cl_gl_ext.h contains vendor (non-KHR) OpenCL extensions which have */ -/* OpenGL dependencies. */ - -#ifndef __OPENCL_CL_GL_EXT_H -#define __OPENCL_CL_GL_EXT_H - -#ifdef __cplusplus -extern "C" { -#endif - -#ifdef __APPLE__ - #include -#else - #include -#endif - -/* - * For each extension, follow this template - * cl_VEN_extname extension */ -/* #define cl_VEN_extname 1 - * ... define new types, if any - * ... define new tokens, if any - * ... define new APIs, if any - * - * If you need GLtypes here, mirror them with a cl_GLtype, rather than including a GL header - * This allows us to avoid having to decide whether to include GL headers or GLES here. - */ - -/* - * cl_khr_gl_event extension - * See section 9.9 in the OpenCL 1.1 spec for more information - */ -#define CL_COMMAND_GL_FENCE_SYNC_OBJECT_KHR 0x200D - -extern CL_API_ENTRY cl_event CL_API_CALL -clCreateEventFromGLsyncKHR(cl_context /* context */, - cl_GLsync /* cl_GLsync */, - cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_1; - -#ifdef __cplusplus -} -#endif - -#endif /* __OPENCL_CL_GL_EXT_H */ diff --git a/third_party/opencl/include/CL/cl_platform.h b/third_party/opencl/include/CL/cl_platform.h deleted file mode 100644 index 4e334a291..000000000 --- a/third_party/opencl/include/CL/cl_platform.h +++ /dev/null @@ -1,1333 +0,0 @@ -/********************************************************************************** - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - **********************************************************************************/ - -/* $Revision: 11803 $ on $Date: 2010-06-25 10:02:12 -0700 (Fri, 25 Jun 2010) $ */ - -#ifndef __CL_PLATFORM_H -#define __CL_PLATFORM_H - -#ifdef __APPLE__ - /* Contains #defines for AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER below */ - #include -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -#if defined(_WIN32) - #define CL_API_ENTRY - #define CL_API_CALL __stdcall - #define CL_CALLBACK __stdcall -#else - #define CL_API_ENTRY - #define CL_API_CALL - #define CL_CALLBACK -#endif - -/* - * Deprecation flags refer to the last version of the header in which the - * feature was not deprecated. - * - * E.g. VERSION_1_1_DEPRECATED means the feature is present in 1.1 without - * deprecation but is deprecated in versions later than 1.1. - */ - -#ifdef __APPLE__ - #define CL_EXTENSION_WEAK_LINK __attribute__((weak_import)) - #define CL_API_SUFFIX__VERSION_1_0 AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER - #define CL_EXT_SUFFIX__VERSION_1_0 CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER - #define CL_API_SUFFIX__VERSION_1_1 AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER - #define GCL_API_SUFFIX__VERSION_1_1 AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER - #define CL_EXT_SUFFIX__VERSION_1_1 CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER - #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_7 - - #ifdef AVAILABLE_MAC_OS_X_VERSION_10_8_AND_LATER - #define CL_API_SUFFIX__VERSION_1_2 AVAILABLE_MAC_OS_X_VERSION_10_8_AND_LATER - #define GCL_API_SUFFIX__VERSION_1_2 AVAILABLE_MAC_OS_X_VERSION_10_8_AND_LATER - #define CL_EXT_SUFFIX__VERSION_1_2 CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_8_AND_LATER - #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED - #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_8 - #else - #warning This path should never happen outside of internal operating system development. AvailabilityMacros do not function correctly here! - #define CL_API_SUFFIX__VERSION_1_2 AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER - #define GCL_API_SUFFIX__VERSION_1_2 AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER - #define CL_EXT_SUFFIX__VERSION_1_2 CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER - #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER - #endif -#else - #define CL_EXTENSION_WEAK_LINK - #define CL_API_SUFFIX__VERSION_1_0 - #define CL_EXT_SUFFIX__VERSION_1_0 - #define CL_API_SUFFIX__VERSION_1_1 - #define CL_EXT_SUFFIX__VERSION_1_1 - #define CL_API_SUFFIX__VERSION_1_2 - #define CL_EXT_SUFFIX__VERSION_1_2 - #define CL_API_SUFFIX__VERSION_2_0 - #define CL_EXT_SUFFIX__VERSION_2_0 - #define CL_API_SUFFIX__VERSION_2_1 - #define CL_EXT_SUFFIX__VERSION_2_1 - - #ifdef __GNUC__ - #ifdef CL_USE_DEPRECATED_OPENCL_1_0_APIS - #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_0_DEPRECATED - #else - #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED __attribute__((deprecated)) - #define CL_EXT_PREFIX__VERSION_1_0_DEPRECATED - #endif - - #ifdef CL_USE_DEPRECATED_OPENCL_1_1_APIS - #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED - #else - #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED __attribute__((deprecated)) - #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED - #endif - - #ifdef CL_USE_DEPRECATED_OPENCL_1_2_APIS - #define CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_2_DEPRECATED - #else - #define CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED __attribute__((deprecated)) - #define CL_EXT_PREFIX__VERSION_1_2_DEPRECATED - #endif - - #ifdef CL_USE_DEPRECATED_OPENCL_2_0_APIS - #define CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED - #define CL_EXT_PREFIX__VERSION_2_0_DEPRECATED - #else - #define CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED __attribute__((deprecated)) - #define CL_EXT_PREFIX__VERSION_2_0_DEPRECATED - #endif - #elif _WIN32 - #ifdef CL_USE_DEPRECATED_OPENCL_1_0_APIS - #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_0_DEPRECATED - #else - #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_0_DEPRECATED __declspec(deprecated) - #endif - - #ifdef CL_USE_DEPRECATED_OPENCL_1_1_APIS - #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED - #else - #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED __declspec(deprecated) - #endif - - #ifdef CL_USE_DEPRECATED_OPENCL_1_2_APIS - #define CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_2_DEPRECATED - #else - #define CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_2_DEPRECATED __declspec(deprecated) - #endif - - #ifdef CL_USE_DEPRECATED_OPENCL_2_0_APIS - #define CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED - #define CL_EXT_PREFIX__VERSION_2_0_DEPRECATED - #else - #define CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED - #define CL_EXT_PREFIX__VERSION_2_0_DEPRECATED __declspec(deprecated) - #endif - #else - #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_0_DEPRECATED - - #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED - - #define CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_2_DEPRECATED - - #define CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED - #define CL_EXT_PREFIX__VERSION_2_0_DEPRECATED - #endif -#endif - -#if (defined (_WIN32) && defined(_MSC_VER)) - -/* scalar types */ -typedef signed __int8 cl_char; -typedef unsigned __int8 cl_uchar; -typedef signed __int16 cl_short; -typedef unsigned __int16 cl_ushort; -typedef signed __int32 cl_int; -typedef unsigned __int32 cl_uint; -typedef signed __int64 cl_long; -typedef unsigned __int64 cl_ulong; - -typedef unsigned __int16 cl_half; -typedef float cl_float; -typedef double cl_double; - -/* Macro names and corresponding values defined by OpenCL */ -#define CL_CHAR_BIT 8 -#define CL_SCHAR_MAX 127 -#define CL_SCHAR_MIN (-127-1) -#define CL_CHAR_MAX CL_SCHAR_MAX -#define CL_CHAR_MIN CL_SCHAR_MIN -#define CL_UCHAR_MAX 255 -#define CL_SHRT_MAX 32767 -#define CL_SHRT_MIN (-32767-1) -#define CL_USHRT_MAX 65535 -#define CL_INT_MAX 2147483647 -#define CL_INT_MIN (-2147483647-1) -#define CL_UINT_MAX 0xffffffffU -#define CL_LONG_MAX ((cl_long) 0x7FFFFFFFFFFFFFFFLL) -#define CL_LONG_MIN ((cl_long) -0x7FFFFFFFFFFFFFFFLL - 1LL) -#define CL_ULONG_MAX ((cl_ulong) 0xFFFFFFFFFFFFFFFFULL) - -#define CL_FLT_DIG 6 -#define CL_FLT_MANT_DIG 24 -#define CL_FLT_MAX_10_EXP +38 -#define CL_FLT_MAX_EXP +128 -#define CL_FLT_MIN_10_EXP -37 -#define CL_FLT_MIN_EXP -125 -#define CL_FLT_RADIX 2 -#define CL_FLT_MAX 340282346638528859811704183484516925440.0f -#define CL_FLT_MIN 1.175494350822287507969e-38f -#define CL_FLT_EPSILON 0x1.0p-23f - -#define CL_DBL_DIG 15 -#define CL_DBL_MANT_DIG 53 -#define CL_DBL_MAX_10_EXP +308 -#define CL_DBL_MAX_EXP +1024 -#define CL_DBL_MIN_10_EXP -307 -#define CL_DBL_MIN_EXP -1021 -#define CL_DBL_RADIX 2 -#define CL_DBL_MAX 179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.0 -#define CL_DBL_MIN 2.225073858507201383090e-308 -#define CL_DBL_EPSILON 2.220446049250313080847e-16 - -#define CL_M_E 2.718281828459045090796 -#define CL_M_LOG2E 1.442695040888963387005 -#define CL_M_LOG10E 0.434294481903251816668 -#define CL_M_LN2 0.693147180559945286227 -#define CL_M_LN10 2.302585092994045901094 -#define CL_M_PI 3.141592653589793115998 -#define CL_M_PI_2 1.570796326794896557999 -#define CL_M_PI_4 0.785398163397448278999 -#define CL_M_1_PI 0.318309886183790691216 -#define CL_M_2_PI 0.636619772367581382433 -#define CL_M_2_SQRTPI 1.128379167095512558561 -#define CL_M_SQRT2 1.414213562373095145475 -#define CL_M_SQRT1_2 0.707106781186547572737 - -#define CL_M_E_F 2.71828174591064f -#define CL_M_LOG2E_F 1.44269502162933f -#define CL_M_LOG10E_F 0.43429449200630f -#define CL_M_LN2_F 0.69314718246460f -#define CL_M_LN10_F 2.30258512496948f -#define CL_M_PI_F 3.14159274101257f -#define CL_M_PI_2_F 1.57079637050629f -#define CL_M_PI_4_F 0.78539818525314f -#define CL_M_1_PI_F 0.31830987334251f -#define CL_M_2_PI_F 0.63661974668503f -#define CL_M_2_SQRTPI_F 1.12837922573090f -#define CL_M_SQRT2_F 1.41421353816986f -#define CL_M_SQRT1_2_F 0.70710676908493f - -#define CL_NAN (CL_INFINITY - CL_INFINITY) -#define CL_HUGE_VALF ((cl_float) 1e50) -#define CL_HUGE_VAL ((cl_double) 1e500) -#define CL_MAXFLOAT CL_FLT_MAX -#define CL_INFINITY CL_HUGE_VALF - -#else - -#include - -/* scalar types */ -typedef int8_t cl_char; -typedef uint8_t cl_uchar; -typedef int16_t cl_short __attribute__((aligned(2))); -typedef uint16_t cl_ushort __attribute__((aligned(2))); -typedef int32_t cl_int __attribute__((aligned(4))); -typedef uint32_t cl_uint __attribute__((aligned(4))); -typedef int64_t cl_long __attribute__((aligned(8))); -typedef uint64_t cl_ulong __attribute__((aligned(8))); - -typedef uint16_t cl_half __attribute__((aligned(2))); -typedef float cl_float __attribute__((aligned(4))); -typedef double cl_double __attribute__((aligned(8))); - -/* Macro names and corresponding values defined by OpenCL */ -#define CL_CHAR_BIT 8 -#define CL_SCHAR_MAX 127 -#define CL_SCHAR_MIN (-127-1) -#define CL_CHAR_MAX CL_SCHAR_MAX -#define CL_CHAR_MIN CL_SCHAR_MIN -#define CL_UCHAR_MAX 255 -#define CL_SHRT_MAX 32767 -#define CL_SHRT_MIN (-32767-1) -#define CL_USHRT_MAX 65535 -#define CL_INT_MAX 2147483647 -#define CL_INT_MIN (-2147483647-1) -#define CL_UINT_MAX 0xffffffffU -#define CL_LONG_MAX ((cl_long) 0x7FFFFFFFFFFFFFFFLL) -#define CL_LONG_MIN ((cl_long) -0x7FFFFFFFFFFFFFFFLL - 1LL) -#define CL_ULONG_MAX ((cl_ulong) 0xFFFFFFFFFFFFFFFFULL) - -#define CL_FLT_DIG 6 -#define CL_FLT_MANT_DIG 24 -#define CL_FLT_MAX_10_EXP +38 -#define CL_FLT_MAX_EXP +128 -#define CL_FLT_MIN_10_EXP -37 -#define CL_FLT_MIN_EXP -125 -#define CL_FLT_RADIX 2 -#define CL_FLT_MAX 0x1.fffffep127f -#define CL_FLT_MIN 0x1.0p-126f -#define CL_FLT_EPSILON 0x1.0p-23f - -#define CL_DBL_DIG 15 -#define CL_DBL_MANT_DIG 53 -#define CL_DBL_MAX_10_EXP +308 -#define CL_DBL_MAX_EXP +1024 -#define CL_DBL_MIN_10_EXP -307 -#define CL_DBL_MIN_EXP -1021 -#define CL_DBL_RADIX 2 -#define CL_DBL_MAX 0x1.fffffffffffffp1023 -#define CL_DBL_MIN 0x1.0p-1022 -#define CL_DBL_EPSILON 0x1.0p-52 - -#define CL_M_E 2.718281828459045090796 -#define CL_M_LOG2E 1.442695040888963387005 -#define CL_M_LOG10E 0.434294481903251816668 -#define CL_M_LN2 0.693147180559945286227 -#define CL_M_LN10 2.302585092994045901094 -#define CL_M_PI 3.141592653589793115998 -#define CL_M_PI_2 1.570796326794896557999 -#define CL_M_PI_4 0.785398163397448278999 -#define CL_M_1_PI 0.318309886183790691216 -#define CL_M_2_PI 0.636619772367581382433 -#define CL_M_2_SQRTPI 1.128379167095512558561 -#define CL_M_SQRT2 1.414213562373095145475 -#define CL_M_SQRT1_2 0.707106781186547572737 - -#define CL_M_E_F 2.71828174591064f -#define CL_M_LOG2E_F 1.44269502162933f -#define CL_M_LOG10E_F 0.43429449200630f -#define CL_M_LN2_F 0.69314718246460f -#define CL_M_LN10_F 2.30258512496948f -#define CL_M_PI_F 3.14159274101257f -#define CL_M_PI_2_F 1.57079637050629f -#define CL_M_PI_4_F 0.78539818525314f -#define CL_M_1_PI_F 0.31830987334251f -#define CL_M_2_PI_F 0.63661974668503f -#define CL_M_2_SQRTPI_F 1.12837922573090f -#define CL_M_SQRT2_F 1.41421353816986f -#define CL_M_SQRT1_2_F 0.70710676908493f - -#if defined( __GNUC__ ) - #define CL_HUGE_VALF __builtin_huge_valf() - #define CL_HUGE_VAL __builtin_huge_val() - #define CL_NAN __builtin_nanf( "" ) -#else - #define CL_HUGE_VALF ((cl_float) 1e50) - #define CL_HUGE_VAL ((cl_double) 1e500) - float nanf( const char * ); - #define CL_NAN nanf( "" ) -#endif -#define CL_MAXFLOAT CL_FLT_MAX -#define CL_INFINITY CL_HUGE_VALF - -#endif - -#include - -/* Mirror types to GL types. Mirror types allow us to avoid deciding which 87s to load based on whether we are using GL or GLES here. */ -typedef unsigned int cl_GLuint; -typedef int cl_GLint; -typedef unsigned int cl_GLenum; - -/* - * Vector types - * - * Note: OpenCL requires that all types be naturally aligned. - * This means that vector types must be naturally aligned. - * For example, a vector of four floats must be aligned to - * a 16 byte boundary (calculated as 4 * the natural 4-byte - * alignment of the float). The alignment qualifiers here - * will only function properly if your compiler supports them - * and if you don't actively work to defeat them. For example, - * in order for a cl_float4 to be 16 byte aligned in a struct, - * the start of the struct must itself be 16-byte aligned. - * - * Maintaining proper alignment is the user's responsibility. - */ - -/* Define basic vector types */ -#if defined( __VEC__ ) - #include /* may be omitted depending on compiler. AltiVec spec provides no way to detect whether the header is required. */ - typedef vector unsigned char __cl_uchar16; - typedef vector signed char __cl_char16; - typedef vector unsigned short __cl_ushort8; - typedef vector signed short __cl_short8; - typedef vector unsigned int __cl_uint4; - typedef vector signed int __cl_int4; - typedef vector float __cl_float4; - #define __CL_UCHAR16__ 1 - #define __CL_CHAR16__ 1 - #define __CL_USHORT8__ 1 - #define __CL_SHORT8__ 1 - #define __CL_UINT4__ 1 - #define __CL_INT4__ 1 - #define __CL_FLOAT4__ 1 -#endif - -#if defined( __SSE__ ) - #if defined( __MINGW64__ ) - #include - #else - #include - #endif - #if defined( __GNUC__ ) - typedef float __cl_float4 __attribute__((vector_size(16))); - #else - typedef __m128 __cl_float4; - #endif - #define __CL_FLOAT4__ 1 -#endif - -#if defined( __SSE2__ ) - #if defined( __MINGW64__ ) - #include - #else - #include - #endif - #if defined( __GNUC__ ) - typedef cl_uchar __cl_uchar16 __attribute__((vector_size(16))); - typedef cl_char __cl_char16 __attribute__((vector_size(16))); - typedef cl_ushort __cl_ushort8 __attribute__((vector_size(16))); - typedef cl_short __cl_short8 __attribute__((vector_size(16))); - typedef cl_uint __cl_uint4 __attribute__((vector_size(16))); - typedef cl_int __cl_int4 __attribute__((vector_size(16))); - typedef cl_ulong __cl_ulong2 __attribute__((vector_size(16))); - typedef cl_long __cl_long2 __attribute__((vector_size(16))); - typedef cl_double __cl_double2 __attribute__((vector_size(16))); - #else - typedef __m128i __cl_uchar16; - typedef __m128i __cl_char16; - typedef __m128i __cl_ushort8; - typedef __m128i __cl_short8; - typedef __m128i __cl_uint4; - typedef __m128i __cl_int4; - typedef __m128i __cl_ulong2; - typedef __m128i __cl_long2; - typedef __m128d __cl_double2; - #endif - #define __CL_UCHAR16__ 1 - #define __CL_CHAR16__ 1 - #define __CL_USHORT8__ 1 - #define __CL_SHORT8__ 1 - #define __CL_INT4__ 1 - #define __CL_UINT4__ 1 - #define __CL_ULONG2__ 1 - #define __CL_LONG2__ 1 - #define __CL_DOUBLE2__ 1 -#endif - -#if defined( __MMX__ ) - #include - #if defined( __GNUC__ ) - typedef cl_uchar __cl_uchar8 __attribute__((vector_size(8))); - typedef cl_char __cl_char8 __attribute__((vector_size(8))); - typedef cl_ushort __cl_ushort4 __attribute__((vector_size(8))); - typedef cl_short __cl_short4 __attribute__((vector_size(8))); - typedef cl_uint __cl_uint2 __attribute__((vector_size(8))); - typedef cl_int __cl_int2 __attribute__((vector_size(8))); - typedef cl_ulong __cl_ulong1 __attribute__((vector_size(8))); - typedef cl_long __cl_long1 __attribute__((vector_size(8))); - typedef cl_float __cl_float2 __attribute__((vector_size(8))); - #else - typedef __m64 __cl_uchar8; - typedef __m64 __cl_char8; - typedef __m64 __cl_ushort4; - typedef __m64 __cl_short4; - typedef __m64 __cl_uint2; - typedef __m64 __cl_int2; - typedef __m64 __cl_ulong1; - typedef __m64 __cl_long1; - typedef __m64 __cl_float2; - #endif - #define __CL_UCHAR8__ 1 - #define __CL_CHAR8__ 1 - #define __CL_USHORT4__ 1 - #define __CL_SHORT4__ 1 - #define __CL_INT2__ 1 - #define __CL_UINT2__ 1 - #define __CL_ULONG1__ 1 - #define __CL_LONG1__ 1 - #define __CL_FLOAT2__ 1 -#endif - -#if defined( __AVX__ ) - #if defined( __MINGW64__ ) - #include - #else - #include - #endif - #if defined( __GNUC__ ) - typedef cl_float __cl_float8 __attribute__((vector_size(32))); - typedef cl_double __cl_double4 __attribute__((vector_size(32))); - #else - typedef __m256 __cl_float8; - typedef __m256d __cl_double4; - #endif - #define __CL_FLOAT8__ 1 - #define __CL_DOUBLE4__ 1 -#endif - -/* Define capabilities for anonymous struct members. */ -#if defined( __GNUC__) && ! defined( __STRICT_ANSI__ ) -#define __CL_HAS_ANON_STRUCT__ 1 -#define __CL_ANON_STRUCT__ __extension__ -#elif defined( _WIN32) && (_MSC_VER >= 1500) - /* Microsoft Developer Studio 2008 supports anonymous structs, but - * complains by default. */ -#define __CL_HAS_ANON_STRUCT__ 1 -#define __CL_ANON_STRUCT__ - /* Disable warning C4201: nonstandard extension used : nameless - * struct/union */ -#pragma warning( push ) -#pragma warning( disable : 4201 ) -#else -#define __CL_HAS_ANON_STRUCT__ 0 -#define __CL_ANON_STRUCT__ -#endif - -/* Define alignment keys */ -#if defined( __GNUC__ ) - #define CL_ALIGNED(_x) __attribute__ ((aligned(_x))) -#elif defined( _WIN32) && (_MSC_VER) - /* Alignment keys neutered on windows because MSVC can't swallow function arguments with alignment requirements */ - /* http://msdn.microsoft.com/en-us/library/373ak2y1%28VS.71%29.aspx */ - /* #include */ - /* #define CL_ALIGNED(_x) _CRT_ALIGN(_x) */ - #define CL_ALIGNED(_x) -#else - #warning Need to implement some method to align data here - #define CL_ALIGNED(_x) -#endif - -/* Indicate whether .xyzw, .s0123 and .hi.lo are supported */ -#if __CL_HAS_ANON_STRUCT__ - /* .xyzw and .s0123...{f|F} are supported */ - #define CL_HAS_NAMED_VECTOR_FIELDS 1 - /* .hi and .lo are supported */ - #define CL_HAS_HI_LO_VECTOR_FIELDS 1 -#endif - -/* Define cl_vector types */ - -/* ---- cl_charn ---- */ -typedef union -{ - cl_char CL_ALIGNED(2) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_char x, y; }; - __CL_ANON_STRUCT__ struct{ cl_char s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_char lo, hi; }; -#endif -#if defined( __CL_CHAR2__) - __cl_char2 v2; -#endif -}cl_char2; - -typedef union -{ - cl_char CL_ALIGNED(4) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_char x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_char s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_char2 lo, hi; }; -#endif -#if defined( __CL_CHAR2__) - __cl_char2 v2[2]; -#endif -#if defined( __CL_CHAR4__) - __cl_char4 v4; -#endif -}cl_char4; - -/* cl_char3 is identical in size, alignment and behavior to cl_char4. See section 6.1.5. */ -typedef cl_char4 cl_char3; - -typedef union -{ - cl_char CL_ALIGNED(8) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_char x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_char s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_char4 lo, hi; }; -#endif -#if defined( __CL_CHAR2__) - __cl_char2 v2[4]; -#endif -#if defined( __CL_CHAR4__) - __cl_char4 v4[2]; -#endif -#if defined( __CL_CHAR8__ ) - __cl_char8 v8; -#endif -}cl_char8; - -typedef union -{ - cl_char CL_ALIGNED(16) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_char x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_char s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_char8 lo, hi; }; -#endif -#if defined( __CL_CHAR2__) - __cl_char2 v2[8]; -#endif -#if defined( __CL_CHAR4__) - __cl_char4 v4[4]; -#endif -#if defined( __CL_CHAR8__ ) - __cl_char8 v8[2]; -#endif -#if defined( __CL_CHAR16__ ) - __cl_char16 v16; -#endif -}cl_char16; - - -/* ---- cl_ucharn ---- */ -typedef union -{ - cl_uchar CL_ALIGNED(2) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_uchar x, y; }; - __CL_ANON_STRUCT__ struct{ cl_uchar s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_uchar lo, hi; }; -#endif -#if defined( __cl_uchar2__) - __cl_uchar2 v2; -#endif -}cl_uchar2; - -typedef union -{ - cl_uchar CL_ALIGNED(4) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_uchar x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_uchar s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_uchar2 lo, hi; }; -#endif -#if defined( __CL_UCHAR2__) - __cl_uchar2 v2[2]; -#endif -#if defined( __CL_UCHAR4__) - __cl_uchar4 v4; -#endif -}cl_uchar4; - -/* cl_uchar3 is identical in size, alignment and behavior to cl_uchar4. See section 6.1.5. */ -typedef cl_uchar4 cl_uchar3; - -typedef union -{ - cl_uchar CL_ALIGNED(8) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_uchar x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_uchar s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_uchar4 lo, hi; }; -#endif -#if defined( __CL_UCHAR2__) - __cl_uchar2 v2[4]; -#endif -#if defined( __CL_UCHAR4__) - __cl_uchar4 v4[2]; -#endif -#if defined( __CL_UCHAR8__ ) - __cl_uchar8 v8; -#endif -}cl_uchar8; - -typedef union -{ - cl_uchar CL_ALIGNED(16) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_uchar x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_uchar s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_uchar8 lo, hi; }; -#endif -#if defined( __CL_UCHAR2__) - __cl_uchar2 v2[8]; -#endif -#if defined( __CL_UCHAR4__) - __cl_uchar4 v4[4]; -#endif -#if defined( __CL_UCHAR8__ ) - __cl_uchar8 v8[2]; -#endif -#if defined( __CL_UCHAR16__ ) - __cl_uchar16 v16; -#endif -}cl_uchar16; - - -/* ---- cl_shortn ---- */ -typedef union -{ - cl_short CL_ALIGNED(4) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_short x, y; }; - __CL_ANON_STRUCT__ struct{ cl_short s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_short lo, hi; }; -#endif -#if defined( __CL_SHORT2__) - __cl_short2 v2; -#endif -}cl_short2; - -typedef union -{ - cl_short CL_ALIGNED(8) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_short x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_short s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_short2 lo, hi; }; -#endif -#if defined( __CL_SHORT2__) - __cl_short2 v2[2]; -#endif -#if defined( __CL_SHORT4__) - __cl_short4 v4; -#endif -}cl_short4; - -/* cl_short3 is identical in size, alignment and behavior to cl_short4. See section 6.1.5. */ -typedef cl_short4 cl_short3; - -typedef union -{ - cl_short CL_ALIGNED(16) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_short x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_short s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_short4 lo, hi; }; -#endif -#if defined( __CL_SHORT2__) - __cl_short2 v2[4]; -#endif -#if defined( __CL_SHORT4__) - __cl_short4 v4[2]; -#endif -#if defined( __CL_SHORT8__ ) - __cl_short8 v8; -#endif -}cl_short8; - -typedef union -{ - cl_short CL_ALIGNED(32) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_short x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_short s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_short8 lo, hi; }; -#endif -#if defined( __CL_SHORT2__) - __cl_short2 v2[8]; -#endif -#if defined( __CL_SHORT4__) - __cl_short4 v4[4]; -#endif -#if defined( __CL_SHORT8__ ) - __cl_short8 v8[2]; -#endif -#if defined( __CL_SHORT16__ ) - __cl_short16 v16; -#endif -}cl_short16; - - -/* ---- cl_ushortn ---- */ -typedef union -{ - cl_ushort CL_ALIGNED(4) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_ushort x, y; }; - __CL_ANON_STRUCT__ struct{ cl_ushort s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_ushort lo, hi; }; -#endif -#if defined( __CL_USHORT2__) - __cl_ushort2 v2; -#endif -}cl_ushort2; - -typedef union -{ - cl_ushort CL_ALIGNED(8) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_ushort x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_ushort s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_ushort2 lo, hi; }; -#endif -#if defined( __CL_USHORT2__) - __cl_ushort2 v2[2]; -#endif -#if defined( __CL_USHORT4__) - __cl_ushort4 v4; -#endif -}cl_ushort4; - -/* cl_ushort3 is identical in size, alignment and behavior to cl_ushort4. See section 6.1.5. */ -typedef cl_ushort4 cl_ushort3; - -typedef union -{ - cl_ushort CL_ALIGNED(16) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_ushort x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_ushort s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_ushort4 lo, hi; }; -#endif -#if defined( __CL_USHORT2__) - __cl_ushort2 v2[4]; -#endif -#if defined( __CL_USHORT4__) - __cl_ushort4 v4[2]; -#endif -#if defined( __CL_USHORT8__ ) - __cl_ushort8 v8; -#endif -}cl_ushort8; - -typedef union -{ - cl_ushort CL_ALIGNED(32) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_ushort x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_ushort s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_ushort8 lo, hi; }; -#endif -#if defined( __CL_USHORT2__) - __cl_ushort2 v2[8]; -#endif -#if defined( __CL_USHORT4__) - __cl_ushort4 v4[4]; -#endif -#if defined( __CL_USHORT8__ ) - __cl_ushort8 v8[2]; -#endif -#if defined( __CL_USHORT16__ ) - __cl_ushort16 v16; -#endif -}cl_ushort16; - -/* ---- cl_intn ---- */ -typedef union -{ - cl_int CL_ALIGNED(8) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_int x, y; }; - __CL_ANON_STRUCT__ struct{ cl_int s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_int lo, hi; }; -#endif -#if defined( __CL_INT2__) - __cl_int2 v2; -#endif -}cl_int2; - -typedef union -{ - cl_int CL_ALIGNED(16) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_int x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_int s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_int2 lo, hi; }; -#endif -#if defined( __CL_INT2__) - __cl_int2 v2[2]; -#endif -#if defined( __CL_INT4__) - __cl_int4 v4; -#endif -}cl_int4; - -/* cl_int3 is identical in size, alignment and behavior to cl_int4. See section 6.1.5. */ -typedef cl_int4 cl_int3; - -typedef union -{ - cl_int CL_ALIGNED(32) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_int x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_int s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_int4 lo, hi; }; -#endif -#if defined( __CL_INT2__) - __cl_int2 v2[4]; -#endif -#if defined( __CL_INT4__) - __cl_int4 v4[2]; -#endif -#if defined( __CL_INT8__ ) - __cl_int8 v8; -#endif -}cl_int8; - -typedef union -{ - cl_int CL_ALIGNED(64) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_int x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_int s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_int8 lo, hi; }; -#endif -#if defined( __CL_INT2__) - __cl_int2 v2[8]; -#endif -#if defined( __CL_INT4__) - __cl_int4 v4[4]; -#endif -#if defined( __CL_INT8__ ) - __cl_int8 v8[2]; -#endif -#if defined( __CL_INT16__ ) - __cl_int16 v16; -#endif -}cl_int16; - - -/* ---- cl_uintn ---- */ -typedef union -{ - cl_uint CL_ALIGNED(8) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_uint x, y; }; - __CL_ANON_STRUCT__ struct{ cl_uint s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_uint lo, hi; }; -#endif -#if defined( __CL_UINT2__) - __cl_uint2 v2; -#endif -}cl_uint2; - -typedef union -{ - cl_uint CL_ALIGNED(16) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_uint x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_uint s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_uint2 lo, hi; }; -#endif -#if defined( __CL_UINT2__) - __cl_uint2 v2[2]; -#endif -#if defined( __CL_UINT4__) - __cl_uint4 v4; -#endif -}cl_uint4; - -/* cl_uint3 is identical in size, alignment and behavior to cl_uint4. See section 6.1.5. */ -typedef cl_uint4 cl_uint3; - -typedef union -{ - cl_uint CL_ALIGNED(32) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_uint x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_uint s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_uint4 lo, hi; }; -#endif -#if defined( __CL_UINT2__) - __cl_uint2 v2[4]; -#endif -#if defined( __CL_UINT4__) - __cl_uint4 v4[2]; -#endif -#if defined( __CL_UINT8__ ) - __cl_uint8 v8; -#endif -}cl_uint8; - -typedef union -{ - cl_uint CL_ALIGNED(64) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_uint x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_uint s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_uint8 lo, hi; }; -#endif -#if defined( __CL_UINT2__) - __cl_uint2 v2[8]; -#endif -#if defined( __CL_UINT4__) - __cl_uint4 v4[4]; -#endif -#if defined( __CL_UINT8__ ) - __cl_uint8 v8[2]; -#endif -#if defined( __CL_UINT16__ ) - __cl_uint16 v16; -#endif -}cl_uint16; - -/* ---- cl_longn ---- */ -typedef union -{ - cl_long CL_ALIGNED(16) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_long x, y; }; - __CL_ANON_STRUCT__ struct{ cl_long s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_long lo, hi; }; -#endif -#if defined( __CL_LONG2__) - __cl_long2 v2; -#endif -}cl_long2; - -typedef union -{ - cl_long CL_ALIGNED(32) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_long x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_long s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_long2 lo, hi; }; -#endif -#if defined( __CL_LONG2__) - __cl_long2 v2[2]; -#endif -#if defined( __CL_LONG4__) - __cl_long4 v4; -#endif -}cl_long4; - -/* cl_long3 is identical in size, alignment and behavior to cl_long4. See section 6.1.5. */ -typedef cl_long4 cl_long3; - -typedef union -{ - cl_long CL_ALIGNED(64) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_long x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_long s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_long4 lo, hi; }; -#endif -#if defined( __CL_LONG2__) - __cl_long2 v2[4]; -#endif -#if defined( __CL_LONG4__) - __cl_long4 v4[2]; -#endif -#if defined( __CL_LONG8__ ) - __cl_long8 v8; -#endif -}cl_long8; - -typedef union -{ - cl_long CL_ALIGNED(128) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_long x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_long s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_long8 lo, hi; }; -#endif -#if defined( __CL_LONG2__) - __cl_long2 v2[8]; -#endif -#if defined( __CL_LONG4__) - __cl_long4 v4[4]; -#endif -#if defined( __CL_LONG8__ ) - __cl_long8 v8[2]; -#endif -#if defined( __CL_LONG16__ ) - __cl_long16 v16; -#endif -}cl_long16; - - -/* ---- cl_ulongn ---- */ -typedef union -{ - cl_ulong CL_ALIGNED(16) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_ulong x, y; }; - __CL_ANON_STRUCT__ struct{ cl_ulong s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_ulong lo, hi; }; -#endif -#if defined( __CL_ULONG2__) - __cl_ulong2 v2; -#endif -}cl_ulong2; - -typedef union -{ - cl_ulong CL_ALIGNED(32) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_ulong x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_ulong s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_ulong2 lo, hi; }; -#endif -#if defined( __CL_ULONG2__) - __cl_ulong2 v2[2]; -#endif -#if defined( __CL_ULONG4__) - __cl_ulong4 v4; -#endif -}cl_ulong4; - -/* cl_ulong3 is identical in size, alignment and behavior to cl_ulong4. See section 6.1.5. */ -typedef cl_ulong4 cl_ulong3; - -typedef union -{ - cl_ulong CL_ALIGNED(64) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_ulong x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_ulong s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_ulong4 lo, hi; }; -#endif -#if defined( __CL_ULONG2__) - __cl_ulong2 v2[4]; -#endif -#if defined( __CL_ULONG4__) - __cl_ulong4 v4[2]; -#endif -#if defined( __CL_ULONG8__ ) - __cl_ulong8 v8; -#endif -}cl_ulong8; - -typedef union -{ - cl_ulong CL_ALIGNED(128) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_ulong x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_ulong s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_ulong8 lo, hi; }; -#endif -#if defined( __CL_ULONG2__) - __cl_ulong2 v2[8]; -#endif -#if defined( __CL_ULONG4__) - __cl_ulong4 v4[4]; -#endif -#if defined( __CL_ULONG8__ ) - __cl_ulong8 v8[2]; -#endif -#if defined( __CL_ULONG16__ ) - __cl_ulong16 v16; -#endif -}cl_ulong16; - - -/* --- cl_floatn ---- */ - -typedef union -{ - cl_float CL_ALIGNED(8) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_float x, y; }; - __CL_ANON_STRUCT__ struct{ cl_float s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_float lo, hi; }; -#endif -#if defined( __CL_FLOAT2__) - __cl_float2 v2; -#endif -}cl_float2; - -typedef union -{ - cl_float CL_ALIGNED(16) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_float x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_float s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_float2 lo, hi; }; -#endif -#if defined( __CL_FLOAT2__) - __cl_float2 v2[2]; -#endif -#if defined( __CL_FLOAT4__) - __cl_float4 v4; -#endif -}cl_float4; - -/* cl_float3 is identical in size, alignment and behavior to cl_float4. See section 6.1.5. */ -typedef cl_float4 cl_float3; - -typedef union -{ - cl_float CL_ALIGNED(32) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_float x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_float s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_float4 lo, hi; }; -#endif -#if defined( __CL_FLOAT2__) - __cl_float2 v2[4]; -#endif -#if defined( __CL_FLOAT4__) - __cl_float4 v4[2]; -#endif -#if defined( __CL_FLOAT8__ ) - __cl_float8 v8; -#endif -}cl_float8; - -typedef union -{ - cl_float CL_ALIGNED(64) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_float x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_float s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_float8 lo, hi; }; -#endif -#if defined( __CL_FLOAT2__) - __cl_float2 v2[8]; -#endif -#if defined( __CL_FLOAT4__) - __cl_float4 v4[4]; -#endif -#if defined( __CL_FLOAT8__ ) - __cl_float8 v8[2]; -#endif -#if defined( __CL_FLOAT16__ ) - __cl_float16 v16; -#endif -}cl_float16; - -/* --- cl_doublen ---- */ - -typedef union -{ - cl_double CL_ALIGNED(16) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_double x, y; }; - __CL_ANON_STRUCT__ struct{ cl_double s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_double lo, hi; }; -#endif -#if defined( __CL_DOUBLE2__) - __cl_double2 v2; -#endif -}cl_double2; - -typedef union -{ - cl_double CL_ALIGNED(32) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_double x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_double s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_double2 lo, hi; }; -#endif -#if defined( __CL_DOUBLE2__) - __cl_double2 v2[2]; -#endif -#if defined( __CL_DOUBLE4__) - __cl_double4 v4; -#endif -}cl_double4; - -/* cl_double3 is identical in size, alignment and behavior to cl_double4. See section 6.1.5. */ -typedef cl_double4 cl_double3; - -typedef union -{ - cl_double CL_ALIGNED(64) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_double x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_double s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_double4 lo, hi; }; -#endif -#if defined( __CL_DOUBLE2__) - __cl_double2 v2[4]; -#endif -#if defined( __CL_DOUBLE4__) - __cl_double4 v4[2]; -#endif -#if defined( __CL_DOUBLE8__ ) - __cl_double8 v8; -#endif -}cl_double8; - -typedef union -{ - cl_double CL_ALIGNED(128) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_double x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_double s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_double8 lo, hi; }; -#endif -#if defined( __CL_DOUBLE2__) - __cl_double2 v2[8]; -#endif -#if defined( __CL_DOUBLE4__) - __cl_double4 v4[4]; -#endif -#if defined( __CL_DOUBLE8__ ) - __cl_double8 v8[2]; -#endif -#if defined( __CL_DOUBLE16__ ) - __cl_double16 v16; -#endif -}cl_double16; - -/* Macro to facilitate debugging - * Usage: - * Place CL_PROGRAM_STRING_DEBUG_INFO on the line before the first line of your source. - * The first line ends with: CL_PROGRAM_STRING_DEBUG_INFO \" - * Each line thereafter of OpenCL C source must end with: \n\ - * The last line ends in "; - * - * Example: - * - * const char *my_program = CL_PROGRAM_STRING_DEBUG_INFO "\ - * kernel void foo( int a, float * b ) \n\ - * { \n\ - * // my comment \n\ - * *b[ get_global_id(0)] = a; \n\ - * } \n\ - * "; - * - * This should correctly set up the line, (column) and file information for your source - * string so you can do source level debugging. - */ -#define __CL_STRINGIFY( _x ) # _x -#define _CL_STRINGIFY( _x ) __CL_STRINGIFY( _x ) -#define CL_PROGRAM_STRING_DEBUG_INFO "#line " _CL_STRINGIFY(__LINE__) " \"" __FILE__ "\" \n\n" - -#ifdef __cplusplus -} -#endif - -#undef __CL_HAS_ANON_STRUCT__ -#undef __CL_ANON_STRUCT__ -#if defined( _WIN32) && (_MSC_VER >= 1500) -#pragma warning( pop ) -#endif - -#endif /* __CL_PLATFORM_H */ diff --git a/third_party/opencl/include/CL/opencl.h b/third_party/opencl/include/CL/opencl.h deleted file mode 100644 index 9855cd75e..000000000 --- a/third_party/opencl/include/CL/opencl.h +++ /dev/null @@ -1,59 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - ******************************************************************************/ - -/* $Revision: 11708 $ on $Date: 2010-06-13 23:36:24 -0700 (Sun, 13 Jun 2010) $ */ - -#ifndef __OPENCL_H -#define __OPENCL_H - -#ifdef __cplusplus -extern "C" { -#endif - -#ifdef __APPLE__ - -#include -#include -#include -#include - -#else - -#include -#include -#include -#include - -#endif - -#ifdef __cplusplus -} -#endif - -#endif /* __OPENCL_H */ - diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index d4cfc6728..b79d046fc 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -57,11 +57,9 @@ base_frameworks = qt_env['FRAMEWORKS'] base_libs = [common, messaging, cereal, visionipc, 'm', 'ssl', 'crypto', 'pthread'] + qt_env["LIBS"] if arch == "Darwin": - base_frameworks.append('OpenCL') base_frameworks.append('QtCharts') base_frameworks.append('QtSerialBus') else: - base_libs.append('OpenCL') base_libs.append('Qt5Charts') base_libs.append('Qt5SerialBus') diff --git a/tools/install_ubuntu_dependencies.sh b/tools/install_ubuntu_dependencies.sh index 5c2131d4b..f428e9972 100755 --- a/tools/install_ubuntu_dependencies.sh +++ b/tools/install_ubuntu_dependencies.sh @@ -58,9 +58,6 @@ function install_ubuntu_common_requirements() { libzmq3-dev \ libzstd-dev \ libsqlite3-dev \ - opencl-headers \ - ocl-icd-libopencl1 \ - ocl-icd-opencl-dev \ portaudio19-dev \ qttools5-dev-tools \ libqt5svg5-dev \ diff --git a/tools/replay/SConscript b/tools/replay/SConscript index 136c4119f..698ab9885 100644 --- a/tools/replay/SConscript +++ b/tools/replay/SConscript @@ -6,11 +6,6 @@ replay_env['CCFLAGS'] += ['-Wno-deprecated-declarations'] base_frameworks = [] base_libs = [common, messaging, cereal, visionipc, 'm', 'ssl', 'crypto', 'pthread'] -if arch == "Darwin": - base_frameworks.append('OpenCL') -else: - base_libs.append('OpenCL') - replay_lib_src = ["replay.cc", "consoleui.cc", "camera.cc", "filereader.cc", "logreader.cc", "framereader.cc", "route.cc", "util.cc", "seg_mgr.cc", "timeline.cc", "api.cc"] if arch != "Darwin": diff --git a/tools/webcam/README.md b/tools/webcam/README.md index 67ad2cc8c..6abbc4793 100644 --- a/tools/webcam/README.md +++ b/tools/webcam/README.md @@ -10,10 +10,6 @@ What's needed: ## Setup openpilot - Follow [this readme](../README.md) to install and build the requirements -- Install OpenCL Driver (Ubuntu) -``` -sudo apt install pocl-opencl-icd -``` ## Connect the hardware - Connect the camera first From 46f74753cd2ee9c72a23acfb1f310338149d3c48 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 6 Feb 2026 17:11:17 -0800 Subject: [PATCH 844/910] clip: use wrap_text helper (#37102) * they are same * no duplication! * rm rstrip --- tools/clip/run.py | 25 ++----------------------- 1 file changed, 2 insertions(+), 23 deletions(-) diff --git a/tools/clip/run.py b/tools/clip/run.py index 13f591eb4..855ac6fea 100755 --- a/tools/clip/run.py +++ b/tools/clip/run.py @@ -238,30 +238,9 @@ def draw_text_box(text, x, y, size, gui_app, font, color=None, center=False): rl.draw_text_ex(font, text, rl.Vector2(x, y), size, 0, text_color) -def _wrap_text_by_delimiter(text: str, font, font_size: int, max_width: int, delimiter: str = ", ") -> list[str]: - from openpilot.system.ui.lib.text_measure import measure_text_cached - """Wrap text by splitting on delimiter when it exceeds max_width.""" - words = text.split(delimiter) - lines: list[str] = [] - current_line: list[str] = [] - # Build lines word by word - for word in words: - current_line.append(word) - check_line = delimiter.join(current_line) - # Check if line exceeds max width - if measure_text_cached(font, check_line, font_size).x > max_width: - current_line.pop() # Line is too long, move word to next line - if current_line: - lines.append(delimiter.join(current_line)) - current_line = [word] - # Add leftover words as last line - if current_line: - lines.append(delimiter.join(current_line)) - return lines - - def render_overlays(gui_app, font, big, metadata, title, start_time, frame_idx, show_metadata, show_time): from openpilot.system.ui.lib.text_measure import measure_text_cached + from openpilot.system.ui.lib.wrap_text import wrap_text metadata_size = 16 if big else 12 title_size = 32 if big else 24 time_size = 24 if big else 16 @@ -282,7 +261,7 @@ def render_overlays(gui_app, font, big, metadata, title, start_time, frame_idx, # Wrap text if too wide (leave margin on each side) margin = 2 * (time_width + 10 if show_time else 20) # leave enough margin for time overlay max_width = gui_app.width - margin - lines = _wrap_text_by_delimiter(text, font, metadata_size, max_width) + lines = wrap_text(font, text, metadata_size, max_width) # Draw wrapped metadata text y_offset = 6 From e531f844f6ada4020850873e808ec7441fbcc2c5 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 6 Feb 2026 20:15:12 -0800 Subject: [PATCH 845/910] bump opendbc (#37108) bump --- opendbc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc_repo b/opendbc_repo index 39ee86119..05348982c 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 39ee861197edca894fc1b2e28d6fd1a38aea9ca1 +Subproject commit 05348982cb7b6bc6fed73611ac26ecda658194fc From efc23644c74714725ce8022f9dae0bcf7a293617 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 6 Feb 2026 22:59:05 -0800 Subject: [PATCH 846/910] Delete less dialogs (#37111) * try * revert * this is fine --- selfdrive/ui/mici/layouts/settings/device.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/device.py b/selfdrive/ui/mici/layouts/settings/device.py index c12a92482..f8dba659d 100644 --- a/selfdrive/ui/mici/layouts/settings/device.py +++ b/selfdrive/ui/mici/layouts/settings/device.py @@ -326,7 +326,7 @@ class DeviceLayoutMici(NavWidget): driver_cam_btn.set_click_callback(self._show_driver_camera) driver_cam_btn.set_enabled(lambda: ui_state.is_offroad()) - review_training_guide_btn = BigButton("review\nntraining guide", "", "icons_mici/settings/device/info.png") + review_training_guide_btn = BigButton("review\ntraining guide", "", "icons_mici/settings/device/info.png") review_training_guide_btn.set_click_callback(self._on_review_training_guide) review_training_guide_btn.set_enabled(lambda: ui_state.is_offroad()) @@ -353,7 +353,7 @@ class DeviceLayoutMici(NavWidget): def _on_regulatory(self): if not self._fcc_dialog: self._fcc_dialog = MiciFccModal(os.path.join(BASEDIR, "selfdrive/assets/offroad/mici_fcc.html")) - gui_app.set_modal_overlay(self._fcc_dialog, callback=setattr(self, '_fcc_dialog', None)) + gui_app.set_modal_overlay(self._fcc_dialog) def _offroad_transition(self): self._power_off_btn.set_visible(ui_state.is_offroad()) From 51312afd3da58ac73cc98d5c908cbebd70498ba5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Sat, 7 Feb 2026 09:10:29 -0800 Subject: [PATCH 847/910] revert tg calib and opencl cleanup (#37113) * Revert "Remove all the OpenCL (#37105)" This reverts commit d5cbb89d84212bfd99d5fdd8b19f76d90b4f40e0. * Revert "rm common/mat.h" This reverts commit 4ce701150a3f932aca210e3ff372ffcdb2a23e83. * Revert "Calibrate in tg (#36621)" This reverts commit 593c3a0c8e69686ee821b2a0b2cfa9901b67dc27. --- Dockerfile.openpilot_base | 37 + SConstruct | 1 + common/SConscript | 1 + common/clutil.cc | 98 ++ common/clutil.h | 28 + common/mat.h | 85 + msgq_repo | 2 +- selfdrive/modeld/SConscript | 49 +- selfdrive/modeld/compile_warp.py | 206 --- selfdrive/modeld/dmonitoringmodeld.py | 33 +- selfdrive/modeld/modeld.py | 59 +- selfdrive/modeld/models/commonmodel.cc | 64 + selfdrive/modeld/models/commonmodel.h | 97 ++ selfdrive/modeld/models/commonmodel.pxd | 27 + selfdrive/modeld/models/commonmodel_pyx.pxd | 13 + selfdrive/modeld/models/commonmodel_pyx.pyx | 74 + selfdrive/modeld/runners/tinygrad_helpers.py | 8 + selfdrive/modeld/transforms/loadyuv.cc | 76 + selfdrive/modeld/transforms/loadyuv.cl | 47 + selfdrive/modeld/transforms/loadyuv.h | 20 + selfdrive/modeld/transforms/transform.cc | 97 ++ selfdrive/modeld/transforms/transform.cl | 54 + selfdrive/modeld/transforms/transform.h | 25 + selfdrive/test/process_replay/model_replay.py | 2 +- .../test/process_replay/process_replay.py | 15 +- system/camerad/SConscript | 2 +- system/camerad/cameras/camera_common.cc | 5 +- system/camerad/cameras/camera_common.h | 2 +- system/camerad/cameras/camera_qcom2.cc | 22 +- system/camerad/cameras/spectra.cc | 4 +- system/camerad/cameras/spectra.h | 2 +- system/loggerd/SConscript | 5 +- third_party/opencl/include/CL/cl.h | 1452 +++++++++++++++++ third_party/opencl/include/CL/cl_d3d10.h | 131 ++ third_party/opencl/include/CL/cl_d3d11.h | 131 ++ .../opencl/include/CL/cl_dx9_media_sharing.h | 132 ++ third_party/opencl/include/CL/cl_egl.h | 136 ++ third_party/opencl/include/CL/cl_ext.h | 391 +++++ third_party/opencl/include/CL/cl_ext_qcom.h | 255 +++ third_party/opencl/include/CL/cl_gl.h | 167 ++ third_party/opencl/include/CL/cl_gl_ext.h | 74 + third_party/opencl/include/CL/cl_platform.h | 1333 +++++++++++++++ third_party/opencl/include/CL/opencl.h | 59 + tools/cabana/SConscript | 2 + tools/install_ubuntu_dependencies.sh | 3 + tools/replay/SConscript | 5 + tools/webcam/README.md | 4 + 47 files changed, 5235 insertions(+), 300 deletions(-) create mode 100644 common/clutil.cc create mode 100644 common/clutil.h create mode 100644 common/mat.h delete mode 100755 selfdrive/modeld/compile_warp.py create mode 100644 selfdrive/modeld/models/commonmodel.cc create mode 100644 selfdrive/modeld/models/commonmodel.h create mode 100644 selfdrive/modeld/models/commonmodel.pxd create mode 100644 selfdrive/modeld/models/commonmodel_pyx.pxd create mode 100644 selfdrive/modeld/models/commonmodel_pyx.pyx create mode 100644 selfdrive/modeld/runners/tinygrad_helpers.py create mode 100644 selfdrive/modeld/transforms/loadyuv.cc create mode 100644 selfdrive/modeld/transforms/loadyuv.cl create mode 100644 selfdrive/modeld/transforms/loadyuv.h create mode 100644 selfdrive/modeld/transforms/transform.cc create mode 100644 selfdrive/modeld/transforms/transform.cl create mode 100644 selfdrive/modeld/transforms/transform.h create mode 100644 third_party/opencl/include/CL/cl.h create mode 100644 third_party/opencl/include/CL/cl_d3d10.h create mode 100644 third_party/opencl/include/CL/cl_d3d11.h create mode 100644 third_party/opencl/include/CL/cl_dx9_media_sharing.h create mode 100644 third_party/opencl/include/CL/cl_egl.h create mode 100644 third_party/opencl/include/CL/cl_ext.h create mode 100644 third_party/opencl/include/CL/cl_ext_qcom.h create mode 100644 third_party/opencl/include/CL/cl_gl.h create mode 100644 third_party/opencl/include/CL/cl_gl_ext.h create mode 100644 third_party/opencl/include/CL/cl_platform.h create mode 100644 third_party/opencl/include/CL/opencl.h diff --git a/Dockerfile.openpilot_base b/Dockerfile.openpilot_base index 8a6041299..44d8d95e9 100644 --- a/Dockerfile.openpilot_base +++ b/Dockerfile.openpilot_base @@ -18,6 +18,43 @@ RUN /tmp/tools/install_ubuntu_dependencies.sh && \ cd /usr/lib/gcc/arm-none-eabi/* && \ rm -rf arm/ thumb/nofp thumb/v6* thumb/v8* thumb/v7+fp thumb/v7-r+fp.sp +# Add OpenCL +RUN apt-get update && apt-get install -y --no-install-recommends \ + apt-utils \ + alien \ + unzip \ + tar \ + curl \ + xz-utils \ + dbus \ + gcc-arm-none-eabi \ + tmux \ + vim \ + libx11-6 \ + wget \ + && rm -rf /var/lib/apt/lists/* + +RUN mkdir -p /tmp/opencl-driver-intel && \ + cd /tmp/opencl-driver-intel && \ + wget https://github.com/intel/llvm/releases/download/2024-WW14/oclcpuexp-2024.17.3.0.09_rel.tar.gz && \ + wget https://github.com/oneapi-src/oneTBB/releases/download/v2021.12.0/oneapi-tbb-2021.12.0-lin.tgz && \ + mkdir -p /opt/intel/oclcpuexp_2024.17.3.0.09_rel && \ + cd /opt/intel/oclcpuexp_2024.17.3.0.09_rel && \ + tar -zxvf /tmp/opencl-driver-intel/oclcpuexp-2024.17.3.0.09_rel.tar.gz && \ + mkdir -p /etc/OpenCL/vendors && \ + echo /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64/libintelocl.so > /etc/OpenCL/vendors/intel_expcpu.icd && \ + cd /opt/intel && \ + tar -zxvf /tmp/opencl-driver-intel/oneapi-tbb-2021.12.0-lin.tgz && \ + ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbb.so /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \ + ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbbmalloc.so /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \ + ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbb.so.12 /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \ + ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbbmalloc.so.2 /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \ + mkdir -p /etc/ld.so.conf.d && \ + echo /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 > /etc/ld.so.conf.d/libintelopenclexp.conf && \ + ldconfig -f /etc/ld.so.conf.d/libintelopenclexp.conf && \ + cd / && \ + rm -rf /tmp/opencl-driver-intel + ENV NVIDIA_VISIBLE_DEVICES=all ENV NVIDIA_DRIVER_CAPABILITIES=graphics,utility,compute ENV QTWEBENGINE_DISABLE_SANDBOX=1 diff --git a/SConstruct b/SConstruct index 4f04be624..ca5b7b6cb 100644 --- a/SConstruct +++ b/SConstruct @@ -94,6 +94,7 @@ env = Environment( # Arch-specific flags and paths if arch == "larch64": + env.Append(CPPPATH=["#third_party/opencl/include"]) env.Append(LIBPATH=[ "/usr/local/lib", "/system/vendor/lib64", diff --git a/common/SConscript b/common/SConscript index 15a0e5eff..1c68cf05c 100644 --- a/common/SConscript +++ b/common/SConscript @@ -5,6 +5,7 @@ common_libs = [ 'swaglog.cc', 'util.cc', 'ratekeeper.cc', + 'clutil.cc', ] _common = env.Library('common', common_libs, LIBS="json11") diff --git a/common/clutil.cc b/common/clutil.cc new file mode 100644 index 000000000..f8381a7e0 --- /dev/null +++ b/common/clutil.cc @@ -0,0 +1,98 @@ +#include "common/clutil.h" + +#include +#include +#include + +#include "common/util.h" +#include "common/swaglog.h" + +namespace { // helper functions + +template +std::string get_info(Func get_info_func, Id id, Name param_name) { + size_t size = 0; + CL_CHECK(get_info_func(id, param_name, 0, NULL, &size)); + std::string info(size, '\0'); + CL_CHECK(get_info_func(id, param_name, size, info.data(), NULL)); + return info; +} +inline std::string get_platform_info(cl_platform_id id, cl_platform_info name) { return get_info(&clGetPlatformInfo, id, name); } +inline std::string get_device_info(cl_device_id id, cl_device_info name) { return get_info(&clGetDeviceInfo, id, name); } + +void cl_print_info(cl_platform_id platform, cl_device_id device) { + size_t work_group_size = 0; + cl_device_type device_type = 0; + clGetDeviceInfo(device, CL_DEVICE_MAX_WORK_GROUP_SIZE, sizeof(work_group_size), &work_group_size, NULL); + clGetDeviceInfo(device, CL_DEVICE_TYPE, sizeof(device_type), &device_type, NULL); + const char *type_str = "Other..."; + switch (device_type) { + case CL_DEVICE_TYPE_CPU: type_str ="CL_DEVICE_TYPE_CPU"; break; + case CL_DEVICE_TYPE_GPU: type_str = "CL_DEVICE_TYPE_GPU"; break; + case CL_DEVICE_TYPE_ACCELERATOR: type_str = "CL_DEVICE_TYPE_ACCELERATOR"; break; + } + + LOGD("vendor: %s", get_platform_info(platform, CL_PLATFORM_VENDOR).c_str()); + LOGD("platform version: %s", get_platform_info(platform, CL_PLATFORM_VERSION).c_str()); + LOGD("profile: %s", get_platform_info(platform, CL_PLATFORM_PROFILE).c_str()); + LOGD("extensions: %s", get_platform_info(platform, CL_PLATFORM_EXTENSIONS).c_str()); + LOGD("name: %s", get_device_info(device, CL_DEVICE_NAME).c_str()); + LOGD("device version: %s", get_device_info(device, CL_DEVICE_VERSION).c_str()); + LOGD("max work group size: %zu", work_group_size); + LOGD("type = %d, %s", (int)device_type, type_str); +} + +void cl_print_build_errors(cl_program program, cl_device_id device) { + cl_build_status status; + clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_STATUS, sizeof(status), &status, NULL); + size_t log_size; + clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, 0, NULL, &log_size); + std::string log(log_size, '\0'); + clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, log_size, &log[0], NULL); + + LOGE("build failed; status=%d, log: %s", status, log.c_str()); +} + +} // namespace + +cl_device_id cl_get_device_id(cl_device_type device_type) { + cl_uint num_platforms = 0; + CL_CHECK(clGetPlatformIDs(0, NULL, &num_platforms)); + std::unique_ptr platform_ids = std::make_unique(num_platforms); + CL_CHECK(clGetPlatformIDs(num_platforms, &platform_ids[0], NULL)); + + for (size_t i = 0; i < num_platforms; ++i) { + LOGD("platform[%zu] CL_PLATFORM_NAME: %s", i, get_platform_info(platform_ids[i], CL_PLATFORM_NAME).c_str()); + + // Get first device + if (cl_device_id device_id = NULL; clGetDeviceIDs(platform_ids[i], device_type, 1, &device_id, NULL) == 0 && device_id) { + cl_print_info(platform_ids[i], device_id); + return device_id; + } + } + LOGE("No valid openCL platform found"); + assert(0); + return nullptr; +} + +cl_context cl_create_context(cl_device_id device_id) { + return CL_CHECK_ERR(clCreateContext(NULL, 1, &device_id, NULL, NULL, &err)); +} + +void cl_release_context(cl_context context) { + clReleaseContext(context); +} + +cl_program cl_program_from_file(cl_context ctx, cl_device_id device_id, const char* path, const char* args) { + return cl_program_from_source(ctx, device_id, util::read_file(path), args); +} + +cl_program cl_program_from_source(cl_context ctx, cl_device_id device_id, const std::string& src, const char* args) { + const char *csrc = src.c_str(); + cl_program prg = CL_CHECK_ERR(clCreateProgramWithSource(ctx, 1, &csrc, NULL, &err)); + if (int err = clBuildProgram(prg, 1, &device_id, args, NULL, NULL); err != 0) { + cl_print_build_errors(prg, device_id); + assert(0); + } + return prg; +} diff --git a/common/clutil.h b/common/clutil.h new file mode 100644 index 000000000..b364e79d4 --- /dev/null +++ b/common/clutil.h @@ -0,0 +1,28 @@ +#pragma once + +#ifdef __APPLE__ +#include +#else +#include +#endif + +#include + +#define CL_CHECK(_expr) \ + do { \ + assert(CL_SUCCESS == (_expr)); \ + } while (0) + +#define CL_CHECK_ERR(_expr) \ + ({ \ + cl_int err = CL_INVALID_VALUE; \ + __typeof__(_expr) _ret = _expr; \ + assert(_ret&& err == CL_SUCCESS); \ + _ret; \ + }) + +cl_device_id cl_get_device_id(cl_device_type device_type); +cl_context cl_create_context(cl_device_id device_id); +void cl_release_context(cl_context context); +cl_program cl_program_from_source(cl_context ctx, cl_device_id device_id, const std::string& src, const char* args = nullptr); +cl_program cl_program_from_file(cl_context ctx, cl_device_id device_id, const char* path, const char* args); diff --git a/common/mat.h b/common/mat.h new file mode 100644 index 000000000..8e10d6197 --- /dev/null +++ b/common/mat.h @@ -0,0 +1,85 @@ +#pragma once + +typedef struct vec3 { + float v[3]; +} vec3; + +typedef struct vec4 { + float v[4]; +} vec4; + +typedef struct mat3 { + float v[3*3]; +} mat3; + +typedef struct mat4 { + float v[4*4]; +} mat4; + +static inline mat3 matmul3(const mat3 &a, const mat3 &b) { + mat3 ret = {{0.0}}; + for (int r=0; r<3; r++) { + for (int c=0; c<3; c++) { + float v = 0.0; + for (int k=0; k<3; k++) { + v += a.v[r*3+k] * b.v[k*3+c]; + } + ret.v[r*3+c] = v; + } + } + return ret; +} + +static inline vec3 matvecmul3(const mat3 &a, const vec3 &b) { + vec3 ret = {{0.0}}; + for (int r=0; r<3; r++) { + for (int c=0; c<3; c++) { + ret.v[r] += a.v[r*3+c] * b.v[c]; + } + } + return ret; +} + +static inline mat4 matmul(const mat4 &a, const mat4 &b) { + mat4 ret = {{0.0}}; + for (int r=0; r<4; r++) { + for (int c=0; c<4; c++) { + float v = 0.0; + for (int k=0; k<4; k++) { + v += a.v[r*4+k] * b.v[k*4+c]; + } + ret.v[r*4+c] = v; + } + } + return ret; +} + +static inline vec4 matvecmul(const mat4 &a, const vec4 &b) { + vec4 ret = {{0.0}}; + for (int r=0; r<4; r++) { + for (int c=0; c<4; c++) { + ret.v[r] += a.v[r*4+c] * b.v[c]; + } + } + return ret; +} + +// scales the input and output space of a transformation matrix +// that assumes pixel-center origin. +static inline mat3 transform_scale_buffer(const mat3 &in, float s) { + // in_pt = ( transform(out_pt/s + 0.5) - 0.5) * s + + mat3 transform_out = {{ + 1.0f/s, 0.0f, 0.5f, + 0.0f, 1.0f/s, 0.5f, + 0.0f, 0.0f, 1.0f, + }}; + + mat3 transform_in = {{ + s, 0.0f, -0.5f*s, + 0.0f, s, -0.5f*s, + 0.0f, 0.0f, 1.0f, + }}; + + return matmul3(transform_in, matmul3(in, transform_out)); +} diff --git a/msgq_repo b/msgq_repo index f9ebdca88..20f249385 160000 --- a/msgq_repo +++ b/msgq_repo @@ -1 +1 @@ -Subproject commit f9ebdca885cfe25295d09de9fd57023a10758de5 +Subproject commit 20f2493855ef32339b80f0ad76b3cb82210dc474 diff --git a/selfdrive/modeld/SConscript b/selfdrive/modeld/SConscript index 72d3f95c4..91f359744 100644 --- a/selfdrive/modeld/SConscript +++ b/selfdrive/modeld/SConscript @@ -1,10 +1,35 @@ import os import glob -Import('env', 'arch') +Import('env', 'envCython', 'arch', 'cereal', 'messaging', 'common', 'visionipc') lenv = env.Clone() +lenvCython = envCython.Clone() -tinygrad_files = ["#"+x for x in glob.glob(env.Dir("#tinygrad_repo").relpath + "/**", recursive=True, root_dir=env.Dir("#").abspath) if 'pycache' not in x] +libs = [cereal, messaging, visionipc, common, 'capnp', 'kj', 'pthread'] +frameworks = [] + +common_src = [ + "models/commonmodel.cc", + "transforms/loadyuv.cc", + "transforms/transform.cc", +] + +# OpenCL is a framework on Mac +if arch == "Darwin": + frameworks += ['OpenCL'] +else: + libs += ['OpenCL'] + +# Set path definitions +for pathdef, fn in {'TRANSFORM': 'transforms/transform.cl', 'LOADYUV': 'transforms/loadyuv.cl'}.items(): + for xenv in (lenv, lenvCython): + xenv['CXXFLAGS'].append(f'-D{pathdef}_PATH=\\"{File(fn).abspath}\\"') + +# Compile cython +cython_libs = envCython["LIBS"] + libs +commonmodel_lib = lenv.Library('commonmodel', common_src) +lenvCython.Program('models/commonmodel_pyx.so', 'models/commonmodel_pyx.pyx', LIBS=[commonmodel_lib, *cython_libs], FRAMEWORKS=frameworks) +tinygrad_files = sorted(["#"+x for x in glob.glob(env.Dir("#tinygrad_repo").relpath + "/**", recursive=True, root_dir=env.Dir("#").abspath) if 'pycache' not in x]) # Get model metadata for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']: @@ -13,30 +38,22 @@ for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']: 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 warp -tg_flags = { - 'larch64': 'DEV=QCOM FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0', - 'Darwin': f'DEV=CPU HOME={os.path.expanduser("~")}', # tinygrad calls brew which needs a $HOME in the env -}.get(arch, 'DEV=CPU CPU_LLVM=1') -image_flag = { - 'larch64': 'IMAGE=2', -}.get(arch, 'IMAGE=0') -script_files = [File(Dir("#selfdrive/modeld").File("compile_warp.py").abspath)] -cmd = f'{tg_flags} python3 {Dir("#selfdrive/modeld").abspath}/compile_warp.py ' -lenv.Command(fn + "warp_tinygrad.pkl", tinygrad_files + script_files, cmd) - 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} {image_flag} python3 {Dir("#tinygrad_repo").abspath}/examples/openpilot/compile3.py {fn}.onnx {fn}_tinygrad.pkl' + 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']: - tg_compile(tg_flags, model_name) + flags = { + 'larch64': 'DEV=QCOM FLOAT16=1 NOLOCALS=1 IMAGE=2 JIT_BATCH_SIZE=0', + 'Darwin': f'DEV=CPU HOME={os.path.expanduser("~")}', # tinygrad calls brew which needs a $HOME in the env + }.get(arch, 'DEV=CPU CPU_LLVM=1') + tg_compile(flags, model_name) # Compile BIG model if USB GPU is available if "USBGPU" in os.environ: diff --git a/selfdrive/modeld/compile_warp.py b/selfdrive/modeld/compile_warp.py deleted file mode 100755 index 5adb60e62..000000000 --- a/selfdrive/modeld/compile_warp.py +++ /dev/null @@ -1,206 +0,0 @@ -#!/usr/bin/env python3 -import time -import pickle -import numpy as np -from pathlib import Path -from tinygrad.tensor import Tensor -from tinygrad.helpers import Context -from tinygrad.device import Device -from tinygrad.engine.jit import TinyJit - -from openpilot.system.camerad.cameras.nv12_info import get_nv12_info -from openpilot.common.transformations.model import MEDMODEL_INPUT_SIZE, DM_INPUT_SIZE -from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye - -MODELS_DIR = Path(__file__).parent / 'models' - -CAMERA_CONFIGS = [ - (_ar_ox_fisheye.width, _ar_ox_fisheye.height), # tici: 1928x1208 - (_os_fisheye.width, _os_fisheye.height), # mici: 1344x760 -] - -UV_SCALE_MATRIX = np.array([[0.5, 0, 0], [0, 0.5, 0], [0, 0, 1]], dtype=np.float32) -UV_SCALE_MATRIX_INV = np.linalg.inv(UV_SCALE_MATRIX) - -IMG_BUFFER_SHAPE = (30, MEDMODEL_INPUT_SIZE[1] // 2, MEDMODEL_INPUT_SIZE[0] // 2) - - -def warp_pkl_path(w, h): - return MODELS_DIR / f'warp_{w}x{h}_tinygrad.pkl' - - -def dm_warp_pkl_path(w, h): - return MODELS_DIR / f'dm_warp_{w}x{h}_tinygrad.pkl' - - -def warp_perspective_tinygrad(src_flat, M_inv, dst_shape, src_shape, stride_pad, ratio): - w_dst, h_dst = dst_shape - h_src, w_src = src_shape - - x = Tensor.arange(w_dst).reshape(1, w_dst).expand(h_dst, w_dst) - y = Tensor.arange(h_dst).reshape(h_dst, 1).expand(h_dst, w_dst) - ones = Tensor.ones_like(x) - dst_coords = x.reshape(1, -1).cat(y.reshape(1, -1)).cat(ones.reshape(1, -1)) - - src_coords = M_inv @ dst_coords - src_coords = src_coords / src_coords[2:3, :] - - x_nn_clipped = Tensor.round(src_coords[0]).clip(0, w_src - 1).cast('int') - y_nn_clipped = Tensor.round(src_coords[1]).clip(0, h_src - 1).cast('int') - idx = y_nn_clipped * w_src + (y_nn_clipped * ratio).cast('int') * stride_pad + x_nn_clipped - - sampled = src_flat[idx] - return sampled - - -def frames_to_tensor(frames, model_w, model_h): - H = (frames.shape[0] * 2) // 3 - W = frames.shape[1] - in_img1 = Tensor.cat(frames[0:H:2, 0::2], - frames[1:H:2, 0::2], - frames[0:H:2, 1::2], - frames[1:H:2, 1::2], - frames[H:H+H//4].reshape((H//2, W//2)), - frames[H+H//4:H+H//2].reshape((H//2, W//2)), dim=0).reshape((6, H//2, W//2)) - return in_img1 - - -def make_frame_prepare(cam_w, cam_h, model_w, model_h): - stride, y_height, _, _ = get_nv12_info(cam_w, cam_h) - uv_offset = stride * y_height - stride_pad = stride - cam_w - - def frame_prepare_tinygrad(input_frame, M_inv): - tg_scale = Tensor(UV_SCALE_MATRIX) - M_inv_uv = tg_scale @ M_inv @ Tensor(UV_SCALE_MATRIX_INV) - with Context(SPLIT_REDUCEOP=0): - y = warp_perspective_tinygrad(input_frame[:cam_h*stride], - M_inv, (model_w, model_h), - (cam_h, cam_w), stride_pad, 1).realize() - u = warp_perspective_tinygrad(input_frame[uv_offset:uv_offset + (cam_h//4)*stride], - M_inv_uv, (model_w//2, model_h//2), - (cam_h//2, cam_w//2), stride_pad, 0.5).realize() - v = warp_perspective_tinygrad(input_frame[uv_offset + (cam_h//4)*stride:uv_offset + (cam_h//2)*stride], - M_inv_uv, (model_w//2, model_h//2), - (cam_h//2, cam_w//2), stride_pad, 0.5).realize() - yuv = y.cat(u).cat(v).reshape((model_h * 3 // 2, model_w)) - tensor = frames_to_tensor(yuv, model_w, model_h) - return tensor - return frame_prepare_tinygrad - - -def make_update_img_input(frame_prepare, model_w, model_h): - def update_img_input_tinygrad(tensor, frame, M_inv): - M_inv = M_inv.to(Device.DEFAULT) - new_img = frame_prepare(frame, M_inv) - full_buffer = tensor[6:].cat(new_img, dim=0).contiguous() - return full_buffer, Tensor.cat(full_buffer[:6], full_buffer[-6:], dim=0).contiguous().reshape(1, 12, model_h//2, model_w//2) - return update_img_input_tinygrad - - -def make_update_both_imgs(frame_prepare, model_w, model_h): - update_img = make_update_img_input(frame_prepare, model_w, model_h) - - def update_both_imgs_tinygrad(calib_img_buffer, new_img, M_inv, - calib_big_img_buffer, new_big_img, M_inv_big): - calib_img_buffer, calib_img_pair = update_img(calib_img_buffer, new_img, M_inv) - calib_big_img_buffer, calib_big_img_pair = update_img(calib_big_img_buffer, new_big_img, M_inv_big) - return calib_img_buffer, calib_img_pair, calib_big_img_buffer, calib_big_img_pair - return update_both_imgs_tinygrad - - -def make_warp_dm(cam_w, cam_h, dm_w, dm_h): - stride, y_height, _, _ = get_nv12_info(cam_w, cam_h) - stride_pad = stride - cam_w - - def warp_dm(input_frame, M_inv): - M_inv = M_inv.to(Device.DEFAULT) - with Context(SPLIT_REDUCEOP=0): - result = warp_perspective_tinygrad(input_frame[:cam_h*stride], M_inv, (dm_w, dm_h), (cam_h, cam_w), stride_pad, 1).reshape(-1, dm_h * dm_w) - return result - return warp_dm - - -def compile_modeld_warp(cam_w, cam_h): - model_w, model_h = MEDMODEL_INPUT_SIZE - _, _, _, yuv_size = get_nv12_info(cam_w, cam_h) - - print(f"Compiling modeld warp for {cam_w}x{cam_h}...") - - frame_prepare = make_frame_prepare(cam_w, cam_h, model_w, model_h) - update_both_imgs = make_update_both_imgs(frame_prepare, model_w, model_h) - update_img_jit = TinyJit(update_both_imgs, prune=True) - - full_buffer = Tensor.zeros(IMG_BUFFER_SHAPE, dtype='uint8').contiguous().realize() - big_full_buffer = Tensor.zeros(IMG_BUFFER_SHAPE, dtype='uint8').contiguous().realize() - full_buffer_np = np.zeros(IMG_BUFFER_SHAPE, dtype=np.uint8) - big_full_buffer_np = np.zeros(IMG_BUFFER_SHAPE, dtype=np.uint8) - - for i in range(10): - new_frame_np = (32 * np.random.randn(yuv_size).astype(np.float32) + 128).clip(0, 255).astype(np.uint8) - img_inputs = [full_buffer, - Tensor.from_blob(new_frame_np.ctypes.data, (yuv_size,), dtype='uint8').realize(), - Tensor(Tensor.randn(3, 3).mul(8).realize().numpy(), device='NPY')] - new_big_frame_np = (32 * np.random.randn(yuv_size).astype(np.float32) + 128).clip(0, 255).astype(np.uint8) - big_img_inputs = [big_full_buffer, - Tensor.from_blob(new_big_frame_np.ctypes.data, (yuv_size,), dtype='uint8').realize(), - Tensor(Tensor.randn(3, 3).mul(8).realize().numpy(), device='NPY')] - inputs = img_inputs + big_img_inputs - Device.default.synchronize() - - inputs_np = [x.numpy() for x in inputs] - inputs_np[0] = full_buffer_np - inputs_np[3] = big_full_buffer_np - - st = time.perf_counter() - out = update_img_jit(*inputs) - full_buffer = out[0].contiguous().realize().clone() - big_full_buffer = out[2].contiguous().realize().clone() - mt = time.perf_counter() - Device.default.synchronize() - et = time.perf_counter() - print(f" [{i+1}/10] enqueue {(mt-st)*1e3:6.2f} ms -- total {(et-st)*1e3:6.2f} ms") - - pkl_path = warp_pkl_path(cam_w, cam_h) - with open(pkl_path, "wb") as f: - pickle.dump(update_img_jit, f) - print(f" Saved to {pkl_path}") - - jit = pickle.load(open(pkl_path, "rb")) - jit(*inputs) - - -def compile_dm_warp(cam_w, cam_h): - dm_w, dm_h = DM_INPUT_SIZE - _, _, _, yuv_size = get_nv12_info(cam_w, cam_h) - - print(f"Compiling DM warp for {cam_w}x{cam_h}...") - - warp_dm = make_warp_dm(cam_w, cam_h, dm_w, dm_h) - warp_dm_jit = TinyJit(warp_dm, prune=True) - - for i in range(10): - inputs = [Tensor.from_blob((32 * Tensor.randn(yuv_size,) + 128).cast(dtype='uint8').realize().numpy().ctypes.data, (yuv_size,), dtype='uint8'), - Tensor(Tensor.randn(3, 3).mul(8).realize().numpy(), device='NPY')] - Device.default.synchronize() - st = time.perf_counter() - warp_dm_jit(*inputs) - mt = time.perf_counter() - Device.default.synchronize() - et = time.perf_counter() - print(f" [{i+1}/10] enqueue {(mt-st)*1e3:6.2f} ms -- total {(et-st)*1e3:6.2f} ms") - - pkl_path = dm_warp_pkl_path(cam_w, cam_h) - with open(pkl_path, "wb") as f: - pickle.dump(warp_dm_jit, f) - print(f" Saved to {pkl_path}") - - -def run_and_save_pickle(): - for cam_w, cam_h in CAMERA_CONFIGS: - compile_modeld_warp(cam_w, cam_h) - compile_dm_warp(cam_w, cam_h) - - -if __name__ == "__main__": - run_and_save_pickle() diff --git a/selfdrive/modeld/dmonitoringmodeld.py b/selfdrive/modeld/dmonitoringmodeld.py index 7d4df713c..fca762c69 100755 --- a/selfdrive/modeld/dmonitoringmodeld.py +++ b/selfdrive/modeld/dmonitoringmodeld.py @@ -3,6 +3,7 @@ import os from openpilot.system.hardware import TICI os.environ['DEV'] = 'QCOM' if TICI else 'CPU' from tinygrad.tensor import Tensor +from tinygrad.dtype import dtypes import time import pickle import numpy as np @@ -15,34 +16,32 @@ from openpilot.common.swaglog import cloudlog from openpilot.common.realtime import config_realtime_process from openpilot.common.transformations.model import dmonitoringmodel_intrinsics from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye -from openpilot.system.camerad.cameras.nv12_info import get_nv12_info +from openpilot.selfdrive.modeld.models.commonmodel_pyx import CLContext, MonitoringModelFrame from openpilot.selfdrive.modeld.parse_model_outputs import sigmoid, safe_exp +from openpilot.selfdrive.modeld.runners.tinygrad_helpers import qcom_tensor_from_opencl_address PROCESS_NAME = "selfdrive.modeld.dmonitoringmodeld" SEND_RAW_PRED = os.getenv('SEND_RAW_PRED') MODEL_PKL_PATH = Path(__file__).parent / 'models/dmonitoring_model_tinygrad.pkl' METADATA_PATH = Path(__file__).parent / 'models/dmonitoring_model_metadata.pkl' -MODELS_DIR = Path(__file__).parent / 'models' + class ModelState: inputs: dict[str, np.ndarray] output: np.ndarray - def __init__(self): + def __init__(self, cl_ctx): with open(METADATA_PATH, 'rb') as f: model_metadata = pickle.load(f) self.input_shapes = model_metadata['input_shapes'] self.output_slices = model_metadata['output_slices'] + self.frame = MonitoringModelFrame(cl_ctx) self.numpy_inputs = { 'calib': np.zeros(self.input_shapes['calib'], dtype=np.float32), } - self.warp_inputs_np = {'transform': np.zeros((3,3), dtype=np.float32)} - self.warp_inputs = {k: Tensor(v, device='NPY') for k,v in self.warp_inputs_np.items()} - self.frame_buf_params = None self.tensor_inputs = {k: Tensor(v, device='NPY').realize() for k,v in self.numpy_inputs.items()} - self.image_warp = None with open(MODEL_PKL_PATH, "rb") as f: self.model_run = pickle.load(f) @@ -51,15 +50,14 @@ class ModelState: t1 = time.perf_counter() - if self.image_warp is None: - self.frame_buf_params = get_nv12_info(buf.width, buf.height) - warp_path = MODELS_DIR / f'dm_warp_{buf.width}x{buf.height}_tinygrad.pkl' - with open(warp_path, "rb") as f: - self.image_warp = pickle.load(f) - self.warp_inputs['frame'] = Tensor.from_blob(buf.data.ctypes.data, (self.frame_buf_params[3],), dtype='uint8').realize() + input_img_cl = self.frame.prepare(buf, transform.flatten()) + if TICI: + # The imgs tensors are backed by opencl memory, only need init once + if 'input_img' not in self.tensor_inputs: + self.tensor_inputs['input_img'] = qcom_tensor_from_opencl_address(input_img_cl.mem_address, self.input_shapes['input_img'], dtype=dtypes.uint8) + else: + self.tensor_inputs['input_img'] = Tensor(self.frame.buffer_from_cl(input_img_cl).reshape(self.input_shapes['input_img']), dtype=dtypes.uint8).realize() - self.warp_inputs_np['transform'][:] = transform[:] - self.tensor_inputs['input_img'] = self.image_warp(self.warp_inputs['frame'], self.warp_inputs['transform']).realize() output = self.model_run(**self.tensor_inputs).contiguous().realize().uop.base.buffer.numpy() @@ -109,11 +107,12 @@ def get_driverstate_packet(model_output, frame_id: int, location_ts: int, exec_t def main(): config_realtime_process(7, 5) - model = ModelState() + cl_context = CLContext() + model = ModelState(cl_context) cloudlog.warning("models loaded, dmonitoringmodeld starting") cloudlog.warning("connecting to driver stream") - vipc_client = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_DRIVER, True) + vipc_client = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_DRIVER, True, cl_context) while not vipc_client.connect(False): time.sleep(0.1) assert vipc_client.is_connected() diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index 7fae36510..1f347dc32 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -7,6 +7,7 @@ if USBGPU: os.environ['DEV'] = 'AMD' os.environ['AMD_IFACE'] = 'USB' from tinygrad.tensor import Tensor +from tinygrad.dtype import dtypes import time import pickle import numpy as np @@ -21,13 +22,14 @@ from openpilot.common.params import Params from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.realtime import config_realtime_process, DT_MDL from openpilot.common.transformations.camera import DEVICE_CAMERAS -from openpilot.system.camerad.cameras.nv12_info import get_nv12_info from openpilot.common.transformations.model import get_warp_matrix 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.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 +from openpilot.selfdrive.modeld.models.commonmodel_pyx import DrivingModelFrame, CLContext +from openpilot.selfdrive.modeld.runners.tinygrad_helpers import qcom_tensor_from_opencl_address PROCESS_NAME = "selfdrive.modeld.modeld" @@ -37,15 +39,11 @@ VISION_PKL_PATH = Path(__file__).parent / 'models/driving_vision_tinygrad.pkl' 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' -MODELS_DIR = Path(__file__).parent / 'models' LAT_SMOOTH_SECONDS = 0.0 LONG_SMOOTH_SECONDS = 0.3 MIN_LAT_CONTROL_SPEED = 0.3 -IMG_QUEUE_SHAPE = (6*(ModelConstants.MODEL_RUN_FREQ//ModelConstants.MODEL_CONTEXT_FREQ + 1), 128, 256) -assert IMG_QUEUE_SHAPE[0] == 30 - def get_action_from_model(model_output: dict[str, np.ndarray], prev_action: log.ModelDataV2.Action, lat_action_t: float, long_action_t: float, v_ego: float) -> log.ModelDataV2.Action: @@ -138,11 +136,12 @@ class InputQueues: return out class ModelState: + frames: dict[str, DrivingModelFrame] inputs: dict[str, np.ndarray] output: np.ndarray prev_desire: np.ndarray # for tracking the rising edge of the pulse - def __init__(self): + def __init__(self, context: CLContext): with open(VISION_METADATA_PATH, 'rb') as f: vision_metadata = pickle.load(f) self.vision_input_shapes = vision_metadata['input_shapes'] @@ -156,6 +155,7 @@ class ModelState: self.policy_output_slices = policy_metadata['output_slices'] policy_output_size = policy_metadata['output_shapes']['outputs'][1] + self.frames = {name: DrivingModelFrame(context, ModelConstants.MODEL_RUN_FREQ//ModelConstants.MODEL_CONTEXT_FREQ) for name in self.vision_input_names} self.prev_desire = np.zeros(ModelConstants.DESIRE_LEN, dtype=np.float32) # policy inputs @@ -165,17 +165,12 @@ class ModelState: self.full_input_queues.update_dtypes_and_shapes({k: self.numpy_inputs[k].dtype}, {k: self.numpy_inputs[k].shape}) self.full_input_queues.reset() - self.img_queues = {'img': Tensor.zeros(IMG_QUEUE_SHAPE, dtype='uint8').contiguous().realize(), - 'big_img': Tensor.zeros(IMG_QUEUE_SHAPE, dtype='uint8').contiguous().realize(),} - self.full_frames : dict[str, Tensor] = {} - self.transforms_np = {k: np.zeros((3,3), dtype=np.float32) for k in self.img_queues} - self.transforms = {k: Tensor(v, device='NPY').realize() for k, v in self.transforms_np.items()} + # img buffers are managed in openCL transform code + self.vision_inputs: dict[str, Tensor] = {} self.vision_output = np.zeros(vision_output_size, dtype=np.float32) self.policy_inputs = {k: Tensor(v, device='NPY').realize() for k,v in self.numpy_inputs.items()} self.policy_output = np.zeros(policy_output_size, dtype=np.float32) self.parser = Parser() - self.frame_buf_params : dict[str, tuple[int, int, int, int]] = {} - self.update_imgs = None with open(VISION_PKL_PATH, "rb") as f: self.vision_run = pickle.load(f) @@ -193,28 +188,23 @@ class ModelState: inputs['desire_pulse'][0] = 0 new_desire = np.where(inputs['desire_pulse'] - self.prev_desire > .99, inputs['desire_pulse'], 0) self.prev_desire[:] = inputs['desire_pulse'] - if self.update_imgs is None: - for key in bufs.keys(): - w, h = bufs[key].width, bufs[key].height - self.frame_buf_params[key] = get_nv12_info(w, h) - warp_path = MODELS_DIR / f'warp_{w}x{h}_tinygrad.pkl' - with open(warp_path, "rb") as f: - self.update_imgs = pickle.load(f) + imgs_cl = {name: self.frames[name].prepare(bufs[name], transforms[name].flatten()) for name in self.vision_input_names} - for key in bufs.keys(): - self.full_frames[key] = Tensor.from_blob(bufs[key].data.ctypes.data, (self.frame_buf_params[key][3],), dtype='uint8').realize() - self.transforms_np[key][:,:] = transforms[key][:,:] - - out = self.update_imgs(self.img_queues['img'], self.full_frames['img'], self.transforms['img'], - self.img_queues['big_img'], self.full_frames['big_img'], self.transforms['big_img']) - self.img_queues['img'], self.img_queues['big_img'], = out[0].realize(), out[2].realize() - vision_inputs = {'img': out[1], 'big_img': out[3]} + 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: + self.vision_inputs[key] = qcom_tensor_from_opencl_address(imgs_cl[key].mem_address, self.vision_input_shapes[key], dtype=dtypes.uint8) + else: + for key in imgs_cl: + frame_input = self.frames[key].buffer_from_cl(imgs_cl[key]).reshape(self.vision_input_shapes[key]) + self.vision_inputs[key] = Tensor(frame_input, dtype=dtypes.uint8).realize() if prepare_only: return None - self.vision_output = self.vision_run(**vision_inputs).contiguous().realize().uop.base.buffer.numpy() + self.vision_output = self.vision_run(**self.vision_inputs).contiguous().realize().uop.base.buffer.numpy() vision_outputs_dict = self.parser.parse_vision_outputs(self.slice_outputs(self.vision_output, self.vision_output_slices)) self.full_input_queues.enqueue({'features_buffer': vision_outputs_dict['hidden_state'], 'desire_pulse': new_desire}) @@ -224,6 +214,7 @@ class ModelState: self.policy_output = self.policy_run(**self.policy_inputs).contiguous().realize().uop.base.buffer.numpy() policy_outputs_dict = self.parser.parse_policy_outputs(self.slice_outputs(self.policy_output, self.policy_output_slices)) + combined_outputs_dict = {**vision_outputs_dict, **policy_outputs_dict} if SEND_RAW_PRED: combined_outputs_dict['raw_pred'] = np.concatenate([self.vision_output.copy(), self.policy_output.copy()]) @@ -240,8 +231,10 @@ def main(demo=False): config_realtime_process(7, 54) st = time.monotonic() - cloudlog.warning("loading model") - model = ModelState() + cloudlog.warning("setting up CL context") + cl_context = CLContext() + cloudlog.warning("CL context ready; loading model") + model = ModelState(cl_context) cloudlog.warning(f"models loaded in {time.monotonic() - st:.1f}s, modeld starting") # visionipc clients @@ -254,8 +247,8 @@ def main(demo=False): time.sleep(.1) vipc_client_main_stream = VisionStreamType.VISION_STREAM_WIDE_ROAD if main_wide_camera else VisionStreamType.VISION_STREAM_ROAD - vipc_client_main = VisionIpcClient("camerad", vipc_client_main_stream, True) - vipc_client_extra = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_WIDE_ROAD, False) + vipc_client_main = VisionIpcClient("camerad", vipc_client_main_stream, True, cl_context) + vipc_client_extra = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_WIDE_ROAD, False, cl_context) cloudlog.warning(f"vision stream set up, main_wide_camera: {main_wide_camera}, use_extra_client: {use_extra_client}") while not vipc_client_main.connect(False): diff --git a/selfdrive/modeld/models/commonmodel.cc b/selfdrive/modeld/models/commonmodel.cc new file mode 100644 index 000000000..d3341e76e --- /dev/null +++ b/selfdrive/modeld/models/commonmodel.cc @@ -0,0 +1,64 @@ +#include "selfdrive/modeld/models/commonmodel.h" + +#include +#include + +#include "common/clutil.h" + +DrivingModelFrame::DrivingModelFrame(cl_device_id device_id, cl_context context, int _temporal_skip) : ModelFrame(device_id, context) { + input_frames = std::make_unique(buf_size); + temporal_skip = _temporal_skip; + input_frames_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, buf_size, NULL, &err)); + img_buffer_20hz_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, (temporal_skip+1)*frame_size_bytes, NULL, &err)); + region.origin = temporal_skip * frame_size_bytes; + region.size = frame_size_bytes; + last_img_cl = CL_CHECK_ERR(clCreateSubBuffer(img_buffer_20hz_cl, CL_MEM_READ_WRITE, CL_BUFFER_CREATE_TYPE_REGION, ®ion, &err)); + + loadyuv_init(&loadyuv, context, device_id, MODEL_WIDTH, MODEL_HEIGHT); + init_transform(device_id, context, MODEL_WIDTH, MODEL_HEIGHT); +} + +cl_mem* DrivingModelFrame::prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection) { + run_transform(yuv_cl, MODEL_WIDTH, MODEL_HEIGHT, frame_width, frame_height, frame_stride, frame_uv_offset, projection); + + for (int i = 0; i < temporal_skip; i++) { + CL_CHECK(clEnqueueCopyBuffer(q, img_buffer_20hz_cl, img_buffer_20hz_cl, (i+1)*frame_size_bytes, i*frame_size_bytes, frame_size_bytes, 0, nullptr, nullptr)); + } + loadyuv_queue(&loadyuv, q, y_cl, u_cl, v_cl, last_img_cl); + + copy_queue(&loadyuv, q, img_buffer_20hz_cl, input_frames_cl, 0, 0, frame_size_bytes); + copy_queue(&loadyuv, q, last_img_cl, input_frames_cl, 0, frame_size_bytes, frame_size_bytes); + + // NOTE: Since thneed is using a different command queue, this clFinish is needed to ensure the image is ready. + clFinish(q); + return &input_frames_cl; +} + +DrivingModelFrame::~DrivingModelFrame() { + deinit_transform(); + loadyuv_destroy(&loadyuv); + CL_CHECK(clReleaseMemObject(input_frames_cl)); + CL_CHECK(clReleaseMemObject(img_buffer_20hz_cl)); + CL_CHECK(clReleaseMemObject(last_img_cl)); + CL_CHECK(clReleaseCommandQueue(q)); +} + + +MonitoringModelFrame::MonitoringModelFrame(cl_device_id device_id, cl_context context) : ModelFrame(device_id, context) { + input_frames = std::make_unique(buf_size); + input_frame_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, buf_size, NULL, &err)); + + init_transform(device_id, context, MODEL_WIDTH, MODEL_HEIGHT); +} + +cl_mem* MonitoringModelFrame::prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection) { + run_transform(yuv_cl, MODEL_WIDTH, MODEL_HEIGHT, frame_width, frame_height, frame_stride, frame_uv_offset, projection); + clFinish(q); + return &y_cl; +} + +MonitoringModelFrame::~MonitoringModelFrame() { + deinit_transform(); + CL_CHECK(clReleaseMemObject(input_frame_cl)); + CL_CHECK(clReleaseCommandQueue(q)); +} diff --git a/selfdrive/modeld/models/commonmodel.h b/selfdrive/modeld/models/commonmodel.h new file mode 100644 index 000000000..176d7eb6d --- /dev/null +++ b/selfdrive/modeld/models/commonmodel.h @@ -0,0 +1,97 @@ +#pragma once + +#include +#include +#include + +#include + +#define CL_USE_DEPRECATED_OPENCL_1_2_APIS +#ifdef __APPLE__ +#include +#else +#include +#endif + +#include "common/mat.h" +#include "selfdrive/modeld/transforms/loadyuv.h" +#include "selfdrive/modeld/transforms/transform.h" + +class ModelFrame { +public: + ModelFrame(cl_device_id device_id, cl_context context) { + q = CL_CHECK_ERR(clCreateCommandQueue(context, device_id, 0, &err)); + } + virtual ~ModelFrame() {} + virtual cl_mem* prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection) { return NULL; } + uint8_t* buffer_from_cl(cl_mem *in_frames, int buffer_size) { + CL_CHECK(clEnqueueReadBuffer(q, *in_frames, CL_TRUE, 0, buffer_size, input_frames.get(), 0, nullptr, nullptr)); + clFinish(q); + return &input_frames[0]; + } + + int MODEL_WIDTH; + int MODEL_HEIGHT; + int MODEL_FRAME_SIZE; + int buf_size; + +protected: + cl_mem y_cl, u_cl, v_cl; + Transform transform; + cl_command_queue q; + std::unique_ptr input_frames; + + void init_transform(cl_device_id device_id, cl_context context, int model_width, int model_height) { + y_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, model_width * model_height, NULL, &err)); + u_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, (model_width / 2) * (model_height / 2), NULL, &err)); + v_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, (model_width / 2) * (model_height / 2), NULL, &err)); + transform_init(&transform, context, device_id); + } + + void deinit_transform() { + transform_destroy(&transform); + CL_CHECK(clReleaseMemObject(v_cl)); + CL_CHECK(clReleaseMemObject(u_cl)); + CL_CHECK(clReleaseMemObject(y_cl)); + } + + void run_transform(cl_mem yuv_cl, int model_width, int model_height, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection) { + transform_queue(&transform, q, + yuv_cl, frame_width, frame_height, frame_stride, frame_uv_offset, + y_cl, u_cl, v_cl, model_width, model_height, projection); + } +}; + +class DrivingModelFrame : public ModelFrame { +public: + DrivingModelFrame(cl_device_id device_id, cl_context context, int _temporal_skip); + ~DrivingModelFrame(); + cl_mem* prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection); + + const int MODEL_WIDTH = 512; + const int MODEL_HEIGHT = 256; + const int MODEL_FRAME_SIZE = MODEL_WIDTH * MODEL_HEIGHT * 3 / 2; + const int buf_size = MODEL_FRAME_SIZE * 2; // 2 frames are temporal_skip frames apart + const size_t frame_size_bytes = MODEL_FRAME_SIZE * sizeof(uint8_t); + +private: + LoadYUVState loadyuv; + cl_mem img_buffer_20hz_cl, last_img_cl, input_frames_cl; + cl_buffer_region region; + int temporal_skip; +}; + +class MonitoringModelFrame : public ModelFrame { +public: + MonitoringModelFrame(cl_device_id device_id, cl_context context); + ~MonitoringModelFrame(); + cl_mem* prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection); + + const int MODEL_WIDTH = 1440; + const int MODEL_HEIGHT = 960; + const int MODEL_FRAME_SIZE = MODEL_WIDTH * MODEL_HEIGHT; + const int buf_size = MODEL_FRAME_SIZE; + +private: + cl_mem input_frame_cl; +}; diff --git a/selfdrive/modeld/models/commonmodel.pxd b/selfdrive/modeld/models/commonmodel.pxd new file mode 100644 index 000000000..4ac64d917 --- /dev/null +++ b/selfdrive/modeld/models/commonmodel.pxd @@ -0,0 +1,27 @@ +# distutils: language = c++ + +from msgq.visionipc.visionipc cimport cl_device_id, cl_context, cl_mem + +cdef extern from "common/mat.h": + cdef struct mat3: + float v[9] + +cdef extern from "common/clutil.h": + cdef unsigned long CL_DEVICE_TYPE_DEFAULT + cl_device_id cl_get_device_id(unsigned long) + cl_context cl_create_context(cl_device_id) + void cl_release_context(cl_context) + +cdef extern from "selfdrive/modeld/models/commonmodel.h": + cppclass ModelFrame: + int buf_size + unsigned char * buffer_from_cl(cl_mem*, int); + cl_mem * prepare(cl_mem, int, int, int, int, mat3) + + cppclass DrivingModelFrame: + int buf_size + DrivingModelFrame(cl_device_id, cl_context, int) + + cppclass MonitoringModelFrame: + int buf_size + MonitoringModelFrame(cl_device_id, cl_context) diff --git a/selfdrive/modeld/models/commonmodel_pyx.pxd b/selfdrive/modeld/models/commonmodel_pyx.pxd new file mode 100644 index 000000000..0bb798625 --- /dev/null +++ b/selfdrive/modeld/models/commonmodel_pyx.pxd @@ -0,0 +1,13 @@ +# distutils: language = c++ + +from msgq.visionipc.visionipc cimport cl_mem +from msgq.visionipc.visionipc_pyx cimport CLContext as BaseCLContext + +cdef class CLContext(BaseCLContext): + pass + +cdef class CLMem: + cdef cl_mem * mem + + @staticmethod + cdef create(void*) diff --git a/selfdrive/modeld/models/commonmodel_pyx.pyx b/selfdrive/modeld/models/commonmodel_pyx.pyx new file mode 100644 index 000000000..5b7d11bc7 --- /dev/null +++ b/selfdrive/modeld/models/commonmodel_pyx.pyx @@ -0,0 +1,74 @@ +# distutils: language = c++ +# cython: c_string_encoding=ascii, language_level=3 + +import numpy as np +cimport numpy as cnp +from libc.string cimport memcpy +from libc.stdint cimport uintptr_t + +from msgq.visionipc.visionipc cimport cl_mem +from msgq.visionipc.visionipc_pyx cimport VisionBuf, CLContext as BaseCLContext +from .commonmodel cimport CL_DEVICE_TYPE_DEFAULT, cl_get_device_id, cl_create_context, cl_release_context +from .commonmodel cimport mat3, ModelFrame as cppModelFrame, DrivingModelFrame as cppDrivingModelFrame, MonitoringModelFrame as cppMonitoringModelFrame + + +cdef class CLContext(BaseCLContext): + def __cinit__(self): + self.device_id = cl_get_device_id(CL_DEVICE_TYPE_DEFAULT) + self.context = cl_create_context(self.device_id) + + def __dealloc__(self): + if self.context: + cl_release_context(self.context) + +cdef class CLMem: + @staticmethod + cdef create(void * cmem): + mem = CLMem() + mem.mem = cmem + return mem + + @property + def mem_address(self): + return (self.mem) + +def cl_from_visionbuf(VisionBuf buf): + return CLMem.create(&buf.buf.buf_cl) + + +cdef class ModelFrame: + cdef cppModelFrame * frame + cdef int buf_size + + def __dealloc__(self): + del self.frame + + def prepare(self, VisionBuf buf, float[:] projection): + cdef mat3 cprojection + memcpy(cprojection.v, &projection[0], 9*sizeof(float)) + cdef cl_mem * data + data = self.frame.prepare(buf.buf.buf_cl, buf.width, buf.height, buf.stride, buf.uv_offset, cprojection) + return CLMem.create(data) + + def buffer_from_cl(self, CLMem in_frames): + cdef unsigned char * data2 + data2 = self.frame.buffer_from_cl(in_frames.mem, self.buf_size) + return np.asarray( data2) + + +cdef class DrivingModelFrame(ModelFrame): + cdef cppDrivingModelFrame * _frame + + def __cinit__(self, CLContext context, int temporal_skip): + self._frame = new cppDrivingModelFrame(context.device_id, context.context, temporal_skip) + self.frame = (self._frame) + self.buf_size = self._frame.buf_size + +cdef class MonitoringModelFrame(ModelFrame): + cdef cppMonitoringModelFrame * _frame + + def __cinit__(self, CLContext context): + self._frame = new cppMonitoringModelFrame(context.device_id, context.context) + self.frame = (self._frame) + self.buf_size = self._frame.buf_size + diff --git a/selfdrive/modeld/runners/tinygrad_helpers.py b/selfdrive/modeld/runners/tinygrad_helpers.py new file mode 100644 index 000000000..776381341 --- /dev/null +++ b/selfdrive/modeld/runners/tinygrad_helpers.py @@ -0,0 +1,8 @@ + +from tinygrad.tensor import Tensor +from tinygrad.helpers import to_mv + +def qcom_tensor_from_opencl_address(opencl_address, shape, dtype): + cl_buf_desc_ptr = to_mv(opencl_address, 8).cast('Q')[0] + rawbuf_ptr = to_mv(cl_buf_desc_ptr, 0x100).cast('Q')[20] # offset 0xA0 is a raw gpu pointer. + return Tensor.from_blob(rawbuf_ptr, shape, dtype=dtype, device='QCOM') diff --git a/selfdrive/modeld/transforms/loadyuv.cc b/selfdrive/modeld/transforms/loadyuv.cc new file mode 100644 index 000000000..c93f5cd03 --- /dev/null +++ b/selfdrive/modeld/transforms/loadyuv.cc @@ -0,0 +1,76 @@ +#include "selfdrive/modeld/transforms/loadyuv.h" + +#include +#include +#include + +void loadyuv_init(LoadYUVState* s, cl_context ctx, cl_device_id device_id, int width, int height) { + memset(s, 0, sizeof(*s)); + + s->width = width; + s->height = height; + + char args[1024]; + snprintf(args, sizeof(args), + "-cl-fast-relaxed-math -cl-denorms-are-zero " + "-DTRANSFORMED_WIDTH=%d -DTRANSFORMED_HEIGHT=%d", + width, height); + cl_program prg = cl_program_from_file(ctx, device_id, LOADYUV_PATH, args); + + s->loadys_krnl = CL_CHECK_ERR(clCreateKernel(prg, "loadys", &err)); + s->loaduv_krnl = CL_CHECK_ERR(clCreateKernel(prg, "loaduv", &err)); + s->copy_krnl = CL_CHECK_ERR(clCreateKernel(prg, "copy", &err)); + + // done with this + CL_CHECK(clReleaseProgram(prg)); +} + +void loadyuv_destroy(LoadYUVState* s) { + CL_CHECK(clReleaseKernel(s->loadys_krnl)); + CL_CHECK(clReleaseKernel(s->loaduv_krnl)); + CL_CHECK(clReleaseKernel(s->copy_krnl)); +} + +void loadyuv_queue(LoadYUVState* s, cl_command_queue q, + cl_mem y_cl, cl_mem u_cl, cl_mem v_cl, + cl_mem out_cl) { + cl_int global_out_off = 0; + + CL_CHECK(clSetKernelArg(s->loadys_krnl, 0, sizeof(cl_mem), &y_cl)); + CL_CHECK(clSetKernelArg(s->loadys_krnl, 1, sizeof(cl_mem), &out_cl)); + CL_CHECK(clSetKernelArg(s->loadys_krnl, 2, sizeof(cl_int), &global_out_off)); + + const size_t loadys_work_size = (s->width*s->height)/8; + CL_CHECK(clEnqueueNDRangeKernel(q, s->loadys_krnl, 1, NULL, + &loadys_work_size, NULL, 0, 0, NULL)); + + const size_t loaduv_work_size = ((s->width/2)*(s->height/2))/8; + global_out_off += (s->width*s->height); + + CL_CHECK(clSetKernelArg(s->loaduv_krnl, 0, sizeof(cl_mem), &u_cl)); + CL_CHECK(clSetKernelArg(s->loaduv_krnl, 1, sizeof(cl_mem), &out_cl)); + CL_CHECK(clSetKernelArg(s->loaduv_krnl, 2, sizeof(cl_int), &global_out_off)); + + CL_CHECK(clEnqueueNDRangeKernel(q, s->loaduv_krnl, 1, NULL, + &loaduv_work_size, NULL, 0, 0, NULL)); + + global_out_off += (s->width/2)*(s->height/2); + + CL_CHECK(clSetKernelArg(s->loaduv_krnl, 0, sizeof(cl_mem), &v_cl)); + CL_CHECK(clSetKernelArg(s->loaduv_krnl, 1, sizeof(cl_mem), &out_cl)); + CL_CHECK(clSetKernelArg(s->loaduv_krnl, 2, sizeof(cl_int), &global_out_off)); + + CL_CHECK(clEnqueueNDRangeKernel(q, s->loaduv_krnl, 1, NULL, + &loaduv_work_size, NULL, 0, 0, NULL)); +} + +void copy_queue(LoadYUVState* s, cl_command_queue q, cl_mem src, cl_mem dst, + size_t src_offset, size_t dst_offset, size_t size) { + CL_CHECK(clSetKernelArg(s->copy_krnl, 0, sizeof(cl_mem), &src)); + CL_CHECK(clSetKernelArg(s->copy_krnl, 1, sizeof(cl_mem), &dst)); + CL_CHECK(clSetKernelArg(s->copy_krnl, 2, sizeof(cl_int), &src_offset)); + CL_CHECK(clSetKernelArg(s->copy_krnl, 3, sizeof(cl_int), &dst_offset)); + const size_t copy_work_size = size/8; + CL_CHECK(clEnqueueNDRangeKernel(q, s->copy_krnl, 1, NULL, + ©_work_size, NULL, 0, 0, NULL)); +} \ No newline at end of file diff --git a/selfdrive/modeld/transforms/loadyuv.cl b/selfdrive/modeld/transforms/loadyuv.cl new file mode 100644 index 000000000..970187a6d --- /dev/null +++ b/selfdrive/modeld/transforms/loadyuv.cl @@ -0,0 +1,47 @@ +#define UV_SIZE ((TRANSFORMED_WIDTH/2)*(TRANSFORMED_HEIGHT/2)) + +__kernel void loadys(__global uchar8 const * const Y, + __global uchar * out, + int out_offset) +{ + const int gid = get_global_id(0); + const int ois = gid * 8; + const int oy = ois / TRANSFORMED_WIDTH; + const int ox = ois % TRANSFORMED_WIDTH; + + const uchar8 ys = Y[gid]; + + // 02 + // 13 + + __global uchar* outy0; + __global uchar* outy1; + if ((oy & 1) == 0) { + outy0 = out + out_offset; //y0 + outy1 = out + out_offset + UV_SIZE*2; //y2 + } else { + outy0 = out + out_offset + UV_SIZE; //y1 + outy1 = out + out_offset + UV_SIZE*3; //y3 + } + + vstore4(ys.s0246, 0, outy0 + (oy/2) * (TRANSFORMED_WIDTH/2) + ox/2); + vstore4(ys.s1357, 0, outy1 + (oy/2) * (TRANSFORMED_WIDTH/2) + ox/2); +} + +__kernel void loaduv(__global uchar8 const * const in, + __global uchar8 * out, + int out_offset) +{ + const int gid = get_global_id(0); + const uchar8 inv = in[gid]; + out[gid + out_offset / 8] = inv; +} + +__kernel void copy(__global uchar8 * in, + __global uchar8 * out, + int in_offset, + int out_offset) +{ + const int gid = get_global_id(0); + out[gid + out_offset / 8] = in[gid + in_offset / 8]; +} diff --git a/selfdrive/modeld/transforms/loadyuv.h b/selfdrive/modeld/transforms/loadyuv.h new file mode 100644 index 000000000..659059cd2 --- /dev/null +++ b/selfdrive/modeld/transforms/loadyuv.h @@ -0,0 +1,20 @@ +#pragma once + +#include "common/clutil.h" + +typedef struct { + int width, height; + cl_kernel loadys_krnl, loaduv_krnl, copy_krnl; +} LoadYUVState; + +void loadyuv_init(LoadYUVState* s, cl_context ctx, cl_device_id device_id, int width, int height); + +void loadyuv_destroy(LoadYUVState* s); + +void loadyuv_queue(LoadYUVState* s, cl_command_queue q, + cl_mem y_cl, cl_mem u_cl, cl_mem v_cl, + cl_mem out_cl); + + +void copy_queue(LoadYUVState* s, cl_command_queue q, cl_mem src, cl_mem dst, + size_t src_offset, size_t dst_offset, size_t size); \ No newline at end of file diff --git a/selfdrive/modeld/transforms/transform.cc b/selfdrive/modeld/transforms/transform.cc new file mode 100644 index 000000000..305643cf4 --- /dev/null +++ b/selfdrive/modeld/transforms/transform.cc @@ -0,0 +1,97 @@ +#include "selfdrive/modeld/transforms/transform.h" + +#include +#include + +#include "common/clutil.h" + +void transform_init(Transform* s, cl_context ctx, cl_device_id device_id) { + memset(s, 0, sizeof(*s)); + + cl_program prg = cl_program_from_file(ctx, device_id, TRANSFORM_PATH, ""); + s->krnl = CL_CHECK_ERR(clCreateKernel(prg, "warpPerspective", &err)); + // done with this + CL_CHECK(clReleaseProgram(prg)); + + s->m_y_cl = CL_CHECK_ERR(clCreateBuffer(ctx, CL_MEM_READ_WRITE, 3*3*sizeof(float), NULL, &err)); + s->m_uv_cl = CL_CHECK_ERR(clCreateBuffer(ctx, CL_MEM_READ_WRITE, 3*3*sizeof(float), NULL, &err)); +} + +void transform_destroy(Transform* s) { + CL_CHECK(clReleaseMemObject(s->m_y_cl)); + CL_CHECK(clReleaseMemObject(s->m_uv_cl)); + CL_CHECK(clReleaseKernel(s->krnl)); +} + +void transform_queue(Transform* s, + cl_command_queue q, + cl_mem in_yuv, int in_width, int in_height, int in_stride, int in_uv_offset, + cl_mem out_y, cl_mem out_u, cl_mem out_v, + int out_width, int out_height, + const mat3& projection) { + const int zero = 0; + + // sampled using pixel center origin + // (because that's how fastcv and opencv does it) + + mat3 projection_y = projection; + + // in and out uv is half the size of y. + mat3 projection_uv = transform_scale_buffer(projection, 0.5); + + CL_CHECK(clEnqueueWriteBuffer(q, s->m_y_cl, CL_TRUE, 0, 3*3*sizeof(float), (void*)projection_y.v, 0, NULL, NULL)); + CL_CHECK(clEnqueueWriteBuffer(q, s->m_uv_cl, CL_TRUE, 0, 3*3*sizeof(float), (void*)projection_uv.v, 0, NULL, NULL)); + + const int in_y_width = in_width; + const int in_y_height = in_height; + const int in_y_px_stride = 1; + const int in_uv_width = in_width/2; + const int in_uv_height = in_height/2; + const int in_uv_px_stride = 2; + const int in_u_offset = in_uv_offset; + const int in_v_offset = in_uv_offset + 1; + + const int out_y_width = out_width; + const int out_y_height = out_height; + const int out_uv_width = out_width/2; + const int out_uv_height = out_height/2; + + CL_CHECK(clSetKernelArg(s->krnl, 0, sizeof(cl_mem), &in_yuv)); // src + CL_CHECK(clSetKernelArg(s->krnl, 1, sizeof(cl_int), &in_stride)); // src_row_stride + CL_CHECK(clSetKernelArg(s->krnl, 2, sizeof(cl_int), &in_y_px_stride)); // src_px_stride + CL_CHECK(clSetKernelArg(s->krnl, 3, sizeof(cl_int), &zero)); // src_offset + CL_CHECK(clSetKernelArg(s->krnl, 4, sizeof(cl_int), &in_y_height)); // src_rows + CL_CHECK(clSetKernelArg(s->krnl, 5, sizeof(cl_int), &in_y_width)); // src_cols + CL_CHECK(clSetKernelArg(s->krnl, 6, sizeof(cl_mem), &out_y)); // dst + CL_CHECK(clSetKernelArg(s->krnl, 7, sizeof(cl_int), &out_y_width)); // dst_row_stride + CL_CHECK(clSetKernelArg(s->krnl, 8, sizeof(cl_int), &zero)); // dst_offset + CL_CHECK(clSetKernelArg(s->krnl, 9, sizeof(cl_int), &out_y_height)); // dst_rows + CL_CHECK(clSetKernelArg(s->krnl, 10, sizeof(cl_int), &out_y_width)); // dst_cols + CL_CHECK(clSetKernelArg(s->krnl, 11, sizeof(cl_mem), &s->m_y_cl)); // M + + const size_t work_size_y[2] = {(size_t)out_y_width, (size_t)out_y_height}; + + CL_CHECK(clEnqueueNDRangeKernel(q, s->krnl, 2, NULL, + (const size_t*)&work_size_y, NULL, 0, 0, NULL)); + + const size_t work_size_uv[2] = {(size_t)out_uv_width, (size_t)out_uv_height}; + + CL_CHECK(clSetKernelArg(s->krnl, 2, sizeof(cl_int), &in_uv_px_stride)); // src_px_stride + CL_CHECK(clSetKernelArg(s->krnl, 3, sizeof(cl_int), &in_u_offset)); // src_offset + CL_CHECK(clSetKernelArg(s->krnl, 4, sizeof(cl_int), &in_uv_height)); // src_rows + CL_CHECK(clSetKernelArg(s->krnl, 5, sizeof(cl_int), &in_uv_width)); // src_cols + CL_CHECK(clSetKernelArg(s->krnl, 6, sizeof(cl_mem), &out_u)); // dst + CL_CHECK(clSetKernelArg(s->krnl, 7, sizeof(cl_int), &out_uv_width)); // dst_row_stride + CL_CHECK(clSetKernelArg(s->krnl, 8, sizeof(cl_int), &zero)); // dst_offset + CL_CHECK(clSetKernelArg(s->krnl, 9, sizeof(cl_int), &out_uv_height)); // dst_rows + CL_CHECK(clSetKernelArg(s->krnl, 10, sizeof(cl_int), &out_uv_width)); // dst_cols + CL_CHECK(clSetKernelArg(s->krnl, 11, sizeof(cl_mem), &s->m_uv_cl)); // M + + CL_CHECK(clEnqueueNDRangeKernel(q, s->krnl, 2, NULL, + (const size_t*)&work_size_uv, NULL, 0, 0, NULL)); + CL_CHECK(clSetKernelArg(s->krnl, 3, sizeof(cl_int), &in_v_offset)); // src_ofset + CL_CHECK(clSetKernelArg(s->krnl, 6, sizeof(cl_mem), &out_v)); // dst + + CL_CHECK(clEnqueueNDRangeKernel(q, s->krnl, 2, NULL, + (const size_t*)&work_size_uv, NULL, 0, 0, NULL)); +} diff --git a/selfdrive/modeld/transforms/transform.cl b/selfdrive/modeld/transforms/transform.cl new file mode 100644 index 000000000..2ca25920c --- /dev/null +++ b/selfdrive/modeld/transforms/transform.cl @@ -0,0 +1,54 @@ +#define INTER_BITS 5 +#define INTER_TAB_SIZE (1 << INTER_BITS) +#define INTER_SCALE 1.f / INTER_TAB_SIZE + +#define INTER_REMAP_COEF_BITS 15 +#define INTER_REMAP_COEF_SCALE (1 << INTER_REMAP_COEF_BITS) + +__kernel void warpPerspective(__global const uchar * src, + int src_row_stride, int src_px_stride, int src_offset, int src_rows, int src_cols, + __global uchar * dst, + int dst_row_stride, int dst_offset, int dst_rows, int dst_cols, + __constant float * M) +{ + int dx = get_global_id(0); + int dy = get_global_id(1); + + if (dx < dst_cols && dy < dst_rows) + { + float X0 = M[0] * dx + M[1] * dy + M[2]; + float Y0 = M[3] * dx + M[4] * dy + M[5]; + float W = M[6] * dx + M[7] * dy + M[8]; + W = W != 0.0f ? INTER_TAB_SIZE / W : 0.0f; + int X = rint(X0 * W), Y = rint(Y0 * W); + + int sx = convert_short_sat(X >> INTER_BITS); + int sy = convert_short_sat(Y >> INTER_BITS); + + short sx_clamp = clamp(sx, 0, src_cols - 1); + short sx_p1_clamp = clamp(sx + 1, 0, src_cols - 1); + short sy_clamp = clamp(sy, 0, src_rows - 1); + short sy_p1_clamp = clamp(sy + 1, 0, src_rows - 1); + int v0 = convert_int(src[mad24(sy_clamp, src_row_stride, src_offset + sx_clamp*src_px_stride)]); + int v1 = convert_int(src[mad24(sy_clamp, src_row_stride, src_offset + sx_p1_clamp*src_px_stride)]); + int v2 = convert_int(src[mad24(sy_p1_clamp, src_row_stride, src_offset + sx_clamp*src_px_stride)]); + int v3 = convert_int(src[mad24(sy_p1_clamp, src_row_stride, src_offset + sx_p1_clamp*src_px_stride)]); + + short ay = (short)(Y & (INTER_TAB_SIZE - 1)); + short ax = (short)(X & (INTER_TAB_SIZE - 1)); + float taby = 1.f/INTER_TAB_SIZE*ay; + float tabx = 1.f/INTER_TAB_SIZE*ax; + + int dst_index = mad24(dy, dst_row_stride, dst_offset + dx); + + int itab0 = convert_short_sat_rte( (1.0f-taby)*(1.0f-tabx) * INTER_REMAP_COEF_SCALE ); + int itab1 = convert_short_sat_rte( (1.0f-taby)*tabx * INTER_REMAP_COEF_SCALE ); + int itab2 = convert_short_sat_rte( taby*(1.0f-tabx) * INTER_REMAP_COEF_SCALE ); + int itab3 = convert_short_sat_rte( taby*tabx * INTER_REMAP_COEF_SCALE ); + + int val = v0 * itab0 + v1 * itab1 + v2 * itab2 + v3 * itab3; + + uchar pix = convert_uchar_sat((val + (1 << (INTER_REMAP_COEF_BITS-1))) >> INTER_REMAP_COEF_BITS); + dst[dst_index] = pix; + } +} diff --git a/selfdrive/modeld/transforms/transform.h b/selfdrive/modeld/transforms/transform.h new file mode 100644 index 000000000..771a7054b --- /dev/null +++ b/selfdrive/modeld/transforms/transform.h @@ -0,0 +1,25 @@ +#pragma once + +#define CL_USE_DEPRECATED_OPENCL_1_2_APIS +#ifdef __APPLE__ +#include +#else +#include +#endif + +#include "common/mat.h" + +typedef struct { + cl_kernel krnl; + cl_mem m_y_cl, m_uv_cl; +} Transform; + +void transform_init(Transform* s, cl_context ctx, cl_device_id device_id); + +void transform_destroy(Transform* transform); + +void transform_queue(Transform* s, cl_command_queue q, + cl_mem yuv, int in_width, int in_height, int in_stride, int in_uv_offset, + cl_mem out_y, cl_mem out_u, cl_mem out_v, + int out_width, int out_height, + const mat3& projection); diff --git a/selfdrive/test/process_replay/model_replay.py b/selfdrive/test/process_replay/model_replay.py index c24232840..9ba599bac 100755 --- a/selfdrive/test/process_replay/model_replay.py +++ b/selfdrive/test/process_replay/model_replay.py @@ -34,7 +34,7 @@ GITHUB = GithubUtils(API_TOKEN, DATA_TOKEN) EXEC_TIMINGS = [ # model, instant max, average max - ("modelV2", 0.035, 0.028), + ("modelV2", 0.035, 0.025), ("driverStateV2", 0.02, 0.015), ] diff --git a/selfdrive/test/process_replay/process_replay.py b/selfdrive/test/process_replay/process_replay.py index 5143334bc..8af72e5f4 100755 --- a/selfdrive/test/process_replay/process_replay.py +++ b/selfdrive/test/process_replay/process_replay.py @@ -4,7 +4,6 @@ import time import copy import heapq import signal -import numpy as np from collections import Counter from dataclasses import dataclass, field from itertools import islice @@ -24,7 +23,6 @@ from openpilot.common.params import Params from openpilot.common.prefix import OpenpilotPrefix from openpilot.common.timeout import Timeout from openpilot.common.realtime import DT_CTRL -from openpilot.system.camerad.cameras.nv12_info import get_nv12_info from openpilot.system.manager.process_config import managed_processes from openpilot.selfdrive.test.process_replay.vision_meta import meta_from_camera_state, available_streams from openpilot.selfdrive.test.process_replay.migration import migrate_all @@ -205,8 +203,7 @@ class ProcessContainer: if meta.camera_state in self.cfg.vision_pubs: assert frs[meta.camera_state].pix_fmt == 'nv12' frame_size = (frs[meta.camera_state].w, frs[meta.camera_state].h) - stride, y_height, _, yuv_size = get_nv12_info(frame_size[0], frame_size[1]) - vipc_server.create_buffers_with_sizes(meta.stream, 2, frame_size[0], frame_size[1], yuv_size, stride, stride * y_height) + vipc_server.create_buffers(meta.stream, 2, *frame_size) vipc_server.start_listener() self.vipc_server = vipc_server @@ -303,15 +300,7 @@ class ProcessContainer: camera_meta = meta_from_camera_state(m.which()) assert frs is not None img = frs[m.which()].get(camera_state.frameId) - - h, w = frs[m.which()].h, frs[m.which()].w - stride, y_height, _, yuv_size = get_nv12_info(w, h) - uv_offset = stride * y_height - padded_img = np.zeros((yuv_size), dtype=np.uint8).reshape((-1, stride)) - padded_img[:h, :w] = img[:h * w].reshape((-1, w)) - padded_img[uv_offset // stride:uv_offset // stride + h // 2, :w] = img[h * w:].reshape((-1, w)) - - self.vipc_server.send(camera_meta.stream, padded_img.flatten().tobytes(), + self.vipc_server.send(camera_meta.stream, img.flatten().tobytes(), camera_state.frameId, camera_state.timestampSof, camera_state.timestampEof) self.msg_queue = [] diff --git a/system/camerad/SConscript b/system/camerad/SConscript index c28330b32..e288c6d8b 100644 --- a/system/camerad/SConscript +++ b/system/camerad/SConscript @@ -1,6 +1,6 @@ Import('env', 'arch', 'messaging', 'common', 'visionipc') -libs = [common, messaging, visionipc] +libs = [common, 'OpenCL', messaging, visionipc] if arch != "Darwin": camera_obj = env.Object(['cameras/camera_qcom2.cc', 'cameras/camera_common.cc', 'cameras/spectra.cc', diff --git a/system/camerad/cameras/camera_common.cc b/system/camerad/cameras/camera_common.cc index 329192b63..88bca7f77 100644 --- a/system/camerad/cameras/camera_common.cc +++ b/system/camerad/cameras/camera_common.cc @@ -7,7 +7,7 @@ #include "system/camerad/cameras/spectra.h" -void CameraBuf::init(SpectraCamera *cam, VisionIpcServer * v, int frame_cnt, VisionStreamType type) { +void CameraBuf::init(cl_device_id device_id, cl_context context, SpectraCamera *cam, VisionIpcServer * v, int frame_cnt, VisionStreamType type) { vipc_server = v; stream_type = type; frame_buf_count = frame_cnt; @@ -21,8 +21,9 @@ void CameraBuf::init(SpectraCamera *cam, VisionIpcServer * v, int frame_cnt, Vis const int raw_frame_size = (sensor->frame_height + sensor->extra_height) * sensor->frame_stride; for (int i = 0; i < frame_buf_count; i++) { camera_bufs_raw[i].allocate(raw_frame_size); + camera_bufs_raw[i].init_cl(device_id, context); } - LOGD("allocated %d buffers", frame_buf_count); + LOGD("allocated %d CL buffers", frame_buf_count); } vipc_server->create_buffers_with_sizes(stream_type, VIPC_BUFFER_COUNT, out_img_width, out_img_height, cam->yuv_size, cam->stride, cam->uv_offset); diff --git a/system/camerad/cameras/camera_common.h b/system/camerad/cameras/camera_common.h index 7f35e06a8..c26859cbc 100644 --- a/system/camerad/cameras/camera_common.h +++ b/system/camerad/cameras/camera_common.h @@ -36,7 +36,7 @@ public: CameraBuf() = default; ~CameraBuf(); - void init(SpectraCamera *cam, VisionIpcServer * v, int frame_cnt, VisionStreamType type); + void init(cl_device_id device_id, cl_context context, SpectraCamera *cam, VisionIpcServer * v, int frame_cnt, VisionStreamType type); void sendFrameToVipc(); }; diff --git a/system/camerad/cameras/camera_qcom2.cc b/system/camerad/cameras/camera_qcom2.cc index 6a7f599ab..d741e13cf 100644 --- a/system/camerad/cameras/camera_qcom2.cc +++ b/system/camerad/cameras/camera_qcom2.cc @@ -12,8 +12,16 @@ #include #include +#ifdef __TICI__ +#include "CL/cl_ext_qcom.h" +#else +#define CL_PRIORITY_HINT_HIGH_QCOM NULL +#define CL_CONTEXT_PRIORITY_HINT_QCOM NULL +#endif + #include "media/cam_sensor_cmn_header.h" +#include "common/clutil.h" #include "common/params.h" #include "common/swaglog.h" @@ -49,7 +57,7 @@ public: CameraState(SpectraMaster *master, const CameraConfig &config) : camera(master, config) {}; ~CameraState(); - void init(VisionIpcServer *v); + void init(VisionIpcServer *v, cl_device_id device_id, cl_context ctx); void update_exposure_score(float desired_ev, int exp_t, int exp_g_idx, float exp_gain); void set_camera_exposure(float grey_frac); void set_exposure_rect(); @@ -60,8 +68,8 @@ public: } }; -void CameraState::init(VisionIpcServer *v) { - camera.camera_open(v); +void CameraState::init(VisionIpcServer *v, cl_device_id device_id, cl_context ctx) { + camera.camera_open(v, device_id, ctx); if (!camera.enabled) return; @@ -249,7 +257,11 @@ void CameraState::sendState() { void camerad_thread() { // TODO: centralize enabled handling - VisionIpcServer v("camerad"); + cl_device_id device_id = cl_get_device_id(CL_DEVICE_TYPE_DEFAULT); + const cl_context_properties props[] = {CL_CONTEXT_PRIORITY_HINT_QCOM, CL_PRIORITY_HINT_HIGH_QCOM, 0}; + cl_context ctx = CL_CHECK_ERR(clCreateContext(props, 1, &device_id, NULL, NULL, &err)); + + VisionIpcServer v("camerad", device_id, ctx); // *** initial ISP init *** SpectraMaster m; @@ -259,7 +271,7 @@ void camerad_thread() { std::vector> cams; for (const auto &config : ALL_CAMERA_CONFIGS) { auto cam = std::make_unique(&m, config); - cam->init(&v); + cam->init(&v, device_id, ctx); cams.emplace_back(std::move(cam)); } diff --git a/system/camerad/cameras/spectra.cc b/system/camerad/cameras/spectra.cc index 73e0a78da..5c3e7a9d2 100644 --- a/system/camerad/cameras/spectra.cc +++ b/system/camerad/cameras/spectra.cc @@ -274,7 +274,7 @@ int SpectraCamera::clear_req_queue() { return ret; } -void SpectraCamera::camera_open(VisionIpcServer *v) { +void SpectraCamera::camera_open(VisionIpcServer *v, cl_device_id device_id, cl_context ctx) { if (!openSensor()) { return; } @@ -296,7 +296,7 @@ void SpectraCamera::camera_open(VisionIpcServer *v) { linkDevices(); LOGD("camera init %d", cc.camera_num); - buf.init(this, v, ife_buf_depth, cc.stream_type); + buf.init(device_id, ctx, this, v, ife_buf_depth, cc.stream_type); camera_map_bufs(); clearAndRequeue(1); } diff --git a/system/camerad/cameras/spectra.h b/system/camerad/cameras/spectra.h index a02b8a6ca..13cb13f98 100644 --- a/system/camerad/cameras/spectra.h +++ b/system/camerad/cameras/spectra.h @@ -113,7 +113,7 @@ public: SpectraCamera(SpectraMaster *master, const CameraConfig &config); ~SpectraCamera(); - void camera_open(VisionIpcServer *v); + void camera_open(VisionIpcServer *v, cl_device_id device_id, cl_context ctx); bool handle_camera_event(const cam_req_mgr_message *event_data); void camera_close(); void camera_map_bufs(); diff --git a/system/loggerd/SConscript b/system/loggerd/SConscript index cc8ef7c88..cf169f4dc 100644 --- a/system/loggerd/SConscript +++ b/system/loggerd/SConscript @@ -2,13 +2,16 @@ Import('env', 'arch', 'messaging', 'common', 'visionipc') libs = [common, messaging, visionipc, 'avformat', 'avcodec', 'avutil', - 'yuv', 'pthread', 'zstd'] + 'yuv', 'OpenCL', 'pthread', 'zstd'] src = ['logger.cc', 'zstd_writer.cc', 'video_writer.cc', 'encoder/encoder.cc', 'encoder/v4l_encoder.cc', 'encoder/jpeg_encoder.cc'] if arch != "larch64": src += ['encoder/ffmpeg_encoder.cc'] if arch == "Darwin": + # fix OpenCL + del libs[libs.index('OpenCL')] + env['FRAMEWORKS'] = ['OpenCL'] # exclude v4l del src[src.index('encoder/v4l_encoder.cc')] diff --git a/third_party/opencl/include/CL/cl.h b/third_party/opencl/include/CL/cl.h new file mode 100644 index 000000000..0086319f5 --- /dev/null +++ b/third_party/opencl/include/CL/cl.h @@ -0,0 +1,1452 @@ +/******************************************************************************* + * Copyright (c) 2008-2015 The Khronos Group Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and/or associated documentation files (the + * "Materials"), to deal in the Materials without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Materials, and to + * permit persons to whom the Materials are furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Materials. + * + * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS + * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS + * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT + * https://www.khronos.org/registry/ + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. + ******************************************************************************/ + +#ifndef __OPENCL_CL_H +#define __OPENCL_CL_H + +#ifdef __APPLE__ +#include +#else +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/******************************************************************************/ + +typedef struct _cl_platform_id * cl_platform_id; +typedef struct _cl_device_id * cl_device_id; +typedef struct _cl_context * cl_context; +typedef struct _cl_command_queue * cl_command_queue; +typedef struct _cl_mem * cl_mem; +typedef struct _cl_program * cl_program; +typedef struct _cl_kernel * cl_kernel; +typedef struct _cl_event * cl_event; +typedef struct _cl_sampler * cl_sampler; + +typedef cl_uint cl_bool; /* WARNING! Unlike cl_ types in cl_platform.h, cl_bool is not guaranteed to be the same size as the bool in kernels. */ +typedef cl_ulong cl_bitfield; +typedef cl_bitfield cl_device_type; +typedef cl_uint cl_platform_info; +typedef cl_uint cl_device_info; +typedef cl_bitfield cl_device_fp_config; +typedef cl_uint cl_device_mem_cache_type; +typedef cl_uint cl_device_local_mem_type; +typedef cl_bitfield cl_device_exec_capabilities; +typedef cl_bitfield cl_device_svm_capabilities; +typedef cl_bitfield cl_command_queue_properties; +typedef intptr_t cl_device_partition_property; +typedef cl_bitfield cl_device_affinity_domain; + +typedef intptr_t cl_context_properties; +typedef cl_uint cl_context_info; +typedef cl_bitfield cl_queue_properties; +typedef cl_uint cl_command_queue_info; +typedef cl_uint cl_channel_order; +typedef cl_uint cl_channel_type; +typedef cl_bitfield cl_mem_flags; +typedef cl_bitfield cl_svm_mem_flags; +typedef cl_uint cl_mem_object_type; +typedef cl_uint cl_mem_info; +typedef cl_bitfield cl_mem_migration_flags; +typedef cl_uint cl_image_info; +typedef cl_uint cl_buffer_create_type; +typedef cl_uint cl_addressing_mode; +typedef cl_uint cl_filter_mode; +typedef cl_uint cl_sampler_info; +typedef cl_bitfield cl_map_flags; +typedef intptr_t cl_pipe_properties; +typedef cl_uint cl_pipe_info; +typedef cl_uint cl_program_info; +typedef cl_uint cl_program_build_info; +typedef cl_uint cl_program_binary_type; +typedef cl_int cl_build_status; +typedef cl_uint cl_kernel_info; +typedef cl_uint cl_kernel_arg_info; +typedef cl_uint cl_kernel_arg_address_qualifier; +typedef cl_uint cl_kernel_arg_access_qualifier; +typedef cl_bitfield cl_kernel_arg_type_qualifier; +typedef cl_uint cl_kernel_work_group_info; +typedef cl_uint cl_kernel_sub_group_info; +typedef cl_uint cl_event_info; +typedef cl_uint cl_command_type; +typedef cl_uint cl_profiling_info; +typedef cl_bitfield cl_sampler_properties; +typedef cl_uint cl_kernel_exec_info; + +typedef struct _cl_image_format { + cl_channel_order image_channel_order; + cl_channel_type image_channel_data_type; +} cl_image_format; + +typedef struct _cl_image_desc { + cl_mem_object_type image_type; + size_t image_width; + size_t image_height; + size_t image_depth; + size_t image_array_size; + size_t image_row_pitch; + size_t image_slice_pitch; + cl_uint num_mip_levels; + cl_uint num_samples; +#ifdef __GNUC__ + __extension__ /* Prevents warnings about anonymous union in -pedantic builds */ +#endif + union { + cl_mem buffer; + cl_mem mem_object; + }; +} cl_image_desc; + +typedef struct _cl_buffer_region { + size_t origin; + size_t size; +} cl_buffer_region; + + +/******************************************************************************/ + +/* Error Codes */ +#define CL_SUCCESS 0 +#define CL_DEVICE_NOT_FOUND -1 +#define CL_DEVICE_NOT_AVAILABLE -2 +#define CL_COMPILER_NOT_AVAILABLE -3 +#define CL_MEM_OBJECT_ALLOCATION_FAILURE -4 +#define CL_OUT_OF_RESOURCES -5 +#define CL_OUT_OF_HOST_MEMORY -6 +#define CL_PROFILING_INFO_NOT_AVAILABLE -7 +#define CL_MEM_COPY_OVERLAP -8 +#define CL_IMAGE_FORMAT_MISMATCH -9 +#define CL_IMAGE_FORMAT_NOT_SUPPORTED -10 +#define CL_BUILD_PROGRAM_FAILURE -11 +#define CL_MAP_FAILURE -12 +#define CL_MISALIGNED_SUB_BUFFER_OFFSET -13 +#define CL_EXEC_STATUS_ERROR_FOR_EVENTS_IN_WAIT_LIST -14 +#define CL_COMPILE_PROGRAM_FAILURE -15 +#define CL_LINKER_NOT_AVAILABLE -16 +#define CL_LINK_PROGRAM_FAILURE -17 +#define CL_DEVICE_PARTITION_FAILED -18 +#define CL_KERNEL_ARG_INFO_NOT_AVAILABLE -19 + +#define CL_INVALID_VALUE -30 +#define CL_INVALID_DEVICE_TYPE -31 +#define CL_INVALID_PLATFORM -32 +#define CL_INVALID_DEVICE -33 +#define CL_INVALID_CONTEXT -34 +#define CL_INVALID_QUEUE_PROPERTIES -35 +#define CL_INVALID_COMMAND_QUEUE -36 +#define CL_INVALID_HOST_PTR -37 +#define CL_INVALID_MEM_OBJECT -38 +#define CL_INVALID_IMAGE_FORMAT_DESCRIPTOR -39 +#define CL_INVALID_IMAGE_SIZE -40 +#define CL_INVALID_SAMPLER -41 +#define CL_INVALID_BINARY -42 +#define CL_INVALID_BUILD_OPTIONS -43 +#define CL_INVALID_PROGRAM -44 +#define CL_INVALID_PROGRAM_EXECUTABLE -45 +#define CL_INVALID_KERNEL_NAME -46 +#define CL_INVALID_KERNEL_DEFINITION -47 +#define CL_INVALID_KERNEL -48 +#define CL_INVALID_ARG_INDEX -49 +#define CL_INVALID_ARG_VALUE -50 +#define CL_INVALID_ARG_SIZE -51 +#define CL_INVALID_KERNEL_ARGS -52 +#define CL_INVALID_WORK_DIMENSION -53 +#define CL_INVALID_WORK_GROUP_SIZE -54 +#define CL_INVALID_WORK_ITEM_SIZE -55 +#define CL_INVALID_GLOBAL_OFFSET -56 +#define CL_INVALID_EVENT_WAIT_LIST -57 +#define CL_INVALID_EVENT -58 +#define CL_INVALID_OPERATION -59 +#define CL_INVALID_GL_OBJECT -60 +#define CL_INVALID_BUFFER_SIZE -61 +#define CL_INVALID_MIP_LEVEL -62 +#define CL_INVALID_GLOBAL_WORK_SIZE -63 +#define CL_INVALID_PROPERTY -64 +#define CL_INVALID_IMAGE_DESCRIPTOR -65 +#define CL_INVALID_COMPILER_OPTIONS -66 +#define CL_INVALID_LINKER_OPTIONS -67 +#define CL_INVALID_DEVICE_PARTITION_COUNT -68 +#define CL_INVALID_PIPE_SIZE -69 +#define CL_INVALID_DEVICE_QUEUE -70 + +/* OpenCL Version */ +#define CL_VERSION_1_0 1 +#define CL_VERSION_1_1 1 +#define CL_VERSION_1_2 1 +#define CL_VERSION_2_0 1 +#define CL_VERSION_2_1 1 + +/* cl_bool */ +#define CL_FALSE 0 +#define CL_TRUE 1 +#define CL_BLOCKING CL_TRUE +#define CL_NON_BLOCKING CL_FALSE + +/* cl_platform_info */ +#define CL_PLATFORM_PROFILE 0x0900 +#define CL_PLATFORM_VERSION 0x0901 +#define CL_PLATFORM_NAME 0x0902 +#define CL_PLATFORM_VENDOR 0x0903 +#define CL_PLATFORM_EXTENSIONS 0x0904 +#define CL_PLATFORM_HOST_TIMER_RESOLUTION 0x0905 + +/* cl_device_type - bitfield */ +#define CL_DEVICE_TYPE_DEFAULT (1 << 0) +#define CL_DEVICE_TYPE_CPU (1 << 1) +#define CL_DEVICE_TYPE_GPU (1 << 2) +#define CL_DEVICE_TYPE_ACCELERATOR (1 << 3) +#define CL_DEVICE_TYPE_CUSTOM (1 << 4) +#define CL_DEVICE_TYPE_ALL 0xFFFFFFFF + +/* cl_device_info */ +#define CL_DEVICE_TYPE 0x1000 +#define CL_DEVICE_VENDOR_ID 0x1001 +#define CL_DEVICE_MAX_COMPUTE_UNITS 0x1002 +#define CL_DEVICE_MAX_WORK_ITEM_DIMENSIONS 0x1003 +#define CL_DEVICE_MAX_WORK_GROUP_SIZE 0x1004 +#define CL_DEVICE_MAX_WORK_ITEM_SIZES 0x1005 +#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_CHAR 0x1006 +#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_SHORT 0x1007 +#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_INT 0x1008 +#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_LONG 0x1009 +#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_FLOAT 0x100A +#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_DOUBLE 0x100B +#define CL_DEVICE_MAX_CLOCK_FREQUENCY 0x100C +#define CL_DEVICE_ADDRESS_BITS 0x100D +#define CL_DEVICE_MAX_READ_IMAGE_ARGS 0x100E +#define CL_DEVICE_MAX_WRITE_IMAGE_ARGS 0x100F +#define CL_DEVICE_MAX_MEM_ALLOC_SIZE 0x1010 +#define CL_DEVICE_IMAGE2D_MAX_WIDTH 0x1011 +#define CL_DEVICE_IMAGE2D_MAX_HEIGHT 0x1012 +#define CL_DEVICE_IMAGE3D_MAX_WIDTH 0x1013 +#define CL_DEVICE_IMAGE3D_MAX_HEIGHT 0x1014 +#define CL_DEVICE_IMAGE3D_MAX_DEPTH 0x1015 +#define CL_DEVICE_IMAGE_SUPPORT 0x1016 +#define CL_DEVICE_MAX_PARAMETER_SIZE 0x1017 +#define CL_DEVICE_MAX_SAMPLERS 0x1018 +#define CL_DEVICE_MEM_BASE_ADDR_ALIGN 0x1019 +#define CL_DEVICE_MIN_DATA_TYPE_ALIGN_SIZE 0x101A +#define CL_DEVICE_SINGLE_FP_CONFIG 0x101B +#define CL_DEVICE_GLOBAL_MEM_CACHE_TYPE 0x101C +#define CL_DEVICE_GLOBAL_MEM_CACHELINE_SIZE 0x101D +#define CL_DEVICE_GLOBAL_MEM_CACHE_SIZE 0x101E +#define CL_DEVICE_GLOBAL_MEM_SIZE 0x101F +#define CL_DEVICE_MAX_CONSTANT_BUFFER_SIZE 0x1020 +#define CL_DEVICE_MAX_CONSTANT_ARGS 0x1021 +#define CL_DEVICE_LOCAL_MEM_TYPE 0x1022 +#define CL_DEVICE_LOCAL_MEM_SIZE 0x1023 +#define CL_DEVICE_ERROR_CORRECTION_SUPPORT 0x1024 +#define CL_DEVICE_PROFILING_TIMER_RESOLUTION 0x1025 +#define CL_DEVICE_ENDIAN_LITTLE 0x1026 +#define CL_DEVICE_AVAILABLE 0x1027 +#define CL_DEVICE_COMPILER_AVAILABLE 0x1028 +#define CL_DEVICE_EXECUTION_CAPABILITIES 0x1029 +#define CL_DEVICE_QUEUE_PROPERTIES 0x102A /* deprecated */ +#define CL_DEVICE_QUEUE_ON_HOST_PROPERTIES 0x102A +#define CL_DEVICE_NAME 0x102B +#define CL_DEVICE_VENDOR 0x102C +#define CL_DRIVER_VERSION 0x102D +#define CL_DEVICE_PROFILE 0x102E +#define CL_DEVICE_VERSION 0x102F +#define CL_DEVICE_EXTENSIONS 0x1030 +#define CL_DEVICE_PLATFORM 0x1031 +#define CL_DEVICE_DOUBLE_FP_CONFIG 0x1032 +/* 0x1033 reserved for CL_DEVICE_HALF_FP_CONFIG */ +#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_HALF 0x1034 +#define CL_DEVICE_HOST_UNIFIED_MEMORY 0x1035 /* deprecated */ +#define CL_DEVICE_NATIVE_VECTOR_WIDTH_CHAR 0x1036 +#define CL_DEVICE_NATIVE_VECTOR_WIDTH_SHORT 0x1037 +#define CL_DEVICE_NATIVE_VECTOR_WIDTH_INT 0x1038 +#define CL_DEVICE_NATIVE_VECTOR_WIDTH_LONG 0x1039 +#define CL_DEVICE_NATIVE_VECTOR_WIDTH_FLOAT 0x103A +#define CL_DEVICE_NATIVE_VECTOR_WIDTH_DOUBLE 0x103B +#define CL_DEVICE_NATIVE_VECTOR_WIDTH_HALF 0x103C +#define CL_DEVICE_OPENCL_C_VERSION 0x103D +#define CL_DEVICE_LINKER_AVAILABLE 0x103E +#define CL_DEVICE_BUILT_IN_KERNELS 0x103F +#define CL_DEVICE_IMAGE_MAX_BUFFER_SIZE 0x1040 +#define CL_DEVICE_IMAGE_MAX_ARRAY_SIZE 0x1041 +#define CL_DEVICE_PARENT_DEVICE 0x1042 +#define CL_DEVICE_PARTITION_MAX_SUB_DEVICES 0x1043 +#define CL_DEVICE_PARTITION_PROPERTIES 0x1044 +#define CL_DEVICE_PARTITION_AFFINITY_DOMAIN 0x1045 +#define CL_DEVICE_PARTITION_TYPE 0x1046 +#define CL_DEVICE_REFERENCE_COUNT 0x1047 +#define CL_DEVICE_PREFERRED_INTEROP_USER_SYNC 0x1048 +#define CL_DEVICE_PRINTF_BUFFER_SIZE 0x1049 +#define CL_DEVICE_IMAGE_PITCH_ALIGNMENT 0x104A +#define CL_DEVICE_IMAGE_BASE_ADDRESS_ALIGNMENT 0x104B +#define CL_DEVICE_MAX_READ_WRITE_IMAGE_ARGS 0x104C +#define CL_DEVICE_MAX_GLOBAL_VARIABLE_SIZE 0x104D +#define CL_DEVICE_QUEUE_ON_DEVICE_PROPERTIES 0x104E +#define CL_DEVICE_QUEUE_ON_DEVICE_PREFERRED_SIZE 0x104F +#define CL_DEVICE_QUEUE_ON_DEVICE_MAX_SIZE 0x1050 +#define CL_DEVICE_MAX_ON_DEVICE_QUEUES 0x1051 +#define CL_DEVICE_MAX_ON_DEVICE_EVENTS 0x1052 +#define CL_DEVICE_SVM_CAPABILITIES 0x1053 +#define CL_DEVICE_GLOBAL_VARIABLE_PREFERRED_TOTAL_SIZE 0x1054 +#define CL_DEVICE_MAX_PIPE_ARGS 0x1055 +#define CL_DEVICE_PIPE_MAX_ACTIVE_RESERVATIONS 0x1056 +#define CL_DEVICE_PIPE_MAX_PACKET_SIZE 0x1057 +#define CL_DEVICE_PREFERRED_PLATFORM_ATOMIC_ALIGNMENT 0x1058 +#define CL_DEVICE_PREFERRED_GLOBAL_ATOMIC_ALIGNMENT 0x1059 +#define CL_DEVICE_PREFERRED_LOCAL_ATOMIC_ALIGNMENT 0x105A +#define CL_DEVICE_IL_VERSION 0x105B +#define CL_DEVICE_MAX_NUM_SUB_GROUPS 0x105C +#define CL_DEVICE_SUB_GROUP_INDEPENDENT_FORWARD_PROGRESS 0x105D + +/* cl_device_fp_config - bitfield */ +#define CL_FP_DENORM (1 << 0) +#define CL_FP_INF_NAN (1 << 1) +#define CL_FP_ROUND_TO_NEAREST (1 << 2) +#define CL_FP_ROUND_TO_ZERO (1 << 3) +#define CL_FP_ROUND_TO_INF (1 << 4) +#define CL_FP_FMA (1 << 5) +#define CL_FP_SOFT_FLOAT (1 << 6) +#define CL_FP_CORRECTLY_ROUNDED_DIVIDE_SQRT (1 << 7) + +/* cl_device_mem_cache_type */ +#define CL_NONE 0x0 +#define CL_READ_ONLY_CACHE 0x1 +#define CL_READ_WRITE_CACHE 0x2 + +/* cl_device_local_mem_type */ +#define CL_LOCAL 0x1 +#define CL_GLOBAL 0x2 + +/* cl_device_exec_capabilities - bitfield */ +#define CL_EXEC_KERNEL (1 << 0) +#define CL_EXEC_NATIVE_KERNEL (1 << 1) + +/* cl_command_queue_properties - bitfield */ +#define CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE (1 << 0) +#define CL_QUEUE_PROFILING_ENABLE (1 << 1) +#define CL_QUEUE_ON_DEVICE (1 << 2) +#define CL_QUEUE_ON_DEVICE_DEFAULT (1 << 3) + +/* cl_context_info */ +#define CL_CONTEXT_REFERENCE_COUNT 0x1080 +#define CL_CONTEXT_DEVICES 0x1081 +#define CL_CONTEXT_PROPERTIES 0x1082 +#define CL_CONTEXT_NUM_DEVICES 0x1083 + +/* cl_context_properties */ +#define CL_CONTEXT_PLATFORM 0x1084 +#define CL_CONTEXT_INTEROP_USER_SYNC 0x1085 + +/* cl_device_partition_property */ +#define CL_DEVICE_PARTITION_EQUALLY 0x1086 +#define CL_DEVICE_PARTITION_BY_COUNTS 0x1087 +#define CL_DEVICE_PARTITION_BY_COUNTS_LIST_END 0x0 +#define CL_DEVICE_PARTITION_BY_AFFINITY_DOMAIN 0x1088 + +/* cl_device_affinity_domain */ +#define CL_DEVICE_AFFINITY_DOMAIN_NUMA (1 << 0) +#define CL_DEVICE_AFFINITY_DOMAIN_L4_CACHE (1 << 1) +#define CL_DEVICE_AFFINITY_DOMAIN_L3_CACHE (1 << 2) +#define CL_DEVICE_AFFINITY_DOMAIN_L2_CACHE (1 << 3) +#define CL_DEVICE_AFFINITY_DOMAIN_L1_CACHE (1 << 4) +#define CL_DEVICE_AFFINITY_DOMAIN_NEXT_PARTITIONABLE (1 << 5) + +/* cl_device_svm_capabilities */ +#define CL_DEVICE_SVM_COARSE_GRAIN_BUFFER (1 << 0) +#define CL_DEVICE_SVM_FINE_GRAIN_BUFFER (1 << 1) +#define CL_DEVICE_SVM_FINE_GRAIN_SYSTEM (1 << 2) +#define CL_DEVICE_SVM_ATOMICS (1 << 3) + +/* cl_command_queue_info */ +#define CL_QUEUE_CONTEXT 0x1090 +#define CL_QUEUE_DEVICE 0x1091 +#define CL_QUEUE_REFERENCE_COUNT 0x1092 +#define CL_QUEUE_PROPERTIES 0x1093 +#define CL_QUEUE_SIZE 0x1094 +#define CL_QUEUE_DEVICE_DEFAULT 0x1095 + +/* cl_mem_flags and cl_svm_mem_flags - bitfield */ +#define CL_MEM_READ_WRITE (1 << 0) +#define CL_MEM_WRITE_ONLY (1 << 1) +#define CL_MEM_READ_ONLY (1 << 2) +#define CL_MEM_USE_HOST_PTR (1 << 3) +#define CL_MEM_ALLOC_HOST_PTR (1 << 4) +#define CL_MEM_COPY_HOST_PTR (1 << 5) +/* reserved (1 << 6) */ +#define CL_MEM_HOST_WRITE_ONLY (1 << 7) +#define CL_MEM_HOST_READ_ONLY (1 << 8) +#define CL_MEM_HOST_NO_ACCESS (1 << 9) +#define CL_MEM_SVM_FINE_GRAIN_BUFFER (1 << 10) /* used by cl_svm_mem_flags only */ +#define CL_MEM_SVM_ATOMICS (1 << 11) /* used by cl_svm_mem_flags only */ +#define CL_MEM_KERNEL_READ_AND_WRITE (1 << 12) + +/* cl_mem_migration_flags - bitfield */ +#define CL_MIGRATE_MEM_OBJECT_HOST (1 << 0) +#define CL_MIGRATE_MEM_OBJECT_CONTENT_UNDEFINED (1 << 1) + +/* cl_channel_order */ +#define CL_R 0x10B0 +#define CL_A 0x10B1 +#define CL_RG 0x10B2 +#define CL_RA 0x10B3 +#define CL_RGB 0x10B4 +#define CL_RGBA 0x10B5 +#define CL_BGRA 0x10B6 +#define CL_ARGB 0x10B7 +#define CL_INTENSITY 0x10B8 +#define CL_LUMINANCE 0x10B9 +#define CL_Rx 0x10BA +#define CL_RGx 0x10BB +#define CL_RGBx 0x10BC +#define CL_DEPTH 0x10BD +#define CL_DEPTH_STENCIL 0x10BE +#define CL_sRGB 0x10BF +#define CL_sRGBx 0x10C0 +#define CL_sRGBA 0x10C1 +#define CL_sBGRA 0x10C2 +#define CL_ABGR 0x10C3 + +/* cl_channel_type */ +#define CL_SNORM_INT8 0x10D0 +#define CL_SNORM_INT16 0x10D1 +#define CL_UNORM_INT8 0x10D2 +#define CL_UNORM_INT16 0x10D3 +#define CL_UNORM_SHORT_565 0x10D4 +#define CL_UNORM_SHORT_555 0x10D5 +#define CL_UNORM_INT_101010 0x10D6 +#define CL_SIGNED_INT8 0x10D7 +#define CL_SIGNED_INT16 0x10D8 +#define CL_SIGNED_INT32 0x10D9 +#define CL_UNSIGNED_INT8 0x10DA +#define CL_UNSIGNED_INT16 0x10DB +#define CL_UNSIGNED_INT32 0x10DC +#define CL_HALF_FLOAT 0x10DD +#define CL_FLOAT 0x10DE +#define CL_UNORM_INT24 0x10DF +#define CL_UNORM_INT_101010_2 0x10E0 + +/* cl_mem_object_type */ +#define CL_MEM_OBJECT_BUFFER 0x10F0 +#define CL_MEM_OBJECT_IMAGE2D 0x10F1 +#define CL_MEM_OBJECT_IMAGE3D 0x10F2 +#define CL_MEM_OBJECT_IMAGE2D_ARRAY 0x10F3 +#define CL_MEM_OBJECT_IMAGE1D 0x10F4 +#define CL_MEM_OBJECT_IMAGE1D_ARRAY 0x10F5 +#define CL_MEM_OBJECT_IMAGE1D_BUFFER 0x10F6 +#define CL_MEM_OBJECT_PIPE 0x10F7 + +/* cl_mem_info */ +#define CL_MEM_TYPE 0x1100 +#define CL_MEM_FLAGS 0x1101 +#define CL_MEM_SIZE 0x1102 +#define CL_MEM_HOST_PTR 0x1103 +#define CL_MEM_MAP_COUNT 0x1104 +#define CL_MEM_REFERENCE_COUNT 0x1105 +#define CL_MEM_CONTEXT 0x1106 +#define CL_MEM_ASSOCIATED_MEMOBJECT 0x1107 +#define CL_MEM_OFFSET 0x1108 +#define CL_MEM_USES_SVM_POINTER 0x1109 + +/* cl_image_info */ +#define CL_IMAGE_FORMAT 0x1110 +#define CL_IMAGE_ELEMENT_SIZE 0x1111 +#define CL_IMAGE_ROW_PITCH 0x1112 +#define CL_IMAGE_SLICE_PITCH 0x1113 +#define CL_IMAGE_WIDTH 0x1114 +#define CL_IMAGE_HEIGHT 0x1115 +#define CL_IMAGE_DEPTH 0x1116 +#define CL_IMAGE_ARRAY_SIZE 0x1117 +#define CL_IMAGE_BUFFER 0x1118 +#define CL_IMAGE_NUM_MIP_LEVELS 0x1119 +#define CL_IMAGE_NUM_SAMPLES 0x111A + +/* cl_pipe_info */ +#define CL_PIPE_PACKET_SIZE 0x1120 +#define CL_PIPE_MAX_PACKETS 0x1121 + +/* cl_addressing_mode */ +#define CL_ADDRESS_NONE 0x1130 +#define CL_ADDRESS_CLAMP_TO_EDGE 0x1131 +#define CL_ADDRESS_CLAMP 0x1132 +#define CL_ADDRESS_REPEAT 0x1133 +#define CL_ADDRESS_MIRRORED_REPEAT 0x1134 + +/* cl_filter_mode */ +#define CL_FILTER_NEAREST 0x1140 +#define CL_FILTER_LINEAR 0x1141 + +/* cl_sampler_info */ +#define CL_SAMPLER_REFERENCE_COUNT 0x1150 +#define CL_SAMPLER_CONTEXT 0x1151 +#define CL_SAMPLER_NORMALIZED_COORDS 0x1152 +#define CL_SAMPLER_ADDRESSING_MODE 0x1153 +#define CL_SAMPLER_FILTER_MODE 0x1154 +#define CL_SAMPLER_MIP_FILTER_MODE 0x1155 +#define CL_SAMPLER_LOD_MIN 0x1156 +#define CL_SAMPLER_LOD_MAX 0x1157 + +/* cl_map_flags - bitfield */ +#define CL_MAP_READ (1 << 0) +#define CL_MAP_WRITE (1 << 1) +#define CL_MAP_WRITE_INVALIDATE_REGION (1 << 2) + +/* cl_program_info */ +#define CL_PROGRAM_REFERENCE_COUNT 0x1160 +#define CL_PROGRAM_CONTEXT 0x1161 +#define CL_PROGRAM_NUM_DEVICES 0x1162 +#define CL_PROGRAM_DEVICES 0x1163 +#define CL_PROGRAM_SOURCE 0x1164 +#define CL_PROGRAM_BINARY_SIZES 0x1165 +#define CL_PROGRAM_BINARIES 0x1166 +#define CL_PROGRAM_NUM_KERNELS 0x1167 +#define CL_PROGRAM_KERNEL_NAMES 0x1168 +#define CL_PROGRAM_IL 0x1169 + +/* cl_program_build_info */ +#define CL_PROGRAM_BUILD_STATUS 0x1181 +#define CL_PROGRAM_BUILD_OPTIONS 0x1182 +#define CL_PROGRAM_BUILD_LOG 0x1183 +#define CL_PROGRAM_BINARY_TYPE 0x1184 +#define CL_PROGRAM_BUILD_GLOBAL_VARIABLE_TOTAL_SIZE 0x1185 + +/* cl_program_binary_type */ +#define CL_PROGRAM_BINARY_TYPE_NONE 0x0 +#define CL_PROGRAM_BINARY_TYPE_COMPILED_OBJECT 0x1 +#define CL_PROGRAM_BINARY_TYPE_LIBRARY 0x2 +#define CL_PROGRAM_BINARY_TYPE_EXECUTABLE 0x4 + +/* cl_build_status */ +#define CL_BUILD_SUCCESS 0 +#define CL_BUILD_NONE -1 +#define CL_BUILD_ERROR -2 +#define CL_BUILD_IN_PROGRESS -3 + +/* cl_kernel_info */ +#define CL_KERNEL_FUNCTION_NAME 0x1190 +#define CL_KERNEL_NUM_ARGS 0x1191 +#define CL_KERNEL_REFERENCE_COUNT 0x1192 +#define CL_KERNEL_CONTEXT 0x1193 +#define CL_KERNEL_PROGRAM 0x1194 +#define CL_KERNEL_ATTRIBUTES 0x1195 +#define CL_KERNEL_MAX_NUM_SUB_GROUPS 0x11B9 +#define CL_KERNEL_COMPILE_NUM_SUB_GROUPS 0x11BA + +/* cl_kernel_arg_info */ +#define CL_KERNEL_ARG_ADDRESS_QUALIFIER 0x1196 +#define CL_KERNEL_ARG_ACCESS_QUALIFIER 0x1197 +#define CL_KERNEL_ARG_TYPE_NAME 0x1198 +#define CL_KERNEL_ARG_TYPE_QUALIFIER 0x1199 +#define CL_KERNEL_ARG_NAME 0x119A + +/* cl_kernel_arg_address_qualifier */ +#define CL_KERNEL_ARG_ADDRESS_GLOBAL 0x119B +#define CL_KERNEL_ARG_ADDRESS_LOCAL 0x119C +#define CL_KERNEL_ARG_ADDRESS_CONSTANT 0x119D +#define CL_KERNEL_ARG_ADDRESS_PRIVATE 0x119E + +/* cl_kernel_arg_access_qualifier */ +#define CL_KERNEL_ARG_ACCESS_READ_ONLY 0x11A0 +#define CL_KERNEL_ARG_ACCESS_WRITE_ONLY 0x11A1 +#define CL_KERNEL_ARG_ACCESS_READ_WRITE 0x11A2 +#define CL_KERNEL_ARG_ACCESS_NONE 0x11A3 + +/* cl_kernel_arg_type_qualifer */ +#define CL_KERNEL_ARG_TYPE_NONE 0 +#define CL_KERNEL_ARG_TYPE_CONST (1 << 0) +#define CL_KERNEL_ARG_TYPE_RESTRICT (1 << 1) +#define CL_KERNEL_ARG_TYPE_VOLATILE (1 << 2) +#define CL_KERNEL_ARG_TYPE_PIPE (1 << 3) + +/* cl_kernel_work_group_info */ +#define CL_KERNEL_WORK_GROUP_SIZE 0x11B0 +#define CL_KERNEL_COMPILE_WORK_GROUP_SIZE 0x11B1 +#define CL_KERNEL_LOCAL_MEM_SIZE 0x11B2 +#define CL_KERNEL_PREFERRED_WORK_GROUP_SIZE_MULTIPLE 0x11B3 +#define CL_KERNEL_PRIVATE_MEM_SIZE 0x11B4 +#define CL_KERNEL_GLOBAL_WORK_SIZE 0x11B5 + +/* cl_kernel_sub_group_info */ +#define CL_KERNEL_MAX_SUB_GROUP_SIZE_FOR_NDRANGE 0x2033 +#define CL_KERNEL_SUB_GROUP_COUNT_FOR_NDRANGE 0x2034 +#define CL_KERNEL_LOCAL_SIZE_FOR_SUB_GROUP_COUNT 0x11B8 + +/* cl_kernel_exec_info */ +#define CL_KERNEL_EXEC_INFO_SVM_PTRS 0x11B6 +#define CL_KERNEL_EXEC_INFO_SVM_FINE_GRAIN_SYSTEM 0x11B7 + +/* cl_event_info */ +#define CL_EVENT_COMMAND_QUEUE 0x11D0 +#define CL_EVENT_COMMAND_TYPE 0x11D1 +#define CL_EVENT_REFERENCE_COUNT 0x11D2 +#define CL_EVENT_COMMAND_EXECUTION_STATUS 0x11D3 +#define CL_EVENT_CONTEXT 0x11D4 + +/* cl_command_type */ +#define CL_COMMAND_NDRANGE_KERNEL 0x11F0 +#define CL_COMMAND_TASK 0x11F1 +#define CL_COMMAND_NATIVE_KERNEL 0x11F2 +#define CL_COMMAND_READ_BUFFER 0x11F3 +#define CL_COMMAND_WRITE_BUFFER 0x11F4 +#define CL_COMMAND_COPY_BUFFER 0x11F5 +#define CL_COMMAND_READ_IMAGE 0x11F6 +#define CL_COMMAND_WRITE_IMAGE 0x11F7 +#define CL_COMMAND_COPY_IMAGE 0x11F8 +#define CL_COMMAND_COPY_IMAGE_TO_BUFFER 0x11F9 +#define CL_COMMAND_COPY_BUFFER_TO_IMAGE 0x11FA +#define CL_COMMAND_MAP_BUFFER 0x11FB +#define CL_COMMAND_MAP_IMAGE 0x11FC +#define CL_COMMAND_UNMAP_MEM_OBJECT 0x11FD +#define CL_COMMAND_MARKER 0x11FE +#define CL_COMMAND_ACQUIRE_GL_OBJECTS 0x11FF +#define CL_COMMAND_RELEASE_GL_OBJECTS 0x1200 +#define CL_COMMAND_READ_BUFFER_RECT 0x1201 +#define CL_COMMAND_WRITE_BUFFER_RECT 0x1202 +#define CL_COMMAND_COPY_BUFFER_RECT 0x1203 +#define CL_COMMAND_USER 0x1204 +#define CL_COMMAND_BARRIER 0x1205 +#define CL_COMMAND_MIGRATE_MEM_OBJECTS 0x1206 +#define CL_COMMAND_FILL_BUFFER 0x1207 +#define CL_COMMAND_FILL_IMAGE 0x1208 +#define CL_COMMAND_SVM_FREE 0x1209 +#define CL_COMMAND_SVM_MEMCPY 0x120A +#define CL_COMMAND_SVM_MEMFILL 0x120B +#define CL_COMMAND_SVM_MAP 0x120C +#define CL_COMMAND_SVM_UNMAP 0x120D + +/* command execution status */ +#define CL_COMPLETE 0x0 +#define CL_RUNNING 0x1 +#define CL_SUBMITTED 0x2 +#define CL_QUEUED 0x3 + +/* cl_buffer_create_type */ +#define CL_BUFFER_CREATE_TYPE_REGION 0x1220 + +/* cl_profiling_info */ +#define CL_PROFILING_COMMAND_QUEUED 0x1280 +#define CL_PROFILING_COMMAND_SUBMIT 0x1281 +#define CL_PROFILING_COMMAND_START 0x1282 +#define CL_PROFILING_COMMAND_END 0x1283 +#define CL_PROFILING_COMMAND_COMPLETE 0x1284 + +/********************************************************************************************************/ + +/* Platform API */ +extern CL_API_ENTRY cl_int CL_API_CALL +clGetPlatformIDs(cl_uint /* num_entries */, + cl_platform_id * /* platforms */, + cl_uint * /* num_platforms */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetPlatformInfo(cl_platform_id /* platform */, + cl_platform_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +/* Device APIs */ +extern CL_API_ENTRY cl_int CL_API_CALL +clGetDeviceIDs(cl_platform_id /* platform */, + cl_device_type /* device_type */, + cl_uint /* num_entries */, + cl_device_id * /* devices */, + cl_uint * /* num_devices */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetDeviceInfo(cl_device_id /* device */, + cl_device_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clCreateSubDevices(cl_device_id /* in_device */, + const cl_device_partition_property * /* properties */, + cl_uint /* num_devices */, + cl_device_id * /* out_devices */, + cl_uint * /* num_devices_ret */) CL_API_SUFFIX__VERSION_1_2; + +extern CL_API_ENTRY cl_int CL_API_CALL +clRetainDevice(cl_device_id /* device */) CL_API_SUFFIX__VERSION_1_2; + +extern CL_API_ENTRY cl_int CL_API_CALL +clReleaseDevice(cl_device_id /* device */) CL_API_SUFFIX__VERSION_1_2; + +extern CL_API_ENTRY cl_int CL_API_CALL +clSetDefaultDeviceCommandQueue(cl_context /* context */, + cl_device_id /* device */, + cl_command_queue /* command_queue */) CL_API_SUFFIX__VERSION_2_1; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetDeviceAndHostTimer(cl_device_id /* device */, + cl_ulong* /* device_timestamp */, + cl_ulong* /* host_timestamp */) CL_API_SUFFIX__VERSION_2_1; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetHostTimer(cl_device_id /* device */, + cl_ulong * /* host_timestamp */) CL_API_SUFFIX__VERSION_2_1; + + +/* Context APIs */ +extern CL_API_ENTRY cl_context CL_API_CALL +clCreateContext(const cl_context_properties * /* properties */, + cl_uint /* num_devices */, + const cl_device_id * /* devices */, + void (CL_CALLBACK * /* pfn_notify */)(const char *, const void *, size_t, void *), + void * /* user_data */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_context CL_API_CALL +clCreateContextFromType(const cl_context_properties * /* properties */, + cl_device_type /* device_type */, + void (CL_CALLBACK * /* pfn_notify*/ )(const char *, const void *, size_t, void *), + void * /* user_data */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clRetainContext(cl_context /* context */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clReleaseContext(cl_context /* context */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetContextInfo(cl_context /* context */, + cl_context_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +/* Command Queue APIs */ +extern CL_API_ENTRY cl_command_queue CL_API_CALL +clCreateCommandQueueWithProperties(cl_context /* context */, + cl_device_id /* device */, + const cl_queue_properties * /* properties */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_2_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clRetainCommandQueue(cl_command_queue /* command_queue */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clReleaseCommandQueue(cl_command_queue /* command_queue */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetCommandQueueInfo(cl_command_queue /* command_queue */, + cl_command_queue_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +/* Memory Object APIs */ +extern CL_API_ENTRY cl_mem CL_API_CALL +clCreateBuffer(cl_context /* context */, + cl_mem_flags /* flags */, + size_t /* size */, + void * /* host_ptr */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_mem CL_API_CALL +clCreateSubBuffer(cl_mem /* buffer */, + cl_mem_flags /* flags */, + cl_buffer_create_type /* buffer_create_type */, + const void * /* buffer_create_info */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_1; + +extern CL_API_ENTRY cl_mem CL_API_CALL +clCreateImage(cl_context /* context */, + cl_mem_flags /* flags */, + const cl_image_format * /* image_format */, + const cl_image_desc * /* image_desc */, + void * /* host_ptr */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_2; + +extern CL_API_ENTRY cl_mem CL_API_CALL +clCreatePipe(cl_context /* context */, + cl_mem_flags /* flags */, + cl_uint /* pipe_packet_size */, + cl_uint /* pipe_max_packets */, + const cl_pipe_properties * /* properties */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_2_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clRetainMemObject(cl_mem /* memobj */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clReleaseMemObject(cl_mem /* memobj */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetSupportedImageFormats(cl_context /* context */, + cl_mem_flags /* flags */, + cl_mem_object_type /* image_type */, + cl_uint /* num_entries */, + cl_image_format * /* image_formats */, + cl_uint * /* num_image_formats */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetMemObjectInfo(cl_mem /* memobj */, + cl_mem_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetImageInfo(cl_mem /* image */, + cl_image_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetPipeInfo(cl_mem /* pipe */, + cl_pipe_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_2_0; + + +extern CL_API_ENTRY cl_int CL_API_CALL +clSetMemObjectDestructorCallback(cl_mem /* memobj */, + void (CL_CALLBACK * /*pfn_notify*/)( cl_mem /* memobj */, void* /*user_data*/), + void * /*user_data */ ) CL_API_SUFFIX__VERSION_1_1; + +/* SVM Allocation APIs */ +extern CL_API_ENTRY void * CL_API_CALL +clSVMAlloc(cl_context /* context */, + cl_svm_mem_flags /* flags */, + size_t /* size */, + cl_uint /* alignment */) CL_API_SUFFIX__VERSION_2_0; + +extern CL_API_ENTRY void CL_API_CALL +clSVMFree(cl_context /* context */, + void * /* svm_pointer */) CL_API_SUFFIX__VERSION_2_0; + +/* Sampler APIs */ +extern CL_API_ENTRY cl_sampler CL_API_CALL +clCreateSamplerWithProperties(cl_context /* context */, + const cl_sampler_properties * /* normalized_coords */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_2_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clRetainSampler(cl_sampler /* sampler */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clReleaseSampler(cl_sampler /* sampler */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetSamplerInfo(cl_sampler /* sampler */, + cl_sampler_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +/* Program Object APIs */ +extern CL_API_ENTRY cl_program CL_API_CALL +clCreateProgramWithSource(cl_context /* context */, + cl_uint /* count */, + const char ** /* strings */, + const size_t * /* lengths */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_program CL_API_CALL +clCreateProgramWithBinary(cl_context /* context */, + cl_uint /* num_devices */, + const cl_device_id * /* device_list */, + const size_t * /* lengths */, + const unsigned char ** /* binaries */, + cl_int * /* binary_status */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_program CL_API_CALL +clCreateProgramWithBuiltInKernels(cl_context /* context */, + cl_uint /* num_devices */, + const cl_device_id * /* device_list */, + const char * /* kernel_names */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_2; + +extern CL_API_ENTRY cl_program CL_API_CALL +clCreateProgramWithIL(cl_context /* context */, + const void* /* il */, + size_t /* length */, + cl_int* /* errcode_ret */) CL_API_SUFFIX__VERSION_2_1; + + +extern CL_API_ENTRY cl_int CL_API_CALL +clRetainProgram(cl_program /* program */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clReleaseProgram(cl_program /* program */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clBuildProgram(cl_program /* program */, + cl_uint /* num_devices */, + const cl_device_id * /* device_list */, + const char * /* options */, + void (CL_CALLBACK * /* pfn_notify */)(cl_program /* program */, void * /* user_data */), + void * /* user_data */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clCompileProgram(cl_program /* program */, + cl_uint /* num_devices */, + const cl_device_id * /* device_list */, + const char * /* options */, + cl_uint /* num_input_headers */, + const cl_program * /* input_headers */, + const char ** /* header_include_names */, + void (CL_CALLBACK * /* pfn_notify */)(cl_program /* program */, void * /* user_data */), + void * /* user_data */) CL_API_SUFFIX__VERSION_1_2; + +extern CL_API_ENTRY cl_program CL_API_CALL +clLinkProgram(cl_context /* context */, + cl_uint /* num_devices */, + const cl_device_id * /* device_list */, + const char * /* options */, + cl_uint /* num_input_programs */, + const cl_program * /* input_programs */, + void (CL_CALLBACK * /* pfn_notify */)(cl_program /* program */, void * /* user_data */), + void * /* user_data */, + cl_int * /* errcode_ret */ ) CL_API_SUFFIX__VERSION_1_2; + + +extern CL_API_ENTRY cl_int CL_API_CALL +clUnloadPlatformCompiler(cl_platform_id /* platform */) CL_API_SUFFIX__VERSION_1_2; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetProgramInfo(cl_program /* program */, + cl_program_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetProgramBuildInfo(cl_program /* program */, + cl_device_id /* device */, + cl_program_build_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +/* Kernel Object APIs */ +extern CL_API_ENTRY cl_kernel CL_API_CALL +clCreateKernel(cl_program /* program */, + const char * /* kernel_name */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clCreateKernelsInProgram(cl_program /* program */, + cl_uint /* num_kernels */, + cl_kernel * /* kernels */, + cl_uint * /* num_kernels_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_kernel CL_API_CALL +clCloneKernel(cl_kernel /* source_kernel */, + cl_int* /* errcode_ret */) CL_API_SUFFIX__VERSION_2_1; + +extern CL_API_ENTRY cl_int CL_API_CALL +clRetainKernel(cl_kernel /* kernel */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clReleaseKernel(cl_kernel /* kernel */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clSetKernelArg(cl_kernel /* kernel */, + cl_uint /* arg_index */, + size_t /* arg_size */, + const void * /* arg_value */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clSetKernelArgSVMPointer(cl_kernel /* kernel */, + cl_uint /* arg_index */, + const void * /* arg_value */) CL_API_SUFFIX__VERSION_2_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clSetKernelExecInfo(cl_kernel /* kernel */, + cl_kernel_exec_info /* param_name */, + size_t /* param_value_size */, + const void * /* param_value */) CL_API_SUFFIX__VERSION_2_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetKernelInfo(cl_kernel /* kernel */, + cl_kernel_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetKernelArgInfo(cl_kernel /* kernel */, + cl_uint /* arg_indx */, + cl_kernel_arg_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_2; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetKernelWorkGroupInfo(cl_kernel /* kernel */, + cl_device_id /* device */, + cl_kernel_work_group_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetKernelSubGroupInfo(cl_kernel /* kernel */, + cl_device_id /* device */, + cl_kernel_sub_group_info /* param_name */, + size_t /* input_value_size */, + const void* /*input_value */, + size_t /* param_value_size */, + void* /* param_value */, + size_t* /* param_value_size_ret */ ) CL_API_SUFFIX__VERSION_2_1; + + +/* Event Object APIs */ +extern CL_API_ENTRY cl_int CL_API_CALL +clWaitForEvents(cl_uint /* num_events */, + const cl_event * /* event_list */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetEventInfo(cl_event /* event */, + cl_event_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_event CL_API_CALL +clCreateUserEvent(cl_context /* context */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_1; + +extern CL_API_ENTRY cl_int CL_API_CALL +clRetainEvent(cl_event /* event */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clReleaseEvent(cl_event /* event */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clSetUserEventStatus(cl_event /* event */, + cl_int /* execution_status */) CL_API_SUFFIX__VERSION_1_1; + +extern CL_API_ENTRY cl_int CL_API_CALL +clSetEventCallback( cl_event /* event */, + cl_int /* command_exec_callback_type */, + void (CL_CALLBACK * /* pfn_notify */)(cl_event, cl_int, void *), + void * /* user_data */) CL_API_SUFFIX__VERSION_1_1; + +/* Profiling APIs */ +extern CL_API_ENTRY cl_int CL_API_CALL +clGetEventProfilingInfo(cl_event /* event */, + cl_profiling_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +/* Flush and Finish APIs */ +extern CL_API_ENTRY cl_int CL_API_CALL +clFlush(cl_command_queue /* command_queue */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clFinish(cl_command_queue /* command_queue */) CL_API_SUFFIX__VERSION_1_0; + +/* Enqueued Commands APIs */ +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueReadBuffer(cl_command_queue /* command_queue */, + cl_mem /* buffer */, + cl_bool /* blocking_read */, + size_t /* offset */, + size_t /* size */, + void * /* ptr */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueReadBufferRect(cl_command_queue /* command_queue */, + cl_mem /* buffer */, + cl_bool /* blocking_read */, + const size_t * /* buffer_offset */, + const size_t * /* host_offset */, + const size_t * /* region */, + size_t /* buffer_row_pitch */, + size_t /* buffer_slice_pitch */, + size_t /* host_row_pitch */, + size_t /* host_slice_pitch */, + void * /* ptr */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_1; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueWriteBuffer(cl_command_queue /* command_queue */, + cl_mem /* buffer */, + cl_bool /* blocking_write */, + size_t /* offset */, + size_t /* size */, + const void * /* ptr */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueWriteBufferRect(cl_command_queue /* command_queue */, + cl_mem /* buffer */, + cl_bool /* blocking_write */, + const size_t * /* buffer_offset */, + const size_t * /* host_offset */, + const size_t * /* region */, + size_t /* buffer_row_pitch */, + size_t /* buffer_slice_pitch */, + size_t /* host_row_pitch */, + size_t /* host_slice_pitch */, + const void * /* ptr */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_1; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueFillBuffer(cl_command_queue /* command_queue */, + cl_mem /* buffer */, + const void * /* pattern */, + size_t /* pattern_size */, + size_t /* offset */, + size_t /* size */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_2; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueCopyBuffer(cl_command_queue /* command_queue */, + cl_mem /* src_buffer */, + cl_mem /* dst_buffer */, + size_t /* src_offset */, + size_t /* dst_offset */, + size_t /* size */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueCopyBufferRect(cl_command_queue /* command_queue */, + cl_mem /* src_buffer */, + cl_mem /* dst_buffer */, + const size_t * /* src_origin */, + const size_t * /* dst_origin */, + const size_t * /* region */, + size_t /* src_row_pitch */, + size_t /* src_slice_pitch */, + size_t /* dst_row_pitch */, + size_t /* dst_slice_pitch */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_1; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueReadImage(cl_command_queue /* command_queue */, + cl_mem /* image */, + cl_bool /* blocking_read */, + const size_t * /* origin[3] */, + const size_t * /* region[3] */, + size_t /* row_pitch */, + size_t /* slice_pitch */, + void * /* ptr */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueWriteImage(cl_command_queue /* command_queue */, + cl_mem /* image */, + cl_bool /* blocking_write */, + const size_t * /* origin[3] */, + const size_t * /* region[3] */, + size_t /* input_row_pitch */, + size_t /* input_slice_pitch */, + const void * /* ptr */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueFillImage(cl_command_queue /* command_queue */, + cl_mem /* image */, + const void * /* fill_color */, + const size_t * /* origin[3] */, + const size_t * /* region[3] */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_2; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueCopyImage(cl_command_queue /* command_queue */, + cl_mem /* src_image */, + cl_mem /* dst_image */, + const size_t * /* src_origin[3] */, + const size_t * /* dst_origin[3] */, + const size_t * /* region[3] */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueCopyImageToBuffer(cl_command_queue /* command_queue */, + cl_mem /* src_image */, + cl_mem /* dst_buffer */, + const size_t * /* src_origin[3] */, + const size_t * /* region[3] */, + size_t /* dst_offset */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueCopyBufferToImage(cl_command_queue /* command_queue */, + cl_mem /* src_buffer */, + cl_mem /* dst_image */, + size_t /* src_offset */, + const size_t * /* dst_origin[3] */, + const size_t * /* region[3] */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY void * CL_API_CALL +clEnqueueMapBuffer(cl_command_queue /* command_queue */, + cl_mem /* buffer */, + cl_bool /* blocking_map */, + cl_map_flags /* map_flags */, + size_t /* offset */, + size_t /* size */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY void * CL_API_CALL +clEnqueueMapImage(cl_command_queue /* command_queue */, + cl_mem /* image */, + cl_bool /* blocking_map */, + cl_map_flags /* map_flags */, + const size_t * /* origin[3] */, + const size_t * /* region[3] */, + size_t * /* image_row_pitch */, + size_t * /* image_slice_pitch */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueUnmapMemObject(cl_command_queue /* command_queue */, + cl_mem /* memobj */, + void * /* mapped_ptr */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueMigrateMemObjects(cl_command_queue /* command_queue */, + cl_uint /* num_mem_objects */, + const cl_mem * /* mem_objects */, + cl_mem_migration_flags /* flags */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_2; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueNDRangeKernel(cl_command_queue /* command_queue */, + cl_kernel /* kernel */, + cl_uint /* work_dim */, + const size_t * /* global_work_offset */, + const size_t * /* global_work_size */, + const size_t * /* local_work_size */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueNativeKernel(cl_command_queue /* command_queue */, + void (CL_CALLBACK * /*user_func*/)(void *), + void * /* args */, + size_t /* cb_args */, + cl_uint /* num_mem_objects */, + const cl_mem * /* mem_list */, + const void ** /* args_mem_loc */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueMarkerWithWaitList(cl_command_queue /* command_queue */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_2; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueBarrierWithWaitList(cl_command_queue /* command_queue */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_2; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueSVMFree(cl_command_queue /* command_queue */, + cl_uint /* num_svm_pointers */, + void *[] /* svm_pointers[] */, + void (CL_CALLBACK * /*pfn_free_func*/)(cl_command_queue /* queue */, + cl_uint /* num_svm_pointers */, + void *[] /* svm_pointers[] */, + void * /* user_data */), + void * /* user_data */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_2_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueSVMMemcpy(cl_command_queue /* command_queue */, + cl_bool /* blocking_copy */, + void * /* dst_ptr */, + const void * /* src_ptr */, + size_t /* size */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_2_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueSVMMemFill(cl_command_queue /* command_queue */, + void * /* svm_ptr */, + const void * /* pattern */, + size_t /* pattern_size */, + size_t /* size */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_2_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueSVMMap(cl_command_queue /* command_queue */, + cl_bool /* blocking_map */, + cl_map_flags /* flags */, + void * /* svm_ptr */, + size_t /* size */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_2_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueSVMUnmap(cl_command_queue /* command_queue */, + void * /* svm_ptr */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_2_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueSVMMigrateMem(cl_command_queue /* command_queue */, + cl_uint /* num_svm_pointers */, + const void ** /* svm_pointers */, + const size_t * /* sizes */, + cl_mem_migration_flags /* flags */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_2_1; + + +/* Extension function access + * + * Returns the extension function address for the given function name, + * or NULL if a valid function can not be found. The client must + * check to make sure the address is not NULL, before using or + * calling the returned function address. + */ +extern CL_API_ENTRY void * CL_API_CALL +clGetExtensionFunctionAddressForPlatform(cl_platform_id /* platform */, + const char * /* func_name */) CL_API_SUFFIX__VERSION_1_2; + + +/* Deprecated OpenCL 1.1 APIs */ +extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_mem CL_API_CALL +clCreateImage2D(cl_context /* context */, + cl_mem_flags /* flags */, + const cl_image_format * /* image_format */, + size_t /* image_width */, + size_t /* image_height */, + size_t /* image_row_pitch */, + void * /* host_ptr */, + cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; + +extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_mem CL_API_CALL +clCreateImage3D(cl_context /* context */, + cl_mem_flags /* flags */, + const cl_image_format * /* image_format */, + size_t /* image_width */, + size_t /* image_height */, + size_t /* image_depth */, + size_t /* image_row_pitch */, + size_t /* image_slice_pitch */, + void * /* host_ptr */, + cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; + +extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_int CL_API_CALL +clEnqueueMarker(cl_command_queue /* command_queue */, + cl_event * /* event */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; + +extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_int CL_API_CALL +clEnqueueWaitForEvents(cl_command_queue /* command_queue */, + cl_uint /* num_events */, + const cl_event * /* event_list */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; + +extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_int CL_API_CALL +clEnqueueBarrier(cl_command_queue /* command_queue */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; + +extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_int CL_API_CALL +clUnloadCompiler(void) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; + +extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED void * CL_API_CALL +clGetExtensionFunctionAddress(const char * /* func_name */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; + +/* Deprecated OpenCL 2.0 APIs */ +extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_2_DEPRECATED cl_command_queue CL_API_CALL +clCreateCommandQueue(cl_context /* context */, + cl_device_id /* device */, + cl_command_queue_properties /* properties */, + cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED; + + +extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_2_DEPRECATED cl_sampler CL_API_CALL +clCreateSampler(cl_context /* context */, + cl_bool /* normalized_coords */, + cl_addressing_mode /* addressing_mode */, + cl_filter_mode /* filter_mode */, + cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED; + +extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_2_DEPRECATED cl_int CL_API_CALL +clEnqueueTask(cl_command_queue /* command_queue */, + cl_kernel /* kernel */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED; + +#ifdef __cplusplus +} +#endif + +#endif /* __OPENCL_CL_H */ + diff --git a/third_party/opencl/include/CL/cl_d3d10.h b/third_party/opencl/include/CL/cl_d3d10.h new file mode 100644 index 000000000..d5960a43f --- /dev/null +++ b/third_party/opencl/include/CL/cl_d3d10.h @@ -0,0 +1,131 @@ +/********************************************************************************** + * Copyright (c) 2008-2015 The Khronos Group Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and/or associated documentation files (the + * "Materials"), to deal in the Materials without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Materials, and to + * permit persons to whom the Materials are furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Materials. + * + * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS + * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS + * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT + * https://www.khronos.org/registry/ + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. + **********************************************************************************/ + +/* $Revision: 11708 $ on $Date: 2010-06-13 23:36:24 -0700 (Sun, 13 Jun 2010) $ */ + +#ifndef __OPENCL_CL_D3D10_H +#define __OPENCL_CL_D3D10_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/****************************************************************************** + * cl_khr_d3d10_sharing */ +#define cl_khr_d3d10_sharing 1 + +typedef cl_uint cl_d3d10_device_source_khr; +typedef cl_uint cl_d3d10_device_set_khr; + +/******************************************************************************/ + +/* Error Codes */ +#define CL_INVALID_D3D10_DEVICE_KHR -1002 +#define CL_INVALID_D3D10_RESOURCE_KHR -1003 +#define CL_D3D10_RESOURCE_ALREADY_ACQUIRED_KHR -1004 +#define CL_D3D10_RESOURCE_NOT_ACQUIRED_KHR -1005 + +/* cl_d3d10_device_source_nv */ +#define CL_D3D10_DEVICE_KHR 0x4010 +#define CL_D3D10_DXGI_ADAPTER_KHR 0x4011 + +/* cl_d3d10_device_set_nv */ +#define CL_PREFERRED_DEVICES_FOR_D3D10_KHR 0x4012 +#define CL_ALL_DEVICES_FOR_D3D10_KHR 0x4013 + +/* cl_context_info */ +#define CL_CONTEXT_D3D10_DEVICE_KHR 0x4014 +#define CL_CONTEXT_D3D10_PREFER_SHARED_RESOURCES_KHR 0x402C + +/* cl_mem_info */ +#define CL_MEM_D3D10_RESOURCE_KHR 0x4015 + +/* cl_image_info */ +#define CL_IMAGE_D3D10_SUBRESOURCE_KHR 0x4016 + +/* cl_command_type */ +#define CL_COMMAND_ACQUIRE_D3D10_OBJECTS_KHR 0x4017 +#define CL_COMMAND_RELEASE_D3D10_OBJECTS_KHR 0x4018 + +/******************************************************************************/ + +typedef CL_API_ENTRY cl_int (CL_API_CALL *clGetDeviceIDsFromD3D10KHR_fn)( + cl_platform_id platform, + cl_d3d10_device_source_khr d3d_device_source, + void * d3d_object, + cl_d3d10_device_set_khr d3d_device_set, + cl_uint num_entries, + cl_device_id * devices, + cl_uint * num_devices) CL_API_SUFFIX__VERSION_1_0; + +typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D10BufferKHR_fn)( + cl_context context, + cl_mem_flags flags, + ID3D10Buffer * resource, + cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_0; + +typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D10Texture2DKHR_fn)( + cl_context context, + cl_mem_flags flags, + ID3D10Texture2D * resource, + UINT subresource, + cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_0; + +typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D10Texture3DKHR_fn)( + cl_context context, + cl_mem_flags flags, + ID3D10Texture3D * resource, + UINT subresource, + cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_0; + +typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueAcquireD3D10ObjectsKHR_fn)( + cl_command_queue command_queue, + cl_uint num_objects, + const cl_mem * mem_objects, + cl_uint num_events_in_wait_list, + const cl_event * event_wait_list, + cl_event * event) CL_API_SUFFIX__VERSION_1_0; + +typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueReleaseD3D10ObjectsKHR_fn)( + cl_command_queue command_queue, + cl_uint num_objects, + const cl_mem * mem_objects, + cl_uint num_events_in_wait_list, + const cl_event * event_wait_list, + cl_event * event) CL_API_SUFFIX__VERSION_1_0; + +#ifdef __cplusplus +} +#endif + +#endif /* __OPENCL_CL_D3D10_H */ + diff --git a/third_party/opencl/include/CL/cl_d3d11.h b/third_party/opencl/include/CL/cl_d3d11.h new file mode 100644 index 000000000..39f907239 --- /dev/null +++ b/third_party/opencl/include/CL/cl_d3d11.h @@ -0,0 +1,131 @@ +/********************************************************************************** + * Copyright (c) 2008-2015 The Khronos Group Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and/or associated documentation files (the + * "Materials"), to deal in the Materials without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Materials, and to + * permit persons to whom the Materials are furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Materials. + * + * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS + * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS + * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT + * https://www.khronos.org/registry/ + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. + **********************************************************************************/ + +/* $Revision: 11708 $ on $Date: 2010-06-13 23:36:24 -0700 (Sun, 13 Jun 2010) $ */ + +#ifndef __OPENCL_CL_D3D11_H +#define __OPENCL_CL_D3D11_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/****************************************************************************** + * cl_khr_d3d11_sharing */ +#define cl_khr_d3d11_sharing 1 + +typedef cl_uint cl_d3d11_device_source_khr; +typedef cl_uint cl_d3d11_device_set_khr; + +/******************************************************************************/ + +/* Error Codes */ +#define CL_INVALID_D3D11_DEVICE_KHR -1006 +#define CL_INVALID_D3D11_RESOURCE_KHR -1007 +#define CL_D3D11_RESOURCE_ALREADY_ACQUIRED_KHR -1008 +#define CL_D3D11_RESOURCE_NOT_ACQUIRED_KHR -1009 + +/* cl_d3d11_device_source */ +#define CL_D3D11_DEVICE_KHR 0x4019 +#define CL_D3D11_DXGI_ADAPTER_KHR 0x401A + +/* cl_d3d11_device_set */ +#define CL_PREFERRED_DEVICES_FOR_D3D11_KHR 0x401B +#define CL_ALL_DEVICES_FOR_D3D11_KHR 0x401C + +/* cl_context_info */ +#define CL_CONTEXT_D3D11_DEVICE_KHR 0x401D +#define CL_CONTEXT_D3D11_PREFER_SHARED_RESOURCES_KHR 0x402D + +/* cl_mem_info */ +#define CL_MEM_D3D11_RESOURCE_KHR 0x401E + +/* cl_image_info */ +#define CL_IMAGE_D3D11_SUBRESOURCE_KHR 0x401F + +/* cl_command_type */ +#define CL_COMMAND_ACQUIRE_D3D11_OBJECTS_KHR 0x4020 +#define CL_COMMAND_RELEASE_D3D11_OBJECTS_KHR 0x4021 + +/******************************************************************************/ + +typedef CL_API_ENTRY cl_int (CL_API_CALL *clGetDeviceIDsFromD3D11KHR_fn)( + cl_platform_id platform, + cl_d3d11_device_source_khr d3d_device_source, + void * d3d_object, + cl_d3d11_device_set_khr d3d_device_set, + cl_uint num_entries, + cl_device_id * devices, + cl_uint * num_devices) CL_API_SUFFIX__VERSION_1_2; + +typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D11BufferKHR_fn)( + cl_context context, + cl_mem_flags flags, + ID3D11Buffer * resource, + cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_2; + +typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D11Texture2DKHR_fn)( + cl_context context, + cl_mem_flags flags, + ID3D11Texture2D * resource, + UINT subresource, + cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_2; + +typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D11Texture3DKHR_fn)( + cl_context context, + cl_mem_flags flags, + ID3D11Texture3D * resource, + UINT subresource, + cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_2; + +typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueAcquireD3D11ObjectsKHR_fn)( + cl_command_queue command_queue, + cl_uint num_objects, + const cl_mem * mem_objects, + cl_uint num_events_in_wait_list, + const cl_event * event_wait_list, + cl_event * event) CL_API_SUFFIX__VERSION_1_2; + +typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueReleaseD3D11ObjectsKHR_fn)( + cl_command_queue command_queue, + cl_uint num_objects, + const cl_mem * mem_objects, + cl_uint num_events_in_wait_list, + const cl_event * event_wait_list, + cl_event * event) CL_API_SUFFIX__VERSION_1_2; + +#ifdef __cplusplus +} +#endif + +#endif /* __OPENCL_CL_D3D11_H */ + diff --git a/third_party/opencl/include/CL/cl_dx9_media_sharing.h b/third_party/opencl/include/CL/cl_dx9_media_sharing.h new file mode 100644 index 000000000..2729e8b9e --- /dev/null +++ b/third_party/opencl/include/CL/cl_dx9_media_sharing.h @@ -0,0 +1,132 @@ +/********************************************************************************** + * Copyright (c) 2008-2015 The Khronos Group Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and/or associated documentation files (the + * "Materials"), to deal in the Materials without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Materials, and to + * permit persons to whom the Materials are furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Materials. + * + * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS + * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS + * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT + * https://www.khronos.org/registry/ + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. + **********************************************************************************/ + +/* $Revision: 11708 $ on $Date: 2010-06-13 23:36:24 -0700 (Sun, 13 Jun 2010) $ */ + +#ifndef __OPENCL_CL_DX9_MEDIA_SHARING_H +#define __OPENCL_CL_DX9_MEDIA_SHARING_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/******************************************************************************/ +/* cl_khr_dx9_media_sharing */ +#define cl_khr_dx9_media_sharing 1 + +typedef cl_uint cl_dx9_media_adapter_type_khr; +typedef cl_uint cl_dx9_media_adapter_set_khr; + +#if defined(_WIN32) +#include +typedef struct _cl_dx9_surface_info_khr +{ + IDirect3DSurface9 *resource; + HANDLE shared_handle; +} cl_dx9_surface_info_khr; +#endif + + +/******************************************************************************/ + +/* Error Codes */ +#define CL_INVALID_DX9_MEDIA_ADAPTER_KHR -1010 +#define CL_INVALID_DX9_MEDIA_SURFACE_KHR -1011 +#define CL_DX9_MEDIA_SURFACE_ALREADY_ACQUIRED_KHR -1012 +#define CL_DX9_MEDIA_SURFACE_NOT_ACQUIRED_KHR -1013 + +/* cl_media_adapter_type_khr */ +#define CL_ADAPTER_D3D9_KHR 0x2020 +#define CL_ADAPTER_D3D9EX_KHR 0x2021 +#define CL_ADAPTER_DXVA_KHR 0x2022 + +/* cl_media_adapter_set_khr */ +#define CL_PREFERRED_DEVICES_FOR_DX9_MEDIA_ADAPTER_KHR 0x2023 +#define CL_ALL_DEVICES_FOR_DX9_MEDIA_ADAPTER_KHR 0x2024 + +/* cl_context_info */ +#define CL_CONTEXT_ADAPTER_D3D9_KHR 0x2025 +#define CL_CONTEXT_ADAPTER_D3D9EX_KHR 0x2026 +#define CL_CONTEXT_ADAPTER_DXVA_KHR 0x2027 + +/* cl_mem_info */ +#define CL_MEM_DX9_MEDIA_ADAPTER_TYPE_KHR 0x2028 +#define CL_MEM_DX9_MEDIA_SURFACE_INFO_KHR 0x2029 + +/* cl_image_info */ +#define CL_IMAGE_DX9_MEDIA_PLANE_KHR 0x202A + +/* cl_command_type */ +#define CL_COMMAND_ACQUIRE_DX9_MEDIA_SURFACES_KHR 0x202B +#define CL_COMMAND_RELEASE_DX9_MEDIA_SURFACES_KHR 0x202C + +/******************************************************************************/ + +typedef CL_API_ENTRY cl_int (CL_API_CALL *clGetDeviceIDsFromDX9MediaAdapterKHR_fn)( + cl_platform_id platform, + cl_uint num_media_adapters, + cl_dx9_media_adapter_type_khr * media_adapter_type, + void * media_adapters, + cl_dx9_media_adapter_set_khr media_adapter_set, + cl_uint num_entries, + cl_device_id * devices, + cl_uint * num_devices) CL_API_SUFFIX__VERSION_1_2; + +typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromDX9MediaSurfaceKHR_fn)( + cl_context context, + cl_mem_flags flags, + cl_dx9_media_adapter_type_khr adapter_type, + void * surface_info, + cl_uint plane, + cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_2; + +typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueAcquireDX9MediaSurfacesKHR_fn)( + cl_command_queue command_queue, + cl_uint num_objects, + const cl_mem * mem_objects, + cl_uint num_events_in_wait_list, + const cl_event * event_wait_list, + cl_event * event) CL_API_SUFFIX__VERSION_1_2; + +typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueReleaseDX9MediaSurfacesKHR_fn)( + cl_command_queue command_queue, + cl_uint num_objects, + const cl_mem * mem_objects, + cl_uint num_events_in_wait_list, + const cl_event * event_wait_list, + cl_event * event) CL_API_SUFFIX__VERSION_1_2; + +#ifdef __cplusplus +} +#endif + +#endif /* __OPENCL_CL_DX9_MEDIA_SHARING_H */ + diff --git a/third_party/opencl/include/CL/cl_egl.h b/third_party/opencl/include/CL/cl_egl.h new file mode 100644 index 000000000..a765bd526 --- /dev/null +++ b/third_party/opencl/include/CL/cl_egl.h @@ -0,0 +1,136 @@ +/******************************************************************************* + * Copyright (c) 2008-2015 The Khronos Group Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and/or associated documentation files (the + * "Materials"), to deal in the Materials without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Materials, and to + * permit persons to whom the Materials are furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Materials. + * + * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS + * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS + * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT + * https://www.khronos.org/registry/ + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. + ******************************************************************************/ + +#ifndef __OPENCL_CL_EGL_H +#define __OPENCL_CL_EGL_H + +#ifdef __APPLE__ + +#else +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + + +/* Command type for events created with clEnqueueAcquireEGLObjectsKHR */ +#define CL_COMMAND_EGL_FENCE_SYNC_OBJECT_KHR 0x202F +#define CL_COMMAND_ACQUIRE_EGL_OBJECTS_KHR 0x202D +#define CL_COMMAND_RELEASE_EGL_OBJECTS_KHR 0x202E + +/* Error type for clCreateFromEGLImageKHR */ +#define CL_INVALID_EGL_OBJECT_KHR -1093 +#define CL_EGL_RESOURCE_NOT_ACQUIRED_KHR -1092 + +/* CLeglImageKHR is an opaque handle to an EGLImage */ +typedef void* CLeglImageKHR; + +/* CLeglDisplayKHR is an opaque handle to an EGLDisplay */ +typedef void* CLeglDisplayKHR; + +/* CLeglSyncKHR is an opaque handle to an EGLSync object */ +typedef void* CLeglSyncKHR; + +/* properties passed to clCreateFromEGLImageKHR */ +typedef intptr_t cl_egl_image_properties_khr; + + +#define cl_khr_egl_image 1 + +extern CL_API_ENTRY cl_mem CL_API_CALL +clCreateFromEGLImageKHR(cl_context /* context */, + CLeglDisplayKHR /* egldisplay */, + CLeglImageKHR /* eglimage */, + cl_mem_flags /* flags */, + const cl_egl_image_properties_khr * /* properties */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; + +typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromEGLImageKHR_fn)( + cl_context context, + CLeglDisplayKHR egldisplay, + CLeglImageKHR eglimage, + cl_mem_flags flags, + const cl_egl_image_properties_khr * properties, + cl_int * errcode_ret); + + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueAcquireEGLObjectsKHR(cl_command_queue /* command_queue */, + cl_uint /* num_objects */, + const cl_mem * /* mem_objects */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + +typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueAcquireEGLObjectsKHR_fn)( + cl_command_queue command_queue, + cl_uint num_objects, + const cl_mem * mem_objects, + cl_uint num_events_in_wait_list, + const cl_event * event_wait_list, + cl_event * event); + + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueReleaseEGLObjectsKHR(cl_command_queue /* command_queue */, + cl_uint /* num_objects */, + const cl_mem * /* mem_objects */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + +typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueReleaseEGLObjectsKHR_fn)( + cl_command_queue command_queue, + cl_uint num_objects, + const cl_mem * mem_objects, + cl_uint num_events_in_wait_list, + const cl_event * event_wait_list, + cl_event * event); + + +#define cl_khr_egl_event 1 + +extern CL_API_ENTRY cl_event CL_API_CALL +clCreateEventFromEGLSyncKHR(cl_context /* context */, + CLeglSyncKHR /* sync */, + CLeglDisplayKHR /* display */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; + +typedef CL_API_ENTRY cl_event (CL_API_CALL *clCreateEventFromEGLSyncKHR_fn)( + cl_context context, + CLeglSyncKHR sync, + CLeglDisplayKHR display, + cl_int * errcode_ret); + +#ifdef __cplusplus +} +#endif + +#endif /* __OPENCL_CL_EGL_H */ diff --git a/third_party/opencl/include/CL/cl_ext.h b/third_party/opencl/include/CL/cl_ext.h new file mode 100644 index 000000000..794158389 --- /dev/null +++ b/third_party/opencl/include/CL/cl_ext.h @@ -0,0 +1,391 @@ +/******************************************************************************* + * Copyright (c) 2008-2015 The Khronos Group Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and/or associated documentation files (the + * "Materials"), to deal in the Materials without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Materials, and to + * permit persons to whom the Materials are furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Materials. + * + * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS + * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS + * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT + * https://www.khronos.org/registry/ + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. + ******************************************************************************/ + +/* $Revision: 11928 $ on $Date: 2010-07-13 09:04:56 -0700 (Tue, 13 Jul 2010) $ */ + +/* cl_ext.h contains OpenCL extensions which don't have external */ +/* (OpenGL, D3D) dependencies. */ + +#ifndef __CL_EXT_H +#define __CL_EXT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __APPLE__ + #include + #include +#else + #include +#endif + +/* cl_khr_fp16 extension - no extension #define since it has no functions */ +#define CL_DEVICE_HALF_FP_CONFIG 0x1033 + +/* Memory object destruction + * + * Apple extension for use to manage externally allocated buffers used with cl_mem objects with CL_MEM_USE_HOST_PTR + * + * Registers a user callback function that will be called when the memory object is deleted and its resources + * freed. Each call to clSetMemObjectCallbackFn registers the specified user callback function on a callback + * stack associated with memobj. The registered user callback functions are called in the reverse order in + * which they were registered. The user callback functions are called and then the memory object is deleted + * and its resources freed. This provides a mechanism for the application (and libraries) using memobj to be + * notified when the memory referenced by host_ptr, specified when the memory object is created and used as + * the storage bits for the memory object, can be reused or freed. + * + * The application may not call CL api's with the cl_mem object passed to the pfn_notify. + * + * Please check for the "cl_APPLE_SetMemObjectDestructor" extension using clGetDeviceInfo(CL_DEVICE_EXTENSIONS) + * before using. + */ +#define cl_APPLE_SetMemObjectDestructor 1 +cl_int CL_API_ENTRY clSetMemObjectDestructorAPPLE( cl_mem /* memobj */, + void (* /*pfn_notify*/)( cl_mem /* memobj */, void* /*user_data*/), + void * /*user_data */ ) CL_EXT_SUFFIX__VERSION_1_0; + + +/* Context Logging Functions + * + * The next three convenience functions are intended to be used as the pfn_notify parameter to clCreateContext(). + * Please check for the "cl_APPLE_ContextLoggingFunctions" extension using clGetDeviceInfo(CL_DEVICE_EXTENSIONS) + * before using. + * + * clLogMessagesToSystemLog fowards on all log messages to the Apple System Logger + */ +#define cl_APPLE_ContextLoggingFunctions 1 +extern void CL_API_ENTRY clLogMessagesToSystemLogAPPLE( const char * /* errstr */, + const void * /* private_info */, + size_t /* cb */, + void * /* user_data */ ) CL_EXT_SUFFIX__VERSION_1_0; + +/* clLogMessagesToStdout sends all log messages to the file descriptor stdout */ +extern void CL_API_ENTRY clLogMessagesToStdoutAPPLE( const char * /* errstr */, + const void * /* private_info */, + size_t /* cb */, + void * /* user_data */ ) CL_EXT_SUFFIX__VERSION_1_0; + +/* clLogMessagesToStderr sends all log messages to the file descriptor stderr */ +extern void CL_API_ENTRY clLogMessagesToStderrAPPLE( const char * /* errstr */, + const void * /* private_info */, + size_t /* cb */, + void * /* user_data */ ) CL_EXT_SUFFIX__VERSION_1_0; + + +/************************ +* cl_khr_icd extension * +************************/ +#define cl_khr_icd 1 + +/* cl_platform_info */ +#define CL_PLATFORM_ICD_SUFFIX_KHR 0x0920 + +/* Additional Error Codes */ +#define CL_PLATFORM_NOT_FOUND_KHR -1001 + +extern CL_API_ENTRY cl_int CL_API_CALL +clIcdGetPlatformIDsKHR(cl_uint /* num_entries */, + cl_platform_id * /* platforms */, + cl_uint * /* num_platforms */); + +typedef CL_API_ENTRY cl_int (CL_API_CALL *clIcdGetPlatformIDsKHR_fn)( + cl_uint /* num_entries */, + cl_platform_id * /* platforms */, + cl_uint * /* num_platforms */); + + +/* Extension: cl_khr_image2D_buffer + * + * This extension allows a 2D image to be created from a cl_mem buffer without a copy. + * The type associated with a 2D image created from a buffer in an OpenCL program is image2d_t. + * Both the sampler and sampler-less read_image built-in functions are supported for 2D images + * and 2D images created from a buffer. Similarly, the write_image built-ins are also supported + * for 2D images created from a buffer. + * + * When the 2D image from buffer is created, the client must specify the width, + * height, image format (i.e. channel order and channel data type) and optionally the row pitch + * + * The pitch specified must be a multiple of CL_DEVICE_IMAGE_PITCH_ALIGNMENT pixels. + * The base address of the buffer must be aligned to CL_DEVICE_IMAGE_BASE_ADDRESS_ALIGNMENT pixels. + */ + +/************************************* + * cl_khr_initalize_memory extension * + *************************************/ + +#define CL_CONTEXT_MEMORY_INITIALIZE_KHR 0x2030 + + +/************************************** + * cl_khr_terminate_context extension * + **************************************/ + +#define CL_DEVICE_TERMINATE_CAPABILITY_KHR 0x2031 +#define CL_CONTEXT_TERMINATE_KHR 0x2032 + +#define cl_khr_terminate_context 1 +extern CL_API_ENTRY cl_int CL_API_CALL clTerminateContextKHR(cl_context /* context */) CL_EXT_SUFFIX__VERSION_1_2; + +typedef CL_API_ENTRY cl_int (CL_API_CALL *clTerminateContextKHR_fn)(cl_context /* context */) CL_EXT_SUFFIX__VERSION_1_2; + + +/* + * Extension: cl_khr_spir + * + * This extension adds support to create an OpenCL program object from a + * Standard Portable Intermediate Representation (SPIR) instance + */ + +#define CL_DEVICE_SPIR_VERSIONS 0x40E0 +#define CL_PROGRAM_BINARY_TYPE_INTERMEDIATE 0x40E1 + + +/****************************************** +* cl_nv_device_attribute_query extension * +******************************************/ +/* cl_nv_device_attribute_query extension - no extension #define since it has no functions */ +#define CL_DEVICE_COMPUTE_CAPABILITY_MAJOR_NV 0x4000 +#define CL_DEVICE_COMPUTE_CAPABILITY_MINOR_NV 0x4001 +#define CL_DEVICE_REGISTERS_PER_BLOCK_NV 0x4002 +#define CL_DEVICE_WARP_SIZE_NV 0x4003 +#define CL_DEVICE_GPU_OVERLAP_NV 0x4004 +#define CL_DEVICE_KERNEL_EXEC_TIMEOUT_NV 0x4005 +#define CL_DEVICE_INTEGRATED_MEMORY_NV 0x4006 + +/********************************* +* cl_amd_device_attribute_query * +*********************************/ +#define CL_DEVICE_PROFILING_TIMER_OFFSET_AMD 0x4036 + +/********************************* +* cl_arm_printf extension +*********************************/ +#define CL_PRINTF_CALLBACK_ARM 0x40B0 +#define CL_PRINTF_BUFFERSIZE_ARM 0x40B1 + +#ifdef CL_VERSION_1_1 + /*********************************** + * cl_ext_device_fission extension * + ***********************************/ + #define cl_ext_device_fission 1 + + extern CL_API_ENTRY cl_int CL_API_CALL + clReleaseDeviceEXT( cl_device_id /*device*/ ) CL_EXT_SUFFIX__VERSION_1_1; + + typedef CL_API_ENTRY cl_int + (CL_API_CALL *clReleaseDeviceEXT_fn)( cl_device_id /*device*/ ) CL_EXT_SUFFIX__VERSION_1_1; + + extern CL_API_ENTRY cl_int CL_API_CALL + clRetainDeviceEXT( cl_device_id /*device*/ ) CL_EXT_SUFFIX__VERSION_1_1; + + typedef CL_API_ENTRY cl_int + (CL_API_CALL *clRetainDeviceEXT_fn)( cl_device_id /*device*/ ) CL_EXT_SUFFIX__VERSION_1_1; + + typedef cl_ulong cl_device_partition_property_ext; + extern CL_API_ENTRY cl_int CL_API_CALL + clCreateSubDevicesEXT( cl_device_id /*in_device*/, + const cl_device_partition_property_ext * /* properties */, + cl_uint /*num_entries*/, + cl_device_id * /*out_devices*/, + cl_uint * /*num_devices*/ ) CL_EXT_SUFFIX__VERSION_1_1; + + typedef CL_API_ENTRY cl_int + ( CL_API_CALL * clCreateSubDevicesEXT_fn)( cl_device_id /*in_device*/, + const cl_device_partition_property_ext * /* properties */, + cl_uint /*num_entries*/, + cl_device_id * /*out_devices*/, + cl_uint * /*num_devices*/ ) CL_EXT_SUFFIX__VERSION_1_1; + + /* cl_device_partition_property_ext */ + #define CL_DEVICE_PARTITION_EQUALLY_EXT 0x4050 + #define CL_DEVICE_PARTITION_BY_COUNTS_EXT 0x4051 + #define CL_DEVICE_PARTITION_BY_NAMES_EXT 0x4052 + #define CL_DEVICE_PARTITION_BY_AFFINITY_DOMAIN_EXT 0x4053 + + /* clDeviceGetInfo selectors */ + #define CL_DEVICE_PARENT_DEVICE_EXT 0x4054 + #define CL_DEVICE_PARTITION_TYPES_EXT 0x4055 + #define CL_DEVICE_AFFINITY_DOMAINS_EXT 0x4056 + #define CL_DEVICE_REFERENCE_COUNT_EXT 0x4057 + #define CL_DEVICE_PARTITION_STYLE_EXT 0x4058 + + /* error codes */ + #define CL_DEVICE_PARTITION_FAILED_EXT -1057 + #define CL_INVALID_PARTITION_COUNT_EXT -1058 + #define CL_INVALID_PARTITION_NAME_EXT -1059 + + /* CL_AFFINITY_DOMAINs */ + #define CL_AFFINITY_DOMAIN_L1_CACHE_EXT 0x1 + #define CL_AFFINITY_DOMAIN_L2_CACHE_EXT 0x2 + #define CL_AFFINITY_DOMAIN_L3_CACHE_EXT 0x3 + #define CL_AFFINITY_DOMAIN_L4_CACHE_EXT 0x4 + #define CL_AFFINITY_DOMAIN_NUMA_EXT 0x10 + #define CL_AFFINITY_DOMAIN_NEXT_FISSIONABLE_EXT 0x100 + + /* cl_device_partition_property_ext list terminators */ + #define CL_PROPERTIES_LIST_END_EXT ((cl_device_partition_property_ext) 0) + #define CL_PARTITION_BY_COUNTS_LIST_END_EXT ((cl_device_partition_property_ext) 0) + #define CL_PARTITION_BY_NAMES_LIST_END_EXT ((cl_device_partition_property_ext) 0 - 1) + +/********************************* +* cl_qcom_ext_host_ptr extension +*********************************/ + +#define CL_MEM_EXT_HOST_PTR_QCOM (1 << 29) + +#define CL_DEVICE_EXT_MEM_PADDING_IN_BYTES_QCOM 0x40A0 +#define CL_DEVICE_PAGE_SIZE_QCOM 0x40A1 +#define CL_IMAGE_ROW_ALIGNMENT_QCOM 0x40A2 +#define CL_IMAGE_SLICE_ALIGNMENT_QCOM 0x40A3 +#define CL_MEM_HOST_UNCACHED_QCOM 0x40A4 +#define CL_MEM_HOST_WRITEBACK_QCOM 0x40A5 +#define CL_MEM_HOST_WRITETHROUGH_QCOM 0x40A6 +#define CL_MEM_HOST_WRITE_COMBINING_QCOM 0x40A7 + +typedef cl_uint cl_image_pitch_info_qcom; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetDeviceImageInfoQCOM(cl_device_id device, + size_t image_width, + size_t image_height, + const cl_image_format *image_format, + cl_image_pitch_info_qcom param_name, + size_t param_value_size, + void *param_value, + size_t *param_value_size_ret); + +typedef struct _cl_mem_ext_host_ptr +{ + /* Type of external memory allocation. */ + /* Legal values will be defined in layered extensions. */ + cl_uint allocation_type; + + /* Host cache policy for this external memory allocation. */ + cl_uint host_cache_policy; + +} cl_mem_ext_host_ptr; + +/********************************* +* cl_qcom_ion_host_ptr extension +*********************************/ + +#define CL_MEM_ION_HOST_PTR_QCOM 0x40A8 + +typedef struct _cl_mem_ion_host_ptr +{ + /* Type of external memory allocation. */ + /* Must be CL_MEM_ION_HOST_PTR_QCOM for ION allocations. */ + cl_mem_ext_host_ptr ext_host_ptr; + + /* ION file descriptor */ + int ion_filedesc; + + /* Host pointer to the ION allocated memory */ + void* ion_hostptr; + +} cl_mem_ion_host_ptr; + +#endif /* CL_VERSION_1_1 */ + + +#ifdef CL_VERSION_2_0 +/********************************* +* cl_khr_sub_groups extension +*********************************/ +#define cl_khr_sub_groups 1 + +typedef cl_uint cl_kernel_sub_group_info_khr; + +/* cl_khr_sub_group_info */ +#define CL_KERNEL_MAX_SUB_GROUP_SIZE_FOR_NDRANGE_KHR 0x2033 +#define CL_KERNEL_SUB_GROUP_COUNT_FOR_NDRANGE_KHR 0x2034 + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetKernelSubGroupInfoKHR(cl_kernel /* in_kernel */, + cl_device_id /*in_device*/, + cl_kernel_sub_group_info_khr /* param_name */, + size_t /*input_value_size*/, + const void * /*input_value*/, + size_t /*param_value_size*/, + void* /*param_value*/, + size_t* /*param_value_size_ret*/ ) CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED; + +typedef CL_API_ENTRY cl_int + ( CL_API_CALL * clGetKernelSubGroupInfoKHR_fn)(cl_kernel /* in_kernel */, + cl_device_id /*in_device*/, + cl_kernel_sub_group_info_khr /* param_name */, + size_t /*input_value_size*/, + const void * /*input_value*/, + size_t /*param_value_size*/, + void* /*param_value*/, + size_t* /*param_value_size_ret*/ ) CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED; +#endif /* CL_VERSION_2_0 */ + +#ifdef CL_VERSION_2_1 +/********************************* +* cl_khr_priority_hints extension +*********************************/ +#define cl_khr_priority_hints 1 + +typedef cl_uint cl_queue_priority_khr; + +/* cl_command_queue_properties */ +#define CL_QUEUE_PRIORITY_KHR 0x1096 + +/* cl_queue_priority_khr */ +#define CL_QUEUE_PRIORITY_HIGH_KHR (1<<0) +#define CL_QUEUE_PRIORITY_MED_KHR (1<<1) +#define CL_QUEUE_PRIORITY_LOW_KHR (1<<2) + +#endif /* CL_VERSION_2_1 */ + +#ifdef CL_VERSION_2_1 +/********************************* +* cl_khr_throttle_hints extension +*********************************/ +#define cl_khr_throttle_hints 1 + +typedef cl_uint cl_queue_throttle_khr; + +/* cl_command_queue_properties */ +#define CL_QUEUE_THROTTLE_KHR 0x1097 + +/* cl_queue_throttle_khr */ +#define CL_QUEUE_THROTTLE_HIGH_KHR (1<<0) +#define CL_QUEUE_THROTTLE_MED_KHR (1<<1) +#define CL_QUEUE_THROTTLE_LOW_KHR (1<<2) + +#endif /* CL_VERSION_2_1 */ + +#ifdef __cplusplus +} +#endif + + +#endif /* __CL_EXT_H */ diff --git a/third_party/opencl/include/CL/cl_ext_qcom.h b/third_party/opencl/include/CL/cl_ext_qcom.h new file mode 100644 index 000000000..6328a1cd9 --- /dev/null +++ b/third_party/opencl/include/CL/cl_ext_qcom.h @@ -0,0 +1,255 @@ +/* Copyright (c) 2009-2017 Qualcomm Technologies, Inc. All Rights Reserved. + * Qualcomm Technologies Proprietary and Confidential. + */ + +#ifndef __OPENCL_CL_EXT_QCOM_H +#define __OPENCL_CL_EXT_QCOM_H + +// Needed by cl_khr_egl_event extension +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +/************************************ + * cl_qcom_create_buffer_from_image * + ************************************/ + +#define CL_BUFFER_FROM_IMAGE_ROW_PITCH_QCOM 0x40C0 +#define CL_BUFFER_FROM_IMAGE_SLICE_PITCH_QCOM 0x40C1 + +extern CL_API_ENTRY cl_mem CL_API_CALL +clCreateBufferFromImageQCOM(cl_mem image, + cl_mem_flags flags, + cl_int *errcode_ret); + + +/************************************ + * cl_qcom_limited_printf extension * + ************************************/ + +/* Builtin printf function buffer size in bytes. */ +#define CL_DEVICE_PRINTF_BUFFER_SIZE_QCOM 0x1049 + + +/************************************* + * cl_qcom_extended_images extension * + *************************************/ + +#define CL_CONTEXT_ENABLE_EXTENDED_IMAGES_QCOM 0x40AA +#define CL_DEVICE_EXTENDED_IMAGE2D_MAX_WIDTH_QCOM 0x40AB +#define CL_DEVICE_EXTENDED_IMAGE2D_MAX_HEIGHT_QCOM 0x40AC +#define CL_DEVICE_EXTENDED_IMAGE3D_MAX_WIDTH_QCOM 0x40AD +#define CL_DEVICE_EXTENDED_IMAGE3D_MAX_HEIGHT_QCOM 0x40AE +#define CL_DEVICE_EXTENDED_IMAGE3D_MAX_DEPTH_QCOM 0x40AF + +/************************************* + * cl_qcom_perf_hint extension * + *************************************/ + +typedef cl_uint cl_perf_hint; + +#define CL_CONTEXT_PERF_HINT_QCOM 0x40C2 + +/*cl_perf_hint*/ +#define CL_PERF_HINT_HIGH_QCOM 0x40C3 +#define CL_PERF_HINT_NORMAL_QCOM 0x40C4 +#define CL_PERF_HINT_LOW_QCOM 0x40C5 + +extern CL_API_ENTRY cl_int CL_API_CALL +clSetPerfHintQCOM(cl_context context, + cl_perf_hint perf_hint); + +// This extension is published at Khronos, so its definitions are made in cl_ext.h. +// This duplication is for backward compatibility. + +#ifndef CL_MEM_ANDROID_NATIVE_BUFFER_HOST_PTR_QCOM + +/********************************* +* cl_qcom_android_native_buffer_host_ptr extension +*********************************/ + +#define CL_MEM_ANDROID_NATIVE_BUFFER_HOST_PTR_QCOM 0x40C6 + + +typedef struct _cl_mem_android_native_buffer_host_ptr +{ + // Type of external memory allocation. + // Must be CL_MEM_ANDROID_NATIVE_BUFFER_HOST_PTR_QCOM for Android native buffers. + cl_mem_ext_host_ptr ext_host_ptr; + + // Virtual pointer to the android native buffer + void* anb_ptr; + +} cl_mem_android_native_buffer_host_ptr; + +#endif //#ifndef CL_MEM_ANDROID_NATIVE_BUFFER_HOST_PTR_QCOM + +/*********************************** +* cl_img_egl_image extension * +************************************/ +typedef void* CLeglImageIMG; +typedef void* CLeglDisplayIMG; + +extern CL_API_ENTRY cl_mem CL_API_CALL +clCreateFromEGLImageIMG(cl_context context, + cl_mem_flags flags, + CLeglImageIMG image, + CLeglDisplayIMG display, + cl_int *errcode_ret); + + +/********************************* +* cl_qcom_other_image extension +*********************************/ + +// Extended flag for creating/querying QCOM non-standard images +#define CL_MEM_OTHER_IMAGE_QCOM (1<<25) + +// cl_channel_type +#define CL_QCOM_UNORM_MIPI10 0x4159 +#define CL_QCOM_UNORM_MIPI12 0x415A +#define CL_QCOM_UNSIGNED_MIPI10 0x415B +#define CL_QCOM_UNSIGNED_MIPI12 0x415C +#define CL_QCOM_UNORM_INT10 0x415D +#define CL_QCOM_UNORM_INT12 0x415E +#define CL_QCOM_UNSIGNED_INT16 0x415F + +// cl_channel_order +// Dedicate 0x4130-0x415F range for QCOM extended image formats +// 0x4130 - 0x4132 range is assigned to pixel-oriented compressed format +#define CL_QCOM_BAYER 0x414E + +#define CL_QCOM_NV12 0x4133 +#define CL_QCOM_NV12_Y 0x4134 +#define CL_QCOM_NV12_UV 0x4135 + +#define CL_QCOM_TILED_NV12 0x4136 +#define CL_QCOM_TILED_NV12_Y 0x4137 +#define CL_QCOM_TILED_NV12_UV 0x4138 + +#define CL_QCOM_P010 0x413C +#define CL_QCOM_P010_Y 0x413D +#define CL_QCOM_P010_UV 0x413E + +#define CL_QCOM_TILED_P010 0x413F +#define CL_QCOM_TILED_P010_Y 0x4140 +#define CL_QCOM_TILED_P010_UV 0x4141 + + +#define CL_QCOM_TP10 0x4145 +#define CL_QCOM_TP10_Y 0x4146 +#define CL_QCOM_TP10_UV 0x4147 + +#define CL_QCOM_TILED_TP10 0x4148 +#define CL_QCOM_TILED_TP10_Y 0x4149 +#define CL_QCOM_TILED_TP10_UV 0x414A + +/********************************* +* cl_qcom_compressed_image extension +*********************************/ + +// Extended flag for creating/querying QCOM non-planar compressed images +#define CL_MEM_COMPRESSED_IMAGE_QCOM (1<<27) + +// Extended image format +// cl_channel_order +#define CL_QCOM_COMPRESSED_RGBA 0x4130 +#define CL_QCOM_COMPRESSED_RGBx 0x4131 + +#define CL_QCOM_COMPRESSED_NV12_Y 0x413A +#define CL_QCOM_COMPRESSED_NV12_UV 0x413B + +#define CL_QCOM_COMPRESSED_P010 0x4142 +#define CL_QCOM_COMPRESSED_P010_Y 0x4143 +#define CL_QCOM_COMPRESSED_P010_UV 0x4144 + +#define CL_QCOM_COMPRESSED_TP10 0x414B +#define CL_QCOM_COMPRESSED_TP10_Y 0x414C +#define CL_QCOM_COMPRESSED_TP10_UV 0x414D + +#define CL_QCOM_COMPRESSED_NV12_4R 0x414F +#define CL_QCOM_COMPRESSED_NV12_4R_Y 0x4150 +#define CL_QCOM_COMPRESSED_NV12_4R_UV 0x4151 +/********************************* +* cl_qcom_compressed_yuv_image_read extension +*********************************/ + +// Extended flag for creating/querying QCOM compressed images +#define CL_MEM_COMPRESSED_YUV_IMAGE_QCOM (1<<28) + +// Extended image format +#define CL_QCOM_COMPRESSED_NV12 0x10C4 + +// Extended flag for setting ION buffer allocation type +#define CL_MEM_ION_HOST_PTR_COMPRESSED_YUV_QCOM 0x40CD +#define CL_MEM_ION_HOST_PTR_PROTECTED_COMPRESSED_YUV_QCOM 0x40CE + +/********************************* +* cl_qcom_accelerated_image_ops +*********************************/ +#define CL_MEM_OBJECT_WEIGHT_IMAGE_QCOM 0x4110 +#define CL_DEVICE_HOF_MAX_NUM_PHASES_QCOM 0x4111 +#define CL_DEVICE_HOF_MAX_FILTER_SIZE_X_QCOM 0x4112 +#define CL_DEVICE_HOF_MAX_FILTER_SIZE_Y_QCOM 0x4113 +#define CL_DEVICE_BLOCK_MATCHING_MAX_REGION_SIZE_X_QCOM 0x4114 +#define CL_DEVICE_BLOCK_MATCHING_MAX_REGION_SIZE_Y_QCOM 0x4115 + +//Extended flag for specifying weight image type +#define CL_WEIGHT_IMAGE_SEPARABLE_QCOM (1<<0) + +// Box Filter +typedef struct _cl_box_filter_size_qcom +{ + // Width of box filter on X direction. + float box_filter_width; + + // Height of box filter on Y direction. + float box_filter_height; +} cl_box_filter_size_qcom; + +// HOF Weight Image Desc +typedef struct _cl_weight_desc_qcom +{ + /** Coordinate of the "center" point of the weight image, + based on the weight image's top-left corner as the origin. */ + size_t center_coord_x; + size_t center_coord_y; + cl_bitfield flags; +} cl_weight_desc_qcom; + +typedef struct _cl_weight_image_desc_qcom +{ + cl_image_desc image_desc; + cl_weight_desc_qcom weight_desc; +} cl_weight_image_desc_qcom; + +/************************************* + * cl_qcom_protected_context extension * + *************************************/ + +#define CL_CONTEXT_PROTECTED_QCOM 0x40C7 +#define CL_MEM_ION_HOST_PTR_PROTECTED_QCOM 0x40C8 + +/************************************* + * cl_qcom_priority_hint extension * + *************************************/ +#define CL_PRIORITY_HINT_NONE_QCOM 0 +typedef cl_uint cl_priority_hint; + +#define CL_CONTEXT_PRIORITY_HINT_QCOM 0x40C9 + +/*cl_priority_hint*/ +#define CL_PRIORITY_HINT_HIGH_QCOM 0x40CA +#define CL_PRIORITY_HINT_NORMAL_QCOM 0x40CB +#define CL_PRIORITY_HINT_LOW_QCOM 0x40CC + +#ifdef __cplusplus +} +#endif + +#endif /* __OPENCL_CL_EXT_QCOM_H */ diff --git a/third_party/opencl/include/CL/cl_gl.h b/third_party/opencl/include/CL/cl_gl.h new file mode 100644 index 000000000..945daa83d --- /dev/null +++ b/third_party/opencl/include/CL/cl_gl.h @@ -0,0 +1,167 @@ +/********************************************************************************** + * Copyright (c) 2008-2015 The Khronos Group Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and/or associated documentation files (the + * "Materials"), to deal in the Materials without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Materials, and to + * permit persons to whom the Materials are furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Materials. + * + * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS + * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS + * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT + * https://www.khronos.org/registry/ + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. + **********************************************************************************/ + +#ifndef __OPENCL_CL_GL_H +#define __OPENCL_CL_GL_H + +#ifdef __APPLE__ +#include +#else +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef cl_uint cl_gl_object_type; +typedef cl_uint cl_gl_texture_info; +typedef cl_uint cl_gl_platform_info; +typedef struct __GLsync *cl_GLsync; + +/* cl_gl_object_type = 0x2000 - 0x200F enum values are currently taken */ +#define CL_GL_OBJECT_BUFFER 0x2000 +#define CL_GL_OBJECT_TEXTURE2D 0x2001 +#define CL_GL_OBJECT_TEXTURE3D 0x2002 +#define CL_GL_OBJECT_RENDERBUFFER 0x2003 +#define CL_GL_OBJECT_TEXTURE2D_ARRAY 0x200E +#define CL_GL_OBJECT_TEXTURE1D 0x200F +#define CL_GL_OBJECT_TEXTURE1D_ARRAY 0x2010 +#define CL_GL_OBJECT_TEXTURE_BUFFER 0x2011 + +/* cl_gl_texture_info */ +#define CL_GL_TEXTURE_TARGET 0x2004 +#define CL_GL_MIPMAP_LEVEL 0x2005 +#define CL_GL_NUM_SAMPLES 0x2012 + + +extern CL_API_ENTRY cl_mem CL_API_CALL +clCreateFromGLBuffer(cl_context /* context */, + cl_mem_flags /* flags */, + cl_GLuint /* bufobj */, + int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_mem CL_API_CALL +clCreateFromGLTexture(cl_context /* context */, + cl_mem_flags /* flags */, + cl_GLenum /* target */, + cl_GLint /* miplevel */, + cl_GLuint /* texture */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_2; + +extern CL_API_ENTRY cl_mem CL_API_CALL +clCreateFromGLRenderbuffer(cl_context /* context */, + cl_mem_flags /* flags */, + cl_GLuint /* renderbuffer */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetGLObjectInfo(cl_mem /* memobj */, + cl_gl_object_type * /* gl_object_type */, + cl_GLuint * /* gl_object_name */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetGLTextureInfo(cl_mem /* memobj */, + cl_gl_texture_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueAcquireGLObjects(cl_command_queue /* command_queue */, + cl_uint /* num_objects */, + const cl_mem * /* mem_objects */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueReleaseGLObjects(cl_command_queue /* command_queue */, + cl_uint /* num_objects */, + const cl_mem * /* mem_objects */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + + +/* Deprecated OpenCL 1.1 APIs */ +extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_mem CL_API_CALL +clCreateFromGLTexture2D(cl_context /* context */, + cl_mem_flags /* flags */, + cl_GLenum /* target */, + cl_GLint /* miplevel */, + cl_GLuint /* texture */, + cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; + +extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_mem CL_API_CALL +clCreateFromGLTexture3D(cl_context /* context */, + cl_mem_flags /* flags */, + cl_GLenum /* target */, + cl_GLint /* miplevel */, + cl_GLuint /* texture */, + cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; + +/* cl_khr_gl_sharing extension */ + +#define cl_khr_gl_sharing 1 + +typedef cl_uint cl_gl_context_info; + +/* Additional Error Codes */ +#define CL_INVALID_GL_SHAREGROUP_REFERENCE_KHR -1000 + +/* cl_gl_context_info */ +#define CL_CURRENT_DEVICE_FOR_GL_CONTEXT_KHR 0x2006 +#define CL_DEVICES_FOR_GL_CONTEXT_KHR 0x2007 + +/* Additional cl_context_properties */ +#define CL_GL_CONTEXT_KHR 0x2008 +#define CL_EGL_DISPLAY_KHR 0x2009 +#define CL_GLX_DISPLAY_KHR 0x200A +#define CL_WGL_HDC_KHR 0x200B +#define CL_CGL_SHAREGROUP_KHR 0x200C + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetGLContextInfoKHR(const cl_context_properties * /* properties */, + cl_gl_context_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +typedef CL_API_ENTRY cl_int (CL_API_CALL *clGetGLContextInfoKHR_fn)( + const cl_context_properties * properties, + cl_gl_context_info param_name, + size_t param_value_size, + void * param_value, + size_t * param_value_size_ret); + +#ifdef __cplusplus +} +#endif + +#endif /* __OPENCL_CL_GL_H */ diff --git a/third_party/opencl/include/CL/cl_gl_ext.h b/third_party/opencl/include/CL/cl_gl_ext.h new file mode 100644 index 000000000..e3c14c640 --- /dev/null +++ b/third_party/opencl/include/CL/cl_gl_ext.h @@ -0,0 +1,74 @@ +/********************************************************************************** + * Copyright (c) 2008-2015 The Khronos Group Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and/or associated documentation files (the + * "Materials"), to deal in the Materials without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Materials, and to + * permit persons to whom the Materials are furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Materials. + * + * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS + * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS + * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT + * https://www.khronos.org/registry/ + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. + **********************************************************************************/ + +/* $Revision: 11708 $ on $Date: 2010-06-13 23:36:24 -0700 (Sun, 13 Jun 2010) $ */ + +/* cl_gl_ext.h contains vendor (non-KHR) OpenCL extensions which have */ +/* OpenGL dependencies. */ + +#ifndef __OPENCL_CL_GL_EXT_H +#define __OPENCL_CL_GL_EXT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __APPLE__ + #include +#else + #include +#endif + +/* + * For each extension, follow this template + * cl_VEN_extname extension */ +/* #define cl_VEN_extname 1 + * ... define new types, if any + * ... define new tokens, if any + * ... define new APIs, if any + * + * If you need GLtypes here, mirror them with a cl_GLtype, rather than including a GL header + * This allows us to avoid having to decide whether to include GL headers or GLES here. + */ + +/* + * cl_khr_gl_event extension + * See section 9.9 in the OpenCL 1.1 spec for more information + */ +#define CL_COMMAND_GL_FENCE_SYNC_OBJECT_KHR 0x200D + +extern CL_API_ENTRY cl_event CL_API_CALL +clCreateEventFromGLsyncKHR(cl_context /* context */, + cl_GLsync /* cl_GLsync */, + cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_1; + +#ifdef __cplusplus +} +#endif + +#endif /* __OPENCL_CL_GL_EXT_H */ diff --git a/third_party/opencl/include/CL/cl_platform.h b/third_party/opencl/include/CL/cl_platform.h new file mode 100644 index 000000000..4e334a291 --- /dev/null +++ b/third_party/opencl/include/CL/cl_platform.h @@ -0,0 +1,1333 @@ +/********************************************************************************** + * Copyright (c) 2008-2015 The Khronos Group Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and/or associated documentation files (the + * "Materials"), to deal in the Materials without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Materials, and to + * permit persons to whom the Materials are furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Materials. + * + * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS + * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS + * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT + * https://www.khronos.org/registry/ + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. + **********************************************************************************/ + +/* $Revision: 11803 $ on $Date: 2010-06-25 10:02:12 -0700 (Fri, 25 Jun 2010) $ */ + +#ifndef __CL_PLATFORM_H +#define __CL_PLATFORM_H + +#ifdef __APPLE__ + /* Contains #defines for AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER below */ + #include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(_WIN32) + #define CL_API_ENTRY + #define CL_API_CALL __stdcall + #define CL_CALLBACK __stdcall +#else + #define CL_API_ENTRY + #define CL_API_CALL + #define CL_CALLBACK +#endif + +/* + * Deprecation flags refer to the last version of the header in which the + * feature was not deprecated. + * + * E.g. VERSION_1_1_DEPRECATED means the feature is present in 1.1 without + * deprecation but is deprecated in versions later than 1.1. + */ + +#ifdef __APPLE__ + #define CL_EXTENSION_WEAK_LINK __attribute__((weak_import)) + #define CL_API_SUFFIX__VERSION_1_0 AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER + #define CL_EXT_SUFFIX__VERSION_1_0 CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER + #define CL_API_SUFFIX__VERSION_1_1 AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER + #define GCL_API_SUFFIX__VERSION_1_1 AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER + #define CL_EXT_SUFFIX__VERSION_1_1 CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER + #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_7 + + #ifdef AVAILABLE_MAC_OS_X_VERSION_10_8_AND_LATER + #define CL_API_SUFFIX__VERSION_1_2 AVAILABLE_MAC_OS_X_VERSION_10_8_AND_LATER + #define GCL_API_SUFFIX__VERSION_1_2 AVAILABLE_MAC_OS_X_VERSION_10_8_AND_LATER + #define CL_EXT_SUFFIX__VERSION_1_2 CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_8_AND_LATER + #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED + #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_8 + #else + #warning This path should never happen outside of internal operating system development. AvailabilityMacros do not function correctly here! + #define CL_API_SUFFIX__VERSION_1_2 AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER + #define GCL_API_SUFFIX__VERSION_1_2 AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER + #define CL_EXT_SUFFIX__VERSION_1_2 CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER + #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER + #endif +#else + #define CL_EXTENSION_WEAK_LINK + #define CL_API_SUFFIX__VERSION_1_0 + #define CL_EXT_SUFFIX__VERSION_1_0 + #define CL_API_SUFFIX__VERSION_1_1 + #define CL_EXT_SUFFIX__VERSION_1_1 + #define CL_API_SUFFIX__VERSION_1_2 + #define CL_EXT_SUFFIX__VERSION_1_2 + #define CL_API_SUFFIX__VERSION_2_0 + #define CL_EXT_SUFFIX__VERSION_2_0 + #define CL_API_SUFFIX__VERSION_2_1 + #define CL_EXT_SUFFIX__VERSION_2_1 + + #ifdef __GNUC__ + #ifdef CL_USE_DEPRECATED_OPENCL_1_0_APIS + #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED + #define CL_EXT_PREFIX__VERSION_1_0_DEPRECATED + #else + #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED __attribute__((deprecated)) + #define CL_EXT_PREFIX__VERSION_1_0_DEPRECATED + #endif + + #ifdef CL_USE_DEPRECATED_OPENCL_1_1_APIS + #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED + #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED + #else + #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED __attribute__((deprecated)) + #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED + #endif + + #ifdef CL_USE_DEPRECATED_OPENCL_1_2_APIS + #define CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED + #define CL_EXT_PREFIX__VERSION_1_2_DEPRECATED + #else + #define CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED __attribute__((deprecated)) + #define CL_EXT_PREFIX__VERSION_1_2_DEPRECATED + #endif + + #ifdef CL_USE_DEPRECATED_OPENCL_2_0_APIS + #define CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED + #define CL_EXT_PREFIX__VERSION_2_0_DEPRECATED + #else + #define CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED __attribute__((deprecated)) + #define CL_EXT_PREFIX__VERSION_2_0_DEPRECATED + #endif + #elif _WIN32 + #ifdef CL_USE_DEPRECATED_OPENCL_1_0_APIS + #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED + #define CL_EXT_PREFIX__VERSION_1_0_DEPRECATED + #else + #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED + #define CL_EXT_PREFIX__VERSION_1_0_DEPRECATED __declspec(deprecated) + #endif + + #ifdef CL_USE_DEPRECATED_OPENCL_1_1_APIS + #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED + #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED + #else + #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED + #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED __declspec(deprecated) + #endif + + #ifdef CL_USE_DEPRECATED_OPENCL_1_2_APIS + #define CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED + #define CL_EXT_PREFIX__VERSION_1_2_DEPRECATED + #else + #define CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED + #define CL_EXT_PREFIX__VERSION_1_2_DEPRECATED __declspec(deprecated) + #endif + + #ifdef CL_USE_DEPRECATED_OPENCL_2_0_APIS + #define CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED + #define CL_EXT_PREFIX__VERSION_2_0_DEPRECATED + #else + #define CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED + #define CL_EXT_PREFIX__VERSION_2_0_DEPRECATED __declspec(deprecated) + #endif + #else + #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED + #define CL_EXT_PREFIX__VERSION_1_0_DEPRECATED + + #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED + #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED + + #define CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED + #define CL_EXT_PREFIX__VERSION_1_2_DEPRECATED + + #define CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED + #define CL_EXT_PREFIX__VERSION_2_0_DEPRECATED + #endif +#endif + +#if (defined (_WIN32) && defined(_MSC_VER)) + +/* scalar types */ +typedef signed __int8 cl_char; +typedef unsigned __int8 cl_uchar; +typedef signed __int16 cl_short; +typedef unsigned __int16 cl_ushort; +typedef signed __int32 cl_int; +typedef unsigned __int32 cl_uint; +typedef signed __int64 cl_long; +typedef unsigned __int64 cl_ulong; + +typedef unsigned __int16 cl_half; +typedef float cl_float; +typedef double cl_double; + +/* Macro names and corresponding values defined by OpenCL */ +#define CL_CHAR_BIT 8 +#define CL_SCHAR_MAX 127 +#define CL_SCHAR_MIN (-127-1) +#define CL_CHAR_MAX CL_SCHAR_MAX +#define CL_CHAR_MIN CL_SCHAR_MIN +#define CL_UCHAR_MAX 255 +#define CL_SHRT_MAX 32767 +#define CL_SHRT_MIN (-32767-1) +#define CL_USHRT_MAX 65535 +#define CL_INT_MAX 2147483647 +#define CL_INT_MIN (-2147483647-1) +#define CL_UINT_MAX 0xffffffffU +#define CL_LONG_MAX ((cl_long) 0x7FFFFFFFFFFFFFFFLL) +#define CL_LONG_MIN ((cl_long) -0x7FFFFFFFFFFFFFFFLL - 1LL) +#define CL_ULONG_MAX ((cl_ulong) 0xFFFFFFFFFFFFFFFFULL) + +#define CL_FLT_DIG 6 +#define CL_FLT_MANT_DIG 24 +#define CL_FLT_MAX_10_EXP +38 +#define CL_FLT_MAX_EXP +128 +#define CL_FLT_MIN_10_EXP -37 +#define CL_FLT_MIN_EXP -125 +#define CL_FLT_RADIX 2 +#define CL_FLT_MAX 340282346638528859811704183484516925440.0f +#define CL_FLT_MIN 1.175494350822287507969e-38f +#define CL_FLT_EPSILON 0x1.0p-23f + +#define CL_DBL_DIG 15 +#define CL_DBL_MANT_DIG 53 +#define CL_DBL_MAX_10_EXP +308 +#define CL_DBL_MAX_EXP +1024 +#define CL_DBL_MIN_10_EXP -307 +#define CL_DBL_MIN_EXP -1021 +#define CL_DBL_RADIX 2 +#define CL_DBL_MAX 179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.0 +#define CL_DBL_MIN 2.225073858507201383090e-308 +#define CL_DBL_EPSILON 2.220446049250313080847e-16 + +#define CL_M_E 2.718281828459045090796 +#define CL_M_LOG2E 1.442695040888963387005 +#define CL_M_LOG10E 0.434294481903251816668 +#define CL_M_LN2 0.693147180559945286227 +#define CL_M_LN10 2.302585092994045901094 +#define CL_M_PI 3.141592653589793115998 +#define CL_M_PI_2 1.570796326794896557999 +#define CL_M_PI_4 0.785398163397448278999 +#define CL_M_1_PI 0.318309886183790691216 +#define CL_M_2_PI 0.636619772367581382433 +#define CL_M_2_SQRTPI 1.128379167095512558561 +#define CL_M_SQRT2 1.414213562373095145475 +#define CL_M_SQRT1_2 0.707106781186547572737 + +#define CL_M_E_F 2.71828174591064f +#define CL_M_LOG2E_F 1.44269502162933f +#define CL_M_LOG10E_F 0.43429449200630f +#define CL_M_LN2_F 0.69314718246460f +#define CL_M_LN10_F 2.30258512496948f +#define CL_M_PI_F 3.14159274101257f +#define CL_M_PI_2_F 1.57079637050629f +#define CL_M_PI_4_F 0.78539818525314f +#define CL_M_1_PI_F 0.31830987334251f +#define CL_M_2_PI_F 0.63661974668503f +#define CL_M_2_SQRTPI_F 1.12837922573090f +#define CL_M_SQRT2_F 1.41421353816986f +#define CL_M_SQRT1_2_F 0.70710676908493f + +#define CL_NAN (CL_INFINITY - CL_INFINITY) +#define CL_HUGE_VALF ((cl_float) 1e50) +#define CL_HUGE_VAL ((cl_double) 1e500) +#define CL_MAXFLOAT CL_FLT_MAX +#define CL_INFINITY CL_HUGE_VALF + +#else + +#include + +/* scalar types */ +typedef int8_t cl_char; +typedef uint8_t cl_uchar; +typedef int16_t cl_short __attribute__((aligned(2))); +typedef uint16_t cl_ushort __attribute__((aligned(2))); +typedef int32_t cl_int __attribute__((aligned(4))); +typedef uint32_t cl_uint __attribute__((aligned(4))); +typedef int64_t cl_long __attribute__((aligned(8))); +typedef uint64_t cl_ulong __attribute__((aligned(8))); + +typedef uint16_t cl_half __attribute__((aligned(2))); +typedef float cl_float __attribute__((aligned(4))); +typedef double cl_double __attribute__((aligned(8))); + +/* Macro names and corresponding values defined by OpenCL */ +#define CL_CHAR_BIT 8 +#define CL_SCHAR_MAX 127 +#define CL_SCHAR_MIN (-127-1) +#define CL_CHAR_MAX CL_SCHAR_MAX +#define CL_CHAR_MIN CL_SCHAR_MIN +#define CL_UCHAR_MAX 255 +#define CL_SHRT_MAX 32767 +#define CL_SHRT_MIN (-32767-1) +#define CL_USHRT_MAX 65535 +#define CL_INT_MAX 2147483647 +#define CL_INT_MIN (-2147483647-1) +#define CL_UINT_MAX 0xffffffffU +#define CL_LONG_MAX ((cl_long) 0x7FFFFFFFFFFFFFFFLL) +#define CL_LONG_MIN ((cl_long) -0x7FFFFFFFFFFFFFFFLL - 1LL) +#define CL_ULONG_MAX ((cl_ulong) 0xFFFFFFFFFFFFFFFFULL) + +#define CL_FLT_DIG 6 +#define CL_FLT_MANT_DIG 24 +#define CL_FLT_MAX_10_EXP +38 +#define CL_FLT_MAX_EXP +128 +#define CL_FLT_MIN_10_EXP -37 +#define CL_FLT_MIN_EXP -125 +#define CL_FLT_RADIX 2 +#define CL_FLT_MAX 0x1.fffffep127f +#define CL_FLT_MIN 0x1.0p-126f +#define CL_FLT_EPSILON 0x1.0p-23f + +#define CL_DBL_DIG 15 +#define CL_DBL_MANT_DIG 53 +#define CL_DBL_MAX_10_EXP +308 +#define CL_DBL_MAX_EXP +1024 +#define CL_DBL_MIN_10_EXP -307 +#define CL_DBL_MIN_EXP -1021 +#define CL_DBL_RADIX 2 +#define CL_DBL_MAX 0x1.fffffffffffffp1023 +#define CL_DBL_MIN 0x1.0p-1022 +#define CL_DBL_EPSILON 0x1.0p-52 + +#define CL_M_E 2.718281828459045090796 +#define CL_M_LOG2E 1.442695040888963387005 +#define CL_M_LOG10E 0.434294481903251816668 +#define CL_M_LN2 0.693147180559945286227 +#define CL_M_LN10 2.302585092994045901094 +#define CL_M_PI 3.141592653589793115998 +#define CL_M_PI_2 1.570796326794896557999 +#define CL_M_PI_4 0.785398163397448278999 +#define CL_M_1_PI 0.318309886183790691216 +#define CL_M_2_PI 0.636619772367581382433 +#define CL_M_2_SQRTPI 1.128379167095512558561 +#define CL_M_SQRT2 1.414213562373095145475 +#define CL_M_SQRT1_2 0.707106781186547572737 + +#define CL_M_E_F 2.71828174591064f +#define CL_M_LOG2E_F 1.44269502162933f +#define CL_M_LOG10E_F 0.43429449200630f +#define CL_M_LN2_F 0.69314718246460f +#define CL_M_LN10_F 2.30258512496948f +#define CL_M_PI_F 3.14159274101257f +#define CL_M_PI_2_F 1.57079637050629f +#define CL_M_PI_4_F 0.78539818525314f +#define CL_M_1_PI_F 0.31830987334251f +#define CL_M_2_PI_F 0.63661974668503f +#define CL_M_2_SQRTPI_F 1.12837922573090f +#define CL_M_SQRT2_F 1.41421353816986f +#define CL_M_SQRT1_2_F 0.70710676908493f + +#if defined( __GNUC__ ) + #define CL_HUGE_VALF __builtin_huge_valf() + #define CL_HUGE_VAL __builtin_huge_val() + #define CL_NAN __builtin_nanf( "" ) +#else + #define CL_HUGE_VALF ((cl_float) 1e50) + #define CL_HUGE_VAL ((cl_double) 1e500) + float nanf( const char * ); + #define CL_NAN nanf( "" ) +#endif +#define CL_MAXFLOAT CL_FLT_MAX +#define CL_INFINITY CL_HUGE_VALF + +#endif + +#include + +/* Mirror types to GL types. Mirror types allow us to avoid deciding which 87s to load based on whether we are using GL or GLES here. */ +typedef unsigned int cl_GLuint; +typedef int cl_GLint; +typedef unsigned int cl_GLenum; + +/* + * Vector types + * + * Note: OpenCL requires that all types be naturally aligned. + * This means that vector types must be naturally aligned. + * For example, a vector of four floats must be aligned to + * a 16 byte boundary (calculated as 4 * the natural 4-byte + * alignment of the float). The alignment qualifiers here + * will only function properly if your compiler supports them + * and if you don't actively work to defeat them. For example, + * in order for a cl_float4 to be 16 byte aligned in a struct, + * the start of the struct must itself be 16-byte aligned. + * + * Maintaining proper alignment is the user's responsibility. + */ + +/* Define basic vector types */ +#if defined( __VEC__ ) + #include /* may be omitted depending on compiler. AltiVec spec provides no way to detect whether the header is required. */ + typedef vector unsigned char __cl_uchar16; + typedef vector signed char __cl_char16; + typedef vector unsigned short __cl_ushort8; + typedef vector signed short __cl_short8; + typedef vector unsigned int __cl_uint4; + typedef vector signed int __cl_int4; + typedef vector float __cl_float4; + #define __CL_UCHAR16__ 1 + #define __CL_CHAR16__ 1 + #define __CL_USHORT8__ 1 + #define __CL_SHORT8__ 1 + #define __CL_UINT4__ 1 + #define __CL_INT4__ 1 + #define __CL_FLOAT4__ 1 +#endif + +#if defined( __SSE__ ) + #if defined( __MINGW64__ ) + #include + #else + #include + #endif + #if defined( __GNUC__ ) + typedef float __cl_float4 __attribute__((vector_size(16))); + #else + typedef __m128 __cl_float4; + #endif + #define __CL_FLOAT4__ 1 +#endif + +#if defined( __SSE2__ ) + #if defined( __MINGW64__ ) + #include + #else + #include + #endif + #if defined( __GNUC__ ) + typedef cl_uchar __cl_uchar16 __attribute__((vector_size(16))); + typedef cl_char __cl_char16 __attribute__((vector_size(16))); + typedef cl_ushort __cl_ushort8 __attribute__((vector_size(16))); + typedef cl_short __cl_short8 __attribute__((vector_size(16))); + typedef cl_uint __cl_uint4 __attribute__((vector_size(16))); + typedef cl_int __cl_int4 __attribute__((vector_size(16))); + typedef cl_ulong __cl_ulong2 __attribute__((vector_size(16))); + typedef cl_long __cl_long2 __attribute__((vector_size(16))); + typedef cl_double __cl_double2 __attribute__((vector_size(16))); + #else + typedef __m128i __cl_uchar16; + typedef __m128i __cl_char16; + typedef __m128i __cl_ushort8; + typedef __m128i __cl_short8; + typedef __m128i __cl_uint4; + typedef __m128i __cl_int4; + typedef __m128i __cl_ulong2; + typedef __m128i __cl_long2; + typedef __m128d __cl_double2; + #endif + #define __CL_UCHAR16__ 1 + #define __CL_CHAR16__ 1 + #define __CL_USHORT8__ 1 + #define __CL_SHORT8__ 1 + #define __CL_INT4__ 1 + #define __CL_UINT4__ 1 + #define __CL_ULONG2__ 1 + #define __CL_LONG2__ 1 + #define __CL_DOUBLE2__ 1 +#endif + +#if defined( __MMX__ ) + #include + #if defined( __GNUC__ ) + typedef cl_uchar __cl_uchar8 __attribute__((vector_size(8))); + typedef cl_char __cl_char8 __attribute__((vector_size(8))); + typedef cl_ushort __cl_ushort4 __attribute__((vector_size(8))); + typedef cl_short __cl_short4 __attribute__((vector_size(8))); + typedef cl_uint __cl_uint2 __attribute__((vector_size(8))); + typedef cl_int __cl_int2 __attribute__((vector_size(8))); + typedef cl_ulong __cl_ulong1 __attribute__((vector_size(8))); + typedef cl_long __cl_long1 __attribute__((vector_size(8))); + typedef cl_float __cl_float2 __attribute__((vector_size(8))); + #else + typedef __m64 __cl_uchar8; + typedef __m64 __cl_char8; + typedef __m64 __cl_ushort4; + typedef __m64 __cl_short4; + typedef __m64 __cl_uint2; + typedef __m64 __cl_int2; + typedef __m64 __cl_ulong1; + typedef __m64 __cl_long1; + typedef __m64 __cl_float2; + #endif + #define __CL_UCHAR8__ 1 + #define __CL_CHAR8__ 1 + #define __CL_USHORT4__ 1 + #define __CL_SHORT4__ 1 + #define __CL_INT2__ 1 + #define __CL_UINT2__ 1 + #define __CL_ULONG1__ 1 + #define __CL_LONG1__ 1 + #define __CL_FLOAT2__ 1 +#endif + +#if defined( __AVX__ ) + #if defined( __MINGW64__ ) + #include + #else + #include + #endif + #if defined( __GNUC__ ) + typedef cl_float __cl_float8 __attribute__((vector_size(32))); + typedef cl_double __cl_double4 __attribute__((vector_size(32))); + #else + typedef __m256 __cl_float8; + typedef __m256d __cl_double4; + #endif + #define __CL_FLOAT8__ 1 + #define __CL_DOUBLE4__ 1 +#endif + +/* Define capabilities for anonymous struct members. */ +#if defined( __GNUC__) && ! defined( __STRICT_ANSI__ ) +#define __CL_HAS_ANON_STRUCT__ 1 +#define __CL_ANON_STRUCT__ __extension__ +#elif defined( _WIN32) && (_MSC_VER >= 1500) + /* Microsoft Developer Studio 2008 supports anonymous structs, but + * complains by default. */ +#define __CL_HAS_ANON_STRUCT__ 1 +#define __CL_ANON_STRUCT__ + /* Disable warning C4201: nonstandard extension used : nameless + * struct/union */ +#pragma warning( push ) +#pragma warning( disable : 4201 ) +#else +#define __CL_HAS_ANON_STRUCT__ 0 +#define __CL_ANON_STRUCT__ +#endif + +/* Define alignment keys */ +#if defined( __GNUC__ ) + #define CL_ALIGNED(_x) __attribute__ ((aligned(_x))) +#elif defined( _WIN32) && (_MSC_VER) + /* Alignment keys neutered on windows because MSVC can't swallow function arguments with alignment requirements */ + /* http://msdn.microsoft.com/en-us/library/373ak2y1%28VS.71%29.aspx */ + /* #include */ + /* #define CL_ALIGNED(_x) _CRT_ALIGN(_x) */ + #define CL_ALIGNED(_x) +#else + #warning Need to implement some method to align data here + #define CL_ALIGNED(_x) +#endif + +/* Indicate whether .xyzw, .s0123 and .hi.lo are supported */ +#if __CL_HAS_ANON_STRUCT__ + /* .xyzw and .s0123...{f|F} are supported */ + #define CL_HAS_NAMED_VECTOR_FIELDS 1 + /* .hi and .lo are supported */ + #define CL_HAS_HI_LO_VECTOR_FIELDS 1 +#endif + +/* Define cl_vector types */ + +/* ---- cl_charn ---- */ +typedef union +{ + cl_char CL_ALIGNED(2) s[2]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_char x, y; }; + __CL_ANON_STRUCT__ struct{ cl_char s0, s1; }; + __CL_ANON_STRUCT__ struct{ cl_char lo, hi; }; +#endif +#if defined( __CL_CHAR2__) + __cl_char2 v2; +#endif +}cl_char2; + +typedef union +{ + cl_char CL_ALIGNED(4) s[4]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_char x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_char s0, s1, s2, s3; }; + __CL_ANON_STRUCT__ struct{ cl_char2 lo, hi; }; +#endif +#if defined( __CL_CHAR2__) + __cl_char2 v2[2]; +#endif +#if defined( __CL_CHAR4__) + __cl_char4 v4; +#endif +}cl_char4; + +/* cl_char3 is identical in size, alignment and behavior to cl_char4. See section 6.1.5. */ +typedef cl_char4 cl_char3; + +typedef union +{ + cl_char CL_ALIGNED(8) s[8]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_char x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_char s0, s1, s2, s3, s4, s5, s6, s7; }; + __CL_ANON_STRUCT__ struct{ cl_char4 lo, hi; }; +#endif +#if defined( __CL_CHAR2__) + __cl_char2 v2[4]; +#endif +#if defined( __CL_CHAR4__) + __cl_char4 v4[2]; +#endif +#if defined( __CL_CHAR8__ ) + __cl_char8 v8; +#endif +}cl_char8; + +typedef union +{ + cl_char CL_ALIGNED(16) s[16]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_char x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; + __CL_ANON_STRUCT__ struct{ cl_char s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; + __CL_ANON_STRUCT__ struct{ cl_char8 lo, hi; }; +#endif +#if defined( __CL_CHAR2__) + __cl_char2 v2[8]; +#endif +#if defined( __CL_CHAR4__) + __cl_char4 v4[4]; +#endif +#if defined( __CL_CHAR8__ ) + __cl_char8 v8[2]; +#endif +#if defined( __CL_CHAR16__ ) + __cl_char16 v16; +#endif +}cl_char16; + + +/* ---- cl_ucharn ---- */ +typedef union +{ + cl_uchar CL_ALIGNED(2) s[2]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_uchar x, y; }; + __CL_ANON_STRUCT__ struct{ cl_uchar s0, s1; }; + __CL_ANON_STRUCT__ struct{ cl_uchar lo, hi; }; +#endif +#if defined( __cl_uchar2__) + __cl_uchar2 v2; +#endif +}cl_uchar2; + +typedef union +{ + cl_uchar CL_ALIGNED(4) s[4]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_uchar x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_uchar s0, s1, s2, s3; }; + __CL_ANON_STRUCT__ struct{ cl_uchar2 lo, hi; }; +#endif +#if defined( __CL_UCHAR2__) + __cl_uchar2 v2[2]; +#endif +#if defined( __CL_UCHAR4__) + __cl_uchar4 v4; +#endif +}cl_uchar4; + +/* cl_uchar3 is identical in size, alignment and behavior to cl_uchar4. See section 6.1.5. */ +typedef cl_uchar4 cl_uchar3; + +typedef union +{ + cl_uchar CL_ALIGNED(8) s[8]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_uchar x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_uchar s0, s1, s2, s3, s4, s5, s6, s7; }; + __CL_ANON_STRUCT__ struct{ cl_uchar4 lo, hi; }; +#endif +#if defined( __CL_UCHAR2__) + __cl_uchar2 v2[4]; +#endif +#if defined( __CL_UCHAR4__) + __cl_uchar4 v4[2]; +#endif +#if defined( __CL_UCHAR8__ ) + __cl_uchar8 v8; +#endif +}cl_uchar8; + +typedef union +{ + cl_uchar CL_ALIGNED(16) s[16]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_uchar x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; + __CL_ANON_STRUCT__ struct{ cl_uchar s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; + __CL_ANON_STRUCT__ struct{ cl_uchar8 lo, hi; }; +#endif +#if defined( __CL_UCHAR2__) + __cl_uchar2 v2[8]; +#endif +#if defined( __CL_UCHAR4__) + __cl_uchar4 v4[4]; +#endif +#if defined( __CL_UCHAR8__ ) + __cl_uchar8 v8[2]; +#endif +#if defined( __CL_UCHAR16__ ) + __cl_uchar16 v16; +#endif +}cl_uchar16; + + +/* ---- cl_shortn ---- */ +typedef union +{ + cl_short CL_ALIGNED(4) s[2]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_short x, y; }; + __CL_ANON_STRUCT__ struct{ cl_short s0, s1; }; + __CL_ANON_STRUCT__ struct{ cl_short lo, hi; }; +#endif +#if defined( __CL_SHORT2__) + __cl_short2 v2; +#endif +}cl_short2; + +typedef union +{ + cl_short CL_ALIGNED(8) s[4]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_short x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_short s0, s1, s2, s3; }; + __CL_ANON_STRUCT__ struct{ cl_short2 lo, hi; }; +#endif +#if defined( __CL_SHORT2__) + __cl_short2 v2[2]; +#endif +#if defined( __CL_SHORT4__) + __cl_short4 v4; +#endif +}cl_short4; + +/* cl_short3 is identical in size, alignment and behavior to cl_short4. See section 6.1.5. */ +typedef cl_short4 cl_short3; + +typedef union +{ + cl_short CL_ALIGNED(16) s[8]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_short x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_short s0, s1, s2, s3, s4, s5, s6, s7; }; + __CL_ANON_STRUCT__ struct{ cl_short4 lo, hi; }; +#endif +#if defined( __CL_SHORT2__) + __cl_short2 v2[4]; +#endif +#if defined( __CL_SHORT4__) + __cl_short4 v4[2]; +#endif +#if defined( __CL_SHORT8__ ) + __cl_short8 v8; +#endif +}cl_short8; + +typedef union +{ + cl_short CL_ALIGNED(32) s[16]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_short x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; + __CL_ANON_STRUCT__ struct{ cl_short s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; + __CL_ANON_STRUCT__ struct{ cl_short8 lo, hi; }; +#endif +#if defined( __CL_SHORT2__) + __cl_short2 v2[8]; +#endif +#if defined( __CL_SHORT4__) + __cl_short4 v4[4]; +#endif +#if defined( __CL_SHORT8__ ) + __cl_short8 v8[2]; +#endif +#if defined( __CL_SHORT16__ ) + __cl_short16 v16; +#endif +}cl_short16; + + +/* ---- cl_ushortn ---- */ +typedef union +{ + cl_ushort CL_ALIGNED(4) s[2]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_ushort x, y; }; + __CL_ANON_STRUCT__ struct{ cl_ushort s0, s1; }; + __CL_ANON_STRUCT__ struct{ cl_ushort lo, hi; }; +#endif +#if defined( __CL_USHORT2__) + __cl_ushort2 v2; +#endif +}cl_ushort2; + +typedef union +{ + cl_ushort CL_ALIGNED(8) s[4]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_ushort x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_ushort s0, s1, s2, s3; }; + __CL_ANON_STRUCT__ struct{ cl_ushort2 lo, hi; }; +#endif +#if defined( __CL_USHORT2__) + __cl_ushort2 v2[2]; +#endif +#if defined( __CL_USHORT4__) + __cl_ushort4 v4; +#endif +}cl_ushort4; + +/* cl_ushort3 is identical in size, alignment and behavior to cl_ushort4. See section 6.1.5. */ +typedef cl_ushort4 cl_ushort3; + +typedef union +{ + cl_ushort CL_ALIGNED(16) s[8]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_ushort x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_ushort s0, s1, s2, s3, s4, s5, s6, s7; }; + __CL_ANON_STRUCT__ struct{ cl_ushort4 lo, hi; }; +#endif +#if defined( __CL_USHORT2__) + __cl_ushort2 v2[4]; +#endif +#if defined( __CL_USHORT4__) + __cl_ushort4 v4[2]; +#endif +#if defined( __CL_USHORT8__ ) + __cl_ushort8 v8; +#endif +}cl_ushort8; + +typedef union +{ + cl_ushort CL_ALIGNED(32) s[16]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_ushort x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; + __CL_ANON_STRUCT__ struct{ cl_ushort s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; + __CL_ANON_STRUCT__ struct{ cl_ushort8 lo, hi; }; +#endif +#if defined( __CL_USHORT2__) + __cl_ushort2 v2[8]; +#endif +#if defined( __CL_USHORT4__) + __cl_ushort4 v4[4]; +#endif +#if defined( __CL_USHORT8__ ) + __cl_ushort8 v8[2]; +#endif +#if defined( __CL_USHORT16__ ) + __cl_ushort16 v16; +#endif +}cl_ushort16; + +/* ---- cl_intn ---- */ +typedef union +{ + cl_int CL_ALIGNED(8) s[2]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_int x, y; }; + __CL_ANON_STRUCT__ struct{ cl_int s0, s1; }; + __CL_ANON_STRUCT__ struct{ cl_int lo, hi; }; +#endif +#if defined( __CL_INT2__) + __cl_int2 v2; +#endif +}cl_int2; + +typedef union +{ + cl_int CL_ALIGNED(16) s[4]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_int x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_int s0, s1, s2, s3; }; + __CL_ANON_STRUCT__ struct{ cl_int2 lo, hi; }; +#endif +#if defined( __CL_INT2__) + __cl_int2 v2[2]; +#endif +#if defined( __CL_INT4__) + __cl_int4 v4; +#endif +}cl_int4; + +/* cl_int3 is identical in size, alignment and behavior to cl_int4. See section 6.1.5. */ +typedef cl_int4 cl_int3; + +typedef union +{ + cl_int CL_ALIGNED(32) s[8]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_int x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_int s0, s1, s2, s3, s4, s5, s6, s7; }; + __CL_ANON_STRUCT__ struct{ cl_int4 lo, hi; }; +#endif +#if defined( __CL_INT2__) + __cl_int2 v2[4]; +#endif +#if defined( __CL_INT4__) + __cl_int4 v4[2]; +#endif +#if defined( __CL_INT8__ ) + __cl_int8 v8; +#endif +}cl_int8; + +typedef union +{ + cl_int CL_ALIGNED(64) s[16]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_int x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; + __CL_ANON_STRUCT__ struct{ cl_int s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; + __CL_ANON_STRUCT__ struct{ cl_int8 lo, hi; }; +#endif +#if defined( __CL_INT2__) + __cl_int2 v2[8]; +#endif +#if defined( __CL_INT4__) + __cl_int4 v4[4]; +#endif +#if defined( __CL_INT8__ ) + __cl_int8 v8[2]; +#endif +#if defined( __CL_INT16__ ) + __cl_int16 v16; +#endif +}cl_int16; + + +/* ---- cl_uintn ---- */ +typedef union +{ + cl_uint CL_ALIGNED(8) s[2]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_uint x, y; }; + __CL_ANON_STRUCT__ struct{ cl_uint s0, s1; }; + __CL_ANON_STRUCT__ struct{ cl_uint lo, hi; }; +#endif +#if defined( __CL_UINT2__) + __cl_uint2 v2; +#endif +}cl_uint2; + +typedef union +{ + cl_uint CL_ALIGNED(16) s[4]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_uint x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_uint s0, s1, s2, s3; }; + __CL_ANON_STRUCT__ struct{ cl_uint2 lo, hi; }; +#endif +#if defined( __CL_UINT2__) + __cl_uint2 v2[2]; +#endif +#if defined( __CL_UINT4__) + __cl_uint4 v4; +#endif +}cl_uint4; + +/* cl_uint3 is identical in size, alignment and behavior to cl_uint4. See section 6.1.5. */ +typedef cl_uint4 cl_uint3; + +typedef union +{ + cl_uint CL_ALIGNED(32) s[8]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_uint x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_uint s0, s1, s2, s3, s4, s5, s6, s7; }; + __CL_ANON_STRUCT__ struct{ cl_uint4 lo, hi; }; +#endif +#if defined( __CL_UINT2__) + __cl_uint2 v2[4]; +#endif +#if defined( __CL_UINT4__) + __cl_uint4 v4[2]; +#endif +#if defined( __CL_UINT8__ ) + __cl_uint8 v8; +#endif +}cl_uint8; + +typedef union +{ + cl_uint CL_ALIGNED(64) s[16]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_uint x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; + __CL_ANON_STRUCT__ struct{ cl_uint s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; + __CL_ANON_STRUCT__ struct{ cl_uint8 lo, hi; }; +#endif +#if defined( __CL_UINT2__) + __cl_uint2 v2[8]; +#endif +#if defined( __CL_UINT4__) + __cl_uint4 v4[4]; +#endif +#if defined( __CL_UINT8__ ) + __cl_uint8 v8[2]; +#endif +#if defined( __CL_UINT16__ ) + __cl_uint16 v16; +#endif +}cl_uint16; + +/* ---- cl_longn ---- */ +typedef union +{ + cl_long CL_ALIGNED(16) s[2]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_long x, y; }; + __CL_ANON_STRUCT__ struct{ cl_long s0, s1; }; + __CL_ANON_STRUCT__ struct{ cl_long lo, hi; }; +#endif +#if defined( __CL_LONG2__) + __cl_long2 v2; +#endif +}cl_long2; + +typedef union +{ + cl_long CL_ALIGNED(32) s[4]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_long x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_long s0, s1, s2, s3; }; + __CL_ANON_STRUCT__ struct{ cl_long2 lo, hi; }; +#endif +#if defined( __CL_LONG2__) + __cl_long2 v2[2]; +#endif +#if defined( __CL_LONG4__) + __cl_long4 v4; +#endif +}cl_long4; + +/* cl_long3 is identical in size, alignment and behavior to cl_long4. See section 6.1.5. */ +typedef cl_long4 cl_long3; + +typedef union +{ + cl_long CL_ALIGNED(64) s[8]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_long x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_long s0, s1, s2, s3, s4, s5, s6, s7; }; + __CL_ANON_STRUCT__ struct{ cl_long4 lo, hi; }; +#endif +#if defined( __CL_LONG2__) + __cl_long2 v2[4]; +#endif +#if defined( __CL_LONG4__) + __cl_long4 v4[2]; +#endif +#if defined( __CL_LONG8__ ) + __cl_long8 v8; +#endif +}cl_long8; + +typedef union +{ + cl_long CL_ALIGNED(128) s[16]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_long x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; + __CL_ANON_STRUCT__ struct{ cl_long s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; + __CL_ANON_STRUCT__ struct{ cl_long8 lo, hi; }; +#endif +#if defined( __CL_LONG2__) + __cl_long2 v2[8]; +#endif +#if defined( __CL_LONG4__) + __cl_long4 v4[4]; +#endif +#if defined( __CL_LONG8__ ) + __cl_long8 v8[2]; +#endif +#if defined( __CL_LONG16__ ) + __cl_long16 v16; +#endif +}cl_long16; + + +/* ---- cl_ulongn ---- */ +typedef union +{ + cl_ulong CL_ALIGNED(16) s[2]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_ulong x, y; }; + __CL_ANON_STRUCT__ struct{ cl_ulong s0, s1; }; + __CL_ANON_STRUCT__ struct{ cl_ulong lo, hi; }; +#endif +#if defined( __CL_ULONG2__) + __cl_ulong2 v2; +#endif +}cl_ulong2; + +typedef union +{ + cl_ulong CL_ALIGNED(32) s[4]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_ulong x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_ulong s0, s1, s2, s3; }; + __CL_ANON_STRUCT__ struct{ cl_ulong2 lo, hi; }; +#endif +#if defined( __CL_ULONG2__) + __cl_ulong2 v2[2]; +#endif +#if defined( __CL_ULONG4__) + __cl_ulong4 v4; +#endif +}cl_ulong4; + +/* cl_ulong3 is identical in size, alignment and behavior to cl_ulong4. See section 6.1.5. */ +typedef cl_ulong4 cl_ulong3; + +typedef union +{ + cl_ulong CL_ALIGNED(64) s[8]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_ulong x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_ulong s0, s1, s2, s3, s4, s5, s6, s7; }; + __CL_ANON_STRUCT__ struct{ cl_ulong4 lo, hi; }; +#endif +#if defined( __CL_ULONG2__) + __cl_ulong2 v2[4]; +#endif +#if defined( __CL_ULONG4__) + __cl_ulong4 v4[2]; +#endif +#if defined( __CL_ULONG8__ ) + __cl_ulong8 v8; +#endif +}cl_ulong8; + +typedef union +{ + cl_ulong CL_ALIGNED(128) s[16]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_ulong x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; + __CL_ANON_STRUCT__ struct{ cl_ulong s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; + __CL_ANON_STRUCT__ struct{ cl_ulong8 lo, hi; }; +#endif +#if defined( __CL_ULONG2__) + __cl_ulong2 v2[8]; +#endif +#if defined( __CL_ULONG4__) + __cl_ulong4 v4[4]; +#endif +#if defined( __CL_ULONG8__ ) + __cl_ulong8 v8[2]; +#endif +#if defined( __CL_ULONG16__ ) + __cl_ulong16 v16; +#endif +}cl_ulong16; + + +/* --- cl_floatn ---- */ + +typedef union +{ + cl_float CL_ALIGNED(8) s[2]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_float x, y; }; + __CL_ANON_STRUCT__ struct{ cl_float s0, s1; }; + __CL_ANON_STRUCT__ struct{ cl_float lo, hi; }; +#endif +#if defined( __CL_FLOAT2__) + __cl_float2 v2; +#endif +}cl_float2; + +typedef union +{ + cl_float CL_ALIGNED(16) s[4]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_float x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_float s0, s1, s2, s3; }; + __CL_ANON_STRUCT__ struct{ cl_float2 lo, hi; }; +#endif +#if defined( __CL_FLOAT2__) + __cl_float2 v2[2]; +#endif +#if defined( __CL_FLOAT4__) + __cl_float4 v4; +#endif +}cl_float4; + +/* cl_float3 is identical in size, alignment and behavior to cl_float4. See section 6.1.5. */ +typedef cl_float4 cl_float3; + +typedef union +{ + cl_float CL_ALIGNED(32) s[8]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_float x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_float s0, s1, s2, s3, s4, s5, s6, s7; }; + __CL_ANON_STRUCT__ struct{ cl_float4 lo, hi; }; +#endif +#if defined( __CL_FLOAT2__) + __cl_float2 v2[4]; +#endif +#if defined( __CL_FLOAT4__) + __cl_float4 v4[2]; +#endif +#if defined( __CL_FLOAT8__ ) + __cl_float8 v8; +#endif +}cl_float8; + +typedef union +{ + cl_float CL_ALIGNED(64) s[16]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_float x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; + __CL_ANON_STRUCT__ struct{ cl_float s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; + __CL_ANON_STRUCT__ struct{ cl_float8 lo, hi; }; +#endif +#if defined( __CL_FLOAT2__) + __cl_float2 v2[8]; +#endif +#if defined( __CL_FLOAT4__) + __cl_float4 v4[4]; +#endif +#if defined( __CL_FLOAT8__ ) + __cl_float8 v8[2]; +#endif +#if defined( __CL_FLOAT16__ ) + __cl_float16 v16; +#endif +}cl_float16; + +/* --- cl_doublen ---- */ + +typedef union +{ + cl_double CL_ALIGNED(16) s[2]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_double x, y; }; + __CL_ANON_STRUCT__ struct{ cl_double s0, s1; }; + __CL_ANON_STRUCT__ struct{ cl_double lo, hi; }; +#endif +#if defined( __CL_DOUBLE2__) + __cl_double2 v2; +#endif +}cl_double2; + +typedef union +{ + cl_double CL_ALIGNED(32) s[4]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_double x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_double s0, s1, s2, s3; }; + __CL_ANON_STRUCT__ struct{ cl_double2 lo, hi; }; +#endif +#if defined( __CL_DOUBLE2__) + __cl_double2 v2[2]; +#endif +#if defined( __CL_DOUBLE4__) + __cl_double4 v4; +#endif +}cl_double4; + +/* cl_double3 is identical in size, alignment and behavior to cl_double4. See section 6.1.5. */ +typedef cl_double4 cl_double3; + +typedef union +{ + cl_double CL_ALIGNED(64) s[8]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_double x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_double s0, s1, s2, s3, s4, s5, s6, s7; }; + __CL_ANON_STRUCT__ struct{ cl_double4 lo, hi; }; +#endif +#if defined( __CL_DOUBLE2__) + __cl_double2 v2[4]; +#endif +#if defined( __CL_DOUBLE4__) + __cl_double4 v4[2]; +#endif +#if defined( __CL_DOUBLE8__ ) + __cl_double8 v8; +#endif +}cl_double8; + +typedef union +{ + cl_double CL_ALIGNED(128) s[16]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_double x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; + __CL_ANON_STRUCT__ struct{ cl_double s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; + __CL_ANON_STRUCT__ struct{ cl_double8 lo, hi; }; +#endif +#if defined( __CL_DOUBLE2__) + __cl_double2 v2[8]; +#endif +#if defined( __CL_DOUBLE4__) + __cl_double4 v4[4]; +#endif +#if defined( __CL_DOUBLE8__ ) + __cl_double8 v8[2]; +#endif +#if defined( __CL_DOUBLE16__ ) + __cl_double16 v16; +#endif +}cl_double16; + +/* Macro to facilitate debugging + * Usage: + * Place CL_PROGRAM_STRING_DEBUG_INFO on the line before the first line of your source. + * The first line ends with: CL_PROGRAM_STRING_DEBUG_INFO \" + * Each line thereafter of OpenCL C source must end with: \n\ + * The last line ends in "; + * + * Example: + * + * const char *my_program = CL_PROGRAM_STRING_DEBUG_INFO "\ + * kernel void foo( int a, float * b ) \n\ + * { \n\ + * // my comment \n\ + * *b[ get_global_id(0)] = a; \n\ + * } \n\ + * "; + * + * This should correctly set up the line, (column) and file information for your source + * string so you can do source level debugging. + */ +#define __CL_STRINGIFY( _x ) # _x +#define _CL_STRINGIFY( _x ) __CL_STRINGIFY( _x ) +#define CL_PROGRAM_STRING_DEBUG_INFO "#line " _CL_STRINGIFY(__LINE__) " \"" __FILE__ "\" \n\n" + +#ifdef __cplusplus +} +#endif + +#undef __CL_HAS_ANON_STRUCT__ +#undef __CL_ANON_STRUCT__ +#if defined( _WIN32) && (_MSC_VER >= 1500) +#pragma warning( pop ) +#endif + +#endif /* __CL_PLATFORM_H */ diff --git a/third_party/opencl/include/CL/opencl.h b/third_party/opencl/include/CL/opencl.h new file mode 100644 index 000000000..9855cd75e --- /dev/null +++ b/third_party/opencl/include/CL/opencl.h @@ -0,0 +1,59 @@ +/******************************************************************************* + * Copyright (c) 2008-2015 The Khronos Group Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and/or associated documentation files (the + * "Materials"), to deal in the Materials without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Materials, and to + * permit persons to whom the Materials are furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Materials. + * + * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS + * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS + * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT + * https://www.khronos.org/registry/ + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. + ******************************************************************************/ + +/* $Revision: 11708 $ on $Date: 2010-06-13 23:36:24 -0700 (Sun, 13 Jun 2010) $ */ + +#ifndef __OPENCL_H +#define __OPENCL_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __APPLE__ + +#include +#include +#include +#include + +#else + +#include +#include +#include +#include + +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* __OPENCL_H */ + diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index b79d046fc..d4cfc6728 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -57,9 +57,11 @@ base_frameworks = qt_env['FRAMEWORKS'] base_libs = [common, messaging, cereal, visionipc, 'm', 'ssl', 'crypto', 'pthread'] + qt_env["LIBS"] if arch == "Darwin": + base_frameworks.append('OpenCL') base_frameworks.append('QtCharts') base_frameworks.append('QtSerialBus') else: + base_libs.append('OpenCL') base_libs.append('Qt5Charts') base_libs.append('Qt5SerialBus') diff --git a/tools/install_ubuntu_dependencies.sh b/tools/install_ubuntu_dependencies.sh index f428e9972..5c2131d4b 100755 --- a/tools/install_ubuntu_dependencies.sh +++ b/tools/install_ubuntu_dependencies.sh @@ -58,6 +58,9 @@ function install_ubuntu_common_requirements() { libzmq3-dev \ libzstd-dev \ libsqlite3-dev \ + opencl-headers \ + ocl-icd-libopencl1 \ + ocl-icd-opencl-dev \ portaudio19-dev \ qttools5-dev-tools \ libqt5svg5-dev \ diff --git a/tools/replay/SConscript b/tools/replay/SConscript index 698ab9885..136c4119f 100644 --- a/tools/replay/SConscript +++ b/tools/replay/SConscript @@ -6,6 +6,11 @@ replay_env['CCFLAGS'] += ['-Wno-deprecated-declarations'] base_frameworks = [] base_libs = [common, messaging, cereal, visionipc, 'm', 'ssl', 'crypto', 'pthread'] +if arch == "Darwin": + base_frameworks.append('OpenCL') +else: + base_libs.append('OpenCL') + replay_lib_src = ["replay.cc", "consoleui.cc", "camera.cc", "filereader.cc", "logreader.cc", "framereader.cc", "route.cc", "util.cc", "seg_mgr.cc", "timeline.cc", "api.cc"] if arch != "Darwin": diff --git a/tools/webcam/README.md b/tools/webcam/README.md index 6abbc4793..67ad2cc8c 100644 --- a/tools/webcam/README.md +++ b/tools/webcam/README.md @@ -10,6 +10,10 @@ What's needed: ## Setup openpilot - Follow [this readme](../README.md) to install and build the requirements +- Install OpenCL Driver (Ubuntu) +``` +sudo apt install pocl-opencl-icd +``` ## Connect the hardware - Connect the camera first From db8cd9f411f08d75696b36c5fd0fa6b6cb0bfb2d Mon Sep 17 00:00:00 2001 From: Trey Moen <50057480+greatgitsby@users.noreply.github.com> Date: Sat, 7 Feb 2026 12:52:28 -0700 Subject: [PATCH 848/910] fix: route fetch metadata when first log isnt uploaded (#37114) * fix: route fetch metadata when first log isnt uploaded * default * simple --------- Co-authored-by: Trey Moen --- tools/clip/run.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tools/clip/run.py b/tools/clip/run.py index 855ac6fea..d338f097d 100755 --- a/tools/clip/run.py +++ b/tools/clip/run.py @@ -208,7 +208,10 @@ class FrameQueue: def load_route_metadata(route): from openpilot.common.params import Params, UnknownKeyName - lr = LogReader(route.log_paths()[0]) + path = next((item for item in route.log_paths() if item), None) + if not path: + raise Exception('error getting route metadata: cannot find any uploaded logs') + lr = LogReader(path) init_data, car_params = lr.first('initData'), lr.first('carParams') params = Params() From 0ce28803eccb443a4f90eceaaf1037776366293a Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 7 Feb 2026 12:20:34 -0800 Subject: [PATCH 849/910] gitignore .context/ --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index e2a30fb70..1fef0a125 100644 --- a/.gitignore +++ b/.gitignore @@ -97,5 +97,6 @@ Pipfile .ionide .claude/ +.context/ PLAN.md TASK.md From bcb13a7229d6c583da7c6f0fdd52785c5cfa993c Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Sat, 7 Feb 2026 16:19:08 -0600 Subject: [PATCH 850/910] ui diff replay: remove unused frame_data list for individual frame display (#37117) Remove unused base64 encoding and simplify frame data handling in diff video report --- selfdrive/ui/tests/diff/diff.py | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/selfdrive/ui/tests/diff/diff.py b/selfdrive/ui/tests/diff/diff.py index be7af5438..a581f6874 100755 --- a/selfdrive/ui/tests/diff/diff.py +++ b/selfdrive/ui/tests/diff/diff.py @@ -3,7 +3,6 @@ import os import sys import subprocess import tempfile -import base64 import webbrowser import argparse from pathlib import Path @@ -25,12 +24,6 @@ def compare_frames(frame1_path, frame2_path): return result.returncode == 0 -def frame_to_data_url(frame_path): - with open(frame_path, 'rb') as f: - data = f.read() - return f"data:image/png;base64,{base64.b64encode(data).decode()}" - - def create_diff_video(video1, video2, output_path): """Create a diff video using ffmpeg blend filter with difference mode.""" print("Creating diff video...") @@ -60,20 +53,16 @@ def find_differences(video1, video2): print(f"Comparing {len(frames1)} frames...") different_frames = [] - frame_data = [] for i, (f1, f2) in enumerate(zip(frames1, frames2, strict=False)): is_different = not compare_frames(f1, f2) if is_different: different_frames.append(i) - if i < 10 or i >= len(frames1) - 10 or is_different: - frame_data.append({'index': i, 'different': is_different, 'frame1_url': frame_to_data_url(f1), 'frame2_url': frame_to_data_url(f2)}) - - return different_frames, frame_data, len(frames1) + return different_frames, len(frames1) -def generate_html_report(video1, video2, basedir, different_frames, frame_data, total_frames): +def generate_html_report(video1, video2, basedir, different_frames, total_frames): chunks = [] if different_frames: current_chunk = [different_frames[0]] @@ -177,14 +166,14 @@ def main(): diff_video_path = os.path.join(os.path.dirname(args.output), DIFF_OUT_DIR / "diff.mp4") create_diff_video(args.video1, args.video2, diff_video_path) - different_frames, frame_data, total_frames = find_differences(args.video1, args.video2) + different_frames, total_frames = find_differences(args.video1, args.video2) if different_frames is None: sys.exit(1) print() print("Generating HTML report...") - html = generate_html_report(args.video1, args.video2, args.basedir, different_frames, frame_data, total_frames) + html = generate_html_report(args.video1, args.video2, args.basedir, different_frames, total_frames) with open(DIFF_OUT_DIR / args.output, 'w') as f: f.write(html) From ac17c35cfe898681f7b5cf9e57b98c4a844d7459 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 7 Feb 2026 15:18:00 -0800 Subject: [PATCH 851/910] bridge: move ZMQ handling over (#37118) --- cereal/SConscript | 2 +- cereal/messaging/bridge.cc | 8 +- cereal/messaging/bridge_zmq.cc | 170 ++++++++++++++++++++++++++++++++ cereal/messaging/bridge_zmq.h | 72 ++++++++++++++ cereal/messaging/msgq_to_zmq.cc | 10 +- cereal/messaging/msgq_to_zmq.h | 9 +- 6 files changed, 256 insertions(+), 15 deletions(-) create mode 100644 cereal/messaging/bridge_zmq.cc create mode 100644 cereal/messaging/bridge_zmq.h diff --git a/cereal/SConscript b/cereal/SConscript index a58a9490c..73dc61844 100644 --- a/cereal/SConscript +++ b/cereal/SConscript @@ -13,7 +13,7 @@ cereal = env.Library('cereal', [f'gen/cpp/{s}.c++' for s in schema_files]) # Build messaging services_h = env.Command(['services.h'], ['services.py'], 'python3 ' + cereal_dir.path + '/services.py > $TARGET') -env.Program('messaging/bridge', ['messaging/bridge.cc', 'messaging/msgq_to_zmq.cc'], LIBS=[msgq, common, 'pthread']) +env.Program('messaging/bridge', ['messaging/bridge.cc', 'messaging/msgq_to_zmq.cc', 'messaging/bridge_zmq.cc'], LIBS=[msgq, common, 'pthread']) socketmaster = env.Library('socketmaster', ['messaging/socketmaster.cc']) diff --git a/cereal/messaging/bridge.cc b/cereal/messaging/bridge.cc index fb92c575c..2eb3de32c 100644 --- a/cereal/messaging/bridge.cc +++ b/cereal/messaging/bridge.cc @@ -25,14 +25,14 @@ void msgq_to_zmq(const std::vector &endpoints, const std::string &i } void zmq_to_msgq(const std::vector &endpoints, const std::string &ip) { - auto poller = std::make_unique(); + auto poller = std::make_unique(); auto pub_context = std::make_unique(); - auto sub_context = std::make_unique(); - std::map sub2pub; + auto sub_context = std::make_unique(); + std::map sub2pub; for (auto endpoint : endpoints) { auto pub_sock = new MSGQPubSocket(); - auto sub_sock = new ZMQSubSocket(); + auto sub_sock = new BridgeZmqSubSocket(); size_t queue_size = services.at(endpoint).queue_size; pub_sock->connect(pub_context.get(), endpoint, true, queue_size); sub_sock->connect(sub_context.get(), endpoint, ip, false); diff --git a/cereal/messaging/bridge_zmq.cc b/cereal/messaging/bridge_zmq.cc new file mode 100644 index 000000000..5c56673b4 --- /dev/null +++ b/cereal/messaging/bridge_zmq.cc @@ -0,0 +1,170 @@ +#include "cereal/messaging/bridge_zmq.h" + +#include +#include +#include + +static size_t fnv1a_hash(const std::string &str) { + const size_t fnv_prime = 0x100000001b3; + size_t hash_value = 0xcbf29ce484222325; + for (char c : str) { + hash_value ^= (unsigned char)c; + hash_value *= fnv_prime; + } + return hash_value; +} + +// FIXME: This is a hack to get the port number from the socket name, might have collisions. +static int get_port(std::string endpoint) { + size_t hash_value = fnv1a_hash(endpoint); + int start_port = 8023; + int max_port = 65535; + return start_port + (hash_value % (max_port - start_port)); +} + +BridgeZmqContext::BridgeZmqContext() { + context = zmq_ctx_new(); +} + +BridgeZmqContext::~BridgeZmqContext() { + if (context != nullptr) { + zmq_ctx_term(context); + } +} + +void BridgeZmqMessage::init(size_t sz) { + size = sz; + data = new char[size]; +} + +void BridgeZmqMessage::init(char *d, size_t sz) { + size = sz; + data = new char[size]; + memcpy(data, d, size); +} + +void BridgeZmqMessage::close() { + if (size > 0) { + delete[] data; + } + data = nullptr; + size = 0; +} + +BridgeZmqMessage::~BridgeZmqMessage() { + close(); +} + +int BridgeZmqSubSocket::connect(BridgeZmqContext *context, std::string endpoint, std::string address, bool conflate, bool check_endpoint) { + sock = zmq_socket(context->getRawContext(), ZMQ_SUB); + if (sock == nullptr) { + return -1; + } + + zmq_setsockopt(sock, ZMQ_SUBSCRIBE, "", 0); + + if (conflate) { + int arg = 1; + zmq_setsockopt(sock, ZMQ_CONFLATE, &arg, sizeof(int)); + } + + int reconnect_ivl = 500; + zmq_setsockopt(sock, ZMQ_RECONNECT_IVL_MAX, &reconnect_ivl, sizeof(reconnect_ivl)); + + full_endpoint = "tcp://" + address + ":"; + if (check_endpoint) { + full_endpoint += std::to_string(get_port(endpoint)); + } else { + full_endpoint += endpoint; + } + + return zmq_connect(sock, full_endpoint.c_str()); +} + +void BridgeZmqSubSocket::setTimeout(int timeout) { + zmq_setsockopt(sock, ZMQ_RCVTIMEO, &timeout, sizeof(int)); +} + +Message *BridgeZmqSubSocket::receive(bool non_blocking) { + zmq_msg_t msg; + assert(zmq_msg_init(&msg) == 0); + + int flags = non_blocking ? ZMQ_DONTWAIT : 0; + int rc = zmq_msg_recv(&msg, sock, flags); + + Message *ret = nullptr; + if (rc >= 0) { + ret = new BridgeZmqMessage; + ret->init((char *)zmq_msg_data(&msg), zmq_msg_size(&msg)); + } + + zmq_msg_close(&msg); + return ret; +} + +BridgeZmqSubSocket::~BridgeZmqSubSocket() { + if (sock != nullptr) { + zmq_close(sock); + } +} + +int BridgeZmqPubSocket::connect(BridgeZmqContext *context, std::string endpoint, bool check_endpoint) { + sock = zmq_socket(context->getRawContext(), ZMQ_PUB); + if (sock == nullptr) { + return -1; + } + + full_endpoint = "tcp://*:"; + if (check_endpoint) { + full_endpoint += std::to_string(get_port(endpoint)); + } else { + full_endpoint += endpoint; + } + + // ZMQ pub sockets cannot be shared between processes, so we need to ensure pid stays the same. + pid = getpid(); + + return zmq_bind(sock, full_endpoint.c_str()); +} + +int BridgeZmqPubSocket::sendMessage(Message *message) { + assert(pid == getpid()); + return zmq_send(sock, message->getData(), message->getSize(), ZMQ_DONTWAIT); +} + +int BridgeZmqPubSocket::send(char *data, size_t size) { + assert(pid == getpid()); + return zmq_send(sock, data, size, ZMQ_DONTWAIT); +} + +BridgeZmqPubSocket::~BridgeZmqPubSocket() { + if (sock != nullptr) { + zmq_close(sock); + } +} + +void BridgeZmqPoller::registerSocket(BridgeZmqSubSocket *socket) { + assert(num_polls + 1 < (sizeof(polls) / sizeof(polls[0]))); + polls[num_polls].socket = socket->getRawSocket(); + polls[num_polls].events = ZMQ_POLLIN; + + sockets.push_back(socket); + num_polls++; +} + +std::vector BridgeZmqPoller::poll(int timeout) { + std::vector ret; + + int rc = zmq_poll(polls, num_polls, timeout); + if (rc < 0) { + return ret; + } + + for (size_t i = 0; i < num_polls; i++) { + if (polls[i].revents) { + ret.push_back(sockets[i]); + } + } + + return ret; +} diff --git a/cereal/messaging/bridge_zmq.h b/cereal/messaging/bridge_zmq.h new file mode 100644 index 000000000..eab48a91e --- /dev/null +++ b/cereal/messaging/bridge_zmq.h @@ -0,0 +1,72 @@ +#pragma once + +#include +#include +#include + +#include + +#include "msgq/ipc.h" + +class BridgeZmqContext { +public: + BridgeZmqContext(); + void *getRawContext() { return context; } + ~BridgeZmqContext(); + +private: + void *context = nullptr; +}; + +class BridgeZmqMessage : public Message { +public: + void init(size_t size) override; + void init(char *data, size_t size) override; + void close() override; + size_t getSize() override { return size; } + char *getData() override { return data; } + ~BridgeZmqMessage() override; + +private: + char *data = nullptr; + size_t size = 0; +}; + +class BridgeZmqSubSocket { +public: + int connect(BridgeZmqContext *context, std::string endpoint, std::string address, bool conflate = false, bool check_endpoint = true); + void setTimeout(int timeout); + Message *receive(bool non_blocking = false); + void *getRawSocket() { return sock; } + ~BridgeZmqSubSocket(); + +private: + void *sock = nullptr; + std::string full_endpoint; +}; + +class BridgeZmqPubSocket { +public: + int connect(BridgeZmqContext *context, std::string endpoint, bool check_endpoint = true); + int sendMessage(Message *message); + int send(char *data, size_t size); + void *getRawSocket() { return sock; } + ~BridgeZmqPubSocket(); + +private: + void *sock = nullptr; + std::string full_endpoint; + int pid = -1; +}; + +class BridgeZmqPoller { +public: + void registerSocket(BridgeZmqSubSocket *socket); + std::vector poll(int timeout); + +private: + static constexpr size_t MAX_BRIDGE_ZMQ_POLLERS = 128; + std::vector sockets; + zmq_pollitem_t polls[MAX_BRIDGE_ZMQ_POLLERS] = {}; + size_t num_polls = 0; +}; diff --git a/cereal/messaging/msgq_to_zmq.cc b/cereal/messaging/msgq_to_zmq.cc index 7f8c738d4..930b617e7 100644 --- a/cereal/messaging/msgq_to_zmq.cc +++ b/cereal/messaging/msgq_to_zmq.cc @@ -22,14 +22,14 @@ static std::string recv_zmq_msg(void *sock) { } void MsgqToZmq::run(const std::vector &endpoints, const std::string &ip) { - zmq_context = std::make_unique(); + zmq_context = std::make_unique(); msgq_context = std::make_unique(); // Create ZMQPubSockets for each endpoint for (const auto &endpoint : endpoints) { auto &socket_pair = socket_pairs.emplace_back(); socket_pair.endpoint = endpoint; - socket_pair.pub_sock = std::make_unique(); + socket_pair.pub_sock = std::make_unique(); int ret = socket_pair.pub_sock->connect(zmq_context.get(), endpoint); if (ret != 0) { printf("Failed to create ZMQ publisher for [%s]: %s\n", endpoint.c_str(), zmq_strerror(zmq_errno())); @@ -49,7 +49,7 @@ void MsgqToZmq::run(const std::vector &endpoints, const std::string for (auto sub_sock : msgq_poller->poll(100)) { // Process messages for each socket - ZMQPubSocket *pub_sock = sub2pub.at(sub_sock); + BridgeZmqPubSocket *pub_sock = sub2pub.at(sub_sock); for (int i = 0; i < MAX_MESSAGES_PER_SOCKET; ++i) { auto msg = std::unique_ptr(sub_sock->receive(true)); if (!msg) break; @@ -72,7 +72,7 @@ void MsgqToZmq::zmqMonitorThread() { // Set up ZMQ monitor for each pub socket for (int i = 0; i < socket_pairs.size(); ++i) { std::string addr = "inproc://op-bridge-monitor-" + std::to_string(i); - zmq_socket_monitor(socket_pairs[i].pub_sock->sock, addr.c_str(), ZMQ_EVENT_ACCEPTED | ZMQ_EVENT_DISCONNECTED); + zmq_socket_monitor(socket_pairs[i].pub_sock->getRawSocket(), addr.c_str(), ZMQ_EVENT_ACCEPTED | ZMQ_EVENT_DISCONNECTED); void *monitor_socket = zmq_socket(zmq_context->getRawContext(), ZMQ_PAIR); zmq_connect(monitor_socket, addr.c_str()); @@ -130,7 +130,7 @@ void MsgqToZmq::zmqMonitorThread() { // Clean up monitor sockets for (int i = 0; i < pollitems.size(); ++i) { - zmq_socket_monitor(socket_pairs[i].pub_sock->sock, nullptr, 0); + zmq_socket_monitor(socket_pairs[i].pub_sock->getRawSocket(), nullptr, 0); zmq_close(pollitems[i].socket); } cv.notify_one(); diff --git a/cereal/messaging/msgq_to_zmq.h b/cereal/messaging/msgq_to_zmq.h index ebdbe5df6..ac92631fe 100644 --- a/cereal/messaging/msgq_to_zmq.h +++ b/cereal/messaging/msgq_to_zmq.h @@ -7,9 +7,8 @@ #include #include -#define private public #include "msgq/impl_msgq.h" -#include "msgq/impl_zmq.h" +#include "cereal/messaging/bridge_zmq.h" class MsgqToZmq { public: @@ -22,16 +21,16 @@ protected: struct SocketPair { std::string endpoint; - std::unique_ptr pub_sock; + std::unique_ptr pub_sock; std::unique_ptr sub_sock; int connected_clients = 0; }; std::unique_ptr msgq_context; - std::unique_ptr zmq_context; + std::unique_ptr zmq_context; std::mutex mutex; std::condition_variable cv; std::unique_ptr msgq_poller; - std::map sub2pub; + std::map sub2pub; std::vector socket_pairs; }; From 55a31d76571afca6a07b8efdc945d009c31d19ae Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 7 Feb 2026 18:27:16 -0800 Subject: [PATCH 852/910] replace tabulate with simple helper (#37122) --- common/utils.py | 86 +++ pyproject.toml | 1 - selfdrive/test/process_replay/model_replay.py | 2 +- selfdrive/test/test_onroad.py | 2 +- system/hardware/tici/tests/test_power_draw.py | 2 +- .../longitudinal_maneuvers/generate_report.py | 2 +- uv.lock | 515 +----------------- 7 files changed, 91 insertions(+), 519 deletions(-) diff --git a/common/utils.py b/common/utils.py index ccc6719f5..faaa96ecb 100644 --- a/common/utils.py +++ b/common/utils.py @@ -167,6 +167,92 @@ def managed_proc(cmd: list[str], env: dict[str, str]): proc.kill() +def tabulate(tabular_data, headers=(), tablefmt="simple", floatfmt="g", stralign="left", numalign=None): + rows = [list(row) for row in tabular_data] + + def fmt(val): + if isinstance(val, str): + return val + if isinstance(val, (bool, int)): + return str(val) + try: + return format(val, floatfmt) + except (TypeError, ValueError): + return str(val) + + formatted = [[fmt(c) for c in row] for row in rows] + hdrs = [str(h) for h in headers] if headers else None + + ncols = max((len(r) for r in formatted), default=0) + if hdrs: + ncols = max(ncols, len(hdrs)) + if ncols == 0: + return "" + + for r in formatted: + r.extend([""] * (ncols - len(r))) + if hdrs: + hdrs.extend([""] * (ncols - len(hdrs))) + + widths = [0] * ncols + if hdrs: + for i in range(ncols): + widths[i] = len(hdrs[i]) + for row in formatted: + for i in range(ncols): + widths[i] = max(widths[i], max(len(ln) for ln in row[i].split('\n'))) + + def _align(s, w): + if stralign == "center": + return s.center(w) + return s.ljust(w) + + if tablefmt == "html": + parts = [""] + if hdrs: + parts.append("") + parts.append("" + "".join(f"" for h in hdrs) + "") + parts.append("") + parts.append("") + for row in formatted: + parts.append("" + "".join(f"" for c in row) + "") + parts.append("") + parts.append("
{h}
{c}
") + return "\n".join(parts) + + if tablefmt == "simple_grid": + def _sep(left, mid, right): + return left + mid.join("\u2500" * (w + 2) for w in widths) + right + + top, mid_sep, bot = _sep("\u250c", "\u252c", "\u2510"), _sep("\u251c", "\u253c", "\u2524"), _sep("\u2514", "\u2534", "\u2518") + + def _fmt_row(cells): + split = [c.split('\n') for c in cells] + nlines = max(len(s) for s in split) + for s in split: + s.extend([""] * (nlines - len(s))) + return ["\u2502" + "\u2502".join(f" {_align(split[i][li], widths[i])} " for i in range(ncols)) + "\u2502" for li in range(nlines)] + + lines = [top] + if hdrs: + lines.extend(_fmt_row(hdrs)) + lines.append(mid_sep) + for ri, row in enumerate(formatted): + lines.extend(_fmt_row(row)) + lines.append(mid_sep if ri < len(formatted) - 1 else bot) + return "\n".join(lines) + + # simple + gap = " " + lines = [] + if hdrs: + lines.append(gap.join(h.ljust(w) for h, w in zip(hdrs, widths, strict=True))) + lines.append(gap.join("-" * w for w in widths)) + for row in formatted: + lines.append(gap.join(_align(row[i], widths[i]) for i in range(ncols))) + return "\n".join(lines) + + def retry(attempts=3, delay=1.0, ignore_failure=False): def decorator(func): @functools.wraps(func) diff --git a/pyproject.toml b/pyproject.toml index 2400364c7..8b0f4004d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -105,7 +105,6 @@ dev = [ "parameterized >=0.8, <0.9", "pyautogui", "pywinctl", - "tabulate", ] tools = [ diff --git a/selfdrive/test/process_replay/model_replay.py b/selfdrive/test/process_replay/model_replay.py index 9ba599bac..d35474f37 100755 --- a/selfdrive/test/process_replay/model_replay.py +++ b/selfdrive/test/process_replay/model_replay.py @@ -9,7 +9,7 @@ from itertools import zip_longest import matplotlib.pyplot as plt import numpy as np -from tabulate import tabulate +from openpilot.common.utils import tabulate from openpilot.common.git import get_commit from openpilot.system.hardware import PC diff --git a/selfdrive/test/test_onroad.py b/selfdrive/test/test_onroad.py index f57751c06..33e8685a3 100644 --- a/selfdrive/test/test_onroad.py +++ b/selfdrive/test/test_onroad.py @@ -8,7 +8,7 @@ import time import numpy as np from collections import Counter, defaultdict from pathlib import Path -from tabulate import tabulate +from openpilot.common.utils import tabulate from cereal import log import cereal.messaging as messaging diff --git a/system/hardware/tici/tests/test_power_draw.py b/system/hardware/tici/tests/test_power_draw.py index 4fbde8167..a92e4c8de 100644 --- a/system/hardware/tici/tests/test_power_draw.py +++ b/system/hardware/tici/tests/test_power_draw.py @@ -3,7 +3,7 @@ import pytest import time import numpy as np from dataclasses import dataclass -from tabulate import tabulate +from openpilot.common.utils import tabulate import cereal.messaging as messaging from cereal.services import SERVICE_LIST diff --git a/tools/longitudinal_maneuvers/generate_report.py b/tools/longitudinal_maneuvers/generate_report.py index 8c16e30d5..32bdb5b1c 100755 --- a/tools/longitudinal_maneuvers/generate_report.py +++ b/tools/longitudinal_maneuvers/generate_report.py @@ -9,7 +9,7 @@ import webbrowser from collections import defaultdict from pathlib import Path import matplotlib.pyplot as plt -from tabulate import tabulate +from openpilot.common.utils import tabulate from openpilot.tools.lib.logreader import LogReader from openpilot.system.hardware.hw import Paths diff --git a/uv.lock b/uv.lock index 054a67cc9..81e2d9136 100644 --- a/uv.lock +++ b/uv.lock @@ -1,10 +1,6 @@ version = 1 revision = 3 -requires-python = ">=3.11, <3.13" -resolution-markers = [ - "python_full_version >= '3.12'", - "python_full_version < '3.12'", -] +requires-python = ">=3.12.3, <3.13" [[package]] name = "aiohappyeyeballs" @@ -30,23 +26,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/50/42/32cf8e7704ceb4481406eb87161349abb46a57fee3f008ba9cb610968646/aiohttp-3.13.3.tar.gz", hash = "sha256:a949eee43d3782f2daae4f4a2819b2cb9b0c5d3b7f7a927067cc84dafdbb9f88", size = 7844556, upload-time = "2026-01-03T17:33:05.204Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f1/4c/a164164834f03924d9a29dc3acd9e7ee58f95857e0b467f6d04298594ebb/aiohttp-3.13.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5b6073099fb654e0a068ae678b10feff95c5cae95bbfcbfa7af669d361a8aa6b", size = 746051, upload-time = "2026-01-03T17:29:43.287Z" }, - { url = "https://files.pythonhosted.org/packages/82/71/d5c31390d18d4f58115037c432b7e0348c60f6f53b727cad33172144a112/aiohttp-3.13.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cb93e166e6c28716c8c6aeb5f99dfb6d5ccf482d29fe9bf9a794110e6d0ab64", size = 499234, upload-time = "2026-01-03T17:29:44.822Z" }, - { url = "https://files.pythonhosted.org/packages/0e/c9/741f8ac91e14b1d2e7100690425a5b2b919a87a5075406582991fb7de920/aiohttp-3.13.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:28e027cf2f6b641693a09f631759b4d9ce9165099d2b5d92af9bd4e197690eea", size = 494979, upload-time = "2026-01-03T17:29:46.405Z" }, - { url = "https://files.pythonhosted.org/packages/75/b5/31d4d2e802dfd59f74ed47eba48869c1c21552c586d5e81a9d0d5c2ad640/aiohttp-3.13.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3b61b7169ababd7802f9568ed96142616a9118dd2be0d1866e920e77ec8fa92a", size = 1748297, upload-time = "2026-01-03T17:29:48.083Z" }, - { url = "https://files.pythonhosted.org/packages/1a/3e/eefad0ad42959f226bb79664826883f2687d602a9ae2941a18e0484a74d3/aiohttp-3.13.3-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:80dd4c21b0f6237676449c6baaa1039abae86b91636b6c91a7f8e61c87f89540", size = 1707172, upload-time = "2026-01-03T17:29:49.648Z" }, - { url = "https://files.pythonhosted.org/packages/c5/3a/54a64299fac2891c346cdcf2aa6803f994a2e4beeaf2e5a09dcc54acc842/aiohttp-3.13.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:65d2ccb7eabee90ce0503c17716fc77226be026dcc3e65cce859a30db715025b", size = 1805405, upload-time = "2026-01-03T17:29:51.244Z" }, - { url = "https://files.pythonhosted.org/packages/6c/70/ddc1b7169cf64075e864f64595a14b147a895a868394a48f6a8031979038/aiohttp-3.13.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5b179331a481cb5529fca8b432d8d3c7001cb217513c94cd72d668d1248688a3", size = 1899449, upload-time = "2026-01-03T17:29:53.938Z" }, - { url = "https://files.pythonhosted.org/packages/a1/7e/6815aab7d3a56610891c76ef79095677b8b5be6646aaf00f69b221765021/aiohttp-3.13.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d4c940f02f49483b18b079d1c27ab948721852b281f8b015c058100e9421dd1", size = 1748444, upload-time = "2026-01-03T17:29:55.484Z" }, - { url = "https://files.pythonhosted.org/packages/6b/f2/073b145c4100da5511f457dc0f7558e99b2987cf72600d42b559db856fbc/aiohttp-3.13.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f9444f105664c4ce47a2a7171a2418bce5b7bae45fb610f4e2c36045d85911d3", size = 1606038, upload-time = "2026-01-03T17:29:57.179Z" }, - { url = "https://files.pythonhosted.org/packages/0a/c1/778d011920cae03ae01424ec202c513dc69243cf2db303965615b81deeea/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:694976222c711d1d00ba131904beb60534f93966562f64440d0c9d41b8cdb440", size = 1724156, upload-time = "2026-01-03T17:29:58.914Z" }, - { url = "https://files.pythonhosted.org/packages/0e/cb/3419eabf4ec1e9ec6f242c32b689248365a1cf621891f6f0386632525494/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f33ed1a2bf1997a36661874b017f5c4b760f41266341af36febaf271d179f6d7", size = 1722340, upload-time = "2026-01-03T17:30:01.962Z" }, - { url = "https://files.pythonhosted.org/packages/7a/e5/76cf77bdbc435bf233c1f114edad39ed4177ccbfab7c329482b179cff4f4/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e636b3c5f61da31a92bf0d91da83e58fdfa96f178ba682f11d24f31944cdd28c", size = 1783041, upload-time = "2026-01-03T17:30:03.609Z" }, - { url = "https://files.pythonhosted.org/packages/9d/d4/dd1ca234c794fd29c057ce8c0566b8ef7fd6a51069de5f06fa84b9a1971c/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:5d2d94f1f5fcbe40838ac51a6ab5704a6f9ea42e72ceda48de5e6b898521da51", size = 1596024, upload-time = "2026-01-03T17:30:05.132Z" }, - { url = "https://files.pythonhosted.org/packages/55/58/4345b5f26661a6180afa686c473620c30a66afdf120ed3dd545bbc809e85/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2be0e9ccf23e8a94f6f0650ce06042cefc6ac703d0d7ab6c7a917289f2539ad4", size = 1804590, upload-time = "2026-01-03T17:30:07.135Z" }, - { url = "https://files.pythonhosted.org/packages/7b/06/05950619af6c2df7e0a431d889ba2813c9f0129cec76f663e547a5ad56f2/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9af5e68ee47d6534d36791bbe9b646d2a7c7deb6fc24d7943628edfbb3581f29", size = 1740355, upload-time = "2026-01-03T17:30:09.083Z" }, - { url = "https://files.pythonhosted.org/packages/3e/80/958f16de79ba0422d7c1e284b2abd0c84bc03394fbe631d0a39ffa10e1eb/aiohttp-3.13.3-cp311-cp311-win32.whl", hash = "sha256:a2212ad43c0833a873d0fb3c63fa1bacedd4cf6af2fee62bf4b739ceec3ab239", size = 433701, upload-time = "2026-01-03T17:30:10.869Z" }, - { url = "https://files.pythonhosted.org/packages/dc/f2/27cdf04c9851712d6c1b99df6821a6623c3c9e55956d4b1e318c337b5a48/aiohttp-3.13.3-cp311-cp311-win_amd64.whl", hash = "sha256:642f752c3eb117b105acbd87e2c143de710987e09860d674e068c4c2c441034f", size = 457678, upload-time = "2026-01-03T17:30:12.719Z" }, { url = "https://files.pythonhosted.org/packages/a0/be/4fc11f202955a69e0db803a12a062b8379c970c7c84f4882b6da17337cc1/aiohttp-3.13.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b903a4dfee7d347e2d87697d0713be59e0b87925be030c9178c5faa58ea58d5c", size = 739732, upload-time = "2026-01-03T17:30:14.23Z" }, { url = "https://files.pythonhosted.org/packages/97/2c/621d5b851f94fa0bb7430d6089b3aa970a9d9b75196bc93bb624b0db237a/aiohttp-3.13.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a45530014d7a1e09f4a55f4f43097ba0fd155089372e105e4bff4ca76cb1b168", size = 494293, upload-time = "2026-01-03T17:30:15.96Z" }, { url = "https://files.pythonhosted.org/packages/5d/43/4be01406b78e1be8320bb8316dc9c42dbab553d281c40364e0f862d5661c/aiohttp-3.13.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:27234ef6d85c914f9efeb77ff616dbf4ad2380be0cda40b4db086ffc7ddd1b7d", size = 493533, upload-time = "2026-01-03T17:30:17.431Z" }, @@ -132,12 +111,6 @@ version = "13.1.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/0c/9d/486d31e76784cc0ad943f420c5e05867263b32b37e2f4b0f7f22fdc1ca3a/av-13.1.0.tar.gz", hash = "sha256:d3da736c55847d8596eb8c26c60e036f193001db3bc5c10da8665622d906c17e", size = 3957908, upload-time = "2024-10-06T04:54:57.507Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/39/54/c4227080c9700384db90072ace70d89b6a288b3748bd2ec0e32580a49e7f/av-13.1.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:867385e6701464a5c95903e24d2e0df1c7e0dbf211ed91d0ce639cd687373e10", size = 24255112, upload-time = "2024-10-06T04:52:48.49Z" }, - { url = "https://files.pythonhosted.org/packages/32/4a/eb9348231655ca99b200b380f4edbceff7358c927a285badcc84b18fb1c9/av-13.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cb7a3f319401a46b0017771268ff4928501e77cf00b1a2aa0721e20b2fd1146e", size = 19467930, upload-time = "2024-10-06T04:52:52.118Z" }, - { url = "https://files.pythonhosted.org/packages/14/c7/48c80252bdbc3a75a54dd205a7fab8f613914009b9e5416202757208e040/av-13.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad904f860147bceaca65b0d3174a8153f35c570d465161d210f1879970b15559", size = 32207671, upload-time = "2024-10-06T04:52:55.82Z" }, - { url = "https://files.pythonhosted.org/packages/f9/66/3332c7fa8c43b65680a94f279ea3e832b5500de3a1392bac6112881e984b/av-13.1.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a906e017b29d0eb80d9ccf7a98d19268122da792dbb68eb741cfebba156e6aed", size = 31520911, upload-time = "2024-10-06T04:52:59.231Z" }, - { url = "https://files.pythonhosted.org/packages/e5/bb/2e03acb9b27591d97f700a3a6c27cfd1bc53fa148177747eda8a70cca1e9/av-13.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ce894d7847897da7be63277a0875bd93c51327134ac226c67978de014c7979f", size = 34048399, upload-time = "2024-10-06T04:53:03.934Z" }, - { url = "https://files.pythonhosted.org/packages/85/44/527aa3b65947d42cfe829326026edf0cd1a8c459390076034be275616c36/av-13.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:384bcdb5fc3238a263a5a25cc9efc690859fa4148cc4b07e00fae927178db22a", size = 25779569, upload-time = "2024-10-06T04:53:07.582Z" }, { url = "https://files.pythonhosted.org/packages/9b/aa/4bdd8ce59173574fc6e0c282c71ee6f96fca82643d97bf172bc4cb5a5674/av-13.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:261dbc3f4b55f4f8f3375b10b2258fca7f2ab7a6365c01bc65e77a0d5327a195", size = 24268674, upload-time = "2024-10-06T04:53:11.251Z" }, { url = "https://files.pythonhosted.org/packages/17/b4/b267dd5bad99eed49ec6731827c6bcb5ab03864bf732a7ebb81e3df79911/av-13.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83d259ef86b9054eb914bc7c6a7f6092a6d75cb939295e70ee979cfd92a67b99", size = 19475617, upload-time = "2024-10-06T04:53:13.832Z" }, { url = "https://files.pythonhosted.org/packages/68/32/4209e51f54d7b54a1feb576d309c671ed1ff437b54fcc4ec68c239199e0a/av-13.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3b4d3ca159eceab97e3c0fb08fe756520fb95508417f76e48198fda2a5b0806", size = 32468873, upload-time = "2024-10-06T04:53:17.639Z" }, @@ -199,12 +172,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/92/62/1e98662024915ecb09c6894c26a3f497f4afa66570af3f53db4651fc45f1/casadi-3.7.2.tar.gz", hash = "sha256:b4d7bd8acdc4180306903ae1c9eddaf41be2a3ae2fa7154c57174ae64acdc60d", size = 6053600, upload-time = "2025-09-10T10:05:49.521Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/29/01/d5e3058775ec8e24a01eb74d36099493b872536ef9e39f1e49624b977778/casadi-3.7.2-cp311-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:f43b0562d05a5e6e81f1885fc4ae426c382e36eebfd8d27f1baff6052178a9b0", size = 47115880, upload-time = "2025-09-10T07:52:24.399Z" }, - { url = "https://files.pythonhosted.org/packages/0e/cf/4af27e010d599a5419129d34fdde41637029a1cca2a40bef0965d6d52228/casadi-3.7.2-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:70add3334b437b60a9bc0f864d094350f1a4fcbf9e8bafec870b61aed64674df", size = 42293337, upload-time = "2025-09-10T08:03:32.556Z" }, - { url = "https://files.pythonhosted.org/packages/ac/4c/d1a50cc840103e00effcbaf8e911b6b3fb6ba2c8f4025466f524854968ed/casadi-3.7.2-cp311-none-manylinux2014_aarch64.whl", hash = "sha256:392d3367a4b33cf223013dad8122a0e549da40b1702a5375f82f85b563e5c0cf", size = 47277175, upload-time = "2025-09-10T08:04:08.811Z" }, - { url = "https://files.pythonhosted.org/packages/be/29/6e5714d124e6ddafbccc3ed774ca603081caa1175c7f0e1c52484184dfb3/casadi-3.7.2-cp311-none-manylinux2014_i686.whl", hash = "sha256:2ce09e0ced6df33048dccd582b5cfa2c9ff5193b12858b2584078afc17761905", size = 72438460, upload-time = "2025-09-10T08:05:02.769Z" }, - { url = "https://files.pythonhosted.org/packages/23/32/ac1f3999273aa4aae48516f6f4b7b267e0cc70d8527866989798cb81312f/casadi-3.7.2-cp311-none-manylinux2014_x86_64.whl", hash = "sha256:5086799a46d10ba884b72fd02c21be09dae52cbc189272354a5d424791b55f37", size = 75574474, upload-time = "2025-09-10T08:06:00.709Z" }, - { url = "https://files.pythonhosted.org/packages/68/78/7fd10709504c1757f70db3893870a891fcb9f1ec9f05e8ef2e3f3b9d7e2f/casadi-3.7.2-cp311-none-win_amd64.whl", hash = "sha256:72aa5727417d781ed216f16b5e93c6ddca5db27d83b0015a729e8ad570cdc465", size = 50994144, upload-time = "2025-09-10T08:06:42.384Z" }, { url = "https://files.pythonhosted.org/packages/65/c8/689d085447b1966f42bdb8aa4fbebef49a09697dbee32ab02a865c17ac1b/casadi-3.7.2-cp312-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:309ea41a69c9230390d349b0dd899c6a19504d1904c0756bef463e47fb5c8f9a", size = 47116756, upload-time = "2025-09-10T07:53:00.931Z" }, { url = "https://files.pythonhosted.org/packages/1e/c0/3c4704394a6fd4dfb2123a4fd71ba64a001f340670a3eba45be7a19ac736/casadi-3.7.2-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:6033381234db810b2247d16c6352e679a009ec4365d04008fc768866e011ed58", size = 42293718, upload-time = "2025-09-10T08:07:16.415Z" }, { url = "https://files.pythonhosted.org/packages/f3/24/4cf05469ddf8544da5e92f359f96d716a97e7482999f085a632bc4ef344a/casadi-3.7.2-cp312-none-manylinux2014_aarch64.whl", hash = "sha256:732f2804d0766454bb75596339e4f2da6662ffb669621da0f630ed4af9e83d6a", size = 47276175, upload-time = "2025-09-10T08:08:09.29Z" }, @@ -231,19 +198,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" }, - { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" }, - { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" }, - { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" }, - { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" }, - { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" }, - { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" }, - { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" }, - { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" }, - { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" }, - { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" }, - { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" }, - { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" }, { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, @@ -264,22 +218,6 @@ version = "3.4.4" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", size = 206988, upload-time = "2025-10-14T04:40:33.79Z" }, - { url = "https://files.pythonhosted.org/packages/94/59/2e87300fe67ab820b5428580a53cad894272dbb97f38a7a814a2a1ac1011/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", size = 147324, upload-time = "2025-10-14T04:40:34.961Z" }, - { url = "https://files.pythonhosted.org/packages/07/fb/0cf61dc84b2b088391830f6274cb57c82e4da8bbc2efeac8c025edb88772/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", size = 142742, upload-time = "2025-10-14T04:40:36.105Z" }, - { url = "https://files.pythonhosted.org/packages/62/8b/171935adf2312cd745d290ed93cf16cf0dfe320863ab7cbeeae1dcd6535f/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", size = 160863, upload-time = "2025-10-14T04:40:37.188Z" }, - { url = "https://files.pythonhosted.org/packages/09/73/ad875b192bda14f2173bfc1bc9a55e009808484a4b256748d931b6948442/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", size = 157837, upload-time = "2025-10-14T04:40:38.435Z" }, - { url = "https://files.pythonhosted.org/packages/6d/fc/de9cce525b2c5b94b47c70a4b4fb19f871b24995c728e957ee68ab1671ea/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", size = 151550, upload-time = "2025-10-14T04:40:40.053Z" }, - { url = "https://files.pythonhosted.org/packages/55/c2/43edd615fdfba8c6f2dfbd459b25a6b3b551f24ea21981e23fb768503ce1/charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", size = 149162, upload-time = "2025-10-14T04:40:41.163Z" }, - { url = "https://files.pythonhosted.org/packages/03/86/bde4ad8b4d0e9429a4e82c1e8f5c659993a9a863ad62c7df05cf7b678d75/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", size = 150019, upload-time = "2025-10-14T04:40:42.276Z" }, - { url = "https://files.pythonhosted.org/packages/1f/86/a151eb2af293a7e7bac3a739b81072585ce36ccfb4493039f49f1d3cae8c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", size = 143310, upload-time = "2025-10-14T04:40:43.439Z" }, - { url = "https://files.pythonhosted.org/packages/b5/fe/43dae6144a7e07b87478fdfc4dbe9efd5defb0e7ec29f5f58a55aeef7bf7/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", size = 162022, upload-time = "2025-10-14T04:40:44.547Z" }, - { url = "https://files.pythonhosted.org/packages/80/e6/7aab83774f5d2bca81f42ac58d04caf44f0cc2b65fc6db2b3b2e8a05f3b3/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", size = 149383, upload-time = "2025-10-14T04:40:46.018Z" }, - { url = "https://files.pythonhosted.org/packages/4f/e8/b289173b4edae05c0dde07f69f8db476a0b511eac556dfe0d6bda3c43384/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", size = 159098, upload-time = "2025-10-14T04:40:47.081Z" }, - { url = "https://files.pythonhosted.org/packages/d8/df/fe699727754cae3f8478493c7f45f777b17c3ef0600e28abfec8619eb49c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", size = 152991, upload-time = "2025-10-14T04:40:48.246Z" }, - { url = "https://files.pythonhosted.org/packages/1a/86/584869fe4ddb6ffa3bd9f491b87a01568797fb9bd8933f557dba9771beaf/charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", size = 99456, upload-time = "2025-10-14T04:40:49.376Z" }, - { url = "https://files.pythonhosted.org/packages/65/f6/62fdd5feb60530f50f7e38b4f6a1d5203f4d16ff4f9f0952962c044e919a/charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", size = 106978, upload-time = "2025-10-14T04:40:50.844Z" }, - { url = "https://files.pythonhosted.org/packages/7a/9d/0710916e6c82948b3be62d9d398cb4fcf4e97b56d6a6aeccd66c4b2f2bd5/charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", size = 99969, upload-time = "2025-10-14T04:40:52.272Z" }, { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" }, { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" }, { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" }, @@ -347,17 +285,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/91/2e/c4390a31919d8a78b90e8ecf87cd4b4c4f05a5b48d05ec17db8e5404c6f4/contourpy-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:709a48ef9a690e1343202916450bc48b9e51c049b089c7f79a267b46cffcdaa1", size = 288773, upload-time = "2025-07-26T12:01:02.277Z" }, - { url = "https://files.pythonhosted.org/packages/0d/44/c4b0b6095fef4dc9c420e041799591e3b63e9619e3044f7f4f6c21c0ab24/contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:23416f38bfd74d5d28ab8429cc4d63fa67d5068bd711a85edb1c3fb0c3e2f381", size = 270149, upload-time = "2025-07-26T12:01:04.072Z" }, - { url = "https://files.pythonhosted.org/packages/30/2e/dd4ced42fefac8470661d7cb7e264808425e6c5d56d175291e93890cce09/contourpy-1.3.3-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:929ddf8c4c7f348e4c0a5a3a714b5c8542ffaa8c22954862a46ca1813b667ee7", size = 329222, upload-time = "2025-07-26T12:01:05.688Z" }, - { url = "https://files.pythonhosted.org/packages/f2/74/cc6ec2548e3d276c71389ea4802a774b7aa3558223b7bade3f25787fafc2/contourpy-1.3.3-cp311-cp311-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e999574eddae35f1312c2b4b717b7885d4edd6cb46700e04f7f02db454e67c1", size = 377234, upload-time = "2025-07-26T12:01:07.054Z" }, - { url = "https://files.pythonhosted.org/packages/03/b3/64ef723029f917410f75c09da54254c5f9ea90ef89b143ccadb09df14c15/contourpy-1.3.3-cp311-cp311-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf67e0e3f482cb69779dd3061b534eb35ac9b17f163d851e2a547d56dba0a3a", size = 380555, upload-time = "2025-07-26T12:01:08.801Z" }, - { url = "https://files.pythonhosted.org/packages/5f/4b/6157f24ca425b89fe2eb7e7be642375711ab671135be21e6faa100f7448c/contourpy-1.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51e79c1f7470158e838808d4a996fa9bac72c498e93d8ebe5119bc1e6becb0db", size = 355238, upload-time = "2025-07-26T12:01:10.319Z" }, - { url = "https://files.pythonhosted.org/packages/98/56/f914f0dd678480708a04cfd2206e7c382533249bc5001eb9f58aa693e200/contourpy-1.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:598c3aaece21c503615fd59c92a3598b428b2f01bfb4b8ca9c4edeecc2438620", size = 1326218, upload-time = "2025-07-26T12:01:12.659Z" }, - { url = "https://files.pythonhosted.org/packages/fb/d7/4a972334a0c971acd5172389671113ae82aa7527073980c38d5868ff1161/contourpy-1.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:322ab1c99b008dad206d406bb61d014cf0174df491ae9d9d0fac6a6fda4f977f", size = 1392867, upload-time = "2025-07-26T12:01:15.533Z" }, - { url = "https://files.pythonhosted.org/packages/75/3e/f2cc6cd56dc8cff46b1a56232eabc6feea52720083ea71ab15523daab796/contourpy-1.3.3-cp311-cp311-win32.whl", hash = "sha256:fd907ae12cd483cd83e414b12941c632a969171bf90fc937d0c9f268a31cafff", size = 183677, upload-time = "2025-07-26T12:01:17.088Z" }, - { url = "https://files.pythonhosted.org/packages/98/4b/9bd370b004b5c9d8045c6c33cf65bae018b27aca550a3f657cdc99acdbd8/contourpy-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:3519428f6be58431c56581f1694ba8e50626f2dd550af225f82fb5f5814d2a42", size = 225234, upload-time = "2025-07-26T12:01:18.256Z" }, - { url = "https://files.pythonhosted.org/packages/d9/b6/71771e02c2e004450c12b1120a5f488cad2e4d5b590b1af8bad060360fe4/contourpy-1.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:15ff10bfada4bf92ec8b31c62bf7c1834c244019b4a33095a68000d7075df470", size = 193123, upload-time = "2025-07-26T12:01:19.848Z" }, { url = "https://files.pythonhosted.org/packages/be/45/adfee365d9ea3d853550b2e735f9d66366701c65db7855cd07621732ccfc/contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb", size = 293419, upload-time = "2025-07-26T12:01:21.16Z" }, { url = "https://files.pythonhosted.org/packages/53/3e/405b59cfa13021a56bba395a6b3aca8cec012b45bf177b0eaf7a202cde2c/contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6", size = 273979, upload-time = "2025-07-26T12:01:22.448Z" }, { url = "https://files.pythonhosted.org/packages/d4/1c/a12359b9b2ca3a845e8f7f9ac08bdf776114eb931392fcad91743e2ea17b/contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7", size = 332653, upload-time = "2025-07-26T12:01:24.155Z" }, @@ -369,11 +296,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cf/8f/5847f44a7fddf859704217a99a23a4f6417b10e5ab1256a179264561540e/contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69", size = 185018, upload-time = "2025-07-26T12:01:35.64Z" }, { url = "https://files.pythonhosted.org/packages/19/e8/6026ed58a64563186a9ee3f29f41261fd1828f527dd93d33b60feca63352/contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b", size = 226567, upload-time = "2025-07-26T12:01:36.804Z" }, { url = "https://files.pythonhosted.org/packages/d1/e2/f05240d2c39a1ed228d8328a78b6f44cd695f7ef47beb3e684cf93604f86/contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc", size = 193655, upload-time = "2025-07-26T12:01:37.999Z" }, - { url = "https://files.pythonhosted.org/packages/a5/29/8dcfe16f0107943fa92388c23f6e05cff0ba58058c4c95b00280d4c75a14/contourpy-1.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cd5dfcaeb10f7b7f9dc8941717c6c2ade08f587be2226222c12b25f0483ed497", size = 278809, upload-time = "2025-07-26T12:02:52.74Z" }, - { url = "https://files.pythonhosted.org/packages/85/a9/8b37ef4f7dafeb335daee3c8254645ef5725be4d9c6aa70b50ec46ef2f7e/contourpy-1.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:0c1fc238306b35f246d61a1d416a627348b5cf0648648a031e14bb8705fcdfe8", size = 261593, upload-time = "2025-07-26T12:02:54.037Z" }, - { url = "https://files.pythonhosted.org/packages/0a/59/ebfb8c677c75605cc27f7122c90313fd2f375ff3c8d19a1694bda74aaa63/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70f9aad7de812d6541d29d2bbf8feb22ff7e1c299523db288004e3157ff4674e", size = 302202, upload-time = "2025-07-26T12:02:55.947Z" }, - { url = "https://files.pythonhosted.org/packages/3c/37/21972a15834d90bfbfb009b9d004779bd5a07a0ec0234e5ba8f64d5736f4/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ed3657edf08512fc3fe81b510e35c2012fbd3081d2e26160f27ca28affec989", size = 329207, upload-time = "2025-07-26T12:02:57.468Z" }, - { url = "https://files.pythonhosted.org/packages/0c/58/bd257695f39d05594ca4ad60df5bcb7e32247f9951fd09a9b8edb82d1daa/contourpy-1.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:3d1a3799d62d45c18bafd41c5fa05120b96a28079f2393af559b843d1a966a77", size = 225315, upload-time = "2025-07-26T12:02:58.801Z" }, ] [[package]] @@ -382,19 +304,6 @@ version = "7.13.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/ad/49/349848445b0e53660e258acbcc9b0d014895b6739237920886672240f84b/coverage-7.13.2.tar.gz", hash = "sha256:044c6951ec37146b72a50cc81ef02217d27d4c3640efd2640311393cbbf143d3", size = 826523, upload-time = "2026-01-25T13:00:04.889Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6c/01/abca50583a8975bb6e1c59eff67ed8e48bb127c07dad5c28d9e96ccc09ec/coverage-7.13.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:060ebf6f2c51aff5ba38e1f43a2095e087389b1c69d559fde6049a4b0001320e", size = 218971, upload-time = "2026-01-25T12:57:36.953Z" }, - { url = "https://files.pythonhosted.org/packages/eb/0e/b6489f344d99cd1e5b4d5e1be52dfd3f8a3dc5112aa6c33948da8cabad4e/coverage-7.13.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c1ea8ca9db5e7469cd364552985e15911548ea5b69c48a17291f0cac70484b2e", size = 219473, upload-time = "2026-01-25T12:57:38.934Z" }, - { url = "https://files.pythonhosted.org/packages/17/11/db2f414915a8e4ec53f60b17956c27f21fb68fcf20f8a455ce7c2ccec638/coverage-7.13.2-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b780090d15fd58f07cf2011943e25a5f0c1c894384b13a216b6c86c8a8a7c508", size = 249896, upload-time = "2026-01-25T12:57:40.365Z" }, - { url = "https://files.pythonhosted.org/packages/80/06/0823fe93913663c017e508e8810c998c8ebd3ec2a5a85d2c3754297bdede/coverage-7.13.2-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:88a800258d83acb803c38175b4495d293656d5fac48659c953c18e5f539a274b", size = 251810, upload-time = "2026-01-25T12:57:42.045Z" }, - { url = "https://files.pythonhosted.org/packages/61/dc/b151c3cc41b28cdf7f0166c5fa1271cbc305a8ec0124cce4b04f74791a18/coverage-7.13.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6326e18e9a553e674d948536a04a80d850a5eeefe2aae2e6d7cf05d54046c01b", size = 253920, upload-time = "2026-01-25T12:57:44.026Z" }, - { url = "https://files.pythonhosted.org/packages/2d/35/e83de0556e54a4729a2b94ea816f74ce08732e81945024adee46851c2264/coverage-7.13.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:59562de3f797979e1ff07c587e2ac36ba60ca59d16c211eceaa579c266c5022f", size = 250025, upload-time = "2026-01-25T12:57:45.624Z" }, - { url = "https://files.pythonhosted.org/packages/39/67/af2eb9c3926ce3ea0d58a0d2516fcbdacf7a9fc9559fe63076beaf3f2596/coverage-7.13.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:27ba1ed6f66b0e2d61bfa78874dffd4f8c3a12f8e2b5410e515ab345ba7bc9c3", size = 251612, upload-time = "2026-01-25T12:57:47.713Z" }, - { url = "https://files.pythonhosted.org/packages/26/62/5be2e25f3d6c711d23b71296f8b44c978d4c8b4e5b26871abfc164297502/coverage-7.13.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8be48da4d47cc68754ce643ea50b3234557cbefe47c2f120495e7bd0a2756f2b", size = 249670, upload-time = "2026-01-25T12:57:49.378Z" }, - { url = "https://files.pythonhosted.org/packages/b3/51/400d1b09a8344199f9b6a6fc1868005d766b7ea95e7882e494fa862ca69c/coverage-7.13.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:2a47a4223d3361b91176aedd9d4e05844ca67d7188456227b6bf5e436630c9a1", size = 249395, upload-time = "2026-01-25T12:57:50.86Z" }, - { url = "https://files.pythonhosted.org/packages/e0/36/f02234bc6e5230e2f0a63fd125d0a2093c73ef20fdf681c7af62a140e4e7/coverage-7.13.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c6f141b468740197d6bd38f2b26ade124363228cc3f9858bd9924ab059e00059", size = 250298, upload-time = "2026-01-25T12:57:52.287Z" }, - { url = "https://files.pythonhosted.org/packages/b0/06/713110d3dd3151b93611c9cbfc65c15b4156b44f927fced49ac0b20b32a4/coverage-7.13.2-cp311-cp311-win32.whl", hash = "sha256:89567798404af067604246e01a49ef907d112edf2b75ef814b1364d5ce267031", size = 221485, upload-time = "2026-01-25T12:57:53.876Z" }, - { url = "https://files.pythonhosted.org/packages/16/0c/3ae6255fa1ebcb7dec19c9a59e85ef5f34566d1265c70af5b2fc981da834/coverage-7.13.2-cp311-cp311-win_amd64.whl", hash = "sha256:21dd57941804ae2ac7e921771a5e21bbf9aabec317a041d164853ad0a96ce31e", size = 222421, upload-time = "2026-01-25T12:57:55.433Z" }, - { url = "https://files.pythonhosted.org/packages/b5/37/fabc3179af4d61d89ea47bd04333fec735cd5e8b59baad44fed9fc4170d7/coverage-7.13.2-cp311-cp311-win_arm64.whl", hash = "sha256:10758e0586c134a0bafa28f2d37dd2cdb5e4a90de25c0fc0c77dabbad46eca28", size = 221088, upload-time = "2026-01-25T12:57:57.41Z" }, { url = "https://files.pythonhosted.org/packages/46/39/e92a35f7800222d3f7b2cbb7bbc3b65672ae8d501cb31801b2d2bd7acdf1/coverage-7.13.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f106b2af193f965d0d3234f3f83fc35278c7fb935dfbde56ae2da3dd2c03b84d", size = 219142, upload-time = "2026-01-25T12:58:00.448Z" }, { url = "https://files.pythonhosted.org/packages/45/7a/8bf9e9309c4c996e65c52a7c5a112707ecdd9fbaf49e10b5a705a402bbb4/coverage-7.13.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78f45d21dc4d5d6bd29323f0320089ef7eae16e4bef712dff79d184fa7330af3", size = 219503, upload-time = "2026-01-25T12:58:02.451Z" }, { url = "https://files.pythonhosted.org/packages/87/93/17661e06b7b37580923f3f12406ac91d78aeed293fb6da0b69cc7957582f/coverage-7.13.2-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:fae91dfecd816444c74531a9c3d6ded17a504767e97aa674d44f638107265b99", size = 251006, upload-time = "2026-01-25T12:58:04.059Z" }, @@ -425,10 +334,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8e/ba/501ef1b02119402cf1a31c01eb2cb8399660bca863c2f4dd3dc060220284/crcmod_plus-2.3.1-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9166bc3c9b5e7b07b4e6854cac392b4a451b31d58d3950e48c140ab7b5d05394", size = 27135, upload-time = "2025-10-10T22:13:51.889Z" }, { url = "https://files.pythonhosted.org/packages/49/90/d4556c9db69c83e726c5b88da3d656fdaac7d60c4d27b43cb939bed80069/crcmod_plus-2.3.1-cp311-abi3-win32.whl", hash = "sha256:cb99b694cce5c862560cf332a8b5e793620e28f0de3726995608bbd6f9b6e09a", size = 22384, upload-time = "2025-10-10T22:13:53.016Z" }, { url = "https://files.pythonhosted.org/packages/4d/7e/57bb97a8c7b4e19900744f58b67dc83bc9c83aaac670deeede9fb3bfab6a/crcmod_plus-2.3.1-cp311-abi3-win_amd64.whl", hash = "sha256:82b0f7e968c430c5a80fe0fc59e75cb54f2e84df2ed0cee5a3ff9cadfbf8a220", size = 22912, upload-time = "2025-10-10T22:13:53.849Z" }, - { url = "https://files.pythonhosted.org/packages/76/66/419ae3991bb68943cb752e2f4d317c555e3f02a298dd498f26113874ee59/crcmod_plus-2.3.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:9397324da1be2729f894744d9031a21ed97584c17fb0289e69e0c3c60916fc5f", size = 19880, upload-time = "2025-10-10T22:14:17.269Z" }, - { url = "https://files.pythonhosted.org/packages/18/f0/d10c9b859927b2cdc38eafc33c8b66e4ede02eaa174df4575681dab5a0f1/crcmod_plus-2.3.1-pp311-pypy311_pp73-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:073c7a3b832652e66c41c8b8705eaecda704d1cbe850b9fa05fdee36cd50745a", size = 21120, upload-time = "2025-10-10T22:14:18.117Z" }, - { url = "https://files.pythonhosted.org/packages/6c/68/cbd8f1707b37b80f9a0bf643e04747b0196f69cf065b52ed56639afbecef/crcmod_plus-2.3.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e5f4c62553f772ea7ae12d9484801b752622c9c288e49ee7ea34a20b94e4920", size = 21698, upload-time = "2025-10-10T22:14:20.044Z" }, - { url = "https://files.pythonhosted.org/packages/41/1b/4ab1681ecbfc48d7e4641fb178c97374eb475ae4109255bdd832110cbbe2/crcmod_plus-2.3.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:5e80a9860f66f339956f540d86a768f4fe8c8bfcb139811f14be864425c48d64", size = 23289, upload-time = "2025-10-10T22:14:20.875Z" }, ] [[package]] @@ -475,10 +380,6 @@ version = "3.2.4" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/91/85/7574c9cd44b69a27210444b6650f6477f56c75fee1b70d7672d3e4166167/cython-3.2.4.tar.gz", hash = "sha256:84226ecd313b233da27dc2eb3601b4f222b8209c3a7216d8733b031da1dc64e6", size = 3280291, upload-time = "2026-01-04T14:14:14.473Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/85/cc/8f06145ec3efa121c8b1b67f06a640386ddacd77ee3e574da582a21b14ee/cython-3.2.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ff9af2134c05e3734064808db95b4dd7341a39af06e8945d05ea358e1741aaed", size = 2953769, upload-time = "2026-01-04T14:15:00.361Z" }, - { url = "https://files.pythonhosted.org/packages/55/b0/706cf830eddd831666208af1b3058c2e0758ae157590909c1f634b53bed9/cython-3.2.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:67922c9de058a0bfb72d2e75222c52d09395614108c68a76d9800f150296ddb3", size = 3243841, upload-time = "2026-01-04T14:15:02.066Z" }, - { url = "https://files.pythonhosted.org/packages/ac/25/58893afd4ef45f79e3d4db82742fa4ff874b936d67a83c92939053920ccd/cython-3.2.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b362819d155fff1482575e804e43e3a8825332d32baa15245f4642022664a3f4", size = 3378083, upload-time = "2026-01-04T14:15:04.248Z" }, - { url = "https://files.pythonhosted.org/packages/32/e4/424a004d7c0d8a4050c81846ebbd22272ececfa9a498cb340aa44fccbec2/cython-3.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:1a64a112a34ec719b47c01395647e54fb4cf088a511613f9a3a5196694e8e382", size = 2769990, upload-time = "2026-01-04T14:15:06.53Z" }, { url = "https://files.pythonhosted.org/packages/91/4d/1eb0c7c196a136b1926f4d7f0492a96c6fabd604d77e6cd43b56a3a16d83/cython-3.2.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:64d7f71be3dd6d6d4a4c575bb3a4674ea06d1e1e5e4cd1b9882a2bc40ed3c4c9", size = 2970064, upload-time = "2026-01-04T14:15:08.567Z" }, { url = "https://files.pythonhosted.org/packages/03/1c/46e34b08bea19a1cdd1e938a4c123e6299241074642db9d81983cef95e9f/cython-3.2.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:869487ea41d004f8b92171f42271fbfadb1ec03bede3158705d16cd570d6b891", size = 3226757, upload-time = "2026-01-04T14:15:10.812Z" }, { url = "https://files.pythonhosted.org/packages/12/33/3298a44d201c45bcf0d769659725ae70e9c6c42adf8032f6d89c8241098d/cython-3.2.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:55b6c44cd30821f0b25220ceba6fe636ede48981d2a41b9bbfe3c7902ce44ea7", size = 3388969, upload-time = "2026-01-04T14:15:12.45Z" }, @@ -500,10 +401,6 @@ name = "dearpygui" version = "2.1.1" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/57/b5/d5bae262633d05f82aeeb3f84ae6240fe3f2955ab6fb4509ebf8048a0b9e/dearpygui-2.1.1-cp311-cp311-macosx_10_6_x86_64.whl", hash = "sha256:8a7c8608b365f4b380b7326679023595fecd04b78c514f2cfd349b0a1108bd0e", size = 2100934, upload-time = "2025-11-14T14:47:38.172Z" }, - { url = "https://files.pythonhosted.org/packages/93/86/75fa9a0f0a7b4b62810cc6f1e8ebaea3df0a825c0adf27d2024aaac2d178/dearpygui-2.1.1-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:ee87153fdd494ccead8c2345553acd7a9ee61c031e3223f4160aa560709248a3", size = 1895473, upload-time = "2025-11-14T14:47:47.064Z" }, - { url = "https://files.pythonhosted.org/packages/ab/7a/e109e06f8f4379d41a4e672c49aba42e7fcf0eec88056fa06185f4e52c98/dearpygui-2.1.1-cp311-cp311-manylinux1_x86_64.whl", hash = "sha256:964fbb3735017b919efa58104b2d7e9b84a168ff5c1031ae0652d5bc0a48bf5b", size = 2640408, upload-time = "2025-11-14T14:47:53.124Z" }, - { url = "https://files.pythonhosted.org/packages/f3/b5/2ec29d9b47c30ecee96c6f6a0cf229f2898ce3e133a1a0e5b0cd5db82e6b/dearpygui-2.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:6141184ff59fa4b8df1b81b077cb8cc2b2ef9c0ff92e69c6063062b6d251f426", size = 1808736, upload-time = "2025-11-14T14:47:26.46Z" }, { url = "https://files.pythonhosted.org/packages/79/41/2146e8d03d28b5a66d5282beb26ffd9ab68a729a29d31e2fe91809271bf5/dearpygui-2.1.1-cp312-cp312-macosx_10_6_x86_64.whl", hash = "sha256:238aea7b4be7376f564dae8edd563b280ec1483a03786022969938507691e017", size = 2101529, upload-time = "2025-11-14T14:47:39.646Z" }, { url = "https://files.pythonhosted.org/packages/b0/c5/fcc37ef834fe225241aa4f18d77aaa2903134f283077978d65a901c624c6/dearpygui-2.1.1-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:c27ca6ecd4913555b717f3bb341c0b6a27d6c9fdc9932f0b3c31ae2ef893ae35", size = 1895555, upload-time = "2025-11-14T14:47:48.149Z" }, { url = "https://files.pythonhosted.org/packages/74/66/19f454ba02d5f03a847cc1dfee4a849cd2307d97add5ba26fecdca318adb/dearpygui-2.1.1-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:8c071e9c165d89217bdcdaf769c6069252fcaee50bf369489add524107932273", size = 2641509, upload-time = "2025-11-14T14:47:54.581Z" }, @@ -573,14 +470,6 @@ version = "4.61.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/ec/ca/cf17b88a8df95691275a3d77dc0a5ad9907f328ae53acbe6795da1b2f5ed/fonttools-4.61.1.tar.gz", hash = "sha256:6675329885c44657f826ef01d9e4fb33b9158e9d93c537d84ad8399539bc6f69", size = 3565756, upload-time = "2025-12-12T17:31:24.246Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/69/12/bf9f4eaa2fad039356cc627587e30ed008c03f1cebd3034376b5ee8d1d44/fonttools-4.61.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c6604b735bb12fef8e0efd5578c9fb5d3d8532d5001ea13a19cddf295673ee09", size = 2852213, upload-time = "2025-12-12T17:29:46.675Z" }, - { url = "https://files.pythonhosted.org/packages/ac/49/4138d1acb6261499bedde1c07f8c2605d1d8f9d77a151e5507fd3ef084b6/fonttools-4.61.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5ce02f38a754f207f2f06557523cd39a06438ba3aafc0639c477ac409fc64e37", size = 2401689, upload-time = "2025-12-12T17:29:48.769Z" }, - { url = "https://files.pythonhosted.org/packages/e5/fe/e6ce0fe20a40e03aef906af60aa87668696f9e4802fa283627d0b5ed777f/fonttools-4.61.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77efb033d8d7ff233385f30c62c7c79271c8885d5c9657d967ede124671bbdfb", size = 5058809, upload-time = "2025-12-12T17:29:51.701Z" }, - { url = "https://files.pythonhosted.org/packages/79/61/1ca198af22f7dd22c17ab86e9024ed3c06299cfdb08170640e9996d501a0/fonttools-4.61.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:75c1a6dfac6abd407634420c93864a1e274ebc1c7531346d9254c0d8f6ca00f9", size = 5036039, upload-time = "2025-12-12T17:29:53.659Z" }, - { url = "https://files.pythonhosted.org/packages/99/cc/fa1801e408586b5fce4da9f5455af8d770f4fc57391cd5da7256bb364d38/fonttools-4.61.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0de30bfe7745c0d1ffa2b0b7048fb7123ad0d71107e10ee090fa0b16b9452e87", size = 5034714, upload-time = "2025-12-12T17:29:55.592Z" }, - { url = "https://files.pythonhosted.org/packages/bf/aa/b7aeafe65adb1b0a925f8f25725e09f078c635bc22754f3fecb7456955b0/fonttools-4.61.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:58b0ee0ab5b1fc9921eccfe11d1435added19d6494dde14e323f25ad2bc30c56", size = 5158648, upload-time = "2025-12-12T17:29:57.861Z" }, - { url = "https://files.pythonhosted.org/packages/99/f9/08ea7a38663328881384c6e7777bbefc46fd7d282adfd87a7d2b84ec9d50/fonttools-4.61.1-cp311-cp311-win32.whl", hash = "sha256:f79b168428351d11e10c5aeb61a74e1851ec221081299f4cf56036a95431c43a", size = 2280681, upload-time = "2025-12-12T17:29:59.943Z" }, - { url = "https://files.pythonhosted.org/packages/07/ad/37dd1ae5fa6e01612a1fbb954f0927681f282925a86e86198ccd7b15d515/fonttools-4.61.1-cp311-cp311-win_amd64.whl", hash = "sha256:fe2efccb324948a11dd09d22136fe2ac8a97d6c1347cf0b58a911dcd529f66b7", size = 2331951, upload-time = "2025-12-12T17:30:02.254Z" }, { url = "https://files.pythonhosted.org/packages/6f/16/7decaa24a1bd3a70c607b2e29f0adc6159f36a7e40eaba59846414765fd4/fonttools-4.61.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f3cb4a569029b9f291f88aafc927dd53683757e640081ca8c412781ea144565e", size = 2851593, upload-time = "2025-12-12T17:30:04.225Z" }, { url = "https://files.pythonhosted.org/packages/94/98/3c4cb97c64713a8cf499b3245c3bf9a2b8fd16a3e375feff2aed78f96259/fonttools-4.61.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41a7170d042e8c0024703ed13b71893519a1a6d6e18e933e3ec7507a2c26a4b2", size = 2400231, upload-time = "2025-12-12T17:30:06.47Z" }, { url = "https://files.pythonhosted.org/packages/b7/37/82dbef0f6342eb01f54bca073ac1498433d6ce71e50c3c3282b655733b31/fonttools-4.61.1-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10d88e55330e092940584774ee5e8a6971b01fc2f4d3466a1d6c158230880796", size = 4954103, upload-time = "2025-12-12T17:30:08.432Z" }, @@ -598,22 +487,6 @@ version = "1.8.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875, upload-time = "2025-10-06T05:38:17.865Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bc/03/077f869d540370db12165c0aa51640a873fb661d8b315d1d4d67b284d7ac/frozenlist-1.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:09474e9831bc2b2199fad6da3c14c7b0fbdd377cce9d3d77131be28906cb7d84", size = 86912, upload-time = "2025-10-06T05:35:45.98Z" }, - { url = "https://files.pythonhosted.org/packages/df/b5/7610b6bd13e4ae77b96ba85abea1c8cb249683217ef09ac9e0ae93f25a91/frozenlist-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:17c883ab0ab67200b5f964d2b9ed6b00971917d5d8a92df149dc2c9779208ee9", size = 50046, upload-time = "2025-10-06T05:35:47.009Z" }, - { url = "https://files.pythonhosted.org/packages/6e/ef/0e8f1fe32f8a53dd26bdd1f9347efe0778b0fddf62789ea683f4cc7d787d/frozenlist-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa47e444b8ba08fffd1c18e8cdb9a75db1b6a27f17507522834ad13ed5922b93", size = 50119, upload-time = "2025-10-06T05:35:48.38Z" }, - { url = "https://files.pythonhosted.org/packages/11/b1/71a477adc7c36e5fb628245dfbdea2166feae310757dea848d02bd0689fd/frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2552f44204b744fba866e573be4c1f9048d6a324dfe14475103fd51613eb1d1f", size = 231067, upload-time = "2025-10-06T05:35:49.97Z" }, - { url = "https://files.pythonhosted.org/packages/45/7e/afe40eca3a2dc19b9904c0f5d7edfe82b5304cb831391edec0ac04af94c2/frozenlist-1.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:957e7c38f250991e48a9a73e6423db1bb9dd14e722a10f6b8bb8e16a0f55f695", size = 233160, upload-time = "2025-10-06T05:35:51.729Z" }, - { url = "https://files.pythonhosted.org/packages/a6/aa/7416eac95603ce428679d273255ffc7c998d4132cfae200103f164b108aa/frozenlist-1.8.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8585e3bb2cdea02fc88ffa245069c36555557ad3609e83be0ec71f54fd4abb52", size = 228544, upload-time = "2025-10-06T05:35:53.246Z" }, - { url = "https://files.pythonhosted.org/packages/8b/3d/2a2d1f683d55ac7e3875e4263d28410063e738384d3adc294f5ff3d7105e/frozenlist-1.8.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:edee74874ce20a373d62dc28b0b18b93f645633c2943fd90ee9d898550770581", size = 243797, upload-time = "2025-10-06T05:35:54.497Z" }, - { url = "https://files.pythonhosted.org/packages/78/1e/2d5565b589e580c296d3bb54da08d206e797d941a83a6fdea42af23be79c/frozenlist-1.8.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c9a63152fe95756b85f31186bddf42e4c02c6321207fd6601a1c89ebac4fe567", size = 247923, upload-time = "2025-10-06T05:35:55.861Z" }, - { url = "https://files.pythonhosted.org/packages/aa/c3/65872fcf1d326a7f101ad4d86285c403c87be7d832b7470b77f6d2ed5ddc/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b6db2185db9be0a04fecf2f241c70b63b1a242e2805be291855078f2b404dd6b", size = 230886, upload-time = "2025-10-06T05:35:57.399Z" }, - { url = "https://files.pythonhosted.org/packages/a0/76/ac9ced601d62f6956f03cc794f9e04c81719509f85255abf96e2510f4265/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f4be2e3d8bc8aabd566f8d5b8ba7ecc09249d74ba3c9ed52e54dc23a293f0b92", size = 245731, upload-time = "2025-10-06T05:35:58.563Z" }, - { url = "https://files.pythonhosted.org/packages/b9/49/ecccb5f2598daf0b4a1415497eba4c33c1e8ce07495eb07d2860c731b8d5/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c8d1634419f39ea6f5c427ea2f90ca85126b54b50837f31497f3bf38266e853d", size = 241544, upload-time = "2025-10-06T05:35:59.719Z" }, - { url = "https://files.pythonhosted.org/packages/53/4b/ddf24113323c0bbcc54cb38c8b8916f1da7165e07b8e24a717b4a12cbf10/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:1a7fa382a4a223773ed64242dbe1c9c326ec09457e6b8428efb4118c685c3dfd", size = 241806, upload-time = "2025-10-06T05:36:00.959Z" }, - { url = "https://files.pythonhosted.org/packages/a7/fb/9b9a084d73c67175484ba2789a59f8eebebd0827d186a8102005ce41e1ba/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:11847b53d722050808926e785df837353bd4d75f1d494377e59b23594d834967", size = 229382, upload-time = "2025-10-06T05:36:02.22Z" }, - { url = "https://files.pythonhosted.org/packages/95/a3/c8fb25aac55bf5e12dae5c5aa6a98f85d436c1dc658f21c3ac73f9fa95e5/frozenlist-1.8.0-cp311-cp311-win32.whl", hash = "sha256:27c6e8077956cf73eadd514be8fb04d77fc946a7fe9f7fe167648b0b9085cc25", size = 39647, upload-time = "2025-10-06T05:36:03.409Z" }, - { url = "https://files.pythonhosted.org/packages/0a/f5/603d0d6a02cfd4c8f2a095a54672b3cf967ad688a60fb9faf04fc4887f65/frozenlist-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:ac913f8403b36a2c8610bbfd25b8013488533e71e62b4b4adce9c86c8cea905b", size = 44064, upload-time = "2025-10-06T05:36:04.368Z" }, - { url = "https://files.pythonhosted.org/packages/5d/16/c2c9ab44e181f043a86f9a8f84d5124b62dbcb3a02c0977ec72b9ac1d3e0/frozenlist-1.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:d4d3214a0f8394edfa3e303136d0575eece0745ff2b47bd2cb2e66dd92d4351a", size = 39937, upload-time = "2025-10-06T05:36:05.669Z" }, { url = "https://files.pythonhosted.org/packages/69/29/948b9aa87e75820a38650af445d2ef2b6b8a6fab1a23b6bb9e4ef0be2d59/frozenlist-1.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1", size = 87782, upload-time = "2025-10-06T05:36:06.649Z" }, { url = "https://files.pythonhosted.org/packages/64/80/4f6e318ee2a7c0750ed724fa33a4bdf1eacdc5a39a7a24e818a773cd91af/frozenlist-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:229bf37d2e4acdaf808fd3f06e854a4a7a3661e871b10dc1f8f1896a3b05f18b", size = 50594, upload-time = "2025-10-06T05:36:07.69Z" }, { url = "https://files.pythonhosted.org/packages/2b/94/5c8a2b50a496b11dd519f4a24cb5496cf125681dd99e94c604ccdea9419a/frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f833670942247a14eafbb675458b4e61c82e002a148f49e68257b79296e865c4", size = 50448, upload-time = "2025-10-06T05:36:08.78Z" }, @@ -651,18 +524,11 @@ version = "1.8.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/03/41/4b9c02f99e4c5fb477122cd5437403b552873f014616ac1d19ac8221a58d/google_crc32c-1.8.0.tar.gz", hash = "sha256:a428e25fb7691024de47fecfbff7ff957214da51eddded0da0ae0e0f03a2cf79", size = 14192, upload-time = "2025-12-16T00:35:25.142Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/ef/21ccfaab3d5078d41efe8612e0ed0bfc9ce22475de074162a91a25f7980d/google_crc32c-1.8.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:014a7e68d623e9a4222d663931febc3033c5c7c9730785727de2a81f87d5bab8", size = 31298, upload-time = "2025-12-16T00:20:32.241Z" }, - { url = "https://files.pythonhosted.org/packages/c5/b8/f8413d3f4b676136e965e764ceedec904fe38ae8de0cdc52a12d8eb1096e/google_crc32c-1.8.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:86cfc00fe45a0ac7359e5214a1704e51a99e757d0272554874f419f79838c5f7", size = 30872, upload-time = "2025-12-16T00:33:58.785Z" }, - { url = "https://files.pythonhosted.org/packages/f6/fd/33aa4ec62b290477181c55bb1c9302c9698c58c0ce9a6ab4874abc8b0d60/google_crc32c-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:19b40d637a54cb71e0829179f6cb41835f0fbd9e8eb60552152a8b52c36cbe15", size = 33243, upload-time = "2025-12-16T00:40:21.46Z" }, - { url = "https://files.pythonhosted.org/packages/71/03/4820b3bd99c9653d1a5210cb32f9ba4da9681619b4d35b6a052432df4773/google_crc32c-1.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:17446feb05abddc187e5441a45971b8394ea4c1b6efd88ab0af393fd9e0a156a", size = 33608, upload-time = "2025-12-16T00:40:22.204Z" }, - { url = "https://files.pythonhosted.org/packages/7c/43/acf61476a11437bf9733fb2f70599b1ced11ec7ed9ea760fdd9a77d0c619/google_crc32c-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:71734788a88f551fbd6a97be9668a0020698e07b2bf5b3aa26a36c10cdfb27b2", size = 34439, upload-time = "2025-12-16T00:35:20.458Z" }, { url = "https://files.pythonhosted.org/packages/e9/5f/7307325b1198b59324c0fa9807cafb551afb65e831699f2ce211ad5c8240/google_crc32c-1.8.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:4b8286b659c1335172e39563ab0a768b8015e88e08329fa5321f774275fc3113", size = 31300, upload-time = "2025-12-16T00:21:56.723Z" }, { url = "https://files.pythonhosted.org/packages/21/8e/58c0d5d86e2220e6a37befe7e6a94dd2f6006044b1a33edf1ff6d9f7e319/google_crc32c-1.8.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:2a3dc3318507de089c5384cc74d54318401410f82aa65b2d9cdde9d297aca7cb", size = 30867, upload-time = "2025-12-16T00:38:31.302Z" }, { url = "https://files.pythonhosted.org/packages/ce/a9/a780cc66f86335a6019f557a8aaca8fbb970728f0efd2430d15ff1beae0e/google_crc32c-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:14f87e04d613dfa218d6135e81b78272c3b904e2a7053b841481b38a7d901411", size = 33364, upload-time = "2025-12-16T00:40:22.96Z" }, { url = "https://files.pythonhosted.org/packages/21/3f/3457ea803db0198c9aaca2dd373750972ce28a26f00544b6b85088811939/google_crc32c-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb5c869c2923d56cb0c8e6bcdd73c009c36ae39b652dbe46a05eb4ef0ad01454", size = 33740, upload-time = "2025-12-16T00:40:23.96Z" }, { url = "https://files.pythonhosted.org/packages/df/c0/87c2073e0c72515bb8733d4eef7b21548e8d189f094b5dad20b0ecaf64f6/google_crc32c-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:3cc0c8912038065eafa603b238abf252e204accab2a704c63b9e14837a854962", size = 34437, upload-time = "2025-12-16T00:35:21.395Z" }, - { url = "https://files.pythonhosted.org/packages/52/c5/c171e4d8c44fec1422d801a6d2e5d7ddabd733eeda505c79730ee9607f07/google_crc32c-1.8.0-pp311-pypy311_pp73-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:87fa445064e7db928226b2e6f0d5304ab4cd0339e664a4e9a25029f384d9bb93", size = 28615, upload-time = "2025-12-16T00:40:29.298Z" }, - { url = "https://files.pythonhosted.org/packages/9c/97/7d75fe37a7a6ed171a2cf17117177e7aab7e6e0d115858741b41e9dd4254/google_crc32c-1.8.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f639065ea2042d5c034bf258a9f085eaa7af0cd250667c0635a3118e8f92c69c", size = 28800, upload-time = "2025-12-16T00:40:30.322Z" }, ] [[package]] @@ -774,19 +640,6 @@ version = "1.4.9" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/5c/3c/85844f1b0feb11ee581ac23fe5fce65cd049a200c1446708cc1b7f922875/kiwisolver-1.4.9.tar.gz", hash = "sha256:c3b22c26c6fd6811b0ae8363b95ca8ce4ea3c202d3d0975b2914310ceb1bcc4d", size = 97564, upload-time = "2025-08-10T21:27:49.279Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6f/ab/c80b0d5a9d8a1a65f4f815f2afff9798b12c3b9f31f1d304dd233dd920e2/kiwisolver-1.4.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:eb14a5da6dc7642b0f3a18f13654847cd8b7a2550e2645a5bda677862b03ba16", size = 124167, upload-time = "2025-08-10T21:25:53.403Z" }, - { url = "https://files.pythonhosted.org/packages/a0/c0/27fe1a68a39cf62472a300e2879ffc13c0538546c359b86f149cc19f6ac3/kiwisolver-1.4.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:39a219e1c81ae3b103643d2aedb90f1ef22650deb266ff12a19e7773f3e5f089", size = 66579, upload-time = "2025-08-10T21:25:54.79Z" }, - { url = "https://files.pythonhosted.org/packages/31/a2/a12a503ac1fd4943c50f9822678e8015a790a13b5490354c68afb8489814/kiwisolver-1.4.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2405a7d98604b87f3fc28b1716783534b1b4b8510d8142adca34ee0bc3c87543", size = 65309, upload-time = "2025-08-10T21:25:55.76Z" }, - { url = "https://files.pythonhosted.org/packages/66/e1/e533435c0be77c3f64040d68d7a657771194a63c279f55573188161e81ca/kiwisolver-1.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dc1ae486f9abcef254b5618dfb4113dd49f94c68e3e027d03cf0143f3f772b61", size = 1435596, upload-time = "2025-08-10T21:25:56.861Z" }, - { url = "https://files.pythonhosted.org/packages/67/1e/51b73c7347f9aabdc7215aa79e8b15299097dc2f8e67dee2b095faca9cb0/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a1f570ce4d62d718dce3f179ee78dac3b545ac16c0c04bb363b7607a949c0d1", size = 1246548, upload-time = "2025-08-10T21:25:58.246Z" }, - { url = "https://files.pythonhosted.org/packages/21/aa/72a1c5d1e430294f2d32adb9542719cfb441b5da368d09d268c7757af46c/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb27e7b78d716c591e88e0a09a2139c6577865d7f2e152488c2cc6257f460872", size = 1263618, upload-time = "2025-08-10T21:25:59.857Z" }, - { url = "https://files.pythonhosted.org/packages/a3/af/db1509a9e79dbf4c260ce0cfa3903ea8945f6240e9e59d1e4deb731b1a40/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:15163165efc2f627eb9687ea5f3a28137217d217ac4024893d753f46bce9de26", size = 1317437, upload-time = "2025-08-10T21:26:01.105Z" }, - { url = "https://files.pythonhosted.org/packages/e0/f2/3ea5ee5d52abacdd12013a94130436e19969fa183faa1e7c7fbc89e9a42f/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bdee92c56a71d2b24c33a7d4c2856bd6419d017e08caa7802d2963870e315028", size = 2195742, upload-time = "2025-08-10T21:26:02.675Z" }, - { url = "https://files.pythonhosted.org/packages/6f/9b/1efdd3013c2d9a2566aa6a337e9923a00590c516add9a1e89a768a3eb2fc/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:412f287c55a6f54b0650bd9b6dce5aceddb95864a1a90c87af16979d37c89771", size = 2290810, upload-time = "2025-08-10T21:26:04.009Z" }, - { url = "https://files.pythonhosted.org/packages/fb/e5/cfdc36109ae4e67361f9bc5b41323648cb24a01b9ade18784657e022e65f/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2c93f00dcba2eea70af2be5f11a830a742fe6b579a1d4e00f47760ef13be247a", size = 2461579, upload-time = "2025-08-10T21:26:05.317Z" }, - { url = "https://files.pythonhosted.org/packages/62/86/b589e5e86c7610842213994cdea5add00960076bef4ae290c5fa68589cac/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f117e1a089d9411663a3207ba874f31be9ac8eaa5b533787024dc07aeb74f464", size = 2268071, upload-time = "2025-08-10T21:26:06.686Z" }, - { url = "https://files.pythonhosted.org/packages/3b/c6/f8df8509fd1eee6c622febe54384a96cfaf4d43bf2ccec7a0cc17e4715c9/kiwisolver-1.4.9-cp311-cp311-win_amd64.whl", hash = "sha256:be6a04e6c79819c9a8c2373317d19a96048e5a3f90bec587787e86a1153883c2", size = 73840, upload-time = "2025-08-10T21:26:07.94Z" }, - { url = "https://files.pythonhosted.org/packages/e2/2d/16e0581daafd147bc11ac53f032a2b45eabac897f42a338d0a13c1e5c436/kiwisolver-1.4.9-cp311-cp311-win_arm64.whl", hash = "sha256:0ae37737256ba2de764ddc12aed4956460277f00c4996d51a197e72f62f5eec7", size = 65159, upload-time = "2025-08-10T21:26:09.048Z" }, { url = "https://files.pythonhosted.org/packages/86/c9/13573a747838aeb1c76e3267620daa054f4152444d1f3d1a2324b78255b5/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ac5a486ac389dddcc5bef4f365b6ae3ffff2c433324fb38dd35e3fab7c957999", size = 123686, upload-time = "2025-08-10T21:26:10.034Z" }, { url = "https://files.pythonhosted.org/packages/51/ea/2ecf727927f103ffd1739271ca19c424d0e65ea473fbaeea1c014aea93f6/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2ba92255faa7309d06fe44c3a4a97efe1c8d640c2a79a5ef728b685762a6fd2", size = 66460, upload-time = "2025-08-10T21:26:11.083Z" }, { url = "https://files.pythonhosted.org/packages/5b/5a/51f5464373ce2aeb5194508298a508b6f21d3867f499556263c64c621914/kiwisolver-1.4.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a2899935e724dd1074cb568ce7ac0dce28b2cd6ab539c8e001a8578eb106d14", size = 64952, upload-time = "2025-08-10T21:26:12.058Z" }, @@ -800,11 +653,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/39/e9/61e4813b2c97e86b6fdbd4dd824bf72d28bcd8d4849b8084a357bc0dd64d/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ed0fecd28cc62c54b262e3736f8bb2512d8dcfdc2bcf08be5f47f96bf405b145", size = 2291817, upload-time = "2025-08-10T21:26:22.812Z" }, { url = "https://files.pythonhosted.org/packages/a0/41/85d82b0291db7504da3c2defe35c9a8a5c9803a730f297bd823d11d5fb77/kiwisolver-1.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:f68208a520c3d86ea51acf688a3e3002615a7f0238002cccc17affecc86a8a54", size = 73895, upload-time = "2025-08-10T21:26:24.37Z" }, { url = "https://files.pythonhosted.org/packages/e2/92/5f3068cf15ee5cb624a0c7596e67e2a0bb2adee33f71c379054a491d07da/kiwisolver-1.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:2c1a4f57df73965f3f14df20b80ee29e6a7930a57d2d9e8491a25f676e197c60", size = 64992, upload-time = "2025-08-10T21:26:25.732Z" }, - { url = "https://files.pythonhosted.org/packages/a3/0f/36d89194b5a32c054ce93e586d4049b6c2c22887b0eb229c61c68afd3078/kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:720e05574713db64c356e86732c0f3c5252818d05f9df320f0ad8380641acea5", size = 60104, upload-time = "2025-08-10T21:27:43.287Z" }, - { url = "https://files.pythonhosted.org/packages/52/ba/4ed75f59e4658fd21fe7dde1fee0ac397c678ec3befba3fe6482d987af87/kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:17680d737d5335b552994a2008fab4c851bcd7de33094a82067ef3a576ff02fa", size = 58592, upload-time = "2025-08-10T21:27:44.314Z" }, - { url = "https://files.pythonhosted.org/packages/33/01/a8ea7c5ea32a9b45ceeaee051a04c8ed4320f5add3c51bfa20879b765b70/kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:85b5352f94e490c028926ea567fc569c52ec79ce131dadb968d3853e809518c2", size = 80281, upload-time = "2025-08-10T21:27:45.369Z" }, - { url = "https://files.pythonhosted.org/packages/da/e3/dbd2ecdce306f1d07a1aaf324817ee993aab7aee9db47ceac757deabafbe/kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:464415881e4801295659462c49461a24fb107c140de781d55518c4b80cb6790f", size = 78009, upload-time = "2025-08-10T21:27:46.376Z" }, - { url = "https://files.pythonhosted.org/packages/da/e9/0d4add7873a73e462aeb45c036a2dead2562b825aa46ba326727b3f31016/kiwisolver-1.4.9-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:fb940820c63a9590d31d88b815e7a3aa5915cad3ce735ab45f0c730b39547de1", size = 73929, upload-time = "2025-08-10T21:27:48.236Z" }, ] [[package]] @@ -824,22 +672,6 @@ version = "6.0.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/aa/88/262177de60548e5a2bfc46ad28232c9e9cbde697bd94132aeb80364675cb/lxml-6.0.2.tar.gz", hash = "sha256:cd79f3367bd74b317dda655dc8fcfa304d9eb6e4fb06b7168c5cf27f96e0cd62", size = 4073426, upload-time = "2025-09-22T04:04:59.287Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/77/d5/becbe1e2569b474a23f0c672ead8a29ac50b2dc1d5b9de184831bda8d14c/lxml-6.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:13e35cbc684aadf05d8711a5d1b5857c92e5e580efa9a0d2be197199c8def607", size = 8634365, upload-time = "2025-09-22T04:00:45.672Z" }, - { url = "https://files.pythonhosted.org/packages/28/66/1ced58f12e804644426b85d0bb8a4478ca77bc1761455da310505f1a3526/lxml-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b1675e096e17c6fe9c0e8c81434f5736c0739ff9ac6123c87c2d452f48fc938", size = 4650793, upload-time = "2025-09-22T04:00:47.783Z" }, - { url = "https://files.pythonhosted.org/packages/11/84/549098ffea39dfd167e3f174b4ce983d0eed61f9d8d25b7bf2a57c3247fc/lxml-6.0.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8ac6e5811ae2870953390452e3476694196f98d447573234592d30488147404d", size = 4944362, upload-time = "2025-09-22T04:00:49.845Z" }, - { url = "https://files.pythonhosted.org/packages/ac/bd/f207f16abf9749d2037453d56b643a7471d8fde855a231a12d1e095c4f01/lxml-6.0.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5aa0fc67ae19d7a64c3fe725dc9a1bb11f80e01f78289d05c6f62545affec438", size = 5083152, upload-time = "2025-09-22T04:00:51.709Z" }, - { url = "https://files.pythonhosted.org/packages/15/ae/bd813e87d8941d52ad5b65071b1affb48da01c4ed3c9c99e40abb266fbff/lxml-6.0.2-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de496365750cc472b4e7902a485d3f152ecf57bd3ba03ddd5578ed8ceb4c5964", size = 5023539, upload-time = "2025-09-22T04:00:53.593Z" }, - { url = "https://files.pythonhosted.org/packages/02/cd/9bfef16bd1d874fbe0cb51afb00329540f30a3283beb9f0780adbb7eec03/lxml-6.0.2-cp311-cp311-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:200069a593c5e40b8f6fc0d84d86d970ba43138c3e68619ffa234bc9bb806a4d", size = 5344853, upload-time = "2025-09-22T04:00:55.524Z" }, - { url = "https://files.pythonhosted.org/packages/b8/89/ea8f91594bc5dbb879734d35a6f2b0ad50605d7fb419de2b63d4211765cc/lxml-6.0.2-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d2de809c2ee3b888b59f995625385f74629707c9355e0ff856445cdcae682b7", size = 5225133, upload-time = "2025-09-22T04:00:57.269Z" }, - { url = "https://files.pythonhosted.org/packages/b9/37/9c735274f5dbec726b2db99b98a43950395ba3d4a1043083dba2ad814170/lxml-6.0.2-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:b2c3da8d93cf5db60e8858c17684c47d01fee6405e554fb55018dd85fc23b178", size = 4677944, upload-time = "2025-09-22T04:00:59.052Z" }, - { url = "https://files.pythonhosted.org/packages/20/28/7dfe1ba3475d8bfca3878365075abe002e05d40dfaaeb7ec01b4c587d533/lxml-6.0.2-cp311-cp311-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:442de7530296ef5e188373a1ea5789a46ce90c4847e597856570439621d9c553", size = 5284535, upload-time = "2025-09-22T04:01:01.335Z" }, - { url = "https://files.pythonhosted.org/packages/e7/cf/5f14bc0de763498fc29510e3532bf2b4b3a1c1d5d0dff2e900c16ba021ef/lxml-6.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2593c77efde7bfea7f6389f1ab249b15ed4aa5bc5cb5131faa3b843c429fbedb", size = 5067343, upload-time = "2025-09-22T04:01:03.13Z" }, - { url = "https://files.pythonhosted.org/packages/1c/b0/bb8275ab5472f32b28cfbbcc6db7c9d092482d3439ca279d8d6fa02f7025/lxml-6.0.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:3e3cb08855967a20f553ff32d147e14329b3ae70ced6edc2f282b94afbc74b2a", size = 4725419, upload-time = "2025-09-22T04:01:05.013Z" }, - { url = "https://files.pythonhosted.org/packages/25/4c/7c222753bc72edca3b99dbadba1b064209bc8ed4ad448af990e60dcce462/lxml-6.0.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:2ed6c667fcbb8c19c6791bbf40b7268ef8ddf5a96940ba9404b9f9a304832f6c", size = 5275008, upload-time = "2025-09-22T04:01:07.327Z" }, - { url = "https://files.pythonhosted.org/packages/6c/8c/478a0dc6b6ed661451379447cdbec77c05741a75736d97e5b2b729687828/lxml-6.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b8f18914faec94132e5b91e69d76a5c1d7b0c73e2489ea8929c4aaa10b76bbf7", size = 5248906, upload-time = "2025-09-22T04:01:09.452Z" }, - { url = "https://files.pythonhosted.org/packages/2d/d9/5be3a6ab2784cdf9accb0703b65e1b64fcdd9311c9f007630c7db0cfcce1/lxml-6.0.2-cp311-cp311-win32.whl", hash = "sha256:6605c604e6daa9e0d7f0a2137bdc47a2e93b59c60a65466353e37f8272f47c46", size = 3610357, upload-time = "2025-09-22T04:01:11.102Z" }, - { url = "https://files.pythonhosted.org/packages/e2/7d/ca6fb13349b473d5732fb0ee3eec8f6c80fc0688e76b7d79c1008481bf1f/lxml-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e5867f2651016a3afd8dd2c8238baa66f1e2802f44bc17e236f547ace6647078", size = 4036583, upload-time = "2025-09-22T04:01:12.766Z" }, - { url = "https://files.pythonhosted.org/packages/ab/a2/51363b5ecd3eab46563645f3a2c3836a2fc67d01a1b87c5017040f39f567/lxml-6.0.2-cp311-cp311-win_arm64.whl", hash = "sha256:4197fb2534ee05fd3e7afaab5d8bfd6c2e186f65ea7f9cd6a82809c887bd1285", size = 3680591, upload-time = "2025-09-22T04:01:14.874Z" }, { url = "https://files.pythonhosted.org/packages/f3/c8/8ff2bc6b920c84355146cd1ab7d181bc543b89241cfb1ebee824a7c81457/lxml-6.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a59f5448ba2ceccd06995c95ea59a7674a10de0810f2ce90c9006f3cbc044456", size = 8661887, upload-time = "2025-09-22T04:01:17.265Z" }, { url = "https://files.pythonhosted.org/packages/37/6f/9aae1008083bb501ef63284220ce81638332f9ccbfa53765b2b7502203cf/lxml-6.0.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e8113639f3296706fbac34a30813929e29247718e88173ad849f57ca59754924", size = 4667818, upload-time = "2025-09-22T04:01:19.688Z" }, { url = "https://files.pythonhosted.org/packages/f1/ca/31fb37f99f37f1536c133476674c10b577e409c0a624384147653e38baf2/lxml-6.0.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a8bef9b9825fa8bc816a6e641bb67219489229ebc648be422af695f6e7a4fa7f", size = 4950807, upload-time = "2025-09-22T04:01:21.487Z" }, @@ -858,12 +690,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c6/80/c06de80bfce881d0ad738576f243911fccf992687ae09fd80b734712b39c/lxml-6.0.2-cp312-cp312-win32.whl", hash = "sha256:3ae2ce7d6fedfb3414a2b6c5e20b249c4c607f72cb8d2bb7cc9c6ec7c6f4e849", size = 3611456, upload-time = "2025-09-22T04:01:48.243Z" }, { url = "https://files.pythonhosted.org/packages/f7/d7/0cdfb6c3e30893463fb3d1e52bc5f5f99684a03c29a0b6b605cfae879cd5/lxml-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:72c87e5ee4e58a8354fb9c7c84cbf95a1c8236c127a5d1b7683f04bed8361e1f", size = 4011793, upload-time = "2025-09-22T04:01:50.042Z" }, { url = "https://files.pythonhosted.org/packages/ea/7b/93c73c67db235931527301ed3785f849c78991e2e34f3fd9a6663ffda4c5/lxml-6.0.2-cp312-cp312-win_arm64.whl", hash = "sha256:61cb10eeb95570153e0c0e554f58df92ecf5109f75eacad4a95baa709e26c3d6", size = 3672836, upload-time = "2025-09-22T04:01:52.145Z" }, - { url = "https://files.pythonhosted.org/packages/0b/11/29d08bc103a62c0eba8016e7ed5aeebbf1e4312e83b0b1648dd203b0e87d/lxml-6.0.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1c06035eafa8404b5cf475bb37a9f6088b0aca288d4ccc9d69389750d5543700", size = 3949829, upload-time = "2025-09-22T04:04:45.608Z" }, - { url = "https://files.pythonhosted.org/packages/12/b3/52ab9a3b31e5ab8238da241baa19eec44d2ab426532441ee607165aebb52/lxml-6.0.2-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c7d13103045de1bdd6fe5d61802565f1a3537d70cd3abf596aa0af62761921ee", size = 4226277, upload-time = "2025-09-22T04:04:47.754Z" }, - { url = "https://files.pythonhosted.org/packages/a0/33/1eaf780c1baad88224611df13b1c2a9dfa460b526cacfe769103ff50d845/lxml-6.0.2-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0a3c150a95fbe5ac91de323aa756219ef9cf7fde5a3f00e2281e30f33fa5fa4f", size = 4330433, upload-time = "2025-09-22T04:04:49.907Z" }, - { url = "https://files.pythonhosted.org/packages/7a/c1/27428a2ff348e994ab4f8777d3a0ad510b6b92d37718e5887d2da99952a2/lxml-6.0.2-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:60fa43be34f78bebb27812ed90f1925ec99560b0fa1decdb7d12b84d857d31e9", size = 4272119, upload-time = "2025-09-22T04:04:51.801Z" }, - { url = "https://files.pythonhosted.org/packages/f0/d0/3020fa12bcec4ab62f97aab026d57c2f0cfd480a558758d9ca233bb6a79d/lxml-6.0.2-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:21c73b476d3cfe836be731225ec3421fa2f048d84f6df6a8e70433dff1376d5a", size = 4417314, upload-time = "2025-09-22T04:04:55.024Z" }, - { url = "https://files.pythonhosted.org/packages/6c/77/d7f491cbc05303ac6801651aabeb262d43f319288c1ea96c66b1d2692ff3/lxml-6.0.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:27220da5be049e936c3aca06f174e8827ca6445a4353a1995584311487fc4e3e", size = 3518768, upload-time = "2025-09-22T04:04:57.097Z" }, ] [[package]] @@ -875,15 +701,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/bc/7b/bbf6b00488662be5d2eb7a188222c264b6f713bac10dc4a77bf37a4cb4b6/mapbox_earcut-2.0.0.tar.gz", hash = "sha256:81eab6b86cf99551deb698b98e3f7502c57900e5c479df15e1bdaf1a57f0f9d6", size = 39934, upload-time = "2025-11-16T18:41:27.251Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/07/9f/fbd15d9e348e75e986d6912c4eab99888106b7e5fb0a01e765422f7cd464/mapbox_earcut-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:9b5040e79e3783295e99c90277f31c1cbaddd3335297275331995ba5680e3649", size = 55773, upload-time = "2025-11-16T18:40:20.045Z" }, - { url = "https://files.pythonhosted.org/packages/72/40/be761298704fbbaa81c5618bb306f1510fb068e482f6a1c8b3b6c1b31479/mapbox_earcut-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1cf43baafec3ef1e967319d9b5da96bc6ddf3dbb204b6f3535275eda4b519a72", size = 52444, upload-time = "2025-11-16T18:40:21.501Z" }, - { url = "https://files.pythonhosted.org/packages/5a/0b/0c0c08db9663238ffb82c48259582dc0047a3255d98c0ac83c48026b7544/mapbox_earcut-2.0.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a283531847f603dd9d69afb75b21bd009d385ca9485fcd3e5a7fa5db1ccd913", size = 56803, upload-time = "2025-11-16T18:40:22.891Z" }, - { url = "https://files.pythonhosted.org/packages/f0/4a/86796859383d7d11fa5d4bcf1983f94c6cbb9eeb60fb3bab527fec4b32fa/mapbox_earcut-2.0.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ab697676f4cec4572d4e941b7a3429a6687bf2ac6e8db3f3781024e3239ae3a0", size = 59403, upload-time = "2025-11-16T18:40:24.021Z" }, - { url = "https://files.pythonhosted.org/packages/6c/db/adaf981ab3bcfcf993ef317636b1f27210d6834bb1e8d63db6ad7c08214a/mapbox_earcut-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f1bdac76e048f4299accf4eaf797079ddfc330442e7231c15535ed198100d6c5", size = 152876, upload-time = "2025-11-16T18:40:25.588Z" }, - { url = "https://files.pythonhosted.org/packages/d2/83/86417974039e7554c9e1e55c852a7e9c2a1390d64675eb85d70e5fa7eb37/mapbox_earcut-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4a6945b23f859bef11ce3194303d17bd371c86b637e7029f81b1feaff3db3758", size = 157548, upload-time = "2025-11-16T18:40:27.202Z" }, - { url = "https://files.pythonhosted.org/packages/aa/4c/c82a292bb21e5c651d81334123db2d654c5c9d19b2197080d3429dc1e49a/mapbox_earcut-2.0.0-cp311-cp311-win32.whl", hash = "sha256:8e119524c29406afb5eaa15e933f297d35679293a3ca62ced22f97a14c484cb5", size = 51424, upload-time = "2025-11-16T18:40:28.415Z" }, - { url = "https://files.pythonhosted.org/packages/30/57/6c39d7db81f72a3e4814ef152c8fb8dfe275dc4b03c9bfa073d251e3755f/mapbox_earcut-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:378bbbb3304e446023752db8f44ecd6e7ef965bcbda36541d2ae64442ba94254", size = 56662, upload-time = "2025-11-16T18:40:29.863Z" }, - { url = "https://files.pythonhosted.org/packages/f4/d6/a1ef6e196b3d6968bf6546d4f7e54c559f9cff8991fdb880df0ba1618f52/mapbox_earcut-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:6d249a431abd6bbff36f1fd0493247a86de962244cc4081b4d5050b02ed48fb1", size = 50505, upload-time = "2025-11-16T18:40:30.992Z" }, { url = "https://files.pythonhosted.org/packages/8d/93/846804029d955c3c841d8efff77c2b0e8d9aab057d3a077dc8e3f88b5ea4/mapbox_earcut-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:db55ce18e698bc9d90914ee7d4f8c3e4d23827456ece7c5d7a1ec91e90c7122b", size = 55623, upload-time = "2025-11-16T18:40:32.113Z" }, { url = "https://files.pythonhosted.org/packages/d3/f6/cc9ece104bc3876b350dba6fef7f34fb7b20ecc028d2cdbdbecb436b1ed1/mapbox_earcut-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:01dd6099d16123baf582a11b2bd1d59ce848498cf0cdca3812fd1f8b20ff33b7", size = 52028, upload-time = "2025-11-16T18:40:33.516Z" }, { url = "https://files.pythonhosted.org/packages/88/6e/230da4aabcc56c99e9bddb4c43ce7d4ba3609c0caf2d316fb26535d7c60c/mapbox_earcut-2.0.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2d5a098aae26a52282bc981a38e7bf6b889d2ea7442f2cd1903d2ba842f4ff07", size = 56351, upload-time = "2025-11-16T18:40:35.217Z" }, @@ -910,17 +727,6 @@ version = "3.0.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, - { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, - { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, - { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" }, - { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" }, - { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" }, - { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" }, - { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" }, - { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" }, - { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" }, - { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" }, { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, @@ -951,13 +757,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/8a/76/d3c6e3a13fe484ebe7718d14e269c9569c4eb0020a968a327acb3b9a8fe6/matplotlib-3.10.8.tar.gz", hash = "sha256:2299372c19d56bcd35cf05a2738308758d32b9eaed2371898d8f5bd33f084aa3", size = 34806269, upload-time = "2025-12-10T22:56:51.155Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/86/de7e3a1cdcfc941483af70609edc06b83e7c8a0e0dc9ac325200a3f4d220/matplotlib-3.10.8-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6be43b667360fef5c754dda5d25a32e6307a03c204f3c0fc5468b78fa87b4160", size = 8251215, upload-time = "2025-12-10T22:55:16.175Z" }, - { url = "https://files.pythonhosted.org/packages/fd/14/baad3222f424b19ce6ad243c71de1ad9ec6b2e4eb1e458a48fdc6d120401/matplotlib-3.10.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2b336e2d91a3d7006864e0990c83b216fcdca64b5a6484912902cef87313d78", size = 8139625, upload-time = "2025-12-10T22:55:17.712Z" }, - { url = "https://files.pythonhosted.org/packages/8f/a0/7024215e95d456de5883e6732e708d8187d9753a21d32f8ddb3befc0c445/matplotlib-3.10.8-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:efb30e3baaea72ce5928e32bab719ab4770099079d66726a62b11b1ef7273be4", size = 8712614, upload-time = "2025-12-10T22:55:20.8Z" }, - { url = "https://files.pythonhosted.org/packages/5a/f4/b8347351da9a5b3f41e26cf547252d861f685c6867d179a7c9d60ad50189/matplotlib-3.10.8-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d56a1efd5bfd61486c8bc968fa18734464556f0fb8e51690f4ac25d85cbbbbc2", size = 9540997, upload-time = "2025-12-10T22:55:23.258Z" }, - { url = "https://files.pythonhosted.org/packages/9e/c0/c7b914e297efe0bc36917bf216b2acb91044b91e930e878ae12981e461e5/matplotlib-3.10.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:238b7ce5717600615c895050239ec955d91f321c209dd110db988500558e70d6", size = 9596825, upload-time = "2025-12-10T22:55:25.217Z" }, - { url = "https://files.pythonhosted.org/packages/6f/d3/a4bbc01c237ab710a1f22b4da72f4ff6d77eb4c7735ea9811a94ae239067/matplotlib-3.10.8-cp311-cp311-win_amd64.whl", hash = "sha256:18821ace09c763ec93aef5eeff087ee493a24051936d7b9ebcad9662f66501f9", size = 8135090, upload-time = "2025-12-10T22:55:27.162Z" }, - { url = "https://files.pythonhosted.org/packages/89/dd/a0b6588f102beab33ca6f5218b31725216577b2a24172f327eaf6417d5c9/matplotlib-3.10.8-cp311-cp311-win_arm64.whl", hash = "sha256:bab485bcf8b1c7d2060b4fcb6fc368a9e6f4cd754c9c2fea281f4be21df394a2", size = 8012377, upload-time = "2025-12-10T22:55:29.185Z" }, { url = "https://files.pythonhosted.org/packages/9e/67/f997cdcbb514012eb0d10cd2b4b332667997fb5ebe26b8d41d04962fa0e6/matplotlib-3.10.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:64fcc24778ca0404ce0cb7b6b77ae1f4c7231cdd60e6778f999ee05cbd581b9a", size = 8260453, upload-time = "2025-12-10T22:55:30.709Z" }, { url = "https://files.pythonhosted.org/packages/7e/65/07d5f5c7f7c994f12c768708bd2e17a4f01a2b0f44a1c9eccad872433e2e/matplotlib-3.10.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b9a5ca4ac220a0cdd1ba6bcba3608547117d30468fefce49bb26f55c1a3d5c58", size = 8148321, upload-time = "2025-12-10T22:55:33.265Z" }, { url = "https://files.pythonhosted.org/packages/3e/f3/c5195b1ae57ef85339fd7285dfb603b22c8b4e79114bae5f4f0fcf688677/matplotlib-3.10.8-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3ab4aabc72de4ff77b3ec33a6d78a68227bf1123465887f9905ba79184a1cc04", size = 8716944, upload-time = "2025-12-10T22:55:34.922Z" }, @@ -965,9 +764,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/57/61/78cd5920d35b29fd2a0fe894de8adf672ff52939d2e9b43cb83cd5ce1bc7/matplotlib-3.10.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:99eefd13c0dc3b3c1b4d561c1169e65fe47aab7b8158754d7c084088e2329466", size = 9613040, upload-time = "2025-12-10T22:55:38.715Z" }, { url = "https://files.pythonhosted.org/packages/30/4e/c10f171b6e2f44d9e3a2b96efa38b1677439d79c99357600a62cc1e9594e/matplotlib-3.10.8-cp312-cp312-win_amd64.whl", hash = "sha256:dd80ecb295460a5d9d260df63c43f4afbdd832d725a531f008dad1664f458adf", size = 8142717, upload-time = "2025-12-10T22:55:41.103Z" }, { url = "https://files.pythonhosted.org/packages/f1/76/934db220026b5fef85f45d51a738b91dea7d70207581063cd9bd8fafcf74/matplotlib-3.10.8-cp312-cp312-win_arm64.whl", hash = "sha256:3c624e43ed56313651bc18a47f838b60d7b8032ed348911c54906b130b20071b", size = 8012751, upload-time = "2025-12-10T22:55:42.684Z" }, - { url = "https://files.pythonhosted.org/packages/04/30/3afaa31c757f34b7725ab9d2ba8b48b5e89c2019c003e7d0ead143aabc5a/matplotlib-3.10.8-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6da7c2ce169267d0d066adcf63758f0604aa6c3eebf67458930f9d9b79ad1db1", size = 8249198, upload-time = "2025-12-10T22:56:45.584Z" }, - { url = "https://files.pythonhosted.org/packages/48/2f/6334aec331f57485a642a7c8be03cb286f29111ae71c46c38b363230063c/matplotlib-3.10.8-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9153c3292705be9f9c64498a8872118540c3f4123d1a1c840172edf262c8be4a", size = 8136817, upload-time = "2025-12-10T22:56:47.339Z" }, - { url = "https://files.pythonhosted.org/packages/73/e4/6d6f14b2a759c622f191b2d67e9075a3f56aaccb3be4bb9bb6890030d0a0/matplotlib-3.10.8-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ae029229a57cd1e8fe542485f27e7ca7b23aa9e8944ddb4985d0bc444f1eca2", size = 8713867, upload-time = "2025-12-10T22:56:48.954Z" }, ] [[package]] @@ -1080,11 +876,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/0e/4a/c27b42ed9b1c7d13d9ba8b6905dece787d6259152f2309338aed29b2447b/ml_dtypes-0.5.4.tar.gz", hash = "sha256:8ab06a50fb9bf9666dd0fe5dfb4676fa2b0ac0f31ecff72a6c3af8e22c063453", size = 692314, upload-time = "2025-11-17T22:32:31.031Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c6/5e/712092cfe7e5eb667b8ad9ca7c54442f21ed7ca8979745f1000e24cf8737/ml_dtypes-0.5.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6c7ecb74c4bd71db68a6bea1edf8da8c34f3d9fe218f038814fd1d310ac76c90", size = 679734, upload-time = "2025-11-17T22:31:39.223Z" }, - { url = "https://files.pythonhosted.org/packages/4f/cf/912146dfd4b5c0eea956836c01dcd2fce6c9c844b2691f5152aca196ce4f/ml_dtypes-0.5.4-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bc11d7e8c44a65115d05e2ab9989d1e045125d7be8e05a071a48bc76eb6d6040", size = 5056165, upload-time = "2025-11-17T22:31:41.071Z" }, - { url = "https://files.pythonhosted.org/packages/a9/80/19189ea605017473660e43762dc853d2797984b3c7bf30ce656099add30c/ml_dtypes-0.5.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:19b9a53598f21e453ea2fbda8aa783c20faff8e1eeb0d7ab899309a0053f1483", size = 5034975, upload-time = "2025-11-17T22:31:42.758Z" }, - { url = "https://files.pythonhosted.org/packages/b4/24/70bd59276883fdd91600ca20040b41efd4902a923283c4d6edcb1de128d2/ml_dtypes-0.5.4-cp311-cp311-win_amd64.whl", hash = "sha256:7c23c54a00ae43edf48d44066a7ec31e05fdc2eee0be2b8b50dd1903a1db94bb", size = 210742, upload-time = "2025-11-17T22:31:44.068Z" }, - { url = "https://files.pythonhosted.org/packages/a0/c9/64230ef14e40aa3f1cb254ef623bf812735e6bec7772848d19131111ac0d/ml_dtypes-0.5.4-cp311-cp311-win_arm64.whl", hash = "sha256:557a31a390b7e9439056644cb80ed0735a6e3e3bb09d67fd5687e4b04238d1de", size = 160709, upload-time = "2025-11-17T22:31:46.557Z" }, { url = "https://files.pythonhosted.org/packages/a8/b8/3c70881695e056f8a32f8b941126cf78775d9a4d7feba8abcb52cb7b04f2/ml_dtypes-0.5.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a174837a64f5b16cab6f368171a1a03a27936b31699d167684073ff1c4237dac", size = 676927, upload-time = "2025-11-17T22:31:48.182Z" }, { url = "https://files.pythonhosted.org/packages/54/0f/428ef6881782e5ebb7eca459689448c0394fa0a80bea3aa9262cba5445ea/ml_dtypes-0.5.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a7f7c643e8b1320fd958bf098aa7ecf70623a42ec5154e3be3be673f4c34d900", size = 5028464, upload-time = "2025-11-17T22:31:50.135Z" }, { url = "https://files.pythonhosted.org/packages/3a/cb/28ce52eb94390dda42599c98ea0204d74799e4d8047a0eb559b6fd648056/ml_dtypes-0.5.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ad459e99793fa6e13bd5b7e6792c8f9190b4e5a1b45c63aba14a4d0a7f1d5ff", size = 5009002, upload-time = "2025-11-17T22:31:52.001Z" }, @@ -1144,24 +935,6 @@ version = "6.7.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/1a/c2/c2d94cbe6ac1753f3fc980da97b3d930efe1da3af3c9f5125354436c073d/multidict-6.7.1.tar.gz", hash = "sha256:ec6652a1bee61c53a3e5776b6049172c53b6aaba34f18c9ad04f82712bac623d", size = 102010, upload-time = "2026-01-26T02:46:45.979Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ce/f1/a90635c4f88fb913fbf4ce660b83b7445b7a02615bda034b2f8eb38fd597/multidict-6.7.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7ff981b266af91d7b4b3793ca3382e53229088d193a85dfad6f5f4c27fc73e5d", size = 76626, upload-time = "2026-01-26T02:43:26.485Z" }, - { url = "https://files.pythonhosted.org/packages/a6/9b/267e64eaf6fc637a15b35f5de31a566634a2740f97d8d094a69d34f524a4/multidict-6.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:844c5bca0b5444adb44a623fb0a1310c2f4cd41f402126bb269cd44c9b3f3e1e", size = 44706, upload-time = "2026-01-26T02:43:27.607Z" }, - { url = "https://files.pythonhosted.org/packages/dd/a4/d45caf2b97b035c57267791ecfaafbd59c68212004b3842830954bb4b02e/multidict-6.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f2a0a924d4c2e9afcd7ec64f9de35fcd96915149b2216e1cb2c10a56df483855", size = 44356, upload-time = "2026-01-26T02:43:28.661Z" }, - { url = "https://files.pythonhosted.org/packages/fd/d2/0a36c8473f0cbaeadd5db6c8b72d15bbceeec275807772bfcd059bef487d/multidict-6.7.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8be1802715a8e892c784c0197c2ace276ea52702a0ede98b6310c8f255a5afb3", size = 244355, upload-time = "2026-01-26T02:43:31.165Z" }, - { url = "https://files.pythonhosted.org/packages/5d/16/8c65be997fd7dd311b7d39c7b6e71a0cb449bad093761481eccbbe4b42a2/multidict-6.7.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e2d2ed645ea29f31c4c7ea1552fcfd7cb7ba656e1eafd4134a6620c9f5fdd9e", size = 246433, upload-time = "2026-01-26T02:43:32.581Z" }, - { url = "https://files.pythonhosted.org/packages/01/fb/4dbd7e848d2799c6a026ec88ad39cf2b8416aa167fcc903baa55ecaa045c/multidict-6.7.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:95922cee9a778659e91db6497596435777bd25ed116701a4c034f8e46544955a", size = 225376, upload-time = "2026-01-26T02:43:34.417Z" }, - { url = "https://files.pythonhosted.org/packages/b6/8a/4a3a6341eac3830f6053062f8fbc9a9e54407c80755b3f05bc427295c2d0/multidict-6.7.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6b83cabdc375ffaaa15edd97eb7c0c672ad788e2687004990074d7d6c9b140c8", size = 257365, upload-time = "2026-01-26T02:43:35.741Z" }, - { url = "https://files.pythonhosted.org/packages/f7/a2/dd575a69c1aa206e12d27d0770cdf9b92434b48a9ef0cd0d1afdecaa93c4/multidict-6.7.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:38fb49540705369bab8484db0689d86c0a33a0a9f2c1b197f506b71b4b6c19b0", size = 254747, upload-time = "2026-01-26T02:43:36.976Z" }, - { url = "https://files.pythonhosted.org/packages/5a/56/21b27c560c13822ed93133f08aa6372c53a8e067f11fbed37b4adcdac922/multidict-6.7.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:439cbebd499f92e9aa6793016a8acaa161dfa749ae86d20960189f5398a19144", size = 246293, upload-time = "2026-01-26T02:43:38.258Z" }, - { url = "https://files.pythonhosted.org/packages/5a/a4/23466059dc3854763423d0ad6c0f3683a379d97673b1b89ec33826e46728/multidict-6.7.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6d3bc717b6fe763b8be3f2bee2701d3c8eb1b2a8ae9f60910f1b2860c82b6c49", size = 242962, upload-time = "2026-01-26T02:43:40.034Z" }, - { url = "https://files.pythonhosted.org/packages/1f/67/51dd754a3524d685958001e8fa20a0f5f90a6a856e0a9dcabff69be3dbb7/multidict-6.7.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:619e5a1ac57986dbfec9f0b301d865dddf763696435e2962f6d9cf2fdff2bb71", size = 237360, upload-time = "2026-01-26T02:43:41.752Z" }, - { url = "https://files.pythonhosted.org/packages/64/3f/036dfc8c174934d4b55d86ff4f978e558b0e585cef70cfc1ad01adc6bf18/multidict-6.7.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0b38ebffd9be37c1170d33bc0f36f4f262e0a09bc1aac1c34c7aa51a7293f0b3", size = 245940, upload-time = "2026-01-26T02:43:43.042Z" }, - { url = "https://files.pythonhosted.org/packages/3d/20/6214d3c105928ebc353a1c644a6ef1408bc5794fcb4f170bb524a3c16311/multidict-6.7.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:10ae39c9cfe6adedcdb764f5e8411d4a92b055e35573a2eaa88d3323289ef93c", size = 253502, upload-time = "2026-01-26T02:43:44.371Z" }, - { url = "https://files.pythonhosted.org/packages/b1/e2/c653bc4ae1be70a0f836b82172d643fcf1dade042ba2676ab08ec08bff0f/multidict-6.7.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:25167cc263257660290fba06b9318d2026e3c910be240a146e1f66dd114af2b0", size = 247065, upload-time = "2026-01-26T02:43:45.745Z" }, - { url = "https://files.pythonhosted.org/packages/c8/11/a854b4154cd3bd8b1fd375e8a8ca9d73be37610c361543d56f764109509b/multidict-6.7.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:128441d052254f42989ef98b7b6a6ecb1e6f708aa962c7984235316db59f50fa", size = 241870, upload-time = "2026-01-26T02:43:47.054Z" }, - { url = "https://files.pythonhosted.org/packages/13/bf/9676c0392309b5fdae322333d22a829715b570edb9baa8016a517b55b558/multidict-6.7.1-cp311-cp311-win32.whl", hash = "sha256:d62b7f64ffde3b99d06b707a280db04fb3855b55f5a06df387236051d0668f4a", size = 41302, upload-time = "2026-01-26T02:43:48.753Z" }, - { url = "https://files.pythonhosted.org/packages/c9/68/f16a3a8ba6f7b6dc92a1f19669c0810bd2c43fc5a02da13b1cbf8e253845/multidict-6.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:bdbf9f3b332abd0cdb306e7c2113818ab1e922dc84b8f8fd06ec89ed2a19ab8b", size = 45981, upload-time = "2026-01-26T02:43:49.921Z" }, - { url = "https://files.pythonhosted.org/packages/ac/ad/9dd5305253fa00cd3c7555dbef69d5bf4133debc53b87ab8d6a44d411665/multidict-6.7.1-cp311-cp311-win_arm64.whl", hash = "sha256:b8c990b037d2fff2f4e33d3f21b9b531c5745b33a49a7d6dbe7a177266af44f6", size = 43159, upload-time = "2026-01-26T02:43:51.635Z" }, { url = "https://files.pythonhosted.org/packages/8d/9c/f20e0e2cf80e4b2e4b1c365bf5fe104ee633c751a724246262db8f1a0b13/multidict-6.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a90f75c956e32891a4eda3639ce6dd86e87105271f43d43442a3aedf3cddf172", size = 76893, upload-time = "2026-01-26T02:43:52.754Z" }, { url = "https://files.pythonhosted.org/packages/fe/cf/18ef143a81610136d3da8193da9d80bfe1cb548a1e2d1c775f26b23d024a/multidict-6.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fccb473e87eaa1382689053e4a4618e7ba7b9b9b8d6adf2027ee474597128cd", size = 45456, upload-time = "2026-01-26T02:43:53.893Z" }, { url = "https://files.pythonhosted.org/packages/a9/65/1caac9d4cd32e8433908683446eebc953e82d22b03d10d41a5f0fefe991b/multidict-6.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0fa96985700739c4c7853a43c0b3e169360d6855780021bfc6d0f1ce7c123e7", size = 43872, upload-time = "2026-01-26T02:43:55.041Z" }, @@ -1189,17 +962,6 @@ version = "2.4.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/24/62/ae72ff66c0f1fd959925b4c11f8c2dea61f47f6acaea75a08512cdfe3fed/numpy-2.4.1.tar.gz", hash = "sha256:a1ceafc5042451a858231588a104093474c6a5c57dcc724841f5c888d237d690", size = 20721320, upload-time = "2026-01-10T06:44:59.619Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a5/34/2b1bc18424f3ad9af577f6ce23600319968a70575bd7db31ce66731bbef9/numpy-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0cce2a669e3c8ba02ee563c7835f92c153cf02edff1ae05e1823f1dde21b16a5", size = 16944563, upload-time = "2026-01-10T06:42:14.615Z" }, - { url = "https://files.pythonhosted.org/packages/2c/57/26e5f97d075aef3794045a6ca9eada6a4ed70eb9a40e7a4a93f9ac80d704/numpy-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:899d2c18024984814ac7e83f8f49d8e8180e2fbe1b2e252f2e7f1d06bea92425", size = 12645658, upload-time = "2026-01-10T06:42:17.298Z" }, - { url = "https://files.pythonhosted.org/packages/8e/ba/80fc0b1e3cb2fd5c6143f00f42eb67762aa043eaa05ca924ecc3222a7849/numpy-2.4.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:09aa8a87e45b55a1c2c205d42e2808849ece5c484b2aab11fecabec3841cafba", size = 5474132, upload-time = "2026-01-10T06:42:19.637Z" }, - { url = "https://files.pythonhosted.org/packages/40/ae/0a5b9a397f0e865ec171187c78d9b57e5588afc439a04ba9cab1ebb2c945/numpy-2.4.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:edee228f76ee2dab4579fad6f51f6a305de09d444280109e0f75df247ff21501", size = 6804159, upload-time = "2026-01-10T06:42:21.44Z" }, - { url = "https://files.pythonhosted.org/packages/86/9c/841c15e691c7085caa6fd162f063eff494099c8327aeccd509d1ab1e36ab/numpy-2.4.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a92f227dbcdc9e4c3e193add1a189a9909947d4f8504c576f4a732fd0b54240a", size = 14708058, upload-time = "2026-01-10T06:42:23.546Z" }, - { url = "https://files.pythonhosted.org/packages/5d/9d/7862db06743f489e6a502a3b93136d73aea27d97b2cf91504f70a27501d6/numpy-2.4.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:538bf4ec353709c765ff75ae616c34d3c3dca1a68312727e8f2676ea644f8509", size = 16651501, upload-time = "2026-01-10T06:42:25.909Z" }, - { url = "https://files.pythonhosted.org/packages/a6/9c/6fc34ebcbd4015c6e5f0c0ce38264010ce8a546cb6beacb457b84a75dfc8/numpy-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ac08c63cb7779b85e9d5318e6c3518b424bc1f364ac4cb2c6136f12e5ff2dccc", size = 16492627, upload-time = "2026-01-10T06:42:28.938Z" }, - { url = "https://files.pythonhosted.org/packages/aa/63/2494a8597502dacda439f61b3c0db4da59928150e62be0e99395c3ad23c5/numpy-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4f9c360ecef085e5841c539a9a12b883dff005fbd7ce46722f5e9cef52634d82", size = 18585052, upload-time = "2026-01-10T06:42:31.312Z" }, - { url = "https://files.pythonhosted.org/packages/6a/93/098e1162ae7522fc9b618d6272b77404c4656c72432ecee3abc029aa3de0/numpy-2.4.1-cp311-cp311-win32.whl", hash = "sha256:0f118ce6b972080ba0758c6087c3617b5ba243d806268623dc34216d69099ba0", size = 6236575, upload-time = "2026-01-10T06:42:33.872Z" }, - { url = "https://files.pythonhosted.org/packages/8c/de/f5e79650d23d9e12f38a7bc6b03ea0835b9575494f8ec94c11c6e773b1b1/numpy-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:18e14c4d09d55eef39a6ab5b08406e84bc6869c1e34eef45564804f90b7e0574", size = 12604479, upload-time = "2026-01-10T06:42:35.778Z" }, - { url = "https://files.pythonhosted.org/packages/dd/65/e1097a7047cff12ce3369bd003811516b20ba1078dbdec135e1cd7c16c56/numpy-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:6461de5113088b399d655d45c3897fa188766415d0f568f175ab071c8873bd73", size = 10578325, upload-time = "2026-01-10T06:42:38.518Z" }, { url = "https://files.pythonhosted.org/packages/78/7f/ec53e32bf10c813604edf07a3682616bd931d026fcde7b6d13195dfb684a/numpy-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d3703409aac693fa82c0aee023a1ae06a6e9d065dba10f5e8e80f642f1e9d0a2", size = 16656888, upload-time = "2026-01-10T06:42:40.913Z" }, { url = "https://files.pythonhosted.org/packages/b8/e0/1f9585d7dae8f14864e948fd7fa86c6cb72dee2676ca2748e63b1c5acfe0/numpy-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7211b95ca365519d3596a1d8688a95874cc94219d417504d9ecb2df99fa7bfa8", size = 12373956, upload-time = "2026-01-10T06:42:43.091Z" }, { url = "https://files.pythonhosted.org/packages/8e/43/9762e88909ff2326f5e7536fa8cb3c49fb03a7d92705f23e6e7f553d9cb3/numpy-2.4.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:5adf01965456a664fc727ed69cc71848f28d063217c63e1a0e200a118d5eec9a", size = 5202567, upload-time = "2026-01-10T06:42:45.107Z" }, @@ -1211,13 +973,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/31/4c/14cb9d86240bd8c386c881bafbe43f001284b7cce3bc01623ac9475da163/numpy-2.4.1-cp312-cp312-win32.whl", hash = "sha256:b6bcf39112e956594b3331316d90c90c90fb961e39696bda97b89462f5f3943f", size = 5959015, upload-time = "2026-01-10T06:42:59.631Z" }, { url = "https://files.pythonhosted.org/packages/51/cf/52a703dbeb0c65807540d29699fef5fda073434ff61846a564d5c296420f/numpy-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:e1a27bb1b2dee45a2a53f5ca6ff2d1a7f135287883a1689e930d44d1ff296c87", size = 12310730, upload-time = "2026-01-10T06:43:01.627Z" }, { url = "https://files.pythonhosted.org/packages/69/80/a828b2d0ade5e74a9fe0f4e0a17c30fdc26232ad2bc8c9f8b3197cf7cf18/numpy-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:0e6e8f9d9ecf95399982019c01223dc130542960a12edfa8edd1122dfa66a8a8", size = 10312166, upload-time = "2026-01-10T06:43:03.673Z" }, - { url = "https://files.pythonhosted.org/packages/1e/48/d86f97919e79314a1cdee4c832178763e6e98e623e123d0bada19e92c15a/numpy-2.4.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:8ad35f20be147a204e28b6a0575fbf3540c5e5f802634d4258d55b1ff5facce1", size = 16822202, upload-time = "2026-01-10T06:44:43.738Z" }, - { url = "https://files.pythonhosted.org/packages/51/e9/1e62a7f77e0f37dcfb0ad6a9744e65df00242b6ea37dfafb55debcbf5b55/numpy-2.4.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:8097529164c0f3e32bb89412a0905d9100bf434d9692d9fc275e18dcf53c9344", size = 12569985, upload-time = "2026-01-10T06:44:45.945Z" }, - { url = "https://files.pythonhosted.org/packages/c7/7e/914d54f0c801342306fdcdce3e994a56476f1b818c46c47fc21ae968088c/numpy-2.4.1-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:ea66d2b41ca4a1630aae5507ee0a71647d3124d1741980138aa8f28f44dac36e", size = 5398484, upload-time = "2026-01-10T06:44:48.012Z" }, - { url = "https://files.pythonhosted.org/packages/1c/d8/9570b68584e293a33474e7b5a77ca404f1dcc655e40050a600dee81d27fb/numpy-2.4.1-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:d3f8f0df9f4b8be57b3bf74a1d087fec68f927a2fab68231fdb442bf2c12e426", size = 6713216, upload-time = "2026-01-10T06:44:49.725Z" }, - { url = "https://files.pythonhosted.org/packages/33/9b/9dd6e2db8d49eb24f86acaaa5258e5f4c8ed38209a4ee9de2d1a0ca25045/numpy-2.4.1-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2023ef86243690c2791fd6353e5b4848eedaa88ca8a2d129f462049f6d484696", size = 14538937, upload-time = "2026-01-10T06:44:51.498Z" }, - { url = "https://files.pythonhosted.org/packages/53/87/d5bd995b0f798a37105b876350d346eea5838bd8f77ea3d7a48392f3812b/numpy-2.4.1-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8361ea4220d763e54cff2fbe7d8c93526b744f7cd9ddab47afeff7e14e8503be", size = 16479830, upload-time = "2026-01-10T06:44:53.931Z" }, - { url = "https://files.pythonhosted.org/packages/5b/c7/b801bf98514b6ae6475e941ac05c58e6411dd863ea92916bfd6d510b08c1/numpy-2.4.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:4f1b68ff47680c2925f8063402a693ede215f0257f02596b1318ecdfb1d79e33", size = 12492579, upload-time = "2026-01-10T06:44:57.094Z" }, ] [[package]] @@ -1232,12 +987,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/3b/8a/335c03a8683a88a32f9a6bb98899ea6df241a41df64b37b9696772414794/onnx-1.20.1.tar.gz", hash = "sha256:ded16de1df563d51fbc1ad885f2a426f814039d8b5f4feb77febe09c0295ad67", size = 12048980, upload-time = "2026-01-10T01:40:03.043Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/38/1a0e74d586c08833404100f5c052f92732fb5be417c0b2d7cb0838443bfe/onnx-1.20.1-cp311-cp311-macosx_12_0_universal2.whl", hash = "sha256:53426e1b458641e7a537e9f176330012ff59d90206cac1c1a9d03cdd73ed3095", size = 17904965, upload-time = "2026-01-10T01:39:13.532Z" }, - { url = "https://files.pythonhosted.org/packages/96/25/64b076e9684d17335f80b15b3bf502f7a8e1a89f08a6b208d4f2861b3011/onnx-1.20.1-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ca7281f8c576adf396c338cf43fff26faee8d4d2e2577b8e73738f37ceccf945", size = 17415179, upload-time = "2026-01-10T01:39:16.516Z" }, - { url = "https://files.pythonhosted.org/packages/ac/d5/6743b409421ced20ad5af1b3a7b4c4e568689ffaca86db431692fca409a6/onnx-1.20.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2297f428c51c7fc6d8fad0cf34384284dfeff3f86799f8e83ef905451348ade0", size = 17513672, upload-time = "2026-01-10T01:39:19.35Z" }, - { url = "https://files.pythonhosted.org/packages/9a/6b/dae82e6fdb2043302f29adca37522312ea2be55b75907b59be06fbdffe87/onnx-1.20.1-cp311-cp311-win32.whl", hash = "sha256:63d9cbcab8c96841eadeb7c930e07bfab4dde8081eb76fb68e0dfb222706b81e", size = 16239336, upload-time = "2026-01-10T01:39:22.506Z" }, - { url = "https://files.pythonhosted.org/packages/8e/17/a0d7863390c1f2067d7c02dcc1477034965c32aaa1407bfcf775305ffee4/onnx-1.20.1-cp311-cp311-win_amd64.whl", hash = "sha256:d78cde72d7ca8356a2d99c5dc0dbf67264254828cae2c5780184486c0cd7b3bf", size = 16392120, upload-time = "2026-01-10T01:39:25.106Z" }, - { url = "https://files.pythonhosted.org/packages/aa/72/9b879a46eb7a3322223791f36bf9c25d95da9ed93779eabb75a560f22e5b/onnx-1.20.1-cp311-cp311-win_arm64.whl", hash = "sha256:0104bb2d4394c179bcea3df7599a45a2932b80f4633840896fcf0d7d8daecea2", size = 16346923, upload-time = "2026-01-10T01:39:27.782Z" }, { url = "https://files.pythonhosted.org/packages/7c/4c/4b17e82f91ab9aa07ff595771e935ca73547b035030dc5f5a76e63fbfea9/onnx-1.20.1-cp312-abi3-macosx_12_0_universal2.whl", hash = "sha256:1d923bb4f0ce1b24c6859222a7e6b2f123e7bfe7623683662805f2e7b9e95af2", size = 17903547, upload-time = "2026-01-10T01:39:31.015Z" }, { url = "https://files.pythonhosted.org/packages/64/5e/1bfa100a9cb3f2d3d5f2f05f52f7e60323b0e20bb0abace1ae64dbc88f25/onnx-1.20.1-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ddc0b7d8b5a94627dc86c533d5e415af94cbfd103019a582669dad1f56d30281", size = 17412021, upload-time = "2026-01-10T01:39:33.885Z" }, { url = "https://files.pythonhosted.org/packages/fb/71/d3fec0dcf9a7a99e7368112d9c765154e81da70fcba1e3121131a45c245b/onnx-1.20.1-cp312-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9336b6b8e6efcf5c490a845f6afd7e041c89a56199aeda384ed7d58fb953b080", size = 17510450, upload-time = "2026-01-10T01:39:36.589Z" }, @@ -1317,7 +1066,6 @@ dev = [ { name = "parameterized" }, { name = "pyautogui" }, { name = "pywinctl" }, - { name = "tabulate" }, ] docs = [ { name = "jinja2" }, @@ -1403,7 +1151,6 @@ requires-dist = [ { name = "sounddevice" }, { name = "spidev", marker = "sys_platform == 'linux'" }, { name = "sympy" }, - { name = "tabulate", marker = "extra == 'dev'" }, { name = "tqdm" }, { name = "ty", marker = "extra == 'testing'" }, { name = "websocket-client" }, @@ -1426,11 +1173,6 @@ name = "panda3d" version = "1.10.14" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f5/9a/31d07e3d7c1b40335e8418c540d63f4d33c571648ed8d69896ab778e65c3/panda3d-1.10.14-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:54b8ef9fe3684960a2c7d47b0d63c0354c17bc516795e59db6c1e5bda8c16c1c", size = 67700752, upload-time = "2024-01-08T19:05:55.559Z" }, - { url = "https://files.pythonhosted.org/packages/61/05/fce327535d8907ac01f43813c980f30ea86d37db62c340847519ea2ab222/panda3d-1.10.14-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:93414675894b18eea8d27edc1bbd1dc719eae207d45ec263d47195504bc4705f", size = 118966179, upload-time = "2024-01-08T19:06:03.165Z" }, - { url = "https://files.pythonhosted.org/packages/8a/54/24e205231e7b1bced58ba9620fbec7114673d821fc7ad9ed1804cab556b4/panda3d-1.10.14-cp311-cp311-manylinux2014_x86_64.whl", hash = "sha256:d1bc0d926f90c8fa14a1587fa9dbe5f89a4eda8c9684fa183a8eaa35fc8e891a", size = 55145295, upload-time = "2024-01-08T19:06:10.319Z" }, - { url = "https://files.pythonhosted.org/packages/06/d3/38e989822292935d7473d35117099f71481cc6b104cb2dd048cb8058a5a8/panda3d-1.10.14-cp311-cp311-win32.whl", hash = "sha256:1039340a4a7965fe4c3e904edb4fff691584df435a154fecccf534587cd07a34", size = 53137177, upload-time = "2024-01-08T19:06:15.901Z" }, - { url = "https://files.pythonhosted.org/packages/5c/32/b16c81661ed0d8ad62976004d81845baa321e53314e253ef0841155be770/panda3d-1.10.14-cp311-cp311-win_amd64.whl", hash = "sha256:1ddf01040b9c5497fb8659e3c5ef793a26c869cfdfb1b75e6d04d6fba0c03b73", size = 64447666, upload-time = "2024-01-08T19:06:22.105Z" }, { url = "https://files.pythonhosted.org/packages/5a/d4/90e98993b1a3f3c9fae83267f8c51186e676a8c1365c4180dfc65cd7ba62/panda3d-1.10.14-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:1bfbcee77779f12ecce6a3d5a856e573b25d6343f8c4b107e814d9702e70a65d", size = 67839196, upload-time = "2024-01-08T19:01:00.417Z" }, { url = "https://files.pythonhosted.org/packages/dc/e5/862821575073863ce49cc57b8349b47cb25ce11feae0e419b3d023ac1a69/panda3d-1.10.14-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:bc6540c5559d7e14a8992eff7de0157b7c42406b7ba221941ed224289496841c", size = 119271341, upload-time = "2024-01-08T19:01:09.455Z" }, { url = "https://files.pythonhosted.org/packages/f4/20/f16d91805777825e530037177d9075c83da7384f12b778b133e3164a31f3/panda3d-1.10.14-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:143daab1ce6bedcba711ea3f6cab0ebe5082f22c5f43e7178fadd2dd01209da7", size = 47604077, upload-time = "2024-05-28T20:25:37.118Z" }, @@ -1489,17 +1231,6 @@ version = "12.1.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/d0/02/d52c733a2452ef1ffcc123b68e6606d07276b0e358db70eabad7e40042b7/pillow-12.1.0.tar.gz", hash = "sha256:5c5ae0a06e9ea030ab786b0251b32c7e4ce10e58d983c0d5c56029455180b5b9", size = 46977283, upload-time = "2026-01-02T09:13:29.892Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/43/c4/bf8328039de6cc22182c3ef007a2abfbbdab153661c0a9aa78af8d706391/pillow-12.1.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:a83e0850cb8f5ac975291ebfc4170ba481f41a28065277f7f735c202cd8e0af3", size = 5304057, upload-time = "2026-01-02T09:10:46.627Z" }, - { url = "https://files.pythonhosted.org/packages/43/06/7264c0597e676104cc22ca73ee48f752767cd4b1fe084662620b17e10120/pillow-12.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b6e53e82ec2db0717eabb276aa56cf4e500c9a7cec2c2e189b55c24f65a3e8c0", size = 4657811, upload-time = "2026-01-02T09:10:49.548Z" }, - { url = "https://files.pythonhosted.org/packages/72/64/f9189e44474610daf83da31145fa56710b627b5c4c0b9c235e34058f6b31/pillow-12.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:40a8e3b9e8773876d6e30daed22f016509e3987bab61b3b7fe309d7019a87451", size = 6232243, upload-time = "2026-01-02T09:10:51.62Z" }, - { url = "https://files.pythonhosted.org/packages/ef/30/0df458009be6a4caca4ca2c52975e6275c387d4e5c95544e34138b41dc86/pillow-12.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:800429ac32c9b72909c671aaf17ecd13110f823ddb7db4dfef412a5587c2c24e", size = 8037872, upload-time = "2026-01-02T09:10:53.446Z" }, - { url = "https://files.pythonhosted.org/packages/e4/86/95845d4eda4f4f9557e25381d70876aa213560243ac1a6d619c46caaedd9/pillow-12.1.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b022eaaf709541b391ee069f0022ee5b36c709df71986e3f7be312e46f42c84", size = 6345398, upload-time = "2026-01-02T09:10:55.426Z" }, - { url = "https://files.pythonhosted.org/packages/5c/1f/8e66ab9be3aaf1435bc03edd1ebdf58ffcd17f7349c1d970cafe87af27d9/pillow-12.1.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f345e7bc9d7f368887c712aa5054558bad44d2a301ddf9248599f4161abc7c0", size = 7034667, upload-time = "2026-01-02T09:10:57.11Z" }, - { url = "https://files.pythonhosted.org/packages/f9/f6/683b83cb9b1db1fb52b87951b1c0b99bdcfceaa75febf11406c19f82cb5e/pillow-12.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d70347c8a5b7ccd803ec0c85c8709f036e6348f1e6a5bf048ecd9c64d3550b8b", size = 6458743, upload-time = "2026-01-02T09:10:59.331Z" }, - { url = "https://files.pythonhosted.org/packages/9a/7d/de833d63622538c1d58ce5395e7c6cb7e7dce80decdd8bde4a484e095d9f/pillow-12.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1fcc52d86ce7a34fd17cb04e87cfdb164648a3662a6f20565910a99653d66c18", size = 7159342, upload-time = "2026-01-02T09:11:01.82Z" }, - { url = "https://files.pythonhosted.org/packages/8c/40/50d86571c9e5868c42b81fe7da0c76ca26373f3b95a8dd675425f4a92ec1/pillow-12.1.0-cp311-cp311-win32.whl", hash = "sha256:3ffaa2f0659e2f740473bcf03c702c39a8d4b2b7ffc629052028764324842c64", size = 6328655, upload-time = "2026-01-02T09:11:04.556Z" }, - { url = "https://files.pythonhosted.org/packages/6c/af/b1d7e301c4cd26cd45d4af884d9ee9b6fab893b0ad2450d4746d74a6968c/pillow-12.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:806f3987ffe10e867bab0ddad45df1148a2b98221798457fa097ad85d6e8bc75", size = 7031469, upload-time = "2026-01-02T09:11:06.538Z" }, - { url = "https://files.pythonhosted.org/packages/48/36/d5716586d887fb2a810a4a61518a327a1e21c8b7134c89283af272efe84b/pillow-12.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:9f5fefaca968e700ad1a4a9de98bf0869a94e397fe3524c4c9450c1445252304", size = 2452515, upload-time = "2026-01-02T09:11:08.226Z" }, { url = "https://files.pythonhosted.org/packages/20/31/dc53fe21a2f2996e1b7d92bf671cdb157079385183ef7c1ae08b485db510/pillow-12.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a332ac4ccb84b6dde65dbace8431f3af08874bf9770719d32a635c4ef411b18b", size = 5262642, upload-time = "2026-01-02T09:11:10.138Z" }, { url = "https://files.pythonhosted.org/packages/ab/c1/10e45ac9cc79419cedf5121b42dcca5a50ad2b601fa080f58c22fb27626e/pillow-12.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:907bfa8a9cb790748a9aa4513e37c88c59660da3bcfffbd24a7d9e6abf224551", size = 4657464, upload-time = "2026-01-02T09:11:12.319Z" }, { url = "https://files.pythonhosted.org/packages/ad/26/7b82c0ab7ef40ebede7a97c72d473bda5950f609f8e0c77b04af574a0ddb/pillow-12.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:efdc140e7b63b8f739d09a99033aa430accce485ff78e6d311973a67b6bf3208", size = 6234878, upload-time = "2026-01-02T09:11:14.096Z" }, @@ -1511,13 +1242,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8f/c8/993d4b7ab2e341fe02ceef9576afcf5830cdec640be2ac5bee1820d693d4/pillow-12.1.0-cp312-cp312-win32.whl", hash = "sha256:aa0c9cc0b82b14766a99fbe6084409972266e82f459821cd26997a488a7261a7", size = 6328770, upload-time = "2026-01-02T09:11:25.661Z" }, { url = "https://files.pythonhosted.org/packages/a7/87/90b358775a3f02765d87655237229ba64a997b87efa8ccaca7dd3e36e7a7/pillow-12.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:d70534cea9e7966169ad29a903b99fc507e932069a881d0965a1a84bb57f6c6d", size = 7033406, upload-time = "2026-01-02T09:11:27.474Z" }, { url = "https://files.pythonhosted.org/packages/5d/cf/881b457eccacac9e5b2ddd97d5071fb6d668307c57cbf4e3b5278e06e536/pillow-12.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:65b80c1ee7e14a87d6a068dd3b0aea268ffcabfe0498d38661b00c5b4b22e74c", size = 2452612, upload-time = "2026-01-02T09:11:29.309Z" }, - { url = "https://files.pythonhosted.org/packages/8b/bc/224b1d98cffd7164b14707c91aac83c07b047fbd8f58eba4066a3e53746a/pillow-12.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ca94b6aac0d7af2a10ba08c0f888b3d5114439b6b3ef39968378723622fed377", size = 5228605, upload-time = "2026-01-02T09:13:14.084Z" }, - { url = "https://files.pythonhosted.org/packages/0c/ca/49ca7769c4550107de049ed85208240ba0f330b3f2e316f24534795702ce/pillow-12.1.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:351889afef0f485b84078ea40fe33727a0492b9af3904661b0abbafee0355b72", size = 4622245, upload-time = "2026-01-02T09:13:15.964Z" }, - { url = "https://files.pythonhosted.org/packages/73/48/fac807ce82e5955bcc2718642b94b1bd22a82a6d452aea31cbb678cddf12/pillow-12.1.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb0984b30e973f7e2884362b7d23d0a348c7143ee559f38ef3eaab640144204c", size = 5247593, upload-time = "2026-01-02T09:13:17.913Z" }, - { url = "https://files.pythonhosted.org/packages/d2/95/3e0742fe358c4664aed4fd05d5f5373dcdad0b27af52aa0972568541e3f4/pillow-12.1.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:84cabc7095dd535ca934d57e9ce2a72ffd216e435a84acb06b2277b1de2689bd", size = 6989008, upload-time = "2026-01-02T09:13:20.083Z" }, - { url = "https://files.pythonhosted.org/packages/5a/74/fe2ac378e4e202e56d50540d92e1ef4ff34ed687f3c60f6a121bcf99437e/pillow-12.1.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53d8b764726d3af1a138dd353116f774e3862ec7e3794e0c8781e30db0f35dfc", size = 5313824, upload-time = "2026-01-02T09:13:22.405Z" }, - { url = "https://files.pythonhosted.org/packages/f3/77/2a60dee1adee4e2655ac328dd05c02a955c1cd683b9f1b82ec3feb44727c/pillow-12.1.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5da841d81b1a05ef940a8567da92decaa15bc4d7dedb540a8c219ad83d91808a", size = 5963278, upload-time = "2026-01-02T09:13:24.706Z" }, - { url = "https://files.pythonhosted.org/packages/2d/71/64e9b1c7f04ae0027f788a248e6297d7fcc29571371fe7d45495a78172c0/pillow-12.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:75af0b4c229ac519b155028fa1be632d812a519abba9b46b20e50c6caa184f19", size = 7029809, upload-time = "2026-01-02T09:13:26.541Z" }, ] [[package]] @@ -1562,21 +1286,6 @@ version = "0.4.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/9e/da/e9fc233cf63743258bff22b3dfa7ea5baef7b5bc324af47a0ad89b8ffc6f/propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d", size = 46442, upload-time = "2025-10-08T19:49:02.291Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8c/d4/4e2c9aaf7ac2242b9358f98dccd8f90f2605402f5afeff6c578682c2c491/propcache-0.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:60a8fda9644b7dfd5dece8c61d8a85e271cb958075bfc4e01083c148b61a7caf", size = 80208, upload-time = "2025-10-08T19:46:24.597Z" }, - { url = "https://files.pythonhosted.org/packages/c2/21/d7b68e911f9c8e18e4ae43bdbc1e1e9bbd971f8866eb81608947b6f585ff/propcache-0.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c30b53e7e6bda1d547cabb47c825f3843a0a1a42b0496087bb58d8fedf9f41b5", size = 45777, upload-time = "2025-10-08T19:46:25.733Z" }, - { url = "https://files.pythonhosted.org/packages/d3/1d/11605e99ac8ea9435651ee71ab4cb4bf03f0949586246476a25aadfec54a/propcache-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6918ecbd897443087a3b7cd978d56546a812517dcaaca51b49526720571fa93e", size = 47647, upload-time = "2025-10-08T19:46:27.304Z" }, - { url = "https://files.pythonhosted.org/packages/58/1a/3c62c127a8466c9c843bccb503d40a273e5cc69838805f322e2826509e0d/propcache-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3d902a36df4e5989763425a8ab9e98cd8ad5c52c823b34ee7ef307fd50582566", size = 214929, upload-time = "2025-10-08T19:46:28.62Z" }, - { url = "https://files.pythonhosted.org/packages/56/b9/8fa98f850960b367c4b8fe0592e7fc341daa7a9462e925228f10a60cf74f/propcache-0.4.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a9695397f85973bb40427dedddf70d8dc4a44b22f1650dd4af9eedf443d45165", size = 221778, upload-time = "2025-10-08T19:46:30.358Z" }, - { url = "https://files.pythonhosted.org/packages/46/a6/0ab4f660eb59649d14b3d3d65c439421cf2f87fe5dd68591cbe3c1e78a89/propcache-0.4.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2bb07ffd7eaad486576430c89f9b215f9e4be68c4866a96e97db9e97fead85dc", size = 228144, upload-time = "2025-10-08T19:46:32.607Z" }, - { url = "https://files.pythonhosted.org/packages/52/6a/57f43e054fb3d3a56ac9fc532bc684fc6169a26c75c353e65425b3e56eef/propcache-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd6f30fdcf9ae2a70abd34da54f18da086160e4d7d9251f81f3da0ff84fc5a48", size = 210030, upload-time = "2025-10-08T19:46:33.969Z" }, - { url = "https://files.pythonhosted.org/packages/40/e2/27e6feebb5f6b8408fa29f5efbb765cd54c153ac77314d27e457a3e993b7/propcache-0.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fc38cba02d1acba4e2869eef1a57a43dfbd3d49a59bf90dda7444ec2be6a5570", size = 208252, upload-time = "2025-10-08T19:46:35.309Z" }, - { url = "https://files.pythonhosted.org/packages/9e/f8/91c27b22ccda1dbc7967f921c42825564fa5336a01ecd72eb78a9f4f53c2/propcache-0.4.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:67fad6162281e80e882fb3ec355398cf72864a54069d060321f6cd0ade95fe85", size = 202064, upload-time = "2025-10-08T19:46:36.993Z" }, - { url = "https://files.pythonhosted.org/packages/f2/26/7f00bd6bd1adba5aafe5f4a66390f243acab58eab24ff1a08bebb2ef9d40/propcache-0.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f10207adf04d08bec185bae14d9606a1444715bc99180f9331c9c02093e1959e", size = 212429, upload-time = "2025-10-08T19:46:38.398Z" }, - { url = "https://files.pythonhosted.org/packages/84/89/fd108ba7815c1117ddca79c228f3f8a15fc82a73bca8b142eb5de13b2785/propcache-0.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e9b0d8d0845bbc4cfcdcbcdbf5086886bc8157aa963c31c777ceff7846c77757", size = 216727, upload-time = "2025-10-08T19:46:39.732Z" }, - { url = "https://files.pythonhosted.org/packages/79/37/3ec3f7e3173e73f1d600495d8b545b53802cbf35506e5732dd8578db3724/propcache-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:981333cb2f4c1896a12f4ab92a9cc8f09ea664e9b7dbdc4eff74627af3a11c0f", size = 205097, upload-time = "2025-10-08T19:46:41.025Z" }, - { url = "https://files.pythonhosted.org/packages/61/b0/b2631c19793f869d35f47d5a3a56fb19e9160d3c119f15ac7344fc3ccae7/propcache-0.4.1-cp311-cp311-win32.whl", hash = "sha256:f1d2f90aeec838a52f1c1a32fe9a619fefd5e411721a9117fbf82aea638fe8a1", size = 38084, upload-time = "2025-10-08T19:46:42.693Z" }, - { url = "https://files.pythonhosted.org/packages/f4/78/6cce448e2098e9f3bfc91bb877f06aa24b6ccace872e39c53b2f707c4648/propcache-0.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:364426a62660f3f699949ac8c621aad6977be7126c5807ce48c0aeb8e7333ea6", size = 41637, upload-time = "2025-10-08T19:46:43.778Z" }, - { url = "https://files.pythonhosted.org/packages/9c/e9/754f180cccd7f51a39913782c74717c581b9cc8177ad0e949f4d51812383/propcache-0.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:e53f3a38d3510c11953f3e6a33f205c6d1b001129f972805ca9b42fc308bc239", size = 38064, upload-time = "2025-10-08T19:46:44.872Z" }, { url = "https://files.pythonhosted.org/packages/a2/0f/f17b1b2b221d5ca28b4b876e8bb046ac40466513960646bda8e1853cdfa2/propcache-0.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e153e9cd40cc8945138822807139367f256f89c6810c2634a4f6902b52d3b4e2", size = 80061, upload-time = "2025-10-08T19:46:46.075Z" }, { url = "https://files.pythonhosted.org/packages/76/47/8ccf75935f51448ba9a16a71b783eb7ef6b9ee60f5d14c7f8a8a79fbeed7/propcache-0.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cd547953428f7abb73c5ad82cbb32109566204260d98e41e5dfdc682eb7f8403", size = 46037, upload-time = "2025-10-08T19:46:47.23Z" }, { url = "https://files.pythonhosted.org/packages/0a/b6/5c9a0e42df4d00bfb4a3cbbe5cf9f54260300c88a0e9af1f47ca5ce17ac0/propcache-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f048da1b4f243fc44f205dfd320933a951b8d89e0afd4c7cacc762a8b9165207", size = 47324, upload-time = "2025-10-08T19:46:48.384Z" }, @@ -1632,8 +1341,6 @@ version = "0.2.14" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/26/1d/8878c7752febb0f6716a7e1a52cb92ac98871c5aa522cba181878091607c/PyAudio-0.2.14.tar.gz", hash = "sha256:78dfff3879b4994d1f4fc6485646a57755c6ee3c19647a491f790a0895bd2f87", size = 47066, upload-time = "2023-11-07T07:11:48.806Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7b/f0/b0eab89eafa70a86b7b566a4df2f94c7880a2d483aa8de1c77d335335b5b/PyAudio-0.2.14-cp311-cp311-win32.whl", hash = "sha256:506b32a595f8693811682ab4b127602d404df7dfc453b499c91a80d0f7bad289", size = 144624, upload-time = "2023-11-07T07:11:36.94Z" }, - { url = "https://files.pythonhosted.org/packages/82/d8/f043c854aad450a76e476b0cf9cda1956419e1dacf1062eb9df3c0055abe/PyAudio-0.2.14-cp311-cp311-win_amd64.whl", hash = "sha256:bbeb01d36a2f472ae5ee5e1451cacc42112986abe622f735bb870a5db77cf903", size = 164070, upload-time = "2023-11-07T07:11:38.579Z" }, { url = "https://files.pythonhosted.org/packages/8d/45/8d2b76e8f6db783f9326c1305f3f816d4a12c8eda5edc6a2e1d03c097c3b/PyAudio-0.2.14-cp312-cp312-win32.whl", hash = "sha256:5fce4bcdd2e0e8c063d835dbe2860dac46437506af509353c7f8114d4bacbd5b", size = 144750, upload-time = "2023-11-07T07:11:40.142Z" }, { url = "https://files.pythonhosted.org/packages/b0/6a/d25812e5f79f06285767ec607b39149d02aa3b31d50c2269768f48768930/PyAudio-0.2.14-cp312-cp312-win_amd64.whl", hash = "sha256:12f2f1ba04e06ff95d80700a78967897a489c05e093e3bffa05a84ed9c0a7fa3", size = 164126, upload-time = "2023-11-07T07:11:41.539Z" }, ] @@ -1660,18 +1367,6 @@ version = "2.1.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/15/86/a57e3c92acd3e1d2fc3dcad683ada191f722e4ac927e1a384b228ec2780a/pycapnp-2.1.0.tar.gz", hash = "sha256:69cc3d861fee1c9b26c73ad2e8a5d51e76ad87e4ff9be33a4fd2fc72f5846aec", size = 689734, upload-time = "2025-09-05T03:50:40.851Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/68/7c/934750a0ca77431a22e68e11521dcc6b801bea3ff37331d6a519e5ad142e/pycapnp-2.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:efacc439ec287d9e8a0ebf01a515404eff795659401e65ba6f1819c7b24f4380", size = 1628855, upload-time = "2025-09-05T03:48:32.317Z" }, - { url = "https://files.pythonhosted.org/packages/2e/a2/fd2c10b3f2e5010c747aa946b27fe09f665d65d5dc2afdd31838a3ef2f5d/pycapnp-2.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f3d8af535a8b44dfd71731a191386c6b821b8a4915806948893d18c79f547a8e", size = 1496942, upload-time = "2025-09-05T03:48:34.905Z" }, - { url = "https://files.pythonhosted.org/packages/0b/8a/42bd0e4c094ef534ac6890d34adae580cbbf5b0497fc0a6340bea833a617/pycapnp-2.1.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:117d1d5ebfc08cc189aca4f771b34fedc1291a3f9417167bd2d9b2a4e607e640", size = 5200170, upload-time = "2025-09-05T03:48:36.502Z" }, - { url = "https://files.pythonhosted.org/packages/af/30/2e92268383135082191c3dea4a9ad184d20b7fb2dda1477fd6ee520fd88e/pycapnp-2.1.0-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:d881ccc69e381863a88c7b6c7092a6baecb6dfc8c5558d66bc967c7f778fe7bc", size = 5684026, upload-time = "2025-09-05T03:48:38.063Z" }, - { url = "https://files.pythonhosted.org/packages/46/9c/bca1cbd7711c9c0f0f62ca95a49835369a61c4f6527a6900c8982045bf2f/pycapnp-2.1.0-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:8a4ea330e38ba83f6f03fbdc1f58642eb53e6f6f66734a426fa592dc988d70e9", size = 5709307, upload-time = "2025-09-05T03:48:40.127Z" }, - { url = "https://files.pythonhosted.org/packages/2d/29/cd14676d992c7b166baa7e022b369c15240d408b202410d105b23b25f737/pycapnp-2.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:fb2563de4619d359820de9d47b4704e4f7eda193ffc4a56e39cdcd2c8301c336", size = 5386505, upload-time = "2025-09-05T03:48:41.785Z" }, - { url = "https://files.pythonhosted.org/packages/ae/dd/2fc57cebe9be7e4cd3d6aec0b9c8a0db9772c1b17c37cfe4f04c050422cf/pycapnp-2.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5265d1ae34f9c089fa6983f6c1be404ce480c82b927017290bd703328fa3f5df", size = 6095180, upload-time = "2025-09-05T03:48:43.795Z" }, - { url = "https://files.pythonhosted.org/packages/5a/16/da8c1ada7770a532c859df475533eec5a1b2f5e81a269466a2fe670c5747/pycapnp-2.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b0a56370a868f752375a785bfb7e06b55cbe71605972615d1220c380bc452380", size = 6603414, upload-time = "2025-09-05T03:48:45.457Z" }, - { url = "https://files.pythonhosted.org/packages/f0/e6/a36eacaf2da6a5ac9c6565600e559edf95115ff990aa3379aee8dd7ba4fe/pycapnp-2.1.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5d7403c25275cf4badf6f9d0c07b1cb94fcdd599df81aba9b621c32b3dcefae9", size = 6621440, upload-time = "2025-09-05T03:48:47.706Z" }, - { url = "https://files.pythonhosted.org/packages/81/54/9150c03638cf4ecdf1664867382d0049146c658d6de30f189817c502df1a/pycapnp-2.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dea5d0d250fe4851b42cd380a207d773ebae76a990e542a888a5f1442f4c247e", size = 6354219, upload-time = "2025-09-05T03:48:49.336Z" }, - { url = "https://files.pythonhosted.org/packages/66/3e/e49ba2d74456d53b570c8d30a660c3b29ecfea075d5dd663132ff9049f19/pycapnp-2.1.0-cp311-cp311-win32.whl", hash = "sha256:593844c3cd92937eb5e7cd47ea3a62cde2d49a1fc05dba644f513c68f60f1318", size = 1053647, upload-time = "2025-09-05T03:48:51.108Z" }, - { url = "https://files.pythonhosted.org/packages/53/de/2b61908dc6abf25b17fed6b5a3b42a2226ec09467a3944f1d845ac29ef9b/pycapnp-2.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:ac13dd30062bb9985ae9ec4feca106af2b4fdac6468a09c7b74ad754f3921a06", size = 1208911, upload-time = "2025-09-05T03:48:53.219Z" }, { url = "https://files.pythonhosted.org/packages/74/0e/66b41ba600e5f2523e900b7cc0d2e8496b397a1f2d6a5b7b323ab83418b7/pycapnp-2.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2d2ec561bc948d11f64f43bf9601bede5d6a603d105ae311bd5583c7130624a4", size = 1619223, upload-time = "2025-09-05T03:48:54.64Z" }, { url = "https://files.pythonhosted.org/packages/40/6e/9bcb30180bd40cb0534124ff7f8ba8746a735018d593f608bf40c97821c0/pycapnp-2.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:132cd97f57f6b6636323ca9b68d389dd90b96e87af38cde31e2b5c5a064f277e", size = 1484321, upload-time = "2025-09-05T03:48:55.85Z" }, { url = "https://files.pythonhosted.org/packages/14/0a/9ee1c9ecaff499e4fd1df2f0335bc20f666ec6ce5cd80f8ab055007f3c9b/pycapnp-2.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:568e79268ba7c02a71fe558a8aec1ae3c0f0e6aff809ff618a46afe4964957d2", size = 5143502, upload-time = "2025-09-05T03:48:57.733Z" }, @@ -1982,7 +1677,6 @@ version = "12.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/b8/b6/d5612eb40be4fd5ef88c259339e6313f46ba67577a95d86c3470b951fce0/pyobjc_core-12.1.tar.gz", hash = "sha256:2bb3903f5387f72422145e1466b3ac3f7f0ef2e9960afa9bcd8961c5cbf8bd21", size = 1000532, upload-time = "2025-11-14T10:08:28.292Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/95/df/d2b290708e9da86d6e7a9a2a2022b91915cf2e712a5a82e306cb6ee99792/pyobjc_core-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c918ebca280925e7fcb14c5c43ce12dcb9574a33cccb889be7c8c17f3bcce8b6", size = 671263, upload-time = "2025-11-14T09:31:35.231Z" }, { url = "https://files.pythonhosted.org/packages/64/5a/6b15e499de73050f4a2c88fff664ae154307d25dc04da8fb38998a428358/pyobjc_core-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:818bcc6723561f207e5b5453efe9703f34bc8781d11ce9b8be286bb415eb4962", size = 678335, upload-time = "2025-11-14T09:32:20.107Z" }, ] @@ -1997,7 +1691,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/2d/87/8ca40428d05a668fecc638f2f47dba86054dbdc35351d247f039749de955/pyobjc_framework_accessibility-12.1.tar.gz", hash = "sha256:5ff362c3425edc242d49deec11f5f3e26e565cefb6a2872eda59ab7362149772", size = 29800, upload-time = "2025-11-14T10:08:31.949Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/76/00/182c57584ad8e5946a82dacdc83c9791567e10bffdea1fe92272b3fdec14/pyobjc_framework_accessibility-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5e29dac0ce8327cd5a8b9a5a8bd8aa83e4070018b93699e97ac0c3af09b42a9a", size = 11301, upload-time = "2025-11-14T09:35:28.678Z" }, { url = "https://files.pythonhosted.org/packages/cc/95/9ea0d1c16316b4b5babf4b0515e9a133ac64269d3ec031f15ee9c7c2a8c1/pyobjc_framework_accessibility-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:537691a0b28fedb8385cd093df069a6e5d7e027629671fc47b50210404eca20b", size = 11335, upload-time = "2025-11-14T09:35:30.81Z" }, ] @@ -2024,7 +1717,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/18/28/0404af2a1c6fa8fd266df26fb6196a8f3fb500d6fe3dab94701949247bea/pyobjc_framework_addressbook-12.1.tar.gz", hash = "sha256:c48b740cf981103cef1743d0804a226d86481fcb839bd84b80e9a586187e8000", size = 44359, upload-time = "2025-11-14T10:08:37.687Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9f/5a/2ecaa94e5f56c6631f0820ec4209f8075c1b7561fe37495e2d024de1c8df/pyobjc_framework_addressbook-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:681755ada6c95bd4a096bc2b9f9c24661ffe6bff19a96963ee3fad34f3d61d2b", size = 12879, upload-time = "2025-11-14T09:35:45.21Z" }, { url = "https://files.pythonhosted.org/packages/b6/33/da709c69cbb60df9522cd614d5c23c15b649b72e5d62fed1048e75c70e7b/pyobjc_framework_addressbook-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7893dd784322f4674299fb3ca40cb03385e5eddb78defd38f08c0b730813b56c", size = 12894, upload-time = "2025-11-14T09:35:47.498Z" }, ] @@ -2092,7 +1784,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/be/6a/d4e613c8e926a5744fc47a9e9fea08384a510dc4f27d844f7ad7a2d793bd/pyobjc_framework_applicationservices-12.1.tar.gz", hash = "sha256:c06abb74f119bc27aeb41bf1aef8102c0ae1288aec1ac8665ea186a067a8945b", size = 103247, upload-time = "2025-11-14T10:08:52.18Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/17/86/d07eff705ff909a0ffa96d14fc14026e9fc9dd716233648c53dfd5056b8e/pyobjc_framework_applicationservices-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bdddd492eeac6d14ff2f5bd342aba29e30dffa72a2d358c08444da22129890e2", size = 32784, upload-time = "2025-11-14T09:36:08.755Z" }, { url = "https://files.pythonhosted.org/packages/37/a7/55fa88def5c02732c4b747606ff1cbce6e1f890734bbd00f5596b21eaa02/pyobjc_framework_applicationservices-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c8f6e2fb3b3e9214ab4864ef04eee18f592b46a986c86ea0113448b310520532", size = 32835, upload-time = "2025-11-14T09:36:11.855Z" }, ] @@ -2132,7 +1823,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/9f/51/f81581e7a3c5cb6c9254c6f1e1ee1d614930493761dec491b5b0d49544b9/pyobjc_framework_audiovideobridging-12.1.tar.gz", hash = "sha256:6230ace6bec1f38e8a727c35d054a7be54e039b3053f98e6dd8d08d6baee2625", size = 38457, upload-time = "2025-11-14T10:09:01.122Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0a/f8/c614630fa382720bbd42a0ff567378630c36d10f114476d6c70b73f73b49/pyobjc_framework_audiovideobridging-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6bc24a7063b08c7d9f1749a4641430d363b6dba642c04d09b58abcee7a5260cb", size = 11037, upload-time = "2025-11-14T09:36:32.583Z" }, { url = "https://files.pythonhosted.org/packages/f3/8e/a28badfcc6c731696e3d3a8a83927bd844d992f9152f903c2fee355702ca/pyobjc_framework_audiovideobridging-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:010021502649e2cca4e999a7c09358d48c6b0ed83530bbc0b85bba6834340e4b", size = 11052, upload-time = "2025-11-14T09:36:34.475Z" }, ] @@ -2146,7 +1836,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/6c/18/86218de3bf67fc1d810065f353d9df70c740de567ebee8550d476cb23862/pyobjc_framework_authenticationservices-12.1.tar.gz", hash = "sha256:cef71faeae2559f5c0ff9a81c9ceea1c81108e2f4ec7de52a98c269feff7a4b6", size = 58683, upload-time = "2025-11-14T10:09:06.003Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/16/2f19d8a95f0cf8e940f7b7fb506ced805d5522b4118336c8e640c34517ae/pyobjc_framework_authenticationservices-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c15bb81282356f3f062ac79ff4166c93097448edc44b17dcf686e1dac78cc832", size = 20636, upload-time = "2025-11-14T09:36:48.35Z" }, { url = "https://files.pythonhosted.org/packages/f1/1d/e9f296fe1ee9a074ff6c45ce9eb109fc3b45696de000f373265c8e42fd47/pyobjc_framework_authenticationservices-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:6fd5ce10fe5359cbbfe03eb12cab3e01992b32ab65653c579b00ac93cf674985", size = 20738, upload-time = "2025-11-14T09:36:51.094Z" }, ] @@ -2160,7 +1849,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/e4/24/080afe8189c47c4bb3daa191ccfd962400ca31a67c14b0f7c2d002c2e249/pyobjc_framework_automaticassessmentconfiguration-12.1.tar.gz", hash = "sha256:2b732c02d9097682ca16e48f5d3b10056b740bc091e217ee4d5715194c8970b1", size = 21895, upload-time = "2025-11-14T10:09:08.779Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/c9/4d2785565cc470daa222f93f3d332af97de600aef6bd23507ec07501999d/pyobjc_framework_automaticassessmentconfiguration-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d94a4a3beb77b3b2ab7b610c4b41e28593d15571724a9e6ab196b82acc98dc13", size = 9316, upload-time = "2025-11-14T09:37:05.052Z" }, { url = "https://files.pythonhosted.org/packages/f2/b2/fbec3d649bf275d7a9604e5f56015be02ef8dcf002f4ae4d760436b8e222/pyobjc_framework_automaticassessmentconfiguration-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c2e22ea67d7e6d6a84d968169f83d92b59857a49ab12132de07345adbfea8a62", size = 9332, upload-time = "2025-11-14T09:37:07.083Z" }, ] @@ -2174,7 +1862,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/7e/08/362bf6ac2bba393c46cf56078d4578b692b56857c385e47690637a72f0dd/pyobjc_framework_automator-12.1.tar.gz", hash = "sha256:7491a99347bb30da3a3f744052a03434ee29bee3e2ae520576f7e796740e4ba7", size = 186068, upload-time = "2025-11-14T10:09:20.82Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/99/480e07eef053a2ad2a5cf1e15f71982f21d7f4119daafac338fa0352309c/pyobjc_framework_automator-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4f3d96da10d28c5c197193a9d805a13157b1cb694b6c535983f8572f5f8746ea", size = 10016, upload-time = "2025-11-14T09:37:18.621Z" }, { url = "https://files.pythonhosted.org/packages/e3/36/2e8c36ddf20d501f9d344ed694e39021190faffc44b596f3a430bf437174/pyobjc_framework_automator-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4df9aec77f0fbca66cd3534d1b8398fe6f3e3c2748c0fc12fec2546c7f2e3ffd", size = 10034, upload-time = "2025-11-14T09:37:20.293Z" }, ] @@ -2191,7 +1878,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/cd/42/c026ab308edc2ed5582d8b4b93da6b15d1b6557c0086914a4aabedd1f032/pyobjc_framework_avfoundation-12.1.tar.gz", hash = "sha256:eda0bb60be380f9ba2344600c4231dd58a3efafa99fdc65d3673ecfbb83f6fcb", size = 310047, upload-time = "2025-11-14T10:09:40.069Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/5a/4ef36b309138840ff8cd85364f66c29e27023f291004c335a99f6e87e599/pyobjc_framework_avfoundation-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:82cc2c2d9ab6cc04feeb4700ff251d00f1fcafff573c63d4e87168ff80adb926", size = 83328, upload-time = "2025-11-14T09:37:40.808Z" }, { url = "https://files.pythonhosted.org/packages/a6/00/ca471e5dd33f040f69320832e45415d00440260bf7f8221a9df4c4662659/pyobjc_framework_avfoundation-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bf634f89265b4d93126153200d885b6de4859ed6b3bc65e69ff75540bc398406", size = 83375, upload-time = "2025-11-14T09:37:47.262Z" }, ] @@ -2206,7 +1892,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/34/a9/e44db1a1f26e2882c140f1d502d508b1f240af9048909dcf1e1a687375b4/pyobjc_framework_avkit-12.1.tar.gz", hash = "sha256:a5c0ddb0cb700f9b09c8afeca2c58952d554139e9bb078236d2355b1fddfb588", size = 28473, upload-time = "2025-11-14T10:09:43.105Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8c/68/409ee30f3418b76573c70aa05fa4c38e9b8b1d4864093edcc781d66019c2/pyobjc_framework_avkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:78bd31a8aed48644e5407b444dec8b1e15ff77af765607b52edf88b8f1213ac7", size = 11583, upload-time = "2025-11-14T09:38:17.569Z" }, { url = "https://files.pythonhosted.org/packages/75/34/e77b18f7ed0bd707afd388702e910bdf2d0acee39d1139e8619c916d3eb4/pyobjc_framework_avkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eef2c0a51465de025a4509db05ef18ca2b678bb00ee0a8fbad7fd470edfd58f9", size = 11613, upload-time = "2025-11-14T09:38:19.78Z" }, ] @@ -2220,7 +1905,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/6e/83/15bf6c28ec100dae7f92d37c9e117b3b4ee6b4873db062833e16f1cfd6c4/pyobjc_framework_avrouting-12.1.tar.gz", hash = "sha256:6a6c5e583d14f6501df530a9d0559a32269a821fc8140e3646015f097155cd1c", size = 20031, upload-time = "2025-11-14T10:09:45.701Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/69/a7/5c5725db9c91b492ffbd4ae3e40025deeb9e60fcc7c8fbd5279b52280b95/pyobjc_framework_avrouting-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9a79f05fb66e337cabc19a9d949c8b29a5145c879f42e29ba02b601b7700d1bb", size = 8431, upload-time = "2025-11-14T09:38:33.018Z" }, { url = "https://files.pythonhosted.org/packages/68/54/fa24f666525c1332a11b2de959c9877b0fe08f00f29ecf96964b24246c13/pyobjc_framework_avrouting-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4c0fb0d3d260527320377a70c87688ca5e4a208b09fddcae2b4257d7fe9b1e18", size = 8450, upload-time = "2025-11-14T09:38:34.941Z" }, ] @@ -2234,7 +1918,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/34/d1/e917fba82790495152fd3508c5053827658881cf7e9887ba60def5e3f221/pyobjc_framework_backgroundassets-12.1.tar.gz", hash = "sha256:8da34df9ae4519c360c429415477fdaf3fbba5addbc647b3340b8783454eb419", size = 26210, upload-time = "2025-11-14T10:09:48.792Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/49/33c1c3eaf26a7d89dd414e14939d4f02063d66252d0f51c02082350223e0/pyobjc_framework_backgroundassets-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:17de7990b5ea8047d447339f9e9e6f54b954ffc06647c830932a1688c4743fea", size = 10763, upload-time = "2025-11-14T09:38:46.671Z" }, { url = "https://files.pythonhosted.org/packages/de/34/bbba61f0e8ecb0fe0da7aa2c9ea15f7cb0dca2fb2914fcdcd77b782b5c11/pyobjc_framework_backgroundassets-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2c11cb98650c1a4bc68eeb4b040541ba96613434c5957e98e9bb363413b23c91", size = 10786, upload-time = "2025-11-14T09:38:48.341Z" }, ] @@ -2251,7 +1934,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/5d/b9/39f9de1730e6f8e73be0e4f0c6087cd9439cbe11645b8052d22e1fb8e69b/pyobjc_framework_browserenginekit-12.1.tar.gz", hash = "sha256:6a1a34a155778ab55ab5f463e885f2a3b4680231264e1fe078e62ddeccce49ed", size = 29120, upload-time = "2025-11-14T10:09:51.582Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f2/a4/2d576d71b2e4b3e1a9aa9fd62eb73167d90cdc2e07b425bbaba8edd32ff5/pyobjc_framework_browserenginekit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:41229c766fb3e5bba2de5e580776388297303b4d63d3065fef3f67b77ec46c3f", size = 11526, upload-time = "2025-11-14T09:38:58.861Z" }, { url = "https://files.pythonhosted.org/packages/46/e0/8d2cebbfcfd6aacb805ae0ae7ba931f6a39140540b2e1e96719e7be28359/pyobjc_framework_browserenginekit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d15766bb841b081447015c9626e2a766febfe651f487893d29c5d72bef976b94", size = 11545, upload-time = "2025-11-14T09:39:00.988Z" }, ] @@ -2291,7 +1973,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/1a/c0/1859d4532d39254df085309aff55b85323576f00a883626325af40da4653/pyobjc_framework_callkit-12.1.tar.gz", hash = "sha256:fd6dc9688b785aab360139d683be56f0844bf68bf5e45d0eb770cb68221083cc", size = 29171, upload-time = "2025-11-14T10:10:01.336Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/f6/aafd14b31e00d59d830f9a8e8e46c4f41a249f0370499d5b017599362cf1/pyobjc_framework_callkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e73beae08e6a32bcced8d5bdb45b52d6a0866dd1485eaaddba6063f17d41fcb0", size = 11273, upload-time = "2025-11-14T09:39:16.837Z" }, { url = "https://files.pythonhosted.org/packages/2e/b7/b3a498b14751b4be6af5272c9be9ded718aa850ebf769b052c7d610a142a/pyobjc_framework_callkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:12adc0ace464a057f8908187698e1d417c6c53619797a69d096f4329bffb1089", size = 11334, upload-time = "2025-11-14T09:39:18.622Z" }, ] @@ -2318,7 +1999,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/d2/6a/f5f0f191956e187db85312cbffcc41bf863670d121b9190b4a35f0d36403/pyobjc_framework_cfnetwork-12.1.tar.gz", hash = "sha256:2d16e820f2d43522c793f55833fda89888139d7a84ca5758548ba1f3a325a88d", size = 44383, upload-time = "2025-11-14T10:10:08.428Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f0/7e/82aca783499b690163dd19d5ccbba580398970874a3431bfd7c14ceddbb3/pyobjc_framework_cfnetwork-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3bf93c0f3d262f629e72f8dd43384d0930ed8e610b3fc5ff555c0c1a1e05334a", size = 18949, upload-time = "2025-11-14T09:39:32.924Z" }, { url = "https://files.pythonhosted.org/packages/f9/0b/28034e63f3a25b30ede814469c3f57d44268cbced19664c84a8664200f9d/pyobjc_framework_cfnetwork-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:92760da248c757085fc39bce4388a0f6f0b67540e51edf60a92ad60ca907d071", size = 19135, upload-time = "2025-11-14T09:39:36.382Z" }, ] @@ -2348,7 +2028,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/ac/ef/67815278023b344a79c7e95f748f647245d6f5305136fc80615254ad447c/pyobjc_framework_classkit-12.1.tar.gz", hash = "sha256:8d1e9dd75c3d14938ff533d88b72bca2d34918e4461f418ea323bfb2498473b4", size = 26298, upload-time = "2025-11-14T10:10:13.406Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/14/e2/67bd062fbc9761c34b9911ed099ee50ccddc3032779ce420ca40083ee15c/pyobjc_framework_classkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bd90aacc68eff3412204a9040fa81eb18348cbd88ed56d33558349f3e51bff52", size = 8857, upload-time = "2025-11-14T09:39:53.283Z" }, { url = "https://files.pythonhosted.org/packages/87/5e/cf43c647af872499fc8e80cc6ac6e9ad77d9c77861dc2e62bdd9b01473ce/pyobjc_framework_classkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c027a3cd9be5fee3f605589118b8b278297c384a271f224c1a98b224e0c087e6", size = 8877, upload-time = "2025-11-14T09:39:54.979Z" }, ] @@ -2377,7 +2056,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/02/a3/16ca9a15e77c061a9250afbae2eae26f2e1579eb8ca9462ae2d2c71e1169/pyobjc_framework_cocoa-12.1.tar.gz", hash = "sha256:5556c87db95711b985d5efdaaf01c917ddd41d148b1e52a0c66b1a2e2c5c1640", size = 2772191, upload-time = "2025-11-14T10:13:02.069Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3f/07/5760735c0fffc65107e648eaf7e0991f46da442ac4493501be5380e6d9d4/pyobjc_framework_cocoa-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f52228bcf38da64b77328787967d464e28b981492b33a7675585141e1b0a01e6", size = 383812, upload-time = "2025-11-14T09:40:53.169Z" }, { url = "https://files.pythonhosted.org/packages/95/bf/ee4f27ec3920d5c6fc63c63e797c5b2cc4e20fe439217085d01ea5b63856/pyobjc_framework_cocoa-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:547c182837214b7ec4796dac5aee3aa25abc665757b75d7f44f83c994bcb0858", size = 384590, upload-time = "2025-11-14T09:41:17.336Z" }, ] @@ -2431,7 +2109,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/4b/a0/ce0542d211d4ea02f5cbcf72ee0a16b66b0d477a4ba5c32e00117703f2f0/pyobjc_framework_contacts-12.1.tar.gz", hash = "sha256:89bca3c5cf31404b714abaa1673577e1aaad6f2ef49d4141c6dbcc0643a789ad", size = 42378, upload-time = "2025-11-14T10:13:14.203Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/94/f5/5d2c03cf5219f2e35f3f908afa11868e9096aff33b29b41d63f2de3595f2/pyobjc_framework_contacts-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8ab86070895a005239256d207e18209b1a79d35335b6604db160e8375a7165e6", size = 12086, upload-time = "2025-11-14T09:43:03.225Z" }, { url = "https://files.pythonhosted.org/packages/32/c8/2c4638c0d06447886a34070eebb9ba57407d4dd5f0fcb7ab642568272b88/pyobjc_framework_contacts-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2e5ce33b686eb9c0a39351938a756442ea8dea88f6ae2f16bff5494a8569c687", size = 12165, upload-time = "2025-11-14T09:43:05.119Z" }, ] @@ -2446,7 +2123,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/cd/0c/7bb7f898456a81d88d06a1084a42e374519d2e40a668a872b69b11f8c1f9/pyobjc_framework_contactsui-12.1.tar.gz", hash = "sha256:aaeca7c9e0c9c4e224d73636f9a558f9368c2c7422155a41fd4d7a13613a77c1", size = 18769, upload-time = "2025-11-14T10:13:16.301Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/04/e3/8d330640bf0337289834334c54c599fec2dad38a8a3b736d40bcb5d8db6e/pyobjc_framework_contactsui-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:10e7ce3b105795919605be89ebeecffd656e82dbf1bafa5db6d51d6def2265ee", size = 7871, upload-time = "2025-11-14T09:43:16.973Z" }, { url = "https://files.pythonhosted.org/packages/f3/ab/319aa52dfe6f836f4dc542282c2c13996222d4f5c9ea7ff8f391b12dac83/pyobjc_framework_contactsui-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:057f40d2f6eb1b169a300675ec75cc7a747cddcbcee8ece133e652a7086c5ab5", size = 7888, upload-time = "2025-11-14T09:43:18.502Z" }, ] @@ -2460,7 +2136,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/84/d1/0b884c5564ab952ff5daa949128c64815300556019c1bba0cf2ca752a1a0/pyobjc_framework_coreaudio-12.1.tar.gz", hash = "sha256:a9e72925fcc1795430496ce0bffd4ddaa92c22460a10308a7283ade830089fe1", size = 75077, upload-time = "2025-11-14T10:13:22.345Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/25/491ff549fd9a40be4416793d335bff1911d3d1d1e1635e3b0defbd2cf585/pyobjc_framework_coreaudio-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a452de6b509fa4a20160c0410b72330ac871696cd80237883955a5b3a4de8f2a", size = 35327, upload-time = "2025-11-14T09:43:32.523Z" }, { url = "https://files.pythonhosted.org/packages/a9/48/05b5192122e23140cf583eac99ccc5bf615591d6ff76483ba986c38ee750/pyobjc_framework_coreaudio-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a5ad6309779663f846ab36fe6c49647e470b7e08473c3e48b4f004017bdb68a4", size = 36908, upload-time = "2025-11-14T09:43:36.108Z" }, ] @@ -2475,7 +2150,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/41/1c/5c7e39b9361d4eec99b9115b593edd9825388acd594cb3b4519f8f1ac12c/pyobjc_framework_coreaudiokit-12.1.tar.gz", hash = "sha256:b83624f8de3068ab2ca279f786be0804da5cf904ff9979d96007b69ef4869e1e", size = 20137, upload-time = "2025-11-14T10:13:24.611Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/53/e4233fbe5b94b124f5612e1edc130a9280c4674a1d1bf42079ea14b816e1/pyobjc_framework_coreaudiokit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e1144c272f8d6429a34a6757700048f4631eb067c4b08d4768ddc28c371a7014", size = 7250, upload-time = "2025-11-14T09:43:53.208Z" }, { url = "https://files.pythonhosted.org/packages/19/d7/f171c04c6496afeaad2ab658b0c810682c8407127edc94d4b3f3b90c2bb1/pyobjc_framework_coreaudiokit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:97d5dd857e73d5b597cfc980972b021314b760e2f5bdde7bbba0334fbf404722", size = 7273, upload-time = "2025-11-14T09:43:55.411Z" }, ] @@ -2489,7 +2163,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/4b/25/d21d6cb3fd249c2c2aa96ee54279f40876a0c93e7161b3304bf21cbd0bfe/pyobjc_framework_corebluetooth-12.1.tar.gz", hash = "sha256:8060c1466d90bbb9100741a1091bb79975d9ba43911c9841599879fc45c2bbe0", size = 33157, upload-time = "2025-11-14T10:13:28.064Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/57/7a/26ae106beb97e9c4745065edb3ce3c2bdd91d81f5b52b8224f82ce9d5fb9/pyobjc_framework_corebluetooth-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:37e6456c8a076bd5a2bdd781d0324edd5e7397ef9ac9234a97433b522efb13cf", size = 13189, upload-time = "2025-11-14T09:44:06.229Z" }, { url = "https://files.pythonhosted.org/packages/2a/56/01fef62a479cdd6ff9ee40b6e062a205408ff386ce5ba56d7e14a71fcf73/pyobjc_framework_corebluetooth-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fe72c9732ee6c5c793b9543f08c1f5bdd98cd95dfc9d96efd5708ec9d6eeb213", size = 13209, upload-time = "2025-11-14T09:44:08.203Z" }, ] @@ -2503,7 +2176,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/3e/c5/8cd46cd4f1b7cf88bdeed3848f830ea9cdcc4e55cd0287a968a2838033fb/pyobjc_framework_coredata-12.1.tar.gz", hash = "sha256:1e47d3c5e51fdc87a90da62b97cae1bc49931a2bb064db1305827028e1fc0ffa", size = 124348, upload-time = "2025-11-14T10:13:36.435Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/a8/4c694c85365071baef36013a7460850dcf6ebfea0ba239e52d7293cdcb93/pyobjc_framework_coredata-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c861dc42b786243cbd96d9ea07d74023787d03637ef69a2f75a1191a2f16d9d6", size = 16395, upload-time = "2025-11-14T09:44:21.105Z" }, { url = "https://files.pythonhosted.org/packages/a3/29/fe24dc81e0f154805534923a56fe572c3b296092f086cf5a239fccc2d46a/pyobjc_framework_coredata-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a3ee3581ca23ead0b152257e98622fe0bf7e7948f30a62a25a17cafe28fe015e", size = 16409, upload-time = "2025-11-14T09:44:23.582Z" }, ] @@ -2530,7 +2202,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/cc/79/b75885e0d75397dc2fe1ed9ca80be2b64c18b817f5fb924277cb1bf7b163/pyobjc_framework_corelocation-12.1.tar.gz", hash = "sha256:3674e9353f949d91dde6230ad68f6d5748a7f0424751e08a2c09d06050d66231", size = 53511, upload-time = "2025-11-14T10:13:43.384Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/34/ac/44b6cb414ce647da8328d0ed39f0a8b6eb54e72189ce9049678ce2cb04c3/pyobjc_framework_corelocation-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ffc96b9ba504b35fe3e0fcfb0153e68fdfca6fe71663d240829ceab2d7122588", size = 12700, upload-time = "2025-11-14T09:44:38.717Z" }, { url = "https://files.pythonhosted.org/packages/71/57/1b670890fbf650f1a00afe5ee897ea3856a4a1417c2304c633ee2e978ed0/pyobjc_framework_corelocation-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8c35ad29a062fea7d417fd8997a9309660ba7963f2847c004e670efbe6bb5b00", size = 12721, upload-time = "2025-11-14T09:44:41.185Z" }, ] @@ -2544,7 +2215,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/da/7d/5ad600ff7aedfef8ba8f51b11d9aaacdf247b870bd14045d6e6f232e3df9/pyobjc_framework_coremedia-12.1.tar.gz", hash = "sha256:166c66a9c01e7a70103f3ca44c571431d124b9070612ef63a1511a4e6d9d84a7", size = 89566, upload-time = "2025-11-14T10:13:49.788Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/bc/e66de468b3777d8fece69279cf6d2af51d2263e9a1ccad21b90c35c74b1b/pyobjc_framework_coremedia-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ee7b822c9bb674b5b0a70bfb133410acae354e9241b6983f075395f3562f3c46", size = 29503, upload-time = "2025-11-14T09:44:54.716Z" }, { url = "https://files.pythonhosted.org/packages/c8/ae/f773cdc33c34a3f9ce6db829dbf72661b65c28ea9efaec8940364185b977/pyobjc_framework_coremedia-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:161a627f5c8cd30a5ebb935189f740e21e6cd94871a9afd463efdb5d51e255fa", size = 29396, upload-time = "2025-11-14T09:44:57.563Z" }, ] @@ -2558,7 +2228,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/08/8e/23baee53ccd6c011c965cff62eb55638b4088c3df27d2bf05004105d6190/pyobjc_framework_coremediaio-12.1.tar.gz", hash = "sha256:880b313b28f00b27775d630174d09e0b53d1cdbadb74216618c9dd5b3eb6806a", size = 51100, upload-time = "2025-11-14T10:13:54.277Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/46/6c/88514f8938719f74aa13abb9fd5492499f1834391133809b4e125c3e7150/pyobjc_framework_coremediaio-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3da79c5b9785c5ccc1f5982de61d4d0f1ba29717909eb6720734076ccdc0633c", size = 17218, upload-time = "2025-11-14T09:45:15.294Z" }, { url = "https://files.pythonhosted.org/packages/d4/0c/9425c53c9a8c26e468e065ba12ef076bab20197ff7c82052a6dddd46d42b/pyobjc_framework_coremediaio-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1108f8a278928fbca465f95123ea4a56456bd6571c1dc8b91793e6c61d624517", size = 17277, upload-time = "2025-11-14T09:45:17.457Z" }, ] @@ -2572,7 +2241,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/75/96/2d583060a71a73c8a7e6d92f2a02675621b63c1f489f2639e020fae34792/pyobjc_framework_coremidi-12.1.tar.gz", hash = "sha256:3c6f1fd03997c3b0f20ab8545126b1ce5f0cddcc1587dffacad876c161da8c54", size = 55587, upload-time = "2025-11-14T10:13:58.903Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/76/d5/49b8720ec86f64e3dc3c804bd7e16fabb2a234a9a8b1b6753332ed343b4e/pyobjc_framework_coremidi-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:af3cdf195e8d5e30d1203889cc4107bebc6eb901aaa81bf3faf15e9ffaca0735", size = 24282, upload-time = "2025-11-14T09:45:32.288Z" }, { url = "https://files.pythonhosted.org/packages/e3/2d/99520f6f1685e4cad816e55cbf6d85f8ce6ea908107950e2d37dc17219d8/pyobjc_framework_coremidi-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e84ffc1de59691c04201b0872e184fe55b5589f3a14876bd14460f3b5f3cd109", size = 24317, upload-time = "2025-11-14T09:45:34.92Z" }, ] @@ -2586,7 +2254,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/30/2d/baa9ea02cbb1c200683cb7273b69b4bee5070e86f2060b77e6a27c2a9d7e/pyobjc_framework_coreml-12.1.tar.gz", hash = "sha256:0d1a4216891a18775c9e0170d908714c18e4f53f9dc79fb0f5263b2aa81609ba", size = 40465, upload-time = "2025-11-14T10:14:02.265Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/34/0f/f55369da4a33cfe1db38a3512aac4487602783d3a1d572d2c8c4ccce6abc/pyobjc_framework_coreml-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:16dafcfb123f022e62f47a590a7eccf7d0cb5957a77fd5f062b5ee751cb5a423", size = 11331, upload-time = "2025-11-14T09:45:50.445Z" }, { url = "https://files.pythonhosted.org/packages/bb/39/4defef0deb25c5d7e3b7826d301e71ac5b54ef901b7dac4db1adc00f172d/pyobjc_framework_coreml-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:10dc8e8db53d7631ebc712cad146e3a9a9a443f4e1a037e844149a24c3c42669", size = 11356, upload-time = "2025-11-14T09:45:52.271Z" }, ] @@ -2600,7 +2267,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/2c/eb/abef7d405670cf9c844befc2330a46ee59f6ff7bac6f199bf249561a2ca6/pyobjc_framework_coremotion-12.1.tar.gz", hash = "sha256:8e1b094d34084cc8cf07bedc0630b4ee7f32b0215011f79c9e3cd09d205a27c7", size = 33851, upload-time = "2025-11-14T10:14:05.619Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/77/fd/0d24796779e4d8187abbce5d06cfd7614496d57a68081c5ff1e978b398f9/pyobjc_framework_coremotion-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ed8cb67927985d97b1dd23ab6a4a1b716fc7c409c35349816108781efdcbb5b6", size = 10382, upload-time = "2025-11-14T09:46:03.438Z" }, { url = "https://files.pythonhosted.org/packages/bc/75/89fa4aab818aeca21ac0a60b7ceb89a9e685df0ddd3828d36a6f84a0cff0/pyobjc_framework_coremotion-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a77908ab83c422030f913a2a761d196359ab47f6d1e7c76f21de2c6c05ea2f5f", size = 10406, upload-time = "2025-11-14T09:46:05.076Z" }, ] @@ -2615,7 +2281,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/4c/b3/52338a3ff41713f7d7bccaf63bef4ba4a8f2ce0c7eaff39a3629d022a79a/pyobjc_framework_coreservices-12.1.tar.gz", hash = "sha256:fc6a9f18fc6da64c166fe95f2defeb7ac8a9836b3b03bb6a891d36035260dbaa", size = 366150, upload-time = "2025-11-14T10:14:28.133Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/55/56/c905deb5ab6f7f758faac3f2cbc6f62fde89f8364837b626801bba0975c3/pyobjc_framework_coreservices-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b6ef07bcf99e941395491f1efcf46e99e5fb83eb6bfa12ae5371135d83f731e1", size = 30196, upload-time = "2025-11-14T09:46:19.356Z" }, { url = "https://files.pythonhosted.org/packages/61/6c/33984caaf497fc5a6f86350d7ca4fac8abeb2bc33203edc96955a21e8c05/pyobjc_framework_coreservices-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8751dc2edcb7cfa248bf8a274c4d6493e8d53ef28a843827a4fc9a0a8b04b8be", size = 30206, upload-time = "2025-11-14T09:46:22.732Z" }, ] @@ -2629,7 +2294,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/99/d0/88ca73b0cf23847af463334989dd8f98e44f801b811e7e1d8a5627ec20b4/pyobjc_framework_corespotlight-12.1.tar.gz", hash = "sha256:57add47380cd0bbb9793f50a4a4b435a90d4ebd2a33698e058cb353ddfb0d068", size = 38002, upload-time = "2025-11-14T10:14:31.948Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/37/1e7bacb9307a8df52234923e054b7303783e7a48a4637d44ce390b015921/pyobjc_framework_corespotlight-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:404a1e362fe19f0dff477edc1665d8ad90aada928246802da777399f7c06b22e", size = 9976, upload-time = "2025-11-14T09:46:45.221Z" }, { url = "https://files.pythonhosted.org/packages/f6/3b/d3031eddff8029859de6d92b1f741625b1c233748889141a6a5a89b96f0e/pyobjc_framework_corespotlight-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bfcea64ab3250e2886d202b8731be3817b5ac0c8c9f43e77d0d5a0b6602e71a7", size = 9996, upload-time = "2025-11-14T09:46:47.157Z" }, ] @@ -2644,7 +2308,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/29/da/682c9c92a39f713bd3c56e7375fa8f1b10ad558ecb075258ab6f1cdd4a6d/pyobjc_framework_coretext-12.1.tar.gz", hash = "sha256:e0adb717738fae395dc645c9e8a10bb5f6a4277e73cba8fa2a57f3b518e71da5", size = 90124, upload-time = "2025-11-14T10:14:38.596Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f0/81/7b8efc41e743adfa2d74b92dec263c91bcebfb188d2a8f5eea1886a195ff/pyobjc_framework_coretext-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4f6742ba5b0bb7629c345e99eff928fbfd9e9d3d667421ac1a2a43bdb7ba9833", size = 29990, upload-time = "2025-11-14T09:47:01.206Z" }, { url = "https://files.pythonhosted.org/packages/cd/0f/ddf45bf0e3ba4fbdc7772de4728fd97ffc34a0b5a15e1ab1115b202fe4ae/pyobjc_framework_coretext-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d246fa654bdbf43bae3969887d58f0b336c29b795ad55a54eb76397d0e62b93c", size = 30108, upload-time = "2025-11-14T09:47:04.228Z" }, ] @@ -2658,7 +2321,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/88/71/739a5d023566b506b3fd3d2412983faa95a8c16226c0dcd0f67a9294a342/pyobjc_framework_corewlan-12.1.tar.gz", hash = "sha256:a9d82ec71ef61f37e1d611caf51a4203f3dbd8caf827e98128a1afaa0fd2feb5", size = 32417, upload-time = "2025-11-14T10:14:41.921Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f5/74/4d8a52b930a276f6f9b4f3b1e07cd518cb6d923cb512e39c935e3adb0b86/pyobjc_framework_corewlan-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3e3f2614eb37dfd6860d6a0683877c2f3b909758ef78b68e5f6b7ea9c858cc51", size = 9931, upload-time = "2025-11-14T09:47:20.849Z" }, { url = "https://files.pythonhosted.org/packages/4e/31/3e9cf2c0ac3c979062958eae7a275b602515c9c76fd30680e1ee0fea82ae/pyobjc_framework_corewlan-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5cba04c0550fc777767cd3a5471e4ed837406ab182d7d5c273bc5ce6ea237bfe", size = 9958, upload-time = "2025-11-14T09:47:22.474Z" }, ] @@ -2672,7 +2334,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/6b/7c/d03ff4f74054578577296f33bc669fce16c7827eb1a553bb372b5aab30ca/pyobjc_framework_cryptotokenkit-12.1.tar.gz", hash = "sha256:c95116b4b7a41bf5b54aff823a4ef6f4d9da4d0441996d6d2c115026a42d82f5", size = 32716, upload-time = "2025-11-14T10:14:45.024Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/90/1623b60d6189db08f642777374fd32287b06932c51dfeb1e9ed5bbf67f35/pyobjc_framework_cryptotokenkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d84b75569054fa0886e3e341c00d7179d5fe287e6d1509630dd698ee60ec5af1", size = 12598, upload-time = "2025-11-14T09:47:33.798Z" }, { url = "https://files.pythonhosted.org/packages/0f/c7/aecba253cf21303b2c9f3ce03fc0e987523609d7839ea8e0a688ae816c96/pyobjc_framework_cryptotokenkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ef51a86c1d0125fabdfad0b3efa51098fb03660d8dad2787d82e8b71c9f189de", size = 12633, upload-time = "2025-11-14T09:47:35.707Z" }, ] @@ -2738,7 +2399,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/c4/87/8bd4544793bfcdf507174abddd02b1f077b48fab0004b3db9a63142ce7e9/pyobjc_framework_discrecording-12.1.tar.gz", hash = "sha256:6defc8ea97fb33b4d43870c673710c04c3dc48be30cdf78ba28191a922094990", size = 55607, upload-time = "2025-11-14T10:14:58.276Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/ce/89df4d53a0a5e3a590d6e735eca4f0ba4d1ccc0e0acfbc14164026a3c502/pyobjc_framework_discrecording-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f7d815f28f781e20de0bf278aaa10b0de7e5ea1189aa17676c0bf5b99e9e0d52", size = 14540, upload-time = "2025-11-14T09:47:55.442Z" }, { url = "https://files.pythonhosted.org/packages/c8/70/14a5aa348a5eba16e8773bb56698575cf114aa55aa303037b7000fc53959/pyobjc_framework_discrecording-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:865f1551e58459da6073360afc8f2cc452472c676ba83dcaa9b0c44e7775e4b5", size = 14566, upload-time = "2025-11-14T09:47:57.503Z" }, ] @@ -2831,7 +2491,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/9a/d4/e9b1f74d29ad9dea3d60468d59b80e14ed3a19f9f7a25afcbc10d29c8a1e/pyobjc_framework_extensionkit-12.1.tar.gz", hash = "sha256:773987353e8aba04223dbba3149253db944abfb090c35318b3a770195b75da6d", size = 18694, upload-time = "2025-11-14T10:15:14.104Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4f/02/3d1df48f838dc9d64f03bedd29f0fdac6c31945251c9818c3e34083eb731/pyobjc_framework_extensionkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9139c064e1c7f21455411848eb39f092af6085a26cad322aa26309260e7929d9", size = 7919, upload-time = "2025-11-14T09:48:22.14Z" }, { url = "https://files.pythonhosted.org/packages/9c/d9/8064dad6114a489e5439cc20d9fb0dd64cfc406d875b4a3c87015b3f6266/pyobjc_framework_extensionkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7e01d705c7ac6d080ae34a81db6d9b81875eabefa63fd6eafbfa30f676dd780b", size = 7932, upload-time = "2025-11-14T09:48:23.653Z" }, ] @@ -2845,7 +2504,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/8e/35/86c097ae2fdf912c61c1276e80f3e090a3fc898c75effdf51d86afec456b/pyobjc_framework_externalaccessory-12.1.tar.gz", hash = "sha256:079f770a115d517a6ab87db1b8a62ca6cdf6c35ae65f45eecc21b491e78776c0", size = 20958, upload-time = "2025-11-14T10:15:16.419Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/18/01/2a83b63e82ce58722277a00521c3aeec58ac5abb3086704554e47f8becf3/pyobjc_framework_externalaccessory-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:32208e05c9448c8f41b3efdd35dbea4a8b119af190f7a2db0d580be8a5cf962e", size = 8911, upload-time = "2025-11-14T09:48:35.349Z" }, { url = "https://files.pythonhosted.org/packages/ec/52/984034396089766b6e5ff3be0f93470e721c420fa9d1076398557532234f/pyobjc_framework_externalaccessory-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:dedbf7a09375ac19668156c1417bd7829565b164a246b714e225b9cbb6a351ad", size = 8932, upload-time = "2025-11-14T09:48:37.393Z" }, ] @@ -2859,7 +2517,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/cc/9a/724b1fae5709f8860f06a6a2a46de568f9bb8bdb2e2aae45b4e010368f51/pyobjc_framework_fileprovider-12.1.tar.gz", hash = "sha256:45034e0d00ae153c991aa01cb1fd41874650a30093e77ba73401dcce5534c8ad", size = 43071, upload-time = "2025-11-14T10:15:19.989Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1d/37/2f56167e9f43d3b25a5ed073305ca0cfbfc66bedec7aae9e1f2c9c337265/pyobjc_framework_fileprovider-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9d527c417f06d27c4908e51d4e6ccce0adcd80c054f19e709626e55c511dc963", size = 20970, upload-time = "2025-11-14T09:48:50.557Z" }, { url = "https://files.pythonhosted.org/packages/2c/f5/56f0751a2988b2caca89d6800c8f29246828d1a7498bb676ef1ab28000b7/pyobjc_framework_fileprovider-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:89b140ea8369512ddf4164b007cbe35b4d97d1dcb8affa12a7264c0ab8d56e45", size = 21003, upload-time = "2025-11-14T09:48:53.128Z" }, ] @@ -2899,7 +2556,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/43/17/21f45d2bca2efc72b975f2dfeae7a163dbeabb1236c1f188578403fd4f09/pyobjc_framework_fsevents-12.1.tar.gz", hash = "sha256:a22350e2aa789dec59b62da869c1b494a429f8c618854b1383d6473f4c065a02", size = 26487, upload-time = "2025-11-14T10:15:26.796Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/3f/a7fe5656b205ee3a9fd828e342157b91e643ee3e5c0d50b12bd4c737f683/pyobjc_framework_fsevents-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:459cc0aac9850c489d238ba778379d09f073bbc3626248855e78c4bc4d97fe46", size = 13059, upload-time = "2025-11-14T09:49:09.814Z" }, { url = "https://files.pythonhosted.org/packages/2d/e3/2c5eeea390c0b053e2d73b223af3ec87a3e99a8106e8d3ee79942edb0822/pyobjc_framework_fsevents-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a2949358513fd7bc622fb362b5c4af4fc24fc6307320070ca410885e5e13d975", size = 13141, upload-time = "2025-11-14T09:49:11.947Z" }, ] @@ -2913,7 +2569,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/a1/55/d00246d6e6d9756e129e1d94bc131c99eece2daa84b2696f6442b8a22177/pyobjc_framework_fskit-12.1.tar.gz", hash = "sha256:ec54e941cdb0b7d800616c06ca76a93685bd7119b8aa6eb4e7a3ee27658fc7ba", size = 42372, upload-time = "2025-11-14T10:15:30.411Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/1a/5a0b6b8dc18b9dbcb7d1ef7bebdd93f12560097dafa6d7c4b3c15649afba/pyobjc_framework_fskit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:95b9135eea81eeed319dcca32c9db04b38688301586180b86c4585fef6b0e9cd", size = 20228, upload-time = "2025-11-14T09:49:25.324Z" }, { url = "https://files.pythonhosted.org/packages/6d/a9/0c47469fe80fa14bc698bb0a5b772b44283cc3aca0f67e7f70ab45e09b24/pyobjc_framework_fskit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:50972897adea86508cfee33ec4c23aa91dede97e9da1640ea2fe74702b065be1", size = 20250, upload-time = "2025-11-14T09:49:28.065Z" }, ] @@ -2927,7 +2582,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/d2/f8/b5fd86f6b722d4259228922e125b50e0a6975120a1c4d957e990fb84e42c/pyobjc_framework_gamecenter-12.1.tar.gz", hash = "sha256:de4118f14c9cf93eb0316d49da410faded3609ce9cd63425e9ef878cebb7ea72", size = 31473, upload-time = "2025-11-14T10:15:33.38Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ca/17/6491f9e96664e05ec00af7942a6c2f69217771522c9d1180524273cac7cb/pyobjc_framework_gamecenter-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:30943512f2aa8cb129f8e1abf951bf06922ca20b868e918b26c19202f4ee5cc4", size = 18824, upload-time = "2025-11-14T09:49:42.543Z" }, { url = "https://files.pythonhosted.org/packages/16/ee/b496cc4248c5b901e159d6d9a437da9b86a3105fc3999a66744ba2b2c884/pyobjc_framework_gamecenter-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e8d6d10b868be7c00c2d5a0944cc79315945735dcf17eaa3fec1a7986d26be9b", size = 18868, upload-time = "2025-11-14T09:49:44.767Z" }, ] @@ -2941,7 +2595,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/21/14/353bb1fe448cd833839fd199ab26426c0248088753e63c22fe19dc07530f/pyobjc_framework_gamecontroller-12.1.tar.gz", hash = "sha256:64ed3cc4844b67f1faeb540c7cc8d512c84f70b3a4bafdb33d4663a2b2a2b1d8", size = 54554, upload-time = "2025-11-14T10:15:37.591Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e4/dc/1d8bd4845a46cb5a5c1f860d85394e64729b2447bbe149bb33301bc99056/pyobjc_framework_gamecontroller-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2633c2703fb30ce068b2f5ce145edbd10fd574d2670b5cdee77a9a126f154fec", size = 20913, upload-time = "2025-11-14T09:49:58.863Z" }, { url = "https://files.pythonhosted.org/packages/06/28/9f03d0ef7c78340441f78b19fb2d2c952af04a240da5ed30c7cf2d0d0f4e/pyobjc_framework_gamecontroller-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:878aa6590c1510e91bfc8710d6c880e7a8f3656a7b7b6f4f3af487a6f677ccd5", size = 20949, upload-time = "2025-11-14T09:50:01.608Z" }, ] @@ -2956,7 +2609,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/52/7b/d625c0937557f7e2e64200fdbeb867d2f6f86b2f148b8d6bfe085e32d872/pyobjc_framework_gamekit-12.1.tar.gz", hash = "sha256:014d032c3484093f1409f8f631ba8a0fd2ff7a3ae23fd9d14235340889854c16", size = 63833, upload-time = "2025-11-14T10:15:42.842Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/06/47/d3b78cf57bc2d62dc1408aaad226b776d167832063bbaa0c7cc7a9a6fa12/pyobjc_framework_gamekit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:eb263e90a6af3d7294bc1b1ea5907f8e33bb77d62fb806696f8df7e14806ccad", size = 22463, upload-time = "2025-11-14T09:50:16.444Z" }, { url = "https://files.pythonhosted.org/packages/c4/05/1c49e1030dc9f2812fa8049442158be76c32f271075f4571f94e4389ea86/pyobjc_framework_gamekit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2eee796d5781157f2c5684f7ef4c2a7ace9d9b408a26a9e7e92e8adf5a3f63d7", size = 22493, upload-time = "2025-11-14T09:50:19.129Z" }, ] @@ -2971,7 +2623,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/e2/11/c310bbc2526f95cce662cc1f1359bb11e2458eab0689737b4850d0f6acb7/pyobjc_framework_gameplaykit-12.1.tar.gz", hash = "sha256:935ebd806d802888969357946245d35a304c530c86f1ffe584e2cf21f0a608a8", size = 41511, upload-time = "2025-11-14T10:15:46.529Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/84/7a4a2c358770f5ffdb6bdabb74dcefdfa248b17c250a7c0f9d16d3b8d987/pyobjc_framework_gameplaykit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b2fb27f9f48c3279937e938a0456a5231b5c89e53e3199b9d54009a0bbd1228a", size = 13125, upload-time = "2025-11-14T09:50:34.384Z" }, { url = "https://files.pythonhosted.org/packages/35/1f/e5fe404f92ec0f9c8c37b00d6cb3ba96ee396c7f91b0a41a39b64bfc2743/pyobjc_framework_gameplaykit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:309b0d7479f702830c9be92dbe5855ac2557a9d23f05f063caf9d9fdb85ff5f0", size = 13150, upload-time = "2025-11-14T09:50:36.884Z" }, ] @@ -2998,7 +2649,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/af/67/436630d00ba1028ea33cc9df2fc28e081481433e5075600f2ea1ff00f45e/pyobjc_framework_healthkit-12.1.tar.gz", hash = "sha256:29c5e5de54b41080b7a4b0207698ac6f600dcb9149becc9c6b3a69957e200e5c", size = 91802, upload-time = "2025-11-14T10:15:54.661Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/37/b23d3c04ee37cbb94ff92caedc3669cd259be0344fcf6bdf1ff75ff0a078/pyobjc_framework_healthkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e67bce41f8f63c11000394c6ce1dc694655d9ff0458771340d2c782f9eafcc6e", size = 20785, upload-time = "2025-11-14T09:50:52.152Z" }, { url = "https://files.pythonhosted.org/packages/65/87/bb1c438de51c4fa733a99ce4d3301e585f14d7efd94031a97707c0be2b46/pyobjc_framework_healthkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:15b6fc958ff5de42888b18dffdec839cb36d2dd8b82076ed2f21a51db5271109", size = 20799, upload-time = "2025-11-14T09:50:54.531Z" }, ] @@ -3012,7 +2662,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/6d/a1/39347381fc7d3cd5ab942d86af347b25c73f0ddf6f5227d8b4d8f5328016/pyobjc_framework_imagecapturecore-12.1.tar.gz", hash = "sha256:c4776c59f4db57727389d17e1ffd9c567b854b8db52198b3ccc11281711074e5", size = 46397, upload-time = "2025-11-14T10:15:58.541Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b4/6b/b34d5c9041e90b8a82d87025a1854b60a8ec2d88d9ef9e715f3a40109ed5/pyobjc_framework_imagecapturecore-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:64d1eb677fe5b658a6b6ed734b7120998ea738ca038ec18c4f9c776e90bd9402", size = 15983, upload-time = "2025-11-14T09:51:09.978Z" }, { url = "https://files.pythonhosted.org/packages/50/13/632957b284dec3743d73fb30dbdf03793b3cf1b4c62e61e6484d870f3879/pyobjc_framework_imagecapturecore-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a2777e17ff71fb5a327a897e48c5c7b5a561723a80f990d26e6ed5a1b8748816", size = 16012, upload-time = "2025-11-14T09:51:12.058Z" }, ] @@ -3026,7 +2675,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/5d/b8/d33dd8b7306029bbbd80525bf833fc547e6a223c494bf69a534487283a28/pyobjc_framework_inputmethodkit-12.1.tar.gz", hash = "sha256:f63b6fe2fa7f1412eae63baea1e120e7865e3b68ccfb7d8b0a4aadb309f2b278", size = 23054, upload-time = "2025-11-14T10:16:01.464Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/04/1315f84dba5704a4976ea0185f877f0f33f28781473a817010cee209a8f0/pyobjc_framework_inputmethodkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c4e02f49816799a31d558866492048d69e8086178770b76f4c511295610e02ab", size = 9502, upload-time = "2025-11-14T09:51:24.708Z" }, { url = "https://files.pythonhosted.org/packages/01/c2/59bea66405784b25f5d4e821467ba534a0b92dfc98e07257c971e2a8ed73/pyobjc_framework_inputmethodkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0b7d813d46a060572fc0c14ef832e4fe538ebf64e5cab80ee955191792ce0110", size = 9506, upload-time = "2025-11-14T09:51:26.924Z" }, ] @@ -3067,7 +2715,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/f1/a1/3bab6139e94b97eca098e1562f5d6840e3ff10ea1f7fd704a17111a97d5b/pyobjc_framework_intents-12.1.tar.gz", hash = "sha256:bd688c3ab34a18412f56e459e9dae29e1f4152d3c2048fcacdef5fc49dfb9765", size = 132262, upload-time = "2025-11-14T10:16:16.428Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/25/648db47b9c3879fa50c65ab7cc5fbe0dd400cc97141ac2658ef2e196c0b6/pyobjc_framework_intents-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:dc68dc49f1f8d9f8d2ffbc0f57ad25caac35312ddc444899707461e596024fec", size = 32134, upload-time = "2025-11-14T09:51:46.369Z" }, { url = "https://files.pythonhosted.org/packages/7a/90/e9489492ae90b4c1ffd02c1221c0432b8768d475787e7887f79032c2487a/pyobjc_framework_intents-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0ea9f3e79bf4baf6c7b0fd2d2797184ed51a372bf7f32974b4424f9bd067ef50", size = 32156, upload-time = "2025-11-14T09:51:49.438Z" }, ] @@ -3081,7 +2728,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/19/cf/f0e385b9cfbf153d68efe8d19e5ae672b59acbbfc1f9b58faaefc5ec8c9e/pyobjc_framework_intentsui-12.1.tar.gz", hash = "sha256:16bdf4b7b91c0d1ec9d5513a1182861f1b5b7af95d4f4218ff7cf03032d57f99", size = 19784, upload-time = "2025-11-14T10:16:18.716Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/84/cc/7678f901cbf5bca8ccace568ae85ee7baddcd93d78754ac43a3bb5e5a7ac/pyobjc_framework_intentsui-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a877555e313d74ac3b10f7b4e738647eea9f744c00a227d1238935ac3f9d7968", size = 8961, upload-time = "2025-11-14T09:52:05.595Z" }, { url = "https://files.pythonhosted.org/packages/f1/17/06812542a9028f5b2dcce56f52f25633c08b638faacd43bad862aad1b41d/pyobjc_framework_intentsui-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:cb894fcc4c9ea613a424dcf6fb48142d51174559b82cfdafac8cb47555c842cf", size = 8983, upload-time = "2025-11-14T09:52:07.667Z" }, ] @@ -3095,7 +2741,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/e4/aa/ca3944bbdfead4201b4ae6b51510942c5a7d8e5e2dc3139a071c74061fdf/pyobjc_framework_iobluetooth-12.1.tar.gz", hash = "sha256:8a434118812f4c01dfc64339d41fe8229516864a59d2803e9094ee4cbe2b7edd", size = 155241, upload-time = "2025-11-14T10:16:28.896Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f6/ab/ad6b36f574c3d52b5e935b1d57ab0f14f4e4cd328cc922d2b6ba6428c12d/pyobjc_framework_iobluetooth-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:77959f2ecf379aa41eb0848fdb25da7c322f9f4a82429965c87c4bc147137953", size = 40415, upload-time = "2025-11-14T09:52:22.069Z" }, { url = "https://files.pythonhosted.org/packages/0b/b6/933b56afb5e84c3c35c074c9e30d7b701c6038989d4867867bdaa7ab618b/pyobjc_framework_iobluetooth-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:111a6e54be9e9dcf77fa2bf84fdac09fae339aa33087d8647ea7ffbd34765d4c", size = 40439, upload-time = "2025-11-14T09:52:26.071Z" }, ] @@ -3187,7 +2832,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/26/e8/75b6b9b3c88b37723c237e5a7600384ea2d84874548671139db02e76652b/pyobjc_framework_libdispatch-12.1.tar.gz", hash = "sha256:4035535b4fae1b5e976f3e0e38b6e3442ffea1b8aa178d0ca89faa9b8ecdea41", size = 38277, upload-time = "2025-11-14T10:16:46.235Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1f/75/c4aeab6ce7268373d4ceabbc5c406c4bbf557038649784384910932985f8/pyobjc_framework_libdispatch-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:954cc2d817b71383bd267cc5cd27d83536c5f879539122353ca59f1c945ac706", size = 20463, upload-time = "2025-11-14T09:52:55.703Z" }, { url = "https://files.pythonhosted.org/packages/83/6f/96e15c7b2f7b51fc53252216cd0bed0c3541bc0f0aeb32756fefd31bed7d/pyobjc_framework_libdispatch-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0e9570d7a9a3136f54b0b834683bf3f206acd5df0e421c30f8fd4f8b9b556789", size = 15650, upload-time = "2025-11-14T09:52:59.284Z" }, ] @@ -3201,7 +2845,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/16/e4/364db7dc26f235e3d7eaab2f92057f460b39800bffdec3128f113388ac9f/pyobjc_framework_libxpc-12.1.tar.gz", hash = "sha256:e46363a735f3ecc9a2f91637750623f90ee74f9938a4e7c833e01233174af44d", size = 35186, upload-time = "2025-11-14T10:16:49.503Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7c/c9/701630d025407497b7af50a795ddb6202c184da7f12b46aa683dae3d3552/pyobjc_framework_libxpc-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8d7201db995e5dcd38775fd103641d8fb69b8577d8e6a405c5562e6c0bb72fd1", size = 19620, upload-time = "2025-11-14T09:53:12.529Z" }, { url = "https://files.pythonhosted.org/packages/82/7f/fdec72430f90921b154517a6f9bbeefa7bacfb16b91320742eb16a5955c5/pyobjc_framework_libxpc-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ba93e91e9ca79603dd265382e9f80e9bd32309cd09c8ac3e6489fc5b233676c8", size = 19730, upload-time = "2025-11-14T09:53:17.113Z" }, ] @@ -3230,7 +2873,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/8d/0e/7e5d9a58bb3d5b79a75d925557ef68084171526191b1c0929a887a553d4f/pyobjc_framework_localauthentication-12.1.tar.gz", hash = "sha256:2284f587d8e1206166e4495b33f420c1de486c36c28c4921d09eec858a699d05", size = 29947, upload-time = "2025-11-14T10:16:54.923Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6e/cb/cf9d13943e13dc868a68844448a7714c16f4ee6ecac384d21aaa5ac43796/pyobjc_framework_localauthentication-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2d7e1b3f987dc387361517525c2c38550dc44dfb3ba42dec3a9fbf35015831a6", size = 10762, upload-time = "2025-11-14T09:53:32.035Z" }, { url = "https://files.pythonhosted.org/packages/05/93/91761ad4e5fa1c3ec25819865d1ccfbee033987147087bff4fcce67a4dc4/pyobjc_framework_localauthentication-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3af1acd287d830cc7f912f46cde0dab054952bde0adaf66c8e8524311a68d279", size = 10773, upload-time = "2025-11-14T09:53:34.074Z" }, ] @@ -3273,7 +2915,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/36/bb/2a668203c20e509a648c35e803d79d0c7f7816dacba74eb5ad8acb186790/pyobjc_framework_mapkit-12.1.tar.gz", hash = "sha256:dbc32dc48e821aaa9b4294402c240adbc1c6834e658a07677b7c19b7990533c5", size = 63520, upload-time = "2025-11-14T10:17:04.221Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/8f/411067e5c5cd23b9fe4c5edfb02ed94417b94eefe56562d36e244edc70ff/pyobjc_framework_mapkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e8aa82d4aae81765c05dcd53fd362af615aa04159fc7a1df1d0eac9c252cb7d5", size = 22493, upload-time = "2025-11-14T09:53:50.112Z" }, { url = "https://files.pythonhosted.org/packages/11/00/a3de41cdf3e6cd7a144e38999fe1ea9777ad19e19d863f2da862e7affe7b/pyobjc_framework_mapkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:84ad7766271c114bdc423e4e2ff5433e5fc6771a3338b5f8e4b54d0340775800", size = 22518, upload-time = "2025-11-14T09:53:52.727Z" }, ] @@ -3302,7 +2943,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/d6/aa/1e8015711df1cdb5e4a0aa0ed4721409d39971ae6e1e71915e3ab72423a3/pyobjc_framework_mediaextension-12.1.tar.gz", hash = "sha256:44409d63cc7d74e5724a68e3f9252cb62fd0fd3ccf0ca94c6a33e5c990149953", size = 39425, upload-time = "2025-11-14T10:17:11.486Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8e/6f/60b63edf5d27acf450e4937b7193c1a2bd6195fee18e15df6a5734dedb71/pyobjc_framework_mediaextension-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9555f937f2508bd2b6264cba088e2c2e516b2f94a6c804aee40e33fd89c2fb78", size = 38957, upload-time = "2025-11-14T09:54:13.22Z" }, { url = "https://files.pythonhosted.org/packages/2b/ed/99038bcf72ec68e452709af10a087c1377c2d595ba4e66d7a2b0775145d2/pyobjc_framework_mediaextension-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:442bc3a759efb5c154cb75d643a5e182297093533fcdd1c24be6f64f68b93371", size = 38973, upload-time = "2025-11-14T09:54:16.701Z" }, ] @@ -3343,7 +2983,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/a3/71/be5879380a161f98212a336b432256f307d1dcbaaaeb8ec988aea2ada2cd/pyobjc_framework_mediatoolbox-12.1.tar.gz", hash = "sha256:385b48746a5f08756ee87afc14037e552954c427ed5745d7ece31a21a7bad5ab", size = 22305, upload-time = "2025-11-14T10:17:22.501Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8f/7a/f20ebd3c590b2cc85cde3e608e49309bfccf9312e4aca7b7ea60908d36d7/pyobjc_framework_mediatoolbox-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:74de0cb2d5aaa77e81f8b97eab0f39cd2fab5bf6fa7c6fb5546740cbfb1f8c1f", size = 12656, upload-time = "2025-11-14T09:54:39.215Z" }, { url = "https://files.pythonhosted.org/packages/9c/94/d5ee221f2afbc64b2a7074efe25387cd8700e8116518904b28091ea6ad74/pyobjc_framework_mediatoolbox-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d7bcfeeff3fbf7e9e556ecafd8eaed2411df15c52baf134efa7480494e6faf6d", size = 12818, upload-time = "2025-11-14T09:54:41.251Z" }, ] @@ -3357,7 +2996,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/e7/06/a84f7eb8561d5631954b9458cfca04b690b80b5b85ce70642bc89335f52a/pyobjc_framework_metal-12.1.tar.gz", hash = "sha256:bb554877d5ee2bf3f340ad88e8fe1b85baab7b5ec4bd6ae0f4f7604147e3eae7", size = 181847, upload-time = "2025-11-14T10:17:34.157Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1d/cf/edbb8b6dd084df3d235b74dbeb1fc5daf4d063ee79d13fa3bc1cb1779177/pyobjc_framework_metal-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:59e10f9b36d2e409f80f42b6175457a07b18a21ca57ff268f4bc519cd30db202", size = 75920, upload-time = "2025-11-14T09:55:01.048Z" }, { url = "https://files.pythonhosted.org/packages/d0/48/9286d06e1b14c11b65d3fea1555edc0061d9ebe11898dff8a14089e3a4c9/pyobjc_framework_metal-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:38ab566b5a2979a43e13593d3eb12000a45e574576fe76996a5e1eb75ad7ac78", size = 75841, upload-time = "2025-11-14T09:55:06.801Z" }, ] @@ -3371,7 +3009,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/f1/09/ce5c74565677fde66de3b9d35389066b19e5d1bfef9d9a4ad80f0c858c0c/pyobjc_framework_metalfx-12.1.tar.gz", hash = "sha256:1551b686fb80083a97879ce0331bdb1d4c9b94557570b7ecc35ebf40ff65c90b", size = 29470, upload-time = "2025-11-14T10:17:37.16Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/e5/5494639c927085bbba1a310e73662e0bda44b90cdff67fa03a4e1c24d4c4/pyobjc_framework_metalfx-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1ec3f7ab036eae45e067fbf209676f98075892aa307d73bb9394304960746cd2", size = 15026, upload-time = "2025-11-14T09:55:35.239Z" }, { url = "https://files.pythonhosted.org/packages/2a/0b/508e3af499694f4eec74cc3ab0530e38db76e43a27db9ecb98c50c68f5f9/pyobjc_framework_metalfx-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a4418ae5c2eb77ec00695fa720a547638dc252dfd77ecb6feb88f713f5a948fd", size = 15062, upload-time = "2025-11-14T09:55:37.352Z" }, ] @@ -3386,7 +3023,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/14/15/5091147aae12d4011a788b93971c3376aaaf9bf32aa935a2c9a06a71e18b/pyobjc_framework_metalkit-12.1.tar.gz", hash = "sha256:14cc5c256f0e3471b412a5b3582cb2a0d36d3d57401a8aa09e433252d1c34824", size = 25473, upload-time = "2025-11-14T10:17:39.721Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/10/c5/f72cbc3a5e83211cbfa33b60611abcebbe893854d0f2b28ff6f444f97549/pyobjc_framework_metalkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:28636454f222d9b20cb61f6e8dc1ebd48237903feb4d0dbdf9d7904c542475e5", size = 8735, upload-time = "2025-11-14T09:55:50.053Z" }, { url = "https://files.pythonhosted.org/packages/bf/c0/c8b5b060895cd51493afe3f09909b7e34893b1161cf4d93bc8e3cd306129/pyobjc_framework_metalkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1c4869076571d94788fe539fabfdd568a5c8e340936c7726d2551196640bd152", size = 8755, upload-time = "2025-11-14T09:55:51.683Z" }, ] @@ -3400,7 +3036,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/c5/68/58da38e54aa0d8c19f0d3084d8c84e92d54cc8c9254041f07119d86aa073/pyobjc_framework_metalperformanceshaders-12.1.tar.gz", hash = "sha256:b198e755b95a1de1525e63c3b14327ae93ef1d88359e6be1ce554a3493755b50", size = 137301, upload-time = "2025-11-14T10:17:49.554Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/00/0f/6dc06a08599a3bc211852a5e6dcb4ed65dfbf1066958feb367ba7702798a/pyobjc_framework_metalperformanceshaders-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e0159a6f731dc0fd126481a26490683586864e9d47c678900049a8ffe0135f56", size = 32988, upload-time = "2025-11-14T09:56:05.323Z" }, { url = "https://files.pythonhosted.org/packages/62/84/d505496fca9341e0cb11258ace7640cd986fe3e831f8b4749035e9f82109/pyobjc_framework_metalperformanceshaders-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c00e786c352b3ff5d86cf0cf3a830dc9f6fc32a03ae1a7539d20d11324adb2e8", size = 33242, upload-time = "2025-11-14T09:56:09.354Z" }, ] @@ -3427,7 +3062,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/ba/13/5576ddfbc0b174810a49171e2dbe610bdafd3b701011c6ecd9b3a461de8a/pyobjc_framework_metrickit-12.1.tar.gz", hash = "sha256:77841daf6b36ba0c19df88545fd910c0516acf279e6b7b4fa0a712a046eaa9f1", size = 27627, upload-time = "2025-11-14T10:17:56.353Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5e/b0/e57c60af3e9214e05309dca201abb82e10e8cf91952d90d572b641d62027/pyobjc_framework_metrickit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:da6650afd9523cf7a9cae177f4bbd1ad45cc122d97784785fa1482847485142c", size = 8102, upload-time = "2025-11-14T09:56:27.194Z" }, { url = "https://files.pythonhosted.org/packages/b7/04/8da5126e47306438c99750f1dfed430d7cc388f6b7f420ae748f3060ab96/pyobjc_framework_metrickit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3ec96e9ec7dc37fbce57dae277f0d89c66ffe1c3fa2feaca1b7125f8b2b29d87", size = 8120, upload-time = "2025-11-14T09:56:28.73Z" }, ] @@ -3455,7 +3089,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/b4/11/32c358111b623b4a0af9e90470b198fffc068b45acac74e1ba711aee7199/pyobjc_framework_modelio-12.1.tar.gz", hash = "sha256:d041d7bca7c2a4526344d3e593347225b7a2e51a499b3aa548895ba516d1bdbb", size = 66482, upload-time = "2025-11-14T10:18:04.92Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/35/c0/c67b806f3f2bb6264a4f7778a2aa82c7b0f50dfac40f6a60366ffc5afaf5/pyobjc_framework_modelio-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1c2c99d47a7e4956a75ce19bddbe2d8ada7d7ce9e2f56ff53fc2898367187749", size = 20180, upload-time = "2025-11-14T09:56:41.924Z" }, { url = "https://files.pythonhosted.org/packages/f6/0e/b8331100f0d658ecb3e87e75c108e2ae8ac7c78b521fd5ad0205b60a2584/pyobjc_framework_modelio-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:68d971917c289fdddf69094c74915d2ccb746b42b150e0bdc16d8161e6164022", size = 20193, upload-time = "2025-11-14T09:56:44.296Z" }, ] @@ -3469,7 +3102,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/87/35/0d0bb6881004cb238cfd7bf74f4b2e42601a1accdf27b2189ec61cf3a2dc/pyobjc_framework_multipeerconnectivity-12.1.tar.gz", hash = "sha256:7123f734b7174cacbe92a51a62b4645cc9033f6b462ff945b504b62e1b9e6c1c", size = 22816, upload-time = "2025-11-14T10:18:07.363Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/12/eb/e3e4ba158167696498f6491f91a8ac7e24f1ebbab5042cd34318e5d2035c/pyobjc_framework_multipeerconnectivity-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7372e505ed050286aeb83d7e158fda65ad379eae12e1526f32da0a260a8b7d06", size = 11981, upload-time = "2025-11-14T09:56:58.858Z" }, { url = "https://files.pythonhosted.org/packages/33/8d/0646ff7db36942829f0e84be18ba44bc5cd96d6a81651f8e7dc0974821c1/pyobjc_framework_multipeerconnectivity-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1c3bd254a16debed321debf4858f9c9b7d41572ddf1058a4bacf6a5bcfedeeff", size = 12001, upload-time = "2025-11-14T09:57:01.027Z" }, ] @@ -3509,7 +3141,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/38/13/a71270a1b0a9ec979e68b8ec84b0f960e908b17b51cb3cac246a74d52b6b/pyobjc_framework_network-12.1.tar.gz", hash = "sha256:dbf736ff84d1caa41224e86ff84d34b4e9eb6918ae4e373a44d3cb597648a16a", size = 56990, upload-time = "2025-11-14T10:18:16.714Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e3/7c/4f9fc6b94be3e949b7579128cbb9171943e27d1d7841db12d66b76aeadc3/pyobjc_framework_network-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d1ad948b9b977f432bf05363381d7d85a7021246ebf9d50771b35bf8d4548d2b", size = 19593, upload-time = "2025-11-14T09:57:17.027Z" }, { url = "https://files.pythonhosted.org/packages/9d/ef/a53f04f43e93932817f2ea71689dcc8afe3b908d631c21d11ec30c7b2e87/pyobjc_framework_network-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5e53aad64eae2933fe12d49185d66aca62fb817abf8a46f86b01e436ce1b79e4", size = 19613, upload-time = "2025-11-14T09:57:19.571Z" }, ] @@ -3523,7 +3154,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/bf/3e/ac51dbb2efa16903e6af01f3c1f5a854c558661a7a5375c3e8767ac668e8/pyobjc_framework_networkextension-12.1.tar.gz", hash = "sha256:36abc339a7f214ab6a05cb2384a9df912f247163710741e118662bd049acfa2e", size = 62796, upload-time = "2025-11-14T10:18:21.769Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6e/4e/aa34fc983f001cdb1afbbb4d08b42fd019fc9816caca0bf0b166db1688c1/pyobjc_framework_networkextension-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c3082c29f94ca3a05cd1f3219999ca3af9b6dece1302ccf789f347e612bb9303", size = 14368, upload-time = "2025-11-14T09:57:33.748Z" }, { url = "https://files.pythonhosted.org/packages/f6/14/4934b10ade5ad0518001bfc25260d926816b9c7d08d85ef45e8a61fdef1b/pyobjc_framework_networkextension-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:adc9baacfc532944d67018e381c7645f66a9fa0064939a5a841476d81422cdcc", size = 14376, upload-time = "2025-11-14T09:57:36.132Z" }, ] @@ -3537,7 +3167,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/c6/12/ae0fe82fb1e02365c9fe9531c9de46322f7af09e3659882212c6bf24d75e/pyobjc_framework_notificationcenter-12.1.tar.gz", hash = "sha256:2d09f5ab9dc39770bae4fa0c7cfe961e6c440c8fc465191d403633dccc941094", size = 21282, upload-time = "2025-11-14T10:18:24.51Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/47/aa/03526fc0cc285c0f8cf31c74ce3a7a464011cc8fa82a35a1637d9878c788/pyobjc_framework_notificationcenter-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:84e254f2a56ff5372793dea938a2b2683dd0bc40c5107fede76f9c2c1f6641a2", size = 9871, upload-time = "2025-11-14T09:57:49.208Z" }, { url = "https://files.pythonhosted.org/packages/d8/05/3168637dd425257df5693c2ceafecf92d2e6833c0aaa6594d894a528d797/pyobjc_framework_notificationcenter-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:82a735bd63f315f0a56abd206373917b7d09a0ae35fd99f1639a0fac4c525c0a", size = 9895, upload-time = "2025-11-14T09:57:51.151Z" }, ] @@ -3579,7 +3208,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/12/42/805c9b4ac6ad25deb4215989d8fc41533d01e07ffd23f31b65620bade546/pyobjc_framework_oslog-12.1.tar.gz", hash = "sha256:d0ec6f4e3d1689d5e4341bc1130c6f24cb4ad619939f6c14d11a7e80c0ac4553", size = 21193, upload-time = "2025-11-14T10:18:33.645Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d9/d5/8d37c2e733bd8a9a16546ceca07809d14401a059f8433cdc13579cd6a41a/pyobjc_framework_oslog-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8dd03386331fbb6b39df8941d99071da0bfeda7d10f6434d1daa1c69f0e7bb14", size = 7802, upload-time = "2025-11-14T09:58:05.619Z" }, { url = "https://files.pythonhosted.org/packages/ee/60/0b742347d484068e9d6867cd95dedd1810c790b6aca45f6ef1d0f089f1f5/pyobjc_framework_oslog-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:072a41d36fcf780a070f13ac2569f8bafbb5ae4792fab4136b1a4d602dd9f5b4", size = 7813, upload-time = "2025-11-14T09:58:07.768Z" }, ] @@ -3593,7 +3221,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/6c/d4/2afb59fb0f99eb2f03888850887e536f1ef64b303fd756283679471a5189/pyobjc_framework_passkit-12.1.tar.gz", hash = "sha256:d8c27c352e86a3549bf696504e6b25af5f2134b173d9dd60d66c6d3da53bb078", size = 53835, upload-time = "2025-11-14T10:18:37.906Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/25/e6/dabd6b99bdadc50aa0306495d8d0afe4b9b3475c2bafdad182721401a724/pyobjc_framework_passkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cb5c8f0fdc46db6b91c51ee1f41a2b81e9a482c96a0c91c096dcb78a012b740a", size = 14087, upload-time = "2025-11-14T09:58:18.991Z" }, { url = "https://files.pythonhosted.org/packages/d8/dc/9cb27e8b7b00649af5e802815ffa8928bd8a619f2984a1bea7dabd28f741/pyobjc_framework_passkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7e95a484ec529dbf1d44f5f7f1406502a77bda733511e117856e3dca9fa29c5c", size = 14102, upload-time = "2025-11-14T09:58:20.903Z" }, ] @@ -3633,7 +3260,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/b8/53/f8a3dc7f711034d2283e289cd966fb7486028ea132a24260290ff32d3525/pyobjc_framework_photos-12.1.tar.gz", hash = "sha256:adb68aaa29e186832d3c36a0b60b0592a834e24c5263e9d78c956b2b77dce563", size = 47034, upload-time = "2025-11-14T10:18:47.27Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e4/e0/8824f7cb167934a8aa1c088b7e6f1b5a9342b14694e76eda95fc736282b2/pyobjc_framework_photos-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f28db92602daac9d760067449fc9bf940594536e65ad542aec47d52b56f51959", size = 12319, upload-time = "2025-11-14T09:58:36.324Z" }, { url = "https://files.pythonhosted.org/packages/13/38/e6f25aec46a1a9d0a310795606cc43f9823d41c3e152114b814b597835a8/pyobjc_framework_photos-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eda8a584a851506a1ebbb2ee8de2cb1ed9e3431e6a642ef6a9543e32117d17b9", size = 12358, upload-time = "2025-11-14T09:58:38.131Z" }, ] @@ -3647,7 +3273,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/40/a5/14c538828ed1a420e047388aedc4a2d7d9292030d81bf6b1ced2ec27b6e9/pyobjc_framework_photosui-12.1.tar.gz", hash = "sha256:9141234bb9d17687f1e8b66303158eccdd45132341fbe5e892174910035f029a", size = 29886, upload-time = "2025-11-14T10:18:50.238Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/64/6c/d678767bbeafa932b91c88bc8bb3a586a1b404b5564b0dc791702eb376c3/pyobjc_framework_photosui-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:02ca941187b2a2dcbbd4964d7b2a05de869653ed8484dc059a51cc70f520cd07", size = 11688, upload-time = "2025-11-14T09:58:51.84Z" }, { url = "https://files.pythonhosted.org/packages/16/a2/b5afca8039b1a659a2a979bb1bdbdddfdf9b1d2724a2cc4633dca2573d5f/pyobjc_framework_photosui-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:713e3bb25feb5ea891e67260c2c0769cab44a7f11b252023bfcf9f8c29dd1206", size = 11714, upload-time = "2025-11-14T09:58:53.674Z" }, ] @@ -3674,7 +3299,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/a9/45/de756b62709add6d0615f86e48291ee2bee40223e7dde7bbe68a952593f0/pyobjc_framework_pushkit-12.1.tar.gz", hash = "sha256:829a2fc8f4780e75fc2a41217290ee0ff92d4ade43c42def4d7e5af436d8ae82", size = 19465, upload-time = "2025-11-14T10:18:57.727Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a1/b2/d92045e0d4399feda83ee56a9fd685b5c5c175f7ac8423e2cd9b3d52a9da/pyobjc_framework_pushkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:03f41be8b27d06302ea487a6b250aaf811917a0e7d648cd4043fac759d027210", size = 8158, upload-time = "2025-11-14T09:59:09.593Z" }, { url = "https://files.pythonhosted.org/packages/b9/01/74cf1dd0764c590de05dc1e87d168031e424f834721940b7bb02c67fe821/pyobjc_framework_pushkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7bdf472a55ac65154e03f54ae0bcad64c4cf45e9b1acba62f15107f2bc994d69", size = 8177, upload-time = "2025-11-14T09:59:11.155Z" }, ] @@ -3688,7 +3312,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/94/18/cc59f3d4355c9456fc945eae7fe8797003c4da99212dd531ad1b0de8a0c6/pyobjc_framework_quartz-12.1.tar.gz", hash = "sha256:27f782f3513ac88ec9b6c82d9767eef95a5cf4175ce88a1e5a65875fee799608", size = 3159099, upload-time = "2025-11-14T10:21:24.31Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/ef/dcd22b743e38b3c430fce4788176c2c5afa8bfb01085b8143b02d1e75201/pyobjc_framework_quartz-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:19f99ac49a0b15dd892e155644fe80242d741411a9ed9c119b18b7466048625a", size = 217795, upload-time = "2025-11-14T09:59:46.922Z" }, { url = "https://files.pythonhosted.org/packages/e9/9b/780f057e5962f690f23fdff1083a4cfda5a96d5b4d3bb49505cac4f624f2/pyobjc_framework_quartz-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7730cdce46c7e985535b5a42c31381af4aa6556e5642dc55b5e6597595e57a16", size = 218798, upload-time = "2025-11-14T10:00:01.236Z" }, ] @@ -3716,7 +3339,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/35/f8/b92af879734d91c1726227e7a03b9e68ab8d9d2bb1716d1a5c29254087f2/pyobjc_framework_replaykit-12.1.tar.gz", hash = "sha256:95801fd35c329d7302b2541f2754e6574bf36547ab869fbbf41e408dfa07268a", size = 23312, upload-time = "2025-11-14T10:21:29.18Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/10/b1/fab264c6a82a78cd050a773c61dec397c5df7e7969eba3c57e17c8964ea3/pyobjc_framework_replaykit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3a2f9da6939d7695fa40de9c560c20948d31b0cc2f892fdd611fc566a6b83606", size = 10090, upload-time = "2025-11-14T10:01:06.321Z" }, { url = "https://files.pythonhosted.org/packages/6b/fc/c68d2111b2655148d88574959d3d8b21d3a003573013301d4d2a7254c1af/pyobjc_framework_replaykit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b0528c2a6188440fdc2017f0924c0a0f15d0a2f6aa295f1d1c2d6b3894c22f1d", size = 10120, upload-time = "2025-11-14T10:01:08.397Z" }, ] @@ -3730,7 +3352,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/3e/4b/8f896bafbdbfa180a5ba1e21a6f5dc63150c09cba69d85f68708e02866ae/pyobjc_framework_safariservices-12.1.tar.gz", hash = "sha256:6a56f71c1e692bca1f48fe7c40e4c5a41e148b4e3c6cfb185fd80a4d4a951897", size = 25165, upload-time = "2025-11-14T10:21:32.041Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f1/bb/da1059bfad021c417e090058c0a155419b735b4891a7eedc03177b376012/pyobjc_framework_safariservices-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ae709cf7a72ac7b95d2f131349f852d5d7a1729a8d760ea3308883f8269a4c37", size = 7281, upload-time = "2025-11-14T10:01:19.294Z" }, { url = "https://files.pythonhosted.org/packages/67/3a/8c525562fd782c88bc44e8c07fc2c073919f98dead08fffd50f280ef1afa/pyobjc_framework_safariservices-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b475abc82504fc1c0801096a639562d6a6d37370193e8e4a406de9199a7cea13", size = 7281, upload-time = "2025-11-14T10:01:21.238Z" }, ] @@ -3745,7 +3366,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/f4/bf/ad6bf60ceb61614c9c9f5758190971e9b90c45b1c7a244e45db64138b6c2/pyobjc_framework_safetykit-12.1.tar.gz", hash = "sha256:0cd4850659fb9b5632fd8ad21f2de6863e8303ff0d51c5cc9c0034aac5db08d8", size = 20086, upload-time = "2025-11-14T10:21:34.212Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/94/68/77f17fba082de7c65176e0d74aacbce5c9c9066d6d6edcde5a537c8c140a/pyobjc_framework_safetykit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6c63bcd5d571bba149e28c49c8db06073e54e073b08589e94b850b39a43e52b0", size = 8539, upload-time = "2025-11-14T10:01:31.201Z" }, { url = "https://files.pythonhosted.org/packages/b7/0c/08a20fb7516405186c0fe7299530edd4aa22c24f73290198312447f26c8c/pyobjc_framework_safetykit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e4977f7069a23252053d1a42b1a053aefc19b85c960a5214b05daf3c037a6f16", size = 8550, upload-time = "2025-11-14T10:01:32.885Z" }, ] @@ -3760,7 +3380,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/94/8c/1f4005cf0cb68f84dd98b93bbc0974ee7851bb33d976791c85e042dc2278/pyobjc_framework_scenekit-12.1.tar.gz", hash = "sha256:1bd5b866f31fd829f26feac52e807ed942254fd248115c7c742cfad41d949426", size = 101212, upload-time = "2025-11-14T10:21:41.265Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/7f/eda261013dc41cc70f3157d1a750712dc29b64fc05be84232006b5cd57e5/pyobjc_framework_scenekit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:01bf1336a7a8bdc96fabde8f3506aa7a7d1905e20a5c46030a57daf0ce2cbd16", size = 33542, upload-time = "2025-11-14T10:01:47.613Z" }, { url = "https://files.pythonhosted.org/packages/d2/f1/4986bd96e0ba0f60bff482a6b135b9d6db65d56578d535751f18f88190f0/pyobjc_framework_scenekit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:40aea10098893f0b06191f1e79d7b25e12e36a9265549d324238bdb25c7e6df0", size = 33597, upload-time = "2025-11-14T10:01:51.297Z" }, ] @@ -3775,7 +3394,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/2d/7f/73458db1361d2cb408f43821a1e3819318a0f81885f833d78d93bdc698e0/pyobjc_framework_screencapturekit-12.1.tar.gz", hash = "sha256:50992c6128b35ab45d9e336f0993ddd112f58b8c8c8f0892a9cb42d61bd1f4c9", size = 32573, upload-time = "2025-11-14T10:21:44.497Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/79/92/fe66408f4bd74f6b6da75977d534a7091efe988301d13da4f009bf54ab71/pyobjc_framework_screencapturekit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ae412d397eedf189e763defe3497fcb8dffa5e0b54f62390cb33bf9b1cfb864a", size = 11473, upload-time = "2025-11-14T10:02:09.177Z" }, { url = "https://files.pythonhosted.org/packages/05/a8/533acdbf26e0a908ff640d3a445481f3c948682ca887be6711b5fcf82682/pyobjc_framework_screencapturekit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:27df138ce2dfa9d4aae5106d4877e9ed694b5a174643c058f1c48678ffc7001a", size = 11504, upload-time = "2025-11-14T10:02:11.36Z" }, ] @@ -3789,7 +3407,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/7d/99/7cfbce880cea61253a44eed594dce66c2b2fbf29e37eaedcd40cffa949e9/pyobjc_framework_screensaver-12.1.tar.gz", hash = "sha256:c4ca111317c5a3883b7eace0a9e7dd72bc6ffaa2ca954bdec918c3ab7c65c96f", size = 22229, upload-time = "2025-11-14T10:21:47.299Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2d/8d/87ca0fa0a9eda3097a0f4f2eef1544bf1d984697939fbef7cda7495fddb9/pyobjc_framework_screensaver-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5bd10809005fbe0d68fe651f32a393ce059e90da38e74b6b3cd055ed5b23eaa9", size = 8480, upload-time = "2025-11-14T10:02:22.798Z" }, { url = "https://files.pythonhosted.org/packages/5a/a4/2481711f2e9557b90bac74fa8bf821162cf7b65835732ae560fd52e9037e/pyobjc_framework_screensaver-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a3c90c2299eac6d01add81427ae2f90d7724f15d676261e838d7a7750f812322", size = 8422, upload-time = "2025-11-14T10:02:24.49Z" }, ] @@ -3816,7 +3433,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/0c/cb/adc0a09e8c4755c2281bd12803a87f36e0832a8fc853a2d663433dbb72ce/pyobjc_framework_scriptingbridge-12.1.tar.gz", hash = "sha256:0e90f866a7e6a8aeaf723d04c826657dd528c8c1b91e7a605f8bb947c74ad082", size = 20339, upload-time = "2025-11-14T10:21:51.769Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/42/de/0943ee8d7f1a7d8467df6e2ea017a6d5041caff2fb0283f37fea4c4ce370/pyobjc_framework_scriptingbridge-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e6e37e69760d6ac9d813decf135d107760d33e1cdf7335016522235607f6f31b", size = 8335, upload-time = "2025-11-14T10:02:36.654Z" }, { url = "https://files.pythonhosted.org/packages/51/46/e0b07d2b3ff9effb8b1179a6cc681a953d3dfbf0eb8b1d6a0e54cef2e922/pyobjc_framework_scriptingbridge-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8083cd68c559c55a3787b2e74fc983c8665e5078571475aaeabf4f34add36b62", size = 8356, upload-time = "2025-11-14T10:02:38.559Z" }, ] @@ -3843,7 +3459,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/80/aa/796e09a3e3d5cee32ebeebb7dcf421b48ea86e28c387924608a05e3f668b/pyobjc_framework_security-12.1.tar.gz", hash = "sha256:7fecb982bd2f7c4354513faf90ba4c53c190b7e88167984c2d0da99741de6da9", size = 168044, upload-time = "2025-11-14T10:22:06.334Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5e/3d/8d3a39cd292d7c76ab76233498189bc7170fc80f573b415308464f68c7ee/pyobjc_framework_security-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1b2d8819f0fb7b619ec7627a0d8c1cac1a57c5143579ce8ac21548165680684b", size = 41287, upload-time = "2025-11-14T10:02:54.491Z" }, { url = "https://files.pythonhosted.org/packages/76/66/5160c0f938fc0515fe8d9af146aac1b093f7ef285ce797fedae161b6c0e8/pyobjc_framework_security-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ab42e55f5b782332be5442750fcd9637ee33247d57c7b1d5801bc0e24ee13278", size = 41280, upload-time = "2025-11-14T10:02:58.097Z" }, ] @@ -3872,7 +3487,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/f9/64/bf5b5d82655112a2314422ee649f1e1e73d4381afa87e1651ce7e8444694/pyobjc_framework_securityinterface-12.1.tar.gz", hash = "sha256:deef11ad03be8d9ff77db6e7ac40f6b641ee2d72eaafcf91040537942472e88b", size = 25552, upload-time = "2025-11-14T10:22:12.098Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/37/1c/a01fd56765792d1614eb5e8dc0a7d5467564be6a2056b417c9ec7efc648f/pyobjc_framework_securityinterface-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ed599be750122376392e95c2407d57bd94644e8320ddef1d67660e16e96b0d06", size = 10719, upload-time = "2025-11-14T10:03:18.353Z" }, { url = "https://files.pythonhosted.org/packages/59/3e/17889a6de03dc813606bb97887dc2c4c2d4e7c8f266bc439548bae756e90/pyobjc_framework_securityinterface-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5cb5e79a73ea17663ebd29e350401162d93e42343da7d96c77efb38ae64ff01f", size = 10783, upload-time = "2025-11-14T10:03:20.202Z" }, ] @@ -3927,7 +3541,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/0f/8b/8ab209a143c11575a857e2111acc5427fb4986b84708b21324cbcbf5591b/pyobjc_framework_sharedwithyou-12.1.tar.gz", hash = "sha256:167d84794a48f408ee51f885210c616fda1ec4bff3dd8617a4b5547f61b05caf", size = 24791, upload-time = "2025-11-14T10:22:21.248Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/19/69/3ad9b344808c5619adc253b665f8677829dfb978888227e07233d120cfab/pyobjc_framework_sharedwithyou-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:359c03096a6988371ea89921806bb81483ea509c9aa7114f9cd20efd511b3576", size = 8739, upload-time = "2025-11-14T10:03:36.48Z" }, { url = "https://files.pythonhosted.org/packages/ec/ee/e5113ce985a480d13a0fa3d41a242c8068dc09b3c13210557cf5cc6a544a/pyobjc_framework_sharedwithyou-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a99a6ebc6b6de7bc8663b1f07332fab9560b984a57ce344dc5703f25258f258d", size = 8763, upload-time = "2025-11-14T10:03:38.467Z" }, ] @@ -3941,7 +3554,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/55/ef/84059c5774fd5435551ab7ab40b51271cfb9997b0d21f491c6b429fe57a8/pyobjc_framework_sharedwithyoucore-12.1.tar.gz", hash = "sha256:0813149eeb755d718b146ec9365eb4ca3262b6af9ff9ba7db2f7b6f4fd104518", size = 22350, upload-time = "2025-11-14T10:22:23.611Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ce/a1/83e58eca8827a1a9975a9c5de7f8c0bdc73b5f53ee79768d1fdbec6747de/pyobjc_framework_sharedwithyoucore-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f4f9f7fed0768ebbbc2d24248365da2cf5f014b8822b2a1fbbce5fa920f410f1", size = 8512, upload-time = "2025-11-14T10:03:49.176Z" }, { url = "https://files.pythonhosted.org/packages/dd/0e/0c2b0591ebc72d437dccca7a1e7164c5f11dde2189d4f4c707a132bab740/pyobjc_framework_sharedwithyoucore-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ed928266ae9d577ff73de72a03bebc66a751918eb59ca660a9eca157392f17be", size = 8530, upload-time = "2025-11-14T10:03:50.839Z" }, ] @@ -3955,7 +3567,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/ed/2c/8d82c5066cc376de68ad8c1454b7c722c7a62215e5c2f9dac5b33a6c3d42/pyobjc_framework_shazamkit-12.1.tar.gz", hash = "sha256:71db2addd016874639a224ed32b2000b858802b0370c595a283cce27f76883fe", size = 22518, upload-time = "2025-11-14T10:22:25.996Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/92/12/09d83a8ac51dc11a574449dea48ffa99b3a7c9baf74afeedb487394d110d/pyobjc_framework_shazamkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0c10ba22de524fbedf06270a71bb0a3dbd4a3853b7002ddf54394589c3be6939", size = 8555, upload-time = "2025-11-14T10:04:02.552Z" }, { url = "https://files.pythonhosted.org/packages/04/5e/7d60d8e7b036b20d0e94cd7c4563e7414653344482e85fbc7facffabc95f/pyobjc_framework_shazamkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e184dd0f61a604b1cfcf44418eb95b943e7b8f536058a29e4b81acadd27a9420", size = 8577, upload-time = "2025-11-14T10:04:04.182Z" }, ] @@ -3995,7 +3606,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/8d/3d/194cf19fe7a56c2be5dfc28f42b3b597a62ebb1e1f52a7dd9c55b917ac6c/pyobjc_framework_speech-12.1.tar.gz", hash = "sha256:2a2a546ba6c52d5dd35ddcfee3fd9226a428043d1719597e8701851a6566afdd", size = 25218, upload-time = "2025-11-14T10:22:32.505Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/03/54/77e12e4c23a98fc49d874f9703c9f8fd0257d64bb0c6ae329b91fc7a99e3/pyobjc_framework_speech-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0301bfae5d0d09b6e69bd4dbabc5631209e291cc40bda223c69ed0c618f8f2dc", size = 9248, upload-time = "2025-11-14T10:04:19.73Z" }, { url = "https://files.pythonhosted.org/packages/f9/1b/224cb98c9c32a6d5e68072f89d26444095be54c6f461efe4fefe9d1330a5/pyobjc_framework_speech-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:cae4b88ef9563157a6c9e66b37778fc4022ee44dd1a2a53081c2adbb69698945", size = 9254, upload-time = "2025-11-14T10:04:21.361Z" }, ] @@ -4010,7 +3620,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/b6/78/d683ebe0afb49f46d2d21d38c870646e7cb3c2e83251f264e79d357b1b74/pyobjc_framework_spritekit-12.1.tar.gz", hash = "sha256:a851f4ef5aa65cc9e08008644a528e83cb31021a1c0f17ebfce4de343764d403", size = 64470, upload-time = "2025-11-14T10:22:37.569Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/60/6a/e8e44fc690d898394093f3a1c5fe90110d1fbcc6e3f486764437c022b0f8/pyobjc_framework_spritekit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:26fd12944684713ae1e3cdd229348609c1142e60802624161ca0c3540eec3ffa", size = 17736, upload-time = "2025-11-14T10:04:33.202Z" }, { url = "https://files.pythonhosted.org/packages/3b/38/97c3b6c3437e3e9267fb4e1cd86e0da4eff07e0abe7cd6923644d2dfc878/pyobjc_framework_spritekit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1649e57c25145795d04bb6a1ec44c20ef7cf0af7c60a9f6f5bc7998dd269db1e", size = 17802, upload-time = "2025-11-14T10:04:35.346Z" }, ] @@ -4024,7 +3633,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/00/87/8a66a145feb026819775d44975c71c1c64df4e5e9ea20338f01456a61208/pyobjc_framework_storekit-12.1.tar.gz", hash = "sha256:818452e67e937a10b5c8451758274faa44ad5d4329df0fa85735115fb0608da9", size = 34574, upload-time = "2025-11-14T10:22:40.73Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d9/41/af2afc4d27bde026cfd3b725ee1b082b2838dcaa9880ab719226957bc7cd/pyobjc_framework_storekit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a29f45bcba9dee4cf73dae05ab0f94d06a32fb052e31414d0c23791c1ec7931c", size = 12810, upload-time = "2025-11-14T10:04:48.693Z" }, { url = "https://files.pythonhosted.org/packages/8a/9f/938985e506de0cc3a543e44e1f9990e9e2fb8980b8f3bcfc8f7921d09061/pyobjc_framework_storekit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9fe2d65a2b644bb6b4fdd3002292cba153560917de3dd6cf969431fa32d21dd0", size = 12819, upload-time = "2025-11-14T10:04:50.945Z" }, ] @@ -4052,7 +3660,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/21/91/6d03a988831ddb0fb001b13573560e9a5bcccde575b99350f98fe56a2dd4/pyobjc_framework_syncservices-12.1.tar.gz", hash = "sha256:6a213e93d9ce15128810987e4c5de8c73cfab1564ac8d273e6b437a49965e976", size = 31032, upload-time = "2025-11-14T10:22:45.902Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4a/9b/25c117f8ffe15aa6cc447da7f5c179627ebafb2b5ec30dfb5e70fede2549/pyobjc_framework_syncservices-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e81a38c2eb7617cb0ecfc4406c1ae2a97c60e95af42e863b2b0f1f6facd9b0da", size = 13380, upload-time = "2025-11-14T10:05:05.814Z" }, { url = "https://files.pythonhosted.org/packages/54/ac/a83cdd120e279ee905e9085afda90992159ed30c6a728b2c56fa2d36b6ea/pyobjc_framework_syncservices-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0cd629bea95692aad2d26196657cde2fbadedae252c7846964228661a600b900", size = 13411, upload-time = "2025-11-14T10:05:07.741Z" }, ] @@ -4066,7 +3673,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/90/7d/50848df8e1c6b5e13967dee9fb91d3391fe1f2399d2d0797d2fc5edb32ba/pyobjc_framework_systemconfiguration-12.1.tar.gz", hash = "sha256:90fe04aa059876a21626931c71eaff742a27c79798a46347fd053d7008ec496e", size = 59158, upload-time = "2025-11-14T10:22:53.056Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1d/7b/9126a7af1b798998837027390a20b981e0298e51c4c55eed6435967145cb/pyobjc_framework_systemconfiguration-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:796390a80500cc7fde86adc71b11cdc41d09507dd69103d3443fbb60e94fb438", size = 21663, upload-time = "2025-11-14T10:05:21.259Z" }, { url = "https://files.pythonhosted.org/packages/d3/d3/bb935c3d4bae9e6ce4a52638e30eea7039c480dd96bc4f0777c9fabda21b/pyobjc_framework_systemconfiguration-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0e5bb9103d39483964431db7125195c59001b7bff2961869cfe157b4c861e52d", size = 21578, upload-time = "2025-11-14T10:05:25.572Z" }, ] @@ -4080,7 +3686,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/12/01/8a706cd3f7dfcb9a5017831f2e6f9e5538298e90052db3bb8163230cbc4f/pyobjc_framework_systemextensions-12.1.tar.gz", hash = "sha256:243e043e2daee4b5c46cd90af5fff46b34596aac25011bab8ba8a37099685eeb", size = 20701, upload-time = "2025-11-14T10:22:58.257Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ac/a1/f8df6d59e06bc4b5989a76724e8551935e5b99aff6a21d3592e5ced91f1c/pyobjc_framework_systemextensions-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2a4e82160e43c0b1aa17e6d4435e840a655737fbe534e00e37fc1961fbf3bebd", size = 9156, upload-time = "2025-11-14T10:05:39.744Z" }, { url = "https://files.pythonhosted.org/packages/0a/cc/a42883d6ad0ae257a7fa62660b4dd13be15f8fa657922f9a5b6697f26e28/pyobjc_framework_systemextensions-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:01fac4f8d88c0956d9fc714d24811cd070e67200ba811904317d91e849e38233", size = 9166, upload-time = "2025-11-14T10:05:41.479Z" }, ] @@ -4120,7 +3725,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/90/cd/e0253072f221fa89a42fe53f1a2650cc9bf415eb94ae455235bd010ee12e/pyobjc_framework_usernotifications-12.1.tar.gz", hash = "sha256:019ccdf2d400f9a428769df7dba4ea97c02453372bc5f8b75ce7ae54dfe130f9", size = 29749, upload-time = "2025-11-14T10:23:05.364Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/96/aa25bb0727e661a352d1c52e7288e25c12fe77047f988bb45557c17cf2d7/pyobjc_framework_usernotifications-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c62e8d7153d72c4379071e34258aa8b7263fa59212cfffd2f137013667e50381", size = 9632, upload-time = "2025-11-14T10:05:55.166Z" }, { url = "https://files.pythonhosted.org/packages/61/ad/c95053a475246464cba686e16269b0973821601910d1947d088b855a8dac/pyobjc_framework_usernotifications-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:412afb2bf5fe0049f9c4e732e81a8a35d5ebf97c30a5a6abd276259d020c82ac", size = 9644, upload-time = "2025-11-14T10:05:56.801Z" }, ] @@ -4163,7 +3767,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/b3/5f/6995ee40dc0d1a3460ee183f696e5254c0ad14a25b5bc5fd9bd7266c077b/pyobjc_framework_videotoolbox-12.1.tar.gz", hash = "sha256:7adc8670f3b94b086aed6e86c3199b388892edab4f02933c2e2d9b1657561bef", size = 57825, upload-time = "2025-11-14T10:23:13.825Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/42/53d57b09fd4879988084ec0d9b74c645c9fdd322be594c9601f6cf265dd0/pyobjc_framework_videotoolbox-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a1eb1eb41c0ffdd8dcc6a9b68ab2b5bc50824a85820c8a7802a94a22dfbb4f91", size = 18781, upload-time = "2025-11-14T10:06:11.89Z" }, { url = "https://files.pythonhosted.org/packages/94/a5/91c6c95416f41c412c2079950527cb746c0712ec319c51a6c728c8d6b231/pyobjc_framework_videotoolbox-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eb6ce6837344ee319122066c16ada4beb913e7bfd62188a8d14b1ecbb5a89234", size = 18908, upload-time = "2025-11-14T10:06:14.087Z" }, ] @@ -4177,7 +3780,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/3b/6a/9d110b5521d9b898fad10928818c9f55d66a4af9ac097426c65a9878b095/pyobjc_framework_virtualization-12.1.tar.gz", hash = "sha256:e96afd8e801e92c6863da0921e40a3b68f724804f888bce43791330658abdb0f", size = 40682, upload-time = "2025-11-14T10:23:17.456Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/ee/e18d0d9014c42758d7169144acb2d37eb5ff19bf959db74b20eac706bd8c/pyobjc_framework_virtualization-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a88a307dc96885afc227ceda4067f1af787f024063f4ccf453d59e7afd47cda8", size = 13099, upload-time = "2025-11-14T10:06:27.403Z" }, { url = "https://files.pythonhosted.org/packages/c6/f2/0da47e91f3f8eeda9a8b4bb0d3a0c54a18925009e99b66a8226b9e06ce1e/pyobjc_framework_virtualization-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7d5724b38e64b39ab5ec3b45993afa29fc88b307d99ee2c7a1c0fd770e9b4b21", size = 13131, upload-time = "2025-11-14T10:06:29.337Z" }, ] @@ -4193,7 +3795,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/c2/5a/08bb3e278f870443d226c141af14205ff41c0274da1e053b72b11dfc9fb2/pyobjc_framework_vision-12.1.tar.gz", hash = "sha256:a30959100e85dcede3a786c544e621ad6eb65ff6abf85721f805822b8c5fe9b0", size = 59538, upload-time = "2025-11-14T10:23:21.979Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/37/e30cf4eef2b4c7e20ccadc1249117c77305fbc38b2e5904eb42e3753f63c/pyobjc_framework_vision-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1edbf2fc18ce3b31108f845901a88f2236783ae6bf0bc68438d7ece572dc2a29", size = 21432, upload-time = "2025-11-14T10:06:42.373Z" }, { url = "https://files.pythonhosted.org/packages/3a/5a/23502935b3fc877d7573e743fc3e6c28748f33a45c43851d503bde52cde7/pyobjc_framework_vision-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:6b3211d84f3a12aad0cde752cfd43a80d0218960ac9e6b46b141c730e7d655bd", size = 16625, upload-time = "2025-11-14T10:06:44.422Z" }, ] @@ -4207,7 +3808,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/14/10/110a50e8e6670765d25190ca7f7bfeecc47ec4a8c018cb928f4f82c56e04/pyobjc_framework_webkit-12.1.tar.gz", hash = "sha256:97a54dd05ab5266bd4f614e41add517ae62cdd5a30328eabb06792474b37d82a", size = 284531, upload-time = "2025-11-14T10:23:40.287Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/37/5082a0bbe12e48d4ffa53b0c0f09c77a4a6ffcfa119e26fa8dd77c08dc1c/pyobjc_framework_webkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3db734877025614eaef4504fadc0fbbe1279f68686a6f106f2e614e89e0d1a9d", size = 49970, upload-time = "2025-11-14T10:07:01.413Z" }, { url = "https://files.pythonhosted.org/packages/db/67/64920c8d201a7fc27962f467c636c4e763b43845baba2e091a50a97a5d52/pyobjc_framework_webkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:af2c7197447638b92aafbe4847c063b6dd5e1ed83b44d3ce7e71e4c9b042ab5a", size = 50084, upload-time = "2025-11-14T10:07:05.868Z" }, ] @@ -4251,9 +3851,6 @@ sdist = { url = "https://files.pythonhosted.org/packages/cb/04/2ba023d5f771b645f name = "pyscreeze" version = "1.0.1" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pillow", marker = "python_full_version < '3.12'" }, -] sdist = { url = "https://files.pythonhosted.org/packages/ee/f0/cb456ac4f1a73723d5b866933b7986f02bacea27516629c00f8e7da94c2d/pyscreeze-1.0.1.tar.gz", hash = "sha256:cf1662710f1b46aa5ff229ee23f367da9e20af4a78e6e365bee973cad0ead4be", size = 27826, upload-time = "2024-08-20T23:03:07.291Z" } [[package]] @@ -4405,9 +4002,6 @@ name = "pywin32" version = "311" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7c/af/449a6a91e5d6db51420875c54f6aff7c97a86a3b13a0b4f1a5c13b988de3/pywin32-311-cp311-cp311-win32.whl", hash = "sha256:184eb5e436dea364dcd3d2316d577d625c0351bf237c4e9a5fabbcfa5a58b151", size = 8697031, upload-time = "2025-07-14T20:13:13.266Z" }, - { url = "https://files.pythonhosted.org/packages/51/8f/9bb81dd5bb77d22243d33c8397f09377056d5c687aa6d4042bea7fbf8364/pywin32-311-cp311-cp311-win_amd64.whl", hash = "sha256:3ce80b34b22b17ccbd937a6e78e7225d80c52f5ab9940fe0506a1a16f3dab503", size = 9508308, upload-time = "2025-07-14T20:13:15.147Z" }, - { url = "https://files.pythonhosted.org/packages/44/7b/9c2ab54f74a138c491aba1b1cd0795ba61f144c711daea84a88b63dc0f6c/pywin32-311-cp311-cp311-win_arm64.whl", hash = "sha256:a733f1388e1a842abb67ffa8e7aad0e70ac519e09b0f6a784e65a136ec7cefd2", size = 8703930, upload-time = "2025-07-14T20:13:16.945Z" }, { url = "https://files.pythonhosted.org/packages/e7/ab/01ea1943d4eba0f850c3c61e78e8dd59757ff815ff3ccd0a84de5f541f42/pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31", size = 8706543, upload-time = "2025-07-14T20:13:20.765Z" }, { url = "https://files.pythonhosted.org/packages/d1/a8/a0e8d07d4d051ec7502cd58b291ec98dcc0c3fff027caad0470b72cfcc2f/pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067", size = 9495040, upload-time = "2025-07-14T20:13:22.543Z" }, { url = "https://files.pythonhosted.org/packages/ba/3a/2ae996277b4b50f17d61f0603efd8253cb2d79cc7ae159468007b586396d/pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852", size = 8710102, upload-time = "2025-07-14T20:13:24.682Z" }, @@ -4451,15 +4045,6 @@ version = "6.0.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, - { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, - { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, - { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, - { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, - { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, - { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, - { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, - { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, @@ -4493,16 +4078,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/04/0b/3c9baedbdf613ecaa7aa07027780b8867f57b6293b6ee50de316c9f3222b/pyzmq-27.1.0.tar.gz", hash = "sha256:ac0765e3d44455adb6ddbf4417dcce460fc40a05978c08efdf2948072f6db540", size = 281750, upload-time = "2025-09-08T23:10:18.157Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/06/5d/305323ba86b284e6fcb0d842d6adaa2999035f70f8c38a9b6d21ad28c3d4/pyzmq-27.1.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:226b091818d461a3bef763805e75685e478ac17e9008f49fce2d3e52b3d58b86", size = 1333328, upload-time = "2025-09-08T23:07:45.946Z" }, - { url = "https://files.pythonhosted.org/packages/bd/a0/fc7e78a23748ad5443ac3275943457e8452da67fda347e05260261108cbc/pyzmq-27.1.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0790a0161c281ca9723f804871b4027f2e8b5a528d357c8952d08cd1a9c15581", size = 908803, upload-time = "2025-09-08T23:07:47.551Z" }, - { url = "https://files.pythonhosted.org/packages/7e/22/37d15eb05f3bdfa4abea6f6d96eb3bb58585fbd3e4e0ded4e743bc650c97/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c895a6f35476b0c3a54e3eb6ccf41bf3018de937016e6e18748317f25d4e925f", size = 668836, upload-time = "2025-09-08T23:07:49.436Z" }, - { url = "https://files.pythonhosted.org/packages/b1/c4/2a6fe5111a01005fc7af3878259ce17684fabb8852815eda6225620f3c59/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bbf8d3630bf96550b3be8e1fc0fea5cbdc8d5466c1192887bd94869da17a63e", size = 857038, upload-time = "2025-09-08T23:07:51.234Z" }, - { url = "https://files.pythonhosted.org/packages/cb/eb/bfdcb41d0db9cd233d6fb22dc131583774135505ada800ebf14dfb0a7c40/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:15c8bd0fe0dabf808e2d7a681398c4e5ded70a551ab47482067a572c054c8e2e", size = 1657531, upload-time = "2025-09-08T23:07:52.795Z" }, - { url = "https://files.pythonhosted.org/packages/ab/21/e3180ca269ed4a0de5c34417dfe71a8ae80421198be83ee619a8a485b0c7/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:bafcb3dd171b4ae9f19ee6380dfc71ce0390fefaf26b504c0e5f628d7c8c54f2", size = 2034786, upload-time = "2025-09-08T23:07:55.047Z" }, - { url = "https://files.pythonhosted.org/packages/3b/b1/5e21d0b517434b7f33588ff76c177c5a167858cc38ef740608898cd329f2/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e829529fcaa09937189178115c49c504e69289abd39967cd8a4c215761373394", size = 1894220, upload-time = "2025-09-08T23:07:57.172Z" }, - { url = "https://files.pythonhosted.org/packages/03/f2/44913a6ff6941905efc24a1acf3d3cb6146b636c546c7406c38c49c403d4/pyzmq-27.1.0-cp311-cp311-win32.whl", hash = "sha256:6df079c47d5902af6db298ec92151db82ecb557af663098b92f2508c398bb54f", size = 567155, upload-time = "2025-09-08T23:07:59.05Z" }, - { url = "https://files.pythonhosted.org/packages/23/6d/d8d92a0eb270a925c9b4dd039c0b4dc10abc2fcbc48331788824ef113935/pyzmq-27.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:190cbf120fbc0fc4957b56866830def56628934a9d112aec0e2507aa6a032b97", size = 633428, upload-time = "2025-09-08T23:08:00.663Z" }, - { url = "https://files.pythonhosted.org/packages/ae/14/01afebc96c5abbbd713ecfc7469cfb1bc801c819a74ed5c9fad9a48801cb/pyzmq-27.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:eca6b47df11a132d1745eb3b5b5e557a7dae2c303277aa0e69c6ba91b8736e07", size = 559497, upload-time = "2025-09-08T23:08:02.15Z" }, { url = "https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:452631b640340c928fa343801b0d07eb0c3789a5ffa843f6e1a9cee0ba4eb4fc", size = 1306279, upload-time = "2025-09-08T23:08:03.807Z" }, { url = "https://files.pythonhosted.org/packages/e8/5e/c3c49fdd0f535ef45eefcc16934648e9e59dace4a37ee88fc53f6cd8e641/pyzmq-27.1.0-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1c179799b118e554b66da67d88ed66cd37a169f1f23b5d9f0a231b4e8d44a113", size = 895645, upload-time = "2025-09-08T23:08:05.301Z" }, { url = "https://files.pythonhosted.org/packages/f8/e5/b0b2504cb4e903a74dcf1ebae157f9e20ebb6ea76095f6cfffea28c42ecd/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3837439b7f99e60312f0c926a6ad437b067356dc2bc2ec96eb395fd0fe804233", size = 652574, upload-time = "2025-09-08T23:08:06.828Z" }, @@ -4513,11 +4088,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e6/2f/104c0a3c778d7c2ab8190e9db4f62f0b6957b53c9d87db77c284b69f33ea/pyzmq-27.1.0-cp312-abi3-win32.whl", hash = "sha256:250e5436a4ba13885494412b3da5d518cd0d3a278a1ae640e113c073a5f88edd", size = 559184, upload-time = "2025-09-08T23:08:15.163Z" }, { url = "https://files.pythonhosted.org/packages/fc/7f/a21b20d577e4100c6a41795842028235998a643b1ad406a6d4163ea8f53e/pyzmq-27.1.0-cp312-abi3-win_amd64.whl", hash = "sha256:9ce490cf1d2ca2ad84733aa1d69ce6855372cb5ce9223802450c9b2a7cba0ccf", size = 619480, upload-time = "2025-09-08T23:08:17.192Z" }, { url = "https://files.pythonhosted.org/packages/78/c2/c012beae5f76b72f007a9e91ee9401cb88c51d0f83c6257a03e785c81cc2/pyzmq-27.1.0-cp312-abi3-win_arm64.whl", hash = "sha256:75a2f36223f0d535a0c919e23615fc85a1e23b71f40c7eb43d7b1dedb4d8f15f", size = 552993, upload-time = "2025-09-08T23:08:18.926Z" }, - { url = "https://files.pythonhosted.org/packages/4c/c6/c4dcdecdbaa70969ee1fdced6d7b8f60cfabe64d25361f27ac4665a70620/pyzmq-27.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:18770c8d3563715387139060d37859c02ce40718d1faf299abddcdcc6a649066", size = 836265, upload-time = "2025-09-08T23:09:49.376Z" }, - { url = "https://files.pythonhosted.org/packages/3e/79/f38c92eeaeb03a2ccc2ba9866f0439593bb08c5e3b714ac1d553e5c96e25/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:ac25465d42f92e990f8d8b0546b01c391ad431c3bf447683fdc40565941d0604", size = 800208, upload-time = "2025-09-08T23:09:51.073Z" }, - { url = "https://files.pythonhosted.org/packages/49/0e/3f0d0d335c6b3abb9b7b723776d0b21fa7f3a6c819a0db6097059aada160/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53b40f8ae006f2734ee7608d59ed661419f087521edbfc2149c3932e9c14808c", size = 567747, upload-time = "2025-09-08T23:09:52.698Z" }, - { url = "https://files.pythonhosted.org/packages/a1/cf/f2b3784d536250ffd4be70e049f3b60981235d70c6e8ce7e3ef21e1adb25/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f605d884e7c8be8fe1aa94e0a783bf3f591b84c24e4bc4f3e7564c82ac25e271", size = 747371, upload-time = "2025-09-08T23:09:54.563Z" }, - { url = "https://files.pythonhosted.org/packages/01/1b/5dbe84eefc86f48473947e2f41711aded97eecef1231f4558f1f02713c12/pyzmq-27.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c9f7f6e13dff2e44a6afeaf2cf54cee5929ad64afaf4d40b50f93c58fc687355", size = 544862, upload-time = "2025-09-08T23:09:56.509Z" }, ] [[package]] @@ -4541,13 +4111,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/7c/4b/858958762c075c54058ee3b0771838fd505ca908871e6a0397b01086e526/raylib-5.5.0.4.tar.gz", hash = "sha256:996506e8a533cd7a6a3ef6c44ec11f9d6936698f2c394a991af8022be33079a0", size = 184413, upload-time = "2025-12-11T15:32:12.465Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/14/98a78b819d7374dab309525ce45cd591d0d62db7f6ed2d5ed32b8f55d62b/raylib-5.5.0.4-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:09717ed32c9ec1c574370e2e2d30e9bc13876f7e2f2dd6e04dc366dae23e0994", size = 1632797, upload-time = "2025-12-11T15:27:15.429Z" }, - { url = "https://files.pythonhosted.org/packages/1e/f4/ec949f45274cf266875b30b67f8cb7243ecced05080cec54bf65ec73a8b2/raylib-5.5.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cef7b0e238eafc80a3be7e3c656a3ddc94cc523790758b7130df1957ba4ad4ad", size = 1550301, upload-time = "2025-12-11T15:27:17.429Z" }, - { url = "https://files.pythonhosted.org/packages/c4/5a/8f60d367147019acef342746f20121b2341ec6596acd5c7941cb36bda02e/raylib-5.5.0.4-cp311-cp311-manylinux2010_i686.manylinux_2_12_i686.whl", hash = "sha256:bdaa119b767f380caf6dd4f9d42ab3bf8596d8fb98737d2951b36924a5a83ac0", size = 2036797, upload-time = "2025-12-11T15:27:20.044Z" }, - { url = "https://files.pythonhosted.org/packages/dd/ad/97dd93c389263c61a3057065f0f70db5fdc3c5768fa383a9b3e989ddb6a7/raylib-5.5.0.4-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:6a5cdeeb803d081342961eb1f7c4161af27e951d9ecf2b56d469d5730fcc6213", size = 2188009, upload-time = "2025-12-11T18:50:05.612Z" }, - { url = "https://files.pythonhosted.org/packages/42/6a/55be04012f3459842389689326910204f985cffcb8989a92475221f5660a/raylib-5.5.0.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4067fa8a6ed3eb78a1162fc2d40ce8c26c26c5ee544019d1902accf21ec22add", size = 2187633, upload-time = "2025-12-11T15:27:22.345Z" }, - { url = "https://files.pythonhosted.org/packages/6b/18/b69d9ad9f4064785ad29c73672d40b36c59c3b3efd1dee264cdff4b48bf6/raylib-5.5.0.4-cp311-cp311-win32.whl", hash = "sha256:f01a769bb0797ab4f6e1efc950d5d8aca53548e97da7f527190a1ca5f671c389", size = 1456775, upload-time = "2025-12-11T15:27:26.776Z" }, - { url = "https://files.pythonhosted.org/packages/1a/7a/4025d9ceeee8e3ae4748b0f6c356c5ce97628bd5da8a056b6782c87f7e65/raylib-5.5.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:34771dea34a30fa4657f35b344d5ebf9eb11d9b62b23d9349742db5c5f3992bd", size = 1705555, upload-time = "2025-12-11T15:27:28.888Z" }, { url = "https://files.pythonhosted.org/packages/95/21/9117d7013997a65f6d51c6f56145b2c583eeba8f7c1af71a60776eaae9b9/raylib-5.5.0.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:31f64f71e42fed10e8f3629028c9f5700906e0e573b915cfc2244d7a3f3b2ed9", size = 1635486, upload-time = "2025-12-11T15:27:31.05Z" }, { url = "https://files.pythonhosted.org/packages/1c/a3/e55039c8f49856c5a194f2b81f27ca6ba2d5900024f09435587e177bfaf2/raylib-5.5.0.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:80bfa053e765d47a9f58d59e321a999184b5a5190e369dd015c12fcfd08d6217", size = 1554132, upload-time = "2025-12-11T15:27:33.291Z" }, { url = "https://files.pythonhosted.org/packages/58/1c/86bee75ecaa577214da16b374f8de70b45885452703f622c63e06baa0b8e/raylib-5.5.0.4-cp312-cp312-manylinux2010_i686.manylinux_2_12_i686.whl", hash = "sha256:033240c61c1a1fc06fecff747a183671431a4ce63a0c8aafec59217845f86888", size = 2039888, upload-time = "2025-12-11T15:27:36.059Z" }, @@ -4555,9 +4118,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6b/e9/0123385e369904335985ebd59157f7a10c89c3a706dffcf6dace863a1fa2/raylib-5.5.0.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:788830bc371ce067c4930ff46a1b6eca0c9cf27bac88f81b035e4b73cc6bf197", size = 2205629, upload-time = "2025-12-11T15:27:39.491Z" }, { url = "https://files.pythonhosted.org/packages/5c/fa/c25087b39d2db2d833a52b4056ae62db74e64b4be677f816e0b368e65453/raylib-5.5.0.4-cp312-cp312-win32.whl", hash = "sha256:e09f395035484337776c90e6c9955c5876b988db7e13168dcadb6ed11974f8ee", size = 1457266, upload-time = "2025-12-11T15:27:43.798Z" }, { url = "https://files.pythonhosted.org/packages/2c/66/a307e61c953ace906ba68ba1174ed8f1e90e68d5fc3e3af9fb7dc46d68d1/raylib-5.5.0.4-cp312-cp312-win_amd64.whl", hash = "sha256:553043a050a31f2ef072f26d3a70373f838a04733f7c5b26a4e9ee3f8caf06ec", size = 1708354, upload-time = "2025-12-11T15:27:45.979Z" }, - { url = "https://files.pythonhosted.org/packages/e8/ba/fee7e6ae0be850f6581d4084ea97825b7895c8866fa8b2df347d408c8293/raylib-5.5.0.4-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c318357ce721c62a6848b6d84b26574cd77267e5758cfa2dbc01d4deb2a2b0b8", size = 1211520, upload-time = "2025-12-11T15:28:30.266Z" }, - { url = "https://files.pythonhosted.org/packages/80/a0/847066c6d824f535068112ed362d41c499f9a4aca52b82b74d9dfb1bdfc7/raylib-5.5.0.4-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:82a0ea2859d04f3b5b441910881ec48789484463856168fa8f35c7165e11d44c", size = 1433828, upload-time = "2025-12-11T15:28:32.204Z" }, - { url = "https://files.pythonhosted.org/packages/40/c6/a2cfb01d63246602ce49111f08d8716e1c7c2994efe4e14d87450176393c/raylib-5.5.0.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:871e77547cd3f78d98a47bef491821cd25c879b3b3b79f1973d8fb3f8841cdfb", size = 1572456, upload-time = "2025-12-11T15:28:34.333Z" }, ] [[package]] @@ -4647,16 +4207,6 @@ version = "1.3.7" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/8d/48/49393a96a2eef1ab418b17475fb92b8fcfad83d099e678751b05472e69de/setproctitle-1.3.7.tar.gz", hash = "sha256:bc2bc917691c1537d5b9bca1468437176809c7e11e5694ca79a9ca12345dcb9e", size = 27002, upload-time = "2025-09-05T12:51:25.278Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/04/cd/1b7ba5cad635510720ce19d7122154df96a2387d2a74217be552887c93e5/setproctitle-1.3.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a600eeb4145fb0ee6c287cb82a2884bd4ec5bbb076921e287039dcc7b7cc6dd0", size = 18085, upload-time = "2025-09-05T12:49:22.183Z" }, - { url = "https://files.pythonhosted.org/packages/8f/1a/b2da0a620490aae355f9d72072ac13e901a9fec809a6a24fc6493a8f3c35/setproctitle-1.3.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:97a090fed480471bb175689859532709e28c085087e344bca45cf318034f70c4", size = 13097, upload-time = "2025-09-05T12:49:23.322Z" }, - { url = "https://files.pythonhosted.org/packages/18/2e/bd03ff02432a181c1787f6fc2a678f53b7dacdd5ded69c318fe1619556e8/setproctitle-1.3.7-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1607b963e7b53e24ec8a2cb4e0ab3ae591d7c6bf0a160feef0551da63452b37f", size = 32191, upload-time = "2025-09-05T12:49:24.567Z" }, - { url = "https://files.pythonhosted.org/packages/28/78/1e62fc0937a8549f2220445ed2175daacee9b6764c7963b16148119b016d/setproctitle-1.3.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a20fb1a3974e2dab857870cf874b325b8705605cb7e7e8bcbb915bca896f52a9", size = 33203, upload-time = "2025-09-05T12:49:25.871Z" }, - { url = "https://files.pythonhosted.org/packages/a0/3c/65edc65db3fa3df400cf13b05e9d41a3c77517b4839ce873aa6b4043184f/setproctitle-1.3.7-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f8d961bba676e07d77665204f36cffaa260f526e7b32d07ab3df6a2c1dfb44ba", size = 34963, upload-time = "2025-09-05T12:49:27.044Z" }, - { url = "https://files.pythonhosted.org/packages/a1/32/89157e3de997973e306e44152522385f428e16f92f3cf113461489e1e2ee/setproctitle-1.3.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:db0fd964fbd3a9f8999b502f65bd2e20883fdb5b1fae3a424e66db9a793ed307", size = 32398, upload-time = "2025-09-05T12:49:28.909Z" }, - { url = "https://files.pythonhosted.org/packages/4a/18/77a765a339ddf046844cb4513353d8e9dcd8183da9cdba6e078713e6b0b2/setproctitle-1.3.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:db116850fcf7cca19492030f8d3b4b6e231278e8fe097a043957d22ce1bdf3ee", size = 33657, upload-time = "2025-09-05T12:49:30.323Z" }, - { url = "https://files.pythonhosted.org/packages/6b/63/f0b6205c64d74d2a24a58644a38ec77bdbaa6afc13747e75973bf8904932/setproctitle-1.3.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:316664d8b24a5c91ee244460bdaf7a74a707adaa9e14fbe0dc0a53168bb9aba1", size = 31836, upload-time = "2025-09-05T12:49:32.309Z" }, - { url = "https://files.pythonhosted.org/packages/ba/51/e1277f9ba302f1a250bbd3eedbbee747a244b3cc682eb58fb9733968f6d8/setproctitle-1.3.7-cp311-cp311-win32.whl", hash = "sha256:b74774ca471c86c09b9d5037c8451fff06bb82cd320d26ae5a01c758088c0d5d", size = 12556, upload-time = "2025-09-05T12:49:33.529Z" }, - { url = "https://files.pythonhosted.org/packages/b6/7b/822a23f17e9003dfdee92cd72758441ca2a3680388da813a371b716fb07f/setproctitle-1.3.7-cp311-cp311-win_amd64.whl", hash = "sha256:acb9097213a8dd3410ed9f0dc147840e45ca9797785272928d4be3f0e69e3be4", size = 13243, upload-time = "2025-09-05T12:49:34.553Z" }, { url = "https://files.pythonhosted.org/packages/fb/f0/2dc88e842077719d7384d86cc47403e5102810492b33680e7dadcee64cd8/setproctitle-1.3.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2dc99aec591ab6126e636b11035a70991bc1ab7a261da428491a40b84376654e", size = 18049, upload-time = "2025-09-05T12:49:36.241Z" }, { url = "https://files.pythonhosted.org/packages/f0/b4/50940504466689cda65680c9e9a1e518e5750c10490639fa687489ac7013/setproctitle-1.3.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cdd8aa571b7aa39840fdbea620e308a19691ff595c3a10231e9ee830339dd798", size = 13079, upload-time = "2025-09-05T12:49:38.088Z" }, { url = "https://files.pythonhosted.org/packages/d0/99/71630546b9395b095f4082be41165d1078204d1696c2d9baade3de3202d0/setproctitle-1.3.7-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2906b6c7959cdb75f46159bf0acd8cc9906cf1361c9e1ded0d065fe8f9039629", size = 32932, upload-time = "2025-09-05T12:49:39.271Z" }, @@ -4667,9 +4217,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c8/b7/06145c238c0a6d2c4bc881f8be230bb9f36d2bf51aff7bddcb796d5eed67/setproctitle-1.3.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d8828b356114f6b308b04afe398ed93803d7fca4a955dd3abe84430e28d33739", size = 32795, upload-time = "2025-09-05T12:49:46.419Z" }, { url = "https://files.pythonhosted.org/packages/ef/dc/ef76a81fac9bf27b84ed23df19c1f67391a753eed6e3c2254ebcb5133f56/setproctitle-1.3.7-cp312-cp312-win32.whl", hash = "sha256:b0304f905efc845829ac2bc791ddebb976db2885f6171f4a3de678d7ee3f7c9f", size = 12552, upload-time = "2025-09-05T12:49:47.635Z" }, { url = "https://files.pythonhosted.org/packages/e2/5b/a9fe517912cd6e28cf43a212b80cb679ff179a91b623138a99796d7d18a0/setproctitle-1.3.7-cp312-cp312-win_amd64.whl", hash = "sha256:9888ceb4faea3116cf02a920ff00bfbc8cc899743e4b4ac914b03625bdc3c300", size = 13247, upload-time = "2025-09-05T12:49:49.16Z" }, - { url = "https://files.pythonhosted.org/packages/c3/5b/5e1c117ac84e3cefcf8d7a7f6b2461795a87e20869da065a5c087149060b/setproctitle-1.3.7-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:b1cac6a4b0252b8811d60b6d8d0f157c0fdfed379ac89c25a914e6346cf355a1", size = 12587, upload-time = "2025-09-05T12:51:21.195Z" }, - { url = "https://files.pythonhosted.org/packages/73/02/b9eadc226195dcfa90eed37afe56b5dd6fa2f0e5220ab8b7867b8862b926/setproctitle-1.3.7-pp311-pypy311_pp73-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f1704c9e041f2b1dc38f5be4552e141e1432fba3dd52c72eeffd5bc2db04dc65", size = 14286, upload-time = "2025-09-05T12:51:22.61Z" }, - { url = "https://files.pythonhosted.org/packages/28/26/1be1d2a53c2a91ec48fa2ff4a409b395f836798adf194d99de9c059419ea/setproctitle-1.3.7-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:b08b61976ffa548bd5349ce54404bf6b2d51bd74d4f1b241ed1b0f25bce09c3a", size = 13282, upload-time = "2025-09-05T12:51:24.094Z" }, ] [[package]] @@ -4690,14 +4237,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/4d/bc/0989043118a27cccb4e906a46b7565ce36ca7b57f5a18b78f4f1b0f72d9d/shapely-2.1.2.tar.gz", hash = "sha256:2ed4ecb28320a433db18a5bf029986aa8afcfd740745e78847e330d5d94922a9", size = 315489, upload-time = "2025-09-24T13:51:41.432Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8f/8d/1ff672dea9ec6a7b5d422eb6d095ed886e2e523733329f75fdcb14ee1149/shapely-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:91121757b0a36c9aac3427a651a7e6567110a4a67c97edf04f8d55d4765f6618", size = 1820038, upload-time = "2025-09-24T13:50:15.628Z" }, - { url = "https://files.pythonhosted.org/packages/4f/ce/28fab8c772ce5db23a0d86bf0adaee0c4c79d5ad1db766055fa3dab442e2/shapely-2.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:16a9c722ba774cf50b5d4541242b4cce05aafd44a015290c82ba8a16931ff63d", size = 1626039, upload-time = "2025-09-24T13:50:16.881Z" }, - { url = "https://files.pythonhosted.org/packages/70/8b/868b7e3f4982f5006e9395c1e12343c66a8155c0374fdc07c0e6a1ab547d/shapely-2.1.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cc4f7397459b12c0b196c9efe1f9d7e92463cbba142632b4cc6d8bbbbd3e2b09", size = 3001519, upload-time = "2025-09-24T13:50:18.606Z" }, - { url = "https://files.pythonhosted.org/packages/13/02/58b0b8d9c17c93ab6340edd8b7308c0c5a5b81f94ce65705819b7416dba5/shapely-2.1.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:136ab87b17e733e22f0961504d05e77e7be8c9b5a8184f685b4a91a84efe3c26", size = 3110842, upload-time = "2025-09-24T13:50:21.77Z" }, - { url = "https://files.pythonhosted.org/packages/af/61/8e389c97994d5f331dcffb25e2fa761aeedfb52b3ad9bcdd7b8671f4810a/shapely-2.1.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:16c5d0fc45d3aa0a69074979f4f1928ca2734fb2e0dde8af9611e134e46774e7", size = 4021316, upload-time = "2025-09-24T13:50:23.626Z" }, - { url = "https://files.pythonhosted.org/packages/d3/d4/9b2a9fe6039f9e42ccf2cb3e84f219fd8364b0c3b8e7bbc857b5fbe9c14c/shapely-2.1.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6ddc759f72b5b2b0f54a7e7cde44acef680a55019eb52ac63a7af2cf17cb9cd2", size = 4178586, upload-time = "2025-09-24T13:50:25.443Z" }, - { url = "https://files.pythonhosted.org/packages/16/f6/9840f6963ed4decf76b08fd6d7fed14f8779fb7a62cb45c5617fa8ac6eab/shapely-2.1.2-cp311-cp311-win32.whl", hash = "sha256:2fa78b49485391224755a856ed3b3bd91c8455f6121fee0db0e71cefb07d0ef6", size = 1543961, upload-time = "2025-09-24T13:50:26.968Z" }, - { url = "https://files.pythonhosted.org/packages/38/1e/3f8ea46353c2a33c1669eb7327f9665103aa3a8dfe7f2e4ef714c210b2c2/shapely-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:c64d5c97b2f47e3cd9b712eaced3b061f2b71234b3fc263e0fcf7d889c6559dc", size = 1722856, upload-time = "2025-09-24T13:50:28.497Z" }, { url = "https://files.pythonhosted.org/packages/24/c0/f3b6453cf2dfa99adc0ba6675f9aaff9e526d2224cbd7ff9c1a879238693/shapely-2.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fe2533caae6a91a543dec62e8360fe86ffcdc42a7c55f9dfd0128a977a896b94", size = 1833550, upload-time = "2025-09-24T13:50:30.019Z" }, { url = "https://files.pythonhosted.org/packages/86/07/59dee0bc4b913b7ab59ab1086225baca5b8f19865e6101db9ebb7243e132/shapely-2.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ba4d1333cc0bc94381d6d4308d2e4e008e0bd128bdcff5573199742ee3634359", size = 1643556, upload-time = "2025-09-24T13:50:32.291Z" }, { url = "https://files.pythonhosted.org/packages/26/29/a5397e75b435b9895cd53e165083faed5d12fd9626eadec15a83a2411f0f/shapely-2.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0bd308103340030feef6c111d3eb98d50dc13feea33affc8a6f9fa549e9458a3", size = 2988308, upload-time = "2025-09-24T13:50:33.862Z" }, @@ -4760,15 +4299,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" }, ] -[[package]] -name = "tabulate" -version = "0.9.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ec/fe/802052aecb21e3797b8f7902564ab6ea0d60ff8ca23952079064155d1ae1/tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c", size = 81090, upload-time = "2022-10-06T17:21:48.54Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252, upload-time = "2022-10-06T17:21:44.262Z" }, -] - [[package]] name = "tqdm" version = "4.67.1" @@ -4829,9 +4359,6 @@ version = "6.0.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload-time = "2024-11-01T14:07:13.037Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e0/24/d9be5cd6642a6aa68352ded4b4b10fb0d7889cb7f45814fb92cecd35f101/watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c", size = 96393, upload-time = "2024-11-01T14:06:31.756Z" }, - { url = "https://files.pythonhosted.org/packages/63/7a/6013b0d8dbc56adca7fdd4f0beed381c59f6752341b12fa0886fa7afc78b/watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2", size = 88392, upload-time = "2024-11-01T14:06:32.99Z" }, - { url = "https://files.pythonhosted.org/packages/d1/40/b75381494851556de56281e053700e46bff5b37bf4c7267e858640af5a7f/watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c", size = 89019, upload-time = "2024-11-01T14:06:34.963Z" }, { url = "https://files.pythonhosted.org/packages/39/ea/3930d07dafc9e286ed356a679aa02d777c06e9bfd1164fa7c19c288a5483/watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948", size = 96471, upload-time = "2024-11-01T14:06:37.745Z" }, { url = "https://files.pythonhosted.org/packages/12/87/48361531f70b1f87928b045df868a9fd4e253d9ae087fa4cf3f7113be363/watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860", size = 88449, upload-time = "2024-11-01T14:06:39.748Z" }, { url = "https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0", size = 89054, upload-time = "2024-11-01T14:06:41.009Z" }, @@ -4865,13 +4392,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/08/d5/25f7b19af3a2cb4000cac4f9e5525a40bec79f4f5d0ac9b517c0544586a0/xattr-1.3.0.tar.gz", hash = "sha256:30439fabd7de0787b27e9a6e1d569c5959854cb322f64ce7380fedbfa5035036", size = 17148, upload-time = "2025-10-13T22:16:47.353Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8a/64/292426ad5653e72c6e1325bbff22868a20077290d967cebb9c0624ad08b6/xattr-1.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:331a51bf8f20c27822f44054b0d760588462d3ed472d5e52ba135cf0bea510e8", size = 23448, upload-time = "2025-10-13T22:15:59.229Z" }, - { url = "https://files.pythonhosted.org/packages/63/84/6539fbe620da8e5927406e76b9c8abad8953025d5f578d792747c38a8c0e/xattr-1.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:196360f068b74fa0132a8c6001ce1333f095364b8f43b6fd8cdaf2f18741ef89", size = 18553, upload-time = "2025-10-13T22:16:00.151Z" }, - { url = "https://files.pythonhosted.org/packages/cc/bb/c1c2e24a49f8d13ff878fb85aabc42ea1b2f98ce08d8205b9661d517a9cc/xattr-1.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:405d2e4911d37f2b9400fa501acd920fe0c97fe2b2ec252cb23df4b59c000811", size = 18848, upload-time = "2025-10-13T22:16:01.046Z" }, - { url = "https://files.pythonhosted.org/packages/02/c2/a60aad150322b217dfe33695d8d9f32bc01e8f300641b6ba4b73f4b3c03f/xattr-1.3.0-cp311-cp311-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4ae3a66ae1effd40994f64defeeaa97da369406485e60bfb421f2d781be3b75d", size = 38547, upload-time = "2025-10-13T22:16:01.973Z" }, - { url = "https://files.pythonhosted.org/packages/c6/58/2eca142bad4ea0a2be6b58d3122d0acce310c4e53fa7defd168202772178/xattr-1.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:69cd3bfe779f7ba87abe6473fdfa428460cf9e78aeb7e390cfd737b784edf1b5", size = 38753, upload-time = "2025-10-13T22:16:03.244Z" }, - { url = "https://files.pythonhosted.org/packages/2b/50/d032e5254c2c27d36bdb02abdf2735db6768a441f0e3d0f139e0f9f56638/xattr-1.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c5742ca61761a99ae0c522f90a39d5fb8139280f27b254e3128482296d1df2db", size = 38054, upload-time = "2025-10-13T22:16:04.656Z" }, - { url = "https://files.pythonhosted.org/packages/04/24/458a306439aabe0083ca0a7b14c3e6a800ab9782b5ec0bdcec4ec9f3dc6c/xattr-1.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4a04ada131e9bdfd32db3ab1efa9f852646f4f7c9d6fde0596c3825c67161be3", size = 37562, upload-time = "2025-10-13T22:16:05.97Z" }, { url = "https://files.pythonhosted.org/packages/bf/78/00bdc9290066173e53e1e734d8d8e1a84a6faa9c66aee9df81e4d9aeec1c/xattr-1.3.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:dd4e63614722d183e81842cb237fd1cc978d43384166f9fe22368bfcb187ebe5", size = 23476, upload-time = "2025-10-13T22:16:06.942Z" }, { url = "https://files.pythonhosted.org/packages/53/16/5243722294eb982514fa7b6b87a29dfb7b29b8e5e1486500c5babaf6e4b3/xattr-1.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:995843ef374af73e3370b0c107319611f3cdcdb6d151d629449efecad36be4c4", size = 18556, upload-time = "2025-10-13T22:16:08.209Z" }, { url = "https://files.pythonhosted.org/packages/d6/5c/d7ab0e547bea885b55f097206459bd612cefb652c5fc1f747130cbc0d42c/xattr-1.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fa23a25220e29d956cedf75746e3df6cc824cc1553326d6516479967c540e386", size = 18869, upload-time = "2025-10-13T22:16:10.319Z" }, @@ -4904,22 +4424,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/57/63/0c6ebca57330cd313f6102b16dd57ffaf3ec4c83403dcb45dbd15c6f3ea1/yarl-1.22.0.tar.gz", hash = "sha256:bebf8557577d4401ba8bd9ff33906f1376c877aa78d1fe216ad01b4d6745af71", size = 187169, upload-time = "2025-10-06T14:12:55.963Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/27/5ab13fc84c76a0250afd3d26d5936349a35be56ce5785447d6c423b26d92/yarl-1.22.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1ab72135b1f2db3fed3997d7e7dc1b80573c67138023852b6efb336a5eae6511", size = 141607, upload-time = "2025-10-06T14:09:16.298Z" }, - { url = "https://files.pythonhosted.org/packages/6a/a1/d065d51d02dc02ce81501d476b9ed2229d9a990818332242a882d5d60340/yarl-1.22.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:669930400e375570189492dc8d8341301578e8493aec04aebc20d4717f899dd6", size = 94027, upload-time = "2025-10-06T14:09:17.786Z" }, - { url = "https://files.pythonhosted.org/packages/c1/da/8da9f6a53f67b5106ffe902c6fa0164e10398d4e150d85838b82f424072a/yarl-1.22.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:792a2af6d58177ef7c19cbf0097aba92ca1b9cb3ffdd9c7470e156c8f9b5e028", size = 94963, upload-time = "2025-10-06T14:09:19.662Z" }, - { url = "https://files.pythonhosted.org/packages/68/fe/2c1f674960c376e29cb0bec1249b117d11738db92a6ccc4a530b972648db/yarl-1.22.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ea66b1c11c9150f1372f69afb6b8116f2dd7286f38e14ea71a44eee9ec51b9d", size = 368406, upload-time = "2025-10-06T14:09:21.402Z" }, - { url = "https://files.pythonhosted.org/packages/95/26/812a540e1c3c6418fec60e9bbd38e871eaba9545e94fa5eff8f4a8e28e1e/yarl-1.22.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3e2daa88dc91870215961e96a039ec73e4937da13cf77ce17f9cad0c18df3503", size = 336581, upload-time = "2025-10-06T14:09:22.98Z" }, - { url = "https://files.pythonhosted.org/packages/0b/f5/5777b19e26fdf98563985e481f8be3d8a39f8734147a6ebf459d0dab5a6b/yarl-1.22.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba440ae430c00eee41509353628600212112cd5018d5def7e9b05ea7ac34eb65", size = 388924, upload-time = "2025-10-06T14:09:24.655Z" }, - { url = "https://files.pythonhosted.org/packages/86/08/24bd2477bd59c0bbd994fe1d93b126e0472e4e3df5a96a277b0a55309e89/yarl-1.22.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e6438cc8f23a9c1478633d216b16104a586b9761db62bfacb6425bac0a36679e", size = 392890, upload-time = "2025-10-06T14:09:26.617Z" }, - { url = "https://files.pythonhosted.org/packages/46/00/71b90ed48e895667ecfb1eaab27c1523ee2fa217433ed77a73b13205ca4b/yarl-1.22.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c52a6e78aef5cf47a98ef8e934755abf53953379b7d53e68b15ff4420e6683d", size = 365819, upload-time = "2025-10-06T14:09:28.544Z" }, - { url = "https://files.pythonhosted.org/packages/30/2d/f715501cae832651d3282387c6a9236cd26bd00d0ff1e404b3dc52447884/yarl-1.22.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3b06bcadaac49c70f4c88af4ffcfbe3dc155aab3163e75777818092478bcbbe7", size = 363601, upload-time = "2025-10-06T14:09:30.568Z" }, - { url = "https://files.pythonhosted.org/packages/f8/f9/a678c992d78e394e7126ee0b0e4e71bd2775e4334d00a9278c06a6cce96a/yarl-1.22.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:6944b2dc72c4d7f7052683487e3677456050ff77fcf5e6204e98caf785ad1967", size = 358072, upload-time = "2025-10-06T14:09:32.528Z" }, - { url = "https://files.pythonhosted.org/packages/2c/d1/b49454411a60edb6fefdcad4f8e6dbba7d8019e3a508a1c5836cba6d0781/yarl-1.22.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d5372ca1df0f91a86b047d1277c2aaf1edb32d78bbcefffc81b40ffd18f027ed", size = 385311, upload-time = "2025-10-06T14:09:34.634Z" }, - { url = "https://files.pythonhosted.org/packages/87/e5/40d7a94debb8448c7771a916d1861d6609dddf7958dc381117e7ba36d9e8/yarl-1.22.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:51af598701f5299012b8416486b40fceef8c26fc87dc6d7d1f6fc30609ea0aa6", size = 381094, upload-time = "2025-10-06T14:09:36.268Z" }, - { url = "https://files.pythonhosted.org/packages/35/d8/611cc282502381ad855448643e1ad0538957fc82ae83dfe7762c14069e14/yarl-1.22.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b266bd01fedeffeeac01a79ae181719ff848a5a13ce10075adbefc8f1daee70e", size = 370944, upload-time = "2025-10-06T14:09:37.872Z" }, - { url = "https://files.pythonhosted.org/packages/2d/df/fadd00fb1c90e1a5a8bd731fa3d3de2e165e5a3666a095b04e31b04d9cb6/yarl-1.22.0-cp311-cp311-win32.whl", hash = "sha256:a9b1ba5610a4e20f655258d5a1fdc7ebe3d837bb0e45b581398b99eb98b1f5ca", size = 81804, upload-time = "2025-10-06T14:09:39.359Z" }, - { url = "https://files.pythonhosted.org/packages/b5/f7/149bb6f45f267cb5c074ac40c01c6b3ea6d8a620d34b337f6321928a1b4d/yarl-1.22.0-cp311-cp311-win_amd64.whl", hash = "sha256:078278b9b0b11568937d9509b589ee83ef98ed6d561dfe2020e24a9fd08eaa2b", size = 86858, upload-time = "2025-10-06T14:09:41.068Z" }, - { url = "https://files.pythonhosted.org/packages/2b/13/88b78b93ad3f2f0b78e13bfaaa24d11cbc746e93fe76d8c06bf139615646/yarl-1.22.0-cp311-cp311-win_arm64.whl", hash = "sha256:b6a6f620cfe13ccec221fa312139135166e47ae169f8253f72a0abc0dae94376", size = 81637, upload-time = "2025-10-06T14:09:42.712Z" }, { url = "https://files.pythonhosted.org/packages/75/ff/46736024fee3429b80a165a732e38e5d5a238721e634ab41b040d49f8738/yarl-1.22.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e340382d1afa5d32b892b3ff062436d592ec3d692aeea3bef3a5cfe11bbf8c6f", size = 142000, upload-time = "2025-10-06T14:09:44.631Z" }, { url = "https://files.pythonhosted.org/packages/5a/9a/b312ed670df903145598914770eb12de1bac44599549b3360acc96878df8/yarl-1.22.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f1e09112a2c31ffe8d80be1b0988fa6a18c5d5cad92a9ffbb1c04c91bfe52ad2", size = 94338, upload-time = "2025-10-06T14:09:46.372Z" }, { url = "https://files.pythonhosted.org/packages/ba/f5/0601483296f09c3c65e303d60c070a5c19fcdbc72daa061e96170785bc7d/yarl-1.22.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:939fe60db294c786f6b7c2d2e121576628468f65453d86b0fe36cb52f987bd74", size = 94909, upload-time = "2025-10-06T14:09:48.648Z" }, @@ -4945,23 +4449,6 @@ version = "0.25.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/fd/aa/3e0508d5a5dd96529cdc5a97011299056e14c6505b678fd58938792794b1/zstandard-0.25.0.tar.gz", hash = "sha256:7713e1179d162cf5c7906da876ec2ccb9c3a9dcbdffef0cc7f70c3667a205f0b", size = 711513, upload-time = "2025-09-14T22:15:54.002Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/83/c3ca27c363d104980f1c9cee1101cc8ba724ac8c28a033ede6aab89585b1/zstandard-0.25.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:933b65d7680ea337180733cf9e87293cc5500cc0eb3fc8769f4d3c88d724ec5c", size = 795254, upload-time = "2025-09-14T22:16:26.137Z" }, - { url = "https://files.pythonhosted.org/packages/ac/4d/e66465c5411a7cf4866aeadc7d108081d8ceba9bc7abe6b14aa21c671ec3/zstandard-0.25.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3f79487c687b1fc69f19e487cd949bf3aae653d181dfb5fde3bf6d18894706f", size = 640559, upload-time = "2025-09-14T22:16:27.973Z" }, - { url = "https://files.pythonhosted.org/packages/12/56/354fe655905f290d3b147b33fe946b0f27e791e4b50a5f004c802cb3eb7b/zstandard-0.25.0-cp311-cp311-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:0bbc9a0c65ce0eea3c34a691e3c4b6889f5f3909ba4822ab385fab9057099431", size = 5348020, upload-time = "2025-09-14T22:16:29.523Z" }, - { url = "https://files.pythonhosted.org/packages/3b/13/2b7ed68bd85e69a2069bcc72141d378f22cae5a0f3b353a2c8f50ef30c1b/zstandard-0.25.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:01582723b3ccd6939ab7b3a78622c573799d5d8737b534b86d0e06ac18dbde4a", size = 5058126, upload-time = "2025-09-14T22:16:31.811Z" }, - { url = "https://files.pythonhosted.org/packages/c9/dd/fdaf0674f4b10d92cb120ccff58bbb6626bf8368f00ebfd2a41ba4a0dc99/zstandard-0.25.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5f1ad7bf88535edcf30038f6919abe087f606f62c00a87d7e33e7fc57cb69fcc", size = 5405390, upload-time = "2025-09-14T22:16:33.486Z" }, - { url = "https://files.pythonhosted.org/packages/0f/67/354d1555575bc2490435f90d67ca4dd65238ff2f119f30f72d5cde09c2ad/zstandard-0.25.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:06acb75eebeedb77b69048031282737717a63e71e4ae3f77cc0c3b9508320df6", size = 5452914, upload-time = "2025-09-14T22:16:35.277Z" }, - { url = "https://files.pythonhosted.org/packages/bb/1f/e9cfd801a3f9190bf3e759c422bbfd2247db9d7f3d54a56ecde70137791a/zstandard-0.25.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9300d02ea7c6506f00e627e287e0492a5eb0371ec1670ae852fefffa6164b072", size = 5559635, upload-time = "2025-09-14T22:16:37.141Z" }, - { url = "https://files.pythonhosted.org/packages/21/88/5ba550f797ca953a52d708c8e4f380959e7e3280af029e38fbf47b55916e/zstandard-0.25.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bfd06b1c5584b657a2892a6014c2f4c20e0db0208c159148fa78c65f7e0b0277", size = 5048277, upload-time = "2025-09-14T22:16:38.807Z" }, - { url = "https://files.pythonhosted.org/packages/46/c0/ca3e533b4fa03112facbe7fbe7779cb1ebec215688e5df576fe5429172e0/zstandard-0.25.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f373da2c1757bb7f1acaf09369cdc1d51d84131e50d5fa9863982fd626466313", size = 5574377, upload-time = "2025-09-14T22:16:40.523Z" }, - { url = "https://files.pythonhosted.org/packages/12/9b/3fb626390113f272abd0799fd677ea33d5fc3ec185e62e6be534493c4b60/zstandard-0.25.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6c0e5a65158a7946e7a7affa6418878ef97ab66636f13353b8502d7ea03c8097", size = 4961493, upload-time = "2025-09-14T22:16:43.3Z" }, - { url = "https://files.pythonhosted.org/packages/cb/d3/23094a6b6a4b1343b27ae68249daa17ae0651fcfec9ed4de09d14b940285/zstandard-0.25.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c8e167d5adf59476fa3e37bee730890e389410c354771a62e3c076c86f9f7778", size = 5269018, upload-time = "2025-09-14T22:16:45.292Z" }, - { url = "https://files.pythonhosted.org/packages/8c/a7/bb5a0c1c0f3f4b5e9d5b55198e39de91e04ba7c205cc46fcb0f95f0383c1/zstandard-0.25.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:98750a309eb2f020da61e727de7d7ba3c57c97cf6213f6f6277bb7fb42a8e065", size = 5443672, upload-time = "2025-09-14T22:16:47.076Z" }, - { url = "https://files.pythonhosted.org/packages/27/22/503347aa08d073993f25109c36c8d9f029c7d5949198050962cb568dfa5e/zstandard-0.25.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:22a086cff1b6ceca18a8dd6096ec631e430e93a8e70a9ca5efa7561a00f826fa", size = 5822753, upload-time = "2025-09-14T22:16:49.316Z" }, - { url = "https://files.pythonhosted.org/packages/e2/be/94267dc6ee64f0f8ba2b2ae7c7a2df934a816baaa7291db9e1aa77394c3c/zstandard-0.25.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:72d35d7aa0bba323965da807a462b0966c91608ef3a48ba761678cb20ce5d8b7", size = 5366047, upload-time = "2025-09-14T22:16:51.328Z" }, - { url = "https://files.pythonhosted.org/packages/7b/a3/732893eab0a3a7aecff8b99052fecf9f605cf0fb5fb6d0290e36beee47a4/zstandard-0.25.0-cp311-cp311-win32.whl", hash = "sha256:f5aeea11ded7320a84dcdd62a3d95b5186834224a9e55b92ccae35d21a8b63d4", size = 436484, upload-time = "2025-09-14T22:16:55.005Z" }, - { url = "https://files.pythonhosted.org/packages/43/a3/c6155f5c1cce691cb80dfd38627046e50af3ee9ddc5d0b45b9b063bfb8c9/zstandard-0.25.0-cp311-cp311-win_amd64.whl", hash = "sha256:daab68faadb847063d0c56f361a289c4f268706b598afbf9ad113cbe5c38b6b2", size = 506183, upload-time = "2025-09-14T22:16:52.753Z" }, - { url = "https://files.pythonhosted.org/packages/8c/3e/8945ab86a0820cc0e0cdbf38086a92868a9172020fdab8a03ac19662b0e5/zstandard-0.25.0-cp311-cp311-win_arm64.whl", hash = "sha256:22a06c5df3751bb7dc67406f5374734ccee8ed37fc5981bf1ad7041831fa1137", size = 462533, upload-time = "2025-09-14T22:16:53.878Z" }, { url = "https://files.pythonhosted.org/packages/82/fc/f26eb6ef91ae723a03e16eddb198abcfce2bc5a42e224d44cc8b6765e57e/zstandard-0.25.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7b3c3a3ab9daa3eed242d6ecceead93aebbb8f5f84318d82cee643e019c4b73b", size = 795738, upload-time = "2025-09-14T22:16:56.237Z" }, { url = "https://files.pythonhosted.org/packages/aa/1c/d920d64b22f8dd028a8b90e2d756e431a5d86194caa78e3819c7bf53b4b3/zstandard-0.25.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:913cbd31a400febff93b564a23e17c3ed2d56c064006f54efec210d586171c00", size = 640436, upload-time = "2025-09-14T22:16:57.774Z" }, { url = "https://files.pythonhosted.org/packages/53/6c/288c3f0bd9fcfe9ca41e2c2fbfd17b2097f6af57b62a81161941f09afa76/zstandard-0.25.0-cp312-cp312-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:011d388c76b11a0c165374ce660ce2c8efa8e5d87f34996aa80f9c0816698b64", size = 5343019, upload-time = "2025-09-14T22:16:59.302Z" }, From c65cf18c7564d985837dbc7bb054556002bb5953 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 7 Feb 2026 21:00:56 -0800 Subject: [PATCH 853/910] Better memory usage debugging (#37120) --- cereal/log.capnp | 5 + selfdrive/debug/mem_usage.py | 238 ++++++++++++++++++++++++++++++++++ selfdrive/test/test_onroad.py | 7 +- system/proclogd.py | 59 +++++++++ 4 files changed, 307 insertions(+), 2 deletions(-) create mode 100755 selfdrive/debug/mem_usage.py diff --git a/cereal/log.capnp b/cereal/log.capnp index 12bef17b9..119cf2999 100644 --- a/cereal/log.capnp +++ b/cereal/log.capnp @@ -1478,6 +1478,11 @@ struct ProcLog { cmdline @15 :List(Text); exe @16 :Text; + + # from /proc//smaps_rollup (proportional/private memory) + memPss @17 :UInt64; # Pss — shared pages split by mapper count + memPssAnon @18 :UInt64; # Pss_Anon — private anonymous (heap, stack) + memPssShmem @19 :UInt64; # Pss_Shmem — proportional MSGQ/tmpfs share } struct CPUTimes { diff --git a/selfdrive/debug/mem_usage.py b/selfdrive/debug/mem_usage.py new file mode 100755 index 000000000..9bed566e1 --- /dev/null +++ b/selfdrive/debug/mem_usage.py @@ -0,0 +1,238 @@ +#!/usr/bin/env python3 +import argparse +import os +from collections import defaultdict + +import numpy as np +from tabulate import tabulate + +from openpilot.tools.lib.logreader import LogReader + +DEMO_ROUTE = "a2a0ccea32023010|2023-07-27--13-01-19" +MB = 1024 * 1024 +TABULATE_OPTS = dict(tablefmt="simple_grid", stralign="center", numalign="center") + + +def _get_procs(): + from openpilot.selfdrive.test.test_onroad import PROCS + return PROCS + + +def is_openpilot_proc(name): + if any(p in name for p in _get_procs()): + return True + # catch openpilot processes not in PROCS (athenad, manager, etc.) + return 'openpilot' in name or name.startswith(('selfdrive.', 'system.')) + + +def get_proc_name(proc): + if len(proc.cmdline) > 0: + return list(proc.cmdline)[0] + return proc.name + + +def pct(val_mb, total_mb): + return val_mb / total_mb * 100 if total_mb else 0 + + +def has_pss(proc_logs): + """Check if logs contain PSS data (new field, not in old logs).""" + try: + for proc in proc_logs[-1].procLog.procs: + if proc.memPss > 0: + return True + except AttributeError: + pass + return False + + +def print_summary(proc_logs, device_states): + mem = proc_logs[-1].procLog.mem + total = mem.total / MB + used = (mem.total - mem.available) / MB + cached = mem.cached / MB + shared = mem.shared / MB + buffers = mem.buffers / MB + + lines = [ + f" Total: {total:.0f} MB", + f" Used (total-avail): {used:.0f} MB ({pct(used, total):.0f}%)", + f" Cached: {cached:.0f} MB ({pct(cached, total):.0f}%) Buffers: {buffers:.0f} MB ({pct(buffers, total):.0f}%)", + f" Shared/MSGQ: {shared:.0f} MB ({pct(shared, total):.0f}%)", + ] + + if device_states: + mem_pcts = [m.deviceState.memoryUsagePercent for m in device_states] + lines.append(f" deviceState memory: {np.min(mem_pcts)}-{np.max(mem_pcts)}% (avg {np.mean(mem_pcts):.0f}%)") + + print("\n-- Memory Summary --") + print("\n".join(lines)) + return total + + +def collect_per_process_mem(proc_logs, use_pss): + """Collect per-process memory samples. Returns {name: {metric: [values_per_sample_in_MB]}}.""" + by_proc = defaultdict(lambda: defaultdict(list)) + + for msg in proc_logs: + sample = defaultdict(lambda: defaultdict(float)) + for proc in msg.procLog.procs: + name = get_proc_name(proc) + sample[name]['rss'] += proc.memRss / MB + if use_pss: + sample[name]['pss'] += proc.memPss / MB + sample[name]['pss_anon'] += proc.memPssAnon / MB + sample[name]['pss_shmem'] += proc.memPssShmem / MB + + for name, metrics in sample.items(): + for metric, val in metrics.items(): + by_proc[name][metric].append(val) + + return by_proc + + +def _has_pss_detail(by_proc) -> bool: + """Check if any process has non-zero pss_anon/pss_shmem (unavailable on some kernels).""" + return any(sum(v.get('pss_anon', [])) > 0 or sum(v.get('pss_shmem', [])) > 0 for v in by_proc.values()) + + +def process_table_rows(by_proc, total_mb, use_pss, show_detail): + """Build table rows. Returns (rows, total_row).""" + mem_key = 'pss' if use_pss else 'rss' + rows = [] + for name in sorted(by_proc, key=lambda n: np.mean(by_proc[n][mem_key]), reverse=True): + m = by_proc[name] + vals = m[mem_key] + avg = round(np.mean(vals)) + row = [name, f"{avg} MB", f"{round(np.max(vals))} MB", f"{round(pct(avg, total_mb), 1)}%"] + if show_detail: + row.append(f"{round(np.mean(m['pss_anon']))} MB") + row.append(f"{round(np.mean(m['pss_shmem']))} MB") + rows.append(row) + + # Total row + total_row = None + if by_proc: + max_samples = max(len(v[mem_key]) for v in by_proc.values()) + totals = [] + for i in range(max_samples): + s = sum(v[mem_key][i] for v in by_proc.values() if i < len(v[mem_key])) + totals.append(s) + avg_total = round(np.mean(totals)) + total_row = ["TOTAL", f"{avg_total} MB", f"{round(np.max(totals))} MB", f"{round(pct(avg_total, total_mb), 1)}%"] + if show_detail: + total_row.append(f"{round(sum(np.mean(v['pss_anon']) for v in by_proc.values()))} MB") + total_row.append(f"{round(sum(np.mean(v['pss_shmem']) for v in by_proc.values()))} MB") + + return rows, total_row + + +def print_process_tables(op_procs, other_procs, total_mb, use_pss): + all_procs = {**op_procs, **other_procs} + show_detail = use_pss and _has_pss_detail(all_procs) + + header = ["process", "avg", "max", "%"] + if show_detail: + header += ["anon", "shmem"] + + op_rows, op_total = process_table_rows(op_procs, total_mb, use_pss, show_detail) + # filter other: >5MB avg and not bare interpreter paths (test infra noise) + other_filtered = {n: v for n, v in other_procs.items() + if np.mean(v['pss' if use_pss else 'rss']) > 5.0 + and os.path.basename(n.split()[0]) not in ('python', 'python3')} + other_rows, other_total = process_table_rows(other_filtered, total_mb, use_pss, show_detail) + + rows = op_rows + if op_total: + rows.append(op_total) + if other_rows: + sep_width = len(header) + rows.append([""] * sep_width) + rows.extend(other_rows) + if other_total: + other_total[0] = "TOTAL (other)" + rows.append(other_total) + + metric = "PSS (no shared double-count)" if use_pss else "RSS (includes shared, overcounts)" + print(f"\n-- Per-Process Memory: {metric} --") + print(tabulate(rows, header, **TABULATE_OPTS)) + + +def print_memory_accounting(proc_logs, op_procs, other_procs, total_mb, use_pss): + last = proc_logs[-1].procLog.mem + used = (last.total - last.available) / MB + shared = last.shared / MB + cached_buf = (last.buffers + last.cached) / MB - shared # shared (MSGQ) is in Cached; separate it + msgq = shared + + mem_key = 'pss' if use_pss else 'rss' + op_total = sum(v[mem_key][-1] for v in op_procs.values()) if op_procs else 0 + other_total = sum(v[mem_key][-1] for v in other_procs.values()) if other_procs else 0 + proc_sum = op_total + other_total + remainder = used - (cached_buf + msgq) - proc_sum + + if not use_pss: + # RSS double-counts shared; add back once to partially correct + remainder += shared + + header = ["", "MB", "%", ""] + label = "PSS" if use_pss else "RSS*" + rows = [ + ["Used (total - avail)", f"{used:.0f}", f"{pct(used, total_mb):.1f}", "memory in use by the system"], + [" Cached + Buffers", f"{cached_buf:.0f}", f"{pct(cached_buf, total_mb):.1f}", "pagecache + fs metadata, reclaimable"], + [" MSGQ (shared)", f"{msgq:.0f}", f"{pct(msgq, total_mb):.1f}", "/dev/shm tmpfs, also in process PSS"], + [f" openpilot {label}", f"{op_total:.0f}", f"{pct(op_total, total_mb):.1f}", "sum of openpilot process memory"], + [f" other {label}", f"{other_total:.0f}", f"{pct(other_total, total_mb):.1f}", "sum of non-openpilot process memory"], + [" kernel/ION/GPU", f"{remainder:.0f}", f"{pct(remainder, total_mb):.1f}", "slab, ION/DMA-BUF, GPU, page tables"], + ] + note = "" if use_pss else " (*RSS overcounts shared mem)" + print(f"\n-- Memory Accounting (last sample){note} --") + print(tabulate(rows, header, tablefmt="simple_grid", stralign="right")) + + +def print_report(proc_logs, device_states=None): + """Print full memory analysis report. Can be called from tests or CLI.""" + if not proc_logs: + print("No procLog messages found") + return + + print(f"{len(proc_logs)} procLog samples, {len(device_states or [])} deviceState samples") + + use_pss = has_pss(proc_logs) + if not use_pss: + print(" (no PSS data — re-record with updated proclogd for accurate numbers)") + + total_mb = print_summary(proc_logs, device_states or []) + + by_proc = collect_per_process_mem(proc_logs, use_pss) + op_procs = {n: v for n, v in by_proc.items() if is_openpilot_proc(n)} + other_procs = {n: v for n, v in by_proc.items() if not is_openpilot_proc(n)} + + print_process_tables(op_procs, other_procs, total_mb, use_pss) + print_memory_accounting(proc_logs, op_procs, other_procs, total_mb, use_pss) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Analyze memory usage from route logs") + parser.add_argument("route", nargs="?", default=None, help="route ID or local rlog path") + parser.add_argument("--demo", action="store_true", help=f"use demo route ({DEMO_ROUTE})") + args = parser.parse_args() + + if args.demo: + route = DEMO_ROUTE + elif args.route: + route = args.route + else: + parser.error("provide a route or use --demo") + + print(f"Reading logs from: {route}") + + proc_logs = [] + device_states = [] + for msg in LogReader(route): + if msg.which() == 'procLog': + proc_logs.append(msg) + elif msg.which() == 'deviceState': + device_states.append(msg) + + print_report(proc_logs, device_states) diff --git a/selfdrive/test/test_onroad.py b/selfdrive/test/test_onroad.py index 33e8685a3..008b8ebe7 100644 --- a/selfdrive/test/test_onroad.py +++ b/selfdrive/test/test_onroad.py @@ -56,7 +56,7 @@ PROCS = { "selfdrive.ui.soundd": 3.0, "selfdrive.ui.feedback.feedbackd": 1.0, "selfdrive.monitoring.dmonitoringd": 4.0, - "system.proclogd": 3.0, + "system.proclogd": 7.0, "system.logmessaged": 1.0, "system.tombstoned": 0, "system.journald": 1.0, @@ -282,9 +282,12 @@ class TestOnroad: print("\n------------------------------------------------") print("--------------- Memory Usage -------------------") print("------------------------------------------------") + + from openpilot.selfdrive.debug.mem_usage import print_report + print_report(self.msgs['procLog'], self.msgs['deviceState']) + offset = int(SERVICE_LIST['deviceState'].frequency * LOG_OFFSET) mems = [m.deviceState.memoryUsagePercent for m in self.msgs['deviceState'][offset:]] - print("Overall memory usage: ", mems) print("MSGQ (/dev/shm/) usage: ", subprocess.check_output(["du", "-hs", "/dev/shm"]).split()[0].decode()) # check for big leaks. note that memory usage is diff --git a/system/proclogd.py b/system/proclogd.py index 3279425b7..b008f8ed9 100755 --- a/system/proclogd.py +++ b/system/proclogd.py @@ -115,6 +115,55 @@ def _parse_proc_stat(stat: str) -> ProcStat | None: cloudlog.exception("failed to parse /proc//stat") return None +class SmapsData(TypedDict): + pss: int # bytes + pss_anon: int # bytes + pss_shmem: int # bytes + + +_SMAPS_KEYS = {b'Pss:', b'Pss_Anon:', b'Pss_Shmem:'} + +# smaps_rollup (kernel 4.14+) is ideal but missing on some BSP kernels; +# fall back to per-VMA smaps (any kernel). Pss_Anon/Pss_Shmem only in 5.x+. +_smaps_path: str | None = None # auto-detected on first call + +# per-VMA smaps is expensive (kernel walks page tables for every VMA). +# cache results and only refresh every N cycles to keep CPU low. +_smaps_cache: dict[int, SmapsData] = {} +_smaps_cycle = 0 +_SMAPS_EVERY = 20 # refresh every 20th cycle (40s at 0.5Hz) + + +def _read_smaps(pid: int) -> SmapsData: + global _smaps_path + try: + if _smaps_path is None: + _smaps_path = 'smaps_rollup' if os.path.exists(f'/proc/{pid}/smaps_rollup') else 'smaps' + + result: SmapsData = {'pss': 0, 'pss_anon': 0, 'pss_shmem': 0} + with open(f'/proc/{pid}/{_smaps_path}', 'rb') as f: + for line in f: + parts = line.split() + if len(parts) >= 2 and parts[0] in _SMAPS_KEYS: + val = int(parts[1]) * 1024 # kB -> bytes + if parts[0] == b'Pss:': + result['pss'] += val + elif parts[0] == b'Pss_Anon:': + result['pss_anon'] += val + elif parts[0] == b'Pss_Shmem:': + result['pss_shmem'] += val + return result + except (FileNotFoundError, PermissionError, ProcessLookupError, OSError): + return {'pss': 0, 'pss_anon': 0, 'pss_shmem': 0} + + +def _get_smaps_cached(pid: int) -> SmapsData: + """Return cached smaps data, refreshing every _SMAPS_EVERY cycles.""" + if _smaps_cycle == 0 or pid not in _smaps_cache: + _smaps_cache[pid] = _read_smaps(pid) + return _smaps_cache.get(pid, {'pss': 0, 'pss_anon': 0, 'pss_shmem': 0}) + + class ProcExtra(TypedDict): pid: int name: str @@ -189,6 +238,13 @@ def build_proc_log_message(msg) -> None: for j, arg in enumerate(extra['cmdline']): cmdline[j] = arg + # smaps is expensive (kernel walks page tables); skip small processes, use cache + if r['rss'] * PAGE_SIZE > 5 * 1024 * 1024: + smaps = _get_smaps_cached(r['pid']) + proc.memPss = smaps['pss'] + proc.memPssAnon = smaps['pss_anon'] + proc.memPssShmem = smaps['pss_shmem'] + cpu_times = _cpu_times() cpu_list = pl.init('cpuTimes', len(cpu_times)) for i, ct in enumerate(cpu_times): @@ -212,6 +268,9 @@ def build_proc_log_message(msg) -> None: pl.mem.inactive = mem_info["Inactive:"] pl.mem.shared = mem_info["Shmem:"] + global _smaps_cycle + _smaps_cycle = (_smaps_cycle + 1) % _SMAPS_EVERY + def main() -> NoReturn: pm = messaging.PubMaster(['procLog']) From c908189e73af3b3d15f1c1945d8b05dae035fd42 Mon Sep 17 00:00:00 2001 From: royjr Date: Sun, 8 Feb 2026 00:32:12 -0500 Subject: [PATCH 854/910] ui: use correct signals while using PID with Developer UI (#1674) * only if angleState * pidState element --- .../onroad/developer_ui/__init__.py | 7 ++++-- .../onroad/developer_ui/elements.py | 25 +++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/sunnypilot/onroad/developer_ui/__init__.py b/selfdrive/ui/sunnypilot/onroad/developer_ui/__init__.py index 441a169d2..f74f1f4c3 100644 --- a/selfdrive/ui/sunnypilot/onroad/developer_ui/__init__.py +++ b/selfdrive/ui/sunnypilot/onroad/developer_ui/__init__.py @@ -10,7 +10,7 @@ from openpilot.selfdrive.ui.sunnypilot.onroad.developer_ui.elements import ( UiElement, RelDistElement, RelSpeedElement, SteeringAngleElement, DesiredLateralAccelElement, ActualLateralAccelElement, DesiredSteeringAngleElement, AEgoElement, LeadSpeedElement, FrictionCoefficientElement, LatAccelFactorElement, - SteeringTorqueEpsElement, BearingDegElement, AltitudeElement + SteeringTorqueEpsElement, BearingDegElement, AltitudeElement, DesiredSteeringPIDElement ) from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.lib.text_measure import measure_text_cached @@ -36,6 +36,7 @@ class DeveloperUiRenderer(Widget): self.desired_lat_accel_elem = DesiredLateralAccelElement() self.actual_lat_accel_elem = ActualLateralAccelElement() self.desired_steer_elem = DesiredSteeringAngleElement() + self.desired_pid_steer_elem = DesiredSteeringPIDElement() self.a_ego_elem = AEgoElement() self.lead_speed_elem = LeadSpeedElement() self.friction_elem = FrictionCoefficientElement() @@ -85,8 +86,10 @@ class DeveloperUiRenderer(Widget): ] if controls_state.lateralControlState.which() == 'torqueState': elements.append(self.desired_lat_accel_elem.update(sm, ui_state.is_metric)) - else: + elif controls_state.lateralControlState.which() == 'angleState': elements.append(self.desired_steer_elem.update(sm, ui_state.is_metric)) + elif controls_state.lateralControlState.which() == 'pidState': + elements.append(self.desired_pid_steer_elem.update(sm, ui_state.is_metric)) elements.append(self.actual_lat_accel_elem.update(sm, ui_state.is_metric)) diff --git a/selfdrive/ui/sunnypilot/onroad/developer_ui/elements.py b/selfdrive/ui/sunnypilot/onroad/developer_ui/elements.py index 652469bdd..94e3af42e 100644 --- a/selfdrive/ui/sunnypilot/onroad/developer_ui/elements.py +++ b/selfdrive/ui/sunnypilot/onroad/developer_ui/elements.py @@ -191,6 +191,31 @@ class DesiredLateralAccelElement(LateralControlElement): return UiElement(value, "DESIRED L.A.", self.unit, color) +class DesiredSteeringPIDElement(LateralControlElement): + def __init__(self): + self.unit = "" + + def update(self, sm, is_metric: bool) -> UiElement: + car_state = sm['carState'] + controls_state = sm['controlsState'] + lat_active = sm['carControl'].latActive + angle_steers = car_state.steeringAngleDeg + steer_angle_desired = controls_state.lateralControlState.pidState.steeringAngleDesiredDeg + + value = f"{steer_angle_desired:.1f}°" if lat_active else "-" + + color = rl.WHITE + if lat_active: + if abs(angle_steers) > 180: + color = rl.RED + elif abs(angle_steers) > 90: + color = rl.Color(255, 188, 0, 255) + else: + color = rl.Color(0, 255, 0, 255) + + return UiElement(value, "DESIRED STEER", self.unit, color) + + class AEgoElement: def __init__(self): self.unit = "m/s^2" From 667f3bb32f696b8bdcd93bce9143cd4171e436a3 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 7 Feb 2026 21:36:44 -0800 Subject: [PATCH 855/910] Revert "revert tg calib and opencl cleanup (#37113)" (#37115) * Revert "revert tg calib and opencl cleanup (#37113)" This reverts commit 51312afd3da58ac73cc98d5c908cbebd70498ba5. * power draw is a lil higher * just don't miss a cycle * fix warp targets * fix tinygrad dep --- Dockerfile.openpilot_base | 37 - SConstruct | 1 - common/SConscript | 1 - common/clutil.cc | 98 -- common/clutil.h | 28 - common/mat.h | 85 - msgq_repo | 2 +- selfdrive/modeld/SConscript | 56 +- selfdrive/modeld/compile_warp.py | 206 +++ selfdrive/modeld/dmonitoringmodeld.py | 33 +- selfdrive/modeld/modeld.py | 59 +- selfdrive/modeld/models/commonmodel.cc | 64 - selfdrive/modeld/models/commonmodel.h | 97 -- selfdrive/modeld/models/commonmodel.pxd | 27 - selfdrive/modeld/models/commonmodel_pyx.pxd | 13 - selfdrive/modeld/models/commonmodel_pyx.pyx | 74 - selfdrive/modeld/runners/tinygrad_helpers.py | 8 - selfdrive/modeld/transforms/loadyuv.cc | 76 - selfdrive/modeld/transforms/loadyuv.cl | 47 - selfdrive/modeld/transforms/loadyuv.h | 20 - selfdrive/modeld/transforms/transform.cc | 97 -- selfdrive/modeld/transforms/transform.cl | 54 - selfdrive/modeld/transforms/transform.h | 25 - selfdrive/test/process_replay/model_replay.py | 4 +- .../test/process_replay/process_replay.py | 15 +- system/camerad/SConscript | 2 +- system/camerad/cameras/camera_common.cc | 5 +- system/camerad/cameras/camera_common.h | 2 +- system/camerad/cameras/camera_qcom2.cc | 22 +- system/camerad/cameras/spectra.cc | 4 +- system/camerad/cameras/spectra.h | 2 +- system/hardware/tici/tests/test_power_draw.py | 2 +- system/loggerd/SConscript | 5 +- third_party/opencl/include/CL/cl.h | 1452 ----------------- third_party/opencl/include/CL/cl_d3d10.h | 131 -- third_party/opencl/include/CL/cl_d3d11.h | 131 -- .../opencl/include/CL/cl_dx9_media_sharing.h | 132 -- third_party/opencl/include/CL/cl_egl.h | 136 -- third_party/opencl/include/CL/cl_ext.h | 391 ----- third_party/opencl/include/CL/cl_ext_qcom.h | 255 --- third_party/opencl/include/CL/cl_gl.h | 167 -- third_party/opencl/include/CL/cl_gl_ext.h | 74 - third_party/opencl/include/CL/cl_platform.h | 1333 --------------- third_party/opencl/include/CL/opencl.h | 59 - tools/cabana/SConscript | 2 - tools/install_ubuntu_dependencies.sh | 3 - tools/replay/SConscript | 5 - tools/webcam/README.md | 4 - 48 files changed, 309 insertions(+), 5237 deletions(-) delete mode 100644 common/clutil.cc delete mode 100644 common/clutil.h delete mode 100644 common/mat.h create mode 100755 selfdrive/modeld/compile_warp.py delete mode 100644 selfdrive/modeld/models/commonmodel.cc delete mode 100644 selfdrive/modeld/models/commonmodel.h delete mode 100644 selfdrive/modeld/models/commonmodel.pxd delete mode 100644 selfdrive/modeld/models/commonmodel_pyx.pxd delete mode 100644 selfdrive/modeld/models/commonmodel_pyx.pyx delete mode 100644 selfdrive/modeld/runners/tinygrad_helpers.py delete mode 100644 selfdrive/modeld/transforms/loadyuv.cc delete mode 100644 selfdrive/modeld/transforms/loadyuv.cl delete mode 100644 selfdrive/modeld/transforms/loadyuv.h delete mode 100644 selfdrive/modeld/transforms/transform.cc delete mode 100644 selfdrive/modeld/transforms/transform.cl delete mode 100644 selfdrive/modeld/transforms/transform.h delete mode 100644 third_party/opencl/include/CL/cl.h delete mode 100644 third_party/opencl/include/CL/cl_d3d10.h delete mode 100644 third_party/opencl/include/CL/cl_d3d11.h delete mode 100644 third_party/opencl/include/CL/cl_dx9_media_sharing.h delete mode 100644 third_party/opencl/include/CL/cl_egl.h delete mode 100644 third_party/opencl/include/CL/cl_ext.h delete mode 100644 third_party/opencl/include/CL/cl_ext_qcom.h delete mode 100644 third_party/opencl/include/CL/cl_gl.h delete mode 100644 third_party/opencl/include/CL/cl_gl_ext.h delete mode 100644 third_party/opencl/include/CL/cl_platform.h delete mode 100644 third_party/opencl/include/CL/opencl.h diff --git a/Dockerfile.openpilot_base b/Dockerfile.openpilot_base index 44d8d95e9..8a6041299 100644 --- a/Dockerfile.openpilot_base +++ b/Dockerfile.openpilot_base @@ -18,43 +18,6 @@ RUN /tmp/tools/install_ubuntu_dependencies.sh && \ cd /usr/lib/gcc/arm-none-eabi/* && \ rm -rf arm/ thumb/nofp thumb/v6* thumb/v8* thumb/v7+fp thumb/v7-r+fp.sp -# Add OpenCL -RUN apt-get update && apt-get install -y --no-install-recommends \ - apt-utils \ - alien \ - unzip \ - tar \ - curl \ - xz-utils \ - dbus \ - gcc-arm-none-eabi \ - tmux \ - vim \ - libx11-6 \ - wget \ - && rm -rf /var/lib/apt/lists/* - -RUN mkdir -p /tmp/opencl-driver-intel && \ - cd /tmp/opencl-driver-intel && \ - wget https://github.com/intel/llvm/releases/download/2024-WW14/oclcpuexp-2024.17.3.0.09_rel.tar.gz && \ - wget https://github.com/oneapi-src/oneTBB/releases/download/v2021.12.0/oneapi-tbb-2021.12.0-lin.tgz && \ - mkdir -p /opt/intel/oclcpuexp_2024.17.3.0.09_rel && \ - cd /opt/intel/oclcpuexp_2024.17.3.0.09_rel && \ - tar -zxvf /tmp/opencl-driver-intel/oclcpuexp-2024.17.3.0.09_rel.tar.gz && \ - mkdir -p /etc/OpenCL/vendors && \ - echo /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64/libintelocl.so > /etc/OpenCL/vendors/intel_expcpu.icd && \ - cd /opt/intel && \ - tar -zxvf /tmp/opencl-driver-intel/oneapi-tbb-2021.12.0-lin.tgz && \ - ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbb.so /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \ - ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbbmalloc.so /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \ - ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbb.so.12 /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \ - ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbbmalloc.so.2 /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \ - mkdir -p /etc/ld.so.conf.d && \ - echo /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 > /etc/ld.so.conf.d/libintelopenclexp.conf && \ - ldconfig -f /etc/ld.so.conf.d/libintelopenclexp.conf && \ - cd / && \ - rm -rf /tmp/opencl-driver-intel - ENV NVIDIA_VISIBLE_DEVICES=all ENV NVIDIA_DRIVER_CAPABILITIES=graphics,utility,compute ENV QTWEBENGINE_DISABLE_SANDBOX=1 diff --git a/SConstruct b/SConstruct index ca5b7b6cb..4f04be624 100644 --- a/SConstruct +++ b/SConstruct @@ -94,7 +94,6 @@ env = Environment( # Arch-specific flags and paths if arch == "larch64": - env.Append(CPPPATH=["#third_party/opencl/include"]) env.Append(LIBPATH=[ "/usr/local/lib", "/system/vendor/lib64", diff --git a/common/SConscript b/common/SConscript index 1c68cf05c..15a0e5eff 100644 --- a/common/SConscript +++ b/common/SConscript @@ -5,7 +5,6 @@ common_libs = [ 'swaglog.cc', 'util.cc', 'ratekeeper.cc', - 'clutil.cc', ] _common = env.Library('common', common_libs, LIBS="json11") diff --git a/common/clutil.cc b/common/clutil.cc deleted file mode 100644 index f8381a7e0..000000000 --- a/common/clutil.cc +++ /dev/null @@ -1,98 +0,0 @@ -#include "common/clutil.h" - -#include -#include -#include - -#include "common/util.h" -#include "common/swaglog.h" - -namespace { // helper functions - -template -std::string get_info(Func get_info_func, Id id, Name param_name) { - size_t size = 0; - CL_CHECK(get_info_func(id, param_name, 0, NULL, &size)); - std::string info(size, '\0'); - CL_CHECK(get_info_func(id, param_name, size, info.data(), NULL)); - return info; -} -inline std::string get_platform_info(cl_platform_id id, cl_platform_info name) { return get_info(&clGetPlatformInfo, id, name); } -inline std::string get_device_info(cl_device_id id, cl_device_info name) { return get_info(&clGetDeviceInfo, id, name); } - -void cl_print_info(cl_platform_id platform, cl_device_id device) { - size_t work_group_size = 0; - cl_device_type device_type = 0; - clGetDeviceInfo(device, CL_DEVICE_MAX_WORK_GROUP_SIZE, sizeof(work_group_size), &work_group_size, NULL); - clGetDeviceInfo(device, CL_DEVICE_TYPE, sizeof(device_type), &device_type, NULL); - const char *type_str = "Other..."; - switch (device_type) { - case CL_DEVICE_TYPE_CPU: type_str ="CL_DEVICE_TYPE_CPU"; break; - case CL_DEVICE_TYPE_GPU: type_str = "CL_DEVICE_TYPE_GPU"; break; - case CL_DEVICE_TYPE_ACCELERATOR: type_str = "CL_DEVICE_TYPE_ACCELERATOR"; break; - } - - LOGD("vendor: %s", get_platform_info(platform, CL_PLATFORM_VENDOR).c_str()); - LOGD("platform version: %s", get_platform_info(platform, CL_PLATFORM_VERSION).c_str()); - LOGD("profile: %s", get_platform_info(platform, CL_PLATFORM_PROFILE).c_str()); - LOGD("extensions: %s", get_platform_info(platform, CL_PLATFORM_EXTENSIONS).c_str()); - LOGD("name: %s", get_device_info(device, CL_DEVICE_NAME).c_str()); - LOGD("device version: %s", get_device_info(device, CL_DEVICE_VERSION).c_str()); - LOGD("max work group size: %zu", work_group_size); - LOGD("type = %d, %s", (int)device_type, type_str); -} - -void cl_print_build_errors(cl_program program, cl_device_id device) { - cl_build_status status; - clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_STATUS, sizeof(status), &status, NULL); - size_t log_size; - clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, 0, NULL, &log_size); - std::string log(log_size, '\0'); - clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, log_size, &log[0], NULL); - - LOGE("build failed; status=%d, log: %s", status, log.c_str()); -} - -} // namespace - -cl_device_id cl_get_device_id(cl_device_type device_type) { - cl_uint num_platforms = 0; - CL_CHECK(clGetPlatformIDs(0, NULL, &num_platforms)); - std::unique_ptr platform_ids = std::make_unique(num_platforms); - CL_CHECK(clGetPlatformIDs(num_platforms, &platform_ids[0], NULL)); - - for (size_t i = 0; i < num_platforms; ++i) { - LOGD("platform[%zu] CL_PLATFORM_NAME: %s", i, get_platform_info(platform_ids[i], CL_PLATFORM_NAME).c_str()); - - // Get first device - if (cl_device_id device_id = NULL; clGetDeviceIDs(platform_ids[i], device_type, 1, &device_id, NULL) == 0 && device_id) { - cl_print_info(platform_ids[i], device_id); - return device_id; - } - } - LOGE("No valid openCL platform found"); - assert(0); - return nullptr; -} - -cl_context cl_create_context(cl_device_id device_id) { - return CL_CHECK_ERR(clCreateContext(NULL, 1, &device_id, NULL, NULL, &err)); -} - -void cl_release_context(cl_context context) { - clReleaseContext(context); -} - -cl_program cl_program_from_file(cl_context ctx, cl_device_id device_id, const char* path, const char* args) { - return cl_program_from_source(ctx, device_id, util::read_file(path), args); -} - -cl_program cl_program_from_source(cl_context ctx, cl_device_id device_id, const std::string& src, const char* args) { - const char *csrc = src.c_str(); - cl_program prg = CL_CHECK_ERR(clCreateProgramWithSource(ctx, 1, &csrc, NULL, &err)); - if (int err = clBuildProgram(prg, 1, &device_id, args, NULL, NULL); err != 0) { - cl_print_build_errors(prg, device_id); - assert(0); - } - return prg; -} diff --git a/common/clutil.h b/common/clutil.h deleted file mode 100644 index b364e79d4..000000000 --- a/common/clutil.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -#ifdef __APPLE__ -#include -#else -#include -#endif - -#include - -#define CL_CHECK(_expr) \ - do { \ - assert(CL_SUCCESS == (_expr)); \ - } while (0) - -#define CL_CHECK_ERR(_expr) \ - ({ \ - cl_int err = CL_INVALID_VALUE; \ - __typeof__(_expr) _ret = _expr; \ - assert(_ret&& err == CL_SUCCESS); \ - _ret; \ - }) - -cl_device_id cl_get_device_id(cl_device_type device_type); -cl_context cl_create_context(cl_device_id device_id); -void cl_release_context(cl_context context); -cl_program cl_program_from_source(cl_context ctx, cl_device_id device_id, const std::string& src, const char* args = nullptr); -cl_program cl_program_from_file(cl_context ctx, cl_device_id device_id, const char* path, const char* args); diff --git a/common/mat.h b/common/mat.h deleted file mode 100644 index 8e10d6197..000000000 --- a/common/mat.h +++ /dev/null @@ -1,85 +0,0 @@ -#pragma once - -typedef struct vec3 { - float v[3]; -} vec3; - -typedef struct vec4 { - float v[4]; -} vec4; - -typedef struct mat3 { - float v[3*3]; -} mat3; - -typedef struct mat4 { - float v[4*4]; -} mat4; - -static inline mat3 matmul3(const mat3 &a, const mat3 &b) { - mat3 ret = {{0.0}}; - for (int r=0; r<3; r++) { - for (int c=0; c<3; c++) { - float v = 0.0; - for (int k=0; k<3; k++) { - v += a.v[r*3+k] * b.v[k*3+c]; - } - ret.v[r*3+c] = v; - } - } - return ret; -} - -static inline vec3 matvecmul3(const mat3 &a, const vec3 &b) { - vec3 ret = {{0.0}}; - for (int r=0; r<3; r++) { - for (int c=0; c<3; c++) { - ret.v[r] += a.v[r*3+c] * b.v[c]; - } - } - return ret; -} - -static inline mat4 matmul(const mat4 &a, const mat4 &b) { - mat4 ret = {{0.0}}; - for (int r=0; r<4; r++) { - for (int c=0; c<4; c++) { - float v = 0.0; - for (int k=0; k<4; k++) { - v += a.v[r*4+k] * b.v[k*4+c]; - } - ret.v[r*4+c] = v; - } - } - return ret; -} - -static inline vec4 matvecmul(const mat4 &a, const vec4 &b) { - vec4 ret = {{0.0}}; - for (int r=0; r<4; r++) { - for (int c=0; c<4; c++) { - ret.v[r] += a.v[r*4+c] * b.v[c]; - } - } - return ret; -} - -// scales the input and output space of a transformation matrix -// that assumes pixel-center origin. -static inline mat3 transform_scale_buffer(const mat3 &in, float s) { - // in_pt = ( transform(out_pt/s + 0.5) - 0.5) * s - - mat3 transform_out = {{ - 1.0f/s, 0.0f, 0.5f, - 0.0f, 1.0f/s, 0.5f, - 0.0f, 0.0f, 1.0f, - }}; - - mat3 transform_in = {{ - s, 0.0f, -0.5f*s, - 0.0f, s, -0.5f*s, - 0.0f, 0.0f, 1.0f, - }}; - - return matmul3(transform_in, matmul3(in, transform_out)); -} diff --git a/msgq_repo b/msgq_repo index 20f249385..f9ebdca88 160000 --- a/msgq_repo +++ b/msgq_repo @@ -1 +1 @@ -Subproject commit 20f2493855ef32339b80f0ad76b3cb82210dc474 +Subproject commit f9ebdca885cfe25295d09de9fd57023a10758de5 diff --git a/selfdrive/modeld/SConscript b/selfdrive/modeld/SConscript index 91f359744..84e94df4a 100644 --- a/selfdrive/modeld/SConscript +++ b/selfdrive/modeld/SConscript @@ -1,35 +1,12 @@ import os import glob -Import('env', 'envCython', 'arch', 'cereal', 'messaging', 'common', 'visionipc') +Import('env', 'arch') lenv = env.Clone() -lenvCython = envCython.Clone() -libs = [cereal, messaging, visionipc, common, 'capnp', 'kj', 'pthread'] -frameworks = [] - -common_src = [ - "models/commonmodel.cc", - "transforms/loadyuv.cc", - "transforms/transform.cc", -] - -# OpenCL is a framework on Mac -if arch == "Darwin": - frameworks += ['OpenCL'] -else: - libs += ['OpenCL'] - -# Set path definitions -for pathdef, fn in {'TRANSFORM': 'transforms/transform.cl', 'LOADYUV': 'transforms/loadyuv.cl'}.items(): - for xenv in (lenv, lenvCython): - xenv['CXXFLAGS'].append(f'-D{pathdef}_PATH=\\"{File(fn).abspath}\\"') - -# Compile cython -cython_libs = envCython["LIBS"] + libs -commonmodel_lib = lenv.Library('commonmodel', common_src) -lenvCython.Program('models/commonmodel_pyx.so', 'models/commonmodel_pyx.pyx', LIBS=[commonmodel_lib, *cython_libs], FRAMEWORKS=frameworks) -tinygrad_files = sorted(["#"+x for x in glob.glob(env.Dir("#tinygrad_repo").relpath + "/**", recursive=True, root_dir=env.Dir("#").abspath) if 'pycache' not in x]) +tinygrad_root = env.Dir("#").abspath +tinygrad_files = ["#"+x for x in glob.glob(env.Dir("#tinygrad_repo").relpath + "/**", recursive=True, root_dir=tinygrad_root) + if 'pycache' not in x and os.path.isfile(os.path.join(tinygrad_root, x))] # Get model metadata for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']: @@ -38,22 +15,35 @@ for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']: 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 warp +tg_flags = { + 'larch64': 'DEV=QCOM FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0', + 'Darwin': f'DEV=CPU HOME={os.path.expanduser("~")}', # tinygrad calls brew which needs a $HOME in the env +}.get(arch, 'DEV=CPU CPU_LLVM=1') +image_flag = { + 'larch64': 'IMAGE=2', +}.get(arch, 'IMAGE=0') +script_files = [File(Dir("#selfdrive/modeld").File("compile_warp.py").abspath)] +cmd = f'{tg_flags} python3 {Dir("#selfdrive/modeld").abspath}/compile_warp.py ' +from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye +warp_targets = [] +for cam in [_ar_ox_fisheye, _os_fisheye]: + w, h = cam.width, cam.height + warp_targets += [File(f"models/warp_{w}x{h}_tinygrad.pkl").abspath, File(f"models/dm_warp_{w}x{h}_tinygrad.pkl").abspath] +lenv.Command(warp_targets, tinygrad_files + script_files, cmd) + 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' + f'{pythonpath_string} {flags} {image_flag} 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': 'DEV=QCOM FLOAT16=1 NOLOCALS=1 IMAGE=2 JIT_BATCH_SIZE=0', - 'Darwin': f'DEV=CPU HOME={os.path.expanduser("~")}', # tinygrad calls brew which needs a $HOME in the env - }.get(arch, 'DEV=CPU CPU_LLVM=1') - tg_compile(flags, model_name) + tg_compile(tg_flags, model_name) # Compile BIG model if USB GPU is available if "USBGPU" in os.environ: diff --git a/selfdrive/modeld/compile_warp.py b/selfdrive/modeld/compile_warp.py new file mode 100755 index 000000000..5adb60e62 --- /dev/null +++ b/selfdrive/modeld/compile_warp.py @@ -0,0 +1,206 @@ +#!/usr/bin/env python3 +import time +import pickle +import numpy as np +from pathlib import Path +from tinygrad.tensor import Tensor +from tinygrad.helpers import Context +from tinygrad.device import Device +from tinygrad.engine.jit import TinyJit + +from openpilot.system.camerad.cameras.nv12_info import get_nv12_info +from openpilot.common.transformations.model import MEDMODEL_INPUT_SIZE, DM_INPUT_SIZE +from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye + +MODELS_DIR = Path(__file__).parent / 'models' + +CAMERA_CONFIGS = [ + (_ar_ox_fisheye.width, _ar_ox_fisheye.height), # tici: 1928x1208 + (_os_fisheye.width, _os_fisheye.height), # mici: 1344x760 +] + +UV_SCALE_MATRIX = np.array([[0.5, 0, 0], [0, 0.5, 0], [0, 0, 1]], dtype=np.float32) +UV_SCALE_MATRIX_INV = np.linalg.inv(UV_SCALE_MATRIX) + +IMG_BUFFER_SHAPE = (30, MEDMODEL_INPUT_SIZE[1] // 2, MEDMODEL_INPUT_SIZE[0] // 2) + + +def warp_pkl_path(w, h): + return MODELS_DIR / f'warp_{w}x{h}_tinygrad.pkl' + + +def dm_warp_pkl_path(w, h): + return MODELS_DIR / f'dm_warp_{w}x{h}_tinygrad.pkl' + + +def warp_perspective_tinygrad(src_flat, M_inv, dst_shape, src_shape, stride_pad, ratio): + w_dst, h_dst = dst_shape + h_src, w_src = src_shape + + x = Tensor.arange(w_dst).reshape(1, w_dst).expand(h_dst, w_dst) + y = Tensor.arange(h_dst).reshape(h_dst, 1).expand(h_dst, w_dst) + ones = Tensor.ones_like(x) + dst_coords = x.reshape(1, -1).cat(y.reshape(1, -1)).cat(ones.reshape(1, -1)) + + src_coords = M_inv @ dst_coords + src_coords = src_coords / src_coords[2:3, :] + + x_nn_clipped = Tensor.round(src_coords[0]).clip(0, w_src - 1).cast('int') + y_nn_clipped = Tensor.round(src_coords[1]).clip(0, h_src - 1).cast('int') + idx = y_nn_clipped * w_src + (y_nn_clipped * ratio).cast('int') * stride_pad + x_nn_clipped + + sampled = src_flat[idx] + return sampled + + +def frames_to_tensor(frames, model_w, model_h): + H = (frames.shape[0] * 2) // 3 + W = frames.shape[1] + in_img1 = Tensor.cat(frames[0:H:2, 0::2], + frames[1:H:2, 0::2], + frames[0:H:2, 1::2], + frames[1:H:2, 1::2], + frames[H:H+H//4].reshape((H//2, W//2)), + frames[H+H//4:H+H//2].reshape((H//2, W//2)), dim=0).reshape((6, H//2, W//2)) + return in_img1 + + +def make_frame_prepare(cam_w, cam_h, model_w, model_h): + stride, y_height, _, _ = get_nv12_info(cam_w, cam_h) + uv_offset = stride * y_height + stride_pad = stride - cam_w + + def frame_prepare_tinygrad(input_frame, M_inv): + tg_scale = Tensor(UV_SCALE_MATRIX) + M_inv_uv = tg_scale @ M_inv @ Tensor(UV_SCALE_MATRIX_INV) + with Context(SPLIT_REDUCEOP=0): + y = warp_perspective_tinygrad(input_frame[:cam_h*stride], + M_inv, (model_w, model_h), + (cam_h, cam_w), stride_pad, 1).realize() + u = warp_perspective_tinygrad(input_frame[uv_offset:uv_offset + (cam_h//4)*stride], + M_inv_uv, (model_w//2, model_h//2), + (cam_h//2, cam_w//2), stride_pad, 0.5).realize() + v = warp_perspective_tinygrad(input_frame[uv_offset + (cam_h//4)*stride:uv_offset + (cam_h//2)*stride], + M_inv_uv, (model_w//2, model_h//2), + (cam_h//2, cam_w//2), stride_pad, 0.5).realize() + yuv = y.cat(u).cat(v).reshape((model_h * 3 // 2, model_w)) + tensor = frames_to_tensor(yuv, model_w, model_h) + return tensor + return frame_prepare_tinygrad + + +def make_update_img_input(frame_prepare, model_w, model_h): + def update_img_input_tinygrad(tensor, frame, M_inv): + M_inv = M_inv.to(Device.DEFAULT) + new_img = frame_prepare(frame, M_inv) + full_buffer = tensor[6:].cat(new_img, dim=0).contiguous() + return full_buffer, Tensor.cat(full_buffer[:6], full_buffer[-6:], dim=0).contiguous().reshape(1, 12, model_h//2, model_w//2) + return update_img_input_tinygrad + + +def make_update_both_imgs(frame_prepare, model_w, model_h): + update_img = make_update_img_input(frame_prepare, model_w, model_h) + + def update_both_imgs_tinygrad(calib_img_buffer, new_img, M_inv, + calib_big_img_buffer, new_big_img, M_inv_big): + calib_img_buffer, calib_img_pair = update_img(calib_img_buffer, new_img, M_inv) + calib_big_img_buffer, calib_big_img_pair = update_img(calib_big_img_buffer, new_big_img, M_inv_big) + return calib_img_buffer, calib_img_pair, calib_big_img_buffer, calib_big_img_pair + return update_both_imgs_tinygrad + + +def make_warp_dm(cam_w, cam_h, dm_w, dm_h): + stride, y_height, _, _ = get_nv12_info(cam_w, cam_h) + stride_pad = stride - cam_w + + def warp_dm(input_frame, M_inv): + M_inv = M_inv.to(Device.DEFAULT) + with Context(SPLIT_REDUCEOP=0): + result = warp_perspective_tinygrad(input_frame[:cam_h*stride], M_inv, (dm_w, dm_h), (cam_h, cam_w), stride_pad, 1).reshape(-1, dm_h * dm_w) + return result + return warp_dm + + +def compile_modeld_warp(cam_w, cam_h): + model_w, model_h = MEDMODEL_INPUT_SIZE + _, _, _, yuv_size = get_nv12_info(cam_w, cam_h) + + print(f"Compiling modeld warp for {cam_w}x{cam_h}...") + + frame_prepare = make_frame_prepare(cam_w, cam_h, model_w, model_h) + update_both_imgs = make_update_both_imgs(frame_prepare, model_w, model_h) + update_img_jit = TinyJit(update_both_imgs, prune=True) + + full_buffer = Tensor.zeros(IMG_BUFFER_SHAPE, dtype='uint8').contiguous().realize() + big_full_buffer = Tensor.zeros(IMG_BUFFER_SHAPE, dtype='uint8').contiguous().realize() + full_buffer_np = np.zeros(IMG_BUFFER_SHAPE, dtype=np.uint8) + big_full_buffer_np = np.zeros(IMG_BUFFER_SHAPE, dtype=np.uint8) + + for i in range(10): + new_frame_np = (32 * np.random.randn(yuv_size).astype(np.float32) + 128).clip(0, 255).astype(np.uint8) + img_inputs = [full_buffer, + Tensor.from_blob(new_frame_np.ctypes.data, (yuv_size,), dtype='uint8').realize(), + Tensor(Tensor.randn(3, 3).mul(8).realize().numpy(), device='NPY')] + new_big_frame_np = (32 * np.random.randn(yuv_size).astype(np.float32) + 128).clip(0, 255).astype(np.uint8) + big_img_inputs = [big_full_buffer, + Tensor.from_blob(new_big_frame_np.ctypes.data, (yuv_size,), dtype='uint8').realize(), + Tensor(Tensor.randn(3, 3).mul(8).realize().numpy(), device='NPY')] + inputs = img_inputs + big_img_inputs + Device.default.synchronize() + + inputs_np = [x.numpy() for x in inputs] + inputs_np[0] = full_buffer_np + inputs_np[3] = big_full_buffer_np + + st = time.perf_counter() + out = update_img_jit(*inputs) + full_buffer = out[0].contiguous().realize().clone() + big_full_buffer = out[2].contiguous().realize().clone() + mt = time.perf_counter() + Device.default.synchronize() + et = time.perf_counter() + print(f" [{i+1}/10] enqueue {(mt-st)*1e3:6.2f} ms -- total {(et-st)*1e3:6.2f} ms") + + pkl_path = warp_pkl_path(cam_w, cam_h) + with open(pkl_path, "wb") as f: + pickle.dump(update_img_jit, f) + print(f" Saved to {pkl_path}") + + jit = pickle.load(open(pkl_path, "rb")) + jit(*inputs) + + +def compile_dm_warp(cam_w, cam_h): + dm_w, dm_h = DM_INPUT_SIZE + _, _, _, yuv_size = get_nv12_info(cam_w, cam_h) + + print(f"Compiling DM warp for {cam_w}x{cam_h}...") + + warp_dm = make_warp_dm(cam_w, cam_h, dm_w, dm_h) + warp_dm_jit = TinyJit(warp_dm, prune=True) + + for i in range(10): + inputs = [Tensor.from_blob((32 * Tensor.randn(yuv_size,) + 128).cast(dtype='uint8').realize().numpy().ctypes.data, (yuv_size,), dtype='uint8'), + Tensor(Tensor.randn(3, 3).mul(8).realize().numpy(), device='NPY')] + Device.default.synchronize() + st = time.perf_counter() + warp_dm_jit(*inputs) + mt = time.perf_counter() + Device.default.synchronize() + et = time.perf_counter() + print(f" [{i+1}/10] enqueue {(mt-st)*1e3:6.2f} ms -- total {(et-st)*1e3:6.2f} ms") + + pkl_path = dm_warp_pkl_path(cam_w, cam_h) + with open(pkl_path, "wb") as f: + pickle.dump(warp_dm_jit, f) + print(f" Saved to {pkl_path}") + + +def run_and_save_pickle(): + for cam_w, cam_h in CAMERA_CONFIGS: + compile_modeld_warp(cam_w, cam_h) + compile_dm_warp(cam_w, cam_h) + + +if __name__ == "__main__": + run_and_save_pickle() diff --git a/selfdrive/modeld/dmonitoringmodeld.py b/selfdrive/modeld/dmonitoringmodeld.py index fca762c69..7d4df713c 100755 --- a/selfdrive/modeld/dmonitoringmodeld.py +++ b/selfdrive/modeld/dmonitoringmodeld.py @@ -3,7 +3,6 @@ import os from openpilot.system.hardware import TICI os.environ['DEV'] = 'QCOM' if TICI else 'CPU' from tinygrad.tensor import Tensor -from tinygrad.dtype import dtypes import time import pickle import numpy as np @@ -16,32 +15,34 @@ from openpilot.common.swaglog import cloudlog from openpilot.common.realtime import config_realtime_process from openpilot.common.transformations.model import dmonitoringmodel_intrinsics from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye -from openpilot.selfdrive.modeld.models.commonmodel_pyx import CLContext, MonitoringModelFrame +from openpilot.system.camerad.cameras.nv12_info import get_nv12_info from openpilot.selfdrive.modeld.parse_model_outputs import sigmoid, safe_exp -from openpilot.selfdrive.modeld.runners.tinygrad_helpers import qcom_tensor_from_opencl_address PROCESS_NAME = "selfdrive.modeld.dmonitoringmodeld" SEND_RAW_PRED = os.getenv('SEND_RAW_PRED') MODEL_PKL_PATH = Path(__file__).parent / 'models/dmonitoring_model_tinygrad.pkl' METADATA_PATH = Path(__file__).parent / 'models/dmonitoring_model_metadata.pkl' - +MODELS_DIR = Path(__file__).parent / 'models' class ModelState: inputs: dict[str, np.ndarray] output: np.ndarray - def __init__(self, cl_ctx): + def __init__(self): with open(METADATA_PATH, 'rb') as f: model_metadata = pickle.load(f) self.input_shapes = model_metadata['input_shapes'] self.output_slices = model_metadata['output_slices'] - self.frame = MonitoringModelFrame(cl_ctx) self.numpy_inputs = { 'calib': np.zeros(self.input_shapes['calib'], dtype=np.float32), } + self.warp_inputs_np = {'transform': np.zeros((3,3), dtype=np.float32)} + self.warp_inputs = {k: Tensor(v, device='NPY') for k,v in self.warp_inputs_np.items()} + self.frame_buf_params = None self.tensor_inputs = {k: Tensor(v, device='NPY').realize() for k,v in self.numpy_inputs.items()} + self.image_warp = None with open(MODEL_PKL_PATH, "rb") as f: self.model_run = pickle.load(f) @@ -50,14 +51,15 @@ class ModelState: t1 = time.perf_counter() - input_img_cl = self.frame.prepare(buf, transform.flatten()) - if TICI: - # The imgs tensors are backed by opencl memory, only need init once - if 'input_img' not in self.tensor_inputs: - self.tensor_inputs['input_img'] = qcom_tensor_from_opencl_address(input_img_cl.mem_address, self.input_shapes['input_img'], dtype=dtypes.uint8) - else: - self.tensor_inputs['input_img'] = Tensor(self.frame.buffer_from_cl(input_img_cl).reshape(self.input_shapes['input_img']), dtype=dtypes.uint8).realize() + if self.image_warp is None: + self.frame_buf_params = get_nv12_info(buf.width, buf.height) + warp_path = MODELS_DIR / f'dm_warp_{buf.width}x{buf.height}_tinygrad.pkl' + with open(warp_path, "rb") as f: + self.image_warp = pickle.load(f) + self.warp_inputs['frame'] = Tensor.from_blob(buf.data.ctypes.data, (self.frame_buf_params[3],), dtype='uint8').realize() + self.warp_inputs_np['transform'][:] = transform[:] + self.tensor_inputs['input_img'] = self.image_warp(self.warp_inputs['frame'], self.warp_inputs['transform']).realize() output = self.model_run(**self.tensor_inputs).contiguous().realize().uop.base.buffer.numpy() @@ -107,12 +109,11 @@ def get_driverstate_packet(model_output, frame_id: int, location_ts: int, exec_t def main(): config_realtime_process(7, 5) - cl_context = CLContext() - model = ModelState(cl_context) + model = ModelState() cloudlog.warning("models loaded, dmonitoringmodeld starting") cloudlog.warning("connecting to driver stream") - vipc_client = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_DRIVER, True, cl_context) + vipc_client = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_DRIVER, True) while not vipc_client.connect(False): time.sleep(0.1) assert vipc_client.is_connected() diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index 1f347dc32..7fae36510 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -7,7 +7,6 @@ if USBGPU: os.environ['DEV'] = 'AMD' os.environ['AMD_IFACE'] = 'USB' from tinygrad.tensor import Tensor -from tinygrad.dtype import dtypes import time import pickle import numpy as np @@ -22,14 +21,13 @@ from openpilot.common.params import Params from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.realtime import config_realtime_process, DT_MDL from openpilot.common.transformations.camera import DEVICE_CAMERAS +from openpilot.system.camerad.cameras.nv12_info import get_nv12_info from openpilot.common.transformations.model import get_warp_matrix 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.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 -from openpilot.selfdrive.modeld.models.commonmodel_pyx import DrivingModelFrame, CLContext -from openpilot.selfdrive.modeld.runners.tinygrad_helpers import qcom_tensor_from_opencl_address PROCESS_NAME = "selfdrive.modeld.modeld" @@ -39,11 +37,15 @@ VISION_PKL_PATH = Path(__file__).parent / 'models/driving_vision_tinygrad.pkl' 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' +MODELS_DIR = Path(__file__).parent / 'models' LAT_SMOOTH_SECONDS = 0.0 LONG_SMOOTH_SECONDS = 0.3 MIN_LAT_CONTROL_SPEED = 0.3 +IMG_QUEUE_SHAPE = (6*(ModelConstants.MODEL_RUN_FREQ//ModelConstants.MODEL_CONTEXT_FREQ + 1), 128, 256) +assert IMG_QUEUE_SHAPE[0] == 30 + def get_action_from_model(model_output: dict[str, np.ndarray], prev_action: log.ModelDataV2.Action, lat_action_t: float, long_action_t: float, v_ego: float) -> log.ModelDataV2.Action: @@ -136,12 +138,11 @@ class InputQueues: return out class ModelState: - frames: dict[str, DrivingModelFrame] inputs: dict[str, np.ndarray] output: np.ndarray prev_desire: np.ndarray # for tracking the rising edge of the pulse - def __init__(self, context: CLContext): + def __init__(self): with open(VISION_METADATA_PATH, 'rb') as f: vision_metadata = pickle.load(f) self.vision_input_shapes = vision_metadata['input_shapes'] @@ -155,7 +156,6 @@ class ModelState: self.policy_output_slices = policy_metadata['output_slices'] policy_output_size = policy_metadata['output_shapes']['outputs'][1] - self.frames = {name: DrivingModelFrame(context, ModelConstants.MODEL_RUN_FREQ//ModelConstants.MODEL_CONTEXT_FREQ) for name in self.vision_input_names} self.prev_desire = np.zeros(ModelConstants.DESIRE_LEN, dtype=np.float32) # policy inputs @@ -165,12 +165,17 @@ class ModelState: self.full_input_queues.update_dtypes_and_shapes({k: self.numpy_inputs[k].dtype}, {k: self.numpy_inputs[k].shape}) self.full_input_queues.reset() - # img buffers are managed in openCL transform code - self.vision_inputs: dict[str, Tensor] = {} + self.img_queues = {'img': Tensor.zeros(IMG_QUEUE_SHAPE, dtype='uint8').contiguous().realize(), + 'big_img': Tensor.zeros(IMG_QUEUE_SHAPE, dtype='uint8').contiguous().realize(),} + self.full_frames : dict[str, Tensor] = {} + self.transforms_np = {k: np.zeros((3,3), dtype=np.float32) for k in self.img_queues} + self.transforms = {k: Tensor(v, device='NPY').realize() for k, v in self.transforms_np.items()} self.vision_output = np.zeros(vision_output_size, dtype=np.float32) self.policy_inputs = {k: Tensor(v, device='NPY').realize() for k,v in self.numpy_inputs.items()} self.policy_output = np.zeros(policy_output_size, dtype=np.float32) self.parser = Parser() + self.frame_buf_params : dict[str, tuple[int, int, int, int]] = {} + self.update_imgs = None with open(VISION_PKL_PATH, "rb") as f: self.vision_run = pickle.load(f) @@ -188,23 +193,28 @@ class ModelState: inputs['desire_pulse'][0] = 0 new_desire = np.where(inputs['desire_pulse'] - self.prev_desire > .99, inputs['desire_pulse'], 0) self.prev_desire[:] = inputs['desire_pulse'] + if self.update_imgs is None: + for key in bufs.keys(): + w, h = bufs[key].width, bufs[key].height + self.frame_buf_params[key] = get_nv12_info(w, h) + warp_path = MODELS_DIR / f'warp_{w}x{h}_tinygrad.pkl' + with open(warp_path, "rb") as f: + self.update_imgs = pickle.load(f) - imgs_cl = {name: self.frames[name].prepare(bufs[name], transforms[name].flatten()) for name in self.vision_input_names} - 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: - self.vision_inputs[key] = qcom_tensor_from_opencl_address(imgs_cl[key].mem_address, self.vision_input_shapes[key], dtype=dtypes.uint8) - else: - for key in imgs_cl: - frame_input = self.frames[key].buffer_from_cl(imgs_cl[key]).reshape(self.vision_input_shapes[key]) - self.vision_inputs[key] = Tensor(frame_input, dtype=dtypes.uint8).realize() + for key in bufs.keys(): + self.full_frames[key] = Tensor.from_blob(bufs[key].data.ctypes.data, (self.frame_buf_params[key][3],), dtype='uint8').realize() + self.transforms_np[key][:,:] = transforms[key][:,:] + + out = self.update_imgs(self.img_queues['img'], self.full_frames['img'], self.transforms['img'], + self.img_queues['big_img'], self.full_frames['big_img'], self.transforms['big_img']) + self.img_queues['img'], self.img_queues['big_img'], = out[0].realize(), out[2].realize() + vision_inputs = {'img': out[1], 'big_img': out[3]} if prepare_only: return None - self.vision_output = self.vision_run(**self.vision_inputs).contiguous().realize().uop.base.buffer.numpy() + self.vision_output = self.vision_run(**vision_inputs).contiguous().realize().uop.base.buffer.numpy() vision_outputs_dict = self.parser.parse_vision_outputs(self.slice_outputs(self.vision_output, self.vision_output_slices)) self.full_input_queues.enqueue({'features_buffer': vision_outputs_dict['hidden_state'], 'desire_pulse': new_desire}) @@ -214,7 +224,6 @@ class ModelState: self.policy_output = self.policy_run(**self.policy_inputs).contiguous().realize().uop.base.buffer.numpy() policy_outputs_dict = self.parser.parse_policy_outputs(self.slice_outputs(self.policy_output, self.policy_output_slices)) - combined_outputs_dict = {**vision_outputs_dict, **policy_outputs_dict} if SEND_RAW_PRED: combined_outputs_dict['raw_pred'] = np.concatenate([self.vision_output.copy(), self.policy_output.copy()]) @@ -231,10 +240,8 @@ def main(demo=False): 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("loading model") + model = ModelState() cloudlog.warning(f"models loaded in {time.monotonic() - st:.1f}s, modeld starting") # visionipc clients @@ -247,8 +254,8 @@ def main(demo=False): time.sleep(.1) vipc_client_main_stream = VisionStreamType.VISION_STREAM_WIDE_ROAD if main_wide_camera else VisionStreamType.VISION_STREAM_ROAD - vipc_client_main = VisionIpcClient("camerad", vipc_client_main_stream, True, cl_context) - vipc_client_extra = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_WIDE_ROAD, False, cl_context) + vipc_client_main = VisionIpcClient("camerad", vipc_client_main_stream, True) + vipc_client_extra = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_WIDE_ROAD, False) cloudlog.warning(f"vision stream set up, main_wide_camera: {main_wide_camera}, use_extra_client: {use_extra_client}") while not vipc_client_main.connect(False): diff --git a/selfdrive/modeld/models/commonmodel.cc b/selfdrive/modeld/models/commonmodel.cc deleted file mode 100644 index d3341e76e..000000000 --- a/selfdrive/modeld/models/commonmodel.cc +++ /dev/null @@ -1,64 +0,0 @@ -#include "selfdrive/modeld/models/commonmodel.h" - -#include -#include - -#include "common/clutil.h" - -DrivingModelFrame::DrivingModelFrame(cl_device_id device_id, cl_context context, int _temporal_skip) : ModelFrame(device_id, context) { - input_frames = std::make_unique(buf_size); - temporal_skip = _temporal_skip; - input_frames_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, buf_size, NULL, &err)); - img_buffer_20hz_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, (temporal_skip+1)*frame_size_bytes, NULL, &err)); - region.origin = temporal_skip * frame_size_bytes; - region.size = frame_size_bytes; - last_img_cl = CL_CHECK_ERR(clCreateSubBuffer(img_buffer_20hz_cl, CL_MEM_READ_WRITE, CL_BUFFER_CREATE_TYPE_REGION, ®ion, &err)); - - loadyuv_init(&loadyuv, context, device_id, MODEL_WIDTH, MODEL_HEIGHT); - init_transform(device_id, context, MODEL_WIDTH, MODEL_HEIGHT); -} - -cl_mem* DrivingModelFrame::prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection) { - run_transform(yuv_cl, MODEL_WIDTH, MODEL_HEIGHT, frame_width, frame_height, frame_stride, frame_uv_offset, projection); - - for (int i = 0; i < temporal_skip; i++) { - CL_CHECK(clEnqueueCopyBuffer(q, img_buffer_20hz_cl, img_buffer_20hz_cl, (i+1)*frame_size_bytes, i*frame_size_bytes, frame_size_bytes, 0, nullptr, nullptr)); - } - loadyuv_queue(&loadyuv, q, y_cl, u_cl, v_cl, last_img_cl); - - copy_queue(&loadyuv, q, img_buffer_20hz_cl, input_frames_cl, 0, 0, frame_size_bytes); - copy_queue(&loadyuv, q, last_img_cl, input_frames_cl, 0, frame_size_bytes, frame_size_bytes); - - // NOTE: Since thneed is using a different command queue, this clFinish is needed to ensure the image is ready. - clFinish(q); - return &input_frames_cl; -} - -DrivingModelFrame::~DrivingModelFrame() { - deinit_transform(); - loadyuv_destroy(&loadyuv); - CL_CHECK(clReleaseMemObject(input_frames_cl)); - CL_CHECK(clReleaseMemObject(img_buffer_20hz_cl)); - CL_CHECK(clReleaseMemObject(last_img_cl)); - CL_CHECK(clReleaseCommandQueue(q)); -} - - -MonitoringModelFrame::MonitoringModelFrame(cl_device_id device_id, cl_context context) : ModelFrame(device_id, context) { - input_frames = std::make_unique(buf_size); - input_frame_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, buf_size, NULL, &err)); - - init_transform(device_id, context, MODEL_WIDTH, MODEL_HEIGHT); -} - -cl_mem* MonitoringModelFrame::prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection) { - run_transform(yuv_cl, MODEL_WIDTH, MODEL_HEIGHT, frame_width, frame_height, frame_stride, frame_uv_offset, projection); - clFinish(q); - return &y_cl; -} - -MonitoringModelFrame::~MonitoringModelFrame() { - deinit_transform(); - CL_CHECK(clReleaseMemObject(input_frame_cl)); - CL_CHECK(clReleaseCommandQueue(q)); -} diff --git a/selfdrive/modeld/models/commonmodel.h b/selfdrive/modeld/models/commonmodel.h deleted file mode 100644 index 176d7eb6d..000000000 --- a/selfdrive/modeld/models/commonmodel.h +++ /dev/null @@ -1,97 +0,0 @@ -#pragma once - -#include -#include -#include - -#include - -#define CL_USE_DEPRECATED_OPENCL_1_2_APIS -#ifdef __APPLE__ -#include -#else -#include -#endif - -#include "common/mat.h" -#include "selfdrive/modeld/transforms/loadyuv.h" -#include "selfdrive/modeld/transforms/transform.h" - -class ModelFrame { -public: - ModelFrame(cl_device_id device_id, cl_context context) { - q = CL_CHECK_ERR(clCreateCommandQueue(context, device_id, 0, &err)); - } - virtual ~ModelFrame() {} - virtual cl_mem* prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection) { return NULL; } - uint8_t* buffer_from_cl(cl_mem *in_frames, int buffer_size) { - CL_CHECK(clEnqueueReadBuffer(q, *in_frames, CL_TRUE, 0, buffer_size, input_frames.get(), 0, nullptr, nullptr)); - clFinish(q); - return &input_frames[0]; - } - - int MODEL_WIDTH; - int MODEL_HEIGHT; - int MODEL_FRAME_SIZE; - int buf_size; - -protected: - cl_mem y_cl, u_cl, v_cl; - Transform transform; - cl_command_queue q; - std::unique_ptr input_frames; - - void init_transform(cl_device_id device_id, cl_context context, int model_width, int model_height) { - y_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, model_width * model_height, NULL, &err)); - u_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, (model_width / 2) * (model_height / 2), NULL, &err)); - v_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, (model_width / 2) * (model_height / 2), NULL, &err)); - transform_init(&transform, context, device_id); - } - - void deinit_transform() { - transform_destroy(&transform); - CL_CHECK(clReleaseMemObject(v_cl)); - CL_CHECK(clReleaseMemObject(u_cl)); - CL_CHECK(clReleaseMemObject(y_cl)); - } - - void run_transform(cl_mem yuv_cl, int model_width, int model_height, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection) { - transform_queue(&transform, q, - yuv_cl, frame_width, frame_height, frame_stride, frame_uv_offset, - y_cl, u_cl, v_cl, model_width, model_height, projection); - } -}; - -class DrivingModelFrame : public ModelFrame { -public: - DrivingModelFrame(cl_device_id device_id, cl_context context, int _temporal_skip); - ~DrivingModelFrame(); - cl_mem* prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection); - - const int MODEL_WIDTH = 512; - const int MODEL_HEIGHT = 256; - const int MODEL_FRAME_SIZE = MODEL_WIDTH * MODEL_HEIGHT * 3 / 2; - const int buf_size = MODEL_FRAME_SIZE * 2; // 2 frames are temporal_skip frames apart - const size_t frame_size_bytes = MODEL_FRAME_SIZE * sizeof(uint8_t); - -private: - LoadYUVState loadyuv; - cl_mem img_buffer_20hz_cl, last_img_cl, input_frames_cl; - cl_buffer_region region; - int temporal_skip; -}; - -class MonitoringModelFrame : public ModelFrame { -public: - MonitoringModelFrame(cl_device_id device_id, cl_context context); - ~MonitoringModelFrame(); - cl_mem* prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection); - - const int MODEL_WIDTH = 1440; - const int MODEL_HEIGHT = 960; - const int MODEL_FRAME_SIZE = MODEL_WIDTH * MODEL_HEIGHT; - const int buf_size = MODEL_FRAME_SIZE; - -private: - cl_mem input_frame_cl; -}; diff --git a/selfdrive/modeld/models/commonmodel.pxd b/selfdrive/modeld/models/commonmodel.pxd deleted file mode 100644 index 4ac64d917..000000000 --- a/selfdrive/modeld/models/commonmodel.pxd +++ /dev/null @@ -1,27 +0,0 @@ -# distutils: language = c++ - -from msgq.visionipc.visionipc cimport cl_device_id, cl_context, cl_mem - -cdef extern from "common/mat.h": - cdef struct mat3: - float v[9] - -cdef extern from "common/clutil.h": - cdef unsigned long CL_DEVICE_TYPE_DEFAULT - cl_device_id cl_get_device_id(unsigned long) - cl_context cl_create_context(cl_device_id) - void cl_release_context(cl_context) - -cdef extern from "selfdrive/modeld/models/commonmodel.h": - cppclass ModelFrame: - int buf_size - unsigned char * buffer_from_cl(cl_mem*, int); - cl_mem * prepare(cl_mem, int, int, int, int, mat3) - - cppclass DrivingModelFrame: - int buf_size - DrivingModelFrame(cl_device_id, cl_context, int) - - cppclass MonitoringModelFrame: - int buf_size - MonitoringModelFrame(cl_device_id, cl_context) diff --git a/selfdrive/modeld/models/commonmodel_pyx.pxd b/selfdrive/modeld/models/commonmodel_pyx.pxd deleted file mode 100644 index 0bb798625..000000000 --- a/selfdrive/modeld/models/commonmodel_pyx.pxd +++ /dev/null @@ -1,13 +0,0 @@ -# distutils: language = c++ - -from msgq.visionipc.visionipc cimport cl_mem -from msgq.visionipc.visionipc_pyx cimport CLContext as BaseCLContext - -cdef class CLContext(BaseCLContext): - pass - -cdef class CLMem: - cdef cl_mem * mem - - @staticmethod - cdef create(void*) diff --git a/selfdrive/modeld/models/commonmodel_pyx.pyx b/selfdrive/modeld/models/commonmodel_pyx.pyx deleted file mode 100644 index 5b7d11bc7..000000000 --- a/selfdrive/modeld/models/commonmodel_pyx.pyx +++ /dev/null @@ -1,74 +0,0 @@ -# distutils: language = c++ -# cython: c_string_encoding=ascii, language_level=3 - -import numpy as np -cimport numpy as cnp -from libc.string cimport memcpy -from libc.stdint cimport uintptr_t - -from msgq.visionipc.visionipc cimport cl_mem -from msgq.visionipc.visionipc_pyx cimport VisionBuf, CLContext as BaseCLContext -from .commonmodel cimport CL_DEVICE_TYPE_DEFAULT, cl_get_device_id, cl_create_context, cl_release_context -from .commonmodel cimport mat3, ModelFrame as cppModelFrame, DrivingModelFrame as cppDrivingModelFrame, MonitoringModelFrame as cppMonitoringModelFrame - - -cdef class CLContext(BaseCLContext): - def __cinit__(self): - self.device_id = cl_get_device_id(CL_DEVICE_TYPE_DEFAULT) - self.context = cl_create_context(self.device_id) - - def __dealloc__(self): - if self.context: - cl_release_context(self.context) - -cdef class CLMem: - @staticmethod - cdef create(void * cmem): - mem = CLMem() - mem.mem = cmem - return mem - - @property - def mem_address(self): - return (self.mem) - -def cl_from_visionbuf(VisionBuf buf): - return CLMem.create(&buf.buf.buf_cl) - - -cdef class ModelFrame: - cdef cppModelFrame * frame - cdef int buf_size - - def __dealloc__(self): - del self.frame - - def prepare(self, VisionBuf buf, float[:] projection): - cdef mat3 cprojection - memcpy(cprojection.v, &projection[0], 9*sizeof(float)) - cdef cl_mem * data - data = self.frame.prepare(buf.buf.buf_cl, buf.width, buf.height, buf.stride, buf.uv_offset, cprojection) - return CLMem.create(data) - - def buffer_from_cl(self, CLMem in_frames): - cdef unsigned char * data2 - data2 = self.frame.buffer_from_cl(in_frames.mem, self.buf_size) - return np.asarray( data2) - - -cdef class DrivingModelFrame(ModelFrame): - cdef cppDrivingModelFrame * _frame - - def __cinit__(self, CLContext context, int temporal_skip): - self._frame = new cppDrivingModelFrame(context.device_id, context.context, temporal_skip) - self.frame = (self._frame) - self.buf_size = self._frame.buf_size - -cdef class MonitoringModelFrame(ModelFrame): - cdef cppMonitoringModelFrame * _frame - - def __cinit__(self, CLContext context): - self._frame = new cppMonitoringModelFrame(context.device_id, context.context) - self.frame = (self._frame) - self.buf_size = self._frame.buf_size - diff --git a/selfdrive/modeld/runners/tinygrad_helpers.py b/selfdrive/modeld/runners/tinygrad_helpers.py deleted file mode 100644 index 776381341..000000000 --- a/selfdrive/modeld/runners/tinygrad_helpers.py +++ /dev/null @@ -1,8 +0,0 @@ - -from tinygrad.tensor import Tensor -from tinygrad.helpers import to_mv - -def qcom_tensor_from_opencl_address(opencl_address, shape, dtype): - cl_buf_desc_ptr = to_mv(opencl_address, 8).cast('Q')[0] - rawbuf_ptr = to_mv(cl_buf_desc_ptr, 0x100).cast('Q')[20] # offset 0xA0 is a raw gpu pointer. - return Tensor.from_blob(rawbuf_ptr, shape, dtype=dtype, device='QCOM') diff --git a/selfdrive/modeld/transforms/loadyuv.cc b/selfdrive/modeld/transforms/loadyuv.cc deleted file mode 100644 index c93f5cd03..000000000 --- a/selfdrive/modeld/transforms/loadyuv.cc +++ /dev/null @@ -1,76 +0,0 @@ -#include "selfdrive/modeld/transforms/loadyuv.h" - -#include -#include -#include - -void loadyuv_init(LoadYUVState* s, cl_context ctx, cl_device_id device_id, int width, int height) { - memset(s, 0, sizeof(*s)); - - s->width = width; - s->height = height; - - char args[1024]; - snprintf(args, sizeof(args), - "-cl-fast-relaxed-math -cl-denorms-are-zero " - "-DTRANSFORMED_WIDTH=%d -DTRANSFORMED_HEIGHT=%d", - width, height); - cl_program prg = cl_program_from_file(ctx, device_id, LOADYUV_PATH, args); - - s->loadys_krnl = CL_CHECK_ERR(clCreateKernel(prg, "loadys", &err)); - s->loaduv_krnl = CL_CHECK_ERR(clCreateKernel(prg, "loaduv", &err)); - s->copy_krnl = CL_CHECK_ERR(clCreateKernel(prg, "copy", &err)); - - // done with this - CL_CHECK(clReleaseProgram(prg)); -} - -void loadyuv_destroy(LoadYUVState* s) { - CL_CHECK(clReleaseKernel(s->loadys_krnl)); - CL_CHECK(clReleaseKernel(s->loaduv_krnl)); - CL_CHECK(clReleaseKernel(s->copy_krnl)); -} - -void loadyuv_queue(LoadYUVState* s, cl_command_queue q, - cl_mem y_cl, cl_mem u_cl, cl_mem v_cl, - cl_mem out_cl) { - cl_int global_out_off = 0; - - CL_CHECK(clSetKernelArg(s->loadys_krnl, 0, sizeof(cl_mem), &y_cl)); - CL_CHECK(clSetKernelArg(s->loadys_krnl, 1, sizeof(cl_mem), &out_cl)); - CL_CHECK(clSetKernelArg(s->loadys_krnl, 2, sizeof(cl_int), &global_out_off)); - - const size_t loadys_work_size = (s->width*s->height)/8; - CL_CHECK(clEnqueueNDRangeKernel(q, s->loadys_krnl, 1, NULL, - &loadys_work_size, NULL, 0, 0, NULL)); - - const size_t loaduv_work_size = ((s->width/2)*(s->height/2))/8; - global_out_off += (s->width*s->height); - - CL_CHECK(clSetKernelArg(s->loaduv_krnl, 0, sizeof(cl_mem), &u_cl)); - CL_CHECK(clSetKernelArg(s->loaduv_krnl, 1, sizeof(cl_mem), &out_cl)); - CL_CHECK(clSetKernelArg(s->loaduv_krnl, 2, sizeof(cl_int), &global_out_off)); - - CL_CHECK(clEnqueueNDRangeKernel(q, s->loaduv_krnl, 1, NULL, - &loaduv_work_size, NULL, 0, 0, NULL)); - - global_out_off += (s->width/2)*(s->height/2); - - CL_CHECK(clSetKernelArg(s->loaduv_krnl, 0, sizeof(cl_mem), &v_cl)); - CL_CHECK(clSetKernelArg(s->loaduv_krnl, 1, sizeof(cl_mem), &out_cl)); - CL_CHECK(clSetKernelArg(s->loaduv_krnl, 2, sizeof(cl_int), &global_out_off)); - - CL_CHECK(clEnqueueNDRangeKernel(q, s->loaduv_krnl, 1, NULL, - &loaduv_work_size, NULL, 0, 0, NULL)); -} - -void copy_queue(LoadYUVState* s, cl_command_queue q, cl_mem src, cl_mem dst, - size_t src_offset, size_t dst_offset, size_t size) { - CL_CHECK(clSetKernelArg(s->copy_krnl, 0, sizeof(cl_mem), &src)); - CL_CHECK(clSetKernelArg(s->copy_krnl, 1, sizeof(cl_mem), &dst)); - CL_CHECK(clSetKernelArg(s->copy_krnl, 2, sizeof(cl_int), &src_offset)); - CL_CHECK(clSetKernelArg(s->copy_krnl, 3, sizeof(cl_int), &dst_offset)); - const size_t copy_work_size = size/8; - CL_CHECK(clEnqueueNDRangeKernel(q, s->copy_krnl, 1, NULL, - ©_work_size, NULL, 0, 0, NULL)); -} \ No newline at end of file diff --git a/selfdrive/modeld/transforms/loadyuv.cl b/selfdrive/modeld/transforms/loadyuv.cl deleted file mode 100644 index 970187a6d..000000000 --- a/selfdrive/modeld/transforms/loadyuv.cl +++ /dev/null @@ -1,47 +0,0 @@ -#define UV_SIZE ((TRANSFORMED_WIDTH/2)*(TRANSFORMED_HEIGHT/2)) - -__kernel void loadys(__global uchar8 const * const Y, - __global uchar * out, - int out_offset) -{ - const int gid = get_global_id(0); - const int ois = gid * 8; - const int oy = ois / TRANSFORMED_WIDTH; - const int ox = ois % TRANSFORMED_WIDTH; - - const uchar8 ys = Y[gid]; - - // 02 - // 13 - - __global uchar* outy0; - __global uchar* outy1; - if ((oy & 1) == 0) { - outy0 = out + out_offset; //y0 - outy1 = out + out_offset + UV_SIZE*2; //y2 - } else { - outy0 = out + out_offset + UV_SIZE; //y1 - outy1 = out + out_offset + UV_SIZE*3; //y3 - } - - vstore4(ys.s0246, 0, outy0 + (oy/2) * (TRANSFORMED_WIDTH/2) + ox/2); - vstore4(ys.s1357, 0, outy1 + (oy/2) * (TRANSFORMED_WIDTH/2) + ox/2); -} - -__kernel void loaduv(__global uchar8 const * const in, - __global uchar8 * out, - int out_offset) -{ - const int gid = get_global_id(0); - const uchar8 inv = in[gid]; - out[gid + out_offset / 8] = inv; -} - -__kernel void copy(__global uchar8 * in, - __global uchar8 * out, - int in_offset, - int out_offset) -{ - const int gid = get_global_id(0); - out[gid + out_offset / 8] = in[gid + in_offset / 8]; -} diff --git a/selfdrive/modeld/transforms/loadyuv.h b/selfdrive/modeld/transforms/loadyuv.h deleted file mode 100644 index 659059cd2..000000000 --- a/selfdrive/modeld/transforms/loadyuv.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include "common/clutil.h" - -typedef struct { - int width, height; - cl_kernel loadys_krnl, loaduv_krnl, copy_krnl; -} LoadYUVState; - -void loadyuv_init(LoadYUVState* s, cl_context ctx, cl_device_id device_id, int width, int height); - -void loadyuv_destroy(LoadYUVState* s); - -void loadyuv_queue(LoadYUVState* s, cl_command_queue q, - cl_mem y_cl, cl_mem u_cl, cl_mem v_cl, - cl_mem out_cl); - - -void copy_queue(LoadYUVState* s, cl_command_queue q, cl_mem src, cl_mem dst, - size_t src_offset, size_t dst_offset, size_t size); \ No newline at end of file diff --git a/selfdrive/modeld/transforms/transform.cc b/selfdrive/modeld/transforms/transform.cc deleted file mode 100644 index 305643cf4..000000000 --- a/selfdrive/modeld/transforms/transform.cc +++ /dev/null @@ -1,97 +0,0 @@ -#include "selfdrive/modeld/transforms/transform.h" - -#include -#include - -#include "common/clutil.h" - -void transform_init(Transform* s, cl_context ctx, cl_device_id device_id) { - memset(s, 0, sizeof(*s)); - - cl_program prg = cl_program_from_file(ctx, device_id, TRANSFORM_PATH, ""); - s->krnl = CL_CHECK_ERR(clCreateKernel(prg, "warpPerspective", &err)); - // done with this - CL_CHECK(clReleaseProgram(prg)); - - s->m_y_cl = CL_CHECK_ERR(clCreateBuffer(ctx, CL_MEM_READ_WRITE, 3*3*sizeof(float), NULL, &err)); - s->m_uv_cl = CL_CHECK_ERR(clCreateBuffer(ctx, CL_MEM_READ_WRITE, 3*3*sizeof(float), NULL, &err)); -} - -void transform_destroy(Transform* s) { - CL_CHECK(clReleaseMemObject(s->m_y_cl)); - CL_CHECK(clReleaseMemObject(s->m_uv_cl)); - CL_CHECK(clReleaseKernel(s->krnl)); -} - -void transform_queue(Transform* s, - cl_command_queue q, - cl_mem in_yuv, int in_width, int in_height, int in_stride, int in_uv_offset, - cl_mem out_y, cl_mem out_u, cl_mem out_v, - int out_width, int out_height, - const mat3& projection) { - const int zero = 0; - - // sampled using pixel center origin - // (because that's how fastcv and opencv does it) - - mat3 projection_y = projection; - - // in and out uv is half the size of y. - mat3 projection_uv = transform_scale_buffer(projection, 0.5); - - CL_CHECK(clEnqueueWriteBuffer(q, s->m_y_cl, CL_TRUE, 0, 3*3*sizeof(float), (void*)projection_y.v, 0, NULL, NULL)); - CL_CHECK(clEnqueueWriteBuffer(q, s->m_uv_cl, CL_TRUE, 0, 3*3*sizeof(float), (void*)projection_uv.v, 0, NULL, NULL)); - - const int in_y_width = in_width; - const int in_y_height = in_height; - const int in_y_px_stride = 1; - const int in_uv_width = in_width/2; - const int in_uv_height = in_height/2; - const int in_uv_px_stride = 2; - const int in_u_offset = in_uv_offset; - const int in_v_offset = in_uv_offset + 1; - - const int out_y_width = out_width; - const int out_y_height = out_height; - const int out_uv_width = out_width/2; - const int out_uv_height = out_height/2; - - CL_CHECK(clSetKernelArg(s->krnl, 0, sizeof(cl_mem), &in_yuv)); // src - CL_CHECK(clSetKernelArg(s->krnl, 1, sizeof(cl_int), &in_stride)); // src_row_stride - CL_CHECK(clSetKernelArg(s->krnl, 2, sizeof(cl_int), &in_y_px_stride)); // src_px_stride - CL_CHECK(clSetKernelArg(s->krnl, 3, sizeof(cl_int), &zero)); // src_offset - CL_CHECK(clSetKernelArg(s->krnl, 4, sizeof(cl_int), &in_y_height)); // src_rows - CL_CHECK(clSetKernelArg(s->krnl, 5, sizeof(cl_int), &in_y_width)); // src_cols - CL_CHECK(clSetKernelArg(s->krnl, 6, sizeof(cl_mem), &out_y)); // dst - CL_CHECK(clSetKernelArg(s->krnl, 7, sizeof(cl_int), &out_y_width)); // dst_row_stride - CL_CHECK(clSetKernelArg(s->krnl, 8, sizeof(cl_int), &zero)); // dst_offset - CL_CHECK(clSetKernelArg(s->krnl, 9, sizeof(cl_int), &out_y_height)); // dst_rows - CL_CHECK(clSetKernelArg(s->krnl, 10, sizeof(cl_int), &out_y_width)); // dst_cols - CL_CHECK(clSetKernelArg(s->krnl, 11, sizeof(cl_mem), &s->m_y_cl)); // M - - const size_t work_size_y[2] = {(size_t)out_y_width, (size_t)out_y_height}; - - CL_CHECK(clEnqueueNDRangeKernel(q, s->krnl, 2, NULL, - (const size_t*)&work_size_y, NULL, 0, 0, NULL)); - - const size_t work_size_uv[2] = {(size_t)out_uv_width, (size_t)out_uv_height}; - - CL_CHECK(clSetKernelArg(s->krnl, 2, sizeof(cl_int), &in_uv_px_stride)); // src_px_stride - CL_CHECK(clSetKernelArg(s->krnl, 3, sizeof(cl_int), &in_u_offset)); // src_offset - CL_CHECK(clSetKernelArg(s->krnl, 4, sizeof(cl_int), &in_uv_height)); // src_rows - CL_CHECK(clSetKernelArg(s->krnl, 5, sizeof(cl_int), &in_uv_width)); // src_cols - CL_CHECK(clSetKernelArg(s->krnl, 6, sizeof(cl_mem), &out_u)); // dst - CL_CHECK(clSetKernelArg(s->krnl, 7, sizeof(cl_int), &out_uv_width)); // dst_row_stride - CL_CHECK(clSetKernelArg(s->krnl, 8, sizeof(cl_int), &zero)); // dst_offset - CL_CHECK(clSetKernelArg(s->krnl, 9, sizeof(cl_int), &out_uv_height)); // dst_rows - CL_CHECK(clSetKernelArg(s->krnl, 10, sizeof(cl_int), &out_uv_width)); // dst_cols - CL_CHECK(clSetKernelArg(s->krnl, 11, sizeof(cl_mem), &s->m_uv_cl)); // M - - CL_CHECK(clEnqueueNDRangeKernel(q, s->krnl, 2, NULL, - (const size_t*)&work_size_uv, NULL, 0, 0, NULL)); - CL_CHECK(clSetKernelArg(s->krnl, 3, sizeof(cl_int), &in_v_offset)); // src_ofset - CL_CHECK(clSetKernelArg(s->krnl, 6, sizeof(cl_mem), &out_v)); // dst - - CL_CHECK(clEnqueueNDRangeKernel(q, s->krnl, 2, NULL, - (const size_t*)&work_size_uv, NULL, 0, 0, NULL)); -} diff --git a/selfdrive/modeld/transforms/transform.cl b/selfdrive/modeld/transforms/transform.cl deleted file mode 100644 index 2ca25920c..000000000 --- a/selfdrive/modeld/transforms/transform.cl +++ /dev/null @@ -1,54 +0,0 @@ -#define INTER_BITS 5 -#define INTER_TAB_SIZE (1 << INTER_BITS) -#define INTER_SCALE 1.f / INTER_TAB_SIZE - -#define INTER_REMAP_COEF_BITS 15 -#define INTER_REMAP_COEF_SCALE (1 << INTER_REMAP_COEF_BITS) - -__kernel void warpPerspective(__global const uchar * src, - int src_row_stride, int src_px_stride, int src_offset, int src_rows, int src_cols, - __global uchar * dst, - int dst_row_stride, int dst_offset, int dst_rows, int dst_cols, - __constant float * M) -{ - int dx = get_global_id(0); - int dy = get_global_id(1); - - if (dx < dst_cols && dy < dst_rows) - { - float X0 = M[0] * dx + M[1] * dy + M[2]; - float Y0 = M[3] * dx + M[4] * dy + M[5]; - float W = M[6] * dx + M[7] * dy + M[8]; - W = W != 0.0f ? INTER_TAB_SIZE / W : 0.0f; - int X = rint(X0 * W), Y = rint(Y0 * W); - - int sx = convert_short_sat(X >> INTER_BITS); - int sy = convert_short_sat(Y >> INTER_BITS); - - short sx_clamp = clamp(sx, 0, src_cols - 1); - short sx_p1_clamp = clamp(sx + 1, 0, src_cols - 1); - short sy_clamp = clamp(sy, 0, src_rows - 1); - short sy_p1_clamp = clamp(sy + 1, 0, src_rows - 1); - int v0 = convert_int(src[mad24(sy_clamp, src_row_stride, src_offset + sx_clamp*src_px_stride)]); - int v1 = convert_int(src[mad24(sy_clamp, src_row_stride, src_offset + sx_p1_clamp*src_px_stride)]); - int v2 = convert_int(src[mad24(sy_p1_clamp, src_row_stride, src_offset + sx_clamp*src_px_stride)]); - int v3 = convert_int(src[mad24(sy_p1_clamp, src_row_stride, src_offset + sx_p1_clamp*src_px_stride)]); - - short ay = (short)(Y & (INTER_TAB_SIZE - 1)); - short ax = (short)(X & (INTER_TAB_SIZE - 1)); - float taby = 1.f/INTER_TAB_SIZE*ay; - float tabx = 1.f/INTER_TAB_SIZE*ax; - - int dst_index = mad24(dy, dst_row_stride, dst_offset + dx); - - int itab0 = convert_short_sat_rte( (1.0f-taby)*(1.0f-tabx) * INTER_REMAP_COEF_SCALE ); - int itab1 = convert_short_sat_rte( (1.0f-taby)*tabx * INTER_REMAP_COEF_SCALE ); - int itab2 = convert_short_sat_rte( taby*(1.0f-tabx) * INTER_REMAP_COEF_SCALE ); - int itab3 = convert_short_sat_rte( taby*tabx * INTER_REMAP_COEF_SCALE ); - - int val = v0 * itab0 + v1 * itab1 + v2 * itab2 + v3 * itab3; - - uchar pix = convert_uchar_sat((val + (1 << (INTER_REMAP_COEF_BITS-1))) >> INTER_REMAP_COEF_BITS); - dst[dst_index] = pix; - } -} diff --git a/selfdrive/modeld/transforms/transform.h b/selfdrive/modeld/transforms/transform.h deleted file mode 100644 index 771a7054b..000000000 --- a/selfdrive/modeld/transforms/transform.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -#define CL_USE_DEPRECATED_OPENCL_1_2_APIS -#ifdef __APPLE__ -#include -#else -#include -#endif - -#include "common/mat.h" - -typedef struct { - cl_kernel krnl; - cl_mem m_y_cl, m_uv_cl; -} Transform; - -void transform_init(Transform* s, cl_context ctx, cl_device_id device_id); - -void transform_destroy(Transform* transform); - -void transform_queue(Transform* s, cl_command_queue q, - cl_mem yuv, int in_width, int in_height, int in_stride, int in_uv_offset, - cl_mem out_y, cl_mem out_u, cl_mem out_v, - int out_width, int out_height, - const mat3& projection); diff --git a/selfdrive/test/process_replay/model_replay.py b/selfdrive/test/process_replay/model_replay.py index d35474f37..3f671f610 100755 --- a/selfdrive/test/process_replay/model_replay.py +++ b/selfdrive/test/process_replay/model_replay.py @@ -34,8 +34,8 @@ GITHUB = GithubUtils(API_TOKEN, DATA_TOKEN) EXEC_TIMINGS = [ # model, instant max, average max - ("modelV2", 0.035, 0.025), - ("driverStateV2", 0.02, 0.015), + ("modelV2", 0.05, 0.028), + ("driverStateV2", 0.05, 0.015), ] def get_log_fn(test_route, ref="master"): diff --git a/selfdrive/test/process_replay/process_replay.py b/selfdrive/test/process_replay/process_replay.py index 8af72e5f4..5143334bc 100755 --- a/selfdrive/test/process_replay/process_replay.py +++ b/selfdrive/test/process_replay/process_replay.py @@ -4,6 +4,7 @@ import time import copy import heapq import signal +import numpy as np from collections import Counter from dataclasses import dataclass, field from itertools import islice @@ -23,6 +24,7 @@ from openpilot.common.params import Params from openpilot.common.prefix import OpenpilotPrefix from openpilot.common.timeout import Timeout from openpilot.common.realtime import DT_CTRL +from openpilot.system.camerad.cameras.nv12_info import get_nv12_info from openpilot.system.manager.process_config import managed_processes from openpilot.selfdrive.test.process_replay.vision_meta import meta_from_camera_state, available_streams from openpilot.selfdrive.test.process_replay.migration import migrate_all @@ -203,7 +205,8 @@ class ProcessContainer: if meta.camera_state in self.cfg.vision_pubs: assert frs[meta.camera_state].pix_fmt == 'nv12' frame_size = (frs[meta.camera_state].w, frs[meta.camera_state].h) - vipc_server.create_buffers(meta.stream, 2, *frame_size) + stride, y_height, _, yuv_size = get_nv12_info(frame_size[0], frame_size[1]) + vipc_server.create_buffers_with_sizes(meta.stream, 2, frame_size[0], frame_size[1], yuv_size, stride, stride * y_height) vipc_server.start_listener() self.vipc_server = vipc_server @@ -300,7 +303,15 @@ class ProcessContainer: camera_meta = meta_from_camera_state(m.which()) assert frs is not None img = frs[m.which()].get(camera_state.frameId) - self.vipc_server.send(camera_meta.stream, img.flatten().tobytes(), + + h, w = frs[m.which()].h, frs[m.which()].w + stride, y_height, _, yuv_size = get_nv12_info(w, h) + uv_offset = stride * y_height + padded_img = np.zeros((yuv_size), dtype=np.uint8).reshape((-1, stride)) + padded_img[:h, :w] = img[:h * w].reshape((-1, w)) + padded_img[uv_offset // stride:uv_offset // stride + h // 2, :w] = img[h * w:].reshape((-1, w)) + + self.vipc_server.send(camera_meta.stream, padded_img.flatten().tobytes(), camera_state.frameId, camera_state.timestampSof, camera_state.timestampEof) self.msg_queue = [] diff --git a/system/camerad/SConscript b/system/camerad/SConscript index e288c6d8b..c28330b32 100644 --- a/system/camerad/SConscript +++ b/system/camerad/SConscript @@ -1,6 +1,6 @@ Import('env', 'arch', 'messaging', 'common', 'visionipc') -libs = [common, 'OpenCL', messaging, visionipc] +libs = [common, messaging, visionipc] if arch != "Darwin": camera_obj = env.Object(['cameras/camera_qcom2.cc', 'cameras/camera_common.cc', 'cameras/spectra.cc', diff --git a/system/camerad/cameras/camera_common.cc b/system/camerad/cameras/camera_common.cc index 88bca7f77..329192b63 100644 --- a/system/camerad/cameras/camera_common.cc +++ b/system/camerad/cameras/camera_common.cc @@ -7,7 +7,7 @@ #include "system/camerad/cameras/spectra.h" -void CameraBuf::init(cl_device_id device_id, cl_context context, SpectraCamera *cam, VisionIpcServer * v, int frame_cnt, VisionStreamType type) { +void CameraBuf::init(SpectraCamera *cam, VisionIpcServer * v, int frame_cnt, VisionStreamType type) { vipc_server = v; stream_type = type; frame_buf_count = frame_cnt; @@ -21,9 +21,8 @@ void CameraBuf::init(cl_device_id device_id, cl_context context, SpectraCamera * const int raw_frame_size = (sensor->frame_height + sensor->extra_height) * sensor->frame_stride; for (int i = 0; i < frame_buf_count; i++) { camera_bufs_raw[i].allocate(raw_frame_size); - camera_bufs_raw[i].init_cl(device_id, context); } - LOGD("allocated %d CL buffers", frame_buf_count); + LOGD("allocated %d buffers", frame_buf_count); } vipc_server->create_buffers_with_sizes(stream_type, VIPC_BUFFER_COUNT, out_img_width, out_img_height, cam->yuv_size, cam->stride, cam->uv_offset); diff --git a/system/camerad/cameras/camera_common.h b/system/camerad/cameras/camera_common.h index c26859cbc..7f35e06a8 100644 --- a/system/camerad/cameras/camera_common.h +++ b/system/camerad/cameras/camera_common.h @@ -36,7 +36,7 @@ public: CameraBuf() = default; ~CameraBuf(); - void init(cl_device_id device_id, cl_context context, SpectraCamera *cam, VisionIpcServer * v, int frame_cnt, VisionStreamType type); + void init(SpectraCamera *cam, VisionIpcServer * v, int frame_cnt, VisionStreamType type); void sendFrameToVipc(); }; diff --git a/system/camerad/cameras/camera_qcom2.cc b/system/camerad/cameras/camera_qcom2.cc index d741e13cf..6a7f599ab 100644 --- a/system/camerad/cameras/camera_qcom2.cc +++ b/system/camerad/cameras/camera_qcom2.cc @@ -12,16 +12,8 @@ #include #include -#ifdef __TICI__ -#include "CL/cl_ext_qcom.h" -#else -#define CL_PRIORITY_HINT_HIGH_QCOM NULL -#define CL_CONTEXT_PRIORITY_HINT_QCOM NULL -#endif - #include "media/cam_sensor_cmn_header.h" -#include "common/clutil.h" #include "common/params.h" #include "common/swaglog.h" @@ -57,7 +49,7 @@ public: CameraState(SpectraMaster *master, const CameraConfig &config) : camera(master, config) {}; ~CameraState(); - void init(VisionIpcServer *v, cl_device_id device_id, cl_context ctx); + void init(VisionIpcServer *v); void update_exposure_score(float desired_ev, int exp_t, int exp_g_idx, float exp_gain); void set_camera_exposure(float grey_frac); void set_exposure_rect(); @@ -68,8 +60,8 @@ public: } }; -void CameraState::init(VisionIpcServer *v, cl_device_id device_id, cl_context ctx) { - camera.camera_open(v, device_id, ctx); +void CameraState::init(VisionIpcServer *v) { + camera.camera_open(v); if (!camera.enabled) return; @@ -257,11 +249,7 @@ void CameraState::sendState() { void camerad_thread() { // TODO: centralize enabled handling - cl_device_id device_id = cl_get_device_id(CL_DEVICE_TYPE_DEFAULT); - const cl_context_properties props[] = {CL_CONTEXT_PRIORITY_HINT_QCOM, CL_PRIORITY_HINT_HIGH_QCOM, 0}; - cl_context ctx = CL_CHECK_ERR(clCreateContext(props, 1, &device_id, NULL, NULL, &err)); - - VisionIpcServer v("camerad", device_id, ctx); + VisionIpcServer v("camerad"); // *** initial ISP init *** SpectraMaster m; @@ -271,7 +259,7 @@ void camerad_thread() { std::vector> cams; for (const auto &config : ALL_CAMERA_CONFIGS) { auto cam = std::make_unique(&m, config); - cam->init(&v, device_id, ctx); + cam->init(&v); cams.emplace_back(std::move(cam)); } diff --git a/system/camerad/cameras/spectra.cc b/system/camerad/cameras/spectra.cc index 5c3e7a9d2..73e0a78da 100644 --- a/system/camerad/cameras/spectra.cc +++ b/system/camerad/cameras/spectra.cc @@ -274,7 +274,7 @@ int SpectraCamera::clear_req_queue() { return ret; } -void SpectraCamera::camera_open(VisionIpcServer *v, cl_device_id device_id, cl_context ctx) { +void SpectraCamera::camera_open(VisionIpcServer *v) { if (!openSensor()) { return; } @@ -296,7 +296,7 @@ void SpectraCamera::camera_open(VisionIpcServer *v, cl_device_id device_id, cl_c linkDevices(); LOGD("camera init %d", cc.camera_num); - buf.init(device_id, ctx, this, v, ife_buf_depth, cc.stream_type); + buf.init(this, v, ife_buf_depth, cc.stream_type); camera_map_bufs(); clearAndRequeue(1); } diff --git a/system/camerad/cameras/spectra.h b/system/camerad/cameras/spectra.h index 13cb13f98..a02b8a6ca 100644 --- a/system/camerad/cameras/spectra.h +++ b/system/camerad/cameras/spectra.h @@ -113,7 +113,7 @@ public: SpectraCamera(SpectraMaster *master, const CameraConfig &config); ~SpectraCamera(); - void camera_open(VisionIpcServer *v, cl_device_id device_id, cl_context ctx); + void camera_open(VisionIpcServer *v); bool handle_camera_event(const cam_req_mgr_message *event_data); void camera_close(); void camera_map_bufs(); diff --git a/system/hardware/tici/tests/test_power_draw.py b/system/hardware/tici/tests/test_power_draw.py index a92e4c8de..c4401c958 100644 --- a/system/hardware/tici/tests/test_power_draw.py +++ b/system/hardware/tici/tests/test_power_draw.py @@ -32,7 +32,7 @@ class Proc: PROCS = [ Proc(['camerad'], 1.65, atol=0.4, msgs=['roadCameraState', 'wideRoadCameraState', 'driverCameraState']), - Proc(['modeld'], 1.24, atol=0.2, msgs=['modelV2']), + Proc(['modeld'], 1.5, atol=0.2, msgs=['modelV2']), Proc(['dmonitoringmodeld'], 0.65, atol=0.35, msgs=['driverStateV2']), Proc(['encoderd'], 0.23, msgs=[]), ] diff --git a/system/loggerd/SConscript b/system/loggerd/SConscript index cf169f4dc..cc8ef7c88 100644 --- a/system/loggerd/SConscript +++ b/system/loggerd/SConscript @@ -2,16 +2,13 @@ Import('env', 'arch', 'messaging', 'common', 'visionipc') libs = [common, messaging, visionipc, 'avformat', 'avcodec', 'avutil', - 'yuv', 'OpenCL', 'pthread', 'zstd'] + 'yuv', 'pthread', 'zstd'] src = ['logger.cc', 'zstd_writer.cc', 'video_writer.cc', 'encoder/encoder.cc', 'encoder/v4l_encoder.cc', 'encoder/jpeg_encoder.cc'] if arch != "larch64": src += ['encoder/ffmpeg_encoder.cc'] if arch == "Darwin": - # fix OpenCL - del libs[libs.index('OpenCL')] - env['FRAMEWORKS'] = ['OpenCL'] # exclude v4l del src[src.index('encoder/v4l_encoder.cc')] diff --git a/third_party/opencl/include/CL/cl.h b/third_party/opencl/include/CL/cl.h deleted file mode 100644 index 0086319f5..000000000 --- a/third_party/opencl/include/CL/cl.h +++ /dev/null @@ -1,1452 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - ******************************************************************************/ - -#ifndef __OPENCL_CL_H -#define __OPENCL_CL_H - -#ifdef __APPLE__ -#include -#else -#include -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -/******************************************************************************/ - -typedef struct _cl_platform_id * cl_platform_id; -typedef struct _cl_device_id * cl_device_id; -typedef struct _cl_context * cl_context; -typedef struct _cl_command_queue * cl_command_queue; -typedef struct _cl_mem * cl_mem; -typedef struct _cl_program * cl_program; -typedef struct _cl_kernel * cl_kernel; -typedef struct _cl_event * cl_event; -typedef struct _cl_sampler * cl_sampler; - -typedef cl_uint cl_bool; /* WARNING! Unlike cl_ types in cl_platform.h, cl_bool is not guaranteed to be the same size as the bool in kernels. */ -typedef cl_ulong cl_bitfield; -typedef cl_bitfield cl_device_type; -typedef cl_uint cl_platform_info; -typedef cl_uint cl_device_info; -typedef cl_bitfield cl_device_fp_config; -typedef cl_uint cl_device_mem_cache_type; -typedef cl_uint cl_device_local_mem_type; -typedef cl_bitfield cl_device_exec_capabilities; -typedef cl_bitfield cl_device_svm_capabilities; -typedef cl_bitfield cl_command_queue_properties; -typedef intptr_t cl_device_partition_property; -typedef cl_bitfield cl_device_affinity_domain; - -typedef intptr_t cl_context_properties; -typedef cl_uint cl_context_info; -typedef cl_bitfield cl_queue_properties; -typedef cl_uint cl_command_queue_info; -typedef cl_uint cl_channel_order; -typedef cl_uint cl_channel_type; -typedef cl_bitfield cl_mem_flags; -typedef cl_bitfield cl_svm_mem_flags; -typedef cl_uint cl_mem_object_type; -typedef cl_uint cl_mem_info; -typedef cl_bitfield cl_mem_migration_flags; -typedef cl_uint cl_image_info; -typedef cl_uint cl_buffer_create_type; -typedef cl_uint cl_addressing_mode; -typedef cl_uint cl_filter_mode; -typedef cl_uint cl_sampler_info; -typedef cl_bitfield cl_map_flags; -typedef intptr_t cl_pipe_properties; -typedef cl_uint cl_pipe_info; -typedef cl_uint cl_program_info; -typedef cl_uint cl_program_build_info; -typedef cl_uint cl_program_binary_type; -typedef cl_int cl_build_status; -typedef cl_uint cl_kernel_info; -typedef cl_uint cl_kernel_arg_info; -typedef cl_uint cl_kernel_arg_address_qualifier; -typedef cl_uint cl_kernel_arg_access_qualifier; -typedef cl_bitfield cl_kernel_arg_type_qualifier; -typedef cl_uint cl_kernel_work_group_info; -typedef cl_uint cl_kernel_sub_group_info; -typedef cl_uint cl_event_info; -typedef cl_uint cl_command_type; -typedef cl_uint cl_profiling_info; -typedef cl_bitfield cl_sampler_properties; -typedef cl_uint cl_kernel_exec_info; - -typedef struct _cl_image_format { - cl_channel_order image_channel_order; - cl_channel_type image_channel_data_type; -} cl_image_format; - -typedef struct _cl_image_desc { - cl_mem_object_type image_type; - size_t image_width; - size_t image_height; - size_t image_depth; - size_t image_array_size; - size_t image_row_pitch; - size_t image_slice_pitch; - cl_uint num_mip_levels; - cl_uint num_samples; -#ifdef __GNUC__ - __extension__ /* Prevents warnings about anonymous union in -pedantic builds */ -#endif - union { - cl_mem buffer; - cl_mem mem_object; - }; -} cl_image_desc; - -typedef struct _cl_buffer_region { - size_t origin; - size_t size; -} cl_buffer_region; - - -/******************************************************************************/ - -/* Error Codes */ -#define CL_SUCCESS 0 -#define CL_DEVICE_NOT_FOUND -1 -#define CL_DEVICE_NOT_AVAILABLE -2 -#define CL_COMPILER_NOT_AVAILABLE -3 -#define CL_MEM_OBJECT_ALLOCATION_FAILURE -4 -#define CL_OUT_OF_RESOURCES -5 -#define CL_OUT_OF_HOST_MEMORY -6 -#define CL_PROFILING_INFO_NOT_AVAILABLE -7 -#define CL_MEM_COPY_OVERLAP -8 -#define CL_IMAGE_FORMAT_MISMATCH -9 -#define CL_IMAGE_FORMAT_NOT_SUPPORTED -10 -#define CL_BUILD_PROGRAM_FAILURE -11 -#define CL_MAP_FAILURE -12 -#define CL_MISALIGNED_SUB_BUFFER_OFFSET -13 -#define CL_EXEC_STATUS_ERROR_FOR_EVENTS_IN_WAIT_LIST -14 -#define CL_COMPILE_PROGRAM_FAILURE -15 -#define CL_LINKER_NOT_AVAILABLE -16 -#define CL_LINK_PROGRAM_FAILURE -17 -#define CL_DEVICE_PARTITION_FAILED -18 -#define CL_KERNEL_ARG_INFO_NOT_AVAILABLE -19 - -#define CL_INVALID_VALUE -30 -#define CL_INVALID_DEVICE_TYPE -31 -#define CL_INVALID_PLATFORM -32 -#define CL_INVALID_DEVICE -33 -#define CL_INVALID_CONTEXT -34 -#define CL_INVALID_QUEUE_PROPERTIES -35 -#define CL_INVALID_COMMAND_QUEUE -36 -#define CL_INVALID_HOST_PTR -37 -#define CL_INVALID_MEM_OBJECT -38 -#define CL_INVALID_IMAGE_FORMAT_DESCRIPTOR -39 -#define CL_INVALID_IMAGE_SIZE -40 -#define CL_INVALID_SAMPLER -41 -#define CL_INVALID_BINARY -42 -#define CL_INVALID_BUILD_OPTIONS -43 -#define CL_INVALID_PROGRAM -44 -#define CL_INVALID_PROGRAM_EXECUTABLE -45 -#define CL_INVALID_KERNEL_NAME -46 -#define CL_INVALID_KERNEL_DEFINITION -47 -#define CL_INVALID_KERNEL -48 -#define CL_INVALID_ARG_INDEX -49 -#define CL_INVALID_ARG_VALUE -50 -#define CL_INVALID_ARG_SIZE -51 -#define CL_INVALID_KERNEL_ARGS -52 -#define CL_INVALID_WORK_DIMENSION -53 -#define CL_INVALID_WORK_GROUP_SIZE -54 -#define CL_INVALID_WORK_ITEM_SIZE -55 -#define CL_INVALID_GLOBAL_OFFSET -56 -#define CL_INVALID_EVENT_WAIT_LIST -57 -#define CL_INVALID_EVENT -58 -#define CL_INVALID_OPERATION -59 -#define CL_INVALID_GL_OBJECT -60 -#define CL_INVALID_BUFFER_SIZE -61 -#define CL_INVALID_MIP_LEVEL -62 -#define CL_INVALID_GLOBAL_WORK_SIZE -63 -#define CL_INVALID_PROPERTY -64 -#define CL_INVALID_IMAGE_DESCRIPTOR -65 -#define CL_INVALID_COMPILER_OPTIONS -66 -#define CL_INVALID_LINKER_OPTIONS -67 -#define CL_INVALID_DEVICE_PARTITION_COUNT -68 -#define CL_INVALID_PIPE_SIZE -69 -#define CL_INVALID_DEVICE_QUEUE -70 - -/* OpenCL Version */ -#define CL_VERSION_1_0 1 -#define CL_VERSION_1_1 1 -#define CL_VERSION_1_2 1 -#define CL_VERSION_2_0 1 -#define CL_VERSION_2_1 1 - -/* cl_bool */ -#define CL_FALSE 0 -#define CL_TRUE 1 -#define CL_BLOCKING CL_TRUE -#define CL_NON_BLOCKING CL_FALSE - -/* cl_platform_info */ -#define CL_PLATFORM_PROFILE 0x0900 -#define CL_PLATFORM_VERSION 0x0901 -#define CL_PLATFORM_NAME 0x0902 -#define CL_PLATFORM_VENDOR 0x0903 -#define CL_PLATFORM_EXTENSIONS 0x0904 -#define CL_PLATFORM_HOST_TIMER_RESOLUTION 0x0905 - -/* cl_device_type - bitfield */ -#define CL_DEVICE_TYPE_DEFAULT (1 << 0) -#define CL_DEVICE_TYPE_CPU (1 << 1) -#define CL_DEVICE_TYPE_GPU (1 << 2) -#define CL_DEVICE_TYPE_ACCELERATOR (1 << 3) -#define CL_DEVICE_TYPE_CUSTOM (1 << 4) -#define CL_DEVICE_TYPE_ALL 0xFFFFFFFF - -/* cl_device_info */ -#define CL_DEVICE_TYPE 0x1000 -#define CL_DEVICE_VENDOR_ID 0x1001 -#define CL_DEVICE_MAX_COMPUTE_UNITS 0x1002 -#define CL_DEVICE_MAX_WORK_ITEM_DIMENSIONS 0x1003 -#define CL_DEVICE_MAX_WORK_GROUP_SIZE 0x1004 -#define CL_DEVICE_MAX_WORK_ITEM_SIZES 0x1005 -#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_CHAR 0x1006 -#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_SHORT 0x1007 -#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_INT 0x1008 -#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_LONG 0x1009 -#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_FLOAT 0x100A -#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_DOUBLE 0x100B -#define CL_DEVICE_MAX_CLOCK_FREQUENCY 0x100C -#define CL_DEVICE_ADDRESS_BITS 0x100D -#define CL_DEVICE_MAX_READ_IMAGE_ARGS 0x100E -#define CL_DEVICE_MAX_WRITE_IMAGE_ARGS 0x100F -#define CL_DEVICE_MAX_MEM_ALLOC_SIZE 0x1010 -#define CL_DEVICE_IMAGE2D_MAX_WIDTH 0x1011 -#define CL_DEVICE_IMAGE2D_MAX_HEIGHT 0x1012 -#define CL_DEVICE_IMAGE3D_MAX_WIDTH 0x1013 -#define CL_DEVICE_IMAGE3D_MAX_HEIGHT 0x1014 -#define CL_DEVICE_IMAGE3D_MAX_DEPTH 0x1015 -#define CL_DEVICE_IMAGE_SUPPORT 0x1016 -#define CL_DEVICE_MAX_PARAMETER_SIZE 0x1017 -#define CL_DEVICE_MAX_SAMPLERS 0x1018 -#define CL_DEVICE_MEM_BASE_ADDR_ALIGN 0x1019 -#define CL_DEVICE_MIN_DATA_TYPE_ALIGN_SIZE 0x101A -#define CL_DEVICE_SINGLE_FP_CONFIG 0x101B -#define CL_DEVICE_GLOBAL_MEM_CACHE_TYPE 0x101C -#define CL_DEVICE_GLOBAL_MEM_CACHELINE_SIZE 0x101D -#define CL_DEVICE_GLOBAL_MEM_CACHE_SIZE 0x101E -#define CL_DEVICE_GLOBAL_MEM_SIZE 0x101F -#define CL_DEVICE_MAX_CONSTANT_BUFFER_SIZE 0x1020 -#define CL_DEVICE_MAX_CONSTANT_ARGS 0x1021 -#define CL_DEVICE_LOCAL_MEM_TYPE 0x1022 -#define CL_DEVICE_LOCAL_MEM_SIZE 0x1023 -#define CL_DEVICE_ERROR_CORRECTION_SUPPORT 0x1024 -#define CL_DEVICE_PROFILING_TIMER_RESOLUTION 0x1025 -#define CL_DEVICE_ENDIAN_LITTLE 0x1026 -#define CL_DEVICE_AVAILABLE 0x1027 -#define CL_DEVICE_COMPILER_AVAILABLE 0x1028 -#define CL_DEVICE_EXECUTION_CAPABILITIES 0x1029 -#define CL_DEVICE_QUEUE_PROPERTIES 0x102A /* deprecated */ -#define CL_DEVICE_QUEUE_ON_HOST_PROPERTIES 0x102A -#define CL_DEVICE_NAME 0x102B -#define CL_DEVICE_VENDOR 0x102C -#define CL_DRIVER_VERSION 0x102D -#define CL_DEVICE_PROFILE 0x102E -#define CL_DEVICE_VERSION 0x102F -#define CL_DEVICE_EXTENSIONS 0x1030 -#define CL_DEVICE_PLATFORM 0x1031 -#define CL_DEVICE_DOUBLE_FP_CONFIG 0x1032 -/* 0x1033 reserved for CL_DEVICE_HALF_FP_CONFIG */ -#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_HALF 0x1034 -#define CL_DEVICE_HOST_UNIFIED_MEMORY 0x1035 /* deprecated */ -#define CL_DEVICE_NATIVE_VECTOR_WIDTH_CHAR 0x1036 -#define CL_DEVICE_NATIVE_VECTOR_WIDTH_SHORT 0x1037 -#define CL_DEVICE_NATIVE_VECTOR_WIDTH_INT 0x1038 -#define CL_DEVICE_NATIVE_VECTOR_WIDTH_LONG 0x1039 -#define CL_DEVICE_NATIVE_VECTOR_WIDTH_FLOAT 0x103A -#define CL_DEVICE_NATIVE_VECTOR_WIDTH_DOUBLE 0x103B -#define CL_DEVICE_NATIVE_VECTOR_WIDTH_HALF 0x103C -#define CL_DEVICE_OPENCL_C_VERSION 0x103D -#define CL_DEVICE_LINKER_AVAILABLE 0x103E -#define CL_DEVICE_BUILT_IN_KERNELS 0x103F -#define CL_DEVICE_IMAGE_MAX_BUFFER_SIZE 0x1040 -#define CL_DEVICE_IMAGE_MAX_ARRAY_SIZE 0x1041 -#define CL_DEVICE_PARENT_DEVICE 0x1042 -#define CL_DEVICE_PARTITION_MAX_SUB_DEVICES 0x1043 -#define CL_DEVICE_PARTITION_PROPERTIES 0x1044 -#define CL_DEVICE_PARTITION_AFFINITY_DOMAIN 0x1045 -#define CL_DEVICE_PARTITION_TYPE 0x1046 -#define CL_DEVICE_REFERENCE_COUNT 0x1047 -#define CL_DEVICE_PREFERRED_INTEROP_USER_SYNC 0x1048 -#define CL_DEVICE_PRINTF_BUFFER_SIZE 0x1049 -#define CL_DEVICE_IMAGE_PITCH_ALIGNMENT 0x104A -#define CL_DEVICE_IMAGE_BASE_ADDRESS_ALIGNMENT 0x104B -#define CL_DEVICE_MAX_READ_WRITE_IMAGE_ARGS 0x104C -#define CL_DEVICE_MAX_GLOBAL_VARIABLE_SIZE 0x104D -#define CL_DEVICE_QUEUE_ON_DEVICE_PROPERTIES 0x104E -#define CL_DEVICE_QUEUE_ON_DEVICE_PREFERRED_SIZE 0x104F -#define CL_DEVICE_QUEUE_ON_DEVICE_MAX_SIZE 0x1050 -#define CL_DEVICE_MAX_ON_DEVICE_QUEUES 0x1051 -#define CL_DEVICE_MAX_ON_DEVICE_EVENTS 0x1052 -#define CL_DEVICE_SVM_CAPABILITIES 0x1053 -#define CL_DEVICE_GLOBAL_VARIABLE_PREFERRED_TOTAL_SIZE 0x1054 -#define CL_DEVICE_MAX_PIPE_ARGS 0x1055 -#define CL_DEVICE_PIPE_MAX_ACTIVE_RESERVATIONS 0x1056 -#define CL_DEVICE_PIPE_MAX_PACKET_SIZE 0x1057 -#define CL_DEVICE_PREFERRED_PLATFORM_ATOMIC_ALIGNMENT 0x1058 -#define CL_DEVICE_PREFERRED_GLOBAL_ATOMIC_ALIGNMENT 0x1059 -#define CL_DEVICE_PREFERRED_LOCAL_ATOMIC_ALIGNMENT 0x105A -#define CL_DEVICE_IL_VERSION 0x105B -#define CL_DEVICE_MAX_NUM_SUB_GROUPS 0x105C -#define CL_DEVICE_SUB_GROUP_INDEPENDENT_FORWARD_PROGRESS 0x105D - -/* cl_device_fp_config - bitfield */ -#define CL_FP_DENORM (1 << 0) -#define CL_FP_INF_NAN (1 << 1) -#define CL_FP_ROUND_TO_NEAREST (1 << 2) -#define CL_FP_ROUND_TO_ZERO (1 << 3) -#define CL_FP_ROUND_TO_INF (1 << 4) -#define CL_FP_FMA (1 << 5) -#define CL_FP_SOFT_FLOAT (1 << 6) -#define CL_FP_CORRECTLY_ROUNDED_DIVIDE_SQRT (1 << 7) - -/* cl_device_mem_cache_type */ -#define CL_NONE 0x0 -#define CL_READ_ONLY_CACHE 0x1 -#define CL_READ_WRITE_CACHE 0x2 - -/* cl_device_local_mem_type */ -#define CL_LOCAL 0x1 -#define CL_GLOBAL 0x2 - -/* cl_device_exec_capabilities - bitfield */ -#define CL_EXEC_KERNEL (1 << 0) -#define CL_EXEC_NATIVE_KERNEL (1 << 1) - -/* cl_command_queue_properties - bitfield */ -#define CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE (1 << 0) -#define CL_QUEUE_PROFILING_ENABLE (1 << 1) -#define CL_QUEUE_ON_DEVICE (1 << 2) -#define CL_QUEUE_ON_DEVICE_DEFAULT (1 << 3) - -/* cl_context_info */ -#define CL_CONTEXT_REFERENCE_COUNT 0x1080 -#define CL_CONTEXT_DEVICES 0x1081 -#define CL_CONTEXT_PROPERTIES 0x1082 -#define CL_CONTEXT_NUM_DEVICES 0x1083 - -/* cl_context_properties */ -#define CL_CONTEXT_PLATFORM 0x1084 -#define CL_CONTEXT_INTEROP_USER_SYNC 0x1085 - -/* cl_device_partition_property */ -#define CL_DEVICE_PARTITION_EQUALLY 0x1086 -#define CL_DEVICE_PARTITION_BY_COUNTS 0x1087 -#define CL_DEVICE_PARTITION_BY_COUNTS_LIST_END 0x0 -#define CL_DEVICE_PARTITION_BY_AFFINITY_DOMAIN 0x1088 - -/* cl_device_affinity_domain */ -#define CL_DEVICE_AFFINITY_DOMAIN_NUMA (1 << 0) -#define CL_DEVICE_AFFINITY_DOMAIN_L4_CACHE (1 << 1) -#define CL_DEVICE_AFFINITY_DOMAIN_L3_CACHE (1 << 2) -#define CL_DEVICE_AFFINITY_DOMAIN_L2_CACHE (1 << 3) -#define CL_DEVICE_AFFINITY_DOMAIN_L1_CACHE (1 << 4) -#define CL_DEVICE_AFFINITY_DOMAIN_NEXT_PARTITIONABLE (1 << 5) - -/* cl_device_svm_capabilities */ -#define CL_DEVICE_SVM_COARSE_GRAIN_BUFFER (1 << 0) -#define CL_DEVICE_SVM_FINE_GRAIN_BUFFER (1 << 1) -#define CL_DEVICE_SVM_FINE_GRAIN_SYSTEM (1 << 2) -#define CL_DEVICE_SVM_ATOMICS (1 << 3) - -/* cl_command_queue_info */ -#define CL_QUEUE_CONTEXT 0x1090 -#define CL_QUEUE_DEVICE 0x1091 -#define CL_QUEUE_REFERENCE_COUNT 0x1092 -#define CL_QUEUE_PROPERTIES 0x1093 -#define CL_QUEUE_SIZE 0x1094 -#define CL_QUEUE_DEVICE_DEFAULT 0x1095 - -/* cl_mem_flags and cl_svm_mem_flags - bitfield */ -#define CL_MEM_READ_WRITE (1 << 0) -#define CL_MEM_WRITE_ONLY (1 << 1) -#define CL_MEM_READ_ONLY (1 << 2) -#define CL_MEM_USE_HOST_PTR (1 << 3) -#define CL_MEM_ALLOC_HOST_PTR (1 << 4) -#define CL_MEM_COPY_HOST_PTR (1 << 5) -/* reserved (1 << 6) */ -#define CL_MEM_HOST_WRITE_ONLY (1 << 7) -#define CL_MEM_HOST_READ_ONLY (1 << 8) -#define CL_MEM_HOST_NO_ACCESS (1 << 9) -#define CL_MEM_SVM_FINE_GRAIN_BUFFER (1 << 10) /* used by cl_svm_mem_flags only */ -#define CL_MEM_SVM_ATOMICS (1 << 11) /* used by cl_svm_mem_flags only */ -#define CL_MEM_KERNEL_READ_AND_WRITE (1 << 12) - -/* cl_mem_migration_flags - bitfield */ -#define CL_MIGRATE_MEM_OBJECT_HOST (1 << 0) -#define CL_MIGRATE_MEM_OBJECT_CONTENT_UNDEFINED (1 << 1) - -/* cl_channel_order */ -#define CL_R 0x10B0 -#define CL_A 0x10B1 -#define CL_RG 0x10B2 -#define CL_RA 0x10B3 -#define CL_RGB 0x10B4 -#define CL_RGBA 0x10B5 -#define CL_BGRA 0x10B6 -#define CL_ARGB 0x10B7 -#define CL_INTENSITY 0x10B8 -#define CL_LUMINANCE 0x10B9 -#define CL_Rx 0x10BA -#define CL_RGx 0x10BB -#define CL_RGBx 0x10BC -#define CL_DEPTH 0x10BD -#define CL_DEPTH_STENCIL 0x10BE -#define CL_sRGB 0x10BF -#define CL_sRGBx 0x10C0 -#define CL_sRGBA 0x10C1 -#define CL_sBGRA 0x10C2 -#define CL_ABGR 0x10C3 - -/* cl_channel_type */ -#define CL_SNORM_INT8 0x10D0 -#define CL_SNORM_INT16 0x10D1 -#define CL_UNORM_INT8 0x10D2 -#define CL_UNORM_INT16 0x10D3 -#define CL_UNORM_SHORT_565 0x10D4 -#define CL_UNORM_SHORT_555 0x10D5 -#define CL_UNORM_INT_101010 0x10D6 -#define CL_SIGNED_INT8 0x10D7 -#define CL_SIGNED_INT16 0x10D8 -#define CL_SIGNED_INT32 0x10D9 -#define CL_UNSIGNED_INT8 0x10DA -#define CL_UNSIGNED_INT16 0x10DB -#define CL_UNSIGNED_INT32 0x10DC -#define CL_HALF_FLOAT 0x10DD -#define CL_FLOAT 0x10DE -#define CL_UNORM_INT24 0x10DF -#define CL_UNORM_INT_101010_2 0x10E0 - -/* cl_mem_object_type */ -#define CL_MEM_OBJECT_BUFFER 0x10F0 -#define CL_MEM_OBJECT_IMAGE2D 0x10F1 -#define CL_MEM_OBJECT_IMAGE3D 0x10F2 -#define CL_MEM_OBJECT_IMAGE2D_ARRAY 0x10F3 -#define CL_MEM_OBJECT_IMAGE1D 0x10F4 -#define CL_MEM_OBJECT_IMAGE1D_ARRAY 0x10F5 -#define CL_MEM_OBJECT_IMAGE1D_BUFFER 0x10F6 -#define CL_MEM_OBJECT_PIPE 0x10F7 - -/* cl_mem_info */ -#define CL_MEM_TYPE 0x1100 -#define CL_MEM_FLAGS 0x1101 -#define CL_MEM_SIZE 0x1102 -#define CL_MEM_HOST_PTR 0x1103 -#define CL_MEM_MAP_COUNT 0x1104 -#define CL_MEM_REFERENCE_COUNT 0x1105 -#define CL_MEM_CONTEXT 0x1106 -#define CL_MEM_ASSOCIATED_MEMOBJECT 0x1107 -#define CL_MEM_OFFSET 0x1108 -#define CL_MEM_USES_SVM_POINTER 0x1109 - -/* cl_image_info */ -#define CL_IMAGE_FORMAT 0x1110 -#define CL_IMAGE_ELEMENT_SIZE 0x1111 -#define CL_IMAGE_ROW_PITCH 0x1112 -#define CL_IMAGE_SLICE_PITCH 0x1113 -#define CL_IMAGE_WIDTH 0x1114 -#define CL_IMAGE_HEIGHT 0x1115 -#define CL_IMAGE_DEPTH 0x1116 -#define CL_IMAGE_ARRAY_SIZE 0x1117 -#define CL_IMAGE_BUFFER 0x1118 -#define CL_IMAGE_NUM_MIP_LEVELS 0x1119 -#define CL_IMAGE_NUM_SAMPLES 0x111A - -/* cl_pipe_info */ -#define CL_PIPE_PACKET_SIZE 0x1120 -#define CL_PIPE_MAX_PACKETS 0x1121 - -/* cl_addressing_mode */ -#define CL_ADDRESS_NONE 0x1130 -#define CL_ADDRESS_CLAMP_TO_EDGE 0x1131 -#define CL_ADDRESS_CLAMP 0x1132 -#define CL_ADDRESS_REPEAT 0x1133 -#define CL_ADDRESS_MIRRORED_REPEAT 0x1134 - -/* cl_filter_mode */ -#define CL_FILTER_NEAREST 0x1140 -#define CL_FILTER_LINEAR 0x1141 - -/* cl_sampler_info */ -#define CL_SAMPLER_REFERENCE_COUNT 0x1150 -#define CL_SAMPLER_CONTEXT 0x1151 -#define CL_SAMPLER_NORMALIZED_COORDS 0x1152 -#define CL_SAMPLER_ADDRESSING_MODE 0x1153 -#define CL_SAMPLER_FILTER_MODE 0x1154 -#define CL_SAMPLER_MIP_FILTER_MODE 0x1155 -#define CL_SAMPLER_LOD_MIN 0x1156 -#define CL_SAMPLER_LOD_MAX 0x1157 - -/* cl_map_flags - bitfield */ -#define CL_MAP_READ (1 << 0) -#define CL_MAP_WRITE (1 << 1) -#define CL_MAP_WRITE_INVALIDATE_REGION (1 << 2) - -/* cl_program_info */ -#define CL_PROGRAM_REFERENCE_COUNT 0x1160 -#define CL_PROGRAM_CONTEXT 0x1161 -#define CL_PROGRAM_NUM_DEVICES 0x1162 -#define CL_PROGRAM_DEVICES 0x1163 -#define CL_PROGRAM_SOURCE 0x1164 -#define CL_PROGRAM_BINARY_SIZES 0x1165 -#define CL_PROGRAM_BINARIES 0x1166 -#define CL_PROGRAM_NUM_KERNELS 0x1167 -#define CL_PROGRAM_KERNEL_NAMES 0x1168 -#define CL_PROGRAM_IL 0x1169 - -/* cl_program_build_info */ -#define CL_PROGRAM_BUILD_STATUS 0x1181 -#define CL_PROGRAM_BUILD_OPTIONS 0x1182 -#define CL_PROGRAM_BUILD_LOG 0x1183 -#define CL_PROGRAM_BINARY_TYPE 0x1184 -#define CL_PROGRAM_BUILD_GLOBAL_VARIABLE_TOTAL_SIZE 0x1185 - -/* cl_program_binary_type */ -#define CL_PROGRAM_BINARY_TYPE_NONE 0x0 -#define CL_PROGRAM_BINARY_TYPE_COMPILED_OBJECT 0x1 -#define CL_PROGRAM_BINARY_TYPE_LIBRARY 0x2 -#define CL_PROGRAM_BINARY_TYPE_EXECUTABLE 0x4 - -/* cl_build_status */ -#define CL_BUILD_SUCCESS 0 -#define CL_BUILD_NONE -1 -#define CL_BUILD_ERROR -2 -#define CL_BUILD_IN_PROGRESS -3 - -/* cl_kernel_info */ -#define CL_KERNEL_FUNCTION_NAME 0x1190 -#define CL_KERNEL_NUM_ARGS 0x1191 -#define CL_KERNEL_REFERENCE_COUNT 0x1192 -#define CL_KERNEL_CONTEXT 0x1193 -#define CL_KERNEL_PROGRAM 0x1194 -#define CL_KERNEL_ATTRIBUTES 0x1195 -#define CL_KERNEL_MAX_NUM_SUB_GROUPS 0x11B9 -#define CL_KERNEL_COMPILE_NUM_SUB_GROUPS 0x11BA - -/* cl_kernel_arg_info */ -#define CL_KERNEL_ARG_ADDRESS_QUALIFIER 0x1196 -#define CL_KERNEL_ARG_ACCESS_QUALIFIER 0x1197 -#define CL_KERNEL_ARG_TYPE_NAME 0x1198 -#define CL_KERNEL_ARG_TYPE_QUALIFIER 0x1199 -#define CL_KERNEL_ARG_NAME 0x119A - -/* cl_kernel_arg_address_qualifier */ -#define CL_KERNEL_ARG_ADDRESS_GLOBAL 0x119B -#define CL_KERNEL_ARG_ADDRESS_LOCAL 0x119C -#define CL_KERNEL_ARG_ADDRESS_CONSTANT 0x119D -#define CL_KERNEL_ARG_ADDRESS_PRIVATE 0x119E - -/* cl_kernel_arg_access_qualifier */ -#define CL_KERNEL_ARG_ACCESS_READ_ONLY 0x11A0 -#define CL_KERNEL_ARG_ACCESS_WRITE_ONLY 0x11A1 -#define CL_KERNEL_ARG_ACCESS_READ_WRITE 0x11A2 -#define CL_KERNEL_ARG_ACCESS_NONE 0x11A3 - -/* cl_kernel_arg_type_qualifer */ -#define CL_KERNEL_ARG_TYPE_NONE 0 -#define CL_KERNEL_ARG_TYPE_CONST (1 << 0) -#define CL_KERNEL_ARG_TYPE_RESTRICT (1 << 1) -#define CL_KERNEL_ARG_TYPE_VOLATILE (1 << 2) -#define CL_KERNEL_ARG_TYPE_PIPE (1 << 3) - -/* cl_kernel_work_group_info */ -#define CL_KERNEL_WORK_GROUP_SIZE 0x11B0 -#define CL_KERNEL_COMPILE_WORK_GROUP_SIZE 0x11B1 -#define CL_KERNEL_LOCAL_MEM_SIZE 0x11B2 -#define CL_KERNEL_PREFERRED_WORK_GROUP_SIZE_MULTIPLE 0x11B3 -#define CL_KERNEL_PRIVATE_MEM_SIZE 0x11B4 -#define CL_KERNEL_GLOBAL_WORK_SIZE 0x11B5 - -/* cl_kernel_sub_group_info */ -#define CL_KERNEL_MAX_SUB_GROUP_SIZE_FOR_NDRANGE 0x2033 -#define CL_KERNEL_SUB_GROUP_COUNT_FOR_NDRANGE 0x2034 -#define CL_KERNEL_LOCAL_SIZE_FOR_SUB_GROUP_COUNT 0x11B8 - -/* cl_kernel_exec_info */ -#define CL_KERNEL_EXEC_INFO_SVM_PTRS 0x11B6 -#define CL_KERNEL_EXEC_INFO_SVM_FINE_GRAIN_SYSTEM 0x11B7 - -/* cl_event_info */ -#define CL_EVENT_COMMAND_QUEUE 0x11D0 -#define CL_EVENT_COMMAND_TYPE 0x11D1 -#define CL_EVENT_REFERENCE_COUNT 0x11D2 -#define CL_EVENT_COMMAND_EXECUTION_STATUS 0x11D3 -#define CL_EVENT_CONTEXT 0x11D4 - -/* cl_command_type */ -#define CL_COMMAND_NDRANGE_KERNEL 0x11F0 -#define CL_COMMAND_TASK 0x11F1 -#define CL_COMMAND_NATIVE_KERNEL 0x11F2 -#define CL_COMMAND_READ_BUFFER 0x11F3 -#define CL_COMMAND_WRITE_BUFFER 0x11F4 -#define CL_COMMAND_COPY_BUFFER 0x11F5 -#define CL_COMMAND_READ_IMAGE 0x11F6 -#define CL_COMMAND_WRITE_IMAGE 0x11F7 -#define CL_COMMAND_COPY_IMAGE 0x11F8 -#define CL_COMMAND_COPY_IMAGE_TO_BUFFER 0x11F9 -#define CL_COMMAND_COPY_BUFFER_TO_IMAGE 0x11FA -#define CL_COMMAND_MAP_BUFFER 0x11FB -#define CL_COMMAND_MAP_IMAGE 0x11FC -#define CL_COMMAND_UNMAP_MEM_OBJECT 0x11FD -#define CL_COMMAND_MARKER 0x11FE -#define CL_COMMAND_ACQUIRE_GL_OBJECTS 0x11FF -#define CL_COMMAND_RELEASE_GL_OBJECTS 0x1200 -#define CL_COMMAND_READ_BUFFER_RECT 0x1201 -#define CL_COMMAND_WRITE_BUFFER_RECT 0x1202 -#define CL_COMMAND_COPY_BUFFER_RECT 0x1203 -#define CL_COMMAND_USER 0x1204 -#define CL_COMMAND_BARRIER 0x1205 -#define CL_COMMAND_MIGRATE_MEM_OBJECTS 0x1206 -#define CL_COMMAND_FILL_BUFFER 0x1207 -#define CL_COMMAND_FILL_IMAGE 0x1208 -#define CL_COMMAND_SVM_FREE 0x1209 -#define CL_COMMAND_SVM_MEMCPY 0x120A -#define CL_COMMAND_SVM_MEMFILL 0x120B -#define CL_COMMAND_SVM_MAP 0x120C -#define CL_COMMAND_SVM_UNMAP 0x120D - -/* command execution status */ -#define CL_COMPLETE 0x0 -#define CL_RUNNING 0x1 -#define CL_SUBMITTED 0x2 -#define CL_QUEUED 0x3 - -/* cl_buffer_create_type */ -#define CL_BUFFER_CREATE_TYPE_REGION 0x1220 - -/* cl_profiling_info */ -#define CL_PROFILING_COMMAND_QUEUED 0x1280 -#define CL_PROFILING_COMMAND_SUBMIT 0x1281 -#define CL_PROFILING_COMMAND_START 0x1282 -#define CL_PROFILING_COMMAND_END 0x1283 -#define CL_PROFILING_COMMAND_COMPLETE 0x1284 - -/********************************************************************************************************/ - -/* Platform API */ -extern CL_API_ENTRY cl_int CL_API_CALL -clGetPlatformIDs(cl_uint /* num_entries */, - cl_platform_id * /* platforms */, - cl_uint * /* num_platforms */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetPlatformInfo(cl_platform_id /* platform */, - cl_platform_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -/* Device APIs */ -extern CL_API_ENTRY cl_int CL_API_CALL -clGetDeviceIDs(cl_platform_id /* platform */, - cl_device_type /* device_type */, - cl_uint /* num_entries */, - cl_device_id * /* devices */, - cl_uint * /* num_devices */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetDeviceInfo(cl_device_id /* device */, - cl_device_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clCreateSubDevices(cl_device_id /* in_device */, - const cl_device_partition_property * /* properties */, - cl_uint /* num_devices */, - cl_device_id * /* out_devices */, - cl_uint * /* num_devices_ret */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clRetainDevice(cl_device_id /* device */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clReleaseDevice(cl_device_id /* device */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clSetDefaultDeviceCommandQueue(cl_context /* context */, - cl_device_id /* device */, - cl_command_queue /* command_queue */) CL_API_SUFFIX__VERSION_2_1; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetDeviceAndHostTimer(cl_device_id /* device */, - cl_ulong* /* device_timestamp */, - cl_ulong* /* host_timestamp */) CL_API_SUFFIX__VERSION_2_1; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetHostTimer(cl_device_id /* device */, - cl_ulong * /* host_timestamp */) CL_API_SUFFIX__VERSION_2_1; - - -/* Context APIs */ -extern CL_API_ENTRY cl_context CL_API_CALL -clCreateContext(const cl_context_properties * /* properties */, - cl_uint /* num_devices */, - const cl_device_id * /* devices */, - void (CL_CALLBACK * /* pfn_notify */)(const char *, const void *, size_t, void *), - void * /* user_data */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_context CL_API_CALL -clCreateContextFromType(const cl_context_properties * /* properties */, - cl_device_type /* device_type */, - void (CL_CALLBACK * /* pfn_notify*/ )(const char *, const void *, size_t, void *), - void * /* user_data */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clRetainContext(cl_context /* context */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clReleaseContext(cl_context /* context */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetContextInfo(cl_context /* context */, - cl_context_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -/* Command Queue APIs */ -extern CL_API_ENTRY cl_command_queue CL_API_CALL -clCreateCommandQueueWithProperties(cl_context /* context */, - cl_device_id /* device */, - const cl_queue_properties * /* properties */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clRetainCommandQueue(cl_command_queue /* command_queue */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clReleaseCommandQueue(cl_command_queue /* command_queue */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetCommandQueueInfo(cl_command_queue /* command_queue */, - cl_command_queue_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -/* Memory Object APIs */ -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreateBuffer(cl_context /* context */, - cl_mem_flags /* flags */, - size_t /* size */, - void * /* host_ptr */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreateSubBuffer(cl_mem /* buffer */, - cl_mem_flags /* flags */, - cl_buffer_create_type /* buffer_create_type */, - const void * /* buffer_create_info */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_1; - -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreateImage(cl_context /* context */, - cl_mem_flags /* flags */, - const cl_image_format * /* image_format */, - const cl_image_desc * /* image_desc */, - void * /* host_ptr */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreatePipe(cl_context /* context */, - cl_mem_flags /* flags */, - cl_uint /* pipe_packet_size */, - cl_uint /* pipe_max_packets */, - const cl_pipe_properties * /* properties */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clRetainMemObject(cl_mem /* memobj */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clReleaseMemObject(cl_mem /* memobj */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetSupportedImageFormats(cl_context /* context */, - cl_mem_flags /* flags */, - cl_mem_object_type /* image_type */, - cl_uint /* num_entries */, - cl_image_format * /* image_formats */, - cl_uint * /* num_image_formats */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetMemObjectInfo(cl_mem /* memobj */, - cl_mem_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetImageInfo(cl_mem /* image */, - cl_image_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetPipeInfo(cl_mem /* pipe */, - cl_pipe_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_2_0; - - -extern CL_API_ENTRY cl_int CL_API_CALL -clSetMemObjectDestructorCallback(cl_mem /* memobj */, - void (CL_CALLBACK * /*pfn_notify*/)( cl_mem /* memobj */, void* /*user_data*/), - void * /*user_data */ ) CL_API_SUFFIX__VERSION_1_1; - -/* SVM Allocation APIs */ -extern CL_API_ENTRY void * CL_API_CALL -clSVMAlloc(cl_context /* context */, - cl_svm_mem_flags /* flags */, - size_t /* size */, - cl_uint /* alignment */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY void CL_API_CALL -clSVMFree(cl_context /* context */, - void * /* svm_pointer */) CL_API_SUFFIX__VERSION_2_0; - -/* Sampler APIs */ -extern CL_API_ENTRY cl_sampler CL_API_CALL -clCreateSamplerWithProperties(cl_context /* context */, - const cl_sampler_properties * /* normalized_coords */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clRetainSampler(cl_sampler /* sampler */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clReleaseSampler(cl_sampler /* sampler */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetSamplerInfo(cl_sampler /* sampler */, - cl_sampler_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -/* Program Object APIs */ -extern CL_API_ENTRY cl_program CL_API_CALL -clCreateProgramWithSource(cl_context /* context */, - cl_uint /* count */, - const char ** /* strings */, - const size_t * /* lengths */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_program CL_API_CALL -clCreateProgramWithBinary(cl_context /* context */, - cl_uint /* num_devices */, - const cl_device_id * /* device_list */, - const size_t * /* lengths */, - const unsigned char ** /* binaries */, - cl_int * /* binary_status */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_program CL_API_CALL -clCreateProgramWithBuiltInKernels(cl_context /* context */, - cl_uint /* num_devices */, - const cl_device_id * /* device_list */, - const char * /* kernel_names */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_program CL_API_CALL -clCreateProgramWithIL(cl_context /* context */, - const void* /* il */, - size_t /* length */, - cl_int* /* errcode_ret */) CL_API_SUFFIX__VERSION_2_1; - - -extern CL_API_ENTRY cl_int CL_API_CALL -clRetainProgram(cl_program /* program */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clReleaseProgram(cl_program /* program */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clBuildProgram(cl_program /* program */, - cl_uint /* num_devices */, - const cl_device_id * /* device_list */, - const char * /* options */, - void (CL_CALLBACK * /* pfn_notify */)(cl_program /* program */, void * /* user_data */), - void * /* user_data */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clCompileProgram(cl_program /* program */, - cl_uint /* num_devices */, - const cl_device_id * /* device_list */, - const char * /* options */, - cl_uint /* num_input_headers */, - const cl_program * /* input_headers */, - const char ** /* header_include_names */, - void (CL_CALLBACK * /* pfn_notify */)(cl_program /* program */, void * /* user_data */), - void * /* user_data */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_program CL_API_CALL -clLinkProgram(cl_context /* context */, - cl_uint /* num_devices */, - const cl_device_id * /* device_list */, - const char * /* options */, - cl_uint /* num_input_programs */, - const cl_program * /* input_programs */, - void (CL_CALLBACK * /* pfn_notify */)(cl_program /* program */, void * /* user_data */), - void * /* user_data */, - cl_int * /* errcode_ret */ ) CL_API_SUFFIX__VERSION_1_2; - - -extern CL_API_ENTRY cl_int CL_API_CALL -clUnloadPlatformCompiler(cl_platform_id /* platform */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetProgramInfo(cl_program /* program */, - cl_program_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetProgramBuildInfo(cl_program /* program */, - cl_device_id /* device */, - cl_program_build_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -/* Kernel Object APIs */ -extern CL_API_ENTRY cl_kernel CL_API_CALL -clCreateKernel(cl_program /* program */, - const char * /* kernel_name */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clCreateKernelsInProgram(cl_program /* program */, - cl_uint /* num_kernels */, - cl_kernel * /* kernels */, - cl_uint * /* num_kernels_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_kernel CL_API_CALL -clCloneKernel(cl_kernel /* source_kernel */, - cl_int* /* errcode_ret */) CL_API_SUFFIX__VERSION_2_1; - -extern CL_API_ENTRY cl_int CL_API_CALL -clRetainKernel(cl_kernel /* kernel */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clReleaseKernel(cl_kernel /* kernel */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clSetKernelArg(cl_kernel /* kernel */, - cl_uint /* arg_index */, - size_t /* arg_size */, - const void * /* arg_value */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clSetKernelArgSVMPointer(cl_kernel /* kernel */, - cl_uint /* arg_index */, - const void * /* arg_value */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clSetKernelExecInfo(cl_kernel /* kernel */, - cl_kernel_exec_info /* param_name */, - size_t /* param_value_size */, - const void * /* param_value */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetKernelInfo(cl_kernel /* kernel */, - cl_kernel_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetKernelArgInfo(cl_kernel /* kernel */, - cl_uint /* arg_indx */, - cl_kernel_arg_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetKernelWorkGroupInfo(cl_kernel /* kernel */, - cl_device_id /* device */, - cl_kernel_work_group_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetKernelSubGroupInfo(cl_kernel /* kernel */, - cl_device_id /* device */, - cl_kernel_sub_group_info /* param_name */, - size_t /* input_value_size */, - const void* /*input_value */, - size_t /* param_value_size */, - void* /* param_value */, - size_t* /* param_value_size_ret */ ) CL_API_SUFFIX__VERSION_2_1; - - -/* Event Object APIs */ -extern CL_API_ENTRY cl_int CL_API_CALL -clWaitForEvents(cl_uint /* num_events */, - const cl_event * /* event_list */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetEventInfo(cl_event /* event */, - cl_event_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_event CL_API_CALL -clCreateUserEvent(cl_context /* context */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_1; - -extern CL_API_ENTRY cl_int CL_API_CALL -clRetainEvent(cl_event /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clReleaseEvent(cl_event /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clSetUserEventStatus(cl_event /* event */, - cl_int /* execution_status */) CL_API_SUFFIX__VERSION_1_1; - -extern CL_API_ENTRY cl_int CL_API_CALL -clSetEventCallback( cl_event /* event */, - cl_int /* command_exec_callback_type */, - void (CL_CALLBACK * /* pfn_notify */)(cl_event, cl_int, void *), - void * /* user_data */) CL_API_SUFFIX__VERSION_1_1; - -/* Profiling APIs */ -extern CL_API_ENTRY cl_int CL_API_CALL -clGetEventProfilingInfo(cl_event /* event */, - cl_profiling_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -/* Flush and Finish APIs */ -extern CL_API_ENTRY cl_int CL_API_CALL -clFlush(cl_command_queue /* command_queue */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clFinish(cl_command_queue /* command_queue */) CL_API_SUFFIX__VERSION_1_0; - -/* Enqueued Commands APIs */ -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueReadBuffer(cl_command_queue /* command_queue */, - cl_mem /* buffer */, - cl_bool /* blocking_read */, - size_t /* offset */, - size_t /* size */, - void * /* ptr */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueReadBufferRect(cl_command_queue /* command_queue */, - cl_mem /* buffer */, - cl_bool /* blocking_read */, - const size_t * /* buffer_offset */, - const size_t * /* host_offset */, - const size_t * /* region */, - size_t /* buffer_row_pitch */, - size_t /* buffer_slice_pitch */, - size_t /* host_row_pitch */, - size_t /* host_slice_pitch */, - void * /* ptr */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_1; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueWriteBuffer(cl_command_queue /* command_queue */, - cl_mem /* buffer */, - cl_bool /* blocking_write */, - size_t /* offset */, - size_t /* size */, - const void * /* ptr */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueWriteBufferRect(cl_command_queue /* command_queue */, - cl_mem /* buffer */, - cl_bool /* blocking_write */, - const size_t * /* buffer_offset */, - const size_t * /* host_offset */, - const size_t * /* region */, - size_t /* buffer_row_pitch */, - size_t /* buffer_slice_pitch */, - size_t /* host_row_pitch */, - size_t /* host_slice_pitch */, - const void * /* ptr */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_1; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueFillBuffer(cl_command_queue /* command_queue */, - cl_mem /* buffer */, - const void * /* pattern */, - size_t /* pattern_size */, - size_t /* offset */, - size_t /* size */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueCopyBuffer(cl_command_queue /* command_queue */, - cl_mem /* src_buffer */, - cl_mem /* dst_buffer */, - size_t /* src_offset */, - size_t /* dst_offset */, - size_t /* size */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueCopyBufferRect(cl_command_queue /* command_queue */, - cl_mem /* src_buffer */, - cl_mem /* dst_buffer */, - const size_t * /* src_origin */, - const size_t * /* dst_origin */, - const size_t * /* region */, - size_t /* src_row_pitch */, - size_t /* src_slice_pitch */, - size_t /* dst_row_pitch */, - size_t /* dst_slice_pitch */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_1; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueReadImage(cl_command_queue /* command_queue */, - cl_mem /* image */, - cl_bool /* blocking_read */, - const size_t * /* origin[3] */, - const size_t * /* region[3] */, - size_t /* row_pitch */, - size_t /* slice_pitch */, - void * /* ptr */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueWriteImage(cl_command_queue /* command_queue */, - cl_mem /* image */, - cl_bool /* blocking_write */, - const size_t * /* origin[3] */, - const size_t * /* region[3] */, - size_t /* input_row_pitch */, - size_t /* input_slice_pitch */, - const void * /* ptr */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueFillImage(cl_command_queue /* command_queue */, - cl_mem /* image */, - const void * /* fill_color */, - const size_t * /* origin[3] */, - const size_t * /* region[3] */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueCopyImage(cl_command_queue /* command_queue */, - cl_mem /* src_image */, - cl_mem /* dst_image */, - const size_t * /* src_origin[3] */, - const size_t * /* dst_origin[3] */, - const size_t * /* region[3] */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueCopyImageToBuffer(cl_command_queue /* command_queue */, - cl_mem /* src_image */, - cl_mem /* dst_buffer */, - const size_t * /* src_origin[3] */, - const size_t * /* region[3] */, - size_t /* dst_offset */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueCopyBufferToImage(cl_command_queue /* command_queue */, - cl_mem /* src_buffer */, - cl_mem /* dst_image */, - size_t /* src_offset */, - const size_t * /* dst_origin[3] */, - const size_t * /* region[3] */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY void * CL_API_CALL -clEnqueueMapBuffer(cl_command_queue /* command_queue */, - cl_mem /* buffer */, - cl_bool /* blocking_map */, - cl_map_flags /* map_flags */, - size_t /* offset */, - size_t /* size */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY void * CL_API_CALL -clEnqueueMapImage(cl_command_queue /* command_queue */, - cl_mem /* image */, - cl_bool /* blocking_map */, - cl_map_flags /* map_flags */, - const size_t * /* origin[3] */, - const size_t * /* region[3] */, - size_t * /* image_row_pitch */, - size_t * /* image_slice_pitch */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueUnmapMemObject(cl_command_queue /* command_queue */, - cl_mem /* memobj */, - void * /* mapped_ptr */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueMigrateMemObjects(cl_command_queue /* command_queue */, - cl_uint /* num_mem_objects */, - const cl_mem * /* mem_objects */, - cl_mem_migration_flags /* flags */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueNDRangeKernel(cl_command_queue /* command_queue */, - cl_kernel /* kernel */, - cl_uint /* work_dim */, - const size_t * /* global_work_offset */, - const size_t * /* global_work_size */, - const size_t * /* local_work_size */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueNativeKernel(cl_command_queue /* command_queue */, - void (CL_CALLBACK * /*user_func*/)(void *), - void * /* args */, - size_t /* cb_args */, - cl_uint /* num_mem_objects */, - const cl_mem * /* mem_list */, - const void ** /* args_mem_loc */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueMarkerWithWaitList(cl_command_queue /* command_queue */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueBarrierWithWaitList(cl_command_queue /* command_queue */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueSVMFree(cl_command_queue /* command_queue */, - cl_uint /* num_svm_pointers */, - void *[] /* svm_pointers[] */, - void (CL_CALLBACK * /*pfn_free_func*/)(cl_command_queue /* queue */, - cl_uint /* num_svm_pointers */, - void *[] /* svm_pointers[] */, - void * /* user_data */), - void * /* user_data */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueSVMMemcpy(cl_command_queue /* command_queue */, - cl_bool /* blocking_copy */, - void * /* dst_ptr */, - const void * /* src_ptr */, - size_t /* size */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueSVMMemFill(cl_command_queue /* command_queue */, - void * /* svm_ptr */, - const void * /* pattern */, - size_t /* pattern_size */, - size_t /* size */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueSVMMap(cl_command_queue /* command_queue */, - cl_bool /* blocking_map */, - cl_map_flags /* flags */, - void * /* svm_ptr */, - size_t /* size */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueSVMUnmap(cl_command_queue /* command_queue */, - void * /* svm_ptr */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueSVMMigrateMem(cl_command_queue /* command_queue */, - cl_uint /* num_svm_pointers */, - const void ** /* svm_pointers */, - const size_t * /* sizes */, - cl_mem_migration_flags /* flags */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_2_1; - - -/* Extension function access - * - * Returns the extension function address for the given function name, - * or NULL if a valid function can not be found. The client must - * check to make sure the address is not NULL, before using or - * calling the returned function address. - */ -extern CL_API_ENTRY void * CL_API_CALL -clGetExtensionFunctionAddressForPlatform(cl_platform_id /* platform */, - const char * /* func_name */) CL_API_SUFFIX__VERSION_1_2; - - -/* Deprecated OpenCL 1.1 APIs */ -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_mem CL_API_CALL -clCreateImage2D(cl_context /* context */, - cl_mem_flags /* flags */, - const cl_image_format * /* image_format */, - size_t /* image_width */, - size_t /* image_height */, - size_t /* image_row_pitch */, - void * /* host_ptr */, - cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; - -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_mem CL_API_CALL -clCreateImage3D(cl_context /* context */, - cl_mem_flags /* flags */, - const cl_image_format * /* image_format */, - size_t /* image_width */, - size_t /* image_height */, - size_t /* image_depth */, - size_t /* image_row_pitch */, - size_t /* image_slice_pitch */, - void * /* host_ptr */, - cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; - -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_int CL_API_CALL -clEnqueueMarker(cl_command_queue /* command_queue */, - cl_event * /* event */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; - -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_int CL_API_CALL -clEnqueueWaitForEvents(cl_command_queue /* command_queue */, - cl_uint /* num_events */, - const cl_event * /* event_list */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; - -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_int CL_API_CALL -clEnqueueBarrier(cl_command_queue /* command_queue */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; - -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_int CL_API_CALL -clUnloadCompiler(void) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; - -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED void * CL_API_CALL -clGetExtensionFunctionAddress(const char * /* func_name */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; - -/* Deprecated OpenCL 2.0 APIs */ -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_2_DEPRECATED cl_command_queue CL_API_CALL -clCreateCommandQueue(cl_context /* context */, - cl_device_id /* device */, - cl_command_queue_properties /* properties */, - cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED; - - -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_2_DEPRECATED cl_sampler CL_API_CALL -clCreateSampler(cl_context /* context */, - cl_bool /* normalized_coords */, - cl_addressing_mode /* addressing_mode */, - cl_filter_mode /* filter_mode */, - cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED; - -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_2_DEPRECATED cl_int CL_API_CALL -clEnqueueTask(cl_command_queue /* command_queue */, - cl_kernel /* kernel */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED; - -#ifdef __cplusplus -} -#endif - -#endif /* __OPENCL_CL_H */ - diff --git a/third_party/opencl/include/CL/cl_d3d10.h b/third_party/opencl/include/CL/cl_d3d10.h deleted file mode 100644 index d5960a43f..000000000 --- a/third_party/opencl/include/CL/cl_d3d10.h +++ /dev/null @@ -1,131 +0,0 @@ -/********************************************************************************** - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - **********************************************************************************/ - -/* $Revision: 11708 $ on $Date: 2010-06-13 23:36:24 -0700 (Sun, 13 Jun 2010) $ */ - -#ifndef __OPENCL_CL_D3D10_H -#define __OPENCL_CL_D3D10_H - -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/****************************************************************************** - * cl_khr_d3d10_sharing */ -#define cl_khr_d3d10_sharing 1 - -typedef cl_uint cl_d3d10_device_source_khr; -typedef cl_uint cl_d3d10_device_set_khr; - -/******************************************************************************/ - -/* Error Codes */ -#define CL_INVALID_D3D10_DEVICE_KHR -1002 -#define CL_INVALID_D3D10_RESOURCE_KHR -1003 -#define CL_D3D10_RESOURCE_ALREADY_ACQUIRED_KHR -1004 -#define CL_D3D10_RESOURCE_NOT_ACQUIRED_KHR -1005 - -/* cl_d3d10_device_source_nv */ -#define CL_D3D10_DEVICE_KHR 0x4010 -#define CL_D3D10_DXGI_ADAPTER_KHR 0x4011 - -/* cl_d3d10_device_set_nv */ -#define CL_PREFERRED_DEVICES_FOR_D3D10_KHR 0x4012 -#define CL_ALL_DEVICES_FOR_D3D10_KHR 0x4013 - -/* cl_context_info */ -#define CL_CONTEXT_D3D10_DEVICE_KHR 0x4014 -#define CL_CONTEXT_D3D10_PREFER_SHARED_RESOURCES_KHR 0x402C - -/* cl_mem_info */ -#define CL_MEM_D3D10_RESOURCE_KHR 0x4015 - -/* cl_image_info */ -#define CL_IMAGE_D3D10_SUBRESOURCE_KHR 0x4016 - -/* cl_command_type */ -#define CL_COMMAND_ACQUIRE_D3D10_OBJECTS_KHR 0x4017 -#define CL_COMMAND_RELEASE_D3D10_OBJECTS_KHR 0x4018 - -/******************************************************************************/ - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clGetDeviceIDsFromD3D10KHR_fn)( - cl_platform_id platform, - cl_d3d10_device_source_khr d3d_device_source, - void * d3d_object, - cl_d3d10_device_set_khr d3d_device_set, - cl_uint num_entries, - cl_device_id * devices, - cl_uint * num_devices) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D10BufferKHR_fn)( - cl_context context, - cl_mem_flags flags, - ID3D10Buffer * resource, - cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D10Texture2DKHR_fn)( - cl_context context, - cl_mem_flags flags, - ID3D10Texture2D * resource, - UINT subresource, - cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D10Texture3DKHR_fn)( - cl_context context, - cl_mem_flags flags, - ID3D10Texture3D * resource, - UINT subresource, - cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueAcquireD3D10ObjectsKHR_fn)( - cl_command_queue command_queue, - cl_uint num_objects, - const cl_mem * mem_objects, - cl_uint num_events_in_wait_list, - const cl_event * event_wait_list, - cl_event * event) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueReleaseD3D10ObjectsKHR_fn)( - cl_command_queue command_queue, - cl_uint num_objects, - const cl_mem * mem_objects, - cl_uint num_events_in_wait_list, - const cl_event * event_wait_list, - cl_event * event) CL_API_SUFFIX__VERSION_1_0; - -#ifdef __cplusplus -} -#endif - -#endif /* __OPENCL_CL_D3D10_H */ - diff --git a/third_party/opencl/include/CL/cl_d3d11.h b/third_party/opencl/include/CL/cl_d3d11.h deleted file mode 100644 index 39f907239..000000000 --- a/third_party/opencl/include/CL/cl_d3d11.h +++ /dev/null @@ -1,131 +0,0 @@ -/********************************************************************************** - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - **********************************************************************************/ - -/* $Revision: 11708 $ on $Date: 2010-06-13 23:36:24 -0700 (Sun, 13 Jun 2010) $ */ - -#ifndef __OPENCL_CL_D3D11_H -#define __OPENCL_CL_D3D11_H - -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/****************************************************************************** - * cl_khr_d3d11_sharing */ -#define cl_khr_d3d11_sharing 1 - -typedef cl_uint cl_d3d11_device_source_khr; -typedef cl_uint cl_d3d11_device_set_khr; - -/******************************************************************************/ - -/* Error Codes */ -#define CL_INVALID_D3D11_DEVICE_KHR -1006 -#define CL_INVALID_D3D11_RESOURCE_KHR -1007 -#define CL_D3D11_RESOURCE_ALREADY_ACQUIRED_KHR -1008 -#define CL_D3D11_RESOURCE_NOT_ACQUIRED_KHR -1009 - -/* cl_d3d11_device_source */ -#define CL_D3D11_DEVICE_KHR 0x4019 -#define CL_D3D11_DXGI_ADAPTER_KHR 0x401A - -/* cl_d3d11_device_set */ -#define CL_PREFERRED_DEVICES_FOR_D3D11_KHR 0x401B -#define CL_ALL_DEVICES_FOR_D3D11_KHR 0x401C - -/* cl_context_info */ -#define CL_CONTEXT_D3D11_DEVICE_KHR 0x401D -#define CL_CONTEXT_D3D11_PREFER_SHARED_RESOURCES_KHR 0x402D - -/* cl_mem_info */ -#define CL_MEM_D3D11_RESOURCE_KHR 0x401E - -/* cl_image_info */ -#define CL_IMAGE_D3D11_SUBRESOURCE_KHR 0x401F - -/* cl_command_type */ -#define CL_COMMAND_ACQUIRE_D3D11_OBJECTS_KHR 0x4020 -#define CL_COMMAND_RELEASE_D3D11_OBJECTS_KHR 0x4021 - -/******************************************************************************/ - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clGetDeviceIDsFromD3D11KHR_fn)( - cl_platform_id platform, - cl_d3d11_device_source_khr d3d_device_source, - void * d3d_object, - cl_d3d11_device_set_khr d3d_device_set, - cl_uint num_entries, - cl_device_id * devices, - cl_uint * num_devices) CL_API_SUFFIX__VERSION_1_2; - -typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D11BufferKHR_fn)( - cl_context context, - cl_mem_flags flags, - ID3D11Buffer * resource, - cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_2; - -typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D11Texture2DKHR_fn)( - cl_context context, - cl_mem_flags flags, - ID3D11Texture2D * resource, - UINT subresource, - cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_2; - -typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D11Texture3DKHR_fn)( - cl_context context, - cl_mem_flags flags, - ID3D11Texture3D * resource, - UINT subresource, - cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_2; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueAcquireD3D11ObjectsKHR_fn)( - cl_command_queue command_queue, - cl_uint num_objects, - const cl_mem * mem_objects, - cl_uint num_events_in_wait_list, - const cl_event * event_wait_list, - cl_event * event) CL_API_SUFFIX__VERSION_1_2; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueReleaseD3D11ObjectsKHR_fn)( - cl_command_queue command_queue, - cl_uint num_objects, - const cl_mem * mem_objects, - cl_uint num_events_in_wait_list, - const cl_event * event_wait_list, - cl_event * event) CL_API_SUFFIX__VERSION_1_2; - -#ifdef __cplusplus -} -#endif - -#endif /* __OPENCL_CL_D3D11_H */ - diff --git a/third_party/opencl/include/CL/cl_dx9_media_sharing.h b/third_party/opencl/include/CL/cl_dx9_media_sharing.h deleted file mode 100644 index 2729e8b9e..000000000 --- a/third_party/opencl/include/CL/cl_dx9_media_sharing.h +++ /dev/null @@ -1,132 +0,0 @@ -/********************************************************************************** - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - **********************************************************************************/ - -/* $Revision: 11708 $ on $Date: 2010-06-13 23:36:24 -0700 (Sun, 13 Jun 2010) $ */ - -#ifndef __OPENCL_CL_DX9_MEDIA_SHARING_H -#define __OPENCL_CL_DX9_MEDIA_SHARING_H - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/******************************************************************************/ -/* cl_khr_dx9_media_sharing */ -#define cl_khr_dx9_media_sharing 1 - -typedef cl_uint cl_dx9_media_adapter_type_khr; -typedef cl_uint cl_dx9_media_adapter_set_khr; - -#if defined(_WIN32) -#include -typedef struct _cl_dx9_surface_info_khr -{ - IDirect3DSurface9 *resource; - HANDLE shared_handle; -} cl_dx9_surface_info_khr; -#endif - - -/******************************************************************************/ - -/* Error Codes */ -#define CL_INVALID_DX9_MEDIA_ADAPTER_KHR -1010 -#define CL_INVALID_DX9_MEDIA_SURFACE_KHR -1011 -#define CL_DX9_MEDIA_SURFACE_ALREADY_ACQUIRED_KHR -1012 -#define CL_DX9_MEDIA_SURFACE_NOT_ACQUIRED_KHR -1013 - -/* cl_media_adapter_type_khr */ -#define CL_ADAPTER_D3D9_KHR 0x2020 -#define CL_ADAPTER_D3D9EX_KHR 0x2021 -#define CL_ADAPTER_DXVA_KHR 0x2022 - -/* cl_media_adapter_set_khr */ -#define CL_PREFERRED_DEVICES_FOR_DX9_MEDIA_ADAPTER_KHR 0x2023 -#define CL_ALL_DEVICES_FOR_DX9_MEDIA_ADAPTER_KHR 0x2024 - -/* cl_context_info */ -#define CL_CONTEXT_ADAPTER_D3D9_KHR 0x2025 -#define CL_CONTEXT_ADAPTER_D3D9EX_KHR 0x2026 -#define CL_CONTEXT_ADAPTER_DXVA_KHR 0x2027 - -/* cl_mem_info */ -#define CL_MEM_DX9_MEDIA_ADAPTER_TYPE_KHR 0x2028 -#define CL_MEM_DX9_MEDIA_SURFACE_INFO_KHR 0x2029 - -/* cl_image_info */ -#define CL_IMAGE_DX9_MEDIA_PLANE_KHR 0x202A - -/* cl_command_type */ -#define CL_COMMAND_ACQUIRE_DX9_MEDIA_SURFACES_KHR 0x202B -#define CL_COMMAND_RELEASE_DX9_MEDIA_SURFACES_KHR 0x202C - -/******************************************************************************/ - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clGetDeviceIDsFromDX9MediaAdapterKHR_fn)( - cl_platform_id platform, - cl_uint num_media_adapters, - cl_dx9_media_adapter_type_khr * media_adapter_type, - void * media_adapters, - cl_dx9_media_adapter_set_khr media_adapter_set, - cl_uint num_entries, - cl_device_id * devices, - cl_uint * num_devices) CL_API_SUFFIX__VERSION_1_2; - -typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromDX9MediaSurfaceKHR_fn)( - cl_context context, - cl_mem_flags flags, - cl_dx9_media_adapter_type_khr adapter_type, - void * surface_info, - cl_uint plane, - cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_2; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueAcquireDX9MediaSurfacesKHR_fn)( - cl_command_queue command_queue, - cl_uint num_objects, - const cl_mem * mem_objects, - cl_uint num_events_in_wait_list, - const cl_event * event_wait_list, - cl_event * event) CL_API_SUFFIX__VERSION_1_2; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueReleaseDX9MediaSurfacesKHR_fn)( - cl_command_queue command_queue, - cl_uint num_objects, - const cl_mem * mem_objects, - cl_uint num_events_in_wait_list, - const cl_event * event_wait_list, - cl_event * event) CL_API_SUFFIX__VERSION_1_2; - -#ifdef __cplusplus -} -#endif - -#endif /* __OPENCL_CL_DX9_MEDIA_SHARING_H */ - diff --git a/third_party/opencl/include/CL/cl_egl.h b/third_party/opencl/include/CL/cl_egl.h deleted file mode 100644 index a765bd526..000000000 --- a/third_party/opencl/include/CL/cl_egl.h +++ /dev/null @@ -1,136 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - ******************************************************************************/ - -#ifndef __OPENCL_CL_EGL_H -#define __OPENCL_CL_EGL_H - -#ifdef __APPLE__ - -#else -#include -#endif - -#ifdef __cplusplus -extern "C" { -#endif - - -/* Command type for events created with clEnqueueAcquireEGLObjectsKHR */ -#define CL_COMMAND_EGL_FENCE_SYNC_OBJECT_KHR 0x202F -#define CL_COMMAND_ACQUIRE_EGL_OBJECTS_KHR 0x202D -#define CL_COMMAND_RELEASE_EGL_OBJECTS_KHR 0x202E - -/* Error type for clCreateFromEGLImageKHR */ -#define CL_INVALID_EGL_OBJECT_KHR -1093 -#define CL_EGL_RESOURCE_NOT_ACQUIRED_KHR -1092 - -/* CLeglImageKHR is an opaque handle to an EGLImage */ -typedef void* CLeglImageKHR; - -/* CLeglDisplayKHR is an opaque handle to an EGLDisplay */ -typedef void* CLeglDisplayKHR; - -/* CLeglSyncKHR is an opaque handle to an EGLSync object */ -typedef void* CLeglSyncKHR; - -/* properties passed to clCreateFromEGLImageKHR */ -typedef intptr_t cl_egl_image_properties_khr; - - -#define cl_khr_egl_image 1 - -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreateFromEGLImageKHR(cl_context /* context */, - CLeglDisplayKHR /* egldisplay */, - CLeglImageKHR /* eglimage */, - cl_mem_flags /* flags */, - const cl_egl_image_properties_khr * /* properties */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromEGLImageKHR_fn)( - cl_context context, - CLeglDisplayKHR egldisplay, - CLeglImageKHR eglimage, - cl_mem_flags flags, - const cl_egl_image_properties_khr * properties, - cl_int * errcode_ret); - - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueAcquireEGLObjectsKHR(cl_command_queue /* command_queue */, - cl_uint /* num_objects */, - const cl_mem * /* mem_objects */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueAcquireEGLObjectsKHR_fn)( - cl_command_queue command_queue, - cl_uint num_objects, - const cl_mem * mem_objects, - cl_uint num_events_in_wait_list, - const cl_event * event_wait_list, - cl_event * event); - - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueReleaseEGLObjectsKHR(cl_command_queue /* command_queue */, - cl_uint /* num_objects */, - const cl_mem * /* mem_objects */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueReleaseEGLObjectsKHR_fn)( - cl_command_queue command_queue, - cl_uint num_objects, - const cl_mem * mem_objects, - cl_uint num_events_in_wait_list, - const cl_event * event_wait_list, - cl_event * event); - - -#define cl_khr_egl_event 1 - -extern CL_API_ENTRY cl_event CL_API_CALL -clCreateEventFromEGLSyncKHR(cl_context /* context */, - CLeglSyncKHR /* sync */, - CLeglDisplayKHR /* display */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_event (CL_API_CALL *clCreateEventFromEGLSyncKHR_fn)( - cl_context context, - CLeglSyncKHR sync, - CLeglDisplayKHR display, - cl_int * errcode_ret); - -#ifdef __cplusplus -} -#endif - -#endif /* __OPENCL_CL_EGL_H */ diff --git a/third_party/opencl/include/CL/cl_ext.h b/third_party/opencl/include/CL/cl_ext.h deleted file mode 100644 index 794158389..000000000 --- a/third_party/opencl/include/CL/cl_ext.h +++ /dev/null @@ -1,391 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - ******************************************************************************/ - -/* $Revision: 11928 $ on $Date: 2010-07-13 09:04:56 -0700 (Tue, 13 Jul 2010) $ */ - -/* cl_ext.h contains OpenCL extensions which don't have external */ -/* (OpenGL, D3D) dependencies. */ - -#ifndef __CL_EXT_H -#define __CL_EXT_H - -#ifdef __cplusplus -extern "C" { -#endif - -#ifdef __APPLE__ - #include - #include -#else - #include -#endif - -/* cl_khr_fp16 extension - no extension #define since it has no functions */ -#define CL_DEVICE_HALF_FP_CONFIG 0x1033 - -/* Memory object destruction - * - * Apple extension for use to manage externally allocated buffers used with cl_mem objects with CL_MEM_USE_HOST_PTR - * - * Registers a user callback function that will be called when the memory object is deleted and its resources - * freed. Each call to clSetMemObjectCallbackFn registers the specified user callback function on a callback - * stack associated with memobj. The registered user callback functions are called in the reverse order in - * which they were registered. The user callback functions are called and then the memory object is deleted - * and its resources freed. This provides a mechanism for the application (and libraries) using memobj to be - * notified when the memory referenced by host_ptr, specified when the memory object is created and used as - * the storage bits for the memory object, can be reused or freed. - * - * The application may not call CL api's with the cl_mem object passed to the pfn_notify. - * - * Please check for the "cl_APPLE_SetMemObjectDestructor" extension using clGetDeviceInfo(CL_DEVICE_EXTENSIONS) - * before using. - */ -#define cl_APPLE_SetMemObjectDestructor 1 -cl_int CL_API_ENTRY clSetMemObjectDestructorAPPLE( cl_mem /* memobj */, - void (* /*pfn_notify*/)( cl_mem /* memobj */, void* /*user_data*/), - void * /*user_data */ ) CL_EXT_SUFFIX__VERSION_1_0; - - -/* Context Logging Functions - * - * The next three convenience functions are intended to be used as the pfn_notify parameter to clCreateContext(). - * Please check for the "cl_APPLE_ContextLoggingFunctions" extension using clGetDeviceInfo(CL_DEVICE_EXTENSIONS) - * before using. - * - * clLogMessagesToSystemLog fowards on all log messages to the Apple System Logger - */ -#define cl_APPLE_ContextLoggingFunctions 1 -extern void CL_API_ENTRY clLogMessagesToSystemLogAPPLE( const char * /* errstr */, - const void * /* private_info */, - size_t /* cb */, - void * /* user_data */ ) CL_EXT_SUFFIX__VERSION_1_0; - -/* clLogMessagesToStdout sends all log messages to the file descriptor stdout */ -extern void CL_API_ENTRY clLogMessagesToStdoutAPPLE( const char * /* errstr */, - const void * /* private_info */, - size_t /* cb */, - void * /* user_data */ ) CL_EXT_SUFFIX__VERSION_1_0; - -/* clLogMessagesToStderr sends all log messages to the file descriptor stderr */ -extern void CL_API_ENTRY clLogMessagesToStderrAPPLE( const char * /* errstr */, - const void * /* private_info */, - size_t /* cb */, - void * /* user_data */ ) CL_EXT_SUFFIX__VERSION_1_0; - - -/************************ -* cl_khr_icd extension * -************************/ -#define cl_khr_icd 1 - -/* cl_platform_info */ -#define CL_PLATFORM_ICD_SUFFIX_KHR 0x0920 - -/* Additional Error Codes */ -#define CL_PLATFORM_NOT_FOUND_KHR -1001 - -extern CL_API_ENTRY cl_int CL_API_CALL -clIcdGetPlatformIDsKHR(cl_uint /* num_entries */, - cl_platform_id * /* platforms */, - cl_uint * /* num_platforms */); - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clIcdGetPlatformIDsKHR_fn)( - cl_uint /* num_entries */, - cl_platform_id * /* platforms */, - cl_uint * /* num_platforms */); - - -/* Extension: cl_khr_image2D_buffer - * - * This extension allows a 2D image to be created from a cl_mem buffer without a copy. - * The type associated with a 2D image created from a buffer in an OpenCL program is image2d_t. - * Both the sampler and sampler-less read_image built-in functions are supported for 2D images - * and 2D images created from a buffer. Similarly, the write_image built-ins are also supported - * for 2D images created from a buffer. - * - * When the 2D image from buffer is created, the client must specify the width, - * height, image format (i.e. channel order and channel data type) and optionally the row pitch - * - * The pitch specified must be a multiple of CL_DEVICE_IMAGE_PITCH_ALIGNMENT pixels. - * The base address of the buffer must be aligned to CL_DEVICE_IMAGE_BASE_ADDRESS_ALIGNMENT pixels. - */ - -/************************************* - * cl_khr_initalize_memory extension * - *************************************/ - -#define CL_CONTEXT_MEMORY_INITIALIZE_KHR 0x2030 - - -/************************************** - * cl_khr_terminate_context extension * - **************************************/ - -#define CL_DEVICE_TERMINATE_CAPABILITY_KHR 0x2031 -#define CL_CONTEXT_TERMINATE_KHR 0x2032 - -#define cl_khr_terminate_context 1 -extern CL_API_ENTRY cl_int CL_API_CALL clTerminateContextKHR(cl_context /* context */) CL_EXT_SUFFIX__VERSION_1_2; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clTerminateContextKHR_fn)(cl_context /* context */) CL_EXT_SUFFIX__VERSION_1_2; - - -/* - * Extension: cl_khr_spir - * - * This extension adds support to create an OpenCL program object from a - * Standard Portable Intermediate Representation (SPIR) instance - */ - -#define CL_DEVICE_SPIR_VERSIONS 0x40E0 -#define CL_PROGRAM_BINARY_TYPE_INTERMEDIATE 0x40E1 - - -/****************************************** -* cl_nv_device_attribute_query extension * -******************************************/ -/* cl_nv_device_attribute_query extension - no extension #define since it has no functions */ -#define CL_DEVICE_COMPUTE_CAPABILITY_MAJOR_NV 0x4000 -#define CL_DEVICE_COMPUTE_CAPABILITY_MINOR_NV 0x4001 -#define CL_DEVICE_REGISTERS_PER_BLOCK_NV 0x4002 -#define CL_DEVICE_WARP_SIZE_NV 0x4003 -#define CL_DEVICE_GPU_OVERLAP_NV 0x4004 -#define CL_DEVICE_KERNEL_EXEC_TIMEOUT_NV 0x4005 -#define CL_DEVICE_INTEGRATED_MEMORY_NV 0x4006 - -/********************************* -* cl_amd_device_attribute_query * -*********************************/ -#define CL_DEVICE_PROFILING_TIMER_OFFSET_AMD 0x4036 - -/********************************* -* cl_arm_printf extension -*********************************/ -#define CL_PRINTF_CALLBACK_ARM 0x40B0 -#define CL_PRINTF_BUFFERSIZE_ARM 0x40B1 - -#ifdef CL_VERSION_1_1 - /*********************************** - * cl_ext_device_fission extension * - ***********************************/ - #define cl_ext_device_fission 1 - - extern CL_API_ENTRY cl_int CL_API_CALL - clReleaseDeviceEXT( cl_device_id /*device*/ ) CL_EXT_SUFFIX__VERSION_1_1; - - typedef CL_API_ENTRY cl_int - (CL_API_CALL *clReleaseDeviceEXT_fn)( cl_device_id /*device*/ ) CL_EXT_SUFFIX__VERSION_1_1; - - extern CL_API_ENTRY cl_int CL_API_CALL - clRetainDeviceEXT( cl_device_id /*device*/ ) CL_EXT_SUFFIX__VERSION_1_1; - - typedef CL_API_ENTRY cl_int - (CL_API_CALL *clRetainDeviceEXT_fn)( cl_device_id /*device*/ ) CL_EXT_SUFFIX__VERSION_1_1; - - typedef cl_ulong cl_device_partition_property_ext; - extern CL_API_ENTRY cl_int CL_API_CALL - clCreateSubDevicesEXT( cl_device_id /*in_device*/, - const cl_device_partition_property_ext * /* properties */, - cl_uint /*num_entries*/, - cl_device_id * /*out_devices*/, - cl_uint * /*num_devices*/ ) CL_EXT_SUFFIX__VERSION_1_1; - - typedef CL_API_ENTRY cl_int - ( CL_API_CALL * clCreateSubDevicesEXT_fn)( cl_device_id /*in_device*/, - const cl_device_partition_property_ext * /* properties */, - cl_uint /*num_entries*/, - cl_device_id * /*out_devices*/, - cl_uint * /*num_devices*/ ) CL_EXT_SUFFIX__VERSION_1_1; - - /* cl_device_partition_property_ext */ - #define CL_DEVICE_PARTITION_EQUALLY_EXT 0x4050 - #define CL_DEVICE_PARTITION_BY_COUNTS_EXT 0x4051 - #define CL_DEVICE_PARTITION_BY_NAMES_EXT 0x4052 - #define CL_DEVICE_PARTITION_BY_AFFINITY_DOMAIN_EXT 0x4053 - - /* clDeviceGetInfo selectors */ - #define CL_DEVICE_PARENT_DEVICE_EXT 0x4054 - #define CL_DEVICE_PARTITION_TYPES_EXT 0x4055 - #define CL_DEVICE_AFFINITY_DOMAINS_EXT 0x4056 - #define CL_DEVICE_REFERENCE_COUNT_EXT 0x4057 - #define CL_DEVICE_PARTITION_STYLE_EXT 0x4058 - - /* error codes */ - #define CL_DEVICE_PARTITION_FAILED_EXT -1057 - #define CL_INVALID_PARTITION_COUNT_EXT -1058 - #define CL_INVALID_PARTITION_NAME_EXT -1059 - - /* CL_AFFINITY_DOMAINs */ - #define CL_AFFINITY_DOMAIN_L1_CACHE_EXT 0x1 - #define CL_AFFINITY_DOMAIN_L2_CACHE_EXT 0x2 - #define CL_AFFINITY_DOMAIN_L3_CACHE_EXT 0x3 - #define CL_AFFINITY_DOMAIN_L4_CACHE_EXT 0x4 - #define CL_AFFINITY_DOMAIN_NUMA_EXT 0x10 - #define CL_AFFINITY_DOMAIN_NEXT_FISSIONABLE_EXT 0x100 - - /* cl_device_partition_property_ext list terminators */ - #define CL_PROPERTIES_LIST_END_EXT ((cl_device_partition_property_ext) 0) - #define CL_PARTITION_BY_COUNTS_LIST_END_EXT ((cl_device_partition_property_ext) 0) - #define CL_PARTITION_BY_NAMES_LIST_END_EXT ((cl_device_partition_property_ext) 0 - 1) - -/********************************* -* cl_qcom_ext_host_ptr extension -*********************************/ - -#define CL_MEM_EXT_HOST_PTR_QCOM (1 << 29) - -#define CL_DEVICE_EXT_MEM_PADDING_IN_BYTES_QCOM 0x40A0 -#define CL_DEVICE_PAGE_SIZE_QCOM 0x40A1 -#define CL_IMAGE_ROW_ALIGNMENT_QCOM 0x40A2 -#define CL_IMAGE_SLICE_ALIGNMENT_QCOM 0x40A3 -#define CL_MEM_HOST_UNCACHED_QCOM 0x40A4 -#define CL_MEM_HOST_WRITEBACK_QCOM 0x40A5 -#define CL_MEM_HOST_WRITETHROUGH_QCOM 0x40A6 -#define CL_MEM_HOST_WRITE_COMBINING_QCOM 0x40A7 - -typedef cl_uint cl_image_pitch_info_qcom; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetDeviceImageInfoQCOM(cl_device_id device, - size_t image_width, - size_t image_height, - const cl_image_format *image_format, - cl_image_pitch_info_qcom param_name, - size_t param_value_size, - void *param_value, - size_t *param_value_size_ret); - -typedef struct _cl_mem_ext_host_ptr -{ - /* Type of external memory allocation. */ - /* Legal values will be defined in layered extensions. */ - cl_uint allocation_type; - - /* Host cache policy for this external memory allocation. */ - cl_uint host_cache_policy; - -} cl_mem_ext_host_ptr; - -/********************************* -* cl_qcom_ion_host_ptr extension -*********************************/ - -#define CL_MEM_ION_HOST_PTR_QCOM 0x40A8 - -typedef struct _cl_mem_ion_host_ptr -{ - /* Type of external memory allocation. */ - /* Must be CL_MEM_ION_HOST_PTR_QCOM for ION allocations. */ - cl_mem_ext_host_ptr ext_host_ptr; - - /* ION file descriptor */ - int ion_filedesc; - - /* Host pointer to the ION allocated memory */ - void* ion_hostptr; - -} cl_mem_ion_host_ptr; - -#endif /* CL_VERSION_1_1 */ - - -#ifdef CL_VERSION_2_0 -/********************************* -* cl_khr_sub_groups extension -*********************************/ -#define cl_khr_sub_groups 1 - -typedef cl_uint cl_kernel_sub_group_info_khr; - -/* cl_khr_sub_group_info */ -#define CL_KERNEL_MAX_SUB_GROUP_SIZE_FOR_NDRANGE_KHR 0x2033 -#define CL_KERNEL_SUB_GROUP_COUNT_FOR_NDRANGE_KHR 0x2034 - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetKernelSubGroupInfoKHR(cl_kernel /* in_kernel */, - cl_device_id /*in_device*/, - cl_kernel_sub_group_info_khr /* param_name */, - size_t /*input_value_size*/, - const void * /*input_value*/, - size_t /*param_value_size*/, - void* /*param_value*/, - size_t* /*param_value_size_ret*/ ) CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED; - -typedef CL_API_ENTRY cl_int - ( CL_API_CALL * clGetKernelSubGroupInfoKHR_fn)(cl_kernel /* in_kernel */, - cl_device_id /*in_device*/, - cl_kernel_sub_group_info_khr /* param_name */, - size_t /*input_value_size*/, - const void * /*input_value*/, - size_t /*param_value_size*/, - void* /*param_value*/, - size_t* /*param_value_size_ret*/ ) CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED; -#endif /* CL_VERSION_2_0 */ - -#ifdef CL_VERSION_2_1 -/********************************* -* cl_khr_priority_hints extension -*********************************/ -#define cl_khr_priority_hints 1 - -typedef cl_uint cl_queue_priority_khr; - -/* cl_command_queue_properties */ -#define CL_QUEUE_PRIORITY_KHR 0x1096 - -/* cl_queue_priority_khr */ -#define CL_QUEUE_PRIORITY_HIGH_KHR (1<<0) -#define CL_QUEUE_PRIORITY_MED_KHR (1<<1) -#define CL_QUEUE_PRIORITY_LOW_KHR (1<<2) - -#endif /* CL_VERSION_2_1 */ - -#ifdef CL_VERSION_2_1 -/********************************* -* cl_khr_throttle_hints extension -*********************************/ -#define cl_khr_throttle_hints 1 - -typedef cl_uint cl_queue_throttle_khr; - -/* cl_command_queue_properties */ -#define CL_QUEUE_THROTTLE_KHR 0x1097 - -/* cl_queue_throttle_khr */ -#define CL_QUEUE_THROTTLE_HIGH_KHR (1<<0) -#define CL_QUEUE_THROTTLE_MED_KHR (1<<1) -#define CL_QUEUE_THROTTLE_LOW_KHR (1<<2) - -#endif /* CL_VERSION_2_1 */ - -#ifdef __cplusplus -} -#endif - - -#endif /* __CL_EXT_H */ diff --git a/third_party/opencl/include/CL/cl_ext_qcom.h b/third_party/opencl/include/CL/cl_ext_qcom.h deleted file mode 100644 index 6328a1cd9..000000000 --- a/third_party/opencl/include/CL/cl_ext_qcom.h +++ /dev/null @@ -1,255 +0,0 @@ -/* Copyright (c) 2009-2017 Qualcomm Technologies, Inc. All Rights Reserved. - * Qualcomm Technologies Proprietary and Confidential. - */ - -#ifndef __OPENCL_CL_EXT_QCOM_H -#define __OPENCL_CL_EXT_QCOM_H - -// Needed by cl_khr_egl_event extension -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - - -/************************************ - * cl_qcom_create_buffer_from_image * - ************************************/ - -#define CL_BUFFER_FROM_IMAGE_ROW_PITCH_QCOM 0x40C0 -#define CL_BUFFER_FROM_IMAGE_SLICE_PITCH_QCOM 0x40C1 - -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreateBufferFromImageQCOM(cl_mem image, - cl_mem_flags flags, - cl_int *errcode_ret); - - -/************************************ - * cl_qcom_limited_printf extension * - ************************************/ - -/* Builtin printf function buffer size in bytes. */ -#define CL_DEVICE_PRINTF_BUFFER_SIZE_QCOM 0x1049 - - -/************************************* - * cl_qcom_extended_images extension * - *************************************/ - -#define CL_CONTEXT_ENABLE_EXTENDED_IMAGES_QCOM 0x40AA -#define CL_DEVICE_EXTENDED_IMAGE2D_MAX_WIDTH_QCOM 0x40AB -#define CL_DEVICE_EXTENDED_IMAGE2D_MAX_HEIGHT_QCOM 0x40AC -#define CL_DEVICE_EXTENDED_IMAGE3D_MAX_WIDTH_QCOM 0x40AD -#define CL_DEVICE_EXTENDED_IMAGE3D_MAX_HEIGHT_QCOM 0x40AE -#define CL_DEVICE_EXTENDED_IMAGE3D_MAX_DEPTH_QCOM 0x40AF - -/************************************* - * cl_qcom_perf_hint extension * - *************************************/ - -typedef cl_uint cl_perf_hint; - -#define CL_CONTEXT_PERF_HINT_QCOM 0x40C2 - -/*cl_perf_hint*/ -#define CL_PERF_HINT_HIGH_QCOM 0x40C3 -#define CL_PERF_HINT_NORMAL_QCOM 0x40C4 -#define CL_PERF_HINT_LOW_QCOM 0x40C5 - -extern CL_API_ENTRY cl_int CL_API_CALL -clSetPerfHintQCOM(cl_context context, - cl_perf_hint perf_hint); - -// This extension is published at Khronos, so its definitions are made in cl_ext.h. -// This duplication is for backward compatibility. - -#ifndef CL_MEM_ANDROID_NATIVE_BUFFER_HOST_PTR_QCOM - -/********************************* -* cl_qcom_android_native_buffer_host_ptr extension -*********************************/ - -#define CL_MEM_ANDROID_NATIVE_BUFFER_HOST_PTR_QCOM 0x40C6 - - -typedef struct _cl_mem_android_native_buffer_host_ptr -{ - // Type of external memory allocation. - // Must be CL_MEM_ANDROID_NATIVE_BUFFER_HOST_PTR_QCOM for Android native buffers. - cl_mem_ext_host_ptr ext_host_ptr; - - // Virtual pointer to the android native buffer - void* anb_ptr; - -} cl_mem_android_native_buffer_host_ptr; - -#endif //#ifndef CL_MEM_ANDROID_NATIVE_BUFFER_HOST_PTR_QCOM - -/*********************************** -* cl_img_egl_image extension * -************************************/ -typedef void* CLeglImageIMG; -typedef void* CLeglDisplayIMG; - -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreateFromEGLImageIMG(cl_context context, - cl_mem_flags flags, - CLeglImageIMG image, - CLeglDisplayIMG display, - cl_int *errcode_ret); - - -/********************************* -* cl_qcom_other_image extension -*********************************/ - -// Extended flag for creating/querying QCOM non-standard images -#define CL_MEM_OTHER_IMAGE_QCOM (1<<25) - -// cl_channel_type -#define CL_QCOM_UNORM_MIPI10 0x4159 -#define CL_QCOM_UNORM_MIPI12 0x415A -#define CL_QCOM_UNSIGNED_MIPI10 0x415B -#define CL_QCOM_UNSIGNED_MIPI12 0x415C -#define CL_QCOM_UNORM_INT10 0x415D -#define CL_QCOM_UNORM_INT12 0x415E -#define CL_QCOM_UNSIGNED_INT16 0x415F - -// cl_channel_order -// Dedicate 0x4130-0x415F range for QCOM extended image formats -// 0x4130 - 0x4132 range is assigned to pixel-oriented compressed format -#define CL_QCOM_BAYER 0x414E - -#define CL_QCOM_NV12 0x4133 -#define CL_QCOM_NV12_Y 0x4134 -#define CL_QCOM_NV12_UV 0x4135 - -#define CL_QCOM_TILED_NV12 0x4136 -#define CL_QCOM_TILED_NV12_Y 0x4137 -#define CL_QCOM_TILED_NV12_UV 0x4138 - -#define CL_QCOM_P010 0x413C -#define CL_QCOM_P010_Y 0x413D -#define CL_QCOM_P010_UV 0x413E - -#define CL_QCOM_TILED_P010 0x413F -#define CL_QCOM_TILED_P010_Y 0x4140 -#define CL_QCOM_TILED_P010_UV 0x4141 - - -#define CL_QCOM_TP10 0x4145 -#define CL_QCOM_TP10_Y 0x4146 -#define CL_QCOM_TP10_UV 0x4147 - -#define CL_QCOM_TILED_TP10 0x4148 -#define CL_QCOM_TILED_TP10_Y 0x4149 -#define CL_QCOM_TILED_TP10_UV 0x414A - -/********************************* -* cl_qcom_compressed_image extension -*********************************/ - -// Extended flag for creating/querying QCOM non-planar compressed images -#define CL_MEM_COMPRESSED_IMAGE_QCOM (1<<27) - -// Extended image format -// cl_channel_order -#define CL_QCOM_COMPRESSED_RGBA 0x4130 -#define CL_QCOM_COMPRESSED_RGBx 0x4131 - -#define CL_QCOM_COMPRESSED_NV12_Y 0x413A -#define CL_QCOM_COMPRESSED_NV12_UV 0x413B - -#define CL_QCOM_COMPRESSED_P010 0x4142 -#define CL_QCOM_COMPRESSED_P010_Y 0x4143 -#define CL_QCOM_COMPRESSED_P010_UV 0x4144 - -#define CL_QCOM_COMPRESSED_TP10 0x414B -#define CL_QCOM_COMPRESSED_TP10_Y 0x414C -#define CL_QCOM_COMPRESSED_TP10_UV 0x414D - -#define CL_QCOM_COMPRESSED_NV12_4R 0x414F -#define CL_QCOM_COMPRESSED_NV12_4R_Y 0x4150 -#define CL_QCOM_COMPRESSED_NV12_4R_UV 0x4151 -/********************************* -* cl_qcom_compressed_yuv_image_read extension -*********************************/ - -// Extended flag for creating/querying QCOM compressed images -#define CL_MEM_COMPRESSED_YUV_IMAGE_QCOM (1<<28) - -// Extended image format -#define CL_QCOM_COMPRESSED_NV12 0x10C4 - -// Extended flag for setting ION buffer allocation type -#define CL_MEM_ION_HOST_PTR_COMPRESSED_YUV_QCOM 0x40CD -#define CL_MEM_ION_HOST_PTR_PROTECTED_COMPRESSED_YUV_QCOM 0x40CE - -/********************************* -* cl_qcom_accelerated_image_ops -*********************************/ -#define CL_MEM_OBJECT_WEIGHT_IMAGE_QCOM 0x4110 -#define CL_DEVICE_HOF_MAX_NUM_PHASES_QCOM 0x4111 -#define CL_DEVICE_HOF_MAX_FILTER_SIZE_X_QCOM 0x4112 -#define CL_DEVICE_HOF_MAX_FILTER_SIZE_Y_QCOM 0x4113 -#define CL_DEVICE_BLOCK_MATCHING_MAX_REGION_SIZE_X_QCOM 0x4114 -#define CL_DEVICE_BLOCK_MATCHING_MAX_REGION_SIZE_Y_QCOM 0x4115 - -//Extended flag for specifying weight image type -#define CL_WEIGHT_IMAGE_SEPARABLE_QCOM (1<<0) - -// Box Filter -typedef struct _cl_box_filter_size_qcom -{ - // Width of box filter on X direction. - float box_filter_width; - - // Height of box filter on Y direction. - float box_filter_height; -} cl_box_filter_size_qcom; - -// HOF Weight Image Desc -typedef struct _cl_weight_desc_qcom -{ - /** Coordinate of the "center" point of the weight image, - based on the weight image's top-left corner as the origin. */ - size_t center_coord_x; - size_t center_coord_y; - cl_bitfield flags; -} cl_weight_desc_qcom; - -typedef struct _cl_weight_image_desc_qcom -{ - cl_image_desc image_desc; - cl_weight_desc_qcom weight_desc; -} cl_weight_image_desc_qcom; - -/************************************* - * cl_qcom_protected_context extension * - *************************************/ - -#define CL_CONTEXT_PROTECTED_QCOM 0x40C7 -#define CL_MEM_ION_HOST_PTR_PROTECTED_QCOM 0x40C8 - -/************************************* - * cl_qcom_priority_hint extension * - *************************************/ -#define CL_PRIORITY_HINT_NONE_QCOM 0 -typedef cl_uint cl_priority_hint; - -#define CL_CONTEXT_PRIORITY_HINT_QCOM 0x40C9 - -/*cl_priority_hint*/ -#define CL_PRIORITY_HINT_HIGH_QCOM 0x40CA -#define CL_PRIORITY_HINT_NORMAL_QCOM 0x40CB -#define CL_PRIORITY_HINT_LOW_QCOM 0x40CC - -#ifdef __cplusplus -} -#endif - -#endif /* __OPENCL_CL_EXT_QCOM_H */ diff --git a/third_party/opencl/include/CL/cl_gl.h b/third_party/opencl/include/CL/cl_gl.h deleted file mode 100644 index 945daa83d..000000000 --- a/third_party/opencl/include/CL/cl_gl.h +++ /dev/null @@ -1,167 +0,0 @@ -/********************************************************************************** - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - **********************************************************************************/ - -#ifndef __OPENCL_CL_GL_H -#define __OPENCL_CL_GL_H - -#ifdef __APPLE__ -#include -#else -#include -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -typedef cl_uint cl_gl_object_type; -typedef cl_uint cl_gl_texture_info; -typedef cl_uint cl_gl_platform_info; -typedef struct __GLsync *cl_GLsync; - -/* cl_gl_object_type = 0x2000 - 0x200F enum values are currently taken */ -#define CL_GL_OBJECT_BUFFER 0x2000 -#define CL_GL_OBJECT_TEXTURE2D 0x2001 -#define CL_GL_OBJECT_TEXTURE3D 0x2002 -#define CL_GL_OBJECT_RENDERBUFFER 0x2003 -#define CL_GL_OBJECT_TEXTURE2D_ARRAY 0x200E -#define CL_GL_OBJECT_TEXTURE1D 0x200F -#define CL_GL_OBJECT_TEXTURE1D_ARRAY 0x2010 -#define CL_GL_OBJECT_TEXTURE_BUFFER 0x2011 - -/* cl_gl_texture_info */ -#define CL_GL_TEXTURE_TARGET 0x2004 -#define CL_GL_MIPMAP_LEVEL 0x2005 -#define CL_GL_NUM_SAMPLES 0x2012 - - -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreateFromGLBuffer(cl_context /* context */, - cl_mem_flags /* flags */, - cl_GLuint /* bufobj */, - int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreateFromGLTexture(cl_context /* context */, - cl_mem_flags /* flags */, - cl_GLenum /* target */, - cl_GLint /* miplevel */, - cl_GLuint /* texture */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreateFromGLRenderbuffer(cl_context /* context */, - cl_mem_flags /* flags */, - cl_GLuint /* renderbuffer */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetGLObjectInfo(cl_mem /* memobj */, - cl_gl_object_type * /* gl_object_type */, - cl_GLuint * /* gl_object_name */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetGLTextureInfo(cl_mem /* memobj */, - cl_gl_texture_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueAcquireGLObjects(cl_command_queue /* command_queue */, - cl_uint /* num_objects */, - const cl_mem * /* mem_objects */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueReleaseGLObjects(cl_command_queue /* command_queue */, - cl_uint /* num_objects */, - const cl_mem * /* mem_objects */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - - -/* Deprecated OpenCL 1.1 APIs */ -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_mem CL_API_CALL -clCreateFromGLTexture2D(cl_context /* context */, - cl_mem_flags /* flags */, - cl_GLenum /* target */, - cl_GLint /* miplevel */, - cl_GLuint /* texture */, - cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; - -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_mem CL_API_CALL -clCreateFromGLTexture3D(cl_context /* context */, - cl_mem_flags /* flags */, - cl_GLenum /* target */, - cl_GLint /* miplevel */, - cl_GLuint /* texture */, - cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; - -/* cl_khr_gl_sharing extension */ - -#define cl_khr_gl_sharing 1 - -typedef cl_uint cl_gl_context_info; - -/* Additional Error Codes */ -#define CL_INVALID_GL_SHAREGROUP_REFERENCE_KHR -1000 - -/* cl_gl_context_info */ -#define CL_CURRENT_DEVICE_FOR_GL_CONTEXT_KHR 0x2006 -#define CL_DEVICES_FOR_GL_CONTEXT_KHR 0x2007 - -/* Additional cl_context_properties */ -#define CL_GL_CONTEXT_KHR 0x2008 -#define CL_EGL_DISPLAY_KHR 0x2009 -#define CL_GLX_DISPLAY_KHR 0x200A -#define CL_WGL_HDC_KHR 0x200B -#define CL_CGL_SHAREGROUP_KHR 0x200C - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetGLContextInfoKHR(const cl_context_properties * /* properties */, - cl_gl_context_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clGetGLContextInfoKHR_fn)( - const cl_context_properties * properties, - cl_gl_context_info param_name, - size_t param_value_size, - void * param_value, - size_t * param_value_size_ret); - -#ifdef __cplusplus -} -#endif - -#endif /* __OPENCL_CL_GL_H */ diff --git a/third_party/opencl/include/CL/cl_gl_ext.h b/third_party/opencl/include/CL/cl_gl_ext.h deleted file mode 100644 index e3c14c640..000000000 --- a/third_party/opencl/include/CL/cl_gl_ext.h +++ /dev/null @@ -1,74 +0,0 @@ -/********************************************************************************** - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - **********************************************************************************/ - -/* $Revision: 11708 $ on $Date: 2010-06-13 23:36:24 -0700 (Sun, 13 Jun 2010) $ */ - -/* cl_gl_ext.h contains vendor (non-KHR) OpenCL extensions which have */ -/* OpenGL dependencies. */ - -#ifndef __OPENCL_CL_GL_EXT_H -#define __OPENCL_CL_GL_EXT_H - -#ifdef __cplusplus -extern "C" { -#endif - -#ifdef __APPLE__ - #include -#else - #include -#endif - -/* - * For each extension, follow this template - * cl_VEN_extname extension */ -/* #define cl_VEN_extname 1 - * ... define new types, if any - * ... define new tokens, if any - * ... define new APIs, if any - * - * If you need GLtypes here, mirror them with a cl_GLtype, rather than including a GL header - * This allows us to avoid having to decide whether to include GL headers or GLES here. - */ - -/* - * cl_khr_gl_event extension - * See section 9.9 in the OpenCL 1.1 spec for more information - */ -#define CL_COMMAND_GL_FENCE_SYNC_OBJECT_KHR 0x200D - -extern CL_API_ENTRY cl_event CL_API_CALL -clCreateEventFromGLsyncKHR(cl_context /* context */, - cl_GLsync /* cl_GLsync */, - cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_1; - -#ifdef __cplusplus -} -#endif - -#endif /* __OPENCL_CL_GL_EXT_H */ diff --git a/third_party/opencl/include/CL/cl_platform.h b/third_party/opencl/include/CL/cl_platform.h deleted file mode 100644 index 4e334a291..000000000 --- a/third_party/opencl/include/CL/cl_platform.h +++ /dev/null @@ -1,1333 +0,0 @@ -/********************************************************************************** - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - **********************************************************************************/ - -/* $Revision: 11803 $ on $Date: 2010-06-25 10:02:12 -0700 (Fri, 25 Jun 2010) $ */ - -#ifndef __CL_PLATFORM_H -#define __CL_PLATFORM_H - -#ifdef __APPLE__ - /* Contains #defines for AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER below */ - #include -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -#if defined(_WIN32) - #define CL_API_ENTRY - #define CL_API_CALL __stdcall - #define CL_CALLBACK __stdcall -#else - #define CL_API_ENTRY - #define CL_API_CALL - #define CL_CALLBACK -#endif - -/* - * Deprecation flags refer to the last version of the header in which the - * feature was not deprecated. - * - * E.g. VERSION_1_1_DEPRECATED means the feature is present in 1.1 without - * deprecation but is deprecated in versions later than 1.1. - */ - -#ifdef __APPLE__ - #define CL_EXTENSION_WEAK_LINK __attribute__((weak_import)) - #define CL_API_SUFFIX__VERSION_1_0 AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER - #define CL_EXT_SUFFIX__VERSION_1_0 CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER - #define CL_API_SUFFIX__VERSION_1_1 AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER - #define GCL_API_SUFFIX__VERSION_1_1 AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER - #define CL_EXT_SUFFIX__VERSION_1_1 CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER - #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_7 - - #ifdef AVAILABLE_MAC_OS_X_VERSION_10_8_AND_LATER - #define CL_API_SUFFIX__VERSION_1_2 AVAILABLE_MAC_OS_X_VERSION_10_8_AND_LATER - #define GCL_API_SUFFIX__VERSION_1_2 AVAILABLE_MAC_OS_X_VERSION_10_8_AND_LATER - #define CL_EXT_SUFFIX__VERSION_1_2 CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_8_AND_LATER - #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED - #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_8 - #else - #warning This path should never happen outside of internal operating system development. AvailabilityMacros do not function correctly here! - #define CL_API_SUFFIX__VERSION_1_2 AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER - #define GCL_API_SUFFIX__VERSION_1_2 AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER - #define CL_EXT_SUFFIX__VERSION_1_2 CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER - #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER - #endif -#else - #define CL_EXTENSION_WEAK_LINK - #define CL_API_SUFFIX__VERSION_1_0 - #define CL_EXT_SUFFIX__VERSION_1_0 - #define CL_API_SUFFIX__VERSION_1_1 - #define CL_EXT_SUFFIX__VERSION_1_1 - #define CL_API_SUFFIX__VERSION_1_2 - #define CL_EXT_SUFFIX__VERSION_1_2 - #define CL_API_SUFFIX__VERSION_2_0 - #define CL_EXT_SUFFIX__VERSION_2_0 - #define CL_API_SUFFIX__VERSION_2_1 - #define CL_EXT_SUFFIX__VERSION_2_1 - - #ifdef __GNUC__ - #ifdef CL_USE_DEPRECATED_OPENCL_1_0_APIS - #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_0_DEPRECATED - #else - #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED __attribute__((deprecated)) - #define CL_EXT_PREFIX__VERSION_1_0_DEPRECATED - #endif - - #ifdef CL_USE_DEPRECATED_OPENCL_1_1_APIS - #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED - #else - #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED __attribute__((deprecated)) - #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED - #endif - - #ifdef CL_USE_DEPRECATED_OPENCL_1_2_APIS - #define CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_2_DEPRECATED - #else - #define CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED __attribute__((deprecated)) - #define CL_EXT_PREFIX__VERSION_1_2_DEPRECATED - #endif - - #ifdef CL_USE_DEPRECATED_OPENCL_2_0_APIS - #define CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED - #define CL_EXT_PREFIX__VERSION_2_0_DEPRECATED - #else - #define CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED __attribute__((deprecated)) - #define CL_EXT_PREFIX__VERSION_2_0_DEPRECATED - #endif - #elif _WIN32 - #ifdef CL_USE_DEPRECATED_OPENCL_1_0_APIS - #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_0_DEPRECATED - #else - #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_0_DEPRECATED __declspec(deprecated) - #endif - - #ifdef CL_USE_DEPRECATED_OPENCL_1_1_APIS - #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED - #else - #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED __declspec(deprecated) - #endif - - #ifdef CL_USE_DEPRECATED_OPENCL_1_2_APIS - #define CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_2_DEPRECATED - #else - #define CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_2_DEPRECATED __declspec(deprecated) - #endif - - #ifdef CL_USE_DEPRECATED_OPENCL_2_0_APIS - #define CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED - #define CL_EXT_PREFIX__VERSION_2_0_DEPRECATED - #else - #define CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED - #define CL_EXT_PREFIX__VERSION_2_0_DEPRECATED __declspec(deprecated) - #endif - #else - #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_0_DEPRECATED - - #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED - - #define CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_2_DEPRECATED - - #define CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED - #define CL_EXT_PREFIX__VERSION_2_0_DEPRECATED - #endif -#endif - -#if (defined (_WIN32) && defined(_MSC_VER)) - -/* scalar types */ -typedef signed __int8 cl_char; -typedef unsigned __int8 cl_uchar; -typedef signed __int16 cl_short; -typedef unsigned __int16 cl_ushort; -typedef signed __int32 cl_int; -typedef unsigned __int32 cl_uint; -typedef signed __int64 cl_long; -typedef unsigned __int64 cl_ulong; - -typedef unsigned __int16 cl_half; -typedef float cl_float; -typedef double cl_double; - -/* Macro names and corresponding values defined by OpenCL */ -#define CL_CHAR_BIT 8 -#define CL_SCHAR_MAX 127 -#define CL_SCHAR_MIN (-127-1) -#define CL_CHAR_MAX CL_SCHAR_MAX -#define CL_CHAR_MIN CL_SCHAR_MIN -#define CL_UCHAR_MAX 255 -#define CL_SHRT_MAX 32767 -#define CL_SHRT_MIN (-32767-1) -#define CL_USHRT_MAX 65535 -#define CL_INT_MAX 2147483647 -#define CL_INT_MIN (-2147483647-1) -#define CL_UINT_MAX 0xffffffffU -#define CL_LONG_MAX ((cl_long) 0x7FFFFFFFFFFFFFFFLL) -#define CL_LONG_MIN ((cl_long) -0x7FFFFFFFFFFFFFFFLL - 1LL) -#define CL_ULONG_MAX ((cl_ulong) 0xFFFFFFFFFFFFFFFFULL) - -#define CL_FLT_DIG 6 -#define CL_FLT_MANT_DIG 24 -#define CL_FLT_MAX_10_EXP +38 -#define CL_FLT_MAX_EXP +128 -#define CL_FLT_MIN_10_EXP -37 -#define CL_FLT_MIN_EXP -125 -#define CL_FLT_RADIX 2 -#define CL_FLT_MAX 340282346638528859811704183484516925440.0f -#define CL_FLT_MIN 1.175494350822287507969e-38f -#define CL_FLT_EPSILON 0x1.0p-23f - -#define CL_DBL_DIG 15 -#define CL_DBL_MANT_DIG 53 -#define CL_DBL_MAX_10_EXP +308 -#define CL_DBL_MAX_EXP +1024 -#define CL_DBL_MIN_10_EXP -307 -#define CL_DBL_MIN_EXP -1021 -#define CL_DBL_RADIX 2 -#define CL_DBL_MAX 179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.0 -#define CL_DBL_MIN 2.225073858507201383090e-308 -#define CL_DBL_EPSILON 2.220446049250313080847e-16 - -#define CL_M_E 2.718281828459045090796 -#define CL_M_LOG2E 1.442695040888963387005 -#define CL_M_LOG10E 0.434294481903251816668 -#define CL_M_LN2 0.693147180559945286227 -#define CL_M_LN10 2.302585092994045901094 -#define CL_M_PI 3.141592653589793115998 -#define CL_M_PI_2 1.570796326794896557999 -#define CL_M_PI_4 0.785398163397448278999 -#define CL_M_1_PI 0.318309886183790691216 -#define CL_M_2_PI 0.636619772367581382433 -#define CL_M_2_SQRTPI 1.128379167095512558561 -#define CL_M_SQRT2 1.414213562373095145475 -#define CL_M_SQRT1_2 0.707106781186547572737 - -#define CL_M_E_F 2.71828174591064f -#define CL_M_LOG2E_F 1.44269502162933f -#define CL_M_LOG10E_F 0.43429449200630f -#define CL_M_LN2_F 0.69314718246460f -#define CL_M_LN10_F 2.30258512496948f -#define CL_M_PI_F 3.14159274101257f -#define CL_M_PI_2_F 1.57079637050629f -#define CL_M_PI_4_F 0.78539818525314f -#define CL_M_1_PI_F 0.31830987334251f -#define CL_M_2_PI_F 0.63661974668503f -#define CL_M_2_SQRTPI_F 1.12837922573090f -#define CL_M_SQRT2_F 1.41421353816986f -#define CL_M_SQRT1_2_F 0.70710676908493f - -#define CL_NAN (CL_INFINITY - CL_INFINITY) -#define CL_HUGE_VALF ((cl_float) 1e50) -#define CL_HUGE_VAL ((cl_double) 1e500) -#define CL_MAXFLOAT CL_FLT_MAX -#define CL_INFINITY CL_HUGE_VALF - -#else - -#include - -/* scalar types */ -typedef int8_t cl_char; -typedef uint8_t cl_uchar; -typedef int16_t cl_short __attribute__((aligned(2))); -typedef uint16_t cl_ushort __attribute__((aligned(2))); -typedef int32_t cl_int __attribute__((aligned(4))); -typedef uint32_t cl_uint __attribute__((aligned(4))); -typedef int64_t cl_long __attribute__((aligned(8))); -typedef uint64_t cl_ulong __attribute__((aligned(8))); - -typedef uint16_t cl_half __attribute__((aligned(2))); -typedef float cl_float __attribute__((aligned(4))); -typedef double cl_double __attribute__((aligned(8))); - -/* Macro names and corresponding values defined by OpenCL */ -#define CL_CHAR_BIT 8 -#define CL_SCHAR_MAX 127 -#define CL_SCHAR_MIN (-127-1) -#define CL_CHAR_MAX CL_SCHAR_MAX -#define CL_CHAR_MIN CL_SCHAR_MIN -#define CL_UCHAR_MAX 255 -#define CL_SHRT_MAX 32767 -#define CL_SHRT_MIN (-32767-1) -#define CL_USHRT_MAX 65535 -#define CL_INT_MAX 2147483647 -#define CL_INT_MIN (-2147483647-1) -#define CL_UINT_MAX 0xffffffffU -#define CL_LONG_MAX ((cl_long) 0x7FFFFFFFFFFFFFFFLL) -#define CL_LONG_MIN ((cl_long) -0x7FFFFFFFFFFFFFFFLL - 1LL) -#define CL_ULONG_MAX ((cl_ulong) 0xFFFFFFFFFFFFFFFFULL) - -#define CL_FLT_DIG 6 -#define CL_FLT_MANT_DIG 24 -#define CL_FLT_MAX_10_EXP +38 -#define CL_FLT_MAX_EXP +128 -#define CL_FLT_MIN_10_EXP -37 -#define CL_FLT_MIN_EXP -125 -#define CL_FLT_RADIX 2 -#define CL_FLT_MAX 0x1.fffffep127f -#define CL_FLT_MIN 0x1.0p-126f -#define CL_FLT_EPSILON 0x1.0p-23f - -#define CL_DBL_DIG 15 -#define CL_DBL_MANT_DIG 53 -#define CL_DBL_MAX_10_EXP +308 -#define CL_DBL_MAX_EXP +1024 -#define CL_DBL_MIN_10_EXP -307 -#define CL_DBL_MIN_EXP -1021 -#define CL_DBL_RADIX 2 -#define CL_DBL_MAX 0x1.fffffffffffffp1023 -#define CL_DBL_MIN 0x1.0p-1022 -#define CL_DBL_EPSILON 0x1.0p-52 - -#define CL_M_E 2.718281828459045090796 -#define CL_M_LOG2E 1.442695040888963387005 -#define CL_M_LOG10E 0.434294481903251816668 -#define CL_M_LN2 0.693147180559945286227 -#define CL_M_LN10 2.302585092994045901094 -#define CL_M_PI 3.141592653589793115998 -#define CL_M_PI_2 1.570796326794896557999 -#define CL_M_PI_4 0.785398163397448278999 -#define CL_M_1_PI 0.318309886183790691216 -#define CL_M_2_PI 0.636619772367581382433 -#define CL_M_2_SQRTPI 1.128379167095512558561 -#define CL_M_SQRT2 1.414213562373095145475 -#define CL_M_SQRT1_2 0.707106781186547572737 - -#define CL_M_E_F 2.71828174591064f -#define CL_M_LOG2E_F 1.44269502162933f -#define CL_M_LOG10E_F 0.43429449200630f -#define CL_M_LN2_F 0.69314718246460f -#define CL_M_LN10_F 2.30258512496948f -#define CL_M_PI_F 3.14159274101257f -#define CL_M_PI_2_F 1.57079637050629f -#define CL_M_PI_4_F 0.78539818525314f -#define CL_M_1_PI_F 0.31830987334251f -#define CL_M_2_PI_F 0.63661974668503f -#define CL_M_2_SQRTPI_F 1.12837922573090f -#define CL_M_SQRT2_F 1.41421353816986f -#define CL_M_SQRT1_2_F 0.70710676908493f - -#if defined( __GNUC__ ) - #define CL_HUGE_VALF __builtin_huge_valf() - #define CL_HUGE_VAL __builtin_huge_val() - #define CL_NAN __builtin_nanf( "" ) -#else - #define CL_HUGE_VALF ((cl_float) 1e50) - #define CL_HUGE_VAL ((cl_double) 1e500) - float nanf( const char * ); - #define CL_NAN nanf( "" ) -#endif -#define CL_MAXFLOAT CL_FLT_MAX -#define CL_INFINITY CL_HUGE_VALF - -#endif - -#include - -/* Mirror types to GL types. Mirror types allow us to avoid deciding which 87s to load based on whether we are using GL or GLES here. */ -typedef unsigned int cl_GLuint; -typedef int cl_GLint; -typedef unsigned int cl_GLenum; - -/* - * Vector types - * - * Note: OpenCL requires that all types be naturally aligned. - * This means that vector types must be naturally aligned. - * For example, a vector of four floats must be aligned to - * a 16 byte boundary (calculated as 4 * the natural 4-byte - * alignment of the float). The alignment qualifiers here - * will only function properly if your compiler supports them - * and if you don't actively work to defeat them. For example, - * in order for a cl_float4 to be 16 byte aligned in a struct, - * the start of the struct must itself be 16-byte aligned. - * - * Maintaining proper alignment is the user's responsibility. - */ - -/* Define basic vector types */ -#if defined( __VEC__ ) - #include /* may be omitted depending on compiler. AltiVec spec provides no way to detect whether the header is required. */ - typedef vector unsigned char __cl_uchar16; - typedef vector signed char __cl_char16; - typedef vector unsigned short __cl_ushort8; - typedef vector signed short __cl_short8; - typedef vector unsigned int __cl_uint4; - typedef vector signed int __cl_int4; - typedef vector float __cl_float4; - #define __CL_UCHAR16__ 1 - #define __CL_CHAR16__ 1 - #define __CL_USHORT8__ 1 - #define __CL_SHORT8__ 1 - #define __CL_UINT4__ 1 - #define __CL_INT4__ 1 - #define __CL_FLOAT4__ 1 -#endif - -#if defined( __SSE__ ) - #if defined( __MINGW64__ ) - #include - #else - #include - #endif - #if defined( __GNUC__ ) - typedef float __cl_float4 __attribute__((vector_size(16))); - #else - typedef __m128 __cl_float4; - #endif - #define __CL_FLOAT4__ 1 -#endif - -#if defined( __SSE2__ ) - #if defined( __MINGW64__ ) - #include - #else - #include - #endif - #if defined( __GNUC__ ) - typedef cl_uchar __cl_uchar16 __attribute__((vector_size(16))); - typedef cl_char __cl_char16 __attribute__((vector_size(16))); - typedef cl_ushort __cl_ushort8 __attribute__((vector_size(16))); - typedef cl_short __cl_short8 __attribute__((vector_size(16))); - typedef cl_uint __cl_uint4 __attribute__((vector_size(16))); - typedef cl_int __cl_int4 __attribute__((vector_size(16))); - typedef cl_ulong __cl_ulong2 __attribute__((vector_size(16))); - typedef cl_long __cl_long2 __attribute__((vector_size(16))); - typedef cl_double __cl_double2 __attribute__((vector_size(16))); - #else - typedef __m128i __cl_uchar16; - typedef __m128i __cl_char16; - typedef __m128i __cl_ushort8; - typedef __m128i __cl_short8; - typedef __m128i __cl_uint4; - typedef __m128i __cl_int4; - typedef __m128i __cl_ulong2; - typedef __m128i __cl_long2; - typedef __m128d __cl_double2; - #endif - #define __CL_UCHAR16__ 1 - #define __CL_CHAR16__ 1 - #define __CL_USHORT8__ 1 - #define __CL_SHORT8__ 1 - #define __CL_INT4__ 1 - #define __CL_UINT4__ 1 - #define __CL_ULONG2__ 1 - #define __CL_LONG2__ 1 - #define __CL_DOUBLE2__ 1 -#endif - -#if defined( __MMX__ ) - #include - #if defined( __GNUC__ ) - typedef cl_uchar __cl_uchar8 __attribute__((vector_size(8))); - typedef cl_char __cl_char8 __attribute__((vector_size(8))); - typedef cl_ushort __cl_ushort4 __attribute__((vector_size(8))); - typedef cl_short __cl_short4 __attribute__((vector_size(8))); - typedef cl_uint __cl_uint2 __attribute__((vector_size(8))); - typedef cl_int __cl_int2 __attribute__((vector_size(8))); - typedef cl_ulong __cl_ulong1 __attribute__((vector_size(8))); - typedef cl_long __cl_long1 __attribute__((vector_size(8))); - typedef cl_float __cl_float2 __attribute__((vector_size(8))); - #else - typedef __m64 __cl_uchar8; - typedef __m64 __cl_char8; - typedef __m64 __cl_ushort4; - typedef __m64 __cl_short4; - typedef __m64 __cl_uint2; - typedef __m64 __cl_int2; - typedef __m64 __cl_ulong1; - typedef __m64 __cl_long1; - typedef __m64 __cl_float2; - #endif - #define __CL_UCHAR8__ 1 - #define __CL_CHAR8__ 1 - #define __CL_USHORT4__ 1 - #define __CL_SHORT4__ 1 - #define __CL_INT2__ 1 - #define __CL_UINT2__ 1 - #define __CL_ULONG1__ 1 - #define __CL_LONG1__ 1 - #define __CL_FLOAT2__ 1 -#endif - -#if defined( __AVX__ ) - #if defined( __MINGW64__ ) - #include - #else - #include - #endif - #if defined( __GNUC__ ) - typedef cl_float __cl_float8 __attribute__((vector_size(32))); - typedef cl_double __cl_double4 __attribute__((vector_size(32))); - #else - typedef __m256 __cl_float8; - typedef __m256d __cl_double4; - #endif - #define __CL_FLOAT8__ 1 - #define __CL_DOUBLE4__ 1 -#endif - -/* Define capabilities for anonymous struct members. */ -#if defined( __GNUC__) && ! defined( __STRICT_ANSI__ ) -#define __CL_HAS_ANON_STRUCT__ 1 -#define __CL_ANON_STRUCT__ __extension__ -#elif defined( _WIN32) && (_MSC_VER >= 1500) - /* Microsoft Developer Studio 2008 supports anonymous structs, but - * complains by default. */ -#define __CL_HAS_ANON_STRUCT__ 1 -#define __CL_ANON_STRUCT__ - /* Disable warning C4201: nonstandard extension used : nameless - * struct/union */ -#pragma warning( push ) -#pragma warning( disable : 4201 ) -#else -#define __CL_HAS_ANON_STRUCT__ 0 -#define __CL_ANON_STRUCT__ -#endif - -/* Define alignment keys */ -#if defined( __GNUC__ ) - #define CL_ALIGNED(_x) __attribute__ ((aligned(_x))) -#elif defined( _WIN32) && (_MSC_VER) - /* Alignment keys neutered on windows because MSVC can't swallow function arguments with alignment requirements */ - /* http://msdn.microsoft.com/en-us/library/373ak2y1%28VS.71%29.aspx */ - /* #include */ - /* #define CL_ALIGNED(_x) _CRT_ALIGN(_x) */ - #define CL_ALIGNED(_x) -#else - #warning Need to implement some method to align data here - #define CL_ALIGNED(_x) -#endif - -/* Indicate whether .xyzw, .s0123 and .hi.lo are supported */ -#if __CL_HAS_ANON_STRUCT__ - /* .xyzw and .s0123...{f|F} are supported */ - #define CL_HAS_NAMED_VECTOR_FIELDS 1 - /* .hi and .lo are supported */ - #define CL_HAS_HI_LO_VECTOR_FIELDS 1 -#endif - -/* Define cl_vector types */ - -/* ---- cl_charn ---- */ -typedef union -{ - cl_char CL_ALIGNED(2) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_char x, y; }; - __CL_ANON_STRUCT__ struct{ cl_char s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_char lo, hi; }; -#endif -#if defined( __CL_CHAR2__) - __cl_char2 v2; -#endif -}cl_char2; - -typedef union -{ - cl_char CL_ALIGNED(4) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_char x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_char s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_char2 lo, hi; }; -#endif -#if defined( __CL_CHAR2__) - __cl_char2 v2[2]; -#endif -#if defined( __CL_CHAR4__) - __cl_char4 v4; -#endif -}cl_char4; - -/* cl_char3 is identical in size, alignment and behavior to cl_char4. See section 6.1.5. */ -typedef cl_char4 cl_char3; - -typedef union -{ - cl_char CL_ALIGNED(8) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_char x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_char s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_char4 lo, hi; }; -#endif -#if defined( __CL_CHAR2__) - __cl_char2 v2[4]; -#endif -#if defined( __CL_CHAR4__) - __cl_char4 v4[2]; -#endif -#if defined( __CL_CHAR8__ ) - __cl_char8 v8; -#endif -}cl_char8; - -typedef union -{ - cl_char CL_ALIGNED(16) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_char x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_char s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_char8 lo, hi; }; -#endif -#if defined( __CL_CHAR2__) - __cl_char2 v2[8]; -#endif -#if defined( __CL_CHAR4__) - __cl_char4 v4[4]; -#endif -#if defined( __CL_CHAR8__ ) - __cl_char8 v8[2]; -#endif -#if defined( __CL_CHAR16__ ) - __cl_char16 v16; -#endif -}cl_char16; - - -/* ---- cl_ucharn ---- */ -typedef union -{ - cl_uchar CL_ALIGNED(2) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_uchar x, y; }; - __CL_ANON_STRUCT__ struct{ cl_uchar s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_uchar lo, hi; }; -#endif -#if defined( __cl_uchar2__) - __cl_uchar2 v2; -#endif -}cl_uchar2; - -typedef union -{ - cl_uchar CL_ALIGNED(4) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_uchar x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_uchar s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_uchar2 lo, hi; }; -#endif -#if defined( __CL_UCHAR2__) - __cl_uchar2 v2[2]; -#endif -#if defined( __CL_UCHAR4__) - __cl_uchar4 v4; -#endif -}cl_uchar4; - -/* cl_uchar3 is identical in size, alignment and behavior to cl_uchar4. See section 6.1.5. */ -typedef cl_uchar4 cl_uchar3; - -typedef union -{ - cl_uchar CL_ALIGNED(8) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_uchar x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_uchar s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_uchar4 lo, hi; }; -#endif -#if defined( __CL_UCHAR2__) - __cl_uchar2 v2[4]; -#endif -#if defined( __CL_UCHAR4__) - __cl_uchar4 v4[2]; -#endif -#if defined( __CL_UCHAR8__ ) - __cl_uchar8 v8; -#endif -}cl_uchar8; - -typedef union -{ - cl_uchar CL_ALIGNED(16) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_uchar x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_uchar s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_uchar8 lo, hi; }; -#endif -#if defined( __CL_UCHAR2__) - __cl_uchar2 v2[8]; -#endif -#if defined( __CL_UCHAR4__) - __cl_uchar4 v4[4]; -#endif -#if defined( __CL_UCHAR8__ ) - __cl_uchar8 v8[2]; -#endif -#if defined( __CL_UCHAR16__ ) - __cl_uchar16 v16; -#endif -}cl_uchar16; - - -/* ---- cl_shortn ---- */ -typedef union -{ - cl_short CL_ALIGNED(4) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_short x, y; }; - __CL_ANON_STRUCT__ struct{ cl_short s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_short lo, hi; }; -#endif -#if defined( __CL_SHORT2__) - __cl_short2 v2; -#endif -}cl_short2; - -typedef union -{ - cl_short CL_ALIGNED(8) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_short x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_short s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_short2 lo, hi; }; -#endif -#if defined( __CL_SHORT2__) - __cl_short2 v2[2]; -#endif -#if defined( __CL_SHORT4__) - __cl_short4 v4; -#endif -}cl_short4; - -/* cl_short3 is identical in size, alignment and behavior to cl_short4. See section 6.1.5. */ -typedef cl_short4 cl_short3; - -typedef union -{ - cl_short CL_ALIGNED(16) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_short x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_short s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_short4 lo, hi; }; -#endif -#if defined( __CL_SHORT2__) - __cl_short2 v2[4]; -#endif -#if defined( __CL_SHORT4__) - __cl_short4 v4[2]; -#endif -#if defined( __CL_SHORT8__ ) - __cl_short8 v8; -#endif -}cl_short8; - -typedef union -{ - cl_short CL_ALIGNED(32) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_short x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_short s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_short8 lo, hi; }; -#endif -#if defined( __CL_SHORT2__) - __cl_short2 v2[8]; -#endif -#if defined( __CL_SHORT4__) - __cl_short4 v4[4]; -#endif -#if defined( __CL_SHORT8__ ) - __cl_short8 v8[2]; -#endif -#if defined( __CL_SHORT16__ ) - __cl_short16 v16; -#endif -}cl_short16; - - -/* ---- cl_ushortn ---- */ -typedef union -{ - cl_ushort CL_ALIGNED(4) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_ushort x, y; }; - __CL_ANON_STRUCT__ struct{ cl_ushort s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_ushort lo, hi; }; -#endif -#if defined( __CL_USHORT2__) - __cl_ushort2 v2; -#endif -}cl_ushort2; - -typedef union -{ - cl_ushort CL_ALIGNED(8) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_ushort x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_ushort s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_ushort2 lo, hi; }; -#endif -#if defined( __CL_USHORT2__) - __cl_ushort2 v2[2]; -#endif -#if defined( __CL_USHORT4__) - __cl_ushort4 v4; -#endif -}cl_ushort4; - -/* cl_ushort3 is identical in size, alignment and behavior to cl_ushort4. See section 6.1.5. */ -typedef cl_ushort4 cl_ushort3; - -typedef union -{ - cl_ushort CL_ALIGNED(16) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_ushort x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_ushort s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_ushort4 lo, hi; }; -#endif -#if defined( __CL_USHORT2__) - __cl_ushort2 v2[4]; -#endif -#if defined( __CL_USHORT4__) - __cl_ushort4 v4[2]; -#endif -#if defined( __CL_USHORT8__ ) - __cl_ushort8 v8; -#endif -}cl_ushort8; - -typedef union -{ - cl_ushort CL_ALIGNED(32) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_ushort x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_ushort s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_ushort8 lo, hi; }; -#endif -#if defined( __CL_USHORT2__) - __cl_ushort2 v2[8]; -#endif -#if defined( __CL_USHORT4__) - __cl_ushort4 v4[4]; -#endif -#if defined( __CL_USHORT8__ ) - __cl_ushort8 v8[2]; -#endif -#if defined( __CL_USHORT16__ ) - __cl_ushort16 v16; -#endif -}cl_ushort16; - -/* ---- cl_intn ---- */ -typedef union -{ - cl_int CL_ALIGNED(8) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_int x, y; }; - __CL_ANON_STRUCT__ struct{ cl_int s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_int lo, hi; }; -#endif -#if defined( __CL_INT2__) - __cl_int2 v2; -#endif -}cl_int2; - -typedef union -{ - cl_int CL_ALIGNED(16) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_int x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_int s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_int2 lo, hi; }; -#endif -#if defined( __CL_INT2__) - __cl_int2 v2[2]; -#endif -#if defined( __CL_INT4__) - __cl_int4 v4; -#endif -}cl_int4; - -/* cl_int3 is identical in size, alignment and behavior to cl_int4. See section 6.1.5. */ -typedef cl_int4 cl_int3; - -typedef union -{ - cl_int CL_ALIGNED(32) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_int x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_int s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_int4 lo, hi; }; -#endif -#if defined( __CL_INT2__) - __cl_int2 v2[4]; -#endif -#if defined( __CL_INT4__) - __cl_int4 v4[2]; -#endif -#if defined( __CL_INT8__ ) - __cl_int8 v8; -#endif -}cl_int8; - -typedef union -{ - cl_int CL_ALIGNED(64) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_int x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_int s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_int8 lo, hi; }; -#endif -#if defined( __CL_INT2__) - __cl_int2 v2[8]; -#endif -#if defined( __CL_INT4__) - __cl_int4 v4[4]; -#endif -#if defined( __CL_INT8__ ) - __cl_int8 v8[2]; -#endif -#if defined( __CL_INT16__ ) - __cl_int16 v16; -#endif -}cl_int16; - - -/* ---- cl_uintn ---- */ -typedef union -{ - cl_uint CL_ALIGNED(8) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_uint x, y; }; - __CL_ANON_STRUCT__ struct{ cl_uint s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_uint lo, hi; }; -#endif -#if defined( __CL_UINT2__) - __cl_uint2 v2; -#endif -}cl_uint2; - -typedef union -{ - cl_uint CL_ALIGNED(16) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_uint x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_uint s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_uint2 lo, hi; }; -#endif -#if defined( __CL_UINT2__) - __cl_uint2 v2[2]; -#endif -#if defined( __CL_UINT4__) - __cl_uint4 v4; -#endif -}cl_uint4; - -/* cl_uint3 is identical in size, alignment and behavior to cl_uint4. See section 6.1.5. */ -typedef cl_uint4 cl_uint3; - -typedef union -{ - cl_uint CL_ALIGNED(32) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_uint x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_uint s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_uint4 lo, hi; }; -#endif -#if defined( __CL_UINT2__) - __cl_uint2 v2[4]; -#endif -#if defined( __CL_UINT4__) - __cl_uint4 v4[2]; -#endif -#if defined( __CL_UINT8__ ) - __cl_uint8 v8; -#endif -}cl_uint8; - -typedef union -{ - cl_uint CL_ALIGNED(64) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_uint x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_uint s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_uint8 lo, hi; }; -#endif -#if defined( __CL_UINT2__) - __cl_uint2 v2[8]; -#endif -#if defined( __CL_UINT4__) - __cl_uint4 v4[4]; -#endif -#if defined( __CL_UINT8__ ) - __cl_uint8 v8[2]; -#endif -#if defined( __CL_UINT16__ ) - __cl_uint16 v16; -#endif -}cl_uint16; - -/* ---- cl_longn ---- */ -typedef union -{ - cl_long CL_ALIGNED(16) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_long x, y; }; - __CL_ANON_STRUCT__ struct{ cl_long s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_long lo, hi; }; -#endif -#if defined( __CL_LONG2__) - __cl_long2 v2; -#endif -}cl_long2; - -typedef union -{ - cl_long CL_ALIGNED(32) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_long x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_long s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_long2 lo, hi; }; -#endif -#if defined( __CL_LONG2__) - __cl_long2 v2[2]; -#endif -#if defined( __CL_LONG4__) - __cl_long4 v4; -#endif -}cl_long4; - -/* cl_long3 is identical in size, alignment and behavior to cl_long4. See section 6.1.5. */ -typedef cl_long4 cl_long3; - -typedef union -{ - cl_long CL_ALIGNED(64) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_long x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_long s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_long4 lo, hi; }; -#endif -#if defined( __CL_LONG2__) - __cl_long2 v2[4]; -#endif -#if defined( __CL_LONG4__) - __cl_long4 v4[2]; -#endif -#if defined( __CL_LONG8__ ) - __cl_long8 v8; -#endif -}cl_long8; - -typedef union -{ - cl_long CL_ALIGNED(128) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_long x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_long s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_long8 lo, hi; }; -#endif -#if defined( __CL_LONG2__) - __cl_long2 v2[8]; -#endif -#if defined( __CL_LONG4__) - __cl_long4 v4[4]; -#endif -#if defined( __CL_LONG8__ ) - __cl_long8 v8[2]; -#endif -#if defined( __CL_LONG16__ ) - __cl_long16 v16; -#endif -}cl_long16; - - -/* ---- cl_ulongn ---- */ -typedef union -{ - cl_ulong CL_ALIGNED(16) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_ulong x, y; }; - __CL_ANON_STRUCT__ struct{ cl_ulong s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_ulong lo, hi; }; -#endif -#if defined( __CL_ULONG2__) - __cl_ulong2 v2; -#endif -}cl_ulong2; - -typedef union -{ - cl_ulong CL_ALIGNED(32) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_ulong x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_ulong s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_ulong2 lo, hi; }; -#endif -#if defined( __CL_ULONG2__) - __cl_ulong2 v2[2]; -#endif -#if defined( __CL_ULONG4__) - __cl_ulong4 v4; -#endif -}cl_ulong4; - -/* cl_ulong3 is identical in size, alignment and behavior to cl_ulong4. See section 6.1.5. */ -typedef cl_ulong4 cl_ulong3; - -typedef union -{ - cl_ulong CL_ALIGNED(64) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_ulong x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_ulong s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_ulong4 lo, hi; }; -#endif -#if defined( __CL_ULONG2__) - __cl_ulong2 v2[4]; -#endif -#if defined( __CL_ULONG4__) - __cl_ulong4 v4[2]; -#endif -#if defined( __CL_ULONG8__ ) - __cl_ulong8 v8; -#endif -}cl_ulong8; - -typedef union -{ - cl_ulong CL_ALIGNED(128) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_ulong x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_ulong s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_ulong8 lo, hi; }; -#endif -#if defined( __CL_ULONG2__) - __cl_ulong2 v2[8]; -#endif -#if defined( __CL_ULONG4__) - __cl_ulong4 v4[4]; -#endif -#if defined( __CL_ULONG8__ ) - __cl_ulong8 v8[2]; -#endif -#if defined( __CL_ULONG16__ ) - __cl_ulong16 v16; -#endif -}cl_ulong16; - - -/* --- cl_floatn ---- */ - -typedef union -{ - cl_float CL_ALIGNED(8) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_float x, y; }; - __CL_ANON_STRUCT__ struct{ cl_float s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_float lo, hi; }; -#endif -#if defined( __CL_FLOAT2__) - __cl_float2 v2; -#endif -}cl_float2; - -typedef union -{ - cl_float CL_ALIGNED(16) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_float x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_float s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_float2 lo, hi; }; -#endif -#if defined( __CL_FLOAT2__) - __cl_float2 v2[2]; -#endif -#if defined( __CL_FLOAT4__) - __cl_float4 v4; -#endif -}cl_float4; - -/* cl_float3 is identical in size, alignment and behavior to cl_float4. See section 6.1.5. */ -typedef cl_float4 cl_float3; - -typedef union -{ - cl_float CL_ALIGNED(32) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_float x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_float s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_float4 lo, hi; }; -#endif -#if defined( __CL_FLOAT2__) - __cl_float2 v2[4]; -#endif -#if defined( __CL_FLOAT4__) - __cl_float4 v4[2]; -#endif -#if defined( __CL_FLOAT8__ ) - __cl_float8 v8; -#endif -}cl_float8; - -typedef union -{ - cl_float CL_ALIGNED(64) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_float x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_float s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_float8 lo, hi; }; -#endif -#if defined( __CL_FLOAT2__) - __cl_float2 v2[8]; -#endif -#if defined( __CL_FLOAT4__) - __cl_float4 v4[4]; -#endif -#if defined( __CL_FLOAT8__ ) - __cl_float8 v8[2]; -#endif -#if defined( __CL_FLOAT16__ ) - __cl_float16 v16; -#endif -}cl_float16; - -/* --- cl_doublen ---- */ - -typedef union -{ - cl_double CL_ALIGNED(16) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_double x, y; }; - __CL_ANON_STRUCT__ struct{ cl_double s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_double lo, hi; }; -#endif -#if defined( __CL_DOUBLE2__) - __cl_double2 v2; -#endif -}cl_double2; - -typedef union -{ - cl_double CL_ALIGNED(32) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_double x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_double s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_double2 lo, hi; }; -#endif -#if defined( __CL_DOUBLE2__) - __cl_double2 v2[2]; -#endif -#if defined( __CL_DOUBLE4__) - __cl_double4 v4; -#endif -}cl_double4; - -/* cl_double3 is identical in size, alignment and behavior to cl_double4. See section 6.1.5. */ -typedef cl_double4 cl_double3; - -typedef union -{ - cl_double CL_ALIGNED(64) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_double x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_double s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_double4 lo, hi; }; -#endif -#if defined( __CL_DOUBLE2__) - __cl_double2 v2[4]; -#endif -#if defined( __CL_DOUBLE4__) - __cl_double4 v4[2]; -#endif -#if defined( __CL_DOUBLE8__ ) - __cl_double8 v8; -#endif -}cl_double8; - -typedef union -{ - cl_double CL_ALIGNED(128) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_double x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_double s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_double8 lo, hi; }; -#endif -#if defined( __CL_DOUBLE2__) - __cl_double2 v2[8]; -#endif -#if defined( __CL_DOUBLE4__) - __cl_double4 v4[4]; -#endif -#if defined( __CL_DOUBLE8__ ) - __cl_double8 v8[2]; -#endif -#if defined( __CL_DOUBLE16__ ) - __cl_double16 v16; -#endif -}cl_double16; - -/* Macro to facilitate debugging - * Usage: - * Place CL_PROGRAM_STRING_DEBUG_INFO on the line before the first line of your source. - * The first line ends with: CL_PROGRAM_STRING_DEBUG_INFO \" - * Each line thereafter of OpenCL C source must end with: \n\ - * The last line ends in "; - * - * Example: - * - * const char *my_program = CL_PROGRAM_STRING_DEBUG_INFO "\ - * kernel void foo( int a, float * b ) \n\ - * { \n\ - * // my comment \n\ - * *b[ get_global_id(0)] = a; \n\ - * } \n\ - * "; - * - * This should correctly set up the line, (column) and file information for your source - * string so you can do source level debugging. - */ -#define __CL_STRINGIFY( _x ) # _x -#define _CL_STRINGIFY( _x ) __CL_STRINGIFY( _x ) -#define CL_PROGRAM_STRING_DEBUG_INFO "#line " _CL_STRINGIFY(__LINE__) " \"" __FILE__ "\" \n\n" - -#ifdef __cplusplus -} -#endif - -#undef __CL_HAS_ANON_STRUCT__ -#undef __CL_ANON_STRUCT__ -#if defined( _WIN32) && (_MSC_VER >= 1500) -#pragma warning( pop ) -#endif - -#endif /* __CL_PLATFORM_H */ diff --git a/third_party/opencl/include/CL/opencl.h b/third_party/opencl/include/CL/opencl.h deleted file mode 100644 index 9855cd75e..000000000 --- a/third_party/opencl/include/CL/opencl.h +++ /dev/null @@ -1,59 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - ******************************************************************************/ - -/* $Revision: 11708 $ on $Date: 2010-06-13 23:36:24 -0700 (Sun, 13 Jun 2010) $ */ - -#ifndef __OPENCL_H -#define __OPENCL_H - -#ifdef __cplusplus -extern "C" { -#endif - -#ifdef __APPLE__ - -#include -#include -#include -#include - -#else - -#include -#include -#include -#include - -#endif - -#ifdef __cplusplus -} -#endif - -#endif /* __OPENCL_H */ - diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index d4cfc6728..b79d046fc 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -57,11 +57,9 @@ base_frameworks = qt_env['FRAMEWORKS'] base_libs = [common, messaging, cereal, visionipc, 'm', 'ssl', 'crypto', 'pthread'] + qt_env["LIBS"] if arch == "Darwin": - base_frameworks.append('OpenCL') base_frameworks.append('QtCharts') base_frameworks.append('QtSerialBus') else: - base_libs.append('OpenCL') base_libs.append('Qt5Charts') base_libs.append('Qt5SerialBus') diff --git a/tools/install_ubuntu_dependencies.sh b/tools/install_ubuntu_dependencies.sh index 5c2131d4b..f428e9972 100755 --- a/tools/install_ubuntu_dependencies.sh +++ b/tools/install_ubuntu_dependencies.sh @@ -58,9 +58,6 @@ function install_ubuntu_common_requirements() { libzmq3-dev \ libzstd-dev \ libsqlite3-dev \ - opencl-headers \ - ocl-icd-libopencl1 \ - ocl-icd-opencl-dev \ portaudio19-dev \ qttools5-dev-tools \ libqt5svg5-dev \ diff --git a/tools/replay/SConscript b/tools/replay/SConscript index 136c4119f..698ab9885 100644 --- a/tools/replay/SConscript +++ b/tools/replay/SConscript @@ -6,11 +6,6 @@ replay_env['CCFLAGS'] += ['-Wno-deprecated-declarations'] base_frameworks = [] base_libs = [common, messaging, cereal, visionipc, 'm', 'ssl', 'crypto', 'pthread'] -if arch == "Darwin": - base_frameworks.append('OpenCL') -else: - base_libs.append('OpenCL') - replay_lib_src = ["replay.cc", "consoleui.cc", "camera.cc", "filereader.cc", "logreader.cc", "framereader.cc", "route.cc", "util.cc", "seg_mgr.cc", "timeline.cc", "api.cc"] if arch != "Darwin": diff --git a/tools/webcam/README.md b/tools/webcam/README.md index 67ad2cc8c..6abbc4793 100644 --- a/tools/webcam/README.md +++ b/tools/webcam/README.md @@ -10,10 +10,6 @@ What's needed: ## Setup openpilot - Follow [this readme](../README.md) to install and build the requirements -- Install OpenCL Driver (Ubuntu) -``` -sudo apt install pocl-opencl-icd -``` ## Connect the hardware - Connect the camera first From 81bd8aa0e262ed2a5cfb024d1214d0a34231c352 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 8 Feb 2026 01:03:16 -0500 Subject: [PATCH 856/910] [bot] Update Python packages (#1662) * Update Python packages * bump --------- Co-authored-by: github-actions[bot] Co-authored-by: Jason Wen --- docs/CARS.md | 11 +---------- opendbc_repo | 2 +- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index a1f50e41d..ab1eabeb1 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -4,7 +4,7 @@ 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. -# 344 Supported Cars +# 335 Supported Cars |Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Hardware Needed
 |Video|Setup Video| |---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| @@ -23,10 +23,7 @@ A supported vehicle is one that just works when you install a comma device. All |Audi|S3 2015-17|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Chevrolet|Bolt EUV 2022-23|Premier or Premier Redline Trim, without Super Cruise Package|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 harness box
- 1 mount
Buy Here
||| |Chevrolet|Bolt EV 2022-23|2LT Trim with Adaptive Cruise Control Package|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 harness box
- 1 mount
Buy Here
||| -|Chevrolet|Bolt EV Non-ACC 2017|Adaptive Cruise Control (ACC)|Stock|24 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 harness box
- 1 mount
Buy Here
||| -|Chevrolet|Bolt EV Non-ACC 2018-21|Adaptive Cruise Control (ACC)|Stock|24 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 harness box
- 1 mount
Buy Here
||| |Chevrolet|Equinox 2019-22|Adaptive Cruise Control (ACC)|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 harness box
- 1 mount
Buy Here
||| -|Chevrolet|Malibu Non-ACC 2016-23|Adaptive Cruise Control (ACC)|Stock|24 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 harness box
- 1 mount
Buy Here
||| |Chevrolet|Silverado 1500 2020-21|Safety Package II|openpilot available[1](#footnotes)|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 harness box
- 1 mount
Buy Here
||| |Chevrolet|Trailblazer 2021-22|Adaptive Cruise Control (ACC)|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 harness box
- 1 mount
Buy Here
||| |Chrysler|Pacifica 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| @@ -91,7 +88,6 @@ A supported vehicle is one that just works when you install a comma device. All |Honda|Civic Hatchback Hybrid 2025-26|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Honda|Civic Hatchback Hybrid (Europe only) 2023|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Honda|Civic Hybrid 2025-26|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Honda|Clarity 2018-21|Honda Sensing|openpilot|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Nidec connector + Honda Clarity Proxy Board
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Honda|CR-V 2015-16|Touring Trim|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Honda|CR-V 2017-22|Honda Sensing|openpilot available[1](#footnotes)|0 mph|15 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Honda|CR-V 2023-26|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| @@ -122,7 +118,6 @@ A supported vehicle is one that just works when you install a comma device. All |Hyundai|Elantra 2021-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Hyundai|Elantra GT 2017-20|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Hyundai|Elantra Hybrid 2021-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Hyundai|Elantra Non-SCC 2022|No Smart Cruise Control (Non-SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Hyundai|Genesis 2015-16|Smart Cruise Control (SCC)|Stock|19 mph|37 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai J connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Hyundai|i30 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Hyundai|Ioniq 5 (Southeast Asia and Europe only) 2022-24|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai Q connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| @@ -140,9 +135,7 @@ A supported vehicle is one that just works when you install a comma device. All |Hyundai|Kona Electric 2018-21|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai G connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Hyundai|Kona Electric 2022-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai O connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Hyundai|Kona Electric (with HDA II, Korea only) 2023|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai R connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Hyundai|Kona Electric Non-SCC 2019|No Smart Cruise Control (Non-SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai G connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Hyundai|Kona Hybrid 2020|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai I connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Hyundai|Kona Non-SCC 2019|No Smart Cruise Control (Non-SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai B connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Hyundai|Nexo 2021|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Hyundai|Palisade 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Hyundai|Santa Cruz 2022-24|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| @@ -166,13 +159,11 @@ A supported vehicle is one that just works when you install a comma device. All |Kia|Carnival 2022-24|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Kia|Carnival (China only) 2023|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Kia|Ceed 2019-21|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Kia|Ceed Plug-in Hybrid Non-SCC 2022|No Smart Cruise Control (Non-SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai I connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Kia|EV6 (Southeast Asia only) 2022-24|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai P connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Kia|EV6 (with HDA II) 2022-24|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai P connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Kia|EV6 (without HDA II) 2022-24|Highway Driving Assist|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Kia|Forte 2019-21|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|6 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai G connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Kia|Forte 2022-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Kia|Forte Non-SCC 2019|No Smart Cruise Control (Non-SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai G connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Kia|K5 2021-24|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Kia|K5 Hybrid 2020-22|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Kia|K8 Hybrid (with HDA II) 2023|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai Q connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| diff --git a/opendbc_repo b/opendbc_repo index 1abdb9872..ff2f9686c 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 1abdb9872f22c361322ac9e29d376c35233ba890 +Subproject commit ff2f9686c208824a72b30d4cc540a6ab78c5983e From 46d65095af09d372735676539c9cabcc37453ccc Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 7 Feb 2026 23:04:01 -0800 Subject: [PATCH 857/910] CI: garbage collect tmp jenkins branches (#37125) --- .github/workflows/jenkins-pr-trigger.yaml | 39 ++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/.github/workflows/jenkins-pr-trigger.yaml b/.github/workflows/jenkins-pr-trigger.yaml index f8a53c5ae..fdd26253f 100644 --- a/.github/workflows/jenkins-pr-trigger.yaml +++ b/.github/workflows/jenkins-pr-trigger.yaml @@ -5,7 +5,44 @@ on: types: [created, edited] jobs: - # TODO: gc old branches in a separate job in this workflow + cleanup-branches: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Delete stale Jenkins branches + uses: actions/github-script@v8 + with: + script: | + const cutoff = Date.now() - 24 * 60 * 60 * 1000; + const prefixes = ['tmp-jenkins', '__jenkins']; + + for await (const response of github.paginate.iterator(github.rest.repos.listBranches, { + owner: context.repo.owner, + repo: context.repo.repo, + per_page: 100, + })) { + for (const branch of response.data) { + if (!prefixes.some(p => branch.name.startsWith(p))) continue; + + const { data: commit } = await github.rest.repos.getCommit({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: branch.commit.sha, + }); + + const commitDate = new Date(commit.commit.committer.date).getTime(); + if (commitDate < cutoff) { + console.log(`Deleting branch: ${branch.name} (last commit: ${commit.commit.committer.date})`); + await github.rest.git.deleteRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: `heads/${branch.name}`, + }); + } + } + } + scan-comments: runs-on: ubuntu-latest if: ${{ github.event.issue.pull_request }} From ac087d085ec599a3ed8a8d487ccec2b8fc56c75e Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 8 Feb 2026 09:59:33 -0800 Subject: [PATCH 858/910] Build vendored artifacts in CI (#37127) * Build vendored artifacts in CI * parallel * deterministic * fix up * fix gitignores * lil more * lil more consistency --- .github/workflows/vendor_third_party.yaml | 51 ++++++++++++++++++ third_party/acados/.gitignore | 6 ++- .../acados/acados_template/gnsf/__init__.py | 1 + third_party/acados/build.sh | 3 ++ third_party/acados/x86_64/lib/libacados.so | 4 +- third_party/acados/x86_64/lib/libblasfeo.so | 4 +- third_party/acados/x86_64/lib/libhpipm.so | 4 +- .../acados/x86_64/lib/libqpOASES_e.so.3.1 | 4 +- third_party/acados/x86_64/t_renderer | 4 +- third_party/build.sh | 53 +++++++++++++++++++ third_party/libyuv/.gitignore | 3 +- third_party/libyuv/build.sh | 3 ++ third_party/libyuv/larch64/lib/libyuv.a | 2 +- third_party/libyuv/x86_64/include | 1 - third_party/libyuv/x86_64/lib/libyuv.a | 4 +- third_party/raylib/.gitignore | 1 + third_party/raylib/Darwin/libraylib.a | 4 +- third_party/raylib/build.sh | 3 ++ third_party/raylib/larch64/libraylib.a | 2 +- third_party/raylib/x86_64/libraylib.a | 4 +- 20 files changed, 140 insertions(+), 21 deletions(-) create mode 100644 .github/workflows/vendor_third_party.yaml create mode 100644 third_party/acados/acados_template/gnsf/__init__.py create mode 100755 third_party/build.sh delete mode 120000 third_party/libyuv/x86_64/include diff --git a/.github/workflows/vendor_third_party.yaml b/.github/workflows/vendor_third_party.yaml new file mode 100644 index 000000000..df50cfad2 --- /dev/null +++ b/.github/workflows/vendor_third_party.yaml @@ -0,0 +1,51 @@ +name: vendor third_party + +on: + workflow_dispatch: + +jobs: + build: + if: github.ref != 'refs/heads/master' + strategy: + matrix: + os: [ubuntu-24.04, macos-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v6 + with: + submodules: true + - name: Build + run: third_party/build.sh + - name: Package artifacts + run: | + git add -A third_party/ + git diff --cached --name-only -- third_party/ | tar -cf /tmp/third_party_build.tar -T - + - uses: actions/upload-artifact@v4 + with: + name: third-party-${{ runner.os }} + path: /tmp/third_party_build.tar + + commit: + needs: build + runs-on: ubuntu-24.04 + permissions: + contents: write + steps: + - uses: actions/checkout@v6 + - uses: actions/download-artifact@v4 + with: + path: /tmp/artifacts + - name: Commit vendored libraries + run: | + for f in /tmp/artifacts/*/third_party_build.tar; do + tar xf "$f" + done + git add third_party/ + if git diff --cached --quiet; then + echo "No changes to commit" + exit 0 + fi + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git commit -m "third_party: rebuild vendor libraries" + git push diff --git a/third_party/acados/.gitignore b/third_party/acados/.gitignore index 68858c62e..0ae664dff 100644 --- a/third_party/acados/.gitignore +++ b/third_party/acados/.gitignore @@ -1,5 +1,9 @@ acados_repo/ -lib +/lib !x86_64/ !larch64/ !aarch64/ +!Darwin/ +!*.so +!*.so.* +!*.dylib diff --git a/third_party/acados/acados_template/gnsf/__init__.py b/third_party/acados/acados_template/gnsf/__init__.py new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/third_party/acados/acados_template/gnsf/__init__.py @@ -0,0 +1 @@ + diff --git a/third_party/acados/build.sh b/third_party/acados/build.sh index 2b803ef6b..2c4d839ae 100755 --- a/third_party/acados/build.sh +++ b/third_party/acados/build.sh @@ -1,6 +1,9 @@ #!/usr/bin/env bash set -e +export SOURCE_DATE_EPOCH=0 +export ZERO_AR_DATE=1 + DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" ARCHNAME="x86_64" diff --git a/third_party/acados/x86_64/lib/libacados.so b/third_party/acados/x86_64/lib/libacados.so index 4e80f7c76..50d647a86 100644 --- a/third_party/acados/x86_64/lib/libacados.so +++ b/third_party/acados/x86_64/lib/libacados.so @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:821ce18f417d211c4845b60482d465b809f90dc7d04f023d652d8221e87679b1 -size 553544 +oid sha256:05a1ba3cf37fa929cdd56f892608b2f89c35a05ef1b07fedb86b2f0d76607263 +size 540488 diff --git a/third_party/acados/x86_64/lib/libblasfeo.so b/third_party/acados/x86_64/lib/libblasfeo.so index 26d5a3dbe..a98f45abd 100644 --- a/third_party/acados/x86_64/lib/libblasfeo.so +++ b/third_party/acados/x86_64/lib/libblasfeo.so @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3feea7927d004064bbc5a13c3287467669ce801cb0a3c616cf9e089816da5a0b -size 2155088 +oid sha256:c0bf22898d9c59b672d3d0961f5f4c804b9957478125d99eb297de3091bedd15 +size 2416112 diff --git a/third_party/acados/x86_64/lib/libhpipm.so b/third_party/acados/x86_64/lib/libhpipm.so index 40e2e4e7d..f40cb487c 100644 --- a/third_party/acados/x86_64/lib/libhpipm.so +++ b/third_party/acados/x86_64/lib/libhpipm.so @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a042716f515913786581dff39799eb71fc66caddfa18b1c9f0d54f00c1568fd2 -size 1572648 +oid sha256:5b6875fb47940764d4ebb916c2373cb0e04929229feb654b290676c28d48fa9d +size 1531024 diff --git a/third_party/acados/x86_64/lib/libqpOASES_e.so.3.1 b/third_party/acados/x86_64/lib/libqpOASES_e.so.3.1 index cf5e550fa..81afd059f 100644 --- a/third_party/acados/x86_64/lib/libqpOASES_e.so.3.1 +++ b/third_party/acados/x86_64/lib/libqpOASES_e.so.3.1 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a6abea4815e3f03cff06fe8a9602e97f9acf102f18f803571460a94595b93be4 -size 262824 +oid sha256:04be908c3f707e5c968022b9cdd79ab75ae7af46e7fa019ceee98f854ddd3f64 +size 262464 diff --git a/third_party/acados/x86_64/t_renderer b/third_party/acados/x86_64/t_renderer index e995a209b..d41f6c372 100755 --- a/third_party/acados/x86_64/t_renderer +++ b/third_party/acados/x86_64/t_renderer @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7a360d4b53826b91ada3358156d44a14d497bdd8ace88707fd4b386ed6d194c7 -size 17503920 +oid sha256:a53ae46650c4df5b0ddb87a658f59a0422e41743e8bc2d822da0aefd1d280791 +size 5088536 diff --git a/third_party/build.sh b/third_party/build.sh new file mode 100755 index 000000000..d3a9c6579 --- /dev/null +++ b/third_party/build.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +set -e + +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" + +# Reproducible builds: pin timestamps to epoch +export SOURCE_DATE_EPOCH=0 +export ZERO_AR_DATE=1 + +pids=() +names=() +logs=() + +for script in "$DIR"/*/build.sh; do + [ -f "$script" ] || continue + name=$(basename "$(dirname "$script")") + log=$(mktemp) + names+=("$name") + logs+=("$log") + (cd "$(dirname "$script")" && bash "$(basename "$script")") >"$log" 2>&1 & + pids+=($!) +done + +failed=0 +for i in "${!pids[@]}"; do + echo "--- ${names[$i]} ---" + if wait "${pids[$i]}"; then + echo "OK" + else + echo "FAILED (exit $?)" + failed=1 + fi + cat "${logs[$i]}" + rm -f "${logs[$i]}" + echo +done + +[ $failed -ne 0 ] && exit $failed + +# Repack ar archives with deterministic headers (zero timestamps/uid/gid) +# Skip foreign-platform archives that ar can't read (e.g. Mach-O on Linux) +while IFS= read -r -d '' lib; do + tmpdir=$(mktemp -d) + lib=$(realpath "$lib") + if (cd "$tmpdir" && ar x "$lib" 2>/dev/null); then + (cd "$tmpdir" && ar Drcs repacked.a * && mv repacked.a "$lib") + fi + rm -rf "$tmpdir" +done < <(find "$DIR" -name '*.a' \ + \( -path '*/x86_64/*' -o -path '*/Darwin/*' -o -path '*/larch64/*' -o -path '*/aarch64/*' \) \ + -print0) + +echo -e "\033[32mAll third_party builds succeeded.\033[0m" diff --git a/third_party/libyuv/.gitignore b/third_party/libyuv/.gitignore index 450712e47..1e943ae6c 100644 --- a/third_party/libyuv/.gitignore +++ b/third_party/libyuv/.gitignore @@ -1 +1,2 @@ -libyuv/ +/libyuv/ +!*.a diff --git a/third_party/libyuv/build.sh b/third_party/libyuv/build.sh index b960f60ef..35a7d947a 100755 --- a/third_party/libyuv/build.sh +++ b/third_party/libyuv/build.sh @@ -1,6 +1,9 @@ #!/usr/bin/env bash set -e +export SOURCE_DATE_EPOCH=0 +export ZERO_AR_DATE=1 + DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" ARCHNAME=$(uname -m) diff --git a/third_party/libyuv/larch64/lib/libyuv.a b/third_party/libyuv/larch64/lib/libyuv.a index 1c9125023..9c4a32bcd 100644 --- a/third_party/libyuv/larch64/lib/libyuv.a +++ b/third_party/libyuv/larch64/lib/libyuv.a @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:320bef5a75a62dd2731a496040921d5000f1ed237ae70fd7aeb6c010a1534363 +oid sha256:adafce26582e425164df7af36253ce58e3ed1dba9965650745c93bd96e42e976 size 462482 diff --git a/third_party/libyuv/x86_64/include b/third_party/libyuv/x86_64/include deleted file mode 120000 index f5030fe88..000000000 --- a/third_party/libyuv/x86_64/include +++ /dev/null @@ -1 +0,0 @@ -../include \ No newline at end of file diff --git a/third_party/libyuv/x86_64/lib/libyuv.a b/third_party/libyuv/x86_64/lib/libyuv.a index 8915f167d..391b1c876 100644 --- a/third_party/libyuv/x86_64/lib/libyuv.a +++ b/third_party/libyuv/x86_64/lib/libyuv.a @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e21a3bd8df01cf4ce5461e7bf6654239196036c3f829255145265c7bf31a791d -size 511974 +oid sha256:00f9759c67c6fa21657fabde9e096478ea5809716989599f673f638f039431e5 +size 504790 diff --git a/third_party/raylib/.gitignore b/third_party/raylib/.gitignore index c4afad9c3..6b1d3ad74 100644 --- a/third_party/raylib/.gitignore +++ b/third_party/raylib/.gitignore @@ -1,3 +1,4 @@ /raylib_repo/ /raylib_python_repo/ /wheel/ +!*.a diff --git a/third_party/raylib/Darwin/libraylib.a b/third_party/raylib/Darwin/libraylib.a index 837ad8818..dd2e9b33f 100644 --- a/third_party/raylib/Darwin/libraylib.a +++ b/third_party/raylib/Darwin/libraylib.a @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7ffe1fc6497f0c111fc507988e94fd29ce4db53a4876dc82ab9267895ad82584 -size 6515352 +oid sha256:fd045c1d4bca5c9b2ad044ea730826ff6cedeef0b64451b123717b136f1cd702 +size 6392532 diff --git a/third_party/raylib/build.sh b/third_party/raylib/build.sh index 7f2ce5951..d20f9d33a 100755 --- a/third_party/raylib/build.sh +++ b/third_party/raylib/build.sh @@ -1,6 +1,9 @@ #!/usr/bin/env bash set -e +export SOURCE_DATE_EPOCH=0 +export ZERO_AR_DATE=1 + SUDO="" # Use sudo if not root diff --git a/third_party/raylib/larch64/libraylib.a b/third_party/raylib/larch64/libraylib.a index 4e810c8b7..fa538e521 100644 --- a/third_party/raylib/larch64/libraylib.a +++ b/third_party/raylib/larch64/libraylib.a @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:91e9a07513e84f7b553da01b34b24e12fe7130131ef73ebdb3dac3b838db815b +oid sha256:f760af8b4693cf60e3760341e5275890d78d933da2354c4bad0572ec575b970a size 2001860 diff --git a/third_party/raylib/x86_64/libraylib.a b/third_party/raylib/x86_64/libraylib.a index cf6948256..ea124c1bc 100644 --- a/third_party/raylib/x86_64/libraylib.a +++ b/third_party/raylib/x86_64/libraylib.a @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f0b8f59758fe1291be82a8bda7a7ca05629c7addb0683936dd404ed08e19e143 -size 2769684 +oid sha256:3c928e849b51b04d8e3603cd649184299efed0e9e0fb02201612b967b31efd73 +size 2771092 From 35c87a151972c88f8251f8d877be615ced253334 Mon Sep 17 00:00:00 2001 From: royjr Date: Sun, 8 Feb 2026 17:00:37 -0500 Subject: [PATCH 859/910] [TIZI/TICI] ui: steering arc (#1628) * init * lint * add toggle * Update params_keys.h * Update params_metadata.json * Update params_keys.h * bool * decouple * no * make it perfect * fade it * only with torque bar * dynamic * in another PR --------- Co-authored-by: Jason Wen --- common/params_keys.h | 1 + selfdrive/ui/mici/onroad/torque_bar.py | 16 ++++++++------- selfdrive/ui/onroad/augmented_road_view.py | 8 +++++--- .../sunnypilot/onroad/augmented_road_view.py | 20 ++++++++++++++++++- .../ui/sunnypilot/onroad/hud_renderer.py | 9 +++++++++ selfdrive/ui/sunnypilot/ui_state.py | 1 + sunnypilot/sunnylink/params_metadata.json | 4 ++++ 7 files changed, 48 insertions(+), 11 deletions(-) diff --git a/common/params_keys.h b/common/params_keys.h index ecc656cc7..44584c911 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -115,6 +115,7 @@ inline static std::unordered_map keys = { {"SnoozeUpdate", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}}, {"SshEnabled", {PERSISTENT | BACKUP, BOOL}}, {"TermsVersion", {PERSISTENT, STRING}}, + {"TorqueBar", {PERSISTENT | BACKUP, BOOL, "0"}}, {"TrainingVersion", {PERSISTENT, STRING}}, {"UbloxAvailable", {PERSISTENT, BOOL}}, {"UpdateAvailable", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BOOL}}, diff --git a/selfdrive/ui/mici/onroad/torque_bar.py b/selfdrive/ui/mici/onroad/torque_bar.py index c8485a310..c1de69463 100644 --- a/selfdrive/ui/mici/onroad/torque_bar.py +++ b/selfdrive/ui/mici/onroad/torque_bar.py @@ -146,9 +146,11 @@ def arc_bar_pts(cx: float, cy: float, class TorqueBar(Widget): - def __init__(self, demo: bool = False): + def __init__(self, demo: bool = False, scale: float = 1.0, always: bool = False): super().__init__() self._demo = demo + self._scale = scale + self._always = always self._torque_filter = FirstOrderFilter(0, 0.1, 1 / gui_app.target_fps) self._torque_line_alpha_filter = FirstOrderFilter(0.0, 0.1, 1 / gui_app.target_fps) @@ -180,8 +182,8 @@ class TorqueBar(Widget): def _render(self, rect: rl.Rectangle) -> None: # adjust y pos with torque - torque_line_offset = np.interp(abs(self._torque_filter.x), [0.5, 1], [22, 26]) - torque_line_height = np.interp(abs(self._torque_filter.x), [0.5, 1], [14, 56]) + torque_line_offset = np.interp(abs(self._torque_filter.x), [0.5, 1], [22 * self._scale, 26 * self._scale]) + torque_line_height = np.interp(abs(self._torque_filter.x), [0.5, 1], [14 * self._scale, 56 * self._scale]) # animate alpha and angle span if not self._demo: @@ -195,7 +197,7 @@ class TorqueBar(Widget): torque_line_bg_color = rl.Color(255, 255, 255, int(255 * 0.15 * self._torque_line_alpha_filter.x)) # draw curved line polygon torque bar - torque_line_radius = 1200 + torque_line_radius = 1200 * self._scale top_angle = -90 torque_bg_angle_span = self._torque_line_alpha_filter.x * TORQUE_ANGLE_SPAN torque_start_angle = top_angle - torque_bg_angle_span / 2 @@ -207,13 +209,13 @@ class TorqueBar(Widget): cy = rect.y + rect.height + torque_line_radius - torque_line_offset # draw bg torque indicator line - bg_pts = arc_bar_pts(cx, cy, mid_r, torque_line_height, torque_start_angle, torque_end_angle) + bg_pts = arc_bar_pts(cx, cy, mid_r, torque_line_height, torque_start_angle, torque_end_angle, cap_radius=7 * self._scale) draw_polygon(rect, bg_pts, color=torque_line_bg_color) # draw torque indicator line a0s = top_angle a1s = a0s + torque_bg_angle_span / 2 * self._torque_filter.x - sl_pts = arc_bar_pts(cx, cy, mid_r, torque_line_height, a0s, a1s) + sl_pts = arc_bar_pts(cx, cy, mid_r, torque_line_height, a0s, a1s, cap_radius=7 * self._scale) # draw beautiful gradient from center to 65% of the bg torque bar width start_grad_pt = cx / rect.width @@ -252,5 +254,5 @@ class TorqueBar(Widget): # draw center torque bar dot if abs(self._torque_filter.x) < 0.5: dot_y = self._rect.y + self._rect.height - torque_line_offset - torque_line_height / 2 - rl.draw_circle(int(cx), int(dot_y), 10 // 2, + rl.draw_circle(int(cx), int(dot_y), (10 // 2 * self._scale), rl.Color(182, 182, 182, int(255 * 0.9 * self._torque_line_alpha_filter.x))) diff --git a/selfdrive/ui/onroad/augmented_road_view.py b/selfdrive/ui/onroad/augmented_road_view.py index bcbcb2dcf..76e7b078d 100644 --- a/selfdrive/ui/onroad/augmented_road_view.py +++ b/selfdrive/ui/onroad/augmented_road_view.py @@ -15,7 +15,7 @@ from openpilot.common.transformations.camera import DEVICE_CAMERAS, DeviceCamera from openpilot.common.transformations.orientation import rot_from_euler if gui_app.sunnypilot_ui(): - from openpilot.selfdrive.ui.sunnypilot.onroad.augmented_road_view import BORDER_COLORS_SP + from openpilot.selfdrive.ui.sunnypilot.onroad.augmented_road_view import BORDER_COLORS_SP, AugmentedRoadViewSP from openpilot.selfdrive.ui.sunnypilot.onroad.driver_state import DriverStateRendererSP as DriverStateRenderer from openpilot.selfdrive.ui.sunnypilot.onroad.hud_renderer import HudRendererSP as HudRenderer from openpilot.selfdrive.ui.sunnypilot.ui_state import OnroadTimerStatus @@ -38,9 +38,10 @@ ROAD_CAM_MIN_SPEED = 15.0 # m/s (34 mph) INF_POINT = np.array([1000.0, 0.0, 0.0]) -class AugmentedRoadView(CameraView): +class AugmentedRoadView(CameraView, AugmentedRoadViewSP): def __init__(self, stream_type: VisionStreamType = VisionStreamType.VISION_STREAM_ROAD): - super().__init__("camerad", stream_type) + CameraView.__init__(self, "camerad", stream_type) + AugmentedRoadViewSP.__init__(self) self._set_placeholder_color(BORDER_COLORS[UIStatus.DISENGAGED]) self.device_camera: DeviceCameraConfig | None = None @@ -92,6 +93,7 @@ class AugmentedRoadView(CameraView): # Draw all UI overlays self.model_renderer.render(self._content_rect) + AugmentedRoadViewSP.update_fade_out_bottom_overlay(self, self._content_rect) self._hud_renderer.render(self._content_rect) self.alert_renderer.render(self._content_rect) self.driver_state_renderer.render(self._content_rect) diff --git a/selfdrive/ui/sunnypilot/onroad/augmented_road_view.py b/selfdrive/ui/sunnypilot/onroad/augmented_road_view.py index 0a5739cc0..c7dedee54 100644 --- a/selfdrive/ui/sunnypilot/onroad/augmented_road_view.py +++ b/selfdrive/ui/sunnypilot/onroad/augmented_road_view.py @@ -5,9 +5,27 @@ This file is part of sunnypilot and is licensed under the MIT License. See the LICENSE.md file in the root directory for more details. """ import pyray as rl -from openpilot.selfdrive.ui.ui_state import UIStatus +from openpilot.common.filter_simple import FirstOrderFilter +from openpilot.selfdrive.ui.ui_state import UIStatus, ui_state +from openpilot.system.ui.lib.application import gui_app BORDER_COLORS_SP = { UIStatus.LAT_ONLY: rl.Color(0x00, 0xC8, 0xC8, 0xFF), # Cyan for lateral-only state UIStatus.LONG_ONLY: rl.Color(0x96, 0x1C, 0xA8, 0xFF), # Purple for longitudinal-only state } + + +class AugmentedRoadViewSP: + def __init__(self): + self._fade_texture = gui_app.texture("icons_mici/onroad/onroad_fade.png") + self._fade_alpha_filter = FirstOrderFilter(0, 0.1, 1 / gui_app.target_fps) + + def update_fade_out_bottom_overlay(self, _content_rect): + # Fade out bottom of overlays for looks (only when engaged) + fade_alpha = self._fade_alpha_filter.update(ui_state.status != UIStatus.DISENGAGED) + if ui_state.torque_bar and fade_alpha > 1e-2: + # Scale the fade texture to the content rect + rl.draw_texture_pro(self._fade_texture, + rl.Rectangle(0, 0, self._fade_texture.width, self._fade_texture.height), + _content_rect, rl.Vector2(0, 0), 0.0, + rl.Color(255, 255, 255, int(255 * fade_alpha))) diff --git a/selfdrive/ui/sunnypilot/onroad/hud_renderer.py b/selfdrive/ui/sunnypilot/onroad/hud_renderer.py index 8ca726980..74e15fe0b 100644 --- a/selfdrive/ui/sunnypilot/onroad/hud_renderer.py +++ b/selfdrive/ui/sunnypilot/onroad/hud_renderer.py @@ -6,6 +6,7 @@ See the LICENSE.md file in the root directory for more details. """ import pyray as rl +from openpilot.selfdrive.ui.mici.onroad.torque_bar import TorqueBar from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.selfdrive.ui.onroad.hud_renderer import HudRenderer from openpilot.selfdrive.ui.sunnypilot.onroad.developer_ui import DeveloperUiRenderer @@ -23,6 +24,7 @@ class HudRendererSP(HudRenderer): self.rocket_fuel = RocketFuel() self.speed_limit_renderer = SpeedLimitRenderer() self.turn_signal_controller = TurnSignalController() + self._torque_bar = TorqueBar(scale=3.0, always=True) def _update_state(self) -> None: super()._update_state() @@ -32,6 +34,13 @@ class HudRendererSP(HudRenderer): def _render(self, rect: rl.Rectangle) -> None: super()._render(rect) + + if ui_state.torque_bar and ui_state.sm['controlsState'].lateralControlState.which() != 'angleState': + torque_rect = rect + if ui_state.developer_ui in (DeveloperUiRenderer.DEV_UI_BOTTOM, DeveloperUiRenderer.DEV_UI_BOTH): + torque_rect = rl.Rectangle(rect.x, rect.y, rect.width, rect.height - DeveloperUiRenderer.BOTTOM_BAR_HEIGHT) + self._torque_bar.render(torque_rect) + self.developer_ui.render(rect) self.road_name_renderer.render(rect) self.speed_limit_renderer.render(rect) diff --git a/selfdrive/ui/sunnypilot/ui_state.py b/selfdrive/ui/sunnypilot/ui_state.py index f38280d49..98ddbe526 100644 --- a/selfdrive/ui/sunnypilot/ui_state.py +++ b/selfdrive/ui/sunnypilot/ui_state.py @@ -125,6 +125,7 @@ class UIStateSP: self.rocket_fuel = self.params.get_bool("RocketFuel") self.rainbow_path = self.params.get_bool("RainbowMode") self.chevron_metrics = self.params.get("ChevronInfo") + self.torque_bar = self.params.get_bool("TorqueBar") self.active_bundle = self.params.get("ModelManager_ActiveBundle") self.custom_interactive_timeout = self.params.get("InteractivityTimeout", return_default=True) self.speed_limit_mode = self.params.get("SpeedLimitMode", return_default=True) diff --git a/sunnypilot/sunnylink/params_metadata.json b/sunnypilot/sunnylink/params_metadata.json index 7f597a962..090beb49f 100644 --- a/sunnypilot/sunnylink/params_metadata.json +++ b/sunnypilot/sunnylink/params_metadata.json @@ -1239,6 +1239,10 @@ "title": "Tesla Coop Steering", "description": "" }, + "TorqueBar": { + "title": "Steering Arc", + "description": "[TIZI/TICI only] Display steering arc on the driving screen when lateral control is enabled." + }, "TorqueParamsOverrideEnabled": { "title": "Manual Real-Time Tuning", "description": "" From c274dba36ed91f82eb1ad8cddafe9de2ea09c0c7 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 8 Feb 2026 18:28:36 -0500 Subject: [PATCH 860/910] [TIZI/TICI] ui: Smart Cruise Control elements (#1675) * init * punch * lower * colors --- .../ui/sunnypilot/onroad/hud_renderer.py | 4 + .../sunnypilot/onroad/smart_cruise_control.py | 131 ++++++++++++++++++ 2 files changed, 135 insertions(+) create mode 100644 selfdrive/ui/sunnypilot/onroad/smart_cruise_control.py diff --git a/selfdrive/ui/sunnypilot/onroad/hud_renderer.py b/selfdrive/ui/sunnypilot/onroad/hud_renderer.py index 74e15fe0b..d6f9278d2 100644 --- a/selfdrive/ui/sunnypilot/onroad/hud_renderer.py +++ b/selfdrive/ui/sunnypilot/onroad/hud_renderer.py @@ -13,6 +13,7 @@ from openpilot.selfdrive.ui.sunnypilot.onroad.developer_ui import DeveloperUiRen from openpilot.selfdrive.ui.sunnypilot.onroad.road_name import RoadNameRenderer from openpilot.selfdrive.ui.sunnypilot.onroad.rocket_fuel import RocketFuel from openpilot.selfdrive.ui.sunnypilot.onroad.speed_limit import SpeedLimitRenderer +from openpilot.selfdrive.ui.sunnypilot.onroad.smart_cruise_control import SmartCruiseControlRenderer from openpilot.selfdrive.ui.sunnypilot.onroad.turn_signal import TurnSignalController @@ -23,6 +24,7 @@ class HudRendererSP(HudRenderer): self.road_name_renderer = RoadNameRenderer() self.rocket_fuel = RocketFuel() self.speed_limit_renderer = SpeedLimitRenderer() + self.smart_cruise_control_renderer = SmartCruiseControlRenderer() self.turn_signal_controller = TurnSignalController() self._torque_bar = TorqueBar(scale=3.0, always=True) @@ -30,6 +32,7 @@ class HudRendererSP(HudRenderer): super()._update_state() self.road_name_renderer.update() self.speed_limit_renderer.update() + self.smart_cruise_control_renderer.update() self.turn_signal_controller.update() def _render(self, rect: rl.Rectangle) -> None: @@ -44,6 +47,7 @@ class HudRendererSP(HudRenderer): self.developer_ui.render(rect) self.road_name_renderer.render(rect) self.speed_limit_renderer.render(rect) + self.smart_cruise_control_renderer.render(rect) self.turn_signal_controller.render(rect) if ui_state.rocket_fuel: diff --git a/selfdrive/ui/sunnypilot/onroad/smart_cruise_control.py b/selfdrive/ui/sunnypilot/onroad/smart_cruise_control.py new file mode 100644 index 000000000..ca71fcac4 --- /dev/null +++ b/selfdrive/ui/sunnypilot/onroad/smart_cruise_control.py @@ -0,0 +1,131 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +import pyray as rl + +from openpilot.selfdrive.ui.onroad.hud_renderer import COLORS +from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.system.ui.lib.application import gui_app, FontWeight +from openpilot.system.ui.lib.text_measure import measure_text_cached +from openpilot.system.ui.widgets import Widget + + +class SmartCruiseControlRenderer(Widget): + def __init__(self): + super().__init__() + self.vision_enabled = False + self.vision_active = False + self.vision_frame = 0 + self.map_enabled = False + self.map_active = False + self.map_frame = 0 + self.long_override = False + + self.font = gui_app.font(FontWeight.BOLD) + self.scc_tex = rl.load_render_texture(256, 128) + + def update(self): + sm = ui_state.sm + if sm.updated["longitudinalPlanSP"]: + lp_sp = sm["longitudinalPlanSP"] + vision = lp_sp.smartCruiseControl.vision + map_ = lp_sp.smartCruiseControl.map + + self.vision_enabled = vision.enabled + self.vision_active = vision.active + self.map_enabled = map_.enabled + self.map_active = map_.active + + if sm.updated["carControl"]: + self.long_override = sm["carControl"].cruiseControl.override + + if self.vision_active: + self.vision_frame += 1 + else: + self.vision_frame = 0 + + if self.map_active: + self.map_frame += 1 + else: + self.map_frame = 0 + + @staticmethod + def _pulse_element(frame): + return not (frame % gui_app.target_fps < (gui_app.target_fps / 2.5)) + + def _draw_icon(self, rect_center_x, rect_height, x_offset, y_offset, name): + text = name + font_size = 36 + padding_v = 5 + box_width = 160 + + sz = measure_text_cached(self.font, text, font_size) + box_height = int(sz.y + padding_v * 2) + + texture_width = 256 + texture_height = 128 + + rl.begin_texture_mode(self.scc_tex) + rl.clear_background(rl.Color(0, 0, 0, 0)) + + if self.long_override: + box_color = COLORS.OVERRIDE + else: + box_color = rl.Color(0, 255, 0, 255) + + # Center box in texture + box_x = (texture_width - box_width) // 2 + box_y = (texture_height - box_height) // 2 + + rl.draw_rectangle_rounded(rl.Rectangle(box_x, box_y, box_width, box_height), 0.2, 10, box_color) + + # Draw text with custom blend mode to punch hole + rl.rl_set_blend_factors(rl.RL_ZERO, rl.RL_ONE_MINUS_SRC_ALPHA, 0x8006) + rl.rl_set_blend_mode(rl.BLEND_CUSTOM) + + text_pos_x = box_x + (box_width - sz.x) / 2 + text_pos_y = box_y + (box_height - sz.y) / 2 + + rl.draw_text_ex(self.font, text, rl.Vector2(text_pos_x, text_pos_y), font_size, 0, rl.WHITE) + + rl.rl_set_blend_mode(rl.BLEND_ALPHA) # Reset + rl.end_texture_mode() + + screen_y = rect_height / 4 + y_offset + + dest_x = rect_center_x + x_offset - texture_width / 2 + dest_y = screen_y - texture_height / 2 + + src_rect = rl.Rectangle(0, 0, texture_width, -texture_height) + dst_rect = rl.Rectangle(dest_x, dest_y, texture_width, texture_height) + + rl.draw_texture_pro(self.scc_tex.texture, src_rect, dst_rect, rl.Vector2(0, 0), 0, rl.WHITE) + + def _render(self, rect: rl.Rectangle): + x_offset = -260 + y1_offset = -40 + y2_offset = -100 + + orders = [y1_offset, y2_offset] + y_scc_v = 0 + y_scc_m = 0 + idx = 0 + + if self.vision_enabled: + y_scc_v = orders[idx] + idx += 1 + + if self.map_enabled: + y_scc_m = orders[idx] + idx += 1 + + scc_vision_pulse = self._pulse_element(self.vision_frame) + if (self.vision_enabled and not self.vision_active) or (self.vision_active and scc_vision_pulse): + self._draw_icon(rect.x + rect.width / 2, rect.height, x_offset, y_scc_v, "SCC-V") + + scc_map_pulse = self._pulse_element(self.map_frame) + if (self.map_enabled and not self.map_active) or (self.map_active and scc_map_pulse): + self._draw_icon(rect.x + rect.width / 2, rect.height, x_offset, y_scc_m, "SCC-M") From 020f503364c3a4380de23175aace486f04d88828 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 8 Feb 2026 19:14:19 -0500 Subject: [PATCH 861/910] [TIZI/TICI] ui: Green Light and Lead Departure elements (#1676) * init * big for now * only adjust for right dev ui for now * final * final final --- selfdrive/ui/sunnypilot/onroad/e2e_alerts.py | 101 ++++++++++++++++++ .../ui/sunnypilot/onroad/hud_renderer.py | 4 + 2 files changed, 105 insertions(+) create mode 100644 selfdrive/ui/sunnypilot/onroad/e2e_alerts.py diff --git a/selfdrive/ui/sunnypilot/onroad/e2e_alerts.py b/selfdrive/ui/sunnypilot/onroad/e2e_alerts.py new file mode 100644 index 000000000..b15db94c1 --- /dev/null +++ b/selfdrive/ui/sunnypilot/onroad/e2e_alerts.py @@ -0,0 +1,101 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +import pyray as rl + +from cereal import log +from openpilot.selfdrive.ui import UI_BORDER_SIZE +from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.selfdrive.ui.sunnypilot.onroad.developer_ui import DeveloperUiRenderer +from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE +from openpilot.system.ui.lib.text_measure import measure_text_cached + + +class E2eAlertsRenderer: + def __init__(self): + self._green_light_alert_img = gui_app.texture("../../sunnypilot/selfdrive/assets/images/green_light.png", 250, 250) + self._lead_depart_alert_img = gui_app.texture("../../sunnypilot/selfdrive/assets/images/lead_depart.png", 250, 250) + + self._e2e_alert_display_timer = 0 + self._e2e_alert_frame = 0 + self._green_light_alert = False + self._lead_depart_alert = False + self._alert_text = "" + self._alert_img = None + self._allow_e2e_alerts = False + + def update(self) -> None: + sm = ui_state.sm + lp_sp = sm['longitudinalPlanSP'] + self._green_light_alert = lp_sp.e2eAlerts.greenLightAlert + self._lead_depart_alert = lp_sp.e2eAlerts.leadDepartAlert + + self._allow_e2e_alerts = sm['selfdriveState'].alertSize == log.SelfdriveState.AlertSize.none and \ + sm.recv_frame['driverStateV2'] > ui_state.started_frame + + if self._green_light_alert or self._lead_depart_alert: + self._e2e_alert_display_timer = 3 * gui_app.target_fps + + if self._e2e_alert_display_timer > 0: + self._e2e_alert_frame += 1 + self._e2e_alert_display_timer -= 1 + + if self._green_light_alert: + self._alert_text = "GREEN\nLIGHT" + self._alert_img = self._green_light_alert_img + elif self._lead_depart_alert: + self._alert_text = "LEAD VEHICLE\nDEPARTING" + self._alert_img = self._lead_depart_alert_img + else: + self._e2e_alert_frame = 0 + + def render(self, rect: rl.Rectangle) -> None: + if not self._allow_e2e_alerts or self._e2e_alert_display_timer <= 0: + return + + e2e_alert_size = 250 + dev_ui_width_adjustment = 180 if ui_state.developer_ui in (DeveloperUiRenderer.DEV_UI_RIGHT, DeveloperUiRenderer.DEV_UI_BOTH) else 100 + + x = rect.x + rect.width - e2e_alert_size - dev_ui_width_adjustment - (UI_BORDER_SIZE * 3) + y = rect.y + rect.height / 2 + 20 + + alert_rect = rl.Rectangle(x - e2e_alert_size, y - e2e_alert_size, e2e_alert_size * 2, e2e_alert_size * 2) + center = rl.Vector2(alert_rect.x + alert_rect.width / 2, alert_rect.y + alert_rect.height / 2) + + # Pulse logic + is_pulsing = (self._e2e_alert_frame % gui_app.target_fps) < (gui_app.target_fps / 2.5) + frame_color = rl.Color(255, 255, 255, 75) if is_pulsing else rl.Color(0, 255, 0, 75) + + # Draw Circle + rl.draw_circle_v(center, e2e_alert_size, rl.Color(0, 0, 0, 190)) + # Draw Ring (Border) + rl.draw_ring(center, e2e_alert_size - 7.5, e2e_alert_size + 7.5, 0, 360, 0, frame_color) + + # Draw Image + if self._alert_img: + img_x = int(center.x - self._alert_img.width / 2) + img_y = int(center.y - self._alert_img.height / 2) + rl.draw_texture(self._alert_img, img_x, img_y, rl.WHITE) + + # Draw Text + txt_color = rl.Color(255, 255, 255, 255) if is_pulsing else rl.Color(0, 255, 0, 190) + font = gui_app.font(FontWeight.BOLD) + text_size = 48 + spacing = 0 + + lines = self._alert_text.split('\n') + + # Position text at bottom of alert circle + bottom_y = (alert_rect.y + alert_rect.height) - (alert_rect.height / 7) + + # Draw lines upwards from bottom + current_y = bottom_y - (len(lines) * text_size * FONT_SCALE) + + for line in lines: + measure = measure_text_cached(font, line, text_size, spacing) + line_x = center.x - measure.x / 2 + rl.draw_text_ex(font, line, rl.Vector2(line_x, current_y), text_size, spacing, txt_color) + current_y += text_size * FONT_SCALE diff --git a/selfdrive/ui/sunnypilot/onroad/hud_renderer.py b/selfdrive/ui/sunnypilot/onroad/hud_renderer.py index d6f9278d2..d99e3cb0d 100644 --- a/selfdrive/ui/sunnypilot/onroad/hud_renderer.py +++ b/selfdrive/ui/sunnypilot/onroad/hud_renderer.py @@ -15,6 +15,7 @@ from openpilot.selfdrive.ui.sunnypilot.onroad.rocket_fuel import RocketFuel from openpilot.selfdrive.ui.sunnypilot.onroad.speed_limit import SpeedLimitRenderer from openpilot.selfdrive.ui.sunnypilot.onroad.smart_cruise_control import SmartCruiseControlRenderer from openpilot.selfdrive.ui.sunnypilot.onroad.turn_signal import TurnSignalController +from openpilot.selfdrive.ui.sunnypilot.onroad.e2e_alerts import E2eAlertsRenderer class HudRendererSP(HudRenderer): @@ -26,6 +27,7 @@ class HudRendererSP(HudRenderer): self.speed_limit_renderer = SpeedLimitRenderer() self.smart_cruise_control_renderer = SmartCruiseControlRenderer() self.turn_signal_controller = TurnSignalController() + self.e2e_alerts_renderer = E2eAlertsRenderer() self._torque_bar = TorqueBar(scale=3.0, always=True) def _update_state(self) -> None: @@ -34,6 +36,7 @@ class HudRendererSP(HudRenderer): self.speed_limit_renderer.update() self.smart_cruise_control_renderer.update() self.turn_signal_controller.update() + self.e2e_alerts_renderer.update() def _render(self, rect: rl.Rectangle) -> None: super()._render(rect) @@ -49,6 +52,7 @@ class HudRendererSP(HudRenderer): self.speed_limit_renderer.render(rect) self.smart_cruise_control_renderer.render(rect) self.turn_signal_controller.render(rect) + self.e2e_alerts_renderer.render(rect) if ui_state.rocket_fuel: self.rocket_fuel.render(rect, ui_state.sm) From a9229e11a067ef75c64565f4065b58f6bb38e404 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 8 Feb 2026 19:40:04 -0500 Subject: [PATCH 862/910] [TIZI/TICI] ui: standstill timer (#1677) * standstill timer * final --- .../{e2e_alerts.py => circular_alerts.py} | 59 ++++++++++++++++--- .../ui/sunnypilot/onroad/hud_renderer.py | 8 +-- selfdrive/ui/sunnypilot/ui_state.py | 1 + 3 files changed, 55 insertions(+), 13 deletions(-) rename selfdrive/ui/sunnypilot/onroad/{e2e_alerts.py => circular_alerts.py} (59%) diff --git a/selfdrive/ui/sunnypilot/onroad/e2e_alerts.py b/selfdrive/ui/sunnypilot/onroad/circular_alerts.py similarity index 59% rename from selfdrive/ui/sunnypilot/onroad/e2e_alerts.py rename to selfdrive/ui/sunnypilot/onroad/circular_alerts.py index b15db94c1..965ce7fe7 100644 --- a/selfdrive/ui/sunnypilot/onroad/e2e_alerts.py +++ b/selfdrive/ui/sunnypilot/onroad/circular_alerts.py @@ -14,7 +14,7 @@ from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE from openpilot.system.ui.lib.text_measure import measure_text_cached -class E2eAlertsRenderer: +class CircularAlertsRenderer: def __init__(self): self._green_light_alert_img = gui_app.texture("../../sunnypilot/selfdrive/assets/images/green_light.png", 250, 250) self._lead_depart_alert_img = gui_app.texture("../../sunnypilot/selfdrive/assets/images/lead_depart.png", 250, 250) @@ -23,6 +23,9 @@ class E2eAlertsRenderer: self._e2e_alert_frame = 0 self._green_light_alert = False self._lead_depart_alert = False + self._standstill_timer = False + self._standstill_elapsed_time = 0.0 + self._is_standstill = False self._alert_text = "" self._alert_img = None self._allow_e2e_alerts = False @@ -30,14 +33,22 @@ class E2eAlertsRenderer: def update(self) -> None: sm = ui_state.sm lp_sp = sm['longitudinalPlanSP'] + car_state = sm['carState'] self._green_light_alert = lp_sp.e2eAlerts.greenLightAlert self._lead_depart_alert = lp_sp.e2eAlerts.leadDepartAlert + self._standstill_timer = ui_state.standstill_timer + self._is_standstill = car_state.standstill + + if not ui_state.started: + self._standstill_elapsed_time = 0.0 self._allow_e2e_alerts = sm['selfdriveState'].alertSize == log.SelfdriveState.AlertSize.none and \ sm.recv_frame['driverStateV2'] > ui_state.started_frame if self._green_light_alert or self._lead_depart_alert: self._e2e_alert_display_timer = 3 * gui_app.target_fps + # reset onroad sleep timer for e2e alerts + ui_state.reset_onroad_sleep_timer() if self._e2e_alert_display_timer > 0: self._e2e_alert_frame += 1 @@ -49,11 +60,22 @@ class E2eAlertsRenderer: elif self._lead_depart_alert: self._alert_text = "LEAD VEHICLE\nDEPARTING" self._alert_img = self._lead_depart_alert_img + + elif self._standstill_timer and self._is_standstill: + self._alert_img = None + self._standstill_elapsed_time += 1.0 / gui_app.target_fps + minute = int(self._standstill_elapsed_time / 60) + second = int(self._standstill_elapsed_time - (minute * 60)) + self._alert_text = f"{minute:01d}:{second:02d}" + self._e2e_alert_frame += 1 + else: self._e2e_alert_frame = 0 + if not self._is_standstill: + self._standstill_elapsed_time = 0.0 def render(self, rect: rl.Rectangle) -> None: - if not self._allow_e2e_alerts or self._e2e_alert_display_timer <= 0: + if not self._allow_e2e_alerts or (self._e2e_alert_display_timer <= 0 and not (self._standstill_timer and self._is_standstill)): return e2e_alert_size = 250 @@ -67,7 +89,12 @@ class E2eAlertsRenderer: # Pulse logic is_pulsing = (self._e2e_alert_frame % gui_app.target_fps) < (gui_app.target_fps / 2.5) - frame_color = rl.Color(255, 255, 255, 75) if is_pulsing else rl.Color(0, 255, 0, 75) + + # Standstill Timer (STOPPED) should be static white + if self._e2e_alert_display_timer == 0 and self._standstill_timer and self._is_standstill: + frame_color = rl.Color(255, 255, 255, 75) + else: + frame_color = rl.Color(255, 255, 255, 75) if is_pulsing else rl.Color(0, 255, 0, 75) # Draw Circle rl.draw_circle_v(center, e2e_alert_size, rl.Color(0, 0, 0, 190)) @@ -75,7 +102,7 @@ class E2eAlertsRenderer: rl.draw_ring(center, e2e_alert_size - 7.5, e2e_alert_size + 7.5, 0, 360, 0, frame_color) # Draw Image - if self._alert_img: + if self._alert_img and self._e2e_alert_display_timer > 0: img_x = int(center.x - self._alert_img.width / 2) img_y = int(center.y - self._alert_img.height / 2) rl.draw_texture(self._alert_img, img_x, img_y, rl.WHITE) @@ -94,8 +121,22 @@ class E2eAlertsRenderer: # Draw lines upwards from bottom current_y = bottom_y - (len(lines) * text_size * FONT_SCALE) - for line in lines: - measure = measure_text_cached(font, line, text_size, spacing) - line_x = center.x - measure.x / 2 - rl.draw_text_ex(font, line, rl.Vector2(line_x, current_y), text_size, spacing, txt_color) - current_y += text_size * FONT_SCALE + if self._e2e_alert_display_timer == 0 and self._standstill_timer and self._is_standstill: + # Standstill Timer Text + alert_alt_text = "STOPPED" + top_text_size = 80 + measure_top = measure_text_cached(font, alert_alt_text, top_text_size, spacing) + top_y = alert_rect.y + alert_rect.height / 3.5 + rl.draw_text_ex(font, alert_alt_text, rl.Vector2(center.x - measure_top.x / 2, top_y), top_text_size, spacing, rl.Color(255, 175, 3, 240)) + + # Timer + timer_text_size = 100 + measure_timer = measure_text_cached(font, self._alert_text, timer_text_size, spacing) + timer_y = (alert_rect.y + alert_rect.height) - (alert_rect.height / 5) - measure_timer.y + rl.draw_text_ex(font, self._alert_text, rl.Vector2(center.x - measure_timer.x / 2, timer_y), timer_text_size, spacing, rl.WHITE) + else: + for line in lines: + measure = measure_text_cached(font, line, text_size, spacing) + line_x = center.x - measure.x / 2 + rl.draw_text_ex(font, line, rl.Vector2(line_x, current_y), text_size, spacing, txt_color) + current_y += text_size * FONT_SCALE diff --git a/selfdrive/ui/sunnypilot/onroad/hud_renderer.py b/selfdrive/ui/sunnypilot/onroad/hud_renderer.py index d99e3cb0d..f765936d6 100644 --- a/selfdrive/ui/sunnypilot/onroad/hud_renderer.py +++ b/selfdrive/ui/sunnypilot/onroad/hud_renderer.py @@ -15,7 +15,7 @@ from openpilot.selfdrive.ui.sunnypilot.onroad.rocket_fuel import RocketFuel from openpilot.selfdrive.ui.sunnypilot.onroad.speed_limit import SpeedLimitRenderer from openpilot.selfdrive.ui.sunnypilot.onroad.smart_cruise_control import SmartCruiseControlRenderer from openpilot.selfdrive.ui.sunnypilot.onroad.turn_signal import TurnSignalController -from openpilot.selfdrive.ui.sunnypilot.onroad.e2e_alerts import E2eAlertsRenderer +from openpilot.selfdrive.ui.sunnypilot.onroad.circular_alerts import CircularAlertsRenderer class HudRendererSP(HudRenderer): @@ -27,7 +27,7 @@ class HudRendererSP(HudRenderer): self.speed_limit_renderer = SpeedLimitRenderer() self.smart_cruise_control_renderer = SmartCruiseControlRenderer() self.turn_signal_controller = TurnSignalController() - self.e2e_alerts_renderer = E2eAlertsRenderer() + self.circular_alerts_renderer = CircularAlertsRenderer() self._torque_bar = TorqueBar(scale=3.0, always=True) def _update_state(self) -> None: @@ -36,7 +36,7 @@ class HudRendererSP(HudRenderer): self.speed_limit_renderer.update() self.smart_cruise_control_renderer.update() self.turn_signal_controller.update() - self.e2e_alerts_renderer.update() + self.circular_alerts_renderer.update() def _render(self, rect: rl.Rectangle) -> None: super()._render(rect) @@ -52,7 +52,7 @@ class HudRendererSP(HudRenderer): self.speed_limit_renderer.render(rect) self.smart_cruise_control_renderer.render(rect) self.turn_signal_controller.render(rect) - self.e2e_alerts_renderer.render(rect) + self.circular_alerts_renderer.render(rect) if ui_state.rocket_fuel: self.rocket_fuel.render(rect, ui_state.sm) diff --git a/selfdrive/ui/sunnypilot/ui_state.py b/selfdrive/ui/sunnypilot/ui_state.py index 98ddbe526..89a650bae 100644 --- a/selfdrive/ui/sunnypilot/ui_state.py +++ b/selfdrive/ui/sunnypilot/ui_state.py @@ -129,6 +129,7 @@ class UIStateSP: self.active_bundle = self.params.get("ModelManager_ActiveBundle") self.custom_interactive_timeout = self.params.get("InteractivityTimeout", return_default=True) self.speed_limit_mode = self.params.get("SpeedLimitMode", return_default=True) + self.standstill_timer = self.params.get_bool("StandstillTimer") # Onroad Screen Brightness self.onroad_brightness = int(float(self.params.get("OnroadScreenOffBrightness", return_default=True))) From 96b58024abcb25eed9d2e0a7a1917cd2fadaeff9 Mon Sep 17 00:00:00 2001 From: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Sun, 8 Feb 2026 16:54:33 -0800 Subject: [PATCH 863/910] [MICI] ui: driving models selector (#1574) * ui: models mici * Update models.py * Update models.py * sync --------- Co-authored-by: Jason Wen --- .../ui/sunnypilot/mici/layouts/models.py | 122 ++++++++++++++++++ .../ui/sunnypilot/mici/layouts/settings.py | 8 ++ 2 files changed, 130 insertions(+) create mode 100644 selfdrive/ui/sunnypilot/mici/layouts/models.py diff --git a/selfdrive/ui/sunnypilot/mici/layouts/models.py b/selfdrive/ui/sunnypilot/mici/layouts/models.py new file mode 100644 index 000000000..d5964ea96 --- /dev/null +++ b/selfdrive/ui/sunnypilot/mici/layouts/models.py @@ -0,0 +1,122 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +from collections.abc import Callable + +from cereal import custom +from openpilot.selfdrive.ui.mici.widgets.button import BigButton +from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.system.ui.lib.multilang import tr +from openpilot.system.ui.widgets import NavWidget, Widget +from openpilot.system.ui.widgets.scroller import Scroller + + +class ModelsLayoutMici(NavWidget): + def __init__(self, back_callback: Callable): + super().__init__() + self.set_back_callback(back_callback) + self.original_back_callback = back_callback + self.focused_widget = None + + self.current_model_btn = BigButton(tr("current model"), "", "") + self.current_model_btn.set_click_callback(self._show_folders) + + self.cancel_download_btn = BigButton(tr("cancel download"), "", "") + self.cancel_download_btn.set_click_callback(lambda: ui_state.params.remove("ModelManager_DownloadIndex")) + + self.main_items: list[Widget] = [self.current_model_btn, self.cancel_download_btn] + self._scroller = Scroller(self.main_items, snap_items=False) + + @property + def model_manager(self): + return ui_state.sm["modelManagerSP"] + + def _get_grouped_bundles(self): + bundles = self.model_manager.availableBundles + folders = {} + for bundle in bundles: + folder = next((override.value for override in bundle.overrides if override.key == "folder"), "") + folders.setdefault(folder, []).append(bundle) + return folders + + def _show_selection_view(self, items: list[Widget], back_callback: Callable): + self._scroller._items = items + for item in items: + item.set_touch_valid_callback(lambda: self._scroller.scroll_panel.is_touch_valid() and self._scroller.enabled) + self._scroller.scroll_panel.set_offset(0) + self.set_back_callback(back_callback) + + def _show_folders(self): + self.focused_widget = self.current_model_btn + folders = self._get_grouped_bundles() + folder_buttons = [] + default_btn = BigButton(tr("default model"), "", "") + default_btn.set_click_callback(self._select_default) + folder_buttons.append(default_btn) + + for folder in sorted(folders.keys(), key=lambda f: max((bundle.index for bundle in folders[f]), default=-1), reverse=True): + if folder.lower() in ["release models", "master models"]: + btn = BigButton(folder.lower(), "", "") + btn.set_click_callback(lambda f=folder: self._select_folder(f)) + folder_buttons.append(btn) + self._show_selection_view(folder_buttons, self._reset_main_view) + + def _select_model(self, bundle): + ui_state.params.put("ModelManager_DownloadIndex", bundle.index) + self._reset_main_view() + + def _select_default(self): + ui_state.params.remove("ModelManager_ActiveBundle") + self._reset_main_view() + + def _select_folder(self, folder_name): + folders = self._get_grouped_bundles() + bundles = sorted(folders.get(folder_name, []), key=lambda b: b.index, reverse=True) + + btns = [] + for bundle in bundles: + txt = bundle.displayName.lower() + btn = BigButton(txt, "", "") + btn.set_click_callback(lambda b=bundle: self._select_model(b)) + btns.append(btn) + self._show_selection_view(btns, self._show_folders) + + def _reset_main_view(self): + self._scroller._items = self.main_items + self.set_back_callback(self.original_back_callback) + if self.focused_widget and self.focused_widget in self.main_items: + x = self._scroller._pad_start + for item in self.main_items: + if not item.is_visible: + continue + if item == self.focused_widget: + break + x += item.rect.width + self._scroller._spacing + self._scroller.scroll_panel.set_offset(0) + self._scroller.scroll_to(x) + self.focused_widget = None + else: + self._scroller.scroll_panel.set_offset(0) + + def _update_state(self): + super()._update_state() + + manager = self.model_manager + if manager.selectedBundle and manager.selectedBundle.status == custom.ModelManagerSP.DownloadStatus.downloading: + self.current_model_btn.set_value("downloading...") + self.cancel_download_btn.set_visible(True) + else: + self.current_model_btn.set_value(manager.activeBundle.internalName.lower() if manager.activeBundle else tr("default model")) + self.cancel_download_btn.set_visible(False) + self.current_model_btn.set_enabled(ui_state.is_offroad()) + self.current_model_btn.set_text(tr("current model")) + + def _render(self, rect): + self._scroller.render(rect) + + def show_event(self): + super().show_event() + self._scroller.show_event() diff --git a/selfdrive/ui/sunnypilot/mici/layouts/settings.py b/selfdrive/ui/sunnypilot/mici/layouts/settings.py index f6fae6630..69982e229 100644 --- a/selfdrive/ui/sunnypilot/mici/layouts/settings.py +++ b/selfdrive/ui/sunnypilot/mici/layouts/settings.py @@ -9,6 +9,7 @@ from enum import IntEnum from openpilot.selfdrive.ui.mici.layouts.settings import settings as OP from openpilot.selfdrive.ui.mici.widgets.button import BigButton from openpilot.selfdrive.ui.sunnypilot.mici.layouts.sunnylink import SunnylinkLayoutMici +from openpilot.selfdrive.ui.sunnypilot.mici.layouts.models import ModelsLayoutMici ICON_SIZE = 70 @@ -16,6 +17,7 @@ OP.PanelType = IntEnum( "PanelType", [es.name for es in OP.PanelType] + [ "SUNNYLINK", + "MODELS", ], start=0, ) @@ -27,13 +29,19 @@ class SettingsLayoutSP(OP.SettingsLayout): sunnylink_btn = BigButton("sunnylink", "", "icons_mici/settings/developer/ssh.png") sunnylink_btn.set_click_callback(lambda: self._set_current_panel(OP.PanelType.SUNNYLINK)) + + models_btn = BigButton("models", "", "../../sunnypilot/selfdrive/assets/offroad/icon_models.png") + models_btn.set_click_callback(lambda: self._set_current_panel(OP.PanelType.MODELS)) + self._panels.update({ OP.PanelType.SUNNYLINK: OP.PanelInfo("sunnylink", SunnylinkLayoutMici(back_callback=lambda: self._set_current_panel(None))), + OP.PanelType.MODELS: OP.PanelInfo("models", ModelsLayoutMici(back_callback=lambda: self._set_current_panel(None))), }) items = self._scroller._items.copy() items.insert(1, sunnylink_btn) + items.insert(2, models_btn) self._scroller._items.clear() for item in items: self._scroller.add_widget(item) From 254f55ac15a40343d7255f2f098de3442e0c4a6f Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 8 Feb 2026 20:42:23 -0500 Subject: [PATCH 864/910] [TIZI/TICI] ui: Hide vEgo and True vEgo (#1678) --- .../ui/sunnypilot/onroad/hud_renderer.py | 6 +++ .../ui/sunnypilot/onroad/speed_renderer.py | 46 +++++++++++++++++++ selfdrive/ui/sunnypilot/ui_state.py | 2 + 3 files changed, 54 insertions(+) create mode 100644 selfdrive/ui/sunnypilot/onroad/speed_renderer.py diff --git a/selfdrive/ui/sunnypilot/onroad/hud_renderer.py b/selfdrive/ui/sunnypilot/onroad/hud_renderer.py index f765936d6..3b810d62e 100644 --- a/selfdrive/ui/sunnypilot/onroad/hud_renderer.py +++ b/selfdrive/ui/sunnypilot/onroad/hud_renderer.py @@ -16,6 +16,7 @@ from openpilot.selfdrive.ui.sunnypilot.onroad.speed_limit import SpeedLimitRende from openpilot.selfdrive.ui.sunnypilot.onroad.smart_cruise_control import SmartCruiseControlRenderer from openpilot.selfdrive.ui.sunnypilot.onroad.turn_signal import TurnSignalController from openpilot.selfdrive.ui.sunnypilot.onroad.circular_alerts import CircularAlertsRenderer +from openpilot.selfdrive.ui.sunnypilot.onroad.speed_renderer import SpeedRenderer class HudRendererSP(HudRenderer): @@ -28,6 +29,7 @@ class HudRendererSP(HudRenderer): self.smart_cruise_control_renderer = SmartCruiseControlRenderer() self.turn_signal_controller = TurnSignalController() self.circular_alerts_renderer = CircularAlertsRenderer() + self.speed_renderer = SpeedRenderer() self._torque_bar = TorqueBar(scale=3.0, always=True) def _update_state(self) -> None: @@ -37,6 +39,10 @@ class HudRendererSP(HudRenderer): self.smart_cruise_control_renderer.update() self.turn_signal_controller.update() self.circular_alerts_renderer.update() + self.speed_renderer.update() + + def _draw_current_speed(self, rect: rl.Rectangle) -> None: + self.speed_renderer.render(rect) def _render(self, rect: rl.Rectangle) -> None: super()._render(rect) diff --git a/selfdrive/ui/sunnypilot/onroad/speed_renderer.py b/selfdrive/ui/sunnypilot/onroad/speed_renderer.py new file mode 100644 index 000000000..0a017876e --- /dev/null +++ b/selfdrive/ui/sunnypilot/onroad/speed_renderer.py @@ -0,0 +1,46 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +import pyray as rl + +from openpilot.common.constants import CV +from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.system.ui.lib.application import gui_app, FontWeight +from openpilot.system.ui.lib.multilang import tr +from openpilot.system.ui.lib.text_measure import measure_text_cached +from openpilot.selfdrive.ui.onroad.hud_renderer import FONT_SIZES, COLORS + + +class SpeedRenderer: + def __init__(self): + self.speed: float = 0.0 + self.v_ego_cluster_seen: bool = False + + self._font_bold: rl.Font = gui_app.font(FontWeight.BOLD) + self._font_medium: rl.Font = gui_app.font(FontWeight.MEDIUM) + + def update(self) -> None: + car_state = ui_state.sm['carState'] + v_ego_cluster = car_state.vEgoCluster + self.v_ego_cluster_seen = self.v_ego_cluster_seen or v_ego_cluster != 0.0 + v_ego = v_ego_cluster if self.v_ego_cluster_seen and not ui_state.true_v_ego_ui else car_state.vEgo + speed_conversion = CV.MS_TO_KPH if ui_state.is_metric else CV.MS_TO_MPH + self.speed = max(0.0, v_ego * speed_conversion) + + def render(self, rect: rl.Rectangle) -> None: + if ui_state.hide_v_ego_ui: + return + + # Draw current speed and unit + speed_text = str(round(self.speed)) + speed_text_size = measure_text_cached(self._font_bold, speed_text, FONT_SIZES.current_speed) + speed_pos = rl.Vector2(rect.x + rect.width / 2 - speed_text_size.x / 2, 180 - speed_text_size.y / 2) + rl.draw_text_ex(self._font_bold, speed_text, speed_pos, FONT_SIZES.current_speed, 0, COLORS.WHITE) + + unit_text = tr("km/h") if ui_state.is_metric else tr("mph") + unit_text_size = measure_text_cached(self._font_medium, unit_text, FONT_SIZES.speed_unit) + unit_pos = rl.Vector2(rect.x + rect.width / 2 - unit_text_size.x / 2, 290 - unit_text_size.y / 2) + rl.draw_text_ex(self._font_medium, unit_text, unit_pos, FONT_SIZES.speed_unit, 0, COLORS.WHITE_TRANSLUCENT) diff --git a/selfdrive/ui/sunnypilot/ui_state.py b/selfdrive/ui/sunnypilot/ui_state.py index 89a650bae..6d7eab51c 100644 --- a/selfdrive/ui/sunnypilot/ui_state.py +++ b/selfdrive/ui/sunnypilot/ui_state.py @@ -130,6 +130,8 @@ class UIStateSP: self.custom_interactive_timeout = self.params.get("InteractivityTimeout", return_default=True) self.speed_limit_mode = self.params.get("SpeedLimitMode", return_default=True) self.standstill_timer = self.params.get_bool("StandstillTimer") + self.true_v_ego_ui = self.params.get_bool("TrueVEgoUI") + self.hide_v_ego_ui = self.params.get_bool("HideVEgoUI") # Onroad Screen Brightness self.onroad_brightness = int(float(self.params.get("OnroadScreenOffBrightness", return_default=True))) From 981494a35433e4edbef7b38ea6676a9cdafe121b Mon Sep 17 00:00:00 2001 From: Nayan Date: Mon, 9 Feb 2026 00:17:34 -0500 Subject: [PATCH 865/910] [TIZI/TICI] ui: Visuals panel (#1496) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * commaai/openpilot:d05cb31e2e916fba41ba8167030945f427fd811b * bump opendbc * bump opendbc * bump opendbc * bump opendbc * bump opendbc * sunnypilot: remove Qt * cabana: revert to stock Qt * commaai/openpilot:5198b1b079c37742c1050f02ce0aa6dd42b038b9 * commaai/openpilot:954b567b9ba0f3d1ae57d6aa7797fa86dd92ec6e * commaai/openpilot:7534b2a160faa683412c04c1254440e338931c5e * sum more * bump opendbc * not yet * should've been symlink'ed * raylib says wut * quiet mode back * more fixes * no more * too extra red diff on the side * need to bring this back * too extra * let's update docs here * Revert "let's update docs here" This reverts commit 51fe03cd5121e6fdf14657b2c33852c34922b851. * param to control stock vs sp ui * init styles * SP Toggles * Lint * optimizations * multi-button * Lint * param to control stock vs sp ui * init styles * SP Toggles * Lint * optimizations * Panels. With Icons. And Scroller. * patience, grasshopper * more patience, grasshopper * sp raylib preview * fix callback * fix ui preview * add ui previews * introducing ui_state_sp for py * param to control stock vs sp ui * better * add ui_update callback * better padding * init * revert padding to 20 * new line, who dis * this * support for next line multi-button * use inline=false * uhh * disabled colors * hide em all * lambdas * NOT inline * final touches * hide HIDE * ruff.. RUFF.. WHY RUFF * listitem -> listitemsp * Revert "add ui_update callback" This reverts commit 4da32cc0097434aab0aa6a3c35465eabb23c8958. * add show_description method * remove padding from line separator. like, WHY? 😩😩 * scroller -> scroller_tici * scroller -> scroller_tici * remove line separator padding * ui: `GuiApplicationExt` * add to readme * use gui_app.sunnypilot_ui() * use gui_app.sunnypilot_ui() * use gui_app.sunnypilot_ui() * uhhh. nope * optimizations * I THINK this is not needed, i don't see it used on the visuals panel... * unhide for now... Why hidden tho? * refresh controls * missing * blindspot * standstill timer * road name toggle * more descriptions * more descriptions * update desc * param turn signals * sort * fix * always show desc if not available * should be bool * rocket fuel * steering arc * lint --------- Co-authored-by: Jason Wen Co-authored-by: DevTekVE --- common/params_keys.h | 2 +- .../ui/sunnypilot/layouts/settings/visuals.py | 126 +++++++++++++++++- .../onroad/blind_spot_indicators.py | 3 + .../ui/sunnypilot/onroad/circular_alerts.py | 10 +- .../ui/sunnypilot/onroad/hud_renderer.py | 4 +- selfdrive/ui/sunnypilot/onroad/road_name.py | 2 +- selfdrive/ui/sunnypilot/onroad/rocket_fuel.py | 5 + selfdrive/ui/sunnypilot/onroad/turn_signal.py | 3 + selfdrive/ui/sunnypilot/ui_state.py | 23 ++-- sunnypilot/sunnylink/params_metadata.json | 40 +++--- 10 files changed, 175 insertions(+), 43 deletions(-) diff --git a/common/params_keys.h b/common/params_keys.h index 44584c911..f2a63ec1b 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -249,7 +249,7 @@ inline static std::unordered_map keys = { {"OsmStateTitle", {PERSISTENT, STRING}}, {"OsmWayTest", {PERSISTENT, STRING}}, {"RoadName", {CLEAR_ON_ONROAD_TRANSITION, STRING}}, - {"RoadNameToggle", {PERSISTENT, STRING}}, + {"RoadNameToggle", {PERSISTENT | BACKUP, BOOL, "0"}}, // Speed Limit {"SpeedLimitMode", {PERSISTENT | BACKUP, INT, "1"}}, diff --git a/selfdrive/ui/sunnypilot/layouts/settings/visuals.py b/selfdrive/ui/sunnypilot/layouts/settings/visuals.py index 1036af3e5..84be5a26a 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/visuals.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/visuals.py @@ -5,9 +5,18 @@ This file is part of sunnypilot and is licensed under the MIT License. See the LICENSE.md file in the root directory for more details. """ from openpilot.common.params import Params +from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.system.ui.lib.multilang import tr, tr_noop +from openpilot.system.ui.sunnypilot.widgets.list_view import toggle_item_sp, multiple_button_item_sp from openpilot.system.ui.widgets.scroller_tici import Scroller from openpilot.system.ui.widgets import Widget +CHEVRON_INFO_DESCRIPTION = { + "enabled": tr_noop("Display useful metrics below the chevron that tracks the lead car " + + "only applicable to cars with sunnypilot longitudinal control."), + "disabled": tr_noop("This feature requires sunnypilot longitudinal control to be available.") +} + class VisualsLayout(Widget): def __init__(self): @@ -18,13 +27,128 @@ class VisualsLayout(Widget): self._scroller = Scroller(items, line_separator=True, spacing=0) def _initialize_items(self): - items = [ + self._toggle_defs = { + "BlindSpot": ( + lambda: tr("Show Blind Spot Warnings"), + tr("Enabling this will display warnings when a vehicle is detected in your " + + "blind spot as long as your car has BSM supported."), + None, + ), + "TorqueBar": ( + lambda: tr("Steering Arc"), + tr("Display steering arc on the driving screen when lateral control is enabled."), + None, + ), + "RainbowMode": ( + lambda: tr("Enable Tesla Rainbow Mode"), + tr("A beautiful rainbow effect on the path the model wants to take. " + + "It does not affect driving in any way."), + None, + ), + "StandstillTimer": ( + lambda: tr("Enable Standstill Timer"), + tr("Show a timer on the HUD when the car is at a standstill."), + None, + ), + "RoadNameToggle": ( + lambda: tr("Display Road Name"), + tr("Displays the name of the road the car is traveling on." + + "
The OpenStreetMap database of the location must be downloaded from " + + "the OSM panel to fetch the road name."), + None, + ), + "GreenLightAlert": ( + lambda: tr("Green Traffic Light Alert (Beta)"), + tr("A chime and on-screen alert will play when the traffic light you are waiting for " + + "turns green and you have no vehicle in front of you." + + "
Note: This chime is only designed as a notification. " + + "It is the driver's responsibility to observe their environment and make decisions accordingly."), + None, + ), + "LeadDepartAlert": ( + lambda: tr("Lead Departure Alert (Beta)"), + tr("A chime and on-screen alert will play when you are stopped, and the vehicle in front of you start moving." + + "
Note: This chime is only designed as a notification. " + + "It is the driver's responsibility to observe their environment and make decisions accordingly."), + None, + ), + "TrueVEgoUI": ( + lambda: tr("Speedometer: Always Display True Speed"), + tr("For applicable vehicles, always display the true vehicle current speed from wheel speed sensors."), + None, + ), + "HideVEgoUI": ( + lambda: tr("Speedometer: Hide from Onroad Screen"), + tr("When enabled, the speedometer on the onroad screen is not displayed."), + None, + ), + "ShowTurnSignals": ( + lambda: tr("Display Turn Signals"), + tr("When enabled, visual turn indicators are drawn on the HUD."), + None, + ), + "RocketFuel": ( + lambda: tr("Real-time Acceleration Bar"), + tr("Show an indicator on the left side of the screen to display real-time vehicle acceleration and deceleration. " + + "This displays what the car is currently doing, not what the planner is requesting."), + None, + ), + } + self._toggles = {} + for param, (title, desc, callback) in self._toggle_defs.items(): + toggle = toggle_item_sp( + title=title, + description=desc, + param=param, + initial_state=ui_state.params.get_bool(param), + callback=callback, + ) + self._toggles[param] = toggle + self._chevron_info = multiple_button_item_sp( + title=lambda: tr("Display Metrics Below Chevron"), + description="", + buttons=[lambda: tr("Off"), lambda: tr("Distance"), lambda: tr("Speed"), lambda: tr("Time"), lambda: tr("All")], + param="ChevronInfo", + inline=False + ) + self._dev_ui_info = multiple_button_item_sp( + title=lambda: tr("Developer UI"), + description=lambda: tr("Display real-time parameters and metrics from various sources."), + buttons=[lambda: tr("Off"), lambda: tr("Bottom"), lambda: tr("Right"), lambda: tr("Right & Bottom")], + param="DevUIInfo", + button_width=350, + inline=False + ) + + items = list(self._toggles.values()) + [ + self._chevron_info, + self._dev_ui_info, ] return items + def _update_state(self): + super()._update_state() + + for param in self._toggle_defs: + self._toggles[param].action_item.set_state(self._params.get_bool(param)) + + self._dev_ui_info.action_item.set_selected_button(ui_state.params.get("DevUIInfo", return_default=True)) + + if ui_state.has_longitudinal_control: + self._chevron_info.set_description(tr(CHEVRON_INFO_DESCRIPTION["enabled"])) + self._chevron_info.action_item.set_selected_button(ui_state.params.get("ChevronInfo", return_default=True)) + self._chevron_info.action_item.set_enabled(True) + else: + self._chevron_info.set_description(tr(CHEVRON_INFO_DESCRIPTION["disabled"])) + self._chevron_info.action_item.set_enabled(False) + ui_state.params.put("ChevronInfo", 0) + def _render(self, rect): self._scroller.render(rect) def show_event(self): self._scroller.show_event() + if not ui_state.has_longitudinal_control: + self._chevron_info.set_description(tr(CHEVRON_INFO_DESCRIPTION["disabled"])) + self._chevron_info.show_description(True) diff --git a/selfdrive/ui/sunnypilot/onroad/blind_spot_indicators.py b/selfdrive/ui/sunnypilot/onroad/blind_spot_indicators.py index 1087579fe..61aa52537 100644 --- a/selfdrive/ui/sunnypilot/onroad/blind_spot_indicators.py +++ b/selfdrive/ui/sunnypilot/onroad/blind_spot_indicators.py @@ -31,6 +31,9 @@ class BlindSpotIndicators: return self._blind_spot_left_alpha_filter.x > 0.01 or self._blind_spot_right_alpha_filter.x > 0.01 def render(self, rect: rl.Rectangle) -> None: + if not ui_state.blindspot: + return + BLIND_SPOT_MARGIN_X = 20 # Distance from edge of screen BLIND_SPOT_Y_OFFSET = 100 # Distance from top of screen diff --git a/selfdrive/ui/sunnypilot/onroad/circular_alerts.py b/selfdrive/ui/sunnypilot/onroad/circular_alerts.py index 965ce7fe7..8aa4c71d0 100644 --- a/selfdrive/ui/sunnypilot/onroad/circular_alerts.py +++ b/selfdrive/ui/sunnypilot/onroad/circular_alerts.py @@ -23,7 +23,6 @@ class CircularAlertsRenderer: self._e2e_alert_frame = 0 self._green_light_alert = False self._lead_depart_alert = False - self._standstill_timer = False self._standstill_elapsed_time = 0.0 self._is_standstill = False self._alert_text = "" @@ -36,7 +35,6 @@ class CircularAlertsRenderer: car_state = sm['carState'] self._green_light_alert = lp_sp.e2eAlerts.greenLightAlert self._lead_depart_alert = lp_sp.e2eAlerts.leadDepartAlert - self._standstill_timer = ui_state.standstill_timer self._is_standstill = car_state.standstill if not ui_state.started: @@ -61,7 +59,7 @@ class CircularAlertsRenderer: self._alert_text = "LEAD VEHICLE\nDEPARTING" self._alert_img = self._lead_depart_alert_img - elif self._standstill_timer and self._is_standstill: + elif ui_state.standstill_timer and self._is_standstill: self._alert_img = None self._standstill_elapsed_time += 1.0 / gui_app.target_fps minute = int(self._standstill_elapsed_time / 60) @@ -75,7 +73,7 @@ class CircularAlertsRenderer: self._standstill_elapsed_time = 0.0 def render(self, rect: rl.Rectangle) -> None: - if not self._allow_e2e_alerts or (self._e2e_alert_display_timer <= 0 and not (self._standstill_timer and self._is_standstill)): + if not self._allow_e2e_alerts or (self._e2e_alert_display_timer <= 0 and not (ui_state.standstill_timer and self._is_standstill)): return e2e_alert_size = 250 @@ -91,7 +89,7 @@ class CircularAlertsRenderer: is_pulsing = (self._e2e_alert_frame % gui_app.target_fps) < (gui_app.target_fps / 2.5) # Standstill Timer (STOPPED) should be static white - if self._e2e_alert_display_timer == 0 and self._standstill_timer and self._is_standstill: + if self._e2e_alert_display_timer == 0 and ui_state.standstill_timer and self._is_standstill: frame_color = rl.Color(255, 255, 255, 75) else: frame_color = rl.Color(255, 255, 255, 75) if is_pulsing else rl.Color(0, 255, 0, 75) @@ -121,7 +119,7 @@ class CircularAlertsRenderer: # Draw lines upwards from bottom current_y = bottom_y - (len(lines) * text_size * FONT_SCALE) - if self._e2e_alert_display_timer == 0 and self._standstill_timer and self._is_standstill: + if self._e2e_alert_display_timer == 0 and ui_state.standstill_timer and self._is_standstill: # Standstill Timer Text alert_alt_text = "STOPPED" top_text_size = 80 diff --git a/selfdrive/ui/sunnypilot/onroad/hud_renderer.py b/selfdrive/ui/sunnypilot/onroad/hud_renderer.py index 3b810d62e..d8ba4b8bf 100644 --- a/selfdrive/ui/sunnypilot/onroad/hud_renderer.py +++ b/selfdrive/ui/sunnypilot/onroad/hud_renderer.py @@ -59,6 +59,4 @@ class HudRendererSP(HudRenderer): self.smart_cruise_control_renderer.render(rect) self.turn_signal_controller.render(rect) self.circular_alerts_renderer.render(rect) - - if ui_state.rocket_fuel: - self.rocket_fuel.render(rect, ui_state.sm) + self.rocket_fuel.render(rect, ui_state.sm) diff --git a/selfdrive/ui/sunnypilot/onroad/road_name.py b/selfdrive/ui/sunnypilot/onroad/road_name.py index 652e620ad..f85285ef5 100644 --- a/selfdrive/ui/sunnypilot/onroad/road_name.py +++ b/selfdrive/ui/sunnypilot/onroad/road_name.py @@ -31,7 +31,7 @@ class RoadNameRenderer(Widget): self.road_name = lmd.roadName def _render(self, rect: rl.Rectangle): - if not self.road_name: + if not self.road_name or not ui_state.road_name_toggle: return text = self.road_name diff --git a/selfdrive/ui/sunnypilot/onroad/rocket_fuel.py b/selfdrive/ui/sunnypilot/onroad/rocket_fuel.py index af25711a9..cb1012890 100644 --- a/selfdrive/ui/sunnypilot/onroad/rocket_fuel.py +++ b/selfdrive/ui/sunnypilot/onroad/rocket_fuel.py @@ -6,12 +6,17 @@ See the LICENSE.md file in the root directory for more details. """ import pyray as rl +from openpilot.selfdrive.ui.ui_state import ui_state + class RocketFuel: def __init__(self): self.vc_accel = 0.0 def render(self, rect: rl.Rectangle, sm) -> None: + if not ui_state.rocket_fuel: + return + vc_accel0 = sm['carState'].aEgo # Smooth the acceleration diff --git a/selfdrive/ui/sunnypilot/onroad/turn_signal.py b/selfdrive/ui/sunnypilot/onroad/turn_signal.py index 3a66ffeb0..04fff7db7 100644 --- a/selfdrive/ui/sunnypilot/onroad/turn_signal.py +++ b/selfdrive/ui/sunnypilot/onroad/turn_signal.py @@ -137,6 +137,9 @@ class TurnSignalController: self._right_signal.deactivate() def render(self, rect: rl.Rectangle): + if not ui_state.turn_signals: + return + x = rect.x + rect.width / 2 left_x = x - self._config.left_x - self._config.size diff --git a/selfdrive/ui/sunnypilot/ui_state.py b/selfdrive/ui/sunnypilot/ui_state.py index 6d7eab51c..6403157d5 100644 --- a/selfdrive/ui/sunnypilot/ui_state.py +++ b/selfdrive/ui/sunnypilot/ui_state.py @@ -120,22 +120,23 @@ class UIStateSP: CP_SP_bytes = self.params.get("CarParamsSPPersistent") if CP_SP_bytes is not None: self.CP_SP = messaging.log_from_bytes(CP_SP_bytes, custom.CarParamsSP) - self.sunnylink_enabled = self.params.get_bool("SunnylinkEnabled") - self.developer_ui = self.params.get("DevUIInfo") - self.rocket_fuel = self.params.get_bool("RocketFuel") - self.rainbow_path = self.params.get_bool("RainbowMode") - self.chevron_metrics = self.params.get("ChevronInfo") - self.torque_bar = self.params.get_bool("TorqueBar") self.active_bundle = self.params.get("ModelManager_ActiveBundle") + self.blindspot = self.params.get_bool("BlindSpot") + self.chevron_metrics = self.params.get("ChevronInfo") self.custom_interactive_timeout = self.params.get("InteractivityTimeout", return_default=True) - self.speed_limit_mode = self.params.get("SpeedLimitMode", return_default=True) - self.standstill_timer = self.params.get_bool("StandstillTimer") - self.true_v_ego_ui = self.params.get_bool("TrueVEgoUI") + self.developer_ui = self.params.get("DevUIInfo") self.hide_v_ego_ui = self.params.get_bool("HideVEgoUI") - - # Onroad Screen Brightness self.onroad_brightness = int(float(self.params.get("OnroadScreenOffBrightness", return_default=True))) self.onroad_brightness_timer_param = self.params.get("OnroadScreenOffTimer", return_default=True) + self.rainbow_path = self.params.get_bool("RainbowMode") + self.road_name_toggle = self.params.get_bool("RoadNameToggle") + self.rocket_fuel = self.params.get_bool("RocketFuel") + self.speed_limit_mode = self.params.get("SpeedLimitMode", return_default=True) + self.standstill_timer = self.params.get_bool("StandstillTimer") + self.sunnylink_enabled = self.params.get_bool("SunnylinkEnabled") + self.torque_bar = self.params.get_bool("TorqueBar") + self.true_v_ego_ui = self.params.get_bool("TrueVEgoUI") + self.turn_signals = self.params.get_bool("ShowTurnSignals") class DeviceSP: diff --git a/sunnypilot/sunnylink/params_metadata.json b/sunnypilot/sunnylink/params_metadata.json index 090beb49f..c0c84eac0 100644 --- a/sunnypilot/sunnylink/params_metadata.json +++ b/sunnypilot/sunnylink/params_metadata.json @@ -90,8 +90,8 @@ "description": "" }, "BlindSpot": { - "title": "Blind Spot Detection", - "description": "" + "title": "[TIZI/TICI only] Blind Spot Detection", + "description": "Enabling this will display warnings when a vehicle is detected in your blind spot as long as your car has BSM supported." }, "BlinkerMinLateralControlSpeed": { "title": "Blinker Min Lateral Control Speed", @@ -339,8 +339,8 @@ "description": "" }, "GreenLightAlert": { - "title": "Green Light Alert", - "description": "" + "title": "Green Traffic Light Alert (Beta)", + "description": "A chime and on-screen alert (TIZI/TICI only) will play when the traffic light you are waiting for turns green and you have no vehicle in front of you.
Note: This chime is only designed as a notification. It is the driver's responsibility to observe their environment and make decisions accordingly." }, "GsmApn": { "title": "GSM APN", @@ -367,8 +367,8 @@ "description": "" }, "HideVEgoUI": { - "title": "Hide vEgo UI", - "description": "" + "title": "[TIZI/TICI only] Speedometer: Hide from Onroad Screen", + "description": "When enabled, the speedometer on the onroad screen is not displayed." }, "HyundaiLongitudinalTuning": { "title": "Hyundai Longitudinal Tuning", @@ -585,8 +585,8 @@ "description": "" }, "LeadDepartAlert": { - "title": "Lead Depart Alert", - "description": "" + "title": "Lead Departure Alert (Beta)", + "description": "A chime and on-screen alert (TIZI/TICI only) will play when you are stopped, and the vehicle in front of you start moving.
Note: This chime is only designed as a notification. It is the driver's responsibility to observe their environment and make decisions accordingly." }, "LiveDelay": { "title": "Live Delay", @@ -1079,12 +1079,12 @@ "description": "" }, "RoadNameToggle": { - "title": "Display Road Name", - "description": "" + "title": "[TIZI/TICI only] Display Road Name", + "description": "Displays the name of the road the car is traveling on.
The OpenStreetMap database of the location must be downloaded to fetch the road name." }, "RocketFuel": { - "title": "Display Rocket Fuel Bar", - "description": "Show an indicator on the left side of the screen to display real-time vehicle acceleration and deceleration." + "title": "[TIZI/TICI only] Real-time Acceleration Bar", + "description": "Show an indicator on the left side of the screen to display real-time vehicle acceleration and deceleration. This displays what the car is currently doing, not what the planner is requesting." }, "RouteCount": { "title": "Route Count", @@ -1103,8 +1103,8 @@ "description": "" }, "ShowTurnSignals": { - "title": "Show Turn Signals", - "description": "" + "title": "[TIZI/TICI only] Display Turn Signals", + "description": "When enabled, visual turn indicators are drawn on the HUD." }, "SmartCruiseControlMap": { "title": "Smart Cruise Control - Map", @@ -1196,8 +1196,8 @@ "description": "" }, "StandstillTimer": { - "title": "Standstill Timer", - "description": "" + "title": "[TIZI/TICI only] Standstill Timer", + "description": "Show a timer on the HUD when the car is at a standstill." }, "SubaruStopAndGo": { "title": "Subaru Stop and Go", @@ -1240,8 +1240,8 @@ "description": "" }, "TorqueBar": { - "title": "Steering Arc", - "description": "[TIZI/TICI only] Display steering arc on the driving screen when lateral control is enabled." + "title": "[TIZI/TICI only] Steering Arc", + "description": "Display steering arc on the driving screen when lateral control is enabled." }, "TorqueParamsOverrideEnabled": { "title": "Manual Real-Time Tuning", @@ -1271,8 +1271,8 @@ "description": "" }, "TrueVEgoUI": { - "title": "True vEgo UI", - "description": "" + "title": "[TIZI/TICI only] Speedometer: Always Display True Speed", + "description": "For applicable vehicles, always display the true vehicle current speed from wheel speed sensors." }, "UbloxAvailable": { "title": "Ublox Available", From 1f778c8c23df8508fff09c3535f05356c4fa683d Mon Sep 17 00:00:00 2001 From: Nayan Date: Mon, 9 Feb 2026 00:38:39 -0500 Subject: [PATCH 866/910] Device: Retain QuickBoot state after op switch (#1333) Device: Retain QuickBoot state after SSH Update Co-authored-by: Jason Wen Co-authored-by: DevTekVE --- system/manager/manager.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/system/manager/manager.py b/system/manager/manager.py index 36e45488f..8c5d35d07 100755 --- a/system/manager/manager.py +++ b/system/manager/manager.py @@ -20,6 +20,7 @@ from openpilot.system.athena.registration import register, UNREGISTERED_DONGLE_I from openpilot.common.swaglog import cloudlog, add_file_handler from openpilot.system.version import get_build_metadata from openpilot.system.hardware.hw import Paths +from openpilot.system.hardware import PC def manager_init() -> None: @@ -39,6 +40,12 @@ def manager_init() -> None: if params.get("DeviceBootMode") == 1: # start in Always Offroad mode params.put_bool("OffroadMode", True) + # quick boot + if params.get_bool("QuickBootToggle") and not PC: + prebuilt_path = "/data/openpilot/prebuilt" + if not os.path.exists(prebuilt_path): + open(prebuilt_path, 'x').close() + if params.get_bool("RecordFrontLock"): params.put_bool("RecordFront", True) From 8be8bbbfdbbbd5d773413c5b7834dbf1377b0f45 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 9 Feb 2026 02:43:28 -0500 Subject: [PATCH 867/910] [TIZI/TICI] ui: Trips panel (#1679) * init * more * change * exist * better title * adjust * more * seems better * slightly more * slightly more * center it * final * move * no bc ew * more less --- .../ui/sunnypilot/layouts/settings/trips.py | 146 ++++++++++++++++-- sunnypilot/selfdrive/assets/icons/clock.png | 3 + 2 files changed, 137 insertions(+), 12 deletions(-) create mode 100644 sunnypilot/selfdrive/assets/icons/clock.png diff --git a/selfdrive/ui/sunnypilot/layouts/settings/trips.py b/selfdrive/ui/sunnypilot/layouts/settings/trips.py index a318cebeb..185b0adc4 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/trips.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/trips.py @@ -4,27 +4,149 @@ Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. This file is part of sunnypilot and is licensed under the MIT License. See the LICENSE.md file in the root directory for more details. """ +import json +import requests +import threading +import time +import pyray as rl + +from openpilot.common.api import api_get +from openpilot.common.constants import CV from openpilot.common.params import Params -from openpilot.system.ui.widgets.scroller_tici import Scroller +from openpilot.common.swaglog import cloudlog +from openpilot.selfdrive.ui.lib.api_helpers import get_token +from openpilot.selfdrive.ui.ui_state import ui_state, device +from openpilot.system.athena.registration import UNREGISTERED_DONGLE_ID +from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE +from openpilot.system.ui.lib.multilang import tr +from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.widgets import Widget class TripsLayout(Widget): + PARAM_KEY = "ApiCache_DriveStats" + UPDATE_INTERVAL = 30 # seconds + def __init__(self): super().__init__() - self._params = Params() - items = self._initialize_items() - self._scroller = Scroller(items, line_separator=True, spacing=0) + self._session = requests.Session() + self._stats = self._get_stats() - def _initialize_items(self): - items = [ + self._icon_distance = gui_app.texture("icons/road.png", 100, 100, keep_aspect_ratio=True) + self._icon_drives = gui_app.texture("icons_mici/wheel.png", 80, 80, keep_aspect_ratio=True) + self._icon_hours = gui_app.texture("../../sunnypilot/selfdrive/assets/icons/clock.png", 80, 80, keep_aspect_ratio=True) - ] - return items + self._running = True + self._update_thread = threading.Thread(target=self._update_loop, daemon=True) + self._update_thread.start() - def _render(self, rect): - self._scroller.render(rect) + def __del__(self): + self._running = False + try: + if self._update_thread and self._update_thread.is_alive(): + self._update_thread.join(timeout=1.0) + except Exception: + pass - def show_event(self): - self._scroller.show_event() + def _get_stats(self): + stats = self._params.get(self.PARAM_KEY) + if not stats: + return {} + try: + return json.loads(stats) + except Exception: + cloudlog.exception(f"Failed to decode drive stats: {stats}") + return {} + + def _fetch_drive_stats(self): + try: + dongle_id = self._params.get("DongleId") + if not dongle_id or dongle_id == UNREGISTERED_DONGLE_ID: + return + identity_token = get_token(dongle_id) + response = api_get(f"v1.1/devices/{dongle_id}/stats", access_token=identity_token, session=self._session) + if response.status_code == 200: + data = response.json() + self._stats = data + self._params.put(self.PARAM_KEY, data) + except Exception as e: + cloudlog.error(f"Failed to fetch drive stats: {e}") + + def _update_loop(self): + while self._running: + if not ui_state.started and device._awake: + self._fetch_drive_stats() + time.sleep(self.UPDATE_INTERVAL) + + def _render_stat_group(self, x, y, width, height, title, data, is_metric): + # Card Background + rl.draw_rectangle_rounded(rl.Rectangle(x, y, width, height), 0.05, 10, rl.Color(30, 30, 30, 255)) + + # Title + title_font = gui_app.font(FontWeight.BOLD) + rl.draw_text_ex(title_font, title, rl.Vector2(x + 60, y + 30), 50 * FONT_SCALE, 0, rl.Color(200, 200, 200, 255)) + + # Internal content area + # Center the content block (Icon + Value + Unit) vertically + content_y = y + (height / 2) - (140 * FONT_SCALE) + col_width = width / 3 + + # Values + number_font = gui_app.font(FontWeight.BOLD) + unit_font = gui_app.font(FontWeight.LIGHT) + number_base_size = 92 + unit_base_size = 55 + number_size = number_base_size * FONT_SCALE + unit_size = unit_base_size * FONT_SCALE + color_unit = rl.Color(160, 160, 160, 255) + + routes = int(data.get("routes", 0)) + distance = data.get("distance", 0) + distance_str = str(int(distance * CV.MPH_TO_KPH)) if is_metric else str(int(distance)) + hours = int(data.get("minutes", 0) / 60) + + dist_unit = tr("KM") if is_metric else tr("Miles") + + def draw_col(col_idx, icon, value, unit): + col_x = x + (col_width * col_idx) + center_x = col_x + (col_width / 2) + + # Icon + icon_x = int(center_x - (icon.width / 2)) + icon_y = int(content_y + 60) + rl.draw_texture(icon, icon_x, icon_y, rl.WHITE) + + # Value + val_size = measure_text_cached(number_font, value, number_base_size) + rl.draw_text_ex(number_font, value, rl.Vector2(center_x - val_size.x / 1.65, content_y + 145 * FONT_SCALE), number_size, 0, rl.WHITE) + + # Unit + unit_size_vec = measure_text_cached(unit_font, unit, unit_base_size) + rl.draw_text_ex(unit_font, unit, rl.Vector2(center_x - unit_size_vec.x / 1.65, content_y + 255 * FONT_SCALE), unit_size, 0, color_unit) + + draw_col(0, self._icon_drives, str(routes), tr("Drives")) + draw_col(1, self._icon_distance, distance_str, dist_unit) + draw_col(2, self._icon_hours, str(hours), tr("Hours")) + + return y + height + + def _render(self, rect: rl.Rectangle): + x = rect.x + y = rect.y + w = rect.width + + spacing = 30 + available_h = rect.height - 30 + card_height = available_h / 2 + + is_metric = self._params.get_bool("IsMetric") + + all_time = self._stats.get("all", {}) + week = self._stats.get("week", {}) + + y = self._render_stat_group(x, y, w, card_height, tr("ALL TIME"), all_time, is_metric) + y += spacing + y = self._render_stat_group(x, y, w, card_height, tr("PAST WEEK"), week, is_metric) + + return -1 diff --git a/sunnypilot/selfdrive/assets/icons/clock.png b/sunnypilot/selfdrive/assets/icons/clock.png new file mode 100644 index 000000000..e04d1db94 --- /dev/null +++ b/sunnypilot/selfdrive/assets/icons/clock.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e095cfc4de71788bd4a99699a2e7ab4098cd426277d672b9e43981c5fab8b40f +size 16407 From 51d3f0dea29ee4ddb773fc8d499e2cc6398291d8 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 9 Feb 2026 02:47:35 -0500 Subject: [PATCH 868/910] [TIZI/TICI] ui: drive stat cache is a json already --- selfdrive/ui/sunnypilot/layouts/settings/trips.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/sunnypilot/layouts/settings/trips.py b/selfdrive/ui/sunnypilot/layouts/settings/trips.py index 185b0adc4..1a51509ad 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/trips.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/trips.py @@ -54,7 +54,7 @@ class TripsLayout(Widget): if not stats: return {} try: - return json.loads(stats) + return stats except Exception: cloudlog.exception(f"Failed to decode drive stats: {stats}") return {} From 043eab56204ac3b915fbaa24fbc16a7d2420b9d4 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 9 Feb 2026 02:54:58 -0500 Subject: [PATCH 869/910] Revert "[TIZI/TICI] ui: drive stat cache is a json already" This reverts commit 51d3f0dea29ee4ddb773fc8d499e2cc6398291d8. --- selfdrive/ui/sunnypilot/layouts/settings/trips.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/sunnypilot/layouts/settings/trips.py b/selfdrive/ui/sunnypilot/layouts/settings/trips.py index 1a51509ad..185b0adc4 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/trips.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/trips.py @@ -54,7 +54,7 @@ class TripsLayout(Widget): if not stats: return {} try: - return stats + return json.loads(stats) except Exception: cloudlog.exception(f"Failed to decode drive stats: {stats}") return {} From 7722d14b8923ad21e65adeca4df3a2786e6d47fa Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 9 Feb 2026 02:55:06 -0500 Subject: [PATCH 870/910] Reapply "[TIZI/TICI] ui: drive stat cache is a json already" This reverts commit 043eab56204ac3b915fbaa24fbc16a7d2420b9d4. --- selfdrive/ui/sunnypilot/layouts/settings/trips.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/selfdrive/ui/sunnypilot/layouts/settings/trips.py b/selfdrive/ui/sunnypilot/layouts/settings/trips.py index 185b0adc4..b389892d5 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/trips.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/trips.py @@ -4,7 +4,6 @@ Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. This file is part of sunnypilot and is licensed under the MIT License. See the LICENSE.md file in the root directory for more details. """ -import json import requests import threading import time @@ -54,7 +53,7 @@ class TripsLayout(Widget): if not stats: return {} try: - return json.loads(stats) + return stats except Exception: cloudlog.exception(f"Failed to decode drive stats: {stats}") return {} From 9aca13cf2de5ef8b8db7f536d79bef7ee4909fb1 Mon Sep 17 00:00:00 2001 From: Andi Radulescu Date: Mon, 9 Feb 2026 19:36:04 +0200 Subject: [PATCH 871/910] remove get_mcu_type from pandad.py (#37132) --- selfdrive/pandad/pandad.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/selfdrive/pandad/pandad.py b/selfdrive/pandad/pandad.py index d75af283f..d5c58ddd6 100755 --- a/selfdrive/pandad/pandad.py +++ b/selfdrive/pandad/pandad.py @@ -6,16 +6,16 @@ import time import signal import subprocess -from panda import Panda, PandaDFU, PandaProtocolMismatch, FW_PATH +from panda import Panda, PandaDFU, PandaProtocolMismatch, McuType, FW_PATH from openpilot.common.basedir import BASEDIR from openpilot.common.params import Params from openpilot.system.hardware import HARDWARE from openpilot.common.swaglog import cloudlog -def get_expected_signature(panda: Panda) -> bytes: +def get_expected_signature() -> bytes: try: - fn = os.path.join(FW_PATH, panda.get_mcu_type().config.app_fn) + fn = os.path.join(FW_PATH, McuType.H7.config.app_fn) return Panda.get_signature_from_firmware(fn) except Exception: cloudlog.exception("Error computing expected signature") @@ -29,7 +29,7 @@ def flash_panda(panda_serial: str) -> Panda: HARDWARE.recover_internal_panda() raise - fw_signature = get_expected_signature(panda) + fw_signature = get_expected_signature() internal_panda = panda.is_internal() panda_version = "bootstub" if panda.bootstub else panda.get_version() From 3a61891197cd9bed96eb4f2d41f6f6a760a963af Mon Sep 17 00:00:00 2001 From: infiniteCable2 Date: Mon, 9 Feb 2026 19:04:39 +0100 Subject: [PATCH 872/910] Force Enable Torque Bar --- common/params_keys.h | 1 + selfdrive/ui/mici/layouts/settings/ictoggles.py | 3 +++ selfdrive/ui/mici/onroad/hud_renderer.py | 2 +- selfdrive/ui/ui_state.py | 2 ++ 4 files changed, 7 insertions(+), 1 deletion(-) diff --git a/common/params_keys.h b/common/params_keys.h index 6a1f029ca..d34d40bd0 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -145,6 +145,7 @@ inline static std::unordered_map keys = { {"BatteryDetails", {PERSISTENT, BOOL}}, {"ForceRHDForBSM", {PERSISTENT, BOOL}}, {"DisableCarSteerAlerts", {PERSISTENT, BOOL}}, + {"ForceShowTorqueBar", {PERSISTENT, BOOL}}, // --- sunnypilot params --- // {"ApiCache_DriveStats", {PERSISTENT, JSON}}, diff --git a/selfdrive/ui/mici/layouts/settings/ictoggles.py b/selfdrive/ui/mici/layouts/settings/ictoggles.py index 89eea6984..fcbbd4363 100644 --- a/selfdrive/ui/mici/layouts/settings/ictoggles.py +++ b/selfdrive/ui/mici/layouts/settings/ictoggles.py @@ -26,6 +26,7 @@ class ICTogglesLayoutMici(NavWidget): enable_smooth_steer = BigParamControl("Steer Smoothing", "EnableSmoothSteer") enable_dark_mode = BigParamControl("Dark Mode", "DarkMode") enable_onroad_screen_timer = BigParamControl("Onroad Screen Timeout", "DisableScreenTimer") + force_enable_torque_bar = BigParamControl("Force Enable Torque Bar", "ForceShowTorqueBar") self._scroller = Scroller([ @@ -40,6 +41,7 @@ class ICTogglesLayoutMici(NavWidget): enable_smooth_steer, enable_dark_mode, enable_onroad_screen_timer, + force_enable_torque_bar, ], snap_items=False) # Toggle lists @@ -55,6 +57,7 @@ class ICTogglesLayoutMici(NavWidget): ("EnableSmoothSteer", enable_smooth_steer), ("DarkMode", enable_dark_mode), ("DisableScreenTimer", enable_onroad_screen_timer), + ("ForceShowTorqueBar", force_enable_torque_bar), ) if ui_state.params.get_bool("ShowDebugInfo"): diff --git a/selfdrive/ui/mici/onroad/hud_renderer.py b/selfdrive/ui/mici/onroad/hud_renderer.py index 659d13fad..5d80af461 100644 --- a/selfdrive/ui/mici/onroad/hud_renderer.py +++ b/selfdrive/ui/mici/onroad/hud_renderer.py @@ -172,7 +172,7 @@ class HudRenderer(Widget): def _render(self, rect: rl.Rectangle) -> None: """Render HUD elements to the screen.""" - if ui_state.sm['controlsState'].lateralControlState.which() != 'angleState': + if ui_state.sm['controlsState'].lateralControlState.which() != 'angleState' or ui_state.force_enable_torque_bar: self._torque_bar.render(rect) if self.is_cruise_set: diff --git a/selfdrive/ui/ui_state.py b/selfdrive/ui/ui_state.py index cb9c9f128..bbc26d96b 100644 --- a/selfdrive/ui/ui_state.py +++ b/selfdrive/ui/ui_state.py @@ -88,6 +88,7 @@ class UIState(UIStateSP): self.dark_mode: bool = False self.onroad_screen_timeout: bool = False + self.force_enable_torque_bar: bool = False self.has_alert: bool = False self.has_status_change: bool = False self._status_prev: UIStatus = self.status @@ -205,6 +206,7 @@ class UIState(UIStateSP): self._param_update_time = time.monotonic() self.dark_mode = self.params.get_bool("DarkMode") self.onroad_screen_timeout = self.params.get_bool("DisableScreenTimer") + self.force_enable_torque_bar = self.params.get_bool("ForceShowTorqueBar") class Device(DeviceSP): From a941e8f78f36f15d54645d70ab5289dffc509fbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Mon, 9 Feb 2026 15:29:50 -0800 Subject: [PATCH 873/910] Chunk big model files (#37134) * file chunking * try this * more cleanup * cleaner --- .gitignore | 2 +- common/file_chunker.py | 27 +++++++++++++++++++++++++++ selfdrive/modeld/SConscript | 12 ++++++++---- selfdrive/modeld/dmonitoringmodeld.py | 7 +++---- selfdrive/modeld/modeld.py | 11 ++++------- 5 files changed, 43 insertions(+), 16 deletions(-) create mode 100644 common/file_chunker.py diff --git a/.gitignore b/.gitignore index 1fef0a125..062801d78 100644 --- a/.gitignore +++ b/.gitignore @@ -64,7 +64,7 @@ flycheck_* cppcheck_report.txt comma*.sh -selfdrive/modeld/models/*.pkl +selfdrive/modeld/models/*.pkl* # openpilot log files *.bz2 diff --git a/common/file_chunker.py b/common/file_chunker.py new file mode 100644 index 000000000..2a78963d1 --- /dev/null +++ b/common/file_chunker.py @@ -0,0 +1,27 @@ +import glob +import os +import sys +from pathlib import Path + +CHUNK_SIZE = 49 * 1024 * 1024 # 49MB, under GitHub's 50MB limit + + +def chunk_file(path): + with open(path, 'rb') as f: + data = f.read() + for i in range(0, len(data), CHUNK_SIZE): + with open(f"{path}.chunk{i // CHUNK_SIZE:02d}", 'wb') as f: + f.write(data[i:i + CHUNK_SIZE]) + os.remove(path) + + +def read_file_chunked(path): + files = sorted(glob.glob(f"{path}.chunk*")) or ([path] if os.path.isfile(path) else []) + if not files: + raise FileNotFoundError(path) + return b''.join(Path(f).read_bytes() for f in files) + + +if __name__ == "__main__": + for path in sys.argv[1:]: + chunk_file(path) diff --git a/selfdrive/modeld/SConscript b/selfdrive/modeld/SConscript index 84e94df4a..a48c79570 100644 --- a/selfdrive/modeld/SConscript +++ b/selfdrive/modeld/SConscript @@ -8,6 +8,8 @@ tinygrad_root = env.Dir("#").abspath tinygrad_files = ["#"+x for x in glob.glob(env.Dir("#tinygrad_repo").relpath + "/**", recursive=True, root_dir=tinygrad_root) if 'pycache' not in x and os.path.isfile(os.path.join(tinygrad_root, x))] +chunk_cmd = f'python3 {Dir("#common").abspath}/file_chunker.py' + # Get model metadata for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']: fn = File(f"models/{model_name}").abspath @@ -24,21 +26,23 @@ image_flag = { 'larch64': 'IMAGE=2', }.get(arch, 'IMAGE=0') script_files = [File(Dir("#selfdrive/modeld").File("compile_warp.py").abspath)] -cmd = f'{tg_flags} python3 {Dir("#selfdrive/modeld").abspath}/compile_warp.py ' +compile_warp_cmd = f'{tg_flags} python3 {Dir("#selfdrive/modeld").abspath}/compile_warp.py ' from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye warp_targets = [] for cam in [_ar_ox_fisheye, _os_fisheye]: w, h = cam.width, cam.height warp_targets += [File(f"models/warp_{w}x{h}_tinygrad.pkl").abspath, File(f"models/dm_warp_{w}x{h}_tinygrad.pkl").abspath] -lenv.Command(warp_targets, tinygrad_files + script_files, cmd) +lenv.Command([t + ".chunk00" for t in warp_targets], tinygrad_files + script_files, + f'{compile_warp_cmd} && {chunk_cmd} {" ".join(warp_targets)}') def tg_compile(flags, model_name): pythonpath_string = 'PYTHONPATH="${PYTHONPATH}:' + env.Dir("#tinygrad_repo").abspath + '"' fn = File(f"models/{model_name}").abspath + pkl = fn + "_tinygrad.pkl" return lenv.Command( - fn + "_tinygrad.pkl", + pkl + ".chunk00", [fn + ".onnx"] + tinygrad_files, - f'{pythonpath_string} {flags} {image_flag} python3 {Dir("#tinygrad_repo").abspath}/examples/openpilot/compile3.py {fn}.onnx {fn}_tinygrad.pkl' + f'{pythonpath_string} {flags} {image_flag} python3 {Dir("#tinygrad_repo").abspath}/examples/openpilot/compile3.py {fn}.onnx {pkl} && {chunk_cmd} {pkl}' ) # Compile small models diff --git a/selfdrive/modeld/dmonitoringmodeld.py b/selfdrive/modeld/dmonitoringmodeld.py index 7d4df713c..b5cf2f71e 100755 --- a/selfdrive/modeld/dmonitoringmodeld.py +++ b/selfdrive/modeld/dmonitoringmodeld.py @@ -16,6 +16,7 @@ from openpilot.common.realtime import config_realtime_process from openpilot.common.transformations.model import dmonitoringmodel_intrinsics from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye from openpilot.system.camerad.cameras.nv12_info import get_nv12_info +from openpilot.common.file_chunker import read_file_chunked from openpilot.selfdrive.modeld.parse_model_outputs import sigmoid, safe_exp PROCESS_NAME = "selfdrive.modeld.dmonitoringmodeld" @@ -43,8 +44,7 @@ class ModelState: self.frame_buf_params = None self.tensor_inputs = {k: Tensor(v, device='NPY').realize() for k,v in self.numpy_inputs.items()} self.image_warp = None - with open(MODEL_PKL_PATH, "rb") as f: - self.model_run = pickle.load(f) + self.model_run = pickle.loads(read_file_chunked(str(MODEL_PKL_PATH))) def run(self, buf: VisionBuf, calib: np.ndarray, transform: np.ndarray) -> tuple[np.ndarray, float]: self.numpy_inputs['calib'][0,:] = calib @@ -54,8 +54,7 @@ class ModelState: if self.image_warp is None: self.frame_buf_params = get_nv12_info(buf.width, buf.height) warp_path = MODELS_DIR / f'dm_warp_{buf.width}x{buf.height}_tinygrad.pkl' - with open(warp_path, "rb") as f: - self.image_warp = pickle.load(f) + self.image_warp = pickle.loads(read_file_chunked(str(warp_path))) self.warp_inputs['frame'] = Tensor.from_blob(buf.data.ctypes.data, (self.frame_buf_params[3],), dtype='uint8').realize() self.warp_inputs_np['transform'][:] = transform[:] diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index 7fae36510..f76d5b081 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -27,6 +27,7 @@ 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.modeld.parse_model_outputs import Parser from openpilot.selfdrive.modeld.fill_model_msg import fill_model_msg, fill_pose_msg, PublishState +from openpilot.common.file_chunker import read_file_chunked from openpilot.selfdrive.modeld.constants import ModelConstants, Plan @@ -177,11 +178,8 @@ class ModelState: self.frame_buf_params : dict[str, tuple[int, int, int, int]] = {} self.update_imgs = None - with open(VISION_PKL_PATH, "rb") as f: - self.vision_run = pickle.load(f) - - with open(POLICY_PKL_PATH, "rb") as f: - self.policy_run = pickle.load(f) + self.vision_run = pickle.loads(read_file_chunked(str(VISION_PKL_PATH))) + self.policy_run = pickle.loads(read_file_chunked(str(POLICY_PKL_PATH))) def slice_outputs(self, model_outputs: np.ndarray, output_slices: dict[str, slice]) -> dict[str, np.ndarray]: parsed_model_outputs = {k: model_outputs[np.newaxis, v] for k,v in output_slices.items()} @@ -198,8 +196,7 @@ class ModelState: w, h = bufs[key].width, bufs[key].height self.frame_buf_params[key] = get_nv12_info(w, h) warp_path = MODELS_DIR / f'warp_{w}x{h}_tinygrad.pkl' - with open(warp_path, "rb") as f: - self.update_imgs = pickle.load(f) + self.update_imgs = pickle.loads(read_file_chunked(str(warp_path))) for key in bufs.keys(): From a1202eaf2af9c240bb7e115b17607df86f5fc774 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 9 Feb 2026 17:16:36 -0800 Subject: [PATCH 874/910] ui: delay nav bar animation (#37137) * from da bounce * try this * you can get it to clean up wow --- system/ui/widgets/__init__.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/system/ui/widgets/__init__.py b/system/ui/widgets/__init__.py index 5d474e8ae..b3b1276a5 100644 --- a/system/ui/widgets/__init__.py +++ b/system/ui/widgets/__init__.py @@ -242,6 +242,7 @@ class NavWidget(Widget, abc.ABC): self._pos_filter = BounceFilter(0.0, 0.1, 1 / gui_app.target_fps, bounce=1) self._playing_dismiss_animation = False self._trigger_animate_in = False + self._nav_bar_show_time = 0.0 self._back_enabled: bool | Callable[[], bool] = True self._nav_bar = NavBar() @@ -330,6 +331,7 @@ class NavWidget(Widget, abc.ABC): if self._trigger_animate_in: self._pos_filter.x = self._rect.height self._nav_bar_y_filter.x = -NAV_BAR_MARGIN - NAV_BAR_HEIGHT + self._nav_bar_show_time = rl.get_time() self._trigger_animate_in = False new_y = 0.0 @@ -366,8 +368,14 @@ class NavWidget(Widget, abc.ABC): if self.back_enabled: bar_x = self._rect.x + (self._rect.width - self._nav_bar.rect.width) / 2 + nav_bar_delayed = rl.get_time() - self._nav_bar_show_time < 0.4 + # User dragging or dismissing, nav bar follows NavWidget if self._back_button_start_pos is not None or self._playing_dismiss_animation: self._nav_bar_y_filter.x = NAV_BAR_MARGIN + self._pos_filter.x + # Waiting to show + elif nav_bar_delayed: + self._nav_bar_y_filter.x = -NAV_BAR_MARGIN - NAV_BAR_HEIGHT + # Animate back to top else: self._nav_bar_y_filter.update(NAV_BAR_MARGIN) From 73f720220bf326d52e9ae644e4cabf634abb06f4 Mon Sep 17 00:00:00 2001 From: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Mon, 9 Feb 2026 20:24:25 -0800 Subject: [PATCH 875/910] modeld: simplify model run processing (#37138) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hi! The point of this pr is to make the model run easier to read. On the latest tinygrad numpy().flatten() empirically does the same thing as the internal contiguous().realize().uop.base.buffer.numpy(). numpy() is also documented (docstrings), which can assist new contributors in learning what each potential execution does. Torq_boi or yassine, I know you want proof in the code base, so here it is. As of tinygrad commit 2f55005: in tinygrad_repo/tinygrad/tensor.py Lines 316-318 (def _buffer): ensure the tenso is contiguous() and realized() before accessing the raw buffer. Line 378 (def numpy): Wraps the buffer access and adds a reshape to match the tensor shape. self._buffer() is what executes contiguous().realize() and returns the buffer object. Calling numpy() on that buffer object returns a 1D array (defined in tinygrad/device.py:193 via np.frombuffer). The reshape(self.shape) at the end of Tensor.numpy() then adds dimensions to that 1D array. The added .flatten() removes those dimensions, flattening it back to a 1D array. Effectively the same as what is currently done, but less complex. --- selfdrive/modeld/dmonitoringmodeld.py | 2 +- selfdrive/modeld/modeld.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/selfdrive/modeld/dmonitoringmodeld.py b/selfdrive/modeld/dmonitoringmodeld.py index b5cf2f71e..7cac17fa8 100755 --- a/selfdrive/modeld/dmonitoringmodeld.py +++ b/selfdrive/modeld/dmonitoringmodeld.py @@ -60,7 +60,7 @@ class ModelState: self.warp_inputs_np['transform'][:] = transform[:] self.tensor_inputs['input_img'] = self.image_warp(self.warp_inputs['frame'], self.warp_inputs['transform']).realize() - output = self.model_run(**self.tensor_inputs).contiguous().realize().uop.base.buffer.numpy() + output = self.model_run(**self.tensor_inputs).numpy().flatten() t2 = time.perf_counter() return output, t2 - t1 diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index f76d5b081..01052840a 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -211,7 +211,7 @@ class ModelState: if prepare_only: return None - self.vision_output = self.vision_run(**vision_inputs).contiguous().realize().uop.base.buffer.numpy() + self.vision_output = self.vision_run(**vision_inputs).numpy().flatten() vision_outputs_dict = self.parser.parse_vision_outputs(self.slice_outputs(self.vision_output, self.vision_output_slices)) self.full_input_queues.enqueue({'features_buffer': vision_outputs_dict['hidden_state'], 'desire_pulse': new_desire}) @@ -219,7 +219,7 @@ class ModelState: self.numpy_inputs[k][:] = self.full_input_queues.get(k)[k] self.numpy_inputs['traffic_convention'][:] = inputs['traffic_convention'] - self.policy_output = self.policy_run(**self.policy_inputs).contiguous().realize().uop.base.buffer.numpy() + self.policy_output = self.policy_run(**self.policy_inputs).numpy().flatten() policy_outputs_dict = self.parser.parse_policy_outputs(self.slice_outputs(self.policy_output, self.policy_output_slices)) combined_outputs_dict = {**vision_outputs_dict, **policy_outputs_dict} if SEND_RAW_PRED: From c51b41e96a3d22844cd394d4ec4c13877ef6f29b Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 9 Feb 2026 23:46:39 -0500 Subject: [PATCH 876/910] sunnylink: shorter timeout for websocket reconnect (#1681) * separate * no need * shorten * lint --- common/api/__init__.py | 4 ++-- sunnypilot/sunnylink/athena/sunnylinkd.py | 8 ++------ 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/common/api/__init__.py b/common/api/__init__.py index 45c73e2a0..2186542cb 100644 --- a/common/api/__init__.py +++ b/common/api/__init__.py @@ -18,8 +18,8 @@ class Api: return self.service.get_token(payload_extra, expiry_hours) -def api_get(endpoint, method='GET', timeout=None, access_token=None, session=None, **params): - return CommaConnectApi(None).api_get(endpoint, method, timeout, access_token, session, **params) +def api_get(endpoint, method='GET', timeout=None, access_token=None, session=None, json=None, **params): + return CommaConnectApi(None).api_get(endpoint, method, timeout, access_token, session, json, **params) def get_key_pair() -> tuple[str, str, str] | tuple[None, None, None]: diff --git a/sunnypilot/sunnylink/athena/sunnylinkd.py b/sunnypilot/sunnylink/athena/sunnylinkd.py index fe0a24810..3e44c8c96 100755 --- a/sunnypilot/sunnylink/athena/sunnylinkd.py +++ b/sunnypilot/sunnylink/athena/sunnylinkd.py @@ -12,7 +12,6 @@ import errno import gzip import json import os -import ssl import threading import time @@ -274,9 +273,7 @@ def startLocalProxy(global_end_event: threading.Event, remote_ws_uri: str, local sunnylink_api = SunnylinkApi(sunnylink_dongle_id) cloudlog.debug("athena.startLocalProxy.starting") - ws = create_connection( - remote_ws_uri, header={"Authorization": f"Bearer {sunnylink_api.get_token()}"}, enable_multithread=True, sslopt={"cert_reqs": ssl.CERT_NONE} - ) + ws = create_connection(remote_ws_uri, header={"Authorization": f"Bearer {sunnylink_api.get_token()}"}, enable_multithread=True) return start_local_proxy_shim(global_end_event, local_port, ws) @@ -310,8 +307,7 @@ def main(exit_event: threading.Event | None = None): ws_uri, header={"Authorization": f"Bearer {sunnylink_api.get_token()}"}, enable_multithread=True, - sslopt={"cert_reqs": ssl.CERT_NONE if "localhost" in ws_uri else ssl.CERT_REQUIRED}, - timeout=SUNNYLINK_RECONNECT_TIMEOUT_S, + timeout=30.0, ) cloudlog.event("sunnylinkd.main.connected_ws", ws_uri=ws_uri, retries=conn_retries, duration=time.monotonic() - conn_start) From 3d11e8ef362155cea1494b1a75bdb369c1c73d6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Mon, 9 Feb 2026 20:58:22 -0800 Subject: [PATCH 877/910] Revert "Chunk big model files (#37134)" (#37139) This reverts commit a941e8f78f36f15d54645d70ab5289dffc509fbb. --- .gitignore | 2 +- common/file_chunker.py | 27 --------------------------- selfdrive/modeld/SConscript | 12 ++++-------- selfdrive/modeld/dmonitoringmodeld.py | 7 ++++--- selfdrive/modeld/modeld.py | 11 +++++++---- 5 files changed, 16 insertions(+), 43 deletions(-) delete mode 100644 common/file_chunker.py diff --git a/.gitignore b/.gitignore index 062801d78..1fef0a125 100644 --- a/.gitignore +++ b/.gitignore @@ -64,7 +64,7 @@ flycheck_* cppcheck_report.txt comma*.sh -selfdrive/modeld/models/*.pkl* +selfdrive/modeld/models/*.pkl # openpilot log files *.bz2 diff --git a/common/file_chunker.py b/common/file_chunker.py deleted file mode 100644 index 2a78963d1..000000000 --- a/common/file_chunker.py +++ /dev/null @@ -1,27 +0,0 @@ -import glob -import os -import sys -from pathlib import Path - -CHUNK_SIZE = 49 * 1024 * 1024 # 49MB, under GitHub's 50MB limit - - -def chunk_file(path): - with open(path, 'rb') as f: - data = f.read() - for i in range(0, len(data), CHUNK_SIZE): - with open(f"{path}.chunk{i // CHUNK_SIZE:02d}", 'wb') as f: - f.write(data[i:i + CHUNK_SIZE]) - os.remove(path) - - -def read_file_chunked(path): - files = sorted(glob.glob(f"{path}.chunk*")) or ([path] if os.path.isfile(path) else []) - if not files: - raise FileNotFoundError(path) - return b''.join(Path(f).read_bytes() for f in files) - - -if __name__ == "__main__": - for path in sys.argv[1:]: - chunk_file(path) diff --git a/selfdrive/modeld/SConscript b/selfdrive/modeld/SConscript index a48c79570..84e94df4a 100644 --- a/selfdrive/modeld/SConscript +++ b/selfdrive/modeld/SConscript @@ -8,8 +8,6 @@ tinygrad_root = env.Dir("#").abspath tinygrad_files = ["#"+x for x in glob.glob(env.Dir("#tinygrad_repo").relpath + "/**", recursive=True, root_dir=tinygrad_root) if 'pycache' not in x and os.path.isfile(os.path.join(tinygrad_root, x))] -chunk_cmd = f'python3 {Dir("#common").abspath}/file_chunker.py' - # Get model metadata for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']: fn = File(f"models/{model_name}").abspath @@ -26,23 +24,21 @@ image_flag = { 'larch64': 'IMAGE=2', }.get(arch, 'IMAGE=0') script_files = [File(Dir("#selfdrive/modeld").File("compile_warp.py").abspath)] -compile_warp_cmd = f'{tg_flags} python3 {Dir("#selfdrive/modeld").abspath}/compile_warp.py ' +cmd = f'{tg_flags} python3 {Dir("#selfdrive/modeld").abspath}/compile_warp.py ' from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye warp_targets = [] for cam in [_ar_ox_fisheye, _os_fisheye]: w, h = cam.width, cam.height warp_targets += [File(f"models/warp_{w}x{h}_tinygrad.pkl").abspath, File(f"models/dm_warp_{w}x{h}_tinygrad.pkl").abspath] -lenv.Command([t + ".chunk00" for t in warp_targets], tinygrad_files + script_files, - f'{compile_warp_cmd} && {chunk_cmd} {" ".join(warp_targets)}') +lenv.Command(warp_targets, tinygrad_files + script_files, cmd) def tg_compile(flags, model_name): pythonpath_string = 'PYTHONPATH="${PYTHONPATH}:' + env.Dir("#tinygrad_repo").abspath + '"' fn = File(f"models/{model_name}").abspath - pkl = fn + "_tinygrad.pkl" return lenv.Command( - pkl + ".chunk00", + fn + "_tinygrad.pkl", [fn + ".onnx"] + tinygrad_files, - f'{pythonpath_string} {flags} {image_flag} python3 {Dir("#tinygrad_repo").abspath}/examples/openpilot/compile3.py {fn}.onnx {pkl} && {chunk_cmd} {pkl}' + f'{pythonpath_string} {flags} {image_flag} python3 {Dir("#tinygrad_repo").abspath}/examples/openpilot/compile3.py {fn}.onnx {fn}_tinygrad.pkl' ) # Compile small models diff --git a/selfdrive/modeld/dmonitoringmodeld.py b/selfdrive/modeld/dmonitoringmodeld.py index 7cac17fa8..bc3adffff 100755 --- a/selfdrive/modeld/dmonitoringmodeld.py +++ b/selfdrive/modeld/dmonitoringmodeld.py @@ -16,7 +16,6 @@ from openpilot.common.realtime import config_realtime_process from openpilot.common.transformations.model import dmonitoringmodel_intrinsics from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye from openpilot.system.camerad.cameras.nv12_info import get_nv12_info -from openpilot.common.file_chunker import read_file_chunked from openpilot.selfdrive.modeld.parse_model_outputs import sigmoid, safe_exp PROCESS_NAME = "selfdrive.modeld.dmonitoringmodeld" @@ -44,7 +43,8 @@ class ModelState: self.frame_buf_params = None self.tensor_inputs = {k: Tensor(v, device='NPY').realize() for k,v in self.numpy_inputs.items()} self.image_warp = None - self.model_run = pickle.loads(read_file_chunked(str(MODEL_PKL_PATH))) + with open(MODEL_PKL_PATH, "rb") as f: + self.model_run = pickle.load(f) def run(self, buf: VisionBuf, calib: np.ndarray, transform: np.ndarray) -> tuple[np.ndarray, float]: self.numpy_inputs['calib'][0,:] = calib @@ -54,7 +54,8 @@ class ModelState: if self.image_warp is None: self.frame_buf_params = get_nv12_info(buf.width, buf.height) warp_path = MODELS_DIR / f'dm_warp_{buf.width}x{buf.height}_tinygrad.pkl' - self.image_warp = pickle.loads(read_file_chunked(str(warp_path))) + with open(warp_path, "rb") as f: + self.image_warp = pickle.load(f) self.warp_inputs['frame'] = Tensor.from_blob(buf.data.ctypes.data, (self.frame_buf_params[3],), dtype='uint8').realize() self.warp_inputs_np['transform'][:] = transform[:] diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index 01052840a..6f3c481ea 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -27,7 +27,6 @@ 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.modeld.parse_model_outputs import Parser from openpilot.selfdrive.modeld.fill_model_msg import fill_model_msg, fill_pose_msg, PublishState -from openpilot.common.file_chunker import read_file_chunked from openpilot.selfdrive.modeld.constants import ModelConstants, Plan @@ -178,8 +177,11 @@ class ModelState: self.frame_buf_params : dict[str, tuple[int, int, int, int]] = {} self.update_imgs = None - self.vision_run = pickle.loads(read_file_chunked(str(VISION_PKL_PATH))) - self.policy_run = pickle.loads(read_file_chunked(str(POLICY_PKL_PATH))) + with open(VISION_PKL_PATH, "rb") as f: + self.vision_run = pickle.load(f) + + with open(POLICY_PKL_PATH, "rb") as f: + self.policy_run = pickle.load(f) def slice_outputs(self, model_outputs: np.ndarray, output_slices: dict[str, slice]) -> dict[str, np.ndarray]: parsed_model_outputs = {k: model_outputs[np.newaxis, v] for k,v in output_slices.items()} @@ -196,7 +198,8 @@ class ModelState: w, h = bufs[key].width, bufs[key].height self.frame_buf_params[key] = get_nv12_info(w, h) warp_path = MODELS_DIR / f'warp_{w}x{h}_tinygrad.pkl' - self.update_imgs = pickle.loads(read_file_chunked(str(warp_path))) + with open(warp_path, "rb") as f: + self.update_imgs = pickle.load(f) for key in bufs.keys(): From e35a1eca9931c342c1f9c0e6167f969a2748a545 Mon Sep 17 00:00:00 2001 From: Daniel Koepping Date: Mon, 9 Feb 2026 21:37:20 -0800 Subject: [PATCH 878/910] Process replay: move refs to ci-artifacts (#37049) * rm upload * use ci-artifacts * sanitize * rm ref_commit * add ci * handle exept * bootstrap * always * fix * replay * keep ref_commit fork compatibility * remove upload-only * apply comments * safe diffs in master * Revert "safe diffs in master" This reverts commit 369fccac786a67799193e9152488813c6df20414. * continue on master diff * Update .github/workflows/tests.yaml Co-authored-by: Shane Smiskol --------- Co-authored-by: Shane Smiskol --- .github/workflows/tests.yaml | 27 +++++++--- selfdrive/test/process_replay/README.md | 3 +- selfdrive/test/process_replay/ref_commit | 1 - .../test/process_replay/test_processes.py | 54 +++++++------------ 4 files changed, 41 insertions(+), 44 deletions(-) delete mode 100644 selfdrive/test/process_replay/ref_commit diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 4ade42b66..fe7c8ef33 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -20,8 +20,6 @@ concurrency: env: PYTHONWARNINGS: error BASE_IMAGE: openpilot-base - AZURE_TOKEN: ${{ secrets.AZURE_COMMADATACI_OPENPILOTCI_TOKEN }} - DOCKER_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }} BUILD: selfdrive/test/docker_build.sh base @@ -185,12 +183,13 @@ jobs: uses: actions/cache@v5 with: path: .ci_cache/comma_download_cache - key: proc-replay-${{ hashFiles('selfdrive/test/process_replay/ref_commit', 'selfdrive/test/process_replay/test_processes.py') }} + key: proc-replay-${{ hashFiles('selfdrive/test/process_replay/test_processes.py') }} - name: Build openpilot run: | ${{ env.RUN }} "scons -j$(nproc)" - name: Run replay timeout-minutes: ${{ contains(runner.name, 'nsc') && (steps.dependency-cache.outputs.cache-hit == 'true') && ((steps.setup-step.outputs.duration < 18) && 1 || 2) || 20 }} + continue-on-error: ${{ github.ref == 'refs/heads/master' }} run: | ${{ env.RUN }} "selfdrive/test/process_replay/test_processes.py -j$(nproc) && \ chmod -R 777 /tmp/comma_download_cache" @@ -204,10 +203,26 @@ jobs: with: name: process_replay_diff.txt path: selfdrive/test/process_replay/diff.txt - - name: Upload reference logs - if: false # TODO: move this to github instead of azure + - name: Checkout ci-artifacts + if: github.repository == 'commaai/openpilot' && github.ref == 'refs/heads/master' + uses: actions/checkout@v4 + with: + repository: commaai/ci-artifacts + ssh-key: ${{ secrets.CI_ARTIFACTS_DEPLOY_KEY }} + path: ${{ github.workspace }}/ci-artifacts + - name: Push refs + if: github.repository == 'commaai/openpilot' && github.ref == 'refs/heads/master' + working-directory: ${{ github.workspace }}/ci-artifacts run: | - ${{ env.RUN }} "unset PYTHONWARNINGS && AZURE_TOKEN='$AZURE_TOKEN' python3 selfdrive/test/process_replay/test_processes.py -j$(nproc) --upload-only" + git checkout --orphan process-replay + git rm -rf . + git config user.name "GitHub Actions Bot" + git config user.email "<>" + cp ${{ github.workspace }}/selfdrive/test/process_replay/fakedata/*.zst . + echo "${{ github.sha }}" > ref_commit + git add . + git commit -m "process-replay refs for ${{ github.repository }}@${{ github.sha }}" + git push origin process-replay --force - name: Run regen if: false timeout-minutes: 4 diff --git a/selfdrive/test/process_replay/README.md b/selfdrive/test/process_replay/README.md index 8e279c71c..28f3b7cd2 100644 --- a/selfdrive/test/process_replay/README.md +++ b/selfdrive/test/process_replay/README.md @@ -22,7 +22,7 @@ Currently the following processes are tested: ### Usage ``` Usage: test_processes.py [-h] [--whitelist-procs PROCS] [--whitelist-cars CARS] [--blacklist-procs PROCS] - [--blacklist-cars CARS] [--ignore-fields FIELDS] [--ignore-msgs MSGS] [--update-refs] [--upload-only] + [--blacklist-cars CARS] [--ignore-fields FIELDS] [--ignore-msgs MSGS] [--update-refs] Regression test to identify changes in a process's output optional arguments: -h, --help show this help message and exit @@ -33,7 +33,6 @@ optional arguments: --ignore-fields IGNORE_FIELDS Extra fields or msgs to ignore (e.g. driverMonitoringState.events) --ignore-msgs IGNORE_MSGS Msgs to ignore (e.g. onroadEvents) --update-refs Updates reference logs using current commit - --upload-only Skips testing processes and uploads logs from previous test run ``` ## Forks diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit deleted file mode 100644 index 85b79391c..000000000 --- a/selfdrive/test/process_replay/ref_commit +++ /dev/null @@ -1 +0,0 @@ -67f3daf309dc6cbb6844fcbaeb83e6596637e551 \ 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 59e1ae054..9f79c8ddc 100755 --- a/selfdrive/test/process_replay/test_processes.py +++ b/selfdrive/test/process_replay/test_processes.py @@ -9,12 +9,13 @@ from typing import Any from opendbc.car.car_helpers import interface_names from openpilot.common.git import get_commit -from openpilot.tools.lib.openpilotci import get_url, upload_file +from openpilot.tools.lib.openpilotci import get_url from openpilot.selfdrive.test.process_replay.compare_logs import compare_logs, format_diff from openpilot.selfdrive.test.process_replay.process_replay import CONFIGS, PROC_REPLAY_DIR, FAKEDATA, replay_process, \ check_most_messages_valid from openpilot.tools.lib.filereader import FileReader from openpilot.tools.lib.logreader import LogReader, save_log +from openpilot.tools.lib.url_file import URLFile source_segments = [ ("HYUNDAI", "02c45f73a2e5c6e9|2021-01-01--19-08-22--1"), # HYUNDAI.HYUNDAI_SONATA @@ -64,25 +65,17 @@ segments = [ # dashcamOnly makes don't need to be tested until a full port is done excluded_interfaces = ["mock", "body", "psa"] -BASE_URL = "https://commadataci.blob.core.windows.net/openpilotci/" +BASE_URL = "https://raw.githubusercontent.com/commaai/ci-artifacts/refs/heads/process-replay/" REF_COMMIT_FN = os.path.join(PROC_REPLAY_DIR, "ref_commit") EXCLUDED_PROCS = {"modeld", "dmonitoringmodeld"} def run_test_process(data): segment, cfg, args, cur_log_fn, ref_log_path, lr_dat = data - res = None - if not args.upload_only: - lr = LogReader.from_bytes(lr_dat) - res, log_msgs = test_process(cfg, lr, segment, ref_log_path, cur_log_fn, args.ignore_fields, args.ignore_msgs) - # save logs so we can upload when updating refs - save_log(cur_log_fn, log_msgs) - - if args.update_refs or args.upload_only: - print(f'Uploading: {os.path.basename(cur_log_fn)}') - assert os.path.exists(cur_log_fn), f"Cannot find log to upload: {cur_log_fn}" - upload_file(cur_log_fn, os.path.basename(cur_log_fn)) - os.remove(cur_log_fn) + lr = LogReader.from_bytes(lr_dat) + res, log_msgs = test_process(cfg, lr, segment, ref_log_path, cur_log_fn, args.ignore_fields, args.ignore_msgs) + # save logs so we can update refs + save_log(cur_log_fn, log_msgs) return (segment, cfg.proc_name, res) @@ -142,8 +135,6 @@ if __name__ == "__main__": help="Msgs to ignore (e.g. carEvents)") parser.add_argument("--update-refs", action="store_true", help="Updates reference logs using current commit") - parser.add_argument("--upload-only", action="store_true", - help="Skips testing processes and uploads logs from previous test run") parser.add_argument("-j", "--jobs", type=int, default=max(cpu_count - 2, 1), help="Max amount of parallel jobs") args = parser.parse_args() @@ -153,18 +144,16 @@ if __name__ == "__main__": tested_cars = {c.upper() for c in tested_cars} full_test = (tested_procs == all_procs) and (tested_cars == all_cars) and all(len(x) == 0 for x in (args.ignore_fields, args.ignore_msgs)) - upload = args.update_refs or args.upload_only os.makedirs(os.path.dirname(FAKEDATA), exist_ok=True) - if upload: + if args.update_refs: assert full_test, "Need to run full test when updating refs" try: with open(REF_COMMIT_FN) as f: ref_commit = f.read().strip() except FileNotFoundError: - print("Couldn't find reference commit") - sys.exit(1) + ref_commit = URLFile(BASE_URL + "ref_commit", cache=False).read().decode().strip() cur_commit = get_commit() if not cur_commit: @@ -179,12 +168,11 @@ if __name__ == "__main__": log_paths: defaultdict[str, dict[str, dict[str, str]]] = defaultdict(lambda: defaultdict(dict)) with concurrent.futures.ProcessPoolExecutor(max_workers=args.jobs) as pool: - if not args.upload_only: - download_segments = [seg for car, seg in segments if car in tested_cars] - log_data: dict[str, LogReader] = {} - p1 = pool.map(get_log_data, download_segments) - for segment, lr in tqdm(p1, desc="Getting Logs", total=len(download_segments)): - log_data[segment] = lr + download_segments = [seg for car, seg in segments if car in tested_cars] + log_data: dict[str, LogReader] = {} + p1 = pool.map(get_log_data, download_segments) + for segment, lr in tqdm(p1, desc="Getting Logs", total=len(download_segments)): + log_data[segment] = lr pool_args: Any = [] for car_brand, segment in segments: @@ -199,15 +187,14 @@ if __name__ == "__main__": if cfg.proc_name not in ('card', 'controlsd', 'lagd') and car_brand not in ('HYUNDAI', 'TOYOTA'): continue - cur_log_fn = os.path.join(FAKEDATA, f"{segment}_{cfg.proc_name}_{cur_commit}.zst") + cur_log_fn = os.path.join(FAKEDATA, f"{segment}_{cfg.proc_name}_{cur_commit}.zst".replace("|", "_")) if args.update_refs: # reference logs will not exist if routes were just regenerated ref_log_path = get_url(*segment.rsplit("--", 1,), "rlog.zst") else: - ref_log_fn = os.path.join(FAKEDATA, f"{segment}_{cfg.proc_name}_{ref_commit}.zst") + ref_log_fn = os.path.join(FAKEDATA, f"{segment}_{cfg.proc_name}_{ref_commit}.zst".replace("|", "_")) ref_log_path = ref_log_fn if os.path.exists(ref_log_fn) else BASE_URL + os.path.basename(ref_log_fn) - dat = None if args.upload_only else log_data[segment] - pool_args.append((segment, cfg, args, cur_log_fn, ref_log_path, dat)) + pool_args.append((segment, cfg, args, cur_log_fn, ref_log_path, log_data[segment])) log_paths[segment][cfg.proc_name]['ref'] = ref_log_path log_paths[segment][cfg.proc_name]['new'] = cur_log_fn @@ -215,19 +202,16 @@ if __name__ == "__main__": results: Any = defaultdict(dict) p2 = pool.map(run_test_process, pool_args) for (segment, proc, result) in tqdm(p2, desc="Running Tests", total=len(pool_args)): - if not args.upload_only: - results[segment][proc] = result + results[segment][proc] = result diff_short, diff_long, failed = format_diff(results, log_paths, ref_commit) - if not upload: + if not args.update_refs: with open(os.path.join(PROC_REPLAY_DIR, "diff.txt"), "w") as f: f.write(diff_long) print(diff_short) if failed: print("TEST FAILED") - print("\n\nTo push the new reference logs for this commit run:") - print("./test_processes.py --upload-only") else: print("TEST SUCCEEDED") From 117927321714571cd3f4d9686a99df86c8c7f264 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Tue, 10 Feb 2026 17:28:51 +0100 Subject: [PATCH 879/910] Revert "sunnylink: shorter timeout for websocket reconnect" (#1683) Revert "sunnylink: shorter timeout for websocket reconnect (#1681)" This reverts commit c51b41e96a3d22844cd394d4ec4c13877ef6f29b. --- common/api/__init__.py | 4 ++-- sunnypilot/sunnylink/athena/sunnylinkd.py | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/common/api/__init__.py b/common/api/__init__.py index 2186542cb..45c73e2a0 100644 --- a/common/api/__init__.py +++ b/common/api/__init__.py @@ -18,8 +18,8 @@ class Api: return self.service.get_token(payload_extra, expiry_hours) -def api_get(endpoint, method='GET', timeout=None, access_token=None, session=None, json=None, **params): - return CommaConnectApi(None).api_get(endpoint, method, timeout, access_token, session, json, **params) +def api_get(endpoint, method='GET', timeout=None, access_token=None, session=None, **params): + return CommaConnectApi(None).api_get(endpoint, method, timeout, access_token, session, **params) def get_key_pair() -> tuple[str, str, str] | tuple[None, None, None]: diff --git a/sunnypilot/sunnylink/athena/sunnylinkd.py b/sunnypilot/sunnylink/athena/sunnylinkd.py index 3e44c8c96..fe0a24810 100755 --- a/sunnypilot/sunnylink/athena/sunnylinkd.py +++ b/sunnypilot/sunnylink/athena/sunnylinkd.py @@ -12,6 +12,7 @@ import errno import gzip import json import os +import ssl import threading import time @@ -273,7 +274,9 @@ def startLocalProxy(global_end_event: threading.Event, remote_ws_uri: str, local sunnylink_api = SunnylinkApi(sunnylink_dongle_id) cloudlog.debug("athena.startLocalProxy.starting") - ws = create_connection(remote_ws_uri, header={"Authorization": f"Bearer {sunnylink_api.get_token()}"}, enable_multithread=True) + ws = create_connection( + remote_ws_uri, header={"Authorization": f"Bearer {sunnylink_api.get_token()}"}, enable_multithread=True, sslopt={"cert_reqs": ssl.CERT_NONE} + ) return start_local_proxy_shim(global_end_event, local_port, ws) @@ -307,7 +310,8 @@ def main(exit_event: threading.Event | None = None): ws_uri, header={"Authorization": f"Bearer {sunnylink_api.get_token()}"}, enable_multithread=True, - timeout=30.0, + sslopt={"cert_reqs": ssl.CERT_NONE if "localhost" in ws_uri else ssl.CERT_REQUIRED}, + timeout=SUNNYLINK_RECONNECT_TIMEOUT_S, ) cloudlog.event("sunnylinkd.main.connected_ws", ws_uri=ws_uri, retries=conn_retries, duration=time.monotonic() - conn_start) From 053441fbb3b4a6f400fccecfd47f3fddbca04f09 Mon Sep 17 00:00:00 2001 From: Andi Radulescu Date: Tue, 10 Feb 2026 19:19:37 +0200 Subject: [PATCH 880/910] fix first-interaction action inputs for v3 (#37144) v3 renamed inputs from kebab-case to snake_case (repo-token -> repo_token, pr-message -> pr_message). The old names were silently ignored, causing "Input required and not supplied: issue_message" errors. --- .github/workflows/auto_pr_review.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/auto_pr_review.yaml b/.github/workflows/auto_pr_review.yaml index 725154d21..cf12360e6 100644 --- a/.github/workflows/auto_pr_review.yaml +++ b/.github/workflows/auto_pr_review.yaml @@ -39,8 +39,8 @@ jobs: uses: actions/first-interaction@v3 if: github.event.pull_request.head.repo.full_name != 'commaai/openpilot' with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - pr-message: | + repo_token: ${{ secrets.GITHUB_TOKEN }} + pr_message: | Thanks for contributing to openpilot! In order for us to review your PR as quickly as possible, check the following: * Convert your PR to a draft unless it's ready to review From 9476a8a7f609e251263908d5f9a2f31b71c15697 Mon Sep 17 00:00:00 2001 From: Andi Radulescu Date: Tue, 10 Feb 2026 19:19:56 +0200 Subject: [PATCH 881/910] bump create-pull-request action to v8.1.0 (#37143) The pinned SHA was v6.0.4, which is incompatible with actions/checkout@v6 and causes a "Duplicate header: Authorization" 400 error during git remote operations. See peter-evans/create-pull-request#4272. --- .github/workflows/repo-maintenance.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/repo-maintenance.yaml b/.github/workflows/repo-maintenance.yaml index 810b602d7..f88db3eaf 100644 --- a/.github/workflows/repo-maintenance.yaml +++ b/.github/workflows/repo-maintenance.yaml @@ -21,7 +21,7 @@ jobs: run: | ${{ env.RUN }} "python3 selfdrive/ui/update_translations.py --vanish" - name: Create Pull Request - uses: peter-evans/create-pull-request@9153d834b60caba6d51c9b9510b087acf9f33f83 + uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 with: author: Vehicle Researcher commit-message: "Update translations" @@ -59,7 +59,7 @@ jobs: python selfdrive/car/docs.py git add docs/CARS.md - name: Create Pull Request - uses: peter-evans/create-pull-request@9153d834b60caba6d51c9b9510b087acf9f33f83 + uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 with: author: Vehicle Researcher token: ${{ secrets.ACTIONS_CREATE_PR_PAT }} From 43edc51cb6591b63fd1994c91a2a7ec977e36bfa Mon Sep 17 00:00:00 2001 From: Andi Radulescu Date: Tue, 10 Feb 2026 19:20:52 +0200 Subject: [PATCH 882/910] bump numpy to 2.4.2 (#37145) --- uv.lock | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/uv.lock b/uv.lock index 81e2d9136..83805c646 100644 --- a/uv.lock +++ b/uv.lock @@ -958,21 +958,21 @@ wheels = [ [[package]] name = "numpy" -version = "2.4.1" +version = "2.4.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/24/62/ae72ff66c0f1fd959925b4c11f8c2dea61f47f6acaea75a08512cdfe3fed/numpy-2.4.1.tar.gz", hash = "sha256:a1ceafc5042451a858231588a104093474c6a5c57dcc724841f5c888d237d690", size = 20721320, upload-time = "2026-01-10T06:44:59.619Z" } +sdist = { url = "https://files.pythonhosted.org/packages/57/fd/0005efbd0af48e55eb3c7208af93f2862d4b1a56cd78e84309a2d959208d/numpy-2.4.2.tar.gz", hash = "sha256:659a6107e31a83c4e33f763942275fd278b21d095094044eb35569e86a21ddae", size = 20723651, upload-time = "2026-01-31T23:13:10.135Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/78/7f/ec53e32bf10c813604edf07a3682616bd931d026fcde7b6d13195dfb684a/numpy-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d3703409aac693fa82c0aee023a1ae06a6e9d065dba10f5e8e80f642f1e9d0a2", size = 16656888, upload-time = "2026-01-10T06:42:40.913Z" }, - { url = "https://files.pythonhosted.org/packages/b8/e0/1f9585d7dae8f14864e948fd7fa86c6cb72dee2676ca2748e63b1c5acfe0/numpy-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7211b95ca365519d3596a1d8688a95874cc94219d417504d9ecb2df99fa7bfa8", size = 12373956, upload-time = "2026-01-10T06:42:43.091Z" }, - { url = "https://files.pythonhosted.org/packages/8e/43/9762e88909ff2326f5e7536fa8cb3c49fb03a7d92705f23e6e7f553d9cb3/numpy-2.4.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:5adf01965456a664fc727ed69cc71848f28d063217c63e1a0e200a118d5eec9a", size = 5202567, upload-time = "2026-01-10T06:42:45.107Z" }, - { url = "https://files.pythonhosted.org/packages/4b/ee/34b7930eb61e79feb4478800a4b95b46566969d837546aa7c034c742ef98/numpy-2.4.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:26f0bcd9c79a00e339565b303badc74d3ea2bd6d52191eeca5f95936cad107d0", size = 6549459, upload-time = "2026-01-10T06:42:48.152Z" }, - { url = "https://files.pythonhosted.org/packages/79/e3/5f115fae982565771be994867c89bcd8d7208dbfe9469185497d70de5ddf/numpy-2.4.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0093e85df2960d7e4049664b26afc58b03236e967fb942354deef3208857a04c", size = 14404859, upload-time = "2026-01-10T06:42:49.947Z" }, - { url = "https://files.pythonhosted.org/packages/d9/7d/9c8a781c88933725445a859cac5d01b5871588a15969ee6aeb618ba99eee/numpy-2.4.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7ad270f438cbdd402c364980317fb6b117d9ec5e226fff5b4148dd9aa9fc6e02", size = 16371419, upload-time = "2026-01-10T06:42:52.409Z" }, - { url = "https://files.pythonhosted.org/packages/a6/d2/8aa084818554543f17cf4162c42f162acbd3bb42688aefdba6628a859f77/numpy-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:297c72b1b98100c2e8f873d5d35fb551fce7040ade83d67dd51d38c8d42a2162", size = 16182131, upload-time = "2026-01-10T06:42:54.694Z" }, - { url = "https://files.pythonhosted.org/packages/60/db/0425216684297c58a8df35f3284ef56ec4a043e6d283f8a59c53562caf1b/numpy-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:cf6470d91d34bf669f61d515499859fa7a4c2f7c36434afb70e82df7217933f9", size = 18295342, upload-time = "2026-01-10T06:42:56.991Z" }, - { url = "https://files.pythonhosted.org/packages/31/4c/14cb9d86240bd8c386c881bafbe43f001284b7cce3bc01623ac9475da163/numpy-2.4.1-cp312-cp312-win32.whl", hash = "sha256:b6bcf39112e956594b3331316d90c90c90fb961e39696bda97b89462f5f3943f", size = 5959015, upload-time = "2026-01-10T06:42:59.631Z" }, - { url = "https://files.pythonhosted.org/packages/51/cf/52a703dbeb0c65807540d29699fef5fda073434ff61846a564d5c296420f/numpy-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:e1a27bb1b2dee45a2a53f5ca6ff2d1a7f135287883a1689e930d44d1ff296c87", size = 12310730, upload-time = "2026-01-10T06:43:01.627Z" }, - { url = "https://files.pythonhosted.org/packages/69/80/a828b2d0ade5e74a9fe0f4e0a17c30fdc26232ad2bc8c9f8b3197cf7cf18/numpy-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:0e6e8f9d9ecf95399982019c01223dc130542960a12edfa8edd1122dfa66a8a8", size = 10312166, upload-time = "2026-01-10T06:43:03.673Z" }, + { url = "https://files.pythonhosted.org/packages/51/6e/6f394c9c77668153e14d4da83bcc247beb5952f6ead7699a1a2992613bea/numpy-2.4.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:21982668592194c609de53ba4933a7471880ccbaadcc52352694a59ecc860b3a", size = 16667963, upload-time = "2026-01-31T23:10:52.147Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f8/55483431f2b2fd015ae6ed4fe62288823ce908437ed49db5a03d15151678/numpy-2.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40397bda92382fcec844066efb11f13e1c9a3e2a8e8f318fb72ed8b6db9f60f1", size = 14693571, upload-time = "2026-01-31T23:10:54.789Z" }, + { url = "https://files.pythonhosted.org/packages/2f/20/18026832b1845cdc82248208dd929ca14c9d8f2bac391f67440707fff27c/numpy-2.4.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:b3a24467af63c67829bfaa61eecf18d5432d4f11992688537be59ecd6ad32f5e", size = 5203469, upload-time = "2026-01-31T23:10:57.343Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/2eb97c8a77daaba34eaa3fa7241a14ac5f51c46a6bd5911361b644c4a1e2/numpy-2.4.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:805cc8de9fd6e7a22da5aed858e0ab16be5a4db6c873dde1d7451c541553aa27", size = 6550820, upload-time = "2026-01-31T23:10:59.429Z" }, + { url = "https://files.pythonhosted.org/packages/b1/91/b97fdfd12dc75b02c44e26c6638241cc004d4079a0321a69c62f51470c4c/numpy-2.4.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d82351358ffbcdcd7b686b90742a9b86632d6c1c051016484fa0b326a0a1548", size = 15663067, upload-time = "2026-01-31T23:11:01.291Z" }, + { url = "https://files.pythonhosted.org/packages/f5/c6/a18e59f3f0b8071cc85cbc8d80cd02d68aa9710170b2553a117203d46936/numpy-2.4.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e35d3e0144137d9fdae62912e869136164534d64a169f86438bc9561b6ad49f", size = 16619782, upload-time = "2026-01-31T23:11:03.669Z" }, + { url = "https://files.pythonhosted.org/packages/b7/83/9751502164601a79e18847309f5ceec0b1446d7b6aa12305759b72cf98b2/numpy-2.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adb6ed2ad29b9e15321d167d152ee909ec73395901b70936f029c3bc6d7f4460", size = 17013128, upload-time = "2026-01-31T23:11:05.913Z" }, + { url = "https://files.pythonhosted.org/packages/61/c4/c4066322256ec740acc1c8923a10047818691d2f8aec254798f3dd90f5f2/numpy-2.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8906e71fd8afcb76580404e2a950caef2685df3d2a57fe82a86ac8d33cc007ba", size = 18345324, upload-time = "2026-01-31T23:11:08.248Z" }, + { url = "https://files.pythonhosted.org/packages/ab/af/6157aa6da728fa4525a755bfad486ae7e3f76d4c1864138003eb84328497/numpy-2.4.2-cp312-cp312-win32.whl", hash = "sha256:ec055f6dae239a6299cace477b479cca2fc125c5675482daf1dd886933a1076f", size = 5960282, upload-time = "2026-01-31T23:11:10.497Z" }, + { url = "https://files.pythonhosted.org/packages/92/0f/7ceaaeaacb40567071e94dbf2c9480c0ae453d5bb4f52bea3892c39dc83c/numpy-2.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:209fae046e62d0ce6435fcfe3b1a10537e858249b3d9b05829e2a05218296a85", size = 12314210, upload-time = "2026-01-31T23:11:12.176Z" }, + { url = "https://files.pythonhosted.org/packages/2f/a3/56c5c604fae6dd40fa2ed3040d005fca97e91bd320d232ac9931d77ba13c/numpy-2.4.2-cp312-cp312-win_arm64.whl", hash = "sha256:fbde1b0c6e81d56f5dccd95dd4a711d9b95df1ae4009a60887e56b27e8d903fa", size = 10220171, upload-time = "2026-01-31T23:11:14.684Z" }, ] [[package]] From 7d2563880afd61351d3462367df35d224df4cac2 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 10 Feb 2026 09:31:50 -0800 Subject: [PATCH 883/910] show dependency tree in weekly uv lock job (#37146) --- .github/workflows/repo-maintenance.yaml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/workflows/repo-maintenance.yaml b/.github/workflows/repo-maintenance.yaml index f88db3eaf..ec361536d 100644 --- a/.github/workflows/repo-maintenance.yaml +++ b/.github/workflows/repo-maintenance.yaml @@ -47,6 +47,12 @@ jobs: python3 -m ensurepip --upgrade pip3 install uv uv lock --upgrade + - name: uv pip tree + id: pip_tree + run: | + echo 'PIP_TREE<> $GITHUB_OUTPUT + uv pip tree >> $GITHUB_OUTPUT + echo 'EOF' >> $GITHUB_OUTPUT - name: bump submodules run: | git config --global --add safe.directory '*' @@ -68,5 +74,10 @@ jobs: branch: auto-package-updates base: master delete-branch: true - body: 'Automatic PR from repo-maintenance -> package_updates' + body: | + Automatic PR from repo-maintenance -> package_updates + + ``` + ${{ steps.pip_tree.outputs.PIP_TREE }} + ``` labels: bot From e946e9de0b5a7c1be296d88336fad37155f683f8 Mon Sep 17 00:00:00 2001 From: ZwX1616 Date: Tue, 10 Feb 2026 13:56:07 -0800 Subject: [PATCH 884/910] Revert "DM: Ford GT model" (#37148) Revert "DM: Ford GT model (#37013)" This reverts commit 1459d3519da2fdb2d981baf7811c2eaa2127eb80. --- cereal/log.capnp | 4 +-- .../modeld/models/dmonitoring_model.onnx | 4 +-- selfdrive/monitoring/helpers.py | 25 ++++++++++++++++--- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/cereal/log.capnp b/cereal/log.capnp index 119cf2999..afb710e80 100644 --- a/cereal/log.capnp +++ b/cereal/log.capnp @@ -2232,9 +2232,9 @@ struct DriverMonitoringState @0xb83cda094a1da284 { isActiveMode @16 :Bool; isRHD @4 :Bool; uncertainCount @19 :UInt32; + phoneProbOffset @20 :Float32; + phoneProbValidCount @21 :UInt32; - phoneProbOffsetDEPRECATED @20 :Float32; - phoneProbValidCountDEPRECATED @21 :UInt32; isPreviewDEPRECATED @15 :Bool; rhdCheckedDEPRECATED @5 :Bool; eventsDEPRECATED @0 :List(Car.OnroadEventDEPRECATED); diff --git a/selfdrive/modeld/models/dmonitoring_model.onnx b/selfdrive/modeld/models/dmonitoring_model.onnx index 4052a1548..9b1c4a183 100644 --- a/selfdrive/modeld/models/dmonitoring_model.onnx +++ b/selfdrive/modeld/models/dmonitoring_model.onnx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:35e4a5d4c4d481f915e42358af4665b2c92b8f5c1efd1c0731f21b876ad1d856 -size 6954249 +oid sha256:3446bf8b22e50e47669a25bf32460ae8baf8547037f346753e19ecbfcf6d4e59 +size 6954368 diff --git a/selfdrive/monitoring/helpers.py b/selfdrive/monitoring/helpers.py index 0b54504b6..3377ce6c6 100644 --- a/selfdrive/monitoring/helpers.py +++ b/selfdrive/monitoring/helpers.py @@ -35,7 +35,14 @@ class DRIVER_MONITOR_SETTINGS: self._EYE_THRESHOLD = 0.65 self._SG_THRESHOLD = 0.9 self._BLINK_THRESHOLD = 0.865 - self._PHONE_THRESH = 0.5 + + self._PHONE_THRESH = 0.75 if device_type == 'mici' else 0.4 + self._PHONE_THRESH2 = 15.0 + self._PHONE_MAX_OFFSET = 0.06 + self._PHONE_MIN_OFFSET = 0.025 + self._PHONE_DATA_AVG = 0.05 + self._PHONE_DATA_VAR = 3*0.005 + self._PHONE_MAX_COUNT = int(360 / self._DT_DMON) self._POSE_PITCH_THRESHOLD = 0.3133 self._POSE_PITCH_THRESHOLD_SLACK = 0.3237 @@ -145,10 +152,11 @@ class DriverMonitoring: # init driver status wheelpos_filter_raw_priors = (self.settings._WHEELPOS_DATA_AVG, self.settings._WHEELPOS_DATA_VAR, 2) + phone_filter_raw_priors = (self.settings._PHONE_DATA_AVG, self.settings._PHONE_DATA_VAR, 2) self.wheelpos = DriverProb(raw_priors=wheelpos_filter_raw_priors, max_trackable=self.settings._WHEELPOS_MAX_COUNT) + self.phone = DriverProb(raw_priors=phone_filter_raw_priors, max_trackable=self.settings._PHONE_MAX_COUNT) self.pose = DriverPose(settings=self.settings) self.blink = DriverBlink() - self.phone_prob = 0. self.always_on = always_on self.distracted_types = [] @@ -249,7 +257,12 @@ class DriverMonitoring: if (self.blink.left + self.blink.right)*0.5 > self.settings._BLINK_THRESHOLD: distracted_types.append(DistractedType.DISTRACTED_BLINK) - if self.phone_prob > self.settings._PHONE_THRESH: + if self.phone.prob_calibrated: + using_phone = self.phone.prob > max(min(self.phone.prob_offseter.filtered_stat.M, self.settings._PHONE_MAX_OFFSET), self.settings._PHONE_MIN_OFFSET) \ + * self.settings._PHONE_THRESH2 + else: + using_phone = self.phone.prob > self.settings._PHONE_THRESH + if using_phone: distracted_types.append(DistractedType.DISTRACTED_PHONE) return distracted_types @@ -288,7 +301,7 @@ class DriverMonitoring: * (driver_data.sunglassesProb < self.settings._SG_THRESHOLD) self.blink.right = driver_data.rightBlinkProb * (driver_data.rightEyeProb > self.settings._EYE_THRESHOLD) \ * (driver_data.sunglassesProb < self.settings._SG_THRESHOLD) - self.phone_prob = driver_data.phoneProb + self.phone.prob = driver_data.phoneProb self.distracted_types = self._get_distracted_types() self.driver_distracted = (DistractedType.DISTRACTED_PHONE in self.distracted_types @@ -302,9 +315,11 @@ class DriverMonitoring: if self.face_detected and car_speed > self.settings._POSE_CALIB_MIN_SPEED and self.pose.low_std and (not op_engaged or not self.driver_distracted): self.pose.pitch_offseter.push_and_update(self.pose.pitch) self.pose.yaw_offseter.push_and_update(self.pose.yaw) + self.phone.prob_offseter.push_and_update(self.phone.prob) self.pose.calibrated = self.pose.pitch_offseter.filtered_stat.n > self.settings._POSE_OFFSET_MIN_COUNT and \ self.pose.yaw_offseter.filtered_stat.n > self.settings._POSE_OFFSET_MIN_COUNT + self.phone.prob_calibrated = self.phone.prob_offseter.filtered_stat.n > self.settings._POSE_OFFSET_MIN_COUNT if self.face_detected and not self.driver_distracted: if model_std_max > self.settings._DCAM_UNCERTAIN_ALERT_THRESHOLD: @@ -410,6 +425,8 @@ class DriverMonitoring: "posePitchValidCount": self.pose.pitch_offseter.filtered_stat.n, "poseYawOffset": self.pose.yaw_offseter.filtered_stat.mean(), "poseYawValidCount": self.pose.yaw_offseter.filtered_stat.n, + "phoneProbOffset": self.phone.prob_offseter.filtered_stat.mean(), + "phoneProbValidCount": self.phone.prob_offseter.filtered_stat.n, "stepChange": self.step_change, "awarenessActive": self.awareness_active, "awarenessPassive": self.awareness_passive, From ba67e468abb681ab73f2a7e4fd1cdf80bd6d2fdf Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 10 Feb 2026 14:53:25 -0800 Subject: [PATCH 885/910] remove dead multilang for mici (#37150) --- selfdrive/ui/mici/layouts/settings/device.py | 23 +------------------- 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/device.py b/selfdrive/ui/mici/layouts/settings/device.py index f8dba659d..f4b6217ac 100644 --- a/selfdrive/ui/mici/layouts/settings/device.py +++ b/selfdrive/ui/mici/layouts/settings/device.py @@ -1,6 +1,5 @@ import os import threading -import json import pyray as rl from enum import IntEnum from collections.abc import Callable @@ -11,7 +10,7 @@ from openpilot.common.time_helpers import system_time_valid from openpilot.system.ui.widgets.scroller import Scroller from openpilot.system.ui.lib.scroll_panel2 import GuiScrollPanel2 from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigCircleButton -from openpilot.selfdrive.ui.mici.widgets.dialog import BigMultiOptionDialog, BigDialog, BigConfirmationDialogV2 +from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigConfirmationDialogV2 from openpilot.selfdrive.ui.mici.widgets.pairing_dialog import PairingDialog from openpilot.selfdrive.ui.mici.onroad.driver_camera_dialog import DriverCameraDialog from openpilot.selfdrive.ui.mici.layouts.onboarding import TrainingGuide @@ -303,22 +302,6 @@ class DeviceLayoutMici(NavWidget): self._power_off_btn = BigCircleButton("icons_mici/settings/device/power.png", red=True, icon_size=(64, 66)) self._power_off_btn.set_click_callback(lambda: _engaged_confirmation_callback(power_off_callback, "power off")) - self._load_languages() - - def language_callback(): - def selected_language_callback(): - selected_language = dlg.get_selected_option() - ui_state.params.put("LanguageSetting", self._languages[selected_language]) - - current_language_name = ui_state.params.get("LanguageSetting") - current_language = next(name for name, lang in self._languages.items() if lang == current_language_name) - - dlg = BigMultiOptionDialog(list(self._languages), default=current_language, right_btn_callback=selected_language_callback) - gui_app.set_modal_overlay(dlg) - - # lang_button = BigButton("change language", "", "icons_mici/settings/device/language.png") - # lang_button.set_click_callback(language_callback) - regulatory_btn = BigButton("regulatory info", "", "icons_mici/settings/device/info.png") regulatory_btn.set_click_callback(self._on_regulatory) @@ -371,10 +354,6 @@ class DeviceLayoutMici(NavWidget): self._training_guide = TrainingGuide(completed_callback=completed_callback) gui_app.set_modal_overlay(self._training_guide, callback=lambda result: setattr(self, '_training_guide', None)) - def _load_languages(self): - with open(os.path.join(BASEDIR, "selfdrive/ui/translations/languages.json")) as f: - self._languages = json.load(f) - def show_event(self): super().show_event() self._scroller.show_event() From 4d3aeaba6d03d0e7e28907e77d167366093c6d5b Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 10 Feb 2026 15:04:36 -0800 Subject: [PATCH 886/910] ui: remove dead side button (#37151) * rm side button * fix * fix --- .../icons_mici/buttons/button_side_back.png | 3 -- .../buttons/button_side_back_pressed.png | 3 -- .../icons_mici/buttons/button_side_check.png | 3 -- .../buttons/button_side_check_pressed.png | 3 -- .../mici/layouts/settings/network/wifi_ui.py | 2 +- selfdrive/ui/mici/widgets/dialog.py | 35 ++++--------------- selfdrive/ui/mici/widgets/side_button.py | 31 ---------------- 7 files changed, 7 insertions(+), 73 deletions(-) delete mode 100644 selfdrive/assets/icons_mici/buttons/button_side_back.png delete mode 100644 selfdrive/assets/icons_mici/buttons/button_side_back_pressed.png delete mode 100644 selfdrive/assets/icons_mici/buttons/button_side_check.png delete mode 100644 selfdrive/assets/icons_mici/buttons/button_side_check_pressed.png delete mode 100644 selfdrive/ui/mici/widgets/side_button.py diff --git a/selfdrive/assets/icons_mici/buttons/button_side_back.png b/selfdrive/assets/icons_mici/buttons/button_side_back.png deleted file mode 100644 index 3d648d34f..000000000 --- a/selfdrive/assets/icons_mici/buttons/button_side_back.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9df44871e9f5fa910622b0b92205b92a54d137dbdc3827b92e8622d85ff2e08e -size 5189 diff --git a/selfdrive/assets/icons_mici/buttons/button_side_back_pressed.png b/selfdrive/assets/icons_mici/buttons/button_side_back_pressed.png deleted file mode 100644 index e431cb0c7..000000000 --- a/selfdrive/assets/icons_mici/buttons/button_side_back_pressed.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:013b368b38b17d9b2ef6aaf0f498f672deed95888084b7287f42bdfba617cbb6 -size 10142 diff --git a/selfdrive/assets/icons_mici/buttons/button_side_check.png b/selfdrive/assets/icons_mici/buttons/button_side_check.png deleted file mode 100644 index 820b23606..000000000 --- a/selfdrive/assets/icons_mici/buttons/button_side_check.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8fd563eec78d5ce4a8204c2f596789e1090cb3e26a35b4ffeacee4ab61968538 -size 8303 diff --git a/selfdrive/assets/icons_mici/buttons/button_side_check_pressed.png b/selfdrive/assets/icons_mici/buttons/button_side_check_pressed.png deleted file mode 100644 index 6c38508af..000000000 --- a/selfdrive/assets/icons_mici/buttons/button_side_check_pressed.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0be8d5eddcd9f87acbf1daccf446be6218522120f64aee1ee0a3c0b31560f076 -size 15761 diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index 7791f18cf..db68632c0 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -318,7 +318,7 @@ class WifiUIMici(BigMultiOptionDialog): INACTIVITY_TIMEOUT = 1 def __init__(self, wifi_manager: WifiManager, back_callback: Callable): - super().__init__([], None, None, right_btn_callback=None) + super().__init__([], None) # Set up back navigation self.set_back_callback(back_callback) diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index 49d73f7b0..b88ac2049 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -14,7 +14,6 @@ from openpilot.system.ui.widgets.scroller import Scroller from openpilot.system.ui.widgets.slider import RedBigSlider, BigSlider from openpilot.common.filter_simple import FirstOrderFilter from openpilot.selfdrive.ui.mici.widgets.button import BigButton -from openpilot.selfdrive.ui.mici.widgets.side_button import SideButton DEBUG = False @@ -22,32 +21,17 @@ PADDING = 20 class BigDialogBase(NavWidget, abc.ABC): - def __init__(self, right_btn: str | None = None, right_btn_callback: Callable | None = None): + def __init__(self): super().__init__() self._ret = DialogResult.NO_ACTION self.set_rect(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) self.set_back_callback(lambda: setattr(self, '_ret', DialogResult.CANCEL)) - self._right_btn = None - if right_btn: - def right_btn_callback_wrapper(): - gui_app.set_modal_overlay(None) - if right_btn_callback: - right_btn_callback() - - self._right_btn = SideButton(right_btn) - self._right_btn.set_click_callback(right_btn_callback_wrapper) - # move to right side - self._right_btn._rect.x = self._rect.x + self._rect.width - self._right_btn._rect.width - def _render(self, _) -> DialogResult: """ Allows `gui_app.set_modal_overlay(BigDialog(...))`. The overlay runner keeps calling until result != NO_ACTION. """ - if self._right_btn: - self._right_btn.set_position(self._right_btn._rect.x, self._rect.y) - self._right_btn.render() return self._ret @@ -55,10 +39,8 @@ class BigDialogBase(NavWidget, abc.ABC): class BigDialog(BigDialogBase): def __init__(self, title: str, - description: str, - right_btn: str | None = None, - right_btn_callback: Callable | None = None): - super().__init__(right_btn, right_btn_callback) + description: str): + super().__init__() self._title = title self._description = description @@ -70,8 +52,6 @@ class BigDialog(BigDialogBase): # TODO: coming up with these numbers manually is a pain and not scalable # TODO: no clue what any of these numbers mean. VBox and HBox would remove all of this shite max_width = self._rect.width - PADDING * 2 - if self._right_btn: - max_width -= self._right_btn._rect.width title_wrapped = '\n'.join(wrap_text(gui_app.font(FontWeight.BOLD), self._title, 50, int(max_width))) title_size = measure_text_cached(gui_app.font(FontWeight.BOLD), title_wrapped, 50) @@ -139,7 +119,7 @@ class BigInputDialog(BigDialogBase): default_text: str = "", minimum_length: int = 1, confirm_callback: Callable[[str], None] | None = None): - super().__init__(None, None) + super().__init__() self._hint_label = UnifiedLabel(hint, font_size=35, text_color=rl.Color(255, 255, 255, int(255 * 0.35)), font_weight=FontWeight.MEDIUM) self._keyboard = MiciKeyboard() @@ -310,9 +290,8 @@ class BigDialogOptionButton(Widget): class BigMultiOptionDialog(BigDialogBase): BACK_TOUCH_AREA_PERCENTAGE = 0.1 - def __init__(self, options: list[str], default: str | None, - right_btn: str | None = 'check', right_btn_callback: Callable[[], None] | None = None): - super().__init__(right_btn, right_btn_callback=right_btn_callback) + def __init__(self, options: list[str], default: str | None): + super().__init__() self._options = options if default is not None: assert default in options @@ -325,8 +304,6 @@ class BigMultiOptionDialog(BigDialogBase): self._can_click = True self._scroller = Scroller([], horizontal=False, pad_start=100, pad_end=100, spacing=0, snap_items=True) - if self._right_btn is not None: - self._scroller.set_enabled(lambda: not cast(Widget, self._right_btn).is_pressed) for option in options: self._scroller.add_widget(BigDialogOptionButton(option)) diff --git a/selfdrive/ui/mici/widgets/side_button.py b/selfdrive/ui/mici/widgets/side_button.py deleted file mode 100644 index 4803b6d20..000000000 --- a/selfdrive/ui/mici/widgets/side_button.py +++ /dev/null @@ -1,31 +0,0 @@ -import pyray as rl -from openpilot.system.ui.widgets import Widget -from openpilot.system.ui.lib.application import gui_app - -# --------------------------------------------------------------------------- -# Constants extracted from the original Qt style -# --------------------------------------------------------------------------- -# TODO: this should be corrected, but Scroller relies on this being incorrect :/ -WIDTH, HEIGHT = 112, 240 - - -class SideButton(Widget): - def __init__(self, btn_type: str): - super().__init__() - self.type = btn_type - self.set_rect(rl.Rectangle(0, 0, WIDTH, HEIGHT)) - - # load pre-rendered button images - if btn_type not in ("check", "back"): - btn_type = "back" - btn_img_path = f"icons_mici/buttons/button_side_{btn_type}.png" - btn_img_pressed_path = f"icons_mici/buttons/button_side_{btn_type}_pressed.png" - self._txt_btn, self._txt_btn_back = gui_app.texture(btn_img_path, 100, 224), gui_app.texture(btn_img_pressed_path, 100, 224) - - def _render(self, _) -> bool: - x = int(self._rect.x + 12) - y = int(self._rect.y + (self._rect.height - self._txt_btn.height) / 2) - rl.draw_texture(self._txt_btn if not self.is_pressed else self._txt_btn_back, - x, y, rl.WHITE) - - return False From 46ae67b6071c3983eb4cb0dc46bb3af832c88e1d Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 10 Feb 2026 17:15:59 -0800 Subject: [PATCH 887/910] BigButton: fix alignment and style (#37153) * correct from bottom alignment * temp * fix scale animation w/ btn_y * home settings are always 64 * cleanup * some clean up * make 23 const * rev * more --- selfdrive/ui/mici/layouts/settings/device.py | 3 ++ .../ui/mici/layouts/settings/settings.py | 15 ++++--- selfdrive/ui/mici/widgets/button.py | 44 +++++++++---------- 3 files changed, 35 insertions(+), 27 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/device.py b/selfdrive/ui/mici/layouts/settings/device.py index f4b6217ac..60eb7e5b0 100644 --- a/selfdrive/ui/mici/layouts/settings/device.py +++ b/selfdrive/ui/mici/layouts/settings/device.py @@ -120,6 +120,9 @@ class PairBigButton(BigButton): def __init__(self): super().__init__("pair", "connect.comma.ai", "icons_mici/settings/comma_icon.png", icon_size=(33, 60)) + def _get_label_font_size(self): + return 64 + def _update_state(self): if ui_state.prime_state.is_paired(): self.set_text("paired") diff --git a/selfdrive/ui/mici/layouts/settings/settings.py b/selfdrive/ui/mici/layouts/settings/settings.py index 391789903..a6f59a038 100644 --- a/selfdrive/ui/mici/layouts/settings/settings.py +++ b/selfdrive/ui/mici/layouts/settings/settings.py @@ -30,22 +30,27 @@ class PanelInfo: instance: Widget +class SettingsBigButton(BigButton): + def _get_label_font_size(self): + return 64 + + class SettingsLayout(NavWidget): def __init__(self): super().__init__() self._params = Params() self._current_panel = None # PanelType.DEVICE - toggles_btn = BigButton("toggles", "", "icons_mici/settings.png") + toggles_btn = SettingsBigButton("toggles", "", "icons_mici/settings.png") toggles_btn.set_click_callback(lambda: self._set_current_panel(PanelType.TOGGLES)) - network_btn = BigButton("network", "", "icons_mici/settings/network/wifi_strength_full.png", icon_size=(76, 56)) + network_btn = SettingsBigButton("network", "", "icons_mici/settings/network/wifi_strength_full.png", icon_size=(76, 56)) network_btn.set_click_callback(lambda: self._set_current_panel(PanelType.NETWORK)) - device_btn = BigButton("device", "", "icons_mici/settings/device_icon.png", icon_size=(74, 60)) + device_btn = SettingsBigButton("device", "", "icons_mici/settings/device_icon.png", icon_size=(74, 60)) device_btn.set_click_callback(lambda: self._set_current_panel(PanelType.DEVICE)) - developer_btn = BigButton("developer", "", "icons_mici/settings/developer_icon.png", icon_size=(64, 60)) + developer_btn = SettingsBigButton("developer", "", "icons_mici/settings/developer_icon.png", icon_size=(64, 60)) developer_btn.set_click_callback(lambda: self._set_current_panel(PanelType.DEVELOPER)) - firehose_btn = BigButton("firehose", "", "icons_mici/settings/firehose.png", icon_size=(52, 62)) + firehose_btn = SettingsBigButton("firehose", "", "icons_mici/settings/firehose.png", icon_size=(52, 62)) firehose_btn.set_click_callback(lambda: self._set_current_panel(PanelType.FIREHOSE)) self._scroller = Scroller([ diff --git a/selfdrive/ui/mici/widgets/button.py b/selfdrive/ui/mici/widgets/button.py index a5f7ee686..56dd3f7a2 100644 --- a/selfdrive/ui/mici/widgets/button.py +++ b/selfdrive/ui/mici/widgets/button.py @@ -17,6 +17,7 @@ SCROLLING_SPEED_PX_S = 50 COMPLICATION_SIZE = 36 LABEL_COLOR = rl.Color(255, 255, 255, int(255 * 0.9)) LABEL_HORIZONTAL_PADDING = 40 +LABEL_VERTICAL_PADDING = 23 # visually matches 30 in figma COMPLICATION_GREY = rl.Color(0xAA, 0xAA, 0xAA, 255) PRESSED_SCALE = 1.15 if DO_ZOOM else 1.07 @@ -118,14 +119,12 @@ class BigButton(Widget): self._rotate_icon_t: float | None = None - self._label_font = gui_app.font(FontWeight.DISPLAY) - self._value_font = gui_app.font(FontWeight.ROMAN) - - self._label = UnifiedLabel(text, font_size=self._get_label_font_size(), font_weight=FontWeight.DISPLAY, + self._label = UnifiedLabel(text, font_size=self._get_label_font_size(), font_weight=FontWeight.BOLD, text_color=LABEL_COLOR, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM, scroll=scroll, line_height=0.9) self._sub_label = UnifiedLabel(value, font_size=COMPLICATION_SIZE, font_weight=FontWeight.ROMAN, text_color=COMPLICATION_GREY, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM) + self._update_label_layout() self._load_images() @@ -149,25 +148,27 @@ class BigButton(Widget): return int(self._rect.width - LABEL_HORIZONTAL_PADDING * 2 - icon_size) def _get_label_font_size(self): - if len(self.text) < 12: - font_size = 64 - elif len(self.text) < 17: - font_size = 48 + if len(self.text) <= 18: + return 48 else: - font_size = 42 + return 42 + def _update_label_layout(self): + self._label.set_font_size(self._get_label_font_size()) if self.value: - font_size -= 20 - - return font_size + self._label.set_alignment_vertical(rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP) + else: + self._label.set_alignment_vertical(rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM) def set_text(self, text: str): self.text = text self._label.set_text(text) + self._update_label_layout() def set_value(self, value: str): self.value = value self._sub_label.set_text(value) + self._update_label_layout() def get_value(self) -> str: return self.value @@ -189,21 +190,20 @@ class BigButton(Widget): rl.draw_texture_ex(txt_bg, (btn_x, btn_y), 0, scale, rl.WHITE) # LABEL ------------------------------------------------------------------ - lx = self._rect.x + LABEL_HORIZONTAL_PADDING - ly = btn_y + self._rect.height - 33 # - 40# - self._get_label_font_size() / 2 - - if self.value: - sub_label_height = self._sub_label.get_content_height(self._width_hint()) - sub_label_rect = rl.Rectangle(lx, ly - sub_label_height, self._width_hint(), sub_label_height) - self._sub_label.render(sub_label_rect) - ly -= sub_label_height + label_x = self._rect.x + LABEL_HORIZONTAL_PADDING label_color = LABEL_COLOR if self.enabled else rl.Color(255, 255, 255, int(255 * 0.35)) self._label.set_color(label_color) - label_height = self._label.get_content_height(self._width_hint()) - label_rect = rl.Rectangle(lx, ly - label_height, self._width_hint(), label_height) + label_rect = rl.Rectangle(label_x, btn_y + LABEL_VERTICAL_PADDING, self._width_hint(), + self._rect.height - LABEL_VERTICAL_PADDING * 2) self._label.render(label_rect) + if self.value: + label_y = btn_y + self._rect.height - LABEL_VERTICAL_PADDING + sub_label_height = self._sub_label.get_content_height(self._width_hint()) + sub_label_rect = rl.Rectangle(label_x, label_y - sub_label_height, self._width_hint(), sub_label_height) + self._sub_label.render(sub_label_rect) + # ICON ------------------------------------------------------------------- if self._txt_icon: rotation = 0 From a18ddf12eb8b2d38894415bc5f07ea8049e28e8f Mon Sep 17 00:00:00 2001 From: Daniel Koepping Date: Tue, 10 Feb 2026 17:51:09 -0800 Subject: [PATCH 888/910] remove azure deps (#37084) * remove azure deps * fix lint * restore scripts --- pyproject.toml | 2 - .../test/process_replay/test_processes.py | 3 +- tools/lib/openpilotci.py | 14 +-- uv.lock | 88 ------------------- 4 files changed, 5 insertions(+), 102 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8b0f4004d..b1295ef81 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -97,8 +97,6 @@ testing = [ dev = [ "av", - "azure-identity", - "azure-storage-blob", "dictdiffer", "matplotlib", "opencv-python-headless", diff --git a/selfdrive/test/process_replay/test_processes.py b/selfdrive/test/process_replay/test_processes.py index 9f79c8ddc..fbe300a7c 100755 --- a/selfdrive/test/process_replay/test_processes.py +++ b/selfdrive/test/process_replay/test_processes.py @@ -189,7 +189,8 @@ if __name__ == "__main__": cur_log_fn = os.path.join(FAKEDATA, f"{segment}_{cfg.proc_name}_{cur_commit}.zst".replace("|", "_")) if args.update_refs: # reference logs will not exist if routes were just regenerated - ref_log_path = get_url(*segment.rsplit("--", 1,), "rlog.zst") + route, seg_num = segment.rsplit("--", 1) + ref_log_path = get_url(route, seg_num, "rlog.zst") else: ref_log_fn = os.path.join(FAKEDATA, f"{segment}_{cfg.proc_name}_{ref_commit}.zst".replace("|", "_")) ref_log_path = ref_log_fn if os.path.exists(ref_log_fn) else BASE_URL + os.path.basename(ref_log_fn) diff --git a/tools/lib/openpilotci.py b/tools/lib/openpilotci.py index 1c1e1f171..6b484dfda 100644 --- a/tools/lib/openpilotci.py +++ b/tools/lib/openpilotci.py @@ -1,12 +1,4 @@ -from openpilot.tools.lib.openpilotcontainers import OpenpilotCIContainer +BASE_URL = "https://commadataci.blob.core.windows.net/openpilotci/" -def get_url(*args, **kwargs): - return OpenpilotCIContainer.get_url(*args, **kwargs) - -def upload_file(*args, **kwargs): - return OpenpilotCIContainer.upload_file(*args, **kwargs) - -def upload_bytes(*args, **kwargs): - return OpenpilotCIContainer.upload_bytes(*args, **kwargs) - -BASE_URL = OpenpilotCIContainer.BASE_URL +def get_url(route_name: str, segment_num, filename: str) -> str: + return BASE_URL + f"{route_name.replace('|', '/')}/{segment_num}/{filename}" diff --git a/uv.lock b/uv.lock index 83805c646..d4c75f2e5 100644 --- a/uv.lock +++ b/uv.lock @@ -119,50 +119,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7b/ff/48fa68888b8d5bae36d915556ff18f9e5fdc6b5ff5ae23dc4904c9713168/av-13.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:5ea0deab0e6a739cb742fba2a3983d8102f7516a3cdf3c46669f3cac0ed1f351", size = 25781343, upload-time = "2024-10-06T04:53:29.577Z" }, ] -[[package]] -name = "azure-core" -version = "1.38.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "requests" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/dc/1b/e503e08e755ea94e7d3419c9242315f888fc664211c90d032e40479022bf/azure_core-1.38.0.tar.gz", hash = "sha256:8194d2682245a3e4e3151a667c686464c3786fed7918b394d035bdcd61bb5993", size = 363033, upload-time = "2026-01-12T17:03:05.535Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/d8/b8fcba9464f02b121f39de2db2bf57f0b216fe11d014513d666e8634380d/azure_core-1.38.0-py3-none-any.whl", hash = "sha256:ab0c9b2cd71fecb1842d52c965c95285d3cfb38902f6766e4a471f1cd8905335", size = 217825, upload-time = "2026-01-12T17:03:07.291Z" }, -] - -[[package]] -name = "azure-identity" -version = "1.25.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "azure-core" }, - { name = "cryptography" }, - { name = "msal" }, - { name = "msal-extensions" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/06/8d/1a6c41c28a37eab26dc85ab6c86992c700cd3f4a597d9ed174b0e9c69489/azure_identity-1.25.1.tar.gz", hash = "sha256:87ca8328883de6036443e1c37b40e8dc8fb74898240f61071e09d2e369361456", size = 279826, upload-time = "2025-10-06T20:30:02.194Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/83/7b/5652771e24fff12da9dde4c20ecf4682e606b104f26419d139758cc935a6/azure_identity-1.25.1-py3-none-any.whl", hash = "sha256:e9edd720af03dff020223cd269fa3a61e8f345ea75443858273bcb44844ab651", size = 191317, upload-time = "2025-10-06T20:30:04.251Z" }, -] - -[[package]] -name = "azure-storage-blob" -version = "12.28.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "azure-core" }, - { name = "cryptography" }, - { name = "isodate" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/71/24/072ba8e27b0e2d8fec401e9969b429d4f5fc4c8d4f0f05f4661e11f7234a/azure_storage_blob-12.28.0.tar.gz", hash = "sha256:e7d98ea108258d29aa0efbfd591b2e2075fa1722a2fae8699f0b3c9de11eff41", size = 604225, upload-time = "2026-01-06T23:48:57.282Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/3a/6ef2047a072e54e1142718d433d50e9514c999a58f51abfff7902f3a72f8/azure_storage_blob-12.28.0-py3-none-any.whl", hash = "sha256:00fb1db28bf6a7b7ecaa48e3b1d5c83bfadacc5a678b77826081304bd87d6461", size = 431499, upload-time = "2026-01-06T23:48:58.995Z" }, -] - [[package]] name = "casadi" version = "3.7.2" @@ -595,15 +551,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d0/94/040a0d9c81f018c39bd887b7b825013b024deb0a6c795f9524797e2cd41b/inputs-0.5-py2.py3-none-any.whl", hash = "sha256:13f894564e52134cf1e3862b1811da034875eb1f2b62e6021e3776e9669a96ec", size = 33630, upload-time = "2018-10-05T22:38:28.28Z" }, ] -[[package]] -name = "isodate" -version = "0.7.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/54/4d/e940025e2ce31a8ce1202635910747e5a87cc3a6a6bb2d00973375014749/isodate-0.7.2.tar.gz", hash = "sha256:4cd1aa0f43ca76f4a6c6c0292a85f40b35ec2e43e315b59f06e6d32171a953e6", size = 29705, upload-time = "2024-10-08T23:04:11.5Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/15/aa/0aca39a37d3c7eb941ba736ede56d689e7be91cab5d9ca846bde3999eba6/isodate-0.7.2-py3-none-any.whl", hash = "sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15", size = 22320, upload-time = "2024-10-08T23:04:09.501Z" }, -] - [[package]] name = "jeepney" version = "0.9.0" @@ -903,32 +850,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" }, ] -[[package]] -name = "msal" -version = "1.34.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cryptography" }, - { name = "pyjwt", extra = ["crypto"] }, - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/cf/0e/c857c46d653e104019a84f22d4494f2119b4fe9f896c92b4b864b3b045cc/msal-1.34.0.tar.gz", hash = "sha256:76ba83b716ea5a6d75b0279c0ac353a0e05b820ca1f6682c0eb7f45190c43c2f", size = 153961, upload-time = "2025-09-22T23:05:48.989Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/dc/18d48843499e278538890dc709e9ee3dea8375f8be8e82682851df1b48b5/msal-1.34.0-py3-none-any.whl", hash = "sha256:f669b1644e4950115da7a176441b0e13ec2975c29528d8b9e81316023676d6e1", size = 116987, upload-time = "2025-09-22T23:05:47.294Z" }, -] - -[[package]] -name = "msal-extensions" -version = "1.3.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "msal" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/01/99/5d239b6156eddf761a636bded1118414d161bd6b7b37a9335549ed159396/msal_extensions-1.3.1.tar.gz", hash = "sha256:c5b0fd10f65ef62b5f1d62f4251d51cbcaf003fcedae8c91b040a488614be1a4", size = 23315, upload-time = "2025-03-14T23:51:03.902Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5e/75/bd9b7bb966668920f06b200e84454c8f3566b102183bc55c5473d96cb2b9/msal_extensions-1.3.1-py3-none-any.whl", hash = "sha256:96d3de4d034504e969ac5e85bae8106c8373b5c6568e4c8fa7af2eca9dbe6bca", size = 20583, upload-time = "2025-03-14T23:51:03.016Z" }, -] - [[package]] name = "multidict" version = "6.7.1" @@ -1058,8 +979,6 @@ dependencies = [ [package.optional-dependencies] dev = [ { name = "av" }, - { name = "azure-identity" }, - { name = "azure-storage-blob" }, { name = "dictdiffer" }, { name = "matplotlib" }, { name = "opencv-python-headless" }, @@ -1097,8 +1016,6 @@ requires-dist = [ { name = "aiohttp" }, { name = "aiortc" }, { name = "av", marker = "extra == 'dev'" }, - { name = "azure-identity", marker = "extra == 'dev'" }, - { name = "azure-storage-blob", marker = "extra == 'dev'" }, { name = "casadi", specifier = ">=3.6.6" }, { name = "cffi" }, { name = "codespell", marker = "extra == 'testing'" }, @@ -1448,11 +1365,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997, upload-time = "2024-11-28T03:43:27.893Z" }, ] -[package.optional-dependencies] -crypto = [ - { name = "cryptography" }, -] - [[package]] name = "pylibsrtp" version = "1.0.0" From bef93ecf656998882f3b4b45449ece9130c59fad Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 10 Feb 2026 22:24:18 -0500 Subject: [PATCH 889/910] [TIZI/TICI] ui: fix Developer UI orders (#1682) --- .../ui/sunnypilot/onroad/developer_ui/__init__.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/selfdrive/ui/sunnypilot/onroad/developer_ui/__init__.py b/selfdrive/ui/sunnypilot/onroad/developer_ui/__init__.py index f74f1f4c3..8e19f876a 100644 --- a/selfdrive/ui/sunnypilot/onroad/developer_ui/__init__.py +++ b/selfdrive/ui/sunnypilot/onroad/developer_ui/__init__.py @@ -19,8 +19,8 @@ from openpilot.system.ui.widgets import Widget class DeveloperUiRenderer(Widget): DEV_UI_OFF = 0 - DEV_UI_RIGHT = 1 - DEV_UI_BOTTOM = 2 + DEV_UI_BOTTOM = 1 + DEV_UI_RIGHT = 2 DEV_UI_BOTH = 3 BOTTOM_BAR_HEIGHT = 61 @@ -62,10 +62,10 @@ class DeveloperUiRenderer(Widget): if sm.recv_frame["carState"] < ui_state.started_frame: return - if self.dev_ui_mode == self.DEV_UI_RIGHT: - self._draw_right_dev_ui(rect) - elif self.dev_ui_mode == self.DEV_UI_BOTTOM: + if self.dev_ui_mode == self.DEV_UI_BOTTOM: self._draw_bottom_dev_ui(rect) + elif self.dev_ui_mode == self.DEV_UI_RIGHT: + self._draw_right_dev_ui(rect) elif self.dev_ui_mode == self.DEV_UI_BOTH: self._draw_right_dev_ui(rect) self._draw_bottom_dev_ui(rect) From 3d3a5de4097ade84e192313a3d62be69f8305f18 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 10 Feb 2026 22:58:59 -0500 Subject: [PATCH 890/910] [TIZI/TICI] ui: dynamic ICBM status (#1684) * [TIZI/TICI] ui: dynamic ICBM status * wat * make sure to init * make sure it exists * send it for all --- .../ui/sunnypilot/onroad/hud_renderer.py | 80 ++++++++++++++++++- selfdrive/ui/sunnypilot/ui_state.py | 1 + 2 files changed, 79 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/sunnypilot/onroad/hud_renderer.py b/selfdrive/ui/sunnypilot/onroad/hud_renderer.py index d8ba4b8bf..86fcf3c69 100644 --- a/selfdrive/ui/sunnypilot/onroad/hud_renderer.py +++ b/selfdrive/ui/sunnypilot/onroad/hud_renderer.py @@ -6,9 +6,8 @@ See the LICENSE.md file in the root directory for more details. """ import pyray as rl +from openpilot.common.constants import CV from openpilot.selfdrive.ui.mici.onroad.torque_bar import TorqueBar -from openpilot.selfdrive.ui.ui_state import ui_state -from openpilot.selfdrive.ui.onroad.hud_renderer import HudRenderer from openpilot.selfdrive.ui.sunnypilot.onroad.developer_ui import DeveloperUiRenderer from openpilot.selfdrive.ui.sunnypilot.onroad.road_name import RoadNameRenderer from openpilot.selfdrive.ui.sunnypilot.onroad.rocket_fuel import RocketFuel @@ -17,6 +16,11 @@ from openpilot.selfdrive.ui.sunnypilot.onroad.smart_cruise_control import SmartC from openpilot.selfdrive.ui.sunnypilot.onroad.turn_signal import TurnSignalController from openpilot.selfdrive.ui.sunnypilot.onroad.circular_alerts import CircularAlertsRenderer from openpilot.selfdrive.ui.sunnypilot.onroad.speed_renderer import SpeedRenderer +from openpilot.selfdrive.ui.ui_state import ui_state, UIStatus +from openpilot.selfdrive.ui.onroad.hud_renderer import HudRenderer, UI_CONFIG, FONT_SIZES, COLORS, CRUISE_DISABLED_CHAR +from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.lib.multilang import tr +from openpilot.system.ui.lib.text_measure import measure_text_cached class HudRendererSP(HudRenderer): @@ -32,7 +36,21 @@ class HudRendererSP(HudRenderer): self.speed_renderer = SpeedRenderer() self._torque_bar = TorqueBar(scale=3.0, always=True) + self.pcm_cruise_speed: bool = True + self.show_icbm_status: bool = False + self.icbm_active_counter: int = 0 + self.speed_cluster: float = 0.0 + self.speed_conv: float = CV.MS_TO_KPH if ui_state.is_metric else CV.MS_TO_MPH + def _update_state(self) -> None: + if ui_state.sm.recv_frame["carState"] < ui_state.started_frame: + return + + if ui_state.CP_SP is not None: + self.pcm_cruise_speed = ui_state.CP_SP.pcmCruiseSpeed + self.speed_conv = CV.MS_TO_KPH if ui_state.is_metric else CV.MS_TO_MPH + self.speed_cluster = ui_state.sm['carState'].cruiseState.speedCluster * self.speed_conv + super()._update_state() self.road_name_renderer.update() self.speed_limit_renderer.update() @@ -41,6 +59,64 @@ class HudRendererSP(HudRenderer): self.circular_alerts_renderer.update() self.speed_renderer.update() + def _get_icbm_status(self): + if not self.pcm_cruise_speed and ui_state.sm['carControl'].enabled: + if round(self.set_speed) != round(self.speed_cluster): + self.icbm_active_counter = 3 * gui_app.target_fps # 3 seconds usually + elif self.icbm_active_counter > 0: + self.icbm_active_counter -= 1 + else: + self.icbm_active_counter = 0 + + self.show_icbm_status = self.icbm_active_counter > 0 + + def _draw_set_speed(self, rect: rl.Rectangle) -> None: + self._get_icbm_status() + + set_speed_width = UI_CONFIG.set_speed_width_metric if ui_state.is_metric else UI_CONFIG.set_speed_width_imperial + x = rect.x + 60 + (UI_CONFIG.set_speed_width_imperial - set_speed_width) // 2 + y = rect.y + 45 + + set_speed_rect = rl.Rectangle(x, y, set_speed_width, UI_CONFIG.set_speed_height) + rl.draw_rectangle_rounded(set_speed_rect, 0.35, 10, COLORS.BLACK_TRANSLUCENT) + rl.draw_rectangle_rounded_lines_ex(set_speed_rect, 0.35, 10, 6, COLORS.BORDER_TRANSLUCENT) + + max_color = COLORS.GREY + set_speed_color = COLORS.DARK_GREY + if self.is_cruise_set: + set_speed_color = COLORS.WHITE + if ui_state.status == UIStatus.ENGAGED: + max_color = COLORS.ENGAGED + elif ui_state.status == UIStatus.DISENGAGED: + max_color = COLORS.DISENGAGED + elif ui_state.status == UIStatus.OVERRIDE: + max_color = COLORS.OVERRIDE + + max_str_size = 60 if self.show_icbm_status else 40 + max_str_y = 15 if self.show_icbm_status else 27 + + max_text = str(round(self.speed_cluster)) if self.show_icbm_status else tr("MAX") + max_text_width = measure_text_cached(self._font_semi_bold, max_text, max_str_size).x + rl.draw_text_ex( + self._font_semi_bold, + max_text, + rl.Vector2(x + (set_speed_width - max_text_width) / 2, y + max_str_y), + max_str_size, + 0, + max_color, + ) + + set_speed_text = CRUISE_DISABLED_CHAR if not self.is_cruise_set else str(round(self.set_speed)) + speed_text_width = measure_text_cached(self._font_bold, set_speed_text, FONT_SIZES.set_speed).x + rl.draw_text_ex( + self._font_bold, + set_speed_text, + rl.Vector2(x + (set_speed_width - speed_text_width) / 2, y + 77), + FONT_SIZES.set_speed, + 0, + set_speed_color, + ) + def _draw_current_speed(self, rect: rl.Rectangle) -> None: self.speed_renderer.render(rect) diff --git a/selfdrive/ui/sunnypilot/ui_state.py b/selfdrive/ui/sunnypilot/ui_state.py index 6403157d5..369909812 100644 --- a/selfdrive/ui/sunnypilot/ui_state.py +++ b/selfdrive/ui/sunnypilot/ui_state.py @@ -26,6 +26,7 @@ class OnroadTimerStatus(Enum): class UIStateSP: def __init__(self): + self.CP_SP: custom.CarParamsSP | None = None self.params = Params() self.sm_services_ext = [ "modelManagerSP", "selfdriveStateSP", "longitudinalPlanSP", "backupManagerSP", From 6892b62761d9009c9028f51ab5b90d9460386ed7 Mon Sep 17 00:00:00 2001 From: commaci-public <60409688+commaci-public@users.noreply.github.com> Date: Tue, 10 Feb 2026 20:48:34 -0800 Subject: [PATCH 891/910] [bot] Update Python packages (#37147) * Update Python packages * fix * bump panda * revert tinygrad --------- Co-authored-by: Vehicle Researcher Co-authored-by: Adeeb Shihadeh --- cereal/messaging/bridge.cc | 6 +- cereal/messaging/bridge_zmq.h | 12 +- cereal/messaging/msgq_to_zmq.cc | 2 +- cereal/messaging/msgq_to_zmq.h | 2 +- msgq_repo | 2 +- opendbc_repo | 2 +- panda | 2 +- uv.lock | 201 ++++++++++++++++---------------- 8 files changed, 115 insertions(+), 114 deletions(-) diff --git a/cereal/messaging/bridge.cc b/cereal/messaging/bridge.cc index 2eb3de32c..77823c413 100644 --- a/cereal/messaging/bridge.cc +++ b/cereal/messaging/bridge.cc @@ -26,12 +26,12 @@ void msgq_to_zmq(const std::vector &endpoints, const std::string &i void zmq_to_msgq(const std::vector &endpoints, const std::string &ip) { auto poller = std::make_unique(); - auto pub_context = std::make_unique(); + auto pub_context = std::make_unique(); auto sub_context = std::make_unique(); - std::map sub2pub; + std::map sub2pub; for (auto endpoint : endpoints) { - auto pub_sock = new MSGQPubSocket(); + auto pub_sock = new PubSocket(); auto sub_sock = new BridgeZmqSubSocket(); size_t queue_size = services.at(endpoint).queue_size; pub_sock->connect(pub_context.get(), endpoint, true, queue_size); diff --git a/cereal/messaging/bridge_zmq.h b/cereal/messaging/bridge_zmq.h index eab48a91e..ebdbc56c2 100644 --- a/cereal/messaging/bridge_zmq.h +++ b/cereal/messaging/bridge_zmq.h @@ -20,12 +20,12 @@ private: class BridgeZmqMessage : public Message { public: - void init(size_t size) override; - void init(char *data, size_t size) override; - void close() override; - size_t getSize() override { return size; } - char *getData() override { return data; } - ~BridgeZmqMessage() override; + void init(size_t size); + void init(char *data, size_t size); + void close(); + size_t getSize() { return size; } + char *getData() { return data; } + ~BridgeZmqMessage(); private: char *data = nullptr; diff --git a/cereal/messaging/msgq_to_zmq.cc b/cereal/messaging/msgq_to_zmq.cc index 930b617e7..5e7ea2227 100644 --- a/cereal/messaging/msgq_to_zmq.cc +++ b/cereal/messaging/msgq_to_zmq.cc @@ -23,7 +23,7 @@ static std::string recv_zmq_msg(void *sock) { void MsgqToZmq::run(const std::vector &endpoints, const std::string &ip) { zmq_context = std::make_unique(); - msgq_context = std::make_unique(); + msgq_context = std::make_unique(); // Create ZMQPubSockets for each endpoint for (const auto &endpoint : endpoints) { diff --git a/cereal/messaging/msgq_to_zmq.h b/cereal/messaging/msgq_to_zmq.h index ac92631fe..64f3a2173 100644 --- a/cereal/messaging/msgq_to_zmq.h +++ b/cereal/messaging/msgq_to_zmq.h @@ -26,7 +26,7 @@ protected: int connected_clients = 0; }; - std::unique_ptr msgq_context; + std::unique_ptr msgq_context; std::unique_ptr zmq_context; std::mutex mutex; std::condition_variable cv; diff --git a/msgq_repo b/msgq_repo index f9ebdca88..2c191c1a7 160000 --- a/msgq_repo +++ b/msgq_repo @@ -1 +1 @@ -Subproject commit f9ebdca885cfe25295d09de9fd57023a10758de5 +Subproject commit 2c191c1a72ae8119b93b49e1bc12d4a99b751855 diff --git a/opendbc_repo b/opendbc_repo index 05348982c..143e87f3e 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 05348982cb7b6bc6fed73611ac26ecda658194fc +Subproject commit 143e87f3e4565abd1d2d70c32adcb3167d9c64ca diff --git a/panda b/panda index 81615ad9d..b99d79692 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 81615ad9d53aef5583e064f340e9cdeb23d4119c +Subproject commit b99d79692409514d296d85f367897fa2f86daaa5 diff --git a/uv.lock b/uv.lock index d4c75f2e5..816733d12 100644 --- a/uv.lock +++ b/uv.lock @@ -256,24 +256,26 @@ wheels = [ [[package]] name = "coverage" -version = "7.13.2" +version = "7.13.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ad/49/349848445b0e53660e258acbcc9b0d014895b6739237920886672240f84b/coverage-7.13.2.tar.gz", hash = "sha256:044c6951ec37146b72a50cc81ef02217d27d4c3640efd2640311393cbbf143d3", size = 826523, upload-time = "2026-01-25T13:00:04.889Z" } +sdist = { url = "https://files.pythonhosted.org/packages/24/56/95b7e30fa389756cb56630faa728da46a27b8c6eb46f9d557c68fff12b65/coverage-7.13.4.tar.gz", hash = "sha256:e5c8f6ed1e61a8b2dcdf31eb0b9bbf0130750ca79c1c49eb898e2ad86f5ccc91", size = 827239, upload-time = "2026-02-09T12:59:03.86Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/46/39/e92a35f7800222d3f7b2cbb7bbc3b65672ae8d501cb31801b2d2bd7acdf1/coverage-7.13.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f106b2af193f965d0d3234f3f83fc35278c7fb935dfbde56ae2da3dd2c03b84d", size = 219142, upload-time = "2026-01-25T12:58:00.448Z" }, - { url = "https://files.pythonhosted.org/packages/45/7a/8bf9e9309c4c996e65c52a7c5a112707ecdd9fbaf49e10b5a705a402bbb4/coverage-7.13.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78f45d21dc4d5d6bd29323f0320089ef7eae16e4bef712dff79d184fa7330af3", size = 219503, upload-time = "2026-01-25T12:58:02.451Z" }, - { url = "https://files.pythonhosted.org/packages/87/93/17661e06b7b37580923f3f12406ac91d78aeed293fb6da0b69cc7957582f/coverage-7.13.2-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:fae91dfecd816444c74531a9c3d6ded17a504767e97aa674d44f638107265b99", size = 251006, upload-time = "2026-01-25T12:58:04.059Z" }, - { url = "https://files.pythonhosted.org/packages/12/f0/f9e59fb8c310171497f379e25db060abef9fa605e09d63157eebec102676/coverage-7.13.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:264657171406c114787b441484de620e03d8f7202f113d62fcd3d9688baa3e6f", size = 253750, upload-time = "2026-01-25T12:58:05.574Z" }, - { url = "https://files.pythonhosted.org/packages/e5/b1/1935e31add2232663cf7edd8269548b122a7d100047ff93475dbaaae673e/coverage-7.13.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae47d8dcd3ded0155afbb59c62bd8ab07ea0fd4902e1c40567439e6db9dcaf2f", size = 254862, upload-time = "2026-01-25T12:58:07.647Z" }, - { url = "https://files.pythonhosted.org/packages/af/59/b5e97071ec13df5f45da2b3391b6cdbec78ba20757bc92580a5b3d5fa53c/coverage-7.13.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8a0b33e9fd838220b007ce8f299114d406c1e8edb21336af4c97a26ecfd185aa", size = 251420, upload-time = "2026-01-25T12:58:09.309Z" }, - { url = "https://files.pythonhosted.org/packages/3f/75/9495932f87469d013dc515fb0ce1aac5fa97766f38f6b1a1deb1ee7b7f3a/coverage-7.13.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b3becbea7f3ce9a2d4d430f223ec15888e4deb31395840a79e916368d6004cce", size = 252786, upload-time = "2026-01-25T12:58:10.909Z" }, - { url = "https://files.pythonhosted.org/packages/6a/59/af550721f0eb62f46f7b8cb7e6f1860592189267b1c411a4e3a057caacee/coverage-7.13.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f819c727a6e6eeb8711e4ce63d78c620f69630a2e9d53bc95ca5379f57b6ba94", size = 250928, upload-time = "2026-01-25T12:58:12.449Z" }, - { url = "https://files.pythonhosted.org/packages/9b/b1/21b4445709aae500be4ab43bbcfb4e53dc0811c3396dcb11bf9f23fd0226/coverage-7.13.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:4f7b71757a3ab19f7ba286e04c181004c1d61be921795ee8ba6970fd0ec91da5", size = 250496, upload-time = "2026-01-25T12:58:14.047Z" }, - { url = "https://files.pythonhosted.org/packages/ba/b1/0f5d89dfe0392990e4f3980adbde3eb34885bc1effb2dc369e0bf385e389/coverage-7.13.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b7fc50d2afd2e6b4f6f2f403b70103d280a8e0cb35320cbbe6debcda02a1030b", size = 252373, upload-time = "2026-01-25T12:58:15.976Z" }, - { url = "https://files.pythonhosted.org/packages/01/c9/0cf1a6a57a9968cc049a6b896693faa523c638a5314b1fc374eb2b2ac904/coverage-7.13.2-cp312-cp312-win32.whl", hash = "sha256:292250282cf9bcf206b543d7608bda17ca6fc151f4cbae949fc7e115112fbd41", size = 221696, upload-time = "2026-01-25T12:58:17.517Z" }, - { url = "https://files.pythonhosted.org/packages/4d/05/d7540bf983f09d32803911afed135524570f8c47bb394bf6206c1dc3a786/coverage-7.13.2-cp312-cp312-win_amd64.whl", hash = "sha256:eeea10169fac01549a7921d27a3e517194ae254b542102267bef7a93ed38c40e", size = 222504, upload-time = "2026-01-25T12:58:19.115Z" }, - { url = "https://files.pythonhosted.org/packages/15/8b/1a9f037a736ced0a12aacf6330cdaad5008081142a7070bc58b0f7930cbc/coverage-7.13.2-cp312-cp312-win_arm64.whl", hash = "sha256:2a5b567f0b635b592c917f96b9a9cb3dbd4c320d03f4bf94e9084e494f2e8894", size = 221120, upload-time = "2026-01-25T12:58:21.334Z" }, - { url = "https://files.pythonhosted.org/packages/d2/db/d291e30fdf7ea617a335531e72294e0c723356d7fdde8fba00610a76bda9/coverage-7.13.2-py3-none-any.whl", hash = "sha256:40ce1ea1e25125556d8e76bd0b61500839a07944cc287ac21d5626f3e620cad5", size = 210943, upload-time = "2026-01-25T13:00:02.388Z" }, + { url = "https://files.pythonhosted.org/packages/d1/81/4ce2fdd909c5a0ed1f6dedb88aa57ab79b6d1fbd9b588c1ac7ef45659566/coverage-7.13.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:02231499b08dabbe2b96612993e5fc34217cdae907a51b906ac7fca8027a4459", size = 219449, upload-time = "2026-02-09T12:56:54.889Z" }, + { url = "https://files.pythonhosted.org/packages/5d/96/5238b1efc5922ddbdc9b0db9243152c09777804fb7c02ad1741eb18a11c0/coverage-7.13.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40aa8808140e55dc022b15d8aa7f651b6b3d68b365ea0398f1441e0b04d859c3", size = 219810, upload-time = "2026-02-09T12:56:56.33Z" }, + { url = "https://files.pythonhosted.org/packages/78/72/2f372b726d433c9c35e56377cf1d513b4c16fe51841060d826b95caacec1/coverage-7.13.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5b856a8ccf749480024ff3bd7310adaef57bf31fd17e1bfc404b7940b6986634", size = 251308, upload-time = "2026-02-09T12:56:57.858Z" }, + { url = "https://files.pythonhosted.org/packages/5d/a0/2ea570925524ef4e00bb6c82649f5682a77fac5ab910a65c9284de422600/coverage-7.13.4-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2c048ea43875fbf8b45d476ad79f179809c590ec7b79e2035c662e7afa3192e3", size = 254052, upload-time = "2026-02-09T12:56:59.754Z" }, + { url = "https://files.pythonhosted.org/packages/e8/ac/45dc2e19a1939098d783c846e130b8f862fbb50d09e0af663988f2f21973/coverage-7.13.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b7b38448866e83176e28086674fe7368ab8590e4610fb662b44e345b86d63ffa", size = 255165, upload-time = "2026-02-09T12:57:01.287Z" }, + { url = "https://files.pythonhosted.org/packages/2d/4d/26d236ff35abc3b5e63540d3386e4c3b192168c1d96da5cb2f43c640970f/coverage-7.13.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:de6defc1c9badbf8b9e67ae90fd00519186d6ab64e5cc5f3d21359c2a9b2c1d3", size = 257432, upload-time = "2026-02-09T12:57:02.637Z" }, + { url = "https://files.pythonhosted.org/packages/ec/55/14a966c757d1348b2e19caf699415a2a4c4f7feaa4bbc6326a51f5c7dd1b/coverage-7.13.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7eda778067ad7ffccd23ecffce537dface96212576a07924cbf0d8799d2ded5a", size = 251716, upload-time = "2026-02-09T12:57:04.056Z" }, + { url = "https://files.pythonhosted.org/packages/77/33/50116647905837c66d28b2af1321b845d5f5d19be9655cb84d4a0ea806b4/coverage-7.13.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e87f6c587c3f34356c3759f0420693e35e7eb0e2e41e4c011cb6ec6ecbbf1db7", size = 253089, upload-time = "2026-02-09T12:57:05.503Z" }, + { url = "https://files.pythonhosted.org/packages/c2/b4/8efb11a46e3665d92635a56e4f2d4529de6d33f2cb38afd47d779d15fc99/coverage-7.13.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8248977c2e33aecb2ced42fef99f2d319e9904a36e55a8a68b69207fb7e43edc", size = 251232, upload-time = "2026-02-09T12:57:06.879Z" }, + { url = "https://files.pythonhosted.org/packages/51/24/8cd73dd399b812cc76bb0ac260e671c4163093441847ffe058ac9fda1e32/coverage-7.13.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:25381386e80ae727608e662474db537d4df1ecd42379b5ba33c84633a2b36d47", size = 255299, upload-time = "2026-02-09T12:57:08.245Z" }, + { url = "https://files.pythonhosted.org/packages/03/94/0a4b12f1d0e029ce1ccc1c800944a9984cbe7d678e470bb6d3c6bc38a0da/coverage-7.13.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:ee756f00726693e5ba94d6df2bdfd64d4852d23b09bb0bc700e3b30e6f333985", size = 250796, upload-time = "2026-02-09T12:57:10.142Z" }, + { url = "https://files.pythonhosted.org/packages/73/44/6002fbf88f6698ca034360ce474c406be6d5a985b3fdb3401128031eef6b/coverage-7.13.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fdfc1e28e7c7cdce44985b3043bc13bbd9c747520f94a4d7164af8260b3d91f0", size = 252673, upload-time = "2026-02-09T12:57:12.197Z" }, + { url = "https://files.pythonhosted.org/packages/de/c6/a0279f7c00e786be75a749a5674e6fa267bcbd8209cd10c9a450c655dfa7/coverage-7.13.4-cp312-cp312-win32.whl", hash = "sha256:01d4cbc3c283a17fc1e42d614a119f7f438eabb593391283adca8dc86eff1246", size = 221990, upload-time = "2026-02-09T12:57:14.085Z" }, + { url = "https://files.pythonhosted.org/packages/77/4e/c0a25a425fcf5557d9abd18419c95b63922e897bc86c1f327f155ef234a9/coverage-7.13.4-cp312-cp312-win_amd64.whl", hash = "sha256:9401ebc7ef522f01d01d45532c68c5ac40fb27113019b6b7d8b208f6e9baa126", size = 222800, upload-time = "2026-02-09T12:57:15.944Z" }, + { url = "https://files.pythonhosted.org/packages/47/ac/92da44ad9a6f4e3a7debd178949d6f3769bedca33830ce9b1dcdab589a37/coverage-7.13.4-cp312-cp312-win_arm64.whl", hash = "sha256:b1ec7b6b6e93255f952e27ab58fbc68dcc468844b16ecbee881aeb29b6ab4d8d", size = 221415, upload-time = "2026-02-09T12:57:17.497Z" }, + { url = "https://files.pythonhosted.org/packages/0d/4a/331fe2caf6799d591109bb9c08083080f6de90a823695d412a935622abb2/coverage-7.13.4-py3-none-any.whl", hash = "sha256:1af1641e57cf7ba1bd67d677c9abdbcd6cc2ab7da3bca7fa1e2b7e50e65f2ad0", size = 211242, upload-time = "2026-02-09T12:59:02.032Z" }, ] [[package]] @@ -661,11 +663,11 @@ wheels = [ [[package]] name = "markdown" -version = "3.10.1" +version = "3.10.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b7/b1/af95bcae8549f1f3fd70faacb29075826a0d689a27f232e8cee315efa053/markdown-3.10.1.tar.gz", hash = "sha256:1c19c10bd5c14ac948c53d0d762a04e2fa35a6d58a6b7b1e6bfcbe6fefc0001a", size = 365402, upload-time = "2026-01-21T18:09:28.206Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2b/f4/69fa6ed85ae003c2378ffa8f6d2e3234662abd02c10d216c0ba96081a238/markdown-3.10.2.tar.gz", hash = "sha256:994d51325d25ad8aa7ce4ebaec003febcce822c3f8c911e3b17c52f7f589f950", size = 368805, upload-time = "2026-02-09T14:57:26.942Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/59/1b/6ef961f543593969d25b2afe57a3564200280528caa9bd1082eecdd7b3bc/markdown-3.10.1-py3-none-any.whl", hash = "sha256:867d788939fe33e4b736426f5b9f651ad0c0ae0ecf89df0ca5d1176c70812fe3", size = 107684, upload-time = "2026-01-21T18:09:27.203Z" }, + { url = "https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl", hash = "sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36", size = 108180, upload-time = "2026-02-09T14:57:25.787Z" }, ] [[package]] @@ -918,20 +920,20 @@ wheels = [ [[package]] name = "opencv-python-headless" -version = "4.13.0.90" +version = "4.13.0.92" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/ed/76/38c4cbb5ccfce7aaf36fd9be9fc74a15c85a48ef90bfaca2049b486e10c5/opencv_python_headless-4.13.0.90-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:12a28674f215542c9bf93338de1b5bffd76996d32da9acb9e739fdb9c8bbd738", size = 46020414, upload-time = "2026-01-18T09:07:10.801Z" }, - { url = "https://files.pythonhosted.org/packages/93/c5/4b40daa5003b45aa8397f160324a091ed323733e2446dc0bdf3655e77b84/opencv_python_headless-4.13.0.90-cp37-abi3-macosx_14_0_x86_64.whl", hash = "sha256:32255203040dc98803be96362e13f9e4bce20146898222d2e5c242f80de50da5", size = 32568519, upload-time = "2026-01-18T09:07:52.368Z" }, - { url = "https://files.pythonhosted.org/packages/da/65/920e64a7f03cf5917cd2c6a3046293843c1a16ad89f0ed0f1c683979c9de/opencv_python_headless-4.13.0.90-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e13790342591557050157713af17a7435ac1b50c65282715093c9297fa045d8f", size = 35191272, upload-time = "2026-01-18T09:08:49.235Z" }, - { url = "https://files.pythonhosted.org/packages/fc/13/af150685be342dc09bfb0824e2a280020ccf1c7fc64e15a31d9209016aa9/opencv_python_headless-4.13.0.90-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dbc1f4625e5af3a80ebdbd84380227c0f445228588f2521b11af47710caca1ba", size = 57683677, upload-time = "2026-01-18T09:10:23.588Z" }, - { url = "https://files.pythonhosted.org/packages/cd/47/baab2a3b6d8da8c52e73d00207d1ed3155601c2c332ea855455b3fbc8ff4/opencv_python_headless-4.13.0.90-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:eba38bc255d0b7d1969c5bcc90a060ca2b61a3403b613872c750bfa5dfe9e03b", size = 36590019, upload-time = "2026-01-18T09:10:49.053Z" }, - { url = "https://files.pythonhosted.org/packages/81/a1/facfe2801a861b424c4221d66e1281cf19735c00e07f063a337a208c11b5/opencv_python_headless-4.13.0.90-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f46b17ea0aa7e4124ca6ad71143f89233ae9557f61d2326bcdb34329a1ddf9bd", size = 62535926, upload-time = "2026-01-18T09:12:47.229Z" }, - { url = "https://files.pythonhosted.org/packages/06/d2/5e9ee7512306c1caa518be929d1f44bb1c189f342f538f73bea6fb94919f/opencv_python_headless-4.13.0.90-cp37-abi3-win32.whl", hash = "sha256:96060fc57a1abb1144b0b8129e2ff3bfcdd0ccd8e8bd05bd85256ff4ed587d3b", size = 30811665, upload-time = "2026-01-18T09:13:44.517Z" }, - { url = "https://files.pythonhosted.org/packages/a0/09/0a4d832448dccd03b2b1bdee70b9fc2e02c147cc7e06975e9cd729569d90/opencv_python_headless-4.13.0.90-cp37-abi3-win_amd64.whl", hash = "sha256:0e0c8c9f620802fddc4fa7f471a1d263c7b0dca16cd9e7e2f996bb8bd2128c0c", size = 40070035, upload-time = "2026-01-18T09:15:14.652Z" }, + { url = "https://files.pythonhosted.org/packages/79/42/2310883be3b8826ac58c3f2787b9358a2d46923d61f88fedf930bc59c60c/opencv_python_headless-4.13.0.92-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:1a7d040ac656c11b8c38677cc8cccdc149f98535089dbe5b081e80a4e5903209", size = 46247192, upload-time = "2026-02-05T07:01:35.187Z" }, + { url = "https://files.pythonhosted.org/packages/2d/1e/6f9e38005a6f7f22af785df42a43139d0e20f169eb5787ce8be37ee7fcc9/opencv_python_headless-4.13.0.92-cp37-abi3-macosx_14_0_x86_64.whl", hash = "sha256:3e0a6f0a37994ec6ce5f59e936be21d5d6384a4556f2d2da9c2f9c5dc948394c", size = 32568914, upload-time = "2026-02-05T07:01:51.989Z" }, + { url = "https://files.pythonhosted.org/packages/21/76/9417a6aef9def70e467a5bf560579f816148a4c658b7d525581b356eda9e/opencv_python_headless-4.13.0.92-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5c8cfc8e87ed452b5cecb9419473ee5560a989859fe1d10d1ce11ae87b09a2cb", size = 33703709, upload-time = "2026-02-05T10:24:46.469Z" }, + { url = "https://files.pythonhosted.org/packages/92/ce/bd17ff5772938267fd49716e94ca24f616ff4cb1ff4c6be13085108037be/opencv_python_headless-4.13.0.92-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0525a3d2c0b46c611e2130b5fdebc94cf404845d8fa64d2f3a3b679572a5bd22", size = 56016764, upload-time = "2026-02-05T10:26:48.904Z" }, + { url = "https://files.pythonhosted.org/packages/8f/b4/b7bcbf7c874665825a8c8e1097e93ea25d1f1d210a3e20d4451d01da30aa/opencv_python_headless-4.13.0.92-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:eb60e36b237b1ebd40a912da5384b348df8ed534f6f644d8e0b4f103e272ba7d", size = 35010236, upload-time = "2026-02-05T10:28:11.031Z" }, + { url = "https://files.pythonhosted.org/packages/4b/33/b5db29a6c00eb8f50708110d8d453747ca125c8b805bc437b289dbdcc057/opencv_python_headless-4.13.0.92-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0bd48544f77c68b2941392fcdf9bcd2b9cdf00e98cb8c29b2455d194763cf99e", size = 60391106, upload-time = "2026-02-05T10:30:14.236Z" }, + { url = "https://files.pythonhosted.org/packages/fb/c3/52cfea47cd33e53e8c0fbd6e7c800b457245c1fda7d61660b4ffe9596a7f/opencv_python_headless-4.13.0.92-cp37-abi3-win32.whl", hash = "sha256:a7cf08e5b191f4ebb530791acc0825a7986e0d0dee2a3c491184bd8599848a4b", size = 30812232, upload-time = "2026-02-05T07:02:29.594Z" }, + { url = "https://files.pythonhosted.org/packages/4a/90/b338326131ccb2aaa3c2c85d00f41822c0050139a4bfe723cfd95455bd2d/opencv_python_headless-4.13.0.92-cp37-abi3-win_amd64.whl", hash = "sha256:77a82fe35ddcec0f62c15f2ba8a12ecc2ed4207c17b0902c7a3151ae29f37fb6", size = 40070414, upload-time = "2026-02-05T07:02:26.448Z" }, ] [[package]] @@ -1135,11 +1137,11 @@ wheels = [ [[package]] name = "pathspec" -version = "1.0.3" +version = "1.0.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4c/b2/bb8e495d5262bfec41ab5cb18f522f1012933347fb5d9e62452d446baca2/pathspec-1.0.3.tar.gz", hash = "sha256:bac5cf97ae2c2876e2d25ebb15078eb04d76e4b98921ee31c6f85ade8b59444d", size = 130841, upload-time = "2026-01-09T15:46:46.009Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/36/e27608899f9b8d4dff0617b2d9ab17ca5608956ca44461ac14ac48b44015/pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645", size = 131200, upload-time = "2026-01-27T03:59:46.938Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/32/2b/121e912bd60eebd623f873fd090de0e84f322972ab25a7f9044c056804ed/pathspec-1.0.3-py3-none-any.whl", hash = "sha256:e80767021c1cc524aa3fb14bedda9c34406591343cc42797b386ce7b9354fb6c", size = 55021, upload-time = "2026-01-09T15:46:44.652Z" }, + { url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z" }, ] [[package]] @@ -1223,33 +1225,33 @@ wheels = [ [[package]] name = "protobuf" -version = "6.33.4" +version = "6.33.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/53/b8/cda15d9d46d03d4aa3a67cb6bffe05173440ccf86a9541afaf7ac59a1b6b/protobuf-6.33.4.tar.gz", hash = "sha256:dc2e61bca3b10470c1912d166fe0af67bfc20eb55971dcef8dfa48ce14f0ed91", size = 444346, upload-time = "2026-01-12T18:33:40.109Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/25/7c72c307aafc96fa87062aa6291d9f7c94836e43214d43722e86037aac02/protobuf-6.33.5.tar.gz", hash = "sha256:6ddcac2a081f8b7b9642c09406bc6a4290128fce5f471cddd165960bb9119e5c", size = 444465, upload-time = "2026-01-29T21:51:33.494Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e0/be/24ef9f3095bacdf95b458543334d0c4908ccdaee5130420bf064492c325f/protobuf-6.33.4-cp310-abi3-win32.whl", hash = "sha256:918966612c8232fc6c24c78e1cd89784307f5814ad7506c308ee3cf86662850d", size = 425612, upload-time = "2026-01-12T18:33:29.656Z" }, - { url = "https://files.pythonhosted.org/packages/31/ad/e5693e1974a28869e7cd244302911955c1cebc0161eb32dfa2b25b6e96f0/protobuf-6.33.4-cp310-abi3-win_amd64.whl", hash = "sha256:8f11ffae31ec67fc2554c2ef891dcb561dae9a2a3ed941f9e134c2db06657dbc", size = 436962, upload-time = "2026-01-12T18:33:31.345Z" }, - { url = "https://files.pythonhosted.org/packages/66/15/6ee23553b6bfd82670207ead921f4d8ef14c107e5e11443b04caeb5ab5ec/protobuf-6.33.4-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:2fe67f6c014c84f655ee06f6f66213f9254b3a8b6bda6cda0ccd4232c73c06f0", size = 427612, upload-time = "2026-01-12T18:33:32.646Z" }, - { url = "https://files.pythonhosted.org/packages/2b/48/d301907ce6d0db75f959ca74f44b475a9caa8fcba102d098d3c3dd0f2d3f/protobuf-6.33.4-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:757c978f82e74d75cba88eddec479df9b99a42b31193313b75e492c06a51764e", size = 324484, upload-time = "2026-01-12T18:33:33.789Z" }, - { url = "https://files.pythonhosted.org/packages/92/1c/e53078d3f7fe710572ab2dcffd993e1e3b438ae71cfc031b71bae44fcb2d/protobuf-6.33.4-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:c7c64f259c618f0bef7bee042075e390debbf9682334be2b67408ec7c1c09ee6", size = 339256, upload-time = "2026-01-12T18:33:35.231Z" }, - { url = "https://files.pythonhosted.org/packages/e8/8e/971c0edd084914f7ee7c23aa70ba89e8903918adca179319ee94403701d5/protobuf-6.33.4-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:3df850c2f8db9934de4cf8f9152f8dc2558f49f298f37f90c517e8e5c84c30e9", size = 323311, upload-time = "2026-01-12T18:33:36.305Z" }, - { url = "https://files.pythonhosted.org/packages/75/b1/1dc83c2c661b4c62d56cc081706ee33a4fc2835bd90f965baa2663ef7676/protobuf-6.33.4-py3-none-any.whl", hash = "sha256:1fe3730068fcf2e595816a6c34fe66eeedd37d51d0400b72fabc848811fdc1bc", size = 170532, upload-time = "2026-01-12T18:33:39.199Z" }, + { url = "https://files.pythonhosted.org/packages/b1/79/af92d0a8369732b027e6d6084251dd8e782c685c72da161bd4a2e00fbabb/protobuf-6.33.5-cp310-abi3-win32.whl", hash = "sha256:d71b040839446bac0f4d162e758bea99c8251161dae9d0983a3b88dee345153b", size = 425769, upload-time = "2026-01-29T21:51:21.751Z" }, + { url = "https://files.pythonhosted.org/packages/55/75/bb9bc917d10e9ee13dee8607eb9ab963b7cf8be607c46e7862c748aa2af7/protobuf-6.33.5-cp310-abi3-win_amd64.whl", hash = "sha256:3093804752167bcab3998bec9f1048baae6e29505adaf1afd14a37bddede533c", size = 437118, upload-time = "2026-01-29T21:51:24.022Z" }, + { url = "https://files.pythonhosted.org/packages/a2/6b/e48dfc1191bc5b52950246275bf4089773e91cb5ba3592621723cdddca62/protobuf-6.33.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:a5cb85982d95d906df1e2210e58f8e4f1e3cdc088e52c921a041f9c9a0386de5", size = 427766, upload-time = "2026-01-29T21:51:25.413Z" }, + { url = "https://files.pythonhosted.org/packages/4e/b1/c79468184310de09d75095ed1314b839eb2f72df71097db9d1404a1b2717/protobuf-6.33.5-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:9b71e0281f36f179d00cbcb119cb19dec4d14a81393e5ea220f64b286173e190", size = 324638, upload-time = "2026-01-29T21:51:26.423Z" }, + { url = "https://files.pythonhosted.org/packages/c5/f5/65d838092fd01c44d16037953fd4c2cc851e783de9b8f02b27ec4ffd906f/protobuf-6.33.5-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:8afa18e1d6d20af15b417e728e9f60f3aa108ee76f23c3b2c07a2c3b546d3afd", size = 339411, upload-time = "2026-01-29T21:51:27.446Z" }, + { url = "https://files.pythonhosted.org/packages/9b/53/a9443aa3ca9ba8724fdfa02dd1887c1bcd8e89556b715cfbacca6b63dbec/protobuf-6.33.5-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:cbf16ba3350fb7b889fca858fb215967792dc125b35c7976ca4818bee3521cf0", size = 323465, upload-time = "2026-01-29T21:51:28.925Z" }, + { url = "https://files.pythonhosted.org/packages/57/bf/2086963c69bdac3d7cff1cc7ff79b8ce5ea0bec6797a017e1be338a46248/protobuf-6.33.5-py3-none-any.whl", hash = "sha256:69915a973dd0f60f31a08b8318b73eab2bd6a392c79184b3612226b0a3f8ec02", size = 170687, upload-time = "2026-01-29T21:51:32.557Z" }, ] [[package]] name = "psutil" -version = "7.2.1" +version = "7.2.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/73/cb/09e5184fb5fc0358d110fc3ca7f6b1d033800734d34cac10f4136cfac10e/psutil-7.2.1.tar.gz", hash = "sha256:f7583aec590485b43ca601dd9cea0dcd65bd7bb21d30ef4ddbf4ea6b5ed1bdd3", size = 490253, upload-time = "2025-12-29T08:26:00.169Z" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c5/cf/5180eb8c8bdf6a503c6919f1da28328bd1e6b3b1b5b9d5b01ae64f019616/psutil-7.2.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b2e953fcfaedcfbc952b44744f22d16575d3aa78eb4f51ae74165b4e96e55f42", size = 128137, upload-time = "2025-12-29T08:26:27.759Z" }, - { url = "https://files.pythonhosted.org/packages/c5/2c/78e4a789306a92ade5000da4f5de3255202c534acdadc3aac7b5458fadef/psutil-7.2.1-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:05cc68dbb8c174828624062e73078e7e35406f4ca2d0866c272c2410d8ef06d1", size = 128947, upload-time = "2025-12-29T08:26:29.548Z" }, - { url = "https://files.pythonhosted.org/packages/29/f8/40e01c350ad9a2b3cb4e6adbcc8a83b17ee50dd5792102b6142385937db5/psutil-7.2.1-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e38404ca2bb30ed7267a46c02f06ff842e92da3bb8c5bfdadbd35a5722314d8", size = 154694, upload-time = "2025-12-29T08:26:32.147Z" }, - { url = "https://files.pythonhosted.org/packages/06/e4/b751cdf839c011a9714a783f120e6a86b7494eb70044d7d81a25a5cd295f/psutil-7.2.1-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab2b98c9fc19f13f59628d94df5cc4cc4844bc572467d113a8b517d634e362c6", size = 156136, upload-time = "2025-12-29T08:26:34.079Z" }, - { url = "https://files.pythonhosted.org/packages/44/ad/bbf6595a8134ee1e94a4487af3f132cef7fce43aef4a93b49912a48c3af7/psutil-7.2.1-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:f78baafb38436d5a128f837fab2d92c276dfb48af01a240b861ae02b2413ada8", size = 148108, upload-time = "2025-12-29T08:26:36.225Z" }, - { url = "https://files.pythonhosted.org/packages/1c/15/dd6fd869753ce82ff64dcbc18356093471a5a5adf4f77ed1f805d473d859/psutil-7.2.1-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:99a4cd17a5fdd1f3d014396502daa70b5ec21bf4ffe38393e152f8e449757d67", size = 147402, upload-time = "2025-12-29T08:26:39.21Z" }, - { url = "https://files.pythonhosted.org/packages/34/68/d9317542e3f2b180c4306e3f45d3c922d7e86d8ce39f941bb9e2e9d8599e/psutil-7.2.1-cp37-abi3-win_amd64.whl", hash = "sha256:b1b0671619343aa71c20ff9767eced0483e4fc9e1f489d50923738caf6a03c17", size = 136938, upload-time = "2025-12-29T08:26:41.036Z" }, - { url = "https://files.pythonhosted.org/packages/3e/73/2ce007f4198c80fcf2cb24c169884f833fe93fbc03d55d302627b094ee91/psutil-7.2.1-cp37-abi3-win_arm64.whl", hash = "sha256:0d67c1822c355aa6f7314d92018fb4268a76668a536f133599b91edd48759442", size = 133836, upload-time = "2025-12-29T08:26:43.086Z" }, + { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" }, + { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" }, + { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" }, + { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" }, + { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" }, + { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" }, + { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" }, + { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, ] [[package]] @@ -1358,11 +1360,11 @@ wheels = [ [[package]] name = "pyjwt" -version = "2.10.1" +version = "2.11.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/46/bd74733ff231675599650d3e47f361794b22ef3e3770998dda30d3b63726/pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", size = 87785, upload-time = "2024-11-28T03:43:29.933Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/5a/b46fa56bf322901eee5b0454a34343cdbdae202cd421775a8ee4e42fd519/pyjwt-2.11.0.tar.gz", hash = "sha256:35f95c1f0fbe5d5ba6e43f00271c275f7a1a4db1dab27bf708073b75318ea623", size = 98019, upload-time = "2026-01-30T19:59:55.694Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997, upload-time = "2024-11-28T03:43:27.893Z" }, + { url = "https://files.pythonhosted.org/packages/6f/01/c26ce75ba460d5cd503da9e13b21a33804d38c2165dec7b716d06b13010c/pyjwt-2.11.0-py3-none-any.whl", hash = "sha256:94a6bde30eb5c8e04fee991062b534071fd1439ef58d2adc9ccb823e7bcd0469", size = 28224, upload-time = "2026-01-30T19:59:54.539Z" }, ] [[package]] @@ -4067,28 +4069,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.14.14" +version = "0.15.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2e/06/f71e3a86b2df0dfa2d2f72195941cd09b44f87711cb7fa5193732cb9a5fc/ruff-0.14.14.tar.gz", hash = "sha256:2d0f819c9a90205f3a867dbbd0be083bee9912e170fd7d9704cc8ae45824896b", size = 4515732, upload-time = "2026-01-22T22:30:17.527Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c8/39/5cee96809fbca590abea6b46c6d1c586b49663d1d2830a751cc8fc42c666/ruff-0.15.0.tar.gz", hash = "sha256:6bdea47cdbea30d40f8f8d7d69c0854ba7c15420ec75a26f463290949d7f7e9a", size = 4524893, upload-time = "2026-02-03T17:53:35.357Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/89/20a12e97bc6b9f9f68343952da08a8099c57237aef953a56b82711d55edd/ruff-0.14.14-py3-none-linux_armv6l.whl", hash = "sha256:7cfe36b56e8489dee8fbc777c61959f60ec0f1f11817e8f2415f429552846aed", size = 10467650, upload-time = "2026-01-22T22:30:08.578Z" }, - { url = "https://files.pythonhosted.org/packages/a3/b1/c5de3fd2d5a831fcae21beda5e3589c0ba67eec8202e992388e4b17a6040/ruff-0.14.14-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6006a0082336e7920b9573ef8a7f52eec837add1265cc74e04ea8a4368cd704c", size = 10883245, upload-time = "2026-01-22T22:30:04.155Z" }, - { url = "https://files.pythonhosted.org/packages/b8/7c/3c1db59a10e7490f8f6f8559d1db8636cbb13dccebf18686f4e3c9d7c772/ruff-0.14.14-py3-none-macosx_11_0_arm64.whl", hash = "sha256:026c1d25996818f0bf498636686199d9bd0d9d6341c9c2c3b62e2a0198b758de", size = 10231273, upload-time = "2026-01-22T22:30:34.642Z" }, - { url = "https://files.pythonhosted.org/packages/a1/6e/5e0e0d9674be0f8581d1f5e0f0a04761203affce3232c1a1189d0e3b4dad/ruff-0.14.14-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f666445819d31210b71e0a6d1c01e24447a20b85458eea25a25fe8142210ae0e", size = 10585753, upload-time = "2026-01-22T22:30:31.781Z" }, - { url = "https://files.pythonhosted.org/packages/23/09/754ab09f46ff1884d422dc26d59ba18b4e5d355be147721bb2518aa2a014/ruff-0.14.14-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3c0f18b922c6d2ff9a5e6c3ee16259adc513ca775bcf82c67ebab7cbd9da5bc8", size = 10286052, upload-time = "2026-01-22T22:30:24.827Z" }, - { url = "https://files.pythonhosted.org/packages/c8/cc/e71f88dd2a12afb5f50733851729d6b571a7c3a35bfdb16c3035132675a0/ruff-0.14.14-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1629e67489c2dea43e8658c3dba659edbfd87361624b4040d1df04c9740ae906", size = 11043637, upload-time = "2026-01-22T22:30:13.239Z" }, - { url = "https://files.pythonhosted.org/packages/67/b2/397245026352494497dac935d7f00f1468c03a23a0c5db6ad8fc49ca3fb2/ruff-0.14.14-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:27493a2131ea0f899057d49d303e4292b2cae2bb57253c1ed1f256fbcd1da480", size = 12194761, upload-time = "2026-01-22T22:30:22.542Z" }, - { url = "https://files.pythonhosted.org/packages/5b/06/06ef271459f778323112c51b7587ce85230785cd64e91772034ddb88f200/ruff-0.14.14-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:01ff589aab3f5b539e35db38425da31a57521efd1e4ad1ae08fc34dbe30bd7df", size = 12005701, upload-time = "2026-01-22T22:30:20.499Z" }, - { url = "https://files.pythonhosted.org/packages/41/d6/99364514541cf811ccc5ac44362f88df66373e9fec1b9d1c4cc830593fe7/ruff-0.14.14-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1cc12d74eef0f29f51775f5b755913eb523546b88e2d733e1d701fe65144e89b", size = 11282455, upload-time = "2026-01-22T22:29:59.679Z" }, - { url = "https://files.pythonhosted.org/packages/ca/71/37daa46f89475f8582b7762ecd2722492df26421714a33e72ccc9a84d7a5/ruff-0.14.14-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb8481604b7a9e75eff53772496201690ce2687067e038b3cc31aaf16aa0b974", size = 11215882, upload-time = "2026-01-22T22:29:57.032Z" }, - { url = "https://files.pythonhosted.org/packages/2c/10/a31f86169ec91c0705e618443ee74ede0bdd94da0a57b28e72db68b2dbac/ruff-0.14.14-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:14649acb1cf7b5d2d283ebd2f58d56b75836ed8c6f329664fa91cdea19e76e66", size = 11180549, upload-time = "2026-01-22T22:30:27.175Z" }, - { url = "https://files.pythonhosted.org/packages/fd/1e/c723f20536b5163adf79bdd10c5f093414293cdf567eed9bdb7b83940f3f/ruff-0.14.14-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e8058d2145566510790eab4e2fad186002e288dec5e0d343a92fe7b0bc1b3e13", size = 10543416, upload-time = "2026-01-22T22:30:01.964Z" }, - { url = "https://files.pythonhosted.org/packages/3e/34/8a84cea7e42c2d94ba5bde1d7a4fae164d6318f13f933d92da6d7c2041ff/ruff-0.14.14-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e651e977a79e4c758eb807f0481d673a67ffe53cfa92209781dfa3a996cf8412", size = 10285491, upload-time = "2026-01-22T22:30:29.51Z" }, - { url = "https://files.pythonhosted.org/packages/55/ef/b7c5ea0be82518906c978e365e56a77f8de7678c8bb6651ccfbdc178c29f/ruff-0.14.14-py3-none-musllinux_1_2_i686.whl", hash = "sha256:cc8b22da8d9d6fdd844a68ae937e2a0adf9b16514e9a97cc60355e2d4b219fc3", size = 10733525, upload-time = "2026-01-22T22:30:06.499Z" }, - { url = "https://files.pythonhosted.org/packages/6a/5b/aaf1dfbcc53a2811f6cc0a1759de24e4b03e02ba8762daabd9b6bd8c59e3/ruff-0.14.14-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:16bc890fb4cc9781bb05beb5ab4cd51be9e7cb376bf1dd3580512b24eb3fda2b", size = 11315626, upload-time = "2026-01-22T22:30:36.848Z" }, - { url = "https://files.pythonhosted.org/packages/2c/aa/9f89c719c467dfaf8ad799b9bae0df494513fb21d31a6059cb5870e57e74/ruff-0.14.14-py3-none-win32.whl", hash = "sha256:b530c191970b143375b6a68e6f743800b2b786bbcf03a7965b06c4bf04568167", size = 10502442, upload-time = "2026-01-22T22:30:38.93Z" }, - { url = "https://files.pythonhosted.org/packages/87/44/90fa543014c45560cae1fffc63ea059fb3575ee6e1cb654562197e5d16fb/ruff-0.14.14-py3-none-win_amd64.whl", hash = "sha256:3dde1435e6b6fe5b66506c1dff67a421d0b7f6488d466f651c07f4cab3bf20fd", size = 11630486, upload-time = "2026-01-22T22:30:10.852Z" }, - { url = "https://files.pythonhosted.org/packages/9e/6a/40fee331a52339926a92e17ae748827270b288a35ef4a15c9c8f2ec54715/ruff-0.14.14-py3-none-win_arm64.whl", hash = "sha256:56e6981a98b13a32236a72a8da421d7839221fa308b223b9283312312e5ac76c", size = 10920448, upload-time = "2026-01-22T22:30:15.417Z" }, + { url = "https://files.pythonhosted.org/packages/bc/88/3fd1b0aa4b6330d6aaa63a285bc96c9f71970351579152d231ed90914586/ruff-0.15.0-py3-none-linux_armv6l.whl", hash = "sha256:aac4ebaa612a82b23d45964586f24ae9bc23ca101919f5590bdb368d74ad5455", size = 10354332, upload-time = "2026-02-03T17:52:54.892Z" }, + { url = "https://files.pythonhosted.org/packages/72/f6/62e173fbb7eb75cc29fe2576a1e20f0a46f671a2587b5f604bfb0eaf5f6f/ruff-0.15.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:dcd4be7cc75cfbbca24a98d04d0b9b36a270d0833241f776b788d59f4142b14d", size = 10767189, upload-time = "2026-02-03T17:53:19.778Z" }, + { url = "https://files.pythonhosted.org/packages/99/e4/968ae17b676d1d2ff101d56dc69cf333e3a4c985e1ec23803df84fc7bf9e/ruff-0.15.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d747e3319b2bce179c7c1eaad3d884dc0a199b5f4d5187620530adf9105268ce", size = 10075384, upload-time = "2026-02-03T17:53:29.241Z" }, + { url = "https://files.pythonhosted.org/packages/a2/bf/9843c6044ab9e20af879c751487e61333ca79a2c8c3058b15722386b8cae/ruff-0.15.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:650bd9c56ae03102c51a5e4b554d74d825ff3abe4db22b90fd32d816c2e90621", size = 10481363, upload-time = "2026-02-03T17:52:43.332Z" }, + { url = "https://files.pythonhosted.org/packages/55/d9/4ada5ccf4cd1f532db1c8d44b6f664f2208d3d93acbeec18f82315e15193/ruff-0.15.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a6664b7eac559e3048223a2da77769c2f92b43a6dfd4720cef42654299a599c9", size = 10187736, upload-time = "2026-02-03T17:53:00.522Z" }, + { url = "https://files.pythonhosted.org/packages/86/e2/f25eaecd446af7bb132af0a1d5b135a62971a41f5366ff41d06d25e77a91/ruff-0.15.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f811f97b0f092b35320d1556f3353bf238763420ade5d9e62ebd2b73f2ff179", size = 10968415, upload-time = "2026-02-03T17:53:15.705Z" }, + { url = "https://files.pythonhosted.org/packages/e7/dc/f06a8558d06333bf79b497d29a50c3a673d9251214e0d7ec78f90b30aa79/ruff-0.15.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:761ec0a66680fab6454236635a39abaf14198818c8cdf691e036f4bc0f406b2d", size = 11809643, upload-time = "2026-02-03T17:53:23.031Z" }, + { url = "https://files.pythonhosted.org/packages/dd/45/0ece8db2c474ad7df13af3a6d50f76e22a09d078af63078f005057ca59eb/ruff-0.15.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:940f11c2604d317e797b289f4f9f3fa5555ffe4fb574b55ed006c3d9b6f0eb78", size = 11234787, upload-time = "2026-02-03T17:52:46.432Z" }, + { url = "https://files.pythonhosted.org/packages/8a/d9/0e3a81467a120fd265658d127db648e4d3acfe3e4f6f5d4ea79fac47e587/ruff-0.15.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bcbca3d40558789126da91d7ef9a7c87772ee107033db7191edefa34e2c7f1b4", size = 11112797, upload-time = "2026-02-03T17:52:49.274Z" }, + { url = "https://files.pythonhosted.org/packages/b2/cb/8c0b3b0c692683f8ff31351dfb6241047fa873a4481a76df4335a8bff716/ruff-0.15.0-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:9a121a96db1d75fa3eb39c4539e607f628920dd72ff1f7c5ee4f1b768ac62d6e", size = 11033133, upload-time = "2026-02-03T17:53:33.105Z" }, + { url = "https://files.pythonhosted.org/packages/f8/5e/23b87370cf0f9081a8c89a753e69a4e8778805b8802ccfe175cc410e50b9/ruff-0.15.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5298d518e493061f2eabd4abd067c7e4fb89e2f63291c94332e35631c07c3662", size = 10442646, upload-time = "2026-02-03T17:53:06.278Z" }, + { url = "https://files.pythonhosted.org/packages/e1/9a/3c94de5ce642830167e6d00b5c75aacd73e6347b4c7fc6828699b150a5ee/ruff-0.15.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:afb6e603d6375ff0d6b0cee563fa21ab570fd15e65c852cb24922cef25050cf1", size = 10195750, upload-time = "2026-02-03T17:53:26.084Z" }, + { url = "https://files.pythonhosted.org/packages/30/15/e396325080d600b436acc970848d69df9c13977942fb62bb8722d729bee8/ruff-0.15.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:77e515f6b15f828b94dc17d2b4ace334c9ddb7d9468c54b2f9ed2b9c1593ef16", size = 10676120, upload-time = "2026-02-03T17:53:09.363Z" }, + { url = "https://files.pythonhosted.org/packages/8d/c9/229a23d52a2983de1ad0fb0ee37d36e0257e6f28bfd6b498ee2c76361874/ruff-0.15.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:6f6e80850a01eb13b3e42ee0ebdf6e4497151b48c35051aab51c101266d187a3", size = 11201636, upload-time = "2026-02-03T17:52:57.281Z" }, + { url = "https://files.pythonhosted.org/packages/6f/b0/69adf22f4e24f3677208adb715c578266842e6e6a3cc77483f48dd999ede/ruff-0.15.0-py3-none-win32.whl", hash = "sha256:238a717ef803e501b6d51e0bdd0d2c6e8513fe9eec14002445134d3907cd46c3", size = 10465945, upload-time = "2026-02-03T17:53:12.591Z" }, + { url = "https://files.pythonhosted.org/packages/51/ad/f813b6e2c97e9b4598be25e94a9147b9af7e60523b0cb5d94d307c15229d/ruff-0.15.0-py3-none-win_amd64.whl", hash = "sha256:dd5e4d3301dc01de614da3cdffc33d4b1b96fb89e45721f1598e5532ccf78b18", size = 11564657, upload-time = "2026-02-03T17:52:51.893Z" }, + { url = "https://files.pythonhosted.org/packages/f6/b0/2d823f6e77ebe560f4e397d078487e8d52c1516b331e3521bc75db4272ca/ruff-0.15.0-py3-none-win_arm64.whl", hash = "sha256:c480d632cc0ca3f0727acac8b7d053542d9e114a462a145d0b00e7cd658c515a", size = 10865753, upload-time = "2026-02-03T17:53:03.014Z" }, ] [[package]] @@ -4102,15 +4103,15 @@ wheels = [ [[package]] name = "sentry-sdk" -version = "2.50.0" +version = "2.52.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/15/8a/3c4f53d32c21012e9870913544e56bfa9e931aede080779a0f177513f534/sentry_sdk-2.50.0.tar.gz", hash = "sha256:873437a989ee1b8b25579847bae8384515bf18cfed231b06c591b735c1781fe3", size = 401233, upload-time = "2026-01-20T12:53:16.244Z" } +sdist = { url = "https://files.pythonhosted.org/packages/59/eb/1b497650eb564701f9a7b8a95c51b2abe9347ed2c0b290ba78f027ebe4ea/sentry_sdk-2.52.0.tar.gz", hash = "sha256:fa0bec872cfec0302970b2996825723d67390cdd5f0229fb9efed93bd5384899", size = 410273, upload-time = "2026-02-04T15:03:54.706Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4e/5b/cbc2bb9569f03c8e15d928357e7e6179e5cfab45544a3bbac8aec4caf9be/sentry_sdk-2.50.0-py2.py3-none-any.whl", hash = "sha256:0ef0ed7168657ceb5a0be081f4102d92042a125462d1d1a29277992e344e749e", size = 424961, upload-time = "2026-01-20T12:53:14.826Z" }, + { url = "https://files.pythonhosted.org/packages/ca/63/2c6daf59d86b1c30600bff679d039f57fd1932af82c43c0bde1cbc55e8d4/sentry_sdk-2.52.0-py2.py3-none-any.whl", hash = "sha256:931c8f86169fc6f2752cb5c4e6480f0d516112e78750c312e081ababecbaf2ed", size = 435547, upload-time = "2026-02-04T15:03:51.567Z" }, ] [[package]] @@ -4133,11 +4134,11 @@ wheels = [ [[package]] name = "setuptools" -version = "80.10.2" +version = "82.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/76/95/faf61eb8363f26aa7e1d762267a8d602a1b26d4f3a1e758e92cb3cb8b054/setuptools-80.10.2.tar.gz", hash = "sha256:8b0e9d10c784bf7d262c4e5ec5d4ec94127ce206e8738f29a437945fbc219b70", size = 1200343, upload-time = "2026-01-25T22:38:17.252Z" } +sdist = { url = "https://files.pythonhosted.org/packages/82/f3/748f4d6f65d1756b9ae577f329c951cda23fb900e4de9f70900ced962085/setuptools-82.0.0.tar.gz", hash = "sha256:22e0a2d69474c6ae4feb01951cb69d515ed23728cf96d05513d36e42b62b37cb", size = 1144893, upload-time = "2026-02-08T15:08:40.206Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/94/b8/f1f62a5e3c0ad2ff1d189590bfa4c46b4f3b6e49cef6f26c6ee4e575394d/setuptools-80.10.2-py3-none-any.whl", hash = "sha256:95b30ddfb717250edb492926c92b5221f7ef3fbcc2b07579bcd4a27da21d0173", size = 1064234, upload-time = "2026-01-25T22:38:15.216Z" }, + { url = "https://files.pythonhosted.org/packages/e1/c6/76dc613121b793286a3f91621d7b75a2b493e0390ddca50f11993eadf192/setuptools-82.0.0-py3-none-any.whl", hash = "sha256:70b18734b607bd1da571d097d236cfcfacaf01de45717d59e6e04b96877532e0", size = 1003468, upload-time = "2026-02-08T15:08:38.723Z" }, ] [[package]] @@ -4213,38 +4214,38 @@ wheels = [ [[package]] name = "tqdm" -version = "4.67.1" +version = "4.67.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737, upload-time = "2024-11-24T20:12:22.481Z" } +sdist = { url = "https://files.pythonhosted.org/packages/09/a9/6ba95a270c6f1fbcd8dac228323f2777d886cb206987444e4bce66338dd4/tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb", size = 169598, upload-time = "2026-02-03T17:35:53.048Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" }, + { url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374, upload-time = "2026-02-03T17:35:50.982Z" }, ] [[package]] name = "ty" -version = "0.0.13" +version = "0.0.15" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5a/dc/b607f00916f5a7c52860b84a66dc17bc6988e8445e96b1d6e175a3837397/ty-0.0.13.tar.gz", hash = "sha256:7a1d135a400ca076407ea30012d1f75419634160ed3b9cad96607bf2956b23b3", size = 4999183, upload-time = "2026-01-21T13:21:16.133Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/25/257602d316b9333089b688a7a11b33ebc660b74e8dacf400dc3dfdea1594/ty-0.0.15.tar.gz", hash = "sha256:4f9a5b8df208c62dba56e91b93bed8b5bb714839691b8cff16d12c983bfa1174", size = 5101936, upload-time = "2026-02-05T01:06:34.922Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1a/df/3632f1918f4c0a33184f107efc5d436ab6da147fd3d3b94b3af6461efbf4/ty-0.0.13-py3-none-linux_armv6l.whl", hash = "sha256:1b2b8e02697c3a94c722957d712a0615bcc317c9b9497be116ef746615d892f2", size = 9993501, upload-time = "2026-01-21T13:21:26.628Z" }, - { url = "https://files.pythonhosted.org/packages/92/87/6a473ced5ac280c6ce5b1627c71a8a695c64481b99aabc798718376a441e/ty-0.0.13-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:f15cdb8e233e2b5adfce673bb21f4c5e8eaf3334842f7eea3c70ac6fda8c1de5", size = 9860986, upload-time = "2026-01-21T13:21:24.425Z" }, - { url = "https://files.pythonhosted.org/packages/5d/9b/d89ae375cf0a7cd9360e1164ce017f8c753759be63b6a11ed4c944abe8c6/ty-0.0.13-py3-none-macosx_11_0_arm64.whl", hash = "sha256:0819e89ac9f0d8af7a062837ce197f0461fee2fc14fd07e2c368780d3a397b73", size = 9350748, upload-time = "2026-01-21T13:21:28.502Z" }, - { url = "https://files.pythonhosted.org/packages/a8/a6/9ad58518056fab344b20c0bb2c1911936ebe195318e8acc3bc45ac1c6b6b/ty-0.0.13-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1de79f481084b7cc7a202ba0d7a75e10970d10ffa4f025b23f2e6b7324b74886", size = 9849884, upload-time = "2026-01-21T13:21:21.886Z" }, - { url = "https://files.pythonhosted.org/packages/b1/c3/8add69095fa179f523d9e9afcc15a00818af0a37f2b237a9b59bc0046c34/ty-0.0.13-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4fb2154cff7c6e95d46bfaba283c60642616f20d73e5f96d0c89c269f3e1bcec", size = 9822975, upload-time = "2026-01-21T13:21:14.292Z" }, - { url = "https://files.pythonhosted.org/packages/a4/05/4c0927c68a0a6d43fb02f3f0b6c19c64e3461dc8ed6c404dde0efb8058f7/ty-0.0.13-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00be58d89337c27968a20d58ca553458608c5b634170e2bec82824c2e4cf4d96", size = 10294045, upload-time = "2026-01-21T13:21:30.505Z" }, - { url = "https://files.pythonhosted.org/packages/b4/86/6dc190838aba967557fe0bfd494c595d00b5081315a98aaf60c0e632aaeb/ty-0.0.13-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:72435eade1fa58c6218abb4340f43a6c3ff856ae2dc5722a247d3a6dd32e9737", size = 10916460, upload-time = "2026-01-21T13:21:07.788Z" }, - { url = "https://files.pythonhosted.org/packages/04/40/9ead96b7c122e1109dfcd11671184c3506996bf6a649306ec427e81d9544/ty-0.0.13-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:77a548742ee8f621d718159e7027c3b555051d096a49bb580249a6c5fc86c271", size = 10597154, upload-time = "2026-01-21T13:21:18.064Z" }, - { url = "https://files.pythonhosted.org/packages/aa/7d/e832a2c081d2be845dc6972d0c7998914d168ccbc0b9c86794419ab7376e/ty-0.0.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da067c57c289b7cf914669704b552b6207c2cc7f50da4118c3e12388642e6b3f", size = 10410710, upload-time = "2026-01-21T13:21:12.388Z" }, - { url = "https://files.pythonhosted.org/packages/31/e3/898be3a96237a32f05c4c29b43594dc3b46e0eedfe8243058e46153b324f/ty-0.0.13-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:d1b50a01fffa140417fca5a24b658fbe0734074a095d5b6f0552484724474343", size = 9826299, upload-time = "2026-01-21T13:21:00.845Z" }, - { url = "https://files.pythonhosted.org/packages/bb/eb/db2d852ce0ed742505ff18ee10d7d252f3acfd6fc60eca7e9c7a0288a6d8/ty-0.0.13-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0f33c46f52e5e9378378eca0d8059f026f3c8073ace02f7f2e8d079ddfe5207e", size = 9831610, upload-time = "2026-01-21T13:21:05.842Z" }, - { url = "https://files.pythonhosted.org/packages/9e/61/149f59c8abaddcbcbb0bd13b89c7741ae1c637823c5cf92ed2c644fcadef/ty-0.0.13-py3-none-musllinux_1_2_i686.whl", hash = "sha256:168eda24d9a0b202cf3758c2962cc295878842042b7eca9ed2965259f59ce9f2", size = 9978885, upload-time = "2026-01-21T13:21:10.306Z" }, - { url = "https://files.pythonhosted.org/packages/a0/cd/026d4e4af60a80918a8d73d2c42b8262dd43ab2fa7b28d9743004cb88d57/ty-0.0.13-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:d4917678b95dc8cb399cc459fab568ba8d5f0f33b7a94bf840d9733043c43f29", size = 10506453, upload-time = "2026-01-21T13:20:56.633Z" }, - { url = "https://files.pythonhosted.org/packages/63/06/8932833a4eca2df49c997a29afb26721612de8078ae79074c8fe87e17516/ty-0.0.13-py3-none-win32.whl", hash = "sha256:c1f2ec40daa405508b053e5b8e440fbae5fdb85c69c9ab0ee078f8bc00eeec3d", size = 9433482, upload-time = "2026-01-21T13:20:58.717Z" }, - { url = "https://files.pythonhosted.org/packages/aa/fd/e8d972d1a69df25c2cecb20ea50e49ad5f27a06f55f1f5f399a563e71645/ty-0.0.13-py3-none-win_amd64.whl", hash = "sha256:8b7b1ab9f187affbceff89d51076038363b14113be29bda2ddfa17116de1d476", size = 10319156, upload-time = "2026-01-21T13:21:03.266Z" }, - { url = "https://files.pythonhosted.org/packages/2d/c2/05fdd64ac003a560d4fbd1faa7d9a31d75df8f901675e5bed1ee2ceeff87/ty-0.0.13-py3-none-win_arm64.whl", hash = "sha256:1c9630333497c77bb9bcabba42971b96ee1f36c601dd3dcac66b4134f9fa38f0", size = 9808316, upload-time = "2026-01-21T13:20:54.053Z" }, + { url = "https://files.pythonhosted.org/packages/ce/c5/35626e732b79bf0e6213de9f79aff59b5f247c0a1e3ce0d93e675ab9b728/ty-0.0.15-py3-none-linux_armv6l.whl", hash = "sha256:68e092458516c61512dac541cde0a5e4e5842df00b4e81881ead8f745ddec794", size = 10138374, upload-time = "2026-02-05T01:07:03.804Z" }, + { url = "https://files.pythonhosted.org/packages/d5/8a/48fd81664604848f79d03879b3ca3633762d457a069b07e09fb1b87edd6e/ty-0.0.15-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:79f2e75289eae3cece94c51118b730211af4ba5762906f52a878041b67e54959", size = 9947858, upload-time = "2026-02-05T01:06:47.453Z" }, + { url = "https://files.pythonhosted.org/packages/b6/85/c1ac8e97bcd930946f4c94db85b675561d590b4e72703bf3733419fc3973/ty-0.0.15-py3-none-macosx_11_0_arm64.whl", hash = "sha256:112a7b26e63e48cc72c8c5b03227d1db280cfa57a45f2df0e264c3a016aa8c3c", size = 9443220, upload-time = "2026-02-05T01:06:44.98Z" }, + { url = "https://files.pythonhosted.org/packages/3c/d9/244bc02599d950f7a4298fbc0c1b25cc808646b9577bdf7a83470b2d1cec/ty-0.0.15-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71f62a2644972975a657d9dc867bf901235cde51e8d24c20311067e7afd44a56", size = 9949976, upload-time = "2026-02-05T01:07:01.515Z" }, + { url = "https://files.pythonhosted.org/packages/7e/ab/3a0daad66798c91a33867a3ececf17d314ac65d4ae2bbbd28cbfde94da63/ty-0.0.15-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9e48b42be2d257317c85b78559233273b655dd636fc61e7e1d69abd90fd3cba4", size = 9965918, upload-time = "2026-02-05T01:06:54.283Z" }, + { url = "https://files.pythonhosted.org/packages/39/4e/e62b01338f653059a7c0cd09d1a326e9a9eedc351a0f0de9db0601658c3d/ty-0.0.15-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27dd5b52a421e6871c5bfe9841160331b60866ed2040250cb161886478ab3e4f", size = 10424943, upload-time = "2026-02-05T01:07:08.777Z" }, + { url = "https://files.pythonhosted.org/packages/65/b5/7aa06655ce69c0d4f3e845d2d85e79c12994b6d84c71699cfb437e0bc8cf/ty-0.0.15-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76b85c9ec2219e11c358a7db8e21b7e5c6674a1fb9b6f633836949de98d12286", size = 10964692, upload-time = "2026-02-05T01:06:37.103Z" }, + { url = "https://files.pythonhosted.org/packages/13/04/36fdfe1f3c908b471e246e37ce3d011175584c26d3853e6c5d9a0364564c/ty-0.0.15-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9e8204c61d8ede4f21f2975dce74efdb80fafb2fae1915c666cceb33ea3c90b", size = 10692225, upload-time = "2026-02-05T01:06:49.714Z" }, + { url = "https://files.pythonhosted.org/packages/13/41/5bf882649bd8b64ded5fbce7fb8d77fb3b868de1a3b1a6c4796402b47308/ty-0.0.15-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af87c3be7c944bb4d6609d6c63e4594944b0028c7bd490a525a82b88fe010d6d", size = 10516776, upload-time = "2026-02-05T01:06:52.047Z" }, + { url = "https://files.pythonhosted.org/packages/56/75/66852d7e004f859839c17ffe1d16513c1e7cc04bcc810edb80ca022a9124/ty-0.0.15-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:50dccf7398505e5966847d366c9e4c650b8c225411c2a68c32040a63b9521eea", size = 9928828, upload-time = "2026-02-05T01:06:56.647Z" }, + { url = "https://files.pythonhosted.org/packages/65/72/96bc16c7b337a3ef358fd227b3c8ef0c77405f3bfbbfb59ee5915f0d9d71/ty-0.0.15-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:bd797b8f231a4f4715110259ad1ad5340a87b802307f3e06d92bfb37b858a8f3", size = 9978960, upload-time = "2026-02-05T01:06:29.567Z" }, + { url = "https://files.pythonhosted.org/packages/a0/18/d2e316a35b626de2227f832cd36d21205e4f5d96fd036a8af84c72ecec1b/ty-0.0.15-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9deb7f20e18b25440a9aa4884f934ba5628ef456dbde91819d5af1a73da48af3", size = 10135903, upload-time = "2026-02-05T01:06:59.256Z" }, + { url = "https://files.pythonhosted.org/packages/02/d3/b617a79c9dad10c888d7c15cd78859e0160b8772273637b9c4241a049491/ty-0.0.15-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:7b31b3de031255b90a5f4d9cb3d050feae246067c87130e5a6861a8061c71754", size = 10615879, upload-time = "2026-02-05T01:07:06.661Z" }, + { url = "https://files.pythonhosted.org/packages/fb/b0/2652a73c71c77296a6343217063f05745da60c67b7e8a8e25f2064167fce/ty-0.0.15-py3-none-win32.whl", hash = "sha256:9362c528ceb62c89d65c216336d28d500bc9f4c10418413f63ebc16886e16cc1", size = 9578058, upload-time = "2026-02-05T01:06:42.928Z" }, + { url = "https://files.pythonhosted.org/packages/84/6e/08a4aedebd2a6ce2784b5bc3760e43d1861f1a184734a78215c2d397c1df/ty-0.0.15-py3-none-win_amd64.whl", hash = "sha256:4db040695ae67c5524f59cb8179a8fa277112e69042d7dfdac862caa7e3b0d9c", size = 10457112, upload-time = "2026-02-05T01:06:39.885Z" }, + { url = "https://files.pythonhosted.org/packages/b3/be/1991f2bc12847ae2d4f1e3ac5dcff8bb7bc1261390645c0755bb55616355/ty-0.0.15-py3-none-win_arm64.whl", hash = "sha256:e5a98d4119e77d6136461e16ae505f8f8069002874ab073de03fbcb1a5e8bf25", size = 9937490, upload-time = "2026-02-05T01:06:32.388Z" }, ] [[package]] From f1785c245ade96a1dff1a4d663f526d8de9df280 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 10 Feb 2026 20:53:02 -0800 Subject: [PATCH 892/910] remove pytest-repeat (#37156) --- pyproject.toml | 1 - uv.lock | 14 -------------- 2 files changed, 15 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b1295ef81..f959c613c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -89,7 +89,6 @@ testing = [ "pytest-timeout", "pytest-asyncio", "pytest-mock", - "pytest-repeat", "ruff", "codespell", "pre-commit-hooks", diff --git a/uv.lock b/uv.lock index 816733d12..8909934e9 100644 --- a/uv.lock +++ b/uv.lock @@ -1001,7 +1001,6 @@ testing = [ { name = "pytest-asyncio" }, { name = "pytest-cpp" }, { name = "pytest-mock" }, - { name = "pytest-repeat" }, { name = "pytest-subtests" }, { name = "pytest-timeout" }, { name = "pytest-xdist" }, @@ -1053,7 +1052,6 @@ requires-dist = [ { name = "pytest-asyncio", marker = "extra == 'testing'" }, { name = "pytest-cpp", marker = "extra == 'testing'" }, { name = "pytest-mock", marker = "extra == 'testing'" }, - { name = "pytest-repeat", marker = "extra == 'testing'" }, { name = "pytest-subtests", marker = "extra == 'testing'" }, { name = "pytest-timeout", marker = "extra == 'testing'" }, { name = "pytest-xdist", marker = "extra == 'testing'", git = "https://github.com/sshane/pytest-xdist?rev=2b4372bd62699fb412c4fe2f95bf9f01bd2018da" }, @@ -3829,18 +3827,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5a/cc/06253936f4a7fa2e0f48dfe6d851d9c56df896a9ab09ac019d70b760619c/pytest_mock-3.15.1-py3-none-any.whl", hash = "sha256:0a25e2eb88fe5168d535041d09a4529a188176ae608a6d249ee65abc0949630d", size = 10095, upload-time = "2025-09-16T16:37:25.734Z" }, ] -[[package]] -name = "pytest-repeat" -version = "0.9.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pytest" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/80/d4/69e9dbb9b8266df0b157c72be32083403c412990af15c7c15f7a3fd1b142/pytest_repeat-0.9.4.tar.gz", hash = "sha256:d92ac14dfaa6ffcfe6917e5d16f0c9bc82380c135b03c2a5f412d2637f224485", size = 6488, upload-time = "2025-04-07T14:59:53.077Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/73/d4/8b706b81b07b43081bd68a2c0359fe895b74bf664b20aca8005d2bb3be71/pytest_repeat-0.9.4-py3-none-any.whl", hash = "sha256:c1738b4e412a6f3b3b9e0b8b29fcd7a423e50f87381ad9307ef6f5a8601139f3", size = 4180, upload-time = "2025-04-07T14:59:51.492Z" }, -] - [[package]] name = "pytest-subtests" version = "0.15.0" From f9c57ff285a51439d9d00106dd4ba7dbb35669b1 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 11 Feb 2026 00:20:23 -0500 Subject: [PATCH 893/910] Revert "`CI`: enable `macos` tests (#37005)" This reverts commit c179a3ccb7a5e3a6ff550feb04e97255f40256f2. --- .github/workflows/tests.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 18f01715d..62a30bdde 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -107,6 +107,7 @@ jobs: build_mac: name: build macOS + if: false # tmp disable due to brew install not working runs-on: ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-macos-8x14' || 'macos-latest' }} steps: - uses: actions/checkout@v6 From dc5dfe7f3ba353a084b801cb04ff00374e00552a Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 11 Feb 2026 00:27:57 -0500 Subject: [PATCH 894/910] maneuversd: keep services happy (#1685) --- tools/longitudinal_maneuvers/maneuversd.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tools/longitudinal_maneuvers/maneuversd.py b/tools/longitudinal_maneuvers/maneuversd.py index c17ae2375..9044afaa8 100755 --- a/tools/longitudinal_maneuvers/maneuversd.py +++ b/tools/longitudinal_maneuvers/maneuversd.py @@ -132,7 +132,7 @@ def main(): CP = messaging.log_from_bytes(params.get("CarParams", block=True), car.CarParams) sm = messaging.SubMaster(['carState', 'carControl', 'controlsState', 'selfdriveState', 'modelV2'], poll='modelV2') - pm = messaging.PubMaster(['longitudinalPlan', 'driverAssistance', 'alertDebug']) + pm = messaging.PubMaster(['longitudinalPlan', 'longitudinalPlanSP', 'driverAssistance', 'alertDebug']) maneuvers = iter(MANEUVERS) maneuver = None @@ -177,6 +177,10 @@ def main(): pm.send('longitudinalPlan', plan_send) + plan_sp_send = messaging.new_message('longitudinalPlanSP') + plan_sp_send.valid = True + pm.send('longitudinalPlanSP', plan_sp_send) + assistance_send = messaging.new_message('driverAssistance') assistance_send.valid = True pm.send('driverAssistance', assistance_send) From fcd5897650610317051229ed043b5ece1d73cf90 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 10 Feb 2026 21:29:14 -0800 Subject: [PATCH 895/910] BigButton: push up all content when pressed (#37157) clean implementation --- selfdrive/ui/mici/widgets/button.py | 63 ++++++++++++++++------------- 1 file changed, 35 insertions(+), 28 deletions(-) diff --git a/selfdrive/ui/mici/widgets/button.py b/selfdrive/ui/mici/widgets/button.py index 56dd3f7a2..118d33496 100644 --- a/selfdrive/ui/mici/widgets/button.py +++ b/selfdrive/ui/mici/widgets/button.py @@ -52,6 +52,12 @@ class BigCircleButton(Widget): def set_enable_pressed_state(self, pressed: bool): self._press_state_enabled = pressed + def _draw_content(self, btn_y: float): + # draw icon + icon_color = rl.WHITE if self.enabled else rl.Color(255, 255, 255, int(255 * 0.35)) + rl.draw_texture(self._txt_icon, int(self._rect.x + (self._rect.width - self._txt_icon.width) / 2 + self._icon_offset[0]), + int(btn_y + (self._rect.height - self._txt_icon.height) / 2 + self._icon_offset[1]), icon_color) + def _render(self, _): # draw background txt_bg = self._txt_btn_bg if not self._red else self._txt_btn_red_bg @@ -65,10 +71,7 @@ class BigCircleButton(Widget): btn_y = self._rect.y + (self._rect.height * (1 - scale)) / 2 rl.draw_texture_ex(txt_bg, (btn_x, btn_y), 0, scale, rl.WHITE) - # draw icon - icon_color = rl.WHITE if self.enabled else rl.Color(255, 255, 255, int(255 * 0.35)) - rl.draw_texture(self._txt_icon, int(self._rect.x + (self._rect.width - self._txt_icon.width) / 2 + self._icon_offset[0]), - int(self._rect.y + (self._rect.height - self._txt_icon.height) / 2 + self._icon_offset[1]), icon_color) + self._draw_content(btn_y) class BigCircleToggle(BigCircleButton): @@ -93,13 +96,13 @@ class BigCircleToggle(BigCircleButton): if self._toggle_callback: self._toggle_callback(self._checked) - def _render(self, _): - super()._render(_) + def _draw_content(self, btn_y: float): + super()._draw_content(btn_y) # draw status icon rl.draw_texture(self._txt_toggle_enabled if self._checked else self._txt_toggle_disabled, int(self._rect.x + (self._rect.width - self._txt_toggle_enabled.width) / 2), - int(self._rect.y + 5), rl.WHITE) + int(btn_y + 5), rl.WHITE) class BigButton(Widget): @@ -176,19 +179,7 @@ class BigButton(Widget): def get_text(self): return self.text - def _render(self, _): - # draw _txt_default_bg - txt_bg = self._txt_default_bg - if not self.enabled: - txt_bg = self._txt_disabled_bg - elif self.is_pressed: - txt_bg = self._txt_hover_bg - - scale = self._scale_filter.update(PRESSED_SCALE if self.is_pressed else 1.0) - btn_x = self._rect.x + (self._rect.width * (1 - scale)) / 2 - btn_y = self._rect.y + (self._rect.height * (1 - scale)) / 2 - rl.draw_texture_ex(txt_bg, (btn_x, btn_y), 0, scale, rl.WHITE) - + def _draw_content(self, btn_y: float): # LABEL ------------------------------------------------------------------ label_x = self._rect.x + LABEL_HORIZONTAL_PADDING @@ -210,14 +201,29 @@ class BigButton(Widget): if self._rotate_icon_t is not None: rotation = (rl.get_time() - self._rotate_icon_t) * 180 - # drop top right with 30px padding + # draw top right with 30px padding x = self._rect.x + self._rect.width - 30 - self._txt_icon.width / 2 - y = self._rect.y + 30 + self._txt_icon.height / 2 + y = btn_y + 30 + self._txt_icon.height / 2 source_rec = rl.Rectangle(0, 0, self._txt_icon.width, self._txt_icon.height) dest_rec = rl.Rectangle(int(x), int(y), self._txt_icon.width, self._txt_icon.height) origin = rl.Vector2(self._txt_icon.width / 2, self._txt_icon.height / 2) rl.draw_texture_pro(self._txt_icon, source_rec, dest_rec, origin, rotation, rl.WHITE) + def _render(self, _): + # draw _txt_default_bg + txt_bg = self._txt_default_bg + if not self.enabled: + txt_bg = self._txt_disabled_bg + elif self.is_pressed: + txt_bg = self._txt_hover_bg + + scale = self._scale_filter.update(PRESSED_SCALE if self.is_pressed else 1.0) + btn_x = self._rect.x + (self._rect.width * (1 - scale)) / 2 + btn_y = self._rect.y + (self._rect.height * (1 - scale)) / 2 + rl.draw_texture_ex(txt_bg, (btn_x, btn_y), 0, scale, rl.WHITE) + + self._draw_content(btn_y) + class BigToggle(BigButton): def __init__(self, text: str, value: str = "", initial_state: bool = False, toggle_callback: Callable | None = None): @@ -246,11 +252,11 @@ class BigToggle(BigButton): else: rl.draw_texture(self._txt_disabled_toggle, int(x), int(y), rl.WHITE) - def _render(self, _): - super()._render(_) + def _draw_content(self, btn_y: float): + super()._draw_content(btn_y) x = self._rect.x + self._rect.width - self._txt_enabled_toggle.width - y = self._rect.y + y = btn_y self._draw_pill(x, y, self._checked) @@ -275,13 +281,14 @@ class BigMultiToggle(BigToggle): if self._select_callback: self._select_callback(self.value) - def _render(self, _): - BigButton._render(self, _) + def _draw_content(self, btn_y: float): + # don't draw pill from BigToggle + BigButton._draw_content(self, btn_y) checked_idx = self._options.index(self.value) x = self._rect.x + self._rect.width - self._txt_enabled_toggle.width - y = self._rect.y + y = btn_y for i in range(len(self._options)): self._draw_pill(x, y, checked_idx == i) From decd7d4c1c71913b7ab579344eed0b0f29ab7f89 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 11 Feb 2026 00:35:03 -0500 Subject: [PATCH 896/910] [TIZI/TICI] ui: exclude angleState from fade effects when rendering torque bar (#1686) --- selfdrive/ui/sunnypilot/onroad/augmented_road_view.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/sunnypilot/onroad/augmented_road_view.py b/selfdrive/ui/sunnypilot/onroad/augmented_road_view.py index c7dedee54..64d7a3d94 100644 --- a/selfdrive/ui/sunnypilot/onroad/augmented_road_view.py +++ b/selfdrive/ui/sunnypilot/onroad/augmented_road_view.py @@ -23,7 +23,7 @@ class AugmentedRoadViewSP: def update_fade_out_bottom_overlay(self, _content_rect): # Fade out bottom of overlays for looks (only when engaged) fade_alpha = self._fade_alpha_filter.update(ui_state.status != UIStatus.DISENGAGED) - if ui_state.torque_bar and fade_alpha > 1e-2: + if ui_state.torque_bar and ui_state.sm['controlsState'].lateralControlState.which() != 'angleState' and fade_alpha > 1e-2: # Scale the fade texture to the content rect rl.draw_texture_pro(self._fade_texture, rl.Rectangle(0, 0, self._fade_texture.width, self._fade_texture.height), From 1070dda3ee4f38bb16645800ca508023fcfc8df1 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 10 Feb 2026 21:56:45 -0800 Subject: [PATCH 897/910] ui.py: fix stride (#37159) fix uii.py --- tools/replay/ui.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tools/replay/ui.py b/tools/replay/ui.py index 54667ebfe..8707f2be9 100755 --- a/tools/replay/ui.py +++ b/tools/replay/ui.py @@ -160,9 +160,12 @@ def ui_thread(addr): camera = DEVICE_CAMERAS[("tici", str(sm['roadCameraState'].sensor))] - imgff = np.frombuffer(yuv_img_raw.data, dtype=np.uint8).reshape((len(yuv_img_raw.data) // vipc_client.stride, vipc_client.stride)) - num_px = vipc_client.width * vipc_client.height - rgb = cv2.cvtColor(imgff[: vipc_client.height * 3 // 2, : vipc_client.width], cv2.COLOR_YUV2RGB_NV12) + # Use received buffer dimensions (full HEVC can have stride != buffer_len/rows due to VENUS padding) + h, w, stride = yuv_img_raw.height, yuv_img_raw.width, yuv_img_raw.stride + nv12_size = h * 3 // 2 * stride + imgff = np.frombuffer(yuv_img_raw.data, dtype=np.uint8, count=nv12_size).reshape((h * 3 // 2, stride)) + num_px = w * h + rgb = cv2.cvtColor(imgff[: h * 3 // 2, : w], cv2.COLOR_YUV2RGB_NV12) qcam = "QCAM" in os.environ bb_scale = (528 if qcam else camera.fcam.width) / 640.0 From 77f069cbe5afe1d2adb8895c3b2b2d60626403b1 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 10 Feb 2026 21:57:34 -0800 Subject: [PATCH 898/910] BigButton: don't round drawn content (#37158) * unround icons * unround rest --- selfdrive/ui/mici/widgets/button.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/selfdrive/ui/mici/widgets/button.py b/selfdrive/ui/mici/widgets/button.py index 118d33496..29844ccda 100644 --- a/selfdrive/ui/mici/widgets/button.py +++ b/selfdrive/ui/mici/widgets/button.py @@ -55,8 +55,8 @@ class BigCircleButton(Widget): def _draw_content(self, btn_y: float): # draw icon icon_color = rl.WHITE if self.enabled else rl.Color(255, 255, 255, int(255 * 0.35)) - rl.draw_texture(self._txt_icon, int(self._rect.x + (self._rect.width - self._txt_icon.width) / 2 + self._icon_offset[0]), - int(btn_y + (self._rect.height - self._txt_icon.height) / 2 + self._icon_offset[1]), icon_color) + rl.draw_texture_ex(self._txt_icon, (self._rect.x + (self._rect.width - self._txt_icon.width) / 2 + self._icon_offset[0], + btn_y + (self._rect.height - self._txt_icon.height) / 2 + self._icon_offset[1]), 0, 1.0, icon_color) def _render(self, _): # draw background @@ -100,9 +100,9 @@ class BigCircleToggle(BigCircleButton): super()._draw_content(btn_y) # draw status icon - rl.draw_texture(self._txt_toggle_enabled if self._checked else self._txt_toggle_disabled, - int(self._rect.x + (self._rect.width - self._txt_toggle_enabled.width) / 2), - int(btn_y + 5), rl.WHITE) + rl.draw_texture_ex(self._txt_toggle_enabled if self._checked else self._txt_toggle_disabled, + (self._rect.x + (self._rect.width - self._txt_toggle_enabled.width) / 2, btn_y + 5), + 0, 1.0, rl.WHITE) class BigButton(Widget): @@ -205,7 +205,7 @@ class BigButton(Widget): x = self._rect.x + self._rect.width - 30 - self._txt_icon.width / 2 y = btn_y + 30 + self._txt_icon.height / 2 source_rec = rl.Rectangle(0, 0, self._txt_icon.width, self._txt_icon.height) - dest_rec = rl.Rectangle(int(x), int(y), self._txt_icon.width, self._txt_icon.height) + dest_rec = rl.Rectangle(x, y, self._txt_icon.width, self._txt_icon.height) origin = rl.Vector2(self._txt_icon.width / 2, self._txt_icon.height / 2) rl.draw_texture_pro(self._txt_icon, source_rec, dest_rec, origin, rotation, rl.WHITE) @@ -248,9 +248,9 @@ class BigToggle(BigButton): def _draw_pill(self, x: float, y: float, checked: bool): # draw toggle icon top right if checked: - rl.draw_texture(self._txt_enabled_toggle, int(x), int(y), rl.WHITE) + rl.draw_texture_ex(self._txt_enabled_toggle, (x, y), 0, 1.0, rl.WHITE) else: - rl.draw_texture(self._txt_disabled_toggle, int(x), int(y), rl.WHITE) + rl.draw_texture_ex(self._txt_disabled_toggle, (x, y), 0, 1.0, rl.WHITE) def _draw_content(self, btn_y: float): super()._draw_content(btn_y) From 45099e7fcd384bb2dac36b55a6c10ba9de58a1e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Tue, 10 Feb 2026 23:12:41 -0800 Subject: [PATCH 899/910] Revert tgwarp again (#37161) * Reapply "revert tg calib and opencl cleanup (#37113)" (#37115) This reverts commit 667f3bb32f696b8bdcd93bce9143cd4171e436a3. * revert msgq too * msgq on master --- Dockerfile.openpilot_base | 37 + SConstruct | 1 + common/SConscript | 1 + common/clutil.cc | 98 ++ common/clutil.h | 28 + common/mat.h | 85 + msgq_repo | 2 +- selfdrive/modeld/SConscript | 56 +- selfdrive/modeld/compile_warp.py | 206 --- selfdrive/modeld/dmonitoringmodeld.py | 33 +- selfdrive/modeld/modeld.py | 59 +- selfdrive/modeld/models/commonmodel.cc | 64 + selfdrive/modeld/models/commonmodel.h | 97 ++ selfdrive/modeld/models/commonmodel.pxd | 27 + selfdrive/modeld/models/commonmodel_pyx.pxd | 13 + selfdrive/modeld/models/commonmodel_pyx.pyx | 74 + selfdrive/modeld/runners/tinygrad_helpers.py | 8 + selfdrive/modeld/transforms/loadyuv.cc | 76 + selfdrive/modeld/transforms/loadyuv.cl | 47 + selfdrive/modeld/transforms/loadyuv.h | 20 + selfdrive/modeld/transforms/transform.cc | 97 ++ selfdrive/modeld/transforms/transform.cl | 54 + selfdrive/modeld/transforms/transform.h | 25 + selfdrive/test/process_replay/model_replay.py | 4 +- .../test/process_replay/process_replay.py | 15 +- system/camerad/SConscript | 2 +- system/camerad/cameras/camera_common.cc | 5 +- system/camerad/cameras/camera_common.h | 2 +- system/camerad/cameras/camera_qcom2.cc | 22 +- system/camerad/cameras/spectra.cc | 4 +- system/camerad/cameras/spectra.h | 2 +- system/hardware/tici/tests/test_power_draw.py | 2 +- system/loggerd/SConscript | 5 +- third_party/opencl/include/CL/cl.h | 1452 +++++++++++++++++ third_party/opencl/include/CL/cl_d3d10.h | 131 ++ third_party/opencl/include/CL/cl_d3d11.h | 131 ++ .../opencl/include/CL/cl_dx9_media_sharing.h | 132 ++ third_party/opencl/include/CL/cl_egl.h | 136 ++ third_party/opencl/include/CL/cl_ext.h | 391 +++++ third_party/opencl/include/CL/cl_ext_qcom.h | 255 +++ third_party/opencl/include/CL/cl_gl.h | 167 ++ third_party/opencl/include/CL/cl_gl_ext.h | 74 + third_party/opencl/include/CL/cl_platform.h | 1333 +++++++++++++++ third_party/opencl/include/CL/opencl.h | 59 + tools/cabana/SConscript | 2 + tools/install_ubuntu_dependencies.sh | 3 + tools/replay/SConscript | 5 + tools/webcam/README.md | 4 + 48 files changed, 5237 insertions(+), 309 deletions(-) create mode 100644 common/clutil.cc create mode 100644 common/clutil.h create mode 100644 common/mat.h delete mode 100755 selfdrive/modeld/compile_warp.py create mode 100644 selfdrive/modeld/models/commonmodel.cc create mode 100644 selfdrive/modeld/models/commonmodel.h create mode 100644 selfdrive/modeld/models/commonmodel.pxd create mode 100644 selfdrive/modeld/models/commonmodel_pyx.pxd create mode 100644 selfdrive/modeld/models/commonmodel_pyx.pyx create mode 100644 selfdrive/modeld/runners/tinygrad_helpers.py create mode 100644 selfdrive/modeld/transforms/loadyuv.cc create mode 100644 selfdrive/modeld/transforms/loadyuv.cl create mode 100644 selfdrive/modeld/transforms/loadyuv.h create mode 100644 selfdrive/modeld/transforms/transform.cc create mode 100644 selfdrive/modeld/transforms/transform.cl create mode 100644 selfdrive/modeld/transforms/transform.h create mode 100644 third_party/opencl/include/CL/cl.h create mode 100644 third_party/opencl/include/CL/cl_d3d10.h create mode 100644 third_party/opencl/include/CL/cl_d3d11.h create mode 100644 third_party/opencl/include/CL/cl_dx9_media_sharing.h create mode 100644 third_party/opencl/include/CL/cl_egl.h create mode 100644 third_party/opencl/include/CL/cl_ext.h create mode 100644 third_party/opencl/include/CL/cl_ext_qcom.h create mode 100644 third_party/opencl/include/CL/cl_gl.h create mode 100644 third_party/opencl/include/CL/cl_gl_ext.h create mode 100644 third_party/opencl/include/CL/cl_platform.h create mode 100644 third_party/opencl/include/CL/opencl.h diff --git a/Dockerfile.openpilot_base b/Dockerfile.openpilot_base index 8a6041299..44d8d95e9 100644 --- a/Dockerfile.openpilot_base +++ b/Dockerfile.openpilot_base @@ -18,6 +18,43 @@ RUN /tmp/tools/install_ubuntu_dependencies.sh && \ cd /usr/lib/gcc/arm-none-eabi/* && \ rm -rf arm/ thumb/nofp thumb/v6* thumb/v8* thumb/v7+fp thumb/v7-r+fp.sp +# Add OpenCL +RUN apt-get update && apt-get install -y --no-install-recommends \ + apt-utils \ + alien \ + unzip \ + tar \ + curl \ + xz-utils \ + dbus \ + gcc-arm-none-eabi \ + tmux \ + vim \ + libx11-6 \ + wget \ + && rm -rf /var/lib/apt/lists/* + +RUN mkdir -p /tmp/opencl-driver-intel && \ + cd /tmp/opencl-driver-intel && \ + wget https://github.com/intel/llvm/releases/download/2024-WW14/oclcpuexp-2024.17.3.0.09_rel.tar.gz && \ + wget https://github.com/oneapi-src/oneTBB/releases/download/v2021.12.0/oneapi-tbb-2021.12.0-lin.tgz && \ + mkdir -p /opt/intel/oclcpuexp_2024.17.3.0.09_rel && \ + cd /opt/intel/oclcpuexp_2024.17.3.0.09_rel && \ + tar -zxvf /tmp/opencl-driver-intel/oclcpuexp-2024.17.3.0.09_rel.tar.gz && \ + mkdir -p /etc/OpenCL/vendors && \ + echo /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64/libintelocl.so > /etc/OpenCL/vendors/intel_expcpu.icd && \ + cd /opt/intel && \ + tar -zxvf /tmp/opencl-driver-intel/oneapi-tbb-2021.12.0-lin.tgz && \ + ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbb.so /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \ + ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbbmalloc.so /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \ + ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbb.so.12 /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \ + ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbbmalloc.so.2 /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \ + mkdir -p /etc/ld.so.conf.d && \ + echo /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 > /etc/ld.so.conf.d/libintelopenclexp.conf && \ + ldconfig -f /etc/ld.so.conf.d/libintelopenclexp.conf && \ + cd / && \ + rm -rf /tmp/opencl-driver-intel + ENV NVIDIA_VISIBLE_DEVICES=all ENV NVIDIA_DRIVER_CAPABILITIES=graphics,utility,compute ENV QTWEBENGINE_DISABLE_SANDBOX=1 diff --git a/SConstruct b/SConstruct index 4f04be624..ca5b7b6cb 100644 --- a/SConstruct +++ b/SConstruct @@ -94,6 +94,7 @@ env = Environment( # Arch-specific flags and paths if arch == "larch64": + env.Append(CPPPATH=["#third_party/opencl/include"]) env.Append(LIBPATH=[ "/usr/local/lib", "/system/vendor/lib64", diff --git a/common/SConscript b/common/SConscript index 15a0e5eff..1c68cf05c 100644 --- a/common/SConscript +++ b/common/SConscript @@ -5,6 +5,7 @@ common_libs = [ 'swaglog.cc', 'util.cc', 'ratekeeper.cc', + 'clutil.cc', ] _common = env.Library('common', common_libs, LIBS="json11") diff --git a/common/clutil.cc b/common/clutil.cc new file mode 100644 index 000000000..f8381a7e0 --- /dev/null +++ b/common/clutil.cc @@ -0,0 +1,98 @@ +#include "common/clutil.h" + +#include +#include +#include + +#include "common/util.h" +#include "common/swaglog.h" + +namespace { // helper functions + +template +std::string get_info(Func get_info_func, Id id, Name param_name) { + size_t size = 0; + CL_CHECK(get_info_func(id, param_name, 0, NULL, &size)); + std::string info(size, '\0'); + CL_CHECK(get_info_func(id, param_name, size, info.data(), NULL)); + return info; +} +inline std::string get_platform_info(cl_platform_id id, cl_platform_info name) { return get_info(&clGetPlatformInfo, id, name); } +inline std::string get_device_info(cl_device_id id, cl_device_info name) { return get_info(&clGetDeviceInfo, id, name); } + +void cl_print_info(cl_platform_id platform, cl_device_id device) { + size_t work_group_size = 0; + cl_device_type device_type = 0; + clGetDeviceInfo(device, CL_DEVICE_MAX_WORK_GROUP_SIZE, sizeof(work_group_size), &work_group_size, NULL); + clGetDeviceInfo(device, CL_DEVICE_TYPE, sizeof(device_type), &device_type, NULL); + const char *type_str = "Other..."; + switch (device_type) { + case CL_DEVICE_TYPE_CPU: type_str ="CL_DEVICE_TYPE_CPU"; break; + case CL_DEVICE_TYPE_GPU: type_str = "CL_DEVICE_TYPE_GPU"; break; + case CL_DEVICE_TYPE_ACCELERATOR: type_str = "CL_DEVICE_TYPE_ACCELERATOR"; break; + } + + LOGD("vendor: %s", get_platform_info(platform, CL_PLATFORM_VENDOR).c_str()); + LOGD("platform version: %s", get_platform_info(platform, CL_PLATFORM_VERSION).c_str()); + LOGD("profile: %s", get_platform_info(platform, CL_PLATFORM_PROFILE).c_str()); + LOGD("extensions: %s", get_platform_info(platform, CL_PLATFORM_EXTENSIONS).c_str()); + LOGD("name: %s", get_device_info(device, CL_DEVICE_NAME).c_str()); + LOGD("device version: %s", get_device_info(device, CL_DEVICE_VERSION).c_str()); + LOGD("max work group size: %zu", work_group_size); + LOGD("type = %d, %s", (int)device_type, type_str); +} + +void cl_print_build_errors(cl_program program, cl_device_id device) { + cl_build_status status; + clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_STATUS, sizeof(status), &status, NULL); + size_t log_size; + clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, 0, NULL, &log_size); + std::string log(log_size, '\0'); + clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, log_size, &log[0], NULL); + + LOGE("build failed; status=%d, log: %s", status, log.c_str()); +} + +} // namespace + +cl_device_id cl_get_device_id(cl_device_type device_type) { + cl_uint num_platforms = 0; + CL_CHECK(clGetPlatformIDs(0, NULL, &num_platforms)); + std::unique_ptr platform_ids = std::make_unique(num_platforms); + CL_CHECK(clGetPlatformIDs(num_platforms, &platform_ids[0], NULL)); + + for (size_t i = 0; i < num_platforms; ++i) { + LOGD("platform[%zu] CL_PLATFORM_NAME: %s", i, get_platform_info(platform_ids[i], CL_PLATFORM_NAME).c_str()); + + // Get first device + if (cl_device_id device_id = NULL; clGetDeviceIDs(platform_ids[i], device_type, 1, &device_id, NULL) == 0 && device_id) { + cl_print_info(platform_ids[i], device_id); + return device_id; + } + } + LOGE("No valid openCL platform found"); + assert(0); + return nullptr; +} + +cl_context cl_create_context(cl_device_id device_id) { + return CL_CHECK_ERR(clCreateContext(NULL, 1, &device_id, NULL, NULL, &err)); +} + +void cl_release_context(cl_context context) { + clReleaseContext(context); +} + +cl_program cl_program_from_file(cl_context ctx, cl_device_id device_id, const char* path, const char* args) { + return cl_program_from_source(ctx, device_id, util::read_file(path), args); +} + +cl_program cl_program_from_source(cl_context ctx, cl_device_id device_id, const std::string& src, const char* args) { + const char *csrc = src.c_str(); + cl_program prg = CL_CHECK_ERR(clCreateProgramWithSource(ctx, 1, &csrc, NULL, &err)); + if (int err = clBuildProgram(prg, 1, &device_id, args, NULL, NULL); err != 0) { + cl_print_build_errors(prg, device_id); + assert(0); + } + return prg; +} diff --git a/common/clutil.h b/common/clutil.h new file mode 100644 index 000000000..b364e79d4 --- /dev/null +++ b/common/clutil.h @@ -0,0 +1,28 @@ +#pragma once + +#ifdef __APPLE__ +#include +#else +#include +#endif + +#include + +#define CL_CHECK(_expr) \ + do { \ + assert(CL_SUCCESS == (_expr)); \ + } while (0) + +#define CL_CHECK_ERR(_expr) \ + ({ \ + cl_int err = CL_INVALID_VALUE; \ + __typeof__(_expr) _ret = _expr; \ + assert(_ret&& err == CL_SUCCESS); \ + _ret; \ + }) + +cl_device_id cl_get_device_id(cl_device_type device_type); +cl_context cl_create_context(cl_device_id device_id); +void cl_release_context(cl_context context); +cl_program cl_program_from_source(cl_context ctx, cl_device_id device_id, const std::string& src, const char* args = nullptr); +cl_program cl_program_from_file(cl_context ctx, cl_device_id device_id, const char* path, const char* args); diff --git a/common/mat.h b/common/mat.h new file mode 100644 index 000000000..8e10d6197 --- /dev/null +++ b/common/mat.h @@ -0,0 +1,85 @@ +#pragma once + +typedef struct vec3 { + float v[3]; +} vec3; + +typedef struct vec4 { + float v[4]; +} vec4; + +typedef struct mat3 { + float v[3*3]; +} mat3; + +typedef struct mat4 { + float v[4*4]; +} mat4; + +static inline mat3 matmul3(const mat3 &a, const mat3 &b) { + mat3 ret = {{0.0}}; + for (int r=0; r<3; r++) { + for (int c=0; c<3; c++) { + float v = 0.0; + for (int k=0; k<3; k++) { + v += a.v[r*3+k] * b.v[k*3+c]; + } + ret.v[r*3+c] = v; + } + } + return ret; +} + +static inline vec3 matvecmul3(const mat3 &a, const vec3 &b) { + vec3 ret = {{0.0}}; + for (int r=0; r<3; r++) { + for (int c=0; c<3; c++) { + ret.v[r] += a.v[r*3+c] * b.v[c]; + } + } + return ret; +} + +static inline mat4 matmul(const mat4 &a, const mat4 &b) { + mat4 ret = {{0.0}}; + for (int r=0; r<4; r++) { + for (int c=0; c<4; c++) { + float v = 0.0; + for (int k=0; k<4; k++) { + v += a.v[r*4+k] * b.v[k*4+c]; + } + ret.v[r*4+c] = v; + } + } + return ret; +} + +static inline vec4 matvecmul(const mat4 &a, const vec4 &b) { + vec4 ret = {{0.0}}; + for (int r=0; r<4; r++) { + for (int c=0; c<4; c++) { + ret.v[r] += a.v[r*4+c] * b.v[c]; + } + } + return ret; +} + +// scales the input and output space of a transformation matrix +// that assumes pixel-center origin. +static inline mat3 transform_scale_buffer(const mat3 &in, float s) { + // in_pt = ( transform(out_pt/s + 0.5) - 0.5) * s + + mat3 transform_out = {{ + 1.0f/s, 0.0f, 0.5f, + 0.0f, 1.0f/s, 0.5f, + 0.0f, 0.0f, 1.0f, + }}; + + mat3 transform_in = {{ + s, 0.0f, -0.5f*s, + 0.0f, s, -0.5f*s, + 0.0f, 0.0f, 1.0f, + }}; + + return matmul3(transform_in, matmul3(in, transform_out)); +} diff --git a/msgq_repo b/msgq_repo index 2c191c1a7..4c4e814ed 160000 --- a/msgq_repo +++ b/msgq_repo @@ -1 +1 @@ -Subproject commit 2c191c1a72ae8119b93b49e1bc12d4a99b751855 +Subproject commit 4c4e814ed592db52431bb53d37f0bb93299e960c diff --git a/selfdrive/modeld/SConscript b/selfdrive/modeld/SConscript index 84e94df4a..91f359744 100644 --- a/selfdrive/modeld/SConscript +++ b/selfdrive/modeld/SConscript @@ -1,12 +1,35 @@ import os import glob -Import('env', 'arch') +Import('env', 'envCython', 'arch', 'cereal', 'messaging', 'common', 'visionipc') lenv = env.Clone() +lenvCython = envCython.Clone() -tinygrad_root = env.Dir("#").abspath -tinygrad_files = ["#"+x for x in glob.glob(env.Dir("#tinygrad_repo").relpath + "/**", recursive=True, root_dir=tinygrad_root) - if 'pycache' not in x and os.path.isfile(os.path.join(tinygrad_root, x))] +libs = [cereal, messaging, visionipc, common, 'capnp', 'kj', 'pthread'] +frameworks = [] + +common_src = [ + "models/commonmodel.cc", + "transforms/loadyuv.cc", + "transforms/transform.cc", +] + +# OpenCL is a framework on Mac +if arch == "Darwin": + frameworks += ['OpenCL'] +else: + libs += ['OpenCL'] + +# Set path definitions +for pathdef, fn in {'TRANSFORM': 'transforms/transform.cl', 'LOADYUV': 'transforms/loadyuv.cl'}.items(): + for xenv in (lenv, lenvCython): + xenv['CXXFLAGS'].append(f'-D{pathdef}_PATH=\\"{File(fn).abspath}\\"') + +# Compile cython +cython_libs = envCython["LIBS"] + libs +commonmodel_lib = lenv.Library('commonmodel', common_src) +lenvCython.Program('models/commonmodel_pyx.so', 'models/commonmodel_pyx.pyx', LIBS=[commonmodel_lib, *cython_libs], FRAMEWORKS=frameworks) +tinygrad_files = sorted(["#"+x for x in glob.glob(env.Dir("#tinygrad_repo").relpath + "/**", recursive=True, root_dir=env.Dir("#").abspath) if 'pycache' not in x]) # Get model metadata for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']: @@ -15,35 +38,22 @@ for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']: 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 warp -tg_flags = { - 'larch64': 'DEV=QCOM FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0', - 'Darwin': f'DEV=CPU HOME={os.path.expanduser("~")}', # tinygrad calls brew which needs a $HOME in the env -}.get(arch, 'DEV=CPU CPU_LLVM=1') -image_flag = { - 'larch64': 'IMAGE=2', -}.get(arch, 'IMAGE=0') -script_files = [File(Dir("#selfdrive/modeld").File("compile_warp.py").abspath)] -cmd = f'{tg_flags} python3 {Dir("#selfdrive/modeld").abspath}/compile_warp.py ' -from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye -warp_targets = [] -for cam in [_ar_ox_fisheye, _os_fisheye]: - w, h = cam.width, cam.height - warp_targets += [File(f"models/warp_{w}x{h}_tinygrad.pkl").abspath, File(f"models/dm_warp_{w}x{h}_tinygrad.pkl").abspath] -lenv.Command(warp_targets, tinygrad_files + script_files, cmd) - 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} {image_flag} python3 {Dir("#tinygrad_repo").abspath}/examples/openpilot/compile3.py {fn}.onnx {fn}_tinygrad.pkl' + 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']: - tg_compile(tg_flags, model_name) + flags = { + 'larch64': 'DEV=QCOM FLOAT16=1 NOLOCALS=1 IMAGE=2 JIT_BATCH_SIZE=0', + 'Darwin': f'DEV=CPU HOME={os.path.expanduser("~")}', # tinygrad calls brew which needs a $HOME in the env + }.get(arch, 'DEV=CPU CPU_LLVM=1') + tg_compile(flags, model_name) # Compile BIG model if USB GPU is available if "USBGPU" in os.environ: diff --git a/selfdrive/modeld/compile_warp.py b/selfdrive/modeld/compile_warp.py deleted file mode 100755 index 5adb60e62..000000000 --- a/selfdrive/modeld/compile_warp.py +++ /dev/null @@ -1,206 +0,0 @@ -#!/usr/bin/env python3 -import time -import pickle -import numpy as np -from pathlib import Path -from tinygrad.tensor import Tensor -from tinygrad.helpers import Context -from tinygrad.device import Device -from tinygrad.engine.jit import TinyJit - -from openpilot.system.camerad.cameras.nv12_info import get_nv12_info -from openpilot.common.transformations.model import MEDMODEL_INPUT_SIZE, DM_INPUT_SIZE -from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye - -MODELS_DIR = Path(__file__).parent / 'models' - -CAMERA_CONFIGS = [ - (_ar_ox_fisheye.width, _ar_ox_fisheye.height), # tici: 1928x1208 - (_os_fisheye.width, _os_fisheye.height), # mici: 1344x760 -] - -UV_SCALE_MATRIX = np.array([[0.5, 0, 0], [0, 0.5, 0], [0, 0, 1]], dtype=np.float32) -UV_SCALE_MATRIX_INV = np.linalg.inv(UV_SCALE_MATRIX) - -IMG_BUFFER_SHAPE = (30, MEDMODEL_INPUT_SIZE[1] // 2, MEDMODEL_INPUT_SIZE[0] // 2) - - -def warp_pkl_path(w, h): - return MODELS_DIR / f'warp_{w}x{h}_tinygrad.pkl' - - -def dm_warp_pkl_path(w, h): - return MODELS_DIR / f'dm_warp_{w}x{h}_tinygrad.pkl' - - -def warp_perspective_tinygrad(src_flat, M_inv, dst_shape, src_shape, stride_pad, ratio): - w_dst, h_dst = dst_shape - h_src, w_src = src_shape - - x = Tensor.arange(w_dst).reshape(1, w_dst).expand(h_dst, w_dst) - y = Tensor.arange(h_dst).reshape(h_dst, 1).expand(h_dst, w_dst) - ones = Tensor.ones_like(x) - dst_coords = x.reshape(1, -1).cat(y.reshape(1, -1)).cat(ones.reshape(1, -1)) - - src_coords = M_inv @ dst_coords - src_coords = src_coords / src_coords[2:3, :] - - x_nn_clipped = Tensor.round(src_coords[0]).clip(0, w_src - 1).cast('int') - y_nn_clipped = Tensor.round(src_coords[1]).clip(0, h_src - 1).cast('int') - idx = y_nn_clipped * w_src + (y_nn_clipped * ratio).cast('int') * stride_pad + x_nn_clipped - - sampled = src_flat[idx] - return sampled - - -def frames_to_tensor(frames, model_w, model_h): - H = (frames.shape[0] * 2) // 3 - W = frames.shape[1] - in_img1 = Tensor.cat(frames[0:H:2, 0::2], - frames[1:H:2, 0::2], - frames[0:H:2, 1::2], - frames[1:H:2, 1::2], - frames[H:H+H//4].reshape((H//2, W//2)), - frames[H+H//4:H+H//2].reshape((H//2, W//2)), dim=0).reshape((6, H//2, W//2)) - return in_img1 - - -def make_frame_prepare(cam_w, cam_h, model_w, model_h): - stride, y_height, _, _ = get_nv12_info(cam_w, cam_h) - uv_offset = stride * y_height - stride_pad = stride - cam_w - - def frame_prepare_tinygrad(input_frame, M_inv): - tg_scale = Tensor(UV_SCALE_MATRIX) - M_inv_uv = tg_scale @ M_inv @ Tensor(UV_SCALE_MATRIX_INV) - with Context(SPLIT_REDUCEOP=0): - y = warp_perspective_tinygrad(input_frame[:cam_h*stride], - M_inv, (model_w, model_h), - (cam_h, cam_w), stride_pad, 1).realize() - u = warp_perspective_tinygrad(input_frame[uv_offset:uv_offset + (cam_h//4)*stride], - M_inv_uv, (model_w//2, model_h//2), - (cam_h//2, cam_w//2), stride_pad, 0.5).realize() - v = warp_perspective_tinygrad(input_frame[uv_offset + (cam_h//4)*stride:uv_offset + (cam_h//2)*stride], - M_inv_uv, (model_w//2, model_h//2), - (cam_h//2, cam_w//2), stride_pad, 0.5).realize() - yuv = y.cat(u).cat(v).reshape((model_h * 3 // 2, model_w)) - tensor = frames_to_tensor(yuv, model_w, model_h) - return tensor - return frame_prepare_tinygrad - - -def make_update_img_input(frame_prepare, model_w, model_h): - def update_img_input_tinygrad(tensor, frame, M_inv): - M_inv = M_inv.to(Device.DEFAULT) - new_img = frame_prepare(frame, M_inv) - full_buffer = tensor[6:].cat(new_img, dim=0).contiguous() - return full_buffer, Tensor.cat(full_buffer[:6], full_buffer[-6:], dim=0).contiguous().reshape(1, 12, model_h//2, model_w//2) - return update_img_input_tinygrad - - -def make_update_both_imgs(frame_prepare, model_w, model_h): - update_img = make_update_img_input(frame_prepare, model_w, model_h) - - def update_both_imgs_tinygrad(calib_img_buffer, new_img, M_inv, - calib_big_img_buffer, new_big_img, M_inv_big): - calib_img_buffer, calib_img_pair = update_img(calib_img_buffer, new_img, M_inv) - calib_big_img_buffer, calib_big_img_pair = update_img(calib_big_img_buffer, new_big_img, M_inv_big) - return calib_img_buffer, calib_img_pair, calib_big_img_buffer, calib_big_img_pair - return update_both_imgs_tinygrad - - -def make_warp_dm(cam_w, cam_h, dm_w, dm_h): - stride, y_height, _, _ = get_nv12_info(cam_w, cam_h) - stride_pad = stride - cam_w - - def warp_dm(input_frame, M_inv): - M_inv = M_inv.to(Device.DEFAULT) - with Context(SPLIT_REDUCEOP=0): - result = warp_perspective_tinygrad(input_frame[:cam_h*stride], M_inv, (dm_w, dm_h), (cam_h, cam_w), stride_pad, 1).reshape(-1, dm_h * dm_w) - return result - return warp_dm - - -def compile_modeld_warp(cam_w, cam_h): - model_w, model_h = MEDMODEL_INPUT_SIZE - _, _, _, yuv_size = get_nv12_info(cam_w, cam_h) - - print(f"Compiling modeld warp for {cam_w}x{cam_h}...") - - frame_prepare = make_frame_prepare(cam_w, cam_h, model_w, model_h) - update_both_imgs = make_update_both_imgs(frame_prepare, model_w, model_h) - update_img_jit = TinyJit(update_both_imgs, prune=True) - - full_buffer = Tensor.zeros(IMG_BUFFER_SHAPE, dtype='uint8').contiguous().realize() - big_full_buffer = Tensor.zeros(IMG_BUFFER_SHAPE, dtype='uint8').contiguous().realize() - full_buffer_np = np.zeros(IMG_BUFFER_SHAPE, dtype=np.uint8) - big_full_buffer_np = np.zeros(IMG_BUFFER_SHAPE, dtype=np.uint8) - - for i in range(10): - new_frame_np = (32 * np.random.randn(yuv_size).astype(np.float32) + 128).clip(0, 255).astype(np.uint8) - img_inputs = [full_buffer, - Tensor.from_blob(new_frame_np.ctypes.data, (yuv_size,), dtype='uint8').realize(), - Tensor(Tensor.randn(3, 3).mul(8).realize().numpy(), device='NPY')] - new_big_frame_np = (32 * np.random.randn(yuv_size).astype(np.float32) + 128).clip(0, 255).astype(np.uint8) - big_img_inputs = [big_full_buffer, - Tensor.from_blob(new_big_frame_np.ctypes.data, (yuv_size,), dtype='uint8').realize(), - Tensor(Tensor.randn(3, 3).mul(8).realize().numpy(), device='NPY')] - inputs = img_inputs + big_img_inputs - Device.default.synchronize() - - inputs_np = [x.numpy() for x in inputs] - inputs_np[0] = full_buffer_np - inputs_np[3] = big_full_buffer_np - - st = time.perf_counter() - out = update_img_jit(*inputs) - full_buffer = out[0].contiguous().realize().clone() - big_full_buffer = out[2].contiguous().realize().clone() - mt = time.perf_counter() - Device.default.synchronize() - et = time.perf_counter() - print(f" [{i+1}/10] enqueue {(mt-st)*1e3:6.2f} ms -- total {(et-st)*1e3:6.2f} ms") - - pkl_path = warp_pkl_path(cam_w, cam_h) - with open(pkl_path, "wb") as f: - pickle.dump(update_img_jit, f) - print(f" Saved to {pkl_path}") - - jit = pickle.load(open(pkl_path, "rb")) - jit(*inputs) - - -def compile_dm_warp(cam_w, cam_h): - dm_w, dm_h = DM_INPUT_SIZE - _, _, _, yuv_size = get_nv12_info(cam_w, cam_h) - - print(f"Compiling DM warp for {cam_w}x{cam_h}...") - - warp_dm = make_warp_dm(cam_w, cam_h, dm_w, dm_h) - warp_dm_jit = TinyJit(warp_dm, prune=True) - - for i in range(10): - inputs = [Tensor.from_blob((32 * Tensor.randn(yuv_size,) + 128).cast(dtype='uint8').realize().numpy().ctypes.data, (yuv_size,), dtype='uint8'), - Tensor(Tensor.randn(3, 3).mul(8).realize().numpy(), device='NPY')] - Device.default.synchronize() - st = time.perf_counter() - warp_dm_jit(*inputs) - mt = time.perf_counter() - Device.default.synchronize() - et = time.perf_counter() - print(f" [{i+1}/10] enqueue {(mt-st)*1e3:6.2f} ms -- total {(et-st)*1e3:6.2f} ms") - - pkl_path = dm_warp_pkl_path(cam_w, cam_h) - with open(pkl_path, "wb") as f: - pickle.dump(warp_dm_jit, f) - print(f" Saved to {pkl_path}") - - -def run_and_save_pickle(): - for cam_w, cam_h in CAMERA_CONFIGS: - compile_modeld_warp(cam_w, cam_h) - compile_dm_warp(cam_w, cam_h) - - -if __name__ == "__main__": - run_and_save_pickle() diff --git a/selfdrive/modeld/dmonitoringmodeld.py b/selfdrive/modeld/dmonitoringmodeld.py index bc3adffff..717799857 100755 --- a/selfdrive/modeld/dmonitoringmodeld.py +++ b/selfdrive/modeld/dmonitoringmodeld.py @@ -3,6 +3,7 @@ import os from openpilot.system.hardware import TICI os.environ['DEV'] = 'QCOM' if TICI else 'CPU' from tinygrad.tensor import Tensor +from tinygrad.dtype import dtypes import time import pickle import numpy as np @@ -15,34 +16,32 @@ from openpilot.common.swaglog import cloudlog from openpilot.common.realtime import config_realtime_process from openpilot.common.transformations.model import dmonitoringmodel_intrinsics from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye -from openpilot.system.camerad.cameras.nv12_info import get_nv12_info +from openpilot.selfdrive.modeld.models.commonmodel_pyx import CLContext, MonitoringModelFrame from openpilot.selfdrive.modeld.parse_model_outputs import sigmoid, safe_exp +from openpilot.selfdrive.modeld.runners.tinygrad_helpers import qcom_tensor_from_opencl_address PROCESS_NAME = "selfdrive.modeld.dmonitoringmodeld" SEND_RAW_PRED = os.getenv('SEND_RAW_PRED') MODEL_PKL_PATH = Path(__file__).parent / 'models/dmonitoring_model_tinygrad.pkl' METADATA_PATH = Path(__file__).parent / 'models/dmonitoring_model_metadata.pkl' -MODELS_DIR = Path(__file__).parent / 'models' + class ModelState: inputs: dict[str, np.ndarray] output: np.ndarray - def __init__(self): + def __init__(self, cl_ctx): with open(METADATA_PATH, 'rb') as f: model_metadata = pickle.load(f) self.input_shapes = model_metadata['input_shapes'] self.output_slices = model_metadata['output_slices'] + self.frame = MonitoringModelFrame(cl_ctx) self.numpy_inputs = { 'calib': np.zeros(self.input_shapes['calib'], dtype=np.float32), } - self.warp_inputs_np = {'transform': np.zeros((3,3), dtype=np.float32)} - self.warp_inputs = {k: Tensor(v, device='NPY') for k,v in self.warp_inputs_np.items()} - self.frame_buf_params = None self.tensor_inputs = {k: Tensor(v, device='NPY').realize() for k,v in self.numpy_inputs.items()} - self.image_warp = None with open(MODEL_PKL_PATH, "rb") as f: self.model_run = pickle.load(f) @@ -51,15 +50,14 @@ class ModelState: t1 = time.perf_counter() - if self.image_warp is None: - self.frame_buf_params = get_nv12_info(buf.width, buf.height) - warp_path = MODELS_DIR / f'dm_warp_{buf.width}x{buf.height}_tinygrad.pkl' - with open(warp_path, "rb") as f: - self.image_warp = pickle.load(f) - self.warp_inputs['frame'] = Tensor.from_blob(buf.data.ctypes.data, (self.frame_buf_params[3],), dtype='uint8').realize() + input_img_cl = self.frame.prepare(buf, transform.flatten()) + if TICI: + # The imgs tensors are backed by opencl memory, only need init once + if 'input_img' not in self.tensor_inputs: + self.tensor_inputs['input_img'] = qcom_tensor_from_opencl_address(input_img_cl.mem_address, self.input_shapes['input_img'], dtype=dtypes.uint8) + else: + self.tensor_inputs['input_img'] = Tensor(self.frame.buffer_from_cl(input_img_cl).reshape(self.input_shapes['input_img']), dtype=dtypes.uint8).realize() - self.warp_inputs_np['transform'][:] = transform[:] - self.tensor_inputs['input_img'] = self.image_warp(self.warp_inputs['frame'], self.warp_inputs['transform']).realize() output = self.model_run(**self.tensor_inputs).numpy().flatten() @@ -109,11 +107,12 @@ def get_driverstate_packet(model_output, frame_id: int, location_ts: int, exec_t def main(): config_realtime_process(7, 5) - model = ModelState() + cl_context = CLContext() + model = ModelState(cl_context) cloudlog.warning("models loaded, dmonitoringmodeld starting") cloudlog.warning("connecting to driver stream") - vipc_client = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_DRIVER, True) + vipc_client = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_DRIVER, True, cl_context) while not vipc_client.connect(False): time.sleep(0.1) assert vipc_client.is_connected() diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index 6f3c481ea..846bb8d2c 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -7,6 +7,7 @@ if USBGPU: os.environ['DEV'] = 'AMD' os.environ['AMD_IFACE'] = 'USB' from tinygrad.tensor import Tensor +from tinygrad.dtype import dtypes import time import pickle import numpy as np @@ -21,13 +22,14 @@ from openpilot.common.params import Params from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.realtime import config_realtime_process, DT_MDL from openpilot.common.transformations.camera import DEVICE_CAMERAS -from openpilot.system.camerad.cameras.nv12_info import get_nv12_info from openpilot.common.transformations.model import get_warp_matrix 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.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 +from openpilot.selfdrive.modeld.models.commonmodel_pyx import DrivingModelFrame, CLContext +from openpilot.selfdrive.modeld.runners.tinygrad_helpers import qcom_tensor_from_opencl_address PROCESS_NAME = "selfdrive.modeld.modeld" @@ -37,15 +39,11 @@ VISION_PKL_PATH = Path(__file__).parent / 'models/driving_vision_tinygrad.pkl' 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' -MODELS_DIR = Path(__file__).parent / 'models' LAT_SMOOTH_SECONDS = 0.0 LONG_SMOOTH_SECONDS = 0.3 MIN_LAT_CONTROL_SPEED = 0.3 -IMG_QUEUE_SHAPE = (6*(ModelConstants.MODEL_RUN_FREQ//ModelConstants.MODEL_CONTEXT_FREQ + 1), 128, 256) -assert IMG_QUEUE_SHAPE[0] == 30 - def get_action_from_model(model_output: dict[str, np.ndarray], prev_action: log.ModelDataV2.Action, lat_action_t: float, long_action_t: float, v_ego: float) -> log.ModelDataV2.Action: @@ -138,11 +136,12 @@ class InputQueues: return out class ModelState: + frames: dict[str, DrivingModelFrame] inputs: dict[str, np.ndarray] output: np.ndarray prev_desire: np.ndarray # for tracking the rising edge of the pulse - def __init__(self): + def __init__(self, context: CLContext): with open(VISION_METADATA_PATH, 'rb') as f: vision_metadata = pickle.load(f) self.vision_input_shapes = vision_metadata['input_shapes'] @@ -156,6 +155,7 @@ class ModelState: self.policy_output_slices = policy_metadata['output_slices'] policy_output_size = policy_metadata['output_shapes']['outputs'][1] + self.frames = {name: DrivingModelFrame(context, ModelConstants.MODEL_RUN_FREQ//ModelConstants.MODEL_CONTEXT_FREQ) for name in self.vision_input_names} self.prev_desire = np.zeros(ModelConstants.DESIRE_LEN, dtype=np.float32) # policy inputs @@ -165,17 +165,12 @@ class ModelState: self.full_input_queues.update_dtypes_and_shapes({k: self.numpy_inputs[k].dtype}, {k: self.numpy_inputs[k].shape}) self.full_input_queues.reset() - self.img_queues = {'img': Tensor.zeros(IMG_QUEUE_SHAPE, dtype='uint8').contiguous().realize(), - 'big_img': Tensor.zeros(IMG_QUEUE_SHAPE, dtype='uint8').contiguous().realize(),} - self.full_frames : dict[str, Tensor] = {} - self.transforms_np = {k: np.zeros((3,3), dtype=np.float32) for k in self.img_queues} - self.transforms = {k: Tensor(v, device='NPY').realize() for k, v in self.transforms_np.items()} + # img buffers are managed in openCL transform code + self.vision_inputs: dict[str, Tensor] = {} self.vision_output = np.zeros(vision_output_size, dtype=np.float32) self.policy_inputs = {k: Tensor(v, device='NPY').realize() for k,v in self.numpy_inputs.items()} self.policy_output = np.zeros(policy_output_size, dtype=np.float32) self.parser = Parser() - self.frame_buf_params : dict[str, tuple[int, int, int, int]] = {} - self.update_imgs = None with open(VISION_PKL_PATH, "rb") as f: self.vision_run = pickle.load(f) @@ -193,28 +188,23 @@ class ModelState: inputs['desire_pulse'][0] = 0 new_desire = np.where(inputs['desire_pulse'] - self.prev_desire > .99, inputs['desire_pulse'], 0) self.prev_desire[:] = inputs['desire_pulse'] - if self.update_imgs is None: - for key in bufs.keys(): - w, h = bufs[key].width, bufs[key].height - self.frame_buf_params[key] = get_nv12_info(w, h) - warp_path = MODELS_DIR / f'warp_{w}x{h}_tinygrad.pkl' - with open(warp_path, "rb") as f: - self.update_imgs = pickle.load(f) + imgs_cl = {name: self.frames[name].prepare(bufs[name], transforms[name].flatten()) for name in self.vision_input_names} - for key in bufs.keys(): - self.full_frames[key] = Tensor.from_blob(bufs[key].data.ctypes.data, (self.frame_buf_params[key][3],), dtype='uint8').realize() - self.transforms_np[key][:,:] = transforms[key][:,:] - - out = self.update_imgs(self.img_queues['img'], self.full_frames['img'], self.transforms['img'], - self.img_queues['big_img'], self.full_frames['big_img'], self.transforms['big_img']) - self.img_queues['img'], self.img_queues['big_img'], = out[0].realize(), out[2].realize() - vision_inputs = {'img': out[1], 'big_img': out[3]} + 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: + self.vision_inputs[key] = qcom_tensor_from_opencl_address(imgs_cl[key].mem_address, self.vision_input_shapes[key], dtype=dtypes.uint8) + else: + for key in imgs_cl: + frame_input = self.frames[key].buffer_from_cl(imgs_cl[key]).reshape(self.vision_input_shapes[key]) + self.vision_inputs[key] = Tensor(frame_input, dtype=dtypes.uint8).realize() if prepare_only: return None - self.vision_output = self.vision_run(**vision_inputs).numpy().flatten() + self.vision_output = self.vision_run(**self.vision_inputs).contiguous().realize().uop.base.buffer.numpy() vision_outputs_dict = self.parser.parse_vision_outputs(self.slice_outputs(self.vision_output, self.vision_output_slices)) self.full_input_queues.enqueue({'features_buffer': vision_outputs_dict['hidden_state'], 'desire_pulse': new_desire}) @@ -224,6 +214,7 @@ class ModelState: self.policy_output = self.policy_run(**self.policy_inputs).numpy().flatten() policy_outputs_dict = self.parser.parse_policy_outputs(self.slice_outputs(self.policy_output, self.policy_output_slices)) + combined_outputs_dict = {**vision_outputs_dict, **policy_outputs_dict} if SEND_RAW_PRED: combined_outputs_dict['raw_pred'] = np.concatenate([self.vision_output.copy(), self.policy_output.copy()]) @@ -240,8 +231,10 @@ def main(demo=False): config_realtime_process(7, 54) st = time.monotonic() - cloudlog.warning("loading model") - model = ModelState() + cloudlog.warning("setting up CL context") + cl_context = CLContext() + cloudlog.warning("CL context ready; loading model") + model = ModelState(cl_context) cloudlog.warning(f"models loaded in {time.monotonic() - st:.1f}s, modeld starting") # visionipc clients @@ -254,8 +247,8 @@ def main(demo=False): time.sleep(.1) vipc_client_main_stream = VisionStreamType.VISION_STREAM_WIDE_ROAD if main_wide_camera else VisionStreamType.VISION_STREAM_ROAD - vipc_client_main = VisionIpcClient("camerad", vipc_client_main_stream, True) - vipc_client_extra = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_WIDE_ROAD, False) + vipc_client_main = VisionIpcClient("camerad", vipc_client_main_stream, True, cl_context) + vipc_client_extra = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_WIDE_ROAD, False, cl_context) cloudlog.warning(f"vision stream set up, main_wide_camera: {main_wide_camera}, use_extra_client: {use_extra_client}") while not vipc_client_main.connect(False): diff --git a/selfdrive/modeld/models/commonmodel.cc b/selfdrive/modeld/models/commonmodel.cc new file mode 100644 index 000000000..d3341e76e --- /dev/null +++ b/selfdrive/modeld/models/commonmodel.cc @@ -0,0 +1,64 @@ +#include "selfdrive/modeld/models/commonmodel.h" + +#include +#include + +#include "common/clutil.h" + +DrivingModelFrame::DrivingModelFrame(cl_device_id device_id, cl_context context, int _temporal_skip) : ModelFrame(device_id, context) { + input_frames = std::make_unique(buf_size); + temporal_skip = _temporal_skip; + input_frames_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, buf_size, NULL, &err)); + img_buffer_20hz_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, (temporal_skip+1)*frame_size_bytes, NULL, &err)); + region.origin = temporal_skip * frame_size_bytes; + region.size = frame_size_bytes; + last_img_cl = CL_CHECK_ERR(clCreateSubBuffer(img_buffer_20hz_cl, CL_MEM_READ_WRITE, CL_BUFFER_CREATE_TYPE_REGION, ®ion, &err)); + + loadyuv_init(&loadyuv, context, device_id, MODEL_WIDTH, MODEL_HEIGHT); + init_transform(device_id, context, MODEL_WIDTH, MODEL_HEIGHT); +} + +cl_mem* DrivingModelFrame::prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection) { + run_transform(yuv_cl, MODEL_WIDTH, MODEL_HEIGHT, frame_width, frame_height, frame_stride, frame_uv_offset, projection); + + for (int i = 0; i < temporal_skip; i++) { + CL_CHECK(clEnqueueCopyBuffer(q, img_buffer_20hz_cl, img_buffer_20hz_cl, (i+1)*frame_size_bytes, i*frame_size_bytes, frame_size_bytes, 0, nullptr, nullptr)); + } + loadyuv_queue(&loadyuv, q, y_cl, u_cl, v_cl, last_img_cl); + + copy_queue(&loadyuv, q, img_buffer_20hz_cl, input_frames_cl, 0, 0, frame_size_bytes); + copy_queue(&loadyuv, q, last_img_cl, input_frames_cl, 0, frame_size_bytes, frame_size_bytes); + + // NOTE: Since thneed is using a different command queue, this clFinish is needed to ensure the image is ready. + clFinish(q); + return &input_frames_cl; +} + +DrivingModelFrame::~DrivingModelFrame() { + deinit_transform(); + loadyuv_destroy(&loadyuv); + CL_CHECK(clReleaseMemObject(input_frames_cl)); + CL_CHECK(clReleaseMemObject(img_buffer_20hz_cl)); + CL_CHECK(clReleaseMemObject(last_img_cl)); + CL_CHECK(clReleaseCommandQueue(q)); +} + + +MonitoringModelFrame::MonitoringModelFrame(cl_device_id device_id, cl_context context) : ModelFrame(device_id, context) { + input_frames = std::make_unique(buf_size); + input_frame_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, buf_size, NULL, &err)); + + init_transform(device_id, context, MODEL_WIDTH, MODEL_HEIGHT); +} + +cl_mem* MonitoringModelFrame::prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection) { + run_transform(yuv_cl, MODEL_WIDTH, MODEL_HEIGHT, frame_width, frame_height, frame_stride, frame_uv_offset, projection); + clFinish(q); + return &y_cl; +} + +MonitoringModelFrame::~MonitoringModelFrame() { + deinit_transform(); + CL_CHECK(clReleaseMemObject(input_frame_cl)); + CL_CHECK(clReleaseCommandQueue(q)); +} diff --git a/selfdrive/modeld/models/commonmodel.h b/selfdrive/modeld/models/commonmodel.h new file mode 100644 index 000000000..176d7eb6d --- /dev/null +++ b/selfdrive/modeld/models/commonmodel.h @@ -0,0 +1,97 @@ +#pragma once + +#include +#include +#include + +#include + +#define CL_USE_DEPRECATED_OPENCL_1_2_APIS +#ifdef __APPLE__ +#include +#else +#include +#endif + +#include "common/mat.h" +#include "selfdrive/modeld/transforms/loadyuv.h" +#include "selfdrive/modeld/transforms/transform.h" + +class ModelFrame { +public: + ModelFrame(cl_device_id device_id, cl_context context) { + q = CL_CHECK_ERR(clCreateCommandQueue(context, device_id, 0, &err)); + } + virtual ~ModelFrame() {} + virtual cl_mem* prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection) { return NULL; } + uint8_t* buffer_from_cl(cl_mem *in_frames, int buffer_size) { + CL_CHECK(clEnqueueReadBuffer(q, *in_frames, CL_TRUE, 0, buffer_size, input_frames.get(), 0, nullptr, nullptr)); + clFinish(q); + return &input_frames[0]; + } + + int MODEL_WIDTH; + int MODEL_HEIGHT; + int MODEL_FRAME_SIZE; + int buf_size; + +protected: + cl_mem y_cl, u_cl, v_cl; + Transform transform; + cl_command_queue q; + std::unique_ptr input_frames; + + void init_transform(cl_device_id device_id, cl_context context, int model_width, int model_height) { + y_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, model_width * model_height, NULL, &err)); + u_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, (model_width / 2) * (model_height / 2), NULL, &err)); + v_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, (model_width / 2) * (model_height / 2), NULL, &err)); + transform_init(&transform, context, device_id); + } + + void deinit_transform() { + transform_destroy(&transform); + CL_CHECK(clReleaseMemObject(v_cl)); + CL_CHECK(clReleaseMemObject(u_cl)); + CL_CHECK(clReleaseMemObject(y_cl)); + } + + void run_transform(cl_mem yuv_cl, int model_width, int model_height, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection) { + transform_queue(&transform, q, + yuv_cl, frame_width, frame_height, frame_stride, frame_uv_offset, + y_cl, u_cl, v_cl, model_width, model_height, projection); + } +}; + +class DrivingModelFrame : public ModelFrame { +public: + DrivingModelFrame(cl_device_id device_id, cl_context context, int _temporal_skip); + ~DrivingModelFrame(); + cl_mem* prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection); + + const int MODEL_WIDTH = 512; + const int MODEL_HEIGHT = 256; + const int MODEL_FRAME_SIZE = MODEL_WIDTH * MODEL_HEIGHT * 3 / 2; + const int buf_size = MODEL_FRAME_SIZE * 2; // 2 frames are temporal_skip frames apart + const size_t frame_size_bytes = MODEL_FRAME_SIZE * sizeof(uint8_t); + +private: + LoadYUVState loadyuv; + cl_mem img_buffer_20hz_cl, last_img_cl, input_frames_cl; + cl_buffer_region region; + int temporal_skip; +}; + +class MonitoringModelFrame : public ModelFrame { +public: + MonitoringModelFrame(cl_device_id device_id, cl_context context); + ~MonitoringModelFrame(); + cl_mem* prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection); + + const int MODEL_WIDTH = 1440; + const int MODEL_HEIGHT = 960; + const int MODEL_FRAME_SIZE = MODEL_WIDTH * MODEL_HEIGHT; + const int buf_size = MODEL_FRAME_SIZE; + +private: + cl_mem input_frame_cl; +}; diff --git a/selfdrive/modeld/models/commonmodel.pxd b/selfdrive/modeld/models/commonmodel.pxd new file mode 100644 index 000000000..4ac64d917 --- /dev/null +++ b/selfdrive/modeld/models/commonmodel.pxd @@ -0,0 +1,27 @@ +# distutils: language = c++ + +from msgq.visionipc.visionipc cimport cl_device_id, cl_context, cl_mem + +cdef extern from "common/mat.h": + cdef struct mat3: + float v[9] + +cdef extern from "common/clutil.h": + cdef unsigned long CL_DEVICE_TYPE_DEFAULT + cl_device_id cl_get_device_id(unsigned long) + cl_context cl_create_context(cl_device_id) + void cl_release_context(cl_context) + +cdef extern from "selfdrive/modeld/models/commonmodel.h": + cppclass ModelFrame: + int buf_size + unsigned char * buffer_from_cl(cl_mem*, int); + cl_mem * prepare(cl_mem, int, int, int, int, mat3) + + cppclass DrivingModelFrame: + int buf_size + DrivingModelFrame(cl_device_id, cl_context, int) + + cppclass MonitoringModelFrame: + int buf_size + MonitoringModelFrame(cl_device_id, cl_context) diff --git a/selfdrive/modeld/models/commonmodel_pyx.pxd b/selfdrive/modeld/models/commonmodel_pyx.pxd new file mode 100644 index 000000000..0bb798625 --- /dev/null +++ b/selfdrive/modeld/models/commonmodel_pyx.pxd @@ -0,0 +1,13 @@ +# distutils: language = c++ + +from msgq.visionipc.visionipc cimport cl_mem +from msgq.visionipc.visionipc_pyx cimport CLContext as BaseCLContext + +cdef class CLContext(BaseCLContext): + pass + +cdef class CLMem: + cdef cl_mem * mem + + @staticmethod + cdef create(void*) diff --git a/selfdrive/modeld/models/commonmodel_pyx.pyx b/selfdrive/modeld/models/commonmodel_pyx.pyx new file mode 100644 index 000000000..5b7d11bc7 --- /dev/null +++ b/selfdrive/modeld/models/commonmodel_pyx.pyx @@ -0,0 +1,74 @@ +# distutils: language = c++ +# cython: c_string_encoding=ascii, language_level=3 + +import numpy as np +cimport numpy as cnp +from libc.string cimport memcpy +from libc.stdint cimport uintptr_t + +from msgq.visionipc.visionipc cimport cl_mem +from msgq.visionipc.visionipc_pyx cimport VisionBuf, CLContext as BaseCLContext +from .commonmodel cimport CL_DEVICE_TYPE_DEFAULT, cl_get_device_id, cl_create_context, cl_release_context +from .commonmodel cimport mat3, ModelFrame as cppModelFrame, DrivingModelFrame as cppDrivingModelFrame, MonitoringModelFrame as cppMonitoringModelFrame + + +cdef class CLContext(BaseCLContext): + def __cinit__(self): + self.device_id = cl_get_device_id(CL_DEVICE_TYPE_DEFAULT) + self.context = cl_create_context(self.device_id) + + def __dealloc__(self): + if self.context: + cl_release_context(self.context) + +cdef class CLMem: + @staticmethod + cdef create(void * cmem): + mem = CLMem() + mem.mem = cmem + return mem + + @property + def mem_address(self): + return (self.mem) + +def cl_from_visionbuf(VisionBuf buf): + return CLMem.create(&buf.buf.buf_cl) + + +cdef class ModelFrame: + cdef cppModelFrame * frame + cdef int buf_size + + def __dealloc__(self): + del self.frame + + def prepare(self, VisionBuf buf, float[:] projection): + cdef mat3 cprojection + memcpy(cprojection.v, &projection[0], 9*sizeof(float)) + cdef cl_mem * data + data = self.frame.prepare(buf.buf.buf_cl, buf.width, buf.height, buf.stride, buf.uv_offset, cprojection) + return CLMem.create(data) + + def buffer_from_cl(self, CLMem in_frames): + cdef unsigned char * data2 + data2 = self.frame.buffer_from_cl(in_frames.mem, self.buf_size) + return np.asarray( data2) + + +cdef class DrivingModelFrame(ModelFrame): + cdef cppDrivingModelFrame * _frame + + def __cinit__(self, CLContext context, int temporal_skip): + self._frame = new cppDrivingModelFrame(context.device_id, context.context, temporal_skip) + self.frame = (self._frame) + self.buf_size = self._frame.buf_size + +cdef class MonitoringModelFrame(ModelFrame): + cdef cppMonitoringModelFrame * _frame + + def __cinit__(self, CLContext context): + self._frame = new cppMonitoringModelFrame(context.device_id, context.context) + self.frame = (self._frame) + self.buf_size = self._frame.buf_size + diff --git a/selfdrive/modeld/runners/tinygrad_helpers.py b/selfdrive/modeld/runners/tinygrad_helpers.py new file mode 100644 index 000000000..776381341 --- /dev/null +++ b/selfdrive/modeld/runners/tinygrad_helpers.py @@ -0,0 +1,8 @@ + +from tinygrad.tensor import Tensor +from tinygrad.helpers import to_mv + +def qcom_tensor_from_opencl_address(opencl_address, shape, dtype): + cl_buf_desc_ptr = to_mv(opencl_address, 8).cast('Q')[0] + rawbuf_ptr = to_mv(cl_buf_desc_ptr, 0x100).cast('Q')[20] # offset 0xA0 is a raw gpu pointer. + return Tensor.from_blob(rawbuf_ptr, shape, dtype=dtype, device='QCOM') diff --git a/selfdrive/modeld/transforms/loadyuv.cc b/selfdrive/modeld/transforms/loadyuv.cc new file mode 100644 index 000000000..c93f5cd03 --- /dev/null +++ b/selfdrive/modeld/transforms/loadyuv.cc @@ -0,0 +1,76 @@ +#include "selfdrive/modeld/transforms/loadyuv.h" + +#include +#include +#include + +void loadyuv_init(LoadYUVState* s, cl_context ctx, cl_device_id device_id, int width, int height) { + memset(s, 0, sizeof(*s)); + + s->width = width; + s->height = height; + + char args[1024]; + snprintf(args, sizeof(args), + "-cl-fast-relaxed-math -cl-denorms-are-zero " + "-DTRANSFORMED_WIDTH=%d -DTRANSFORMED_HEIGHT=%d", + width, height); + cl_program prg = cl_program_from_file(ctx, device_id, LOADYUV_PATH, args); + + s->loadys_krnl = CL_CHECK_ERR(clCreateKernel(prg, "loadys", &err)); + s->loaduv_krnl = CL_CHECK_ERR(clCreateKernel(prg, "loaduv", &err)); + s->copy_krnl = CL_CHECK_ERR(clCreateKernel(prg, "copy", &err)); + + // done with this + CL_CHECK(clReleaseProgram(prg)); +} + +void loadyuv_destroy(LoadYUVState* s) { + CL_CHECK(clReleaseKernel(s->loadys_krnl)); + CL_CHECK(clReleaseKernel(s->loaduv_krnl)); + CL_CHECK(clReleaseKernel(s->copy_krnl)); +} + +void loadyuv_queue(LoadYUVState* s, cl_command_queue q, + cl_mem y_cl, cl_mem u_cl, cl_mem v_cl, + cl_mem out_cl) { + cl_int global_out_off = 0; + + CL_CHECK(clSetKernelArg(s->loadys_krnl, 0, sizeof(cl_mem), &y_cl)); + CL_CHECK(clSetKernelArg(s->loadys_krnl, 1, sizeof(cl_mem), &out_cl)); + CL_CHECK(clSetKernelArg(s->loadys_krnl, 2, sizeof(cl_int), &global_out_off)); + + const size_t loadys_work_size = (s->width*s->height)/8; + CL_CHECK(clEnqueueNDRangeKernel(q, s->loadys_krnl, 1, NULL, + &loadys_work_size, NULL, 0, 0, NULL)); + + const size_t loaduv_work_size = ((s->width/2)*(s->height/2))/8; + global_out_off += (s->width*s->height); + + CL_CHECK(clSetKernelArg(s->loaduv_krnl, 0, sizeof(cl_mem), &u_cl)); + CL_CHECK(clSetKernelArg(s->loaduv_krnl, 1, sizeof(cl_mem), &out_cl)); + CL_CHECK(clSetKernelArg(s->loaduv_krnl, 2, sizeof(cl_int), &global_out_off)); + + CL_CHECK(clEnqueueNDRangeKernel(q, s->loaduv_krnl, 1, NULL, + &loaduv_work_size, NULL, 0, 0, NULL)); + + global_out_off += (s->width/2)*(s->height/2); + + CL_CHECK(clSetKernelArg(s->loaduv_krnl, 0, sizeof(cl_mem), &v_cl)); + CL_CHECK(clSetKernelArg(s->loaduv_krnl, 1, sizeof(cl_mem), &out_cl)); + CL_CHECK(clSetKernelArg(s->loaduv_krnl, 2, sizeof(cl_int), &global_out_off)); + + CL_CHECK(clEnqueueNDRangeKernel(q, s->loaduv_krnl, 1, NULL, + &loaduv_work_size, NULL, 0, 0, NULL)); +} + +void copy_queue(LoadYUVState* s, cl_command_queue q, cl_mem src, cl_mem dst, + size_t src_offset, size_t dst_offset, size_t size) { + CL_CHECK(clSetKernelArg(s->copy_krnl, 0, sizeof(cl_mem), &src)); + CL_CHECK(clSetKernelArg(s->copy_krnl, 1, sizeof(cl_mem), &dst)); + CL_CHECK(clSetKernelArg(s->copy_krnl, 2, sizeof(cl_int), &src_offset)); + CL_CHECK(clSetKernelArg(s->copy_krnl, 3, sizeof(cl_int), &dst_offset)); + const size_t copy_work_size = size/8; + CL_CHECK(clEnqueueNDRangeKernel(q, s->copy_krnl, 1, NULL, + ©_work_size, NULL, 0, 0, NULL)); +} \ No newline at end of file diff --git a/selfdrive/modeld/transforms/loadyuv.cl b/selfdrive/modeld/transforms/loadyuv.cl new file mode 100644 index 000000000..970187a6d --- /dev/null +++ b/selfdrive/modeld/transforms/loadyuv.cl @@ -0,0 +1,47 @@ +#define UV_SIZE ((TRANSFORMED_WIDTH/2)*(TRANSFORMED_HEIGHT/2)) + +__kernel void loadys(__global uchar8 const * const Y, + __global uchar * out, + int out_offset) +{ + const int gid = get_global_id(0); + const int ois = gid * 8; + const int oy = ois / TRANSFORMED_WIDTH; + const int ox = ois % TRANSFORMED_WIDTH; + + const uchar8 ys = Y[gid]; + + // 02 + // 13 + + __global uchar* outy0; + __global uchar* outy1; + if ((oy & 1) == 0) { + outy0 = out + out_offset; //y0 + outy1 = out + out_offset + UV_SIZE*2; //y2 + } else { + outy0 = out + out_offset + UV_SIZE; //y1 + outy1 = out + out_offset + UV_SIZE*3; //y3 + } + + vstore4(ys.s0246, 0, outy0 + (oy/2) * (TRANSFORMED_WIDTH/2) + ox/2); + vstore4(ys.s1357, 0, outy1 + (oy/2) * (TRANSFORMED_WIDTH/2) + ox/2); +} + +__kernel void loaduv(__global uchar8 const * const in, + __global uchar8 * out, + int out_offset) +{ + const int gid = get_global_id(0); + const uchar8 inv = in[gid]; + out[gid + out_offset / 8] = inv; +} + +__kernel void copy(__global uchar8 * in, + __global uchar8 * out, + int in_offset, + int out_offset) +{ + const int gid = get_global_id(0); + out[gid + out_offset / 8] = in[gid + in_offset / 8]; +} diff --git a/selfdrive/modeld/transforms/loadyuv.h b/selfdrive/modeld/transforms/loadyuv.h new file mode 100644 index 000000000..659059cd2 --- /dev/null +++ b/selfdrive/modeld/transforms/loadyuv.h @@ -0,0 +1,20 @@ +#pragma once + +#include "common/clutil.h" + +typedef struct { + int width, height; + cl_kernel loadys_krnl, loaduv_krnl, copy_krnl; +} LoadYUVState; + +void loadyuv_init(LoadYUVState* s, cl_context ctx, cl_device_id device_id, int width, int height); + +void loadyuv_destroy(LoadYUVState* s); + +void loadyuv_queue(LoadYUVState* s, cl_command_queue q, + cl_mem y_cl, cl_mem u_cl, cl_mem v_cl, + cl_mem out_cl); + + +void copy_queue(LoadYUVState* s, cl_command_queue q, cl_mem src, cl_mem dst, + size_t src_offset, size_t dst_offset, size_t size); \ No newline at end of file diff --git a/selfdrive/modeld/transforms/transform.cc b/selfdrive/modeld/transforms/transform.cc new file mode 100644 index 000000000..305643cf4 --- /dev/null +++ b/selfdrive/modeld/transforms/transform.cc @@ -0,0 +1,97 @@ +#include "selfdrive/modeld/transforms/transform.h" + +#include +#include + +#include "common/clutil.h" + +void transform_init(Transform* s, cl_context ctx, cl_device_id device_id) { + memset(s, 0, sizeof(*s)); + + cl_program prg = cl_program_from_file(ctx, device_id, TRANSFORM_PATH, ""); + s->krnl = CL_CHECK_ERR(clCreateKernel(prg, "warpPerspective", &err)); + // done with this + CL_CHECK(clReleaseProgram(prg)); + + s->m_y_cl = CL_CHECK_ERR(clCreateBuffer(ctx, CL_MEM_READ_WRITE, 3*3*sizeof(float), NULL, &err)); + s->m_uv_cl = CL_CHECK_ERR(clCreateBuffer(ctx, CL_MEM_READ_WRITE, 3*3*sizeof(float), NULL, &err)); +} + +void transform_destroy(Transform* s) { + CL_CHECK(clReleaseMemObject(s->m_y_cl)); + CL_CHECK(clReleaseMemObject(s->m_uv_cl)); + CL_CHECK(clReleaseKernel(s->krnl)); +} + +void transform_queue(Transform* s, + cl_command_queue q, + cl_mem in_yuv, int in_width, int in_height, int in_stride, int in_uv_offset, + cl_mem out_y, cl_mem out_u, cl_mem out_v, + int out_width, int out_height, + const mat3& projection) { + const int zero = 0; + + // sampled using pixel center origin + // (because that's how fastcv and opencv does it) + + mat3 projection_y = projection; + + // in and out uv is half the size of y. + mat3 projection_uv = transform_scale_buffer(projection, 0.5); + + CL_CHECK(clEnqueueWriteBuffer(q, s->m_y_cl, CL_TRUE, 0, 3*3*sizeof(float), (void*)projection_y.v, 0, NULL, NULL)); + CL_CHECK(clEnqueueWriteBuffer(q, s->m_uv_cl, CL_TRUE, 0, 3*3*sizeof(float), (void*)projection_uv.v, 0, NULL, NULL)); + + const int in_y_width = in_width; + const int in_y_height = in_height; + const int in_y_px_stride = 1; + const int in_uv_width = in_width/2; + const int in_uv_height = in_height/2; + const int in_uv_px_stride = 2; + const int in_u_offset = in_uv_offset; + const int in_v_offset = in_uv_offset + 1; + + const int out_y_width = out_width; + const int out_y_height = out_height; + const int out_uv_width = out_width/2; + const int out_uv_height = out_height/2; + + CL_CHECK(clSetKernelArg(s->krnl, 0, sizeof(cl_mem), &in_yuv)); // src + CL_CHECK(clSetKernelArg(s->krnl, 1, sizeof(cl_int), &in_stride)); // src_row_stride + CL_CHECK(clSetKernelArg(s->krnl, 2, sizeof(cl_int), &in_y_px_stride)); // src_px_stride + CL_CHECK(clSetKernelArg(s->krnl, 3, sizeof(cl_int), &zero)); // src_offset + CL_CHECK(clSetKernelArg(s->krnl, 4, sizeof(cl_int), &in_y_height)); // src_rows + CL_CHECK(clSetKernelArg(s->krnl, 5, sizeof(cl_int), &in_y_width)); // src_cols + CL_CHECK(clSetKernelArg(s->krnl, 6, sizeof(cl_mem), &out_y)); // dst + CL_CHECK(clSetKernelArg(s->krnl, 7, sizeof(cl_int), &out_y_width)); // dst_row_stride + CL_CHECK(clSetKernelArg(s->krnl, 8, sizeof(cl_int), &zero)); // dst_offset + CL_CHECK(clSetKernelArg(s->krnl, 9, sizeof(cl_int), &out_y_height)); // dst_rows + CL_CHECK(clSetKernelArg(s->krnl, 10, sizeof(cl_int), &out_y_width)); // dst_cols + CL_CHECK(clSetKernelArg(s->krnl, 11, sizeof(cl_mem), &s->m_y_cl)); // M + + const size_t work_size_y[2] = {(size_t)out_y_width, (size_t)out_y_height}; + + CL_CHECK(clEnqueueNDRangeKernel(q, s->krnl, 2, NULL, + (const size_t*)&work_size_y, NULL, 0, 0, NULL)); + + const size_t work_size_uv[2] = {(size_t)out_uv_width, (size_t)out_uv_height}; + + CL_CHECK(clSetKernelArg(s->krnl, 2, sizeof(cl_int), &in_uv_px_stride)); // src_px_stride + CL_CHECK(clSetKernelArg(s->krnl, 3, sizeof(cl_int), &in_u_offset)); // src_offset + CL_CHECK(clSetKernelArg(s->krnl, 4, sizeof(cl_int), &in_uv_height)); // src_rows + CL_CHECK(clSetKernelArg(s->krnl, 5, sizeof(cl_int), &in_uv_width)); // src_cols + CL_CHECK(clSetKernelArg(s->krnl, 6, sizeof(cl_mem), &out_u)); // dst + CL_CHECK(clSetKernelArg(s->krnl, 7, sizeof(cl_int), &out_uv_width)); // dst_row_stride + CL_CHECK(clSetKernelArg(s->krnl, 8, sizeof(cl_int), &zero)); // dst_offset + CL_CHECK(clSetKernelArg(s->krnl, 9, sizeof(cl_int), &out_uv_height)); // dst_rows + CL_CHECK(clSetKernelArg(s->krnl, 10, sizeof(cl_int), &out_uv_width)); // dst_cols + CL_CHECK(clSetKernelArg(s->krnl, 11, sizeof(cl_mem), &s->m_uv_cl)); // M + + CL_CHECK(clEnqueueNDRangeKernel(q, s->krnl, 2, NULL, + (const size_t*)&work_size_uv, NULL, 0, 0, NULL)); + CL_CHECK(clSetKernelArg(s->krnl, 3, sizeof(cl_int), &in_v_offset)); // src_ofset + CL_CHECK(clSetKernelArg(s->krnl, 6, sizeof(cl_mem), &out_v)); // dst + + CL_CHECK(clEnqueueNDRangeKernel(q, s->krnl, 2, NULL, + (const size_t*)&work_size_uv, NULL, 0, 0, NULL)); +} diff --git a/selfdrive/modeld/transforms/transform.cl b/selfdrive/modeld/transforms/transform.cl new file mode 100644 index 000000000..2ca25920c --- /dev/null +++ b/selfdrive/modeld/transforms/transform.cl @@ -0,0 +1,54 @@ +#define INTER_BITS 5 +#define INTER_TAB_SIZE (1 << INTER_BITS) +#define INTER_SCALE 1.f / INTER_TAB_SIZE + +#define INTER_REMAP_COEF_BITS 15 +#define INTER_REMAP_COEF_SCALE (1 << INTER_REMAP_COEF_BITS) + +__kernel void warpPerspective(__global const uchar * src, + int src_row_stride, int src_px_stride, int src_offset, int src_rows, int src_cols, + __global uchar * dst, + int dst_row_stride, int dst_offset, int dst_rows, int dst_cols, + __constant float * M) +{ + int dx = get_global_id(0); + int dy = get_global_id(1); + + if (dx < dst_cols && dy < dst_rows) + { + float X0 = M[0] * dx + M[1] * dy + M[2]; + float Y0 = M[3] * dx + M[4] * dy + M[5]; + float W = M[6] * dx + M[7] * dy + M[8]; + W = W != 0.0f ? INTER_TAB_SIZE / W : 0.0f; + int X = rint(X0 * W), Y = rint(Y0 * W); + + int sx = convert_short_sat(X >> INTER_BITS); + int sy = convert_short_sat(Y >> INTER_BITS); + + short sx_clamp = clamp(sx, 0, src_cols - 1); + short sx_p1_clamp = clamp(sx + 1, 0, src_cols - 1); + short sy_clamp = clamp(sy, 0, src_rows - 1); + short sy_p1_clamp = clamp(sy + 1, 0, src_rows - 1); + int v0 = convert_int(src[mad24(sy_clamp, src_row_stride, src_offset + sx_clamp*src_px_stride)]); + int v1 = convert_int(src[mad24(sy_clamp, src_row_stride, src_offset + sx_p1_clamp*src_px_stride)]); + int v2 = convert_int(src[mad24(sy_p1_clamp, src_row_stride, src_offset + sx_clamp*src_px_stride)]); + int v3 = convert_int(src[mad24(sy_p1_clamp, src_row_stride, src_offset + sx_p1_clamp*src_px_stride)]); + + short ay = (short)(Y & (INTER_TAB_SIZE - 1)); + short ax = (short)(X & (INTER_TAB_SIZE - 1)); + float taby = 1.f/INTER_TAB_SIZE*ay; + float tabx = 1.f/INTER_TAB_SIZE*ax; + + int dst_index = mad24(dy, dst_row_stride, dst_offset + dx); + + int itab0 = convert_short_sat_rte( (1.0f-taby)*(1.0f-tabx) * INTER_REMAP_COEF_SCALE ); + int itab1 = convert_short_sat_rte( (1.0f-taby)*tabx * INTER_REMAP_COEF_SCALE ); + int itab2 = convert_short_sat_rte( taby*(1.0f-tabx) * INTER_REMAP_COEF_SCALE ); + int itab3 = convert_short_sat_rte( taby*tabx * INTER_REMAP_COEF_SCALE ); + + int val = v0 * itab0 + v1 * itab1 + v2 * itab2 + v3 * itab3; + + uchar pix = convert_uchar_sat((val + (1 << (INTER_REMAP_COEF_BITS-1))) >> INTER_REMAP_COEF_BITS); + dst[dst_index] = pix; + } +} diff --git a/selfdrive/modeld/transforms/transform.h b/selfdrive/modeld/transforms/transform.h new file mode 100644 index 000000000..771a7054b --- /dev/null +++ b/selfdrive/modeld/transforms/transform.h @@ -0,0 +1,25 @@ +#pragma once + +#define CL_USE_DEPRECATED_OPENCL_1_2_APIS +#ifdef __APPLE__ +#include +#else +#include +#endif + +#include "common/mat.h" + +typedef struct { + cl_kernel krnl; + cl_mem m_y_cl, m_uv_cl; +} Transform; + +void transform_init(Transform* s, cl_context ctx, cl_device_id device_id); + +void transform_destroy(Transform* transform); + +void transform_queue(Transform* s, cl_command_queue q, + cl_mem yuv, int in_width, int in_height, int in_stride, int in_uv_offset, + cl_mem out_y, cl_mem out_u, cl_mem out_v, + int out_width, int out_height, + const mat3& projection); diff --git a/selfdrive/test/process_replay/model_replay.py b/selfdrive/test/process_replay/model_replay.py index 3f671f610..d35474f37 100755 --- a/selfdrive/test/process_replay/model_replay.py +++ b/selfdrive/test/process_replay/model_replay.py @@ -34,8 +34,8 @@ GITHUB = GithubUtils(API_TOKEN, DATA_TOKEN) EXEC_TIMINGS = [ # model, instant max, average max - ("modelV2", 0.05, 0.028), - ("driverStateV2", 0.05, 0.015), + ("modelV2", 0.035, 0.025), + ("driverStateV2", 0.02, 0.015), ] def get_log_fn(test_route, ref="master"): diff --git a/selfdrive/test/process_replay/process_replay.py b/selfdrive/test/process_replay/process_replay.py index 5143334bc..8af72e5f4 100755 --- a/selfdrive/test/process_replay/process_replay.py +++ b/selfdrive/test/process_replay/process_replay.py @@ -4,7 +4,6 @@ import time import copy import heapq import signal -import numpy as np from collections import Counter from dataclasses import dataclass, field from itertools import islice @@ -24,7 +23,6 @@ from openpilot.common.params import Params from openpilot.common.prefix import OpenpilotPrefix from openpilot.common.timeout import Timeout from openpilot.common.realtime import DT_CTRL -from openpilot.system.camerad.cameras.nv12_info import get_nv12_info from openpilot.system.manager.process_config import managed_processes from openpilot.selfdrive.test.process_replay.vision_meta import meta_from_camera_state, available_streams from openpilot.selfdrive.test.process_replay.migration import migrate_all @@ -205,8 +203,7 @@ class ProcessContainer: if meta.camera_state in self.cfg.vision_pubs: assert frs[meta.camera_state].pix_fmt == 'nv12' frame_size = (frs[meta.camera_state].w, frs[meta.camera_state].h) - stride, y_height, _, yuv_size = get_nv12_info(frame_size[0], frame_size[1]) - vipc_server.create_buffers_with_sizes(meta.stream, 2, frame_size[0], frame_size[1], yuv_size, stride, stride * y_height) + vipc_server.create_buffers(meta.stream, 2, *frame_size) vipc_server.start_listener() self.vipc_server = vipc_server @@ -303,15 +300,7 @@ class ProcessContainer: camera_meta = meta_from_camera_state(m.which()) assert frs is not None img = frs[m.which()].get(camera_state.frameId) - - h, w = frs[m.which()].h, frs[m.which()].w - stride, y_height, _, yuv_size = get_nv12_info(w, h) - uv_offset = stride * y_height - padded_img = np.zeros((yuv_size), dtype=np.uint8).reshape((-1, stride)) - padded_img[:h, :w] = img[:h * w].reshape((-1, w)) - padded_img[uv_offset // stride:uv_offset // stride + h // 2, :w] = img[h * w:].reshape((-1, w)) - - self.vipc_server.send(camera_meta.stream, padded_img.flatten().tobytes(), + self.vipc_server.send(camera_meta.stream, img.flatten().tobytes(), camera_state.frameId, camera_state.timestampSof, camera_state.timestampEof) self.msg_queue = [] diff --git a/system/camerad/SConscript b/system/camerad/SConscript index c28330b32..e288c6d8b 100644 --- a/system/camerad/SConscript +++ b/system/camerad/SConscript @@ -1,6 +1,6 @@ Import('env', 'arch', 'messaging', 'common', 'visionipc') -libs = [common, messaging, visionipc] +libs = [common, 'OpenCL', messaging, visionipc] if arch != "Darwin": camera_obj = env.Object(['cameras/camera_qcom2.cc', 'cameras/camera_common.cc', 'cameras/spectra.cc', diff --git a/system/camerad/cameras/camera_common.cc b/system/camerad/cameras/camera_common.cc index 329192b63..88bca7f77 100644 --- a/system/camerad/cameras/camera_common.cc +++ b/system/camerad/cameras/camera_common.cc @@ -7,7 +7,7 @@ #include "system/camerad/cameras/spectra.h" -void CameraBuf::init(SpectraCamera *cam, VisionIpcServer * v, int frame_cnt, VisionStreamType type) { +void CameraBuf::init(cl_device_id device_id, cl_context context, SpectraCamera *cam, VisionIpcServer * v, int frame_cnt, VisionStreamType type) { vipc_server = v; stream_type = type; frame_buf_count = frame_cnt; @@ -21,8 +21,9 @@ void CameraBuf::init(SpectraCamera *cam, VisionIpcServer * v, int frame_cnt, Vis const int raw_frame_size = (sensor->frame_height + sensor->extra_height) * sensor->frame_stride; for (int i = 0; i < frame_buf_count; i++) { camera_bufs_raw[i].allocate(raw_frame_size); + camera_bufs_raw[i].init_cl(device_id, context); } - LOGD("allocated %d buffers", frame_buf_count); + LOGD("allocated %d CL buffers", frame_buf_count); } vipc_server->create_buffers_with_sizes(stream_type, VIPC_BUFFER_COUNT, out_img_width, out_img_height, cam->yuv_size, cam->stride, cam->uv_offset); diff --git a/system/camerad/cameras/camera_common.h b/system/camerad/cameras/camera_common.h index 7f35e06a8..c26859cbc 100644 --- a/system/camerad/cameras/camera_common.h +++ b/system/camerad/cameras/camera_common.h @@ -36,7 +36,7 @@ public: CameraBuf() = default; ~CameraBuf(); - void init(SpectraCamera *cam, VisionIpcServer * v, int frame_cnt, VisionStreamType type); + void init(cl_device_id device_id, cl_context context, SpectraCamera *cam, VisionIpcServer * v, int frame_cnt, VisionStreamType type); void sendFrameToVipc(); }; diff --git a/system/camerad/cameras/camera_qcom2.cc b/system/camerad/cameras/camera_qcom2.cc index 6a7f599ab..d741e13cf 100644 --- a/system/camerad/cameras/camera_qcom2.cc +++ b/system/camerad/cameras/camera_qcom2.cc @@ -12,8 +12,16 @@ #include #include +#ifdef __TICI__ +#include "CL/cl_ext_qcom.h" +#else +#define CL_PRIORITY_HINT_HIGH_QCOM NULL +#define CL_CONTEXT_PRIORITY_HINT_QCOM NULL +#endif + #include "media/cam_sensor_cmn_header.h" +#include "common/clutil.h" #include "common/params.h" #include "common/swaglog.h" @@ -49,7 +57,7 @@ public: CameraState(SpectraMaster *master, const CameraConfig &config) : camera(master, config) {}; ~CameraState(); - void init(VisionIpcServer *v); + void init(VisionIpcServer *v, cl_device_id device_id, cl_context ctx); void update_exposure_score(float desired_ev, int exp_t, int exp_g_idx, float exp_gain); void set_camera_exposure(float grey_frac); void set_exposure_rect(); @@ -60,8 +68,8 @@ public: } }; -void CameraState::init(VisionIpcServer *v) { - camera.camera_open(v); +void CameraState::init(VisionIpcServer *v, cl_device_id device_id, cl_context ctx) { + camera.camera_open(v, device_id, ctx); if (!camera.enabled) return; @@ -249,7 +257,11 @@ void CameraState::sendState() { void camerad_thread() { // TODO: centralize enabled handling - VisionIpcServer v("camerad"); + cl_device_id device_id = cl_get_device_id(CL_DEVICE_TYPE_DEFAULT); + const cl_context_properties props[] = {CL_CONTEXT_PRIORITY_HINT_QCOM, CL_PRIORITY_HINT_HIGH_QCOM, 0}; + cl_context ctx = CL_CHECK_ERR(clCreateContext(props, 1, &device_id, NULL, NULL, &err)); + + VisionIpcServer v("camerad", device_id, ctx); // *** initial ISP init *** SpectraMaster m; @@ -259,7 +271,7 @@ void camerad_thread() { std::vector> cams; for (const auto &config : ALL_CAMERA_CONFIGS) { auto cam = std::make_unique(&m, config); - cam->init(&v); + cam->init(&v, device_id, ctx); cams.emplace_back(std::move(cam)); } diff --git a/system/camerad/cameras/spectra.cc b/system/camerad/cameras/spectra.cc index 73e0a78da..5c3e7a9d2 100644 --- a/system/camerad/cameras/spectra.cc +++ b/system/camerad/cameras/spectra.cc @@ -274,7 +274,7 @@ int SpectraCamera::clear_req_queue() { return ret; } -void SpectraCamera::camera_open(VisionIpcServer *v) { +void SpectraCamera::camera_open(VisionIpcServer *v, cl_device_id device_id, cl_context ctx) { if (!openSensor()) { return; } @@ -296,7 +296,7 @@ void SpectraCamera::camera_open(VisionIpcServer *v) { linkDevices(); LOGD("camera init %d", cc.camera_num); - buf.init(this, v, ife_buf_depth, cc.stream_type); + buf.init(device_id, ctx, this, v, ife_buf_depth, cc.stream_type); camera_map_bufs(); clearAndRequeue(1); } diff --git a/system/camerad/cameras/spectra.h b/system/camerad/cameras/spectra.h index a02b8a6ca..13cb13f98 100644 --- a/system/camerad/cameras/spectra.h +++ b/system/camerad/cameras/spectra.h @@ -113,7 +113,7 @@ public: SpectraCamera(SpectraMaster *master, const CameraConfig &config); ~SpectraCamera(); - void camera_open(VisionIpcServer *v); + void camera_open(VisionIpcServer *v, cl_device_id device_id, cl_context ctx); bool handle_camera_event(const cam_req_mgr_message *event_data); void camera_close(); void camera_map_bufs(); diff --git a/system/hardware/tici/tests/test_power_draw.py b/system/hardware/tici/tests/test_power_draw.py index c4401c958..a92e4c8de 100644 --- a/system/hardware/tici/tests/test_power_draw.py +++ b/system/hardware/tici/tests/test_power_draw.py @@ -32,7 +32,7 @@ class Proc: PROCS = [ Proc(['camerad'], 1.65, atol=0.4, msgs=['roadCameraState', 'wideRoadCameraState', 'driverCameraState']), - Proc(['modeld'], 1.5, atol=0.2, msgs=['modelV2']), + Proc(['modeld'], 1.24, atol=0.2, msgs=['modelV2']), Proc(['dmonitoringmodeld'], 0.65, atol=0.35, msgs=['driverStateV2']), Proc(['encoderd'], 0.23, msgs=[]), ] diff --git a/system/loggerd/SConscript b/system/loggerd/SConscript index cc8ef7c88..cf169f4dc 100644 --- a/system/loggerd/SConscript +++ b/system/loggerd/SConscript @@ -2,13 +2,16 @@ Import('env', 'arch', 'messaging', 'common', 'visionipc') libs = [common, messaging, visionipc, 'avformat', 'avcodec', 'avutil', - 'yuv', 'pthread', 'zstd'] + 'yuv', 'OpenCL', 'pthread', 'zstd'] src = ['logger.cc', 'zstd_writer.cc', 'video_writer.cc', 'encoder/encoder.cc', 'encoder/v4l_encoder.cc', 'encoder/jpeg_encoder.cc'] if arch != "larch64": src += ['encoder/ffmpeg_encoder.cc'] if arch == "Darwin": + # fix OpenCL + del libs[libs.index('OpenCL')] + env['FRAMEWORKS'] = ['OpenCL'] # exclude v4l del src[src.index('encoder/v4l_encoder.cc')] diff --git a/third_party/opencl/include/CL/cl.h b/third_party/opencl/include/CL/cl.h new file mode 100644 index 000000000..0086319f5 --- /dev/null +++ b/third_party/opencl/include/CL/cl.h @@ -0,0 +1,1452 @@ +/******************************************************************************* + * Copyright (c) 2008-2015 The Khronos Group Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and/or associated documentation files (the + * "Materials"), to deal in the Materials without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Materials, and to + * permit persons to whom the Materials are furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Materials. + * + * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS + * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS + * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT + * https://www.khronos.org/registry/ + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. + ******************************************************************************/ + +#ifndef __OPENCL_CL_H +#define __OPENCL_CL_H + +#ifdef __APPLE__ +#include +#else +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/******************************************************************************/ + +typedef struct _cl_platform_id * cl_platform_id; +typedef struct _cl_device_id * cl_device_id; +typedef struct _cl_context * cl_context; +typedef struct _cl_command_queue * cl_command_queue; +typedef struct _cl_mem * cl_mem; +typedef struct _cl_program * cl_program; +typedef struct _cl_kernel * cl_kernel; +typedef struct _cl_event * cl_event; +typedef struct _cl_sampler * cl_sampler; + +typedef cl_uint cl_bool; /* WARNING! Unlike cl_ types in cl_platform.h, cl_bool is not guaranteed to be the same size as the bool in kernels. */ +typedef cl_ulong cl_bitfield; +typedef cl_bitfield cl_device_type; +typedef cl_uint cl_platform_info; +typedef cl_uint cl_device_info; +typedef cl_bitfield cl_device_fp_config; +typedef cl_uint cl_device_mem_cache_type; +typedef cl_uint cl_device_local_mem_type; +typedef cl_bitfield cl_device_exec_capabilities; +typedef cl_bitfield cl_device_svm_capabilities; +typedef cl_bitfield cl_command_queue_properties; +typedef intptr_t cl_device_partition_property; +typedef cl_bitfield cl_device_affinity_domain; + +typedef intptr_t cl_context_properties; +typedef cl_uint cl_context_info; +typedef cl_bitfield cl_queue_properties; +typedef cl_uint cl_command_queue_info; +typedef cl_uint cl_channel_order; +typedef cl_uint cl_channel_type; +typedef cl_bitfield cl_mem_flags; +typedef cl_bitfield cl_svm_mem_flags; +typedef cl_uint cl_mem_object_type; +typedef cl_uint cl_mem_info; +typedef cl_bitfield cl_mem_migration_flags; +typedef cl_uint cl_image_info; +typedef cl_uint cl_buffer_create_type; +typedef cl_uint cl_addressing_mode; +typedef cl_uint cl_filter_mode; +typedef cl_uint cl_sampler_info; +typedef cl_bitfield cl_map_flags; +typedef intptr_t cl_pipe_properties; +typedef cl_uint cl_pipe_info; +typedef cl_uint cl_program_info; +typedef cl_uint cl_program_build_info; +typedef cl_uint cl_program_binary_type; +typedef cl_int cl_build_status; +typedef cl_uint cl_kernel_info; +typedef cl_uint cl_kernel_arg_info; +typedef cl_uint cl_kernel_arg_address_qualifier; +typedef cl_uint cl_kernel_arg_access_qualifier; +typedef cl_bitfield cl_kernel_arg_type_qualifier; +typedef cl_uint cl_kernel_work_group_info; +typedef cl_uint cl_kernel_sub_group_info; +typedef cl_uint cl_event_info; +typedef cl_uint cl_command_type; +typedef cl_uint cl_profiling_info; +typedef cl_bitfield cl_sampler_properties; +typedef cl_uint cl_kernel_exec_info; + +typedef struct _cl_image_format { + cl_channel_order image_channel_order; + cl_channel_type image_channel_data_type; +} cl_image_format; + +typedef struct _cl_image_desc { + cl_mem_object_type image_type; + size_t image_width; + size_t image_height; + size_t image_depth; + size_t image_array_size; + size_t image_row_pitch; + size_t image_slice_pitch; + cl_uint num_mip_levels; + cl_uint num_samples; +#ifdef __GNUC__ + __extension__ /* Prevents warnings about anonymous union in -pedantic builds */ +#endif + union { + cl_mem buffer; + cl_mem mem_object; + }; +} cl_image_desc; + +typedef struct _cl_buffer_region { + size_t origin; + size_t size; +} cl_buffer_region; + + +/******************************************************************************/ + +/* Error Codes */ +#define CL_SUCCESS 0 +#define CL_DEVICE_NOT_FOUND -1 +#define CL_DEVICE_NOT_AVAILABLE -2 +#define CL_COMPILER_NOT_AVAILABLE -3 +#define CL_MEM_OBJECT_ALLOCATION_FAILURE -4 +#define CL_OUT_OF_RESOURCES -5 +#define CL_OUT_OF_HOST_MEMORY -6 +#define CL_PROFILING_INFO_NOT_AVAILABLE -7 +#define CL_MEM_COPY_OVERLAP -8 +#define CL_IMAGE_FORMAT_MISMATCH -9 +#define CL_IMAGE_FORMAT_NOT_SUPPORTED -10 +#define CL_BUILD_PROGRAM_FAILURE -11 +#define CL_MAP_FAILURE -12 +#define CL_MISALIGNED_SUB_BUFFER_OFFSET -13 +#define CL_EXEC_STATUS_ERROR_FOR_EVENTS_IN_WAIT_LIST -14 +#define CL_COMPILE_PROGRAM_FAILURE -15 +#define CL_LINKER_NOT_AVAILABLE -16 +#define CL_LINK_PROGRAM_FAILURE -17 +#define CL_DEVICE_PARTITION_FAILED -18 +#define CL_KERNEL_ARG_INFO_NOT_AVAILABLE -19 + +#define CL_INVALID_VALUE -30 +#define CL_INVALID_DEVICE_TYPE -31 +#define CL_INVALID_PLATFORM -32 +#define CL_INVALID_DEVICE -33 +#define CL_INVALID_CONTEXT -34 +#define CL_INVALID_QUEUE_PROPERTIES -35 +#define CL_INVALID_COMMAND_QUEUE -36 +#define CL_INVALID_HOST_PTR -37 +#define CL_INVALID_MEM_OBJECT -38 +#define CL_INVALID_IMAGE_FORMAT_DESCRIPTOR -39 +#define CL_INVALID_IMAGE_SIZE -40 +#define CL_INVALID_SAMPLER -41 +#define CL_INVALID_BINARY -42 +#define CL_INVALID_BUILD_OPTIONS -43 +#define CL_INVALID_PROGRAM -44 +#define CL_INVALID_PROGRAM_EXECUTABLE -45 +#define CL_INVALID_KERNEL_NAME -46 +#define CL_INVALID_KERNEL_DEFINITION -47 +#define CL_INVALID_KERNEL -48 +#define CL_INVALID_ARG_INDEX -49 +#define CL_INVALID_ARG_VALUE -50 +#define CL_INVALID_ARG_SIZE -51 +#define CL_INVALID_KERNEL_ARGS -52 +#define CL_INVALID_WORK_DIMENSION -53 +#define CL_INVALID_WORK_GROUP_SIZE -54 +#define CL_INVALID_WORK_ITEM_SIZE -55 +#define CL_INVALID_GLOBAL_OFFSET -56 +#define CL_INVALID_EVENT_WAIT_LIST -57 +#define CL_INVALID_EVENT -58 +#define CL_INVALID_OPERATION -59 +#define CL_INVALID_GL_OBJECT -60 +#define CL_INVALID_BUFFER_SIZE -61 +#define CL_INVALID_MIP_LEVEL -62 +#define CL_INVALID_GLOBAL_WORK_SIZE -63 +#define CL_INVALID_PROPERTY -64 +#define CL_INVALID_IMAGE_DESCRIPTOR -65 +#define CL_INVALID_COMPILER_OPTIONS -66 +#define CL_INVALID_LINKER_OPTIONS -67 +#define CL_INVALID_DEVICE_PARTITION_COUNT -68 +#define CL_INVALID_PIPE_SIZE -69 +#define CL_INVALID_DEVICE_QUEUE -70 + +/* OpenCL Version */ +#define CL_VERSION_1_0 1 +#define CL_VERSION_1_1 1 +#define CL_VERSION_1_2 1 +#define CL_VERSION_2_0 1 +#define CL_VERSION_2_1 1 + +/* cl_bool */ +#define CL_FALSE 0 +#define CL_TRUE 1 +#define CL_BLOCKING CL_TRUE +#define CL_NON_BLOCKING CL_FALSE + +/* cl_platform_info */ +#define CL_PLATFORM_PROFILE 0x0900 +#define CL_PLATFORM_VERSION 0x0901 +#define CL_PLATFORM_NAME 0x0902 +#define CL_PLATFORM_VENDOR 0x0903 +#define CL_PLATFORM_EXTENSIONS 0x0904 +#define CL_PLATFORM_HOST_TIMER_RESOLUTION 0x0905 + +/* cl_device_type - bitfield */ +#define CL_DEVICE_TYPE_DEFAULT (1 << 0) +#define CL_DEVICE_TYPE_CPU (1 << 1) +#define CL_DEVICE_TYPE_GPU (1 << 2) +#define CL_DEVICE_TYPE_ACCELERATOR (1 << 3) +#define CL_DEVICE_TYPE_CUSTOM (1 << 4) +#define CL_DEVICE_TYPE_ALL 0xFFFFFFFF + +/* cl_device_info */ +#define CL_DEVICE_TYPE 0x1000 +#define CL_DEVICE_VENDOR_ID 0x1001 +#define CL_DEVICE_MAX_COMPUTE_UNITS 0x1002 +#define CL_DEVICE_MAX_WORK_ITEM_DIMENSIONS 0x1003 +#define CL_DEVICE_MAX_WORK_GROUP_SIZE 0x1004 +#define CL_DEVICE_MAX_WORK_ITEM_SIZES 0x1005 +#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_CHAR 0x1006 +#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_SHORT 0x1007 +#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_INT 0x1008 +#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_LONG 0x1009 +#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_FLOAT 0x100A +#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_DOUBLE 0x100B +#define CL_DEVICE_MAX_CLOCK_FREQUENCY 0x100C +#define CL_DEVICE_ADDRESS_BITS 0x100D +#define CL_DEVICE_MAX_READ_IMAGE_ARGS 0x100E +#define CL_DEVICE_MAX_WRITE_IMAGE_ARGS 0x100F +#define CL_DEVICE_MAX_MEM_ALLOC_SIZE 0x1010 +#define CL_DEVICE_IMAGE2D_MAX_WIDTH 0x1011 +#define CL_DEVICE_IMAGE2D_MAX_HEIGHT 0x1012 +#define CL_DEVICE_IMAGE3D_MAX_WIDTH 0x1013 +#define CL_DEVICE_IMAGE3D_MAX_HEIGHT 0x1014 +#define CL_DEVICE_IMAGE3D_MAX_DEPTH 0x1015 +#define CL_DEVICE_IMAGE_SUPPORT 0x1016 +#define CL_DEVICE_MAX_PARAMETER_SIZE 0x1017 +#define CL_DEVICE_MAX_SAMPLERS 0x1018 +#define CL_DEVICE_MEM_BASE_ADDR_ALIGN 0x1019 +#define CL_DEVICE_MIN_DATA_TYPE_ALIGN_SIZE 0x101A +#define CL_DEVICE_SINGLE_FP_CONFIG 0x101B +#define CL_DEVICE_GLOBAL_MEM_CACHE_TYPE 0x101C +#define CL_DEVICE_GLOBAL_MEM_CACHELINE_SIZE 0x101D +#define CL_DEVICE_GLOBAL_MEM_CACHE_SIZE 0x101E +#define CL_DEVICE_GLOBAL_MEM_SIZE 0x101F +#define CL_DEVICE_MAX_CONSTANT_BUFFER_SIZE 0x1020 +#define CL_DEVICE_MAX_CONSTANT_ARGS 0x1021 +#define CL_DEVICE_LOCAL_MEM_TYPE 0x1022 +#define CL_DEVICE_LOCAL_MEM_SIZE 0x1023 +#define CL_DEVICE_ERROR_CORRECTION_SUPPORT 0x1024 +#define CL_DEVICE_PROFILING_TIMER_RESOLUTION 0x1025 +#define CL_DEVICE_ENDIAN_LITTLE 0x1026 +#define CL_DEVICE_AVAILABLE 0x1027 +#define CL_DEVICE_COMPILER_AVAILABLE 0x1028 +#define CL_DEVICE_EXECUTION_CAPABILITIES 0x1029 +#define CL_DEVICE_QUEUE_PROPERTIES 0x102A /* deprecated */ +#define CL_DEVICE_QUEUE_ON_HOST_PROPERTIES 0x102A +#define CL_DEVICE_NAME 0x102B +#define CL_DEVICE_VENDOR 0x102C +#define CL_DRIVER_VERSION 0x102D +#define CL_DEVICE_PROFILE 0x102E +#define CL_DEVICE_VERSION 0x102F +#define CL_DEVICE_EXTENSIONS 0x1030 +#define CL_DEVICE_PLATFORM 0x1031 +#define CL_DEVICE_DOUBLE_FP_CONFIG 0x1032 +/* 0x1033 reserved for CL_DEVICE_HALF_FP_CONFIG */ +#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_HALF 0x1034 +#define CL_DEVICE_HOST_UNIFIED_MEMORY 0x1035 /* deprecated */ +#define CL_DEVICE_NATIVE_VECTOR_WIDTH_CHAR 0x1036 +#define CL_DEVICE_NATIVE_VECTOR_WIDTH_SHORT 0x1037 +#define CL_DEVICE_NATIVE_VECTOR_WIDTH_INT 0x1038 +#define CL_DEVICE_NATIVE_VECTOR_WIDTH_LONG 0x1039 +#define CL_DEVICE_NATIVE_VECTOR_WIDTH_FLOAT 0x103A +#define CL_DEVICE_NATIVE_VECTOR_WIDTH_DOUBLE 0x103B +#define CL_DEVICE_NATIVE_VECTOR_WIDTH_HALF 0x103C +#define CL_DEVICE_OPENCL_C_VERSION 0x103D +#define CL_DEVICE_LINKER_AVAILABLE 0x103E +#define CL_DEVICE_BUILT_IN_KERNELS 0x103F +#define CL_DEVICE_IMAGE_MAX_BUFFER_SIZE 0x1040 +#define CL_DEVICE_IMAGE_MAX_ARRAY_SIZE 0x1041 +#define CL_DEVICE_PARENT_DEVICE 0x1042 +#define CL_DEVICE_PARTITION_MAX_SUB_DEVICES 0x1043 +#define CL_DEVICE_PARTITION_PROPERTIES 0x1044 +#define CL_DEVICE_PARTITION_AFFINITY_DOMAIN 0x1045 +#define CL_DEVICE_PARTITION_TYPE 0x1046 +#define CL_DEVICE_REFERENCE_COUNT 0x1047 +#define CL_DEVICE_PREFERRED_INTEROP_USER_SYNC 0x1048 +#define CL_DEVICE_PRINTF_BUFFER_SIZE 0x1049 +#define CL_DEVICE_IMAGE_PITCH_ALIGNMENT 0x104A +#define CL_DEVICE_IMAGE_BASE_ADDRESS_ALIGNMENT 0x104B +#define CL_DEVICE_MAX_READ_WRITE_IMAGE_ARGS 0x104C +#define CL_DEVICE_MAX_GLOBAL_VARIABLE_SIZE 0x104D +#define CL_DEVICE_QUEUE_ON_DEVICE_PROPERTIES 0x104E +#define CL_DEVICE_QUEUE_ON_DEVICE_PREFERRED_SIZE 0x104F +#define CL_DEVICE_QUEUE_ON_DEVICE_MAX_SIZE 0x1050 +#define CL_DEVICE_MAX_ON_DEVICE_QUEUES 0x1051 +#define CL_DEVICE_MAX_ON_DEVICE_EVENTS 0x1052 +#define CL_DEVICE_SVM_CAPABILITIES 0x1053 +#define CL_DEVICE_GLOBAL_VARIABLE_PREFERRED_TOTAL_SIZE 0x1054 +#define CL_DEVICE_MAX_PIPE_ARGS 0x1055 +#define CL_DEVICE_PIPE_MAX_ACTIVE_RESERVATIONS 0x1056 +#define CL_DEVICE_PIPE_MAX_PACKET_SIZE 0x1057 +#define CL_DEVICE_PREFERRED_PLATFORM_ATOMIC_ALIGNMENT 0x1058 +#define CL_DEVICE_PREFERRED_GLOBAL_ATOMIC_ALIGNMENT 0x1059 +#define CL_DEVICE_PREFERRED_LOCAL_ATOMIC_ALIGNMENT 0x105A +#define CL_DEVICE_IL_VERSION 0x105B +#define CL_DEVICE_MAX_NUM_SUB_GROUPS 0x105C +#define CL_DEVICE_SUB_GROUP_INDEPENDENT_FORWARD_PROGRESS 0x105D + +/* cl_device_fp_config - bitfield */ +#define CL_FP_DENORM (1 << 0) +#define CL_FP_INF_NAN (1 << 1) +#define CL_FP_ROUND_TO_NEAREST (1 << 2) +#define CL_FP_ROUND_TO_ZERO (1 << 3) +#define CL_FP_ROUND_TO_INF (1 << 4) +#define CL_FP_FMA (1 << 5) +#define CL_FP_SOFT_FLOAT (1 << 6) +#define CL_FP_CORRECTLY_ROUNDED_DIVIDE_SQRT (1 << 7) + +/* cl_device_mem_cache_type */ +#define CL_NONE 0x0 +#define CL_READ_ONLY_CACHE 0x1 +#define CL_READ_WRITE_CACHE 0x2 + +/* cl_device_local_mem_type */ +#define CL_LOCAL 0x1 +#define CL_GLOBAL 0x2 + +/* cl_device_exec_capabilities - bitfield */ +#define CL_EXEC_KERNEL (1 << 0) +#define CL_EXEC_NATIVE_KERNEL (1 << 1) + +/* cl_command_queue_properties - bitfield */ +#define CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE (1 << 0) +#define CL_QUEUE_PROFILING_ENABLE (1 << 1) +#define CL_QUEUE_ON_DEVICE (1 << 2) +#define CL_QUEUE_ON_DEVICE_DEFAULT (1 << 3) + +/* cl_context_info */ +#define CL_CONTEXT_REFERENCE_COUNT 0x1080 +#define CL_CONTEXT_DEVICES 0x1081 +#define CL_CONTEXT_PROPERTIES 0x1082 +#define CL_CONTEXT_NUM_DEVICES 0x1083 + +/* cl_context_properties */ +#define CL_CONTEXT_PLATFORM 0x1084 +#define CL_CONTEXT_INTEROP_USER_SYNC 0x1085 + +/* cl_device_partition_property */ +#define CL_DEVICE_PARTITION_EQUALLY 0x1086 +#define CL_DEVICE_PARTITION_BY_COUNTS 0x1087 +#define CL_DEVICE_PARTITION_BY_COUNTS_LIST_END 0x0 +#define CL_DEVICE_PARTITION_BY_AFFINITY_DOMAIN 0x1088 + +/* cl_device_affinity_domain */ +#define CL_DEVICE_AFFINITY_DOMAIN_NUMA (1 << 0) +#define CL_DEVICE_AFFINITY_DOMAIN_L4_CACHE (1 << 1) +#define CL_DEVICE_AFFINITY_DOMAIN_L3_CACHE (1 << 2) +#define CL_DEVICE_AFFINITY_DOMAIN_L2_CACHE (1 << 3) +#define CL_DEVICE_AFFINITY_DOMAIN_L1_CACHE (1 << 4) +#define CL_DEVICE_AFFINITY_DOMAIN_NEXT_PARTITIONABLE (1 << 5) + +/* cl_device_svm_capabilities */ +#define CL_DEVICE_SVM_COARSE_GRAIN_BUFFER (1 << 0) +#define CL_DEVICE_SVM_FINE_GRAIN_BUFFER (1 << 1) +#define CL_DEVICE_SVM_FINE_GRAIN_SYSTEM (1 << 2) +#define CL_DEVICE_SVM_ATOMICS (1 << 3) + +/* cl_command_queue_info */ +#define CL_QUEUE_CONTEXT 0x1090 +#define CL_QUEUE_DEVICE 0x1091 +#define CL_QUEUE_REFERENCE_COUNT 0x1092 +#define CL_QUEUE_PROPERTIES 0x1093 +#define CL_QUEUE_SIZE 0x1094 +#define CL_QUEUE_DEVICE_DEFAULT 0x1095 + +/* cl_mem_flags and cl_svm_mem_flags - bitfield */ +#define CL_MEM_READ_WRITE (1 << 0) +#define CL_MEM_WRITE_ONLY (1 << 1) +#define CL_MEM_READ_ONLY (1 << 2) +#define CL_MEM_USE_HOST_PTR (1 << 3) +#define CL_MEM_ALLOC_HOST_PTR (1 << 4) +#define CL_MEM_COPY_HOST_PTR (1 << 5) +/* reserved (1 << 6) */ +#define CL_MEM_HOST_WRITE_ONLY (1 << 7) +#define CL_MEM_HOST_READ_ONLY (1 << 8) +#define CL_MEM_HOST_NO_ACCESS (1 << 9) +#define CL_MEM_SVM_FINE_GRAIN_BUFFER (1 << 10) /* used by cl_svm_mem_flags only */ +#define CL_MEM_SVM_ATOMICS (1 << 11) /* used by cl_svm_mem_flags only */ +#define CL_MEM_KERNEL_READ_AND_WRITE (1 << 12) + +/* cl_mem_migration_flags - bitfield */ +#define CL_MIGRATE_MEM_OBJECT_HOST (1 << 0) +#define CL_MIGRATE_MEM_OBJECT_CONTENT_UNDEFINED (1 << 1) + +/* cl_channel_order */ +#define CL_R 0x10B0 +#define CL_A 0x10B1 +#define CL_RG 0x10B2 +#define CL_RA 0x10B3 +#define CL_RGB 0x10B4 +#define CL_RGBA 0x10B5 +#define CL_BGRA 0x10B6 +#define CL_ARGB 0x10B7 +#define CL_INTENSITY 0x10B8 +#define CL_LUMINANCE 0x10B9 +#define CL_Rx 0x10BA +#define CL_RGx 0x10BB +#define CL_RGBx 0x10BC +#define CL_DEPTH 0x10BD +#define CL_DEPTH_STENCIL 0x10BE +#define CL_sRGB 0x10BF +#define CL_sRGBx 0x10C0 +#define CL_sRGBA 0x10C1 +#define CL_sBGRA 0x10C2 +#define CL_ABGR 0x10C3 + +/* cl_channel_type */ +#define CL_SNORM_INT8 0x10D0 +#define CL_SNORM_INT16 0x10D1 +#define CL_UNORM_INT8 0x10D2 +#define CL_UNORM_INT16 0x10D3 +#define CL_UNORM_SHORT_565 0x10D4 +#define CL_UNORM_SHORT_555 0x10D5 +#define CL_UNORM_INT_101010 0x10D6 +#define CL_SIGNED_INT8 0x10D7 +#define CL_SIGNED_INT16 0x10D8 +#define CL_SIGNED_INT32 0x10D9 +#define CL_UNSIGNED_INT8 0x10DA +#define CL_UNSIGNED_INT16 0x10DB +#define CL_UNSIGNED_INT32 0x10DC +#define CL_HALF_FLOAT 0x10DD +#define CL_FLOAT 0x10DE +#define CL_UNORM_INT24 0x10DF +#define CL_UNORM_INT_101010_2 0x10E0 + +/* cl_mem_object_type */ +#define CL_MEM_OBJECT_BUFFER 0x10F0 +#define CL_MEM_OBJECT_IMAGE2D 0x10F1 +#define CL_MEM_OBJECT_IMAGE3D 0x10F2 +#define CL_MEM_OBJECT_IMAGE2D_ARRAY 0x10F3 +#define CL_MEM_OBJECT_IMAGE1D 0x10F4 +#define CL_MEM_OBJECT_IMAGE1D_ARRAY 0x10F5 +#define CL_MEM_OBJECT_IMAGE1D_BUFFER 0x10F6 +#define CL_MEM_OBJECT_PIPE 0x10F7 + +/* cl_mem_info */ +#define CL_MEM_TYPE 0x1100 +#define CL_MEM_FLAGS 0x1101 +#define CL_MEM_SIZE 0x1102 +#define CL_MEM_HOST_PTR 0x1103 +#define CL_MEM_MAP_COUNT 0x1104 +#define CL_MEM_REFERENCE_COUNT 0x1105 +#define CL_MEM_CONTEXT 0x1106 +#define CL_MEM_ASSOCIATED_MEMOBJECT 0x1107 +#define CL_MEM_OFFSET 0x1108 +#define CL_MEM_USES_SVM_POINTER 0x1109 + +/* cl_image_info */ +#define CL_IMAGE_FORMAT 0x1110 +#define CL_IMAGE_ELEMENT_SIZE 0x1111 +#define CL_IMAGE_ROW_PITCH 0x1112 +#define CL_IMAGE_SLICE_PITCH 0x1113 +#define CL_IMAGE_WIDTH 0x1114 +#define CL_IMAGE_HEIGHT 0x1115 +#define CL_IMAGE_DEPTH 0x1116 +#define CL_IMAGE_ARRAY_SIZE 0x1117 +#define CL_IMAGE_BUFFER 0x1118 +#define CL_IMAGE_NUM_MIP_LEVELS 0x1119 +#define CL_IMAGE_NUM_SAMPLES 0x111A + +/* cl_pipe_info */ +#define CL_PIPE_PACKET_SIZE 0x1120 +#define CL_PIPE_MAX_PACKETS 0x1121 + +/* cl_addressing_mode */ +#define CL_ADDRESS_NONE 0x1130 +#define CL_ADDRESS_CLAMP_TO_EDGE 0x1131 +#define CL_ADDRESS_CLAMP 0x1132 +#define CL_ADDRESS_REPEAT 0x1133 +#define CL_ADDRESS_MIRRORED_REPEAT 0x1134 + +/* cl_filter_mode */ +#define CL_FILTER_NEAREST 0x1140 +#define CL_FILTER_LINEAR 0x1141 + +/* cl_sampler_info */ +#define CL_SAMPLER_REFERENCE_COUNT 0x1150 +#define CL_SAMPLER_CONTEXT 0x1151 +#define CL_SAMPLER_NORMALIZED_COORDS 0x1152 +#define CL_SAMPLER_ADDRESSING_MODE 0x1153 +#define CL_SAMPLER_FILTER_MODE 0x1154 +#define CL_SAMPLER_MIP_FILTER_MODE 0x1155 +#define CL_SAMPLER_LOD_MIN 0x1156 +#define CL_SAMPLER_LOD_MAX 0x1157 + +/* cl_map_flags - bitfield */ +#define CL_MAP_READ (1 << 0) +#define CL_MAP_WRITE (1 << 1) +#define CL_MAP_WRITE_INVALIDATE_REGION (1 << 2) + +/* cl_program_info */ +#define CL_PROGRAM_REFERENCE_COUNT 0x1160 +#define CL_PROGRAM_CONTEXT 0x1161 +#define CL_PROGRAM_NUM_DEVICES 0x1162 +#define CL_PROGRAM_DEVICES 0x1163 +#define CL_PROGRAM_SOURCE 0x1164 +#define CL_PROGRAM_BINARY_SIZES 0x1165 +#define CL_PROGRAM_BINARIES 0x1166 +#define CL_PROGRAM_NUM_KERNELS 0x1167 +#define CL_PROGRAM_KERNEL_NAMES 0x1168 +#define CL_PROGRAM_IL 0x1169 + +/* cl_program_build_info */ +#define CL_PROGRAM_BUILD_STATUS 0x1181 +#define CL_PROGRAM_BUILD_OPTIONS 0x1182 +#define CL_PROGRAM_BUILD_LOG 0x1183 +#define CL_PROGRAM_BINARY_TYPE 0x1184 +#define CL_PROGRAM_BUILD_GLOBAL_VARIABLE_TOTAL_SIZE 0x1185 + +/* cl_program_binary_type */ +#define CL_PROGRAM_BINARY_TYPE_NONE 0x0 +#define CL_PROGRAM_BINARY_TYPE_COMPILED_OBJECT 0x1 +#define CL_PROGRAM_BINARY_TYPE_LIBRARY 0x2 +#define CL_PROGRAM_BINARY_TYPE_EXECUTABLE 0x4 + +/* cl_build_status */ +#define CL_BUILD_SUCCESS 0 +#define CL_BUILD_NONE -1 +#define CL_BUILD_ERROR -2 +#define CL_BUILD_IN_PROGRESS -3 + +/* cl_kernel_info */ +#define CL_KERNEL_FUNCTION_NAME 0x1190 +#define CL_KERNEL_NUM_ARGS 0x1191 +#define CL_KERNEL_REFERENCE_COUNT 0x1192 +#define CL_KERNEL_CONTEXT 0x1193 +#define CL_KERNEL_PROGRAM 0x1194 +#define CL_KERNEL_ATTRIBUTES 0x1195 +#define CL_KERNEL_MAX_NUM_SUB_GROUPS 0x11B9 +#define CL_KERNEL_COMPILE_NUM_SUB_GROUPS 0x11BA + +/* cl_kernel_arg_info */ +#define CL_KERNEL_ARG_ADDRESS_QUALIFIER 0x1196 +#define CL_KERNEL_ARG_ACCESS_QUALIFIER 0x1197 +#define CL_KERNEL_ARG_TYPE_NAME 0x1198 +#define CL_KERNEL_ARG_TYPE_QUALIFIER 0x1199 +#define CL_KERNEL_ARG_NAME 0x119A + +/* cl_kernel_arg_address_qualifier */ +#define CL_KERNEL_ARG_ADDRESS_GLOBAL 0x119B +#define CL_KERNEL_ARG_ADDRESS_LOCAL 0x119C +#define CL_KERNEL_ARG_ADDRESS_CONSTANT 0x119D +#define CL_KERNEL_ARG_ADDRESS_PRIVATE 0x119E + +/* cl_kernel_arg_access_qualifier */ +#define CL_KERNEL_ARG_ACCESS_READ_ONLY 0x11A0 +#define CL_KERNEL_ARG_ACCESS_WRITE_ONLY 0x11A1 +#define CL_KERNEL_ARG_ACCESS_READ_WRITE 0x11A2 +#define CL_KERNEL_ARG_ACCESS_NONE 0x11A3 + +/* cl_kernel_arg_type_qualifer */ +#define CL_KERNEL_ARG_TYPE_NONE 0 +#define CL_KERNEL_ARG_TYPE_CONST (1 << 0) +#define CL_KERNEL_ARG_TYPE_RESTRICT (1 << 1) +#define CL_KERNEL_ARG_TYPE_VOLATILE (1 << 2) +#define CL_KERNEL_ARG_TYPE_PIPE (1 << 3) + +/* cl_kernel_work_group_info */ +#define CL_KERNEL_WORK_GROUP_SIZE 0x11B0 +#define CL_KERNEL_COMPILE_WORK_GROUP_SIZE 0x11B1 +#define CL_KERNEL_LOCAL_MEM_SIZE 0x11B2 +#define CL_KERNEL_PREFERRED_WORK_GROUP_SIZE_MULTIPLE 0x11B3 +#define CL_KERNEL_PRIVATE_MEM_SIZE 0x11B4 +#define CL_KERNEL_GLOBAL_WORK_SIZE 0x11B5 + +/* cl_kernel_sub_group_info */ +#define CL_KERNEL_MAX_SUB_GROUP_SIZE_FOR_NDRANGE 0x2033 +#define CL_KERNEL_SUB_GROUP_COUNT_FOR_NDRANGE 0x2034 +#define CL_KERNEL_LOCAL_SIZE_FOR_SUB_GROUP_COUNT 0x11B8 + +/* cl_kernel_exec_info */ +#define CL_KERNEL_EXEC_INFO_SVM_PTRS 0x11B6 +#define CL_KERNEL_EXEC_INFO_SVM_FINE_GRAIN_SYSTEM 0x11B7 + +/* cl_event_info */ +#define CL_EVENT_COMMAND_QUEUE 0x11D0 +#define CL_EVENT_COMMAND_TYPE 0x11D1 +#define CL_EVENT_REFERENCE_COUNT 0x11D2 +#define CL_EVENT_COMMAND_EXECUTION_STATUS 0x11D3 +#define CL_EVENT_CONTEXT 0x11D4 + +/* cl_command_type */ +#define CL_COMMAND_NDRANGE_KERNEL 0x11F0 +#define CL_COMMAND_TASK 0x11F1 +#define CL_COMMAND_NATIVE_KERNEL 0x11F2 +#define CL_COMMAND_READ_BUFFER 0x11F3 +#define CL_COMMAND_WRITE_BUFFER 0x11F4 +#define CL_COMMAND_COPY_BUFFER 0x11F5 +#define CL_COMMAND_READ_IMAGE 0x11F6 +#define CL_COMMAND_WRITE_IMAGE 0x11F7 +#define CL_COMMAND_COPY_IMAGE 0x11F8 +#define CL_COMMAND_COPY_IMAGE_TO_BUFFER 0x11F9 +#define CL_COMMAND_COPY_BUFFER_TO_IMAGE 0x11FA +#define CL_COMMAND_MAP_BUFFER 0x11FB +#define CL_COMMAND_MAP_IMAGE 0x11FC +#define CL_COMMAND_UNMAP_MEM_OBJECT 0x11FD +#define CL_COMMAND_MARKER 0x11FE +#define CL_COMMAND_ACQUIRE_GL_OBJECTS 0x11FF +#define CL_COMMAND_RELEASE_GL_OBJECTS 0x1200 +#define CL_COMMAND_READ_BUFFER_RECT 0x1201 +#define CL_COMMAND_WRITE_BUFFER_RECT 0x1202 +#define CL_COMMAND_COPY_BUFFER_RECT 0x1203 +#define CL_COMMAND_USER 0x1204 +#define CL_COMMAND_BARRIER 0x1205 +#define CL_COMMAND_MIGRATE_MEM_OBJECTS 0x1206 +#define CL_COMMAND_FILL_BUFFER 0x1207 +#define CL_COMMAND_FILL_IMAGE 0x1208 +#define CL_COMMAND_SVM_FREE 0x1209 +#define CL_COMMAND_SVM_MEMCPY 0x120A +#define CL_COMMAND_SVM_MEMFILL 0x120B +#define CL_COMMAND_SVM_MAP 0x120C +#define CL_COMMAND_SVM_UNMAP 0x120D + +/* command execution status */ +#define CL_COMPLETE 0x0 +#define CL_RUNNING 0x1 +#define CL_SUBMITTED 0x2 +#define CL_QUEUED 0x3 + +/* cl_buffer_create_type */ +#define CL_BUFFER_CREATE_TYPE_REGION 0x1220 + +/* cl_profiling_info */ +#define CL_PROFILING_COMMAND_QUEUED 0x1280 +#define CL_PROFILING_COMMAND_SUBMIT 0x1281 +#define CL_PROFILING_COMMAND_START 0x1282 +#define CL_PROFILING_COMMAND_END 0x1283 +#define CL_PROFILING_COMMAND_COMPLETE 0x1284 + +/********************************************************************************************************/ + +/* Platform API */ +extern CL_API_ENTRY cl_int CL_API_CALL +clGetPlatformIDs(cl_uint /* num_entries */, + cl_platform_id * /* platforms */, + cl_uint * /* num_platforms */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetPlatformInfo(cl_platform_id /* platform */, + cl_platform_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +/* Device APIs */ +extern CL_API_ENTRY cl_int CL_API_CALL +clGetDeviceIDs(cl_platform_id /* platform */, + cl_device_type /* device_type */, + cl_uint /* num_entries */, + cl_device_id * /* devices */, + cl_uint * /* num_devices */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetDeviceInfo(cl_device_id /* device */, + cl_device_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clCreateSubDevices(cl_device_id /* in_device */, + const cl_device_partition_property * /* properties */, + cl_uint /* num_devices */, + cl_device_id * /* out_devices */, + cl_uint * /* num_devices_ret */) CL_API_SUFFIX__VERSION_1_2; + +extern CL_API_ENTRY cl_int CL_API_CALL +clRetainDevice(cl_device_id /* device */) CL_API_SUFFIX__VERSION_1_2; + +extern CL_API_ENTRY cl_int CL_API_CALL +clReleaseDevice(cl_device_id /* device */) CL_API_SUFFIX__VERSION_1_2; + +extern CL_API_ENTRY cl_int CL_API_CALL +clSetDefaultDeviceCommandQueue(cl_context /* context */, + cl_device_id /* device */, + cl_command_queue /* command_queue */) CL_API_SUFFIX__VERSION_2_1; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetDeviceAndHostTimer(cl_device_id /* device */, + cl_ulong* /* device_timestamp */, + cl_ulong* /* host_timestamp */) CL_API_SUFFIX__VERSION_2_1; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetHostTimer(cl_device_id /* device */, + cl_ulong * /* host_timestamp */) CL_API_SUFFIX__VERSION_2_1; + + +/* Context APIs */ +extern CL_API_ENTRY cl_context CL_API_CALL +clCreateContext(const cl_context_properties * /* properties */, + cl_uint /* num_devices */, + const cl_device_id * /* devices */, + void (CL_CALLBACK * /* pfn_notify */)(const char *, const void *, size_t, void *), + void * /* user_data */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_context CL_API_CALL +clCreateContextFromType(const cl_context_properties * /* properties */, + cl_device_type /* device_type */, + void (CL_CALLBACK * /* pfn_notify*/ )(const char *, const void *, size_t, void *), + void * /* user_data */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clRetainContext(cl_context /* context */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clReleaseContext(cl_context /* context */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetContextInfo(cl_context /* context */, + cl_context_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +/* Command Queue APIs */ +extern CL_API_ENTRY cl_command_queue CL_API_CALL +clCreateCommandQueueWithProperties(cl_context /* context */, + cl_device_id /* device */, + const cl_queue_properties * /* properties */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_2_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clRetainCommandQueue(cl_command_queue /* command_queue */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clReleaseCommandQueue(cl_command_queue /* command_queue */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetCommandQueueInfo(cl_command_queue /* command_queue */, + cl_command_queue_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +/* Memory Object APIs */ +extern CL_API_ENTRY cl_mem CL_API_CALL +clCreateBuffer(cl_context /* context */, + cl_mem_flags /* flags */, + size_t /* size */, + void * /* host_ptr */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_mem CL_API_CALL +clCreateSubBuffer(cl_mem /* buffer */, + cl_mem_flags /* flags */, + cl_buffer_create_type /* buffer_create_type */, + const void * /* buffer_create_info */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_1; + +extern CL_API_ENTRY cl_mem CL_API_CALL +clCreateImage(cl_context /* context */, + cl_mem_flags /* flags */, + const cl_image_format * /* image_format */, + const cl_image_desc * /* image_desc */, + void * /* host_ptr */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_2; + +extern CL_API_ENTRY cl_mem CL_API_CALL +clCreatePipe(cl_context /* context */, + cl_mem_flags /* flags */, + cl_uint /* pipe_packet_size */, + cl_uint /* pipe_max_packets */, + const cl_pipe_properties * /* properties */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_2_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clRetainMemObject(cl_mem /* memobj */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clReleaseMemObject(cl_mem /* memobj */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetSupportedImageFormats(cl_context /* context */, + cl_mem_flags /* flags */, + cl_mem_object_type /* image_type */, + cl_uint /* num_entries */, + cl_image_format * /* image_formats */, + cl_uint * /* num_image_formats */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetMemObjectInfo(cl_mem /* memobj */, + cl_mem_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetImageInfo(cl_mem /* image */, + cl_image_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetPipeInfo(cl_mem /* pipe */, + cl_pipe_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_2_0; + + +extern CL_API_ENTRY cl_int CL_API_CALL +clSetMemObjectDestructorCallback(cl_mem /* memobj */, + void (CL_CALLBACK * /*pfn_notify*/)( cl_mem /* memobj */, void* /*user_data*/), + void * /*user_data */ ) CL_API_SUFFIX__VERSION_1_1; + +/* SVM Allocation APIs */ +extern CL_API_ENTRY void * CL_API_CALL +clSVMAlloc(cl_context /* context */, + cl_svm_mem_flags /* flags */, + size_t /* size */, + cl_uint /* alignment */) CL_API_SUFFIX__VERSION_2_0; + +extern CL_API_ENTRY void CL_API_CALL +clSVMFree(cl_context /* context */, + void * /* svm_pointer */) CL_API_SUFFIX__VERSION_2_0; + +/* Sampler APIs */ +extern CL_API_ENTRY cl_sampler CL_API_CALL +clCreateSamplerWithProperties(cl_context /* context */, + const cl_sampler_properties * /* normalized_coords */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_2_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clRetainSampler(cl_sampler /* sampler */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clReleaseSampler(cl_sampler /* sampler */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetSamplerInfo(cl_sampler /* sampler */, + cl_sampler_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +/* Program Object APIs */ +extern CL_API_ENTRY cl_program CL_API_CALL +clCreateProgramWithSource(cl_context /* context */, + cl_uint /* count */, + const char ** /* strings */, + const size_t * /* lengths */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_program CL_API_CALL +clCreateProgramWithBinary(cl_context /* context */, + cl_uint /* num_devices */, + const cl_device_id * /* device_list */, + const size_t * /* lengths */, + const unsigned char ** /* binaries */, + cl_int * /* binary_status */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_program CL_API_CALL +clCreateProgramWithBuiltInKernels(cl_context /* context */, + cl_uint /* num_devices */, + const cl_device_id * /* device_list */, + const char * /* kernel_names */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_2; + +extern CL_API_ENTRY cl_program CL_API_CALL +clCreateProgramWithIL(cl_context /* context */, + const void* /* il */, + size_t /* length */, + cl_int* /* errcode_ret */) CL_API_SUFFIX__VERSION_2_1; + + +extern CL_API_ENTRY cl_int CL_API_CALL +clRetainProgram(cl_program /* program */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clReleaseProgram(cl_program /* program */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clBuildProgram(cl_program /* program */, + cl_uint /* num_devices */, + const cl_device_id * /* device_list */, + const char * /* options */, + void (CL_CALLBACK * /* pfn_notify */)(cl_program /* program */, void * /* user_data */), + void * /* user_data */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clCompileProgram(cl_program /* program */, + cl_uint /* num_devices */, + const cl_device_id * /* device_list */, + const char * /* options */, + cl_uint /* num_input_headers */, + const cl_program * /* input_headers */, + const char ** /* header_include_names */, + void (CL_CALLBACK * /* pfn_notify */)(cl_program /* program */, void * /* user_data */), + void * /* user_data */) CL_API_SUFFIX__VERSION_1_2; + +extern CL_API_ENTRY cl_program CL_API_CALL +clLinkProgram(cl_context /* context */, + cl_uint /* num_devices */, + const cl_device_id * /* device_list */, + const char * /* options */, + cl_uint /* num_input_programs */, + const cl_program * /* input_programs */, + void (CL_CALLBACK * /* pfn_notify */)(cl_program /* program */, void * /* user_data */), + void * /* user_data */, + cl_int * /* errcode_ret */ ) CL_API_SUFFIX__VERSION_1_2; + + +extern CL_API_ENTRY cl_int CL_API_CALL +clUnloadPlatformCompiler(cl_platform_id /* platform */) CL_API_SUFFIX__VERSION_1_2; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetProgramInfo(cl_program /* program */, + cl_program_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetProgramBuildInfo(cl_program /* program */, + cl_device_id /* device */, + cl_program_build_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +/* Kernel Object APIs */ +extern CL_API_ENTRY cl_kernel CL_API_CALL +clCreateKernel(cl_program /* program */, + const char * /* kernel_name */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clCreateKernelsInProgram(cl_program /* program */, + cl_uint /* num_kernels */, + cl_kernel * /* kernels */, + cl_uint * /* num_kernels_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_kernel CL_API_CALL +clCloneKernel(cl_kernel /* source_kernel */, + cl_int* /* errcode_ret */) CL_API_SUFFIX__VERSION_2_1; + +extern CL_API_ENTRY cl_int CL_API_CALL +clRetainKernel(cl_kernel /* kernel */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clReleaseKernel(cl_kernel /* kernel */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clSetKernelArg(cl_kernel /* kernel */, + cl_uint /* arg_index */, + size_t /* arg_size */, + const void * /* arg_value */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clSetKernelArgSVMPointer(cl_kernel /* kernel */, + cl_uint /* arg_index */, + const void * /* arg_value */) CL_API_SUFFIX__VERSION_2_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clSetKernelExecInfo(cl_kernel /* kernel */, + cl_kernel_exec_info /* param_name */, + size_t /* param_value_size */, + const void * /* param_value */) CL_API_SUFFIX__VERSION_2_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetKernelInfo(cl_kernel /* kernel */, + cl_kernel_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetKernelArgInfo(cl_kernel /* kernel */, + cl_uint /* arg_indx */, + cl_kernel_arg_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_2; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetKernelWorkGroupInfo(cl_kernel /* kernel */, + cl_device_id /* device */, + cl_kernel_work_group_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetKernelSubGroupInfo(cl_kernel /* kernel */, + cl_device_id /* device */, + cl_kernel_sub_group_info /* param_name */, + size_t /* input_value_size */, + const void* /*input_value */, + size_t /* param_value_size */, + void* /* param_value */, + size_t* /* param_value_size_ret */ ) CL_API_SUFFIX__VERSION_2_1; + + +/* Event Object APIs */ +extern CL_API_ENTRY cl_int CL_API_CALL +clWaitForEvents(cl_uint /* num_events */, + const cl_event * /* event_list */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetEventInfo(cl_event /* event */, + cl_event_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_event CL_API_CALL +clCreateUserEvent(cl_context /* context */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_1; + +extern CL_API_ENTRY cl_int CL_API_CALL +clRetainEvent(cl_event /* event */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clReleaseEvent(cl_event /* event */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clSetUserEventStatus(cl_event /* event */, + cl_int /* execution_status */) CL_API_SUFFIX__VERSION_1_1; + +extern CL_API_ENTRY cl_int CL_API_CALL +clSetEventCallback( cl_event /* event */, + cl_int /* command_exec_callback_type */, + void (CL_CALLBACK * /* pfn_notify */)(cl_event, cl_int, void *), + void * /* user_data */) CL_API_SUFFIX__VERSION_1_1; + +/* Profiling APIs */ +extern CL_API_ENTRY cl_int CL_API_CALL +clGetEventProfilingInfo(cl_event /* event */, + cl_profiling_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +/* Flush and Finish APIs */ +extern CL_API_ENTRY cl_int CL_API_CALL +clFlush(cl_command_queue /* command_queue */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clFinish(cl_command_queue /* command_queue */) CL_API_SUFFIX__VERSION_1_0; + +/* Enqueued Commands APIs */ +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueReadBuffer(cl_command_queue /* command_queue */, + cl_mem /* buffer */, + cl_bool /* blocking_read */, + size_t /* offset */, + size_t /* size */, + void * /* ptr */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueReadBufferRect(cl_command_queue /* command_queue */, + cl_mem /* buffer */, + cl_bool /* blocking_read */, + const size_t * /* buffer_offset */, + const size_t * /* host_offset */, + const size_t * /* region */, + size_t /* buffer_row_pitch */, + size_t /* buffer_slice_pitch */, + size_t /* host_row_pitch */, + size_t /* host_slice_pitch */, + void * /* ptr */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_1; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueWriteBuffer(cl_command_queue /* command_queue */, + cl_mem /* buffer */, + cl_bool /* blocking_write */, + size_t /* offset */, + size_t /* size */, + const void * /* ptr */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueWriteBufferRect(cl_command_queue /* command_queue */, + cl_mem /* buffer */, + cl_bool /* blocking_write */, + const size_t * /* buffer_offset */, + const size_t * /* host_offset */, + const size_t * /* region */, + size_t /* buffer_row_pitch */, + size_t /* buffer_slice_pitch */, + size_t /* host_row_pitch */, + size_t /* host_slice_pitch */, + const void * /* ptr */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_1; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueFillBuffer(cl_command_queue /* command_queue */, + cl_mem /* buffer */, + const void * /* pattern */, + size_t /* pattern_size */, + size_t /* offset */, + size_t /* size */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_2; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueCopyBuffer(cl_command_queue /* command_queue */, + cl_mem /* src_buffer */, + cl_mem /* dst_buffer */, + size_t /* src_offset */, + size_t /* dst_offset */, + size_t /* size */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueCopyBufferRect(cl_command_queue /* command_queue */, + cl_mem /* src_buffer */, + cl_mem /* dst_buffer */, + const size_t * /* src_origin */, + const size_t * /* dst_origin */, + const size_t * /* region */, + size_t /* src_row_pitch */, + size_t /* src_slice_pitch */, + size_t /* dst_row_pitch */, + size_t /* dst_slice_pitch */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_1; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueReadImage(cl_command_queue /* command_queue */, + cl_mem /* image */, + cl_bool /* blocking_read */, + const size_t * /* origin[3] */, + const size_t * /* region[3] */, + size_t /* row_pitch */, + size_t /* slice_pitch */, + void * /* ptr */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueWriteImage(cl_command_queue /* command_queue */, + cl_mem /* image */, + cl_bool /* blocking_write */, + const size_t * /* origin[3] */, + const size_t * /* region[3] */, + size_t /* input_row_pitch */, + size_t /* input_slice_pitch */, + const void * /* ptr */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueFillImage(cl_command_queue /* command_queue */, + cl_mem /* image */, + const void * /* fill_color */, + const size_t * /* origin[3] */, + const size_t * /* region[3] */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_2; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueCopyImage(cl_command_queue /* command_queue */, + cl_mem /* src_image */, + cl_mem /* dst_image */, + const size_t * /* src_origin[3] */, + const size_t * /* dst_origin[3] */, + const size_t * /* region[3] */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueCopyImageToBuffer(cl_command_queue /* command_queue */, + cl_mem /* src_image */, + cl_mem /* dst_buffer */, + const size_t * /* src_origin[3] */, + const size_t * /* region[3] */, + size_t /* dst_offset */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueCopyBufferToImage(cl_command_queue /* command_queue */, + cl_mem /* src_buffer */, + cl_mem /* dst_image */, + size_t /* src_offset */, + const size_t * /* dst_origin[3] */, + const size_t * /* region[3] */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY void * CL_API_CALL +clEnqueueMapBuffer(cl_command_queue /* command_queue */, + cl_mem /* buffer */, + cl_bool /* blocking_map */, + cl_map_flags /* map_flags */, + size_t /* offset */, + size_t /* size */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY void * CL_API_CALL +clEnqueueMapImage(cl_command_queue /* command_queue */, + cl_mem /* image */, + cl_bool /* blocking_map */, + cl_map_flags /* map_flags */, + const size_t * /* origin[3] */, + const size_t * /* region[3] */, + size_t * /* image_row_pitch */, + size_t * /* image_slice_pitch */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueUnmapMemObject(cl_command_queue /* command_queue */, + cl_mem /* memobj */, + void * /* mapped_ptr */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueMigrateMemObjects(cl_command_queue /* command_queue */, + cl_uint /* num_mem_objects */, + const cl_mem * /* mem_objects */, + cl_mem_migration_flags /* flags */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_2; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueNDRangeKernel(cl_command_queue /* command_queue */, + cl_kernel /* kernel */, + cl_uint /* work_dim */, + const size_t * /* global_work_offset */, + const size_t * /* global_work_size */, + const size_t * /* local_work_size */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueNativeKernel(cl_command_queue /* command_queue */, + void (CL_CALLBACK * /*user_func*/)(void *), + void * /* args */, + size_t /* cb_args */, + cl_uint /* num_mem_objects */, + const cl_mem * /* mem_list */, + const void ** /* args_mem_loc */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueMarkerWithWaitList(cl_command_queue /* command_queue */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_2; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueBarrierWithWaitList(cl_command_queue /* command_queue */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_2; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueSVMFree(cl_command_queue /* command_queue */, + cl_uint /* num_svm_pointers */, + void *[] /* svm_pointers[] */, + void (CL_CALLBACK * /*pfn_free_func*/)(cl_command_queue /* queue */, + cl_uint /* num_svm_pointers */, + void *[] /* svm_pointers[] */, + void * /* user_data */), + void * /* user_data */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_2_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueSVMMemcpy(cl_command_queue /* command_queue */, + cl_bool /* blocking_copy */, + void * /* dst_ptr */, + const void * /* src_ptr */, + size_t /* size */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_2_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueSVMMemFill(cl_command_queue /* command_queue */, + void * /* svm_ptr */, + const void * /* pattern */, + size_t /* pattern_size */, + size_t /* size */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_2_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueSVMMap(cl_command_queue /* command_queue */, + cl_bool /* blocking_map */, + cl_map_flags /* flags */, + void * /* svm_ptr */, + size_t /* size */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_2_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueSVMUnmap(cl_command_queue /* command_queue */, + void * /* svm_ptr */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_2_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueSVMMigrateMem(cl_command_queue /* command_queue */, + cl_uint /* num_svm_pointers */, + const void ** /* svm_pointers */, + const size_t * /* sizes */, + cl_mem_migration_flags /* flags */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_2_1; + + +/* Extension function access + * + * Returns the extension function address for the given function name, + * or NULL if a valid function can not be found. The client must + * check to make sure the address is not NULL, before using or + * calling the returned function address. + */ +extern CL_API_ENTRY void * CL_API_CALL +clGetExtensionFunctionAddressForPlatform(cl_platform_id /* platform */, + const char * /* func_name */) CL_API_SUFFIX__VERSION_1_2; + + +/* Deprecated OpenCL 1.1 APIs */ +extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_mem CL_API_CALL +clCreateImage2D(cl_context /* context */, + cl_mem_flags /* flags */, + const cl_image_format * /* image_format */, + size_t /* image_width */, + size_t /* image_height */, + size_t /* image_row_pitch */, + void * /* host_ptr */, + cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; + +extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_mem CL_API_CALL +clCreateImage3D(cl_context /* context */, + cl_mem_flags /* flags */, + const cl_image_format * /* image_format */, + size_t /* image_width */, + size_t /* image_height */, + size_t /* image_depth */, + size_t /* image_row_pitch */, + size_t /* image_slice_pitch */, + void * /* host_ptr */, + cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; + +extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_int CL_API_CALL +clEnqueueMarker(cl_command_queue /* command_queue */, + cl_event * /* event */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; + +extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_int CL_API_CALL +clEnqueueWaitForEvents(cl_command_queue /* command_queue */, + cl_uint /* num_events */, + const cl_event * /* event_list */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; + +extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_int CL_API_CALL +clEnqueueBarrier(cl_command_queue /* command_queue */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; + +extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_int CL_API_CALL +clUnloadCompiler(void) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; + +extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED void * CL_API_CALL +clGetExtensionFunctionAddress(const char * /* func_name */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; + +/* Deprecated OpenCL 2.0 APIs */ +extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_2_DEPRECATED cl_command_queue CL_API_CALL +clCreateCommandQueue(cl_context /* context */, + cl_device_id /* device */, + cl_command_queue_properties /* properties */, + cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED; + + +extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_2_DEPRECATED cl_sampler CL_API_CALL +clCreateSampler(cl_context /* context */, + cl_bool /* normalized_coords */, + cl_addressing_mode /* addressing_mode */, + cl_filter_mode /* filter_mode */, + cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED; + +extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_2_DEPRECATED cl_int CL_API_CALL +clEnqueueTask(cl_command_queue /* command_queue */, + cl_kernel /* kernel */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED; + +#ifdef __cplusplus +} +#endif + +#endif /* __OPENCL_CL_H */ + diff --git a/third_party/opencl/include/CL/cl_d3d10.h b/third_party/opencl/include/CL/cl_d3d10.h new file mode 100644 index 000000000..d5960a43f --- /dev/null +++ b/third_party/opencl/include/CL/cl_d3d10.h @@ -0,0 +1,131 @@ +/********************************************************************************** + * Copyright (c) 2008-2015 The Khronos Group Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and/or associated documentation files (the + * "Materials"), to deal in the Materials without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Materials, and to + * permit persons to whom the Materials are furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Materials. + * + * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS + * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS + * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT + * https://www.khronos.org/registry/ + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. + **********************************************************************************/ + +/* $Revision: 11708 $ on $Date: 2010-06-13 23:36:24 -0700 (Sun, 13 Jun 2010) $ */ + +#ifndef __OPENCL_CL_D3D10_H +#define __OPENCL_CL_D3D10_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/****************************************************************************** + * cl_khr_d3d10_sharing */ +#define cl_khr_d3d10_sharing 1 + +typedef cl_uint cl_d3d10_device_source_khr; +typedef cl_uint cl_d3d10_device_set_khr; + +/******************************************************************************/ + +/* Error Codes */ +#define CL_INVALID_D3D10_DEVICE_KHR -1002 +#define CL_INVALID_D3D10_RESOURCE_KHR -1003 +#define CL_D3D10_RESOURCE_ALREADY_ACQUIRED_KHR -1004 +#define CL_D3D10_RESOURCE_NOT_ACQUIRED_KHR -1005 + +/* cl_d3d10_device_source_nv */ +#define CL_D3D10_DEVICE_KHR 0x4010 +#define CL_D3D10_DXGI_ADAPTER_KHR 0x4011 + +/* cl_d3d10_device_set_nv */ +#define CL_PREFERRED_DEVICES_FOR_D3D10_KHR 0x4012 +#define CL_ALL_DEVICES_FOR_D3D10_KHR 0x4013 + +/* cl_context_info */ +#define CL_CONTEXT_D3D10_DEVICE_KHR 0x4014 +#define CL_CONTEXT_D3D10_PREFER_SHARED_RESOURCES_KHR 0x402C + +/* cl_mem_info */ +#define CL_MEM_D3D10_RESOURCE_KHR 0x4015 + +/* cl_image_info */ +#define CL_IMAGE_D3D10_SUBRESOURCE_KHR 0x4016 + +/* cl_command_type */ +#define CL_COMMAND_ACQUIRE_D3D10_OBJECTS_KHR 0x4017 +#define CL_COMMAND_RELEASE_D3D10_OBJECTS_KHR 0x4018 + +/******************************************************************************/ + +typedef CL_API_ENTRY cl_int (CL_API_CALL *clGetDeviceIDsFromD3D10KHR_fn)( + cl_platform_id platform, + cl_d3d10_device_source_khr d3d_device_source, + void * d3d_object, + cl_d3d10_device_set_khr d3d_device_set, + cl_uint num_entries, + cl_device_id * devices, + cl_uint * num_devices) CL_API_SUFFIX__VERSION_1_0; + +typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D10BufferKHR_fn)( + cl_context context, + cl_mem_flags flags, + ID3D10Buffer * resource, + cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_0; + +typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D10Texture2DKHR_fn)( + cl_context context, + cl_mem_flags flags, + ID3D10Texture2D * resource, + UINT subresource, + cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_0; + +typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D10Texture3DKHR_fn)( + cl_context context, + cl_mem_flags flags, + ID3D10Texture3D * resource, + UINT subresource, + cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_0; + +typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueAcquireD3D10ObjectsKHR_fn)( + cl_command_queue command_queue, + cl_uint num_objects, + const cl_mem * mem_objects, + cl_uint num_events_in_wait_list, + const cl_event * event_wait_list, + cl_event * event) CL_API_SUFFIX__VERSION_1_0; + +typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueReleaseD3D10ObjectsKHR_fn)( + cl_command_queue command_queue, + cl_uint num_objects, + const cl_mem * mem_objects, + cl_uint num_events_in_wait_list, + const cl_event * event_wait_list, + cl_event * event) CL_API_SUFFIX__VERSION_1_0; + +#ifdef __cplusplus +} +#endif + +#endif /* __OPENCL_CL_D3D10_H */ + diff --git a/third_party/opencl/include/CL/cl_d3d11.h b/third_party/opencl/include/CL/cl_d3d11.h new file mode 100644 index 000000000..39f907239 --- /dev/null +++ b/third_party/opencl/include/CL/cl_d3d11.h @@ -0,0 +1,131 @@ +/********************************************************************************** + * Copyright (c) 2008-2015 The Khronos Group Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and/or associated documentation files (the + * "Materials"), to deal in the Materials without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Materials, and to + * permit persons to whom the Materials are furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Materials. + * + * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS + * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS + * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT + * https://www.khronos.org/registry/ + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. + **********************************************************************************/ + +/* $Revision: 11708 $ on $Date: 2010-06-13 23:36:24 -0700 (Sun, 13 Jun 2010) $ */ + +#ifndef __OPENCL_CL_D3D11_H +#define __OPENCL_CL_D3D11_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/****************************************************************************** + * cl_khr_d3d11_sharing */ +#define cl_khr_d3d11_sharing 1 + +typedef cl_uint cl_d3d11_device_source_khr; +typedef cl_uint cl_d3d11_device_set_khr; + +/******************************************************************************/ + +/* Error Codes */ +#define CL_INVALID_D3D11_DEVICE_KHR -1006 +#define CL_INVALID_D3D11_RESOURCE_KHR -1007 +#define CL_D3D11_RESOURCE_ALREADY_ACQUIRED_KHR -1008 +#define CL_D3D11_RESOURCE_NOT_ACQUIRED_KHR -1009 + +/* cl_d3d11_device_source */ +#define CL_D3D11_DEVICE_KHR 0x4019 +#define CL_D3D11_DXGI_ADAPTER_KHR 0x401A + +/* cl_d3d11_device_set */ +#define CL_PREFERRED_DEVICES_FOR_D3D11_KHR 0x401B +#define CL_ALL_DEVICES_FOR_D3D11_KHR 0x401C + +/* cl_context_info */ +#define CL_CONTEXT_D3D11_DEVICE_KHR 0x401D +#define CL_CONTEXT_D3D11_PREFER_SHARED_RESOURCES_KHR 0x402D + +/* cl_mem_info */ +#define CL_MEM_D3D11_RESOURCE_KHR 0x401E + +/* cl_image_info */ +#define CL_IMAGE_D3D11_SUBRESOURCE_KHR 0x401F + +/* cl_command_type */ +#define CL_COMMAND_ACQUIRE_D3D11_OBJECTS_KHR 0x4020 +#define CL_COMMAND_RELEASE_D3D11_OBJECTS_KHR 0x4021 + +/******************************************************************************/ + +typedef CL_API_ENTRY cl_int (CL_API_CALL *clGetDeviceIDsFromD3D11KHR_fn)( + cl_platform_id platform, + cl_d3d11_device_source_khr d3d_device_source, + void * d3d_object, + cl_d3d11_device_set_khr d3d_device_set, + cl_uint num_entries, + cl_device_id * devices, + cl_uint * num_devices) CL_API_SUFFIX__VERSION_1_2; + +typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D11BufferKHR_fn)( + cl_context context, + cl_mem_flags flags, + ID3D11Buffer * resource, + cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_2; + +typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D11Texture2DKHR_fn)( + cl_context context, + cl_mem_flags flags, + ID3D11Texture2D * resource, + UINT subresource, + cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_2; + +typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D11Texture3DKHR_fn)( + cl_context context, + cl_mem_flags flags, + ID3D11Texture3D * resource, + UINT subresource, + cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_2; + +typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueAcquireD3D11ObjectsKHR_fn)( + cl_command_queue command_queue, + cl_uint num_objects, + const cl_mem * mem_objects, + cl_uint num_events_in_wait_list, + const cl_event * event_wait_list, + cl_event * event) CL_API_SUFFIX__VERSION_1_2; + +typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueReleaseD3D11ObjectsKHR_fn)( + cl_command_queue command_queue, + cl_uint num_objects, + const cl_mem * mem_objects, + cl_uint num_events_in_wait_list, + const cl_event * event_wait_list, + cl_event * event) CL_API_SUFFIX__VERSION_1_2; + +#ifdef __cplusplus +} +#endif + +#endif /* __OPENCL_CL_D3D11_H */ + diff --git a/third_party/opencl/include/CL/cl_dx9_media_sharing.h b/third_party/opencl/include/CL/cl_dx9_media_sharing.h new file mode 100644 index 000000000..2729e8b9e --- /dev/null +++ b/third_party/opencl/include/CL/cl_dx9_media_sharing.h @@ -0,0 +1,132 @@ +/********************************************************************************** + * Copyright (c) 2008-2015 The Khronos Group Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and/or associated documentation files (the + * "Materials"), to deal in the Materials without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Materials, and to + * permit persons to whom the Materials are furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Materials. + * + * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS + * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS + * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT + * https://www.khronos.org/registry/ + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. + **********************************************************************************/ + +/* $Revision: 11708 $ on $Date: 2010-06-13 23:36:24 -0700 (Sun, 13 Jun 2010) $ */ + +#ifndef __OPENCL_CL_DX9_MEDIA_SHARING_H +#define __OPENCL_CL_DX9_MEDIA_SHARING_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/******************************************************************************/ +/* cl_khr_dx9_media_sharing */ +#define cl_khr_dx9_media_sharing 1 + +typedef cl_uint cl_dx9_media_adapter_type_khr; +typedef cl_uint cl_dx9_media_adapter_set_khr; + +#if defined(_WIN32) +#include +typedef struct _cl_dx9_surface_info_khr +{ + IDirect3DSurface9 *resource; + HANDLE shared_handle; +} cl_dx9_surface_info_khr; +#endif + + +/******************************************************************************/ + +/* Error Codes */ +#define CL_INVALID_DX9_MEDIA_ADAPTER_KHR -1010 +#define CL_INVALID_DX9_MEDIA_SURFACE_KHR -1011 +#define CL_DX9_MEDIA_SURFACE_ALREADY_ACQUIRED_KHR -1012 +#define CL_DX9_MEDIA_SURFACE_NOT_ACQUIRED_KHR -1013 + +/* cl_media_adapter_type_khr */ +#define CL_ADAPTER_D3D9_KHR 0x2020 +#define CL_ADAPTER_D3D9EX_KHR 0x2021 +#define CL_ADAPTER_DXVA_KHR 0x2022 + +/* cl_media_adapter_set_khr */ +#define CL_PREFERRED_DEVICES_FOR_DX9_MEDIA_ADAPTER_KHR 0x2023 +#define CL_ALL_DEVICES_FOR_DX9_MEDIA_ADAPTER_KHR 0x2024 + +/* cl_context_info */ +#define CL_CONTEXT_ADAPTER_D3D9_KHR 0x2025 +#define CL_CONTEXT_ADAPTER_D3D9EX_KHR 0x2026 +#define CL_CONTEXT_ADAPTER_DXVA_KHR 0x2027 + +/* cl_mem_info */ +#define CL_MEM_DX9_MEDIA_ADAPTER_TYPE_KHR 0x2028 +#define CL_MEM_DX9_MEDIA_SURFACE_INFO_KHR 0x2029 + +/* cl_image_info */ +#define CL_IMAGE_DX9_MEDIA_PLANE_KHR 0x202A + +/* cl_command_type */ +#define CL_COMMAND_ACQUIRE_DX9_MEDIA_SURFACES_KHR 0x202B +#define CL_COMMAND_RELEASE_DX9_MEDIA_SURFACES_KHR 0x202C + +/******************************************************************************/ + +typedef CL_API_ENTRY cl_int (CL_API_CALL *clGetDeviceIDsFromDX9MediaAdapterKHR_fn)( + cl_platform_id platform, + cl_uint num_media_adapters, + cl_dx9_media_adapter_type_khr * media_adapter_type, + void * media_adapters, + cl_dx9_media_adapter_set_khr media_adapter_set, + cl_uint num_entries, + cl_device_id * devices, + cl_uint * num_devices) CL_API_SUFFIX__VERSION_1_2; + +typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromDX9MediaSurfaceKHR_fn)( + cl_context context, + cl_mem_flags flags, + cl_dx9_media_adapter_type_khr adapter_type, + void * surface_info, + cl_uint plane, + cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_2; + +typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueAcquireDX9MediaSurfacesKHR_fn)( + cl_command_queue command_queue, + cl_uint num_objects, + const cl_mem * mem_objects, + cl_uint num_events_in_wait_list, + const cl_event * event_wait_list, + cl_event * event) CL_API_SUFFIX__VERSION_1_2; + +typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueReleaseDX9MediaSurfacesKHR_fn)( + cl_command_queue command_queue, + cl_uint num_objects, + const cl_mem * mem_objects, + cl_uint num_events_in_wait_list, + const cl_event * event_wait_list, + cl_event * event) CL_API_SUFFIX__VERSION_1_2; + +#ifdef __cplusplus +} +#endif + +#endif /* __OPENCL_CL_DX9_MEDIA_SHARING_H */ + diff --git a/third_party/opencl/include/CL/cl_egl.h b/third_party/opencl/include/CL/cl_egl.h new file mode 100644 index 000000000..a765bd526 --- /dev/null +++ b/third_party/opencl/include/CL/cl_egl.h @@ -0,0 +1,136 @@ +/******************************************************************************* + * Copyright (c) 2008-2015 The Khronos Group Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and/or associated documentation files (the + * "Materials"), to deal in the Materials without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Materials, and to + * permit persons to whom the Materials are furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Materials. + * + * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS + * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS + * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT + * https://www.khronos.org/registry/ + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. + ******************************************************************************/ + +#ifndef __OPENCL_CL_EGL_H +#define __OPENCL_CL_EGL_H + +#ifdef __APPLE__ + +#else +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + + +/* Command type for events created with clEnqueueAcquireEGLObjectsKHR */ +#define CL_COMMAND_EGL_FENCE_SYNC_OBJECT_KHR 0x202F +#define CL_COMMAND_ACQUIRE_EGL_OBJECTS_KHR 0x202D +#define CL_COMMAND_RELEASE_EGL_OBJECTS_KHR 0x202E + +/* Error type for clCreateFromEGLImageKHR */ +#define CL_INVALID_EGL_OBJECT_KHR -1093 +#define CL_EGL_RESOURCE_NOT_ACQUIRED_KHR -1092 + +/* CLeglImageKHR is an opaque handle to an EGLImage */ +typedef void* CLeglImageKHR; + +/* CLeglDisplayKHR is an opaque handle to an EGLDisplay */ +typedef void* CLeglDisplayKHR; + +/* CLeglSyncKHR is an opaque handle to an EGLSync object */ +typedef void* CLeglSyncKHR; + +/* properties passed to clCreateFromEGLImageKHR */ +typedef intptr_t cl_egl_image_properties_khr; + + +#define cl_khr_egl_image 1 + +extern CL_API_ENTRY cl_mem CL_API_CALL +clCreateFromEGLImageKHR(cl_context /* context */, + CLeglDisplayKHR /* egldisplay */, + CLeglImageKHR /* eglimage */, + cl_mem_flags /* flags */, + const cl_egl_image_properties_khr * /* properties */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; + +typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromEGLImageKHR_fn)( + cl_context context, + CLeglDisplayKHR egldisplay, + CLeglImageKHR eglimage, + cl_mem_flags flags, + const cl_egl_image_properties_khr * properties, + cl_int * errcode_ret); + + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueAcquireEGLObjectsKHR(cl_command_queue /* command_queue */, + cl_uint /* num_objects */, + const cl_mem * /* mem_objects */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + +typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueAcquireEGLObjectsKHR_fn)( + cl_command_queue command_queue, + cl_uint num_objects, + const cl_mem * mem_objects, + cl_uint num_events_in_wait_list, + const cl_event * event_wait_list, + cl_event * event); + + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueReleaseEGLObjectsKHR(cl_command_queue /* command_queue */, + cl_uint /* num_objects */, + const cl_mem * /* mem_objects */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + +typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueReleaseEGLObjectsKHR_fn)( + cl_command_queue command_queue, + cl_uint num_objects, + const cl_mem * mem_objects, + cl_uint num_events_in_wait_list, + const cl_event * event_wait_list, + cl_event * event); + + +#define cl_khr_egl_event 1 + +extern CL_API_ENTRY cl_event CL_API_CALL +clCreateEventFromEGLSyncKHR(cl_context /* context */, + CLeglSyncKHR /* sync */, + CLeglDisplayKHR /* display */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; + +typedef CL_API_ENTRY cl_event (CL_API_CALL *clCreateEventFromEGLSyncKHR_fn)( + cl_context context, + CLeglSyncKHR sync, + CLeglDisplayKHR display, + cl_int * errcode_ret); + +#ifdef __cplusplus +} +#endif + +#endif /* __OPENCL_CL_EGL_H */ diff --git a/third_party/opencl/include/CL/cl_ext.h b/third_party/opencl/include/CL/cl_ext.h new file mode 100644 index 000000000..794158389 --- /dev/null +++ b/third_party/opencl/include/CL/cl_ext.h @@ -0,0 +1,391 @@ +/******************************************************************************* + * Copyright (c) 2008-2015 The Khronos Group Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and/or associated documentation files (the + * "Materials"), to deal in the Materials without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Materials, and to + * permit persons to whom the Materials are furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Materials. + * + * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS + * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS + * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT + * https://www.khronos.org/registry/ + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. + ******************************************************************************/ + +/* $Revision: 11928 $ on $Date: 2010-07-13 09:04:56 -0700 (Tue, 13 Jul 2010) $ */ + +/* cl_ext.h contains OpenCL extensions which don't have external */ +/* (OpenGL, D3D) dependencies. */ + +#ifndef __CL_EXT_H +#define __CL_EXT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __APPLE__ + #include + #include +#else + #include +#endif + +/* cl_khr_fp16 extension - no extension #define since it has no functions */ +#define CL_DEVICE_HALF_FP_CONFIG 0x1033 + +/* Memory object destruction + * + * Apple extension for use to manage externally allocated buffers used with cl_mem objects with CL_MEM_USE_HOST_PTR + * + * Registers a user callback function that will be called when the memory object is deleted and its resources + * freed. Each call to clSetMemObjectCallbackFn registers the specified user callback function on a callback + * stack associated with memobj. The registered user callback functions are called in the reverse order in + * which they were registered. The user callback functions are called and then the memory object is deleted + * and its resources freed. This provides a mechanism for the application (and libraries) using memobj to be + * notified when the memory referenced by host_ptr, specified when the memory object is created and used as + * the storage bits for the memory object, can be reused or freed. + * + * The application may not call CL api's with the cl_mem object passed to the pfn_notify. + * + * Please check for the "cl_APPLE_SetMemObjectDestructor" extension using clGetDeviceInfo(CL_DEVICE_EXTENSIONS) + * before using. + */ +#define cl_APPLE_SetMemObjectDestructor 1 +cl_int CL_API_ENTRY clSetMemObjectDestructorAPPLE( cl_mem /* memobj */, + void (* /*pfn_notify*/)( cl_mem /* memobj */, void* /*user_data*/), + void * /*user_data */ ) CL_EXT_SUFFIX__VERSION_1_0; + + +/* Context Logging Functions + * + * The next three convenience functions are intended to be used as the pfn_notify parameter to clCreateContext(). + * Please check for the "cl_APPLE_ContextLoggingFunctions" extension using clGetDeviceInfo(CL_DEVICE_EXTENSIONS) + * before using. + * + * clLogMessagesToSystemLog fowards on all log messages to the Apple System Logger + */ +#define cl_APPLE_ContextLoggingFunctions 1 +extern void CL_API_ENTRY clLogMessagesToSystemLogAPPLE( const char * /* errstr */, + const void * /* private_info */, + size_t /* cb */, + void * /* user_data */ ) CL_EXT_SUFFIX__VERSION_1_0; + +/* clLogMessagesToStdout sends all log messages to the file descriptor stdout */ +extern void CL_API_ENTRY clLogMessagesToStdoutAPPLE( const char * /* errstr */, + const void * /* private_info */, + size_t /* cb */, + void * /* user_data */ ) CL_EXT_SUFFIX__VERSION_1_0; + +/* clLogMessagesToStderr sends all log messages to the file descriptor stderr */ +extern void CL_API_ENTRY clLogMessagesToStderrAPPLE( const char * /* errstr */, + const void * /* private_info */, + size_t /* cb */, + void * /* user_data */ ) CL_EXT_SUFFIX__VERSION_1_0; + + +/************************ +* cl_khr_icd extension * +************************/ +#define cl_khr_icd 1 + +/* cl_platform_info */ +#define CL_PLATFORM_ICD_SUFFIX_KHR 0x0920 + +/* Additional Error Codes */ +#define CL_PLATFORM_NOT_FOUND_KHR -1001 + +extern CL_API_ENTRY cl_int CL_API_CALL +clIcdGetPlatformIDsKHR(cl_uint /* num_entries */, + cl_platform_id * /* platforms */, + cl_uint * /* num_platforms */); + +typedef CL_API_ENTRY cl_int (CL_API_CALL *clIcdGetPlatformIDsKHR_fn)( + cl_uint /* num_entries */, + cl_platform_id * /* platforms */, + cl_uint * /* num_platforms */); + + +/* Extension: cl_khr_image2D_buffer + * + * This extension allows a 2D image to be created from a cl_mem buffer without a copy. + * The type associated with a 2D image created from a buffer in an OpenCL program is image2d_t. + * Both the sampler and sampler-less read_image built-in functions are supported for 2D images + * and 2D images created from a buffer. Similarly, the write_image built-ins are also supported + * for 2D images created from a buffer. + * + * When the 2D image from buffer is created, the client must specify the width, + * height, image format (i.e. channel order and channel data type) and optionally the row pitch + * + * The pitch specified must be a multiple of CL_DEVICE_IMAGE_PITCH_ALIGNMENT pixels. + * The base address of the buffer must be aligned to CL_DEVICE_IMAGE_BASE_ADDRESS_ALIGNMENT pixels. + */ + +/************************************* + * cl_khr_initalize_memory extension * + *************************************/ + +#define CL_CONTEXT_MEMORY_INITIALIZE_KHR 0x2030 + + +/************************************** + * cl_khr_terminate_context extension * + **************************************/ + +#define CL_DEVICE_TERMINATE_CAPABILITY_KHR 0x2031 +#define CL_CONTEXT_TERMINATE_KHR 0x2032 + +#define cl_khr_terminate_context 1 +extern CL_API_ENTRY cl_int CL_API_CALL clTerminateContextKHR(cl_context /* context */) CL_EXT_SUFFIX__VERSION_1_2; + +typedef CL_API_ENTRY cl_int (CL_API_CALL *clTerminateContextKHR_fn)(cl_context /* context */) CL_EXT_SUFFIX__VERSION_1_2; + + +/* + * Extension: cl_khr_spir + * + * This extension adds support to create an OpenCL program object from a + * Standard Portable Intermediate Representation (SPIR) instance + */ + +#define CL_DEVICE_SPIR_VERSIONS 0x40E0 +#define CL_PROGRAM_BINARY_TYPE_INTERMEDIATE 0x40E1 + + +/****************************************** +* cl_nv_device_attribute_query extension * +******************************************/ +/* cl_nv_device_attribute_query extension - no extension #define since it has no functions */ +#define CL_DEVICE_COMPUTE_CAPABILITY_MAJOR_NV 0x4000 +#define CL_DEVICE_COMPUTE_CAPABILITY_MINOR_NV 0x4001 +#define CL_DEVICE_REGISTERS_PER_BLOCK_NV 0x4002 +#define CL_DEVICE_WARP_SIZE_NV 0x4003 +#define CL_DEVICE_GPU_OVERLAP_NV 0x4004 +#define CL_DEVICE_KERNEL_EXEC_TIMEOUT_NV 0x4005 +#define CL_DEVICE_INTEGRATED_MEMORY_NV 0x4006 + +/********************************* +* cl_amd_device_attribute_query * +*********************************/ +#define CL_DEVICE_PROFILING_TIMER_OFFSET_AMD 0x4036 + +/********************************* +* cl_arm_printf extension +*********************************/ +#define CL_PRINTF_CALLBACK_ARM 0x40B0 +#define CL_PRINTF_BUFFERSIZE_ARM 0x40B1 + +#ifdef CL_VERSION_1_1 + /*********************************** + * cl_ext_device_fission extension * + ***********************************/ + #define cl_ext_device_fission 1 + + extern CL_API_ENTRY cl_int CL_API_CALL + clReleaseDeviceEXT( cl_device_id /*device*/ ) CL_EXT_SUFFIX__VERSION_1_1; + + typedef CL_API_ENTRY cl_int + (CL_API_CALL *clReleaseDeviceEXT_fn)( cl_device_id /*device*/ ) CL_EXT_SUFFIX__VERSION_1_1; + + extern CL_API_ENTRY cl_int CL_API_CALL + clRetainDeviceEXT( cl_device_id /*device*/ ) CL_EXT_SUFFIX__VERSION_1_1; + + typedef CL_API_ENTRY cl_int + (CL_API_CALL *clRetainDeviceEXT_fn)( cl_device_id /*device*/ ) CL_EXT_SUFFIX__VERSION_1_1; + + typedef cl_ulong cl_device_partition_property_ext; + extern CL_API_ENTRY cl_int CL_API_CALL + clCreateSubDevicesEXT( cl_device_id /*in_device*/, + const cl_device_partition_property_ext * /* properties */, + cl_uint /*num_entries*/, + cl_device_id * /*out_devices*/, + cl_uint * /*num_devices*/ ) CL_EXT_SUFFIX__VERSION_1_1; + + typedef CL_API_ENTRY cl_int + ( CL_API_CALL * clCreateSubDevicesEXT_fn)( cl_device_id /*in_device*/, + const cl_device_partition_property_ext * /* properties */, + cl_uint /*num_entries*/, + cl_device_id * /*out_devices*/, + cl_uint * /*num_devices*/ ) CL_EXT_SUFFIX__VERSION_1_1; + + /* cl_device_partition_property_ext */ + #define CL_DEVICE_PARTITION_EQUALLY_EXT 0x4050 + #define CL_DEVICE_PARTITION_BY_COUNTS_EXT 0x4051 + #define CL_DEVICE_PARTITION_BY_NAMES_EXT 0x4052 + #define CL_DEVICE_PARTITION_BY_AFFINITY_DOMAIN_EXT 0x4053 + + /* clDeviceGetInfo selectors */ + #define CL_DEVICE_PARENT_DEVICE_EXT 0x4054 + #define CL_DEVICE_PARTITION_TYPES_EXT 0x4055 + #define CL_DEVICE_AFFINITY_DOMAINS_EXT 0x4056 + #define CL_DEVICE_REFERENCE_COUNT_EXT 0x4057 + #define CL_DEVICE_PARTITION_STYLE_EXT 0x4058 + + /* error codes */ + #define CL_DEVICE_PARTITION_FAILED_EXT -1057 + #define CL_INVALID_PARTITION_COUNT_EXT -1058 + #define CL_INVALID_PARTITION_NAME_EXT -1059 + + /* CL_AFFINITY_DOMAINs */ + #define CL_AFFINITY_DOMAIN_L1_CACHE_EXT 0x1 + #define CL_AFFINITY_DOMAIN_L2_CACHE_EXT 0x2 + #define CL_AFFINITY_DOMAIN_L3_CACHE_EXT 0x3 + #define CL_AFFINITY_DOMAIN_L4_CACHE_EXT 0x4 + #define CL_AFFINITY_DOMAIN_NUMA_EXT 0x10 + #define CL_AFFINITY_DOMAIN_NEXT_FISSIONABLE_EXT 0x100 + + /* cl_device_partition_property_ext list terminators */ + #define CL_PROPERTIES_LIST_END_EXT ((cl_device_partition_property_ext) 0) + #define CL_PARTITION_BY_COUNTS_LIST_END_EXT ((cl_device_partition_property_ext) 0) + #define CL_PARTITION_BY_NAMES_LIST_END_EXT ((cl_device_partition_property_ext) 0 - 1) + +/********************************* +* cl_qcom_ext_host_ptr extension +*********************************/ + +#define CL_MEM_EXT_HOST_PTR_QCOM (1 << 29) + +#define CL_DEVICE_EXT_MEM_PADDING_IN_BYTES_QCOM 0x40A0 +#define CL_DEVICE_PAGE_SIZE_QCOM 0x40A1 +#define CL_IMAGE_ROW_ALIGNMENT_QCOM 0x40A2 +#define CL_IMAGE_SLICE_ALIGNMENT_QCOM 0x40A3 +#define CL_MEM_HOST_UNCACHED_QCOM 0x40A4 +#define CL_MEM_HOST_WRITEBACK_QCOM 0x40A5 +#define CL_MEM_HOST_WRITETHROUGH_QCOM 0x40A6 +#define CL_MEM_HOST_WRITE_COMBINING_QCOM 0x40A7 + +typedef cl_uint cl_image_pitch_info_qcom; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetDeviceImageInfoQCOM(cl_device_id device, + size_t image_width, + size_t image_height, + const cl_image_format *image_format, + cl_image_pitch_info_qcom param_name, + size_t param_value_size, + void *param_value, + size_t *param_value_size_ret); + +typedef struct _cl_mem_ext_host_ptr +{ + /* Type of external memory allocation. */ + /* Legal values will be defined in layered extensions. */ + cl_uint allocation_type; + + /* Host cache policy for this external memory allocation. */ + cl_uint host_cache_policy; + +} cl_mem_ext_host_ptr; + +/********************************* +* cl_qcom_ion_host_ptr extension +*********************************/ + +#define CL_MEM_ION_HOST_PTR_QCOM 0x40A8 + +typedef struct _cl_mem_ion_host_ptr +{ + /* Type of external memory allocation. */ + /* Must be CL_MEM_ION_HOST_PTR_QCOM for ION allocations. */ + cl_mem_ext_host_ptr ext_host_ptr; + + /* ION file descriptor */ + int ion_filedesc; + + /* Host pointer to the ION allocated memory */ + void* ion_hostptr; + +} cl_mem_ion_host_ptr; + +#endif /* CL_VERSION_1_1 */ + + +#ifdef CL_VERSION_2_0 +/********************************* +* cl_khr_sub_groups extension +*********************************/ +#define cl_khr_sub_groups 1 + +typedef cl_uint cl_kernel_sub_group_info_khr; + +/* cl_khr_sub_group_info */ +#define CL_KERNEL_MAX_SUB_GROUP_SIZE_FOR_NDRANGE_KHR 0x2033 +#define CL_KERNEL_SUB_GROUP_COUNT_FOR_NDRANGE_KHR 0x2034 + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetKernelSubGroupInfoKHR(cl_kernel /* in_kernel */, + cl_device_id /*in_device*/, + cl_kernel_sub_group_info_khr /* param_name */, + size_t /*input_value_size*/, + const void * /*input_value*/, + size_t /*param_value_size*/, + void* /*param_value*/, + size_t* /*param_value_size_ret*/ ) CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED; + +typedef CL_API_ENTRY cl_int + ( CL_API_CALL * clGetKernelSubGroupInfoKHR_fn)(cl_kernel /* in_kernel */, + cl_device_id /*in_device*/, + cl_kernel_sub_group_info_khr /* param_name */, + size_t /*input_value_size*/, + const void * /*input_value*/, + size_t /*param_value_size*/, + void* /*param_value*/, + size_t* /*param_value_size_ret*/ ) CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED; +#endif /* CL_VERSION_2_0 */ + +#ifdef CL_VERSION_2_1 +/********************************* +* cl_khr_priority_hints extension +*********************************/ +#define cl_khr_priority_hints 1 + +typedef cl_uint cl_queue_priority_khr; + +/* cl_command_queue_properties */ +#define CL_QUEUE_PRIORITY_KHR 0x1096 + +/* cl_queue_priority_khr */ +#define CL_QUEUE_PRIORITY_HIGH_KHR (1<<0) +#define CL_QUEUE_PRIORITY_MED_KHR (1<<1) +#define CL_QUEUE_PRIORITY_LOW_KHR (1<<2) + +#endif /* CL_VERSION_2_1 */ + +#ifdef CL_VERSION_2_1 +/********************************* +* cl_khr_throttle_hints extension +*********************************/ +#define cl_khr_throttle_hints 1 + +typedef cl_uint cl_queue_throttle_khr; + +/* cl_command_queue_properties */ +#define CL_QUEUE_THROTTLE_KHR 0x1097 + +/* cl_queue_throttle_khr */ +#define CL_QUEUE_THROTTLE_HIGH_KHR (1<<0) +#define CL_QUEUE_THROTTLE_MED_KHR (1<<1) +#define CL_QUEUE_THROTTLE_LOW_KHR (1<<2) + +#endif /* CL_VERSION_2_1 */ + +#ifdef __cplusplus +} +#endif + + +#endif /* __CL_EXT_H */ diff --git a/third_party/opencl/include/CL/cl_ext_qcom.h b/third_party/opencl/include/CL/cl_ext_qcom.h new file mode 100644 index 000000000..6328a1cd9 --- /dev/null +++ b/third_party/opencl/include/CL/cl_ext_qcom.h @@ -0,0 +1,255 @@ +/* Copyright (c) 2009-2017 Qualcomm Technologies, Inc. All Rights Reserved. + * Qualcomm Technologies Proprietary and Confidential. + */ + +#ifndef __OPENCL_CL_EXT_QCOM_H +#define __OPENCL_CL_EXT_QCOM_H + +// Needed by cl_khr_egl_event extension +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +/************************************ + * cl_qcom_create_buffer_from_image * + ************************************/ + +#define CL_BUFFER_FROM_IMAGE_ROW_PITCH_QCOM 0x40C0 +#define CL_BUFFER_FROM_IMAGE_SLICE_PITCH_QCOM 0x40C1 + +extern CL_API_ENTRY cl_mem CL_API_CALL +clCreateBufferFromImageQCOM(cl_mem image, + cl_mem_flags flags, + cl_int *errcode_ret); + + +/************************************ + * cl_qcom_limited_printf extension * + ************************************/ + +/* Builtin printf function buffer size in bytes. */ +#define CL_DEVICE_PRINTF_BUFFER_SIZE_QCOM 0x1049 + + +/************************************* + * cl_qcom_extended_images extension * + *************************************/ + +#define CL_CONTEXT_ENABLE_EXTENDED_IMAGES_QCOM 0x40AA +#define CL_DEVICE_EXTENDED_IMAGE2D_MAX_WIDTH_QCOM 0x40AB +#define CL_DEVICE_EXTENDED_IMAGE2D_MAX_HEIGHT_QCOM 0x40AC +#define CL_DEVICE_EXTENDED_IMAGE3D_MAX_WIDTH_QCOM 0x40AD +#define CL_DEVICE_EXTENDED_IMAGE3D_MAX_HEIGHT_QCOM 0x40AE +#define CL_DEVICE_EXTENDED_IMAGE3D_MAX_DEPTH_QCOM 0x40AF + +/************************************* + * cl_qcom_perf_hint extension * + *************************************/ + +typedef cl_uint cl_perf_hint; + +#define CL_CONTEXT_PERF_HINT_QCOM 0x40C2 + +/*cl_perf_hint*/ +#define CL_PERF_HINT_HIGH_QCOM 0x40C3 +#define CL_PERF_HINT_NORMAL_QCOM 0x40C4 +#define CL_PERF_HINT_LOW_QCOM 0x40C5 + +extern CL_API_ENTRY cl_int CL_API_CALL +clSetPerfHintQCOM(cl_context context, + cl_perf_hint perf_hint); + +// This extension is published at Khronos, so its definitions are made in cl_ext.h. +// This duplication is for backward compatibility. + +#ifndef CL_MEM_ANDROID_NATIVE_BUFFER_HOST_PTR_QCOM + +/********************************* +* cl_qcom_android_native_buffer_host_ptr extension +*********************************/ + +#define CL_MEM_ANDROID_NATIVE_BUFFER_HOST_PTR_QCOM 0x40C6 + + +typedef struct _cl_mem_android_native_buffer_host_ptr +{ + // Type of external memory allocation. + // Must be CL_MEM_ANDROID_NATIVE_BUFFER_HOST_PTR_QCOM for Android native buffers. + cl_mem_ext_host_ptr ext_host_ptr; + + // Virtual pointer to the android native buffer + void* anb_ptr; + +} cl_mem_android_native_buffer_host_ptr; + +#endif //#ifndef CL_MEM_ANDROID_NATIVE_BUFFER_HOST_PTR_QCOM + +/*********************************** +* cl_img_egl_image extension * +************************************/ +typedef void* CLeglImageIMG; +typedef void* CLeglDisplayIMG; + +extern CL_API_ENTRY cl_mem CL_API_CALL +clCreateFromEGLImageIMG(cl_context context, + cl_mem_flags flags, + CLeglImageIMG image, + CLeglDisplayIMG display, + cl_int *errcode_ret); + + +/********************************* +* cl_qcom_other_image extension +*********************************/ + +// Extended flag for creating/querying QCOM non-standard images +#define CL_MEM_OTHER_IMAGE_QCOM (1<<25) + +// cl_channel_type +#define CL_QCOM_UNORM_MIPI10 0x4159 +#define CL_QCOM_UNORM_MIPI12 0x415A +#define CL_QCOM_UNSIGNED_MIPI10 0x415B +#define CL_QCOM_UNSIGNED_MIPI12 0x415C +#define CL_QCOM_UNORM_INT10 0x415D +#define CL_QCOM_UNORM_INT12 0x415E +#define CL_QCOM_UNSIGNED_INT16 0x415F + +// cl_channel_order +// Dedicate 0x4130-0x415F range for QCOM extended image formats +// 0x4130 - 0x4132 range is assigned to pixel-oriented compressed format +#define CL_QCOM_BAYER 0x414E + +#define CL_QCOM_NV12 0x4133 +#define CL_QCOM_NV12_Y 0x4134 +#define CL_QCOM_NV12_UV 0x4135 + +#define CL_QCOM_TILED_NV12 0x4136 +#define CL_QCOM_TILED_NV12_Y 0x4137 +#define CL_QCOM_TILED_NV12_UV 0x4138 + +#define CL_QCOM_P010 0x413C +#define CL_QCOM_P010_Y 0x413D +#define CL_QCOM_P010_UV 0x413E + +#define CL_QCOM_TILED_P010 0x413F +#define CL_QCOM_TILED_P010_Y 0x4140 +#define CL_QCOM_TILED_P010_UV 0x4141 + + +#define CL_QCOM_TP10 0x4145 +#define CL_QCOM_TP10_Y 0x4146 +#define CL_QCOM_TP10_UV 0x4147 + +#define CL_QCOM_TILED_TP10 0x4148 +#define CL_QCOM_TILED_TP10_Y 0x4149 +#define CL_QCOM_TILED_TP10_UV 0x414A + +/********************************* +* cl_qcom_compressed_image extension +*********************************/ + +// Extended flag for creating/querying QCOM non-planar compressed images +#define CL_MEM_COMPRESSED_IMAGE_QCOM (1<<27) + +// Extended image format +// cl_channel_order +#define CL_QCOM_COMPRESSED_RGBA 0x4130 +#define CL_QCOM_COMPRESSED_RGBx 0x4131 + +#define CL_QCOM_COMPRESSED_NV12_Y 0x413A +#define CL_QCOM_COMPRESSED_NV12_UV 0x413B + +#define CL_QCOM_COMPRESSED_P010 0x4142 +#define CL_QCOM_COMPRESSED_P010_Y 0x4143 +#define CL_QCOM_COMPRESSED_P010_UV 0x4144 + +#define CL_QCOM_COMPRESSED_TP10 0x414B +#define CL_QCOM_COMPRESSED_TP10_Y 0x414C +#define CL_QCOM_COMPRESSED_TP10_UV 0x414D + +#define CL_QCOM_COMPRESSED_NV12_4R 0x414F +#define CL_QCOM_COMPRESSED_NV12_4R_Y 0x4150 +#define CL_QCOM_COMPRESSED_NV12_4R_UV 0x4151 +/********************************* +* cl_qcom_compressed_yuv_image_read extension +*********************************/ + +// Extended flag for creating/querying QCOM compressed images +#define CL_MEM_COMPRESSED_YUV_IMAGE_QCOM (1<<28) + +// Extended image format +#define CL_QCOM_COMPRESSED_NV12 0x10C4 + +// Extended flag for setting ION buffer allocation type +#define CL_MEM_ION_HOST_PTR_COMPRESSED_YUV_QCOM 0x40CD +#define CL_MEM_ION_HOST_PTR_PROTECTED_COMPRESSED_YUV_QCOM 0x40CE + +/********************************* +* cl_qcom_accelerated_image_ops +*********************************/ +#define CL_MEM_OBJECT_WEIGHT_IMAGE_QCOM 0x4110 +#define CL_DEVICE_HOF_MAX_NUM_PHASES_QCOM 0x4111 +#define CL_DEVICE_HOF_MAX_FILTER_SIZE_X_QCOM 0x4112 +#define CL_DEVICE_HOF_MAX_FILTER_SIZE_Y_QCOM 0x4113 +#define CL_DEVICE_BLOCK_MATCHING_MAX_REGION_SIZE_X_QCOM 0x4114 +#define CL_DEVICE_BLOCK_MATCHING_MAX_REGION_SIZE_Y_QCOM 0x4115 + +//Extended flag for specifying weight image type +#define CL_WEIGHT_IMAGE_SEPARABLE_QCOM (1<<0) + +// Box Filter +typedef struct _cl_box_filter_size_qcom +{ + // Width of box filter on X direction. + float box_filter_width; + + // Height of box filter on Y direction. + float box_filter_height; +} cl_box_filter_size_qcom; + +// HOF Weight Image Desc +typedef struct _cl_weight_desc_qcom +{ + /** Coordinate of the "center" point of the weight image, + based on the weight image's top-left corner as the origin. */ + size_t center_coord_x; + size_t center_coord_y; + cl_bitfield flags; +} cl_weight_desc_qcom; + +typedef struct _cl_weight_image_desc_qcom +{ + cl_image_desc image_desc; + cl_weight_desc_qcom weight_desc; +} cl_weight_image_desc_qcom; + +/************************************* + * cl_qcom_protected_context extension * + *************************************/ + +#define CL_CONTEXT_PROTECTED_QCOM 0x40C7 +#define CL_MEM_ION_HOST_PTR_PROTECTED_QCOM 0x40C8 + +/************************************* + * cl_qcom_priority_hint extension * + *************************************/ +#define CL_PRIORITY_HINT_NONE_QCOM 0 +typedef cl_uint cl_priority_hint; + +#define CL_CONTEXT_PRIORITY_HINT_QCOM 0x40C9 + +/*cl_priority_hint*/ +#define CL_PRIORITY_HINT_HIGH_QCOM 0x40CA +#define CL_PRIORITY_HINT_NORMAL_QCOM 0x40CB +#define CL_PRIORITY_HINT_LOW_QCOM 0x40CC + +#ifdef __cplusplus +} +#endif + +#endif /* __OPENCL_CL_EXT_QCOM_H */ diff --git a/third_party/opencl/include/CL/cl_gl.h b/third_party/opencl/include/CL/cl_gl.h new file mode 100644 index 000000000..945daa83d --- /dev/null +++ b/third_party/opencl/include/CL/cl_gl.h @@ -0,0 +1,167 @@ +/********************************************************************************** + * Copyright (c) 2008-2015 The Khronos Group Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and/or associated documentation files (the + * "Materials"), to deal in the Materials without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Materials, and to + * permit persons to whom the Materials are furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Materials. + * + * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS + * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS + * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT + * https://www.khronos.org/registry/ + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. + **********************************************************************************/ + +#ifndef __OPENCL_CL_GL_H +#define __OPENCL_CL_GL_H + +#ifdef __APPLE__ +#include +#else +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef cl_uint cl_gl_object_type; +typedef cl_uint cl_gl_texture_info; +typedef cl_uint cl_gl_platform_info; +typedef struct __GLsync *cl_GLsync; + +/* cl_gl_object_type = 0x2000 - 0x200F enum values are currently taken */ +#define CL_GL_OBJECT_BUFFER 0x2000 +#define CL_GL_OBJECT_TEXTURE2D 0x2001 +#define CL_GL_OBJECT_TEXTURE3D 0x2002 +#define CL_GL_OBJECT_RENDERBUFFER 0x2003 +#define CL_GL_OBJECT_TEXTURE2D_ARRAY 0x200E +#define CL_GL_OBJECT_TEXTURE1D 0x200F +#define CL_GL_OBJECT_TEXTURE1D_ARRAY 0x2010 +#define CL_GL_OBJECT_TEXTURE_BUFFER 0x2011 + +/* cl_gl_texture_info */ +#define CL_GL_TEXTURE_TARGET 0x2004 +#define CL_GL_MIPMAP_LEVEL 0x2005 +#define CL_GL_NUM_SAMPLES 0x2012 + + +extern CL_API_ENTRY cl_mem CL_API_CALL +clCreateFromGLBuffer(cl_context /* context */, + cl_mem_flags /* flags */, + cl_GLuint /* bufobj */, + int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_mem CL_API_CALL +clCreateFromGLTexture(cl_context /* context */, + cl_mem_flags /* flags */, + cl_GLenum /* target */, + cl_GLint /* miplevel */, + cl_GLuint /* texture */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_2; + +extern CL_API_ENTRY cl_mem CL_API_CALL +clCreateFromGLRenderbuffer(cl_context /* context */, + cl_mem_flags /* flags */, + cl_GLuint /* renderbuffer */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetGLObjectInfo(cl_mem /* memobj */, + cl_gl_object_type * /* gl_object_type */, + cl_GLuint * /* gl_object_name */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetGLTextureInfo(cl_mem /* memobj */, + cl_gl_texture_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueAcquireGLObjects(cl_command_queue /* command_queue */, + cl_uint /* num_objects */, + const cl_mem * /* mem_objects */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueReleaseGLObjects(cl_command_queue /* command_queue */, + cl_uint /* num_objects */, + const cl_mem * /* mem_objects */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + + +/* Deprecated OpenCL 1.1 APIs */ +extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_mem CL_API_CALL +clCreateFromGLTexture2D(cl_context /* context */, + cl_mem_flags /* flags */, + cl_GLenum /* target */, + cl_GLint /* miplevel */, + cl_GLuint /* texture */, + cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; + +extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_mem CL_API_CALL +clCreateFromGLTexture3D(cl_context /* context */, + cl_mem_flags /* flags */, + cl_GLenum /* target */, + cl_GLint /* miplevel */, + cl_GLuint /* texture */, + cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; + +/* cl_khr_gl_sharing extension */ + +#define cl_khr_gl_sharing 1 + +typedef cl_uint cl_gl_context_info; + +/* Additional Error Codes */ +#define CL_INVALID_GL_SHAREGROUP_REFERENCE_KHR -1000 + +/* cl_gl_context_info */ +#define CL_CURRENT_DEVICE_FOR_GL_CONTEXT_KHR 0x2006 +#define CL_DEVICES_FOR_GL_CONTEXT_KHR 0x2007 + +/* Additional cl_context_properties */ +#define CL_GL_CONTEXT_KHR 0x2008 +#define CL_EGL_DISPLAY_KHR 0x2009 +#define CL_GLX_DISPLAY_KHR 0x200A +#define CL_WGL_HDC_KHR 0x200B +#define CL_CGL_SHAREGROUP_KHR 0x200C + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetGLContextInfoKHR(const cl_context_properties * /* properties */, + cl_gl_context_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +typedef CL_API_ENTRY cl_int (CL_API_CALL *clGetGLContextInfoKHR_fn)( + const cl_context_properties * properties, + cl_gl_context_info param_name, + size_t param_value_size, + void * param_value, + size_t * param_value_size_ret); + +#ifdef __cplusplus +} +#endif + +#endif /* __OPENCL_CL_GL_H */ diff --git a/third_party/opencl/include/CL/cl_gl_ext.h b/third_party/opencl/include/CL/cl_gl_ext.h new file mode 100644 index 000000000..e3c14c640 --- /dev/null +++ b/third_party/opencl/include/CL/cl_gl_ext.h @@ -0,0 +1,74 @@ +/********************************************************************************** + * Copyright (c) 2008-2015 The Khronos Group Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and/or associated documentation files (the + * "Materials"), to deal in the Materials without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Materials, and to + * permit persons to whom the Materials are furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Materials. + * + * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS + * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS + * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT + * https://www.khronos.org/registry/ + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. + **********************************************************************************/ + +/* $Revision: 11708 $ on $Date: 2010-06-13 23:36:24 -0700 (Sun, 13 Jun 2010) $ */ + +/* cl_gl_ext.h contains vendor (non-KHR) OpenCL extensions which have */ +/* OpenGL dependencies. */ + +#ifndef __OPENCL_CL_GL_EXT_H +#define __OPENCL_CL_GL_EXT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __APPLE__ + #include +#else + #include +#endif + +/* + * For each extension, follow this template + * cl_VEN_extname extension */ +/* #define cl_VEN_extname 1 + * ... define new types, if any + * ... define new tokens, if any + * ... define new APIs, if any + * + * If you need GLtypes here, mirror them with a cl_GLtype, rather than including a GL header + * This allows us to avoid having to decide whether to include GL headers or GLES here. + */ + +/* + * cl_khr_gl_event extension + * See section 9.9 in the OpenCL 1.1 spec for more information + */ +#define CL_COMMAND_GL_FENCE_SYNC_OBJECT_KHR 0x200D + +extern CL_API_ENTRY cl_event CL_API_CALL +clCreateEventFromGLsyncKHR(cl_context /* context */, + cl_GLsync /* cl_GLsync */, + cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_1; + +#ifdef __cplusplus +} +#endif + +#endif /* __OPENCL_CL_GL_EXT_H */ diff --git a/third_party/opencl/include/CL/cl_platform.h b/third_party/opencl/include/CL/cl_platform.h new file mode 100644 index 000000000..4e334a291 --- /dev/null +++ b/third_party/opencl/include/CL/cl_platform.h @@ -0,0 +1,1333 @@ +/********************************************************************************** + * Copyright (c) 2008-2015 The Khronos Group Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and/or associated documentation files (the + * "Materials"), to deal in the Materials without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Materials, and to + * permit persons to whom the Materials are furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Materials. + * + * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS + * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS + * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT + * https://www.khronos.org/registry/ + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. + **********************************************************************************/ + +/* $Revision: 11803 $ on $Date: 2010-06-25 10:02:12 -0700 (Fri, 25 Jun 2010) $ */ + +#ifndef __CL_PLATFORM_H +#define __CL_PLATFORM_H + +#ifdef __APPLE__ + /* Contains #defines for AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER below */ + #include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(_WIN32) + #define CL_API_ENTRY + #define CL_API_CALL __stdcall + #define CL_CALLBACK __stdcall +#else + #define CL_API_ENTRY + #define CL_API_CALL + #define CL_CALLBACK +#endif + +/* + * Deprecation flags refer to the last version of the header in which the + * feature was not deprecated. + * + * E.g. VERSION_1_1_DEPRECATED means the feature is present in 1.1 without + * deprecation but is deprecated in versions later than 1.1. + */ + +#ifdef __APPLE__ + #define CL_EXTENSION_WEAK_LINK __attribute__((weak_import)) + #define CL_API_SUFFIX__VERSION_1_0 AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER + #define CL_EXT_SUFFIX__VERSION_1_0 CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER + #define CL_API_SUFFIX__VERSION_1_1 AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER + #define GCL_API_SUFFIX__VERSION_1_1 AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER + #define CL_EXT_SUFFIX__VERSION_1_1 CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER + #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_7 + + #ifdef AVAILABLE_MAC_OS_X_VERSION_10_8_AND_LATER + #define CL_API_SUFFIX__VERSION_1_2 AVAILABLE_MAC_OS_X_VERSION_10_8_AND_LATER + #define GCL_API_SUFFIX__VERSION_1_2 AVAILABLE_MAC_OS_X_VERSION_10_8_AND_LATER + #define CL_EXT_SUFFIX__VERSION_1_2 CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_8_AND_LATER + #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED + #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_8 + #else + #warning This path should never happen outside of internal operating system development. AvailabilityMacros do not function correctly here! + #define CL_API_SUFFIX__VERSION_1_2 AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER + #define GCL_API_SUFFIX__VERSION_1_2 AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER + #define CL_EXT_SUFFIX__VERSION_1_2 CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER + #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER + #endif +#else + #define CL_EXTENSION_WEAK_LINK + #define CL_API_SUFFIX__VERSION_1_0 + #define CL_EXT_SUFFIX__VERSION_1_0 + #define CL_API_SUFFIX__VERSION_1_1 + #define CL_EXT_SUFFIX__VERSION_1_1 + #define CL_API_SUFFIX__VERSION_1_2 + #define CL_EXT_SUFFIX__VERSION_1_2 + #define CL_API_SUFFIX__VERSION_2_0 + #define CL_EXT_SUFFIX__VERSION_2_0 + #define CL_API_SUFFIX__VERSION_2_1 + #define CL_EXT_SUFFIX__VERSION_2_1 + + #ifdef __GNUC__ + #ifdef CL_USE_DEPRECATED_OPENCL_1_0_APIS + #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED + #define CL_EXT_PREFIX__VERSION_1_0_DEPRECATED + #else + #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED __attribute__((deprecated)) + #define CL_EXT_PREFIX__VERSION_1_0_DEPRECATED + #endif + + #ifdef CL_USE_DEPRECATED_OPENCL_1_1_APIS + #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED + #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED + #else + #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED __attribute__((deprecated)) + #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED + #endif + + #ifdef CL_USE_DEPRECATED_OPENCL_1_2_APIS + #define CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED + #define CL_EXT_PREFIX__VERSION_1_2_DEPRECATED + #else + #define CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED __attribute__((deprecated)) + #define CL_EXT_PREFIX__VERSION_1_2_DEPRECATED + #endif + + #ifdef CL_USE_DEPRECATED_OPENCL_2_0_APIS + #define CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED + #define CL_EXT_PREFIX__VERSION_2_0_DEPRECATED + #else + #define CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED __attribute__((deprecated)) + #define CL_EXT_PREFIX__VERSION_2_0_DEPRECATED + #endif + #elif _WIN32 + #ifdef CL_USE_DEPRECATED_OPENCL_1_0_APIS + #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED + #define CL_EXT_PREFIX__VERSION_1_0_DEPRECATED + #else + #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED + #define CL_EXT_PREFIX__VERSION_1_0_DEPRECATED __declspec(deprecated) + #endif + + #ifdef CL_USE_DEPRECATED_OPENCL_1_1_APIS + #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED + #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED + #else + #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED + #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED __declspec(deprecated) + #endif + + #ifdef CL_USE_DEPRECATED_OPENCL_1_2_APIS + #define CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED + #define CL_EXT_PREFIX__VERSION_1_2_DEPRECATED + #else + #define CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED + #define CL_EXT_PREFIX__VERSION_1_2_DEPRECATED __declspec(deprecated) + #endif + + #ifdef CL_USE_DEPRECATED_OPENCL_2_0_APIS + #define CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED + #define CL_EXT_PREFIX__VERSION_2_0_DEPRECATED + #else + #define CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED + #define CL_EXT_PREFIX__VERSION_2_0_DEPRECATED __declspec(deprecated) + #endif + #else + #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED + #define CL_EXT_PREFIX__VERSION_1_0_DEPRECATED + + #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED + #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED + + #define CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED + #define CL_EXT_PREFIX__VERSION_1_2_DEPRECATED + + #define CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED + #define CL_EXT_PREFIX__VERSION_2_0_DEPRECATED + #endif +#endif + +#if (defined (_WIN32) && defined(_MSC_VER)) + +/* scalar types */ +typedef signed __int8 cl_char; +typedef unsigned __int8 cl_uchar; +typedef signed __int16 cl_short; +typedef unsigned __int16 cl_ushort; +typedef signed __int32 cl_int; +typedef unsigned __int32 cl_uint; +typedef signed __int64 cl_long; +typedef unsigned __int64 cl_ulong; + +typedef unsigned __int16 cl_half; +typedef float cl_float; +typedef double cl_double; + +/* Macro names and corresponding values defined by OpenCL */ +#define CL_CHAR_BIT 8 +#define CL_SCHAR_MAX 127 +#define CL_SCHAR_MIN (-127-1) +#define CL_CHAR_MAX CL_SCHAR_MAX +#define CL_CHAR_MIN CL_SCHAR_MIN +#define CL_UCHAR_MAX 255 +#define CL_SHRT_MAX 32767 +#define CL_SHRT_MIN (-32767-1) +#define CL_USHRT_MAX 65535 +#define CL_INT_MAX 2147483647 +#define CL_INT_MIN (-2147483647-1) +#define CL_UINT_MAX 0xffffffffU +#define CL_LONG_MAX ((cl_long) 0x7FFFFFFFFFFFFFFFLL) +#define CL_LONG_MIN ((cl_long) -0x7FFFFFFFFFFFFFFFLL - 1LL) +#define CL_ULONG_MAX ((cl_ulong) 0xFFFFFFFFFFFFFFFFULL) + +#define CL_FLT_DIG 6 +#define CL_FLT_MANT_DIG 24 +#define CL_FLT_MAX_10_EXP +38 +#define CL_FLT_MAX_EXP +128 +#define CL_FLT_MIN_10_EXP -37 +#define CL_FLT_MIN_EXP -125 +#define CL_FLT_RADIX 2 +#define CL_FLT_MAX 340282346638528859811704183484516925440.0f +#define CL_FLT_MIN 1.175494350822287507969e-38f +#define CL_FLT_EPSILON 0x1.0p-23f + +#define CL_DBL_DIG 15 +#define CL_DBL_MANT_DIG 53 +#define CL_DBL_MAX_10_EXP +308 +#define CL_DBL_MAX_EXP +1024 +#define CL_DBL_MIN_10_EXP -307 +#define CL_DBL_MIN_EXP -1021 +#define CL_DBL_RADIX 2 +#define CL_DBL_MAX 179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.0 +#define CL_DBL_MIN 2.225073858507201383090e-308 +#define CL_DBL_EPSILON 2.220446049250313080847e-16 + +#define CL_M_E 2.718281828459045090796 +#define CL_M_LOG2E 1.442695040888963387005 +#define CL_M_LOG10E 0.434294481903251816668 +#define CL_M_LN2 0.693147180559945286227 +#define CL_M_LN10 2.302585092994045901094 +#define CL_M_PI 3.141592653589793115998 +#define CL_M_PI_2 1.570796326794896557999 +#define CL_M_PI_4 0.785398163397448278999 +#define CL_M_1_PI 0.318309886183790691216 +#define CL_M_2_PI 0.636619772367581382433 +#define CL_M_2_SQRTPI 1.128379167095512558561 +#define CL_M_SQRT2 1.414213562373095145475 +#define CL_M_SQRT1_2 0.707106781186547572737 + +#define CL_M_E_F 2.71828174591064f +#define CL_M_LOG2E_F 1.44269502162933f +#define CL_M_LOG10E_F 0.43429449200630f +#define CL_M_LN2_F 0.69314718246460f +#define CL_M_LN10_F 2.30258512496948f +#define CL_M_PI_F 3.14159274101257f +#define CL_M_PI_2_F 1.57079637050629f +#define CL_M_PI_4_F 0.78539818525314f +#define CL_M_1_PI_F 0.31830987334251f +#define CL_M_2_PI_F 0.63661974668503f +#define CL_M_2_SQRTPI_F 1.12837922573090f +#define CL_M_SQRT2_F 1.41421353816986f +#define CL_M_SQRT1_2_F 0.70710676908493f + +#define CL_NAN (CL_INFINITY - CL_INFINITY) +#define CL_HUGE_VALF ((cl_float) 1e50) +#define CL_HUGE_VAL ((cl_double) 1e500) +#define CL_MAXFLOAT CL_FLT_MAX +#define CL_INFINITY CL_HUGE_VALF + +#else + +#include + +/* scalar types */ +typedef int8_t cl_char; +typedef uint8_t cl_uchar; +typedef int16_t cl_short __attribute__((aligned(2))); +typedef uint16_t cl_ushort __attribute__((aligned(2))); +typedef int32_t cl_int __attribute__((aligned(4))); +typedef uint32_t cl_uint __attribute__((aligned(4))); +typedef int64_t cl_long __attribute__((aligned(8))); +typedef uint64_t cl_ulong __attribute__((aligned(8))); + +typedef uint16_t cl_half __attribute__((aligned(2))); +typedef float cl_float __attribute__((aligned(4))); +typedef double cl_double __attribute__((aligned(8))); + +/* Macro names and corresponding values defined by OpenCL */ +#define CL_CHAR_BIT 8 +#define CL_SCHAR_MAX 127 +#define CL_SCHAR_MIN (-127-1) +#define CL_CHAR_MAX CL_SCHAR_MAX +#define CL_CHAR_MIN CL_SCHAR_MIN +#define CL_UCHAR_MAX 255 +#define CL_SHRT_MAX 32767 +#define CL_SHRT_MIN (-32767-1) +#define CL_USHRT_MAX 65535 +#define CL_INT_MAX 2147483647 +#define CL_INT_MIN (-2147483647-1) +#define CL_UINT_MAX 0xffffffffU +#define CL_LONG_MAX ((cl_long) 0x7FFFFFFFFFFFFFFFLL) +#define CL_LONG_MIN ((cl_long) -0x7FFFFFFFFFFFFFFFLL - 1LL) +#define CL_ULONG_MAX ((cl_ulong) 0xFFFFFFFFFFFFFFFFULL) + +#define CL_FLT_DIG 6 +#define CL_FLT_MANT_DIG 24 +#define CL_FLT_MAX_10_EXP +38 +#define CL_FLT_MAX_EXP +128 +#define CL_FLT_MIN_10_EXP -37 +#define CL_FLT_MIN_EXP -125 +#define CL_FLT_RADIX 2 +#define CL_FLT_MAX 0x1.fffffep127f +#define CL_FLT_MIN 0x1.0p-126f +#define CL_FLT_EPSILON 0x1.0p-23f + +#define CL_DBL_DIG 15 +#define CL_DBL_MANT_DIG 53 +#define CL_DBL_MAX_10_EXP +308 +#define CL_DBL_MAX_EXP +1024 +#define CL_DBL_MIN_10_EXP -307 +#define CL_DBL_MIN_EXP -1021 +#define CL_DBL_RADIX 2 +#define CL_DBL_MAX 0x1.fffffffffffffp1023 +#define CL_DBL_MIN 0x1.0p-1022 +#define CL_DBL_EPSILON 0x1.0p-52 + +#define CL_M_E 2.718281828459045090796 +#define CL_M_LOG2E 1.442695040888963387005 +#define CL_M_LOG10E 0.434294481903251816668 +#define CL_M_LN2 0.693147180559945286227 +#define CL_M_LN10 2.302585092994045901094 +#define CL_M_PI 3.141592653589793115998 +#define CL_M_PI_2 1.570796326794896557999 +#define CL_M_PI_4 0.785398163397448278999 +#define CL_M_1_PI 0.318309886183790691216 +#define CL_M_2_PI 0.636619772367581382433 +#define CL_M_2_SQRTPI 1.128379167095512558561 +#define CL_M_SQRT2 1.414213562373095145475 +#define CL_M_SQRT1_2 0.707106781186547572737 + +#define CL_M_E_F 2.71828174591064f +#define CL_M_LOG2E_F 1.44269502162933f +#define CL_M_LOG10E_F 0.43429449200630f +#define CL_M_LN2_F 0.69314718246460f +#define CL_M_LN10_F 2.30258512496948f +#define CL_M_PI_F 3.14159274101257f +#define CL_M_PI_2_F 1.57079637050629f +#define CL_M_PI_4_F 0.78539818525314f +#define CL_M_1_PI_F 0.31830987334251f +#define CL_M_2_PI_F 0.63661974668503f +#define CL_M_2_SQRTPI_F 1.12837922573090f +#define CL_M_SQRT2_F 1.41421353816986f +#define CL_M_SQRT1_2_F 0.70710676908493f + +#if defined( __GNUC__ ) + #define CL_HUGE_VALF __builtin_huge_valf() + #define CL_HUGE_VAL __builtin_huge_val() + #define CL_NAN __builtin_nanf( "" ) +#else + #define CL_HUGE_VALF ((cl_float) 1e50) + #define CL_HUGE_VAL ((cl_double) 1e500) + float nanf( const char * ); + #define CL_NAN nanf( "" ) +#endif +#define CL_MAXFLOAT CL_FLT_MAX +#define CL_INFINITY CL_HUGE_VALF + +#endif + +#include + +/* Mirror types to GL types. Mirror types allow us to avoid deciding which 87s to load based on whether we are using GL or GLES here. */ +typedef unsigned int cl_GLuint; +typedef int cl_GLint; +typedef unsigned int cl_GLenum; + +/* + * Vector types + * + * Note: OpenCL requires that all types be naturally aligned. + * This means that vector types must be naturally aligned. + * For example, a vector of four floats must be aligned to + * a 16 byte boundary (calculated as 4 * the natural 4-byte + * alignment of the float). The alignment qualifiers here + * will only function properly if your compiler supports them + * and if you don't actively work to defeat them. For example, + * in order for a cl_float4 to be 16 byte aligned in a struct, + * the start of the struct must itself be 16-byte aligned. + * + * Maintaining proper alignment is the user's responsibility. + */ + +/* Define basic vector types */ +#if defined( __VEC__ ) + #include /* may be omitted depending on compiler. AltiVec spec provides no way to detect whether the header is required. */ + typedef vector unsigned char __cl_uchar16; + typedef vector signed char __cl_char16; + typedef vector unsigned short __cl_ushort8; + typedef vector signed short __cl_short8; + typedef vector unsigned int __cl_uint4; + typedef vector signed int __cl_int4; + typedef vector float __cl_float4; + #define __CL_UCHAR16__ 1 + #define __CL_CHAR16__ 1 + #define __CL_USHORT8__ 1 + #define __CL_SHORT8__ 1 + #define __CL_UINT4__ 1 + #define __CL_INT4__ 1 + #define __CL_FLOAT4__ 1 +#endif + +#if defined( __SSE__ ) + #if defined( __MINGW64__ ) + #include + #else + #include + #endif + #if defined( __GNUC__ ) + typedef float __cl_float4 __attribute__((vector_size(16))); + #else + typedef __m128 __cl_float4; + #endif + #define __CL_FLOAT4__ 1 +#endif + +#if defined( __SSE2__ ) + #if defined( __MINGW64__ ) + #include + #else + #include + #endif + #if defined( __GNUC__ ) + typedef cl_uchar __cl_uchar16 __attribute__((vector_size(16))); + typedef cl_char __cl_char16 __attribute__((vector_size(16))); + typedef cl_ushort __cl_ushort8 __attribute__((vector_size(16))); + typedef cl_short __cl_short8 __attribute__((vector_size(16))); + typedef cl_uint __cl_uint4 __attribute__((vector_size(16))); + typedef cl_int __cl_int4 __attribute__((vector_size(16))); + typedef cl_ulong __cl_ulong2 __attribute__((vector_size(16))); + typedef cl_long __cl_long2 __attribute__((vector_size(16))); + typedef cl_double __cl_double2 __attribute__((vector_size(16))); + #else + typedef __m128i __cl_uchar16; + typedef __m128i __cl_char16; + typedef __m128i __cl_ushort8; + typedef __m128i __cl_short8; + typedef __m128i __cl_uint4; + typedef __m128i __cl_int4; + typedef __m128i __cl_ulong2; + typedef __m128i __cl_long2; + typedef __m128d __cl_double2; + #endif + #define __CL_UCHAR16__ 1 + #define __CL_CHAR16__ 1 + #define __CL_USHORT8__ 1 + #define __CL_SHORT8__ 1 + #define __CL_INT4__ 1 + #define __CL_UINT4__ 1 + #define __CL_ULONG2__ 1 + #define __CL_LONG2__ 1 + #define __CL_DOUBLE2__ 1 +#endif + +#if defined( __MMX__ ) + #include + #if defined( __GNUC__ ) + typedef cl_uchar __cl_uchar8 __attribute__((vector_size(8))); + typedef cl_char __cl_char8 __attribute__((vector_size(8))); + typedef cl_ushort __cl_ushort4 __attribute__((vector_size(8))); + typedef cl_short __cl_short4 __attribute__((vector_size(8))); + typedef cl_uint __cl_uint2 __attribute__((vector_size(8))); + typedef cl_int __cl_int2 __attribute__((vector_size(8))); + typedef cl_ulong __cl_ulong1 __attribute__((vector_size(8))); + typedef cl_long __cl_long1 __attribute__((vector_size(8))); + typedef cl_float __cl_float2 __attribute__((vector_size(8))); + #else + typedef __m64 __cl_uchar8; + typedef __m64 __cl_char8; + typedef __m64 __cl_ushort4; + typedef __m64 __cl_short4; + typedef __m64 __cl_uint2; + typedef __m64 __cl_int2; + typedef __m64 __cl_ulong1; + typedef __m64 __cl_long1; + typedef __m64 __cl_float2; + #endif + #define __CL_UCHAR8__ 1 + #define __CL_CHAR8__ 1 + #define __CL_USHORT4__ 1 + #define __CL_SHORT4__ 1 + #define __CL_INT2__ 1 + #define __CL_UINT2__ 1 + #define __CL_ULONG1__ 1 + #define __CL_LONG1__ 1 + #define __CL_FLOAT2__ 1 +#endif + +#if defined( __AVX__ ) + #if defined( __MINGW64__ ) + #include + #else + #include + #endif + #if defined( __GNUC__ ) + typedef cl_float __cl_float8 __attribute__((vector_size(32))); + typedef cl_double __cl_double4 __attribute__((vector_size(32))); + #else + typedef __m256 __cl_float8; + typedef __m256d __cl_double4; + #endif + #define __CL_FLOAT8__ 1 + #define __CL_DOUBLE4__ 1 +#endif + +/* Define capabilities for anonymous struct members. */ +#if defined( __GNUC__) && ! defined( __STRICT_ANSI__ ) +#define __CL_HAS_ANON_STRUCT__ 1 +#define __CL_ANON_STRUCT__ __extension__ +#elif defined( _WIN32) && (_MSC_VER >= 1500) + /* Microsoft Developer Studio 2008 supports anonymous structs, but + * complains by default. */ +#define __CL_HAS_ANON_STRUCT__ 1 +#define __CL_ANON_STRUCT__ + /* Disable warning C4201: nonstandard extension used : nameless + * struct/union */ +#pragma warning( push ) +#pragma warning( disable : 4201 ) +#else +#define __CL_HAS_ANON_STRUCT__ 0 +#define __CL_ANON_STRUCT__ +#endif + +/* Define alignment keys */ +#if defined( __GNUC__ ) + #define CL_ALIGNED(_x) __attribute__ ((aligned(_x))) +#elif defined( _WIN32) && (_MSC_VER) + /* Alignment keys neutered on windows because MSVC can't swallow function arguments with alignment requirements */ + /* http://msdn.microsoft.com/en-us/library/373ak2y1%28VS.71%29.aspx */ + /* #include */ + /* #define CL_ALIGNED(_x) _CRT_ALIGN(_x) */ + #define CL_ALIGNED(_x) +#else + #warning Need to implement some method to align data here + #define CL_ALIGNED(_x) +#endif + +/* Indicate whether .xyzw, .s0123 and .hi.lo are supported */ +#if __CL_HAS_ANON_STRUCT__ + /* .xyzw and .s0123...{f|F} are supported */ + #define CL_HAS_NAMED_VECTOR_FIELDS 1 + /* .hi and .lo are supported */ + #define CL_HAS_HI_LO_VECTOR_FIELDS 1 +#endif + +/* Define cl_vector types */ + +/* ---- cl_charn ---- */ +typedef union +{ + cl_char CL_ALIGNED(2) s[2]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_char x, y; }; + __CL_ANON_STRUCT__ struct{ cl_char s0, s1; }; + __CL_ANON_STRUCT__ struct{ cl_char lo, hi; }; +#endif +#if defined( __CL_CHAR2__) + __cl_char2 v2; +#endif +}cl_char2; + +typedef union +{ + cl_char CL_ALIGNED(4) s[4]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_char x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_char s0, s1, s2, s3; }; + __CL_ANON_STRUCT__ struct{ cl_char2 lo, hi; }; +#endif +#if defined( __CL_CHAR2__) + __cl_char2 v2[2]; +#endif +#if defined( __CL_CHAR4__) + __cl_char4 v4; +#endif +}cl_char4; + +/* cl_char3 is identical in size, alignment and behavior to cl_char4. See section 6.1.5. */ +typedef cl_char4 cl_char3; + +typedef union +{ + cl_char CL_ALIGNED(8) s[8]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_char x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_char s0, s1, s2, s3, s4, s5, s6, s7; }; + __CL_ANON_STRUCT__ struct{ cl_char4 lo, hi; }; +#endif +#if defined( __CL_CHAR2__) + __cl_char2 v2[4]; +#endif +#if defined( __CL_CHAR4__) + __cl_char4 v4[2]; +#endif +#if defined( __CL_CHAR8__ ) + __cl_char8 v8; +#endif +}cl_char8; + +typedef union +{ + cl_char CL_ALIGNED(16) s[16]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_char x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; + __CL_ANON_STRUCT__ struct{ cl_char s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; + __CL_ANON_STRUCT__ struct{ cl_char8 lo, hi; }; +#endif +#if defined( __CL_CHAR2__) + __cl_char2 v2[8]; +#endif +#if defined( __CL_CHAR4__) + __cl_char4 v4[4]; +#endif +#if defined( __CL_CHAR8__ ) + __cl_char8 v8[2]; +#endif +#if defined( __CL_CHAR16__ ) + __cl_char16 v16; +#endif +}cl_char16; + + +/* ---- cl_ucharn ---- */ +typedef union +{ + cl_uchar CL_ALIGNED(2) s[2]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_uchar x, y; }; + __CL_ANON_STRUCT__ struct{ cl_uchar s0, s1; }; + __CL_ANON_STRUCT__ struct{ cl_uchar lo, hi; }; +#endif +#if defined( __cl_uchar2__) + __cl_uchar2 v2; +#endif +}cl_uchar2; + +typedef union +{ + cl_uchar CL_ALIGNED(4) s[4]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_uchar x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_uchar s0, s1, s2, s3; }; + __CL_ANON_STRUCT__ struct{ cl_uchar2 lo, hi; }; +#endif +#if defined( __CL_UCHAR2__) + __cl_uchar2 v2[2]; +#endif +#if defined( __CL_UCHAR4__) + __cl_uchar4 v4; +#endif +}cl_uchar4; + +/* cl_uchar3 is identical in size, alignment and behavior to cl_uchar4. See section 6.1.5. */ +typedef cl_uchar4 cl_uchar3; + +typedef union +{ + cl_uchar CL_ALIGNED(8) s[8]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_uchar x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_uchar s0, s1, s2, s3, s4, s5, s6, s7; }; + __CL_ANON_STRUCT__ struct{ cl_uchar4 lo, hi; }; +#endif +#if defined( __CL_UCHAR2__) + __cl_uchar2 v2[4]; +#endif +#if defined( __CL_UCHAR4__) + __cl_uchar4 v4[2]; +#endif +#if defined( __CL_UCHAR8__ ) + __cl_uchar8 v8; +#endif +}cl_uchar8; + +typedef union +{ + cl_uchar CL_ALIGNED(16) s[16]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_uchar x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; + __CL_ANON_STRUCT__ struct{ cl_uchar s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; + __CL_ANON_STRUCT__ struct{ cl_uchar8 lo, hi; }; +#endif +#if defined( __CL_UCHAR2__) + __cl_uchar2 v2[8]; +#endif +#if defined( __CL_UCHAR4__) + __cl_uchar4 v4[4]; +#endif +#if defined( __CL_UCHAR8__ ) + __cl_uchar8 v8[2]; +#endif +#if defined( __CL_UCHAR16__ ) + __cl_uchar16 v16; +#endif +}cl_uchar16; + + +/* ---- cl_shortn ---- */ +typedef union +{ + cl_short CL_ALIGNED(4) s[2]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_short x, y; }; + __CL_ANON_STRUCT__ struct{ cl_short s0, s1; }; + __CL_ANON_STRUCT__ struct{ cl_short lo, hi; }; +#endif +#if defined( __CL_SHORT2__) + __cl_short2 v2; +#endif +}cl_short2; + +typedef union +{ + cl_short CL_ALIGNED(8) s[4]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_short x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_short s0, s1, s2, s3; }; + __CL_ANON_STRUCT__ struct{ cl_short2 lo, hi; }; +#endif +#if defined( __CL_SHORT2__) + __cl_short2 v2[2]; +#endif +#if defined( __CL_SHORT4__) + __cl_short4 v4; +#endif +}cl_short4; + +/* cl_short3 is identical in size, alignment and behavior to cl_short4. See section 6.1.5. */ +typedef cl_short4 cl_short3; + +typedef union +{ + cl_short CL_ALIGNED(16) s[8]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_short x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_short s0, s1, s2, s3, s4, s5, s6, s7; }; + __CL_ANON_STRUCT__ struct{ cl_short4 lo, hi; }; +#endif +#if defined( __CL_SHORT2__) + __cl_short2 v2[4]; +#endif +#if defined( __CL_SHORT4__) + __cl_short4 v4[2]; +#endif +#if defined( __CL_SHORT8__ ) + __cl_short8 v8; +#endif +}cl_short8; + +typedef union +{ + cl_short CL_ALIGNED(32) s[16]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_short x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; + __CL_ANON_STRUCT__ struct{ cl_short s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; + __CL_ANON_STRUCT__ struct{ cl_short8 lo, hi; }; +#endif +#if defined( __CL_SHORT2__) + __cl_short2 v2[8]; +#endif +#if defined( __CL_SHORT4__) + __cl_short4 v4[4]; +#endif +#if defined( __CL_SHORT8__ ) + __cl_short8 v8[2]; +#endif +#if defined( __CL_SHORT16__ ) + __cl_short16 v16; +#endif +}cl_short16; + + +/* ---- cl_ushortn ---- */ +typedef union +{ + cl_ushort CL_ALIGNED(4) s[2]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_ushort x, y; }; + __CL_ANON_STRUCT__ struct{ cl_ushort s0, s1; }; + __CL_ANON_STRUCT__ struct{ cl_ushort lo, hi; }; +#endif +#if defined( __CL_USHORT2__) + __cl_ushort2 v2; +#endif +}cl_ushort2; + +typedef union +{ + cl_ushort CL_ALIGNED(8) s[4]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_ushort x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_ushort s0, s1, s2, s3; }; + __CL_ANON_STRUCT__ struct{ cl_ushort2 lo, hi; }; +#endif +#if defined( __CL_USHORT2__) + __cl_ushort2 v2[2]; +#endif +#if defined( __CL_USHORT4__) + __cl_ushort4 v4; +#endif +}cl_ushort4; + +/* cl_ushort3 is identical in size, alignment and behavior to cl_ushort4. See section 6.1.5. */ +typedef cl_ushort4 cl_ushort3; + +typedef union +{ + cl_ushort CL_ALIGNED(16) s[8]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_ushort x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_ushort s0, s1, s2, s3, s4, s5, s6, s7; }; + __CL_ANON_STRUCT__ struct{ cl_ushort4 lo, hi; }; +#endif +#if defined( __CL_USHORT2__) + __cl_ushort2 v2[4]; +#endif +#if defined( __CL_USHORT4__) + __cl_ushort4 v4[2]; +#endif +#if defined( __CL_USHORT8__ ) + __cl_ushort8 v8; +#endif +}cl_ushort8; + +typedef union +{ + cl_ushort CL_ALIGNED(32) s[16]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_ushort x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; + __CL_ANON_STRUCT__ struct{ cl_ushort s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; + __CL_ANON_STRUCT__ struct{ cl_ushort8 lo, hi; }; +#endif +#if defined( __CL_USHORT2__) + __cl_ushort2 v2[8]; +#endif +#if defined( __CL_USHORT4__) + __cl_ushort4 v4[4]; +#endif +#if defined( __CL_USHORT8__ ) + __cl_ushort8 v8[2]; +#endif +#if defined( __CL_USHORT16__ ) + __cl_ushort16 v16; +#endif +}cl_ushort16; + +/* ---- cl_intn ---- */ +typedef union +{ + cl_int CL_ALIGNED(8) s[2]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_int x, y; }; + __CL_ANON_STRUCT__ struct{ cl_int s0, s1; }; + __CL_ANON_STRUCT__ struct{ cl_int lo, hi; }; +#endif +#if defined( __CL_INT2__) + __cl_int2 v2; +#endif +}cl_int2; + +typedef union +{ + cl_int CL_ALIGNED(16) s[4]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_int x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_int s0, s1, s2, s3; }; + __CL_ANON_STRUCT__ struct{ cl_int2 lo, hi; }; +#endif +#if defined( __CL_INT2__) + __cl_int2 v2[2]; +#endif +#if defined( __CL_INT4__) + __cl_int4 v4; +#endif +}cl_int4; + +/* cl_int3 is identical in size, alignment and behavior to cl_int4. See section 6.1.5. */ +typedef cl_int4 cl_int3; + +typedef union +{ + cl_int CL_ALIGNED(32) s[8]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_int x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_int s0, s1, s2, s3, s4, s5, s6, s7; }; + __CL_ANON_STRUCT__ struct{ cl_int4 lo, hi; }; +#endif +#if defined( __CL_INT2__) + __cl_int2 v2[4]; +#endif +#if defined( __CL_INT4__) + __cl_int4 v4[2]; +#endif +#if defined( __CL_INT8__ ) + __cl_int8 v8; +#endif +}cl_int8; + +typedef union +{ + cl_int CL_ALIGNED(64) s[16]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_int x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; + __CL_ANON_STRUCT__ struct{ cl_int s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; + __CL_ANON_STRUCT__ struct{ cl_int8 lo, hi; }; +#endif +#if defined( __CL_INT2__) + __cl_int2 v2[8]; +#endif +#if defined( __CL_INT4__) + __cl_int4 v4[4]; +#endif +#if defined( __CL_INT8__ ) + __cl_int8 v8[2]; +#endif +#if defined( __CL_INT16__ ) + __cl_int16 v16; +#endif +}cl_int16; + + +/* ---- cl_uintn ---- */ +typedef union +{ + cl_uint CL_ALIGNED(8) s[2]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_uint x, y; }; + __CL_ANON_STRUCT__ struct{ cl_uint s0, s1; }; + __CL_ANON_STRUCT__ struct{ cl_uint lo, hi; }; +#endif +#if defined( __CL_UINT2__) + __cl_uint2 v2; +#endif +}cl_uint2; + +typedef union +{ + cl_uint CL_ALIGNED(16) s[4]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_uint x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_uint s0, s1, s2, s3; }; + __CL_ANON_STRUCT__ struct{ cl_uint2 lo, hi; }; +#endif +#if defined( __CL_UINT2__) + __cl_uint2 v2[2]; +#endif +#if defined( __CL_UINT4__) + __cl_uint4 v4; +#endif +}cl_uint4; + +/* cl_uint3 is identical in size, alignment and behavior to cl_uint4. See section 6.1.5. */ +typedef cl_uint4 cl_uint3; + +typedef union +{ + cl_uint CL_ALIGNED(32) s[8]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_uint x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_uint s0, s1, s2, s3, s4, s5, s6, s7; }; + __CL_ANON_STRUCT__ struct{ cl_uint4 lo, hi; }; +#endif +#if defined( __CL_UINT2__) + __cl_uint2 v2[4]; +#endif +#if defined( __CL_UINT4__) + __cl_uint4 v4[2]; +#endif +#if defined( __CL_UINT8__ ) + __cl_uint8 v8; +#endif +}cl_uint8; + +typedef union +{ + cl_uint CL_ALIGNED(64) s[16]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_uint x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; + __CL_ANON_STRUCT__ struct{ cl_uint s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; + __CL_ANON_STRUCT__ struct{ cl_uint8 lo, hi; }; +#endif +#if defined( __CL_UINT2__) + __cl_uint2 v2[8]; +#endif +#if defined( __CL_UINT4__) + __cl_uint4 v4[4]; +#endif +#if defined( __CL_UINT8__ ) + __cl_uint8 v8[2]; +#endif +#if defined( __CL_UINT16__ ) + __cl_uint16 v16; +#endif +}cl_uint16; + +/* ---- cl_longn ---- */ +typedef union +{ + cl_long CL_ALIGNED(16) s[2]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_long x, y; }; + __CL_ANON_STRUCT__ struct{ cl_long s0, s1; }; + __CL_ANON_STRUCT__ struct{ cl_long lo, hi; }; +#endif +#if defined( __CL_LONG2__) + __cl_long2 v2; +#endif +}cl_long2; + +typedef union +{ + cl_long CL_ALIGNED(32) s[4]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_long x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_long s0, s1, s2, s3; }; + __CL_ANON_STRUCT__ struct{ cl_long2 lo, hi; }; +#endif +#if defined( __CL_LONG2__) + __cl_long2 v2[2]; +#endif +#if defined( __CL_LONG4__) + __cl_long4 v4; +#endif +}cl_long4; + +/* cl_long3 is identical in size, alignment and behavior to cl_long4. See section 6.1.5. */ +typedef cl_long4 cl_long3; + +typedef union +{ + cl_long CL_ALIGNED(64) s[8]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_long x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_long s0, s1, s2, s3, s4, s5, s6, s7; }; + __CL_ANON_STRUCT__ struct{ cl_long4 lo, hi; }; +#endif +#if defined( __CL_LONG2__) + __cl_long2 v2[4]; +#endif +#if defined( __CL_LONG4__) + __cl_long4 v4[2]; +#endif +#if defined( __CL_LONG8__ ) + __cl_long8 v8; +#endif +}cl_long8; + +typedef union +{ + cl_long CL_ALIGNED(128) s[16]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_long x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; + __CL_ANON_STRUCT__ struct{ cl_long s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; + __CL_ANON_STRUCT__ struct{ cl_long8 lo, hi; }; +#endif +#if defined( __CL_LONG2__) + __cl_long2 v2[8]; +#endif +#if defined( __CL_LONG4__) + __cl_long4 v4[4]; +#endif +#if defined( __CL_LONG8__ ) + __cl_long8 v8[2]; +#endif +#if defined( __CL_LONG16__ ) + __cl_long16 v16; +#endif +}cl_long16; + + +/* ---- cl_ulongn ---- */ +typedef union +{ + cl_ulong CL_ALIGNED(16) s[2]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_ulong x, y; }; + __CL_ANON_STRUCT__ struct{ cl_ulong s0, s1; }; + __CL_ANON_STRUCT__ struct{ cl_ulong lo, hi; }; +#endif +#if defined( __CL_ULONG2__) + __cl_ulong2 v2; +#endif +}cl_ulong2; + +typedef union +{ + cl_ulong CL_ALIGNED(32) s[4]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_ulong x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_ulong s0, s1, s2, s3; }; + __CL_ANON_STRUCT__ struct{ cl_ulong2 lo, hi; }; +#endif +#if defined( __CL_ULONG2__) + __cl_ulong2 v2[2]; +#endif +#if defined( __CL_ULONG4__) + __cl_ulong4 v4; +#endif +}cl_ulong4; + +/* cl_ulong3 is identical in size, alignment and behavior to cl_ulong4. See section 6.1.5. */ +typedef cl_ulong4 cl_ulong3; + +typedef union +{ + cl_ulong CL_ALIGNED(64) s[8]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_ulong x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_ulong s0, s1, s2, s3, s4, s5, s6, s7; }; + __CL_ANON_STRUCT__ struct{ cl_ulong4 lo, hi; }; +#endif +#if defined( __CL_ULONG2__) + __cl_ulong2 v2[4]; +#endif +#if defined( __CL_ULONG4__) + __cl_ulong4 v4[2]; +#endif +#if defined( __CL_ULONG8__ ) + __cl_ulong8 v8; +#endif +}cl_ulong8; + +typedef union +{ + cl_ulong CL_ALIGNED(128) s[16]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_ulong x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; + __CL_ANON_STRUCT__ struct{ cl_ulong s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; + __CL_ANON_STRUCT__ struct{ cl_ulong8 lo, hi; }; +#endif +#if defined( __CL_ULONG2__) + __cl_ulong2 v2[8]; +#endif +#if defined( __CL_ULONG4__) + __cl_ulong4 v4[4]; +#endif +#if defined( __CL_ULONG8__ ) + __cl_ulong8 v8[2]; +#endif +#if defined( __CL_ULONG16__ ) + __cl_ulong16 v16; +#endif +}cl_ulong16; + + +/* --- cl_floatn ---- */ + +typedef union +{ + cl_float CL_ALIGNED(8) s[2]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_float x, y; }; + __CL_ANON_STRUCT__ struct{ cl_float s0, s1; }; + __CL_ANON_STRUCT__ struct{ cl_float lo, hi; }; +#endif +#if defined( __CL_FLOAT2__) + __cl_float2 v2; +#endif +}cl_float2; + +typedef union +{ + cl_float CL_ALIGNED(16) s[4]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_float x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_float s0, s1, s2, s3; }; + __CL_ANON_STRUCT__ struct{ cl_float2 lo, hi; }; +#endif +#if defined( __CL_FLOAT2__) + __cl_float2 v2[2]; +#endif +#if defined( __CL_FLOAT4__) + __cl_float4 v4; +#endif +}cl_float4; + +/* cl_float3 is identical in size, alignment and behavior to cl_float4. See section 6.1.5. */ +typedef cl_float4 cl_float3; + +typedef union +{ + cl_float CL_ALIGNED(32) s[8]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_float x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_float s0, s1, s2, s3, s4, s5, s6, s7; }; + __CL_ANON_STRUCT__ struct{ cl_float4 lo, hi; }; +#endif +#if defined( __CL_FLOAT2__) + __cl_float2 v2[4]; +#endif +#if defined( __CL_FLOAT4__) + __cl_float4 v4[2]; +#endif +#if defined( __CL_FLOAT8__ ) + __cl_float8 v8; +#endif +}cl_float8; + +typedef union +{ + cl_float CL_ALIGNED(64) s[16]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_float x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; + __CL_ANON_STRUCT__ struct{ cl_float s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; + __CL_ANON_STRUCT__ struct{ cl_float8 lo, hi; }; +#endif +#if defined( __CL_FLOAT2__) + __cl_float2 v2[8]; +#endif +#if defined( __CL_FLOAT4__) + __cl_float4 v4[4]; +#endif +#if defined( __CL_FLOAT8__ ) + __cl_float8 v8[2]; +#endif +#if defined( __CL_FLOAT16__ ) + __cl_float16 v16; +#endif +}cl_float16; + +/* --- cl_doublen ---- */ + +typedef union +{ + cl_double CL_ALIGNED(16) s[2]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_double x, y; }; + __CL_ANON_STRUCT__ struct{ cl_double s0, s1; }; + __CL_ANON_STRUCT__ struct{ cl_double lo, hi; }; +#endif +#if defined( __CL_DOUBLE2__) + __cl_double2 v2; +#endif +}cl_double2; + +typedef union +{ + cl_double CL_ALIGNED(32) s[4]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_double x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_double s0, s1, s2, s3; }; + __CL_ANON_STRUCT__ struct{ cl_double2 lo, hi; }; +#endif +#if defined( __CL_DOUBLE2__) + __cl_double2 v2[2]; +#endif +#if defined( __CL_DOUBLE4__) + __cl_double4 v4; +#endif +}cl_double4; + +/* cl_double3 is identical in size, alignment and behavior to cl_double4. See section 6.1.5. */ +typedef cl_double4 cl_double3; + +typedef union +{ + cl_double CL_ALIGNED(64) s[8]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_double x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_double s0, s1, s2, s3, s4, s5, s6, s7; }; + __CL_ANON_STRUCT__ struct{ cl_double4 lo, hi; }; +#endif +#if defined( __CL_DOUBLE2__) + __cl_double2 v2[4]; +#endif +#if defined( __CL_DOUBLE4__) + __cl_double4 v4[2]; +#endif +#if defined( __CL_DOUBLE8__ ) + __cl_double8 v8; +#endif +}cl_double8; + +typedef union +{ + cl_double CL_ALIGNED(128) s[16]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_double x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; + __CL_ANON_STRUCT__ struct{ cl_double s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; + __CL_ANON_STRUCT__ struct{ cl_double8 lo, hi; }; +#endif +#if defined( __CL_DOUBLE2__) + __cl_double2 v2[8]; +#endif +#if defined( __CL_DOUBLE4__) + __cl_double4 v4[4]; +#endif +#if defined( __CL_DOUBLE8__ ) + __cl_double8 v8[2]; +#endif +#if defined( __CL_DOUBLE16__ ) + __cl_double16 v16; +#endif +}cl_double16; + +/* Macro to facilitate debugging + * Usage: + * Place CL_PROGRAM_STRING_DEBUG_INFO on the line before the first line of your source. + * The first line ends with: CL_PROGRAM_STRING_DEBUG_INFO \" + * Each line thereafter of OpenCL C source must end with: \n\ + * The last line ends in "; + * + * Example: + * + * const char *my_program = CL_PROGRAM_STRING_DEBUG_INFO "\ + * kernel void foo( int a, float * b ) \n\ + * { \n\ + * // my comment \n\ + * *b[ get_global_id(0)] = a; \n\ + * } \n\ + * "; + * + * This should correctly set up the line, (column) and file information for your source + * string so you can do source level debugging. + */ +#define __CL_STRINGIFY( _x ) # _x +#define _CL_STRINGIFY( _x ) __CL_STRINGIFY( _x ) +#define CL_PROGRAM_STRING_DEBUG_INFO "#line " _CL_STRINGIFY(__LINE__) " \"" __FILE__ "\" \n\n" + +#ifdef __cplusplus +} +#endif + +#undef __CL_HAS_ANON_STRUCT__ +#undef __CL_ANON_STRUCT__ +#if defined( _WIN32) && (_MSC_VER >= 1500) +#pragma warning( pop ) +#endif + +#endif /* __CL_PLATFORM_H */ diff --git a/third_party/opencl/include/CL/opencl.h b/third_party/opencl/include/CL/opencl.h new file mode 100644 index 000000000..9855cd75e --- /dev/null +++ b/third_party/opencl/include/CL/opencl.h @@ -0,0 +1,59 @@ +/******************************************************************************* + * Copyright (c) 2008-2015 The Khronos Group Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and/or associated documentation files (the + * "Materials"), to deal in the Materials without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Materials, and to + * permit persons to whom the Materials are furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Materials. + * + * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS + * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS + * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT + * https://www.khronos.org/registry/ + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. + ******************************************************************************/ + +/* $Revision: 11708 $ on $Date: 2010-06-13 23:36:24 -0700 (Sun, 13 Jun 2010) $ */ + +#ifndef __OPENCL_H +#define __OPENCL_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __APPLE__ + +#include +#include +#include +#include + +#else + +#include +#include +#include +#include + +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* __OPENCL_H */ + diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index b79d046fc..d4cfc6728 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -57,9 +57,11 @@ base_frameworks = qt_env['FRAMEWORKS'] base_libs = [common, messaging, cereal, visionipc, 'm', 'ssl', 'crypto', 'pthread'] + qt_env["LIBS"] if arch == "Darwin": + base_frameworks.append('OpenCL') base_frameworks.append('QtCharts') base_frameworks.append('QtSerialBus') else: + base_libs.append('OpenCL') base_libs.append('Qt5Charts') base_libs.append('Qt5SerialBus') diff --git a/tools/install_ubuntu_dependencies.sh b/tools/install_ubuntu_dependencies.sh index f428e9972..5c2131d4b 100755 --- a/tools/install_ubuntu_dependencies.sh +++ b/tools/install_ubuntu_dependencies.sh @@ -58,6 +58,9 @@ function install_ubuntu_common_requirements() { libzmq3-dev \ libzstd-dev \ libsqlite3-dev \ + opencl-headers \ + ocl-icd-libopencl1 \ + ocl-icd-opencl-dev \ portaudio19-dev \ qttools5-dev-tools \ libqt5svg5-dev \ diff --git a/tools/replay/SConscript b/tools/replay/SConscript index 698ab9885..136c4119f 100644 --- a/tools/replay/SConscript +++ b/tools/replay/SConscript @@ -6,6 +6,11 @@ replay_env['CCFLAGS'] += ['-Wno-deprecated-declarations'] base_frameworks = [] base_libs = [common, messaging, cereal, visionipc, 'm', 'ssl', 'crypto', 'pthread'] +if arch == "Darwin": + base_frameworks.append('OpenCL') +else: + base_libs.append('OpenCL') + replay_lib_src = ["replay.cc", "consoleui.cc", "camera.cc", "filereader.cc", "logreader.cc", "framereader.cc", "route.cc", "util.cc", "seg_mgr.cc", "timeline.cc", "api.cc"] if arch != "Darwin": diff --git a/tools/webcam/README.md b/tools/webcam/README.md index 6abbc4793..67ad2cc8c 100644 --- a/tools/webcam/README.md +++ b/tools/webcam/README.md @@ -10,6 +10,10 @@ What's needed: ## Setup openpilot - Follow [this readme](../README.md) to install and build the requirements +- Install OpenCL Driver (Ubuntu) +``` +sudo apt install pocl-opencl-icd +``` ## Connect the hardware - Connect the camera first From 10edb90ac65b9bcbdd5371b46314abe2a471246b Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 10 Feb 2026 23:27:38 -0800 Subject: [PATCH 900/910] newline in updater error --- selfdrive/ui/mici/layouts/settings/device.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/mici/layouts/settings/device.py b/selfdrive/ui/mici/layouts/settings/device.py index 60eb7e5b0..a47231340 100644 --- a/selfdrive/ui/mici/layouts/settings/device.py +++ b/selfdrive/ui/mici/layouts/settings/device.py @@ -224,7 +224,7 @@ class UpdateOpenpilotBigButton(BigButton): if self._waiting_for_updater_t is not None and rl.get_time() - self._waiting_for_updater_t > UPDATER_TIMEOUT: self.set_rotate_icon(False) - self.set_value("updater failed to respond") + self.set_value("updater failed\nto respond") self._state = UpdaterState.IDLE self._hide_value_t = rl.get_time() From 3f382d6e695809afda39efef17d6a993b1e72a52 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 11 Feb 2026 00:18:02 -0800 Subject: [PATCH 901/910] Remove vertical scroll bar --- .../icons_mici/settings/vertical_scroll_indicator.png | 3 --- system/ui/widgets/scroller.py | 10 ---------- 2 files changed, 13 deletions(-) delete mode 100644 selfdrive/assets/icons_mici/settings/vertical_scroll_indicator.png diff --git a/selfdrive/assets/icons_mici/settings/vertical_scroll_indicator.png b/selfdrive/assets/icons_mici/settings/vertical_scroll_indicator.png deleted file mode 100644 index 77d9a77d6..000000000 --- a/selfdrive/assets/icons_mici/settings/vertical_scroll_indicator.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:88e6c50358f627fc714c1e9883143aeed00baabeab16132e16001aa1051e5eb8 -size 1272 diff --git a/system/ui/widgets/scroller.py b/system/ui/widgets/scroller.py index f33ba941b..5f932fba7 100644 --- a/system/ui/widgets/scroller.py +++ b/system/ui/widgets/scroller.py @@ -15,7 +15,6 @@ ANIMATION_SCALE = 0.6 MIN_ZOOM_ANIMATION_TIME = 0.075 # seconds DO_ZOOM = False DO_JELLO = False -SCROLL_BAR = False class LineSeparator(Widget): @@ -65,8 +64,6 @@ class Scroller(Widget): self.scroll_panel = GuiScrollPanel2(self._horizontal, handle_out_of_bounds=not self._snap_items) self._scroll_enabled: bool | Callable[[], bool] = True - self._txt_scroll_indicator = gui_app.texture("icons_mici/settings/vertical_scroll_indicator.png", 40, 80) - for item in items: self.add_widget(item) @@ -241,13 +238,6 @@ class Scroller(Widget): else: item.render() - # Draw scroll indicator - if SCROLL_BAR and not self._horizontal and len(self._visible_items) > 0: - _real_content_size = self._content_size - self._rect.height + self._txt_scroll_indicator.height - scroll_bar_y = -self._scroll_offset / _real_content_size * self._rect.height - scroll_bar_y = min(max(scroll_bar_y, self._rect.y), self._rect.y + self._rect.height - self._txt_scroll_indicator.height) - rl.draw_texture_ex(self._txt_scroll_indicator, rl.Vector2(self._rect.x, scroll_bar_y), 0, 1.0, rl.WHITE) - rl.end_scissor_mode() def show_event(self): From 8ba36b76a0eb45d863c8cee38f4e85c77b1c948a Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 11 Feb 2026 01:15:02 -0800 Subject: [PATCH 902/910] Simple scroll indicator (#37162) * scroll indicator * 65% * calibrate * margin * cleaner? * manual clean up * clean up * full scroll bar * look * looks * unlook * no fade, looks good but "too much" * clean up * cmt --- .../settings/horizontal_scroll_indicator.png | 3 ++ selfdrive/ui/mici/layouts/main.py | 2 +- system/ui/widgets/scroller.py | 53 ++++++++++++++++++- 3 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 selfdrive/assets/icons_mici/settings/horizontal_scroll_indicator.png diff --git a/selfdrive/assets/icons_mici/settings/horizontal_scroll_indicator.png b/selfdrive/assets/icons_mici/settings/horizontal_scroll_indicator.png new file mode 100644 index 000000000..39dd7b194 --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/horizontal_scroll_indicator.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:af8d5ecb6468442361462aa838a2d234b1256b8139418be8ef2962e4350cfbef +size 2176 diff --git a/selfdrive/ui/mici/layouts/main.py b/selfdrive/ui/mici/layouts/main.py index b52f9ed39..f12c95eaf 100644 --- a/selfdrive/ui/mici/layouts/main.py +++ b/selfdrive/ui/mici/layouts/main.py @@ -47,7 +47,7 @@ class MiciMainLayout(Widget): self._alerts_layout, self._home_layout, self._onroad_layout, - ], spacing=0, pad_start=0, pad_end=0) + ], spacing=0, pad_start=0, pad_end=0, scroll_indicator=False) self._scroller.set_reset_scroll_at_show(False) # Disable scrolling when onroad is interacting with bookmark diff --git a/system/ui/widgets/scroller.py b/system/ui/widgets/scroller.py index 5f932fba7..03c49fca6 100644 --- a/system/ui/widgets/scroller.py +++ b/system/ui/widgets/scroller.py @@ -32,9 +32,52 @@ class LineSeparator(Widget): LINE_COLOR) +class ScrollIndicator(Widget): + HORIZONTAL_MARGIN = 4 + + def __init__(self): + super().__init__() + self._txt_scroll_indicator = gui_app.texture("icons_mici/settings/horizontal_scroll_indicator.png", 96, 48) + self._scroll_offset: float = 0.0 + self._content_size: float = 0.0 + self._viewport: rl.Rectangle = rl.Rectangle(0, 0, 0, 0) + + def update(self, scroll_offset: float, content_size: float, viewport: rl.Rectangle) -> None: + self._scroll_offset = scroll_offset + self._content_size = content_size + self._viewport = viewport + + def _render(self, _): + # scale indicator width based on content size + indicator_w = float(np.interp(self._content_size, [1000, 3000], [300, 100])) + + # position based on scroll ratio + slide_range = self._viewport.width - indicator_w + max_scroll = self._content_size - self._viewport.width + scroll_ratio = -self._scroll_offset / max_scroll + x = self._viewport.x + scroll_ratio * slide_range + # don't bounce up when NavWidget shows + y = max(self._viewport.y, 0) + self._viewport.height - self._txt_scroll_indicator.height / 2 + + # squeeze when overscrolling past edges + dest_left = max(x, self._viewport.x) + dest_right = min(x + indicator_w, self._viewport.x + self._viewport.width) + dest_w = max(indicator_w / 2, dest_right - dest_left) + + # keep within viewport after applying minimum width + dest_left = min(dest_left, self._viewport.x + self._viewport.width - dest_w) + dest_left = max(dest_left, self._viewport.x) + + src_rec = rl.Rectangle(0, 0, self._txt_scroll_indicator.width, self._txt_scroll_indicator.height) + dest_rec = rl.Rectangle(dest_left, y, dest_w, self._txt_scroll_indicator.height) + rl.draw_texture_pro(self._txt_scroll_indicator, src_rec, dest_rec, rl.Vector2(0, 0), 0.0, + rl.Color(255, 255, 255, int(255 * 0.45))) + + class Scroller(Widget): def __init__(self, items: list[Widget], horizontal: bool = True, snap_items: bool = True, spacing: int = ITEM_SPACING, - line_separator: bool = False, pad_start: int = ITEM_SPACING, pad_end: int = ITEM_SPACING): + line_separator: bool = False, pad_start: int = ITEM_SPACING, pad_end: int = ITEM_SPACING, + scroll_indicator: bool = True): super().__init__() self._items: list[Widget] = [] self._horizontal = horizontal @@ -64,6 +107,9 @@ class Scroller(Widget): self.scroll_panel = GuiScrollPanel2(self._horizontal, handle_out_of_bounds=not self._snap_items) self._scroll_enabled: bool | Callable[[], bool] = True + self._show_scroll_indicator = scroll_indicator + self._scroll_indicator = ScrollIndicator() + for item in items: self.add_widget(item) @@ -240,6 +286,11 @@ class Scroller(Widget): rl.end_scissor_mode() + # Draw scroll indicator + if self._show_scroll_indicator and self._horizontal and len(self._visible_items) > 0: + self._scroll_indicator.update(self._scroll_offset, self._content_size, self._rect) + self._scroll_indicator.render() + def show_event(self): super().show_event() if self._reset_scroll_at_show: From 1e0f1a8abc8302e405527de609e150252e128354 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 11 Feb 2026 01:23:00 -0800 Subject: [PATCH 903/910] Scroll panel: adapt to content size shrinking (#37163) fix --- system/ui/lib/scroll_panel2.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/system/ui/lib/scroll_panel2.py b/system/ui/lib/scroll_panel2.py index 0859071da..e2a548ba2 100644 --- a/system/ui/lib/scroll_panel2.py +++ b/system/ui/lib/scroll_panel2.py @@ -73,8 +73,14 @@ class GuiScrollPanel2: def _update_state(self, bounds_size: float, content_size: float) -> None: """Runs per render frame, independent of mouse events. Updates auto-scrolling state and velocity.""" - if self._state == ScrollState.AUTO_SCROLL: - max_offset, min_offset = self._get_offset_bounds(bounds_size, content_size) + max_offset, min_offset = self._get_offset_bounds(bounds_size, content_size) + + if self._state == ScrollState.STEADY: + # if we find ourselves out of bounds, scroll back in (from external layout dimension changes, etc.) + if self.get_offset() > max_offset or self.get_offset() < min_offset: + self._state = ScrollState.AUTO_SCROLL + + elif self._state == ScrollState.AUTO_SCROLL: # simple exponential return if out of bounds out_of_bounds = self.get_offset() > max_offset or self.get_offset() < min_offset if out_of_bounds and self._handle_out_of_bounds: From b9344af9bb8ffcef001471f37936bee4eba57b6d Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 11 Feb 2026 01:23:14 -0800 Subject: [PATCH 904/910] WifiManager: sort by known networks (#37164) sort by known --- system/ui/lib/wifi_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index bd66b8e03..e0a9e7ca7 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -631,7 +631,7 @@ class WifiManager: known_connections = self._get_connections() networks = [Network.from_dbus(ssid, ap_list, ssid in known_connections) for ssid, ap_list in aps.items()] # sort with quantized strength to reduce jumping - networks.sort(key=lambda n: (-n.is_connected, -round(n.strength / 100 * 2), n.ssid.lower())) + networks.sort(key=lambda n: (-n.is_connected, -n.is_saved, -round(n.strength / 100 * 2), n.ssid.lower())) self._networks = networks self._update_ipv4_address() From 5b98ea04ad8fea33dbde3d9d1c22b7ee74dcfd3c Mon Sep 17 00:00:00 2001 From: felsager <76905857+felsager@users.noreply.github.com> Date: Wed, 11 Feb 2026 10:21:12 -0800 Subject: [PATCH 905/910] mpc tuning report: minor improvements (#37167) --- .../mpc_longitudinal_tuning_report.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tools/longitudinal_maneuvers/mpc_longitudinal_tuning_report.py b/tools/longitudinal_maneuvers/mpc_longitudinal_tuning_report.py index 547f45aa2..7623f669c 100644 --- a/tools/longitudinal_maneuvers/mpc_longitudinal_tuning_report.py +++ b/tools/longitudinal_maneuvers/mpc_longitudinal_tuning_report.py @@ -186,12 +186,13 @@ def generate_mpc_tuning_report(): for stop_time in np.arange(4, 14, 1): man = Maneuver( '', - duration=50, + duration=30, initial_speed=30.0, + cruise_values=[30.0, 30.0, 30.0], lead_relevancy=True, initial_distance_lead=60.0, - speed_lead_values=[30.0, 30.0, 0.0, 0.0], - breakpoints=[0., 20., 20 + stop_time, 30 + stop_time], + speed_lead_values=[30.0, 30.0, 0.0], + breakpoints=[0., 5., 5 + stop_time], ) valid, results[stop_time] = man.evaluate() results[stop_time][:,2] = results[stop_time][:,2] - results[stop_time][:,1] @@ -208,12 +209,12 @@ def generate_mpc_tuning_report(): for speed in np.arange(0, 40, 5): man = Maneuver( '', - duration=10, + duration=20, initial_speed=float(speed), + cruise_values=[speed, speed, speed], lead_relevancy=True, initial_distance_lead=desired_follow_distance(speed, speed)/2, speed_lead_values=[speed, speed, speed], - cruise_values=[speed, speed, speed], prob_lead_values=[0.0, 0.0, 1.0], breakpoints=[0., 5.0, 5.01], ) @@ -231,7 +232,7 @@ def generate_mpc_tuning_report(): for speed in np.arange(0, 40, 5): man = Maneuver( '', - duration=50, + duration=60, initial_speed=0.0, lead_relevancy=True, initial_distance_lead=desired_follow_distance(0.0, 0.0), From dae95af1df6299a47778ecc225ee11b5d71eb0c8 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 12 Feb 2026 22:07:05 -0500 Subject: [PATCH 906/910] [TIZI/TICI] ui: override `set_parent_rect` to dynamically update item height (#1689) [TIZI/TICI] ui: override `set_parent_rect` method to update item height dynamically --- system/ui/sunnypilot/widgets/list_view.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/system/ui/sunnypilot/widgets/list_view.py b/system/ui/sunnypilot/widgets/list_view.py index 39e88169d..30a2d992a 100644 --- a/system/ui/sunnypilot/widgets/list_view.py +++ b/system/ui/sunnypilot/widgets/list_view.py @@ -212,6 +212,12 @@ class ListItemSP(ListItem): content_width = int(self._rect.width - style.ITEM_PADDING * 2) self._rect.height = self.get_item_height(self._font, content_width) + def set_parent_rect(self, parent_rect: rl.Rectangle) -> None: + super().set_parent_rect(parent_rect) + if self.description_visible: + content_width = int(self._rect.width - style.ITEM_PADDING * 2) + self._rect.height = self.get_item_height(self._font, content_width) + def get_item_height(self, font: rl.Font, max_width: int) -> float: height = super().get_item_height(font, max_width) From bafbfe19b4d31bff31e0185f99c8f3b958d5613b Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 12 Feb 2026 22:32:15 -0500 Subject: [PATCH 907/910] [TIZI/TICI] ui: individual button states support for `MultipleButtonActionSP` (#1690) * [TIZI/TICI] ui: individual button states support for MultipleButtonActionSP * no magic nums --- .../steering_sub_layouts/mads_settings.py | 8 +++-- system/ui/sunnypilot/widgets/list_view.py | 29 +++++++++++++++++-- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/selfdrive/ui/sunnypilot/layouts/settings/steering_sub_layouts/mads_settings.py b/selfdrive/ui/sunnypilot/layouts/settings/steering_sub_layouts/mads_settings.py index d3f38e04d..3542adf28 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/steering_sub_layouts/mads_settings.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/steering_sub_layouts/mads_settings.py @@ -9,6 +9,7 @@ import pyray as rl from opendbc.sunnypilot.car.tesla.values import TeslaFlagsSP from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.sunnypilot.mads.helpers import MadsSteeringModeOnBrake from openpilot.system.ui.lib.multilang import tr, tr_noop from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.network import NavButton @@ -112,7 +113,7 @@ class MadsSettingsLayout(Widget): if self._mads_limited_settings(): ui_state.params.remove("MadsMainCruiseAllowed") ui_state.params.put_bool("MadsUnifiedEngagementMode", True) - ui_state.params.put("MadsSteeringMode", 2) + ui_state.params.put("MadsSteeringMode", MadsSteeringModeOnBrake.DISENGAGE) self._main_cruise_toggle.action_item.set_enabled(False) self._main_cruise_toggle.action_item.set_state(False) @@ -122,9 +123,9 @@ class MadsSettingsLayout(Widget): self._unified_engagement_toggle.action_item.set_state(True) self._unified_engagement_toggle.set_description("" + DEFAULT_TO_ON + "
" + MADS_UNIFIED_ENGAGEMENT_MODE_BASE_DESC) - self._steering_mode.action_item.set_enabled(False) self._steering_mode.set_description(STATUS_DISENGAGE_ONLY) - self._steering_mode.action_item.set_selected_button(2) + self._steering_mode.action_item.set_selected_button(MadsSteeringModeOnBrake.DISENGAGE) + self._steering_mode.action_item.set_enabled_buttons({MadsSteeringModeOnBrake.DISENGAGE}) else: self._main_cruise_toggle.action_item.set_enabled(True) self._main_cruise_toggle.set_description(MADS_MAIN_CRUISE_BASE_DESC) @@ -133,3 +134,4 @@ class MadsSettingsLayout(Widget): self._unified_engagement_toggle.set_description(MADS_UNIFIED_ENGAGEMENT_MODE_BASE_DESC) self._steering_mode.action_item.set_enabled(True) + self._steering_mode.action_item.set_enabled_buttons(None) diff --git a/system/ui/sunnypilot/widgets/list_view.py b/system/ui/sunnypilot/widgets/list_view.py index 30a2d992a..1dd69c24c 100644 --- a/system/ui/sunnypilot/widgets/list_view.py +++ b/system/ui/sunnypilot/widgets/list_view.py @@ -134,6 +134,10 @@ class MultipleButtonActionSP(MultipleButtonAction): if self.param_key: self.selected_button = int(self.params.get(self.param_key, return_default=True)) self._anim_x: float | None = None + self.enabled_buttons: set[int] | None = None + + def set_enabled_buttons(self, indices: set[int] | None): + self.enabled_buttons = indices def _render(self, rect: rl.Rectangle): @@ -171,10 +175,31 @@ class MultipleButtonActionSP(MultipleButtonAction): text_x = button_x + (self.button_width - text_size.x) / 2 text_y = button_y + (style.BUTTON_HEIGHT - text_size.y) / 2 - rl.draw_text_ex(self._font, text, rl.Vector2(text_x, text_y), 40, 0, text_color) + # Check individual button enabled state + is_button_enabled = self.enabled and (self.enabled_buttons is None or i in self.enabled_buttons) + current_text_color = text_color if is_button_enabled else style.MBC_DISABLED + + rl.draw_text_ex(self._font, text, rl.Vector2(text_x, text_y), 40, 0, current_text_color) def _handle_mouse_release(self, mouse_pos: MousePos): - MultipleButtonAction._handle_mouse_release(self, mouse_pos) + # Override parent method to check individual button enabled state + if not self.enabled: + return + + button_y = self._rect.y + (self._rect.height - style.BUTTON_HEIGHT) / 2 + for i, _ in enumerate(self.buttons): + button_x = self._rect.x + i * self.button_width + button_rect = rl.Rectangle(button_x, button_y, self.button_width, style.BUTTON_HEIGHT) + + if rl.check_collision_point_rec(mouse_pos, button_rect): + # Check if this specific button is enabled + if self.enabled_buttons is not None and i not in self.enabled_buttons: + return + + self.selected_button = i + if self.callback: + self.callback(i) + if self.param_key: self.params.put(self.param_key, self.selected_button) From 58af294ffd527fe9df4c534c8bebd82720de1fc5 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 12 Feb 2026 22:56:18 -0500 Subject: [PATCH 908/910] [TIZI/TICI] ui: Cruise panel (#1691) * init cruise panel * init descriptions * damn descriptions * init SLA sub layout * reorder * icbm it * callback for `Custom ACC Speed Increments` toggle to update dependent UI elements dynamically. * works * more init * more * [TIZI/TICI] ui: individual button states support for MultipleButtonActionSP * less * convert * init properly --------- Co-authored-by: nayan --- .../ui/sunnypilot/layouts/settings/cruise.py | 171 ++++++++++++++++- .../cruise_sub_layouts/speed_limit_policy.py | 65 +++++++ .../speed_limit_settings.py | 178 ++++++++++++++++++ selfdrive/ui/sunnypilot/ui_state.py | 4 + system/ui/sunnypilot/widgets/__init__.py | 18 ++ 5 files changed, 432 insertions(+), 4 deletions(-) create mode 100644 selfdrive/ui/sunnypilot/layouts/settings/cruise_sub_layouts/speed_limit_policy.py create mode 100644 selfdrive/ui/sunnypilot/layouts/settings/cruise_sub_layouts/speed_limit_settings.py diff --git a/selfdrive/ui/sunnypilot/layouts/settings/cruise.py b/selfdrive/ui/sunnypilot/layouts/settings/cruise.py index 9439c588d..671174ac7 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/cruise.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/cruise.py @@ -4,27 +4,190 @@ Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. This file is part of sunnypilot and is licensed under the MIT License. See the LICENSE.md file in the root directory for more details. """ -from openpilot.common.params import Params -from openpilot.system.ui.widgets.scroller_tici import Scroller +from enum import IntEnum + +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.cruise_sub_layouts.speed_limit_settings import SpeedLimitSettingsLayout +from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.system.ui.lib.multilang import tr, tr_noop +from openpilot.system.ui.sunnypilot.widgets.list_view import toggle_item_sp, option_item_sp, simple_button_item_sp from openpilot.system.ui.widgets import Widget +from openpilot.system.ui.widgets.scroller_tici import Scroller + + +class PanelType(IntEnum): + CRUISE = 0 + SLA = 1 + + +ICBM_DESC = tr_noop("When enabled, sunnypilot will attempt to manage the built-in cruise control buttons " + + "by emulating button presses for limited longitudinal control.") +ICMB_UNAVAILABLE = tr_noop("Intelligent Cruise Button Management is currently unavailable on this platform.") +ICMB_UNAVAILABLE_LONG_AVAILABLE = tr_noop("Disable the sunnypilot Longitudinal Control (alpha) toggle to allow Intelligent Cruise Button Management.") +ICMB_UNAVAILABLE_LONG_UNAVAILABLE = tr_noop("sunnypilot Longitudinal Control is the default longitudinal control for this platform.") + +ACC_ENABLED_DESCRIPTION = tr_noop("Enable custom Short & Long press increments for cruise speed increase/decrease.") +ACC_NOLONG_DESCRIPTION = tr_noop("This feature can only be used with sunnypilot longitudinal control enabled.") +ACC_PCMCRUISE_DISABLED_DESCRIPTION = tr_noop("This feature is not supported on this platform due to vehicle limitations.") +ONROAD_ONLY_DESCRIPTION = tr_noop("Start the vehicle to check vehicle compatibility.") class CruiseLayout(Widget): def __init__(self): super().__init__() + self._current_panel = PanelType.CRUISE + self._speed_limit_layout = SpeedLimitSettingsLayout(lambda: self._set_current_panel(PanelType.CRUISE)) - self._params = Params() items = self._initialize_items() self._scroller = Scroller(items, line_separator=True, spacing=0) def _initialize_items(self): + self.icbm_toggle = toggle_item_sp( + title=tr("Intelligent Cruise Button Management (ICBM) (Alpha)"), + description="", + param="IntelligentCruiseButtonManagement") + + self.scc_v_toggle = toggle_item_sp( + title=tr("Smart Cruise Control - Vision"), + description=tr("Use vision path predictions to estimate the appropriate speed to drive through turns ahead."), + param="SmartCruiseControlVision") + + self.scc_m_toggle = toggle_item_sp( + title=tr("Smart Cruise Control - Map"), + description=tr("Use map data to estimate the appropriate speed to drive through turns ahead."), + param="SmartCruiseControlMap") + + self.custom_acc_toggle = toggle_item_sp( + title=tr("Custom ACC Speed Increments"), + description="", + param="CustomAccIncrementsEnabled", + callback=self._on_custom_acc_toggle) + + self.custom_acc_short_increment = option_item_sp( + title=tr("Short Press Increment"), + param="CustomAccShortPressIncrement", + min_value=1, max_value=10, value_change_step=1, + inline=True) + + self.custom_acc_long_increment = option_item_sp( + title=tr("Long Press Increment"), + param="CustomAccLongPressIncrement", + value_map={1: 1, 2: 5, 3: 10}, + min_value=1, max_value=3, value_change_step=1, + inline=True) + + self.sla_settings_button = simple_button_item_sp( + button_text=lambda: tr("Speed Limit"), + button_width=800, + callback=lambda: self._set_current_panel(PanelType.SLA) + ) + + self.dec_toggle = toggle_item_sp( + title=tr("Enable Dynamic Experimental Control"), + description=tr("Enable toggle to allow the model to determine when to use sunnypilot ACC or sunnypilot End to End Longitudinal."), + param="DynamicExperimentalControl") + items = [ + self.icbm_toggle, + self.dec_toggle, + self.scc_v_toggle, + self.scc_m_toggle, + self.custom_acc_toggle, + self.custom_acc_short_increment, + self.custom_acc_long_increment, + self.sla_settings_button, ] return items def _render(self, rect): - self._scroller.render(rect) + if self._current_panel == PanelType.SLA: + self._speed_limit_layout.render(rect) + else: + self._scroller.render(rect) def show_event(self): + self._set_current_panel(PanelType.CRUISE) self._scroller.show_event() + self.icbm_toggle.show_description(True) + self.custom_acc_toggle.show_description(True) + + def _set_current_panel(self, panel: PanelType): + self._current_panel = panel + if panel == PanelType.SLA: + self._speed_limit_layout.show_event() + + def _update_state(self): + super()._update_state() + + if ui_state.CP is not None and ui_state.CP_SP is not None: + has_icbm = ui_state.has_icbm + has_long = ui_state.has_longitudinal_control + + if ui_state.CP_SP.intelligentCruiseButtonManagementAvailable and not has_long: + self.icbm_toggle.action_item.set_enabled(ui_state.is_offroad()) + self.icbm_toggle.set_description(tr(ICBM_DESC)) + else: + ui_state.params.remove("IntelligentCruiseButtonManagement") + self.icbm_toggle.action_item.set_enabled(False) + + long_desc = ICMB_UNAVAILABLE + if has_long: + if ui_state.CP.alphaLongitudinalAvailable: + long_desc += " " + ICMB_UNAVAILABLE_LONG_AVAILABLE + else: + long_desc += " " + ICMB_UNAVAILABLE_LONG_UNAVAILABLE + + new_desc = "" + tr(long_desc) + "\n\n" + tr(ICBM_DESC) + if self.icbm_toggle.description != new_desc: + self.icbm_toggle.set_description(new_desc) + self.icbm_toggle.show_description(True) + + if has_long or has_icbm: + self.custom_acc_toggle.action_item.set_enabled(((has_long and not ui_state.CP.pcmCruise) or has_icbm) and ui_state.is_offroad()) + self.dec_toggle.action_item.set_enabled(has_long) + self.scc_v_toggle.action_item.set_enabled(True) + self.scc_m_toggle.action_item.set_enabled(True) + else: + ui_state.params.remove("CustomAccIncrementsEnabled") + ui_state.params.remove("DynamicExperimentalControl") + ui_state.params.remove("SmartCruiseControlVision") + ui_state.params.remove("SmartCruiseControlMap") + self.custom_acc_toggle.action_item.set_enabled(False) + self.dec_toggle.action_item.set_enabled(False) + self.scc_v_toggle.action_item.set_enabled(False) + self.scc_m_toggle.action_item.set_enabled(False) + + else: + has_icbm = has_long = False + self.icbm_toggle.action_item.set_enabled(False) + self.icbm_toggle.set_description(tr(ONROAD_ONLY_DESCRIPTION)) + + show_custom_acc_desc = False + + if ui_state.is_offroad(): + new_custom_acc_desc = tr(ONROAD_ONLY_DESCRIPTION) + show_custom_acc_desc = True + else: + if has_long or has_icbm: + if has_long and ui_state.CP.pcmCruise: + new_custom_acc_desc = tr(ACC_PCMCRUISE_DISABLED_DESCRIPTION) + show_custom_acc_desc = True + else: + new_custom_acc_desc = tr(ACC_ENABLED_DESCRIPTION) + else: + new_custom_acc_desc = tr(ACC_NOLONG_DESCRIPTION) + show_custom_acc_desc = True + self.custom_acc_toggle.action_item.set_state(False) + + if self.custom_acc_toggle.description != new_custom_acc_desc: + self.custom_acc_toggle.set_description(new_custom_acc_desc) + if show_custom_acc_desc: + self.custom_acc_toggle.show_description(True) + + self._on_custom_acc_toggle(self.custom_acc_toggle.action_item.get_state()) + + def _on_custom_acc_toggle(self, state): + self.custom_acc_short_increment.set_visible(state) + self.custom_acc_long_increment.set_visible(state) + self.custom_acc_short_increment.action_item.set_enabled(self.custom_acc_toggle.action_item.enabled) + self.custom_acc_long_increment.action_item.set_enabled(self.custom_acc_toggle.action_item.enabled) diff --git a/selfdrive/ui/sunnypilot/layouts/settings/cruise_sub_layouts/speed_limit_policy.py b/selfdrive/ui/sunnypilot/layouts/settings/cruise_sub_layouts/speed_limit_policy.py new file mode 100644 index 000000000..e6846744a --- /dev/null +++ b/selfdrive/ui/sunnypilot/layouts/settings/cruise_sub_layouts/speed_limit_policy.py @@ -0,0 +1,65 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +from collections.abc import Callable + +import pyray as rl +from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.system.ui.lib.multilang import tr +from openpilot.system.ui.sunnypilot.widgets.list_view import multiple_button_item_sp +from openpilot.system.ui.widgets.network import NavButton +from openpilot.system.ui.widgets.scroller_tici import Scroller +from openpilot.system.ui.widgets import Widget +from openpilot.system.ui.sunnypilot.widgets import get_highlighted_description + +SPEED_LIMIT_POLICY_BUTTONS = [tr("Car Only"), tr("Map Only"), tr("Car First"), tr("Map First"), tr("Combined")] + +SPEED_LIMIT_POLICY_DESCRIPTIONS = [ + tr("Car Only: Use Speed Limit data only from Car"), + tr("Map Only: Use Speed Limit data only from OpenStreetMaps"), + tr("Car First: Use Speed Limit data from Car if available, else use from OpenStreetMaps"), + tr("Map First: Use Speed Limit data from OpenStreetMaps if available, else use from Car"), + tr("Combined: Use combined Speed Limit data from Car & OpenStreetMaps") +] + + +class SpeedLimitPolicyLayout(Widget): + def __init__(self, back_btn_callback: Callable): + super().__init__() + self._back_button = NavButton(tr("Back")) + self._back_button.set_click_callback(back_btn_callback) + + items = self._initialize_items() + self._scroller = Scroller(items, line_separator=False, spacing=0) + + def _initialize_items(self): + self._speed_limit_policy = multiple_button_item_sp( + title=lambda: tr("Speed Limit Source"), + description=self._get_policy_description, + buttons=SPEED_LIMIT_POLICY_BUTTONS, + param="SpeedLimitPolicy", + button_width=250, + ) + + items = [ + self._speed_limit_policy + ] + return items + + @staticmethod + def _get_policy_description(): + return get_highlighted_description(ui_state.params, "SpeedLimitPolicy", SPEED_LIMIT_POLICY_DESCRIPTIONS) + + def _render(self, rect): + self._back_button.set_position(self._rect.x, self._rect.y + 20) + self._back_button.render() + + content_rect = rl.Rectangle(rect.x, rect.y + self._back_button.rect.height + 40, rect.width, rect.height - self._back_button.rect.height - 40) + self._scroller.render(content_rect) + + def show_event(self): + self._scroller.show_event() + self._speed_limit_policy.show_description(True) diff --git a/selfdrive/ui/sunnypilot/layouts/settings/cruise_sub_layouts/speed_limit_settings.py b/selfdrive/ui/sunnypilot/layouts/settings/cruise_sub_layouts/speed_limit_settings.py new file mode 100644 index 000000000..c14330d9a --- /dev/null +++ b/selfdrive/ui/sunnypilot/layouts/settings/cruise_sub_layouts/speed_limit_settings.py @@ -0,0 +1,178 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +from collections.abc import Callable +from enum import IntEnum + +import pyray as rl +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.cruise_sub_layouts.speed_limit_policy import SpeedLimitPolicyLayout +from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit.common import Mode as SpeedLimitMode +from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit.common import OffsetType as SpeedLimitOffsetType +from openpilot.system.ui.lib.multilang import tr +from openpilot.system.ui.sunnypilot.widgets import get_highlighted_description +from openpilot.system.ui.sunnypilot.widgets.list_view import multiple_button_item_sp, option_item_sp, simple_button_item_sp, LineSeparatorSP +from openpilot.system.ui.widgets import Widget +from openpilot.system.ui.widgets.network import NavButton +from openpilot.system.ui.widgets.scroller_tici import Scroller + +SPEED_LIMIT_MODE_BUTTONS = [tr("Off"), tr("Info"), tr("Warning"), tr("Assist")] +SPEED_LIMIT_OFFSET_TYPE_BUTTONS = [tr("None"), tr("Fixed"), tr("%")] + +SPEED_LIMIT_MODE_DESCRIPTIONS = [ + tr("Off: Disables the Speed Limit functions."), + tr("Information: Displays the current road's speed limit."), + tr("Warning: Provides a warning when exceeding the current road's speed limit."), + tr("Assist: Adjusts the vehicle's cruise speed based on the current road's speed limit when operating the +/- buttons."), +] + +SPEED_LIMIT_OFFSET_DESCRIPTIONS = [ + tr("None: No Offset"), + tr("Fixed: Adds a fixed offset [Speed Limit + Offset]"), + tr("Percent: Adds a percent offset [Speed Limit + (Offset % Speed Limit)]"), +] + + +class PanelType(IntEnum): + SETTINGS = 0 + POLICY = 1 + + +class SpeedLimitSettingsLayout(Widget): + def __init__(self, back_btn_callback: Callable): + super().__init__() + self._current_panel = PanelType.SETTINGS + + self._back_button = NavButton(tr("Back")) + self._back_button.set_click_callback(back_btn_callback) + + self._policy_layout = SpeedLimitPolicyLayout(lambda: self._set_current_panel(PanelType.SETTINGS)) + + items = self._initialize_items() + self._scroller = Scroller(items, line_separator=False, spacing=0) + + def _initialize_items(self): + self._speed_limit_mode = multiple_button_item_sp( + title=lambda: tr("Speed Limit"), + description=self._get_mode_description, + buttons=SPEED_LIMIT_MODE_BUTTONS, + param="SpeedLimitMode", + button_width=380, + ) + + self._source_button = simple_button_item_sp( + button_text=lambda: tr("Customize Source"), + button_width=720, + callback=lambda: self._set_current_panel(PanelType.POLICY) + ) + + self._speed_limit_offset_type = multiple_button_item_sp( + title=lambda: tr("Speed Limit Offset"), + description="", + buttons=SPEED_LIMIT_OFFSET_TYPE_BUTTONS, + param="SpeedLimitOffsetType", + button_width=450, + ) + + self._speed_limit_value_offset = option_item_sp( + title="", + param="SpeedLimitValueOffset", + min_value=-30, + max_value=30, + description=self._get_offset_description, + label_callback=self._get_offset_label, + ) + + items = [ + self._speed_limit_mode, + LineSeparatorSP(40), + self._source_button, + LineSeparatorSP(40), + self._speed_limit_offset_type, + self._speed_limit_value_offset + ] + return items + + def _set_current_panel(self, panel: PanelType): + self._current_panel = panel + if panel == PanelType.POLICY: + self._policy_layout.show_event() + + @staticmethod + def _get_mode_description(): + return get_highlighted_description(ui_state.params, "SpeedLimitMode", SPEED_LIMIT_MODE_DESCRIPTIONS) + + @staticmethod + def _get_offset_description(): + return get_highlighted_description(ui_state.params, "SpeedLimitOffsetType", SPEED_LIMIT_OFFSET_DESCRIPTIONS) + + @staticmethod + def _get_offset_label(value): + offset_type = int(ui_state.params.get("SpeedLimitOffsetType", return_default=True)) + unit = tr("km/h") if ui_state.is_metric else tr("mph") + + if offset_type == int(SpeedLimitOffsetType.percentage): + return f"{value}%" + elif offset_type == int(SpeedLimitOffsetType.fixed): + return f"{value} {unit}" + return str(value) + + def _update_state(self): + super()._update_state() + + speed_limit_mode_param = ui_state.params.get("SpeedLimitMode", return_default=True) + if ui_state.CP is not None and ui_state.CP_SP is not None: + brand = ui_state.CP.brand + has_long = ui_state.has_longitudinal_control + has_icbm = ui_state.has_icbm + + """ + Speed Limit Assist is available when: + - has_long or has_icbm, and + - is not a release branch or not a disallowed brand, and + - is not always disallwed + """ + sla_disallow_in_release = brand == "tesla" and ui_state.is_sp_release + sla_always_disallow = brand == "rivian" + sla_available = (has_long or has_icbm) and not sla_disallow_in_release and not sla_always_disallow + + if not sla_available and speed_limit_mode_param == int(SpeedLimitMode.assist): + ui_state.params.put("SpeedLimitMode", int(SpeedLimitMode.warning)) + + else: + sla_available = False + + if not sla_available: + self._speed_limit_mode.action_item.set_enabled_buttons({ + int(SpeedLimitMode.off), + int(SpeedLimitMode.information), + int(SpeedLimitMode.warning), + }) + else: + self._speed_limit_mode.action_item.set_enabled_buttons(None) + + offset_type = ui_state.params.get("SpeedLimitOffsetType", return_default=True) + self._speed_limit_value_offset.set_visible(offset_type != int(SpeedLimitOffsetType.off)) + + def _render(self, rect): + if self._current_panel == PanelType.POLICY: + self._policy_layout.render(rect) + return + + self._back_button.set_position(self._rect.x, self._rect.y + 20) + self._back_button.render() + + content_rect = rl.Rectangle(rect.x, rect.y + self._back_button.rect.height + 40, rect.width, rect.height - self._back_button.rect.height - 40) + self._scroller.render(content_rect) + + def show_event(self): + self._current_panel = PanelType.SETTINGS + self._scroller.show_event() + self._speed_limit_mode.show_description(True) + + def hide_event(self): + self._current_panel = PanelType.SETTINGS + self._scroller.hide_event() diff --git a/selfdrive/ui/sunnypilot/ui_state.py b/selfdrive/ui/sunnypilot/ui_state.py index 369909812..5a2f722fe 100644 --- a/selfdrive/ui/sunnypilot/ui_state.py +++ b/selfdrive/ui/sunnypilot/ui_state.py @@ -39,6 +39,9 @@ class UIStateSP: self.onroad_brightness_timer: int = 0 self.custom_interactive_timeout: int = self.params.get("InteractivityTimeout", return_default=True) self.reset_onroad_sleep_timer() + self.CP_SP: custom.CarParamsSP | None = None + self.has_icbm: bool = False + self.is_sp_release: bool = self.params.get_bool("IsReleaseSpBranch") def update(self) -> None: if self.sunnylink_enabled: @@ -121,6 +124,7 @@ class UIStateSP: CP_SP_bytes = self.params.get("CarParamsSPPersistent") if CP_SP_bytes is not None: self.CP_SP = messaging.log_from_bytes(CP_SP_bytes, custom.CarParamsSP) + self.has_icbm = self.CP_SP.intelligentCruiseButtonManagementAvailable and self.params.get_bool("IntelligentCruiseButtonManagement") self.active_bundle = self.params.get("ModelManager_ActiveBundle") self.blindspot = self.params.get_bool("BlindSpot") self.chevron_metrics = self.params.get("ChevronInfo") diff --git a/system/ui/sunnypilot/widgets/__init__.py b/system/ui/sunnypilot/widgets/__init__.py index e69de29bb..96865e604 100644 --- a/system/ui/sunnypilot/widgets/__init__.py +++ b/system/ui/sunnypilot/widgets/__init__.py @@ -0,0 +1,18 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" + + +def get_highlighted_description(params, param_name: str, descriptions: list[str]) -> str: + index = int(params.get(param_name, return_default=True)) + lines = [] + for i, desc in enumerate(descriptions): + if i == index: + lines.append(f"{desc}") + else: + lines.append(f"{desc}") + + return "
".join(lines) From 33a26ba373fa80bfa9fb9a52f842dc1b79094658 Mon Sep 17 00:00:00 2001 From: Nayan Date: Thu, 12 Feb 2026 23:09:24 -0500 Subject: [PATCH 909/910] ui: better wake mode support (#1578) * wake mode support * use ui_state.params * better * cleanup * fix --------- Co-authored-by: Jason Wen --- selfdrive/ui/sunnypilot/ui_state.py | 11 +++++------ selfdrive/ui/ui_state.py | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/selfdrive/ui/sunnypilot/ui_state.py b/selfdrive/ui/sunnypilot/ui_state.py index 5a2f722fe..4d26e138b 100644 --- a/selfdrive/ui/sunnypilot/ui_state.py +++ b/selfdrive/ui/sunnypilot/ui_state.py @@ -142,15 +142,14 @@ class UIStateSP: self.torque_bar = self.params.get_bool("TorqueBar") self.true_v_ego_ui = self.params.get_bool("TrueVEgoUI") self.turn_signals = self.params.get_bool("ShowTurnSignals") + self.boot_offroad_mode = self.params.get("DeviceBootMode", return_default=True) class DeviceSP: - def __init__(self): - self._params = Params() - - def _set_awake(self, on: bool): - if on and self._params.get("DeviceBootMode", return_default=True) == 1: - self._params.put_bool("OffroadMode", True) + @staticmethod + def _set_awake(on: bool, _ui_state): + if _ui_state.boot_offroad_mode == 1 and not on: + _ui_state.params.put_bool("OffroadMode", True) @staticmethod def set_onroad_brightness(_ui_state, awake: bool, cur_brightness: float) -> float: diff --git a/selfdrive/ui/ui_state.py b/selfdrive/ui/ui_state.py index a9127ac3c..f7a5d44d9 100644 --- a/selfdrive/ui/ui_state.py +++ b/selfdrive/ui/ui_state.py @@ -299,7 +299,7 @@ class Device(DeviceSP): def _set_awake(self, on: bool): if on != self._awake: - DeviceSP._set_awake(self, on) + DeviceSP._set_awake(on, ui_state) self._awake = on cloudlog.debug(f"setting display power {int(on)}") HARDWARE.set_display_power(on) From b3878fb211f3a3a03acd061096da049cae17f6c3 Mon Sep 17 00:00:00 2001 From: Christopher Haucke <132518562+CHaucke89@users.noreply.github.com> Date: Thu, 12 Feb 2026 23:33:38 -0500 Subject: [PATCH 910/910] Pause Lateral Control with Blinker: Post-Blinker Delay (#1567) * Delay lateral reengagement * UI elements * Add tests * Update title and description * Update params_metadata * Didn't mean to pass this to int() * Keep sentry happy * Title and description update * always 100 hz --------- Co-authored-by: Jason Wen --- common/params_keys.h | 1 + .../ui/sunnypilot/layouts/settings/steering.py | 11 +++++++++++ .../controls/lib/blinker_pause_lateral.py | 16 +++++++++++++--- .../lib/tests/test_blinker_pause_lateral.py | 14 ++++++++++++++ sunnypilot/sunnylink/params_metadata.json | 4 ++++ 5 files changed, 43 insertions(+), 3 deletions(-) diff --git a/common/params_keys.h b/common/params_keys.h index f2a63ec1b..dba074226 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -137,6 +137,7 @@ inline static std::unordered_map keys = { {"ApiCache_DriveStats", {PERSISTENT, JSON}}, {"AutoLaneChangeBsmDelay", {PERSISTENT | BACKUP, BOOL, "0"}}, {"AutoLaneChangeTimer", {PERSISTENT | BACKUP, INT, "0"}}, + {"BlinkerLateralReengageDelay", {PERSISTENT | BACKUP, INT, "0"}}, // seconds {"BlinkerMinLateralControlSpeed", {PERSISTENT | BACKUP, INT, "20"}}, // MPH or km/h {"BlinkerPauseLateralControl", {PERSISTENT | BACKUP, INT, "0"}}, {"Brightness", {PERSISTENT | BACKUP, INT, "0"}}, diff --git a/selfdrive/ui/sunnypilot/layouts/settings/steering.py b/selfdrive/ui/sunnypilot/layouts/settings/steering.py index f82c4097c..14d840138 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/steering.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/steering.py @@ -72,6 +72,15 @@ class SteeringLayout(Widget): description="", label_callback=lambda speed: f'{speed} {"km/h" if ui_state.is_metric else "mph"}', ) + self._blinker_reengage_delay = option_item_sp( + param="BlinkerLateralReengageDelay", + title=lambda: tr("Post-Blinker Delay"), + min_value=0, + max_value=10, + value_change_step=1, + description=lambda: tr("Delay before lateral control resumes after the turn signal ends."), + label_callback=lambda delay: f'{delay} {"s"}' + ) self._torque_control_toggle = toggle_item_sp( param="EnforceTorqueControl", title=lambda: tr("Enforce Torque Lateral Control"), @@ -96,6 +105,7 @@ class SteeringLayout(Widget): LineSeparatorSP(40), self._blinker_control_toggle, self._blinker_control_options, + self._blinker_reengage_delay, LineSeparatorSP(40), self._torque_control_toggle, self._torque_customization_button, @@ -128,6 +138,7 @@ class SteeringLayout(Widget): self._mads_toggle.action_item.set_enabled(ui_state.is_offroad()) self._mads_settings_button.action_item.set_enabled(ui_state.is_offroad() and self._mads_toggle.action_item.get_state()) self._blinker_control_options.set_visible(self._blinker_control_toggle.action_item.get_state()) + self._blinker_reengage_delay.set_visible(self._blinker_control_toggle.action_item.get_state()) enforce_torque_enabled = self._torque_control_toggle.action_item.get_state() nnlc_enabled = self._nnlc_toggle.action_item.get_state() diff --git a/sunnypilot/selfdrive/controls/lib/blinker_pause_lateral.py b/sunnypilot/selfdrive/controls/lib/blinker_pause_lateral.py index 2f27e7e42..98757cd53 100644 --- a/sunnypilot/selfdrive/controls/lib/blinker_pause_lateral.py +++ b/sunnypilot/selfdrive/controls/lib/blinker_pause_lateral.py @@ -17,13 +17,16 @@ class BlinkerPauseLateral: self.enabled = self.params.get_bool("BlinkerPauseLateralControl") self.is_metric = self.params.get_bool("IsMetric") self.min_speed = 0 + self.reengage_delay = 0 + self.blinker_off_timer = 0.0 def get_params(self) -> None: self.enabled = self.params.get_bool("BlinkerPauseLateralControl") self.is_metric = self.params.get_bool("IsMetric") - self.min_speed = self.params.get("BlinkerMinLateralControlSpeed") + self.min_speed = self.params.get("BlinkerMinLateralControlSpeed", return_default=True) + self.reengage_delay = self.params.get("BlinkerLateralReengageDelay", return_default=True) - def update(self, CS: car.CarState) -> bool: + def update(self, CS: car.CarState, DT_CTRL: float = 0.01) -> bool: if not self.enabled: return False @@ -31,4 +34,11 @@ class BlinkerPauseLateral: speed_factor = CV.KPH_TO_MS if self.is_metric else CV.MPH_TO_MS min_speed_ms = self.min_speed * speed_factor - return bool(one_blinker and CS.vEgo < min_speed_ms) + below_speed = CS.vEgo < min_speed_ms + + if one_blinker and below_speed: + self.blinker_off_timer = self.reengage_delay + elif self.blinker_off_timer > 0: + self.blinker_off_timer -= DT_CTRL + + return bool((one_blinker and below_speed) or self.blinker_off_timer > 0) diff --git a/sunnypilot/selfdrive/controls/lib/tests/test_blinker_pause_lateral.py b/sunnypilot/selfdrive/controls/lib/tests/test_blinker_pause_lateral.py index ca8f16fe7..b547ea96b 100644 --- a/sunnypilot/selfdrive/controls/lib/tests/test_blinker_pause_lateral.py +++ b/sunnypilot/selfdrive/controls/lib/tests/test_blinker_pause_lateral.py @@ -20,6 +20,8 @@ class TestBlinkerPauseLateral: self.blinker_pause_lateral.enabled = True self.blinker_pause_lateral.is_metric = False self.blinker_pause_lateral.min_speed = 20 # MPH + self.blinker_pause_lateral.reengage_delay = 0 + self.blinker_pause_lateral.blinker_off_timer = 0.0 self.CS = car.CarState.new_message() self.CS.vEgo = 0 @@ -46,6 +48,18 @@ class TestBlinkerPauseLateral: } self._test_should_blinker_pause_lateral(expected_results) + def test_reengage_delay(self): + self.blinker_pause_lateral.reengage_delay = 2 # seconds + self.CS.vEgo = 4.5 # ~10 MPH + + expected_results = { + (False, False): True, + (True, False): True, + (False, True): True, + (True, True): False + } + self._test_should_blinker_pause_lateral(expected_results) + def test_above_min_speed_blinker(self): self.CS.vEgo = 13.4 # ~30 MPH diff --git a/sunnypilot/sunnylink/params_metadata.json b/sunnypilot/sunnylink/params_metadata.json index c0c84eac0..ad94ad35f 100644 --- a/sunnypilot/sunnylink/params_metadata.json +++ b/sunnypilot/sunnylink/params_metadata.json @@ -93,6 +93,10 @@ "title": "[TIZI/TICI only] Blind Spot Detection", "description": "Enabling this will display warnings when a vehicle is detected in your blind spot as long as your car has BSM supported." }, + "BlinkerLateralReengageDelay": { + "title": "Post-Blinker Delay", + "description": "Delay before lateral control resumes after the turn signal ends." + }, "BlinkerMinLateralControlSpeed": { "title": "Blinker Min Lateral Control Speed", "description": ""